方案对比

HLS

什么是HLS?

HLS全称是HTTP Live Streaming 是一个由苹果公司提出的基于HTTP的流媒体网络传输协议,用于直播或点播场景, 应该算是当前平台兼容性最好的流媒体协议了。HLS协议是苹果推出的解决方案,将视频分成5-10秒的视频小分片,然后用m3u8索引表进行管理,由于客户端下载到的视频都是5-10秒的完整数据,故视频的流畅性很好,但也同样引入了很大的延迟(HLS的一般延迟在10-30s左右)。

HLS协议客户端支持简单, 只需要支持 HTTP 请求即可, HTTP 协议无状态, 只需要按顺序下载媒体片段即可,而且网络兼容性好, 与实时传输协议(RTP)不同,HLS可以穿过任何允许HTTP数据通过的防火墙或者代理服务器。它也很容易使用内容分发网络来传输媒体流。

HLS以ts为传输格式,m3u8为索引文件(文件中包含了所要用到的ts文件名称,时长等信息,可以用播放器播放,也可以用vscode之类的编辑器打开查看),在移动端大部分浏览器都支持,也就是说你可以用video标签直接加载一个m3u8文件播放视频或者直播,但是在pc端,除了苹果的Safari,需要引入库来支持。

它的工作原理是把整个流分成一个个小的基于HTTP的文件来下载,每次只下载一些。当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,允许流媒体会话适应不同的数据速率。在开始一个流媒体会话时,客户端会下载一个包含元数据的extended M3U (m3u8)playlist文件,用于寻找可用的媒体流。

其他主流的流媒体协议还有RTP(内容传输使用UDP)和Adobe的RTMP(基于TCP)。

HLS 协议格式要求

  • 视频的封装格式 TS(流媒体文件);
  • 保存 TS 索引的 M3U8 文件;
  • 视频的编码格式:H264 (只要 MPEG-TS 支持,基本都可以,只是有些格式不是免费的;音频类似);
  • 音频的编码格式:AAC、MP3、AC-3。

HLS协议在客户端的表现形式

我们打开一个使用HLS技术的视频网站,比如优酷,我们可以看到在视频播放时通过调试查看Network里的xhr请求,会发现一个m3u8文件,和每隔一段时间请求几个ts文件。


为什么苹果要提出 HLS 这个协议,其实它的主要是为了解决 RTMP 协议存在的一些问题。比如 RTMP 协议不使用标准的 HTTP 接口传输数据,所以在一些特殊的网络环境下可能被防火墙屏蔽掉。但是 HLS 由于使用的 HTTP 协议传输数据,通常情况下不会遇到被防火墙屏蔽的情况。除此之外,它也很容易通过 CDN(内容分发网络)来传输媒体流。

HLS 支持直播或者点播,同时支持加密和认证。从概念上来说,HTTP通常包括三部分:服务器端、发布端、客户端。

使用HLS协议的案例

那么有哪些知名网站是使用的HLS技术的呢?
优酷、腾讯视频、芒果TV

HLS 协议优势

  • 客户端支持简单, 只需要支持 HTTP 请求即可, HTTP 协议无状态, 只需要按顺序下载媒体片段即可.
  • 使用 HTTP 协议网络兼容性好, HTTP 数据包也可以方便地通过防火墙或者代理服务器, CDN 支持良好.
  • Apple 的全系列产品支持, 由于 HLS 是苹果提出的, 所以在 Apple 的全系列产品包括 iphone, ipad, safari 都不需要安装任何插件就可以原生支持播放 HLS, 现在, Android 也加入了对 HLS 的支持.
  • 自带多码率自适应, Apple 在提出 HLS 时, 就已经考虑了码流自适应的问题.

HLS 协议劣势

  • 延时较大,尤其是在直播的情况下,很难做到 10s 以内的延时(不排除网上各种改进版本及算法);
  • 内容生成时对编码端性能要求较高;
  • 对于点播服务来说, 由于 TS 切片通常较小, 海量碎片在文件分发, 一致性缓存, 存储等方面都有较大挑战。

m3u8文件协议


客户端逻辑:

1、客户端通过 URI 获取 Playlist. 如果是 Master Playlist, 客户端可以选择一个 Variant Stream 来播放.
2、客户端检查 EXT-X-VERSION 版本是否满足.
3、客户端应该忽略不可识别的 tags, 忽略不可识别的属性键值对.
4、加载 Media Playlist file.
5、 播放 Media Playlist file.
6、重加载 Media Playlist file.
7、决定下一次要加载的 Media Segment.

一级m3u8文件
#EXTM3U
#EXT-X-VERSION:5
#EXT-X-STREAM-INF:BANDWIDTH=3128000,CODECS="avc1.4d001f,mp4a.40.2",RESOLUTION=1280x720
chunklist_w1690990834_b3128000.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1778000,CODECS="avc1.4d001e,mp4a.40.2",RESOLUTION=852x480
chunklist_w1690990834_b1778000.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1048000,CODECS="avc1.4d001e,mp4a.40.2",RESOLUTION=640x360
chunklist_w1690990834_b1048000.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=738000,CODECS="avc1.4d0015,mp4a.40.2",RESOLUTION=428x240
chunklist_w1690990834_b738000.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=528000,CODECS="avc1.4d000d,mp4a.40.2",RESOLUTION=312x176
chunklist_w1690990834_b528000.m3u8

