2.参考图像列表

解码器每解码完一幅图像,都会判断该图像是否用于参考,并标记相应的参考图像,而且会在解码下一幅图像前,将参考图像列表初始化好;解码下一幅图像时,先根据图像的片头信息判断是否需要对参考列表重排序,如果需要,就根据片头的附加信息重新排序,之后开始对图像解码,解码完成后,重新进行参考图像的标记……如此循环。(IDR帧不需要参考帧,它解码完后标记产生第一个参考图像,供之后的图像解码时使用)

a.参考图像列表的初始化

前面我们已经知道,参考图像有短期参考和长期参考,这两种参考帧分别以不同的方式表示,其中短期参考图像使用 FrameNum(最终用的是PicNum) 的值作为标志,而长期参考图像则被分配一个长期参考帧索引LongTermFrameIdx(最终用的是LongTermPicNum)。参考图像列是一个由变量PicNum 和LongTermPicNum构成的数组,当解码一个宏块时,要指定参考图像,只需指定所需要的参考图像在这个数组中对应的索引值。关于LongTermFrameIdx的相关说明在下面介绍“参考图像的标记-几个重要的结构体”中说到,不妨先跳到那里去看看,这样继续往下看时也就少了些疑惑。

另外,当产生的RefPicList0 或 RefPicList1 中的分量个数分别大于num_ref_idx_l0_active_minus1 + 1 或 num_ref_idx_l1_active_minus1+ 1 时,多余的分量将从列表中丢弃;分量个数分别小于 num_ref_idx_l0_active_minus1 + 1 或num_ref_idx_l1_active_minus1 + 1 时,列表中剩余的分量设置为"no reference picture"。

在初始化图像列表之前,需要先计算出PicNum,这个在前面已经提及。

上图的计算过程用到了FrameNumWrap(每个参考图像都对应一个FrameNum和FrameNumWrap),这个变量是这样取值的:如果FrameNum(参考帧的frame_num) 不大于当前图像片头中的frame_num ,则FrameNumWrap 等于FrameNum;否则,FrameNumWrap 等于FrameNum 减去MaxFrameNum。使用FrameNumWrap的原因是:若直接使用frame_num来计算PicNum,由于frame_num是在0到MaxFrameNum之间循环变化的,所以如果当前图像的frame_num正好处于临界值,比如0时,那前几帧(按解码顺序)参考图像的frame_num就会比当前图像的frame_num(此处假设是0)大,由此计算出来的PicNum也就会比CurrPicNum大,这在处理上会带来一些麻烦,而对于FrameNumWrap,当前图像前几个参考帧对应的FrameNumWrap始终会比当前图像的frame_num小(如果当前图像处于临界位置0,那前几帧参考图像的FrameNumWrap就变成了负值),因此使用FrameNumWrap计算PicNum的话,只要某个参考图像在解码顺序上比当前图像靠前,那它对应的PicNum就比CurrPicNum小,不需要再分情况讨论。这个特点为场图像内P和SP片的参考帧列表中短期参考图像的排序提供了方便。

下面开始详细讨论图像列表的初始化。根据参考图像是帧还是场、当前片是P(或SP)片还是B片,可将这一节分为四部分讨论。

(1)      “帧”中P和SP片的参考帧列表的初始化

排序规则:短期参考帧(或短期补充参考场对)位于长期参考帧(或长期参考场对)之前;短期参考帧和短期补充参考场对根据PicNum 值进行降序排列;长期参考帧和长期参考场对根据LongTermPicNum 值进行升序排列。由此可直接得到RefPicList0列表。

可见,对于P片和SP片,其参考图像列表中的短期参考帧都是基于PicNum排序的。

(2)      “场”中P和SP片的参考帧列表的初始化

与(1)中的情况相比,参考图像列表中的每个场都有单独的列表索引,而且对一个场解码时,可用的参考图像数将是解码一个帧时的两倍。在对场中P和SP片参考图像列表初始化时,先要将短期参考图像和长期参考图像分别生成一个refFrameList0ShortTerm和refFrameList0LongTerm,这两个列表用于确定最后的RefPicList0。

