天天看點

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

這一章中我們來看Wifi Display連接配接過程的建立,包含P2P的部分和RTSP的部分,首先來大緻看一下Wifi Display規範相關的東西。

Android WifiDisplay分析二: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的流程。

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

從上面的架構圖我們可以看到,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中的代碼分析:

private void pairWifiDisplay(WifiDisplay display) {
        if (display.canConnect()) {
            mDisplayManager.connectWifiDisplay(display.getDeviceAddress());
        }
    }           

WifiDisplaySettings通過AIDL調用到DisplayManagerService的connectWifiDisplay方法,關于AIDL的調用過程這裡不講了,直接到DisplayManagerService的connectWifiDisplay方法來看:

public void connectWifiDisplay(String address) {
        if (address == null) {
            throw new IllegalArgumentException("address must not be null");
        }
        mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
                "Permission required to connect to a wifi display");

        final long token = Binder.clearCallingIdentity();
        try {
            synchronized (mSyncRoot) {
                if (mWifiDisplayAdapter != null) {
                    mWifiDisplayAdapter.requestConnectLocked(address);
                }
            }
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }           

首先做參數的檢查,即MAC位址不能為空,然後做權限檢查,調用這個方法的application必須要在manifest中聲明有CONFIGURE_WIFI_DISPLAY權限,最後直接調用WifiDisplayAdapter的requestConnectLocked方法:

public void requestConnectLocked(final String address) {
        if (DEBUG) {
            Slog.d(TAG, "requestConnectLocked: address=" + address);
        }

        getHandler().post(new Runnable() {
            @Override
            public void run() {
                if (mDisplayController != null) {
                    mDisplayController.requestConnect(address);
                }
            }
        });
    }           

這裡比較簡單,直接調用WifiDisplayController的requestConnect方法。前面都是直接的調用,最終做事情的還是WifiDisplayController。

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) {
                Slog.d(TAG, "connect: nothing to do, already connecting to "
                        + describeWifiP2pDevice(device));
            }
            return;
        }

        if (mConnectedDevice != null
                && !mConnectedDevice.deviceAddress.equals(device.deviceAddress)
                && mDesiredDevice == null) {
            if (DEBUG) {
                Slog.d(TAG, "connect: nothing to do, already connected to "
                        + describeWifiP2pDevice(device) + " and not part way through "
                        + "connecting to a different device.");
            }
            return;
        }

        if (!mWfdEnabled) {
            Slog.i(TAG, "Ignoring request to connect to Wifi display because the "
                    +" feature is currently disabled: " + device.deviceName);
            return;
        }

        mDesiredDevice = device;
        mConnectionRetriesLeft = CONNECT_MAX_RETRIES;
        updateConnection();
    }           

requestConnect先從mAvaiableWifiDsiplayPeers中通過Mac位址找到所有連接配接的WifiP2pDevice,然後調用connect方法,在connect方法中會做一系列的判斷,看首先是否有正在連接配接中或者斷開中的裝置,如果有就直接傳回;再看有沒有已經連接配接上的裝置,如果有,也直接傳回,然後指派mDesiredDevice為這次要連接配接的裝置,最後調用updateConnection來更新連接配接狀态并發起連接配接。updateConnection的代碼比較長,我們分段來分析:

