Android Camera 使用 OpenGL ES 2.0 和 TextureView 对预览进行实时二次处理(黑白滤镜)
共 20282字,需浏览 41分钟
·
2023-09-19 05:57
本文使用TextureView作为布局界面,并且仍将使用SurfaceTexture来获取预览图像,需要注意的是TextureView内置了一个SurfaceTexture,也就是说本文需要两个SurfaceTexture。
TextureView内置的SurfaceTexture用来配合EGL来将图像显示到屏幕上,自定义的SurfaceTexture用来接收Camera的预览图像来做二次处理(黑白滤镜)。这点可能比较难理解,后面通过代码进行详细讲解。
TextureView是没有配置 OpenGL ES 环境和 Renderer 线程,所以需要我们自己来初始化EGL和创建Renderer子线程。下面开始讲解。
1, 添加TextureView作为布局界面、创建SurfaceTexture以及开启相机
伪代码如下,这个不细讲了,可以参考我之前的博客 Android初始化OpenGL ES,并且分析Renderer子线程原理
//实例化一个TextureView
mTextureView = new TextureView(this);
//设置SurfaceTexture监听函数
mTextureView.setSurfaceTextureListener(mTextureListener);
//在屏幕上显示TextureView
setContentView(mTextureView);
public TextureView.SurfaceTextureListener mTextureListener = new TextureView.SurfaceTextureListener() {
//当SurfaceTexture可用时回调此方法,此时暂时用不到回调生成的surface
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
//获取外部纹理ID
mOESTextureId = createOESTextureObject();
//获取自定义的SurfaceTexture
mOESSurfaceTexture = initOESTexture();
//开启相机
mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
mCamera = Camera.open(mCameraId)
//设置camera参数
......
//设置预览输出
mCamera.setPreviewTexture(mOESSurfaceTexture);
//开启预览
mCamera.startPreview();
}
......
}
//创建外部纹理
public static int createOESTextureObject() {
int[] tex = new int[1];
GLES20.glGenTextures(1, tex, 0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, tex[0]);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
return tex[0];
}
//根据外部纹理ID创建SurfaceTexture
public SurfaceTexture initOESTexture() {
mOESSurfaceTexture = new SurfaceTexture(mOESTextureId);
mOESSurfaceTexture.setOnFrameAvailableListener(this);
return mOESSurfaceTexture;
}
//监听SurfaceTexture的每帧数据
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
if (mHandler != null) {
//因为没有GLSurfaceView的Renderer线程,所以我们需要自己通知子线程进行渲染图像
mHandler.sendEmptyMessage(MSG_RENDER);
}
}
2, 自定义Renderer线程
public class CameraV1GLRenderer implements SurfaceTexture.OnFrameAvailableListener {
//此init方法应该在Activity的OnCreate方法中调用
public void init(TextureView textureView, int oesTextureId) {
mTextureView = textureView;
mOESTextureId = oesTextureId;
//开启子线程
mHandlerThread = new HandlerThread("Renderer Thread");
mHandlerThread.start();
//主线程与子线程需要通过Handler进行通信
mHandler = new Handler(mHandlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INIT:
initEGL();
return;
case MSG_RENDER:
drawFrame();
return;
case MSG_DEINIT:
return;
default:
return;
}
}
};
//初始化EGL环境
mHandler.sendEmptyMessage(MSG_INIT);
}
3, 初始化EGL环境
EGL是OpenGL ES与设备的系统屏幕进行通信的桥梁,因为TextureView是没有任何OpenGL ES相关的环境的,而上篇文章讲的GLSurfaceView是封装好了OpenGL ES相关的环境,包括EGL环境。
当OpenGL ES需要绘制图像时,会找到EGL的EGLSurface,通过此对象请求SurfaceFlinger返回系统屏幕的图形访问接口,这个接口也就是屏幕的帧缓冲区,这样OpenGL就可以将图像渲染到屏幕的帧缓冲区中。
EGL主要需要四个对象,一个EGLDisplay描述EGL显示屏,一个EGLConfig描述帧缓冲区配置参数,一个EGLContext描述EGL上下文环境,一个EGLSurface描述EGL绘图表面。整个EGL初始化见下述代码。
//定义所需变量
private EGL10 mEgl = null;
private EGLDisplay mEGLDisplay = EGL10.EGL_NO_DISPLAY;
private EGLContext mEGLContext = EGL10.EGL_NO_CONTEXT;
private EGLConfig[] mEGLConfig = new EGLConfig[1];
private EGLSurface mEglSurface;
private void initEGL() {
//获取系统的EGL对象
mEgl = (EGL10) EGLContext.getEGL();
//获取显示设备
mEGLDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) {
throw new RuntimeException("eglGetDisplay failed! " + mEgl.eglGetError());
}
//version中存放当前的EGL版本号,版本号即为version[0].version[1],如1.0
int[] version = new int[2];
//初始化EGL
if (!mEgl.eglInitialize(mEGLDisplay, version)) {
throw new RuntimeException("eglInitialize failed! " + mEgl.eglGetError());
}
//构造需要的配置列表
int[] attributes = {
//颜色缓冲区所有颜色分量的位数
EGL10.EGL_BUFFER_SIZE, 32,
//颜色缓冲区R、G、B、A分量的位数
EGL10.EGL_RED_SIZE, 8,
EGL10.EGL_GREEN_SIZE,8,
EGL10.EGL_BLUE_SIZE, 8,
EGL10.EGL_ALPHA_SIZE, 8,
EGL10.EGL_NONE
};
int[] configsNum = new int[1];
//EGL根据attributes选择最匹配的配置
if (!mEgl.eglChooseConfig(mEGLDisplay, attributes, mEGLConfig, 1, configsNum)) {
throw new RuntimeException("eglChooseConfig failed! " + mEgl.eglGetError());
}
//如本文开始所讲的,获取TextureView内置的SurfaceTexture作为EGL的绘图表面,也就是跟系统屏幕打交道
SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
if (surfaceTexture == null)
return;
//根据SurfaceTexture创建EGL绘图表面
mEglSurface = mEgl.eglCreateWindowSurface(mEGLDisplay, mEGLConfig[0], surfaceTexture, null);
//指定哪个版本的OpenGL ES上下文,本文为OpenGL ES 2.0
int[] contextAttribs = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
EGL10.EGL_NONE
};
//创建上下文,EGL10.EGL_NO_CONTEXT表示不和别的上下文共享资源
mEGLContext = mEgl.eglCreateContext(mEGLDisplay, mEGLConfig[0], EGL10.EGL_NO_CONTEXT, contextAttribs);
if (mEGLDisplay == EGL10.EGL_NO_DISPLAY || mEGLContext == EGL10.EGL_NO_CONTEXT){
throw new RuntimeException("eglCreateContext fail failed! " + mEgl.eglGetError());
}
//指定mEGLContext为当前系统的EGL上下文,你可能发现了使用两个mEglSurface,第一个表示绘图表面,第二个表示读取表面
if (!mEgl.eglMakeCurrent(mEGLDisplay,mEglSurface, mEglSurface, mEGLContext)) {
throw new RuntimeException("eglMakeCurrent failed! " + mEgl.eglGetError());
}
}
4. OpenGL ES代码编写
有了EGL环境,现在可开始OpenGL ES部分的代码编写,这一块可以参考上篇文章,内容基本一样,只不过通知渲染是由SurfaceTexture来做的。
//监听SurfaceTexture的每帧数据
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
if (mHandler != null) {
//因为没有GLSurfaceView的Renderer线程,所以我们需要自己通知自定义的Renderder子线程进行渲染图像
mHandler.sendEmptyMessage(MSG_RENDER);
}
}
//通过handlerMessage()方法调用此方法
private void drawFrame() {
//更新预览图像
if (mOESSurfaceTexture != null) {
mOESSurfaceTexture.updateTexImage();
mOESSurfaceTexture.getTransformMatrix(transformMatrix);
}
//指定mEGLContext为当前系统的EGL上下文,这里比较消耗性能
mEgl.eglMakeCurrent(mEGLDisplay, mEglSurface, mEglSurface, mEGLContext);
//设置视口
GLES20.glViewport(0,0,mTextureView.getWidth(),mTextureView.getHeight());
//清楚屏幕
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glClearColor(0f, 0f, 0f, 1f);
//绘制图像
drawTexture(transformMatrix);
//交换缓冲区,Android使用双缓冲机制,我们绘制的都是在后台缓冲区,通过交换将后台缓冲区变为前台显示区,下一帧的绘制仍然在后台缓冲区
mEgl.eglSwapBuffers(mEGLDisplay, mEglSurface);
}
//绘制图像,参考第一篇文章,有详细讲解
public void drawTexture(float[] transformMatrix) {
aPositionLocation = glGetAttribLocation(mShaderProgram, FilterEngine.POSITION_ATTRIBUTE);
aTextureCoordLocation = glGetAttribLocation(mShaderProgram, FilterEngine.TEXTURE_COORD_ATTRIBUTE);
uTextureMatrixLocation = glGetUniformLocation(mShaderProgram, FilterEngine.TEXTURE_MATRIX_UNIFORM);
uTextureSamplerLocation = glGetUniformLocation(mShaderProgram, FilterEngine.TEXTURE_SAMPLER_UNIFORM);
glActiveTexture(GLES20.GL_TEXTURE0);
glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mOESTextureId);
glUniform1i(uTextureSamplerLocation, 0);
glUniformMatrix4fv(uTextureMatrixLocation, 1, false, transformMatrix, 0);
if (mBuffer != null) {
mBuffer.position(0);
glEnableVertexAttribArray(aPositionLocation);
glVertexAttribPointer(aPositionLocation, 2, GL_FLOAT, false, 16, mBuffer);
mBuffer.position(2);
glEnableVertexAttribArray(aTextureCoordLocation);
glVertexAttribPointer(aTextureCoordLocation, 2, GL_FLOAT, false, 16, mBuffer);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
}
至此运行此程序Camera预览就会显示为黑白滤镜,如下图所示。
总结一下:首先创建TextureView和开启相机,之后创建一个外部纹理,根据此纹理ID创建一个SurfaceTexture,Camera将预览数据输出至此SurfaceTexture上。
创建EGL环境来与系统屏幕进行通信,EGL使用TextureView内置的SurfaceTexture来表示绘图表面,之后OpenGL绘制的图像也就是渲染到此EglSurface上,通过eglSwapBuffers来显示OpenGL所绘制的图像。
代码地址(顺手给个Star啊):https://github.com/lb377463323/GraphicsTestBed
作者:lb377463323
出处:http://blog.csdn.net/lb377463323
原文链接:http://blog.csdn.net/lb377463323/article/details/77096652
-- END --
进技术交流群,扫码添加我的微信:Byte-Flow
获取相关资料和源码
推荐:
全网最全的 Android 音视频和 OpenGL ES 干货,都在这了