2023-10-10 18:51:36 +02:00
|
|
|
package com.mrousavy.camera.core
|
|
|
|
|
|
|
|
import android.media.ImageReader
|
|
|
|
import android.util.Size
|
|
|
|
import android.view.Surface
|
|
|
|
import com.google.mlkit.vision.barcode.BarcodeScanner
|
|
|
|
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
|
|
|
|
import com.google.mlkit.vision.barcode.BarcodeScanning
|
|
|
|
import com.google.mlkit.vision.common.InputImage
|
2023-10-17 11:49:04 +02:00
|
|
|
import com.mrousavy.camera.types.Orientation
|
2023-10-10 18:51:36 +02:00
|
|
|
import java.io.Closeable
|
|
|
|
|
2023-10-24 11:19:03 +02:00
|
|
|
class CodeScannerPipeline(
|
|
|
|
val size: Size,
|
|
|
|
val format: Int,
|
|
|
|
val configuration: CameraConfiguration.CodeScanner,
|
|
|
|
val callback: CameraSession.CameraSessionCallback
|
|
|
|
) : Closeable {
|
2023-10-10 18:51:36 +02:00
|
|
|
companion object {
|
2023-10-16 16:28:51 +02:00
|
|
|
// We want to have a buffer of 2 images, but we always only acquire one.
|
|
|
|
// That way the pipeline is free to stream one frame into the unused buffer,
|
2023-10-10 18:51:36 +02:00
|
|
|
// while the other buffer is being used for code scanning.
|
2023-10-16 16:28:51 +02:00
|
|
|
private const val MAX_IMAGES = 2
|
2023-10-10 18:51:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private val imageReader: ImageReader
|
|
|
|
private val scanner: BarcodeScanner
|
|
|
|
|
|
|
|
val surface: Surface
|
|
|
|
get() = imageReader.surface
|
|
|
|
|
|
|
|
init {
|
2023-10-24 11:19:03 +02:00
|
|
|
val types = configuration.codeTypes.map { it.toBarcodeType() }
|
2023-10-10 18:51:36 +02:00
|
|
|
val barcodeScannerOptions = BarcodeScannerOptions.Builder()
|
|
|
|
.setBarcodeFormats(types[0], *types.toIntArray())
|
|
|
|
.build()
|
|
|
|
scanner = BarcodeScanning.getClient(barcodeScannerOptions)
|
|
|
|
|
|
|
|
var isBusy = false
|
|
|
|
imageReader = ImageReader.newInstance(size.width, size.height, format, MAX_IMAGES)
|
|
|
|
imageReader.setOnImageAvailableListener({ reader ->
|
2023-10-16 16:28:51 +02:00
|
|
|
val image = reader.acquireNextImage() ?: return@setOnImageAvailableListener
|
|
|
|
|
2023-10-10 18:51:36 +02:00
|
|
|
if (isBusy) {
|
|
|
|
// We're currently executing on a previous Frame, so we skip this one.
|
2023-10-16 16:28:51 +02:00
|
|
|
// Close it and free it again, so that the Camera does not stall.
|
|
|
|
image.close()
|
2023-10-10 18:51:36 +02:00
|
|
|
return@setOnImageAvailableListener
|
|
|
|
}
|
|
|
|
|
|
|
|
isBusy = true
|
|
|
|
// TODO: Get correct orientation
|
|
|
|
val inputImage = InputImage.fromMediaImage(image, Orientation.PORTRAIT.toDegrees())
|
|
|
|
scanner.process(inputImage)
|
|
|
|
.addOnSuccessListener { barcodes ->
|
|
|
|
image.close()
|
|
|
|
isBusy = false
|
|
|
|
if (barcodes.isNotEmpty()) {
|
2023-10-24 11:19:03 +02:00
|
|
|
callback.onCodeScanned(barcodes)
|
2023-10-10 18:51:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
.addOnFailureListener { error ->
|
|
|
|
image.close()
|
|
|
|
isBusy = false
|
2023-10-24 11:19:03 +02:00
|
|
|
callback.onError(error)
|
2023-10-10 18:51:36 +02:00
|
|
|
}
|
|
|
|
}, CameraQueues.videoQueue.handler)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun close() {
|
|
|
|
imageReader.close()
|
|
|
|
scanner.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun toString(): String {
|
2023-10-24 11:19:03 +02:00
|
|
|
val codeTypes = configuration.codeTypes.joinToString(", ")
|
2023-10-10 18:51:36 +02:00
|
|
|
return "${size.width} x ${size.height} CodeScanner for [$codeTypes] ($format)"
|
|
|
|
}
|
|
|
|
}
|