《视频技术研究院》第5期:如何在H5视频播放中实现P2P的应用

爱情像~蓝天白云~晴空万里~

上半年忽然就过去?

时间

就这样

从你刷X音的指缝里流走了

上半年

你是不是也立了很多“小目标”?

就像这样

Flag不倒,就是这么坚挺!

看看上半年你改了又改的“小目标”

其实生活状态是“海草舞”

“人海啊茫茫啊,随波逐流浮浮沉沉”

拔掉了无数个flag又如何

马上要步入新的征途

学习《视频技术研究院》的“小目标”

下半年言出必行,重新做人!

"如何在H5视频播放中实现P2P的应用"

一、简介

TS在h5中实现P2P功能需要考虑两个方面的问题:

第一:h5播放器如何播放流式视频数据;

第二:h5播放器支持什么格式的视频格式。

针对这个两个方面对浏览器进行的测试需要解决的也就是两个问题了。

一解决H5播放器对append数据添加的支持(mediaSource扩展接口支持了该需求);

二针对不同浏览器数据格式实现在线转封装(主要是mp4格式的支持)的需求。所以接下来我们就需要了解一下TS-MP4转封装和MediaSource的知识了。

二、FMP4封装的介绍

2012年W3C已经在官网上了发布了mediaSource扩展接口文档,到现在随着接口的不断完善和更新,部分浏览器已经逐渐开始支持这种扩展接口来实现视频数据内容的播放了。

不同浏览器的支持程度还是有相应的不同,例如现在的safari浏览器最新版8.0支持的视频格式包括ts,mp4。chrome浏览器支持的格式为mp4和webm。其他部分浏览器暂时还未支持这种接口。随着一元一流的发展以及m3u8标准的推广,现在大部分网络视频媒体数据已经转变为m3u8标准的ts格式媒体文件碎片。

关于mediaSource的前景和应用这里就不赘述了。对于最新的safari8.0来说可以直接使用mediaSource扩展对ts数据进行播放,而在chrome浏览器下是无法直接播放ts数据的,这样我们就面对了一个转封装的问题。

普通的mp4转封装是无法使用mediaSource来播放的。不过在mp4视频格式描述中同样也定了fmp4格式参数,通过对格式描述的修改可以实现ts数据到fmp4的转封装。

而对于移动平台来说现阶段支持mediaSource接口的相对还比较少,根据最新测试只有安卓4.4系统实现了该接口,苹果手机暂时还未支持该接口。而且即使支持该接口的移动端浏览器也不一定可以完全应用这个技术实现播放。

但是不管怎么说这种流式的播放已经逐渐开始走近我们的视线,虽然这种技术的应用和推广还有待发展。但是随着移动平台的发展以及流媒体视频在移动生活的应用,这种技术的应用也会越来越广泛。

随着流式播放的发展,出现最近比较流行基于FMP4的dash封装格式,它的封装比原来更完善,播放上效果也会更好。

三、mediaSource接口

W3C上有明确关于mediaSource扩展接口的文档,不过这里我还是简单介绍一下。mediaSource扩展文档中是这么定义的它允许JS脚本动态构建媒体流用于<audio>和<video>,允许JS传送媒体块到H5媒体元素。

这种接口的应用可以让h5播放器实现持续添加数据进行播放。做as的朋友都知道as中的appendBytes方法,一种添加二进制数据进行播放的方式。这两种接口在概念上是类似的。只是里面的定义和对媒体文件的要求有所不同。

对于mediaSource扩展接口我只介绍我们主要应用的几个:

1、判断是否存在mediaSource扩展类,该语句决定了整个播放方式是否可以使用meidaSource接口控制播放。

window.MediaSource=window.MediaSource||window.WebKitMediaSource;//分为pc和移动两种

2、isTypeSupported:判断是否支持要解码播放的视频文件编码和类型。

例如:

