RTMP协议详解
一.总体介绍
RTMP协议是应⽤层协议,是要靠底层可靠的传输层协议(通常是TCP)来保证信息传输的可靠性的。在 基于传输层协议的链接建⽴完成后,RTMP协议也要客户端和服务器通过“握⼿”来建⽴基于传输层链接之 上的RTMP Connection链接,在Connection链接上会传输⼀些控制信息,如 SetChunkSize,SetACKWindowSize。其中CreateStream命令会创建⼀个Stream链接,⽤于传输具体的 ⾳视频数据和控制这些信息传输的命令信息。RTMP协议传输时会对数据做⾃⼰的格式化,这种格式的消 息我们称之为RTMP Message,⽽实际传输的时候为了更好地实现多路复⽤、分包和信息的公平性,发送 端会把Message划分为带有Message ID的Chunk,每个Chunk可能是⼀个单独的Message,也可能是 Message的⼀部分,在接受端会根据chunk中包含的data的⻓度,message id和message的⻓度把 chunk还原成完整的Message,从⽽实现信息的收发。
RTMP 是 Real Time Messaging Protocol( 实时消息传输协议) 的首字母缩写。该协议基于 TCP,是一个协议族,包括 RTMP 基本协议及 RTMPT/RTMPS/RTMPE 等多种变种。
RTMP 与 HTTP 一样, 都属于 TCP/IP 四层模型的应用层。
上述红色部分的含义如下图:
二. 握手
要建⽴⼀个有效的RTMP Connection链接,⾸先要“握⼿”:客户端要向服务器发送C0,C1,C2(按序)三个 chunk,服务器向客户端发送S0,S1,S2(按序)三个chunk,然后才能进⾏有效的信息传输。RTMP协议 本身并没有规定这6个Message的具体传输顺序,但RTMP协议的实现者需要保证这⼏点:
- 客户端要等收到S1之后才能发送C2
- 客户端要等收到S2之后才能发送其他信息(控制信息和真实⾳视频等数据)
- 服务端要等到收到C0之后发送S1
- 服务端必须等到收到C1之后才能发送S2
- 服务端必须等到收到C2之后才能发送其他信息(控制信息和真实⾳视频等数据)
如果每次发送⼀个握⼿chunk的话握⼿顺序会是这样:
理论上来讲只要满⾜以上条件,如何安排6个Message的顺序都是可以的,但实际实现中为了在保证握⼿ 的身份验证功能的基础上尽量减少通信的次数,⼀般的发送顺序是这样的,这⼀点可以通过wireshark抓 ffmpeg推流包进⾏验证:
|client|Server |
|----C0+C1---->|
|<----S0+S1+S2----|
|----C2---->|
握手完成后,就可以发送数据了,那么这个数据的形式是什么样子的呢? 是 RTMP Chunk Stream,那么这个RMP dChunk Stream 具体是啥呢?
三. RTMP Chunk Stream (重点)
Chunk Stream是对传输RTMP Chunk的流的逻辑上的抽象,客户端和服务器之间有关RTMP的信息都在 这个流上通信。这个流上的操作也是我们关注RTMP协议的重点。
3.1 Message(消息)
这⾥的Message是指满⾜该协议格式的、可以切分成Chunk发送的消息,消息包含的字段如下:
- Timestamp(时间戳):消息的时间戳(但不⼀定是当前时间,后⾯会介绍),4个字节
- Length(⻓度):是指Message Payload(消息负载)即⾳视频等信息的数据的⻓度,3个字节
- TypeId(类型Id):消息的类型Id,1个字节
- Message Stream ID(消息的流ID):每个消息的唯⼀标识,划分成Chunk和还原Chunk为Message 的时候都是根据这个ID来辨识是否是同⼀个消息的Chunk的,4个字节,并且以⼩端格式存储 (Message Stream ID如何产⽣?audio和video使⽤不同的Message Stream ID)
3.2 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的控制信息(⻅ spec 5.4.1 )可以设置Chunk数据量的最⼤值,在发送端和接受端会各⾃维护⼀个Chunk Size(srs流媒 体服务器默认是60000),可以分别设置这个值来改变⾃⼰这⼀⽅发送的Chunk的最⼤⼤⼩。⼤⼀点的 Chunk减少了计算每个chunk的时间从⽽减少了CPU的占⽤率,但是它会占⽤更多的时间在发送上,尤其 是在低带宽的⽹络情况下,很可能会阻塞后⾯更重要信息的传输。⼩⼀点的Chunk可以减少这种阻塞问 题,但⼩的Chunk会引⼊过多额外的信息(Chunk中的Header),少量多次的传输也可能会造成发送的间 断导致不能充分利⽤⾼带宽的优势,因此并不适合在⾼⽐特率的流中传输。在实际发送时应对要发送的数 据⽤不同的Chunk Size去尝试,通过抓包分析等⼿段得出合适的Chunk⼤⼩,并且在传输过程中可以根据 当前的带宽信息和实际信息的⼤⼩动态调整Chunk的⼤⼩,从⽽尽量提⾼CPU的利⽤率并减少信息的阻塞 机率。
3.3 Chunk Format(块格式)Chunk的默认⼤⼩是128字节
3.3.1 Basic Header(基本的头信息):
Basic Header是变⻓的,Basic Header的⻓度可能是1,2,或3个字节。也就是8/16/24位.
包含了 chunk type(chunk的类型)和 chunk stream ID(流通道Id,也叫做CSID)
CSID 为0,1,2,
当CSID 为0时,表示了Basic Header占⽤2个字节
当CSID 为1时,表示了Basic Header占⽤3个字节
当CSID 为2时,表示了Basic Header占⽤1个字节 ,代表该chunk是控制信息和⼀些命令信息,后⾯会有详细的介绍。(这块不确定,还需要再往后看)
当Basic Header为1个字节时,CSID占6位,6位最多可以表示64个数,因此这种情况下CSID在 [0, 63] 之间,其中⽤户可⾃定义的范围为 [3,63] ,实际是可以⽤2开始⽤。
当Basic Header为2个字节时,CSID占只占8位,第⼀个字节除chunk type占⽤的bit都置为0,第⼆ 个字节⽤来表示CSID-64,8位可以表示 [0, 255] 共256个数,ID的计算⽅法为(第⼆个字节+64),范围为 [64,319]。
注意如果不加64,那么就和 Basic Header为1个字节 时表示的数据重复了,因此规定了要加64
当Basic Header为3个字节时,以在此字段⽤3字节版本编码。ID的计算⽅法为(第三字节 *256+第⼆字节+64)(Basic Header是采⽤⼩端存储的⽅式),范围为 [64,65599]
可以看到2个字节和3个字节的Basic Header所能表示的CSID是有交集的 [64,319],但实际实现时还 是应该秉着最少字节的原则使⽤2个字节的表示⽅式来表示 [64,319] 的CSID。
3.3.2 Message Header(消息的头信息)
包含了要发送的实际信息的描述信息。(这个实际信息 可能是完整的,也可能是⼀部分)
Basic Header的chunk type 决定了Message Header的格式和 ⻓度 。
共有4种不同的格式。
由上⾯所提到的Basic Header中的fmt 字段控制。
其中第⼀种格式可以表示其他三种表示的所有数据,
但由于其他三种格式是基于对之前chunk 的差量化的表示,因此可以更简洁地表示相同的数据,实际使⽤的时候还是应该采⽤尽量少的字节表示相 同意义的数据。以下按照字节数从多到少的顺序分别介绍这4种格式的Message Header。
Type=0: 占⽤11个字节
type=0时Message Header占⽤11个字节,其他三种能表示的数据它都能表示,但在chunk stream的 开始的第⼀个chunk和头信息中的时间戳后退(即值与上⼀个chunk相⽐减⼩,通常在回退播放的时候会出 现这种情况)的时候必须采⽤这种格式。
- timestamp(时间戳):占⽤3个字节,因此它最多能表示到16777215=0xFFFFFF=2 -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,和Basic Header的CSID ⼀样,它采⽤⼩端存储的⽅式,
Type = 1:占⽤7个字节
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:占⽤3个字节
type=2时Message Header占⽤3个字节,相对于type=1格式⼜省去了表示消息⻓度的3个字节和表示消 息类型的1个字节,表示此chunk和上⼀次发送的chunk所在的流、消息的⻓度和消息的类型都相同。余下 的这三个字节表示timestamp delta,使⽤同type=1。
Type = 3:占⽤0字节
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
4种type对⽐
(type 3⽆字段就不画上去了)
3.3.3 Extended Timestamp(扩展时间戳)
上⾯我们提到在chunk中会有时间戳timestamp和时间戳差timestamp delta,并且它们不会同时存 在,只有这两者之⼀⼤于3个字节能表示的最⼤数值0xFFFFFF=16777215时,才会⽤这个字段来表示真正 的时间戳,否则这个字段为0。扩展时间戳占4个字节,能表示的最⼤数值就是0xFFFFFFFF=4294967295。当扩展时间戳启⽤时,timestamp字段或者timestamp delta要全置为0xFFFFFF,表示 应该去扩展时间戳字段来提取真正的时间戳或者时间戳差。注意扩展时间戳存储的是完整值,⽽不是减去 时间戳或者时间戳差的值。
3.3.4 Chunk Data(块数据):
⽤户层⾯上真正想要发送的与协议⽆关的数据,⻓度在(0,chunkSize]之间。
3.3.5 chunk表示例1
这个例⼦显示了⼀个简单的⾳频信息流。这个例⼦演示了信息的冗余。
⾸先包含第⼀个Message的chunk的Chunk Type为0,因为它没有前⾯可参考的chunk,timestamp 为1000,表示时间戳。type为0的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个字节。 最后实际发送的chunk如下:
3.3.6 chunk表示例2
此示例说明了⼀个消息因为太⻓,以⾄于⽆法适⽤⼀个128字节的chunk,从⽽被分解成多个 chunk。
注意到Data的Length=307>128,因此这个Message要切分成⼏个chunk发送,第⼀个chunk的Type= 0,Timestamp=1000,承担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 个字节。 最后实际发送的chunk如下:
从两个例⼦中注意到,类型3的chunk可以⽤在两种不同的⽅式中。第⼀种是指定消息的继 续。第⼆种是指定⼀个新的消息的开始,它的头可以来⾃于现有的状态数据。
3.4 协议控制消息(Protocol Control Message)
在RTMP的chunk流会⽤⼀些特殊的值来代表协议的控制消息,
它们的Message Stream ID必须为 0(代表控制流信息),CSID必须为2,
Message Type ID可以为1,2,3,5,6,具体代表的消息会在 下⾯依次说明。
控制消息的接受端会忽略掉chunk中的时间戳,收到后⽴即⽣效。
Message Type ID = 1 ------------ Set Chunk Size(ID=1)
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。在实际写代码的时候⼀般会把chunk size设置的很⼤,有的会 设置为4096,FFMPEG推流的时候设置的是 60*1000,这样设置的好处是避免了频繁的拆包组包,占 ⽤过多的CPU。
以下为代表Set Chunk Size消息的chunk的Data:
其中第⼀位必须为0,chunk Size占31个位,最⼤可代表2147483647=0x7FFFFFFF=2 -1,但实际 上所有⼤于16777215=0xFFFFFF的值都⽤不上,因为chunk size不能⼤于Message的⻓度,表示 Message的⻓度字段是⽤3个字节表示的,最⼤只能为0xFFFFFF。
Message Type ID=2 ----------- Abort Message (ID=2)
Abort Message(Message Type ID=2):当⼀个Message被切分为多个chunk,接受端只接收到了部 分chunk时,发送该控制消息表示发送端不再传输同Message的chunk,接受端接收到这个消息后要丢 弃这些不完整的chunk。Data数据中只需要⼀个CSID,表示丢弃该CSID的所有已接收到的chunk。
Message Type ID=3 和 Message Type ID=5 Acknowledgement (ID=3)和Window Acknowledgement Size (ID=5)
会话开始时,双⽅都要先对端发送Window Acknowledgement Size,⽤于指明期望获得确认的⼤⼩。当 ⼀端收到内容⼤⼩超过Window Acknowledgement Size,就要像对⽅发送Acknowledgement。
1、会话开始计算收到byte个数的时间点是收到Window Acknowledgement Size消息开始。
2、byte size不包括tcp包头,应该是chunk的大小,即从tcp 的recv函数中获得的内容大小。
3、双方都要向对方发送Window Acknowledgement Size和Acknowledgement。
4、发送端发送完Window Acknowledgement Size消息后,没有收到Acknowledgement是不再发送进行步的消息的——这样会容易引起错误,导致再也发送不出消息了。
Window Acknowledgement Size (ID=5):
‘客户端或者服务器端发送这条消息来通知对端发送和应答之间的窗口大小。发送者在发
送完窗口大小字节之后期望对端的确认。接收端在上次确认发送后接收到的指示数值后,或
者会话建立之后尚未发送确认,必须发送一个确认。
对于拉流端,一般在收到av_createStream后,接着play,然后发送Acknowledgement 以让服务器继续发送数据。
Acknowledgement (ID=3):
客户端或者服务器在接收到等同于窗口大小的字节之后必须要发送给对端一个确认。窗
口大小是指发送者在没有收到接收者确认之前发送的最大数量的字节。这个消息定义了序列
号,也就是目前接收到的字节数。
实际 Window Acknowledgement Size 跟 Acknowledgement 非常鸡肋,因为 TCP 本身有滑动窗口跟ACK。所以一般 Window Acknowledgement Size 会设置得非常大。
Message Type ID=6 Set Peer Bandwidth (Message Type ID=6)
Set Peer Bandwidth(Message Type ID=6):限制对端的输出带宽。接受端接收到该消息后会通过设置消息中的Window ACK Size来限制已发送但未接受到反馈的消息的大小来限制发送端的发送带宽。如果消息中的Window ACK Size与上一次发送给发送端的size不同的话要回馈一个Window Acknowledgement Size的控制消息。
1、Hard(Limit Type=0):接受端应该将Window Ack Size设置为消息中的值。
2、Soft(Limit Type=1):接受端可以讲Window Ack Size设为消息中的值,也可以保存原来的值(前提是原来的Size小与该控制消息中的Window Ack Size)。
3、Dynamic(Limit Type=2):如果上次的Set Peer Bandwidth消息中的Limit Type为0,本次也按Hard处理,否则忽略本消息,不去设置Window Ack Size。
3.5 Command Message(命令消息,Message Type ID=17或20)
表示在客户端 和 服务器间传递的在对 端执⾏某些操作的命令消息,如connect表示连接对端,对端如果同意连接的话会记录发送端信息并返 回连接成功消息,publish表示开始向对⽅推流,接受端接到命令后准备好接受对端发送的流信息,后 ⾯会对⽐较常⻅的Command Message具体介绍。当信息使⽤AMF0编码时,Message Type ID= 20,AMF3编码时Message Type ID=17.
3.6 Data Message(数据消息,Message Type ID=15或18)
Data Message(数据消息,Message Type ID=15或18):传递⼀些元数据(MetaData,⽐如视频 名,分辨率等等)或者⽤户⾃定义的⼀些消息。当信息使⽤AMF0编码时,Message Type ID=18, AMF3编码时Message Type ID=15.
3.7 Shared Object Message(共享消息,Message Type ID=16或19)
Shared Object Message(共享消息,Message Type ID=16或19):表示⼀个Flash类型的对象,由 键值对的集合组成,⽤于多客户端,多实例时使⽤。当信息使⽤AMF0编码时,Message Type ID= 19,AMF3编码时Message Type ID=16.
3.8 Audio Message(⾳频信息,Message Type ID=8):⾳频数据。
Audio Message(⾳频信息,Message Type ID=8):⾳频数据。
3.9 Video Message(视频信息,Message Type ID=9):视频数据。
Video Message(视频信息,Message Type ID=9):视频数据。
3.10 Aggregate Message (聚集信息,Message Type ID=22):多个RTMP⼦消息的集合
Aggregate Message (聚集信息,Message Type ID=22):多个RTMP⼦消息的集合
3.11 User Control Message Events(⽤户控制消息,Message Type ID=4)
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 Type Id=4。
四 3.5中Command Message(命令消息,Message Type ID=17或20)详解
表示在客户端 和 服务器间传递的在对 端执⾏某些操作的命令消息,如connect表示连接对端,对端如果同意连接的话会记录发送端信息并返 回连接成功消息,publish表示开始向对⽅推流,接受端接到命令后准备好接受对端发送的流信息,后 ⾯会对⽐较常⻅的Command Message具体介绍。当信息使⽤AMF0编码时,Message Type ID= 20,AMF3编码时Message Type ID=17.
实际使⽤时只是使⽤了ID=20,
发送端发送时会带有命令的名字,如connect,TransactionID表示此次命
令的标识,Command Object表示相关参数。接受端收到命令后,会返回以下三种消息中的一种:
_result消息表示接受该命令,对端可以继续往下执行流程。
_error消息代表拒绝该命令要执行的操作。
method name消息代表要在之前命令的发送端执行的函数名称。这三种回应的消息都要带有收到的命令消息中的TransactionId来表示本次的回应作用于哪个命令。
可以认为发送命令消息的对象有两种:
一种是NetConnection,表示双端的上层连接。
一种是NetStream,表示流信息的传输通道,控制流信息的状态,如Play播放流,Pause暂停。
4.1、NetConnection Commands(连接层的命令)
用来管理双端之间的连接状态,同时也提供了异步远程方法调用(RPC)在对端执行某方法,以下是常见的连接层的命令:
4.1.1、connect:用于客户端向服务器发送连接请求
握手之后先发送一个connect 命令消息,这些信息是以AMF格式发送的,消息的结构如下:
第三个字段中的Command Object中会涉及到很多键值对,使用时可以参考协议的官方文档。
消息的回应有两种,_result表示接受连接,_error表示连接失败。
以下是连接命令对象中使用的名称-值对的描述:
命令执行时消息流动如下:
1、 客户端发送 connect 命令到服务器端以请求对服务器端应用实例的连接。
2、 收到 connect 命令后,服务器端发送协议消息 ‘窗口确认大小’ 到客户端。服务器端也会连接到 connect 命令中提到的应用。
3、 服务器端发送协议消息 ‘设置对端带宽’ 到客户端。
4、在处理完协议消息 ‘设置对端带宽’ 之后客户端发送协议消息 '窗口确认大小’到服务器端。
5、 服务器端发送另一个用户控制消息 (StreamBegin) 类型的协议消息到客户端。
6、 服务器端发送结果命令消息告知客户端连接状态 (success/fail)。这一命令定义了事务ID(常常为 connect 命令设置为 1)。这一消息也定义了一些属性,比如 FMS 服务器版本 (字符串)。之外,它还定义了݊其他连接关联到的信息,比如 level (字符串)、code (字符串)、 description (字符串)、objectencoding (数字) 等等。
4.1.2、Call:用于在对端执行某函数
即常说的RPC:远程进程调用,消息的结构如下:
如果消息中的TransactionID不为0的话,对端需要对该命令做出响应,响应的消息结构如下:
4.1.3、Create Stream:创建传递具体信息的通道
创建传递具体信息的通道
从而可以在这个流中传递具体信息,传输信息单元为Chunk。
当发送完createStream消息之后,解析服务器返回的消息会得到一个stream ID, 这个ID也就是以后和服务器通信的 message stream ID, 一般返回的是1,不固定。
4.2、NetStream Commands(流连接上的命令)
Netstream建立在NetConnection之上,通过NetConnection的createStream命令创建,用于传输具体的音频、视频等信息。在传输层协议之上只能连接一个NetConnection,但一个NetConnection可以建立多个NetStream来建立不同的流通道传输数据。
以下会列出一些常用的NetStream Commands,服务端收到命令后会通过onStatus的命令来响应客户端,表示当前NetStream的状态。
onStatus命令的消息结构如下:
4.2.1、play(播放)
由客户端向服务器发起请求从服务器端接受数据(如果传输的信息是视频的话就是请求开始播流),可以多次调用,这样本地就会形成一组数据流的接收者(创建一个播放列表)。注意其中有一个reset字段,表示是覆盖之前的播流(设为true)还是重新开始一路播放(设为false)。
play命令的结构如下:
4.2.3、deleteStream(删除流)
用于客户端告知服务器端本地的某个流对象已被删除,不需要再传输此路流。
4.2.4、 receiveAudio(接收音频)
NetStream 通过发送 receiveAudio 消息来通知服务器端是否发送音频到客户端。
receiveAudio命令结构如下:
如果发送来的 receiveAudio 命令布尔字段被设为 false 时服务器端不发送任何回复。
如果这一标识被设为 true, 服务器端发送状态消息NetStream.Seek.Notify 和
NetStream.Play.Start 进行回复。
4.2.5、 receiveVideo(接收视频)
NetStream 通过发送receiveVideo 消息来通知服务器端是否发送视频到客户端。
receiveVideo命令结构如下:
如果发送来的 receiveVideo 命令布尔字段被设为 false 时服务器端不发送任何回复。
如果这一标识被设为 true, 服务器端发送状态消息NetStream.Seek.Notify 和
NetStream.Play.Start 进行回复。
4.2.6、 publish(推送数据)
由客户端向服务器发起请求推流到服务器。
publish命令结构如下:
4.2.7、 seek(定位流的位置)
定位到视频或音频的某个位置,以毫秒为单位。
seek命令的结构如下:
4.2.8、pause(暂停)
客户端告知服务端停止或恢复播放。
pause命令的结构如下:
如果Pause为true即表示客户端请求暂停的话,服务端暂停对应的流会返回NetStream.Pause.Notify的onStatus命令来告知客户端当前流处于暂停的状态,当Pause为false时,服务端会返回NetStream.Unpause.Notify的命令来告知客户端当前流恢复。如果服务端对该命令响应失败,返回_error信息。
五、数据消息(Data Message ID=15或18)(也是对应flv中的script data的tag type = 18 )
(数据消息,Message Type ID=15或18):传递一些元数据(MetaData,比如视频名,分辨率等等)或者用户自定义的一些消息。当信息使用AMF0编码时,Message Type ID=18,AMF3编码时Message Type ID=15。
六、共享消息(Shared Object Message ID=16或19)
(共享消息,Message Type ID=16或19):表示一个Flash类型的对象,由键值对的集合组成,用于多客户端,多实例时使用。当信息使用AMF0编码时,Message Type ID=19,AMF3编码时Message Type ID=16。
每个消息可以包含有不同事件。
支持以下事件:
七、音频消息(Audio Message ID=8)(也是对应flv中的audio data的tag type = 8 )
(音频信息,Message Type ID=8):音频数据。
八、视频消息(Video Message ID=9)(也是对应flv中的video data的tag type = 9 )
(视频信息,Message Type ID=9):视频数据。
九、聚集消息(Aggregate Message ID=22)
(聚集信息,Message Type ID=22):多个RTMP子消息的集合。
十、用户控制消息(User Control Message Events ID=4)
(用户控制消息,Message Type ID=4):告知对方执行该信息中包含的用户控制事件,比如Stream Begin事件告知对方流信息开始传输。和前面提到的协议控制信息(Protocol Control Message)不同,这是在RTMP协议层的,而不是在RTMP chunk流协议层的,这个很容易弄混。该信息在chunk流中发送时,Message Stream ID=0,Chunk Stream
Id=2,Message Type Id=4。
支持以下用户控制事件类型:
十一、推流流程
十二、拉流流程
学习建议
如果读者仔细读完了上⾯讲的RTMP协议,想必会觉得RTMP协议⾮常繁琐,事实也确实是这样, RTMP协议中充斥着很多冗余的字段,⽐如三次握⼿中的时间戳的校对,还有⼀些特殊的命令,如 FCPublish、UnFCPublish等,但在实际实现中为了保证更⼤兼容性通常还是要处理这些看似多余的命 令。加上Adobe对RTMP协议的实现细节有些并没有按照协议来或者协议中没有写清楚⾃⼰搞了⼀套实 现,其他应⽤开发时还要兼容Adobe错误的实现,从⽽使的RTMP也⼀直为开发者所诟病。但不管怎样, RTMP确实提供了⼀种能够全⾯并且实现简单的协议来保证流信息的传输,这⽅⾯暂时还没有⼀种更完善 更简洁的协议能够取代它在视频流开发中的地位。 新⼈⼀开始接触RTMP的时候肯定会觉得头⼤,这也是RTMP协议不简洁的后果。建议读者在学习时先 过⼀遍协议理解⼤概的概念和流程,然后对照wireshark抓的包,和协议进⾏⽐对,这样将理论和实践结合,应该会理解的更快⼀点。
源码参考
1. librtmp 2. ffmpeg rtmppkt.h 3. srs
csid是⾃定义的,主要不使⽤保留的0,1即可,⽐如ffmpeg的rtmppkt.h
对于chunk size的设置,ffmpeg保持和服务器⼀致。
librtmp的
RTMP_Write适合将FLV⽂件帧进⾏发送
RTMP_Read适合⽤来dump RTMP码流
源码
https://github.com/zkzszd/NativeCodec.git
https://github.com/Cooliodtryl/upstream-h.264-es-file-as-rtmp-.git
https://github.com/ireader/media-server
https://github.com/medooze/media-server 重点参
参考
直播推流实现RTMP协议的⼀些注意事项 https://www.cnblogs.com/lidabo/p/7232594.html RTMP 协议规范(中⽂版) https://www.cnblogs.com/Kingfans/p/7083100.html