Android Camera Preview 在 SurfaceView 上失真



我实现了相机预览活动:

package ai.my.mysdktest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import android.Manifest
import android.content.Context
import android.graphics.ImageFormat
import android.graphics.ImageFormat.YUV_420_888
import android.graphics.PointF
import android.graphics.Rect
import android.hardware.camera2.*
import android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW
import android.media.ImageReader
import android.provider.Settings
import android.widget.Toast
import android.os.Handler
import android.util.Log
import android.hardware.camera2.CaptureRequest
import android.util.Size
import android.view.*
import java.nio.ByteBuffer
import ai.my.mysdk.CameraPermissionHelper
import android.graphics.Camera
import kotlinx.android.synthetic.main.activity_camera2.*



class CameraActivity2 : AppCompatActivity() {
private val TAG: String = CameraActivity::class.java.simpleName
lateinit var camera: CameraDevice
private lateinit var cameraManager: CameraManager
lateinit var cameraCharacteristics: CameraCharacteristics
lateinit var captureSession: CameraCaptureSession
lateinit var previewRequestBuilder: CaptureRequest.Builder
lateinit var captureCallback: CameraCaptureSession.StateCallback
//Zooming
var fingerSpacing: Float = 0F
var zoomLevel: Float = 1F
private var maximumZoomLevel: Float = 1F
private var zoom: Rect = Rect()

lateinit var imageReader: ImageReader
//lateinit var interpreter: Interpreter
lateinit var imgData: ByteBuffer

lateinit var previewSurface: Surface
lateinit var recordingSurface: Surface

lateinit var previewSize: Size
lateinit var decodeSize: Size
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE)
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
setContentView(R.layout.activity_camera)
cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager

surfaceView.holder.addCallback(surfaceReadyCallback)
if (!CameraPermissionHelper.hasCameraPermission(this)) {
CameraPermissionHelper.requestCameraPermission(this)
return
}

}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
Log.d(TAG, "")
if (!CameraPermissionHelper.hasCameraPermission(this)) {
Toast.makeText(this, "Camera permission is needed to run this application", Toast.LENGTH_LONG)
.show()
if (!CameraPermissionHelper.shouldShowRequestPermissionRationale(this)) {
// Permission denied with checking "Do not ask again".
CameraPermissionHelper.launchPermissionSettings(this)
}
finish()
}
recreate()
}

override fun onTouchEvent(event: MotionEvent): Boolean {
try {
val rect = cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)
?: return false
val currentFingerSpacing: Float
if (event.pointerCount == 2) { //Multi touch.
currentFingerSpacing = event.getFingerSpacing()
var delta = 0.05f //Control this value to control the zooming sensibility
if (fingerSpacing != 0F) {
if (currentFingerSpacing > fingerSpacing) { //Don't over zoom-in
if (maximumZoomLevel - zoomLevel <= delta) {
delta = maximumZoomLevel - zoomLevel
}
zoomLevel += delta
} else if (currentFingerSpacing < fingerSpacing) { //Don't over zoom-out
if (zoomLevel - delta < 1f) {
delta = zoomLevel - 1f
}
zoomLevel -= delta
}
val ratio = 1.toFloat() / zoomLevel //This ratio is the ratio of cropped Rect to Camera's original(Maximum) Rect
//croppedWidth and croppedHeight are the pixels cropped away, not pixels after cropped
val croppedWidth = rect.width() - Math.round(rect.width() * ratio)
val croppedHeight = rect.height() - Math.round(rect.height() * ratio)
//Finally, zoom represents the zoomed visible area
zoom = Rect(croppedWidth / 2, croppedHeight / 2,
rect.width() - croppedWidth / 2, rect.height() - croppedHeight / 2)
previewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom)
}
fingerSpacing = currentFingerSpacing
} else { //Single touch point, needs to return true in order to detect one more touch point
return true
}
captureSession.setRepeatingRequest(previewRequestBuilder.build(), object: CameraCaptureSession.CaptureCallback() {}, Handler { true })
return true
} catch (e: Exception) {
//Error handling up to you
return true
}
}
private fun MotionEvent.getFingerSpacing(): Float {
val x = getX(0) - getX(1)
val y = getY(0) - getY(1)
return Math.sqrt((x * x + y * y).toDouble()).toFloat()
}

