Camera 预览变形问题解决
最近开发一款自定义相机采集照片的demo,花了一个上午开发了一个在测试机上功能正常的apk连同测试机一起交付(需求方反馈没有Android设备),然而晚上被喊去说是在华为畅玩某型号上预览会变形,拍到的图片边界都移位了,只要加个班处理一下机型适配的问题。根据开发经验,防止预览图像变形的终极奥义就是保持照相机硬件支持的某个size(每个特定的手机都有其支持的camera分辨率)、屏幕宽高比、界面上SurfaceView的宽高比尽量一致,同时保持拍照图与预览图分辨率一致,拍出来的照片就和预览看到的相同。
下面给出实现代码:
private fun getOptimalPreviewSize(sizes: List<Camera.Size>?, w: Int, h: Int): Camera.Size? { val ASPECT_TOLERANCE = 0.1 val targetRatio = h.toDouble() / w if (sizes == null) return null var optimalSize: Camera.Size? = null var minDiff = java.lang.Double.MAX_VALUE for (size in sizes) { val ratio = size.width.toDouble() / size.height if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue if (Math.abs(size.height - h) < minDiff) { optimalSize = size minDiff = Math.abs(size.height - h).toDouble() } } if (optimalSize == null) { minDiff = java.lang.Double.MAX_VALUE for (size in sizes) { if (Math.abs(size.height - h) < minDiff) { optimalSize = size minDiff = Math.abs(size.height - h).toDouble() } } } return optimalSize }
其中第一个参数sizes是camera支持的预览size列表,第二个是View的宽,第三个是View的高,由于我这里的surfaceview是全屏的,所以传入的宽和高可以是屏幕的宽和高
下面给出完整代码:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) screenWidth = ScreenUtil.getWindowWidth(this) screenHeight = ScreenUtil.getWindowHeigh(this) camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK) surface.holder.addCallback(this) } override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) { camera?.setPreviewDisplay(holder) camera?.startPreview() } override fun surfaceDestroyed(holder: SurfaceHolder?) { } override fun surfaceCreated(holder: SurfaceHolder?) { if (camera == null) { camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK) } val parameters = camera?.parameters var sizes = parameters!!.supportedPreviewSizes var preSize = getOptimalPreviewSize(sizes, screenWidth, screenHeight) parameters.setPreviewSize(preSize!!.width, preSize.height) //重新给定surfaceView宽高 surface.resize(preSize.width, preSize.height) if (parameters.supportedFocusModes!!.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { parameters.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO } parameters.sceneMode = Camera.Parameters.SCENE_MODE_AUTO //保持picturesize与presize一致 parameters.setPictureSize(preSize.width, preSize.height) parameters.previewFormat = ImageFormat.NV21 camera?.parameters = parameters camera?.setPreviewDisplay(holder) camera?.startPreview() } private fun getOptimalPreviewSize(sizes: List<Camera.Size>?, w: Int, h: Int): Camera.Size? { val ASPECT_TOLERANCE = 0.1 val targetRatio = h.toDouble() / w if (sizes == null) return null var optimalSize: Camera.Size? = null var minDiff = java.lang.Double.MAX_VALUE for (size in sizes) { val ratio = size.width.toDouble() / size.height if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue if (Math.abs(size.height - h) < minDiff) { optimalSize = size minDiff = Math.abs(size.height - h).toDouble() } } if (optimalSize == null) { minDiff = java.lang.Double.MAX_VALUE for (size in sizes) { if (Math.abs(size.height - h) < minDiff) { optimalSize = size minDiff = Math.abs(size.height - h).toDouble() } } } return optimalSize } override fun onWindowFocusChanged(hasFocus: Boolean) { val decorView = this@CameraActivity.window.decorView decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) } override fun onPause() { super.onPause() camera?.stopPreview() } override fun onDestroy() { super.onDestroy() camera?.setPreviewCallback(null) camera?.release() }