[PATCH 4/4] qv4l2: add OpenGL video render

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The qv4l2 test utility now supports OpenGL-accelerated display of video.
This allows for using the graphics card to render the video content to screen
and to performing color space conversion.

Signed-off-by: Bård Eirik Winther <bwinther@xxxxxxxxx>
---
 configure.ac                |   8 +-
 utils/qv4l2/Makefile.am     |   9 +-
 utils/qv4l2/capture-win.cpp | 559 ++++++++++++++++++++++++++++++++++++++++++--
 utils/qv4l2/capture-win.h   |  81 ++++++-
 utils/qv4l2/qv4l2.cpp       | 173 +++++++++++---
 utils/qv4l2/qv4l2.h         |   8 +
 6 files changed, 782 insertions(+), 56 deletions(-)

diff --git a/configure.ac b/configure.ac
index e249546..97a0dfc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -128,7 +128,12 @@ if test "x$qt_pkgconfig" = "xtrue"; then
    AC_SUBST(UIC)
    AC_SUBST(RCC)
 else
-   AC_MSG_WARN(Qt4 is not available)
+   AC_MSG_WARN(Qt4 or higher is not available)
+fi
+
+PKG_CHECK_MODULES(QTOGL, [QtOpenGL >= 4.4 gl], [qt_pkgconfig_ogl=true], [qt_pkgconfig_ogl=false])
+if test "x$qt_pkgconfig_ogl" = "xfalse"; then
+   AC_MSG_WARN(Qt4 OpenGL or higher is not available)
 fi
 
 AC_SUBST([JPEG_LIBS])
@@ -237,6 +242,7 @@ AM_CONDITIONAL([WITH_LIBDVBV5], [test x$enable_libdvbv5 = xyes])
 AM_CONDITIONAL([WITH_LIBV4L], [test x$enable_libv4l != xno])
 AM_CONDITIONAL([WITH_V4LUTILS], [test x$enable_v4lutils != xno])
 AM_CONDITIONAL([WITH_QV4L2], [test ${qt_pkgconfig} = true -a x$enable_qv4l2 != xno])
+AM_CONDITIONAL([WITH_QV4L2_OGL_COMPILE], [test WITH_QV4L2 -a ${qt_pkgconfig_ogl} = true])
 AM_CONDITIONAL([WITH_V4L_PLUGINS], [test x$enable_libv4l != xno -a x$enable_shared != xno])
 AM_CONDITIONAL([WITH_V4L_WRAPPERS], [test x$enable_libv4l != xno -a x$enable_shared != xno])
 
diff --git a/utils/qv4l2/Makefile.am b/utils/qv4l2/Makefile.am
index 1f5a49f..86bf786 100644
--- a/utils/qv4l2/Makefile.am
+++ b/utils/qv4l2/Makefile.am
@@ -4,8 +4,14 @@ qv4l2_SOURCES = qv4l2.cpp general-tab.cpp ctrl-tab.cpp vbi-tab.cpp v4l2-api.cpp
   raw2sliced.cpp qv4l2.h capture-win.h general-tab.h vbi-tab.h v4l2-api.h raw2sliced.h
 nodist_qv4l2_SOURCES = moc_qv4l2.cpp moc_general-tab.cpp moc_capture-win.cpp moc_vbi-tab.cpp qrc_qv4l2.cpp
 qv4l2_LDADD = ../../lib/libv4l2/libv4l2.la ../../lib/libv4lconvert/libv4lconvert.la ../libv4l2util/libv4l2util.la
+
+if WITH_QV4L2_OGL_COMPILE
+qv4l2_CPPFLAGS = $(QTOGL_CFLAGS) -DENABLE_OGL
+qv4l2_LDFLAGS = $(QTOGL_LIBS)
+else
 qv4l2_CPPFLAGS = $(QT_CFLAGS)
 qv4l2_LDFLAGS = $(QT_LIBS)
+endif
 
 EXTRA_DIST = exit.png fileopen.png qv4l2_24x24.png qv4l2_64x64.png qv4l2.png qv4l2.svg snapshot.png \
   video-television.png fileclose.png qv4l2_16x16.png qv4l2_32x32.png qv4l2.desktop qv4l2.qrc record.png \
@@ -37,5 +43,4 @@ install-data-local:
 	$(INSTALL_DATA) -D -p "$(srcdir)/qv4l2_24x24.png" "$(DESTDIR)$(datadir)/icons/hicolor/24x24/apps/qv4l2.png"
 	$(INSTALL_DATA) -D -p "$(srcdir)/qv4l2_32x32.png" "$(DESTDIR)$(datadir)/icons/hicolor/32x32/apps/qv4l2.png"
 	$(INSTALL_DATA) -D -p "$(srcdir)/qv4l2_64x64.png" "$(DESTDIR)$(datadir)/icons/hicolor/64x64/apps/qv4l2.png"
-	$(INSTALL_DATA) -D -p "$(srcdir)/qv4l2.svg"       "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/qv4l2.svg"
-
+	$(INSTALL_DATA) -D -p "$(srcdir)/qv4l2.svg"       "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/qv4l2.svg"
\ No newline at end of file
diff --git a/utils/qv4l2/capture-win.cpp b/utils/qv4l2/capture-win.cpp
index 7ac3fa1..132d23d 100644
--- a/utils/qv4l2/capture-win.cpp
+++ b/utils/qv4l2/capture-win.cpp
@@ -16,6 +16,7 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 #include <stdio.h>
 #include <QLabel>
 #include <QImage>
@@ -24,23 +25,12 @@
 #include <QApplication>
 #include <QDesktopWidget>
 
-#include "qv4l2.h"
 #include "capture-win.h"
 
