目录
一、协议概述
二、握手(Handshake)
2.1 简单握手
2.2 复杂握手
三、RTMP消息格式(RTMP Message Formats)
3.1 Message介绍
3.2 Chunk介绍
四、RTMP消息类型(RTMP Message Type)
4.1 协议控制消息(Protocol Control Messages)
4.2 命令消息(Command Message)
4.3 数据消息(Data Message)
4.5 音/视频信息(Audio/Video Message)
4.6 聚合消息(Aggregate Message)
4.7 用户控制消息(User Control Message Event)
五、数据传输过程
5.1 发送/接收过程概述
5.2 Chunk传输过程示例
5.3 RTMP推拉流过程
六、知识点汇总
七、RTMP使用场景及优缺点
八、QA
1、rtmp为什么会低延时?
2、为什么会有AMF0和AMF3?有什么区别?
3、接收端如何知道哪些chunk属于同一个message?
4、每个chunk的csid是否相同?
5、chunk的哪些字段继承至message ?
6、message拆分成chunk传输是防止低优先级的数据传输时间长阻塞优先级高的数据传输,哪些是高优先级数据,哪些是低优先级数据?
7、rtmp和flv的关系?
1、写在前面:学习的时候一定要对照官方文档、结合分析工具,才能深入理解。
(1)rtmp官方文档:https://rtmp.veriskope.com/pdf/rtmp_specification_1.0.pdf
(2)学习rtmp的时候可以配合抓包工具(wireshark)去分析,收发的消息类型及消息内容。
2、RTMP协议是Real Time Message Protocol(实时信息传输协议)的缩写,它是由Adobe公司提出的一种应用层的协议,用来解决多媒体数据传输流的多路复用(Multiplexing)和分包(packetizing)的问题。
3、RTMP协议有三个分支,第一种是工作在TCP协议上的明文传输,它使用的端口是1935;第二种是RTMPT,RTMPT被封装在HTTP请求之中,可以穿越防火墙进行传输;第三种是RTMPS,它也是封装在HTTP之中,不过与RTMPT不同的是,它使用HTTPS安全连接,可以保证传输的安全。本文介绍的是第一种分支。
4、RTMP协议是要靠底层可靠的传输层协议(通常是TCP)来保证信息传输的可靠性的,默认使用端口1935。在基于传输层协议的链接建立完成后,RTMP协议也要客户端和服务器通过“握手”来建立基于传输层链接之上的RTMP Connection链接。RTMP Connection成功后会传输一些控制信息,如CreateStream命令会创建一个Stream链接,用于传输具体的音视频数据和控制这些信息传输的命令信息。
5、RTMP协议中基本的数据单元称为消息(Message),即封装、解封装都是以Message为单位进行操作。当RTMP协议在互联网中传输数据的时候,为了更好地实现多路复用、分包和信息的公平性,发送端会把Message划分为带有Message ID的Chunk,每个Chunk可能是一个单独的Message,也可能是Message的一部分,在接受端会根据chunk中包含的data的长度,message id和message的长度把chunk还原成完整的Message,从而实现信息的收发。
6、多路复用,RTMP可以将来自不同视频流的切片(chunk)在单个连接上传输,这种方法被称为“多路复用”
1、一个 RTMP 连接以握手开始。先进行TCP握手后再进行RTMP握手。RTMP 握手由三个固定长度的块组成,有简单握手和复杂握手两种方式,两种握手方式信息流转的过程是相同的,只是消息中携带的信息不同。
2、握手实质上起到的是验证的作用,其中一项是会校验服务器,客户端的rtmp版本,如果版本兼容则可以收发数据,如果版本不兼容则说明不能收发数据,则握手会失败。
3、rtmp握手成功之后才会有rtmp header、rtmp body出现。握手的过程中不会有这些标识出现,因为此时rtmp还没有建立链接。握手的过程中的数据是纯数据,就是一个个字符,不属于rtmp header、rtmp body的范畴。
如下图所示:在handshake中没有rtmp header、rtmp body,而在handshake后面的connect中就出现了rtmp header、rtmp body的标识。
4、无论推流(直播)还是拉流(观看),都是客户端向服务端发起握手请求。第一条握手消息是客户端发送的。
5、客户端向服务端按序发送C0,C1,C2(按序)3个chunk,服务端向客户端按序发送S0,S1,S2(按序)3个chunk,然后才能进行有效信息的传输。RTMP协议并没有规定这6个Message的具体传输顺序,但需要保证以下几点:(简单握手和复杂握手均是如此)
(1)客户端要等收到S1之后才能发送C2
(2)客户端要等收到S2之后才能发送其他信息(控制信息和真实音视频等数据)
(3)服务端要等到收到C0之后发送S1
(4)服务端必须等到收到C1之后才能发送S2
(5)服务端必须等到收到C2之后才能发送其他信息(控制信息和真实音视频等数据)
如果每次发送一个握手chunk的话握手顺序会是这样:
理论上来讲只要满足以上条件,如何安排6个Message的顺序都是可以的,但实际实现中为了在保证握手的身份验证功能的基础上尽量减少通信的次数,一般的发送顺序是这样的,这一点可以通过wireshark抓ffmpeg推流包进行验证:(简单握手和复杂握手均是如此)
|client|Server |
|---C0 + C1---> |
|<--S0 + S1 + S2--|
|---- C2 ---->|
6、简单握手中S2是C1的复制,C2是S1的复制。
1、C0和S0
C0 和 S0 包都是一个单一的8位字节,以一个单独的8位整型域进行处理:
Field |
Type | Comment |
version |
8 bytes | 在 C0中,这一字段指示出客户端要求的 RTMP 版本号。在 S0 中,这一字段指示出服务端选择的 RTMP 版本号。版本号基本都是3。 |
2 、C1和S1
C1 和 S1 数据包的长度都是 1536 字节,分布如下:
Field |
Type | Comment |
time | 4 bytes | 这个字段包含一个 timestamp,用于本终端发送的所有后续块的时间起点。这个值可以是 0。 |
zero | 4 bytes | 这个字段必须都是 0。如果不是0,代表要使用复杂握手。 |
random | 1528 bytes | 这个字段可以包含任意值。终端需要区分出响应来自它发起的握手还是对端发起的握手,这个数据应该发送一些足够随机的数。这个不需要对随机数进行加密保护,也不需要动态值。 |
3、C2和S2
C2 和 S2 数据包长度都是 1536 个节,基本就是 S1 和 C1 的副本。S2是C1的复制。 C2是S1的复制。分布如下:
Field |
Type | Comment |
time | 4 bytes | 这个字段必须包含终端在 S1 (给 C2) 或者 C1 (给 S2) 发的timestamp。 |
time2 | 4 bytes | 这个字段必须包含终端先前发出数据包 (s1 或者 c1) timestamp。 |
random | 1528 bytes | 这个字段必须包含终端发的 S1 (给 C2) 或者 S2 (给 C1)的随机数。 |
1、相对于简单握手,复杂握手主要是增加了更严格的验证。 主要是将简单握手中1528Bytes随机数的部分平均分成两部分, 一部分764Bytes存储public key(公共密钥,32字节),另一部分 764Bytes存储digest(密文,32字节)。
2、另外, 复杂握手还有一个明显的特征就是: C1、S1的version部分不为0, 服务端可根据这个来判断是否简单握手或复杂握手。
3、C1/S1、C2/S2的计算过程比较复杂,可以参考本文档并结合srs相关代码进行学习
4、C0和S0
C0 和 S0 包都是一个单一的8位字节,以一个单独的8位整型域进行处理:
Field |
Type | Comment |
version |
8 bytes | 说明是明文还是密文。如果使用的是明文(0X03),同时代表当前使用的rtmp协议的版本号。如果是密文,该位为0x06 |
5、C1和S1
(1)和简单握手相比,主要是将简单握手中random(1528Bytes)的部分平均分成两部分, 一部分764Bytes存储public key(公共密钥,32字节),另一部分 764Bytes存储digest(密文,32字节)。以此来增加更加严格的验证。在不同的包里,key和diest顺序可能会颠倒,比如nginx-rtmp
Field |
Type | Comment |
time |
4 bytes | 说明是明文还是密文。如果使用的是明文(0X03),同时代表当前使用的rtmp协议的版本号。如果是密文,该位为0x06 |
version | 4 bytes | 非0值,如果是0则表示简单握手。 |
key | 764 bytes | random-data:长度由这个字段的最后4个byte决定,即761 - 764 key-data:128个字节。Key字段对应C1和S1有不同的算法。发送端(C1)中的Key应该是随机的,接收端(S1)的key需要按照发送端的key去计算然后返回给发送端。 random-data:(764 - offset - 128 - 4)个字节 key_offset:4字节, 最后4字节定义了key的offset(相对于KeyBlock开头而言,相当于第一个random_data的长度) |
digest | 764 bytes | offset:4字节, 开头4字节定义了digest的offset random-data:长度由这个字段起始的4个byte决定 digest-data:32个字节 random-data:(764 - 4 - offset - 32)个字节 |
(2)C1、S1的key、digest的计算过程:key、digest构造好之后再发给对端
(rtmp复杂握手_施工中请绕行的博客-CSDN博客_rtmp复杂握手、Rtmp协议复杂握手(handshake)详解_Fuction.的博客-CSDN博客_rtmp 复杂握手、rtmp complex handshake,变更的握手,支持h264/aac_win_lin的博客-CSDN博客)
C1的key为128bytes随机数。
C1的digest由HmacSHA256(FPKey, 30, P1+P2)计算出,其中P1为key+digest中digest-data(注意是digest-data,不是digest)之前的部分,P2为key+digest中digest-data之后的部分,P1+P2是将这两部分拷贝到新的数组,共(1536 - 32)长度,相当于把key+digest这部分的digest-data用剪子剪掉,剩余的部分再拼接起来。其中,FPkey是官方定义的公共密钥(从下面的注释中也可以看出FPkey的来源):
u_int8_t FMSKey[] = {
0x47, 0x65, 0x6e, 0x75, 0x69, 0x6e, 0x65, 0x20,
0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x46, 0x6c,
0x61, 0x73, 0x68, 0x20, 0x4d, 0x65, 0x64, 0x69,
0x61, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Media Server 001
0xf0, 0xee, 0xc2, 0x4a, 0x80, 0x68, 0xbe, 0xe8,
0x2e, 0x00, 0xd0, 0xd1, 0x02, 0x9e, 0x7e, 0x57,
0x6e, 0xec, 0x5d, 0x2d, 0x29, 0x80, 0x6f, 0xab,
0x93, 0xb8, 0xe6, 0x36, 0xcf, 0xeb, 0x31, 0xae
}; // 68
u_int8_t FPKey[] = {
0x47, 0x65, 0x6E, 0x75, 0x69, 0x6E, 0x65, 0x20,
0x41, 0x64, 0x6F, 0x62, 0x65, 0x20, 0x46, 0x6C,
0x61, 0x73, 0x68, 0x20, 0x50, 0x6C, 0x61, 0x79,
0x65, 0x72, 0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Player 001
0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8,
0x2E, 0x00, 0xD0, 0xD1, 0x02, 0x9E, 0x7E, 0x57,
0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,
0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE
}; // 62
S1的key根据 C1的key算出来:OpenSSL中的一个加密算法传入C1的key秘钥从而得到共享秘钥,把这个共享秘钥作为S1的key秘钥。
S1的digest算法同C1。注意,必须先计算S1的key,因为key变化后digest也重新计算。
6、C2和S2
(1)C2、S2就是把digest放到最后那32字节上,主要是用来对C1、S1的验证。
Field |
Type | Comment |
time |
4 bytes | 这个字段必须包含终端在 S1 (给 C2) 或者 C1 (给 S2) 发的timestamp。 |
time2 | 4 bytes | 这个字段必须包含终端先前发出数据包 (s1 或者 c1) timestamp。 |
random | 1504 bytes | random-data:1504字节 |
digest | 32 bytes | digest-data:32个字节 |
(2)C2、S2digest算法如下:digest构造好之后再发给对端。其中,FPKey、FMSKey可以参考上文内容。
// client generate C2, or server valid C2
temp-key = HMACsha256(FPKey, 62, s1-digest)//是s1-digest,不是s1-digest-data
c2-digest-data = HMACsha256(c2-random-data, temp-key, 32)
// server generate S2, or client valid S2
temp-key = HMACsha256(FMSKey, 68, c1-digest)//是c1-digest,不是c1-digest-data
s2-digest-data = HMACsha256(s2-random-data, temp-key, 32)
1、消息(Message)是RTMP协议中基本的数据单元。由Message Header和Message Payload(可以理解成message body)组成。
2、对于音视频数据而言每一个message就是一帧数据。对于flv的tag而言,就是对应rtmp每个message,一个tag就是一个message,是一一对应的关系;相当于每一个tag都封装成一个message。message payload的数据格式和tag data的数据格式是相同的,message header和tag header的格式不同。
3、Message Format如下:
Field |
Type | Comment |
|
Message Header | Length |
3 bytes | Message Payload(消息负载)的长度,不包含Message Header |
Timestamp |
4 bytes | 时间戳(既是pts也是dts,因为直播场景中没有B帧,所以pts=dts) |
|
Message Type Id |
1 bytes | 消息类型,主要包括协议控制消息、音视频消息、命令消息等 |
|
Message Stream Id |
3 bytes | 消息流ID可以是任意值。不同的message可以有相同的值。复用到同一块流上的不同消息流基于它们的消息流ID解复用。 |
|
Message Payload | — | n bytes | 是消息中包含的实际数据,消息类型不同payload大小也不同。例如,它可以是一些音频样本或压缩视频数据或Metadata等 |
4、多路复用,RTMP可以将来自不同视频流的切片(chunk)在单个连接上传输,这种方法被称为“多路复用”,不同的流就用不同的Message Stream Id区分。
1、RTMP以Message为基本单位,通过把Message拆分成Chunk来进行网络发送。chunk data默认是128字节。chunk是RTMP最小的传输单元。目的是:防止一个大的数据包传输时间过长,阻塞其它数据包的传输。chunk合成message:接收端将接收到chunk的chunk data的大小加和,如果等于message payload(通过chunk->message header->message length获取)的则认为是同一个message。
2、Chunk在传输时:同一个Message产生的多个Chunk只会串行发送。先发送的Chunk一定先到达。不同Message产生的Chunk可以并行发送。并行发送的Chunk复用了一条TCP链接。
3、Chunk Format如下:
Field |
Type | Comment |
|
Chunk Header | Basic Header |
1-3 bytes | 包含fmt(chunk type)和chunk stream id(csid),其中fmt决定了chunk的类型及message header的长度,占2 bit,而Basic header的长度取决于csid的数值大小,最少占1 byte。 |
Message Header |
0,3,7 or 11 bytes | 要发送的实际信息(可能是完整的,也可能是一部分)的描述信息。 长度取决于Basic Header中的chunk type,有Type 0,1,2,3类型的header |
|
Extended Timestamp |
0 or 4 bytes | 扩展时间戳(0 bytes时表示此字段不存在) |
|
Chunk Data | — | n bytes | 是消息中包含的实际数据,消息类型不同data大小也不同 |
4、有多种chunk type的目的是:减少重复数据发送,提高chunk data的占比。
3.2.1 Basic Header
1、Basic Header结构如下:
Field |
Type | Comment |
|
Basic Header | fmt |
2 bits | 表示chunk type,取值[0, 3],即chunk共有4种类型 |
csid (chunk stream id) |
6,14 or 22 bits | csid范围是3~65599,0~2为协议保留用作特殊信息; 上文提到Basic Header大小为1-3 bytes,由于fmt域占2bits,所以CSID长度分别是6 bits、14 bits或22 bits |
(1)Basic Header为1bytes时:csid为6bits,取值在[3~63]
(2)Basic Header为2bytes时:第一个字节除了fmt外,其余6位表示数字0,csid范围是[64~319],即最大为(2^8 - 1) + 64 = 319
(3)Basic Header为3bytes时:第一个字节除了fmt外,其余6位表示数字1,csid范围是[64~65599],最大值为 (2^16 - 1) + 64 = 65599
3.2.2 Message Header
1、Message Header的格式和长度取决于Basic Header的chunk type,即fmt,fmt取值[0-3],所以共有4种不同的chunk格式,目的是减少重复数据发送,提高 chunk data的占比。同时也有4种不同的Message Header。
2、Message Header结构如下:
(1)chunk type = 0(fmt = 0):Message Header共11字节,此类型必须在块流开始时使用,当流时间戳向后(例如,回退播放)时也要使用此格式。
Field |
Type | Comment |
|
Message Header | timestamp | 3 bytes | 时间戳,如果值大于等于16777215(0xFFFFFF),该字段必须等于16777215,然后转存到4字节的Extended Timstamp字段中。接收端判断是0xFFFFFF后会去Extended Timstamp解析时间戳 |
message length | 3 bytes | 指的是message拆分之前message payload的长度(不包含header),而且如果被拆分成chunk,此字段填充拆分前message的body长度,而不是chunk的长度 | |
message type id | 1 byte | 消息类型,如8代表audio数据,9代表video,其它值可参考后文的消息类型 | |
message stream id | 4 bytes | 表示该Chunk所在的流的ID |
(抓包分析和官方文档描述有些不同,可能是抓包工具解析、显示的问题,不必纠结)
(2)chunk type = 1(fmt = 1):Message Header共7字节,和前一个chunk共用message stream id(msid),因此省去了message stream id的4字节,表示此Chunk和上一次发的Chunk所在的流相同(不是相同的message),如果在发送端和对端有一个流连接的时候尽量采用这种格式。
Field |
Type | Comment |
|
Message Header | timestamp delta | 3 bytes | 和上一个chunk的时间差。如果值大于等于16777215(0xFFFFFF),该字段必须等于16777215,然后转存到4字节的Extended Timstamp字段中。接收端判断是0xFFFFFF后会去Extended Timstamp解析时间戳 |
message length | 3 bytes | 指的是message拆分之前message payload的长度(不包含header),而且如果被拆分成chunk,此字段填充拆分前message的body长度,而不是chunk的长度 | |
message type id | 1 byte | 消息类型,如8代表audio数据,9代表video,其它值可参考后文的消息类型 |
(抓包分析和官方文档描述有些不同,可能是抓包工具解析、显示的问题,不必纠结)
(3)chunk type = 2(fmt = 2):Message Header共3字节,相对于 chunk type = 1 格式又省去了message length的3个字节和message type id的1个字节,表示此 chunk和上一次发送的 chunk 的message length、message type id都相同。余下的这三个字节表示 timestamp delta,使用同type=1。
Field |
Type | Comment |
|
Message Header | timestamp delta | 3 bytes | 和上一个chunk的时间差。如果值大于等于16777215(0xFFFFFF),该字段必须等于16777215,然后转存到4字节的Extended Timstamp字段中。接收端判断是0xFFFFFF后会去Extended Timstamp解析时间戳 |
(抓包分析和官方文档描述有些不同,可能是抓包工具解析、显示的问题,不必纠结)
(4)chunk type = 3(fmt = 3):Message Header共0字节,即此时chunk没有Message header。
当它在type = 0的chunk后面时,表示和前一个chunk的时间戳是相同的,也就是一个Message拆分成多个chunk时,后一个chunk和前一个chunk同属一个Message自然也就可以不用传Message header。
当它跟在type = 1或type = 2的chunk后面时,表示和前一个时间戳的差相同。如第一个chunk是type = 0,timestamp = 0,第二个chunk是type = 2,timestamp delta = 20,表示时间戳为0+20=20,第三个chunk是type = 3,则timestamp delta = 20,表示时间戳为20+20=40。
(抓包分析和官方文档描述有些不同,可能是抓包工具解析、显示的问题,不必纠结)
3、chunk->message header 和 message->message header 的关系
(1)chunk type = 0(fmt = 0)时,如果没有chunk->extended timestamp则chunk->message header的内容和message->message header完全相同。如果存在chunk->extended timestamp则chunk->message header->timestamp和message->message header->timestamp的内容不相同,但是其余字段的内容是完全相同的。
(2)chunk type = 1(fmt = 1)时,chunk->message header除了timestamp delta的字段,其余字段内容都和message->message header相同。
(3)chunk type = 2(fmt = 2)时,chunk->message header没有字段和message->message header相同。
(4)chunk type = 3(fmt = 3)时,chunk->message header没有字段和message->message header相同。
1、rtmp协议中有多种消息,用于数据传输和命令控制等操作,所有的消息都是封装成message,然后通过chunk来传输。
1、在RTMP的chunk流会用一些特殊的值来代表协议的控制消息,属于RTMP chunk流协议层的消息,它们的Message Stream ID必须为0(代表控制流信息),CSID必须为2,Message Type ID可以为1,2,3,5,6,具体代表的消息会在下面依次说明。控制消息的接受端会忽略掉chunk中的时间戳,收到后立即生效。(带你吃透RTMP_会敲代码的咩的博客-CSDN博客_带你吃透rtmp)
4.1.1 设置块大小(Set Chunk Size ,Message Type ID = 1)
1、RTMP消息需要以chunk size为单位封装成chunk包发送,因此接收端需要根据chunk size才能正确解包,所以双端都要记录对端的封包单位chunk size,默认128 bytes。
2、通信过程中可发送此消息通知对端更新其记录的本端的chunk size。比如client想发送131 bytes的音频数据(此时chunk size为128 bytes,不更新chunk size的话需要拆成两个chunk),此时client可通知对端,这边client的chunk size更新为131 bytes,之后发送一个data为131 bytes的chunk即可,server端收到Set Chunk Size之后更新chunk size即可正确解析之后到来的chunk。
3、双端的chunk size各自独立维护,可以不同。例如client可以发送131 bytes的chunk,server也按照131 bytes解析,server发送128 bytes的chunk,client也按照128 bytes解析。
4、message payload如下所示:
Field |
Type | Comment |
|
Message Payload | 第一个bit |
1 bit (是bit不是byte) |
恒为0 |
chunk size | 31 bit | 可表示[1, 0x7FFFFFFF]区间,但是由于chunk size要小于Message的length,而Message length字段用3个字节存储,最大值为0xFFFFFF,所以实际可取值区间为[1, 0xFFFFFF] |
Message Type ID 就是chunk中Type ID,也就是0x01
4.1.2 中断消息(Abort Message,Message Type ID = 2)
1、发送数据过程中,发送端可发送Abort消息通知接收端丢弃当前未接收完的Message及忽略之后的消息。先前已收到的chunk将被全部被抛弃,接收端根据Abort消息中的chunk stream id(csid)可丢弃对应chunk流中之后的所有数据。比如在发送端需要关闭时,发送此消息通知对端之后的数据可以不用处理了。
2、message payload如下所示:
Field |
Type | Comment |
|
Message Payload | chunk stream id | 32 bits | 此字段保存要丢弃其当前消息的块流ID |
4.1.3 应答消息(Acknowledgement,Message Type ID = 3)
1、当收到对端消息字节数等于接收窗口大小时,接收端要回复一个应答消息(相当于ack)告知对端可以继续发送数据,发送方在收到应答消息之前不会再继续发送消息。
2、message payload如下所示:
Field |
Type | Comment |
|
Message Payload | chunk stream id | 32 bits | 截止目前接收到的数据总和,以字节为单位 |
4.1.4 应答窗口大小(Window Acknowledgement Size,Message Type ID = 5)
1、规定接收端接收多少数据后需要发送一个应答消息。发送端可以发送此消息通知对端更新窗口大小,一般在音视频数据之前发送。并且双端的window size共同维护,保持相同。
2、message payload如下所示:
Field |
Type | Comment |
|
Message Payload | acknowledgement window size | 32 bits | 接收端接收多少数据后需要发送一个应答消息 |
Message Type ID 就是chunk中Type ID,也就是0x05
4.1.5 设置流带宽(Set Peer Bandwidth,Message Type ID = 6)
1、客户端或服务器发送此消息以限制其对等端的输出带宽。
2、message payload如下所示:
Field |
Type | Comment |
|
Message Payload | acknowledgement window size | 32 bits | 设置对端出口带宽 |
limit type | 8 bits | limit type = 0:硬限制,立即更新出口带宽大小 limit type = 1:软限制,可以更新出口带宽大小,也可以保留原值,但是原值一定要小于期望更新的大小 limit type = 2:动态限制,如果上一次为硬限制,此消息被视为硬限制,否则忽略此消息 |
Message Type ID 就是chunk中Type ID,也就是0x06
(流媒体:RTMP 命令消息与流管理 - 知乎)
1、命令消息(Command Messages)是用于 C-S 进行直接交互应答的一类消息。一般情况下,命令消息的发送对端,是需要对端进行应答信号反馈的。需要AMF编码,AMF0编码时Message Type ID = 20,使用AMF3编码时Message Type ID = 17,CSID通常为3。
2、命令类型的消息包含命令名称、事务ID和相关参数。如client端发送connect命令时需要包含要连接的应用名称作为参数,然后server端回复消息时带上收到的transaction ID表示对此条消息的回应。回复命令有_result,_error,或者其他如verifyClient,contactExternalServer的方法名。
3、发送命令消息的对象有两种分别是NetConnection和NetStream:NetConnection:表示双端的上层连接,服务器和客户端之间进行网络连接的一种高级表示形式。NetStream:表示流信息的传输通道如音频流、视频流,以及控制流信息的状态,如Play播放流,Pause暂停。
4.2.1 网络连接命令(NetConnection Commands)
1、表示双端的上层连接,服务器和客户端之间进行网络连接的一种高级表示形式。
2、网络连接允许使用:连接(connect)、调用(call)、创建流(createStream),每一种消息都有应答消息。
3、应答消息中的“Transaction ID”表明对哪个请求的应答。
4.2.1.1 连接(connect)
1、客户端向服务器发送connect命令,以请求连接到服务器应用程序实例(Application Instance)。不同的的Application Instance可根据功能进行区分,比如直播可以用live表示,点播可以用vod表示,测试环境可以用test表示,用户可自定义。例如:rtmp://192.127.0.1/test/16,可以表示test环境,16是对流的描述,用户可自定义。ip后面的内容可以用来标识流的内容
2、消息的结构如下所示:
(1)请求消息:
Field |
Type | Comment |
|
Message Payload | Command Name(命令名字) | String | ”connect” |
Transaction ID(事务ID) | Number | 恒为1 | |
Command Object(命令包含的参数对象) | Object | 键值对集合表示的命令参数(具体内容可参考官方文档) | |
Optional User Arguments(额外的用户参数) | Object | 用户自定义的额外信息 |
Optional User Arguments(额外的用户参数) 包含如下内容:
Property |
Type | Description | Example Value |
app |
String |
客户端要连接的服务器应用程序名 |
testapp |
flashver |
String |
Flash Player的版本号。它与ApplicationScript中getversion()方法返回的字符串是一样的。 |
FMSc/1.0 |
swfUrl |
String |
发起连接的SWF源文件的URL |
file://C:/FlvPlayer.swf |
tcUrl |
String |
服务器URL。格式:protocol://servername:port/appName /appInstance |
rtmp://localhost:1935/testapp/instance1 |
fpad |
Boolean |
如果使用代理,值为true |
true或false |
audioCodecs |
Number |
指示客户端支持的音频编码 |
SUPPORT_SND_MP3 |
videoCodecs |
Number |
指示客户端支持的视频编码 |
SUPPORT_VID_SORENSON |
videoFunction |
Number |
指示客户端支持的专用的视频方法 |
SUPPORT_VID_CLIENT_SEEK |
pageUrl |
String |
SWF文件从哪里加载的网页URL |
http://somehost/sqmple.html |
objectEncoding |
Number |
AMF编码方法 |
AMF3 |
audioCodecs包含如下内容:
Codec Flag |
Usage | Value |
SUPPORT_SND_NONE |
原始音频,无压缩 |
0x0001 |
SUPPORT_SND_ADPCM |
ADPCM压缩 |
0x0002 |
SUPPORT_SND_MP3 |
mp3压缩 |
0x0004 |
SUPPORT_SND_INTEL |
未使用 |
0x0008 |
SUPPORT_SND_UNUSED |
未使用 |
0x0010 |
SUPPORT_SND_NELLY8 |
8-kHz采样速率的NellyMoser压缩 |
0x0020 |
SUPPORT_SND_NELLY |
NellyMoser压缩(5,11,22,和44 kHz采样速率) |
0x0040 |
SUPPORT_SND_G711A |
G711A声音压缩(仅Flash Media Server支持) |
0x0080 |
SUPPORT_SND_G711U |
G711U声音压缩(仅Flash Media Server支持) |
0x0100 |
SUPPORT_SND_NELLY16 |
16-kHz采样速率的NellyMoser压缩 |
0x0200 |
SUPPORT_SND_AAC |
高级音频编码(AAC)编解码 |
0x0400 |
SUPPORT_SND_SPEEX |
Speex音频 |
0x0800 |
SUPPORT_SND_ALL |
所有RTMP支持的音频编解码 |
0x0FFF |
videoCodecs包含如下内容:
Codec Flag |
Usage | Value |
SUPPORT_VID_UNUSED |
废弃值 |
0x0001 |
SUPPORT_VID_JPEG |
废弃值 |
0x0002 |
SUPPORT_VID_SORENSON |
Sorenson Flash视频 |
0x0004 |
SUPPORT_VID_HOMEBREW |
V1屏幕共享 |
0x0008 |
SUPPORT_VID_VP6 (On2) |
On2视频(Flash 8+) |
0x0010 |
SUPPORT_VID_VP6ALPHA(On2 with alphachannel) |
带有alpha通道的On2视频 |
0x0020 |
SUPPORT_VID_HOMEBREWV(screensharing v2) |
屏幕共享版本2(Flash 8+) |
0x0040 |
SUPPORT_VID_H264 |
H264视频 |
0x0080 |
SUPPORT_VID_ALL |
所有的RTMP支持的视频编解码 |
0x00FF |
videoFunction包含如下内容:
Codec Flag |
Usage | Value |
SUPPORT_VID_CLIENT_SEEK |
指示客户端可以进行帧精确控制查找 |
0x0001 |
推流地址:rtmp:/ip:port/study_rtmp/gogogo
Field |
Type | Comment |
|
Message Payload | Command Name(命令名字) | String | ”_result”或"_err",表示响应是否为结果或者错误 |
Transaction ID(事务ID) | Number | 恒为1 | |
Properties(命令包含的参数对象) | Object | 键值对集合表示的命令参数(具体内容可参考官方文档) | |
Information(应答信息) | Object | 描述应答的名称-值对,如“code”,“level”,“description”之类的内容 |
4.2.1.2 调用(call)
1、NetConnection对象的调用方法在接收端运行远程过程调用(RPC)。被调用的RPC名称作为参数传递给调用命令。
2、消息的结构如下所示:
(1)请求消息
Field |
Type | Comment |
|
Message Payload | Procedure Name(程序名称) | String | 指定调用对端的远程功能名称 |
Transaction ID(事务ID) | Number | 事物ID,用来标记请求,如不需要也可以传0 | |
Command Object(命令包含的参数对象) | Object | 键值对集合表示的命令参数(具体内容可参考官方文档) | |
Optional User Arguments(额外的用户参数) | Object | 用户自定义的额外信息 |
(2)应答消息
Field |
Type | Comment |
|
Message Payload | Command Name(命令名字) | String | RPC请求方定义的回应方法的名称 |
Transaction ID(事务ID) | Number | 需要响应的RPC请求的事务ID,标识它属于哪个应答的 | |
Command Object(命令包含的参数对象) | Object | RPC需求的配置参数 | |
Response(应答信息) | Object | 被调用的RPC的返回结果 |
4.2.1.3 创建流(createStream)
1、客户端将此命令发送到服务器,以创建消息通信的逻辑通道。音频、视频和元数据的发布通过使用createStream命令创建的流通道执行。NetConnection是默认的通信信道,其流ID为0。协议和一些命令消息(包括createStream)使用默认的通信通道。
2、消息的结构如下所示:
(1)请求消息
Field |
Type | Comment |
|
Message Payload | Command Name(程序名称) | String | “createstream” |
Transaction ID(事务ID) | Number | 命令会话ID | |
Command Object(命令包含的参数对象) | Object | 如果存在任何命令信息,则设置此对象,否则设置为空类型。 |
(2)应答消息
Field |
Type | Comment |
|
Message Payload | Command Name(命令名字) | String | ”_result”或"_err",表示响应是否为结果或者错误 |
Transaction ID(事务ID) | Number | 标识它属于哪个应答的 | |
Command Object(命令包含的参数对象) | Object | 如果存在任何命令信息,则设置此对象,否则设置为空类型。 | |
Stream ID(应答信息) | Object | stream id或错误信息 |
1、网络流命令是在数据信道建立完毕后(先有NetConnection,才有NetStream命令),用来对音视频数据流进行直接控制的命令类型。这些命令作用于当前信道对应数据的操控行为,都是客户端向服务端发送的命令,一个NetConnection对象可以有多个NetStream,进而支持多种数据。常见的命令如下:
命令 |
作用 |
play |
播放 |
play2 | 播放2 |
deleteStream | 删除流 |
closeStream | 关闭流 |
receiveAudio | 接收音频 |
receiveVideo | 接收视频 |
publish | 推流 |
seek | 定位 |
pause | 暂停 |
2、网络流命令的请求命令所对应的应答命令,被统一命名为 “onStatus” 以描述数据所处的状态发生变更,因此具有统一的格式(注意:不是所有的消息都有应答,而是有应答消息的都是如下格式)
Field |
Type | Comment |
|
Message Payload | Command Name(命令名字) | String | onStatus |
Transaction ID(事务ID) | Number | 恒为0 | |
Command Object(命令包含的参数对象) | Object | NULL | |
Info Object(应答信息) | Object | stream id或错误信息 |
因此,我们通过处理 NetStream 的应答指令的 ‘level’ 和 ’code’ 字段,就可以知道相应的请求指令是否有成功执行了。进而判断当前状态,并确认是否需要执行后续的操作流程。
4.2.2.1 播放(play)
1、客户端发送此命令让服务器播放流。也可以多次使用此命令创建一个播放列表。如果你想创建一个在不同直播或录制流之间切换的动态播放列表,多次调用play并将reset字段设置为false。相反,如果你想立即播放指定的流,将reset字段设置为true。
2、消息的结构如下所示:
(1)请求消息(客户端发送给服务器):
Field |
Type | Comment |
Command Name(命令名字) | String |
“play” |
Transaction ID(事务ID) | Number |
Transaction ID设置为0 |
Command Object(命令包含的参数对象) | Null |
不存在命令信息,设置为null类型 |
Stream Name(流名称) |
String |
待播放流的名称和格式。 如果要播放视频(FLV)文件,指定不带文件扩展名(比如,“sample”)的流名称。 如果要播放H.264/AAC文件,你必须在流名称前加上mp4:的前缀,并且指定文件名后缀。比如,要播放文件sample.m4v,指定“mp4:sample.m4v” 如果要回放MP3或者ID3标签,你必须要在流名称前加上mp3:的前缀。比如,“mp3:sample” |
Start(开始时间) |
String |
一个可选的参数,指定开始播放的起开时间,单位是秒,默认值是-2。 Start = -2,代表选取对应该流名称的直播流,即当前正在推送的流开始播放,如果对应该名称的直播流不存在,就选取该名称的流的录播版本,如果这也没有,当前播流端要等待直到对端开始该名称的流的直播。 Start = -1,那么只会选取直播流进行播放,即使有录播流也不会播放;如果传值或者正数,就代表从该流的该时间点开始播放,如果流不存在的话就会自动播放播放列表中的下一个流 Duration >= 0,一个在流名称字段指定的录播流会被播放,起始时间是Start字段指定的时间。如果找不到该记录流,播放列表的下一个条目会被播放。 |
Duration(时长) |
String |
一个可选的参数,指定回放时长,单位是秒,默认值是-1。 Duration = -1,直播流被播放直到它不可用,或者录播流被播放直到结束。(如果你传递一个不同于-1的负数,它会把该值解析为-1) Duration = 0,它会播放录播流中Start字段指定开始时间的一帧。 Duration > 0,如果你指定一个正数,它会播放Duration字段指定该段时间的直播流。之后,它能够播放Duration字段指定该段时间的录播流。(如果流在Duration字段指定的时间前结束,回放随着流的结束而结束) |
Reset(重置) |
Boolean |
一个可选的布尔值,它指定是否清除之前的所有播放列表 |
4.2.2.2 播放2(play2)
1、play命令不同的是,play2命令可以将当前正在播放的流切换到同样数据但不同码率的流上,服务端会维护多种比特率的文件来供客户端使用play2命令来切换。
2、消息的结构如下所示:
(1)请求消息(客户端发送给服务器):
Field |
Type | Comment |
Command Name(命令名字) | String |
“play2” |
Transaction ID(事务ID) | Number |
Transaction ID设置为0 |
Command Object(命令包含的参数对象) | Null |
不存在命令信息,设置为null类型 |
Parameters(参数) | Object | AMF编码的Flash对象,包括了一些用于描述flash.net.NetstreamPlayOptions ActionScript obejct的参数 |
4.2.2.3 删除流(deleteStream)
1、NetStream在NetStream对象被销毁时发送deleteStream命令,用于客户端告知服务端本地的某个流对象已被删除,不需要再传输此路流。
2、消息的结构如下所示:
(1)请求消息(客户端发送给服务器):
Field |
Type | Comment |
Command Name(命令名字) | String |
“deleteStream” |
Transaction ID(事务ID) | Number |
Transaction ID设置为0 |
Command Object(命令包含的参数对象) | Null |
不存在命令信息,设置为null类型 |
Stream ID(流ID) | Number | 本地已删除,不再需要服务器传输的流的ID |
(2)应答消息(应答格式参考“4.2.2 网络流命令(NetStream)--> 2”):
无应答消息
4.2.2.4 接收音频(receiveAudio)
1、客户端通知服务端是否要发送音频。
2、消息的结构如下所示:
(1)请求消息(客户端发送给服务器):
Field |
Type | Comment |
Command Name(命令名字) | String |
“deleteStream” |
Transaction ID(事务ID) | Number |
Transaction ID设置为0 |
Command Object(命令包含的参数对象) | Null |
不存在命令信息,设置为null类型 |
Bool Flag(标识) | Boolean | Bool Flag = true,表示发送音频,服务端就会准备接受音频数据,会向客户端回复NetStream.Seek.Notify和NetStream.Play.Start的Onstatus命令告知客户端当前流的状态 Bool Flag = false,服务端不做响应 |
(2)应答消息(应答格式参考“4.2.2 网络流命令(NetStream)--> 2”):
如果,Bool Flag = false 这个消息只用来通知服务器,没有统一应答返回。
如果,Bool Flag = true则应答消息’code’为 NetStream.Seek.Notify 或 NetStream.Play.Start
4.2.2.5 接收视频(receiveVideo)
1、客户端通知服务端是否要发送视频。
2、消息的结构如下所示:
(1)请求消息(客户端发送给服务器):
Field |
Type | Comment |
Command Name(命令名字) | String |
“deleteStream” |
Transaction ID(事务ID) | Number |
Transaction ID设置为0 |
Command Object(命令包含的参数对象) | Null |
不存在命令信息,设置为null类型 |
Bool Flag(标识) | Boolean | Bool Flag = true,表示发送音频,服务端就会准备接受音频数据,会向客户端回复NetStream.Seek.Notify和NetStream.Play.Start的Onstatus命令告知客户端当前流的状态 Bool Flag = false,服务端不做响应 |
(2)应答消息(应答格式参考“4.2.2 网络流命令(NetStream)--> 2”):
如果,Bool Flag = false 这个消息只用来通知服务器,没有统一应答返回。
如果,Bool Flag = true则应答消息’code’为 NetStream.Seek.Notify 或 NetStream.Play.Start
4.2.2.6 发布(publish)
1、客户端发送此消息,将命名流发布到服务器。其他客户端可以使用此流名来播放流,接收发布的音频,视频,以及其它数据消息。
2、消息的结构如下所示:
(1)请求消息(客户端发送给服务器):
Field |
Type | Comment |
Command Name(命令名字) | String |
“publish“ |
Transaction ID(事务ID) | Number |
Transaction ID设置为0 |
Command Object(命令包含的参数对象) | Null |
不存在命令信息,设置为null类型 |
PublishingName(发布的流名称) |
String |
发布流的名称 |
PublishingType(发布类型) |
String |
有三种发布的类型:live record 或 append PublishingType = record : 该流已被发布并且数据被记录到一个新的文件。该文件存储在服务器的一个包含服务器应用程序目录的子目录。如果文件已经存在,则它被覆盖 PublishingType = append:流已经被发布,并且该数据被追加到一个文件。如果找不到文件,则创建它 PublishingType = live:直播数据被发布,而没有记录到文件 |
推流地址:rtmp:/ip:port/study_rtmp/gogogo
(2)应答消息(应答格式参考“4.2.2 网络流命令(NetStream)--> 2”):
publish 的返回状态 NetStream.Publish.Start,这个消息不止由 onStatus 统一应答携带,也会由 onFCPublish 返回。
4.2.2.7 定位(seek)
1、定位到视频或音频的某个位置,以毫秒为单位。
2、消息的结构如下所示:
(1)请求消息(客户端发送给服务器):
Field |
Type | Comment |
Command Name(命令名字) | String |
“seek“ |
Transaction ID(事务ID) | Number |
Transaction ID设置为0 |
Command Object(命令包含的参数对象) | Null |
不存在命令信息,设置为null类型 |
milliSeconds(毫秒) | Number | 定位到该文件的xx毫秒处 |
(2)应答消息(应答格式参考“4.2.2 网络流命令(NetStream)--> 2”):
当定位成功,服务器发送NetStream.Seek.Notify的状态消息。失败的时候,它返回一个_error的消息
4.2.2.8 暂停(pause)
1、客户端发送pause命令以告诉服务器暂停或者开始播放。
2、消息的结构如下所示:
(1)请求消息(客户端发送给服务器):
Field |
Type | Comment |
Command Name(命令名字) | String |
“pause“ |
Transaction ID(事务ID) | Number |
Transaction ID设置为0 |
Command Object(命令包含的参数对象) | Null |
不存在命令信息,设置为null类型 |
Pause/Unpause Flag(暂停/恢复播放) | Boolean | true,暂停 false,恢复播放 |
milliSeconds(毫秒) | Number | 定位到该文件的xx毫秒处 |
(2)应答消息
pause 成功后,会有 ’code’ 为 NetStream.Pause.Notify 消息返回。
unpause 成功后,会有 ’code’ 为 NetStream.Unpause.Notify 消息返回。
1、传递一些元数据(MetaData,比如视频名,分辨率等等)或者用户自定义的一些消息。当信息使用AMF0编码时,Message Type ID=18,AMF3编码时Message Type ID=15。
1、共享对象是在多个客户端、实例等之间同步的Flash对象,Flash对象是由键值对组成的集合。每条消息可以包含多个事件。当信息使用AMF0编码时,Message Type ID=19,AMF3编码时Message Type ID=16。
2、消息结构如下所示:
每一个Event Type代表一个事件,每条消息可以包含多个事件。
Event Type | Comment |
Use(=1) |
客户端发送此事件通知服务器一个共享对象的创建 |
Release(=2) |
当在客户端删除共享对象时,客户端将此事件发送到服务器上 |
Request Change(=3) |
客户端发送此事件以请求更改与共享对象命名参数关联的值 |
Change(=4) |
服务器发送此事件通知除了正在发起请求之外的所有客户端命名参数值的变化 |
Success(=5) |
如果请求被受理,服务器发送此事件给正在请求的客户端,以响应RequestChange事件 |
SendMessage(=6) |
客户端将此事件发送到服务器以广播一个消息。在接收此事件时,服务器向所有客户端广播消息,包括该发送者 |
Status(=7) |
服务器发送此事件以通知客户端相关的错误状况 |
Clear(=8) |
服务器将此事件发送给客户端,以清除共享对象。服务器还发送此事件以响应客户端在连接时发送Use event |
Remove(=9) |
服务器发送此事件让客户端删除一个slot |
Request Remove(=10) |
客户端发送此事件让服务器删除一个slot |
Use Success(=11) |
成功连接时,服务器将此事件发送给客户端8. 音频信息(Audio Message) |
1、每一个message就是一帧数据。对于flv的tag而言,就是对应rtmp每个message,一个tag就是一个message,是一一对应的关系;相当于每一个tag都封装成一个message。
2、RTMP 块流使用Message Type ID=8 作为音频数据,flv的tag header->tag type也用8来表示音频。通常音频流的csid是4(也可以自定义),音频流的每一个chunk的csid都是相同的。
3、RTMP 块流使用Message Type ID=9 作为视频数据,flv的tag header->tag type也用9来表示音频。通常视频流的csid是6(也可以自定义),视频流的每一个chunk的csid都是相同的。
1、聚合消息是包含一系列RTMP子消息的单个消息,Message Type ID=22。
2、聚合消息的消息流ID覆盖聚合内部的子消息的消息流程ID;聚合消息和第一个子消息的时间戳之间的差异是用于将子消息的时间戳记重新规范化为流时间尺度的偏移量。偏移量被添加到每个子消息的时间戳,以达到标准化的流时间。第一个子消息的时间戳应与聚合消息的时间戳记相同,因此偏移量应为零;返回指针包含前一条消息的大小,包括其标头,它被包括以匹配FLV文件的格式,并用于反向搜索,类似于flv的previous tag header。
3、使用聚合消息有几个性能优势:
(1)区块流最多可以在一个区块内发送一条完整的消息。因此,增加块大小并使用聚合消息可以减少发送的块数。
(2)子消息可以连续存储在内存中。当进行系统调用以在网络上发送数据时,效率更高。
4、消息格式如下所示:
1、RTMP 流中的用户控制消息在接收时立即生效,消息中的时间戳被忽略。该信息在 chunk 流中发送时,它们的Message Stream ID必须为0(代表控制流信息),CSID必须为2,Message Type ID必须为4。和前面提到的协议控制信息(Protocol Control Message)不同,这是在RTMP协议层的,而不是在RTMP chunk流协议层的,这个很容易弄混。
2、消息格式如下所示:
前 2 个字节数据用来标识事件类型,事件类型后面是事件数据。事件数据字段的大小是不固定的。因为消息是通过RTMP chunk发送的,所以最大块大小应足够大,以允许这些消息在单个块中。
Event Type | Comment |
StreamBegin(=0) |
服务器发送此事件通知客户端流成功创建,并可用于通信。默认情况下,在从客户端成功收到应用程序连接命令后,此事件以ID 0来发送 Event Data大小是4字节,表示开始运行的流的stream ID |
StreamEOF(=1) |
服务器发送此事件通知客户端该流请求的数据回放结束。若不发出额外的命令,就没有更多的数据被发送了。客户端丢弃该流收到的消息 Event Data大小是4字节,代表播放已结束流的stream ID |
StreamDry(=2) |
服务器发送此事件通知客户端,在流上没有更多的数据。如果服务器在一段时间内没有检测到任何的消息,它可以通知订阅的客户端流是结束的 Event Data大小是4字节,代表已结束流的stream ID |
SetBufferLength(=3) |
客户端发送此事件通知服务器用于缓冲从流过来的任何数据的缓冲区大小(以毫秒为单位)。此事件在服务器开始处理流之前被发送 Event Data大小是8字节,前4字节是stream ID,后4字节是每毫秒缓冲区的长度 |
StreamIsRecorded (=4) |
服务器发送此事件来通知客户端,需要记录(录像) Event Data大小是4字节,代表需要记录的流的stream ID |
PingRequest(=6) |
服务器发送此事件来测试客户端是否是可到达的 Event Data大小是4字节,内容是时间戳,表示当服务器发出命令时,本地服务器的时间。客户收到PingRequest时响应PingResponse |
PingResponse(=7) |
客户端向服务器发送此事件响应ping请求 Event Data大小是4字节,内容是收到PingRequest时时间戳 |
Event Type = StreamBegin
Event Type = SetBufferLength
1、Chunk在传输时:同一个Message产生的多个Chunk只会串行发送,先发送的Chunk一定先到达。不同Message产生的Chunk可以并行发送,不同的Message可以是不同的流,并行发送的Chunk复用了一条TCP链接。这就是所说的多路复用(Multiplexing)
从下图中可以看出,发送端同时发送了3个chunk
2、发送端、服务端
(1)发送端
把数据封装成消息(Message):协议控制消息、用户控制消息、音视频数据消息等。
把消息分割成消息块(Chunk,网络中实际传输的内容)。
将分割后的消息块(Chunk)通过TCP协议发送出去。
(2)接收端
在通过TCP协议收到数据后,现将消息块重新组合成消息(Message)。
通过对消息进行解封装处理就可以恢复出数据。
3、RTMP消息优先级
(1)在RTMP中,消息(Message)主要分为两大类:控制消息(协议控制消息、用户控制消息)和数据消息(音视频消息)但是通路只有一条(RTMP是单通路),到底谁先走呢,谁后走呢?在实际传输过程中是对消息分优先级,优先级高的先行。优先级低的不能阻塞 优先级高的。
(2)总结起来就是协议先行,数据次之
协议控制消息(Protocol Control Messages)和用户控制消息(User Control Messages)应该包含消息它们的Message Stream ID必须为0(代表控制流信息),CSID必须为2。
数据消息(音频信息、音频消息)比控制信息的优先级低。 另外,一般情况下,音频消息比视频数据优先级高。
1、下面2个例子均来源于RTMP官方文档
5.2.1 Audio Example
一个audio的消息流:Message Type ID = 8 表示音频,每个message有相同的message stream ID,message type ID,message length,以及增量相同的timestamp。
audio message比较小,基本不会对message进行拆分,一个message对应一个chunk,实际发送的情况为:
第1个Message的chunk的chunk type为0,因为前面没有可参考的chunk,此时chunk的长度为Basic Header(1 byte)+ Message Header(11 bytes)+ Payload(32 bytes)= 44 bytes。
第2个Message的chunk可与前一个共用length、type id和msid,但是不可省略timestamp delta字段,所以chunk type为2,同理可得chunk长度为36 bytes。
第3个Message的chunk可以共用前一个的timestamp delta字段,所以chunk type为3,chunk长度为33 bytes。
第4个Message的chunk可以共用前一个的timestamp delta字段,所以chunk type为3,chunk长度为33 bytes。
5.2.2 Video Example
一个video的消息流:Message Type ID = 9 表示视频,
Payload length为307 > 128字节(chunk data默认是128字节),所以需要将Message拆分成多个chunks,首个chunk使用Type 0;
之后的chunk由于是同一个Message拆分而来,以上字段都可共享,所以直接使用Type 3
需要注意的是,第一个chunk的length需要传入整个message的负载长度即307
5.3.1 推流过程
1、客户端发送握手请求,和服务器完成握手。
2、客户端发送命令消息中的“连接”(connect)到服务器,请求与一个服务应用实例建立连接。
3、服务器接收到连接命令消息后,发送确认窗口大小(Window Acknowledgement Size)到客户端,同时连接到连接命令中提到的应用程序。
4、服务器发送设置带宽协议(Set Peer Bandwidth)消息到客户端。
5、客户端处理设置带宽协议控制消息后,发送确认窗口大小(Window Acknowledgement Size)到服务器端。
6、服务器发送用户控制消息中的“流开始”(StreamBegin)消息到客户端,通知客户端流成功创建,可用于通信。
7、服务端发送connect的“应答消息”(_result),通知客户端连接的状态。
8、客户端发送网络连接命令的“创建流”(createStream)消息到服务端,以创建消息通信的逻辑通道。音频、视频和元数据的发布通过使用createStream命令创建的流通道执行。服务端发送createStream的“应答消息”(_result)。
9、客户端发送网络流命令的“发布”(publish)到服务端,将命名流发布到服务器。其它客户端可以使用此流名来播放流,接收发布的音频,视频,以及其他数据消息。
10、客户端发送命令消息或音视频数据至服务端。
注意:
上图中在看发送的数据的同时也要关注发送端和接收端的ip,弄清楚是客户端还是服务器发送的消息;在推流的时候往往能看到 FCPublish、releasestream 消息,这两种消息官方文档中没有描述,在srs代码中只是接收了消息并给出了应答,但是在代码内部并没有处理这两种消息
5.3.2 拉流过程
1、客户端发送握手请求,和服务器完成握手。
2、客户端发送命令消息中的“连接”(connect)到服务器,请求与一个服务应用实例建立连接。
3、客户端发送网络连接命令的“创建流”(createStream)消息到服务端,以创建消息通信的逻辑通道。音频、视频和元数据的发布通过使用createStream命令创建的流通道执行。服务端发送createStream的“应答消息”(_result)。
4、客户端发送网络流命令中的“播放”(play)到服务端。
5、服务端发送协议控制消息中的“设置块大小”(Set Chunk Size)到客户端设置chunk大小。
6、服务器发送另一个协议控制消息(用户控制),指定事件“StreamIsRecorded”和该消息中的流ID。消息在前2字节中携带事件类型,在后4字节中携带流ID。
7、服务端发送用户控制消息中的“流开始”(StreamBegin)消息到客户端,通知客户端流成功创建,可用于通信。
8、如果客户端发送的播放命令成功,则服务器发送onStatus命令消息NetStream.Play.Start和NetStream.Play.Reset。仅当客户端发送的播放命令设置了重置标志时,服务器才会发送NetStream.Play.Reset。如果未找到要播放的流,服务器将发送onStatus消息NetStream.Play.StreamNotFound。
9、服务端发送音视频数据到客户端。
1、客户端发送网络流命令中的“播放2”(play2)到服务端
2、服务端发送不同码率的音视频数据到客户端
1、message分成chunk的目的:防止一个大的数据包传输时间过长,阻塞其它数据包的传输。
2、chunk分成4中形式的目的:减少重复数据发送,提高chunk data的占比。
3、消息优先级:总结起来就是协议先行,数据次之。详见“五、数据传输过程-> 5.1->细节说明->3、RTMP消息优先级”。
4、协议控制消息(Protocol Control Messages):中断消息只能是数据发送端给接收端(如果是推流就是客户端发给服务端,如果是拉流则是服务端发给客户端),其它消息都可以双端发送。
5、命令消息(Command Message)
(1)先有网络连接命令再有网络流命令,也就是说先建立连接,才能传输数据。
(2)所有命令消息都需要应答,网络连接命中不同的命令应答格式不同,网络流命令的应答命令格式相同。
(3)除了调用(call)命令可以双端发送外,其余命令消息都是客户端向服务端发送,服务端向客户端发送应答消息。
1、使用场景:用于流式传输到 Flash 播放器,广泛应用于直播。
2、RTMP的优点
(1)低延迟:RTMP使用独占的 1935 端口,无需缓冲,可以实现低延迟。
(2)适应性强:所有 RTMP 服务器都可以录制直播媒体流,同时还允许观众跳过部分广播并在直播开始后加入直播流。
(3)灵活性:RTMP 支持整合文本、视频和音频,支持 MP3 和 AAC 音频流,也支持MP4、FLV 和 F4V 视频。
3、RTMP的缺点
(1)HTML5 不支持:标准HTML5 播放器不支持 RTMP 流。
(2)容易受到带宽问题的影响:RTMP 流经常会出现低带宽问题,造成视频中断。
(3)HTTP 不兼容:无法通过 HTTP 流式传输 RTMP,必须需要实现一个特殊的服务器,并使用第三方内容交付网络或使用流媒体视频平台。
(什么是RTMP 和 RTSP?它们之间有什么区别?_一口Linux的博客-CSDN博客_rtsp)
一个message就是帧,相当于一帧数据产生后立刻发送过来,所以延时较低。相比于HLS,一个切片生成后才会发送,延时就会很大。
AMF从Flash MX时代的AMF0发展到现在的AMF3。AMF3用作Flash Playe 9的ActionScript 3.0的默认序列化格式,而AMF0则用作旧版的ActionScript 1.0和2.0的序列化格式。 在网络传输数据方面,AMF3比AMF0更有效率。AMF3能将int和uint对象作为整数(integer)传输,并且能序列化ActionScript 3.0才支持的数据类型, 比如ByteArray,XML和Iexternalizable。(AMF的概念,产生原因和特点(AMF0和AMF3)_mosaic_born的博客-CSDN博客_amf0编码)
通过srs代码(SrsProtocol::read_message_payload)得知:接收端将接收到chunk的chunk data的大小加和,如果等于message payload(通过chunk->message header->message length获取)的则认为是同一个message。
csid是chunk的一个标识,csid和chunk的类型有关,详见“3.2.1 Basic Header”,所以同一类型的不同chunk的csid是相同的。csid和chunk是否属于同一个message没关系。
可以参考“3.2.2 Message Header->3、chunk->message header 和 message->message header 的关系”
消息优先级:总结起来就是协议先行,数据次之。详见“五、数据传输过程-> 5.1->细节说明->3、RTMP消息优先级”。
1、rtmp是数据传输协议,flv是音视频的封装格式。
2、rtmp和flv在音视频数据的封装上是相同的,即message payload的数据形式和flv tag data的数据形式是相同的,也就是说flv tag和rtmp message,除了header部分,body部分格式是完全一样的。
音频
视频