视频的播放过程可以简单理解为一帧一帧的画面按照时间顺序呈现出来的过程,就像在一个本子的每一页画上画,然后快速翻动的感觉。

       但是在实际应用中,并不是每一帧都是完整的画面,因为如果每一帧画面都是完整的图片,那么一个视频的体积就会很大,这样对于网络传输或者视频数据存储来说成本太高,所以通常会对视频流中的一部分画面进行压缩(编码)处理。由于压缩处理的方式不同,视频中的画面帧就分为了不同的类别,其中包括:I 帧、P 帧、B 帧。
I、P、B 帧

I 帧、P 帧、B 帧的区别在于:

    I 帧(Intra coded frames):I 帧图像采用帧内编码方式,即只利用了单帧图像内的空间相关性,而没有利用时间相关性。I 帧使用帧内压缩,不使用运动补偿,由于 I 帧不依赖其它帧,所以是随机存取的入点,同时是解码的基准帧。I 帧主要用于接收机的初始化和信道的获取,以及节目的切换和插入,I 帧图像的压缩倍数相对较低。I 帧图像是周期性出现在图像序列中的,出现频率可由编码器选择。
    P 帧(Predicted frames):P 帧和 B 帧图像采用帧间编码方式,即同时利用了空间和时间上的相关性。P 帧图像只采用前向时间预测,可以提高压缩效率和图像质量。P 帧图像中可以包含帧内编码的部分,即 P 帧中的每一个宏块可以是前向预测,也可以是帧内编码。
    B 帧(Bi-directional predicted frames):B 帧图像采用双向时间预测,可以大大提高压缩倍数。值得注意的是,由于 B 帧图像采用了未来帧作为参考,因此 MPEG-2 编码码流中图像帧的传输顺序和显示顺序是不同的。

也就是说,一个 I 帧可以不依赖其他帧就解码出一幅完整的图像,而 P 帧、B 帧不行。P 帧需要依赖视频流中排在它前面的帧才能解码出图像。B 帧则需要依赖视频流中排在它前面或后面的帧才能解码出图像。

这就带来一个问题:在视频流中,先到来的 B 帧无法立即解码,需要等待它依赖的后面的 I、P 帧先解码完成,这样一来播放时间与解码时间不一致了,顺序打乱了,那这些帧该如何播放呢?这时就需要我们来了解另外两个概念:DTS 和 PTS(详见下边说明)。

       两个I frame之间形成一个GOP,在x264中同时可以通过参数来设定bf的大小,即:I 和p或者两个P之间B的数量。

  通过上述基本可以说明如果有B frame 存在的情况下一个GOP的最后一个frame一定是P.

  看x264代码,感觉GOP 就是IDR帧到另一个IDR帧之间 就是一个GOP.在视频编码序列中,GOP即Group of picture(图像组),指两个I帧之间的距离,Reference(参考周期)指两个P帧之间的距离(如下图)。一个I帧所占用的字节数大于一个P帧,一个P帧所占用的字节数大于一个B帧(如下图所示)。

  所以在码率不变的前提下,GOP值越大,P、B帧的数量会越多,平均每个I、P、B帧所占用的字节数就越多,也就更容易获取较好的图像质量;Reference越大,B帧的数量越多,同理也更容易获得较好的图像质量。

  需要说明的是,通过提高GOP值来提高图像质量是有限度的,在遇到场景切换的情况时,H.264编码器会自动强制插入一个I帧,此时实际的GOP值被缩短了。另一方面,在一个GOP中,P、B帧是由I帧预测得到的,当I帧的图像质量比较差时,会影响到一个GOP中后续P、B帧的图像质量,直到下一个GOP开始才有可能得以恢复,所以GOP值也不宜设置过大。

  同时,由于P、B帧的复杂度大于I帧,所以过多的P、B帧会影响编码效率,使编码效率降低。另外,过长的GOP还会影响Seek操作的响应速度,由于P、B帧是由前面的I或P帧预测得到的,所以Seek操作需要直接定位,解码某一个P或B帧时,需要先解码得到本GOP内的I帧及之前的N个预测帧才可以,GOP值越长,需要解码的预测帧就越多,seek响应的时间也越长。
DTS、PTS 的概念

DTS、PTS 的概念如下所述:

    DTS(Decoding Time Stamp):即解码时间戳,这个时间戳的意义在于告诉播放器该在什么时候解码这一帧的数据。
    PTS(Presentation Time Stamp):即显示时间戳,这个时间戳用来告诉播放器该在什么时候显示这一帧的数据。

需要注意的是:虽然 DTS、PTS 是用于指导播放端的行为,但它们是在编码的时候由编码器生成的。

当视频流中没有 B 帧时,通常 DTS 和 PTS 的顺序是一致的。但如果有 B 帧时,就回到了我们前面说的问题:解码顺序和播放顺序不一致了。

