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

目录(?)[-]

  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  

  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  

  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. final long token = Binder.clearCallingIdentity();
  8. try {
  9. synchronized (mSyncRoot) {
  10. if (mWifiDisplayAdapter != null) {
  11. mWifiDisplayAdapter.requestConnectLocked(address);
  12. }
  13. }
  14. finally {
  15. Binder.restoreCallingIdentity(token);
  16. }
  17. }

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

[java] view plain copy  

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

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

[java] view plain copy  

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

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

[java] view plain copy  

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

  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. handleConnectionChanged(networkInfo);
  9. private void handleConnectionChanged(NetworkInfo networkInfo) {
  10. mNetworkInfo = networkInfo;
  11. if (mWfdEnabled && networkInfo.isConnected()) {
  12. if (mDesiredDevice != null || mWifiDisplayCertMode) {
  13. mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() {
  14. @Override
  15. public void onGroupInfoAvailable(WifiP2pGroup info) {
  16. if (DEBUG) {
  17. Slog.d(TAG, "Received group info: " + describeWifiP2pGroup(info));
  18. }
  19. if (mConnectingDevice != null && !info.contains(mConnectingDevice)) {
  20. Slog.i(TAG, "Aborting connection to Wifi display because "
  21. + "the current P2P group does not contain the device "
  22. + "we expected to find: " + mConnectingDevice.deviceName
  23. + ", group info was: " + describeWifiP2pGroup(info));
  24. handleConnectionFailure(false);
  25. return;
  26. }
  27. if (mDesiredDevice != null && !info.contains(mDesiredDevice)) {
  28. disconnect();
  29. return;
  30. }
  31. if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
  32. Slog.i(TAG, "Connected to Wifi display: "
  33. + mConnectingDevice.deviceName);
  34. mHandler.removeCallbacks(mConnectionTimeout);
  35. mConnectedDeviceGroupInfo = info;
  36. mConnectedDevice = mConnectingDevice;
  37. mConnectingDevice = null;
  38. updateConnection();
  39. }
  40. }
  41. });
  42. }
  43. }

当WifiDisplayController收到WIFI_P2P_CONNECTION_CHANGED_ACTION广播后,会调用handleConnectionChanged来获取当前P2P Group相关的信息,如果获取到的P2P Group信息里面没有mConnectingDevice或者mDesiredDevice的信息,则表示连接出错了,直接退出。如果当前连接信息与前面设置的mConnectingDevice一直,则表示连接P2P成功,这里首先会移除前面设置的连接timeout的callback,然后设置mConnectedDevice为当前连接的设备,并设置mConnectingDevice为空,最后调用updateConnection来更新连接状态信息。我们又回到updateConnection这个函数了,但这次进入的分支与之前连接请求的分支又不同了,我们来看代码:

[java] view plain copy  

  1. private void updateConnection() {
  2. // 更新是否需要scan或者停止scan
  3. updateScanState();
  4. // 如果有连接上的RemoteDisplay,这里先断开
  5. if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {
  6. }
  7. // 接着上面的一步,先断开之前连接的设备
  8. if (mDisconnectingDevice != null) {
  9. return; // wait for asynchronous callback
  10. }
  11. if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {
  12. }
  13. // 如果有正在连接的设备,先断开之前连接的设备
  14. if (mCancelingDevice != null) {
  15. return; // wait for asynchronous callback
  16. }
  17. if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {
  18. }
  19. // 当断开之前的连接或者匿名GO时,这里就结束了
  20. if (mDesiredDevice == null) {
  21. }
  22. // 如果有连接请求,则进入此
  23. if (mConnectedDevice == null && mConnectingDevice == null) {
  24. }
  25. // 当连接上P2P后,就进入到此
  26. if (mConnectedDevice != null && mRemoteDisplay == null) {
  27. Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);
  28. if (addr == null) {
  29. Slog.i(TAG, "Failed to get local interface address for communicating "
  30. + "with Wifi display: " + mConnectedDevice.deviceName);
  31. handleConnectionFailure(false);
  32. return; // done
  33. }
  34. mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_SOURCE);
  35. final WifiP2pDevice oldDevice = mConnectedDevice;
  36. final int port = getPortNumber(mConnectedDevice);
  37. final String iface = addr.getHostAddress() + ":" + port;
  38. mRemoteDisplayInterface = iface;
  39. Slog.i(TAG, "Listening for RTSP connection on " + iface
  40. + " from Wifi display: " + mConnectedDevice.deviceName);
  41. mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() {
  42. @Override
  43. public void onDisplayConnected(Surface surface,
  44. int width, int height, int flags, int session) {
  45. if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) {
  46. Slog.i(TAG, "Opened RTSP connection with Wifi display: "
  47. + mConnectedDevice.deviceName);
  48. mRemoteDisplayConnected = true;
  49. mHandler.removeCallbacks(mRtspTimeout);
  50. if (mWifiDisplayCertMode) {
  51. mListener.onDisplaySessionInfo(
  52. getSessionInfo(mConnectedDeviceGroupInfo, session));
  53. }
  54. final WifiDisplay display = createWifiDisplay(mConnectedDevice);
  55. advertiseDisplay(display, surface, width, height, flags);
  56. }
  57. }
  58. @Override
  59. public void onDisplayDisconnected() {
  60. if (mConnectedDevice == oldDevice) {
  61. Slog.i(TAG, "Closed RTSP connection with Wifi display: "
  62. + mConnectedDevice.deviceName);
  63. mHandler.removeCallbacks(mRtspTimeout);
  64. disconnect();
  65. }
  66. }
  67. @Override
  68. public void onDisplayError(int error) {
  69. if (mConnectedDevice == oldDevice) {
  70. Slog.i(TAG, "Lost RTSP connection with Wifi display due to error "
  71. + error + ": " + mConnectedDevice.deviceName);
  72. mHandler.removeCallbacks(mRtspTimeout);
  73. handleConnectionFailure(false);
  74. }
  75. }
  76. }, mHandler);
  77. // Use extended timeout value for certification, as some tests require user inputs
  78. int rtspTimeout = mWifiDisplayCertMode ?
  79. RTSP_TIMEOUT_SECONDS_CERT_MODE : RTSP_TIMEOUT_SECONDS;
  80. mHandler.postDelayed(mRtspTimeout, rtspTimeout * 1000);
  81. }
  82. }

