本篇博客主要讲解RTMP协议重点,包括握手、连接、消息类别以及解码后各类型帧的缓存帧数设置等内容。
本专栏知识点是通过<零声教育>的音视频流媒体高级开发课程进行系统学习,梳理总结后写下文章,对音视频相关内容感兴趣的读者,可以点击观看课程网址:零声教育
RTMP协议详解
- 1. RTMP
- 2. RTMP协议
- 3.音视频解码后帧队列
1. RTMP
RTMP(Real Time Messaging Protocol)是一个应用层协议,主要用于在Flash player和服务器之间传输视频、音频、控制命令等内容。该协议的突出优点是: 低延时。
播放流程:
传输基本流程:
2. RTMP协议
2.1握手
RTMP握手分为简单握手和复杂握手。
实际实现中为了在保证握⼿的身份验证功能的基础上尽量减少通信的次数,⼀般的发送顺序如下所示,可以通过wireshark抓ffmpeg推流包进⾏验证:
| client | Server |
|---C0+C1---> |
|<--S0+S1+S2-- |
|----C2----> |
简单握手:
简单握手:
- C1和S1,4个字节表示时间,4个字节0,从第9个字节开始为随机数。
S2是C1的复制,C2是S1的复制
。- C0和S0都有8个字节,代表协议版本号,C0(客户端版本)、S0(服务器版本),目前版本为3。
复杂握手:
复杂握手将简单握手中1528比特的随机数部分平分为两部分。
1.768比特的public key
(公共密匙)
2.768比特的digest
(32字节的密文)
复杂握手的Version
部分不为0
,服务器可以由此判断握手类型。
2.2 连接
不同的Application Instance可根据功能等进行划分,例如,直播->live,点播回放->vod。
2.3 创建逻辑通道
creatStream
命令用于创建逻辑通道,用来传输视频、音频和metadata。
服务器的响应报文中会返回Stream ID,标识改Stream。
2.4 play
客户端发送play
命令播放指定流。
想要立即播放,需要先清空play队列中的其他流,并将reset置为true。
2.5 删除流deleteStream
删除指定Stream ID的流,服务器就不用对这条命令发送响应报文。
2.6 RTMP层次关键结构
对于FLV的tag而言,对应RTMP中每个message。
2.7 消息分类
消息主要分为三类: 协议控制消息、数据消息、命令消息等。
- 协议控制消息
Message Type ID
= 1 2 3 5 6和Message Type ID
= 4两大类,主要用于协议内的控制 - 数据消息
Message Type ID = 8 9 18
8: Audio 音频数据
9: Video 视频数据
18: Metadata 包括音视频编码、视频宽高等信息。 - 命令消息
Command Message ID = 20 17
此类型消息主要有NetConnection和NetStream两个类。
Chunk通过Chunk Stream ID判别自己所属的Message。同一个Chunk Stream ID必然属于同一个Message。
RTMP流中视频和音频拥有单独的Chunk Stream ID
比如音频的cs id=20
,视频的cs id=21
。
接收端接收到Chunk之后,根据cs id分别将音频和视频“拼成消息”。
Message被拆分成一个或多个Chunk,然后在网络上一个接一个的进行发送。
默认Chunk Size是128字节
2.8 RTMP实质
•发送端
首先将数据加工成消息(中间物),然后再将消息分割成
hunk(加上Chunk Header),然后将Chunk通过网络发送出去。
•接收端
接收端将接收到的Chunk组装成消息。
2.9 RTMP Chunk Header
RTMP Chunk Header包含Basic Header
、Message Heade
r和Extended Timestamp
三种,其长度不是固定的,分为:12 Bytes、8 Bytes、4 Bytes、1 Byte
四种,由RTMP Chunk Header
前2
位决定。
前2 Bits | Header Length | 说明 |
---|---|---|
00 | 12 bytes | MetaData流刚开始的绝对时间戳 视频帧流刚开始的绝对时间戳 音频流刚开始的绝对时间戳 connect控制消息 |
01 | 8 bytes | 常见类型 |
10 | 4 bytes | 比较少见 |
11 | 1 bytes | 偶尔会出现 频率远远低于8 Bytes |
RTMP Basic Header 一般为1字节。
- 一般情况下, msg stream id是不会变的,所以针对视频或音频, 除了第一个RTMP Chunk Header是12Bytes的,后续即可采用8Bytes的。
- 如果消息的长度(message length)和类型(msg type id,如视频为9或音频为8)又相同,即可将这两部分也省去, RTMP Chunk Header采用4Bytes类型的。
- 如果当前Chunk与之前的Chunk相比, msg stream id相同, msg typeid相同, message length相同,而且都属于同一个消息(由同一个Message切割成),这类Chunk的时间戳(timestamp)也是相同的,故后续的也可以省去,RTMP Chunk Header采用1 Byte类型的。
2.10 时间戳:
RTMP时间戳为相对于某个时间点的相对值,单位为毫秒(ms)
- 时间戳的长度为32bit,不考虑回滚的话,最大可表示49天17小
时2分钟47.296秒 - Timestamp delta 单位也是毫秒,为相对于前一个时间戳的一个
无符号整数;可能为24bit或32bit。 - RTMP Message的时间戳 4个字节
- 大端存储
rtmp流的chunk视频流(或音频流)除第一个视频时间戳为绝对时间戳外,后续的时间戳均为timestamp delta,即当前时间戳与上一个时间戳的差值。比如帧率为25帧/秒的视频流, timestamp delta基本上都为40ms。
一般chunk
的时间戳timestamp
为3
字节,当时间戳值超过0xFFFFFF
时,启用Extend Timestamp(4字节)
。
音频:1024/48k=21.333333毫秒
call back间隔肯定是不匀称的。
帧差值矫正:
误差按浮点数做累加,当误差超过半帧的话,按照当前时间发送,误差清零。
3.音视频解码后帧队列
FrameQueue
解码后的数据量比较大,需要要控制解码后帧队列的大小
参考ffplay固定大小:
#define VIDEO_PICTURE_QUEUE_SIZE 3 // 图像帧缓存数量
#define SUBPICTURE_QUEUE_SIZE 16 // 字幕帧缓存数量
#define SAMPLE_QUEUE_SIZE 9 // 采样帧缓存数量
#define FRAME_QUEUE_SIZE FFMAX(SAMPLE_QUEUE_SIZE,
FFMAX(VIDEO_PICTURE_QUEUE_SIZE, SUBPICTURE_QUEUE_SIZE))
考虑到不同类型的帧数据量大小不同,数据量大的,缓存帧数应该少些。
图像帧缓存数量一般设为3,采样帧缓存数量一般设为9,字幕帧缓存数量设为16。
补充:
1.客户端的首帧秒开,本质上就是不做同步先把第一帧显示出来。
服务器首帧秒开:这个功能不能降低延迟 发送i帧本身就有延迟,
2.推流没有问题时,如果拉流不能正常播放:
- 没有声音: dump rtmp拉流后的数据是否可以正常播放
- 声音异常:是否有解码错误报告,重采样前的pcm数据是否正常
- 没有图像: dump rtmp拉流后的数据是否可以正常播放
- 画面异常:是否有解码错误报告, scale前的数据是否正常