RTMP协议详解及Wiresahrk抓包分析

2023-11-08 10:16:50 浏览数 (2)

前言

本文主要讲解 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 推流

推流命令:

代码语言:javascript复制
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 链接上会传输一些控制信息,如 SetChunkSizeSetACKWindowSize

其中 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

2) Message Header(消息头消息)

包含了要发送的实际信息(可能是完整的,也可能是一部分)的描述信息。Message Header 的格式和长度取决于 Basic Header 的 chunk type,共有 4 种不同的格式,由上面所提到的 Basic Header 中的 fmt 字段控制。以下按照字节数从多到少的顺序分别介绍这 4 种格式的 Message Header。

  • Type=0
    • type=0 时 Message Header 占用 11 个字节,其他三种能表示的数据它都能表示,但在 chunk stream 的开始的第一个 chunk 和头信息中的时间戳后退(即值与上一个 chunk 相比减小,通常在回退播放的时候会出现这种情况)的时候必须采用这种格式。
    • timestamp(时间戳):占用 3 个字节,因此它最多能表示到 16777215=0xFFFFFF=
    2^{24} -1

    ,当它的值 超过这个最大值时,这三个字节都置为 1,这样实际的 timestamp 会转存到 Extended Timestamp 字段中,接收端在判断 timestamp 字段 24 个位都为 1 时就会去 Extended timestamp 中解析实际的时间戳。

    • message length(消息数据的长度) :占用 3 个字节,表示实际发送的消息的数据如音频帧、视频帧等数据的长度,单位是字节。注意这里是 Message 的长度,也就是 chunk 属于的 Message 的总数据长度 ,而不是 chunk 本身 Data 的数据的长度。
    • message type id(消息的类型 id):占用 1 个字节,表示实际发送的数据的类型,如 8 代表音频数据、9 代表视频数据。
    • msg stream id(消息的流 id) :占用 4 个字节,表示 该 chunk 所在的流的 ID(注:我还是不甚明白这个 ID 到底是指什么 stream ID,既然 basic header 里已经有 chunk stream id 了,为什么这又冒出个 msg stream id,下面提到了“省去了表示 msg stream id 的 4 个字节,表示此 chunk 和上一次发的 chunk 所在的流相同”,是针对流链接说的,那么意思就是说 msg stream id 和 chunk stream id 指的是一个东西咯?) ,和 Basic Header 的 CSID 一样,它采用小端存储的方式。
  • Type=1
    • type=1 时 Message Header 占用 7 个字节,省去了表示 msg stream id 的 4 个字节,表示此 chunk 和上一次发的 chunk 所在的流相同,如果在发送端只和对端有一个流链接的时候可以尽量去采取这种格式。
    • timestamp delta:占用 3 个字节,注意这里和 type=0 时不同,存储的是和上一个 chunk 的时间差。类似上面提到的 timestamp,当它的值超过 3 个字节所能表示的最大值时,三个字节都置为 1,实际的时间戳差值就会转存到 Extended Timestamp 字段中,接受端在判断 timestamp delta 字段 24 个位都为 1 时就会去 Extended timestamp 中解析时机的与上次时间戳的差值。
  • Type=2
    • type=2 时 Message Header 占用 3 个字节,相对于 type=1 格式又省去了表示消息长度的 3 个字节和表示消息类型的 1 个字节,表示此 chunk 和上一次发送的 chunk 所在的流、消息的长度和消息的类型都相同。余下的这三个字节表示 timestamp delta,使用同 type=1。
  • Type=3
    • 0 字节!!!好吧,它表示这个 chunk 的 Message Header 和上一个是完全相同的,自然就不用再传输一遍了。当它跟在 Type=0 的 chunk 后面时,表示和前一个 chunk 的时间戳都是相同的。什么时候连时间戳都相同呢?就是一个 Message 拆分成了多个 chunk,这个 chunk 和上一个 chunk 同属于一个 Message。而当它跟在 Type=1 或者 Type=2 的 chunk 后面时,表示和前一个 chunk 的时间戳的差是相同的。比如第一个 chunk 的 Type=0,timestamp=100,第二个 chunk 的 Type=2,timestamp delta=20,表示时间戳为 100 20=120,第三个 chunk 的 Type=3,表示 timestamp delta=20,时间戳为 120 20=140。
