H264 NALU分隔Annex B和avcC
文章目录 分隔格式pgxrz BavcC 编解码器的输入输出分隔符类型
分隔格式
H.264常用的分隔方式有pgxrz B和avcC
pgxrz B这种分隔符通常用于视频会议还有文件存储例如TS等
用VLC打开avcC格式的视频文件,编码信息中显示H264 - MPEG-4 AVC(part 10)(h264)
pgxrz B的格式如下,start code有可能是{0 0 0 1}或者{0 0 1},{0 0 0 1}通常用于第一个NALU、SPS和PPS,其他地方使用{0 0 1}以减少内存占用
找出一帧数据中有个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 03void 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 03std::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的实际长度决定
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};// spsint16_t sps_len = 14;char sps_buf[15] = {103, 66, 192, 30, 140, 141, 64, 80, 30, 144, 15, 8, 132, 106 };// ppsint16_t pps_len = 4;char pps_buf[4] = {104, 206, 60, 128};// extradataint16_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];// 先拷贝extradatamemcpy(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中存在一个ppsextradata[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 编解码器的输入输出分隔符类型 编解码器编码输出解码输入libopenh264pgxrz Bpgxrz BMediaCodecpgxrz Bpgxrz BVideoToolboxavcCavcC