skip to Main Content

I’m making video from taking snapshots (30 times per sec), and I really need to find the best approach to get snapshot in the background thread with the best possible performance.

I have two approach in UIView extension

extension UIView {
 
    func snapshotInMain(view: UIView) -> UIImage {
        let render = UIGraphicsImageRenderer(size: view.bounds.size)
        let image = render.image { (context) in
            view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
        }
        return image
    }
    
    
    func snapShotInBackground(viewLayer: CALayer, viewBounds: CGRect) -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: viewBounds)
        return renderer.image { rendererContext in
            viewLayer.render(in: rendererContext.cgContext)
        }
    }
}

The first one snapshotInMaincompletely execute in main thread, it froze the app but the screenshot itself is taken faster and I have a smoother video from it

the second one snapShotInBackground, the layer and bounds are calculated in the main thread, but then will execute in background, but it’s way slower from the the one that execute in main (first one).
it works like that

DispatchQueue.main.async {
   let layer = self.selectedView.layer
   let bounds = self.selectedView.bounds
   DispatchQueue.global(qos: .background).async {
       let image = self.selectedView.snapShotInBackground(viewLayer: layer, viewBounds: bounds)
     }
}

My job is really depand on it, I’m pretty stuck here and really need help. please help me to find the best option possible. My main requests are
1- App should not freeze
2- The taking snapshot should be fast that I can do it 30 times per sec.

Thank you! Looking forward to your help

2

Answers


  1. You can use a Timer to throttle the snapshots on the main thread. Here’s an example

        @objc
        private func beginTapped() {
            print("Start time (Date.now)")
            frameCount = 0
            let timer = Timer.scheduledTimer(timeInterval: 0.03, target: self, selector: #selector(takeSnapshot), userInfo: nil, repeats: true)
            
            DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: {
                timer.invalidate()
                print("Finished at (Date.now) with (self.frameCount) frames captured")
            })
        }
        
        @objc
        private func takeSnapshot() {
            frameCount += 1
            let render = UIGraphicsImageRenderer(size: selectedView.bounds.size)
            let _ = render.image { (context) in
                selectedView.drawHierarchy(in: selectedView.bounds, afterScreenUpdates: true)
            }
        }
    

    which results in

    Start time 2022-07-01 04:20:01 +0000
    Finished at 2022-07-01 04:20:07 +0000 with 180 frames captured
    

    Decreasing the timeInterval to 0.01 yields

    Start time 2022-07-01 04:25:30 +0000
    Finished at 2022-07-01 04:25:36 +0000 with 506 frames captured
    

    CPU usage peaked around 20% during this time and the app did not lag at all in my trivial example.

    Login or Signup to reply.
  2. Rather than taking the snapshot I thing use generator is a good option here it takes cpu but not memory which cause smoother ui/ux

    class VideoController:UIViewController {
        
        var timer = Timer()
        var secs:Double = 0
        
        var urls = [URL?]() {
            didSet {
                print(urls.count)
            }
        }
        
        override func viewDidLoad() {
            super.viewDidLoad()
            everSecond()
        }
        
        func everSecond() {
            // every second trigger
            timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(generate), userInfo: nil, repeats: true)
        }
        
        
        @objc
        func generate() {
            let url = Bundle.main.url(forResource: "Collection", withExtension: "mov")
            let videoAsset = AVAsset(url: url!)
            let duration = videoAsset.duration.seconds // duration of video
            if secs < duration {
                var timesArray:[NSValue] = []
                for i in 1...30 {
                    let value:Double = Double(31 - i) // invers
                    let t = CMTime(value: CMTimeValue(1 / value) + Int64(secs), timescale: 1) // getting time
                    timesArray.append(NSValue(time: t)) // appending array
                }
                let generator = AVAssetImageGenerator(asset: videoAsset)
                generator.requestedTimeToleranceBefore = .zero
                //Optional generator.requestedTimeToleranceAfter = .zero //Optional
                generator.generateCGImagesAsynchronously(forTimes: timesArray) { requestedTime, image, actualTime, result, error in
                        if let img = image {
                            let m = UIImage(cgImage: img) // uiimage
                            let url = ViewController.saveImageInDocumentDirectory(image: m, fileName: "(Date().timeStamp()).jpeg") //saved in documents with unique name
                            self.urls.append(url) // append url to array urls for save refrecnce
                        }
                    
                }
                secs += 1
            } else {
                timer.invalidate()
            }
        }
        
        
        public static func saveImageInDocumentDirectory(image: UIImage, fileName: String) -> URL? {
    
                let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!;
                let fileURL = documentsUrl.appendingPathComponent(fileName)
                if let imageData = image.pngData() {
                    try? imageData.write(to: fileURL, options: .atomic)
                    return fileURL
                }
                return nil
            }
    
        public static func loadImageFromDocumentDirectory(fileName: String) -> UIImage? {
    
                let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!;
                let fileURL = documentsUrl.appendingPathComponent(fileName)
                do {
                    let imageData = try Data(contentsOf: fileURL)
                    return UIImage(data: imageData)
                } catch {}
                return nil
            }
    }
    extension Date {
        
        func timeStamp() -> String {
            let fomatter = DateFormatter()
            fomatter.dateFormat = "yyyy-MM-dd HH:mm:ss:SSS"
            return fomatter.string(from: self)
        }
    }
    
        
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search