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@xxxxxxxxx> --- 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 -- 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