2024年直播推流实现RTMP协议的一些注意事项_rtmp推流会被禁吗,2024年最新全网疯传

img
img

既有适合小白学习的零基础资料,也有适合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行业感兴趣的新人**

**都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

一: 使用javacv来实现,最终也是用过ffmpeg来进行编码和推流,javacv实现到可以直接接收摄像头的帧数据 需要自己实现的代码只是打开摄像头,写一个SurfaceView进行预览,然后实现PreviewCallback将摄像头每一帧的数据交给javacv即可 javacv地址:https://github.com/bytedeco/javacv demo地址:https://github.com/beautifulSoup/RtmpRecoder/tree/master 二: 使用Android自带的编码工具,可实现硬编码,这里有一个国内大神开源的封装很完善的的库yasea,第一种方法需要实现的Camera采集部分也一起封装好了,进行一些简单配置就可以实现编码推流,并且yasea目前已经直接支持摄像头的热切换,和各种滤镜效果 yasea地址(内置demo):https://github.com/begeekmyfriend/yasea 服务器 流媒体服务器我用的是srs,项目地址:https://github.com/ossrs/srs 关于srs的编译、配置、部署、在官方wiki中已经写的很详细了,并且srs同样是国内开发人员开源的项目,有中文的文档,看起来很方便 这里有最基本的简单编译部署过程 Android直播实现(二)srs流媒体服务器部署 播放器 android端的播放使用vitamio,还是国内的开源播放器,是不是感觉国内的前辈们越来越屌了^~^! vitamio支持几乎所有常见的的视频格式和流媒体协议 vitamio地址(内置demo):https://github.com/yixia/VitamioBundle 这里使用的是yaesa库,先介绍一下直播实现的流程:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值