private void updateConnection() {
	//更新是否需要scan或者停止scan
        updateScanState();

	//如果有已經連接配接上的RemoteDisplay,先斷開。這裡先不看
        if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {
            
        }

        // 接上面的一步,段開這個group
        if (mDisconnectingDevice != null) {
            return; // wait for asynchronous callback
        }
        if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {

        }

        // 如果有正在連接配接的裝置,先停止連接配接之前的裝置
        if (mCancelingDevice != null) {
            return; // wait for asynchronous callback
        }
        if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {
            
        }

        // 當斷開之前的連接配接或者啟動匿名GROUP時,這裡就結束了
        if (mDesiredDevice == null) {

        }

        // 開始連接配接,這是我們要看的重點
        if (mConnectedDevice == null && mConnectingDevice == null) {
            Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName);

            mConnectingDevice = mDesiredDevice;
            WifiP2pConfig config = new WifiP2pConfig();
            WpsInfo wps = new WpsInfo();
            if (mWifiDisplayWpsConfig != WpsInfo.INVALID) {
                wps.setup = mWifiDisplayWpsConfig;
            } else if (mConnectingDevice.wpsPbcSupported()) {
                wps.setup = WpsInfo.PBC;
            } else if (mConnectingDevice.wpsDisplaySupported()) {
                wps.setup = WpsInfo.KEYPAD;
            } else {
                wps.setup = WpsInfo.DISPLAY;
            }
            config.wps = wps;
            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() {
                @Override
                public void onSuccess() {
                    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; 
        }              

這段函數比較長,我們先看我們需要的,剩下的後面再來分析。首先指派給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:

} else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) {
                NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(
                        WifiP2pManager.EXTRA_NETWORK_INFO);
                if (DEBUG) {
                    Slog.d(TAG, "Received WIFI_P2P_CONNECTION_CHANGED_ACTION: networkInfo="
                            + networkInfo);
                }

                handleConnectionChanged(networkInfo);

    private void handleConnectionChanged(NetworkInfo networkInfo) {
        mNetworkInfo = networkInfo;
        if (mWfdEnabled && networkInfo.isConnected()) {
            if (mDesiredDevice != null || mWifiDisplayCertMode) {
                mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() {
                    @Override
                    public void onGroupInfoAvailable(WifiP2pGroup info) {
                        if (DEBUG) {
                            Slog.d(TAG, "Received group info: " + describeWifiP2pGroup(info));
                        }

                        if (mConnectingDevice != null && !info.contains(mConnectingDevice)) {
                            Slog.i(TAG, "Aborting connection to Wifi display because "
                                    + "the current P2P group does not contain the device "
                                    + "we expected to find: " + mConnectingDevice.deviceName
                                    + ", group info was: " + describeWifiP2pGroup(info));
                            handleConnectionFailure(false);
                            return;
                        }

                        if (mDesiredDevice != null && !info.contains(mDesiredDevice)) {
                            disconnect();
                            return;
                        }

                        if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
                            Slog.i(TAG, "Connected to Wifi display: "
                                    + mConnectingDevice.deviceName);

                            mHandler.removeCallbacks(mConnectionTimeout);
                            mConnectedDeviceGroupInfo = info;
                            mConnectedDevice = mConnectingDevice;
                            mConnectingDevice = null;
                            updateConnection();
                        }
                    }
                });
            }
        }           

當WifiDisplayController收到WIFI_P2P_CONNECTION_CHANGED_ACTION廣播後,會調用handleConnectionChanged來擷取目前P2P Group相關的資訊,如果擷取到的P2P Group資訊裡面沒有mConnectingDevice或者mDesiredDevice的資訊,則表示連接配接出錯了,直接退出。如果目前連接配接資訊與前面設定的mConnectingDevice一直,則表示連接配接P2P成功,這裡首先會移除前面設定的連接配接timeout的callback,然後設定mConnectedDevice為目前連接配接的裝置,并設定mConnectingDevice為空,最後調用updateConnection來更新連接配接狀态資訊。我們又回到updateConnection這個函數了,但這次進入的分支與之前連接配接請求的分支又不同了,我們來看代碼:

private void updateConnection() {
        // 更新是否需要scan或者停止scan
        updateScanState();

        // 如果有連接配接上的RemoteDisplay,這裡先斷開
        if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {

        }

        // 接着上面的一步,先斷開之前連接配接的裝置
        if (mDisconnectingDevice != null) {
            return; // wait for asynchronous callback
        }
        if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {
  
        }

        // 如果有正在連接配接的裝置,先斷開之前連接配接的裝置
        if (mCancelingDevice != null) {
            return; // wait for asynchronous callback
        }
        if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {
 
        }

        // 當斷開之前的連接配接或者匿名GO時,這裡就結束了
        if (mDesiredDevice == null) {

        }

        // 如果有連接配接請求,則進入此
        if (mConnectedDevice == null && mConnectingDevice == null) {

        }

        // 當連接配接上P2P後,就進入到此
        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
            }

            mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_SOURCE);

            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, int session) {
                    if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) {
                        Slog.i(TAG, "Opened RTSP connection with Wifi display: "
                                + mConnectedDevice.deviceName);
                        mRemoteDisplayConnected = true;
                        mHandler.removeCallbacks(mRtspTimeout);

                        if (mWifiDisplayCertMode) {
                            mListener.onDisplaySessionInfo(
                                    getSessionInfo(mConnectedDeviceGroupInfo, session));
                        }

                        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);

            // Use extended timeout value for certification, as some tests require user inputs
            int rtspTimeout = mWifiDisplayCertMode ?
                    RTSP_TIMEOUT_SECONDS_CERT_MODE : RTSP_TIMEOUT_SECONDS;

            mHandler.postDelayed(mRtspTimeout, rtspTimeout * 1000);
        }
    }           

