文章目录
一 音视频同步
让视频帧的显示节奏与音频播放节奏保持一致,避免“画面快/慢于声音”。
为此,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 | 当前真实系统时间(秒) |
三、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.2s | delay → 0,甚至丢帧 | 快速跳帧追赶 |
| 正常播放 | ±0.02s | 不处理 | 流畅播放 |
23

被折叠的 条评论
为什么被折叠?



