Android OpenGL基础相机预览及滤镜
Posted 小陈乱敲代码
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android OpenGL基础相机预览及滤镜相关的知识,希望对你有一定的参考价值。
一、相机简介
在android OpenGL基础绘制Bitmap纹理一文中,我们简单介绍了如何绘制如何把一张图片贴到四边形上。本文介绍如何用GLSurfaceView来实现预览相机。与单张图片纹理不同的地方在于,相机是一个内容不断变化的纹理。
首先,先简单介绍相机的几个常用方法:
1.1 声明相机权限
如果APP需要使用相机,则需要在manifest.xml中声明:
<uses-permission android:name="android.permission.CAMERA" />
1.2 检查相机权限
Android权限类型有两种:
- 安装时权限:例如普通权限或签名权限,系统会在安装您的应用时自动为其授予相应权限。
- 运行时权限:在 Android 6.0(API 级别 23)或更高版本的设备上,必须请求权限。
检查当前是否获得相机权限的方法如下:
// 方法1:在Activity中调用Activity提供的API
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
this.checkSelfPermission(Manifest.permission.CAMERA)
// 方法2:androidx提供的API
ContextCompat.checkSelfPermission(this,
Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
1.3 请求相机权限
请求相机权限的方法如下:
// 方法1::在Activity中调用Activity提供的API
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
val permissions = arrayOf(Manifest.permission.CAMERA)
this.requestPermissions(permissions, PERMISSION_REQUEST_CODE)
// 方法2:androidx提供的API
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA),1)
1.4 常用方法
下面简单列举Camera几个常用的方法:
public class Camera1Utils
private Camera camera;
/**
* 打开相机
**/
public void openCamera()
// 打开相机
camera = Camera.open();
Camera.Parameters parameters = camera.getParameters();
// 自动对焦
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
camera.setParameters(parameters);
// 开始相机预览
camera.startPreview();
public void stopCamera()
if (camera != null)
camera.stopPreview();
camera.release();
/**
* 用SurfaceHolder承接相机预览数据
**/
public void setPreviewDisplay(SurfaceHolder surfaceHolder)
try
// 把相机预览数据传给SurfaceHolder
camera.setPreviewDisplay(surfaceHolder);
catch (Exception e)
e.printStackTrace();
/**
* 用SurfaceTexture承接相机预览数据
**/
public void setPreviewTexture(SurfaceTexture surfaceTexture)
try
// 把相机预览数据传给SurfaceTexture
camera.setPreviewTexture(surfaceTexture);
catch (Exception e)
e.printStackTrace();
二、相机预览
为了可以在屏幕上看到相机预览画面,在打开相机后,需要把相机数据传递给一个View进行显示。常用的方式是用SurfaceView来显示相机实时画面。
2.1 SurfaceView
在SurfaceView创建成功后,可以将相机数据传递给SurfaceView的SurfaceHolder,来在SurfaceView中显示相机画面。
class CameraPreview(context: Context,=private val mCamera: Camera
) : SurfaceView(context), SurfaceHolder.Callback
override fun surfaceCreated(holder: SurfaceHolder)
// Surface创建成功后,把相机数据传递给SurfaceView的SurfaceHolder
mCamera.apply
try
setPreviewDisplay(holder)
startPreview()
catch (e: IOException)
Log.d(TAG, "Error setting camera preview: $e.message")
2.2 GLSurfaceView
GLSurfaceView类提供了帮助管理 EGL 上下文、在线程间通信以及与 activity 生命周期交互的辅助程序类。GLSurfaceView本身无法和相机数据直接关联起来,需要通过SurfaceTexture。在打开相机后,可以把相机数据传递给SurfaceTexture,在SurfaceTexture中将相机纹理绘制到GLSurfaceView中。本文主要介绍这种方式,在第三节详细介绍。
三、OpenGL实现相机预览
用OpenGL实现相机预览,下面分为SurfaceTexture、GLSurfaceView、GLSurfaceView.Render、绘制相机纹理几部分来介绍。
3.1 SurfaceTexture
SurfaceTexture用于在相机启动后,承接相机预览数据,常用方法如下:
public class SurfaceTexture
/**
* 注册OnFrameAvailableListener回调;
* 当SurfaceTexture有新的数据可用时会回调OnFrameAvailableListener的onFrameAvailable方法
*/
public void setOnFrameAvailableListener(SurfaceTexture.OnFrameAvailableListener listener)
setOnFrameAvailableListener(listener, null);
/**
* Update the texture image to the most recent frame from the image stream.
* 把SurfaceTexture中的数据更新为最新一次的数据
*/
public void updateTexImage()
nativeUpdateTexImage();
3.2 GLSurfaceView
在GLSurfaceView中,在SurfaceTexture中有新的数据(onFrameAvailable)时,调用自身的requestRender(),即可触发自身的重新渲染(onDrawFrame()方法):
class MyGLSurfaceView(context: Context?, attrs: AttributeSet?) : GLSurfaceView(context, attrs),
SurfaceTexture.OnFrameAvailableListener
private val renderer: MyGLRenderer
init
setEGLContextClientVersion(2)
renderer = MyGLRenderer(this)
setRenderer(renderer)
renderMode = RENDERMODE_WHEN_DIRTY
override fun onFrameAvailable(surfaceTexture: SurfaceTexture?)
// renderMode设置为RENDERMODE_WHEN_DIRTY;
// 在相机把新的数据传给SurfaceTexture时会回调onFrameAvailable()方法
// 在onFrameAvailable()方法里调用requestRender()触发渲染更新Surface
requestRender()
3.3 GLSurfaceView.Renderer
GLSurfaceView.Renderer的工作比较简单,在onSurfaceCreated后启动相机,并把相机预览数据传递给SurfaceTexture,将SurfaceTexture的listener设置为GLSurfaceView,绘制的主要工作在我们自定义的CameraDrawer类中:
class MyGLRenderer(private val frameAvailableListener: SurfaceTexture.OnFrameAvailableListener)
: GLSurfaceView.Renderer
private lateinit var cameraDrawer: CameraDrawer
private val cameraManager: Camera1Utils = Camera1Utils()
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?)
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f)
cameraDrawer = CameraDrawer()
cameraDrawer.getSurfaceTexture().setOnFrameAvailableListener(frameAvailableListener)
cameraManager.openCamera()
cameraManager.setPreviewTexture(cameraDrawer.getSurfaceTexture())
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int)
GLES20.glViewport(0, 0, width, height)
override fun onDrawFrame(gl: GL10?)
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
cameraDrawer.getSurfaceTexture().updateTexImage()
cameraDrawer.draw()
3.4 绘制相机纹理
下面看我们自定义的CameraDrawer类是如何完成相机预览画面的绘制的。
3.4.1 创建纹理
创建纹理仍然使用Android OpenGL基础绘制Bitmap纹理中,1.2小节提供的OpenGLUtils工具类,不同的是相机的纹理类型是GLES11Ext.GL_TEXTURE_EXTERNAL_OES:
val texture = OpenGLUtils.createTextures(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 1,
GLES20.GL_NEAREST, GLES20.GL_LINEAR,
GLES20.GL_CLAMP_TO_EDGE, GLES20.GL_CLAMP_TO_EDGE
)
3.4.2 相机纹理GLSL
相机纹理的GLSL代码与Android OpenGL基础绘制Bitmap纹理中2D图片纹理类似,不同点在于相机纹理需要声明uniform samplerExternalOES s_texture:
/**
* 顶点着色器代码
*/
private val vertexShaderCode = """
attribute vec4 vPosition;
attribute vec2 inputTextureCoordinate;
varying vec2 textureCoordinate;
void main()
gl_Position = vPosition;
textureCoordinate = inputTextureCoordinate;
"""
/**
* 片段着色器代码
*/
private val fragmentShaderCode = """
#extension GL_OES_EGL_image_external : require
precision mediump float;varying vec2 textureCoordinate;
uniform samplerExternalOES s_texture;
void main() gl_FragColor = texture2D( s_texture, textureCoordinate );
"""
3.4.3 相机纹理顶点坐标
OpenGL预览相机画面其实就是将相机纹理绘制到一个四边形上,与Android OpenGL基础绘制Bitmap纹理中绘制2D图片纹理不同的地方在于,相机数据的起点是手机横屏时的左上角为(0,0)点,所以如果想要让相机的画面符合我们想要的竖屏预览,需要将顶点对应的纹理坐标设置为:
// 四边形顶点的坐标
private var squareCoords = floatArrayOf(
-1f, 1f, 0.0f, // top left
-1f, -1f, 0.0f, // bottom left
1f, -1f, 0.0f, // bottom right
1f, 1f, 0.0f // top right
)
// 顶点所对应的纹理坐标
private var textureVertices = floatArrayOf(
0f, 1f, // top left
1f, 1f, // bottom left
1f, 0f, // bottom right
0f, 0f // top right
)
3.4.4 总结
在修改了以上代码后,实际的绘制方法draw()与Android OpenGL基础绘制Bitmap纹理中绘制2D纹理完全一致。完整代码如下:
class CameraDrawer
/**
* 顶点着色器代码
*/
private val vertexShaderCode = """
attribute vec4 vPosition;
attribute vec2 inputTextureCoordinate;
varying vec2 textureCoordinate;
void main()
gl_Position = vPosition;
textureCoordinate = inputTextureCoordinate;
"""
/**
* 片段着色器代码
*/
private val fragmentShaderCode = """
#extension GL_OES_EGL_image_external : require
precision mediump float;varying vec2 textureCoordinate;
uniform samplerExternalOES s_texture;
void main() gl_FragColor = texture2D( s_texture, textureCoordinate );
"""
/**
* 着色器程序ID引用
*/
private var mProgram = 0
/**
* 相机预览SurfaceTexture
*/
private var cameraSurfaceTexture: SurfaceTexture
// 四边形顶点的坐标
private var squareCoords = floatArrayOf(
-1f, 1f, 0.0f, // top left
-1f, -1f, 0.0f, // bottom left
1f, -1f, 0.0f, // bottom right
1f, 1f, 0.0f // top right
)
// 顶点所对应的纹理坐标
private var textureVertices = floatArrayOf(
0f, 1f, // top left
1f, 1f, // bottom left
1f, 0f, // bottom right
0f, 0f // top right
)
// 四个顶点的缓冲数组
private val vertexBuffer: FloatBuffer =
ByteBuffer.allocateDirect(squareCoords.size * 4).order(ByteOrder.nativeOrder())
.asFloatBuffer().apply
put(squareCoords)
position(0)
// 四个顶点的绘制顺序数组
private val drawOrder = shortArrayOf(0, 1, 2, 0, 2, 3)
// 四个顶点绘制顺序数组的缓冲数组
private val drawListBuffer: ShortBuffer =
ByteBuffer.allocateDirect(drawOrder.size * 2).order(ByteOrder.nativeOrder())
.asShortBuffer().apply
put(drawOrder)
position(0)
// 四个顶点的纹理坐标缓冲数组
private val textureVerticesBuffer: FloatBuffer =
ByteBuffer.allocateDirect(textureVertices.size * 4).order(ByteOrder.nativeOrder())
.asFloatBuffer().apply
put(textureVertices)
position(0)
private var textureID = 0
// 每个顶点的坐标数
private val COORDS_PER_VERTEX = 3
// 每个纹理顶点的坐标数
private val COORDS_PER_TEXTURE_VERTEX = 2
private val vertexStride: Int = COORDS_PER_VERTEX * 4
private val textVertexStride: Int = COORDS_PER_TEXTURE_VERTEX * 4
init
// 编译顶点着色器和片段着色器
val vertexShader: Int = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)
val fragmentShader: Int = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode)
// glCreateProgram函数创建一个着色器程序,并返回新创建程序对象的ID引用
mProgram = GLES20.glCreateProgram().also
// 把顶点着色器添加到程序对象
GLES20.glAttachShader(it, vertexShader)
// 把片段着色器添加到程序对象
GLES20.glAttachShader(it, fragmentShader)
// 连接并创建一个可执行的OpenGL ES程序对象
GLES20.glLinkProgram(it)
val texture = OpenGLUtils.createTextures(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 1,
GLES20.GL_NEAREST, GLES20.GL_LINEAR,
GLES20.GL_CLAMP_TO_EDGE, GLES20.GL_CLAMP_TO_EDGE
)
textureID = texture[0]
cameraSurfaceTexture = SurfaceTexture(textureID)
fun getSurfaceTexture(): SurfaceTexture
return cameraSurfaceTexture
fun draw()
// 激活着色器程序 Add program to OpenGL ES environment
GLES20.glUseProgram(mProgram)
// 获取顶点着色器中的vPosition变量(因为之前已经编译过着色器代码,所以可以从着色器程序中获取);用唯一ID表示
val position = GLES20.glGetAttribLocation(mProgram, "vPosition")
// 允许操作顶点对象position
GLES20.glEnableVertexAttribArray(position)
// 将顶点数据传递给position指向的vPosition变量;将顶点属性与顶点缓冲对象关联
GLES20.glVertexAttribPointer(
position, COORDS_PER_VERTEX, GLES20.GL_FLOAT,
false, vertexStride, vertexBuffer
)
// 激活textureID对应的纹理单元
GLES20.glActiveTexture(textureID)
// 绑定纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureID)
// 获取顶点着色器中的inputTextureCoordinate变量(纹理坐标);用唯一ID表示
val textureCoordinate = GLES20.glGetAttribLocation(mProgram, "inputTextureCoordinate")
// 允许操作纹理坐标inputTextureCoordinate变量
GLES20.glEnableVertexAttribArray(textureCoordinate)
// 将纹理坐标数据传递给inputTextureCoordinate变量
GLES20.glVertexAttribPointer(
textureCoordinate, COORDS_PER_TEXTURE_VERTEX, GLES20.GL_FLOAT,
false, textVertexStride, textureVerticesBuffer
)
// 按drawListBuffer中指定的顺序绘制四边形
GLES20.glDrawElements(
GLES20.GL_TRIANGLE_STRIP, drawOrder.size,
GLES20.GL_UNSIGNED_SHORT, drawListBuffer
)
// 操作完后,取消允许操作顶点对象position
GLES20.glDisableVertexAttribArray(position)
GLES20.glDisableVertexAttribArray(textureCoordinate)
private fun loadShader(type: Int, shaderCode: String): Int
// glCreateShader函数创建一个顶点着色器或者片段着色器,并返回新创建着色器的ID引用
val shader = GLES20.glCreateShader(type)
// 把着色器和代码关联,然后编译着色器
GLES20.glShaderSource(shader, shaderCode)
GLES20.glCompileShader(shader)
return shader
四、相机滤镜
与Android OpenGL基础图片后处理中介绍的图片后处理类似,在相机的预览中,可以通过修改片段着色的代码实现相机滤镜。
4.1 灰度滤镜
/**
* 片段着色器代码
*/
private val fragmentShaderCode = """
#extension GL_OES_EGL_image_external : require
precision mediump float;varying vec2 textureCoordinate;
uniform samplerExternalOES s_texture;
void main()
gl_FragColor = texture2D( s_texture, textureCoordinate );
float average = 0.2126 * gl_FragColor.r + 0.7152 * gl_FragColor.g + 0.0722 * gl_FragColor.b;
gl_FragColor = vec4(average, average, average, 1.0);
"""
4.2 边缘检测滤镜
/**
* 片段着色器代码
*/
private val fragmentShaderCode = """
#extension GL_OES_EGL_image_external : require
precision mediump float;varying vec2 textureCoordinate;
uniform samplerExternalOES s_texture;
const float offset = 1.0f / 300.0f;
void main()
vec2 offsets[9];
offsets[0] = vec2(-offset, offset); // 左上
offsets[1] = vec2( 0.0f, offset); // 正上
offsets[2] = vec2( offset, offset); // 右上
offsets[3] = vec2(-offset, 0.0f); // 左
offsets[4] = vec2( 0.0f, 0.0f); // 中
offsets[5] = vec2( offset, 0.0f); // 右
offsets[6] = vec2(-offset, -offset); // 左下
offsets[7] = vec2( 0.0f, -offset); // 正下
offsets[8] = vec2( offset, -offset); // 右下
// 核函数
float kernel[9];
kernel[0] = 1.0f;
kernel[1] = 1.0f;
kernel[2] = 1.0f;
kernel[3] = 1.0f;
kernel[4] = -8.0f;
kernel[5] = 1.0f;
kernel[6] = 1.0f;
kernel[7] = 1.0f;
kernel[8] = 1.0f;
// 计算采样值
vec3 sampleTex[9];
for(int i = 0; i < 9; i++)
sampleTex[i] = vec3(texture2D(s_texture, textureCoordinate.xy + offsets[i]));
r
vec3 col = vec3(0.0);
for(int i = 0; i < 9; i++)
col += sampleTex[i] * kernel[i];
gl_FragColor = vec4(col, 1.0);
"""
其他类型的滤镜也是类似地修改片段着色器代码即可,下面给出了相机原画面以及几种其他滤镜的效果:
最后
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长。而不成体系的学习效果低效漫长且无助。时间久了,付出巨大的时间成本和努力,没有看到应有的效果,会气馁是再正常不过的。
所以学习一定要找到最适合自己的方式,有一个思路方法,不然不止浪费时间,更可能把未来发展都一起耽误了。
如果你是卡在缺少学习资源的瓶颈上,那么刚刚好我能帮到你。以上知识笔记全部免费分享,**如有需要获取知识笔记的朋友,可以点击下方二维码费领取。
以上是关于Android OpenGL基础相机预览及滤镜的主要内容,如果未能解决你的问题,请参考以下文章
Android Camera API/Camera2 API 相机预览及滤镜贴纸等处理
Android Camera API/Camera2 API 相机预览及滤镜贴纸等处理
android 采集摄像头预览帧,使用opencv和MediaCodec直接录制水印滤镜视频
android 采集摄像头预览帧,使用opencv和MediaCodec直接录制水印滤镜视频