【H.264/AVC 句法和语义详解】(十二):H264中的帧场编码模式详解

原文链接:https://mp.weixin.qq.com/s/i0vqlXj_z796Axb5CA_wgg

上篇中,我们已经解析出了Slice_Header的句法元素,下面我们就可以根据前面已经解析出的SPS、PPS、Slice_Header的句法元素,来做一些准备性的工作。

我们首先要做的,就是要确定当前Slice所采用的编码方式,即当前Slice进行的是帧编码还是场编码,如果是帧编码,又是不是宏块级的帧场自适应?如果是场编码,那么当前Slice是作为顶场,还是底场?

为了要彻底弄清楚这些问题,我们先来介绍下,上面讨论的帧场及帧场自适应到底是什么意思。为此呢,我们就先来看下,什么是逐行扫描(progressive scan)和隔行扫描(interlaced scan)?

1、逐行扫描和隔行扫描

我们曾在【显示器是如何显示图形数据的】中,介绍过逐行扫描,并在【彩色电视制】中略提过隔行扫描。但是无论从显示器的角度,还是从电视机的角度,我们都是站在显示端(接收端)的角度,来看待逐行扫描和隔行扫描的。

事实上,逐行扫描和隔行扫描从最初的摄像端,到中间的逐行/隔行扫描信号传输,再到最后的显示端,它们的问题一直存在。在最原始的时候,从摄像端选择用隔行还是逐行采集图像之后,扫描方式在中间的信号传输,直到显示在屏幕上,都不会发生改变。

而科技发展到现在,可能最开始逐行扫描采集的视频,传输的时候转换成了隔行扫描,显示的时候又变成了逐行扫描。总之,隔行或逐行扫描的视频,它们之间是可以通过编码和解码相互转换的。

为了方便理解,我们画一个,从摄像到显示的最简单的一个模型图来看下:

在这里插入图片描述
从采集到显示

如果你乍一看这个流程有点陌生,请联系目前非常火爆的手机直播。左边摄像机就相当于主播的手机,主播打开摄像头采集图像,通过编码器实时编码后,图像通过网络传输到服务器,然后服务器将播放地址下发到用户端。此刻我们拿到播放地址进行播放,实时解码并显示在手机屏幕上。

与之类似,电视直播、网络在线视频也是同样的道理。

讲这一段是因为,我们要注意到,我们今天要讲的帧场编码模式,其实主要发生在上图左侧,也即摄像机采集图像然后进行编码的阶段。下面我们会体会到,站在摄像机的角度来理解逐行/隔行的编码方式非常好理解。

而为了引入逐行和隔行扫描这两个概念,我们还是先从右侧的显示端开始讲起。

1.1 逐行扫描

逐行扫描应用广泛,在我们目前的各类电脑显示屏、外接的显示器上,使用的都是逐行扫描。逐行扫描非常易于理解,它就是在光栅图像下,从图像的第一行顺序扫描到最后一行,也即下图:

在这里插入图片描述
原始图像(左) 和 逐行扫描帧(右)

逐行扫描的结果,就是产生完整的一帧图像,因此它进行的通常是帧编码。逐行扫描虽然现在应用广泛,但是在最初电视机刚刚兴起,还没有电脑的时代,逐行扫描确实是不实用的。主要有以下两点原因:

  • (1)逐行扫描产生的完整的一帧图像,占据的电视信号的带宽较大
  • (2)在【彩色电视制】中我们知道,PAL制的帧频为25帧每秒,NTSC的为30帧每秒。在这种情况下,如果采用逐行扫描的方式,人眼是会察觉到闪烁的。

因此聪明的人们想到,可以将一帧图像先扫描奇数场显示在屏幕上,然后再扫描偶数场。这样在相同的时间内,PAL制每秒可显示50场,NTSC可显示60场,这样既降低了带宽,又解决了闪烁的问题,隔行扫描也就这样产生了。

1.2 隔行扫描