到这里P2P的连接就算建立成功了,接下来就是RTSP的部分了

WifiDisplay之RTSP server的创建

这里首先设置MiracastMode,博主认为这部分应该放在enable WifiDisplay时,不知道Google为什么放在这里? 然后从GroupInfo中取出对方设备的IP地址,利用默认的CONTROL PORT构建mRemoteDisplayInterface,接着调用RemoteDisplay的listen方法去listen指定的IP和端口上面的TCP连接请求。最后会设置Rtsp的连接请求的timeout,当用于Miracast认证时是120秒,正常的使用中是30秒,如果在这么长的时间内没有收到Sink的TCP请求,则表示失败了。下面来看RemoteDisplay的listen的实现:

[java] view plain copy  

  1. public static RemoteDisplay listen(String iface, Listener listener, Handler handler) {
  2. if (iface == null) {
  3. throw new IllegalArgumentException("iface must not be null");
  4. }
  5. if (listener == null) {
  6. throw new IllegalArgumentException("listener must not be null");
  7. }
  8. if (handler == null) {
  9. throw new IllegalArgumentException("handler must not be null");
  10. }
  11. RemoteDisplay display = new RemoteDisplay(listener, handler);
  12. display.startListening(iface);
  13. return display;
  14. }

这里首先进行参数的检查,然后创建一个RemoteDisplay对象(这里不能直接创建RemoteDisplay对象,因为它的构造函数是private的),接着调用RemoteDisplay的startListening方法:

[java] view plain copy  

  1. private void startListening(String iface) {
  2. mPtr = nativeListen(iface);
  3. if (mPtr == 0) {
  4. throw new IllegalStateException("Could not start listening for "
  5. + "remote display connection on \"" + iface + "\"");
  6. }
  7. mGuard.open("dispose");
  8. }

nativeListen会调用JNI中的实现,相关代码在android_media_RemoteDisplay.cpp中。注意上面的mGuard是CloseGuard对象,是一种用于显示释放一些资源的机制。

[java] view plain copy  

  1. static jint nativeListen(JNIEnv* env, jobject remoteDisplayObj, jstring ifaceStr) {
  2. ScopedUtfChars iface(env, ifaceStr);
  3. sp<IServiceManager> sm = defaultServiceManager();
  4. sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(
  5. sm->getService(String16("media.player")));
  6. if (service == NULL) {
  7. ALOGE("Could not obtain IMediaPlayerService from service manager");
  8. return 0;
  9. }
  10. sp<NativeRemoteDisplayClient> client(new NativeRemoteDisplayClient(env, remoteDisplayObj));
  11. sp<IRemoteDisplay> display = service->listenForRemoteDisplay(
  12. client, String8(iface.c_str()));
  13. if (display == NULL) {
  14. ALOGE("Media player service rejected request to listen for remote display '%s'.",
  15. iface.c_str());
  16. return 0;
  17. }
  18. NativeRemoteDisplay* wrapper = new NativeRemoteDisplay(display, client);
  19. return reinterpret_cast<jint>(wrapper);
  20. }

上面的代码中先从ServiceManager中获取MediaPlayerService的Bpbinder引用,然后由传入的第二个参数remoteDisplayObj,也就是RemoteDisplay对象构造一个NativeRemoteDisplayClient,在framework中,我们经常看到像这样的用法,类似于设计模式中的包装模式,例如在framework中对Java层的BnBinder也是做了一层封装JavaBBinder。在NativeRemoteDisplayClient中通过JNI的反向调用,就可以直接回调RemoteDisplay中的一些函数,实现回调方法了,下面来看它的实现:

[java] view plain copy  

  1. class NativeRemoteDisplayClient : public BnRemoteDisplayClient {
  2. public:
  3. NativeRemoteDisplayClient(JNIEnv* env, jobject remoteDisplayObj) :
  4. mRemoteDisplayObjGlobal(env->NewGlobalRef(remoteDisplayObj)) {
  5. }
  6. protected:
  7. ~NativeRemoteDisplayClient() {
  8. JNIEnv* env = AndroidRuntime::getJNIEnv();
  9. env->DeleteGlobalRef(mRemoteDisplayObjGlobal);
  10. }
  11. public:
  12. virtual void onDisplayConnected(const sp<IGraphicBufferProducer>& bufferProducer,
  13. uint32_t width, uint32_t height, uint32_t flags, uint32_t session) {
  14. env->CallVoidMethod(mRemoteDisplayObjGlobal,
  15. gRemoteDisplayClassInfo.notifyDisplayConnected,
  16. surfaceObj, width, height, flags, session);
  17. }
  18. virtual void onDisplayDisconnected() {
  19. }
  20. virtual void onDisplayError(int32_t error) {
  21. }
  22. private:
  23. jobject mRemoteDisplayObjGlobal;
  24. static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
  25. }
  26. }
  27. };

