2011-05-31 11 views
12

Estoy tratando de usar AVAssetWriter para escribir CGImages en un archivo para crear un video a partir de imágenes.AVAssetWriter Woes

He conseguido esto para trabajar con éxito de tres maneras diferentes en el simulador, pero todos los métodos fallan en un iPhone 4 con iOS 4.3.

Todo esto tiene que ver con los búferes de píxeles.

Mi primer método fue simplemente crear los búferes de píxeles según sea necesario sin usar un grupo. Eso funciona, pero requiere demasiada memoria para funcionar en el dispositivo.

Mi segundo método fue utilizar el AVAssetWriterInputPixelBufferAdaptor recomendado y luego extraer los búfers de píxeles de los adaptadores pixelBufferPool con CVPixelBufferPoolCreatePixelBuffer.

Eso también funciona en el simulador, pero falla en el dispositivo porque el grupo de búferes de píxeles del adaptador nunca se asigna. No recibo mensajes de error

Por último, intenté crear mi propio grupo de buffer de píxeles con CVPixelBufferPoolCreate. Eso también funciona en el simulador pero en el dispositivo, todo funciona bien hasta que intento agregar el búfer de píxeles con appendPixelBuffer, que falla cada vez.

He encontrado información muy mínima sobre esto en la web. He basado mi código en los ejemplos que he encontrado, pero no tengo suerte en estos días. Si ALGUIEN tiene experiencia haciendo esto con AVAssetWriter con éxito, eche un vistazo y avíseme si ve algo fuera de lugar.

NOTA: verá el bloque de intentos comentado.

En primer lugar, la configuración

- (BOOL) openVideoFile: (NSString *) path withSize:(CGSize)imageSize { 
size = CGSizeMake (480.0, 320.0);//imageSize; 

NSError *error = nil; 
videoWriter = [[AVAssetWriter alloc] initWithURL: 
           [NSURL fileURLWithPath:path] fileType:AVFileTypeQuickTimeMovie 
                  error:&error]; 
if (error != nil) 
    return NO; 

NSDictionary *videoCleanApertureSettings = [NSDictionary dictionaryWithObjectsAndKeys: 
              [NSNumber numberWithDouble:size.width], AVVideoCleanApertureWidthKey, 
              [NSNumber numberWithDouble:size.height], AVVideoCleanApertureHeightKey, 
              [NSNumber numberWithInt:10], AVVideoCleanApertureHorizontalOffsetKey, 
              [NSNumber numberWithInt:10], AVVideoCleanApertureVerticalOffsetKey, 
              nil]; 


NSDictionary *videoAspectRatioSettings = [NSDictionary dictionaryWithObjectsAndKeys: 
              [NSNumber numberWithInt:1], AVVideoPixelAspectRatioHorizontalSpacingKey, 
              [NSNumber numberWithInt:1],AVVideoPixelAspectRatioVerticalSpacingKey, 
              nil]; 



NSDictionary *codecSettings = [NSDictionary dictionaryWithObjectsAndKeys: 
           //[NSNumber numberWithInt:960000], AVVideoAverageBitRateKey, 
           // [NSNumber numberWithInt:1],AVVideoMaxKeyFrameIntervalKey, 
           videoCleanApertureSettings, AVVideoCleanApertureKey, 
           videoAspectRatioSettings, AVVideoPixelAspectRatioKey, 
           //AVVideoProfileLevelH264Main31, AVVideoProfileLevelKey, 
           nil]; 

NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys: 
           AVVideoCodecH264, AVVideoCodecKey, 
           codecSettings,AVVideoCompressionPropertiesKey, 
           [NSNumber numberWithDouble:size.width], AVVideoWidthKey, 
           [NSNumber numberWithDouble:size.height], AVVideoHeightKey, 
           nil]; 
writerInput = [[AVAssetWriterInput 
            assetWriterInputWithMediaType:AVMediaTypeVideo 
            outputSettings:videoSettings] retain]; 
NSMutableDictionary * bufferAttributes = [[NSMutableDictionary alloc] init]; 
[bufferAttributes setObject: [NSNumber numberWithInt: kCVPixelFormatType_32ARGB] 
        forKey: (NSString *) kCVPixelBufferPixelFormatTypeKey]; 
[bufferAttributes setObject: [NSNumber numberWithInt: 480] 
        forKey: (NSString *) kCVPixelBufferWidthKey]; 
[bufferAttributes setObject: [NSNumber numberWithInt: 320] 
        forKey: (NSString *) kCVPixelBufferHeightKey]; 


//NSDictionary *bufferAttributes = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:kCVPixelFormatType_32ARGB], kCVPixelBufferPixelFormatTypeKey, nil]; 
//[bufferAttributes setObject: [NSNumber numberWithInt: 640] 
//     forKey: (NSString *) kCVPixelBufferWidthKey]; 
//[bufferAttributes setObject: [NSNumber numberWithInt: 480] 
//     forKey: (NSString *) kCVPixelBufferHeightKey]; 
adaptor = [[AVAssetWriterInputPixelBufferAdaptor 
      assetWriterInputPixelBufferAdaptorWithAssetWriterInput:writerInput 
      sourcePixelBufferAttributes:nil] retain]; 

//CVPixelBufferPoolCreate (kCFAllocatorSystemDefault,NULL,(CFDictionaryRef)bufferAttributes,&pixelBufferPool); 
//Create buffer pool 
NSMutableDictionary*  attributes; 
attributes = [NSMutableDictionary dictionary]; 

int width = 480; 
int height = 320; 

[attributes setObject:[NSNumber numberWithInt:kCVPixelFormatType_32ARGB] forKey:(NSString*)kCVPixelBufferPixelFormatTypeKey]; 
[attributes setObject:[NSNumber numberWithInt:width] forKey: (NSString*)kCVPixelBufferWidthKey]; 
[attributes setObject:[NSNumber numberWithInt:height] forKey: (NSString*)kCVPixelBufferHeightKey]; 
CVReturn theError = CVPixelBufferPoolCreate(kCFAllocatorDefault, NULL, (CFDictionaryRef) attributes, &pixelBufferPool);           


NSParameterAssert(writerInput); 
NSParameterAssert([videoWriter canAddInput:writerInput]); 
[videoWriter addInput:writerInput]; 

writerInput.expectsMediaDataInRealTime = YES; 

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

buffer = NULL; 
lastTime = kCMTimeZero; 
presentTime = kCMTimeZero; 

return YES; 
} 

A continuación, los dos métodos que anexar el escritor y crean el búfer de píxeles para anexar.

- (void) writeImageToMovie:(CGImageRef)image 
{ 
    if([writerInput isReadyForMoreMediaData]) 
    { 
//   CMTime frameTime = CMTimeMake(1, 20); 
//   CMTime lastTime=CMTimeMake(i, 20); //i is from 0 to 24 of the loop above 
//   CMTime presentTime=CMTimeAdd(lastTime, frameTime); 

     buffer = [self pixelBufferFromCGImage:image]; 
     BOOL success = [adaptor appendPixelBuffer:buffer withPresentationTime:presentTime]; 
     if (!success) NSLog(@"Failed to appendPixelBuffer"); 
     CVPixelBufferRelease(buffer); 

     presentTime = CMTimeAdd(lastTime, CMTimeMake(5, 1000)); 
     lastTime = presentTime; 
    } 
    else 
    { 
     NSLog(@"error - writerInput not ready"); 
    } 
} 

- (CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image 
{ 
CVPixelBufferRef pxbuffer; 
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: 
         [NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey, 
         [NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey, 
         nil]; 
if (pixelBufferPool == NULL) NSLog(@"pixelBufferPool is null!"); 
CVReturn status = CVPixelBufferPoolCreatePixelBuffer (NULL, pixelBufferPool, &pxbuffer); 
/*if (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(90, 10, CGImageGetWidth(image), 
             CGImageGetHeight(image)), image); 
CGColorSpaceRelease(rgbColorSpace); 
CGContextRelease(context); 

CVPixelBufferUnlockBaseAddress(pxbuffer, 0); 

return pxbuffer; 
} 
+0

Wow, esto es decepcionante. ¿Nadie tiene una pista? –

Respuesta

2

He encontrado la solución a este problema.

Si desea que AVAudioPlayer y AVAssetWriter se comporten correctamente juntos, debe tener una categoría de sesión de audio que sea "mezclable".

Puede usar una categoría que se pueda mezclar como AVAudioSessionCategoryAmbient.

Sin embargo, necesitaba usar AVAudioSessionCategoryPlayAndRecord.

Se puede establecer cualquier categoría que se pueden mezclar mediante la implementación de esto:

OSStatus propertySetError = 0; 

UInt32 allowMixing = true; 

propertySetError = AudioSessionSetProperty (
         kAudioSessionProperty_OverrideCategoryMixWithOthers, // 1 
         sizeof (allowMixing),         // 2 
         &allowMixing           // 3 
        ); 
+4

No entiendo muy bien cómo se relaciona esta solución con su código original. – ninjudd

1

Bueno, primero que hay que pasar algún bufferAttributes al crear dicho objeto adaptador:

NSDictionary *bufferAttributes = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA], kCVPixelBufferPixelFormatTypeKey, nil]; 

AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor 
               assetWriterInputPixelBufferAdaptorWithAssetWriterInput:_videoWriterInput 
               sourcePixelBufferAttributes:bufferAttributes]; 

Luego retire esa llamada a CVPixelBufferPoolCreate, ya hay un grupo de búfer de píxeles creado en el objeto adaptador, así llamada sólo esta vez:

   CVPixelBufferRef pixelBuffer = NULL; 
      CVPixelBufferPoolCreatePixelBuffer(NULL, adaptor.pixelBufferPool, &pixelBuffer); 
      CVPixelBufferLockBaseAddress(pixelBuffer, 0); 

      // ...fill the pixelbuffer here 

      CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); 

      CMTime frameTime = CMTimeMake(frameCount,(int32_t) 30); 

      BOOL res = [adaptor appendPixelBuffer:pixelBuffer withPresentationTime:frameTime]; 
      CVPixelBufferRelease(pixelBuffer); 
      CFRelease(sampleBuffer); 

Creo que debería hacerlo, he tenido un error similar en algún momento y me lo resolvió mediante la creación y el adaptador pix el buffer como se muestra aquí ...

+0

Además de la sugerencia anterior, debe probar para asegurarse de que el búfer de píxeles se haya creado correctamente después de llamar a startSessionAtSourceTime en su código. Su código debe marcar "if (adaptor.pixelBufferPool == nil)" y luego manejar el caso de falla antes de entrar en el bucle de marcos de codificación. – MoDJ

Cuestiones relacionadas