« 上一篇下一篇 »

WIFI-Display浅析

前段时间弄的车机手机互联产品,当把Android手机插入车机上的时候需要通过ADB推送一个后台程序到手机上,由这个后台程序负责获取屏幕镜像然后编码通过网络传送给车机端,再由车机端将手机屏幕显示出来,那么如果不推送任何后台程序给Android手机的情况下能否实现类似的功能呢?答案当然是可以的,比如现在市面上有一种叫做同屏器的产品就可以实现这种功能,它实际利用了Miracast技术。Miracast实际上是WiFi联盟(WiFi Alliance)对支持WiFi Display功能的设备的认证名称(该认证项目已经在2012年9月正式启动)。而通过Miracast认证的设备,便可提供简化发现和设置,实现设备间高速传输视频。而WiFi Display功能又是基于WiFi Direct(WIFI P2P)之上的,所以要搞明白Miracast最好先看看两文档:Wi-Fi_Display_Specification_v1.1和WiFi_P2P_Technical_Specification_v1.2。而对于安卓系统来说,自从Android4.2版本以后就开始支持,Android4.2的源码中就已经包含了这部分的代码。


在WiFi Display(WDF)中数据发送端称为WFD Source(比如安卓手机),数据接收端我们称为WFD Sink(比如车机平台),简图如下:
如果要实现WiFi Display功能,整个流程可以分成两个大的部分,第一部分WIFI P2P部分,第二部分RTSP部分。简单来说就是WFD Source和WFD Sink之间首先通过WIFI P2P互相发现并且建立WIFI连接,连接建立好以后双方就可以进行网络通信了,然后双方之间会建立TCP通信,利用RTSP协议对音视频能力进行协商,最后WFD Source利用RTP/RTCP协议将音视频流(MPEG2-TS)源源不断的传给WFD Sink,WFD Sink收到后播放出来,就这样安卓手机的屏幕画面就可以显示到车机平台上了。其中WIFI P2P部分因为牵扯到底层所以这里暂时不讨论,我们只分析下RTSP部分。从现实主义出发,如果我们想开发一个Miracast产品,比如同屏器,那么我们应该重点关注WFD Sink端的实现,因为WFD Source端已经被安卓手机内置实现了。前面已经说过Android源码中已经包含了WiFi Display的实现,其实不单单是WFD Source端的实现,也包含了WFD Sink端的实现,所以说Android源码就像一个巨大的宝库,可以挖出很多宝贝。接下来我们利用Android4.2.2的源码,站在WFD Sink的角度来看看RTSP部分的实现,先看看例子图:



其中src代表WFD Source,snk代表WFD Sink。接下来我们看具体实现代码,整个WFD Sink端大体由以下几个关键类构成:
struct WifiDisplaySink : public AHandler {
    WifiDisplaySink(
            const sp<ANetworkSession> &netSession,
            const sp<ISurfaceTexture> &surfaceTex = NULL);
    void start(const char *sourceHost, int32_t sourcePort);
    省略若干...
    virtual void onMessageReceived(const sp<AMessage> &msg);
    status_t sendM2(int32_t sessionID);
    status_t sendSetup(int32_t sessionID, const char *uri);
    status_t sendPlay(int32_t sessionID, const char *uri);
    status_t onReceiveM2Response(int32_t sessionID, const sp<ParsedMessage> &msg);
    status_t onReceiveSetupResponse(int32_t sessionID, const sp<ParsedMessage> &msg);
    status_t onReceivePlayResponse(int32_t sessionID, const sp<ParsedMessage> &msg);
    void registerResponseHandler(int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func);
    void onReceiveClientData(const sp<AMessage> &msg);
    void onOptionsRequest(int32_t sessionID, int32_t cseq, const sp<ParsedMessage> &data);
    void onGetParameterRequest(int32_t sessionID, int32_t cseq, const sp<ParsedMessage> &data);
    void onSetParameterRequest(int32_t sessionID, int32_t cseq, const sp<ParsedMessage> &data);
    省略若干...
};

struct RTPSink : public AHandler {
    省略若干...
    RTPSink(const sp<ANetworkSession> &netSession, const sp<ISurfaceTexture> &surfaceTex);
    virtual void onMessageReceived(const sp<AMessage> &msg);
    status_t parseRTP(const sp<ABuffer> &buffer);
    status_t parseRTCP(const sp<ABuffer> &buffer);
    省略若干...
};

struct TunnelRenderer : public AHandler {
    TunnelRenderer(const sp<AMessage> &notifyLost, const sp<ISurfaceTexture> &surfaceTex);
    省略若干...
    virtual void onMessageReceived(const sp<AMessage> &msg);
    void initPlayer();
    省略若干...
};

