(转)Live555中RTSPClient分析

有RTSPServer,当然就要有RTSPClient。

如果按照Server端的架构,想一下Client端各部分的组成可能是这样:
因为要连接RTSP server,所以RTSPClient要有TCP socket。当获取到server端的DESCRIBE后,应建立一个对应于ServerMediaSession的ClientMediaSession。对应每个Track,ClientMediaSession中应建立ClientMediaSubsession。当建立RTP Session时,应分别为所拥有的Track发送SETUP请求连接,在获取回应后,分别为所有的track建立RTP socket,然后请求PLAY,然后开始传输数据。事实是这样吗?只能分析代码了。

testProgs中的OpenRTSP是典型的RTSPClient示例,所以分析它吧。
main()函数在playCommon.cpp文件中。main()的流程比较简单,跟服务端差别不大:建立任务计划对象--建立环境对象--处理用户输入的参数(RTSP地址)--创建RTSPClient实例--发出第一个RTSP请求(可能是OPTIONS也可能是DESCRIBE)--进入Loop。

RTSP的tcp连接是在发送第一个RTSP请求时才建立的,在RTSPClient的那几个发请求的函数sendXXXXXXCommand()中最终都调用sendRequest(),sendRequest()中会跟据情况建立起TCP连接。在建立连接时马上向任务计划中加入处理从这个TCP接收数据的socket handler:RTSPClient::incomingDataHandler()。
下面就是发送RTSP请求,OPTIONS就不必看了,从请求DESCRIBE开始:

[cpp] view plaincopy

  1. void getSDPDescription(RTSPClient::responseHandler* afterFunc)
  2. {
  3. ourRTSPClient->sendDescribeCommand(afterFunc, ourAuthenticator);
  4. }
  5. unsigned RTSPClient::sendDescribeCommand(responseHandler* responseHandler,
  6. Authenticator* authenticator)
  7. {
  8. if (authenticator != NULL)
  9. fCurrentAuthenticator = *authenticator;
  10. return sendRequest(new RequestRecord(++fCSeq, "DESCRIBE", responseHandler));
  11. }

参数responseHandler是调用者提供的回调函数,用于在处理完请求的回应后再调用之。并且在这个回调函数中会发出下一个请求--所有的请求都是这样依次发出的。使用回调函数的原因主要是因为socket的发送与接收不是同步进行的。类RequestRecord就代表一个请求,它不但保存了RTSP请求相关的信息,而且保存了请求完成后的回调函数--就是responseHandler。有些请求发出时还没建立tcp连接,不能立即发送,则加入fRequestsAwaitingConnection队列;有些发出后要等待Server端的回应,就加入fRequestsAwaitingResponse队列,当收到回应后再从队列中把它取出。
由于RTSPClient::sendRequest()太复杂,就不列其代码了,其无非是建立起RTSP请求字符串然后用TCP socket发送之。

现在看一下收到DESCRIBE的回应后如何处理它。理论上是跟据媒体信息建立起MediaSession了,看看是不是这样:

[cpp] view plaincopy

  1. void continueAfterDESCRIBE(RTSPClient*, int resultCode, char* resultString)
  2. {
  3. char* sdpDescription = resultString;
  4. //跟据SDP创建MediaSession。
  5. // Create a media session object from this SDP description:
  6. session = MediaSession::createNew(*env, sdpDescription);
  7. delete[] sdpDescription;
  8. // Then, setup the "RTPSource"s for the session:
  9. MediaSubsessionIterator iter(*session);
  10. MediaSubsession *subsession;
  11. Boolean madeProgress = False;
  12. char const* singleMediumToTest = singleMedium;
  13. //循环所有的MediaSubsession,为每个设置其RTPSource的参数
  14. while ((subsession = iter.next()) != NULL) {
  15. //初始化subsession,在其中会建立RTP/RTCP socket以及RTPSource。
  16. if (subsession->initiate(simpleRTPoffsetArg)) {
  17. madeProgress = True;
  18. if (subsession->rtpSource() != NULL) {
  19. // Because we‘re saving the incoming data, rather than playing
  20. // it in real time, allow an especially large time threshold
  21. // (1 second) for reordering misordered incoming packets:
  22. unsigned const thresh = 1000000; // 1 second
  23. subsession->rtpSource()->setPacketReorderingThresholdTime(thresh);
  24. // Set the RTP source‘s OS socket buffer size as appropriate - either if we were explicitly asked (using -B),
  25. // or if the desired FileSink buffer size happens to be larger than the current OS socket buffer size.
  26. // (The latter case is a heuristic, on the assumption that if the user asked for a large FileSink buffer size,
  27. // then the input data rate may be large enough to justify increasing the OS socket buffer size also.)
  28. int socketNum = subsession->rtpSource()->RTPgs()->socketNum();
  29. unsigned curBufferSize = getReceiveBufferSize(*env,socketNum);
  30. if (socketInputBufferSize > 0 || fileSinkBufferSize > curBufferSize) {
  31. unsigned newBufferSize = socketInputBufferSize > 0 ? 
  32. socketInputBufferSize : fileSinkBufferSize;
  33. newBufferSize = setReceiveBufferTo(*env, socketNum, newBufferSize);
  34. if (socketInputBufferSize > 0) { // The user explicitly asked for the new socket buffer size; announce it:
  35. *env
  36. << "Changed socket receive buffer size for the \""
  37. << subsession->mediumName() << "/"
  38. << subsession->codecName()
  39. << "\" subsession from " << curBufferSize
  40. << " to " << newBufferSize << " bytes\n";
  41. }
  42. }
  43. }
  44. }
  45. }
  46. if (!madeProgress)
  47. shutdown();
  48. // Perform additional ‘setup‘ on each subsession, before playing them:
  49. //下一步就是发送SETUP请求了。需要为每个Track分别发送一次。
  50. setupStreams();
  51. }

