Возможно, мой вопрос связан не с самим Qt и / или QOpenGLWidget, а с буферами OpenGL в целом. В любом случае, я пытаюсь реализовать кроссплатформенный рендерер видеокадров YUV, который требует преобразования YUV в RGB и последующей визуализации результата на виджете.
Пока мне удалось следующее:
- Найдены два подходящих шейдера (1 фрагмент и 1 вершина) для улучшения преобразования YUV 2 RGB (пока наш проект поддерживает только Qt 5.6, лучшего способа для меня нет)
- Использовал QOpenGLWidget для получения корректно работающего виджета
- Сделал все возможное, чтобы QOpenGLTexture сделал рисунок
Здесь - мой очень схематичный код, который отображает видеокадры из необработанного файла YUV, и большая часть работы выполняется графическим процессором. Я был бы счастлив, если бы не проблема распределения буферов. Дело в том, что фреймы получены из некоего устаревшего кода, который предоставляет мне пользовательские обертки вокруг чего-то вроде unsigned char *data
, поэтому я должен скопировать его так:
//-----------------------------------------
GLvoid* mBufYuv; // buffer somewhere
int mFrameSize;
//-------------------------
void OpenGLDisplay::DisplayVideoFrame(unsigned char *data, int frameWidth, int frameHeight)
{
impl->mVideoW = frameWidth;
impl->mVideoH = frameHeight;
memcpy(impl->mBufYuv, data, impl->mFrameSize);
update();
}
При тестировании концепции размеры кадров и буферов были жестко закодированы, как:
// Called from the outside, assuming video frame height/width are constant
void OpenGLDisplay::InitDrawBuffer(unsigned bsize)
{
impl->mFrameSize = bsize;
impl->mBufYuv = new unsigned char[bsize];
}
Классы текстур Qt хорошо послужили целям, так что ...
// Create y, u, v texture objects respectively
impl->mTextureY = new QOpenGLTexture(QOpenGLTexture::Target2D);
impl->mTextureU = new QOpenGLTexture(QOpenGLTexture::Target2D);
impl->mTextureV = new QOpenGLTexture(QOpenGLTexture::Target2D);
impl->mTextureY->create();
impl->mTextureU->create();
impl->mTextureV->create();
// Get the texture index value of the return y component
impl->id_y = impl->mTextureY->textureId();
// Get the texture index value of the returned u component
impl->id_u = impl->mTextureU->textureId();
// Get the texture index value of the returned v component
impl->id_v = impl->mTextureV->textureId();
А ведь сам рендеринг выглядит так:
void OpenGLDisplay::paintGL()
{
// Load y data texture
// Activate the texture unit GL_TEXTURE0
glActiveTexture(GL_TEXTURE0);
// Use the texture generated from y to generate texture
glBindTexture(GL_TEXTURE_2D, impl->id_y);
// Use the memory mBufYuv data to create a real y data texture
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, impl->mVideoW, impl->mVideoH, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, impl->mBufYuv);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_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);
// Load u data texture
glActiveTexture(GL_TEXTURE1);//Activate texture unit GL_TEXTURE1
glBindTexture(GL_TEXTURE_2D, impl->id_u);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, impl->mVideoW/2, impl->mVideoH/2
, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, (char*)impl->mBufYuv + impl->mVideoW * impl->mVideoH);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_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);
// Load v data texture
glActiveTexture(GL_TEXTURE2);//Activate texture unit GL_TEXTURE2
glBindTexture(GL_TEXTURE_2D, impl->id_v);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, impl->mVideoW / 2, impl->mVideoH / 2
, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, (char*)impl->mBufYuv + impl->mVideoW * impl->mVideoH * 5/4);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_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);
// Specify y texture to use the new value can only use 0, 1, 2, etc. to represent
// the index of the texture unit, this is the place where opengl is not humanized
//0 corresponds to the texture unit GL_TEXTURE0 1 corresponds to the
// texture unit GL_TEXTURE1 2 corresponds to the texture unit GL_TEXTURE2
glUniform1i(impl->textureUniformY, 0);
// Specify the u texture to use the new value
glUniform1i(impl->textureUniformU, 1);
// Specify v texture to use the new value
glUniform1i(impl->textureUniformV, 2);
// Use the vertex array way to draw graphics
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
Как я уже упоминал выше, он отлично работает, но это всего лишь демонстрационный скетч, цель состояла в том, чтобы реализовать универсальный рендеринг видео, что означает, что соотношение сторон, разрешение и размер кадра могут изменяться динамически; таким образом, буфер (GLvoid* mBufYuv;
в приведенном выше коде) должен быть перераспределен, и, что еще хуже, мне придется записывать в него данные 25 раз в секунду. Выглядит определенно как что-то, что не будет работать слишком быстро с видео Full HD, например.
Конечно, возможны несколько тривиальных оптимизаций, приводящих к сокращению копирования данных, но Google сказал мне, что есть разные способы размещения буферов в OpenGL напрямую, по крайней мере, для PBO / PUBO и QOpenGLBuffer.
Теперь есть проблема - я совершенно запутался со всеми этими многими способами обработки текстур и не знаю ни лучшего / оптимального, ни того, который лучше всего подходит для моего случая.
Любой совет приветствуется.