Android WifiDisplay分析二:Wifi display連接過程

文章轉載自:李煉        原文地址:http://blog.csdn.net/lilian0118/article/details/23168531

這一章中我們來看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 plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void pairWifiDisplay(WifiDisplay display) {  
  2.     if (display.canConnect()) {  
  3.         mDisplayManager.connectWifiDisplay(display.getDeviceAddress());  
  4.     }  
  5. }  

WifiDisplaySettings通過AIDL調用到DisplayManagerService的connectWifiDisplay方法,關於AIDL的調用過程這裏不講了,直接到DisplayManagerService的connectWifiDisplay方法來看:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public void connectWifiDisplay(String address) {  
  2.     if (address == null) {  
  3.         throw new IllegalArgumentException("address must not be null");  
  4.     }  
  5.     mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,  
  6.             "Permission required to connect to a wifi display");  
  7.   
  8.     final long token = Binder.clearCallingIdentity();  
  9.     try {  
  10.         synchronized (mSyncRoot) {  
  11.             if (mWifiDisplayAdapter != null) {  
  12.                 mWifiDisplayAdapter.requestConnectLocked(address);  
  13.             }  
  14.         }  
  15.     } finally {  
  16.         Binder.restoreCallingIdentity(token);  
  17.     }  
  18. }  

首先做參數的檢查,即MAC地址不能爲空,然後做權限檢查,調用這個方法的application必須要在manifest中聲明有CONFIGURE_WIFI_DISPLAY權限,最後直接調用WifiDisplayAdapter的requestConnectLocked方法:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public void requestConnectLocked(final String address) {  
  2.     if (DEBUG) {  
  3.         Slog.d(TAG, "requestConnectLocked: address=" + address);  
  4.     }  
  5.   
  6.     getHandler().post(new Runnable() {  
  7.         @Override  
  8.         public void run() {  
  9.             if (mDisplayController != null) {  
  10.                 mDisplayController.requestConnect(address);  
  11.             }  
  12.         }  
  13.     });  
  14. }  

這裏比較簡單,直接調用WifiDisplayController的requestConnect方法。前面都是直接的調用,最終做事情的還是WifiDisplayController。
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public void requestConnect(String address) {  
  2.     for (WifiP2pDevice device : mAvailableWifiDisplayPeers) {  
  3.         if (device.deviceAddress.equals(address)) {  
  4.             connect(device);  
  5.         }  
  6.     }  
  7. }  
  8.   
  9. private void connect(final WifiP2pDevice device) {  
  10.     if (mDesiredDevice != null  
  11.             && !mDesiredDevice.deviceAddress.equals(device.deviceAddress)) {  
  12.         if (DEBUG) {  
  13.             Slog.d(TAG, "connect: nothing to do, already connecting to "  
  14.                     + describeWifiP2pDevice(device));  
  15.         }  
  16.         return;  
  17.     }  
  18.   
  19.     if (mConnectedDevice != null  
  20.             && !mConnectedDevice.deviceAddress.equals(device.deviceAddress)  
  21.             && mDesiredDevice == null) {  
  22.         if (DEBUG) {  
  23.             Slog.d(TAG, "connect: nothing to do, already connected to "  
  24.                     + describeWifiP2pDevice(device) + " and not part way through "  
  25.                     + "connecting to a different device.");  
  26.         }  
  27.         return;  
  28.     }  
  29.   
  30.     if (!mWfdEnabled) {  
  31.         Slog.i(TAG, "Ignoring request to connect to Wifi display because the "  
  32.                 +" feature is currently disabled: " + device.deviceName);  
  33.         return;  
  34.     }  
  35.   
  36.     mDesiredDevice = device;  
  37.     mConnectionRetriesLeft = CONNECT_MAX_RETRIES;  
  38.     updateConnection();  
  39. }  

requestConnect先從mAvaiableWifiDsiplayPeers中通過Mac地址找到所有連接的WifiP2pDevice,然後調用connect方法,在connect方法中會做一系列的判斷,看首先是否有正在連接中或者斷開中的設備,如果有就直接返回;再看有沒有已經連接上的設備,如果有,也直接返回,然後賦值mDesiredDevice爲這次要連接的設備,最後調用updateConnection來更新連接狀態併發起連接。updateConnection的代碼比較長,我們分段來分析:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void updateConnection() {  
  2. n style="white-space:pre">  </span>//更新是否需要scan或者停止scan  
  3.     updateScanState();  
  4.   
  5. n style="white-space:pre">  </span>//如果有已經連接上的RemoteDisplay,先斷開。這裏先不看  
  6.     if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {  
  7.           
  8.     }  
  9.   
  10.     // 接上面的一步,段開這個group  
  11.     if (mDisconnectingDevice != null) {  
  12.         return// wait for asynchronous callback  
  13.     }  
  14.     if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {  
  15.   
  16.     }  
  17.   
  18.     // 如果有正在連接的設備,先停止連接之前的設備  
  19.     if (mCancelingDevice != null) {  
  20.         return// wait for asynchronous callback  
  21.     }  
  22.     if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {  
  23.           
  24.     }  
  25.   
  26.     // 當斷開之前的連接或者啓動匿名GROUP時,這裏就結束了  
  27.     if (mDesiredDevice == null) {  
  28.   
  29.     }  
  30.   
  31.     // 開始連接,這是我們要看的重點  
  32.     if (mConnectedDevice == null && mConnectingDevice == null) {  
  33.         Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName);  
  34.   
  35.         mConnectingDevice = mDesiredDevice;  
  36.         WifiP2pConfig config = new WifiP2pConfig();  
  37.         WpsInfo wps = new WpsInfo();  
  38.         if (mWifiDisplayWpsConfig != WpsInfo.INVALID) {  
  39.             wps.setup = mWifiDisplayWpsConfig;  
  40.         } else if (mConnectingDevice.wpsPbcSupported()) {  
  41.             wps.setup = WpsInfo.PBC;  
  42.         } else if (mConnectingDevice.wpsDisplaySupported()) {  
  43.             wps.setup = WpsInfo.KEYPAD;  
  44.         } else {  
  45.             wps.setup = WpsInfo.DISPLAY;  
  46.         }  
  47.         config.wps = wps;  
  48.         config.deviceAddress = mConnectingDevice.deviceAddress;  
  49.         config.groupOwnerIntent = WifiP2pConfig.MIN_GROUP_OWNER_INTENT;  
  50.   
  51.         WifiDisplay display = createWifiDisplay(mConnectingDevice);  
  52.         advertiseDisplay(display, null000);  
  53.   
  54.         final WifiP2pDevice newDevice = mDesiredDevice;  
  55.         mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() {  
  56.             @Override  
  57.             public void onSuccess() {  
  58.                 Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName);  
  59.   
  60.                 mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000);  
  61.             }  
  62.   
  63.             @Override  
  64.             public void onFailure(int reason) {  
  65.                 if (mConnectingDevice == newDevice) {  
  66.                     Slog.i(TAG, "Failed to initiate connection to Wifi display: "  
  67.                             + newDevice.deviceName + ", reason=" + reason);  
  68.                     mConnectingDevice = null;  
  69.                     handleConnectionFailure(false);  
  70.                 }  
  71.             }  
  72.         });  
  73.         return;   
  74.     }<span style="font-family: Arial, Helvetica, sans-serif;">   </span>  

