[vlc-devel] [PATCH 2/6] doc: add a Qt app to showcase the OpenGL surface callbacks

Steve Lhomme robux4 at ycbcr.xyz
Fri Jan 25 16:47:01 CET 2019


---
 doc/libvlc/QtGL/main.cpp        |  40 ++++
 doc/libvlc/QtGL/qtvlcwidget.cpp | 313 ++++++++++++++++++++++++++++++++
 doc/libvlc/QtGL/qtvlcwidget.h   |  46 +++++
 3 files changed, 399 insertions(+)
 create mode 100644 doc/libvlc/QtGL/main.cpp
 create mode 100644 doc/libvlc/QtGL/qtvlcwidget.cpp
 create mode 100644 doc/libvlc/QtGL/qtvlcwidget.h

diff --git a/doc/libvlc/QtGL/main.cpp b/doc/libvlc/QtGL/main.cpp
new file mode 100644
index 0000000000..eb15b92fa5
--- /dev/null
+++ b/doc/libvlc/QtGL/main.cpp
@@ -0,0 +1,40 @@
+#include <QApplication>
+#include <QDesktopWidget>
+#include <QSurfaceFormat>
+#include <QMainWindow>
+
+#include <QtPlugin>
+Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
+
+
+#include "qtvlcwidget.h"
+
+int main(int argc, char *argv[])
+{
+    QApplication app(argc, argv);
+
+    // this important so we can call makeCurrent from our rendering thread
+    QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
+
+    QSurfaceFormat fmt;
+    fmt.setDepthBufferSize(24);
+    QSurfaceFormat::setDefaultFormat(fmt);
+
+    QMainWindow mainWindow;
+
+    QtVLCWidget *glWidget = new QtVLCWidget;
+    mainWindow.setCentralWidget(glWidget);
+
+    mainWindow.resize(mainWindow.sizeHint());
+    int desktopArea = QApplication::desktop()->width() *
+                     QApplication::desktop()->height();
+    int widgetArea = mainWindow.width() * mainWindow.height();
+    if (((float)widgetArea / (float)desktopArea) < 0.75f)
+        mainWindow.show();
+    else
+        mainWindow.showMaximized();
+
+    glWidget->playMedia(argv[1]);
+
+    return app.exec();
+}
diff --git a/doc/libvlc/QtGL/qtvlcwidget.cpp b/doc/libvlc/QtGL/qtvlcwidget.cpp
new file mode 100644
index 0000000000..6ad6685e6a
--- /dev/null
+++ b/doc/libvlc/QtGL/qtvlcwidget.cpp
@@ -0,0 +1,313 @@
+#include "qtvlcwidget.h"
+#include <QMouseEvent>
+#include <QOpenGLShaderProgram>
+#include <QCoreApplication>
+#include <QOpenGLFramebufferObject>
+#include <QThread>
+#include <cmath>
+
+#include <mutex>
+
+#include <vlc/vlc.h>
+
+class VLCVideo
+{
+public:
+    VLCVideo(QtVLCWidget *widget)
+        :mWidget(widget)
+    {
+        mBuffers[0] = NULL;
+        mBuffers[1] = NULL;
+        mBuffers[2] = NULL;
+    }
+
+    ~VLCVideo()
+    {
+        cleanup(this);
+    }
+
+    /// return the texture to be displayed
+    QOpenGLFramebufferObject *getVideoFrame()
+    {
+        std::lock_guard<std::mutex> lock(m_text_lock);
+        if (m_updated) {
+            std::swap(m_idx_swap, m_idx_display);
+            m_updated = false;
+        }
+        return mBuffers[m_idx_display];
+    }
+
+    /// this callback will create the surfaces and FBO used by VLC to perform its rendering
+    static void resizeRenderTextures(void* data, unsigned width, unsigned height)
+    {
+       VLCVideo* that = static_cast<VLCVideo*>(data);
+        if (width != that->m_width || height != that->m_height)
+            cleanup(data);
+
+        that->mBuffers[0] = new QOpenGLFramebufferObject(width, height);
+        that->mBuffers[1] = new QOpenGLFramebufferObject(width, height);
+        that->mBuffers[2] = new QOpenGLFramebufferObject(width, height);
+
+        that->m_width = width;
+        that->m_height = height;
+
+        that->mBuffers[that->m_idx_render]->bind();
+    }
+
+    // This callback is called during initialisation.
+    static bool setup(void* data)
+    {
+        if (!QOpenGLContext::supportsThreadedOpenGL())
+            return false;
+
+        VLCVideo* that = static_cast<VLCVideo*>(data);
+        that->m_width = 0;
+        that->m_height = 0;
+        return true;
+    }
+
+
+    // This callback is called to release the texture and FBO created in resize
+    static void cleanup(void* data)
+    {
+        VLCVideo* that = static_cast<VLCVideo*>(data);
+        if (that->m_width == 0 && that->m_height == 0)
+            return;
+        delete that->mBuffers[0];
+        that->mBuffers[0] = NULL;
+        delete that->mBuffers[1];
+        that->mBuffers[1] = NULL;
+        delete that->mBuffers[2];
+        that->mBuffers[2] = NULL;
+    }
+
+    //This callback is called after VLC performs drawing calls
+    static void swap(void* data)
+    {
+        VLCVideo* that = static_cast<VLCVideo*>(data);
+        std::lock_guard<std::mutex> lock(that->m_text_lock);
+        that->m_updated = true;
+        that->mWidget->update();
+        std::swap(that->m_idx_swap, that->m_idx_render);
+        that->mBuffers[that->m_idx_render]->bind();
+    }
+
+    // This callback is called to set the OpenGL context
+    static bool make_current(void* data, bool current)
+    {
+        VLCVideo* that = static_cast<VLCVideo*>(data);
+        if (current)
+            that->mWidget->makeCurrent();
+        else
+            that->mWidget->doneCurrent();
+        return true;
+    }
+
+    // This callback is called by VLC to get OpenGL functions.
+    static void* get_proc_address(void* data, const char* current)
+    {
+        VLCVideo* that = static_cast<VLCVideo*>(data);
+        QOpenGLContext *ctx = that->mWidget->context();
+        return reinterpret_cast<void*>(ctx->getProcAddress(current));
+    }
+
+private:
+    QtVLCWidget *mWidget;
+
+    //FBO data
+    unsigned m_width = 0;
+    unsigned m_height = 0;
+    std::mutex m_text_lock;
+    QOpenGLFramebufferObject *mBuffers[3];
+    GLuint m_tex[3];
+    GLuint m_fbo[3];
+    size_t m_idx_render = 0;
+    size_t m_idx_swap = 1;
+    size_t m_idx_display = 2;
+    bool m_updated = false;
+};
+
+
+QtVLCWidget::QtVLCWidget(QWidget *parent)
+    : QOpenGLWidget(parent),
+      m_program(nullptr),
+      vertexBuffer(QOpenGLBuffer::VertexBuffer),
+      vertexIndexBuffer(QOpenGLBuffer::IndexBuffer)
+{
+    // --transparent causes the clear color to be transparent. Therefore, on systems that
+    // support it, the widget will become transparent apart from the logo.
+
+    const char *args[] = {
+        "--verbose=4"
+    };
+    m_vlc = libvlc_new(sizeof(args) / sizeof(*args), args);
+
+    mVLC = new VLCVideo(this);
+}
+
+bool QtVLCWidget::playMedia(const char* url)
+{
+    m_media = libvlc_media_new_location (m_vlc, url);
+    if (m_media == nullptr) {
+        fprintf(stderr, "unable to create media %s", url);
+        return false;
+    }
+    m_mp = libvlc_media_player_new_from_media (m_media);
+    if (m_mp == nullptr) {
+        fprintf(stderr, "unable to create media player");
+        libvlc_media_release(m_media);
+        return false;
+    }
+
+    // Define the opengl rendering callbacks
+    libvlc_video_set_surface_callbacks(m_mp, libvlc_video_engine_opengl,
+        VLCVideo::setup, VLCVideo::cleanup, VLCVideo::resizeRenderTextures, VLCVideo::swap,
+        VLCVideo::make_current, VLCVideo::get_proc_address,
+        mVLC);
+
+    // Play the video
+    libvlc_media_player_play (m_mp);
+
+    return true;
+}
+
+QtVLCWidget::~QtVLCWidget()
+{
+    cleanup();
+}
+
+QSize QtVLCWidget::minimumSizeHint() const
+{
+    return QSize(50, 50);
+}
+
+QSize QtVLCWidget::sizeHint() const
+{
+    return QSize(400, 400);
+}
+
+void QtVLCWidget::cleanup()
+{
+    stop();
+    if (m_vlc)
+        libvlc_release(m_vlc);
+    if (m_program == nullptr)
+        return;
+    makeCurrent();
+    vertexBuffer.destroy();
+    vertexIndexBuffer.destroy();
+    delete m_program;
+    m_program = 0;
+    doneCurrent();
+}
+
+void QtVLCWidget::stop()
+{
+    if (m_mp) {
+        libvlc_media_player_release(m_mp);
+        m_mp = nullptr;
+    }
+    if (m_media) {
+        libvlc_media_release(m_media);
+        m_media = nullptr;
+    }
+}
+
+static const char *vertexShaderSource =
+    "attribute vec2 position;\n"
+    "varying vec2 texcoord;\n"
+    "void main()\n"
+    "{\n"
+    "    gl_Position = vec4(position, 0.0, 1.0);\n"
+    "    texcoord = position * vec2(0.5) + vec2(0.5);\n"
+    "}\n";
+
+static const char *fragmentShaderSource =
+    "uniform sampler2D texture;\n"
+    "\n"
+    "varying vec2 texcoord;\n"
+    "\n"
+    "void main()\n"
+    "{\n"
+    "    gl_FragColor = texture2D(texture, texcoord);\n"
+    "};\n";
+
+/*
+ * Data used to seed our vertex array and element array buffers:
+ */
+static const GLfloat g_vertex_buffer_data[] = {
+    -1.0f, -1.0f,
+     1.0f, -1.0f,
+    -1.0f,  1.0f,
+     1.0f,  1.0f
+};
+static const GLushort g_element_buffer_data[] = { 0, 1, 2, 3 };
+
+void QtVLCWidget::initializeGL()
+{
+    // In this example the widget's corresponding top-level window can change
+    // several times during the widget's lifetime. Whenever this happens, the
+    // QOpenGLWidget's associated context is destroyed and a new one is created.
+    // Therefore we have to be prepared to clean up the resources on the
+    // aboutToBeDestroyed() signal, instead of the destructor. The emission of
+    // the signal will be followed by an invocation of initializeGL() where we
+    // can recreate all resources.
+    connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &QtVLCWidget::cleanup);
+
+    initializeOpenGLFunctions();
+
+    vertexBuffer.create();
+    vertexBuffer.bind();
+    vertexBuffer.allocate(g_vertex_buffer_data, sizeof(g_vertex_buffer_data));
+
+    vertexIndexBuffer.create();
+    vertexIndexBuffer.bind();
+    vertexIndexBuffer.allocate(g_element_buffer_data, sizeof(g_element_buffer_data));
+
+    m_program = new QOpenGLShaderProgram;
+    m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
+    m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
+    m_program->link();
+
+    m_program->setUniformValue("texture", 0);
+
+    m_program->bindAttributeLocation("position", 0);
+}
+
+void QtVLCWidget::paintGL()
+{
+    QOpenGLFramebufferObject *fbo = mVLC->getVideoFrame();
+    if (fbo != NULL)
+    {
+        m_program->bind();
+
+        glClearColor(1.0, 0.5, 0.0, 1.0);
+
+        glActiveTexture(GL_TEXTURE0);
+        glBindTexture(GL_TEXTURE_2D, fbo->takeTexture());
+
+        vertexBuffer.bind();
+        m_program->setAttributeArray("position", (const QVector2D *)nullptr, sizeof(GLfloat)*2);
+        //vertexBuffer.release();
+
+        m_program->enableAttributeArray("position");
+
+        vertexIndexBuffer.bind();
+        glDrawElements(
+            GL_TRIANGLE_STRIP,  /* mode */
+            4,                  /* count */
+            GL_UNSIGNED_SHORT,  /* type */
+            (void*)0            /* element array buffer offset */
+        );
+        //vertexIndexBuffer.release();
+
+        m_program->disableAttributeArray("position");
+
+        //m_program->release();
+    }
+}
+
+void QtVLCWidget::resizeGL(int w, int h)
+{
+    /* TODO */
+}
diff --git a/doc/libvlc/QtGL/qtvlcwidget.h b/doc/libvlc/QtGL/qtvlcwidget.h
new file mode 100644
index 0000000000..febbc6252f
--- /dev/null
+++ b/doc/libvlc/QtGL/qtvlcwidget.h
@@ -0,0 +1,46 @@
+#ifndef GLWIDGET_H
+#define GLWIDGET_H
+
+#include <QOpenGLWidget>
+#include <QOpenGLFunctions>
+#include <QOpenGLVertexArrayObject>
+#include <QOpenGLBuffer>
+
+QT_FORWARD_DECLARE_CLASS(QOpenGLShaderProgram)
+
+class QtVLCWidget : public QOpenGLWidget, protected QOpenGLFunctions
+{
+    Q_OBJECT
+
+public:
+    QtVLCWidget(QWidget *parent = 0);
+    ~QtVLCWidget();
+
+    QSize minimumSizeHint() const override;
+    QSize sizeHint() const override;
+
+    bool playMedia(const char* url);
+
+public slots:
+    void cleanup();
+
+protected:
+    void initializeGL() override;
+    void paintGL() override;
+    void resizeGL(int width, int height) override;
+
+private:
+    QOpenGLVertexArrayObject m_vao;
+    QOpenGLShaderProgram *m_program;
+
+    class VLCVideo  *mVLC;
+
+    void stop();
+    struct libvlc_instance_t*  m_vlc = nullptr;
+    struct libvlc_media_player_t* m_mp = nullptr;
+    struct libvlc_media_t* m_media = nullptr;
+
+    QOpenGLBuffer vertexBuffer, vertexIndexBuffer;
+};
+
+#endif /* GLWIDGET_H */
-- 
2.17.1



More information about the vlc-devel mailing list