3) Extended Timestamp(扩展时间戳)

上面我们提到在 chunk 中会有时间戳 timestamp 和时间戳差 timestamp delta,并且它们不会同时存在,只有这两者之一大于 3 个字节能表示的最大数值 0xFFFFFF=16777215 时,才会用这个字段来表示真正的时间戳,否则这个字段为 0。扩展时间戳占 4 个字节,能表示的最大数值就是 0xFFFFFFFF=4294967295。

当扩展时间戳启用时,timestamp 字段或者 timestamp delta 要全置为 1,表示应该去扩展时间戳字段来提取真正的时间戳或者时间戳差。注意扩展时间戳存储的是完整值,而不是减去时间戳或者时间戳差的值

4) chunk data(块数据)

用户层面上真正想要发送的与协议无关的数据,长度在 (0,chunkSize] 之间。

5) chunk 表
  • 示例一
    • 首先包含第一个 Message 的 chunk 的 Chunk Type 为 0,因为它没有前面可参考的 chunk,timestamp 为 1000,表示时间戳。type 为 0 的(注:应该是 message header)header 占用 11 个字节,假定 chunkstreamId 为 3<127,因此 Basic Header 占用 1 个字节,再加上 Data 的 32 个字节,因此第一个 chunk 共 44=11 1 32 个字节
    • 第二个 chunk 和第一个 chunk 的 CSID,TypeId,Data 的长度都相同,因此采用 Chunk Type=2,timestamp delta=1020-1000=20,因此第二个 chunk 占用 36=3 1 32 个字节。
    • 第三个 chunk 和第二个 chunk 的 CSID,TypeId,Data 的长度和时间戳差都相同,因此采用 Chunk Type =3 省去全部 Message Header 的信息,占用 33=1 32 个字节。
    • 第四个 chunk 和第三个 chunk 情况相同,也占用 33=1 32 个字节。
  • 示例二
    • 注意到 Data 的 Length=307 > 128,因此这个 Message 要切分成几个 chunk 发送,第一个 chunk 的 Type=0,Timestamp=1000,承担 128 个字节(注:Chunk 的默认大小是 128 字节)的 Data,因此共占用 140=11 1 128 个字节。
    • 第二个 chunk 也要发送 128 个字节,其他字段也同第一个 chunk,因此采用 Chunk Type=3,此时时间戳也为 1000,共占用 129=1 128 个字节。
    • 第三个 chunk 要发送的 Data 的长度为 307-128-128=51 个字节,还是采用 Type=3,共占用 1 51=52 个字节
④、协议控制消息(Protocal Control Message)

