前端流媒體:MSE入門

前言

流媒體協議多種多樣,音視頻編碼格式更是繁多,要想在瀏覽器中正常瀏覽並不是不容易。除開 WebRTC 這種瀏覽器已經支持的協議,HLS、FLV、RTSP、RTMP、DASH 等協議都須要預處理,不過流程大體都是:javascript

  • 經過 HTTP、WebSocket 等方式獲取數據;
  • 處理數據,解協議、組幀等獲得媒體信息及數據;
  • 封裝成媒體片斷,或解碼成一幀畫面;
  • 經過 video 或 canvas(WebGL)等進行播放。

目前市面上也有一些前端解碼的方案,如藉助 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

MIME TYPE

// 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

      • 0x42AVCProfileIndication)表示視頻的Profile,常見的有 Baseline/Extended/Main/High profile等;
      • 0xE0profile_compatibility)表示編碼級別的約束條件;
      • 0x1EAVCLevlIndication)表示H264的level,表示最大支持的分辨率、幀率、碼率等;
    • 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');
}

Media Segment

SourceBuffer.appendBuffer(source) 旨在將媒體片斷數據 source 添加到 SourceBuffer 對象中,看 MDN 上對 source 的描述:

一個 BufferSource (en-US) 對象(ArrayBufferView 或 ArrayBuffer),存儲了你要添加到 SourceBuffer 中去的媒體片斷數據。

因此 source 就是一串二進制數據,固然也不是隨便一串就行,那該 媒體片斷 須要知足怎樣的條件呢?

  1. 知足 MSE Byte Stream Format Registry 規定的 MIME 類型
  2. 屬於 Initialization SegmentMedia Segment 中的一種

對於第一個條件,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.mp4big-buck-bunny.webm 兩個文件進行測試:

👉 ffmpeg -i lol.mp4 -c copy -f dash index.mpd
👇
image.png

👉 ffmpeg -i big-buck-bunny.webm -c copy -f dash index.mpd
👇
image.png

從測試結果能夠看出,都是生成了 init-xxx.xxchunk-xxx-xxx.xx 的文件,
很明顯 init-xxx.xx 表示初始化片斷,chunk-xxx-xxx.xx 表示媒體片斷,而其中的 stream0stream1 分別表明了視頻和音頻通道。

藉助在線的 mp4 box 解析工具,看下 fmp4 的初始化片斷和媒體片斷的內部構造:

image.png

image.png

ISO BMFF 描述一致,初始化分片由 ftyp box + moov box 組成;媒體分片 styp boxsidx boxmoof boxmdat box 組成,想要了解各類盒子的含義能夠前往 學好 MP4,讓直播更給力 學習。

EXAMPLE

👇

🖥 在線Demo 🌰

🔗 github 🌟

相關文章
相關標籤/搜索