PTS、Timebase和编码器码率控制

1. Timestamp

音视频处理离不开时间戳timestamp的概念:

  • PTS是Presentation timestamp,显示时间戳
  • DTS是Decoding timestamp,解码时间戳

从采集、前处理、编码、封装传输、解封装、解码、后处理和播放,时间戳流转如下:

PTS、Timebase和编码器码率控制

1.1 PTS的首要作用是让采集端的音视频内容在播放端正确呈现

  • 一方面让音视频以和采集端相同的速度播放(这里的速度不能简单理解成帧率,音频可以当成固定帧率,视频存在固定帧率和变帧率的情况)
  • 一方面让音视频同步播放(唇音同步)

1.2 同步主要发生在采集端和播放端

  • 采集端:音视频共享同一个时钟,同一时刻的音频帧和视频帧打上相同的时间戳(换算成相同单位之后相等)
  • 播放端:音视频主动共享同一个时钟,主动控制让相同时间戳的音视频内容同时呈现

可以认为,采集端的音视频同步是自然发生的(忽略采集设备自身延迟),播放端的音视频同步是主动控制的结果。

1.3 生成时间戳的位置原则上尽量靠近采集端

  • 采集端离音视频事件发生的时刻最接近
  • 越靠近采集端,越容易补偿采集设备自身引入的延迟
  • 在后续环节打时间戳,离音视频事件发生的时刻变远了,引入中间环节的浮动时间,音视频同步效果变差
  • 在更远的环节根据帧率采样率等信息生成时间戳,音视频同步效果得不到保证

可以类比观测雷电现象:

  • 在足够近的距离观测雷电,闪电和雷声几乎同时到达观测点
  • 随着距离变远,因为光的传播速度远大于声音,先观测到闪电,后接收到雷声,表现出“音视频的不同步”(除非已知光速和声速,通过计算做出补偿)
  • 在不做补偿计算的情况下,距离越远,“音视频不同步”现象越明显

当然,对于影视内容后期配音等场景,那是通过人工二次校准的,不在讨论范围之内。

1.4 生成时间戳之后,后续环节应尽量保持同源

对时间戳的合理处理包括:

  • 同时对音视频时间戳做偏移变换(pts + offset),不影响音视频同步
  • 变换时间单位,数值虽然变了,表示的物理时间不变

特殊情况如倍速播放等不做讨论。如果出现了“无中生有制造时间戳”的场景,一般是媒体处理流程设计出了问题。典型例子是从H.264/H.265裸流生成mp4之类。裸流可以出现在测试中,不该出现在线上环境。

1.5 PTS与DTS

  • DTS是视频编解码重排序引入的
    • 音频可以不需要显示的写DTS
    • 没有重排序的视频编码格式,或者像AV1等在解码器内部处理重排序的编码格式,理论上也不需要显示的DTS,但现有的媒体处理框架很多地方依赖DTS
  • 编码器
    • 视频frame按照PTS的顺序输入
    • 视频packet按照DTS的顺序输出
  • 解码器
    • 这里有例外,比如Apple的videotoolbox以DTS顺序解码输出,需要用户自己按照PTS做排序。FFmpeg封装的videotoolbox解码在libavcodec框架内实现了重排序,会按照PTS输出frame。
    • 视频packet按照DTS的顺序输入
    • 一般解码器按照PTS的顺序输出视频frame

1.6 PTS与POC

了解视频编码的同学知道POC的概念,H.264中POC的定义:

picture order count: A variable that is associated with each coded field and each field of a coded frame and has a value that is non-decreasing with increasing field position in output order relative to the first output field of the previous IDR picture in decoding order or relative to the first output field of the previous picture, in decoding order, that contains a memory management control operation that marks all reference pictures as “unused for reference” .

POC表达的是显示序,只有顺序的概念。PTS既包含顺序,也包含“时刻”。POC是编解码层的,PTS是编解码层之上的概念。正常情况下:

  • POC的顺序和PTS的顺序是一致的
  • 解码器是按照POC排序输出图像,因POC和PTS顺序一致,所以也就按照PTS输出图像

但是异常情况会生成POC和PTS不一致的码流。编解码器一般不会弄错POC,POC和PTS不一致往往是开发者对时间戳概念理解不到位,随意生成时间戳导致的。你会发现不同播放器播放此类视频会有不同的表现:

  • FFmpeg/ffplay会按照POC顺序播放,画面基本正常
  • Apple设备系统播放器按照PTS顺序播放,画面呈现出往复抖动的现象

