Ryan Duffy

Better memory footprint and using a dispatch queue

By not passing in the buffer to the barcode detect and using an image instead the footprint of the memory usage is significantly reduced.
@@ -2,6 +2,7 @@ import AVFoundation @@ -2,6 +2,7 @@ import AVFoundation
2 import FlutterMacOS 2 import FlutterMacOS
3 import Vision 3 import Vision
4 import AppKit 4 import AppKit
  5 +import VideoToolbox
5 6
6 public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, FlutterTexture, AVCaptureVideoDataOutputSampleBufferDelegate { 7 public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, FlutterTexture, AVCaptureVideoDataOutputSampleBufferDelegate {
7 8
@@ -17,7 +18,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -17,7 +18,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
17 var captureSession: AVCaptureSession! 18 var captureSession: AVCaptureSession!
18 19
19 // The selected camera 20 // The selected camera
20 - var device: AVCaptureDevice! 21 + weak var device: AVCaptureDevice!
21 22
22 // Image to be sent to the texture 23 // Image to be sent to the texture
23 var latestBuffer: CVImageBuffer! 24 var latestBuffer: CVImageBuffer!
@@ -95,7 +96,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -95,7 +96,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
95 } 96 }
96 97
97 var nextScanTime = 0.0 98 var nextScanTime = 0.0
98 - var imagesCurrentlyBeingProcessed = 0 99 + var imagesCurrentlyBeingProcessed = false
99 100
100 // Gets called when a new image is added to the buffer 101 // Gets called when a new image is added to the buffer
101 public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { 102 public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
@@ -111,45 +112,47 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -111,45 +112,47 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
111 registry.textureFrameAvailable(textureId) 112 registry.textureFrameAvailable(textureId)
112 113
113 let currentTime = Date().timeIntervalSince1970 114 let currentTime = Date().timeIntervalSince1970
114 - let eligibleForScan = currentTime > nextScanTime && imagesCurrentlyBeingProcessed == 0; 115 + let eligibleForScan = currentTime > nextScanTime && imagesCurrentlyBeingProcessed == false
115 if ((detectionSpeed == DetectionSpeed.normal || detectionSpeed == DetectionSpeed.noDuplicates) && eligibleForScan || detectionSpeed == DetectionSpeed.unrestricted) { 116 if ((detectionSpeed == DetectionSpeed.normal || detectionSpeed == DetectionSpeed.noDuplicates) && eligibleForScan || detectionSpeed == DetectionSpeed.unrestricted) {
116 nextScanTime = currentTime + timeoutSeconds 117 nextScanTime = currentTime + timeoutSeconds
117 - imagesCurrentlyBeingProcessed += 1  
118 - let imageRequestHandler = VNImageRequestHandler(  
119 - cvPixelBuffer: latestBuffer,  
120 - orientation: .right)  
121 - 118 + imagesCurrentlyBeingProcessed = true
  119 + DispatchQueue.global(qos: .userInitiated).async { [weak self] in
  120 + if(self!.latestBuffer == nil){
  121 + return
  122 + }
  123 + var cgImage: CGImage?
  124 + VTCreateCGImageFromCVPixelBuffer(self!.latestBuffer, options: nil, imageOut: &cgImage)
  125 + let imageRequestHandler = VNImageRequestHandler(cgImage: cgImage!)
122 do { 126 do {
123 - let barcodeRequest:VNDetectBarcodesRequest = VNDetectBarcodesRequest(completionHandler: { [self] (request, error) in  
124 - imagesCurrentlyBeingProcessed -= 1 127 + let barcodeRequest:VNDetectBarcodesRequest = VNDetectBarcodesRequest(completionHandler: { [weak self] (request, error) in
  128 + self?.imagesCurrentlyBeingProcessed = false
125 if error == nil { 129 if error == nil {
126 if let results = request.results as? [VNBarcodeObservation] { 130 if let results = request.results as? [VNBarcodeObservation] {
127 for barcode in results { 131 for barcode in results {
128 - if self.scanWindow != nil {  
129 - let match = self.isbarCodeInScanWindow(self.scanWindow!, barcode, self.latestBuffer)  
130 - if (!match) { 132 + if self?.scanWindow != nil && cgImage != nil {
  133 + let match = self?.isbarCodeInScanWindow(self!.scanWindow!, barcode, cgImage!)
  134 + if (match == false) {
131 continue 135 continue
132 } 136 }
133 } 137 }
134 138
135 - let barcodeType = String(barcode.symbology.rawValue).replacingOccurrences(of: "VNBarcodeSymbology", with: "")  
136 - let event: [String: Any?] = ["name": "barcodeMac", "data" : ["payload": barcode.payloadStringValue, "symbology": barcode.symbology.toInt as Any?]]  
137 - self.sink?(event)  
138 -  
139 - // if barcodeType == "QR" {  
140 - // let image = CIImage(image: source)  
141 - // image?.cropping(to: barcode.boundingBox)  
142 - // self.qrCodeDescriptor(qrCode: barcode, qrCodeImage: image!)  
143 - // } 139 + DispatchQueue.main.async {
  140 + self?.sink?(["name": "barcodeMac", "data" : ["payload": barcode.payloadStringValue, "symbology": barcode.symbology.toInt as Any?]] as [String : Any])
  141 + }
  142 +// if barcodeType == "QR" {
  143 +// let image = CIImage(image: source)
  144 +// image?.cropping(to: barcode.boundingBox)
  145 +// self.qrCodeDescriptor(qrCode: barcode, qrCodeImage: image!)
  146 +// }
144 } 147 }
145 } 148 }
146 } else { 149 } else {
147 print(error!.localizedDescription) 150 print(error!.localizedDescription)
148 } 151 }
149 }) 152 })
150 - if(symbologies.isEmpty == false){ 153 + if(self?.symbologies.isEmpty == false){
151 // add the symbologies the user wishes to support 154 // add the symbologies the user wishes to support
152 - barcodeRequest.symbologies = symbologies 155 + barcodeRequest.symbologies = self!.symbologies
153 } 156 }
154 try imageRequestHandler.perform([barcodeRequest]) 157 try imageRequestHandler.perform([barcodeRequest])
155 } catch { 158 } catch {
@@ -157,6 +160,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -157,6 +160,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
157 } 160 }
158 } 161 }
159 } 162 }
  163 + }
