rtmp封包协议讲解

我就想做一个直播推流而已,为什么还要学习RTMP的协议?

不是在​ ​《视频轨码率默认设置为600Kbps,音频轨码率默认设置为64Kbps》​​已经交叉编译好好RTMP库了吗,作为一个调库仔直接调用API不就完事了吗,为什么还要学生RTMP协议?学不不动了,告辞!!!

我们想回想一下我们推流端的简要流程:

那么在第三步,RTMP包如何封装呢?真的是简单的调用一下API就完事了吗?

我们现在看一段RTMP组包的代码:

 

void VideoChannel::sendSpsPps(uint8_t *sps, uint8_t *pps, int sps_len, int pps_len) {

int bodySize = 13 + sps_len + 3 + pps_len;
RTMPPacket *packet = new RTMPPacket;
//
RTMPPacket_Alloc(packet, bodySize);
int i = 0;
//固定头
packet->m_body[i++] = 0x17;
//类型
packet->m_body[i++] = 0x00;
//composition time 0x000000
packet->m_body[i++] = 0x00;
packet->m_body[i++] = 0x00;
packet->m_body[i++] = 0x00;

//版本
packet->m_body[i++] = 0x01;
//编码规格
packet->m_body[i++] = sps[1];
packet->m_body[i++] = sps[2];
packet->m_body[i++] = sps[3];
packet->m_body[i++] = 0xFF;

//整个sps
packet->m_body[i++] = 0xE1;
//sps长度
packet->m_body[i++] = (sps_len >> 8) & 0xff;
packet->m_body[i++] = sps_len & 0xff;
memcpy(&packet->m_body[i], sps, sps_len);
i += sps_len;

//pps
packet->m_body[i++] = 0x01;
packet->m_body[i++] = (pps_len >> 8) & 0xff;
packet->m_body[i++] = (pps_len) & 0xff;
memcpy(&packet->m_body[i], pps, pps_len);

//视频
packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
packet->m_nBodySize = bodySize;
//随意分配一个管道(尽量避开rtmp.c中使用的)
packet->m_nChannel = 10;
//sps pps没有时间戳
packet->m_nTimeStamp = 0;
//不使用绝对时间
packet->m_hasAbsTimestamp = 0;
packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;

videoCallback(packet);
}

void VideoChannel::sendFrame(int type, uint8_t *payload, int i_payload) {
if (payload[2] == 0x00) {
i_payload -= 4;
payload += 4;
} else {
i_payload -= 3;
payload += 3;
}
//看表
int bodySize = 9 + i_payload;
RTMPPacket *packet = new RTMPPacket;
//
RTMPPacket_Alloc(packet, bodySize);

packet->m_body[0] = 0x27;
if(type == NAL_SLICE_IDR){
packet->m_body[0] = 0x17;
LOGE("关键帧");
}
//类型
packet->m_body[1] = 0x01;
//时间戳
packet->m_body[2] = 0x00;
packet->m_body[3] = 0x00;
packet->m_body[4] = 0x00;
//数据长度 int 4个字节
packet->m_body[5] = (i_payload >> 24) & 0xff;
packet->m_body[6] = (i_payload >> 16) & 0xff;
packet->m_body[7] = (i_payload >> 8) & 0xff;
packet->m_body[8] = (i_payload) & 0xff;

//图片数据
memcpy(&packet->m_body[9], payload, i_payload);

packet->m_hasAbsTimestamp = 0;
packet->m_nBodySize = bodySize;
packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
packet->m_nChannel = 0x10;
packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
videoCallback(packet);
}

通过代码我们可以看到,在组RTMP包的时候我们会往里面写入各种莫名其妙的十六进制的数据,那么这些数据代表的意义是什么呢?

这就是我们今天要学习的内容。

RTMP是​​Real Time Messaging Protocol​​(实时消息传输协议)的首字母缩写。该协议是基于TCP的高层协议族。

默认使用的端口是1935。

RTMP的包结构

RTMP协议封包是由一个包头和一个包体组成,包头可以是4种长度的任意一种:12, 8, 4, 1 个字节。

完整的RTMP包头应该是12个字节,包含了时间戳,Head_Type,AMFSize,AMFType,StreamID信息,

8字节的包头只纪录了时间戳,Head_Type,AMFSize,AMFType;

4个字节的包头记录了时间戳,Head_Type。

1个字节的包头只记录了Head_Type。

包体最大长度默认为128字节,通过chunkSize可改变包体最大长度,通常当一段AFM数据超过128字节后,超过128的部分就放到了其他的RTMP封包中,包头为一个字节。

一个完整的RTMP包头有12字节,由下面5个部分组成:

名称

所占字节大小

备注

Head_Type

1

包头

TIMER

3

时间戳

AMFSize

3

数据大小

AMFType

1

数据类型

StreamID

4

流ID

1、 协议包头信息

Head_Type占用RTMP包的第一个字节,这个字节里面记录了包的类型和包的ChannelID。

Head_Type字节的前两个Bit决定了包头的长度,它可以用掩码​​0xC0​​进行"与"计算得出。

计算得出结果后对照下表即可知道包头的长度:

Head_Type的前两个Bit用掩码​​0xC0​​进行"与"计算后的结果与包头的长度对应关系:

结果

包头的长度(字节)

00

12 bytes

01

8 bytes

10

4 bytes

11

1 byte

例如rtmp包里面经常看到的0xC2, 就表示一字节的包头。

0xC2的二进制是 11000010
掩码0xC0的二进制是 11000000
它们的前两位与运算的结果是11,所以是查表得出它们的包头是长度是一个字节。
而ChannelID则是2。

而Head_Type的后面6个Bit和StreamID决定了ChannelID。 StreamID和ChannelID对应关系:

StreamID=(ChannelID-4)/5+1

ChannelID与相关值的对应关系表:

ChannelID

用途

02

Ping 和ByteRead通道

03

Invoke通道 我们的connect() publish()和自字写的NetConnection.Call() 数据都是在这个通道的

04

Audio和Vidio通道

05 06 07

服务器保留,经观察FMS2用这些Channel也用来发送音频或视频数据

2、 TIMMER时间戳

时间戳占用RTMP包头的第2、3、4 三个字节。

音视频流的时间戳是统一排的。可分为绝对时间戳和相对时间戳。

fms对于同一个流,发布(publish)的时间戳和播放(play)的时间戳是有区别的。

publish时间戳,采用相对时间戳,时间戳值等于当前媒体包的绝对时间戳与上个媒体包的绝对时间戳之间的差距,也就是说音视频时间戳在一个时间轴上面,单位毫秒。

play时间戳,相对时间戳,时间戳值等于当前媒体包的绝对时间戳与上个同类型媒体包的绝对时间戳之间的差距,也就是说音视频时间戳分别为单独的时间轴,单位毫秒。

3、 AMFSize

AMFSize占三个字节,这个长度是AMF长度,可超过RTMP包的最大长度128字节。

如果超过了128字节,那么由多个后续RTMP封包组合,每个后续RTMP封包的头只占一个字节。所以后续的包一般就是以0xC?开头。

1个字节的包头表示这个包的时间戳、数据大小、数据类型、流ID都和上一个相同ChannelID的RTMP包完全一样。

4、 AMFType - 数据类型

AMFType是RTMP包里面的数据的类型,占用1个字节。例如音频包的类型为8,视频包的类型为9。下面列出的是常用的数据类型:

AMFType

数据包类型

0×08

音频数据包

0×09

视频数据包

其他值这里不列出来了

5、 StreamID - 流ID

StreamID占用RTMP包头的最后4个字节,StreamID是音视频流的唯一ID,

一路流如果既有音频包又有视频包,那么这路流音频包的StreamID和他视频包的StreamID相同,但ChannelID不同。

ChannelID 和StreamID之间的计算公式:​​StreamID=(ChannelID-4)/5+1​

如果这个封包既不是音频包,也不是视频包,那么他的StreamID=0。

也就是说AMFType不等于0×08并且不等于0×09时,那么他的StreamID=0。

例如当音视频包ChannelID为2、3、4时StreamID都为1 当音视频包ChannelID为9的时候StreamID为2

封包例子分析

例如有一个RTMP封包的数据 03 00 00 00 00 01 02 14 00 00 00 00 02 00 07 63 6F 6E 6E 65 63 74 00 3F F0 00 00 00 00 00 00 08 ,,,

数据依次解析的含义:

03表示12字节头,channelid=3

00 00 00表示时间戳 Timer=0

00 01 02表示AMFSize=18

14表示AMFType=Invoke 方法调用

00 00 00 00 表示StreamID = 0

到这里RTMP的包头信息就分析完毕了。

RTMP包数据AMF分析

Rtmp包默认的最大长度为128字节,(或通过chunksize改变rtmp包最大长度),

当AMF数据超过128Byte的时候就可能有多个rtmp包组成,如果需要解码的rtmp包太长则被TCP协议分割成多个TCP包。

那么解码的时候需要先将包含rtmp包的tcp封包合并, 再把合并的数据解码,解码后可得到amf格式的数据,将这些AMF数据取出来就可以对AMF数据解码了。

AMF数据里面可以是命令也可以是音视频数据。

组成服务器和Flash客户端之间的所有数据都是用AMF格式的数据在传送,例如connect() publish()等命令。

AMF数据由2部分组成: ObjType 加上 ObjValue。ObjType的大小为一个字节。ObjValue的大小不固定,和ObjType相关。

常用的ObjType类型和对应的ObjValue大小整理如下:

类型说明(ObjType)

数据

dataSize

CORE_String

0x02

2字节 (2字节的数据纪录了String的实际长度)

CORE_Object

0x03

0字节(开始嵌套0x00000009表示嵌套结束)

NULL

0x05

0字节 空字节无意义

CORE_NUMBER

0x00

8字节

CORE_Map

0x08

4字节(开始嵌套)

CORE_BOOLEAN

0x01

1字节

RTMP的相关API

最后我们来看一下使用RTMP推流的主要API:

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340