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
diff vidclk - audclk:视频当前时间减去音频当前时间(diff < 0 视频落后,diff > 0 视频超前)
sync_threshold 同步容忍阈值(通常 40~100ms),小于此值不调整,防止抖动
frame_timer "理想情况下,当前帧应该显示到什么时刻"(累积 delay 的虚拟时钟)
time 当前真实系统时间(秒)
  • time: 当前系统时间(单位:秒)
  • is->frame_timer: 上一帧实际开始显示的时刻(系统时间)
  • delay: 上一帧应该持续显示多久(由 PTS 差 + 同步校正得出)
  • is->frame_timer + delay: 上一帧应该结束显示的时刻

✅ 注意:这里讨论的是"上一帧"的显示生命周期,不是当前帧!

c 复制代码
time = av_gettime_relative() / 1000000.0;  // 获取当前时间(秒)
if (time < is->frame_timer + delay) {
    *remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time);
    goto display;  // 不换帧,继续显示上一帧
}

情景假设

上一帧(lastvp)在 t = 10.0 秒 开始显示 → frame_timer = 10.0

它应该显示 0.04 秒(约 25fps)→ delay = 0.04

所以它应该 在 t = 10.04 秒结束显示

现在系统时间是 t = 10.02 秒

此时:

time (10.02) < frame_timer + delay (10.04) → 条件成立!

那么,"继续显示上一帧"是什么意思?

答案:这一帧还没到该消失的时候,所以不能换新帧!

视频播放不是"有帧就立刻显示",而是要严格按时间表。

即使下一帧(vp)已经解码好了、放在队列里了,也不能提前显示。

必须等到上一帧的显示时间窗口完全结束。

不同时间的定义和使用:

时间类型 来源 单位 用途
time av_gettime_relative() 秒(double 判断"现在真实时间"
vp->pts 解码后的视频帧 秒(经av_q2d转换) 帧的"理想播放时刻"
is->frame_timer 上次换帧时记录的time 标记上一帧开始显示的系统时间
audio_clock 音频回调更新 主同步源(默认)

表格采用对齐式设计,通过冒号指定对齐方式(默认左对齐)。时间类型列使用代码块标记强调技术术语,单位列明确标注数据类型或转换函数。

在播放过程中,time(当前系统时间) 作为现实世界的基准,用于衡量播放进度是否"准时";vp->pts 表示每一帧在媒体时间轴上的"理想播放时刻";is->frame_timer 记录了上一帧实际开始显示时的系统时间,结合计算出的 delay(由 PTS 差和同步策略决定),确定当前帧应持续显示到何时;而 audio_clock(通常来自主音频流)作为默认的主时钟,代表"正确的播放进度"。播放器通过不断比较 time 与 frame_timer + delay 来决定是否换帧,并利用 audio_clock 与视频 PTS 的偏差动态调整 delay,从而让视频"追赶"或"等待"音频,最终实现音画同步。

三、delay实现详解

场景 diff 处理方式 效果
音频卡顿(视频超前) +0.15s 延长 delay(若帧较长) 视频暂停一帧等音频
视频解码慢(视频落后) -0.2s delay → 0,甚至丢帧 快速跳帧追赶
正常播放 ±0.02s 不处理 流畅播放
c 复制代码
static double compute_target_delay(double delay, VideoState *is)  
{  
    double sync_threshold, diff = 0;  

1. 视频主同步?直接返回原始 delay

c 复制代码
    if (is->av_sync_type == AV_SYNC_VIDEO_MASTER)  
        return delay;  

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

2. 计算视频 vs 音频的时钟差

c 复制代码
    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. 动态设置同步阈值

c 复制代码
    sync_threshold = FFMAX(AV_SYNC_THRESHOLD_MIN, FFMIN(AV_SYNC_THRESHOLD_MAX, delay));  

宏定义(通常):

c 复制代码
#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

c 复制代码
    if (!isnan(diff) && fabs(diff) < is->max_frame_duration) {  

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

情况 A:视频落后音频(diff ≤ -threshold)

c 复制代码
        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 较大)

c 复制代码
        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?

c 复制代码
last_duration = nextvp->pts - vp->pts;          // 原始帧间隔  
delay = compute_target_delay(last_duration, is); // 调整后的实际显示时长  

frame_timer 是虚拟播放进度锚点 :

c 复制代码
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

c 复制代码
// 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;
}
相关推荐
ShiMetaPi2 小时前
GM-3568JHF丨ARM+FPGA异构开发板系列教程:外设教程 07 音频
arm开发·fpga开发·音视频·fpga·rk3568
TheNextByte12 小时前
如何通过 7 种方式将视频从 iPhone 传输到 U 盘?
ios·音视频·iphone
戴着眼镜看不清2 小时前
ComfyUI 阿波罗AI专属ComfyUI插件-支持图像、视频、对话、音频等60+专业节点最新完成安装使用教程
人工智能·音视频·comfyui·nanobanana·sora2
cver12316 小时前
足球视频检测数据集介绍-160张图片-智能体育转播 运动数据分析 自动化视频剪辑 裁判辅助系统 青训技术分析 虚拟现实体验
数据分析·自动化·音视频
来鸟 鸣间17 小时前
MIPI D-PHY 理解
linux·音视频·sensor·mipi
美狐美颜SDK开放平台20 小时前
专业直播美颜SDK如何打造?美型功能开发思路与方案分享
大数据·人工智能·音视频·美颜sdk·直播美颜sdk·视频美颜sdk
你好音视频20 小时前
FFmpeg FLV解码器原理深度解析
c++·ffmpeg·音视频
行业探路者1 天前
如何利用活码生成产品画册二维码?
学习·音视频·语音识别·二维码·设备巡检
web前端进阶者1 天前
webRTC指定设备加自定义用户头像
音视频·webrtc