You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用FFmpeg库混流音视频生成MP4时帧率异常问题排查

音视频合并后帧率偏差的问题分析与修复方案

我帮你排查了下代码里的问题,帧率出现30.13fps的偏差,核心原因是输出流时间基设置不规范加上PTS/DTS校正逻辑的漏洞,导致MP4容器计算帧率时无法识别到精确的30fps。下面具体分析问题点和修复方案:

1. 输出视频流的时间基与帧率未显式对齐

你的代码在创建输出流时,仅复制了输入流的编码参数,但没有显式设置视频流的时间基和帧率。MP4容器对时间戳的精度要求很高,如果输出流的时间基没有和帧率严格对应(比如30fps对应1/30的时间基),容器会通过时间戳间隔计算出近似帧率,就会出现30.13这种偏差。

修复代码:

在创建视频输出流的部分,添加时间基和帧率的显式设置:

// 找到视频输入流后,创建输出流
AVStream* in_stream = video_context->streams[video_index_in];
AVStream* out_stream = avformat_new_stream(output_context, NULL);
// 直接复制输入流的编码参数,比临时上下文更可靠
avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
out_stream->codecpar->codec_tag = 0;
if(output_context->oformat->flags & AVFMT_GLOBALHEADER){
    out_stream->codecpar->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
// 显式设置时间基为帧率的倒数,确保和30fps严格对齐
out_stream->avg_frame_rate = in_stream->avg_frame_rate;
out_stream->r_frame_rate = in_stream->r_frame_rate;
out_stream->time_base = av_inv_q(out_stream->avg_frame_rate);

音频流也可以做同样的处理,保证时间基的正确性。

2. PTS/DTS校正逻辑的错误

你的校正逻辑存在两个明显问题:

  • 初始值错误last_video_dtslast_audio_dts初始化为0,但输入流的DTS可能不是从0开始的,这会导致第一帧的DTS被错误校正,破坏整个时间线的连续性。
  • 校正逻辑不严谨:判断条件packet.dts < (*last_dts + !(output_context->oformat->flags & AVFMT_TS_NONSTRICT))没有考虑AV_NOPTS_VALUE的完整场景,且强制修改PTS的逻辑会破坏原始时间戳的关系。

修复代码:

首先修改初始值为AV_NOPTS_VALUE

int64_t last_video_dts = AV_NOPTS_VALUE, last_audio_dts = AV_NOPTS_VALUE;

然后替换校正逻辑,参考FFmpeg源码的严谨处理:

// 仅当last_dts和当前packet的dts都有效时才校正
if ((*last_dts) != AV_NOPTS_VALUE && packet.dts != AV_NOPTS_VALUE) {
    int64_t min_expected_dts = (*last_dts) + 1;
    // 如果当前dts小于预期的最小dts,说明乱序,需要调整
    if (packet.dts < min_expected_dts) {
        int64_t delta = min_expected_dts - packet.dts;
        packet.dts = min_expected_dts;
        // 如果pts有效,同步调整pts
        if (packet.pts != AV_NOPTS_VALUE) {
            packet.pts += delta;
        }
    }
}
// 只有当当前dts有效时,才更新last_dts
if (packet.dts != AV_NOPTS_VALUE) {
    (*last_dts) = packet.dts;
}

3. 包读取逻辑的潜在问题

你当前的包读取逻辑是每次判断读音频还是视频,然后循环过滤到目标流的包,这种方式可能会跳过一些包,导致时间戳的连续性被破坏。更可靠的方式是分别缓存音频和视频的包队列,然后比较队列头部的PTS来决定输出哪个包,保证时间线的严格交错。

如果暂时不想改队列逻辑,至少要保证每次读取到非目标流的包时,不要直接丢弃(不过FFmpeg的av_read_frame不支持放回,所以更建议用队列缓存)。

4. 其他优化点

  • 替换临时CodecContext的创建逻辑:你当前创建临时AVCodecContext再复制参数的方式冗余,直接用avcodec_parameters_copy更简洁可靠(前面的修复已经包含这一点)。
  • 确保av_packet_rescale_ts的顺序:应该先处理输入流的PTS/DTS校正,再转换到输出流的时间基。

按照这些修改后,输出视频的帧率应该会精确到30fps,和FFmpeg命令行的效果一致。

内容的提问来源于stack exchange,提问作者therex

火山引擎 最新活动