在 RTMP 的 chunk 流会用一些特殊的值来代表协议的控制消息,它们的 Message Stream ID 必须为 0(代表控制流信息),CSID 必须为 2,Message Type ID(注:message type ID 在 message header 里面)可以为 1,2,3,5,6,具体代表的消息会在下面依次说明。控制消息的接收端会忽略掉 chunk 中的时间戳,收到后立即生效。(注:我所理解的协议控制消息应该是放在 chunk data 里的)

  • Set Chunk Size(Message Type ID=1):设置 chunk 中 Data 字段所能承载的最大字节数,默认为 128B,通信过程中可以通过发送该消息来设置 chunk Size 的大小(不得小于 128B),而且通信双方会各自维护一个 chunkSize,两端的 chunkSize 是独立的。比如当 A 想向 B 发送一个 200B 的 Message,但默认的 chunkSize 是 128B,因此就要将该消息拆分为 Data 分别为 128B 和 72B 的两个 chunk 发送,如果此时先发送一个设置 chunkSize 为 256B 的消息,再发送 Data 为 200B 的 chunk,本地不再划分 Message,B 接受到 Set Chunk Size 的协议控制消息时会调整的接受的 chunk 的 Data的大小,也不用再将两个 chunk 组成为一个 Message。
    • 以下为代表 Set Chunk Size 消息的 chunk 的 Data: 其中第一位必须为 0,chunk Size 占 31 个位,最大可代表 2147483647=0x7FFFFFFF=
    2^{31}-1

    ,但实际上所有大于 16777215=0xFFFFFF 的值都用不上,因为 chunk size 不能大于 Message 的长度,表示 Message 的长度字段是用 3 个字节表示的,最大只能为 0xFFFFFF。

  • Abort Message(Message Type ID=2):当一个 Message 被切分为多个 chunk,接受端只接收到了部分 chunk 时,(注:这里缺少主语,我所理解的是 “发送端发送该控制消息”)发送该控制消息表示发送端不再传输同 Message 的 chunk,接受端接收到这个消息后要丢弃这些不完整的 chunk。Data 数据中只需要一个 CSID,表示丢弃该 CSID 的所有已接收到的 chunk。
  • Acknowledgement(Message Type ID=3):当收到对端的消息大小等于窗口大小(Window Size)时接受端要回馈一个 ACK 给发送端告知对方可以继续发送数据。窗口大小就是指收到接受端返回的 ACK 前最多可以发送的字节数量,返回的 ACK 中会带有从发送上一个 ACK 后接收到的字节数。(注:这里其实我没怎么看懂。大概是对窗口大小的一个确认信息吧,暂不深究。)
  • Window Acknowledgement Size(Message Type ID=5):发送端在接收到接受端返回的两个 ACK 间最多可以发送的字节数
  • Set Peer Bandwidth(Message Type ID=6):限制对端的输出带宽。接受端接收到该消息后会通过设置消息中的 Window ACK Size 来限制已发送但未接受到反馈的消息的大小来限制发送端的发送带宽。如果消息中的 Window ACK Size 与上一次发送给发送端的 size 不同的话要回馈一个 Window Acknowledgement Size 的控制消息。
  • Hard(Limit Type=0):接受端应该将 Window Ack Size 设置为消息中的值
  • Soft(Limit Type=1):接受端可以讲 Window Ack Size 设为消息中的值,也可以保存原来的值(前提是原来的 Size 小与该控制消息中的 Window Ack Size)
  • Dynamic(Limit Type=2):如果上次的 Set Peer Bandwidth 消息中的 Limit Type 为 0,本次也按 Hard 处理,否则忽略本消息,不去设置 Window Ack Size。

5、RTMP message

不同类型的 RTMP message

  • Command Message(命令消息,Message Type ID=17 或 20):表示在客户端和服务器间传递的在对端执行某些操作的命令消息(注:简单来说就是通知对方要开始干啥的命令),如 connect 表示连接对端,对端如果同意连接的话会记录发送端信息并返回连接成功消息,publish 表示开始向对方推流,接受端接到命令后准备好接受对端发送的流信息,后面会对比较常见的 Command Message 具体介绍。当信息使用 AMF0 编码时,Message Type ID=20,AMF3 编码时 Message Type ID=17。
  • Data Message(数据消息,Message Type ID=15 或 18):传递一些元数据(MetaData,比如视频名,分辨率等等)或者用户自定义的一些消息。当信息使用 AMF0 编码时,Message Type ID=18,AMF3 编码时 Message Type ID=15。
  • Shared Object Message(共享消息,Message Type ID=16 或 19):表示一个 Flash 类型的对象,由键值对的集合组成,用于多客户端,多实例时使用。当信息使用 AMF0 编码时,Message Type ID=19,AMF3 编码时 Message Type ID=16。
  • Audio Message(音频信息,Message Type ID=8):音频数据。
  • Video Message(视频信息,Message Type ID=9):视频数据。
  • Aggregate Message (聚集信息,Message Type ID=22):多个 RTMP 子消息的集合
  • User Control Message Events(用户控制消息,Message Type ID=4):告知对方执行该信息中包含的用户控制事件,比如 Stream Begin 事件告知对方流信息开始传输。和前面提到的协议控制信息(Protocol Control Message)不同,这是在 RTMP 协议层的,而不是在 RTMP chunk 流协议层的,这个很容易弄混。该信息在 chunk 流中发送时,Message Stream ID=0,Chunk Stream Id=2,Message TypeId=4。
