Swift — Как достичь 25 кадров в секунду для записи экрана несколькими AVPlayers одновременно

#ios #swift #avplayer #uigraphicscontext #screen-recording

Вопрос:

У меня есть экранный рекордер, который может записывать два воспроизведения AVPlayer одновременно, но я хочу увеличить частоту кадров в секунду до 25.

Я использую AVAssetImageGenerator (), чтобы сделать снимок, а затем загрузить это изображение в представление, скрытое под соответствующим AVPlayer. Затем я делаю снимок экрана, используя UIGraphicsGetImageFromCurrentImageContext (), объединяющий все вместе. Затем я сохраняю изображения в приложении. Эта функция выполняется примерно 14 раз в секунду. Когда запись останавливается, я использую FFMPEG для объединения всех изображений в видео со скоростью около 30 кадров в секунду.

Результат видео выглядит нормально, но мне нравится увеличивать количество снимков экрана, которые я делаю в секунду, чтобы он выглядел более плавным. Есть какие-нибудь идеи о том, как я мог бы улучшить код, чтобы делать еще несколько скриншотов в секунду? Я надеюсь, что в этом есть смысл.

 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: "d", 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)
            }
        }
    }
}
 

Комментарии:

1. Вы рассматривали возможность использования ReplayKit для захвата экрана? Постоянное создание скриншотов звучит не очень эффективно.

2. Да, но проблема в том, что ReplayKit несовместим с контентом AVPlayer