1

So I'm working on an app that requires QR scanner as a main feature. Previously I was using camerax-alpha06 with Firebase ML vision 24.0.3 and they were working fine for months, no customer complaints about scanning issues.

Then about two weeks ago I had to change Firebase ML vision to MLKit barcode scanning (related to the Crashlytics migration - out of topic) and now some of the users who could scan in the previous version now could not. Some sample devices be Samsung Tab A7 (Android 5.1.1) and Vivo 1919 (Android 10)

This is my build.gradle section that involves this feature

 def camerax_version = "1.0.0-beta11"
    implementation "androidx.camera:camera-core:${camerax_version}"
    implementation "androidx.camera:camera-camera2:${camerax_version}"
    implementation "androidx.camera:camera-lifecycle:${camerax_version}"
    implementation "androidx.camera:camera-view:1.0.0-alpha18"
    implementation "androidx.camera:camera-extensions:1.0.0-alpha18"
    implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:16.1.2'

This is my camera handler file

class ScanQRCameraViewHandler(
    private val fragment: ScanQRDialogFragment,
    private val previewView: PreviewView
) {
    private val displayLayout get() = previewView
    companion object {
        private const val RATIO_4_3_VALUE = 4.0 / 3.0
        private const val RATIO_16_9_VALUE = 16.0 / 9.0
    }

    private val analyzer = GMSMLKitAnalyzer(onFoundQR = { extractedString ->
        fragment.verifyExtractedString(extractedString)
    }, onNotFoundQR = {
        resetStateToAllowNewImageStream()
    })
    private var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>? = null
    private var camera: Camera? = null
    private var isAnalyzing = false

    internal fun resetStateToAllowNewImageStream() {
        isAnalyzing = false
    }

    internal fun setTorceEnable(isEnabled: Boolean) {
        camera?.cameraControl?.enableTorch(isEnabled)
    }

    internal fun initCameraProviderIfHasNot() {
        if (cameraProviderFuture == null) {
            fragment.context?.let {
                cameraProviderFuture = ProcessCameraProvider.getInstance(it)
                val executor = ContextCompat.getMainExecutor(it)
                cameraProviderFuture?.addListener({
                    bindPreview(cameraProviderFuture?.get(), executor)
                }, executor)
            }
        }
    }

    private fun bindPreview(cameraProvider: ProcessCameraProvider?, executor: Executor) {
        val metrics = DisplayMetrics().also { displayLayout.display.getRealMetrics(it) }
        val screenAspectRatio = aspectRatio(metrics.widthPixels, metrics.heightPixels)

        val preview = initPreview(screenAspectRatio)
        val imageAnalyzer = createImageAnalyzer()
        val imageAnalysis = createImageAnalysis(executor, imageAnalyzer, screenAspectRatio)
        val cameraSelector = createCameraSelector()

        cameraProvider?.unbindAll()
        camera = cameraProvider?.bindToLifecycle(
            fragment as LifecycleOwner,
            cameraSelector, imageAnalysis, preview
        )
    }

    private fun createCameraSelector(): CameraSelector {
        return CameraSelector.Builder()
            .requireLensFacing(CameraSelector.LENS_FACING_BACK)
            .build()
    }

    private fun createImageAnalysis(
        executor: Executor, imageAnalyzer: ImageAnalysis.Analyzer, screenAspectRatio: Int
    ): ImageAnalysis {
        val rotation = displayLayout.rotation
        val imageAnalysis = ImageAnalysis.Builder()
//            .setTargetRotation(rotation.toInt())
//            .setTargetAspectRatio(screenAspectRatio)
            .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
            .build()

        imageAnalysis.setAnalyzer(executor, imageAnalyzer)
        return imageAnalysis
    }

    private fun createImageAnalyzer(): ImageAnalysis.Analyzer {
        return ImageAnalysis.Analyzer {
            isAnalyzing = true
            analyzer.analyze(it)
        }
    }

    private fun initPreview(screenAspectRatio: Int): Preview {

        val preview: Preview = Preview.Builder()
            //.setTargetResolution(Size(840, 840))
          //  .setTargetAspectRatio(screenAspectRatio)
          //  .setTargetRotation(displayLayout.rotation.toInt())
            .build()
        preview.setSurfaceProvider(previewView.surfaceProvider)
        return preview
    }

    fun unbindAll() {
        cameraProviderFuture?.get()?.unbindAll()
    }


    private fun aspectRatio(width: Int, height: Int): Int {
        val previewRatio = width.coerceAtLeast(height).toDouble() / width.coerceAtMost(height)
        if (kotlin.math.abs(previewRatio - RATIO_4_3_VALUE) <= kotlin.math.abs(previewRatio - RATIO_16_9_VALUE)) {
            return AspectRatio.RATIO_4_3
        }
        return AspectRatio.RATIO_16_9
    }
}

And my analyzer

internal class GMSMLKitAnalyzer(
    private val onFoundQR: (String) -> Unit,
    private val onNotFoundQR: () -> Unit
) :
    ImageAnalysis.Analyzer {

    private val options = BarcodeScannerOptions.Builder()
        .setBarcodeFormats(Barcode.FORMAT_QR_CODE).build()

    @SuppressLint("UnsafeExperimentalUsageError")
    override fun analyze(imageProxy: ImageProxy) {
        imageProxy.image?.let { mediaImage ->
            val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
            val scanner = BarcodeScanning.getClient(options)
            CoroutineScope(Dispatchers.Main).launch {
                val result = scanner.process(image).await()
                result.result?.let { barcodes ->
                    barcodes.find { it.rawValue != null }?.rawValue?.let {
                        onFoundQR(it)
                    } ?: run { onNotFoundQR() }
                }
                imageProxy.close()
            }
        } ?: imageProxy.close()

    }
}

The commented out lines are what I've tried to add and didn't help, some even caused issues on other (used-to-be-working) devices.

I am unsure if I misconfigure anything or not, so I would like any suggestions that would help me find the solution.

Thank you

P.S. This is my first post so if I've done anything wrong or missed something please advise.

2 Answers2

2

BarcodeScanning does not work on some devices running with camera-camera2:1.0.0-beta08 version or later. You can use an earlier version of camera-camera2 to bypass this issue. For example:

See: https://developers.google.com/ml-kit/known-issues We are working on fix internally in MLKit for the next SDK release.

Chenxi Song
  • 557
  • 3
  • 6
  • Thank you for clarifying this @chenxi . Do you maybe have approximate timeline of when the fix would be release? So I can see if I should rollback or wait for the new one. – Kamolwan Kunanusont Nov 14 '20 at 04:47
  • We plan to release the new sdk in mid Dec 2020. Please be aware that this is planned but not landed yet:) – Chenxi Song Nov 18 '20 at 18:17
  • Hi! FYI, I've changed to beta07 and it still have the same issue for a couple of devices (that used to work). I guess I'll have to wait for the new sdk you've mentioned. – Kamolwan Kunanusont Nov 27 '20 at 04:16
0

Update your ML barcode scan plugin above 16.1.1

This issue was fixed in 'com.google.mlkit:barcode-scanning:16.1.1'

Navin Kumar
  • 3,393
  • 3
  • 21
  • 46