Viewing is open to everyone. Downloading and starring need a GitHub sign-in. Sign in with GitHub

CKeyAnalyzer.kt

v0.1.0-build.85 Latest Commit ede38f5

Size
3.1 KB
Lines
77
Language
kotlin
SHA-256
6265a23bbb1e…
View on GitHub

Sign in with GitHub to download. Each download is recorded with your GitHub username.

1package org.openckey.ckey.camerax
2
3import android.graphics.ImageFormat
4import android.os.Handler
5import android.os.Looper
6import androidx.camera.core.ImageAnalysis
7import androidx.camera.core.ImageProxy
8import java.util.concurrent.Executor
9import org.openckey.ckey.CKeyResult
10import org.openckey.ckey.CKeyScanner
11
12/**
13 * A CameraX analyzer that decodes CKeys from the luma (Y) plane of each
14 * frame and drives a telemetry scan session (docs/adoption-and-telemetry.md):
15 * the session begins with the first frame (or [start]), every analyzed
16 * frame is counted with its processing time, the first decode succeeds it,
17 * and [stop] without a decode fails it.
18 *
19 * ```
20 * val analyzer = CKeyAnalyzer { result -> showResult(result) }
21 * val analysis = ImageAnalysis.Builder()
22 * .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
23 * .setResolutionSelector(...) // CKeys need ~6-11 camera px per symbol unit
24 * .build().apply { setAnalyzer(cameraExecutor, analyzer) }
25 * // when the camera is unbound / the screen goes away:
26 * analyzer.stop()
27 * ```
28 *
29 * After a decode, frames are skipped until [scanAgain]. [onResult] runs on
30 * [callbackExecutor] (the main thread by default), never as an automatic
31 * action: show the payload and let the user choose (CKeyResult.action).
32 * Use the default YUV_420_888 output format. Call `CKey.install(context)`
33 * once (e.g. in Application.onCreate) so telemetry can also be flushed when
34 * the app goes to the background.
35 */
36class CKeyAnalyzer @JvmOverloads constructor(
37 private val callbackExecutor: Executor = MainThreadExecutor,
38 /** Minimum milliseconds between analyzed frames (50 = at most 20 fps, as the iOS reader). */
39 private val minFrameIntervalMs: Long = 50,
40 private val onResult: (CKeyResult) -> Unit,
41) : ImageAnalysis.Analyzer {
42
43 private val scanner = CKeyScanner()
44 private var lastFrameNs = 0L
45
46 /** Begins a scan session (optional: the first frame begins one). */
47 fun start() = scanner.start()
48
49 /** Ends the scan session; call when the camera is unbound or the scanner UI goes away. */
50 fun stop() = scanner.stop()
51
52 /** Starts a new session after a decode. */
53 fun scanAgain() = scanner.scanAgain()
54
55 override fun analyze(image: ImageProxy) {
56 try {
57 if (scanner.hasResult) return
58 val now = System.nanoTime()
59 if (lastFrameNs != 0L && now - lastFrameNs < minFrameIntervalMs * 1_000_000L) return
60 lastFrameNs = now
61 if (image.format != ImageFormat.YUV_420_888 && image.format != ImageFormat.YUV_422_888 &&
62 image.format != ImageFormat.YUV_444_888) return
63 val y = image.planes[0]
64 val result = scanner.processLuma(y.buffer, y.rowStride, y.pixelStride, image.width, image.height)
65 if (result != null) callbackExecutor.execute { onResult(result) }
66 } finally {
67 image.close()
68 }
69 }
70
71 private object MainThreadExecutor : Executor {
72 private val handler = Handler(Looper.getMainLooper())
73 override fun execute(command: Runnable) {
74 handler.post(command)
75 }
76 }
77}