此函数被删掉很多枝叶,所以发现与原版不同请不要惊掉大牙。
的确在DESCRIBE回应后建立起了MediaSession,而且我们发现Client端的MediaSession不叫ClientMediaSesson,SubSession亦不是。我现在很想看看MediaSession与MediaSubsession的建立过程:

[cpp] view plaincopy

  1. MediaSession* MediaSession::createNew(UsageEnvironment& env,char const* sdpDescription)
  2. {
  3. MediaSession* newSession = new MediaSession(env);
  4. if (newSession != NULL) {
  5. if (!newSession->initializeWithSDP(sdpDescription)) {
  6. delete newSession;
  7. return NULL;
  8. }
  9. }
  10. return newSession;
  11. }

我可以告诉你,MediaSession的构造函数没什么可看的,那么就来看initializeWithSDP():
内容太多,不必看了,我大体说说吧:就是处理SDP,跟据每一行来初始化一些变量。当遇到"m="行时,就建立一个MediaSubsession,然后再处理这一行之下,下一个"m="行之上的行们,用这些参数初始化MediaSubsession的变量。循环往复,直到尽头。然而这其中并没有建立RTP socket。我们发现在continueAfterDESCRIBE()中,创建MediaSession之后又调用了subsession->initiate(simpleRTPoffsetArg),那么socket是不是在它里面创建的呢?look:

[cpp] view plaincopy

  1. Boolean MediaSubsession::initiate(int useSpecialRTPoffset)
  2. {
  3. if (fReadSource != NULL)
  4. return True; // has already been initiated
  5. do {
  6. if (fCodecName == NULL) {
  7. env().setResultMsg("Codec is unspecified");
  8. break;
  9. }
  10. //创建RTP/RTCP sockets
  11. // Create RTP and RTCP ‘Groupsocks‘ on which to receive incoming data.
  12. // (Groupsocks will work even for unicast addresses)
  13. struct in_addr tempAddr;
  14. tempAddr.s_addr = connectionEndpointAddress();
  15. // This could get changed later, as a result of a RTSP "SETUP"
  16. if (fClientPortNum != 0) {
  17. //当server端指定了建议的client端口
  18. // The sockets‘ port numbers were specified for us.  Use these:
  19. fClientPortNum = fClientPortNum & ~1; // even
  20. if (isSSM()) {
  21. fRTPSocket = new Groupsock(env(), tempAddr, fSourceFilterAddr,
  22. fClientPortNum);
  23. } else {
  24. fRTPSocket = new Groupsock(env(), tempAddr, fClientPortNum,
  25. 255);
  26. }
  27. if (fRTPSocket == NULL) {
  28. env().setResultMsg("Failed to create RTP socket");
  29. break;
  30. }
  31. // Set our RTCP port to be the RTP port +1
  32. portNumBits const rtcpPortNum = fClientPortNum | 1;
  33. if (isSSM()) {
  34. fRTCPSocket = new Groupsock(env(), tempAddr, fSourceFilterAddr,
  35. rtcpPortNum);
  36. } else {
  37. fRTCPSocket = new Groupsock(env(), tempAddr, rtcpPortNum, 255);
  38. }
  39. if (fRTCPSocket == NULL) {
  40. char tmpBuf[100];
  41. sprintf(tmpBuf, "Failed to create RTCP socket (port %d)",
  42. rtcpPortNum);
  43. env().setResultMsg(tmpBuf);
  44. break;
  45. }
  46. } else {
  47. //Server端没有指定client端口,我们自己找一个。之所以做的这样复杂,是为了能找到连续的两个端口
  48. //RTP/RTCP的端口号不是要连续吗?还记得不?
  49. // Port numbers were not specified in advance, so we use ephemeral port numbers.
  50. // Create sockets until we get a port-number pair (even: RTP; even+1: RTCP).
  51. // We need to make sure that we don‘t keep trying to use the same bad port numbers over and over again.
  52. // so we store bad sockets in a table, and delete them all when we‘re done.
  53. HashTable* socketHashTable = HashTable::create(ONE_WORD_HASH_KEYS);
  54. if (socketHashTable == NULL)
  55. break;
  56. Boolean success = False;
  57. NoReuse dummy; // ensures that our new ephemeral port number won‘t be one that‘s already in use
  58. while (1) {
  59. // Create a new socket:
  60. if (isSSM()) {
  61. fRTPSocket = new Groupsock(env(), tempAddr,
  62. fSourceFilterAddr, 0);
  63. } else {
  64. fRTPSocket = new Groupsock(env(), tempAddr, 0, 255);
  65. }
  66. if (fRTPSocket == NULL) {
  67. env().setResultMsg(
  68. "MediaSession::initiate(): unable to create RTP and RTCP sockets");
  69. break;
  70. }
  71. // Get the client port number, and check whether it‘s even (for RTP):
  72. Port clientPort(0);
  73. if (!getSourcePort(env(), fRTPSocket->socketNum(),
  74. clientPort)) {
  75. break;
  76. }
  77. fClientPortNum = ntohs(clientPort.num());
  78. if ((fClientPortNum & 1) != 0) { // it‘s odd
  79. // Record this socket in our table, and keep trying:
  80. unsigned key = (unsigned) fClientPortNum;
  81. Groupsock* existing = (Groupsock*) socketHashTable->Add(
  82. (char const*) key, fRTPSocket);
  83. delete existing; // in case it wasn‘t NULL
  84. continue;
  85. }
  86. // Make sure we can use the next (i.e., odd) port number, for RTCP:
  87. portNumBits rtcpPortNum = fClientPortNum | 1;
  88. if (isSSM()) {
  89. fRTCPSocket = new Groupsock(env(), tempAddr,
  90. fSourceFilterAddr, rtcpPortNum);
  91. } else {
  92. fRTCPSocket = new Groupsock(env(), tempAddr, rtcpPortNum,
  93. 255);
  94. }
  95. if (fRTCPSocket != NULL && fRTCPSocket->socketNum() >= 0) {
  96. // Success! Use these two sockets.
  97. success = True;
  98. break;
  99. } else {
  100. // We couldn‘t create the RTCP socket (perhaps that port number‘s already in use elsewhere?).
  101. delete fRTCPSocket;
  102. // Record the first socket in our table, and keep trying:
  103. unsigned key = (unsigned) fClientPortNum;
  104. Groupsock* existing = (Groupsock*) socketHashTable->Add(
  105. (char const*) key, fRTPSocket);
  106. delete existing; // in case it wasn‘t NULL
  107. continue;
  108. }
  109. }
  110. // Clean up the socket hash table (and contents):
  111. Groupsock* oldGS;
  112. while ((oldGS = (Groupsock*) socketHashTable->RemoveNext()) != NULL) {
  113. delete oldGS;
  114. }
  115. delete socketHashTable;
  116. if (!success)
  117. break; // a fatal error occurred trying to create the RTP and RTCP sockets; we can‘t continue
  118. }
  119. // Try to use a big receive buffer for RTP - at least 0.1 second of
  120. // specified bandwidth and at least 50 KB
  121. unsigned rtpBufSize = fBandwidth * 25 / 2; // 1 kbps * 0.1 s = 12.5 bytes
  122. if (rtpBufSize < 50 * 1024)
  123. rtpBufSize = 50 * 1024;
  124. increaseReceiveBufferTo(env(), fRTPSocket->socketNum(), rtpBufSize);
  125. // ASSERT: fRTPSocket != NULL && fRTCPSocket != NULL
  126. if (isSSM()) {
  127. // Special case for RTCP SSM: Send RTCP packets back to the source via unicast:
  128. fRTCPSocket->changeDestinationParameters(fSourceFilterAddr, 0, ~0);
  129. }
  130. //创建RTPSource的地方
  131. // Create "fRTPSource" and "fReadSource":
  132. if (!createSourceObjects(useSpecialRTPoffset))
  133. break;
  134. if (fReadSource == NULL) {
  135. env().setResultMsg("Failed to create read source");
  136. break;
  137. }
  138. // Finally, create our RTCP instance. (It starts running automatically)
  139. if (fRTPSource != NULL) {
  140. // If bandwidth is specified, use it and add 5% for RTCP overhead.
  141. // Otherwise make a guess at 500 kbps.
  142. unsigned totSessionBandwidth =
  143. fBandwidth ? fBandwidth + fBandwidth / 20 : 500;
  144. fRTCPInstance = RTCPInstance::createNew(env(), fRTCPSocket,
  145. totSessionBandwidth, (unsigned char const*) fParent.CNAME(),
  146. NULL /* we‘re a client */, fRTPSource);
  147. if (fRTCPInstance == NULL) {
  148. env().setResultMsg("Failed to create RTCP instance");
  149. break;
  150. }
  151. }
  152. return True;
  153. } while (0);
  154. //失败时执行到这里
  155. delete fRTPSocket;
  156. fRTPSocket = NULL;
  157. delete fRTCPSocket;
  158. fRTCPSocket = NULL;
  159. Medium::close(fRTCPInstance);
  160. fRTCPInstance = NULL;
  161. Medium::close(fReadSource);
  162. fReadSource = fRTPSource = NULL;
  163. fClientPortNum = 0;
  164. return False;
  165. }