在NativeRemoteDisplayClient的构造函数中,把RemoteDisplay对象先保存到mRemoteDisplayObjGlobal中,可以看到上面主要实现了三个回调函数,onDisplayConnected、onDisplayDisconnected、onDisplayError,这三个回调函数对应到RemoteDisplay类的notifyDisplayConnected、notifyDisplayDisconnected和notifyDisplayError三个方法。接着回到nativeListen中,接着会调用MediaPlayerService的listenForRemoteDisplay方法去监听socket连接,这个方法是返回一个RemoteDisplay对象,当然经过binder的调用,最终返回到nativeListen的是BpRemoteDisplay对象,然后会由这个BpRemoteDisplay对象构造一个NativeRemoteDisplay对象并把它的指针地址返回给上层RemoteDisplay使用。

[java] view plain copy  

  1. class NativeRemoteDisplay {
  2. public:
  3. NativeRemoteDisplay(const sp<IRemoteDisplay>& display,
  4. const sp<NativeRemoteDisplayClient>& client) :
  5. mDisplay(display), mClient(client) {
  6. }
  7. ~NativeRemoteDisplay() {
  8. mDisplay->dispose();
  9. }
  10. void pause() {
  11. mDisplay->pause();
  12. }
  13. void resume() {
  14. mDisplay->resume();
  15. }
  16. private:
  17. sp<IRemoteDisplay> mDisplay;
  18. sp<NativeRemoteDisplayClient> mClient;
  19. };

来看一下这时Java层的RemoteDisplay和Native层RemoteDisplay之间的关系:

WifiDisplayController通过左边的一条线路关系去控制WifiDisplaySource,而WifiDisplaySource又通过右边一条线路关系去回调WifiDisplayController的一些方法。

接着来看MediaPlayerService的listenForRemoteDisplay方法:

[java] view plain copy  

  1. sp<IRemoteDisplay> MediaPlayerService::listenForRemoteDisplay(
  2. const sp<IRemoteDisplayClient>& client, const String8& iface) {
  3. if (!checkPermission("android.permission.CONTROL_WIFI_DISPLAY")) {
  4. return NULL;
  5. }
  6. return new RemoteDisplay(client, iface.string());
  7. }

首先进行权限的检查,然后创建一个RemoteDisplay对象(注意现在已经在C++层了),这里看RemoteDisplay.cpp文件。RemoteDisplay继承于BnRemoteDisplay,并实现BnRemoteDisplay中的一些方法,有兴趣的可以去看一下IRemoteDisplay的实现。接下来来看RemoteDisplay的构造函数:

[java] view plain copy  

  1. RemoteDisplay::RemoteDisplay(
  2. const sp<IRemoteDisplayClient> &client,
  3. const char *iface)
  4. : mLooper(new ALooper),
  5. mNetSession(new ANetworkSession) {
  6. mLooper->setName("wfd_looper");
  7. mSource = new WifiDisplaySource(mNetSession, client);
  8. mLooper->registerHandler(mSource);
  9. mNetSession->start();
  10. mLooper->start();
  11. mSource->start(iface);
  12. }

RemoteDisplay类包含三个比较重要的元素:ALooper、ANetworkSession、WifiDisplaySource。首先来看一下在Native层的类图:

ALooper中会创建一个Thread,并且不断的进行Looper循环去收消息,并dispatch给WifiDisplaySource去处理消息。首先来看它的构造函数和setName以及registerHandler这三个方法:

[java] view plain copy  

  1. ALooper::ALooper()
  2. : mRunningLocally(false) {
  3. }
  4. void ALooper::setName(const char *name) {
  5. mName = name;
  6. }
  7. ALooper::handler_id ALooper::registerHandler(const sp<AHandler> &handler) {
  8. return gLooperRoster.registerHandler(this, handler);
  9. }

这三个方法都比较简单,我们看LooperRoster的registerHandler方法:

[java] view plain copy  

  1. ALooper::handler_id ALooperRoster::registerHandler(
  2. const sp<ALooper> looper, const sp<AHandler> &handler) {
  3. Mutex::Autolock autoLock(mLock);
  4. if (handler->id() != 0) {
  5. CHECK(!"A handler must only be registered once.");
  6. return INVALID_OPERATION;
  7. }
  8. HandlerInfo info;
  9. info.mLooper = looper;
  10. info.mHandler = handler;
  11. ALooper::handler_id handlerID = mNextHandlerID++;
  12. mHandlers.add(handlerID, info);
  13. handler->setID(handlerID);
  14. return handlerID;
  15. }

这里为每一个注册的AHandler分配一个handlerID,并且把注册的AHandler保存在mHandlers列表中,后面使用时,就可以快速的通过HandlerID找到对应的AHandler以及ALooper了。注意这里HandlerInfo结构中的mLooper和mHander都是是wp,是一个弱引用,在使用中必须调用其promote方法获取sp指针才能使用。再回到RemoteDisplay的构造函数中看ALooper的start方法:

[java] view plain copy  

  1. status_t ALooper::start(
  2. bool runOnCallingThread, bool canCallJava, int32_t priority) {
  3. if (runOnCallingThread) {
  4. }
  5. Mutex::Autolock autoLock(mLock);
  6. mThread = new LooperThread(this, canCallJava);
  7. status_t err = mThread->run(
  8. mName.empty() ? "ALooper" : mName.c_str(), priority);
  9. if (err != OK) {
  10. mThread.clear();
  11. }
  12. return err;
  13. }

这里的runOnCallingThread会根据默认形参为false,所以会新建一个LooperThread来不断的做循环,LooperThread是继承于Thread,并实现它的readyToRun和threadLoop方法,在threadLoop方法中去调用ALooper的loop方法,代码如下:

