【问题标题】:Exporting mp4 through AVAssetExportSession fails通过 AVAssetExportSession 导出 mp4 失败
【发布时间】:2017-01-09 09:04:37
【问题描述】:

我开始说我花了很多时间搜索文档、这里和其他地方的帖子,但我无法找到解决这个问题的方法。

我使用AVAssetExportSession 导出存储在AVAsset 实例中的.mp4 文件。 我要做的是:

  • 我检查了AVAssetisExportable 属性
  • 然后我得到一个与AVAsset 实例兼容的exportPresets 数组
  • 我使用AVAssetExportPreset1920x1080,或者,如果不存在,我尝试使用AVAssetExportPresetPassthrough 导出媒体(仅供参考,100% 的时间,我需要的预设总是包含在列表中,但我也尝试了直通选项,但它无论如何都不起作用)

outputFileTypeAVFileTypeMPEG4,我也尝试通过将.mp4 扩展名分配给文件,但没有任何效果。 我总是收到这个错误

错误域=AVFoundationErrorDomain 代码=-11838 “操作已停止” UserInfo={NSUnderlyingError=0x600000658c30 {错误 域=NSOSStatusErrorDomain 代码=-12109 "(null)"}, NSLocalizedFailureReason=此操作不支持 media., NSLocalizedDescription=操作已停止}

下面是我正在使用的代码

func _getDataFor(_ item: AVPlayerItem, completion: @escaping (Data?) -> ()) {
    guard item.asset.isExportable else {
        completion(nil)
        return
    }

    let compatiblePresets = AVAssetExportSession.exportPresets(compatibleWith: item.asset)
    var preset: String = AVAssetExportPresetPassthrough
    if compatiblePresets.contains(AVAssetExportPreset1920x1080) { preset = AVAssetExportPreset1920x1080 }

    guard
        let exportSession = AVAssetExportSession(asset: item.asset, presetName: preset),
        exportSession.supportedFileTypes.contains(AVFileTypeMPEG4) else {
        completion(nil)
        return
    }

    var tempFileUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("temp_video_data.mp4", isDirectory: false)
    tempFileUrl = URL(fileURLWithPath: tempFileUrl.path)

    exportSession.outputURL = tempFileUrl
    exportSession.outputFileType = AVFileTypeMPEG4
    let startTime = CMTimeMake(0, 1)
    let timeRange = CMTimeRangeMake(startTime, item.duration)
    exportSession.timeRange = timeRange

    exportSession.exportAsynchronously {
        print("\(exportSession.error)")
        let data = try? Data(contentsOf: tempFileUrl)
        _ = try? FileManager.default.removeItem(at: tempFileUrl)
        completion(data)
    }
}

【问题讨论】:

    标签: swift avfoundation avassetexportsession


    【解决方案1】:

    似乎将AVAsset 实例转换为AVMutableComposition 就可以了。如果有人知道这行得通的原因,请告诉我。

    这是新的_getDataFor(_:completion:) 方法实现

    func _getDataFor(_ item: AVPlayerItem, completion: @escaping (Data?) -> ()) {
        guard item.asset.isExportable else {
            completion(nil)
            return
        }
    
        let composition = AVMutableComposition()
        let compositionVideoTrack = composition.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid))
        let compositionAudioTrack = composition.addMutableTrack(withMediaType: AVMediaTypeAudio, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid))
    
        let sourceVideoTrack = item.asset.tracks(withMediaType: AVMediaTypeVideo).first!
        let sourceAudioTrack = item.asset.tracks(withMediaType: AVMediaTypeAudio).first!
        do {
            try compositionVideoTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, item.duration), of: sourceVideoTrack, at: kCMTimeZero)
            try compositionAudioTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, item.duration), of: sourceAudioTrack, at: kCMTimeZero)
        } catch(_) {
            completion(nil)
            return
        }
    
        let compatiblePresets = AVAssetExportSession.exportPresets(compatibleWith: composition)
        var preset: String = AVAssetExportPresetPassthrough
        if compatiblePresets.contains(AVAssetExportPreset1920x1080) { preset = AVAssetExportPreset1920x1080 }
    
        guard
            let exportSession = AVAssetExportSession(asset: composition, presetName: preset),
            exportSession.supportedFileTypes.contains(AVFileTypeMPEG4) else {
            completion(nil)
            return
        }
    
        var tempFileUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("temp_video_data.mp4", isDirectory: false)
        tempFileUrl = URL(fileURLWithPath: tempFileUrl.path)
    
        exportSession.outputURL = tempFileUrl
        exportSession.outputFileType = AVFileTypeMPEG4
        let startTime = CMTimeMake(0, 1)
        let timeRange = CMTimeRangeMake(startTime, item.duration)
        exportSession.timeRange = timeRange
    
        exportSession.exportAsynchronously {
            print("\(tempFileUrl)")
            print("\(exportSession.error)")
            let data = try? Data(contentsOf: tempFileUrl)
            _ = try? FileManager.default.removeItem(at: tempFileUrl)
            completion(data)
        }
    }
    

    【讨论】:

    • 嗨@Dincer,对不起,到目前为止我还没有在iOS11上尝试过,我停止了那个项目
    • 是的,这很奇怪,但是使用合成可以解决问题。
    【解决方案2】:

    我遇到了同样的问题,因为我在没有音频的视频中添加了音轨。删除音轨修复了它。

    【解决方案3】:

    斯威夫特 5:

    import Foundation
    import AVKit
    
    func getDataFor(_ asset: AVAsset, completion: @escaping (Data?) -> ()) {
        
        guard asset.isExportable,
              let sourceVideoTrack = asset.tracks(withMediaType: .video).first,
              let sourceAudioTrack = asset.tracks(withMediaType: .audio).first else {
                  completion(nil)
                  return
              }
        
        let composition = AVMutableComposition()
        let compositionVideoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid))
        let compositionAudioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid))
                
        do {
            try compositionVideoTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: asset.duration), of: sourceVideoTrack, at: .zero)
            try compositionAudioTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: asset.duration), of: sourceAudioTrack, at: .zero)
        } catch {
            completion(nil)
            return
        }
        
        let compatiblePresets = AVAssetExportSession.exportPresets(compatibleWith: composition)
        var preset = AVAssetExportPresetPassthrough
        let preferredPreset = AVAssetExportPreset1920x1080
        if compatiblePresets.contains(preferredPreset) {
            preset = preferredPreset
        }
        
        let fileType: AVFileType = .mp4
    
        guard let exportSession = AVAssetExportSession(asset: composition, presetName: preset),
              exportSession.supportedFileTypes.contains(fileType) else {
                  completion(nil)
                  return
              }
        
        let tempFileUrl = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("temp_video_data")
        
        exportSession.outputURL = tempFileUrl
        exportSession.outputFileType = fileType
        let startTime = CMTimeMake(value: 0, timescale: 1)
        let timeRange = CMTimeRangeMake(start: startTime, duration: asset.duration)
        exportSession.timeRange = timeRange
        
        exportSession.exportAsynchronously {
            print(tempFileUrl)
            print(String(describing: exportSession.error))
            let data = try? Data(contentsOf: tempFileUrl)
            try? FileManager.default.removeItem(at: tempFileUrl)
            completion(data)
        }
    }
    

    【讨论】:

      【解决方案4】:

      检查您是否正确设置了 AVURLAsset 的委托属性。

      [self.playerAsset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()];
      

      并且符合 AVAssetResourceLoaderDelegate 协议。 这就是你需要做的。

      【讨论】:

      • AVURLAsset.resourceLoader 应该与AVAssetExportSession 无关(适用于任何 AVAsset,而不仅仅是 AVURLAsset)
      • 虽然这是零意义,但实际上这就是您所要做的。将资产资源加载器上的委托设置为 self 并符合协议。您甚至不必实现任何方法或任何东西。
      【解决方案5】:

      我遇到了这个问题,因为Microphone 权限被关闭/拒绝。一旦我设置打开它,这个错误就消失了。

      【讨论】:

        【解决方案6】:

        我通过从AVMutableComposition 中删除媒体类型为.audio 和空segmentsCompositionTrack 解决了这个问题

        if let audioTrack = exportComposition.tracks(withMediaType: .audio).first,
            audioTrack.segments.isEmpty {
                exportComposition.removeTrack(audioTrack)
        }
        

        【讨论】:

          【解决方案7】:

          我遇到了同样的错误,当我尝试 ExportSessionAVAssetExportPresetPassthrough 时总是失败,在我的情况下,我不能使用另一个预设,因为我必须具有与原始视频相同的分辨率

          我修好了

          let asset = AVAsset(url: originUrl)
          let videoTrack = asset.tracks( withMediaType: .video ).first! as AVAssetTrack
          
          let videoComposition = AVMutableVideoComposition()
          videoComposition.renderSize = CGSize(
                 width: videoTrack.naturalSize.width, 
                 height: videoTrack.naturalSize.height
          )
          videoComposition.frameDuration = CMTime(
                 value: 1, 
                 timescale: videoTrack.naturalTimeScale
          )
                  
          let transformer = AVMutableVideoCompositionLayerInstruction(
                 assetTrack: videoTrack 
          )
          transformer.setOpacity(1.0, at: CMTime.zero)
                  
          let instruction = AVMutableVideoCompositionInstruction()
          instruction.timeRange = timeRange
          instruction.layerInstructions = [transformer]
          videoComposition.instructions = [instruction]
          
          guard let exportSession = AVAssetExportSession(
                 asset: asset, 
                 presetName: AVAssetExportPresetMediumQuality
          ) else {
                 return handleFailure(error: .mediaSavingError, completion: completion)
          }
                  
          exportSession.videoComposition = videoComposition
          exportSession.outputURL = outputUrl
          exportSession.outputFileType = .mp4
          exportSession.timeRange = timeRange
          
          exportSession.exportAsynchronously { [weak self] in 
              "your code"
          }
          

          叉子对我来说很棒,它保存的分辨率与之前的视频相同

          【讨论】:

            猜你喜欢
            • 2019-03-21
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-07-11
            • 1970-01-01
            • 1970-01-01
            • 2016-07-08
            • 1970-01-01
            相关资源
            最近更新 更多