Android WifiDisplay分析二:Wifi display连接过程

版权声明:本文为博主原创文章,未经博主允许不得转载。

目录(?)[-]

  1. WifiDisplay之P2P的建立
  2. WifiDisplay之RTSP server的创建

这一章中我们来看Wifi Display连接过程的建立,包含P2P的部分和RTSP的部分,首先来大致看一下Wifi Display规范相关的东西。

 

HIDC: Human Interface Device Class  (遵循HID标准的设备类)
UIBC: User Input Back Channel  (UIBC分为两种,一种是Generic,包含鼠标、键盘等;另一种是HIDC,HID是一个规范,只有遵循HID的标准,都可以叫做HID设备,包含USB鼠标、键盘、蓝牙、红外等)
PES: Packetized Elementary Stream (数字电视基本码流)
HDCP: High-bandwidth Digital Content Protection  (加密方式,用于加密传输的MPEG2-TS流)
MPEG2-TS: Moving Picture Experts Group 2 Transport Stream   (Wifi display之间传输的是MPEG2-TS流)
RTSP: Real-Time Streaming Protocol     (Wifi display通过RTSP协议来交互两边的能力)
RTP: Real-time Transport Protocol        (Wifi display通过RTP来传输MPEG2-TS流)
Wi-Fi P2P: Wi-Fi Direct
TDLS: Tunneled Direct Link Setup        (另一种方式建立两台设备之间的直连,与P2P类似,但要借助一台AP)

 

另一种比较重要的概念是在Wifi Display中分为Source和Sink两种角色,如下图。Source是用于encode并输出TS流;Sink用于decode并显示TS流。相当于Server/Client架构中,Source就是Server,用于提供服务;Sink就是Client。当然,我们这篇文章主要介绍在Android上Wifi display Source的流程。

 

从上面的架构图我们可以看到,Wifi display是建立在TCP/UDP上面的应用层协议,L2链路层是通过P2P和TDLS两种方式建立,TDLS是optional的。在L2层建立连接后,Source就会在一个特定的port上listen,等待client的TCP连接。当与Client建立了TCP连接后,就会有M1~M7七个消息的交互,用户获取对方设备的能力,包括视频编码能力、Audio输出能力、是否支持HDCP加密等等。在获取这些能力之后,Source就会选择一种视频编码格式以及Audio格式用于这次会话当中。当一个RTSP会话建立后,双方就会决定出用于传输TS流的RTP port,RTP协议是基于UDP的。当这些都准备好后,Sink设备就会发送M7消息,也就是Play给Source,双方就可以开始传输数据了。

关于M1~M7是什么,我们后面再来介绍。首先我们来介绍在Android WifiDisplay中如何建立P2P的连接。

 

WifiDisplay之P2P的建立

 

通过我们之间关于Wifi display的service启动以及enable的分析,我们知道当扫描到可用的设备后,就会显示在WifiDisplaySettings这个页面上,当我们选择其中一个后,就会开始P2P的建立了,首先到WifiDisplaySettings中的代码分析:

[java] view plain copy  在CODE上查看代码片派生到我的代码片

  1. private void pairWifiDisplay(WifiDisplay display) {  
  2.     if (display.canConnect()) {  
  3.         mDisplayManager.connectWifiDisplay(display.getDeviceAddress());  
  4.     }  
  5. }  


WifiDisplaySettings通过AIDL调用到DisplayManagerService的connectWifiDisplay方法,关于AIDL的调用过程这里不讲了,直接到DisplayManagerService的connectWifiDisplay方法来看:

[java] view plain copy  在CODE上查看代码片派生到我的代码片

  1. public void connectWifiDisplay(String address) {  
  2.     if (address == null) {  
  3.         throw new IllegalArgumentException("address must not be null");  
  4.     }  
  5.     mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,  
  6.             "Permission required to connect to a wifi display");  
  7.   
  8.     final long token = Binder.clearCallingIdentity();  
  9.     try {  
  10.         synchronized (mSyncRoot) {  
  11.             if (mWifiDisplayAdapter != null) {  
  12.                 mWifiDisplayAdapter.requestConnectLocked(address);  
  13.             }  
  14.         }  
  15.     } finally {  
  16.         Binder.restoreCallingIdentity(token);  
  17.     }  
  18. }  


首先做参数的检查,即MAC地址不能为空,然后做权限检查,调用这个方法的application必须要在manifest中声明有CONFIGURE_WIFI_DISPLAY权限,最后直接调用WifiDisplayAdapter的requestConnectLocked方法:

[java] view plain copy  在CODE上查看代码片派生到我的代码片

  1. public void requestConnectLocked(final String address) {  
  2.     if (DEBUG) {  
  3.         Slog.d(TAG, "requestConnectLocked: address=" + address);  
  4.     }  
  5.   
  6.     getHandler().post(new Runnable() {  
  7.         @Override  
  8.         public void run() {  
  9.             if (mDisplayController != null) {  
  10.                 mDisplayController.requestConnect(address);  
  11.             }  
  12.         }  
  13.     });  
  14. }  


这里比较简单,直接调用WifiDisplayController的requestConnect方法。前面都是直接的调用,最终做事情的还是WifiDisplayController。

[java] view plain copy  在CODE上查看代码片派生到我的代码片

  1. public void requestConnect(String address) {  
  2.     for (WifiP2pDevice device : mAvailableWifiDisplayPeers) {  
  3.         if (device.deviceAddress.equals(address)) {  
  4.             connect(device);  
  5.         }  
  6.     }  
  7. }  
  8.   
  9. private void connect(final WifiP2pDevice device) {  
  10.     if (mDesiredDevice != null  
  11.             && !mDesiredDevice.deviceAddress.equals(device.deviceAddress)) {  
  12.         if (DEBUG) {  
  13.             Slog.d(TAG, "connect: nothing to do, already connecting to "  
  14.                     + describeWifiP2pDevice(device));  
  15.         }  
  16.         return;  
  17.     }  
  18.   
  19.     if (mConnectedDevice != null  
  20.             && !mConnectedDevice.deviceAddress.equals(device.deviceAddress)  
  21.             && mDesiredDevice == null) {  
  22.         if (DEBUG) {  
  23.             Slog.d(TAG, "connect: nothing to do, already connected to "  
  24.                     + describeWifiP2pDevice(device) + " and not part way through "  
  25.                     + "connecting to a different device.");  
  26.         }  
  27.         return;  
  28.     }  
  29.   
  30.     if (!mWfdEnabled) {  
  31.         Slog.i(TAG, "Ignoring request to connect to Wifi display because the "  
  32.                 +" feature is currently disabled: " + device.deviceName);  
  33.         return;  
  34.     }  
  35.   
  36.     mDesiredDevice = device;  
  37.     mConnectionRetriesLeft = CONNECT_MAX_RETRIES;  
  38.     updateConnection();  
  39. }  


requestConnect先从mAvaiableWifiDsiplayPeers中通过Mac地址找到所有连接的WifiP2pDevice,然后调用connect方法,在connect方法中会做一系列的判断,看首先是否有正在连接中或者断开中的设备,如果有就直接返回;再看有没有已经连接上的设备,如果有,也直接返回,然后赋值mDesiredDevice为这次要连接的设备,最后调用updateConnection来更新连接状态并发起连接。updateConnection的代码比较长,我们分段来分析:

