代码改变世界

Android WifiDisplay分析三:RTSP交互以及数据传输

2015-10-13 20:20  指针空间  阅读(1494)  评论(0编辑  收藏  举报

前面我们分析到WifiDisplaySource会调用ANetworkSession的接口去创建一个socket,并在这个socket上监听是否有客户端的连接请求。先来看看Wifi Display规范的一些流程图:

 

从之前的一篇文章中,当ANetworkSession创建好RTSP的listen socket后,就会把它加入到selelct中等待对方的连接,那我们首先来看ANetworkSession的threadLoop方法:

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. void ANetworkSession::threadLoop() {  
  2.   
  3.     int res = select(maxFd + 1, &rs, &ws, NULL, NULL /* tv */);  
  4.   
  5.     {  
  6.         Mutex::Autolock autoLock(mLock);  
  7.   
  8.         List<sp<Session> > sessionsToAdd;  
  9.   
  10.         for (size_t i = mSessions.size(); res > 0 && i-- > 0;) {  
  11.             const sp<Session> &session = mSessions.valueAt(i);  
  12.   
  13.             int s = session->socket();  
  14.   
  15.             if (s < 0) {  
  16.                 continue;  
  17.             }  
  18.   
  19.             if (FD_ISSET(s, &rs) || FD_ISSET(s, &ws)) {  
  20.                 --res;  
  21.             }  
  22.   
  23.             if (FD_ISSET(s, &rs)) {  
  24.                 if (session->isRTSPServer() || session->isTCPDatagramServer()) {  
  25.                     struct sockaddr_in remoteAddr;  
  26.                     socklen_t remoteAddrLen = sizeof(remoteAddr);  
  27.   
  28.                     int clientSocket = accept(  
  29.                             s, (struct sockaddr *)&remoteAddr, &remoteAddrLen);  
  30.   
  31.                     if (clientSocket >= 0) {  
  32.                         status_t err = MakeSocketNonBlocking(clientSocket);  
  33.   
  34.                         if (err != OK) {  
  35.   
  36.                         } else {  
  37.                             in_addr_t addr = ntohl(remoteAddr.sin_addr.s_addr);  
  38.   
  39.                             ALOGI("incoming connection from %d.%d.%d.%d:%d "  
  40.                                   "(socket %d)",  
  41.                                   (addr >> 24),  
  42.                                   (addr >> 16) & 0xff,  
  43.                                   (addr >> 8) & 0xff,  
  44.                                   addr & 0xff,  
  45.                                   ntohs(remoteAddr.sin_port),  
  46.                                   clientSocket);  
  47.   
  48.                             sp<Session> clientSession =  
  49.                                 new Session(  
  50.                                         mNextSessionID++,  
  51.                                         Session::CONNECTED,  
  52.                                         clientSocket,  
  53.                                         session->getNotificationMessage());  
  54.   
  55.                             clientSession->setMode(  
  56.                                     session->isRTSPServer()  
  57.                                         ? Session::MODE_RTSP  
  58.                                         : Session::MODE_DATAGRAM);  
  59.   
  60.                             sessionsToAdd.push_back(clientSession);  
  61.                         }  
  62.                     } else {  
  63.                         ALOGE("accept returned error %d (%s)",  
  64.                               errno, strerror(errno));  
  65.                     }  
  66.                 }   
  67.             }  
  68.   
  69.         while (!sessionsToAdd.empty()) {  
  70.             sp<Session> session = *sessionsToAdd.begin();  
  71.             sessionsToAdd.erase(sessionsToAdd.begin());  
  72.   
  73.             mSessions.add(session->sessionID(), session);  
  74.   
  75.             ALOGI("added clientSession %d", session->sessionID());  
  76.         }  
  77.     }  


上面在selelct循环中,首先只有刚创建的RTSP的listen socket,接着如果有客户端的连接请求,就会跳出select语句,然后调用accept去接收对方的连接。接着会去创建一个新的Session会话,我们去看它的构造函数:

 

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. ANetworkSession::Session::Session(  
  2.         int32_t sessionID,  
  3.         State state,  
  4.         int s,  
  5.         const sp<AMessage> ¬ify)  
  6.     : mSessionID(sessionID),  
  7.       mState(state),  
  8.       mMode(MODE_DATAGRAM),  
  9.       mSocket(s),  
  10.       mNotify(notify),  
  11.       mSawReceiveFailure(false),  
  12.       mSawSendFailure(false),  
  13.       mUDPRetries(kMaxUDPRetries),  
  14.       mLastStallReportUs(-1ll) {  
  15.     if (mState == CONNECTED) {  
  16.         struct sockaddr_in localAddr;  
  17.         socklen_t localAddrLen = sizeof(localAddr);  
  18.   
  19.         int res = getsockname(  
  20.                 mSocket, (struct sockaddr *)&localAddr, &localAddrLen);  
  21.         CHECK_GE(res, 0);  
  22.   
  23.         struct sockaddr_in remoteAddr;  
  24.         socklen_t remoteAddrLen = sizeof(remoteAddr);  
  25.   
  26.         res = getpeername(  
  27.                 mSocket, (struct sockaddr *)&remoteAddr, &remoteAddrLen);  
  28.         CHECK_GE(res, 0);  
  29.   
  30.         sp<AMessage> msg = mNotify->dup();  
  31.         msg->setInt32("sessionID", mSessionID);  
  32.         msg->setInt32("reason", kWhatClientConnected);  
  33.         msg->setString("server-ip", localAddrString.c_str());  
  34.         msg->setInt32("server-port", ntohs(localAddr.sin_port));  
  35.         msg->setString("client-ip", remoteAddrString.c_str());  
  36.         msg->setInt32("client-port", ntohs(remoteAddr.sin_port));  
  37.         msg->post();  
  38.     }  
  39. }  


这里的状态mState为CONNECTED,所以会构建一个AMessage并post出去,由前一章的知识,我们知道这里的mNotify是一个kWhatRTSPNotify消息,会在WifiDisplaySource调用createRTSPServer时传进来的一个参数,所以这里创建的AMessage最终还是会被WifiDisplaySource去处理,跳过中间AMessage、ALooperRoster、ALooper的调用关系,我们直接到WifiDisplaySource的onMessageReceived去看如何处理:

 

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. case kWhatRTSPNotify:  
  2. {  
  3.     int32_t reason;  
  4.     CHECK(msg->findInt32("reason", &reason));  
  5.   
  6.     switch (reason) {  
  7.         case ANetworkSession::kWhatError:  
  8.         {  
  9.   
  10.             break;  
  11.         }  
  12.   
  13.         case ANetworkSession::kWhatClientConnected:  
  14.         {  
  15.             int32_t sessionID;  
  16.             CHECK(msg->findInt32("sessionID", &sessionID));  
  17.   
  18.             if (mClientSessionID > 0) {  
  19.                 ALOGW("A client tried to connect, but we already "  
  20.                       "have one.");  
  21.   
  22.                 mNetSession->destroySession(sessionID);  
  23.                 break;  
  24.             }  
  25.   
  26.             CHECK_EQ(mState, AWAITING_CLIENT_CONNECTION);  
  27.   
  28.             CHECK(msg->findString("client-ip", &mClientInfo.mRemoteIP));  
  29.             CHECK(msg->findString("server-ip", &mClientInfo.mLocalIP));  
  30.   
  31.             if (mClientInfo.mRemoteIP == mClientInfo.mLocalIP) {  
  32.                 // Disallow connections from the local interface  
  33.                 // for security reasons.  
  34.                 mNetSession->destroySession(sessionID);  
  35.                 break;  
  36.             }  
  37.   
  38.             CHECK(msg->findInt32(  
  39.                         "server-port", &mClientInfo.mLocalPort));  
  40.             mClientInfo.mPlaybackSessionID = -1;  
  41.   
  42.             mClientSessionID = sessionID;  
  43.   
  44.             ALOGI("We now have a client (%d) connected.", sessionID);  
  45.   
  46.             mState = AWAITING_CLIENT_SETUP;  
  47.   
  48.             status_t err = sendM1(sessionID);  
  49.             CHECK_EQ(err, (status_t)OK);  
  50.             break;  
  51.         }  
  52.   
  53.         case ANetworkSession::kWhatData:  
  54.         {  
  55.   
  56.             break;  
  57.         }  
  58.   
  59.         case ANetworkSession::kWhatNetworkStall:  
  60.         {  
  61.             break;  
  62.         }  
  63.   
  64.         default:  
  65.             TRESPASS();  
  66.     }  
  67.     break;  
  68. }  

 

这里的reason是kWhatClientConnected,跳过前面不必要的case语句。如果先前已经连上其它的Sink device,这里就先断开之前的连接;如果没有,将新的SessionID赋予给mClientSessionID,并更改状态为AWAITING_CLIENT_SETUP,接着去看sendM1消息,这时候就要开始WifiDisplay M1~M7消息的发送了。

下面列举M1~M7消息的格式,有兴趣的可以去对照代码分析,我们后面着重分析M6(SetUp)和M7(Play)两个消息。

M1 reqeust:

OPTIONS * RTSP/1.0
Date: Tue, 29 Fri 2014 02:41:24 +0000
Server: stagefright/1.2 (Linux;Android 4.4)
CSeq: 1
Require: org.wfa.wfd1.0

 

M1 respose:

RTSP/1.0 200 OK
CSeq: 1
Date: Fri, Jan 01 2014 09:02:37 GMT
Public: org.wfa.wfd1.0, GET_PARAMETER, SET_PARAMETER

 

M2 request:

OPTIONS * RTSP/1.0
CSeq: 2
Require: org.wfa.wfd1.0

 


M2 response:

RTSP/1.0 200 OK
Date: Tue, 29 Fri 2014 02:41:25 +0000
Server: stagefright/1.2 (Linux;Android 4.3)
CSeq: 2
Public: org.wfa.wfd1.0, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER


M3 request:

GET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0
Date: Tue, 29 Fri 2014 02:41:25 +0000
Server: stagefright/1.2 (Linux;Android 4.3)
CSeq: 2
Content-Type: text/parameters
Content-Length: 83

wfd_content_protection
wfd_video_formats
wfd_audio_codecs
wfd_client_rtp_ports

 

M3 response:

RTSP/1.0 200 OK
CSeq: 2
Content-Length: 124
Content-Type: text/parameters

wfd_audio_codecs: LPCM 00000003 00, AAC 00000007 00
wfd_video_formats: 00 00 02 02 0000FFFF 0FFFFFFF 00000FFF 00 0000 0000 01 none none
wfd_content_protection: none
wfd_client_rtp_ports: RTP/AVP/UDP;unicast 19990 0 mode=play

 

M4 request:

SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0
Date: Tue, 29 Fri 2014 02:41:25 +0000
Server: stagefright/1.2 (Linux;Android 4.3)
CSeq: 3
Content-Type: text/parameters
Content-Length: 247
wfd_video_formats: 00 00 02 02 00000020 00000000 00000000 00 0000 0000 00 none none
wfd_audio_codecs: AAC 00000001 00
wfd_presentation_URL: rtsp://192.168.5.200/wfd1.0/streamid=0 none
wfd_client_rtp_ports: RTP/AVP/UDP;unicast 19990 0 mode=play

 

M4 response:

RTSP/1.0 200 OK
CSeq: 3

 

M5 request:

SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0
Date: Tue, 29 Fri 2014 02:41:25 +0000
Server: stagefright/1.2 (Linux;Android 4.3)
CSeq: 4
Content-Type: text/parameters
Content-Length: 27
wfd_trigger_method: SETUP

 

 

M5 response:

RTSP/1.0 200 OK
CSeq: 4

 

M6 request:

SETUP rtsp://192.168.5.200/wfd1.0/streamid=0 RTSP/1.0
CSeq: 3
Transport: RTP/AVP/UDP;unicast;client_port=19990

 

M6 response:

RTSP/1.0 200 OK
Date: Tue, 29 Fri 2014 02:41:25 +0000
Server: stagefright/1.2 (Linux;Android 4.3)
CSeq: 3
Session: 988982966;timeout=30
Transport: RTP/AVP/UDP;unicast;client_port=19990;server_port=22220

 

我们先来看处理M6 request的方法,代码在onSetupRequest中:

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. status_t WifiDisplaySource::onSetupRequest(  
  2.         int32_t sessionID,  
  3.         int32_t cseq,  
  4.         const sp<ParsedMessage> &data) {  
  5.     CHECK_EQ(sessionID, mClientSessionID);  
  6.     if (mClientInfo.mPlaybackSessionID != -1) {  
  7.         sendErrorResponse(sessionID, "400 Bad Request", cseq);  
  8.         return ERROR_MALFORMED;  
  9.     }  
  10.   
  11.     int32_t playbackSessionID = makeUniquePlaybackSessionID();  
  12.   
  13.     sp<AMessage> notify = new AMessage(kWhatPlaybackSessionNotify, id());  
  14.     notify->setInt32("playbackSessionID", playbackSessionID);  
  15.     notify->setInt32("sessionID", sessionID);  
  16.   
  17.     sp<PlaybackSession> playbackSession =  
  18.         new PlaybackSession(  
  19.                 mNetSession, notify, mInterfaceAddr, mHDCP, mMediaPath.c_str());  
  20.   
  21.     looper()->registerHandler(playbackSession);  
  22.   
  23.     AString uri;  
  24.     data->getRequestField(1, &uri);  
  25.   
  26.     if (strncasecmp("rtsp://", uri.c_str(), 7)) {  
  27.         sendErrorResponse(sessionID, "400 Bad Request", cseq);  
  28.         return ERROR_MALFORMED;  
  29.     }  
  30.   
  31.     if (!(uri.startsWith("rtsp://") && uri.endsWith("/wfd1.0/streamid=0"))) {  
  32.         sendErrorResponse(sessionID, "404 Not found", cseq);  
  33.         return ERROR_MALFORMED;  
  34.     }  
  35.   
  36.     RTPSender::TransportMode rtcpMode = RTPSender::TRANSPORT_UDP;  
  37.     if (clientRtcp < 0) {  
  38.         rtcpMode = RTPSender::TRANSPORT_NONE;  
  39.     }  
  40.   
  41.     status_t err = playbackSession->init(  
  42.             mClientInfo.mRemoteIP.c_str(),  
  43.             clientRtp,  
  44.             rtpMode,  
  45.             clientRtcp,  
  46.             rtcpMode,  
  47.             mSinkSupportsAudio,  
  48.             mUsingPCMAudio,  
  49.             mSinkSupportsVideo,  
  50.             mChosenVideoResolutionType,  
  51.             mChosenVideoResolutionIndex,  
  52.             mChosenVideoProfile,  
  53.             mChosenVideoLevel);  
  54.   
  55.     if (err != OK) {  
  56.         looper()->unregisterHandler(playbackSession->id());  
  57.         playbackSession.clear();  
  58.     }  
  59.   
  60.     mClientInfo.mPlaybackSessionID = playbackSessionID;  
  61.     mClientInfo.mPlaybackSession = playbackSession;  
  62.   
  63.     mState = AWAITING_CLIENT_PLAY;  
  64.   
  65.     scheduleReaper();  
  66.     scheduleKeepAlive(sessionID);  
  67.   
  68.     return OK;  
  69. }  

 

跳过前面的关于RTSP回复消息的组织,这里还会创建一个PlaybackSession对象,并调用它的init方法做初始化。

 

根据前面的背景知识介绍,设备之间的交互将由Session来管理。在代码中,Session的概念由WifiDisplaySource的内部类PlaybackSession来表示。先来看和其相关的类图结构,如下图所示:

由上图可知:

  •  PlaybackSession及其内部类Track都从AHandler派生。故它们的工作也依赖于消息循环和处理。Track代表视频流或音频流。
  • Track内部通过mMediaPull变量指向一个MediaPull对象。而MediaPull对象则保存了一个MediaSource对象。在PlaybackSession中,此MediaSource的真正类型为SurfaceMediaSource。它表明该Media的源来自Surface。
  •  BufferQueue从ISurfaceTexure中派生,根据前面对SurfaceFlinger的介绍,它就是SurfaceFlinger代码示例中代表虚拟设备的State的surface变量。
  • 左图中,MediaPull通过kWhatPull消息不断调用MediaSource的read函数。
  • 右图中,SurfaceMediaSource的read函数由通过mBufferQueue来读取数据。
  • 那么mBufferQueue的数据来自什么地方呢?对,正是来自SurfaceFlinger。

    当然,PlaybackSession拿到这些数据后还需要做编码,然后才能发送给远端设备。由于篇幅关系,本文就不再讨论这些问题了。

当双方设备准备就绪后,MediaPull会通过kWhatPull消息处理不断调用MediaSource的read函数。在SurfaceMediaSource实现的read函数中,来自SurfaceFlinger的混屏后的数据经由BufferQueue传递到MediaPull中。