实话实说吧 發表於 2021-2-23 10:14:00

HTML5 直播协议之 WebSocket 和 MSE fmp4

<p>当前为了满足比较火热的移动 Web 端直播需求, 一系列的 HTML5 直播技术迅速的发展了起来.</p>
<p>常见的可用于 HTML5 的直播技术有 HLS, WebSocket 与 WebRTC. 今天我要向大家介绍一下 WebSocket 与 MSE 相关的内容, 并在最后通过一个实际的例子, 来展示其具体的用法.</p>
<h2 id="articleHeader0">大纲</h2>
<ul>
<li>WebSocket 协议介绍.</li>
<li>WebSocket Client/Server API 介绍.</li>
<li>MSE 介绍.</li>
<li>fMP4 介绍.</li>
<li>Demo 展示.</li>
</ul>
<h2 id="articleHeader1">WebSocket</h2>
<p>通常的 Web 应用都是围绕着 HTTP 的请求/响应模型而构建的. 所有的 HTTP 通信都是通过客户端来控制的, 都是由客户端向服务器发出一个请求, 服务器接收和处理完毕后再返回结果给客户端, 客户端再将数据展现出来. 这种模式不能满足实时应用的需求, 于是出现了 SSE, Comet 等 “服务器推” 的长连接技术.</p>
<p>WebSocket 是直接基于 TCP 连接之上的通信协议, 可以在单个 TCP 连接上进行全双工的通信. WebSocket 在 2011 年被 IETF 定为标准 RFC 6455, 并被 RFC 7936 所补充规范, WebSocket API 被 W3C 定为标准.</p>
<p>WebSocket 是独立的创建在 TCP 上的协议, HTTP 协议中的那些概念都不复存在, 和 HTTP 的唯一关联是使用 HTTP 协议的 101 状态码进行协议切换, 使用的 TCP 端口是 80, 可以用于绕过大多数防火墙的限制.</p>
<p><img src="http://static.open-open.com/lib/uploadImg/20161202/20161202160713_908.png" alt=""></p>
<h3 id="articleHeader2">WebSocket 握手</h3>
<p>为了更方便地部署新协议,HTTP/1.1 引入了 Upgrade 机制, 它使得客户端和服务端之间可以借助已有的 HTTP 语法升级到其它协议. 这个机制在 RFC7230 的 6.7 Upgrade ) 一节中有详细描述.</p>
<p>要发起 HTTP/1.1 协议升级,客户端必须在请求头部中指定这两个字段:</p>
<pre>Connection: Upgrade
Upgrade: protocol-name</pre>
<p>如果服务端同意升级, 那么需要这样响应:</p>
<pre>HTTP/1.1 101 Switching Protocols
Connection: upgrade
Upgrade: protocol-name