160 164
161 func checkPermission(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 165 func checkPermission(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
162 if #available(macOS 10.14, *) { 166 if #available(macOS 10.14, *) {
@@ -199,6 +203,20 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -199,6 +203,20 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
199 scanWindow = CGRect(x: minX, y: minY, width: width, height: height) 203 scanWindow = CGRect(x: minX, y: minY, width: width, height: height)
200 } 204 }
201 205
  206 + func isbarCodeInScanWindow(_ scanWindow: CGRect, _ barcode: VNBarcodeObservation, _ inputImage: CGImage) -> Bool {
  207 +
  208 + let imageWidth = CGFloat(inputImage.width);
  209 + let imageHeight = CGFloat(inputImage.height);
  210 +
  211 + let minX = scanWindow.minX * imageWidth
  212 + let minY = scanWindow.minY * imageHeight
  213 + let width = scanWindow.width * imageWidth
  214 + let height = scanWindow.height * imageHeight
  215 +
  216 + let scaledScanWindow = CGRect(x: minX, y: minY, width: width, height: height)
  217 + return scaledScanWindow.contains(barcode.boundingBox)
  218 + }
  219 +
202 func isbarCodeInScanWindow(_ scanWindow: CGRect, _ barcode: VNBarcodeObservation, _ inputImage: CVImageBuffer) -> Bool { 220 func isbarCodeInScanWindow(_ scanWindow: CGRect, _ barcode: VNBarcodeObservation, _ inputImage: CVImageBuffer) -> Bool {
203 let size = CVImageBufferGetEncodedSize(inputImage) 221 let size = CVImageBufferGetEncodedSize(inputImage)
204 222
@@ -227,11 +245,11 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -227,11 +245,11 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
227 245
228 let argReader = MapArgumentReader(call.arguments as? [String: Any]) 246 let argReader = MapArgumentReader(call.arguments as? [String: Any])
229 247
230 -// let ratio: Int = argReader.int(key: "ratio")  
231 - let torch: Bool = argReader.bool(key: "torch") ?? false  
232 - let facing: Int = argReader.int(key: "facing") ?? 1  
233 - let speed: Int = (call.arguments as! Dictionary<String, Any?>)["speed"] as? Int ?? 0  
234 - let timeoutMs: Int = (call.arguments as! Dictionary<String, Any?>)["timeout"] as? Int ?? 0 248 + // let ratio: Int = argReader.int(key: "ratio")
  249 + let torch:Bool = argReader.bool(key: "torch") ?? false
  250 + let facing:Int = argReader.int(key: "facing") ?? 1
  251 + let speed:Int = argReader.int(key: "speed") ?? 0
  252 + let timeoutMs:Int = argReader.int(key: "timeout") ?? 0
235 symbologies = argReader.toSymbology() 253 symbologies = argReader.toSymbology()
236 254
237 timeoutSeconds = Double(timeoutMs) / 1000.0 255 timeoutSeconds = Double(timeoutMs) / 1000.0
@@ -285,7 +303,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -285,7 +303,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
285 videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main) 303 videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main)
286 captureSession.addOutput(videoOutput) 304 captureSession.addOutput(videoOutput)
287 for connection in videoOutput.connections { 305 for connection in videoOutput.connections {
288 -// connection.videoOrientation = .portrait 306 + // connection.videoOrientation = .portrait
289 if position == .front && connection.isVideoMirroringSupported { 307 if position == .front && connection.isVideoMirroringSupported {
290 connection.isVideoMirrored = true 308 connection.isVideoMirrored = true
291 } 309 }
@@ -472,5 +490,4 @@ extension VNBarcodeSymbology { @@ -472,5 +490,4 @@ extension VNBarcodeSymbology {
472 return -1; 490 return -1;
473 } 491 }
474 } 492 }
475 -  
476 } 493 }