到這裡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的實作:

public static RemoteDisplay listen(String iface, Listener listener, Handler handler) {
        if (iface == null) {
            throw new IllegalArgumentException("iface must not be null");
        }
        if (listener == null) {
            throw new IllegalArgumentException("listener must not be null");
        }
        if (handler == null) {
            throw new IllegalArgumentException("handler must not be null");
        }

        RemoteDisplay display = new RemoteDisplay(listener, handler);
        display.startListening(iface);
        return display;
    }           

這裡首先進行參數的檢查,然後建立一個RemoteDisplay對象(這裡不能直接建立RemoteDisplay對象,因為它的構造函數是private的),接着調用RemoteDisplay的startListening方法:

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");
    }           

nativeListen會調用JNI中的實作,相關代碼在android_media_RemoteDisplay.cpp中。注意上面的mGuard是CloseGuard對象,是一種用于顯示釋放一些資源的機制。

static jint nativeListen(JNIEnv* env, jobject remoteDisplayObj, jstring ifaceStr) {
    ScopedUtfChars iface(env, ifaceStr);

    sp<IServiceManager> sm = defaultServiceManager();
    sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(
            sm->getService(String16("media.player")));
    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()));
    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);
}           

上面的代碼中先從ServiceManager中擷取MediaPlayerService的Bpbinder引用,然後由傳入的第二個參數remoteDisplayObj,也就是RemoteDisplay對象構造一個NativeRemoteDisplayClient,在framework中,我們經常看到像這樣的用法,類似于設計模式中的包裝模式,例如在framework中對Java層的BnBinder也是做了一層封裝JavaBBinder。在NativeRemoteDisplayClient中通過JNI的反向調用,就可以直接回調RemoteDisplay中的一些函數,實作回調方法了,下面來看它的實作:

class NativeRemoteDisplayClient : public BnRemoteDisplayClient {
public:
    NativeRemoteDisplayClient(JNIEnv* env, jobject remoteDisplayObj) :
            mRemoteDisplayObjGlobal(env->NewGlobalRef(remoteDisplayObj)) {
    }

protected:
    ~NativeRemoteDisplayClient() {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        env->DeleteGlobalRef(mRemoteDisplayObjGlobal);
    }

public:
    virtual void onDisplayConnected(const sp<IGraphicBufferProducer>& bufferProducer,
            uint32_t width, uint32_t height, uint32_t flags, uint32_t session) {
        env->CallVoidMethod(mRemoteDisplayObjGlobal,
                gRemoteDisplayClassInfo.notifyDisplayConnected,
                surfaceObj, width, height, flags, session);
    }

    virtual void onDisplayDisconnected() {

    }

    virtual void onDisplayError(int32_t error) {

    }

private:
    jobject mRemoteDisplayObjGlobal;

    static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {

        }
    }
};           

在NativeRemoteDisplayClient的構造函數中,把RemoteDisplay對象先儲存到mRemoteDisplayObjGlobal中,可以看到上面主要實作了三個回調函數,onDisplayConnected、onDisplayDisconnected、onDisplayError,這三個回調函數對應到RemoteDisplay類的notifyDisplayConnected、notifyDisplayDisconnected和notifyDisplayError三個方法。接着回到nativeListen中,接着會調用MediaPlayerService的listenForRemoteDisplay方法去監聽socket連接配接,這個方法是傳回一個RemoteDisplay對象,當然經過binder的調用,最終傳回到nativeListen的是BpRemoteDisplay對象,然後會由這個BpRemoteDisplay對象構造一個NativeRemoteDisplay對象并把它的指針位址傳回給上層RemoteDisplay使用。

class NativeRemoteDisplay {
public:
    NativeRemoteDisplay(const sp<IRemoteDisplay>& display,
            const sp<NativeRemoteDisplayClient>& client) :
            mDisplay(display), mClient(client) {
    }

    ~NativeRemoteDisplay() {
        mDisplay->dispose();
    }

    void pause() {
        mDisplay->pause();
    }

