Реализация сканирования штрих-кодов через камеру мобильного приложения
Сканирование штрих-кодов в реальном времени через видеопоток — задача, в которой платформенные API сильно отличаются по подходу. iOS даёт AVCaptureSession, Android — Camera2 API или CameraX. Цель одна: захватить кадр, передать в декодер, получить результат с минимальной задержкой.
iOS: AVCaptureSession + AVCaptureMetadataOutput
Классический подход — pipeline через AVCaptureSession:
let session = AVCaptureSession()
guard let device = AVCaptureDevice.default(for: .video),
let input = try? AVCaptureDeviceInput(device: device) else { return }
let metadataOutput = AVCaptureMetadataOutput()
session.addInput(input)
session.addOutput(metadataOutput)
metadataOutput.setMetadataObjectsDelegate(self, queue: .main)
metadataOutput.metadataObjectTypes = [.ean13, .ean8, .code128, .upce, .qr]
// Превью
let previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.frame = view.bounds
previewLayer.videoGravity = .resizeAspectFill
view.layer.addSublayer(previewLayer)
DispatchQueue.global(qos: .userInitiated).async {
session.startRunning()
}
session.startRunning() всегда в фоновом потоке — на main thread это блокирует UI на 300-600ms при старте.
Делегат AVCaptureMetadataOutputObjectsDelegate получает результат:
func metadataOutput(_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection) {
guard let object = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
let code = object.stringValue else { return }
session.stopRunning()
handleCode(code)
}
Зона сканирования
metadataOutput.rectOfInterest — ограничивает область, в которой ищутся коды. Координаты в нормализованном пространстве (0.0-1.0), причём оси перевёрнуты по сравнению с UIKit. AVCaptureVideoPreviewLayer.metadataOutputRectConverted(fromLayerRect:) конвертирует из UIKit-координат в нужный формат.
Без rectOfInterest на плотных полках с товарами камера может распознать соседний штрих-код, а не тот, на который наведён прицел.
Android: CameraX + ML Kit
CameraX — рекомендованный подход с API 21+:
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build()
preview.setSurfaceProvider(previewView.surfaceProvider)
val imageAnalysis = ImageAnalysis.Builder()
.setTargetResolution(Size(1280, 720))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(), BarcodeAnalyzer { barcode ->
handleBarcode(barcode)
})
cameraProvider.bindToLifecycle(this, CameraSelector.DEFAULT_BACK_CAMERA, preview, imageAnalysis)
}, ContextCompat.getMainExecutor(context))
STRATEGY_KEEP_ONLY_LATEST — критически важно. Без этого кадры накапливаются в очереди и декодер начинает отставать на 1-2 секунды.
BarcodeAnalyzer — реализация ImageAnalysis.Analyzer, внутри ML Kit:
class BarcodeAnalyzer(private val onDetected: (String) -> Unit) : ImageAnalysis.Analyzer {
private val scanner = BarcodeScanning.getClient(
BarcodeScannerOptions.Builder().setBarcodeFormats(Barcode.FORMAT_ALL_FORMATS).build()
)
@androidx.camera.core.ExperimentalGetImage
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image ?: run { imageProxy.close(); return }
val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
scanner.process(inputImage)
.addOnSuccessListener { barcodes ->
barcodes.firstOrNull()?.rawValue?.let { onDetected(it) }
}
.addOnCompleteListener { imageProxy.close() }
}
}
imageProxy.close() в addOnCompleteListener — обязательно, иначе CameraX остановит подачу новых кадров.
Разрешение камеры и автофокус
На Android бюджетных устройствах (Realme C-серия, Tecno) автофокус может работать нестабильно. CameraControl.startFocusAndMetering() с FocusMeteringAction помогает принудительно сфокусироваться по центру каждые 2 секунды.
На iOS — AVCaptureDevice.focusMode = .continuousAutoFocus и autoFocusRangeRestriction = .near для сканирования на близком расстоянии.
Срок реализации: 1-3 дня. Стоимость рассчитывается индивидуально.







