SRS/ZLMediaKit流媒体对比分析

SRS流媒体简介

1.1 程序运行

1.1.1编译及运行

cd srs/trunk

./configure

make

./objs/srs -c conf/srs.conf

1.1.2RTMP推、拉流

1.推流

  • Ffmpeg推流

e:

cd e:\Demo\CGAvioRead\Debug

ffmpeg -re -stream_loop -1 -i d:/H264_AAC_2021-02-10_1080P.mp4 -vcodec copy -acodec copy -f flv -y rtmp://192.168.145.128:1935/live/Camera_00002

  • OBS推流

2.拉流

用VLC:rtmp://192.168.145.128:1935/live/Camera_00002

1.1.3 WebRTC推流

1.服务器运行

./objs/srs -c conf/rtmp2rtc.conf

2.推流

(1)chrome浏览器设置参数

--ignore-certificate-errors --allow-running-insecure-content --unsafely-treat-insecure-origin-as-secure="http://192.168.145.128:8080"

(2)打开网页

http://192.168.145.128:8080/players/rtc_player.html

3.拉流

用VLC:rtmp://192.168.145.128:1935/live/livestream

1.2 SRS 支持的协议

SRS是运营级的互联网直播服务器集群,支持的协议有:

https://img2022.cnblogs.com/blog/1037399/202203/1037399-20220321174552940-1601633163.png

2.ZLMediaKit流媒体简介

2.1.构建和编译ZLMediaKit

cd /opt/ZLMediaKit

mkdir build

cd build

cmake .. -DENABLE_WEBRTC=true -DOPENSSL_ROOT_DIR=/opt/openssl -DOPENSSL_LIBRARIES=/opt/openssl/lib

cmake --build . --target MediaServer

2.2.启动服务

cd /opt/ZLMediaKit/release/linux/Debug

./MediaServer

2.3.RTMP、RTSP推、拉流

e:

cd e:\Demo\CGAvioRead\Debug

ffmpeg -re -stream_loop -1 -i d:/H264_AAC_2021-02-10_1080P.mp4 -vcodec copy -acodec copy -f flv -y rtmp://192.168.145.128:1935/live/Camera_00002

ffmpeg -re -stream_loop -1 -i d:/H264_AAC_2021-02-10_1080P.mp4 -vcodec copy -acodec copy -f rtsp -rtsp_transport tcp rtsp://192.168.145.128:554/live/Camera_00001

用VLC:rtmp://192.168.145.128:1935/live/Camera_00002

rtsp://192.168.145.128:554/live/Camera_00001

2.4.ZLMediaKit支持的协议

https://user-images.githubusercontent.com/11495632/190864440-91c45f8f-480f-43db-8110-5bb44e6300ff.png

3.RTMP协议

3.1.RTMP协议的简介

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

RTMP协议传输时会对数据做自己的格式化,这种格式的消息我们称之为RTMP Message,而实际传输的时候为了更好地实现多路复用、分包和信息的公平性,发送端会把Message划分为带有Message ID的Chunk,每个Chunk可能是一个单独的Message,也可能是Message的一部分,在接受端会根据Rtmp message header(chunk中包含的data的长度,message id和message的长度)把chunk还原成完整的Message,从而实现信息的收发。

一个 RTMP 连接以握手开始。RTMP 的握手不同于其他协议,RTMP 握手由三个固定长度的块组成,而不是像其他协议一样的带有报头的可变长度的块。客户端 (发起连接请求的终端) 和服务器端各自发送相同的三块。客户端发送的这些块称为C0、 C1 和 C2,服务器端发送的这些块称为 S0、S1 和 S2。

实际实现中为了在保证握手的身份验证功能的基础上尽量减少通信的次数,一般的发送顺序是这样的:

 Client--> Server : 发送一个创建流的请求(C0、C1)

 Server--> Client : 返回一个流的索引号( S0、S1、S2)。

 Client--> Server : 开始发送 (C2)

 Client--> Server : 发送音视频数据(这些包用流的索引号来唯一标识)

在这里插入图片描述

Rtmp协议握手完成之后,就可以进行数据交互了,但交换的数据格式需要一个组织的标准,发送端按照该标准进行数据的组装,接收方按照该标准进行数据的拆解,这样才能完成通信。rtmp的协议的数据包,总的来讲分为两大部分,一部分是Rtmp Header,另一部分为Rtmp Body。

发送的命令消息有:connect、createStream、publish、play等命令消息。

发送数据的数据有:meta(元数据)、video、audio数据。

3.2.RTMP的格式

3.2.1. rtmp header数据包的长度

rtmp header的长度不固定,可能的长度为12字节,8字节,4字节,1字节。

1.具体长度为多少个字节,由rtmp header数据包的第一个字节的高2位决定。

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EUkVHaFRhaWJoSVBsMWlheXNoblJJNkVodGV1YVRRNDRoYVBpYVJoa0xhUDhzRzJ5TlJuQTIyYnBvZWd1QzM5VVlHN2xoaWNzZ0FXMXoyRlEvNjQw?x-oss-process=image/format,png

2.Chunk Stream ID用来表示消息的级别:

3.2.2字节的RTMP Header

1.

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EUkVHaFRhaWJoSVBsMWlheXNoblJJNkVoTEhsYjI5dmNDZXU1aWJZTkZHU2lia1JRTWliaWNLV2JUNWZmd1BpYlE0QW0xZFp2Rkdxb2EyWmRRaEEvNjQw?x-oss-process=image/format,png

  • 第一个时间戳三个字节。
  • BodySize字段,表示RTMP Body所包含数据包的大小,除去RTMP Header部分,后面的数据部分长度。
  • Type ID字段表示消息类型ID,比如此处0x14表示以AMF0编码(还有AMF3编码,Adobe定义的编码方式)。另外还有如0x04表示用户控制消息,0x05表示Window Acknowledgement Size,0x06表示Set Peer Bandwith等等。
  • 还有最后一个Stream ID,Stream ID通常用以完成某些特定的工作,如使用ID为0的Stream来完成客户端和服务器的连接和控制,使用ID为1的Stream来完成视频流的控制和播放等工作。

2.

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EUkVHaFRhaWJoSVBsMWlheXNoblJJNkVoQVRDNHFCd0FVNFpBcW1XYVFBZXdrSGlhZm5FUnZ4d2Q5cjlpY01wTTRGTzhIMENPQ2Q4czU4Q1EvNjQw?x-oss-process=image/format,png

3.

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EUkVHaFRhaWJoSVBsMWlheXNoblJJNkVoVHRUV1Y4WHNpYzRsNkNYWGljTzhiSjI5Nkd0NjVzbmRiOHpJUUhBNGJpY1V1T2lhY1h3dExtWktLQS82NDA?x-oss-process=image/format,png

4.

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EUkVHaFRhaWJoSVBsMWlheXNoblJJNkVoWE1vcnQwUnUxdHVuM2NSb280amJTdHI4d2hRWHAwY0s0SUNlUFM2WmlhYkFZNzFOVXNzOXNKZy82NDA?x-oss-process=image/format,png

3.2.3常见的消息

1.publish消息

对于推流端,经过releaseStream,createStream消息之后,得到了_result消息之后,接下来客户端就可以发起publish消息。推流端使用publish消息向rtmp服务器端发布一个命名的流,发布之后,任意客户端都可以以该名称请求视频、音频和数据。我们首先来看一下publish消息的组织结构:

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EVHZwOVdiOGlhRHdhYVljVmoxUVIzM1ZjMWJyN1RxN2d2SlhpYmQ1YWRYQjJ4MU5DYlkxWHA3T3VXRmxJVWlhemxONEVpYnZ2cHRMcUd0eXcvNjQw?x-oss-process=image/format,png

  • commandName:使用string类型,表示消息类型(“publish”);
  • transactionID:使用number类型表示事物ID;
  • commandObject:对于publish消息,该部分为空,用null类型表示;
  • publishName:发布的流的名称,使用string类型表示,比如我们发布到rtmp://192.168.1.101:1935/rtmp_live/test,则test为流名称,也可以省略,此时该字段为空字符;
  • publishType:发布的流的类型,使用string类型表示,有3种类型,分别为live、record、append。record表示发布的视频流到rtmp服务器application对应的目录下会将发布的流录制成文件,append表示会将发布的视频流追加到原有的文件,如果原来没有文件就创建,live则不会在rtmp服务器上产生文件。

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EVHZwOVdiOGlhRHdhYVljVmoxUVIzM1ZKMkdSbEd2SFdodGR4VjVzZ081MXc1VU1JQUVoaWE2Z0pCcFY3d2s1eGtEV2dTUEU2dGFtM2pnLzY0MA?x-oss-process=image/format,png

