文档目录

一、为什么用音频做主时钟

音频时钟:声卡以固定采样率播放,每一帧时长精确可计算
  一帧 1024 个样本,48000Hz → 每帧时长 = 1024/48000 = 21.33ms
  声卡中断或 DMA 传输时精确触发

视频时钟:显示器刷新率不稳定(VSync/Gsync/丢帧)
  一帧 60fps → 16.67ms
  实际可能因为解码延迟、渲染延迟而漂移

所以:
  播放器初始化时记录音频开始播放的时钟
  每一帧音频计算出当前的"期望位置"
  视频根据音频位置决定"是丢帧还是重复帧"

二、实现逻辑

// 最简单的音视频同步逻辑(伪代码)

struct SyncClock {
    // 音频时钟(主时钟)
    int64_t audio_start_time;          // 开始播放时的系统时间(毫秒)
    size_t total_samples_played;       // 已播放的总采样数
    int sample_rate;                   // 采样率

    // 获取当前音频播放位置(毫秒)
    double audio_clock() {
        double seconds = (double)total_samples_played / sample_rate;
        return seconds * 1000.0;
    }

    // 判断视频帧是否同步
    SyncAction check_video_sync(double video_pts_ms) {
        double audio_pts_ms = audio_clock();
        double diff = video_pts_ms - audio_pts_ms;

        // 偏差阈值
        if (diff < -100.0) {
            // 视频比音频快了 100ms 以上 → 需要等待(重复上一帧)
            return SyncAction::DELAY;
        } else if (diff > 100.0) {
            // 视频比音频慢了 100ms 以上 → 需要丢帧
            return SyncAction::DROP;
        } else {
            // 在同步范围内,正常显示
            return SyncAction::SHOW;
        }
    }
};

enum class SyncAction { SHOW, DELAY, DROP };

三、同步策略对比

视频快了(视频 PTS < 音频时钟):
    策略一:把等待时间计入,推迟显示当前帧
    策略二:重复显示上一帧(画面静止,但感觉不到)
    后果:画面延迟增大,但不会卡顿

视频慢了(视频 PTS > 音频时钟):
    策略一:直接丢弃当前帧(跳帧)
    策略二:跳过解码,直接拿下一帧
    后果:画面有轻微跳帧,但声音保持连续

经验法则:
    快 20ms 以内 → 忽略(人眼感觉不到)
    快 20-100ms → 延迟显示
    快 100ms+ → 丢帧

    播放器选择"丢帧保留音频同步":
    声音卡顿是最容易被注意到的,画面稍微跳一下在可接受范围。