Android流媒體開發之路三:基於NDK開發Android平臺RTSP播放器
基於NDK開發Android平臺RTSP播放器
最近做了不少android端的開發,有推流、播放、直播、對講等各種應用,做了RTMP、RTSP、HTTP-FLV、自定義等各種協議,還是有不少收穫和心得的。我這邊做,核心模組和核心程式碼部分,都是基於NDK,用C++開發的,然後將so動態庫,在Android java環境中使用,這個既能保證核心部分的程式碼效能,也能最大程度複用之前寫的流媒體相關的大量程式碼,實踐證明,這樣的程式架構,還是很有效的。這篇文章裡,我打算描述一下我對於開發Android端RTSP播放器的程式框架,和設計思路,有相關需求的,希望能借此擴充套件下思路。
邏輯思路
首先,既然是RTSP播放器,那必然要做RTSP的解析,這部分對我來說已經是非常熟悉了。我常用的RTSP解析程式碼,一般是基於Live555和FFMpeg的庫,通過呼叫相關的介面,來實現RTSP客戶端協議的資料接收,然後再做資料分析。這兩種方式,各有適合的應用場景,相容性也各有優劣,要根據具體專案具體選擇。除非是整套都是自己做的RTSP伺服器和RTSP客戶端,否則我一般都是用他們兩個,為的是最大程度的相容第三方RTSP伺服器,比如各種網路攝像頭、各種裝置、以及其他公司自己寫的RTSP server等等,具體就不說了,做過類似的估計都清楚。當然,資料接收是需要做緩衝的,否則會卡頓,這個需要自己來做。
其次是解碼,對於這點,為了保證記憶體使用效率,以及避免JNI呼叫開銷,最好是在c++層來做。這個可以基於FFMpeg解碼器或者MediaCodec解碼器來寫,不過要注意後者對Android的版本有要求。解碼後需要對資料進行緩衝,按照時間戳進行排隊。這個不管是直播還是點播,都需要做佇列,否則同樣會出現卡頓、音視訊不同步,以及其他的情況,這個是非常重要的一點。
最後是渲染,這個可以選擇在c++層繪製,或者回調上層,交給EGL來進行繪製,後者需要編寫EGL程式碼,建立EGL surface,在渲染執行緒中進行繪製。
總結一下:
- 連線RTSP伺服器,接收資料並進行分析,提取視訊和音訊資料
- 對編碼資料,比如h.264、aac等,進行解碼,還原原始資料
- 把原始資料,進行繪製或回撥上層,opengl繪製
程式框架
結構示意圖:
c++部分是主要程式碼,java層只需要做封裝和呼叫操作即可
框架圖:
Android c++工程編譯
本人的交叉編譯平臺是ubuntu 64bit,編譯成動態庫,然後讓APP通過JNI來呼叫,跟其他程式的編譯方式差不多。當然,首先需要系統內佈置好NDK編譯環境。Google提供了完整的編譯工具鏈,也包括SDK,下載地址在這裡:“NDK Downloads”。我在之前的一篇文章裡也寫了這部分,可以參考一下:"NDK開發Android端RTMP直播推流程式"。
1. 編譯依賴庫
對第三方庫,我通常都是首先嚐試NDK工具鏈的方式來編譯,這樣的好處,一個是工作量小,能直接使用專案的makefile,當前前提是先配置好編譯環境,指定好交叉編譯工具;另一個是不同的庫的編譯方式是相同的,很容易處理。這裡以FFMpeg為例
第三方庫準備好,這樣就行了。
2. 編寫程式主體的Android.mk檔案
程式主體,直接寫Android.mk,程式碼和預編譯條件,連結引數等自己都清楚,也很方面控制編譯輸出。之前有篇文章裡也有簡單介紹,可以參考"NDK開發Android端RTMP直播推流程式",具體的語法可以參考官方網站Android Developer。
寫好後,呼叫ndk-build指令碼編譯,OK。
需要注意的地方和部分程式碼
1.在寫JNI封裝介面的時候,一定要注意jni型別和c++型別的對應關係,尤其是注意返回值。本人就曾經因為jni介面返回值,和程式碼實現時候的不對應,從而導致android app呼叫介面的時候異常退出
其中一個介面對應的JNI c語言程式碼是這樣的:
2.在按照時間戳做播放佇列的時候,為了音訊和視訊的同步,必須注意音訊和視訊各自的時間戳,需要按照真實的時間進行還原。而當發現視訊和音訊不同步的時候,或者因為緩衝問題,導致視訊需要丟包的情況下,需要及時調整音訊播放佇列的基準時間戳,避免音視訊不同步的情況出現。同時,這樣做也能避免長期累積造成的計算誤差。
3.由於是手機端或者嵌入式裝置端進行播放,因為需要考慮到裝置效能不足的情況。這個時候,如果碼流較大而裝置來不及解碼或者渲染,必須及時拋棄視訊資料,否則會造成記憶體溢位,程式崩潰。同時在拋棄資料的時候,要考慮到關鍵幀的問題,也就是如果發生了拋幀,那麼整個GOP的資料都應當放棄,除非是有冗餘編碼等編碼技術,以此來避免花屏的情況,以及第2點列出的音視訊同步問題。解決這幾點,基本上就可以了。
4.當需要回調給java層,讓EGL來渲染畫面時,需要用到c++回撥Java的技術手段。首先寫好java層封裝的回撥介面,然後在c++程式碼中,通過JNI環境,獲取到java層封裝的類jclass物件和方法。注意在呼叫GetMethodID時,需要寫正確函式的簽名,例如我在java層的函式是
void OnVideoDataBuf(int width, int height, byte[] frameBuf)
那麼對應的簽名是“(II[B)V”
以下是呼叫例子:
注意最後需要DetachCurrentThread()。
執行效果
在手機端執行畫面:
haibindev.cnblogs.com,合作請聯絡QQ或微信。(轉載請註明作者和出處~)