权限

Android上打开摄像头需要camera权限,在Android 6.0及以上的版本需要动态申请权限,在AndroidManifest.xml中添加camera权限:

        ...

是camera权限

动态申请camera权限代码如下:

class CameraActivity : AppCompatActivity(), SurfaceTexture.OnFrameAvailableListener {    private lateinit var mRenderer: MyRenderer    override fun onFrameAvailable(surfaceTexture: SurfaceTexture?) {        glSurfaceView.queueEvent {            surfaceTexture?.updateTexImage()            glSurfaceView.requestRender()        }    }    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.surface)        glSurfaceView.setEGLContextClientVersion(2)        mRenderer = MyRenderer(context = baseContext, listener = this)        glSurfaceView.setRenderer(mRenderer)        glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY        if (ContextCompat.checkSelfPermission(                this,                Manifest.permission.CAMERA            ) != PackageManager.PERMISSION_GRANTED        ) {            //没有权限            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 100)        } else {            mRenderer.cameraPermission = true            mRenderer.startCamera()        }    }    override fun onRequestPermissionsResult(        requestCode: Int,        permissions: Array,        grantResults: IntArray    ) {        super.onRequestPermissionsResult(requestCode, permissions, grantResults)        if (requestCode == 100 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {            mRenderer.cameraPermission = true            mRenderer.startCamera()        }    }    override fun onResume() {        super.onResume()        glSurfaceView.onResume()    }    override fun onPause() {        super.onPause()        glSurfaceView.onPause()    }}

在onCreate中先判断是否有camera权限,如果没有则申请权限权限 , 如果有则打开camera。弹出权限申请对话框,用户点击是否允许,不管是同意还是拒绝都会回调onRequestPermissionsResult方法,用户点击同意后打开camera,和已经有权限的操作是一样的。

创建program并获取参数句柄

顶点shader代码如下:

attribute vec4 a_Position;attribute vec2 a_TexCoordinate;varying vec2 v_TexCoord;void main(){    v_TexCoord = a_TexCoordinate;    gl_Position = a_Position;}

片段shader代码如下:

#extension GL_OES_EGL_image_external : requireprecision mediump float;uniform samplerExternalOES u_Texture;varying vec2 v_TexCoord;void main(){    gl_FragColor = texture2D(u_Texture, v_TexCoord);}
注意:顶点和片段shader是单独的文件,分别是camera_vs.glsl和camera_fs.glsl,存放于assets/glsl目录下。

onSurfaceCreated回调中创建program并获取参数句柄,创建纹理,代码如下:

