接下来我们分析RTMP_SendPacket函数。我们先了解一下rtmp的消息格式chunk。

RTMP的head组成

RTMP的head在协议中的表现形式是chunk head,前面已经说到一个Message + head可以分成一个和多个chunk,为了区分这些chunk,肯定是需要一个chunk head的,具体的实现就把Message  head的信息和chunk head的信息合并在一起以chunk head的形式表现。 
        一个完整的chunk的组成如下图所示

Chunk basic header: 
该字段包含chunk的stream ID和 type 。chunk的Type决定了消息头的编码方式。该字段的长度完全依赖于stream ID,该字段是一个可变长的字段。


Chunk Msg Header:0, 3 ,7, 11 
该字段包含了将要发送的消息的信息(或者是一部分,一个消息拆成多个chunk的情况下是一部分)该字段的长度由chunk basic header中的type决定。

Extend Timestamp: 0 ,4 bytes
该字段发送的时候必须是正常的时间戳设置成0xffffff时,当正常时间戳不为0xffffff时,该字段不发送。当时间戳比0xffffff小该字段不发送,当时间戳比0xffffff大时该字段必须发送,且正常时间戳设置成0xffffff。

Chunk Data
实际数据(Payload),可以是信令,也可以是媒体数据。

总结如下图所示:

6.1.2 块消息头
有四种格式的块消息ID,供块流基本头中的fmt 字段选择。一个实现应该使用最紧致的方式来表示块消息头。

6.1.2.1 类型0
0 类型的块长度为11 字节。在一个块流的开始和时间戳返回的时候必须有这种块。

时间戳:3 字节
对于0 类型的块。消息的绝对时间戳在这里发送。如果时间戳大于或等于16777215(16 进制0x00ffffff),该值必须为16777215,并且扩展时间戳必须出现。否则该值就是整个的时间戳。

6.1.2.2. 类型1
类型1 的块占7 个字节长。消息流ID 不包含在本块中。块的消息流ID 与先前的块相同。具有可变大小消息的流,在第一个消息之后的每个消息的第一个块应该使用这个格式。

6.1.2.3. 类型2
类型2 的块占3 个字节。既不包含流ID 也不包含消息长度。本块使用的流ID 和消息长度与先前的块相同。具有固定大小消息的流,在第一个消息之后的每个消息的第一个块应该使用这个格式。


6.1.2.4 类型3
类型3 的块没有头。流ID,消息长度,时间戳都不出现。这种类型的块使用与先前块相同的数据。当一个消息被分成多个块,除了第一块以外,所有的块都应使用这种类型。示例可参考6.2.2 节中的例2 。由相同大小,流ID,和时间间隔的流在类型2 的块之后应使用这种块。示例可参考6.2.1 节中的例1 。如果第一个消息和第二个消息的时间增量与第一个消息的时间戳相同,那么0类型的块之后必须是3 类型的块而,不需要类型2 的块来注册时间增量。如果类型3 的块在类型0 的块之后,那么类型3 的时间戳增量与0 类型的块的时间戳相同。

时间戳增量:3 字节
对于类型1 的块和类型2 的块,本字段表示先前块的时间戳与当前块的时间戳的差值。如果增量大于等于1677215(16 进制0x00ffffff),这个值必须是16777215 ,并且扩展时间戳必须出现。否则这个值就是整个的增量。

消息长度:3 字节
对于类型0 或类型1 的块本字段表示消息的长度。注意,这个值通常与负载长度是不相同的。The chunk payload length is the maximum chunk size for all but the last chunk, and the remainder (which may be the entire length, for small messages) for the last chunk.

消息类型ID:1 字节
对于0 类型和1 类型的块,本字段发送消息类型。

消息流ID:4 字节
对于0 类型的块,本字段存储消息流ID。通常,在一个块流中的消息来自于同一个消息流。虽然,由于不同的消息可能复用到一个块流中而使头压缩无法有效实施。但是,如果一个消息流关闭而另一个消息流才打开,那么通过发送一个新的0 类型的块重复使用一个存在的块流也不是不可以。

