通过 AVAssetExportSession 导出 mp4 失败
Posted
技术标签:
【中文标题】通过 AVAssetExportSession 导出 mp4 失败【英文标题】:Exporting mp4 through AVAssetExportSession fails 【发布时间】:2017-01-09 09:04:37 【问题描述】:我开始说我花了很多时间搜索文档、这里和其他地方的帖子,但我无法找到解决这个问题的方法。
我使用AVAssetExportSession
导出存储在AVAsset
实例中的.mp4
文件。
我要做的是:
AVAsset
的isExportable
属性
然后我得到一个与AVAsset
实例兼容的exportPresets
数组
我使用AVAssetExportPreset1920x1080
,或者,如果不存在,我尝试使用AVAssetExportPresetPassthrough
导出媒体(仅供参考,100% 的时间,我需要的预设总是包含在列表中,但我也尝试了直通选项,但它无论如何都不起作用)
outputFileType
是AVFileTypeMPEG4
,我也尝试通过将.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)
【问题讨论】:
【参考方案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】:我遇到了同样的问题,因为我在没有音频的视频中添加了音轨。删除音轨修复了它。
【讨论】:
***.com/questions/56729461/…【参考方案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
和空segments
的CompositionTrack
解决了这个问题
if let audioTrack = exportComposition.tracks(withMediaType: .audio).first,
audioTrack.segments.isEmpty
exportComposition.removeTrack(audioTrack)
【讨论】:
【参考方案7】:我遇到了同样的错误,当我尝试 ExportSession
和 AVAssetExportPresetPassthrough
时总是失败,在我的情况下,我不能使用另一个预设,因为我必须具有与原始视频相同的分辨率
我修好了
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"
叉子对我来说很棒,它保存的分辨率与之前的视频相同
【讨论】:
以上是关于通过 AVAssetExportSession 导出 mp4 失败的主要内容,如果未能解决你的问题,请参考以下文章
AVAssetExportSession 错误 -11820