FFmepg-- 40-ffplay源码- 视频同步刷新

一 音视频同步

让视频帧的显示节奏与音频播放节奏保持一致,避免“画面快/慢于声音”。

为此,ffplay:

  • 以音频时钟为主时钟(默认);
  • 动态调整每帧视频的实际显示时长(不是固定按 PTS 差);
  • 利用一个虚拟的 frame_timer 来模拟“理想播放进度”。

二、关键变量说明

帧率(Frames Per Second,简称 FPS)是指每秒钟显示的图像帧数。

25 FPS 表示:每秒播放 25 张画面;
10 FPS 表示:每秒播放 10 张画面。
帧率越高,画面越流畅;帧率越低,画面越“卡顿”。

delay 指的是相邻两帧之间的时间间隔(单位:秒)。

帧率与 delay 的关系为:
d e l a y = 1 F P S delay = \frac{1}{FPS} delay=FPS1

25 FPS → delay = 1 / 25 = 0.04 秒 = 40 毫秒
10 FPS → delay = 1 / 10 = 0.1 秒 = 100 毫秒

帧率越高,帧与帧之间的时间间隔越短(delay 越小);
帧率越低,delay 越大。

变量含义
delay当前帧理论应显示的时长(单位:秒),初始为 nextvp->pts - vp->pts
diffvidclk - audclk:视频当前时间减去音频当前时间(diff < 0 视频落后,diff > 0 视频超前)
sync_threshold同步容忍阈值(通常 40~100ms),小于此值不调整,防止抖动
frame_timer“理想情况下,当前帧应该显示到什么时刻”(累积 delay 的虚拟时钟)
time当前真实系统时间(秒)

三、compute_target_delay() 逐行详解

