2015-06-27 11 views
13

Aggiungiamo i sottotitoli a un video registrato dall'utente, ma l'esportazione dal nostro oggetto AVAssetExportSession non riesce in maniera non deterministica: a volte funziona e talvolta no. Non è ancora chiaro come riprodurre l'errore.L'esportazione di AVAssetExportSession non riesce in modo non deterministico con errore: "Operazione interrotta, NSLocalizedFailureReason = Il video non può essere composto."

Abbiamo notato che le tracce delle risorse sembrano andare perse durante l'esportazione.

Prima dell'esportazione, ci sono due tracce (una per audio, una per video) come previsto. Tuttavia, il controllo del numero di tracce per lo stesso URL file in exportDidFinish mostra 0 tracce. Quindi qualcosa sembra sbagliato con il processo di esportazione.

Aggiornamento: Commentando exporter.videoComposition = mutableComposition si corregge l'errore, ma ovviamente nessuna trasformazione viene applicata al video. Quindi il problema sembra essere nella creazione di AVMutableVideoComposition, che causa problemi a valle durante l'esportazione. La documentazione e le esercitazioni su AVMutableVideoComposition sono sparse, quindi anche se non si dispone di una soluzione, ma si potrebbero consigliare fonti di riferimento oltre a Apple, sarebbe utile.

Errore:

Error Domain=AVFoundationErrorDomain Code=-11841 "Operation Stopped" UserInfo=0x170676e80 {NSLocalizedDescription=Operation Stopped, NSLocalizedFailureReason=The video could not be composed.}

Codice:

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)") 
} 

Domande:

1) Che cosa sta causando il problema, e che cosa è la soluzione?

2) Suggerimenti su come riprodurre l'errore in modo coerente, che sperabilmente aiuta a risolvere il problema?

+1

Considerare di aggiungere la gestione degli errori alle chiamate 'insertTimeRange' e alle chiamate NSFileManager; impostare un errore restituito a 'nil' non è mai una buona idea, poiché se qualcosa va storto, non ne sentirai più. Personalmente, non mi preoccuperei nemmeno di lavorare su questo codice fino a che non sia stato fatto: chiunque getta allegramente il controllo degli errori è semplicemente ostinato. Quindi, considera di aggiungere più logging al processo in modo da sapere se tutto è andato bene al punto in cui inizia l'esportazione. - Inoltre mi chiedo del tuo approccio alla randomizzazione ai nomi dei file, ma dubito che questo sia il problema .. – matt

+0

@matt sei al 100% giusto sui messaggi di errore. Non sono sicuro, però, quale altro tipo di registrazione includere oltre a verificare che esistano più tracce: cosa consiglia? – Crashalot

+0

@matt ha aggiunto i messaggi di errore, ma nulla viene attivato con "insertTimeRange". L'errore NSFileManager non è rilevante perché in realtà commentiamo quella riga poiché stiamo utilizzando uno schema di nomi casuali durante il test e il monitoraggio dei log per garantire che i conflitti tra i file non costituiscano un problema (ad esempio, "Eliminazione di file esistenti" non viene mai stampato). – Crashalot

risposta

14

Quello che sembra essere la cura è fare in modo il parametro assetTrack in AVMutableVideoCompositionLayerInstruction non è dall'oggetto AVURLAsset, ma dalla oggetto video restituito da addMutableTrackWithMediaType.

In altre parole, questa linea:

let videoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: sourceVideoTrack) 

dovrebbe essere:

let videoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack) 

Argh. Ore di frustrazioni infinite perché a volte la prima linea funzionava, ea volte no.

Ancora piacerebbe assegnare la generosità a qualcuno.

Se è possibile spiegare perché la prima riga non è riuscita in modo non deterministico, invece di ogni volta, o fornire un tutorial più approfondito in AVMutableComposition e le relative classi - allo scopo di aggiungere overlay di testo ai video registrati dall'utente - il la taglia è tutta tua:)

+1

Grazie, ho avuto lo stesso problema ma non ho trovato il motivo. Problema visualizzato quando ho utilizzato l'output di una sessione di esportazione come input per un'altra composizione. – laltin

+0

Questo ha anche risolto un altro problema con la concatinazione di più video. Prima di applicare questa correzione c'erano dei fotogrammi vuoti o il primo fotogramma è stato ripetuto per un breve periodo (che appare come passato) in transizione da un video al successivo – laltin

+1

Ciò potrebbe essere dovuto alla lunghezza del video. Mi piace se la scala temporale accetta solo 60 secondi di video e stiamo utilizzando video di durata superiore a 1 minuto. Succede nel mio caso e si blocca con lo stesso codice di errore "-11841" –

6

Sto indovinando che alcuni dei tuoi video sourceVideoTrack s sono o:

  • tracce che sono non contigui
  • tracce con intervallo di tempo più brevi di intervallo di tempo intero del video

La traccia mutabile videoTrack, d'altra parte, è garantita l'intervallo di tempo corretto (come indicato dallo AVMutableVideoCompositionInstruction) quindi funziona sempre.

0

se U impostare la larghezza o l'altezza a zero potrebbe portare a dura con un'operazione interrotta, NSLocalizedFailureReason = Il video non può essere composta

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

Ho risolto questo problema utilizzando il AVAssetExportPresetPassthrough esportazione preset piuttosto rispetto all'utilizzo di una risoluzione specifica o AVAssetExportHighestQuality ...

let exportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetPassthrough) 

Questo dovrebbe utilizzare la risoluzione del video importato nel file esportato.