我们分析该 m3u8 文件可知:

  • #EXTM3U:扩展标记 ,意思是我是 m3u 文件
  • #EXT-X-VERSION:版本
  • #EXT-X-STREAM-INF:指定一个包含多媒体信息的 media URI 作为 PlayList,一般做 M3U8 的嵌套使用,它只对紧跟后面的 URI 有效,#EXT-X-STREAM-INF:有以下属性:
    • BANDWIDTH:带宽
    • CODECS:不是必须的。
    • RESOLUTION:分辨率。
二级m3u8文件
#EXTM3U
#EXT-X-VERSION:5
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:6.0,
media_w570392994_b3128000_0.ts
#EXTINF:6.0,
media_w570392994_b3128000_1.ts
#EXTINF:6.0,
media_w570392994_b3128000_2.ts
#EXTINF:6.0,
media_w570392994_b3128000_3.ts
#EXTINF:6.0,
media_w570392994_b3128000_4.ts
#EXTINF:6.0,
media_w570392994_b3128000_5.ts
#EXTINF:6.0,
media_w570392994_b3128000_6.ts
#EXTINF:6.0,
media_w570392994_b3128000_7.ts
#EXTINF:6.0,
media_w570392994_b3128000_8.ts
#EXTINF:6.0,
media_w570392994_b3128000_9.ts
#EXTINF:6.0,
media_w570392994_b3128000_10.ts
#EXTINF:6.0,
media_w570392994_b3128000_11.ts
#EXTINF:6.0,
media_w570392994_b3128000_12.ts
#EXTINF:6.0,
media_w570392994_b3128000_13.ts
#EXTINF:6.0,
media_w570392994_b3128000_14.ts
#EXTINF:6.0,
media_w570392994_b3128000_15.ts
#EXTINF:6.0,
media_w570392994_b3128000_16.ts
#EXTINF:6.0,
media_w570392994_b3128000_17.ts
#EXTINF:6.0,
media_w570392994_b3128000_18.ts
#EXTINF:6.0,
media_w570392994_b3128000_19.ts
#EXTINF:6.0,
media_w570392994_b3128000_20.ts
#EXTINF:6.0,
media_w570392994_b3128000_21.ts
#EXTINF:2.66,
media_w570392994_b3128000_22.ts
#EXT-X-ENDLIST

我们分析该二级索引文件:

#EXTM3U          m3u文件头,必须放在第一行
#EXT-X-VERSION : 版本;
#EXT-X-MEDIA-SEQUENCE:0  第一个TS分片的序列号  
#EXT-X-TARGETDURATION:指定最大的流片段时间长(秒),也就是说这些 ts 切片的时长不能大于这个值;
#EXTINF: extra info,分片TS的信息,如时长,带宽等;指定每个流片段(ts)的持续时间(秒),仅对其后面的 URI 有效,title 是下载资源的 URI;
#EXT-X-ENDLIST: m3u8文件结束符 表示视频已经结束,这个标志同时也说明当前的流是一个非直播流。

此外,还有可能经常出现的属性是

#EXT-X-ALLOW-CACHE:             //是否允许cache

HLS应用

HLS目前广泛应用于点播和直播领域。
如果是直播,客户端会不停的去请求这个m3u8文件,当这个列表有新的ts文件,客户端会请求新的ts文件追加到本地播放序列。
在HTML5页面上使用HLS非常简单。

<video src="index.m3u8" controls></video><video controls>
    <source src="index.m3u8"></source>
</video>

example

这里随便拿了个优酷视频的某个免费视频来做的例子:

<!DOCTYPE html>
<html>
<head>
  <meta charset=utf-8 />
  <title>videojs-contrib-hls embed</title>

  <link href="https://unpkg.com/video.js/dist/video-js.css" rel="stylesheet">
  <script src="https://unpkg.com/video.js/dist/video.js"></script>
  <script src="https://unpkg.com/videojs-contrib-hls/dist/videojs-contrib-hls.js"></script>
  <!-- videojs-contrib-hls 用于在电脑端播放 如果只需手机播放可以不引入 -->
</head>
<body>
<h1>Example Embed</h1>

<video id="my_video_1" class="video-js vjs-default-skin" controls preload="auto" width="640" height="268"
       data-setup='{}'>
  <source src="https://valipl.cp31.ott.cibntv.net/6572BEC45C53C719F051721A6/030006000061CBFF23C2B9C54F41303BB2A83E-D58B-4324-9B44-164321AEAF62.m3u8?ccode=0502&duration=96&expire=18000&psid=3d3e616c3cebda69def362a20035371b41346&ups_client_netip=7407e77c&ups_ts=1659584344&ups_userid=&utid=8jNxGwIyqxYCAXQH53rh1l91&vid=XNTgzMDMyOTA5Ng%3D%3D&vkey=B59a90fc078349c435eaf5188a9a0e1cd&s=fddf89aa4dba4e9e8103&eo=0&t=8db26e7f1b05f5b&cug=1&fms=678c4f1e5bc47433&tr=96&le=5d4030ea4312aa29865be19596e9a660&ckt=5&rid=20000000901F6A3DC11AFF45AD947B2F8A620E7D02000000&type=mp4hdv3&bc=2&dre=u37&si=73&dst=1&sm=1&operate_type=1" type="application/x-mpegURL">
