WebRTC在发送视频帧的时候,是会被切割成一个个小包的,作为网络传输,任何在网络中传输的包都应该小于MTU,这是常识。那么问题来了,假设需要发送一个又一个已压缩的H.264视频帧的时候,是如何切帧组包的呢?
H.264裸帧切割逻辑
首先,每一次WriteFrame时,我们塞入的都是一个完整的H.264裸帧,例如以下帧的Hex显示:
- 该帧为一个H.264帧
- 携带StartCode
那么,像这样的一个帧到了WebRTC内部,会怎么处理呢?
- 首先,内部会先对该帧进行编码类型判别,要确认是H.264。
- 然后,根据StartCode,对帧的每一个NALU进行一次切割,StartCode包括:00 00 00 01 与 00 00 01都算,切割完毕之后,得到所有切割后的Buffer块数量。
- 假设有10个块,那就用一个数组容器将这9个块分别缓存起来,并将他们头部的 00 00 00 01 或 00 00 01 去除,剩下的字节等待下一步行动。
- 那么,假设NALU超过MTU怎么办?那就只能根据MTU再进行二次切割。每一个NALU又被切割成多个NALU子分片,这些分片会被套上一个FU头,FU头部包含了NALU的类型(如I帧、P帧等)、NALU的长度、以及当前分片的序号和结束标志等信息,这些信息都可以用来重组NALU。
- 接下来,对每一个切割后的数据作为Payload载荷,进行加密,这里要注意,SRTP加密后的数据和原始数据的长度是不一样的,例如下图,加密前后加密后的长度差异:
- 加密后,对每一段数据套上一个RTP头,以及一个加密尾,如下图,其中SRTP头占12字节,SRTP尾还有个认证标签(AuthTag)占了10个字节。
- 这样的一个包,最终被发送到网络上。
接收端如何组包呢?
首先,接收端会现在JitterBuffer中进行一些算法,来保证传输的有序性与完整性。然后等整个帧组合完毕后,我们得到了一个完整的帧。
这个帧是如何组成的呢?大部分都是切帧的反向操作,但区别在于:
- 与切帧区别的是,每一个子包并不知道原先的StartCode是00 00 00 01 还是 00 00 01,所以,都用 00 00 00 01来替代。这就造成了裸帧数据长度与原先产生了差异。
- 另外,组和完毕后的裸帧,也是携带StartCode,不需要我们再去手动添加。但要清楚的是,网络传输的时候,RTP的载荷是不携带StartCode的,他是根据我们收到后根据包序列自己添加上去的,这点不要混淆。
- 假如NALU本身超过了MTU,收到的时候不会立刻对每一个NALU子分片加上StartCode,而是等到NALU全部组和完成后,再对整个NALU完整分片加上StartCode。
- 根据RTP特性,同一个帧在传输的时候,时间戳一致,以Marker结尾,你可以看成是: