client API: expose OpenGL renderer

This adds API to libmpv that lets host applications use the mpv opengl
renderer. This is a more flexible (and possibly more portable) option to
foreign window embedding (via --wid).

This assumes that methods like context sharing and multithreaded OpenGL
rendering are infeasible, and that a way is needed to integrate it with
an application that uses a single thread to render everything.

Add an example that does this with QtQuick/qml. The example is
relatively lazy, but still shows how relatively simple the integration
is. The FBO indirection could probably be avoided, but would require
more work (and would probably lead to worse QtQuick integration, because
it would have to ignore transformations like rotation).

Because this makes mpv directly use the host application's OpenGL
context, there is no platform specific code involved in mpv, except
for hw decoding interop.

main.qml is derived from some Qt example.

The following things are still missing:
- a way to do better video timing
- expose GL renderer options, allow changing them at runtime
- support for color equalizer controls
- support for screenshots
This commit is contained in:
wm4
2014-12-09 17:47:02 +01:00
parent d38bc531cc
commit fb855b8659
28 changed files with 993 additions and 25 deletions

View File

@@ -0,0 +1,19 @@
#include <QGuiApplication>
#include <QtQuick/QQuickView>
#include "mpvrenderer.h"
int main(int argc, char **argv)
{
QGuiApplication app(argc, argv);
qmlRegisterType<MpvObject>("mpvtest", 1, 0, "MpvObject");
QQuickView view;
view.setResizeMode(QQuickView::SizeRootObjectToView);
view.setSource(QUrl("qrc:///mpvtest/main.qml"));
view.show();
return app.exec();
}

View File

@@ -0,0 +1,39 @@
import QtQuick 2.0
import QtQuick.Controls 1.0
import mpvtest 1.0
Item {
width: 1280
height: 720
MpvObject {
id: renderer
anchors.fill: parent
MouseArea {
anchors.fill: parent
onClicked: renderer.loadfile("../../../test.mkv")
}
}
Rectangle {
id: labelFrame
anchors.margins: -50
radius: 5
color: "white"
border.color: "black"
opacity: 0.8
anchors.fill: label
}
Text {
id: label
anchors.bottom: renderer.bottom
anchors.left: renderer.left
anchors.right: renderer.right
anchors.margins: 100
wrapMode: Text.WordWrap
text: "QtQuick and mpv are both rendering stuff."
}
}

View File

@@ -0,0 +1,125 @@
#include "mpvrenderer.h"
#include <QObject>
#include <QtGlobal>
#include <QOpenGLContext>
#include <QtGui/QOpenGLFramebufferObject>
#include <QtQuick/QQuickWindow>
#include <qsgsimpletexturenode.h>
class MpvRenderer : public QQuickFramebufferObject::Renderer
{
static void *get_proc_address(void *ctx, const char *name) {
(void)ctx;
QOpenGLContext *glctx = QOpenGLContext::currentContext();
if (!glctx)
return NULL;
return (void *)glctx->getProcAddress(QByteArray(name));
}
mpv_opengl_cb_context *mpv_gl;
QQuickWindow *window;
public:
MpvRenderer(mpv_opengl_cb_context *a_mpv_gl)
: mpv_gl(a_mpv_gl), window(NULL)
{
int r = mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address, NULL);
if (r < 0)
throw "could not initialize OpenGL";
}
virtual ~MpvRenderer()
{
mpv_opengl_cb_uninit_gl(mpv_gl);
}
void render()
{
assert(window); // must have been set by synchronize()
QOpenGLFramebufferObject *fbo = framebufferObject();
int vp[4] = {0, 0, fbo->width(), fbo->height()};
window->resetOpenGLState();
mpv_opengl_cb_render(mpv_gl, fbo->handle(), vp);
window->resetOpenGLState();
}
QOpenGLFramebufferObject *createFramebufferObject(const QSize &size)
{
QOpenGLFramebufferObjectFormat format;
format.setSamples(4);
return new QOpenGLFramebufferObject(size, format);
}
protected:
virtual void synchronize(QQuickFramebufferObject *item)
{
window = item->window();
}
};
MpvObject::MpvObject(QQuickItem * parent)
: QQuickFramebufferObject(parent)
{
mpv = mpv_create();
if (!mpv)
throw "could not create mpv context";
mpv_set_option_string(mpv, "terminal", "yes");
mpv_set_option_string(mpv, "msg-level", "all=v");
if (mpv_initialize(mpv) < 0) {
mpv_terminate_destroy(mpv);
throw "could not initialize mpv context";
}
// Make use of the MPV_SUB_API_OPENGL_CB API.
mpv::qt::set_option_variant(mpv, "vo", "opengl-cb");
// Request hw decoding, just for testing.
mpv::qt::set_option_variant(mpv, "hwdec", "auto");
mpv_gl = (mpv_opengl_cb_context *)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB);
if (!mpv_gl) {
mpv_terminate_destroy(mpv);
throw "OpenGL not compiled in";
}
mpv_opengl_cb_set_update_callback(mpv_gl, on_update, (void *)this);
connect(this, &MpvObject::onUpdate, this, &MpvObject::doUpdate, Qt::QueuedConnection);
}
MpvObject::~MpvObject()
{
mpv_terminate_destroy(mpv);
}
void MpvObject::on_update(void *ctx)
{
MpvObject *self = (MpvObject *)ctx;
emit self->onUpdate();
}
// connected to onUpdate(); signal makes sure it runs on the GUI thread
void MpvObject::doUpdate()
{
update();
}
void MpvObject::loadfile(const QString& filename)
{
QVariantList cmd;
cmd.append("loadfile");
cmd.append(filename);
mpv::qt::command_variant(mpv, cmd);
}
QQuickFramebufferObject::Renderer *MpvObject::createRenderer() const
{
window()->setPersistentOpenGLContext(true);
window()->setPersistentSceneGraph(true);
return new MpvRenderer(mpv_gl);
}

View File

@@ -0,0 +1,33 @@
#ifndef MPVRENDERER_H_
#define MPVRENDERER_H_
#include <assert.h>
#include <QtQuick/QQuickFramebufferObject>
#include "libmpv/client.h"
#include "libmpv/opengl_cb.h"
#include "libmpv/qthelper.hpp"
class MpvObject : public QQuickFramebufferObject
{
Q_OBJECT
mpv_handle *mpv;
mpv_opengl_cb_context *mpv_gl;
public:
MpvObject(QQuickItem * parent = 0);
virtual ~MpvObject();
Renderer *createRenderer() const;
public slots:
void loadfile(const QString& filename);
signals:
void onUpdate();
private slots:
void doUpdate();
private:
static void on_update(void *ctx);
};
#endif

View File

@@ -0,0 +1,11 @@
QT += qml quick
HEADERS += mpvrenderer.h
SOURCES += mpvrenderer.cpp main.cpp
CONFIG += link_pkgconfig debug
PKGCONFIG = mpv
RESOURCES += mpvtest.qrc
OTHER_FILES += main.qml

View File

@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/mpvtest">
<file>main.qml</file>
</qresource>
</RCC>