這段函數比較長,我們先看我們需要的,剩下的後面再來分析。首先賦值給mConnectingDevice表示當前正在連接的設備,然後構造一個WifiP2pConfig對象,這個對象包含這次連接的設備的Mac地址、wps方式以及我們自己的GROUP_OWNER intent值,然後調用advertieseDisplay方法來通知WifiDisplayAdapter相關狀態的改變,WifiDisplayAdapter會發送相應的broadcast出來,這是WifiDisplaySettings可以接收這些broadcast,然後在UI上更新相應的狀態。關於advertieseDisplay的實現,我們後面再來分析。

接着看updateConnection,調用WifiP2pManager的connect方法去實現兩臺設備的P2P連接,具體過程可以參考前面介紹的P2P連接的文章。這裏的onSuccess()並不是表示P2P已經建立成功,而只是表示這個發送命令到wpa_supplicant成功,所以在這裏設置了一個連接超時的timeout,爲30秒。當連接成功後,會發送WIFI_P2P_CONNECTION_CHANGED_ACTION的廣播出來,接着回到WifiDisplayController看如何處理連接成功的broadcast:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1.         } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) {  
  2.             NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(  
  3.                     WifiP2pManager.EXTRA_NETWORK_INFO);  
  4.             if (DEBUG) {  
  5.                 Slog.d(TAG, "Received WIFI_P2P_CONNECTION_CHANGED_ACTION: networkInfo="  
  6.                         + networkInfo);  
  7.             }  
  8.   
  9.             handleConnectionChanged(networkInfo);  
  10.   
  11. private void handleConnectionChanged(NetworkInfo networkInfo) {  
  12.     mNetworkInfo = networkInfo;  
  13.     if (mWfdEnabled && networkInfo.isConnected()) {  
  14.         if (mDesiredDevice != null || mWifiDisplayCertMode) {  
  15.             mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() {  
  16.                 @Override  
  17.                 public void onGroupInfoAvailable(WifiP2pGroup info) {  
  18.                     if (DEBUG) {  
  19.                         Slog.d(TAG, "Received group info: " + describeWifiP2pGroup(info));  
  20.                     }  
  21.   
  22.                     if (mConnectingDevice != null && !info.contains(mConnectingDevice)) {  
  23.                         Slog.i(TAG, "Aborting connection to Wifi display because "  
  24.                                 + "the current P2P group does not contain the device "  
  25.                                 + "we expected to find: " + mConnectingDevice.deviceName  
  26.                                 + ", group info was: " + describeWifiP2pGroup(info));  
  27.                         handleConnectionFailure(false);  
  28.                         return;  
  29.                     }  
  30.   
  31.                     if (mDesiredDevice != null && !info.contains(mDesiredDevice)) {  
  32.                         disconnect();  
  33.                         return;  
  34.                     }  
  35.   
  36.                     if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {  
  37.                         Slog.i(TAG, "Connected to Wifi display: "  
  38.                                 + mConnectingDevice.deviceName);  
  39.   
  40.                         mHandler.removeCallbacks(mConnectionTimeout);  
  41.                         mConnectedDeviceGroupInfo = info;  
  42.                         mConnectedDevice = mConnectingDevice;  
  43.                         mConnectingDevice = null;  
  44.                         updateConnection();  
  45.                     }  
  46.                 }  
  47.             });  
  48.         }  
  49.     }  

當WifiDisplayController收到WIFI_P2P_CONNECTION_CHANGED_ACTION廣播後,會調用handleConnectionChanged來獲取當前P2P Group相關的信息,如果獲取到的P2P Group信息裏面沒有mConnectingDevice或者mDesiredDevice的信息,則表示連接出錯了,直接退出。如果當前連接信息與前面設置的mConnectingDevice一直,則表示連接P2P成功,這裏首先會移除前面設置的連接timeout的callback,然後設置mConnectedDevice爲當前連接的設備,並設置mConnectingDevice爲空,最後調用updateConnection來更新連接狀態信息。我們又回到updateConnection這個函數了,但這次進入的分支與之前連接請求的分支又不同了,我們來看代碼:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void updateConnection() {  
  2.      // 更新是否需要scan或者停止scan  
  3.      updateScanState();  
  4.   
  5.      // 如果有連接上的RemoteDisplay,這裏先斷開  
  6.      if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {  
  7.   
  8.      }  
  9.   
  10.      // 接着上面的一步,先斷開之前連接的設備  
  11.      if (mDisconnectingDevice != null) {  
  12.          return// wait for asynchronous callback  
  13.      }  
  14.      if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {  
  15.   
  16.      }  
  17.   
  18.      // 如果有正在連接的設備,先斷開之前連接的設備  
  19.      if (mCancelingDevice != null) {  
  20.          return// wait for asynchronous callback  
  21.      }  
  22.      if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {  
  23.   
  24.      }  
  25.   
  26.      // 當斷開之前的連接或者匿名GO時,這裏就結束了  
  27.      if (mDesiredDevice == null) {  
  28.   
  29.      }  
  30.   
  31.      // 如果有連接請求,則進入此  
  32.      if (mConnectedDevice == null && mConnectingDevice == null) {  
  33.   
  34.      }  
  35.   
  36.      // 當連接上P2P後,就進入到此  
  37.      if (mConnectedDevice != null && mRemoteDisplay == null) {  
  38.          Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);  
  39.          if (addr == null) {  
  40.              Slog.i(TAG, "Failed to get local interface address for communicating "  
  41.                      + "with Wifi display: " + mConnectedDevice.deviceName);  
  42.              handleConnectionFailure(false);  
  43.              return// done  
  44.          }  
  45.   
  46.          mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_SOURCE);  
  47.   
  48.          final WifiP2pDevice oldDevice = mConnectedDevice;  
  49.          final int port = getPortNumber(mConnectedDevice);  
  50.          final String iface = addr.getHostAddress() + ":" + port;  
  51.          mRemoteDisplayInterface = iface;  
  52.   
  53.          Slog.i(TAG, "Listening for RTSP connection on " + iface  
  54.                  + " from Wifi display: " + mConnectedDevice.deviceName);  
  55.   
  56.          mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() {  
  57.              @Override  
  58.              public void onDisplayConnected(Surface surface,  
  59.                      int width, int height, int flags, int session) {  
  60.                  if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) {  
  61.                      Slog.i(TAG, "Opened RTSP connection with Wifi display: "  
  62.                              + mConnectedDevice.deviceName);  
  63.                      mRemoteDisplayConnected = true;  
  64.                      mHandler.removeCallbacks(mRtspTimeout);  
  65.   
  66.                      if (mWifiDisplayCertMode) {  
  67.                          mListener.onDisplaySessionInfo(  
  68.                                  getSessionInfo(mConnectedDeviceGroupInfo, session));  
  69.                      }  
  70.   
  71.                      final WifiDisplay display = createWifiDisplay(mConnectedDevice);  
  72.                      advertiseDisplay(display, surface, width, height, flags);  
  73.                  }  
  74.              }  
  75.   
  76.              @Override  
  77.              public void onDisplayDisconnected() {  
  78.                  if (mConnectedDevice == oldDevice) {  
  79.                      Slog.i(TAG, "Closed RTSP connection with Wifi display: "  
  80.                              + mConnectedDevice.deviceName);  
  81.                      mHandler.removeCallbacks(mRtspTimeout);  
  82.                      disconnect();  
  83.                  }  
  84.              }  
  85.   
  86.              @Override  
  87.              public void onDisplayError(int error) {  
  88.                  if (mConnectedDevice == oldDevice) {  
  89.                      Slog.i(TAG, "Lost RTSP connection with Wifi display due to error "  
  90.                              + error + ": " + mConnectedDevice.deviceName);  
  91.                      mHandler.removeCallbacks(mRtspTimeout);  
  92.                      handleConnectionFailure(false);  
  93.                  }  
  94.              }  
  95.          }, mHandler);  
  96.   
  97.          // Use extended timeout value for certification, as some tests require user inputs  
  98.          int rtspTimeout = mWifiDisplayCertMode ?  
  99.                  RTSP_TIMEOUT_SECONDS_CERT_MODE : RTSP_TIMEOUT_SECONDS;  
  100.   
  101.          mHandler.postDelayed(mRtspTimeout, rtspTimeout * 1000);  
  102.      }  
  103.  }  

