ios - La exportación de AVAssetExportSession falla de manera no determinista con el error: "Operación detenida, NSLocalizedFailureReason=El video no se pudo componer".
swift avfoundation (4)
Añadimos subtítulos a un video grabado por el usuario, pero la exportación de nuestro objeto AVAssetExportSession falla de manera no determinista: a veces funciona, y otras no. No está claro incluso cómo reproducir el error.
Notamos que las pistas de activos parecen perderse durante la exportación.
Antes de exportar, hay dos pistas (una para audio, una para video) como se esperaba. Pero al verificar el número de pistas para la misma URL del archivo en exportDidFinish
muestra 0 pistas. Así que algo parece mal con el proceso de exportación.
Actualización: al comentar exporter.videoComposition = mutableComposition
corrige el error, pero, por supuesto, no se aplican transformaciones al video. Por lo tanto, el problema parece estar en la creación de AVMutableVideoComposition
, que causa problemas descendentes durante la exportación. La documentación y los tutoriales en AVMutableVideoComposition
son escasos, por lo que incluso si no tiene una solución pero podría recomendar fuentes de referencia más allá de Apple, eso sería útil.
Error:
Error de dominio = AVFoundationErrorDomain Código = -11841 "Operación detenida" UserInfo = 0x170676e80 {NSLocalizedDescription = Operación detenida, NSLocalizedFailureReason = El video no se pudo componer.}
Código:
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)")
}
Preguntas:
1) ¿Qué está causando el problema y cuál es la solución?
2) ¿Sugerencias sobre cómo reproducir el error de forma coherente, lo que esperamos ayude a solucionar el problema?
Lo que parece ser la assetTrack
es asegurarse de que el parámetro assetTrack
en AVMutableVideoCompositionLayerInstruction
no sea del objeto AVURLAsset
, sino del objeto de video devuelto por addMutableTrackWithMediaType
.
En otras palabras, esta línea:
let videoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: sourceVideoTrack)
Debiera ser:
let videoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
Argh. Horas de frustración interminable porque a veces la primera línea funcionaba y otras no.
Todavía me gustaría otorgar la recompensa a alguien.
Si puede explicar por qué la primera línea falló de manera no determinista, en lugar de hacerlo cada vez, o puede proporcionar un tutorial más profundo sobre AVMutableComposition y sus clases relacionadas, con el fin de agregar superposiciones de texto a los videos grabados por el usuario, la recompensa es suya. . :)
Si U establece el ancho o la altura a cero, se puede producir un bloqueo con la Operación detenida, NSLocalizedFailureReason = El video no se pudo componer
self.mutableVideoComposition.renderSize = CGSizeMake(assetVideoTrack.naturalSize.height,assetVideoTrack.naturalSize.width);
Supongo que algunos de los videos de sourceVideoTrack
de tus videos son:
- pistas que no son contiguas
- Pistas con un rango de tiempo más corto que el rango de tiempo completo del video
Por otro lado, la pista de videoTrack
mutable, pista, tiene garantizado el rango de tiempo correcto (como lo indica AVMutableVideoCompositionInstruction
) por lo que siempre funciona.
AVAssetExportPresetPassthrough
este problema usando el AVAssetExportPresetPassthrough
exportación AVAssetExportPresetPassthrough
lugar de usar una resolución específica o AVAssetExportHighestQuality
...
let exportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetPassthrough)
Esto debería usar la resolución del video importado en el archivo exportado.