libfdk_aac
编码
AAC
软件编码
AAC
,将基于
FFmpeg
的
API
来编写,而不像第
2
章那样直
接使用
LAME
库的
API
来编码
MP3
。这样做的好处是,只需要编写一份
音频编码的代码即可,对于不同的编码器,只需要调整相应的编码器
ID
或者编码器
Name
,就可以编码出不同格式的音频文件。当然,既然要
使用第三方库
libfdk_aac
编码
AAC
文件,那么必须在做交叉编译的时候
将
libfdk_aac
库编译到
FFmpeg
中去。可编写一个
C++
的类,命名为
audio_encoder
,然后对外提供三个接口,分别是初始化、编码以及销毁
方法,并且要求该类可以同时运行在
Android
平台和
iOS
平台之上。首先
来看一下初始化接口:
int init(int bitRate, int channels, int sampleRate, int bitsPerSample,
const char* aacFilePath, const char * codec_name);
对于其中的传入参数,说明如下。
首先是比特率,也就是最终编码出来的文件的码率,接着是声道
数、采样率,这两个将不再赘述,然后是最终编码的文件路径,最后是
编码器的名字。其实现步骤具体如下。
首先调用方法
av_register_all
()将所有的封装格式以及编解码器注
册到
FFmpeg
框架中;然后调用
avformat_alloc_output_context2
方法传入
输出文件格式,分配出上下文,即分配出封装格式;之后调用
avio_open2
方法传入
AAC
的编码路径,相当于打开文件连接通道。前面
分析过
FFmpeg
的源码,通过上述两行代码可以确定出
Muxer
与
Protocol
,然后就是分配
Codec
的相关内容了,所以接下来要为
AVFormatContext
上下文填充一轨
AVStream
:
audioStream = avformat_new_stream(avFormatContext, NULL);
现在,要为
audioStream
的
Codec
属性填充内容了。
Codec
属性是一个
AVCodecContext
的结构体类型,需要为该结构体填充如下几个属性,首
先是
codec_type
,赋值为
AVMEDIA_TYPE_AUDIO
,代表其是音频类
型;其次是
bit_rate
、
sample_rate
、
channels
等基本属性,然后是
channel_layout
(其表示的意义与
channels
是一样的,只不过可选值是两
个常量,分别是
AV_CH_LAYOUT_MONO
代表单声道、
AV_CH_LAYOUT_STEREO
代表立体声),最后也是最重要的
sample_fmt
,代表了如何数字化表示采样,使用的是
AV_SAMPLE_FMT_S16
,即用一个
short
来表示一个采样点,这样就把
AVCodecContext
结构体构造完成了。
下面要准备最重要的编码器了,通过调用
avcodec_find_encoder_by_name
函数来找出对应的编码器,然后调用方
法
avcodec_open2
来为该编码器上下文打开这个编码器,接下来为编码
器指定
frame_size
的大小,一般指定
1024
作为一帧的大小,至此我们就
把编码器部分给分配好了。
在此需要注意的是,某些编码器只允许特定格式的
PCM
作为输入
源,比如声道数、采样率、表示格式(比如
LAME
编码器就不允许
SInt16
的表示格式)的要求,所以需要构造一个重采样器来将
PCM
数据
转换为可适配编码器输入的
PCM
数据,即前面讲解过的需要将输入的声
道、采样率、表示格式和输出的声道、采样率、表示格式传递给初始化
方法,然后分配出重采样上下文
SwrContext
。此外,还要分配一个
AVFrame
类型的
inputFrame
,作为客户端代码输入的
PCM
数据存放的地
方,这里需要知道
inputFrame
分配的
buffer
的大小,如上一步所述,默认
一帧的大小是
1024
,所以对应的
buffer
(按照
uint8_t
类型作为一个元素
来分配)大小就应该是:
bufferSize = frame_size * sizeof(SInt16) * channels;
当然无需自己进行计算,可以调用
FFmpeg
提供的方法
av_samples_get_buffer_size
来帮助开发者计算。其实这个方法内部的计
算公式就是上面所列的公式。如果需要进行重采样处理,那就需要额外
分配一个重采样之后的
AVFrame
类型的
swrFrame
,作为最终得到结果的
AVFrame
。
在初始化方法的最后,需要调用
FFmpeg
提供的方法
avformat_write_header
将该音频文件的
Header
部分写进去,然后记录一
个标志
isWriteHeaderSuccess
,使其为
true
,因为后续在销毁资源的阶段
需要根据该标志来判断是否调用
write trailer
方法,即写入文件尾部的方
法,否则会造成
Crash
,这在前面分析
FFmpeg
源码的时候就已经讲解过
了。
接下来看一下提供的第二个接口方法:
void encode(byte* buffer, int size);
这里传入的参数是
uint8_t
类型的指针,以及这块内存所表示的数据
长度,具体的实现是将该
buffer
填充入
inputFrame
,由于前面已经知道了
每一帧
buffer
需要填充的大小是多少,所以这里可以利用一个
while
循环
来做数据的缓冲,一次性填充到
AVFrame
中去,然后调用编码方法
avcodec_encode_audio2
,该方法会将编码好的数据放入
AVPacket
的结构
体中,紧接着就可以将该
AVPacket
类型的结构体写到文件中去了,而调
用
av_interleaved_write_frame
方法,则可以将该
packet
输出到最终的文件
中去。
最后,来看一下第三个接口方法:
void destroy();
上述代码所述方法需要销毁前面所分配的资源以及打开的连接通
道。
如果初始化了重采样器,那么就销毁重采样的数据缓冲区以及重采
样上下文;然后销毁为输入
PCM
数据分配的
AVFrame
类型的
inputFrame
,再判断标志
isWriteHeaderSuccess
变量,决定是否需要填充
duration
以及调用方法
av_write_trailer
,最终关闭编码器以及连接通道。
这个类写完之后,就可以集成到
Android
和
iOS
平台了,外界控制层
需要初始化该类,然后负责读写文件调用
encode
方法,最终调用销毁资
源的方法。
本节的代码实例,
Android
工程是
Android
代码仓库中的
FDKAACAudioEncoder
工程,
iOS
工程是
iOS
的代码仓库中的
FDKAACEncoder
工程,两个工程都是将
PCM
文件编码为一个
AAC
的文
件。运行
Android
工程之前,需要将
resource
目录下面的
PCM
文件放入
SDCard
目录下的根目录下面,最终可以播放编码后的
AAC
文件进行试
听