</video>

<script>
</script>

</body>
</html>

DASH

什么是DASH?

DASH(MPEG-DASH)是 Dynamic Adaptive Streaming over HTTP的缩写,是国际标准组 MPEG 2014年推出的技术标准, 主要目标是形成IP网络承载单一格式的流媒体并提供高效与高质量服务的统一方案, 解决多制式传输方案(HTTP Live Streaming, Microsoft Smooth Streaming, HTTP Dynamic Streaming)并存格局下的存储与服务能力浪费、运营高成本与复杂度、系统间互操作弱等问题.

DASH是基于HTTP的动态自适应的比特率流技术,使用的传输协议是TCP(有些老的客户端直播会采用UDP协议直播, 例如YY, 齐齐视频等). 和HLS, HDS技术类似, 都是把视频分割成一小段一小段, 通过HTTP协议进行传输,客户端得到之后进行播放;不同的是MPEG-DASH支持MPEG-2 TS、MP4等多种格式, 可以将视频按照多种编码切割, 下载下来的媒体格式既可以是ts文件也可以是mp4文件, 所以当客户端加载视频时, 按照当前的网速和支持的编码加载相应的视频片段进行播放。

这个方案索引文件通常是mpd文件(类似HLS的m3u8文件功能),传输格式推荐的是fmp4(Fragmented MP4),文件扩展名通常为.m4s、.m4a、.m4v或直接用.mp4。所以用调试查看b站视频播放时的网络请求,会发现每隔一段时间有几个m4s文件请求。

虽然 HTML5 不直接支持 MPEG-DASH,但是已有一些 MPEG-DASH 的 JavaScript 实现允许在网页浏览器中通过 HTML5 Media Source Extensions(MSE)使用 MPEG-DASH。另有其他 JavaScript 实现,如 bitdash 播放器支持使用 HTML5 加密媒体扩展播放有 DRM 的MPEG-DASH。当与 WebGL 结合使用,MPEG-DASH 基于 HTML5 的自适应比特率流还可实现 360° 视频的实时和按需的高效流式传输。


DASH工作原理

  • Server端将媒体文件切割一个个时间长度相等的切片(Segment),每个切片被编码为不同的码率/分辨率。
  • Client端通过评估自身的性能和带宽情况,下载相应码率和分辨率的切片。带宽好,下载码率高的切片;带宽差,下载码率低的切片。

因为不同质量的切片在时间上是对齐的,所以在不同质量的切片之前切换的话,自然是顺畅的,从而达到无缝切换的效果。

DASH相比于HLS的优势

  • 更加通用且正式的码率切换流媒体协议,由MPEG组织制定,而非私人公司制定的。
  • 相比广泛应用的HLS v3来说,可以减少多轨媒体的存储空间和传输带宽。多轨媒体有不同的audio、video和字幕,如果要将它们组合的话,那会有很多种不同的组合,(多视角case会有不同的video track)而FMP4是将audio、video和字幕分开的,没有封装在一起,那么就不需要将它们一一组合并存储以满足客户的不同需求。客户需要什么版本的audio 或video或字幕,分开请求即可,从而减少服务器的存储空间。
  • 基于模板的媒体描述文件相比m3u8文件,文件体积大大减小。

DASH和HLS协议的对比

协议索引文件传输格式
HLSm3u8ts
DASHmpdm4s

DASH协议MPD结构

DASH系统中的MPD文件是用XML编写的,先来看看MPD文件长什么样子