到這裏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 plaincopy在CODE上查看代碼片派生到我的代碼片
  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.   
  12.     RemoteDisplay display = new RemoteDisplay(listener, handler);  
  13.     display.startListening(iface);  
  14.     return display;  
  15. }  

這裏首先進行參數的檢查,然後創建一個RemoteDisplay對象(這裏不能直接創建RemoteDisplay對象,因爲它的構造函數是private的),接着調用RemoteDisplay的startListening方法:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  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 plaincopy在CODE上查看代碼片派生到我的代碼片
  1. static jint nativeListen(JNIEnv* env, jobject remoteDisplayObj, jstring ifaceStr) {  
  2.     ScopedUtfChars iface(env, ifaceStr);  
  3.   
  4.     sp<IServiceManager> sm = defaultServiceManager();  
  5.     sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(  
  6.             sm->getService(String16("media.player")));  
  7.     if (service == NULL) {  
  8.         ALOGE("Could not obtain IMediaPlayerService from service manager");  
  9.         return 0;  
  10.     }  
  11.   
  12.     sp<NativeRemoteDisplayClient> client(new NativeRemoteDisplayClient(env, remoteDisplayObj));  
  13.     sp<IRemoteDisplay> display = service->listenForRemoteDisplay(  
  14.             client, String8(iface.c_str()));  
  15.     if (display == NULL) {  
  16.         ALOGE("Media player service rejected request to listen for remote display '%s'.",  
  17.                 iface.c_str());  
  18.         return 0;  
  19.     }  
  20.   
  21.     NativeRemoteDisplay* wrapper = new NativeRemoteDisplay(display, client);  
  22.     return reinterpret_cast<jint>(wrapper);  
  23. }  