[java] view plain copy  

  1. virtual bool threadLoop() {
  2. return mLooper->loop();
  3. }
  4. ALooper::loop() {
  5. Event event;
  6. {
  7. Mutex::Autolock autoLock(mLock);
  8. if (mEventQueue.empty()) {
  9. mQueueChangedCondition.wait(mLock);
  10. return true;
  11. }
  12. int64_t whenUs = (*mEventQueue.begin()).mWhenUs;
  13. int64_t nowUs = GetNowUs();
  14. if (whenUs > nowUs) {
  15. int64_t delayUs = whenUs - nowUs;
  16. mQueueChangedCondition.waitRelative(mLock, delayUs * 1000ll);
  17. return true;
  18. }
  19. event = *mEventQueue.begin();
  20. mEventQueue.erase(mEventQueue.begin());
  21. }
  22. gLooperRoster.deliverMessage(event.mMessage);
  23. return true;

在loop方法中,不断的从mEventQueue取出消息,并dispatch给LooperRoster处理,mEventQueue是一个list链表,其元素都是Event结构,Event结构又包含消息处理的时间以及消息本身AMessage。再来看ALooperRoster的deliverMessage方法:

[java] view plain copy  

  1. void ALooperRoster::deliverMessage(const sp<AMessage> &msg) {
  2. sp<AHandler> handler;
  3. {
  4. Mutex::Autolock autoLock(mLock);
  5. ssize_t index = mHandlers.indexOfKey(msg->target());
  6. if (index < 0) {
  7. ALOGW("failed to deliver message. Target handler not registered.");
  8. return;
  9. }
  10. const HandlerInfo &info = mHandlers.valueAt(index);
  11. handler = info.mHandler.promote();
  12. if (handler == NULL) {
  13. ALOGW("failed to deliver message. "
  14. "Target handler %d registered, but object gone.",
  15. msg->target());
  16. mHandlers.removeItemsAt(index);
  17. return;
  18. }
  19. }
  20. handler->onMessageReceived(msg);
  21. }

这里首先通过AMessage的target找到需要哪个AHandler处理,然后调用这个AHandler的onMessageReceived去处理这个消息。注意前面的info.mHandler.promote()用于当前AHandler的强引用指针,也可以用来判断当前AHandler是否还存活在。由前面的知识我们知道,这里会调用到WifiDisplaySource的onMessageReceived方法,至于这些消息如何被处理,我们后面再来分析。再回到RemoteDisplay的构造函数中,ANetworkSession用于处理与网络请求相关的工作,比如创建socket,从socket中收发数据,当然这些工作都是由WifiDisplaySource控制的,我们先来看ANetworkSession的构造方法和start方法:

[java] view plain copy  

  1. ANetworkSession::ANetworkSession()
  2. : mNextSessionID(1) {
  3. mPipeFd[0] = mPipeFd[1] = -1;
  4. }
  5. status_t ANetworkSession::start() {
  6. if (mThread != NULL) {
  7. return INVALID_OPERATION;
  8. }
  9. int res = pipe(mPipeFd);
  10. if (res != 0) {
  11. mPipeFd[0] = mPipeFd[1] = -1;
  12. return -errno;
  13. }
  14. mThread = new NetworkThread(this);
  15. status_t err = mThread->run("ANetworkSession", ANDROID_PRIORITY_AUDIO);
  16. if (err != OK) {
  17. mThread.clear();
  18. close(mPipeFd[0]);
  19. close(mPipeFd[1]);
  20. mPipeFd[0] = mPipeFd[1] = -1;
  21. return err;
  22. }
  23. return OK;
  24. }

在start方法中,首先创建一个管道,这里创建的管理主要用于让ANetworkSession不断的做select循环,当有事务要处理时,就从select中跳出来处理,我们后面会分析到具体的代码。接着创建一个NetworkThread,NetworkThread也是继承于Thread,并实现threadLoop方法,在threadLoop方法中只是简单的调用ANetworkSession的threadLoop方法,我们来分析threadLoop方法:

[java] view plain copy  

  1. void ANetworkSession::threadLoop() {
  2. fd_set rs, ws;
  3. FD_ZERO(&rs);
  4. FD_ZERO(&ws);
  5. FD_SET(mPipeFd[0], &rs);
  6. int maxFd = mPipeFd[0];
  7. {
  8. Mutex::Autolock autoLock(mLock);
  9. for (size_t i = 0; i < mSessions.size(); ++i) {
  10. const sp<Session> &session = mSessions.valueAt(i);
  11. int s = session->socket();
  12. if (s < 0) {
  13. continue;
  14. }
  15. if (session->wantsToRead()) {
  16. FD_SET(s, &rs);
  17. if (s > maxFd) {
  18. maxFd = s;
  19. }
  20. }
  21. if (session->wantsToWrite()) {
  22. FD_SET(s, &ws);
  23. if (s > maxFd) {
  24. maxFd = s;
  25. }
  26. }
  27. }
  28. }
  29. int res = select(maxFd + 1, &rs, &ws, NULL, NULL /* tv */);
  30. if (res == 0) {
  31. return;
  32. }
  33. if (res < 0) {
  34. if (errno == EINTR) {
  35. return;
  36. }
  37. ALOGE("select failed w/ error %d (%s)", errno, strerror(errno));
  38. return;
  39. }
  40. }

这个函数比较长,我们分段来看,首先看select前半段部分,首先将mPipeFd[0]作为select监听的一个fd。然后循环的从mSessions中取出各个子Session(Session即为一个回话,在RTSP中当双方连接好TCP连接,并交互完Setup以后,就表示一个回话建立成功了,在RTSP中,可以在一对Server & Client之间建立多个回话,用于传输不同的数据),并通过socket类型添加到ReadFd和WirteFd中,最后调用select去等待是否有可读或者可写的事件发生。mSessions是一个KeyedVector,保存所有的Session及其SessionID,方便查找。关于Session何时创建,如何创建,我们后面再来分析。

接着回到RemoteDisplay的构造函数,再来分析WifiDisplaySource,WifiDisplaySource继承于AHandler,并实现其中的onMessageReceived方法用于处理消息。先来看WifiDisplaySource的构造函数:

[java] view plain copy  

  1. WifiDisplaySource::WifiDisplaySource(
  2. const sp<ANetworkSession> &netSession,
  3. const sp<IRemoteDisplayClient> &client,
  4. const char *path)
  5. : mState(INITIALIZED),
  6. mNetSession(netSession),
  7. mClient(client),
  8. mSessionID(0),
  9. mStopReplyID(0),
  10. mChosenRTPPort(-1),
  11. mUsingPCMAudio(false),
  12. mClientSessionID(0),
  13. mReaperPending(false),
  14. mNextCSeq(1),
  15. mUsingHDCP(false),
  16. mIsHDCP2_0(false),
  17. mHDCPPort(0),
  18. mHDCPInitializationComplete(false),
  19. mSetupTriggerDeferred(false),
  20. mPlaybackSessionEstablished(false) {
  21. if (path != NULL) {
  22. mMediaPath.setTo(path);
  23. }
  24. mSupportedSourceVideoFormats.disableAll();
  25. mSupportedSourceVideoFormats.setNativeResolution(
  26. VideoFormats::RESOLUTION_CEA, 5);  // 1280x720 p30
  27. // Enable all resolutions up to 1280x720p30
  28. mSupportedSourceVideoFormats.enableResolutionUpto(
  29. VideoFormats::RESOLUTION_CEA, 5,
  30. VideoFormats::PROFILE_CHP,  // Constrained High Profile
  31. VideoFormats::LEVEL_32);    // Level 3.2
  32. }

首先给一些变量出初始化处理,由默认形参我们知道path为空。接着去清空VideoFormats中所有的设置,并把1280*720p以上的所有分辨率打开。VideoFormats是用于与Sink回复的M3作比对用的,可以快速找出我们和Sink支持的分辨率以及帧率,作为回复M4消息用,也用作后续传输TS数据的格式。首先来看VideoFormats的构造函数:

[java] view plain copy  

  1. VideoFormats::VideoFormats() {
  2. memcpy(mConfigs, mResolutionTable, sizeof(mConfigs));
  3. for (size_t i = 0; i < kNumResolutionTypes; ++i) {
  4. mResolutionEnabled[i] = 0;
  5. }
  6. setNativeResolution(RESOLUTION_CEA, 0);  // default to 640x480 p60
  7. }

mResolutionTable是按照Wifi Display 规范定义好的一个3*32数组,里面的元素是config_t类型:

[java] view plain copy  

  1. struct config_t {
  2. size_t width, height, framesPerSecond;
  3. bool interlaced;
  4. unsigned char profile, level;
  5. };

config_t包含了长、宽、帧率、隔行视频、profile和H.264 level。然后在构造函数中,对mResolutionEnabled[]数组全部置为0,mResolutionEnabled数组有三个元素,分别对应CEA、VESA、HH被选取的位,如果在mConfigs数组中相应的格式被选取,就会置mResolutionEnabled对应的位为1;相反取消支持一种格式时,相应的位就被置为0。在来看setNativeResolution:

[java] view plain copy  

  1. void VideoFormats::setNativeResolution(ResolutionType type, size_t index) {
  2. CHECK_LT(type, kNumResolutionTypes);
  3. CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL));
  4. mNativeType = type;
  5. mNativeIndex = index;
  6. setResolutionEnabled(type, index);
  7. }