2. Timebase

2.1 Timebase的选择

timebase时间基是时间的单位。很多时候我们说时间戳,既包含时间戳的数值,也隐含了时间的单位timebase,没有timebase的PTS是不完整的。一些媒体处理框架预设了固定的时间单位,另一些媒体处理框架要求用户指定时间单位,并且不同环节用不同时间单位,时间单位和时间戳的数值字段分离,搞不清PTS/DTS和哪里的timebase对应,由此衍生的复杂性,是很多同学搞不清timebase的原因。但归根结底,timebase仅仅是时间的单位

timebase可以用分数形式表达:num/den seconds。毫秒、微秒用分数形式表示的timebase是1/1000 seconds1/1000000 secondspts * timebase是以秒为单位的时间。除了毫秒微秒,视频里经常用的timebase是1/90000。90000是常用视频帧率的整数倍,例如:

  • 90000 / 24 = 3750
  • 90000 / 30 = 3000
  • 90000 / (30 * 1000 / 1001) = 3003

注意帧率30 * 1000 / 1001,常说的29.97 FPS实际是30 * 1000 / 1001的近似值,而1000 / 1001又是黑白电视机向彩色电视机过渡阶段工程设计带来的奇葩数值。

音频经常用的timebase是1/441001/48000。44100、48000是常用的音频采样率,音频的timebase也就是选择了音频采样率的倒数。用采样率做timebase的好处是计算方便又精确,第N个sample的PTS

PTS = N

实际上视频的timebase也可以选择视频帧率的倒数,但因为UGC的内容采集往往是变帧率的,且帧率浮动性较大,timebase = 1/fps得到的时间戳精度差

timebase的选择有如下考虑:

  1. 精确性:避免计算时的精度损失、累计误差等
  2. 方便计算
  3. 避免溢出:timebase = 1/N,N过大要考虑整型溢出问题

2.2 变换Timebase

把一个时间戳从一个timebase_a换算成另一个timebase_b:

pts_b * timebase_b = pts_a * timebase_a==>pts_b = pts_a * timebase_a / timebase_b

timebase_a 和timebase_b都是num/den的形式。只要记住pts * timebase = 以秒为单位的时间,其他都是小学数学问题。

3. PTS、Timebase和视频编码器码率控制

码率的定义是一段时间内码流的大小 / 码流的持续时间 = video_bits / duration。码流的持续时间是

(pts_end - pts_begin) * timebase

可见,PTS和Timebase与码流码率紧密关联。PTS和Timebase都要正确的传递给编码器,编码器才能输出符合预期的码率。 开发者容易犯的一类错误是,设置Timebase = 1 / FPS,而PTS的数值又是按照毫秒或微秒得来的,即设置给编码器的Timebase,不是PTS真正的时间单位

注意这里还有另一种情况。有些编码器支持配置是否为VFR变帧率

  • VFR为true时,根据前面的vide_bits / ((pts_end - pts_begin) * timebase)来做码率控制
  • VFR为false时,video_bits / (frame_num / fps)来做码率控制

对于固定帧率的视频,frame_num / fps 和 pts * timebase两个是等效的,可以认为固定帧率时,用frame_num作为pts,用1/fps作为timebase。 固定帧率的视频,以上两种码率控制结果相同。

但对于VFR的视频,以上两种码率控制结果不同:

  • 设置VFR = true给编码器,则帧率虽然上下浮动,输出的码率是相对恒定的
  • 设置VFR = false给编码器,则实际帧率 < 设置给编码器的帧率时,编码输出码率偏低,反之,实际帧率 > 设置给编码器的帧率,编码输出码率偏高

一般而言,设置给编码器的VFR参数应该按照真实采集情况而定。但两者相悖的情况也有用武之地:

  • 设置给编码器VFR = false
  • 网络卡顿时,降低采集帧率,或者做编码前的丢帧处理,编码输出码率随之降低,达到码率自适应的效果

另一种码率自适应实现方式是设置VFR = true,需要调整码率时reconfig新的码率给编码器。

作者:quink
来源:Fun With FFmpeg
原文:https://mp.weixin.qq.com/s/onMmPmR4Q9bdJoFyVlxCKA

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论