上面的代碼中先從ServiceManager中獲取MediaPlayerService的Bpbinder引用,然後由傳入的第二個參數remoteDisplayObj,也就是RemoteDisplay對象構造一個NativeRemoteDisplayClient,在framework中,我們經常看到像這樣的用法,類似於設計模式中的包裝模式,例如在framework中對Java層的BnBinder也是做了一層封裝JavaBBinder。在NativeRemoteDisplayClient中通過JNI的反向調用,就可以直接回調RemoteDisplay中的一些函數,實現回調方法了,下面來看它的實現:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. class NativeRemoteDisplayClient : public BnRemoteDisplayClient {  
  2. public:  
  3.     NativeRemoteDisplayClient(JNIEnv* env, jobject remoteDisplayObj) :  
  4.             mRemoteDisplayObjGlobal(env->NewGlobalRef(remoteDisplayObj)) {  
  5.     }  
  6.   
  7. protected:  
  8.     ~NativeRemoteDisplayClient() {  
  9.         JNIEnv* env = AndroidRuntime::getJNIEnv();  
  10.         env->DeleteGlobalRef(mRemoteDisplayObjGlobal);  
  11.     }  
  12.   
  13. public:  
  14.     virtual void onDisplayConnected(const sp<IGraphicBufferProducer>& bufferProducer,  
  15.             uint32_t width, uint32_t height, uint32_t flags, uint32_t session) {  
  16.         env->CallVoidMethod(mRemoteDisplayObjGlobal,  
  17.                 gRemoteDisplayClassInfo.notifyDisplayConnected,  
  18.                 surfaceObj, width, height, flags, session);  
  19.     }  
  20.   
  21.     virtual void onDisplayDisconnected() {  
  22.   
  23.     }  
  24.   
  25.     virtual void onDisplayError(int32_t error) {  
  26.   
  27.     }  
  28.   
  29. private:  
  30.     jobject mRemoteDisplayObjGlobal;  
  31.   
  32.     static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {  
  33.   
  34.         }  
  35.     }  
  36. };  

在NativeRemoteDisplayClient的構造函數中,把RemoteDisplay對象先保存到mRemoteDisplayObjGlobal中,可以看到上面主要實現了三個回調函數,onDisplayConnected、onDisplayDisconnected、onDisplayError,這三個回調函數對應到RemoteDisplay類的notifyDisplayConnected、notifyDisplayDisconnected和notifyDisplayError三個方法。接着回到nativeListen中,接着會調用MediaPlayerService的listenForRemoteDisplay方法去監聽socket連接,這個方法是返回一個RemoteDisplay對象,當然經過binder的調用,最終返回到nativeListen的是BpRemoteDisplay對象,然後會由這個BpRemoteDisplay對象構造一個NativeRemoteDisplay對象並把它的指針地址返回給上層RemoteDisplay使用。
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. class NativeRemoteDisplay {  
  2. public:  
  3.     NativeRemoteDisplay(const sp<IRemoteDisplay>& display,  
  4.             const sp<NativeRemoteDisplayClient>& client) :  
  5.             mDisplay(display), mClient(client) {  
  6.     }  
  7.   
  8.     ~NativeRemoteDisplay() {  
  9.         mDisplay->dispose();  
  10.     }  
  11.   
  12.     void pause() {  
  13.         mDisplay->pause();  
  14.     }  
  15.   
  16.     void resume() {  
  17.         mDisplay->resume();  
  18.     }  
  19.   
  20. private:  
  21.     sp<IRemoteDisplay> mDisplay;  
  22.     sp<NativeRemoteDisplayClient> mClient;  
  23. };  

來看一下這時Java層的RemoteDisplay和Native層RemoteDisplay之間的關係:

WifiDisplayController通過左邊的一條線路關係去控制WifiDisplaySource,而WifiDisplaySource又通過右邊一條線路關係去回調WifiDisplayController的一些方法。

接着來看MediaPlayerService的listenForRemoteDisplay方法:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  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.   
  7.     return new RemoteDisplay(client, iface.string());  
  8. }  

首先進行權限的檢查,然後創建一個RemoteDisplay對象(注意現在已經在C++層了),這裏看RemoteDisplay.cpp文件。RemoteDisplay繼承於BnRemoteDisplay,並實現BnRemoteDisplay中的一些方法,有興趣的可以去看一下IRemoteDisplay的實現。接下來來看RemoteDisplay的構造函數:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  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.   
  8.     mSource = new WifiDisplaySource(mNetSession, client);  
  9.     mLooper->registerHandler(mSource);  
  10.   
  11.     mNetSession->start();  
  12.     mLooper->start();  
  13.   
  14.     mSource->start(iface);  
  15. }  

RemoteDisplay類包含三個比較重要的元素:ALooper、ANetworkSession、WifiDisplaySource。首先來看一下在Native層的類圖:


ALooper中會創建一個Thread,並且不斷的進行Looper循環去收消息,並dispatch給WifiDisplaySource去處理消息。首先來看它的構造函數和setName以及registerHandler這三個方法:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. ALooper::ALooper()  
  2.     : mRunningLocally(false) {  
  3. }  
  4.   
  5. void ALooper::setName(const char *name) {  
  6.     mName = name;  
  7. }  
  8.   
  9. ALooper::handler_id ALooper::registerHandler(const sp<AHandler> &handler) {  
  10.     return gLooperRoster.registerHandler(this, handler);  
  11. }  