-CaptureWin::CaptureWin()
+CaptureWin::CaptureWin(bool useOGL)
 {
-	QVBoxLayout *vbox = new QVBoxLayout(this);
-
-	setWindowTitle("V4L2 Capture");
-	m_label = new QLabel();
-	m_msg = new QLabel("No frame");
-
-	vbox->addWidget(m_label);
-	vbox->addWidget(m_msg);
-
-	int l, t, r, b;
-	vbox->getContentsMargins(&l, &t, &r, &b);
-	vbox->setSpacing(b);
+	m_enableOGL = useOGL;
+	buildCanvas();
 
 	hotkeyClose = new QShortcut(Qt::CTRL+Qt::Key_W, this);
 	QObject::connect(hotkeyClose, SIGNAL(activated()), this, SLOT(close()));
@@ -49,6 +39,48 @@ CaptureWin::CaptureWin()
 CaptureWin::~CaptureWin()
 {
 	delete hotkeyClose;
+	clearCanvas();
+}
+
+void CaptureWin::clearCanvas()
+{
+	if (layout() != NULL) {
+		if (m_enableOGL) {
+#ifdef ENABLE_OGL
+			delete m_canvas;
+#endif
+		} else {
+			delete m_videoSurface;
+		}
+
+		delete m_information;
+		layout()->removeWidget(this);
+		delete layout();
+
+	}
+}
+
+void CaptureWin::buildCanvas()
+{
+	QVBoxLayout *vbox = new QVBoxLayout(this);
+
+	if (m_enableOGL) {
+		setWindowTitle("V4L2 Preview (OpenGL)");
+#if ENABLE_OGL
+		m_canvas = new CaptureCanvas();
+		vbox->addWidget(m_canvas, 2000);
+#endif
+	} else {
+		setWindowTitle("V4L2 Preview");
+		m_videoSurface = new QLabel();
+		vbox->addWidget(m_videoSurface, 2000);
+	}
+
+	int l, t, r, b;
+	m_information = new QLabel("No Frame");
+	vbox->addWidget(m_information, 1, Qt::AlignBottom);
+	vbox->getContentsMargins(&l, &t, &r, &b);
+	vbox->setSpacing(b);
 }
 
 void CaptureWin::setMinimumSize(int minw, int minh)
@@ -59,7 +91,7 @@ void CaptureWin::setMinimumSize(int minw, int minh)
 	int l, t, r, b;
 	layout()->getContentsMargins(&l, &t, &r, &b);
 	minw += l + r;
-	minh += t + b + m_msg->minimumSizeHint().height() + layout()->spacing();
+	minh += t + b + m_information->minimumSizeHint().height() + layout()->spacing();
 
 	if (minw > resolution.width())
 		minw = resolution.width();
@@ -71,10 +103,43 @@ void CaptureWin::setMinimumSize(int minw, int minh)
 	resize(minw, minh);
 }
 
-void CaptureWin::setImage(const QImage &image, const QString &status)
+void CaptureWin::setFrameCPU(const QImage &image, const QString &status)
+{
+	m_videoSurface->setPixmap(QPixmap::fromImage(image));
+	m_information->setText(status);
+}
+
+void CaptureWin::setFrameOGL(int width, int height, __u32 format,
+			     unsigned char *data, const QString &status)
+{
+#if ENABLE_OGL
+	m_canvas->setFrame(width, height, format, data);
+#endif
+	m_information->setText(status);
+}
+
+void CaptureWin::stop()
+{
+#if ENABLE_OGL
+	if (m_enableOGL)
+		m_canvas->stop();
+#endif
+}
+
+void CaptureWin::setEnableOGL(bool enable)
+{
+#ifdef ENABLE_OGL
+	if (m_enableOGL != enable) {
+		clearCanvas();
+		m_enableOGL = enable;
+		buildCanvas();
+	}
+#endif
+}
+
+bool CaptureWin::isEnableOGL()
 {
-	m_label->setPixmap(QPixmap::fromImage(image));
-	m_msg->setText(status);
+	return m_enableOGL;
 }
 
 void CaptureWin::closeEvent(QCloseEvent *event)
@@ -82,3 +147,461 @@ void CaptureWin::closeEvent(QCloseEvent *event)
 	QWidget::closeEvent(event);
 	emit close();
 }
