簡體   English   中英

你如何創建一個新的 AVAsset 視頻,它只包含來自另一個視頻的給定“CMTimeRange”的幀?

[英]How do you create a new AVAsset video that consists of only frames from given `CMTimeRange`s of another video?

Apple 的識別視頻軌跡的示例代碼包含以下委托回調:

func cameraViewController(_ controller: CameraViewController, didReceiveBuffer buffer: CMSampleBuffer, orientation: CGImagePropertyOrientation) {
    let visionHandler = VNImageRequestHandler(cmSampleBuffer: buffer, orientation: orientation, options: [:])
    
    if gameManager.stateMachine.currentState is GameManager.TrackThrowsState {
        DispatchQueue.main.async {
            // Get the frame of rendered view
            let normalizedFrame = CGRect(x: 0, y: 0, width: 1, height: 1)
            self.jointSegmentView.frame = controller.viewRectForVisionRect(normalizedFrame)
            self.trajectoryView.frame = controller.viewRectForVisionRect(normalizedFrame)
        }
        // Perform the trajectory request in a separate dispatch queue.
        trajectoryQueue.async {
            do {
                try visionHandler.perform([self.detectTrajectoryRequest])
                if let results = self.detectTrajectoryRequest.results {
                    DispatchQueue.main.async {
                        self.processTrajectoryObservations(controller, results)
                    }
                }
            } catch {
                AppError.display(error, inViewController: self)
            }
        }
    } 
}

但是,我沒有在detectTrajectoryRequest.results存在時繪制 UI( https://developer.apple.com/documentation/vision/vndetecttrajectoriesrequest/3675672-results ),而是有興趣使用每個結果提供的CMTimeRange來構建新視頻. 實際上,這會將原始視頻過濾為僅具有軌跡的幀。

什么是僅將帶有軌跡的幀從AVAssetReaderAVAssetWriter的好方法?

當您在捕獲的視頻幀或從文件解碼的幀中識別軌跡時,您可能不再擁有 memory 中的初始幀,因此創建僅包含軌跡的文件的最簡單方法是將原始文件保留在手邊,然后將其軌跡片段插入到AVComposition中,然后使用AVAssetExportSession導出。

此示例從相機捕獲幀,將它們編碼到文件中,同時分析它們的軌跡,20 秒后,它關閉文件,然后創建僅包含軌跡片段的新文件。

如果您對檢測預先存在的文件中的軌跡感興趣,重新連接此代碼並不難。

import UIKit
import AVFoundation
import Vision

class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate {
    let session = AVCaptureSession()
    
    var assetWriter: AVAssetWriter!
    var assetWriterInput: AVAssetWriterInput!
    var assetWriterStartTime: CMTime = .zero
    var assetWriterStarted = false

    var referenceFileURL: URL!
    var timeRangesOfInterest: [Double : CMTimeRange] = [:]

    func startWritingFile(outputURL: URL, initialSampleBuffer: CMSampleBuffer) {
        try? FileManager.default.removeItem(at: outputURL)
        assetWriter = try! AVAssetWriter(outputURL: outputURL, fileType: .mov)

        let dimensions = initialSampleBuffer.formatDescription!.dimensions
        assetWriterInput = AVAssetWriterInput(mediaType: .video, outputSettings: [AVVideoCodecKey: AVVideoCodecType.h264, AVVideoWidthKey: dimensions.width, AVVideoHeightKey: dimensions.height])
        
        assetWriter.add(assetWriterInput)

        assetWriter.startWriting()
        
        self.assetWriterStartTime = CMSampleBufferGetPresentationTimeStamp(initialSampleBuffer)
        assetWriter.startSession(atSourceTime: self.assetWriterStartTime)
    }
    
    func stopWritingFile(completion: @escaping (() -> Void)) {
        let assetWriterToFinish = self.assetWriter!
        self.assetWriterInput = nil
        self.assetWriter = nil
                
        assetWriterToFinish.finishWriting {
            print("finished writing: \(assetWriterToFinish.status.rawValue)")
            completion()
        }
    }
    
    func exportVideoTimeRanges(inputFileURL: URL, outputFileURL: URL, timeRanges: [CMTimeRange]) {
        let inputAsset = AVURLAsset(url: inputFileURL)
        let inputVideoTrack = inputAsset.tracks(withMediaType: .video).first!
        
        let composition = AVMutableComposition()
        
        let compositionTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)!
        
        var insertionPoint: CMTime = .zero
        for timeRange in timeRanges {
            try! compositionTrack.insertTimeRange(timeRange, of: inputVideoTrack, at: insertionPoint)
            insertionPoint = insertionPoint + timeRange.duration
        }
        
        let exportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality)!
        try? FileManager.default.removeItem(at: outputFileURL)
        exportSession.outputURL = outputFileURL
        exportSession.outputFileType = .mov
        exportSession.exportAsynchronously {
            print("export finished: \(exportSession.status.rawValue) - \(exportSession.error)")
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
                
        let inputDevice = AVCaptureDevice.default(for: .video)!
        let input = try! AVCaptureDeviceInput(device: inputDevice)
        let output = AVCaptureVideoDataOutput()
        
        output.setSampleBufferDelegate(self, queue: DispatchQueue.main)
        
        session.addInput(input)
        session.addOutput(output)
        
        session.startRunning()
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 20) {
            self.stopWritingFile {
                print("finished writing")
                
                let trajectoriesFileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] .appendingPathComponent("trajectories.mov")

                self.exportVideoTimeRanges(inputFileURL: self.referenceFileURL, outputFileURL: trajectoriesFileURL, timeRanges: self.timeRangesOfInterest.map { $0.1 })
            }
        }
    }
    
    // Lazily create a single instance of VNDetectTrajectoriesRequest.
    private lazy var request: VNDetectTrajectoriesRequest = {
        return VNDetectTrajectoriesRequest(frameAnalysisSpacing: .zero,
                                           trajectoryLength: 10,
                                           completionHandler: completionHandler)
    }()
    
    // AVCaptureVideoDataOutputSampleBufferDelegate callback.
    func captureOutput(_ output: AVCaptureOutput,
                       didOutput sampleBuffer: CMSampleBuffer,
                       from connection: AVCaptureConnection) {
        if !assetWriterStarted {
            self.referenceFileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] .appendingPathComponent("reference.mov")

            startWritingFile(outputURL: self.referenceFileURL, initialSampleBuffer: sampleBuffer)
            assetWriterStarted = true
        }
        
        if assetWriterInput != nil && assetWriterInput.isReadyForMoreMediaData {
            assetWriterInput.append(sampleBuffer)
        }
        
        do {
            let requestHandler = VNImageRequestHandler(cmSampleBuffer: sampleBuffer)
            try requestHandler.perform([request])
        } catch {
            // Handle the error.
        }
    }
    
    func completionHandler(request: VNRequest, error: Error?) {
        guard let request = request as? VNDetectTrajectoriesRequest else { return }

        if let results = request.results,
           results.count > 0 {
            NSLog("\(results)")
            for result in results {
                var fileRelativeTimeRange = result.timeRange
                fileRelativeTimeRange.start = fileRelativeTimeRange.start - self.assetWriterStartTime
                self.timeRangesOfInterest[fileRelativeTimeRange.start.seconds] = fileRelativeTimeRange
            }
        }
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM