Экспорт AVAssetExportSession завершается неудачно недетерминированным образом с ошибкой: "Операция остановлена, NSLocalizedFailureReason= Видео не может быть создано".
Мы добавляем субтитры к видео, записанному пользователем, но экспорт нашим объектом AVAssetExportSession завершается неудачно недетерминированно: иногда это работает, а иногда нет. Непонятно даже, как воспроизвести ошибку.
Мы заметили, что треки активов теряются во время экспорта.
Перед экспортом, как и ожидалось, есть две дорожки (одна для аудио, одна для видео). Но проверка количества дорожек для того же файла URL в exportDidFinish
показывает 0 треков. Так что что-то не так с процессом экспорта.
Обновление: комментирование exporter.videoComposition = mutableComposition
исправляет ошибку, но, конечно, к видео не применяются преобразования. Таким образом, проблема, кажется, заключается в создании AVMutableVideoComposition
, что вызывает проблемы вниз по течению во время экспорта. Документация и учебники по AVMutableVideoComposition
являются редкими, поэтому, даже если у вас нет решения, но вы можете порекомендовать источники для справки за пределами Apple, это будет полезно.
Ошибка:
Ошибка Domain=AVFoundationErrorDomain Код =-11841 "Операция остановлена" UserInfo=0x170676e80 {NSLocalizedDescription= Операция остановлена, NSLocalizedFailureReason= Видео не может быть создано.}
Код:
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) Предложения о том, как последовательно воспроизвести ошибку, которая, как мы надеемся, поможет отладить проблему?
4 ответа
То, что кажется лечением, - это assetTrack
параметр в AVMutableVideoCompositionLayerInstruction
не из AVURLAsset
объект, но из видео объекта, возвращенного addMutableTrackWithMediaType
,
Другими словами, эта строка:
let videoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: sourceVideoTrack)
Должно быть:
let videoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
Argh. Часы бесконечного разочарования, потому что иногда первая строка работала, а иногда нет.
Еще хотел бы наградить кого-то.
Если вы можете объяснить, почему первая строка завершилась неудачно недетерминированно, а не каждый раз, или предоставить более подробное руководство по AVMutableComposition и связанным с ним классам - с целью добавления текстовых оверлеев к видео, записанным пользователем, - щедрость остается за вами.,:)
Я решил эту проблему с помощью AVAssetExportPresetPassthrough
экспортировать предустановки вместо использования определенного разрешения или AVAssetExportHighestQuality
...
let exportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetPassthrough)
Это должно использовать разрешение импортированного видео в экспортированном файле.
Я предполагаю, что некоторые из ваших видео sourceVideoTrack
Это либо:
- треки, которые не являются смежными
- дорожки с временным диапазоном, меньшим, чем весь временной диапазон видео
Изменчивый трек videoTrack
с другой стороны, гарантируется правильный диапазон времени (как указано в AVMutableVideoCompositionInstruction
) так что всегда работает.
Поздно на вечеринку, но вот что сработало для меня. Экспорт завершится ошибкой "случайно". Затем я отлаживаю длину видеодорожки и длину звуковой дорожки.
Я заметил, что когда звуковая дорожка была длиннее, чем видеодорожка, экспорт не удался.
Итак, я внес это изменение:
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
к более короткому времени двух треков, и экспорт не удастся.
Если U установить ширину или высоту равными нулю, это может привести к сбою при операции "Остановлено", NSLocalizedFailureReason= Видео не может быть создано
self.mutableVideoComposition.renderSize = CGSizeMake(assetVideoTrack.naturalSize.height,assetVideoTrack.naturalSize.width);