Android Camera2 + OpenGL离屏渲染示例

  1. 初始化EGL环境

    kotlin 复制代码
    // 创建带 EGL 环境的 GL 线程
    class GLThread : HandlerThread("GLThread") {
        private lateinit var eglDisplay: EGLDisplay
        private lateinit var eglContext: EGLContext
        private lateinit var handler: Handler
    
        override fun onLooperPrepared() {
            super.onLooperPrepared()
            initEGL()
            handler = Handler(looper)
        }
    
        private fun initEGL() {
            // 1. 获取 EGL Display
            eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
            
            // 2. 初始化 EGL
            val version = IntArray(2)
            EGL14.eglInitialize(eglDisplay, version, 0, version, 1)
            
            // 3. 选择配置
            val configs = arrayOfNulls<EGLConfig>(1)
            val configAttribs = intArrayOf(
                EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
                EGL14.EGL_RED_SIZE, 8,
                EGL14.EGL_GREEN_SIZE, 8,
                EGL14.EGL_BLUE_SIZE, 8,
                EGL14.EGL_NONE
            )
            EGL14.eglChooseConfig(eglDisplay, configAttribs, 0, configs, 0, 1, intArrayOf(0), 0)
            
            // 4. 创建 EGL Context
            val contextAttribs = intArrayOf(
                EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
                EGL14.EGL_NONE
            )
            eglContext = EGL14.eglCreateContext(
                eglDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
                contextAttribs, 0
            )
            
            // 5. 创建虚拟 Surface (离屏渲染)
            val surfaceAttribs = intArrayOf(EGL14.EGL_WIDTH, 1, EGL14.EGL_HEIGHT, 1, EGL14.EGL_NONE)
            val eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, configs[0], surfaceAttribs, 0)
            
            // 6. 绑定上下文
            EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)
        }
    
        fun runOnGLThread(runnable: Runnable) {
            handler.post {
                // 确保在当前线程绑定上下文
                EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, eglContext)
                runnable.run()
            }
        }
    }
  2. 在 GL 线程中初始化 SurfaceTexture

    kotlin 复制代码
    // 创建 GL 线程
    val glThread = GLThread().apply { start() }
    
    // 等待线程准备就绪
    Thread.sleep(300)  // 简化的等待机制,实际应用中应使用回调
    
    // 在 GL 线程中创建 SurfaceTexture
    lateinit var mSurfaceTexture: SurfaceTexture
    
    glThread.runOnGLThread {
        // 在有效的 GL 环境中创建纹理
        val textureIds = IntArray(1)
        GLES30.glGenTextures(1, textureIds, 0)
        GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureIds[0])
        
        // 创建绑定到纹理的 SurfaceTexture
        mSurfaceTexture = SurfaceTexture(textureIds[0]).apply {
            setDefaultBufferSize(width, height)
            
            // 注意:监听器内部操作将在 GL 线程执行
            setOnFrameAvailableListener({ surfaceTexture ->
                Log.i(TAG, "onFrameAvailable in GL context")
                try {
                    surfaceTexture.updateTexImage()  // 现在可以安全调用
                    
                    // 可选:获取变换矩阵
                    val transformMatrix = FloatArray(16)
                    surfaceTexture.getTransformMatrix(transformMatrix)
                    
                    // 处理帧数据...
                } catch (e: Exception) {
                    Log.e(TAG, "Error updating texture", e)
                }
            }, null)  // 不指定 Handler,使用当前线程
        }
    }
  3. 在 Camera 配置中使用 Surface

    kotlin 复制代码
    // 创建 Surface 供相机使用
    val mSurface = Surface(mSurfaceTexture)
    
    // 配置相机输出目标
    mCaptureRequestBuild?.addTarget(mSurface)
    outputConfiguration.add(OutputConfiguration(mSurface))
  4. 注意事项

    • SurfaceTexture 的创建和销毁必须在同一个线程

    • updateTexImage() 必须在创建 SurfaceTexture 的线程调用

    • 资源释放

      kotlin 复制代码
      fun release() {
          glThread.runOnGLThread {
              mSurfaceTexture.release()
              // 释放 EGL 资源
              EGL14.eglDestroyContext(glThread.eglDisplay, glThread.eglContext)
          }
          glThread.quitSafely()
      }
相关推荐
杉氧19 分钟前
兼容与共生:如何在旧项目中优雅地引入 Compose?
android·架构·android jetpack
Flynt1 小时前
Room 3.0 包名重构 + KMP 迁移:我把项目升级踩了个遍
android·数据库·kotlin
杉氧1 小时前
性能优化实战:如何定位冗余重组并榨干 Compose 的每一帧性能?
android·架构·android jetpack
alexhilton13 小时前
将应用迁移到Navigation 3:痛点、加班和紧急修复
android·kotlin·android jetpack
杉氧18 小时前
Navigation Compose 深度实践:如何优雅地串联起你的全栈 App?
android·架构·android jetpack
雨白1 天前
指针与数组的核心机制
android
黄林晴1 天前
Room 3.0 正式发布!包名彻底重构,KMP 成为核心主线
android·android jetpack
三少爷的鞋1 天前
Kotlin 协程环境下的 DCL 懒加载:别把线程时代的经验直接搬过来
android
plainGeekDev1 天前
Gson → kotlinx.serialization
android·java·kotlin