Chinaunix首页 | 论坛 | 博客
  • 博客访问: 176054
  • 博文数量: 24
  • 博客积分: 1116
  • 博客等级: 少尉
  • 技术积分: 342
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-20 19:15
文章分类

全部博文(24)

文章存档

2011年(20)

2010年(4)

我的朋友

分类: LINUX

2011-07-09 17:07:57

H.264 的句法和语义
在编码器输出的码流中,数据的基本单位是句法元素,每个句法元素由若干比特组成,它表示某个特定的物理意义,例如:宏块类型、量化参数等。句法表征句法元素的组织结构,语义阐述句法元素的具体含义。所有的视频编码标准都是通过定义句法和语义来规范编解码器的工作流程。
句法元素的分层结构
编码器输出的比特码流中,每个比特都隶属某个句法元素,也就是说,码流是由一个个句法元素依次衔接组成的,码流中除了句法元素并不存在专门用于控制或同步的内容。在H.264 定义的码流中,句法元素被组织成有层次的结构,分别描述各个层次的信息。
句法元素的分层结构有助于更有效地节省码流。例如,在一个图像中,经常会在各个片之间有
相同的数据,如果每个片都同时携带这些数据,势必会造成码流的浪费。更为有效的做法是将该图
像的公共信息抽取出来,形成图像一级的句法元素,而在片级只携带该片自身独有的句法元素。
H.264 中,句法元素共被组织成 序列、图像、片、宏块、子宏块五个层次。
H.264 的分层结构是经过精心设计的,与以往的视频编码标准相比有很大的改进,这些改进主
要针对传输中的错误掩藏,在有误码发生时可以提高图像重建的性能。在以往的标准中,分层的组
织结构如图7.2,它们如同TCP/IP 协议的结构,每一层都有头部,然后在每层的数据部分包含该层
的数据。
在这样的结构中,每一层的头部和它的数据部分形成管理与被管理的强依赖关系,头部的句法
元素是该层数据的核心,而一旦头部丢失,数据部分的信息几乎不可能再被正确解码出来。尤其在
序列层及图像层,由于网络中MTU(最大传输单元)大小的限制,不可能将整个层的句法元素全部
放入同一个分组中,这个时候如果头部所在的分组丢失,该层其他分组即使能被正确接收也无法解
码,造成资源浪费。(应该可以这样理解,也就是说,一个图像层作为一个单元被交付给IP层,但是被分割成了几个分组,如果图像层的头部信息的分组丢失,那么这个图像层的所有片都无法被解码)




H.264 中,分层结构最大的不同是取消了序列层和图像层,并将原本属于序列和图像头部的
大部分句法元素游离出来形成序列和图像两级参数集,其余的部分则放入片层。参数集是一个独立
的数据单位,不依赖于参数集外的其他句法元素。图7.3 描述了参数集与参数集外句法元素的关系,
在图中我们可以看到,参数集只是在片层句法元素需要的时候被引用,而且,一个参数集并不对应
某个特定的图像或序列,同一个序列参数集可以被多个序列中的图像参数集引用,同理,同一个图
像参数集也可以被多个图像引用。只在编码器认为需要更新参数集的内容时,才会发送出新的参数
集。在这种机制下,由于参数集是独立的,可以被多次重发或者采用特殊技术加以保护。