refFrameList0ShortTerm的生成:所有包含一个或多个标记为“用于短期参考”场的帧,均包含在短期参考帧列表refFrameList0ShortTerm中。若当前场是互补参考场对中的第二场(解码顺序),并且第一场被标记为“用于短期参考”时,则该“第一场”包含在短期参考帧列表refFrameList0ShortTerm中。refFrameList0ShortTerm的顺序以具有最大FrameNumWrap 值的帧开始,以递减顺序直到具有最小FrameNumWrap 值的帧为止。(P帧是单向参考, FrameNumWrap越大,说明越靠近当前图像)。

refFrameListLongTerm的生成:所有包含一个或多个帧被标记为“用于长期参考”场的帧,均包含在长期参考帧列表LongTermFrameIdx中。当当前场是互补参考场对中的第二场(解码顺序),并且第一场被标记为“用于长期参考”时,则该第一场包含在长期参考帧列表refFrameList0LongTerm中。refFrameList0LongTerm 的顺序以具有最小LongTermFrameIdx值的帧开始,以递增顺序直到具有最大LongTermFrameIdx值的帧为止。

至于如何由refFrameList0ShortTerm 和refFrameList0LongTerm得到RefPicList0,将在后面提到。

(3)      “帧”中B片的参考帧列表的初始化

B片需要双向参考。与P片不同的是,B片的RefPicList0 和RefPicList1 中短期参考图像的排列顺序取决于输出次序(而不再是FrameNum),即由PicOrderCnt( )给定。

函数PicOrderCnt( picX )如下:

if( picX 是一帧或互补场对 )

PicOrderCnt(picX)= picX 互补场对的Min(TopFieldOrderCnt,BottomFieldOrderCnt )

else if( picX 是顶场)

PicOrderCnt( picX ) = 场picX 的TopFieldOrderCnt

else if( picX 是底场)

PicOrderCnt( picX ) = 场picX 的BottomFieldOrderCnt

排序规则:短期参考帧和短期参考场对均排在长期参考帧和长期参考场对之前。

具体如下:

对于RefPicList0:

可将列表分为三段,头一段中所有的图像的POC都比当前图像的小,且按POC降序排列;中间一段,所有图像的POC都比当前图像的大,且按POC升序排列;前两段都是短期参考帧,最后一段是长期参考帧,各图像按其LongTermPicNum升序排列。

对于RefPicList1:

与RefPicList0类似,也可将列表分为三段:头一段中所有的图像的POC都比当前图像的大,且按POC升序排列;中间一段,所有图像的POC都比当前图像的小,且按POC降序排列;最后一段长期参考帧,与RefPicList0一样,也是按LongTermPicNum升序排列。

(4)      “场”中B片的参考帧列表的初始化

需要先生成refFrameList0ShortTerm, refFrameList1ShortTerm 和 refFrameListLongTerm三个参考列表。然后用refFrameList0ShortTerm和refFrameListLongTerm可得到RefPicList0,用refFrameList1ShortTerm和refFrameListLongTerm可得到RefPicList1。

refFrameList0ShortTerm的生成:

分成两段:头一段中所有的图像的POC都比当前图像的小或相等(在帧模式下没有相等的POC,但是场模式下就可能有,比如一帧中的两场就可以有相等的POC),且按POC降序排列;第二段中,将剩下的POC比当前图像大的参考图像按POC升序排列。

refFrameList1ShortTerm的生成:

也分两段:头一段中所有的图像的POC都比当前图像的大(此处不包含相等),按POC升序排列;剩下的POC比当前图像小的参考图像按POC降序排列。

refFrameListLongTerm的生成:

按LongTermFrameIdx升序排列。

至于如何用这三个列表最终生成RefPicList0和RefPicList1,将在下面讲述。

(剩余的解释)    从refFrameListXShortTerm (X 可为 0 或1) 和refFrameListLongTerm得到RefPicListX的步骤:(本次排序的主要特点就是尽可能使奇偶场交叉出现)

从与当前场具有相同奇偶性的场(如果存在)开始,对短期参考场进行排序,其方法为从有序的帧列表refFrameListXShortTerm中通过交替选择具有不同奇偶性的场作为参考场。当一个参考帧的一个场还没有解码或被标记为“用于短期参考”时,忽略该场,并且把refFrameListXShortTerm帧列表中与该场具有相同奇偶性的下一个可用已存参考场插入到RefPicListX中。当已经排好序的refFrameListXShortTerm帧列表中没有更多的具有交替奇偶性的短期参考场时,把接下来的奇偶性可用、还没有被索引的场按照它们在已经排好序的refFrameListXShortTerm帧列表中具有的顺序插入到RefPicListX中。

