Метод ImageReader.onImageAvailable() не вызывается, а превью показывает только 8 первых кадров (Camera2)

Заметил странное поведение на Xiaomi Redmi Note 9 Pro. Я тестировал приложение на сотнях устройств, но эта проблема возникла только на данном устройстве и только когда формат изображения YUV_420_888 и разрешение 176*144 (к примеру, с разрешением 320 * 240 или JPEG или без ImageReader в качестве capture surface все работает корректно). Метод onImageAvailable не вызывается, превью показывает только 8 кадров и зависает, а приложение жутко тормозит. onCaptureCompleted() в CameraCurrentParamsReceiver тоже вызывается всего 8 раз.

Я получаю наименьшее разрешение с помощью getMinPreviewSize (176 * 144 для этого Xiaomi).

const val PREVIEW_IMAGE_FORMAT = ImageFormat.YUV_420_888
const val IMAGE_READER_MAX_SIMULTANEOUS_IMAGES = 4
val previewCaptureCallback = CameraCurrentParamsReceiver(this)

    private fun startPreview(cameraDevice: CameraDevice, cameraProperties: CameraProperties)
    {
        val imageReader = ImageReader.newInstance(cameraProperties.previewSize.width,
                cameraProperties.previewSize.height,
                PREVIEW_IMAGE_FORMAT,
                IMAGE_READER_MAX_SIMULTANEOUS_IMAGES)
    
        this.imageReader = imageReader
        bufferedImageConverter = BufferedImageConverter(cameraProperties.previewSize.width, cameraProperties.previewSize.height)
    
        val previewSurface = previewSurface
        val previewSurfaceForCamera =
                if (previewSurface != null)
                {
                    if (previewSurface.isValid)
                    {
                        previewSurface
                    }
                    else
                    {
                        Log.w(TAG, "Invalid preview surface - camera preview display is not available")
                        null
                    }
                }
                else
                {
                    null
                }
    
        val captureSurfaces = listOfNotNull(imageReader.surface, previewSurfaceForCamera)
    
        cameraDevice.createCaptureSession(
                captureSurfaces,
                object : CameraCaptureSession.StateCallback()
                {
                    override fun onConfigureFailed(cameraCaptureSession: CameraCaptureSession)
                    {
                        Log.e(TAG, "onConfigureFailed() cannot configure camera")
                        if (isCameraOpened(cameraDevice))
                        {
                            shutDown("onConfigureFailed")
                        }
                    }
    
                    override fun onConfigured(cameraCaptureSession: CameraCaptureSession)
                    {
                        Log.d(TAG, "onConfigured()")
    
                        if (!isCameraOpened(cameraDevice))
                        {
                            cameraCaptureSession.close()
                            shutDown("onConfigured.isCameraOpened")
    
                            return
                        }
    
                        captureSession = cameraCaptureSession
                        try
                        {
                            val request = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
                            captureSurfaces.forEach { request.addTarget(it) }
                            CameraPreviewRequestInitializer.initializePreviewRequest(request, cameraProperties, controlParams, isControlParamsStrict)
                            captureRequestBuilder = request
    
                            val previewCallback = PreviewFrameHandler(this@Camera2)
                            [email protected] = previewCallback
                            imageReader.setOnImageAvailableListener(previewCallback, previewCallback.backgroundHandler)
                            
                            cameraCaptureSession.setRepeatingRequest(request.build(), previewCaptureCallback, null)
                        }
                        catch (ex: CameraAccessException)
                        {
                            Log.e(TAG, "onConfigured() failed with exception", ex)
    
                            shutDown("onConfigured.CameraAccessException")
                        }
                    }
                },
                null)
    }
    
    private fun chooseCamera(manager: CameraManager): CameraProperties?
    {
        val cameraIdList = manager.cameraIdList
        if (cameraIdList.isEmpty())
        {
            return null
        }
    
        for (cameraId in cameraIdList)
        {
            val characteristics = manager.getCameraCharacteristics(cameraId)
            val facing = characteristics.get(CameraCharacteristics.LENS_FACING)
            if (facing != null && facing == CameraCharacteristics.LENS_FACING_BACK)
            {
                val minPreviewSize = getMinPreviewSize(characteristics)
                if (minPreviewSize == null)
                {
                    Log.e(TAG, "chooseCamera() Cannot determine the preview size")
    
                    return null
                }
    
                Log.d(TAG, "chooseCamera() chosen camera id: $cameraId, preview size: $minPreviewSize")
    
                return CameraProperties(cameraId,
                        minPreviewSize,
                        characteristics)
            }
        }
    
        return null
    }
    
    private fun getMinPreviewSize(characteristics: CameraCharacteristics): Size?
    {
        val map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
        if (map == null)
        {
            Log.e(TAG, "getMinPreviewSize() Map is empty")
    
            return null
        }
    
        return map.getOutputSizes(Constants.Camera.PREVIEW_IMAGE_FORMAT)?.minBy { it.width * it.height }
    }