是的,在其中创建了RTP/RTCP socket并创建了RTPSource,创建RTPSource在函数createSourceObjects()中,看一下:

[cpp] view plaincopy

  1. Boolean MediaSubsession::createSourceObjects(int useSpecialRTPoffset)
  2. {
  3. do {
  4. // First, check "fProtocolName"
  5. if (strcmp(fProtocolName, "UDP") == 0) {
  6. // A UDP-packetized stream (*not* a RTP stream)
  7. fReadSource = BasicUDPSource::createNew(env(), fRTPSocket);
  8. fRTPSource = NULL; // Note!
  9. if (strcmp(fCodecName, "MP2T") == 0) { // MPEG-2 Transport Stream
  10. fReadSource = MPEG2TransportStreamFramer::createNew(env(),
  11. fReadSource);
  12. // this sets "durationInMicroseconds" correctly, based on the PCR values
  13. }
  14. } else {
  15. // Check "fCodecName" against the set of codecs that we support,
  16. // and create our RTP source accordingly
  17. // (Later make this code more efficient, as this set grows #####)
  18. // (Also, add more fmts that can be implemented by SimpleRTPSource#####)
  19. Boolean createSimpleRTPSource = False; // by default; can be changed below
  20. Boolean doNormalMBitRule = False; // default behavior if "createSimpleRTPSource" is True
  21. if (strcmp(fCodecName, "QCELP") == 0) { // QCELP audio
  22. fReadSource = QCELPAudioRTPSource::createNew(env(), fRTPSocket,
  23. fRTPSource, fRTPPayloadFormat, fRTPTimestampFrequency);
  24. // Note that fReadSource will differ from fRTPSource in this case
  25. } else if (strcmp(fCodecName, "AMR") == 0) { // AMR audio (narrowband)
  26. fReadSource = AMRAudioRTPSource::createNew(env(), fRTPSocket,
  27. fRTPSource, fRTPPayloadFormat, 0 /*isWideband*/,
  28. fNumChannels, fOctetalign, fInterleaving,
  29. fRobustsorting, fCRC);
  30. // Note that fReadSource will differ from fRTPSource in this case
  31. } else if (strcmp(fCodecName, "AMR-WB") == 0) { // AMR audio (wideband)
  32. fReadSource = AMRAudioRTPSource::createNew(env(), fRTPSocket,
  33. fRTPSource, fRTPPayloadFormat, 1 /*isWideband*/,
  34. fNumChannels, fOctetalign, fInterleaving,
  35. fRobustsorting, fCRC);
  36. // Note that fReadSource will differ from fRTPSource in this case
  37. } else if (strcmp(fCodecName, "MPA") == 0) { // MPEG-1 or 2 audio
  38. fReadSource = fRTPSource = MPEG1or2AudioRTPSource::createNew(
  39. env(), fRTPSocket, fRTPPayloadFormat,
  40. fRTPTimestampFrequency);
  41. } else if (strcmp(fCodecName, "MPA-ROBUST") == 0) { // robust MP3 audio
  42. fRTPSource = MP3ADURTPSource::createNew(env(), fRTPSocket,
  43. fRTPPayloadFormat, fRTPTimestampFrequency);
  44. if (fRTPSource == NULL)
  45. break;
  46. // Add a filter that deinterleaves the ADUs after depacketizing them:
  47. MP3ADUdeinterleaver* deinterleaver = MP3ADUdeinterleaver::createNew(
  48. env(), fRTPSource);
  49. if (deinterleaver == NULL)
  50. break;
  51. // Add another filter that converts these ADUs to MP3 frames:
  52. fReadSource = MP3FromADUSource::createNew(env(), deinterleaver);
  53. } else if (strcmp(fCodecName, "X-MP3-DRAFT-00") == 0) {
  54. // a non-standard variant of "MPA-ROBUST" used by RealNetworks
  55. // (one ‘ADU‘ized MP3 frame per packet; no headers)
  56. fRTPSource = SimpleRTPSource::createNew(env(), fRTPSocket,
  57. fRTPPayloadFormat, fRTPTimestampFrequency,
  58. "audio/MPA-ROBUST" /*hack*/);
  59. if (fRTPSource == NULL)
  60. break;
  61. // Add a filter that converts these ADUs to MP3 frames:
  62. fReadSource = MP3FromADUSource::createNew(env(), fRTPSource,
  63. False /*no ADU header*/);
  64. } else if (strcmp(fCodecName, "MP4A-LATM") == 0) { // MPEG-4 LATM audio
  65. fReadSource = fRTPSource = MPEG4LATMAudioRTPSource::createNew(
  66. env(), fRTPSocket, fRTPPayloadFormat,
  67. fRTPTimestampFrequency);
  68. } else if (strcmp(fCodecName, "AC3") == 0
  69. || strcmp(fCodecName, "EAC3") == 0) { // AC3 audio
  70. fReadSource = fRTPSource = AC3AudioRTPSource::createNew(env(),
  71. fRTPSocket, fRTPPayloadFormat, fRTPTimestampFrequency);
  72. } else if (strcmp(fCodecName, "MP4V-ES") == 0) { // MPEG-4 Elem Str vid
  73. fReadSource = fRTPSource = MPEG4ESVideoRTPSource::createNew(
  74. env(), fRTPSocket, fRTPPayloadFormat,
  75. fRTPTimestampFrequency);
  76. } else if (strcmp(fCodecName, "MPEG4-GENERIC") == 0) {
  77. fReadSource = fRTPSource = MPEG4GenericRTPSource::createNew(
  78. env(), fRTPSocket, fRTPPayloadFormat,
  79. fRTPTimestampFrequency, fMediumName, fMode, fSizelength,
  80. fIndexlength, fIndexdeltalength);
  81. } else if (strcmp(fCodecName, "MPV") == 0) { // MPEG-1 or 2 video
  82. fReadSource = fRTPSource = MPEG1or2VideoRTPSource::createNew(
  83. env(), fRTPSocket, fRTPPayloadFormat,
  84. fRTPTimestampFrequency);
  85. } else if (strcmp(fCodecName, "MP2T") == 0) { // MPEG-2 Transport Stream
  86. fRTPSource = SimpleRTPSource::createNew(env(), fRTPSocket,
  87. fRTPPayloadFormat, fRTPTimestampFrequency, "video/MP2T",
  88. 0, False);
  89. fReadSource = MPEG2TransportStreamFramer::createNew(env(),
  90. fRTPSource);
  91. // this sets "durationInMicroseconds" correctly, based on the PCR values
  92. } else if (strcmp(fCodecName, "H261") == 0) { // H.261
  93. fReadSource = fRTPSource = H261VideoRTPSource::createNew(env(),
  94. fRTPSocket, fRTPPayloadFormat, fRTPTimestampFrequency);
  95. } else if (strcmp(fCodecName, "H263-1998") == 0
  96. || strcmp(fCodecName, "H263-2000") == 0) { // H.263+
  97. fReadSource = fRTPSource = H263plusVideoRTPSource::createNew(
  98. env(), fRTPSocket, fRTPPayloadFormat,
  99. fRTPTimestampFrequency);
  100. } else if (strcmp(fCodecName, "H264") == 0) {
  101. fReadSource = fRTPSource = H264VideoRTPSource::createNew(env(),
  102. fRTPSocket, fRTPPayloadFormat, fRTPTimestampFrequency);
  103. } else if (strcmp(fCodecName, "DV") == 0) {
  104. fReadSource = fRTPSource = DVVideoRTPSource::createNew(env(),
  105. fRTPSocket, fRTPPayloadFormat, fRTPTimestampFrequency);
  106. } else if (strcmp(fCodecName, "JPEG") == 0) { // motion JPEG
  107. fReadSource = fRTPSource = JPEGVideoRTPSource::createNew(env(),
  108. fRTPSocket, fRTPPayloadFormat, fRTPTimestampFrequency,
  109. videoWidth(), videoHeight());
  110. } else if (strcmp(fCodecName, "X-QT") == 0
  111. || strcmp(fCodecName, "X-QUICKTIME") == 0) {
  112. // Generic QuickTime streams, as defined in
  113. // <http://developer.apple.com/quicktime/icefloe/dispatch026.html>
  114. char* mimeType = new char[strlen(mediumName())
  115. + strlen(codecName()) + 2];
  116. sprintf(mimeType, "%s/%s", mediumName(), codecName());
  117. fReadSource = fRTPSource = QuickTimeGenericRTPSource::createNew(
  118. env(), fRTPSocket, fRTPPayloadFormat,
  119. fRTPTimestampFrequency, mimeType);
  120. delete[] mimeType;
  121. } else if (strcmp(fCodecName, "PCMU") == 0 // PCM u-law audio
  122. || strcmp(fCodecName, "GSM") == 0 // GSM audio
  123. || strcmp(fCodecName, "DVI4") == 0 // DVI4 (IMA ADPCM) audio
  124. || strcmp(fCodecName, "PCMA") == 0 // PCM a-law audio
  125. || strcmp(fCodecName, "MP1S") == 0 // MPEG-1 System Stream
  126. || strcmp(fCodecName, "MP2P") == 0 // MPEG-2 Program Stream
  127. || strcmp(fCodecName, "L8") == 0 // 8-bit linear audio
  128. || strcmp(fCodecName, "L16") == 0 // 16-bit linear audio
  129. || strcmp(fCodecName, "L20") == 0 // 20-bit linear audio (RFC 3190)
  130. || strcmp(fCodecName, "L24") == 0 // 24-bit linear audio (RFC 3190)
  131. || strcmp(fCodecName, "G726-16") == 0 // G.726, 16 kbps
  132. || strcmp(fCodecName, "G726-24") == 0 // G.726, 24 kbps
  133. || strcmp(fCodecName, "G726-32") == 0 // G.726, 32 kbps
  134. || strcmp(fCodecName, "G726-40") == 0 // G.726, 40 kbps
  135. || strcmp(fCodecName, "SPEEX") == 0 // SPEEX audio
  136. || strcmp(fCodecName, "T140") == 0 // T.140 text (RFC 4103)
  137. || strcmp(fCodecName, "DAT12") == 0 // 12-bit nonlinear audio (RFC 3190)
  138. ) {
  139. createSimpleRTPSource = True;
  140. useSpecialRTPoffset = 0;
  141. } else if (useSpecialRTPoffset >= 0) {
  142. // We don‘t know this RTP payload format, but try to receive
  143. // it using a ‘SimpleRTPSource‘ with the specified header offset:
  144. createSimpleRTPSource = True;
  145. } else {
  146. env().setResultMsg(
  147. "RTP payload format unknown or not supported");
  148. break;
  149. }
  150. if (createSimpleRTPSource) {
  151. char* mimeType = new char[strlen(mediumName())
  152. + strlen(codecName()) + 2];
  153. sprintf(mimeType, "%s/%s", mediumName(), codecName());
  154. fReadSource = fRTPSource = SimpleRTPSource::createNew(env(),
  155. fRTPSocket, fRTPPayloadFormat, fRTPTimestampFrequency,
  156. mimeType, (unsigned) useSpecialRTPoffset,
  157. doNormalMBitRule);
  158. delete[] mimeType;
  159. }
  160. }
  161. return True;
  162. } while (0);
  163. return False; // an error occurred
  164. }

