天天看點

Android5.1多屏互動(Miracast、Wifi-Display)發送端和接收端實作要點事先說明:miracast接收端實作:

事先說明:

授人以魚不如授人以漁,這套要點并不是直接扔你一套coding告訴你內建就行了,而是我這幾周在內建miracast功能時遇到的關鍵節點,重點在接收端,基本不貼源碼,但是如果你認真把流程看下去,可能會獲得更多的收獲,幫到你的話麻煩評論點贊,話不多說,直接上幹貨:

1.“設定”應用(發送端需要):

大家手上不同的源碼,在這裡可能有不同的表現。在Android5.1的安卓原生設定裡面,預設在“顯示”欄是有投射螢幕這部分的選項的,該界面能完成開關,裝置搜尋和點選發起連接配接等功能,部分廠商的platform可能會對其進行屏蔽,需要去手動打開。//--現在看來此步驟非必須,這部分其實可以參照源碼單獨寫成應用APK,另外該界面是否能正常取決于下一項的Miracast Flag使能。

2.Miracast Flag使能(兩端均需要):

路徑:platform/android/frameworks/base/core/res/res/values/config.xml

該檔案包含:”config_enableWifiDisplay"選項,是一個功能總開關,需要将其置為true才能正常使用,原理是DisplayManager在初始化的時候會根據該選項的值進行wifiDisplay的相關初始化操作。

3.Miracast 發送端實作:

這部分不是我要分析的重點,因為這個是Android每個版本都攜帶的功能,我在完成了上述兩點的開關之後,便能夠正常使能。相關詳細步驟可參見網絡資料。其步驟和接收端一一對應,大概也是RTSP握手->RTP建立連接配接->media codec->RTP推送。唯一跟平台相關的内容是media codec,這部分在stagefright裡面可能需要進行一個适配工作。

miracast接收端實作:

前言:衆所周知谷歌爸爸在Android4.2.2之後砍掉了接收端的功能(不明覺厲),是以要內建這部分功能,初級手段是從AOSP上面搞到老代碼進行移植,當你讀完下文并閱讀源碼對流程非常清楚之後,相信自己寫也不算難事。

4.WifiDisplay模式更改:

platform/android/frameworks/base/services/java/com/android/server/display/WifiDisplayController.java
private void updateWfdEnableState()
-                wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE);
+                wfdInfo.setDeviceType(WifiP2pWfdInfo.SOURCE_OR_PRIMARY_SINK);//雙模式
+                wfdInfo.setDeviceType(WifiP2pWfdInfo.PRIMARY_SINK);//單接收模式
           

上述位置是第一個開關,安卓預設的該位置隻賦予了source端功能,需要切換模式,切換模式後,裝置可被發現。(不依賴上層設定)

5.WIFI p2p連接配接成功

需要确認你的裝置wifi子產品支援P2P(直連)模式且功能正常,則發送端在“投射螢幕”中發起的連接配接能被你的裝置正常響應,此時可用APK對該連接配接建立事件進行監聽(監聽方式與設定中的source端監聽連接配接建立代碼基本一緻,利用全局廣播,Action由WifiP2pManager枚舉),或者在上述檔案WifiDisplayController.java中進行監聽,目的隻有一個:擷取建立WIFI P2P對象的IP位址和端口,進入下一個步驟

6.RTSP通信階段:

自這個階段開始,需要porting Android4.2.2中的相關源碼,位置與source的目錄一緻,均在libstagefright内,核心檔案主要是RTPSink.cpp、TunnelRender.cpp、WifiDisplaysink.cpp。如果目前版本比較新(以我使用的5.1為例),那麼上述sink端的依賴庫,即stagefright内的foundation及ANetworkSession.cpp這一套,如果直接使用新版本的依賴,可能會産生相容性問題,建議将4.2.2中的這部分代碼也全部打包過來編譯形成自己的庫依賴。(當然解決相容性問題是上策,但是比較費時費力)

