IV. HEVC的编码技术
HEVC Video Coding Techniques
As in all prior ITU-T and ISO/IEC JTC 1 video coding standards since H.261 [2],
the HEVC design follows the classic block-based hybrid video coding approach
(as depicted in Fig. 1). The basic source-coding algorithm is a hybrid
of interpicture prediction to exploit temporal statistical dependences,
intrapicture prediction to exploit spatial statistical dependences, and transform
coding of the prediction residual signals to further exploit spatial statistical
dependences. There is no single coding element in the HEVC design that provides
the majority of its significant improvement in compression efficiency in relation
to prior video coding standards. It is, rather, a plurality of smaller improvements
that add up to the significant gain.
和从H.261以来的视频编码标准一样,HEVC的设计沿用了经典的基于块的混合视频编码方式(如图1所示)。
基本的信源编码算法是对时域统计相关性使用帧间预测,对空域统计相关性使用帧内预测,
再对预测残差信号使得变换编码进一步去除空间统计相关性。
HEVC取得比以前的标准更好的压缩效果,而且以很小的改善就取得了很大的收获。
Fig. 1. Typical HEVC video encoder (with decoder modeling elements shaded in light gray).
A. 图像的像素表示
Sampled Representation of Pictures
For representing color video signals, HEVC typically uses a tristimulus YCbCr color
space with 4:2:0 sampling (although extension to other sampling formats is
straightforward, and is planned to be defined in a subsequent version). This separates
a color representation into three components called Y, Cb, and Cr. The Y component is
also called luma, and represents brightness. The two chroma components Cb and Cr represent
the extent to which the color deviates from gray toward blue and red, respectively.
Because the human visual system is more sensitive to luma than chroma, the 4:2:0 sampling
structure is typically used, in which each chroma component has one fourth of the number
of samples of the luma component (half the number of samples in both the horizontal
and vertical dimensions). Each sample for each component is typically represented with 8
or 10 b of precision, and the 8-b case is the more typical one. In the remainder of
单例模式大家并不陌生,也都知道它分为什么懒汉式、饿汉式之类的。但是你对单例模式的理解足够透彻吗?今天我带大家一起来看看我眼中的单例,可能会跟你的认识有所不同。
下面是一个简单的小实例:
//简单懒汉式 public class Singleton { //单例实例变量 private static Singleton instance = null; //私有化的构造方法,保证外部的类不能通过构造器来实例化 private Singleton() {} //获取单例对象实例 public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } System.out.println("我是简单懒汉式单例!"); return instance; } }
很容易看出,上面这段代码在多线程的情况下是不安全的,当两个线程进入if (instance == null)时,两个线程都判断instance为空,接下来就会得到两个实例了。这不是我们想要的单例。
接下来我们用加锁的方式来实现互斥,从而保证单例的实现。
//同步法懒汉式 public class Singleton { //单例实例变量 private static Singleton instance = null; //私有化的构造方法,保证外部的类不能通过构造器来实例化 private Singleton() {} //获取单例对象实例 public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } System.out.println("我是同步法懒汉式单例!"); return instance; } }
加上synchronized后确实保证了线程安全,但是这样就是最好的方法吗?很显然它不是,因为这样一来每次调用getInstance()方法是都会被加锁,而我们只需要在第一次调用getInstance()的时候加锁就可以了。这显然影响了我们程序的性能。我们继续寻找更好的方法。
经过分析发现,只需要保证instance = new Singleton()是线程互斥就可以保证线程安全,所以就有了下面这个版本:
//双重锁定懒汉式 public class Singleton { //单例实例变量 private static Singleton instance = null; //私有化的构造方法,保证外部的类不能通过构造器来实例化 private Singleton() {} //获取单例对象实例 public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } System.out.println("我是双重锁定懒汉式单例!"); return instance; } }
这次看起来既解决了线程安全问题,又不至于每次调用getInstance()都会加锁导致降低性能。看起来是一个完美的解决方案,事实上是这样的吗?
很遗憾,事实并非我们想的那么完美。java平台内存模型中有一个叫“无序写”(out-of-order writes)的机制。正是这个机制导致了双重检查加锁方法的失效。这个问题的关键在上面代码上的第5行:instance = new Singleton(); 这行其实做了两个事情:1、调用构造方法,创建了一个实例。2、把这个实例赋值给instance这个实例变量。可问题就是,这两步jvm是不保证顺序的。也就是说。可能在调用构造方法之前,instance已经被设置为非空了。下面我们一起来分析一下:
假设有两个线程A、B
1、线程A进入getInstance()方法。
2、因为此时instance为空,所以线程A进入synchronized块。
3、线程A执行 instance = new Singleton(); 把实例变量instance设置成了非空。(注意,是在调用构造方法之前。)
4、线程A退出,线程B进入。
5、线程B检查instance是否为空,此时不为空(第三步的时候被线程A设置成了非空)。线程B返回instance的引用。(问题出现了,这时instance的引用并不是Singleton的实例,因为没有调用构造方法。)
6、线程B退出,线程A进入。
7、线程A继续调用构造方法,完成instance的初始化,再返回。
难道就没有一个好方法了吗?好的方法肯定是有的,我们继续探索!
//解决无序写问题懒汉式 public class Singleton { //单例实例变量 private static Singleton instance = null; //私有化的构造方法,保证外部的类不能通过构造器来实例化 private Singleton() {} //获取单例对象实例 public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { //1 Singleton temp = instance; //2 if (temp == null) { synchronized (Singleton.class) { //3 temp = new Singleton(); //4 } instance = temp; //5 } } } System.out.println("我是解决无序写懒汉式单例!"); return instance; } }
1、线程A进入getInstance()方法。
2、因为instance是空的 ,所以线程A进入位置//1的第一个synchronized块。
3、线程A执行位置//2的代码,把instance赋值给本地变量temp。instance为空,所以temp也为空。
4、因为temp为空,所以线程A进入位置//3的第二个synchronized块。
5、线程A执行位置//4的代码,把temp设置成非空,但还没有调用构造方法!(“无序写”问题)
6、线程A阻塞,线程B进入getInstance()方法。
7、因为instance为空,所以线程B试图进入第一个synchronized块。但由于线程A已经在里面了。所以无法进入。线程B阻塞。
8、线程A激活,继续执行位置//4的代码。调用构造方法。生成实例。
9、将temp的实例引用赋值给instance。退出两个synchronized块。返回实例。
10、线程B激活,进入第一个synchronized块。
11、线程B执行位置//2的代码,把instance实例赋值给temp本地变量。
12、线程B判断本地变量temp不为空,所以跳过if块。返回instance实例。
到此为止,上面的问题我们是解决了,但是我们突然发现为了解决线程安全问题,但给人的感觉就像身上缠了很多毛线.... 乱糟糟的,所以我们要精简一下:
//饿汉式 public class Singleton { //单例变量 ,static的,在类加载时进行初始化一次,保证线程安全 private static Singleton instance = new Singleton(); //私有化的构造方法,保证外部的类不能通过构造器来实例化。 private Singleton() {} //获取单例对象实例 public static Singleton getInstance() { System.out.println("我是饿汉式单例!"); return instance; } }
看到上面的代码,瞬间觉得这个世界清静了。不过这种方式采用的是饿汉式的方法,就是预先声明Singleton对象,这样带来的一个缺点就是:如果构造的单例很大,构造完又迟迟不使用,会导致资源浪费。