H.264 NALU分隔Annex B和avcC

本文详细介绍了H264编码中两种常见的分隔格式——AnnexB和avcC,包括它们的应用场景和数据结构。AnnexB主要用于视频会议和文件存储,avcC则常见于mp4、flv和直播rtmp。文章还展示了处理编码数据以避免分隔符冲突的方法,以及在解码前的反向处理过程。同时,列举了不同编解码器如libopenh264和MediaCodec的输入输出分隔符类型。
摘要由CSDN通过智能技术生成

分隔格式

H.264常用的分隔方式有Annex B和avcC

Annex B

这种分隔符通常用于视频会议还有文件存储例如TS等
用VLC打开avcC格式的视频文件,编码信息中显示H264 - MPEG-4 AVC(part 10)(h264)
Annex B的格式如下,start code有可能是{0 0 0 1}或者{0 0 1},{0 0 0 1}通常用于第一个NALU、SPS和PPS,其他地方使用{0 0 1}以减少内存占用

([start code] NALU) | ( [start code] NALU) | ...

找出一帧数据中有个NALU,代码如下:

const uint8_t kNaluTypeMask = 0x1F;

std::vector<NaluIndex> FindNaluIndices(const uint8_t* buffer,  size_t buffer_size) {
  // This is sorta like Boyer-Moore, but with only the first optimization step:
  // given a 3-byte sequence we're looking at, if the 3rd byte isn't 1 or 0,
  // skip ahead to the next 3-byte sequence. 0s and 1s are relatively rare, so
  // this will skip the majority of reads/checks.
  std::vector<NaluIndex> sequences;
  if (buffer_size < kNaluShortStartSequenceSize)
    return sequences;

  const size_t end = buffer_size - kNaluShortStartSequenceSize;
  for (size_t i = 0; i < end;) {
    if (buffer[i + 2] > 1) {
      i += 3;
    } else if (buffer[i + 2] == 1 && buffer[i + 1] == 0 && buffer[i] == 0) {
      // We found a start sequence, now check if it was a 3 of 4 byte one.
      NaluIndex index = {i, i + 3, 0};
      if (index.start_offset > 0 && buffer[index.start_offset - 1] == 0)
        --index.start_offset;

      // Update length of previous entry.
      auto it = sequences.rbegin();
      if (it != sequences.rend())
        it->payload_size = index.start_offset - it->payload_start_offset;

      sequences.push_back(index);

      i += 3;
    } else {
      ++i;
    }
  }

  // Update length of last entry, if any.
  auto it = sequences.rbegin();
  if (it != sequences.rend())
    it->payload_size = buffer_size - it->payload_start_offset;

  return sequences;
}

为防止编码数据和分隔符冲突,需要对编码数据进行处理,需要对编码数据做如下处理:

// 00 00 00 -> 00 00 03 00
// 00 00 01 -> 00 00 03 01
// 00 00 02 -> 00 00 03 02
// 00 00 03 -> 00 00 03 03

void WriteRbsp(const uint8_t* bytes, size_t length, rtc::Buffer* destination) {
  static const uint8_t kZerosInStartSequence = 2;
  static const uint8_t kEmulationByte = 0x03u;
  size_t num_consecutive_zeros = 0;
  destination->EnsureCapacity(destination->size() + length);

  for (size_t i = 0; i < length; ++i) {
    uint8_t byte = bytes[i];
    if (byte <= kEmulationByte &&
        num_consecutive_zeros >= kZerosInStartSequence) {
      // Need to escape.
      destination->AppendData(kEmulationByte);
      num_consecutive_zeros = 0;
    }
    destination->AppendData(byte);
    if (byte == 0) {
      ++num_consecutive_zeros;
    } else {
      num_consecutive_zeros = 0;
    }
  }
}

在解码之前需要做一次反向处理,处理代码如下:

//  00 00 03 00 -> 00 00 00 
//  00 00 03 01 -> 00 00 01
//  00 00 03 02 -> 00 00 02
//  00 00 03 03 -> 00 00 03

