一、背景
实时音讯传输协定(Real-Time Messaging Protocol)是目前直播的次要协定,是Adobe公司为Flash播放器和服务器之间提供音视频数据传输服务而设计的应用层公有协定。RTMP协定是目前各大云厂商直线直播业务所专用的根本直播推拉流协定,随着国内直播行业的倒退和5G时代的到来,对RTMP协定有根本的理解,也是咱们程序员必须要把握的基本技能。
本文次要论述RTMP的根本思维和外围概念,并且辅之以livego的源码剖析,和大家一起深刻学习RTMP协定最外围的知识点。
二、RTMP协定特点
RTMP协定次要的特点有:多路复用,分包和应用层协定。以下将对这些特点进行具体的形容。
2.1 多路复用
多路复用(multiplex)指的是信号发送端通过一个信道同时传输多路信号,而后信号接收端将一个信道中传递过去的多个信号别离组合起来,别离造成独立残缺的信号信息,以此来更加无效地应用通信线路。
简而言之,就是在一个 TCP 连贯上,将须要传递的Message分成一个或者多个 Chunk,同一个Message 的多个Chunk 组成 ChunkStream,在接收端,再把 ChunkStream 中一个个 Chunk 组合起来就能够还原成一个残缺的 Message,这就是多路复用的根本理念。
上图是一个简略例子,假如须要传递一个300字节长的Message,咱们能够将其拆分成3个Chunk,每一个Chunk能够分成 Chunk Header 和 Chunk Data。在Chunk Header 里咱们能够标记这个Chunk中的一些根本信息,如 Chunk Stream Id 和 Message Type;Chunk Data 就是原始信息,上图中将 Message 分成128+128+44 =300,这样就能够残缺的传输这个Message了。
对于 Chunk Header 和 Chunk Data 的格局,后文会进行具体介绍。
2.2 分包
RTMP协定的第二个大的个性就是分包,与RTSP协定相比,分包是RTMP的一个特点。与一般的业务应用层协定(如:RPC协定)不一样的是,在多媒体网络传输案例中,绝大多数的多媒体传输的音频和视频的数据包都绝对比拟偏大,在TCP这种牢靠的传输协定之上进行大的数据包传递,很有可能阻塞连贯,导致优先级更高的信息无奈传递,分包传输就是为了解决这个问题而呈现的,具体的分包格局,下文会有介绍。
2.3 应用层协定
RTMP最初的一个个性,就是应用层协定。RTMP协定默认基于传输层协定TCP而实现,然而在RTMP的官网文档中,只给定了规范的数据传输格局阐明和一些具体的协定格局阐明,并没有具体官网的残缺实现,这就催生出了很多相干的其余业内实现,例如RTMP over UDP等等相干的公有改编的协定呈现,给了大家更多的可扩大的空间,不便大家解决原生RTMP存在的直播时延等问题。
三、RTMP协定解析
作为一种应用层协定,和其余公有传输协定一样(如RPC协定),RTMP也有一些具体代码实现,如 nginx-rtmp、livego 和 srs。本文选用基于go语言实现的开源直播服务器 livego 进行源码级的主流程剖析,和大家一起深刻学习 RTMP 推拉流的外围流程的实现,帮忙大家对RTMP的协定有一个整体的了解。
在进行源码剖析之前,咱们会通过类比RPC协定的形式,帮忙大家对RTMP协定的格局有一个根本的理解,首先咱们能够看一个比较简单但实用的RPC协定格局,如下图所示:
咱们能够看到这是一个在RPC调用过程中所应用的数据传输格局,之所以应用这样的格局,基本目标还是为了解决"粘包和拆包"的问题。
以下简要形容图中RPC协定的格局:首先用2个字节,MAGIC来示意魔数,标记该协定是对端都能辨认的标识,如果接管到的2个字节不是0xbabe的话,则间接抛弃该包;第二个sign占用1个字节,低4位示意音讯的类型request/response/heartbeat,高4位示意序列化类型例如json,hessian,protobuf,kyro等等;第三个 status 占用一个字节,示意状态位;随后应用8个字节来示意调用的requestId,个别应用低48位(2的48次方)就足够示意requestId了;接着应用4字节定长的body size来示意Body Content,通过这样的形式就可能很快的解析出RPC音讯Message的残缺申请对象了。
通过剖析上述的一个简略的RPC协定,其实咱们可能发现一个很好的思维,就是最大效率的应用字节,即应用最小的字节数组,来传输最多的数据信息。小小的一个字节可能带来很多的信息量,毕竟一个字节它有64种不同的变动。在网络中,如果只须要利用一个字节就可能传递很多有用的信息的话,那么咱们就能够应用极其无限的资源来失去最大的资源利用了。RTMP的官网文档在2012年就呈现了,尽管以目前的眼光来看,RTMP协定实现的非常复杂,甚至有些臃肿,然而它在2012年的时候,就可能有比拟先进的思维,确实是咱们学习的楷模。
在当今WebRTC协定横行的年代里,咱们也可能从WebRTC的设计实现中,看到RTMP的影子,上述的RPC协定咱们就能够认为是一个与RTMP具备类似设计理念的简化版设计。
3.1 RTMP外围概念阐明
在剖析RTMP源码之前,咱们先对RTMP协定中的几个外围概念做具体阐明,不便咱们在宏观上对RTMP整个协定栈有一个根本的理解,并且在后文源码剖析期间,咱们也会通过抓包的形式,更加直观地帮忙咱们去剖析相干的原理。
首先,和方才的RPC协定格局一样,RTMP理论传输的实体对象是Chunk,一个Chunk由Chunk Header和Chunk Body两个局部组成,如下图所示。
3.1.1Chunk Header
Chunk Header这个局部和咱们后面说过的RPC协定不太一样,次要是RTMP协定的Chunk Header的长度不是固定的,为什么不是固定的呢?其实还是Adobe公司为了节俭数据传输开销。从方才将一个300字节的Message拆分成3个Chunk的例子中,咱们能够看到多路复用其实也是有一个比拟显著的毛病,就是咱们须要有一个Chunk Header来标记这个Chunk的根本信息,这样其实就是在传输的时候有了额定字节流传输的开销。所以为了保障传输的字节数起码,咱们就须要一直地压迫着RTMP的Header的大小,确保Header的大小达到最小,这样能力达到最高的传输效率。
首先咱们钻研一下Chunk Header中Basic Header的局部,Basic Header的长度就是不固定的,能够是1个字节,2个字节或者3个字节,这取决于Chunk Stream Id(缩写:csid)。
RTMP协定反对的csid的范畴是2~65599,0和1是协定保留值,用户不可应用。Basic Header至多含有1个字节(低8位),它的长度就是这1个字节决定的,如下图所示。该字节高2位留给 fmt,fmt的取值决定了 Message Header 的格局,这个在前面会讲到。该字节的低6位就是 csid 的值,当低6位的 csid 取值为0时,示意实在 csid 值大到无奈用6个bit示意了,须要借助后续的一个字节才行;当低6位的 csid 取值为1时,示意实在 csid 值大到无奈用14个bit示意了,须要再借助后续的一个字节才行。于是,整个Basic Header的长度看起来就不是固定的了,齐全取决于首字节的低6位的csid的值。
理论利用中,并没有应用到那么多csid,也就是说个别状况下,Basic Header长度为一个字节,csid取值范畴为 2~63。
方才说了那么多,才仅仅说了Basic Header,而Basci Header只是Chunk Header的组成部分之一,比拟喜爱折腾的RTMP协定的作者,把RTMP的Chunk Header模块又设计成了动静大小的,简而言之也是为了节俭传输空间,这边可能不便了解的中央就是Chunk Message Header的长度也分四种状况,这就是后面提到的 fmt 这个值决定的。
Message Header 的四种格局如下图所示:
当 fmt 为 0 的时候,Message Header占用11个字节(请留神,这边的11个字节不包含Basic Header的长度),由3个字节长度的timestamp,3个字节长度的message length,1个字节长度的message type Id,4个字节长度的message stream Id所组成的。
其中,timestamp 是相对工夫戳,示意的是这个音讯发送的工夫;message length 示意的是chunk body的长度;message type id 示意的是音讯类型,这个在后文会具体讲到;message stream id 是音讯惟一标识。这边须要留神的是,如果这个音讯的相对工夫戳大于0xFFFFFF,阐明这个工夫大到无奈用3个字节来示意,须要借助扩大工夫戳(Extended Timestamp)来示意,扩大工夫戳长度为4个字节,默认放在Chunk Header和Chunk Body之间。
当 fmt 为 1的时候,Message Header占用7个字节,与之前的11个字节的chunk header相比,少了一个message stream id,这个chunk是复用之前的chunk stream id,这个个别用于可变长的音讯构造。
当 fmt 为 2的时候,Message Header只占用3个字节,就只蕴含timestamp的三个字节,与之前相比,既少了stream id也少了message length,这种少了message length的,个别用于固定长度然而须要修改工夫的音讯(如:音频数据)。
当 fmt 为 3的时候,Chunk Header里就不蕴含 Message Header 了。一般来说,在拆包的时候,把一个残缺的RTMP的Message音讯,会拆成第一个是fmt 为 0的Chunk音讯,随后的音讯也会拆成fmt为3的音讯,这样的做的形式就是第一个Chunk附带着最全的Chunk音讯信息,后续Chunk信息的Header就会比拟小,这样实现比较简单,压缩率也是比拟好。当然,如果第一个Message发送胜利之后,第二个Message再次发送的时候,就会把第二个Message的第一个Chunk设置成fmt为1类型的Chunk,随后该Message的Chunk的fmt为3,这样就可能进行音讯的辨别。
3.1.2 Chunk Body
方才花了很多工夫去形容Chunk Header,接下来咱们再针对Chunk Body进行简略的形容。与Chunk Header相比,Chunk Body就比较简单,没有那么多变长的管制,构造也比较简单,这个外面的数据也就是真正有业务含意的数据,长度默认是128个字节(能够通过 set chunk size 命令协商更改)。外面的数据包组织格局个别是AMF或者FLV格局的音视频数据(不含FLV TAG头)。AMF组织构造的数据组成如下图所示,FLV格局本文不做深刻形容,感兴趣的话能够浏览 FLV 官网文档。
3.1.3 AMF
AMF(Action Message Format) 是一种相似JSON,XML的二进制数据序列化格局,Adobe Flash与近程服务端可通过AMF格局的数据进行数据通信。
AMF具体的格局其实与Map的数据结构很类似,就是在KV键值对的根底上,两头多加了一个Value值的length。AMF的后果根本如下图所示,有时候len字段就是空,这个是由type来决定的,咱们举例来说,例如咱们传输的是number类型的AMF格局的数据,那么len字段咱们就能够疏忽,因为咱们默认number类型的字段占用8个字节,咱们这边就能够疏忽了。
再举例来说,AMF如果传输的是0x02 string类型的数据的时候,len的长度就默认占据2个字节,因为2个字节足够示意前面value的最大长度了。以此类推,当然有些时候,len和value的值都不存在,就比方传递0x05 传递null的时候,len和value咱们就都不须要了。
以下列举一些罕用的AMF的type的对应表格,更多信息能够查看官网文档。
咱们能够通过WireShark来抓包,理论来体验一下具体的AMF0的格局。
如上图所示,这是一个十分典型的AMF0类型string构造的抓包。AMF目前有2个次要的版本,别离是AFM0和AMF3,在目前的理论应用场景中,AMF0还是占据支流的位置。那么AMF0和AMF3有什么区别呢,当客户端给服务器端发送AMF格局Chunk Data数据的时候,服务端在接管到该信息的时候,如何是晓得AMF0或者是AMF3呢?实际上RTMP在Chunk Header中应用message type id来进行辨别,当音讯应用AMF0编码时,message type id等于20,应用AMF3编码时message type id等于17。
3.1.4 Chunk & Message
首先,用一句话来总结一下Chunk和Message的关系,一个Message是由多个Chunk组成,多个Chunk Stream id一样的Chunk称之为Chunk Stream,接收端能够从新合并解析为残缺的Message。RTMP相比于RPC音讯来说,音讯类型多了很多,前文讲的RPC音讯类型归根结底就request,response和heartbeat这三种类型,然而RTMP协定的音讯类型就比拟丰盛。RTMP音讯次要分为以下三大类型:协定管制音讯,数据音讯和命令音讯。
协定管制音讯:Message Type ID = 1~6,次要用于协定内的管制。
数据音讯:Message Type ID = 8 9
188: Audio 音频数据
9: Video 视频数据1
8: Metadata 包含音视频编码、视频宽低等音视频元数据。
命令音讯 Command Message (20, 17):此类型音讯次要有 NetConnection 和 NetStream 两类,两类别离有多个函数,该音讯的调用,可了解为近程函数调用。
总览图如下,后续在源码解析章节,会进行具体介绍,其中着色局部为罕用音讯。
3.2 外围实现流程
网络协议的学习是一个干燥的过程,咱们尝试联合 RTMP协定原文和WireShark抓包的形式,尽量形象地给大家形容 RTMP 协定中的外围流程,包含握手,连贯,createStream,推流和拉流。本节所有的抓包数据的根本环境是:livego作为RTMP服务器(服务端口为1935),OBS作为推流利用,VLC作为拉流利用。
作为一个应用层协定解析来说,首先,咱们要留神的就是主体流程的把握,对于每一个 RTMP 服务器来说,每一个推流和拉流从代码层面来说,都是一个网络链接,针对每一个连贯,咱们要进行对应的工序进行解决,咱们能够看到livego中源码中所展现的一样,有一个handleConn办法,顾名思义,就是用来解决每一个连贯,依照主流程来说,分为第一局部的握手,第二个外围模块的根据RTMP包协定,进行Chunk header和Chunk body的解析,后续再依据解析进去的Chunk header和Chunk body再做具体的解决。
能够看到上述代码块,次要有2个外围办法:一个是HandshakeServer,次要解决握手逻辑;另一个是ReadMsg办法,次要解决Chunk header和Chunk body信息的读取。
3.2.1 第一局部-握手(Handshake)
协定原文的5.2.5节具体介绍了 RTMP 握手的过程,图示如下:
乍一看,可能会感觉此过程有些简单。所以,咱们还是先用 WireShark 抓包来整体看看过程吧。
WireShark 抓包的 Info 可能为咱们解读 RTMP 包的含意,从下图能够看出,握手次要波及到3个包。其中第16号包是客户端向服务端发送 C0 和 C1 音讯,18号包是服务端向客户端发送 S0,S1 和 S2 音讯,20号包是客户端向服务端发送 C2 音讯。如此,客户端和服务端就实现了握手过程。
通过 WireShark 抓包能够看出,握手过程还是十分简洁的,有点相似 TCP 三次握手的过程,所以从理论抓包来说,与RTMP协定原文的5.2.5节介绍的还是有些出入的,整体流程变得很简洁。
当初能够回头看看下面那个比较复杂的握手流程图了。图中将客户端和服务端分为四种状态,别离是:未初始化,已发送版本号,已发送 ACK,握手实现。
未初始化:客户端和服务端无任何交换阶段;
已发送版本号:发送了 C0 或者 S0;
已发送 ACK:发送了 C2 或者 S2;
握手实现:接管到了 S2 或者 C2。
RTMP 协定标准并没有限定死 C0,C1,C2 和 S0,S1,S2 的程序,然而制订了以下规定:
客户端必须收到服务端发来的 S1 后能力发送 C2;
客户端必须收到服务端发来的 S2 后能力发送其余数据;
服务端必须收到客户端发来的 C0 后能力发送 S0 和 S1;
服务端必须收到客户端发来的 C1 后能力发送 S2;
服务端必须收到客户端发来的 C2 后能力发送其余数据。
从 WireShark 抓包剖析能够看出,整个握手过程确实是遵循了以上规定。当初问题来了,C0,C1,C2,S0,S1 和 S2 这些音讯到底是些什么玩意?其实,RTMP 协定标准外面明确定义了它们的数据格式。
C0 和 S0:1个字节长度,该音讯指定了 RTMP 版本号。取值范畴 0~255,咱们只须要晓得 3 才是咱们须要的就行。其余取值含意感兴趣的话能够浏览协定原文。
C1 和 S1:1536个字节长度,由 工夫戳+零值+随机数据 组成,握手过程的两头包。
C2 和 S2:1536个字节长度,由 工夫戳+工夫戳2+随机数据回传 组成,基本上是 C1 和 S1 的 echo 数据。个别在实现上,会令 S2 = C1,C2 = S1。
上面咱们联合 livego 源码来增强对握手过程的了解。
到此为止,最简略的握手流程就到此结束了,能够看出整个握手流程还是比拟清晰的,解决逻辑也是比较简单,也比拟便于了解。
3.2.2 第二局部-信息替换
3.2.2.1 解析RTMP协定的Chunk信息
握手之后,就要做开始做连贯等相干的事件解决了,再做此信息处理之前,工欲善其事必先利其器。
咱们先要依照RTMP协定的标准来解析Chunk Header和Chunk body了,将网络传输的字节包数据转换成咱们可辨认的信息处理,再依据这些可辨认的信息数据,再做对应流程的解决,这块是源码解析的要害外围,波及的知识点十分多,大家能够联合上文一起看,能够不便大家了解ReadMsg这块外围逻辑的了解。
上述的代码块逻辑很清晰,次要是读取每一个conn连贯中,进行对应的编解码,获取到一个个Chunk,并且将雷同ChunkStreamId的Chunk再次进行合并,合并成对应的Chunk Stream,最初一个个残缺的Chunk Stream就是Message了。
这块代码就是和咱们之前实践局部常识介绍的chunkstreamId那块常识比拟靠近的中央了,大家能够联合起来一起看,大家在脑海中,要留神就是一个conn连贯,会传递多个Message,例如连贯Message,createStreamMessage等等,每一个Message就是Chunk Stream,也就是多个csid雷同的Chunk,所以livego的作者应用map这样的数据结构进行存储,key就是csid,value就是chunkstream,这样就能够将向rtmp服务器发送过去的信息可能全副保留下来。
readChunk代码的具体逻辑实现分成如下几个局部:
1)csid的修改,至于实践局部参照上述逻辑,这块其实是basic header的解决。
2)Chunk Header依照format的数值进行对应的解析解决,上文实践局部也曾经介绍过了,下文也有具体的正文解释,有两个技术点须要留神第一就是timestramp工夫戳的解决,第二个留神点是chunk.new(pool)这行代码,也是须要大家留神,代码正文中也写的比较清楚。
3)Chunk Body的读取解决,上文实践局部说过,Chunk header中当fmt 为 0 的时候,会有一个message length字段,这个字段会管制Chunk Body的大小,根据这个字段,咱们能够很轻松地读取到Chunk body信息的读取,整体逻辑如下。
到此为止,咱们曾经胜利解析了Chunk Header,读取了Chunk Body,留神咱们只是读取了Chunk Body还没有依照AMF格局对Chunk Body进行解析,针对Chunk Body局部的逻辑解决,在下文会进行具体的源码介绍,不过当初咱们曾经解析到了一个连贯发送过去的ChunkStream了,接下来咱们就能够回到主流程的剖析了。
方才说了握手实现后,并且咱们也解析到了ChunkStream信息了,接下来咱们就要根据ChunkStream的typeId和Chunk Body中的AMF数据进行对应的工序流程解决了,具体思路大家能够这样了解,客户端A发送xxxCmd命令,RTMP服务端依据typeId和AMF信息解析出xxxCmd命令,并给以对应命令的响应。
上述代码块中的handleCmdMsg中也是这个RTMP服务端解决客户端命令的代码精华了,能够看出livego是反对AMF3和AMF0的,AMF3和AMF0的区别,上文也曾经介绍过了,下文的代码正文写的也比较清楚,而后就是解析AMF格局的Chunk Body的数据,解析进去的后果也是依照Slice格局进行存储。
解析好typeId和AMF,接下来就是瓜熟蒂落的对各个命令进行解决了。
接下来是针对每一个客户端命令的解决了。
3.2.2.2 连贯
连贯(Connect)命令处理过程:连贯过程客户端和服务端会实现窗口大小,传输块大小和带宽大小的确认,RTMP 协定原文具体介绍了连贯过程,如下图所示:
同样,咱们这里用 WireShark 抓包剖析:
从抓包能够看出,连贯过程只用了3个包就实现了:
22 号包:客户端通知服务端,我想要设置 chunk size 为 4096;
24 号包:客户端通知服务端,我想要连贯叫 “live” 的利用;
26 号包:服务端响应客户端的连贯申请,确定窗口大小,带宽大小和 chunk size,以及返回 “_result” 示意响应胜利。这些都是通过一个 TCP 包来实现的。
那么客户端和服务端是如何晓得这些包的含意的呢?这就是 RTMP 协定标准所制订的规定了,咱们能够通过浏览标准来理解,当然也能够通过 wrieshark 来帮忙咱们疾速解析。以下是 22 号包的具体解析,咱们重点关注 RTMP 协定解析信息就行。
从图中能够看出, RTMP Header 蕴含有 Format 信息,Chunk Stream ID 信息,Timestamp 信息,Body size 信息,Message Type ID 信息和 Messgae Stream ID 信息。Type ID 的十六进制值为 0x01,含意为 Set Chunk Size,属于协定管制音讯(Protocol Control Messages)。
RTMP 协定标准5.4节规定了,对于协定管制音讯,Chunk Stream ID 必须设为 2,Message Stream ID 必须设为 0,工夫戳间接疏忽。从 WireShark 抓包解析出的信息可知,22号包确实是合乎 RTMP 标准的。
当初咱们来看看 24 号包的具体解析。
24 号包也是客户端收回的,能够看到它设置Message Stream ID 为 0,Message Type ID 为 0x14(即十进制的20),含意为 AMF0 命令。AMF0 属于 RTMP 命令音讯(RTMP Command Messages),RTMP 协定标准并没有规定连贯过程必须要应用的 Chunk Stream ID,因为真正起作用的是 Message Type ID,服务端依据 Message Type ID 来做相应的响应。连贯过程发送的 AMF0 命令携带的是 Object 类型的数据,会通知服务端要连贯的利用名和播放地址等信息。
以下代码是 livego 解决客户端申请连贯的过程。
收到客户端连贯利用的申请后,服务端须要作出相应响应给客户端,也就是 WireShark 抓取的 26 号包的内容,具体内容如下图所示,能够看到服务端在一个包外面做了好几件事件。
咱们能够联合 livego 源码来深刻学习该过程。
3.2.2.3 createStream
连贯实现后,就能够创立流了。创立流的过程相对来说比较简单,只须要两个包就可能实现,如下所示:
其中 32 号包是客户端发动 createStream 申请,34 号包是服务端响应,以下是 livego 解决客户端连贯申请的源码。
3.2.2.4 推流
创立流实现后,就能够开始推流或者拉流了,RTMP 协定标准的7.3.1节也有给出推流示意图,如下图所示。其中连贯和创立流的过程上文曾经具体介绍过了,咱们重点看公布内容(Publishing Content)的过程就行。
应用 livego 推流前,须要先获取推流的 channelkey。咱们能够通过如下命令获取频道为 “movie” 的 channelKey。响应内容中的 Content 的 data 字段值就是推流须要的 channelKey。
$ curl http://localhost:8090/control/get?room=movie StatusCode : 200StatusDescription : OKContent : {"status":200,"data":"rfBd56ti2SMtYvSgD5xAV0YU99zampta7Z7S575K LkIZ9PYk"}RawContent : HTTP/1.1 200 OK Content-Length: 72 Content-Type: application/json Date: Tue, 09 Feb 2021 09:19:34 GMT {"status":200,"data":"rfBd56ti2SMtYvSgD5xAV0YU99zampta7Z7S575K LkIZ9PYk"}Forms : {}Headers : {[Content-Length, 72], [Content-Type, application/json], [Date , Tue, 09 Feb 2021 09:19:34 GMT]}Images : {}InputFields : {}Links : {}ParsedHtml : mshtml.HTMLDocumentClassRawContentLength : 72
应用OBS推流到 livego 服务器中利用名为 live 的 movie 频道,推流地址为:rtmp://localhost:1935/live/rfBd56ti2SMtYvSgD5xAV0YU99zampta7Z7S575KLkIZ9PYk。同样,咱们还是先看一下WireShark 的抓包内容吧。
推流初期,客户端发动 publish 申请,也就是36号包的内容,该申请中须要带上频道名,在这个包外面就是"rfBd56ti2SMtYvSgD5xAV0YU99zampta7Z7S575KLkIZ9PYk"。
服务端会首先会检测这个频道名是否存在以及查看这个推流名是否被应用中,如果不存在或者在应用的话就会回绝客户端的推流申请。因为咱们在推流前曾经生成了该频道名,客户端能够非法应用,于是服务端在38号包中回应的是 "NetStream.Publish.Start",也就是通知客户端能够开始推流了。客户端在推流音视频数据前须要先把音视频的的元数据发给服务端,也就是40号包所做的事件,咱们能够看一下该包的具体内容。从下图能够看出,发送元数据信息比拟多,蕴含有视频分辨率,帧率,音频采样率和音频声道等要害信息。
通知服务端音视频元数据后,客户端就能够开始发送无效的音视频数据了,服务端会始终接管这些数据,直到客户端收回 FCUnpublish 和 deleteStream 命令为止。stream.go 的 TransStart() 办法次要逻辑为接管推流客户端的音视频数据,而后在本地缓存最新的一个数据包,最初将音视频数据发给各个拉流端。其中读取推流客户单音视频数据次要是应用到 rtmp.go 中的 VirReader.Read() 办法,相干代码和正文如下所示。
附媒体头信息解析的局部源码剖析。
解析音频头
解析视频头
3.2.2.5 拉流
有了推流客户端的继续推流,拉流客户端就能够通过服务器继续拉取到音视频数据了。RTMP 协定标准的7.2.2.1节对拉流过程进行了详细描述。其中,握手、连贯和创立流的过程前文曾经讲述过了,咱们重点关注下 play 命令的过程就行。
同样,咱们先用 WireShark 抓包来剖析下。客户端通过 640 号包通知服务器,我想要播放叫 “movie” 的频道。
此处为什么是叫 “movie” 而不是推流时候用的“rfBd56ti2SMtYvSgD5xAV0YU99zampta7Z7S575KLkIZ9PYk”,其实这两个指向的是同一个频道,只不过一个用于推流一个用于拉流,咱们能够从 livego 的源码来印证这一点。
服务端收到拉流客户端的 play 申请后,会做出响应 "NetStream.Play.Reset","NetStream.Play.Start" ,"NetStream.Play.PublishNotify" 和音视频元数据。这些工作做完后,就能够继续发送音视频数据给拉流客户端了。咱们能够通过 livego 源码来加深一下对此过程的了解。
通过 chan 读取推流数据,而后发给拉流客户端。
到此为止整个RTMP的主体流程就是这样了,这边不波及FLV,HLS等具体传输协定或者格局转换的源码阐明,也就是说RTMP服务器怎么收到推流客户端的音视频包也会一成不变地分发给拉流客户端,并没有做额定的解决,不过当初各大云厂商拉流端都反对http-flv,hls等传输协定的反对,并且也反对音视频的录制回放点播性能,这块livego其实也是反对的。
因为篇幅限度,这边就不再开展介绍,后续有机会,再独自一起学习分享介绍livego对于这块逻辑的解决。
四、瞻望
目前基于RTMP协定的直播是国内直播的基准协定,也是各大云厂商都兼容的直播协定,它的多路复用,分包等优良个性也是各大厂商抉择它的一个重要起因。在这个根底之上,也是因为它是应用层协定,腾讯,阿里,声网等大型云厂商,也会对其协定的细节,进行源码的革新,例如实现多路音视频流的混流,单路的录制等性能。
然而RTMP也有它本人自身的毛病,时延较高就是RTMP一个最大的问题,在理论的生产过程中,即便在比拟衰弱的网络环境中,RTMP的时延也会有3~8s,这与各大云厂商给出的1~3s实践时延值还是有较大出入的。那么时延会带来哪些问题呢?咱们能够设想如下的一些场景:
在线教育,学生发问,老师都讲到下一个知识点了,才看到学生上一个发问。
电商直播,询问宝贝信息,主播“视而不理”。
打赏后迟迟听不到主播的口播感激。
在他人的呐喊声晓得球进了,你看的还是直播吗?
特地是当初直播曾经造成产业链的大环境下,很多主播都是将其作为一个职业,很多主播应用在公司同一个网络下进行直播,在公司网络的进口带宽无限的状况下,RTMP和FLV格局的提早会更加重大,高时延的直播影响了用户和主播的实时互动,也妨碍了一些非凡直播场景的落地,例如带货直播,教育直播等。
以下是应用RTMP协定惯例的解决方案:
依据理论的网络状况和推流的一些设置,例如关键帧距离,推流码率等等,时延个别会在8秒左右,时延次要来自于2个大的方面:
CDN链路提早, 这分为两局部,一部分是网络传输提早。CDN外部有四段网络传输,假如每段网络传输带来的提早是20ms,那这四段提早便是100ms;此外,应用RTMP帧为传输单位,意味着每个节点都要收满一帧之后能力启动向上游转发的流程;CDN为了晋升并发性能,会有肯定的优化发包策略,会减少局部提早。在网络抖动的场景下,提早就更加无法控制了,牢靠传输协定下,一旦有网络抖动,后续的发送流程都将阻塞,须要期待前序包的重传。
播放端buffer,这个是提早的次要起源。公网环境千差万别,推流、CDN传输、播放接管这几个环节任何一个环节产生网络抖动,都会影响到播放端。为了反抗前边链路的抖动,播放器的惯例策略是保留6s 左右的媒体buffer。
通过上述阐明,咱们能够分明的晓得,直播最大的提早就是在于拉流端(播放端buffer)的时延,所以如何疾速地去打消这个阶段的时延,就是各大云厂商亟待解决的问题,这就是后续各大云厂商推出打消RTMP协定时延的新的产品,例如腾讯云的"快"直播,阿里云的超低时延RTS直播等等,其实这些直播都引入了WebRTC技术,后续咱们有机会能够一起学习相干常识。
五、参考资料
1.RTMP 官网文档
2.AMF0
3.AMF3
4.FLV 官网文档
5.FLV 文件格式剖析
6.livego 源码
7.手撕rtmp协定专项
作者:vivo互联网服务器团队-Xiong Langyu