概述

        在上一篇文章中,我们已经详细介绍了RTP包的打包格式。RTP打包通常需要与具体的编码格式相结合,设计出适合当前编码格式的打包方法。H264,也被称为AVC(Advanced Video Coding),是一种广泛使用和高度压缩的数字视频编解码器标准。当使用RTP传输H264视频流时,需要遵循一定的打包和传输规则。

H264 NALU

        H264 NALU是H264视频编码标准中用于网络传输和存储的基本数据单元。NALU的设计旨在为视频编码数据提供一个与底层网络无关的抽象层,使得经过H264压缩编码的视频能够适应各种不同的网络环境和存储介质。

        每个NALU都以一个固定长度的NAL Unit Header开始,NAL Unit Header占用一个字节,通常包含以下几个字段。

        Forbidden Zero Bit (F): 占1位,用于错误检测,通常设置为0。如果收到的NALU中此位为1,则可能表示数据损坏。

        NAL Reference IDC (RefIdc): 占2位,用于标识该NALU内容的重要性。高两位分别代表非参考帧(00)、参考帧(01或10)和关键帧(11,如IDR帧)。

        NALU Type (Type): 占5位,定义了NALU所携带数据的类型。不同的NALU Type对应着不同的编码数据或控制信息,比如:P帧和B帧为1,IDR帧为5,SEI(Supplemental Enhancement Information)为6,SPS(Sequence Parameter Set)为7,PPS(Picture Parameter Set)为8等。

        紧跟在NAL Unit Header之后的是NAL Unit Payload,也称为Raw Byte Sequence Payload (RBSP)。它包含了实际的编码视频数据,比如:编码后的像素信息、运动矢量、量化参数等。RBSP内部可能还包括一些额外的填充字节,用于防止在某些网络环境中可能出现的“Start Code Emulation”问题。

        在实际的网络传输和存储中,NALU通常还需要进一步封装成以下格式中的一种。

        Annex B格式:在Annex B格式中,每个NALU之前添加一个Start Code Prefix,可以是0x000001或0x00000001,用于标识NALU的起始位置。相邻NALU之间,以此方式明确分隔。

        AVCC (Advanced Video Coding Container) 格式:AVCC格式常见于MP4容器中,NALU不再使用Start Code Prefix,而是通过Length字段来标识每个NALU的长度。SPS和PPS等参数以NALU形式封装,并在MP4文件的avcC盒(Box)中以字节串的形式存储。

封装方法

        H264 NALU在封装到 RTP包中时,需要遵循一定的规则和流程,以确保数据能够被正确地传输、接收和解码。根据NALU的大小和传输需求,可以选择以下三种常见的封装方法。

        1、单NALU封装。对于小型的NALU,(比如:P帧、B帧),可以直接将整个NALU放入一个RTP包的Payload中,无需额外处理。此时,RTP包的结构如下。

+-----------------------------+
| RTP Header (12 Byte)        |
| NALU Header (1 Byte)        |
| NALU Data ...               |
+-----------------------------+

        2、FU-A分包。对于大型NALU(比如:某些关键帧),如果其大小超过了RTP包的最大有效载荷MTU,可以使用Fragmentation Unit A方式进行分片。原始NALU会被拆分成多个片段,每个片段作为一个独立的RTP包发送。此时,RTP包的结构如下。

+-----------------------------+
| RTP Header (12 Byte)        |
| FU Indicator (1 Byte)       |
| FU Header (1 Byte)          |
| Fragmented NALU Data ...    |
+-----------------------------+

        可以看到,FU-A分包在12个字节的RTP Header后,有两个字节的分包头,分别为:FU Indicator和FU Header。

        FU Indicator占用一个字节,由以下部分组成。

        F (1 bit): 禁止位,与NALU Header的F位一致。

        NRI (2 bits): 重要程度,与NALU Header的NRI一致。

        Type (5 bits): 分包类型,二进制固定为11100(对应十进制的28),表示FU-A类型。

        FU Header占用一个字节,由以下部分组成。

        S (1 bit): 分包起始位。如果该FU是原始NALU的第一个片段,S设为1。否则,设为0。

        E (1 bit): 分包结束位。如果该FU是原始NALU的最后一个片段,E设为1。否则,设为0。

        R (1 bit): 保留位,必须设为0。

        Type (5 bits): 原始NALU类型,与NALU Header的Type一致,用于在重组时恢复原始NALU Header。

        3、STAP-A聚合

        对于多个小尺寸NALU,如果它们具有相近的解码时间戳,且合并后总尺寸仍小于MTU,可以使用Single-Time Aggregation Packet A方式将多个NALU合并到一个RTP包中。此时,RTP包的结构如下。

+-----------------------------+
| RTP Header (12 Byte)        |
| STAP-A Header (1 Byte)      |
| NALU Payload1 Size (2 Byte) |
| NALU Payload1               |
| NALU Payload2 Size (2 Byte) |
| NALU Payload2               |
| ...                         |
+-----------------------------+

        STAP-A Header紧跟在RTP Header之后,占用一个字节(与NALU Header结构类似),用于标识这是一个STAP-A包,其Type值固定为24。在每个聚合的NALU前,会有一个长度字段(通常为2个字节),表明后续NALU数据的长度。所有聚合在STAP-A包中的NALU都共享相同的时间戳,这是STAP-A包的一个重要特征。

        注意:无论采用上面的哪种封装方法,NALU Data或NALU Payload中都不包括Annex B格式中的起始码(比如:0x000001或0x00000001),因为RTP包已经提供了足够的信息来标识NALU的边界。

FU-A分包及重组

        在服务端,FU-A分包的大致步骤如下。

        1、原NALU切割: 大型NALU被拆分成多个连续的片段。切割位置通常选择在NALU内部的编码块边界,以避免破坏编码结构。

        2、片段标识: 每个片段(FU)在RTP Payload中添加一个FU Header,用于标识该片段属于哪个原始NALU,以及其在原始NALU中的位置。

        3、独立传输: 每个FU作为一个独立的RTP包发送,每个RTP包的Payload仅包含一个FU。

        客户端接收到FU-A分包的RTP包后,根据RTP Header解析出Payload Type,确认为H264 FU-A数据后,按照以下步骤处理。

        1、FU分包头解析: 提取FU Indicator和FU Header中的信息。

        2、片段重组: 将收到的FU片段按照RTP包的Sequence Number顺序重新组合,将所有片段的Fragmented NALU Data拼接在一起。

        3、NALU还原: 在重组后的NALU数据前添加原始NALU Header(根据FU分包头中的信息恢复),形成完整的NALU结构。

        4、解码处理: 将还原后的完整NALU提交给H264解码器进行解码。

        使用FU-A封装方法进行分包和重组时,有以下几点需要特别注意。

        1、顺序传输: FU-A分包的RTP包必须严格按照分包顺序发送和接收,以确保正确重组。

        2、丢包处理: 如果中间某个FU片段丢失,可能导致原始NALU无法正确重组。接收端可以根据RTP包的序列号和确认机制检测丢包,并尝试通过重传请求(比如:RTCP的NACK)恢复丢失片段。

        3、时间戳同步: 所有FU片段共享同一个解码时间戳,确保解码时的正确同步。

总结

        总的来说,RTP包通过其标准化的头部和灵活的负载格式,能够有效地封装和传输H264视频流。在实际应用中,流媒体服务器会根据具体情况动态选择合适的分包策略。有时也会结合使用若干种不同的分包方法,以适应不同类型的NALU和网络条件。此外,还有一些更复杂的分包方法,比如:STAP-B(跨时间聚合包)、MTAP16(多时间聚合包,16个时间单元)等,但它们在H264视频流传输中并不常用。

05-03 11:54