Экспорт AVAssetExportSession не может быть детерминирован с ошибкой: "Операция остановлена, NSLocalizedFailureReason = Видео не может быть составлено".

Мы добавляем субтитры к видео, записанному пользователем, но экспорт нашего объекта AVAssetExportSession не проходит детерминистически: иногда он работает, а иногда и нет. Неясно даже, как воспроизвести ошибку.

Мы заметили, что трассы активов, похоже, теряются во время экспорта.

Перед экспортом есть два трека (один для аудио, один для видео), как и ожидалось. Но при проверке количества дорожек для одного и того же файла URL в exportDidFinish отображается 0 треков. Так что что-то кажется неправильным в процессе экспорта.

Обновление: комментарий exporter.videoComposition = mutableComposition исправляет ошибку, но, конечно же, никакие преобразования не применяются к видео. Таким образом, проблема заключается в создании AVMutableVideoComposition, что вызывает проблемы в процессе экспорта. Документация и руководства по AVMutableVideoComposition разрежены, поэтому даже если у вас нет решения, но вы можете рекомендовать источники для справки за пределами Apple, это было бы полезно.

Ошибка:

Ошибка домена = AVFoundationErrorDomain Code = -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) Предложения о том, как последовательно воспроизводить ошибку, которая, надеюсь, поможет отладить проблему?

Ответ 1

Кажется, что излечение заключается в том, что параметр assetTrack в AVMutableVideoCompositionLayerInstruction не относится к объекту AVURLAsset, а к объекту видео, возвращаемому addMutableTrackWithMediaType.

Другими словами, эта строка:

let videoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: sourceVideoTrack)

Должно быть:

let videoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)

Argh. Часы бесконечного расстройства, потому что иногда работала первая линия, а иногда и не делала.

По-прежнему хотелось бы наградить кого-то наградой.

Если вы можете объяснить, почему первая строка провалилась не детерминистически, а не каждый раз, или предоставила более глубокий учебник в AVMutableComposition и связанные с ним классы - для добавления текстовых оверлеев к записанным пользователем видео - щедрость это все твое.:)

Ответ 2

Я предполагаю, что некоторые из ваших видеороликов sourceVideoTrack:

  • треки, которые не являются непрерывными
  • дорожки с временным диапазоном короче, чем диапазон времени видео

Сменяемая дорожка videoTrack, с другой стороны, гарантирует правильный временной диапазон (согласно инструкциям AVMutableVideoCompositionInstruction), поэтому он всегда работает.

Ответ 3

если U задает ширину или высоту до нуля, может привести к сбою с помощью Операция остановлена, NSLocalizedFailureReason = Видео не может быть составлено

self.mutableVideoComposition.renderSize = CGSizeMake(assetVideoTrack.naturalSize.height,assetVideoTrack.naturalSize.width);