0%

无插件直播流媒体方案

今天在想后续的视频流分发方案的时候,突然想到可能能做个简单的无插件视频流预览app来验证视频流分发方案。
于是乎,就去看来一下HTML5下面的无插件直播多媒体方案。

多媒体服务形式


当下多媒体形式无外乎两种:

  1. 视频点播:例如 某bili,某奇艺,某讯视频,主流的多媒体服务商提供的视频点播娱乐服务,无外乎就是客户端以点播形式从服务器上获取多媒体数据。
  2. 视频直播:例如 某🐟,某牙这些显而易见的直播界的秀儿。这种无外乎也是单点客户端(各大主播)推流到多媒体服务器,然后由服务器进行分发。
    这些都是一对多的。一对多当然需要分发网络来进行更好的直播体验(几万人从一个点拉流,那网络肯定撑不住)。

但是如果是家用设备直连,比如摄像头这类的多媒体采集设备,其也属于视频直播的范畴,不过应用上还跟主流方案一样用推拉的方式,那可就太没必要了。
因此需要一种点对点的直播方案,并且呢,使用B/S架构,能直接在浏览器上进行预览。

方案需求


方案需求中的关键词:

  • 无插件:浏览器无插件支持(flash都快淘汰了,还是用HTML5吧)。挺好,从这点上已经可以排除大量的老方案了,比如rtsp,rtmp之类的推拉流方案,没额外插件支持无法实现。
  • 点对点:基于H5 使用websocket进行通信。

进一步的思考


HTML5是支持<video>标签的,并且不同浏览器均支持解析h264+acc的音视频编码流。ok,这样的话需要传什么内容大概就定了。这点还是挺好的,目前主流的摄像机都是使用的H264编码。
剩下的就是搞清楚这东西的实现原理,然后进一步推导方案。

流媒体服务中的概念


在开始进一步弄明白前,首先要搞清楚流媒体传输中的一堆概念,主要是容易弄混,比如什么H264编码,PS,TS混合流封装,再者还有什么RTSPRTMP,HLS流媒体传输协议,以及mp4,flv云云。

  • 编码:视频在网络中传输,必然是要经过编码的,编码的主要用途就是压缩视频帧数据,降低网络带宽压力,音频也是同理。
    此处的H264就是主流的视频编码方案,还有Acc对应的是音频编码类型。
  • 封装:视频在网络中传输,一般来说是需要经过封装的。之所以说是一般,当然也可以提供无封装的裸流。封装是为了提供编码后数据的一个容器格式。其好处就是可以封装混合流(音频+视频)甚至是字幕等其他资源数据(比如MKV格式封装)
    PS,全称是Program Stream,即程序流封装,其特点为封装包为流式变长数据包。
    TS,全称是Transport Stream,即传输流,其特点为任意一段开始都可以独立解码,其封装包为定长切割的数据包。
    除了以上两种外,还包括其他的ES封装之类的,mp4flv也是同性质的概念。
  • 网传协议:剩下的RTSP,RTMP,HLS都属于网传协议的一种,其作用就是做一个取流前的交互或者是流传输过程中的一个流控。也就是说网传协议是流媒体分发中所必须要的。
    当然,如果只是简单的点对点的话,交互很简单的情况下,应该也不需要复杂的流控协议。

以上就是流媒体传输中的主要涉及概念。

HTML5中video标签


如果需要在HTML5中进行视频播放,使用原生的Video标签可以支持。先看标准用例:

1
<video src="move.mp4"></video>

或者是

1
2
3
4
5
6
<video preload="metadata">
<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=676422 -->
<source src="assets/dizzy.mp4" type="video/mp4" />
<source src="assets/dizzy.webm" type="video/webm" />
<source src="assets/dizzy.ogv" type="video/ogg" />
</video>

类似所有的HTML标签,其作为一个对象,拥有着设定的属性,以及一系列的方法。
其中最需要关注的就是src这个属性,可以看到在示例中,src属性指向当前域下的一个mp4(或webm,ogv)资源文件。
从资源文件的后缀名入手,我们可以了解到,浏览器video标签支持以下封装格式:

  • MPEG-4
  • WEBM
  • OGG
    其浏览器支持程度各不相同,参加下表:
