HTML躬行記(4)——Web音影片基礎
- 2022 年 11 月 7 日
- 筆記
- Node.js躬行記
公司目前的業務會接觸比較多的音影片,所以有必要了解一些基本概念。
文章涉及的一些源碼已上傳至 Github,可隨意下載。
一、基礎概念
本節音影片的基礎概念摘自書籍《FFmpeg入門詳解 音影片原理及應用》。
1)音頻
聲音的三要素為頻率、振幅和波形,即聲音的音調、聲波的響度和聲音的音色。
音頻是一種利用數字化手段對聲音進行錄製、存放、編輯、壓縮和播放的技術,相關概念包括取樣、量化、編碼、取樣率、聲道數和比特率等。
取樣是指只在時間軸上對訊號進行數字化。
量化是指在幅度軸上對訊號進行數字化。
每個量化都是一個取樣,將這麼多取樣進行存儲就叫做編碼。
聲道數是指所支援的能發不同聲音的音響個數,常見的有單聲道、立體聲道等。
比特率,也叫碼率(b/s)指一個數據流中每秒能通過的資訊量。
WebRTC 對音頻的雜訊抑制和回聲消除做了很好的處理。
音頻格式是指要在電腦內播放或處理的音頻文件的格式,是對聲音文件進行數、模轉換的過程,常見的有 MP3、WAV、AAC 等。
音頻訊號能壓縮的依據包括聲音訊號中存在大量的冗餘度,以及人的聽覺具有強音能抑制同時存在的弱音現象。
壓縮編碼原理是在壓縮掉冗餘的訊號,冗餘訊號是指不能被人耳感知到的資訊,包括聽覺範圍之外以及被掩蔽掉的音頻訊號,壓縮編碼分為 2 類。
- 無損壓縮:熵編碼,包括哈夫曼、算術和行程等編碼。
- 有損壓縮:波形、參數、混合等編碼,波形編碼包括 PCM、DPCM、ADPCM、子帶編碼、矢量量化等。
2)影片
影片泛指將一系列靜態影像以電訊號的方式加以捕捉、記錄、處理、存儲、傳送與重現的各種技術。
幀(Frame)是影片的一個基本概念,表示一副畫面,一段影片由許多幀組成。
影片幀又分為 I 幀、P 幀和 B 幀:
- I 幀是幀內編碼幀,是一個完整都關鍵幀,無需輔助就能完整顯示畫面;
- P 幀是前向預測編碼幀,是一個非完整幀,需要參考前面的 I 幀或 P幀生成畫面;
- B 幀是雙向預測編碼幀,需要參考前後影像幀編碼生成。
幀率(f/s 或 Hz)是單位時間內幀的數量,電視一般 1 秒 24 幀,幀率越高,畫面越流暢、逼真。
碼率即比特率(b/s),指單位時間內播放連續媒體(如壓縮後的音頻或影片)的比特數量,碼率越高頻寬消耗得就越多。
影片格式非常多,包括影片文件格式、影片封裝格式和影片編碼格式等。
影片文件格式有 MP4、RMVB、MKV、FLV、TS、M3U8 等。FLV 是一種流媒體格式,TS 廣泛應用於數字廣播系統。
M3U8 是使用 HLS 協議格式的基礎,文件內容是一個播放列表(Playlist),採用 UTF-8 編碼,記錄了一些列媒體片段資源,順序播放片段即可完整展示資源,如下所示。
#EXTM3U #EXT-X-STREAM-INF:BANDWIDTH=150000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2" http://example.com/low/index.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=240000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2" http://example.com/lo_mid/index.m3u8 #EXTINF:15.169000 94256c7244451f8fd_20221020113637199.ts #EXT-X-ENDLIST
其中 codecs 參數提供解碼特定流所需的編解碼器的完整資訊。之所以使用 ts 格式的片段是為了可以無縫拼接,讓影片連續。
HLS(HTTP Live Steaming,HTTP 直播流協議)的工作原理是把整個流分成一個一個的基於 HTTP 的文件來下載,每次只下載部分。
影片封裝格式也叫容器,可以將已經編碼並壓縮好的影片軌和音頻軌按照一定的格式放到一個文件中。
影片編碼格式能夠對數字影片進行壓縮或解壓縮的程式或設備,也可以指通過特定的壓縮技術,將某種影片格式轉換成另一種影片格式。
常見的影片編碼格式有幾個大系列,包括 MPEG-X、H.26X 和 VPX 等。
H.264(H.264/MPEG-4 或 AVC)是一種被廣泛使用的高精度影片的錄製、壓縮和發布格式,H.265 是它的繼任者。
一個原始影片,若沒有編碼,則體積會非常大。假設圖的解析度是 1920*1080,幀率為 30,每像素占 24b,那沒張圖占 6.22MB左右,1 秒的影片大小是 186.6MB左右,1 分鐘就是 11G了。
對原始影片進行壓縮的目的是去除冗餘資訊,這些資訊包括:
- 空間冗餘,在影像數據中,像素間在行、列方向上都有很大的相關性,相鄰像素的值比較接近或者完全相同。
- 時間冗餘,在影片影像序列中,相鄰兩幀又許多共同的地方,可採用運動補償演算法來去掉冗餘。
- 視覺冗餘,相對於人眼的視覺特性而言,人類視覺系統對影像的敏感性是非均勻和非線性的,並不是所有變化都能被觀察到。
- 結構冗餘,在影像的紋理區,以及影像的像素值存在明顯的分布模式。
- 知識冗餘,對許多影像的理解與某些先驗知識有相當大的相關性,這類規律可由先驗知識和背景知識得到。
影片播放器播放本地影片文件或互聯網上的流媒體文件大概需要解協議、解封裝、解碼、音影片同步、渲染等幾個步驟,如下圖所示。
二、Web中的音影片
HTML5 標準推出後,提供了播放影片的 video 元素,以及播放音頻的 audio 元素。
為了能更精準的控制時間、容器格式轉換、媒體品質和記憶體釋放等複雜的媒體處理,W3C 推出了 MSE(Media Source Extensions)媒體源擴展標準。
若要訪問瀏覽器中已有的編解碼器,可以試試 WebCodecs,它可以訪問原始影片幀、音頻數據塊、影像解碼器、音頻和影片編碼器和解碼器。
在瀏覽器中主流的影片編碼格式是 H.264/MPEG-4,不過需要支付專利費。
而 Google 推出的開源編碼格式:VP8,除了 IE 之外,其他瀏覽器的高版本都能支援。
最新的 H.265 和 VP9 在瀏覽器的兼容性上都不理想,有些第三方庫會自己寫一個 H.265 的解碼器腳本,然後來播放影片。
1)播放器
直播使用 video 元素播放影片很多功能都無法滿足,因此很多時候都會引入一個播放器,例如 video.js、react-player 等。
這些播放器都能支援多種格式的影片,例如 flv、m3u8、mp4 等;並且有完整的控制鍵,例如音量、縮放、倍速等,覆蓋移動和 PC 兩個平台,以及可引入插件等。
下圖是一種播放器的整體架構圖,來源於《Web端H.265播放器研發解密》。
除了常規的使用 video 元素播放影片之外,還可以用 canvas 播放,具體實現可以參考 JSMpeg。
2)MSE
在 MSE 規範中,提供了 MediaSource 對象,它可以附著在 HTMLMediaElement 中,即 video 元素的 src 的屬性值可以是它。
一個 MediaSource 包含一個或多個 SourceBuffer 實例(下圖來源於W3C官網),SourceBuffer 表示通過 MediaSource 傳遞到 HTMLMediaElement 並播放的媒體片段。
下面是一個使用 MSE 的完整示例,修改了 MDN 中的程式碼首先是聲明影片路徑和 MIME 參數,注意,要正確指定 codecs 參數,否則影片無法播放。
const video = document.getElementById('video'); const assetURL = 'demo.mp4'; const mime = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
然後實例化 MediaSource 類,並將其與 video 元素關聯,註冊 sourceopen 事件。
const mediaSource = new MediaSource(); video.src = URL.createObjectURL(mediaSource); mediaSource.addEventListener('sourceopen', sourceOpen);
最後實現 sourceOpen 函數,通過 fetch() 請求影片資源,將讀取到的 ArrayBuffer 數據附加到 sourceBuffer 中。
function sourceOpen(e) { URL.revokeObjectURL(video.src); const mediaSource = e.target; // 創建指定 MIME 類型的 SourceBuffer 並添加到 MediaSource 的 SourceBuffers 列表 const sourceBuffer = mediaSource.addSourceBuffer(mime); // 請求資源 fetch(assetURL) .then(function(response) { return response.arrayBuffer(); // 轉換成 ArrayBuffer }) .then(function(buf) { sourceBuffer.addEventListener('updateend', function() { if (!sourceBuffer.updating && mediaSource.readyState === 'open') { mediaSource.endOfStream(); // 影片流傳輸完成後關閉流 video.play(); } }); sourceBuffer.appendBuffer(buf); // 添加已轉換成 ArrayBuffer 的影片流數據 }); }
為 sourceBuffer 註冊 updateend 事件,並在影片流傳輸完成後關閉流。
注意,要想看到影片的播放,不能直接靜態 HTML 文件,需要將文件附加到 HTTP 伺服器中。
本文藉助 Node.js,搭建了一個極簡的 HTTP 伺服器,當然也可以將 HTML 文件掛載到 Nginx 或 IIS 伺服器中。
const http = require('http'); const fs = require('fs'); // HTTP伺服器 const server = http.createServer((req, res) => { // 實例化 URL 類 const url = new URL(req.url, '//localhost:1000'); const { pathname } = url; // 路由 if(pathname === '/') { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(fs.readFileSync('./index.html')); }else if(pathname === '/demo.mp4') { res.writeHead(200, { 'Content-Type': 'video/mp4' }); res.end(fs.readFileSync('./demo.mp4')); }else if(pathname === '/client.js') { res.writeHead(200, { 'Content-Type': 'application/javascript' }); res.end(fs.readFileSync('./client.js')); } }); server.listen(1000);
B站的 flv.js 播放器是依賴 MSE,可自動解析 flv 格式的文件並在 video 元素中播放,完全拋棄了 Flash。
順便說一句,flv 格式的數據傳輸一般採用 RTMP(Real Time Messaging Protocol)直播協議,這是由 Adobe 公司提出的私有協議,工作在 TCP 協議之上。
參考資料:
Support for ISOBMFF-based MIME types in Browsers
從 Chrome 源碼 video 實現到 Web H265 Player