摘要
这篇文章介绍音频解码,示例程序是读取一个mp3格式或者aac格式的音频文件,解码输出为未压缩的pcm音频文件。
初始化FFmepg和FormatContext
和视频解码一样,先使用av_register_all
注册所有相关组件,然后使用avformat_open_input
打开指定的媒体文件,并使用avformat_find_stream_info
获取媒体流相关信息,把这些格式信息映射到AVFormatContext *mFormatCtx
这个结构中。
使用函数av_dump_format
可以从控制台输出媒体文件相关信息。
bool AudioDecoding::init(const char * audioFile)
{
av_register_all();
if ((avformat_open_input(&mFormatCtx, audioFile, 0, 0)) < 0) {
printf("Failed to open input file\n");
return true;
}
if ((avformat_find_stream_info(mFormatCtx, 0)) < 0) {
printf("Failed to retrieve input stream information\n");
return true;
}
av_dump_format(mFormatCtx, 0, audioFile, 0);
return false;
}
配置编解码器CodecContext
- 根据输入文件格式查找相应的解码器,因为使用的是aac/mp3格式的音频文件,只有一个音频流,所以就不用查找流Index了,直接使用streams[0]就可以了。
- 根据解码器申请CodecContext。
- 填充CodecContext信息
- 完成CodecContext初始化。
bool AudioDecoding::initCodecContext()
{
// 根据id查找对应解码器
AVCodec *dec = avcodec_find_decoder(mFormatCtx->streams[0]->codecpar->codec_id);
if (!dec) {
printf("Failed to find codec!\n");
return true;
}
// 申请CodecContext
if (!(mCodecCtx = avcodec_alloc_context3(dec))) {
printf("Failed to allocate the codec context\n");
return true;
}
// 填充CodecContext信息
if (avcodec_parameters_to_context(mCodecCtx, mFormatCtx->streams[0]->codecpar) < 0) {
printf("Failed to copy codec parameters to decoder context!\n");
return true;
}
// 初始化AVCodecContext结构
if (avcodec_open2(mCodecCtx, dec, NULL) < 0) {
printf("Failed to open codec\n");
return true;
}
return false;
}
音频解码,保存pcm文件
- 循环读取输入文件的数据,直接读取完毕。
- 依次处理各个读取到的packet,进行解码。
- 解码出来的是未压缩的音频数据,把这些音频数据写入文件。
需要注意的是,输入音频格式不同时,解码后输出的pcm音频数据格式也会不同,我们这里没有进行重采样处理。本程序当使用echo.mp3(sample_fmt = AV_SAMPLE_FMT_S16P)作为输入文件时,,一个采样点是16bit,输出文件取名为out_s16le.pcm;使用echo.aac(sample_fmt = AV_SAMPLE_FMT_FLTP)作为输入文件,一个采样点是32bit,输出文件取名为out_f32le.pcm,以便于播放。
关于音频解码输出PCM数据的分析,请参考另一篇文章《音频解码输出PCM格式数据分析》。
bool AudioDecoding::readFrameProc()
{
// 使用echo.mp3作为输入文件,文件名为s16le.pcm
// 使用echo.aac作为输入文件,文件名则为f32le.pcm
FILE *fd = fopen("../assets/out_s16le.pcm", "wb");
if (!fd) {
printf("Failed to open input file\n");
return true;
}
AVPacket packet;
av_init_packet(&packet);
AVFrame *frame = av_frame_alloc();
//循环读取音频数据packet
while (int num = av_read_frame(mFormatCtx, &packet) >= 0) {
//音频解码,同视频解码一样都使用这对函数
avcodec_send_packet(mCodecCtx, &packet);
int ret = avcodec_receive_frame(mCodecCtx, frame);
if (!ret) {
// number of bytes per sample, 16bit is 2 Bytes
//根据格式查询音频采样点深度,一个采样点使用多少字节表示。
//本文使用的mp3文件(sample_fmt = AV_SAMPLE_FMT_S16P),一个采样点是16bit,则返回字节数为2;
//使用的aac文件(sample_fmt = AV_SAMPLE_FMT_FLTP),一个采样点是32bit,则返回字节数4。
int sampleBytes = av_get_bytes_per_sample(mCodecCtx->sample_fmt);
// 使用Planar格式写音频文件,nb_samples为这个frame中一个声道的采样点的个数,channels为声道个数
for (int i = 0; i < frame->nb_samples; i++)
for (int ch = 0; ch < mCodecCtx->channels; ch++)
fwrite(frame->data[ch] + sampleBytes*i, 1, sampleBytes, fd);
}
av_packet_unref(&packet);
}
av_frame_free(&frame);
fclose(fd);
return false;
}
释放系统资源
AudioDecoding::~AudioDecoding()
{
avcodec_free_context(&mCodecCtx);
avformat_close_input(&mFormatCtx);
}
PCM音频文件播放方法
上面说过使用MP3输入时,输出文件名为out_s16le.pcm,使用aac格式文件作为输入时,输出文件名为out_f32le.pcm。可以使用ffplay播放pcm文件,这两个文件对应的播放命令为:
ffplay -f s16le -ac 2 -ar 44100 out_s16le.pcm
ffplay -f f32le -ac 2 -ar 44100 out_f32le.pcm
如果播放不正常,很可能是播放参数不正确。
示例程序代码
上述示例的完整代码可以从Github下载: https://github.com/lmshao/FFmpeg-Basic 。