限时福利领取


为什么需要关注PTS和DTS?

刚开始用FFmpeg+SDL做播放器时,我最头疼的就是音画不同步问题。明明解码没问题,但播放时声音和画面总对不上,快慢不一致。后来发现,这通常是因为没有正确处理PTS(显示时间戳)和DTS(解码时间戳)。

视频播放流程示意图

PTS和DTS到底有什么区别?

  1. DTS(Decoding Time Stamp):告诉解码器什么时候该解码这一帧
  2. PTS(Presentation Time Stamp):告诉播放器什么时候该显示这一帧

对于没有B帧的视频,PTS和DTS是一样的。但如果有B帧(双向预测帧),因为解码顺序和显示顺序不同,这两个值就会不一样。

新版FFmpeg API的处理变化

旧版FFmpeg使用avcodec_decode_video2时,PTS/DTS处理比较简单。但新的avcodec_send_packet/avcodec_receive_frameAPI更高效,但也更需要注意时序:

// 发送packet到解码器
avcodec_send_packet(codec_ctx, packet);

// 从解码器获取frame
while (avcodec_receive_frame(codec_ctx, frame) == 0) {
    // 这里处理解码后的帧
}

关键实现步骤

1. 提取和转换时间戳

从AVPacket中获取原始PTS/DTS后,需要转换成秒为单位的时间:

// 获取时间基
AVRational time_base = stream->time_base;

// 转换PTS为秒
double pts_seconds = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(time_base);

// DTS转换同理
double dts_seconds = (frame->pkt_dts == AV_NOPTS_VALUE) ? NAN : frame->pkt_dts * av_q2d(time_base);

2. 处理B帧特殊情况

遇到B帧时,解码顺序和显示顺序可能不同,需要特别注意:

// 如果没有有效PTS,尝试用DTS推算
if (frame->pts == AV_NOPTS_VALUE) {
    frame->pts = frame->pkt_dts;
}

3. SDL同步实现

音视频同步示意图

使用SDL的音频回调作为主时钟,视频帧根据音频时间进行同步:

// 音频回调中更新时间基准
void audio_callback(void* userdata, Uint8* stream, int len) {
    // ...更新音频时钟...
    audio_clock = ...;
}

// 视频播放线程
while (1) {
    double delay = frame->pts - audio_clock;
    if (delay > 0) {
        SDL_Delay(delay * 1000); // 转换为毫秒
    }
    // 显示帧...
}

性能优化要点

  1. 环形缓冲区大小:建议设置为解码速度的1.5-2倍
  2. 多线程处理:解码、音频、视频分别用不同线程
  3. 动态调整:根据CPU负载动态调整缓冲区大小

常见问题解决

  1. FFmpeg版本差异:4.0+版本对时间戳处理更严格
  2. SDL定时器精度:SDL_AddTimer精度不够,建议用SDL_Delay+手动计算
  3. 网络流处理:遇到网络波动时,可以动态调整播放速度

进一步优化方向

对于网络播放器,可以尝试实现动态调速算法:

  1. 监测缓冲区填充状态
  2. 根据缓冲情况微调播放速度
  3. 平滑过渡避免观众察觉

希望这些经验对你开发播放器有帮助!在实际项目中,时间戳处理确实是个需要特别留意的点。

Logo

音视频技术社区,一个全球开发者共同探讨、分享、学习音视频技术的平台,加入我们,与全球开发者一起创造更加优秀的音视频产品!

更多推荐