①、Command Message(命令消息,Message Type ID=17 或 20)

发送端发送时会带有命令的名字,如 connect,TransactionID 表示此次命令的标识,Command Object 表示相关参数。接受端收到命令后,会返回以下三种消息中的一种:_result 消息表示接受该命令,对端可以继续往下执行流程,_error 消息代表拒绝该命令要执行的操作,method name 消息代表要在之前命令的发送端执行的函数名称。这三种回应的消息都要带有收到的命令消息中的 TransactionId 来表示本次的回应作用于哪个命令。

可以认为发送命令消息的对象有两种,一种是 NetConnection,表示双端的上层连接,一种是 NetStream,表示流信息的传输通道,控制流信息的状态,如 Play 播放流,Pause 暂停。

1) NetConnection Commands(连接层的命令)

用来管理双端之间的连接状态,同时也提供了异步远程方法调用(RPC)在对端执行某方法,以下是常见的连接层的命令。

  • connect:用于客户端向服务器发送连接请求,消息的结构如下

消息的回应有两种,_result 表示接受连接,_error 表示连接失败。

  • Call:用于在对端执行某函数,即常说的 RPC:远程进程调用,消息的结构如下:

如果消息中的 TransactionID 不为 0 的话,对端需要对该命令做出响应,响应的消息结构如下:

  • Create Stream:创建传递具体信息的通道,从而可以在这个流中传递具体信息,传输信息单元为 Chunk
2) NetStream Commands(流连接上的命令)

Netstream 建立在 NetConnection 之上,通过 NetConnection 的 createStream 命令创建,用于传输具体的音频、视频等信息。在传输层协议之上只能连接一个 NetConnection,但一个 NetConnection 可以建立多个 NetStream 来建立不同的流通道传输数据。以下会列出一些常用的 NetStream Commands,服务端收到命令后会通过 onStatus 的命令来响应客户端,表示当前 NetStream 的状态。

  • onStatus 命令的消息结构如下
  • play(播放): 由客户端向服务器发起请求从服务器端接受数据(如果传输的信息是视频的话就是请求开始播流),可以多次调用,这样本地就会形成一组数据流的接收者。注意其中有一个 reset 字段,表示是覆盖之前的播流(设为 true)还是重新开始一路播放(设为 false)。
  • play2(播放): 和上面的 play 命令不同的是,play2 命令可以将当前正在播放的流切换到同样数据但不同比特率的流上,服务器端会维护多种比特率的文件来供客户端使用 play2 命令来切换。
  • deleteStream(删除流):用于客户端告知服务器端本地的某个流对象已被删除,不需要再传输此路流。
  • receiveAudio(接收音频):通知服务器端该客户端是否要发送音频,receiveAudio 命令结构如下:
  • receiveVideo(接收视频):通知服务器端该客户端是否要发送视频,receiveVideo 命令结构如下:
  • publish(推送数据):由客户端向服务器发起请求推流到服务器。publish 命令结构如下:
  • seek(定位流的位置): 定位到视频或音频的某个位置,以毫秒为单位。seek 命令的结构如下:
  • pause(暂停):客户端告知服务端停止或恢复播放。pause 命令的结构如下:

如果 Pause 为 true 即表示客户端请求暂停的话,服务端暂停对应的流会返回 NetStream.Pause.Notify 的 onStatus 命令来告知客户端当前流处于暂停的状态,当 Pause 为 false 时,服务端会返回 NetStream.Unpause.Notify 的命令来告知客户端当前流恢复。如果服务端对该命令响应失败,返回_error 信息。