首先做参数检查,检查输入的type和index是否合法,然后调用setResolutionEnabled去设置mResolutionEnabled和mConfigs中的相应的值:

[java] view plain copy  

  1. void VideoFormats::setResolutionEnabled(
  2. ResolutionType type, size_t index, bool enabled) {
  3. CHECK_LT(type, kNumResolutionTypes);
  4. CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL));
  5. if (enabled) {
  6. mResolutionEnabled[type] |= (1ul << index);
  7. mConfigs[type][index].profile = (1ul << PROFILE_CBP);
  8. mConfigs[type][index].level = (1ul << LEVEL_31);
  9. else {
  10. mResolutionEnabled[type] &= ~(1ul << index);
  11. mConfigs[type][index].profile = 0;
  12. mConfigs[type][index].level = 0;
  13. }
  14. }

这里首先还是做参数的检查,由默认形参我们知道,enable是true,则设置mResolutionEnabled相应type中的对应格式为1,并设置mConfigs中的profile和level值为CBP和Level 3.1。这里设置640*480 p60是因为在Wifi Display规范中,这个格式是必须要强制支持的,在Miracast认证中,这种格式也会被测试到。然后回到WifiDisplaySource的构造函数中,接下来会调用setNativeResolution去设置当前系统支持的默认格式为1280*720 p30,并调用enableResolutionUpto去将1280*720 p30以上的格式都设置为支持:

[java] view plain copy  

  1. void VideoFormats::enableResolutionUpto(
  2. ResolutionType type, size_t index,
  3. ProfileType profile, LevelType level) {
  4. size_t width, height, fps, score;
  5. bool interlaced;
  6. if (!GetConfiguration(type, index, &width, &height,
  7. &fps, &interlaced)) {
  8. ALOGE("Maximum resolution not found!");
  9. return;
  10. }
  11. score = width * height * fps * (!interlaced + 1);
  12. for (size_t i = 0; i < kNumResolutionTypes; ++i) {
  13. for (size_t j = 0; j < 32; j++) {
  14. if (GetConfiguration((ResolutionType)i, j,
  15. &width, &height, &fps, &interlaced)
  16. && score >= width * height * fps * (!interlaced + 1)) {
  17. setResolutionEnabled((ResolutionType)i, j);
  18. setProfileLevel((ResolutionType)i, j, profile, level);
  19. }
  20. }
  21. }
  22. }

这里采用width * height * fps * (!interlaced + 1)的方式去计算一个score值,然后遍历所有的mResolutionTable中的值去检查是否计算到的值比当前score要高,如果大于当前score值,就将这种分辨率enable,并设置mConfigs中对应分辨率的profile和H.264 level为CHP和Level 3.2。到这里WifiDisplaySource的构造函数分析完了,接着回到RemoteDisplay构造函数中,它会调用WifiDisplaySource的start方法,参数是的"ip:rtspPort":

[java] view plain copy  

  1. status_t WifiDisplaySource::start(const char *iface) {
  2. CHECK_EQ(mState, INITIALIZED);
  3. sp<AMessage> msg = new AMessage(kWhatStart, id());
  4. msg->setString("iface", iface);
  5. sp<AMessage> response;
  6. return PostAndAwaitResponse(msg, &response);
  7. }
  8. static status_t PostAndAwaitResponse(
  9. const sp<AMessage> &msg, sp<AMessage> *response) {
  10. status_t err = msg->postAndAwaitResponse(response);
  11. if (err != OK) {
  12. return err;
  13. }
  14. if (response == NULL || !(*response)->findInt32("err", &err)) {
  15. err = OK;
  16. }
  17. return err;
  18. }

在start函数中,构造一个AMessage,消息种类是kWhatStart,id()返回在ALooperRoster注册的handlerID值,ALooperRoster通过handlerID值可以快速找到对应的AHandler,我们知道,这里的id()返回WifiDisplaySource这个AHander的id值,这个消息最终也会被WifiDisplaySource的onMessageReceived方法处理。首先来看AMessage的postAndAwaitResponse方法:

[java] view plain copy  

  1. status_t AMessage::postAndAwaitResponse(sp<AMessage> *response) {
  2. return gLooperRoster.postAndAwaitResponse(this, response);
  3. }

这里直接调用LooperRoster的postAndAwaitResponse方法,这里比较重要的是gLooperRoster在这里只是被extern引用:extern ALooperRoster gLooperRoster,其最终的声明和定义是在我们前面讲到的ALooper中。接着去看LooperRoster的postAndAwaitResponse方法:

[java] view plain copy  

  1. status_t ALooperRoster::postAndAwaitResponse(
  2. const sp<AMessage> &msg, sp<AMessage> *response) {
  3. Mutex::Autolock autoLock(mLock);
  4. uint32_t replyID = mNextReplyID++;
  5. msg->setInt32("replyID", replyID);
  6. status_t err = postMessage_l(msg, 0 /* delayUs */);
  7. if (err != OK) {
  8. response->clear();
  9. return err;
  10. }
  11. ssize_t index;
  12. while ((index = mReplies.indexOfKey(replyID)) < 0) {
  13. mRepliesCondition.wait(mLock);
  14. }
  15. *response = mReplies.valueAt(index);
  16. mReplies.removeItemsAt(index);
  17. return OK;
  18. }

首先会为每个需要reply的消息赋予一个replyID,后面会根据这个replyID去mReplies找到对应的response。再来看postMessage_l的实现:

[java] view plain copy  

  1. status_t ALooperRoster::postMessage_l(
  2. const sp<AMessage> &msg, int64_t delayUs) {
  3. ssize_t index = mHandlers.indexOfKey(msg->target());
  4. if (index < 0) {
  5. ALOGW("failed to post message '%s'. Target handler not registered.",
  6. msg->debugString().c_str());
  7. return -ENOENT;
  8. }
  9. const HandlerInfo &info = mHandlers.valueAt(index);
  10. sp<ALooper> looper = info.mLooper.promote();
  11. if (looper == NULL) {
  12. ALOGW("failed to post message. "
  13. "Target handler %d still registered, but object gone.",
  14. msg->target());
  15. mHandlers.removeItemsAt(index);
  16. return -ENOENT;
  17. }
  18. looper->post(msg, delayUs);
  19. return OK;
  20. }

首先从mHandler数组中找到当前AMessage对应的ALooper,然后调用ALooper的post方法,来看一下实现:

