繁体   English   中英

使用 AVAssetExportSession 导出延时会导致黑色视频

[英]Exporting time lapse with AVAssetExportSession results in black video

我需要能够在 iOS 上的相机应用程序中合并使用延时拍摄功能拍摄的视频并导出为单个视频。

但是,即使我尝试将单个未更改的延时视频导出到照片库,它也会保存为全黑视频(具有正确的持续时间)。 这是我编写的示例代码,用于导出单个未更改的视频(其中大部分改编自 Ray Wenderlich 教程):

@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))")
            }
        }
    }
}

为什么生成的视频会显示为全黑? 我似乎找不到关于 Apple 延时视频的太多文档,所以我不确定为什么它们可能与常规视频文件不同。 它们的帧速率似乎为 30fps,如果我在 Mac 上检查一个,它只是一个没有音频通道的普通 QuickTime 电影文件。 有什么想法吗? 使用此代码导出任何其他视频(即使是没有音频的视频)也能完美运行。

问题代码是:

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

此转换仅适用于“向上”(默认)方向,并且它使其他方向的视频完全变黑。 您应该为每个方向进行正确的转换,例如:

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)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM