流媒體協議多種多樣,音視頻編碼格式更是繁多,要想在瀏覽器中正常瀏覽並不是不容易。除開 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,讓直播更給力 學習。
👇