RTMP是Real Time Messaging Protocol(实时消息传输协议)的首字母缩写。该协议基于TCP,是一个协议族,常用在视频直播领域。RTMP协议的默认端口是1935。

学习一个协议最好的方法就是调试其通信过程,期间还可以使用wireshark抓包分析。本人在libRTMP的基础上添加了推流部分,并且使得整个过程变得可调试,学习其协议就变得简单多了。配置好的VS2010可调试的libRTMP工程:https://github.com/jiayayao/librtmp。该工程可以使用VS调试RTMP协议内部的代码,并且对RTMP协议部分做了详细的注释。推流部分参考leixiaohua的blog,RTMP Server可以采用Nginx-RTMP module的方式,搭建RTMP Server过程可以参考:使用nginx+nginx-rtmp-module+ffmpeg搭建流媒体服务器笔记(一)

testRTMP工程是推流客户端,推送一个FLV文件需要经过以下几个步骤:握手,建立连接,建立流,推流。RTMP连接都是以握手作为开始的。建立连接阶段用于建立客户端与服务器之间的“网络连接”;建立流阶段用于建立客户端与服务器之间的“网络流”;推流即按照FLV格式将数据传送至RTMP Server。

一、握手

RTMP握手过程如下:

1. 客户端向服务器发送C0、C1块,服务器收到后发送S0和S1块;

2. 客户端收到S0和S1后,向服务器发送C2块;服务器收到C2块后发送S2块;

3. 客户端和服务器分别收到S2和C2后,握手建立完成。

与HandShake相对应,还有SHandShake函数是服务器部分的握手部分,有兴趣的可以看一下。

二、建立网络连接

三、建立网络流

RTMP_ConnectStream()时接收到的packet的type依次是:

  1. 0x05: Set server bindwidthBW =
  2. 0x06: Set client bindwidthBW =
  3. 0x01: Set in chunk size()
  4.  
  5. 0x20:Invoke <_result>
  6. (object begin)
  7. (object begin)
  8. Property: <Name: fmsVer, STRING: FMS/,,,>
  9. Property: <Name: capabilities, NUMBER: 31.00>
  10. (object end)
  11. (object begin)
  12. Property: <Name: level, STRING: status>
  13. Property: <Name: code, STRING: NetConnection.Connect.Success>
  14. Property: <Name: description, STRING: Connection succeeded.>
  15. Property: <Name: objectEncoding, NUMBER: 0.00>
  16. (object end)
  17. (object end)
  18.  
  19. 0x20:Invoke <_result>
  20. (object begin)
  21. Property: NULL
  22. (object end)
  23.  
  24. 0x20:Invoke <onStatus>
  25. (object begin)
  26. Property: NULL
  27. (object begin)
  28. Property: <Name: level, STRING: status>
  29. Property: <Name: code, STRING: NetStream.Publish.Start>
  30. Property: <Name: description, STRING: Start publishing>
  31. (object end)
  32. (object end)

服务器接收到“connect”消息后,会返回_result给客户端,客户端接收到是connect的response后,会发送“createStream”命令到服务器。

服务器接收到“createStream”消息后,会返回_result给客户端,客户端接收到是“createStream”命令返回的response后,会发送“publish”命令到服务器。网络流建立完成,开始传送数据。

四、推流

推流部分的关键代码如下:

  1. int publish_using_packet(){
  2. RTMP *rtmp=NULL;
  3. RTMPPacket *packet=NULL;
  4. uint32_t start_time=;
  5. uint32_t now_time=;
  6. //the timestamp of the previous frame
  7. long pre_frame_time=;
  8. long lasttime=;
  9. int bNextIsKey=;
  10. uint32_t preTagsize=;
  11.  
  12. //packet attributes
  13. uint32_t type=;
  14. uint32_t datalength=;
  15. uint32_t timestamp=;
  16. uint32_t streamid=;
  17.  
  18. FILE*fp=NULL;
  19. fp=fopen("cuc_ieschool.flv","rb");
  20. if (!fp){
  21. RTMP_LogPrintf("Open File Error.\n");
  22. CleanupSockets();
  23. return -;
  24. }
  25.  
  26. if (!InitSockets()){
  27. RTMP_LogPrintf("Init Socket Err\n");
  28. return -;
  29. }
  30.  
  31. // 创建一个RTMP会话的句柄
  32. rtmp=RTMP_Alloc();
  33. // 初始化RTMP句柄
  34. RTMP_Init(rtmp);
  35. //set connection timeout,default 30s
  36. rtmp->Link.timeout=;
  37. // 设置URL
  38. if(!RTMP_SetupURL(rtmp,"rtmp://192.168.37.130:1935/myapp/test1"))
  39. {
  40. RTMP_Log(RTMP_LOGERROR,"SetupURL Err\n");
  41. RTMP_Free(rtmp);
  42. CleanupSockets();
  43. return -;
  44. }
  45.  
  46. //if unable,the AMF command would be 'play' instead of 'publish'
  47. RTMP_EnableWrite(rtmp);
  48.  
  49. // RTMP_Connect分为2步:RTMP_Connect0和RTMP_Connect1
  50. // 0负责建立TCP底层连接
  51. // 1负责RTMP握手操作
  52. if (!RTMP_Connect(rtmp,NULL)){
  53. RTMP_Log(RTMP_LOGERROR,"Connect Err\n");
  54. RTMP_Free(rtmp);
  55. CleanupSockets();
  56. return -;
  57. }
  58.  
  59. if (!RTMP_ConnectStream(rtmp,)){
  60. RTMP_Log(RTMP_LOGERROR,"ConnectStream Err\n");
  61. RTMP_Close(rtmp);
  62. RTMP_Free(rtmp);
  63. CleanupSockets();
  64. return -;
  65. }
  66.  
  67. packet=(RTMPPacket*)malloc(sizeof(RTMPPacket));
  68. RTMPPacket_Alloc(packet,*);
  69. RTMPPacket_Reset(packet);
  70.  
  71. packet->m_hasAbsTimestamp = ;
  72. packet->m_nChannel = 0x04;
  73. packet->m_nInfoField2 = rtmp->m_stream_id;
  74.  
  75. RTMP_LogPrintf("Start to send data ...\n");
  76.  
  77. //jump over FLV Header
  78. // FLV格式的header为9个字节
  79. fseek(fp,,SEEK_SET);
  80. //jump over previousTagSizen
  81. // 跳过表征前一段Tag大小的4个字节
  82. fseek(fp,,SEEK_CUR);
  83. start_time=RTMP_GetTime();
  84. while()
  85. {
  86. if((((now_time=RTMP_GetTime())-start_time)
  87. <(pre_frame_time)) && bNextIsKey){
  88. //wait for 1 sec if the send process is too fast
  89. //this mechanism is not very good,need some improvement
  90. if(pre_frame_time>lasttime){
  91. RTMP_LogPrintf("TimeStamp:%8lu ms\n",pre_frame_time);
  92. lasttime=pre_frame_time;
  93. }
  94. Sleep();
  95. continue;
  96. }
  97.  
  98. //not quite the same as FLV spec
  99. // 读取当前Tag的类型(1个字节)
  100. if(!ReadU8(&type,fp))
  101. break;
  102. // 读取当前Tag data部分的大小(3个字节)
  103. if(!ReadU24(&datalength,fp))
  104. break;
  105. // 读取时间戳(4个字节)
  106. if(!ReadTime(&timestamp,fp))
  107. break;
  108. // 读取stream id(3个字节),一般为0
  109. if(!ReadU24(&streamid,fp))
  110. break;
  111.  
  112. // 跳过既非视频也非音频的Tag
  113. if (type!=0x08&&type!=0x09){
  114. //jump over non_audio and non_video frame,
  115. //jump over next previousTagSizen at the same time
  116. fseek(fp,datalength+,SEEK_CUR);
  117. continue;
  118. }
  119.  
  120. // 读取当前音视频Tag的数据到packet
  121. if(fread(packet->m_body,,datalength,fp)!=datalength)
  122. break;
  123.  
  124. packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
  125. packet->m_nTimeStamp = timestamp;
  126. packet->m_packetType = type;
  127. packet->m_nBodySize = datalength;
  128. pre_frame_time=timestamp;
  129.  
  130. if (!RTMP_IsConnected(rtmp)){
  131. RTMP_Log(RTMP_LOGERROR,"rtmp is not connect\n");
  132. break;
  133. }
  134. // 这样看下来是一个FLV的Tag发送一个RTMPPacket
  135. if (!RTMP_SendPacket(rtmp,packet,)){
  136. RTMP_Log(RTMP_LOGERROR,"Send Error\n");
  137. break;
  138. }
  139.  
  140. // 读取前一个Tag的size
  141. if(!ReadU32(&preTagsize,fp))
  142. break;
  143.  
  144. // 读取当前Tag的type
  145. if(!PeekU8(&type,fp))
  146. break;
  147. if(type==0x09){
  148. if(fseek(fp,,SEEK_CUR)!=)
  149. break;
  150. if(!PeekU8(&type,fp)){
  151. break;
  152. }
  153. if(type==0x17)
  154. bNextIsKey=;
  155. else
  156. bNextIsKey=;
  157.  
  158. fseek(fp,-,SEEK_CUR);
  159. }
  160. }
  161.  
  162. RTMP_LogPrintf("\nSend Data Over\n");
  163.  
  164. if(fp)
  165. fclose(fp);
  166.  
  167. if (rtmp!=NULL){
  168. RTMP_Close(rtmp);
  169. RTMP_Free(rtmp);
  170. rtmp=NULL;
  171. }
  172. if (packet!=NULL){
  173. RTMPPacket_Free(packet);
  174. free(packet);
  175. packet=NULL;
  176. }
  177.  
  178. CleanupSockets();
  179. return ;
  180. }

在推送过程中,打开VLC播放器,输入网络流地址为:"rtmp://192.168.37.130:1935/myapp/test1",即可看到推流客户端推送的视频。

