「1.4萬字」玩轉前端 Video 播放器 | 多圖預警

Web 開發者們一直以來想在 Web 中使用音頻和視頻,但早些時候,傳統的 Web 技術不可以在 Web 中嵌入音頻和視頻,因此一些像 Flash、Silverlight 的專利技術在處理這些內容上變得很受歡迎。這些技術可以正常的工做,可是卻有着一系列的問題,包括沒法很好的支持 HTML/CSS 特性、安全問題,以及可行性問題。javascript

幸運的是,當 HTML5 標準公佈後,其中包含許多的新特性,包括 <video><audio> 標籤,以及一些 JavaScript APIs 用於對其進行控制。隨着通訊技術和網絡技術的不斷髮展,目前音視頻已經成爲你們生活中不可或缺的一部分。此外,伴隨着 5G 技術的慢慢普及,實時音視頻領域還會有更大的想象空間。html

接下來本文將從八個方面入手,全方位帶你一塊兒探索前端 Video 播放器和主流的流媒體技術。閱讀完本文後,你將瞭解如下內容:前端

  • 爲何一些網頁中的 Video 元素,其視頻源地址是採用 Blob URL 的形式;
  • 什麼是 HTTP Range 請求及流媒體技術相關概念;
  • 瞭解 HLS、DASH 的概念、自適應比特率流技術及流媒體加密技術;
  • 瞭解 FLV 文件結構、flv.js 的功能特性與使用限制及內部的工做原理;
  • 瞭解 MSE(Media Source Extensions)API 及相關的使用;
  • 瞭解視頻播放器的原理、多媒體封裝格式及 MP4 與 Fragmented MP4 封裝格式的區別;

在最後的 阿寶哥有話說 環節,阿寶哥將介紹如何實現播放器截圖、如何基於截圖生成 GIF、如何使用 Canvas 播放視頻及如何實現色度鍵控等功能。java

1、傳統的播放模式

大多數 Web 開發者對 <video> 都不會陌生,在如下 HTML 片斷中,咱們聲明瞭一個 <video> 元素並設置相關的屬性,而後經過 <source> 標籤設置視頻源和視頻格式:git

<video id="mse" autoplay=true playsinline controls="controls">
   <source src="https://h5player.bytedance.com/video/mp4/xgplayer-demo-720p.mp4" type="video/mp4">
   你的瀏覽器不支持Video標籤
</video>

上述代碼在瀏覽器渲染以後,在頁面中會顯示一個 Video 視頻播放器,具體以下圖所示:github

(圖片來源:https://h5player.bytedance.co...算法

經過 Chrome 開發者工具,咱們能夠知道當播放 xgplayer-demo-720p.mp4 視頻文件時,發了 3 個 HTTP 請求:typescript

此外,從圖中能夠清楚地看到,頭兩個 HTTP 請求響應的狀態碼是 206。這裏咱們來分析第一個 HTTP 請求的請求頭和響應頭:shell

在上面的請求頭中,有一個 range: bytes=0- 首部信息,該信息用於檢測服務端是否支持 Range 請求。若是在響應中存在 Accept-Ranges 首部(而且它的值不爲 「none」),那麼表示該服務器支持範圍請求。數據庫

在上面的響應頭中, Accept-Ranges: bytes 表示界定範圍的單位是 bytes 。這裏 Content-Length 也是有效信息,由於它提供了要下載的視頻的完整大小。

1.1 從服務器端請求特定的範圍

假如服務器支持範圍請求的話,你可使用 Range 首部來生成該類請求。該首部指示服務器應該返回文件的哪一或哪幾部分。

1.1.1 單一範圍

咱們能夠請求資源的某一部分。這裏咱們使用 Visual Studio Code 中的 REST Client 擴展來進行測試,在這個例子中,咱們使用 Range 首部來請求 www.example.com 首頁的前 1024 個字節。

對於使用 REST Client 發起的 單一範圍請求,服務器端會返回狀態碼爲 206 Partial Content 的響應。而響應頭中的 Content-Length 首部如今用來表示先前請求範圍的大小(而不是整個文件的大小)。Content-Range 響應首部則表示這一部份內容在整個資源中所處的位置。

1.1.2 多重範圍

Range 頭部也支持一次請求文檔的多個部分。請求範圍用一個逗號分隔開。好比:

$ curl http://www.example.com -i -H "Range: bytes=0-50, 100-150"

對於該請求會返回如下響應信息:

由於咱們是請求文檔的多個部分,因此每一個部分都會擁有獨立的 Content-TypeContent-Range 信息,而且使用 boundary 參數對響應體進行劃分。

1.1.3 條件式範圍請求

當從新開始請求更多資源片斷的時候,必須確保自從上一個片斷被接收以後該資源沒有進行過修改。

If-Range 請求首部能夠用來生成條件式範圍請求:假如條件知足的話,條件請求就會生效,服務器會返回狀態碼爲 206 Partial 的響應,以及相應的消息主體。假如條件未能獲得知足,那麼就會返回狀態碼爲 200 OK 的響應,同時返回整個資源。該首部能夠與 Last-Modified 驗證器或者 ETag 一塊兒使用,可是兩者不能同時使用。

1.1.4 範圍請求的響應

與範圍請求相關的有三種狀態:

  • 在請求成功的狀況下,服務器會返回 206 Partial Content 狀態碼。
  • 在請求的範圍越界的狀況下(範圍值超過了資源的大小),服務器會返回 416 Requested Range Not Satisfiable (請求的範圍沒法知足) 狀態碼。
  • 在不支持範圍請求的狀況下,服務器會返回 200 OK 狀態碼。

剩餘的兩個請求,阿寶哥就再也不詳細分析了。感興趣的小夥伴,可使用 Chrome 開發者工具查看一下具體的請求報文。經過第 3 個請求,咱們能夠知道整個視頻的大小大約爲 7.9 MB。若播放的視頻文件太大或出現網絡不穩定,則會致使播放時,須要等待較長的時間,這嚴重下降了用戶體驗。

那麼如何解決這個問題呢?要解決該問題咱們可使用流媒體技術,接下來咱們來介紹流媒體。

2、流媒體

流媒體是指將一連串的媒體數據壓縮後,通過網上分段發送數據,在網上即時傳輸影音以供觀賞的一種技術與過程,此技術使得數據包得以像流水同樣發送;若是不使用此技術,就必須在使用前下載整個媒體文件。

流媒體實際指的是一種新的媒體傳送方式,有聲音流、視頻流、文本流、圖像流、動畫流等,而非一種新的媒體。流媒體最主要的技術特徵就是流式傳輸,它使得數據能夠像流水同樣傳輸。流式傳輸是指經過網絡傳送媒體技術的總稱。實現流式傳輸主要有兩種方式:順序流式傳輸(Progressive Streaming)和實時流式傳輸(Real Time Streaming)。

目前網絡上常見的流媒體協議:

經過上表可知,不一樣的協議有着不一樣的優缺點。在實際使用過程當中,咱們一般會在平臺兼容的條件下選用最優的流媒體傳輸協議。好比,在瀏覽器裏作直播,選用 HTTP-FLV 協議是不錯的,性能優於 RTMP+Flash,延遲能夠作到和 RTMP+Flash 同樣甚至更好。

而因爲 HLS 延遲較大,通常只適合視頻點播的場景,但因爲它在移動端擁有較好的兼容性,因此在接受高延遲的條件下,也是能夠應用在直播場景。

講到這裏相信有些小夥伴會好奇,對於 Video 元素來講使用流媒體技術以後與傳統的播放模式有什麼直觀的區別。下面阿寶哥以常見的 HLS 流媒體協議爲例,來簡單對比一下它們之間的區別。

經過觀察上圖,咱們能夠很明顯地看到,當使用 HLS 流媒體網絡傳輸協議時,<video> 元素 src 屬性使用的是 blob:// 協議。講到該協議,咱們就不得不聊一下 Blob 與 Blob URL。

2.1 Blob

Blob(Binary Large Object)表示二進制類型的大對象。在數據庫管理系統中,將二進制數據存儲爲一個單一個體的集合。Blob 一般是影像、聲音或多媒體文件。在 JavaScript 中 Blob 類型的對象表示不可變的相似文件對象的原始數據。

Blob 由一個可選的字符串 type(一般是 MIME 類型)和 blobParts 組成:

MIME(Multipurpose Internet Mail Extensions)多用途互聯網郵件擴展類型,是設定某種擴展名的文件用一種應用程序來打開的方式類型,當該擴展名文件被訪問的時候,瀏覽器會自動使用指定應用程序來打開。多用於指定一些客戶端自定義的文件名,以及一些媒體文件打開方式。

常見的 MIME 類型有:超文本標記語言文本 .html text/html、PNG圖像 .png image/png、普通文本 .txt text/plain 等。

爲了更直觀的感覺 Blob 對象,咱們先來使用 Blob 構造函數,建立一個 myBlob 對象,具體以下圖所示:

如你所見,myBlob 對象含有兩個屬性:size 和 type。其中 size 屬性用於表示數據的大小(以字節爲單位),type 是 MIME 類型的字符串。Blob 表示的不必定是 JavaScript 原生格式的數據。好比 File 接口基於 Blob,繼承了 blob 的功能並將其擴展使其支持用戶系統上的文件。

2.2 Blob URL/Object URL

Blob URL/Object URL 是一種僞協議,容許 Blob 和 File 對象用做圖像,下載二進制數據連接等的 URL 源。在瀏覽器中,咱們使用 URL.createObjectURL 方法來建立 Blob URL,該方法接收一個 Blob 對象,併爲其建立一個惟一的 URL,其形式爲 blob:<origin>/<uuid>,對應的示例以下:

blob:https://example.org/40a5fb5a-d56d-4a33-b4e2-0acf6a8e5f641

瀏覽器內部爲每一個經過 URL.createObjectURL 生成的 URL 存儲了一個 URL → Blob 映射。所以,此類 URL 較短,但能夠訪問 Blob。生成的 URL 僅在當前文檔打開的狀態下才有效。但若是你訪問的 Blob URL 再也不存在,則會從瀏覽器中收到 404 錯誤。

上述的 Blob URL 看似很不錯,但實際上它也有反作用。雖然存儲了 URL → Blob 的映射,但 Blob 自己仍駐留在內存中,瀏覽器沒法釋放它。映射在文檔卸載時自動清除,所以 Blob 對象隨後被釋放。可是,若是應用程序壽命很長,那不會很快發生。所以,若是咱們建立一個 Blob URL,即便再也不須要該 Blob,它也會存在內存中。

針對這個問題,咱們能夠調用 URL.revokeObjectURL(url) 方法,從內部映射中刪除引用,從而容許刪除 Blob(若是沒有其餘引用),並釋放內存。

2.3 Blob vs ArrayBuffer

其實在前端除了 Blob 對象 以外,你還可能會遇到 ArrayBuffer 對象。它用於表示通用的,固定長度的原始二進制數據緩衝區。你不能直接操縱 ArrayBuffer 的內容,而是須要建立一個 TypedArray 對象或 DataView 對象,該對象以特定格式表示緩衝區,並使用該對象讀取和寫入緩衝區的內容。

Blob 對象與 ArrayBuffer 對象擁有各自的特色,它們之間的區別以下:

  • 除非你須要使用 ArrayBuffer 提供的寫入/編輯的能力,不然 Blob 格式多是最好的。
  • Blob 對象是不可變的,而 ArrayBuffer 是能夠經過 TypedArrays 或 DataView 來操做。
  • ArrayBuffer 是存在內存中的,能夠直接操做。而 Blob 能夠位於磁盤、高速緩存內存和其餘不可用的位置。
  • 雖然 Blob 能夠直接做爲參數傳遞給其餘函數,好比 window.URL.createObjectURL()。可是,你可能仍須要 FileReader 之類的 File API 才能與 Blob 一塊兒使用。
  • Blob 與 ArrayBuffer 對象之間是能夠相互轉化的:

    • 使用 FileReader 的 readAsArrayBuffer() 方法,能夠把 Blob 對象轉換爲 ArrayBuffer 對象;
    • 使用 Blob 構造函數,如 new Blob([new Uint8Array(data]);,能夠把 ArrayBuffer 對象轉換爲 Blob 對象。

在前端 AJAX 場景下,除了常見的 JSON 格式以外,咱們也可能會用到 Blob 或 ArrayBuffer 對象:

function GET(url, callback) {
  let xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);
  xhr.responseType = 'arraybuffer'; // or xhr.responseType = "blob";
  xhr.send();

  xhr.onload = function(e) {
    if (xhr.status != 200) {
      alert("Unexpected status code " + xhr.status + " for " + url);
      return false;
    }
    callback(new Uint8Array(xhr.response)); // or new Blob([xhr.response]);
  };
}

在以上示例中,經過爲 xhr.responseType 設置不一樣的數據類型,咱們就能夠根據實際須要獲取對應類型的數據了。介紹完上述內容,下面咱們先來介紹目前應用比較普遍的 HLS 流媒體傳輸協議。

3、HLS

3.1 HLS 簡介

HTTP Live Streaming(縮寫是 HLS)是由蘋果公司提出基於 HTTP 的流媒體網絡傳輸協議,它是蘋果公司 QuickTime X 和 iPhone 軟件系統的一部分。它的工做原理是把整個流分紅一個個小的基於 HTTP 的文件來下載,每次只下載一些。當媒體流正在播放時,客戶端能夠選擇從許多不一樣的備用源中以不一樣的速率下載一樣的資源,容許流媒體會話適應不一樣的數據速率。

此外,當用戶的信號強度發生抖動時,視頻流會動態調整以提供出色的再現效果。

(圖片來源:https://www.wowza.com/blog/hl...

最初, 僅 iOS 支持 HLS。但如今 HLS 已成爲專有格式,幾乎全部設備都支持它。顧名思義,HLS(HTTP Live Streaming)協議經過標準的 HTTP Web 服務器傳送視頻內容。這意味着你無需集成任何特殊的基礎架構便可分發 HLS 內容。

HLS 擁有如下特性:

  • HLS 將播放使用 H.264 或 HEVC / H.265 編解碼器編碼的視頻。
  • HLS 將播放使用 AAC 或 MP3 編解碼器編碼的音頻。
  • HLS 視頻流通常被切成 10 秒的片斷。
  • HLS 的傳輸/封裝格式是 MPEG-2 TS。
  • HLS 支持 DRM(數字版權管理)。
  • HLS 支持各類廣告標準,例如 VAST 和 VPAID。

爲何蘋果要提出 HLS 這個協議,其實它的主要是爲了解決 RTMP 協議存在的一些問題。好比 RTMP 協議不使用標準的 HTTP 接口傳輸數據,因此在一些特殊的網絡環境下可能被防火牆屏蔽掉。可是 HLS 因爲使用的 HTTP 協議傳輸數據,一般狀況下不會遇到被防火牆屏蔽的狀況。除此以外,它也很容易經過 CDN(內容分發網絡)來傳輸媒體流。

3.2 HLS 自適應比特流

HLS 是一種自適應比特率流協議。所以,HLS 流能夠動態地使視頻分辨率自適應每一個人的網絡情況。若是你正在使用高速 WiFi,則能夠在手機上流式傳輸高清視頻。可是,若是你在有限數據鏈接的公共汽車或地鐵上,則能夠以較低的分辨率觀看相同的視頻。

在開始一個流媒體會話時,客戶端會下載一個包含元數據的 Extended M3U(m3u8)Playlist 文件,用於尋找可用的媒體流。

(圖片來源:https://www.wowza.com/blog/hl...

爲了便於你們的理解,咱們使用 hls.js 這個 JavaScript 實現的 HLS 客戶端,所提供的 在線示例,來看一下具體的 m3u8 文件。

x36xhzz.m3u8

#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2149280,CODECS="mp4a.40.2,avc1.64001f",RESOLUTION=1280x720,NAME="720"
url_0/193039199_mp4_h264_aac_hd_7.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=246440,CODECS="mp4a.40.5,avc1.42000d",RESOLUTION=320x184,NAME="240"
url_2/193039199_mp4_h264_aac_ld_7.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=460560,CODECS="mp4a.40.5,avc1.420016",RESOLUTION=512x288,NAME="380"
url_4/193039199_mp4_h264_aac_7.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=836280,CODECS="mp4a.40.2,avc1.64001f",RESOLUTION=848x480,NAME="480"
url_6/193039199_mp4_h264_aac_hq_7.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=6221600,CODECS="mp4a.40.2,avc1.640028",RESOLUTION=1920x1080,NAME="1080"
url_8/193039199_mp4_h264_aac_fhd_7.m3u8

經過觀察 Master Playlist 對應的 m3u8 文件,咱們能夠知道該視頻支持如下 5 種不一樣清晰度的視頻:

  • 1920x1080(1080P)
  • 1280x720(720P)
  • 848x480(480P)
  • 512x288
  • 320x184

而不一樣清晰度視頻對應的媒體播放列表,會定義在各自的 m3u8 文件中。這裏咱們以 720P 的視頻爲例,來查看其對應的 m3u8 文件:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:11
#EXTINF:10.000,
url_462/193039199_mp4_h264_aac_hd_7.ts
#EXTINF:10.000,
url_463/193039199_mp4_h264_aac_hd_7.ts
#EXTINF:10.000,
url_464/193039199_mp4_h264_aac_hd_7.ts
#EXTINF:10.000,
...
url_525/193039199_mp4_h264_aac_hd_7.ts
#EXT-X-ENDLIST

當用戶選定某種清晰度的視頻以後,將會下載該清晰度對應的媒體播放列表(m3u8 文件),該列表中就會列出每一個片斷的信息。HLS 的傳輸/封裝格式是 MPEG-2 TS(MPEG-2 Transport Stream),是一種傳輸和存儲包含視頻、音頻與通訊協議各類數據的標準格式,用於數字電視廣播系統,如 DVB、ATSC、IPTV 等等。

須要注意的是利用一些現成的工具,咱們是能夠把多個 TS 文件合併爲 mp4 格式的視頻文件。 若是要作視頻版權保護,那咱們能夠考慮使用對稱加密算法,好比 AES-128 對切片進行對稱加密。當客戶端進行播放時,先根據 m3u8 文件中配置的密鑰服務器地址,獲取對稱加密的密鑰,而後再下載分片,當分片下載完成後再使用匹配的對稱加密算法進行解密播放。

對上述過程感興趣的小夥伴能夠參考 Github 上 video-hls-encrypt 這個項目,該項目深刻淺出介紹了基於 HLS 流媒體協議視頻加密的解決方案並提供了完整的示例代碼。

(圖片來源:https://github.com/hauk0101/v...

介紹完蘋果公司推出的 HLS (HTTP Live Streaming)技術,接下來咱們來介紹另外一種基於 HTTP 的動態自適應流 —— DASH。

4、DASH

4.1 DASH 簡介

基於 HTTP 的動態自適應流(英語:Dynamic Adaptive Streaming over HTTP,縮寫 DASH,也稱 MPEG-DASH)是一種自適應比特率流技術,使高質量流媒體能夠經過傳統的 HTTP 網絡服務器以互聯網傳遞。 相似蘋果公司的 HTTP Live Streaming(HLS)方案,MPEG-DASH 會將內容分解成一系列小型的基於 HTTP 的文件片斷,每一個片斷包含很短長度的可播放內容,而內容總長度可能長達數小時。

內容將被製成多種比特率的備選片斷,以提供多種比特率的版本供選用。當內容被 MPEG-DASH 客戶端回放時,客戶端將根據當前網絡條件自動選擇下載和播放哪個備選方案。客戶端將選擇可及時下載的最高比特率片斷進行播放,從而避免播放卡頓或從新緩衝事件。也因如此,MPEG-DASH 客戶端能夠無縫適應不斷變化的網絡條件並提供高質量的播放體驗,擁有更少的卡頓與從新緩衝發生率。

MPEG-DASH 是首個基於 HTTP 的自適應比特率流解決方案,它也是一項國際標準。MPEG-DASH 不該該與傳輸協議混淆 —— MPEG-DASH 使用 TCP 傳輸協議。不一樣於 HLS、HDS 和 Smooth Streaming,DASH 不關心編解碼器,所以它能夠接受任何編碼格式編碼的內容,如 H.26五、H.26四、VP9 等。

雖然 HTML5 不直接支持 MPEG-DASH,可是已有一些 MPEG-DASH 的 JavaScript 實現容許在網頁瀏覽器中經過 HTML5 Media Source Extensions(MSE)使用 MPEG-DASH。另有其餘 JavaScript 實現,如 bitdash 播放器支持使用 HTML5 加密媒體擴展播放有 DRM 的MPEG-DASH。當與 WebGL 結合使用,MPEG-DASH 基於 HTML5 的自適應比特率流還可實現 360° 視頻的實時和按需的高效流式傳輸。

4.2 DASH 重要概念

  • MPD:媒體文件的描述文件(manifest),做用相似 HLS 的 m3u8 文件。
  • Representation:對應一個可選擇的輸出(alternative)。如 480p 視頻,720p 視頻,44100 採樣音頻等都使用 Representation 描述。
  • Segment(分片):每一個 Representation 會劃分爲多個 Segment。Segment 分爲 4 類,其中,最重要的是:Initialization Segment(每一個 Representation 都包含 1 個 Init Segment),Media Segment(每一個 Representation 的媒體內容包含若干 Media Segment)。

(圖片來源:https://blog.csdn.net/yue_hua...

在國內 Bilibili 於 2018 年開始使用 DASH 技術,至於爲何選擇 DASH 技術。感興趣的小夥伴能夠閱讀 咱們爲何使用DASH 這篇文章。

講了那麼多,相信有些小夥伴會好奇 MPD 文件長什麼樣?這裏咱們來看一下西瓜視頻播放器 DASH 示例中的 MPD 文件:

<?xml version="1.0"?>
<!-- MPD file Generated with GPAC version 0.7.2-DEV-rev559-g61a50f45-master  at 2018-06-11T11:40:23.972Z-->
<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="68082016-68083543" indexRange="68082016-68082059"/>
    </SegmentList>
   </Representation>
  </AdaptationSet>
 </Period>
</MPD>

(文件來源:https://h5player.bytedance.co...

在播放視頻時,西瓜視頻播放器會根據 MPD 文件,自動請求對應的分片進行播放。

前面咱們已經提到了 Bilibili,接下來不得不提其開源的一個著名的開源項目 —— flv.js,不過在介紹它以前咱們須要來了解一下 FLV 流媒體格式。

5、FLV

5.1 FLV 文件結構

FLV 是 FLASH Video 的簡稱,FLV 流媒體格式是隨着 Flash MX 的推出發展而來的視頻格式。因爲它造成的文件極小、加載速度極快,使得網絡觀看視頻文件成爲可能,它的出現有效地解決了視頻文件導入 Flash 後,使導出的 SWF 文件體積龐大,不能在網絡上很好的使用等問題。

FLV 文件由 FLV Header 和 FLV Body 兩部分構成,而 FLV Body 由一系列的 Tag 構成:

5.1.1 FLV 頭文件

FLV 頭文件:(9 字節)

  • 1-3:前 3 個字節是文件格式標識(FLV 0x46 0x4C 0x56)。
  • 4-4:第 4 個字節是版本(0x01)。
  • 5-5:第 5 個字節的前 5 個 bit 是保留的必須是 0。

    • 第 5 個字節的第 6 個 bit 音頻類型標誌(TypeFlagsAudio)。
    • 第 5 個字節的第 7 個 bit 也是保留的必須是 0。
    • 第5個字節的第8個bit視頻類型標誌(TypeFlagsVideo)。
  • 6-9: 第 6-9 的四個字節仍是保留的,其數據爲 00000009。
  • 整個文件頭的長度,通常是 9(3+1+1+4)。
5.1.2 tag 基本格式

tag 類型信息,固定長度爲 15 字節:

  • 1-4:前一個 tag 長度(4字節),第一個 tag 就是 0。
  • 5-5:tag 類型(1 字節);0x8 音頻;0x9 視頻;0x12 腳本數據。
  • 6-8:tag 內容大小(3 字節)。
  • 9-11:時間戳(3 字節,毫秒)(第 1 個 tag 的時候老是爲 0,若是是腳本 tag 就是 0)。
  • 12-12:時間戳擴展(1 字節)讓時間戳變成 4 字節(以存儲更長時間的 flv 時間信息),本字節做爲時間戳的最高位。

在 flv 回放過程當中,播放順序是按照 tag 的時間戳順序播放。任何加入到文件中時間設置數據格式都將被忽略。

  • 13-15:streamID(3 字節)老是 0。

FLV 格式詳細的結構圖以下圖所示:

在瀏覽器中 HTML5 的 <video> 是不支持直接播放 FLV 視頻格式,須要藉助 flv.js 這個開源庫來實現播放 FLV 視頻格式的功能。

5.2 flv.js 簡介

flv.js 是用純 JavaScript 編寫的 HTML5 Flash Video(FLV)播放器,它底層依賴於 Media Source Extensions。在實際運行過程當中,它會自動解析 FLV 格式文件並餵給原生 HTML5 Video 標籤播放音視頻數據,使瀏覽器在不借助 Flash 的狀況下播放 FLV 成爲可能。

5.2.1 flv.js 的特性
  • 支持播放 H.264 + AAC / MP3 編碼的 FLV 文件;
  • 支持播放多段分段視頻;
  • 支持播放 HTTP FLV 低延遲實時流;
  • 支持播放基於 WebSocket 傳輸的 FLV 實時流;
  • 兼容 Chrome,FireFox,Safari 10,IE11 和 Edge;
  • 極低的開銷,支持瀏覽器的硬件加速。
5.2.2 flv.js 的限制
  • MP3 音頻編解碼器沒法在 IE11/Edge 上運行;
  • HTTP FLV 直播流不支持全部的瀏覽器。
5.2.3 flv.js 的使用
<script src="flv.min.js"></script>
<video id="videoElement"></video>
<script>
    if (flvjs.isSupported()) {
        var videoElement = document.getElementById('videoElement');
        var flvPlayer = flvjs.createPlayer({
            type: 'flv',
            url: 'http://example.com/flv/video.flv'
        });
        flvPlayer.attachMediaElement(videoElement);
        flvPlayer.load();
        flvPlayer.play();
    }
</script>

5.3 flv.js 工做原理

flv.js 的工做原理是將 FLV 文件流轉換爲 ISO BMFF(Fragmented MP4)片斷,而後經過 Media Source Extensions API 將 mp4 段餵給 HTML5 <video> 元素。flv.js 的設計架構圖以下圖所示:

(圖片來源:https://github.com/bilibili/f...

有關 flv.js 工做原理更詳細的介紹,感興趣的小夥們能夠閱讀 花椒開源項目實時互動流媒體播放器 這篇文章。如今咱們已經介紹了 hls.jsflv.js 這兩個主流的流媒體解決方案,其實它們的成功離不開 Media Source Extensions 這個幕後英雄默默地支持。所以,接下來阿寶哥將帶你們一塊兒認識一下 MSE(Media Source Extensions)。

6、MSE

6.1 MSE API

媒體源擴展 API(Media Source Extensions) 提供了實現無插件且基於 Web 的流媒體的功能。使用 MSE,媒體串流可以經過 JavaScript 建立,而且能經過使用 audiovideo 元素進行播放。

近幾年來,咱們已經能夠在 Web 應用程序上無插件地播放視頻和音頻了。可是,現有架構過於簡單,只能知足一次播放整個曲目的須要,沒法實現拆分/合併數個緩衝文件。早期的流媒體主要使用 Flash 進行服務,以及經過 RTMP 協議進行視頻串流的 Flash 媒體服務器。

媒體源擴展(MSE)實現後,狀況就不同了。MSE 使咱們能夠把一般的單個媒體文件的 src 值替換成引用 MediaSource 對象(一個包含即將播放的媒體文件的準備狀態等信息的容器),以及引用多個 SourceBuffer 對象(表明多個組成整個串流的不一樣媒體塊)的元素。

爲了便於你們理解,咱們來看一下基礎的 MSE 數據流:

MSE 讓咱們可以根據內容獲取的大小和頻率,或是內存佔用詳情(例如何時緩存被回收),進行更加精準地控制。 它是基於它可擴展的 API 創建自適應比特率流客戶端(例如 DASH 或 HLS 的客戶端)的基礎。

在現代瀏覽器中創造能兼容 MSE 的媒體很是費時費力,還要消耗大量計算機資源和能源。此外,還須使用外部應用程序將內容轉換成合適的格式。雖然瀏覽器支持兼容 MSE 的各類媒體容器,但採用 H.264 視頻編碼、AAC 音頻編碼和 MP4 容器的格式是很是常見的,因此 MSE 須要兼容這些主流的格式。此外 MSE 還爲開發者提供了一個 API,用於運行時檢測容器和編解碼是否受支持。

6.2 MediaSource 接口

MediaSource 是 Media Source Extensions API 表示媒體資源 HTMLMediaElement 對象的接口。MediaSource 對象能夠附着在 HTMLMediaElement 在客戶端進行播放。在介紹 MediaSource 接口前,咱們先來看一下它的結構圖:

(圖片來源 —— https://www.w3.org/TR/media-s...

要理解 MediaSource 的結構圖,咱們得先來介紹一下客戶端音視頻播放器播放一個視頻流的主要流程:

獲取流媒體 -> 解協議 -> 解封裝 -> 音、視頻解碼 -> 音頻播放及視頻渲染(需處理音視頻同步)。

因爲採集的原始音視頻數據比較大,爲了方便網絡傳輸,咱們一般會使用編碼器,如常見的 H.264 或 AAC 來壓縮原始媒體信號。最多見的媒體信號是視頻,音頻和字幕。好比,平常生活中的電影,就是由不一樣的媒體信號組成,除運動圖片外,大多數電影還含有音頻和字幕。

常見的視頻編解碼器有:H.264,HEVC,VP9 和 AV1。而音頻編解碼器有:AAC,MP3 或 Opus。每一個媒體信號都有許多不一樣的編解碼器。下面咱們以西瓜視頻播放器的 Demo 爲例,來直觀感覺一下音頻軌、視頻軌和字幕軌:

如今咱們來開始介紹 MediaSource 接口的相關內容。

6.2.1 狀態
enum ReadyState {
    "closed", // 指示當前源未附加到媒體元素。
    "open", // 源已經被媒體元素打開,數據即將被添加到SourceBuffer對象中
    "ended" // 源仍附加到媒體元素,但endOfStream()已被調用。
};
6.2.2 流終止異常
enum EndOfStreamError {
    "network", // 終止播放併發出網絡錯誤信號。
    "decode" // 終止播放併發出解碼錯誤信號。
};
6.2.3 構造器
[Constructor]
interface MediaSource : EventTarget {
    readonly attribute SourceBufferList    sourceBuffers;
    readonly attribute SourceBufferList    activeSourceBuffers;
    readonly attribute ReadyState          readyState;
             attribute unrestricted double duration;
             attribute EventHandler        onsourceopen;
             attribute EventHandler        onsourceended;
             attribute EventHandler        onsourceclose;
  
    SourceBuffer addSourceBuffer(DOMString type);
    void         removeSourceBuffer(SourceBuffer sourceBuffer);
    void         endOfStream(optional EndOfStreamError error);
    void         setLiveSeekableRange(double start, double end);
    void         clearLiveSeekableRange();
    static boolean isTypeSupported(DOMString type);
};
6.2.4 屬性
  • MediaSource.sourceBuffers —— 只讀:返回一個 SourceBufferList 對象,包含了這個 MediaSource 的SourceBuffer 的對象列表。
  • MediaSource.activeSourceBuffers —— 只讀:返回一個 SourceBufferList 對象,包含了這個MediaSource.sourceBuffers 中的 SourceBuffer 子集的對象—即提供當前被選中的視頻軌(video track),啓用的音頻軌(audio tracks)以及顯示/隱藏的字幕軌(text tracks)的對象列表
  • MediaSource.readyState —— 只讀:返回一個包含當前 MediaSource 狀態的集合,即便它當前沒有附着到一個 media 元素(closed),或者已附着並準備接收 SourceBuffer 對象(open),亦或者已附着但這個流已被 MediaSource.endOfStream() 關閉。
  • MediaSource.duration:獲取和設置當前正在推流媒體的持續時間。
  • onsourceopen:設置 sourceopen 事件對應的事件處理程序。
  • onsourceended:設置 sourceended 事件對應的事件處理程序。
  • onsourceclose:設置 sourceclose 事件對應的事件處理程序。
6.2.5 方法
  • MediaSource.addSourceBuffer():建立一個帶有給定 MIME 類型的新的 SourceBuffer 並添加到 MediaSource 的 SourceBuffers 列表。
  • MediaSource.removeSourceBuffer():刪除指定的 SourceBuffer 從這個 MediaSource 對象中的 SourceBuffers 列表。
  • MediaSource.endOfStream():表示流的結束。
6.2.6 靜態方法
  • MediaSource.isTypeSupported():返回一個 Boolean 值代表給定的 MIME 類型是否被當前的瀏覽器支持—— 這意味着是否能夠成功的建立這個 MIME 類型的 SourceBuffer 對象。
6.2.7 使用示例
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)
    });
}

以上示例介紹瞭如何使用 MSE API,接下來咱們來分析一下主要的工做流程:

  • (1) 判斷當前平臺是否支持 Media Source Extensions API,若支持的話,則建立 MediaSource 對象,且綁定 sourceopen 事件處理函數。
  • (2) 建立一個帶有給定 MIME 類型的新的 SourceBuffer 並添加到 MediaSource 的 SourceBuffers 列表。
  • (3) 從遠程流服務器下載視頻流,並轉換成 ArrayBuffer 對象。
  • (4) 爲 sourceBuffer 對象添加 updateend 事件處理函數,在視頻流傳輸完成後關閉流。
  • (5) 往 sourceBuffer 對象中添加已轉換的 ArrayBuffer 格式的視頻流數據。

上面阿寶哥只是簡單介紹了一下 MSE API,想深刻了解它實際應用的小夥伴,能夠進一步瞭解一下 hls.jsflv.js 項目。接下來阿寶哥將介紹音視頻基礎之多媒體容器格式。

7、多媒體封裝格式

通常狀況下,一個完整的視頻文件是由音頻和視頻兩部分組成的。常見的 AVI、RMVB、MKV、ASF、WMV、MP四、3GP、FLV 等文件只能算是一種封裝格式。H.264,HEVC,VP9 和 AV1 等就是視頻編碼格式,MP三、AAC 和 Opus 等就是音頻編碼格式。 好比:將一個 H.264 視頻編碼文件和一個 AAC 音頻編碼文件按 MP4 封裝標準封裝之後,就獲得一個 MP4 後綴的視頻文件,也就是咱們常見的 MP4 視頻文件了。

音視頻編碼的主要目的是壓縮原始數據的體積,而封裝格式(也稱爲多媒體容器),好比 MP4,MKV,是用來存儲/傳輸編碼數據,並按必定規則把音視頻、字幕等數據組織起來,同時還會包含一些元信息,好比當前流中包含哪些編碼類型、時間戳等,播放器能夠按照這些信息來匹配解碼器、同步音視頻。

爲了能更好地理解多媒體封裝格式,咱們再來回顧一下視頻播放器的原理。

7.1 視頻播放器原理

視頻播放器是指能播放以數字信號形式存儲的視頻的軟件,也指具備播放視頻功能的電子器件產品。大多數視頻播放器(除了少數波形文件外)攜帶解碼器以還原通過壓縮的媒體文件,視頻播放器還要內置一整套轉換頻率以及緩衝的算法。大多數的視頻播放器還能支持播放音頻文件。

視頻播放基本處理流程大體包括如下幾個階段:

(1)解協議

從原始的流媒體協議數據中刪除信令數據,只保留音視頻數據,如採用 RTMP 協議傳輸的數據,通過解協議後輸出 flv 格式的數據。

(2)解封裝

分離音頻和視頻壓縮編碼數據,常見的封裝格式 MP4,MKV,RMVB,FLV,AVI 這些格式。從而將已經壓縮編碼的視頻、音頻數據放到一塊兒。例如 FLV 格式的數據通過解封裝後輸出 H.264 編碼的視頻碼流和 AAC 編碼的音頻碼流。

(3)解碼

視頻,音頻壓縮編碼數據,還原成非壓縮的視頻,音頻原始數據,音頻的壓縮編碼標準包括 AAC,MP3,AC-3 等,視頻壓縮編碼標準包含 H.264,MPEG2,VC-1 等通過解碼獲得非壓縮的視頻顏色數據如 YUV420P,RGB 和非壓縮的音頻數據如 PCM 等。

(4)音視頻同步

將同步解碼出來的音頻和視頻數據分別送至系統聲卡和顯卡播放。

瞭解完視頻播放器的原理,下一步咱們來介紹多媒體封裝格式。

7.2 多媒體封裝格式

對於數字媒體數據來講,容器就是一個能夠將多媒體數據混在一塊兒存放的東西,就像是一個包裝箱,它能夠對音、視頻數據進行打包裝箱,將原來的兩塊獨立的媒體數據整合到一塊兒,固然也能夠單單隻存放一種類型的媒體數據。

有時候,多媒體容器也稱封裝格式,它只是爲編碼後的多媒體數據提供了一個 「外殼」,也就是將全部的處理好的音頻、視頻或字幕都包裝到一個文件容器內呈現給觀衆,這個包裝的過程就叫封裝。 經常使用的封裝格式有: MP4,MOV,TS,FLV,MKV 等。這裏咱們來介紹你們比較熟悉的 MP4 封裝格式。

7.2.1 MP4 封裝格式

MPEG-4 Part 14(MP4)是最經常使用的容器格式之一,一般以 .mp4 文件結尾。它用於 HTTP(DASH)上的動態自適應流,也能夠用於 Apple 的 HLS 流。MP4 基於 ISO 基本媒體文件格式(MPEG-4 Part 12),該格式基於 QuickTime 文件格式。MPEG 表明動態圖像專家組,是國際標準化組織(ISO)和國際電工委員會(IEC)的合做。MPEG 的成立是爲了設置音頻和視頻壓縮與傳輸的標準。

MP4 支持多種編解碼器,經常使用的視頻編解碼器是 H.264 和 HEVC,而經常使用的音頻編解碼器是 AAC,AAC 是著名的 MP3 音頻編解碼器的後繼產品。

MP4 是由一些列的 box 組成,它的最小組成單元是 box。MP4 文件中的全部數據都裝在 box 中,即 MP4 文件由若干個 box 組成,每一個 box 有類型和長度,能夠將 box 理解爲一個數據對象塊。box 中能夠包含另外一個 box,這種 box 稱爲 container box。

一個 MP4 文件首先會有且僅有 一個 ftype 類型的 box,做爲 MP4 格式的標誌幷包含關於文件的一些信息,以後會有且只有一個 moov 類型的 box(movie box),它是一種 container box,能夠有多個,也能夠沒有,媒體數據的結構由 metadata 進行描述。

相信有些讀者會有疑問 —— 實際的 MP4 文件結構是怎麼樣的?經過使用 mp4box.js 提供的在線服務,咱們能夠方便的查看本地或在線 MP4 文件內部的結構:

mp4box.js 在線地址: https://gpac.github.io/mp4box...

因爲 MP4 文件結構比較複雜(不信請看下圖),這裏咱們就不繼續展開,有興趣的讀者,能夠自行閱讀相關文章。

接下來,咱們來介紹 Fragmented MP4 容器格式。

7.2.2 Fragmented MP4 封裝格式

MP4 ISO Base Media 文件格式標準容許以 fragmented 方式組織 box,這也就意味着 MP4 文件能夠組織成這樣的結構,由一系列的短的 metadata/data box 對組成,而不是一個長的 metadata/data 對。Fragmented MP4 文件結構以下圖所示,圖中只包含了兩個 fragments:

(圖片來源 —— https://alexzambelli.com/blog...

在 Fragmented MP4 文件中含有三個很是關鍵的 boxes:moovmoofmdat

  • moov(movie metadata box):用於存放多媒體 file-level 的元信息。
  • mdat(media data box):和普通 MP4 文件的 mdat 同樣,用於存放媒體數據,不一樣的是普通 MP4 文件只有一個 mdat box,而 Fragmented MP4 文件中,每一個 fragment 都會有一個 mdat 類型的 box。
  • moof(movie fragment box):用於存放 fragment-level 的元信息。該類型的 box 在普通的 MP4 文件中是不存在的,而在 Fragmented MP4 文件中,每一個 fragment 都會有一個 moof 類型的 box。

Fragmented MP4 文件中的 fragment 由 moofmdat 兩部分組成,每一個 fragment 能夠包含一個音頻軌或視頻軌,而且也會包含足夠的元信息,以保證這部分數據能夠單獨解碼。Fragment 的結構以下圖所示:

(圖片來源 —— https://alexzambelli.com/blog...

一樣,利用 mp4box.js 提供的在線服務,咱們也能夠清晰的查看 Fragmented MP4 文件的內部結構:

咱們已經介紹了 MP4 和 Fragmented MP4 這兩種容器格式,咱們用一張圖來總結一下它們之間的主要區別:

8、阿寶哥有話說

8.1 如何實現視頻本地預覽

視頻本地預覽的功能主要利用 URL.createObjectURL() 方法來實現。URL.createObjectURL() 靜態方法會建立一個 DOMString,其中包含一個表示參數中給出的對象的 URL。這個 URL 的生命週期和建立它的窗口中的 document 綁定。 這個新的 URL 對象表示指定的 File 對象或 Blob 對象。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>視頻本地預覽示例</title>
  </head>
  <body>
    <h3>阿寶哥:視頻本地預覽示例</h3>
    <input type="file" accept="video/*" onchange="loadFile(event)" />
    <video
      id="previewContainer"
      controls
      width="480"
      height="270"
      style="display: none;"
    ></video>

    <script>
      const loadFile = function (event) {
        const reader = new FileReader();
        reader.onload = function () {
          const output = document.querySelector("#previewContainer");
          output.style.display = "block";
          output.src = URL.createObjectURL(new Blob([reader.result]));
        };
        reader.readAsArrayBuffer(event.target.files[0]);
      };
    </script>
  </body>
</html>

8.2 如何實現播放器截圖

播放器截圖功能主要利用 CanvasRenderingContext2D.drawImage() API 來實現。Canvas 2D API 中的 CanvasRenderingContext2D.drawImage() 方法提供了多種方式在 Canvas 上繪製圖像。

drawImage API 的語法以下:

void ctx.drawImage(image, dx, dy);

void ctx.drawImage(image, dx, dy, dWidth, dHeight);

void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

其中 image 參數表示繪製到上下文的元素。容許任何的 canvas 圖像源(CanvasImageSource),例如:CSSImageValue,HTMLImageElement,SVGImageElement,HTMLVideoElement,HTMLCanvasElement,ImageBitmap 或者 OffscreenCanvas。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>播放器截圖示例</title>
  </head>
  <body>
    <h3>阿寶哥:播放器截圖示例</h3>
    <video id="video" controls="controls" width="460" height="270" crossorigin="anonymous">
      <!-- 請替換爲實際視頻地址 -->
      <source src="https://xxx.com/vid_159411468092581" />
    </video>
    <button onclick="captureVideo()">截圖</button>
    <script>
      let video = document.querySelector("#video");
      let canvas = document.createElement("canvas");
      let img = document.createElement("img");
      img.crossOrigin = "";
      let ctx = canvas.getContext("2d");

      function captureVideo() {
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;
        ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
        img.src = canvas.toDataURL();
        document.body.append(img);
      }
    </script>
  </body>
</html>

如今咱們已經知道如何獲取視頻的每一幀,其實在結合 gif.js 這個庫提供的 GIF 編碼功能,咱們就能夠快速地實現截取視頻幀生成 GIF 動畫的功能。這裏阿寶哥不繼續展開介紹,有興趣的小夥伴能夠閱讀 使用 JS 直接截取 視頻片斷 生成 gif 動畫 這篇文章。

8.3 如何實現 Canvas 播放視頻

使用 Canvas 播放視頻主要是利用 ctx.drawImage(video, x, y, width, height) 來對視頻當前幀的圖像進行繪製,其中 video 參數就是頁面中的 video 對象。因此若是咱們按照特定的頻率不斷獲取 video 當前畫面,並渲染到 Canvas 畫布上,就能夠實現使用 Canvas 播放視頻的功能。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>使用 Canvas 播放視頻</title>
  </head>
  <body>
    <h3>阿寶哥:使用 Canvas 播放視頻</h3>
    <video id="video" controls="controls" style="display: none;">
      <!-- 請替換爲實際視頻地址 -->
      <source src="https://xxx.com/vid_159411468092581" />
    </video>
    <canvas
      id="myCanvas"
      width="460"
      height="270"
      style="border: 1px solid blue;"
    ></canvas>
    <div>
      <button id="playBtn">播放</button>
      <button id="pauseBtn">暫停</button>
    </div>
    <script>
      const video = document.querySelector("#video");
      const canvas = document.querySelector("#myCanvas");
      const playBtn = document.querySelector("#playBtn");
      const pauseBtn = document.querySelector("#pauseBtn");
      const context = canvas.getContext("2d");
      let timerId = null;

      function draw() {
        if (video.paused || video.ended) return;
        context.clearRect(0, 0, canvas.width, canvas.height);
        context.drawImage(video, 0, 0, canvas.width, canvas.height);
        timerId = setTimeout(draw, 0);
      }

      playBtn.addEventListener("click", () => {
        if (!video.paused) return;
        video.play();
        draw();
      });

      pauseBtn.addEventListener("click", () => {
        if (video.paused) return;
        video.pause();
        clearTimeout(timerId);
      });
    </script>
  </body>
</html>

8.4 如何實現色度鍵控(綠屏效果)

上一個示例咱們介紹了使用 Canvas 播放視頻,那麼可能有一些小夥伴會有疑問,爲何要經過 Canvas 繪製視頻,Video 標籤不 「香」 麼?這是由於 Canvas 提供了 getImageDataputImageData 方法使得開發者能夠動態地更改每一幀圖像的顯示內容。這樣的話,咱們就能夠實時地操縱視頻數據來合成各類視覺特效到正在呈現的視頻畫面中。

好比 MDN 上的 」使用 canvas 處理視頻「 的教程中就演示瞭如何使用 JavaScript 代碼執行色度鍵控(綠屏或藍屏效果)。所謂的色度鍵控,又稱色彩嵌空,是一種去背合成技術。Chroma 爲純色之意,Key 則是抽離顏色之意。把被拍攝的人物或物體放置於綠幕的前面,並進行去背後,將其替換成其餘的背景。此技術在電影、電視劇及遊戲製做中被大量使用,色鍵也是虛擬攝影棚(Virtual studio)與視覺效果(Visual effects)當中的一個重要環節。

下面咱們來看一下關鍵代碼:

processor.computeFrame = function computeFrame() {
    this.ctx1.drawImage(this.video, 0, 0, this.width, this.height);
    let frame = this.ctx1.getImageData(0, 0, this.width, this.height);
    let l = frame.data.length / 4;

    for (let i = 0; i < l; i++) {
      let r = frame.data[i * 4 + 0];
      let g = frame.data[i * 4 + 1];
      let b = frame.data[i * 4 + 2];
      if (g > 100 && r > 100 && b < 43)
        frame.data[i * 4 + 3] = 0;
    }
    this.ctx2.putImageData(frame, 0, 0);
    return;
}

以上的 computeFrame() 方法負責獲取一幀數據並執行色度鍵控效果。利用色度鍵控技術,咱們還能夠實現純客戶端實時蒙版彈幕。這裏阿寶哥就不詳細介紹了,感興趣的小夥伴能夠閱讀一下創宇前端 彈幕不擋人!基於色鍵技術的純客戶端實時蒙版彈幕 這篇文章。

9、參考資源

相關文章
相關標籤/搜索