Mezcla de imágenes y video mediante AVFoundation

Estoy intentando dividir las imágenes en un video preexistente para crear un nuevo file de video usando AVFoundation en Mac.

Hasta ahora, he leído el ejemplo de documentation de Apple,

ASSETWriterInput para hacer video desde UIImages en problemas de Iphone

Mezcla video con image estática en CALayer usando AVVideoCompositionCoreAnimationTool

AVFoundation Tutorial: Adición de superposiciones y animaciones a videos y algunos otros enlaces SO

Ahora, estos han demostrado ser bastante útiles a veces, pero mi problema es que no estoy creando una marca de agua estática o una superposition exactamente quiero poner imágenes entre partes del video. Hasta ahora he logrado get el video y crear secciones en blanco para que estas imágenes se inserten y exportarlas.

Mi problema es hacer que las imágenes se inserten en estas secciones en blanco. La única manera que puedo ver de hacerlo de manera factible es crear una serie de capas animadas para cambiar su opacidad en los momentos correctos, pero no puedo hacer que la animation funcione.

El código a continuación es lo que estoy usando para crear segmentos de video y animaciones de capas.

//https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/03_Editing.html#//apple_ref/doc/uid/TP40010188-CH8-SW7 // let's start by making our video composition AVMutableComposition* mutableComposition = [AVMutableComposition composition]; AVMutableCompositionTrack* mutableCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo prefernetworkingTrackID:kCMPersistentTrackID_Invalid]; AVMutableVideoComposition* mutableVideoComposition = [AVMutableVideoComposition videoCompositionWithPropertiesOfAsset:gVideoAsset]; // if the first point's frame doesn't start on 0 if (gFrames[0].startTime.value != 0) { DebugLog("Inserting vid at 0"); // then add the video track to the composition track with a time range from 0 to the first point's startTime [mutableCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, gFrames[0].startTime) ofTrack:gVideoTrack atTime:kCMTimeZero error:&gError]; } if(gError) { DebugLog("Error inserting original video segment"); GetError(); } // create our parent layer and video layer CALayer* parentLayer = [CALayer layer]; CALayer* videoLayer = [CALayer layer]; parentLayer.frame = CGRectMake(0, 0, 1280, 720); videoLayer.frame = CGRectMake(0, 0, 1280, 720); [parentLayer addSublayer:videoLayer]; // create an offset value that should be added to each point where a new video segment should go CMTime timeOffset = CMTimeMake(0, 600); // loop through each additional frame for(int i = 0; i < gFrames.size(); i++) { // create an animation layer and assign it's content to the CGImage of the frame CALayer* Frame = [CALayer layer]; Frame.contents = (__bridge id)gFrames[i].frameImage; Frame.frame = CGRectMake(0, 720, 1280, -720); DebugLog("inserting empty time range"); // add frame point to the composition track starting at the point's start time // insert an empty time range for the duration of the frame animation [mutableCompositionTrack insertEmptyTimeRange:CMTimeRangeMake(CMTimeAdd(gFrames[i].startTime, timeOffset), gFrames[i].duration)]; // update the time offset by the duration timeOffset = CMTimeAdd(timeOffset, gFrames[i].duration); // make the layer completely transparent Frame.opacity = 0.0f; // create an animation for setting opacity to 0 on start CABasicAnimation* frameAnim = [CABasicAnimation animationWithKeyPath:@"opacity"]; frameAnim.duration = 1.0f; frameAnim.repeatCount = 0; frameAnim.autoreverses = NO; frameAnim.fromValue = [NSNumber numberWithFloat:0.0]; frameAnim.toValue = [NSNumber numberWithFloat:0.0]; frameAnim.beginTime = AVCoreAnimationBeginTimeAtZero; frameAnim.speed = 1.0f; [Frame addAnimation:frameAnim forKey:@"animateOpacity"]; // create an animation for setting opacity to 1 frameAnim = [CABasicAnimation animationWithKeyPath:@"opacity"]; frameAnim.duration = 1.0f; frameAnim.repeatCount = 0; frameAnim.autoreverses = NO; frameAnim.fromValue = [NSNumber numberWithFloat:1.0]; frameAnim.toValue = [NSNumber numberWithFloat:1.0]; frameAnim.beginTime = AVCoreAnimationBeginTimeAtZero + CMTimeGetSeconds(gFrames[i].startTime); frameAnim.speed = 1.0f; [Frame addAnimation:frameAnim forKey:@"animateOpacity"]; // create an animation for setting opacity to 0 frameAnim = [CABasicAnimation animationWithKeyPath:@"opacity"]; frameAnim.duration = 1.0f; frameAnim.repeatCount = 0; frameAnim.autoreverses = NO; frameAnim.fromValue = [NSNumber numberWithFloat:0.0]; frameAnim.toValue = [NSNumber numberWithFloat:0.0]; frameAnim.beginTime = AVCoreAnimationBeginTimeAtZero + CMTimeGetSeconds(gFrames[i].endTime); frameAnim.speed = 1.0f; [Frame addAnimation:frameAnim forKey:@"animateOpacity"]; // add the frame layer to our parent layer [parentLayer addSublayer:Frame]; gError = nil; // if there's another point after this one if( i < gFrames.size()-1) { // add our video file to the composition with a range of this point's end and the next point's start [mutableCompositionTrack insertTimeRange:CMTimeRangeMake(gFrames[i].startTime, CMTimeMake(gFrames[i+1].startTime.value - gFrames[i].startTime.value, 600)) ofTrack:gVideoTrack atTime:CMTimeAdd(gFrames[i].startTime, timeOffset) error:&gError]; } // else just add our video file with a range of this points end point and the videos duration else { [mutableCompositionTrack insertTimeRange:CMTimeRangeMake(gFrames[i].startTime, CMTimeSubtract(gVideoAsset.duration, gFrames[i].startTime)) ofTrack:gVideoTrack atTime:CMTimeAdd(gFrames[i].startTime, timeOffset) error:&gError]; } if(gError) { char errorMsg[256]; sprintf(errorMsg, "Error inserting original video segment at: %d", i); DebugLog(errorMsg); GetError(); } } 

Ahora, en ese segmento, la opacidad del Marco se establece en 0.0f, sin embargo, cuando la ajusté a 1.0f, lo único que hace es colocar el último de estos fotogtwigs encima del video durante toda la duración.

Después de eso, la vide se exporta usando AVAssetExportSession como se muestra a continuación

 mutableVideoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer]; // create a layer instruction for our newly created animation tool AVMutableVideoCompositionLayerInstruction *layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:gVideoTrack]; AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; [instruction setTimeRange:CMTimeRangeMake(kCMTimeZero, [mutableComposition duration])]; [layerInstruction setOpacity:1.0f atTime:kCMTimeZero]; [layerInstruction setOpacity:0.0f atTime:mutableComposition.duration]; instruction.layerInstructions = [NSArray arrayWithObject:layerInstruction]; // set the instructions on our videoComposition mutableVideoComposition.instructions = [NSArray arrayWithObject:instruction]; // export final composition to a video file // convert the videopath into a url for our AVAssetWriter to create a file at NSString* vidPath = CreateNSString(outputVideoPath); NSURL* vidURL = [NSURL fileURLWithPath:vidPath]; AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mutableComposition presetName:AVAssetExportPreset1280x720]; exporter.outputFileType = AVFileTypeMPEG4; exporter.outputURL = vidURL; exporter.videoComposition = mutableVideoComposition; exporter.timeRange = CMTimeRangeMake(kCMTimeZero, mutableComposition.duration); // Asynchronously export the composition to a video file and save this file to the camera roll once export completes. [exporter exportAsynchronouslyWithCompletionHandler:^{ dispatch_async(dispatch_get_main_queue(), ^{ if (exporter.status == AVAssetExportSessionStatusCompleted) { DebugLog("!!!file created!!!"); _Close(); } else if(exporter.status == AVAssetExportSessionStatusFailed) { DebugLog("failed damn"); DebugLog(cStringCopy([[[exporter error] localizedDescription] UTF8String])); DebugLog(cStringCopy([[[exporter error] description] UTF8String])); _Close(); } else { DebugLog("NoIdea"); _Close(); } }); }]; } 

Tengo la sensación de que la animation no se está iniciando, pero no lo sé. ¿Voy por el path correcto para empalmar datos de image en un video como este?

Cualquier ayuda sería muy apreciada.

Bueno, resolví mi problema de otra manera. La ruta de animation no funcionaba, por lo que mi solución fue comstackr todas mis imágenes insertables en un file de video temporal y usar ese video para insert las imágenes en mi video de salida final.

