zoukankan      html  css  js  c++  java
  • ffplay源码分析07 ---- 音视频同步

     =====================================================

    ffplay源码分析01 ---- 框架

    ffplay源码分析02 ---- 数据读取线程

    ffplay源码分析03 ---- 视频解码线程

    ffplay源码分析03 ---- 音频解码线程

    ffplay源码分析04 ---- 音频输出

    ffplay源码分析05 ---- 音频重采样

    ffplay源码分析06 ---- 视频输出

    ffplay源码分析07 ---- 音视频同步

    =====================================================

    ⾳视频同步策略

    1. 以⾳频为基准,同步视频到⾳频(AV_SYNC_AUDIO_MASTER
      • 视频慢了则丢掉部分视频帧(视觉->画⾯跳帧)
      • 视频快了则继续渲染上⼀帧 
    2. 以视频为基准,同步⾳频到视频(AV_SYNC_VIDEO_MASTER
      • ⾳频慢了则加快播放速度(或丢掉部分⾳频帧,丢帧极容易听出来断⾳)
      • ⾳频快了则放慢播放速度(或重复上⼀帧 )
      • ⾳频改变播放速度时涉及到重采样
    3. 以外部时钟为基准,同步⾳频和视频到外部时钟(AV_SYNC_EXTERNAL_CLOCK
      • 前两者的综合,根据外部时钟改变播放速度
    4. 视频和⾳频各⾃输出,即不作同步处理(FREE RUN

    一般是第一种,就是将视频同步到音频。

    音视频同步基本概念

    • DTS(Decoding Time Stamp):即解码时间戳,这个时间戳的意义在于告诉播放器该在什么时候解码这⼀帧的数据。
    • PTS(Presentation Time Stamp):即显示时间戳,这个时间戳⽤来告诉播放器该在什么时候显示这⼀帧的数据。
    • timebase 时基:pts的值的真正单位
    • ffplay中的pts,ffplay在做⾳视频同步时使⽤秒为单位,使⽤double类型去标识pts,在ffmpeg内部不会⽤浮点数去标记pts。
    • Clock 时钟

    当视频流中没有 B 帧时,通常 DTS 和 PTS 的顺序是⼀致的。但存在B帧的时候两者的顺序就不⼀致了。

    1. pts是presentation timestamp的缩写,即显示时间戳,⽤于标记⼀个帧的呈现时刻,它的单位由timebase决定。timebase的类型是结构体AVRational(⽤于表示分数):

    typedef struct AVRational{
        int num; ///< Numerator
        int den; ///< Denominator
    } AVRational;

    如timebase={1, 1000} 表示千分之⼀秒(毫秒),那么pts=1000,即为pts*1/1000 = 1秒,那么这⼀帧就需要在第⼀秒的时候呈现。

    将AVRatioal结构转换成double:

    static inline double av_q2d(AVRational a){
        return a.num / (double) a.den;
    }

    计算时间戳:

    timestamp(秒) = pts * av_q2d(st->time_base)

    计算帧时长:

    time(秒) = st->duration * av_q2d(st->time_base)

    不同时间基之间的转换:

    int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq)

    在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. 需要不断“对时”。对时的⽅法set_clock_at(Clock *c, double pts, int serial,double time) ,需要⽤pts、serial、time(系统时间)进⾏对时。

    2. 获取的时间是⼀个估算值。估算是通过对时时记录的pts_drift估算的。pts_drift是最精华的设计,⼀定要理解。

    可以看这个图来帮助理解:

    图中央是⼀个时间轴(time是⼀直在按时间递增),从左往右看。⾸先我们调⽤set_clock 进⾏⼀次对时,假设这时的pts 是落后时间time 的,那么计算pts_drift = pts - time ,计算出pts和time的相对差值。这个可以理解为pts到time的距离,所以pts = time + pts_drift。pts是随着时间增加的,系统时间也在同步增加,这个距离pts_drift是不会变的。

    接着,过了⼀会⼉,且在下次对时前,通过get_clock 来查询时间,因为set_clock时的pts 已经过时,不能直接拿set_clock时的pts当做这个时钟的时间。不过我们前⾯计算过pts_drift ,所以我们可以通过当前时刻的时间来估算当前时刻的pts: pts = time + pts_drift 。

    FFmpeg中的时间单位

    AV_TIME_BASE

    • 定义#define AV_TIME_BASE 1 000 000
    • ffmpeg中的内部计时单位(时间基)

    AV_TIME_BASE_Q

    • 定义#define AV_TIME_BASE_Q (AVRational){1, AV_TIME_BASE}
    • ffmpeg内部时间基的分数表示,实际上它是AV_TIME_BASE的倒数

    时间基转换公式

    • timestamp(ffmpeg内部时间戳) = AV_TIME_BASE * time(秒)
    • time(秒) = AV_TIME_BASE_Q * timestamp(ffmpeg内部时间戳)
  • 相关阅读:
    魅族Flyme OS使用小技巧
    Android应用里面调用Google Earth应用
    关于连接网络时抛出“android.os.NetworkOnMainThreadException”异常问题
    《深入理解计算机系统》实验一 —Data Lab
    《深入理解计算机系统》(CSAPP)读书笔记 —— 第一章 计算机系统漫游
    使用 VB.NET 封装 Javascript 常用功能(这是在asp.net中的)
    Net线程问题解答
    线程同步
    利用辅助线程更新用户界面UI
    FFMPEG参数说明
  • 原文地址:https://www.cnblogs.com/vczf/p/14192668.html
Copyright © 2011-2022 走看看