公司目前的業務會接觸比較多的音視訊,所以有必要了解一些基本概念。
文章涉及的一些原始碼已上傳至 Github,可隨意下載。
本節音視訊的基礎概念摘自書籍《FFmpeg入門詳解 音視訊原理及應用》。
1)音訊
聲音的三要素為頻率、振幅和波形,即聲音的音調、聲波的響度和聲音的音色。
音訊是一種利用數位化手段對聲音進行錄製、存放、編輯、壓縮和播放的技術,相關概念包括取樣、量化、編碼、取樣率、聲道數和位元率等。
取樣是指只在時間軸上對訊號進行數位化。
量化是指在幅度軸上對訊號進行數位化。
每個量化都是一個取樣,將這麼多取樣進行儲存就叫做編碼。
聲道數是指所支援的能發不同聲音的音響個數,常見的有單聲道、立體聲道等。
位元率,也叫位元速率(b/s)指一個資料流中每秒能通過的資訊量。
WebRTC 對音訊的噪聲抑制和回聲消除做了很好的處理。
音訊格式是指要在計算機內播放或處理的音訊檔的格式,是對聲音檔案進行數、模轉換的過程,常見的有 MP3、WAV、AAC 等。
音訊訊號能壓縮的依據包括聲音訊號中存在大量的冗餘度,以及人的聽覺具有強音能抑制同時存在的弱音現象。
壓縮編碼原理是在壓縮掉冗餘的訊號,冗餘訊號是指不能被人耳感知到的資訊,包括聽覺範圍之外以及被掩蔽掉的音訊訊號,壓縮編碼分為 2 類。
2)視訊
視訊泛指將一系列靜態影像以電訊號的方式加以捕捉、記錄、處理、儲存、傳送與重現的各種技術。
幀(Frame)是視訊的一個基本概念,表示一副畫面,一段視訊由許多幀組成。
視訊幀又分為 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了。
對原始視訊進行壓縮的目的是去除冗餘資訊,這些資訊包括:
視訊播放器播放本地視訊檔或網際網路上的串流媒體檔案大概需要解協定、解封裝、解碼、音視訊同步、渲染等幾個步驟,如下圖所示。
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, 'http://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