前段时间弄的车机手机互联产品,当把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的源码中就已经包含了这部分的代码。
2017-4-11 12:52:10
WIFI-Display浅析
在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> ¬ifyLost, 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> ¬ify, int32_t *sessionID);
status_t createUDPSession(unsigned localPort, const sp<AMessage> ¬ify, int32_t *sessionID);
status_t createUDPSession(
unsigned localPort,
const char *remoteHost,
unsigned remotePort,
const sp<AMessage> ¬ify,
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> ¬ify);
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(), 因为 WifiDisplaySink是AHandler的派生类,所以这里
//指的就是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 高带宽数字内容保护技术,如果要了解可以自行查阅相关资料。