AVAssetExportSession export fails non-deterministically with error: "Operation Stopped, NSLocalizedFailureReason=The video could not be composed."
AVAssetExportSession export fails non-deterministically with error: "Operation Stopped, NSLocalizedFailureReason=The video could not be composed."
我们为用户录制的视频添加了字幕,但是我们的 AVAssetExportSession 对象的导出不确定性地失败了:有时可以,有时则不能。甚至不清楚如何重现错误。
我们注意到资产轨道似乎在导出过程中丢失了。
在导出之前,如预期的那样有两个轨道(一个用于音频,一个用于视频)。但是检查 exportDidFinish
中同一文件 URL 的曲目数显示 0 曲目。所以导出过程似乎有问题。
更新:注释掉 exporter.videoComposition = mutableComposition
修复了错误,但当然没有对视频应用任何转换。所以问题似乎出在创建 AVMutableVideoComposition
上,这会导致下游在导出过程中出现问题。 AVMutableVideoComposition
上的文档和教程很少,因此即使您没有解决方案但可以推荐 Apple 以外的参考资源,这也会有所帮助。
错误:
Error Domain=AVFoundationErrorDomain Code=-11841 "Operation Stopped"
UserInfo=0x170676e80 {NSLocalizedDescription=Operation Stopped,
NSLocalizedFailureReason=The video could not be composed.}
代码:
let videoAsset = AVURLAsset(URL: fileUrl, options: nil)
let mixComposition = AVMutableComposition()
let videoTrack = mixComposition.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid))
let audioTrack = mixComposition.addMutableTrackWithMediaType(AVMediaTypeAudio, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid))
let sourceVideoTrack = videoAsset.tracksWithMediaType(AVMediaTypeVideo)[0] as! AVAssetTrack
let sourceAudioTrack = videoAsset.tracksWithMediaType(AVMediaTypeAudio)[0] as! AVAssetTrack
videoTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, videoAsset.duration), ofTrack: sourceVideoTrack, atTime: kCMTimeZero, error: nil)
audioTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, videoAsset.duration), ofTrack: sourceAudioTrack, atTime: kCMTimeZero, error: nil)
// Create something mutable???
// -- Create instruction
let instruction = AVMutableVideoCompositionInstruction()
instruction.timeRange = CMTimeRangeMake(kCMTimeZero, videoAsset.duration)
let videoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: sourceVideoTrack)
instruction.layerInstructions = [videoLayerInstruction]
let mutableComposition = AVMutableVideoComposition()
//mutableComposition.renderSize = videoTrack.naturalSize
mutableComposition.renderSize = CGSize(width: 320, height: 320)
mutableComposition.frameDuration = CMTimeMake(1, 60)
mutableComposition.instructions = [instruction]
// Animate
mutableComposition.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer, inLayer: parentLayer)
// -- Get path
let fileName = "/editedVideo-\(arc4random() % 10000).mp4"
let allPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let docsPath = allPaths[0] as! NSString
let exportPath = docsPath.stringByAppendingFormat(fileName)
let exportUrl = NSURL.fileURLWithPath(exportPath as String)!
println("Tracks before export: \(mixComposition.tracks.count). File URL: \(exportUrl)")
// -- Remove old video?
if NSFileManager.defaultManager().fileExistsAtPath(exportPath as String) {
println("Deleting existing file\n")
NSFileManager.defaultManager().removeItemAtPath(exportPath as String, error: nil)
}
// -- Create exporter
let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality)
exporter.videoComposition = mutableComposition
exporter.outputFileType = AVFileTypeMPEG4
exporter.outputURL = exportUrl
exporter.shouldOptimizeForNetworkUse = true
// -- Export video
exporter.exportAsynchronouslyWithCompletionHandler({
self.exportDidFinish(exporter)
})
func exportDidFinish(exporter: AVAssetExportSession) {
println("Exported video with status: \(getExportStatus(exporter))")
// Save video to photo album
let assetLibrary = ALAssetsLibrary()
assetLibrary.writeVideoAtPathToSavedPhotosAlbum(exporter.outputURL, completionBlock: {(url: NSURL!, error: NSError!) in
println("Saved video to album \(exporter.outputURL)")
if (error != nil) {
println("Error saving video")
}
})
// Check asset tracks
let asset = AVAsset.assetWithURL(exporter.outputURL) as? AVAsset
println("Tracks after export: \(asset!.tracks.count). File URL: \(exporter.outputURL)")
}
问题:
1) 导致问题的原因是什么,解决方案是什么?
2) 关于如何始终如一地重现错误的建议,希望有助于调试问题?
似乎解决方法是确保 AVMutableVideoCompositionLayerInstruction
中的 assetTrack
参数不是来自 AVURLAsset
对象,而是来自 addMutableTrackWithMediaType
返回的视频对象.
换句话说,这一行:
let videoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: sourceVideoTrack)
应该是:
let videoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
唉。数小时无尽的挫败感,因为有时第一行有效,有时却无效。
仍然想将赏金奖励给某人。
如果你能解释为什么第一行非确定性地失败,而不是每次都失败,或者提供更深入的 AVMutableComposition 及其相关教程 类 - 用于向用户添加文本叠加层 -录制的视频——赏金都是你的。 :)
我猜您的某些视频的 sourceVideoTrack
是:
- 不连续的曲目
- 时间范围比视频的整个时间范围短的曲目
另一方面,可变轨道 videoTrack
保证了正确的时间范围(如 AVMutableVideoCompositionInstruction
所指示),因此它始终有效。
如果将宽度或高度设置为零可能会导致崩溃并出现 Operation Stopped,NSLocalizedFailureReason=The video could not be composed
self.mutableVideoComposition.renderSize = CGSizeMake(assetVideoTrack.naturalSize.height,assetVideoTrack.naturalSize.width);
我使用 AVAssetExportPresetPassthrough
导出预设解决了这个问题,而不是使用特定分辨率或 AVAssetExportHighestQuality
…
let exportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetPassthrough)
这应该使用导出文件中导入视频的分辨率。
聚会迟到了,但这对我有用。导出将失败 "randomly"。所以然后我调试视频轨道的长度和音频轨道的长度。
我注意到当音频轨道比视频轨道长时导出会失败。
所以我做了这个改变:
let assetVideoTrack = asset.tracks(withMediaType: .video).first!
let assetAudioTrack = asset.tracks(withMediaType: .audio).first!
var validTimeRange:CMTimeRange
if assetVideoTrack.timeRange.duration.value > assetAudioTrack.timeRange.duration.value {
validTimeRange = assetVideoTrack.timeRange
} else {
validTimeRange = assetAudioTrack.timeRange
}
那么我将在此处使用该值:
let instruction = AVMutableVideoCompositionInstruction()
instruction.layerInstructions = [layerInstructions]
instruction.timeRange = validTimeRange
这已经解决了我的问题。现在 100% 有效。
导出的视频看起来不错,用它录制的音频听起来也很棒。
问题答案:
1) 导致问题的原因是什么,解决方案是什么?
2) 关于如何始终如一地重现错误的建议,希望有助于调试问题?
对我来说有以下几点:
视频和音频轨道的持续时间略有不同。在 instruction.timeRange
中使用较短的时间将导致导出失败。
设置instruction.timeRange
为两个曲目的较短的时间导出失败
我们为用户录制的视频添加了字幕,但是我们的 AVAssetExportSession 对象的导出不确定性地失败了:有时可以,有时则不能。甚至不清楚如何重现错误。
我们注意到资产轨道似乎在导出过程中丢失了。
在导出之前,如预期的那样有两个轨道(一个用于音频,一个用于视频)。但是检查 exportDidFinish
中同一文件 URL 的曲目数显示 0 曲目。所以导出过程似乎有问题。
更新:注释掉 exporter.videoComposition = mutableComposition
修复了错误,但当然没有对视频应用任何转换。所以问题似乎出在创建 AVMutableVideoComposition
上,这会导致下游在导出过程中出现问题。 AVMutableVideoComposition
上的文档和教程很少,因此即使您没有解决方案但可以推荐 Apple 以外的参考资源,这也会有所帮助。
错误:
Error Domain=AVFoundationErrorDomain Code=-11841 "Operation Stopped" UserInfo=0x170676e80 {NSLocalizedDescription=Operation Stopped, NSLocalizedFailureReason=The video could not be composed.}
代码:
let videoAsset = AVURLAsset(URL: fileUrl, options: nil)
let mixComposition = AVMutableComposition()
let videoTrack = mixComposition.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid))
let audioTrack = mixComposition.addMutableTrackWithMediaType(AVMediaTypeAudio, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid))
let sourceVideoTrack = videoAsset.tracksWithMediaType(AVMediaTypeVideo)[0] as! AVAssetTrack
let sourceAudioTrack = videoAsset.tracksWithMediaType(AVMediaTypeAudio)[0] as! AVAssetTrack
videoTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, videoAsset.duration), ofTrack: sourceVideoTrack, atTime: kCMTimeZero, error: nil)
audioTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, videoAsset.duration), ofTrack: sourceAudioTrack, atTime: kCMTimeZero, error: nil)
// Create something mutable???
// -- Create instruction
let instruction = AVMutableVideoCompositionInstruction()
instruction.timeRange = CMTimeRangeMake(kCMTimeZero, videoAsset.duration)
let videoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: sourceVideoTrack)
instruction.layerInstructions = [videoLayerInstruction]
let mutableComposition = AVMutableVideoComposition()
//mutableComposition.renderSize = videoTrack.naturalSize
mutableComposition.renderSize = CGSize(width: 320, height: 320)
mutableComposition.frameDuration = CMTimeMake(1, 60)
mutableComposition.instructions = [instruction]
// Animate
mutableComposition.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer, inLayer: parentLayer)
// -- Get path
let fileName = "/editedVideo-\(arc4random() % 10000).mp4"
let allPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let docsPath = allPaths[0] as! NSString
let exportPath = docsPath.stringByAppendingFormat(fileName)
let exportUrl = NSURL.fileURLWithPath(exportPath as String)!
println("Tracks before export: \(mixComposition.tracks.count). File URL: \(exportUrl)")
// -- Remove old video?
if NSFileManager.defaultManager().fileExistsAtPath(exportPath as String) {
println("Deleting existing file\n")
NSFileManager.defaultManager().removeItemAtPath(exportPath as String, error: nil)
}
// -- Create exporter
let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality)
exporter.videoComposition = mutableComposition
exporter.outputFileType = AVFileTypeMPEG4
exporter.outputURL = exportUrl
exporter.shouldOptimizeForNetworkUse = true
// -- Export video
exporter.exportAsynchronouslyWithCompletionHandler({
self.exportDidFinish(exporter)
})
func exportDidFinish(exporter: AVAssetExportSession) {
println("Exported video with status: \(getExportStatus(exporter))")
// Save video to photo album
let assetLibrary = ALAssetsLibrary()
assetLibrary.writeVideoAtPathToSavedPhotosAlbum(exporter.outputURL, completionBlock: {(url: NSURL!, error: NSError!) in
println("Saved video to album \(exporter.outputURL)")
if (error != nil) {
println("Error saving video")
}
})
// Check asset tracks
let asset = AVAsset.assetWithURL(exporter.outputURL) as? AVAsset
println("Tracks after export: \(asset!.tracks.count). File URL: \(exporter.outputURL)")
}
问题:
1) 导致问题的原因是什么,解决方案是什么?
2) 关于如何始终如一地重现错误的建议,希望有助于调试问题?
似乎解决方法是确保 AVMutableVideoCompositionLayerInstruction
中的 assetTrack
参数不是来自 AVURLAsset
对象,而是来自 addMutableTrackWithMediaType
返回的视频对象.
换句话说,这一行:
let videoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: sourceVideoTrack)
应该是:
let videoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
唉。数小时无尽的挫败感,因为有时第一行有效,有时却无效。
仍然想将赏金奖励给某人。
如果你能解释为什么第一行非确定性地失败,而不是每次都失败,或者提供更深入的 AVMutableComposition 及其相关教程 类 - 用于向用户添加文本叠加层 -录制的视频——赏金都是你的。 :)
我猜您的某些视频的 sourceVideoTrack
是:
- 不连续的曲目
- 时间范围比视频的整个时间范围短的曲目
另一方面,可变轨道 videoTrack
保证了正确的时间范围(如 AVMutableVideoCompositionInstruction
所指示),因此它始终有效。
如果将宽度或高度设置为零可能会导致崩溃并出现 Operation Stopped,NSLocalizedFailureReason=The video could not be composed
self.mutableVideoComposition.renderSize = CGSizeMake(assetVideoTrack.naturalSize.height,assetVideoTrack.naturalSize.width);
我使用 AVAssetExportPresetPassthrough
导出预设解决了这个问题,而不是使用特定分辨率或 AVAssetExportHighestQuality
…
let exportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetPassthrough)
这应该使用导出文件中导入视频的分辨率。
聚会迟到了,但这对我有用。导出将失败 "randomly"。所以然后我调试视频轨道的长度和音频轨道的长度。
我注意到当音频轨道比视频轨道长时导出会失败。
所以我做了这个改变:
let assetVideoTrack = asset.tracks(withMediaType: .video).first!
let assetAudioTrack = asset.tracks(withMediaType: .audio).first!
var validTimeRange:CMTimeRange
if assetVideoTrack.timeRange.duration.value > assetAudioTrack.timeRange.duration.value {
validTimeRange = assetVideoTrack.timeRange
} else {
validTimeRange = assetAudioTrack.timeRange
}
那么我将在此处使用该值:
let instruction = AVMutableVideoCompositionInstruction()
instruction.layerInstructions = [layerInstructions]
instruction.timeRange = validTimeRange
这已经解决了我的问题。现在 100% 有效。
导出的视频看起来不错,用它录制的音频听起来也很棒。
问题答案:
1) 导致问题的原因是什么,解决方案是什么?
2) 关于如何始终如一地重现错误的建议,希望有助于调试问题?
对我来说有以下几点:
视频和音频轨道的持续时间略有不同。在
instruction.timeRange
中使用较短的时间将导致导出失败。设置
instruction.timeRange
为两个曲目的较短的时间导出失败