<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" minBufferTime="PT1.500S" type="static" mediaPresentationDuration="PT0H1M30.080S" maxSegmentDuration="PT0H0M1.000S" profiles="urn:mpeg:dash:profile:full:2011">
    <ProgramInformation moreInformationURL="http://gpac.io">
        <Title>xgplayer-demo_dash.mpd generated by GPAC</Title>
    </ProgramInformation>
    <Period duration="PT0H1M30.080S">
        <AdaptationSet segmentAlignment="true" maxWidth="1280" maxHeight="720" maxFrameRate="25" par="16:9" lang="eng">
            <ContentComponent id="1" contentType="audio"/>
            <ContentComponent id="2" contentType="video"/>
            <Representation id="1" mimeType="video/mp4" codecs="mp4a.40.2,avc3.4D4020" width="1280" height="720" frameRate="25" sar="1:1" startWithSAP="0" bandwidth="6046495">
                <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
                <BaseURL>xgplayer-demo_dashinit.mp4</BaseURL>
                <SegmentList timescale="1000" duration="1000">
                    <Initialization range="0-1256"/>
                    <SegmentURL mediaRange="1257-1006330" indexRange="1257-1300"/>
                    <SegmentURL mediaRange="1006331-1909476" indexRange="1006331-1006374"/>
                    <SegmentURL mediaRange="1909477-2306027" indexRange="1909477-1909520"/>
                    <SegmentURL mediaRange="2306028-3069819" indexRange="2306028-2306071"/>
                    <SegmentURL mediaRange="3069820-3670530" indexRange="3069820-3069863"/>
                    <SegmentURL mediaRange="3670531-4377737" indexRange="3670531-3670574"/>
                    <SegmentURL mediaRange="4377738-4654247" indexRange="4377738-4377781"/>
                    <SegmentURL mediaRange="4654248-4867468" indexRange="4654248-4654291"/>
                    <SegmentURL mediaRange="4867469-5070859" indexRange="4867469-4867512"/>
                    <SegmentURL mediaRange="5070860-5247149" indexRange="5070860-5070903"/>
                    <SegmentURL mediaRange="5247150-5465633" indexRange="5247150-5247193"/>
                    <SegmentURL mediaRange="5465634-6663456" indexRange="5465634-5465677"/>
                    <SegmentURL mediaRange="6663457-7130372" indexRange="6663457-6663500"/>
                    <SegmentURL mediaRange="7130373-8300949" indexRange="7130373-7130416"/>
                    <SegmentURL mediaRange="8300950-9362155" indexRange="8300950-8300993"/>
                    <SegmentURL mediaRange="9362156-10536551" indexRange="9362156-9362199"/>
                    <SegmentURL mediaRange="10536552-11225063" indexRange="10536552-10536595"/>
                    <SegmentURL mediaRange="11225064-11913769" indexRange="11225064-11225107"/>
                    <SegmentURL mediaRange="11913770-12572215" indexRange="11913770-11913813"/>
                    <SegmentURL mediaRange="12572216-13208116" indexRange="12572216-12572259"/>
                    <SegmentURL mediaRange="13208117-14757656" indexRange="13208117-13208160"/>
                    <SegmentURL mediaRange="14757657-16589942" indexRange="14757657-14757700"/>
                    <SegmentURL mediaRange="16589943-18522704" indexRange="16589943-16589986"/>
                    <SegmentURL mediaRange="18522705-20694609" indexRange="18522705-18522748"/>
                    <SegmentURL mediaRange="20694610-22833797" indexRange="20694610-20694653"/>
                    <SegmentURL mediaRange="22833798-24331031" indexRange="22833798-22833841"/>
                    <SegmentURL mediaRange="24331032-26636091" indexRange="24331032-24331075"/>
                    <SegmentURL mediaRange="26636092-28285534" indexRange="26636092-26636135"/>
                    <SegmentURL mediaRange="28285535-28794821" indexRange="28285535-28285578"/>
                    <SegmentURL mediaRange="28794822-29358710" indexRange="28794822-28794865"/>
                    <SegmentURL mediaRange="29358711-29763375" indexRange="29358711-29358754"/>
                    <SegmentURL mediaRange="29763376-30299423" indexRange="29763376-29763419"/>
                    <SegmentURL mediaRange="30299424-30780313" indexRange="30299424-30299467"/>
                    <SegmentURL mediaRange="30780314-31266216" indexRange="30780314-30780357"/>
                    <SegmentURL mediaRange="31266217-31762283" indexRange="31266217-31266260"/>
                    <SegmentURL mediaRange="31762284-32276303" indexRange="31762284-31762327"/>
                    <SegmentURL mediaRange="32276304-32627589" indexRange="32276304-32276347"/>
                    <SegmentURL mediaRange="32627590-32730118" indexRange="32627590-32627633"/>
                    <SegmentURL mediaRange="32730119-32826391" indexRange="32730119-32730162"/>
                    <SegmentURL mediaRange="32826392-32910777" indexRange="32826392-32826435"/>
                    <SegmentURL mediaRange="32910778-33164888" indexRange="32910778-32910821"/>
                    <SegmentURL mediaRange="33164889-33731126" indexRange="33164889-33164932"/>
                    <SegmentURL mediaRange="33731127-34377257" indexRange="33731127-33731170"/>
                    <SegmentURL mediaRange="34377258-35385471" indexRange="34377258-34377301"/>
                    <SegmentURL mediaRange="35385472-35863136" indexRange="35385472-35385515"/>
                    <SegmentURL mediaRange="35863137-36606203" indexRange="35863137-35863180"/>
                    <SegmentURL mediaRange="36606204-37820101" indexRange="36606204-36606247"/>
                    <SegmentURL mediaRange="37820102-38753987" indexRange="37820102-37820145"/>
                    <SegmentURL mediaRange="38753988-39813946" indexRange="38753988-38754031"/>
                    <SegmentURL mediaRange="39813947-40789232" indexRange="39813947-39813990"/>
                    <SegmentURL mediaRange="40789233-41421325" indexRange="40789233-40789276"/>
                    <SegmentURL mediaRange="41421326-41978192" indexRange="41421326-41421369"/>
                    <SegmentURL mediaRange="41978193-43125449" indexRange="41978193-41978236"/>
                    <SegmentURL mediaRange="43125450-44403588" indexRange="43125450-43125493"/>
                    <SegmentURL mediaRange="44403589-45976624" indexRange="44403589-44403632"/>
                    <SegmentURL mediaRange="45976625-46969632" indexRange="45976625-45976668"/>
                    <SegmentURL mediaRange="46969633-48818001" indexRange="46969633-46969676"/>
                    <SegmentURL mediaRange="48818002-49338441" indexRange="48818002-48818045"/>
                    <SegmentURL mediaRange="49338442-50561941" indexRange="49338442-49338485"/>
                    <SegmentURL mediaRange="50561942-51403945" indexRange="50561942-50561985"/>
                    <SegmentURL mediaRange="51403946-52050093" indexRange="51403946-51403989"/>
                    <SegmentURL mediaRange="52050094-52260731" indexRange="52050094-52050137"/>
                    <SegmentURL mediaRange="52260732-52407092" indexRange="52260732-52260775"/>
                    <SegmentURL mediaRange="52407093-52581896" indexRange="52407093-52407136"/>
                    <SegmentURL mediaRange="52581897-52745635" indexRange="52581897-52581940"/>
                    <SegmentURL mediaRange="52745636-52897527" indexRange="52745636-52745679"/>
                    <SegmentURL mediaRange="52897528-53061758" indexRange="52897528-52897571"/>
                    <SegmentURL mediaRange="53061759-53502125" indexRange="53061759-53061802"/>
                    <SegmentURL mediaRange="53502126-54077769" indexRange="53502126-53502169"/>
                    <SegmentURL mediaRange="54077770-54857615" indexRange="54077770-54077813"/>
                    <SegmentURL mediaRange="54857616-55932404" indexRange="54857616-54857659"/>
                    <SegmentURL mediaRange="55932405-56944664" indexRange="55932405-55932448"/>
                    <SegmentURL mediaRange="56944665-58363830" indexRange="56944665-56944708"/>
                    <SegmentURL mediaRange="58363831-59518213" indexRange="58363831-58363874"/>
                    <SegmentURL mediaRange="59518214-60373109" indexRange="59518214-59518257"/>
                    <SegmentURL mediaRange="60373110-61436227" indexRange="60373110-60373153"/>
                    <SegmentURL mediaRange="61436228-61653990" indexRange="61436228-61436271"/>
                    <SegmentURL mediaRange="61653991-62408497" indexRange="61653991-61654034"/>
                    <SegmentURL mediaRange="62408498-63325686" indexRange="62408498-62408541"/>
                    <SegmentURL mediaRange="63325687-64065073" indexRange="63325687-63325730"/>
                    <SegmentURL mediaRange="64065074-64473335" indexRange="64065074-64065117"/>
                    <SegmentURL mediaRange="64473336-64804509" indexRange="64473336-64473379"/>
                    <SegmentURL mediaRange="64804510-65446270" indexRange="64804510-64804553"/>
                    <SegmentURL mediaRange="65446271-66505071" indexRange="65446271-65446314"/>
                    <SegmentURL mediaRange="66505072-67261634" indexRange="66505072-66505115"/>
                    <SegmentURL mediaRange="67261635-67619101" indexRange="67261635-67261678"/>
                    <SegmentURL mediaRange="67619102-67842862" indexRange="67619102-67619145"/>
                    <SegmentURL mediaRange="67842863-67932427" indexRange="67842863-67842906"/>
                    <SegmentURL mediaRange="67932428-68012127" indexRange="67932428-67932471"/>
                    <SegmentURL mediaRange="68012128-68082015" indexRange="68012128-68012171"/>
                    <SegmentURL mediaRange="68082016-68083543" indexRange="68082016-68082059"/>
                </SegmentList>
            </Representation>
        </AdaptationSet>
    </Period>