通过抓包文件,我们可以看到这一抓包文件发送一条publish的消息,事务id为5,没有指定发布的流的名称,发布流的方式是live。如果发布的地址为rtmp://192.168.1.101:1935/rtmp_live,则其他任何客户端都可以访问该url获取视频资源,进而进行播放。

2.SetDataFrame/OnMetaData

一般在客户端收到服务端返回的针对publish的onStatus消息之后,如果没有异常,推流端还会向服务器发送一条SetDataFrame的消息,其中包含onMetaData消息,这一条消息的主要作用是告诉服务端,推流关于音视频的处理采用的一些参数,比如音频的采样率,通道数,帧率,视频的宽、高等信息。

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EVHZwOVdiOGlhRHdhYVljVmoxUVIzM1ZJRzF3UlJRaWM4MTlwMnFpYlo1QXhSempYUVRiN3E0RzRnUEwwdVBrV25FQnBRUUxXcVAyZ05MZy82NDA?x-oss-process=image/format,png

RTMP Body部分首先以AMF0格式(string)表示SetDataFrame消息;然后以AMF0格式编码(string)表示onMetaData消息;最后描述具体的关于音视频相关的参数,该部分使用ECMA Array的类型来表示。

(1)SetDataFrame部分

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EVHZwOVdiOGlhRHdhYVljVmoxUVIzM1Z4ZktTdldLS2ljbjRHVm9NMTNCQXFyVmljMklpY1JWeXhMUWhaSnJSMVVOVlB5Y0NhMTZLMjFwaWN3LzY0MA?x-oss-process=image/format,png

(2)onMetaData部分

图片

(3)property部分

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EVHZwOVdiOGlhRHdhYVljVmoxUVIzM1Z4bnRqWm5YZmdidG5UdWRIU2liMkcyQk1rTmtiQWVndlNWUEZPbEY3SWR2enk0aFh0ckZrWHJnLzY0MA?x-oss-process=image/format,png

ECMA Arrya本身的类型为0x08,其后紧跟着的是数组元素的个数,此处为20,占用4个字节表示,在之后便是具体的数组中的每一个元素。而数组中的每一个元素的具体编码方式又是遵循AMF0编码标准的。此例中共表示了20个属性:包含文件大小,视频宽度和高度,视频编码codec_id,帧率信息,比特率信息,音频的codec_id,音频采样率,channel数量等。最后还有一个encoder字段来表示编码器,我们推流使用的是obs(open broad cast),所以该字段中表明了使用obs-output module,obs的版本号为25.0.0。

3.play拉流

(1)综述

在客户端发起createStream命令之后,客户端收到服务端反馈的_result消息,接下来客户端就可以向服务端发起请求播放的指令,这个指令就是play。首先我们看一下官方给出的关于play的消息流示意图。

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EVHZwOVdiOGlhRHdhYVljVmoxUVIzM1ZHQ29LaWJOZGxFZTA5VzA0am1JZ0NwQnZLSDVIU1U2amliWFFXZGlhM3BPa2ljbUhhN1Z0QllmcUV3LzY0MA?x-oss-process=image/format,png

首先我们来简单介绍一下关于play的流程,客户端向服务端发送play指令之后,服务端收到之后向客户端发送SetChunkSize消息,实际场景中大都在服务器回复客户端connect消息的时候一起发送setChunkSize消息;

服务端向客户端发送StreamIsRecorded消息(实际场景中比较少见);服务端向客户端发送StreamBegin消息,向客户端指示流传输的开始;StreamIsRecoreded消息和StreamBegin消息组织结构比较简单,RTMP Body部分使用2个字节表示事件类型,StreamBegin的类型为0x00,4个字节表示StreamID。

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EVHZwOVdiOGlhRHdhYVljVmoxUVIzM1ZaTTZSNXNUTnBUbVZyaWFEcHc0WTBJMlNhNHA4cllOajhoRkVEY3FNR0FlTUhFMUtReENxQzN3LzY0MA?x-oss-process=image/format,png

说明:streamBegin wireshark过滤条件

rtmpt.ucm.eventtype == 0x00

客户端成功发送play请求后,服务端向客户端发送onStatus命令消息NetStream.Play.Start 和 NetStream.Play.Reset消息。其中NetStream.Play.Reset消息只有在客户端发送play消息的时候设置了reset标志的时候才会发。如果客户端请求播放的流不存在,服务端会返回onStatus命令消息NetStream.Play.StreamNotFound。

这些交互结束之后,服务端就会向客户端发送音频和视频数据,客户端就可以进行解码,然后渲染播放了。接下来我们来看一下这几条消息(streamBegin前面已介绍):

(2)play

play消息的整体组织结构

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EVHZwOVdiOGlhRHdhYVljVmoxUVIzM1ZUT3JiVFEwdldDZHZlTTFDNFc2azZUbHZrTDlLYlZMcHRVcno0aWIzZG45MUpLVzZBdTRZR2pBLzY0MA?x-oss-process=image/format,png

  • commandName:命令的名称,为“connect”;
  • transaction ID:事务ID,用number类型表示;
  • command Object:如果有,用object类型表示,如果没有,则使用null类型指明;
  • stream Name:请求的流的名称,一般在url中application后面的字段,如rtmp://192.17.1.202:1935/rtmp_live/test,rtmp_live为application,test为流的名称;
  • start:可选字段,使用number类型表示,指示开始时间,默认值为-2,表示客户端首先尝试命名为streamName的实时流(官方文档中说以秒单位,实际抓包文件中看到的单位应该是毫秒,要注意);
  • duration:可选字段,用number类型表示,指定播放时间,默认值为-1,表示播放到流结束;
  • reset:可选字段,用boolean类型表示,用来指示是否刷新之前的播放列表;

该抓包文件中的play命令,transactionID为4,commandObject中没有内容,streamName为“test”,没有duration和reset字段,有start字段,默认值为-2000,2000毫秒,即2s。

3.3. videoData

Rtmp Header + Rtmp Body的组织结构,Body中打包的是经过压缩的视频数据。Body中打包视频数据的方式。首先用一个字节表示视频数据的header,之后是压缩后的视频数据(压缩后的数据是使用FLV的标准进行封装的)。

Frame type:

1 = key frame (for AVC, a seekable frame)
2 = inter frame (for AVC, a non-seekable frame)

===================================================================

CodecID:

7 = AVC,即H.264

===================================================================

AVCPacketType:

0 = AVC sequence header
1 = AVC NALU

===================================================================

CompositionTime:

IF AVCPacketType == 1
Composition time offset
ELSE
0

===================================================================

紧接着为4个字节的长度。

1.frame type的类型

2.数据编码codecID

对于codecID,其实我们主要关注AVC即可,它代表的是H264编码。

3.AVC sequence header

3.4.audio

rtmp协议wireshark中过滤音频数据包的条件为:

rtmpt.header.typeid == 0x08

通过抓包文件,我们看到音频数据也是按照RTMP Header + Rtmp Body的组织结构来进行封装的。

音频的数据的Body部分正是按照FLV的格式进行组装的。而Flv的封装以tag为单位来进行组织,对于音频数据,包含tagHeader + tagData,tagHeader占用一个字节,表明音频编码的相关参数,tagData为具体的音频编码数据。

1.音频编码格式

