=====================================================

RTMPdump(libRTMP) 源代码分析系列文章:

RTMPdump 源代码分析 1: main()函数

RTMPDump (libRTMP) 源代码分析2:解析RTMP地址——RTMP_ParseURL()

RTMPdump (libRTMP) 源代码分析3: AMF编码

RTMPdump (libRTMP) 源代码分析4: 连接第一步——握手 (HandShake)

RTMPdump (libRTMP) 源代码分析5: 建立一个流媒体连接  (NetConnection部分)

RTMPdump (libRTMP) 源代码分析6: 建立一个流媒体连接  (NetStream部分 1)

RTMPdump (libRTMP) 源代码分析7: 建立一个流媒体连接  (NetStream部分 2)

RTMPdump (libRTMP) 源代码分析8: 发送消息 (Message)

RTMPdump (libRTMP) 源代码分析9: 接收消息 (Message)  (接收视音频数据)

RTMPdump (libRTMP) 源代码分析10: 处理各种消息 (Message)

=====================================================

函数调用结构图

RTMPDump (libRTMP)的整体的函数调用结构图如下图所示。

单击查看大图

详细分析

前文已经分析了 RTMPdump中建立一个NetConnection的过程:RTMPdump 源代码分析 5: 建立一个流媒体连接 (NetConnection部分)

多余的话不多说,下面先来看看RTMP_ConnectStream(),该函数主要用于在NetConnection基础上建立一个NetStream。

RTMP_ConnectStream()

[cpp] view plaincopy
  1. //创建流
  2. int
  3. RTMP_ConnectStream(RTMP *r, int seekTime)
  4. {
  5. RTMPPacket packet = { 0 };
  6. /* seekTime was already set by SetupStream / SetupURL.
  7. * This is only needed by ReconnectStream.
  8. */
  9. if (seekTime > 0)
  10. r->Link.seekTime = seekTime;
  11. r->m_mediaChannel = 0;
  12. while (!r->m_bPlaying && RTMP_IsConnected(r) && RTMP_ReadPacket(r, &packet))
  13. {
  14. if (RTMPPacket_IsReady(&packet))
  15. {
  16. if (!packet.m_nBodySize)
  17. continue;
  18. if ((packet.m_packetType == RTMP_PACKET_TYPE_AUDIO) ||
  19. (packet.m_packetType == RTMP_PACKET_TYPE_VIDEO) ||
  20. (packet.m_packetType == RTMP_PACKET_TYPE_INFO))
  21. {
  22. RTMP_Log(RTMP_LOGWARNING, "Received FLV packet before play()! Ignoring.");
  23. RTMPPacket_Free(&packet);
  24. continue;
  25. }
  26. //处理Packet!
  27. //----------------
  28. r->dlg->AppendCInfo("建立网络流:处理收到的数据。开始处理收到的数据");
  29. //-----------------------------
  30. RTMP_ClientPacket(r, &packet);
  31. //----------------
  32. r->dlg->AppendCInfo("建立网络流:处理收到的数据。处理完毕,清除数据。");
  33. //-----------------------------
  34. RTMPPacket_Free(&packet);
  35. }
  36. }
  37. return r->m_bPlaying;
  38. }

乍一看,这个函数的代码量好像挺少的,实际上不然,其复杂度还是挺高的。我觉得比RTMP_Connect()要复杂不少。

其关键就在于这个While()循环。首先,循环的三个条件都满足,就能进行循环。只有出错或者建立网络流(NetStream)的步骤完成后,才能跳出循环。

在这个函数中有两个函数尤为重要:

RTMP_ReadPacket()

RTMP_ClientPacket()

第一个函数的作用是读取通过Socket接收下来的消息(Message)包,但是不做任何处理。第二个函数则是处理消息(Message),并做出响应。这两个函数结合,就可以完成接收消息然后响应消息的步骤。

下面来开一下RTMP_ReadPacket():