</MPD>
MPD 标签
  • MPD(Media Presentation Description)全称为媒体呈现描述,是一个XML文件。
  • 它完整的表示了视频的所有信息,包括视频长度,码率和分辨率等等。
  • 请求DASH URL实际上就是获得一个MPD文件。
    属性:
    profiles: 不同的profile对应不同的MPD要求和Segment格式要求;
    mediaPresentationDuration:整个节目的时长;
    minBufferTime: 至少需要缓冲的时间
    type:点播对应static,直播对应dynamic
    availabilityStartTime=2019-05-22T22:16:57Z:如果是直播流的话,则必须提供,代表MPD中所有Seg从该时间开始可以request了
    minimumUpdatePeriod=PT10H:至少每隔这么长时间,MPD就有可能更新一次,只用于直播流。
BaseURL 根目录

该元素可以在MPD\Period\AdaptationSet\Representation同时出现,若同时出现,则层层嵌套;在每一层也可以出现多次,默认使用第一个BaseURL;

Period 区段

时间段。一个或者多个Period组成一个MPD文件,每个Period表示一个时间段的媒体。
比如某个码流的长度为60秒,假如分为三个Periods:Period1->015s;Period2->1640s;Period3->41~60s。
在同一个Period内,可用的媒体内容及其各个可用码率都不会发生变更。
直播情况下,服务器周期性的从MPD文件中移除已过时的Period,并增添新的Period。

一条完整的mpeg dash码流可能由一个或多个Period构成,每个Period代表某一个时间段。比如某条码流有60秒时间,Period1从0-15秒,Period2从16秒到40秒,Period3从41秒到60秒。同一个Period内,意味着可用的媒体内容及其各个可用码率(Representation)不会发生变更。直播情况下,“可能”需要周期地去服务器更新MPD文件,服务器可能会移除旧的已经过时的Period,或是添加新的Period。新的Period中可能会添加新的可用码率或去掉上一个Period中存在的某些码率, 即上面的 Representation 字段。

