Skip to content

Instantly share code, notes, and snippets.

@quink-black
Last active November 15, 2021 03:55
Show Gist options
  • Save quink-black/6828ebf722f6a4d35fbc5c5bc2dbaf42 to your computer and use it in GitHub Desktop.
Save quink-black/6828ebf722f6a4d35fbc5c5bc2dbaf42 to your computer and use it in GitHub Desktop.
H.264参数集处理

H.264参数集处理

[toc]

摘要

介绍H.264参数集与几种字节流格式的关系,以及in-band/out-of-band传递参数集的方式与容器格式、传输协议的关系,描述MP4格式、FLV格式处理参数集的一些兼容性问题。

H.264参数集

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参数集与字节流格式

Annex-B

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格式,不论是参数集还是宏块数据格式都一样。

AVCC

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

RTP

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                                     -

in-band、out-of-band和容器格式、传输协议

前面说过,从编码格式设计角度来说,参数集是不经常变动的信息,宏块数据是持续变化的信息,两者解耦:

  • 降低数据冗余;
  • 可以使用out-of-band带外传输提高参数集传输的可靠性(宏块数据走不可靠传输)

下面描述参数集in-band、out-of-band与H.264字节流格式、容器封装格式的关系。

Annex-B:in-band

Annex-B格式字节流通常使用in-band方式传输参数集,并且在每个IDR帧前面带上可能重复的参数集。

Annex-B格式面向有损传输场景,start code是一种resync机制,码流中间出现数据丢失,可以通过搜索start code找到下个NALU的开始。Annex-B格式应对数据丢失的三个机制:

  1. start code resync机制
  2. 参数集in-band
  3. 参数集重复出现

TS(transport stream)容器格式使用Annex-B格式字节流。另外,H.264 ES流(裸流)一般使用Annex-B格式,FFplay、VLC等播放器支持H.264 ES流播放。

因为参数集反复出现,Annex-B/TS格式天然支持参数集变更,支持分辨率变化如横竖屏切换等。但是从播放器角度来说,要支持分辨率变化,需要解封装、解码器、渲染甚至UI界面一起配合实现。

AVCC:in-band和out-of-band

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面向可靠流式传输,不适合作为本地存储播放格式。FLV + AVCC用in-band方式传递参数集,且允许更新参数集。

  1. FLV用宏块数据的NALU_size + NALU_data格式更新参数集不符合标准

    前面说过,MP4标准H.264格式包括两部分:宏块数据格式和参数集格式。AVCC格式的参数集不是简单的NALU_size + NALU_data的格式。按照FLV标准,H.264参数集应该按照AVCDecoderConfigurationRecord格式来传递。但实际上,有很多FLV文件开头用AVCDecoderConfigurationRecord传递参数集,中间又用NALU_size + NALU_data的格式更新参数集。

  2. 用标准的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的兼容性很差:

    1. FFmpeg内部,H.264软件解码器、FLV muxer等支持,mp4 muxer不支持,所以把FLV转封装成MP4时,这种通过AVCDecoderConfigurationRecord更新的参数集会丢失,转出来的MP4后半段可能无法播放。而通过NALU_size + NALU_data的格式更新参数集转封装出来的MP4,虽然不标准,能够播放的概率更大一些。
    2. VLC用FFmpeg做FLV demuxer,但已发布的VLC 3.0版本没处理AV_PKT_DATA_NEW_EXTRADATA,可能导致解码出错。开发中的VLC 4.0修复了这一问题,见 demux: avformat: handle AV_PKT_DATA_NEW_EXTRADATA
    3. 业务代码,往往忽略对AV_PKT_DATA_NEW_EXTRADATA side data的处理

建议:

  1. 参数集变更需谨慎,横竖屏切换之类操作必须前后端全链路打通
  2. 处理参数集变更的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

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment