既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新
| S0 | |
|<-------------------| |
| S1 | |
|<-------------------| Version sent
| | C1 |
| |-------------------->|
| C2 | |
|------------------->| S2 |
| |<--------------------|
Ack sent | Ack Sent
| S2 | |
|<-------------------| |
| | C2 |
| |-------------------->|
Handshake Done | Handshake Done
| | |
Pictorial Representation of Handshake
握手开始于客户端发送`C0`、`C1`块。服务器收到`C0`或`C1`后发送`S0`和`S1`。
当客户端收齐`S0`和`S1`后,开始发送`C2`。当服务器收齐`C0`和`C1`后,开始发送`S2`。
当客户端和服务器分别收到`S2`和`C2`后,握手完成。
**注意事项:** 在实际工程应用中,一般是客户端先将`C0`, `C1`块同时发出,服务器在收到`C1` 之后同时将`S0`, `S1`, `S2`发给客户端。`S2`的内容就是收到的`C1`块的内容。之后客户端收到`S1`块,并原样返回给服务器,简单握手完成。按照RTMP协议个要求,客户端需要校验`C1`块的内容和`S2`块的内容是否相同,相同的话才彻底完成握手过程,实际编写程序用一般都不去做校验。
RTMP握手的这个过程就是完成了两件事:
校验客户端和服务器端RTMP协议版本号
是发了一堆随机数据,校验网络状况。
#### **3**. RTMP 消息
RTMP消息格式:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±+
| Message Type | Payload length |
| (1 byte) | (3 bytes) |
±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±+
| Timestamp |
| (4 bytes) |
±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±+
| Stream ID |
| (3 bytes) |
±±±±±±±±±±±±±±±±±±±±±±±±+
Message Header
• 1字节消息类型
• 3字节负载消息长度
• 4字节时间戳
• 3字节 `Stream ID`,区分消息流
**注意事项:** 实际RTMP通信中并未按照上述格式去发送RTMP消息,而是将RTMP 消息分块发送,之后将介绍RTMP消息分块。
#### **3.1**. RTMP 消息分块(chunking)
而对于基于`TCP`的`RTMP`协议而言,协议显得特别繁琐,但是有没有更好的替代方案。同时创建`RTMP`消息分块是比较复杂的地方,涉及到了`AFM`(也是`Adobe`家的东西)格式数据的数据。
#### **RTMP消息块格式:**
±-------------±---------------±-------------------±-------------+
| Basic Header | Message Header | Extended Timestamp | Chunk Data |
±-------------±---------------±-------------------±-------------+
| |
|<------------------- Chunk Header ----------------->|
Chunk Format
RTMP消息块构成:
• Basic Header
• Message Header
• Extended Timestamp
• Chunk Data
Chunk Basic header格式有3种:
格式1:
0 1 2 3 4 5 6 7
±±±±±±±±+
|fmt| cs id |
±±±±±±±±+
Chunk basic header 1
格式2:
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
±±±±±±±±±±±±±±±±+
|fmt| 0 | cs id - 64 |
±±±±±±±±±±±±±±±±+
Chunk basic header 2
格式3:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
±±±±±±±±±±±±±±±±±±±±±±±±+
|fmt| 1 | cs id - 64 |
±±±±±±±±±±±±±±±±±±±±±±±±+
Chunk basic header 3
注意事项:
**fmt**: 用于指定`Chunk Header` 里面 `Message Header`的类型,后面会介绍到
**cs id**: 是`chunk stream id`的缩写,同一个`RTMP`消息拆成的 `chunk` 块拥有相同的 `cs id`, 用于区分chunk所属的RTMP消息, `chunk basic header` 的类型`cs id`占用的字节数来确定
#### **Message Header格式:**
Message Header的类型通过上文`chunk basic header`中的`fmt`指定,共4种:
格式0:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±+
| timestamp | message length|
±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±+
| message length (cont) |message type id| msg stream id |
±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±+
| message stream id (cont) |
±±±±±±±±±±±±±±±±±±±±±±±±+
Chunk Message Header - Type 0
`Message Header`占用`11`个字节, 在`chunk stream`的开始的第一个`chunk`的时候必须采用这种格式。
• **timestamp**:`3`个字节,因此它最多能表示到`16777215=0xFFFFFF=2^24-1`, 当它的值超过这个最大值时,这三个字节都置为1,实际的`timestamp`会转存到`Extended Timestamp`字段中,接受端在判断`timestamp`字段24个位都为1时就会去`Extended timestamp`中解析实际的时间戳。
• **message length**:`3`个字节,表示实际发送的消息的数据如音频帧、视频帧等数据的长度,单位是字节。注意这里是Message的长度,也就是chunk属于的Message的总数据长度,而不是chunk本身Data的数据的长度。
• **message type id**:`1`个字节,表示实际发送的数据的类型,如`8`代表音频数据、`9`代表视频数据。
• **msg stream id**:4个字节,表示该chunk所在的流的`ID`,和`Basic Header`的`CSID`一样,它采用小端存储的方式
格式1:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±+
| timestamp | message length|
±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±+
| message length (cont) |message type id|
±±±±±±±±±±±±±±±±±±±±±±±±+
Chunk Message Header - Type 1
`Message Header`占用7个字节,省去了表示`msg stream id`的4个字节,表示此`chunk`和上一次发的`chunk`所在的流相同。
• **timestamp delta**:3个字节,注意这里和格式0时不同,存储的是和上一个chunk的时间差。类似上面提到的`timestamp`,当它的值超过3个字节所能表示的最大值时,三个字节都置为1,实际的时间戳差值就会转存到`Extended Timestamp`字段中,接受端在判断`timestamp delta`字段24个位都为1时就会去`Extended timestamp`中解析时机的与上次时间戳的差值。
格式2:
0 1 2
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
±±±±±±±±±±±±±±±±±±±±±±±±+
| timestamp |
±±±±±±±±±±±±±±±±±±±±±±±±+
Chunk Message Header - Type 2
`Message Header`占用3个字节,相对于格式1,又省去了表示消息长度的3个字节和表示消息类型的1个字节,表示此chunk和上一次发送的chunk所在的流、消息的长度和消息的类型都相同。余下的这三个字节表示`timestamp delta`,使用同格式1。
格式3:
0字节,它表示这个chunk的`Message Header`和上一个是完全相同的,无需再次传送
#### **Extended Timestamp(扩展时间戳):**
在`chunk`中会有时间戳`timestamp`和时间戳差`timestamp delta`,并且它们不会同时存在,只有这两者之一大于3个字节能表示的最大数值`0xFFFFFF=16777215`时,才会用这个字段来表示真正的时间戳,否则这个字段为0。
扩展时间戳占`4`个字节,能表示的最大数值就是`0xFFFFFFFF=4294967295`。当扩展时间戳启用时,`timestamp`字段或者`timestamp delta`要全置为`1`,表示应该去扩展时间戳字段来提取真正的时间戳或者时间戳差。注意扩展时间戳存储的是完整值,而不是减去时间戳或者时间戳差的值。
**Chunk Data(块数据)**: 用户层面上真正想要发送的与协议无关的数据,长度在(0,chunkSize]之间, `chunk size`默认为`128`字节。
#### **RTMP 消息分块注意事项**
• **Chunk Size**:
RTMP是按照`chunk size`进行分块,`chunk size` 指的是 `chunk`的`payload`部分的大小,不包括`chunk basic header` 和 `chunk message header`长度。客户端和服务器端各自维护了两个`chunk size`, 分别是自身分块的`chunk size` 和 对端 的`chunk size`, 默认的这两个`chunk size`都是128字节。通过向对端发送`set chunk size` 消息可以告知对方更改了 `chunk size`的大小。
• **Chunk Type**:
RTMP消息分成的`Chunk`有`4`种类型,可以通过 `chunk basic header`的高两位(`fmt`)指定,一般在拆包的时候会把一个RTMP消息拆成以格式`0`开始的`chunk`,之后的包拆成格式`3` 类型的`chunk`,我查看了有不少代码也是这样实现的,这样也是最简单的实现。
如果第二个`message`和第一个`message`的`message stream ID` 相同,并且第二个`message`的长度也大于了`chunk size`,那么该如何拆包?当时查了很多资料,都没有介绍。后来看了一些源码,如 `SRS`,`FFMPEG`中的实现,发现第二个`message`可以拆成`Type_1`类型一个`chunk`, `message`剩余的部分拆成`Type_3`类型的`chunk`。`FFMPEG`中就是这么做的。
#### 3.2 **RTMP 交互消息**
推流RTMP消息交互流程:
* ![](//upload-images.jianshu.io/upload_images/1111194-54ff023d7e493ad6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/890/format/webp)
pic\_2.png
关于推流的过程,RTMP的协议文档上给了上图示例,说一下推流注意事项:
#### **3.2.1** **Connect 消息**
RTMP 命令消息格式:
±---------------±--------±--------------------------------------+
| Field Name | Type | Description |
±-------------- ±--------±--------------------------------------+
| Command Name | String | Name of the command. Set to “connect”.|
±---------------±--------±--------------------------------------+
| Transaction ID | Number | Always set to 1. |
±---------------±--------±--------------------------------------+
| Command Object | Object | Command information object which has |
| | | the name-value pairs. |
±---------------±--------±--------------------------------------+
| Optional User | Object | Any optional information |
| Arguments | | |
±---------------±--------±--------------------------------------+
RTMP握手之后先发送一个`connect`命令消息,命令里面包含什么东西,协议中没有具体规定,实际通信中要携带 rtmp url 中的 `appName` 字段,并且指定一些编解码的信息,并以`AMF`格式发送, 下面是用wireshake抓取`connect`命令需要包含的参数信息:
* ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=%2F%2Fupload-images.jianshu.io%2Fupload_images%2F1111194-5dea339938a56f7c.png%3FimageMogr2%2Fauto-orient%2Fstrip%257CimageView2%2F2%2Fw%2F1000%2Fformat%2Fwebp&pos_id=img-2sULZr8e-1715666341135)
pic\_3.png
这些信息协议中并没有特别详细说明, 在`librtmp`,`srs-librtmp`这些源码中,以及用wireshark 抓包的时候可以看到。
服务器返回的是一个\_result命令类型消息,这个消息的`payload length`一般不会大于`128`字节,但是在最新的`nginx-rtmp`中返回的消息长度会大于128字节。
消息的`transactionID`是用来标识`command`类型的消息的,服务器返回的`_result`消息可以通过`transactionID`来区分是对哪个命令的回应,`connect` 命令发完之后还要发送其他命令消息,要保证他们的`transactionID`不相同。
发送完`connect`命令之后一般会发一个 `set chunk size`消息来设置`chunk size`的大小,也可以不发。
`Window Acknowledgement Size` 是设置接收端消息窗口大小,一般是`2500000`字节,即告诉对端在收到设置的窗口大小长度的数据之后要返回一个`ACK`消息。在实际做推流的时候推流端要接收很少的服务器数据,远远到达不了窗口大小,所以这个消息可以不发。而对于服务器返回的`ACK`消息一般也不做处理,默认服务器都已经收到了所有消息了。
之后要等待服务器对于`connect`消息的回应的,一般是把服务器返回的`chunk`都读完,组包成完整的`RTMP`消息,没有错误就可以进行下一步了。
#### **3.2.2** **Create Stream 消息**
创建完`RTMP`连接之后就可以创建`RTMP`流,客户端要想服务器发送一个`releaseStream`命令消息,之后是`FCPublish`命令消息,在之后是`createStream`命令消息。
当发送完`createStream`消息之后,解析服务器返回的消息会得到一个`stream ID`。
* ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=%2F%2Fupload-images.jianshu.io%2Fupload_images%2F1111194-f370aee91bc73c2d.png%3FimageMogr2%2Fauto-orient%2Fstrip%257CimageView2%2F2%2Fw%2F1000%2Fformat%2Fwebp&pos_id=img-PHx5S3pg-1715666341136)
pic\_4.png
这个ID也就是以后和服务器通信的 `message stream ID`, 一般返回的是1,不固定。
#### **3.2.3** **Publish Stream**
推流准备工作的最后一步是`Publish Stream`,即向服务器发一个`publish`命令消息,消息中会带有流名称字段,即rtmp url中的 `streamName`,这个命令的`message stream ID` 就是上面 `create stream` 之后服务器返回的`stream ID`,发完这个命令一般不用等待服务器返回的回应,直接发送音视频类型的RTMP数据包即可。有些rtmp库还会发`setMetaData`消息,这个消息可以发也可以不发,里面包含了一些音视频`meta data`的信息,如视频的分辨率等等。
**整个推流过程rtmp 消息抓包**
* ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=%2F%2Fupload-images.jianshu.io%2Fupload_images%2F1111194-2fcba45b9234e725.png%3FimageMogr2%2Fauto-orient%2Fstrip%257CimageView2%2F2%2Fw%2F1000%2Fformat%2Fwebp&pos_id=img-OLZPfQqR-1715666341138)
rtmp\_pulish\_message.png
#### **4. 推送音视频**
当以上工作都完成的时候,就可以发送音视频了。音视频RTMP消息的Payload(消息体)中都放的是按照`FLV-TAG`格式封的音视频包,具体可以参照`FLV`封装的协议文档。格式必须封装正确,否则会造成播放端不能正常拿到音视频数据,无法播放音视频。
#### **5. 关于RTMP的时间戳**
RTMP的时间戳单位是毫秒`ms`,在发送音视频之前一直为零,发送音视频消息包后时候必须保证时间戳是单调递增的,时间戳必须打准确,否则播放端可能出现音视频不同步的情况。Srs-librtmp的源码中,如果推的是视频文件的话,发现他们是用H264的`dts`作为时间戳的。实时音视频传输的时候是先获取了下某一时刻系统时间作为基准,然后每次相机采集到的视频包,与起始的基准时间相减,得到时间戳,这样可以保证时间戳的正确性。
#### **6. 关于Chunk Stream ID**
RTMP 的`Chunk Steam ID`是用来区分某一个chunk是属于哪一个`message`的 ,0和1是保留的。每次在发送一个不同类型的RTMP消息时都要有不用的chunk stream ID, 如上一个Message 是command类型的,之后要发送视频类型的消息,视频消息的`chunk stream ID` 要保证和上面 `command`类型的消息不同。每一种消息类型的起始chunk 的类型必须是 `Type_0` 类型的,表明新的消息的起始。
#### **总结:**
RTMP协议是个比较啰嗦的协议,实现起来也比较复杂,但通信过程过程相对简单。在直播的实际工程应用中,协议上很多地方都没有详细说明,注意了以上提到几点,基本能够保证RTMP音视频的通信正常。以上就是对RTMP协议的简介和一些注意事项,希望能帮到有需要的朋友,另外本文难免有错误或说的不够详细的地方,欢迎指正,一起交流探讨。
---
#### 本篇文章2017年版本
**收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。**
![img](https://img-blog.csdnimg.cn/img_convert/2f64a41257fc6dae3fd5071887495978.png)
![img](https://img-blog.csdnimg.cn/img_convert/a57eb7a5e6ab08d5e25a373a40ef60c4.png)
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人**
**都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
是对RTMP协议的简介和一些注意事项,希望能帮到有需要的朋友,另外本文难免有错误或说的不够详细的地方,欢迎指正,一起交流探讨。
---
#### 本篇文章2017年版本
**收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。**
[外链图片转存中...(img-P8ekEYNR-1715666341139)]
[外链图片转存中...(img-4E2YiBr6-1715666341141)]
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人**
**都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**