GLSurfaceView为咱们构建了一个OpenGl环境,若是咱们想经过GLSurfaceView来渲染camera的Preview内容,那么咱们必须掌握一些基础的OpenGl相关知识。如何使用OpenGL es接口绘制图形。重点须要学习下面的知识内容:node
为了可以在GLSurfaceView上绘制内容,我定义了一个CustomRender类,这个类实现了GLSurfaceView.Renderer接口。咱们能够在onDrawFrame方法中使用OPenGl来绘制camera preview内容。一般状况下咱们除了须要绘制camera的preview内容,咱们还须要绘制水印,sticker,filter等内容。因此我在这里把每个绘制内容都抽象成一个node。NodeRender就是用于管理这些内容的。咱们能够根据具体的需求来动态的添加绘制内容。frontBuffer是一个framebuffer的texture,NodeRender会将全部的node内容绘制到这个framebuffer上。android
override fun onDrawFrame(gl: GL10?) { //绘制因此的node到frontBuffer上 val frontBuffer = nodesRender.render() //清除屏幕内容 GLES20.glViewport(0, 0, width, height) GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f) GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT or GLES20.GL_DEPTH_BUFFER_BIT) //初识话显示用的Opengl程序 if (displayProgram == null) { displayProgram = TextureProgram() } frontBuffer?:return //对显示位置进行变换,实现preview内容的缩放、移动操做。 matrixHandler.applyVertexMatrix(OpenGlUtils.BufferUtils.SQUARE_VERTICES, displayPosition) //进行描画 displayProgram!!.draw( frontBuffer.textureId,//描画的texture displayPosition,//描画的位置信息 displayTextureCoordinate,//描画的纹理范围信息 matrixValues//这里没有进行位置变换,因此使用的是单位矩阵 ) }
TextureProgram中的opengl程序是什么样的呢?首先来看下它定义的vertex shader和fragment shader。这两个shader很是简单,惟一须要注意的是位置坐标会通过mvp矩阵变换。咱们这里传递的是单位矩阵,至关于无转换。若是咱们的位置信息是参照view系统的而不是opengl系统定义的范围,那么咱们须要根据具体的viewport大小来设置mvp矩阵。mvp矩阵设置方法。git
""" attribute vec2 vPosition; attribute vec2 vInputTextureCoordinate; uniform mat4 mvpMatrix; varying vec2 vTextureCoordinate; void main(){ gl_Position = mvpMatrix * vec4(vPosition,0.0,1.0); vTextureCoordinate = vInputTextureCoordinate; } """, """ precision mediump float; uniform sampler2D inputTexture; varying vec2 vTextureCoordinate; void main(){ gl_FragColor = texture2D(inputTexture, vTextureCoordinate); } """
matrixHandler是如何实现的缩放和移动处理的呢?这里经过对显示矩形进行缩放和移动来实现的。显示矩形的计算主要经过updateVertexMatrix方法实现的。这个方法的输入参数包括屏幕的宽高和显示texture的宽高。经过输入的参数能够计算出fix center的显示矩形和fill center的显示矩形。咱们能够根据实际需求来选择使用哪一种显示矩形。详细的计算代码请参照下面的代码段。github
protected void updateVertexMatrix(int screenWidth, int screenHeight, int sourceWidth, int sourceHeight) { if (screenWidth <= 0 || screenHeight <= 0 || sourceWidth <= 0 || sourceHeight <= 0) { return; } boolean isLandscape = sourceRotate % 180 != 0; screenRectF = new RectF(0, 0, screenWidth, screenHeight); sourceRectF = new RectF(0, 0, isLandscape ? sourceHeight : sourceWidth, isLandscape ? sourceWidth : sourceHeight); if (displayRectF == null) { displayRectF = new RectF(0, 0, screenWidth, screenHeight); } minimumScaleSourceRectF = new RectF(); maximumScaleSourceRectF = new RectF(); float scaleH = displayRectF.height() / sourceRectF.height(); float scaleW = displayRectF.width() / sourceRectF.width(); minimumRealScale = scaleH < scaleW ? scaleH : scaleW; maximumRealScale = scaleH > scaleW ? scaleH : scaleW; Matrix matrix = new Matrix(); matrix.setTranslate(displayRectF.centerX() - sourceRectF.centerX(), displayRectF.centerY() - sourceRectF.centerY()); matrix.postScale(minimumRealScale, minimumRealScale, displayRectF.centerX(), displayRectF.centerY()); matrix.mapRect(minimumScaleSourceRectF, sourceRectF); matrix.reset(); matrix.setTranslate(displayRectF.centerX() - sourceRectF.centerX(), displayRectF.centerY() - sourceRectF.centerY()); matrix.postScale(maximumRealScale, maximumRealScale, displayRectF.centerX(), displayRectF.centerY()); matrix.mapRect(maximumScaleSourceRectF, sourceRectF); currentScaleRectF = new RectF(minimumScaleSourceRectF); currentScale = minimumRealScale; initRectFlag = true; updateMatrix(); }
显示矩形已经计算好了,下面须要更新vertex的变换矩阵,使显示矩形的位置可以正确的映射到opengl的position进行显示位置的控制。这里经过updateMatrix()方法来更新变换矩阵。而后咱们就能够对vertext坐标应用applyVertexMatrix来控制显示位置。在发生缩放或是移动手势的时候,咱们对currentScaleRectF矩形实施缩放和移动操做,而后在调用updateMatrix()更新变换矩阵。app
private void updateMatrix() { vertexMatrixLock.lock(); try { float scaleX = currentScaleRectF.width() / screenRectF.width(); float scaleY = currentScaleRectF.height() / screenRectF.height(); applyVertexMatrix.reset(); applyVertexMatrix.setScale(scaleX, scaleY); applyVertexMatrix.postTranslate((currentScaleRectF.centerX() - screenRectF.centerX()) * 2 / screenRectF.width(), -(currentScaleRectF.centerY() - screenRectF.centerY()) * 2 / screenRectF.height()); needUpdateVertexMatrix = true; } finally { vertexMatrixLock.unlock(); } }
因为camera须要设置surface来接收录制的内容,因此咱们来看下如何经过textureId来生成surface。这里我定义了一个CombineSurfaceTexture类用于封装surface类型的texture。首先生成一个textureId,而后使用textureId生成一个SurfaceTexture对象,再用SurfaceTexture生成Surface对象。我这里使用GLSurfaceView.RENDERMODE_WHEN_DIRTY方式更新,因此在OnFrameAvailableListener中须要通知GLSurfaceView来更新。在咱们绘制这个textureId的内容时,咱们须要主动调用surfaceTexture.updateTexImage()来刷新到最新的数据。ide
class CombineSurfaceTexture( width: Int, height: Int, orientation: Int, flipX: Boolean = false, flipY: Boolean = false, notify: () -> Unit = {} ) : BasicTexture(width, height, orientation, flipX, flipY) { private val surfaceTexture: SurfaceTexture val surface: Surface init { textureId = OpenGlUtils.createTexture() surfaceTexture = SurfaceTexture(textureId) surfaceTexture.setDefaultBufferSize(width, height) surfaceTexture.setOnFrameAvailableListener { notify.invoke() } surface = Surface(surfaceTexture) } fun update() { if (surface.isValid) { surfaceTexture.updateTexImage() } } override fun release() { super.release() surface.release() surfaceTexture.release() } }
在绘制Surface类型的texture的时候,咱们须要声明输入texture的类型为samplerExternalOES。post
""" attribute vec2 vPosition; attribute vec2 vInputTextureCoordinate; uniform mat4 mvpMatrix; varying vec2 vTextureCoordinate; void main(){ gl_Position = mvpMatrix * vec4(vPosition,0.0,1.0); vTextureCoordinate = vInputTextureCoordinate; } """, """ #extension GL_OES_EGL_image_external : require precision mediump float; uniform samplerExternalOES inputTexture; varying vec2 vTextureCoordinate; void main(){ gl_FragColor = texture2D(inputTexture, vTextureCoordinate); } """
这里没有直接把surface类型的texture绘制到GLSurfaceView上,而是先将它绘制到frameBuffer上而后再绘制到GLSurfaceView。这是由于为后面实现录制、添加filter、添加水印等工做作准备。
先后摄像头切换和更改分辨率的操做是什么样的处理呢?其实从下面的代码能够看到这两种状况下都把preview使用的texture释放了并生成新的texture和surface。因为我这里把绘制的程序封装到node里,因此这里进行了preview node替换。学习
switchCamera.setOnClickListener { nodesRender.runInRender { val cameraId = when (cameraHolder.cameraId) { CAMERA_REAR -> CAMERA_FRONT else -> CAMERA_REAR } cameraHolder.cameraId = cameraId updatePreviewNode( cameraHolder.previewSizes.first().width, cameraHolder.previewSizes.first().height ) cameraHolder.setSurface(cameraPreviewNode!!.combineSurfaceTexture.surface) .invalidate() } } previewSize.setOnClickListener { cameraHolder?.let { it.previewSizes?.let { sizes -> val builder = AlertDialog.Builder(this@MainActivity) val sizesString: Array<String> = Array(sizes?.size ?: 0) { "" } sizes?.forEachIndexed { index, item -> sizesString[index] = item.width.toString() + "*" + item.height.toString() } builder.setItems(sizesString) { d, index -> val size = sizesString[index].split("*") val width = size[0].toInt() val height = size[1].toInt() nodesRender.runInRender { updatePreviewNode(width, height) cameraHolder.setSurface(cameraPreviewNode!!.combineSurfaceTexture.surface) .invalidate() } } builder.create().show() } } }