Более быстрая альтернатива glReadPixels в iPhone OpenGL ES 2.0
Есть ли более быстрый способ доступа к буферу кадров, чем с помощью glReadPixels? Мне понадобится доступ только для чтения к небольшой прямоугольной области рендеринга в буфере кадров, чтобы обрабатывать данные дальше в ЦП. Производительность важна, потому что я должен выполнять эту операцию повторно. Я искал в Интернете и нашел какой-то подход, такой как использование Pixel Buffer Object и glMapBuffer, но кажется, что OpenGL ES 2.0 их не поддерживает.
2 ответа
Начиная с iOS 5.0, теперь есть более быстрый способ получения данных из OpenGL ES. Это не очевидно, но оказывается, что поддержка кэша текстур, добавленная в iOS 5.0, не только работает для быстрой загрузки кадров камеры в OpenGL ES, но и может использоваться в обратном порядке, чтобы получить быстрый доступ к необработанным пикселям. в текстуре OpenGL ES.
Вы можете воспользоваться этим, чтобы получить пиксели для рендеринга OpenGL ES, используя объект кадрового буфера (FBO) с прикрепленной текстурой, причем эта текстура была получена из кэша текстур. Как только вы отрендерите свою сцену в этом FBO, пиксели BGRA для этой сцены будут содержаться в вашем CVPixelBufferRef, поэтому вам не нужно будет опускать их, используя glReadPixels()
,
Это намного, намного быстрее, чем при использовании glReadPixels()
в моих тестах. Я обнаружил, что на моем iPhone 4, glReadPixels()
было узким местом в чтении видеокадров 720p для кодирования на диск. Это ограничивало кодирование от 8 до 9 FPS. Замена этого на быстрое чтение текстурного кэша позволяет мне теперь кодировать видео 720p со скоростью 20 кадров в секунду, и узкое место перешло от чтения пикселей к обработке OpenGL ES и фактической части кодирования фильма конвейера. На iPhone 4S это позволяет записывать видео в формате 1080p со скоростью 30 кадров в секунду.
Моя реализация может быть найдена в классе 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.
Жаль, что ничего из этого нигде не задокументировано, потому что это значительно повышает производительность захвата видео.
Что касается того, что Атисман упомянул о черном экране, у меня тоже была эта проблема. Убедитесь, что все в порядке с вашей текстурой и другими настройками. Я пытался запечатлеть слой OpenGL в AIR, что я и сделал в итоге, проблема заключалась в том, что, когда я не установил значение "deepAndStencil" в значение true случайно в манифесте приложения, моя текстура FBO была наполовину высотой (экран был разделен пополам и зеркальный, я думаю, из-за материала обтекания параметров парам). И мое видео было черным.
Это было довольно неприятно, так как, исходя из того, что Брэд публикует, это должно было сработать, как только у меня появились данные в текстуре. К сожалению, это не так, все должно быть "правильно", чтобы это работало - данные в текстуре не являются гарантией для просмотра равных данных в видео. Как только я добавил глубину и AndStencil, моя текстура зафиксировалась на полную высоту, и я начал получать запись видео прямо из слоя AIR OpenGL, без glReadPixels или чего-либо еще:)
Так что да, то, что описывает Брэд, действительно работает без необходимости воссоздания буферов в каждом кадре, вам просто нужно убедиться, что ваши настройки правильные. Если вы получаете черноту, попробуйте поиграть с размерами видео / текстуры, возможно, или с некоторыми другими настройками (настройка вашего FBO?).