比如一个视频中,帧的显示顺序是:I B B P,现在我们需要在解码 B 帧时知道 P 帧中信息,因此这几帧在视频流中的顺序可能是:I P B B,这时候就体现出每帧都有 DTS 和 PTS 的作用了。DTS 告诉我们该按什么顺序解码这几帧图像,PTS 告诉我们该按什么顺序显示这几帧图像。顺序大概如下:

    PTS:   480   640  560  520  600  800  720  680  760  960 ...
    DTS:   400   440  480  520  560  600  640  680  720  760 ...
    Stream: I     P    B    B    B    P    B    B    B    P  ...
    播放序: 1     5    3    2    4    9    7    6    8   10  ...               
    PTS >= DTS

音视频的同步

上面说了视频帧、DTS、PTS 相关的概念。我们都知道在一个媒体流中,除了视频以外,通常还包括音频。音频的播放,也有 DTS、PTS 的概念,但是音频没有类似视频中 B 帧,不需要双向预测,所以音频帧的 DTS、PTS 顺序是一致的。

音频视频混合在一起播放,就呈现了我们常常看到的广义的视频。在音视频一起播放的时候,我们通常需要面临一个问题:怎么去同步它们,以免出现画不对声的情况。

要实现音视频同步,通常需要选择一个参考时钟,参考时钟上的时间是线性递增的,编码音视频流时依据参考时钟上的时间给每帧数据打上时间戳。在播放时,读取数据帧上的时间戳,同时参考当前参考时钟上的时间来安排播放。这里的说的时间戳就是我们前面说的 PTS。实践中,我们可以选择:同步视频到音频、同步音频到视频、同步音频和视频到外部时钟。
PTS和DTS的时间基

PST和DTS的单位是什么?

为了回答这个问题,先引入FFmpeg中时间基的概念,也就是time_base。它也是用来度量时间的。
如果把1秒分为25等份,你可以理解就是一把尺,那么每一格表示的就是1/25秒。此时的time_base={1,25}
如果你是把1秒分成90000份,每一个刻度就是1/90000秒,此时的time_base={1,90000}。
所谓时间基表示的就是每个刻度是多少秒
pts的值就是占多少个时间刻度(占多少个格子)。它的单位不是秒,而是时间刻度。只有pts加上time_base两者同时在一起,才能表达出时间是多少。
好比我只告诉你,某物体的长度占某一把尺上的20个刻度。但是我不告诉你,这把尺总共是多少厘米的,你就没办法计算每个刻度是多少厘米,你也就无法知道物体的长度。
pts=20个刻度
time_base={1,10} 每一个刻度是1/10厘米
所以物体的长度=pts*time_base=20*1/10 厘米

在ffmpeg中。av_q2d(time_base)=每个刻度是多少秒
此时你应该不难理解 pts*av_q2d(time_base)才是帧的显示时间戳。

下面理解时间基的转换,为什么要有时间基转换。
首先,不同的封装格式,timebase是不一样的。另外,整个转码过程,不同的数据状态对应的时间基也不一致。拿mpegts封装格式25fps来说(只说视频,音频大致一样,但也略有不同)。非压缩时候的数据(即YUV或者其它),在ffmpeg中对应的结构体为AVFrame,它的时间基为AVCodecContext 的time_base ,AVRational{1,25}。
压缩后的数据(对应的结构体为AVPacket)对应的时间基为AVStream的time_base,AVRational{1,90000}。
因为数据状态不同,时间基不一样,所以我们必须转换,在1/25时间刻度下占10格,在1/90000下是占多少格。这就是pts的转换。

根据pts来计算一桢在整个视频中的时间位置:
timestamp(秒) = pts * av_q2d(st->time_base)

duration和pts单位一样,duration表示当前帧的持续时间占多少格。或者理解是两帧的间隔时间是占多少格。一定要理解单位。
pts:格子数
av_q2d(st->time_base): 秒/格

计算视频长度:
time(秒) = st->duration * av_q2d(st->time_base)

ffmpeg内部的时间与标准的时间转换方法:
ffmpeg内部的时间戳 = AV_TIME_BASE * time(秒)
AV_TIME_BASE_Q=1/AV_TIME_BASE

av_rescale_q(int64_t a, AVRational bq, AVRational cq)函数
这个函数的作用是计算a*bq / cq来把时间戳从一个时间基调整到另外一个时间基。在进行时间基转换的时候,应该首先这个函数,因为它可以避免溢出的情况发生。
函数表示在bq下的占a个格子,在cq下是多少。

关于音频pts的计算:
音频sample_rate:samples per second,即采样率,表示每秒采集多少采样点。
比如44100HZ,就是一秒采集44100个sample.
即每个sample的时间是1/44100秒

一个音频帧的AVFrame有nb_samples个sample,所以一个AVFrame耗时是nb_samples*(1/44100)秒
即标准时间下duration_s=nb_samples*(1/44100)秒,
转换成AVStream时间基下
duration=duration_s / av_q2d(st->time_base)
基于st->time_base的num值一般等于采样率,所以duration=nb_samples.
pts=n*duration=n*nb_samples
————————————————
版权声明:本文为CSDN博主「knowledgebao」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/knowledgebao/article/details/84326649