高4比特,用于表示音频编码格式,具体可选值如下。

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EVHZwOVdiOGlhRHdhYVljVmoxUVIzM1ZpYk1MVmhseDI0dFA5UUlvaWFpY0dsNmJFT1ZNakpoaHltQ1BGdDluTlpMOGRMRDJGQlBqdzNRTXcvNjQw?x-oss-process=image/format,png

2.音频采样率

在之后的2个bit,表示音频采样率。

3.位深

之后的1个bit表示采样位深度,可选值为0,1。0表示8比特深度,1表示16比特深度。

4.声道

之后的1个bit表示声道数的参数,可选值为0,1。0表示sndMono(单声道),1表示sndStereo(立体声道)。

4.RTSP协议

4.1.RTSP/RTP/RTCP的区别

RTP/RTSP/RTCP的区别,用一句简单的话总结: RTSP发起/终结流媒体,RTP传输流媒体数据,RTCP对RTP进行控制、同步。

RTSP的请求主要有DESCRIBE,SETUP,PLAY,PAUSE,TEARDOWN,OPTIONS等,顾名思义可以发起对话和控制作用 。

RTSP的对话过程中SETUP可以确定RTP/RTCP使用的端口,PLAY/PAUSE/TEARDOWN可以开始或者停止RTP的发送,等等。

RTCP包括Sender Report、Receiver Report和Source Description,用来进行音频/视频的同步以及其他用途,是一种控制协议 。

4.2.RTP的包头

RTP数据协议负责对流媒体数据进行封包并实现媒体流的实时传输,每一个RTP数据报都由头部(Header)和负载(Payload)两个部分组成,其中头部前12个字节的含义是固定的,而负载则可以是音频或者视频数据。RTP数据报的头部格式如图所示:

http://pic3.zhimg.com/b6b12283d34ce6ed1ffa6ad4863ed05c_b.jpg

(1)V:RTP协议的版本号,占2位,当前协议版本号为2

(2)P:填充标志,占1位,如果P=1,则在该报文的尾部填充一个或多个额外的八位组,它们不是有效载荷的一部分。

(3)X:扩展标志,占1位,如果X=1,则在RTP报头后跟有一个扩展报头

(4)CC:CSRC计数器,占4位,指示CSRC 标识符的个数

(5)M: 标记,占1位,不同的有效载荷有不同的含义,对于视频,标记一帧的结束;对于音频,标记会话的开始。

(6)PT: 有效荷载类型,占7位,用于说明RTP报文中有效载荷的类型,如GSM音频、JPEM图像等,在流媒体中大部分是用来区分音频流和视频流的,这样便于客户端进行解析。

(7)序列号:占16位,用于标识发送者所发送的RTP报文的序列号,每发送一个报文,序列号增1。这个字段当下层的承载协议用UDP的时候,网络状况不好的时候可以用来检查丢包。同时出现网络抖动的情况可以用来对数据进行重新排序,序列号的初始值是随机的,同时音频包和视频包的sequence是分别记数的。

(8)时戳(Timestamp):占32位,必须使用90 kHz 时钟频率。时戳反映了该RTP报文的第一个八位组的采样时刻。接收者使用时戳来计算延迟和延迟抖动,并进行同步控制。

(9)同步信源(SSRC)标识符:占32位,用于标识同步信源。该标识符是随机选择的,参加同一视频会议的两个同步信源不能有相同的SSRC。

(10)特约信源(CSRC)标识符:每个CSRC标识符占32位,可以有0~15个。每个CSRC标识了包含在该RTP报文有效载荷中的所有特约信源。

4.3.RTP的基本流程

https://img-blog.csdnimg.cn/img_convert/1c580384be39b05558f32b831fb40607.png

4.4.RTSP/RTP/RTCP解析

1.rtp over tcp的RTP/RTCP包格式的前四个字节说明

RTP/RTCP Socket和RTSP Socket共享TCP Socket,所以必须要有一个标识来区别三种数据。

  RTP和RTCP数据会以 "$"符号 + 一个字节的通道编号 + 2个字节的数据长度,共四个字节的前缀开始,RTSP数据没有四个字节的前缀;RTP和RTCP数据的区别在于第二个字节的通道编号。

所以第一个字节’$'用于与RTSP区分,第二个字节用于区分RTP和RTCP(RTP和RTCP的channel是在RTSP的SETUP过程中,客户端发送给服务端的。一般情况下 RTP通道编号是偶数,RTCP通道编号是奇数)。RTP/RTCP的前缀四个字节如下所示:(在rtp over tcp发送协议下)

2.rtp over tcp的包格式

根据前面的说明,现在RTP的打包方式要在之前的每个RTP包前面加上四个字节。

3.RTP包格式:RTP头+RTP载荷

https://img2020.cnblogs.com/blog/2293816/202112/2293816-20211228012014894-1614491306.png

4.5.H264 NAL RTP打包

NALU是H264用于网络传输的单元类型,一个完整的NALU单元一般是以0x000001或者0x00000001开始,其后跟的则是NALU头和NALU的数据;我们在网络传输的时候,会去掉开始的0x000001或者0x00000001的标志;一般需要将这些标志替换为RTP payload的头部(1个字节)。

4.5.1RTP载荷第一个字节

格式跟NALU头一样:【后面的格式与H264的RTP打包格式相关】

F:【1 bit】 forbidden_zero_bit, 占1位,在 H.264 规范中规定了这一位必须为 0

NRI:【2 bits】 nal_ref_idc, 占2位,取值从0到3,指示这个 NALU 的重要性,取值越大约重要。

Type:【5 bits】nalu是指包含在 NAL 单元中的 RBSP 数据结构的类型,其中0未定义,1-19在264协议中有定义,20-23为264协议指定的保留位。

4.5.2单一NAL单元模式

即一个 RTP 包仅由一个完整的 NALU 组成. 这种情况下RTP NAL 头类型字段和原始的 H.264的NALU 头类型字段是一样的。【RTP载荷第一个字节 type=1-23】

      如有一个 H.264 的 NALU 是这样的:[00 00 00 01 67 42 A0 1E 23 56 0E 2F ... ]

  这是一个序列参数集 NAL 单元. [00 00 00 01] 是四个字节的开始码, 67 是 NALU 头, 42 开始的数据是 NALU 内容。

  封装成 RTP 包将如下:[ RTP Header ] [ 67 42 A0 1E 23 56 0E 2F ],即只要去掉 4 个字节的开始码就可以了。

4.5.3组合封包模式 

即可能是由多个 NAL 单元组成一个 RTP 包. 分别有4种组合方式: STAP-A, STAP-B, MTAP16, MTAP24。 【RTP载荷第一个字节 type=24-27

    【STAP-A   单一时间的组合包     24

       STAP-B   单一时间的组合包     25

       MTAP16   多个时间的组合包    26 

       MTAP24   多个时间的组合包    27】

如下图为一个包含sps、pps的包

第一个字节为0X18(即24),表示组合封包。0x00 0x15表示一个封包的长度21,后面的21个字节为一个封包,67(01100111,00111即7为sps帧)为NAL头;0x00 0x04表示一个封包的长度4,后面的4个字节为一个封包,68(01101000,01000即8为pps帧)为NAL头.

https://img2018.cnblogs.com/i-beta/1604637/201911/1604637-20191120174009520-654941385.png

4.5.4分片封包模式

对于较大的NALU,一个NALU可以分为多个RTP包发送。存在两种类型 FU-A 和 FU-B.。【RTP载荷第一个字节 type=28-29】

    【FU-A     分片的单元   28

       FU-B     分片的单元  29  

      没有定义 30-31 】

https://img2020.cnblogs.com/blog/2293816/202110/2293816-20211021172227545-1860999992.png

注意,FU payload中并没有传送NALU的头部,
NALU的头部由FU indicator(前3位)和FU header(后五位)组成:nal_unit_type = (fu_indicator & 0xe0) | (fu_header & 0x1f);

  • RTP载荷第一个字节位FU Indicator,其格式如下:

高三位:与NALU第一个字节的高三位相同