可以看到,这个函数里主要是跟据前面分析出的媒体和传输信息建立合适的Source。

socket建立了,Source也创建了,下一步应该是连接Sink,形成一个流。到此为止还未看到Sink的影子,应该是在下一步SETUP中建立,我们看到在continueAfterDESCRIBE()的最后调用了setupStreams(),那么就来探索一下setupStreams():

[cpp] view plaincopy

  1. void setupStreams()
  2. {
  3. static MediaSubsessionIterator* setupIter = NULL;
  4. if (setupIter == NULL)
  5. setupIter = new MediaSubsessionIterator(*session);
  6. //每次调用此函数只为一个Subsession发出SETUP请求。
  7. while ((subsession = setupIter->next()) != NULL) {
  8. // We have another subsession left to set up:
  9. if (subsession->clientPortNum() == 0)
  10. continue; // port # was not set
  11. //为一个Subsession发送SETUP请求。请求处理完成时调用continueAfterSETUP(),
  12. //continueAfterSETUP()又调用了setupStreams(),在此函数中为下一个SubSession发送SETUP请求。

[cpp] view plaincopy

  1. <span style="white-space:pre">      </span>//直到处理完所有的SubSession
  2. setupSubsession(subsession, streamUsingTCP, continueAfterSETUP);
  3. return;
  4. }
  5. //执行到这里时,已循环完所有的SubSession了
  6. // We‘re done setting up subsessions.
  7. delete setupIter;
  8. if (!madeProgress)
  9. shutdown();
  10. //创建输出文件,看来是在这里创建Sink了。创建sink后,就开始播放它。这个播放应该只是把socket的handler加入到
  11. //计划任务中,而没有数据的接收或发送。只有等到发出PLAY请求后才有数据的收发。
  12. // Create output files:
  13. if (createReceivers) {
  14. if (outputQuickTimeFile) {
  15. // Create a "QuickTimeFileSink", to write to ‘stdout‘:
  16. qtOut = QuickTimeFileSink::createNew(*env, *session, "stdout",
  17. fileSinkBufferSize, movieWidth, movieHeight, movieFPS,
  18. packetLossCompensate, syncStreams, generateHintTracks,
  19. generateMP4Format);
  20. if (qtOut == NULL) {
  21. *env << "Failed to create QuickTime file sink for stdout: "
  22. << env->getResultMsg();
  23. shutdown();
  24. }
  25. qtOut->startPlaying(sessionAfterPlaying, NULL);
  26. } else if (outputAVIFile) {
  27. // Create an "AVIFileSink", to write to ‘stdout‘:
  28. aviOut = AVIFileSink::createNew(*env, *session, "stdout",
  29. fileSinkBufferSize, movieWidth, movieHeight, movieFPS,
  30. packetLossCompensate);
  31. if (aviOut == NULL) {
  32. *env << "Failed to create AVI file sink for stdout: "
  33. << env->getResultMsg();
  34. shutdown();
  35. }
  36. aviOut->startPlaying(sessionAfterPlaying, NULL);
  37. } else {
  38. // Create and start "FileSink"s for each subsession:
  39. madeProgress = False;
  40. MediaSubsessionIterator iter(*session);
  41. while ((subsession = iter.next()) != NULL) {
  42. if (subsession->readSource() == NULL)
  43. continue; // was not initiated
  44. // Create an output file for each desired stream:
  45. char outFileName[1000];
  46. if (singleMedium == NULL) {
  47. // Output file name is
  48. //     "<filename-prefix><medium_name>-<codec_name>-<counter>"
  49. static unsigned streamCounter = 0;
  50. snprintf(outFileName, sizeof outFileName, "%s%s-%s-%d",
  51. fileNamePrefix, subsession->mediumName(),
  52. subsession->codecName(), ++streamCounter);
  53. } else {
  54. sprintf(outFileName, "stdout");
  55. }
  56. FileSink* fileSink;
  57. if (strcmp(subsession->mediumName(), "audio") == 0
  58. && (strcmp(subsession->codecName(), "AMR") == 0
  59. || strcmp(subsession->codecName(), "AMR-WB")
  60. == 0)) {
  61. // For AMR audio streams, we use a special sink that inserts AMR frame hdrs:
  62. fileSink = AMRAudioFileSink::createNew(*env, outFileName,
  63. fileSinkBufferSize, oneFilePerFrame);
  64. } else if (strcmp(subsession->mediumName(), "video") == 0
  65. && (strcmp(subsession->codecName(), "H264") == 0)) {
  66. // For H.264 video stream, we use a special sink that insert start_codes:
  67. fileSink = H264VideoFileSink::createNew(*env, outFileName,
  68. subsession->fmtp_spropparametersets(),
  69. fileSinkBufferSize, oneFilePerFrame);
  70. } else {
  71. // Normal case:
  72. fileSink = FileSink::createNew(*env, outFileName,
  73. fileSinkBufferSize, oneFilePerFrame);
  74. }
  75. subsession->sink = fileSink;
  76. if (subsession->sink == NULL) {
  77. *env << "Failed to create FileSink for \"" << outFileName
  78. << "\": " << env->getResultMsg() << "\n";
  79. } else {
  80. if (singleMedium == NULL) {
  81. *env << "Created output file: \"" << outFileName
  82. << "\"\n";
  83. } else {
  84. *env << "Outputting data from the \""
  85. << subsession->mediumName() << "/"
  86. << subsession->codecName()
  87. << "\" subsession to ‘stdout‘\n";
  88. }
  89. if (strcmp(subsession->mediumName(), "video") == 0
  90. && strcmp(subsession->codecName(), "MP4V-ES") == 0 &&
  91. subsession->fmtp_config() != NULL) {
  92. // For MPEG-4 video RTP streams, the ‘config‘ information
  93. // from the SDP description contains useful VOL etc. headers.
  94. // Insert this data at the front of the output file:
  95. unsigned                    configLen;
  96. unsigned char* configData
  97. = parseGeneralConfigStr(subsession->fmtp_config(), configLen);
  98. struct timeval timeNow;
  99. gettimeofday(&timeNow, NULL);
  100. fileSink->addData(configData, configLen, timeNow);
  101. delete[] configData;
  102. }
  103. //开始传输
  104. subsession->sink->startPlaying(*(subsession->readSource()),
  105. subsessionAfterPlaying, subsession);
  106. // Also set a handler to be called if a RTCP "BYE" arrives
  107. // for this subsession:
  108. if (subsession->rtcpInstance() != NULL) {
  109. subsession->rtcpInstance()->setByeHandler(
  110. subsessionByeHandler, subsession);
  111. }
  112. madeProgress = True;
  113. }
  114. }
  115. if (!madeProgress)
  116. shutdown();
  117. }
  118. }
  119. // Finally, start playing each subsession, to start the data flow:
  120. if (duration == 0) {
  121. if (scale > 0)
  122. duration = session->playEndTime() - initialSeekTime; // use SDP end time
  123. else if (scale < 0)
  124. duration = initialSeekTime;
  125. }
  126. if (duration < 0)
  127. duration = 0.0;
  128. endTime = initialSeekTime;
  129. if (scale > 0) {
  130. if (duration <= 0)
  131. endTime = -1.0f;
  132. else
  133. endTime = initialSeekTime + duration;
  134. } else {
  135. endTime = initialSeekTime - duration;
  136. if (endTime < 0)
  137. endTime = 0.0f;
  138. }
  139. //发送PLAY请求,之后才能从Server端接收数据
  140. startPlayingSession(session, initialSeekTime, endTime, scale,
  141. continueAfterPLAY);
  142. }

仔细看看注释,应很容易了解此函数。

转自:http://blog.csdn.net/niu_gao/article/details/6927461

时间: 2024-10-14 13:50:06

(转)Live555中RTSPClient分析的相关文章

Live555源码分析[2]:RTSPServer中的用户认证

http://blog.csdn.net/njzhujinhua @20140601 说到鉴权,这是我多年来工作中的一部分,但这里rtsp中的认证简单多了,只是最基本的digest鉴权的策略. 在Live555的实现中, 用户信息由如下类维护,其提供增删查的接口.realm默认值为"LIVE555 Streaming Media" class UserAuthenticationDatabase { public: UserAuthenticationDatabase(char con

Live555中RTP包的打包与发送过程分析

这里主要分析一下,live555中关于RTP打包发送的部分.在处理完PLAY命令之后,就开始发送RTP数据包了(其实在发送PLAY命令的response包之前,就会发送一个RTP包,这里传输就已经开始了) 先介绍下主要的流程:RTP包的发送是从MediaSink::startPlaying函数调用开始的,在StartPlaying函数的最后会调用函数continuePlaying. continuePlaying函数是定义在MediaSink类中的纯虚函数,需要到特定媒体的sink子类中实现,对

如何看TCO在虚拟化解决方案中的分析与对比