    void resume() {
        mDisplay->resume();
    }

private:
    sp<IRemoteDisplay> mDisplay;
    sp<NativeRemoteDisplayClient> mClient;
};           

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

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

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

接着來看MediaPlayerService的listenForRemoteDisplay方法:

sp<IRemoteDisplay> MediaPlayerService::listenForRemoteDisplay(
        const sp<IRemoteDisplayClient>& client, const String8& iface) {
    if (!checkPermission("android.permission.CONTROL_WIFI_DISPLAY")) {
        return NULL;
    }

    return new RemoteDisplay(client, iface.string());
}           

首先進行權限的檢查,然後建立一個RemoteDisplay對象(注意現在已經在C++層了),這裡看RemoteDisplay.cpp檔案。RemoteDisplay繼承于BnRemoteDisplay,并實作BnRemoteDisplay中的一些方法,有興趣的可以去看一下IRemoteDisplay的實作。接下來來看RemoteDisplay的構造函數:

RemoteDisplay::RemoteDisplay(
        const sp<IRemoteDisplayClient> &client,
        const char *iface)
    : mLooper(new ALooper),
      mNetSession(new ANetworkSession) {
    mLooper->setName("wfd_looper");

    mSource = new WifiDisplaySource(mNetSession, client);
    mLooper->registerHandler(mSource);

    mNetSession->start();
    mLooper->start();

    mSource->start(iface);
}           

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

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

ALooper中會建立一個Thread,并且不斷的進行Looper循環去收消息,并dispatch給WifiDisplaySource去處理消息。首先來看它的構造函數和setName以及registerHandler這三個方法:

ALooper::ALooper()
    : mRunningLocally(false) {
}

void ALooper::setName(const char *name) {
    mName = name;
}

ALooper::handler_id ALooper::registerHandler(const sp<AHandler> &handler) {
    return gLooperRoster.registerHandler(this, handler);
}           

這三個方法都比較簡單,我們看LooperRoster的registerHandler方法:

ALooper::handler_id ALooperRoster::registerHandler(
        const sp<ALooper> looper, const sp<AHandler> &handler) {
    Mutex::Autolock autoLock(mLock);

    if (handler->id() != 0) {
        CHECK(!"A handler must only be registered once.");
        return INVALID_OPERATION;
    }

    HandlerInfo info;
    info.mLooper = looper;
    info.mHandler = handler;
    ALooper::handler_id handlerID = mNextHandlerID++;
    mHandlers.add(handlerID, info);

    handler->setID(handlerID);

    return handlerID;
}           

這裡為每一個注冊的AHandler配置設定一個handlerID,并且把注冊的AHandler儲存在mHandlers清單中,後面使用時,就可以快速的通過HandlerID找到對應的AHandler以及ALooper了。注意這裡HandlerInfo結構中的mLooper和mHander都是是wp,是一個弱引用,在使用中必須調用其promote方法擷取sp指針才能使用。再回到RemoteDisplay的構造函數中看ALooper的start方法:

status_t ALooper::start(
        bool runOnCallingThread, bool canCallJava, int32_t priority) {
    if (runOnCallingThread) {

    }

    Mutex::Autolock autoLock(mLock);

    mThread = new LooperThread(this, canCallJava);

    status_t err = mThread->run(
            mName.empty() ? "ALooper" : mName.c_str(), priority);
    if (err != OK) {
        mThread.clear();
    }

    return err;
}           

這裡的runOnCallingThread會根據預設形參為false,是以會建立一個LooperThread來不斷的做循環,LooperThread是繼承于Thread,并實作它的readyToRun和threadLoop方法,在threadLoop方法中去調用ALooper的loop方法,代碼如下:

virtual bool threadLoop() {
        return mLooper->loop();
    }

bool ALooper::loop() {
    Event event;

    {
        Mutex::Autolock autoLock(mLock);
 
        if (mEventQueue.empty()) {
            mQueueChangedCondition.wait(mLock);
            return true;
        }
        int64_t whenUs = (*mEventQueue.begin()).mWhenUs;
        int64_t nowUs = GetNowUs();

        if (whenUs > nowUs) {
            int64_t delayUs = whenUs - nowUs;
            mQueueChangedCondition.waitRelative(mLock, delayUs * 1000ll);

            return true;
        }

        event = *mEventQueue.begin();
        mEventQueue.erase(mEventQueue.begin());
    }

    gLooperRoster.deliverMessage(event.mMessage);

    return true;
}           

