wifi display学习总结

一、Wi-FiDisplay相关知识

1.Miracast依赖的Wi-Fi技术项有:


Wi-Fi Direct:也就是Wi-Fi P2P。它支持在没有APAccess Point)的情况下,两个Wi-Fi设备直连并通信。

Wi-Fi Protected Setup用于帮助用户自动配置Wi-Fi网络、添加Wi-Fi设备等。

11n/WMM/WPA2其中,11n就是802.11n协议,它将11a11g提供的Wi-Fi传输速率从56Mbps提升到300甚至600MbpsWMMWi-Fi Multimedia的缩写,是一种针对实时视音频数据的QoS服务。而WPA2意为Wi-Fi Protected Acess第二版,主要用来给传输的数据进行加密保护。

上述的Wi-Fi技术中,绝大部分功能由硬件厂商实现。而在Android中,对Miracast来说最重要的是两个基础技术:

Wi-Fi Direct该功能由Android中的WifiP2pService来管理和控制。

Wi-Fi Multimedia为了支持MiracastAndroid 4.2MultiMedia系统也进行了修改。


2.Miracast的大体工作流程

Miracastsession为单位来管理两个设备之间的交互的工作,主要步骤包括(按顺序):

Device Discovery通过Wi-Fi P2P来查找附近的支持Wi-Fi P2P的设备。

Device Selection当设备A发现设备B后,A设备需要提示用户。用户可根据需要选择是否和设备B配对。

Connection SetupSourceDisplay设备之间通过Wi-Fi P2P建立连接。根据Wi-Fi Direct技术规范,这个步骤包括建立一个Group Owner和一个Client。此后,这两个设备将建立一个TCP连接,同时一个用于RTSP协议的端口将被创建用于后续的Session管理和控制工作。

Capability Negotiation在正式传输视音频数据前,SourceDisplay设备需要交换一些Miracast参数信息,例如双方所支持的视音频格式等。二者协商成功后,才能继续后面的流程。

Session Establishment and streaming上一步工作完成后,SourceDisplay设备将建立一个Miracast Session。而后就可以开始传输视音频数据。Source端的视音频数据将经由MPEG2TS编码后通过RTP协议传给Display设备。Display设备将解码收到的数据,并最终显示出来。

User Input back channel setup这是一个可选步骤。主要用于在传输过程中处理用户发起的一些控制操作。这些控制数据将通过TCPSourceDisplay设备之间传递。

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初始化

流程如下图


DisplayManagerServiceDisplayManagerHandler 发送两个消息:

MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERMSG_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.	    }  






评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值