linux-media.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCHv2 FINAL 0/6] qv4l2: add OpenGL rendering and window fixes
@ 2013-07-30  8:15 Bård Eirik Winther
  2013-07-30  8:15 ` [PATCHv2 FINAL 1/6] qv4l2: move function ctrlEvent Bård Eirik Winther
  2013-07-30 13:12 ` [PATCHv2 FINAL 0/6] qv4l2: add OpenGL rendering and window fixes Mauro Carvalho Chehab
  0 siblings, 2 replies; 12+ messages in thread
From: Bård Eirik Winther @ 2013-07-30  8:15 UTC (permalink / raw)
  To: linux-media

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 perform color space conversion.

The OpenGL implementation requires OpenGL and QtOpenGL libraries as well as an OpenGL driver
(typically from the graphics driver). If OpenGL support is not present,
then the program will fall back to using the CPU to display.

Changelog v2 FINAL:
- Restructured the patch series
- Preview menu is now Capture menu which contains both capture controls and audio/video settings for capture.
- Corrected the license description for CaptureWinGL
- Add doxygen documentation to capture-win.h

Changelog v2:
- Cleaned up the capture win code and classes
- CaptureWin is now a base class that can be used to implement different ways of displaying video.
- setMinimumSize is now more reliable
- Small code tweaks and improvements, including cleaner display flow
- Changed the OpenGL abbreviation from OGL to GL

Some of the changes/improvements:
- Moved the ctrlEvent() function in qv4l2.cpp to be grouped with GUI function
  and to group capFrame() and capVbiFrame() together.
- OpenGL acceleration for supported systems.
- Option to change between GPU or CPU based rendering.
- CaptureWin's setMinimumSize() sets the minimum size for the video frame viewport
  and not for the window itself. If the minimum size is larger than the monitor resolution,
  it will reduce the minimum size to match this.
- Added a new menu option 'Preview' for controlling the CaptureWin's behavior.
- Added a couple of hotkeys:
    CTRL + V : When main window is selected start capture.
               This gives an option other than the button to start recording,
               as this is a frequent operation when using the utility.
    CTRL + W : When CaptureWin is selected close capture window.
               It makes it easier to deal with high resolutions video on
               small screen, especially when the window close button may
               be outside the monitor when repositioning the window.

Known issues:
- Repositioning, scaling or switching windows while the capture is recording will reduce the framerate.
  This is a limitation of Qt and not OpenGL.
- Using 4 streams of RGB3 1080p60 can at random times cease to render correctly
  and reduce the framerate to half. A restart solves this though.
- OpenGL is limited to 60fps. Disabling V-sync might allow for faster framerates.
- Resizing or scaling is not supported, mainly because the YUY2 shader renders the image incorrectly
  when the canvas size is not equal to the frame size.
- Some of the supported OpenGL formats may use the CPU for colorspace conversion, but this is driver dependant.  

Supported formats for OpenGL render:
- Native supported and accelerated:
    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

- All formats supported by V4L conversion library,
  but they will use the CPU to convert to RGB3 before being displayed with OpenGL.

Performance:
All tests are done on an Intel i7-2600S (with Turbo Boost disabled) using the
integrated Intel HD 2000 graphics processor. The mothreboard is an ASUS P8H77-I
with 2x2GB CL 9-9-9-24 DDR3 RAM. The capture card is a Cisco test card with 4 HDMI
inputs connected using PCIe2.0x8. All video input streams used for testing are
progressive HD (1920x1080) with 60fps.

FPS for every input for a given number of streams:
      1 STREAM  2 STREAMS  3 STREAMS  4 STREAMS
RGB3      60        60         60         60
BGR3      60        60         60         50
YUYV      60        60         60         48
YU12      60        60         60         52
YV12      60        60         60         52


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCHv2 FINAL 1/6] qv4l2: move function ctrlEvent
  2013-07-30  8:15 [PATCHv2 FINAL 0/6] qv4l2: add OpenGL rendering and window fixes Bård Eirik Winther
@ 2013-07-30  8:15 ` Bård Eirik Winther
  2013-07-30  8:15   ` [PATCHv2 FINAL 2/6] qv4l2: add hotkeys for common operations Bård Eirik Winther
                     ` (4 more replies)
  2013-07-30 13:12 ` [PATCHv2 FINAL 0/6] qv4l2: add OpenGL rendering and window fixes Mauro Carvalho Chehab
  1 sibling, 5 replies; 12+ messages in thread
From: Bård Eirik Winther @ 2013-07-30  8:15 UTC (permalink / raw)
  To: linux-media

Moved the ctrlEvent() function in qv4l2.cpp to be grouped with GUI function
and to group capFrame() and capVbiFrame() together.

Signed-off-by: Bård Eirik Winther <bwinther@cisco.com>
---
 utils/qv4l2/qv4l2.cpp | 94 +++++++++++++++++++++++++--------------------------
 1 file changed, 47 insertions(+), 47 deletions(-)

diff --git a/utils/qv4l2/qv4l2.cpp b/utils/qv4l2/qv4l2.cpp
index de9b154..a8fcc65 100644
--- a/utils/qv4l2/qv4l2.cpp
+++ b/utils/qv4l2/qv4l2.cpp
@@ -202,6 +202,53 @@ void ApplicationWindow::openrawdev()
 		setDevice(d.selectedFiles().first(), true);
 }
 
+void ApplicationWindow::ctrlEvent()
+{
+	v4l2_event ev;
+
+	while (dqevent(ev)) {
+		if (ev.type != V4L2_EVENT_CTRL)
+			continue;
+		m_ctrlMap[ev.id].flags = ev.u.ctrl.flags;
+		m_ctrlMap[ev.id].minimum = ev.u.ctrl.minimum;
+		m_ctrlMap[ev.id].maximum = ev.u.ctrl.maximum;
+		m_ctrlMap[ev.id].step = ev.u.ctrl.step;
+		m_ctrlMap[ev.id].default_value = ev.u.ctrl.default_value;
+		m_widgetMap[ev.id]->setDisabled(m_ctrlMap[ev.id].flags & CTRL_FLAG_DISABLED);
+		switch (m_ctrlMap[ev.id].type) {
+		case V4L2_CTRL_TYPE_INTEGER:
+		case V4L2_CTRL_TYPE_INTEGER_MENU:
+		case V4L2_CTRL_TYPE_MENU:
+		case V4L2_CTRL_TYPE_BOOLEAN:
+		case V4L2_CTRL_TYPE_BITMASK:
+			setVal(ev.id, ev.u.ctrl.value);
+			break;
+		case V4L2_CTRL_TYPE_INTEGER64:
+			setVal64(ev.id, ev.u.ctrl.value64);
+			break;
+		default:
+			break;
+		}
+		if (m_ctrlMap[ev.id].type != V4L2_CTRL_TYPE_STRING)
+			continue;
+		queryctrl(m_ctrlMap[ev.id]);
+
+		struct v4l2_ext_control c;
+		struct v4l2_ext_controls ctrls;
+
+		c.id = ev.id;
+		c.size = m_ctrlMap[ev.id].maximum + 1;
+		c.string = (char *)malloc(c.size);
+		memset(&ctrls, 0, sizeof(ctrls));
+		ctrls.count = 1;
+		ctrls.ctrl_class = 0;
+		ctrls.controls = &c;
+		if (!ioctl(VIDIOC_G_EXT_CTRLS, &ctrls))
+			setString(ev.id, c.string);
+		free(c.string);
+	}
+}
+
 void ApplicationWindow::capVbiFrame()
 {
 	__u32 buftype = m_genTab->bufType();
@@ -305,53 +352,6 @@ void ApplicationWindow::capVbiFrame()
 		refresh();
 }
 
