###1. YUV
YUV是编译true-color颜色空间(color space)的种类,Y’UV, YUV, YCbCr,YPbPr等专有名词都可以称为YUV,彼此有重叠。“Y”表示明亮度(Luminance、Luma),“U”和“V”则是色度、浓度(Chrominance、Chroma)

YUV 格式有两个格式:平面格式(planar formats) 和 紧缩格式(packed formats)。

  • planar 格式,先连续存储所有像素点的 Y,接着存储像素点 U,最后存储像素点 V。
  • packed 格式,每个像素点的 Y,U,V 是连续交叉存储的。

YUV码流的存储格式其实与其采样的方式密切相关,主流的采样方式有三种,YUV4:4:4,YUV4:2:2,YUV4:2:0;他们之间的区别:

  • YUV 4:4:4采样,每一个Y对应一组UV分量。
  • YUV 4:2:2采样,每两个Y对应一组UV分量。
  • YUV 4:2:0采样,每四个Y对应一组UV分量。

###2. YUV420 存储方式

  • YU12和YV12属于YUV420格式,也是一种Plane模式,将Y、U、V分量分别打包,依次存储。其每一个像素点的YUV数据提取遵循YUV420格式的提取方式,即4个Y分量共用一组UV。
  • NV12和NV21属于YUV420格式,是一种two-plane模式,即Y和UV分为两个Plane,但是UV(CbCr)为交错存储,而不是分为三个plane。

假设一个分辨率为8X4的YUV图像,它们的格式如下图:

YUV420SP 格式

YUV420P 格式

YUV420P,Y,U,V三个分量都是平面格式,分为I420和YV12。I420格式和YV12格式的不同处在U平面和V平面的位置不同。在I420格式中,U平面紧跟在Y平面之后,然后才是V平面(即:YUV);但YV12则是相反(即:YVU)。

YUV420SP, Y分量平面格式,UV打包格式, 即NV12。 NV12与NV21类似,U 和 V 交错排列,不同在于UV顺序。

  • I420: YYYYYYYY UU VV =>YUV420P
  • YV12: YYYYYYYY VV UU =>YUV420P
  • NV12: YYYYYYYY UVUV =>YUV420SP
  • NV21: YYYYYYYY VUVU =>YUV420SP

###3.转换(Java层)

####3.1 YV12toNV21

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void YV12toNV21(final byte[] input,
final byte[] output, final int width, final int height) {
final int size = width * height;
final int quarter = size / 4;
final int vPosition = size; // This is where V starts
final int uPosition = size + quarter; // This is where U starts
System.arraycopy(input, 0, output, 0, size); // Y is same
for (int i = 0; i < quarter; i++) {
output[size + i*2 ] = input[vPosition + i]; // For NV21, V first
output[size + i*2 + 1] = input[uPosition + i]; // For Nv21, U second
}
}

###3.2 YV12toI420

1
2
3
4
5
public void YV12toI420(byte[] yv12bytes, byte[] i420bytes, int width, int height) {
System.arraycopy(yv12bytes, 0, i420bytes, 0, width * height);
System.arraycopy(yv12bytes, width * height + width * height / 4, i420bytes, width * height, width * height / 4);
System.arraycopy(yv12bytes, width * height, i420bytes, width * height + width * height / 4, width * height / 4);
}

###3.3 YV12toNV12

1
2
3
4
5
6
7
8
9
void YV12toNV12(byte[] yv12bytes, byte[] nv12bytes,int width,int height){
int nLenY = width * height;
int nLenU = nLenY /4;
System.arraycopy(yv12bytes,0, nv12bytes,0, width *height);
for(inti = 0; i < nLenU; i++) {
nv12bytes[nLenY +2* i + 1] = yv12bytes[nLenY + i];
nv12bytes[nLenY +2* i] = yv12bytes[nLenY + nLenU + i];
}
}

###3.4 NV21toI420

1
2
3
4
5
6
7
8
9
10
public void Nv21ToI420(byte[] data, byte[] dstData, int w, int h) {
int size = w * h;
// Y
System.arraycopy(data, 0, dstData, 0, size);
for (int i = 0; i < size / 4; i++) {
dstData[size + i] = data[size + i * 2 + 1]; //U
dstData[size + size / 4 + i] = data[size + i * 2]; //V
}
}

###3.5 NV21toNV12

1
2
3
4
5
6
7
8
9
10
public void NV21toNV12(byte[] data, byte[] dstData, int w, int h) {
int size = w * h;
// Y
System.arraycopy(data, 0, dstData, 0, size);
for (int i = 0; i < size / 4; i++) {
dstData[size + i * 2] = data[size + i * 2 + 1]; //U
dstData[size + i * 2 + 1] = data[size + i * 2]; //V
}
}

由于在 Java 层进行转换效率较低且耗费内存,可以将转换放在 jni 层进行。