版权声明:本文为卫伟学习总结文章,转载请注明出处!
近期由于项目需要,开始接触视频像素格式,因此在这里做一个小结;
像素格式描述了像素数据存储所用的格式,定义了像素在内存中编码方式。RGB和YUV为两种经常使用的像素格式。
1.RGB格式
一般较为熟悉,RGB图像具有三个通道R、G、B,分别对应红、绿、蓝三个分量,由三个分量的值决定颜色;通常,会给RGB图像加一个通道alpha,即透明度,于是共有四个分量共同控制颜色。
2.YUV格式
YUV是一种颜色编码格式,可以说YUV流媒体是原始数据,大部分的视频领域都在使用。它与RGB类似,但RGB更多的用于渲染时,而YUV则用于数据传输,因为它占用更少的频宽。当然,实时通讯为了降低带宽都会采用H264/H265编码。从字面理解,YUV的含义:Y代表亮点信息(灰度),UV分别代表色彩信息。
YUV像素格式来源与RGB像素格式,通过公司运算,YUV三分量可以还原RGB,YUV转RGB的公式如下:
R = Y + 1.403V
G = Y - 0.344U - 0.714V
B = Y + 1.770U
一般,将RGB和YUV的范围均限制在[0,255]间,则有如下转换公式:
R = Y + 1.403(V - 128)
G = Y - 0.344(U - 128) - 0.714(V - 128)
B = Y + 1.770(U - 128)
2.1.YUV采样
YUV相比于RGB格式最大的好处是可以做到保持图像质量降低不明显的前提下,减小文件大小。YUV格式之所以能够做到,是因为进行了采样操作。
YUV码流的存储格式与其采样方式密切相关,主流的采样方式有三种:YUV 4:4:4(YUV444),YUV 4:2:2(YUV422),YUV 4:2:0(YUV420)。
若以以黑点表示采样该像素点的Y分量,以空心圆圈表示采用该像素点的UV分量,则这三种采样方式如下:
- YUV 4:4:4采样,每一个Y对应一组UV
- YUV 4:2:2采样,每两个Y共用一组UV分量
- YUV 4:2:0采样,每四个Y公用一组UV分量
2.2.YUV存储格式
YUV存储可以分为两种:packd(打包) 和 planar(平面);
- packed: Y、U、V分量穿插着排列,三个分量存在一个Byte型数组里;
- planar: Y、U、V分量分别存在三个Byte型数组中;
常见的像素格式
1.YUV422:YUYV、YVYU、UYVY、V
这四种格式每一种又可以分为2类(packed和planar),以YUYV为例,一个6*4的图像的存储方式如下:
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
U U U U U U Y U Y V Y U Y V Y U Y V
U U U U U U Y U Y V Y U Y V Y U Y V
V V V V V V Y U Y V Y U Y V Y U Y V
V V V V V V Y U Y V Y U Y V Y U Y V
- Planar - - Packed -
2.YUV420
YUV420p: I420、YV12
YUV420sp: NV12、NV21
同样,对于一个6*4的图像,这四种像素格式的存储方式如下:
Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
U U U U U U V V V V V V U V U V U V V U V U V U
V V V V V V U U U U U U U V U V U V V U V U V U
- I420 - - YV12 - - NV12 - - NV21 -
注:
- I420、YV12三个分量均为平面格式,即分别存在三个Byte型数组中;
- NV12、NV21的存储格式为Y平面,UV打包,即Y信息存储在一个数组中,UV信息存储在一个矩阵中。
3.Android I420转NV21,NV21转I420
/**
* Nv21:
* YYYYYYYY
* YYYYYYYY
* YYYYYYYY
* YYYYYYYY`------` ````
* VUVU
* VUVU
* VUVU
* VUVU
* <p>
*
* YUV420P
* YYYYYYYY
* YYYYYYYY
* YYYYYYYY
* YYYYYYYY`------` ````
* UVUV
* UVUV
* UVUV
* UVUV
* <p>
*
* I420:
* YYYYYYYY
* YYYYYYYY
* YYYYYYYY
* YYYYYYYY
* UUUU
* UUUU
* VVVV
* VVVV
*/
public class I420NV21Test {
public static byte[] I420ToNV21(byte[] data, int width, int height) {
byte[] ret = new byte[data.length];
int total = width * height;
ByteBuffer bufferY = ByteBuffer.wrap(ret, 0, total);
ByteBuffer bufferV = ByteBuffer.wrap(ret, total ,total/4);
ByteBuffer bufferU = ByteBuffer.wrap(ret, total + total / 4, total /4);
bufferY.put(data, 0, total);
for(int i = 0; i < total /4; i+=1) {
bufferV.put(data[total+i]);
bufferU.put(data[i+total+total/4]);
}
return ret;
}
/**
* nv21转I420
* @param data
* @param width
* @param height
* @return
*/
public static byte[] nv21ToI420(byte[] data, int width, int height) {
byte[] ret = new byte[data.length];
int total = width * height;
ByteBuffer bufferY = ByteBuffer.wrap(ret, 0, total);
ByteBuffer bufferU = ByteBuffer.wrap(ret, total, total / 4);
ByteBuffer bufferV = ByteBuffer.wrap(ret, total + total / 4, total / 4);
bufferY.put(data, 0, total);
for (int i=total; i<data.length; i+=2) {
bufferV.put(data[i]);
bufferU.put(data[i+1]);
}
return ret;
}
/*
* @param data Nv21数据
* @param dstData I420(YUV420)数据
* @param w 宽度
* @param h 长度
*/
public static byte[] Nv21ToI420(byte[] data, int w, int h) {
byte[] dstData = new byte[data.length];
int size = w * h;
// Y
System.arraycopy(data, 0, dstData, 0, size);
int sizef = size/4;
for (int i = 0; i < sizef; i++) {
dstData[size + i] = data[size + (i<<1) + 1]; //U
dstData[size + (size>>2) + i] = data[size + (i<<1)]; //V
}
return dstData;
}
/**
* 将Nv21数据转换为Yuv420SP数据
* @param data Nv21数据
* @param dstData Yuv420sp数据
* @param w 宽度
* @param h 高度
*/
public static byte[] Nv21ToYuv420SP(byte[] data, int w, int h) {
byte[] dstData = new byte[data.length];
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
}
return dstData;
}
public static void main(String[] args) {
//模拟nv21 数据 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2]
//模拟I420 数据 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3]
byte[] data=new byte[30];
for(int i=0;i<20;i++){
data[i]=1;
}
for(int i=0;i<10;i++){
if(i%2==0){
data[i+20]=3;
}else{
data[i+20]=2;
}
}
System.out.println("NV21 数据 --> "+Arrays.toString(data));
byte[] i420data=nv21ToI420(data,5,4);
System.out.println("I420 数据 --> "+Arrays.toString(i420data));
byte[] nv21data=I420ToNV21(data,5,4);
System.out.println("NV21 数据 --> "+Arrays.toString(nv21data));
System.out.println("\n");
byte[] i420data1=Nv21ToI420(data,5,4);
System.out.println("方式二 I420 数据 --> "+Arrays.toString(i420data1));
byte[] yuv420sp = Nv21ToYuv420SP(data,5,4);
System.out.println("YUV420SP 数据 --> "+Arrays.toString(yuv420sp));
}
一张图片的大小为widthheight, YUV420图片数据的大小就是widthheight3/2
NV21数据格式
widthheight个字节存的是每个像素的Y分量,后面的widthheight/2字节是VUVUVUVU…
如: YYYY…YYVUVUVUVU…
I420 是数据格式:
widthheight个字节还是每个像素的Y分量,接下来的widthheight/4字节是U分量,最后的widthheight/4字节是V分量
如: YYYY…YYUUUUVVVV…
模拟数据测试结果如下:
其中: 1表示是Y, 2表示是U ,3表示是V
网友评论