6.1.3. 扩展时间戳
只有当块消息头中的普通时间戳设置为0x00ffffff 时,本字段才被传送。如果普通时间戳的值小于0x00ffffff,那么本字段一定不能出现。如果时间戳字段不出现本字段也一定不能出现。类型3 的块一定不能含有本字段。本字段在块消息头之后,块时间之前。

代码分析如下:

[cpp] view plaincopy print?
  1. int RTMP_SendPacket(RTMP *r, RTMPPacket *packet, int queue)
  2. {
  3. const RTMPPacket *prevPacket = r->m_vecChannelsOut[packet->m_nChannel];
  4. uint32_t last = 0;
  5. int nSize;
  6. int hSize, cSize;
  7. char *header, *hptr, *hend, hbuf[RTMP_MAX_HEADER_SIZE], c;
  8. uint32_t t;
  9. char *buffer, *tbuf = NULL, *toff = NULL;
  10. int nChunkSize;
  11. int tlen;
  12. // 前一个packet存在且不是完整的ChunkMsgHeader,因此有可能需要调整块消息头的类型
  13. //fmt字节
  14. /*case 0:chunk msg header 长度为11
  15. * case 1:chunk msg header 长度为7
  16. * case 2:chunk msg header 长度为3
  17. * case 3:chunk msg header 长度为0
  18. */
  19. if (prevPacket && packet->m_headerType != RTMP_PACKET_SIZE_LARGE)
  20. {
  21. /* compress a bit by using the prev packet's attributes */
  22. // 获取ChunkMsgHeader类型,前一个Chunk与当前Chunk比较
  23. // 如果前后两个块的大小、包类型及块头类型都相同,则将块头类型fmt设为2,
  24. // 即可省略消息长度、消息类型id、消息流id
  25. // 可以参考官方协议:流的分块 --- 6.1.2.3节
  26. if (prevPacket->m_nBodySize == packet->m_nBodySize&& prevPacket->m_packetType == packet->m_packetType
  27. && packet->m_headerType == RTMP_PACKET_SIZE_MEDIUM)
  28. packet->m_headerType = RTMP_PACKET_SIZE_SMALL;
  29. // 前后两个块的时间戳相同,且块头类型fmt为2,则相应的时间戳也可省略,因此将块头类型置为3
  30. // 可以参考官方协议:流的分块 --- 6.1.2.4节
  31. if (prevPacket->m_nTimeStamp == packet->m_nTimeStamp && packet->m_headerType == RTMP_PACKET_SIZE_SMALL)
  32. packet->m_headerType = RTMP_PACKET_SIZE_MINIMUM;
  33. last = prevPacket->m_nTimeStamp;// 前一个包的时间戳
  34. }
  35. // 块头类型fmt取值0、1、2、3,超过3就表示出错(fmt占二个字节)
  36. if (packet->m_headerType > 3) /* sanity */
  37. {
  38. RTMP_Log(RTMP_LOGERROR, "sanity failed!! trying to send header of type: 0x%02x.", (unsigned char)packet->m_headerType);
  39. return FALSE;
  40. }
  41. // 块头初始大小 = 基本头(1字节) + 块消息头大小(11/7/3/0) = [12, 8, 4, 1]
  42. // 块基本头是1-3字节,因此用变量cSize来表示剩下的0-2字节
  43. // nSize 表示块头初始大小, hSize表示块头大小
  44. nSize = packetSize[packet->m_headerType];
  45. hSize = nSize;
  46. cSize = 0;
  47. // 时间戳增量
  48. t = packet->m_nTimeStamp - last;
  49. if (packet->m_body)
  50. {
  51. // m_body是指向负载数据首地址的指针;“-”号用于指针前移
  52. header = packet->m_body - nSize;
  53. // 块头的首指针
  54. hend = packet->m_body;
  55. // 块头的尾指针
  56. }
  57. else
  58. {
  59. header = hbuf + 6;
  60. hend = hbuf + sizeof(hbuf);
  61. }
  62. if (packet->m_nChannel > 319)// 块流id(cs id)大于319,则块基本头占3个字节
  63. cSize = 2;
  64. else if (packet->m_nChannel > 63)// 块流id(cs id)在64与319之间,则块基本头占2个字节
  65. cSize = 1;
  66. // ChunkBasicHeader的长度比初始长度还要长
  67. if (cSize)
  68. {
  69. header -= cSize;// header指向块头
  70. hSize += cSize;// hSize加上ChunkBasicHeader的长度(比初始长度多出来的长度)
  71. }
  72. // nSize > 1表示块消息头至少有3个字节,即存在timestamp字段
  73. // 相对TimeStamp大于0xffffff,此时需要使用ExtendTimeStamp
  74. if (nSize > 1 && t >= 0xffffff)
  75. {
  76. header -= 4;
  77. hSize += 4;
  78. }
  79. hptr = header;
  80. c = packet->m_headerType << 6;// 把ChunkBasicHeader的Fmt类型左移6位
  81. // 设置basic header的第一个字节值,前两位为fmt. 可以参考官方协议:流的分块 --- 6.1.1节
  82. switch (cSize)
  83. {
  84. case 0:// 把ChunkBasicHeader的低6位设置成ChunkStreamID( cs id )
  85. c |= packet->m_nChannel;
  86. break;
  87. case 1:// 同理,但低6位设置成000000
  88. break;
  89. case 2:// 同理,但低6位设置成000001
  90. c |= 1;
  91. break;
  92. }
  93. *hptr++ = c;// 可以拆分成两句*hptr=c; hptr++,此时hptr指向第2个字节
  94. // 设置basic header的第二(三)个字节值
  95. if (cSize)
  96. {
  97. int tmp = packet->m_nChannel - 64;// 将要放到第2字节的内容tmp
  98. *hptr++ = tmp & 0xff;// 获取低位存储与第2字节
  99. if (cSize == 2)// ChunkBasicHeader是最大的3字节时,获取高位存储于最后1个字节(注意:排序使用大端序列,和主机相反)
  100. *hptr++ = tmp >> 8;
  101. }
  102. if (nSize > 1)// ChunkMsgHeader长度为11、7、3, 都含有timestamp(3字节)
  103. {
  104. // 将时间戳(相对或绝对)转化为3个字节存入hptr,如果时间戳超过0xffffff,则后面还要填入Extend Timestamp
  105. hptr = AMF_EncodeInt24(hptr, hend, t > 0xffffff ? 0xffffff : t);
  106. }
  107. if (nSize > 4)// ChunkMsgHeader长度为11、7,都含有 msg length + msg type id
  108. {
  109. // 将消息长度(msg length)转化为3个字节存入hptr
  110. hptr = AMF_EncodeInt24(hptr, hend, packet->m_nBodySize);
  111. *hptr++ = packet->m_packetType;
  112. }
  113. // ChunkMsgHeader长度为11, 含有msg stream id( 小端)
  114. if (nSize > 8)
  115. hptr += EncodeInt32LE(hptr, packet->m_nInfoField2);
  116. if (nSize > 1 && t >= 0xffffff)// 如果时间戳大于0xffffff,则需要写入Extend Timestamp
  117. hptr = AMF_EncodeInt32(hptr, hend, t);
  118. // 到此为止,已经将块头填写好了
  119. // 此时nSize表示负载数据的长度, buffer是指向负载数据区的指针
  120. nSize = packet->m_nBodySize;
  121. buffer = packet->m_body;
  122. nChunkSize = r->m_outChunkSize;//Chunk大小,默认是128字节
  123. RTMP_Log(RTMP_LOGDEBUG2, "%s: fd=%d, size=%d", __FUNCTION__, r->m_sb.sb_socket, nSize);
  124. /* send all chunks in one HTTP request ,使用HTTP协议 */
  125. if (r->Link.protocol & RTMP_FEATURE_HTTP)
  126. {
  127. // nSize:Message负载长度;nChunkSize:Chunk长度;
  128. // 例nSize:307,nChunkSize:128;
  129. // 可分为(307 + 128 - 1)/128 = 3个
  130. // 为什么加 nChunkSize - 1?因为除法会只取整数部分!
  131. int chunks = (nSize + nChunkSize - 1) / nChunkSize;
  132. if (chunks > 1)// Chunk个数超过一个
  133. {
  134. // 注意:ChunkBasicHeader的长度 = cSize + 1
  135. // 消息分n块后总的开销:
  136. // n个ChunkBasicHeader,1个ChunkMsgHeader,1个Message负载
  137. // 实际上只有第一个Chunk是完整的,剩下的只有ChunkBasicHeader
  138. tlen = chunks * (cSize + 1) + nSize + hSize;
  139. tbuf = malloc(tlen);
  140. if (!tbuf)
  141. return FALSE;
  142. toff = tbuf;
  143. }
  144. }
  145. // 消息的负载 + 头
  146. while (nSize + hSize)
  147. {
  148. int wrote;
  149. if (nSize < nChunkSize)// 消息负载大小 < Chunk大小(不用分块)
  150. nChunkSize = nSize;// Chunk可能小于设定值
  151. RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)header, hSize);
  152. RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)buffer, nChunkSize);
  153. // 如果r->Link.protocol采用Http协议,则将RTMP包数据封装成多个Chunk,然后一次性发送。
  154. // 否则每封装成一个块,就立即发送出去
  155. if (tbuf)
  156. {
  157. // 将从Chunk头开始的nChunkSize + hSize个字节拷贝至toff中,
  158. // 这些拷贝的数据包括块头数据(hSize字节)和nChunkSize个负载数据
  159. memcpy(toff, header, nChunkSize + hSize);
  160. toff += nChunkSize + hSize;
  161. }
  162. else// 负载数据长度不超过设定的块大小,不需要分块,因此tbuf为NULL;或者r->Link.protocol不采用Http
  163. {
  164. // 直接将负载数据和块头数据发送出去
  165. wrote = WriteN(r, header, nChunkSize + hSize);
  166. if (!wrote)
  167. return FALSE;
  168. }
  169. nSize -= nChunkSize;// 消息负载长度 - Chunk负载长度
  170. buffer += nChunkSize;// buffer指针前移1个Chunk负载长度
  171. hSize = 0;// 重置块头大小为0,后续的块只需要有基本头(或加上扩展时间戳)即可
  172. // 如果消息负载数据还没有发完,准备填充下一个块的块头数据
  173. if (nSize > 0)
  174. {
  175. header = buffer - 1;
  176. hSize = 1;
  177. if (cSize)
  178. {
  179. header -= cSize;
  180. hSize += cSize;
  181. }
  182. *header = (0xc0 | c);
  183. if (cSize)
  184. {
  185. int tmp = packet->m_nChannel - 64;
  186. header[1] = tmp & 0xff;
  187. if (cSize == 2)
  188. header[2] = tmp >> 8;
  189. }
  190. }
  191. }
  192. if (tbuf)
  193. {
  194. int wrote = WriteN(r, tbuf, toff - tbuf);
  195. free(tbuf);
  196. tbuf = NULL;
  197. if (!wrote)
  198. return FALSE;
  199. }
  200. /* we invoked a remote method */
  201. if (packet->m_packetType == 0x14)
  202. {
  203. AVal method;
  204. char *ptr;
  205. ptr = packet->m_body + 1;
  206. AMF_DecodeString(ptr, &method);
  207. RTMP_Log(RTMP_LOGDEBUG, "Invoking %s", method.av_val);
  208. /* keep it in call queue till result arrives */
  209. if (queue)
  210. {
  211. int txn;
  212. ptr += 3 + method.av_len;
  213. txn = (int)AMF_DecodeNumber(ptr);
  214. AV_queue(&r->m_methodCalls, &r->m_numCalls, &method, txn);
  215. }
  216. }
  217. if (!r->m_vecChannelsOut[packet->m_nChannel])
  218. r->m_vecChannelsOut[packet->m_nChannel] = malloc(sizeof(RTMPPacket));
  219. memcpy(r->m_vecChannelsOut[packet->m_nChannel], packet, sizeof(RTMPPacket));
  220. return TRUE;
  221. }