MediaSource.isTypeSupported('video/webm;codecs=“vorbis,vp8”’);//是否支持webm

MediaSource.isTypeSupported('video/mp4;codecs=“avc1.42E01E,mp4a.40.2”’);//是否支持mp4

MediaSource.isTypeSupported('video/mp2t;codecs=“avc1.42E01E,mp4a.40.2”’);//是否支持ts

3、addSourceBuffer:该事件是在触发sourceopen监听时进行的,该动作会创建一个sourceBuffer对象用于数据流的播放处理。如果mediaSource对象无法触发该事件,则无法通过该扩展进行播放的。

sourceBuffer=mediaSource.addSourceBuffer(DOMstring);

如:mediaSource.addSourceBuffer('video/mp4;codecs=“avc1.42E01E,mp4a.40.2”’);

4、appendBuffer:sourceBuffer对象的方法,用于持续数据的添加播放。sourceBuffer.appendBuffer(Uint8array);//Uint8array:媒体二进制数据

5、buffered:类型为TimeRanges,描述了添加进去的所有媒体数据的range信息。为一个数组,里面标示了持续或间断的时间信息列表。

如:for(vari=0;i<buffered.length;i++)

{

start=buffered.start(i);//第i个range信息的开始时间

end=buffered.end(i);//第i个range信息的结束时间

}

如果播放的媒体数据是连续的则只有一个开始时间点和一个结束时间点。所以如果要计算缓冲中还存在多少时间则可以通过该描信息与当前播放时间点进行换算。

实例:functionplay()

{

if(!this.mediaSource)

{

this.mediaSource=newMediaSource();

varme=this;

this.mediaSource.addEventListener('sourceopen',function(){me.onMediaSourceOpen();});

this.mediaSource.addEventListener('sourceended',function(){me.onMediaSourceEnded();});

this.mediaSource.addEventListener('sourceclose',function(){me.onMediaSourceClosed();});

this.mediaSource.addEventListener('error',function(){me.onUpdateError();});

this.video=this.createNewVideo();//创建HTMLMediaElement;

this.video.src=window.URL.createObjectURL(this.mediaSource);

this.video.play();

}

if(!this.sourceBuffer)

{

return;

}

if(this.sourceBuffer.updating)//上一块数据还在添加中

{

return;

}

try

{

this.sourceBuffer.appendBuffer(dataBytes);//添加数据

}

catch(err){}

}

functioncreateNewVideo()

{

varnewVideo=document.createElement("video");

newVideo.id="player";

newVideo.width=this.videoWidth;

newVideo.height=this.videoHeight;

returnnewVideo;

}

//事件侦听

onMediaSourceOpen:function()

{

//DOMString可以通过转码获得;

vartypeName='video/mp2t;codecs=“avc1.64001f”’;

varissurpport=MediaSource.isTypeSupported(typeName);//是否支持

this.mediaSource.duration=this.totalDuration;//设置视频总时长

this.sourceBuffer=this.mediaSource.addSourceBuffer(typeName);

}

onMediaSourceEnded:function()

{

console.log("sourceended!");

}

onMediaSourceClosed:function()

{

console.log("sourceclosed!");

}

其他细节定义大家可以查看W3C详细文档:MediaSourceExtensions

地址:http://www.w3.org/TR/media-source/#mediasource

四、FMP4封装必要条件

在W3C非官方标准文档ISOBMFFByteStreamFormat中明确规定了fmp4媒体文件封装的具体要求。

fmp4的封装可以分为InitializationSegments和mediaSegments两种,这两种的基本封装格式相差不多。

关于InitializationSegments必须注意的以下几点:

1、文件类型声明ftyp的box包含的major_brand或者compatible_brand用户代理方必须支持。

2、mohdbox里的任意box或者字段不能违背在ftypbox里定义的major_brand或者compatible_brands中的任何一个授权。

3、mohd中包含的samples的track(如stts,stsc,或者stco中的entry_count必须被设置成0);

4、mvexbox必须在moov中被包含,注:该box说明该视频包含moof需要解析。

mediaSegments的封装要求基本类似,同时增加了以下几点:

1、styp必须遵循ftyp;

2、traf里面必须包含tfdt;

3、mdat里的samples必须和trun里面的对应。

不同浏览器的解析兼容有所不同,未按上述要求封装的fmp4有的浏览器也可解析,不过数据的播放会存在问题,如卡顿,不流畅等。

五、FMP4封装主要box说明

1、BOX:这个box由长度信息和box类型两部分组成,即size,type。

size为一个长度数据,描述type里面包含的数据长度。

2、fullbox:继承BOX包含version(8bit)和flags(24bit)两个数据,版本和格式描述;

3、ftyp,styp:继承BOX,包含3个描述字段:major_brand,minor_version,compatible_brands.

如:0000001C667479706D7034320000000169736F6D6D70343264617368

其中0000001C为size长度的16进制32位数据,计算结果为28,即该box的总字节长度;

第二个4个字节66747970为ftyp的code码的16进制数;

第三个4字节数6D703432为major_brand字段值,转化成字符串为mp42;

第四个4字节数00000001为版本标示;

第五,六,七分别描述了一个list数组(69736F6D6D70343264617368)为compatible_brands转化为字符串为isommp42dash。

4、moov:继承BOX,包含mvhd,mvex,trak,这里有几点需要注意。mvex必须包含,trak下面的stts,stsc,stco里面的entry_count设置成0.

5、mvex:包含:mehd,trex。

6、trex:继承fullbox,包含字段:

unsignedint(32)version;//0

unsignedint(32)track_ID;//1.视频,2.音频

unsignedint(32)default_sample_description_index;//sample描述索引

unsignedint(32)default_sample_duration;//默认sample时长

unsignedint(32) default_sample_size;//默认sample大小

unsignedint(32) default_sample_flags//默认sample标示。

以上default_的数据会在moof中没有声明时默认使用。

7、trak:继承BOX,包含的box有tkhd,mdia。

8、tkhd:继承fullbox,包含字段:

unsignedint(32) version;//版本,这里默认写成0

unsignedint(32) creation_time;//创建时间

unsignedint(32) modification_time;//修改时间

unsignedint(32) track_ID;//1:视频,2:音频

constunsignedint(32) reserved=0;

unsignedint(32) duration;//时长,因为要封装成fmp4,这里写为0;

constunsignedint(32)[2]reserved=0;

templateint(16)layer=0;

templateint(16)alternate_group=0;

templateint(16)volume={iftrack_is_audio0x0100else0};

constunsignedint(16)reserved=0;

templateint(32)[9]matrix={0x00010000,0,0,0,0x00010000,0,0,0,0x40000000};

//unitymatrix

unsignedint(32)width;

unsignedint(32)height;

9、mdia:继承box,包含mdhd,hdlr,minf;

10、mdhd:继承fullbox,包含字段:

unsignedint(32) creation_time;//创建时间

unsignedint(32) modification_time;//修改时间

unsignedint(32) timescale;//时间比例

unsignedint(32) duration;//时长,fmp4可以写为0

bit(1)pad=0;

unsignedint(5)[3]language;//写为:0x55C4

codeunsignedint(16)pre_defined=0;

11、moof:继承BOX,包含mfhd,traf;

12、mfhd:继承Fullbox,包含字段:

unsignedint(32) versionandflags;

unsignedint(32) sequence_number;//fragment序列号

13、traf:包含tfhd,trun,tfdt;

14、tfhd:继承fullbox,包含字段:

unsignedint(32)versionandflags;

unsignedint(32)track_ID;

unsignedint(64)base_data_offset;

unsignedint(32)sample_description_index;

unsignedint(32)default_sample_duration;

unsignedint(32)default_sample_size;

unsignedint(32)default_sample_flags

字段1版本和标识符值描述:

*0x000001base-data-offset-present//基本数据偏移位置是否出现

*0x000002sample-description-index-present//samplem描述索引是否出现

*0x000008default-sample-duration-present//默认sample时长是否出现

*0x000010default-sample-size-present//默认sample大小是否出现

*0x000020default-sample-flags-present//默认sample标志是否出现

*0x010000duration-is-empty时长为空(default_sample_duration或者trex里的时

0x020000default-base-is-moof//从每个moof的开始计算偏移

在这里我们使用0x020000,即每个视频的偏移位置从moof算起。

15、trdt:继承Fullbox,包含字段:

unsignedint(32)version//使用0;

if(version==1){

unsignedint(64)baseMediaDecodeTime;

}else{//version==0

unsignedint(32)baseMediaDecodeTime;

}//所有添加进去的sample解码时长总和。

16、trun:继承fullbox,这是一个主要字段,描述了各个sample信息。

字段包括:

unsignedint(32)versionandflags;//描述标示

unsignedint(32) sample_count;//sample数量。

signedint(32)data_offset;//sample开始偏移

unsignedint(32) first_sample_flags;//关键标示符

{

unsignedint(32) sample_duration;//sample时长

unsignedint(32) sample_size;//sample大小

unsignedint(32) sample_flags//sample标示

if(version==0)//视频播放时间与解码时间偏移

{unsignedint(32)sample_composition_time_offset;}

else

{signedint(32)sample_composition_time_offset;}

}[sample_count]

字段1说明:

*0x000001:data-offset-present//数据偏移,从moof算起,到mdat结束。

*0x000004:first-sample-flags-present;//关键sample标示是否描述。

*0x000100sample-duration-present:标示每一个sample有他自己的时长,

否则使用默认值.

*0x000200sample-size-present:每个sample拥有大小,否则使用默认值

*0x000400sample-flags-present:每个sample有他自己的标志,否则使用默认值

*0x000800sample-composition-time-offsets-present;每个sample有一个compositiontimeoffset

这里我们根据视频和音频分别采用不同值,标示符的值是有各个要使用的标示值相加获得。

17、mdat:继承Box,视频和音频的主体数据。

字段:bit(8)data[];

以上是一些基本box的描述,具体细节可以参考INTERNATIONALSTANDARDISO/IEC14496-12里面的详细定义。

六、FMP4封装过程中的问题和解决方式

在fmp4转封装的过程中最基本要注意的是box的描述尽量完整,每个代理方对box的兼容性不同,有的在缺失部分描述信息时依然可以完成播放,而有的则会直接出现解码错误退出播放。

我们这里主要注意几点:

1、initsegment描述ftyp开头,包含moov,moof,mdat;

2、mediasegment描述styp开头,包含moov,moof,mdat;有的浏览器,如safari8.0在没有moov的情况下依然可以流畅的播放视频。但是在chrome浏览器下则会出现解码错误。

3、tfhd:tf_flags这里使用0x020000,只说明如果basedataoffset为0时使用trun里面相对于moof的数据偏移信息。该标示在ios5以后的版本中支持。因为我们需要转封装成fmp4,所以应该使用相对于moof的偏移,这样每次append进去的数据才不会在计算起始位置上出现不准确的问题。

4、tfdtbox的信息中baseMediaDecodeTime会决定mediaSource里的TimeRange信息的起始。

这个值有3种途径获得,

1)计算前面添加数据的总时长;

2)提取要添加数据的第一个sample时间戳;

3)根据ts数据的id信息。

5、trunbox的描述:version和flags描述值根据自己的需要计算获得,比如视频数据我们需要描述实际数据偏移(dataoffset),关键帧标示(firstsampleflags)以及每个sample标示符,大小和时间偏移,而这些数据对应的开始标示分别为:

0x000001(dataoffset),0x000004(firstsampleflags),0x000200(samplesizepresent),0x000400(sampleflagspresent),0x000800(sample-composition-time-offsets-present).把这些值相加为0xe05(versionflags).

所以说versionflags的值决定了下面描述信息的分析方式。而对于音频数据我们使用0x201,描述信息中只包含数据偏移值和每个sample的大小。

七、DASH封装格式的不同

1、trun的flags变化,fmp4:视频是0xe05,音频是0x201;dash:视频是0xa05,音频是0x201。视频部分说明做了一下修改。

2、stts,stsc,stsz,stco字段变化,去掉了sample信息的具体描述,设为0;

3、增加新的Box:sidx,描述开始时间戳,片段时长,timescale

等信息。

八、H5实现P2P的几种方式

解决了数据填充和转封装的问题,接下就是实现P2P的几种方案介绍了了,p2p的实现不仅仅是浏览器之间的,其中还包括各种视频终端应用之间的数据共享和传输。要实现数据的共享和传输,

首先,需要有数据节点储存服务器,他可以存储和筛选相同类型的节点并返回给客户端以供连接

其次,就是如何和各种节点建立连接,建立连接目前比较常用的有两种方式,一种是通过websocket建立连接,这种首先需要一端支持websocket的服务.

主要应用在浏览器和终端设备应用之间的建连;第二种就是浏览器之间的建连,首先浏览器本身无法自身建立websocket的服务监听,所以也就不能通过第一种方式建立连接了。

这里我们就要用另一种浏览支持的服务WEBRTC(现在大部分chrome浏览器,firefox等都支持,safari也已经开始支持了)。WebRTC,是一个支持网页浏览器进行实时语音对话或视频对话的技术,是谷歌收购GlobalIPSolutions公司而获得的一项技术。

九、WebRTC的建连过程

1、WebRTC建联首先需要一个中转媒介,这个媒介可以是http的也可以是websocket的。通常我们使用websocket服务作为中转媒介。

2、需要用到的接口说明:

RTCPeerConnection:返回一个新建的 RTCPeerConnection实例,它代表了本地端机器与远端机器的一条连接;

RTCDataChannel:建立两个浏览器之间的传输通道进行数据的传输。

3、建连过程:

首先浏览器首先注册自身到websocket服务,websocket服务返回相应其他节点信息描述。

A通过RTCPeerConnection创建连接,访问stun服务器,通过createOffer利用stun的穿透获得本地sdp信息,然后通过websocket服务转发给B,当B获得A的sdp信息后通过

setRemoteDescription把offer设置,同时通过createAnswer方法获取本地sdp信息并转发给A,A把B的sdp信息设置完成握手,然后A和B之间就可以通过创建dataChannel通到进行数据传输。

十、结尾

随着H5的发展,越来越多的视频web端都逐渐使用H5播放器进行视频的播放。如何让视频的播放在H5中更流畅与高效也成为了我们每个视频公司必须考虑的问题。

新乐视云联已经提供了H5P2P播放的SDK在乐视网的视频播放中。

有新乐视云联提供的P2P技术在H5播放器中应用的可实施性。我相信会有更多的视频公司在视频播放器的迁移上有了更等价的转换基础。

节省带宽和提供高效视频始终都是制约我们视频播放的两个因素,P2P技术的产生为我们降低了带宽成本,同时也为我们提供了高效的传输方式。随着webRTC技术在浏览器中的支持增加,p2p技术在H5中的应用也变得越来越广阔。

这一期的内容就到这里吧了

在评论区留下你下半年的“小目标”

半年后再来回顾一下

看看是否完成了心愿

和flag不倒的#博学小Le#一起出发吧!

发表评论
留言与评论(共有 0 条评论)
   
验证码:

相关文章

推荐文章

'); })();