如何看TCO在虚拟化解决方案中的分析与对比 所谓TCO (Total cost of ownership) 即总体拥有成本,是一种经常采用的技术评价标准,它的目标是分析和对比在一定时间范围内所拥有的包括首次购置成本TCA (Total cost of acquisition) 和每年运维成本在内的总体成本.在某些情况下,这一总体成本是一个为获得可比较的现行开支而对3到5年生命周期范围内的成本进行平均的值. TCO的对比应该明确一个前提,就是IT系统的实现功能.性能.可靠性等方面基本相同,或者说满

使用crash提取vmcore中预分析信息

一.介绍 在linux系统内核发生崩溃或者服务器hang住时,Kdump(kernel crash dump:内核崩溃转储设备)生成vmcore文件,通过分析vmcore信息判断原因,而 crash是一个被广泛应用的内核奔溃转储文件分析工具,前提系统必须安装crash工具和内核调试工具kernel-debuginfo. 二.工具的安装与调试 1.安装包的版本,要与linux内核一致,查看linux内核版本: #uname -a 2.安装.配置.启动kdump:       安装kdump:  

or1200中IMMU分析(续)

以下内容摘自<步步惊芯--软核处理器内部设计分析>一书 2 IMMU中的特殊寄存器 OR1200处理器中的IMMU包含第2组特殊寄存器,如表10.1所示. ITLBW0MRx是指令TLB匹配寄存器,其格式如表10.2所示. 表10.2是OpenRISC 1000规范中的定义,实际在OR1200处理器中只实现了其中一部分字段,包括VPN(Virtual Page Number)的一部分.V(Valid标志位).ITLBW0MRx对应图10.7中MR_RAM的表项,每一个表项对应一个ITLBW0M

