1、前言
如果你经常接触音视频,那么对于 M3U8 应该不会陌生, M3U8 简单来说就是 HLS(HTTP Live Streaming) ,指的是苹果开发的基于 HTTP 协议的流媒体解决方案,它可以在普通的 HTTP 的应用上直接提供点播和直播的能力。
在 HLS 里会将视流文件切分成小片(ts)并建立索引文件(M3U8),一般如下图所示,首先会有一个 M3U8 文件,然后对应在 #EXTINF
的 tag 下会有很多 TS 格式切片,这应该是我们认知中的 M3U8 文件的标准。
❝
详细讲解 M3U8 的这里就不详细展开,感兴趣的可以看之前分享过的探索移动端音视频与GSYVideoPlayer之旅 。
❞
「那么,如果在 M3U8 里的不是 TS 链接,而是 png 链接或者 bmp 链接会是什么情况」?今天的主题就是探讨如何适配带有图片的非标准 M3U8 视频。
❝
「我们不鼓励「非标」,而是通过「非标」的适配来做科普」。
❞
2、为什么 M3U8 里会有图片?
首先,标准的 HLS 协议里 M3U8 文件内肯定是 TS 的切片链接,「那么为什么会有 png/bmp 之类的图片链接存在?或者说,为什么会是图片链接」?
这就不得不说「劳动人民的智慧」,众所周知,如果想让一个视频加载更快,那么最简单的办法就是给视频上 CDN ,但是碍于某些团队或者个人「囊中羞涩」,所以开始有人瞄上了公共图床的 CDN ,然后再结合 M3U8 的特性,一套民间的「免费」 视频 CDN 潜规则就这样悄然流行起来。
❝
如果你想将一个完整视频伪装成图片上传公共图床明显不现实,因为体积太大,很多图床也会对图片的大小做限制,但是如果是 M3U8 ,那么就可以把「视频分解成无数 TS ,再把 TS 伪装成图片分批上传,这样就可以给视频「依附」上 CDN 的能力」。
❞
3、TS & 图片
如下图所示,这就是一个「非标」的 M3U8 视频链接,可以看到 #EXTINF
tag 下会的链接都是 png
格式的后缀,「那么这种 png 后缀,会不会影响视频播放呢」?
「答案是不会,因为 FFmpeg 里播放并不是认定后缀」,而是通过读取每个 #EXTINF
tag 链接的二进制 Header,最终匹配它们的封装和编解码格式。
❝
所以其实在 M3U8 里
#EXTINF
tag 下的链接后缀并不重要,可以是 png 或者 bmp ,甚至你写 txt 也是可以的,重点其实是包本身的编码。❞
那么如果这个视频链接,真的是一个图片呢?如下图所示,可以看到这个 png 本身就是一个完整的图片,不过这个图片的大小和它本身的质量并不匹配,毕竟这样一个图片不可能高达 1.9 MB 。
如下图所示,我们查看这张图片的二进制,可以看到文件的 Header 确实是 PNG ,但是后面还有类似 FFmpeg Service 这样的描述,可以确实这就是一个伪装成真实 PNG 格式的视频文件。
从二进制字节看可以发现这就是一个 TS 封装的视频文件,因为在它的二进制代码里,有「以 0x47
开头,长度为 188 字节,并且通过 0xFF
进行填充的规律 packet 存在」。
❝
后面我们会详细解释。
❞
「那么这个 PNG 可以正常播放吗?答案是可以的」。那为什么明明是 PNG 的 Header ,却可以被解析成视频?
首先 FFmpeg 在播放前,会根据前面提到的 0x47
/ 188 这个特征去识别这是一个 TS 封装的视频,之后在 mpegts.c
的对应封装处理逻辑里,会针对识别 0x47
作为包的起始位置去解析,所以 PNG 包部分会被忽略。
❝
「
0x47
是一个 TS 包的固定 header ,一般一个 TS 包是 188 字节,不够长度一般会用0xFF
填充」,而 FFmpeg 会针对每个格式去做识别,计算它们的score
,根据每种格式的score
决定它可能是什么格式,比如mpegts.c
里是mpegts_probe
函数,它通过analyze
函数就会找到0x47
起始做一系列的判断。❞
另外还一个叫 mpegts_read_header
的函数会读取数据头信息,比如解析出 TS 流当中的数据包大小,节目信息,PMT表,Video PID,Audio PID 等等,这些也是 TS 流播放的重要依据。
而在 mpegts.c
里最重要的 read_packet
函数也是,读取的时候会读取 TS_PACKET_SIZE
(188)的大小,然后判断包的首字节是不是 0x47
,如果不是就通过 mpegts_resync
重新同步一下去尝试寻找 0x47
。
「可能这时候细心的你已经发现了「盲点」,前面 PNG 的 Header 二进制里不就是 89 50 4E 47
吗?这里不也是有 0x47
?,这种情况下 mpegts.c
在解包的时候不就会「错乱」了吗」?
如下图所示,因为如果从图片的 0x47
开始算, 以 188 的包长度计算,下一个包不就找不到 <