ffplay.c学习-6-⾳视频同步基础

ffplay.c学习-6-⾳视频同步基础


目录

  1. ⾳视频同步策略
  2. ⾳视频同步概念
  3. FFmpeg中的时间单位
  4. ⾳视频时间换算的问题
  5. 不同结构体的time_base/duration分析
  6. 不同结构体的PTS/DTS分析
  7. ffplay中PTS的转换流程分析
    1. Video Frame PTS的获取
    2. Audio Frame PTS的获取

  1. 由于⾳频和视频的输出不在同⼀个线程,⽽且,也不⼀定会同时解出同⼀个pts的⾳频帧和视频帧。更有甚者,编码或封装的时候可能pts还是不连续的,或有个别错误的。因此,在进⾏⾳频和视频的播放时,需要对⾳频和视频的播放速度、播放时刻进⾏控制,以实现⾳频和视频保持同步,即所谓的⾳视频同步。
  2. 在ffplay中,⾳频(audio)和视频(video)有各⾃的输出线程,其中⾳频的输出线程是sdl的⾳频输出回调线程,video的输出线程是程序的主线程。

1. ⾳视频同步策略

  1. ⾳视频的同步策略,⼀般有如下⼏种:

    1. 以⾳频为基准,同步视频到⾳频(AV_SYNC_AUDIO_MASTER)
      1. 视频慢了则丢掉部分视频帧(视觉->画⾯跳帧)
      2. 视频快了则继续渲染上⼀帧
    2. 以视频为基准,同步⾳频到视频(AV_SYNC_VIDEO_MASTER)
      1. ⾳频慢了则加快播放速度(或丢掉部分⾳频帧,丢帧极容易听出来断⾳)
      2. ⾳频快了则放慢播放速度(或重复上⼀帧 )
      3. ⾳频改变播放速度时涉及到重采样
    3. 以外部时钟为基准,同步⾳频和视频到外部时钟(AV_SYNC_EXTERNAL_CLOCK)
      1. 前两者的综合,根据外部时钟改变播放速度
    4. 视频和⾳频各⾃输出,即不作同步处理(FREE RUN)
  2. 由于⼈⽿对于声⾳变化的敏感度⽐视觉⾼,因此,⼀般采样的策略是将视频同步到⾳频,即对画⾯进⾏适当的丢帧或重复以追赶或等待⾳频。

  3. 特殊地,有时候会碰到⼀些特殊封装(或者有问题的封装),此时就不作同步处理,各⾃为主时钟,进⾏播放。

  4. 在ffplay中实现了上述前3种的同步策略。由 sync 参数控制:

{ "sync", HAS_ARG | OPT_EXPERT, { .func_arg = opt_sync }, "set audiovideo sync. type (type=audio/video/ext)", "type" },
  1. ⽐如ffplay source.200kbps.768x320.flv -sync video设置以video master

2. ⾳视频同步概念

  1. 在深⼊代码了解其实现前,需要先简单了解下⼀些结构体和概念。
    1. DTS(Decoding Time Stamp):即解码时间戳,这个时间戳的意义在于告诉播放器该在什么时候解码这⼀帧的数据。
    2. PTS(Presentation Time Stamp):即显示时间戳,这个时间戳⽤来告诉播放器该在什么时候显示这⼀帧的数据。
    3. timebase 时基:pts的值的真正单位
    4. ffplay中的pts,ffplay在做⾳视频同步时使⽤秒为单位,使⽤double类型去标识pts,在ffmpeg内部不会⽤浮点数去标记pts。
    5. Clock 时钟
  2. 当视频流中没有 B 帧时,通常 DTS 和 PTS 的顺序是⼀致的。但存在B帧的时候两者的顺序就不⼀致了。
    1. pts是presentation timestamp的缩写,即显示时间戳,⽤于标记⼀个帧的呈现时刻,它的单位由timebase决定。timebase的类型是结构体AVRational(⽤于表示分数):
    /**
     * Rational number (pair of numerator and denominator).
     */
    typedef struct AVRational{
        int num; ///< Numerator
        int den; ///< Denominator
    } AVRational;
    
  3. 如 timebase={1, 1000} 表示千分之⼀秒(毫秒),那么pts=1000,即为pts*1/1000 = 1秒,那么这⼀帧就需要在第⼀秒的时候呈现
  4. 将AVRatioal结构转换成double
static inline double av_q2d(AVRational a)return a.num / (double) a.den;
}
  1. 计算时间戳
timestamp() = pts * av_q2d(st->time_base)
  1. 计算帧时⻓
time() = st->duration * av_q2d(st->time_base)
  1. 不同时间基之间的转换
int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq)
  1. 在ffplay中,将pts转化为秒,⼀般做法是: pts * av_q2d(timebase)
  2. 在做同步的时候,我们需要⼀个"时钟"的概念,⾳频、视频、外部时钟都有⾃⼰独⽴的时钟,各⾃set各⾃的时钟,以谁为基准(master), 其他的则只能get该时钟进⾏同步,ffplay定义的结构体是Clock:
