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

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

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

[java] view plaincopy

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

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

[java] view plaincopy

  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. int res = getsockname(
  19. mSocket, (struct sockaddr *)&localAddr, &localAddrLen);
  20. CHECK_GE(res, 0);
  21. struct sockaddr_in remoteAddr;
  22. socklen_t remoteAddrLen = sizeof(remoteAddr);
  23. res = getpeername(
  24. mSocket, (struct sockaddr *)&remoteAddr, &remoteAddrLen);
  25. CHECK_GE(res, 0);
  26. sp<AMessage> msg = mNotify->dup();
  27. msg->setInt32("sessionID", mSessionID);
  28. msg->setInt32("reason", kWhatClientConnected);
  29. msg->setString("server-ip", localAddrString.c_str());
  30. msg->setInt32("server-port", ntohs(localAddr.sin_port));
  31. msg->setString("client-ip", remoteAddrString.c_str());
  32. msg->setInt32("client-port", ntohs(remoteAddr.sin_port));
  33. msg->post();
  34. }
  35. }

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

[java] view plaincopy

  1. case kWhatRTSPNotify:
  2. {
  3. int32_t reason;
  4. CHECK(msg->findInt32("reason", &reason));
  5. switch (reason) {
  6. case ANetworkSession::kWhatError:
  7. {
  8. break;
  9. }
  10. case ANetworkSession::kWhatClientConnected:
  11. {
  12. int32_t sessionID;
  13. CHECK(msg->findInt32("sessionID", &sessionID));
  14. if (mClientSessionID > 0) {
  15. ALOGW("A client tried to connect, but we already "
  16. "have one.");
  17. mNetSession->destroySession(sessionID);
  18. break;
  19. }
  20. CHECK_EQ(mState, AWAITING_CLIENT_CONNECTION);
  21. CHECK(msg->findString("client-ip", &mClientInfo.mRemoteIP));
  22. CHECK(msg->findString("server-ip", &mClientInfo.mLocalIP));
  23. if (mClientInfo.mRemoteIP == mClientInfo.mLocalIP) {
  24. // Disallow connections from the local interface
  25. // for security reasons.
  26. mNetSession->destroySession(sessionID);
  27. break;
  28. }
  29. CHECK(msg->findInt32(
  30. "server-port", &mClientInfo.mLocalPort));
  31. mClientInfo.mPlaybackSessionID = -1;
  32. mClientSessionID = sessionID;
  33. ALOGI("We now have a client (%d) connected.", sessionID);
  34. mState = AWAITING_CLIENT_SETUP;
  35. status_t err = sendM1(sessionID);
  36. CHECK_EQ(err, (status_t)OK);
  37. break;
  38. }
  39. case ANetworkSession::kWhatData:
  40. {
  41. break;
  42. }
  43. case ANetworkSession::kWhatNetworkStall:
  44. {
  45. break;
  46. }
  47. default:
  48. TRESPASS();
  49. }
  50. break;
  51. }

这里的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

  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. int32_t playbackSessionID = makeUniquePlaybackSessionID();
  11. sp<AMessage> notify = new AMessage(kWhatPlaybackSessionNotify, id());
  12. notify->setInt32("playbackSessionID", playbackSessionID);
  13. notify->setInt32("sessionID", sessionID);
  14. sp<PlaybackSession> playbackSession =
  15. new PlaybackSession(
  16. mNetSession, notify, mInterfaceAddr, mHDCP, mMediaPath.c_str());
  17. looper()->registerHandler(playbackSession);
  18. AString uri;
  19. data->getRequestField(1, &uri);
  20. if (strncasecmp("rtsp://", uri.c_str(), 7)) {
  21. sendErrorResponse(sessionID, "400 Bad Request", cseq);
  22. return ERROR_MALFORMED;
  23. }
  24. if (!(uri.startsWith("rtsp://") && uri.endsWith("/wfd1.0/streamid=0"))) {
  25. sendErrorResponse(sessionID, "404 Not found", cseq);
  26. return ERROR_MALFORMED;
  27. }
  28. RTPSender::TransportMode rtcpMode = RTPSender::TRANSPORT_UDP;
  29. if (clientRtcp < 0) {
  30. rtcpMode = RTPSender::TRANSPORT_NONE;
  31. }
  32. status_t err = playbackSession->init(
  33. mClientInfo.mRemoteIP.c_str(),
  34. clientRtp,
  35. rtpMode,
  36. clientRtcp,
  37. rtcpMode,
  38. mSinkSupportsAudio,
  39. mUsingPCMAudio,
  40. mSinkSupportsVideo,
  41. mChosenVideoResolutionType,
  42. mChosenVideoResolutionIndex,
  43. mChosenVideoProfile,
  44. mChosenVideoLevel);
  45. if (err != OK) {
  46. looper()->unregisterHandler(playbackSession->id());
  47. playbackSession.clear();
  48. }
  49. mClientInfo.mPlaybackSessionID = playbackSessionID;
  50. mClientInfo.mPlaybackSession = playbackSession;
  51. mState = AWAITING_CLIENT_PLAY;
  52. scheduleReaper();
  53. scheduleKeepAlive(sessionID);
  54. return OK;
  55. }

跳过前面的关于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中。

时间: 2024-10-28 20:42:48

Android WifiDisplay分析三:RTSP交互以及数据传输的相关文章

Android WifiDisplay分析二:Wifi display连接过程.

这一章中我们来看Wifi Display连接过程的建立,包含P2P的部分和RTSP的部分,首先来大致看一下Wifi Display规范相关的东西. HIDC: Human Interface Device Class  (遵循HID标准的设备类)UIBC: User Input Back Channel  (UIBC分为两种,一种是Generic,包含鼠标.键盘等:另一种是HIDC,HID是一个规范,只有遵循HID的标准,都可以叫做HID设备,包含USB鼠标.键盘.蓝牙.红外等)PES: Pac

Android ANR分析(三)

http://www.jianshu.com/p/8964812972be http://stackoverflow.com/questions/704311/android-how-do-i-investigate-an-anr Keeping Your App Responsive PreviousNext In this document What Triggers ANR? How to Avoid ANRs Reinforcing Responsiveness You should a

Android Telephony分析(六) ---- 接口扩展(实践篇)

本文将结合前面五篇文章所讲解的知识,综合起来,实现一个接口扩展的功能.如果还没有阅读过前面五篇文章的内容,请先阅读:<Android Telephony分析(一) — Phone详解 ><Android Telephony分析(二) — RegistrantList详解 ><Android Telephony分析(三) — RILJ详解 ><Android Telephony分析(四) — TelephonyManager详解 ><Android Te

[Android]Fragment源码分析(三) 事务

Fragment管理中,不得不谈到的就是它的事务管理,它的事务管理写的非常的出彩.我们先引入一个简单常用的Fragment事务管理代码片段: FragmentTransaction ft = this.getSupportFragmentManager().beginTransaction(); ft.add(R.id.fragmentContainer, fragment, "tag"); ft.addToBackStack("<span style="fo

android 休眠唤醒机制分析(三) — suspend

本文转自:http://blog.csdn.net/g_salamander/article/details/7988340 前面我们分析了休眠的第一个阶段即浅度休眠,现在我们继续看休眠的第二个阶段 — 深度休眠.在深度休眠的过程中系统会首先冻结所有可以冻结的进程,然后依次挂起所有设备的电源,挂起顺序与设备注册的顺序相反,这样保证了设备之间电源的依赖性:直至最后进入省电模式,等待用户或者RTC唤醒:在唤醒过程中则会按照设备注册的顺序依次恢复每个设备的电源进入正常工作状态,解冻相关的进程,然后再进

Android架构分析之Android消息处理机制(三)

作者:刘昊昱 博客:http://blog.csdn.net/liuhaoyutz Android版本:4.4.2 本文我们来分析AndroidUI线程即主线程是怎样实现对消息的处理的. UI线程的实现类定义在frameworks/base/core/java/android/app/ActivityThread.java文件中.我们来看Android对ActivityThread类的说明 : 130/** 131 * This manages the execution of the main

nova boot代码流程分析(三):nova与neutron的交互(2)

继续<nova boot代码流程分析(三):nova与neutron的交互(1)>的分析. #/nova/virt/libvirt/driver.py:LibvirtDriver # NOTE(ilyaalekseyev): Implementation like in multinics # for xenapi(tr3buchet) def spawn(self, context, instance, image_meta, injected_files, admin_password,

Android Binder分析二:Natvie Service的注冊

这一章我们通过MediaPlayerService的注冊来说明怎样在Native层通过binder向ServiceManager注冊一个service,以及client怎样通过binder向ServiceManager获得一个service,并调用这个Service的方法. Native Service的注冊 这里以MediaPlayerService举例来说明怎样在Native层注冊Service,首先来看main_mediaservice.cpp的main方法: int main(int a

Android多线程分析之五:使用AsyncTask异步下载图像

Android多线程分析之五:使用AsyncTask异步下载图像 罗朝辉 (http://blog.csdn.net/kesalin) CC 许可,转载请注明出处 在本系列文章的第一篇<Android多线程分析之中的一个:使用Thread异步下载图像>中.曾演示了怎样使用 Thread 来完毕异步任务. Android 为了简化在 UI 线程中完毕异步任务(毕竟 UI 线程是 app 最重要的线程).实现了一个名为 AysncTask 的模板类.使用 AysncTask 能够在异步任务进行的同