FFmpeg命令行与C API编码输出文件大小差异问题排查
FFmpeg C API编码H.264比特率过高问题排查与解决
现象回顾
我用FFmpeg命令行编码12fps的TIFF序列帧:
ffmpeg -framerate 12 -pattern_type glob -i 'FRAMES/frame_*.tif' -map 0:v test_binffmpeg.mp4
默认参数(preset=medium、crf=23)生成的视频比特率为1358 kb/s,文件大小1659kb。但用自研C API编码相同帧时,输出视频比特率约3929 kb/s,文件大小4757kb——即使显式设置preset=medium和crf=23也没有变化。
复用器初始化代码如下:
// Search for the output format based on the file name. AVOutputFormat* oformat = av_guess_format(nullptr, m_filename.c_str(), nullptr); if ( !oformat ) return false; // Allocate the context based on that format _ret = avformat_alloc_output_context2(&m_ofmt_ctx, oformat, nullptr, m_filename.c_str()); if ( _ret ) return false; // Get the codec appropriate to that format // and be able to set some parameter to that codec. AVCodec* codec = avcodec_find_encoder(oformat->video_codec); if ( !codec ) return false; #if _DEBUG printf("Codec name: %s\n", avcodec_get_name(codec->id)); const AVPixelFormat* p = codec->pix_fmts; while ( p != NULL && *p != AV_PIX_FMT_NONE ) { printf("supported pix fmt: %s\n", av_get_pix_fmt_name(*p)); ++p; } #endif // Prepare stream/context according to above variable (codec/format). AVStream* stream = avformat_new_stream(m_ofmt_ctx, codec); if ( !stream ) return false; // Allocate the context for that codec. m_o_codec_ctx = avcodec_alloc_context3(codec); if ( !m_o_codec_ctx ) return false; // Setup the 'global' parameter for the codec (Size, color format, etc) stream->codecpar->codec_id = codec->id; stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; stream->codecpar->width = m_width; stream->codecpar->height = m_height; stream->codecpar->format = AV_PIX_FMT_YUV444P; // Assign the global parameter to the context. avcodec_parameters_to_context(m_o_codec_ctx, stream->codecpar); // Set the framerate. m_o_codec_ctx->time_base = { 1, m_framerate }; #if 0 // We can enable this, but it still the default parameters anyway. if ( (_ret = av_dict_set(&m_options_dict, "preset", "medium", 0)) < 0 ) return false; if ( (_ret = av_dict_set(&m_options_dict, "crf", "23.0", 0)) < 0 ) return false; #endif avcodec_parameters_from_context(stream->codecpar, m_o_codec_ctx); // Open the file and write the header. if ( (_ret = avcodec_open2(m_o_codec_ctx, codec, &m_options_dict)) < 0 ) return false; if ( (_ret = avio_open(&m_ofmt_ctx->pb, m_filename.c_str(), AVIO_FLAG_WRITE)) < 0 ) return false; if ( (_ret = avformat_write_header(m_ofmt_ctx, &options_dict)) < 0 ) return false; #if _DEBUG // This print the stream/output format. av_dump_format(m_ofmt_ctx, 0, m_filename.c_str(), 1); #endif // Define the pts increment (the time to the next frame). if ( m_ofmt_ctx && m_ofmt_ctx->nb_streams > 0 ) m_pts_increment = av_rescale_q(1, m_o_codec_ctx->time_base, m_ofmt_ctx->streams[0]->time_base); else return false; return true;
调试过程中的发现
1. 视频元数据对比
通过ffmpeg -i test_binffmpeg.mp4 -i test_apiffmpeg.mp4对比两个视频,发现API生成的视频帧率存在微小偏差(12.10fps vs 12fps),时间基也不同。
2. ffprobe参数差异
| 参数 | 命令行值 | API值 |
|---|---|---|
| codec_time_base | 1/24 | 119/2880 |
| avg_frame_rate | 12/1 | 1440/119 |
虽然两个文件都包含120帧,但API生成的视频出现了119相关参数,后续修正帧率参数后该问题解决,但比特率问题依旧存在。
3. QP值的关键差异
进一步分析日志发现:命令行编码的平均QP值在16-22之间,而API编码的平均QP值仅为7-11——这直接导致量化程度不足,比特率飙升。尝试设置qmin与qmax后结果有所接近,但仍存在差异。
问题解释与解决方案
1. QP差异的核心原因
FFmpeg命令行工具在处理CRF模式时,会自动为H.264编码器(尤其是High 4:4:4 Predictive profile)设置一套完整的默认参数约束,而直接通过C API初始化编码器时,这些约束不会自动生效:
- 即使设置了
crf=23,编码器默认的QP范围可能非常宽松(比如qmin=0,qmax=63),导致实际编码时使用了远低于预期的QP值 mediumpreset包含的细节参数(比如参考帧数量、B帧数量、GOP大小)在API中不会自动应用,这些参数也会间接影响QP的分配
2. 完整的参数修正方案
要让API编码结果和命令行对齐,需要在avcodec_open2之前,通过字典传递所有必要的参数,并确保编码器上下文和流参数同步:
// 初始化参数字典 AVDictionary* codec_opts = nullptr; // 核心CRF与preset设置 av_dict_set(&codec_opts, "preset", "medium", 0); av_dict_set(&codec_opts, "crf", "23", 0); // 约束QP范围,对齐命令行默认值 av_dict_set(&codec_opts, "qmin", "16", 0); av_dict_set(&codec_opts, "qmax", "24", 0); // 显式指定High 4:4:4 profile av_dict_set(&codec_opts, "profile", "high444", 0); // 设置medium preset默认的编码器参数 av_dict_set(&codec_opts, "refs", "4", 0); // 参考帧数量 av_dict_set(&codec_opts, "max_b_frames", "3", 0); // B帧数量 // 调整编码器上下文的时间基与帧率 m_o_codec_ctx->time_base = (AVRational){1, m_framerate}; m_o_codec_ctx->framerate = (AVRational){m_framerate, 1}; m_o_codec_ctx->gop_size = m_framerate * 2; // 每2秒一个关键帧,和命令行默认逻辑对齐 // 同步编码器参数到流的codecpar avcodec_parameters_from_context(stream->codecpar, m_o_codec_ctx); // 打开编码器,传入参数字典 if ((_ret = avcodec_open2(m_o_codec_ctx, codec, &codec_opts)) < 0) { av_dict_free(&codec_opts); return false; } av_dict_free(&codec_opts);
3. 额外注意事项
- 确保
avformat_write_header时传递的参数字典正确(如果不需要额外复用器参数,直接传nullptr) - 编码时的PTS计算必须准确,使用
av_rescale_q正确转换时间基,避免编码器因时间戳异常调整量化策略 - 检查输入帧的像素格式是否严格为
YUV444P,并且帧宽高符合编码器的对齐要求(1080为偶数,无需额外处理)
内容的提问来源于stack exchange,提问作者Vuwox
相关产品推荐
相关产品推荐

