OpenGLES:glReadPixels()获取相机GLSurfaceView预览数据并保存

Android现行的Camera API2机制可以通过onImageAvailable(ImageReader reader)回调从底层获取到Jpeg、YuvRaw三种格式的Image,然后通过保存Image实现拍照功能,但是却并没有Api能直接在上层直接拿到预览Surface上的实显数据。

Android Camera预览的实现是上层下发SurfaceCameraHAL,由CameraHAL也就是android.hardware.camera.provider@2.4-service进程往Surface对应的Buffer中填充预览数据,然后再copySurfaceFling中由OpenGL进行渲染显示。

实际相机开发中,不仅仅只是要实现预览,还经常需要拿到预览数据做一些特效处理,那么问题来了,怎么在相机App获取到预览Surface实显数据呢?

这跟上层Camera App用于显示SurfaceView控件有关:

  • 如果上层使用的是GLSurfaceView,可以直接通过OpenGLESglReadPixels()获取到copy到显存中的预览数据
  • 如果上层使用的不是GLSurfaceView,可以通过自己搭建EGL环境,然后在EGL环境中调用OpenGLESglReadPixels()获取到预览数据。

GLSurfaceView其实就是Android封装好的EGL+SufaceView控件,Android的所有渲染最终都是通过OpenGL来实现的,所以万变不离其宗,本质上上层Camera App都只能通过OpenGLESglReadPixels()实现预览数据的获取。

一个SurfaceAndroid EGL中对应一个FrameBuffer,学习过OpenGL的应该都知道,一个FrameBuffer会有多个附着(attachment),其中必须且只能有一个ColorBuffer附着,有一个或多个StencilBufferDepthBuffer附着

glReadPixels()仅限于读取ColorBuffer,无法读取DepthBufferStencilBuffer,它可以将图像内容从显存读取到内存中,将ColorBuffer中的像素值保存到预分配的内存缓冲区。

前面关于OpenGLES的博文中,有两篇是使用OpenGLES实现相机的相关功能,一篇是《OpenGLES:GLSurfaceView实现Android Camera预览》,一篇是《OpenGLES:相机实时滤镜四宫格、九宫格》,今天就在这两篇博文基础上实现相机预览数据的获取和保存。

相机实现部分在此不做过多讲解,有兴趣的可以参看前面两篇博文,有详细的讲解
本文主要展示glReadPixels()对相机预览数据获取的实现

代码实现其实很简单
GLSurfaceView.Renderer实现类的onDrawFrame(GL10 gl)函数中新增如下代码段:

if (shouldTakePic) {
    //预览尺寸
	int w = 1080;int h = 1440;
    //预览数据保存成照片的目录
	String savePath = Environment.getExternalStorageDirectory().getPath() + "/DCIM/MyCamera/";

	int[] iat = new int[w * h];
	IntBuffer ib = IntBuffer.allocate(w * h);
    //(0,580)距离屏幕左下角的距离,与glViewport(0, 580,...)保持一致
	glReadPixels(0, 580, w, h, GL_RGBA, GL_UNSIGNED_BYTE, ib);

	int[] ia = ib.array();
	//glReadPixels 读取的内容是上下翻转的,要处理一下
	for (int i = 0; i < h; i++) {
		for (int j = 0; j < w; j++) {
			iat[(h - i - 1) * w + j] = ia[i * w + j];
		}
	}

	Bitmap inBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
	inBitmap.copyPixelsFromBuffer(IntBuffer.wrap(iat));

	ByteArrayOutputStream bos = new ByteArrayOutputStream();
	inBitmap.compress(Bitmap.CompressFormat.JPEG, 90, bos);
	byte[] bitmapData = bos.toByteArray();

	File tempDir = new File(savePath);
	tempDir.mkdirs();
	String fileName = "temp_" + System.currentTimeMillis() + ".jpeg";
	File imgFile = new File(savePath, fileName);

	try {
		FileOutputStream output = new FileOutputStream(imgFile);
		output.write(bitmapData);
		output.flush();
		output.close();
		Log.v(TAG, "ImageReader X");
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		inBitmap.recycle();
	}
}

glReadPixels读取的内容上下翻转处理还有另外一种实现,
原理都是一样的,实现起来大同小异

if (shouldTakePic) {
	//预览尺寸
	int w = 1080;
	int h = 1440;
	//预览数据保存成照片的目录
	String savePath = Environment.getExternalStorageDirectory().getPath() + "/DCIM/MyCamera/";
	
	int b[] = new int[(int) (w * h)];
	int bt[] = new int[(int) (w * h)];
	IntBuffer buffer = IntBuffer.wrap(b);
	buffer.position(0);
    //(0,580)距离屏幕左下角的距离,与glViewport(0, 580,...)保持一致
	glReadPixels(0, 580, w, h, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
	for (int i = 0; i < h; i++) {
		for (int j = 0; j < w; j++) {
			int pix = b[i * w + j];
			int pb = (pix >> 16) & 0xff;
			int pr = (pix << 16) & 0x00ff0000;
			int pix1 = (pix & 0xff00ff00) | pr | pb;
			bt[(h - i - 1) * w + j] = pix1;
		}
	}
	Bitmap inBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
	inBitmap.copyPixelsFromBuffer(buffer);
	inBitmap = Bitmap.createBitmap(bt, w, h, Bitmap.Config.ARGB_8888);

	ByteArrayOutputStream bos = new ByteArrayOutputStream();
	inBitmap.compress(Bitmap.CompressFormat.JPEG, 90, bos);
	byte[] bitmapData = bos.toByteArray();
	ByteArrayInputStream fis = new ByteArrayInputStream(bitmapData);

	String tempPicFile = "temp_" + System.currentTimeMillis() + ".jpeg";
	File tempDir = new File(savePath);
	tempDir.mkdirs();
	try {
		File tmpFile = new File(tempDir, tempPicFile);
		FileOutputStream fos = new FileOutputStream(tmpFile);
		byte[] buf = new byte[1024];
		int len;
		while ((len = fis.read(buf)) > 0) {
			fos.write(buf, 0, len);
		}
		fis.close();
		fos.close();
		inBitmap.recycle();
	} catch (FileNotFoundException e) {
		e.printStackTrace();
	} catch (IOException e) {
		e.printStackTrace();
	}
}

验证下效果,抓两张预览照试试:

抓一张普通预览:

抓一张四宫格滤镜预览:

  • 16
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
获取 OpenGL ES 绘制后的图像数据,你可以使用帧缓冲对象(Framebuffer Object,FBO)和像素缓冲对象(Pixel Buffer Object,PBO)的组合来实现。下面是一个获取图像数据的基本步骤: 1. 创建并绑定一个帧缓冲对象: ```cpp GLuint fbo; glGenFramebuffers(1, &fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo); ``` 2. 创建并绑定一个像素缓冲对象: ```cpp GLuint pbo; glGenBuffers(1, &pbo); glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); glBufferData(GL_PIXEL_PACK_BUFFER, width * height * sizeof(GLubyte) * 4, NULL, GL_STREAM_READ); ``` 3. 将帧缓冲对象附加到当前绑定的帧缓冲上: ```cpp glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureId, 0); ``` 这里,`textureId` 是你要获取数据的纹理对象的 ID。 4. 使用 glReadPixels 函数将图像数据读取到像素缓冲对象中: ```cpp glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, 0); ``` 5. 解绑并删除帧缓冲对象和像素缓冲对象: ```cpp glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); glDeleteFramebuffers(1, &fbo); glDeleteBuffers(1, &pbo); ``` 6. 最后,你可以使用 glMapBufferRange 函数来映射像素缓冲对象的数据,并将其复制到另一个内存区域中进行进一步处理: ```cpp GLubyte* pixels = (GLubyte*)glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, width * height * sizeof(GLubyte) * 4, GL_MAP_READ_BIT); // 将像素数据复制到另一个内存区域处理 glUnmapBuffer(GL_PIXEL_PACK_BUFFER); ``` 请注意,在这个过程中,你需要确保 OpenGL ES 上下文是当前的,并且你已经创建了一个有效的纹理对象来作为帧缓冲对象的附件。同时,像素缓冲对象的大小应该与你要获取的图像数据大小相匹配。 希望这些信息能帮助到你!如果还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值