h.264参考图像列表、解码图像缓存
1.参考图像列表(reference picture list)
一般来说,h.264会把需要编码的图像分为三种类型:I、P、B,其中的B、P类型的图像由于采用了帧间编码的这种编码方式,而帧间编码又是以参考图像为基础进行的,因此需要有个参考图像列表来管理之前生成的参考图像,方便用于对当前图像进行编码。
2.解码图像缓存(decoded picture buffer)
随着图像编码的进行,(解码阶段)会不断有新的图像生成(重建图像),已解码图像会被放到解码图像缓存区中(或直接输出,这个在下面DPB讲)。但是由于内存大小与效率的限制,不可能一直保存参考图像而不丢弃,所以会有个对解码图像缓存这块内存区的管理策略。

参考图像列表
1. 来源
参考图像列表中的参考图像来自于解码图像缓存(DPB)中的已解码参考图像,DPB中可能包含参考以及非参考图像,参考图像列表把DPB中的参考图像整合成列表(数组)的形式,便于后续的排序等操作
2. 结构
组成
如标准8.4.1.2.3的注1所说:
- 如果当前宏块是场宏块(mb_field),则refIdxL0和refIdxL1指的是一系列的场;
- 如果当前宏块是帧宏块(!mb_field),则refIdxL0和refIdxL1指的是一系列的帧或者互补场对
但是参考图像列表的排列方式是不可能根据每个宏块的帧场时时刻刻做出改变,参考图像列表当前图像编码前就通过图像的帧场属性确定下来了:
- 如果当前图像为帧(structure == Frame),参考图像列表中存放的是一系列的帧或者互补场对
- 如果当前图像为场(structure == Field),参考图像列表中存放的是一系列的场
这就造成了当采用的是mbaff,且当前宏块为mb_field的时候参考图像列表的帧场不匹配,标准中的解决方法是:先得到参考帧RefPicListX[ refIdxLX / 2 ],然后选择该参考帧中对应的场进行编码(8.4.2.1),而JM则采用了一种巧妙的方法:如果当前图像是mbaff,则专门构造另外存放场参考图像列表RefPicListX(X = 2,3,4,5)方便进行提取参考场(JM中的list_offset就是用于决定X的值)。

长度
参考图像列表的最大长度取决于当前宏块是以何种方式进行编码
- 如果当前宏块为帧宏块(纯帧图像 或 mbaff && !mb_field),默认情况下参考图像列表的长度为16,也可以通过num_ref_idx_l0_active_minus1指定最大长度为(num_ref_idx_l0_active_minus1 + 1)
- 如果当前宏块为场宏块(场图像 或 mbaff && mb_field),默认情况下参考图像列表长度为32,也可以通过num_ref_idx_l0_active_minus1指定长度为(2 * num_ref_idx_l0_active_minus1 + 2)
——标准7.4.5.2
参考图像列表中的条目数目可能存在大于,或小于等于最大长度的情况:
- 如果条目数大于最大长度,将删除最大长度之外位置的条目
- 如果条目数小于最大长度,将把空余的条目设置为“非参考图像”
——标准8.2.4.2
3. 排序
参考图像分为短期参考图像(short-term reference)与长期参考图像(long-term reference),在某一个时间点上,参考图像只能是这两种的其中一种(非短即长)。参考图像列表分为两个部分:短期参考部分,长期参考部分。短期参考部分排在列表前头,长期排在后面。
P帧排序
一般来说,距离当前图像最近的参考图像会被当前图像用作最多的参考,距离越远则参考得越少,短期参考图像列表就是依据这种规律来进行排序的。
短期参考图像的序号由FrameNumWrap进行标记,以GOP为一个周期,GOP的第一帧的FrameNumWrap为0,后面持续递增,到当前帧是FrameNumWrap为最大,因此FrameNumWrap越大代表距离当前图像越近,因此将参考帧以FrameNumWrap降序方式放在refPicList0的起始位置
——标准8.2.4.1
也可以参考h.264的POC计算
长期参考图像的序号由LongTermPicNum进行标记,以升序的方式进行排序,(LongTermPicNum由MMCO分配)

B帧排序
B帧的参考帧排列方式与P帧并不完全相同,B帧的短期参考帧是以POC进行排序,其中有前向参考列表refPicList0与后向参考列表refPicList1
- 当参考帧的POC小于当前图像的POC时,将参考帧以POC降序方式放在refPicList0的起始位置上。然后剩余的短期参考图像按照POC升序的方式附加到refPicList0
- 当参考帧的POC大于当前图像的POC时,将参考帧以POC升序方式放在refPicList1的起始位置上。然后剩余的短期参考图像按照POC降序的方式附加到refPicList1
——标准8.2.4.2.3
长期参考图像的序号由LongTermPicNum进行标记,以升序的方式进行排序,分别放进refPicList0与refPicList1中(LongTermPicNum由MMCO分配)
注:一个未配对的场不能用于帧间预测,即表明如果当前为帧,那么其参考帧不可能为一个单一的场

场的处理
无论是P还是B,如果当前图像是场编码时,需要对上述的步骤做进一步处理,即把列表内的帧分为两个场,从与当前场具有相同奇偶性的场(如果存在)开始,对参考场进行排序
- 如果当前场为Top_field,则列表中为|top|bottom|top|bottom..
- 如果当前场为Bottom_field,则列表中为|bottom|top|bottom|top..
- 如果其中某一帧缺少了某个场作为参考场,那么将忽略该缺少的场
——标准8.2.4.2.5

4. 参考图像列表重排序
在某些特殊情况下会需要把已经得到的参考图像列表重新排序,可以参考JM18.6的HierarchicalCoding,下面主要演示重排序过程:

