上一篇文章Camera 采集数据通过 textureview 预览,手动对焦、自动对焦 (一)中使用了textureView进行预览,但是如果想做更多的功能,比如说增加水印、滤镜、离屏渲染等等,这使用GLSurfaceView预览更加合适,所以这篇文章是在上一篇的代码的基础上进行修改替换,并且这里不介绍GLSurfaceView和Android GLES的相关知识。(附带源码)
自定义继承自GLSurfaceView的CameraGLSurfaceView
GLSurfaceView 继承自SurfaceView,都有自己独立的绘制线程,不一样的是Surfaceview是独立的线程在画布上绘制,而GLSurfaceView是GLThread绘制在独立的Surface,并且自己默认创建EGL上下文环境,利用了GPU的能力。
1. 初始化
private void init(Context context) {
setEGLContextClientVersion(2);// 设置EGL的版本,一定要设置
mRender = new CameraGLSufaceRender(context, this); // 自定义的渲染器
setRenderer(mRender);
setRenderMode(RENDERMODE_WHEN_DIRTY);// 绘制模式
}
绘制模式有两种:RENDERMODE_WHEN_DIRTY(按需渲染:调用requestRender才会渲染)、RENDERMODE_CONTINUOUSLY(持续渲染)
自定义Render
CameraGLSufaceRender继承自GLAbstractRender,GLAbstractRender也是自己定义的继承自GLSurfaceView.Renderer的抽象类,用来封装一些GLES常用的方法,例如加载链接着色器等等。
1. 生命周期
GLSurfaceView.Renderer有三个生命周期:
void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig);// surface创建完成
void onSurfaceChanged(GL10 gl10, int width, int height);// surface尺寸发生变化
void onDrawFrame(GL10 gl10);// surface的绘制工作(我们关注的主要部分)
2. 结合Render的生命周期做工作流程
(1)加载顶点着色器、片元着色器
@Override
protected String getVertexSource() {
final String source = "attribute vec4 av_Position; " +
"attribute vec2 af_Position; " +
"varying vec2 v_texPo; " +
"void main() { " +
" v_texPo = af_Position; " +
" gl_Position = av_Position; " +
"}";
return source;
}
@Override
protected String getFragmentSource() {
final String source = "#extension GL_OES_EGL_image_external : require /n" +
"precision mediump float; " +
"varying vec2 v_texPo; " +
"uniform samplerExternalOES s_Texture; " +
"void main() { " +
" gl_FragColor = texture2D(s_Texture, v_texPo); " +
"} ";
return source;
}
- 我们使用的外部的camera的外部纹理,所以这里用的不是sampler2D 而是samplerExternalOES
- 记得
#extension GL_OES_EGL_image_external : require
(2)创建program,链接顶点、片元着色器
protected void createProgram() {
mVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, getVertexSource());
mFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, getFragmentSource());
mProgram = GLES20.glCreateProgram();
GLES20.glAttachShader(mProgram, mVertexShader);
GLES20.glAttachShader(mProgram, mFragmentShader);
GLES20.glLinkProgram(mProgram);
int [] status = new int[1];
GLES20.glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
Log.e(TAG, "createProgam: link error");
Log.e(TAG, "createProgam: " + GLES20.glGetProgramInfoLog(mProgram));
GLES20.glDeleteProgram(mProgram);
return;
}
}
(3) 创建顶点坐标、纹理坐标
private float vertexData[] = {
-1f, -1f, 0.0f, // 左下角
1f, -1f, 0.0f, // 右下角
-1f, 1f, 0.0f, // 左上角
1f, 1f, 0.0f, // 右上角
};
// 纹理坐标对应顶点坐标与后置摄像头映射
private float backTextureData[] = {
1f, 1f, // 右上角
1f, 0f, // 右下角
0f, 1f, // 左上角
0f, 0f // 左下角
};
// 纹理坐标对应顶点坐标与前置摄像头映射
private float frontTextureData[] = {
0f, 1f, // 左上角
0f, 0f, // 左下角
1f, 1f, // 右上角
1f, 0f // 右下角
};
// 每次取点的数量
private final int CoordsPerVertexCount = 3;
// 顶点坐标数量
private final int VertexCount = vertexData.length / CoordsPerVertexCount;
// 一次取出的大小
private final int VertexStride = CoordsPerVertexCount * 4;
// 每次取点的数量
private final int CoordsPerTextureCount = 2;
// 一次取出的大小
private final int TextureStride = CoordsPerTextureCount * 4;
将坐标数据转换为FloatBuffer
this.vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(vertexData);
this.vertexBuffer.position(0);
this.backTextureBuffer = ByteBuffer.allocateDirect(backTextureData.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(backTextureData);
this.backTextureBuffer.position(0);
this.frontTextureBuffer = ByteBuffer.allocateDirect(frontTextureData.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(frontTextureData);
this.frontTextureBuffer.position(0);
以上的工作其实已经封装在GLAbstractRender了
(4)创建外部纹理id,后续可以通过id创建SurfaceTexture传给camera,提供传递预览数据进来
// 创建外部纹理
protected int loadExternelTexture() {
int[] texture = new int[1];
GLES20.glGenTextures(1, texture, 0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[0]);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
return texture[0];
}
(5) 在onSurfaceCreated中,创建一系列的准备工作
当mSurfaceTexture发生数据变化,也就是camera数据变化时候,会通过onFrameAvailable回调进来,我们通知CameraGLSurfaceView重新绘制onRequestRender就可以了
public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
createProgram();
onCreate();
}
protected void onCreate() {
mTexture = loadExternelTexture();//创建外部纹理
av_Position = GLES20.glGetAttribLocation(mProgram, "av_Position");
af_Position = GLES20.glGetAttribLocation(mProgram, "af_Position");
s_Texture = GLES20.glGetUniformLocation(mProgram, "s_Texture");
mSurfaceTexture = new SurfaceTexture(mTexture);
mSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
if (mRenderCallback != null) {
mRenderCallback.onRequestRender();
}
}
});
if (mRenderCallback != null) {
mRenderCallback.onCreate(mSurfaceTexture);
}
}
(6)在onDrawFrame执行绘制工作
public void onDrawFrame(GL10 gl10) {
GLES20.glUseProgram(mProgram);
GLES20.glViewport(0, 0, width, height);
onDraw();
}
@Override
protected void onDraw() {
if (mSurfaceTexture != null) {
mSurfaceTexture.updateTexImage();
// SurfaceTexture 的 updateTexImage() 将更新最新的图像转成GL中的纹理
}
GLES20.glEnableVertexAttribArray(av_Position);
GLES20.glEnableVertexAttribArray(af_Position);
// 设置顶点位置值
GLES20.glVertexAttribPointer(av_Position, CoordsPerVertexCount, GLES20.GL_FLOAT, false, VertexStride, vertexBuffer);
// 设置纹理位置值
if (isBackCamera) {
//判断是前置还是后置
GLES20.glVertexAttribPointer(af_Position, CoordsPerTextureCount, GLES20.GL_FLOAT, false, TextureStride, backTextureBuffer);
} else {
GLES20.glVertexAttribPointer(af_Position, CoordsPerTextureCount, GLES20.GL_FLOAT, false, TextureStride, frontTextureBuffer);
}
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTexture);
//GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTexture);
GLES20.glUniform1i(s_Texture, 0);
//绘制 GLES20.GL_TRIANGLE_STRIP:复用坐标
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, VertexCount);
GLES20.glDisableVertexAttribArray(av_Position);
GLES20.glDisableVertexAttribArray(af_Position);
if (mRenderCallback != null) {
mRenderCallback.onDraw();
}
}
前置摄像头和后置摄像头对应的纹理坐标是不一样的,所以需要根据不同摄像设置不同的纹理坐标
使用CameraGLSurfaceView
使用CameraGLSurfaceView,其实跟textureView没有太大区别,只是回调接口变化了
遇到的问题困惑
我在布局中沿用了上一篇文章的点击CameraGLSurfaceView手动对焦,将自定义FocusView移动到手指位置,这时候发现FocusView在GLSurfaceView的上有一些区域是无法显示完全的。
类似其他同学遇到的问题GLSurfaceView 的上层控件显示问题
猜测是否跟GLSurfaceView的显示机制有关,因为GLSurfaceView是通过让上层的界面透明从而显示。
我的解决办法是在布局的四个角落放置四个透明的textview。但还是找不到问题所在,希望知道的大神告诉我。
源码
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/app/6267.html