-void ApplicationWindow::ctrlEvent()
-{
-	v4l2_event ev;
-
-	while (dqevent(ev)) {
-		if (ev.type != V4L2_EVENT_CTRL)
-			continue;
-		m_ctrlMap[ev.id].flags = ev.u.ctrl.flags;
-		m_ctrlMap[ev.id].minimum = ev.u.ctrl.minimum;
-		m_ctrlMap[ev.id].maximum = ev.u.ctrl.maximum;
-		m_ctrlMap[ev.id].step = ev.u.ctrl.step;
-		m_ctrlMap[ev.id].default_value = ev.u.ctrl.default_value;
-		m_widgetMap[ev.id]->setDisabled(m_ctrlMap[ev.id].flags & CTRL_FLAG_DISABLED);
-		switch (m_ctrlMap[ev.id].type) {
-		case V4L2_CTRL_TYPE_INTEGER:
-		case V4L2_CTRL_TYPE_INTEGER_MENU:
-		case V4L2_CTRL_TYPE_MENU:
-		case V4L2_CTRL_TYPE_BOOLEAN:
-		case V4L2_CTRL_TYPE_BITMASK:
-			setVal(ev.id, ev.u.ctrl.value);
-			break;
-		case V4L2_CTRL_TYPE_INTEGER64:
-			setVal64(ev.id, ev.u.ctrl.value64);
-			break;
-		default:
-			break;
-		}
-		if (m_ctrlMap[ev.id].type != V4L2_CTRL_TYPE_STRING)
-			continue;
-		queryctrl(m_ctrlMap[ev.id]);
-
-		struct v4l2_ext_control c;
-		struct v4l2_ext_controls ctrls;
-
-		c.id = ev.id;
-		c.size = m_ctrlMap[ev.id].maximum + 1;
-		c.string = (char *)malloc(c.size);
-		memset(&ctrls, 0, sizeof(ctrls));
-		ctrls.count = 1;
-		ctrls.ctrl_class = 0;
-		ctrls.controls = &c;
-		if (!ioctl(VIDIOC_G_EXT_CTRLS, &ctrls))
-			setString(ev.id, c.string);
-		free(c.string);
-	}
-}
-
 void ApplicationWindow::capFrame()
 {
 	__u32 buftype = m_genTab->bufType();
-- 
1.8.3.2


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCHv2 FINAL 2/6] qv4l2: add hotkeys for common operations
  2013-07-30  8:15 ` [PATCHv2 FINAL 1/6] qv4l2: move function ctrlEvent Bård Eirik Winther
@ 2013-07-30  8:15   ` Bård Eirik Winther
  2013-07-30 12:39     ` Mauro Carvalho Chehab
  2013-07-30  8:15   ` [PATCHv2 FINAL 3/6] qv4l2: fix minimum size in capture win to frame size Bård Eirik Winther
                     ` (3 subsequent siblings)
  4 siblings, 1 reply; 12+ messages in thread
From: Bård Eirik Winther @ 2013-07-30  8:15 UTC (permalink / raw)
  To: linux-media

CTRL + V : When main window is selected start capture.
           This gives an option other than the button to start recording,
           as this is a frequent operation when using the utility.
CTRL + W : When CaptureWin is selected close capture window
           It makes it easier to deal with high resolutions video on
           small screen, especially when the window close button may
           be outside the monitor when repositioning the window.

Signed-off-by: Bård Eirik Winther <bwinther@cisco.com>
---
 utils/qv4l2/capture-win.cpp | 8 ++++++++
 utils/qv4l2/capture-win.h   | 4 +++-
 utils/qv4l2/qv4l2.cpp       | 1 +
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/utils/qv4l2/capture-win.cpp b/utils/qv4l2/capture-win.cpp
index 6798252..a94c73d 100644
--- a/utils/qv4l2/capture-win.cpp
+++ b/utils/qv4l2/capture-win.cpp
@@ -35,6 +35,14 @@ CaptureWin::CaptureWin()
 
 	vbox->addWidget(m_label);
 	vbox->addWidget(m_msg);
+
+	hotkeyClose = new QShortcut(Qt::CTRL+Qt::Key_W, this);
+	QObject::connect(hotkeyClose, SIGNAL(activated()), this, SLOT(close()));
+}
+
+CaptureWin::~CaptureWin()
+{
+	delete hotkeyClose;
 }
 
 void CaptureWin::setImage(const QImage &image, const QString &status)
diff --git a/utils/qv4l2/capture-win.h b/utils/qv4l2/capture-win.h
index e861b12..4115d56 100644
--- a/utils/qv4l2/capture-win.h
+++ b/utils/qv4l2/capture-win.h
@@ -21,6 +21,7 @@
 #define CAPTURE_WIN_H
 
 #include <QWidget>
+#include <QShortcut>
 #include <sys/time.h>
 
 class QImage;
@@ -32,7 +33,7 @@ class CaptureWin : public QWidget
 
 public:
 	CaptureWin();
-	virtual ~CaptureWin() {}
+	~CaptureWin();
 
 	void setImage(const QImage &image, const QString &status);
 
@@ -45,6 +46,7 @@ signals:
 private:
 	QLabel *m_label;
 	QLabel *m_msg;
+	QShortcut *hotkeyClose;
 };
 
 #endif
diff --git a/utils/qv4l2/qv4l2.cpp b/utils/qv4l2/qv4l2.cpp
index a8fcc65..bb1d84f 100644
--- a/utils/qv4l2/qv4l2.cpp
+++ b/utils/qv4l2/qv4l2.cpp
@@ -78,6 +78,7 @@ ApplicationWindow::ApplicationWindow() :
 	m_capStartAct->setStatusTip("Start capturing");
 	m_capStartAct->setCheckable(true);
 	m_capStartAct->setDisabled(true);
+	m_capStartAct->setShortcut(Qt::CTRL+Qt::Key_V);
 	connect(m_capStartAct, SIGNAL(toggled(bool)), this, SLOT(capStart(bool)));
 
 	m_snapshotAct = new QAction(QIcon(":/snapshot.png"), "&Make Snapshot", this);
-- 
1.8.3.2


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCHv2 FINAL 3/6] qv4l2: fix minimum size in capture win to frame size
  2013-07-30  8:15 ` [PATCHv2 FINAL 1/6] qv4l2: move function ctrlEvent Bård Eirik Winther
  2013-07-30  8:15   ` [PATCHv2 FINAL 2/6] qv4l2: add hotkeys for common operations Bård Eirik Winther
@ 2013-07-30  8:15   ` Bård Eirik Winther
  2013-07-30  8:15   ` [PATCHv2 FINAL 4/6] qv4l2: add Capture menu Bård Eirik Winther
                     ` (2 subsequent siblings)
  4 siblings, 0 replies; 12+ messages in thread
From: Bård Eirik Winther @ 2013-07-30  8:15 UTC (permalink / raw)
  To: linux-media

CaptureWin's setMinimumSize() sets the minimum size for the video frame viewport
and not for the window itself. If the minimum size is larger than the monitor resolution,
it will reduce the minimum size to match this.

Signed-off-by: Bård Eirik Winther <bwinther@cisco.com>
---
 utils/qv4l2/capture-win.cpp | 29 +++++++++++++++++++++++++++++
 utils/qv4l2/capture-win.h   |  1 +
 2 files changed, 30 insertions(+)

diff --git a/utils/qv4l2/capture-win.cpp b/utils/qv4l2/capture-win.cpp
index a94c73d..68dc9ed 100644
--- a/utils/qv4l2/capture-win.cpp
+++ b/utils/qv4l2/capture-win.cpp
@@ -21,6 +21,8 @@
 #include <QImage>
 #include <QVBoxLayout>
 #include <QCloseEvent>
+#include <QApplication>
+#include <QDesktopWidget>
 
 #include "qv4l2.h"
 #include "capture-win.h"
@@ -45,6 +47,33 @@ CaptureWin::~CaptureWin()
 	delete hotkeyClose;
 }
 
+void CaptureWin::setMinimumSize(int minw, int minh)
+{
+	QDesktopWidget *screen = QApplication::desktop();
+	QRect resolution = screen->screenGeometry();
+	QSize maxSize = maximumSize();
+
+	int l, t, r, b;
+	layout()->getContentsMargins(&l, &t, &r, &b);
+	minw += l + r;
+	minh += t + b + m_msg->minimumSizeHint().height() + layout()->spacing();
+
+	if (minw > resolution.width())
+		minw = resolution.width();
+	if (minw < 150)
+		minw = 150;
+
+	if (minh > resolution.height())
+		minh = resolution.height();
+	if (minh < 100)
+		minh = 100;
+
+	QWidget::setMinimumSize(minw, minh);
+	QWidget::setMaximumSize(minw, minh);
+	updateGeometry();
+	QWidget::setMaximumSize(maxSize.width(), maxSize.height());
+}
+
 void CaptureWin::setImage(const QImage &image, const QString &status)
 {
 	m_label->setPixmap(QPixmap::fromImage(image));
diff --git a/utils/qv4l2/capture-win.h b/utils/qv4l2/capture-win.h
index 4115d56..3925757 100644
--- a/utils/qv4l2/capture-win.h
+++ b/utils/qv4l2/capture-win.h
@@ -35,6 +35,7 @@ public:
 	CaptureWin();
 	~CaptureWin();
 
+	void setMinimumSize(int minw, int minh);
 	void setImage(const QImage &image, const QString &status);
 
 protected:
-- 
1.8.3.2


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCHv2 FINAL 4/6] qv4l2: add Capture menu
  2013-07-30  8:15 ` [PATCHv2 FINAL 1/6] qv4l2: move function ctrlEvent Bård Eirik Winther
  2013-07-30  8:15   ` [PATCHv2 FINAL 2/6] qv4l2: add hotkeys for common operations Bård Eirik Winther
  2013-07-30  8:15   ` [PATCHv2 FINAL 3/6] qv4l2: fix minimum size in capture win to frame size Bård Eirik Winther
@ 2013-07-30  8:15   ` Bård Eirik Winther
  2013-07-30  8:15   ` [PATCHv2 FINAL 5/6] qv4l2: new modular capture window design Bård Eirik Winther
  2013-07-30  8:15   ` [PATCHv2 FINAL 6/6] qv4l2: add OpenGL rendering Bård Eirik Winther
  4 siblings, 0 replies; 12+ messages in thread
