隨着無線網絡和智能手機的發展,智能手機與人們日常生活聯系越來越緊密,娛樂、商務應用、金融應用、交通出行各種功能的軟件大批涌現,使得人們的生活豐富多彩、快捷便利,也讓它成為人們生活中不可取代的一部分。其中,多媒體由於其直觀性和實時性,應用范圍越來越廣,視頻的解碼與播放也就成為研究的熱點。
H.264標准技術日漸成熟,采用了統一的VLC符號編碼,高精度、多模式的位移估計,基於4×4塊的整數變換、分層的編碼語法等。這些措施使得H.264算法具有很高的編碼效率,在相同的重建圖像質量下,能夠比H.263節約50%左右的碼率。而且H.264的碼流結構網絡適應性強,增加了差錯恢復能力。正好適用於帶寬受限,差錯率高的無線網絡。
本文結合ffmpeg開源代碼中的解碼方法,采用多線程接收數據包,多級緩沖數據,接收和解碼並行雙線程操作等方法,緩解了由於傳輸的數據量大、速度快而導致的數據堵塞、解碼出錯、視頻畫面遲鈍、延遲等問題。使得h.264視頻的傳輸速度快,穩定性好。最終實現了pc端到android手機端的視頻傳輸,以及在android手機端的解碼播放。
該技術可以應用於視頻會議、視頻監控等應用中。
一、 H.264視頻傳輸播放系統的總體結構
H.264視頻傳輸播放系統分為服務器端和客戶端2個部分,服務器端負責讀取H.264的視頻數據,並且以RTP/RTCP格式打包發送給客戶端,並且接受客戶端的反饋,對傳輸速度等作相應的控制。Android手機客戶端主要完成從服務器端接收實時碼流數據,經過緩沖,進行視頻數據解析,然后送去解碼,最后在手機上顯示播放。服務器端采用c語言實現,客戶端主要用java語言實現。
二、關鍵技術及其實現
1.基於RTP協議的打包及解包
(1)單個NAL打包
H.264NALU單元常由[start code][NALU header][NALU payload]三部分組成,其中start code 用於標志一個NALU單元的開始,必須是“00000001”或者是“000001”,打包時去掉開始碼,把其他數據打包到RTP包就可以了。
(2)分片打包
由於1500個字節是IP數據報的長度的上限,去除20個字節的數據報首部,1480字節是用來存放UDP數據報的。所以當一幀中的字節數超過這個數值時,我們必須將其分片打包。而且UDP在傳輸的過程中也要由包頭開銷,所以將RTP包的最大字節數定位1400字節。
需要分片的包格式有所區別,首先說明下分片的格式:
FU指示字節有以下格式:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
FU指示字節的類型28,29表示FU-A和FU-B。NRI域的值必須根據要分片的NAL單元NRI的值設置。
FU頭的格式如下:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+---------------+
S:開始位(1bit),當設置為1,開始位指示分片NAL單元的開始。第一個分片包設為1,其他的分片設置為0。
E:結束位(1bit),當設置為1,結束位指示分片NAL單元的結束,即,FU荷載是最后分片時設置為1,其他時候設置為0。
R:保留位(1bit),必須設置為0。
Type:5bit
(3)打包和解包的流程分析:
打包:

