Russian Qt Forum

Qt => Мультимедиа => Тема начата: d13mon от Апрель 05, 2012, 23:59



Название: Вывод видеопотока средствами Qt
Отправлено: d13mon от Апрель 05, 2012, 23:59
Ситуация такова: у меня идет видеопоток от ffmpeg в формате RGB (все в отдельном треде). Получаю очередной фрейм, преобразую в QImage, посылаю image сигналом виджету, отвечающему за прорисовку (в основном треде). Упрощенный код таков:
      
Код:
 if (frame.bufferType == AvFrame::BUFFER_TYPE_PLAIN_ARRAY)		
{
QImage::Format format = QImage::Format_RGB32;
int width = frame.format.params.video.frameWidth;
int height = frame.format.params.video.frameHeight;
int size = frame.buffer.size();
const uint8_t * data = frame.buffer.data ();
size_t frameSize = width * height;

....

m_frameData = new uint8_t[frameSize*4];
memcpy(m_frameData, frame.buffer.data (), size);

                        ....

QImage image(m_frameData, width, height, format );
if(!image.isNull())
{
emit newFrame(image);
}
                 }

Виджет (производный от QWidget*) просто присваивает полученный в слоте QImage внутренней переменной, вызывает repaint() и вот код paintEvent:
Код:
void VideoWidget::paintEvent( QPaintEvent* event)
{
QPainter painter(this);
if(drawingImage.isNull())
{
painter.fillRect(contentsRect(), Qt::black);
}
else
{
painter.drawImage(this->contentsRect(), drawingImage);
}
}

Короче, эта схема "ест" очень много процессорного времени (в дебаге 30% строго). Причем через профилировщик проверял, что ресурсы уходят в библиотеки QtGui. Вывод через DirectX происходит куда менее затратно (менее 10%).
Вопрос к знающим людям такой: как можно ускорить этот процесс? Знаю, что нормальный вывод видео через Qt возможен. Слышал, что вроде OpenGL помогает. Если да, то какие настройки использовать? Использовать шейдеры нужно-нет и как?
Есть ли другие варианты?

Буду рад любой помощи в решении данной задачи.



Название: Re: Вывод видеопотока средствами Qt
Отправлено: V1KT0P от Апрель 06, 2012, 00:07
Короче, эта схема "ест" очень много процессорного времени (в дебаге 30% строго). Причем через профилировщик проверял, что ресурсы уходят в библиотеки QtGui. Вывод через DirectX происходит куда менее затратно (менее 10%).
Вопрос к знающим людям такой: как можно ускорить этот процесс? Знаю, что нормальный вывод видео через Qt возможен. Слышал, что вроде OpenGL помогает. Если да, то какие настройки использовать? Использовать шейдеры нужно-нет и как?
Есть ли другие варианты?

Буду рад любой помощи в решении данной задачи.
Ты посылаешь целый объект? Если объект больше десяти байт то лучше посылать указатель на экземпляр объекта. Быстрее будет.


Название: Re: Вывод видеопотока средствами Qt
Отправлено: d13mon от Апрель 06, 2012, 00:29
Ты посылаешь целый объект? Если объект больше десяти байт то лучше посылать указатель на экземпляр объекта. Быстрее будет.

Спасибо за совет, надо попробовать. Хотя не думаю, что этот объект весит много, т.к я создаю image из внешнего буфера, на который он судя по докам, просто хранит ссылку.



Название: Re: Вывод видеопотока средствами Qt
Отправлено: V1KT0P от Апрель 06, 2012, 00:43
Ты посылаешь целый объект? Если объект больше десяти байт то лучше посылать указатель на экземпляр объекта. Быстрее будет.

Спасибо за совет, надо попробовать. Хотя не думаю, что этот объект весит много, т.к я создаю image из внешнего буфера, на который он судя по докам, просто хранит ссылку.


Еще ты на каждый кадр выделяешь память, это относительно медленная операция, можешь создать два буфера. Один уже готовый передавать на отрисовку, второй для генерации нового кадра и просто менять указатели местами. Тоже сэкономит время, но только синхронизация нужна.


Название: Re: Вывод видеопотока средствами Qt
Отправлено: d13mon от Апрель 06, 2012, 11:13
Еще ты на каждый кадр выделяешь память, это относительно медленная операция, можешь создать два буфера. Один уже готовый передавать на отрисовку, второй для генерации нового кадра и просто менять указатели местами. Тоже сэкономит время, но только синхронизация нужна.
Тоже дельный совет, спасибо. Только я думаю, что CPU ест всё-таки в основном из-за медленной прорисовки QImage в VideoWidget::paintEvent(). Как-нибудь ускорить это можно, не в курсе?


Название: Re: Вывод видеопотока средствами Qt
Отправлено: V1KT0P от Апрель 06, 2012, 11:23
Еще ты на каждый кадр выделяешь память, это относительно медленная операция, можешь создать два буфера. Один уже готовый передавать на отрисовку, второй для генерации нового кадра и просто менять указатели местами. Тоже сэкономит время, но только синхронизация нужна.
Тоже дельный совет, спасибо. Только я думаю, что CPU ест всё-таки в основном из-за медленной прорисовки QImage в VideoWidget::paintEvent(). Как-нибудь ускорить это можно, не в курсе?
Попробуй переделать под QPixmap. Вроде как QImage оптимизирован для ввода/вывода и попиксельного редактирования, а QPixmap создан для быстрой отрисовки на экране.
Вот даже нашел в документации откуда я это прочитал:
Цитировать
Qt provides four classes for handling image data: QImage, QPixmap, QBitmap and QPicture. QImage is designed and optimized for I/O, and for direct pixel access and manipulation, while QPixmap is designed and optimized for showing images on screen. QBitmap is only a convenience class that inherits QPixmap, ensuring a depth of 1. The isQBitmap() function returns true if a QPixmap object is really a bitmap, otherwise returns false. Finally, the QPicture class is a paint device that records and replays QPainter commands.
Так что использование QPixmap должно помочь.


Название: Re: Вывод видеопотока средствами Qt
Отправлено: GreatSnake от Апрель 06, 2012, 11:30
Так что использование QPixmap должно помочь.
Абсолютно не факт, т.к. использование QPixmap оправдано для статики, но никак не для динамики.
Тем-более, что painter в paintEvent() и так рисует в backing-store пиксмап, т.к. по умолчанию включён double-buffering.


Название: Re: Вывод видеопотока средствами Qt
Отправлено: V1KT0P от Апрель 06, 2012, 11:36
Так что использование QPixmap должно помочь.
Абсолютно не факт, т.к. использование QPixmap оправдано для статики, но никак не для динамики.
Тем-более, что painter в paintEvent() и так рисует в backing-store пиксмап, т.к. по умолчанию включён double-buffering.
Вот пусть автор сделает доброе дело и сравнит QPixmap и QImage, интересно будет узнать результат.


Название: Re: Вывод видеопотока средствами Qt
Отправлено: d13mon от Апрель 08, 2012, 13:45
Вот пусть автор сделает доброе дело и сравнит QPixmap и QImage, интересно будет узнать результат.
Вывод через QPixmap отличается от QImage несущественно.

Существенно отличается вывод при использовании QGLWidget вместо QWidget. Профилировал, там вместо функций Qt для прорисовки используются dll-ки относящиеся к видеокарте т.е. по идее происходит аппаратное ускорение.  У меня две видюхи на ноуте стоят - через встроенную вывод по скорости практически такой де как средствами Qt, на дискретную (используются ati****.dll) быстрее.
Никаких дополнительных средств и опций  OpenGL не использую, просто меняю QWidget на QGLWidget и также рисую фрейм в paintEvent через QPainter.

Интересует, как правильно использовать QGlWidget для данных целей и какие настройки задавать?


Название: Re: Вывод видеопотока средствами Qt
Отправлено: alexis031182 от Апрель 08, 2012, 14:13
Может быть этот код как-нибудь поможет:

Код:
OpenGLWidget::OpenGLWidget(int index, QWidget *parent) : VidWidget(index, parent)
{
vidFrame = NULL; doubleBuffered = GL_TRUE; isCapturing = false;

setAttribute(Qt::WA_PaintOnScreen);
setAttribute(Qt::WA_NoSystemBackground);

Display *display = x11Info().display();
if(!display) return;

int error_base, event_base;
if(!glXQueryExtension(display, &error_base, &event_base)) return;

int doublebuffervisual[] = {GLX_RGBA, GLX_DOUBLEBUFFER, None};
XVisualInfo *visualinfo = glXChooseVisual(display, DefaultScreen(display), doublebuffervisual);
if(visualinfo == NULL)
{
int singlebuffervisual[] = {GLX_RGBA, None};
visualinfo = glXChooseVisual(display, DefaultScreen(display), singlebuffervisual);
if(visualinfo == NULL) return;

doubleBuffered = false;
}

glxContext = glXCreateContext(display, visualinfo, NULL, GL_TRUE);

connect(this, SIGNAL(needRepaint()), this, SLOT(repaint()), Qt::QueuedConnection);
}


void OpenGLWidget::resizeEvent(QResizeEvent *event)
{
Display *display = x11Info().display();
if(!display) {event->accept(); return;}
if(!glxContext) {event->accept(); return;}
glXMakeCurrent(display, winId(), glxContext);
glViewport(0, 0, event->size().width(), event->size().height());
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-5, 5, -5, 5, 2, 12);
gluLookAt(0, 0, 5, 0, 0, 0, 0, 1, 0);
glMatrixMode(GL_MODELVIEW);
update();
event->accept();
}


