博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android视频渲染: YUV转RGB
阅读量:6862 次
发布时间:2019-06-26

本文共 4909 字,大约阅读时间需要 16 分钟。

  hot3.png

Android SDK为Camera预览提供了一个Demo,这个Demo的大致流程是初始化一个Camera和一个SurfaceView,SurfaceView被 创建之后可以获取到一个SurfaceHolder的实例,将这个SurfaceHolder传递给Camera,这样Camera就会自动的将捕获到的 视频数据渲染到SurfaceView上面,这也就是Camera预览的效果。当然更多的时候我们需要获取到Camera的实时视频数据来自己进行预处理 并渲染,Camera也提供了这个接口,用法如下:

mCamera.setPreviewCallback(new PreviewCallback(){    @Override    public void onPreviewFrame(byte[] data, Camera camera)     {    });

在这个回调里我们就能够获取到当前帧的数据,我们可以对其进行预处理,比如压缩、加密、特效处理等,不过byte[]这个buffer里面的数据是 YUV格式的,一般是YUV420SP,而Android提供的SurfaceView、GLSurfaceView、TextureView等控件只支 持RGB格式的渲染,因此我们需要一个算法来解码。

先介绍一个YUV转RGB的算法,转换的公式一般如下,也是线性的关系:

R=Y+1.4075*(V-128)
G=Y-0.3455*(U-128) – 0.7169*(V-128)
B=Y+1.779*(U-128)

下面是一段将YUV转成ARGB_8888的jni代码,类似的代码网上很多,将这个代码简单修改一下也能直接用在C中。

jintArray Java_com_spore_jni_ImageUtilEngine_decodeYUV420SP(JNIEnv * env,		jobject thiz, jbyteArray buf, jint width, jint height) {	jbyte * yuv420sp = (*env)->GetByteArrayElements(env, buf, 0); 	int frameSize = width * height;	jint rgb[frameSize]; // 新图像像素值 	int i = 0, j = 0,yp = 0;	int uvp = 0, u = 0, v = 0;	for (j = 0, yp = 0; j < height; j++)	{		uvp = frameSize + (j >> 1) * width;		u = 0;		v = 0;		for (i = 0; i < width; i++, yp++)		{			int y = (0xff & ((int) yuv420sp[yp])) - 16;			if (y < 0)				y = 0;			if ((i & 1) == 0)			{				v = (0xff & yuv420sp[uvp++]) - 128;				u = (0xff & yuv420sp[uvp++]) - 128;			} 			int y1192 = 1192 * y;			int r = (y1192 + 1634 * v);			int g = (y1192 - 833 * v - 400 * u);			int b = (y1192 + 2066 * u); 			if (r < 0) r = 0; else if (r > 262143) r = 262143;			if (g < 0) g = 0; else if (g > 262143) g = 262143;			if (b < 0) b = 0; else if (b > 262143) b = 262143; 			rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);		}	} 	jintArray result = (*env)->NewIntArray(env, frameSize);	(*env)->SetIntArrayRegion(env, result, 0, frameSize, rgb);	(*env)->ReleaseByteArrayElements(env, buf, yuv420sp, 0);	return result;}
JNI代码对应的Java接口如下:
public native int[] decodeYUV420SP(byte[] buf, int width, int height);
从这个接口就很容易理解了,参数buf就是从Camera的onPreviewFrame回调用获取到的YUV格式的视频帧数据,width和 height分别是对应的Bitmap的宽高。返回的结果是一个ARGB_8888格式的颜色数组,将这个数组组装成Bitmap也是十分容易的,代码如 下:
mBitmap = Bitmap.createBitmap(data, width, height, Config.ARGB_8888);

基本上这样就能实现YUV2RGB了,但是这样的实现有一个问题:由于是软解码,所以性能并不理想。如果考虑到一般的视频通话的场景,例如 320*240左右的分辨率的话,那基本能满足实时性的需求,但是对于720P的高清视频则基本无望。当然,对于上面的实现,我们也可以尽我们所能的做一 些优化。

上面的算法实现中,已经没有浮点运算了,并且大多数操作已经使用了移位运算,剩下的可优化部分只有中间的乘法了,我们可以使用查表法来替代。上面的 代码我们简单分析就可以发现,Y、U、V的取值都只有256种情况,而对应的r、g、b跟YUV是线性的关系,其中r跟Y和V相关,g跟Y、V、U相 关,b跟Y和U相关,于是我们可以预先计算出所有可能的情况,比如所有的1634 * v的值保存在一个长度为256的数组中,这样我们只需要根据v值查找相乘的结果即可,可以节省这次的乘法运算。

考虑到RGB和YUV的相关性,我们可以把R和B的所有可能值预先计算并缓存,其长度均是256 * 256的int数组,也就是256KB,为什么不针对G值建表呢?因为G值跟YUV三个分量都有关,需要建256 * 256 *256长的表才行,也就是64M,这在手机设备上是不可行的。

下面是查表优化的代码:

int g_v_table[256],g_u_table[256],y_table[256];int r_yv_table[256][256],b_yu_table[256][256];int inited = 0; void initTable(){	if (inited == 0)	{		inited = 1;		int m = 0,n=0;		for (; m < 256; m++)		{			g_v_table[m] = 833 * (m - 128);			g_u_table[m] = 400 * (m - 128);			y_table[m] = 1192 * (m - 16);		}		int temp = 0;		for (m = 0; m < 256; m++)			for (n = 0; n < 256; n++)			{				temp = 1192 * (m - 16) + 1634 * (n - 128);				if (temp < 0) temp = 0; else if (temp > 262143) temp = 262143;				r_yv_table[m][n] = temp; 				temp = 1192 * (m - 16) + 2066 * (n - 128);				if (temp < 0) temp = 0; else if (temp > 262143) temp = 262143;				b_yu_table[m][n] = temp;			}	}} jintArray Java_com_spore_jni_ImageUtilEngine_decodeYUV420SP(JNIEnv * env,		jobject thiz, jbyteArray buf, jint width, jint height) {	jbyte * yuv420sp = (*env)->GetByteArrayElements(env, buf, 0); 	int frameSize = width * height;	jint rgb[frameSize]; // 新图像像素值 	initTable(); 	int i = 0, j = 0,yp = 0;	int uvp = 0, u = 0, v = 0;	for (j = 0, yp = 0; j < height; j++)	{		uvp = frameSize + (j >> 1) * width;		u = 0;		v = 0;		for (i = 0; i < width; i++, yp++)		{			int y = (0xff & ((int) yuv420sp[yp]));			if (y < 0)				y = 0;			if ((i & 1) == 0)			{				v = (0xff & yuv420sp[uvp++]);				u = (0xff & yuv420sp[uvp++]);			} 			int y1192 = y_table[y];			int r = r_yv_table[y][v];			int g = (y1192 - g_v_table[v] - g_u_table[u]);			int b = b_yu_table[y][u]; 			if (g < 0) g = 0; else if (g > 262143) g = 262143; 			rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);		}	} 	jintArray result = (*env)->NewIntArray(env, frameSize);	(*env)->SetIntArrayRegion(env, result, 0, frameSize, rgb);	(*env)->ReleaseByteArrayElements(env, buf, yuv420sp, 0);	return result;}

当然,还有其他的一些细节可以优化一下,比如转化结果的数组,可以预先在Java层分配,将数组的指针传递给JNI,这样可以省去数组在Java和C之间的传递时间,因为720P的图片是很大的,所以这个成本值得去优化。

下面是效果结果:

左边是一个SurfaceView用于Camera的预览,右侧是GLSurfaceView,将转码后的Bitmap渲染出来,由于截屏软件的问题,左侧Camera预览区域变成黑的了。

这样转码的效率如何呢?根据我在Nexus One上的测试结果,720P的图像,也就是1280 * 720的分辨率,转码并渲染的速度大概是8帧。
另外介绍一个看起来速度应该更快的查表转码的算法:。不过这里没有对参数进行说明,所以我调了好久发现转码之后的Bitmap始终很奇怪,大家可以去研究一下,如果调通了请告知一下多谢。
完整的,请点击此处

转载于:https://my.oschina.net/jerikc/blog/113011

你可能感兴趣的文章
学jstl,看这一篇就够了
查看>>
Webpack之tapable深入学习(一)--Sync*Hook
查看>>
Redis 环境配置,缓存必备
查看>>
设计模式 系列记忆之 六大设计原则
查看>>
写给即将面试的你
查看>>
Android NDK开发之JNI基础
查看>>
Java程序员有话说 大专生毕业 6 年月薪 3W+:不从众也不普通
查看>>
D2 日报 2019年5月29日
查看>>
剑指Offer(java答案)(11-20)
查看>>
<HTTP权威指南>记录 ---- Web缓存
查看>>
springmvc+mybatis+dubbo+zookeeper
查看>>
漫话:如何给女朋友解释什么是乐观锁与悲观锁
查看>>
【许晓笛】49行代码就能发币?而且EOS连例子都给你了
查看>>
MySQL 索引机制背后的隐藏之道
查看>>
基于 Vue.js 的支持本地化储存记事本 SPA
查看>>
016-JDK8+可用的反编译工具(JD_GUI+Procyon)
查看>>
ARTS - Week 2
查看>>
区块链数字资产交易系统的种类,源中瑞小六说
查看>>
JavaScript中的浅拷贝与深拷贝
查看>>
Spring Boot RabbitMQ系列之基础概念
查看>>