OpenGL ES for Android 相機(jī)預(yù)覽

權(quán)限

Android上打開攝像頭需要camera權(quán)限,在Android 6.0及以上的版本需要?jiǎng)討B(tài)申請(qǐng)權(quán)限,在AndroidManifest.xml中添加camera權(quán)限:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.arvr.sample">

    <uses-permission android:name="android.permission.CAMERA"/>

    <application>
        ...
    </application>

<uses-permission android:name="android.permission.CAMERA"/> 是camera權(quán)限

動(dòng)態(tài)申請(qǐng)camera權(quán)限代碼如下:

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
        ) {
            //沒有權(quán)限
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 100)
        } else {
            mRenderer.cameraPermission = true
            mRenderer.startCamera()
        }

    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        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權(quán)限,如果沒有則申請(qǐng)權(quán)限權(quán)限 , 如果有則打開camera。彈出權(quán)限申請(qǐng)對(duì)話框,用戶點(diǎn)擊是否允許,不管是同意還是拒絕都會(huì)回調(diào)onRequestPermissionsResult方法,用戶點(diǎn)擊同意后打開camera,和已經(jīng)有權(quán)限的操作是一樣的。

創(chuàng)建program并獲取參數(shù)句柄

頂點(diǎn)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 : require
precision mediump float;

uniform samplerExternalOES u_Texture;
varying vec2 v_TexCoord;

void main()
{
    gl_FragColor = texture2D(u_Texture, v_TexCoord);
}

<font color='red'>
注意:頂點(diǎn)和片段shader是單獨(dú)的文件,分別是camera_vs.glsl和camera_fs.glsl,存放于assets/glsl目錄下。
</font>

onSurfaceCreated回調(diào)中創(chuàng)建program并獲取參數(shù)句柄,創(chuàng)建紋理,代碼如下:

 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方法是其中一個(gè)方法,創(chuàng)建一個(gè)OES紋理,OES紋理用于渲染相機(jī)、視頻。創(chuàng)建紋理id并創(chuàng)建SurfaceTexture,SurfaceTexture在打開相機(jī)方法中用到,用于預(yù)覽相機(jī)。setOnFrameAvailableListener的回調(diào)是從Activity中傳入,真正的實(shí)現(xiàn)在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)
        ...
    }
    ...
}

當(dāng)有新的一幀數(shù)據(jù)時(shí)會(huì)回調(diào)此方法,更新數(shù)據(jù)并調(diào)用glSurfaceView.requestRender() 重新繪制,也就是重新調(diào)用Renderer的onDrawFrame方法。

設(shè)置頂點(diǎn)坐標(biāo)、紋理坐標(biāo)、索引數(shù)據(jù)

設(shè)置頂點(diǎn)坐標(biāo),代碼如下:

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
            )
        )

設(shè)置紋理坐標(biāo),代碼如下:

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

設(shè)置索引數(shù)據(jù),代碼如下:

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

繪制

override fun onDrawFrame(p0: GL10?) {
            GLES20.glUseProgram(mProgramHandle)
            //設(shè)置頂點(diǎn)數(shù)據(jù)
            vertexBuffer.position(0)
            GLES20.glEnableVertexAttribArray(vPositionLoc)
            GLES20.glVertexAttribPointer(vPositionLoc, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer)
            //設(shè)置紋理頂點(diǎn)數(shù)據(jù)
            texBuffer.position(0)
            GLES20.glEnableVertexAttribArray(texCoordLoc)
            GLES20.glVertexAttribPointer(texCoordLoc, 2, GLES20.GL_FLOAT, false, 0, texBuffer)
            //設(shè)置紋理
            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個(gè)條件:

  • 有相機(jī)權(quán)限。
  • SurfaceTexture已經(jīng)創(chuàng)建完成。

相機(jī)權(quán)限申請(qǐng)的回調(diào)和Renderer中onSurfaceCreated(創(chuàng)建SurfaceTexture的方法)方法都是異步的,也就是說無法知道這2個(gè)方法回調(diào)的前后順序,因此需要保存相機(jī)權(quán)限狀態(tài)cameraPermission和SurfaceTexture變量surfaceTexture,在2個(gè)回調(diào)中都調(diào)用打開相機(jī)方法,在打開相機(jī)方法中判斷相機(jī)權(quán)限和SurfaceTexture是否都已經(jīng)準(zhǔn)備完成,是則打開,不是則返回,代碼如下:

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)

                    //設(shè)置分辨率
                    val parameters = mCamera.parameters
                    parameters.setPreviewSize(1280, 720)
                    mCamera.parameters = parameters

                    //開始預(yù)覽
                    mCamera.startPreview()
                    return
                }
            }
        }

運(yùn)行效果如下:

運(yùn)行后發(fā)現(xiàn)相機(jī)的畫面是倒的,這是因?yàn)閏amera本身輸出的預(yù)覽流就是倒的,下面通過矩陣旋轉(zhuǎn)解決此問題,頂點(diǎn)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矩陣。
獲取矩陣參數(shù)句柄,代碼如下:

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

        }

旋轉(zhuǎn)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)
        }

設(shè)置矩陣參數(shù),代碼如下:

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

運(yùn)行后發(fā)現(xiàn)畫面調(diào)整正了,但左右鏡像,這個(gè)時(shí)候需要畫面繞y軸旋轉(zhuǎn)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)
        }

<font color='red'>
注意,對(duì)預(yù)覽流的操作是先繞z軸旋轉(zhuǎn)90度,使畫面調(diào)正,然后再繞y軸旋轉(zhuǎn)180度,但寫代碼的時(shí)候要繞y軸旋轉(zhuǎn)180度寫在前面。
</font>

最終效果如下:

更多相關(guān)閱讀:

如果這篇文章有幫助到您,希望您關(guān)注我的公眾號(hào),謝謝。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容