From: Bård Eirik Winther @ 2013-07-30  8:15 UTC (permalink / raw)
  To: linux-media

Created a new Capture menu that contains both capture controls and audio/video settings for capture.

Signed-off-by: Bård Eirik Winther <bwinther@cisco.com>
---
 utils/qv4l2/qv4l2.cpp | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/utils/qv4l2/qv4l2.cpp b/utils/qv4l2/qv4l2.cpp
index bb1d84f..80937db 100644
--- a/utils/qv4l2/qv4l2.cpp
+++ b/utils/qv4l2/qv4l2.cpp
@@ -111,10 +111,8 @@ ApplicationWindow::ApplicationWindow() :
 	fileMenu->addAction(openAct);
 	fileMenu->addAction(openRawAct);
 	fileMenu->addAction(closeAct);
-	fileMenu->addAction(m_capStartAct);
 	fileMenu->addAction(m_snapshotAct);
 	fileMenu->addAction(m_saveRawAct);
-	fileMenu->addAction(m_showFramesAct);
 	fileMenu->addSeparator();
 	fileMenu->addAction(quitAct);
 
@@ -128,6 +126,10 @@ ApplicationWindow::ApplicationWindow() :
 	toolBar->addSeparator();
 	toolBar->addAction(quitAct);
 
+	QMenu *captureMenu = menuBar()->addMenu("&Capture");
+	captureMenu->addAction(m_capStartAct);
+	captureMenu->addAction(m_showFramesAct);
+
 	QMenu *helpMenu = menuBar()->addMenu("&Help");
 	helpMenu->addAction("&About", this, SLOT(about()), Qt::Key_F1);
 