PreviewFrameHandler и CameraCurrentParamsReceiver (переменная previewCaptureCallback)

private class PreviewFrameHandler(private val parent: Camera2) : ImageReader.OnImageAvailableListener, Handler.Callback
{
    val backgroundHandler: Handler
    private val backgroundHandlerThread: HandlerThread = HandlerThread("Camera2.PreviewFrame.HandlerThread")
    private val mainHandler: Handler = Handler(Looper.getMainLooper(), this)

    /**
     * Main thread.
     */

    init
    {
        backgroundHandlerThread.start()
        backgroundHandler = Handler(backgroundHandlerThread.looper)
    }

    fun shutDown()
    {
        backgroundHandlerThread.quit()
        mainHandler.removeMessages(0)
    }

    override fun handleMessage(msg: Message?): Boolean
    {
        msg ?: return false

        parent.cameraFrameListener.onFrame(msg.obj as RGBImage)

        return true
    }

    /**
     * Background thread.
     */

    private val relativeTimestamp = RelativeTimestamp()

    override fun onImageAvailable(reader: ImageReader)
    {
        var image: Image? = null
        try
        {
            image = reader.acquireNextImage()
            image ?: return

            val rgbImage = parent.bufferedImageConverter?.convertYUV420spToRGB(image, relativeTimestamp.updateAndGetSeconds(image.timestamp))
            rgbImage ?: return

            mainHandler.sendMessage(mainHandler.obtainMessage(0, rgbImage))
        }
        catch (ex: Exception)
        {
            Log.e(TAG, "onImageAvailable()", ex)
        }
        finally
        {
            image?.close()
        }
    }

    private class RelativeTimestamp
    {
        private var initialNanos = 0L

        fun updateAndGetSeconds(currentNanos: Long): Double
        {
            if (initialNanos == 0L)
            {
                initialNanos = currentNanos
            }

            return nanosToSeconds(currentNanos - initialNanos)
        }
    }
}

/**
 * Class used to read current camera params.
 */
private class CameraCurrentParamsReceiver(private val parent: Camera2) : CameraCaptureSession.CaptureCallback()
{
    private var isExposureTimeExceptionLogged = false
    private var isIsoExceptionLogged = false

    override fun onCaptureSequenceAborted(session: CameraCaptureSession, sequenceId: Int)
    {
    }

    override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult)
    {
        try
        {
            val exposureTimeNanos = result.get(CaptureResult.SENSOR_EXPOSURE_TIME)
            if (exposureTimeNanos != null)
            {
                parent.currentExposureTimeNanos = exposureTimeNanos
            }
        }
        catch (ex: IllegalArgumentException)
        {
            if (!isExposureTimeExceptionLogged)
            {
                isExposureTimeExceptionLogged = true
            }
        }

        try
        {
            val iso = result.get(CaptureResult.SENSOR_SENSITIVITY)
            if (iso != null)
            {
                parent.currentIso = iso
            }
        }
        catch (ex: IllegalArgumentException)
        {
            if (!isIsoExceptionLogged)
            {
                Log.i(TAG, "Cannot get current SENSOR_SENSITIVITY, exception: " + ex.message)
                isIsoExceptionLogged = true
            }
        }
    }

    override fun onCaptureFailed(session: CameraCaptureSession, request: CaptureRequest, failure: CaptureFailure)
    {
    }

    override fun onCaptureSequenceCompleted(session: CameraCaptureSession, sequenceId: Int, frameNumber: Long)
    {
    }

    override fun onCaptureStarted(session: CameraCaptureSession, request: CaptureRequest, timestamp: Long, frameNumber: Long)
    {
    }

    override fun onCaptureProgressed(session: CameraCaptureSession, request: CaptureRequest, partialResult: CaptureResult)
    {
    }

    override fun onCaptureBufferLost(session: CameraCaptureSession, request: CaptureRequest, target: Surface, frameNumber: Long)
    {
    }
}

Как я понимаю что-то не так с размером превью, но я не знаю как получить правильное значение. Самое странное что данная проблема возникла только с этим Xiaomi устройством.


Ответы (0 шт):