3、音视频时钟模型:系统时钟、音频时钟、视频时钟的同步机制与主时钟选择策略

做音视频开发,绕不开一个核心问题——时钟。

你想想看,音频和视频是两个完全独立的硬件模块。音频有它的采样率,视频有它的帧率。它们各自按自己的节奏跑,凭什么能对上?

说白了,我们需要一套时钟模型,让它们听同一个指挥。

3.1 三种时钟,各司其职

一个典型的播放器或推流端里,至少存在三种时钟:

时钟类型 驱动源 特点
系统时钟 操作系统 CLOCK_MONOTONIC 单调递增,不受 NTP 调整影响,精度高
音频时钟 音频设备驱动(ALSA / WASAPI / CoreAudio) 以音频采样率为基准,通常最稳定
视频时钟 视频帧的 PTS / 显示设备刷新率 受渲染延迟、丢帧影响,波动较大

我刚开始做播放器时,以为直接用系统时间戳就行。结果发现音频和视频的 PTS 根本对不上——因为音频驱动有自己的晶振,系统时钟也有自己的晶振,两个晶振的漂移方向还不一样。

核心认知:三种时钟的精度和稳定性不同。音频时钟最可靠,视频时钟最不可靠,系统时钟居中但最通用。

3.2 主时钟选择策略

既然有三个时钟,总得选一个当老大。这就是主时钟(Master Clock)的概念。

我个人习惯按场景来选:

  • 音频为主时钟:最常见。因为人耳对音频抖动的敏感度远高于眼睛。只要音频连续,视频稍微跳一帧,用户感知不强。
  • 视频为主时钟:少见。比如屏幕录制场景,视频帧率必须严格对齐显示器刷新率,音频可以适当变速。
  • 系统时钟为主时钟:用于纯网络同步场景,比如 WebRTC 的 RTP 时间戳对齐。

嗯,这里要注意:选音频做主时钟,不代表视频完全跟着音频跑。而是说,音频时钟作为参考基准,视频通过重采样或丢帧/插帧来对齐。

我的经验:在 Android 上做低延迟播放时,我遇到过音频时钟突然跳变的情况(驱动 bug)。后来加了一层时钟滤波,用滑动平均来平滑音频时钟的瞬时抖动。效果立竿见影。

3.3 同步机制:谁跟谁对齐?

选好主时钟后,剩下的时钟需要向它看齐。具体怎么做?

3.3.1 音频为主时钟时的同步流程

  1. 音频回调函数每次写入数据时,记录当前音频时钟值。
  2. 视频渲染线程拿到视频帧的 PTS,与音频时钟比较。
  3. 如果视频帧 PTS 落后音频时钟太多,直接丢帧。
  4. 如果视频帧 PTS 超前音频时钟太多,等待或插入重复帧。

我曾经在项目中遇到一个坑:音频时钟和视频 PTS 的时间基不一样。音频用 44100 Hz 采样率,视频用 90000 Hz 时间基。直接比较数值?那肯定错得离谱。

正确的做法是统一换算成微秒或毫秒:

// 音频时钟:当前播放到第 44100 个采样点
// 采样率 44100 Hz,时间基 1/44100
audio_time_us = audio_clock * 1000000 / 44100;

// 视频 PTS:90000 时间基
video_time_us = video_pts * 1000000 / 90000;

// 现在可以比较了
if (video_time_us < audio_time_us - 20000) {
    // 视频落后超过 20ms,丢帧
} else if (video_time_us > audio_time_us + 20000) {
    // 视频超前超过 20ms,等待
}

3.3.2 视频为主时钟时的同步流程

这个场景我遇到得不多,但有一次做 VR 头显的渲染优化时用过。VR 对帧率要求极高,音频可以稍微拉伸或压缩来匹配视频。

  • 视频渲染完成一帧后,记录当前视频时钟。
  • 音频重采样模块根据视频时钟的进度,动态调整播放速度。
  • 调整范围通常控制在 ±5% 以内,否则人耳能感知到音调变化。

注意:视频为主时钟时,音频变速处理必须使用高质量重采样算法(如 libsamplerate 的 SRC_SINC_BEST_QUALITY)。简单的线性插值会导致明显的音质劣化。

3.4 时钟模型框架图

下面这张图展示了我常用的三层时钟模型。你可以看到主时钟如何驱动其他模块:

音视频时钟模型框架图 音视频时钟模型与主时钟选择 主时钟选择器 系统时钟 CLOCK_MONOTONIC 音频时钟 采样率驱动 视频时钟 PTS / 帧率 同步引擎 音频时钟 → 视频时钟对齐(丢帧/插帧) 或 视频时钟 → 音频时钟对齐(重采样) 音画同步输出 注:主时钟选择器根据场景动态切换, 默认优先选择音频时钟。

3.5 实际项目中的避坑指南

理论说完了,聊聊我踩过的坑。

我曾经犯过的错:在 iOS 上做直播推流时,我直接用 CACurrentMediaTime() 作为系统时钟。结果发现音频采集回调的时钟和这个系统时钟有固定的偏移。后来改用音频设备的 AudioUnit 时间戳才解决。

几个关键点总结一下:

  • 不要混用不同时间基的时钟。统一换算成微秒或纳秒再比较。
  • 音频时钟优先。除非你有特殊需求,否则永远让视频去迁就音频。
  • 加一层滤波。原始时钟值可能有瞬时抖动,用滑动平均或卡尔曼滤波平滑一下。
  • 预留同步容差。±20ms 以内人眼基本感知不到,没必要追求绝对精确。

一个小技巧:调试时把三种时钟的差值打印到日志里。如果发现音频时钟和系统时钟的偏差持续增大,说明硬件晶振漂移严重,需要考虑时钟重同步。

好了,时钟模型这部分就讲到这里。记住一句话:选好主时钟,统一时间基,剩下的就是工程细节了。