四、RTMP 流媒体播放过程

1、简介

下面分析打开一个 RTMP 流媒体到视音频数据开始播放的全过程。

RTMP 协议规定, 播放一个流媒体有两个前提步骤:

  • 第一步, 建立一个网络连接( NetConnection);
  • 第二步, 建立一个网络流( NetStream)。

其中,网络连接代表服务器端应用程序和客户端之间基础的连通关系。网络流代表了发送多媒体数据的通道。服务器和客户端之间只能建立一个网络连接,但是基于该连接可以创建很多网络流。他们的关系如图所示:

播放一个 RTMP 协议的流媒体需要经过以下几个步骤:握手,建立连接,建立流,播放。RTMP 连接都是以握手作为开始的。建立连接阶段用于建立客户端与服务器之间的“ 网络连接” ;建立流阶段用于建立客户端与服务器之间的“ 网络流”;播放阶段用于传输视音频数据。

我们分析上面 wireshark 抓到的报文。

2、握手(HandShake)

rtmp 连接从握手开始。它包含三个固定大小的块。客户端发送的三个块命名为 C0,C1,C2;服务端发送的三个块命名为 S0,S1,S2。

握手序列如下:

  • 握手开始于客户端发送 C0、 C1 块。 服务器收到 C0 或 C1 后发送 S0 和 S1。
  • 当客户端收齐 S0 和 S1 后, 开始发送 C2。 当服务器收齐 C0 和 C1 后, 开始发送 S2。
  • 当客户端和服务器分别收到 S2 和 C2 后, 握手完成。

握手示意图如下图所示:

结合上面理论对比我们抓到的报文:

①、客户端向服务器发送握手 C0 C1

②、服务器向客户端回应握手 S0 S1 S2

③、客户端向服务器发送握手 C2

抓包总结:

  • 第一步:客户端发送 C0 C1
  • 第二步:服务端发送 S0 S1 S2
  • 第三步:客户端发送 C2

疑问: C2 和 S2, 到底哪个先发送? ? ? 【协议没有具体规定】

3、建立网络连接(NetConnection)

包括以下步骤:

  • ①、客户端发起连接请求
  • ②、服务器设置客户端的应答窗口大小
  • ③、服务器设置客户端的发送带宽大小
  • ④、服务器设置客户端的接收块大小
  • ⑤、服务器响应连接结果
  • ⑥、客户端设置服务器的接收块大小
①、客户端发起连接请求

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

②、服务器设置客户端的应答窗口大小、发送带宽大小、接收块大小

服务器接收到连接命令消息后,发送确认窗口大小、发送带宽大小、接收块大小(Window Acknowledgement Size)协议消息到客户端,同时连接到连接命令中提到的应用程序。

③、服务器响应创建流结果

服务器发送命令消息中的 “结果” (_result), 通知客户端连接的状态

④、客户端设置服务器的接收块大小

客户端发送确认窗口大小(Window Acknowledgement Size)协议消息到服务器端。

4、建立网络流(NetStream)

包括以下步骤:

  • 客户端发送命令消息中的“创建流” (createStream) 命令到服务器端。
  • 服务器端接收到“创建流” 命令后, 发送命令消息中的“结果” (_result), 通知客户端流的状态
  • 客户端向服务器获取指定流的长度
①、客户端发起创建流请求
②、服务器响应创建流结果
③、客户端向服务器获取指定流的长度

5、播放(Play)

  • 客户端发送命令消息中的“播放” (play) 命令到服务器。
  • 服务器发送用户控制消息中的 “stream begin” ,告知客户端流 ID
  • 服务器发送客户端要播放的音频和视频数据
①、客户端发送播放请求
②、服务器发送 stream begin
③、服务器发送音视频数据给客户端

0 人点赞