// 这里讲的系统时钟 是通过av_gettime_relative()获取到的时钟,单位为微妙
typedef struct Clock {
    double	pts;            // 时钟基础, 当前帧(待播放)显示时间戳,播放后,当前帧变成上一帧
    // 当前pts与当前系统时钟的差值, audio、video对于该值是独立的
    double	pts_drift;      // clock base minus time at which we updated the clock
    // 当前时钟(如视频时钟)最后一次更新时间,也可称当前时钟时间
    double	last_updated;   // 最后一次更新的系统时钟
    double	speed;          // 时钟速度控制,用于控制播放速度
    // 播放序列,所谓播放序列就是一段连续的播放动作,一个seek操作会启动一段新的播放序列
    int	serial;             // clock is based on a packet with this serial
    int	paused;             // = 1 说明是暂停状态
    // 指向packet_serial
    int *queue_serial;      /* pointer to the current packet queue serial, used for obsolete clock detection */
} Clock;
  1. 这个时钟的⼯作原理是这样的:
    1. 需要不断“对时”。对时的⽅法 set_clock_at(Clock *c, double pts, int serial,double time) ,需要⽤pts、serial、time(系统时间)进⾏对时。
    2. 获取的时间是⼀个估算值。估算是通过对时时记录的pts_drift估算的。pts_drift是最精华的设计,⼀定要理解。
  2. 可以看这个图来帮助理解:
    在这里插入图片描述
  3. 图中央是⼀个时间轴(time是⼀直在按时间递增),从左往右看。⾸先我们调⽤ set_clock 进⾏⼀次对时,假设这时的 pts 是落后时间 time 的,那么计算 pts_drift = pts - time ,计算出pts和time的相对差值。
  4. 接着,过了⼀会⼉,且在下次对时前,通过 get_clock 来查询时间,因为set_clock时的 pts 已经过时,不能直接拿set_clock时的pts当做这个时钟的时间。不过我们前⾯计算过 pts_drift ,也就是 pts和 time 的差值,所以我们可以通过当前时刻的时间来估算当前时刻的pts: pts = time +pts_drift 。
  5. ⼀般time会取CLOCK_MONOTONIC(单调递增的时钟),即系统开机到现在的时间.
  6. ffplay使⽤ffmpeg提供的av_gettime_relative()函数
    在这里插入图片描述

3. FFmpeg中的时间单位

  1. AV_TIME_BASE
    1. 定义#define AV_TIME_BASE 1 000 000
    2. ffmpeg中的内部计时单位(时间基)
  2. AV_TIME_BASE_Q
    1. 定义#define AV_TIME_BASE_Q (AVRational){1, AV_TIME_BASE}
    2. ffmpeg内部时间基的分数表示,实际上它是AV_TIME_BASE的倒数
  3. 时间基转换公式
    1. timestamp(ffmpeg内部时间戳) = AV_TIME_BASE * time(秒)
    2. time(秒) = AV_TIME_BASE_Q * timestamp(ffmpeg内部时间戳)

4. ⾳视频时间换算的问题

  1. 标准时间 秒(seconds)
  2. 标准时间 微秒(microsecond)
  3. ⾃定义时间单位 (a/b 秒)
  4. 以⾳频AAC⾳频帧举例,如果pts以1/采样率为单位,⽐如44.1khz, 则时间单位是1/44100,因此PTS表示:
    1. 第⼀帧 PTS1 = 0
    2. 第⼆帧 PTS2 = 1024
    3. 第三帧 PTS2 = 2048
    4. 播放的时候要将PTS换算成秒的单位,则PTS1 = 0 1/44100, PTS2 = 10241/44100, PTS3 =2048*1/44100= 0.046439
      5.

5. 不同结构体的time_base/duration分析

  1. ffmpeg存在多个时间基准(time_base),对应不同的阶段(结构体),每个time_base具体的值不⼀样,ffmpeg提供函数在各个time_base中进⾏切换。
  2. AVFormatContext
    1. duration:整个码流的时⻓,获取正常时⻓的时候要除以AV_TIME_BASE,得到的结果单位是秒
  3. AVStream
    1. time_base:单位为秒,⽐如AAC⾳频流,他可能是{1,44100}TS流,按{1, 90khz}
    2. duration:表示该数据流的时⻓,以AVStream->time_base 为单位
  4. AVStream的time_base是在demuxer或者muxer内设置的,以TS,FLV,MP4为例⼦:
    1. TS
      avpriv_set_pts_info(st, 33, 1, 90000) (mpegts.c和mpegtsenc.c)
    2. FLV
      avpriv_set_pts_info(st, 32, 1, 1000) (flvdec.c)
      avpriv_set_pts_info(s->streams[i], 32, 1, 1000) (flvenc.c)
    3. MP4
      avpriv_set_pts_info(st, 64, 1, sc->time_scale); (mov.c)
      avpriv_set_pts_info(st, 64, 1, track->timescale); (movenc.c)

6. 不同结构体的PTS/DTS分析

  1. 不同结构体下,pts和dts使⽤哪个time_base来表示?
  2. AVPacket
    pts:以AVStream->time_base为单位
    dts:以AVStream->time_base为单位
    duration:以AVStream->time_base为单位
  3. AVFrame
    pts:以AVStream->time_base为单位
    pkt_pts和pkt_dts:拷⻉⾃AVPacket,同样以AVStream->time_base为单位
    duration:以AVStream->time_base为单位

7. ffplay中PTS的转换流程分析

1. Video Frame PTS的获取
  1. PTS校正
    1. frame->pts = frame->best_effort_timestamp;
    2. 这⾥为什么不⽤AVFrame中的pts来直接计算呢?其实⼤多数情况下AVFrame的pts和best_effort_timestamp值是⼀样的
    /**
     * frame timestamp estimated using various heuristics, in stream time base
     * - encoding: unused
     * - decoding: set by libavcodec, read by user.
     */
    int64_t best_effort_timestamp;
2. Audio Frame PTS的获取
  1. ffplay有3次对于Audio的pts进⾏转换
    1. 第⼀次 将其由AVStrean->time_base转换为(1/采样率)
      frame->pts = av_rescale_q(frame->pts, d->avctx->pkt_timebase, tb);
  2. 第⼆次 将其由(1/采样率)转换为秒
    1. af->pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
  3. 第三次 根据实际拷⻉给sdl的数据⻓度做调整
    1. audio_pts = is->audio_clock -(double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值