对于RTMP协议来说,他的 传输单元是 chunk。一个 RTMP 包就是一个 chunk。所以 chunk 的格式,也就是 RTMP 报文的格式。chunk 的格式如下:
编辑切换为居中
可以看到 Chunk Header 由 3 部分组成。
Basic Header
Message Header
Extended Timestamp
先来 讲解 Basic Header 的格式,Basic Header 由 fmt + chunk stream id 组成,fmt (也叫 chunk type)固定是 2 位。但是 cs id 可以是 6位,也可以是 14 位,也可以是 22 位。
所以 Basic Header 可以是 1个 字节,也可以是2个字节或者 3个字节,这里不太好懂他的变长是如何实现的。我详细讲一下。
Basic Header 占一个字节的情况如下:
编辑
从上图可以看到 前面 2位 是 fmt ,这个字段主要用在后面,暂时不讲。然后还剩 6 位空间没用,所以 cs id 的范围应该是 0 ~ (2^6 - 1) ,也就是 0 ~ 63 。但是因为要实现变长数据,所以不能 直接是用 0~ 63,要预留 一些值作为标记。0~2 就是标记,也就是说 这 6位的数据不能是 0 到2。
所以 Basic Header 是 1个字节的情况下,3 ~ 63 才是 cs id 的取值范围。
接下来讲解 0 ~ 2 是怎么处理的。
1,第一个字节的后6位 如果是 000000,说明后面还有一个字节,就读取后面的一个字节,一个字节最多是 2^8 = 256 ,那是不是说,2字节的 情况下 cs id 的范围 是 0 ~ (2^8 - 1) ,也就是 0 ~ 255。不是,不能这样算,因为是之前一个字节不够用 才扩展到2个字节,所第二个字节的起始值是64,不是从0开始,所以 cs id 的范围 是 64 ~ 255 + 64。
所以 Basic Header 是 2个字节情况下,cs id 的范围是 64 ~319, cs id 不会是 64 以下的值,因为那是一个字节的情况。
2,第一个字节的后6位 如果是 000001,代表后面还有2个字节,两个字节能表示 0 ~ 65535,但是起始值是64,所以 cs id 的范围 是 64 ~ 65535 + 64。
所以 Basic Header 是 3个字节情况下,cs id 的范围是 64 ~ 65599,同时 cs id 不会是 319 以下的值,因为那是两个字节的情况。
纠正:3个字节情况下 cs id 可能是 319 以下的值,只是RTMP 标准说尽量用更少的字节。319以下的值也可以用3字节表示,只不过没必要。
3,第一个字节的后6位 如果是 000002,代表这个RTMP 包是一个 Protocol Control Messages。
接下来分析 Message Header,由于 Message Header 受到 Basic Header 的 fmt 字段影响,fmt 占 2位,所以 有 4 种 Message Header 。
Message Header 看标准文档跟 这篇文章 《rtmp协议详解》 就行,讲得很详细。
Chunk header 已经讲解完毕,还有两个 重点 没有讲解。
1,chunk stream id 是什么东西?
chunk stream id 缩写是 cs id,也就是 RTMP 包开头的 1~3个字节那个地方的数据。这个 cs id 是 chunk 流ID,每个 TCP 链接 可以有多个 cs id。 cs id 要结合 message stream id 来理解。
2,message stream id 是什么东西。
message stream id 就是消息的流ID,例如 音频流的 message stream id 是 1,视频流的 message stream id 是 2。
但是 视频流,音频流 对应的 cs id 可以是同一个。这就叫多路复用。多个流复用一个流通道。
接下来讲解,几种常用的 RTMP 包,括号() 里面的值是 message header 里面的 type id 。
1,Set Chunk Size (1):通知对面 自己会按照 这个 大小来切分数据。
2,Abort Message (2):中断,很少使用。
3,Window Acknowledgement Size (5):告诉发送方,我接受到这么多的数据之后就会发 Acknowledgement (3) 的RTMP包。
4,Acknowledgement (3) :确认已经收到多少数据。
实际 Window Acknowledgement Size 跟 Acknowledgement 非常鸡肋,因为 TCP 本身有滑动窗口跟ACK。所以一般 Window Acknowledgement Size 会设置得非常大。
5,Set Peer Bandwidth (6):客户端或服务端发送该消息来限制对端的输出带宽, 标准文档里面没有讲得很清楚,这个是限制带宽是怎么实现了,网上搜了,大多数也是英文的直接翻译,也没讲得特别清楚,这个逻辑要在源码里面找,先埋个坑,后续填。
RTMP 里面 经常见到的一种包 是 Action Message Format (AMF)。看名字就知道,这是一个 action 包。这种 RTMP 包的 message header 里面的 type id 是 0x14
例如,connect 就是一个 AMF 包,如下:
编辑切换为居中
编辑切换为居中
这种 AMF 包里面有一个 字符串,表示要执行哪些 action (动作)。
上面的RTMP chunk 格式,是 RTMP 协议栈内部的东西,对我们调用层来说是透明的,也就是说他内部切成多少chunk,message header 是怎样的,我们调用层不需要特别关注。
调用层真正丢向 RTMP 协议栈的数据格式是 RTMP Message Format。
重点: RTMP Chunk Format 是协议栈内部的,RTMP Message Format 是调用层自己的。
RTMP Message Format 的头部格式如下:
编辑切换为居中
头部后面就是实际的音频或者视频数据,Payload length 就是调用层发出去的包的总大小。这里第一个字节是 Message Type,在RTMP 协议栈内部会 转换成 Chunk 格式里面的 Message Header 里面的 type id。
补充: 实际上这个 RTMP Message Format 实际实现中并不存在,例如 librtmp 项目里,并没有提供一个接口 传递上面这种 RTMP Message Format 格式的buff。是直接到 Chunk Format 这一层。
调用层还有一个 User Control Messages 格式,对应的 chunk 格式就是 chunk 格式里面的 6位 cs id 等于 000010,也就是 等于2,然后 message header 的 type id 等于 4。
他标准文档 写的这些 User Control Messages 格式 跟 RTMP Message Format 实际上在实现的时候都是没有的,都是直接转成 chunk 格式发送。这个层是个虚拟层。
重点补充:
createStream 命令是向服务器请求 一个 Stream ID。应该是。
AMF 包 会有一个 trans id 事务ID,然后服务器会返回一个 _result 带着客户端的 trans id ,表示执行成功或失败。
AMF 大部分都是远程过程调用。
还有一些 play, pause 也是 AMF 包,type id 是14 。