前言
本文主要讲解 RTMP 协议,并通过 wireshark 对 RTMP 进行抓包并分析。
一、RTMP 简介
1、RTMP 介绍
RTMP 是 Real Time Messaging Protocol( 实时消息传输协议) 的首字母缩写。该协议基于 TCP,是一个协议族,包括 RTMP 基本协议及 RTMPT/RTMPS/RTMPE 等多种变种。
RTMP 是一种设计用来进行实时数据通信的网络协议,主要用来在 Flash/AIR 平台和支持 RTMP 协议的流媒体/交互服务器之间进行音视频和数据通信。支持该协议的软件包括 Adobe Media Server/Ultrant Media Server/red5 等。RTMP 与 HTTP 一样, 都属于 TCP/IP 四层模型的应用层。
RTMP(Real Time Messaging Protocol)实时消息传送协议是 Adobe Systems 公司为 Flash 播放器和服务器之间音频、视频和数据传输开发的开放协议。
RTMP 协议传输时会对数据格式化,而实际传输的时候为了更好地实现多路复用、分包和信息的公平性,发送端会把 Message 划分为带有 Message ID 的 Chunk,每个 Chunk 可能是一个单独的 Message,也可能是 Message 的一部分,在接收端会根据 Chunk 中包含的 data 的长度,message id 和 message 的长度把 chunk 还原成完整的 Message,从而实现信息的收发。 (Message, Chunk)
2、变种
它有多种变种:
- RTMP 工作在 TCP 之上,默认使用端口 1935;
- RTMPE 在 RTMP 的基础上增加了加密功能;encrypt
- RTMPT 封装在 HTTP 请求之上,可穿透防火墙;http–rtmp
- RTMPS 类似 RTMPT,增加了 TLS/SSL 的安全功能;
二、wireshark 抓 RTMP 报文
RTMP 服务器:Nginx+rtmp(windows)
推流:ffmpeg
播放器:VLC(虚拟机 linux)
抓包:wireshark
1、搭建 RTMP 服务器
RTMP 服务器:Nginx+rtmp(windows)的环境搭建如有需要可自取:
链接:https://pan.baidu.com/s/1AcIVERWUPbJL1zu8yCcAzw
提取码:mtdf
2、运行 RTMP 服务器
双击 nginx8080.exe
在任务管理器可以看到目前 nginx 已开始工作
3、打开 wireshark
虚拟机用的 VMware Network Adapter VMnet8 虚拟网卡(NAT 虚拟网络)与主机(Windows)进行通信,因此这里我们直接就抓 VMware Network Adapter VMnet8 网卡即可。
4、ffmpeg 推流
推流命令:
ffmpeg -re -i SampleVideo_1280x720_20mb.mp4 -vcodec libx264 -acodec aac -r 30 -g 150 -f flv -y rtmp://192.168.36.176:1935/live/test1
这个命令使用 FFmpeg 工具来将输入视频文件 SampleVideo_1280x720_20mb.mp4 转换为 FLV 格式并通过 RTMP 协议流式传输到指定的 URL 地址 rtmp://192.168.36.176:1935/live/test1;
-re
:以实时模式(real-time)读取输入文件,模拟实时流传输的速度。-i SampleVideo_1280x720_20mb.mp4
:指定输入文件名为 SampleVideo_1280x720_20mb.mp4。-vcodec libx264
:选择 H.264 编码器作为视频编码器;-acodec aac
:选择 AAC 编码器作为音频编码器;-r 30
:设置输出视频的帧率为 30 帧每秒;-g 150
:设置关键帧间隔为 150 帧。关键帧是视频解码的起点,较短的关键帧间隔可以提高视频的快进/快退性能;-f flv
:指定输出格式为FLV(Flash Video);-y
:自动覆盖输出文件,如果存在同名文件则会被替换;rtmp://192.168.36.176:1935/live/test1
:指定输出的 URL 地址,以 RTMP 协议传输到 192.168.36.176 服务器的 1935 端口的 live 应用程序中的 test1 流
其中:
rtmp://ip:port/application/channelname
ip:本机 ip 地址
- port:RTMP 工作在 TCP 之上,默认使用端口 1935
- Application:参考 nginx.conf,这里是 live
- channelname:自定义
推流过程:
5、VLC 拉流
①、打开虚拟机端 VLC 客户端,媒体 -> 打开网络串流
,输入 rtmp://192.168.36.176:1935/live/test1
②、点击播放,可以看到拉流成功
③、查看 windows 端 wireshark,可以看到我们要的 RTMP 报文
其中 192.168.36.176
为主机 ip
地址,192.168.137.128
为虚拟机端 ip
地址,我们仅看 RTMP 报文即可。
我抓到的 RTMP 报文,这里存一下方便后面用到时直接拿来分析:RTMP报文
三、RTMP 协议详解
我们根据上面我们通过 wireshark 抓到的报文对 RTMP 协议进行学习。
1、前言
直播流:Video 和 Audio 编码器编码后是交织在一起
Timestamp:时间戳
Video(视频流) : -----------------------------------
(每一个 -
代表一个视频包)
Audio(音频流): ++++++++++++++++++++
(每一个 +
代表一个音频包)
Mp4 格式 : ###----++------+++------
,一个文件,只有一个头信息(在网络上传输如果文件头丢失那么中间的音视频流都无法解码)
Ts 格式:[#----+] [#----+] [#----+]
,每一个包都有头信息(适合在网络上传输)
RTMP:Message:[$----+] [$----+] [$----+]
;Chunk 块(消息太长,分成Chunk 块)。(RTMP 采用的传输格式是 flv)
Flv: [$----+] [$----+] [$----+]
;
2、总体介绍
RTMP 协议是应用层协议,是要靠底层可靠的传输层协议(通常是 TCP)来保证信息传输的可靠性的。在基于传输层协议的链接建立完成后,RTMP 协议也要客户端和服务器通过 “RTMP 握手” 来建立基于传输层链接之上的 RTMP Connection 链接,在 Connection 链接上会传输一些控制信息,如 SetChunkSize
,SetACKWindowSize
。
其中 CreateStream 命令会创建一个 Stream 链接,用于传输具体的音视频数据和控制这些信息传输
的命令信息。
RTMP 协议传输时会对数据(直播流,推本地视频文件)做自己的格式化(Message/Chunk),这
种格式的消息我们称之为 RTMP Message,而实际传输的时候为了更好地实现多路复用、分包和信息的公平性,发送端会把 Message 划分为带有 Message ID 的 Chunk,每个 Chunk 可能是一个单独的
Message,也可能是 Message 的一部分,在接收端会根据 chunk 中包含的 data 的长度,message id
和 message 的长度把 chunk 还原成完整的 Message,从而实现信息的收发。
3、握手
要建立一个有效的 RTMP Connection 链接,首先要 “RTMP 握手”
- 客户端要向服务器发送 C0,C1,C2(按序)三个 chunk
- 服务器向客户端发送 S0,S1,S2(按序)三个 chunk,然后才能进行有效的信息传输
RTMP 协议本身并没有规定这 6 个 Message 的具体传输顺序,但 RTMP 协议的实现者需要保证这
几点:
- 客户端要等收到 S1 之后才能发送 C2
- 客户端要等收到 S2 之后才能发送其他信息(控制信息和真实音视频等数据)
- 服务端要等到收到 C0 之后发送 S1
- 服务端必须等到收到 C1 之后才能发送 S2
- 服务端必须等到收到 C2 之后才能发送其他信息(控制信息和真实音视频等数据)
理论上来讲只要满足以上条件,如何安排 6 个 Message 的顺序都是可以的,但实际实现中为了在保
证握手的身份验证功能的基础上尽量减少通信的次数,一般的发送顺序是这样的,这一点可以通过
wireshark 抓 ffmpeg 推流包进行验证。
4、RTMP Chunk Stream
Chunk Stream 是对传输 RTMP Chunk 的流的逻辑上的抽象,客户端和服务器之间有关 RTMP 的
信息都在这个流上通信。这个流上的操作也是我们关注 RTMP 协议的重点。
[控制信息,音视频流信息]
①、message(消息)
这里的 Message 是指满足该协议格式的、可以切分成 Chunk 发送的消息,消息包含的字段如下:
Timestamp(时间戳)
:消息的时间戳(但不一定是当前时间),4 个字节;Length(长度)
:是指 Message Payload(消息负载)即音视频等信息的数据的长度,3 个字节;TypeId(类型 Id)
:消息的类型 Id,1 个字节;Message Stream ID(消息的流 ID)
:每个消息的唯一标识,划分成 Chunk 和还原 Chunk 为 Message 的时候都是根据这个 ID 来辨识是否是同一个消息的 Chunk 的,4 个字节,并且以小端格式(little-endian)存储。
②、Chunking(message 分块)
RTMP 在收发数据的时候并不是以 Message 为单位的,而是把 Message 拆分成 Chunk 发送,而
且必须在一个 Chunk 发送完成之后才能开始发送下一个 Chunk。每个 Chunk 中带有 MessageID 代表属于哪个 Message,接受端也会按照这个 id 来将 chunk 组装成 Message。
问:为什么 RTMP 要将 Message 拆分成不同的 Chunk 呢?
答:通过拆分,数据量较大的 Message 可以被拆分成较小的 “Message”,这样就可以避免优先级低的消息持续发送阻塞优先级高的数据,比如在视频的传输过程中,会包括视频帧,音频帧和 RTMP 控制信息,如果持续发送音频数据或者控制数据的话可能就会造成视频帧的阻塞,然后就会造成看视频时最烦人的卡顿现象。同时对于数据量较小的 Message,可以通过对 Chunk Header 的字段来压缩信息,从而减少信息的传输量
Chunk 的默认大小是 128 字节,在传输过程中,通过一个叫做 Set Chunk Size 的控制信息可以设置 Chunk 数据量的最大值,在发送端和接受端会各自维护一个 Chunk Size,可以分别设置这个值来改变自己这一方发送的 Chunk 的最大大小。
大一点的 Chunk 减少了计算每个 chunk 的时间从而减少了 CPU 的占用率,但是它会占用更多的时
间在发送上,尤其是在低带宽的网络情况下,很可能会阻塞后面更重要信息的传输。小一点的 Chunk 可以减少这种阻塞问题,但小的 Chunk 会引入过多额外的信息(Chunk 中的 Header),少量多次的传输也可能会造成发送的间断导致不能充分利用高带宽的优势,因此并不适合在高比特率的流中传输。
在实际发送时应对要发送的数据用不同的 Chunk Size 去尝试,通过抓包分析等手段得出合适的 Chunk 大小,并且在传输过程中可以根据当前的带宽信息和实际信息的大小动态调整 Chunk 的大小,从而尽量提高 CPU 的利用率并减少信息的阻塞机率。
③、chunk Format(块格式)
1) Basic Header(基本的头部信息)
包含了 chunk stream ID
(流通道 Id)和 chunk type
(chunk 的类型),chunk stream id 一般被简写为 CSID,用来唯一标识一个特定的流通道;chunk type 决定了后面 Message Header 的格式。Basic Header 的长度可能是 1,2,或 3 个字节,其中 chunk type 的长度是固定的(占 2 位,注意单位是位,bit),Basic Header 的长度取决于 CSID 的大小,在足够存储这两个字段的前提下最好用尽量少的字节从而减少由于引入 Header 增加的数据量。
RTMP 协议支持用户自定义[3,65599]之间的 CSID,0,1,2 由协议保留表示特殊信息。0 代表 Basic Header 总共要占用 2 个字节,CSID 在[64,319]之间,1 代表占用 3 个字节,CSID 在[64,65599]之间,2 代表该 chunk 是控制信息和一些命令信息。
chunk type 的长度固定为 2 位,因此 CSID 的长度是(6=8-2)、(14=16-2)、(22=24-2)中的一个。当 Basic Header 为 1 个字节时,CSID 占 6 位,6 位最多可以表示 64 个数,因此这种情况下 CSID 在[0,63]之间,其中用户可自定义的范围为[3,63];当 Basic Header 为 2 个字节时,CSID 占 14 位,此时协议将与 chunk type 所在字节的其他位都置为 0,剩下的一个字节来表示 CSID-64,这样共有 8 个二进制位来存储 CSID,8 位可以表示[0,255]共 256 个数,因此这种情况下 CSID 在[64,319],其中 319=255+64;当 Basic Header 为 3 个字节时,CSID 占 22 位,此时协议将[2,8]字节置为 1,余下的 16 个字节表示 CSID-64,这样共有 16 个位来存储 CSID,16 位可以表示[0,65535]共 65536 个数,因此这种情况下 CSID 在[64,65599],其中 65599=65535+64,需要注意的是,Basic Header 是采用小端存储的方式,越往后的字节数量级越高,因此通过这 3 个字节每一位的值来计算 CSID 时,应该是:<第三个字节的值>x256+<第二个字节的值>+64
可以看到 2 个字节和 3 个字节的 Basic Header 所能表示的 CSID 是有交集的[64,319],但实际实现时还是应该秉着最少字节的原则使用 2 个字节的表示方式来表示[64,319]的 CSID
RTMP协议详解及Wiresahrk抓包分析(二)https://developer.aliyun.com/article/1472345