/**************************************************************************** ** ** Copyright (C) 2018 Klaralvdalens Datakonsult AB (KDAB). ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt3D module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:BSD$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** BSD License Usage ** Alternatively, you may use this file under the terms of the BSD license ** as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of The Qt Company Ltd nor the names of its ** contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include "videoplayer.h" TextureWidget::TextureWidget(QWidget *parent) : QOpenGLWidget(parent) , m_texture(QOpenGLTexture::Target2D) { // Lock mutex so that we never process a frame until we have been initialized m_mutex.lock(); } // Main thread void TextureWidget::initializeGL() { initializeOpenGLFunctions(); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); if (!m_shader.addShaderFromSourceCode(QOpenGLShader::Vertex, "#version 330\n" "out vec2 coords;\n" "const vec2 positions[6] = vec2[] (" " vec2(-1.0, 1.0)," " vec2(-1.0, -1.0)," " vec2(1.0, 1.0)," " vec2(1.0, 1.0)," " vec2(-1.0, -1.0)," " vec2(1.0, -1.0));\n" "const vec2 texCoords[6] = vec2[] (" " vec2(0.0, 0.0)," " vec2(0.0, 1.0)," " vec2(1.0, 0.0)," " vec2(1.0, 0.0)," " vec2(0.0, 1.0)," " vec2(1.0, 1.0));\n" "void main() {\n" " coords = texCoords[gl_VertexID];\n" " gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);\n" "}")) qDebug() << "Failed to load vertex shader" << m_shader.log(); if (!m_shader.addShaderFromSourceCode(QOpenGLShader::Fragment, "#version 330\n" "in vec2 coords;\n" "uniform sampler2D video_texture;\n" "out vec4 fragColor;\n" "void main() {\n" " fragColor = texture(video_texture, coords);\n" "}")) qDebug() << "Failed to load fragment shader" << m_shader.log(); if (!m_shader.link()) qDebug() << "Failed to link shaders" << m_shader.log(); qDebug() << Q_FUNC_INFO << context()->shareContext(); m_vao.create(); // Allow rendering/frame acquisition to go on m_mutex.unlock(); } // Main thread void TextureWidget::paintGL() { QMutexLocker lock(&m_mutex); glViewport(0, 0, width(), height()); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); if (!m_texture.isCreated()) return; m_shader.bind(); m_texture.bind(0); m_shader.setUniformValue("video_texture", 0); m_vao.bind(); glDrawArrays(GL_TRIANGLES, 0, 6); m_vao.release(); m_shader.release(); } // Video Player thread void TextureWidget::setVideoFrame(const QVideoFrame &frame) { // Ensure we won't be rendering while we are processing the frame QMutexLocker lock(&m_mutex); QVideoFrame f = frame; // Map frame if (!f.map(QAbstractVideoBuffer::ReadOnly)) return; makeCurrent(); // Create or recreate texture if (m_texture.width() != f.width() || m_texture.height() != f.height()) { if (m_texture.isCreated()) m_texture.destroy(); m_texture.setSize(f.width(), f.height()); m_texture.setFormat(QOpenGLTexture::RGBA32F); m_texture.setWrapMode(QOpenGLTexture::ClampToBorder); m_texture.setMinificationFilter(QOpenGLTexture::Nearest); m_texture.setMagnificationFilter(QOpenGLTexture::Nearest); m_texture.allocateStorage(); m_texture.create(); emit textureIdChanged(m_texture.textureId()); } const QVideoFrame::PixelFormat pFormat = f.pixelFormat(); if (pFormat == QVideoFrame::Format_RGB32) { m_texture.setData(QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, f.bits()); } doneCurrent(); // Request display udpate QOpenGLWidget::update(); // Unmap f.unmap(); } QList GLVideoSurface::supportedPixelFormats(QAbstractVideoBuffer::HandleType type) const { if (type == QAbstractVideoBuffer::NoHandle) return { QVideoFrame::Format_RGB32, QVideoFrame::Format_ARGB32, QVideoFrame::Format_BGR32, QVideoFrame::Format_BGRA32 }; return {}; } // Video player thread bool GLVideoSurface::present(const QVideoFrame &frame) { emit onNewFrame(frame); return true; } VideoPlayerThread::VideoPlayerThread(TextureWidget *textureWidget) : QThread(textureWidget) , m_player(new QMediaPlayer(nullptr, QMediaPlayer::VideoSurface)) , m_surface(new GLVideoSurface()) { m_player->moveToThread(this); m_player->setMedia(QUrl("https://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_1mb.mp4")); // Tell player to render on GLVideoSurface m_surface->moveToThread(this); m_player->setVideoOutput(m_surface.get()); // Display errors QObject::connect(m_player.get(), QOverload::of(&QMediaPlayer::error), m_player.get(), [this] (QMediaPlayer::Error e) { qDebug() << Q_FUNC_INFO << e << m_player->errorString(); }); // Repeat video indefinitely QObject::connect(m_player.get(), &QMediaPlayer::stateChanged, m_player.get(), [this] (QMediaPlayer::State state) { if (state == QMediaPlayer::StoppedState) m_player->play(); }); // Start playing when thread starts QObject::connect(this, &QThread::started, this, [this] { m_player->play(); }); // Direct connection between 2 objects living in different threads QObject::connect(m_surface.get(), &GLVideoSurface::onNewFrame, textureWidget, &TextureWidget::setVideoFrame, Qt::DirectConnection); } VideoPlayerThread::~VideoPlayerThread() { exit(0); wait(); }