自拿到上個步驟的位址和端口參數,就可以傳入WifiDisplaysink.cpp的初始化函數,進行RTSP連接配接和互動了。

先提前推薦WIFI聯盟出品的官方spec文檔,我單單是查閱都受益匪淺,如果全部都讀懂了,估計自寫協定完全不成問題,文檔标題《Wi-Fi_Display_Technical_Specification_v2.1_0》,随便一搜就能下。

幾個可能需要劃重點的互動階段:

RTSP的互動指令是标号M1~M16的16種指令,其中,前七種為前期握手指令(必須),後續的是控制和參數設定相關。

M1~M2為固定的前置互動,略過

M3~M4為參數确認握手,包括最核心的音視訊編碼傳輸格式(wfd_video_formats,wfd_audio_codecs),音頻傳輸協定(UDP/TCP),以及接收端用于接收的網絡端口(wfd_client_rtp_ports),RTCP控制消息接收端口(可選,一般為接收端口+1)。

此處若有格式錯誤或者參數不支援,RTSP連接配接将被單方面斷開。

這個參數怎麼傳?懶人方法——從github上面找代碼抄之,但是可能會出問題。特别是video格式(基于H264),還是建議閱讀spec文檔,格式裡面的每一個bit都有它的作用,為什麼不去弄懂它呢?

M5~M6為最終的确認階段:發送端會發送setup指令(攜帶自身的資料發送端口,以及RTCP控制消息傳輸端口),此時接收端就可以進入下一個階段——RTP連接配接(RTPSink.cpp),連接配接建立以後發送最後的M7 Play指令,該檔案便完成使命。

一般來說,不管後面如何,這個步驟走完起碼發送端已經會顯示“已連接配接”狀态了,其中M1~M7的正确互動是關鍵,spec中有詳細的正确互動舉例,同時Tcpdump+wireshark也是分析除錯利器,不要忘記使用。

7.RTP資料傳輸階段

本階段源碼位于RTPSink.cpp,本例使用的是UDP連接配接,是以可以看到這裡根據上個步驟傳入的參數,調用ANetworksession建立了udp的連接配接用戶端。

其實udp協定本身,因為不是實時的、帶握手的,是以一般來說隻需要知道自身端口這一個參數即可進行binder,建立一個udp接收線程,但是為了資料的純粹和正确,此處使用上個步驟得到的遠端位址和RTP端口進行了connect操作,對端口接收到的資料進行了來源過濾。

随後ANetworksession的Threadloop中,調用udp session的readmore讀取端口資料包,以AMessage的方式發送給RTPSink進行解析(parseRTP和parseRTCP),驗證完標頭資訊,拿到純粹的MPEG-TS資料包後(資料包的格式取決于之前RTSP M4的握手互動資訊),初始化TunnelRenderer進行播放。

8.播放階段:

這部分就特别簡單了,TunnelRender将拿到的資料包封裝成Stream Source類型,之後擷取MediaplayerService進行播放,三大步驟(setDatasource,setVideoSurfaceTexture,start)(此處關于surface的設定,可以由最開始的應用APK畫出的surfaceview一步步傳下來,也可以在initPlayer中走==NULL的邏輯,自己生成一個)。到這裡,螢幕應該就會出現發送端的投屏畫面和聲音啦!

總結:

懷着僥幸心理去github上面去找輪子,最後天不遂人意,輪子跑不起。最好笑的是一篇文章标題是教你內建miracast接收端,點進去一看,大意通過linux關鍵字搜尋把設定裡面被屏蔽的選項恢複了,一看Yo!原廠把功能做好了,美滋滋交差,告訴大家腦子要靈活,說不定人家已經做好了呢。

看得我苦笑不得,後來自己把流程都掰碎了,網絡抓包抓了上百個版,外加官方出品SPEC,終于把功能內建實作了。動手造輪子不易,但是收獲肯定很多,是以我這邊實作的demo,就不粘貼出來了,各位加油。