音视频技术入门课- 03 如何做音视频的封装和转码
视频转码主要涉及编码压缩算法(Encoding)、格式封装操作 (Muxing)、数据传输 (例如 RTMP、RTP)、格式解封装(Demuxing)、解码解压缩算法(Decoding)几方面的操作。
在我们将视频流、音频流写入到一个封装容器中之前,需要先弄清楚这个容器是否支持我们当前的视频流、音频流数据。
音视频编解码
音频的 PCM 数据、视频的 YUV 和 RGB 的图像数据直接存储或者通过带宽传输会比较消耗空间,那么为了节省存储,或者确保占用的带宽更少一些,就需要把音频、视频的数据压缩。
音频是连续的采样序列,而视频则是连续的图像序列,这些序列是有前后关系的。
有一个 6 帧的连续视频图像,每一帧都是宽 100、高 100 的画幅,在每一帧的正中央都有一个字母在变化。遇到这种情况时,如果我们每一帧图像全都做传输或存储操作的话,占用的带宽或空间都会很大.
为了节省空间,我们可以来分析一下图像的规律。因为这 6 帧图像大范围是相同的,只有正中心的一小部分内容是变化的。刷新第一帧图像后,从第二帧开始,我们只要刷新正中心字母区域的内容即可。这个叫局部更新,只需要逐步更新 A、B、C、D、E、F 的区域就可以了,这样在传输内容的时候既节省带宽,又能在存储内容的时候节省画幅的数据存储空间。
在看视频的时候可不仅仅是内容的更新,还涉及内容位置的运动等变化,所以视频内容更新的算法会更复杂一些。在做视频压缩的时候,就拿前面的这个例子来说,需要有一个参考帧,这里参考的是第一帧,后面每一帧都参考前面一帧做了局部更新。
而我们的视频图像序列不能只做局部更新,因为里面的目标对象还会运动,所以我们不仅可以前向做参考,还有可以做双向参考的技术。在这个过程中就涉及了图像的类型,通常我们遇到的是这三类帧:I 帧、P 帧和 B 帧。
I 帧(Intra-frame)作为关键帧,仅由帧内预测的宏块组成。而 P 帧(predictive-frame)代表预测帧,通过使用已经编码的帧进行运动估计,B 帧(bi-directional interpolated prediction frame)则可以参考自己前后出现的帧。如果比较 IBP 帧包所占空间大小的话,通常是 I 帧>P 帧>B 帧,所以适当地增加 B 帧可以减少视频流占用的带宽或者存储空间
在示意图里,我们可以看出,解码顺序是 1423756,但是显示顺序却是 1234。I frame的解码不依赖于任何其他的帧。而Pframe 的几码则依赖于前面的面的Ifarme或者Pframe.Bframe 的解码则依赖于其前的最近一个I frame 或者P frame 及其后的最近的一个P frame。
当编码中存在 B 帧的时候,因为解码需要双向参考帧,所以需要多缓存几帧作为参考数据,从而也就带来了一定的显示延迟。所以在实时直播场景下,参考标准中推荐的做法通常是不带 B 帧。
在视频编码时,因为图像的画面以及图像中对象运动的复杂程度比较高,为了保证清晰度,运动的图像组中通常也会包含更多的图像运动参考信息,所以压缩难度也提升了很多,压缩后的视频码率也就变得比常规图像更高一些,这个码率的波动通常时高时低,具有可变性,我们一般称之为可变码率(VBR)
而在有些直播场景下,因为一个传输信号通道中会携带多条流,为了确保多条流在同一个信号通道中互不干扰,一般会要求编码时采用恒定码率的方式(CBR)。但是如果采用 CBR 的话,画质往往会有一些损耗,这也就是为什么我们现在在一些老式的电视直播场景中看到的画质偶尔会变得很差,比如交通广播电视场景。
视频封装
平时经常会听到说视频是 MP4 格式的,也会说音乐格式是 M4A等等,可以粗略地认为说的是封装格式,也就是常说的容器格式。
在容器格式的内部会存储音频、视频的数据,称之为视频流、音频流。音视频流在容器中的存储形式有两种,既可以交错式存储,也可以是不同类型的流单独存储在自己的连续区域。
存储音视频数据的时候,如果是顺序读取音视频数据的话,当然就是音视频数据交错存储比较好,这样会给内存、硬盘或者网络节省很多开销。
如果音视频分开,单独存放在各自的区域的话,为了更好地做音视频同步,通常会读取一段视频帧数据,再读取一段音频采样数据,这样势必会不断跳跃式地读取硬盘中的数据,或者不断地发起网络请求,如果是 http 请求的话,我们会看到大量的 http range 请求,给网络开销与服务器并发造成很大的压力。
封装容器格式:MP4
MP4 格式标准为 ISO-14496 Part 12、ISO-14496 Part 14,标准内容并不是特别多,如果要了解 MP4 的格式信息,首先要清楚几个概念:
1. MP4 文件由许多个 Box 与 FullBox 组成;
2. 每个 Box 由 Header 和 Data 两部分组成;
3. FullBox 则是 Box 的扩展,在 Box 结构的基础上,在 Header 中增加 8bit 位 version 标志和 24bit 位的 flags 标志;
4. Header 包含了整个 Box 的长度的大小(Size)和类型(Type),当 Size 等于 0 时,代表这个 Box 是文件中的最后一个 Box;当 Size 等于 1 时说明 Box 长度需要更多的 bits 位来描述,在后面会定义一个 64bits 位的 largesize 用来描述 Box 的长度;当 Type 为 uuid 时,说明这个 Box 中的数据是用户自定义扩展类型
5. Data 为 Box 的实际数据,可以是纯数据,也可以是更多的子 Box;
6. 当一个 Box 中 Data 是一系列的子 Box 时,这个 Box 又可以称为 Container Box。
MP4 文件中 全部Box 的组成可以参考标准 ISO-14496 Part 12。
MP4 封装格式文件中,经常会遇到 moov box 与 mdat box。
存储音频与视频数据的索引信息,就是存在 moov box 中,音频和视频的索引信息在 moov 中分别存储在不同的 trak 里面,trak 里面会保存对应的数据采样相关的索引信息,通过获得 moov 中的索引信息之后,根据索引信息我们才能从 mdat 中读取音视频数据,所以 MP4 文件中必不可少的是 moov 信息,如果缺少 moov 信息的话,这个点播文件将无法被成功打开。
关于 MP4 封装里面能否存当前想存的 codec,还需要查找一下参考标准或者公共约定的标准,看看是否允许存放,可以参考MP4 网站里面的表格。
当基于网络请求播放 MP4 点播文件时,如果 moov box 存储在 mdat box 后面的话,播放器需要先读取到 MP4 文件的 moov 以后,才能够开始播放 MP4 文件,而这个读取的动作,有些播放器是选择先下载全部 MP4 文件,有些则是需要先解析一下 mdat,跳过 mdat 以后再读取 moov,所以为了节省播放器操作,兼容性更好,通常需要将 moov 移到 MP4 文件的头部,节省播放 MP4 文件开始时间段的开销。
,这个 moov 的生成,一般需要先写入 mdat 中的音视频数据,这样我们就知道数据采样存储的位置和大小,然后才能写入到 moov 中,所以是先写入 mdat 后写入 moov 这样一个顺序。解决办法是生成文件之后做一个后处理,也就是将 moov 移动到 mdat 前面。
总结
在音视频技术领域,编解码、封装格式都有对应的参考标准,并且都是开放标准。
思考题
MP4 是否能够应用于直播场景中呢?怎么处理才能够支持直播?
参考答案:
fragmented mp4,也就是fmp4,是适应于现代浏览器的一种流媒体格式。和mp4格式不同的是,以往的mp4格式化也分为header信息和payload信息,一个大的mp4就会有一个很大的头信息,不适合与现在的网络环境。所以新的fmp4格式就出现了,fmp4格式有一个带了metadata的头片段,及后面一序列的fragment,每个fragment都有各自的header信息,这样就把header信息也分成了一个个小的片段,更适合现在的应用场景。所以fmp4格式也就是用于MediaSource对象的SourceBuffer的格式,通过一个initSegment和一序列的segment塞给MediaSource对象来播放。
参考
本文来自博客园,作者:miyan,转载请注明原文链接:https://www.cnblogs.com/xyjk1002-rejuvenation/p/16658960.html