+
+#if ENABLE_OGL
+CaptureCanvas::CaptureCanvas() :
+	m_frameHeight(0),
+	m_frameWidth(0),
+	m_screenTextureCount(0),
+	m_formatChange(false),
+	m_frameFormat(0),
+	m_frameData(NULL)
+{
+	m_glfunction.initializeGLFunctions(context());
+}
+
+CaptureCanvas::~CaptureCanvas()
+{
+	clearShader();
+}
+
+void CaptureCanvas::clearShader()
+{
+	glDeleteTextures(m_screenTextureCount, m_screenTexture);
+	m_shaderProgram.release();
+	m_shaderProgram.removeAllShaders();
+}
+
+void CaptureCanvas::stop()
+{
+	// Setting the m_frameData to NULL stops OpenGL
+	// from updating frames on repaint
+	m_frameData = NULL;
+}
+
+void CaptureCanvas::initializeGL()
+{
+	glShadeModel(GL_FLAT);
+	glEnable(GL_TEXTURE_2D);
+
+	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+	checkError("InitializeGL");
+}
+
+
+void CaptureCanvas::resizeGL(int width, int height)
+{
+	// Resizing is disabled by setting viewport equal to frame size
+	glViewport(0, 0, m_frameWidth, m_frameHeight);
+}
+
+void CaptureCanvas::setFrame(int width, int height, __u32 format, unsigned char *data)
+{
+	if (format != m_frameFormat || width != m_frameWidth || height != m_frameHeight) {
+		m_formatChange = true;
+		m_frameWidth = width;
+		m_frameHeight = height;
+		m_frameFormat = format;
+
+		setMaximumSize(m_frameWidth, m_frameHeight);
+		showFullScreen();
+	}
+
+	m_frameData = data;
+	updateGL();
+}
+
+void CaptureCanvas::checkError(const char *msg)
+{
+	int err = glGetError();
+	if (err) fprintf(stderr, "OpenGL Error 0x%x: %s.\n", err, msg);
+}
+
+void CaptureCanvas::changeShader()
+{
+	m_formatChange = false;
+	clearShader();
+
+	glMatrixMode(GL_PROJECTION);
+	glLoadIdentity();
+	glOrtho(0, m_frameWidth, m_frameHeight, 0, 0, 1);
+	resizeGL(width(), height());
+	checkError("Render settings.\n");
+
+	switch (m_frameFormat) {
+	case V4L2_PIX_FMT_YUYV:
+	case V4L2_PIX_FMT_YVYU:
+	case V4L2_PIX_FMT_UYVY:
+	case V4L2_PIX_FMT_VYUY:
+		shader_YUY2(m_frameFormat);
+		break;
+
+	case V4L2_PIX_FMT_YUV420:
+	case V4L2_PIX_FMT_YVU420:
+		shader_YUV();
+		break;
+
+	case V4L2_PIX_FMT_RGB32:
+		m_screenTextureCount = 1;
+		glActiveTexture(GL_TEXTURE0);
+		glGenTextures(m_screenTextureCount, m_screenTexture);
+		configureTexture(0);
+		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA4, m_frameWidth, m_frameHeight, 0,
+			     GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, NULL);
+		checkError("RGB32 shader");
+		break;
+
+	case V4L2_PIX_FMT_BGR32:
+		m_screenTextureCount = 1;
+		glActiveTexture(GL_TEXTURE0);
+		glGenTextures(m_screenTextureCount, m_screenTexture);
+		configureTexture(0);
+		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA4, m_frameWidth, m_frameHeight, 0,
+			     GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, NULL);
+		checkError("BGR32 shader");
+		break;
+
+	case V4L2_PIX_FMT_RGB555:
+		m_screenTextureCount = 1;
+		glActiveTexture(GL_TEXTURE0);
+		glGenTextures(m_screenTextureCount, m_screenTexture);
+		configureTexture(0);
+		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB5_A1, m_frameWidth, m_frameHeight, 0,
+			     GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, NULL);
+		checkError("RGB555 shader");
+		break;
+
+	case V4L2_PIX_FMT_RGB565:
+		m_screenTextureCount = 1;
+		glActiveTexture(GL_TEXTURE0);
+		glGenTextures(m_screenTextureCount, m_screenTexture);
+		configureTexture(0);
+		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_frameWidth, m_frameHeight, 0,
+			     GL_RGB, GL_UNSIGNED_SHORT_5_6_5, NULL);
+		checkError("RGB565 shader");
+		break;
+	case V4L2_PIX_FMT_BGR24:
+		shader_BGR();
+		break;
+	case V4L2_PIX_FMT_RGB24:
+	default:
+		m_screenTextureCount = 1;
+		glActiveTexture(GL_TEXTURE0);
+		glGenTextures(m_screenTextureCount, m_screenTexture);
+		configureTexture(0);
+		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_frameWidth, m_frameHeight, 0,
+			     GL_RGB, GL_UNSIGNED_BYTE, NULL);
+		checkError("Default shader");
+		break;
+	}
+}
+
+void CaptureCanvas::paintGL()
+{
+	if (m_frameWidth < 1 || m_frameHeight < 1) {
+		glClear(GL_COLOR_BUFFER_BIT);
+		return;
+	}
+
+	if (m_formatChange)
+		changeShader();
+
+	glClear(GL_COLOR_BUFFER_BIT);
+
+	switch (m_frameFormat) {
+	case V4L2_PIX_FMT_YUYV:
+	case V4L2_PIX_FMT_YVYU:
+	case V4L2_PIX_FMT_UYVY:
+	case V4L2_PIX_FMT_VYUY:
+		render_YUY2();
+		break;
+
+	case V4L2_PIX_FMT_YUV420:
+	case V4L2_PIX_FMT_YVU420:
+		render_YUV(m_frameFormat);
+		break;
+
+	case V4L2_PIX_FMT_RGB32:
+		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_frameWidth, m_frameHeight,
+				GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, m_frameData);
+		checkError("RGB32 paint");
+		break;
+
+	case V4L2_PIX_FMT_BGR32:
+		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_frameWidth, m_frameHeight,
+				GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, m_frameData);
+		checkError("BGR32 paint");
+		break;
+
+	case V4L2_PIX_FMT_RGB555:
+		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_frameWidth, m_frameHeight,
+				GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, m_frameData);
+		checkError("RGB555 paint");
+		break;
+
+	case V4L2_PIX_FMT_RGB565:
+		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_frameWidth, m_frameHeight,
+				GL_RGB, GL_UNSIGNED_SHORT_5_6_5, m_frameData);
+		checkError("RGB565 paint");
+		break;
+
+	case V4L2_PIX_FMT_BGR24:
+		render_BGR();
+		break;
+	case V4L2_PIX_FMT_RGB24:
+	default:
+		glActiveTexture(GL_TEXTURE0);
+		glBindTexture(GL_TEXTURE_2D, m_screenTexture[0]);
+		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_frameWidth, m_frameHeight,
+				GL_RGB, GL_UNSIGNED_BYTE, m_frameData);
+		checkError("Default paint");
+		break;
+	}
+
+	glBegin(GL_QUADS);
+	glTexCoord2f(0.0f, 0.0f); glVertex2f(0.0, 0);
+	glTexCoord2f(1.0f, 0.0f); glVertex2f(m_frameWidth, 0);
+	glTexCoord2f(1.0f, 1.0f); glVertex2f(m_frameWidth, m_frameHeight);
+	glTexCoord2f(0.0f, 1.0f); glVertex2f(0, m_frameHeight);
+	glEnd();
+}
+
+void CaptureCanvas::configureTexture(size_t idx)
+{
+	glBindTexture(GL_TEXTURE_2D, m_screenTexture[idx]);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
+}
+
+void CaptureCanvas::shader_YUV()
+{
+	m_screenTextureCount = 3;
+	glGenTextures(m_screenTextureCount, m_screenTexture);
+
+	glActiveTexture(GL_TEXTURE0);
+	configureTexture(0);
+	glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_frameWidth, m_frameHeight, 0,
+		     GL_LUMINANCE, GL_UNSIGNED_BYTE, NULL);
+	checkError("YUV shader texture 0");
+
+	glActiveTexture(GL_TEXTURE1);
+	configureTexture(1);
+	glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_frameWidth / 2, m_frameHeight / 2, 0,
+		     GL_LUMINANCE, GL_UNSIGNED_BYTE, NULL);
+	checkError("YUV shader texture 1");
+
+	glActiveTexture(GL_TEXTURE2);
+	configureTexture(2);
+	glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_frameWidth / 2, m_frameHeight / 2, 0,
+		     GL_LUMINANCE, GL_UNSIGNED_BYTE, NULL);
+	checkError("YUV shader texture 2");
+
+	bool src_c = m_shaderProgram.addShaderFromSourceCode(
+				QGLShader::Fragment,
+				"uniform sampler2D ytex;"
+				"uniform sampler2D utex;"
+				"uniform sampler2D vtex;"
+				"void main()"
+				"{"
+				"   vec2 xy = vec2(gl_TexCoord[0].xy);"
+				"   float y = 1.1640625 * (texture2D(ytex, xy).r - 0.0625);"
+				"   float u = texture2D(utex, xy).r - 0.5;"
+				"   float v = texture2D(vtex, xy).r - 0.5;"
+				"   float r = y + 1.59765625 * v;"
+				"   float g = y - 0.390625 * u - 0.8125 *v;"
+				"   float b = y + 2.015625 * u;"
+				"   gl_FragColor = vec4(r, g, b, 1.0);"
+				"}"
+				);
+
+	if (!src_c)
+		fprintf(stderr, "OpenGL Error: YUV shader compilation failed.\n");
+
+	m_shaderProgram.bind();
+}
+
+void CaptureCanvas::render_YUV(__u32 format)
+{
+	int idxU;
+	int idxV;
+	if (format == V4L2_PIX_FMT_YUV420) {
+		idxU = m_frameWidth * m_frameHeight;
+		idxV = idxU + (idxU / 4);
+	} else {
+		idxV = m_frameWidth * m_frameHeight;
+		idxU = idxV + (idxV / 4);
+	}
+
+	glActiveTexture(GL_TEXTURE0);
+	glBindTexture(GL_TEXTURE_2D, m_screenTexture[0]);
+	GLint Y = m_glfunction.glGetUniformLocation(m_shaderProgram.programId(), "ytex");
+	glUniform1i(Y, 0);
+	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_frameWidth, m_frameHeight,
+			GL_LUMINANCE, GL_UNSIGNED_BYTE, m_frameData);
+	checkError("YUV paint ytex");
+
+	glActiveTexture(GL_TEXTURE1);
+	glBindTexture(GL_TEXTURE_2D, m_screenTexture[1]);
+	GLint U = m_glfunction.glGetUniformLocation(m_shaderProgram.programId(), "utex");
+	glUniform1i(U, 1);
+	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_frameWidth / 2, m_frameHeight / 2,
+			GL_LUMINANCE, GL_UNSIGNED_BYTE, m_frameData == NULL ? NULL : &m_frameData[idxU]);
+	checkError("YUV paint utex");
+
+	glActiveTexture(GL_TEXTURE2);
+	glBindTexture(GL_TEXTURE_2D, m_screenTexture[2]);
+	GLint V = m_glfunction.glGetUniformLocation(m_shaderProgram.programId(), "vtex");
+	glUniform1i(V, 2);
+	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_frameWidth / 2, m_frameHeight / 2,
+			GL_LUMINANCE, GL_UNSIGNED_BYTE, m_frameData == NULL ? NULL : &m_frameData[idxV]);
+	checkError("YUV paint vtex");
+}
+
+void CaptureCanvas::shader_BGR()
+{
+	m_screenTextureCount = 1;
+	glGenTextures(m_screenTextureCount, m_screenTexture);
+	configureTexture(0);
+	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_frameWidth, m_frameHeight, 0,
+		     GL_RGB, GL_UNSIGNED_BYTE, NULL);
+	checkError("BGR shader");
+
+	bool src_c = m_shaderProgram.addShaderFromSourceCode(
+				QGLShader::Fragment,
+				"uniform sampler2D tex;"
+				"void main()"
+				"{"
+				"   vec4 color = texture2D(tex, gl_TexCoord[0].xy);"
+				"   gl_FragColor = vec4(color.b, color.g, color.r, 1.0);"
+				"}"
+				);
+	if (!src_c)
+		fprintf(stderr, "OpenGL Error: BGR shader compilation failed.\n");
+
+	m_shaderProgram.bind();
+}
+
+void CaptureCanvas::render_BGR()
+{
+	glActiveTexture(GL_TEXTURE0);
+	glBindTexture(GL_TEXTURE_2D, m_screenTexture[0]);
+	GLint Y = m_glfunction.glGetUniformLocation(m_shaderProgram.programId(), "tex");
+	glUniform1i(Y, 0);
+	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_frameWidth, m_frameHeight,
+			GL_RGB, GL_UNSIGNED_BYTE, m_frameData);
+	checkError("BGR paint");
+}
+
+QString CaptureCanvas::shader_YUY2_invariant(__u32 format)
+{
+	switch (format) {
+	case V4L2_PIX_FMT_YUYV:
+		return QString("y = (luma_chroma.r - 0.0625) * 1.1643;"
+			       "if (mod(xcoord, 2.0) == 0.0) {"
+			       "   u = luma_chroma.a;"
+			       "   v = texture2D(tex, vec2(pixelx + texl_w, pixely)).a;"
+			       "} else {"
+			       "   v = luma_chroma.a;"
+			       "   u = texture2D(tex, vec2(pixelx - texl_w, pixely)).a;"
+			       "}"
+			       );
+
+	case V4L2_PIX_FMT_YVYU:
+		return QString("y = (luma_chroma.r - 0.0625) * 1.1643;"
+			       "if (mod(xcoord, 2.0) == 0.0) {"
+			       "   v = luma_chroma.a;"
+			       "   u = texture2D(tex, vec2(pixelx + texl_w, pixely)).a;"
+			       "} else {"
+			       "   u = luma_chroma.a;"
+			       "   v = texture2D(tex, vec2(pixelx - texl_w, pixely)).a;"
+			       "}"
+			       );
+
+	case V4L2_PIX_FMT_UYVY:
+		return QString("y = (luma_chroma.a - 0.0625) * 1.1643;"
+			       "if (mod(xcoord, 2.0) == 0.0) {"
+			       "   u = luma_chroma.r;"
+			       "   v = texture2D(tex, vec2(pixelx + texl_w, pixely)).r;"
+			       "} else {"
+			       "   v = luma_chroma.r;"
+			       "   u = texture2D(tex, vec2(pixelx - texl_w, pixely)).r;"
+			       "}"
+			       );
+
+	case V4L2_PIX_FMT_VYUY:
+		return QString("y = (luma_chroma.a - 0.0625) * 1.1643;"
+			       "if (mod(xcoord, 2.0) == 0.0) {"
+			       "   v = luma_chroma.r;"
+			       "   u = texture2D(tex, vec2(pixelx + texl_w, pixely)).r;"
+			       "} else {"
+			       "   u = luma_chroma.r;"
+			       "   v = texture2D(tex, vec2(pixelx - texl_w, pixely)).r;"
+			       "}"
+			       );
+
+	default:
+		return QString();
+	}
+}
+
+void CaptureCanvas::shader_YUY2(__u32 format)
+{
+	m_screenTextureCount = 1;
+	glGenTextures(m_screenTextureCount, m_screenTexture);
+	configureTexture(0);
+	glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, m_frameWidth, m_frameHeight, 0,
+		     GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, NULL);
+	checkError("YUY2 shader");
+
+	QString codeHead = QString("uniform sampler2D tex;"
+				   "uniform float texl_w;"
+				   "uniform float tex_w;"
+				   "void main()"
+				   "{"
+				   "   float y, u, v;"
+				   "   float pixelx = gl_TexCoord[0].x;"
+				   "   float pixely = gl_TexCoord[0].y;"
+				   "   float xcoord = floor(pixelx * tex_w);"
+				   "   vec4 luma_chroma = texture2D(tex, vec2(pixelx, pixely));"
+				   );
+
+	QString codeBody = shader_YUY2_invariant(format);
+
+	QString codeTail = QString("   u = u - 0.5;"
+				   "   v = v - 0.5;"
+				   "   float r = y + 1.5958 * v;"
+				   "   float g = y - 0.39173 * u - 0.81290 * v;"
+				   "   float b = y + 2.017 * u;"
+				   "   gl_FragColor = vec4(r, g, b, 1.0);"
+				   "}"
+				   );
+
+	bool src_ok = m_shaderProgram.addShaderFromSourceCode(
+				QGLShader::Fragment, QString("%1%2%3").arg(codeHead, codeBody, codeTail)
+				);
+
+	if (!src_ok)
+		fprintf(stderr, "OpenGL Error: YUY2 shader compilation failed.\n");
+
+	m_shaderProgram.bind();
+}
+
+void CaptureCanvas::render_YUY2()
+{
+	int idx;
+	idx = glGetUniformLocation(m_shaderProgram.programId(), "texl_w"); // Texel width
+	glUniform1f(idx, 1.0 / m_frameWidth);
+	idx = glGetUniformLocation(m_shaderProgram.programId(), "tex_w"); // Texture width
+	glUniform1f(idx, m_frameWidth);
+
+	glActiveTexture(GL_TEXTURE0);
+	glBindTexture(GL_TEXTURE_2D, m_screenTexture[0]);
+	GLint Y = m_glfunction.glGetUniformLocation(m_shaderProgram.programId(), "tex");
+	glUniform1i(Y, 0);
+	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_frameWidth, m_frameHeight,
+			GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, m_frameData);
+	checkError("YUY2 paint");
+}
+#endif
diff --git a/utils/qv4l2/capture-win.h b/utils/qv4l2/capture-win.h
index 3de3447..4d6f04c 100644
--- a/utils/qv4l2/capture-win.h
+++ b/utils/qv4l2/capture-win.h
@@ -24,19 +24,83 @@
 #include <QShortcut>
 #include <sys/time.h>
 
