网络抽象层单元类型 (NALU):NALU头由一个字节组成,它的语法如下: +---------------+ |0|1|2|3|4|5|6|7| +-+-+-+-+-+-+-+-+ |F|NRI| Type | +---------------+F: 1个比特. forbidden_zero_bit. 在 H.264 规范中规定了这一位必须为 0.NRI: 2个比特. nal_ref_idc. 取00~11,似乎指示这个NALU的重要性,如00的NALU解码器可以丢弃它而不影响图像的回放. Type: 5个比特. nal_unit_type. 这个NALU单元的类型.简述如下: 0 没有定义 1-23 NAL单元 单个 NAL 单元包 24 STAP-A 单一时间的组合包 25 STAP-B 单一时间的组合包 26 MTAP16 多个时间的组合包 27 MTAP24 多个时间的组合包 28 FU-A 分片的单元 29 FU-B 分片的单元 30-31 没有定义h264仅用1-23,24以后的用在RTP H264负载类型头中, 需要特别指出的是,NRI 值为 7 和 8 的NALU 分别为序列参数集(sps)和图像参数集(pps)。参数集是一组很少改变的,为大量VCL NALU 提供解码信息的数据。其中序列参数集作用于一系列连续的编码图像,而图像参数集作用于编码视频序列中一个或多个独立的图像。如果解码器没能正确接收到这两个参数集,那么其他NALU 也是无法解码的。因此它们一般在发送其它 NALU 之前发送,并且使用不同的信道或者更加可靠的传输协议(如TCP)进行传输,也可以重复传输。3.2 适用于 H.264 视频的传输机制 前面分别讨论了RTP 协议及H.264基本流的结构,那么如何使用RTP协议来传输H.264视频呢?一个有效的办法就是从H.264视频中剥离出每个NALU,在每个NALU前添加相应的RTP包头,然后将包含RTP 包头和NALU 的数据包发送出去。下面就从RTP包头和NALU两方面分别阐述。 完整的 RTP 固定包头的格式在前面图 1 中已经指出,根据RFC3984[3],这里详细给出各个位的具体设置。 V:版本号,2 位。根据RFC3984,目前使用的RTP 版本号应设为0x10。 P:填充位,1 位。当前不使用特殊的加密算法,因此该位设为 0。 X:扩展位,1 位。当前固定头后面不跟随头扩展,因此该位也为 0。 CC:CSRC 计数,4 位。表示跟在 RTP 固定包头后面CSRC 的数目,对于本文所要实现的基本的流媒体服务器来说,没有用到混合器,该位也设为 0x0。 M:标示位,1 位。如果当前 NALU为一个接入单元最后的那个NALU,那么将M位置 1;或者当前RTP 数据包为一个NALU 的最后的那个分片时(NALU 的分片在后面讲述),M位置 1。其余情况下M 位保持为 0。 PT:载荷类型,7 位。对于H.264 视频格式,当前并没有规定一个默认的PT 值。因此选用大于 95 的值可以。此处设为0x60(十进制96)。 SQ:序号,16 位。序号的起始值为随机值,此处设为 0,每发送一个RTP 数据包,序号值加 1。 TS:时间戳,32 位。同序号一样,时间戳的起始值也为随机值,此处设为0。根据RFC3984, 与时间戳相应的时钟频率必须为90000HZ。 SSRC:同步源标示,32 位。SSRC应该被随机生成,以使在同一个RTP会话期中没有任何两个同步源具有相同的SSRC 识别符。此处仅有一个同步源,因此将其设为0x12345678。 对于每一个NALU,根据其包含的数据量的不同,其大小也有差异。在IP网络中,当要传输的IP 报文大小超过最大传输单元MTU(Maximum Transmission Unit )时就会产生IP分片情况。在以太网环境中可传输的最大 IP 报文(MTU)的大小为 1500 字节。除去IP头20字节,UDP头8字节,RTP头12字节,RTP的最大有效负载为1460字节。如果发送的IP数据包大于MTU,数据包就会被拆开来传送,这样就会产生很多数据包碎片,增加丢包率,降低网络速度。对于视频传输而言,若RTP 包大于MTU 而由底层协议任意拆包,可能会导致接收端播放器的延时播放甚至无法正常播放。因此对于大于MTU 的NALU 单元,必须进行拆包处理。RFC3984 给出了3 中不同的RTP 打包方案:(1)Single NALU Packet(单一 NAL 单元模式): 即一个 RTP 包仅由一个完整的 NALU 组成在一个RTP 包中只封装一个NALU,在本文中对于小于 1400字节的NALU 便采用这种打包方案。 这种情况下 RTP NAL 头类型字段和原始的 H.264的 NALU 头类型字段是一样的. (2)Aggregation Packet(组合封包模式):在一个RTP 包中封装多个NALU,对于较小的NALU 可以采用这种打包方案,从而提高传输效率。 即可能是由多个 NAL 单元组成一个 RTP 包。 分别有4种组合方式: STAP-A, STAP-B, MTAP16, MTAP24. 那么这里的类型值分别是 24, 25, 26 以及 27. (3)Fragmentation Unit(分片封包模式FU):一个NALU 封装在多个RTP包中,在本文中,对于大于1400字节的NALU 便采用这种方案进行拆包处理。 用于把一个 NALU 单元封装成多个 RTP 包。 存在两种类型 FU-A 和 FU-B. 类型值分别是 28 和 29. FU-A的RTP封包格式如下: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | FU indicator | FU header | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | | FU payload | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | :...OPTIONAL RTP padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ FU-B的RTP封包格式如下: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | FU indicator | FU header | DON | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| | | | FU payload | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | :...OPTIONAL RTP padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 这里解释下DON(解码顺序号): 在交错打包方式, NAL单元的传输顺序允许和NAL单元的解码顺序不同。解码顺序号(DON)是荷载结构中的一个域 或一个获得变量指示NAL单元的解码顺序。FU指示字节(FU indicator)有以下格式: +---------------+ |0|1|2|3|4|5|6|7| +-+-+-+-+-+-+-+-+ |F|NRI| Type | +---------------+FU头的格式(FU header)如下: +---------------+ |0|1|2|3|4|5|6|7| +-+-+-+-+-+-+-+-+ |S|E|R| Type | +---------------+FU头的格式说明:S: 1 bit 当设置成1,开始位指示分片NAL单元的开始。当跟随的FU荷载不是分片NAL单元荷载的开始,开始位设为0。 E: 1 bit 当设置成1, 结束位指示分片NAL单元的结束,即, 荷载的最后字节也是分片NAL单元的最后一个字节。当跟随的 FU荷载不是分片NAL单元的最后分片,结束位设置为0。 R: 1 bit 保留位必须设置为0,接收者必须忽略该位。 Type: 5 bits NAL单元荷载类型定义1~12.RTP封包处理: 对于一个原始的 H.264 NALU 单元常由 [Start Code] [NALU Header] [NALU Payload] 三部分组成, 其中 Start Code 用于标示这是一个NALU 单元的开始, 必须是 "00 00 00 01" 或 "00 00 01", NALU 头仅一个字节, 其后都是 NALU 单元内容. 打包时去除 "00 00 01" 或 "00 00 00 01" 的开始码, 把其他数据封包的 RTP 包即可.rtp h264注意点(FU-A分包方式说明) 关于时间戳,需要注意的是h264的采样率为90000HZ,因此时间戳的单位为1(秒)/90000,因此如果当前视频帧率为25fps,那时间戳间隔或者说增量应该为3600,如果帧率为30fps,则增量为3000,以此类推。G711:20ms间隔:其采样率是8000/秒;帧速率是1/0.02=50;采样率/帧速率=采样率×帧间隔时长=每帧携带的采样点=8000/(1/0.02)=8000×0.02=160;初始timestamp0=100;Ts1=260;ts2=420;ts3=580 ts4=740…0 20ms 40ms 60ms …时间轴例如2:H264:帧速率25fps:采样率是90000次/秒;采样率/帧速率=采样率×帧间隔时长=每帧携带的采样点=90000/25=90000×0.04=3600;初始timestamp0=10;Ts1=3610;ts2=7210;ts3=10810 ts4=14410…0 40ms 80ms 1200ms …时间轴关于h264拆包,按照FU-A方式说明:1)第一个FU-A包的FU indicator:F应该为当前NALU头的F,而NRI应该为当前NALU头的NRI,Type则等于28,表明它是FU-A包。FU header生成方法:S = 1,E = 0,R = 0,Type则等于NALU头中的Type。2)后续的N个FU-A包的FU indicator和第一个是完全一样的,如果不是最后一个包,则FU header应该为:S = 0,E = 0,R = 0,Type等于NALU头中的Type。3)最后一个FU-A包FU header应该为:S = 0,E = 1,R = 0,Type等于NALU头中的Type。因此总结就是:同一个NALU分包厚的FU indicator头是完全一致的,FU header只有S以及E位有区别,分别标记开始和结束,它们的RTP分包的序列号应该是依次递增的,并且它们的时间戳必须一致,而负载数据为NALU包去掉1个字节的NALU头后对剩余数据的拆分,这点很关键,你可以认为NALU头被拆分成了FU indicator和FU header,所以不再需要1字节的NALU头了。关于SPS以及PPS,配置帧的传输我采用了先发SPS,再发送PPS,并使用同样的时间戳,或者按照正常时间戳增量再或者组包发送的形式处理貌似都可以,看播放器怎么解码了,另外提一下,如果我们使用vlc进行播放的话,可以在sdp文件中设置SPS以及PPS,这样就可以不用发送它们了。使用VLC播放时,sdp文件中的分包模式选项:packetization-mode=1,否则有问题。另外sdp里面设置的编码type必须和rtp包中的参考资料: 1、H264 Rtp 封包原理 2、RF 3984 (For H264)