RTMP协议抓包分析推流过程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RTMP协议抓包分析推流过程相关的知识,希望对你有一定的参考价值。

参考技术A RTMP协议规定,发布一个流媒体有两个前提步骤:
第一步,建立一个网络连接(NetConnection)。
第二步,建立一个网络流(NetStream)。
网络连接代表服务器端应用程序和客户端之间基础的连通关系,网络流代表了发送多媒体数据的通道。服务器和客户端之间只能建立一个网络连接,但是基于该连接可以创建很多网络流。

发布一个RTMP协议的流媒体需要经过四个阶段:

下面是使用librtmp执行推流过程的API调用流,如下:

RTMP定义了较为完善的协议标准,遵循规范,我们可以使用合适的工具进行推流,但是由于有些操作是可选的,因而抓包过程又略有差异,下面是我使用ffmpeg工具推流时抓取的报文,使用wireshark分析过程整理为下面的图文。

先看一张总览图,图中显示的报文和时序包含了握手、建立连接、建立流和推流阶段,如下:

还有申明下,以下的流程是根据实际抓包情况分析出来的,由于不同的工具省略了一些不必要的步骤,故不代表标准结果,仅供参考。

由于讲解握手过程的文档资料比较多,我这里就不重复描述了,摘图如下:

个人认为这张图是最符合标准时序的,细节拿捏得非常讲究,虽然很多实现简化了流程。

包括以下报文和步骤:

协议截图如下:

协议方向:客户端 -> 服务器
块头字段:
     HeaderType: 0
     CSID: 3
     时间戳:0
     BodySize: 143
     TypeID: 0x14
     Stream ID: 0
负载格式:AMF0表示,connect 1 object1
     object1属性列表:
          "app": "live"
          "type": "nonprivate"
          "flashVer": "LNX 9,0,124,2"
          "tcUrl": " rtmp://127.0.0.1:1935/live "
          End Of Object Marker

协议截图如下:

协议方向:服务器 -> 客户端
块头字段:
     HeaderType: 0
     CSID: 2
     时间戳:0
     BodySize: 4
     TypeID: 0x05
     Stream ID: 0
负载格式:4字节整型表示,如5000000

协议截图如下:

协议方向:服务器 -> 客户端
块头字段:
     HeaderType: 0
     CSID: 2
     时间戳:0
     BodySize: 5
     TypeID: 0x06
     Stream ID: 0
负载格式:5字节整型表示,前4字节为带宽,后1字节为标志,如5000000, 2(动态调整)

协议截图如下:

协议方向:服务器 -> 客户端
块头字段:
     HeaderType: 0
     CSID: 2
     时间戳:0
     BodySize: 4
     TypeID: 0x01
     Stream ID: 0
负载格式:4字节整型表示,如4096

协议截图如下:

协议方向:服务器 -> 客户端
块头字段:
     HeaderType: 0
     CSID: 3
     时间戳:0
     BodySize: 190
     TypeID: 0x14
     Stream ID: 0
负载格式:AMF0表示,_result 1 object1 object2
     object1属性列表:
          "fmsVer": "FMS/3,0,1,123"
          "capabilities": 31,
          End Of Object Marker
     object2属性列表:
          "level": "status"
          "code": "NetConnection.Connect.Success",
          "description": "Connection succeeded.",
          "objectEncoding": 0
          End Of Object Marker

协议截图如下:

协议方向:客户端 -> 服务器
块头字段:
     HeaderType: 0
     CSID: 2
     时间戳:0
     BodySize: 4
     TypeID: 0x01
     Stream ID: 0
负载格式:4字节整型表示,如4096

包括以下报文和步骤:

协议截图如下:

协议方向:客户端 -> 服务器
块头字段:
     HeaderType: 1
     CSID: 3
     时间戳:0
     BodySize: 30
     TypeID: 0x14
负载格式:AMF0表示,releaseStream 2 object(Null) String("a")

协议截图如下:

协议方向:客户端 -> 服务器
块头字段:
     HeaderType: 1
     CSID: 3
     时间戳:0
     BodySize: 26
     TypeID: 0x14
负载格式:AMF0表示,FCPublish 3 object(Null) String("a")