+#ifdef ENABLE_OGL
+#define GL_GLEXT_PROTOTYPES
+#include <QGLWidget>
+#include <QGLShader>
+#include <QGLShaderProgram>
+#include <QGLFunctions>
+#endif
+
+#include "qv4l2.h"
+
 class QImage;
 class QLabel;
 
+#ifdef ENABLE_OGL
+class CaptureCanvas : public QGLWidget
+{
+public:
+	CaptureCanvas();
+	~CaptureCanvas();
+
+	void setFrame(int width, int height, __u32 format, unsigned char *bufptr);
+	void stop();
+
+protected:
+	void paintGL();
+	void initializeGL();
+	void resizeGL(int width, int height);
+
+private:
+	// Colorspace conversion shaders
+	void shader_YUV();
+	void shader_BGR();
+	void shader_YUY2(__u32 format);
+	QString shader_YUY2_invariant(__u32 format);
+
+	// Colorspace conversion render
+	void render_BGR();
+	void render_YUY2();
+	void render_YUV(__u32 format);
+
+
+	void clearShader();
+	void changeShader();
+	void configureTexture(size_t idx);
+	void checkError(const char *msg);
+
+	int m_frameHeight;
+	int m_frameWidth;
+	int m_screenTextureCount;
+	bool m_formatChange;
+	__u32 m_frameFormat;
+	GLuint m_screenTexture[3]; // This must be equal to the max number of textures that any shader uses
+	QGLFunctions m_glfunction;
+	unsigned char *m_frameData;
+	QGLShaderProgram m_shaderProgram;
+
+};
+#endif /* OGL_COMPILE */
+
+
+
+
 class CaptureWin : public QWidget
 {
 	Q_OBJECT
 
 public:
-	CaptureWin();
+	CaptureWin(bool useOGL);
 	~CaptureWin();
 
 	virtual void setMinimumSize(int minw, int minh);
-	void setImage(const QImage &image, const QString &status);
+	void setFrameCPU(const QImage &image, const QString &status);
+	void setFrameOGL(int width, int height, __u32 format,
+			 unsigned char *data, const QString &status);
+	void setEnableOGL(bool enable);
+	bool isEnableOGL();
+	void stop();
 
 protected:
 	virtual void closeEvent(QCloseEvent *event);
@@ -45,9 +109,16 @@ signals:
 	void close();
 
 private:
-	QLabel *m_label;
-	QLabel *m_msg;
+	void buildCanvas();
+	void clearCanvas();
+
+	bool m_enableOGL;
+	QLabel *m_videoSurface;
+	QLabel *m_information;
 	QShortcut *hotkeyClose;
+#if ENABLE_OGL
+	CaptureCanvas *m_canvas;
+#endif
 };
 
-#endif
+#endif /* CAPTURE_WIN_H */
diff --git a/utils/qv4l2/qv4l2.cpp b/utils/qv4l2/qv4l2.cpp
index bb1d84f..22f93f1 100644
--- a/utils/qv4l2/qv4l2.cpp
+++ b/utils/qv4l2/qv4l2.cpp
@@ -107,6 +107,15 @@ ApplicationWindow::ApplicationWindow() :
 	quitAct->setShortcut(Qt::CTRL+Qt::Key_Q);
 	connect(quitAct, SIGNAL(triggered()), this, SLOT(close()));
 
+#ifdef ENABLE_OGL
+	m_previewAct = new QAction("Use Open&GL", this);
+	m_previewAct->setStatusTip("Use the GPU for video capture preview if set.");
+	m_previewAct->setCheckable(true);
+	m_previewAct->setChecked(true);
+	m_previewAct->setEnabled(true);
+	connect(m_previewAct, SIGNAL(triggered()), this, SLOT(setPreviewMode()));
+#endif
+
 	QMenu *fileMenu = menuBar()->addMenu("&File");
 	fileMenu->addAction(openAct);
 	fileMenu->addAction(openRawAct);
@@ -114,7 +123,6 @@ ApplicationWindow::ApplicationWindow() :
 	fileMenu->addAction(m_capStartAct);
 	fileMenu->addAction(m_snapshotAct);
 	fileMenu->addAction(m_saveRawAct);
-	fileMenu->addAction(m_showFramesAct);
 	fileMenu->addSeparator();
 	fileMenu->addAction(quitAct);
 
@@ -128,6 +136,13 @@ ApplicationWindow::ApplicationWindow() :
 	toolBar->addSeparator();
 	toolBar->addAction(quitAct);
 
+	QMenu *previewMenu = menuBar()->addMenu("&Preview");
+	previewMenu->addAction(m_showFramesAct);
+#ifdef ENABLE_OGL
+	previewMenu->addSeparator();
+	previewMenu->addAction(m_previewAct);
+#endif
+
 	QMenu *helpMenu = menuBar()->addMenu("&Help");
 	helpMenu->addAction("&About", this, SLOT(about()), Qt::Key_F1);
 
@@ -151,6 +166,15 @@ ApplicationWindow::~ApplicationWindow()
 
 void ApplicationWindow::setDevice(const QString &device, bool rawOpen)
 {
+	bool useOGL;
+#ifdef ENABLE_OGL
+	useOGL = true;
+#else
+	useOGL = false;
+#endif
+	if (m_capture != NULL)
+		useOGL = m_capture->isEnableOGL();
+
 	closeDevice();
 	m_sigMapper = new QSignalMapper(this);
 	connect(m_sigMapper, SIGNAL(mapped(int)), this, SLOT(ctrlAction(int)));
@@ -158,9 +182,12 @@ void ApplicationWindow::setDevice(const QString &device, bool rawOpen)
 	if (!open(device, !rawOpen))
 		return;
 
-	m_capture = new CaptureWin;
+	m_capture = new CaptureWin(useOGL);
 	m_capture->setMinimumSize(150, 50);
 	connect(m_capture, SIGNAL(close()), this, SLOT(closeCaptureWin()));
+#ifdef ENABLE_OGL
+	m_previewAct->setChecked(useOGL);
+#endif
 
 	QWidget *w = new QWidget(m_tabs);
 	m_genTab = new GeneralTab(device, *this, 4, w);
@@ -203,6 +230,18 @@ void ApplicationWindow::openrawdev()
 		setDevice(d.selectedFiles().first(), true);
 }
 
+void ApplicationWindow::setPreviewMode()
+{
+	if (m_capStartAct->isChecked()) {
+#ifdef ENABLE_OGL
+		m_previewAct->setChecked(m_capture->isEnableOGL());
+#endif
+		return;
+	}
+
+	m_capture->setEnableOGL(m_previewAct->isChecked());
+}
+
 void ApplicationWindow::ctrlEvent()
 {
 	v4l2_event ev;
@@ -344,8 +383,15 @@ void ApplicationWindow::capVbiFrame()
 		m_tv = tv;
 	}
 	status = QString("Frame: %1 Fps: %2").arg(++m_frame).arg(m_fps);
