如何录制基于SurfaceView扩展的Camera2扫描扭曲效果自定义类?
Camera2扫描扭曲效果录制解决方案
针对你基于Camera2 API和GL实现扫描扭曲效果的SurfaceView,在使用MediaRecorder录制时出现的停止失败等异常,以下是可行的修复和实现方案:
核心问题分析
当前代码仅将Camera预览帧输出到GL渲染管线并显示在SurfaceView上,未将处理后的画面接入MediaRecorder的录制流程;同时缺少MediaRecorder生命周期的正确管理,导致停止时资源释放顺序错误引发异常。
具体实现步骤
1. 添加录制相关成员变量
在Camera2SurfaceView类中新增录制所需变量:
private var mMediaRecorder: MediaRecorder? = null private var mRecordSurface: Surface? = null private var isRecording = false private val recordHandler = Handler(Looper.getMainLooper())
2. 初始化MediaRecorder配置
实现录制初始化方法,参数需与Camera预览分辨率严格匹配:
private fun initMediaRecorder(outputPath: String) { mMediaRecorder = MediaRecorder().apply { setAudioSource(MediaRecorder.AudioSource.MIC) setVideoSource(MediaRecorder.VideoSource.SURFACE) setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) setOutputFile(outputPath) setVideoEncodingBitRate(10 * 1024 * 1024) // 可根据需求调整码率 setVideoFrameRate(30) setVideoSize(previewWidth, previewHeight) // 与Camera预览分辨率一致 setVideoEncoder(MediaRecorder.VideoEncoder.H264) setAudioEncoder(MediaRecorder.AudioEncoder.AAC) prepare() mRecordSurface = surface } }
3. 修改GL渲染逻辑,同时输出到显示和录制Surface
在帧回调中,处理完扫描扭曲效果后,同时渲染到显示Surface和录制Surface:
videoRenderer.setOnFrameAvailableListener { cameraHandler!!.post { if (mCameraCaptureSession == null) return@post videoRenderer.drawFrame() var videoTexture = videoRenderer.texture if (isScanVideo) { // 保留原扫描扭曲处理逻辑 if (!isf) { scanHeight = pixelHeight * speed } else { scanHeight += pixelHeight * speed } if (scanHeight < 2.0) { var fh = scanHeight if (scanHeight >= 1.0) { scanHeight = 3.0f fh = 1.0f } scanRenderer.drawFrame(videoRenderer.texture, fh) } videoTexture = scanRenderer.texture } isf = isScanVideo // 渲染到显示Surface GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0) GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT or GLES20.GL_COLOR_BUFFER_BIT) GLES20.glViewport(rect.left, rect.top, rect.width(), rect.height()) mRenderer.drawFrame(videoTexture) mEglUtils.swap() // 录制状态下,渲染到录制Surface if (isRecording && mRecordSurface != null) { mEglUtils.makeCurrent(mRecordSurface!!) GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT or GLES20.GL_COLOR_BUFFER_BIT) GLES20.glViewport(0, 0, previewWidth, previewHeight) mRenderer.drawFrame(videoTexture) mEglUtils.swap() mEglUtils.makeCurrent(holder.surface) // 切回显示Surface } } }
4. 实现开始/停止录制的方法
fun startRecording(outputPath: String) { if (isRecording || previewWidth == 0 || previewHeight == 0) return recordHandler.post { try { initMediaRecorder(outputPath) mMediaRecorder?.start() isRecording = true } catch (e: IOException) { e.printStackTrace() releaseMediaRecorder() } } } fun stopRecording() { if (!isRecording) return recordHandler.post { try { mMediaRecorder?.apply { stop() reset() } } catch (e: IllegalStateException) { e.printStackTrace() } finally { releaseMediaRecorder() isRecording = false } } } private fun releaseMediaRecorder() { mMediaRecorder?.release() mMediaRecorder = null mRecordSurface?.release() mRecordSurface = null }
5. 修复资源释放逻辑
在surfaceDestroyed中优先停止录制,再释放Camera及GL资源:
override fun surfaceDestroyed(surfaceHolder: SurfaceHolder) { cameraHandler!!.post { stopRecording() // 先停止录制 if (mCameraCaptureSession != null) { mCameraCaptureSession!!.stopRepeating() mCameraCaptureSession!!.close() mCameraCaptureSession = null } mCameraDevice?.close() mCameraDevice = null GLES20.glDisable(GLES20.GL_BLEND) videoRenderer.release() mRenderer.release() scanRenderer.release() mEglUtils.release() } // 安全停止Camera线程 cameraThread?.quitSafely() try { cameraThread?.join() cameraThread = null cameraHandler = null } catch (e: InterruptedException) { e.printStackTrace() } }
关键注意事项
- 录制分辨率必须与Camera预览分辨率严格匹配,否则会出现画面拉伸或录制失败
- 必须在GL线程中切换渲染Surface,避免线程安全问题
- 停止录制时按顺序:先停止MediaRecorder,再释放资源,最后关闭Camera会话
- 确保申请
RECORD_AUDIO和存储相关权限
内容的提问来源于stack exchange,提问作者Adnan Aslam
相关产品推荐
相关产品推荐

