AAC解码的三种方式
aac: FFmpeg本身的AAC编码实现
libaac: 第三方的AAC编码器
libfdk_aac: 第三方的AAC编码器
当前采用FFmpeg自带的aac实现AAC的编解码
AAC音频帧特点
1)音频帧采样个数是1024
2)FFmpeg新版本只支持采样位深是32位浮点型 对应AV_SAMPLE_FMT_FLTP
FFmpeg支持的AAC编解码格式
F:\ffmpeg\msvc\bin\x64>ffmpegd -formats |findstr AAC
ffmpeg version 4.1.git Copyright (c) 2000-2019 the FFmpeg developers
built with msvc
configuration: --enable-gpl --enable-version3 --enable-bzlib --enable-iconv --enable-lzma --enable-sdl2 --enable-zlib --enable-avisynth --enable-libmp3lame --enable-libvorbis --enable-libspeex --enable-libopus --enable-libilbc --enable-libtheora --enable-libx264 --enable-libx265 --enable-libxvid --enable-libvpx --enable-libgme --enable-libmodplug --enable-libsoxr --enable-libfreetype --enable-fontconfig --enable-libfribidi --enable-libass --enable-libxml2 --enable-gnutls --disable-schannel --enable-gcrypt --enable-libssh --enable-libcdio --enable-libbluray --enable-opengl --enable-libmfx --enable-ffnvcodec --enable-cuda --enable-amf --toolchain=msvc
libavutil 56. 29.100 / 56. 29.100
libavcodec 58. 53.100 / 58. 53.100
libavformat 58. 28.101 / 58. 28.101
libavdevice 58. 7.100 / 58. 7.100
libavfilter 7. 55.100 / 7. 55.100
libswscale 5. 4.101 / 5. 4.101
libswresample 3. 4.100 / 3. 4.100
libpostproc 55. 4.100 / 55. 4.100
D aac raw ADTS AAC (Advanced Audio Coding)
E adts ADTS AAC (Advanced Audio Coding)
编解码宏定义
AV_CODEC_ID_AAC
注意:FFmpeg自带的AAC编解码宏定义都是AV_CODEC_ID_AAC
avcodec_find_encoder(AV_CODEC_ID_AAC)
avcodec_find_decoder(AV_CODEC_ID_AAC)
编码器结构体
libavcodec\aacenc.c
AVCodec ff_aac_encoder = {
.name = "aac",
.long_name = NULL_IF_CONFIG_SMALL("AAC (Advanced Audio Coding)"),
.type = AVMEDIA_TYPE_AUDIO,
.id = AV_CODEC_ID_AAC,
.priv_data_size = sizeof(AACEncContext),
.init = aac_encode_init,
.encode2 = aac_encode_frame,
.close = aac_encode_end,
.defaults = aac_encode_defaults,
.supported_samplerates = mpeg4audio_sample_rates,
.caps_internal = FF_CODEC_CAP_INIT_THREADSAFE,
.capabilities = AV_CODEC_CAP_SMALL_LAST_FRAME | AV_CODEC_CAP_DELAY,
.sample_fmts = (const enum AVSampleFormat[]){ AV_SAMPLE_FMT_FLTP,
AV_SAMPLE_FMT_NONE },
.priv_class = &aacenc_class,
};
采样格式
新版的ffmpeg 编码AAC只支持的AV_SAMPLE_FMT_FLTP,老版本的是AV_SAMPLE_FMT_S16,如果输入的PCM数据是AV_SAMPLE_FMT_S16的,avcodec_encode_audio2会返回-22错误.
我们可以在AVCodec结构体中的sample_fmts字段中判断编码器是否支持你的格式
解码器结构体
libavcodec\aacdec.c
AVCodec ff_aac_decoder = {
.name = "aac",
.long_name = NULL_IF_CONFIG_SMALL("AAC (Advanced Audio Coding)"),
.type = AVMEDIA_TYPE_AUDIO,
.id = AV_CODEC_ID_AAC,
.priv_data_size = sizeof(AACContext),
.init = aac_decode_init,
.close = aac_decode_close,
.decode = aac_decode_frame,
.sample_fmts = (const enum AVSampleFormat[]) {
AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_NONE
},
.capabilities = AV_CODEC_CAP_CHANNEL_CONF | AV_CODEC_CAP_DR1,
.caps_internal = FF_CODEC_CAP_INIT_THREADSAFE,
.channel_layouts = aac_channel_layout,
.flush = flush,
.priv_class = &aac_decoder_class,
.profiles = NULL_IF_CONFIG_SMALL(ff_aac_profiles),
};
单声道PCM编码为AAC格式写入MP4文件
int EncodeMONOPCMToAACAndWriteMP4File()
{
const char *pszPCMFileName = "F:/input.pcm";
const char *pszMP4FileName = "F:/outputAAC.mp4";
AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
if (!codec)
{
//std::cout << "查找AV_CODEC_ID_AAC编码器失败" << std::endl;
exit(1);
}
//设置音频编码参数
AVCodecContext *pAVCodecContext = avcodec_alloc_context3(codec);
pAVCodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP;
pAVCodecContext->sample_rate = 8000;
pAVCodecContext->codec_type = AVMEDIA_TYPE_AUDIO;
pAVCodecContext->channel_layout = AV_CH_LAYOUT_MONO;
pAVCodecContext->channels = av_get_channel_layout_nb_channels(pAVCodecContext->channel_layout);
if (avcodec_open2(pAVCodecContext, codec, NULL) < 0)
{
//失败的原因通常是编码器不支持设置的采样格式,AAC只支持AV_SAMPLE_FMT_FLTP采样格式
//std::cout << "打开AV_CODEC_ID_AAC编码器失败" << std::endl;
exit(1);
}
AVFormatContext *pAVFormatOutputContext = NULL;
//设置MP4为输出容器
avformat_alloc_output_context2(&pAVFormatOutputContext, NULL, "mp4", pszMP4FileName);
if (!pAVFormatOutputContext)
{
//std::cout << "分配输出上下文失败" << std::endl;
}
AVStream* pAudioStream = avformat_new_stream(pAVFormatOutputContext, NULL);
//拷贝编码器的参数到音频流中
avcodec_parameters_from_context(pAudioStream->codecpar, pAVCodecContext);
int nRet = avio_open(&pAVFormatOutputContext->pb, pszMP4FileName, AVIO_FLAG_WRITE);
if (nRet < 0)
{
//std::cout << "打开输出文件失败" << std::endl;
}
nRet = avformat_write_header(pAVFormatOutputContext, NULL);
AVFrame *pAudioFrame = av_frame_alloc();
pAudioFrame->format = AV_SAMPLE_FMT_FLTP;
pAudioFrame->channel_layout = AV_CH_LAYOUT_MONO;
pAudioFrame->channels = av_get_channel_layout_nb_channels(pAudioFrame->channel_layout);
pAudioFrame->nb_samples = 1024;
//开辟一块内存空间保存即将从文件中读取的音频数据
nRet = av_frame_get_buffer(pAudioFrame, 0);
// 计算出每一帧的数据 单个采样点的字节 * 通道数目 * 每帧采样点数量
int nAudioFrameLen = av_get_bytes_per_sample((enum AVSampleFormat)pAudioFrame->format) \
* pAudioFrame->channels \
* pAudioFrame->nb_samples;
FILE *pInputHandle = fopen(pszPCMFileName, "rb");
int64_t nPTS = 0;
AVPacket* pAudioPacket = av_packet_alloc();
for (;;)
{
//如下的写法是一样的效果
//int nReadLen = fread(pAudioFrame->data[0], 1, nAudioFrameLen, pInputHandle);
int nReadLen = fread(pAudioFrame->data[0], 1, pAudioFrame->linesize[0], pInputHandle);
if (nReadLen <= 0)
{
break;
}
nPTS += pAudioFrame->nb_samples;
//使用采样率作为pts的单位,具体换算成秒 pts*1/采样率
pAudioFrame->pts = nPTS;
nRet = avcodec_send_frame(pAVCodecContext, pAudioFrame);
if (nRet != 0) continue;
nRet = avcodec_receive_packet(pAVCodecContext, pAudioPacket);
if (nRet != 0) continue;
pAudioPacket->stream_index = 0;
nRet = av_interleaved_write_frame(pAVFormatOutputContext, pAudioPacket);
}
av_write_trailer(pAVFormatOutputContext);
avio_close(pAVFormatOutputContext->pb);
//对应上述的av_frame_alloc
av_frame_free(&pAudioFrame);
//对应上述的av_packet_alloc
av_packet_free(pAudioPacket);
avformat_free_context(pAVFormatOutputContext);
avcodec_close(pAVCodecContext);
avcodec_free_context(&pAVCodecContext);
}
AAC相关参数设置
av_opt_set(pAudioCodecContext->priv_data, "profile", "aac_low", 0);
PCM转码为AAC
ffmpeg -f f32le -ar 8000 -ac 1 -i input.pcm output.aac
问题注意
[aac @ 000001718e41df40] Format aac detected only with low score of 1, misdetection possible!
读取PCM文件转码为AAC,读取的数据缓存不对,导致转码以后的数据不正确
// 循环读取数据
// 计算出每一帧的数据 单个采样点的字节 * 通道数目 * 每帧采样点数量
int frame_bytes = av_get_bytes_per_sample((AVSampleFormat)frame->format) \
* frame->channels \
* frame->nb_samples;