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
相关产品推荐
相关产品推荐

