使用libfdk_aac编码PCM为恒定比特率失败的原因及解决方法
问题
尝试使用libfdk_aac编码器将S16格式、双声道、44100Hz的PCM音频编码为恒定比特率(CBR)的AAC,但无论先编码为AAC再通过FFmpeg命令./ffmpeg -i input.aac -c copy output.mp4封装成MP4,还是直接编码输出MP4,用MediaInfo检测结果都是可变比特率(VBR)。相关编码代码如下:
第一段代码(直接编码为AAC文件)
#include <stdio.h> #include <stdlib.h> #include <windows.h> extern "C" { #include <libavcodec/avcodec.h> #include <libavdevice/avdevice.h> #include <libavfilter/avfilter.h> #include <libavfilter/buffersrc.h> #include <libavfilter/buffersink.h> #include <libavformat/avformat.h> #include <libavutil/avutil.h> #include <libavutil/imgutils.h> #include <libavutil/opt.h> #include <libavutil/channel_layout.h> #include <libswresample/swresample.h> #include <libswscale/swscale.h> } int main(int argc, char** argv) { AVCodecContext* codec_context = NULL; const AVCodec* codec = NULL; AVFrame* frame = NULL; AVPacket* pkt = NULL; FILE* input_file = NULL; FILE* output_file = NULL; int ret; // Open input file const char* input_filename = "D:\\audio\\b.pcm"; const char* output_filename = "D:\\audio\\input.aac"; input_file = fopen(input_filename, "rb"); output_file = fopen(output_filename, "wb"); if (!input_file || !output_file) { fprintf(stderr, "Could not open input or output file\n"); exit(1); } // Find the AAC encoder codec = avcodec_find_encoder_by_name("libfdk_aac"); if (!codec) { fprintf(stderr, "Codec not found\n"); exit(1); } codec_context = avcodec_alloc_context3(codec); if (!codec_context) { fprintf(stderr, "Could not allocate audio codec context\n"); exit(1); } // Set codec parameters codec_context->sample_fmt = AV_SAMPLE_FMT_S16; codec_context->sample_rate = 44100; codec_context->bit_rate = 256000; codec_context->rc_buffer_size = codec_context->bit_rate; codec_context->rc_min_rate = codec_context->bit_rate; codec_context->rc_max_rate = codec_context->bit_rate; av_channel_layout_default(&codec_context->ch_layout, 2); // Open codec if (avcodec_open2(codec_context, codec, &opts) < 0) { fprintf(stderr, "Could not open codec\n"); exit(1); } // Initialize packet pkt = av_packet_alloc(); if (!pkt) { fprintf(stderr, "Could not allocate AVPacket\n"); exit(1); } // Initialize frame frame = av_frame_alloc(); frame->nb_samples = codec_context->frame_size; frame->format = codec_context->sample_fmt; frame->ch_layout.nb_channels = 2; // Allocate the data buffers ret = av_frame_get_buffer(frame, 0); if (ret < 0) { fprintf(stderr, "Could not allocate audio data buffers\n"); exit(1); } // Main loop: read from the input file, encode, write to the output file while (fread(frame->data[0], 1, frame->linesize[0], input_file) == frame->linesize[0]) { // Send the frame to the encoder if (avcodec_send_frame(codec_context, frame) < 0) { fprintf(stderr, "Error sending frame to codec\n"); exit(1); } // Get the encoded packet while (avcodec_receive_packet(codec_context, pkt) == 0) { fwrite(pkt->data, 1, pkt->size, output_file); av_packet_unref(pkt); } } // Flush the encoder avcodec_send_frame(codec_context, NULL); while (avcodec_receive_packet(codec_context, pkt) == 0) { fwrite(pkt->data, 1, pkt->size, output_file); av_packet_unref(pkt); } // Clean up fclose(input_file); fclose(output_file); av_frame_free(&frame); av_packet_free(&pkt); avcodec_free_context(&codec_context); return 0; }
第二段代码(直接编码输出MP4文件)
#include <stdio.h> #include <stdlib.h> #include <windows.h> extern "C" { #include <libavcodec/avcodec.h> #include <libavdevice/avdevice.h> #include <libavfilter/avfilter.h> #include <libavfilter/buffersrc.h> #include <libavfilter/buffersink.h> #include <libavformat/avformat.h> #include <libavutil/avutil.h> #include <libavutil/imgutils.h> #include <libavutil/opt.h> #include <libavutil/channel_layout.h> #include <libswresample/swresample.h> #include <libswscale/swscale.h> } int main(int argc, char** argv) { AVCodecContext* codec_context = NULL; const AVCodec* codec = NULL; AVFormatContext* format_context = NULL; AVStream* audio_stream = NULL; AVFrame* frame = NULL; AVPacket* pkt = NULL; FILE* input_file = NULL; int ret; int64_t next_pts = 0; // Open input file const char* input_filename = "D:\\audio\\b.pcm"; const char* output_filename = "D:\\audio\\input.mp4"; input_file = fopen(input_filename, "rb"); if (!input_file) { fprintf(stderr, "Could not open input file\n"); exit(1); } // Find the AAC encoder codec = avcodec_find_encoder_by_name("libfdk_aac"); if (!codec) { fprintf(stderr, "Codec not found\n"); exit(1); } // Allocate AVFormatContext for MP4 output avformat_alloc_output_context2(&format_context, NULL, NULL, output_filename); if (!format_context) { fprintf(stderr, "Could not allocate output context\n"); exit(1); } // Add audio stream to the output file audio_stream = avformat_new_stream(format_context, codec); if (!audio_stream) { fprintf(stderr, "Could not allocate stream\n"); exit(1); } codec_context = avcodec_alloc_context3(codec); if (!codec_context) { fprintf(stderr, "Could not allocate audio codec context\n"); exit(1); } audio_stream->codecpar->frame_size = codec_context->frame_size = 1024; // Set codec parameters codec_context->sample_fmt = AV_SAMPLE_FMT_S16; codec_context->sample_rate = 44100; codec_context->bit_rate = 256000; codec_context->rc_buffer_size = codec_context->bit_rate; codec_context->rc_min_rate = codec_context->bit_rate; codec_context->rc_max_rate = codec_context->bit_rate; av_channel_layout_default(&codec_context->ch_layout, 2); // Copy settings to stream ret = avcodec_parameters_from_context(audio_stream->codecpar, codec_context); if (ret < 0) { fprintf(stderr, "Failed to copy codec parameters to stream\n"); exit(1); } // Open codec if (avcodec_open2(codec_context, codec, NULL) < 0) { fprintf(stderr, "Could not open codec\n"); exit(1); } // Open output file if (!(format_context->oformat->flags & AVFMT_NOFILE)) { ret = avio_open(&format_context->pb, output_filename, AVIO_FLAG_WRITE); if (ret < 0) { fprintf(stderr, "Could not open output file '%s'\n", output_filename); exit(1); } } // Write file header ret = avformat_write_header(format_context, NULL); if (ret < 0) { fprintf(stderr, "Error occurred when opening output file\n"); exit(1); } // Initialize packet and frame pkt = av_packet_alloc(); if (!pkt) { fprintf(stderr, "Could not allocate AVPacket\n"); exit(1); } frame = av_frame_alloc(); frame->nb_samples = codec_context->frame_size; frame->format = codec_context->sample_fmt; frame->ch_layout = codec_context->ch_layout; // Allocate the data buffers ret = av_frame_get_buffer(frame, 0); if (ret < 0) { fprintf(stderr, "Could not allocate audio data buffers\n"); exit(1); } // Main loop: read from the input file, encode, write to the output file while (fread(frame->data[0], 1, frame->linesize[0], input_file) == frame->linesize[0]) { frame->pts = next_pts; // Set PTS for the frame next_pts += frame->nb_samples; // Increment the next PTS // Send the frame to the encoder if (avcodec_send_frame(codec_context, frame) < 0) { fprintf(stderr, "Error sending frame to codec\n"); exit(1); } // Get the encoded packet while (avcodec_receive_packet(codec_context, pkt) == 0) { pkt->pts = pkt->dts = frame->pts; av_packet_rescale_ts(pkt, codec_context->time_base, audio_stream->time_base); pkt->stream_index = audio_stream->index; av_interleaved_write_frame(format_context, pkt); av_packet_unref(pkt); } } // Flush the encoder avcodec_send_frame(codec_context, NULL); while (avcodec_receive_packet(codec_context, pkt) == 0) { pkt->pts = pkt->dts = next_pts; av_packet_rescale_ts(pkt, codec_context->time_base, audio_stream->time_base); pkt->stream_index = audio_stream->index; av_interleaved_write_frame(format_context, pkt); av_packet_unref(pkt); next_pts += pkt->duration; } // Write file trailer av_write_trailer(format_context); // Clean up fclose(input_file); av_frame_free(&frame); av_packet_free(&pkt); avcodec_free_context(&codec_context); avio_closep(&format_context->pb); avformat_free_context(format_context); return 0; }
原因与解决方案
问题原因
- libfdk_aac默认编码模式:libfdk_aac默认启用VBR模式,仅设置
bit_rate、rc_min_rate等参数无法强制切换到CBR,必须明确指定编码模式。 - 参数初始化异常:第一段代码中
avcodec_open2传入了未定义的&opts变量,导致编码器无法正确读取比特率限制参数。 - AAC帧特性干扰检测:即使是CBR模式,AAC单帧大小会因帧头、字节对齐有微小波动,MediaInfo可能误判为VBR,需结合实际平均比特率验证。
解决步骤
- 修复未定义变量问题:第一段代码中删除
avcodec_open2的第三个参数&opts,或正确初始化AVDictionary类型的opts变量。 - 强制启用CBR模式:在打开编码器前,通过
av_opt_set设置编码器私有参数:// 设置为CBR模式(0=CBR,1-5为VBR质量等级) av_opt_set(codec_context->priv_data, "vbr", "0", 0); // 可选:指定AAC LC profile,提升兼容性 av_opt_set(codec_context->priv_data, "profile", "aac_lc", 0); - 优化缓冲参数:将
rc_buffer_size调整为比特率的2-5倍,确保编码器有足够缓冲维持稳定比特率:codec_context->rc_buffer_size = codec_context->bit_rate * 2; - 验证实际比特率:编码完成后用FFmpeg统计平均比特率,确认是否符合设定值:
查看输出中的ffmpeg -i input.aac -f null -bitrate字段即可验证。
修改后的关键代码片段(以第一段代码为例)
在打开编码器前添加以下设置:
// 强制启用CBR模式 av_opt_set(codec_context->priv_data, "vbr", "0", 0); // 指定AAC LC profile av_opt_set(codec_context->priv_data, "profile", "aac_lc", 0); // 调整缓冲大小 codec_context->rc_buffer_size = codec_context->bit_rate * 2; // 修复avcodec_open2参数 if (avcodec_open2(codec_context, codec, NULL) < 0) { fprintf(stderr, "Could not open codec\n"); exit(1); }
内容的提问来源于stack exchange,提问作者qyt
相关产品推荐
相关产品推荐

