流媒体协议多种多样,音视频编码格式更是繁多,要想在浏览器中正常浏览并不是不容易。除开 WebRTC 这种浏览器已经支持的协议,HLS、FLV、RTSP、RTMP、DASH 等协议都须要预处理,不过流程大体都是:javascript
目前市面上也有一些前端解码的方案,如借助 WASM
的高性能调用 c 解码库,或者直接使用浏览器的 WebCodecs API
进行编解码......但都存在局限性,WebCodecs
还是实验性功能;而 WASM
方案虽然突破浏览器沙盒限制(能播放浏览器不支持的编码格式如H265等),但解码和浏览器原始解码之间仍有差距,而且因为只能走软解致使多路性能也吃不消。因此,市面上更多的是采用另外一种方式,解协议+封装+这篇文章的主角 Media Source Extensions(如下简称MSE)。html
HTML5 规范容许咱们直接在网页中嵌入视频,前端
<video src="demo.mp4"></video>
但 src 指定的资源地址必须是一个完整的媒体文件,如何在 Web 作到流式的媒体资源播放?MSE
提供了这样的可能性,先看下 MDN 对它对描述:java
媒体源扩展 API(MSE) 提供了实现无插件且基于 Web 的流媒体的功能。使用 MSE,媒体串流可以经过 建立,而且能经过使用<audio>
和<video>
元素进行播放。
正如上面所说,MSE
让咱们能够经过 JS 建立媒体资源,使用起来也十分方便:git
const mediaSource = new MediaSource(); const video = document.querySelector('video'); video.src = URL.createObjectURL(mediaSource);
媒体资源对象建立完毕,接下来就是喂给它视频数据(片断),代码看上去就像是:github
mediaSource.addEventListener('sourceopen', () => { const mime = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'; const sourceBuffer = mediaSource.addSourceBuffer(mime); const data = new ArrayBuffer([...]); // 视频数据 sourceBuffer.appendBuffer(data); });
此时,视频就能够正常播放了。要想作到流式播放,只须要不停的调用 appendBuffer
喂音视频数据就好了......但不由有疑问, 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'
这段字符串什么意思?音视频数据又要从哪来的?🤔web
// webm MIME-type 'video/webm;codecs="vp8,vorbis"' // fmp4 MIME-type 'video/mp4;codecs="avc1.42E01E,mp4a.40.2"'
这段字符串描述了视频的相关参数,如封装格式、音/视频编码格式以及其余重要信息。以上面 mp4 这段为例,以 ;
分为两部分:typescript
video/mp4
表示这是 mp4 格式的视频;后半部分的 codecs
描述了视频的编码信息,它是由一个或多个由 ,
分隔的值组成,其中每一个值又由一个或多个由 .
分割的元素组成:npm
avc1
表示视频是 AVC
(即H264)编码;42E01E
由(16进制表示的)三个字节构成,描述了视频的相关信息:canvas
mp4a
表示某种 MPEG-4
音频;40
是由MP4注册机构指定的ObjectTypeIndication(OTI),0x40
对应 Audio ISO/IEC 14496-3 (d)
标准;2
表示某种音频OTI,mp4a.40.2
表示 AAC LC
。但音视频格式多种多样,前端有什么方法直接取到视频的 MIME TYPE
呢?
对于 mp4 格式的可使用:🌟🌟🌟 mp4box 🌟🌟🌟,获取方式以下:
// utils.ts // 添加库 // yarn add mp4box import MP4Box from 'mp4box'; export function getMimeType (buffer: ArrayBuffer) { return new Promise<string>((resolve, reject) => { const mp4boxfile = MP4Box.createFile(); mp4boxfile.onReady = (info: any) => resolve(info.mime); mp4boxfile.onError = () => reject(); (buffer as any).fileStart = 0; mp4boxfile.appendBuffer(buffer); }); }
MIME TYPE
获取到后,能够经过 MSE 的静态方法 MediaSource.isTypeSupported() 检测当前浏览器是否支持该媒体格式。
import { getMimeType } from './utils'; ... const mime = await getMimeType(buffer); if (!MediaSource.isTypeSupported(mime)) { throw new Error('mimetype not supported'); }
SourceBuffer.appendBuffer(source)
旨在将媒体片断数据 source
添加到 SourceBuffer 对象中,看 MDN 上对 source
的描述:
一个 BufferSource (en-US) 对象(ArrayBufferView 或 ArrayBuffer),存储了你要添加到 SourceBuffer 中去的媒体片断数据。
因此 source
就是一串二进制数据,固然也不是随便一串就行,那该 媒体片断 须要知足怎样的条件呢?
对于第一个条件,MSE 支持的媒体格式和音视频格式较少,常见的通常为 fmp4(h264+aac)
和 webm(vp8/vorbis)
等。什么是fmp4?什么是webm?能够点开了解下,本篇文章不展开讨论。
对于第二个条件,Initialization Segment
意为初始化片断,包含了 Media Segment
解码所需的初始化信息,如媒体的分辨率、时长、比特率等信息; Media Segment
则是带有时间戳的音视频片断,并与最近添加的 Initialization Segment
相关联。通常都是 append 一个初始化片断后 append 多个媒体片断。
对于 fmp4
来讲,初始化片断和媒体片断实际上都是 MP4 box
,只是类型不同(了解更多);而对于 webm
来讲,初始化片断是 EBML Header
以及 Cluster
元素前的内容(包括一些媒体、track等信息),媒体片断则是一个 Cluster
元素(了解更多)。
理论都了解了,实操呢?如何从已有的媒体文件生成上述的 媒体片断 呢?
这里咱们用到的是🌟🌟🌟 FFmpeg 🌟🌟🌟,用起来很方便,只须要一行命令:
ffmpeg -i xxx -c copy -f dash index.mpd
xxx
是你本地的媒体文件,我这边分别用 lol.mp4
和 big-buck-bunny.webm
两个文件进行测试:
👉 ffmpeg -i lol.mp4 -c copy -f dash index.mpd
👇
👉 ffmpeg -i big-buck-bunny.webm -c copy -f dash index.mpd
👇
从测试结果能够看出,都是生成了 init-xxx.xx
、 chunk-xxx-xxx.xx
的文件,
很明显 init-xxx.xx
表示初始化片断,chunk-xxx-xxx.xx
表示媒体片断,而其中的 stream0
和 stream1
分别表明了视频和音频通道。
借助在线的 mp4 box 解析工具,看下 fmp4
的初始化片断和媒体片断的内部构造:
跟 ISO BMFF 描述一致,初始化分片由 ftyp box
+ moov box
组成;媒体分片 styp box
、sidx box
、moof box
、mdat box
组成,想要了解各类盒子的含义能够前往 学好 MP4,让直播更给力 学习。
👇