You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

创建ArCore Session时显示黑屏而非相机画面,求问题排查

ARCore 相机黑屏问题解决

核心问题分析

你的代码存在几个关键错误,导致相机画面无法正常显示:

  1. 重复创建Session实例:onResume中先初始化mSession = Session(this),随后调用createSession()又重新创建Session,覆盖了之前的实例,造成相机连接状态混乱。
  2. 错误使用SurfaceView:ARCore相机画面需要通过OpenGL纹理渲染,普通SurfaceView无法直接承载,必须使用GLSurfaceView并配合Renderer处理帧渲染逻辑。你用holder.surface.hashCode()作为纹理名完全错误,这个值并非OpenGL纹理ID。
  3. 缺少帧渲染循环:ARCore不会自动将相机画面输出到Surface,需要主动在每一帧中获取帧数据并完成渲染,你的代码没有实现这一核心逻辑。
  4. Surface回调设置时机错误:在mSession?.resume()之后才添加SurfaceHolder回调,可能导致Surface创建时Session尚未完成初始化。

修复后的完整代码

1. 布局文件(activity_arcore.xml)

将原SurfaceView替换为GLSurfaceView:

<?xml version="1.0" encoding="utf-8"?>
<android.opengl.GLSurfaceView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/gl_surface_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

2. ArcoreActivity代码

