Russian Qt Forum
Сентябрь 23, 2024, 06:27 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2   Вниз
  Печать  
Автор Тема: Вывод видеопотока средствами Qt  (Прочитано 14551 раз)
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 помогает. Если да, то какие настройки использовать? Использовать шейдеры нужно-нет и как?
Есть ли другие варианты?

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

« Последнее редактирование: Апрель 06, 2012, 00:02 от d13mon » Записан
V1KT0P
Гость
« Ответ #1 : Апрель 06, 2012, 00:07 »

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

Буду рад любой помощи в решении данной задачи.
Ты посылаешь целый объект? Если объект больше десяти байт то лучше посылать указатель на экземпляр объекта. Быстрее будет.
Записан
d13mon
Гость
« Ответ #2 : Апрель 06, 2012, 00:29 »

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

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

Записан
V1KT0P
Гость
« Ответ #3 : Апрель 06, 2012, 00:43 »

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

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


Еще ты на каждый кадр выделяешь память, это относительно медленная операция, можешь создать два буфера. Один уже готовый передавать на отрисовку, второй для генерации нового кадра и просто менять указатели местами. Тоже сэкономит время, но только синхронизация нужна.
Записан
d13mon
Гость
« Ответ #4 : Апрель 06, 2012, 11:13 »

Еще ты на каждый кадр выделяешь память, это относительно медленная операция, можешь создать два буфера. Один уже готовый передавать на отрисовку, второй для генерации нового кадра и просто менять указатели местами. Тоже сэкономит время, но только синхронизация нужна.
Тоже дельный совет, спасибо. Только я думаю, что CPU ест всё-таки в основном из-за медленной прорисовки QImage в VideoWidget::paintEvent(). Как-нибудь ускорить это можно, не в курсе?
Записан
V1KT0P
Гость
« Ответ #5 : Апрель 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 должно помочь.
Записан
GreatSnake
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2921



Просмотр профиля
« Ответ #6 : Апрель 06, 2012, 11:30 »

Так что использование QPixmap должно помочь.
Абсолютно не факт, т.к. использование QPixmap оправдано для статики, но никак не для динамики.
Тем-более, что painter в paintEvent() и так рисует в backing-store пиксмап, т.к. по умолчанию включён double-buffering.
Записан

Qt 5.11/4.8.7 (X11/Win)
V1KT0P
Гость
« Ответ #7 : Апрель 06, 2012, 11:36 »

Так что использование QPixmap должно помочь.
Абсолютно не факт, т.к. использование QPixmap оправдано для статики, но никак не для динамики.
Тем-более, что painter в paintEvent() и так рисует в backing-store пиксмап, т.к. по умолчанию включён double-buffering.
Вот пусть автор сделает доброе дело и сравнит QPixmap и QImage, интересно будет узнать результат.
Записан
d13mon
Гость
« Ответ #8 : Апрель 08, 2012, 13:45 »

Вот пусть автор сделает доброе дело и сравнит QPixmap и QImage, интересно будет узнать результат.
Вывод через QPixmap отличается от QImage несущественно.

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

Интересует, как правильно использовать QGlWidget для данных целей и какие настройки задавать?
« Последнее редактирование: Апрель 08, 2012, 15:16 от d13mon » Записан
alexis031182
Гость
« Ответ #9 : Апрель 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
Записан
d13mon
Гость
« Ответ #10 : Апрель 08, 2012, 15:20 »

Может быть этот код как-нибудь поможет:

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

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

« Последнее редактирование: Апрель 08, 2012, 15:29 от d13mon » Записан
d13mon
Гость
« Ответ #11 : Апрель 08, 2012, 15:26 »

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

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

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

Как раз под винду потребует адаптации. Я думал, что Вам именно под неё и нужно. Если под Linux, то для XVideo тоже тогда могу запостить фрагмент кода. В обоих вариантах (OpenGL и XVideo) производительность была на высоте. Я подключал эти виджеты и к FFMpeg'у.

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

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

Да, действительно, надо было мне документацию лучше читать Улыбающийся Благодарю.
Записан
Страниц: [1] 2   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.22 секунд. Запросов: 23.