引言
在局域網內部實時傳輸視頻已經得到廣泛應用。現在用以傳輸視頻的局域網大多數是有線局域網,因為有線局域網技術成熟,傳輸速度快,穩定性好。但是視頻數據量大,有線網絡也會出現工作不穩定,引起數據堵塞,時間久了會導致嚴重的延遲現象;如果工作的環境不固定,要求移動性,那麼就要采用無線網絡,如今無線網卡的工作隨環境的變化而變得不穩定,這樣會導致視頻傳輸的質量大幅度下降,容易引起畫面的重影、抖動、花屏等現象。本文針對不同的局域網,提出一種通用的實時視頻傳輸的解決方案,使用VC++自封裝的Windows VFW SDK軟件開發包進行二次開發,通過Divx編解碼,按照制定的傳輸策略,能夠有效地解決由於網絡的局部不穩定導致的視頻圖像重影、抖動、花屏等的問題。
局域網中實時視頻傳輸存在的問題
為了在局域網上有效的、高質量的傳輸視頻流,需要多種技術的支持,包括視頻的壓縮、編碼技術,應用層質量控制技術等等。
網絡的帶寬是有限的,所以需要壓縮傳輸視頻圖像,MPEG-4被廣泛的應用於網絡環境下的實時視頻傳輸,因為MPEG-4具有:可以達到很高的壓縮比;具有靈活的編碼和解碼復雜性;基於對象的編碼方式,允許視頻、音頻對象的交互;具有很強的容錯能力等優點。本文采用Divx編解碼器對視頻進行編碼、壓縮,實際上Divx=(視頻)MPEG-4+(音頻)MP3。
應用層質量控制技術現在采用的是RTP/RTCP協議,以確保視頻流在網絡中低時延、高質量地傳輸。RTP數據傳輸協議負責音視頻數據的流化和負載,RTCP負責RTP數據報文的傳輸控制。此協議是通過客戶端(接收方)反饋網絡的狀況,服務器端(發送方)來調整信息采集、發送的速度和壓縮率。但是,對於圖像采集速度固定,需要軟件進行壓縮、解壓,調整采集的速度會引起采集的數據來不及壓縮而直接丟棄,調整編碼器的壓縮率需要重新設置編碼器的參數,重啟編碼器,相應的解碼器也要調整,這個過程中需要很長的時間,達不到實時的要求。所以本文沒有采用RTP/RTCP協議,而是從發送端出發,實時判斷網絡狀況,采用“停等”策略進行實時傳輸。
網絡通信有兩種協議TCP和UDP,UDP更適合於網絡環境下的視頻傳輸,但是它不提供檢錯和糾錯功能,一旦網絡出現堵塞時,大量的數據報文會丟失。對於Divx編解碼技術,是以幀為單位進行編解碼的,分為關鍵幀和非關鍵幀。在傳輸過程中,由於壓縮率比較高,只要一幀中錯一比特位,將影響其它幾百甚至幾千的比特位,直接造成圖像的模糊、花屏等現象。只有等到下一次關鍵幀的到來才有可能恢復圖像的清晰。為了保證傳輸的正確性,自己需要在應用層制定協議。如此一來,UDP的優勢蕩然無存。所以本文選擇使用TCP來進行網絡通信。綜合使用VFW技術、流媒體技術,輔助以“停等”控制策略,較好的解決局域網中實時視頻傳輸容易引起的重影、抖動、花屏的問題。
實時視頻傳輸實現
為了達到視頻傳輸的實時性,總的思想是最少的發送冗余信息,最大程度上發送最新的視頻。
局域網實時視頻傳輸采用服務器/客戶機模式,利用VC++實現。其工作流程如圖1所示。
圖1 實時視頻傳輸工作流程
視頻采集采用AVICap從視頻采集卡捕獲視頻圖像,得到的是位圖型式的視頻幀,然後用Divx編碼器進行壓縮,通過Winsock實現壓縮後的視頻數據在局域網中的實時傳輸,接收完的數據交給Divx解碼器解壓,最後實現視頻顯示。
在VC++中,采用VFW技術,客戶端通過capSetCallbackOnFrame()注冊回調函數,當采集卡采集到一幅圖像後,系統就會自動調用回調函數,然後再回調函數中使用ICSeqCompressFrame()函數進行壓縮。然後再通過Winsock將壓縮後的數據發送到服務器端。服務器端接收完一幀以後,交給ICDecompress()解壓,最後用SetDIBitsToDevice()將圖像顯示出來。
1、視頻幀的組建
視頻采集的數據是位圖型式的視頻幀,Divx編碼器壓縮以後形成以幀為格式的Mpeg4流。Divx解碼器也是以幀的格式解壓。所以提出以幀為單位發送視頻數據流。為了在接收端能夠方便地提取出一幀,提出如圖2所示的格式組建幀。
幀開始標志 幀大小 幀編號 幀類型 幀數據 圖2 視頻幀格式
完整的一幀由5個字段組成,各個字段的意義如下:幀開始標志,標志著一幀地開始,占用4個字節的空間。不妨設為0xffffffff。幀大小,表示整個幀的大小,包括5個字段的大小,占用4個字節的空間。幀編號,表示幀的順序編號,占用4個字節的空間。幀類型,標志此幀是否是關鍵幀,占用1個字節的空間。幀數據,存放壓縮後一幀的完整數據。
2、視頻幀的發送
實時視頻傳輸為了實時,要不斷地將壓縮好的數據發送到接受端。所以在發送端創建一個線程,專門用來發送數據。同時主線程仍然不停的采集數據並進行壓縮。發送線程的工作流程如圖3所示。
圖3 發送線程工作流程
不妨假設創建的線程名為sendThread,核心代碼實現如下:
while(1)
{
isOK=true; //准備就緒
SuspendThread(sendThread); //掛起線程
isOK=false; //線程正在發送數據
int length=frameLength; //待發數據長度
if(length<50000) {//判斷數據是否正常
int n=0;
int sendCount=0;
while(length>0) {
n=send(sock,(char*)imageBuf+sendCount,length,0); //發送數據,
//imageBuf是指針,指向待發數據幀
if(n==SOCKET_ERROR) //網絡出現異常,則退出線程
break;
length-=n;
sendCount+=n;
}
}
}
線程中發送的數據幀是按照上一節中的方法組建好的數據幀。這種方法能夠保證正在發送的當前幀能夠完整地到達接收端。
注意此線程中剛開始或者每當發送完一幀以後,線程就轉到掛起狀態,等待外界喚醒。這個任務由回調函數完成,在回調函數中,判定如果發送線程准備就緒(處於掛起狀態),則進行圖像壓縮,然後喚醒線程發送壓縮完的數據,否則直接跳出,等待下一次調用回調函數,這種策略稱之為“停等”策略,在後面有詳細介紹。
3、視頻幀的接收
接收端最重要的是從接受的數據流中提取出完整的一幀。方法的思想是:首先從數據流中尋找幀開始標志,再從緊挨後面的數據中提取出幀的大小,然後再從接收緩沖區中讀入該幀剩余的數據。再尋找下一幀的開始標志,如此往復。圖4是接收端的工作流程。
同樣接收端創建一個線程專門用來執行數據接收。不妨假設線程名為recThread,核心代碼實現如下:
while(temp!=SOCKET_ERROR)
{
if(!isStart) {//幀數據是否開始,true表示開始
if(endNum>3) //endNum紀錄當前接收未處理的數據
endNum=0;
temp=recv(clisock,(char*)(recBuf+endNum),1000,0);//從緩沖區讀取數據
startPos=serchStr(temp+endNum); //查找幀開始標志
if(startPos!=-1) {
isStart=true;
endNum=temp+endNum-startPos-4;
memcpy(imageBuf,recBuf+startPos+4,endNum); //保存幀數據
}
else{
memcpy(recBuf,recBuf+temp+endNum-3,3);//保存最後三個字節的數據
endNum=3;
}
}
else{
if(endNum<4) {//判定緊跟開始標志的數據,如果小於4表示不能獲得幀大小
temp=recv(clisock,(char*)(recBuf),1000,0); //讀入數據
memcpy(imageBuf+endNum,recBuf,temp);//保存數據
endNum+=temp;
if(endNum<4)
continue;
frameSize= *((int*)imageBuf);//獲得幀大小
if(frameSize<500 || frameSize>50000) {//異常處理(幀大小非法)
isStart = false; //丟棄數據重新查找幀開始標志
endNum = 0;
continue;
}
frameSize-=endNum+4;
}
else{
while(frameSize>0&&temp!=SOCKET_ERROR) {//獲得完整幀的剩余數據
temp=recv(clisock,(char*)(imageBuf+endNum),frameSize,0);
endNum+=temp;
frameSize-=temp;
}
if(frameSize<=0) {//幀結束置位,解壓
isStart=false;
endNum=0;
deCompress();//判斷數據的有效性,調用ICDecompress進行解壓
}
}
}
}
以上程序執行的結果是將完整的一幀(除幀開始標志)保存在imageBuf中。
4、“停等”控制策略
如果局域網通信速率很高,而且工作穩定,則按照以上說的方法進行實時視頻傳輸,不需要任何控制策略,就可以達到非常好的效果。但是在很多情況下,網絡會出現異常,這樣會導致數據傳輸率明顯下降,造成發送端數據積壓,等待發送的數據不能正常發出去。此時就要采取一定的策略來控制發送端,以達到實時性的要求。
上文發送程序中,變量isOK是用來表示發送端當前幀有沒有發完,如果發完則置為true,同時也表示發送端准備就緒,可以繼續發送數據,否則為false。那麼可以用isOK來通知視頻采集和壓縮線程,如果isOK為true,則可以采集視頻並且壓縮,然後喚醒發送線程繼續發送新來的幀數據,否則一直等待,直到網絡可以繼續發送數據(isOK為true)。當然,視頻采集一直不停的進行,那麼當網絡發生數據堵塞時,只要不讓編碼器進行壓縮則可解決;當網絡恢復正常時,繼續進行壓縮傳輸,換句話說,當網絡發生堵塞時,直接拋棄等待發送的幀,保證一旦網絡恢復時,發送最新的壓縮幀。當然要保證一旦有一幀開始發送,就要將其完全發出。
按照這樣的“停等”策略進行實時視頻傳輸,只會帶來一個問題:當網絡質量差時,接收端畫面中的移動目標會出現瞬間移動的現象。但是這種策略會保證不會出現重影,抖動,花屏等現象。
結論
本文提出的實時視頻傳輸方案在100M的局域網、10M局域網和11M無線局域網中進行了測試。測試時讓一個目標在鏡頭前(發送端)移動,觀察接收端視頻的顯示。在不同的局域網中進行了多次測試,每次測試時間從10分鐘到30分鐘不等,並且改變目標的運動速度進行實驗。最後將數據匯總,得出統計結果。測試結果如表1所示。
表1 不同局域網下的測試結果
劇烈運動 正常運動 緩慢運動 100M局域網 圖像清晰,很流暢 圖像清晰,很流暢 圖像清晰,很流暢 10M局域網 偶爾出現停頓,丟幀率1%左右 圖像清晰,人眼感覺流暢 圖像清晰,很流暢 11M無線局域網 經常出現停頓,丟幀率5%-6% 經常出現停頓,丟幀率2%-3% 偶爾出現停頓,丟幀率1%左右
其中,
注:11M無線網卡是通過USB1.0接口和PC機連接的,如果采用USB2.0接口效果會更好。
從實際測試的結果看,效果是良好的,除了出現瞬間移動外,圖像能夠保持清晰,消除了由於網絡質量差而導致的重影、抖動等現象,對於不同的局域網都能滿足實時傳輸的要求。