Более быстрая альтернатива glReadPixels в iPhone OpenGL ES 2.0

Есть ли более быстрый способ доступа к буфере кадров, чем использование glReadPixels? Мне нужен доступ только для чтения к небольшой прямоугольной области рендеринга в буфере кадров для обработки данных в ЦП. Производительность важна, потому что я должен повторять эту операцию. Я искал в Интернете и нашел какой-то подход, как использование Pixel Buffer Object и glMapBuffer, но похоже, что OpenGL ES 2.0 их не поддерживает.

Ответ 1

Начиная с iOS 5.0, теперь есть более быстрый способ захвата данных из OpenGL ES. Это не совсем очевидно, но оказывается, что поддержка кеша текстур, добавленная в iOS 5.0, не просто работает для быстрой загрузки кадров камеры в OpenGL ES, но ее можно использовать в обратном порядке, чтобы получить быстрый доступ к необработанным пикселям в текстуре ES OpenGL.

Вы можете воспользоваться этим, чтобы захватить пиксели для рендеринга OpenGL ES, используя объект framebuffer (FBO) с прикрепленной текстурой, причем эта текстура была предоставлена ​​из кеша текстуры. После того, как вы сделаете свою сцену в этом FBO, пиксели BGRA для этой сцены будут содержаться в вашем CVPixelBufferRef, поэтому их не нужно будет тянуть с помощью glReadPixels().

Это намного, намного быстрее, чем использование glReadPixels() в моих тестах. Я обнаружил, что на моем iPhone 4 glReadPixels() было узким местом при чтении видеокадров 720p для кодирования на диск. Это ограничило кодировку тем, что произошло более чем на 8-9 FPS. Замена этого на чтение быстрых текстурных кешей позволяет мне кодировать видео 720p с частотой 20 FPS, а узкое место переместилось из показания пикселя в обработку OpenGL ES и фактические части кодирования фильма в конвейере. На iPhone 4S это позволяет записывать 1080p видео с полным 30 FPS.

Моя реализация может быть найдена в классе GPUImageMovieWriter в моей открытой исходной структуре GPUImage, но она была вдохновлена ​​статья Денниса Мухлестяна по этому вопросу и образец приложения Apple ChromaKey (который был доступен только на WWDC 2011).

Я начинаю с настройки моего AVAssetWriter, добавления ввода и настройки ввода буфера пикселей. Для настройки ввода буфера пикселей используется следующий код:

NSDictionary *sourcePixelBufferAttributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA], kCVPixelBufferPixelFormatTypeKey,
                                                       [NSNumber numberWithInt:videoSize.width], kCVPixelBufferWidthKey,
                                                       [NSNumber numberWithInt:videoSize.height], kCVPixelBufferHeightKey,
                                                       nil];

assetWriterPixelBufferInput = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:assetWriterVideoInput sourcePixelBufferAttributes:sourcePixelBufferAttributesDictionary];

Как только я это сделаю, я настрою FBO, чтобы я передавал свои видеофрагменты, используя следующий код:

if ([GPUImageOpenGLESContext supportsFastTextureUpload])
{
    CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, (__bridge void *)[[GPUImageOpenGLESContext sharedImageProcessingOpenGLESContext] context], NULL, &coreVideoTextureCache);
    if (err) 
    {
        NSAssert(NO, @"Error at CVOpenGLESTextureCacheCreate %d");
    }

    CVPixelBufferPoolCreatePixelBuffer (NULL, [assetWriterPixelBufferInput pixelBufferPool], &renderTarget);

    CVOpenGLESTextureRef renderTexture;
    CVOpenGLESTextureCacheCreateTextureFromImage (kCFAllocatorDefault, coreVideoTextureCache, renderTarget,
                                                  NULL, // texture attributes
                                                  GL_TEXTURE_2D,
                                                  GL_RGBA, // opengl format
                                                  (int)videoSize.width,
                                                  (int)videoSize.height,
                                                  GL_BGRA, // native iOS format
                                                  GL_UNSIGNED_BYTE,
                                                  0,
                                                  &renderTexture);

    glBindTexture(CVOpenGLESTextureGetTarget(renderTexture), CVOpenGLESTextureGetName(renderTexture));
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, CVOpenGLESTextureGetName(renderTexture), 0);
}

Это вытаскивает буфер пикселя из пула, связанного с вводом записи ресурса, создает и связывает текстуру с ним и использует эту текстуру в качестве цели для моего FBO.

Как только я предоставил фрейм, я блокирую базовый адрес буфера пикселей:

CVPixelBufferLockBaseAddress(pixel_buffer, 0);

а затем просто загрузите его в свой файл записи ресурса:

CMTime currentTime = CMTimeMakeWithSeconds([[NSDate date] timeIntervalSinceDate:startTime],120);

if(![assetWriterPixelBufferInput appendPixelBuffer:pixel_buffer withPresentationTime:currentTime]) 
{
    NSLog(@"Problem appending pixel buffer at time: %lld", currentTime.value);
} 
else 
{
//        NSLog(@"Recorded pixel buffer at time: %lld", currentTime.value);
}
CVPixelBufferUnlockBaseAddress(pixel_buffer, 0);

if (![GPUImageOpenGLESContext supportsFastTextureUpload])
{
    CVPixelBufferRelease(pixel_buffer);
}

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

Помимо использования этого в AVAssetWriter, у меня есть код в этом ответе, который я использовал для извлечения необработанных пикселей. На практике также наблюдается значительное ускорение по сравнению с использованием glReadPixels(), хотя меньше, чем я вижу, с пулом пикселей пикселей, который я использую с AVAssetWriter.

Позор, который нигде не зарегистрирован нигде, потому что он обеспечивает огромный прирост производительности захвата видео.

Ответ 2

Что касается того, что атиман упоминал о черном экране, у меня был этот вопрос. Убедитесь, что все в порядке с текстурой и другими настройками. Я пытался захватить слой AIR OpenGL, который я сделал в конце, проблема заключалась в том, что, когда я не установил "depthAndStencil" в true случайно в манифесте приложений, моя текстура FBO была наполовину высотой (экран был разделен в половине и зеркальном отображении, я думаю, из-за параметров текстуры wrap texture). И мое видео было черным.

Это было очень неприятно, поскольку, основываясь на том, что публикует Брэд, он должен был просто работать, когда у меня были некоторые данные в текстуре. К сожалению, это не так, все должно быть "правильным" для его работы - данные в текстуре не являются гарантией для просмотра равных данных в видео. Как только я добавил depthAndStencil, моя текстура зафиксировала себя на полную высоту, и я начал получать видеозапись прямо из слоя AIR OpenGL, никаких glReadPixels и ничего:)

Так что, что Брэд описывает, действительно работает без необходимости воссоздавать буферы на каждом фрейме, вам просто нужно убедиться, что ваша настройка правильная. Если вы получаете черноту, попробуйте сыграть с размерами видео/текстуры, возможно, или некоторыми другими настройками (настройка вашего FBO?).