struct ANetworkSession : public RefBase {
    省略若干...
    status_t createRTSPClient(const char *host, unsigned port,
                              const sp<AMessage> &notify, int32_t *sessionID);
    status_t createUDPSession(unsigned localPort, const sp<AMessage> &notify, int32_t *sessionID);
    status_t createUDPSession(
            unsigned localPort,
            const char *remoteHost,
            unsigned remotePort,
            const sp<AMessage> &notify,
            int32_t *sessionID);
    status_t connectUDPSession(int32_t sessionID, const char *remoteHost, unsigned remotePort);
    status_t sendRequest(int32_t sessionID, const void *data, ssize_t size = -1);
    void threadLoop();
    省略若干...
};

struct ANetworkSession::Session : public RefBase {
    省略若干...
    Session(int32_t sessionID, State state, int s, const sp<AMessage> &notify);
    int32_t sessionID() const;
    status_t sendRequest(const void *data, ssize_t size);
    省略若干...
    int mSocket;
};

struct ParsedMessage : public RefBase {
    static sp<ParsedMessage> Parse(
            const char *data, size_t size, bool noMoreData, size_t *length);
    省略若干...
    bool findString(const char *name, AString *value) const;
    bool findInt32(const char *name, int32_t *value) const;
    省略若干...
};

WifiDisplaySink代表WFD sink端,RTSP协议就在这个类中被处理。

RTPSink类用于处理RTP/RTCP协议。

TunnelRenderer类代表一个呈现通道,这个类会接收来自RTPSink类解包出来的MPEG2-TS音视频流,然后播放出来。

ANetworkSession类用于处理网络通信部分,它会创建一个线程,其中会用select网络模型,用于处理所有的网络收发。

ANetworkSession::Session类是一个内部类,它代表一个网络会话,它包含一个mSocket字段保存了此会话对应的套接字句柄,对于一个WFD sink端来说一般会包含三个Session,其中一个是RTSP会话,另一个是RTP会话,还有一个是RTCP会话。当然RTP/RTCP也可以采用TCP协议与RTSP共用一个TCP通道,这样也就只要一个Session。

ParsedMessage类用于解析具体的RTSP协议格式。

这里要重点强调的是整个程序采用了由ALooper AHandler AMessage三个类配合完成的异步消息处理机制,这种编程方式在Android源码中被大量采用。因为它确实不错。代码中WifiDisplaySink,RTPSink,TunnelRenderer等类都继承于AHandler,并且都实现了onMessageReceived函数,站在进程的角度来看,这些类都运行于由ALooper所创建的消息线程中,而网络通信部分工作在由ANetworkSession创建的网络线程当中,当ANetworkSession收到网络数据报,就会通过AMessage将消息传给消息线程中对应的AHandler派生类处理,比如WifiDisplaySink或者RTPSink。而RTPSink与TunnelRenderer之间也是通过AMessage消息传递,RTPSink解析完RTP数据包后,就会把解析出来的payload通过AMessage::post方法传递给TunnelRenderer,在稍后的某个时间点TunnelRenderer的onMessageReceived函数就会在消息线程中被调用,TunnelRenderer就可以对收到的数据进行下一步处理了,这就是所谓的异步消息处理机制。我们接着看代码:

int main(int argc, char **argv) {
    省略若干...
    sp<ANetworkSession> session = new ANetworkSession;
    //下面函数里面会启动一个网络处理线程。
    session->start();
    sp<ALooper> looper = new ALooper;
    sp<WifiDisplaySink> sink = new WifiDisplaySink(session);
    //将sink注册为消息处理者
    looper->registerHandler(sink);
    //后面会重点分析此函数,此函数将是个引子
    sink->start(connectToHost.c_str(), connectToPort);
    //将当前线程变成消息处理线程
    looper->start(true /* runOnCallingThread */);
    return 0;
}

代码比较简单,也有注释,总的来说就是首先创建网络处理线程,然后将主线程变成消息处理线程,并且把WifiDisplaySink注册为消息处理者,这样程序里面就有了两个线程,一个网络处理线程和一个消息处理线程(也就是主线程),两者配合构成了整个程序的基础(异步消息处理机制 )。基础有了那还需要一个引子推动程序流程继续推进,那么这个引子就是对sink->start函数的调用。我们接着看这个函数。
void WifiDisplaySink::start(const char *sourceHost, int32_t sourcePort) {
    //注意这里第二个参数非常重要,这个参数是指这个消息要发给哪个消息处理者处理,这里的
    //id()指的是AHandler::id() 因为 WifiDisplaySinkAHandler的派生类,所以这里
    //指的就是WifiDisplaySink 自己的id
    sp<AMessage> msg = new AMessage(kWhatStart, id());
    //要连接的IP地址
    msg->setString("sourceHost", sourceHost);
    //要连接的端口
    msg->setInt32("sourcePort", sourcePort);
    msg->post();
}