static double compute_target_delay(double delay, VideoState *is)  
{  
    double sync_threshold, diff = 0;  
1. 视频主同步?直接返回原始 delay
    if (is->av_sync_type == AV_SYNC_VIDEO_MASTER)  
        return delay;  

极少使用。此时视频自己走自己的节奏,音频去追视频。默认是 AV_SYNC_AUDIO_MASTER,所以这行通常跳过。

2. 计算视频 vs 音频的时钟差
    diff = get_clock(&is->vidclk) - get_clock(&is->audclk);  

get_clock() 返回的是基于 PTS + 系统时间校准后的“当前播放时间”。

  • 音频播到 10.5 秒,视频显示的是 10.3 秒 → diff = -0.2(视频落后 200ms)
  • 音频 10.5 秒,视频 10.7 秒 → diff = +0.2(视频超前)
3. 动态设置同步阈值
    sync_threshold = FFMAX(AV_SYNC_THRESHOLD_MIN, FFMIN(AV_SYNC_THRESHOLD_MAX, delay));  

宏定义(通常):

#define AV_SYNC_THRESHOLD_MIN 0.04   // 40ms  
#define AV_SYNC_THRESHOLD_MAX 0.1    // 100ms  

目的:帧率越低(delay 越大),允许的偏差越大。

  • 25fps(delay=0.04s)→ threshold ≈ 40ms
  • 10fps(delay=0.1s)→ threshold = 100ms
4. 主同步逻辑:根据 diff 调整 delay
    if (!isnan(diff) && fabs(diff) < is->max_frame_duration) {  

排除无效时钟(如未初始化);max_frame_duration 通常是 10 秒,防止极端 PTS 跳变导致误判。

情况 A:视频落后音频(diff ≤ -threshold)
        if (diff <= -sync_threshold) {  
            delay = FFMAX(0, delay + diff);  
        }  

diff 是负数,比如 -0.08(落后 80ms),delay=0.04
delay = 0.04 + (-0.08) = -0.04 → 被 FFMAX(0, ...) 截断为 0
效果:立即显示下一帧(甚至可能连续跳多帧,配合 framedrop)

情况 B:视频超前音频(diff ≥ threshold 且 delay 较大)
        else if (diff >= sync_threshold && delay > AV_SYNC_FRAMEDUP_THRESHOLD) {  
            delay = delay + FFMIN(diff, sync_threshold);  
        }  

AV_SYNC_FRAMEDUP_THRESHOLD 通常为 0.1(100ms)

  • 高帧率视频(如 60fps)即使超前也不复制帧,而是靠后续自然同步。
  • 低帧率视频(如 5fps,delay=0.2),diff=0.15 → 新 delay = 0.2 + min(0.15, 0.1) = 0.3
    效果:当前帧多显示 100ms,等音频追上

四、video_refresh() 中如何使用这个 delay?

last_duration = nextvp->pts - vp->pts;          // 原始帧间隔  
delay = compute_target_delay(last_duration, is); // 调整后的实际显示时长  
关键:frame_timer 是虚拟播放进度锚点
time = av_gettime_relative() / 1000000.0;  // 当前真实时间(秒)  
if (time < is->frame_timer + delay) {  
    // 还没到换帧时间 → 继续显示当前帧  
    *remaining_time = FFMIN(delay - (time - is->frame_timer), *remaining_time);  
} else {  
    // 到时间了 → 换帧!  
    is->frame_timer += delay;                // 推进虚拟时钟  
    if (is->frame_timer < time)  
        is->frame_timer = time;              // 防止累计误差过大  
    frame_queue_next(&is->pictq);            // 出队下一帧  
    is->force_refresh = 1;                   // 标记需要重绘  
}  

code

// video.c 中的核心函数
static double compute_target_delay(double delay, VideoState *is)
{
    double sync_threshold, diff = 0;

    // 如果是视频主同步(很少用),直接返回原始帧间隔
    if (is->av_sync_type == AV_SYNC_VIDEO_MASTER)
        return delay;

    // 获取视频时钟与音频时钟的差值
    diff = get_clock(&is->vidclk) - get_clock(&is->audclk);

    // 同步阈值:取 [0.04, min(0.1, delay)] 之间的值
    sync_threshold = FFMAX(AV_SYNC_THRESHOLD_MIN, 
                          FFMIN(AV_SYNC_THRESHOLD_MAX, delay));

    if (!isnan(diff) && fabs(diff) < is->max_frame_duration) {
        if (diff <= -sync_threshold) {
            // 视频落后音频 → 缩短显示时间(尽快显示下一帧)
            delay = FFMAX(0, delay + diff);
        } else if (diff >= sync_threshold && delay > AV_SYNC_FRAMEDUP_THRESHOLD) {
            // 视频超前音频 → 延长显示时间(等音频追上)
            delay = delay + FFMIN(diff, sync_threshold);
        }
        // 注意:轻微超前(< threshold)时不处理,避免抖动
    }

    return delay;
}
配合调用它的 video_refresh 函数中的逻辑:

c
编辑
// 在 video_refresh() 中
last_duration = nextvp->pts - vp->pts;          // 理论帧间隔
delay = compute_target_delay(last_duration, is); // 实际应显示多久

time = av_gettime_relative() / 1000000.0;
if (time < is->frame_timer + delay) {
    // 还没到显示下一帧的时间 → 继续显示当前帧
    *remaining_time = FFMIN(delay - (time - is->frame_timer), *remaining_time);
} else {
    // 到时间了 → 换帧,并更新 frame_timer
    is->frame_timer += delay;
    if (is->frame_timer < time) is->frame_timer = time;
    frame_queue_next(&is->pictq);
    is->force_refresh = 1;
}

五、总结:同步刷新逻辑的三大精髓

精髓说明
动态 delay 调整不是固定按 PTS 差显示,而是根据 A/V 时钟差实时修正
阈值控制 + 滤波小偏差忽略,大偏差才干预,避免抖动
虚拟时钟(frame_timer)解耦“理论播放进度”和“系统时间”,保证长期同步稳定

六、典型场景模拟

场景diff处理方式效果
音频卡顿(视频超前)+0.15s延长 delay(若帧较长)视频暂停一帧等音频
视频解码慢(视频落后)-0.2sdelay → 0,甚至丢帧快速跳帧追赶
正常播放±0.02s不处理流畅播放
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

八月的雨季997

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值