這三個方法都比較簡單,我們看LooperRoster的registerHandler方法:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. ALooper::handler_id ALooperRoster::registerHandler(  
  2.         const sp<ALooper> looper, const sp<AHandler> &handler) {  
  3.     Mutex::Autolock autoLock(mLock);  
  4.   
  5.     if (handler->id() != 0) {  
  6.         CHECK(!"A handler must only be registered once.");  
  7.         return INVALID_OPERATION;  
  8.     }  
  9.   
  10.     HandlerInfo info;  
  11.     info.mLooper = looper;  
  12.     info.mHandler = handler;  
  13.     ALooper::handler_id handlerID = mNextHandlerID++;  
  14.     mHandlers.add(handlerID, info);  
  15.   
  16.     handler->setID(handlerID);  
  17.   
  18.     return handlerID;  
  19. }  

這裏爲每一個註冊的AHandler分配一個handlerID,並且把註冊的AHandler保存在mHandlers列表中,後面使用時,就可以快速的通過HandlerID找到對應的AHandler以及ALooper了。注意這裏HandlerInfo結構中的mLooper和mHander都是是wp,是一個弱引用,在使用中必須調用其promote方法獲取sp指針才能使用。再回到RemoteDisplay的構造函數中看ALooper的start方法:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. status_t ALooper::start(  
  2.         bool runOnCallingThread, bool canCallJava, int32_t priority) {  
  3.     if (runOnCallingThread) {  
  4.   
  5.     }  
  6.   
  7.     Mutex::Autolock autoLock(mLock);  
  8.   
  9.     mThread = new LooperThread(this, canCallJava);  
  10.   
  11.     status_t err = mThread->run(  
  12.             mName.empty() ? "ALooper" : mName.c_str(), priority);  
  13.     if (err != OK) {  
  14.         mThread.clear();  
  15.     }  
  16.   
  17.     return err;  
  18. }  

這裏的runOnCallingThread會根據默認形參爲false,所以會新建一個LooperThread來不斷的做循環,LooperThread是繼承於Thread,並實現它的readyToRun和threadLoop方法,在threadLoop方法中去調用ALooper的loop方法,代碼如下:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. virtual bool threadLoop() {  
  2.     return mLooper->loop();  
  3. }  
  4.   
  5.  ALooper::loop() {  
  6. Event event;  
  7.   
  8. {  
  9.     Mutex::Autolock autoLock(mLock);  
  10.   
  11.     if (mEventQueue.empty()) {  
  12.         mQueueChangedCondition.wait(mLock);  
  13.         return true;  
  14.     }  
  15.     int64_t whenUs = (*mEventQueue.begin()).mWhenUs;  
  16.     int64_t nowUs = GetNowUs();  
  17.   
  18.     if (whenUs > nowUs) {  
  19.         int64_t delayUs = whenUs - nowUs;  
  20.         mQueueChangedCondition.waitRelative(mLock, delayUs * 1000ll);  
  21.   
  22.         return true;  
  23.     }  
  24.   
  25.     event = *mEventQueue.begin();  
  26.     mEventQueue.erase(mEventQueue.begin());  
  27. }  
  28.   
  29. gLooperRoster.deliverMessage(event.mMessage);  
  30.   
  31. return true;  

在loop方法中,不斷的從mEventQueue取出消息,並dispatch給LooperRoster處理,mEventQueue是一個list鏈表,其元素都是Event結構,Event結構又包含消息處理的時間以及消息本身AMessage。再來看ALooperRoster的deliverMessage方法:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. void ALooperRoster::deliverMessage(const sp<AMessage> &msg) {  
  2.     sp<AHandler> handler;  
  3.   
  4.     {  
  5.         Mutex::Autolock autoLock(mLock);  
  6.   
  7.         ssize_t index = mHandlers.indexOfKey(msg->target());  
  8.   
  9.         if (index < 0) {  
  10.             ALOGW("failed to deliver message. Target handler not registered.");  
  11.             return;  
  12.         }  
  13.   
  14.         const HandlerInfo &info = mHandlers.valueAt(index);  
  15.         handler = info.mHandler.promote();  
  16.   
  17.         if (handler == NULL) {  
  18.             ALOGW("failed to deliver message. "  
  19.                  "Target handler %d registered, but object gone.",  
  20.                  msg->target());  
  21.   
  22.             mHandlers.removeItemsAt(index);  
  23.             return;  
  24.         }  
  25.     }  
  26.   
  27.     handler->onMessageReceived(msg);  
  28. }  

這裏首先通過AMessage的target找到需要哪個AHandler處理,然後調用這個AHandler的onMessageReceived去處理這個消息。注意前面的info.mHandler.promote()用於當前AHandler的強引用指針,也可以用來判斷當前AHandler是否還存活在。由前面的知識我們知道,這裏會調用到WifiDisplaySource的onMessageReceived方法,至於這些消息如何被處理,我們後面再來分析。再回到RemoteDisplay的構造函數中,ANetworkSession用於處理與網絡請求相關的工作,比如創建socket,從socket中收發數據,當然這些工作都是由WifiDisplaySource控制的,我們先來看ANetworkSession的構造方法和start方法:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. ANetworkSession::ANetworkSession()  
  2.     : mNextSessionID(1) {  
  3.     mPipeFd[0] = mPipeFd[1] = -1;  
  4. }  
  5.   
  6. status_t ANetworkSession::start() {  
  7.     if (mThread != NULL) {  
  8.         return INVALID_OPERATION;  
  9.     }  
  10.   
  11.     int res = pipe(mPipeFd);  
  12.     if (res != 0) {  
  13.         mPipeFd[0] = mPipeFd[1] = -1;  
  14.         return -errno;  
  15.     }  
  16.   
  17.     mThread = new NetworkThread(this);  
  18.   
  19.     status_t err = mThread->run("ANetworkSession", ANDROID_PRIORITY_AUDIO);  
  20.   
  21.     if (err != OK) {  
  22.         mThread.clear();  
  23.   
  24.         close(mPipeFd[0]);  
  25.         close(mPipeFd[1]);  
  26.         mPipeFd[0] = mPipeFd[1] = -1;  
  27.   
  28.         return err;  
  29.     }  
  30.   
  31.     return OK;  
  32. }  

在start方法中,首先創建一個管道,這裏創建的管理主要用於讓ANetworkSession不斷的做select循環,當有事務要處理時,就從select中跳出來處理,我們後面會分析到具體的代碼。接着創建一個NetworkThread,NetworkThread也是繼承於Thread,並實現threadLoop方法,在threadLoop方法中只是簡單的調用ANetworkSession的threadLoop方法,我們來分析threadLoop方法:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. void ANetworkSession::threadLoop() {  
  2.     fd_set rs, ws;  
  3.     FD_ZERO(&rs);  
  4.     FD_ZERO(&ws);  
  5.   
  6.     FD_SET(mPipeFd[0], &rs);  
  7.     int maxFd = mPipeFd[0];  
  8.   
  9.     {  
  10.         Mutex::Autolock autoLock(mLock);  
  11.   
  12.         for (size_t i = 0; i < mSessions.size(); ++i) {  
  13.             const sp<Session> &session = mSessions.valueAt(i);  
  14.   
  15.             int s = session->socket();  
  16.   
  17.             if (s < 0) {  
  18.                 continue;  
  19.             }  
  20.   
  21.             if (session->wantsToRead()) {  
  22.                 FD_SET(s, &rs);  
  23.                 if (s > maxFd) {  
  24.                     maxFd = s;  
  25.                 }  
  26.             }  
  27.   
  28.             if (session->wantsToWrite()) {  
  29.                 FD_SET(s, &ws);  
  30.                 if (s > maxFd) {  
  31.                     maxFd = s;  
  32.                 }  
  33.             }  
  34.         }  
  35.     }  
  36.   
  37.     int res = select(maxFd + 1, &rs, &ws, NULL, NULL /* tv */);  
  38.   
  39.     if (res == 0) {  
  40.         return;  
  41.     }  
  42.   
  43.     if (res < 0) {  
  44.         if (errno == EINTR) {  
  45.             return;  
  46.         }  
  47.   
  48.         ALOGE("select failed w/ error %d (%s)", errno, strerror(errno));  
  49.         return;  
  50.     }  
  51.   
  52. }  

這個函數比較長,我們分段來看,首先看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 plaincopy在CODE上查看代碼片派生到我的代碼片
  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.   
  25.     mSupportedSourceVideoFormats.disableAll();  
  26.   
  27.     mSupportedSourceVideoFormats.setNativeResolution(  
  28.             VideoFormats::RESOLUTION_CEA, 5);  // 1280x720 p30  
  29.   
  30.     // Enable all resolutions up to 1280x720p30  
  31.     mSupportedSourceVideoFormats.enableResolutionUpto(  
  32.             VideoFormats::RESOLUTION_CEA, 5,  
  33.             VideoFormats::PROFILE_CHP,  // Constrained High Profile  
  34.             VideoFormats::LEVEL_32);    // Level 3.2  
  35. }  

首先給一些變量出初始化處理,由默認形參我們知道path爲空。接着去清空VideoFormats中所有的設置,並把1280*720p以上的所有分辨率打開。VideoFormats是用於與Sink回覆的M3作比對用的,可以快速找出我們和Sink支持的分辨率以及幀率,作爲回覆M4消息用,也用作後續傳輸TS數據的格式。首先來看VideoFormats的構造函數:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. VideoFormats::VideoFormats() {  
  2.     memcpy(mConfigs, mResolutionTable, sizeof(mConfigs));  
  3.   
  4.     for (size_t i = 0; i < kNumResolutionTypes; ++i) {  
  5.         mResolutionEnabled[i] = 0;  
  6.     }  
  7.   
  8.     setNativeResolution(RESOLUTION_CEA, 0);  // default to 640x480 p60  
  9. }  

mResolutionTable是按照Wifi Display 規範定義好的一個3*32數組,裏面的元素是config_t類型:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  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 plaincopy在CODE上查看代碼片派生到我的代碼片
  1. void VideoFormats::setNativeResolution(ResolutionType type, size_t index) {  
  2.     CHECK_LT(type, kNumResolutionTypes);  
  3.     CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL));  
  4.   
  5.     mNativeType = type;  
  6.     mNativeIndex = index;  
  7.   
  8.     setResolutionEnabled(type, index);  
  9. }  

首先做參數檢查,檢查輸入的type和index是否合法,然後調用setResolutionEnabled去設置mResolutionEnabled和mConfigs中的相應的值:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  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.   
  6.     if (enabled) {  
  7.         mResolutionEnabled[type] |= (1ul << index);  
  8.         mConfigs[type][index].profile = (1ul << PROFILE_CBP);  
  9.         mConfigs[type][index].level = (1ul << LEVEL_31);  
  10.     } else {  
  11.         mResolutionEnabled[type] &= ~(1ul << index);  
  12.         mConfigs[type][index].profile = 0;  
  13.         mConfigs[type][index].level = 0;  
  14.     }  
  15. }  

這裏首先還是做參數的檢查,由默認形參我們知道,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 plaincopy在CODE上查看代碼片派生到我的代碼片
  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 plaincopy在CODE上查看代碼片派生到我的代碼片
  1. status_t WifiDisplaySource::start(const char *iface) {  
  2.     CHECK_EQ(mState, INITIALIZED);  
  3.   
  4.     sp<AMessage> msg = new AMessage(kWhatStart, id());  
  5.     msg->setString("iface", iface);  
  6.   
  7.     sp<AMessage> response;  
  8.     return PostAndAwaitResponse(msg, &response);  
  9. }  
  10.   
  11. static status_t PostAndAwaitResponse(  
  12.         const sp<AMessage> &msg, sp<AMessage> *response) {  
  13.     status_t err = msg->postAndAwaitResponse(response);  
  14.   
  15.     if (err != OK) {  
  16.         return err;  
  17.     }  
  18.   
  19.     if (response == NULL || !(*response)->findInt32("err", &err)) {  
  20.         err = OK;  
  21.     }  
  22.   
  23.     return err;  
  24. }  

在start函數中,構造一個AMessage,消息種類是kWhatStart,id()返回在ALooperRoster註冊的handlerID值,ALooperRoster通過handlerID值可以快速找到對應的AHandler,我們知道,這裏的id()返回WifiDisplaySource這個AHander的id值,這個消息最終也會被WifiDisplaySource的onMessageReceived方法處理。首先來看AMessage的postAndAwaitResponse方法:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  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 plaincopy在CODE上查看代碼片派生到我的代碼片
  1. status_t ALooperRoster::postAndAwaitResponse(  
  2.         const sp<AMessage> &msg, sp<AMessage> *response) {  
  3.     Mutex::Autolock autoLock(mLock);  
  4.   
  5.     uint32_t replyID = mNextReplyID++;  
  6.   
  7.     msg->setInt32("replyID", replyID);  
  8.   
  9.     status_t err = postMessage_l(msg, 0 /* delayUs */);  
  10.   
  11.     if (err != OK) {  
  12.         response->clear();  
  13.         return err;  
  14.     }  
  15.   
  16.     ssize_t index;  
  17.     while ((index = mReplies.indexOfKey(replyID)) < 0) {  
  18.         mRepliesCondition.wait(mLock);  
  19.     }  
  20.   
  21.     *response = mReplies.valueAt(index);  
  22.     mReplies.removeItemsAt(index);  
  23.   
  24.     return OK;  
  25. }  

首先會爲每個需要reply的消息賦予一個replyID,後面會根據這個replyID去mReplies找到對應的response。再來看postMessage_l的實現:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. status_t ALooperRoster::postMessage_l(  
  2.         const sp<AMessage> &msg, int64_t delayUs) {  
  3.     ssize_t index = mHandlers.indexOfKey(msg->target());  
  4.   
  5.     if (index < 0) {  
  6.         ALOGW("failed to post message '%s'. Target handler not registered.",  
  7.               msg->debugString().c_str());  
  8.         return -ENOENT;  
  9.     }  
  10.   
  11.     const HandlerInfo &info = mHandlers.valueAt(index);  
  12.   
  13.     sp<ALooper> looper = info.mLooper.promote();  
  14.   
  15.     if (looper == NULL) {  
  16.         ALOGW("failed to post message. "  
  17.              "Target handler %d still registered, but object gone.",  
  18.              msg->target());  
  19.   
  20.         mHandlers.removeItemsAt(index);  
  21.         return -ENOENT;  
  22.     }  
  23.   
  24.     looper->post(msg, delayUs);  
  25.   
  26.     return OK;  
  27. }  

首先從mHandler數組中找到當前AMessage對應的ALooper,然後調用ALooper的post方法,來看一下實現:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. void ALooper::post(const sp<AMessage> &msg, int64_t delayUs) {  
  2.     Mutex::Autolock autoLock(mLock);  
  3.   
  4.     int64_t whenUs;  
  5.     if (delayUs > 0) {  
  6.         whenUs = GetNowUs() + delayUs;  
  7.     } else {  
  8.         whenUs = GetNowUs();  
  9.     }  
  10.   
  11.     List<Event>::iterator it = mEventQueue.begin();  
  12.     while (it != mEventQueue.end() && (*it).mWhenUs <= whenUs) {  
  13.         ++it;  
  14.     }  
  15.   
  16.     Event event;  
  17.     event.mWhenUs = whenUs;  
  18.     event.mMessage = msg;  
  19.   
  20.     if (it == mEventQueue.begin()) {  
  21.         mQueueChangedCondition.signal();  
  22.     }  
  23.   
  24.     mEventQueue.insert(it, event);  
  25. }  