在图7.3 的描述中,参数集与参数集外部的句法元素处于不同信道中,这是H.264 的一个建议,
我们可以使用更安全但成本更昂贵的通道来传输参数集,而使用成本低但不够可靠的信道传输其他
句法元素,只需要保证片层中的某个句法元素需要引用某个参数集时,那个参数集已经到达解码器,
也就是参数集在时间上必须先被传送。当然,在条件不允许的情况下,我们也可以采用妥协的办法:
在同一个物理信道中传输所有的句法元素,但专门为参数集采用安全可靠的通信协议,如TCP。
然,H.264 也允许我们为包括参数集在内的所有句法元素指定同样的通信协议,但这时所有参数集
必须被多次重发,以保证解码器最终至少能接收到一个。在参数集和片使用同个物理信道的情况下,
图7.3 中的信道1 和信道2 应该被理解为逻辑上的信道,因为从逻辑上看,参数集与其它句法元素
还是处于各自彼此独立的信道中。
H.264 在片层增加了新的句法元素指明所引用的参数集的编号,同时因为取消了图像层,片成
为了信道2 中最上层的独立的数据单位,每个片必须自己携带关于所属图像的编号、大小等基本信
息,这些信息在同一图像的每个片中都必须是一致的。在编码时,H.264 的规范要求将参数集、片
这些独立的数据单位尽可能各自完整地放入一个分组中被传送。
从表面上看来,H.264 关于参数集和片层的结构增加了编码后数据的冗余度(比如参数集必须
多次重发,又如每个片都必须携带一部分相同的关于整个图像的信息,而这些数据完全是重复的),
降低了编码效率,但这些技术的采用使得通信的鲁棒性大大增强,当数据传输中出现丢包,能够将
使错误限制在最小范围,防止错误的扩散,解码后对错误的掩藏和恢复也能起到很好的作用。 一个
片的丢失将不会影响其它片的解码,还可以通过该片前后的片来恢复该片的数据。
H.264 片层以下的句法元素的结构大体上和以往标准类似,但在相当多的细节上有所改进,所
有的改进的目的不外乎两个:在错误发生时防止错误扩散、减少冗余信息提高编码效率。这两者往
往是矛盾的,H.264 在这两者上的取舍显得颇具匠心。
图7.3 所示的码流的结构是一种简化的模型,这个模型已经能够正确工作,但还不够完善,不
适合复杂的场合。在复杂的通信环境中,除了片和参数集外还需要其他的数据单位来提供额外的信
息。图7.4 描述了在复杂通信中的码流中可能出现的数据单位。如前文所述,参数集可以被抽取出
来使用其它信道。


(更正:看来一个片不适合作为一个分组,可能太小了。应该是以这里的数据单元为一个分组的?)
在图7.4 中我们看到,一个序列的第一个图像叫做IDR 图像(立即刷新图像),IDR 图像都是I
图像。H.264 引入IDR 图像是为了解码的重同步,当解码器解码到IDR 图像时,立即将参考帧队列
清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。这样,如果在前一
个序列的传输中发生重大错误,如严重的丢包,或其他原因引起数据错位,在这里可以获得重新同
步。IDR 图像之后的图像永远不会引用IDR 图像之前的图像的数据来解码。
要注意IDR 图像和I 图像的区别,IDR 图像一定是I 图像,但I 图像不一定是IDR 图像。一个
序列中可以有很多的I 图像,I 图像之后的图像可以引用I 图像之间的图像做运动参考。
在图7.4 中,除了参数集与片外还有其它的数据单位,这些数据单位可以提供额外的数据或同
步信息,这些数据单位也是一系列句法元素的集合。它们在解码过程中不是必需的,但却可以适当
提高同步性能或定义图像的复杂特征。
插入:关于I,P片的概念

宏块、片

一个编码图像通常划分成若干宏块组成,一个宏块由一个16×16 亮度像素和附加的一个8×8 Cb和一个8×8 Cr 彩色像素块组成。每个图象中,若干宏块被排列成片的形式。

I 片只包含I 宏块,P 片可包含P 和I 宏块,而B 片可包含B 和I 宏块。

I 宏块利用从当前片中已解码的像素作为参考进行帧内预测(不能取其它片中的已解码像素作为参考进行帧内预测)。

P 宏块利用前面已编码图象作为参考图象进行帧内预测,一个帧内编码的宏块可进一步作宏块的分割:即16×16、16×8、8×16 或8×8 亮度像素块(以及附带的彩色像素);如果选了8×8 的子宏块,则可再分成各种子宏块的分割,其尺寸为8×8、8×4、4×8 或4×4 亮度像素块(以及附带的彩色像素)。