属性:

  • duration: Period的时长;
  • start: Period的开始时间.
AdaptationSet 自适应子集

自适应集合。描述同一时间段不同类型的媒体数据,如字幕,音频和视频。
自适应集合,一个或多个AdaptationSet组成一个Period,AdaptationSet包含了逻辑一致的媒体呈现的格式。
对于video来说,每个AdaptationSet由一组可供切换的不同码率/分辨率的码流组成。
而对于audio来说,每个audio AdaptationSet对应同一种语言的不同质量的音频。

一个Period由一个或者多个Adaptationset组成。Adaptationset由一组可供切换的不同码率的码流(Representation)组成,这些码流中可能包含一个(ISO profile)或者多个(TS profile)media content components,因为ISO profile的mp4或者fmp4 segment中通常只含有一个视频或者音频内容,而TS profile中的TS segment同时含有视频和音频内容. 当同时含有多个media component content时,每个被复用的media content component将被单独描述。

属性:

  • segmentAlignment: 如果为true,则代表该AS中的segment互不重叠
  • startWithSAP: 每个Segment的第一帧都是关键帧
  • mimeType AdaptationSet 的媒体类型
  • minWidth 最小宽度
  • par 宽高比
  • contentType: 内容类型
content component 媒体内容

一个media content component表示表示一个不同的音视频内容,比如不同语言的音轨属于不同的media content component,而同一音轨的不同码率(mpeg dash中叫做Representation)属于相同的media content component。如果是TS profile,同一个码率可能包括多个media content components。

SegmentTemplate 片段模板

组成下载 Representation 的URL 模板

属性:

  • media: 指定用来生成Segment列表的模板,可以包含的通配符有 R e p r e s e n t a o n I D RepresentaonID RepresentaonID B a n d w i d t h Bandwidth Bandwidth N u m b e r Number Number, T i m e Time Time
Representation 媒体文件描述

每个Adaptationset包含了一个或者多个Representations,一个Representation包含一个或者多个media streams,每个media stream对应一个media content component。为了适应不同的网络带宽,dash客户端可能会从一个Representation切换到另外一个Representation

属性:

  • codecs=avc1.640028 解码器标准
  • bandwidth=3200000 需要带宽 3.2Mbps
Segment

片段,DASH媒体概念的最小单位,表示一段小的媒体片段。
每个Representation中的内容被切分成一段段Segments,使得客户端在播放时能够方便在不同的Representation之间切换。
每个Segment由一个对应的URL指定,客户端通过访问该URL获得可播放的媒体数据。
Segment之间不允许相互覆盖,且是解码独立的,不依赖其它Segment。
对于承载ISO profile的segment,可分为Initialization Segment和Media Segment。
Initialization Segment包含MOOV,每个Representation只有一个。
Media Segment包含媒体数据(moof+mdat),每个RePresentation有若干个。

前端使用DASH播放视频

有一个开源库专门用于HTML5的web视频播放(使用DASH协议),叫做dash.js;

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
	<!--先引入dash.js-->
	<script defer src="https://cdn.bootcdn.net/ajax/libs/dashjs/4.4.1/dash.all.debug.min.js"></script>
    <style>
        video {
            width: 900px;
            height: 600px;
        }
    </style>
</head>
<body>

<video controls></video>

<script>
    function init() {
        var video,
            player,
            url = 'https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd';

        video = document.querySelector('video');
		// 创建播放器
        player = dashjs.MediaPlayer().create();

        /* restart playback in muted mode when auto playback was not allowed by the browser */
        player.on(dashjs.MediaPlayer.events.PLAYBACK_NOT_ALLOWED, function (data) {
            console.log('Playback did not start due to auto play restrictions. Muting audio and reloading');
            video.muted = true;
            player.initialize(video, url, true);
        });

        player.initialize(video, url, true);
    }

    document.addEventListener('DOMContentLoaded', function () {
        init();
    });
</script>
</body>
</html>

MSE

MSE全称为 Media Source Extensions API。

Media Source Extensions 是什么

媒体源扩展(Media Source Extensions,缩写MSE)是一项W3C规范,MSE允许Javascript为audio标签和video标签动态地构造媒体源。即媒体源扩展,可以理解为一种API,其提供了实现无插件且基于 Web 的流媒体的功能。**通过 MSE,媒体串流能够通过 JavaScript 创建,并且可以使用 HTML5 的 和 标签进行播放。

借助MSE的能力,我们可以将接收到的实时流通过 blob url 往video标签中灌入二进制数据(如fmp4格式流),或者使用 canvas 来实现直播。

MSE的能力

近几年来,我们已经可以在 Web 应用程序上无插件地播放视频和音频了。但是,现有架构过于简单,只能满足一次播放整个曲目的需要,无法实现拆分/合并数个缓冲文件。早期的流媒体主要使用 Flash 进行服务,以及通过 RTMP 协议进行视频串流的 Flash 媒体服务器。