@SuppressLint("MissingPermission")
private fun startCameraSession() {
with (cameraManager) {
if (cameraIdList.isEmpty()) {
//toast("You need a camera to use this app.")
return
}
val firstCamera = cameraIdList[0]
openCamera(firstCamera, object: CameraDevice.StateCallback() {
override fun onDisconnected(p0: CameraDevice) {
Log.d(TAG, "Camera is Disconnected")
}

override fun onError(p0: CameraDevice, p1: Int) {
Log.d(TAG, "Camera Error: $p1")
}
override fun onOpened(cam: CameraDevice) {
camera = cam
cameraCharacteristics = getCameraCharacteristics(camera.id)
val configMap = cameraCharacteristics[CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP]
cameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM)?.let {
maximumZoomLevel = it * 10
}

if (configMap == null) {
//toast("Could not configure your camera for use with this application.")
Log.d(TAG,"No config map for camera: ${camera.id}")
return
}
val yuvSizes = configMap.getOutputSizes(ImageFormat.YUV_420_888)
if (yuvSizes.isEmpty()) {
//toast("Could not configure your camera for use with this application.")
Log.d(TAG,"No sizes found for format YUV")
return
}
/* Beautiful preview */
previewSize = yuvSizes.first()
decodeSize = yuvSizes.first()
val displayRotation = windowManager.defaultDisplay.rotation
val swappedDimensions = areDimensionsSwapped(displayRotation = displayRotation)
val rotatedPreviewWidth = if (swappedDimensions) previewSize.height else previewSize.width
val rotatedPreviewHeight = if (swappedDimensions) previewSize.width else previewSize.height

surfaceView.holder.setSizeFromLayout()// setFixedSize(rotatedPreviewWidth, rotatedPreviewHeight)
// Configure Image Reader
imageReader = ImageReader.newInstance(rotatedPreviewWidth, rotatedPreviewHeight, YUV_420_888, 10)

imageReader.setOnImageAvailableListener(
{ imageReader ->
val image = imageReader.acquireLatestImage()
Log.d(TAG, "Image Available: H (${image?.height}), W (${image?.width})")
Log.d(TAG, "Surface Size: H (${surfaceView.height}), W (${surfaceView.width})")
image?.close()
}, Handler {true})
previewSurface = surfaceView.holder.surface
recordingSurface = imageReader.surface

captureCallback = object : CameraCaptureSession.StateCallback() {
override fun onConfigureFailed(session: CameraCaptureSession) { }
override fun onConfigured(session: CameraCaptureSession) {
previewRequestBuilder = camera.createCaptureRequest(TEMPLATE_PREVIEW).apply {
addTarget(recordingSurface)
addTarget(previewSurface)
}
session.setRepeatingRequest(previewRequestBuilder.build(), object: CameraCaptureSession.CaptureCallback() {}, Handler { true })
captureSession = session
}
}
camera.createCaptureSession(mutableListOf(previewSurface, recordingSurface), captureCallback, Handler { true })
//val hasSeenFTX = sharedPreferences.getBoolean(SHARED_PREFERENCES_FTX_KEY, false)
//if (!hasSeenFTX) {
//    showFirstTooltip()
//}
}
}, Handler { true })
}
}
private fun areDimensionsSwapped(displayRotation: Int): Boolean {
var swappedDimensions = false
when (displayRotation) {
Surface.ROTATION_0, Surface.ROTATION_180 -> {
if (cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) == 90 || cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) == 270) {
swappedDimensions = true
}
}
Surface.ROTATION_90, Surface.ROTATION_270 -> {
if (cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) == 0 || cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) == 180) {
swappedDimensions = true
}
}
else -> {
Log.d(TAG, "Display rotation is invalid: $displayRotation")
}
}
return swappedDimensions
}
private val surfaceReadyCallback = object: SurfaceHolder.Callback {
override fun surfaceChanged(p0: SurfaceHolder?, p1: Int, p2: Int, p3: Int) {
Log.d(TAG, "Surface Changed ${p0?.surface.toString()}")
}
override fun surfaceDestroyed(p0: SurfaceHolder?) {
Log.d(TAG, "Surface Destroyed ${p0?.surface.toString()}")
}
override fun surfaceCreated(p0: SurfaceHolder?) {
startCameraSession()
}
}
/**
* Decode image to [PixelArray]
*/
/*
private val decodeImageToPixels = imageReader. .OnImageAvailableListener()  { imageReader ->
val image  = imageReader.acquireLatestImage()
image?.close()
Log.i(TAG, "Image Available: ${image.timestamp}")
}*/

}

该活动具有以下布局:

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000"
tools:context=".CameraActivity2" >
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>

图像如我预期的那样显示在屏幕上,但是存在大量失真。当手机纵向时,图像显示为拉伸,然后在手机旋转时被压扁。

由于我的调试日志,我可以看到预览大小与surfaceView的大小不匹配:

Image Available: H (1080), W (1920)
Surface Size: H (2094), W (1080)

如何使预览图像与我的 Surface 显示器匹配并停止失真?

您是否尝试过使用SurfaceView,它可以使其大小适应预览尺寸,例如:https://github.com/android/camera-samples/blob/master/CameraUtils/lib/src/main/java/com/example/android/camera/utils/AutoFitSurfaceView.kt?

最新更新