Невозможно обновить UIViewImage в то время как l oop с AVAssetTrackReaderOutput - PullRequest
0 голосов
/ 17 января 2020

Я пытаюсь извлечь каждый кадр из видео, сделать некоторые манипуляции с изображением и затем отобразить изображение обратно в UIImageView.

Если я нажму на UIButton вручную, изображение будет отображаться и повторяться по всему видео и показывать каждый кадр.

Однако, если я встраиваю дисплей обновления через некоторое время l oop, то представление не обновляется (т. е. на устройстве оно не обновляется).

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

Вот код:

import UIKit
import Foundation
import Vision
import AVFoundation
import Darwin

class ViewController: UIViewController {

    var uiImage: UIImage?

    var displayLink: CADisplayLink?

    var videoAsset: AVAsset!
    var videoTrack: AVAssetTrack!
    var videoReader: AVAssetReader!
    var videoOutput: AVAssetReaderTrackOutput!


    @IBOutlet weak var topView: UIImageView!

    @IBOutlet weak var bottomView: UIImageView!

    @IBOutlet weak var rightLabel: UILabel!

    @IBOutlet weak var leftLabel: UILabel!

    @IBAction func tapButton(_ sender: Any) {

        while let sampleBuffer = videoOutput.copyNextSampleBuffer() {
            print ("sample at time \(CMSampleBufferGetPresentationTimeStamp(sampleBuffer))")
            if let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) {

                let ciImage = CIImage(cvImageBuffer: imageBuffer)
                uiImage = UIImage(ciImage: ciImage)
                self.topView.image = uiImage
                self.topView.setNeedsDisplay()
                usleep(useconds_t(24000))

            }

        }
    }


    override func viewDidLoad() {

        super.viewDidLoad()

    }



    override func viewDidAppear(_ animated: Bool) {




        guard let urlPath = Bundle.main.path(forResource: "video1", ofType: "mp4")  else {
            print ("No File")
            return
        }

        videoAsset = AVAsset(url: URL(fileURLWithPath: urlPath))
        let array = videoAsset.tracks(withMediaType: AVMediaType.video)
        videoTrack = array[0]


        do {
            videoReader = try AVAssetReader(asset: videoAsset)
        } catch {
            print ("No reader created")
        }

        videoOutput = AVAssetReaderTrackOutput(track: videoTrack, outputSettings: [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_420YpCbCr8BiPlanarFullRange])
        videoReader.add(videoOutput)
        videoReader.startReading()


    }

}

1 Ответ

2 голосов
/ 17 января 2020

Вместо сна вы должны использовать таймер в вашем случае. Попробуйте что-то вроде следующего:

let frameDuration: TimeInterval = 1.0/60.0 // Using 60 FPS
Timer.scheduledTimer(withTimeInterval: frameDuration, repeats: true) { timer in
    guard let sampleBuffer = videoOutput.copyNextSampleBuffer() else  {
        timer.invalidate()
        return
    }

    print ("sample at time \(CMSampleBufferGetPresentationTimeStamp(sampleBuffer))")
    if let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) {
        let ciImage = CIImage(cvImageBuffer: imageBuffer)
        uiImage = UIImage(ciImage: ciImage)
        self.topView.image = uiImage
        self.topView.setNeedsDisplay()
    }
}

Проблема в вашем случае заключается в том, что вы все еще находитесь в главном потоке, когда делаете это. Поэтому, когда вы спите, вы не позволяете своей основной теме продолжаться. Так что в основном ваш основной поток строит образы и спит, но вы не даете ему времени на фактическое обновление пользовательского интерфейса.

Вы можете сделать аналогичную вещь, используя отдельный поток. Простая диспетчеризация могла бы сделать, например:

DispatchQueue(label: "Updating images").async {
    // Now on separate thread
    while let sampleBuffer = videoOutput.copyNextSampleBuffer() {
        print ("sample at time \(CMSampleBufferGetPresentationTimeStamp(sampleBuffer))")
        if let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) {
            let ciImage = CIImage(cvImageBuffer: imageBuffer)
            uiImage = UIImage(ciImage: ciImage)

            // The UI part now needs to go back to main thread
            DispatchQueue.main.async {
                self.topView.image = uiImage
                self.topView.setNeedsDisplay()
            }

            usleep(useconds_t(24000))

        }
    }
}

, но важно, чтобы вы по-прежнему обновляли (как минимум) часть UIKit в главном потоке. Возможно, даже часть CI должна быть в основном потоке. Лучше всего просто попробовать.

...