[java] view plain copy  在CODE上查看代码片派生到我的代码片

  1. private void updateConnection() {  
  2. n style="white-space:pre">  </span>//更新是否需要scan或者停止scan  
  3.     updateScanState();  
  4.   
  5. n style="white-space:pre">  </span>//如果有已经连接上的RemoteDisplay,先断开。这里先不看  
  6.     if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {  
  7.           
  8.     }  
  9.   
  10.     // 接上面的一步,段开这个group  
  11.     if (mDisconnectingDevice != null) {  
  12.         return// wait for asynchronous callback  
  13.     }  
  14.     if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {  
  15.   
  16.     }  
  17.   
  18.     // 如果有正在连接的设备,先停止连接之前的设备  
  19.     if (mCancelingDevice != null) {  
  20.         return// wait for asynchronous callback  
  21.     }  
  22.     if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {  
  23.           
  24.     }  
  25.   
  26.     // 当断开之前的连接或者启动匿名GROUP时,这里就结束了  
  27.     if (mDesiredDevice == null) {  
  28.   
  29.     }  
  30.   
  31.     // 开始连接,这是我们要看的重点  
  32.     if (mConnectedDevice == null && mConnectingDevice == null) {  
  33.         Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName);  
  34.   
  35.         mConnectingDevice = mDesiredDevice;  
  36.         WifiP2pConfig config = new WifiP2pConfig();  
  37.         WpsInfo wps = new WpsInfo();  
  38.         if (mWifiDisplayWpsConfig != WpsInfo.INVALID) {  
  39.             wps.setup = mWifiDisplayWpsConfig;  
  40.         } else if (mConnectingDevice.wpsPbcSupported()) {  
  41.             wps.setup = WpsInfo.PBC;  
  42.         } else if (mConnectingDevice.wpsDisplaySupported()) {  
  43.             wps.setup = WpsInfo.KEYPAD;  
  44.         } else {  
  45.             wps.setup = WpsInfo.DISPLAY;  
  46.         }  
  47.         config.wps = wps;  
  48.         config.deviceAddress = mConnectingDevice.deviceAddress;  
  49.         config.groupOwnerIntent = WifiP2pConfig.MIN_GROUP_OWNER_INTENT;  
  50.   
  51.         WifiDisplay display = createWifiDisplay(mConnectingDevice);  
  52.         advertiseDisplay(display, null000);  
  53.   
  54.         final WifiP2pDevice newDevice = mDesiredDevice;  
  55.         mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() {  
  56.             @Override  
  57.             public void onSuccess() {  
  58.                 Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName);  
  59.   
  60.                 mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000);  
  61.             }  
  62.   
  63.             @Override  
  64.             public void onFailure(int reason) {  
  65.                 if (mConnectingDevice == newDevice) {  
  66.                     Slog.i(TAG, "Failed to initiate connection to Wifi display: "  
  67.                             + newDevice.deviceName + ", reason=" + reason);  
  68.                     mConnectingDevice = null;  
  69.                     handleConnectionFailure(false);  
  70.                 }  
  71.             }  
  72.         });  
  73.         return;   
  74.     }<span style="font-family: Arial, Helvetica, sans-serif;">   </span>  


这段函数比较长,我们先看我们需要的,剩下的后面再来分析。首先赋值给mConnectingDevice表示当前正在连接的设备,然后构造一个WifiP2pConfig对象,这个对象包含这次连接的设备的Mac地址、wps方式以及我们自己的GROUP_OWNER intent值,然后调用advertieseDisplay方法来通知WifiDisplayAdapter相关状态的改变,WifiDisplayAdapter会发送相应的broadcast出来,这是WifiDisplaySettings可以接收这些broadcast,然后在UI上更新相应的状态。关于advertieseDisplay的实现,我们后面再来分析。

 

接着看updateConnection,调用WifiP2pManager的connect方法去实现两台设备的P2P连接,具体过程可以参考前面介绍的P2P连接的文章。这里的onSuccess()并不是表示P2P已经建立成功,而只是表示这个发送命令到wpa_supplicant成功,所以在这里设置了一个连接超时的timeout,为30秒。当连接成功后,会发送WIFI_P2P_CONNECTION_CHANGED_ACTION的广播出来,接着回到WifiDisplayController看如何处理连接成功的broadcast:

[java] view plain copy  在CODE上查看代码片派生到我的代码片

  1.         } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) {  
  2.             NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(  
  3.                     WifiP2pManager.EXTRA_NETWORK_INFO);  
  4.             if (DEBUG) {  
  5.                 Slog.d(TAG, "Received WIFI_P2P_CONNECTION_CHANGED_ACTION: networkInfo="  
  6.                         + networkInfo);  
  7.             }  
  8.   
  9.             handleConnectionChanged(networkInfo);  
  10.   
  11. private void handleConnectionChanged(NetworkInfo networkInfo) {  
  12.     mNetworkInfo = networkInfo;  
  13.     if (mWfdEnabled && networkInfo.isConnected()) {  
  14.         if (mDesiredDevice != null || mWifiDisplayCertMode) {  
  15.             mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() {  
  16.                 @Override  
  17.                 public void onGroupInfoAvailable(WifiP2pGroup info) {  
  18.                     if (DEBUG) {  
  19.                         Slog.d(TAG, "Received group info: " &
  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值