在loop方法中,不斷的從mEventQueue取出消息,并dispatch給LooperRoster處理,mEventQueue是一個list連結清單,其元素都是Event結構,Event結構又包含消息處理的時間以及消息本身AMessage。再來看ALooperRoster的deliverMessage方法:

void ALooperRoster::deliverMessage(const sp<AMessage> &msg) {
    sp<AHandler> handler;

    {
        Mutex::Autolock autoLock(mLock);

        ssize_t index = mHandlers.indexOfKey(msg->target());

        if (index < 0) {
            ALOGW("failed to deliver message. Target handler not registered.");
            return;
        }

        const HandlerInfo &info = mHandlers.valueAt(index);
        handler = info.mHandler.promote();

        if (handler == NULL) {
            ALOGW("failed to deliver message. "
                 "Target handler %d registered, but object gone.",
                 msg->target());

            mHandlers.removeItemsAt(index);
            return;
        }
    }

    handler->onMessageReceived(msg);
}           

這裡首先通過AMessage的target找到需要哪個AHandler處理,然後調用這個AHandler的onMessageReceived去處理這個消息。注意前面的info.mHandler.promote()用于目前AHandler的強引用指針,也可以用來判斷目前AHandler是否還存活在。由前面的知識我們知道,這裡會調用到WifiDisplaySource的onMessageReceived方法,至于這些消息如何被處理,我們後面再來分析。再回到RemoteDisplay的構造函數中,ANetworkSession用于處理與網絡請求相關的工作,比如建立socket,從socket中收發資料,當然這些工作都是由WifiDisplaySource控制的,我們先來看ANetworkSession的構造方法和start方法:

ANetworkSession::ANetworkSession()
    : mNextSessionID(1) {
    mPipeFd[0] = mPipeFd[1] = -1;
}

status_t ANetworkSession::start() {
    if (mThread != NULL) {
        return INVALID_OPERATION;
    }

    int res = pipe(mPipeFd);
    if (res != 0) {
        mPipeFd[0] = mPipeFd[1] = -1;
        return -errno;
    }

    mThread = new NetworkThread(this);

    status_t err = mThread->run("ANetworkSession", ANDROID_PRIORITY_AUDIO);

    if (err != OK) {
        mThread.clear();

        close(mPipeFd[0]);
        close(mPipeFd[1]);
        mPipeFd[0] = mPipeFd[1] = -1;

        return err;
    }

    return OK;
}           

在start方法中,首先建立一個管道,這裡建立的管理主要用于讓ANetworkSession不斷的做select循環,當有事務要處理時,就從select中跳出來處理,我們後面會分析到具體的代碼。接着建立一個NetworkThread,NetworkThread也是繼承于Thread,并實作threadLoop方法,在threadLoop方法中隻是簡單的調用ANetworkSession的threadLoop方法,我們來分析threadLoop方法:

void ANetworkSession::threadLoop() {
    fd_set rs, ws;
    FD_ZERO(&rs);
    FD_ZERO(&ws);

    FD_SET(mPipeFd[0], &rs);
    int maxFd = mPipeFd[0];

    {
        Mutex::Autolock autoLock(mLock);

        for (size_t i = 0; i < mSessions.size(); ++i) {
            const sp<Session> &session = mSessions.valueAt(i);

            int s = session->socket();

            if (s < 0) {
                continue;
            }

            if (session->wantsToRead()) {
                FD_SET(s, &rs);
                if (s > maxFd) {
                    maxFd = s;
                }
            }

            if (session->wantsToWrite()) {
                FD_SET(s, &ws);
                if (s > maxFd) {
                    maxFd = s;
                }
            }
        }
    }

    int res = select(maxFd + 1, &rs, &ws, NULL, NULL /* tv */);

    if (res == 0) {
        return;
    }

    if (res < 0) {
        if (errno == EINTR) {
            return;
        }

        ALOGE("select failed w/ error %d (%s)", errno, strerror(errno));
        return;
    }

}           

這個函數比較長,我們分段來看,首先看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的構造函數:

