Chinaunix首页 | 论坛 | 博客
  • 博客访问: 535868
  • 博文数量: 86
  • 博客积分: 1076
  • 博客等级: 准尉
  • 技术积分: 1018
  • 用 户 组: 普通用户
  • 注册时间: 2011-11-02 19:15
文章分类

全部博文(86)

文章存档

2013年(15)

2012年(69)

2011年(2)

分类: LINUX

2013-02-18 17:28:06

前段时间尝试在XBMC的框架中添加对Airplay Screen Mirror的功能,有关Airplay的协议可以参考(当然是第三方破解的)

本文指针对AAC-ELD音频的解析做一定说明,对于Airplay Screen Mirror本身暂不扩展。


如果是普通的AAC音频,自然可以使用FAAD的库进行解码,或者直接使用ffmpeg,网上有很多的资料,不过FAAD解不了AAC-ELD等级的AAC音频。

但问题有两点,第一是流媒体的格式的音频,也就是没有文件封装格式,流媒体本身是通过RTSP来传递一些配置信息,再利用RTP来传输数据。

第二,AAC-ELD的编码格式,目前能参考的资料不多,不过最新版的ffmepg(>ffmpeg-1.0)或者libav,已经支持了AAC-ELD格式的编码,可以

查看libavcodec目录下是否有libfdk-aacenc.c文件,其实本质是添加了对libfdk-aac音频库的支持,不过遗憾的是没有提供解码的代码。


其实,有关libfdk-aac解码的代码是有的,只是ffmepg没有将其吸纳进来,可以参考github上第三方修改的libav库,地址是

同时fdk-aac的库地址是

可以看出libavcodec目录下已经有了文件,网友可以自行下载编译,找一个音频文件,先将其转换成AAC-ELD格式的音频,然后在尝试解码。

编码的命令可以参考这种格式

ffmpeg -y -i test.wav -c libfdk_aac -profile:a aac_eld test.mp4

(注意:ffmpeg对于编码一般都采用第三方的库,例如x264等,但解码,ffmpeg本身会调用自己重写的解码代码,所以不一定就能够调用到libfdk-aac来解码

AAC-ELD格式的音频,我当时是修改了ffmpeg的注册模块,强制利用fdk-aac来解码aac的音频来验证的)


以上解决的是文件封装的AAC-ELD音频,可以很容易验证,但对于RTP过来的裸音频数据,就要了解一下AAC的知识了,其中最最重要的是MPEG标准中规定,AAC

格式的音频需要一个AudioSpecificConfig配置,如果你上面生成了mp4文件,就可以利用mp4info.exe这个工具查看esds字段(路径大概在trac->media->

->minf->stbl->stsd->m4a->esds),然后可以参照esds的语法查找相应的AudioSpecificConfig字段。

下面贴一段参考代码,直接利用的fdk-aac的库,在ubuntu上运行的代码

(大概的流程是,有个线程在往队列里面填充数据,然后通过SDL播放,SDL的回调函数会去队列中取音频数据,然后调用fdk-aac解码,然后播放)


/*
 * decode AAC-ELD audio data from mac by XBMC, and play it by SDL
 *
 * modify:
 * 2012-10-31	first version (ffmpeg tutorial03.c)
 *
 */

#include 
#include 
#include 
#include 
#include 
#include "decodeAAC.h"

#ifdef __MINGW32__
#undef main /* Prevents SDL from overriding main() */
#endif

typedef unsigned char u8;
typedef unsigned short int u16;
typedef unsigned int u32;

/* ---------------------------------------------------------- */
/*			enable file save, test pcm source				  */
/* ---------------------------------------------------------- */
//#define ENABLE_PCM_SAVE

#ifdef ENABLE_PCM_SAVE
FILE *pout = NULL;
#endif
/* ---------------------------------------------------------- */
/*			next n lines is libfdk-aac config				  */
/* ---------------------------------------------------------- */
static int fdk_flags = 0;

/* period size 480 samples */
#define N_SAMPLE 480
/* ASC config binary data */
UCHAR eld_conf[] = { 0xF8, 0xE8, 0x50, 0x00 };
UCHAR *conf[] = { eld_conf };					//TODO just for aac eld config
static UINT conf_len = sizeof(eld_conf);