媒体源扩展(MSE)实现后,情况就不一样了。MSE 使我们可以把通常的单个媒体文件的 src 值替换成引用 MediaSource 对象(一个包含即将播放的媒体文件的准备状态等信息的容器),以及引用多个 SourceBuffer 对象(代表多个组成整个串流的不同媒体块)的元素。

为了便于大家理解,我们来看一下基础的 MSE 数据流:


媒体源扩展(MSE)实现后,情况就不一样了。MSE 使我们可以把通常的单个媒体文件的 src 值替换成引用 MediaSource 对象(一个包含即将播放的媒体文件的准备状态等信息的容器),以及引用多个 SourceBuffer 对象(代表多个组成整个串流的不同媒体块)的元素。MSE 让我们能够根据内容获取的大小和频率,或是内存占用详情(例如什么时候缓存被回收),进行更加精准地控制。 它是基于它可扩展的 API 建立自适应比特率流客户端(例如 DASH 或 HLS 的客户端)的基础。

在现代浏览器中创造能兼容 MSE 的媒体(assets)非常费时费力,还要消耗大量计算机资源和能源。此外,还须使用外部实用程序将内容转换成合适的格式。虽然浏览器支持兼容 MSE 的各种媒体容器,但采用 H.264 视频编码、AAC 音频编码和 MP4 容器的格式是非常常见的,且一定兼容。MSE 同时还提供了一个 API,用于运行时检测容器和编解码是否受支持。

如果没有精确的控制时间、媒体质量和内存释放等需求,使用 <video><source> 是一个更加简单但够用的方案。


我们得先来介绍一下客户端音视频播放器播放一个视频流的主要流程:

获取流媒体 -> 解协议 -> 解封装 -> 音、视频解码 -> 音频播放及视频渲染(需处理音视频同步)。

由于采集的原始音视频数据比较大,为了方便网络传输,我们通常会使用编码器,如常见的 H.264 或 AAC 来压缩原始媒体信号。最常见的媒体信号是视频,音频和字幕。比如,日常生活中的电影,就是由不同的媒体信号组成,除运动图片外,大多数电影还含有音频和字幕。

常见的视频编解码器有:H.264,HEVC,VP9 和 AV1。而音频编解码器有:AAC,MP3 或 Opus。每个媒体信号都有许多不同的编解码器。


MSE的一些属性
状态ReadyState
enum ReadyState {
    "closed", // 指示当前源未附加到媒体元素。
    "open", // 源已经被媒体元素打开,数据即将被添加到SourceBuffer对象中
    "ended" // 源仍附加到媒体元素,但endOfStream()已被调用。
}

表示 MediaSource 的当前状态,可选值有:

  • closed,表示当前还没有附加到媒体元素上。
  • open,已经附加到媒体元素上,并准备好将数据附加SourceBuffer到sourceBuffers。
  • ended,仍附加在媒体元素,但已调用了endOfStream()结束当前流。
流终止异常EndOfStreamError
enum EndOfStreamError {
    "network", // 终止播放并发出网络错误信号。
    "decode" // 终止播放并发出解码错误信号。
};
sourceBuffers

只读属性,返回当前 MediaSource 包含的的 SourceBuffer 的对象列表。

activeSourceBuffers

只读属性,返回当前 MediaSource.sourceBuffers 中的 SourceBuffer 子集的对象,这个对象包含当前被选中的视频轨(video track),启用的音频轨(audio tracks)以及显示/隐藏的字幕轨(text tracks)的对象列表。

duration

获取和设置当前正在推流媒体的持续时间。
在获取时,如果readyState属性是closed,则返回 NaN 。
在设置时,如果设置的值是负数或 NaN,则抛出TypeError异常;如果readyState属性不是 open,则抛出InvalidStateError异常;如果updating属性等于 true,则抛出InvalidStateError异常;


事件
onsourceopen

readyState从 closed 变成 open 或从 ended 变成 open 时触发 。
设置 sourceopen 事件对应的事件处理程序。

onsourceended

readyState从 open 变为 ended 时触发。
设置 sourceended 事件对应的事件处理程序。

onsourceclose

readyState从 open 变为 closed 或从 ended 变为 closed 时触发。
设置 sourceclose 事件对应的事件处理程序。

MSE的实例方法
addSourceBuffer

创建一个带有给定 MIME 类型的新的 SourceBuffer 并添加到 MediaSource 的 SourceBuffers 列表。

removeSourceBuffer

删除指定的 SourceBuffer 从这个 MediaSource 对象中的 SourceBuffers 列表。

endOfStream

表示流的结束。

MSE的静态方法
isTypeSupported

返回一个 Boolean 值表明给定的 MIME 类型是否被当前的浏览器支持—— 这意味着是否可以成功的创建这个 MIME 类型的 SourceBuffer 对象。

SourceBuffer
interface SourceBuffer : EventTarget {
                    attribute AppendMode          mode;
    readonly        attribute boolean             updating;
    readonly        attribute TimeRanges          buffered;
                    attribute double              timestampOffset;
    readonly        attribute AudioTrackList      audioTracks;
    readonly        attribute VideoTrackList      videoTracks;
    readonly        attribute TextTrackList       textTracks;
                    attribute double              appendWindowStart;
                    attribute unrestricted double appendWindowEnd;
                    attribute EventHandler        onupdatestart;
                    attribute EventHandler        onupdate;
                    attribute EventHandler        onupdateend;
                    attribute EventHandler        onerror;
                    attribute EventHandler        onabort;
    undefined appendBuffer (BufferSource data);
    undefined abort ();
    undefined changeType (DOMString type);
    undefined remove (double start, unrestricted double end);
};

