初识网络协议:流媒体协议
流媒体(Streaming Media)是指将一连串的媒体数据压缩后,经过网上分段发送数据,在网上即时传输影音以供观赏的一种技术与过程,此技术使得数据包得以像流水一样发送。我们现在可以在线观看视频,就是使用了这种技术。
那什么是视频呢?就是图片,一连串快速播放的连续的图片。
每一张图片,我们称为一帧。只要每秒的帧数足够多,比如每秒30帧,以人眼的敏感程度,是看不出来这是一张张独立的图片的,这就是我们常说的帧率(FPS)。
每一张图片都是由像素组成的,假设为1920*1080(1080P)。每个像素由RGB组成,每个8位,共24位。
那么每秒钟的视频有多大?
30帧 * 1920 * 1080 * 24 = 1,492,992,000Bits = 186,624,000Bytes
每分钟呢?就是11,197,440,000Bytes = 10,935,000KB,已经10个G了。
那么问题来了,看个1080P的视频真的这么消耗流量吗?如果真的这么消耗流量的话,那么2K、4K的视频还怎么看?
为此,人们想到了编码,就是如何用尽可能少的Bit数保存视频,使播放的时候画面看起来仍然很精美。编码是一个压缩的过程。
视频和图片的压缩有如下特点:
-
空间冗余:图像的相邻像素之间有较强的相关性,一张图片相邻像素往往是渐变的,不是突变的,没必要每个像素都完整地保存,可以隔几个保存一个,中间的用算法计算出来。
-
时间冗余:视频序列的相邻图像之间内容相似。一个视频中连续出现的图片也不是突变的,可以根据已有的图片进行预测和推断。
-
视觉冗余:人的视觉系统对某些细节不敏感,因此不会每一个细节都注意到,可以允许丢失一些数据。
-
编码冗余:不同像素值出现的概率不同,概率高的用的字节少,概率低的用的字节多。
总之,用于编码的算法非常复杂,而且多种多样,但是编码过程其实都是类似的。
目前通用的编码是ITY-T(国际电信联盟电信标准化部门,International Telecommunications Union Telecommunication Standardization Sector)和MPEG(Moving Picture Experts Group)联合制定的H.264/MPEG-A AVC,这是我们所需关注的。
经过编码后,一帧一帧图像,就变成一串串二进制,以一定的格式(如AVI、MP4),放在一个文件里面。
其实这些就是视频保存成文件的格式。例如,前几个字节是什么意思,后几个字节是什么意思,然后是数据,数据中保存的就是编码好的结果。
现在非常火的直播,就是将二进制通过某种网络协议进行封装,然后放在互联网上传输。
网络协议将编码好的视屏流,从主播端推送到服务器(推流),在服务器上有个运行了同样协议的服务端来接收这些网络包,从而得到里面的视频流,这个过程称为接流。
服务端接到视频流之后,可以对视频流进行一定的处理,例如转码,即从一个编码格式,转成另一种格式。因为观众使用的客户端千差万别,要保证他们都能看到直播。
流处理完毕之后,就可以等待观众的客户端来请求这些视频流。这个过程叫作拉流。
如果有非常多的观众,同时看同一个直播,都从一个服务器上拉流,那压力太大了。因此需要一个视频的分发网络,将视频预先加载到就近的边缘节点,这样大部分观众看的视频,是从边缘节点拉取的,就能降低服务器的压力。
当客户端拉流之后,就需要进行解码,将一串串二进制转变成一帧帧生动的图片,在客户端播放。
整个直播过程,如下图所示。
如何编码?
虽说一帧是一张图片,但是如果每张图片都完整,那就太大了。因此视频序列被分成三种帧。
-
I帧,也称关键帧。里面是完整的图片,没有什么好解释的。
-
P帧,前向预测编码帧。P帧表示的是这一帧跟之前的一个关键帧(或P帧)的差别,解码时需要用之前缓存的画面,叠加上和本帧定义的差别,生成最终画面。
-
B帧,双向预测内插编码帧。B帧记录的是本帧与前后帧的差别。要解码B帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面的数据与本帧数据的叠加,取得最终的画面。
可以看出,I 帧最完整,B 帧压缩率最高,而压缩后帧的序列,应该是在 IBBP 的间隔出现的。
如何理解 IBBP 呢?
I是完整的,P可以找到I,所以也是完整的。B是由前面的I和后面的P来进行预测的,所以I、P都有了,B也就都有了。
在一帧中,分成多个片,每个片中分成多个宏块,每个宏块分成多个子块,这样将一张大的图分解成一个个小块,可以方便进行空间上的编码。
尽管时空非常立体的组成了一个序列,但是总归还是要压缩成一个二进制流。这个流是有结构的,是一个个的网络提取层单元(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 里面保存的是一片
如何推流?
这里我们用RTMP协议(Real Time Messaging Protocol)。即实时消息传输协议,是 Adobe 公司开发的一个基于 TCP 的应用层协议,目前国内的视频云服务都是以 RTMP 为主要推流协议。
RTMP 是基于 TCP 的,因而肯定需要双方建立一个 TCP 的连接。在有 TCP 的连接的基础上,还需要建立一个 RTMP 的连接,也即在程序里面,你需要调用 RTMP 类库的 Connect 函数,显示创建一个连接。
RTMP 单独建立连接的目的是为了商量一些事情,保证传输能正常进行。主要就是两个事情,一个是版本号,如果客户端、服务器的版本号不一致,则不能工作。另一个就是时间戳,视频播放中,时间是很重要的,后面的数据流互通的时候,经常要带上时间戳的差值,因而一开始双方就要知道对方的时间戳。
连接建立的过程如下。
首先客户端发送 C0 表示自己的版本号,不必等对方回复,然后发送 C1 表示自己的时间戳。
服务器只有在收到 C0 的时候,才能返回 S0,表明自己的版本号,如果版本不匹配,可以断开连接。
服务端发送完 S0 后,直接发送自己的时间戳 S1。
客户端收到 S1 后,发送 ACK C2。同理服务端收到 C1 后,发送 ACK S2。
于是,握手完成。
握手之后,双方需要互相传递一些控制信息,例如 Chunk 块的大小、窗口大小等。
真正传输数据的时候,还是需要创建一个流 Stream,然后通过这个 Stream 来推流 publish。
推流的过程,就是将 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。将大的消息变为小的块再发送,可以在低带宽的情况下,减少网络拥塞。
这有一个分块的例子,你可以看一下。
假设一个视频的消息长度为 307,但是 Chunk 大小约定为 128,于是会拆分为三个 Chunk。
第一个 Chunk 的 Type=0,表示 Chunk 头是完整的;头里面 Timestamp 为 1000,总长度 Length为 307,类型为 9,是个视频,Stream ID 为 12346,正文部分承担 128 个字节的 Data。
第二个 Chunk 也要发送 128 个字节,Chunk 头由于和第一个 Chunk 一样,因此采用 Chunk Type=3,表示头一样就不再发送了。
第三个 Chunk 要发送的 Data 的长度为 307-128-128=51 个字节,还是采用 Type=3。
就这样数据就源源不断到达流媒体服务器,整个过程就像这样。
这个时候,大量观看直播的观众就可以通过 RTMP 协议从流媒体服务器上拉取,但是这么多的用户量,都去同一个地方拉取,服务器压力会很大,而且用户分布在全国甚至全球,如果都去统一的一个地方下载,也会时延比较长,需要有分发网络。
分发网络分为中心和边缘两层。边缘层服务器部署在全国各地及横跨各大运营商里,和用户距离很近。中心层是流媒体服务集群,负责内容的转发。智能负载均衡系统,根据用户的地理位置信息,就近选择边缘服务器,为用户提供推 / 拉流服务。中心层也负责转码服务,例如,把 RTMP 协议的码流转换为 HLS 码流。
如何拉流?
接下来,我们再来看观众的客户端通过 RTMP 拉流的过程。