RGBAYUV色彩格式及libyuv的使用
Posted 湖广午王
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RGBAYUV色彩格式及libyuv的使用相关的知识,希望对你有一定的参考价值。
最近一段时间因为工作的需要,要使用到libyuv。因为之前写录制视频的时候,也要用到rgb转yuv,自己结合网上的资料做了个实现,记录了点笔记,现在索性一起整理下。
常用的色彩格式
常见的色彩格式主要分为两类,一类是RGBA系列,一类是YUV系列。
RGBA系列
首先就是rgba系列的格式,RGBA色彩主要用于色彩的显示和描述。常见的有RGBA/ARGB/BGRA/ABGR/RGB/BGR。这些格式都比较好理解了。R、G、B、A分别表示红绿蓝及透明通道。
以RGBA为例,就是4个字节表示一个颜色值,排列方式就是RGBARGBARGBA这样排列。而RGB,就是三个字节表示一个颜色值,没有透明通道,排列方式就是RGBRGBRGB。在通常的视频中,也是没有透明通道的(也有例外,比如MOV格式,是可以包含透明通道的)。所以当RGBA编码为视频色彩时,A是会被丢掉的。
当然,上面说的,是将每个色彩都用一个字节来表示的情况。RGBA也有RGBA_8888,RGBA_4444,RGB565等等众多格式,也就是并不是每个颜色都用一个字节来表示。以RGB565为例,是用两个字节来表示RGB三个色彩,R占5位,G占6位,B占5位。RGB565与RGB24相比,色彩上稍有损失,一般情况下,不细致对比,不容易发现这个损失,但是内存上会节约1/3的大小。
YUV系列
YUV主要用于优化彩色视频信号的传输,相比RGBA色彩来说,YUV格式占用更少的内存。YUV系列的格式,与RGBA一样,也是五花八门,常见的有YUY2、YUYV、YVYU、UYVY、AYUV、Y41P、Y411、Y211、IF09、IYUV、YV12、YVU9、YUV411、YUV420、YUV422等等。Y表示亮度,U、V都表示色度。如果只有Y分量,没有UV分量,那么得到的就是黑白灰度图像。
与YUV类似的还有YCrCb,YIQ等色彩格式。YIQ模型与YUV模型类似,用于NTSC制式的电视系统。YIQ颜色空间中的I和Q分量相当于将YUV空间中的UV分量做了一个33度的旋转。而YCrCb是YUV的一种派生色彩,Y包含了绿色色度和亮度,Cr表示红色色度,Cb表示蓝色色度。
我们在实际使用时,遇到最多的大概就是NV21、NV12、YUV420P、YUV420SP、I420等这些格式。他们有什么区别呢?
实际上I420就是标准的YUV420P,以4*4的图像来说,YUV排列顺序为YYYYYYYYYYYYYYYYUUUUVVVV。YUV大小分别为4*4、2*2、2*2。
Y1Y2Y7Y8U1V1 可以表示四个像素点,其他的同色区域一样,都是表示四个像素点,像素位置与Y对应。
借用Wiki上的图片表示下:
NV21为标准的YUV420SP,以4*4的图像来说,YUV排列顺序为YYYYYYYYYYYYYYYYUVUVUVUV。Y大小分别为4*4、UV大小为4*2。如图:
NV12与NV21类似,也是YUV420SP,只是排列顺序上UV换了个边,变为YYYYYYYYYYYYYYYYVUVUVUVU。
当然像YUV411,YUV420对比,差异主要在于采样点上。YUV虽然格式众多,但是使用起来也是大同小异。更多可参考Wiki上的YUV介绍。
常用RGB与YUV之间的转换
很多时候,我们在网上找RGB转YUV格式或者YUV转RGB格式的转换公式时,总会得到不一样的公式,让我们无法选择,不知道哪个是正确的。实际上,RGB转YUV或者YUV转RGB的确会有不同的公式。这是由于不同的标准以及转换校正造成的。我们利用RGB转成YUV来传输,然后显示时又需要被还原成RGB。
根据BT.601标准(SDTV,标清),定义参数如下:
Wr=0.299
Wb=0.114
Wg=1-Wr-Wb=0.587
Umax=0.436
Vmax=0.615
RGB转YUV公式如下:
Y‘=WrR+WgG+WbB=0.299R+0.587G+0.114B
U=UmaxB−Y‘1−Wb≈0.492(B−Y‘)
V=VmaxR−Y‘1−Wr≈0.877(R−Y‘)
反向推导YUV转RGB的,得到公式如下:
R=Y‘+1.14V
G=Y‘−0.395U−0.581V
B=Y‘+2.033U
即得到RGB和YUV根据BT.601标准的公式为:
#RGB转YUV
#[Y] [0.299 0.587 0.114 ][R]
#[U] = [-0.147 -0.289 0.436 ][G]
#[V] [0.615 -0.515 -0.100 ][B]
Y = 0.299 R + 0.587 G + 0.114 B
U = -0.147 R - 0.289 G + 0.436 B
V = 0.615 R - 0.515 G - 0.100 B
#YUV转RGB
#[R] [1 0 1.140 ][Y]
#[G] = [1 -0.395 -0.581 ][U]
#[B] [1 2.032 0 ][V]
R = Y + 1.402 V
G= Y - 0.395 U - 0.581V
B= Y + 2.032U
# 在老式的非SIMD体系结构中,浮点运算慢与定点运算,所以变换下:
# RGB转YUV,studio-swing,Y的范围为[16-235],UV的范围为[16-240]
Y = ((66R+129G+25B+128)>>8)+16
U = ((-38R-74G+112B+128)>>8)+128
V = ((112R-94G-18B+128)>>8)+128
# RGB转YUV,full-swing,YUV的范围都为[0-255]
Y = (77R+150G+29B+128)>>8
U = (-43R-84G+127B+128)>>8)+128
V = ((127R-106G-21B+128)>>8)+128
# YUV转RGB
C = Y-16
D = U-128
E = V-128
R = clamp((298*C + 409 * E +128)>>8)
G = clamp((298*C - 100* D - 208* E+ 128)>>8)
B = clamp((298*C + 516* D- 128)>>8)
RGBA转YUV具体实现,可参考Android视频编码——RGBA、RGB、BGRA、BGR转YUV420P、YUV420SP
libyuv的使用
libyuv提供了非常方面好用的色彩空间转换、旋转、缩放的功能,转换效率也非常高。如果有色彩空间转换、旋转、缩放等功能的需求,不妨使用此库来完成。在使用libyuv进行转换或者自己写代码进行转换的过程中,可以使用RawGfx这个软件,来查看你的转换是否正确,无论是RGBA格式还是YUV格式的原始数据,都可以用它进行查看。
libyuv的jni封装
在libyuv中,提供了非常丰富的方法,我们实际使用时往往只需要使用到其中的一小部分。为了在android中调用libyuv,我们需要编写Jni代码,调用libyuv中的方法。
以RGBA转I420为例,libyuv中提供了许多不同的方法来针对RGBA、ARGB、RGB565等等一系列的不同的RGBA格式转I420格式的方法。为了简化我们的工作我们可以做一个简单的封装来实现Java层调用RGBA转I420的方法。
我们可以先写出一个native的接口,来表述我们需要的功能:
public class YuvUtils
//rgba也会有很多类型,所以我们加一个type的值,来表示rgba是什么类型
//也可以用直接写一个rgba转yuv的,rgba和yuv类型都不固定,用type来表示所有类型的rgba到yuv的转换
public static native int RgbaToI420(int type,byte[] rgba,byte[] yuv,int width,int height);
然后就是编写Jni代码了。
Jni代码中定义了一个函数指针数组,包含将会对Java提供的RGBA转I420的类型,值得注意的是在Java层传入byte[]以RGBA顺序排列时,libyuv是用ABGR来表示这个排列,如果期望传入的数据是RGBA排列,使用libyuv是用libyuv::RGBAToI420这个方法,得到的YUV数据将是错误的数据。
#include <assert.h>
#include "libyuv.h"
#include "jni.h"
#include "android/log.h"
#define YUV_UTILS_JAVA "com/wuwang/libyuv/YuvUtils"
#ifdef __cplusplus
extern "C"
#endif
static int (*rgbaToI420Func[])(const uint8 *,int,uint8 *,int,uint8 *,int ,uint8 *,int,int,int)=
libyuv::ABGRToI420,libyuv::RGBAToI420,libyuv::ARGBToI420,libyuv::BGRAToI420,
libyuv::RGB24ToI420,libyuv::RGB565ToI420
;
int rgbaToI420(JNIEnv * env,jclass clazz,jbyteArray rgba,jint rgba_stride,
jbyteArray yuv,jint y_stride,jint u_stride,jint v_stride,
jint width,jint height,
int (*func)(const uint8 *,int,uint8 *,int,uint8 *,int ,uint8 *,int,int,int))
size_t ySize=(size_t) (y_stride * height);
size_t uSize=(size_t) (u_stride * height >> 1);
jbyte * rgbaData= env->GetByteArrayElements(rgba,JNI_FALSE);
jbyte * yuvData=env->GetByteArrayElements(yuv,JNI_FALSE);
int ret=func((const uint8 *) rgbaData, rgba_stride, (uint8 *) yuvData, y_stride,
(uint8 *) (yuvData) + ySize, u_stride, (uint8 *) (yuvData )+ ySize + uSize,
v_stride, width, height);
env->ReleaseByteArrayElements(rgba,rgbaData,JNI_OK);
env->ReleaseByteArrayElements(yuv,yuvData,JNI_OK);
return ret;
int Jni_RgbaToI420(JNIEnv * env,jclass clazz,jint type,jbyteArray rgba,jbyteArray yuv,jint width,jint height)
uint8 cType=(uint8) (type & 0x0F);
int rgba_stride= ((type & 0xF0) >> 4)*width;
int y_stride=width;
int u_stride=width>>1;
int v_stride=u_stride;
return rgbaToI420(env,clazz,rgba,rgba_stride,yuv,y_stride,u_stride,v_stride,width,height,rgbaToI420Func[cType]);
//libyuv中,rgba表示abgrabgrabgr这样的顺序写入文件,java使用的时候习惯rgba表示rgbargbargba写入文件
static JNINativeMethod g_methods[]=
"RgbaToI420","(I[B[BII)I", (void *)Jni_RgbaToI420,
//.... 其他方法映射
;
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
JNIEnv* env = nullptr;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK)
return JNI_ERR;
assert(env != nullptr);
jclass clazz=env->FindClass(YUV_UTILS_JAVA);
env->RegisterNatives(clazz, g_methods, (int) (sizeof(g_methods) / sizeof((g_methods)[0])));
return JNI_VERSION_1_4;
JNIEXPORT void JNI_OnUnload(JavaVM *jvm, void *reserved)
JNIEnv* env = nullptr;
if (jvm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK)
return;
jclass clazz=env->FindClass(YUV_UTILS_JAVA);
env->UnregisterNatives(clazz);
#ifdef __cplusplus
#endif
传入的type,被每4位表示一个具体的意义。从低位到高位,0-3 表示转换类型,4-7 表示rgba_stride的宽度的倍数,8-11 表示yuv_stride宽度移位数,12-15 表示uv左移位数。
根据Jni代码中对于type各位的解析与使用,定义出类型转换的几个常量如下。这样我们就封装了一个通用的rgba转I420的方法了。
public final class Key
private Key();
//0-3 表示转换类型
//4-7 表示rgba_stride的宽度的倍数
//8-11 表示yuv_stride宽度移位数
//12-15 表示uv左移位数
public static final int RGBA_TO_I420=0x01001040;
public static final int ABGR_TO_I420=0x01001041;
public static final int BGRA_TO_I420=0x01001042;
public static final int ARGB_TO_I420=0x01001043;
public static final int RGB24_TO_I420=0x01001034;
public static final int RGB565_TO_I420=0x01001025;
封装后的方法使用与检验
封装后的方法使用也比较简单,直接获取一个Bitmap,然后将Bitmap中的rgba数据copy出来,进行转换就可以了,转换完的结果保存到文件中,然后用RawGfx来检查下转换的结果是否正确。
Bitmap bitmap= BitmapFactory.decodeResource(getResources(),R.mipmap.bg);
width=bitmap.getWidth();
height=bitmap.getHeight();
File file=new File(getExternalFilesDir(null).getAbsolutePath()+"/cache.yuv");
OutputStream os = new FileOutputStream(file);
ByteBuffer buffer=ByteBuffer.allocate(bitmap.getWidth()*bitmap.getHeight()*4);
bitmap.copyPixelsToBuffer(buffer);
byte[] yuvData=new byte[bitmap.getWidth()*bitmap.getHeight()*3/2];
YuvUtils.RgbaToI420(Key.RGBA_TO_I420,buffer.array(),yuvData,bitmap.getWidth(),bitmap.getHeight());
//rgbToYuv(buffer.array(),bitmap.getWidth(),bitmap.getHeight(),yuvData);
Log.e("wuwang","width*height:"+bitmap.getWidth()+"/"+bitmap.getHeight());
os.write(yuvData);
os.flush();
os.close();
如下所示,是使用libyuv中的libyuv::RGBAToI420转换得到的结果:
由于上面说过,libyuv表示的排列顺序和Bitmap的RGBA表示的顺序是反向的。所以实际要调用libyuv::ABGRToI420才能得到正确的结果。
示例代码下载
libyuv提供了丰富的功能,其他功能使用与这个差不多,在github上的示例代码中有其他常用的转换、缩放、旋转等方法。有需要的可自行下载。
以上是关于RGBAYUV色彩格式及libyuv的使用的主要内容,如果未能解决你的问题,请参考以下文章