从与当前场具有相同奇偶性的场(如果存在)开始,对长期参考场进行排序,其方法为从有序的帧列表refFrameListLongTerm 中通过交替选择具有不同奇偶性的场作为参考场。当一个参考帧的一个场还没有解码或被标记为“用于长期参考”时,忽略该场,并且把refFrameListLongTerm 帧列表中与该场具有相同奇偶性的下一个可用已存参考场插入到RefPicListX中。当已经排好序的refFrameListLongTerm 帧列表中没有更多的 具有交替奇偶性的长期参考场时,把剩下的奇偶性可用、还没有被索引的场按照它们在已经排好序的帧refFrameListLongTerm 帧列表中具有的顺序插入到RefPicListX中。

b.参考图像列表的重排序

从上面的参考图像列表的初始化过程中,我们可以看到,不论是四种情况中的哪一种,最后的参考图像列表总是可以看成两部分,第一部分是短期参考图像列表,剩下部分是长期参考列表。下面将分这两种情况讲述参考帧的重排序过程,重排序的目的是为了将需要的参考帧放在参考列表的前面,这样指定参考图像索引号时就可以用更小的数(更少的比特数)。

1)短期参考帧的重排序

首先,根据片头ref_pic_list_reordering( ) 中出现的所有abs_diff_pic_num_minus1元素计算出它们对应的参考图像的实际序号(picNum),并根据这些序号找到对应参考图像。然后按照各个“abs_diff_pic_num_minus1”在码流中出现的顺序,将它们对应的参考图像,依次移动到短期参考列表的开头位置(这些参考图像原来就在短期参考列表中,但重排序前他们在这个列表中的位置可能较靠后,重排序之后就会移动到列表前面)。

由abs_diff_pic_num_minus1计算picNum一般分为两步:

首先,根据片头ref_pic_list_reordering( ) 中出现的所有abs_diff_pic_num_minus1元素计算出它们对应的参考图像的序号picNumLXNoWrap,计算这个序号时考虑了picNumLXNoWrap的循环计数特性,它的值总在0到MaxPicNum之间(MaxPicNum在前面介绍frame_num元素时提到过):

— 如果reordering_of_pic_nums_idc等于0

if( picNumLXPred − ( abs_diff_pic_num_minus1 + 1 ) < 0 )

picNumLXNoWrap=picNumLXPred−(abs_diff_pic_num_minus1+1)+MaxPicNum

else

picNumLXNoWrap = picNumLXPred − ( abs_diff_pic_num_minus1 + 1 )

—  否则(reordering_of_pic_nums_idc 等于1)

if( picNumLXPred + ( abs_diff_pic_num_minus1 + 1 )  >= MaxPicNum )

picNumLXNoWrap=picNumLXPred+(abs_diff_pic_num_minus1+1)−MaxPicNum

else

picNumLXNoWrap = picNumLXPred + ( abs_diff_pic_num_minus1 + 1 )

其中picNumLXPred代表前一个picNumLXNoWrap值;而当计算第一个picNumLXNoWrap时,picNumLXPred=CurrPicNum。

然后,根据picNumLXNoWrap计算对应的picNumLX(类似于之前由frame_num计算FrameNumWrap):如果某个图像的picNumLXNoWrap比CurrPicNum大,则picNumLX=picNumLXNoWrap-MaxPicNum ;否则,picNumLX=picNumLXNoWrap。picNumLX就是某个参考图像的picNum值。

2)长期参考帧的重排序

大致过程与短期参考帧类似,不过这时不再需要计算picNumLXNoWrap和picNumLX这些过程了,因为对长期参考帧重排序时,片头ref_pic_list_reordering( ) 中的“long_term_pic_num”元素直接指定了参考图像的LongTermPicNum。

剩下的主要任务就是:按照各个“long_term_pic_num”在码流中出现的顺序,将它们对应的长期参考图像,依次移动到长期参考列表的开头位置(这些参考图像原来就在长期参考列表中,但重排序前他们在这个列表中的位置可能较靠后,重排序之后就会移动到列表前面)。需要注意的是,虽然是“开头位置”,但这个开头位置是针对参考列表的第二部分,即“长期参考列表”来说的,所有的长期参考图像依然都在短期参考列表之后。

c.参考图像的标记

为了方便后面的理解,这里要先简单介绍几个数据结构:

DPB(DecodedPicture Buffer),它保存了编、解码过程中所有的重建图像(在JM的参考代码中,DecodedPictureBuffer这个结构体是在mbuffer.h中定义的),这里说的“所有重建图像”,并不是真的指所有解码过的图像,那样内存开销太大了,dpb中的图像每隔一段时间会清空一次,“所有”指的是,自上一次清空之后,所有新解码的图像。什么时候dpb会清空一次呢?一般当IDR帧到来时,解码器会根据该IDR帧片头的dec_ref_pic_marking( )中的no_output_of_prior_pics_flag,来决定是否清空dpb,这个在前面已经说过。

一个dpb结构中,包含了三个FrameStore数组,准确的说是三个FrameStore类型的指针(一个FrameStore结构描述一个帧),为什么需要三个呢?其中,一个数组包含了所有重建帧(这个数组叫fs),而另外两个,一个仅包含了所有短期参考帧(数组名fs_ref),一个仅包含了所有长期参考帧(fs_ltref)。FrameStore是个很关键的结构体,它记录了一帧图像的很多有用信息:在帧模式下,它的成员变量标记了当前帧是否是参考帧,以及是否是长期参考;在场模式下,它的成员变量记录了本帧中的每个场的特性,包括某个场是否是参考场、以及是否是长期参考、是否已解码等。如果某帧(或帧中某场)用于长期参考,那此帧对应的LongTermFrameIdx也在这个结构中给出,LongTermFrameIdx表示当前帧在数组fs_ltref中的索引。另外,这个结构体的“is_non_existent”成员用来标记当前帧是否是“不存在”帧。

一个FrameStore结构中,包含三个StorablePicture结构,一个StorablePicture结构描述一幅图像,为什么需要三个呢?一个用于描述整帧图像,另外两个分别用于描述当前帧中顶、底两场图像。StorablePicture也是一个很重要的结构体,它记录了本图像的POC、顶场POC、底场POC等信息。另外,当本图像用于短期参考时,图像对应的PicNum就记录在这个结构体中;当本图像用长期参考时,图像对应的long_term_pic_num也记录在这个结构体中。

有了这些简单的认识后,来看一个序列中参考图像的标记过程。

在每一个序列的开始,会有一个IDR帧,对于IDR帧,不需要参考图像,这时会将之前所有标记为“短期参考”或“长期参考”的图像,都重新标记为“不用于参考”,即清空参考图像列表(此外,当一个片的片头出现memory_management_control_operation等于5的情况时,也会清空参考图像列表);并且会根据片头dec_ref_pic_marking( )中的no_output_of_prior_pics_flag,来决定是否清空dpb中的图像;然后根据long_term_reference_flag将当前的IDR帧标记为短期或长期参考:

如果long_term_reference_flag 等于0 ,则该IDR 图像将被标记为"短期参考" ,并将MaxLongTermFrameIdx 设为“非长期帧索引”(即标记为 -1 )。

否则,该IDR 图像需要被标记为"长期参考" 。 LongTermFrameIdx应被置为0,并将MaxLongTermFrameIdx设为0。(每标记一个长期参考的图像,MaxLongTermFrameIdx会加1,它始终是长期参考图像数组中最后一个长期参考图像的索引)。

上面介绍的IDR图像的标记过程,对于之后的非IDR图像,情况就比较多,看标准的话内容较多,但毕厚杰书中的插图比较直观。

当frame_num允许间隔并且间隔出现时,应为属于“不存在”图像的frame_num 的每一个值产生和标记一个帧,并将生成的这些帧也可标记为“不存在non-existing”或“用于短期参考”(注意,标准中只是允许将这些不存在帧“标记”为用于短期参考,但实际解码时不能真的以他们做参考,否则会引起错误,因此最好都标记成不存在)。

当片头dec_ref_pic_marking( )结构中的adaptive_ref_pic_marking_mode_flag 元素等于0 时,解码完当前片后,要用自动划窗法来标记参考帧。

自动滑窗法:即FIFO的方法,当DPB中参考帧总数量(包括短期和长期参考帧) 等于序列参数集中的num_ref_frames 时,将DPB中PicNum最小的短期参考图像,即最早的参考图像,移出DPB。注意,调用这个过程时,只是将最早的参考图像标记为非参考,而不负责将当前正解码的图像标记为短期参考,因为每个这样的片解码完后(这时当前的图像不一定解码完),都会调用一次这个过程,而如果解码完此片后当前图像还没有解码完,当然就不能标记当前图像。从JM提供的代码中可以看出,每解码完一帧图像,都会调用一次store_picture_in_dpb函数,在这个函数的最后会自动根据刚刚解码的图像的相应参数(参考帧标志位)来决定是否将当前图像标记为短期参考。

当片头dec_ref_pic_marking( )结构中的adaptive_ref_pic_marking_mode_flag 元素等于1 时,解码完当前片后,会用自适应内存控制标记过程。

自适应内存控制标记过程:

(附)

注:

下面介绍的几个函数的定义都在mbuffer.c文件中;

相关变量(dpb和listX数组)也都在mbuffer.c文件中定义,listX数组的定义为“StorablePicture **listX[6]”,但实际上我们只用了listX[0]和listX[1];

相关的结构体如DecodedPictureBuffer、FrameStore、StorablePicture等在mbuffer.h中定义;

最好先回头熟悉一下我上面介绍的三个结构体再往下看;

函数介绍:

store_picture_in_dpb函数,是在解码完一幅图像时调用,它负责将重建图像存储;其内部会进行参考图像的标记工作,在“标记”这一步,一般就是修改图像结构体(dpb.fs[i]->frame)中相应的标识变量的值,这些标识变量负责标记当前图像是否用于参考,以及是长期还是短期参考;修改完这些变量后,调用update_ref_list函数即可。

update_ref_list 函数,根据DPB中包含的所有图像结构体(dpb中的fs数组)中相关的标识变量,判断哪些是短期参考图像,然后将短期参考图像填充到dpb.ref_fs数组中,这之后ref_fs中就存在有用的信息了。与此对应的,update_ltref_list函数则用于对长期参考图像数组dpb.ltref_fs[]进行操作。(这一段中我提到了很多“数组”,但实际上在JM代码中它们只是指针)。

上面的三个函数都是用于参考图像的标记过程,但它们都不会操作参考图像列表(ListX数组),而只是在dpb内部进行操作。

init_lists()       初始化参考图像列表。根据dpb.ref_fs和dpb.ltref_fs数组中的内容,对ListX[0]和ListX[1]进行初始化。实际上就是将先将短期参考数组dpb.ref_fs[]的内容复制到ListX[0](或ListX[1],下面不再重复提示)中,然后对ListX[0]按PicNum排序;接着再将长期参考数组dpb.ltref_fs[]的内容复制到ListX[0]后半部分,然后对ListX[0]后半部分按LontTermPicNum排序。

reorder_ref_pic_list函数以及该函数内部调用的reorder_short_term、reorder_long_term,都用于参考图像列表重排序。他们都是根据片头ref_pic_list_reordering()中相应元素的值对ListX[0]和ListX[1]进行操作。