Type:28<----->0x1c,表示该RTP包一个分片,为什么是28?因为H.264的规范中定义的,此外还有许多其他Type,这里不详讲。

  • RTP载荷第二个字节位FU Header,其格式如下:

S:标记该分片打包的第一个RTP包

E:比较该分片打包的最后一个RTP包

R: 占1位,保留位,为0

Type:NALU的Type,后续才是NALU data)

注意,FU payload中并没有传送NALU的头部,NALU的头部由FU indicator(前3位)和FU header(后五位)组成:nal_unit_type = (fu_indicator & 0xe0) | (fu_header & 0x1f);

  • 第一个包

    1. RTP header中的M为0.
    2. FU Indicator为3C,即00111100, 11100即为28,表示是分片分包。
    3. FU Header为81,即10000001,即s为1,第一个封包;type为1,即b帧。
  • 中间的包

    1. RTP header中的M为0.
    2. FU Indicator为3C,即00111100, 11100即为28,表示是分片分包。
    3. FU Header为01,即00000001,即s、e都为0,中前的封包;type为1,即b帧。
  • 最后的包

    1. RTP header中的M为1.
    2. FU Indicator为3C,即00111100, 11100即为28,表示是分片分包。
    3. FU Header为41,即0 1000001,即s为0、e为1,最后的封包;type为1,即b帧。

5.SRS 对RTMP的处理

RTMP服务模块推拉流的整体逻辑:

1)每个推流客户端对应一个SrsLiveSource对象,每个拉流客户端对应一个SrsLiveConsumer对象。推流端接收协程SrsRecvThread::cycle()将接收到的RTMP数据包通过SrsLiveSource对象最终复制到SrsLiveConsumer对象的数据队列中进行缓存。

2)拉流端处理协程SrsRtmpConn::do_playing()从SrsLiveConsumer对象中不断读取数据并转发给拉流客户端。

3)如果SRS服务器工作在edge模式,

a) SRS将接收到的客户推流数据将通过SrsPublishEdge对象推向源站。

b) 如果本站点还没有缓存用户所需的拉流数据时,SRS会通过SrsPlayEdge::on_client_play()触发回源拉流操作。

===========================================================补充讨论关于RTMP推拉流的延时问题:

通过学习SRS流媒体服务器推流端和拉流端的代码逻辑,可以知道:

1)在没有拉流客户端时,推流端只能在SrsLiveSource对象中通过SrsMetaCache和SrsGopCache缓存音、视频、元数据和最多一组GOP视频。

2)有拉流客户端接入时,数据很快会通过批量写的方式发送到拉流客户端。

3)但是,如果使用VLC之类的播放工具拉流,会发现播放的视频画面存在8~10秒的延时,出现上述原因,主要是因为VLC有比较大的播放缓存,如果使用ffplay配合nobuffer参数,可以将延时降低到小于1秒。

===========================================================

分析代码从SrsLiveSource::on_video_imp开始,编码不编码在这个函数可以看出。

5.1不编、解码的情况

即rtmp推流、rtmp拉流的情况。

5.1.1推流的情况

1.SrsRecvThread::cycle

pumper:SrsPublishRecvThread

2.SrsRecvThread::do_cycle

  1. rtmp->recv_message(&msg)): 用来接受数据,最终调用SrsProtocol::recv_interlaced_message(SrsCommonMessage** pmsg),得到一个完整RTMP Message消息,即一个完整的音频帧或视频帧。
  2. pumper->consume🡪 SrsPublishRecvThread::consume。

3.SrsPublishRecvThread::consume

_conn->handle_publish_message🡪 SrsRtmpConn::handle_publish_message

4.SrsRtmpConn::handle_publish_message

process_publish_message🡪 SrsRtmpConn::process_publish_message

5.SrsRtmpConn::process_publish_message

处理RTMP相关的video, audio, MetaData(元数据)报文:

(1)首先处理的是MetaData报文;

(2)其次是视频第一帧AVC sequence header和音频第一帧AAC sequence header;

(3)最后是普通的音视频数据帧。

5.1.1.1视频

6.SrsLiveSource::on_video

(1)将SrsCommonMessage转换为SrsSharedPtrMessage。

(2)解决乱序问题。

7.SrsLiveSource::on_video_imp

(1)数据转发,转发给消费者,forward给其他服务器(边缘服务器或者源站服务器)。

(2)首先处理AVC sequence header数据,和meta对象中保存的数据比较,将最新的将最新的SPS+PPS数据保存到meta对象。

(3)每个拉流端都需要创建一个属于自己的SrsLiveConsumer消费者对象。SRS接收到推流客户端的数据后,在这里将数据复制到每个拉流端的SrsLiveConsumer缓存队列中。

(4)使用SrsGopCache::cache缓存一组完整的视频GOP。

5.1.1.2音频

6.SrsLiveSource::on_audio

音视频报文的处理流程基本一致,其中有一个mix_queue用于解决时间戳乱序问题

7. SrsLiveSource::on_audio_imp

音视频报文的处理流程基本一致。处理为的音频数据,更新meta不同。

===========================================================

8. SrsLiveConsumer::enqueue

(1)使用SrsRtmpJitter::correct()函数,处理音视频数据的时间戳。

(2)调用SrsMessageQueue::enqueue()函数,将音视频数据保存到一个类似Vector的容器中。

(3)调用srs_cond_signal(mw_wait)函数,唤醒拉流协程读取数据。

5.1.2拉流的情况

从SrsLiveConsumer::dump_packets开始分析,可以追溯到:

  1. SrsRtmpConn::cycle

这些函数推、拉流还是一样的。

  1. SrsRtmpConn::do_cycle

2-1. rtmp->handshake()(开始)握手协议

2-2 rtmp->connect_app建立连接

2-3. service_cycle()

  1. SrsRtmpConn::service_cycle()

3-1.各种消息的交换。

3-2. stream_service_cycle(),循环执行函数,开始处理RTMP推拉流/推流请求。

  1. SrsRtmpConn::stream_service_cycle

4-1. 进行户身份识别代码中rtmp->identify_client,判断是推流还是拉流。

4-2. _srs_sources->fetch_or_create每个推流端通过fetch_or_create函数生成一个对应的SrsLiveSource对象

4-3. case SrsRtmpConnPlay: 拉流客户端类型,拉流执行的是这个操作.

4-4. case SrsRtmpConnFMLEPublish:推流端执行这个操作,可参看相关介绍。

  1. SrsRtmpConn::playing

5-1. source->create_consumer为每个拉流端创建一个SrsLiveConsumer消费者对象,并与推流端SrsLiveSource对象绑定,

5-2. source->consumer_dumps将source中缓存的meta和GOP数据发送到消费者队列

5-3.真正的拉流协程do_playing()就阻塞在条件变量mw_wait,等待有新数据时被唤醒。

  1. SrsRtmpConn::do_playing

6-1. consumer->wait(mw_msgs, mw_sleep);

调用SrsLiveConsumer::wait()阻塞在条件变量mw_wait上,等待被唤醒

(1)推流端接收到推流数据,并调用SrsLiveConsumer::enqueue()时,会唤醒此条件变量。

(2)拉流端接收到用户发送的播放控制命令时,调用SrsLiveConsumer::wakeup()唤醒此条件变量。

6-2. SrsLiveConsumer::dump_packets

consumer->dump_packets:从SrsLiveConsumer内部队列中取出数据

6-3. rtmp->send_and_free_messages

发送message,这里play总出口

5.2解码的情况

5.2.1视频

分析代码从SrsLiveSource::on_video_imp开始。bridger_->on_video(msg) 🡪SrsRtcFromRtmpBridger::on_video(),即由rtmp->rtc。

1.SrsRtcFromRtmpBridger::on_video

(1)直接RTMP推流就可以进此处

(2)SrsLiveSource::on_video_imp--bridger_->on_video

(3)SrsSharedPtrMessage* msg是一个完整的RTMP消息

1-1.SrsFlvVideo::sh

通过前两个字节判断是否为the sequence header。为sequence header,则更新The metadata cache。

1-2. SrsRtmpFormat::on_video

由format->on_video(msg)得来。