这里就是给WifiDisplaySink发了一个kWhatStart消息,接下来流程到了

void WifiDisplaySink::onMessageReceived(const sp<AMessage> &msg) {
    switch (msg->what()) {
        case kWhatStart:
        {
            int32_t sourcePort;
            if (msg->findString("setupURI", &mSetupURI)) {
                AString path, user, pass;
                CHECK(ParseURL(mSetupURI.c_str(), &mRTSPHost, &sourcePort, &path, &user, &pass) && user.empty() && pass.empty());
            } else {
                CHECK(msg->findString("sourceHost", &mRTSPHost));
                CHECK(msg->findInt32("sourcePort", &sourcePort));
            }
                                  //这个实例最终会被传给网络处理线程,以后网络处理线程就会利用此实例向消息处理线程传递消息
            sp<AMessage> notify = new AMessage(kWhatRTSPNotify, id());
            status_t err = mNetSession->createRTSPClient(mRTSPHost.c_str(), sourcePort, notify, &mSessionID);
            CHECK_EQ(err, (status_t)OK);
            mState = CONNECTING;
            break;
        }

它会调用createRTSPClient函数创建与WFD Source端的RTSP连接,此后就会根据文档规范进行M1到M7的信令交互了。这里重点要强调的是sp<AMessage> notify = new AMessage(kWhatRTSPNotify, id());这一句代码,notify会作为参数传给createRTSPClient,以后当网络处理线程接收到相应的网络数据报文就会利用notify将收到的数据post给WifiDisplaySink了。具体的M1到M7的信令交互就不说了,流程基本都在下面的函数中

void WifiDisplaySink::onMessageReceived(const sp<AMessage> &msg) {
    switch (msg->what()) {
    省略若干...
        case kWhatRTSPNotify:
        {
            int32_t reason;
            CHECK(msg->findInt32("reason", &reason));
            switch (reason) {
            省略若干...
                case ANetworkSession::kWhatData:
                {
                    onReceiveClientData(msg);
                    break;
                }
    省略若干...

继续看onReceiveClientData函数的实现:

void WifiDisplaySink::onReceiveClientData(const sp<AMessage> &msg) {
    ALOGD("onReceiveClientData");
    int32_t sessionID;
    CHECK(msg->findInt32("sessionID", &sessionID));
    sp<RefBase> obj;
    CHECK(msg->findObject("data", &obj));
    sp<ParsedMessage> data = static_cast<ParsedMessage *>(obj.get());
    ALOGV("session %d received '%s'", sessionID, data->debugString().c_str());
    AString method;
    AString uri;
    data->getRequestField(0, &method);
    int32_t cseq;
    if (!data->findInt32("cseq", &cseq)) {
        sendErrorResponse(sessionID, "400 Bad Request", -1 /* cseq */);
        return;
    }
    if (method.startsWith("RTSP/")) { //我发出去RTSP请求包以后,收到的响应
        // This is a response.
        ResponseID id;
        id.mSessionID = sessionID;
        id.mCSeq = cseq;
        ssize_t index = mResponseHandlers.indexOfKey(id);
        if (index < 0) {
            ALOGW("Received unsolicited server response, cseq %d", cseq);
            return;
        }
        HandleRTSPResponseFunc func = mResponseHandlers.valueAt(index);
        mResponseHandlers.removeItemsAt(index);
        status_t err = (this->*func)(sessionID, data);
        CHECK_EQ(err, (status_t)OK);
    } else {  //这是对方(source端)发给我的RTSP请求包
        AString version;
        data->getRequestField(2, &version);
        if (!(version == AString("RTSP/1.0"))) {
            sendErrorResponse(sessionID, "505 RTSP Version not supported", cseq);
            return;
        }
        //...
        if (method == "OPTIONS") {
            onOptionsRequest(sessionID, cseq, data);
        } else if (method == "GET_PARAMETER") {
            onGetParameterRequest(sessionID, cseq, data);
        } else if (method == "SET_PARAMETER") {
            onSetParameterRequest(sessionID, cseq, data);
        } else {
            sendErrorResponse(sessionID, "405 Method Not Allowed", cseq);
        }
    }
}

从上面的代码中可以看到,我们会收到两种类型的数据包,一种是Sink端发出RTSP请求包后收到的Source端发回来的RTSP应答包,另外一种是Source端发给Sink端的RTSP请求包,这里要分别进行处理,至于具体的细节就不分析了,代码里都有,不过这里最后要总结的是此程序并没有启用HDCP功能,Sink和Source之间协商的内容是wfd_content_protection: none。HDCP指的是High -bandwidth Digital Content Protection 高带宽数字内容保护技术,如果要了解可以自行查阅相关资料。