RTMP(2):Chunk 和 Message
这一篇笔记主要记录对 chunk 和 message 两个概念的理解。
一个不严谨的比喻
如果把一条 TCP 链接比喻成一条铁路,那么:
- 一个字节就是一份货物。
- 一个 chunk 就是一辆火车。
- 一条 chunk stream 就是一条轨道。
- 一份 message 就是包含若干货物的清单,这些货物被装载至一辆或多辆火车(chunk)上。
需要注意:
- 两个方向上的 chunk stream 数量无需相等,编号可同可不同。
- 同一个 message 产生的多个 chunk 只会在一条 chunk stream 串行发送。
- 先发送的 chunk 一定先到达。
- 多条 chunk stream 复用了一条 TCP 链接。
说的直接点:
- chunk 就是一个报文——预先约定清楚的 header 字段(ID,类型,时间戳等等),以及变长的负载数据(chunk data)。
- chunk stream 就是一串有相同 ID 的 chunk,借助 TCP 链接在网络上流动,从发送端流向接收端。
Chunk - 最小传输单元(这名是我胡诌的)
在 RTMP 层面,chunk 就是最小的传输单元,数据封装为 chunk 后方能通过 RTMP 协议进行发送。
Chunk 的字段可划分为四部分,如下图示:
Basic Header (1 - 3 bytes)
本部分包含两个字段,format 和 chunk stream id。
Format 仅占两个比特,其值决定了 message header 的长度。
Chunk stream id 是一个变长的整数,取值范围为 [2, 65599],可能占用 14,22 或 6 个比特。
CS ID 占用 14 个比特
RTMP规定当第 2 - 7 个比特的值为 0 时,chunk stream id 占用 14 个比特,其值减去 64 后存储在第 8 - 15 个比特中。取值范围为 [64, 319]。
CS ID 占用 22 个比特
RTMP规定当第 2 - 7 个比特的值为 1 时,chunk stream id 占用 22 个比特,其值减去 64 后存储在第 8 - 23 个比特中。取值范围为 [64, 65599]。
CS ID 占用 6 个比特
RTMP规定当第 2 - 7 个比特的值在区间 [2,63] 内时,这 6 比特数据就是 chunk stream id。
Message Header (0, 3, 7, or 11 bytes)
Meassage header 的格式依赖 basic header 中的 format 一起解释。
Format 0 (11 bytes)
此时 message header 占用 11 字节,包含四个字段,如下图示:
Timestamp (3 bytes)
这是一个很重要的字段,FFmpeg 会用其值计算帧的 DTS 和 PTS。
Message Length (3 bytes)
该字段用来描述 message 的长度。注意,是 message 的长度,而非 chunk data 的长度。
Message Type ID (1 byte)
该字段用来描述 message 的类型,后面再详细介绍。
Message Stream ID (4 bytes)
一个 message 可能被封装为多个 chunk,这些 chunk 的 message stream id 的值均相同。
在接收端,会按照先后次序,将具有相同 chunk stream id 和 message stream id 的 chunk 解封为一个 message。配合 message length 字段可判断是否完整接收了一个 message,具体判断逻辑下文有记录。
Format 1 (7 bytes)
此时 message header 占用 7 字节,各字段如下图所示:
相较于 format 0 的字段,这里有两处改动:
移除 Message Stream ID
没有 message stream id,那接收端如何确认该 chunk 应该解封至哪个 message 呢?
RTMP 规定,当 chunk 的 format 不等于 0 时,其 message stream id 的值等于最近一个具有相同 chunk stream id 的 chunk 的 message stream id。
这样做是为了复用了之前的数据,避免冗余传输。
修改 Timestamp 为 Timestamp Delta
RTMP 规定,当 chunk 的 format 不等于 0 时,其 timestamp 可由最近一个具有相同 chunk stream id 的 chunk 的 timestamp 加上 timestamp delta 获得。
一个隐藏的含义
不知大家是否注意到,format == 1 时,移除了 message stream id,但未移除 message type id 和 message type length。
这意味着,message stream 并没有和某一份具体的 message 配置(类型,长度)绑定。换言之,一个 message stream 可以传输不同类型和长度的 message。
Format 2 (3 bytes)
移除了 message length 和 message type id
在 format 1 的基础上又移除了 message length 和 message type id。同样的,RTMP 规定,复用最近一个 format 0 或 format 1 的,具有相同 chunk stream id 的对应字段。
Format 3 (0 bytes)
Umm… 所有字段都复用了,妙啊。
需要注意,若最近一个具有相同 chunk stream id 的 format :
- 为 0 时,将其 timestamp 的值作为当前 chunk 的 timestamp delta。
- 其他类型时,将其 timestamp delta 作为当前chunk 的 timestamp delta。
Extended Timestamp (0 or 4 bytes)
因为 timestamp / timestamp delta 字段仅有三个字节,所以当要传输的值不小于 0xFFFFFF
时可启用该字段。
RTMP 规定,当 timestamp 或 timestamp_delta 的值为 0xFFFFFF
时表示启动 extended timestamp 字段,该字段存储了完整的值。
需要注意的是,即使当 format 为 3 时,也不会复用该字段。换言之,当 format 为 3 时,若最近一个 format 0,1 或 2 类型的,具有相同 chunk stream id 的 timestamp / timestamp delta 为 0xFFFFFF
,则当前的 chunk 仍带有 extended timestamp。
Chunk Data
负载数据。比如音频的采样数据,视频的帧数据。
Chunk 中无指定 chunk data 长度的字段。Chunk data 的长度由两部分决定:
- maximum chunk size
- message length
Maximum Chunk Size
Maximum chunk size 可看做一个配置,RTMP规定默认大小时 128。可通过协议控制消息(Protocol Control Message,后续笔记记录) 中的 SetChunkSize 调整为 [0x1, 0xFFFFFF]
内的任意值。
注意:
- server → client 和 client → server 的 maximum chunk size 是独立的,互不影响。
- Maximum chunk size 只是一个上限,即 chunk data 的超度不能超过该值,但可以小于等于该值。
Message Length
RTMP 规定,一个 message 可以被封装为多个 chunk。设一个 message 被分成了 n(n≥1)n(n\ge 1)n(n≥1) 个 chunk,按照发送次序依次编号为 1,2,3,....,n1,2,3,....,n1,2,3,....,n。
RTMP 规定,除第 nnn 个 chunk 外,前 n−1n-1n−1 个 chunk 的 chunk data 的长度均为 maximum chunk size。
第 nnn 个 chunk 的 chunk data 长度为
messageLength−(n−1)∗(maximumChunkSize)messageLength - (n-1)*(maximumChunkSize)messageLength−(n−1)∗(maximumChunkSize)
Message
一个 message 由 header 和 body 两部分组成。Header 的类型即为上文中的 message header,body 的长度由 message length 给出。
body 的解析方法由 message type id 指定。这一段落只记录 message type id 的含义。
Message Type ID
从《RTMP specification 1.0》的目录里可以看出,message 类型可细分如下:
- Protocol control message
- 控制 Chunk 层级行为的:
1
:Set Chunk Size2
:Abort Message3
:Acknowledgement5
:Windon Acknowledgement Size6
:Set Peer Bandwidth
- 控制 Message 层级行为的:
4
:User Control Message
- 控制 Chunk 层级行为的:
- RTMP command message,用于传输数据,RPC(Remote Procedure Calls)等
8
:Audio Message9
:Video Message17
,20
:Command Message15
,18
:Data Message16
,19
:Shared Object Message22
:Aggregate Message
有两个ID的类型,主要区别在于 AMF0 和 AMF3。可以粗略的理解为定义了两种 json
序列化和反序列化的标准。
这里给出两个链接,有需要的铁子自取:
- 《AMF0 spec 121207》
- 链接: https://pan.baidu.com/s/1_aZttaDbMbI80GZsPDK-iA
- 提取码: w66t
- 《Action Message Format – AMF3》
- 链接: https://pan.baidu.com/s/1OQeWcdbb1YlE9bTrOL9k_Q
- 提取码: 9pi7
Chunk 和 Message 转换示例
假设现在有两个如下的 audio message 要发送,header 分别如下表示:
Message Stream ID | Message Type ID | Timestamp | Length | |
---|---|---|---|---|
MSG # 1 | 10 | 8 | 1000 | 280 |
MSG # 2 | 10 | 8 | 1020 | 150 |
假设当前的 maximum chunk size 为 128,则将两个 message 依次封装为 5 个 chunk,如下:
Chunk Stream ID | Chunk Format | Timestamp(Delta) | Message Length | Message Type ID | Message Stream ID | Chunk Data Size | |
---|---|---|---|---|---|---|---|
CHK # 1 | 3 | 0 | 1000 | 280 | 8 | 10 | 128 |
CHK # 2 | 3 | 3 | \ | \ | \ | \ | 128 |
CHK # 3 | 3 | 3 | \ | \ | \ | \ | 24 |
CHK # 4 | 3 | 1 | 20 | 150 | 8 | 10 | 128 |
CHK # 5 | 3 | 3 | \ | \ | \ | \ | 22 |
如何计算 Timestmap 和 Timestamp Delta
每个 message 有且只有一个 timestamp 字段,但它有可能被封装为多个 chunk。那么一个 message 的 timestamp 和多个 chunk 的 timestamp( delta) 之间该如何换算呢?
// FFmpeg/libavformat/rtmppkt.c
// static int rtmp_packet_read_one_chunk(URLContext *h, RTMPPacket *p ...
// 该函数的功能是从网络读取 chunk。
// ts_field 为 timestamp 或 timestamp delta 的值。
// timestamp 为 extended timestamp 的值。
// 不难发现,当且仅当该 chunk 是 message 的第一个 chunk 时,才使用了 ts_filed 和 timestamp 字段;其他情况下,均直接丢弃了这两个字段。
238 if (!prev_pkt[channel_id].read) {239 if ((ret = ff_rtmp_packet_create(p, channel_id, type, timestamp,
240 size)) < 0)
241 return ret;
242 p->read = written;
243 p->offset = 0;
244 prev_pkt[channel_id].ts_field = ts_field;
245 prev_pkt[channel_id].timestamp = timestamp;
246 }
上述代码是 FFmpeg 中的实现,从网络读取一个 chunk 并将其解封至一个 message。当且仅当读到 message 的第一个 chunk 时,才使用了 ts_filed 和 timestamp 字段,其他情况下,均直接丢弃了这两个字段。
参照 FFmpeg 的实现,可以愉快的得出如下结论~
一个 message 封装为多个 chunk :
- 若第一个 chunk 的 format 为 0,则 timestamp = message.timestamp。
- 反之,timestamp delta 即为当前 message 和前一个 message 的 timestamp 之差。
多个 chunk 解封为一个 message :
- 若第一个 chunk 的 format 为0,则 message.timestamp = chunk.timestamp。
- 反之,则将 timestamp delta 与前一个 message 的 timestamp 的和作为当前 message 的 timestamp。
RTMP(2):Chunk 和 Message相关推荐
- Java报错解决:org.apache.http.ConnectionClosedException: Premature end of chunk coded message body: closi
再跑爬虫程序的时候突然遇到了如下报错: org.apache.http.ConnectionClosedException: Premature end of chunk coded message ...
- RTMP协议中的Chunk Stream ID (CID)的作用
一.协议分层 RTMP包是以Message的结构封装的,结构如下所示: 1)Message Type ID在1-7的消息用于协议控制,这些消息一般是RTMP协议自身管理要使用的消息,用户一般情况下无需 ...
- rtmp Chunk stream ID 说明
2019独角兽企业重金招聘Python工程师标准>>> Chunk basic header: chunk basic head的长度为1~3个字节,具体长度主要是依赖chunk s ...
- rtmp 封包及消息
2019独角兽企业重金招聘Python工程师标准>>> 1 握手 adobe修改了握手部分的协议,但是没有公开.根据rtmp specification 1.0里面的握手过程,fla ...
- RTMPdump(libRTMP) 源代码分析 9: 接收消息(Message)(接收视音频数据)
2019独角兽企业重金招聘Python工程师标准>>> 注:此前写了一些列的分析RTMPdump(libRTMP)源代码的文章,在此列一个列表: RTMPdump 源代码分析 1: ...
- (转)RTMP协议从入门到放弃
转载自: http://blog.csdn.net/shangmingyang/article/details/50837852 RTMP协议是Real Time Message Protocol( ...
- 直播推流实现RTMP协议的一些注意事项
018年8月4日第三次更新,详细介绍了RTMP协议与遇到的坑,另外纯Java重写了RTMP协议,做了个Android 推流项目,包含安卓相机采集,编码和RTMP推流,上传到github了. 项目地址: ...
- EasyRTMP实现的rtmp推流的基本协议流程
EasyRTMP介绍 EasyRTMP是结合了多种音视频缓存及网络技术的一个rtmp直播推流端,包括:圆形缓冲区(circular buffer).智能丢帧.自动重连.rtmp协议等等多种技术,能够非 ...
- RTMP协议从入门到放弃
RTMP协议是Real Time Message Protocol(实时信息传输协议)的缩写,它是由Adobe公司提出的一种应用层的协议,用来解决多媒体数据传输流的多路复用(Multiplexing) ...
- 音视频技术傻瓜版解析:带你解锁RTMP
RTMP协议是Real Time Message Protocol(实时信息传输协议)的缩写,它是由Adobe公司提出的一种应用层的协议,用来解决多媒体数据传输流的多路复用(Multiplexing) ...
最新文章
- C#进阶系列——WebApi 身份认证解决方案:Basic基础认证
- asp.net c#截取指定字符串函数
- zzUbuntu安装配置Qt环境
- 快速了解c#中的索引器
- VMware 提示与 Device/Credential Guard 不兼容
- python sorted list 元组 多列排序
- java逐行读取文件内容执行sql语句_[11/100] 文件和异常
- linux之cenos7修改ip(临时和永久)
- 编译单元为什么只能有一个public类
- FIAA固定资产【03资产主数据】
- 分别使用御剑工具和dirsearch工具(需要在kali下进行安装)对http://159.75.16.25进行扫描, 扫描出敏感文件,敏感文件内有flag值
- IT小公司避坑及生存指南
- 主板24pin接口详图_24Pin接口再见!华擎推出首款ATX12VO标准的Z490主板
- android wifi 信道,WiFi不稳定经常断?手机WiFi信道小工具【图】_Android资讯_太平洋电脑网...
- m3u8解析php,PHP解码转发M3U8 PHP读取转发M3U8的方法
- Java拿到前一天的零点零分
- 国外问卷调查回答问题有什么技巧?
- 《惢客创业日记》2021.06.28-30(周一)防骗的终极解决方案
- 解析几何:第五章 二次曲线(2)抛物线 一般二次曲线
- android连接小票打印机,打印小票数据的两种模式
热门文章
- LEAK: ByteBuf.release() was not called before it‘s garbage-collected
- 8.绘制统计图形——直方图
- itunes下载的软件所在目录
- 离散数学知识点总结(9):集合的性质
- 笔记本加装固态硬盘,安装Ubuntu
- 块存储、文件存储、对象存储的区别
- 贵州支教之第二天(11月8日)
- 前后端分离之图片上传服务端处理方法(亲测通过)
- 平均17.1K?2022软件测试员平均薪资出炉,看看你被平均了没~
- 【超实用的浏览器插件】目前Google Chrome最好用的插件,为什么你还在犹豫不决?