格式 IE Firefox Opera Chrome Safari
Ogg No 3.5+ 10.5+ 5.0+ No
MPEG4 9.0+ No No 5.0+ 3.0+
WebM No 4.0+ 10.6+ 6.0+ No

可以从上面看到,mp4格式的兼容性最好,其次,video标签对封装格式中的编码种类也有要求(在之前的概念简介中有描述过编码封装)
不是所有的mp4封装都能被video标签解析

  • MPEG-4:MPEG-4文件并使用 H264 视频编解码器和AAC音频编解码器
  • WEBM:WebM 文件并使用 VP8 视频编解码器和 Vorbis 音频编解码器
  • OGG:Ogg 文件并使用 Theora 视频编解码器和 Vorbis音频编解码器

嗯,这个不仅对用什么碗装讲究,也讲究碗里装的啥。

HTML5 video标签的缺陷


所以,只要在video标签里关联一个mp4且编码为H264的视频文件就可以万事大吉咯?实际上,还存在很多不确定的地方:

1 页面渲染耗时问题

众所周知,当浏览器进行页面加载的时候,会先获取资源,当获取资源后,再完成页面渲染。
这就导致一个问题,当一个小视频文件的时候,页面渲染不会有延迟,当这个视频文件有好几个G大小的时候,要怎么去做?

2 视频点播时候清晰度切换问题

同问题1,还是资源加载的情况,如果说涉及到视频清晰度切换,那么其本质是播放不同的视频源
(也就是说你不能在浏览器解码的时候将一个1280p视频源变成720p视频)
而切换视频源势必回到资源加载的情况,要整个加载一个大的资源,必然会导致原地爆炸的用户体验。
还有一个关键问题,如果想在切换视频源之后,能够接下去播放,那么势必还要同步时间戳,以及能让切换后的视频在中间开始播放。

3 视频直播流推送不能

回到我们的最终目的,请看标题:视频帧的zero-copy方案
将让video标签去解析一个实时的直播流!ok,问题来了,video标签关联的是一个文件!直播流哪来的文件路径?
难道要将直播流在服务器端封装成一个又一个的文件吗?
好像可以!
实际上完全没可行性,这种方式首先是否要切换DOM里video标签的src属性指向文件?
其次若是封装文件,那么要以怎样的分片大小去封装?如何保证实时性?

4 支持格式有限

主流封装格式只支持mp4,而flv等封装都不支持。=_=

问题的根本原因

首先我们需要支持video标签是如何获取资源的:
HTML5 video does not work like streaming technologies or Flash. So how does the browser manage to play a long video without downloading the whole file before playing it? Part of the trick is that the video is encoded in such a way that the metadata is at the top of the file. This means once the first chunk of data is returned it is enough to determine whether the browser can play it all the way through. If you are encoding video for use with the video element, you will want to choose the Web optimized option in the encoding software. (See the section on video encoding above.)[1]

从上面这段描述,可以知道,video标签的工作方式,不同于flash,这东西天生就不支持流式数据,浏览器会像大多数资源一样,尝试将它下载到本地,然后加载。
要真正实现无插件直播流的方案,上面的问题肯定得搞定,其实这些问题的根本原因在于video标签它就是个憨憨不支持流式传输。ok,下面问题的解决方案,也正是现在主流的web页面上主流视频播放器的解决方案。
当然问题4和流式传输无关,后面另说。

以上问题的解决思路和大致方案


解决这个问题,需要一个模拟流式传输的方案,我们需要:

  1. 每次从服务器上请求完整文件的一部分。
  2. 浏览器端将请求的一部分数据转封装成一个虚拟的mp4文件对象喂给video标签。

继续将思路转换成方案

步骤一方案关键点


关键点 1 progressive download:进度下载,即每次请求一部分文件内容,需要服务器支持byte serving特性

看一下下面应答报文:

1
2
3
4
5
6
7
8
9
10
HTTP/1.1 200 OK
Content-Length: 8343631
Content-Type: video/mp4
Last-Modified: Thu, 20 Dec 2012 19:40:10 GMT
Accept-Ranges: bytes
ETag: "f79b80d2e9decd1:89fd"
Server: Microsoft-IIS/6.0
Access-Control-Allow-Origin: *
X-Powered-By: ASP.NET
Date: Sat, 22 Dec 2012 22:04:23 GMT

