關鍵詞客戶/服務器;實時視頻傳輸;Divx
引言
在局域網內部實時傳輸視頻已經得到廣泛應用。現在用以傳輸視頻的局域網大多數是有線局域網,因為有線局域網技術成熟,傳輸速度快,穩定性好。但是視頻數據量大,有線網絡也會出現工作不穩定,引起數據堵塞,時間久了會導致嚴重的延遲現象;如果工作的環境不固定,要求移動性,那么就要采用無線網絡,如今無線網卡的工作隨環境的變化而變得不穩定,這樣會導致視頻傳輸的質量大幅度下降,容易引起畫面的重影、抖動、花屏等現象。本文針對不同的局域網,提出一種通用的實時視頻傳輸的解決方案,使用VC++自封裝的WindowsVFWSDK軟件開發包進行二次開發,通過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所示。
視頻采集采用AVICap從視頻采集卡捕獲視頻圖像,得到的是位圖型式的視頻幀,然后用Divx編碼器進行壓縮,通過Winsock實現壓縮后的視頻數據在局域網中的實時傳輸,接收完的數據交給Divx解碼器解壓,最后實現視頻顯示。
在VC++中,采用VFW技術,客戶端通過capSetCallbackOnFrame()注冊回調函數,當采集卡采集到一幅圖像后,系統就會自動調用回調函數,然后再回調函數中使用ICSeqCompressFrame()函數進行壓縮。然后再通過Winsock將壓縮后的數據發送到服務器端。服務器端接收完一幀以后,交給ICDecompress()解壓,最后用SetDIBitsToDevice()將圖像顯示出來。
1、視頻幀的組建
視頻采集的數據是位圖型式的視頻幀,Divx編碼器壓縮以后形成以幀為格式的Mpeg4流。Divx解碼器也是以幀的格式解壓。所以提出以幀為單位發送視頻數據流。為了在接收端能夠方便地提取出一幀,提出如所示的格式組建幀。
幀開始標志
幀大小
幀編號
幀類型
幀數據
視頻幀格式
完整的一幀由5個字段組成,各個字段的意義幀開始標志,標志著一幀地開始,占用4個字節的空間。不妨設為0xffffffff。幀大小,表示整個幀的大小,包括5個字段的大小,占用4個字節的空間。幀編號,表示幀的順序編號,占用4個字節的空間。幀類型,標志此幀是否是關鍵幀,占用1個字節的空間。幀數據,存放壓縮后一幀的完整數據。
2、視頻幀的發送
實時視頻傳輸為了實時,要不斷地將壓縮好的數據發送到接受端。所以在發送端創建一個線程,專門用來發送數據。同時主線程仍然不停的采集數據并進行壓縮。發送線程的工作流程如圖3所示。
不妨假設創建的線程名為sendThread,核心代碼實現
while(1)
{
isOK=true;//準備就緒
SuspendThread(sendThread);//掛起線程
isOK=false;//線程正在發送數據
intlength=frameLength;//待發數據長度
if(length<50000){//判斷數據是否正常
intn=0;
intsendCount=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<500frameSize>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接口效果會更好。
從實際測試的結果看,效果是良好的,除了出現瞬間移動外,圖像能夠保持清晰,消除了由于網絡質量差而導致的重影、抖動等現象,對于不同的局域網都能滿足實時傳輸的要求。