协议截图如下:

协议方向:客户端 -> 服务器
块头字段:
     HeaderType: 1
     CSID: 3
     时间戳:0
     BodySize: 25
     TypeID: 0x14
负载格式:AMF0表示,createStream 4 object(Null)

协议截图如下:

协议方向:服务器 -> 客户端
块头字段:
     HeaderType: 0
     CSID: 3
     时间戳:0
     BodySize: 29
     TypeID: 0x14
     Stream ID: 0
负载格式:AMF0表示,_result 4 object(Null) Number(1)

包括以下报文和步骤:

完成推推后,还包括以下报文和步骤:

协议截图如下:

协议方向:客户端 -> 服务器
块头字段:
     HeaderType: 0
     CSID: 8
     时间戳:0
     BodySize: 31
     TypeID: 0x14
     Stream ID: 1
负载格式:AMF0表示,publish 5 Object(Null) String节目ID("a") String("live")

协议截图如下:

协议方向:服务器 -> 客户端
块头字段:
     HeaderType: 0
     CSID: 5
     时间戳:0
     BodySize: 105
     TypeID: 0x14
     Stream ID: 1
负载格式:AMF0表示,onStatus 0 Object1(Null) object2
     object2属性列表:
          "level": "status"
          "code": "NetStream.Publish.Start",
          "description": "Start publishing",
          End Of Object Marker

协议截图如下:

协议方向:客户端 -> 服务器
块头字段:
     HeaderType: 0
     CSID: 4
     时间戳:0
     BodySize: 387
     TypeID: 0x12
     Stream ID: 1
负载格式:AMF0表示,@setDataFrame "onMetaData" ECMAarray
     ECMAarray属性列表:
          "duration": 0,
          "width": 480,
          "height": 270,
          "videodatarate": 195.3125,
          "framerate": 16,
          "videocodeid": 2,
          "audiodatarate": 62.5,
          "audiosamplerate": 44100,
          "audiosamplesize": 16,
          "stereo": false,
          "audiocodeid": 2,
          "major_brand": "isom",
          "minor_version": "512",
          "compatible_brands": "isomiso2avc1mp41",
          "encoder": "Lavf59.8.100",
          "filesize": 0,
          End Of Object Marker

协议截图如下:

协议方向:客户端 -> 服务器
块头字段:
     HeaderType: 0
     CSID: 4
     时间戳:0
     BodySize: 209
     TypeID: 0x08
     Stream ID: 1
负载格式:格式头,媒体数据

协议截图如下:

协议方向:客户端 -> 服务器
块头字段:
     HeaderType: 1
     CSID: 3
     时间戳:0
     BodySize: 28
     TypeID: 0x14
负载格式:AMF0表示,FCUnpublish 6 object(Null) String("a")

协议截图如下:

协议方向:客户端 -> 服务器
块头字段:
     HeaderType: 1
     CSID: 3
     时间戳:0
     BodySize: 34
     TypeID: 0x14
负载格式:AMF0表示,deleteStream 7 object(Null) Number(1)

协议截图如下:

协议方向:服务器 -> 客户端
块头字段:
     HeaderType: 0
     CSID: 5
     时间戳:0
     BodySize: 108
     TypeID: 0x14
     Stream ID: 1
负载格式:AMF0表示,onStatus 0 Object1(Null) object2
     object2属性列表:
          "level": "status"
          "code": "NetStream.Unpublish.Start",
          "description": "Stop publishing",
          End Of Object Marker

结合以上分析,总结时序图如下:

另外,关于HeaderType和CSID的运用,先归纳使用情况:
0x14(connect) HeaderType: 0 CSID: 3
0x05(Ack Window Size) HeaderType: 0 CSID: 2
0x06(BrandWidth) HeaderType: 0 CSID: 2
0x01(ChunkSize) HeaderType: 0 CSID: 2
0x14(connect _result) HeaderType: 0 CSID: 3
0x14(releaseStream) HeaderType: 1 CSID: 3
0x14(FCPublish) HeaderType: 1 CSID: 3
0x14(createStream) HeaderType: 1 CSID: 3
0x14(createStream _result) HeaderType: 0 CSID: 3
0x14(publish) HeaderType: 0 CSID: 8
0x14(publish onStatus) HeaderType: 0 CSID: 5
0x12(onMetaData) HeaderType: 0 CSID: 4
0x08(audioData) HeaderType: 0 CSID: 4
0x09(videoData) HeaderType: 0 CSID: 6
0x14(FCUnpublish) HeaderType: 1 CSID: 3
0x14(deleteStream) HeaderType: 1 CSID: 3
0x14(deleteStream onStatus) HeaderType: 0 CSID: 5

