本文主要目的是通过学习RTMP协议,帮助读者理解SRS4.0源代码中相关协议的处理逻辑。
一、RTMP协议简介
RTMP协议是Real Time Message Protocol(实时消息传输协议)的缩写,它是由Adobe公司提出的⼀种应⽤层的协议。
RTMP协议⽤于解决音视频流传输过程中数据复⽤(Multiplexing,即一条RTMP流中同时包括音频、视频、字幕、控制命令等多种数据)和分包(packetizing)的问题。
随着互联网宽带基础设施更加完善,视频直播等领域逐渐活跃起来,RTMP作为业内⼴泛使⽤的协议也重新被相关开发者重视起来。
RTMP报文向上封装用户的音视频的帧数据和用户命令,向下依赖TCP协议保证音视频数据的可靠传输。
虽然,TCP协议可以保证上层用户报文的可靠传输,即接收的数据不丢包不乱序,但RTMP协议设计者仍然对自身做了比较冗余的过度设计,导致RTMP协议某些部分复杂且神秘,所以,不同厂商之间的协议实现存在兼容性问题。
二、RTMP协议基础1、RTMP Message格式概述
RTMP协议面向上层用户定义了一种Message数据结构用于封装音视频的帧数据和协议控制命令,具体格式如下:
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | Message Type | Payload length | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |Timestamp | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |Stream ID | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | Message Payload | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Message Type(1 Byte):消息类型ID,主要分为三类:协议控制消息、音视频数据帧、命令消息。
Payload Length(3-Bytes):报文净荷的字节数。以大端格式保存。
timestamp(4-Bytes):当前消息的时间戳。以大端格式保存
StreamID(4-Bytes):消息流ID,其实从抓包看,一般只有0、1两种值,0:信令,1:音视频数据
Message Payload:Message报文净荷
2、RTMP Chunk格式概述RTMP协议的一个Message数据包可能会非常大(尤其是传输一个视频帧时,最大可能16M字节)。
为了保证TCP高效传输(超大数据包在网络传输中受MTU限制会自动切片,超大数据包在网络传输过程中丢失一个数据片,都需要重传整个报文,导致传输效率降低)。
所以,RTMP协议的设计者决定在RTMP协议内部对Message数据包分片,这里分片的单位被称为Chunk(数据块)。
Chunk报文长度默认128字节,同时用户也可以修改本端发送的Chunk报文长度,并通过RTMP协议通知接收端。所以,网络中实际传输的RTMP报文,总是如下Chunk封装格式:
1~3字节 |<-Chunk Header->| 0/3/7/11字节 0或4字节 ---------------- ---------------- ------------------- ----------- | Basic header | Msg Header | Extended Timestamp|Chunk Data | ---------------- ---------------- ------------------- ----------- 注:因为Msg Header里有一个3字节的timestamp,当timestamp 被设置为0xffffff时,才会出现Extended Timestamp字段
Basic Header也被称为Chunk header,它的实际格式一共有3种(1~3个字节),目的是为了满足不同的长度的CSID(Chunk Stream ID),所以,在足够存储CSID字段的前提下应该用尽量少的字节格式从而减少由于引入Basic Header而增加的数据量。
----------------------- ------------------ | format [2个bit] | CSID [6bit] | 结构1:1字节 ----------------------- ------------------ ----------------------- ------------------ -------------------- | format [2个bit] | 0 [6bit] | 扩展 CSID [8bit] | 结构2:2字节 ----------------------- ------------------ -------------------- ----------------------- ------------------ -------------------------------- | format [2个bit] | 1 [6bit] | 扩展 CSID [16bit] | 结构3:3字节 ----------------------- ------------------ --------------------------------
实际上为了处理方便,RTMP协议软件在具体实现时总是先读取一个字节的Basic Header,并根据前面2bit的format信息和后面6bit的CSID信息,判断报文的实际结构。
1、首先判断第一个字节的低6bit的CSID
当低6bit的CSID为0时,Basic Header占用2个字节,扩展CSID在[64 0,255 64=319]之间。
当低6bit的CSID为1时,Basic Header占用3个字节,扩展CSID在[64 0,65535 64=65599]之间。
当低6bit的CSID为2~63时,Basic Header占用1个字节,这种情况适用于绝大多数的RTMP Chunk报文。
CSID=2时,Message Type为1,2,3,5,6对应Chunk层控制协议,4对应用户控制命令
CSID=3~8,用于connect、createStream、releaseStream 、publish、metaData、音视频数据
(不同厂家之间,如FFMPEG、OBS在这里使用的CSID差异很大)
显然,接下来更多的CSID已经意义不大了,毕竟有Message Type也够了。当然,用户可以使用更大的CSID做一些私有协议的扩展。
实际上,真实环境中需要的CSID并不多,所以一般情况下,Basic Header长度总是一个字节。
2、接下来通过2bit的format字段,判断后面Msg Header格式
format =00,Msg Header占用11个字节,这种结构最浪费,一般用于流开始发送的第一个chunk报文,且只有这种情况下,报文中的timestamp才是一个绝对时间, 后续chunk报文的Msg Header中要么是没有timestamp,有timestamp也只是相对前一个chunk报文的时间增量。
|<--Basic Header->|<------------------------Msg Header----------------------->| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | format | CSID | timestamp | message length | message type | msg stream id || 00 | | | | | | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2 bits 6 bits 3 bytes 3 bytes 1 bytes 4 bytes
format =01,Msg Header占用7个字节,省去了4个字节的msg stream id,表示当前chunk和上一次发送的chunk所在的流相同
|<--Basic Header->|<---------------Msg Header---------------->| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | format | CSID | timestamp | message length | message type || 01 | | | | | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2 bits 6 bits 3 bytes 3 bytes 1 bytes
format =10,Msg Header占用3个字节,相对于format=01格式又省去了表示消息长度的3个字节和表示消息类型的1个字节。
|<--Basic Header->|<--Msg Header--->| - - - - - - - - - - - - - - - - - - | format | CSID | timestamp || 10 | | | - - - - - - - - - - - - - - - - - - 2 bits 6 bits 3 bytes
format =11,Msg Header占用0个字节,它表示这个chunk的Msg Header和上一个chunk是完全相同的。
|<--Basic Header->| - - - - - - - - - | format | CSID || 11 | | - - - - - - - - - 2 bits 6 bits
3、总结
所以,实际应用过程中,RTMP协议的整体工作逻辑如下:
1、软硬件编码器以帧为单位生成音视频数据流
2、RTMP协议先将每一帧数据封装成一个Message数据包
3、再根据本端设置的trunk size(默认128字节)对Message数据包分片封装成trunk数据包,最终通过TCP协议实现网络传输。
4、反之,RTMP协议将接收的TCP数据包以chunk为单位进行组装,将多个chunk包还原成一个Message数据包,最终上层协议处理Message数据包。
领取音视频开发资料包:音视频流媒体高级开发FFmpegWebRTCRTMPRTSPHLSRTP播放器
企鵝群994289133领取资料
企鵝群994289133领取资料
三、RTMP协议报文根据前面的学习,可知RTMP协议向上的处理逻辑是总是基于Message报文,向下的处理逻辑则基于Chunk报文,所以,接下来首先分析用户层Message报文的各种含义。
1. RTMP协议之Message报文详细说明
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | Message Type | Payload length | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |Timestamp | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | Stream ID | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Message Type的范围是[0, 255],其中1、2、3、5、6用于Chunk层协议控制,4用于用户层控制命令;
Message Type =1 发送端向对端通知本端后续发送报文的chunk报文大小
Message Type =2 中断消息,发送端向对端通知之前收到的指定CSID的chunk报文需要全部丢弃
Message Type =3 ACK消息,接收端收到的消息大小等于ACK窗口大小时,接收端必须返回一个ACK
Message Type =5 客户端或服务端发送本消息来设置对方的ACK窗口大小
Message Type =6 限制对端的输出带宽
Message Type =4 客户端或服务端发送本消息通知对方用户层的控制命令。
Message Type =8 音频数据
Message Type =9 视频数据
Message Type =15(AMF3编码)/18(AMF0编码) 音视频元数据(Metadata)
Message Type =16(AMF3编码)/19(AMF0编码) 共享对象消息,键值对集合
Message Type =17(AMF3编码)/20(AMF0编码) 协议命令,主要分:
NetConnection(网络连接命令)
connect命令:客户端发给服务器端,请求建立连接
call命令:请求对端执行某种(函数)功能
close命令:
createStream命令:客户端通知服务端建立一条音视频数据通道
releaseStream命令:客户端通知服务端释放音视频数据通道资源
NetStream(网络流命令):
play:客户端向服务器发起请求,请求服务器端发送数据
play2:此命令支持将当前正在播放的流切换到同样数据但不同⽐特率的流上
deleteStream:客户端告知服务器端本地的某个流对象已被删除,不需要再传输此路流。
receiveAudio:通知服务器端本客户端是否要发送⾳频
receiveVideo:通知服务器端本客户端是否要发送视频
publish:由客户端向服务器发起请求推流到服务器。
seek:定位到视频或⾳频的某个位置,以毫秒为单位。
pause:客户端告知服务端暂停或恢复播放。
Message Type =22 聚合消息(是一种含有一个消息列表的消息)
2. RTMP协议之Chunk报文详细说明
通过前面的学习,我们知道所有的Message报文最终都需要被分片封装为Chunk报文,再进行网络传输。所以,接下来需要了解一下上述各种类型的Message报文对应的Chunk格式。
Message Type =1;Set Chunk Size消息:发送端向对端通知本端后续发送报文的chunk报文大小
|<--Basic Header->|<------------------------Msg Header----------------------->| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | format | CSID | timestamp | message length | message type | msg stream id |Set chunk size | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2 bits 6 bits 3 bytes 3 bytes 1 bytes 4 bytes 4 bytes 00 000010 0 4 0000,0001 0
Message Type =2;Abort Message消息:发送端向对端通知之前收到的指定CSID的chunk报文需要全部丢弃
|<--Basic Header->|<------------------------Msg Header----------------------->| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | format | CSID | timestamp | message length | message type | msg stream id |Chunk Stream ID| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2 bits 6 bits 3 bytes 3 bytes 1 bytes 4 bytes 4 bytes 00 000010 0 4 0000,0010 0
Message Type =3;Acknowledgement确认消息:接收端收到的消息大小等于ACK窗口大小时,接收端必须返回一个ACK
|<--Basic Header->|<------------------------Msg Header----------------------->| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | format | CSID | timestamp | message length | message type | msg stream id |Sequence Number| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2 bits 6 bits 3 bytes 3 bytes 1 bytes 4 bytes 4 bytes 00 000010 0 4 0000,0011 0其中Sequence Number是到当前时间为止已经接收到的字节数
Message Type =4;用户控制消息:客户端或服务端发送本消息通知对方用户层的控制命令
|<--Basic Header->|<------------------------Msg Header----------------------->| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | format | CSID | timestamp | message length | message type | msg stream id |Event Type |Event Data | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2 bits 6 bits 3 bytes 3 bytes 1 bytes 4 bytes 2 bytes 2 bytes00 000010 0 4 0000,0100 0 事件类型 事件数据
Message Type =5;ACK窗口大小:客户端或服务端发送本消息来设置对方的ACK窗口大小
|<--Basic Header->|<------------------------Msg Header----------------------->| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | format | CSID | timestamp | message length | message type | msg stream id |Acknowledgement Window size| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2 bits 6 bits 3 bytes 3 bytes 1 bytes 4 bytes 4 bytes00 000010 0 4 0000,0101 0
Message Type =6;设置对端输出带宽
|<--Basic Header->|<------------------------Msg Header----------------------->| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | format | CSID | timestamp | message length | message type | msg stream id |Acknowledgement Window size| Limit type| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2 bits 6 bits 3 bytes 3 bytes 1 bytes 4 byte4 bytes 1 bytes 00 000010 0 5 0000,0110 0 限制类型(0硬,1软,2动态)
RTMP客户端与服务器之间连接建立后,开始推拉流时,总是先传输和音视频编码信息相关的MetaData(元数据),再传输音视频数据帧:
1)音视频数据总是采用FLV封装格式:tagHeader(1字节) tagData(编码帧)
2)对于H.264视频,第一个视频帧必须是SPS和PPS,后面才是I帧和P帧。
3)对于AAC音频,第一个音频帧必须是AAC sequence header,后面才是AAC编码音频数据。
所谓元数据MetaData,其实就是一些用字符串描述的音视频编码器ID和宽高信息,个人觉得没啥鸟用,后面会通过wireshark抓包,加深对MetaData数据包的理解。
所以,接下来以H264和ACC为例初步了解一下音视频数据在RTMP中的Chunk封装格式:
Message Type =8;Audio message
协议层 封装层 |RTMP Chunk Header | FLV AudioTagHeader | FLV AudioTagBody | FLV AudioTagHeader |SoundFormat | SoundRate | SoundSize | SoundType | OR |AACPacketType| 4bits 2bits 1bit 1bit 编码格式 采样率 采样精度8或16位 声道数(单/双)
如果音频格式是AAC(0x0A),上面的AudioTagHeader中会多出1个字节的AACPacketType描述后续的ACC包类型:
AACPacketType = 0x00 表示后续数据是AAC sequence header,
AACPacketType = 0x01 表示后续数据是AAC音频数据
最终,AAC sequence header由AudioSpecificConfig定义,简化的AudioSpecificConfig信息包括2字节如下:
|AAC Profile 5bits | 采样率 4bits | 声道数 4bits | 其他 3bits |对于RTMP推拉流,在发送第一个音频数据包前必须要发送这个AAC sequence header包。
Message Type =9;Video message
协议层 封装层|RTMP Chunk Header | FLV VideoTagHeader | FLV VideoTagBody | FLV VideoTagHeader |Frame Type | CodecID | |AVCPacketType | |CompositionTime | 4 bits 4 bits 1 byte3 byte 视频帧类型 编码器ID
Frame Type = 1表示H264的关键帧,包括IDR;Frame Type=2表示H264的非关键帧
codecID = 7表示AVC
当采用AVC编码(即H264)时,会增加1个字节的AVCPacketType字段(描述后续的AVC包类型)和3个字节的CompositionTime :
AVCPacketType=0时,表示后续数据AVC sequence header,此时3字节CompositionTime内容为0
AVCPacketType=1时,表示后续数据AVC NALU
AVCPacketType=2时,表示后续数据AVC end of sequence (一般不需要)
四、RTMP协议流程前面学习了RTMP报文格式的基本概念,接下来学习RTMP客户端与服务器之间与推拉流相关的协议交换过程。
1. RTMP推流端协议交互流程(FFMPEG SRS)
2. RTMP拉流端协议交互流程(VLC SRS)
1、客户端向服务端发送C0 C1(握手)请求
2、服务端向客户端发送S0 S1 S2(握手)响应
3、客户端向服务端发送C2(握手)响应
1、客户端向服务端发送connect请求
2、服务端发送设置ACK窗口大小
3、服务端发送设置对端输出
4、服务端发送set chunk size
5、服务端响应connect success
1、客户端向服务端发送set chunk size请求
2、客户端向服务端发送releaseStream请求
3、客户端向服务端发送FCPublish请求
4、客户端向服务端发送createStream请求
注意:上述报文中只有第一个chunk报文是fmt=00的封装格式,剩下的都是fmt=01的封装格式,即剩下的chunk报文都省略了Stream ID字段
6. 推拉流阶段的协议报文1、客户端向服务端发送publish请求
注意:此时chunk报文中的Stream ID字段值是1,之前报文的Stream ID字段值是0,总之,Stream ID字段其实并不是很有重要
2、服务端向客户端响应onFCPublish和onStatus报文
3、客户端向服务端发送MetaData(元数据)报文
如上,所谓MetaData元数据好像就是一堆字符串描述信息。
4、客户端向服务端发送H264的SPS PPS
5、客户端向服务端发送ACC sequence header
6、客户端向服务端发送视频数据
7、客户端向服务端发送音频数据
本文的目的主要是通过了解RTMP底层报文,消除因为RTMP协议过度设计导致的复杂感和神秘感,为后续的SRS代码学习做好准备。