分片時詳細說明:
①第一個FU-A包的FU indicator 是這么設置的:F=NALU頭中的F,NRI=NALU頭中的NRI,Type=28 FU header: S=1,E=0,R=0,Type=NALU頭中的Type;
②中間的FU-A包的FU indicator是這么設置的:F=NALU頭中的F,NRI=NALU頭中的NRI,Type=28 FU header: S=0,E=0,R=0,Type=NALU頭中的Type;
③尾FU-A包的FU indicator是這么設置的:F=NALU頭中的F,NRI=NALU頭中的NRI,Type=28 FU header: S=0,E=1,R=0,Type=NALU頭中的Type。
解包:
下面我們針對RTP解包時對待分片進行分類的代碼實現做分析:
byte startBit=(byte)(recbuf[13]&0x80); byte endBit=(byte)(recbuf[13]&0x40);
①如果,startBit==-128,這包是分片的首包。
NalBuf[4]=(byte) ((recbuf[12]&0xE0)+(recbuf[13]&0x1F)); 這句用於重建組合NAL單元類型
②如果(startBit==0)&&(endBit==0),這包是分片的中間部分。
③如果 endBit==64 ,這包是分片尾部。
當分類清楚,就可以對各部分做相應的處理,如圖中分析的那樣。
2.碼流管理機制
(1) 碼流的接收。在發送端碼流發送很快的情況下,由於接收端不僅要接收碼流,還要進行分析,解碼,這個處理需要一個較長的過程,如果接收端順序執行這個過程的話,會導致無法完整接收發送端的包、出現丟包,由此而帶來的是解碼錯誤、無法正常播放視頻、甚至程序奔潰等嚴重錯誤。針對這個問題我們采取並發的處理機制予以解決。線程並發存在的一個意義就是為了提高運行在單處理器上的速度。在java中我們采用java.util.concurrent包中的執行器(Executor)來管理線程Thread對象。我們創建20個線程,也就是向SingleThreadExecutor提交了20個任務,這些任務將排好隊,每個任務會在下一個任務開始之前運行結束,每個任務都是按照他們被提交的順序,在下一個任務開始之前完成。這樣不僅實現了快速的接收而且還保證了接收到的包順序是正確的。通過這樣的處理后,接收和分析解碼可以被分成兩個部分,我們可以把接收到的數據暫時存放在緩沖區,然后就可以接着去接收下一包數據,不用等着分析、解碼完成后才去接收下一包數據。這樣做大大提高了接收效率,同時避免了丟包問題。
(2) 視頻數據解析和解碼。由於采用了並發的機制,接收到的數據不止一包,所以對接收到的數據應該做怎樣合理的處理,成為我們接下來的難點。我們需要保證的仍然是數據包的順序,還且每次只能處理一包,這里涉及到一個線程之間的協作問題。我們采用消費者生產者這種線程協作模式來做處理。我們將從存放數據的緩沖區中按順序取到的包經過分析后放入另外一個緩沖區,通知解碼程序可以進行從此緩沖區中獲得數據解碼,然后分析視頻數據的程序進入等待。解碼完成后,通知分析視頻數據的程序繼續進行視頻數據分析,同時解碼程序又進入等待。兩個程序在執行和等待中交替進行。

(3) 多級緩沖機制。上面我們也提到了幾個緩沖,總結如下。
①接收后存放數據的緩沖,由於服務器端源源不斷的實時碼流,和采用了並發機制后帶來更大量的數據,我們不可能馬上處理完,所以必須設置一個緩沖區。
②接收端和處理端之間的緩沖,由於網絡不穩定,接收到的數據可能會有時快有時慢,這直接會造成解碼的不穩定和視頻播放的不連續,所以在此設置一個緩沖,起到一個平滑,過渡的作用,這個緩沖區既要存放接收到大量的碼流還要為視頻數據分析提供數據,有個寫讀入和讀出的過程,所以我們使用先入先出的隊列Queue容器來做緩沖區。
③解析和解碼過程之間的緩沖,由於在此過程中的數據量相較而言不是很大,而這個獲取數據的速度直接影響了解碼的速度,所以我們要用一個高效的緩沖區來擔當此時的緩沖作用,由於stack是由系統自動分配,所以速度比較快,所以我們就在棧上分配一個數組用於存儲即可。
④解碼后到播放之間的緩沖,這個緩沖區同樣除了起到使播放視頻連續穩定的作用外,主要就是用來顯示圖像,還可以對視頻圖像進行一些處理工作,平滑,濾波等。
3.解碼和播放的實現
H.264解碼是移植了ffmpeg 中的H.264解碼部分到Android,並且了深度刪減優化。界面部分,文件接收處理以及視頻顯示都是用java做的,底層的視頻解碼部分則使用C來做從而滿足速度的要求。H.264碼流分割NAl(接受到視頻數據的復原工作)是在java層做而沒有分裝到c中,是因為每次送的數據會受到限制,如果送的數據量大,底層可能會一次解碼好幾幀視頻,但是到界面層只能顯示一幀,造成丟幀。如果每次送的數據量較少,就會使得多次底層調用但並沒有進行實質解碼的現象發生,所以盡管這樣做耦合度差些,速度慢些,但是綜合考慮還是將數據分析工作放在java層完成。

我們將解碼后的視頻數據用bitmap顯示,draw到surfaceView的方法顯示到手機屏上,由於有些手機不支持rgb24但幾乎所有手機都支持rgb565,所以解碼后返回的是rgb565數據。
4.程序流程功能架構
三、結束語
本文完整的設計並實現了從pc端到android手機端的H.264視頻傳輸與解碼播放功能。詳細的分析了實現中的技術要點和難點,詳細分析了rtp打包,解包的流程,針對發送數據快而處理速度慢的問題,采用多線程並發機制予以解決,面對大量,而且不穩定的數據包,針對各個環節的特點,設置了多級緩沖。使得視頻播放更加流暢、平穩。對於分析和解碼的先后次序問題,采用線程協作的思想,利用消費者,生產者模式,保證了視頻數據的時序性。另外對於解碼部分,則利用現有解碼方法進行平台移植,合理處理c層和java層的分工,並以實現了這個完整的功能。在網絡狀況好的情況下,android手機端視頻播放延時短,播放流暢,平穩。本文技術研究可以運用到視頻播放的各個應用中,有着很強的實用價值。