总结:
关于HeaderType的运用,有以下规则:
releaseStream、FCPublish、createStream、FCUnpublish、deleteStream使用1号HeaderType,借用3号CSID之前的StreamID。
audioData和videoData视情况使用0、1、2、3号HeaderType。
关于CSID的运用,有以下规则:

经与拉流对比,发现CSID的使用没有明显的约束规则,如果某类数据需要压缩头部,建议使用相同的CSID。

搞清楚直播协议RTMP

原文地址:https://zhuanlan.zhihu.com/p/439220235


文章目录


说起RTMP协议,相信很多人都比较陌生,这个协议相对HTTP、HTTPS、TCP等我们常见的协议而言,我们在工作中确实较少接触它,但是对现在如火如荼的直播行业,RTMP是一个重要的协议,它在 实时音视频场景中使用非常广泛,而且目前市占率很高。

本文的主要内容是分析RTMP的协议,当然不是纯理论分析,这样没多大意思,还是结合实践抓包文件来具体分析,这样才能较好地理解RTMP的内涵。具体如何抓包见本文末尾的“Android抓包”模块。希望你阅读完本章之后,自己也能简单地动手操作一下,这样理解深刻一下。

推荐视频

音视频面试必问-RTSP/RTMP推流的各种坑分析

SRS-RTMP-WebRTC流媒体服务器入门

学习地址:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发

RTMP基础介绍

RTMP协议的主要特点:

  • RTMP协议是应用层协议,是要靠底层可靠的传输层(通常是TCP)来保证信息传输的可靠性的。
  • 在基于传输层协议的链接建立完成后,RTMP协议也要客户端和服务器通过“握手”来建立基于传输层链接之上的RTMP Connection链接。播放一个RTMP协议的流媒体需要经过以下几个步骤:握手,建立网络连接,建立网络流,播放。服务器和客户端之间只能建立一个网络连接,但是基于该连接可以创建很多网络流。

这儿埋下一个小疑问?为什么传输层已经建立了TCP连接,RTMP还需要再次建立一个连接,有这个必要吗?

  • RTMP协议传输时会对数据做自己的格式化,这种格式的消息我们称之为RTMP Message,而实际传输的时候为了更好地实现多路复用、分包和信息的公平性,发送端会把Message划分为带有Message ID的Chunk,每个Chunk可能是一个单独的Message,也可能是Message的一部分,在接受端会根据chunk中包含的data的长度,message id和message的长度把chunk还原成完整的Message,从而实现信息的收发。

RTMP握手

RTMP基于TCP,已知TCP需要3次握手才可以建立连接,在TCP3次握手成功之后,应用层的RTMP也是需要握手的,就是认证过程。具体的认证过程如下:

  • 客户端发送 C0、C1、 C2,服务器发送 S0、 S1、 S2。
  • 首先,客户端发送 C0 表示自己的版本号,不必等对方的回复,然后发送 C1 表示自己的时间戳。
  • 服务器只有在收到 C0 的时候,才能返回 S0,表明自己的版本号,如果版本不匹配,可以断开连接。
  • 服务器发送完 S0 后,也不用等什么,就直接发送自己的时间戳 S1。客户端收到 S1 的时候,发一个知道了对方时间戳的 ACK C2。同理服务器收到 C1 的时候,发一个知道了对方时间戳的 ACK S2。
  • 握手建立完成。

现在回答上面提出的问题,为什么RTMP还需要单独建立一个连接?

因为它们需要商量一些事情,保证以后的传输能正常进行。主要就是两个事情,一个是版本号,如果客户端、服务器的版本号不一致,则不能工作。另一个就是时间戳,视频播放中,时间是很重要的,后面的数据流互通的时候,经常要带上时间戳的差值,因而一开始双方就要知道对方的时间戳。

光讲纯理论,没意思,还是抓包看一下具体的流程吧。

1. 首先TCP 3次握手

2. RTMP握手过程

我们发现真实发包是C0+C1一起发;S0、S1、S2一起发。但是发送的时候还是会严格按照时序来控制的,这样才能真正校验好版本号等字段。


拉流

RTMP拉流的核心流程如下:


1. 建立网络连接

客户端发送命令消息中的“连接”(connect)到服务器,请求与一个服务应用实例建立连接。

StreamID是每个消息的唯一标识,划分成Chunk和还原Chunk为Message的时候都是根据这个ID来辨识是否是同一个消息的Chunk的,这里面为0说明这个消息是初始的0消息。

Chunk stream ID:一个RTMP message会拆分成多个chunk,同一个Chunk Stream ID必然属于同一个Message。这样在传送过程中发过来的chunk就是通过chunk stream ID最终组装成功我一个完成的message数据的。

message type id(消息的类型id):表示实际发送的数据的类型,如8代表音频数据、9代表视频数据。如下面的两张图,这样看上去是不是好理解一点了。


Format:指的是chunk type。共有4种不同的格式,其中第一种格式字段为0,可以表示其他三种表示的所有数据,但由于其他三种格式是基于对之前chunk的差量化的表示,因此可以更简洁地表示相同的数据,实际使用的时候还是应该采用尽量少的字节表示相同意义的数据。因为type 0是表示不同数据,其他是差量,所以可以想象如果搜不到type 0的包说明这个流肯定有问题。可以通过“rtmp.header.format == 0”过滤。

2. 建立一个网络流

网络流代表了发送多媒体数据的通道。服务器和客户端之间只能建立一个网络连接,且多个网络流可以复用这一个网络连接。这个在上面已经反复说过。

客户端向服务器请求创建流:

服务器收到请求后向客户端发送_result(),对创建流的消息进行响应。此时NetStream创建完成。


3. Play 播放

客户端发送命令消息中的“播放”(play)命令到服务器。

接收到播放命令后,服务器发送设置块大小(ChunkSize)协议消息。

服务器发送用户控制消息中的“streambegin”,告知客户端流ID。

播放命令成功的话,服务器发送命令消息中的“响应状态” NetStream.Play.Start,告知客户端“播放”命令执行成功。

我们发现执行了3个动作,分别如下:



共用一个Stream ID,并且在可以播放消息回来之后,已经解析出视频的基本属性。

推流

分析完拉流的所有操作,其实推流也是类似的,区别在Play —> Publishing了。

Android抓包

  • 进入网站:https://www.androidtcpdump.com/android-tcpdump/downloads,下载最新版本的android tcpdump工具,现在最新版本是4.9.3
  • 找一个root的手机,将下载好的tcpdump文件先push到/sdcard/ 下面,adb push tcpdump /sdcard/tcpdump
  • adb shell进入手机adb 模式下,cp -rf /sdcard/tcpdump /data/local/,将tcpdump拷贝到/data/local/目录下
  • chmod 777 /data/tcpdump,赋予tcpdump完全的执行权限
  • ./data/local/tcpdump -i any -p -s 0 -w /sdcard/capture.pcap
  • 然后开始访问rtmp的请求,访问完成后,会在/sdcard/目录下生成capture.pcap文件
  • adb pull /sdcard/capture.pcap ,本地使用wireshare分析capture.pcap文件
  • rtmp的测试源提供一个:rtmp://58.200.131.2:1935/livetv/hunantv

【文章福利】

需要C/C++ Linux服务器架构师及音视频学习资料加群812855908(资料包括C/C++,Linux,golang技术,内核,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,音视频,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg,大厂面试题 等)

以上是关于RTMP协议抓包分析推流过程的主要内容,如果未能解决你的问题,请参考以下文章

调试libRTMP代码来分析RTMP协议

从wireshark抓包分析rtmp协议,并提取出H264视频流

Wireshark抓包rtmp

Wireshark抓包rtmp

Wireshark抓包rtmp

抓包分析Telnet远程登陆协议信令交互过程