借助MediaSource和SourceBuffer来实现webm格式视频的分片传输
<div class="article-info-box">
<div class="article-bar-top d-flex">
<span class="time">2014年08月30日 16:29:19</span>
<div class="float-right">
<span class="read-count">阅读数:4280</span>
</div>
</div>
</div>
<article>
<div id="article_content" class="article_content clearfix csdn-tracking-statistics" data-pid="blog" data-mod="popu_307" data-dsm="post">
<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/htmledit_views-0a60691e80.css">
<div class="htmledit_views">
在CloudTV项目的初期曾实现了整段视频的对等传输和播放,但是当视频较大时会产生相当长时间的延迟(具体延迟时间由网络状况和视频文件大小决定);因此开始尝试视频的切片传输。
基于没有过多的音视频编码基础,所以尝试了对webm格式视频的强制型切片传输。
之所以选择webm格式的视频,一是webm格式被html5和目前大多数浏览器所支持,二是webm格式视频相对编码方式比较单一,所使用的codec比较单一,所以在添加sourcebuffer时直接使用addSourceBuffer(‘video/webm; codecs=”vorbis,vp8”’)即可;
其基本思想比较简单:
对于视频发送端,用fileReader读取文件后,使用slice()函数将文件切片,然后将每个segment逐一传输到对等接收端;
对于视频接收端,将接收到得每一个文件片append到<video>标签对应的sourcebuffer上(在上一篇文章中已经讲过如何将sourcebuffer对应到video标签上)
但是具体实现时使用了一下几个技巧来解决一些问题:
(1)在发送端开始发送视频数据之前,需要先向对等端发送初始化消息通知(video command: start)对等端准备接受视频数据
对等端接收到初始化消息后,开始初始化工作:
- if(data[‘type’]==“video_com”){
- if(data[‘content’]==“start”){
- var oSourceBuffer, oMediaSource = new MediaSource();
- var url = window.URL.createObjectURL(oMediaSource);
- video.src = url;
- oMediaSource.addEventListener(prefix +’sourceopen’, function () {
- oSourceBuffer = oMediaSource.addSourceBuffer(’video/webm; codecs=”vorbis,vp8”’);
- //oSourceBuffer = oMediaSource.addSourceBuffer(‘video/mp4; codecs=”avc1.4d401f,mp4a.40.2”’);
- mediaSource = oMediaSource;
- sourceBuffer = oSourceBuffer;
- console.debug(’MediaSource readyState: <’, this.readyState, ‘>’);
- }, false);
- //video.src = window.URL.createObjectURL(oMediaSource);
- seg_num=0;
- }
- if(data[‘content’]== “end”){
- mediaSource.endOfStream();
- }
- }
if(data['type']=="video_com"){
if(data['content']=="start"){
var oSourceBuffer, oMediaSource = new MediaSource();
var url = window.URL.createObjectURL(oMediaSource);
video.src = url;
oMediaSource.addEventListener(prefix +'sourceopen', function () {
oSourceBuffer = oMediaSource.addSourceBuffer('video/webm; codecs="vorbis,vp8"');
//oSourceBuffer = oMediaSource.addSourceBuffer('video/mp4; codecs="avc1.4d401f,mp4a.40.2"');
mediaSource = oMediaSource;
sourceBuffer = oSourceBuffer;
console.debug('MediaSource readyState: <', this.readyState, '>');
}, false);
//video.src = window.URL.createObjectURL(oMediaSource);
seg_num=0;
}
if(data['content']== "end"){
mediaSource.endOfStream();
}
}
(2)对等端接收到分片的视频数据之后会将数据append到sourcebuffer上
- if(data[‘type’] == “video_seg”){
- console.debug(”get the ” + seg_num + “ segment”);
- var uint8array = new window.Uint8Array(data[‘content’]);
- sourceBuffer.appendBuffer(uint8array);
- if(seg_num==20){
- video.play();
- }
- seg_num++;
- }
if(data['type'] == "video_seg"){
console.debug("get the " + seg_num + " segment");
var uint8array = new window.Uint8Array(data['content']);
sourceBuffer.appendBuffer(uint8array);
if(seg_num==20){
video.play();
}
seg_num++;
}
此处需注意;append()函数时需要一定的执行时间的,如果在很短的时间内接收到两片及以上的数据,就会引发两次append()函数,在前次append()函数未执行结束的情况下再次调用append()函数会产生错误从而导致该sourcebuffer被移除从而出现错误
对于这个问题的解决方法有两种:
一是在接收端建立一个List或者Queue来按序存放接收到得视频数据片, 然后按顺序append到sourcebuffer上,此时可以通过检测sourcebuffer的appendingstate来检测当然的append函数是否执行完毕,当前一次执行完毕后再新append一个数据片
二是在发送端设置一定的发送间隔,为append()函数留出一定的执行时间,当然这个时间间隔的设置要合理。
实现代码为:
- function inner_streamer() {
- reader = new window.FileReader();
- reader.onload = function (e) {
- //self.push(new window.Uint8Array(e.target.result));
- var send_data = new window.Uint8Array(e.target.result);
- myconn.send({type:”video_seg”,content:send_data});
- startIndex += plus;
- if (startIndex <= size)
- {
- setTimeout(’inner_streamer()’,100);
- }
- else {
- //self.push({end: true});
- myconn.send({type:”video_com”,content:“end”});
- }
- };
- reader.readAsArrayBuffer(send_blob.slice(startIndex, startIndex + plus));
- }
function inner_streamer() {
reader = new window.FileReader();
reader.onload = function (e) {
//self.push(new window.Uint8Array(e.target.result));
var send_data = new window.Uint8Array(e.target.result);
myconn.send({type:"video_seg",content:send_data});
startIndex += plus;
if (startIndex <= size)
{
setTimeout('inner_streamer()',100);
}
else {
//self.push({end: true});
myconn.send({type:"video_com",content:"end"});
}
};
reader.readAsArrayBuffer(send_blob.slice(startIndex, startIndex + plus));
}
</div>
</div>
</article>
<div class="article-bar-bottom">
<div class="tags-box artic-tag-box">
<span class="label">文章标签:</span>
<a class="tag-link" href="http://so.csdn.net/so/search/s.do?q=webm&t=blog" target="_blank">webm </a><a class="tag-link" href="http://so.csdn.net/so/search/s.do?q=视频&t=blog" target="_blank">视频 </a><a class="tag-link" href="http://so.csdn.net/so/search/s.do?q=浏览器&t=blog" target="_blank">浏览器 </a><a class="tag-link" href="http://so.csdn.net/so/search/s.do?q=video&t=blog" target="_blank">video </a><a class="tag-link" href="http://so.csdn.net/so/search/s.do?q=DASH&t=blog" target="_blank">DASH </a>
</div>
<div class="tags-box">
<span class="label">个人分类:</span>
<a class="tag-link" href="https://blog.csdn.net/liulangdeyue/article/category/2377183" target="_blank">开源夏令营 </a>
</div>
</div>
<!-- !empty($pre_next_article[0]) -->
</div>