文档目录

一、为什么需要两个时间戳

I 帧:不需要参考其他帧,解码顺序 = 显示顺序
P 帧:参考前一帧,解码顺序 = 显示顺序

B 帧:需要参考前后帧 → 解码顺序 ≠ 显示顺序!

视频帧顺序(显示顺序):
  I B B P B B P B B ...
  1 2 3 4 5 6 7 8 9 ...

解码器需要的顺序(解码顺序):
  I P B B P B B ...  ← 因为解码 B 帧需要前后的 P 和 I
  1 4 2 3 7 5 6 ...

所以每个帧有两个时间戳:
  DTS(Decode TimeStamp):解码器按这个顺序解码
  PTS(Present TimeStamp):播放器按这个顺序显示

举个实际例子:
  帧序列:I1(pts=0) B2(pts=1) B3(pts=2) P4(pts=3) B5(pts=4) B6(pts=5) P7(pts=6)
  解码顺序:I1(dts=0) P4(dts=1) B2(dts=2) B3(dts=3) P7(dts=4) B5(dts=5) B6(dts=6)
          ↓解码          ↓需要 P4 解码后才能解 B2
  显示顺序:I1 B2 B3 P4 B5 B6 P7

二、在 FFmpeg 中拿正确的 PTS

// 解码后的 frame 中的 pts 要用 stream 的 time_base 转换成秒
void print_frame_timestamp(AVFormatContext* fmt_ctx, int stream_idx, AVFrame* frame) {
    AVStream* stream = fmt_ctx->streams[stream_idx];
    AVRational tb = stream->time_base;

    // frame->pts 的单位是 time_base
    // 例如 time_base = {1, 1000} 表示 pts 单位是毫秒
    // 例如 time_base = {1, 90000} 表示 pts 单位是 1/90000 秒(H.264 常用)

    double pts_seconds = frame->pts * av_q2d(tb);
    printf("PTS = %ld (raw), %.3f (seconds)\n", frame->pts, pts_seconds);
}

// 把 packet 的时间戳从编码器时基转换到流时基
void convert_packet_ts(AVFormatContext* fmt_ctx, AVCodecContext* enc_ctx,
                        AVStream* stream, AVPacket* packet) {
    av_packet_rescale_ts(packet,
                         enc_ctx->time_base,       // 编码器时基
                         stream->time_base);       // 容器时基
}