2012-06-14 14 views
6

Estoy trabajando en un proyecto en el que genero un video de UIImage, con el código que encontré por aquí, y estoy luchando durante algunos días para optimizarlo (para alrededor de 300 imágenes, toma como 5 minutos en el simulador, y simplemente se cuelga en el dispositivo debido a la memoria).Optimización de CVPixelBufferRef

Voy a empezar con el código de trabajo que tengo hoy (trabajo con arco):

-(void) writeImageAsMovie:(NSArray *)array toPath:(NSString*)path size:(CGSize)size duration:(int)duration 
{ 
    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* writerInput = [AVAssetWriterInput 
            assetWriterInputWithMediaType:AVMediaTypeVideo 
            outputSettings:videoSettings]; 

    AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:writerInput sourcePixelBufferAttributes:nil]; 
    NSParameterAssert(writerInput); 
    NSParameterAssert([videoWriter canAddInput:writerInput]); 
    [videoWriter addInput:writerInput]; 


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

    CVPixelBufferRef buffer = NULL; 
    buffer = [self newPixelBufferFromCGImage:[[self.frames objectAtIndex:0] CGImage]]; 

    CVPixelBufferPoolCreatePixelBuffer (NULL, adaptor.pixelBufferPool, &buffer); 

    [adaptor appendPixelBuffer:buffer withPresentationTime:kCMTimeZero]; 

    dispatch_queue_t mediaInputQueue = dispatch_queue_create("mediaInputQueue", NULL); 
    int frameNumber = [self.frames count]; 

    [writerInput requestMediaDataWhenReadyOnQueue:mediaInputQueue usingBlock:^{ 
     NSLog(@"Entering block with frames: %i", [self.frames count]); 
     if(!self.frames || [self.frames count] == 0) 
     { 
      return; 
     } 
     int i = 1; 
     while (1) 
     { 
      if (i == frameNumber) 
      { 
       break; 
      } 
      if ([writerInput isReadyForMoreMediaData]) 
      { 
       freeMemory(); 
       NSLog(@"inside for loop %d (%i)",i, [self.frames count]); 
       UIImage *image = [self.frames objectAtIndex:i]; 
       CGImageRef imageRef = [image CGImage]; 
       CVPixelBufferRef sampleBuffer = [self newPixelBufferFromCGImage:imageRef]; 
       CMTime frameTime = CMTimeMake(1, TIME_STEP); 

       CMTime lastTime=CMTimeMake(i, TIME_STEP); 

       CMTime presentTime=CMTimeAdd(lastTime, frameTime);  

       if (sampleBuffer) 
       { 
        [adaptor appendPixelBuffer:sampleBuffer withPresentationTime:presentTime]; 
        i++; 
        CVPixelBufferRelease(sampleBuffer); 
       } 
       else 
       { 
        break; 
       } 
      } 
     } 

     [writerInput markAsFinished]; 
     [videoWriter finishWriting]; 
     self.frames = nil; 

     CVPixelBufferPoolRelease(adaptor.pixelBufferPool); 

    }]; 
} 

Y ahora la función para obtener el búfer de píxeles, con el que estoy luchando:

- (CVPixelBufferRef) newPixelBufferFromCGImage: (CGImageRef) image 
{ 
    CVPixelBufferRef pxbuffer = NULL; 

    int width = CGImageGetWidth(image)*2; 
    int height = CGImageGetHeight(image)*2; 

    NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithObjectsAndKeys:[NSNumber kCVPixelFormatType_32ARGB], kCVPixelBufferPixelFormatTypeKey, [NSNumber numberWithInt:width], kCVPixelBufferWidthKey, [NSNumber numberWithInt:height], kCVPixelBufferHeightKey, nil]; 
    CVPixelBufferPoolRef pixelBufferPool; 
    CVReturn theError = CVPixelBufferPoolCreate(kCFAllocatorDefault, NULL, (__bridge CFDictionaryRef) attributes, &pixelBufferPool); 
    NSParameterAssert(theError == kCVReturnSuccess); 
    CVReturn status = CVPixelBufferPoolCreatePixelBuffer(NULL, pixelBufferPool, &pxbuffer); 
    NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL); 

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

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

    CVPixelBufferUnlockBaseAddress(pxbuffer, 0); 

    return pxbuffer; 
} 

Primera cosa extraña: como se puede ver en esta función, tengo que multiplicar el ancho y la altura por 2, de lo contrario, el resultado del video está mal, y no puedo entender por qué (puedo publicar capturas de pantalla si ayuda, los píxeles parecen provenir de mi imagen, pero el ancho no es correcto, y hay una gran squa negra re en la mitad inferior del video).

Otro problema es que se necesita una gran cantidad de memoria; Creo que el buffer de píxeles no puede ser desasignado, pero no veo por qué.

Finalmente, es muy lento, pero tengo dos ideas para mejorarlo, que no uso.

  • El primero es evitar el uso de UIImage para crear mis memorias intermedias de píxeles, ya que genero la UIImage mí mismo con (*) uint8_t datos. Traté de usar 'CVPixelBufferCreateWithBytes', pero no funcionaría. Así es como yo lo probé:

    OSType pixFmt = CVPixelBufferGetPixelFormatType(pxbuffer); 
    CVPixelBufferCreateWithBytes(kCFAllocatorDefault, width, height, pixFmt, self.composition.srcImage.resultImageData, width*2, NULL, NULL, (__bridge CFDictionaryRef) attributes, &pxbuffer); 
    

(Los argumentos son los mismos que para las funciones anteriores; mis datos de imagen se codifican en 16 bits por píxel, y no podía encontrar un buen argumento OSType para dar a la función.) Si alguien sabe cómo usarlo (¿no es posible con datos de 16 bits/píxel?), me ayudaría a evitar una conversión realmente inútil.

  • Lo segundo es que me gustaría evitar kCVPixelFormatType_32ARGB para mi video. Supongo que sería más rápido usar algo con menos bits/píxel, pero cuando lo intento (probé todos los formatos kCVPixelFormatType_16XXXXX, con un contexto creado con 5 bits/componente y kCGImageAlphaNoneSkipFirst), o bien se bloquea, ya sea el video resultante no contiene nada (con kCVPixelFormatType_16BE555).

Sé que pido mucho en un solo puesto, pero estoy un poco perdido en este código, he intentado muchas combinaciones y ninguno de ellos trabajado ...

+0

Eso debería tomar como mucho 10s. Parece que estás filtrando y también duplicando tus datos de imágenes. ¿Cómo estás cargando tus imágenes? Tal vez haya una forma más directa de cargarlos en un CVPixelBuffer. –

+0

¡Logré ayer conseguir algo que no gotea! Sin embargo, no estoy seguro de cuál fue el problema, ya que he estado buscando en todas las direcciones al mismo tiempo ... Utilicé el código de aquí: http://codethink.no-ip.org/wordpress/archives/673 que modifiqué mucho y terminé con algo que no se filtra ... Todavía no es perfecto, porque creo las imágenes a partir de datos (int *) y las vuelvo a convertir más tarde, pero al menos ya no se cuelga debido a problemas de memoria ... – Grhyll

+0

tiene la versión final de este ¿código? Estoy teniendo el mismo problema. Aprecio si puedes compartirlo. Gracias. – SpaceDog

Respuesta

0

I have to multiply the width and height by 2, otherwise, the result video is all messed up, and I can't understand why

Puntos vs. pixeles? Las pantallas de retina de alta resolución tienen el doble de píxeles por punto.