B 宏块则利用双向的参考图象(当前和未来的已编码图象帧)进行帧内预测。

档次和级

H.264 规定了三种档次,每个档次支持一组特定的编码功能,并支持一类特定的应用。

1)基本档次:利用I 片和P 片支持帧内和帧间编码,支持利用基于上下文的自适应的变长编码

进行的熵编码(CAVLC)。主要用于可视电话、会议电视、无线通信等实时视频通信;

2)主要档次:支持隔行视频,采用B 片的帧间编码和采用加权预测的帧内编码;支持利用基于

上下文的自适应的算术编码(CABAC)。主要用于数字广播电视与数字视频存储;

3)扩展档次:支持码流之间有效的切换(SP 和SI 片)、改进误码性能(数据分割),但不支持

隔行视频和CABAC。


以下是解码时的知识,现在先不用考虑。

句法的表示方法
句法元素与变量
编码器将数据编码为句法元素然后依次发送。在解码器端,通常要将句法元素作求值计算,得
出一些中间数据,这些中间数据就是H.264 定义的变量。如图7.5:




图7.5 从句法元素解出变量
图中,pic_width_in_mbs_minus1 是解码器直接从码流中提取的句法元素,这个句法元素表征图
像的宽度,以宏块为单位。我们看到,为了提高编码效率,H.264 将图像实际的宽度减去1 后再传
送。
PicWidthInMbs = pic_width_in_mbs_minus1 1
PicWidthInSamplesL = PicWidthInMbs * 16
PicWidthInSamplesC = PicWidthInMbs * 8
以上变量PicWidthInMbs 表示图像以宏块为单位的宽, 变量PicWidthInSamplesL 、
PicWidthInSamplesC 分别表示图像的亮度、色度分量以像素为单位的宽。H.264 定义这些变量是因
为在后续句法元素的提取算法或图像的重建中需要用到它们的值。在H.264 中,句法元素的名称是
由小写字母和一系列的下划线组成,而变量名称是大小写字母组成,中间没有下划线。(根据句法元素可以求解句法变量)
句法
...
语义
 NAL层语义