WifiDisplaySource::WifiDisplaySource(
        const sp<ANetworkSession> &netSession,
        const sp<IRemoteDisplayClient> &client,
        const char *path)
    : mState(INITIALIZED),
      mNetSession(netSession),
      mClient(client),
      mSessionID(0),
      mStopReplyID(0),
      mChosenRTPPort(-1),
      mUsingPCMAudio(false),
      mClientSessionID(0),
      mReaperPending(false),
      mNextCSeq(1),
      mUsingHDCP(false),
      mIsHDCP2_0(false),
      mHDCPPort(0),
      mHDCPInitializationComplete(false),
      mSetupTriggerDeferred(false),
      mPlaybackSessionEstablished(false) {
    if (path != NULL) {
        mMediaPath.setTo(path);
    }

    mSupportedSourceVideoFormats.disableAll();

    mSupportedSourceVideoFormats.setNativeResolution(
            VideoFormats::RESOLUTION_CEA, 5);  // 1280x720 p30

    // Enable all resolutions up to 1280x720p30
    mSupportedSourceVideoFormats.enableResolutionUpto(
            VideoFormats::RESOLUTION_CEA, 5,
            VideoFormats::PROFILE_CHP,  // Constrained High Profile
            VideoFormats::LEVEL_32);    // Level 3.2
}           

首先給一些變量出初始化處理,由預設形參我們知道path為空。接着去清空VideoFormats中所有的設定,并把1280*720p以上的所有分辨率打開。VideoFormats是用于與Sink回複的M3作比對用的,可以快速找出我們和Sink支援的分辨率以及幀率,作為回複M4消息用,也用作後續傳輸TS資料的格式。首先來看VideoFormats的構造函數:

VideoFormats::VideoFormats() {
    memcpy(mConfigs, mResolutionTable, sizeof(mConfigs));

    for (size_t i = 0; i < kNumResolutionTypes; ++i) {
        mResolutionEnabled[i] = 0;
    }

    setNativeResolution(RESOLUTION_CEA, 0);  // default to 640x480 p60
}           

mResolutionTable是按照Wifi Display 規範定義好的一個3*32數組,裡面的元素是config_t類型:

struct config_t {
        size_t width, height, framesPerSecond;
        bool interlaced;
        unsigned char profile, level;
    };           

config_t包含了長、寬、幀率、隔行視訊、profile和H.264 level。然後在構造函數中,對mResolutionEnabled[]數組全部置為0,mResolutionEnabled數組有三個元素,分别對應CEA、VESA、HH被選取的位,如果在mConfigs數組中相應的格式被選取,就會置mResolutionEnabled對應的位為1;相反取消支援一種格式時,相應的位就被置為0。在來看setNativeResolution:

void VideoFormats::setNativeResolution(ResolutionType type, size_t index) {
    CHECK_LT(type, kNumResolutionTypes);
    CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL));

    mNativeType = type;
    mNativeIndex = index;

    setResolutionEnabled(type, index);
}           

首先做參數檢查,檢查輸入的type和index是否合法,然後調用setResolutionEnabled去設定mResolutionEnabled和mConfigs中的相應的值:

void VideoFormats::setResolutionEnabled(
        ResolutionType type, size_t index, bool enabled) {
    CHECK_LT(type, kNumResolutionTypes);
    CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL));

    if (enabled) {
        mResolutionEnabled[type] |= (1ul << index);
        mConfigs[type][index].profile = (1ul << PROFILE_CBP);
        mConfigs[type][index].level = (1ul << LEVEL_31);
    } else {
        mResolutionEnabled[type] &= ~(1ul << index);
        mConfigs[type][index].profile = 0;
        mConfigs[type][index].level = 0;
    }
}           

這裡首先還是做參數的檢查,由預設形參我們知道,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以上的格式都設定為支援:

void VideoFormats::enableResolutionUpto(
        ResolutionType type, size_t index,
        ProfileType profile, LevelType level) {
    size_t width, height, fps, score;
    bool interlaced;
    if (!GetConfiguration(type, index, &width, &height,
            &fps, &interlaced)) {
        ALOGE("Maximum resolution not found!");
        return;
    }
    score = width * height * fps * (!interlaced + 1);
    for (size_t i = 0; i < kNumResolutionTypes; ++i) {
        for (size_t j = 0; j < 32; j++) {
            if (GetConfiguration((ResolutionType)i, j,
                    &width, &height, &fps, &interlaced)
                    && score >= width * height * fps * (!interlaced + 1)) {
                setResolutionEnabled((ResolutionType)i, j);
                setProfileLevel((ResolutionType)i, j, profile, level);
            }
        }
    }
}           

這裡采用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":

