王金環(huán),李寶敏
(1.西安培華學(xué)院 智能科學(xué)與信息工程學(xué)院計算機(jī)系,陜西 西安 710125;2.西安工業(yè)大學(xué) 計算機(jī)學(xué)院,陜西 西安 710021)
目前,網(wǎng)頁上缺乏能夠替代Flash技術(shù)的直播技術(shù),隨著Flash技術(shù)的淘汰,Apple公司的HLS技術(shù)延遲過高,網(wǎng)頁實(shí)時直播就成了技術(shù)上的空白區(qū)域。該系統(tǒng)的研究填補(bǔ)了這片空白,得以解決網(wǎng)頁上直播困難的問題。
同時,彈幕作為一種新興的交流方式,已經(jīng)被大眾廣為接受。該系統(tǒng)整合了彈幕與實(shí)時直播,結(jié)合系統(tǒng)低成本的優(yōu)點(diǎn),允許任何沒有技術(shù)能力的人可以便捷地搭建自己的直播云平臺。
傳統(tǒng)的云直播服務(wù)[1-2]需要提供視頻云轉(zhuǎn)碼的功能,以便于兼容各種設(shè)備。而該項目的直播云服務(wù)器提供的視頻轉(zhuǎn)發(fā)功能[3],則基于最新的接口,將視頻的解碼、編碼、渲染等操作集成到客戶端,這樣做不僅能降低服務(wù)端的硬件需求,更能降低視頻直播延遲。因此,該項目的核心意義在于:降低直播服務(wù)端的硬件需求,將計算壓力分散到客戶端,是一種新型的直播方式?;赪ebSocket[4]與HTML 5的Uint8Array以及Media Source Extension API,為用戶提供高實(shí)時性的視頻互動直播,相對于其他直播系統(tǒng)而言,成本低,延遲少,進(jìn)而將“彈幕”這種新興高效的交流方式推廣給大眾。
(1)通過JavaScript[5-6]與WebSocket處理二進(jìn)制數(shù)據(jù)流;
(2)通過JavaScript對視頻進(jìn)行De-multiplexing、Decoding、Encoding、Multiplexing;
(3)通過網(wǎng)頁顯示RGB圖像信息;
(4)通過網(wǎng)頁API對De-multiplexing后的視頻封包進(jìn)行Decoding并顯示;
(5)通過WebSocket,將視頻彈幕廣播給觀眾。
通過JavaScript對視頻進(jìn)行解碼與重編碼[7],自動判斷瀏覽器所支持的視頻格式,然后重新編碼成為瀏覽器所支持的格式。
由于JavaScript目前已經(jīng)加入了Uint8 Array的數(shù)據(jù)類型,擁有了對字節(jié)數(shù)據(jù)流的處理能力,基于以上前提,服務(wù)端可以通過WebSocket下發(fā)視頻數(shù)據(jù)包,由服務(wù)端解包,并根據(jù)瀏覽器所支持的格式解碼,提供給瀏覽器。
關(guān)于視頻的解碼部分,該系統(tǒng)的工作流程為:首先,所有的視頻流都是由推流端發(fā)送的原樣數(shù)據(jù),因此,服務(wù)端必須存儲該視頻流的格式信息,以便給客戶端選擇Decoder。服務(wù)端在客戶端連接后,立刻先發(fā)送視頻流的格式信息,然后發(fā)送視頻流的元數(shù)據(jù)信息。客戶端根據(jù)視頻流的格式信息選擇Decoder,然后通過Decoder解碼視頻流的元數(shù)據(jù),再根據(jù)元數(shù)據(jù)中的視頻編碼信息選擇Codec,最后,通過Codec對視頻流的每一個Packet進(jìn)行解碼,實(shí)現(xiàn)視頻的解碼。
而關(guān)于判斷瀏覽器所支持的編碼格式方面,該系統(tǒng)采用了遍歷測試的方式,對所有系統(tǒng)中支持的編碼進(jìn)行逐一檢測。檢測的方式是通過生成一段有效的某一編碼的視頻數(shù)據(jù),然后交給瀏覽器播放,等待一段時間后,判斷該視頻是否開始播放,如果沒有開始播放,則說明瀏覽器不支持這種編碼。對于所有編碼都不支持的瀏覽器,該系統(tǒng)將視頻流解碼為RGB數(shù)據(jù),由瀏覽器的canvas繪制出來,實(shí)現(xiàn)視頻的播放。
對于彈幕數(shù)據(jù),首先,彈幕數(shù)據(jù)需要進(jìn)行敏感字審核才能在客戶端顯示,但為了保證服務(wù)端的最低硬件需求,服務(wù)端會下發(fā)所有的敏感字列表到客戶端;其次,由于WebSocket的高實(shí)時性,系統(tǒng)依然使用WebSocket作為彈幕數(shù)據(jù)的傳輸方式,由其他客戶端將彈幕的文字、顏色、位置數(shù)據(jù)格式化為JSON消息,發(fā)送到服務(wù)端,服務(wù)端通過WebSocket轉(zhuǎn)發(fā)給其他客戶端,客戶端根據(jù)關(guān)鍵字列表自動審核。
(1)推流端與服務(wù)端進(jìn)行直播鑒權(quán);
(2)推流端選擇視頻封裝格式;
(3)推流端將封裝格式發(fā)送到服務(wù)端;
(4)推流端對推流設(shè)備攝像頭上的圖像數(shù)據(jù)進(jìn)行編碼壓縮Encoding;
(5)推流端將Encoded的數(shù)據(jù)進(jìn)行Multiplexing打包;
(6)推流端將Multiplexed的視頻封包發(fā)送到服務(wù)端;
(7)服務(wù)端接受視頻封包格式并采用這種格式對后面發(fā)來的視頻封包進(jìn)行Decoding;
(8)服務(wù)端根據(jù)推流端的鑒權(quán)數(shù)據(jù),首先獲取第一個封包元數(shù)據(jù)封包并保存;
(9)服務(wù)端把視頻封包以及音頻封包根據(jù)直播鑒權(quán)信息,將數(shù)據(jù)包轉(zhuǎn)發(fā)到其他相同通道的客戶端;
(10)客戶端根據(jù)瀏覽器兼容性選擇重編碼格式;
(11)客戶端根據(jù)服務(wù)端發(fā)來的視頻封裝格式進(jìn)行Decoding;
(12)客戶端對視頻數(shù)據(jù)進(jìn)行De- Multiplexing;
(13)客戶端轉(zhuǎn)碼;
(14)由Media Source Extension API將視頻數(shù)據(jù)提交給瀏覽器,由瀏覽器播放;
(15)由客戶端對用戶發(fā)送的彈幕信息進(jìn)行JSON編碼,并發(fā)送到服務(wù)器;
(16)服務(wù)器下發(fā)給其他客戶端;
(17)其他客戶端進(jìn)行JSON解碼,然后根據(jù)數(shù)據(jù)顯示彈幕。
視頻文件的解碼分為兩個步驟[8],首先,將原始圖像信息進(jìn)行壓縮Encoding,然后,將壓縮后的數(shù)據(jù)封裝入數(shù)據(jù)包Multiplexing。
該系統(tǒng)默認(rèn)采用MPEG-Video[9]進(jìn)行壓縮,按照MPEG格式進(jìn)行編碼[10-11]。
3.1.1 顏色壓縮
首先,MPEG-Video規(guī)定,在壓縮之前,必須將圖像像素格式轉(zhuǎn)換為Y’CbCr格式(Y’=明度,Cb=藍(lán)色色度,Cr=紅色色度)。由于大部分設(shè)備攝像頭的圖像數(shù)據(jù)均為RGB格式,因此,這個轉(zhuǎn)換必不可少。
Y’CbCr信號被稱為YPbPr,YPbPr信號是通過如下的公式定義的:
Y'=KR·R'+KG·G'+KB·B'
(1)
(2)
(3)
其中,R'G'B'表示應(yīng)用了Gamma矯正后的RGB值,KRKGKB是定義的RGB顏色空間,并且滿足:KR+KG+KB=1。
明度與色度是分離儲存的,色度需要以4∶2∶2進(jìn)行抽樣,抽樣后的圖像大小會變?yōu)樵瓉淼乃姆种?。色度抽樣的原理是由于人眼相對于色度,對明度更加敏感,所以減少色度信息可以在保持畫面質(zhì)量微降的情況下大幅降低圖像尺寸。不同于紅綠藍(lán)三原色的顏色表示法,在視頻領(lǐng)域中通常使用明度和兩個色度通道來表示顏色,色度和明度是由Gamma矯正后的R'G'B'分量的加權(quán)和形成的。因此,明度與亮度并不相同。4∶2∶0是指,在明度上,分辨率是100%的,在色度上,水平方向50%分辨率,垂直方向50%分辨率,由于人眼對色度不如亮度敏感,這樣的壓縮并不會太多地降低畫面質(zhì)量,但能將圖像尺寸大幅降低。示例見圖1,其中,灰圈代表CbCr顏色像素,白圈代表亮度像素。
圖1 4∶2∶0顏色抽樣示例
3.1.2 幀壓縮
MPEG-Video有多種不同的幀以便應(yīng)對不同的情況。
(1)l-frames。
l-frames是關(guān)鍵幀,它保存了完整的圖像信息,在seek視頻進(jìn)度的時候,只能seek到最附近的關(guān)鍵幀,因?yàn)橹挥嘘P(guān)鍵幀才保存了完整的圖像信息。
(2)P-frames。
P-frames是Predicted-frame的縮寫,還被稱為前向預(yù)料幀,P-frame不保存完整的圖像信息,只保存與前一幀的差異信息。
(3)B-frames。
B-frames是Bidirectional-frame的縮寫,還被稱為后向預(yù)料幀,B-frame與P-frame非常類似,但B-frame保存了自己與前一幀和后一幀的差異信息。
(4)D-frames。
D-frames也是一種關(guān)鍵幀,但是它的畫面經(jīng)過了非常嚴(yán)重的有損壓縮,在播放的時候會跳過D-frame,但在seeking的時候,D-frame會用來顯示當(dāng)前seek的畫面。主要用途是在seeking的時候既能讓用戶看到當(dāng)前畫面,又能節(jié)約預(yù)覽圖像所花費(fèi)的時間。
3.1.3 DCT圖像壓縮
每8×8像素的塊會被應(yīng)用離散余弦變換(DCT),離散余弦變換類似于只使用實(shí)數(shù)的離散傅里葉變換,然后再消除變換后的小的高頻信息就可以得到壓縮后的圖像數(shù)據(jù)[12]。圖2是一個編碼后的8×8 DCT塊示例。
圖2 編碼后的8×8 DCT塊示例
通常來說,DCT是一個線性的,可反的函數(shù)f:RN→RN(其中R是實(shí)數(shù)集),也可以說是一個可反的N×N的矩陣。它們都是根據(jù)下面的某一個公式n個實(shí)數(shù)x0,x1,…,xn-1變換到另外n個實(shí)數(shù)f0,f1,…,fn-1的操作:
DCT-I:
k=0,1,…,N-1
(4)
DCT-I的邊界條件是:xk相對于k=0點(diǎn),偶對稱,并且相對于k=n-1點(diǎn)偶對稱;對fm的情況也類似。
DCT-II:
(5)
DCT-III:
(6)
DCT-IV:
(7)
在視頻編碼領(lǐng)域,一般采用分時復(fù)用,在不同時間發(fā)送不同類型的數(shù)據(jù),以便實(shí)現(xiàn)多路復(fù)用的功能。
MPEG的Program Stream是MPEG的多路復(fù)用方式。關(guān)于Program Stream的定義如下:
(1)協(xié)議頭。
首先,每一個Stream都必須由一個32位的起始碼開頭,第0到第3字節(jié)是起始碼前綴,第4字節(jié)為Stream ID。
(2)傳輸數(shù)據(jù)包。
傳輸數(shù)據(jù)視頻包有:Sync byte,Transport Error Indicator (TEI),Payload Unit Start Indicator (PUSI),Transport Priority,PID,Transport Scrambling Control (TSC),Adaptation field control,Continuity counter,Adaptation field,Payload Data。
Media Source Extension允許Java Script從
該項目通過將視頻文Re-Multiplexing為瀏覽器所支持的格式,并提供給瀏覽器播放的方式來實(shí)現(xiàn)視頻文件的渲染。
Media Source Extension定義了如下幾個接口:
MediaSource:代表被HTMLVideo/Audio標(biāo)簽所播放的媒體源對象;
SourceBuffer:代表傳遞給HTML Video /Audio的一部分媒體數(shù)據(jù);
SourceBufferList:一個Source Buffer List列表;
VideoPlaybackQuality:包含了被Video/Audio標(biāo)簽所播放的媒體的質(zhì)量信息,例如被拋棄的或不正確的幀的數(shù)量等;
TrackDefault:提供關(guān)于SourceBuffer的類型、標(biāo)簽、語言等信息;
TrackDefaultList:TrackDefault的列表。
圖3 Media Source Extension 的工作示例
MediaSource代表了被播放的媒體源,對象可以附加到一個HTML Video/Audio標(biāo)簽上。媒體源到事件目標(biāo)的關(guān)系如圖4所示。
圖4 媒體源到事件目標(biāo)的關(guān)系
4.1.1 構(gòu)造函數(shù)
MediaSource():構(gòu)造一個新的Media Source對象[16-17]。
4.1.2 屬 性
MediaSource.sourceBuffers:一個只讀屬性值,返回了SourceBufferList。
MediaSource.activeSourceBuffers:一個只讀屬性值,返回了所有的被激活的Source BufferList。
MediaSource.readyState:一個只讀屬性值,返回了一個表示當(dāng)前媒體源的狀態(tài),有三種值:closed表示媒體尚未打開或已經(jīng)關(guān)閉,open表示媒體已經(jīng)打開,ended表示媒體已經(jīng)播放完成。
MediaSource.duration:獲取或設(shè)置當(dāng)前所播放的媒體源的長度。
4.1.3 事 件
MediaSource.onsourceclose:媒體源關(guān)閉的時候會觸發(fā)。
MediaSource.onsourceended:媒體源結(jié)束的時候觸發(fā)。
MediaSource.onsourceopen:媒體源打開的時候觸發(fā)。
4.1.4 方 法
MediaSource.addSourceBuffer():通過一個MIME類型創(chuàng)建一個新的Source Buffer,并且添加到Source BufferList。
MediaSource.removeSourceBuffer():從Source BufferList中刪除一個Source Buffer。
MediaSource.endOfStream():結(jié)束一個媒體流。
MediaSource.setLiveSeekableRange():設(shè)置用戶可以拖動的時間線的范圍。
MediaSource.clearLiveSeekableRange():清空用戶可拖動的時間線的范圍。
4.1.5 靜態(tài)方法
MediaSource.isTypeSupported():輸入MIME類型,返回瀏覽器是否支持該類型。
對于不支持Media Source Extension的瀏覽器,或找不到適合的編碼格式的瀏覽器,該項目將視頻轉(zhuǎn)換為rgb數(shù)據(jù),提供給Html的Canvas,通過Canvas來繪制視頻幀,實(shí)現(xiàn)視頻渲染。
該項目通過createImageData來創(chuàng)建幀,然后通過對canvas的data屬性賦值的方式在圖片上繪制像素,最后,通過drawImage來實(shí)現(xiàn)在Canvas上畫圖。最終實(shí)現(xiàn)視頻的渲染。
WebGL是一套瀏覽器端的硬件圖形API,它提供了類似于OpenGL、DirectX的功能,使通過JavaScript運(yùn)行游戲成為可能。
WebGL渲染視頻可以加速視頻的渲染速度,然而不同于Native平臺上的硬件渲染,這里的WebGL渲染,由于目前WebGL只能提供有限的圖形API,所以只能作為視頻像素格式的轉(zhuǎn)換器,但可以提供由Pixel Shader實(shí)現(xiàn)的硬件運(yùn)算的更高效率的視頻濾鏡。
WebSocket在瀏覽器與服務(wù)器之間傳輸流媒體[18],通過HTTP或RTMP協(xié)議在推流端傳輸流媒體。
WebSocket協(xié)議:
客戶端發(fā)送握手請求,服務(wù)端返回握手回復(fù),握手協(xié)議大致如下:
客戶端發(fā)送HTTP請求,并帶有Upgrade、Connection、Sec-WebSocket-Key、Sec-Web Socket-Protocol、Sec-WebSocket-Version這五個字段,服務(wù)端會根據(jù)Sec-*的三個字段,確定WebSocket的協(xié)議版本和加密Key。
客戶端請求:
GET /chat HTTP/1.1
Host:server.example.com
Upgrade:websocket
Connection:Upgrade
Sec-WebSocket-Key:x3JJHMbDL1EzLk9 GBh XDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
服務(wù)端返回:
HTTP/1.1 101 Switching Protocols
Upgrade:websocket
Connection:Upgrade
Sec-WebSocket-Accept:HSmrc0sMlYUkAGmm 5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
Connection必須設(shè)置Upgrade,表示客戶端希望連接升級。
Upgrade字段必須設(shè)置WebSocket,表示希望升級到WebSocket協(xié)議。
Sec-WebSocket-Key是隨機(jī)的字符串,服務(wù)器端會用這些數(shù)據(jù)來構(gòu)造出一個SHA-1的信息摘要。把“Sec-WebSocket-Key”加上一個特殊字符串“258EAFA5-E914-47DA -95CA -C5AB0 DC 85B11”,然后計算SHA-1摘要,之后進(jìn)行BASE-64編碼,將結(jié)果作為“Sec- WebSocket -Accept”頭的值,返回給客戶端。如此操作,可以盡量避免普通HTTP請求被誤認(rèn)為WebSocket協(xié)議。
Sec-WebSocket-Version表示支持的WebSocket版本。RFC6455要求使用的版本是13,之前草案的版本均應(yīng)當(dāng)棄用。
Origin字段是可選的,通常用來表示在瀏覽器中發(fā)起此WebSocket連接所在的頁面,類似于Referer。但是,與Referer不同的是,Origin只包含了協(xié)議和主機(jī)名稱。
其他一些定義在HTTP協(xié)議中的字段,如Cookie等,也可以在WebSocket中使用。
電腦網(wǎng)頁端播放效果如圖5所示。
圖5 電腦上播放視頻的效果
IOS手機(jī)端播放效果如圖6所示。
圖6 手機(jī)上播放的效果
該項目實(shí)現(xiàn)了手機(jī)上媒體的編碼,基于Web-Socket的流媒體傳輸,以及網(wǎng)頁上通過JavaScript將流媒體解碼,并且通過Media Source Extension的API將解碼的數(shù)據(jù)發(fā)送給瀏覽器等功能。主流的手機(jī)都可以流暢地編碼解碼視頻,并且?guī)捯笙鄬τ贖LS協(xié)議更低。
但是壓縮算法還有一些優(yōu)化空間,如果能進(jìn)一步優(yōu)化壓縮算法,就能讓用戶在帶寬更低的環(huán)境中流暢播放視頻,并減小服務(wù)器的帶寬壓力;此外該項目還缺乏對足夠多的視頻格式的支持,如果支持更多的格式,推流端的格式選擇就會更加自由。