
視頻封裝格式
常見的視頻封裝格式(簡稱:視頻格式)包括了 AVI,MPEG,VOB 等,即相當(dāng)于一種儲存視頻信息的容器,由相應(yīng)的公司開發(fā)出來的。
AVI
AVI 格式(后綴為.AVI):它的英文全稱為 Audio Video Interleaved,即音頻視頻交錯格式。它于 1992 年被 Microsoft 公司推出。
這種視頻格式的優(yōu)點是圖像質(zhì)量好。由于無損 AVI 可以保存 alpha 通道,經(jīng)常被我們使用。缺點太多,體積過于龐大,而且更加糟糕的是壓縮標(biāo)準不統(tǒng)一,最普遍的現(xiàn)象就是高版本 Windows 媒體播放器播放不了采用早期編碼編輯的 AVI 格式視頻,而低版本 Windows 媒體播放器又播放不了采用最新編碼編輯的 AVI 格式視頻,所以我們在進行一些 AVI 格式的視頻播放時常會出現(xiàn)由于視頻編碼問題而造成的視頻不能播放或即使能夠播放,但存在不能調(diào)節(jié)播放進度和播放時只有聲音沒有圖像等一些莫名其妙的問題。
DV-AVI
DV-AVI 格式(后綴為.AVI):DV 的英文全稱是 Digital Video Format,是由索尼、松下、JVC 等多家廠商聯(lián)合提出的一種家用數(shù)字視頻格式。
數(shù)字攝像機就是使用這種格式記錄視頻數(shù)據(jù)的。它可以通過電腦的 IEEE 1394 端口傳輸視頻數(shù)據(jù)到電腦,也可以將電腦中編輯好的的視頻數(shù)據(jù)回錄到數(shù)碼攝像機中。這種視頻格式的文件擴展名也是 avi。電視臺采用錄像帶記錄模擬信號,通過 EDIUS 由 IEEE 1394 端口采集卡從錄像帶中采集出來的視頻就是這種格式。
MOV
QuickTime File Format 格式(后綴為.MOV):美國 Apple 公司開發(fā)的一種視頻格式,默認的播放器是蘋果的 QuickTime。
具有較高的壓縮比率和較完美的視頻清晰度等特點,并可以保存 alpha 通道。大家可能注意到了,每次安裝 EDIUS,我們都要安裝蘋果公司推出的 QuickTime。安裝其目的就是為了支持 JPG 格式圖像和 MOV 視頻格式導(dǎo)入。
MPEG
MPEG 格式(文件后綴可以是 .MPG .MPEG .MPE .DAT .VOB .ASF .3GP .MP4 等):它的英文全稱為 Moving Picture Experts Group,即運動圖像專家組格式,該專家組建于 1988 年,專門負責(zé)為 CD 建立視頻和音頻標(biāo)準,而成員都是為視頻、音頻及系統(tǒng)領(lǐng)域的技術(shù)專家。
MPEG 文件格式是運動圖像壓縮算法的國際標(biāo)準。MPEG 格式目前有三個壓縮標(biāo)準,分別是 MPEG-1、MPEG-2、和 MPEG-4。MPEG-1、MPEG-2 目前已經(jīng)使用較少,著重介紹 MPEG-4,其制定于 1998 年,MPEG-4 是為了播放流式媒體的高質(zhì)量視頻而專門設(shè)計的,以求使用最少的數(shù)據(jù)獲得最佳的圖像質(zhì)量。目前 MPEG-4 最有吸引力的地方在于它能夠保存接近于 DVD 畫質(zhì)的小體積視頻文件。你可能一定注意到了,怎么沒有 MPEG-3 編碼,因為這個項目原本目標(biāo)是為高分辨率電視(HDTV)設(shè)計,隨后發(fā)現(xiàn) MPEG-2 已足夠 HDTV 應(yīng)用,故 MPEG-3 的研發(fā)便中止。
WMV
WMV 格式(后綴為.WMV .ASF):它的英文全稱為 Windows Media Video,也是微軟推出的一種采用獨立編碼方式并且可以直接在網(wǎng)上實時觀看視頻節(jié)目的文件壓縮格式。
WMV 格式的主要優(yōu)點包括:本地或網(wǎng)絡(luò)回放,豐富的流間關(guān)系以及擴展性等。WMV 格式需要在網(wǎng)站上播放,需要安裝 Windows Media Player(簡稱 WMP),很不方便,現(xiàn)在已經(jīng)幾乎沒有網(wǎng)站采用了。
Real Video
Real Video 格式(后綴為.RM .RMVB):Real Networks 公司所制定的音頻視頻壓縮規(guī)范稱為 Real Media。
用戶可以使用 RealPlayer 根據(jù)不同的網(wǎng)絡(luò)傳輸速率制定出不同的壓縮比率,從而實現(xiàn)在低速率的網(wǎng)絡(luò)上進行影像數(shù)據(jù)實時傳送和播放。RMVB 格式:這是一種由 RM 視頻格式升級延伸出的新視頻格式,當(dāng)然性能上有很大的提升。RMVB 視頻也是有著較明顯的優(yōu)勢,一部大小為 700MB 左右的 DVD 影片,如果將其轉(zhuǎn)錄成同樣品質(zhì)的 RMVB 格式,其個頭最多也就 400MB 左右。大家可能注意到了,以前在網(wǎng)絡(luò)上下載電影和視頻的時候,經(jīng)常接觸到 RMVB 格式,但是隨著時代的發(fā)展這種格式被越來越多的更優(yōu)秀的格式替代,著名的人人影視字幕組在 2013 年已經(jīng)宣布不再壓制 RMVB 格式視頻。
FLV
Flash Video 格式(后綴為.FLV):由 Adobe Flash 延伸出來的的一種流行網(wǎng)絡(luò)視頻封裝格式。隨著視頻網(wǎng)站的豐富,這個格式已經(jīng)非常普及。
MKV
Matroska 格式(后綴為.MKV):是一種新的多媒體封裝格式,這個封裝格式可把多種不同編碼的視頻及 16 條或以上不同格式的音頻和語言不同的字幕封裝到一個 Matroska Media 檔內(nèi)。它也是其中一種開放源代碼的多媒體封裝格式。Matroska 同時還可以提供非常好的交互功能,而且比 MPEG 的方便、強大。
視頻編解碼
視頻實際上就是一幀一幀的圖片,拼接起來進行播放;標(biāo)準的圖像格式使用 RGB 三字節(jié)描述像素顏色值,會占用較大的存儲空間與帶寬。視頻編解碼器會根據(jù)前后圖像的變化做運動檢測,通過各種壓縮把變化的結(jié)果發(fā)送到對方。
實時視頻編碼器需要考慮兩個因素:編碼計算量和碼率帶寬,實時視頻會運行在移動端上,需要保證實時性就需要編碼足夠快,碼率盡量小?;谶@個原因現(xiàn)階段一般認為 H.264 是最佳的實時視頻編碼器,而且各個移動平臺也支持它的硬編碼技術(shù);譬如 1080P 進行過 H.264 編碼后帶寬也就在 200KB/S ~ 300KB/S 左右。
編碼基礎(chǔ)
總的來說,常用的編碼方式分為三種:
變換編碼:消除圖像的幀內(nèi)冗余。涉及到圖像學(xué)里面的兩個概念:空域和頻域??沼蚓褪俏覀兾锢淼膱D片,頻域就是將物理圖片根據(jù)其顏色值等映射為數(shù)字大小。而變換編碼的目的是利用頻域?qū)崿F(xiàn)去相關(guān)和能量集中。常用的正交變換有離散傅里葉變換,離散余弦變換等等。
運動估計和運動補償:消除幀間冗余。視頻壓縮還存在時間上的關(guān)聯(lián)性。例如,針對一些視頻變化,背景圖不變而只是圖片中部分物體的移動,針對這種方式,可以只對相鄰視頻幀中變化的部分進行編碼。
熵編碼:提高壓縮效率,熵編碼主要是針對碼節(jié)長度優(yōu)化實現(xiàn)的。原理是針對信源中出現(xiàn)概率大的符號賦予短碼,對于概率小的符號賦予長碼,然后總的來說實現(xiàn)平均碼長的最小值。編碼方式(可變字長編碼)有:霍夫曼編碼、算術(shù)編碼、游程編碼等。
I,B,P 實際上是從運動補償中引出來的,這里為了后面的方便先介紹一下。
I 幀(I-frame): 學(xué)名叫做: Intra-coded picture。也可以叫做獨立幀。該幀是編碼器隨機挑選的參考圖像,換句話說,一個 I 幀本身就是一個靜態(tài)圖像。它是作為 B,P 幀的參考點。對于它的壓縮,只能使用熵 和 變化編碼 這兩種方式進行幀內(nèi)壓縮。所以,它的運動學(xué)補償基本沒有。
P 幀(P?frame): 又叫做 Predicted picture--前向預(yù)測幀。即,他會根據(jù)前面一張圖像,來進行圖片間的動態(tài)壓縮,它的壓縮率和 I 幀比起來要高一些。
B 幀(B?frame): 又叫做 Bi-predictive picture-- 雙向預(yù)測。它比 P 幀來說,還多了后一張圖像的預(yù)測,所以它的壓縮率更高。
考慮到不同幀傳輸?shù)臒o序性,我們還需要引入 PTS 與 DTS 來進行控制,使用 DTS 來解碼,PTS 來進行播放。
PTS(presentation time stamps): 顯示時間戳,顯示器從接受到解碼到顯示的時間。
DTS(decoder timestamps): 解碼時間戳。也表示該 sample 在整個流中的順序
H.26X
H.26X 系列由 ITU 國際電傳視訊聯(lián)盟主導(dǎo)包括, H.261、H.262、H.263、H.264、H.265 等:
H.261:主要在老的視頻會議和視頻電話產(chǎn)品中使用。
H.263:主要用在視頻會議、視頻電話和網(wǎng)絡(luò)視頻上。
H.264:H.264/MPEG-4 第十部分,或稱 AVC(Advanced Video Coding,高級視頻編碼),是一種視頻壓縮標(biāo)準,一種被廣泛使用的高精度視頻的錄制、壓縮和發(fā)布格式。
H.265:高效率視頻編碼(High Efficiency Video Coding,簡稱 HEVC)是一種視頻壓縮標(biāo)準,H.264/MPEG-4 AVC 的繼任者。HEVC 被認為不僅提升圖像質(zhì)量,同時也能達到 H.264/MPEG-4 AVC 兩倍之壓縮率(等同于同樣畫面質(zhì)量下比特率減少了 50%),可支持 4K 分辨率甚至到超高畫質(zhì)電視,最高分辨率可達到 8192×4320(8K 分辨率),這是目前發(fā)展的趨勢。直至 2013 年,Potplayer 添加了對于 H.265 視頻的解碼,尚未有大眾化編碼軟件出現(xiàn)。
H.264 是由 ITU 和 MPEG 兩個組織共同提出的標(biāo)準,整個編碼器包括幀內(nèi)預(yù)測編碼、幀間預(yù)測編碼、運動估計、熵編碼等過程,支持分層編碼技術(shù)(SVC)。單幀 720P 分辨率一般 PC 上的平均編碼延遲 10 毫秒左右,碼率范圍 1200 ~ 2400kpbs,同等視頻質(zhì)量壓縮率是 MPEG4 的 2 倍,H.264 也提供 VBR、ABR、CBR、CQ 等多種編碼模式,各個移動平臺兼容性好。
H.264 為了防止丟包和減小帶寬還引入一種雙向預(yù)測編碼的 B 幀,B 幀以前面的 I 或 P 幀和后面的 P 幀為參考幀。H.264 為了防止中間 P 幀丟失視頻圖像會一直錯誤它引入分組序列(GOP)編碼,也就是隔一段時間發(fā)一個全量 I 幀,上一個 I 幀與下一個 I 幀之間為一個分組 GOP。
在實時視頻當(dāng)中最好不要加入 B 幀,因為 B 幀是雙向預(yù)測,需要根據(jù)后面的視頻幀來編碼,這會增大編解碼延遲。
MPGA 系列
MPEG 系列由 ISO 國際標(biāo)準組織機構(gòu)下屬的 MPEG 運動圖象專家組開發(fā)視頻編碼方面主要有:
MPEG-1 第二部分(MPEG-1 第二部分主要使用在 VCD 上,有些在線視頻也使用這種格式。該編解碼器的質(zhì)量大致上和原有的 VHS 錄像帶相當(dāng)。)
MPEG-2 第二部分(MPEG-2 第二部分等同于 H.262,使用在 DVD、SVCD 和大多數(shù)數(shù)字視頻廣播系統(tǒng)和有線分布系統(tǒng)(cable distribution systems)中。)
MPEG-4 第二部分(MPEG-4 第二部分標(biāo)準可以使用在網(wǎng)絡(luò)傳輸、廣播和媒體存儲上。比起 MPEG-2 和第一版的 H.263,它的壓縮性能有所提高。)
MPEG-4 第十部分(MPEG-4 第十部分技術(shù)上和 ITU-T H.264 是相同的標(biāo)準,有時候也被叫做“AVC”)最后這兩個編碼組織合作,誕生了 H.264/AVC 標(biāo)準。ITU-T 給這個標(biāo)準命名為 H.264,而 ISO/IEC 稱它為 MPEG-4 高級視頻編碼(Advanced Video Coding,AVC)。
音頻編碼器
實時音視頻除了視頻編碼器以外還需要音頻編碼器,音頻編碼器只需要考慮編碼延遲和丟包容忍度,所以一般的 MP3、AAC、OGG 都不太適合作為實時音頻編碼器。從現(xiàn)在市場上來使用來看,Skype 研發(fā)的 Opus 已經(jīng)成為實時音頻主流的編碼器。Opus 優(yōu)點眾多,編碼計算量小、編碼延遲 20ms、窄帶編碼-silk、寬帶編碼器 CELT、自帶網(wǎng)絡(luò)自適應(yīng)編碼等。
同視頻編碼類似,將原始的音頻流按照一定的標(biāo)準進行編碼,上傳,解碼,同時在播放器里播放,當(dāng)然音頻也有許多編碼標(biāo)準,例如 PCM 編碼,WMA 編碼,AAC 編碼等等。
直播協(xié)議
常用的直播協(xié)議包括了 HLS, RTMP 與 HTTP-FLV 這三種,其對比如下:
協(xié)議優(yōu)勢缺陷延遲性HLS支持性廣延時巨高10s 以上RTMP延時性好,靈活量大的話,負載較高1s 以上HTTP-FLV延時性好,游戲直播常用只能在手機 APP 播放2s 以上
HLS
HLS, HTTP Live Streaming 是 Apple 提出的直播流協(xié)議,其將整個流分成一個個小的塊,并基于 HTTP 的文件來下載;HLS 由兩部分構(gòu)成,一個是 .m3u8 文件,一個是 .ts 視頻文件;每一個 .m3u8 文件,分別對應(yīng)若干個 ts 文件,這些 ts 文件才是真正存放視頻的數(shù)據(jù),m3u8 文件只是存放了一些 ts 文件的配置信息和相關(guān)路徑,當(dāng)視頻播放時,.m3u8 是動態(tài)改變的,video 標(biāo)簽會解析這個文件,并找到對應(yīng)的 ts 文件來播放,所以一般為了加快速度,.m3u8 放在 web 服務(wù)器上,ts 文件放在 CDN 上。 HLS 協(xié)議視頻支持 H.264 格式的編碼,支持的音頻編碼方式是 AAC 編碼。
.m3u8 文件,其實就是以 UTF-8 編碼的 m3u 文件,這個文件本身不能播放,只是存放了播放信息的文本文件:
#EXTM3U m3u文件頭
#EXT-X-MEDIA-SEQUENCE 第一個TS分片的序列號
#EXT-X-TARGETDURATION 每個分片TS的最大的時長
#EXT-X-ALLOW-CACHE 是否允許cache
#EXT-X-ENDLIST m3u8文件結(jié)束符
#EXTINF 指定每個媒體段(ts)的持續(xù)時間(秒),僅對其后面的URI有效
mystream-12.ts
HLS 協(xié)議的使用也非常便捷,將 m3u8 直接寫入到 src 中然后交與瀏覽器解析,也可以使用 fetch 來手動解析并且獲取相關(guān)文件:
<video controls autoplay>
<source
src="http://devimages.apple.com/iphone/samples/bipbop/masterplaylist.m3u8"
type="application/vnd.apple.mpegurl"
/>
<p class="warning">Your browser does not support HTML5 video.</p>
</video>
HLS 詳細版的內(nèi)容比上面的簡版多了一個 playlist,也可以叫做 master。在 master 中,會根據(jù)網(wǎng)絡(luò)段實現(xiàn)設(shè)置好不同的 m3u8 文件,比如,3G/4G/wifi 網(wǎng)速等。比如,一個 master 文件中為:
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2855600,CODECS="avc1.4d001f,mp4a.40.2",RESOLUTION=960x540
live/medium.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=5605600,CODECS="avc1.640028,mp4a.40.2",RESOLUTION=1280x720
live/high.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1755600,CODECS="avc1.42001f,mp4a.40.2",RESOLUTION=640x360
live/low.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=545600,CODECS="avc1.42001e,mp4a.40.2",RESOLUTION=416x234
live/cellular.m3u8
以 high.m3u8 文件為例,其內(nèi)容會包含:
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:26
#EXTINF:9.901,
http://media.example.com/wifi/segment26.ts
#EXTINF:9.901,
http://media.example.com/wifi/segment27.ts
#EXTINF:9.501,
http://media.example.com/wifi/segment28.ts
該二級 m3u8 文件也可以稱為 media 文件,其有三種類型:
live playlist: 動態(tài)列表。顧名思義,該列表是動態(tài)變化的,里面的 ts 文件會實時更新,并且過期的 ts 索引會被刪除。默認,情況下都是使用動態(tài)列表。
event playlist: 靜態(tài)列表。它和動態(tài)列表主要區(qū)別就是,原來的 ts 文件索引不會被刪除,該列表是不斷更新,而且文件大小會逐漸增大。它會在文件中,直接添加 #EXT-X-PLAYLIST-TYPE:EVENT 作為標(biāo)識。
VOD playlist: 全量列表。它就是將所有的 ts 文件都列在 list 當(dāng)中。如果,使用該列表,就和播放一整個視頻沒有啥區(qū)別了。它是使用 #EXT-X-ENDLIST 表示文件結(jié)尾。
顯而易見,HLS 的延時包含了 TCP 握手、m3u8 文件下載與解析、ts 文件下載與解析等多個步驟,可以縮短列表的長度和單個 ts 文件的大小來降低延遲,極致來說可以縮減列表長度為 1,并且 ts 的時長為 1s,但是這樣會造成請求次數(shù)增加,增大服務(wù)器壓力,當(dāng)網(wǎng)速慢時回造成更多的緩沖,所以蘋果官方推薦的 ts 時長時 10s,所以這樣就會大改有 30s 的延遲。
RTMP
RTMP,Real-Time Messaging Protocol 是由 Adobe 推出的音視頻流傳遞協(xié)議;它通過一種自定義的協(xié)議,來完成對指定直播流的播放和相關(guān)的操作。在 Web 上可以通過 MSE(MediaSource Extensions)來接入 RTMP,基本思路是根據(jù) WebSocket 直接建立長連接進行數(shù)據(jù)的交流和監(jiān)聽。RTMP 協(xié)議根據(jù)不同的套層,也可以分為:
純 RTMP: 直接通過 TCP 連接,端口為 1935
RTMPS: RTMP + TLS/SSL,用于安全性的交流。
RTMPE: RTMP + encryption。在 RTMP 原始協(xié)議上使用,Adobe 自身的加密方法
RTMPT: RTMP + HTTP。使用 HTTP 的方式來包裹 RTMP 流,這樣能直接通過防火墻。不過,延遲性比較大。
RTMFP: RMPT + UDP。該協(xié)議常常用于 P2P 的場景中,針對延時有變態(tài)的要求。
RTMP 內(nèi)部是借由 TCP 長連接協(xié)議傳輸相關(guān)數(shù)據(jù),所以,它的延時性非常低。并且,該協(xié)議靈活性非常好(所以,也很復(fù)雜),它可以根據(jù) message stream ID 傳輸數(shù)據(jù),也可以根據(jù) chunk stream ID 傳遞數(shù)據(jù)。兩者都可以起到流的劃分作用。流的內(nèi)容也主要分為:視頻,音頻,相關(guān)協(xié)議包等。
HTTP-FLV
RTMP 是直接將流的傳輸架在 RTMP 協(xié)議之上,而 HTTP-FLV 是在 RTMP 和客戶端之間套了一層轉(zhuǎn)碼的過程,即:
每個 FLV 文件是通過 HTTP 的方式獲取的,所以,它通過抓包得出的協(xié)議頭需要使用 chunked 編碼:
Content-Type:video/x-flv
Expires:Fri, 10 Feb 2017 05:24:03 GMT
Pragma:no-cache
Transfer-Encoding:chunked
網(wǎng)絡(luò)傳輸
單對單模式主要是怎么通過路由路徑優(yōu)化手段達到兩點之間最優(yōu),這方面 SKYPE 首先提出基于 P2P 的 Real-time Network 模型。而 單對多模式是一個分發(fā)樹模型,各個客戶端節(jié)點需要就近接入離自己最近的服務(wù)器,然后在服務(wù)器與服務(wù)器構(gòu)建一個實時通信網(wǎng)絡(luò)。
基礎(chǔ)
推流
所謂推流,就是將我們已經(jīng)編碼好的音視頻數(shù)據(jù)發(fā)往視頻流服務(wù)器中。實時音視頻系統(tǒng)都是一個客戶端到其他一個或者多個客戶端的通信行為,這就意味著需要將客戶端編碼后的音視頻數(shù)據(jù)傳輸?shù)狡渌麑崟r音視頻系統(tǒng)都是一個客戶端到其他一個或者多個客戶端的通信行為,這就意味著需要將客戶端編碼后的音視頻數(shù)據(jù)傳輸?shù)狡渌蛻舳松?,一般做法是先將?shù)據(jù)實時上傳到服務(wù)器上,服務(wù)器再進行轉(zhuǎn)發(fā)到其他客戶端,客戶端這個上傳音視頻數(shù)據(jù)行為稱為推流。
我們可以通過 Nginx 的 RTMP 擴展方便地搭建推流服務(wù)器:
rtmp {
server {
listen 1935; #監(jiān)聽的端口
chunk_size 4000;
application hls { #rtmp推流請求路徑
live on;
hls on;
hls_path /usr/local/var/www/hls;
hls_fragment 5s;
}
}
}
推流會受到客戶端網(wǎng)絡(luò)的影響,例如:wifi 信號衰減、4G 弱網(wǎng)、擁擠的寬帶網(wǎng)絡(luò)等。為了應(yīng)對這個問題,實時音視頻系統(tǒng)會設(shè)計一個基于擁塞控制和 QOS 策略的推流模塊。
WebRTC
WebRTC 是一個開源項目,旨在使得瀏覽器能為實時通信(RTC)提供簡單的 JavaScript 接口。說的簡單明了一點就是讓瀏覽器提供 JS 的即時通信接口。這個接口所創(chuàng)立的信道并不是像 WebSocket 一樣,打通一個瀏覽器與 WebSocket 服務(wù)器之間的通信,而是通過一系列的信令,建立一個瀏覽器與瀏覽器之間(peer-to-peer)的信道,這個信道可以發(fā)送任何數(shù)據(jù),而不需要經(jīng)過服務(wù)器。并且 WebRTC 通過實現(xiàn) MediaStream,通過瀏覽器調(diào)用設(shè)備的攝像頭、話筒,使得瀏覽器之間可以傳遞音頻和視頻。WebRTC 有三個重要的部分:MediaStream、RTCPeerConnection、RTCDataChannel:
MediaStream:通過設(shè)備的攝像頭及話筒獲得視頻、音頻的同步流
PeerConnection: 用于構(gòu)建點對點之間穩(wěn)定、高效的流傳輸?shù)慕M件
DataChannel:能夠使得瀏覽器之間(點對點)簡歷一個高吞吐量、低延時的信道,用于傳輸任何數(shù)據(jù)
實時網(wǎng)絡(luò)傳輸優(yōu)化
TCP 與 UDP
在大規(guī)模實時多媒體傳輸網(wǎng)絡(luò)中,TCP 和 RTMP 都不占優(yōu)勢。TCP 是個擁塞公平傳輸?shù)膮f(xié)議,它的擁塞控制都是為了保證網(wǎng)絡(luò)的公平性而不是快速到達,我們知道,TCP 層只有順序到對應(yīng)的報文才會提示應(yīng)用層讀數(shù)據(jù),如果中間有報文亂序或者丟包都會在 TCP 做等待,所以 TCP 的發(fā)送窗口緩沖和重發(fā)機制在網(wǎng)絡(luò)不穩(wěn)定的情況下會造成延遲不可控,而且傳輸鏈路層級越多延遲會越大。
在實時傳輸中使用 UDP 更加合理,UDP 避免了 TCP 繁重的三次握手、四次揮手和各種繁雜的傳輸特性,只需要在 UDP 上做一層簡單的鏈路 QoS 監(jiān)測和報文重發(fā)機制,實時性會比 TCP 好,這一點從 RTP 和 DDCP 協(xié)議可以證明這一點,我們正式參考了這兩個協(xié)議來設(shè)計自己的通信協(xié)議。
UDP 不可避免地存在抖動、亂序、丟包問題,視頻必須按照嚴格是時間戳來播放,否則的就會出現(xiàn)視頻動作加快或者放慢的現(xiàn)象,如果我們按照接收到視頻數(shù)據(jù)就立即播放,那么這種加快和放慢的現(xiàn)象會非常頻繁和明顯。也就是說網(wǎng)絡(luò)抖動會嚴重影響視頻播放的質(zhì)量,一般為了解決這個問題會設(shè)計一個視頻播放緩沖區(qū),通過緩沖接收到的視頻幀,再按視頻幀內(nèi)部的時間戳來播放既可。
UDP 除了小范圍的抖動以外,還是出現(xiàn)大范圍的亂序現(xiàn)象,就是后發(fā)的報文先于先發(fā)的報文到達接收方。亂序會造成視頻幀順序錯亂,一般解決的這個問題會在視頻播放緩沖區(qū)里做一個先后排序功能讓先發(fā)送的報文先進行播放。
UDP 在傳輸過程還會出現(xiàn)丟包,丟失的原因有多種,例如:網(wǎng)絡(luò)出口不足、中間網(wǎng)絡(luò)路由擁堵、socket 收發(fā)緩沖區(qū)太小、硬件問題、傳輸損耗問題等等。在基于 UDP 視頻傳輸過程中,丟包是非常頻繁發(fā)生的事情,丟包會造成視頻解碼器丟幀,從而引起視頻播放卡頓。這也是大部分視頻直播用 TCP 和 RTMP 的原因,因為 TCP 底層有自己的重傳機制,可以保證在網(wǎng)絡(luò)正常的情況下視頻在傳輸過程不丟。基于 UDP 丟包補償方式一般有以下幾種:
報文冗余,報文冗余很好理解,就是一個報文在發(fā)送的時候發(fā)送 2 次或者多次。這個做的好處是簡單而且延遲小,壞處就是需要額外 N 倍(N 取決于發(fā)送的次數(shù))的帶寬。
FEC, Forward Error Correction,即向前糾錯算法,常用的算法有糾刪碼技術(shù)(EC),在分布式存儲系統(tǒng)中比較常見。最簡單的就是 A B 兩個報文進行 XOR(與或操作)得到 C,同時把這三個報文發(fā)往接收端,如果接收端只收到 AC,通過 A 和 C 的 XOR 操作就可以得到 B 操作。
丟包重傳,丟包重傳有兩種方式,一種是 push 方式,一種是 pull 方式。Push 方式是發(fā)送方?jīng)]有收到接收方的收包確認進行周期性重傳,TCP 用的是 push 方式。pull 方式是接收方發(fā)現(xiàn)報文丟失后發(fā)送一個重傳請求給發(fā)送方,讓發(fā)送方重傳丟失的報文。丟包重傳是按需重傳,比較適合視頻傳輸?shù)膽?yīng)用場景,不會增加太對額外的帶寬,但一旦丟包會引來至少一個 RTT 的延遲。
擁塞控制
要評估一個網(wǎng)絡(luò)通信質(zhì)量的好壞和延遲一個重要的因素就是 Round-Trip Time(網(wǎng)絡(luò)往返延遲),也就是 RTT。評估兩端之間的 RTT 方法很簡單,大致如下:
發(fā)送端方一個帶本地時間戳 T1 的 ping 報文到接收端;
接收端收到 ping 報文,以 ping 中的時間戳 T1 構(gòu)建一個攜帶 T1 的 pong 報文發(fā)往發(fā)送端;
發(fā)送端接收到接收端發(fā)了的 pong 時,獲取本地的時間戳 T2,用 T2 – T1 就是本次評測的 RTT。
因為客戶端有可能在弱網(wǎng)環(huán)境下進行推流,音視頻數(shù)據(jù)如果某一時刻發(fā)多了,就會引起網(wǎng)絡(luò)擁塞或者延遲,如果發(fā)少了,可能視頻的清晰不好。在實時音視頻傳輸過程會設(shè)計一個自動適應(yīng)本地網(wǎng)絡(luò)變化的擁塞控制算法,像 QUIC 中的 BBR、webRTC 中 GCC 和通用的 RUDP。思路是通過 UDP 協(xié)議反饋的丟包和網(wǎng)絡(luò)延遲(RTT)來計算當(dāng)前網(wǎng)絡(luò)的變化和最大瞬時吞吐量,根據(jù)這幾個值調(diào)整上層的視頻編碼器的碼率、視頻分辨率等,從而達到適應(yīng)當(dāng)前網(wǎng)絡(luò)狀態(tài)的目的。
QoS 策略
客戶端推流除了需要考慮網(wǎng)絡(luò)上傳能力以外,還需要考慮客戶端的計算能力。如果在 5 年前的安卓機上去編碼一個分辨率為 640P 的高清視頻流,那這個過程必然會產(chǎn)生延遲甚至無法工作。為此需要針對各個終端的計算能力設(shè)計一個 QoS 策略,不同計算能力的終端采用不同的視頻編碼器、分辨率、音頻處理算法等,這個 QoS 策略會配合擁塞控制做一個狀態(tài)不可逆的查找過程,直到找到最合適的 QoS 策略位置
媒體處理技術(shù)
回聲消除
在實時音視頻系統(tǒng)中,回聲消除是一個難點,盡管 webRTC 提供了開源的回聲消除模塊,但在移動端和一些特殊的場景表現(xiàn)不佳。專業(yè)的實時音視頻系統(tǒng)會進行回聲消除的優(yōu)化。回聲消除的原理描述很簡單,就是將揚聲器播放的聲音波形和麥克風(fēng)錄制的波形進行抵消,達到消除回聲的作用。因為回聲的回錄時間不確定,所以很難確定什么時間點進行對應(yīng)聲音數(shù)據(jù)的抵消。在專業(yè)的回聲消除模塊里面通常會設(shè)計一個逼近函數(shù),通過不斷對輸出和輸入聲音波形進行在線學(xué)習(xí)逼近,確定回聲消除的時間差值點。
簡單 Web 實驗
本部分的代碼實驗參考 MushiChat。
Media Source Extension
MSE 全稱就是 Media Source Extensions。它是一套處理視頻流技術(shù)的簡稱,里面包括了一系列 API:Media Source,Source Buffer 等。在沒有 MSE 出現(xiàn)之前,前端對 video 的操作,僅僅局限在對視頻文件的操作,而并不能對視頻流做任何相關(guān)的操作?,F(xiàn)在 MSE 提供了一系列的接口,使開發(fā)者可以直接提供 media stream。
const vidElement = document.querySelector(video);
if (window.MediaSource) {
const 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);
const mime = video/webm; codecs="opus, vp9";
const mediaSource = e.target;
const sourceBuffer = mediaSource.addSourceBuffer(mime);
const videoUrl = droid.webm;
fetch(videoUrl)
.then(function(response) {
return response.arrayBuffer();
})
.then(function(arrayBuffer) {
sourceBuffer.addEventListener(updateend, function(e) {
if (!sourceBuffer.updating && mediaSource.readyState === open) {
mediaSource.endOfStream();
}
});
sourceBuffer.appendBuffer(arrayBuffer);
});
}