音视频中的PTS和DTS及同步的更多相关文章

  1. transcode_step()在转码过程中对pts、dts、duration的处理

    对pts.dts.duration的处理主要集中在两大函数里面 1.process_input()读入数据并处理,放到滤镜里面 2.reap_filters()从滤镜读出数据,处理后写入文件 proc ...

  2. 理解ffmpeg中的pts,dts,time_base

    首先介绍下概念: PTS:Presentation Time Stamp.PTS主要用于度量解码后的视频帧什么时候被显示出来 DTS:Decode Time Stamp.DTS主要是标识读入内存中的b ...

  3. C++编程音视频库ffmpeg的pts时间换算方法

    ffmpeg中的pts,dts,duration时间记录都是基于timebase换算,我们主要分析下pts的时间怎么换算,其它的是一样的换算.ffmpeg的时间换算对许多新接触同学算是一个大坑,很多刚 ...

  4. FFmpeg简易播放器的实现-音视频同步

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10284653.html 基于FFmpeg和SDL实现的简易视频播放器,主要分为读取视频文 ...

  5. 【Android 直播软件开发:音视频硬解码篇】

    开篇 炙手可热,望而生畏的音视频开发 时至今日,短视频App可谓是如日中天,一片兴兴向荣.随着短视频的兴起,音视频开发也越来越受到重视,但是由于音视频开发涉及知识面比较广,入门门槛相对较高,让许许多多 ...

  6. Android 音视频开发(六): MediaCodec API 详解

    在学习了Android 音视频的基本的相关知识,并整理了相关的API之后,我们应该对基本的音视频有一定的轮廓了. 下面开始接触一个Android音视频中相当重要的一个API: MediaCodec.通 ...

  7. 从零到一,使用实时音视频 SDK 一起开发一款 Zoom 吧

    zoom(zoom.us) 是一款受到广泛使用的在线会议软件.相信各位一定在办公.会议.聊天等各种场景下体验或者使用过,作为一款成熟的商业软件,zoom 提供了稳定的实时音视频通话质量,以及白板.聊天 ...

  8. 音视频开发之H.264 入门知识

    大家如果有做过音视频相关的项目,那么肯定对 H.264 相关的概念了解的比较通透,这里我为什么还要写这样一篇文章呢?一来是为了对知识的总结,二来是为了给刚入门音视频的同学一个参考. 基础概念 H.26 ...

  9. 堪称教科书级别的Android音视频入门进阶学习手册,开源分享!

    概述 随着整个互联网的崛起,数据传递的形式也在不断升级变化,总的流行趋势如下: 纯文本的短信,QQ -> 空间,微博,朋友圈的图片文字结合 -> 微信语音 -> 各大直播软件 -&g ...

随机推荐

  1. MySQL的B+树索引和hash索引的区别

    简述一下索引: 索引是数据库表中一列或多列的值进行排序的一种数据结构:索引分为聚集索引和非聚集索引,聚集索引查询类似书的目录,快速定位查找的数据,非聚集索引查询一般需要再次回表查询一次,如果不使用索引 ...

  2. JavaScript Sanitizer API:原生WEB安全API出现啦

    10月18号, W3C中网络平台孵化器小组(Web Platform Incubator Community Group)公布了HTML Sanitizer API的规范草案.这份草案用来解决浏览器如 ...

  3. 详解在Linux中安装配置MySQL

    最近在整理自己私人服务器上的各种阿猫阿狗,正好就顺手详细记录一下清理之后重装的步骤,今天先写点数据库的内容,关于在Linux中安装配置MySQL 安装环境 CentOS7 + MySQL5.7 下载安 ...

  4. WC2021 云划水记

    Day -38 - 2459208(2020.12.24) CCF 发公告了,线上举办 hopping. 刚看到还纠结了一会儿,但想想还是报了.虽说是去摸鱼,打打暴力分就走人.但毕竟有牌和没牌也是不一 ...

  5. Codeforces 1270H - Number of Components(线段树)

    Codeforces 题目传送门 & 洛谷题目传送门 首先需发现一个性质,那就是每一个连通块所对应的是一个区间.换句话说 \(\forall l<r\),若 \(l,r\) 在同一连通块 ...

  6. P6604 [HNOI2016]序列 加强版

    *I. P6604 [HNOI2016]序列 加强版 摘自学习笔记 简单树论 笛卡尔树部分例题 I. 和 P6503 比较类似.我们设 \(f_i\) 表示全局以 \(i\) 结尾的子区间的最小值之和 ...

  7. C语言 序列反向互补函数

    1 static char *revers(char *s) 2 { 3 int len=strlen(s); 4 char *s2=(char *)malloc(sizeof(char)*(len+ ...

  8. 数仓day04----日志预处理2

    1.详细描述idmap的整个计算方案 (1)使用SparkSession对象读取用户不同类别的埋点日志,解析并抽取出相应的标识id,使用union进行合并,得到装有汇总标识id的rdd(ids) (2 ...

  9. java静态方法调用非静态方法

    我们都知道,静态static方法中不能调用非静态non-static方法,准确地说是不能直接调用non-static方法.但是可以通过将一个对象的引用传入static方法中,再去调用该对象的non-s ...

  10. spring boot druid数据源

    pom.xml配置 <!-- druid --> <dependency> <groupId>com.alibaba</groupId> <art ...