-- 
1.8.3.2


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCHv2 FINAL 5/6] qv4l2: new modular capture window design
  2013-07-30  8:15 ` [PATCHv2 FINAL 1/6] qv4l2: move function ctrlEvent Bård Eirik Winther
                     ` (2 preceding siblings ...)
  2013-07-30  8:15   ` [PATCHv2 FINAL 4/6] qv4l2: add Capture menu Bård Eirik Winther
@ 2013-07-30  8:15   ` Bård Eirik Winther
  2013-07-30  8:15   ` [PATCHv2 FINAL 6/6] qv4l2: add OpenGL rendering Bård Eirik Winther
  4 siblings, 0 replies; 12+ messages in thread
From: Bård Eirik Winther @ 2013-07-30  8:15 UTC (permalink / raw)
  To: linux-media

The display of video has been divided into classes to
easier implement other ways to render frames on screen.

Signed-off-by: Bård Eirik Winther <bwinther@cisco.com>
---
 utils/qv4l2/Makefile.am        |  4 +-
 utils/qv4l2/capture-win-qt.cpp | 89 +++++++++++++++++++++++++++++++++++++++
 utils/qv4l2/capture-win-qt.h   | 47 +++++++++++++++++++++
 utils/qv4l2/capture-win.cpp    | 45 ++++++++++----------
 utils/qv4l2/capture-win.h      | 24 ++++++-----
 utils/qv4l2/qv4l2.cpp          | 94 +++++++++++++++++++-----------------------
 utils/qv4l2/qv4l2.h            |  1 +
 7 files changed, 219 insertions(+), 85 deletions(-)
 create mode 100644 utils/qv4l2/capture-win-qt.cpp
 create mode 100644 utils/qv4l2/capture-win-qt.h

diff --git a/utils/qv4l2/Makefile.am b/utils/qv4l2/Makefile.am
index 1f5a49f..9ef8149 100644
--- a/utils/qv4l2/Makefile.am
+++ b/utils/qv4l2/Makefile.am
@@ -1,6 +1,7 @@
 bin_PROGRAMS = qv4l2
 
 qv4l2_SOURCES = qv4l2.cpp general-tab.cpp ctrl-tab.cpp vbi-tab.cpp v4l2-api.cpp capture-win.cpp \
+  capture-win-qt.cpp capture-win-qt.h \
   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
@@ -37,5 +38,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-qt.cpp b/utils/qv4l2/capture-win-qt.cpp
new file mode 100644
index 0000000..63c77d5
--- /dev/null
+++ b/utils/qv4l2/capture-win-qt.cpp
@@ -0,0 +1,89 @@
+/* qv4l2: a control panel controlling v4l2 devices.
+ *
+ * Copyright (C) 2006 Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "capture-win-qt.h"
+
+CaptureWinQt::CaptureWinQt() :
+	m_frame(new QImage(0, 0, QImage::Format_Invalid))
+{
+
+	CaptureWin::buildWindow(&m_videoSurface);
+}
+
+CaptureWinQt::~CaptureWinQt()
+{
+	delete m_frame;
+}
+
+void CaptureWinQt::setFrame(int width, int height, __u32 format, unsigned char *data, const QString &info)
+{
+	QImage::Format dstFmt;
+	bool supported = findNativeFormat(format, dstFmt);
+	if (!supported)
+		dstFmt = QImage::Format_RGB888;
+
+	if (m_frame->width() != width || m_frame->height() != height || m_frame->format() != dstFmt) {
+		delete m_frame;
+		m_frame = new QImage(width, height, dstFmt);
+	}
+
+	if (data == NULL || !supported)
+		m_frame->fill(0);
+	else
+		memcpy(m_frame->bits(), data, m_frame->numBytes());
+
+	m_information.setText(info);
+	m_videoSurface.setPixmap(QPixmap::fromImage(*m_frame));
+}
+
+bool CaptureWinQt::hasNativeFormat(__u32 format)
+{
+	QImage::Format fmt;
+	return findNativeFormat(format, fmt);
+}
+
+bool CaptureWinQt::findNativeFormat(__u32 format, QImage::Format &dstFmt)
+{
+	static const struct {
+		__u32 v4l2_pixfmt;
+		QImage::Format qt_pixfmt;
+	} supported_fmts[] = {
+#if Q_BYTE_ORDER == Q_BIG_ENDIAN
+		{ V4L2_PIX_FMT_RGB32, QImage::Format_RGB32 },
+		{ V4L2_PIX_FMT_RGB24, QImage::Format_RGB888 },
+		{ V4L2_PIX_FMT_RGB565X, QImage::Format_RGB16 },
+		{ V4L2_PIX_FMT_RGB555X, QImage::Format_RGB555 },
+#else
+		{ V4L2_PIX_FMT_BGR32, QImage::Format_RGB32 },
+		{ V4L2_PIX_FMT_RGB24, QImage::Format_RGB888 },
+		{ V4L2_PIX_FMT_RGB565, QImage::Format_RGB16 },
+		{ V4L2_PIX_FMT_RGB555, QImage::Format_RGB555 },
+		{ V4L2_PIX_FMT_RGB444, QImage::Format_RGB444 },
+#endif
+		{ 0, QImage::Format_Invalid }
+	};
+
+	for (int i = 0; supported_fmts[i].v4l2_pixfmt; i++) {
+		if (supported_fmts[i].v4l2_pixfmt == format) {
+			dstFmt = supported_fmts[i].qt_pixfmt;
+			return true;
+		}
+	}
+	return false;
+}
diff --git a/utils/qv4l2/capture-win-qt.h b/utils/qv4l2/capture-win-qt.h
new file mode 100644
index 0000000..d3b4fe8
--- /dev/null
+++ b/utils/qv4l2/capture-win-qt.h
@@ -0,0 +1,47 @@
+/* qv4l2: a control panel controlling v4l2 devices.
+ *
+ * Copyright (C) 2006 Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef CAPTURE_WIN_QT_H
+#define CAPTURE_WIN_QT_H
+
+#include "qv4l2.h"
+#include "capture-win.h"
+
+#include <QLabel>
+#include <QImage>
+
+class CaptureWinQt : public CaptureWin
+{
+public:
+	CaptureWinQt();
+	~CaptureWinQt();
+
+	void setFrame(int width, int height, __u32 format,
+		      unsigned char *data, const QString &info);
+
+	bool hasNativeFormat(__u32 format);
+	static bool isSupported() { return true; }
+
+private:
+	bool findNativeFormat(__u32 format, QImage::Format &dstFmt);
+
+	QImage *m_frame;
+	QLabel m_videoSurface;
+};
+#endif
diff --git a/utils/qv4l2/capture-win.cpp b/utils/qv4l2/capture-win.cpp
index 68dc9ed..2d57909 100644
--- a/utils/qv4l2/capture-win.cpp
+++ b/utils/qv4l2/capture-win.cpp
@@ -16,35 +16,42 @@
  * 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 "capture-win.h"
+
+#include <QCloseEvent>
 #include <QLabel>
 #include <QImage>
 #include <QVBoxLayout>
-#include <QCloseEvent>
 #include <QApplication>
 #include <QDesktopWidget>
 
-#include "qv4l2.h"
-#include "capture-win.h"
-
 CaptureWin::CaptureWin()
 {
-	QVBoxLayout *vbox = new QVBoxLayout(this);
-
 	setWindowTitle("V4L2 Capture");
-	m_label = new QLabel();
-	m_msg = new QLabel("No frame");
+	m_hotkeyClose = new QShortcut(Qt::CTRL+Qt::Key_W, this);
+	QObject::connect(m_hotkeyClose, SIGNAL(activated()), this, SLOT(close()));
+}
 
-	vbox->addWidget(m_label);
-	vbox->addWidget(m_msg);
+CaptureWin::~CaptureWin()
+{
+	if (layout() == NULL)
+		return;
 
-	hotkeyClose = new QShortcut(Qt::CTRL+Qt::Key_W, this);
-	QObject::connect(hotkeyClose, SIGNAL(activated()), this, SLOT(close()));
+	layout()->removeWidget(this);
+	delete layout();
+	delete m_hotkeyClose;
 }
 
-CaptureWin::~CaptureWin()
+void CaptureWin::buildWindow(QWidget *videoSurface)
 {
-	delete hotkeyClose;
+	int l, t, r, b;
+	QVBoxLayout *vbox = new QVBoxLayout(this);
+	m_information.setText("No Frame");
+	vbox->addWidget(videoSurface, 2000);
+	vbox->addWidget(&m_information, 1, Qt::AlignBottom);
+	vbox->getContentsMargins(&l, &t, &r, &b);
+	vbox->setSpacing(b);
 }
 
 void CaptureWin::setMinimumSize(int minw, int minh)
@@ -56,7 +63,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();
@@ -74,12 +81,6 @@ void CaptureWin::setMinimumSize(int minw, int minh)
 	QWidget::setMaximumSize(maxSize.width(), maxSize.height());
 }
 
-void CaptureWin::setImage(const QImage &image, const QString &status)
-{
-	m_label->setPixmap(QPixmap::fromImage(image));
-	m_msg->setText(status);
-}
-
 void CaptureWin::closeEvent(QCloseEvent *event)
 {
 	QWidget::closeEvent(event);
diff --git a/utils/qv4l2/capture-win.h b/utils/qv4l2/capture-win.h
index 3925757..c3b7d98 100644
--- a/utils/qv4l2/capture-win.h
+++ b/utils/qv4l2/capture-win.h
@@ -20,12 +20,11 @@
 #ifndef CAPTURE_WIN_H
 #define CAPTURE_WIN_H
 
+#include "qv4l2.h"
+
 #include <QWidget>
 #include <QShortcut>
-#include <sys/time.h>
-
-class QImage;
-class QLabel;
+#include <QLabel>
 
 class CaptureWin : public QWidget
 {
@@ -36,18 +35,23 @@ public:
 	~CaptureWin();
 
 	void setMinimumSize(int minw, int minh);
-	void setImage(const QImage &image, const QString &status);
+	virtual void setFrame(int width, int height, __u32 format,
+			      unsigned char *data, const QString &info) = 0;
+
+	virtual bool hasNativeFormat(__u32 format) = 0;
+	static bool isSupported() { return false; }
 
 protected:
-	virtual void closeEvent(QCloseEvent *event);
+	void closeEvent(QCloseEvent *event);
+	void buildWindow(QWidget *videoSurface);
+
+	QLabel m_information;
 
 signals:
 	void close();
 
 private:
-	QLabel *m_label;
-	QLabel *m_msg;
-	QShortcut *hotkeyClose;
-};
+	QShortcut *m_hotkeyClose;
 
+};
 #endif
diff --git a/utils/qv4l2/qv4l2.cpp b/utils/qv4l2/qv4l2.cpp
index 80937db..0c9b74c 100644
--- a/utils/qv4l2/qv4l2.cpp
+++ b/utils/qv4l2/qv4l2.cpp
@@ -21,6 +21,7 @@
 #include "general-tab.h"
 #include "vbi-tab.h"
 #include "capture-win.h"
+#include "capture-win-qt.h"
 
 #include <QToolBar>
 #include <QToolButton>
@@ -160,7 +161,7 @@ void ApplicationWindow::setDevice(const QString &device, bool rawOpen)
 	if (!open(device, !rawOpen))
 		return;
 
-	m_capture = new CaptureWin;
+	m_capture = new CaptureWinQt;
 	m_capture->setMinimumSize(150, 50);
 	connect(m_capture, SIGNAL(close()), this, SLOT(closeCaptureWin()));
 
@@ -347,7 +348,9 @@ void ApplicationWindow::capVbiFrame()
 	}
 	status = QString("Frame: %1 Fps: %2").arg(++m_frame).arg(m_fps);
 	if (m_showFrames)
-		m_capture->setImage(*m_capImage, status);
+		m_capture->setFrame(m_capImage->width(), m_capImage->height(),
+				    m_capDestFormat.fmt.pix.pixelformat, m_capImage->bits(), status);
+
 	curStatus = statusBar()->currentMessage();
 	if (curStatus.isEmpty() || curStatus.startsWith("Frame: "))
 		statusBar()->showMessage(status);
@@ -363,6 +366,8 @@ void ApplicationWindow::capFrame()
 	int err = 0;
 	bool again;
 
+	unsigned char *displaybuf = NULL;
+
 	switch (m_capMethod) {
 	case methodRead:
 		s = read(m_frameData, m_capSrcFormat.fmt.pix.sizeimage);
@@ -382,10 +387,12 @@ 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)
-			memcpy(m_capImage->bits(), m_frameData, std::min(s, m_capImage->numBytes()));
+						 m_frameData, s,
+						 m_capImage->bits(), m_capDestFormat.fmt.pix.sizeimage);
+		if (m_mustConvert && err != -1)
+			displaybuf = m_capImage->bits();
+		if (!m_mustConvert)
+			displaybuf = m_frameData;
 		break;
 
 	case methodMmap:
@@ -399,21 +406,19 @@ 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)
-				memcpy(m_capImage->bits(),
-				       (unsigned char *)m_buffers[buf.index].start,
-				       std::min(buf.bytesused, (unsigned)m_capImage->numBytes()));
+				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 != -1)
+				displaybuf = m_capImage->bits();
+			if (!m_mustConvert)
+				displaybuf = (unsigned char *)m_buffers[buf.index].start;
 		}
 		if (m_makeSnapshot)
 			makeSnapshot((unsigned char *)m_buffers[buf.index].start, buf.bytesused);
 		if (m_saveRaw.openMode())
 			m_saveRaw.write((const char *)m_buffers[buf.index].start, buf.bytesused);
 
-		qbuf(buf);
 		break;
 
 	case methodUser:
@@ -427,20 +432,19 @@ 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)
-				memcpy(m_capImage->bits(), (unsigned char *)buf.m.userptr,
-				       std::min(buf.bytesused, (unsigned)m_capImage->numBytes()));
+				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 != -1)
+				displaybuf = m_capImage->bits();
+			if (!m_mustConvert)
+				displaybuf = (unsigned char *)buf.m.userptr;
 		}
 		if (m_makeSnapshot)
 			makeSnapshot((unsigned char *)buf.m.userptr, buf.bytesused);
 		if (m_saveRaw.openMode())
 			m_saveRaw.write((const char *)buf.m.userptr, buf.bytesused);
 
-		qbuf(buf);
 		break;
 	}
 	if (err == -1 && m_frame == 0)
@@ -460,8 +464,15 @@ void ApplicationWindow::capFrame()
 		m_tv = tv;
 	}
 	status = QString("Frame: %1 Fps: %2").arg(++m_frame).arg(m_fps);
+	if (displaybuf == NULL && m_showFrames)
+		status.append(" Error: Unsupported format.");
 	if (m_showFrames)
-		m_capture->setImage(*m_capImage, status);
+		m_capture->setFrame(m_capImage->width(), m_capImage->height(),
+				    m_capDestFormat.fmt.pix.pixelformat, displaybuf, status);
+
+	if (m_capMethod == methodMmap || m_capMethod == methodUser)
+		qbuf(buf);
+
 	curStatus = statusBar()->currentMessage();
 	if (curStatus.isEmpty() || curStatus.startsWith("Frame: "))
 		statusBar()->showMessage(status);
@@ -642,24 +653,6 @@ void ApplicationWindow::closeCaptureWin()
 
 void ApplicationWindow::capStart(bool start)
 {
-	static const struct {
-		__u32 v4l2_pixfmt;
-		QImage::Format qt_pixfmt;
-	} supported_fmts[] = {
-#if Q_BYTE_ORDER == Q_BIG_ENDIAN
-		{ V4L2_PIX_FMT_RGB32, QImage::Format_RGB32 },
-		{ V4L2_PIX_FMT_RGB24, QImage::Format_RGB888 },
-		{ V4L2_PIX_FMT_RGB565X, QImage::Format_RGB16 },
-		{ V4L2_PIX_FMT_RGB555X, QImage::Format_RGB555 },
-#else
-		{ V4L2_PIX_FMT_BGR32, QImage::Format_RGB32 },
-		{ V4L2_PIX_FMT_RGB24, QImage::Format_RGB888 },
-		{ V4L2_PIX_FMT_RGB565, QImage::Format_RGB16 },
-		{ V4L2_PIX_FMT_RGB555, QImage::Format_RGB555 },
-		{ V4L2_PIX_FMT_RGB444, QImage::Format_RGB444 },
-#endif
-		{ 0, QImage::Format_Invalid }
-	};
 	QImage::Format dstFmt = QImage::Format_RGB888;
 	struct v4l2_fract interval;
 	v4l2_pix_format &srcPix = m_capSrcFormat.fmt.pix;
@@ -724,7 +717,8 @@ 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");
+			m_capture->setFrame(m_capImage->width(), m_capImage->height(),
+					    m_capDestFormat.fmt.pix.pixelformat, m_capImage->bits(), "No frame");
 			m_capture->show();
 		}
 		statusBar()->showMessage("No frame");
@@ -746,14 +740,11 @@ 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 (m_capture->hasNativeFormat(srcPix.pixelformat)) {
+			dstPix.pixelformat = srcPix.pixelformat;
+			m_mustConvert = false;
 		}
+
 		if (m_mustConvert) {
 			v4l2_format copy = m_capSrcFormat;
 
@@ -767,7 +758,8 @@ 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");
+		m_capture->setFrame(m_capImage->width(), m_capImage->height(),
+				    m_capDestFormat.fmt.pix.pixelformat, m_capImage->bits(), "No frame");
 		m_capture->show();
 	}
 
diff --git a/utils/qv4l2/qv4l2.h b/utils/qv4l2/qv4l2.h
index 8634948..ccfc2f9 100644
--- a/utils/qv4l2/qv4l2.h
+++ b/utils/qv4l2/qv4l2.h
@@ -33,6 +33,7 @@
 
 #include "v4l2-api.h"
 #include "raw2sliced.h"
+#include "capture-win.h"
 
 class QComboBox;
 class QSpinBox;
-- 
1.8.3.2


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCHv2 FINAL 6/6] qv4l2: add OpenGL rendering
  2013-07-30  8:15 ` [PATCHv2 FINAL 1/6] qv4l2: move function ctrlEvent Bård Eirik Winther
                     ` (3 preceding siblings ...)
  2013-07-30  8:15   ` [PATCHv2 FINAL 5/6] qv4l2: new modular capture window design Bård Eirik Winther
@ 2013-07-30  8:15   ` Bård Eirik Winther
  2013-08-01 11:05     ` Bård Eirik Winther
  4 siblings, 1 reply; 12+ messages in thread
From: Bård Eirik Winther @ 2013-07-30  8:15 UTC (permalink / raw)
  To: linux-media

Adds OpenGL-accelerated display of video.
This allows for using the graphics card to render
the video content to screen and to perform color space conversion.

Signed-off-by: Bård Eirik Winther <bwinther@cisco.com>
---
 configure.ac                   |   8 +-
 utils/qv4l2/Makefile.am        |   8 +-
 utils/qv4l2/capture-win-gl.cpp | 553 +++++++++++++++++++++++++++++++++++++++++
 utils/qv4l2/capture-win-gl.h   |  96 +++++++
 utils/qv4l2/capture-win-qt.h   |   1 +
 utils/qv4l2/capture-win.h      |  39 +++
 utils/qv4l2/qv4l2.cpp          |  54 +++-
 utils/qv4l2/qv4l2.h            |  10 +
 8 files changed, 765 insertions(+), 4 deletions(-)
 create mode 100644 utils/qv4l2/capture-win-gl.cpp
 create mode 100644 utils/qv4l2/capture-win-gl.h

diff --git a/configure.ac b/configure.ac
index e249546..d74da61 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(QTGL, [QtOpenGL >= 4.4 gl], [qt_pkgconfig_gl=true], [qt_pkgconfig_gl=false])
+if test "x$qt_pkgconfig_gl" = "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_GL], [test WITH_QV4L2 -a ${qt_pkgconfig_gl} = 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 9ef8149..22d4c17 100644
--- a/utils/qv4l2/Makefile.am
+++ b/utils/qv4l2/Makefile.am
@@ -1,12 +1,18 @@
 bin_PROGRAMS = qv4l2
 
 qv4l2_SOURCES = qv4l2.cpp general-tab.cpp ctrl-tab.cpp vbi-tab.cpp v4l2-api.cpp capture-win.cpp \
-  capture-win-qt.cpp capture-win-qt.h \
+  capture-win-qt.cpp capture-win-qt.h capture-win-gl.cpp capture-win-gl.h \
   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_GL
