相机扭曲的解决办法
发生相机扭曲的主要原因是surfaceview的宽高比与相机输出图像的最佳宽高比不一致,相机输出图像适配surfaceview的时候被拉伸导致的扭曲。
步骤1:获取相机支持的输出比例
kotlin
// 在Fragment中初始化预览尺寸
private fun initPreviewSize() {
val characteristics = cameraManager.getCameraCharacteristics(cameraId)
previewSize = getPreviewOutputSize(
binding.surfaceView.display,
characteristics,
SurfaceHolder::class.java
)
}
// 核心尺寸选择算法
private fun getPreviewOutputSize(
display: Display?,
characteristics: CameraCharacteristics,
targetClass: Class<*>
): Size {
// 1. 获取相机支持的所有分辨率
val config = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
val outputSizes = config.getOutputSizes(targetClass)
// 2. 计算显示区域宽高比
val displaySize = Point().apply { display?.getSize(this) }
val displayRatio = displaySize.x.toFloat() / displaySize.y
// 3. 选择最接近屏幕比例的相机分辨率
return outputSizes.minByOrNull {
abs((it.width.toFloat()/it.height) - displayRatio)
} ?: outputSizes.maxByOrNull { it.width * it.height }!!
}
原理说明:通过比较相机支持的分辨率宽高比与屏幕显示比例的差异,选择最匹配的预览尺寸
步骤2:设置视图动态宽高比
kotlin
// 在SurfaceView创建时绑定比例
private fun setAspectRatio() {
binding.surfaceView.setAspectRatio(previewSize.width, previewSize.height)
}
// AutoFitSurfaceView核心实现
class AutoFitSurfaceView(...) : SurfaceView(...) {
private var aspectRatio = 0f
fun setAspectRatio(width: Int, height: Int) {
aspectRatio = width.toFloat() / height
requestLayout()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val width = MeasureSpec.getSize(widthMeasureSpec)
val height = MeasureSpec.getSize(heightMeasureSpec)
// 动态计算适配尺寸
when {
aspectRatio == 0f -> super.onMeasure(...)
width > height -> handleLandscape(width, height)
else -> handlePortrait(width, height)
}
}
private fun handleLandscape(width: Int, height: Int) {
val expectedHeight = (width / aspectRatio).roundToInt()
setMeasuredDimension(width, expectedHeight.coerceAtMost(height))
}
private fun handlePortrait(width: Int, height: Int) {
val expectedWidth = (height * aspectRatio).roundToInt()
setMeasuredDimension(expectedWidth.coerceAtMost(width), height)
}
}
核心机制:基于相机分辨率比例动态计算视图尺寸,保证画面始终居中完整显示
步骤3:处理设备方向变化
kotlin
// 在Fragment中监听方向变化
private val orientationListener = object : OrientationEventListener(context) {
override fun onOrientationChanged(orientation: Int) {
val newRotation = when {
orientation in 45..135 -> Surface.ROTATION_270
orientation in 135..225 -> Surface.ROTATION_180
orientation in 225..315 -> Surface.ROTATION_90
else -> Surface.ROTATION_0
}
if (newRotation != lastRotation) {
adjustPreviewForRotation(newRotation)
}
}
}
private fun adjustPreviewForRotation(rotation: Int) {
// 1. 重新计算传感器方向
val sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!!
val totalRotation = (sensorOrientation + rotation) % 360
// 2. 更新预览方向
captureSession?.setDisplayOrientation(totalRotation)
// 3. 重新设置视图比例
setAspectRatio()
}
方向适配逻辑:
- 计算传感器物理方向与设备旋转的合成方向
- 通过
setDisplayOrientation
同步预览方向 - 强制重绘视图保持比例正确
步骤4:建立预览会话时验证参数
kotlin
private fun createCaptureSession() {
val targets = listOf(surfaceView.holder.surface)
cameraDevice?.createCaptureSession(targets, object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
// 验证实际预览尺寸
val sessionParams = session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
val activeSize = sessionParams.surface?.let {
it.getSize() // 实际生效的分辨率
}
if (activeSize != previewSize) {
Log.w(TAG, "实际预览尺寸($activeSize)与预期($previewSize)不符")
adjustPreviewSize(activeSize)
}
}
}, null)
}
尺寸验证机制:在会话建立后检查实际生效的预览尺寸,必要时动态调整
步骤5:异常情况处理方案
kotlin
// 尺寸不匹配时的自动修复
private fun adjustPreviewSize(actualSize: Size) {
// 1. 关闭当前会话
closeCamera()
// 2. 更新目标尺寸
previewSize = actualSize
// 3. 重新打开相机
openCamera(cameraId).also {
binding.surfaceView.setAspectRatio(actualSize.width, actualSize.height)
}
}
// 在AutoFitSurfaceView中添加保护逻辑
override fun setAspectRatio(width: Int, height: Int) {
if (width == 0 || height == 0) {
throw IllegalArgumentException("无效的尺寸参数: ${width}x$height")
}
aspectRatio = width.toFloat() / height
}
容错机制:
- 自动检测尺寸偏差
- 异常参数校验
- 安全重连机制
实施效果验证
测试用例1:基础比例适配
设备类型 | 相机分辨率 | 屏幕比例 | 预期效果 |
---|---|---|---|
16:9手机竖屏 | 1920x1080 | 9:16 | 上下黑边,无拉伸 |
4:3平板横屏 | 1280x720 | 4:3 | 左右黑边,比例正确 |
测试用例2:动态方向适配
操作步骤 | 预期效果 |
---|---|
竖屏启动后旋转至横屏 | 画面自动旋转并保持比例 |
快速连续旋转设备 | 画面平滑过渡无撕裂 |
测试用例3:异常场景恢复
异常类型 | 系统响应 |
---|---|
相机返回非常规尺寸 | 自动调整视图比例 |
参数传0触发异常 | 抛出明确错误信息 |
通过以上五步实施方案,可系统性地解决Android相机预览画面扭曲问题。建议配合GPU呈现模式分析工具实时监控渲染性能,确保方案在不同设备上的兼容性。