or1200中IMMU分析

以下内容摘自<步步惊芯--软核处理器内部设计分析>一书 1 IMMU结构 OR1200中实现IMMU的文件有or1200_immu_top.v.or1200_immu_tlb.v.or1200_spram.v,其中使用or1200_immu_top.v实现了IMMU模块,使用or1200_immu_tlb.v实现了ITLB模块,or1200_spram.v是一个单口RAM,使用其实现了ITLB的表项.如图10.5所示.本小节将分别介绍IMMU模块与其余模块的连接关系.ITLB结构. 1.1 I

Live555 中的客户端动态库.so的调用方式之一 程序中调用

1.  打开动态链接库:    #include <dlfcn.h>    void *dlopen(const char *filename, int flag);    该函数返回操作句柄,如:    void *pHandle = dlopen(strSoFilePath, RTLD_LAZY); 2.  取动态对象地址:    #include <dlfcn.h>    void *dlsym(void *pHandle, char *symbol);    dlsym根据

or1200中IMMU分析(再续)

以下内容摘自<步步惊芯--软核处理器内部设计分析>一书 ITLB代码分析 ITLB是IMMU中的主要模块,其实现也相对独立.简单.本节对ITLB的代码进行分析.ITLB的输入输出接口如图10.10所示,图中左边是输入接口,右边是输出接口. 因为在ITLB中实现了第2组特殊寄存器,所以有spr_cs.spr_write.spr_addr.spr_dat_i.spr_dat_o等接口,这些接口的含义在分析特殊寄存器类指令的时候已经学习过,应该是非常熟悉的.剩下的输入输出接口含义如下: tlb_en

大开测试:性能-如何在Analysis图表中添加分析注释(连载26)

7.26  如何在Analysis图表中添加分析注释 1.问题提出 Analysis提供了十分丰富的图表,我们可以借助这些图表分析系统的性能,为了使图表更加直观,方便专业及其非专业人事的阅读,提供分析注释是十分必要的,那么LoadRunner的Analysis提供这种功能了吗? 2.问题解答 LoadRunner提供了丰富的图表,通过这些图表可以供性能分析人员分析系统瓶颈,为了使自己和他人方便阅读分析结果,LoadRunner提供了在图表上添加注释信息的功能,下面以"Throughput - R