Android5.1多屏互动(Miracast、Wifi-Display)发送端和接收端实现要点

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/shenghuo59/article/details/81981377

事先说明:

授人以鱼不如授人以渔,这套要点并不是直接扔你一套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,就不粘贴出来了,各位加油。

展开阅读全文

没有更多推荐了,返回首页