+qv4l2_CPPFLAGS = $(QTGL_CFLAGS) -DENABLE_GL
+qv4l2_LDFLAGS = $(QTGL_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 \
diff --git a/utils/qv4l2/capture-win-gl.cpp b/utils/qv4l2/capture-win-gl.cpp
new file mode 100644
index 0000000..807d9e9
--- /dev/null
+++ b/utils/qv4l2/capture-win-gl.cpp
@@ -0,0 +1,553 @@
+/*
+ * The YUY2 shader code was copied and simplified from face-responder. The code is under public domain:
+ * https://bitbucket.org/nateharward/face-responder/src/0c3b4b957039d9f4bf1da09b9471371942de2601/yuv42201_laplace.frag?at=master
+ *
+ * All other OpenGL code:
+ *
+ * Copyright 2013 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you may redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "capture-win-gl.h"
+
+#include <stdio.h>
+
+CaptureWinGL::CaptureWinGL()
+{
+	CaptureWin::buildWindow(&m_videoSurface);
+	CaptureWin::setWindowTitle("V4L2 Capture (OpenGL)");
+}
+
+CaptureWinGL::~CaptureWinGL()
+{
+}
+
+void CaptureWinGL::stop()
+{
+#ifdef ENABLE_GL
+	m_videoSurface.stop();
+#endif
+}
+
+void CaptureWinGL::setFrame(int width, int height, __u32 format, unsigned char *data, const QString &info)
+{
+#ifdef ENABLE_GL
+	m_videoSurface.setFrame(width, height, format, data);
+#endif
+	m_information.setText(info);
+}
+
+bool CaptureWinGL::hasNativeFormat(__u32 format)
+{
+#ifdef ENABLE_GL
+	return m_videoSurface.hasNativeFormat(format);
+#else
+	return false;
+#endif
+}
+
+bool CaptureWinGL::isSupported()
+{
+#ifdef ENABLE_GL
+	return true;
+#else
+	return false;
+#endif
+}
+
+#ifdef ENABLE_GL
+CaptureWinGLEngine::CaptureWinGLEngine() :
+	m_frameHeight(0),
+	m_frameWidth(0),
+	m_screenTextureCount(0),
+	m_formatChange(false),
+	m_frameFormat(0),
+	m_frameData(NULL)
+{
+	m_glfunction.initializeGLFunctions(context());
+}
+
+CaptureWinGLEngine::~CaptureWinGLEngine()
+{
+	clearShader();
+}
+
+void CaptureWinGLEngine::clearShader()
+{
+	glDeleteTextures(m_screenTextureCount, m_screenTexture);
+	m_shaderProgram.release();
+	m_shaderProgram.removeAllShaders();
+}
+
+void CaptureWinGLEngine::stop()
+{
+	// Setting the m_frameData to NULL stops OpenGL
+	// from updating frames on repaint
+	m_frameData = NULL;
+}
+
+void CaptureWinGLEngine::initializeGL()
+{
+	glShadeModel(GL_FLAT);
+	glEnable(GL_TEXTURE_2D);
+
+	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+	checkError("InitializeGL");
+}
+
+
+void CaptureWinGLEngine::resizeGL(int width, int height)
+{
+	// Resizing is disabled by setting viewport equal to frame size
+	glViewport(0, 0, m_frameWidth, m_frameHeight);
+}
+
+void CaptureWinGLEngine::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;
+
+		QGLWidget::setMaximumSize(m_frameWidth, m_frameHeight);
+	}
+
+	m_frameData = data;
+	updateGL();
+}
+
+void CaptureWinGLEngine::checkError(const char *msg)
+{
+	int err = glGetError();
+	if (err) fprintf(stderr, "OpenGL Error 0x%x: %s.\n", err, msg);
+}
+
+bool CaptureWinGLEngine::hasNativeFormat(__u32 format)
+{
+	static const __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)
+			return true;
+
+	return false;
+}
+
+void CaptureWinGLEngine::changeShader()
+{
+	m_formatChange = false;
+	clearShader();
+
+	glMatrixMode(GL_PROJECTION);
+	glLoadIdentity();
+	glOrtho(0, m_frameWidth, m_frameHeight, 0, 0, 1);
+	resizeGL(QGLWidget::width(), QGLWidget::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;
+	}
+
+	glClear(GL_COLOR_BUFFER_BIT);
+}
+
+void CaptureWinGLEngine::paintGL()
+{
+	if (m_frameWidth < 1 || m_frameHeight < 1) {
+		return;
+	}
+
+	if (m_formatChange)
+		changeShader();
+
+	if (m_frameData == NULL) {
+		return;
+	}
+
+	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 CaptureWinGLEngine::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 CaptureWinGLEngine::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 CaptureWinGLEngine::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 CaptureWinGLEngine::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 CaptureWinGLEngine::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 CaptureWinGLEngine::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 CaptureWinGLEngine::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 CaptureWinGLEngine::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-gl.h b/utils/qv4l2/capture-win-gl.h
new file mode 100644
index 0000000..08e72b2
--- /dev/null
+++ b/utils/qv4l2/capture-win-gl.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2013 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you may redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef CAPTURE_WIN_GL_H
+#define CAPTURE_WIN_GL_H
+
+#include "qv4l2.h"
+#include "capture-win.h"
+
+#ifdef ENABLE_GL
+#define GL_GLEXT_PROTOTYPES
+#include <QGLWidget>
+#include <QGLShader>
+#include <QGLShaderProgram>
+#include <QGLFunctions>
+
+// This must be equal to the max number of textures that any shader uses
+#define MAX_TEXTURES_NEEDED 3
+
+class CaptureWinGLEngine : public QGLWidget
+{
+public:
+	CaptureWinGLEngine();
+	~CaptureWinGLEngine();
+
+	void stop();
+	void setFrame(int width, int height, __u32 format, unsigned char *data);
+	bool hasNativeFormat(__u32 format);
+
+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[MAX_TEXTURES_NEEDED];
+	QGLFunctions m_glfunction;
+	unsigned char *m_frameData;
+	QGLShaderProgram m_shaderProgram;
+};
+
+#endif
+
+class CaptureWinGL : public CaptureWin
+{
+public:
+	CaptureWinGL();
+	~CaptureWinGL();
+
+	void setFrame(int width, int height, __u32 format,
+		      unsigned char *data, const QString &info);
+	void stop();
+	bool hasNativeFormat(__u32 format);
+	static bool isSupported();
+
+#ifdef ENABLE_GL
+	CaptureWinGLEngine m_videoSurface;
+#endif
+};
+
+#endif
diff --git a/utils/qv4l2/capture-win-qt.h b/utils/qv4l2/capture-win-qt.h
index d3b4fe8..d192045 100644
--- a/utils/qv4l2/capture-win-qt.h
+++ b/utils/qv4l2/capture-win-qt.h
@@ -35,6 +35,7 @@ public:
 	void setFrame(int width, int height, __u32 format,
 		      unsigned char *data, const QString &info);
 
+	void stop(){}
 	bool hasNativeFormat(__u32 format);
 	static bool isSupported() { return true; }
 
diff --git a/utils/qv4l2/capture-win.h b/utils/qv4l2/capture-win.h
index c3b7d98..ca60244 100644
--- a/utils/qv4l2/capture-win.h
+++ b/utils/qv4l2/capture-win.h
@@ -35,16 +35,55 @@ public:
 	~CaptureWin();
 
 	void setMinimumSize(int minw, int minh);
+
+	/**
+	 * @brief Set a frame into the capture window.
+	 *
+	 * When called the capture stream is
+	 * either already running or starting for the first time.
+	 *
+	 * @param width Frame width in pixels
+	 * @param height Frame height in pixels
+	 * @param format The frame's format, given as a V4L2_PIX_FMT.
+	 * @param data The frame data.
+	 * @param info A string containing capture information.
+	 */
 	virtual void setFrame(int width, int height, __u32 format,
 			      unsigned char *data, const QString &info) = 0;
 
+	/**
+	 * @brief Called when the capture stream is stopped.
+	 */
+	virtual void stop() = 0;
+
+	/**
+	 * @brief Queries the current capture window for its supported formats.
+	 *
+	 * Unsupported formats are converted by v4lconvert_convert().
+	 *
+	 * @param format The frame format question, given as a V4L2_PIX_FMT.
+	 * @return true if the format is supported, false if not.
+	 */
 	virtual bool hasNativeFormat(__u32 format) = 0;
+
+	/**
+	 * @brief Defines wether a capture window is supported.
+	 *
+	 * By default nothing is supported, but derived classes can override this.
+	 *
+	 * @return true if the capture window is supported on the system, false if not.
+	 */
 	static bool isSupported() { return false; }
 
 protected:
 	void closeEvent(QCloseEvent *event);
 	void buildWindow(QWidget *videoSurface);
 
+	/**
+	 * @brief A label that can is used to display capture information.
+	 *
+	 * @note This must be set in the derived class' setFrame() function.
+	 */
 	QLabel m_information;
 
 signals:
diff --git a/utils/qv4l2/qv4l2.cpp b/utils/qv4l2/qv4l2.cpp
index 0c9b74c..4dc5a3e 100644
--- a/utils/qv4l2/qv4l2.cpp
+++ b/utils/qv4l2/qv4l2.cpp
@@ -22,6 +22,7 @@
 #include "vbi-tab.h"
 #include "capture-win.h"
 #include "capture-win-qt.h"
+#include "capture-win-gl.h"
 
 #include <QToolBar>
 #include <QToolButton>
@@ -130,6 +131,20 @@ ApplicationWindow::ApplicationWindow() :
 	QMenu *captureMenu = menuBar()->addMenu("&Capture");
 	captureMenu->addAction(m_capStartAct);
 	captureMenu->addAction(m_showFramesAct);
+	captureMenu->addSeparator();
+
+	if (CaptureWinGL::isSupported()) {
+		m_renderMethod = QV4L2_RENDER_GL;
+
+		m_useGLAct = new QAction("Use Open&GL Render", this);
+		m_useGLAct->setStatusTip("Use GPU with OpenGL for video capture if set.");
+		m_useGLAct->setCheckable(true);
+		m_useGLAct->setChecked(true);
+		connect(m_useGLAct, SIGNAL(triggered()), this, SLOT(setRenderMethod()));
+		captureMenu->addAction(m_useGLAct);
+	} else {
+		m_renderMethod = QV4L2_RENDER_QT;
+	}
 
 	QMenu *helpMenu = menuBar()->addMenu("&Help");
 	helpMenu->addAction("&About", this, SLOT(about()), Qt::Key_F1);
@@ -161,9 +176,9 @@ void ApplicationWindow::setDevice(const QString &device, bool rawOpen)
 	if (!open(device, !rawOpen))
 		return;
 
-	m_capture = new CaptureWinQt;
+	newCaptureWin();
+
 	m_capture->setMinimumSize(150, 50);
-	connect(m_capture, SIGNAL(close()), this, SLOT(closeCaptureWin()));
 
 	QWidget *w = new QWidget(m_tabs);
 	m_genTab = new GeneralTab(device, *this, 4, w);
@@ -206,6 +221,21 @@ void ApplicationWindow::openrawdev()
 		setDevice(d.selectedFiles().first(), true);
 }
 
