创建ArCore Session时显示黑屏而非相机画面,求问题排查
ARCore 相机黑屏问题解决
核心问题分析
你的代码存在几个关键错误,导致相机画面无法正常显示:
- 重复创建Session实例:
onResume中先初始化mSession = Session(this),随后调用createSession()又重新创建Session,覆盖了之前的实例,造成相机连接状态混乱。 - 错误使用SurfaceView:ARCore相机画面需要通过OpenGL纹理渲染,普通SurfaceView无法直接承载,必须使用
GLSurfaceView并配合Renderer处理帧渲染逻辑。你用holder.surface.hashCode()作为纹理名完全错误,这个值并非OpenGL纹理ID。 - 缺少帧渲染循环:ARCore不会自动将相机画面输出到Surface,需要主动在每一帧中获取帧数据并完成渲染,你的代码没有实现这一核心逻辑。
- 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" } }
关键修复说明
- 替换为GLSurfaceView:ARCore依赖OpenGL ES渲染相机画面,GLSurfaceView提供了现成的OpenGL上下文和渲染循环。
- Session单例管理:去掉重复创建Session的逻辑,确保Session仅初始化一次,避免状态混乱。
- 正确生成OpenGL纹理ID:ARCore需要绑定有效的OpenGL纹理ID,而非Surface的哈希值。
- 实现Renderer接口:通过
onDrawFrame持续获取ARCore相机帧,并用OpenGL着色器将纹理渲染到全屏。 - 添加帧渲染逻辑:主动获取
session.update()返回的帧数据,完成画面绘制,这是ARCore显示相机画面的核心步骤。
额外注意事项
- 确保
CameraPermissionHelper类正确处理相机权限的申请与检查。 - 测试前确认设备已安装最新版本的ARCore服务。
- 该代码为基础相机渲染实现,如需添加AR物体,可在此基础上扩展渲染逻辑。
内容的提问来源于stack exchange,提问作者Mary Lens
相关产品推荐
相关产品推荐