status_t WifiDisplaySource::start(const char *iface) {
    CHECK_EQ(mState, INITIALIZED);

    sp<AMessage> msg = new AMessage(kWhatStart, id());
    msg->setString("iface", iface);

    sp<AMessage> response;
    return PostAndAwaitResponse(msg, &response);
}

static status_t PostAndAwaitResponse(
        const sp<AMessage> &msg, sp<AMessage> *response) {
    status_t err = msg->postAndAwaitResponse(response);

    if (err != OK) {
        return err;
    }

    if (response == NULL || !(*response)->findInt32("err", &err)) {
        err = OK;
    }

    return err;
}           

在start函數中,構造一個AMessage,消息種類是kWhatStart,id()傳回在ALooperRoster注冊的handlerID值,ALooperRoster通過handlerID值可以快速找到對應的AHandler,我們知道,這裡的id()傳回WifiDisplaySource這個AHander的id值,這個消息最終也會被WifiDisplaySource的onMessageReceived方法處理。首先來看AMessage的postAndAwaitResponse方法:

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

這裡直接調用LooperRoster的postAndAwaitResponse方法,這裡比較重要的是gLooperRoster在這裡隻是被extern引用:extern ALooperRoster gLooperRoster,其最終的聲明和定義是在我們前面講到的ALooper中。接着去看LooperRoster的postAndAwaitResponse方法:

status_t ALooperRoster::postAndAwaitResponse(
        const sp<AMessage> &msg, sp<AMessage> *response) {
    Mutex::Autolock autoLock(mLock);

    uint32_t replyID = mNextReplyID++;

    msg->setInt32("replyID", replyID);

    status_t err = postMessage_l(msg, 0 /* delayUs */);

    if (err != OK) {
        response->clear();
        return err;
    }

    ssize_t index;
    while ((index = mReplies.indexOfKey(replyID)) < 0) {
        mRepliesCondition.wait(mLock);
    }

    *response = mReplies.valueAt(index);
    mReplies.removeItemsAt(index);

    return OK;
}           

首先會為每個需要reply的消息賦予一個replyID,後面會根據這個replyID去mReplies找到對應的response。再來看postMessage_l的實作:

status_t ALooperRoster::postMessage_l(
        const sp<AMessage> &msg, int64_t delayUs) {
    ssize_t index = mHandlers.indexOfKey(msg->target());

    if (index < 0) {
        ALOGW("failed to post message '%s'. Target handler not registered.",
              msg->debugString().c_str());
        return -ENOENT;
    }

    const HandlerInfo &info = mHandlers.valueAt(index);

    sp<ALooper> looper = info.mLooper.promote();

    if (looper == NULL) {
        ALOGW("failed to post message. "
             "Target handler %d still registered, but object gone.",
             msg->target());

        mHandlers.removeItemsAt(index);
        return -ENOENT;
    }

    looper->post(msg, delayUs);

    return OK;
}           

首先從mHandler數組中找到目前AMessage對應的ALooper,然後調用ALooper的post方法,來看一下實作:

void ALooper::post(const sp<AMessage> &msg, int64_t delayUs) {
    Mutex::Autolock autoLock(mLock);

    int64_t whenUs;
    if (delayUs > 0) {
        whenUs = GetNowUs() + delayUs;
    } else {
        whenUs = GetNowUs();
    }

    List<Event>::iterator it = mEventQueue.begin();
    while (it != mEventQueue.end() && (*it).mWhenUs <= whenUs) {
        ++it;
    }

    Event event;
    event.mWhenUs = whenUs;
    event.mMessage = msg;

    if (it == mEventQueue.begin()) {
        mQueueChangedCondition.signal();
    }

    mEventQueue.insert(it, event);
}           

delayUs用于做延時消息使用,會加上目前時間作為消息應該被處理的時間。然後依次比較mEventQueue連結清單中的所有消息,并把目前消息插入到比whenUs大的前面一個位置。如果這是mEventQueue中的第一個消息,則發出一個signal通知等待的線程。前面我們知道在ALooper的loop方法中會循環的從mEventQueue擷取消息并dispatch出去給WifiDisplaySource的onMessageReceived去處理,我們接着來看這部分的實作。這裡繞這麼大一圈,最後WifiDisplaySource發送的消息還是給自己處理,主要是為了避開主線程處理的事務太多,通過消息機制,讓更多的繁雜的活都在Thread中去完成。