+void ApplicationWindow::setRenderMethod()
+{
+	if (m_capStartAct->isChecked()) {
+		m_useGLAct->setChecked(m_renderMethod == QV4L2_RENDER_GL);
+		return;
+	}
+
+	if (m_useGLAct->isChecked())
+		m_renderMethod = QV4L2_RENDER_GL;
+	else
+		m_renderMethod = QV4L2_RENDER_QT;
+
+	newCaptureWin();
+}
+
 void ApplicationWindow::ctrlEvent()
 {
 	v4l2_event ev;
@@ -253,6 +283,25 @@ void ApplicationWindow::ctrlEvent()
 	}
 }
 
+void ApplicationWindow::newCaptureWin()
+{
+	if (m_capture != NULL) {
+		m_capture->stop();
+		delete m_capture;
+	}
+
+	switch (m_renderMethod) {
+	case QV4L2_RENDER_GL:
+		m_capture = new CaptureWinGL;
+		break;
+	default:
+		m_capture = new CaptureWinQt;
+		break;
+	}
+
+	connect(m_capture, SIGNAL(close()), this, SLOT(closeCaptureWin()));
+}
+
 void ApplicationWindow::capVbiFrame()
 {
 	__u32 buftype = m_genTab->bufType();
@@ -602,6 +651,7 @@ void ApplicationWindow::stopCapture()
 	v4l2_encoder_cmd cmd;
 	unsigned i;
 
+	m_capture->stop();
 	m_snapshotAct->setDisabled(true);
 	switch (m_capMethod) {
 	case methodRead:
diff --git a/utils/qv4l2/qv4l2.h b/utils/qv4l2/qv4l2.h
index ccfc2f9..2921b16 100644
--- a/utils/qv4l2/qv4l2.h
+++ b/utils/qv4l2/qv4l2.h
@@ -60,6 +60,11 @@ enum CapMethod {
 	methodUser
 };
 
+enum RenderMethod {
+	QV4L2_RENDER_GL,
+	QV4L2_RENDER_QT
+};
+
 struct buffer {
 	void   *start;
 	size_t  length;
@@ -92,6 +97,8 @@ private:
 	void stopCapture();
 	void startOutput(unsigned buffer_size);
 	void stopOutput();
+	void newCaptureWin();
+
 	struct buffer *m_buffers;
 	struct v4l2_format m_capSrcFormat;
 	struct v4l2_format m_capDestFormat;
@@ -101,6 +108,7 @@ private:
 	bool m_mustConvert;
 	CapMethod m_capMethod;
 	bool m_makeSnapshot;
+	RenderMethod m_renderMethod;
 
 private slots:
 	void capStart(bool);
@@ -109,6 +117,7 @@ private slots:
 	void snapshot();
 	void capVbiFrame();
 	void saveRaw(bool);
+	void setRenderMethod();
 
 	// gui
 private slots:
@@ -166,6 +175,7 @@ private:
 	QAction *m_snapshotAct;
 	QAction *m_saveRawAct;
 	QAction *m_showFramesAct;
+	QAction *m_useGLAct;
 	QString m_filename;
 	QSignalMapper *m_sigMapper;
 	QTabWidget *m_tabs;
-- 
1.8.3.2


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCHv2 FINAL 2/6] qv4l2: add hotkeys for common operations
  2013-07-30  8:15   ` [PATCHv2 FINAL 2/6] qv4l2: add hotkeys for common operations Bård Eirik Winther
@ 2013-07-30 12:39     ` Mauro Carvalho Chehab
  0 siblings, 0 replies; 12+ messages in thread
From: Mauro Carvalho Chehab @ 2013-07-30 12:39 UTC (permalink / raw)
  To: Bård Eirik Winther, Hans Verkuil; +Cc: linux-media

Hi Bård/Hans,

Em Tue, 30 Jul 2013 10:15:20 +0200
Bård Eirik Winther <bwinther@cisco.com> escreveu:

> CTRL + V : When main window is selected start capture.
>            This gives an option other than the button to start recording,
>            as this is a frequent operation when using the utility.
> CTRL + W : When CaptureWin is selected close capture window
>            It makes it easier to deal with high resolutions video on
>            small screen, especially when the window close button may
>            be outside the monitor when repositioning the window.

It would be great if you could also add some documentation for qv4l2,
adding there the supported hot keys.

IMO, the better is to write it as a man page. If you need an example,
you could take a look at utils/keytable/ir-keytable.1.

At the building system, all that it is needed is to add something like
what's there at utils/keytable/Makefile.am:
	man_MANS = ir-keytable.1

Thanks,
Mauro

> 
> Signed-off-by: Bård Eirik Winther <bwinther@cisco.com>
> ---
>  utils/qv4l2/capture-win.cpp | 8 ++++++++
>  utils/qv4l2/capture-win.h   | 4 +++-
>  utils/qv4l2/qv4l2.cpp       | 1 +
>  3 files changed, 12 insertions(+), 1 deletion(-)
> 
> diff --git a/utils/qv4l2/capture-win.cpp b/utils/qv4l2/capture-win.cpp
> index 6798252..a94c73d 100644
> --- a/utils/qv4l2/capture-win.cpp
> +++ b/utils/qv4l2/capture-win.cpp
> @@ -35,6 +35,14 @@ CaptureWin::CaptureWin()
>  
>  	vbox->addWidget(m_label);
>  	vbox->addWidget(m_msg);
> +
> +	hotkeyClose = new QShortcut(Qt::CTRL+Qt::Key_W, this);
> +	QObject::connect(hotkeyClose, SIGNAL(activated()), this, SLOT(close()));
> +}
> +
> +CaptureWin::~CaptureWin()
> +{
> +	delete hotkeyClose;
>  }
>  
>  void CaptureWin::setImage(const QImage &image, const QString &status)
> diff --git a/utils/qv4l2/capture-win.h b/utils/qv4l2/capture-win.h
> index e861b12..4115d56 100644
> --- a/utils/qv4l2/capture-win.h
> +++ b/utils/qv4l2/capture-win.h
> @@ -21,6 +21,7 @@
>  #define CAPTURE_WIN_H
>  
>  #include <QWidget>
> +#include <QShortcut>
>  #include <sys/time.h>
>  
>  class QImage;
> @@ -32,7 +33,7 @@ class CaptureWin : public QWidget
>  
>  public:
>  	CaptureWin();
> -	virtual ~CaptureWin() {}
> +	~CaptureWin();
>  
>  	void setImage(const QImage &image, const QString &status);
>  
> @@ -45,6 +46,7 @@ signals:
>  private:
>  	QLabel *m_label;
>  	QLabel *m_msg;
> +	QShortcut *hotkeyClose;
>  };
>  
>  #endif
> diff --git a/utils/qv4l2/qv4l2.cpp b/utils/qv4l2/qv4l2.cpp
> index a8fcc65..bb1d84f 100644
> --- a/utils/qv4l2/qv4l2.cpp
> +++ b/utils/qv4l2/qv4l2.cpp
> @@ -78,6 +78,7 @@ ApplicationWindow::ApplicationWindow() :
>  	m_capStartAct->setStatusTip("Start capturing");
>  	m_capStartAct->setCheckable(true);
>  	m_capStartAct->setDisabled(true);
> +	m_capStartAct->setShortcut(Qt::CTRL+Qt::Key_V);
>  	connect(m_capStartAct, SIGNAL(toggled(bool)), this, SLOT(capStart(bool)));
>  
>  	m_snapshotAct = new QAction(QIcon(":/snapshot.png"), "&Make Snapshot", this);


-- 