librtmp协议分析---RTMP_SendPacket函数相关推荐

  1. 宅男抖音某猫协议分析及应用破解

    " 分析传说中的快x,顺便提供破VIP线路及去启动广告方法." 在当今这个由应用市场主导的网络上,流传着一批应用,它们低调又神秘,依赖口碑与独立网站在地下渠道传播,应用市场中从来都 ...

  2. TCP/IP协议分析

    一;前言 学习过TCP/IP协议的人多有一种感觉,这东西太抽象了,没有什么数据实例,看完不久就忘了.本文将介绍一种直观的学习方法,利用协议分析工具学习TCP/IP,在学习的过程中能直观的看到数据的具体 ...

  3. VC++分析数据包实现Telnet协议分析

    Telnet协议是TCP/IP协议族中的一员,是Internet远程登陆服务的标准协议和主要方式.它为用户提供了在本地计算机上完成远程主机工作的能力.在终端使用者的电脑上使用telnet程序,用它连接 ...

  4. webqq2协议分析和qq聊天机器人简单实现(转)

    webqq2协议分析和qq聊天机器人简单实现 转之http://hfutxf.javaeye.com/blog/800866 通过webqq接口,可以实现发送qq消息接收qq消息等,这样,想实现一个q ...

  5. lwIP ARP协议分析

    ARP 协议分析 总的来说,lwip将链路层ethernet的协议分组格式分为ether和etherarp 分开处理.ip分组先进入etharp_ip_input更新一下arp表项,然后直接进入 ne ...

  6. Memcache的使用和协议分析详解

    Memcache的使用和协议分析详解 作者:heiyeluren 博客:http://blog.csdn.net/heiyeshuwu 时间:2006-11-12 关键字:PHP Memcache L ...

  7. arp协议分析python编程实现arp欺骗抓图片

    arp协议分析&python编程实现arp欺骗抓图片 序 学校tcp/ip协议分析课程老师布置的任务,要求分析一种网络协议并且研究安全问题并编程实现,于是我选择了研究arp协议,并且利用pyt ...

  8. 手游-放开那三国socket协议分析

    手游-放开那三国socket协议分析 图文:a4727603 楼主本就是一个偷懒的人,玩游戏最什么的最讨厌了还要一遍遍的刷副本,于是就有了这个破解的过程 刷副本太累,想写个脱机挂自动打副本,背景交代完 ...

  9. 微信安卓协议分析笔记

    一.查资料 网上没找到SDK可以分析,关于微信安卓协议的文章也比较少,比较有用的是<微信交互协议和加密模式研究>,这篇论文里介绍了微信使用RSA2048与AES-CBC-128结合的加密算 ...

最新文章

  1. CF375D Tree and Queries(dsu on tree)
  2. 飞得更高:(三)人不好招啊
  3. 怎么通过media foundation将图像数据写入虚拟摄像头_不知道怎么挑手机?性价比神机绝对适合你...
  4. Unsatisfied dependency expressed through field 'service'
  5. InvalidCharacterError: Failed to execute 'setAttribute' on 'Element': ')' is not a valid
  6. 2017年对口招生c语言及答案,2017年计算机专业对口考试试卷及答案.doc
  7. java跳_用Java实现跳表
  8. 前端修炼の道 | 如何成为一名合格前端开发工程师?
  9. android 选座系统,android 影院选座
  10. 【Android 开发入门】我为什么要在Android找工作越来越难的时候开始学习它
  11. macos双系统 wintogo_aigo固态硬盘,轻松实现macOS运行Windows双系统
  12. 小学教材失实一事体现出僵化的教育思维
  13. SOA 快速指南 1 2 3(转IBM developerWorks 中国) 4
  14. PhpSpreadsheet中文文档 | Spreadsheet操作教程实例
  15. 基于XMPP的即时通信系统的建立(一)— XMPP基础概念
  16. linux dmesg命令参数及用法详解
  17. rabbitmq的安装与命令行管理工具rabbitmqadmin的使用
  18. 介绍理想工作计算机 英语作文,理想工作的英语作文6篇
  19. 【爬虫】网页抓包工具--Charles的使用教程
  20. [开源工具] svg图像编辑工具

热门文章

  1. ASP.NET MVC与RAILS3的比较
  2. CodeForces - 1348C Phoenix and Distribution(思维)
  3. HDU - 3538 A sample Hamilton path(最短哈密顿路径+状压dp)
  4. android 点击item跳转页面,Android RecyclerView Item 点击事件,简单
  5. Web开发-Django初识及实战
  6. html5 fc,HTML5_mob604756fc093d的技术博客_51CTO博客
  7. java list接口方法_java List集合接口的坑
  8. mysql数据库显示问号_mysql数据库中文显示问号
  9. L1-048. 矩阵A乘以B
  10. 关于Java中的String类