作者:mznewfacer 时间:2012年12月7日
在上一回我们一块分析了WifiDisplay有关设备发现的部分,这一回将主要针对设备连接和建立数据流展开分析。
首先,回顾下应用层,当用户在搜寻完设备后,可以选择设备进行连接,当然正在进行连接或已经连接配对的设备,再次点击配置后,会弹出对话框供用户选择断开连接。
packages/apps/Settings/src/com/android/settings/wfd/WifiDisplaySettings.java
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { if (preference instanceof WifiDisplayPreference) { WifiDisplayPreference p = (WifiDisplayPreference)preference; WifiDisplay display = p.getDisplay(); if (display.equals(mWifiDisplayStatus.getActiveDisplay())) { showDisconnectDialog(display); } else { mDisplayManager.connectWifiDisplay(display.getDeviceAddress()); } } return super.onPreferenceTreeClick(preferenceScreen, preference); }
如同设备发现的调用流程,当用户选择设备进行连接后,程序会调用DisplayManager的connectWifiDisplay()函数接口。该函数会进一步根据DisplayManagerGlobal提供的单实例对象调用AIDL提供的接口函数connectWifiDisplay(),这又是上一回已经提到过的调用模式。其实际的调用实现是Displaymanager service中提供的connectWifiDisplay()函数,
frameworks/base/services/java/com/android/server/display/DisplayManagerService.java
public void connectWifiDisplay(String address) { if (address == null) { throw new IllegalArgumentException("address must not be null"); } final boolean trusted = canCallerConfigureWifiDisplay(); final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { if (mWifiDisplayAdapter != null) { mWifiDisplayAdapter.requestConnectLocked(address, trusted); } } } finally { Binder.restoreCallingIdentity(token); } }
到此,我们容易发现连接WifiDisplay设备的函数调用流程与发现设备的流程一致,这里将不做多余解释(详见),在此会罗列出之后的基本流程。
frameworks/base/services/java/com/android/server/display/WifiDisplayAdapter.java
public void requestConnectLocked(final String address, final boolean trusted) { if (DEBUG) { Slog.d(TAG, "requestConnectLocked: address=" + address + ", trusted=" + trusted); } if (!trusted) { synchronized (getSyncRoot()) { if (!isRememberedDisplayLocked(address)) { //如果设备地址不在保存列表中则忽略不做处理 ... return; } } } getHandler().post(new Runnable() { @Override public void run() { if (mDisplayController != null) { mDisplayController.requestConnect(address); } } }); }
frameworks/base/services/java/com/android/server/display/WifiDisplayController.java
public void requestConnect(String address) { for (WifiP2pDevice device : mAvailableWifiDisplayPeers) { if (device.deviceAddress.equals(address)) { connect(device); } } } private void connect(final WifiP2pDevice device) { if (mDesiredDevice != null && !mDesiredDevice.deviceAddress.equals(device.deviceAddress)) { //如果设备已经正在连接则返回 if (DEBUG) { ... } return; } if (mConnectedDevice != null && !mConnectedDevice.deviceAddress.equals(device.deviceAddress) && mDesiredDevice == null) {//如果设备已经连接则返回 if (DEBUG) { ... } return; } mDesiredDevice = device; mConnectionRetriesLeft = CONNECT_MAX_RETRIES; //尝试连接最大次数 updateConnection(); }
接下来,我们将重点看一看updateConnection()函数,此函数是建立Wifidisplay连接,监听RTSP连接的核心实现函数。
private void updateConnection() { //在尝试连接到新设备时,需要通知系统这里已经与旧的设备断开连接 if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) { ... mRemoteDisplay.dispose(); //释放NativeRemoteDisplay资源停止监听 mRemoteDisplay = null; //监听返回对象置为空 mRemoteDisplayInterface = null; //监听端口置为空 mRemoteDisplayConnected = false; //连接标识为未连接 mHandler.removeCallbacks(mRtspTimeout);//将挂起的mRtspTimeout线程从消息队列中移除 setRemoteSubmixOn(false); //关闭远程混音重建模式 unadvertiseDisplay(); } if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) { ... unadvertiseDisplay(); final WifiP2pDevice oldDevice = mConnectedDevice; mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() { @Override public void onSuccess() { ... next(); } @Override public void onFailure(int reason) { ... next(); } private void next() { if (mConnectedDevice == oldDevice) { //确保连接设备已经不是旧的设备否则递归调用该函数 mConnectedDevice = null; updateConnection(); } } }); return; } if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) { ... unadvertiseDisplay(); mHandler.removeCallbacks(mConnectionTimeout); final WifiP2pDevice oldDevice = mConnectingDevice; mWifiP2pManager.cancelConnect(mWifiP2pChannel, new ActionListener() { //在尝试连接到新设备之前,取消正在进行的p2p连接 @Override public void onSuccess() { ... next(); } @Override public void onFailure(int reason) { ... next(); } private void next() { if (mConnectingDevice == oldDevice) { mConnectingDevice = null; updateConnection(); } } }); return; } // 如果想断开连接,则任务结束 if (mDesiredDevice == null) { unadvertiseDisplay(); return; } if (mConnectedDevice == null && mConnectingDevice == null) { Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName); mConnectingDevice = mDesiredDevice; WifiP2pConfig config = new WifiP2pConfig(); config.deviceAddress = mConnectingDevice.deviceAddress; config.groupOwnerIntent = WifiP2pConfig.MIN_GROUP_OWNER_INTENT; WifiDisplay display = createWifiDisplay(mConnectingDevice); advertiseDisplay(display, null, 0, 0, 0); final WifiP2pDevice newDevice = mDesiredDevice; mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() { //以特定的配置信息开启P2P连接,如果当前设备不是P2P组的一部分,会建立P2P小组并发起连接请求;如果当前设备是现存P2P组的一部分,则加入该组的邀请会发送至该配对设备。 @Override public void onSuccess() { //为了防止连接还没有建立成功,这里设定了等待处理函数,如果在定长时间内还没有接受到WIFI_P2P_CONNECTION_CHANGED_ACTION广播,则按照handleConnectionFailure(true)处理。 Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName); mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000); } @Override public void onFailure(int reason) { if (mConnectingDevice == newDevice) { Slog.i(TAG, "Failed to initiate connection to Wifi display: " + newDevice.deviceName + ", reason=" + reason); mConnectingDevice = null; handleConnectionFailure(false); } } }); return; } // 根据连接的网络地址和端口号监听Rtsp流连接 if (mConnectedDevice != null && mRemoteDisplay == null) { Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo); if (addr == null) { Slog.i(TAG, "Failed to get local interface address for communicating " + "with Wifi display: " + mConnectedDevice.deviceName); handleConnectionFailure(false); return; // done } setRemoteSubmixOn(true); final WifiP2pDevice oldDevice = mConnectedDevice; final int port = getPortNumber(mConnectedDevice); final String iface = addr.getHostAddress() + ":" + port; mRemoteDisplayInterface = iface; Slog.i(TAG, "Listening for RTSP connection on " + iface + " from Wifi display: " + mConnectedDevice.deviceName); mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() {//开始监听连接上的接口 @Override public void onDisplayConnected(Surface surface, int width, int height, int flags) { if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) { Slog.i(TAG, "Opened RTSP connection with Wifi display: " + mConnectedDevice.deviceName); mRemoteDisplayConnected = true; mHandler.removeCallbacks(mRtspTimeout); final WifiDisplay display = createWifiDisplay(mConnectedDevice); advertiseDisplay(display, surface, width, height, flags); } } @Override public void onDisplayDisconnected() { if (mConnectedDevice == oldDevice) { Slog.i(TAG, "Closed RTSP connection with Wifi display: " + mConnectedDevice.deviceName); mHandler.removeCallbacks(mRtspTimeout); disconnect(); } } @Override public void onDisplayError(int error) { if (mConnectedDevice == oldDevice) { Slog.i(TAG, "Lost RTSP connection with Wifi display due to error " + error + ": " + mConnectedDevice.deviceName); mHandler.removeCallbacks(mRtspTimeout); handleConnectionFailure(false); } } }, mHandler); mHandler.postDelayed(mRtspTimeout, RTSP_TIMEOUT_SECONDS * 1000); } }
至此,我们已经了解了建立WifiDisplay连接的基本流程,当然可以继续向底层深入,只要用户选择尝试连接并且已经确认处于连接断开的状态,则会调用WifiP2pManager中的connect()接口函数,该函数会向Channel中发送CONNECT信号,并注册监听器监听相应结果。在进入P2pStateMachine状态机后,WifiP2pService会分为两种情况进行处理。如果当前的设备不是P2P组的成员,WifiP2pService会调用WifiNative类中的p2pConnect()函数,该函数会继续向底层调用,最终会调用wifi.cwifi_send_command()命令,把groupnegotiation请求发送至wpa_supplicant供其处理;如果这个设备已经是P2P组的成员,或者自己通过WifiNative类中的p2pGroupAdd()函数创建了一个组,那么会进入GroupCreatedState,进一步会调用WifiNative类中的p2pInvite()函数向设备发送邀请请求。具体的有关wpa_supplicant同底层驱动的交互,以及wpa_supplicant同WifiMonitor与WifiP2pService状态机之间的调用流程以后有机会再讨论。
在本文的最后,还想继续讨论一下监听RTSP连接的核心实现函数RemoteDisplay.listen(...),
frameworks/base/media/java/android/media/RemoteDisplay.java
public static RemoteDisplay listen(String iface, Listener listener, Handler handler) {... RemoteDisplay display = new RemoteDisplay(listener, handler); display.startListening(iface); return display; }可以看到该监听函数会调用以下函数,并把监听端口作为参数进行传递, private void startListening(String iface) { mPtr = nativeListen(iface); if (mPtr == 0) { throw new IllegalStateException("Could not start listening for " + "remote display connection on \"" + iface + "\""); } mGuard.open("dispose"); }
以上函数最终会调用JNI层的接口函数nativeListen()进行监听。至于CloseGuardmGuard.open(),不理解的话,我们就把它看作是Android提供的一种资源清理机制。
接下来,可以具体看一下RemoteDisplay在JNI层的接口实现,
frameworks/base/core/jni/android_media_RemoteDisplay.cpp
static jint nativeListen(JNIEnv* env, jobject remoteDisplayObj, jstring ifaceStr) { ScopedUtfChars iface(env, ifaceStr); //通过智能指针的方式将string类型转化为只读的UTF chars类型 sp<IServiceManager> sm = defaultServiceManager(); sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>( sm->getService(String16("media.player"))); //用service manager获得 media player服务的代理实例,即通过interface_cast将其转化成BpMediaPlayerService (Bridge模式) if (service == NULL) { ALOGE("Could not obtain IMediaPlayerService from service manager"); return 0; } sp<NativeRemoteDisplayClient> client(new NativeRemoteDisplayClient(env, remoteDisplayObj)); sp<IRemoteDisplay> display = service->listenForRemoteDisplay( client, String8(iface.c_str()));//调用BpMediaPlayerService提供的接口函数,与服务端BnMediaPlayerService进行通讯 if (display == NULL) { ALOGE("Media player service rejected request to listen for remote display '%s'.", iface.c_str()); return 0; } NativeRemoteDisplay* wrapper = new NativeRemoteDisplay(display, client); return reinterpret_cast<jint>(wrapper);}
这里采用了Binder通信机制,BpMediaPlayerService继承BpInterface<IMediaPlayerService>作为代理端,采用Bridge模式调用listenForRemoteDisplay()接口函数将上层的监听接口以及实例化的NativeRemoteDisplayClient代理对象传递至服务端BnMediaPlayerService进行处理。
/frameworks/av/media/libmedia/IMediaPlayerService.cpp
class BpMediaPlayerService: public BpInterface<IMediaPlayerService>{ public: … virtual sp<IRemoteDisplay> listenForRemoteDisplay(const sp<IRemoteDisplayClient>& client, const String8& iface) { Parcel data, reply; data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor()); data.writeStrongBinder(client->asBinder()); data.writeString8(iface); remote()->transact(LISTEN_FOR_REMOTE_DISPLAY, data, &reply); //向服务端BnMediaPlayerService发送LISTEN_FOR_REMOTE_DISPLAY 处理命令 return interface_cast<IRemoteDisplay>(reply.readStrongBinder()); }};
进一步可以看到,NativeRemoteDisplayClient继承于BnRemoteDisplayClient,其实这是IRemoteDisplayClient接口的服务端实现。该类提供了三个接口函数onDisplayConnected()、onDisplayDisconnected()、onDisplayError()是frameworks/base/media/java/android/media/RemoteDisplay.java中RemoteDisplay.Listener{}的三个监听函数在JNI层的实现,特别的,对于onDisplayConnected()函数而言,调用android_view_Surface_createFromISurfaceTexture()函数创建surfaceObj并将其向RemoteDisplay中注册的监听线程传递并进行回调。
frameworks/base/core/jni/android_media_RemoteDisplay.cpp
virtual void onDisplayConnected(const sp<ISurfaceTexture>& surfaceTexture, uint32_t width, uint32_t height, uint32_t flags) { JNIEnv* env = AndroidRuntime::getJNIEnv(); jobject surfaceObj = android_view_Surface_createFromISurfaceTexture(env, surfaceTexture); //跟据当前获取的media server的surface texture来创建Surface对象 if (surfaceObj == NULL) { ... return; } env->CallVoidMethod(mRemoteDisplayObjGlobal, gRemoteDisplayClassInfo.notifyDisplayConnected, surfaceObj, width, height, flags); //将Suface对象作为参数传递至notifyDisplayConnected函数用于监听函数的回调 env->DeleteLocalRef(surfaceObj); checkAndClearExceptionFromCallback(env, "notifyDisplayConnected"); }
接下来,我们继续来看服务端BnMediaPlayerService的实现,其中onTransact函数用于接收来自BpMediaPlayerService发送的命令,如果命令为LISTEN_FOR_REMOTE_DISPLAY,则会读取相应数据并作为参数进行传递。这里的listenForRemoteDisplay()函数是纯虚函数,其实现是由派生类MediaPlayerService来完成的。
status_t BnMediaPlayerService::onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags){ switch (code) { … case LISTEN_FOR_REMOTE_DISPLAY: { CHECK_INTERFACE(IMediaPlayerService, data, reply); sp<IRemoteDisplayClient> client( interface_cast<IRemoteDisplayClient>(data.readStrongBinder())); String8 iface(data.readString8()); sp<IRemoteDisplay> display(listenForRemoteDisplay(client, iface));//调用纯虚函数接口,运行时实际调用派生类MediaPlayerService的函数实现 reply->writeStrongBinder(display->asBinder()); return NO_ERROR; } break; default: return BBinder::onTransact(code, data, reply, flags); }}
最后,来看一看该函数的实际实现,
frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp
sp<IRemoteDisplay> MediaPlayerService::listenForRemoteDisplay( const sp<IRemoteDisplayClient>& client, const String8& iface) { if (!checkPermission("android.permission.CONTROL_WIFI_DISPLAY")) { //检查是否有WIFI Display权限 return NULL; } return new RemoteDisplay(client, iface.string()); //直接调用 RemoteDisplay构造函数来开启Wifi display source端}
其中,RemoteDisplay继承于BnRemoteDisplay,也采取了Binder通信机制,代理端BpRemoteDisplay与服务端BnRemoteDisplay的接口实现详见frameworks/av/media/libmedia/IRemoteDisplay.cpp。这里,值得一提的是,函数listenForRemoteDisplay()假设在同一时刻连接到指定网络端口iface的remotedisplay设备最多只有一个。换句话说,在同一时刻只有一个设备能作为WifiDisplay source端设备进行播放。
最后,我们来看一看开启Wifidisplay source端的这个构造函数,frameworks/av/media/libmediaplayerservice/RemoteDisplay.cpp
RemoteDisplay::RemoteDisplay( const sp<IRemoteDisplayClient> &client, const char *iface) : mLooper(new ALooper), mNetSession(new ANetworkSession), mSource(new WifiDisplaySource(mNetSession, client)) { mLooper->setName("wfd_looper"); mLooper->registerHandler(mSource); //注册了Wifi display 处理线程 mNetSession->start(); //初始化数据管道,启动NetworkThread线程,进入threadLoop中监听数据流变化等待处理 mLooper->start(); //开启消息处理管理线程 mSource->start(iface); //将网络端口作为消息载体进行传递处理,并等待响应结果,完成与Wifi Display source端开启播放的相关工作}
其中mLooper,mNetSession, mSource分别为sp<ALooper>mLooper,sp<ANetworkSession>mNetSession以及sp<WifiDisplaySource>mSource等三个强指针,对强指针概念不清的请见此。此处是利用构造函数的初始化列表将这三个强指针指向这三个new出来的对象。之后便是利用这三个指针,调用类中的方法以开启Wifidisplay source端进行播放。这里,ALooper是关于线程以及消息队列等待处理管理相关的一个类。ANetworkSessions是管理所有与数据报文和数据流相关socket的一个单线程帮助类。在此处,该类负责管理与WifiDisplay播放相关的socket,其中相关的数据传递和消息返回通过AMessage类对象和方法进行。WifiDisplaySource光看命名就知道,其主要负责WifiDisplaysource端的开启关闭,以及与其相关的建立Rtsp服务器,管理所有支持的协议连接、数据流传递以及各个状态之间转换处理等内容。此外,该类还定义了关闭WifiDisplay source端,停止相关线程、关闭socket以及释放资源等内容。
至此,有关WifiDisplay设备连接和建立数据流的流程已经交代清楚了,可以看到应用层建立的连接是与source端相关的。Sink端的主程序在frameworks/av/media/libstagefright/wifi-display/wfd.cpp中,与sink端实现相关的程序在frameworks/av/media/libstagefright/wifi-display/sink目录下面。关于source如何建立rtsp连接,开始通信,各个状态之间的转换以及与sink端的交互将在下回介绍。
- 1楼moritz_dev昨天 22:50
- 写的相当不错的,期待继续。方便的话,[email protected] :)