static HANDLE_AACDECODER phandle = NULL;
static TRANSPORT_TYPE transportFmt = 0;			//raw data format
static UINT nrOfLayers = 1;						//only one layer here
static CStreamInfo *aac_stream_info = NULL;

static int pcm_pkt_size = 4 * N_SAMPLE;

/* ---------------------------------------------------------- */
/*			AAC data and queue list struct definition 		  */
/* ---------------------------------------------------------- */
static int quit = 0;

#define FDK_MAX_AUDIO_FRAME_SIZE	192000		//1 second of 48khz 32bit audio
#define SDL_AUDIO_BUFFER_SIZE 4 * N_SAMPLE
#define PCM_RATE		44100
#define PCM_CHANNEL		2

typedef struct AACPacket {
	unsigned char *data;
	unsigned int size;
} AACPacket;

typedef struct AACPacketList {
	AACPacket pkt;
	struct AACPacketList *next;
} AACPacketList;

typedef struct PacketQueue {
	AACPacketList *first_pkt, *last_pkt;
	int nb_packets;
	int size;
	SDL_mutex *mutex;
	SDL_cond *cond;
} PacketQueue;

static PacketQueue audioq;
/* ---------------------------------------------------------- */
/*				local memcpy malloc							  */
/* ---------------------------------------------------------- */
/* for local memcpy malloc */
#define AAC_BUFFER_SIZE 1024 * 1024
#define THRESHOLD		1 * 1024

static u8 repo[AAC_BUFFER_SIZE] = {0};
static u8 *repo_ptr = NULL;
/*
 * init mem repo
 */
static void init_mem_repo(void)
{
	repo_ptr = repo;
}

/*
 * alloc input pkt buffer from input_aac_data[]
 */
static void *alloc_pkt_buf(void)
{
	int space;

	space = AAC_BUFFER_SIZE - (repo_ptr - repo);

	if (space < THRESHOLD) {
		repo_ptr = repo;
		return repo;
	}
	
	return repo_ptr;
}

static void set_pkt_size(int size)
{
	repo_ptr += size;
}
/* ---------------------------------------------------------- */

static void packet_queue_init(PacketQueue *q)
{
	memset(q, 0, sizeof(PacketQueue));
	q->mutex = SDL_CreateMutex();
	q->cond = SDL_CreateCond();
}

static int fdk_dup_packet(AACPacket *pkt)
{
	u8 *repo_ptr;

	repo_ptr = alloc_pkt_buf();
	memcpy(repo_ptr, pkt->data, pkt->size);
	pkt->data = repo_ptr;

	set_pkt_size(pkt->size);

	return 0;
}

static int packet_queue_put(PacketQueue *q, AACPacket *pkt)
{
	//fprintf(stderr, "p");
	AACPacketList *pkt1;
	
	/* memcpy data from xbmc */
	fdk_dup_packet(pkt);

	pkt1 = malloc(sizeof(AACPacketList));
	if (!pkt1)
		return -1;
	pkt1->pkt = *pkt;
	pkt1->next = NULL;

	SDL_LockMutex(q->mutex);

	if (!q->last_pkt)
		q->first_pkt = pkt1;
	else
		q->last_pkt->next = pkt1;
	q->last_pkt = pkt1;
	q->nb_packets++;
	q->size += pkt1->pkt.size;

	SDL_CondSignal(q->cond);
	SDL_UnlockMutex(q->mutex);

	return 0;
}

/*
 * called by external, aac data input queue
 */
int decode_copy_aac_data(u8 *data, int size)
{
	AACPacket pkt;

	pkt.data = data;
	pkt.size = size;

	packet_queue_put(&audioq, &pkt);

	return 0;
}

static int packet_queue_get(PacketQueue *q, AACPacket *pkt, int block)
{
	//fprintf(stderr, "g");
	AACPacketList *pkt1;
	int ret;

	SDL_LockMutex(q->mutex);

	for (;;) {
		if (quit) {
			ret = -1;
			break;
		}

		pkt1 = q->first_pkt;
		if (pkt1) {
			q->first_pkt = pkt1->next;
			if (!q->first_pkt)
				q->last_pkt = NULL;
			q->nb_packets--;
			q->size -= pkt1->pkt.size;
			*pkt = pkt1->pkt;
			free(pkt1);
			ret = 1;
			break;
		} else if (!block) {
			ret = 0;
			break;
		} else {
			SDL_CondWait(q->cond, q->mutex);
		}
	}

	SDL_UnlockMutex(q->mutex);

	//fprintf(stderr, "o");
	return ret;
}