void OpenGLWidget::paintEvent(QPaintEvent *event)
{
if(!vidFrame) {event->accept(); return;}

if(!isCapturing)
{
convertV4L2FmtToGLFmt(vidFrame->getV4L2PixFormat());

Display *display = x11Info().display();
if(!display || !glxContext)
{event->ignore(); vidFrame = NULL; return;}
glXMakeCurrent(display, winId(), glxContext);

glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, width(), height());
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-5, 5, -5, 5, 2, 12);
gluLookAt(0, 0, 5, 0, 0, 0, 0, 1, 0);
glMatrixMode(GL_MODELVIEW);

isCapturing = true;
}

Display *display = x11Info().display();
if(!display || !glxContext)
{event->ignore(); vidFrame = NULL; return;}
glXMakeCurrent(display, winId(), glxContext);

glBindTexture(GL_TEXTURE_2D, 1);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glTexImage2D(GL_TEXTURE_2D, 0, glFormat, vidFrame->getWidth(), vidFrame->getHeight(), 0, glFormat, GL_UNSIGNED_BYTE, vidFrame->getDeinterlacedData());

glDepthFunc(GL_EQUAL);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 1);
glPushMatrix();

glBegin(GL_POLYGON);
glTexCoord2f(1.0, 0.0); glVertex2f(5.0, 5.0);
glTexCoord2f(0.0, 0.0); glVertex2f(-5.0, 5.0);
glTexCoord2f(0.0, 1.0); glVertex2f(-5.0, -5.0);
glTexCoord2f(1.0, 1.0); glVertex2f(5.0, -5.0);
glEnd();

glPopMatrix();
glDisable(GL_TEXTURE_2D);

if(doubleBuffered) glXSwapBuffers(display, winId());
else glFlush();

vidFrame = NULL;

event->accept();
}

Если для Linux, то рекомендую также обратить внимание на XVideo


Название: Re: Вывод видеопотока средствами Qt
Отправлено: d13mon от Апрель 08, 2012, 15:20
Может быть этот код как-нибудь поможет:

Если для Linux, то рекомендую также обратить внимание на XVideo

Может и поможет, спасибо, изучу. Но решение вроде не под винду?



Название: Re: Вывод видеопотока средствами Qt
Отправлено: d13mon от Апрель 08, 2012, 15:26
Вычислил я, что CPU ест. Для этого отключил обработку фрейма и его прорисовку, везде где можно поставил заглушки. Короче, по приходе каждого фрейма я посылаю сигнал. Соединенный с ним слот в видеовиджете вызывает repaint(). Так вот, вызов repaint() и ест примерно 8-10% CPU. При этом неважно, рисуется что-то в  paintEvent() или это просто заглушка. Не пойму, почему так. Потому что событие о перерисовке посылается всем родительским классам виджета? Тогда как это исключить?


Название: Re: Вывод видеопотока средствами Qt
Отправлено: alexis031182 от Апрель 08, 2012, 15:27
Может и поможет, спасибо, изучу. Но решение вроде под винду?
Как раз под винду потребует адаптации. Я думал, что Вам именно под неё и нужно. Если под Linux, то для XVideo тоже тогда могу запостить фрагмент кода. В обоих вариантах (OpenGL и XVideo) производительность была на высоте. Я подключал эти виджеты и к FFMpeg'у.


Название: Re: Вывод видеопотока средствами Qt
Отправлено: alexis031182 от Апрель 08, 2012, 15:30
Вычислил я, что CPU ест. Для этого отключил обработку фрейма и его прорисовку, везде где можно поставил заглушки. Короче, по приходе каждого фрейма я посылаю сигнал. Соединенный с ним слот в видеовиджете вызывает repaint(). Так вот, вызов repaint() и ест примерно 8-10% CPU. При этом неважно, рисуется что-то в  paintEvent() или это просто заглушка. Не пойму, почему так. Потому что событие о перерисовке посылается всем родительским классам виджета? Тогда как это исключить?
По идее "event->accept()" должен исключить передачу события родителю.


Название: Re: Вывод видеопотока средствами Qt
Отправлено: d13mon от Апрель 08, 2012, 15:38
Как раз под винду потребует адаптации. Я думал, что Вам именно под неё и нужно. Если под Linux, то для XVideo тоже тогда могу запостить фрагмент кода. В обоих вариантах (OpenGL и XVideo) производительность была на высоте. Я подключал эти виджеты и к FFMpeg'у.

Да, мне как раз под винду нужно, чтобы заработало. С остальными осями потом разберемся.
А класс Display в коде что из себя представляет и для чего используется?

По идее "event->accept()" должен исключить передачу события родителю.

Да, действительно, надо было мне документацию лучше читать :) Благодарю.


Название: Re: Вывод видеопотока средствами Qt
Отправлено: alexis031182 от Апрель 08, 2012, 15:45
Да, мне как раз под винду нужно, чтобы заработало. С остальными осями потом разберемся.
А класс Display в коде что из себя представляет и для чего используется?
X11Info() - это линуксовый класс. Для винды, честно говоря, я не знаю как получить контекст экрана, но в справке указывается на QDesktopWidget в случае portable-приложения. Может быть он подойдёт.