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

MediaRecorder.prepare()报invalid surface错误,求解决方案

解决MediaRecorder "invalid preview surface" 错误

你的核心问题是在Surface还未完全创建完成时就初始化并启动了MediaRecorder,加上代码里的一些冗余操作,导致预览Surface无效。以下是具体修复方案:

关键问题分析

  • 在onCreate中直接调用startRecording时,Surface还处于创建过程中,此时获取的Surface是未就绪状态。
  • startRecording里重复注册SurfaceHolder.Callback,属于冗余操作,可能导致回调逻辑混乱。
  • onCreate和surfaceCreated中调用mediaRecorder?.setPreviewDisplay(surface)时,mediaRecorder还未实例化,这些调用完全无效。

修复后的代码

private lateinit var surfaceView: SurfaceView
private lateinit var surfaceHolder: SurfaceHolder
private var mediaRecorder: MediaRecorder? = null
private val VIDEO_DURATION = 20000L // 20秒

class CamActivity : AppCompatActivity(), SurfaceHolder.Callback {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_cam)

        surfaceView = findViewById(R.id.surface_view)
        surfaceHolder = surfaceView.holder
        surfaceHolder.addCallback(this)
        // 移除onCreate中直接调用startRecording的代码,等待Surface就绪
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        // Surface就绪后,先检查权限再启动录制
        if (checkPermissions()) {
            startRecording(holder.surface)
        } else {
            requestPermissions()
        }
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
        // 若录制过程中Surface尺寸变化,可在此做适配,当前场景暂时不需要
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        // Surface销毁时立即停止录制并释放资源
        stopRecording()
    }

    private fun startRecording(surface: Surface) {
        try {
            mediaRecorder = MediaRecorder().apply {
                // 配置顺序严格遵循:源 → 输出格式 → 编码器 → 其他参数 → 预览Surface → prepare
                setVideoSource(MediaRecorder.VideoSource.DEFAULT)
                setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
                setVideoEncoder(MediaRecorder.VideoEncoder.H264)
                setVideoEncodingBitRate(512 * 1000)
                setVideoFrameRate(30)
                setVideoSize(640, 480)
                setOutputFile(getOutputFile().absolutePath)
                // 传入已就绪的Surface
                setPreviewDisplay(surface)
                prepare()
                start()
            }

            // 20秒后自动停止录制
            GlobalScope.launch {
                delay(VIDEO_DURATION)
                stopRecording()
            }
        } catch (e: Exception) {
            e.printStackTrace()
            Toast.makeText(this, "录制启动失败: ${e.message}", Toast.LENGTH_SHORT).show()
        }
    }

    private fun stopRecording() {
        mediaRecorder?.run {
            try {
                stop()
                release()
            } catch (e: Exception) {
                e.printStackTrace()
            } finally {
                mediaRecorder = null
            }
        }
    }

    // 权限检查和请求的实现
    private fun checkPermissions(): Boolean {
        return ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
                && ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
    }

    private fun requestPermissions() {
        ActivityCompat.requestPermissions(
            this,
            arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE),
            1001
        )
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == 1001) {
            if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
                // 权限通过后,确认Surface是否还存在,再启动录制
                if (surfaceHolder.surface.isValid) {
                    startRecording(surfaceHolder.surface)
                }
            } else {
                Toast.makeText(this, "需要相机和存储权限才能录制", Toast.LENGTH_SHORT).show()
                finish()
            }
        }
    }

    // 生成输出文件路径
    private fun getOutputFile(): File {
        val mediaDir = externalMediaDirs.firstOrNull()?.let {
            File(it, "录制视频").apply { mkdirs() }
        } ?: filesDir
        return File(mediaDir, "VID_${System.currentTimeMillis()}.mp4")
    }
}

额外注意事项

  • 确保SurfaceView在布局中是可见且有尺寸的,避免因Surface尺寸为0导致的无效问题。
  • Android 10及以上版本,存储权限可改用MediaStore写入,无需申请WRITE_EXTERNAL_STORAGE,但如果要兼容低版本仍需保留权限请求。
  • 建议使用lifecycleScope.launch替代GlobalScope,绑定Activity生命周期,防止内存泄漏。

内容的提问来源于stack exchange,提问作者Jonathan Merchan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 17:27:52