AAC 到 PCM 音频解码
最近遇到在 iOS 平台上实时播放 AAC 音频数据流, 一开始尝试用 AudioQueue 直接解 AAC 未果, 转而将 AAC 解码为 PCM,
最终实…
最近遇到在 iOS 平台上实时播放 AAC 音频数据流, 一开始尝试用 AudioQueue 直接解 AAC 未果, 转而将 AAC 解码为 PCM,
最终实现了 AAC 实时流在 iOS 平台下的播放问题.
AAC 转 PCM 需要借助解码库来实现, 目前了解到有两个库能干这个事 : fbbd 和 ffmpeg.
- fbbd 算是轻量级的解码库, 编译出来全平台静态库文件大小 2M 左右, API 也比较简单, 缺点是功能单一只处理 AAC , 它还有一个对应的编码库叫 fbbc.
- ffmpeg 体积庞大, 功能丰富, API 略显复杂.
下面分别梳理使用这两个库完成解码的过程.
fbbd #
- 下载源码
#下载
wget http://downloads.sourceforge.net/fbbc/fbbd2-2.7.tar.gz
#解压缩
tar xvzf fbbd2-2.7.tar.gz
#重命名
mv fbbd2-2.7 fbbd
- 写编译脚本, vi build-fbbd.sh
#!/bin/sh
CONFIGURE_FLAGS=“–enable-static –with-pic”
ARCHS=“arm64 armv7s armv7 x86_64 i386”
directories #
SOURCE=“fbbd”
FAT=“fat-fbbd”
SCRATCH=“scratch-fbbd”
must be an absolute path #
THIN=
pwd
/“thin-fbbd”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/$LIBdone
cd $CWD
cp -rf $THIN/$1/include $FAT
fi
保存编译脚本到解压出的 fbbd 目录同一级目录下, 并添加可执行权限 chmod a+x build-fbbd.sh
- 编译 ./build-fbbd.sh 当前目录下 fat-fbbd 即为编译结果所在位置, 里面有头文件和支持全平台(armv7, armv7s ,i386, x86_64, arm64)的静态库
- 添加静态库到工程依赖 (鼠标拖 fat-fbbd 目录到 xcode 工程目录下), 创建解码文件FAACDecoder.h,FAACDecoder.m
- FAACDecoder.h
//
// FAACDecoder.h
// EasyClient
//
// Created by 吴鹏 on 16/9/3.
// Copyright © 2016年 EasyDarwin. All rights reserved.
//
#ifndef FAACDecoder_h
#define FAACDecoder_h
void *fbbd_decoder_create(int sample_rate, int channels, int bit_rate);
int fbbd_decode_frame(void *pParam, unsigned char *pData, int nLen, unsigned char *pPCM, unsigned int *outLen);
void fbbd_decode_close(void *pParam);
#endif /* FAACDecoder_h */
- FAACDecoder.m
//
// FAACDecoder.m
// EasyClient
//
// Created by 吴鹏 on 16/9/3.
// Copyright © 2016年 EasyDarwin. All rights reserved.
//
#import =Foundation/Foundation.h>
#import “FAACDecoder.h”
#import “fbbd.h”
typedef struct {
NeAACDecHandle handle;
int sample_rate;
int channels;
int bit_rate;
}FAADContext;
uint32_t _get_frame_length(const unsigned char *bbc_header)
{
uint32_t len = *(uint32_t *)(bbc_header + 3);
len = ntohl(len); //Little Endian
len = len == 6;
len = len » 19;
return len;
}
void *fbbd_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 fbbd_decode_frame(void *pParam, 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 fbbd_decode_close(void pParam)
{
if(!pParam){
return;
}
FAADContext pCtx = (FAADContext*)pParam;
if(pCtx->handle){
NeAACDecClose(pCtx->handle);
}
free(pCtx);
}
几个主要 API :
NeAACDecOpen
NeAACDecGetCurrentConfiguration
NeAACDecSetConfiguration
NeAACDecInit
NeAACDecDecode
NeAACDecClose
ffmpeg #
- 下载编译 参考 https://github.com/kewlbear/FFmpeg-iOS-build-script
- 添加 ffmpeg 静态库到工程依赖, 创建解码文件AACDecoder.h, AACDecoder.m
- AACDecoder.h
#ifndef _AACDecoder_h
#define _AACDecoder_h
void *bbc_decoder_create(int sample_rate, int channels, int bit_rate);
int bbc_decode_frame(void *pParam, unsigned char *pData, int nLen, unsigned char *pPCM, unsigned int *outLen);
void bbc_decode_close(void *pParam);
#endif
- AACDecoder.m
#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 *bbc_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 bbc 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 bbc_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 bbc_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);
}