A partir del primer enlace que originalmente publiqué ASSETWriterInput para hacer video a partir de UIImages en los problemas de Iphone , creé la siguiente function para crear mi video temporal

 void CreateFrameImageVideo(NSString* path) { NSLog(@"Creating writer at path %@", path); NSError *error = nil; AVAssetWriter *videoWriter = [[AVAssetWriter alloc] initWithURL: [NSURL fileURLWithPath:path] fileType:AVFileTypeMPEG4 error:&error]; NSLog(@"Creating video codec settings"); NSDictionary *codecSettings = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:gVideoTrack.estimatedDataRate/*128000*/], AVVideoAverageBitRateKey, [NSNumber numberWithInt:gVideoTrack.nominalFrameRate],AVVideoMaxKeyFrameIntervalKey, AVVideoProfileLevelH264MainAutoLevel, AVVideoProfileLevelKey, nil]; NSLog(@"Creating video settings"); NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys: AVVideoCodecH264, AVVideoCodecKey, codecSettings,AVVideoCompressionPropertiesKey, [NSNumber numberWithInt:1280], AVVideoWidthKey, [NSNumber numberWithInt:720], AVVideoHeightKey, nil]; NSLog(@"Creating writter input"); AVAssetWriterInput* writerInput = [[AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings] retain]; NSLog(@"Creating adaptor"); AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:writerInput sourcePixelBufferAttributes:nil]; [videoWriter addInput:writerInput]; NSLog(@"Starting session"); //Start a session: [videoWriter startWriting]; [videoWriter startSessionAtSourceTime:kCMTimeZero]; CMTime timeOffset = kCMTimeZero;//CMTimeMake(0, 600); NSLog(@"Video Width %d, Height: %d, writing frame video to file", gWidth, gHeight); CVPixelBufferRef buffer; for(int i = 0; i< gAnalysisFrames.size(); i++) { while (adaptor.assetWriterInput.readyForMoreMediaData == FALSE) { NSLog(@"Waiting inside a loop"); NSDate *maxDate = [NSDate dateWithTimeIntervalSinceNow:0.1]; [[NSRunLoop currentRunLoop] runUntilDate:maxDate]; } //Write samples: buffer = pixelBufferFromCGImage(gAnalysisFrames[i].frameImage, gWidth, gHeight); [adaptor appendPixelBuffer:buffer withPresentationTime:timeOffset]; timeOffset = CMTimeAdd(timeOffset, gAnalysisFrames[i].duration); } while (adaptor.assetWriterInput.readyForMoreMediaData == FALSE) { NSLog(@"Waiting outside a loop"); NSDate *maxDate = [NSDate dateWithTimeIntervalSinceNow:0.1]; [[NSRunLoop currentRunLoop] runUntilDate:maxDate]; } buffer = pixelBufferFromCGImage(gAnalysisFrames[gAnalysisFrames.size()-1].frameImage, gWidth, gHeight); [adaptor appendPixelBuffer:buffer withPresentationTime:timeOffset]; NSLog(@"Finishing session"); //Finish the session: [writerInput markAsFinished]; [videoWriter endSessionAtSourceTime:timeOffset]; BOOL successfulWrite = [videoWriter finishWriting]; // if we failed to write the video if(!successfulWrite) { NSLog(@"Session failed with error: %@", [[videoWriter error] description]); // delete the temporary file created NSFileManager *fileManager = [NSFileManager defaultManager]; if ([fileManager fileExistsAtPath:path]) { NSError *error; if ([fileManager removeItemAtPath:path error:&error] == NO) { NSLog(@"removeItemAtPath %@ error:%@", path, error); } } } else { NSLog(@"Session complete"); } [writerInput release]; } 

Una vez creado el video, se carga como un AVAsset y se extrae la pista y luego se inserta el video reemplazando la siguiente línea (desde el primer bloque de código en la publicación original)

 [mutableCompositionTrack insertEmptyTimeRange:CMTimeRangeMake(CMTimeAdd(gFrames[i].startTime, timeOffset), gFrames[i].duration)]; 

con:

 [mutableCompositionTrack insertTimeRange:CMTimeRangeMake(timeOffset,gAnalysisFrames[i].duration) ofTrack:gFramesTrack atTime:CMTimeAdd(gAnalysisFrames[i].startTime, timeOffset) error:&gError]; 

donde gFramesTrack es AVAssetTrack creado a partir del video de marco temporal.

todo el código relacionado con los objects CALayer y CABasicAnimation se ha eliminado porque simplemente no funcionaba.

No es la solución más elegante, no creo sino una que funcione al less. Espero que alguien encuentre esto útil.

Este código también funciona en dispositivos iOS (probado con un iPad 3)

Nota al margen: la function DebugLog de la primera publicación es solo una callback a una function que imprime posts de logging, pueden replacese con llamadas de NSLog () si es necesario.