一、Wi-FiDisplay相关知识
1.Miracast依赖的Wi-Fi技术项有:
Wi-Fi Direct:也就是Wi-Fi P2P。它支持在没有AP(Access Point)的情况下,两个Wi-Fi设备直连并通信。
Wi-Fi Protected Setup:用于帮助用户自动配置Wi-Fi网络、添加Wi-Fi设备等。
11n/WMM/WPA2:其中,11n就是802.11n协议,它将11a和11g提供的Wi-Fi传输速率从56Mbps提升到300甚至600Mbps。WMM是Wi-Fi Multimedia的缩写,是一种针对实时视音频数据的QoS服务。而WPA2意为Wi-Fi Protected Acess第二版,主要用来给传输的数据进行加密保护。
上述的Wi-Fi技术中,绝大部分功能由硬件厂商实现。而在Android中,对Miracast来说最重要的是两个基础技术:
Wi-Fi Direct:该功能由Android中的WifiP2pService来管理和控制。
Wi-Fi Multimedia:为了支持Miracast,Android 4.2对MultiMedia系统也进行了修改。
2.Miracast的大体工作流程。
Miracast以session为单位来管理两个设备之间的交互的工作,主要步骤包括(按顺序):
Device Discovery:通过Wi-Fi P2P来查找附近的支持Wi-Fi P2P的设备。
Device Selection:当设备A发现设备B后,A设备需要提示用户。用户可根据需要选择是否和设备B配对。
Connection Setup:Source和Display设备之间通过Wi-Fi P2P建立连接。根据Wi-Fi Direct技术规范,这个步骤包括建立一个Group Owner和一个Client。此后,这两个设备将建立一个TCP连接,同时一个用于RTSP协议的端口将被创建用于后续的Session管理和控制工作。
Capability Negotiation:在正式传输视音频数据前,Source和Display设备需要交换一些Miracast参数信息,例如双方所支持的视音频格式等。二者协商成功后,才能继续后面的流程。
Session Establishment and streaming:上一步工作完成后,Source和Display设备将建立一个Miracast Session。而后就可以开始传输视音频数据。Source端的视音频数据将经由MPEG2TS编码后通过RTP协议传给Display设备。Display设备将解码收到的数据,并最终显示出来。
User Input back channel setup:这是一个可选步骤。主要用于在传输过程中处理用户发起的一些控制操作。这些控制数据将通过TCP在Source和Display设备之间传递。
Payload Control:传输过程中,设备可根据无线信号的强弱,甚至设备的电量状况来动态调整传输数据和格式。可调整的内容包括压缩率,视音频格式,分辨率等内容。
Session teardown:停止整个Session。
DisplayManagerService用于管理系统显示设备的生命周期,包含物理屏幕、虚拟屏幕、wifi display等,它用一组DiaplayAdapter来管理这些显示设备。
二、Android中WifiDisplay模块分析
(一)服务初始化
相关服务的架构如下图:
DisplayManagerService用于管理系统显示设备的生命周期,包含物理屏幕、虚拟屏幕、wifi display等,它用一组DiaplayAdapter来管理这些显示设备。
MediaRouterService用于管理各个应用程序的多媒体播放的行为。
MediaRouter用于和MediaRouterService交互一起管理多媒体的播放行为,并维护当前已经配对上的remotedisplay设备,包括Wifi diplay、蓝牙A2DP设备、chromecast设备。
WifiDisplayAdapter是用于DisplayManagerService管理Wifi display显示的adapter。
WifiDisplayController用于控制扫描wifi display设备、连接、断开等操作。
1. DisplayManagerService初始化
流程如下图
① DisplayManagerService向DisplayManagerHandler 发送两个消息:
MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER、MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPT;
② DisplayManagerHandler 调用DisplayManagerService中两个函数注册displayAdapter;
③ DisplayManagerService调用registerWifiDisplayAdapterLocked()初始化一个WifiDisplayAdapter;
④ WifiDisplayAdapter创建一个WifiDisplayController,并注册wifip2p相关的广播和监听Settings.Global数据库里的三个表WIFI_DISPLAY_ON、WIFI_DISPLAY_CERTIFICATION_ON和WIFI_DISPLAY_WPS_CONFIG。
2.MediaRouterService和MediaRouter初始化
MediaRouter中主要通过Static对象来实现其大多数的方法,先创建一个Static对象,再调用其startMonitoringRoutes方法:
1. public MediaRouter(Context context) {
2. synchronized (Static.class) {
3. if (sStatic == null) {
4. final Context appContext = context.getApplicationContext();
5. sStatic = new Static(appContext);
6. sStatic.startMonitoringRoutes(appContext);
7. }
8. }
9. }
1. void startMonitoringRoutes(Context appContext) {
2. mDefaultAudioVideo = new RouteInfo(mSystemCategory);
3. mDefaultAudioVideo.mNameResId = com.android.internal.R.string.default_audio_route_name;
4. mDefaultAudioVideo.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;
5. mDefaultAudioVideo.updatePresentationDisplay();
6. addRouteStatic(mDefaultAudioVideo);
7.
8. // This will select the active wifi display route if there is one.
9. updateWifiDisplayStatus(mDisplayService.getWifiDisplayStatus());
10.
11. appContext.registerReceiver(new WifiDisplayStatusChangedReceiver(),
12. new IntentFilter(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED));
13. appContext.registerReceiver(new VolumeChangeReceiver(),
14. new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
15.
16. mDisplayService.registerDisplayListener(this, mHandler);
17.
18.
19. // Bind to the media router service.
20. rebindAsUser(UserHandle.myUserId());
21.
22. // Select the default route if the above didn't sync us up
23. // appropriately with relevant system state.
24. if (mSelectedRoute == null) {
25. selectDefaultRouteStatic();
26. }
27. }
这里通过注册ACTION_WIFI_DISPLAY_STATUS_CHANGED广播,监听WifiDisplay的状态改变,并且通过rebindAsUser(UserHandle.myUserId())绑定MediaRouterService。
(二)WifiDisplaySettings初始化
当用户进入WifiDisplaySettings界面,会调用其对应的onCreate和onStart方法:
1. public void onCreate(Bundle icicle) {
2. super.onCreate(icicle);
3.
4. final Context context = getActivity();
5. mRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
6. mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
7. mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE);
8. mWifiP2pChannel = mWifiP2pManager.initialize(context, Looper.getMainLooper(), null);
9.
10. addPreferencesFromResource(R.xml.wifi_display_settings);
11. setHasOptionsMenu(true);
12. }
13.
14. public void onStart() {
15. super.onStart();
16. mStarted = true;
17.
18. final Context context = getActivity();
19. IntentFilter filter = new IntentFilter();
20. filter.addAction(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED);
21. context.registerReceiver(mReceiver, filter);
22.
23. getContentResolver().registerContentObserver(Settings.Global.getUriFor(
24. Settings.Global.WIFI_DISPLAY_ON), false, mSettingsObserver);
25. getContentResolver().registerContentObserver(Settings.Global.getUriFor(
26. Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON), false, mSettingsObserver);
27. getContentResolver().registerContentObserver(Settings.Global.getUriFor(
28. Settings.Global.WIFI_DISPLAY_WPS_CONFIG), false, mSettingsObserver);
29.
30. mRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mRouterCallback,
31. MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
32.
33. update(CHANGE_ALL);
34. }
首先注册对ACTION_WIFI_DISPLAY_STATUS_CHANGED的receiver,这个broadcast会在WifiDisplayAdapter里面当wifi display的状态发生改变时发送,包括扫描到新的设备、开始连接、连接成功、断开等消息都会被这个receiver接收到。然后类似WifiDisplayController一样,注册一些对数据库改变的ContentObserver
(三)WifiDisplay扫描
当用户点击了optionMenu中enablewifi display后:
1. public boolean onOptionsItemSelected(MenuItem item) {
2. switch (item.getItemId()) {
3. case MENU_ID_ENABLE_WIFI_DISPLAY:
4. mWifiDisplayOnSetting = !item.isChecked();
5. item.setChecked(mWifiDisplayOnSetting);
6. Settings.Global.putInt(getContentResolver(),
7. Settings.Global.WIFI_DISPLAY_ON, mWifiDisplayOnSetting ? 1 : 0);
首先改变menu状态,然后向Settings.Global.WIFI_DISPLAY_ON数据写入1,WifiDisplaySettings在监控到这个值的变化后,主要是调用MediaRouter和DisplayManager的方法去获取系统中已经扫描到的remote display设备,同时WifiDisplayController也监控到该值的变化,处理如下:
1. private void updateSettings() {
2. final ContentResolver resolver = mContext.getContentResolver();
3. mWifiDisplayOnSetting = Settings.Global.getInt(resolver,
4. Settings.Global.WIFI_DISPLAY_ON, 0) != 0;
5. mWifiDisplayCertMode = Settings.Global.getInt(resolver,
6. Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON, 0) != 0;
7.
8. mWifiDisplayWpsConfig = WpsInfo.INVALID;
9. if (mWifiDisplayCertMode) {
10. mWifiDisplayWpsConfig = Settings.Global.getInt(resolver,
11. Settings.Global.WIFI_DISPLAY_WPS_CONFIG, WpsInfo.INVALID);
12. }
13.
14. updateWfdEnableState();
15. }
设置mWifiDisplayOnSetting 为1,然后调用updateWfdEnableState()更新wfd的状态:
1. private void updateWfdEnableState() {
2. if (mWifiDisplayOnSetting && mWifiP2pEnabled) {
3. // WFD should be enabled.
4. if (!mWfdEnabled && !mWfdEnabling) {
5. mWfdEnabling = true;
6.
7. WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();
8. wfdInfo.setWfdEnabled(true);
9. wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE);
10. wfdInfo.setSessionAvailable(true);
11. wfdInfo.setControlPort(DEFAULT_CONTROL_PORT);
12. wfdInfo.setMaxThroughput(MAX_THROUGHPUT);
13. mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener({
14. @Override
15. public void onSuccess() {
16. if (DEBUG) {
17. Slog.d(TAG, "Successfully set WFD info.");
18. }
19. if (mWfdEnabling) {
20. mWfdEnabling = false;
21. mWfdEnabled = true;
22. reportFeatureState();
23. updateScanState();
24. }
25. }
26.
27. @Override
28. public void onFailure(int reason) {
29. if (DEBUG) {
30. Slog.d(TAG, "Failed to set WFD info with reason " + reason + ".");
31. }
32. mWfdEnabling = false;
33. }
34. });
35. }
首先调用WifiP2pMananger的setWFDInfo把与wifi display相关的信息设置到wpa_supplicant,这些信息包括enable状态、device type(指为source还是sink)、sessionavailable(当前可否连接)、control port(用于rtsp连接)、maxThroughput(吞吐量),这些信息最终会随着P2P的IE信息在扫描阶段被对方知道。接着会调用reportFeatureState来通知WifiDisplayAdapter相应状态的变化:
1. private void reportFeatureState() {
2. final int featureState = computeFeatureState();
3. mHandler.post(new Runnable() {
4. @Override
5. public void run() {
6. mListener.onFeatureStateChanged(featureState);
7. }
8. });
9. }
直接回调WifiDisplayListener的onFeatureStateChanged,它由WifiDisplayAdapter注册,查看这部分的实现:
public void onFeatureStateChanged(int featureState) {
2. synchronized (getSyncRoot()) {
3. if (mFeatureState != featureState) {
4. mFeatureState = featureState;
5. scheduleStatusChangedBroadcastLocked();
6. }
7. }
8. }
9.
10. private void scheduleStatusChangedBroadcastLocked() {
11. mCurrentStatus = null;
12. if (!mPendingStatusChangeBroadcast) {
13. mPendingStatusChangeBroadcast = true;
14. mHandler.sendEmptyMessage(MSG_SEND_STATUS_CHANGE_BROADCAST);
15. }
16. }
通过mHandler.sendEmptyMessage(MSG_SEND_STATUS_CHANGE_BROADCAST),在WifiDisplayHandler中处理:
1. public void handleMessage(Message msg) {
2. switch (msg.what) {
3. case MSG_SEND_STATUS_CHANGE_BROADCAST:
4. handleSendStatusChangeBroadcast();
5. break;
6.
7. case MSG_UPDATE_NOTIFICATION:
8. handleUpdateNotification();
9. break;
10. }
11.
12. private void handleSendStatusChangeBroadcast() {
13. final Intent intent;
14. synchronized (getSyncRoot()) {
15. if (!mPendingStatusChangeBroadcast) {
16. return;
17. }
18.
19. mPendingStatusChangeBroadcast = false;
20. intent = new Intent(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED);
21. intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
22. intent.putExtra(DisplayManager.EXTRA_WIFI_DISPLAY_STATUS,
23. getWifiDisplayStatusLocked());
24. }
25.
26. // Send protected broadcast about wifi display status to registered receivers.
27. getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
这里发送广播ACTION_WIFI_DISPLAY_STATUS_CHANGED,通知MediaRouter调用updateWifiDisplayStatus来更新状态。
WifiDisplayController在调用reportFeatureState()后还调用了updateScanState():
1. private void updateScanState() {
2. if (mScanRequested && mWfdEnabled && mDesiredDevice == null) {
3. if (!mDiscoverPeersInProgress) {
4. Slog.i(TAG, "Starting Wifi display scan.");
5. mDiscoverPeersInProgress = true;
6. handleScanStarted();
7. tryDiscoverPeers();
8. }
9. }
handleScanStarted用于通知WifiDisplayAdapter扫描开始了, WifiDisplayAdapter也会发broadcast给MediaRouter。接着会调用tryDiscoverPeers,去扫描所有的p2p设备,扫描成功后,调用requestPeers()获取设备信息,之后再将这些设备信息发送到WifiDisplayAdapter中去跟之前保存的设备比较并更新,并返回给MediaRouter的设备列表信息,最后通过发送广播ACTION_WIFI_DISPLAY_STATUS_CHANGED通知WifiDisplaySettings,将从MediaRouter中获取设备信息展现给用户。
(四)WifiDisplay连接
当用户点击设备列表,发起连接,从WifiDisplaySettings调用WifiDisplayManager的connectWifiDisplay()方法,再通过一系列的调用,主要连接的逻辑实现在WifiDisplayController的updateConnection()方法中:
方法里的code非常多,分为六个步骤:
1. private void updateConnection() {
2. // Step 0. Stop scans if necessary to prevent interference while connected.
3. // Resume scans later when no longer attempting to connect.
4. updateScanState();
5.
6. // Step 1. Before we try to connect to a new device, tell the system we
7. // have disconnected from the old one.
8. //在尝试连接到新设备时,需要通知系统这里已经与旧的设备断开连接
9. if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {
10. ...
11. mRemoteDisplay.dispose(); //释放NativeRemoteDisplay资源停止监听
12. mRemoteDisplay = null; //监听返回对象置为空
13. mRemoteDisplayInterface = null; //监听端口置为空
14. mRemoteDisplayConnected = false; //连接标识为未连接
15. mHandler.removeCallbacks(mRtspTimeout);//将挂起的mRtspTimeout线程从消息队列中移除
16.
17. setRemoteSubmixOn(false); //关闭远程混音重建模式
18. unadvertiseDisplay();
19. }
20. // Step 2. Before we try to connect to a new device, disconnect from the old one.
21. if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {
22. ...
23. unadvertiseDisplay();
24.
25. final WifiP2pDevice oldDevice = mConnectedDevice;
26. mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() {
27. @Override
28. public void onSuccess() {
29. ...
30. next();
31. }
32.
33. @Override
34. public void onFailure(int reason) {
35. ...
36. next();
37. }
38.
39. private void next() {
40. if (mConnectedDevice == oldDevice) { //确保连接设备已经不是旧的设备否则递归调用该函数
41. mConnectedDevice = null;
42. updateConnection();
43. }
44. }
45. });
46. return;
47. }
48. // Step 3. Before we try to connect to a new device, stop trying to connect
49. // to the old one.
50. if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {
51. ...
52. unadvertiseDisplay();
53. mHandler.removeCallbacks(mConnectionTimeout);
54.
55. final WifiP2pDevice oldDevice = mConnectingDevice;
56. mWifiP2pManager.cancelConnect(mWifiP2pChannel, new ActionListener() { //在尝试连接到新设备之前,取消正在进行的p2p连接
57. @Override
58. public void onSuccess() {
59. ...
60. next();
61. }
62.
63. @Override
64. public void onFailure(int reason) {
65. ...
66. next();
67. }
68.
69. private void next() {
70. if (mConnectingDevice == oldDevice) {
71. mConnectingDevice = null;
72. updateConnection();
73. }
74. }
75. });
76. return;
77. }
78. // Step 4. If we wanted to disconnect, or we're updating after starting an
79. // autonomous GO, then mission accomplished.
80. // 如果想断开连接,则任务结束
81. if (mDesiredDevice == null) {
82. unadvertiseDisplay();
83. return;
84. }
85.
86. // Step 5. Try to connect.
87. if (mConnectedDevice == null && mConnectingDevice == null) {
88. Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName);
89. mConnectingDevice = mDesiredDevice;
90. WifiP2pConfig config = new WifiP2pConfig();
91. config.deviceAddress = mConnectingDevice.deviceAddress;
92. config.groupOwnerIntent = WifiP2pConfig.MIN_GROUP_OWNER_INTENT;
93.
94. WifiDisplay display = createWifiDisplay(mConnectingDevice);
95. advertiseDisplay(display, null, 0, 0, 0);
96.
97. final WifiP2pDevice newDevice = mDesiredDevice;
98. mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() {
99. //以特定的配置信息开启P2P连接,如果当前设备不是P2P组的一部分,会建立P2P小组并发起连接请求;如果当前设备是现存P2P组的一部分,则加入该组的邀请会发送至该配对设备。
100.
101. @Override
102. public void onSuccess() {
103. //为了防止连接还没有建立成功,这里设定了等待处理函数,如果在定长时间内还没有接受到WIFI_P2P_CONNECTION_CHANGED_ACTION广播,则按照handleConnectionFailure(true)处理。
104. Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName);
105. mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000);
106. }
107.
108. @Override
109. public void onFailure(int reason) {
110. if (mConnectingDevice == newDevice) {
111. Slog.i(TAG, "Failed to initiate connection to Wifi display: "
112. + newDevice.deviceName + ", reason=" + reason);
113. mConnectingDevice = null;
114. handleConnectionFailure(false);
115. }
116. }
117. });
118. return;
119. }
120. // Step 6. Listen for incoming RTSP connection.
121. // 根据连接的网络地址和端口号监听Rtsp流连接
122. if (mConnectedDevice != null && mRemoteDisplay == null) {
123. Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);
124. if (addr == null) {
125. Slog.i(TAG, "Failed to get local interface address for communicating "
126. + "with Wifi display: " + mConnectedDevice.deviceName);
127. handleConnectionFailure(false);
128. return; // done
129. }
130.
131. setRemoteSubmixOn(true);
132.
133. final WifiP2pDevice oldDevice = mConnectedDevice;
134. final int port = getPortNumber(mConnectedDevice);
135. final String iface = addr.getHostAddress() + ":" + port;
136. mRemoteDisplayInterface = iface;
137.
138. Slog.i(TAG, "Listening for RTSP connection on " + iface
139. + " from Wifi display: " + mConnectedDevice.deviceName);
140.
141. mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() {
142. //开始监听连接上的接口
143. @Override
144. public void onDisplayConnected(Surface surface,
145. int width, int height, int flags) {
146. if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) {
147. Slog.i(TAG, "Opened RTSP connection with Wifi display: "
148. + mConnectedDevice.deviceName);
149. mRemoteDisplayConnected = true;
150. mHandler.removeCallbacks(mRtspTimeout);
151.
152. final WifiDisplay display = createWifiDisplay(mConnectedDevice);
153. advertiseDisplay(display, surface, width, height, flags);
154. }
155. }
156.
157. @Override
158. public void onDisplayDisconnected() {
159. if (mConnectedDevice == oldDevice) {
160. Slog.i(TAG, "Closed RTSP connection with Wifi display: "
161. + mConnectedDevice.deviceName);
162. mHandler.removeCallbacks(mRtspTimeout);
163. disconnect();
164. }
165. }
166.
167. @Override
168. public void onDisplayError(int error) {
169. if (mConnectedDevice == oldDevice) {
170. Slog.i(TAG, "Lost RTSP connection with Wifi display due to error "
171. + error + ": " + mConnectedDevice.deviceName);
172. mHandler.removeCallbacks(mRtspTimeout);
173. handleConnectionFailure(false);
174. }
175. }
176. }, mHandler);
177.
178. mHandler.postDelayed(mRtspTimeout, RTSP_TIMEOUT_SECONDS * 1000);
179. }
180. }