1-2.1 SrsFormat::on_video

最终得到帧视频。

1-2.1-1 vcodec = new SrsVideoCodecConfig()

创建处理SPS+PPS的SrsVideoCodecConfig对象。

1-2.1-2video = new SrsVideoFrame()

创建解码对象。

1-2.2 SrsFormat::video_avc_demux

分为两类进行解析avc_demux_sps_pps和video_nalu_demux。对收到SrsSharedMessage进行解析。

1-2.2-1 SrsFormat::avc_demux_sps_pps

  1. SPS+PPS是编码器自动生成用于指导解码器正确解码的信息,对于流媒体服务器更多的是存储和转发。
  2. 这时数据的格式为type_codec1 + avc_type + composition time + fix header + count of sps + len of sps + sps +…+ count of pps + len of pps + pps+…
  3. 解析出sps编码规格、编码兼容性和编码等级
  4. 解析出pps的图片信息。
  5. 解析出的有用信息存入到SrsVideoCodecConfig类型的变量中。

1-2.2-1.1 SrsFormat::avc_demux_sps

(1) 从SrsVideoCodecConfig中取出sps,解析出NAL头。在实际的网络数据传输过程中H264的数据结构是以NALU(NAL单元)进行传输的,传输数据结构组成为[NALU Header]+[RBSP],如图所示:

https://img-blog.csdn.net/20180517163938667?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dvX3N0cg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70

NAL单元的头部是由forbidden_bit(1bit),nal_reference_bit(2bits)(优先级),nal_unit_type(5bits)(类型)三个部分组成的,组成如图所示:

https://img-blog.csdn.net/20180517164005461?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dvX3N0cg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70

  • F(forbiden):禁止位,占用NAL头的第一个位,当禁止位值为1时表示语法错误;
  • NRI:参考级别,占用NAL头的第二到第三个位;值越大,该NAL越重要。
  • Type:Nal单元数据类型,也就是标识该NAL单元的数据类型是哪种,占用NAL头的第四到第8个位;

enum {

NAL_IDR = 5,

NAL_SEI = 6,

NAL_SPS = 7,

NAL_PPS = 8,

NAL_AUD = 9,

NAL_B_P = 1,

};

(2)将数据转换为rbsp格式。

Annex B 格式用起始码(Start Code)来解决这个问题,在每个 NALU 的开始处添加三字节或四字节的起始码 0x000001 或 0x00000001,通过定位起始码,解码器就可以很容易的识别 NALU 的边界,同时,NALU 中可能存在与起始码相同的数据,为了防止这个问题,在构建 NALU 时, 需要将数据中的 0x000000, 0x000001, 0x000002, 0x000003 中插入防竞争字节 (Emulation Prevention Bytes) 0x03

1-2.2-1.2SrsFormat::avc_demux_sps_rbs

  1. uev的读法:1前面的0的2^n次方-1+n位后面的数
  2. 解析得到sps、pps中的信息

1-2.2-2 SrsFormat::video_nalu_demux

函数用于处理普通NALU报文,此函数的重点是,根据AnnexB或IBMF格式(AVCC格式)解析NALU。分两种解析avc_demux_annexb_format和avc_demux_ibmf_format。

1-2.2-2-1 SrsFormat::avc_demux_annexb_format

通过分隔符(00 00 01或00 00 00 01 )进行分隔。

1-2.2-2-2 SrsFormat::avc_demux_ibmf_format

(1)通过长度进行分隔

(2)长度的字节数通过SrsFormat::avc_demux_sps_pps函数得到,在sps中。

vcodec->NAL_unit_length = lengthSizeMinusOne;

1-2.2-2.1 SrsVideoFrame::add_sample

  1. bytes为每Nalu的数据。
  2. 解析出nalu type设置 IDR 标志。

1-2.2-2.2 SrsFrame::add_sample

(1)上面所有针对音视频RTMP报文的解析处理都是为了去除FLV封装,得到纯H264和AAC数据后,最终,通过SrsFrame::add_sample()对纯的音视频数据进行保存

每帧音视频数据,最终都是通过SrsFrame::add_sample()临时保存在SrsFrame对象中。

(2)最终存入到SrsSample类型的数组中。视频和音频分为两个samples。

1-3 SrsRtcFromRtmpBridger::filter

过滤B帧数据

=================RTP包的处理=======================

1-4 SrsRtcFromRtmpBridger::package_stap_a

https://img2018.cnblogs.com/i-beta/1604637/201911/1604637-20191120174009520-654941385.png

(1)IDR前面插入sps、pps

(2)设置RTP头

  • payload_type:不同厂商设置的不同。
  • 设置 marker 位,用于标记视频是否结束。设置为0.
  • Sequence: 是从0开始的
  • Timestamp:乘以90,因为H264 的采样率是 90KHz

(3)设置RTP packet的性质

  • frame_type:9 = video,8 = audio。
  • nalu_type:参见前文件所述的NAL单元的头部的type,为24。

1-5 SrsRtcFromRtmpBridger::package_nalus

默认不执行这个函数。自动插了001.

1-6 SrsRtcFromRtmpBridger::package_single_nalu

一个包一个Frame。RTP载荷第一个字节 type=1-23。

1-7SrsRtcFromRtmpBridger::package_fu_a

https://img2020.cnblogs.com/blog/2293816/202110/2293816-20211021172227545-1860999992.png

注意,FU payload中并没有传送NALU的头部,
NALU的头部由FU indicator(前3位)和FU header(后五位)组成:nal_unit_type = (fu_indicator & 0xe0) | (fu_header & 0x1f);

  • RTP载荷第一个字节位FU Indicator,其格式如下:

高三位:与NALU第一个字节的高三位相同

Type:28<----->0x1c,表示该RTP包一个分片,为什么是28?因为H.264的规范中定义的,此外还有许多其他Type,这里不详讲。

  • RTP载荷第二个字节位FU Header,其格式如下:

S:标记该分片打包的第一个RTP包

E:比较该分片打包的最后一个RTP包

R: 占1位,保留位,为0

Type:NALU的Type,后续才是NALU data)

代码中按前述的内容对FUA和packet进行赋值。

  1. SrsRtcFromRtmpBridger::consume_packets

将SrsRtpPacket包赋值给SrsRtcSource。

  1. SrsRtcSource::on_rtp

再将SrsRtpPacket包传给各个SrsRtcConsumer。

5.2.2音频

和视频类似。

分析代码从SrsLiveSource::on_audio_imp开始。bridger_->on_audio🡪 SrsRtcFromRtmpBridger::on_audio(),即由rtmp->rtc。

1. SrsRtcFromRtmpBridger::on_audio

音频由ACC变为OPUS。

1-1SrsRtmpFormat::on_audio

简单的赋值和调用。

1-1.1SrsFormat::on_audio

AAC音频帧的FLV封装格式:

处理逻辑是:

(1)读取一个字节的FLV audioTag,并根据高4bit判断音频编码类型,暂时只支持AAC和MP3

(2)创建SrsAudioCodecConfig对象用于保存处理AAC sequence header

(3)根据音频编码类型,调用audio_aac_demux()或audio_mp3_demux()处理实际的音频数据。

1-1.2SrsFormat::audio_aac_demux

(1)读取一个字节的FLV AudioTagHeader,并解析然后用解析的结果初始化SrsFormat::SrsAudioCodecConfig对象:

sound_format:常用的为10—ACC

sound_type: 常用的为0--SrsAudioChannelsMono(单声道),1--SrsAudioChannelsStereo(立体声道)

sound_size :1 = 16-bit samples

sound_rate: rtmp中SrsAudioSampleRate44100 =3多一些

(2)再读取一个字节的AACPacketType,分为0和1,第1个为0(SequenceHeader),其他的为1(RawData)

(3)根据报文类型分别处理AAC sequence header 和 AAC原始数据

  • 原始数据存入到缓存中。
  • AAC sequence header数据由audio_aac_sequence_header_demux处理。

1-1.2-1SrsFormat::audio_aac_sequence_header_demux

  • data为除云flv AudioTagHeader的部分。
  • 解析的值完善编码器。

