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

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
    }
    
  • 第二步:同步配置三层尺寸参数
    要保证预览无拉伸、录制尺寸符合预期,必须同时对三个模块做尺寸配置,缺一不可:
    1. 给Camera参数设置选中的预览尺寸,调用parameters.setPreviewSize(targetSize.width, targetSize.height)后,再通过mCamera.parameters = parameters把参数写回相机
    2. 给MediaRecorder设置匹配的视频编码尺寸,调用mediaRecorder.setVideoSize(targetSize.width, targetSize.height),传入的尺寸必须是当前设备相机支持的录制尺寸,否则会启动失败
    3. 最后调用holder.setFixedSize(targetSize.width, targetSize.height)设置Surface渲染尺寸,保证渲染层和采集层比例一致
  • 第三步:补全裁剪逻辑(适配无原生1:1尺寸的机型)
    绝大多数消费级安卓设备没有预置1:1比例的采集/编码尺寸,这种场景下不需要强行找1:1尺寸,按以下逻辑处理即可:
    1. 选择一个分辨率大于目标1:1输出尺寸的常规4:3硬件支持尺寸作为采集、编码的基础尺寸
    2. 调整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: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 09:54:22