传统直播技术,大多使用RTMP通过Flash进行传输。随着HTML5的逐渐实现,等媒体标签的浏览器支持,
很多视频逐渐向HTML5靠拢。Youtube等视频网站纷纷开始使用HTML5播放器,然而纵观当前的直播网站,大多
还是依赖Flash。直播为何不采用HTML5呢?
目前的HTML5直播思路有以下几种。一是使用js调用WebGL渲染视频,用websocket/XHR传输,比如jsmpeg项目,
实现了一个MPEG1的js解析器,该项目存在很多bug,另外由于MPEG1的效能极低(比GIF好不了多少),传输的视频
质量较差,而且js解析消耗很高CPU,这种方案不是很理想。二是使用Native的解码方案,使用MediaSourceExtension。
MSE支持一些格式,比如H264,VP8,VP9等(因浏览器而异),这里就VP8/VP9进行讨论。VP8/VP9的容器一般是webm,
然而chrome对webm的解析貌似不正确(很多网友说很多webm视频能够在Firefox上面使用MSE播放,但是不能在chrome上面
播放,ffmpeg压制出来的视频就是无法被chrome播放的)。webm格式大致可以分割为两部分,启动信息和媒体片段:
...
...
启动信息中包含了轨道数量,音视频轨道的编码,颜色,持续时间等等信息,后面的媒体信息里面包含了混杂的轨道流。MSE的一般模式
如下:
var video = document.querySelector('video');
var assetURL = 'frag_bunny.webm';
var mimeCodec = 'video/webm; codecs="vp9, vorbis"';
if ('MediaSource' in window && MediaSource.isTypeSupported(mimeCodec)) {
var mediaSource = new MediaSource();
//console.log(mediaSource.readyState); // closed
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.error('Unsupported MIME type or codec: ', mimeCodec);
}
function sourceOpen (_) {
//console.log(this.readyState); // open
var mediaSource = this;
var sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
fetchAB(assetURL, function (buf) {
sourceBuffer.addEventListener('updateend', function (_) {
mediaSource.endOfStream();
video.play();
//console.log(mediaSource.readyState); // ended
});
sourceBuffer.appendBuffer(buf);
});
};
function fetchAB (url, cb) {
console.log(url);
var xhr = new XMLHttpRequest;
xhr.open('get', url);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
cb(xhr.response);
};
xhr.send();
};
基本套路是先创建一个MediaSource,然后将MediaSource的URL传给Video标签,然后MediaSource会open,在sourceopen
事件中创建一个特定编码的SourceBuffer,然后将webm流填入这个SourceBuffer。但是这种方案需要准备好的webm,这种
webm的启动信息里面包含了视频长度,每个偏移在哪里找等等信息。对于直播流来说,因为实时性要求,不可能在头部回写视频
流的信息,所以有了一种DASH的解决方案,DASH.js。这种方案
将webm的启动信息和媒体信息分离,并且将视频和音频轨道分离:
通过HTTP传输,然后在浏览器端使用MSE解析。DASH会为每个轨道(视频/音频)创建一个SourceBuffer,先填入启动信息(头),
然后持续填入媒体片段(chunk)。但是MSE默认会从0开始播放,但是对于直播流,用户开始看的时候可能直播流已经进行了一段时间,
这时候如果向SourceBuffer填入当前时间(比如61.25s)的片段,Video标签并不会播放,这时候通过设置Video的currentTime可以
解决,通过SourceBuffer的buffered属性可以获得已经解析好的视频片段的开始时间和结束时间。但是实践发现,少于2s的chunk
极容易被解析失败,如果要求较小粒度的传输控制,可能要在前端使用js做buffer缓冲。
编程时需要注意的有,SourceBuffer的appendBuffer接受的buffer的时间范围是SourceBuffer的appendWindowStart和appendWindowEnd
控制的,在这个范围外的片段不会被接受。MSE的操作都是异步操作,SourceBuffer在解析的时候,不能再放入片段,所以需要监听updateend
事件,用队列处理异步请求。最后PS:VP9的码流非常清晰,500K就可以达到很好的效果。