[cpp] view plaincopy
  1. //读取收下来的Chunk
  2. int
  3. RTMP_ReadPacket(RTMP *r, RTMPPacket *packet)
  4. {
  5. //packet 存读取完后的的数据
  6. //Chunk Header最大值18
  7. uint8_t hbuf[RTMP_MAX_HEADER_SIZE] = { 0 };
  8. //header 指向的是从Socket中收下来的数据
  9. char *header = (char *)hbuf;
  10. int nSize, hSize, nToRead, nChunk;
  11. int didAlloc = FALSE;
  12. RTMP_Log(RTMP_LOGDEBUG2, "%s: fd=%d", __FUNCTION__, r->m_sb.sb_socket);
  13. //收下来的数据存入hbuf
  14. if (ReadN(r, (char *)hbuf, 1) == 0)
  15. {
  16. RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header", __FUNCTION__);
  17. return FALSE;
  18. }
  19. //块类型fmt
  20. packet->m_headerType = (hbuf[0] & 0xc0) >> 6;
  21. //块流ID(2-63)
  22. packet->m_nChannel = (hbuf[0] & 0x3f);
  23. header++;
  24. //块流ID第1字节为0时,块流ID占2个字节
  25. if (packet->m_nChannel == 0)
  26. {
  27. if (ReadN(r, (char *)&hbuf[1], 1) != 1)
  28. {
  29. RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header 2nd byte",
  30. __FUNCTION__);
  31. return FALSE;
  32. }
  33. //计算块流ID(64-319)
  34. packet->m_nChannel = hbuf[1];
  35. packet->m_nChannel += 64;
  36. header++;
  37. }
  38. //块流ID第1字节为0时,块流ID占3个字节
  39. else if (packet->m_nChannel == 1)
  40. {
  41. int tmp;
  42. if (ReadN(r, (char *)&hbuf[1], 2) != 2)
  43. {
  44. RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header 3nd byte",
  45. __FUNCTION__);
  46. return FALSE;
  47. }
  48. tmp = (hbuf[2] << 8) + hbuf[1];
  49. //计算块流ID(64-65599)
  50. packet->m_nChannel = tmp + 64;
  51. RTMP_Log(RTMP_LOGDEBUG, "%s, m_nChannel: %0x", __FUNCTION__, packet->m_nChannel);
  52. header += 2;
  53. }
  54. //ChunkHeader的大小(4种)
  55. nSize = packetSize[packet->m_headerType];
  56. if (nSize == RTMP_LARGE_HEADER_SIZE)  /* if we get a full header the timestamp is absolute */
  57. packet->m_hasAbsTimestamp = TRUE;    //11字节的完整ChunkMsgHeader的TimeStamp是绝对值
  58. else if (nSize < RTMP_LARGE_HEADER_SIZE)
  59. {               /* using values from the last message of this channel */
  60. if (r->m_vecChannelsIn[packet->m_nChannel])
  61. memcpy(packet, r->m_vecChannelsIn[packet->m_nChannel],
  62. sizeof(RTMPPacket));
  63. }
  64. nSize--;
  65. if (nSize > 0 && ReadN(r, header, nSize) != nSize)
  66. {
  67. RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header. type: %x",
  68. __FUNCTION__, (unsigned int)hbuf[0]);
  69. return FALSE;
  70. }
  71. hSize = nSize + (header - (char *)hbuf);
  72. if (nSize >= 3)
  73. {
  74. //TimeStamp(注意 BigEndian to SmallEndian)(11,7,3字节首部都有)
  75. packet->m_nTimeStamp = AMF_DecodeInt24(header);
  76. /*RTMP_Log(RTMP_LOGDEBUG, "%s, reading RTMP packet chunk on channel %x, headersz %i, timestamp %i, abs timestamp %i", __FUNCTION__, packet.m_nChannel, nSize, packet.m_nTimeStamp, packet.m_hasAbsTimestamp); */
  77. //消息长度(11,7字节首部都有)
  78. if (nSize >= 6)
  79. {
  80. packet->m_nBodySize = AMF_DecodeInt24(header + 3);
  81. packet->m_nBytesRead = 0;
  82. RTMPPacket_Free(packet);
  83. //(11,7字节首部都有)
  84. if (nSize > 6)
  85. {
  86. //Msg type ID
  87. packet->m_packetType = header[6];
  88. //Msg Stream ID
  89. if (nSize == 11)
  90. packet->m_nInfoField2 = DecodeInt32LE(header + 7);
  91. }
  92. }
  93. //Extend TimeStamp
  94. if (packet->m_nTimeStamp == 0xffffff)
  95. {
  96. if (ReadN(r, header + nSize, 4) != 4)
  97. {
  98. RTMP_Log(RTMP_LOGERROR, "%s, failed to read extended timestamp",
  99. __FUNCTION__);
  100. return FALSE;
  101. }
  102. packet->m_nTimeStamp = AMF_DecodeInt32(header + nSize);
  103. hSize += 4;
  104. }
  105. }
  106. RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)hbuf, hSize);
  107. if (packet->m_nBodySize > 0 && packet->m_body == NULL)
  108. {
  109. if (!RTMPPacket_Alloc(packet, packet->m_nBodySize))
  110. {
  111. RTMP_Log(RTMP_LOGDEBUG, "%s, failed to allocate packet", __FUNCTION__);
  112. return FALSE;
  113. }
  114. didAlloc = TRUE;
  115. packet->m_headerType = (hbuf[0] & 0xc0) >> 6;
  116. }
  117. nToRead = packet->m_nBodySize - packet->m_nBytesRead;
  118. nChunk = r->m_inChunkSize;
  119. if (nToRead < nChunk)
  120. nChunk = nToRead;
  121. /* Does the caller want the raw chunk? */
  122. if (packet->m_chunk)
  123. {
  124. packet->m_chunk->c_headerSize = hSize;
  125. memcpy(packet->m_chunk->c_header, hbuf, hSize);
  126. packet->m_chunk->c_chunk = packet->m_body + packet->m_nBytesRead;
  127. packet->m_chunk->c_chunkSize = nChunk;
  128. }
  129. if (ReadN(r, packet->m_body + packet->m_nBytesRead, nChunk) != nChunk)
  130. {
  131. RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet body. len: %lu",
  132. __FUNCTION__, packet->m_nBodySize);
  133. return FALSE;
  134. }
  135. RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)packet->m_body + packet->m_nBytesRead, nChunk);
  136. packet->m_nBytesRead += nChunk;
  137. /* keep the packet as ref for other packets on this channel */
  138. if (!r->m_vecChannelsIn[packet->m_nChannel])
  139. r->m_vecChannelsIn[packet->m_nChannel] = (RTMPPacket *) malloc(sizeof(RTMPPacket));
  140. memcpy(r->m_vecChannelsIn[packet->m_nChannel], packet, sizeof(RTMPPacket));
  141. //读取完毕
  142. if (RTMPPacket_IsReady(packet))
  143. {
  144. /* make packet's timestamp absolute */
  145. if (!packet->m_hasAbsTimestamp)
  146. packet->m_nTimeStamp += r->m_channelTimestamp[packet->m_nChannel]; /* timestamps seem to be always relative!! */
  147. r->m_channelTimestamp[packet->m_nChannel] = packet->m_nTimeStamp;
  148. /* reset the data from the stored packet. we keep the header since we may use it later if a new packet for this channel */
  149. /* arrives and requests to re-use some info (small packet header) */
  150. r->m_vecChannelsIn[packet->m_nChannel]->m_body = NULL;
  151. r->m_vecChannelsIn[packet->m_nChannel]->m_nBytesRead = 0;
  152. r->m_vecChannelsIn[packet->m_nChannel]->m_hasAbsTimestamp = FALSE;   /* can only be false if we reuse header */
  153. }
  154. else
  155. {
  156. packet->m_body = NULL; /* so it won't be erased on free */
  157. }
  158. return TRUE;
  159. }