在网络传输的环境下,编码器将每个NAL 各自独立、完整地放入一个分组,由于分组都有头部,
解码器可以很方便地检测出NAL 的分界,依次取出NAL 进行解码。为了节省码流,H.264 没有另
外在NAL 的头部设立表示起始的句法元素,我们从表7.1 可以看到这点。但是如果编码数据是储存
在介质(如DVD 光盘)上,由于NAL 是依次紧密排列,解码器将无法在数据流中分辨每个NAL
的起始和终止,所以必须要有另外的机制来解决这个问题。
针对这个问题,H.264 草案的附录B 中指明了一种简单又高效的方案。当数据流是存储在介质
上时,在每个NAL 前添加起始码:
0x000001
在某些类型的介质上,为了寻址的方便,要求数据流在长度上对齐,或必须是某个常数的倍数。考
虑到这种情况,H.264 建议在起始码前添加若干字节的0 来填充,直到该NAL 的长度符合要求。
在这样的机制下,解码器在码流中检测起始码,作为一个NAL 的起始标识,当检测到下一个
起始码时当前NAL 结束。H.264 规定当检测到0x000000 时也可以表征当前NAL 的结束,这是因为
连着的三个字节的0 中的任何一个字节的0 要么属于起始码要么是起始码前面添加的0。
添加起始码是一个解决问题的很好的方法,但上面关于起始码的介绍还不完整,因为忽略了一
个重要的问题:如果在NAL 内部出现了0x000001 或是0x000000 的序列怎么办?毫无疑问这种情
况是致命的,解码器将把这些本来不是起始码的字节序列当作起始码,而错误地认为这里往后是一
个新的NAL 的开始,进而造成解码数据的错位!而我们做的大量实验证明,NAL 内部经常会出现
这样的字节序列。
于是H.264 提出了另外一种机制,叫做“防止竞争”,在编码器编码完一个NAL 时,应该检测是
否出现图7.6 左侧 中的四个字节序列,以防止它们和起始码竞争。如果检测到这些序列存在,编
码器将在最后一个字节前插入一个新的字节:0x03,从而使它们变成图7.6 右测的样子。当解码器
在NAL 内部检测到有0x000003 的序列时,将把0x03 抛弃,恢复原始数据。
图7.6 NAL 内部为防止与起始码竞争插入 0x03
图7.6 中的前两个序列我们前文中已经提到,第三个0x000002 是作保留用,而第四个0x000003
是为了保证解码器能正常工作,因为我们刚才提到,解码器恢复原始数据的方法是检测到0x000003
就抛弃其中的0x03,这样当出现原始数据为0x000003 时会破坏数据,所以必须也应该给这个序列
插入0x03。
我们可以从句法表7.1 中看到解码器在NAL 层的处理步骤,其中变量NumBytesInNALunit 是
解码器计算出来的,解码器在逐个字节地读一个NAL 时并不同时对它解码,而是要通过起始码机
制将整个NAL 读进、计算出长度后再开始解码。
接下来的部分就非常重要了,句法元素的语义。想要理解一个编码器输出的码流,就需要熟悉这些。
forbidden_zero_bit 等于0
nal_ref_idc 指示当前NAL 的优先级。取值范围为0-3, ,值越高,表示当前NAL 越重要,需要优 先受到保护。H.264 规定如果当前NAL 是属于参考帧的片,或是序列参数集,或是图像 参数集这些重要的数据单位时,本句法元素必须大于0。但在大于0 时具体该取何值,却没 有进一步规定,通信双方可以灵活地制定策略。
nal_unit_type 指明当前NAL unit 的类型,具体类型的定义如表7.20:
表7.20 nal_uint_type 语义
nal_unit_type NAL类型 C
0 未使用
1 不分区、非IDR 图像的片 2, 3, 4
2 片分区A 2
3 片分区B 3
4 片分区C 4
5 IDR 图像中的片 2, 3
6 补充增强信息单元(SEI) 5
7 序列参数集 0
8 图像参数集 1
9 分界符 6
10 序列结束 7
11 码流结束 8
12 填充 9
13..23 保留
24..31 未使用
nal_unit_type=5 时,表示当前NAL 是IDR 图像的一个片,在这种情况下,IDR 图像中的每个片的nal_unit_type 都应该等于5。注意IDR 图像不能使用片分区。
rbsp_byte[i] RBSP 的第i 个字节。RBSP 指原始字节载荷,它是NAL 单元的数据部分的封装格式,
封装的数据来自SODB(原始数据比特流)。SODB 是编码后的原始数据,SODB 经封装为RBSP 后
放入NAL 的数据部分。下面介绍一个RBSP 的生成顺序。
从SODB 到RBSP 的生成过程:
- 如果SODB 内容是空的,生成的RBSP 也是空的
- 否则,RBSP 由如下的方式生成:
1) RBSP 的第一个字节直接取自SODB 的第1 到8 个比特,(RBSP 字节内的比特按照从左到
右对应为从高到低的顺序排列,most significant),以此类推,RBSP 其余的每个字节都直接
取自SODB 的相应比特。RBSP 的最后一个字节包含SODB 的最后几个比特,及如下的
rbsp_trailing_bits()
2) rbsp_trailing_bits()的第一个比特是1,接下来填充0,直到字节对齐。(填充0 的目的也是为了
字节对齐)
3) 最后添加若干个cabac_zero_word(其值等于0x0000)
emulation_prevention_three_byte NAL 内部为防止与起始码竞争而引入的填充字节 ,值为0x03。
阅读(2576) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~