网络协议 14 - 流媒体协议:要说爱你不容易

 前五篇】系列文章传送门:

  1. 视频编码的两大流派

        视频编码的算法这么多,能不能形成一定的标准呢?当然能,这里咱们就来认识下视频编码的两大流派。

    • 流派一:ITU(International tELECOMMUNICATIONS Union)的 VCEG(Video Coding Experts Group),这个称为国际电联下的 VCEG。既然是电信,可想而知,他们最初是做视频编码,主要侧重传输。我们上面的系列名词二,就是这个组织制定的标准。
    • 流派二:ISO(International Standards Organization)的 MPEG(Moving Picture Experts Group),这个是 ISO 旗下的 MPEG。本来是做视频存储的,就像咱们场面常说的 VCD 和 DVD。后来也慢慢侧重视频传输了。系列名词三就是这个组织制定的标准。
    • 后来,ITU-T(国际电信联盟电信标准化部门)与 MPEG 联合制定了 H.264/MPEG-4 AVC,这也是我们重点关注的。

    直播数据传输

        视频经过编码之后,生动活泼的一帧帧图像就变成了一串串让人看不懂的二进制。这个二进制可以放在一个文件里,然后按照一定的格式保存起来,这里的保存格式,就是系列名词一。

        编码后的二进制文件就可以通过某种网络协议进行封装,放在互联网上传输,这个时候就可以进行网络直播了。

        网络协议将编码好的视频流,从主播端推送到服务器,在服务器上有个运行了同样协议的服务端来接收这些网络数据包,从而得到里面的视频流,这个过程称为接流

        服务端接到视频流之后,可以滴视频流进行一定的处理,比如转码,也就是从一个编码格式转成另一种格式,这样才能适应各个观众使用的客户端,保证他们都能看到直播。

        流处理完毕后,就可以等待观众的客户端来请求这些视频流。观众的客户端请求视频流的过程称为拉流

        如果有非常多的观众同时看一个视频直播,都从一个服务器上拉流,压力就非常大,因此需要一个视频的分发网络,将视频预先加载到就近的边缘节点,这样大部分观众就能通过边缘节点拉取视频,降低服务器的压力。

        当观众将视频流拉下来后,就需要进行解码,也就是通过上述过程的逆过程,将一串串看不懂的二进制转变成一帧帧生动的图片,在客户端播放出来。

        整个直播过程,可以用下图来描述:

        接下来,我们依次来看一下每个过程:

    编码:将丰富多彩的图片变成二进制流

        虽然我们说视频是一张张图片的序列,但如果每张图片都完整,就太大了,因而会将视频序列分成三种帧:

    • I帧,也称关键帧。里面是完整的图片,只需要本帧数据,就可以完成解码。
    • P帧,前向预测编码帧。P 帧表示的是这一帧跟之前一个关键帧(或 P 帧)的差别,解码时需要用之前缓存的画面,叠加上和本帧定义的差别,生成最终画面。
    • B帧,双向预测内插编码帧。B 帧记录的是本帧与前后帧的差别。要解码 B 帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面的数据与本帧数据的叠加,取得最终的画面。

        可以看出,I 帧最完整,B 帧压缩率最高,而压缩后帧的序列,应该是 IBBP 间隔出现。这就是通过时序进行编码

        在一帧中,分成多个片,每个片中分成多个宏块,每个宏块分成多个子块,这样将一张大图分解成一个个小块,可以方便进行空间上的编码。如下图:

        尽管时空非常立体的组成了一个序列,但总归还是要压缩成一个二进制流。这个流是有结构的,是一个个的网络提取层单元(NALU,Network Abstraction Layer Unit)。变成这种格式就是为了传输,因为网络上的传输,默认的是一个个的包,因而这里也就分成了一个个的单元。

        如上图,每个 NALU 首先是一个起始标识符,用于标识 NALU 之间的间隔。然后是 NALU 的头,里面主要配置了 NALU 的类型。最后的 Payload 里面是 NALU 承载的数据。

        在 NALU 头里面,主要的内容是类型 NAL Type,其中:

    • 0x07 表示 SPS,是序列参数集,包括一个图像序列的所有信息,如图像尺寸、视频格式等。
    • 0x08 表示 PPS,是图像参数集,包括一个图像的所有分片的所有相关信息,包括图像类型、序列号等。

        在传输视频流之前,剥削要传输者两类参数,不然就无法解码。为了保证容错性,每一个 I 帧之前,都会传一遍这两个参数集合。

        如果 NALU Header 里面的表示类型是 SPS 或 PPS,则 Payload 中就是真正的参数集的内容。

        如果类型是帧,则 Payload 中是真正的视频数据。当然也是一帧帧保存的。前面说了,一帧的内容还是挺多的,因而每一个 NALU 里面保存的是一片。对于每一片,到底是 I 帧,还是 P 帧,亦或是 B 帧,在片结构里面也有 Header,这里面有个类型用来标识帧的类型,然后是片的内容。

        这样,整个格式就出来了。一个视频,可以拆分成一系列的帧,每一帧拆分成一系列的片,每一片都放在一个 NALU 里面,NALU 之间都是通过特殊的起始标识符分隔,在每一个 I 帧的第一片前面,要插入单独保存 SPS 和 PPS 的 NALU,最终形成一个长长的 NALU 序列

    推流:将数据流打包传输到对端

        形成 NALU 序列后,还需要将这个二进制的流打包成网络包进行发送。这里我们以 RTMP 协议为例,进入第二个过程,推流

        RTMP 是基于 TCP 的,因而也需要双方建立一个 TCP 连接。在有 TCP 的连接的基础上,还需要建立一个 RTMP 连接,也就是在程序里面,我们调用 RTMP 类库的 Connet 函数,显式创建一个连接。

        RTMP 为什么需要建立一个单独的连接呢?

        因为通信双方需要商量一些事情,保证后续的传输能正常进行。其实主要就是两个事情:

    1. 确定版本号。如果客户端、服务端的版本号不一致,就不能正常工作;
    2. 确定时间戳。视频播放中,时间是很重要的一个元素,后面的数据流互通的时候,经常要带上时间戳的差值,因而一开始双方就要知道对方的时间戳。

        沟通这些事情,需要发送 6 条消息:

    • 客户端发送 C0、C1、C2
    • 服务端发送 S0、S1、S2

        首先,客户端发送 C0 表示自己的版本号,不必等对方回复,然后发送 C1 表示自己的时间戳。

        服务器只有在收到 C0 的时候,才会返回 S0,表明自己的版本号,如果版本不匹配,可以断开连接。

        服务器发送完 S0 后,也不用等待,就直接发送自己的时间戳 S1。

        客户端收到 S1 时,发一个知道了最烦时间戳的 ACK C2。同理,服务器收到 C1 的时候,发一个知道了对方时间戳的 ACK S2。

        于是,握手完成。

        握手之后,双方需要互相传递一些控制信息,例如 Chunk 块的大小、窗口大小等。

        真正传输数据的时候,还是需要创建一个流 Stream,然后通过这个 Stream 来推流。

        推流的过程,就是讲 NALU 放在 Message 里面发送,这个也称为 RTMP Packet 包。其中,Message 的格式就像下图所示:

        发送的时候,去掉 NALU 的起始标识符。因为这部分对于 RTMP 协议来讲没有用。接下来,将 SPS 和 PPS 参数集封装成一个 RTMP 包发送,然后发送一个个片的 NALU。

        RTMP 在收发数据的时候并不是以 Message 为单位的,而是把 Message 拆分成 Chunk 发送,而且必须在一个 Chunk 发送完成之后,才能开始发送下一个 Chunk。每个 Chunk 中都带有 Message ID,表示属于哪个 Message,接收端也会按照这个 ID 将 Chunk 组装成 Message。

        前面连接的时候,设置 Chunk 块大小就是指这个 Chunk。将大的消息变为小的块再发送,可以在低带宽的情况下,减少网络拥塞。

        下面用一个分块的示例,来了解下 RTMP 是如何分块的。

        假设一个视频的消息长度是 307,而 Chunk 大小约定为 128,那么消息就会被拆分为 3 个 Chunk。

    关键字:
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信