参考资料:

1. RTMP流媒体播放过程

2. RTMP规范简单分析

调试libRTMP代码来分析RTMP协议的更多相关文章

  1. 从wireshark数据中分析rtmp协议,并提取出H264视频流

    我写的小工具 rtmp_parse.exe 使用用法如先介绍下: -sps  [文件路径] 解析 sps 数据 文件当中的内容就是纯方本的hexstring: 如 42 E0 33 8D 68 05 ...

  2. RTMP协议分析及推流过程

    1.RTMP(实时消息传输协议)是Adobe 公司开发的一个基于TCP的应用层协议. 2.RTMP协议中基本的数据单元称为消息(Message). 3.当RTMP协议在互联网中传输数据的时候,消息会被 ...

  3. rtmp协议分析

    最近需要做一个rtmp服务器,着手分析一下rtmp协议,开干. rtmp握手 这个推荐一篇文章讲解得比较透彻http://blog.sina.com.cn/s/blog_676e11660102v8b ...

  4. C++实现RTMP协议发送H.264编码及AAC编码的音视频

    http://www.cnblogs.com/haibindev/archive/2011/12/29/2305712.html C++实现RTMP协议发送H.264编码及AAC编码的音视频 RTMP ...

  5. C++实现RTMP协议发送H.264编码及AAC编码的音视频(转)

    C++实现RTMP协议发送H.264编码及AAC编码的音视频(转) RTMP(Real Time Messaging Protocol)是专门用来传输音视频数据的流媒体协议,最初由Macromedia ...

  6. 【转】C++实现RTMP协议发送H.264编码及AAC编码的音视频

    RTMP(Real Time Messaging Protocol)是专门用来传输音视频数据的流媒体协议,最初由Macromedia 公司创建,后来归Adobe公司所有,是一种私有协议,主要用来联系F ...

  7. RTMPdump(libRTMP)源代码分析 4: 连接第一步——握手(Hand Shake)

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

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

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

  9. RTMP协议发送H.264编码及AAC编码的音视频,实现摄像头直播

    RTMP(Real Time Messaging Protocol)是专门用来传输音视频数据的流媒体协议,最初由Macromedia 公司创建,后来归Adobe公司所有,是一种私有协议,主要用来联系F ...

随机推荐

  1. Github+hexo+next搭建教程

    今天参考的是大神的教程,学了一个新东西,但是可能由于原教程中运用的npm包与我当前使用的npm包版本不同的原因,有出过多处运行错误,但都在此教程中解决了; 总结了下命令: npm install he ...

  2. word 文档刷文字格式

    WORD文档增加的宏文件, 作用:对全文中文字体更改为,DFKai-SB :对英文字母字体更改为,Times New Roman Sub AutoClose() Selection.WholeStor ...

  3. 前端动画小记---bilibili ( ゜-゜)つロ客户下载小动画

    逛哔哩哔哩 ( ゜-゜)つロPC版的时候看到一个蛮有意思的动画,指导用户去下载客户端,于是摸索实现了一个. 原动画效果 可以看到,一个静止的小电视人,当鼠标移动到电视人身上时,电视人慢慢变身成为一个小 ...

  4. LeetCode赛题395----Longest Substring with At Least K Repeating Characters

    395. Longest Substring with At least K Repeating Characters Find the length of the longest substring ...

  5. MySQL数据库(6)----配置文件 my.cnf 的使用

    1. 使用源码安装好MySQL后,其配置文件一般位于 /usr/local/my.cnf,可以使用如下命令查看查看配置文件的搜索顺序: root@javis:~$ mysqld --help --ve ...

  6. 【转】怎么用PHP发送HTTP请求(POST请求、GET请求)?

    file_get_contents版本: /** * 发送post请求 * @param string $url 请求地址 * @param array $post_data post键值对数据 * ...

  7. 记作为前端开发人员跑去面试C#.NET

    先谈结果,"秦总",与我面试讨论一个半小时,十分感动,然后拒绝了我. 本月17日16时许,收到邀请,于18日9时到司面试,虽如今仅深入前端领域,皆因曾有1年ASP.NET(C#)的 ...

  8. springboot整合fastdfs实现上传和下载

    FastDFS_Client源码 https://github.com/tobato/FastDFS_Client 友情提示:由于FastDFS_Client这个源码不是很多,并且目前没有找到相关文档 ...

  9. 关于webWorker的理解和简单例子

    一.理解 当在 HTML 页面中执行脚本时,页面的状态是不可响应的,直到脚本已完成. web worker 是运行在后台的 JavaScript,独立于其他脚本,不会影响页面的性能.您可以继续做任何愿 ...

  10. Oracle数据库从入门到精通-分组统计查询

    视频课程:李兴华 Oracle从入门到精通 视频课程学习者:阳光罗诺 视频来源:51CTO学院 整体内容: 统计函数的使用 分组统计查询的实现 对分组的数据过滤 统计函数 在之前我们就学习过一个COU ...