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