**Accept-Ranges: bytes**字段表示服务器支持byte serving特性。
同时请求报文里会有发送:
**Content-Range: bytes 0-3771428/3771429**字段,表示了请求的下载段的seek信息。

部分数据是能请求接收了,那么收了之后就能播吗?
让我们把mp4这个盒子拆开。
mp4内部也是一个个的小盒子组成,不同类型的盒子里面装的是不同的东西。
毫无疑问,mp4内部有编码后的视频数据,但是只有视频编码数据,毫无疑问是不够的,还需要其他信息。
一般的mp4,由以下盒子组成:

  • ftype box: mp4说明,告诉解码器其解码信息,一般都处于mp4文件的开头。
  • moov box:包含视频元数据,包括了主要信息,如帧率,码率,分辨率,时长等等等
  • mdat box:视频媒体数据,帧数据在其中依次存放,其位置时间长度都由moov box中记录

也就是说,必须获取到mp4到头,因为视频元数据和mp4文件说明都只在头部存在一份。获取到头部之后,后面到数据可以根据头部解析来算seek长度,进行请求。
对于视频点播来说,这也许足够来,因为同视频源moov box中到信息是可以准确描述mdat box中到内容的。
但是这不适用于动态码率场景!
视频直播流,其必然不存在一个moov box来准确描述不断变化的mdat box,因为直播流的帧率和码率都有可能是变化的,况且也不能知道具体的时长,因为它不是一个真实的文件。

关键点 2 Fragmented MPEG-4:即fmp4,顾名思义,碎片mp4,基于MPEG-4 Part12的流媒体格式

fmp4和mp4基本一样,关键是fmp4的moov box只包含流一些track信息,而不需要用这个moov box来对播放器进行参数初始化。
fmp4的每个mdat box的信息都有对应moof box进行记录,其为流式封装结构。

说白了fmp4就是把分片存在来fmp4一整个文件的内部,每一个分片都是moof box+mdat box,我们请求的时候,只需要以这个最小单元来进行处理即可。

步骤二方案关键点


假设我们已经能搞到了一个最小的分片一段fmp4数据,如何塞给video标签呢?

关键点 3 javescript blob对象

Blob 对象表示一个不可变、原始数据的类文件对象。Blob 表示的不一定是JavaScript原生格式的数据。File 接口基于Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。[2]_
关键点,file接口是基于blob的,进一步可查证,一个blob对象是可以当作文件对象去处理的。
也就是说,只要能把fmp4塞进一个blob,然后生成一个指向blob的url(使用URL.createObjectURL构建),让video标签的src去指向这个url就可以了。
光把标签指向一个地址,播完这段之后,再修改为下一个URL,这个还不够无缝播放(怎能忍受卡顿和掉帧呢?)
实际上,这个blob可以不用去掉,借助MSE就可以持久的往blob里添加数据。

关键点 4 MSE:Media Source Extensions 媒体源拓展规范

MSE是一项W3C规范,其允许给<video>&<audio>标签绑定动态的媒体源。
通过MSE提供的接口MediaSource.appendBuffer,实现往blob中平滑的添加数据,实现平滑播放

方案总结


由以上大致方案,我们可以知道,尽管浏览器支持格式有限,但是只要能够有办法使用js来将数据借助MSEBlob都处理成fmp4格式的封装,喂给video标签,
我们就可以支持更多的视频格式,甚至是一些实时流推送协议(需要把协议承载在websocket上)
(MSE Blob 这些是在仅使用js下,统一数据源抽象的关键,其和websocket等机制的结合,真正取缔了之前插件的那部分工作)
一些主流js播放器,比如hls.js,亦或是flv.js,在js框架下,拓展了浏览器支持的文件格式。
其将浏览器自己请求视频资源并读取播放的过程转换成了js控制数据获取动作,以及完成转封装动作,再将封装数据进行送浏览器解析播放
由此我们可以实现无插件下的视频直播。

写在最后


大概就是这样,后面实操应该还有有其他问题,还需要测试浏览器的内置解码能力是否支持高码率的视频,以及延迟问题,后面再继续补充。
LONG LIVE FLV!

参考文献


[1] video标签说明.https://www.w3.org/TR/html5/embedded-content-0.html#the-video-element
[2] Web API 接口参考.https://developer.mozilla.org/zh-CN/docs/Web/API/Blob