2011-04-12 7 views
36

Tengo que exportar una película desde mi aplicación de iPhone que contiene UIImage desde un NSArray y agregar algunos archivos de audio en formato .caf que tienen que comenzar en momentos preespecificados. Ahora he podido usar el AVAssetWriter (después de revisar muchas preguntas y respuestas en este y otros sitios) para exportar la parte del video que contiene las imágenes, pero no puedo encontrar la manera de agregar los archivos de audio para completar la película.AVFoundation + AssetWriter: Generar película con imágenes y audio

Aquí es lo que he conseguido hasta ahora

-(void) writeImagesToMovieAtPath:(NSString *) path withSize:(CGSize) size 
{ 
    NSLog(@"Write Started"); 

    NSError *error = nil; 

    AVAssetWriter *videoWriter = [[AVAssetWriter alloc] initWithURL: 
           [NSURL fileURLWithPath:path] fileType:AVFileTypeQuickTimeMovie 
                  error:&error];  
    NSParameterAssert(videoWriter); 

    NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys: 
           AVVideoCodecH264, AVVideoCodecKey, 
           [NSNumber numberWithInt:size.width], AVVideoWidthKey, 
           [NSNumber numberWithInt:size.height], AVVideoHeightKey, 
           nil]; 

    AVAssetWriterInput* videoWriterInput = [[AVAssetWriterInput 
            assetWriterInputWithMediaType:AVMediaTypeVideo 
            outputSettings:videoSettings] retain]; 


    AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor 
              assetWriterInputPixelBufferAdaptorWithAssetWriterInput:videoWriterInput 
               sourcePixelBufferAttributes:nil]; 

    NSParameterAssert(videoWriterInput); 
    NSParameterAssert([videoWriter canAddInput:videoWriterInput]); 
    videoWriterInput.expectsMediaDataInRealTime = YES; 
    [videoWriter addInput:videoWriterInput]; 

    //Start a session: 
    [videoWriter startWriting]; 
    [videoWriter startSessionAtSourceTime:kCMTimeZero]; 

    CVPixelBufferRef buffer = NULL; 

    //convert uiimage to CGImage. 

    int frameCount = 0; 

    for(UIImage * img in imageArray) 
    { 
      buffer = [self pixelBufferFromCGImage:[img CGImage] andSize:size]; 

      BOOL append_ok = NO; 
      int j = 0; 
      while (!append_ok && j < 30) 
      { 
       if (adaptor.assetWriterInput.readyForMoreMediaData) 
       { 
        printf("appending %d attemp %d\n", frameCount, j); 

        CMTime frameTime = CMTimeMake(frameCount,(int32_t) kRecordingFPS); 
        append_ok = [adaptor appendPixelBuffer:buffer withPresentationTime:frameTime]; 

        if(buffer) 
         CVBufferRelease(buffer); 
        [NSThread sleepForTimeInterval:0.05]; 
       } 
       else 
       { 
        printf("adaptor not ready %d, %d\n", frameCount, j); 
        [NSThread sleepForTimeInterval:0.1]; 
       } 
       j++; 
      } 
      if (!append_ok) { 
       printf("error appending image %d times %d\n", frameCount, j); 
      } 
      frameCount++; 
     } 
    } 

    //Finish the session: 
    [videoWriterInput markAsFinished]; 
    [videoWriter finishWriting]; 
    NSLog(@"Write Ended"); 
} 

Y ahora el código para pixelBufferFromCGImage

- (CVPixelBufferRef) pixelBufferFromCGImage: (CGImageRef) image andSize:(CGSize) size 
{ 
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: 
         [NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey, 
         [NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey, 
         nil]; 
    CVPixelBufferRef pxbuffer = NULL; 

    CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, size.width, 
             size.height, kCVPixelFormatType_32ARGB, (CFDictionaryRef) options, 
             &pxbuffer); 
    NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL); 

    CVPixelBufferLockBaseAddress(pxbuffer, 0); 
    void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer); 
    NSParameterAssert(pxdata != NULL); 

    CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); 
    CGContextRef context = CGBitmapContextCreate(pxdata, size.width, 
              size.height, 8, 4*size.width, rgbColorSpace, 
              kCGImageAlphaNoneSkipFirst); 
    NSParameterAssert(context); 
    CGContextConcatCTM(context, CGAffineTransformMakeRotation(0)); 
    CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), 
              CGImageGetHeight(image)), image); 
    CGColorSpaceRelease(rgbColorSpace); 
    CGContextRelease(context); 

    CVPixelBufferUnlockBaseAddress(pxbuffer, 0); 

    return pxbuffer; 
} 

Así que me puede ayudar a cabo con respecto a cómo agregar los archivos de audio y cómo hacer que los tampones para ellos y el adaptador y la configuración de entrada, etc.

Si este enfoque pudiera causar un problema, guíame sobre cómo usar una composición de AVMutable para usar la matriz de imágenes para video export

+0

Ok he podido agregar los archivos de audio usando AVAssetReaders y AVAssetWriterInputs, sin embargo cuando agrego los archivos de audio, comienzan uno después del otro sin ninguna pausa (uno finaliza y el siguiente comienza) en lugar de comenzar en momentos predeterminados, así que ¿Cómo le digo a AVAssetWriter que tome la entrada en un momento determinado? Esto porque, como yo entiendo, [startSessionAtSourceTime] es para determinar la hora de la fuente, no el tiempo en la película de destino, así que cualquier sugerencia es – MuTaTeD

+0

. Es increíble para publicar soluciones tan detalladas para otros. – TigerCoding

+0

¿Esto también funciona con 1080 * 1920 imágenes? ¿Porque he implementado el mismo código y está funcionando bien con 720 * 1280 (720/16) pero no funciona con el ancho de video cuyo resultado arroja valor (ancho de video/16) cualquier sugerencia? –

Respuesta

17

Terminé exportando el video por separado usando el código anterior y agregué los archivos de audio por separado usando AVComposition & AVExportSession. Este es el código

-(void) addAudioToFileAtPath:(NSString *) filePath toPath:(NSString *)outFilePath 
{ 
    NSError * error = nil; 

    AVMutableComposition * composition = [AVMutableComposition composition]; 


    AVURLAsset * videoAsset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:filePath] options:nil]; 

    AVAssetTrack * videoAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; 

    AVMutableCompositionTrack *compositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo 
                       preferredTrackID: kCMPersistentTrackID_Invalid]; 

    [compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,videoAsset.duration) ofTrack:videoAssetTrack atTime:kCMTimeZero 
            error:&error];  

    CMTime audioStartTime = kCMTimeZero; 
    for (NSDictionary * audioInfo in audioInfoArray) 
    { 
     NSString * pathString = [audioInfo objectForKey:audioFilePath]; 
     AVURLAsset * urlAsset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:pathString] options:nil]; 

     AVAssetTrack * audioAssetTrack = [[urlAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0]; 
     AVMutableCompositionTrack *compositionAudioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio 
                        preferredTrackID: kCMPersistentTrackID_Invalid]; 

     [compositionAudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,urlAsset.duration) ofTrack:audioAssetTrack atTime:audioStartTime error:&error];  

     audioStartTime = CMTimeAdd(audioStartTime, CMTimeMake((int) (([[audioInfo objectForKey:audioDuration] floatValue] * kRecordingFPS) + 0.5), kRecordingFPS)); 
    } 
    AVAssetExportSession* assetExport = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetMediumQuality]; 
    assetExport.videoComposition = mutableVideoComposition; 

    assetExport.outputFileType =AVFileTypeQuickTimeMovie;// @"com.apple.quicktime-movie"; 
    assetExport.outputURL = [NSURL fileURLWithPath:outFilePath]; 

    [assetExport exportAsynchronouslyWithCompletionHandler: 
    ^(void) { 
     switch (assetExport.status) 
     { 
      case AVAssetExportSessionStatusCompleted: 
//    export complete 
       NSLog(@"Export Complete"); 
       break; 
      case AVAssetExportSessionStatusFailed: 
       NSLog(@"Export Failed"); 
       NSLog(@"ExportSessionError: %@", [assetExport.error localizedDescription]); 
//    export error (see exportSession.error) 
       break; 
      case AVAssetExportSessionStatusCancelled: 
       NSLog(@"Export Failed"); 
       NSLog(@"ExportSessionError: %@", [assetExport.error localizedDescription]); 
//    export cancelled 
       break; 
     } 
    }];  
} 
+0

¿Puede reemplazar el bucle "para" por un solo diccionario "audioInfo" que tenga todos los valores que se deben establecer para que sea más compatible con copiar y pegar? :) –

+0

@Chintan Patel: Tuve que agregar longitudes variables de diferentes archivos de audio a diferentes partes del video en la película generada (termino usando los archivos de audio completos en su lugar), así que hice un diccionario para cada archivo de audio para ser incluido y agregado todos a la matriz (audioInfoArray). El diccionario audioInfo contiene las siguientes claves ** audioFilePath ** y ** audioDuration **, _NSString_ y _float_ respectivamente. – MuTaTeD

+0

Gracias, eso servirá. –

4

Puede usted por favor reemplazar el bucle "for" con un único diccionario "audioInfo", que tiene todos los valores que se deben establecer para que se vuelva más copiar y pegar ambiente? :)

Si lo que desea es añadir un único archivo de audio, el siguiente código debe sustituir el bucle for:

NSString * pathString = [self getAudioFilePath]; 
AVURLAsset * urlAsset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:pathString] options:nil]; 

AVAssetTrack * audioAssetTrack = [[urlAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0]; 
AVMutableCompositionTrack *compositionAudioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio 
                preferredTrackID: kCMPersistentTrackID_Invalid]; 

[compositionAudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,urlAsset.duration) ofTrack:audioAssetTrack atTime:kCMTimeZero error:&error];  
Cuestiones relacionadas