class ArcoreActivity : AppCompatActivity(), GLSurfaceView.Renderer {
    private var mUserRequestedInstall = true
    private var mSession: Session? = null
    private lateinit var glSurfaceView: GLSurfaceView
    private var cameraTextureId = -1
    private var shaderProgram = -1

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_arcore)
        glSurfaceView = findViewById(R.id.gl_surface_view)

        // 配置GLSurfaceView参数
        glSurfaceView.setEGLContextClientVersion(2)
        glSurfaceView.setRenderer(this)
        glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY
    }

    override fun onResume() {
        super.onResume()
        if (!CameraPermissionHelper.hasCameraPermission(this)) {
            CameraPermissionHelper.requestCameraPermission(this)
            return
        }

        // 复用已存在的Session
        mSession?.let {
            try {
                it.resume()
            } catch (e: CameraNotAvailableException) {
                Log.e(TAG, "相机不可用", e)
                mSession = null
                return
            }
            return
        }

        try {
            when (ArCoreApk.getInstance().requestInstall(this, mUserRequestedInstall)) {
                ArCoreApk.InstallStatus.INSTALLED -> {
                    mSession = Session(this).apply {
                        val config = Config(this)
                        configure(config)
                    }
                    mSession?.resume()
                }

                ArCoreApk.InstallStatus.INSTALL_REQUESTED -> {
                    mUserRequestedInstall = false
                    return
                }
            }
        } catch (e: UnavailableUserDeclinedInstallationException) {
            Toast.makeText(this, "用户拒绝安装ARCore", Toast.LENGTH_LONG).show()
            finish()
            return
        } catch (e: Exception) {
            Log.e(TAG, "Session初始化失败", e)
            finish()
            return
        }
    }

    override fun onPause() {
        super.onPause()
        mSession?.pause()
    }

    override fun onDestroy() {
        super.onDestroy()
        mSession?.close()
        mSession = null
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (!CameraPermissionHelper.hasCameraPermission(this)) {
            Toast.makeText(this, "需要相机权限才能运行应用", Toast.LENGTH_LONG).show()
            if (!CameraPermissionHelper.shouldShowRequestPermissionRationale(this)) {
                CameraPermissionHelper.launchPermissionSettings(this)
            }
            finish()
        }
    }

    // GLSurfaceView.Renderer 实现
    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
        // 生成相机纹理ID
        val textures = IntArray(1)
        GLES20.glGenTextures(1, textures, 0)
        cameraTextureId = textures[0]
        // 配置纹理参数
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, cameraTextureId)
        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)
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR)
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR)

        // 将纹理ID绑定到ARCore Session
        mSession?.setCameraTextureName(cameraTextureId)
    }

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
        GLES20.glViewport(0, 0, width, height)
        // 设置ARCore显示尺寸
        mSession?.setDisplayGeometry(width, height)
    }

    override fun onDrawFrame(gl: GL10?) {
        // 清除屏幕缓存
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT or GLES20.GL_DEPTH_BUFFER_BIT)

        mSession?.let { session ->
            try {
                // 获取最新相机帧
                val frame = session.update()
                drawCameraFrame(frame.camera)
            } catch (e: CameraNotAvailableException) {
                Log.e(TAG, "相机帧获取失败", e)
                return
            }
        }
    }

    // 绘制相机画面
    private fun drawCameraFrame(camera: Camera) {
        // 初始化着色器程序(仅执行一次)
        if (shaderProgram == -1) {
            shaderProgram = createCameraShaderProgram()
        }

        GLES20.glUseProgram(shaderProgram)

        // 设置投影矩阵
        val projectionMatrix = FloatArray(16)
        camera.getProjectionMatrix(projectionMatrix, 0, 0.1f, 100.0f)
        val mvpMatrixUniform = GLES20.glGetUniformLocation(shaderProgram, "uMvpMatrix")
        GLES20.glUniformMatrix4fv(mvpMatrixUniform, 1, false, projectionMatrix, 0)

        // 绑定相机纹理
        val textureUniform = GLES20.glGetUniformLocation(shaderProgram, "uTexture")
        GLES20.glUniform1i(textureUniform, 0)
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, cameraTextureId)

        // 绘制全屏四边形
        drawFullScreenQuad()
    }

    // 创建相机渲染着色器程序
    private fun createCameraShaderProgram(): Int {
        // 顶点着色器
        val vertexShaderCode = """
            attribute vec4 aPosition;
            attribute vec2 aTexCoord;
            uniform mat4 uMvpMatrix;
            varying vec2 vTexCoord;
            void main() {
                gl_Position = uMvpMatrix * aPosition;
                vTexCoord = aTexCoord;
            }
        """.trimIndent()

        // 片段着色器(处理外部纹理)
        val fragmentShaderCode = """
            #extension GL_OES_EGL_image_external : require
            precision mediump float;
            varying vec2 vTexCoord;
            uniform samplerExternalOES uTexture;
            void main() {
                gl_FragColor = texture2D(uTexture, vTexCoord);
            }
        """.trimIndent()

        val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)
        val fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode)

        val program = GLES20.glCreateProgram()
        GLES20.glAttachShader(program, vertexShader)
        GLES20.glAttachShader(program, fragmentShader)
        GLES20.glLinkProgram(program)

        // 检查着色器链接状态
        val linkStatus = IntArray(1)
        GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0)
        if (linkStatus[0] != GLES20.GL_TRUE) {
            Log.e(TAG, "着色器链接失败: ${GLES20.glGetProgramInfoLog(program)}")
            GLES20.glDeleteProgram(program)
            return -1
        }
        return program
    }

    // 加载着色器
    private fun loadShader(type: Int, shaderCode: String): Int {
        val shader = GLES20.glCreateShader(type)
        GLES20.glShaderSource(shader, shaderCode)
        GLES20.glCompileShader(shader)

        // 检查着色器编译状态
        val compileStatus = IntArray(1)
        GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0)
        if (compileStatus[0] != GLES20.GL_TRUE) {
            Log.e(TAG, "着色器编译失败: ${GLES20.glGetShaderInfoLog(shader)}")
            GLES20.glDeleteShader(shader)
            return 0
        }
        return shader
    }

    // 全屏四边形顶点数据
    private val quadVertices = floatArrayOf(
        -1.0f, -1.0f, 0.0f,
        1.0f, -1.0f, 0.0f,
        -1.0f, 1.0f, 0.0f,
        1.0f, 1.0f, 0.0f
    )

    private val quadTexCoords = floatArrayOf(
        0.0f, 1.0f,
        1.0f, 1.0f,
        0.0f, 0.0f,
        1.0f, 0.0f
    )

    // 绘制全屏四边形
    private fun drawFullScreenQuad() {
        val positionHandle = GLES20.glGetAttribLocation(shaderProgram, "aPosition")
        val texCoordHandle = GLES20.glGetAttribLocation(shaderProgram, "aTexCoord")

        // 传递顶点坐标数据
        GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 0, FloatBuffer.wrap(quadVertices))
        GLES20.glEnableVertexAttribArray(positionHandle)

        // 传递纹理坐标数据
        GLES20.glVertexAttribPointer(texCoordHandle, 2, GLES20.GL_FLOAT, false, 0, FloatBuffer.wrap(quadTexCoords))
        GLES20.glEnableVertexAttribArray(texCoordHandle)

        // 执行绘制
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)

        // 禁用顶点数组
        GLES20.glDisableVertexAttribArray(positionHandle)
        GLES20.glDisableVertexAttribArray(texCoordHandle)
    }

    companion object {
        private const val TAG = "ArcoreActivity"
    }
}

关键修复说明

  1. 替换为GLSurfaceView:ARCore依赖OpenGL ES渲染相机画面,GLSurfaceView提供了现成的OpenGL上下文和渲染循环。
  2. Session单例管理:去掉重复创建Session的逻辑,确保Session仅初始化一次,避免状态混乱。
  3. 正确生成OpenGL纹理ID:ARCore需要绑定有效的OpenGL纹理ID,而非Surface的哈希值。
  4. 实现Renderer接口:通过onDrawFrame持续获取ARCore相机帧,并用OpenGL着色器将纹理渲染到全屏。
  5. 添加帧渲染逻辑:主动获取session.update()返回的帧数据,完成画面绘制,这是ARCore显示相机画面的核心步骤。

额外注意事项

  • 确保CameraPermissionHelper类正确处理相机权限的申请与检查。
  • 测试前确认设备已安装最新版本的ARCore服务。
  • 该代码为基础相机渲染实现,如需添加AR物体,可在此基础上扩展渲染逻辑。

内容的提问来源于stack exchange,提问作者Mary Lens

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 14:09:51