1-1.2-1.1SrsFrame::add_sample

原始数据存入到缓存中。

1-2.aac_raw_append_adts_header

加入7个字节的ADTS header。AAC的音频文件格式有ADIF & ADTS:

ADTS:Audio Data Transport Stream 音频数据传输流。这种格式的特征是它是一个有同步字的比特流,解码可以在这个流中任何位置开始。它的特征类似于mp3数据流格式。

简单说,ADTS可以在任意帧解码,也就是说它每一帧都有头信息。ADIF只有一个统一的头,所以必须得到所有的数据后解码。

一个 AAC 原始数据块长度是可变的,对原始帧加上 ADTS 头的封装,就形成了 ADTS 帧。ADTS 头中相对重要的信息有:采样率,声道数,帧长度 ,每一个带 ADTS 头信息的 AAC 流会清晰的告诉解码器它需要的这些信息,解码器才能解析读取。一般情况下 ADTS 的头信息都是 7 个字节,分为 2 部分:

  - adts_fixed_header(); —— 固定头信息,头信息中的每一帧都相同.

  - adts_variable_header(); —— 可变头信息,头信息则在帧与帧之间可变.

1-3.SrsFrame::add_sample

和视频相同。

(1)上面所有针对音视频RTMP报文的解析处理都是为了去除FLV封装,得到纯H264和AAC数据后,最终,通过SrsFrame::add_sample()对纯的音视频数据进行保存

每帧音视频数据,最终都是通过SrsFrame::add_sample()临时保存在SrsFrame对象中。

(2)最终存入到SrsSample类型的数组中。视频和音频分为两个samples。

2.SrsRtcFromRtmpBridger::transcode

transcode the AAC to Opus and consume it。未进行详细了解。在传输中也是通过RTP方式传输的,只是编码方式进行了改变。

5.3编码的情况

分析代码从SrsRtcSource::on_rtp开始:bridger_->on_rtp()🡪 SrsRtmpFromRtcBridger::on_rtp 🡪 transcode_audio、packet_video。即rtc 🡪 rtmp的情况

推流:http://192.168.223.136:8080/players/rtc_player.html

运行:./objs/srs -c conf/rtmp2rtc.conf

5.3.1视频

1.SrsRtmpFromRtcBridger::packet_video

主要有两个函数完成RTP包的解包:packet_video_key_frame和packet_video_rtmp。

1-1. SrsRtmpFromRtcBridger::packet_video_key_frame

完成RTMP AVC sequence header报文的封装:

//type_codec1 + avc_type + composition time + fix header + count of sps + len of sps + sps + count of pps + len of pps + pps

int nb_payload = 1 + 1 + 3 + 5 + 1 + 2 + sps->size + 1 + 2 + pps->size;

1-2. SrsRtmpFromRtcBridger::packet_video_rtmp

(1)首先判断RTP包是FU-A、STAP-A、Single NAL 封装格式三种封装格式种的哪一种。cnt为RTP包的数量。

(2)接着前5个字节的写入type_codec1 + avc_type + composition time

(3)FU-A,分三种情况

开始:NALU头部+NALU 数据

中间:NALU 数据

结束:NALU 长度+ NALU 数据

(4)STAP-A

循环:NALU 长度+ NALU 数据

(5)Single NAL

NALU 长度+ NALU 数据

NALU 长度长度为4个字节。

2. SrsLiveSource::on_video

1-1和1-2.封装成RTMP包,存入到RTMP源中,由SrsLiveConsumer拉流端消费。就和不编码的情况对接上了。

5.3.2音频

1.SrsRtmpFromRtcBridger::transcode_audio

分为AVC sequence header和ACC音频数据。

(1)AVC sequence header不需解码。直接调用packet_aac。

(2)ACC音频数据需要先将OPUS转换为ACC ,然后调用packet_aac。

2. SrsRtmpFromRtcBridger::packet_aac

(1)加上RTMP头。

(2)加上一个字节aac_flag。

(3)回上一个字节AACPacketType。

3. SrsLiveSource::on_audio

和不编码的情况对接。

6. ZLMediaKit对RTSP的处理

6.1不编、解码的情况

6.1.1 推流的情况

从RtspMediaSourceImp::onWrite进行分析。

由 RtspSession::onRecv开始……RtspSession::onRtpSorted……

1. RtspMediaSourceImp::onWrite

为不解码的情况。分为解码和不解码的情况:

(1)_demuxer->inputRtp(rtp)

(2)RtspMediaSource::onWrite

2. RtspMediaSource::onWrite

主要代码:PacketCache<RtpPacket>::inputPacket

3.PacketCache—inputPacket

(1) 追加数据到最后_cache->emplace_back(std::move(pkt));

(2)flush();--》onFlush

4. RtspMediaSource—onFlush

_ring->write

5. RingBuffer—write

_storage->write(std::move(in), is_key);

6. _RingStorage—write

_data_cache.back().emplace_back写入环形缓存数据。

6.1.2拉流的情况

从RtspSession::handleReq_Play进行分析。

1. RtspSession::handleReq_Play

_play_reader->setReadCB

2.RtspSession::sendRtpPacket

VLC拉流执行的是case Rtsp::RTP_UDP:

通过sock->send发送。

6.2 RTSP解码的情况

在RtspDemuxer::inputRtp函数中将中解码分为音频和视频。

6.2.1视频

_video_rtp_decoder在makeVideoTrack,然后设置代理。

1. H264RtpDecoder::inputRtp

decodeRtp(rtp)—2

2.H264RtpDecoder::decodeRtp

根据frame[0]的后5位将rtp分为三类:

(1)24:STAP-A,aggregation packet

unpackStapA

(2)28: FU-A, Fragmentation unit

mergeFu

(3)<24,一个packet只有一个frame

singleFrame

2-1. H264RtpDecoder::unpackStapA

(1)对于这样的数据包:类型(1个字节)+frame 1 len(2个字节)+frame 1 data(前述两个字节表示的长度) +frame2 len(2个字节)+frame 2 data(前述两个字节表示的长度)+…..

(2)这种情况一般是sps、pps的合集。

(3)分拆完成以后调用singleFrame。

2-2. H264RtpDecoder::mergeFu

(1)如果是一个frame的开始,加上rpt over rtsp的4字节包头,组织FU indicator(FuFlags)。

(3)合成一个frame以后调用outputFrame(rtp, _frame);

2-3. H264RtpDecoder::singleFrame

(1)加上rpt over rtsp的4字节包头

(2)调用outputFrame(rtp, _frame);

3. H264RtpDecoder::outputFrame

(1)_dts的生成。

(2)RtpCodec::inputFrame(frame);

4. FrameDispatcher : : inputFrame

(1)RtspDemuxer::makeVideoTrack中video_rtp_decoder->addDelegate(_video_track);添加。

(2)最终执行H264Track::inputFrame。

5. H264Track::inputFrame

(1) inputFrame_l

对于b、p、IDR frme直接调用

(2)splitH264

/非I/B/P帧情况下,split一下,防止多个帧粘合在一起。然后调用inputFrame_l

6. H264Track::inputFrame_l

在这里会添加pps,sps信息(添加完pps,sps信息的h264数据就能直接用ffmpeg播放了),然后将数据发出去给各个代理。

7. FrameDispatcher::inputFrame

(1)通过VideoTrack::inputFrame再次进入,和4相同。

(2)代理添加顺序

  • RtspDemuxer::makeVideoTrack中addTrack(_video_track);
  • Demuxer::addTrack中_listener ? _listener->addTrack(track) : false
  • RtspMediaSourceImp::addTrack中_muxer->addTrack(track)
  • MediaSink::addTrack中track->addDelegate

中调用onTrackFrame

  1. MediaSink::inputFrame

checkTrackIfReady();

  1. MediaSink::checkTrackIfReady()

调试时执行的是

if(_ticker.elapsedTime() > kMaxWaitReadyMS){

//如果超过规定时间,那么不再等待并忽略未准备好的Track

emitAllTrackReady();

return;

}

  1. MediaSink::emitAllTrackReady

//全部Track就绪,我们一次性把之前的帧输出

for(auto &pr : _frame_unread){

if (_track_map.find(pr.first) == _track_map.end()) {

//该Track已经被移除

continue;

}

MediaSink::emitAllTrackReady

}

  1. MediaSink::inputFrame

auto ret = it->second.first->inputFrame(frame);//zzl:H264Track::inputFrame

再次进入到5,顺序执行,通过代理进入到12.

  1. 同时进入MultiMediaSourceMuxer::onTrackFrame函数。

封装成其他格式进行。

6.2.2音频

从RtspDemuxer::inputRtp开始。

1. AACRtpDecoder::inputRtp

(1)一个rtp包中包含多个frame。

(2)首2字节表示Au-Header的个数,单位bit,所以除以16得到Au-Header个数。

(3)之后的2字节是AU_HEADER,其中高13位表示一帧AAC负载的字节长度,低3位无用。

(4)拆分每个frame.—flushData--2

(5计算每个frame时间戳。

2. AACRtpDecoder::flushData()

(1) 插入adts头

(2)有adts头则插入adts头—3

3. FrameDispatcher : : inputFrame

(1)在RtmpDemuxer::makeAudioTrack中添加代理。_audio_rtmp_decoder->addDelegate(_audio_track):设置rtmp解码器代理,生成的frame写入该Track。

(2)最终执行AACTrack::inputFrame。--4

4. AACTrack::inputFrame

(1) if (frame_len == (int)frame->size()) {

return inputFrame_l(frame);--5

}

(2)计算pts、dts时间。

5. AACTrack::inputFrame_l

(1)makeAacConfig:根据7个字节的adts头生成aac config

(2)onReady:根据aac config生成_sampleRate、_channel。

(3)AudioTrack::inputFrame(frame)—》FrameDispatcher—inputFrame

6. FrameDispatcher::inputFrame

(1)通过AudioTrack::inputFrame再次进入。

(2)代理添加顺序

  • RtmpDemuxer::makeAudioTrack中addTrack(_audio_track);
  • Demuxer::addTrack中_listener ? _listener->addTrack(track) : false
  • RtspMediaSourceImp::addTrack中_muxer->addTrack(track)
  • MediaSink::addTrack中track->addDelegate

中调用onTrackFrame

7.MediaSink::inputFrame

和视频的相同

8.根据代理进行分发。同时进入MultiMediaSourceMuxer::onTrackFrame函数的_ring->write部分。

6.3RTMP编码的情况

ret = _rtmp->inputFrame(frame)

RtmpMediaSourceMuxer::inputFrame

RtmpMuxer::inputFrame

经过上面三个函数,根据编码器的不同,分为音、视频。

6.3.1视频

1. H264RtmpEncoder::inputFrame

(1) frame是带00 00 00 01

(2)根据第一个字节的后5位(nalu header),判断是何种帧。对于sps、pps: makeConfigPacket--H264RtmpEncoder::makeVideoConfigPkt。

1-1. H264RtmpEncoder::makeVideoConfigPkt

(1). 从track中获取sps pps信息

1-1.1H264RtmpEncoder::makeVideoConfigPkt

//type_codec1 + avc_type + composition time + fix header + count of sps + len of sps + sps + count of pps + len of pps + pps

int nb_payload = 1 + 1 + 3 + 5 + 1 + 2 + sps->size + 1 + 2 + pps->size;

组给sequence header 的rtmp包。

1-1.2 RtmpRing:: inputRtmp

将组合后的rtmp packet 存入到环形缓冲区中。

1-2 _merger.inputFrame

处理普通的包。

2. FrameMerger::inputFrame

(1)frame:输入的帧;cb:回调函数;buffer输出的数据。

2-1 FrameMerger::willFlush

判断是否输出frame:时间戳变化了,或新的一帧,或遇到config帧,或缓存太大了,立即flush。

2-2FrameMerger::doMerge

输出时将frame合并到一起。

2-3_merger.inputFrame回调函数

(1)增加rtmp包的信息。

(2)增加两个字节的FlV VideoTagHeader 和AVCPacketType。

(3)RtmpCodec::inputRtmp:将组合后的rtmp packet 存入到环形缓冲区中。

6.3.2音频

1.AACRtmpEncoder::inputFrame

frame->prefixSize()的值:7。

1-1.makeAacConfig:包含adts头,从adts头获取aac配置信息。

1-2. makeConfigPacket:组建ConfigPkt。

1-3.组建普通的rtmp packet。

ConfigPkt和普通的rtmp packet类似:

(1) rtmp packet头的增加。

(2)flv_flags+Rtmp AACPacketType的增加。RtmpAACPacketType两者有不同。

2.RtmpCodec::inputRtmp

和视频一样,将组合后的rtmp packet 存入到环形缓冲区中。

6.4RTMP的拉流

1.RtmpSession::onCmd_play

(1)解析rtmp url信息。

(2)调用doPlay。

2. RtmpSession::doPlay

异步调用doPlayResponse。

3. RtmpSession::doPlayResponse

异步调用sendPlayResponse

4. RtmpSession::sendPlayResponse

strong_self->onSendMedia发送数据。

7. ZLMediaKit对RTMP的处理

7.1不编、解码的情况

7.1.1 推流的情况

从RtmpMediaSourceImp::onWrite进行分析。

RtmpSession::onRecv……RtmpSession::onRtmpChunk…

1. RtmpMediaSourceImp::onWrite

为不解码的情况。分为解码和不解码的情况:

(1)_demuxer->inputRtmp(pkt)

(2)RtmpMediaSource::onWrite(std::move(pkt)

2. RtmpMediaSource::onWrite

主要代码:PacketCache<RtmpPacket>::inputPacket

=============和RTSP一样==============

3.PacketCache—inputPacket

(1) 追加数据到最后_cache->emplace_back(std::move(pkt));

(2)flush();--》onFlush

4. RtspMediaSource—onFlush

_ring->write

5. RingBuffer—write

_storage->write(std::move(in), is_key);

6. _RingStorage—write

_data_cache.back().emplace_back写入环形缓存数据。

7.1.2拉流的情况

参见6.4RTMP的拉流。

7.2RTMP解码的情况

RtmpDemuxer::inputRtmp函数中将中解码分为音频和视频。

7.2.1视频

_video_rtmp_decoder在makeVideoTrack中设置代理,生成的frame写入该Track。

1.H264RtmpDecoder::inputRtmp

1-1 getH264Config

sps 、pps的解析。按照下列的格式进行解析:

//type_codec1 + avc_type + composition time + fix header + count of sps + len of sps + sps + count of pps + len of pps + pps

int nb_payload = 1 + 1 + 3 + 5 + 1 + 2 + sps->size + 1 + 2 + pps->size;

1-2对于普通的视频

根据FLv VideoTagHeader(1)+AVCPackeType(1)+cts(3)+4个字节的长度解码出每一帧。

2. H264RtmpDecoder::onGetH264

加上00 00 00 01写入环形缓存。

===================和RTSP的一样==============================

  1. FrameDispatcher::inputFrame

(1)通过VideoTrack::inputFrame再次进入,和4相同。

(2)代理添加顺序

  • RtspDemuxer::makeVideoTrack中addTrack(_video_track);
  • Demuxer::addTrack中_listener ? _listener->addTrack(track) : false
  • RtmpMediaSourceImp::addTrack中_muxer->addTrack(track)
  • MediaSink::addTrack中track->addDelegate

中调用onTrackFrame

  1. MediaSink::inputFrame

checkTrackIfReady();

  1. MediaSink::checkTrackIfReady()

调试时执行的是

if(_ticker.elapsedTime() > kMaxWaitReadyMS){

//如果超过规定时间,那么不再等待并忽略未准备好的Track

emitAllTrackReady();

return;

}

  1. MediaSink::emitAllTrackReady

//全部Track就绪,我们一次性把之前的帧输出

for(auto &pr : _frame_unread){

if (_track_map.find(pr.first) == _track_map.end()) {

//该Track已经被移除

continue;

}

MediaSink::emitAllTrackReady

}

  1. MediaSink::inputFrame

auto ret = it->second.first->inputFrame(frame);//zzl:H264Track::inputFrame

再次进入到5,顺序执行,通过代理进入到12.

  1. 同时进入MultiMediaSourceMuxer::onTrackFrame函数。

封装成其他格式进行。

7.2.1音频

_audio_rtmp_decoder在makeAudioTrack中设置代理,生成的frame写入该Track。

1. AACRtmpDecoder::inputRtmp

1-1. getConfig主要是取掉两个字节的flags,

1-2. onGetAAC加上ADTS头。然后存入到存储区。

2.RtmpCodec::inputFrame

以下就和视频的相同了。

7.3RTSP编码的情况

ret = _rtsp->inputFrame(frame)

RtspMediaSourceMuxer::inputFrame

RtspMuxer::inputFrame

经过上面三个函数,根据编码器的不同,分为音、视频。

7.3.1视频

1. H264RtpEncoder::inputFrame

(1)前两次的数据分别是sps/pps。

(2) rtmp推流可以进行这个函数。

(3)frame为一帧图像和音频数据。

(4)默认的是执行的是非低延迟模式。

2. H264RtpEncoder::inputFrame_l

2-1. H264RtpEncoder::insertConfigFrame

保证每一个关键帧前都有SPS与PPS

2-2. packRtp:普通视频帧的打包。

3.H264RtpEncoder::packRtp

3-(0) makeRtp

(1)加4个字节的rtsp over tcp 头。

(2)加12个字节的rtp头。

(3)加有效负载。

3-1. packRtpSmallFrame

采用STAP-A/Single NAL unit packet per H.264 模式

3-1-1 packRtpStapA

加了一个字节(值:24)+两个字节的长度+帧数据

3-1-2 packRtpSingleNalu

直接帧数据。

3-2. packRtpFu

STAP-A模式打包会大于MTU,所以采用FU-A模式。加上两个字节:一个字节FU Indicator+一个字节FU Header。

4. RtpCodec::inputRtp

输入到rtp环形缓存。

7.3.2音频

1. AACRtpEncoder::inputFrame

第一个字节插入0,第二个字节16,第三、四字节为长度

2. makeAACRtp

调用makeRtp

(1)加4个字节的rtsp over tcp 头。

(2)加12个字节的rtp头。

(3)加有效负载。

3. RtpCodec::inputRtp

输入到rtp环形缓存。

7.4RTSP的拉流

同不编、解码的情况(6.1.2拉流的情况)。

8.SDP简介

8.1介绍

sdp,英文全称Session Description Protocol,会话描述协议,对应RFC2327。我们在此介绍,是因为RTSP协议中使用sdp进行媒体信息的描述,不过,sdp的应用不止于此,语音通话SIP协议,监控安防GB28181国标, 当下比较火热的webRtc都用到了sdp,可谓应用广泛!

sdp的目的就是在媒体会话中,传递媒体流信息,允许会话描述的接收者去参与会话,定义了会话描述的统一格式!

sdp信息由多行"<type>=<value>"组成,其中<type>是一个字符串,<value>是一个字符串,type表示类型,value的格式视type而定,整个协议区分大小写,"="两侧不允许有空格!

sdp会话描述包含一个会话级描述(session_level_description)和多个媒体级描述(media_level description)组成!会话级描述的作用域是整个会话,其位置从"v="行开始到第一个媒体描述为止;媒体级描述是对单个的媒体流进行描述,如传输过程中的视频流信息,从m=开始到下一个媒体描述为止,如下图所示!

https://img-blog.csdnimg.cn/img_convert/5343d3407f85826f7273021acc858c0e.png

8.2会话级描述主要包含以下字段

https://img-blog.csdnimg.cn/img_convert/d6a2042d618ca371ee74b2a3942eaa95.png

8.3媒体级描述主要包含以下字段

https://img-blog.csdnimg.cn/img_convert/0a7efef5dbee0227d643a91a01b9366c.png

8.4一些段描述 

1. version(必选)

格式:  v=<version> 

描述: 表示sdp的版本号,不包含次版本号

2.origin(必选)

格式:o=<username> <sessionid> <version> <network type> <address type> <address>

描述:o=选项对会话的发起者进行了描述;

<username>:是用户的登录名, 如果主机不支持<username>,则用"-"代替,<username> 不能包含空格;

<session id>:是一个数字串,在整个会话中,必须是唯一的,建议使用个NTP 时间戳;

<version>: 该会话公告的版本,供公告代理服务器检测同一会话的如果多个公告哪个是最新公告,基本要求是会话数据修改后该版本值递增,建议使用NTP时间戳

<networktype>: 网络类型,一般为"IN",表示internet

<addresstype>: 地址类型,一般为IP4

<adress>:地址

3.Session Name(必选)

格式:s=

会话名称,在整个会话中有且只有1个"s="

4.Times(必选)

格式:t=<start time> <stop time>

描述:t字段描述了会话的开始时间和结束时间,<start time> <stop time>为NTP时间,单位是秒;如果<stop time>为0表示过了<start time>之后,会话一直持续;当<start time> 和<stop time>都为0的时候,表示持久会话;建议两个值不设为0,如果设为0,不知道开始时间和结束时间,增大了调度的难度

5.a=(*) (可选)

格式 :a=<*>

描述:表示一个会话级别或媒体级别下的0个或多个属性

会话级别中有一个属性a,a=control:rtsp://192.17.1.63:554,表示新增的属性的类型为control,值为rtsp://192.17.1.63:554

6.media information(必选)

格式:m=<media> <port> <transport type> <fmt list>

描述:

<media>表示媒体类型

有"audio","video","application","data"(不向用户显示的数据),"control"(描述额外的控制通道);

<port>表示媒体流发往传输层的端口,对于RTP,偶数端口用来传输数据,奇数端口用来传输RTCP;

<transport>表示传输协议,与"c="一行相关联,一般用RTP/AVP表示,即 Realtime Transport Protocol using the Audio/Video profile over udp,即我们常说的RTP over udp;

<fmt list>表示媒体格式,分为静态绑定和动态绑定

静态绑定:媒体编码方式与RTP负载类型有确定的一一对应关系,如: m=audio 0 RTP/AVP 8

动态绑定:媒体编码方式没有完全确定,需要使用rtpmap进行进一步的说明: 如:

m=video 0 RTP/AVP 96

a=rtpmap:96 H264/90000

7.rtpmap(可选)

格式:a=rtpmap:<payload typee> <encoding name>/<clock rate>

描述:

payload type表示动态负载类型,如 98表示h264

encoding name表示编码名称,如H.264

clock rate表示时钟频率,如90000

8.fmtp

定义指定格式的附加参数

a=fmtp:<payload type> <format specific parameters>

<payload type>:负载类型

<format specific parameters>:具体参数.

8.5实际举例

v=0

o=- 1586545639954157 1586545639954157 IN IP4 192.17.1.63

s=Media Presentation

e=NONE

b=AS:5100

t=0 0

a=control:rtsp://192.17.1.63:554/

m=video 0 RTP/AVP 96

c=IN IP4 0.0.0.0

b=AS:5000

a=recvonly

a=x-dimensions:1920,1080

a=control:rtsp://192.17.1.63:554/trackID=1

a=rtpmap:96 H264/90000

a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=Z01AKI2NQDwBE/LgLcBAQFAAAD6AAAw1DoYACYFAABfXgu8uNDAATAoAAL68F3lwoA==,aO44gA==

m=audio 0 RTP/AVP 8

c=IN IP4 0.0.0.0

b=AS:50

a=recvonlya=control:rtsp://192.17.1.63:554/trackID=2

a=rtpmap:8 PCMA/8000

a=Media_header:MEDIAINFO=494D4B48010300000400000111710110401F000000FA000000000000000000000000000000000000;

a=appversion:1.0

posted @ 2024-07-27 16:09  泽良_小涛  阅读(0)  评论(0编辑  收藏  举报