 override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {            createProgram()            //获取vPosition索引            vPositionLoc = GLES20.glGetAttribLocation(mProgramHandle, "a_Position")            texCoordLoc = GLES20.glGetAttribLocation(mProgramHandle, "a_TexCoordinate")            textureLoc = GLES20.glGetUniformLocation(mProgramHandle, "u_Texture")            textureId = GLTools.createOESTextureId()            surfaceTexture = SurfaceTexture(textureId)            surfaceTexture?.setOnFrameAvailableListener(listener)        }private fun createProgram() {            var vertexCode =                AssetsUtils.readAssetsTxt(                    context = context,                    filePath = "glsl/camera_vs.glsl"                )            var fragmentCode =                AssetsUtils.readAssetsTxt(                    context = context,                    filePath = "glsl/camera_fs.glsl"                )            mProgramHandle = GLTools.createAndLinkProgram(vertexCode, fragmentCode)        }fun createOESTextureId(): Int {        val textures = IntArray(1)        GLES20.glGenTextures(1, textures, 0)        glCheck("texture generate")        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[0])        glCheck("texture bind")        GLES20.glTexParameterf(            GLES11Ext.GL_TEXTURE_EXTERNAL_OES,            GLES20.GL_TEXTURE_MIN_FILTER,            GLES20.GL_LINEAR.toFloat()        )        GLES20.glTexParameterf(            GLES11Ext.GL_TEXTURE_EXTERNAL_OES,            GLES20.GL_TEXTURE_MAG_FILTER,            GLES20.GL_LINEAR.toFloat()        )        GLES20.glTexParameteri(            GLES11Ext.GL_TEXTURE_EXTERNAL_OES,            GLES20.GL_TEXTURE_WRAP_S,            GLES20.GL_CLAMP_TO_EDGE        )        GLES20.glTexParameteri(            GLES11Ext.GL_TEXTURE_EXTERNAL_OES,            GLES20.GL_TEXTURE_WRAP_T,            GLES20.GL_CLAMP_TO_EDGE        )        return textures[0]    }

GLTools 为工具类,createOESTextureId方法是其中一个方法,创建一个OES纹理,OES纹理用于渲染相机、视频。创建纹理id并创建SurfaceTexture,SurfaceTexture在打开相机方法中用到,用于预览相机。setOnFrameAvailableListener的回调是从Activity中传入,真正的实现在Activity中,

class CameraActivity : AppCompatActivity(), SurfaceTexture.OnFrameAvailableListener {    private lateinit var mRenderer: MyRenderer    override fun onFrameAvailable(surfaceTexture: SurfaceTexture?) {        glSurfaceView.queueEvent {            surfaceTexture?.updateTexImage()            glSurfaceView.requestRender()        }    }    override fun onCreate(savedInstanceState: Bundle?) {        ...        mRenderer = MyRenderer(context = baseContext, listener = this)...}...}

当有新的一帧数据时会回调此方法,更新数据并调用glSurfaceView.requestRender() 重新绘制,也就是重新调用Renderer的onDrawFrame方法。

设置顶点坐标、纹理坐标、索引数据

设置顶点坐标,代码如下:

var vertexBuffer = GLTools.array2Buffer(            floatArrayOf(                -1.0f, 1.0f, 0.0f,  // top left                -1.0f, -1.0f, 0.0f,  // bottom left                1.0f, -1.0f, 0.0f,  // bottom right                1.0f, 1.0f, 0.0f  // top right            )        )

设置纹理坐标,代码如下:

        var texBuffer = GLTools.array2Buffer(            floatArrayOf(                0.0f, 0.0f,                0.0f, 1.0f,                1.0f, 1.0f,                1.0f, 0.0f            )        )

设置索引数据,代码如下:

var index = shortArrayOf(3, 2, 0, 0, 1, 2)val indexBuffer = GLTools.array2Buffer(index)

绘制

override fun onDrawFrame(p0: GL10?) {            GLES20.glUseProgram(mProgramHandle)            //设置顶点数据            vertexBuffer.position(0)            GLES20.glEnableVertexAttribArray(vPositionLoc)            GLES20.glVertexAttribPointer(vPositionLoc, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer)            //设置纹理顶点数据            texBuffer.position(0)            GLES20.glEnableVertexAttribArray(texCoordLoc)            GLES20.glVertexAttribPointer(texCoordLoc, 2, GLES20.GL_FLOAT, false, 0, texBuffer)            //设置纹理            GLES20.glActiveTexture(GLES20.GL_TEXTURE0)            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId)            GLES20.glUniform1i(textureLoc, 0)            GLES20.glDrawElements(                GLES20.GL_TRIANGLES,                index.size,                GLES20.GL_UNSIGNED_SHORT,                indexBuffer            )        }

打开camera

打开camera有2个条件:

  • 有相机权限。
  • SurfaceTexture已经创建完成。

相机权限申请的回调和Renderer中onSurfaceCreated(创建SurfaceTexture的方法)方法都是异步的,也就是说无法知道这2个方法回调的前后顺序,因此需要保存相机权限状态cameraPermission和SurfaceTexture变量surfaceTexture,在2个回调中都调用打开相机方法,在打开相机方法中判断相机权限和SurfaceTexture是否都已经准备完成,是则打开,不是则返回,代码如下:

override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {...startCamera()}fun startCamera() {            if (!cameraPermission || surfaceTexture == null) {                return            }            val cameraInfo = Camera.CameraInfo()            val cameraCount = Camera.getNumberOfCameras()            for (i in 0 until cameraCount) {                Camera.getCameraInfo(i, cameraInfo)                if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {                    val mCamera = Camera.open(i)                    mCamera.setPreviewTexture(surfaceTexture)                    //设置分辨率                    val parameters = mCamera.parameters                    parameters.setPreviewSize(1280, 720)                    mCamera.parameters = parameters                    //开始预览                    mCamera.startPreview()                    return                }            }        }

运行效果如下:

运行后发现相机的画面是倒的,这是因为camera本身输出的预览流就是倒的,下面通过矩阵旋转解决此问题,顶点shader修改如下:

attribute vec4 a_Position;attribute vec2 a_TexCoordinate;uniform mat4 mMatrix;varying vec2 v_TexCoord;void main(){    v_TexCoord = a_TexCoordinate;    gl_Position = mMatrix * a_Position;}

增加了mMatrix矩阵。
获取矩阵参数句柄,代码如下:

override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {            createProgram()            ...            matrixLoc = GLES20.glGetUniformLocation(mProgramHandle, "mMatrix")            ...        }

旋转90度,代码如下:

var mMatrix = FloatArray(16)override fun onSurfaceChanged(p0: GL10?, width: Int, height: Int) {            GLES20.glViewport(0, 0, width, height)            Matrix.setIdentityM(mMatrix, 0)            Matrix.rotateM(mMatrix,0,90F,0F,0F,1F)        }

设置矩阵参数,代码如下:

override fun onDrawFrame(p0: GL10?) {           ...            GLES20.glUniformMatrix4fv(matrixLoc, 1, false, mMatrix, 0)            ...        }

运行后发现画面调整正了,但左右镜像,这个时候需要画面绕y轴旋转180度,这样就解决了左右镜像问题,代码如下:

override fun onSurfaceChanged(p0: GL10?, width: Int, height: Int) {            GLES20.glViewport(0, 0, width, height)            Matrix.setIdentityM(mMatrix, 0)            Matrix.rotateM(mMatrix,0,180F,0F,1F,0F)            Matrix.rotateM(mMatrix,0,90F,0F,0F,1F)        }
注意,对预览流的操作是先绕z轴旋转90度,使画面调正,然后再绕y轴旋转180度,但写代码的时候要绕y轴旋转180度写在前面。

最终效果如下:

更多相关阅读:

  • OpenGL ES for Android 总览

  • OpenGL ES for Android 绘制旋转的地球

  • OpenGL ES for Android 预览视频

  • OpenGL ES for Android 绘制立方体

如果这篇文章有帮助到您,希望您关注我的公众号,谢谢。

更多相关文章

  1. Android中自定义权限
  2. 整理出15个Android很有用的代码片段
  3. android 驱动文件权限设置
  4. Android简单的Timer小例子
  5. [Android] 升级了新的android studio之后 发生如下的报错,The fol
  6. android中使用特殊符号
  7. Linux下Android(安卓)SDK中adb找不到的解决方案
  8. Android(安卓)SDK下载和更新失败的解决方法
  9. android viewpage的使用

随机推荐

  1. 用table做课程表、用表单做注册界面
  2. 盘点|2021年最受欢迎Linux桌面操作系统前
  3. Android中WebView的简单使用
  4. Android(安卓)读取已知包名的uses-permis
  5. android绘制图标
  6. network: Android(安卓)网络判断(wifi、3G
  7. 全局窗口二
  8. Android(安卓)定时启动应用
  9. Android(安卓)可滚动圆形进度条 滑块和进
  10. android常用