关于服务器:玩转直播系列之RTMP协议和源码解析2

5次阅读

共计 13306 个字符,预计需要花费 34 分钟才能阅读完成。

一、背景

实时音讯传输协定(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        : 200
StatusDescription : OK
Content           : {"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.HTMLDocumentClass
RawContentLength  : 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

正文完
 0