You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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_base1/24119/2880
avg_frame_rate12/11440/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值
  • medium preset包含的细节参数(比如参考帧数量、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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 12:27:27