在这里要注意的是,接收下来的实际上是块(Chunk)而不是消息(Message),因为消息(Message)在网络上传播的时候,实际上要分割成块(Chunk)。

这里解析的就是块(Chunk)

可参考:RTMP规范简单分析

具体的解析代码我就不多说了,直接参考RTMP协议规范就可以了,一个字节一个字节的解析就OK了。

rtmpdump源代码(Linux):http://download.csdn.net/detail/leixiaohua1020/6376561

rtmpdump源代码(VC 2005 工程):http://download.csdn.net/detail/leixiaohua1020/6563163

RTMPdump(libRTMP) 源代码分析 6: 建立一个流媒体连接 (NetStream部分 1)相关推荐

  1. RTMPdump(libRTMP) 源代码分析 7: 建立一个流媒体连接 (NetStream部分 2)

    ===================================================== RTMPdump(libRTMP) 源代码分析系列文章: RTMPdump 源代码分析 1: ...

  2. RTMPdump(libRTMP) 源代码分析 5: 建立一个流媒体连接 (NetConnection部分)

    ===================================================== RTMPdump(libRTMP) 源代码分析系列文章: RTMPdump 源代码分析 1: ...

  3. 主机甲和主机乙之间已建立一个TCP连接,TCP最大段长为1000B。若主机甲的当前拥塞窗口为4000B,在主机甲向主机乙连续发送两个最大段后,成功收到主机乙发送的第一个段的确认段,确认段中通告的接收窗

    主机甲和主机乙之间已建立一个TCP连接,TCP最大段长为1000B.若主机甲的当前拥塞窗口为4000B,在主机甲向主机乙连续发送两个最大段后,成功收到主机乙发送的第一个段的确认段,确认段中通告的接收窗 ...

  4. Android通过WebSocket建立一个长连接(带心跳检测)从服务器端接收消息

    最近公司要做一款内部使用的工具类app,方便销售部门打电话(其实就是在后台有好多用户数据,之前销售部门同事拨打电话,需要自己从销售后台查看用户手机号等信息,然后自己拿自己手机拨号,然后打出去.现在想实 ...

  5. 客户端C和服务器S之间建立一个TCP连接,该连接总是以1KB的最大段长发送TCP段,客户端C有足够的数据要发送。当拥塞窗口为16KB的时候发生超时,如果接下来的4个RTT往返时间内的TCP段的传输是成

    客户端C和服务器S之间建立一个TCP连接,该连接总是以1KB的最大段长发送TCP段,客户端C有足够的数据要发送.当拥塞窗口为16KB的时候发生超时,如果接下来的4个RTT往返时间内的TCP段的传输是成 ...

  6. 建立一个GTalk连接和启动一个IM会话

    一个GTalk连接代表着设备和GTalk服务器之间的管道.一个IM会话是消息通道,用于处理所有的即时消息的交通:在一个会话中的所有的即时消息都在这个管道中流动. 你可以创建一些不同的连接和很多的IM会 ...

  7. LibRTMP源代码分析2:解释RTMP地址

    转载自:http://nkwavelet.blog.163.com/blog/static/227756038201412022720924/ 获取RTMP流媒体数据很重要的前提是RTMP的URL的解 ...

  8. telnetd源代码分析之输入一个字符的四个阶段

    下面的debug信息来自与/tmp/telenet.debug文件的一部分. 是客户端按下字母e后发生的四个阶段.这篇主要分析第一阶段和第二阶段. 也就是telrcv函数的主要的功能. td: net ...

  9. 主机甲和主机乙间已建立一个TCP连接,主机甲向主机乙发送了两个连续的TCP段,分别包含300字节和500字节的有效载荷,第一个段的序列号为200,主机乙正确接收到两个段后,发送给主机甲的确认序列号是?

    确认序列号=原始序列号+TCP段的长度,所以第一次的确认序列号为200+300=500,第二次确认序列号为500+500=1000

最新文章

  1. 使用 CocoaPods 给微信集成 SDK 打印收发消息
  2. c mysql安装教程 pdf_MySQL下载安装、配置与使用教程详细版(win7x64)
  3. 【HDU - 3790】最短路径问题(DIjkstra算法 双权值)
  4. Socket编程实践(5) --TCP粘包问题与解决
  5. AndroidStudio_AndroidStudio debug的时候断点打不上_No executable code found at line---Android原生开发工作笔记236
  6. 一个悄然成为世界最流行的操作系统
  7. rbac 一个用户对应多个账号_SaaS产品用户权限管理-RBAC
  8. [高通MSM8909][Android7.1]电信卡信号优化
  9. Safari浏览器兼容性问题处理
  10. Python基础 - 20210425 - 基础(命名规范,注解,基础语法)
  11. 小程序实现书籍翻页效果
  12. Mysql中时间格式转换
  13. 美国通胀“爆表”要加息100点?
  14. 【从0到1搭建LoRa物联网】14、低成本单通道网关(二)
  15. 【渝粤题库】陕西师范大学292331 证券投资学Ⅰ作业(高起专)
  16. bootstrap table 动态列数据加载(一)
  17. 用ubuntu下载电影:磁力链接,torrent,迅雷链接
  18. Elasticsearch冷热集群搭建
  19. Unity之富文本打字机效果
  20. oracle index 初学

热门文章

  1. 使用CoreText实现图文混排
  2. Unknown opcode
  3. linux系统服务详解 用于Linux系统服务优化
  4. 3COM小型企业有线局域网方案(三、四、五)
  5. python-爬虫(1)
  6. 如何使用git for windows上传文件到git仓库
  7. CodeForces - 1437G Death DBMS(AC自动机fail树上树链剖分建线段树/暴跳fail)
  8. HYSBZ - 1503 郁闷的出纳员(Splay)
  9. java 微信 菜单_java微信开发API第四步 微信自定义个性化菜单实现
  10. 启动sqlserver_微软的 SQL Server 你学会了吗?