音视频 RTMP协议分析

理解字节序

大小端模式
理解字节序

Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端;Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

  • 举一个例子,比如数字0x12 34 56 78在内存中的表示形式为:

    • 大端模式:
      低地址 -----------------> 高地址
      0x12 | 0x34 | 0x56 | 0x78
    • 小端模式:
      低地址 ------------------> 高地址
      0x78 | 0x56 | 0x34 | 0x12
      可见,大端模式和字符串的存储模式类似。
  • RTMP都是大端模式,所以发送数据,包头,交互消息都要填写大端模式的,但是只有streamID是小端模式

什么是RMTP

RTMP协议,Real Time Messaging Protocol(实时消息传输协议),是应用层协议,要靠底层可靠的传输层协议(TCP、UDP)来保证信息传输的可靠性的。

RTMP在传输层建立成功后,还要进行握手、建立链接RTMP Connection、建立流CreateStream、播放/发送四个步骤。

建立链接阶段去建立客户端和服务器之间的“网络链接”。
建立流阶段用于建立客户端和服务器之间的“网络流”。
播放/发送阶段用于传输音视频数据。

官方文档

图例

握手

客户端要向服务器发送C0,C1,C2(按序)三个chunk,服务器向客户端发送S0,S1,S2(按序)三个chunk,然后才能进行有效的信息传输。RTMP协议本身并没有规定这6个Message的具体传输顺序,但RTMP协议的实现者需要保证这几点:
客户端要等收到S1之后才能发送C2
客户端要等收到S2之后才能发送其他信息(控制信息和真实音视频等数据)
服务端要等到收到C0之后发送S1
服务端必须等到收到C1之后才能发送S2
服务端必须等到收到C2之后才能发送其他信息(控制信息和真实音视频等数据)
如果每次发送一个握手chunk的话握手顺序会是这样:
Pictorial Representation of Handshake

实际实现中为了在保证握手的身份验证功能的基础上尽量减少通信的次数,一般的发送顺序是这样的,这一点可以通过wireshark抓ffmpeg推流包进行验证:
|client|Server |
|---C0+C1---->|
|<--S0+S1+S2-- |
|---C2----> |

拉流端

Message flow in the play command

推流端

Message flow in publishing a video stream

RTMP名词定义

  • Payload:
    The data contained in a packet, for example audio samples or compressed video data. The payload format and interpretation are beyond the scope of this document.

  • Packet:
    A data packet consists of fixed header and payload data. Some underlying protocols may require an encapsulation of the packet to be defined.

  • Message stream:
    A logical channel of communication in which messages flow.

  • Chunk:
    A fragment of a message. The messages are broken into smaller parts and interleaved before they are sent over the network. The chunks ensure timestamp-ordered end-to-end delivery of all messages, across multiple streams.

  • Chunk stream:
    A logical channel of communication that allows flow of chunks in a particular direction. The chunk stream can travel from the client to the server and reverse.

  • Multiplexing:
    Process of making separate audio/video data into one coherent audio/video stream, making it possible to transmit several video and audio simultaneously.

  • Action Message Format (AMF):
    A compact binary format that is used to serialize ActionScript object graphs. AMF has two versions: AMF 0 [AMF0] and AMF 3 [AMF3].

握手协议详解

C0、S0格式

In C0, this field identifies the RTMP version requested by the client. In S0, this field identifies the RTMP version selected by the server. The version defined by this specification is 3.
占一个字节,表达version,一般是3。
C0 and S0 bits

C1、S1格式

C1、S1长度为1536个字节,4个字节时间、4个字节的0,1528个字节Random data任意数据。
C1 and S1 bits

C2、S2格式

C2、S2长度为1536个字节,4个字节时间Time、4个字节时间Time2(S1、C1读取的时间),1528字节的Random echo,包含上面的Random data。

C2 and S2 bits

RTMP Chunk Stream

  • RTMP协议传输时会对数据做自己的格式化,这种格式的消息我们称之为RTMP Message

  • 实际传输的时候为了更好地实现多路复用、分包和信息的公平性,发送端会把Message划分为带有Message ID的Chunk,每个Chunk可能是一个单独的Message,也可能是Message的一部分,在接受端会根据chunk中包含的data的长度,messageID和message的长度把chunk还原成完整的Message,从而实现信息的收发。

  • 通过拆分,数据量较大的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的利用率并减少信息的阻塞机率。

1. RTMP消息分块

每个Chunk由Chunk Header + Chunk Data组成
ChunkHeader又由Basic Header + Message Header + Extended Timestamp组成;
如下图所示:
Chunk Format

