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

如何录制基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 06:37:10