AVAssetExportSession not working on devices, but working on simulator (AVFoundationErrorDomain Code = -11800, Unknown Error code -12780)

一曲冷凌霜 提交于 2021-02-05 09:23:58

问题


EDIT: To make things easier to anyone interested in checking out this problem, I added a demo project to this github repository.

Issue

I've seen several questions that had the same error, but none of the solutions found there helped me. Figured I'd try my luck.

I'm trying to export a video as is, mainly to learn about AVFoundation and AVAssetExportSession. My export works perfectly fine on the simulator, but does not work on any iOS device I've tried (namely an iPhone X and an iPhone XR running iOS 12 each). I Mainly followed a Ray Wenderleich tutorial found on this link to perform the Video Exporting: https://www.raywenderlich.com/2734-avfoundation-tutorial-adding-overlays-and-animations-to-videos

Would appreciate any help on the topic. My code is as follows:

Retrieving the URL of a video I've added to the App Bundle called Demo.mp4:

@objc func export() {
    let urlString = Bundle.main.path(forResource: "Demo", ofType: ".mp4")!
    let url = URL(fileURLWithPath: urlString)
    ExportManager.shared.exportWithAVFoundation(url:url) { (outputUrl, errorString) in
        if let outputUrl = outputUrl {
            self.playVideo(url: outputUrl)
        } else if let errorString = errorString {
            print("ERROR: \(errorString)")
        }
    }
}

My Exporting function in ExportManager is as follows (Sorry quite long)

func exportWithAVFoundation(url: URL, completion: @escaping (_ outputUrl: URL?, _ errorString: String?) -> ()) {
    let asset = AVAsset(url: url)
    print("URL IS \(url)")
    guard let avAssetTrack = asset.tracks(withMediaType: .video).first else {
        completion(nil, "Couldn't Create Asset Track")
        return
    }

    let mutableComposition = AVMutableComposition()
    guard let videoTrack = mutableComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else { return }
    try? videoTrack.insertTimeRange(CMTimeRange(start: .zero, duration: asset.duration), of: avAssetTrack, at: .zero)
    videoTrack.preferredTransform = avAssetTrack.preferredTransform

    if let audioAssetTrack = asset.tracks(withMediaType: .audio).first {
        let audioTrack = mutableComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
        try? audioTrack?.insertTimeRange(CMTimeRange(start: .zero, duration: asset.duration), of: audioAssetTrack, at: .zero)
    }

    let mainInstruction = AVMutableVideoCompositionInstruction()
    mainInstruction.timeRange = CMTimeRange(start: .zero, duration: asset.duration)

    let videoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)

    // Fix video orientation
    var videoAssetOrientation = UIImage.Orientation.up
    var isVideoAssetPortrait = false
    let videoTransform = avAssetTrack.preferredTransform

    switch (videoTransform.a, videoTransform.b, videoTransform.c, videoTransform.c) {
    case (0, 1.0, -1.0, 0):
        videoAssetOrientation = .right
        isVideoAssetPortrait = true
    case(0, -1.0, 1.0, 0):
        videoAssetOrientation = .left
        isVideoAssetPortrait = true
    case(1.0, 0, 0, 1.0):
        videoAssetOrientation = .up
    case(-1.0, 0, 0, -1.0):
        videoAssetOrientation = .down
    default:
        break
    }

    var naturalSize = avAssetTrack.naturalSize
    switch (videoAssetOrientation, isVideoAssetPortrait) {
    case (.right, true):
        naturalSize = CGSize(width: avAssetTrack.naturalSize.height, height: avAssetTrack.naturalSize.width)
    case (.left, true):
        naturalSize = CGSize(width: avAssetTrack.naturalSize.height, height: avAssetTrack.naturalSize.width)
    case (.leftMirrored, true):
        naturalSize = CGSize(width: avAssetTrack.naturalSize.height, height: avAssetTrack.naturalSize.width)
    case (.rightMirrored, true):
        naturalSize = CGSize(width: avAssetTrack.naturalSize.height, height: avAssetTrack.naturalSize.width)
    default:
        break
    }

    videoLayerInstruction.setTransform(avAssetTrack.preferredTransform, at: .zero)
    videoLayerInstruction.setOpacity(0, at: asset.duration)

    mainInstruction.layerInstructions = [videoLayerInstruction]

    let mainCompositionInstruction = AVMutableVideoComposition()

    mainCompositionInstruction.renderSize = naturalSize
    mainCompositionInstruction.instructions = [mainInstruction]
    mainCompositionInstruction.frameDuration = CMTimeMake(value: 1, timescale: 30);

    let documentsDirectoryURL = createPath()

    guard let exporter = AVAssetExportSession(asset: mutableComposition, presetName: AVAssetExportPresetHighestQuality) else {
        print("Couldnt create AVAssetExportSession")

        completion(nil, "Couldn't Create AVAssetExportSession")
        return
    }

    exporter.outputURL = documentsDirectoryURL
    exporter.outputFileType = .mov
    exporter.shouldOptimizeForNetworkUse = true
    exporter.videoComposition = mainCompositionInstruction

    exporter.exportAsynchronously {
        if let error = exporter.error {
            print(error)
            completion(nil, error.localizedDescription)
            return
        }

        completion(exporter.outputURL, nil)
        print("Finished Exporting")
    }
}

Some things I've tried to do have been to add an AudioTrack to the composition (which I didn't include previously). Hasn't helped it work on an actual device, but at least my exported video has audio now.

I also tried to change the Preset Quality to Passthrough as opposed to Highest Quality, as I read from other threads that this could help, but to no avail.

EDIT:

Added createPath function:

func createPath() -> URL {
    let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
    let documentDirectory = paths.first!
    let myPathDocs = documentDirectory.appending("FinalVideo.mov")
    let url = URL(fileURLWithPath: myPathDocs)

    if FileManager.default.fileExists(atPath: myPathDocs) {
        try? FileManager.default.removeItem(atPath: myPathDocs)
    }

    return url
}

Note: createPath() just creates a valid path in the directory to save the exported video in. If a file exists at that path prior to exporting, it gets deleted.


回答1:


The problem is that you're appending string to another string resulting in wrong path to the file like file:///var/mobile/Containers/Data/Application/<stripped>/DocumentsFinalVideo.mov

You should use appendingPathComponent() instead:

func createPath() -> URL {
    let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
    let documentDirectory = URL(fileURLWithPath: paths.first!)
    let url = documentDirectory.appendingPathComponent("FinalVideo.mov")

    if FileManager.default.fileExists(atPath: url.path) {
        try? FileManager.default.removeItem(at: url)
    }

    return url
}



回答2:


You must use, default method: appendingPathComponent()



来源:https://stackoverflow.com/questions/57185068/avassetexportsession-not-working-on-devices-but-working-on-simulator-avfoundat

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