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

    这样,整个格式就出来了。一个视频,可以拆分成一系列的帧,每一帧拆分成一系列的片,每一片都放在一个 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 时,发一个知道了最烦时间戳的 ACK C2。同理,服务器收到 C1 的时候,发一个知道了对方时间戳的 ACK S2。

    于是,握手完成。

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

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

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

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

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

    发送的时候,去掉 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。

    第一个 Chunk 的 Type = 0,表示 Chunk 头是完整的。头里面 Timestamp 为 1000,总长度 Length 为 307,类型为 9,是个视频,Stream ID 为 12346,正文部分承担 128 个字节的 Data。

    第二个 Chunk 也要发送 128 个字节,但是由于 Chunk 头和第一个一样,因此它的 Chunk Type = 3,表示头和第一个 Chunk 一样。

    第三个 Chunk 要发送的 Data 的长度为 51 个字节,Chunk Type 还是用的 3。

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

    就这样,数据源源不断的到达流媒体服务器,整个过程就像下图:

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

    这个时候,大量观看直播的观众就可以通过 RTMP 协议从流媒体服务器上拉取。为了减轻服务器压力,我们会使用分发网络

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyfppw.html