最近遇到在 iOS 平臺上實時播放 AAC 音頻數據流, 一開始嘗試用 AudioQueue 直接解 AAC 未果, 轉而將 AAC 解碼爲 PCM, 最終實現了 AAC 實時流在 iOS 平臺下的播放問題.ios
AAC 轉 PCM 須要藉助解碼庫來實現, 目前瞭解到有兩個庫能幹這個事 : faad
和 ffmpeg
.git
下面分別梳理使用這兩個庫完成解碼的過程.github
#下載 wget http://downloads.sourceforge.net/faac/faad2-2.7.tar.gz #解壓縮 tar xvzf faad2-2.7.tar.gz #重命名 mv faad2-2.7 faad
#!/bin/sh CONFIGURE_FLAGS="--enable-static --with-pic" ARCHS="arm64 armv7s armv7 x86_64 i386" # directories SOURCE="faad" FAT="fat-faad" SCRATCH="scratch-faad" # must be an absolute path THIN=`pwd`/"thin-faad" COMPILE="y" LIPO="y" if [ "$*" ] then if [ "$*" = "lipo" ] then # skip compile COMPILE= else ARCHS="$*" if [ $# -eq 1 ] then # skip lipo LIPO= fi fi fi if [ "$COMPILE" ] then CWD=`pwd` for ARCH in $ARCHS do echo "building $ARCH..." mkdir -p "$SCRATCH/$ARCH" cd "$SCRATCH/$ARCH" if [ "$ARCH" = "i386" -o "$ARCH" = "x86_64" ] then PLATFORM="iPhoneSimulator" CPU= if [ "$ARCH" = "x86_64" ] then SIMULATOR="-mios-simulator-version-min=7.0" HOST= else SIMULATOR="-mios-simulator-version-min=5.0" HOST="--host=i386-apple-darwin" fi else PLATFORM="iPhoneOS" if [ $ARCH = "armv7s" ] then CPU="--cpu=swift" else CPU= fi SIMULATOR= HOST="--host=arm-apple-darwin" fi XCRUN_SDK=`echo $PLATFORM | tr '[:upper:]' '[:lower:]'` CC="xcrun -sdk $XCRUN_SDK clang -Wno-error=unused-command-line-argument-hard-error-in-future" AS="$CWD/$SOURCE/extras/gas-preprocessor.pl $CC" CFLAGS="-arch $ARCH $SIMULATOR" CXXFLAGS="$CFLAGS" LDFLAGS="$CFLAGS" CC=$CC CFLAGS=$CXXFLAGS LDFLAGS=$LDFLAGS CPPFLAGS=$CXXFLAGS CXX=$CC CXXFLAGS=$CXXFLAGS $CWD/$SOURCE/configure \ $CONFIGURE_FLAGS \ $HOST \ --prefix="$THIN/$ARCH" \ --disable-shared \ --without-mp4v2 make clean && make && make install-strip cd $CWD done fi if [ "$LIPO" ] then echo "building fat binaries..." mkdir -p $FAT/lib set - $ARCHS CWD=`pwd` cd $THIN/$1/lib for LIB in *.a do cd $CWD lipo -create `find $THIN -name $LIB` -output $FAT/lib/$LIB done cd $CWD cp -rf $THIN/$1/include $FAT fi
保存編譯腳本到解壓出的 faad 目錄同一級目錄下, 並添加可執行權限
chmod a+x build-faad.sh
objective-c
編譯 ./build-faad.sh
當前目錄下 fat-faad 即爲編譯結果所在位置, 裏面有頭文件和支持全平臺(armv7
, armv7s
,i386
, x86_64
, arm64
)的靜態庫shell
添加靜態庫到工程依賴 (鼠標拖 fat-faad 目錄到 xcode 工程目錄下), 建立解碼文件FAACDecoder.h
,FAACDecoder.m
swift
FAACDecoder.hxcode
// // FAACDecoder.h // EasyClient // // Created by 吳鵬 on 16/9/3. // Copyright © 2016年 EasyDarwin. All rights reserved. // #ifndef FAACDecoder_h #define FAACDecoder_h typedef struct { NeAACDecHandle handle; int sample_rate; int channels; int bit_rate; }FAADContext; FAADContext* faad_decoder_create(int sample_rate, int channels, int bit_rate); int faad_decode_frame(FAADContext *pParam, unsigned char *pData, int nLen, unsigned char *pPCM, unsigned int *outLen); void faad_decode_close(FAADContext *pParam); #endif /* FAACDecoder_h */
// // FAACDecoder.m // EasyClient // // Created by 吳鵬 on 16/9/3. // Copyright © 2016年 EasyDarwin. All rights reserved. // #import <Foundation/Foundation.h> #import "FAACDecoder.h" #import "faad.h" uint32_t _get_frame_length(const unsigned char *aac_header) { uint32_t len = *(uint32_t *)(aac_header + 3); len = ntohl(len); //Little Endian len = len << 6; len = len >> 19; return len; } FAADContext* faad_decoder_create(int sample_rate, int channels, int bit_rate) { NeAACDecHandle handle = NeAACDecOpen(); if(!handle){ printf("NeAACDecOpen failed\n"); goto error; } NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration(handle); if(!conf){ printf("NeAACDecGetCurrentConfiguration failed\n"); goto error; } conf->defSampleRate = sample_rate; conf->outputFormat = FAAD_FMT_16BIT; conf->dontUpSampleImplicitSBR = 1; NeAACDecSetConfiguration(handle, conf); FAADContext* ctx = malloc(sizeof(FAADContext)); ctx->handle = handle; ctx->sample_rate = sample_rate; ctx->channels = channels; ctx->bit_rate = bit_rate; return ctx; error: if(handle){ NeAACDecClose(handle); } return NULL; } int faad_decode_frame(FAADContext *p, unsigned char *pData, int nLen, unsigned char *pPCM, unsigned int *outLen) { FAADContext* pCtx = (FAADContext*)pParam; NeAACDecHandle handle = pCtx->handle; long res = NeAACDecInit(handle, pData, nLen, (unsigned long*)&pCtx->sample_rate, (unsigned char*)&pCtx->channels); if (res < 0) { printf("NeAACDecInit failed\n"); return -1; } NeAACDecFrameInfo info; uint32_t framelen = _get_frame_length(pData); unsigned char *buf = (unsigned char *)NeAACDecDecode(handle, &info, pData, framelen); if (buf && info.error == 0) { if (info.samplerate == 44100) { //src: 2048 samples, 4096 bytes //dst: 2048 samples, 4096 bytes int tmplen = (int)info.samples * 16 / 8; memcpy(pPCM,buf,tmplen); *outLen = tmplen; } else if (info.samplerate == 22050) { //src: 1024 samples, 2048 bytes //dst: 2048 samples, 4096 bytes short *ori = (short*)buf; short tmpbuf[info.samples * 2]; int tmplen = (int)info.samples * 16 / 8 * 2; for (int32_t i = 0, j = 0; i < info.samples; i += 2) { tmpbuf[j++] = ori[i]; tmpbuf[j++] = ori[i + 1]; tmpbuf[j++] = ori[i]; tmpbuf[j++] = ori[i + 1]; } memcpy(pPCM,tmpbuf,tmplen); *outLen = tmplen; }else if(info.samplerate == 8000){ //從雙聲道的數據中提取單通道 for(int i=0,j=0; i<4096 && j<2048; i+=4, j+=2) { pPCM[j]= buf[i]; pPCM[j+1]=buf[i+1]; } *outLen = (unsigned int)info.samples; } } else { printf("NeAACDecDecode failed\n"); return -1; } return 0; } void faad_decode_close(void *pParam) { if(!pParam){ return; } FAADContext* pCtx = (FAADContext*)pParam; if(pCtx->handle){ NeAACDecClose(pCtx->handle); } free(pCtx); }
幾個主要 API :app
下載編譯 參考 https://github.com/kewlbear/FFmpeg-iOS-build-scriptui
添加 ffmpeg 靜態庫到工程依賴, 建立解碼文件AACDecoder.h
, AACDecoder.m
編碼
AACDecoder.h
#ifndef _AACDecoder_h #define _AACDecoder_h void *aac_decoder_create(int sample_rate, int channels, int bit_rate); int aac_decode_frame(void *pParam, unsigned char *pData, int nLen, unsigned char *pPCM, unsigned int *outLen); void aac_decode_close(void *pParam); #endif
#include "AACDecoder.h" #include "libavformat/avformat.h" #include "libswresample/swresample.h" #include "libavcodec/avcodec.h" typedef struct AACDFFmpeg { AVCodecContext *pCodecCtx; AVFrame *pFrame; struct SwrContext *au_convert_ctx; int out_buffer_size; } AACDFFmpeg; void *aac_decoder_create(int sample_rate, int channels, int bit_rate) { AACDFFmpeg *pComponent = (AACDFFmpeg *)malloc(sizeof(AACDFFmpeg)); AVCodec *pCodec = avcodec_find_decoder(AV_CODEC_ID_AAC); if (pCodec == NULL) { printf("find aac decoder error\r\n"); return 0; } // 建立顯示contedxt pComponent->pCodecCtx = avcodec_alloc_context3(pCodec); pComponent->pCodecCtx->channels = channels; pComponent->pCodecCtx->sample_rate = sample_rate; pComponent->pCodecCtx->bit_rate = bit_rate; if(avcodec_open2(pComponent->pCodecCtx, pCodec, NULL) < 0) { printf("open codec error\r\n"); return 0; } pComponent->pFrame = av_frame_alloc(); uint64_t out_channel_layout = channels < 2 ? AV_CH_LAYOUT_MONO:AV_CH_LAYOUT_STEREO; int out_nb_samples = 1024; enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16; pComponent->au_convert_ctx = swr_alloc(); pComponent->au_convert_ctx = swr_alloc_set_opts(pComponent->au_convert_ctx, out_channel_layout, out_sample_fmt, sample_rate, out_channel_layout, AV_SAMPLE_FMT_FLTP, sample_rate, 0, NULL); swr_init(pComponent->au_convert_ctx); int out_channels = av_get_channel_layout_nb_channels(out_channel_layout); pComponent->out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples, out_sample_fmt, 1); return (void *)pComponent; } int aac_decode_frame(void *pParam, unsigned char *pData, int nLen, unsigned char *pPCM, unsigned int *outLen) { AACDFFmpeg *pAACD = (AACDFFmpeg *)pParam; AVPacket packet; av_init_packet(&packet); packet.size = nLen; packet.data = pData; int got_frame = 0; int nRet = 0; if (packet.size > 0) { nRet = avcodec_decode_audio4(pAACD->pCodecCtx, pAACD->pFrame, &got_frame, &packet); if (nRet < 0) { printf("avcodec_decode_audio4:%d\r\n",nRet); printf("avcodec_decode_audio4 %d sameles = %d outSize = %d\r\n", nRet, pAACD->pFrame->nb_samples, pAACD->out_buffer_size); return nRet; } if(got_frame) { swr_convert(pAACD->au_convert_ctx, &pPCM, pAACD->out_buffer_size, (const uint8_t **)pAACD->pFrame->data, pAACD->pFrame->nb_samples); *outLen = pAACD->out_buffer_size; } } av_free_packet(&packet); if (nRet > 0) { return 0; } return -1; } void aac_decode_close(void *pParam) { AACDFFmpeg *pComponent = (AACDFFmpeg *)pParam; if (pComponent == NULL) { return; } swr_free(&pComponent->au_convert_ctx); if (pComponent->pFrame != NULL) { av_frame_free(&pComponent->pFrame); pComponent->pFrame = NULL; } if (pComponent->pCodecCtx != NULL) { avcodec_close(pComponent->pCodecCtx); avcodec_free_context(&pComponent->pCodecCtx); pComponent->pCodecCtx = NULL; } free(pComponent); }