-	if (m_showFrames)
-		m_capture->setImage(*m_capImage, status);
+	if (m_showFrames) {
+		if (m_capture->isEnableOGL())
+			m_capture->setFrameOGL(m_capImage->width(), m_capImage->height(),
+					       m_capSrcFormat.fmt.pix.pixelformat, m_capImage->bits(), "No frame");
+		else
+			m_capture->setFrameCPU(*m_capImage, "No frame");
+
+		m_capture->setFrameCPU(*m_capImage, status);
+	}
 	curStatus = statusBar()->currentMessage();
 	if (curStatus.isEmpty() || curStatus.startsWith("Frame: "))
 		statusBar()->showMessage(status);
@@ -361,6 +407,9 @@ void ApplicationWindow::capFrame()
 	int err = 0;
 	bool again;
 
+	unsigned char *displaybuf = NULL;
+	bool ogl_use = m_capture->isEnableOGL();
+
 	switch (m_capMethod) {
 	case methodRead:
 		s = read(m_frameData, m_capSrcFormat.fmt.pix.sizeimage);
@@ -380,9 +429,13 @@ void ApplicationWindow::capFrame()
 			break;
 		if (m_mustConvert)
 			err = v4lconvert_convert(m_convertData, &m_capSrcFormat, &m_capDestFormat,
-				m_frameData, s,
-				m_capImage->bits(), m_capDestFormat.fmt.pix.sizeimage);
-		if (!m_mustConvert || err < 0)
+						 m_frameData, s,
+						 m_capImage->bits(), m_capDestFormat.fmt.pix.sizeimage);
+		if (ogl_use && m_mustConvert && err != -1)
+			displaybuf = m_capImage->bits();
+		if (ogl_use && !m_mustConvert)
+			displaybuf = m_frameData;
+		if (!ogl_use && !m_mustConvert)
 			memcpy(m_capImage->bits(), m_frameData, std::min(s, m_capImage->numBytes()));
 		break;
 
@@ -397,11 +450,14 @@ void ApplicationWindow::capFrame()
 
 		if (m_showFrames) {
 			if (m_mustConvert)
-				err = v4lconvert_convert(m_convertData,
-					&m_capSrcFormat, &m_capDestFormat,
-					(unsigned char *)m_buffers[buf.index].start, buf.bytesused,
-					m_capImage->bits(), m_capDestFormat.fmt.pix.sizeimage);
-			if (!m_mustConvert || err < 0)
+				err = v4lconvert_convert(m_convertData, &m_capSrcFormat, &m_capDestFormat,
+							 (unsigned char *)m_buffers[buf.index].start, buf.bytesused,
+							 m_capImage->bits(), m_capDestFormat.fmt.pix.sizeimage);
+			if (ogl_use && m_mustConvert && err != -1)
+				displaybuf = m_capImage->bits();
+			if (ogl_use && !m_mustConvert)
+				displaybuf = (unsigned char *)m_buffers[buf.index].start;
+			if (!ogl_use && !m_mustConvert)
 				memcpy(m_capImage->bits(),
 				       (unsigned char *)m_buffers[buf.index].start,
 				       std::min(buf.bytesused, (unsigned)m_capImage->numBytes()));
@@ -425,11 +481,14 @@ void ApplicationWindow::capFrame()
 
 		if (m_showFrames) {
 			if (m_mustConvert)
-				err = v4lconvert_convert(m_convertData,
-					&m_capSrcFormat, &m_capDestFormat,
-					(unsigned char *)buf.m.userptr, buf.bytesused,
-					m_capImage->bits(), m_capDestFormat.fmt.pix.sizeimage);
-			if (!m_mustConvert || err < 0)
+				err = v4lconvert_convert(m_convertData, &m_capSrcFormat, &m_capDestFormat,
+							 (unsigned char *)buf.m.userptr, buf.bytesused,
+							 m_capImage->bits(), m_capDestFormat.fmt.pix.sizeimage);
+			if (ogl_use && m_mustConvert && err != -1)
+				displaybuf = m_capImage->bits();
+			if (ogl_use && !m_mustConvert)
+				displaybuf = (unsigned char *)buf.m.userptr;
+			if (!ogl_use && !m_mustConvert)
 				memcpy(m_capImage->bits(), (unsigned char *)buf.m.userptr,
 				       std::min(buf.bytesused, (unsigned)m_capImage->numBytes()));
 		}
@@ -458,8 +517,13 @@ void ApplicationWindow::capFrame()
 		m_tv = tv;
 	}
 	status = QString("Frame: %1 Fps: %2").arg(++m_frame).arg(m_fps);
-	if (m_showFrames)
-		m_capture->setImage(*m_capImage, status);
+	if (m_showFrames) {
+		if (ogl_use)
+			m_capture->setFrameOGL(m_capImage->width(), m_capImage->height(),
+					       m_capSrcFormat.fmt.pix.pixelformat, displaybuf, status);
+		else
+			m_capture->setFrameCPU(*m_capImage, status);
+	}
 	curStatus = statusBar()->currentMessage();
 	if (curStatus.isEmpty() || curStatus.startsWith("Frame: "))
 		statusBar()->showMessage(status);
@@ -589,6 +653,7 @@ void ApplicationWindow::stopCapture()
 	v4l2_encoder_cmd cmd;
 	unsigned i;
 
+	m_capture->stop();
 	m_snapshotAct->setDisabled(true);
 	switch (m_capMethod) {
 	case methodRead:
@@ -638,7 +703,7 @@ void ApplicationWindow::closeCaptureWin()
 	m_capStartAct->setChecked(false);
 }
 
