标准规范学习:
rtmp消息结构,包括几个部分:
时戳:4 byte,单位毫秒。超过最大值后会翻转。
长度:消息负载的长度。
类型ID:Type Id 一部分ID范围用于rtmp的控制信令。还有一部分可以供上层使用,rtmp只是透传。这样可以方便的在rtmp上进行扩展。
消息流ID:Message Stream ID,用于区分不同流的消息。
两个ID的区别:
Message stream:传输消息的逻辑通道。
Message stream ID:每个消息都有一个流id,用于指明属于哪个流。
Chunk:是更底层的一个概念。Message有可能过大,需要分割成一个个碎片,chunk就是一个消息的部分碎片。
Chunk stream:传输chunk的逻辑通道。
Chunk stream ID:用于标示chunk属于哪个逻辑通道。
message stream和chunk stream应该属于不同的层次,message属于应用层次消息,chunk属于更底层rtmp协议层次。
一个chunk stream 上能够跑多个message stream。message stream id在一个chunk stream下要不相等。
消息会切分成一个个小的块进行传输。
rtmp的时戳单位是毫秒。
连接握手流程图:
握手过程:服务器和客户端各自发送三个包:c0,c1,c2,s0,s1,s2,服务器必须在收到c0后才发送s0和s1,也可以等到c1才发送;服务端必须收到c1才能够发送s2,客户端必须收到s2才能够发送c2
c0和s0就是一个字节:表示协议版本号。现在是03。
01 234567
+-+-+-+-+-+-+-+-+
| version |
+-+-+-+-+-+-+-+-+
C0 and S0 bits
c1和s1长度1536,
抓包发现,zero字段不一定为0。
c2和s2的长度也是1536
通过vlc和nginx rtmp抓的包发现和规范不对应。安装规范,time和time2应该是为了计算带宽和延迟。
交互过程:
一般客户端一起发生c0和c1,然后服务器直接发送s0,1,2。客户端收到后发送c2,握手完成。三次交互就可以了。
块流:
一个连接可以穿输多个块流。每个块流有不同的id来区分。
块传输运行将大的消息包切割为小的消息包。防止大的数据比如视频阻塞连接,导致音频和信令也无法传递。
小的消息也可以成本更加低的传输,包头会压缩.
块流大小可以配置。有一个消息:set chunk size可以协商大小。大的块可以降低cpu 负载,但是可能会阻塞重要消息;小的块不利于传输,特别是在低带宽的情况下。
块允许 层协议将大的消息 解 更小的消息,例如,防 体积大的但优先级小的消 息 (比如视频) 阻碍体积较小但优先级高的消息 (比如音频或者控制命 )——传送过程,音频和控制的优先级较高。
块的大小是 配置的 它 使用一个设置块大小的控制消息 行设置 (参考 5.4.1) 更大的块大小 降 CPU 开销,但在 宽 接时因 它的大量的写入也会延 他内容的传递 更小的块不利于高比特率的流化 所 块的大小设置 决于 体情况。——这是使用tcp必须要考虑的问题。块过大会影响音频和控制的发送。过小会影响效率和比特率。
块类型:
块基本头:
第一个字节都是这样。其中fmt用来指示Message Header。
cs id:
cs id是chunk stream id的缩写。范围是3——65599。可变字节,后面可以跟一个字节,也可以跟两个字节。跟几个字节是有第一个字节的后面6位的值决定的。这一点中文翻译写的不完整。
如果最后6位值为0,则表示后面只有一个字节。id范围是64——319,其实就是0——255,最大和最小加64。
如果最后6位值为1,则表示后面只有两个字节。id范围是64 - 65599,其实就是0——65535,最大和最小加64。j
计算方法比较特殊:(第三 个 节) * 256 + 第二 个 节 + 64
块流id应该是区分每一个流,比如,一个视频通话,包括视频和音频两个流。这里的意思应该是一个连接可以传递多个流,通过块流id进行区分。
块消息头
块消息头格式由块基本头的fmt字段决定。共四种。
fmt:
四种类型:0 1 2 3
包头必须尽可能的压缩。
type 0:用在流开始,或者时戳重置的时候。message header消息头共11byte。
三字节时戳:表示范围为0——16777215(0xffffff),如果大于等于16777215,则表示还有一个字节的扩展时戳。chunk header的第三部分就是extend timestamp。这一本部分是否存在由三个字节的值决定。注意,这个是绝对时戳,而后面的几种类型trunk都是时戳的增量;区分消息截止是根据消息的长度来进行的。那么输入处理主动丢弃包的情况?
type 1:message header有7byte,比type 0少了4个字节的message stream id,使用上一个块的id。大小可变的消息(比如视频的每一帧数据)往往会在第一帧(第一帧的第一个chunk需要用type0)后面的帧的第一个chunk中使用type1。为什么?因为可变数据,必须有长度信息记录。
type2:只有三个字节。没有message stream id和message 长度。全部使用同一个chunk stream的上一个chunk的。适用于长度不变的步伐传输。
type 3:0个字节的message header。时戳,长度,消息流id全部使用同一个chunk stream的上一个chunk。当一个消息切分后划分到多个chunk的时候,除了带个chunk,所有的其他chunk应该使用type3。——如何判断包结束?根据消息的长度,第一个chunk中的长度应该是一个消息完成的长度。
四种格式的使用:
第一个消息的第一个chunk 使用type0,携带全部的信息;后面的chunk使用type3;
第二个消息,如果是视频消息,长度可变,则第一个chunk使用type1,值丢弃message stream id即可。第二个消息的其他chunk使用type3。
如果是音频,且长度相同,则第一个消息用type0+type3,第二个消息用type2+type3。
如果音频消息,长度,消息间时戳增量(delta)都相同,则第一个消息可以用type0+type3,后面的所有消息全部用type3即可。这样的话也要求第一个消息的时戳必须和消息间的时戳增量相同。
注意:除type0外,其他的全部是时戳的增量。而不是时戳绝对值。
从上面来看,音频和视频的chunk id应该是不一样的。否则无法区分。因为有些块是没有message stream id,只有chunk stream id的。
公共字段定义:
1、timestamp delta:表示的是上一个chunk 和当前chunk的时戳差。不是消息的时间差。如果大于等于16777215,则表示还有一个字节的扩展时戳。chunk header的第三部分就是extend timestamp。
2、消息长度:消息的长度。他和chunk的payload的大小是不同的。chunk size length是所有chunk的大小再加最后一个大小。——消息长度是消息实际的大小。。
3、message type id:标示消息类型,比如音频,视频,控制等。
4、message stream id:以小端序存储。
Extended Timestamp:当时戳大于
play2:可以实现比特率的切换。
message length,chunk size,tcp分包和消息的切割
终于弄明白其中的原理了。本来这几个概念和方法在规范中描述不是很清楚,结合抓包终于弄清楚 了。
message length:及时消息本身的大小。这个消息要在chunk中进行传输。
chunk size:chunk的大小。chunk的大小默认是128。可以通过消息设置chunk size 的最大值。每个chunk的大小最大不能够超过max chunk size。
明白上面两点后进行分析:
1、如果message length小于等于max chunk size,则一个chunk中就包含着一个message。
2、如果message length大于max chunk size,则需要将这个message切分为多个chunk。前面几个chunk size必须是max
size,最后一个就是剩余的大小。
tcp分包过程:
1、收到chunk,明确chunk stream id,对一个chunk stream的数据进行处理。
2、判断fmt类型,确定消息长度。
3、如果长度小于等于max chunk size,则这个chunk body的大小就是message length。
4、处理完这个chunk,跳过chunk header 和chunk body,就是下一个chunk。
5、如果长度大于max chunk size,则chunk body的大小就是max chunk size。处理这一部分数据,然后跳过max chunk size找到下一个chunk的头。有消息长度减去max chunk size计算剩余的数据长度,判断剩余长度是否大于max chunk size,如果大于,则表明这个chunk 大小还是max chunk size。以此类推,直到最后一个长度小于等于max chunk size,那么这个长度就是这个chunk的实际长度,并且是这个消息的最后一个消息切片。然后把所有的有效切片拼接起来,就是完整的消息。后面收到的将是另外一个消息。
协议控制消息:
RTMP使用1,2,3,4,5,6的message type id作为控制消息。控制协议的message stream id必须是0, chunk stream id必须是2(2的作用。0,1用来表示字节个数,2是信令,3-6xxxx是实际的。)。控制信令一收到就生效——就是没有协商过程。。。——时戳可以忽略。
set chunk size (1):设置chunk的最大size
message type id 为1。共四个字节:
第一位必须为0。
默认值是128,服务端和客户端都可以设置chunk size。注意,一个交互中chunk size是相互独立的,也就是,客户端可以设置他发送过去的chunk size,服务端也可以设置他发送过去的chunk size。两个是没有影响的。
chunk size最小建议128,必须大于等于1。
chunk size的范围是1到0x7fffffff(2147483647),但是因为message header中message length只有三个字节,所以chunk size的最大值是16777215 如果大于此,就去他。
Abort Message (2)
message type id为2,四个字节,携带的内容是chunk stream id。用于通知对端,如果这个chunk stream还有消息正在等待接收未到达的消息内容,则停止,并丢弃原先接受的内容。可以用在带宽有限是优先发送音频和信令,丢弃视频。
Acknowledgement (3) Window Acknowledgement Size (5)
Window Acknowledgement Size用于设置窗口确认大小,Acknowledgement是窗口确认消息。
会话开始时,双方都要先对端发送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是不再发送进一步的消息的——这样会容易引起错误,导致再也发送不出消息了。
Set Peer Bandwidth (6)
设置对端输出带宽。对端是通过设置Window Acknowledgement Size来实现流量控制的。超过Window Acknowledgement Size后未确认发送端将不再发送消息。所以对端收到set peer bandwidth后,如果之前发送的Window Acknowledgement Size和这里写的的Window Acknowledgement Size不一样,一般会发送一个Window Acknowledgement Size。
message format
上面的协议控制消息的type id是1,2,3,5,6(这些是chunk stream的控制),没有4。4是另外一个类型:
User Control Messages (4):用户控制协议,是RTMP stream的控制协议。同样,chunk stream id要设置为2,message stream id要设置为0。
消息体格式:两个字节的event type,后面跟可变长度的event data,
RTMP Command Messages
客户端和服务器之间可以发送的消息包括:音频消息,视频消息,数据消息,命令消息。命令消息采用AMF编码。
command message type 20,17表示是命令消息。20表示使用AMF0编码,17表示使用AMF3编码。
命令消息包括connect, createStream, publish, play, pause ,响应消息使用onstatus,result。
Data Message (18, 15):数据消息,用于传输Metadata或用户数据。18表示AMF0,15是AMF3。
Shared Object Message (19, 16):19表示AMF0,16是AMF3。
Audio Message (8):音频消息
Video Message (9):视频消息
Aggregate Message (22):一个消息中包含多个消息。
Types of Commands( 20,17)
netConnetion 命令:
connect :请求连接到服务器的应用实例。
call:请求一个远程调用。
creatStream:创建一个逻辑通道,用于客户端来发送音频,视频,描述数据。netConnection是默认通道,message stream id是0,creatStream使用0作为message stream id。
NetStream Commands:
多个Netstream可以共用一个netconnection。但是stream的id是在什么时候确定的???——疑问
play2:切换到不同的比特率的流上,而不用更改播放时间。这是一个有用的功能。这个消息是在paly之后再发送。
deleteStream:删除流。
receiveAudio:是否接受音频。如果为false,服务器不用回复。如果为true,服务器回复两个状态:
NetStream.Seek.Notify and NetStream.Play.Start
receiveVideo:同样。
publish:发布一个流到server。需要onStatus响应。
seek:偏移。毫秒为单位。
pause:暂停。
example:
首先,connect是连接的服务器端的应用(nginx rtmp有配置多个应用。)
视频裸数据是如何打包进rtmp包的?
ffmpeg在flv和h264文件将的转换是有问题的。
视频性格的数据包格式和flv的格式非常的像,基本上就是flv格式的变种。参考flv文件格式官方协议详解。
视频相关包包括:onMetaData,AVCDecoderConfigurationRecord,h264数据。和flv文件中的区别是flv文件用tag头,rtmp中相关信息是放在rtmp头中。至于内部内容是一样的。
AMF0编码格式
- *AMF数据类型:
- *Type Byte code
- *Number 0x00
- *Boolean 0x01
- *String 0x02
- *Object 0x03
- *MovieClip 0x04
- *Null 0x05
- *Undefined 0x06
- *Reference 0x07
- *MixedArray 0x08
- *EndOfObject 0x09
- *Array 0x0a
- *Date 0x0b
- *LongString 0x0c
- *Unsupported 0x0d
- *Recordset 0x0e
- *XML 0x0f
- *TypedObject (Class instance) 0x10
- *AMF3 data 0×11
amf编码:对个数据的合集,数据的边界通过不同的类型,以及不同类型的长度来区分。
第一个字节是数据类型,然后根据数据类型确定数据的长度。其中,string是可变长度,两个字节长度值。
如果是object,则object内部是个字典,key是字符串,value是数据,这里还要对数据根据类型对数据解析,同上。