Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 4.2 Wifi Display之Settings源碼分析(二)

Android 4.2 Wifi Display之Settings源碼分析(二)

編輯:關於Android編程

首先,回顧下應用層,當用戶在搜尋完設備後,可以選擇設備進行連接,當然正在進行連接或已經連接配對的設備,再次點擊配置後,會彈出對話框供用戶選擇斷開連接。
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. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved