Exporting time lapse with AVAssetExportSession results in black video

那年仲夏 提交于 2020-12-25 01:02:13

问题


I need to be able to merge videos taken with the time lapse function in the Camera app on iOS and export as a single video.

However, even if I try to export a single, unchanged time lapse video to the Photo Library, it saves as a completely black video (with the correct duration). Here is the sample code I wrote to just export a single, unchanged video (most of which is adapted from a Ray Wenderlich tutorial):

@IBAction func saveVideo(_ sender: UIBarButtonItem) {
    // 1 - Early exit if there's no video file selected

    guard let videoAsset = self.avAsset else {
        let alert = UIAlertController(title: "Error", message: "Failed to load video asset.", preferredStyle: .alert)
        let cancelAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
        alert.addAction(cancelAction)
        self.present(alert, animated: true, completion: nil)

        return
    }

    // 2 - Create AVMutableComposition object. This object will hold your AVMutableCompositionTrack instances.
    print("Preparing AVMutableComposition...")
    let mixComposition = AVMutableComposition()

    // 3 - Video track
    let videoTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)


    do {
        if let videoAssetTrack = videoAsset.tracks(withMediaType: .video).first {
            try videoTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero, videoAsset.duration), of: videoAssetTrack, at: kCMTimeZero)
        }

        if let audioAssetTrack = videoAsset.tracks(withMediaType: .audio).first {
            let audioTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
            try audioTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero, videoAsset.duration), of: audioAssetTrack, at: kCMTimeZero)
        }
    } catch let error as NSError {
        self.presentAlert(title: "Export Error", message: "Unable to complete export due to the following error: \(error). Please try again.", block: nil)
        print("error: \(error)")
    }

    // 3.1 - Create AVMutableVideoCompositionInstruction
    let mainInstruction = AVMutableVideoCompositionInstruction()
    mainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, videoAsset.duration)

    // 3.2 - Create an AVMutableVideoCompositionLayerInstruction for the video track and fix the orientation.
    let videoLayerInstruction: AVMutableVideoCompositionLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack!)
    let videoAssetTrack = videoAsset.tracks(withMediaType: .video).first
    var assetOrientation: UIImageOrientation = .up
    var isPortrait = false
    let t = videoAssetTrack!.preferredTransform
    if t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0 {
        assetOrientation = .right
        isPortrait = true
    } else if t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0 {
        assetOrientation = .left
        isPortrait = true
    } else if t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0 {
        assetOrientation = .up
    } else if t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0 {
        assetOrientation = .down
    }

    videoLayerInstruction.setTransform(videoAssetTrack!.preferredTransform, at: kCMTimeZero)
    videoLayerInstruction.setOpacity(0.0, at: videoAsset.duration)

    // 3.3 - Add instructions
    mainInstruction.layerInstructions = [videoLayerInstruction]

    let mainComposition = AVMutableVideoComposition()
    mainComposition.instructions = [mainInstruction]
    mainComposition.frameDuration = CMTimeMake(1, 30)

    var naturalSize: CGSize
    if isPortrait {
        naturalSize = CGSize(width: videoAssetTrack!.naturalSize.height, height: videoAssetTrack!.naturalSize.width)
    } else {
        naturalSize = videoAssetTrack!.naturalSize
    }

    mainComposition.renderSize = CGSize(width: naturalSize.width, height: naturalSize.height)

    // set up file destination
    let tempName = "temp-thread.mov"
    let tempURL = URL(fileURLWithPath: (NSTemporaryDirectory() as NSString).appendingPathComponent(tempName))
    do {
        if FileManager.default.fileExists(atPath: tempURL.path) {
            try FileManager.default.removeItem(at: tempURL)
        }
    } catch {
        print("Error removing temp file.")
    }
    // create final video using export session
    guard let exportSession = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality) else { return }
    exportSession.outputURL = tempURL
    exportSession.outputFileType = AVFileType.mov
    exportSession.shouldOptimizeForNetworkUse = true
    exportSession.videoComposition = mainComposition
    print("Exporting video...")
    exportSession.exportAsynchronously {
        DispatchQueue.main.async {
            switch exportSession.status {
            // Success
            case .completed:
                print("Saving to Photos Library...")
                PHPhotoLibrary.shared().performChanges({
                    PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: exportSession.outputURL!)
                }) { success, error in
                    if success {
                        print("Added video to library - success: \(success), error: \(String(describing: error?.localizedDescription))")
                    } else {
                        print("Added video to library - success: \(success), error: \(String(describing: error!.localizedDescription))")
                    }

                    let _ = try? FileManager.default.removeItem(at: tempURL)
                }
                print("Export session completed")
            // Status other than success
            case .cancelled, .exporting, .failed, .unknown, .waiting:
                print("Export status: \(exportSession.status.rawValue)")
                print("Reason: \(String(describing: exportSession.error))")
            }
        }
    }
}

Why would the resulting video show up completely black? I can't seem to find much documentation on Apple's time lapse videos, so I'm not sure why they might be different than a regular video file. They seem to have a frame rate of 30fps and if I inspect one on my Mac, it's just a regular QuickTime movie file without an audio channel. Any ideas? Exporting any other video with this code (even ones without audio) works flawlessly.


回答1:


The problem code is:

videoLayerInstruction.setTransform(videoAssetTrack!.preferredTransform, at: kCMTimeZero)

This transformation is eligible to "up" (default) orientation only and it makes a video completely black for the other orientations. You should make a correct transformation for each orientation e.g.:

var transform = videoAssetTrack.preferredTransform
// Right
if transform.a == 0 && transform.b == 1.0 && transform.c == -1.0 && transform.d == 0 {
    isPortrait = true
    let rotate = CGAffineTransform.identity.translatedBy(x: videoAssetTrack.naturalSize.height - videoAssetTrack.preferredTransform.tx, y: -videoAssetTrack.preferredTransform.ty)
    transform = videoAssetTrack.preferredTransform.concatenating(rotate)
}
// Left
else if transform.a == 0 && transform.b == -1.0 && transform.c == 1.0 && transform.d == 0 {
    isPortrait = true
    let rotate = CGAffineTransform.identity.translatedBy(x:  -videoAssetTrack.preferredTransform.tx, y: videoAssetTrack.naturalSize.width - videoAssetTrack.preferredTransform.ty)
    transform = videoAssetTrack.preferredTransform.concatenating(rotate)
}
// Up
else if transform.a == 1.0 && transform.b == 0 && transform.c == 0 && transform.d == 1.0 {
    transform = videoAssetTrack.preferredTransform
}
// Down
else if transform.a == -1.0 && transform.b == 0 && transform.c == 0 && transform.d == -1.0 {
    let rotate = CGAffineTransform.identity.translatedBy(x: videoAssetTrack.naturalSize.width - videoAssetTrack.preferredTransform.tx, y: videoAssetTrack.naturalSize.height - videoAssetTrack.preferredTransform.ty)
    transform = videoAssetTrack.preferredTransform.concatenating(rotate)
}

videoLayerInstruction.setTransform(transform, at: .zero)


来源:https://stackoverflow.com/questions/46243096/exporting-time-lapse-with-avassetexportsession-results-in-black-video

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!