iOS CVImageBuffer искажен из AVCaptureSessionDataOutput с помощью AVCaptureSessionPresetPhoto

На высоком уровне я создал приложение, которое позволяет пользователю направлять камеру своего iPhone и видеть видеокадры, обработанные с визуальными эффектами. Кроме того, пользователь может нажать кнопку, чтобы сделать стоп-кадр текущего предварительного просмотра как фотографию с высоким разрешением, которая сохраняется в его библиотеке iPhone.

Для этого приложение выполняет следующую процедуру:

1) Создайте AVCaptureSession

captureSession = [[AVCaptureSession alloc] init];
[captureSession setSessionPreset:AVCaptureSessionPreset640x480];

2) Подключите AVCaptureDeviceInput, используя камеру, обращенную назад.

videoInput = [[[AVCaptureDeviceInput alloc] initWithDevice:backFacingCamera error:&error] autorelease];
[captureSession addInput:videoInput];

3) Подключите AVCaptureStillImageOutput к сеансу, чтобы иметь возможность захватывать неподвижные кадры с разрешением Photo.

stillOutput = [[AVCaptureStillImageOutput alloc] init];
[stillOutput setOutputSettings:[NSDictionary
    dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA]
    forKey:(id)kCVPixelBufferPixelFormatTypeKey]];
[captureSession addOutput:stillOutput];

4) Подключите AVCaptureVideoDataOutput к сеансу, чтобы иметь возможность захватывать отдельные видеокадры (CVImageBuffers) с более низким разрешением

videoOutput = [[AVCaptureVideoDataOutput alloc] init];
[videoOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];
[videoOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
[captureSession addOutput:videoOutput];

5) По мере захвата видеокадров метод делегата вызывается с каждым новым кадром как CVImageBuffer:

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    [self.delegate processNewCameraFrame:pixelBuffer];
}

6) Затем делегирует процессы / рисует их:

- (void)processNewCameraFrame:(CVImageBufferRef)cameraFrame {
    CVPixelBufferLockBaseAddress(cameraFrame, 0);
    int bufferHeight = CVPixelBufferGetHeight(cameraFrame);
    int bufferWidth = CVPixelBufferGetWidth(cameraFrame);

    glClear(GL_COLOR_BUFFER_BIT);

    glGenTextures(1, &videoFrameTexture_);
    glBindTexture(GL_TEXTURE_2D, videoFrameTexture_);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bufferWidth, bufferHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, CVPixelBufferGetBaseAddress(cameraFrame));

    glBindBuffer(GL_ARRAY_BUFFER, [self vertexBuffer]);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, [self indexBuffer]);

    glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, BUFFER_OFFSET(0));

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    [[self context] presentRenderbuffer:GL_RENDERBUFFER];

    glDeleteTextures(1, &videoFrameTexture_);

    CVPixelBufferUnlockBaseAddress(cameraFrame, 0);
}

Все это работает и приводит к правильным результатам. Я вижу превью видео с разрешением 640x480, обработанное через OpenGL. Это выглядит так:

640x480 Correct Preview

Однако, если я сделаю стоп-кадр из этого сеанса, его разрешение также будет 640x480. Я хочу, чтобы оно было высокого разрешения, поэтому на первом этапе я изменяю предустановленную строку на:

[captureSession setSessionPreset:AVCaptureSessionPresetPhoto];

Это правильно захватывает неподвижные изображения с самым высоким разрешением для iPhone4 (2592x1936).

Однако предварительный просмотр видео (полученный пользователем делегат на шагах 5 и 6) теперь выглядит следующим образом:

Photo preview incorrect

Я подтвердил, что все остальные предустановки (высокий, средний, низкий, 640x480 и 1280x720) выполняются как задумано. Однако предустановка Photo, кажется, отправляет данные буфера в другом формате.

Я также подтвердил, что данные, отправляемые в буфер при предустановке Photo, на самом деле являются действительными данными изображения, взяв буфер и создав UIImage из вместо отправки в openGL:

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(CVPixelBufferGetBaseAddress(cameraFrame), bufferWidth, bufferHeight, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst); 
CGImageRef cgImage = CGBitmapContextCreateImage(context); 
UIImage *anImage = [UIImage imageWithCGImage:cgImage];

Это показывает неискаженный видеокадр.

Я провел кучу поисков и, похоже, не могу это исправить. Я подозреваю, что это проблема формата данных. То есть, я считаю, что буфер установлен правильно, но с форматом, который эта строка не понимает:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bufferWidth, bufferHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, CVPixelBufferGetBaseAddress(cameraFrame));

Я догадывался, что изменение внешнего формата с GL_BGRA на что-то другое поможет, но это не так. .. и различными способами похоже, что буфер действительно находится в GL_BGRA.

Кто-нибудь знает, что здесь происходит? Или у вас есть советы, как я могу отладить, почему это происходит? (Что очень странно, так это то, что это происходит на iphone4, но не на iPhone 3GS ... оба работают под управлением ios4.3)

23
задан genpfault 30 June 2011 в 21:03
поделиться