这一篇笔记主要记录对 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 Size
      • 2:Abort Message
      • 3:Acknowledgement
      • 5:Windon Acknowledgement Size
      • 6:Set Peer Bandwidth
    • 控制 Message 层级行为的:
      • 4:User Control Message
  • RTMP command message,用于传输数据,RPC(Remote Procedure Calls)等
    • 8:Audio Message
    • 9:Video Message
    • 1720:Command Message
    • 1518:Data Message
    • 1619:Shared Object Message
    • 22: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相关推荐

  1. Java报错解决:org.apache.http.ConnectionClosedException: Premature end of chunk coded message body: closi

    再跑爬虫程序的时候突然遇到了如下报错: org.apache.http.ConnectionClosedException: Premature end of chunk coded message ...

  2. RTMP协议中的Chunk Stream ID (CID)的作用

    一.协议分层 RTMP包是以Message的结构封装的,结构如下所示: 1)Message Type ID在1-7的消息用于协议控制,这些消息一般是RTMP协议自身管理要使用的消息,用户一般情况下无需 ...

  3. rtmp Chunk stream ID 说明

    2019独角兽企业重金招聘Python工程师标准>>> Chunk basic header: chunk basic head的长度为1~3个字节,具体长度主要是依赖chunk s ...

  4. rtmp 封包及消息

    2019独角兽企业重金招聘Python工程师标准>>> 1 握手 adobe修改了握手部分的协议,但是没有公开.根据rtmp specification 1.0里面的握手过程,fla ...

  5. RTMPdump(libRTMP) 源代码分析 9: 接收消息(Message)(接收视音频数据)

    2019独角兽企业重金招聘Python工程师标准>>> 注:此前写了一些列的分析RTMPdump(libRTMP)源代码的文章,在此列一个列表: RTMPdump 源代码分析 1: ...

  6. (转)RTMP协议从入门到放弃

    转载自:  http://blog.csdn.net/shangmingyang/article/details/50837852 RTMP协议是Real Time Message Protocol( ...

  7. 直播推流实现RTMP协议的一些注意事项

    018年8月4日第三次更新,详细介绍了RTMP协议与遇到的坑,另外纯Java重写了RTMP协议,做了个Android 推流项目,包含安卓相机采集,编码和RTMP推流,上传到github了. 项目地址: ...

  8. EasyRTMP实现的rtmp推流的基本协议流程

    EasyRTMP介绍 EasyRTMP是结合了多种音视频缓存及网络技术的一个rtmp直播推流端,包括:圆形缓冲区(circular buffer).智能丢帧.自动重连.rtmp协议等等多种技术,能够非 ...

  9. RTMP协议从入门到放弃

    RTMP协议是Real Time Message Protocol(实时信息传输协议)的缩写,它是由Adobe公司提出的一种应用层的协议,用来解决多媒体数据传输流的多路复用(Multiplexing) ...

  10. 音视频技术傻瓜版解析:带你解锁RTMP

    RTMP协议是Real Time Message Protocol(实时信息传输协议)的缩写,它是由Adobe公司提出的一种应用层的协议,用来解决多媒体数据传输流的多路复用(Multiplexing) ...

最新文章

  1. C#进阶系列——WebApi 身份认证解决方案:Basic基础认证
  2. asp.net c#截取指定字符串函数
  3. zzUbuntu安装配置Qt环境
  4. 快速了解c#中的索引器
  5. VMware 提示与 Device/Credential Guard 不兼容
  6. python sorted list 元组 多列排序
  7. java逐行读取文件内容执行sql语句_[11/100] 文件和异常
  8. linux之cenos7修改ip(临时和永久)
  9. 编译单元为什么只能有一个public类
  10. FIAA固定资产【03资产主数据】
  11. 分别使用御剑工具和dirsearch工具(需要在kali下进行安装)对http://159.75.16.25进行扫描, 扫描出敏感文件,敏感文件内有flag值
  12. IT小公司避坑及生存指南
  13. 主板24pin接口详图_24Pin接口再见!华擎推出首款ATX12VO标准的Z490主板
  14. android wifi 信道,WiFi不稳定经常断?手机WiFi信道小工具【图】_Android资讯_太平洋电脑网...
  15. m3u8解析php,PHP解码转发M3U8 PHP读取转发M3U8的方法
  16. Java拿到前一天的零点零分
  17. 国外问卷调查回答问题有什么技巧?
  18. 《惢客创业日记》2021.06.28-30(周一)防骗的终极解决方案
  19. 解析几何:第五章 二次曲线(2)抛物线 一般二次曲线
  20. android连接小票打印机,打印小票数据的两种模式

热门文章

  1. LEAK: ByteBuf.release() was not called before it‘s garbage-collected
  2. 8.绘制统计图形——直方图
  3. itunes下载的软件所在目录
  4. 离散数学知识点总结(9):集合的性质
  5. 笔记本加装固态硬盘,安装Ubuntu
  6. 块存储、文件存储、对象存储的区别
  7. 贵州支教之第二天(11月8日)
  8. 前后端分离之图片上传服务端处理方法(亲测通过)
  9. 平均17.1K?2022软件测试员平均薪资出炉,看看你被平均了没~
  10. 【超实用的浏览器插件】目前Google Chrome最好用的插件,为什么你还在犹豫不决?