精华内容
下载资源
问答
  • 第四章 视频数据的压缩编码 H系列标准和MPEG标准为活动图像压缩... 数据压缩的倍数、重建图像质量的客观评价、视频压缩的率失真性能、重建图像的主观评价 3、JPEG和JPEG2000的不同点? P110  高效的编码方法、R

    第四章     视频数据的压缩编码

    H系列标准和MPEG标准为活动图像压缩标准;JBIG和JPEG标准为静止图像压缩标准。

    1、JPEG的四种运行模式: P109


    2、压缩编码算法的评价机制?   P107

       数据压缩的倍数、重建图像质量的客观评价、视频压缩的率失真性能、重建图像的主观评价

    3、JPEG和JPEG2000的不同点?   P110

       高效的编码方法、RIO编码、可伸缩性编码


    1、 MPEG-1 、-2 、H.261、H.263的概念,剩下的以了解为主? P110

    MPEG-1:速率为1.5M/S,分为三个部分:视频、声音、系统。

    MPEG-2:是1的扩展,同样包括三个部分。

    MPEG-4:基于对象的编码,应用在低速的系统中,引入了视频对象,基于纹理、形状、运动三种特征。

    2、 MPEG-1将图像编码帧分为三类/视频中采用了哪三种图像:解释出各种帧。


      P帧可以做不可以做,区别?

    3、 视频流的数据分层/视频帧分成什么样的大小/码流结构?P91

    序列层>GOP层>图像层>条层>宏块层>块层

    展开全文
  • 视频压缩编码和音频压缩编码的基本原理

    万次阅读 多人点赞 2014-06-03 00:01:20
    本文介绍一下视频压缩编码和音频压缩编码的基本原理。其实有关视频和音频编码的原理的资料非常的多,但是自己一直也没有去归纳和总结一下,在这里简单总结一下,以作备忘。

    本文介绍一下视频压缩编码和音频压缩编码的基本原理。其实有关视频和音频编码的原理的资料非常的多,但是自己一直也没有去归纳和总结一下,在这里简单总结一下,以作备忘。

    1.视频编码基本原理

    (1)  视频信号的冗余信息

    以记录数字视频的YUV分量格式为例,YUV分别代表亮度与两个色差信号。例如对于现有的PAL制电视系统,其亮度信号采样频率为13.5MHz;色度信号的频带通常为亮度信号的一半或更少,为6.75MHz或3.375MHz。以4:2:2的采样频率为例,Y信号采用13.5MHz,色度信号U和V采用6.75MHz采样,采样信号以8bit量化,则可以计算出数字视频的码率为:

    13.5*8 + 6.75*8 + 6.75*8= 216Mbit/s

    如此大的数据量如果直接进行存储或传输将会遇到很大困难,因此必须采用压缩技术以减少码率。数字化后的视频信号能进行压缩主要依据两个基本条件:

    l  数据冗余。例如如空间冗余、时间冗余、结构冗余、信息熵冗余等,即图像的各像素之间存在着很强的相关性。消除这些冗余并不会导致信息损失,属于无损压缩。

    l  视觉冗余。人眼的一些特性比如亮度辨别阈值,视觉阈值,对亮度和色度的敏感度不同,使得在编码的时候引入适量的误差,也不会被察觉出来。可以利用人眼的视觉特性,以一定的客观失真换取数据压缩。这种压缩属于有损压缩。

    数字视频信号的压缩正是基于上述两种条件,使得视频数据量得以极大的压缩,有利于传输和存储。一般的数字视频压缩编码方法都是混合编码,即将变换编码,运动估计和运动补偿,以及熵编码三种方式相结合来进行压缩编码。通常使用变换编码来消去除图像的帧内冗余,用运动估计和运动补偿来去除图像的帧间冗余,用熵编码来进一步提高压缩的效率。下文简单介绍这三种压缩编码方法。

    (2)  压缩编码的方法

    (a)  变换编码

    变换编码的作用是将空间域描述的图像信号变换到频率域,然后对变换后的系数进行编码处理。一般来说,图像在空间上具有较强的相关性,变换到频率域可以实现去相关和能量集中。常用的正交变换有离散傅里叶变换,离散余弦变换等等。数字视频压缩过程中应用广泛的是离散余弦变换。

    离散余弦变换简称为DCT变换。它可以将L*L的图像块从空间域变换为频率域。所以,在基于DCT的图像压缩编码过程中,首先需要将图像分成互不重叠的图像块。假设一帧图像的大小为1280*720,首先将其以网格状的形式分成160*90个尺寸为8*8的彼此没有重叠的图像块,接下来才能对每个图像块进行DCT变换。

    经过分块以后,每个8*8点的图像块被送入DCT编码器,将8*8的图像块从空间域变换为频率域。下图给出一个实际8*8的图像块例子,图中的数字代表了每个像素的亮度值。从图上可以看出,在这个图像块中各个像素亮度值比较均匀,特别是相邻像素亮度值变化不是很大,说明图像信号具有很强的相关性。


    一个实际8*8图像块

    下图是上图中图像块经过DCT变换后的结果。从图中可以看出经过DCT变换后,左上角的低频系数集中了大量能量,而右下角的高频系数上的能量很小。


    图像块经过DCT变换后的系数

    信号经过DCT变换后需要进行量化。由于人的眼睛对图像的低频特性比如物体的总体亮度之类的信息很敏感,而对图像中的高频细节信息不敏感,因此在传送过程中可以少传或不传送高频信息,只传送低频部分。量化过程通过对低频区的系数进行细量化,高频区的系数进行粗量化,去除了人眼不敏感的高频信息,从而降低信息传送量。因此,量化是一个有损压缩的过程,而且是视频压缩编码中质量损伤的主要原因。

    量化的过程可以用下面的公式表示:

         

    其中FQ(u,v)表示经过量化后的DCT系数;F(u,v)表示量化前的DCT系数;Q(u,v)表示量化加权矩阵;q表示量化步长;round表示归整,即将输出的值取为与之最接近的整数值。

    合理选择量化系数,对变换后的图像块进行量化后的结果如图所示。


    量化后的DCT系数

    DCT系数经过量化之后大部分经变为0,而只有很少一部分系数为非零值,此时只需将这些非0值进行压缩编码即可。

    (b)  熵编码

    熵编码是因编码后的平均码长接近信源熵值而得名。熵编码多用可变字长编码(VLC,Variable Length Coding)实现。其基本原理是对信源中出现概率大的符号赋予短码,对于出现概率小的符号赋予长码,从而在统计上获得较短的平均码长。可变字长编码通常有霍夫曼编码、算术编码、游程编码等。其中游程编码是一种十分简单的压缩方法,它的压缩效率不高,但编码、解码速度快,仍被得到广泛的应用,特别在变换编码之后使用游程编码,有很好的效果。

    首先要在量化器输出直流系数后对紧跟其后的交流系数进行Z型扫描(如图箭头线所示)。Z型扫描将二维的量化系数转换为一维的序列,并在此基础上进行游程编码。最后再对游程编码后的数据进行另一种变长编码,例如霍夫曼编码。通过这种变长编码,进一步提高编码的效率。

    (c)  运动估计和运动补偿

    运动估计(Motion Estimation)和运动补偿(Motion Compensation)是消除图像序列时间方向相关性的有效手段。上文介绍的DCT变换、量化、熵编码的方法是在一帧图像的基础上进行,通过这些方法可以消除图像内部各像素间在空间上的相关性。实际上图像信号除了空间上的相关性之外,还有时间上的相关性。例如对于像新闻联播这种背景静止,画面主体运动较小的数字视频,每一幅画面之间的区别很小,画面之间的相关性很大。对于这种情况我们没有必要对每一帧图像单独进行编码,而是可以只对相邻视频帧中变化的部分进行编码,从而进一步减小数据量,这方面的工作是由运动估计和运动补偿来实现的。

    运动估计技术一般将当前的输入图像分割成若干彼此不相重叠的小图像子块,例如一帧图像的大小为1280*720,首先将其以网格状的形式分成40*45个尺寸为16*16的彼此没有重叠的图像块,然后在前一图像或者后一个图像某个搜索窗口的范围内为每一个图像块寻找一个与之最为相似的图像块。这个搜寻的过程叫做运动估计。通过计算最相似的图像块与该图像块之间的位置信息,可以得到一个运动矢量。这样在编码过程中就可以将当前图像中的块与参考图像运动矢量所指向的最相似的图像块相减,得到一个残差图像块,由于残差图像块中的每个像素值很小,所以在压缩编码中可以获得更高的压缩比。这个相减过程叫运动补偿。

    由于编码过程中需要使用参考图像来进行运动估计和运动补偿,因此参考图像的选择显得很重要。一般情况下编码器的将输入的每一帧图像根据其参考图像的不同分成3种不同的类型:I(Intra)帧、B(Bidirection prediction)帧、P(Prediction)帧。如图所示。

    典型的I,B,P帧结构顺序

    如图所示,I帧只使用本帧内的数据进行编码,在编码过程中它不需要进行运动估计和运动补偿。显然,由于I帧没有消除时间方向的相关性,所以压缩比相对不高。P帧在编码过程中使用一个前面的I帧或P帧作为参考图像进行运动补偿,实际上是对当前图像与参考图像的差值进行编码。B帧的编码方式与P帧相似,惟一不同的地方是在编码过程中它要使用一个前面的I帧或P帧和一个后面的I帧或P帧进行预测。由此可见,每一个P帧的编码需要利用一帧图像作为参考图像,而B帧则需要两帧图像作为参考。相比之下,B帧比P帧拥有更高的压缩比。

    (d)  混合编码

    上面介绍了视频压缩编码过程中的几个重要的方法。在实际应用中这几个方法不是分离的,通常将它们结合起来使用以达到最好的压缩效果。下图给出了混合编码(即变换编码+ 运动估计和运动补偿+ 熵编码)的模型。该模型普遍应用于MPEG1,MPEG2,H.264等标准中。

    混合编码模型

    从图中我们可以看到,当前输入的图像首先要经过分块,分块得到的图像块要与经过运动补偿的预测图像相减得到差值图像X,然后对该差值图像块进行DCT变换和量化,量化输出的数据有两个不同的去处:一个是送给熵编码器进行编码,编码后的码流输出到一个缓存器中保存,等待传送出去。另一个应用是进行反量化和反变化后的到信号X’,该信号将与运动补偿输出的图像块相加得到新的预测图像信号,并将新的预测图像块送至帧存储器。


    2.音频编码基本原理

    (1)  音频信号的冗余信息

    数字音频信号如果不加压缩地直接进行传送,将会占用极大的带宽。例如,一套双声道数字音频若取样频率为44.1KHz,每样值按16bit量化,则其码率为:

    2*44.1kHz*16bit=1.411Mbit/s

    如此大的带宽将给信号的传输和处理都带来许多困难,因此必须采取音频压缩技术对音频数据进行处理,才能有效地传输音频数据。

    数字音频压缩编码在保证信号在听觉方面不产生失真的前提下,对音频数据信号进行尽可能大的压缩。数字音频压缩编码采取去除声音信号中冗余成分的方法来实现。所谓冗余成分指的是音频中不能被人耳感知到的信号,它们对确定声音的音色,音调等信息没有任何的帮助。

    冗余信号包含人耳听觉范围外的音频信号以及被掩蔽掉的音频信号等。例如,人耳所能察觉的声音信号的频率范围为20Hz~20KHz,除此之外的其它频率人耳无法察觉,都可视为冗余信号。此外,根据人耳听觉的生理和心理声学现象,当一个强音信号与一个弱音信号同时存在时,弱音信号将被强音信号所掩蔽而听不见,这样弱音信号就可以视为冗余信号而不用传送。这就是人耳听觉的掩蔽效应,主要表现在频谱掩蔽效应和时域掩蔽效应,现分别介绍如下:

    (a)  频谱掩蔽效应

    一个频率的声音能量小于某个阈值之后,人耳就会听不到,这个阈值称为最小可闻阈。当有另外能量较大的声音出现的时候,该声音频率附近的阈值会提高很多,即所谓的掩蔽效应。如图所示:


    频率掩蔽效应

    由图中我们可以看出人耳对2KHz~5KHz的声音最敏感,而对频率太低或太高的声音信号都很迟钝,当有一个频率为0.2KHz、强度为60dB的声音出现时,其附近的阈值提高了很多。由图中我们可以看出在0.1KHz以下、1KHz以上的部分,由于离0.2KHz强信号较远,不受0.2KHz强信号影响,阈值不受影响;而在0.1KHz~1KHz范围,由于0.2KHz强音的出现,阈值有较大的提升,人耳在此范围所能感觉到的最小声音强度大幅提升。如果0.1KHz~1KHz范围内的声音信号的强度在被提升的阈值曲线之下,由于它被0.2KHz强音信号所掩蔽,那么此时我们人耳只能听到0.2KHz的强音信号而根本听不见其它弱信号,这些与0.2KHz强音信号同时存在的弱音信号就可视为冗余信号而不必传送。

    (b)  时域掩蔽效应

    当强音信号和弱音信号同时出现时,还存在时域掩蔽效应。即两者发生时间很接近的时候,也会发生掩蔽效应。时域掩蔽过程曲线如图所示,分为前掩蔽、同时掩蔽和后掩蔽三部分。

    时域掩蔽效应

    由图我们可以看出,时域掩蔽效应可以分成三种:前掩蔽,同时掩蔽,后掩蔽。前掩蔽是指人耳在听到强信号之前的短暂时间内,已经存在的弱信号会被掩蔽而听不到。同时掩蔽是指当强信号与弱信号同时存在时,弱信号会被强信号所掩蔽而听不到。后掩蔽是指当强信号消失后,需经过较长的一段时间才能重新听见弱信号,称为后掩蔽。这些被掩蔽的弱信号即可视为冗余信号。

    (2)  压缩编码方法

    当前数字音频编码领域存在着不同的编码方案和实现方式, 但基本的编码思路大同小异, 如图所示。

    数字音频编码系统模型

    对每一个音频声道中的音频采样信号,首先都要将它们映射到频域中,这种时域到频域的映射可通过子带滤波器实现。每个声道中的音频采样块首先要根据心理声学模型来计算掩蔽门限值, 然后由计算出的掩蔽门限值决定从公共比特池中分配给该声道的不同频率域中多少比特数,接着进行量化以及编码工作,最后将控制参数及辅助数据加入数据之中,产生编码后的数据流。

     


    展开全文
  • VC++实现视频压缩编码标准 MPEG-4

    万次阅读 热门讨论 2012-06-05 14:24:40
    欢迎大家提出意见,一起讨论!...用于甚低数码率的音频/视频编码标准,它由一个数据比特数据流格式和相应的一组协议组成。 用于表示由自然或合成的音频、视频及对象数据组成的多媒体内容。 和MPEG-1、MPEG-2相比,M

     转载请标明是引用于 http://blog.csdn.net/chenyujing1234 

    欢迎大家提出意见,一起讨论!

    需要源码的请与我联系。


    参考书籍<<Visual C++ 视频/音频编解码技术>>

    1、   MPEG-4 编码技术

    用于甚低数码率的音频/视频编码标准,它由一个数据比特数据流格式和相应的一组协议组成。

    用于表示由自然或合成的音频、视频及对象数据组成的多媒体内容。

    和MPEG-1、MPEG-2相比,MPEG-4中系统的概念发生了极大的变化,过去系统仅指整体结构、多路复用及同步功能,

    而MPEG-4除此之外,又引入了视听对象及描述、场景描述、传输多媒体集成框架(Delivery Multimedia Integration Framework, DMIF)以及上载码流等概念

    来支持基于内容的交换型多媒体应用。同时MPEG-4支持将自然信息和合成信息进行组合,包括图像与自然景物、合成声音与自然声音的组合。

    1、1  MPEG-4 基本介绍

    MPEG-4之前有四个运动图像编码标准:

    MPEG-1 、MPEG-2、H.261、H.263

    这四个标准就是把视频序列按时间先后分为一系列的帧,每一帧图像又分成16X16的宏块来进行运行补偿和编码。

    这种基于是帧、块和像素的编码称为第一代视频编码方案,在编码过程中不考虑图像的具体结构和内容,因而会产生以下主要问题:

    将图像固定地分成相同大小块,在高缩比的情况下会产生严重的块效应;

    不能对图像的内容进行查询、编辑和回放等;

    没有利用人类视频系统的特性。

    1、1、1 基本原理

    MPEG-4是基于图像的内容进行编码的,根据内容将图像分成不同的视频对象VO(Video Object)。

    例如在可视电话系统中,经常将讲话的人作为前景视频对象,将其余部分作为背景视频对象

    前景视频对象由于包含重要的边界和轮廓信息,而且纹理又是理解图像的重要附加信息,因而在编码过程中应尽可能地保留这部分信息。

    在编码过程中前景对象与背景对象采用不同的编码策略。前景对象的编码尽可能保留视频对象的细节和平滑。对人们不关心的背景可以采用大压缩比的编码策略,甚至不

    予传输,而是在解码端用其他的背景拼接成新的背景。
    MPEG-4视频标准还将合成技术用于编码。在视频非编码标准中定义了人脸部的合成编码。通过定义人脸的模型以及动画参数,在编码过程中只传输这些动画参数,不仅能

    提高编码效率,还能用于虚拟电视会议系统的实现。在合成编码还实现了网格变形编码,对视频对象生成二维网格或三维网格模型,在编码的大部分时间中只编码传输网格

    顶点的位置变化,在解码端利用已知的纹理信息和传来的网格顶点位置变化来合成编码的视频对象。

    1、1、2 体系结构

    MPEG-4标准通过定义框架、级、算法、工具 4层结构来分层描述具体应用中的编码方案。

    (1)一个框架是一类应用的完整编码码流语法描述,它是整个MPEG-4码流语法的一个子集。

    即使在给定框架的语法规定范围内,依然可能由于码流中参数的不同而导致编码器和解码器的功能和编码效率产生很大的差别。

    (2)为了处理这个问题,在每个框架中又通过定义规定码流中各个参数的类型和限制

    (3)算法是为了完成框架所要求的功能而组织起来的一些工具集合

    1、1、3  主要特点

    (1) 基于对象的功能。

    MPEG-4标准的系统部分用来描述组成一幅画的各个视频对象之间的空间和时间的关系。

    (2)形状编码

    视频对象可以是任意形状的物体,因而定义了平面(Alpha Plane) 来描述对象的形状和位置信息。

    平面主要有二进制灰度两种。

    二进制只确定某一像素点是否属于当前的对象,灰度平面可以描述对象的透明度,将不同的对象混合起来,完成某些特技效果,通常对平面的编码叫做形状编码。

    (3)基于对象的编辑和交互式操作。

    允许随机访问每个视频对象,具体地讲是能以0.5S的时间间隔访问对象,可以单独解码对象的形状信息,而不解码对象的纹理信息,也能对视频对象进行剪贴、平移和旋转。

    (4) 多输入流多视点的编码和解码。

    能有效地对来自不同的视频源图像进行编码,并能根据配置文档,在解码端把这些对象的码流同步地解码出来组成一幅图。在多视点立体视频编辑的应用中,

    MPEG-4能有效地利用同一场景中不同视点图你信息的冗余性提高编码效率,对同一个场景仅能支持4个视点的编码

    (5) 码率方式。

    可以支持各种不同的传输速率下的编码,其中包括小于64Kbit/s的低速码率、码率为64-384Kbit/s的高码率编码。

    (6)不同分辨率的编码。

    提供了灵活的编码策略,这些策略包括对编码图像时间分辨率、空间分辨率和解码图像的信噪比的控制。

    时间分辨率编码策略是每隔几帧选一帧作为基帧,基帧是必须编码传输的,只有传输带宽允许或解码端要求才编码中间帧,使运动图像看起来更平滑;

    空间分辨率编码策略是在编码端对图像进行抽样使图像尺寸变小.

    信噪比编码策略是根据传输带宽的限制和实际应用的需要,降低解码图像的信噪比来提高压缩率。

    (7)在噪声环境中的鲁棒性。

    MPEG-4对每个对象的关键数据如对象的头信息和形状信息提供更高的容错保护,并提供一个可逆变长编码码表,能检验传输中产生的误码。

    (8) 脸部动画。

    (9)网格编码

    可以对需要编码的二维或三维视频对象生成各种网格模型,例如三维的高度栅格、脸和身体的三维多边形网格,以及用二维Delaunay网格来描述物体。

    MPEG-4能有效地进行基于网络模型的视频对象运动信息编码,包括编码网格的拓扑形状以及每个结点的运动矢量。

    (10)全局运动估计和Sprite编码。

    块匹配运动补偿技术对摄像头运行所产生的整幅图像的变化不是很有效。因此MPEG-4定义了二维和三维全局运动模型,包括平移、旋转、映射和投影等来补偿。

     

     

    1、2  MPEG-4 编码结构说明

    MPEG-4是基于内容的方式表示视频数据的,正是这个特点使得MPEG-4具有高效率、基于内容交互(操作、编辑、访问等)和基于内容分级扩展(空域分级、时域分级)等

    优点。MPEG-4中引入了VO(video object)的概念实现基于内容的表示。VO的结构依赖于具体应用和实现系统,在要求超低比特率的情况下,VO可以是一个矩形帧(即传统

    MPEG-1,8263中的矩形帧),从而与原来的标准兼容;

    在VM中,VO被定义为画面中分割出来的不同物体,每个VO有3类信息来描述:运动信息、形状信息和纹理信息。

    编码的第一步是VO的形成,先要从原始视频流中

    分割出VOL,

    由编码控制机制为不同的VO以及各个VO的3类信息分配码率

    以及各个VO分别独立编码,

    最后将和个VO的码流复合成一个位流。

    1、2、1  MPEG-4 视频编码的关键概念

    (1) 视频对象。

    VO是指视频流中用户可以获取和操作的个体。它由时间上连续的帧面序列构成。

    在MPEG-4中,视频对象又分为自然视频对象合成视频对象

    自然视频对象是指从自然图像和视频序列中分割出来的某一视频区域;

    合成视频对象是指表面通过若干顶点和曲面定义的,由计算机产生2D/3D对象。

    (2)视频对象平面。

    视频对象平面(VOP)是指在某一时刻某一帧画面的视频对象。VOP的引入直接导致了基于内容的分级编解码技术。

    VOP编码针对视频对象(VO)形状、运动、纹理等可以进行独立编码、存储和传输。

    (3)Sprite图像。

    Sprite图像是指在一段VideoChip中所有可见的,属于某一视频对象的像素所组成的图像,主要是针对背景视频对象的特点而提出的。

    在许多应用场合,背景视频对象自身没有任何局部运动,为了有效地编码视频对象,可以将其在一段时间内的内容拼接成一幅完整的背景图像,

    这样的图像就叫做Sprite图像。

    MPEG-4定义了两种Sprite:

    一种是静态Sprite。即在整个编码过程中保持不变的Sprite.例如:视频电话和视频会议中的背景全景图。

    另一种是动态Sprite,即在编码过程中不断动态更新产生的Sprite。

    实现 Sprite编码,必须满足:

    A、前景对象与背景对象要能很好的分开;

    B、无痕迹地从一段视频或一些图像中拼接出Sprite图像。

    (4) MPEG视频流逻辑结构。

    一个完整的视频序列通常由几个视频段(Video Session,VS)构成,每个VS由一个或多个VO组成,每个VO又由一个或多个视频对象层(Video Object Layer, VOL)构成,

    每个VOL代表一个层次,即基本层或增强层,每个层表示某一种分辨率。在每个层中,都有时间上连续的一系列VOP。

    1、2、2   VOP编码

    视频编码器包括形状编码(对于任意形状)、运动信息编码纹理编码

    基本编码方法为:首先对输入的原图像序列进行场景分析和对象分割,以划分不同的VOP,得到各个VOP的形状和位置信息,它可以用

    Alpha平面来表示。对Alpha平面进行压缩编码和传送,在接收端就可以恢复Alpha平面。提取的形状和位置信息又用来控制VOP的运动和纹理编码。

    对运动编码和纹理编码仍然采用经典的运动预测/补偿法

    输入第N帧的VOP与帧存储器中存储的N-1帧的VOP进行比较,找到运行矢量,然后对两帧VOP的差值进行量化、编码。编码后得到的纹理信息,与运动编码器和形状编码器

    输出的运动信息和形状信息复合形成该VOP的比特流层。不同视频对象的VOP序列分别进行编码,形成各自的比特流层,经复合后在信道上传送

    传送的顺序依次为形状信息、运动信息和纹理信息

    下图是MPEG-4 Video编码的基本框图。

    (1)形状编码。

    VOP形状信息有两类:二值形状和灰度形状信息。二值形状信息用0、1表示VOP的形状。

    0表示非VOP区域,1表示VOP区域。

    二值形状信息的编码采用基于运动补偿块的技术,可以是无损或有损编码。

    灰度形状信息用0-255之间的值来表示VOP的透明程序,其中0表示完全透明(相当于二值信息中的0);

    255表示完全不透明(相当于二信信息中的1);灰度信息是二值信息的扩展,它可以用来表示透视的物体,并降低混迭现象

    灰度信息的编码采用基于块的运动补偿DCT方法,属于有损编码。

    MPEG-4中采用位图法来表示这两类形状信息。VOP被限定在一个矩形窗口内,称之为VOP窗口,窗口的长、宽均为16的整数倍,

    同时保证VOP窗口中非VOP的宏数目最小。

    位图法实际上是一个边框矩阵的编码,矩阵被分成16X16的“形状块”,允许进行有损编码,这通过对边界信息进行子采样实现,

    同时允许使用宏块的运动向量来做形状的运行补偿

    实验证明,位图表示法具有较高的编码效率和较低的运算复杂度。

    (2) 运动信息编码

    MPEG-4采用运动预测和运动补偿技术来去除图像信息中的时间冗余成分,而这些运动信息的编码技术可视为现有标准向任意形状的VOP的延伸。

    VOP的编码有3种模式,即帧内编码模式(I-VOP)、帧间预测编码模式(P-VOP)、帧间双向预测编码模式(B-VOP)。

    VOP如形状编码一样,外加了边框边框分成16X16的宏块,宏块内是8X8的块

    在MPEG-4中运动预测和运动补偿可以是基于16X16像素宏块的,也可以是基于8X8像素块的。

    为了适应任意形状VOP,MPEG-4引入了图像填充技术和多边形匹配技术图像填充技术利用VOP内部的像素值来外推VOP外的像素值,以此获得运行预测的参考值。

    多边形匹配技术则将VOP的轮廓宏块的活跃部分包含在多边形之内,以此来增加运动估值的有效性。

    (3) 纹理编码

    纹理信息有两种:内部编码的I-YOP的像素值 帧间编码的P-VOP、B-VOP的运动估计残差值

    仍然采用基于分块的纹理编码。VOP边框仍被分成16X16的宏块。

    纹理编码方式分三种情况:

    一是VOP外、边框内的块,不编码;

    二是VOP内的块,采用经典的DCT方法;

    三是部分在VOP内,部分在VOP外的块采用图像填充技术来获取VOP外的像素值,之后进行DCT编码。

     

    1、2、3   VOP分割

    (1) 基于纹理的分割。

    主要是应用模式识别的技术来聚类,但要注意分割结果适度。分割太粗,不能有效地压缩;分得太细,就有可能是物体的各部分,这样对于压缩和基于内容的操作不利。

    (2) 基于运动的分割。

    将具有同一运动参数模型的区域聚类,从而达到分割的目的。这种方法可以分割出运动的物体,但会使得基于运动一致必的分割实现起来非常,效果也不太理想。

    (3) 纹理和运动结合的分割。

    在运行一致性表现得非常明显的区域用运动分割,在一些细节或运动复杂区域仍采用纹理分割。

    1、2、4   可分级编码(Scalable Coding)

    MPEG-4提供了基于对象的分级功能,并同时兼顾MPEG-2中的图像分级功能。视频分级编码的目的在于:

    (1)以不同的带宽及不同的显示能力进行视频数据库的浏览,以及在多媒体环境下视频内容的多分辨率播放;

    (2)提供分层的视频流以适应按优先顺序进行视频传播的要求

    提供了两种分级方法:空域分级(Spatial Scalability)和时域分级(Temporal Scalability).通过视频对象层(VOL)的数据结构实现了基于内容的分级编码

    每一种分级编码都至少有两层VOL,低层称为基本层,高层称为增强层。

    1、2、5   Sprite对象编码

    许多图像序列中的背景本身实际上是静止的,由于摄像机的运动才造成了它们的改变。可以通过图像的镶嵌技术将整个序列的背景图像拼接成一个大的完整的背景,

    这就是Sprite图像。

    Sprite也可以指图像序列中保持不变的比较小的VOL,例如电视台的台标,由于Sprite图像本身是不变的,所以只需传输一次,然后根据摄像机的运动参数在接收端

    重建背景,这样大大减少传输数据量。

    1、3  MPEG-4编码源码详细分析

     1、3、1  入口函数
    (1) encore() 函数。

    这个函数通过param1输入待压缩的视频帧,param1的实际类型是ENC_PARAM

    然后把压缩过的视频帧放到param2中返回, 

    enc_opt表示调用方式,有3种调用方式:ENC_OPT_WRITE, ENC_OPT_INT 和 ENC_OPT_RELEASE,

    分别表示正常压缩、压缩初始化和压缩完成

     

    typedef struct _ENC_PARAM_ {
    	int x_dim;		// 待压缩帧的x大小
    	int y_dim;		// 待压缩帧的y大小
    	float framerate;// 待压缩帧序列本来的帧频
    	long bitrate;	// 压缩后的目标帧率 
    	long rc_period; // the intended rate control averaging period
    	long rc_reaction_period; // 帧率控制的反向时间
    	long rc_reaction_ratio; // 低/高端帧频控制的比例
    	long max_key_interval; // 关键帧之间的最大间隔
    	int max_quantizer; // 量化器的上限值
    	int min_quantizer; // 量化器的下限值
    	int search_range; // 运动估计的向前搜索范围 
    } ENC_PARAM;

     

    typedef struct _REFERENCE
    {
    	unsigned long handle;
    	float framerate;
    	long bitrate;
    	long rc_period;
    	long rc_reaction_period;
    	long rc_reaction_ratio;
    	long max_key_interval;
    	int x_dim, y_dim;
    	int prev_rounding;
    	int search_range;
    	int max_quantizer;
    	int min_quantizer;
    
    	long seq;
    	long curr_run;       /* 最后关键帧前的当前运行 */
    
    	Vop *current;        /* 要编码的当前帧 */
    	Vop *reference;      /* 参考帧 - 重构之前的帧 */
    	Vop *reconstruct;    /* 中间重构的帧 - used in inter */
    	Vop *error;          /* 中间的错误帧 - 用整型来保存预测错误  */
    
    	struct _REFERENCE *pnext;
    } REFERENCE;



     

     要对视频进行MPEG-4压缩,首先要以ENC_OPT_INT方式调用encore()函数,

    然后对待压缩的每一个视频帧都按顺序以ENC_OPT_WRITE方式调用一次encore()函数,

    最后以ENC_OPT_RELEASE方式调用一次encore()就可以了。

     

    int encore(unsigned long handle, unsigned long enc_opt, void *param1, void *param2)
    {
    	static REFERENCE *ref = NULL;
    	static VolConfig *vol_config;
    	// 下面的链表保存之前压缩过的参考
    	REFERENCE *ref_curr, *ref_last = NULL;
    	int x_dim, y_dim, size, length;
    	int headerbits = 0;
    	Vop *curr;
    
    	ref_curr = ref_last = ref;
    	while (ref_curr != NULL)
    	{
    		if (ref_curr->handle == handle) break;
    		ref_last = ref_curr;
    		ref_curr = ref_last->pnext;
    	}
    	// 如果没有找到匹配的参考,则创建一个新的句柄
    	if (ref_curr == NULL)
    	{
    		if (enc_opt & ENC_OPT_RELEASE) return ENC_OK;
    		ref_curr = (REFERENCE *)malloc(sizeof(REFERENCE));
    		ref_curr->handle = handle;
    		ref_curr->seq = 0;
    		ref_curr->curr_run = 0;
    		ref_curr->pnext = NULL;
    		if (ref) ref_last->pnext = ref_curr;
    		else ref = ref_curr;
    	}
    	// 按要求初始化一个句柄
    	if (enc_opt & ENC_OPT_INIT)
    	{
    #ifdef _RC_
    		ftrace = fopen("trace.txt", "w");
    		fflush(ftrace);
    #endif
    
    		init_fdct_enc();
    		init_idct_enc();
    
    		// 初始化率控制器
    		ref_curr->framerate = ((ENC_PARAM *)param1)->framerate;
    		ref_curr->bitrate = ((ENC_PARAM *)param1)->bitrate;
    		ref_curr->rc_period = ((ENC_PARAM *)param1)->rc_period;
    		ref_curr->rc_reaction_period = ((ENC_PARAM *)param1)->rc_reaction_period;
    		ref_curr->rc_reaction_ratio = ((ENC_PARAM *)param1)->rc_reaction_ratio;
    		ref_curr->x_dim = ((ENC_PARAM *)param1)->x_dim;
    		ref_curr->y_dim = ((ENC_PARAM *)param1)->y_dim;
    		ref_curr->max_key_interval = ((ENC_PARAM *)param1)->max_key_interval;
    		ref_curr->search_range = ((ENC_PARAM *)param1)->search_range;
    		ref_curr->max_quantizer = ((ENC_PARAM *)param1)->max_quantizer;
    		ref_curr->min_quantizer = ((ENC_PARAM *)param1)->min_quantizer;
    
    		ref_curr->current = AllocVop(ref_curr->x_dim, ref_curr->y_dim);
    		ref_curr->reference = AllocVop(ref_curr->x_dim + 2 * 16, 
    			ref_curr->y_dim + 2 * 16);
    		ref_curr->reconstruct = AllocVop(ref_curr->x_dim, ref_curr->y_dim);
    		ref_curr->error = AllocVop(ref_curr->x_dim, ref_curr->y_dim);
    		init_vop(ref_curr->current);
    		init_vop(ref_curr->reference);
    		init_vop(ref_curr->reconstruct);
    		init_vop(ref_curr->error);
    		ref_curr->reference->hor_spat_ref = -16;
    		ref_curr->reference->ver_spat_ref = -16;
    		SetConstantImage(ref_curr->reference->y_chan, 0);
    
    		vol_config = (VolConfig *)malloc(sizeof(VolConfig));
    		init_vol_config(vol_config);
    		vol_config->frame_rate = ref_curr->framerate;
    		vol_config->bit_rate = ref_curr->bitrate;
    
    		RateCtlInit(8 /* initial quant*/, vol_config->bit_rate / vol_config->frame_rate,
    			ref_curr->rc_period, ref_curr->rc_reaction_period, ref_curr->rc_reaction_ratio);
    
    		return ENC_OK;
    	}
    	// 释放和句柄有关的参考
    	if (enc_opt & ENC_OPT_RELEASE)
    	{
    		if (ref_curr == ref) ref = NULL;
    		else ref_last->pnext = ref_curr->pnext;
    
    		if (ref_curr->current) FreeVop(ref_curr->current);
    		if (ref_curr->reference) FreeVop(ref_curr->reference);
    		if (ref_curr->reconstruct) FreeVop(ref_curr->reconstruct);
    		if (ref_curr->error) FreeVop(ref_curr->error);
    
    		free(ref_curr);
    		free(vol_config);
    		if (ftrace) {
    			fclose(ftrace);
    			ftrace = NULL;
    		};
    		return ENC_OK;
    	}
    
    	// 对相关参数进行初始化 (need to be cleaned later)
    	max_quantizer = ref_curr->max_quantizer;
    	min_quantizer = ref_curr->min_quantizer;
    
    	x_dim = ref_curr->x_dim;
    	y_dim = ref_curr->y_dim;
    	size = x_dim * y_dim;
    
    	curr = ref_curr->current;
    	curr->width = x_dim;
    	curr->height = y_dim;
    	curr->sr_for = ref_curr->search_range;
    	curr->fcode_for = get_fcode(curr->sr_for);
    
    	// 下面对输入的参数进行变换
    	// 下面这个函数使用图像数据由short int表示变成了由unsigned char 表示
    	// 因为这部分MPEG-4编程代码之前的MoMuSys代码是用short int存放数据的
    	YUV2YUV(x_dim, y_dim, ((ENC_FRAME *)param1)->image,
    		curr->y_chan->f, curr->u_chan->f, curr->v_chan->f);
    
    	// 调整当前图像的取整方式
    	curr->rounding_type = 1 - ref_curr->prev_rounding;
    
    	Bitstream_Init((void *)(((ENC_FRAME *)param1)->bitstream));
    
    	if (ref_curr->seq == 0) 
    	{
    		headerbits = PutVoVolHeader(x_dim, y_dim, curr->time_increment_resolution, ref_curr->framerate);
    	}
    	
    #ifdef _RC_
    	fflush(ftrace);
    	fprintf(ftrace, "\nCoding frame #%d\n", ref_curr->seq);
    #endif
    
    	if (ref_curr->curr_run % ref_curr->max_key_interval == 0) 
    	{
    		curr->prediction_type = I_VOP;
    #ifdef _RC_
    	fprintf(ftrace, "This frame is forced to be coded in INTRA.\n");
    	fprintf(ftrace, "It has been %d frame since the last INTRA.\n", ref_curr->curr_run);
    #endif
    	}
    	else 
    		curr->prediction_type = P_VOP;
    
    
    	// 视频对象的编码函数 Code the image data (YUV) of the current image
    	VopCode(curr,
    		ref_curr->reference,
    		ref_curr->reconstruct,
    		ref_curr->error,
    		1, //enable_8x8_mv,
    		(float)ref_curr->seq/ref_curr->framerate,  // time
    		vol_config);
    
    	length = Bitstream_Close();
    	((ENC_FRAME *)param1)->length = length;
    
    	// 更新率控制参数
    	RateCtlUpdate(length * 8);
    
    	ref_curr->prev_rounding = curr->rounding_type;
    	ref_curr->seq ++;
    	ref_curr->curr_run ++;
    
    	if (curr->prediction_type == I_VOP) 
    	{
    		((ENC_RESULT *)param2)->isKeyFrame = 1;
    		ref_curr->curr_run = 1;
    	} 
    	else 
    		((ENC_RESULT *)param2)->isKeyFrame = 0;
    
    	return ENC_OK;
    }
    
    


    (2) 视频对象编码函数VopCode().

    上面encore()函数进行的编码操作是由VopCode()函数完成的,函数对输入的视频对象平面进行形状、纹理和运动信息编码

    输入Vop必须是有封闭界面的。函数包含对一个帧进行编码的基本流程。

    struct vop
    {
      /* Actual syntax elements for VOP (standard) */
      Int prediction_type;		/* VOP 预测类型 */
      Int mod_time_base;		/* VOP modulo time base (absolute) */
      Float time_inc;		/* VOP 时间增量 (相对于上一mtb) */
      Int rounding_type;
    
      Int width;			/* VOP 高 (smallest rectangle) */
      Int height;			/* VOP 宽  (smallest rectangle) */
      Int hor_spat_ref;		/* VOP 水平参考. (for composition) */
      Int ver_spat_ref;		/* VOP 竖直参考.   (for composition) */
      
      Int intra_dc_vlc_thr;
    
      Int quantizer;		/* P-VOPs的VOP 量化器 */
      Int intra_quantizer;		/* I-VOPs的VOP 量化器 */
     
      /* 从VOL拷贝的 Syntax 元素 */
      Int time_increment_resolution;
    
      Int intra_acdc_pred_disable;	/* VOL disable INTRA DC prediction */
      Int sr_for;			/* VOP 运动向向量的搜索范围 */
      Int fcode_for;		/* VOP 运动向量的动态范围    */
      Int quant_precision;
      Int bits_per_pixel;
    
      /* 指向图像 (YUVA) 和相关系 VOPs */
      Image *y_chan;		/* VOP texture 中的Y组件*/
      Image *u_chan;		/* VOP texture 中的U组件*/
      Image *v_chan;		/* VOP texture 中的V组件*/
    };
    typedef struct vop Vop;


     

     

    Void VopCode(Vop *curr, // 编码的视频对象
    Vop *reference,         // 参考视频对象平面
    Vop *reconstruct,       // 前的构造VOP
    Vop *error,
    Int enable_8x8_mv,      // 8X8运行向量标志
    Float time,             // 帧间间隔时间
    VolConfig *vol_config)
    {
    	ImageF    *mot_x=NULL, *mot_y=NULL;
    	Image     *MB_decisions=NULL;
    	Int       edge,f_code_for=1;
    	Vop       *error_vop=NULL;
    	Int       vop_quantizer;
    
    	Float	  mad_P = 0., mad_I = 0.;
    	Float	  IntraMBRatio = 1.;
    	Int		  numberMB, i, IntraMB;
    
    	edge = 0;
    	f_code_for = curr->fcode_for;
    
    	if (curr->prediction_type == P_VOP) 
    	{
    		/* 进行运动估计和补偿*/
    		MotionEstimationCompensation(curr, reference,
    			enable_8x8_mv, edge ,f_code_for,
    			reconstruct, &mad_P, &mot_x,&mot_y,&MB_decisions);
    
    		/* 计算各宏块在视频对象平面内部的百分比 */
    		IntraMB = 0;
    		numberMB = MB_decisions->x * MB_decisions->y;
    		for (i = 0; i < numberMB; i ++)
    			if (MB_decisions->f[i] == MBM_INTRA) IntraMB ++;
    		IntraMBRatio = (float)IntraMB / (float)numberMB;
    
    		#ifdef _RC_
    		fprintf(ftrace, "ME with MAD : %f\n", mad_P);
    		fprintf(ftrace, "%4.2f of the MBs are I-MBs.\n", IntraMBRatio);
    		#endif
    	}
    	else
    		mad_P = SCENE_CHANGE_THREADHOLD * 2;
    
    	if ((mad_P < SCENE_CHANGE_THREADHOLD / 3) || 
    		((mad_P < SCENE_CHANGE_THREADHOLD) && (IntraMBRatio < MB_RATIO_THREADHOLD)))
    	{
    		// mad 满足要求,继续按P帧形成编码
    		curr->prediction_type = P_VOP;
    		error->prediction_type = P_VOP;
    
    		#ifdef _RC_
    		fprintf(ftrace, "Coding mode : INTER\n");
    		#endif
    
    		vop_quantizer = RateCtlGetQ(mad_P);
    
    		curr->quantizer = vop_quantizer;
    		error->quantizer = vop_quantizer;
    
    		#ifdef _RC_DEBUG_
    		fprintf(stdout, "RC: >>>>> New quantizer= %d\n", vop_quantizer);
    		#endif
    
    		SubImage(curr->y_chan, reconstruct->y_chan, error->y_chan);
    		SubImage(curr->u_chan, reconstruct->u_chan, error->u_chan);
    		SubImage(curr->v_chan, reconstruct->v_chan, error->v_chan); 
    
    		BitstreamPutVopHeader(curr,time,vol_config);
    
    		VopShapeMotText(error, reconstruct, MB_decisions,
    			mot_x, mot_y, f_code_for, 
    			GetVopIntraACDCPredDisable(curr), reference,
    			NULL/*mottext_bitstream*/);
    	} 
    	else 
    	{
    		// mad 太大. 
    		// 按I帧形式编码
    		curr->prediction_type = I_VOP;
    		curr->rounding_type = 1;
    
    		#ifdef _RC_
    		fprintf(ftrace, "Coding mode : INTRA\n");
    		#endif
    
    		// 因为上次计算mad是假定在内部编码基础上的,所以应该重新计算MAD值
    		if (mad_I == 0.) mad_I = (Float) compute_MAD(curr);
    
    		vop_quantizer = RateCtlGetQ(mad_I);
    
    		curr->intra_quantizer = vop_quantizer;
    		curr->rounding_type = 1;
    
    		BitstreamPutVopHeader(curr,time,vol_config);
    
    		/* 内部编码模式下对纹理进行编码 */
    		VopCodeShapeTextIntraCom(curr,
    			reference,
    			NULL/*texture_bitstream*/
    		);
    	} 
    
    
    	if (MB_decisions) FreeImage(MB_decisions);
    	if (mot_x) FreeImage(mot_x);
    	if (mot_y) FreeImage(mot_y);
    
    	ImageRepetitivePadding(reference->y_chan, 16);
    	ImageRepetitivePadding(reference->u_chan, 8);
    	ImageRepetitivePadding(reference->v_chan, 8);
    
    	Bitstream_NextStartCode();	  
    
    	return;
    }												  /* CodeVop() */
    
    


     

     (3) 运动信息编码

    这方面的代码文件为mot_est_comp.h、 mot_est_comp.c、mot_est_mb.h和mot_est_mb.c, 包括运动估计和运行补偿两个部分。

    (3、1)

    运动估计和预测补偿函数MotionEstimationCompensation

    功能:估计运动方向并进行运动补偿运算。在高级模式估计运动向量,函数输出为4个,包含运动向量横纵坐标的图像及其处理模式

     

    Void
    MotionEstimationCompensation (
    Vop    *curr_vop,			/* <-- 当前视频面对象      */
    Vop    *prev_rec_vop,		/* <-- 重建的参考视频对象平面(1/2 pixel)*/
    Int    enable_8x8_mv,		/* <-- 8x8 运动向量 (=1) or only 16x16 运动向量 (=0)*/
    Int    edge,				/* <-- 限制(==0)/非限制(==edge) mode*/
    Int    f_code,				/* <-- 运动向量搜索范围 1/2 pel: 1=32,2=64,...,7=2048*/
    Vop    *curr_comp_vop,		/* <-> 进行运动补偿的视频对象平面*/
    Float  *mad,				/* <-> ME/MC结果的容忍值*/
    Image  **mot_x,				/* --> 运动向量水平坐标*/
    Image  **mot_y,				/* --> 运动向量竖直坐标*/
    Image  **mode				/* --> 每个个宏块的模式*/
    )
    {
    	Image   *pr_rec_y;		/* 参考图像(重建后)   */
    	Image   *pi_y;			/* Interp.ref.image Y  */
    
    	Image   *mode16;
    	Image   *mv16_w;
    	Image   *mv16_h;
    	Image   *mv8_w;
    	Image   *mv8_h;
    
    	SInt    *prev_ipol_y,	/* 有边界的参考原始图像  */
    			*prev_orig_y;	/* Ref. original image with edge (subimage) */
    
    	Int     vop_width, vop_height;
    
    	Int     br_x;
    	Int     br_y;
    	Int     br_height;
    	Int     br_width;
    	Int     mv_h, mv_w;
    
    	/*GETBBR*/
    	br_y=curr_vop->ver_spat_ref;
    	br_x=curr_vop->hor_spat_ref;
    	br_height=curr_vop->height;
    	br_width=curr_vop->width;
    	mv_h=br_height/MB_SIZE;
    	mv_w=br_width/MB_SIZE;
    
    	/*
    	 ** 假定pre_rec_vop和prev_vop初始值相等
    	 */
    
    	vop_width=prev_rec_vop->width;
    	vop_height=prev_rec_vop->height;
    
    	/*
    	 ** 得到图像并将它们插入
    	 */
    
    	pr_rec_y = prev_rec_vop->y_chan;
    	prev_orig_y = (SInt*)GetImageData(pr_rec_y);
    	pi_y = AllocImage (2*vop_width, 2*vop_height, SHORT_TYPE);
    	InterpolateImage(pr_rec_y, pi_y, GetVopRoundingType(curr_vop));
    	prev_ipol_y = (SInt*)GetImageData(pi_y);
    
    	/*
    	 * 给所有运动向量和模式数据分配存储空间
    	 *
    	 */
    
    	mode16=AllocImage (mv_w,mv_h,SHORT_TYPE);
    	SetConstantImage (mode16,(Float)MBM_INTRA);
    
    	/*
    	 **   mv16 有2X2个重复的运动向量值,以便在CodeVopVotion和MotionEstimation
    	 *    之间共享运动向量的预测函数 
    	 */
    
    	mv16_w=AllocImage (mv_w*2,mv_h*2,FLOAT_TYPE);
    	mv16_h=AllocImage (mv_w*2,mv_h*2,FLOAT_TYPE);
    	mv8_w =AllocImage (mv_w*2,mv_h*2,FLOAT_TYPE);
    	mv8_h =AllocImage (mv_w*2,mv_h*2,FLOAT_TYPE);
    	SetConstantImage (mv16_h,+0.0);
    	SetConstantImage (mv16_w,+0.0);
    	SetConstantImage (mv8_h,+0.0);
    	SetConstantImage (mv8_w,+0.0);
    
    	SetConstantImage (curr_comp_vop->u_chan, 0);
    	SetConstantImage (curr_comp_vop->v_chan, 0);
    
    	/* 对每一个宏块计算它的运动向量和模式
    	 */
    
    	MotionEstCompPicture(
    		(SInt *)GetImageData(GetVopY(curr_vop)), //curr_vop,
    		prev_orig_y,							  /* Y padd with edge */
    		prev_ipol_y,							  /* Y interpolated (from pi_y) */
    		(SInt*)GetImageData(prev_rec_vop->u_chan) + (vop_width/2) * (16/2) + (16/2),
    		(SInt*)GetImageData(prev_rec_vop->v_chan) + (vop_width/2) * (16/2) + (16/2),
    		prev_rec_vop->hor_spat_ref,
    		prev_rec_vop->ver_spat_ref,
    		vop_width,vop_height,
    		enable_8x8_mv,
    		edge,
    		GetVopSearchRangeFor(curr_vop), 
    		f_code,
    		GetVopRoundingType(curr_vop),
    		br_x,br_y,					/* pos. of the bounding rectangle */
    		br_width,br_height,			/* dims. of the bounding rectangle */
    		(SInt*)GetImageData(curr_comp_vop->y_chan),
    		(SInt*)GetImageData(curr_comp_vop->u_chan),
    		(SInt*)GetImageData(curr_comp_vop->v_chan),
    		mad,
    		(Float*)GetImageData(mv16_w),
    		(Float*)GetImageData(mv16_h),
    		(Float*)GetImageData(mv8_w),
    		(Float*)GetImageData(mv8_h),
    		(SInt*) GetImageData(mode16)
    		);
    
    	/* 将结果转MOMUSYS格式 */
    	GetMotionImages(mv16_w, mv16_h, mv8_w, mv8_h, mode16, mot_x, mot_y, mode);
    
    	/* 释放动态申请的内存Deallocate dynamic memory */
    	FreeImage(mv16_w); FreeImage(mv16_h);
    	FreeImage(mv8_w);  FreeImage(mv8_h);
    	FreeImage(mode16);
    	FreeImage(pi_y);
    }
    



     

    (3、2) 运动向量和预测误差计算的函数MotionEstCompPicture().

    这个函数在MotionEstimationCompensation()函数中调用到了。这个函数计算运动向量和预测器的误差,并且完成运动补偿计算。

    运动向量一般为8X8和16X16大小,预测器误差是针对整个视频对象平面而言的,并且运动补偿也是对整个视频对象平面而言的

    Void  	MotionEstCompPicture (	
    			SInt *curr,       // 当前视频对象平面
    			SInt *prev,       // 用边界填充的原始Y向量
    			SInt *prev_ipol_y,// 
    			SInt *prev_ipol_u,// 用边界填充的原始U分量
    			SInt *prev_ipol_v,// 用边界填充的原始V分量
    			Int prev_x,      // 前一个视频对象的水平位置位置的绝对值
    			Int prev_y,      // 前一个视频对象的竖直位置位置的绝对值
    			Int vop_width,   // 前一个视频对象的水平方向大小
    			Int vop_height,  // 前一个视频对象的竖直方向大小
    			Int enable_8x8_mv,// 如果运动向量为8x8,则为1; 如果运动向量为16X16,则为0
    			Int edge,        // 围绕参考视频对象平面的边界
    			Int sr_for,      // 向前搜索的范围
    			Int f_code,      // 运动向量的搜索范围
    			Int rounding_type,// 当前视频对象环绕的类型
    			Int br_x,        // 当前视频对象水平空间位置的绝对值
    			Int br_y,		 // 当前视频对象竖直空间位置的绝对值
    			Int br_width,	 // 当前视频对象平面边界的宽度
    			Int br_height,   // 当前视频对象平面边界的高度
    			SInt *curr_comp_y,// 运动补偿的当前Y值 
    			SInt *curr_comp_u,// 运动补偿的当前U值
    			SInt *curr_comp_v,// 运动补偿的当前V值
    			Float *mad,      // 当前ME/MC的误差的容忍值
    			Float *mv16_w,   // 16X16运动向量的水平预测值
    			Float *mv16_h,   // 16X16运动微量的竖直预测值
    			Float *mv8_w,    // 8X8运动向量的水平预测值
    			Float *mv8_h,    // 8X8运动向量的竖直预测值
    			SInt *mode16     // 预测运动向量的模式
    	);


    (3、3) 运动向量图像编码函数Bits_CountMB_Motion()。

    它在VopCode中按P帧形成编码时调用的VopShapeMotText函数中被调用。(在下面我们会讲到VopShapeMotText函数)

    功能:根据宏块的编码模式和Alpha平面图像对运动图像进行编码编码后的数据接在位流后面

    返回值:Int mv_bits, 通过这个数据返回增加到位流的位数。

    描述:

    函数中不考虑图像大小的合法性;

    假设输出图像的空间已经被申请好了;

    不对透明的宏块进行编码

    在不完全透明的宏块里的8X8的透明块的运动向量被转成MV(0, 0),使它不会在重建块和运动向量预测时被使用。

     

    Int
    Bits_CountMB_Motion(
    Image   *mot_h,		/* <-- 运动向量的所有水平分量- per block  */
    Image   *mot_v,		/* <-- 运动向量的所有竖直分量 - per block */
    Image   *alpha,		/* <-- alpha平面图像 - per block  */
    Image   *modes,		/* <-- 宏块的编码模式 (SInt) - per MB*/
    Int     h,			/* <-- 宏块的水平坐标      */
    Int     v,			/* <-- 宏块的竖直坐标      */
    Int     f_code,		/* <-- 运动向量是1/2或1/4 pel 单位 1=32,2=64,...,7=2048 */
    					/* <-- flag for quarter pel MC mode */
    Int     quarter_pel,/* 1/4 pel 单位MC模式标志 */
    Image   *bs,		/* --> 输出 (SInt)   */
    Int     error_res_disable,
    Int     after_marker,
    Int     **slice_nb,
    Int arbitrary_shape
    )
    {
    	Int     vdim, hdim;	/* 宏块的大小 */
    	Float   *ph, *pv;	/* 运动向量            */
    	SInt    *pm;		/* 模式                     */
    	SInt    *pa;		/* Alpha                     */
    	Int     mode;
    	Int     bits_mot = 0;
    
    /* From switch statement */
    	Int     i, error_flag=0,mvx=0,mvy=0;
    	Float   pred_h, pred_v;
    	Float   diff_h, diff_v;
    	Int     bh, bv;
    	Int     local_f_code;/* MW QPEL 07-JUL-1998 */
    	Float   subdim;		 /* MW QPEL 07-JUL-1998 */
    
    	vdim = (Int)modes->y;
    	hdim = (Int)modes->x;
    	ph=(Float*)GetImageData(mot_h);
    	pv=(Float*)GetImageData(mot_v);
    	pm=(SInt*)GetImageData(modes);
    	pa=NULL;//(SInt*)GetImageData(alpha);
    
    
    	/* MW QPEL 07-JUL-1998 >> */
    	/* 根据quarter_pel设置local_f_code和subdim的值 */
    	if (quarter_pel)
    	{
    		local_f_code = f_code+1;
    			subdim = 4.0;
    	}
    	else
    	{
    		local_f_code = f_code;
    			subdim = 2.0;
    	}
    	/* << MW QPEL 07-JUL-1998 */
    
    	switch (mode=MB_MODE(h,v))
    	{
    		case MBM_INTRA:
    		break;
    
    		case MBM_INTER16:
    		/* 进行预测 */
    		find_pmvs(mot_h,mot_v,modes,alpha,h,v,0,MBM_TRANSPARENT,
    									  /* MW QPEL 07-JUL-1998 */
    		quarter_pel,&error_flag,&mvx,&mvy,0);
    
    		pred_h=((Float)mvx)/subdim;			  /* MW QPEL 07-JUL-1998 */
    		pred_v=((Float)mvy)/subdim;		  /* MW QPEL 07-JUL-1998 */
    
    										  /* MW QPEL 07-JUL-1998 */
    		bits_mot += WriteMVcomponent(local_f_code, (Int)(subdim*(MBV_H(h,v) - pred_h)), bs);
    										  /* MW QPEL 07-JUL-1998 */
    		bits_mot += WriteMVcomponent(local_f_code, (Int)(subdim*(MBV_V(h,v) - pred_v)), bs);
    
    		break;
    
    		case MBM_INTER8:
    		i=1;
    		for (bv=0; bv<=1; bv++)
    			for (bh=0; bh<=1; bh++)
    		{
    			find_pmvs(mot_h,mot_v,modes,alpha,h,v,i,MBM_TRANSPARENT,
    								  /* MW QPEL 07-JUL-1998 */
    			quarter_pel,&error_flag,&mvx,&mvy,0);
    
    			pred_h=((Float)mvx)/subdim;		  /* MW QPEL 07-JUL-1998 */
    			pred_v=((Float)mvy)/subdim;	  /* MW QPEL 07-JUL-1998 */
    
    			i++;
    
    			diff_h=BV_H(h,v,bh,bv)-pred_h;
    			diff_v=BV_V(h,v,bh,bv)-pred_v;
    
    									  /* MW QPEL 07-JUL-1998 */
    			bits_mot += WriteMVcomponent(local_f_code, (Int)(subdim*diff_h), bs);
    									  /* MW QPEL 07-JUL-1998 */
    			bits_mot += WriteMVcomponent(local_f_code, (Int)(subdim*diff_v), bs);
    		}
    		break;
    	}
    
    	return bits_mot;
    }
    
    


     

     

     

    Void											  /* MVP/Noel */
    find_pmvs(
    Image  *mot_x,									  /* x-motion vector field                             */
    Image  *mot_y,									  /* y-motion vector field                             */
    Image  *MB_decisions,							  /* MB modes                                          */
    Image  *B_decisions,							  /* field with number of vectors per MB               */
    Int    x,										  /* xpos of the MB in multiples of 16 (hor coord)     */
    Int    y,										  /* ypos of the MB in multiples of 16 (ver coord)     */
    Int    block,									  /* block 号 (0 if one vector per MB, 1..4 else)  */
    Int    transparent_value,						  /* 透明度的值 (0=enc, 2=dec)     */
    Int    quarter_pel,   /* MW QPEL 06-JUL-1998 */	  /* 预测量化器的标志 pel mc                   */
    Int    *error_flag,								  /* 看是否一个错误发生            */
    Int    *mvx,									  /* 水平预测运动向量 [ in half-pixels units ]  */
    Int    *mvy,									  /* 竖直预测运动向量 [ in half-pixels units ]  */
    Int    newgob
    )
    {
    	Float   p1x,p2x,p3x;
    	Float   p1y,p2y,p3y;
    	Int     xin1, xin2, xin3;
    	Int     yin1, yin2, yin3;
    	Int     vec1, vec2, vec3;
    	Int     rule1, rule2, rule3;
    	Int     subdim;								  /* MW QPEL 06-JUL-1998 */
    	Float   *motxdata = (Float *) GetImageData(mot_x);
    	Float   *motydata = (Float *) GetImageData(mot_y);
    	Int     xM = GetImageSizeX(mot_x);
    	Int xB = xM;
    	Int     mb_mode, sum;
    
    	/* MW QPEL 06-JUL-1998 >> */
    	if (quarter_pel)
    	{
    		subdim=4;
    	}
    	else
    	{
    		subdim=2;
    	}
    	/* << MW QPEL 06-JUL-1998 */
    
    	/* In a previous version, a MB vector (block = 0) was predicted the same way
    	   as block 1, which is the most likely interpretation of the VM.
    
    	   Therefore, if we have advanced pred. mode, and if all MBs around have
    	   only one 16x16 vector each, we chose the appropiate block as if these
    	   MBs have 4 vectors.
    
    	   This different prediction affects only 16x16 vectors of MBs with
    	   transparent blocks.
    
    	   In the current version, we choose for the 16x16 mode the first
    	non-transparent block in the surrounding MBs
    	*/
    
    	switch (block)
    	{
    		case 0:
    			vec1 = 1 ; yin1 = y  ; xin1 = x-1;
    			vec2 = 2 ; yin2 = y-1; xin2 = x;
    			vec3 = 2 ; yin3 = y-1; xin3 = x+1;
    			break;
    		case 1:
    			vec1 = 1 ; yin1 = y  ; xin1 = x-1;
    			vec2 = 2 ; yin2 = y-1; xin2 = x;
    			vec3 = 2 ; yin3 = y-1; xin3 = x+1;
    			break;
    		case 2:
    			vec1 = 0 ; yin1 = y  ; xin1 = x;
    			vec2 = 3 ; yin2 = y-1; xin2 = x;
    			vec3 = 2 ; yin3 = y-1; xin3 = x+1;
    			break;
    		case 3:
    			vec1 = 3 ; yin1 = y  ; xin1 = x-1;
    			vec2 = 0 ; yin2 = y  ; xin2 = x;
    			vec3 = 1 ; yin3 = y  ; xin3 = x;
    			break;
    		case 4:
    			vec1 = 2 ; yin1 = y  ; xin1 = x;
    			vec2 = 0 ; yin2 = y  ; xin2 = x;
    			vec3 = 1 ; yin3 = y  ; xin3 = x;
    			break;
    		default:
    			printf("Illegal block number in find_pmv (mot_decode.c)");
    			*error_flag = 1;
    			*mvx=*mvy=0;
    			return ;
    	}
    
    	if (block==0)
    	{
    		/* according to the motion encoding, we must choose a first non-transparent
    		   block in the surrounding MBs (16-mode)
    		 */
    
    		if (x>0 /*&& ValidCandidateMVP(x,y,xin1,yin1,vec1,xB,transparent_value,
    			MB_decisions,dcsn_data)*/)
    			rule1 = 0;
    		else
    			rule1 = 1;
    
    		if ((y>0 && newgob==0) /*&& ValidCandidateMVP(x,y,xin2,yin2,vec2,xB,transparent_value,
    			MB_decisions,dcsn_data)*/)
    			rule2 = 0;
    		else
    			rule2 = 1;
    
    		if ((x != xB/2 -1) &&
    			((y>0 && newgob==0)) /*&& ValidCandidateMVP(x,y,xin3,yin3,vec3,xB,transparent_value,
    			MB_decisions,dcsn_data)*/)
    			rule3 = 0;
    		else
    			rule3 = 1;
    	}
    
    	else
    	{
    		/* check borders for single blocks (advanced mode) */
    		/* rule 1 */
    												  /* left border */
    		if (((block == 1 || block == 3) && (x == 0))  /*||
    			/* left block/MB is transparent *
    			(!ValidCandidateMVP(x,y,xin1,yin1,vec1,xB,transparent_value,
    			MB_decisions,dcsn_data))*/)
    			rule1 = 1;
    		else
    			rule1 = 0;
    
    		/* rule 2 */
    												  /* top border */
    		if (((block == 1 || block == 2) && (y == 0))  /*||
    			/* top block/MB is transparent *
    			(!ValidCandidateMVP(x,y,xin2,yin2,vec2,xB,transparent_value,
    			MB_decisions,dcsn_data))*/)
    			rule2 = 1;
    		else
    			rule2 = 0;
    
    		/* rule 3 */
    		if (((block == 1 || block == 2) && (x == xB/2 -1 || y == 0)) /*||
    			/* right & top border *
    			/* right block/MB is transparent *
    			(!ValidCandidateMVP(x,y,xin3,yin3,vec3,xB,transparent_value,
    			MB_decisions,dcsn_data))*/)
    			rule3 = 1;
    		else
    			rule3 = 0;
    
    	}
    
    	if (rule1 )
    	{
    		p1x = p1y = 0;
    	}
    	else if (((mb_mode = ModeMB(MB_decisions,xin1,yin1)) >= MBM_FIELD00) && (mb_mode <= MBM_FIELD11))
    	{
    		/* MW QPEL 06-JUL-1998 */
    		sum = subdim*(BV(motxdata, xM, xin1, yin1, 0, 0) + BV(motxdata, xM, xin1, yin1, 1, 0));
    		p1x = (Float)((sum & 3) ? ((sum | 2) >> 1) : (sum >> 1))/(Float)subdim;
    		sum = subdim*(BV(motydata, xM, xin1, yin1, 0, 0) + BV(motydata, xM, xin1, yin1, 1, 0));
    		p1y = (Float)((sum & 3) ? ((sum | 2) >> 1) : (sum >> 1))/(Float)subdim;
    	}
    	else
    	{
    		p1x = BV(motxdata, xM, xin1, yin1, vec1&0x1, vec1>>1 );
    		p1y = BV(motydata, xM, xin1, yin1, vec1&0x1, vec1>>1 );
    	}
    
    	if (rule2)
    	{
    		p2x = p2y = 0 ;
    	}
    	else if (((mb_mode = ModeMB(MB_decisions,xin2,yin2)) >= MBM_FIELD00) && (mb_mode <= MBM_FIELD11))
    	{
    		/* MW QPEL 06-JUL-1998 */
    		sum = subdim*(BV(motxdata, xM, xin2, yin2, 0, 0) + BV(motxdata, xM, xin2, yin2, 1, 0));
    		p2x = (Float)((sum & 3) ? ((sum | 2) >> 1) : (sum >> 1))/(Float)subdim;
    		sum = subdim*(BV(motydata, xM, xin2, yin2, 0, 0) + BV(motydata, xM, xin2, yin2, 1, 0));
    		p2y = (Float)((sum & 3) ? ((sum | 2) >> 1) : (sum >> 1))/(Float)subdim;
    	}
    	else
    	{
    		p2x = BV(motxdata, xM, xin2, yin2, vec2&0x1, vec2>>1 );
    		p2y = BV(motydata, xM, xin2, yin2, vec2&0x1, vec2>>1 );
    	}
    
    	if (rule3 )
    	{
    		p3x = p3y =0;
    	}
    	else if (((mb_mode = ModeMB(MB_decisions,xin3,yin3)) >= MBM_FIELD00) && (mb_mode <= MBM_FIELD11))
    	{
    		/* MW QPEL 06-JUL-1998 */
    		sum = subdim*(BV(motxdata, xM, xin3, yin3, 0, 0) + BV(motxdata, xM, xin3, yin3, 1, 0));
    		p3x = (Float)((sum & 3) ? ((sum | 2) >> 1) : (sum >> 1))/(Float)subdim;
    		sum = subdim*(BV(motydata, xM, xin3, yin3, 0, 0) + BV(motydata, xM, xin3, yin3, 1, 0));
    		p3y = (Float)((sum & 3) ? ((sum | 2) >> 1) : (sum >> 1))/(Float)subdim;
    	}
    	else
    	{
    		p3x = BV(motxdata, xM, xin3, yin3, vec3&0x1, vec3>>1 );
    		p3y = BV(motydata, xM, xin3, yin3, vec3&0x1, vec3>>1 );
    	}
    
    	if (rule1 && rule2 && rule3 )
    	{
    		/* all MBs are outside the VOP */
    		*mvx=*mvy=0;
    	}
    	else if (rule1+rule2+rule3 == 2)
    	{
    		/* two of three are zero */
    		/* MW QPEL 06-JUL-1998 */
    		*mvx=(Int) subdim*(p1x+p2x+p3x);		  /* MW QPEL 06-JUL-1998 */
    		*mvy=(Int) subdim*(p1y+p2y+p3y);		  /* MW QPEL 06-JUL-1998 */
    	}
    	else
    	{
    		/* MW QPEL 06-JUL-1998 */
    												  /* MW QPEL 06-JUL-1998 */
    		*mvx=(Int)(subdim*(p1x+p2x+p3x-MAX(p1x,MAX(p2x,p3x))-MIN(p1x,MIN(p2x,p3x))));
    												  /* MW QPEL 06-JUL-1998 */
    		*mvy=(Int)(subdim*(p1y+p2y+p3y-MAX(p1y,MAX(p2y,p3y))-MIN(p1y,MIN(p2y,p3y))));
    	}
    
    	#ifdef _DEBUG_PMVS_
    	fprintf(stdout,"find_pmvs (%2d,%2d, rule %1d%1d%1d) :\np1 %6.2f / %6.2f\np2 %6.2f / %6.2f\np3 %6.2f / %6.2f\n",x,y,rule1,rule2,rule3,p1x,p1y,p2x,p2y,p3x,p3y);
    	#endif
    
    	return;
    }
    
    


     (4) 纹理编码

    这部分代码所在的文件是 text_code.h、text_code.c.

    (4、1)帧内纹理编码函数VopCodeShapeTextIntraCom()(VOP内部纹理编码)。本函数实现的是一个视频对象平面的帧内纹理编码

    它包括了组合形状编码模式和纹理编码模式

    它在VopCode中按 I 帧形成编码时调用的。

    Void VopCodeShapeTextIntraCom(
    Vop *curr,					// 当前要进行编码的视频对象
    Vop *rec_curr,				// 当前重建的视频对象平面
    Image *mottext_bitstream)
    {
    	Int QP = GetVopIntraQuantizer(curr);
    	Int Mode = MODE_INTRA;
    	Int* qcoeff;
    	Int i, j;
    	Int CBP, COD;
    	Int CBPY, CBPC;
    	Int num_pixels = GetImageSizeX(GetVopY(curr));
    	Int num_lines = GetImageSizeY(GetVopY(curr));
    	Int vop_type;
    	Int ***DC_store;						// 为做 DC/AC 预测 存储宏块系数
    	Int MB_width = num_pixels / MB_SIZE;	// 宏块的宽
    	Int MB_height = num_lines / MB_SIZE;	// 宏块的高
    	Int m;
    	Int ACpred_flag=-1;
    	Int direction[6];
    	Int switched=0;
    	Int DQUANT =0;
    
    	Bits nbits, *bits;
    	bits = &nbits;
    
    	qcoeff = (Int *) malloc (sizeof (Int) * 384);
    
    	#ifdef _RC_DEBUG_
    	fprintf(ftrace, "RC - VopCodeShapeTextIntraCom(): ---> CODING WITH: %d \n",QP);
    	#endif
    
    	for (i = 0; i < 6; i++)
    		direction[i] = 0;
    
    	/* 给3D矩阵分配空间,矩阵是用来跟踪为DC或AC判断预测服务的预测值 */
    	DC_store = (Int ***)calloc(MB_width*MB_height, sizeof(Int **));
    	for (i = 0; i < MB_width*MB_height; i++)
    	{
    		DC_store[i] = (Int **)calloc(6, sizeof(Int *));
    		for (j = 0; j < 6; j++)
    			DC_store[i][j] = (Int *)calloc(15, sizeof(Int));
    	}
    
    	Bits_Reset(bits);
    	vop_type = PCT_INTRA;
    
    	// 对宏块循环
    	for (j = 0; j < num_lines/MB_SIZE; j++)		  
    	{
    		for (i = 0; i < num_pixels/MB_SIZE; i++)
    		{
    			DQUANT = 0;
    
    			COD = 0;
    			bits->no_intra++;
    
    			// 对宏块编码
    			// curee rec_curr qcoeff是输出参数
    			CodeMB(curr, rec_curr, NULL, i*MB_SIZE, j*MB_SIZE,
    				num_pixels, QP+DQUANT, MODE_INTRA, qcoeff);
    
    			m =0;
    
    			DC_store[j*MB_width+i][0][m] = qcoeff[m]*cal_dc_scaler(QP+DQUANT,1);
    			DC_store[j*MB_width+i][1][m] = qcoeff[m+64]*cal_dc_scaler(QP+DQUANT,1);
    			DC_store[j*MB_width+i][2][m] = qcoeff[m+128]*cal_dc_scaler(QP+DQUANT,1);
    			DC_store[j*MB_width+i][3][m] = qcoeff[m+192]*cal_dc_scaler(QP+DQUANT,1);
    			DC_store[j*MB_width+i][4][m] = qcoeff[m+256]*cal_dc_scaler(QP+DQUANT,2);
    			DC_store[j*MB_width+i][5][m] = qcoeff[m+320]*cal_dc_scaler(QP+DQUANT,2);
    				
    			for (m = 1; m < 8; m++)
    			{
    				DC_store[j*MB_width+i][0][m] = qcoeff[m];
    				DC_store[j*MB_width+i][1][m] = qcoeff[m+64];
    				DC_store[j*MB_width+i][2][m] = qcoeff[m+128];
    				DC_store[j*MB_width+i][3][m] = qcoeff[m+192];
    				DC_store[j*MB_width+i][4][m] = qcoeff[m+256];
    				DC_store[j*MB_width+i][5][m] = qcoeff[m+320];
    			}
    			for (m = 0; m < 7; m++)
    			{
    				DC_store[j*MB_width+i][0][m+8] = qcoeff[(m+1)*8];
    				DC_store[j*MB_width+i][1][m+8] = qcoeff[(m+1)*8+64];
    				DC_store[j*MB_width+i][2][m+8] = qcoeff[(m+1)*8+128];
    				DC_store[j*MB_width+i][3][m+8] = qcoeff[(m+1)*8+192];
    				DC_store[j*MB_width+i][4][m+8] = qcoeff[(m+1)*8+256];
    				DC_store[j*MB_width+i][5][m+8] = qcoeff[(m+1)*8+320];
    			}
    
    			CBP = FindCBP(qcoeff,Mode,64);
    
    			/* 进行DC/AC预测,适当地量化系数的值*/
    			if (GetVopIntraACDCPredDisable(curr) == 0)
    			{
    				// 做 DC/AC 预测
    				ACpred_flag = doDCACpred(qcoeff, &CBP, 64, i, j, DC_store,
    					QP+DQUANT, MB_width,
    					direction,GetVopMidGrey(curr));
    			}
    			else
    				ACpred_flag = -1;
    
    			switched = IntraDCSwitch_Decision(Mode,
    				GetVopIntraDCVlcThr(curr),
    				QP);
    			if (switched)
    				CBP = FindCBP(qcoeff,MODE_INTER,64);
    			if (DQUANT) Mode=MODE_INTRA_Q;else Mode=MODE_INTRA;
    
    			QP+=DQUANT;
    
    			CBPY = CBP >> 2;
    			CBPY = CBPY & 15;			  /* last 4 bits */
    			CBPC = CBP & 3;				  /* last 2 bits */
    
    			Bits_CountMB_combined (DQUANT, Mode, COD, ACpred_flag, CBP,
    				vop_type,
    				bits, mottext_bitstream,/*MB_transp_pattern*/NULL);
    
    			/* 增加变量 intra_dcpred_diable */
    			MB_CodeCoeff(bits, qcoeff, Mode, CBP, 64,
    				GetVopIntraACDCPredDisable(curr),
    				NULL, mottext_bitstream,
    				/*MB_transp_pattern*/NULL, direction,
    				1 /*通过GetVopErrorResDisable(curr)得到*/,
    				0 /*通过GetVopReverseVlc(curr)得到*/,
    				switched,
    				0 /*表示当前视频对象平面交替扫描控制变量是0  curr->alternate_scan*/);
    		}
    	}
    
    	/* Free allocated memory for 3D matrix */
    	for (i = 0; i < MB_width*MB_height; i++)
    	{
    		for (j = 0; j < 6; j++)
    			free(DC_store[i][j]);
    		free(DC_store[i]);
    	}
    	free(DC_store);
    
    	free ((Char*)qcoeff);
    }
    
    

     

    主要调用了CodeMB对宏块进行编码

    /***********************************************************CommentBegin******
     *
     * -- CodeMB -- Code, 用substraction和增加的操作来编码并重建宏块 
     *
     * 输入参数 :
     *	Int x_pos   宏块的x位置
     *	Int y_pos	宏块的y位置
     *	UInt width	Vop绑定的框的宽 (unpadded size)
     *	Int QP		量化器参数
     *	Int Mode	宏块编码的模式
     *
     * 输出参数 :
     *	Vop *curr	当前的Vop,未编码
     *	Vop *rec_curr	当前Vop,已编码和重建了
     *  Vop *comp   当前Vop,运动 compensated
     *  Int *qcoeff 系数块 (384 * sizeof(Int))
     *
     ***********************************************************CommentEnd********/
    
    Void CodeMB(Vop *curr, Vop *rec_curr, Vop *comp, Int x_pos, Int y_pos, UInt width,
    Int QP, Int Mode, Int *qcoeff)
    {
    	Int k;
    	Int fblock[6][8][8];
    	Int coeff[384];
    	Int *coeff_ind;
    	Int *qcoeff_ind;
    	Int* rcoeff_ind;
    	Int x, y;
    	SInt *current, *recon, *compensated = NULL;
    	UInt xwidth;
    	Int iblock[6][8][8];
    	Int rcoeff[6*64];
    	Int i, j;
    	Int type;									  /* luma = 1, chroma = 2 */
    //	Int *qmat;
    	SInt tmp[64];
    	Int s;
    
    	int operation = curr->prediction_type;
    	/* This variable is for combined operation.
    	If it is an I_VOP, then MB in curr is reconstruct into rec_curr,
    	and comp is not used at all (i.e., it can be set to NULL).
    	If it is a P_VOP, then MB in curr is reconstructed, and the result
    	added with MB_comp is written into rec_curr. 
    	- adamli 11/19/2000 */
    
    	Int max = GetVopBrightWhite(curr);
    	/* This variable is the max value for the clipping of the reconstructed image. */
    
    	coeff_ind = coeff;
    	qcoeff_ind = qcoeff;
    	rcoeff_ind = rcoeff;
    
    	for (k = 0; k < 6; k++)
    	{
    		switch (k)
    		{
    			case 0:
    				x = x_pos;
    				y = y_pos;
    				xwidth = width;
    				current = (SInt *) GetImageData (GetVopY (curr));
    				break;
    			case 1:
    				x = x_pos + 8;
    				y = y_pos;
    				xwidth = width;
    				current = (SInt *) GetImageData (GetVopY (curr));
    				break;
    			case 2:
    				x = x_pos;
    				y = y_pos + 8;
    				xwidth = width;
    				current = (SInt *) GetImageData (GetVopY (curr));
    				break;
    			case 3:
    				x = x_pos + 8;
    				y = y_pos + 8;
    				xwidth = width;
    				current = (SInt *) GetImageData (GetVopY (curr));
    				break;
    			case 4:
    				x = x_pos / 2;
    				y = y_pos / 2;
    				xwidth = width / 2;
    				current = (SInt *) GetImageData (GetVopU (curr));
    				break;
    			case 5:
    				x = x_pos / 2;
    				y = y_pos / 2;
    				xwidth = width / 2;
    				current = (SInt *) GetImageData (GetVopV (curr));
    				break;
    			default:
    				break;
    		}
    		// 块预测
    		BlockPredict (current, x, y, xwidth, fblock[k]);
    	}
    
    	for (k = 0; k < 6; k++)
    	{
    		s = 0;
    		for (i = 0; i < 8; i++)
    			for (j = 0; j < 8; j++)
    				tmp[s++] = (SInt) fblock[k][i][j];
    #ifndef _MMX_
    		fdct_enc(tmp);
    #else
    		fdct_mm32(tmp);
    #endif
    		for (s = 0; s < 64; s++)
    			coeff_ind[s] = (Int) tmp[s];
    
    		if (k < 4) type = 1;
    		else type = 2;
    
    		/* For this release, only H263 quantization is supported. - adamli */
    		BlockQuantH263(coeff_ind,QP,Mode,type,qcoeff_ind,
    			GetVopBrightWhite(curr),1);
    		BlockDequantH263(qcoeff_ind,QP,Mode,type,rcoeff_ind,1, 0, GetVopBitsPerPixel(curr));
    
    		for (s = 0; s < 64; s++)
    			tmp[s] = (SInt) rcoeff_ind[s];
    #ifndef _MMX_
    		idct_enc(tmp);
    #else
    		Fast_IDCT(tmp);
    #endif
    		s = 0;
    		for (i = 0; i < 8; i++)
    			for (j = 0; j < 8; j++)
    				iblock[k][i][j] = (Int)tmp[s++];
    
    		coeff_ind += 64;
    		qcoeff_ind += 64;
    		rcoeff_ind += 64;
    
    		if (Mode == MODE_INTRA||Mode==MODE_INTRA_Q)
    			for (i = 0; i < 8; i++)
    				for (j = 0; j < 8; j ++)
    					iblock[k][i][j] = MIN (GetVopBrightWhite(curr), MAX (0, iblock[k][i][j]));
    
    		switch (k)
    		{
    			case 0:
    			case 1:
    			case 2:
    				continue;
    
    			case 3:
    				recon = (SInt *) GetImageData (GetVopY (rec_curr));
    				if (operation == P_VOP) compensated = (SInt *) GetImageData (GetVopY (comp));
    				BlockRebuild (recon, compensated, operation, max, x_pos,     y_pos,     width, 16, iblock[0]);
    				BlockRebuild (recon, compensated, operation, max, x_pos + 8, y_pos,     width, 16, iblock[1]);
    				BlockRebuild (recon, compensated, operation, max, x_pos,     y_pos + 8, width, 16, iblock[2]);
    				BlockRebuild (recon, compensated, operation, max, x_pos + 8, y_pos + 8, width, 16, iblock[3]);
    				continue;
    
    			case 4:
    				recon = (SInt *) GetImageData (GetVopU (rec_curr));
    				if (operation == P_VOP) compensated = (SInt *) GetImageData (GetVopU (comp));
    				BlockRebuild (recon, compensated, operation, max,
    					x_pos/2, y_pos/2, width/2, 8, iblock[4]);
    				continue;
    
    			case 5:
    				recon = (SInt *) GetImageData (GetVopV (rec_curr));
    				if (operation == P_VOP) compensated = (SInt *) GetImageData (GetVopV (comp));
    				BlockRebuild (recon, compensated, operation, max,
    					x_pos/2, y_pos/2, width/2, 8, iblock[5]);
    				continue;
    		}
    	}
    
    	return;
    }
    
    


     (4、2)形状纹理编码函数VopShapeMotText()。该函数的功能是进行运动形状编码。

    它在Vop_code函数中按P帧形成编码时被调用的。


     * 输入参数 :
     * Vop *curr : 当前要进行编码的视频对象平面
     *  Vop *rec_prev: 先前已重建的视频对象平面
     *  Image *mot_x : 运动向量的x轴坐标值
     *  Image *mot_y : 运动向量的y轴坐标值
     *  Image *MB_decisions: 包含了每一个宏块的编码模式
     * Int f_code_for: 运动向量的搜索范围 1/2 pel: 1=32,2=64,...,7=2048
     *      Image* AB_SizeConversionDecisions:视频对象平面转为视频宏块过程中的块大小
     *      Image* AB_first_MMR_values :      最大宏块阀值
     *      Int intra_dcpred_disable : 不允许帧内DC判断预测的控制变量
     *      VolConfig *vol_config : 设置信息
     *      Int rc_type : 数据传输率控制类型
     *
     * 输出参数 :
     * Vop *rec_curr : 当前重建的视频对象平面
     *  Image *mottext_bitstream : 编码后的比特流
     *  Bits *bits : 数据统计信

    Void VopShapeMotText (Vop *curr, Vop *comp, 
    Image *MB_decisions, Image *mot_x, Image *mot_y,
    Int f_code_for, 
    Int intra_acdc_pred_disable,
    Vop *rec_curr,
    Image *mottext_bitstream
    )
    {
    	Int Mode=0;
    	Int QP = GetVopQuantizer(curr);
    	Int* qcoeff=NULL;
    	Int i, j;
    	Int CBP;
    	Int COD;
    	Int CBPY, CBPC;
    	Int MB_in_width, MB_in_height, B_in_width, mbnum, boff;
    	SInt p;
    	SInt *ptr=NULL;
    	Float *motx_ptr=NULL, *moty_ptr=NULL;
    	Int num_pixels;
    	Int num_lines;
    	Int vop_type=PCT_INTER;
    	Int ***DC_store=NULL;
    	Int m, n;
    	Int ACpred_flag=-1;
    	Int direction[6];
    	Int switched=0;
    	Int DQUANT=0;
    
    	Bits nbits, *bits;
    	bits = &nbits;
    
    	qcoeff = (Int *) malloc (sizeof (Int) * 384);
    
    	num_pixels = GetImageSizeX(GetVopY(curr));
    	num_lines = GetImageSizeY(GetVopY(curr));
    	MB_in_width = num_pixels / MB_SIZE;
    	MB_in_height = num_lines / MB_SIZE;
    	B_in_width = 2 * MB_in_width;
    
    	for (i = 0; i < 6; i++) direction[i] = 0;
    
    	#ifdef _RC_DEBUG_
    	printf("RC - VopShapeMotText(): ---> CODING WITH: %d \n",QP);
    	#endif
    
    	/* 给3D矩阵分配空间矩阵时用来跟踪为DC或者AC判断预测服务的预测值*/
    	DC_store = (Int ***)calloc(MB_in_width*MB_in_height,
    		sizeof(Int **));
    	for (i = 0; i < MB_in_width*MB_in_height; i++)
    	{
    		DC_store[i] = (Int **)calloc(6, sizeof(Int *));
    		for (j = 0; j < 6; j++)
    			DC_store[i][j] = (Int *)calloc(15, sizeof(Int));
    	}
    
    	Bits_Reset (bits);
    
    	vop_type = PCT_INTER;
    
    	ptr = (SInt *) GetImageData(MB_decisions);
    	motx_ptr = (Float *) GetImageData(mot_x);
    	moty_ptr = (Float *) GetImageData(mot_y);
    
    	for (j = 0; j < num_lines/MB_SIZE; j++)
    	{
    		for (i = 0; i < MB_in_width; i++)
    		{
    			switched=0;
    			p = *ptr; // 获得此宏块的编码模式
    			DQUANT = 0;
    
    			/* 用默认的预测值对DC判断进行填充 */
    			for (m = 0; m < 6; m++)
    			{
    				DC_store[j*MB_in_width+i][m][0] = GetVopMidGrey(curr)*8;
    				for (n = 1; n < 15; n++)
    					DC_store[j*MB_in_width+i][m][n] = 0;
    			}
    
    			switch (p)
    			{
    
    				case MBM_INTRA:
    					Mode = (DQUANT == 0) ? MODE_INTRA : MODE_INTRA_Q;
    					bits->no_intra++;
    					break;
    
    				case MBM_INTER16:
    					Mode = (DQUANT == 0) ? MODE_INTER : MODE_INTER_Q;
    					bits->no_inter++;
    					break;
    
    				case MBM_INTER8:
    					Mode = MODE_INTER4V;
    					bits->no_inter4v++;
    					DQUANT = 0;				  /* 这里不能为8X8模式改变QP值*/
    					break;
    
    				default:
    					printf("invalid MB_decision value :%d\n", p);
    					exit(0);
    			}
    			// 对宏块进行编码
    			CodeMB (curr, rec_curr, comp, i*MB_SIZE, j*MB_SIZE,
    				num_pixels, QP + DQUANT, Mode, qcoeff);
    
    			mbnum  = j*MB_in_width + i;
    			boff = (2 * (mbnum  / MB_in_width) * B_in_width
    				+ 2 * (mbnum % MB_in_width));
    
    			CBP = FindCBP(qcoeff,Mode,64);
    
    			if ((CBP == 0) && (p == 1) && (*(motx_ptr +boff) == 0.0)
    				&& (*(moty_ptr +boff) == 0.0))
    			{
    				COD = 1;				  /* 跳过的宏块 */
    				BitstreamPutBits(mottext_bitstream, (long) (COD), 1L);
    				bits->COD ++;
    
    				*ptr = SKIPP;
    				Mode = MODE_INTER;
    			}
    			else
    			{
    				COD = 0;				  /* 已编码的宏块 */
    
    				if ((Mode == MODE_INTRA) || (Mode == MODE_INTRA_Q))
    				{
    
    					/* 存储以后预测所需要的量化系数值 */
    					m =0;
    
    					DC_store[j*MB_in_width+i][0][m] = qcoeff[m]*cal_dc_scaler(QP+DQUANT,1);
    					DC_store[j*MB_in_width+i][1][m] = qcoeff[m+64]*cal_dc_scaler(QP+DQUANT,1);
    					DC_store[j*MB_in_width+i][2][m] = qcoeff[m+128]*cal_dc_scaler(QP+DQUANT,1);
    					DC_store[j*MB_in_width+i][3][m] = qcoeff[m+192]*cal_dc_scaler(QP+DQUANT,1);
    					DC_store[j*MB_in_width+i][4][m] = qcoeff[m+256]*cal_dc_scaler(QP+DQUANT,2);
    					DC_store[j*MB_in_width+i][5][m] = qcoeff[m+320]*cal_dc_scaler(QP+DQUANT,2);
    
    					for (m = 1; m < 8; m++)
    					{
    						DC_store[j*MB_in_width+i][0][m] = qcoeff[m];
    						DC_store[j*MB_in_width+i][1][m] = qcoeff[m+64];
    						DC_store[j*MB_in_width+i][2][m] = qcoeff[m+128];
    						DC_store[j*MB_in_width+i][3][m] = qcoeff[m+192];
    						DC_store[j*MB_in_width+i][4][m] = qcoeff[m+256];
    						DC_store[j*MB_in_width+i][5][m] = qcoeff[m+320];
    					}
    					for (m = 0; m < 7; m++)
    					{
    						DC_store[j*MB_in_width+i][0][m+8] = qcoeff[(m+1)*8];
    						DC_store[j*MB_in_width+i][1][m+8] = qcoeff[(m+1)*8+64];
    						DC_store[j*MB_in_width+i][2][m+8] = qcoeff[(m+1)*8+128];
    						DC_store[j*MB_in_width+i][3][m+8] = qcoeff[(m+1)*8+192];
    						DC_store[j*MB_in_width+i][4][m+8] = qcoeff[(m+1)*8+256];
    						DC_store[j*MB_in_width+i][5][m+8] = qcoeff[(m+1)*8+320];
    					}
    
    					if (intra_acdc_pred_disable == 0)
    						ACpred_flag = doDCACpred(qcoeff, &CBP, 64, i, j,
    							DC_store,
    							QP+DQUANT, MB_in_width,
    							direction,GetVopMidGrey(curr));
    					else
    						ACpred_flag = -1;  /* Not to go into bitstream */
    				}
    
    				switched = IntraDCSwitch_Decision(Mode,
    					GetVopIntraDCVlcThr(curr),
    					QP);
    				if (switched)
    					CBP = FindCBP(qcoeff,MODE_INTER,64);
    
    				CBPY = CBP >> 2;
    				CBPY = CBPY & 15;		  /* 取最后 4 bits  */
    				CBPC = CBP & 3;			  /* 取最后 2 bits */
    
    				Bits_CountMB_combined (DQUANT, Mode, COD, ACpred_flag, CBP,
    					vop_type, bits,
    					mottext_bitstream,/*MB_transp_pattern*/NULL);
    
    				Bits_CountMB_Motion( mot_x, mot_y, NULL, 
    					MB_decisions, i, j, f_code_for, 0 /*quarter_pel*/,
    					mottext_bitstream,
    					1 /*GetVopErrorResDisable(curr)*/, 0,
    					(Int **)NULL, 0 /*GetVopShape(curr)*/);
    
    				MB_CodeCoeff(bits, qcoeff, Mode, CBP, 64,
    					intra_acdc_pred_disable,
    					NULL, mottext_bitstream,
    					/*MB_transp_pattern*/NULL, direction,
    					1/*GetVopErrorResDisable(curr)*/,
    					0/*GetVopReverseVlc(curr)*/,
    					switched,
    					0 /*curr->alternate_scan*/);
    			}
    
    			ptr++; // 取下一宏块的编码模式
    
    		} /* for i loop */
    	} /* for j loop */
    
    	/* 释放已分配的3D矩阵 */
    	for (i = 0; i < MB_in_width*MB_in_height; i++)
    	{
    
    		for (j = 0; j < 6; j++)
    			free(DC_store[i][j]);
    		free(DC_store[i]);
    	}
    	free(DC_store);
    
    	free ((Char*)qcoeff);
    
    }
    


     

    (4、3) DC/AC 系数预测函数MB_CodeCoeff()。 该函数的功能是进行DC/AC系数预测。

     

     

     

     

    展开全文
  • 欢迎大家提出意见,一起讨论! 需要源码的请与我联系。   1、H.263编码技术 ...H.263是国际电联ITU-T的一个标准草案,是为低码流通信而设计...H.263的编码算法与H.261一样,但做了一些改善和改变,以提高性能和纠错能力

    转载请标明是引用于 http://blog.csdn.net/chenyujing1234 

    欢迎大家提出意见,一起讨论!

    需要源码的请与我联系。

     

    1、H.263编码技术

    H.263是国际电联ITU-T的一个标准草案,是为低码流通信而设计的。但实际上这个标准可用在很宽的码流范围

    而非只用于低码流应用。H.263的编码算法与H.261一样,但做了一些改善和改变,以提高性能和纠错能力。
    H.263标准在低码率下能够提供比H.261更好的图像效果,两者的区别有:

    (1)H.263的运动补偿使用半象素精度,而H.261则用全象素精度和循环滤波;

    (2)数据流层次结构的某些部分在H.263中是可选的,使得编解码可以配置成更低的数据率或更好的纠错能力

    (3)H.263包含四个可协商的选项以改善性能;

    (4)H.263采用无限制的运动向量以及基于语法的算术编码

    (5)采用事先预测和与MPEG中的P-B帧一样的帧预测方法;

    (6)H.263支持5种分辨率,即除了支持H.261中所支持的QCIF和CIF外,还支持SQCIF、4CIF和16CIF,SQCIF。

    H.263是为了满足诸如视频会议、视频电子邮件和可视电话等视频通信业务发展的需要,并且克服公用电话交换网(PSTN)和

    无线网络传输速率的瓶颈制定的视频编码标准。

    网络应用最重要的目标之一就是进行多媒体通信。多媒体信息主要包括图像、声音和文本三大类,其中视频、音频等信号的信息量非常大

    因此对这些数据进行有效的表达和适当处理是非常重要的。

    尽管H.263编码的主体框架仍然延续H.261标准编码的主要框架,但在许多方面对H.261进行了改进和扩充,在编码算法复杂度增加很少的情况下,

    能提供更好的图像质量、更低的速率。

    1、1  H.263编码结构说明

    H.263定义了视频编码的数据结构,以便解码器能从接收到的码流中根据数据结构的定义进行解码重建图像

    这种数据结构是一种分级的数据结构,从大到小依次是图像帧、块组、宏块和块这4层结构。

    以CIF图像帧(352X288)为例:

    一帧图像可以分为12个块组,分别为GOB0-GOB11。

    GOB0GOB1
    GOB2GOB3
    GOB4GOB5
    GOB6GOB7
    GOB8GOB9
    GOB10GOB11

    每一个GOB又分为33个宏块。

    MB0MB1MB2MB3MB4MB5MB6MB7MB8MB9MB10
    MB11MB12MB13MB14MB15MB16MB17MB18MB19MB20MB21
    MB22MB23MB24MB25MB26MB27MB28MB29MB30MB31MB32

    每一宏块由4个亮度块(Y0-Y3)和2个色差块(CrCb)组成,块的大小是8X8,块是DCT变换的最小单元。

    综上的分解应该得到: 352X288=101376       12 X 33 X(8X8X2+16*16*4)=456192  

    为什么本该理论本该相等的地方却相差四倍。

     

     视频编码器生成的编码是自我格式化的。该数据流可以和其他多种源信号混合传输。视频解码器执行与视频编码器相逆的过程。

    如下图所示,H.263对视频输出比特率没有进行限制,输出比特率既可以是恒定的,也可以是变化的。输入的图像信号可以在多种频率下采样处理,这一

    采样频率和传输网络的数字时钟是异步的。

    H.263采用的是混合编码技术,即用帧间预测减少时域冗余,用变换编码减少残差信号的空域冗余,相应的解码器具有运用补偿能力。

    H.263采用的是半像素精度,与H.261采用的全像素精度和一个环路滤波器的设计不同。

    被发送的各种符号采用可变长编码技术

    当H.263标准不采用任何高级选项时,称为H.263的基本编码模式,或称为H.263的缺省编码模式。其信源编码器仍然采用可减少时间冗余的帧间编码预测

    可减少空间冗余的DCT变换编码相结合的混合编码方法。

    H.263比H.261在以下几个方面做了改进,以便适应极低码率的传输要求:

    (1) H.263中包含4个基本模式:非限制运行矢量模式、基于语法的算术编码模式、高级预测模式、PB帧模式

    (2) H.263编码器除了支持H.261中的图像格式CIF和QCIF之外,还增加了另外3种图像格式sub-QCIF、4CIF、16CIF,从而使H.263具有更广的应用范围。

    对每种图像采用YUV4:2:0的图像格式。即图像各分量的采样分辩率如下:如果亮度分量按照dx个像素点,每帧dy行采样,则每个色度分量按每行dx/2个像素点,

    每帧dy/2行采样。整理成表如下:

    图像格式

    每行亮度

    图样像素(Y)

    每帧图像亮度行数(Y)

    每行色差取样像素(U、V)每帧图像色差行数(U、V)
    Sub-QCIF(亚1/4的公共中间格式)128966448
    QCIF(1/4的公共中间格式)1761448872
    CIF(公共中间格式)352288176144
    4CIF(4倍公共中间格式)704576352288
    16QCIF(16倍公共中间格式)14081152704576

     

    H.263解码器要求能对Sub-QCIF、QCIF格式的图像码流进行解码,但是不强求能对CIF、4CIF、14CIF图像模式的码流进行解码。同样,

    H.263编码器应该能够对Sub-QCIF和QCIF中任一格式的图像进行编码,是否支持其它格式由用户自己决定。

    视频编码器框图如下,主要包括预测、分块变换和量化几部分。

    H.263是基于块的运动编码标准,它采用减少时间冗余的帧间编码技术和减少空间冗余的DCT变换技术,

    以获得较高压缩比,它的重要部分有基于块的DCT变换、量化、运动估计与帧间预测及VLC熵编码

    (1)块组(Group Of Block, GOB)、宏块(macroblock)和块(block)。

           每帧图像又可以分为多个块组,每个块组包含kX16行,k的取值依赖于图像的格式,(对Sub-QCIF、QCIF、CIF格式,k=1;对于4CIF,k=2;

          对于16CIF格式,k=4)。每帧图像包含的块组个数分别是:

    Sub-QCIF格式为6;

    QCIF格式为9;

    CIF、4CIF、16CIF格式为18;

    块组的标号顺序是自上而下的,由0开始

    每个块组分成多个宏块,每个宏块中亮度信号Y的分辨率为16*16,色度信号的分辨率为8*8。

    每个宏块包含4个亮度块和2个色度块。宏块的标号顺序是先自左向右,再自上而下。宏块数据按宏块顺序发送,块数据也按块顺序发送

    (2)预测。

    帧间预测是指图像间的预测,它可以通过运行补偿加以实现。采用了帧间预测方法的编码是帧间编码,没有采用帧间预测编码的为帧内预测。

    H.263校标允许在图像层选择编码模式,此时,采用帧内编码模式的图像称为I帧,采用帧间编码模式的图像称为P帧;

    P帧图像中还可以在宏块层选择编码,即允许宏块采用帧间编码和帧内编码。

    PB模式下,B帧图像中的所有宏块都采用帧间编码方式,而且部分宏块可以采用双向预测技术,采用双向预测技术的宏块称为B宏块

    (3)运动补偿。

    编码标准的默认框架下,每个宏块使用一个运动矢量作运行补偿,在高级预测模式下,一个宏块可以使用1个或4个运动矢量做运行补偿。

    如果启用了PB模式,则每个宏块还要增加一个偏差矢量,用以估计B宏块的运动矢量。

    宏块的运动矢量采用了差分编码技术。差分编码值是当前宏块的运动矢量和预测因子之差,而预测因子取自3个候选预测因子中的中值。

    3个候选预测因子指3个相临宏块的运行矢量。

    (4)量化

    量化是指用规定范围内的一个值表示值的一个范围。例如把实数转成最接近的一个整数即是一种量化。量化范围可以被精确地表示成一个整数码,

    该整数码在解码过程中用来恢复被量化的那个值,实际值与量化值之间的差值称为量化噪声。

    在某些场合,人类视觉系统对量化噪声不敏感,量化噪声可以很大,因此量化可以提高编码效率。

    量化在整个视频序列编码中占据很重要的地位,因为是先将DCT变换后的系数矩阵进行量化,然后再对这个量化矩阵编码,

    量化后的非零系数越少,编码效果越好,而这是整个编码方案性能良好的主要原因。

    也就是说,宏块经过DCT变换后,形成一个大幅度系统集中在低频区域,而高频区域系数都比较小,量化后许多高频系数0, 这使传输码率降低,从而达到压缩目的。

    H.263默认框架下,帧内编码块的第一个系数使用统一的量化器,量化步长为8。其他各系数可选择的量化器有31个,但一个宏块内的量化器要统一。

     

    1、2    H.263编码应用程序设计及源代码详细分析

    1、2、1 应用程序效果展示

    应用程序采用单文档框架。

    开始编码时先弹出第1帧的lum、Cb、Cr峰值信噪比

     

    转化过程:

    转化结束:

    1、2、2 源代码分析

    首先介绍程序中定义的重要结构体Struct类型的数据结构,

    其中MotionVector表示运动矢量,PictImage包含一帧图像的数据MB_structure包含一个宏块的数据,这些数据结构与H.263标准中的图像构成要素直观地对应,方便了视频数据的读入与处理;

    还有一些与控制相关的数据结构,如Pict

    typedef struct pict {
    	int TR;  /* Time reference */
    	int source_format;  
    	int picture_coding_type;/*PCT_INTRA 帧内编码  PCT_INTER帧间编码*/
    	int unrestricted_mv_mode;
        int PB; 
        int QUANT;
        int BQUANT;           /* 在PB帧中的B-MBs 量化器*/
        int TRB;              /* B图像的时间参考消息 */
              
        int bit_rate;
        int src_frame_rate;
        float target_frame_rate;
    
      int DQUANT;
      int MB;
      int seek_dist;        /* 运动矢量搜索窗口 */
      int use_gobsync;      /*  gob_sync 的标志 */
      int MODB;             /* B-frame mode */
      
      float QP_mean;        /* mean quantizer */
    } Pict;


     

     

    执行编码的主要函数是CodeYUV()。它默认按CIF格式进行编码。它主要完成以下几个工作:

    (1) 初始化界面,包括进度条

    (2) 打开文件、建立输出文件

    (3) 初始化帧间编码参数,为接受YUV文件中的数据的空间、Pict空间、 Bits空间申请空间,

                初始化Pict:包括 PB帧中的B-MBs 量化器、运动矢量搜索窗口(即搜索距离)、指明不插入扩展的同步、

                                       图像编码为帧内编码、编码格式为CIF、帧内初始化因子(为8)

    (4) 从文件中读取352*288*3/2个数据

    (5) 调用FillImage从文件流中先读取一帧图像数据。

    (6) 对第一帧数据进行帧内编码,比较原始图像与构建后的图像寻找到信噪比,并显示信噪比 。

    (7) 对所有帧数据根据条件选择帧内编码或帧间编码进行编码,并把数据转化为H.263保存在文件中。

    判断采用帧内编码或帧间编码的方法是: if(((frame_no-1)%Pbetween)==0) // 帧内编码, 否则采用帧间编码

    Pbetween为相邻I帧之间插入的P帧个数,在编码属性对话框中设置(默认设置为19),

    这样帧号frame_no为21 41 81 161 321 、、、、、时会采用帧内编码,其它帧采用帧间编码。

     

     

    // YUV转H.263编码
    void CMyView::CodeYUV()
    { 
    	//率控制
      #ifndef OFFLINE_RATE_CONTROL
        float DelayBetweenFramesInSeconds;
        int CommBacklog;
      #else
        PictImage *stored_image = NULL;
        int start_rate_control;
      #endif
    
    	CMyDoc* pDoc = GetDocument();
    	ASSERT_VALID(pDoc);
    	//初始化
    	CProgressBar bar(_T("Encode Progress"), 50, MaxFrame);
        m_pImageData=new unsigned char[352*288*3/2]; // 因为假设是按CIF格式来编码,CIF一帧是352*288
    	CFile file;
    	if((file.Open(m_szFilePathName,CFile::modeRead|CFile::shareDenyNone))==NULL)
    	{
    		AfxMessageBox("Can not open the yuv file!");
    		return;
    	}
    
    	PictImage *prev_image = NULL;
        PictImage *curr_image = NULL;
        PictImage *curr_recon = NULL;
        PictImage *prev_recon = NULL;
    
    	int frame_no,first_frameskip=0;  
        int start=1;//从第1帧到第MaxFrame帧
        int orig_frameskip=1;//输入图像原始偏移
        int frameskip=1;//非发送帧
    
    	long_vectors =0;//帧间编码的参数
    	mv_outside_frame=0;
    
    	Pict *pic = (Pict *)malloc(sizeof(Pict));
    	Bits *bits = (Bits *)malloc(sizeof(Bits));
    	//率控制
    	Bits *total_bits = (Bits *)malloc(sizeof(Bits));
        Bits *intra_bits = (Bits *)malloc(sizeof(Bits));
    
    	pic->BQUANT = DEF_BQUANT;
        pic->seek_dist = DEF_SEEK_DIST;
        pic->use_gobsync = DEF_INSERT_SYNC;//=0
        pic->PB = 0;
        pic->TR = 0;
        pic->QP_mean = (float)0.0;
        pic->unrestricted_mv_mode = 0;
    	pic->picture_coding_type =0; // PCT_INTRA;
    	m_orgHeight=288;
    	m_orgWidth=352;
        pic->source_format = SF_CIF; // 按CI格式F编码
    	switch (pic->source_format) {
        case (SF_SQCIF):
          fprintf(stdout, "Encoding format: SQCIF (128x96)\n");
          pels = 128;
          lines = 96;
          break;
        case (SF_QCIF):
          fprintf(stdout, "Encoding format: QCIF (176x144)\n");
          pels = 176;
          lines = 144;
          break;
        case (SF_CIF):
          fprintf(stdout, "Encoding format: CIF (352x288)\n");
          pels = 352;
          lines = 288;
          break;
        case (SF_4CIF):
          fprintf(stdout, "Encoding format: 4CIF (704x576)\n");
          pels = 704;
          lines = 576;
          break;
        case (SF_16CIF):
          fprintf(stdout, "Encoding format: 16CIF (1408x1152)\n");
          pels = 1408;
          lines = 1152;
          break;
        default:
          fprintf(stderr,"Illegal coding format\n");
          exit(-1);
    	}
        cpels = pels/2;
    	curr_recon = InitImage(pels*lines);
    
    //率控制
      #ifndef OFFLINE_RATE_CONTROL
      // rate control variables 
      pic->bit_rate = targetrate;
      pic->src_frame_rate = (int)(ref_frame_rate / orig_frameskip);
      DelayBetweenFramesInSeconds = (float) 1.0/(float)pic->src_frame_rate;
      InitializeRateControl();
      #endif
      #ifdef OFFLINE_RATE_CONTROL
      start_rate_control = 0;//DEF_START_RATE_CONTROL;
      #else
      pic->target_frame_rate = (float)DEF_TARGET_FRAME_RATE;
      #endif
    
    	//建立输出文件
    	CString outfilename=m_szFileName.Left(m_szFileName.GetLength()-4);
    	CFileDialog dlg(FALSE,".263",outfilename,OFN_OVERWRITEPROMPT,"263 Files(*.263)|*.263|",NULL);
    	if (dlg.DoModal()==IDOK)
    	{
    		bFlag=1;
    		pDoc->UpdateAllViews(NULL);
     
    		CString tempname;
    		tempname=dlg.GetPathName();
    		outfilename=tempname.Left(tempname.GetLength()-4);
    	    if((streamfile.Open(tempname,CFile::modeWrite|CFile::modeCreate))==FALSE)
    			AfxMessageBox("Can't create file!"); 
    		streamfile.SeekToBegin();
         	initbits ();
    		
    
    		CTime StartTime=CTime::GetCurrentTime();
            CTimeSpan ElapsedTime;
    
        file.Read(m_pImageData,sizeof(BYTE)*352*288*3/2);
    	pic->QUANT=QPI;
    	curr_image = FillImage(m_pImageData);	// 填充第一帧图像数据
    	curr_recon = CodeOneIntra(curr_image, QPI, bits, pic);
    	bits->header += alignbits ();			// 图像应该是byte对齐的 
    
    	//率控制
    	AddBitsPicture(bits);
    	memcpy(intra_bits,bits,sizeof(Bits));
    	ZeroBits(total_bits);
        //* number of seconds to encode *
    	int chosen_frameskip=1;//jwp
        //* compute first frameskip *
      #ifdef OFFLINE_RATE_CONTROL
       float seconds = (MaxFrame - start + chosen_frameskip) * orig_frameskip/ ref_frame_rate;
       first_frameskip = chosen_frameskip;
       frameskip = chosen_frameskip;
      #else
      CommBacklog = intra_bits->total -(int)(DelayBetweenFramesInSeconds * pic->bit_rate);
    
      if (pic->bit_rate == 0) {
        frameskip = chosen_frameskip;
      }
      else {  //* rate control is used *
        frameskip = 1;
        while ( (int)(DelayBetweenFramesInSeconds*pic->bit_rate) <= CommBacklog) {
          CommBacklog -= (int) ( DelayBetweenFramesInSeconds * pic->bit_rate );
          frameskip += 1;
        }
      }
      first_frameskip = frameskip;
      #endif
    
    	CString kk,m_Spsnr;
        if(ifPsnr)
    	{
    		if(psnrfile.Open(outfilename+".doc",CFile::modeWrite|CFile::modeCreate)==FALSE)
    		MessageBox("Cannot create the output psnr file!", "Error",MB_ICONERROR | MB_OK);
    		SeekPsnr(curr_image,curr_recon,352,288,psnrs);	// 比较原始图像与构建后的图像,寻找到信噪比
    		frame_no=1;
    		kk.Format("第%d帧的lum峰值信噪比为%6.4fdB\n",frame_no,psnrs[0]);
    		m_Spsnr=kk;
    		kk.Format("第%d帧的Cb峰值信噪比为%6.4fdB\n",frame_no,psnrs[1]);
    		m_Spsnr+=kk;
    		kk.Format("第%d帧的Cr峰值信噪比为%6.4fdB\n",frame_no,psnrs[2]);
    		m_Spsnr+=kk;
    		MessageBox(m_Spsnr);
    		psnrfile.SeekToBegin();
    		psnrfile.Write(m_Spsnr,m_Spsnr.GetLength());
    	}
       //第二帧 
       pic->QUANT=QP;
       // 对所有帧进行编码
       for(frame_no=start+first_frameskip;frame_no<=MaxFrame;frame_no+=frameskip)
       {
    	  file.Seek((frame_no-1)*352*288*3/2,SEEK_SET);
    	  file.Read(m_pImageData,sizeof(BYTE)*352*288*3/2);
    	  if(m_pImageData==NULL)
    		return;
    	   
      	  pic->picture_coding_type =1; // PCT_INTER;
    
    	  if(!ratecontrol)
    	  pic->QUANT=QP;
    	  
          prev_image=curr_image;
    	  prev_recon=curr_recon;
          curr_image = FillImage(m_pImageData);
    	  pic->TR+=(((frameskip+(pic->PB?98:0))*orig_frameskip)%256);
    	  if(frameskip+(pic->PB?98:0)>256)
    		MessageBox("Warning:frameskip>256");
    
         streamfile.SeekToEnd();// Pbetween为相邻I帧之间插入的P帧个数
      // 即frame_no = 21 41 81 161、、、时进行帧内编码
        if(((frame_no-1)%Pbetween)==0) // 帧内编码
    	 {
    	   pic->picture_coding_type =0; // PCT_INTRA;
    	   pic->QUANT=QPI;
           curr_recon = CodeOneIntra(curr_image, QPI, bits, pic);
    	   AddBitsPicture(bits);
    	   memcpy(intra_bits,bits,sizeof(Bits));
    
    	 }
        else							// 帧间编码
    	{ 
    	  CodeOneInter(prev_image,curr_image,prev_recon,curr_recon,pic->QUANT,frameskip,bits,pic);
    	  AddBitsPicture(bits); 
    	}
       bits->header += alignbits (); //* pictures shall be byte aligned *
    
     
       if(ifPsnr)
       {	
    		SeekPsnr(curr_image,curr_recon,352,288,psnrs);
    		kk.Format("第%d帧的lum峰值信噪比为%6.4f\n",frame_no,psnrs[0]);
    		m_Spsnr=kk;
    		kk.Format("第%d帧的Cb峰值信噪比为%6.4f\n",frame_no,psnrs[1]);
    		m_Spsnr+=kk;
    		kk.Format("第%d帧的Cr峰值信噪比为%6.4f\n",frame_no,psnrs[2]);
    		m_Spsnr+=kk;
    		psnrfile.SeekToEnd();
    		psnrfile.Write(m_Spsnr,m_Spsnr.GetLength());
       }
    	//率控制
    
       AddBits(total_bits, bits);
    #ifndef OFFLINE_RATE_CONTROL
        if (pic->bit_rate != 0 && pic->PB)
          CommBacklog -= (int)
          ( DelayBetweenFramesInSeconds*pic->bit_rate ) * pdist;
    
        if (pic->bit_rate != 0) {
          UpdateRateControl(bits->total);
    
          CommBacklog += bits->total;
          frameskip = 1;
          CommBacklog -= (int)
            (frameskip * DelayBetweenFramesInSeconds *pic->bit_rate);
    
          while ( (int)(DelayBetweenFramesInSeconds*pic->bit_rate) <= CommBacklog)
          {
            CommBacklog -= (int) ( DelayBetweenFramesInSeconds * pic->bit_rate );
            frameskip += 1;
          }
        }
    #else
        //* Aim for the targetrate with a once per frame rate control scheme *
        if (targetrate != 0)
          if (frame_no - start > (MaxFrame - start) * start_rate_control/100.0)
    
            pic->QUANT = FrameUpdateQP(total_bits->total + intra_bits->total,
               bits->total / (pic->PB?2:1),
               (MaxFrame-frame_no) / chosen_frameskip ,
               pic->QUANT, targetrate, seconds);
       frameskip = chosen_frameskip;
    #endif
    	  CString kkk;
    	  kkk.Format("编码率为%d,%d帧的total_bits->total 为%d,intra_bits->total 为 %d, bits->total 为%d,new quant is %d.",
    		  targetrate,frame_no,total_bits->total,intra_bits->total,bits->total,pic->QUANT);
    	  if(ifPsnr)
    	  {
    		  psnrfile.SeekToEnd();
    	      psnrfile.Write(kkk,kkk.GetLength());
    	  }
    
    	//显示进度信息
    	CString str;
    	str.Format("%d%% complete", frame_no*100/MaxFrame);
    	bar.SetText(str);
    	bar.StepIt();
        PeekAndPump(); //调用函数实现消息的转发
       }// 所有帧的编码结束
    	
    //  pDoc->SetModifiedFlag(TRUE);
      file.Close();
      if(ifPsnr)
      psnrfile.Close();
      streamfile.Close();
      delete prev_image;
      delete prev_recon;
      delete curr_image;
      curr_recon=NULL;
    //  delete curr_recon;
      free(bits);
      free(pic);
      
    
        long hours,minutes,second;//计算所用的时间
        ElapsedTime = CTime::GetCurrentTime() - StartTime;
    	bFlag=0;    
    	hours = ElapsedTime.GetTotalHours();
        minutes = ElapsedTime.GetTotalMinutes();
        second = ElapsedTime.GetTotalSeconds();
        second = second + 60*minutes + 3600*hours;
        csTimeElapse.Format("编码%d帧视频序列,耗时:%d秒!",frame_no-1,second);
        MessageBox(csTimeElapse);
    	pDoc->UpdateAllViews(NULL);
    	}
    	else
        MessageBox("No file is saved.","系统提示",MB_OK);
    //	delete m_pImageData;
    
    }
    


     

     

     

    如上所说,H.263编码包括帧内编码和帧间编码。现在对它们分别进行说明.

    1、2、2、1  帧内编码

    首先对第一帧进行帧内编码,利用ReadImage()函数从原始视频文件中读入一帧数据,FillIIMage()使用上面读入的数据填充一个PictureImage结构

    文件流的分布如下图:

    // 填充Y、Cb、Cr到PictImage结构体中,按 4:1:1
    PictImage *FillImage(unsigned char *in)
    {
      PictImage *Pict;
    
      Pict = InitImage(pels*lines);
    
      memcpy(Pict->lum, in, pels*lines);
      memcpy(Pict->Cb, in + pels*lines, pels*lines/4);
      memcpy(Pict->Cr, in + pels*lines + pels*lines/4, pels*lines/4);
    
    //  free(in);
      return(Pict);
    }
    


    CodeOneIntra()函数专门对一帧图像进行帧内编码,并返回编码结果。它主要完成以下功能:

    (1)申请宏块数据空间、申请存放帧内编码结果的空间、

    (2)指定帧内初始化因子、计算图像帧的头大小

    (3)把读入的数据分为16*16像素的宏块分别处理(重点)。

    CodeOneIntra方法的代码文件为CodeOneIntra.cpp、CodeOneIntra.h

    //帧内编码
    PictImage *CodeOneIntra(PictImage *curr, int QP, Bits *bits, Pict *pic)
    {
      PictImage *recon;
      MB_Structure *data = (MB_Structure *)malloc(sizeof(MB_Structure));
      int * qcoeff;
      int Mode =3;//  帧内模式MODE_INTRA;
      int CBP,COD;
      int i,j;
    
      recon = InitImage(pels*lines);
      ZeroBits(bits);
      
      pic->QUANT = QP;
      bits->header += CountBitsPicture(pic);			// 计算图像帧的头大小
    
      COD = 0; 
      for ( j = 0; j < lines/MB_SIZE; j++) {
    
        /* 如果允许GOB同步,则输出GOB同步头  */
        if (pic->use_gobsync && j != 0)
          bits->header += CountBitsSlice(j,QP);
    
        for ( i = 0; i < pels/MB_SIZE; i++) 
    	{
          pic->MB = i + j * (pels/MB_SIZE);				// 宏块序号
          bits->no_intra++;
          FillLumBlock(i*MB_SIZE, j*MB_SIZE, curr, data);//写亮度图像16*16 curr:图像数据 data:宏块树组
          FillChromBlock(i*MB_SIZE, j*MB_SIZE, curr, data);//写色度图像2*8*8 
          qcoeff = MB_Encode(data, QP, Mode);			// 变换并量化后数据到qcoeff=16*16+2*8*8个单元
          CBP = FindCBP(qcoeff,Mode,64);				// 从一帧的DCT变换与量化处理后系数结果中得到CBR值	
    
          if (!syntax_arith_coding) 
    	  {
            CountBitsMB(Mode,COD,CBP,0,pic,bits);		// 输出宏块层信息
            CountBitsCoeff(qcoeff, Mode, CBP,bits,64);  // 量化系数进行可变长编码,并把结果输出到输出流中
          } 
    
          MB_Decode(qcoeff, data, QP, Mode);			// 负责对DCT系数进行反量化和反DCT,重新构建宏块
          Clip(data);									// 使 0<=data<=255 
          ReconImage(i,j,data,recon);					// 将重建宏块data输出到整个图像recon中
          free(qcoeff);
        }
      }
      pic->QP_mean = (float)QP;
    
      free(data);
      return recon;
    }
    


     下面对CodeOneIntra()函数中几个重要的功能进行讲解:

     对每一宏块,先用FillLumBlock()FillChromBlock()填充一个MB-Structure结构中的成员变量:lum、Cr、 Cb

    把一帧图像的亮度分量Y作个示意图如下:

    void FillLumBlock( int x, int y, PictImage *image, MB_Structure *data)
    {
      int n;
      register int m;
    
      for (n = 0; n < MB_SIZE; n++)
        for (m = 0; m < MB_SIZE; m++)
          data->lum[n][m] = 
            (int)(*(image->lum + x+m + (y+n)*pels));
      return;
    }
    
    


     

    把一帧图像的Cr或Cb分量作个示意图如下:

    void FillChromBlock(int x_curr, int y_curr, PictImage *image,
                MB_Structure *data)
    {
      int n;
      register int m;
      int x, y;
    
      x = x_curr>>1;  // 因为Cr、Cb分量的数据分别只有Y的1/4。x/2 、 y/2后刚好是1/4
      y = y_curr>>1;
    
      for (n = 0; n < (MB_SIZE>>1); n++)
        for (m = 0; m < (MB_SIZE>>1); m++) {
          data->Cr[n][m] = 
            (int)(*(image->Cr +x+m + (y+n)*cpels));
          data->Cb[n][m] = 
            (int)(*(image->Cb +x+m + (y+n)*cpels));
        }
      return;
    }


     MB-Encode()函数对宏块进行处理,对数据进行DCT变换并对变换系数进行量化,最后返回指向数据块的指针

    (即存放在qcoeff中的64*6空间中  前面64*4是存放亮度变换后的结果系数;中间64个是存Cb变换后的结果系数;后面64个是存Cr变换后的结果系数)

     

    // 对亮度图像数据、色度图像数据进行DCT变换,量化处理
    // 结果存放在qcoeff中的64*6中
    int *MB_Encode(MB_Structure *mb_orig, int QP, int I)
    {
      int        i, j, k, l, row, col;
      int        fblock[64];
      int        coeff[384];
      int        *coeff_ind;
      int        *qcoeff;
      int        *qcoeff_ind;
    
      // 为变换系数分配空间
      if ((qcoeff=(int *)malloc(sizeof(int)*384)) == 0)
      {
        fprintf(stderr,"mb_encode(): Couldn't allocate qcoeff.\n");
        exit(-1);
      }
    
      coeff_ind = coeff;
      qcoeff_ind = qcoeff;
    
      // 把16X16的亮度图像分为四次8X8来进行DCT变换和量化处理
      // 结果存放在qcoeff_ind中的64X4的空间内
      for (k=0;k<16;k+=8) 
      {
        for (l=0;l<16;l+=8) 
    	{
          for (i=k,row=0;row<64;i++,row+=8) 
    	  {
            for (j=l,col=0;col<8;j++,col++) 
    		{
              fblock[row+col] = mb_orig->lum[i][j];	// 亮度图像数据
            }
          }
          Dct(fblock,coeff_ind);					// 对8X8的块做DCT变换, 做系数的Z字形搜索
          Quant(coeff_ind,qcoeff_ind,QP,I);         // 量化
          coeff_ind += 64;							// 为下一个8X8的块准备存系数的空间
          qcoeff_ind += 64;
        }
      }
      // 把8*8的色度图像数据Cb进行DCT变换和量化处理
      // 结果存放在qcoeff_ind中从64X4开始的64个空间内	
      for (i=0;i<8;i++) 
      {
        for (j=0;j<8;j++)
    	{
          *(fblock+i*8+j) = mb_orig->Cb[i][j];
        }
      }
      Dct(fblock,coeff_ind);
      Quant(coeff_ind,qcoeff_ind,QP,I); 
      coeff_ind += 64;
      qcoeff_ind += 64;
    
      // 把8*8的色度图像数据Cr进行DCT变换和量化处理
      // 结果存放在qcoeff_ind中从64X4+64开始的64个空间内	
      for (i=0;i<8;i++) {
        for (j=0;j<8;j++) {
          *(fblock+i*8+j) = mb_orig->Cr[i][j];
        }
      }
      Dct(fblock,coeff_ind);
      Quant(coeff_ind,qcoeff_ind,QP,I); 
      return qcoeff;
    }
    


    CountBitsCoeff()对量化系数进行可变长编码并把结果输出到输出流中(即输出到.H263文件中去)。

     

    // 量化系数进行可变长编码,并把结果输出到输出流中
    // 输入:qcoeff,量化系数
    // 输出:bits中的Y、C
    void CountBitsCoeff(int *qcoeff, int Mode, int CBP, Bits *bits, int ncoeffs)
    {
      
      int i;
    
      if (Mode == MODE_INTRA || Mode == MODE_INTRA_Q)
      {
        for (i = 0; i < 4; i++) 
    	{
    		bits->Y += CodeCoeff(Mode, qcoeff,i,ncoeffs);
        }
        for (i = 4; i < 6; i++) 
    	{
    		bits->C += CodeCoeff(Mode, qcoeff,i,ncoeffs);
        }
      }
      else 
      {
        for (i = 0; i < 4; i++) {
          if ((i==0 && CBP&32) || 
              (i==1 && CBP&16) ||
              (i==2 && CBP&8) || 
              (i==3 && CBP&4) || 
              (i==4 && CBP&2) ||
              (i==5 && CBP&1)) {
            bits->Y += CodeCoeff(Mode, qcoeff, i, ncoeffs);
          }
        }
        for (i = 4; i < 6; i++) {
          if ((i==0 && CBP&32) || 
              (i==1 && CBP&16) ||
              (i==2 && CBP&8) || 
              (i==3 && CBP&4) || 
              (i==4 && CBP&2) ||
              (i==5 && CBP&1)) {
            bits->C += CodeCoeff(Mode, qcoeff, i, ncoeffs);
          }
        }
      }
      return;
    }

     


     

    int CodeCoeff(int Mode, int *qcoeff, int block, int ncoeffs)
    {
      int j, bits;
      int prev_run, run, prev_level, level, first;
      int prev_s, s, length;
    
      run = bits = 0;
      first = 1;
      prev_run = prev_level = level = s = prev_s = 0;
    
      for (j = block*ncoeffs; j< (block + 1)*ncoeffs; j++) {
        /* Do this block's DC-coefficient first */
        if (!(j%ncoeffs) && (Mode == MODE_INTRA || Mode == MODE_INTRA_Q)) {
          /* DC coeff */
          if (qcoeff[block*ncoeffs] != 128)
            putbits(8,qcoeff[block*ncoeffs]);
          else
            putbits(8,255);
          bits += 8;
        }
        else {
          /* AC coeff */
          s = 0;
          /* Increment run if coeff is zero */
          if ((level = qcoeff[j]) == 0) {
            run++;
          }
          else {
            /* code run & level and count bits */
            if (level < 0) {
              s = 1;
              level = -level;
            }
    
            if (!first) {
              /* Encode the previous coefficient */
              if (prev_level  < 13 && prev_run < 64)
                length = put_coeff (0,prev_run, prev_level);  
              else
                length = 0;
              if (length == 0) {  /* Escape coding */
                if (prev_s == 1) {prev_level = (prev_level^0xff)+1;}
                putbits(7,3);	/* Escape code */
                putbits(1,0);
                putbits(6,prev_run);
                putbits(8,prev_level);
                bits += 22;
              }
              else {
                putbits(1,prev_s);
                bits += length + 1;
              }
            }
            prev_run = run; prev_s = s;
            prev_level = level;
    
            run = first = 0;
          }
        }
      }
      /* Encode the last coeff */
      if (!first) {
        if (prev_level  < 13 && prev_run < 64)
          length = put_coeff (1,prev_run, prev_level);
        else
          length = 0;
        if (length == 0) {  /* Escape coding */
          if (prev_s == 1) {prev_level = (prev_level^0xff)+1;}
          putbits (7,3);	/* Escape code */
          putbits(1,1);
          putbits(6,prev_run);
          putbits(8,prev_level);
          bits += 22;
        }
        else {
          putbits(1,prev_s);
          bits += length + 1;
        }
      }
      return bits;
    }
    


    MB_Decode()负责对DCT系数进行反量化和反DCT,重新构建宏块。它的操作与MB_Encode()的操作相反。
     

    // 负责对DCT系数进行反量化和反DCT,重新构建宏块     
    // 输入:qcoeff, DCT系数
    // 输出:mb_recon, 存放反量化和反DCT,重新构建的宏块
    int MB_Decode(int *qcoeff, MB_Structure *mb_recon, int QP, int I)
    {
      int	i, j, k, l, row, col;
      int	*iblock;
      int	*qcoeff_ind;
      int	*rcoeff, *rcoeff_ind;
    
      if ((iblock = (int *)malloc(sizeof(int)*64)) == NULL)
      {
        fprintf(stderr,"MB_Coder: Could not allocate space for iblock\n");
        exit(-1);
      }
      if ((rcoeff = (int *)malloc(sizeof(int)*384)) == NULL) 
      {
        fprintf(stderr,"MB_Coder: Could not allocate space for rcoeff\n");
        exit(-1);
      }  
    
      // 宏块图像数据清零
      for (i = 0; i < 16; i++)
        for (j = 0; j < 16; j++)
          mb_recon->lum[j][i] = 0;
      for (i = 0; i < 8; i++) 
        for (j = 0; j < 8; j++) 
    	{
          mb_recon->Cb[j][i] = 0;
          mb_recon->Cr[j][i] = 0;
        }
    
      qcoeff_ind = qcoeff;
      rcoeff_ind = rcoeff;
    
      // 对qcoeff中的64*4大小的亮度图像数据进行反量化和反DCT
      for (k=0;k<16;k+=8)
      {
        for (l=0;l<16;l+=8)
    	{
          Dequant(qcoeff_ind,rcoeff_ind,QP,I);
          idct(rcoeff_ind,iblock); 
          qcoeff_ind += 64;
          rcoeff_ind += 64;
          for (i=k,row=0;row<64;i++,row+=8) {
            for (j=l,col=0;col<8;j++,col++) {
              mb_recon->lum[i][j] = *(iblock+row+col);
            }
          }
        }
      }
      // 对qcoeff中的64大小的色度图像数据Cb进行反量化和反DCT
      Dequant(qcoeff_ind,rcoeff_ind,QP,I);
      idct(rcoeff_ind,iblock); 
      qcoeff_ind += 64;
      rcoeff_ind += 64;
      for (i=0;i<8;i++) 
      {
        for (j=0;j<8;j++) 
    	{
          mb_recon->Cb[i][j] = *(iblock+i*8+j);
        }
      }
    
      // 对qcoeff中的64大小的色度图像数据Cb进行反量化和反DCT
      Dequant(qcoeff_ind,rcoeff_ind,QP,I);
      idct(rcoeff_ind,iblock); 
      for (i=0;i<8;i++) 
      {
        for (j=0;j<8;j++)
    	{
          mb_recon->Cr[i][j] = *(iblock+i*8+j);
        }
      }
      free(iblock);
      free(rcoeff);
      return 0;
    }
    
    
    


     

     

    1、2、2、2 帧间编码

    void CodeOneInter(PictImage *prev,PictImage *curr,PictImage *pr,PictImage *curr_recon,int QP, int frameskip, Bits *bits, Pict *pic)

    函数负责当前帧编码,它是帧间编码的核心。 它主要完成以下功能:

    (1)对前一帧的编码后还原的数据中的高度数据进行图像/2插值,这是为了在运动估计时进行半像素搜索,以提高运动矢量的计算精度;

    (2)初始化运动向量结果数组,为每个编码块标记MV;超出图象边界的MV置为0;

    (3)把读入的数据分为16*16像素的宏块分别处理(重点)。

    CodeOneInter定义了一个指向三维数组的指针MV,数组元素为MotionVector结构的数据。

    后面计算得到的矢量放在这一数组中,以便图像进行预测时应用。

    /**********************************************************************
     *
     *	Name:        CodeOneInter
     *	Description:	正常情况下编码一个image
                        当做为一个PB帧时编码两个image(CodeTwoPB and CodeOnePred merged)
     *	
     *	Input:        pointer to image, prev_image, prev_recon, Q
     *        
     *	Returns:	pointer to reconstructed image
     *	Side effects:	memory is allocated to recon image
     *
     ************************************************************************/
    void CodeOneInter(PictImage *prev,PictImage *curr,
    				  PictImage *pr,PictImage *curr_recon,
    				  int QP, int frameskip, Bits *bits, Pict *pic)
    {
    	 ZeroBits(bits);
    	 // pi : 对前一帧的编码后还原的数据中的高度数据进行图像1/2插值后的结果
    	 unsigned char *prev_ipol,*pi,*orig_lum;
         PictImage *prev_recon=NULL;
    	 MotionVector *MV[6][MBR+1][MBC+2]; 
    	 MotionVector ZERO = {0,0,0,0,0};
    	 int i,j,k;
    	 int newgob,Mode;
      	 int *qcoeff_P;
    	 int CBP, CBPB=0;
         MB_Structure *recon_data_P; 
         MB_Structure *diff; 
           
    	 /* 缓冲器控制变量 */
         float QP_cumulative = (float)0.0;
         int abs_mb_num = 0, QuantChangePostponed = 0;
         int QP_new, QP_prev, dquant, QP_xmitted=QP;
         QP_new = QP_xmitted = QP_prev = QP; /* 复制旧QP */
    
    	 /* 图象插值 */
    	 if(!mv_outside_frame)
    	 {
    		 // 对前一帧的编码后还原的数据中的高度数据进行图像1/2插值
    		 // 这是为了在运动估计时进行半像素搜索,以提高运动矢量的计算精度
    		 pi = InterpolateImage(pr->lum, pels, lines);
    		 prev_ipol = pi;
    		 prev_recon = pr;
    		 orig_lum = prev->lum;
    	 }
    	 
         /* 为每个编码块标记MV  352/16 = 22 */
        for (i = 1; i < (pels>>4)+1; i++)
    	{
          for (k = 0; k < 6; k++) 
    	  {
    			MV[k][0][i] = (MotionVector *)malloc(sizeof(MotionVector));
    			// 标记MV
    			MarkVec(MV[k][0][i]);
    	  }
    	  // 标记模式为帧间编码
          MV[0][0][i]->Mode = MODE_INTRA;
    	}
        /* 超出图象边界的MV置为0   288/16 = 18*/
        for (i = 0; i < (lines>>4)+1; i++) 
    	{
          for (k = 0; k < 6; k++) 
    	  {
    			MV[k][i][0] = (MotionVector *)malloc(sizeof(MotionVector));
    			// 初始化运动向量帧
    			ZeroVec(MV[k][i][0]);
    			MV[k][i][(pels>>4)+1] = (MotionVector *)malloc(sizeof(MotionVector));
    			ZeroVec(MV[k][i][(pels>>4)+1]);
    	  }
          MV[0][i][0]->Mode = MODE_INTRA;
          MV[0][i][(pels>>4)+1]->Mode = MODE_INTRA;
    	}
    
    	/* 整数和半象素运动估值 */
    	// 计算出前一帧的重建和当前帧图像之间的运动向量。
    	// 它也是按宏块处理的,MotionEstimatePicture对当前帧的一个宏块算出运动矢量,
    	// 再填充到前面定义的MV数组中
        MotionEstimatePicture(curr->lum, prev_recon->lum, prev_ipol,
    	                    pic->seek_dist, MV, pic->use_gobsync);
    
    
        #ifndef OFFLINE_RATE_CONTROL
          if (pic->bit_rate != 0) 
    	  {
    		  /* 初始化码率控制 */
    		  QP_new = InitializeQuantizer(PCT_INTER, (float)pic->bit_rate, 
    				   (pic->PB ? pic->target_frame_rate/2 : pic->target_frame_rate),
    				   pic->QP_mean);
    		  QP_xmitted = QP_prev = QP_new; 
    	  }
          else 
    	  {
    		QP_new = QP_xmitted = QP_prev = QP; /* 复制旧 QP */
    	  }
        #else
          QP_new = QP_xmitted = QP_prev = QP; /* 复制旧 QP */
        #endif
    
    	dquant = 0; 
    	for ( j = 0; j < lines/MB_SIZE; j++) 
    	{
        #ifndef OFFLINE_RATE_CONTROL
          if (pic->bit_rate != 0)
    	  {
    
    		  AddBitsPicture(bits);
    		  /* 更新QP */      
    		  QP_new =  UpdateQuantizer(abs_mb_num, pic->QP_mean, PCT_INTER, 
    			   (float)pic->bit_rate, pels/MB_SIZE, lines/MB_SIZE, 
    			   bits->total);
    	  }
        #endif
    
          newgob = 0;
          if (j == 0) 
    	  {
    			pic->QUANT = QP;
    			bits->header += CountBitsPicture(pic);//计算图象层码字
    	  }
          else if (pic->use_gobsync && j%pic->use_gobsync == 0)
    	  {
    			bits->header += CountBitsSlice(j,QP); //输出GOB同步头
    			newgob = 1;
    	  }
    	  for ( i = 0; i < pels/MB_SIZE; i++)
    	  {
    	     /* 更新dquant */
             dquant = QP_new - QP_prev;
             if (dquant != 0 && i != 0 && MV[0][j+1][i+1]->Mode == MODE_INTER4V) 
    		 {
    			   dquant = 0;
    			   QP_xmitted = QP_prev;
    			   QuantChangePostponed = 1;
    		 }
             else 
    		 {
    			   QP_xmitted = QP_new;
    			   QuantChangePostponed = 0;
    		 }
    		 if (dquant > 2)  { dquant =  2; QP_xmitted = QP_prev + dquant;}
             if (dquant < -2) { dquant = -2; QP_xmitted = QP_prev + dquant;}
    
             pic->DQUANT = dquant;
    	
             /* 当dquant != 0,修改宏块类型 (例如 MODE_INTER -> MODE_INTER_Q) */
             Mode = ModifyMode(MV[0][j+1][i+1]->Mode,pic->DQUANT);
             MV[0][j+1][i+1]->Mode = Mode;
    
    		 pic->MB = i + j * (pels/MB_SIZE);
    
             if (Mode == MODE_INTER || Mode == MODE_INTER_Q || Mode==MODE_INTER4V)
    		 {
    			   /* 预测P-宏块 */
    			   diff = Predict_P(curr,prev_recon,prev_ipol,
    			   i*MB_SIZE,j*MB_SIZE,MV,pic->PB);
    		 }
             else
    		 {
    			   diff = (MB_Structure *)malloc(sizeof(MB_Structure));
    			   FillLumBlock(i*MB_SIZE, j*MB_SIZE, curr, diff);//写亮度图像  curr:图像数据 diff:宏块树组
    			   FillChromBlock(i*MB_SIZE, j*MB_SIZE, curr, diff);//写色度图像
    		 }
    
             /* P或INTRA宏块 */
             qcoeff_P = MB_Encode(diff, QP_xmitted, Mode); //对宏块数据(P块为残差数据)进行DCT变换量化
             CBP = FindCBP(qcoeff_P, Mode, 64); 
             if (CBP == 0 && (Mode == MODE_INTER || Mode == MODE_INTER_Q)) 
    			ZeroMBlock(diff); //宏块数据设为0
             else
    			MB_Decode(qcoeff_P, diff, QP_xmitted, Mode);//反变换
             recon_data_P = MB_Recon_P(prev_recon, prev_ipol,diff,i*MB_SIZE,j*MB_SIZE,MV,pic->PB);//重建P图象
             Clip(recon_data_P); //使 0<=recon_data_P<=255 
             free(diff);
            
             if(pic->PB==0)
    			ZeroVec(MV[5][j+1][i+1]); //PB帧矢量差置为0
    
    		 if ((CBP==0) && (CBPB==0) && 
    			 (EqualVec(MV[0][j+1][i+1],&ZERO)) && 
                 (EqualVec(MV[5][j+1][i+1],&ZERO)) &&
                 (Mode == MODE_INTER || Mode == MODE_INTER_Q)) 
    		 {
    			  /* 当 CBP 和 CBPB 为0, 16x16 运动矢量为0,PB矢量差为0,
    				 并且编码模式为MODE_INTER或MODE_INTER_Q,跳过该宏块编码*/
    			 if (!syntax_arith_coding)
    				 CountBitsMB(Mode,1,CBP,CBPB,pic,bits);//输出宏块层信息
    		 }
             else
    		 {
    			 /* 正常编码宏块 */
    			if (!syntax_arith_coding) 
    			{ 
    				  /* VLC */
    				 CountBitsMB(Mode,0,CBP,CBPB,pic,bits);
    				if (Mode == MODE_INTER  || Mode == MODE_INTER_Q)
    				{
    					 bits->no_inter++;
    					 CountBitsVectors(MV, bits, i, j, Mode, newgob, pic);//输出运动矢量数据
    				}
    				else if (Mode == MODE_INTER4V)
    				{
    					 bits->no_inter4v++;
    					 CountBitsVectors(MV, bits, i, j, Mode, newgob, pic);
    				}
    				else 
    				{
    					 /* MODE_INTRA 或 MODE_INTRA_Q */
    					 bits->no_intra++;
    					 if (pic->PB)
    					   CountBitsVectors(MV, bits, i, j, Mode, newgob, pic);
    				}
    				if (CBP || Mode == MODE_INTRA || Mode == MODE_INTRA_Q)
    				  CountBitsCoeff(qcoeff_P, Mode, CBP, bits, 64);//输出系数
    			} // end VLC 
             
    			QP_prev = QP_xmitted;
    		 }//end Normal MB
    
             abs_mb_num++;
             QP_cumulative += QP_xmitted;     
         
             ReconImage(i,j,recon_data_P,curr_recon);//重建图象
             free(recon_data_P);
             free(qcoeff_P);
    	  }//end for i
    	}//end for j
    
    	pic->QP_mean = QP_cumulative/(float)abs_mb_num;
    
    	/* 释放内存 */
    	free(pi);
    	for (j = 0; j < (lines>>4)+1; j++)
    		for (i = 0; i < (pels>>4)+2; i++) 
    			for (k = 0; k < 6; k++)
    				free(MV[k][j][i]);
    	return;
    }
    


    细心的朋友可能看出帧内编码与帧间编码在对每个宏块的处理上的区别了:

    (1)在帧内编码的第一步帧内编码直接把宏块数据复制到data,所以变换并量化的数据源是宏块源数据;

             而帧间编码却对宏块数据预测P-宏块,所以变换并量化的数据源是宏块数据预测的结果diff;

    (2)在帧内编码时对DCT系数进行反量化和反DCT、重新构建宏块 后是直接将将重建宏块data输出到整个图像recon中;

           而帧间编码却对DCT系数进行反量化和反DCT、重新构建宏块 后对反变换的结果 重建P图象,然后再将重建结果输出。

     

    下面对帧间编码的CodeOneIntra()函数中几个重要的功能进行讲解:

    1、2、2、2、1   InterpolateImage对数据image进行插值
    // 对数据image进行插值.
    // 这是为了在运动估计时进行半像素搜索,以提高运动矢量的计算精度
    unsigned char *InterpolateImage(unsigned char *image, int width, int height)
    {
      unsigned char *ipol_image, *ii, *oo;
      int i,j;
      // 申请返回的空间
      ipol_image = (unsigned char *)malloc(sizeof(char)*width*height*4);
      ii = ipol_image;
      oo = image;
    
      /* 主图像 */
      for (j = 0; j < height-1; j++)
      {
        for (i = 0; i  < width-1; i++) 
    	{
          *(ii + (i<<1)) = *(oo + i);
          *(ii + (i<<1)+1) = (unsigned char)((*(oo + i) + *(oo + i + 1) + 1)>>1); 
          *(ii + (i<<1)+(width<<1)) = (unsigned char)((*(oo + i) + *(oo + i + width) + 1)>>1); 
          *(ii + (i<<1)+1+(width<<1)) = (unsigned char)((*(oo+i) + *(oo+i+1) +
             *(oo+i+width) + *(oo+i+1+width) + 2)>>2);									 
        }
        /* 每行的最后一个像素 */
        *(ii+ (width<<1) - 2) = *(oo + width - 1);
        *(ii+ (width<<1) - 1) = *(oo + width - 1);
        *(ii+ (width<<1)+ (width<<1)-2) = (unsigned char)((*(oo+width-1)+*(oo+width+width-1)+1)>>1); 
        *(ii+ (width<<1)+ (width<<1)-1) = (unsigned char)((*(oo+width-1)+*(oo+width+width-1)+1)>>1); 
        ii += (width<<2);
        oo += width;
      }
    
      /*最后的行 */
      for (i = 0; i < width-1; i++)
      {
        *(ii+ (i<<1)) = *(oo + i);    
        *(ii+ (i<<1)+1) = (unsigned char)((*(oo + i) + *(oo + i + 1) + 1)>>1);
        *(ii+ (width<<1)+ (i<<1)) = *(oo + i);    
        *(ii+ (width<<1)+ (i<<1)+1) = (unsigned char)((*(oo + i) + *(oo + i + 1) + 1)>>1);
              
      }
    
      /*右下角像素 */
      *(ii + (width<<1) - 2) = *(oo + width -1);
      *(ii + (width<<1) - 1) = *(oo + width -1);
      *(ii + (width<<2) - 2) = *(oo + width -1);
      *(ii + (width<<2) - 1) = *(oo + width -1);
    
      return ipol_image;
    }
    

    1、2、2、2、2  MotionEstimatePicture 完成运动估值

    MotionEstimatePicture()计算出前一帧的重建和当前帧图像之间的运动矢量。它也是按照宏块处理的,

    MotionEstimatePicture()对当前帧的一个宏块算出运动向量,再填充到前面定义的MV数组中。

    // 计算出前一帧的重建和当前帧图像之间的运动向量。
    // 它也是按宏块处理的,MotionEstimatePicture对当前帧的一个宏块算出运动矢量,
    // 再填充到前面定义的MV数组中
    void MotionEstimatePicture(unsigned char *curr, unsigned char *prev, 
               unsigned char *prev_ipol, int seek_dist, 
               MotionVector *MV[6][MBR+1][MBC+2], int gobsync)
    {
      int i,j,k;
      int pmv0,pmv1,xoff,yoff;
      int curr_mb[16][16];
      int sad8 = INT_MAX, sad16, sad0;
      int newgob;
      MotionVector *f0,*f1,*f2,*f3,*f4;
    
      /* 运动估计并存储结果MV */
      for ( j = 0; j < lines/MB_SIZE; j++) 
      {
    
        newgob = 0;
    	// 
        if (gobsync && j%gobsync == 0) 
    	{
          newgob = 1;
        }
    
        for ( i = 0; i < pels/MB_SIZE; i++) 
    	{
    	  // 每一个宏块存着6个宏块
          for (k = 0; k < 6; k++)
            MV[k][j+1][i+1] = (MotionVector *)malloc(sizeof(MotionVector));
    
          /* 整象素搜索 */
          f0 = MV[0][j+1][i+1];
          f1 = MV[1][j+1][i+1];
          f2 = MV[2][j+1][i+1];
          f3 = MV[3][j+1][i+1];
          f4 = MV[4][j+1][i+1];
    
    	  // 寻找PMV
          FindPMV(MV,i+1,j+1,&pmv0,&pmv1,0,newgob,0);
    
          if (long_vectors) 
    	  {
            xoff = pmv0/2; /* 总是能被2整除 */
            yoff = pmv1/2;
          }
          else 
    	  {
            xoff = yoff = 0;
          }
          // 为宏块估算所有的运动向量
          MotionEstimation(curr, prev, i*MB_SIZE, j*MB_SIZE, 
                   xoff, yoff, seek_dist, MV, &sad0);
    
          sad16 = f0->min_error;
          if (advanced)
            sad8 = f1->min_error + f2->min_error + f3->min_error + f4->min_error;
    
          f0->Mode = ChooseMode(curr,i*MB_SIZE,j*MB_SIZE, mmin(sad8,sad16));
    
          /* 半象素精度搜索 */
          if (f0->Mode != MODE_INTRA) 
    	  {
            FindMB(i*MB_SIZE,j*MB_SIZE ,curr, curr_mb);//当前宏块放入curr_mb
            FindHalfPel(i*MB_SIZE,j*MB_SIZE,f0, prev_ipol, &curr_mb[0][0],16,0);
            sad16 = f0->min_error;
    
            if (advanced) 
    		{
              FindHalfPel(i*MB_SIZE,j*MB_SIZE,f1, prev_ipol, &curr_mb[0][0],8,0);
              FindHalfPel(i*MB_SIZE,j*MB_SIZE,f2, prev_ipol, &curr_mb[0][8],8,1);
              FindHalfPel(i*MB_SIZE,j*MB_SIZE,f3, prev_ipol, &curr_mb[8][0],8,2);
              FindHalfPel(i*MB_SIZE,j*MB_SIZE,f4, prev_ipol, &curr_mb[8][8],8,3);
    
              sad8 = f1->min_error +f2->min_error +f3->min_error +f4->min_error;
              sad8 += PREF_16_VEC;
              
              /* 选择0运动矢量, 基于8x8或16x16的运动矢量  */
              if (sad0 < sad8 && sad0 < sad16) 
    		  {
                f0->x = f0->y = 0;
                f0->x_half = f0->y_half = 0;
              }
              else
    		  {
                if (sad8 < sad16) 
                  f0->Mode = MODE_INTER4V;
              }
            }
            else 
    		{
              /* 选择0运动矢量或基于16x16的运动矢量 */
              if (sad0 < sad16) 
    		  {
                f0->x = f0->y = 0;
                f0->x_half = f0->y_half = 0;
              }
            }
    
          }
          else 
            for (k = 0; k < 5; k++)
              ZeroVec(MV[k][j+1][i+1]);
        }
      }
    
      return;
    }
     

    计算运动矢量也称为运动估计,是H.263帧间预测编码的核心,在MotionEstimatePicture()函数中,先以要计算的宏块中心为坐标,在前一帧

    重建帧中确定一个31X31的搜索区域,分别读入这些块的数据存放到两个数组中。

    用SAD_Macroblock()函数计算每一个宏块与当前帧中选定宏块之间的每一像素的亮度差之和,将最小的插入变量sad中,并记录产生该值的宏块,

    计算完所有宏块就可以确定宏块与它的运动矢量。

    /**********************************************************************
     *
     *	Name:        SAD_Macroblock
     *	Description:    找到向量中的SAD的快速方法
     *
     *	Input:	        pointers to search_area and current block,
     *                      Min_F1/F2/FR
     *	Returns:        sad_f1/f2
     *	Side effects:
     *
     *	Date: 940203        Author: PGB
     *                      Mod:    KOL
     *
     ***********************************************************************/
    
    
    int SAD_Macroblock(unsigned char *ii, unsigned char *act_block,
               int h_length, int Min_FRAME)
    {
      int i;
      int sad = 0;
      unsigned char *kk;
    
      kk = act_block;
      i = 16;
      while (i--) 
      {
        sad += (abs(*ii     - *kk     ) +abs(*(ii+1 ) - *(kk+1) )
                +abs(*(ii+2) - *(kk+2) ) +abs(*(ii+3 ) - *(kk+3) )
                +abs(*(ii+4) - *(kk+4) ) +abs(*(ii+5 ) - *(kk+5) )
                +abs(*(ii+6) - *(kk+6) ) +abs(*(ii+7 ) - *(kk+7) )
                +abs(*(ii+8) - *(kk+8) ) +abs(*(ii+9 ) - *(kk+9) )
                +abs(*(ii+10)- *(kk+10)) +abs(*(ii+11) - *(kk+11))
                +abs(*(ii+12)- *(kk+12)) +abs(*(ii+13) - *(kk+13))
                +abs(*(ii+14)- *(kk+14)) +abs(*(ii+15) - *(kk+15)) );
    
        ii += h_length;
        kk += 16;
        if (sad > Min_FRAME)
          return INT_MAX;
      } 
      return sad;
    }
    


    FindHalfPel()函数在整像素搜索的基础上,在上一步结果附近的几个插值点中心计算,选出最优的半像素运动矢量。

    void FindHalfPel(int x, int y, MotionVector *fr, unsigned char *prev, 
             int *curr, int bs, int comp)
    {
      int i, m, n;
      int half_pel;
      int start_x, start_y, stop_x, stop_y, new_x, new_y, lx;
      int min_pos;
      int AE, AE_min;
      CPoint search[9];
    
      start_x = -1;
      stop_x = 1;
      start_y = -1;
      stop_y = 1;
    
      new_x = x + fr->x;
      new_y = y + fr->y;
    
      new_x += ((comp&1)<<3);
      new_y += ((comp&2)<<2);
    
      lx = (mv_outside_frame ? pels + (long_vectors?64:32) : pels);
    
      if (!mv_outside_frame) {
        if ((new_x) <= 0) 
          start_x = 0;
        if ((new_y) <= 0) 
          start_y = 0;
        if ((new_x) >= (pels-bs)) 
          stop_x = 0;
        if ((new_y) >= (lines-bs)) 
          stop_y = 0;
      }
    
      search[0].x = 0;             search[0].y = 0;
      search[1].x = start_x;       search[1].y = start_y; /*   1 2 3   */
      search[2].x = 0;             search[2].y = start_y; /*   4 0 5   */
      search[3].x = stop_x;        search[3].y = start_y; /*   6 7 8   */
      search[4].x = start_x;       search[4].y = 0;
      search[5].x = stop_x;        search[5].y = 0;
      search[6].x = start_x;       search[6].y = stop_y;
      search[7].x = 0;             search[7].y = stop_y;
      search[8].x = stop_x;        search[8].y = stop_y;
    
      AE_min = INT_MAX;
      min_pos = 0;
      for (i = 0; i < 9; i++) {
        AE = 0;
        for (n = 0; n < bs; n++) {
          for (m = 0; m < bs; m++) {
            /* 计算绝对误差 */
            half_pel = *(prev + 2*new_x + 2*m + search[i].x +
                 (2*new_y + 2*n + search[i].y)*lx*2);
            AE += abs(half_pel - *(curr + m + n*16));
          }
        }
    
        if (AE < AE_min) {
          AE_min = AE;
          min_pos = i;
        }
      }
    
      /* 存储最优值 */
      fr->min_error = AE_min;
      fr->x_half = search[min_pos].x;
      fr->y_half = search[min_pos].y;
            
      return;
    }
    


     

    1、2、2、2、3   ModifyMode()函数选择该宏块采用帧内还是帧间编码方式。

    如果该宏块每一个像素的亮度分量与亮度平均值之差的绝对值的和小于某一固定值,则该宏块采用帧内编码,否则采用帧间编码方式。

    int ChooseMode(unsigned char *curr, int x_pos, int y_pos, int min_SAD)
    {
      int i,j;
      int MB_mean = 0, A = 0;
      int y_off;
    
      for (j = 0; j < MB_SIZE; j++) {
        y_off = (y_pos + j) * pels;
        for (i = 0; i < MB_SIZE; i++) {
          MB_mean += *(curr + x_pos + i + y_off);
        }
      }
      MB_mean /= (MB_SIZE*MB_SIZE);
      for (j = 0; j < MB_SIZE; j++) {
        y_off = (y_pos + j) * pels;
        for (i = 0; i < MB_SIZE; i++) {
          A += abs( *(curr + x_pos + i + y_off) - MB_mean );
        }
      }
    
      if (A < (min_SAD - 500)) 
        return MODE_INTRA;
      else
        return MODE_INTER;
    }
    
    int ModifyMode(int Mode, int dquant)
    {
    
      if (Mode == MODE_INTRA) {
        if(dquant!=0)
          return MODE_INTRA_Q;
        else
          return MODE_INTRA;
      }
      else{ 
        if(dquant!=0)
          return MODE_INTER_Q;
        else
          return Mode;
      }
    }
    


    1、2、2、2、3    Predict_P 预测P-宏块

     Predict_P()函数利用前一帧的对应宏块与运动矢量构建当前待编码宏块的预测宏块,并将计算出待编码宏块与预测宏块对应像素的插值。

    MB_Structure *Predict_P(PictImage *curr_image, PictImage *prev_image,
            unsigned char *prev_ipol, int x, int y, 
            MotionVector *MV[6][MBR+1][MBC+2], int PB)
    {
      int m,n;
      int curr[16][16];
      int pred[16][16];
      MotionVector *fr0,*fr1,*fr2,*fr3,*fr4;
      int sum, dx, dy;
      int xmb, ymb;
    
      MB_Structure *pred_error = (MB_Structure *)malloc(sizeof(MB_Structure));
        
      xmb = x/MB_SIZE+1;
      ymb = y/MB_SIZE+1;
    
      fr0 = MV[0][ymb][xmb];
      fr1 = MV[1][ymb][xmb];
      fr2 = MV[2][ymb][xmb];
      fr3 = MV[3][ymb][xmb];
      fr4 = MV[4][ymb][xmb];
    
      /* 装入当前宏块到curr */
      FindMB(x, y, curr_image->lum, curr);
    
      /* 基于半象素MV预测当前块 */
      if (advanced) {
        FindPredOBMC(x, y, MV, prev_ipol, &pred[0][0], 0, PB);
        FindPredOBMC(x, y, MV, prev_ipol, &pred[0][8], 1, PB);
        FindPredOBMC(x, y, MV, prev_ipol, &pred[8][0], 2, PB);
        FindPredOBMC(x, y, MV, prev_ipol, &pred[8][8], 3, PB);
      }
      else 
        FindPred(x, y, fr0, prev_ipol, &pred[0][0], 16, 0);
    
      /* 进行预测 */
      if (fr0->Mode == MODE_INTER || fr0->Mode == MODE_INTER_Q) {
        for (n = 0; n < MB_SIZE; n++)
          for (m = 0; m < MB_SIZE; m++) 
            pred_error->lum[n][m] = (int)(curr[n][m] - pred[n][m]);
    
        dx = 2*fr0->x + fr0->x_half;
        dy = 2*fr0->y + fr0->y_half;
        dx = ( dx % 4 == 0 ? dx >> 1 : (dx>>1)|1 );
        dy = ( dy % 4 == 0 ? dy >> 1 : (dy>>1)|1 );
    
        DoPredChrom_P(x, y, dx, dy, curr_image, prev_image, pred_error);
      }
    
      else if (fr0->Mode == MODE_INTER4V) {
        for (n = 0; n < MB_SIZE; n++)
          for (m = 0; m < MB_SIZE; m++) 
            pred_error->lum[n][m] = (int)(curr[n][m] - pred[n][m]);
    
        sum = 2*fr1->x + fr1->x_half + 2*fr2->x + fr2->x_half +
          2*fr3->x + fr3->x_half + 2*fr4->x + fr4->x_half ; 
        dx = sign(sum)*(roundtab[abs(sum)%16] + (abs(sum)/16)*2);
    
        sum = 2*fr1->y + fr1->y_half + 2*fr2->y + fr2->y_half +
          2*fr3->y + fr3->y_half + 2*fr4->y + fr4->y_half;
        dy = sign(sum)*(roundtab[abs(sum)%16] + (abs(sum)/16)*2);
    
        DoPredChrom_P(x, y, dx, dy, curr_image, prev_image, pred_error);
      }
    
      else
        fprintf(stderr,"Illegal Mode in Predict_P (pred.c)\n");
    
      return pred_error;
    }
    
    


     

     

    1、2、2、2、4   MB_Recon_P 重建P图象
    展开全文
  • 1.视频编码基本原理 (1) 视频信号的冗余信息 以记录数字视频的YUV分量格式为例,YUV分别代表亮度与两个色差信号。例如对于现有的PAL制电视系统,其亮度信号采样频率为13.5MHz;色度信号的频带通常为...
  • 视频压缩编码基本原理

    千次阅读 2015-06-17 12:56:09
    本文介绍一下视频压缩编码和音频压缩编码的基本原理。其实有关视频和音频编码的原理的资料非常的多,但是自己一直也没有去归纳和总结一下,在这里简单总结一下,以作备忘。 1.视频编码基本原理 (1) 视频信号的...
  • WAV格式中常见的压缩编码

    千次阅读 2016-07-29 10:07:20
    WAV格式中常见的压缩编码(compression code)WAV为微软公司(Microsoft)开发的一种声音文件格式,它符合RIFF(Resource Interchange File Format)文件规范,用于保存Windows平台的音频信息资源,被Windows平台及其应用...
  • 音频压缩编码

    千次阅读 2006-08-28 20:05:00
    音频压缩编码 2006-07-01 数字时代 点击: 256
  • MPEG2标准压缩分层和编码原理

    千次阅读 2009-08-28 01:38:00
    本文介绍了MPEG2标准压缩分层和编码原理,并说明了如何在芯片上实现MPEG2音频编码。 MPEG2是当今最为流行的AV压缩标准,可用于视频、音频和数字信息存储。完整的MPEG2标准可满足STB等广播应用和DVD或D-VHS等多媒体...
  • MPEG4视频压缩编码技术详解

    千次阅读 2019-01-18 14:20:25
    MPEG全称是Moving Pictures Experts Group,它是“动态图象专家组”的英文缩写,该专家组成立于1988年,致力于运动图像及其伴音的压缩编码标准化工作,原先他们打算开发MPEG1、 MPEG2、MPEG3和MPEG4四个版本,以适用...
  • 很显然,压缩编码处理是必须的。一段刚刚捕获的60分钟原始视屏可能达到2G,经过压缩处理可以减至500MB左右,一张单反照片可能有5MB,经过压缩之后只有400KB,而质量不会发生明显的损失。 hadoop面临的情况也是一样...
  • 音频信号的数字化及压缩编码

    千次阅读 2017-03-12 23:14:49
    电视广播离不开声音信号,随着人们对电视质量的要求越来越高,在数字电视广播、高清晰数字电视和数字电影中不仅应有高质量的图像,还要保证有高质量的伴音。 我们行业标准GY/T 156-2000《演播室数字音频参数》中对...
  • 解决数据压缩的问题通常可以从三步来分析:第一步是为什么要做,即数据压缩的必要性问题;第二步是为什么可以做,即分析信源数据的特性,并在此基础上进行数据压缩的可行性分析;第三步是在第二步分析的基础上,如何...
  • 音频压缩编码 FAQ

    千次阅读 2006-08-24 09:52:00
    1、什么是语音编码技术?其发展与现状是怎样的? 答: 语音信号的数字化传输,一直是通信的发展方向之一。...从最初的PCM64k编码到现在标准语音压缩协议,如G.723编码速率为5.3K或 6.3Kbps;G.729编码速率为8Kb
  • 数据压缩知识点整理

    千次阅读 2017-04-27 16:57:20
    数据压缩 是指在不丢失有用信息的前提下, 缩减数据量 以减少存储空间, 提高传输、存储和处理效率, 或按照一定的算法对数据进行重新组织, 减少数据的冗余和存储的空间的一种技术.
  • 块截断编码图像压缩技术

    千次阅读 2020-10-04 03:20:19
    论文先介绍了当前流行的图像压缩技术,重点介绍块截断编码技术,先从理论上介绍块截断编码原理,块截断编码是一种有效、快速的数字图像压缩技术,作为一种经典的图像压缩编码,块截断编码技术的实时性很强。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 17,450
精华内容 6,980
关键字:

声音数据压缩编码标准