就像刚才说的那样,隔行扫描是先扫描奇数场,然后扫描偶数场,也即顶场和底场。如下图:

在这里插入图片描述
隔行扫描的经典之处,就在这个先后上!

可以想象,当从第1行隔行扫到15行,扫描完顶场后,又重头开始扫底场,也即回到第2行开始扫,这中间是有个时间差的。这个时间差,如果站在摄像机采集图像的立场来理解,那么就是接下来理解隔行扫描下的视频编码方式的关键。

上面我们说,逐行扫描一般进行的是帧编码,而隔行扫描,则可以在帧编码和场编码之间做选择,那选择的依据是什么呢?接着往下看。

2、帧编码和场编码

现在我们就要回到,摄像机采集图像到编码器的阶段了。我们已经知道,摄像机采集图像的扫描方式有两种,但是因为隔行扫描的出现,它们的编码方式却不只两种。我们先来介绍最容易想到的情况,那就是针对一部视频,全部使用帧编码或全部使用场编码。

一般而言,对逐行扫描的视频全部进行帧编码。对隔行扫描的视频,就要选择是使用帧编码还是场编码,注意这里是只选择一次,然后整个视频序列都采用帧编码或场编码。如果隔行扫描采用帧编码,则是将顶场和底场合为一帧进行编码。

此时对于帧编码,在做帧间预测时,参考图像为帧图像,运动补偿也为帧运动补偿。对于场编码,则是两个场分别进行编码,在做帧间预测时,参考图像为场图像,运动补偿也为场运动补偿。

以目前二者应用的场合来看:

帧编码应用广泛,因为目前我们无论在手机端录的视频、还是在电脑端、显示器上播放的视频,基本都是逐行扫描。因此在我们这个系列教程里,很有可能并不会涉及到场编码。

由隔行扫描引出的场编码,多应用在电视业,所以对于从事互联网的人士来说,几乎碰不到场编码的情况。

不过即使如此,我们还是要继续介绍一下隔行扫描的编码方式,因为这样便于我们接下来理解H264中的几个重要的句法元素。这里我们介绍隔行扫描的前提,是整个视频序列只采用一种编码方式。

事实上,在整个视频编码中,编码方式的选择可以更灵活,这从我们上篇解析到的片层Slice_Header的field_pic_flag和bottom_field_flag就可以体会到。

3、 PAFF和MBAFF

【注】(Picture adaptive field-frame 和 Macro-block adaptive field-frame)

第2小节中,我们说的是整个视频序列只采用一种编码方式。但实际上对于隔行扫描的视频,我们还可以更灵活,通常可以分为以下三种编码方式:

  • (1)将两个场合为一帧进行帧编码
  • (2)两个场分别进行场编码
  • (3)将两个场合为一帧,但是在宏块级别上,两个上下相邻的场宏块组合成一个宏块对(MB pair,MBP),这种编码方式也称宏块级的帧场自适应,也即MBAFF,这是H264引入的一个新的编码特性。

其中前两点,不同于第2节的是,第1点和第2点是可以进行自适应切换的。也即在一个视频序列中,通过对编码效果的不同,选择是使用帧编码还是场编码,因此这种方式也称图像级的帧场自适应(PAFF)

这就是PAFF和MBAFF,下面我们更加细致一点来说明,为什么有了PAFF还需要MBAFF。

3.1 使用PAFF进行编码

在隔行扫描中,一个视频序列的运动图像部分,和非运动图像部分的编码,区别是很大的。

在开始时我们就说过,摄像机在进行隔行扫描时,顶场和底场的扫描时间上,是有一个时间差的。这个时间差是什么呢?就是当摄像机扫描完顶场的最后一行后,又回过头来扫描底场的第一行。

这样就导致,如果将顶场和底场合为一帧,那么顶场的第一行和底场的第一行就变成了相邻行,而实际上相邻行的扫描时间,整整差了一个场的扫描时间。

这对于非运动图像没什么,但是对于一个正在运动的图像而言,会导致合为一帧后,相邻两行的空间相关性比较低。所以对于运动的图像,两个场分别进行编码,相对合为一帧来编码更加节省码流。

对于非运动图像,两个场合为一帧后,空间相关性也很大,因此合为一帧进行帧编码更加节省码流。

这就是PAFF的工作方式,它会根据编码效果,调整整个图像的编码方式。

但这同时也变成了它的缺点,因为调整整个图像的编码方式,是个粒度非常粗的工作方式。对于视频序列而言,可能某些图像,同时存在运动区域和非运动区域,这时PAFF的缺点就很明显了,对这些图像使用帧编码或场编码似乎都不恰当,这时MBAFF就产生了。

3.2 使用MBAFF进行编码

MBAFF将两个场合为一帧,但是在宏块级别上,使用宏块对(MBAFF)为基本编码单元。这样在图像的运动区域,采用场编码,对于非运动区域,采用帧编码,比较两种方式产生的码流大小,就能在宏块级别选择更节省码流的编码方式。

4、H264中的帧场编码模式

前面三节,都是为这一节做铺垫。我们已经知道了,编码方式有帧编码、场编码、图像级的帧场自适应编码和宏块级的帧场自适应编码,看起来真的挺麻烦,那是因为我们没确定我们的立场。

当我们把立场放在Slice上,一个Slice的编码方式就只有三种:帧编码、场编码或MBAFF。

这就需要用到几个句法元素,分别为:SPS中的frame_mbs_only_flagmb_adaptive_frame_field_flag,Slice_Header中的field_pic_flagbottom_field_flag。具体流程如下:

在这里插入图片描述
H264帧场编码模式判断

判断出当前Slice的编码方式后,如果是宏块级的帧场自适应,通常用字段MbaffFrameFlag等于1来表示,这也是我们在理解Slice_Header中first_mb_in_slice的语义时需要用到的字段。

写到这里,很多人会问,编码模式是计算出来了,但我怎么知道它是逐行扫描还是隔行扫描呢?其实这已经比较能明显推断出来了,frame_mbs_only_flag等于1就代表逐行扫描,否则代表隔行扫描。当然这也是我个人的一个推断,因为资料较少也没找到地儿去验证,如果不对还请指正。

5、计算视频属性

根据句法元素判断出编码模式之后,就可以去计算视频的帧宽高、图像(帧/场)宽高了。

5.1 帧宽高

//(1)frame_width_in_mbs(以宏块为单位的帧/场宽,也是视频的宽度)
frame_width_in_mbs = sps->pic_width_in_mbs_minus1 + 1
//(2)frame_height_in_mbs(以宏块为单位的帧高度,也是视频的高度)
frame_height_in_mbs = (2 - sps->frame_mbs_only_flag) *
(sps->pic_height_in_map_units_minus1 + 1)
//(3)frame_size_in_mbs(以宏块为单位的帧尺寸,也是视频的尺寸)
frame_size_in_mbs = frame_width_in_mbs * frame_height_in_mbs
//(4)frame_width(以像素为单位的帧/场宽,也是视频的宽度)
frame_width = frame_width_in_mbs * 16
//(5)frame_height(以像素为单位的帧高,也是视频的高度)
frame_height_in_mbs * 16
//(6)frame_width_chroma(色度所占的帧宽,假设采用格式为4:2:0)
frame_width_chroma = frame_width / 2
//(7)frame_height_chroma(色度所占的帧高,假设采用格式为4:2:0)
frame_height_chroma = frame_height / 2

5.2 图像宽高

对当前图像而言,在不知道它是帧或场的情况下,只有其高度才能受影响。

//(1)pic_height_in_mbs(以宏块为单位的图像高度)
pic_height_in_mbs = frame_height_in_mbs / (1 + field_pic_flag)
//(2)pic_size_in_mbs(以宏块为单位的图像尺寸)
pic_size_in_mbs = frame_width_in_mbs * pic_height_in_mbs
展开阅读全文

没有更多推荐了,返回首页