Cheers,
Mauro

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCHv2 FINAL 0/6] qv4l2: add OpenGL rendering and window fixes
  2013-07-30  8:15 [PATCHv2 FINAL 0/6] qv4l2: add OpenGL rendering and window fixes Bård Eirik Winther
  2013-07-30  8:15 ` [PATCHv2 FINAL 1/6] qv4l2: move function ctrlEvent Bård Eirik Winther
@ 2013-07-30 13:12 ` Mauro Carvalho Chehab
  2013-07-31  5:57   ` Bård Eirik Winther
  1 sibling, 1 reply; 12+ messages in thread
From: Mauro Carvalho Chehab @ 2013-07-30 13:12 UTC (permalink / raw)
  To: Bård Eirik Winther; +Cc: linux-media

Em Tue, 30 Jul 2013 10:15:18 +0200
Bård Eirik Winther <bwinther@cisco.com> escreveu:

...

> Performance:
> All tests are done on an Intel i7-2600S (with Turbo Boost disabled) using the
> integrated Intel HD 2000 graphics processor. The mothreboard is an ASUS P8H77-I
> with 2x2GB CL 9-9-9-24 DDR3 RAM. The capture card is a Cisco test card with 4 HDMI
> inputs connected using PCIe2.0x8. All video input streams used for testing are
> progressive HD (1920x1080) with 60fps.

I did a quick test here with a radeon HD 7750 GPU on a i7-3770 CPU, using an UVC
camera at VGA resolution and nouveau driver (Kernel 3.10.3).

qv4l2 CPU usage dropped from 13% to 3,75%.

It sounds a nice improvement!

-- 

Cheers,
Mauro

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCHv2 FINAL 0/6] qv4l2: add OpenGL rendering and window fixes
  2013-07-30 13:12 ` [PATCHv2 FINAL 0/6] qv4l2: add OpenGL rendering and window fixes Mauro Carvalho Chehab
@ 2013-07-31  5:57   ` Bård Eirik Winther
  2013-07-31 10:58     ` Mauro Carvalho Chehab
  0 siblings, 1 reply; 12+ messages in thread
From: Bård Eirik Winther @ 2013-07-31  5:57 UTC (permalink / raw)
  To: Mauro Carvalho Chehab; +Cc: linux-media

On Tuesday, July 30, 2013 10:12:33 AM you wrote:
> Em Tue, 30 Jul 2013 10:15:18 +0200
> Bård Eirik Winther <bwinther@cisco.com> escreveu:
> 
> ...
> 
> > Performance:
> > All tests are done on an Intel i7-2600S (with Turbo Boost disabled) using the
> > integrated Intel HD 2000 graphics processor. The mothreboard is an ASUS P8H77-I
> > with 2x2GB CL 9-9-9-24 DDR3 RAM. The capture card is a Cisco test card with 4 HDMI
> > inputs connected using PCIe2.0x8. All video input streams used for testing are
> > progressive HD (1920x1080) with 60fps.
> 
> I did a quick test here with a radeon HD 7750 GPU on a i7-3770 CPU, using an UVC
> camera at VGA resolution and nouveau driver (Kernel 3.10.3).
> 
> qv4l2 CPU usage dropped from 13% to 3,75%.
> 
> It sounds a nice improvement!
> 
> 
That is good to hear. My results where achived using the 3.9 and 3.10 kernels
although I belive that the hardware and opengl driver affects performance the most.

With such a good hardware and relatively low resolution, you wont see that much of a differance :-),
but it is a nice improvement indeed. Anyway, nice to get additional tests!

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCHv2 FINAL 0/6] qv4l2: add OpenGL rendering and window fixes
  2013-07-31  5:57   ` Bård Eirik Winther
@ 2013-07-31 10:58     ` Mauro Carvalho Chehab
  0 siblings, 0 replies; 12+ messages in thread
From: Mauro Carvalho Chehab @ 2013-07-31 10:58 UTC (permalink / raw)
  To: Bård Eirik Winther; +Cc: Mauro Carvalho Chehab, linux-media

[-- Attachment #1: Type: TEXT/PLAIN, Size: 1321 bytes --]

On Wed, 31 Jul 2013, Bård Eirik Winther wrote:

> On Tuesday, July 30, 2013 10:12:33 AM you wrote:
>> Em Tue, 30 Jul 2013 10:15:18 +0200
>> Bård Eirik Winther <bwinther@cisco.com> escreveu:
>>
>> ...
>>
>>> Performance:
>>> All tests are done on an Intel i7-2600S (with Turbo Boost disabled) using the
>>> integrated Intel HD 2000 graphics processor. The mothreboard is an ASUS P8H77-I
>>> with 2x2GB CL 9-9-9-24 DDR3 RAM. The capture card is a Cisco test card with 4 HDMI
>>> inputs connected using PCIe2.0x8. All video input streams used for testing are
>>> progressive HD (1920x1080) with 60fps.
>>
>> I did a quick test here with a radeon HD 7750 GPU on a i7-3770 CPU, using an UVC
>> camera at VGA resolution and nouveau driver (Kernel 3.10.3).
>>
>> qv4l2 CPU usage dropped from 13% to 3,75%.
>>
>> It sounds a nice improvement!
>>
>>
> That is good to hear. My results where achived using the 3.9 and 3.10 kernels
> although I belive that the hardware and opengl driver affects performance the most.
>
> With such a good hardware and relatively low resolution, you wont see that much of a differance :-),

Yes, I know ;)

> but it is a nice improvement indeed. Anyway, nice to get additional tests!

Yeah, that was mainly the reason for the tests: to reproduce it with a 
different setup than yours.

Cheers,
Mauro

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCHv2 FINAL 6/6] qv4l2: add OpenGL rendering
  2013-07-30  8:15   ` [PATCHv2 FINAL 6/6] qv4l2: add OpenGL rendering Bård Eirik Winther
@ 2013-08-01 11:05     ` Bård Eirik Winther
  0 siblings, 0 replies; 12+ messages in thread
From: Bård Eirik Winther @ 2013-08-01 11:05 UTC (permalink / raw)
  To: linux-media

On Tuesday, July 30, 2013 10:15:24 AM you wrote:
> [PATCHv2 FINAL 6/6] qv4l2: add OpenGL rendering
>From 505e803da95dd7c4aeb9d7ec4661c83bb743da1e Mon Sep 17 00:00:00 2001
Message-Id: <505e803da95dd7c4aeb9d7ec4661c83bb743da1e.1375355021.git.bwinther@cisco.com>
From: =?UTF-8?q?B=C3=A5rd=20Eirik=20Winther?= <bwinther@cisco.com>
Date: Thu, 1 Aug 2013 13:02:43 +0200
Subject: [PATCH] qv4l2: fix compile error
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Fixes a compile error caused when opengl is not available

Signed-off-by: Bård Eirik Winther <bwinther@cisco.com>
---
 utils/qv4l2/capture-win-gl.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/utils/qv4l2/capture-win-gl.cpp b/utils/qv4l2/capture-win-gl.cpp
index 807d9e9..52412c7 100644
--- a/utils/qv4l2/capture-win-gl.cpp
+++ b/utils/qv4l2/capture-win-gl.cpp
@@ -26,7 +26,9 @@
 
 CaptureWinGL::CaptureWinGL()
 {
+#ifdef ENABLE_GL
 	CaptureWin::buildWindow(&m_videoSurface);
+#endif
 	CaptureWin::setWindowTitle("V4L2 Capture (OpenGL)");
 }
 
-- 
1.8.3.2



^ permalink raw reply related	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2013-08-01 11:05 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-07-30  8:15 [PATCHv2 FINAL 0/6] qv4l2: add OpenGL rendering and window fixes Bård Eirik Winther
2013-07-30  8:15 ` [PATCHv2 FINAL 1/6] qv4l2: move function ctrlEvent Bård Eirik Winther
2013-07-30  8:15   ` [PATCHv2 FINAL 2/6] qv4l2: add hotkeys for common operations Bård Eirik Winther
2013-07-30 12:39     ` Mauro Carvalho Chehab
2013-07-30  8:15   ` [PATCHv2 FINAL 3/6] qv4l2: fix minimum size in capture win to frame size Bård Eirik Winther
2013-07-30  8:15   ` [PATCHv2 FINAL 4/6] qv4l2: add Capture menu Bård Eirik Winther
2013-07-30  8:15   ` [PATCHv2 FINAL 5/6] qv4l2: new modular capture window design Bård Eirik Winther
2013-07-30  8:15   ` [PATCHv2 FINAL 6/6] qv4l2: add OpenGL rendering Bård Eirik Winther
2013-08-01 11:05     ` Bård Eirik Winther
2013-07-30 13:12 ` [PATCHv2 FINAL 0/6] qv4l2: add OpenGL rendering and window fixes Mauro Carvalho Chehab
2013-07-31  5:57   ` Bård Eirik Winther
2013-07-31 10:58     ` Mauro Carvalho Chehab

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).