公司目前的業務會接觸比較多的音視頻,所以有必要了解一些基本概念。
  文章涉及的一些源碼已上傳至[Github](https://github.com/pwstrick/webrtc),可隨意下載。
## 一、基礎概念
  本節音視頻的基礎概念摘自書籍《FFmpeg入門詳解 音視頻原理及應用》。
**1)音頻**
  聲音的三要素為頻率、振幅和波形,即聲音的音調、聲波的響度和聲音的音色。
  音頻是一種利用數字化手段對聲音進行錄制、存放、編輯、壓縮和播放的技術,相關概念包括采樣、量化、編碼、采樣率、聲道數和比特率等。
  采樣是指只在時間軸上對信號進行數字化。
  量化是指在幅度軸上對信號進行數字化。
  每個量化都是一個采樣,將這么多采樣進行存儲就叫做編碼。
  聲道數是指所支持的能發不同聲音的音響個數,常見的有單聲道、立體聲道等。
  比特率,也叫碼率(b/s)指一個數據流中每秒能通過的信息量。
  WebRTC 對音頻的噪聲抑制和回聲消除做了很好的處理。
  音頻格式是指要在計算機內播放或處理的音頻文件的格式,是對聲音文件進行數、模轉換的過程,常見的有 MP3、WAV、AAC 等。
  音頻信號能壓縮的依據包括聲音信號中存在大量的冗余度,以及人的聽覺具有強音能抑制同時存在的弱音現象。
  壓縮編碼原理是在壓縮掉冗余的信號,冗余信號是指不能被人耳感知到的信息,包括聽覺范圍之外以及被掩蔽掉的音頻信號,壓縮編碼分為 2 類。
1. 無損壓縮:熵編碼,包括哈夫曼、算術和行程等編碼。
2. 有損壓縮:波形、參數、混合等編碼,波形編碼包括 PCM、DPCM、ADPCM、子帶編碼、矢量量化等。
**2)視頻**
  視頻泛指將一系列靜態影像以電信號的方式加以捕捉、記錄、處理、存儲、傳送與重現的各種技術。
  幀(Frame)是視頻的一個基本概念,表示一副畫面,一段視頻由許多幀組成。
  視頻幀又分為 I 幀、P 幀和 B 幀:
1. I 幀是幀內編碼幀,是一個完整都關鍵幀,無需輔助就能完整顯示畫面;
2. P 幀是前向預測編碼幀,是一個非完整幀,需要參考前面的 I 幀或 P幀生成畫面;
3. 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了。
  對原始視頻進行壓縮的目的是去除冗余信息,這些信息包括:
1. 空間冗余,在圖像數據中,像素間在行、列方向上都有很大的相關性,相鄰像素的值比較接近或者完全相同。
2. 時間冗余,在視頻圖像序列中,相鄰兩幀又許多共同的地方,可采用運動補償算法來去掉冗余。
3. 視覺冗余,相對于人眼的視覺特性而言,人類視覺系統對圖像的敏感性是非均勻和非線性的,并不是所有變化都能被觀察到。
4. 結構冗余,在圖像的紋理區,以及圖像的像素值存在明顯的分布模式。
5. 知識冗余,對許多圖像的理解與某些先驗知識有相當大的相關性,這類規律可由先驗知識和背景知識得到。
  視頻播放器播放本地視頻文件或互聯網上的流媒體文件大概需要解協議、解封裝、解碼、音視頻同步、渲染等幾個步驟,如下圖所示。
:-: 
## 二、Web中的音視頻
  HTML5 標準推出后,提供了播放視頻的 video 元素,以及播放音頻的 audio 元素。
  為了能更精準的控制時間、容器格式轉換、媒體質量和內存釋放等復雜的媒體處理,W3C 推出了[MSE](https://developer.mozilla.org/zh-CN/docs/Web/API/Media_Source_Extensions_API)(Media Source Extensions)媒體源擴展標準。
  若要訪問瀏覽器中已有的編解碼器,可以試試[WebCodecs](https://developer.mozilla.org/en-US/docs/Web/API/WebCodecs_API),它可以訪問原始視頻幀、音頻數據塊、圖像解碼器、音頻和視頻編碼器和解碼器。
  在瀏覽器中主流的視頻編碼格式是 H.264/MPEG-4,不過需要支付專利費。
:-: 
  而 Google 推出的開源編碼格式:VP8,除了 IE 之外,其他瀏覽器的高版本都能支持。
:-: 
  最新的 H.265 和 VP9 在瀏覽器的兼容性上都不理想,有些第三方庫會自己寫一個 H.265 的解碼器腳本,然后來播放視頻。
**1)播放器**
  直播使用 video 元素播放視頻很多功能都無法滿足,因此很多時候都會引入一個播放器,例如[video.js](https://videojs.com/)、[react-player](https://github.com/cookpete/react-player)等。
  這些播放器都能支持多種格式的視頻,例如 flv、m3u8、mp4 等;并且有完整的控制鍵,例如音量、縮放、倍速等,覆蓋移動和 PC 兩個平臺,以及可引入插件等。
  下圖是一種播放器的整體架構圖,來源于《[Web端H.265播放器研發解密](https://fed.taobao.org/blog/taofed/do71ct/web-player-h265/)》。
:-: 
  除了常規的使用 video 元素播放視頻之外,還可以用 canvas 播放,具體實現可以參考[JSMpeg](https://github.com/phoboslab/jsmpeg)。
**2)MSE**
  在 MSE 規范中,提供了[MediaSource](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaSource)對象,它可以附著在 HTMLMediaElement 中,即 video 元素的 src 的屬性值可以是它。
  一個 MediaSource 包含一個或多個 SourceBuffer 實例(下圖來源于[W3C官網](https://w3c.github.io/media-source/pipeline_model_description.html)),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](https://github.com/bilibili/flv.js)播放器是依賴 MSE,可自動解析 flv 格式的文件并在 video 元素中播放,完全拋棄了 Flash。
  順便說一句,flv 格式的數據傳輸一般采用 RTMP(Real Time Messaging Protocol)直播協議,這是由 Adobe 公司提出的私有協議,工作在 TCP 協議之上。
參考資料:
[視頻和音頻內容](https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content)
[網頁視頻編碼指南](https://developer.mozilla.org/zh-CN/docs/Web/Media/Formats/Video_codecs)
[Support for ISOBMFF-based MIME types in Browsers](https://cconcolato.github.io/media-mime-support/)
[WebRTC應用該使用哪種音視頻編解碼器](https://www.agora.io/cn/community/blog/21703)
[三種視頻流瀏覽器播放解決方案](https://juejin.cn/post/6844903953126129671)
[Web端H.265播放器研發解密](https://fed.taobao.org/blog/taofed/do71ct/web-player-h265/)
[從 Chrome 源碼 video 實現到 Web H265 Player](https://developer.aliyun.com/article/782832)
[Web音視頻串流](https://jackym06.github.io/2021/04/21/Web%E9%9F%B3%E8%A7%86%E9%A2%91%E4%B8%B2%E6%B5%81/)
[WebCodecs對音視頻進行編碼解碼](https://chenng.cn/posts/WebCodecs%E5%AF%B9%E9%9F%B3%E8%A7%86%E9%A2%91%E8%BF%9B%E8%A1%8C%E7%BC%96%E7%A0%81%E8%A7%A3%E7%A0%81/)
[Media Source Extensions](https://web.dev/media-mse-basics/)
[快速播放音頻和視頻預加載](https://web.dev/fast-playback-with-preload/)
[Web視頻播放原理:介紹](https://paopaolee.github.io/web/LP20190706A/)
[流式播放器的實現原理](https://www.jianshu.com/p/af4a36a8b5ec)
[「1.4 萬字」玩轉前端 Video 播放器](https://xie.infoq.cn/article/8d2b0ba59ea03dd458a902ef0)
*****
> 原文出處:
[博客園-HTML躬行記](https://www.cnblogs.com/strick/category/1770829.html)
[知乎專欄-HTML躬行記](https://zhuanlan.zhihu.com/c_1250826149041238016)
已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎閱讀。

推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、擴展運算符和剩余參數
- 3、解構
- 4、模板字面量
- 5、對象字面量的擴展
- 6、Symbol
- 7、代碼模塊化
- 8、數字
- 9、字符串
- 10、正則表達式
- 11、對象
- 12、數組
- 13、類型化數組
- 14、函數
- 15、箭頭函數和尾調用優化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、類
- 21、類的繼承
- 22、Promise
- 23、Promise的靜態方法和應用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基礎實踐
- 3、WebRTC視頻通話
- 4、Web音視頻基礎
- CSS進階
- 1、CSS基礎拾遺
- 2、偽類和偽元素
- 3、CSS屬性拾遺
- 4、浮動形狀
- 5、漸變
- 6、濾鏡
- 7、合成
- 8、裁剪和遮罩
- 9、網格布局
- 10、CSS方法論
- 11、管理后臺響應式改造
- React
- 1、函數式編程
- 2、JSX
- 3、組件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表單
- 8、樣式
- 9、組件通信
- 10、高階組件
- 11、Redux基礎
- 12、Redux中間件
- 13、React Router
- 14、測試框架
- 15、React Hooks
- 16、React源碼分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基礎
- 4、webpack進階
- 5、Git
- 6、Fiddler
- 7、自制腳手架
- 8、VSCode插件研發
- 9、WebView中的頁面調試方法
- Vue.js
- 1、數據綁定
- 2、指令
- 3、樣式和表單
- 4、組件
- 5、組件通信
- 6、內容分發
- 7、渲染函數和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、數據類型
- 2、接口
- 3、類
- 4、泛型
- 5、類型兼容性
- 6、高級類型
- 7、命名空間
- 8、裝飾器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系統和網絡
- 3、命令行工具
- 4、自建前端監控系統
- 5、定時任務的調試
- 6、自制短鏈系統
- 7、定時任務的進化史
- 8、通用接口
- 9、微前端實踐
- 10、接口日志查詢
- 11、E2E測試
- 12、BFF
- 13、MySQL歸檔
- 14、壓力測試
- 15、活動規則引擎
- 16、活動配置化
- 17、UmiJS版本升級
- 18、半吊子的可視化搭建系統
- 19、KOA源碼分析(上)
- 20、KOA源碼分析(下)
- 21、花10分鐘入門Node.js
- 22、Node環境升級日志
- 23、Worker threads
- 24、低代碼
- 25、Web自動化測試
- 26、接口攔截和頁面回放實驗
- 27、接口管理
- 28、Cypress自動化測試實踐
- 29、基于Electron的開播助手
- Node.js精進
- 1、模塊化
- 2、異步編程
- 3、流
- 4、事件觸發器
- 5、HTTP
- 6、文件
- 7、日志
- 8、錯誤處理
- 9、性能監控(上)
- 10、性能監控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 監控系統
- 1、SDK
- 2、存儲和分析
- 3、性能監控
- 4、內存泄漏
- 5、小程序
- 6、較長的白屏時間
- 7、頁面奔潰
- 8、shin-monitor源碼分析
- 前端性能精進
- 1、優化方法論之測量
- 2、優化方法論之分析
- 3、瀏覽器之圖像
- 4、瀏覽器之呈現
- 5、瀏覽器之JavaScript
- 6、網絡
- 7、構建
- 前端體驗優化
- 1、概述
- 2、基建
- 3、后端
- 4、數據
- 5、后臺
- Web優化
- 1、CSS優化
- 2、JavaScript優化
- 3、圖像和網絡
- 4、用戶體驗和工具
- 5、網站優化
- 6、優化閉環實踐
- 數據結構與算法
- 1、鏈表
- 2、棧、隊列、散列表和位運算
- 3、二叉樹
- 4、二分查找
- 5、回溯算法
- 6、貪心算法
- 7、分治算法
- 8、動態規劃
- 程序員之路
- 大學
- 2011年
- 2012年
- 2013年
- 2014年
- 項目反思
- 前端基礎學習分享
- 2015年
- 再一次項目反思
- 然并卵
- PC網站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端學習之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 2024年
- 日志
- 2020