Swift - How to achieve 25 FPS for screen recording multiple AVPlayers at the same time

174 Views Asked by At

I have a screen recorder that can record two AVPlayer playings simultaneously but I want to improve the frame rate per second to 25.

I use AVAssetImageGenerator() to take a still and then load this image onto a View hidden underneath the corresponding AVPlayer. I then take a screenshot using UIGraphicsGetImageFromCurrentImageContext() combining the lot together. I then save the images to the app. This function happens around 14 times a second. When the recording stops, I use FFMPEG to concatenate all the images together into a video to around 30 fps.

The video result looks okay but I like to improve the number of screenshots I take per second further so it looks smoother. Any ideas on how I could improve the code to take a few more screenshots per second? I hope this makes sense.

var limit = 2000
var screenshotTaken = 0
var view: UIView?

var screenRecording: Bool = false
var compilingVideo: Bool = false

let leftPlayerUrl: URL?
let leftPlayer: AVPlayer?
let leftPlayerImageView: UIImageView?

let rightPlayerUrl: URL?
let rightPlayer: AVPlayer?
let rightPlayerImageView: UIImageView?

init(view: UIView, leftPlayerUrl: URL, leftPlayer: AVPlayer, leftPlayerImageView: UIImageView, rightPlayerUrl: URL, rightPlayer: AVPlayer, rightPlayerImageView: UIImageView) {
    self.view = view
    self.leftPlayerUrl = leftPlayerUrl
    self.leftPlayer = leftPlayer
    self.leftPlayerImageView = leftPlayerImageView
    
    self.rightPlayerUrl = rightPlayerUrl
    self.rightPlayer = rightPlayer
    self.rightPlayerImageView = rightPlayerImageView
    
}

func capture()
{
    if screenRecording {
        if limit >= screenshotTaken {
            //the delay should be 0.04 to hit 25 fps but the max screenshots taken is 16 per second
            delay(0.07) {
                DispatchQueue.main.async {
                    self.complexScreenshot()
                }
                self.capture()
            }
        } else {
            DebugPrint.DBprint("Screenshot limit reached or recording stopped")
            delegate?.screenShotLimitReached()
        }
    }
}

func delay(_ delay: Double, closure: @escaping ()->()) {
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
}

@objc func complexScreenshot() {
    guard let url = leftPlayerUrl else {return}
    let asset = AVAsset(url: url)
    
    let imageGenerator = AVAssetImageGenerator(asset: asset)
    imageGenerator.maximumSize = CGSize(width: 640, height: 480)
    imageGenerator.requestedTimeToleranceAfter = CMTime.zero
    imageGenerator.requestedTimeToleranceBefore = CMTime.zero
    
    if let thumb: CGImage = try? imageGenerator.copyCGImage(at: leftPlayer?.currentTime() ?? CMTime.zero, actualTime: nil) {
        let videoImage = UIImage(cgImage: thumb)
        
        self.leftPlayerImageView?.image = videoImage
    }
    
    guard let url2 = rightPlayerUrl else {return}
    let asset2 = AVAsset(url: url2)
    
    let imageGenerator2 = AVAssetImageGenerator(asset: asset2)
    imageGenerator2.maximumSize = CGSize(width: 640, height: 480)
    imageGenerator2.requestedTimeToleranceAfter = CMTime.zero
    imageGenerator2.requestedTimeToleranceBefore = CMTime.zero
    
    if let thumb2: CGImage = try? imageGenerator2.copyCGImage(at: rightPlayer?.currentTime() ?? CMTime.zero, actualTime: nil) {
        let videoImage = UIImage(cgImage: thumb2)
        
        self.rightPlayerImageView?.image = videoImage
    }
    
    guard let bounds = view?.bounds else {return}
    UIGraphicsBeginImageContextWithOptions(bounds.size, view?.isOpaque ?? true, 0.0)
    self.view?.drawHierarchy(in: bounds, afterScreenUpdates: true)
    
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    
    self.leftPlayerImageView?.image = nil
    self.rightPlayerImageView?.image = nil
    
    
    if image != nil {
        DispatchQueue.global(qos: .utility).async { [weak self] in
            self?.saveScreenshot(image: image!, number: self!.screenshotTaken)
        }
    }
    
    screenshotTaken = screenshotTaken + 1
}

func saveScreenshot(image: UIImage, number: Int) {
    let number = String(format: "%04d", number)
    let filePath = URL(fileURLWithPath: self.mainPath).appendingPathComponent("Temp/image_\(number).jpg")
    
    autoreleasepool {
        if let data = image.jpegData(compressionQuality: 0.4),
           !self.fileManager.fileExists(atPath: filePath.path) {
            
            do {
                try data.write(to: filePath)
            } catch {
                print("Error saving file: ", error)
            }
        }
    }
}
0

There are 0 best solutions below