Chunk Basic Header,1~3个字节

  • This field encodes the chunk stream ID and the chunk type.
    Chunk type determines the format of the encoded message header.
    The length depends entirely on the chunk stream ID, which is a variable-length field.
    根据chunk stream ID来决定Chunk Basic Header长度,1字节长到3字节长

  • The protocol supports up to 65597 streams with IDs 3-65599. The IDs 0, 1, and 2 are reserved. Chunk Stream ID with value 2 is reserved for low-level protocol control messages and commands.
    RTMP协议只支持3-65599这么多的chunkID,共65597种流。 0、1、2保留,为其它协议的控制命令。

  • Value 0 indicates the 2 byte form and an ID in the range of 64-319 (the second byte + 64).
    Value 1 indicates the 3 byte form and an ID in the range of 64-65599 ((the third byte)*256 + the second byte + 64).
    Values in the range of 3-63 represent the complete stream ID.
    0表示2字节、1表示3字节、其它表示1字节。

  • Chunk stream IDs 2-63 can be encoded in the 1-byte version of this field.
    1字节

  • Chunk stream IDs 64-319 can be encoded in the 2-byte form of the header. ID is computed as (the second byte + 64,即2^8 + 64)
    2字节

  • Chunk stream IDs 64-65599 can be encoded in the 3-byte version of this field. ID is computed as ((the third byte)*256 + (the second byte) + 64,即2^16 +64).
    3字节

Chunk Message Header,0、3、7、11字节

  • This field encodes information about the message being sent (whether in whole or in part).
    The length can be determined by the “fmt” field in the chunk basic header.

  • fmt = 0,11个字节
    This type MUST be used at the start of a chunk stream,
    chunk stream的起始点。
    type 0

  • fmt = 1,7字节
    The message stream ID is not included; this chunk takes the same stream ID as the preceding chunk。
    没有streamID的chunk Data,和前一个chunk的streamID相同。
    type1

  • fmt =2,3个字节
    Neither the stream ID nor the message length is included; this chunk has the same stream ID and message length as the preceding chunk.
    和前一个的streamID、length均相同。
    type3

  • fmt = 3,0个字节
    When a single message is split into chunks, all chunks of a message except the first one SHOULD use this type.
    当一个消息被分成多个chunks,除了第一个以外,都使用该类型。

  • 注意: message type id 发送音视频数据的时候

    • 如果包头MessageTypeID为0x8或0x9,数据(chunk data)是flv的tag data(没有tag header),flv格式封装请见FLV格式解析
    • 也可以用新类型MessageTypeID为0x16,数据(chunk data)是一个完整flv的tag(tag header + tag data)
    • message stream id 采用小端存储
    • RTMP都是大端模式,所以发送数据,包头,交互消息都要填写大端模式的,但是只有streamID是小端模式

Extended Timestamp,0或4个字节

The Extended Timestamp field is used to encode timestamps or timestamp deltas that are greater than 16777215 (0xFFFFFF);
超过了三个字节长度,则使用该头部。

If the delta is greater than or equal to 16777215 (hexadecimal 0xFFFFFF), this field MUST be 16777215, indicating the presence of the Extended Timestamp field to encode the full 32 bit delta. Otherwise, this field SHOULD be the actual delta.
如果读到3个字节的Timestamp大于或等于0xFFFFFF,则需要重新读取4个字节的Timestamp,因为加上了Extended Timestamp。

Chunk Data

The payload of this chunk, up to the configured maximum chunk size.
该chunk真实的数据,是协议商定的值。
是通过 Set Chunk Size协议控制消息设置的值,默认值为128字节。

2. RTMP消息分类 RTMP Command Messages

均参考:深入理解rtmp(四)之协议实现分析

1. 协议控制消息

  • MessageType的范围1~7:
    1~2 用于chunk协议
    3~6 用于rtmp协议本身,协议控制消息必须要求

  • Message Stream ID=0 和 Chunk Stream ID=2

    • MessageType=1, Set Chunk Size 设置块的大小,通知对端用使用新的块大小,共4 bytes。默认大小是128字节
    • MessageType=2, Abort Message 取消消息,用于通知正在等待接收块以完成消息的对等端,丢弃一个块流中已经接收的部分并且取消对该消息的处理,共4 bytes。
    • MessageType=3, Acknowledgement 确认消息,客户端或服务端在接收到数量与窗口大小相等的字节后发送确认消息到对方。窗口大小是在没有接收到接收者发送的确认消息之前发送的字节数的最大值。服务端在建立连接之后发送窗口大小。本消息指定序列号。序列号,是到当前时间为止已经接收到的字节数。共4 bytes。
    • MessageType=4, User Control Message 用户控制消息,客户端或服务端发送本消息通知对方用户的控制事件。本消息承载事件类型和事件数据。消息数据的头两个字节用于标识事件类型。事件类型之后是事件数据。事件数据字段是可变长的。
    • MessageType=5, Window Acknowledgement Size 确认窗口大小,客户端或服务端发送本消息来通知对方发送确认消息的窗口大小,共4 bytes.
    • MessageType=6, Set Peer Bandwidth 设置对等端带宽,客户端或服务端发送本消息更新对等端的输出带宽。发送者可以在限制类型字段(1 bytes)把消息标记为硬(0),软(1),或者动态(2)。如果是硬限制对等端必须按提供的带宽发送数据。如果是软限制,对等端可以灵活决定带宽,发送端可以限制带宽?。如果是动态限制,带宽既可以是硬限制也可以是软限制。

2. 音频数据消息 Audio Message

MessageType=8, Audio message:
客户端或服务端发送本消息用于发送音频数据。消息类型 8 ,保留为音频消息

3. 视频数据消息 Video Message

MessageType=9, Video message:
客户端或服务端使用本消息向对方发送视频数据。消息类型值 9 ,保留为视频消息。

4. 元数据消息 Data Message

MessageType=15或18, Data message:
客户端或服务端通过本消息向对方发送元数据和用户数据。
元数据包括数据的创建时间、时长、主题等细节。
消息类型为 18 的用 AMF0 编码;消息类型为 15 的用AMF3 编码。

5. 共享对象消息 Shared Object Message

MessageType=16或19, Shared object message:
共享对象是跨多个客户端,实例同步的 FLASH 对象(名值对的集合)。

6. 命令消息 Command Message

MessageType=17或20, Command message:
命令消息都是用AMF编码的,AMF有两种,为AMF0和AMF3。
命令消息有命令名,传输ID,和命名对象组成。而命名对象是由一系列参数组成的。

命令消息的类型有:

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

代表服务端和客户端之间连接的更高层的对象。包含4个命令类型:

  • connect
    该命令是client先发送给server,意思是我要连接,能建立连接吗?server返回含“_result”或者“_error”命令名, 返回“_result”,表示server能提供服务,client可以进行下一步。“_error”,很明显server端不能提供服务。
  • call
    NetConnection 对象的调用方法在接收端运行远程过程调用。远程方法的名作为调用命令的参数。
  • close
  • createStream
    客户端发送本命令到服务端创建一个消息通讯的逻辑通道。音频,视频和元数据的发布是由创建流命令建立的流通道承载的。

RTMP握手之后先发送一个connect 命令消息,命令里面包含什么东西,协议中没有具体规定,实际通信中要指定一些编解码的信息,并以AMF格式发送。
服务器返回的是一个_result命令类型消息,这个消息的payload length一般不会大于128字节,但是在最新的nginx-rtmp中返回的消息长度会大于128字节。
消息的transactionID是用来标识command类型的消息的,服务器返回的_result消息可以通过 transactionID来区分是对哪个命令的回应,connect 命令发完之后还要发送其他命令消息,要保证他们的transactionID不相同。

2. NetStream Commands(流连接上的命令)

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

  • play
    播放
  • play2
    play2命令可以切换到不同的码率,而不用改变已经播放的内容的时间线。服务端对播放 2 命令可以请求的多个码率维护多个文件。
  • deleteStream
    当 NetStream 对象销毁的时候发送删除流命令。
  • closeStream
  • receiveAudio
    NetStream 对象发送接收音频消息通知服务端发送还是不发送音频到客户端。
  • receiveVideo
    NetStream 对象发送 receiveVideo 消息通知服务端是否发送视频到客户端。
  • publish
    推流准备工作的最后一步是 Publish Stream,即向服务器发一个publish命令消息,这个命令的message stream ID 就是上面 create stream 之后服务器返回的stream ID,发完这个命令一般不用等待服务器返回的回应,直接发送音视频类型的RTMP数据包即可。
    有些rtmp库还会发setMetaData消息,这个消息可以发也可以不发,里面包含了一些音视频meta data的信息,如视频的分辨率等等。
  • seek
    定位到视频或音频的某个位置,以毫秒为单位。 客户端发送搜寻命令在一个媒体文件中或播放列表中搜寻偏移。
  • pause
    客户端告知服务端停止或恢复播放, 客户端发送暂停命令告诉服务端暂停或开始一个命令。

客户端发送命令消息中releaseStream命令到服务器端
客户端发送命令消息中FCPublish命令到服务器端
客户端发送命令消息中的“创建流”(createStream)命令到服务器端。
服务器端接收到“创建流”命令后,发送命令消息中的“结果”(_result),通知客户端流的状态。
解析服务器返回的消息会得到一个stream ID, 这个ID也就是以后和服务器通信的 message stream ID, 一般返回的是1,不固定。

7. 聚合消息 Aggregate Message

MessageType=22, Aggregate message,:
聚合消息是含有一个消息列表的一种消息。消息类型值 22 ,保留用于聚合消息。

3. 接收命令消息反馈结果 ResponseCommand

通过块消息携带的数据,拼接成消息内容,通过AMF解读消息内容
RTMPResponseCommandType

抓包

通过抓包对比协议和源码,看的更清楚,也更能理解协议;
抓包
image.png

总结

本文梳理了RTMP协议相关格式,方便了对代码的理解。

参考:
吃透RTMP
RTMP推流及协议学习
深入理解rtmp(四)之协议实现分析
RTMP规范简单分析
官方文档
srs-librtmp

猜你喜欢

转载自blog.csdn.net/u014099894/article/details/107976783