H264码流分析
title H264BitstreamParser
APP->H264DecoderImpl: Decode(input_image)
H264DecoderImpl->H264BitstreamParser:ParseBitstream(bitstream)
H264BitstreamParser->H264BitstreamParser:H264::indNaluIndices
H264BitstreamParser->H264BitstreamParser:(for) ParseSlice
H264BitstreamParser->SpsParser:ParseSps
H264BitstreamParser->PpsParser:ParsePps
H264BitstreamParser->H264BitstreamParser:ParseNonParameterSetNalu
H264BitstreamParser->H264BitstreamParser:ParseNonParameterSetNalu
H264BitstreamParser->H264BitstreamParser:H264::ParseRbsp
H264BitstreamParser->BitstreamReader:ConsumeBits
H264BitstreamParser->BitstreamReader:ReadExponentialGolomb
H264BitstreamParser->H264BitstreamParser:
H264BitstreamParser->H264BitstreamParser:
1. Sps 结构:
2. Pps结构
3.Slice Header结构图
0. Nalu Type & Slice Type
enum NaluType : uint8_t {
kSlice = 1,
kIdr = 5,
kSei = 6,
kSps = 7,
kPps = 8,
kAud = 9,
kEndOfSequence = 10,
kEndOfStream = 11,
kFiller = 12,
kPrefix = 14,
kStapA = 24,
kFuA = 28
};
enum SliceType : uint8_t { kP = 0, kB = 1, kI = 2, kSp = 3, kSi = 4 };
1. frame_num的检测
frame_num被用作图片的标识符,应由比特流中的 log2_max_frame_num_minus4 + 4 位表示。frame_num的约束如下:
变量 PrevRefFrameNum 的导出方式如下:
- 如果当前图片是IDR图片,PrevRefFrameNum 被设为0。
- 否则(当前图片不是IDR图片),PrevRefFrameNum 被设为:
- 如果在8.2.5.2条款规定的frame_num间隙的解码过程是由包含在解码顺序中跟随前一个包含有参考图片的访问单元的非参考图片的解码过程调用的,PrevRefFrameNum 被设为由8.2.5.2条款规定的frame_num间隙的解码过程推断的“不存在的”参考帧的frame_num的值中的最后一个。
- 否则,PrevRefFrameNum 被设为前一个解码顺序中包含有参考图片的访问单元的frame_num的值。
frame_num的值受以下约束:
- 如果当前图片是IDR图片,frame_num必须等于0。
- 否则(当前图片不是IDR图片),以前一个解码顺序中包含有参考图片的访问单元中的主编码图片作为前一个参考图片,当前图片的frame_num的值不得等于PrevRefFrameNum,除非以下三个条件全部为真:
- a) 当前图片和前一个参考图片属于解码顺序中的连续访问单元。
- b) 当前图片和前一个参考图片是具有相反奇偶校验的参考场。
- c) 以下条件之一或多个为真:
- 前一个参考图片是IDR图片,
- 前一个参考图片包含一个memory_management_control_operation语法元素等于5, 注意3 – 当前一个参考图片包含一个memory_management_control_operation语法元素等于5时,PrevRefFrameNum等于0。
- 在前一个参考图片之前存在一个主编码图片,且该主编码图片的frame_num不等于PrevRefFrameNum,
- 在前一个参考图片之前存在一个主编码图片,且该主编码图片不是参考图片。
当frame_num的值不等于PrevRefFrameNum时,比特流符合性要求将遵守以下约束:
a) 在解码顺序中,当前被标记为“用于短期参考”的任何先前场或帧,其frame_num的值不能等于以下变量取得的任何值:
UnusedShortTermFrameNum = (PrevRefFrameNum + 1) % MaxFrameNum while (UnusedShortTermFrameNum != frame_num) UnusedShortTermFrameNum = (UnusedShortTermFrameNum + 1) % MaxFrameNum
b) frame_num的值受以下约束:
- 如果gaps_in_frame_num_value_allowed_flag等于0,则当前图片的frame_num值必须等于(PrevRefFrameNum + 1) % MaxFrameNum。
- 否则(gaps_in_frame_num_value_allowed_flag等于1),适用以下规定:
- 如果frame_num大于PrevRefFrameNum,则在比特流中不得有在解码顺序中跟随前一个参考图片且在当前图片之前的非参考图片,其中以下条件之一为真:
- 非参考图片的frame_num值小于PrevRefFrameNum,
- 非参考图片的frame_num值大于当前图片的frame_num值。
- 否则(frame_num小于PrevRefFrameNum),在比特流中不得有在解码顺序中跟随前一个参考图片且在当前图片之前的非参考图片,其中以下两个条件都为真:
- 非参考图片的frame_num值小于PrevRefFrameNum,
- 非参考图片的frame_num值大于当前图片的frame_num值。
- 如果frame_num大于PrevRefFrameNum,则在比特流中不得有在解码顺序中跟随前一个参考图片且在当前图片之前的非参考图片,其中以下条件之一为真:
包括memory_management_control_operation等于5的图片应满足上述frame_num的约束,并且在当前图片的解码和内存管理控制操作的处理之后,该图片应被推断为在解码过程中所有后续使用的frame_num等于0,除非在7.4.1.2.4条款中另有规定。
注意4 – 当主编码图片不是IDR图片且不包含memory_management_control_operation语法元素等于5时,相应的冗余编码图片的frame_num值与主编码图片中的frame_num值相同。
或者,冗余编码图片包括一个memory_management_control_operation语法元素等于5,并且相应的主编码图片是IDR图片。
2.检查参考帧是否存在
ffmpeg中的h264parser,有一个 GetBitContext,它以bit计数,并留有一个byte的防越界缓冲 size_in_bits_plus8,其初始化是在这里
1 consumed = ff_h2645_extract_rbsp(buf + buf_index, src_length, &rbsp, &nal, 1); 2 if (consumed < 0) 3 break; 4 5 buf_index += consumed; 6 7 ret = init_get_bits8(&nal.gb, nal.data, nal.size);
1 /** 2 * Initialize GetBitContext. 3 * @param buffer bitstream buffer, must be AV_INPUT_BUFFER_PADDING_SIZE bytes 4 * larger than the actual read bits because some optimized bitstream 5 * readers read 32 or 64 bit at once and could read over the end 6 * @param bit_size the size of the buffer in bits 7 * @return 0 on success, AVERROR_INVALIDDATA if the buffer_size would overflow. 8 */ 9 static inline int init_get_bits(GetBitContext *s, const uint8_t *buffer, 10 int bit_size) 11 { 12 int buffer_size; 13 int ret = 0; 14 15 if (bit_size >= INT_MAX - FFMAX(7, AV_INPUT_BUFFER_PADDING_SIZE*8) || bit_size < 0 || !buffer) { 16 bit_size = 0; 17 buffer = NULL; 18 ret = AVERROR_INVALIDDATA; 19 } 20 21 buffer_size = (bit_size + 7) >> 3; 22 23 s->buffer = buffer; 24 s->size_in_bits = bit_size; 25 s->size_in_bits_plus8 = bit_size + 8; 26 s->buffer_end = buffer + buffer_size; 27 s->index = 0; 28 29 return ret; 30 }
检测参考帧是否存在,要从P帧的slice_header里解析一个num_ref_idx_active_override_flag,通常它是0时ref_count从pps中获得。如果它不是0,则从码流中读一个无符号的指数 Golomb码作为ref_count[0],最后检查一下ref_count[0] - 1是否大于15,大于15则出错。
而如果 num_ref_idx_active_override_flag
被设置为 0,那么就会使用默认的参考图像索引数量,而不是覆盖它。如果 num_ref_idx_active_override_flag
被设置为 1,那么当前 slice 中的参考图像索引数量将被覆盖,并且会使用在当前 slice 中指定的数量。这个数量通常是通过 slice header 中的额外信息指定的。
if (num_ref_idx_active_override_flag) { ref_count[0] = get_ue_golomb(gb) + 1;
}
//ref_count[0]即它的参考帧数
然后从码流中读取一个bit的ref_pic_list_reordering_flag,当它不为0时继续读一个指数哥伦布编码的reordering_of_pic_nums_idc,判断它的值是否为3。当它的值是0,1,2时,参考图像的编号要重新排列,重排会使index ++,然而ref_count[0]中经常为1,一旦index为1,reference count overflow的错误就发生了。
-
0: 重新排序参考图像的编号。这意味着需要对参考图像的编号进行重新排列,以便更有效地利用存储或者以便于更好地执行某些处理。
-
1: 对参考图像的编号进行重排列,并且交换两个编号的位置。这个操作通常用于交换两个相邻的参考图像的位置。
-
2: 将参考图像的编号复制并插入到指定的位置。这个操作通常用于复制参考图像并将其插入到编码序列中的某个特定位置。
-
3: 表示结束MMCO操作(Memory Management Control Operation)的指令。
1 int index; 2 for (index = 0; ; index++) { 3 unsigned int reordering_of_pic_nums_idc = get_ue_golomb_31(gb); 4 5 if (reordering_of_pic_nums_idc < 3) 6 get_ue_golomb_long(gb); 7 else if (reordering_of_pic_nums_idc > 3) { 8 av_log(logctx, AV_LOG_ERROR, 9 "illegal reordering_of_pic_nums_idc %d\n", 10 reordering_of_pic_nums_idc); 11 return AVERROR_INVALIDDATA; 12 } else 13 break; 14 15 if (index >= ref_count[list]) { 16 av_log(logctx, AV_LOG_ERROR, 17 "reference count %d overflow\n", index); 18 return AVERROR_INVALIDDATA; 19 } 20 }
在视频编码中,ref_pic_list_modification_flag_l和reordering_of_pic_nums_idc都是H.264/AVC和H.265/HEVC标准中的一部分,用于描述参考图像列表的修改。
-
ref_pic_list_modification_flag_l:这是一个标志位,用于指示是否要修改参考图像列表。在H.264/AVC和H.265/HEVC中,参考图像列表是用于预测当前图像的运动矢量和残差的图像。如果该标志位被设置为1,那么参考图像列表将会被修改,否则将保持不变。
-
reordering_of_pic_nums_idc:这是一个指示如何修改参考图像列表的指令码。它用于指示参考图像列表中图像的重新排序方式。具体来说,它指示了要删除、移动或添加哪些图像到参考图像列表中。
因此,reordering_of_pic_nums_idc字段的作用是基于ref_pic_list_modification_flag_l字段的取值。只有当ref_pic_list_modification_flag_l被设置为true时,reordering_of_pic_nums_idc字段才会生效,因为只有在需要修改参考图像列表时,才需要使用reordering_of_pic_nums_idc来指示如何修改列表。
视频会议中,因为没有B帧,参考帧列表一般只有1帧,不需要重新排序。
在H.264编码标准中,modification_of_pic_nums_idc是用于指示参考图像列表的修改方式的语法元素之一,它在slice头部的ref_pic_list_modification()语法中使用。modification_of_pic_nums_idc的个数是根据slice头部中的num_ref_idx_l0_active_minus1和num_ref_idx_l1_active_minus1来确定的。
具体规定如下:
- 如果num_ref_idx_l0_active_minus1大于0,则modification_of_pic_nums_idc的个数为num_ref_idx_l0_active_minus1 + 1。
- 如果num_ref_idx_l1_active_minus1大于0,则再额外加上num_ref_idx_l1_active_minus1 + 1。
简单来说,modification_of_pic_nums_idc的个数等于活动参考图像列表中的索引数加一。
在 H.264 标准中,引用图像列表的重新排序确实有两个概念:ref_pic_list_reordering 和 ref_pic_list_modification。这可能会导致一些混淆,但它们实际上是指同一种操作。
-
ref_pic_list_reordering:这是在 H.264 标准早期版本中使用的术语,用于描述对参考图像列表的重新排序。在参考图像列表中,编码器可以通过交换图像的顺序来优化编码效率。这一过程在标准的早期版本中被称为 ref_pic_list_reordering。
-
ref_pic_list_modification:随着 H.264 标准的演进,术语被修改为 ref_pic_list_modification。这是一个更准确的术语,因为它描述了在解码器端修改参考图像列表的操作。在解码器端,对参考图像列表的重新排序操作实际上是一种修改操作,因为解码器必须根据编码器发送的重新排序指令来重新排列图像。
因此,虽然术语发生了变化,但其基本功能和目的保持不变。StreamEye v3.3 中使用的 ref_pic_list_modification 与早期版本中的 ref_pic_list_reordering 是相同的操作,只是术语上的不同。
StreamEye v3.3 是由公司称为 "电信科学技术研究院"(Telecommunication Science and Technology Research Institute)开发的。它主要用于视频流分析和处理。根据我了解到的信息,StreamEye v3.3 于2013年发布。
关于 H.264 标准中 ref_pic_list_reordering 到 ref_pic_list_modification 的术语变更,这发生在标准的修订过程中,大约在 2007 年至 2009 年之间的 H.264 标准修订版本中进行了更改。由于标准的更新通常需要一段时间才能在实际应用中被广泛采用,因此术语的变更可能会在实际工具和软件中出现滞后。