从Slice_Header学习H.264(三.2)--相关细节之 参考图像列表相关推荐

  1. h.264参考图像列表、解码图像缓存

    1.参考图像列表(reference picture list) 一般来说,h.264会把需要编码的图像分为三种类型:I.P.B,其中的B.P类型的图像由于采用了帧间编码的这种编码方式,而帧间编码又是 ...

  2. 从Slice_Header学习H.264--相关细节之 POC的计算

    博客地址:http://blog.csdn.net/newthinker_wei/article/details/8784720 参考网址:H.264中POC类型之探讨:http://www.360d ...

  3. H.264再学习 -- H.264视频压缩标准

    如需转载请注明出处:https://blog.csdn.net/qq_29350001/article/details/78221863 H.264 这部分一直在讲,但是却没有系统的来说.接下来要详细 ...

  4. H.264 中的相关问题

    帧内解码时,在解码端,首先通过当前宏块左边.上边已经解码完成的宏块使用当前宏块的预测模式(预测模式计算过程请参见我的论文<H.264数字视频差错控制技术的研究>,在群FTP"本群 ...

  5. SpringMVC学习笔记:springMVC中相关细节

    SpringMVC中相关细节 1.什么是MVC? MVC是一种软件架构思想,将软件按照模型.视图.控制器来划分. ①M:模型层:指工程中的javaBean,作用是处理数据.javaBean分为两类:一 ...

  6. ROS2学习笔记(三)-- 采集虚拟仿真环境图像并发布

    简介:ROS2功能的学习我们还是在基于OpenAI的gym虚拟仿真环境中来完成,gym虚拟仿真环境安装请参考另一篇教程,这里不再重复说明,接下来我们开始创建一个ROS2的功能节点,并发布虚拟仿真环境小 ...

  7. H.264学习历程(天之骄子)

    半年前,我知道了H.264这个名词.那个时候决定学习H.264,可是我连资料都不知道如何收集.而且整个学校就只有我一个人在学习H.264,找不到人交流,所以那个时候学得真的是举步维艰,很痛苦,而能在网 ...

  8. H.264学习(一)——帧和场的概念

    一.何谓场? 每个电视帧都是通过扫描屏幕两次而产生的,第二个扫描的线条刚好填满第一次扫描所留下的缝隙.每个扫描即称为一个场.因此 25 帧/秒的电视画面实际上为 50 场/秒 (若为 NTSC 则分别 ...

  9. H.264编解码标准的核心技术(提供相关流程图)

    最近在学习H.264编解码知识,上网搜了不少资料看,发现大多数中文资料中都缺少相应的图片,例如编解码流程图.编码模板等,这对加深理解是很有帮助 的.木有办法,只好回去潜心阅读<H.264_MPE ...

  10. Android音视频开发基础(六):学习MediaCodec API,完成视频H.264的解码

    前言 在Android音视频开发中,网上知识点过于零碎,自学起来难度非常大,不过音视频大牛Jhuster提出了<Android 音视频从入门到提高 - 任务列表>.本文是Android音视 ...

最新文章

  1. java.io包对象读写_java.io 包中的____________和____________类主要用于对对象(Object)的读写_学小易找答案...
  2. 第十天2017/04/25(2、企业财富库:)
  3. android 怎么换行,android textview 怎么换行?
  4. LOD优化策略-通篇
  5. 【转】文件读写NDK(或Linux)
  6. 使用OnCtlColor函数消息打造背景
  7. Net方式实现主机与虚拟机互相ping通
  8. java 编译 器 ide_在没有IDE的情况下编译和运行Java
  9. 波卡二层扩容协议Plasm Network发布v1.7.0-dusty版本
  10. python学习之-- redis模块基本介绍
  11. Unity3D脚本编程读书笔记——第3章 Unity 3D 脚本语言的类型系统
  12. 大数据项目实战——基于某招聘网站进行数据采集及数据分析(一)
  13. 《胡雪岩》影评10篇
  14. 利用Map,完成下面的功能: 从命令行读入一个字符串,表示一个年份,输出该年的世界杯冠军是哪支球队。如果该年没有举办世界杯,则输出:没有举办世界杯。
  15. 笔记本 WIFI 热点批处理文件
  16. win10网页找不到服务器dns,找不到服务器或DNS错误怎么办
  17. Session-判断用户登陆验证码是否正确
  18. 廖雪峰python实战(一)
  19. 4t硬盘实际容量是多少_怎么对大容量硬盘(例如4T硬盘)进行查看并分区
  20. 项目管理十大流程,让你轻松管理项目

热门文章

  1. 为ashx文件启用session管理
  2. 子窗口和父窗口交互 (转)
  3. 怎么中文读_来新国大读中文系是一种怎样的体验?
  4. python将图片转化成字符图片_python如何将图片转换为字符图片
  5. 大数据挑战:敢不敢不要加入人的判断?
  6. 黑客的克星或叫“白客”
  7. winpcap的使用
  8. 统计SQL语句和存储过程
  9. Delphi中Hash表的使用方法!
  10. C语言基础知识(期末喽)