/*
 * decoding AAC format audio data by libfdk_aac
 */
int fdk_decode_audio(INT_PCM *output_buf, int *output_size, u8 *buffer, int size)
{
	int ret = 0;
	int pkt_size = size;
	UINT valid_size = size;
	UCHAR *input_buf[1] = {buffer};

	/* step 1 -> fill aac_data_buf to decoder's internal buf */
	ret = aacDecoder_Fill(phandle, input_buf, &pkt_size, &valid_size);
	if (ret != AAC_DEC_OK) {
		fprintf(stderr, "Fill failed: %x\n", ret);
		*output_size  = 0;
		return 0;
	}

	/* step 2 -> call decoder function */
	ret = aacDecoder_DecodeFrame(phandle, output_buf, pcm_pkt_size, fdk_flags);
	if (ret == AAC_DEC_NOT_ENOUGH_BITS) {
		fprintf(stderr, "not enough\n");
		*output_size  = 0;
		/*
		 * TODO FIXME
		 * if not enough, get more data
		 *
		 */
	}
	if (ret != AAC_DEC_OK) {
		fprintf(stderr, "aacDecoder_DecodeFrame : 0x%x\n", ret);
		*output_size  = 0;
		return 0;
	}
	
	*output_size = pcm_pkt_size;

#ifdef ENABLE_PCM_SAVE
	fwrite((u8 *)output_buf, 1, pcm_pkt_size, pout);	
#endif
	/* return aac decode size */
	return (size - valid_size);
}

int audio_decode_frame(uint8_t *audio_buf, int buf_size)
{
	static AACPacket pkt;
	static uint8_t *audio_pkt_data = NULL;
	static int audio_pkt_size = 0;

	int len1, data_size;

	for (;;) {
		while (audio_pkt_size > 0) {
			data_size = buf_size;
			len1 = fdk_decode_audio((INT_PCM *)audio_buf, &data_size, 
					audio_pkt_data, audio_pkt_size);
			if (len1 < 0) {
				/* if error, skip frame */
				audio_pkt_size = 0;
				break;
			}
			audio_pkt_data += len1;
			audio_pkt_size -= len1;
			if (data_size <= 0) {
				/* No data yet, get more frames */
				continue;
			}
			/* We have data, return it and come back for more later */
			//fprintf(stderr, "\ndata size = %d\n", data_size);
			return data_size;
		}
		/* FIXME 
		 * add by juguofeng 
		 * only no nead in this code, because we alloc a memcpy ourselves
		 */
		//if(pkt.data)
		//	free(pkt.data);

		if (quit) {
			return -1;
		}

		if (packet_queue_get(&audioq, &pkt, 1) < 0) {
			return -1;
		}
		audio_pkt_data = pkt.data;
		audio_pkt_size = pkt.size;
	}
}

void audio_callback(void *userdata, Uint8 *stream, int len)
{
	int len1, audio_size;

	static uint8_t audio_buf[(FDK_MAX_AUDIO_FRAME_SIZE * 3) / 2];
	static unsigned int audio_buf_size = 0;
	static unsigned int audio_buf_index = 0;
	
	//fprintf(stderr, "callback len = %d\n", len);

	while (len > 0) {
		if (audio_buf_index >= audio_buf_size) {
			//fprintf(stderr, "c");
			/* We have already sent all our data; get more */
			audio_size = audio_decode_frame(audio_buf, sizeof(audio_buf));
			if (audio_size < 0) {
				/* If error, output silence */
				audio_buf_size = pcm_pkt_size;		 // arbitrary?
				memset(audio_buf, 0, audio_buf_size);
			} else {
				audio_buf_size = audio_size;
			}
			audio_buf_index = 0;
		}
		len1 = audio_buf_size - audio_buf_index;
		if (len1 > len)
			len1 = len;
		memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);
		len -= len1;
		stream += len1;
		audio_buf_index += len1;
	}
}

/*
 * init fdk decoder
 */
void init_fdk_decoder(void)
{
	int ret = 0;

	phandle = aacDecoder_Open(transportFmt, nrOfLayers);
	if (phandle == NULL) {
		printf("aacDecoder open faild!\n");
		exit(0);
	}

	printf("conf_len = %d\n", conf_len);
	ret = aacDecoder_ConfigRaw(phandle, conf, &conf_len);
	if (ret != AAC_DEC_OK) {
		fprintf(stderr, "Unable to set configRaw\n");
		exit(0);
	}

	aac_stream_info = aacDecoder_GetStreamInfo(phandle);
	if (aac_stream_info == NULL) {
		printf("aacDecoder_GetStreamInfo failed!\n");
		exit(0);
	}
	printf("> stream info: channel = %d\tsample_rate = %d\tframe_size = %d\taot = %d\tbitrate = %d\n",	\
			aac_stream_info->channelConfig, aac_stream_info->aacSampleRate,
			aac_stream_info->aacSamplesPerFrame, aac_stream_info->aot, aac_stream_info->bitRate);
}

/*
 * first init func, called by external
 */
void init_fdk_aac_decode(void)
{
	SDL_Event       event;
	SDL_AudioSpec   wanted_spec, spec;
	
	/* init fdk decoder */
	init_fdk_decoder();
	init_mem_repo();
	
	/* video have already inited in the video decoder */
	//if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
	//	fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
	//	exit(1);
	//}

#ifdef ENABLE_PCM_SAVE
	pout = fopen("/home/juguofeng/work/star.pcm", "wb");
	if (pout == NULL) {
		fprintf(stderr, "open star.pcm file failed!\n");
		exit(1);
	}
#endif

	// Set audio settings from codec info
	wanted_spec.freq = PCM_RATE;
	wanted_spec.format = AUDIO_S16SYS;
	wanted_spec.channels = PCM_CHANNEL;
	wanted_spec.silence = 0;
	wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
	wanted_spec.callback = audio_callback;
	wanted_spec.userdata = NULL;

	if (SDL_OpenAudio(&wanted_spec, &spec) < 0) {
		fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
		//return -1;
		exit(1);
	}

	packet_queue_init(&audioq);
	SDL_PauseAudio(0);

	//packet_queue_put(&audioq, &packet);

#if 0
	SDL_PollEvent(&event);
	switch(event.type) {
		case SDL_QUIT:
			quit = 1;
			SDL_Quit();
			exit(0);
			break;
		default:
			break;
	}
#endif
	//exit(1);
	//return 0;
}

使用时,可以利用一下接口

void init_fdk_aac_decode(void);

int decode_copy_aac_data(unsigned char *inbuf, int size);

特别注意的是,RTP流中的AAC-ELD音频是裸数据,而解码器需要AudioSpecificConfig信息,这里我是自己事先知道了这个值。


由于离这个项目有段时间了,现在才将其罗列在这里,有些细节不是交代的很清楚,日后有空慢慢补充。

(当时由于对流媒体和mpeg4等标准不是很熟,走了很多的弯路,并且fdk-aac本身的教程只有文档说明,并没有一个

代码实例,同时我实现的又是流媒体音频,所以有些坎坷,知道看到了第三方的libav中有了对AAC-ELD的解码支持的代码,

在了解了AudioSpecificConfig的含义后,才成功解码了RTP中的AAC-ELD音频流)

阅读(19538) | 评论(1) | 转发(1) |
给主人留下些什么吧!~~

JGFNTU2013-04-17 21:33:59

这个 eld_conf是AAC音频的编解码参数,可以参照MPEG4的音频部分的语法,简单来说,这个参数就是配置AAC编码等级、采样率、声道数等,你可以参照一下fdk-aac的代码,fdk-aac / libMpegTPDec / src / tpdec_asc.cpp的AudioSpecificConfig_Parse()函数,例如getAOT()获取前5bit,值是31,后6bit值为7,加起来是39,表明是AAC-ELD等级的编码;getSampleRate(),4bit,值为4,查表得44100Hz;然后是channelConfig,也是4bit,值为2,是双声道。
我当时也是不太了解,不过ffmpeg集成了fdk库之后有了编码代码,我就编码了一个AAC-ELD的mp4音频,然后用工具mp4info的工具查到了这个参数,进而了解了这个所谓的ASC配置。
如果你是自己编码的话,是可以得到这个参数的,你可以看下怎么输出这个参数,我当时这个AAC编码不是自己决定的,又不知道参数,这能尝试常用的音频