MediaRecorder采用GLSurfaceView的SurfaceTexture作预览时帧率骤降问题
我之前做类似的视频录制功能时也踩过这个坑,咱们来一步步拆解问题的成因和解决办法:
问题成因分析
- 旧Camera API的预览目标限制:如果你用的是
android.hardware.Camera这个旧版API,它只能设置一个预览输出目标——要么把帧发给GLSurfaceView的SurfaceTexture,要么发给MediaRecorder的Surface。你现在的做法相当于让两个组件同时抢Camera的帧资源,结果就是MediaRecorder启动后优先占用了大部分帧,GLSurfaceView的渲染线程拿不到足够的帧更新,自然帧率暴跌甚至静止。 - SurfaceTexture的单消费者特性:
SurfaceTexture的帧数据默认只能被一个消费者有效处理。当你把它创建的Surface传给MediaRecorder后,MediaRecorder会成为主要的帧消费者,GLSurfaceView那边就只能断断续续拿到残留的帧,预览就卡成了静止画面。 - 编码与渲染的资源抢占:MediaRecorder启动后会占用大量CPU、GPU资源做视频编码,如果你的GLSurfaceView还有复杂的渲染逻辑,两个高负载任务抢资源,也会进一步拖慢预览帧率。
针对性解决办法
根据你使用的Camera API版本,分两种场景处理:
场景1:继续使用旧版android.hardware.Camera API
方案A:帧转发(推荐,预览连续性好)
让Camera只把帧输出给GLSurfaceView的SurfaceTexture,然后在GL渲染线程里,把渲染好的帧再转发给MediaRecorder的Surface:
- 给MediaRecorder创建独立的
Surface(可以用MediaRecorder.getSurface(),API 21+支持)。 - 在GLSurfaceView的
Renderer的onDrawFrame方法中,先完成预览帧的渲染,再通过离屏渲染(FBO)或者切换渲染目标的方式,把当前帧绘制到MediaRecorder的Surface对应的纹理上。 - 注意要处理好GL上下文的切换,避免线程冲突。
方案B:临时切换预览目标(实现简单,有短暂卡顿)
录制开始时把Camera的预览目标切给MediaRecorder,结束后切回GLSurfaceView:
// 录制启动逻辑 mCamera.stopPreview(); Surface mediaRecorderSurface = new Surface(glSurfaceViewInstance.getSurfaceTexture()); mMediaRecorder.setPreviewDisplay(mediaRecorderSurface); mCamera.setPreviewDisplay(mediaRecorderSurface); // 确保和MediaRecorder用同一个Surface mCamera.startPreview(); mMediaRecorder.start(); // 录制停止逻辑 mMediaRecorder.stop(); mCamera.stopPreview(); mCamera.setPreviewTexture(glSurfaceViewInstance.getSurfaceTexture()); mCamera.startPreview();
这种方法的缺点是切换瞬间会有预览中断,适合对预览连续性要求不高的场景。
场景2:迁移到android.hardware.camera2 API(长期最优解)
Camera2 API原生支持同时把Camera的帧输出到多个Surface,完美解决资源冲突问题:
- 创建
CameraCaptureSession时,把GLSurfaceView的Surface和MediaRecorder的Surface都加入输出列表。 - 配置
CaptureRequest,让Camera同时向这两个Surface输出帧。这样GLSurfaceView的预览和MediaRecorder的录制可以并行进行,互不干扰。
通用优化小技巧
- 匹配编码与预览参数:让MediaRecorder的视频分辨率、帧率和GLSurfaceView的渲染尺寸一致,比如
mMediaRecorder.setVideoSize(previewWidth, previewHeight),避免编码时额外的缩放转换消耗资源。 - 简化GL渲染逻辑:去掉
onDrawFrame里不必要的计算和绘制操作,减少GPU占用,给视频编码留出足够资源。 - 后台线程处理MediaRecorder操作:把MediaRecorder的初始化、启动、停止等操作放到后台线程执行,别阻塞UI线程或GL渲染线程。
内容的提问来源于stack exchange,提问作者Gensoukyou1337
相关产品推荐
相关产品推荐