SourceBuffer 属性
mode

mode 可以被设置为segments或者sequence。在不同的mode值下,SourceBuffer会用不同的方式去处理添加进来的数据。

segments: 媒体片段中的时间戳决定了各个媒体片段的播放顺序。可以按任何顺序附加媒体片段,但播放顺序只会只会依赖时间戳。

sequence:媒体片段添加顺序决定了播放顺序,播放顺序不受媒体片段的时间戳影响。

该属性的初始值在调用 addSourceBuffer() 时设置,如果媒体片段有时间戳设置为 segments,否则 sequence。,可以通过 changeType() 或设置该属性进行更新。

updating

只读属性,用于说明调用的 appendBuffer() 或 remove() 是否仍在处理中。

buffered

只读属性,用于表示 SourceBuffer 缓冲了哪些 TimeRanges。

timestampOffset

用于控制媒体片段的时间戳偏移量,默认是 0。

audioTracks

只读属性,返回当前包含的 AudioTrack 的 AudioTrackList 对象。

videoTracks

只读属性,返回当前包含的 VideoTrack 的 VideoTrackList 对象。

textTracks

只读属性,返回当前包含的 TextTrack 的 TextTrackList 对象。

appendWindowStart

设置或获取 append window 的开始时间戳。

appendWindowEnd

设置或获取 append window 的结束时间戳。

Append Window 使用appendWindowStart 和 appendWindowEnd来表示一个时间范围,在追加编码帧时,编码帧的时间戳在这个时间范围内,那么这个编码帧就能附加到 SourceBuffer中,否则将被过滤掉。
SourceBuffer 方法
appendBuffer(source)

添加媒体数据片段(ArrayBuffer 或 ArrayBufferView)到 SourceBuffer。

abort

中止对当前媒体片段数据的操作,并重置解析器。调用后 updating 属性回重置为 false。

changeType

更改当前关联的 MIME 类型。

remove(start, end)

移除指定范围的媒体数据。

SourceBuffer 事件
updatestart

当updating 从 false 变为 true 时触发。

update

appendBuffer 或 remove 的操作已经成功完成,updating 从 true 变为 false 时触发。

updateend

appendBuffer 或 remove 的操作已经结束,在 update 事件之后触发。

error

执行 appendBuffer 时发生了错误,updating 从 true 变为 false 时触发。

abort

appendBuffer 或 remove 被 abort() 方法中断,updating 从 true 变为 false 时触发。

示例demo

var vidElement = document.querySelector('video');
 
if (window.MediaSource) { // (1)
  var mediaSource = new MediaSource();
  vidElement.src = URL.createObjectURL(mediaSource);
  mediaSource.addEventListener('sourceopen', sourceOpen); 
} else {
  console.log("The Media Source Extensions API is not supported.")
}
 
function sourceOpen(e) {
  URL.revokeObjectURL(vidElement.src);
  var mime = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
  var mediaSource = e.target;
  var sourceBuffer = mediaSource.addSourceBuffer(mime); // (2)
  var videoUrl = 'hello-mse.mp4';
  fetch(videoUrl) // (3)
    .then(function(response) {
      return response.arrayBuffer();
    })
    .then(function(arrayBuffer) {
      sourceBuffer.addEventListener('updateend', function(e) { // (4)
        if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
          mediaSource.endOfStream(); 
        }
      });
      sourceBuffer.appendBuffer(arrayBuffer); // (5)
    });
}

当 MediaSource.readyState 的值是 ended 时,再次去调用 appendBuffer() 和 remove() 或设置 mode 和 timestampOffset 时,都将会让 readyState 变为 open,并触发 sourceopen 事件。若不需要监听后续操作 SourceBuffer 导致的 sourceopen 事件的话,应只监听首次 sourceopen 事件,然后移除对 sourceopen 事件的监听。

以上示例介绍了如何使用 MSE API,接下来我们来分析一下主要的工作流程:

  1. 判断当前平台是否支持 Media Source Extensions API,若支持的话,则创建 MediaSource 对象,且绑定 sourceopen 事件处理函数。

  2. 创建一个带有给定 MIME 类型的新的 SourceBuffer 并添加到 MediaSource 的 SourceBuffers 列表。

  3. 从远程流服务器下载视频流,并转换成 ArrayBuffer 对象。

  4. 为 sourceBuffer 对象添加 updateend 事件处理函数,在视频流传输完成后关闭流。

  5. 往 sourceBuffer 对象中添加已转换的 ArrayBuffer 格式的视频流数据。

想深入了解实际应用的小伙伴,可以进一步了解一下 hls.jsflv.js 项目。

MSE 兼容性

Media Source Extensions

除了IE11以下和opera(safari低版本也不支持,但他能够原生支持hls)之外,兼容性还是不错的。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