-void ApplicationWindow::capStart(bool start)
+bool ApplicationWindow::isSupportedFormatCPU(v4l2_pix_format &format, QImage::Format &dstFmt)
 {
 	static const struct {
 		__u32 v4l2_pixfmt;
@@ -658,8 +723,46 @@ void ApplicationWindow::capStart(bool start)
 #endif
 		{ 0, QImage::Format_Invalid }
 	};
+
+	for (int i = 0; supported_fmts[i].v4l2_pixfmt; i++) {
+		if (supported_fmts[i].v4l2_pixfmt == format.pixelformat) {
+			dstFmt = supported_fmts[i].qt_pixfmt;
+			return true;
+		}
+	}
+	return false;
+}
+
+bool ApplicationWindow::isSupportedFormatOGL(v4l2_pix_format &format)
+{
+	__u32 supported_fmts[] = {
+		V4L2_PIX_FMT_RGB32,
+		V4L2_PIX_FMT_BGR32,
+		V4L2_PIX_FMT_RGB24,
+		V4L2_PIX_FMT_BGR24,
+		V4L2_PIX_FMT_RGB565,
+		V4L2_PIX_FMT_RGB555,
+		V4L2_PIX_FMT_YUYV,
+		V4L2_PIX_FMT_YVYU,
+		V4L2_PIX_FMT_UYVY,
+		V4L2_PIX_FMT_VYUY,
+		V4L2_PIX_FMT_YVU420,
+		V4L2_PIX_FMT_YUV420,
+		0
+	};
+
+	for (int i = 0; supported_fmts[i]; i++)
+		if (supported_fmts[i] == format.pixelformat)
+			return true;
+
+	return false;
+}
+
+void ApplicationWindow::capStart(bool start)
+{
 	QImage::Format dstFmt = QImage::Format_RGB888;
 	struct v4l2_fract interval;
+	bool ogl_use = m_capture->isEnableOGL();
 	v4l2_pix_format &srcPix = m_capSrcFormat.fmt.pix;
 	v4l2_pix_format &dstPix = m_capDestFormat.fmt.pix;
 
@@ -722,7 +825,11 @@ void ApplicationWindow::capStart(bool start)
 			m_capture->setMinimumSize(m_vbiWidth, m_vbiHeight);
 			m_capImage = new QImage(m_vbiWidth, m_vbiHeight, dstFmt);
 			m_capImage->fill(0);
-			m_capture->setImage(*m_capImage, "No frame");
+			if (ogl_use)
+				m_capture->setFrameOGL(m_capImage->width(), m_capImage->height(),
+						       m_capSrcFormat.fmt.pix.pixelformat, m_capImage->bits(), "No frame");
+			else
+				m_capture->setFrameCPU(*m_capImage, "No frame");
 			m_capture->show();
 		}
 		statusBar()->showMessage("No frame");
@@ -744,14 +851,16 @@ void ApplicationWindow::capStart(bool start)
 		m_capDestFormat = m_capSrcFormat;
 		dstPix.pixelformat = V4L2_PIX_FMT_RGB24;
 
-		for (int i = 0; supported_fmts[i].v4l2_pixfmt; i++) {
-			if (supported_fmts[i].v4l2_pixfmt == srcPix.pixelformat) {
-				dstPix.pixelformat = supported_fmts[i].v4l2_pixfmt;
-				dstFmt = supported_fmts[i].qt_pixfmt;
-				m_mustConvert = false;
-				break;
-			}
+		if (!ogl_use && isSupportedFormatCPU(srcPix, dstFmt)) {
+			dstPix.pixelformat = srcPix.pixelformat;
+			m_mustConvert = false;
+		}
+
+		if (ogl_use && isSupportedFormatOGL(srcPix)) {
+			dstPix.pixelformat = srcPix.pixelformat;
+			m_mustConvert = false;
 		}
+
 		if (m_mustConvert) {
 			v4l2_format copy = m_capSrcFormat;
 
@@ -765,7 +874,11 @@ void ApplicationWindow::capStart(bool start)
 		m_capture->setMinimumSize(dstPix.width, dstPix.height);
 		m_capImage = new QImage(dstPix.width, dstPix.height, dstFmt);
 		m_capImage->fill(0);
-		m_capture->setImage(*m_capImage, "No frame");
+		if (ogl_use)
+			m_capture->setFrameOGL(m_capImage->width(), m_capImage->height(),
+					       m_capSrcFormat.fmt.pix.pixelformat, m_capImage->bits(), "No frame");
+		else
+			m_capture->setFrameCPU(*m_capImage, "No frame");
 		m_capture->show();
 	}
 
@@ -890,7 +1003,7 @@ void ApplicationWindow::saveRaw(bool checked)
 void ApplicationWindow::about()
 {
 	QMessageBox::about(this, "V4L2 Test Bench",
-			"This program allows easy experimenting with video4linux devices.");
+			   "This program allows easy experimenting with video4linux devices.");
 }
 
 void ApplicationWindow::error(const QString &error)
diff --git a/utils/qv4l2/qv4l2.h b/utils/qv4l2/qv4l2.h
index 8634948..27cb72d 100644
--- a/utils/qv4l2/qv4l2.h
+++ b/utils/qv4l2/qv4l2.h
@@ -91,6 +91,9 @@ private:
 	void stopCapture();
 	void startOutput(unsigned buffer_size);
 	void stopOutput();
+	bool isSupportedFormatCPU(v4l2_pix_format &format, QImage::Format &dstFmt);
+	bool isSupportedFormatOGL(v4l2_pix_format &format);
+
 	struct buffer *m_buffers;
 	struct v4l2_format m_capSrcFormat;
 	struct v4l2_format m_capDestFormat;
@@ -108,6 +111,7 @@ private slots:
 	void snapshot();
 	void capVbiFrame();
 	void saveRaw(bool);
+	void setPreviewMode();
 
 	// gui
 private slots:
@@ -186,6 +190,10 @@ private:
 	unsigned m_fps;
 	struct timeval m_tv;
 	QFile m_saveRaw;
+#ifdef ENABLE_OGL
+	QAction *m_previewAct;
+#endif
+
 };
 
 extern ApplicationWindow *g_mw;
-- 
1.8.3.2

--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux