SurfaceViewHolder.setFixedSize未生效 保存视频尺寸异常拉伸
Android MediaRecorder 1:1宽高比视频录制方案
问题核心原因
原有代码的配置逻辑存在本质错误,导致出现预览拉伸、录制尺寸不符合预期的问题:
SurfaceHolder.setFixedSize()仅修改SurfaceView的渲染缓冲区大小,不会对Camera采集分辨率、MediaRecorder编码参数产生任何影响,因此最终保存的视频仍为设备默认的全屏尺寸- 直接取
supportedPreviewSizes列表第一个元素的逻辑没有做比例校验,设备返回的首个支持尺寸基本不是1:1比例,和Surface实际显示比例不匹配就会出现画面拉伸 - 安卓设备的相机硬件、硬件编码器仅支持厂商预置的固定分辨率列表,绝大多数机型原生不支持直接输出1:1比例的视频流,强行设置不支持的尺寸会直接触发录制崩溃、预览花屏等异常
正确实现步骤
- 第一步:筛选匹配的硬件支持尺寸
分别从Camera.Parameters.getSupportedPreviewSizes()、CamcorderProfile支持的视频尺寸列表中,筛选出同时满足预览、录制两个场景支持,且宽高差最小(最接近1:1)的尺寸,禁止直接取列表第一个元素使用。
尺寸筛选参考逻辑:private fun findClosestSquareSize(sizes: List<Camera.Size>): Camera.Size { var bestSize = sizes[0] var minDelta = Int.MAX_VALUE for (size in sizes) { val delta = Math.abs(size.width - size.height) if (delta < minDelta) { minDelta = delta bestSize = size } } return bestSize } - 第二步:同步配置三层尺寸参数
要保证预览无拉伸、录制尺寸符合预期,必须同时对三个模块做尺寸配置,缺一不可:- 给Camera参数设置选中的预览尺寸,调用
parameters.setPreviewSize(targetSize.width, targetSize.height)后,再通过mCamera.parameters = parameters把参数写回相机 - 给MediaRecorder设置匹配的视频编码尺寸,调用
mediaRecorder.setVideoSize(targetSize.width, targetSize.height),传入的尺寸必须是当前设备相机支持的录制尺寸,否则会启动失败 - 最后调用
holder.setFixedSize(targetSize.width, targetSize.height)设置Surface渲染尺寸,保证渲染层和采集层比例一致
- 给Camera参数设置选中的预览尺寸,调用
- 第三步:补全裁剪逻辑(适配无原生1:1尺寸的机型)
绝大多数消费级安卓设备没有预置1:1比例的采集/编码尺寸,这种场景下不需要强行找1:1尺寸,按以下逻辑处理即可:- 选择一个分辨率大于目标1:1输出尺寸的常规4:3硬件支持尺寸作为采集、编码的基础尺寸
- 调整SurfaceView的布局参数,将预览控件的显示区域裁剪为正方形居中显示,实现预览层的1:1效果
// 把SurfaceView显示区域裁成正方形 val svParams = surfaceView.layoutParams val edgeLen = minOf(surfaceView.measuredWidth, surfaceView.measuredHeight) svParams.width = edgeLen svParams.height = edgeLen surfaceView.layoutParams = svParams- 视频录制完成后,对输出的原始视频做中心裁剪,裁成1:1比例后再保存到公共相册目录
注意:MediaRecorder配置参数有严格的顺序要求,必须按照setCamera→setAudioSource→setVideoSource→setProfile/setOutputFormat→setVideoSize→其余参数→setOutputFile→prepare的顺序调用,否则会抛出IllegalStateException
核心配置参考代码
// 初始化阶段先拿到匹配的尺寸 val parameters = mCamera.parameters val targetPreviewSize = findClosestSquareSize(parameters.supportedPreviewSizes) parameters.setPreviewSize(targetPreviewSize.width, targetPreviewSize.height) // 可自行配置对焦、白平衡等其他相机参数 mCamera.parameters = parameters // 配置MediaRecorder val recorder = MediaRecorder() mCamera.unlock() recorder.setCamera(mCamera) recorder.setAudioSource(MediaRecorder.AudioSource.MIC) recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA) val profile = CamcorderProfile.get(CamcorderProfile.QUALITY_720P) // 替换成支持的录制尺寸 profile.videoFrameWidth = targetPreviewSize.width profile.videoFrameHeight = targetPreviewSize.height recorder.setProfile(profile) recorder.setOutputFile(videoSavePath) // 配置录制角度、最大时长、最大文件大小等参数 recorder.prepare() // 最后配置Surface holder.setFixedSize(targetPreviewSize.width, targetPreviewSize.height) // 启动预览和录制 mCamera.setPreviewDisplay(holder) mCamera.startPreview() recorder.start()
内容的提问来源于stack exchange,提问作者Ahmad
相关产品推荐
相关产品推荐

