[toc]
介绍H.264参数集与几种字节流格式的关系,以及in-band/out-of-band传递参数集的方式与容器格式、传输协议的关系,描述MP4格式、FLV格式处理参数集的一些兼容性问题。
H.264参数集包括:
- SPS(sequence parameter set)A syntax structure containing syntax elements that apply to zero or more entire coded video sequences as determined by the content of a seq_parameter_set_id syntax element found in the picture parameter set referred to by the pic_parameter_set_id syntax element found in each slice header.
- PPS(picture parameter set)A syntax structure containing syntax elements that apply to zero or more entire coded pictures as determined by the pic_parameter_set_id syntax element found in each slice header.
SPS包含视频的profile、level、bit depth、宽高、最大参考帧数、切边信息、VUI信息等。VUI信息可能包含色彩空间信息、帧率信息、解码重排序信息等。参数集信息至关重要,丢失参数集不能正常解码。从编码格式设计角度来说,参数集是不经常变动的信息,宏块数据是持续变化的信息,两者解耦:
- 降低数据冗余;
- 可以使用out-of-band带外传输提高参数集传输的可靠性(宏块数据走不可靠传输)。
H.264标准(ISO-14496-10)不限定H.264的字节流格式,在标准的附录章节B给出了一种格式,通常称这种格式为Annex-B。基本结构如下:
byte_stream_nal_unit( NumBytesInNALunit ) { | C | Descriptor |
---|---|---|
while( next_bits( 24 ) != 0x000001 && next_bits( 32 ) != 0x00000001 ) | ||
leading_zero_8bits /* equal to 0x00 */ | f(8) | |
if( next_bits( 24 ) != 0x000001 ) | ||
zero_byte /* equal to 0x00 */ | f(8) | |
start_code_prefix_one_3bytes /* equal to 0x000001 */ | f(24) | |
nal_unit( NumBytesInNALunit ) | ||
while( more_data_in_byte_stream( ) && next_bits( 24 ) != 0x000001 && next_bits( 32 ) != 0x00000001 ) | ||
trailing_zero_8bits /* equal to 0x00 */ | f(8) | |
} |
Annex-B的典型特征是用start code 0x00 00 00 01或者0x00 00 01作为一个NALU的开始。Annex-B格式,不论是参数集还是宏块数据格式都一样。
MP4的附带标准(ISO-14496-15)定义了另一种H.264字节流格式。MP4标准H.264格式包括两部分:宏块数据格式和参数集格式。
先说宏块数据格式。H.264中一张图的数据称为一个access unit,MP4标准称为一个sample。典型特征:
- 每个NALU前面带着NALU的长度,没有start code
- 属于同一个access unit的多个slice必须存放在同一个sample中
再说MP4 H.264格式的参数集。MP4里面称为decoder configuration record,属于AVCConfigurationBox的一部分,而AVCConfigurationBox被VisualSampleEntry所包含。
class AVCConfigurationBox extends Box(‘avcC’) {
AVCDecoderConfigurationRecord() AVCConfig;
}
aligned(8) class AVCDecoderConfigurationRecord {
unsigned int(8) configurationVersion = 1;
unsigned int(8) AVCProfileIndication;
unsigned int(8) profile_compatibility;
unsigned int(8) AVCLevelIndication;
bit(6) reserved = ‘111111’b;
unsigned int(2) lengthSizeMinusOne;
bit(3) reserved = ‘111’b;
unsigned int(5) numOfSequenceParameterSets;
for (i=0; i< numOfSequenceParameterSets; i++) {
unsigned int(16) sequenceParameterSetLength ;
bit(8*sequenceParameterSetLength) sequenceParameterSetNALUnit;
}
unsigned int(8) numOfPictureParameterSets;
for (i=0; i< numOfPictureParameterSets; i++) {
unsigned int(16) pictureParameterSetLength;
bit(8*pictureParameterSetLength) pictureParameterSetNALUnit;
}
if( profile_idc == 100 || profile_idc == 110 ||
profile_idc == 122 || profile_idc == 144 )
{
bit(6) reserved = ‘111111’b;
unsigned int(2) chroma_format;
bit(5) reserved = ‘11111’b;
unsigned int(3) bit_depth_luma_minus8;
bit(5) reserved = ‘11111’b;
unsigned int(3) bit_depth_chroma_minus8;
unsigned int(8) numOfSequenceParameterSetExt;
for (i=0; i< numOfSequenceParameterSetExt; i++) {
unsigned int(16) sequenceParameterSetExtLength;
bit(8*sequenceParameterSetExtLength) sequenceParameterSetExtNALUnit;
}
}
}
不同的编码格式,甚至同一种编码格式,VisualSampleEntry有不同的tag名称。H.264 VisualSampleEntry有4种tag:avc1、avc2、avc3、avc4. 下面是一个avc1的例子:
204EF215 Video (169 bytes)
204EF215 Header (8 bytes)
204EF215 Size: 169 (0x000000A9)
204EF219 Name: avc1
204EF21D Reserved: 0 (0x0000000000000000)
204EF223 Data reference index: 1 (0x0001)
204EF225 Version: 0 (0x0000)
204EF227 Revision level: 0 (0x0000)
204EF229 Vendor:
204EF22D Temporal quality: 512 (0x00000200)
204EF231 Spatial quality: 512 (0x00000200)
204EF235 Width: 1080 (0x0438)
204EF237 Height: 1920 (0x0780)
204EF239 Horizontal resolution: 4718592 (0x00480000)
204EF23D Vertical resolution: 4718592 (0x00480000)
204EF241 Data size: 0 (0x00000000)
204EF245 Frame count: 1 (0x0001)
204EF247 Compressor name size: 5 (0x05)
204EF248 Compressor name: H.264
204EF24D Padding: (26 bytes)
204EF267 Depth: 24 (0x0018)
204EF269 Color table ID: 65535 (0xFFFF)
204EF26B AVC decode (45 bytes)
204EF26B Header (8 bytes)
204EF26B Size: 45 (0x0000002D)
204EF26F Name: avcC
204EF273 Version: 1 (0x01)
204EF274 Specific (36 bytes)
204EF274 AVCProfileIndication: 100 (0x64)
204EF275 profile_compatibility: 0 (0x00)
204EF276 AVCLevelIndication: 41 (0x29)
204EF277 reserved: 63 (0x3F) - (6 bits)
204EF277 lengthSizeMinusOne: 3 (0x3) - (2 bits)
204EF278 reserved: 7 (0x7) - (3 bits)
204EF278 numOfSequenceParameterSets: 1 (0x01) - (5 bits)
204EF279 seq_parameter_set (24 bytes)
204EF279 sequenceParameterSetLength: 22 (0x0016)
204EF27B nal_ref_idc: 1 (0x1) - (2 bits)
204EF27B nal_unit_type: 7 (0x7) - (5 bits)
204EF27C profile_idc: 100 (0x64)
204EF27D constraints (1 bytes)
204EF27D constraint_set0_flag: No
RFC 6184定义了H.264在RTP传输协议中的格式,这种格式既没有Annex-B格式的start code,也没有MP4的前缀NALU长度信息。RTP H.264占用了H.264标准的几个自定义NALU type,作为合并包或拆分包的标识。如果业务中有使用自定义NALU type,以RTP传输H.264时(如WebRTC)可能出现定义冲突问题。
NAL Unit Packet Packet Type Name Section
Type Type
-------------------------------------------------------------
0 reserved -
1-23 NAL unit Single NAL unit packet 5.6
24 STAP-A Single-time aggregation packet 5.7.1
25 STAP-B Single-time aggregation packet 5.7.1
26 MTAP16 Multi-time aggregation packet 5.7.2
27 MTAP24 Multi-time aggregation packet 5.7.2
28 FU-A Fragmentation unit 5.8
29 FU-B Fragmentation unit 5.8
30-31 reserved -
前面说过,从编码格式设计角度来说,参数集是不经常变动的信息,宏块数据是持续变化的信息,两者解耦:
- 降低数据冗余;
- 可以使用out-of-band带外传输提高参数集传输的可靠性(宏块数据走不可靠传输)
下面描述参数集in-band、out-of-band与H.264字节流格式、容器封装格式的关系。
Annex-B格式字节流通常使用in-band方式传输参数集,并且在每个IDR帧前面带上可能重复的参数集。
Annex-B格式面向有损传输场景,start code是一种resync机制,码流中间出现数据丢失,可以通过搜索start code找到下个NALU的开始。Annex-B格式应对数据丢失的三个机制:
- start code resync机制
- 参数集in-band
- 参数集重复出现
TS(transport stream)容器格式使用Annex-B格式字节流。另外,H.264 ES流(裸流)一般使用Annex-B格式,FFplay、VLC等播放器支持H.264 ES流播放。
因为参数集反复出现,Annex-B/TS格式天然支持参数集变更,支持分辨率变化如横竖屏切换等。但是从播放器角度来说,要支持分辨率变化,需要解封装、解码器、渲染甚至UI界面一起配合实现。
AVCC是面向无损场景,一旦NALU中间有数据丢失,NALU前缀长度信息就失效了,不断变化的NALU长度信息不能作为resync机制。
AVCC格式既支持in-band也支持out-of-band传递参数集。
MP4标准定义了4种H.264 tag:
- MP4 avc1和avc2必须使用out-of-band方式传递参数集,即参数集放在moov box,宏块数据在mdat的sample中
- avc3和avc4允许使用in-band方式传递参数集,参数集可以和sample放一块
一些不标准的MP4文件,VisualSampleEntry是avc1,却在mdat sample中带有参数集。用FFmpeg做转封装,如果输入是TS格式,中间有参数集变化,比如横竖屏分辨率切换,会生成in-band参数集的不标准MP4文件。这类文件,根据解码器不同,也许能正常连续播放,但在seek操作时不能更新参数集,seek后会出现解码失败的情况。
MP4除了in-band更新参数集有兼容性问题,还有混用AVCC格式和Annex-B的案例,如华为MediaCodec编码视频iOS播放绿屏问题。
除了MP4,MKV和FLV也使用AVCC格式。
FLV面向可靠流式传输,不适合作为本地存储播放格式。FLV + AVCC用in-band方式传递参数集,且允许更新参数集。
-
FLV用宏块数据的NALU_size + NALU_data格式更新参数集不符合标准
前面说过,MP4标准H.264格式包括两部分:宏块数据格式和参数集格式。AVCC格式的参数集不是简单的NALU_size + NALU_data的格式。按照FLV标准,H.264参数集应该按照AVCDecoderConfigurationRecord格式来传递。但实际上,有很多FLV文件开头用AVCDecoderConfigurationRecord传递参数集,中间又用NALU_size + NALU_data的格式更新参数集。
-
用标准的AVCDecoderConfigurationRecord格式更新参数集兼容性差
这个问题来自FFmpeg FLV demuxer,如果自研FLV demux和播放器此问题可以忽略。对于FLV开头的AVCDecoderConfigurationRecord,FFmpeg demux时用AVCodecParameters extradata带出来。如果FLV中间出现新的AVCDecoderConfigurationRecord,则给AVPacket加个side data,用AV_PKT_DATA_NEW_EXTRADATA带出来。但是AV_PKT_DATA_NEW_EXTRADATA的兼容性很差:
- FFmpeg内部,H.264软件解码器、FLV muxer等支持,mp4 muxer不支持,所以把FLV转封装成MP4时,这种通过AVCDecoderConfigurationRecord更新的参数集会丢失,转出来的MP4后半段可能无法播放。而通过NALU_size + NALU_data的格式更新参数集转封装出来的MP4,虽然不标准,能够播放的概率更大一些。
- VLC用FFmpeg做FLV demuxer,但已发布的VLC 3.0版本没处理AV_PKT_DATA_NEW_EXTRADATA,可能导致解码出错。开发中的VLC 4.0修复了这一问题,见 demux: avformat: handle AV_PKT_DATA_NEW_EXTRADATA
- 业务代码,往往忽略对AV_PKT_DATA_NEW_EXTRADATA side data的处理
建议:
- 参数集变更需谨慎,横竖屏切换之类操作必须前后端全链路打通
- 处理参数集变更的FLV,同时用参数集格式AVCDecoderConfigurationRecord和宏块数据格式NALU_size + NALU_data发两遍参数集
除了参数集格式问题,FFmpeg FLV muxer还涉及Annex-B与AVCC格式转换问题。根据start code来判断是AVCC还是Annex-B,需要累积一定的数据量做推断,主要难点在于start code可以是两个0一个1(0x00 00 01),当成AVCC看就是0x00 00 01 xx |NALU,两者混淆不好区分。FFmpeg FLV muxer是根据extradata的格式来判断送进来的AVPacket是Annex-B还是AVCC格式。正常情况,extradata的格式和AVPacket data格式是一致的。如果业务代码导致extradata和AVPacket用了两种格式,或者AVPacket AV_PKT_DATA_NEW_EXTRADATA和AVPacket data的格式不一致,则合成的FLV无法正常播放。
RTP协议既可以用in-band也可以用out-of-band传递参数集。in-band带在RTP包里,out-of-band往往用SDP格式走其他可靠传输通道,如RTSP交互过程用TCP协议发送SDP文件。SDP中的参数集格式示例:
m=video 49170 RTP/AVP 98
a=rtpmap:98 H264/90000
a=fmtp:98 profile-level-id=42A01E;
packetization-mode=1;
sprop-parameter-sets=<parameter sets data>