H.264 (AVC) 和 H.265 (HEVC) 是两种常见的视频编码标准,它们都有不同的封装格式,即Annex B 和 AVCC(H264)/HVCC(H265) 封装格式。

1、Annex B 封装格式

        Annex B 是 H264/H265中适合流式传输的一种封装格式,在这种封装格式下,视频码流被分割成称为 NALU的小单元,每个 NALU 都以特定的起始码(start code)开头。Annex B 封装格式的特点包括:

  • 起始码:起始码指示了每个 NALU 的开始,是 3 或 4 个字节的特定模式(0x00 00 01/0x00 00 00 01),用于标识 NALU 的开始位置。
  •  容易进行流分割:由于起始码的存在,可以轻松地对码流进行分割和重新组合。

        视频编码参数vps(H265)、sps(H264/H265)、pps(H264/H265)均以Annex B即用起始码分隔NALU的方式包含在视频流中,H264/H265裸流文件就是以Annex B的方式进行存储的。
        Annex B格式如下:

| 起始码 | vps(H265) | 起始码 | sps(H264/H265) | 起始码 | pps(H264/H265) | 起始码 | NALU(H264/H265) | ... | 起始码 | vps(H265) | 起始码 | sps(H264/H265) | 起始码 | pps(H264/H265) | 起始码 | NALU(H264/H265) | ... |

        Annex B中视频编码参数vps、sps、pps和视频码流混合到一起,并且每个GOP的开始都会包含,所以可以从任意位置切入进去,只要找到vps(H265)、sps(H264/H265)、pps(H264/H265)即可开始视频解码。因此Annex B更适合流式传输场景。使用Annex B的封装格式有:TS、PS等。

2、AVCC/HVCC 封装格式

2.1、AVCC

        AVCC是H264的第二种封装格式,主要应用在视频存储中,AVCC不使用起始码分隔NALU,而是在NALU前面加上一个前缀,前缀使用大端模式,一般为4个字节表示当前NALU的长度。sps、pps也不会和视频码流混合到一起,而是把sps、pps按照一定的格式保存下来,通常称为extradata或者sequence header,并且在文件头部包含extradata/sequence header,视频码流就不会再包含sps、pps了。
        格式如下:

| extradata/sequence header | 前缀 | NALU | 前缀 | NALU | ... |

        由于sps、pps不会在每个GOP前重复出现,也因此节约了存储空间,更适合存储,解码的时候先从extradata/sequence header中解析出sps、pps,然后读取NALU解码出视频帧。使用AVCC的封装格式有:MP4、FLV等。
        AVCC的extradata/sequence header定义如下:

H264/H265的两种格式Annex B、AVCC(H264)/HVCC(H265)-LMLPHP

        NALULengthSizeMinusOne表示前缀的字节数,一般为4;number of SPS NALUs usually表示sps的数量,之后2个字节表示sps长度,然后接sps NALU data,如果number of SPS不等于1,后面继续接sps长度、sps NALU,以此类推,pps也同理。

        AVCC的extradata/sequence header封装代码为:

void Muxer::H264WriteExtra(unsigned char *extra_data, int &extra_data_size)
{

    unsigned char *extra_data_start = extra_data;
    extra_data[0] = 0x01;
    extra_data[1] = sps_buf_[0][1];
    extra_data[2] = sps_buf_[0][2];
    extra_data[3] = sps_buf_[0][3];
    extra_data += 4;
    *extra_data++ = 0xff;
    // sps
    *extra_data++ = 0xe0 | (sps_number_ & 0x1f); // sps 个数
    for (int i = 0; i < sps_number_; i++) {
        // 两个字节表示sps
        extra_data[0] = (sps_len_[i]) >> 8;
        extra_data[1] = sps_len_[i];
        extra_data += 2;
        memcpy(extra_data, sps_buf_[i], sps_len_[i]);
        extra_data += sps_len_[i];
    }

    // pps

    *extra_data++ = pps_number_; // 1个sps
    for (int i = 0; i < pps_number_; i++) {
        extra_data[0] = (pps_len_[i]) >> 8;
        extra_data[1] = pps_len_[i];
        extra_data += 2;
        memcpy(extra_data, pps_buf_[i], pps_len_[i]);
        extra_data += pps_len_[i];
    }

    extra_data_size = extra_data - extra_data_start;
}

        sps_number_为sps总个数、sps_len_[]存储了每个sps NALU的长度、sps_buf_[]保存了每个sps NALU data,pps同理。

2.2、HVCC

        HVCC是H265的第二种封装格式,除了extradata/sequence header定义,其他和H264的AVCC格式都是一样的。
        HVCC的extradata/sequence header定义如下:

H264/H265的两种格式Annex B、AVCC(H264)/HVCC(H265)-LMLPHP

        numOfArrays表示数组的个数,一般为0x03,表示包含VPS、SPS、PPS,数组每个元素的数据格式如下:

H264/H265的两种格式Annex B、AVCC(H264)/HVCC(H265)-LMLPHP

        NAL unit type用以区分VPS、SPS、PPS。

        HVCC的extradata/sequence header的封装代码如下:

void Muxer::H265WriteExtra(unsigned char *extra_data, int &extra_data_size)
{
    int i = 0;
    unsigned char *buffer = extra_data;
    buffer[i++] = 0x01;

    // general_profile_idc 8bit
    buffer[i++] = 0x00;
    // general_profile_compatibility_flags 32 bit
    buffer[i++] = 0x00;
    buffer[i++] = 0x00;
    buffer[i++] = 0x00;
    buffer[i++] = 0x00;

    // 48 bit NUll nothing deal in rtmp
    buffer[i++] = 0x00;
    buffer[i++] = 0x00;
    buffer[i++] = 0x00;
    buffer[i++] = 0x00;
    buffer[i++] = 0x00;
    buffer[i++] = 0x00;

    // general_level_idc
    buffer[i++] = 0x00;

    // 48 bit NUll nothing deal in rtmp
    buffer[i++] = 0xf0;
    buffer[i++] = 0x00;
    buffer[i++] = 0xfc;
    buffer[i++] = 0xfc;
    buffer[i++] = 0xf8;
    buffer[i++] = 0xf8;

    // bit(16) avgFrameRate;
    buffer[i++] = 0x00;
    buffer[i++] = 0x00;

    /* bit(2) constantFrameRate; */
    /* bit(3) numTemporalLayers; */
    /* bit(1) temporalIdNested; */
    buffer[i++] = 0x03;

    /* unsigned int(8) numOfArrays; 03 */
    buffer[i++] = 3;

    // printf("HEVCDecoderConfigurationRecord data = %s\n", buffer);
    buffer[i++] = 0xa0; // vps 32
    buffer[i++] = (vps_number_ >> 8) & 0xff;
    buffer[i++] = vps_number_ & 0xff;
    for (int j = 0; j < vps_number_; j++) {
        buffer[i++] = (vps_len_[j] >> 8) & 0xff;
        buffer[i++] = (vps_len_[j]) & 0xff;
        memcpy(&buffer[i], vps_buf_[j], vps_len_[j]);
        i += vps_len_[j];
    }

    // sps
    buffer[i++] = 0xa1; // sps 33
    buffer[i++] = (sps_number_ >> 8) & 0xff;
    buffer[i++] = sps_number_ & 0xff;
    for (int j = 0; j < sps_number_; j++) {
        buffer[i++] = (sps_len_[j] >> 8) & 0xff;
        buffer[i++] = sps_len_[j] & 0xff;
        memcpy(&buffer[i], sps_buf_[j], sps_len_[j]);
        i += sps_len_[j];
    }

    // pps
    buffer[i++] = 0xa2; // pps 34
    buffer[i++] = (pps_number_ >> 8) & 0xff;
    buffer[i++] = pps_number_ & 0xff;
    for (int j = 0; j < pps_number_; j++) {
        buffer[i++] = (pps_len_[j] >> 8) & 0xff;
        buffer[i++] = pps_len_[j] & 0xff;
        memcpy(&buffer[i], pps_buf_[j], pps_len_[j]);
        i += pps_len_[j];
    }
    extra_data_size = i;
    return;
}

3、项目地址:

        完整代码:https://github.com/BreakingY/FFmpeg-Media-Codec-Pipeline里面的MediaMuxer/MediaMuxer.cpp,是用ffmpeg实现的MP4封装C++类。关于FFmpeg-Media-Codec-Pipeline:用ffmpeg实现音视频(H264/H265/AAC)封装、解封装、编解码pipeline,支持NVIDIA硬编解码。

03-26 22:42