[... data defined by new protocol ...]</pre>
<p>可以看到, HTTP Upgrade 响应的状态码是 101 , 并且响应正文可以使用新协议定义的数据格式.</p>
<p>WebSocket 握手就利用了这种 HTTP Upgrade 机制. 一旦握手完成,后续数据传输就直接在 TCP 上完成.</p>
<h3 id="articleHeader3">WebSocket JavaScript API</h3>
<p>目前主流的浏览器提供了 WebSocket 的 API 接口, 可以发送消息(文本或者二进制)给服务器, 并且接收事件驱动的响应数据.</p>
<p>Step1 检查浏览器是否支持 WebSocket.</p>
<pre>if(window.WebSocket) {
        // WebSocket代码
}</pre>
<p>Step2 建立连接</p>
<pre>var ws = new WebSocket('ws://localhost:8327');</pre>
<p>Step3 注册回调函数以及收发数据</p>
<p>分别注册 WebSocket 对象的 onopen, onclose, onerror 以及 onmessage 回调函数.</p>
<p>通过 ws.send() 来进行发送数据, 这里不仅可以发送字符串, 也可以发送 Blob 或 ArrayBuffer 类型的数据.</p>
<p>如果接收的是二进制数据,需要将连接对象的格式设为 blob 或 arraybuffer.</p>
<pre>ws.binaryType = 'arraybuffer';</pre>
<h3 id="articleHeader4">WebSocket Golang API</h3>
<p>服务器端 WebSocket 库我推荐使用 Google 自己的 golang.org/x/net/websocket , 可以非常方便的与 net/http 一起使用.</p>
<p>可以将 websocket 的 handler function 通过 websocket.Handler 转换成 http.Handler , 这样就可以跟 net/http 库一起使用了.</p>
<p>然后通过 websocket.Message.Receive 来接收数据, 通过 websocket.Message.Send 来发送数据.</p>
<p>具体代码可以看下面的 Demo 部分.</p>
<h2 id="articleHeader5">MSE</h2>
<p>在介绍 MSE 之前, 我们先看看 HTML5 &lt;audio&gt; 和 &lt;video&gt; 有哪些限制.</p>
<p>HTML5</p>
<p>和</p>
<p>标签的限制</p>
<ul>
<li>不支持流.</li>
<li>不支持 DRM 和加密.</li>
<li>很难自定义控制, 以及保持跨浏览器的一致性.</li>
<li>编解码和封装在不同浏览器支持不同.</li>
</ul>
<p>MSE 是解决 HTML5 的流问题.</p>
<p>Media Source Extensions (MSE) 是一个主流浏览器支持的新的 Web API. MSE 是一个 W3C 标准, 允许 JavaScript 动态的构建 &lt;video&gt; 和 &lt;audio&gt; 的媒体流. 他定义了对象, 允许 JavaScript 传输媒体流片段到一个 HTMLMediaElement.</p>
<p>通过使用 MSE, 你可以动态地修改媒体流而不需要任何的插件. 这让前端 JavaScript 可以做更多的事情, 我们可以在 JavaScript 进行转封装, 处理, 甚至转码.</p>
<p>虽然 MSE 不能让流直接传输到 media tags 上, 但是 MSE 提供了构建跨浏览器播放器的核心技术, 让浏览器通过 JavaScript API 来推音视频到 media tags 上.</p>
<p>现在每个客户端平台都开始逐步开放流媒体相关的 API: Flash 平台有 Netstream, Android 平台有 Media Codec API, 而 Web 上对应的就是标准的 MSE. 由此可以看出, 未来的趋势是在客户端可以做越来越多的事情.</p>
<h3 id="articleHeader6">Browser Support</h3>
<p>通过&nbsp;caniuse&nbsp;来检查是否浏览器支持情况.</p>
<p><img src="http://static.open-open.com/lib/uploadImg/20161202/20161202160715_329.png" alt=""></p>
<p>通过 MediaSource.isTypeSupported() 可以进一步地检查 codec MIME 类型是否支持.</p>
<p>比较常用的视频封装格式有 webm 和 fMP4.</p>
<p>WebM 和 WebP 是两个姊妹项目, 都是由 Google 赞助的. 由于 WebM 是基于 Matroska 的容器格式, 所以天生是流式的, 很适合用在流媒体领域里.</p>
<p>下面着重介绍一些 fMP4 格式.</p>
<p>我们都知道 MP4 是由一系列的 Boxes 组成的. 普通的 MP4 的是嵌套结构的, 客户端必须要从头加载一个 MP4 文件, 才能够完整播放, 不能从中间一段开始播放.</p>
<p>而 fMP4 由一系列的片段组成, 如果你的服务器支持 byte-range 请求, 那么, 这些片段可以独立的进行请求到客户端进行播放, 而不需要加载整个文件.</p>
<p>为了更加形象的说明这一点, 下面我介绍几个常用的分析 MP4 文件的工具.</p>
<ul>
<li>gpac 原名 mp4box, 是一个媒体开发框架, 在其源码下有大量的媒体分析工具可以使用, testapps</li>
<li>mp4box.js 是 mp4box 的 Javascript 版本.</li>
<li>bento4 一个专门用于 MP4 的分析工具.</li>
<li>mp4parser 在线 MP4 文件分析工具.</li>
</ul>
<h3 id="articleHeader7">fragment mp4 vs non-fragment mp4</h3>
<p>下面一个 fragment mp4 文件通过 mp4parser 分析后的截图</p>
<p><img src="http://static.open-open.com/lib/uploadImg/20161202/20161202160715_847.png" alt=""></p>
<p>下面一个 non-fragment mp4 文件通过 mp4parser 分析后的截图</p>
<p><img src="http://static.open-open.com/lib/uploadImg/20161202/20161202160715_15.png" alt=""></p>
<p>Apple 在今年的 WWDC 大会上宣布会在 iOS 10, tvOS, macOS 的 HLS 中支持 fMP4.</p>
<p>值得一提的是, fMP4, CMAF, ISOBMFF 其实都是类似的东西.</p>
<h3 id="articleHeader8">MSE JavaScript API</h3>
<p>从高层次上看, MSE 提供了 * 一套 JavaScript API 来构建 media streams. * 一个拼接和缓存模型. * 识别一些 byte 流类型: * WebM * ISO Base Media File Format * MPEG-2 Transport Streams</p>
<h3 id="articleHeader9">MSE 内部结构</h3>
<p><img src="http://static.open-open.com/lib/uploadImg/20161202/20161202160715_128.png" alt=""></p>
<p>MSE 本身的设计是不依赖任务特定的编解码和容器格式的, 但是不同的浏览器支持程度是不一样的. 可以通过传递一个 MIME 类型的字符串到静态方法: MediaSource.isTypeSupported 来检查.</p>
<p>比如:</p>
<pre>MediaSource.isTypeSupported('audio/mp3'); // false
MediaSource.isTypeSupported('video/mp4'); // true
MediaSource.isTypeSupported('video/mp4; codecs="avc1.4D4028, mp4a.40.2"'); // true</pre>
<p>获取 Codec MIME string 的方法可以通过在线的 mp4info 或者使用命令行 mp4info test.mp4 | grep Codecs</p>
<p>可以得到类似如下结果:</p>
<pre>❯ mp4info fmp4.mp4| grep Codec
    Codecs String: mp4a.40.2
    Codecs String: avc1.42E01E</pre>
<p>当前, H.264 + AAC 的 MP4 容器在所有的浏览器都支持.</p>
<p>普通的 MP4 文件是不能和 MSE 一起使用的, 需要将 MP4 进行 fragment 化.</p>
<p>检查一个 MP4 是否已经 fragment 的方法</p>
<pre>mp4dump test.mp4 | grep "\[m"</pre>
<p>如果是 non-fragment 会显示类似信息.</p>
<pre>❯ mp4dump nfmp4.mp4 | grep "\[m"
size=8+50873
size=8+7804
size=12+96
    size=8+3335
       size=12+20
       size=8+3250
    size=8+3975
       size=12+20
       size=8+3890
             size=8+82
    size=12+78</pre>
<p>如果已经 fragment, 会显示如下类似信息.</p>
<pre>❯ mp4dump fmp4.mp4 | grep "\[m" | head -n 30
size=8+1871
size=12+96
    size=8+312
       size=12+20
       size=8+219
             size=8+67
    size=8+371
       size=12+20
       size=8+278
    size=8+248
       size=12+20
       size=8+156
    size=8+248
       size=12+20
       size=8+156
size=8+144
    size=12+4
size=8+600
size=12+4
size=8+138679
size=8+536
size=12+4
size=8+24490
size=8+592
size=12+4
size=8+14444
size=8+312
size=12+4
size=8+1840
size=8+600</pre>
<p>把一个 non-fragment MP4 转换成 fragment MP4.</p>
<p>可以使用 FFmpeg 的 -movflags 来转换</p>
<p>对于原始文件为非 MP4 文件</p>
<pre>ffmpeg -i trailer_1080p.mov -c:v copy -c:a copy -movflags frag_keyframe+empty_moov bunny_fragmented.mp4</pre>
<p>对于原始文件已经是 MP4 文件</p>
<pre>ffmpeg -i non_fragmented.mp4 -movflags frag_keyframe+empty_moov fragmented.mp4</pre>
<p>或者使用 mp4fragment</p>
<pre>mp4fragment input.mp4 output.mp4</pre>
<h2 id="articleHeader10">demo</h2>
<ul>
<li>MSE Vod Demo 展示利用 MSE 和 WebSocket 实现一个点播服务.</li>
<li>MSE Live Demo 展示利用 MSE 和 WebSocket 实现一个直播服务.</li>
</ul>
<h2 id="articleHeader11">MSE VOD Demo</h2>
<p>Your browser does not support the video tag.</p>
<h2 id="articleHeader12">MSE Live Demo</h2>
<p>Your browser does not support the video tag.</p>
<h2 id="articleHeader13">Refs</h2>
<h3 id="articleHeader14">WebSocket</h3>
<p>&nbsp;</p>
<p>来自:http://akagi201.org/post/websocket-mse/</p><br><br>
来源:https://www.cnblogs.com/lidabo/p/14434430.html
頁: [1]
查看完整版本: HTML5 直播协议之 WebSocket 和 MSE fmp4