【前五篇】系列文章传送门:网络协议9-TCP协议(下):聪明又被人误解网络协议10-Socket编程(上):实践是检验真理的唯一标准网络协议11-Socket编程(下):眼见为实,耳听为虚网络协议12-HTTP协议:常用但不简单的网络协议13-HTTPS协议:加密无止境大家都知道关注“在浏览器中输入一个地址,然后回车,会发生什么”的问题,但是大家有没有想过这样一个问题:主播开始直播,而用户打开客户端观看,这个过程会发生什么?随着科技的发展,直播技术对人们生活的渗透越来越深。从最初的游戏直播,到前几天爆发的教育直播,现在甚至还有直播招聘。而我们喜欢的这些直播,他们使用的传输协议都有一个共同的名字——流媒体传输协议。要理解流媒体协议,离不开下面三个系列的名词。三系列名词系列一:AVI、MPEG、RMVB、MP4、MOV、FLV、WebM、WMV、ASF、MKV。你熟悉MP4吗?系列2:H.261、H.262、H.263、H.264、H.265。你能认出几个?放轻松,专注于H.264。系列三:MPEG-1、MPEG-2、MPEG-4、MPEG-7。你是不是更糊涂了?在解释以上三个系列的名词之前,我们先来了解一下什么是视频?博主记得小时候经常玩一个叫动态绘本的东西。一本很小的图画书,每一页都画了一幅画,用手快速翻动每一页就能看到一个简短的“漫画”。是的,我们看到的视频本质上就是一系列快速播放的图片。每张图片称为一帧。只要每秒的数据足够多,也就是播放速度足够快,人眼是看不出来是独立画面的。对于人眼来说,临界播放速度是每秒30帧,这里的30就是我们常说的帧率(FPS)。每张图片由像素组成,每个像素由RGB组成,每组8位,共24位。我们假设一个视频中所有图片的像素都是1024*768,我们可以粗略估算出视频的大小:Sizepersecond=30framesx1024x768x24=566,231,010Bits=70,778,880Bytes根据我们上面的估算,一分钟的视频大小是4,,246,732,800Bytes,这里已经有4G了。我们每天接触到的视频大小是否明显不一致?这是因为我们在传输过程中压缩了视频。为什么要压缩视频?根据我们上面的估算,一个小时的视频有240G,这个数据量根本无法存储和传输。因此,人们利用编码技术对视频进行“瘦身”,让视频保持尽可能少的Bits,同时保证播放时画面依然非常清晰。其实编码就是压缩的过程。视频和图片的压缩特性我们之所以能够对视频流中的图片进行压缩,是因为视频和图片具有以下特点:逐渐地,而不是突然地。没有必要完全保存每个像素。每隔几次就可以保存,中间的是算法计算出来的。时间冗余:视频序列的相邻图像之间的内容相似性。视频中连续出现的画面并不突兀,可以根据已有的画面进行预测和推断。视觉冗余:人类视觉系统对某些细节不敏感,因此不会注意到每个细节,可能会丢失一些数据。编码冗余:不同的像素值出现概率不同。概率高的用的字节少,概率低的用字节多,类似于霍夫曼编码的思想。从以上特点可以看出,编码所用的算法是非常复杂和多样的。虽然算法很多,但编码过程其实大同小异,如下图所示:视频编码的两大流派视频编码算法那么多,能否形成一定的标准?当然,这里我们来了解视频编码的两大流派。Genre1:ITU(国际电信联盟)的VCEG(VideoCodingExpertsGroup),在ITU下称为VCEG。既然是电信,可以想象他们最初是做视频编码的,主要是传输为主。上述第二系列术语是本组织制定的标准。Genre2:ISO(国际标准组织)的MPEG(MovingPictureExpertsGroup),这是ISO下的MPEG。它最初是用来存储视频的,就像我们在现场常说的VCD、DVD一样。后来逐渐专注于视频传输。系列名词三是本组织制定的标准。后来ITU-T(国际电信联盟电信标准化部门)和MPEG联合开发了H.264/MPEG-4AVC,这也是我们关注的重点。直播数据传输?视频经过编码后,生动的一帧一帧的图像变成一串串无法理解的二进制。这个二进制文件可以放在一个文件中,然后以某种格式保存。这里的保存格式是系列名词一。编码后的二进制文件可以通过一定的网络协议进行封装,在互联网上传输。这时候就可以在网上直播了。网络协议将编码后的视频流从主播推送到服务器,服务器上有运行相同协议的服务器接收这些网络数据包,从而获取里面的视频流。这个过程称为流式传输。服务器接收到视频流后,可以对视频流进行一定的处理,比如转码,即从一种编码格式转换为另一种编码格式,以适应各种观众使用的客户端,保证他们能够都看到了。直播。处理完流后,您可以等待查看者的客户端请求这些视频流。观看者的客户端请求视频流的过程称为流式传输。如果有很多观众同时看直播,他们都从一个服务器拉流,压力很大。因此,需要一个视频分发网络,将视频预加载到最近的边缘节点,让大部分观众可以通过边缘节点拉取视频,减轻服务器的压力。观看者在拉下视频流时,需要进行解码,即通过上述过程的逆过程,将一串串看不懂的二进制转换成一帧帧生动的画面,在客户端播放。整个直播过程可以用下图来描述:?????????????????????????????????????????????????????????或者说,如果每一张图片都是图片是完整的,它太大了,所以视频序列会被分为三种帧:I帧,也叫关键帧。里面是一张完整的图片,只需要这一帧的数据就可以完成解码。P帧,前向预测编码帧。P帧表示这一帧与前一个关键帧(或P帧)的区别。解码时,需要使用之前缓存的图片,叠加与本帧清晰度的差值,生成最终的图片。B帧,双向预测插值编码帧。B帧记录了当前帧与前后帧的差异。对B帧进行解码,不仅要得到之前缓存的图片,还要得到解码后的图片,将前后图片的数据叠加到本帧的数据上,就可以得到最终的图片。可以看出I帧最完整,B帧压缩率最高,压缩帧序列应该以IBBP间隔出现。这是按时间编码。在一帧中,分为多个切片,每个切片又分为多个宏块,每个宏块又分为多个子块。这样,将大图像分解成小块,便于空间编码。如下图所示:虽然时空是非常三维的才能形成序列,但是还是需要压缩成二进制流。这个流是结构化的,是一个网络抽象层单元(NALU,NetworkAbstractionLayerUnit)。改成这种格式是为了传输,因为网络上的传输默认是单个的包,所以分成了单个的单元。如上图所示,每个NALU首先是一个起始标识,用于标识NALU之间的间隔。然后是NALU的header,主要是配置NALU的类型。最终的Payload包含了NALU携带的数据。在NALUheader中,主要内容是typeNALType,其中:0x07表示SPS,是一个序列参数集,包括一个图像序列的所有信息,比如图像大小,视频格式等。0x08表示PPS,即是一个图像参数集,包括图像所有切片的所有相关信息,包括图像类型、序列号等。在传输视频流之前,利用需要传输两类参数,否则无法解码。为了保证容错性,这两个参数集在每个I帧之前传递。如果NALUHeader中的representationtype是SPS或者PPS,那么payload就是真实参数集的内容。如果类型是帧,则有效载荷是真实的视频数据。当然也是逐帧保存。前面说了,一帧内容还是很多的,所以每个NALU存储一个slice。对于每一个slice,不管是I帧,P帧,还是B帧,在slice结构中也有一个Header,里面有一个type,用来标识帧的类型,然后是header的内容片。这样,整个格式就出来了。一个视频可以拆分成一系列的帧,每个帧拆分成一系列的片,每个片放在一个NALU中,NALU之间用特殊的起始标识分隔,在每个I帧的第一片前面,分别存储SPS和PPS的NALU要插入,组成一个长NALU序列。推流:数据流打包传输给对端形成NALU序列后,需要将二进制流打包成网络包发送。这里我们以RTMP协议为例,进入第二个流程,流式传输。RTMP是基于TCP的,所以双方都需要建立一个TCP连接。在一个TCP连接的基础上,需要建立一个RTMP连接,即在程序中,我们调用RTMP类库的Connet函数来显式建立一个连接。为什么RTMP需要单独建立连接?因为通信双方需要商量一些事情,以保证后续的传输能够正常进行。其实主要有两点:确定版本号。如果客户端和服务器的版本号不一致,将无法正常工作;确定时间戳。在视频播放中,时间是一个非常重要的元素。后续数据流通信时,往往会包含时间戳的差异,因此双方一开始就需要知道对方的时间戳。通信这些东西,需要发送6条消息:客户端发送C0、C1、C2,服务端发送S0、S1、S2首先,客户端发送C0表示自己的版本号,不等对方回复,然后发送C1来指示自己的时间戳。服务器只有收到C0才会返回S0,表示自己的版本号。如果版本不匹配,可以断开连接。发送S0后,服务器不等待,直接发送自己的时间戳S1。当客户端收到S1时,它发送一个知道最新时间戳的ACKC2。同理,服务端收到C1后,发送一个知道对方时间戳的ACKS2。这样,握手就完成了。握手后,双方需要传递一些控制信息给对方,比如Chunk块的大小,窗口的大小等。真正传输数据的时候,还是需要创建一个流Stream,并且然后通过这个Stream推送流。推流的过程就是在Message中发送NALU,也叫RTMPPacket。其中,Message的格式如下图所示:发送时去掉NALU的初始标识。因为这部分对于RTMP协议是没有用的。接下来将SPS和PPS参数集封装成RTMP包发送,再将NALU一个一个发送。RTMP在收发数据时不以Message为单位,而是将Me??ssage拆分成Chunk进行发送,发送完一个Chunk后才能发送下一个Chunk。每个Chunk都有一个MessageID,表示属于哪个Message,接收端也会根据这个ID将Chunk组装成Message。之前连接的时候,设置Chunkblocksize就是指这个Chunk。在发送大消息之前将大消息转换为小块可以减少低带宽情况下的网络拥塞。让我们用一个分块的例子来理解RTMP是如何分块的。假设一个视频的消息长度为307,Chunk大小约定为128,则消息将被拆分为3个Chunk。第一个Chunk的Type=0,说明Chunk头是完整的。header中的Timestamp为1000,总长度为307,type为9,为video,StreamID为12346,text部分承载了128字节的Data。第二个Chunk也需要发送128字节,但是由于Chunkheader和第一个Chunkheader一样,所以它的ChunkType=3,也就是说header和第一个Chunk是一样的。第三个Chunk要发送的数据长度为51字节,ChunkType依然为3。‖‖‖这样,数据源源不断的到达流媒体服务器,整个过程如下图:同时,大量观看直播的观众可以通过RTMP协议从流媒体服务器中拉取。为了减轻服务器压力,我们将采用分布式网络。配电网分为中心和边缘两层。边缘服务器遍布全国和各大运营商,离用户非常近。中心层是流媒体服务集群,负责转发内容。智能负载均衡系统根据用户的地理位置信息选择最近的边缘服务器为用户提供推/拉流服务。中央层还负责转码服务。比如将RTMP协议的码流转为HLS码流。推流:观众客户端看到直播视频?接下来,我们看一下观众通过RTMP拉流的过程。先读取H.264的解码参数,比如SPS、PPS,然后将接收到的NALU帧一帧一帧合成,解码,交给播放器播放,客户端就可以看到直播视频了。总结视频名词很多,编码的两大流派达成一致,通过时空的各种算法对数据进行压缩;压缩后的数据形成一系列的NALU进行传输,按帧和分片依次排列;一个好的NALU,在网络传输的时候,一定要按照RTMP包的格式进行打包,RTMP包会被拆分成Chunks进行传输;推送到流媒体集群的视频流可以被客户端通过RTMP协议转码分发拉取,然后组合成NALU,解码成视频格式进行播放。参考:RTMP协议规范;刘超-趣谈网络协议系列;