- 当中的紫灰色格子为重排序操作,对于短期参考图像,重拍序有符号与操作数,对于长期参考图像,直接通过操作数指定图像;
- 后面还没进行到的列表项会被后移;
- 划掉的格子为重复的图像,会被消除,如果被消除项后面还有其他项,后面的项会被前移填补前面被消除项的空格;
- 重排序的初始操作是以当前图像的FrameNumWrap为基础。
——标准8.2.4.3
解码图像缓存
1.参考图像标记过程
在当前图像解码过后、插入DPB之前,会进行标记过程Decoded reference picture marking process,对先前已经解码并存在于DPB中的图像进行标记。该过程会对DPB中已经存在的图像(当然不包括当前图像)重新标记。标记过程有两种情况:
1.当前图像为IDR
如果指定了no_output_of_prior_pics_flag,表示DPB中所有的图像不进行输出,但是需要从DPB中移除
否则,从DPB中移除所有图像并输出到磁盘
2.当前图像不为IDR,且指定了adaptive_ref_pic_buffering_flag
表明需要对当前DPB进行重新标记,其中有6种标记方法,即内存管理控制操作(memory management control operation)
| mmco | 参数1 | 参数2 | 操作描述 |
| 0 | - | - | 结束mmco |
| 1 | difference_of_pic_nums_minus1 | - | 求得短期参考图像picNumX后,把picNumX标记为非参考图像 |
| 2 | - | long_term_pic_num | 求得长期参考图像LongTermPicNum后,把该长期参考图像标记为非参考图像 |
| 3 | difference_of_pic_nums_minus1 | long_term_pic_num | 把短期参考图像picNumX,标记为长期参考图像,并赋值当前图像的LongTermPicNum = long_term_pic_num |
| 4 | MaxLongTermFrameIdx | - | 把LongTermPicNum > MaxLongTermFrameIdx 的长期参考图像都从DPB移除 |
| 5 | - | - | 把DPB中所有的参考图像移除 |
| 6 | - | long_term_pic_num | 把当前参考图像标记为长期参考图像,并赋值当前图像的LongTermPicNum = long_term_pic_num |
其中
- 短期参考图像序号为picNumX = CurrPicNum − ( difference_of_pic_nums_minus1 + 1 )
- 长期参考图像序号为LongTermPicNum = long_term_pic_num(也就是说长期参考图像可以从上表3,6项指定)
注:把图像作为参考或者非参考,对于参考场对来说,两个场必须共同作为参考图像或者非参考图像,也就是说当你想为一个场标记的时候,其实标准需要你对整个帧(两场)都进行标记
——标准8.2.5
2.DPB
DPB标记完成后,需要把当前图像插入DPB(如果需要)。
DPB结构
解码图像缓存(DPB)是真正管理与存储图像数据的地方,凡是已经解码的图像,如果需要存储在内存中,都需要通过DPB进行管理,基本操作包括对解码图像的插入与删除。在JM中有个dpb结构体表示解码图像缓存。
//! Decoded Picture Buffer
typedef struct decoded_picture_buffer
{
FrameStore **fs; //参考图像列表,该指针主要用于管理一帧图像,其中包括一个帧与两个场,其成员frame管理帧,top_field与bottom_field会用于管理场
FrameStore **fs_ref; //短期参考图像列表(当然,在这里是未经排序的)
FrameStore **fs_ltref; //长期参考图像列表
unsigned size; //dpb最大容量,能容得下多少帧,最大不超过16
unsigned used_size; //dpb实际存了多少帧,以帧为单位,如果只有其中一场,也被看做一帧
unsigned ref_frames_in_buffer;//chj fs中用做short-term reference的帧的个数,短期参考帧,遇到下个I(IDR)会清空缓存
unsigned ltref_frames_in_buffer; //长期参考帧个数
int last_output_poc; //上一个从dpb输出的poc序号
int max_long_term_pic_idx; //最大长期参考帧个数 int init_done;//是否已经初始化dpb FrameStore *last_picture;//如果当前帧为场编码,则用于存其中一场,等待下一场编码完成后合并
} DecodedPictureBuffer;
DPB最大长度
当然DPB的大小也是有限制的,标准附件A就对DPB大小做出了限定:MaxDpbSize = Min( 1024 * MaxDPB / ( PicWidthInMbs * FrameHeightInMbs * 384 ), 16 )
——标准附件A.3.2
DPB管理策略
另外DPB对解码图像的存储有个策略:更倾向于存储对后面编码有用的图像(也就是参考图像)。
虽然说DPB中也可以存储非参考图像,在DPB没满的时候,会无差别地把参考图像与非参考图像一并插入DPB中;
但是一旦DPB满了之后:
如果新重建的图像为参考图像,该参考图像需要插入DPB
- 如果DPB中没有非参考图像,会按照滑动窗口模式把DPB序号最小的参考图像移除; (1)
- 如果DPB中存在非参考图像,会把DPB中已经输出到磁盘的非参考图像移除; (2)
如果新重建的图像为非参考图像
- 如果DPB中不存在比当前图像POC更小的非参考图像,当前非参考图像会被直接输出到磁盘,而不插入DPB; (3)
- 如果DPB中存在比当前图像POC更小的非参考图像,会把DPB中POC最小的参考图像移除,插入当前非参考图像;(4)
可以对应下面代码中的四段:
void store_picture_in_dpb(StorablePicture* p)
{
...
// sliding window, if necessary (1)
if ((!img->currentPicture->idr_flag)&&(p->used_for_reference && (!img->adaptive_ref_pic_buffering_flag)))
{
sliding_window_memory_management(p);
} // first try to remove unused frames (2)
if (dpb.used_size==dpb.size)
{
remove_unused_frame_from_dpb();
} // then output frames until one can be removed
while (dpb.used_size==dpb.size)
{
// non-reference frames may be output directly(3)
if (!p->used_for_reference)
{
get_smallest_poc(&poc, &pos);
if ((-1==pos) || (p->poc < poc))
{
direct_output(p, p_dec);
return;
}
}
// flush a frame(4)
output_one_frame_from_dpb();
}
insert_picture_in_dpb(dpb.fs[dpb.used_size],p);
...
}
h.264参考图像列表、解码图像缓存的更多相关文章
- 【H.264/AVC视频编解码技术具体解释】十三、熵编码算法(4):H.264使用CAVLC解析宏块的残差数据
<H.264/AVC视频编解码技术具体解释>视频教程已经在"CSDN学院"上线,视频中详述了H.264的背景.标准协议和实现,并通过一个实战project的形式对H.2 ...
- h.264语法结构分析
NAL Unit Stream Network Abstraction Layer,简称NAL. h.264把原始的yuv文件编码成码流文件,生成的码流文件就是NAL单元流(NAL unit Stre ...
- FFmpeg的H.264解码器源代码简单分析:熵解码(Entropy Decoding)部分
===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...
- 【视频编解码·学习笔记】3. H.264视频编解码工程JM的下载与编解码
一.下载JM工程: JM是H.264标准制定团队所认可的官方参考软件.网址如下 http://iphome.hhi.de/suehring/tml/ 从页面中可找到相应的工程源码,本次选择JM 8.6 ...
- FFmpeg的H.264解码器源代码简单分析:宏块解码(Decode)部分-帧间宏块(Inter)
===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...
- H.264视频在android手机端的解码与播放(转)
随着无线网络和智能手机的发展,智能手机与人们日常生活联系越来越紧密,娱乐.商务应用.金融应用.交通出行各种功能的软件大批涌现,使得人们的生活丰富多彩.快捷便利,也让它成为人们生活中不可取代的一部分.其 ...
- H.264视频编解码SoC满足高清DVR设计需求
硬盘录像机(DVR)作为监控系统的核心部件之一,在10年里高速发展,从模拟磁带机的替代品演变成具有自己独特价值的专业监控数字平台,并被市场广泛接受.监控系统伴随DVR这些年的发展向着IP化.智能化发展 ...
- (转载)H.264码流的RTP封包说明
H.264的NALU,RTP封包说明(转自牛人) 2010-06-30 16:28 H.264 RTP payload 格式 H.264 视频 RTP 负载格式 1. 网络抽象层单元类型 (NALU) ...
- h.264直接预测
直接预测是B帧上一种独有的预测方式,其中直接预测又分为两种模式: 时域直接模式(temporal direct).空域直接模式(spatial direct). 在分析这两种模式之前,有一个前提概念需 ...
随机推荐
- Linux下生产者与消费者的线程实现
代码见<现代操作系统> 第3版. 为了显示效果,添加了printf()函数来显示运行效果 #include<stdio.h> #include<pthread.h> ...
- ALS数学点滴
其中,$n_{u_i}$表示用户$i$评分的电影数目,$n_{m_j}$表示对电影$j$评分的用户数目.设$I_i$表示用户$i$所评分的电影集合,则$n_{u_i}$是$I_i$的基数,同样的,$I ...
- HDU1097 A hard puzzle
A hard puzzle Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) To ...
- PermGen space错误解决方法
在看下文之前,首先要确认意见事情,就是你是怎样启动tomcat的,我们在平时的开发环境其中,都是通过startup.bat方式启动tomcat的,那么你依照以下的方式,去改动/bin/catalina ...
- Linux后台开发面试问题汇总
个人从事安全后台开发,当然是linux环境下的了.举几个常见的问题.1. 数据结构基础.比如实现一个最简单的哈希表.2. 操作系统基础.linux进程模型,堆/栈的区别,大概的位置,各往哪个方向生长, ...
- java中的泛型(转)
什么是泛型? 泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型的一个 ...
- yii phpexcel <转>
原文详情参见 这里 1.下载phpexcel,将压缩包中的classes复制到protected/extensions下并修改为PHPExcel. 2.修改YII配置文件config/main.php ...
- Day12 - 堡垒机开发
Python之路,Day12 - 那就做个堡垒机吧 本节内容 项目实战:运维堡垒机开发 前景介绍 到目前为止,很多公司对堡垒机依然不太感冒,其实是没有充分认识到堡垒机在IT管理中的重要作用的,很多 ...
- Linux Stu
指定命令别名 alias ..='cd ..' 命令连接符 持续的执行命令,不管错误 [命令1]; [命令2]; [命令3]; 前一个正确才执行下一个 [命令1] && [命令2] ...
- 安卓开发入门之activity
安卓开发主要用到的是java语言,对于一个activity,自己写的程序可以继承至Activity,该Activity先会运行一个叫 onCreat()的类,可以在其中申明一些初始化的函数等,这个函数 ...