[java] view plain copy  

  1. void ALooper::post(const sp<AMessage> &msg, int64_t delayUs) {
  2. Mutex::Autolock autoLock(mLock);
  3. int64_t whenUs;
  4. if (delayUs > 0) {
  5. whenUs = GetNowUs() + delayUs;
  6. else {
  7. whenUs = GetNowUs();
  8. }
  9. List<Event>::iterator it = mEventQueue.begin();
  10. while (it != mEventQueue.end() && (*it).mWhenUs <= whenUs) {
  11. ++it;
  12. }
  13. Event event;
  14. event.mWhenUs = whenUs;
  15. event.mMessage = msg;
  16. if (it == mEventQueue.begin()) {
  17. mQueueChangedCondition.signal();
  18. }
  19. mEventQueue.insert(it, event);
  20. }

delayUs用于做延时消息使用,会加上当前时间作为消息应该被处理的时间。然后依次比较mEventQueue链表中的所有消息,并把当前消息插入到比whenUs大的前面一个位置。如果这是mEventQueue中的第一个消息,则发出一个signal通知等待的线程。前面我们知道在ALooper的loop方法中会循环的从mEventQueue获取消息并dispatch出去给WifiDisplaySource的onMessageReceived去处理,我们接着来看这部分的实现。这里绕这么大一圈,最后WifiDisplaySource发送的消息还是给自己处理,主要是为了避开主线程处理的事务太多,通过消息机制,让更多的繁杂的活都在Thread中去完成。

[java] view plain copy  

  1. void WifiDisplaySource::onMessageReceived(const sp<AMessage> &msg) {
  2. switch (msg->what()) {
  3. case kWhatStart:
  4. {
  5. uint32_t replyID;
  6. CHECK(msg->senderAwaitsResponse(&replyID));
  7. AString iface;
  8. CHECK(msg->findString("iface", &iface));
  9. status_t err = OK;
  10. ssize_t colonPos = iface.find(":");
  11. unsigned long port;
  12. if (colonPos >= 0) {
  13. const char *s = iface.c_str() + colonPos + 1;
  14. char *end;
  15. port = strtoul(s, &end, 10);
  16. if (end == s || *end != '\0' || port > 65535) {
  17. err = -EINVAL;
  18. else {
  19. iface.erase(colonPos, iface.size() - colonPos);
  20. }
  21. else {
  22. port = kWifiDisplayDefaultPort;
  23. }
  24. if (err == OK) {
  25. if (inet_aton(iface.c_str(), &mInterfaceAddr) != 0) {
  26. sp<AMessage> notify = new AMessage(kWhatRTSPNotify, id());
  27. err = mNetSession->createRTSPServer(
  28. mInterfaceAddr, port, notify, &mSessionID);
  29. else {
  30. err = -EINVAL;
  31. }
  32. }
  33. mState = AWAITING_CLIENT_CONNECTION;
  34. sp<AMessage> response = new AMessage;
  35. response->setInt32("err", err);
  36. response->postReply(replyID);
  37. break;
  38. }

首先通过AMessage获取到replayID和iface,然后把iface分割成ip和port,分别保存在mInterfaceAddr和port中。在调用ANetSession的createRTSPServer去创建一个RTSP server,最后构造一个response对象并返回。我们先来看createRTSPServer方法:

[java] view plain copy  

  1. status_t ANetworkSession::createRTSPServer(
  2. const struct in_addr &addr, unsigned port,
  3. const sp<AMessage> ¬ify, int32_t *sessionID) {
  4. return createClientOrServer(
  5. kModeCreateRTSPServer,
  6. &addr,
  7. port,
  8. NULL /* remoteHost */,
  9. 0 /* remotePort */,
  10. notify,
  11. sessionID);
  12. }
  13. status_t ANetworkSession::createClientOrServer(
  14. Mode mode,
  15. const struct in_addr *localAddr,
  16. unsigned port,
  17. const char *remoteHost,
  18. unsigned remotePort,
  19. const sp<AMessage> ¬ify,
  20. int32_t *sessionID) {
  21. Mutex::Autolock autoLock(mLock);
  22. *sessionID = 0;
  23. status_t err = OK;
  24. int s, res;
  25. sp<Session> session;
  26. s = socket(
  27. AF_INET,
  28. (mode == kModeCreateUDPSession) ? SOCK_DGRAM : SOCK_STREAM,
  29. 0);
  30. if (s < 0) {
  31. err = -errno;
  32. goto bail;
  33. }
  34. if (mode == kModeCreateRTSPServer
  35. || mode == kModeCreateTCPDatagramSessionPassive) {
  36. const int yes = 1;
  37. res = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
  38. if (res < 0) {
  39. err = -errno;
  40. goto bail2;
  41. }
  42. }
  43. err = MakeSocketNonBlocking(s);
  44. if (err != OK) {
  45. goto bail2;
  46. }
  47. struct sockaddr_in addr;
  48. memset(addr.sin_zero, 0, sizeof(addr.sin_zero));
  49. addr.sin_family = AF_INET;
  50. else if (localAddr != NULL) {
  51. addr.sin_addr = *localAddr;
  52. addr.sin_port = htons(port);
  53. res = bind(s, (const struct sockaddr *)&addr, sizeof(addr));
  54. if (res == 0) {
  55. if (mode == kModeCreateRTSPServer
  56. || mode == kModeCreateTCPDatagramSessionPassive) {
  57. res = listen(s, 4);
  58. else {
  59. if (res < 0) {
  60. err = -errno;
  61. goto bail2;
  62. }
  63. Session::State state;
  64. switch (mode) {
  65. case kModeCreateRTSPServer:
  66. state = Session::LISTENING_RTSP;
  67. break;
  68. default:
  69. CHECK_EQ(mode, kModeCreateUDPSession);
  70. state = Session::DATAGRAM;
  71. break;
  72. }
  73. session = new Session(
  74. mNextSessionID++,
  75. state,
  76. s,
  77. notify);
  78. mSessions.add(session->sessionID(), session);
  79. interrupt();
  80. *sessionID = session->sessionID();
  81. goto bail;
  82. bail2:
  83. close(s);
  84. s = -1;
  85. bail:
  86. return err;
  87. }

createRTSPServer直接调用createClientOrServer,第一个参数是kModeCreateRTSPServer表示要创建一个RTSP server。createClientOrServer的代码比较长,上面是精简后的代码,其它没看到的代码我们以后遇到的过程中再来分析。上面的代码中首先创建一个socket,然后设置一下socket的reuse和no-block属性,接着bind到指定的IP和port上,然后再此socket上开始listen。接下来置当前ANetworkSession的状态是LISTENING_RTSP。然后创建一个Session会话对象,在构造函数中会传入notify作为参数,notify是一个kWhatRTSPNotify的AMessag,后面会看到如何使用它。然后添加到mSessions数组当中。接着调用interrupt方法,让ANetworkSession的NetworkThread线程跳出select语句,并重新计算readFd和writeFd用于select监听的文件句柄。

[java] view plain copy  

  1. void ANetworkSession::interrupt() {
  2. static const char dummy = 0;
  3. ssize_t n;
  4. do {
  5. n = write(mPipeFd[1], &dummy, 1);
  6. while (n < 0 && errno == EINTR);
  7. if (n < 0) {
  8. ALOGW("Error writing to pipe (%s)", strerror(errno));
  9. }
  10. }

interrupt方法向pipe中写入一个空消息,前面我们已经介绍过threadLoop了,这里就会把刚刚创建的socket加入到监听的readFd中。到这里,关于WifiDisplay连接的建立就讲完了,后面会再从收到Sink的TCP连接请求开始讲起。最后贴一份从WifiDisplaySettings到ANetworkSession如何创建socket的时序图:

Android WifiDisplay分析二:Wifi display连接过程相关推荐

  1. Android WifiDisplay分析一:相关Service的启动

    网址:http://www.2cto.com/kf/201404/290996.html 最近在学习Android 4.4上面的WifiDisplay(Miracast)相关的模块,这里先从WifiD ...

  2. Android Telephony分析(二) ---- RegistrantList详解

    前言 本文主要讲解RegistrantList的原理,以及如何快速分析RegistrantList相关的代码流程.  在Telephony模块中,在RIL.Tracker(ServiceStateTr ...

  3. Media Player Classic - HC 源代码分析 14:PIN连接过程中推模式和拉模式区别

    前面有两篇文章讲解了PIN连接过程中需要做2件事情: 媒体类型的协商 分配器的协商 推模式和拉模式关于媒体类型的协商调用的接口都一样,主要的区别还是在分配器的协商. 前面讲解的分配器的协商过程是推模式 ...

  4. Android Wi-Fi Display(Miracast)介绍

    Android Wi-Fi Display(Miracast)介绍 2012年11月中旬,Google发布了Android 4.2.虽然它和Android 4.1同属Jelly Bean系列,但却添加 ...

  5. android wifi display

    2012年11月中旬,Google发布了Android 4.2.虽然它和Android 4.1同属Jelly Bean系列,但却添加了很多新的功能.其中,在显示部分,Android 4.2在Proje ...

  6. Android 根据账号和密码自动连接 WIFI (兼容Android 10)

    源码地址在这里在这里!!!!!master分支哈 文章目录 1 遇到困难 2 源代码 2.1 IWifiConnectListener.java 2.2 IWifiDisConnectListener ...

  7. Android Telephony分析(三) ---- RILJ详解

    前言 本文主要讲解RILJ工作原理,以便更好地分析代码,分析业务的流程.  这里说的RILJ指的是RIL.java (frameworks\opt\telephony\src\java\com\And ...

  8. Android Telephony分析(六) ---- 接口扩展(实践篇)

    本文将结合前面五篇文章所讲解的知识,综合起来,实现一个接口扩展的功能.  如果还没有阅读过前面五篇文章的内容,请先阅读:  <Android Telephony分析(一) - Phone详解 & ...

  9. 【全教程】qt连接mysql——从qt编译mysql驱动到qt连接mysql数据库(二、编译连接)

    本篇教程分为三个部分: [全教程]qt连接mysql--从qt编译mysql驱动到qt连接mysql数据库(一.编译连接前准备) [全教程]qt连接mysql--从qt编译mysql驱动到qt连接my ...

最新文章

  1. ES6语法~解构赋值、箭头函数、class类继承及属性方法、map、set、symbol、rest、new.target、 Object.entries......
  2. 无后端完成在线翻译功能
  3. 关于ubuntu 14.04 dpkg 问题
  4. 遍历二叉树的全部方法(递归+非递归)
  5. MySQL 优化 —— MySQL 如何使用索引
  6. GDC2017分享:移动VR开发者的赚钱之道
  7. 非常详细GC学习笔记
  8. 安全测试(初测)报告
  9. 2021年应届生,找java后端开发要什么水平才算合格?
  10. Django在Terminal中python manage.py startapp myapp报name ‘os‘ is not defined
  11. Pyecharts 1.7.0制作图表,运行生成的html文件用浏览器打开空白问题(以桑基图为例)
  12. Unity Loading转场学习笔记
  13. 背后的力量 | 升级电子病历基础架构 华云数据助力华中科技大学同济医学院附属协和医院打造就医新模式
  14. 2022前端面试需要掌握的面试题
  15. linux下代码写错了怎么更改_谢宝友:手把手教你给Linux内核发patch
  16. 2020-05-25
  17. 如何分析用户复购行为?
  18. 《薛兆丰经济学讲义》阅读笔记|第一节-真实世界 经济学的视角
  19. 目标检测(后处理):从 NMS 到 Soft-NMS 到 Softer NMS
  20. linux 实验感悟_linux实训心得_linux实习心得体会范文

热门文章

  1. 使用Selenium获取银行账户余额
  2. 《敦泰IC之FT6336的TP代码跟读笔记》
  3. python encoding是什么_python中encoding什么意思
  4. Percona监控数据库解决方案
  5. Mac系统下Gauge初体验
  6. 2022.01.19 - SX10-23.零钱兑换
  7. Ubuntu一次更改用户组后,qv2ray不能运行的修复
  8. 【深度之眼PyTorch框架班第五期】作业打卡01:PyTorch简介及环境配置;PyTorch基础数据结构——张量
  9. 使用 Google Apps 账号申请 GAE 的问题
  10. 配置系统时钟(stm32)