Miracast概述

Miracast

Miracast是由Wi-Fi联盟于2012年所制定,以Wi-Fi直连(Wi-Fi Direct)为基础的无线显示标准。支持此标准的消费性电子产品(又称3C设备)可透过无线方式分享视频画面,例如手机可透过Miracast将影片或照片直接在电视或其他设备播放而无需任何连接线,也不需透过无线热点(AP,Access Point)。

Wi-Fi Direct

Wi-Fi直连(英语:Wi-Fi Direct),之前曾被称为Wi-Fi点对点(Wi-Fi Peer-to-Peer),是一套无线网络互连协议,让wifi设备可以不必透过无线网络接入点(Access Point),以点对点的方式,直接与另一个wifi设备连线,进行高速数据传输。这个协议由Wi-Fi联盟发展、支持与授与认证,通过认证的产品将可获得Wi-Fi CERTIFIED Wi-Fi Direct®标志。

Wi-Fi Display

Wi-Fi Display是Wi-Fi联盟制定的一个标准协议,它结合了Wi-Fi标准和H.264视频编码技术。利用这种技术,消费者可以从一个移动设备将音视频内容实时镜像到大型屏幕,随时、随地、在各种设备之间可靠地传输和观看内容。

Miracast实际上就是Wi-Fi联盟对支持WiFi Display功能的设备的认证名称,产品通过认证后会打上Miracast标签。

Sink & Source

如下图所示,Miracast可分为发送端与接收端。Source端为Miracast音视频数据发送端,负责音视频数据的采集、编码及发送。而Sink端为Miracast业务的接收端,负责接收Source端的音视频码流并解码显示,其中通过Wi-Fi Direct技术进行连接。

/*** A class representing Wifi Display information for a device* @hide*/
public class WifiP2pWfdInfo implements Parcelable {...public WifiP2pWfdInfo() {}public WifiP2pWfdInfo(int devInfo, int ctrlPort, int maxTput) {mWfdEnabled = true;mDeviceInfo = devInfo;mCtrlPort = ctrlPort;mMaxThroughput = maxTput;}...
}

对于setWFDInfo()方法,我们可以采用反射的方式进行调用:

private void setWFDInfoInner(WifiP2pWfdInfo wfdInfo) {try {Method method = ReflectUtil.getPrivateMethod(mManager.getClass(), "setWFDInfo",WifiP2pManager.Channel.class,WifiP2pWfdInfo.class,WifiP2pManager.ActionListener.class);method.invoke(mManager, mChannel, wfdInfo, new WifiP2pManager.ActionListener() {@Overridepublic void onSuccess() {Log.d(TAG, "setWFDInfo onSuccess:");}@Overridepublic void onFailure(int reason) {Log.e(TAG, "setWFDInfo onFailure:" + reason);}});} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}
}

初始化WifiP2pWfdInfo,并进行如下设置,重要的几个字段详见注释:

public void setWfdInfo() {final WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();// 开启WiFi DisplaywfdInfo.setWfdEnabled(true);wfdInfo.setSessionAvailable(true);wfdInfo.setCoupledSinkSupportAtSink(false);wfdInfo.setCoupledSinkSupportAtSource(false);// 设置设备模式为SINK端(Miracast接收端)wfdInfo.setDeviceType(WifiP2pWfdInfo.PRIMARY_SINK);wfdInfo.setControlPort(WFD_DEFAULT_PORT);wfdInfo.setMaxThroughput(WFD_MAX_THROUGHPUT);setWFDInfoInner(wfdInfo);
}

若希望关闭WiFi Display模式,则直接setWfdEnabled(false)即可:

public void clearWfdInfo() {final WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();wfdInfo.setWfdEnabled(false);setWFDInfoInner(wfdInfo);
}

完成上述步骤后,在发送端的投屏或者投射功能中,应该能搜索到对应的Miracast设备了。

Wi-Fi P2P 连接

在发送端搜索到Miracast设备,并点击对应设备后,就进入到了连接过程。此时Sink端应该会弹出一个[连接邀请]的授权窗口,可以选择拒绝或者接受。选择接受后,若是第一次连接,则会进入到GO协商的过程。

