Created
January 15, 2024 10:20
-
-
Save Zlobrynya/953c54c3be1b912d894fd528fa8c1625 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import AVFoundation | |
import UIKit | |
import Vision | |
final class VNScannerController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate { | |
private let session = AVCaptureSession() | |
private let seqHandler = VNSequenceRequestHandler() | |
private let captureDevice = AVCaptureDevice.default(for: AVMediaType.video) | |
private var request: VNDetectBarcodesRequest? | |
private var previewLayer: AVCaptureVideoPreviewLayer! | |
private var originalHeight: CGFloat = .zero | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
setUpScanner() | |
} | |
override func viewWillAppear(_ animated: Bool) { | |
super.viewWillAppear(animated) | |
#if targetEnvironment(simulator) | |
#else | |
if previewLayer == nil { | |
setUpPreviewLayer() | |
} | |
#endif | |
} | |
override func viewDidAppear(_ animated: Bool) { | |
super.viewDidAppear(animated) | |
start() | |
} | |
override func viewDidDisappear(_ animated: Bool) { | |
super.viewDidDisappear(animated) | |
stop() | |
} | |
override func viewDidLayoutSubviews() { | |
super.viewDidLayoutSubviews() | |
previewLayer?.frame = view.bounds | |
} | |
func turnLightOn(_ on: Bool) { | |
#if targetEnvironment(simulator) | |
#else | |
let torchLevel = on ? min(1, AVCaptureDevice.maxAvailableTorchLevel) : 0 | |
guard let captureDevice = captureDevice, | |
UIApplication.shared.applicationState == .active, | |
captureDevice.hasFlash, | |
captureDevice.torchLevel != torchLevel | |
else { return } | |
do { | |
try captureDevice.lockForConfiguration() | |
if on { | |
try captureDevice.setTorchModeOn(level: torchLevel) | |
} else { | |
captureDevice.torchMode = .off | |
} | |
captureDevice.unlockForConfiguration() | |
} catch {} | |
#endif | |
} | |
private func start() { | |
guard session.isRunning == false else { | |
return | |
} | |
DispatchQueue.global(qos: .userInitiated).async { | |
self.session.startRunning() | |
} | |
} | |
private func stop() { | |
guard session.isRunning else { | |
return | |
} | |
turnLightOn(false) | |
DispatchQueue.global(qos: .userInitiated).async { | |
self.session.stopRunning() | |
} | |
} | |
private func setUpPreviewLayer() { | |
previewLayer = AVCaptureVideoPreviewLayer(session: session) | |
previewLayer.frame = view.layer.bounds | |
previewLayer.videoGravity = .resizeAspectFill | |
view.layer.addSublayer(previewLayer) | |
} | |
private func setUpScanner() { | |
session.sessionPreset = .high | |
try? captureDevice?.lockForConfiguration() | |
captureDevice?.focusMode = .continuousAutoFocus | |
try? captureDevice?.unlockForConfiguration() | |
do { | |
try setUpSession() | |
} catch { | |
print(error) | |
} | |
request = VNDetectBarcodesRequest { request, _ in | |
guard let observations = request.results, | |
observations.isEmpty == false else { | |
return | |
} | |
observations | |
.compactMap { $0 as? VNBarcodeObservation } | |
.forEach { barcodeObservation in | |
print(barcodeObservation.payloadStringValue) | |
} | |
} | |
} | |
private func setUpSession() throws { | |
guard let captureDevice else { | |
return | |
} | |
let deviceInput: AVCaptureDeviceInput | |
deviceInput = try AVCaptureDeviceInput(device: captureDevice) | |
let deviceOutput = AVCaptureVideoDataOutput() | |
deviceOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32BGRA)] | |
deviceOutput.setSampleBufferDelegate(self, queue: DispatchQueue.global(qos: .default)) | |
session.addInput(deviceInput) | |
if session.canAddOutput(deviceOutput) { | |
session.addOutput(deviceOutput) | |
} else { | |
throw ScanError.badOutput | |
} | |
} | |
// MARK: Implementing AVCaptureVideoDataOutputSampleBufferDelegate | |
func captureOutput(_: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from _: AVCaptureConnection) { | |
// https://stackoverflow.com/questions/51214586/value-of-type-cmsamplebuffer-has-no-member-imagebuffer | |
guard let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { | |
return | |
} | |
guard let request = request else { | |
return | |
} | |
try? seqHandler.perform([request], on: pixelBuffer) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment