在讲封装格式之前,先讲一下什么是文件格式,计算机存储任何东西都是二进制的,ASII码字符串是二进制的,一个ASII字符占1个字节。int 类型存储在文件也是二进制的,一个 int 占 4个字节。
大家经常使用的文本编辑器,如果打开 ASII码文件是显示正常的,但是如果打开存储 int 的二进制文件,就会显示很奇怪,因为文本编辑器把 int 4字节认为是 4 个 ASII 码了。
不只是 int ,还有很多数据类型也可以存储在文件里面。因此在这里,我把文件格式分为两种:
1,字符串存储,直接用普通的文本编辑器,notepad++,sublime,打开文件即可查看内容。
2,二进制存储,需要专门的软件分析文件的内容。
实际上,音视频封装格式 是 属于 文件格式的二进制存储的。音视频封装格式的特殊之处在于他的英文是 mux(复用)。学过 TCP 的同学应该听过 多路复用,音视频封装格式也是同理,可以把多个流数据合并到一个文件里面,这就是封装格式,这就是 mux。
因为编码压缩系统 输出 的是单路流,视频编码系统输出视频流,音频编码系统输出 音频流,封装格式就是可以把多个流合在一个文件里面。
FLV,MP4,MKV 这些都是封装格式,这些文件存储的内容都是二进制的。封装格式 实际上 就是各个 组织或者公司 之间,定一个标准,这种格式的文件,都有哪些字段,各个字段是什么意思,占多少字节,在文件的什么位置,各个字段之间的嵌套关系是怎么样的。封装格式定义的就是这些东西。
封装格式实际上就是定义一种标准,例如MP4标准,大家都按照 MP4 标准文档这种方式去读写数据内容,就不会有问题。举个例子,我跟别人约定好,面包放在抽屉的第一层,第二层放药品。这样别人就知道去第一层拿面包。如果我不按照约定把面包放在第一层,别人就会找不到面包,他的解析就会报错,找不到面包。
封装格式主要的作用是提供一个约定的规则。封装格式可以把 音视频流合在一起。
封装格式是多种多样的,为了解决不同的场景问题,大家会定义出来不同的格式。举个例子。
1,点播场景,什么是点播,就是视频已经录好了,放在服务器,客户端按需拉取一小端内容播放,不需要下载全部的视频内容。点播场景比较适合用 MP4 格式,因为 MP4 格式定义了 stts 索引表以及一些相关的数据结构,可以很快的跳转,例如 跳转 某个时间点播放,MP4 格式会比 FLV 快很多。(补充:FLV 可以额外添加 keyframeindex 加快跳转速度)
2,直播场景,MP4 格式的box结构要全部视频录完才能生成,而直播是不知道什么时候结束的。而 FLV 是一种渐进式的格式,非常适合用于直播。
所以不同的封装格式,是解决不同场景的问题。不过封装格式还会解决一些共同的问题,就是音视频同步。封装格式会给每个视频帧跟音频帧打上一个时间戳 PTS。
播放器单独播放视频流,或者音频流的时候,是不需要这个 PTS 时间戳的,视频流按帧率播放,音频流按采样率播放即可。这个 PTS 可以帮助音视频同步。
下面来讲解为什么,因为我们使用的操作系统大多都是分时系统,也就是每个任务分配一定的CPU时间片。
假设 Windows 系统正在播放一个视频(没有音频流),帧率是 1秒 24帧,现在是晚上 8点00分00秒。第一帧视频是从 8:00:00:00 开始播放,按帧率播放,41 毫秒就需要显示第二帧,所以第二帧应该在 8:00:00:41 的时候播放,第三帧在 8:00:00:82 的时候播放,这样画面看起来才是流畅的。但是由于是分时系统,在 8:00:00:41 的时候,CPU 在忙着干其他的任务,无法切换回来播放器线程。所以第二帧有可能是在 在 8:00:00:61 的时候才开始播放,慢了 20 毫秒。这种情况我们能怎么办?即使播放器是我们开发的,我们也什么都干不了,在某个时刻 CPU 就是忙不过来。第二帧慢了 20 毫秒,第三帧也慢20毫秒就行了。这样后面CPU不忙碌,看起来也会很流畅。这是单个视频流的情况。
假设如果有个音 视频流 同时播放,本来画面声音是需要同步的,例如第二帧视频播放的时候,第二帧音频也要播放。但是因为视频流慢了 20 毫秒,如果音频流的播放线程不理会 视频播放线程的卡顿情况,音频流还是按自己的采样率播放,就会导致音频流播放快于 视频流 20 毫秒,这样不断累计,音视频流就会逐渐差距很大,画面不同步。所以需要封装格式给 每个 音视频帧打上一个 PTS。音视频流 播放线程 通过观察对方 已经播放的 PTS 来决定自己要继续播放还是休眠,还是丢弃帧。