GO协商(Group Owner Negotiation)

GO协商是一个复杂的过程,共包含三个类型的Action帧:GO Req、GO Resp、GO Confirm,经过这几个帧的交互最终确认是Sink端还是Source端作为Group Owner,因此谁做GO是不确定的。那具体的协商规则是怎样的呢?官方的流程图清晰地给出了答案:

首先通过Group Owner Intent的值进行协商,值大者为GO。若Intent值相同就需要判断Req帧中Tie breaker位,置1者为GO。若2台设备都设置了Intent为最大值,都希望能成为GO,则这次协商失败。

那么,如何设置这个Intent值呢?发送端在connect()的时候,可通过groupOwnerIntent字段设置GO的优先级的(范围从0-15,0表示最小优先级),方法如下:

WifiP2pConfig config = new WifiP2pConfig();
...
config.groupOwnerIntent = 15; // I want this device to become the owner
mManager.connect(mChannel, config, actionListener);

PS: 对GO完整协商过程感兴趣的童鞋可以查看Wi-Fi P2P Technical Specification文档的3.1.4.2 Group Owner Negotiation这章

Miracast Sink端的场景为接收端,因此不能通过groupOwnerIntent字段来设置GO优先级。那么还有其他方式可以让Sink端成为GO吗?毕竟在多台设备通过Miracast投屏的时候,Sink端是必须作为GO才能实现的。答案其实也很简单,就是自己创建一个组,自己成为GO,让其他Client加进来,在连接前直接调用createGroup()方法即可完成建组操作:

mManager.createGroup(mChannel, new WifiP2pManager.ActionListener() {@Overridepublic void onSuccess() {Log.d(TAG, "createGroup onSuccess");}@Overridepublic void onFailure(int reason) {Log.d(TAG, "createGroup onFailure:" + reason);}
});

建组成功后我们可以通过requestGroupInfo()方法来查看组的基本信息,以及组内Client的情况:

mManager.requestGroupInfo(mChannel, wifiP2pGroup -> {Log.d(TAG, "onGroupInfoAvailable detail:\n" + wifiP2pGroup.toString());Collection<WifiP2pDevice> clientList = wifiP2pGroup.getClientList();if (clientList != null) {int size = clientList.size();Log.d(TAG, "onGroupInfoAvailable - client count:" + size);// Handle all p2p client devices}
});

GO协商完毕,并且Wi-Fi Direct连接成功的时候,我们将会收到WIFI_P2P_CONNECTION_CHANGED_ACTION这个广播,此时我们可以调用 requestConnectionInfo(),并在onConnectionInfoAvailable()回调中通过isGroupOwner字段来判断当前设备是Group Owner,还是Peer。通过groupOwnerAddress,我们可以很方便的获取到Group Owner的IP地址。

@Override
public void onConnectionInfoAvailable(WifiP2pInfo wifiP2pInfo) {if (wifiP2pInfo.groupFormed && wifiP2pInfo.isGroupOwner) {Log.d(TAG, "is groupOwner: ");} else if (wifiP2pInfo.groupFormed) {Log.d(TAG, "is peer: ");}String ownerIP = wifiP2pInfo.groupOwnerAddress.getHostAddress();Log.d(TAG, "onConnectionInfoAvailable ownerIP = " + ownerIP);
}

受WiFi P2P API的限制,各设备获取到的MAC和IP地址情况如下图所示:

由于在后续RTSP进行指令通讯的时候,需要通过Socket与Source端建立连接,也就是我们需要先知道Source端的IP地址与端口。根据上图,我们可能出现以下2种情况:

  • 情况1:Sink端为Peer,Source端为GO。

这种情况下,Sink端知道Source端(GO)的IP地址,可以直接进行Socket连接。

  • 情况2:Sink端为GO,Source端为Peer。

这种情况下,Sink端只知道自己(GO)的IP地址,不知道Source端(Peer)的IP地址,但此时能获取到MAC地址。

通过ARP协议获取对应MAC设备的IP地址

针对上述情况2,我们需要通过MAC地址获取到对应主机的IP地址,以完成与Source端的Socket连接,比较经典的方案是采用解析ARP缓存表的形式进行。

ARP(Address Resolution Protocol),即地址解析协议,是根据IP地址获取物理地址的一个TCP/IP协议。主机发送信息时将包含目标IP地址的ARP请求广播到局域网络上的所有主机,并接收返回消息,以此确定目标的物理地址;收到返回消息后将该IP地址和物理地址存入本机ARP缓存中并保留一定时间,下次请求时直接查询ARP缓存以节约资源。

在Android上,我们可以通过以下指令获取ARP缓存表:

方法1:通过busybox arp指令

dior:/ $ busybox arp
? (192.168.0.108) at f8:ff:c2:10:e7:62 [ether]  on wlan0
? (192.168.0.1) at 9c:a6:15:d6:e8:f4 [ether]  on wlan0

方法2:通过cat proc/net/arp命令

dior:/ $ cat proc/net/arp
IP address HW type Flags HW address Mask Device
192.168.0.108 0x1 0x2 f8:ff:c2:10:e7:62 * wlan0
192.168.0.1 0x1 0x2 9c:a6:15:d6:e8:f4 * wlan0

剩下的工作就是采用强大的正则表达式解析返回的字符串,并查找出对应MAC设备的IP地址了。

获取Source端RTSP端口号

经过上面的步骤,我们已经拿到了Source端的IP地址,只剩下端口号了。这一步就比较简单了,通过requestPeers()方法获取已连接的对等设备WifiP2pDevice,再获取其中的WifiP2pWfdInfo即可拿到端口号:

mManager.requestPeers(mChannel, peers -> {Collection<WifiP2pDevice> devices = peers.getDeviceList();for (WifiP2pDevice device : devices) {boolean isConnected = (WifiP2pDevice.CONNECTED == device.status);if (isConnected) {int port = getDevicePort(device);break;}}
});

这里由于WifiP2pDevice中的wfdInfo字段为@hide,因此需要通过反射的方式获取WifiP2pWfdInfo。最后通过getControlPort()方法即可拿到Source端RTSP端口号:

public int getDevicePort(WifiP2pDevice device) {int port = WFD_DEFAULT_PORT;try {Field field = ReflectUtil.getPrivateField(device.getClass(), "wfdInfo");if (field == null) {return port;}WifiP2pWfdInfo wfdInfo = (WifiP2pWfdInfo) field.get(device);if (wfdInfo != null) {port = wfdInfo.getControlPort();if (port == 0) {Log.w(TAG,"set port to WFD_DEFAULT_PORT");port = WFD_DEFAULT_PORT;}}} catch (IllegalAccessException e) {e.printStackTrace();}return port;
}

拿到了Source端的IP地址与端口号后,我们就可以建立RTSP连接,建立后续控制指令的通道了,详见下篇博客。

参考

  • https://developer.android.com/guide/topics/connectivity/wifip2p
  • https://developer.android.com/training/connect-devices-wirelessly/wifi-direct

文章转载:

Miracast技术详解(一):Wi-Fi Display

Miracast技术详解(一):Wi-Fi Display相关推荐

  1. Miracast技术详解(三):RTP MPEG2-TS

    Miracast音视频流概述 在上一篇文章中,我们已经成功完成RTSP能力协商与会话的建立,并准备开始音视频流的传输阶段.那么下一步,就是对音视频流进行解析,并将音视频展示给用户的过程.这样整个Mir ...

  2. miracast技术详解

    miracast是什么? Miracast是由Wi-Fi联盟于2012年所制定,以Wi-Fi直连(Wi-Fi Direct)为基础的无线显示标准.支持此标准的消费性电子产品(又称3C设备)可透过无线方 ...

  3. python数据科学课后答案_Python数据科学-技术详解与商业实践-第五讲作业

    作者:Ben,著有<Python数据科学:技术详解与商业实践>.<用商业案例学R语言数据挖掘>.<胸有成竹-数据分析的SAS EG进阶>作者.2005年进入数据科学 ...

  4. 拐道交叉的css3动画,CSS3图片翻转动画技术详解

    CSS动画非常的有趣:这种技术的美就在于,通过使用很多简单的属性,你能创建出漂亮的消隐效果.其中代表性的一种就是CSS图片翻转效果,能让你看到一张卡片的正反两面上的内容.本文就是要用最简单的方法向大家 ...

  5. 高帧率扑克牌识别技术详解一(可用于车牌识别,字符识别,人脸检测,验证码识别等等成熟领域)

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 高帧率扑 ...

  6. “扫五福”的实现原理和技术详解

    前言 年关扫福的号角一吹响,日常社交用语里就多了一句:你有敬业福嘛?今年是集五福的第6年,这场搭载手机的年味仪式,让古早的"福"文化又登上新时代的C位. 不难发现,我们拿着手机,不 ...

  7. 关于《J2ME手机游戏开发技术详解》的问题!

    这里只是对我书中的bug和没有说明白的技术处说明,对于具体的编程问题,我只是点到为止! 对于3D部分的问题,由于我现在工作重点不在这上面,有些问题的回答我感到吃力,也不太想回答,详情请看我的文章. 我 ...

  8. 最新ChatGPT GPT-4 相似匹配Embedding技术详解(附ipynb与python源码及视频讲解)——开源DataWhale发布入门ChatGPT技术新手从0到1必备使用指南手册(一)

    目录 前言 最新ChatGPT GPT-4 相似匹配Embedding技术详解 1. 何为Embedding 2. 相关API 2.1 LMAS Embedding API 2.2 ChatGPT S ...

  9. 【H.264/AVC视频编解码技术详解】十九:熵编码算法(5)——H.264的CABAC(上):语法元素的二值化方法...

    <H.264/AVC视频编解码技术详解>视频教程已经在"CSDN学院"上线,视频中详述了H.264的背景.标准协议和实现,并通过一个实战工程的形式对H.264的标准进行 ...

最新文章

  1. Sqlserver中一直在用又经常被忽略的知识点一
  2. html轮播图速度加快,各位老师,为什么我用jq写的轮播图,移入移出速度会加快...
  3. 15个最佳的代码评审(Code Review)工具
  4. leetcode 200.岛屿数量 c代码
  5. vue 封装组件供全局使用_vue 封装组件的基本操作
  6. oracle protocol=beq 不可用,学习笔记:Oracle数据库坏块 深入研究obj$坏块导致exp/expdp不能执行原因...
  7. matlab中quat2angle,RPY_Euler_Quaternion_AngleAxis角度转化:Matlab、Python、Halcon版本
  8. js中如何删除json对象的某一个选项
  9. 前端性能优化之图像优化原理
  10. Web开发者不可不知的16条原则
  11. android 百叶窗动画,android 幻灯片效果之百叶窗
  12. 0基础学python要多久-27岁0基础自学Python,多久可以找到工作?
  13. docker 重启gitlab_gitlab从入门到绝望
  14. 美国工程管理计算机方向,理工科同学必看!美国工程管理研究生申请大揭秘~...
  15. css设置遮罩层(半透明)
  16. android rs232串口协议,RS232串口协议详解
  17. SSM整合尚硅谷Spring
  18. usr目录linux,linux之usr目录的概述
  19. 时代落在英伟达身上的是粒什么沙,国产GPU的机会又在哪?
  20. 利用计算机技术全面规划供应,英语翻译《物流术语》国家标准对供应链管理的定义:利用计算机网络技术全面规划供应链中的商流、物流、信息流、资金流等,并进行...

热门文章

  1. 华为2018实习生软件岗机试题目(2018.04.10)
  2. 人工智能系统(二):技术栈
  3. 金融行业要翻软件定义存储SDS的牌子了吗?
  4. 贝塞尔曲线算法之JS获取点
  5. 教你5分钟做个手机APP[视频]
  6. 软件测试和软件开发哪个更好?
  7. retrace 解析混淆代码
  8. 【WebAPI】新手入门WebAPI
  9. WORKNC 2022.1.2228_2022.10.30(免安装)
  10. 计算机学的数学知识竞赛题及答案,数学知识竞赛题