void WifiDisplaySource::onMessageReceived(const sp<AMessage> &msg) {
    switch (msg->what()) {
        case kWhatStart:
        {
            uint32_t replyID;
            CHECK(msg->senderAwaitsResponse(&replyID));

            AString iface;
            CHECK(msg->findString("iface", &iface));

            status_t err = OK;

            ssize_t colonPos = iface.find(":");

            unsigned long port;

            if (colonPos >= 0) {
                const char *s = iface.c_str() + colonPos + 1;

                char *end;
                port = strtoul(s, &end, 10);

                if (end == s || *end != '\0' || port > 65535) {
                    err = -EINVAL;
                } else {
                    iface.erase(colonPos, iface.size() - colonPos);
                }
            } else {
                port = kWifiDisplayDefaultPort;
            }

            if (err == OK) {
                if (inet_aton(iface.c_str(), &mInterfaceAddr) != 0) {
                    sp<AMessage> notify = new AMessage(kWhatRTSPNotify, id());

                    err = mNetSession->createRTSPServer(
                            mInterfaceAddr, port, notify, &mSessionID);
                } else {
                    err = -EINVAL;
                }
            }

            mState = AWAITING_CLIENT_CONNECTION;

            sp<AMessage> response = new AMessage;
            response->setInt32("err", err);
            response->postReply(replyID);
            break;
        }           

首先通過AMessage擷取到replayID和iface,然後把iface分割成ip和port,分别儲存在mInterfaceAddr和port中。在調用ANetSession的createRTSPServer去建立一個RTSP server,最後構造一個response對象并傳回。我們先來看createRTSPServer方法:

status_t ANetworkSession::createRTSPServer(
        const struct in_addr &addr, unsigned port,
        const sp<AMessage> ¬ify, int32_t *sessionID) {
    return createClientOrServer(
            kModeCreateRTSPServer,
            &addr,
            port,
            NULL /* remoteHost */,
            0 /* remotePort */,
            notify,
            sessionID);
}

status_t ANetworkSession::createClientOrServer(
        Mode mode,
        const struct in_addr *localAddr,
        unsigned port,
        const char *remoteHost,
        unsigned remotePort,
        const sp<AMessage> ¬ify,
        int32_t *sessionID) {
    Mutex::Autolock autoLock(mLock);

    *sessionID = 0;
    status_t err = OK;
    int s, res;
    sp<Session> session;

    s = socket(
            AF_INET,
            (mode == kModeCreateUDPSession) ? SOCK_DGRAM : SOCK_STREAM,
            0);

    if (s < 0) {
        err = -errno;
        goto bail;
    }

    if (mode == kModeCreateRTSPServer
            || mode == kModeCreateTCPDatagramSessionPassive) {
        const int yes = 1;
        res = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));

        if (res < 0) {
            err = -errno;
            goto bail2;
        }
    }

    err = MakeSocketNonBlocking(s);

    if (err != OK) {
        goto bail2;
    }

    struct sockaddr_in addr;
    memset(addr.sin_zero, 0, sizeof(addr.sin_zero));
    addr.sin_family = AF_INET;
    } else if (localAddr != NULL) {
        addr.sin_addr = *localAddr;
        addr.sin_port = htons(port);

        res = bind(s, (const struct sockaddr *)&addr, sizeof(addr));

        if (res == 0) {
            if (mode == kModeCreateRTSPServer
                    || mode == kModeCreateTCPDatagramSessionPassive) {
                res = listen(s, 4);
            } else {


    if (res < 0) {
        err = -errno;
        goto bail2;
    }

    Session::State state;
    switch (mode) {

        case kModeCreateRTSPServer:
            state = Session::LISTENING_RTSP;
            break;

        default:
            CHECK_EQ(mode, kModeCreateUDPSession);
            state = Session::DATAGRAM;
            break;
    }

    session = new Session(
            mNextSessionID++,
            state,
            s,
            notify);


    mSessions.add(session->sessionID(), session);

    interrupt();

    *sessionID = session->sessionID();

    goto bail;

bail2:
    close(s);
    s = -1;

bail:
    return err;
}           

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監聽的檔案句柄。

void ANetworkSession::interrupt() {
    static const char dummy = 0;

    ssize_t n;
    do {
        n = write(mPipeFd[1], &dummy, 1);
    } while (n < 0 && errno == EINTR);

    if (n < 0) {
        ALOGW("Error writing to pipe (%s)", strerror(errno));
    }
}           

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

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