std::vector<uint8_t> ParseRbsp(const uint8_t* data, size_t length) {
  std::vector<uint8_t> out;
  out.reserve(length);

  for (size_t i = 0; i < length;) {
    // Be careful about over/underflow here. byte_length_ - 3 can underflow, and
    // i + 3 can overflow, but byte_length_ - i can't, because i < byte_length_
    // above, and that expression will produce the number of bytes left in
    // the stream including the byte at i.
    if (length - i >= 3 && !data[i] && !data[i + 1] && data[i + 2] == 3) {
      // Two rbsp bytes.
      out.push_back(data[i++]);
      out.push_back(data[i++]);
      // Skip the emulation byte.
      i++;
    } else {
      // Single rbsp byte.
      out.push_back(data[i++]);
    }
  }
  return out;
}

avcC

这种分隔符通常用于文件存储例如mp4、flv,还有直播rtmp等
用VLC打开avcC格式的视频文件,编码信息中显示H264 - MPEG-4 AVC(part 10)(avc1)
avcC的格式如下,字段length所占的字节长度由extradata中的NALULengthSizeMinusOne字段决定,length的值由NALU的实际长度决定

([extradata]) | ([length] NALU) | ([length] NALU) | ...
  • extradata的数据结构如下:
bits    
8   version ( always 0x01 )
8   avc profile ( sps[0][1] )
8   avc compatibility ( sps[0][2] )
8   avc level ( sps[0][3] )
6   reserved ( all bits on )
2   NALULengthSizeMinusOne
3   reserved ( all bits on )
5   number of SPS NALUs (usually 1)

repeated once per SPS:
  16         SPS size
  variable   SPS NALU data

8   number of PPS NALUs (usually 1)

repeated once per PPS:
  16       PPS size
  variable PPS NALU data
  • extradata例子:
    profile 为Baseline,Level为31,NALULengthSizeMinusOne为3(占用4个字节),SPS的长度为14 bytes,PPS的长度为4 bytes
uint8_t avcc_extradata[6] = {
    0x01, // version
    0x42, // baseline
    0x00, // compatibility
    0x1F, // level 31
    0xFF, // NALULengthSizeMinusOne is 4 bytes
    0xE1, // sps nalus number
};
// sps
int16_t sps_len = 14;
char sps_buf[15] = {103,  66,  192,  30,  140,  141,  64,  80,  30,  144,  15,  8,  132,  106 };
// pps
int16_t pps_len = 4;
char pps_buf[4] = {104,  206,  60,  128};
// extradata
int16_t extradata_len = 6(extradata的固定长度) + 2(sps的长度占用2bytes) + 14(sps的内容) + 1(用一个byte表示pps的长度) + 2(pps的长度占用2bytes) + 4(pps的内容);
int16_t pos = 0;
char extradata[extradata_len];

// 先拷贝extradata
memcpy(extradata, avcc_extradata, 6);
pos += 6;
// extradata中表示sps大小的字节序采用大端模式(将一个多位数的低位放在较大的地址处,高位放在较小的地址处)
extradata[pos]  = 0xFF & (sps_len >> 8)
extradata[pos+1] = 0xFF & sps;
pos += 2;
// 拷贝sps的内容
memcpy(extradata + pos, sps_buf, sps_len);
pos += sps_len;

// extradata中存在一个pps
extradata[pos] = 0x01;
pos += 1;

// extradata中表示pps大小的字节序采用大端模式(将一个多位数的低位放在较大的地址处,高位放在较小的地址处)
extradata[pos]  = 0xFF & (pps_len >> 8)
extradata[pos+1] = 0xFF & pps_len;
pos += 2;
// 拷贝pps的内容
memcpy(extradata + pos, pps_buf, pps_len);
pos += pps_len;

// 最后生成的内容如下
// 0x1 0x42 0x0 0x1f 0xff 0xe1 0x0 0xe 0x67 0x42 0xc0 0x1e 0x8c 0x8d 0x40 0x50 0x1e 0x90 0xf 0x8 0x84 0x6a 0x1 0x0 0x4 0x68 0xce 0x3c 0x80

编解码器的输入输出分隔符类型

编解码器编码输出解码输入
libopenh264Annex BAnnex B
MediaCodecAnnex BAnnex B
VideoToolboxavcCavcC
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值