delayUs用於做延時消息使用,會加上當前時間作爲消息應該被處理的時間。然後依次比較mEventQueue鏈表中的所有消息,並把當前消息插入到比whenUs大的前面一個位置。如果這是mEventQueue中的第一個消息,則發出一個signal通知等待的線程。前面我們知道在ALooper的loop方法中會循環的從mEventQueue獲取消息並dispatch出去給WifiDisplaySource的onMessageReceived去處理,我們接着來看這部分的實現。這裏繞這麼大一圈,最後WifiDisplaySource發送的消息還是給自己處理,主要是爲了避開主線程處理的事務太多,通過消息機制,讓更多的繁雜的活都在Thread中去完成。
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  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.   
  8.             AString iface;  
  9.             CHECK(msg->findString("iface", &iface));  
  10.   
  11.             status_t err = OK;  
  12.   
  13.             ssize_t colonPos = iface.find(":");  
  14.   
  15.             unsigned long port;  
  16.   
  17.             if (colonPos >= 0) {  
  18.                 const char *s = iface.c_str() + colonPos + 1;  
  19.   
  20.                 char *end;  
  21.                 port = strtoul(s, &end, 10);  
  22.   
  23.                 if (end == s || *end != '\0' || port > 65535) {  
  24.                     err = -EINVAL;  
  25.                 } else {  
  26.                     iface.erase(colonPos, iface.size() - colonPos);  
  27.                 }  
  28.             } else {  
  29.                 port = kWifiDisplayDefaultPort;  
  30.             }  
  31.   
  32.             if (err == OK) {  
  33.                 if (inet_aton(iface.c_str(), &mInterfaceAddr) != 0) {  
  34.                     sp<AMessage> notify = new AMessage(kWhatRTSPNotify, id());  
  35.   
  36.                     err = mNetSession->createRTSPServer(  
  37.                             mInterfaceAddr, port, notify, &mSessionID);  
  38.                 } else {  
  39.                     err = -EINVAL;  
  40.                 }  
  41.             }  
  42.   
  43.             mState = AWAITING_CLIENT_CONNECTION;  
  44.   
  45.             sp<AMessage> response = new AMessage;  
  46.             response->setInt32("err", err);  
  47.             response->postReply(replyID);  
  48.             break;  
  49.         }  

首先通過AMessage獲取到replayID和iface,然後把iface分割成ip和port,分別保存在mInterfaceAddr和port中。在調用ANetSession的createRTSPServer去創建一個RTSP server,最後構造一個response對象並返回。我們先來看createRTSPServer方法:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  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.   
  14. status_t ANetworkSession::createClientOrServer(  
  15.         Mode mode,  
  16.         const struct in_addr *localAddr,  
  17.         unsigned port,  
  18.         const char *remoteHost,  
  19.         unsigned remotePort,  
  20.         const sp<AMessage> ¬ify,  
  21.         int32_t *sessionID) {  
  22.     Mutex::Autolock autoLock(mLock);  
  23.   
  24.     *sessionID = 0;  
  25.     status_t err = OK;  
  26.     int s, res;  
  27.     sp<Session> session;  
  28.   
  29.     s = socket(  
  30.             AF_INET,  
  31.             (mode == kModeCreateUDPSession) ? SOCK_DGRAM : SOCK_STREAM,  
  32.             0);  
  33.   
  34.     if (s < 0) {  
  35.         err = -errno;  
  36.         goto bail;  
  37.     }  
  38.   
  39.     if (mode == kModeCreateRTSPServer  
  40.             || mode == kModeCreateTCPDatagramSessionPassive) {  
  41.         const int yes = 1;  
  42.         res = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));  
  43.   
  44.         if (res < 0) {  
  45.             err = -errno;  
  46.             goto bail2;  
  47.         }  
  48.     }  
  49.   
  50.     err = MakeSocketNonBlocking(s);  
  51.   
  52.     if (err != OK) {  
  53.         goto bail2;  
  54.     }  
  55.   
  56.     struct sockaddr_in addr;  
  57.     memset(addr.sin_zero, 0, sizeof(addr.sin_zero));  
  58.     addr.sin_family = AF_INET;  
  59.     } else if (localAddr != NULL) {  
  60.         addr.sin_addr = *localAddr;  
  61.         addr.sin_port = htons(port);  
  62.   
  63.         res = bind(s, (const struct sockaddr *)&addr, sizeof(addr));  
  64.   
  65.         if (res == 0) {  
  66.             if (mode == kModeCreateRTSPServer  
  67.                     || mode == kModeCreateTCPDatagramSessionPassive) {  
  68.                 res = listen(s, 4);  
  69.             } else {  
  70.   
  71.   
  72.     if (res < 0) {  
  73.         err = -errno;  
  74.         goto bail2;  
  75.     }  
  76.   
  77.     Session::State state;  
  78.     switch (mode) {  
  79.   
  80.         case kModeCreateRTSPServer:  
  81.             state = Session::LISTENING_RTSP;  
  82.             break;  
  83.   
  84.         default:  
  85.             CHECK_EQ(mode, kModeCreateUDPSession);  
  86.             state = Session::DATAGRAM;  
  87.             break;  
  88.     }  
  89.   
  90.     session = new Session(  
  91.             mNextSessionID++,  
  92.             state,  
  93.             s,  
  94.             notify);  
  95.   
  96.   
  97.     mSessions.add(session->sessionID(), session);  
  98.   
  99.     interrupt();  
  100.   
  101.     *sessionID = session->sessionID();  
  102.   
  103.     goto bail;  
  104.   
  105. bail2:  
  106.     close(s);  
  107.     s = -1;  
  108.   
  109. bail:  
  110.     return err;  
  111. }  

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 plaincopy在CODE上查看代碼片派生到我的代碼片
  1. void ANetworkSession::interrupt() {  
  2.     static const char dummy = 0;  
  3.   
  4.     ssize_t n;  
  5.     do {  
  6.         n = write(mPipeFd[1], &dummy, 1);  
  7.     } while (n < 0 && errno == EINTR);  
  8.   
  9.     if (n < 0) {  
  10.         ALOGW("Error writing to pipe (%s)", strerror(errno));  
  11.     }  
  12. }  

interrupt方法向pipe中寫入一個空消息,前面我們已經介紹過threadLoop了,這裏就會把剛剛創建的socket加入到監聽的readFd中。到這裏,關於WifiDisplay連接的建立就講完了,後面會再從收到Sink的TCP連接請求開始講起。最後貼一份從WifiDisplaySettings到ANetworkSession如何創建socket的時序圖:


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章