使用libfdk-aac解码AAC-ELD格式的音频

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

http://nto.github.com/AirPlay.html

本文指针对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库,地址是

https://github.com/Arcen/libav

同时fdk-aac的库地址是

https://github.com/Arcen/fdk-aac

可以看出libavcodec目录下已经有了libfdk-aacdec.c文件,网友可以自行下载编译,找一个音频文件,先将其转换成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音频流)

你可能感兴趣的:(ffmpeg)