You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

377 lines
11 KiB

#include "qtvlcwidget.h"
#include <QMouseEvent>
#include <QOpenGLShaderProgram>
#include <QCoreApplication>
#include <QOpenGLFramebufferObject>
#include <QOpenGLFunctions>
#include <QOffscreenSurface>
#include <QThread>
#include <QSemaphore>
#include <QMutex>
#include <cmath>
#include <vlc/vlc.h>
class VLCVideo
{
public:
VLCVideo(QtVLCWidget *widget)
:mWidget(widget)
{
/* Use default format for context. */
mContext = new QOpenGLContext(widget);
/* Use offscreen surface to render the buffers */
mSurface = new QOffscreenSurface(nullptr, widget);
/* Widget doesn't have an OpenGL context right now, we'll get it later. */
QObject::connect(widget, &QtVLCWidget::contextReady, [this](QOpenGLContext *render_context) {
/* Video view is now ready, we can start */
mSurface->setFormat(mWidget->format());
mSurface->create();
mContext->setFormat(mWidget->format());
mContext->setShareContext(render_context);
mContext->create();
videoReady.release();
});
}
~VLCVideo()
{
cleanup(this);
}
/// return whether we are using OpenGLES or OpenGL, which is needed to know
/// whether we use the libvlc OpenGLES2 engine or the OpenGL one
bool isOpenGLES()
{
return mContext->isOpenGLES();
}
/// return the texture to be displayed
QOpenGLFramebufferObject *getVideoFrame()
{
QMutexLocker locker(&m_text_lock);
if (m_updated) {
std::swap(m_idx_swap, m_idx_display);
m_updated = false;
}
return mBuffers[m_idx_display].get();
}
/// this callback will create the surfaces and FBO used by VLC to perform its rendering
static bool resizeRenderTextures(void* data, const libvlc_video_render_cfg_t *cfg,
libvlc_video_output_cfg_t *render_cfg)
{
VLCVideo* that = static_cast<VLCVideo*>(data);
if (cfg->width != that->m_width || cfg->height != that->m_height)
cleanup(data);
for (auto &buffer : that->mBuffers)
buffer = std::make_unique<QOpenGLFramebufferObject>(cfg->width, cfg->height);
that->m_width = cfg->width;
that->m_height = cfg->height;
that->mBuffers[that->m_idx_render]->bind();
render_cfg->opengl_format = GL_RGBA;
render_cfg->full_range = true;
render_cfg->colorspace = libvlc_video_colorspace_BT709;
render_cfg->primaries = libvlc_video_primaries_BT709;
render_cfg->transfer = libvlc_video_transfer_func_SRGB;
render_cfg->orientation = libvlc_video_orient_top_left;
return true;
}
// This callback is called during initialisation.
static bool setup(void** data, const libvlc_video_setup_device_cfg_t *cfg,
libvlc_video_setup_device_info_t *out)
{
Q_UNUSED(cfg);
Q_UNUSED(out);
if (!QOpenGLContext::supportsThreadedOpenGL())
return false;
VLCVideo* that = static_cast<VLCVideo*>(*data);
/* Wait for rendering view to be ready. */
that->videoReady.acquire();
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);
that->videoReady.release();
if (that->m_width == 0 && that->m_height == 0)
return;
for (auto &buffer : that->mBuffers)
buffer.reset(nullptr);
}
//This callback is called after VLC performs drawing calls
static void swap(void* data)
{
VLCVideo* that = static_cast<VLCVideo*>(data);
QMutexLocker locker(&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->mContext->makeCurrent(that->mSurface);
else
that->mContext->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);
/* Qt usual expects core symbols to be queryable, even though it's not
* mentioned in the API. Cf QPlatformOpenGLContext::getProcAddress.
* Thus, we don't need to wrap the function symbols here, but it might
* fail to work (not crash though) on exotic platforms since Qt doesn't
* commit to this behaviour. A way to handle this in a more stable way
* would be to store the mContext->functions() table in a thread local
* variable, and wrap every call to OpenGL in a function using this
* thread local state to call the correct variant. */
return reinterpret_cast<void*>(that->mContext->getProcAddress(current));
}
private:
QtVLCWidget *mWidget;
QOpenGLContext *mContext;
QOffscreenSurface *mSurface;
QSemaphore videoReady;
//FBO data
unsigned m_width = 0;
unsigned m_height = 0;
QMutex m_text_lock;
std::unique_ptr<QOpenGLFramebufferObject> mBuffers[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),
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=2"
};
m_vlc = libvlc_new(sizeof(args) / sizeof(*args), args);
mVLC = std::make_unique<VLCVideo>(this);
}
bool QtVLCWidget::playMedia(const char* url)
{
m_media = libvlc_media_new_location(url);
if (m_media == nullptr) {
fprintf(stderr, "unable to create media %s", url);
return false;
}
m_mp = libvlc_media_player_new_from_media (m_vlc, 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_output_callbacks(m_mp,
mVLC->isOpenGLES() ? libvlc_video_engine_gles2 : libvlc_video_engine_opengl,
VLCVideo::setup, VLCVideo::cleanup, nullptr, VLCVideo::resizeRenderTextures, VLCVideo::swap,
VLCVideo::make_current, VLCVideo::get_proc_address, nullptr, nullptr,
mVLC.get());
// 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);
m_vlc = nullptr;
if (m_program == nullptr)
return;
makeCurrent();
vertexBuffer.destroy();
vertexIndexBuffer.destroy();
m_program.reset(nullptr);
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 =
/* Precision is available but no-op on GLSL 130 (see [3], section 4.5.2)
* and mandatory for OpenGL ES. It was first reserved on GLSL 120 and
* didn't exist in GLSL <= 110 (= OpenGL 2.0). Since it's a no-op, the
* easiest solution is to never use it for OpenGL code.
*
* [1]: https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.1.10.pdf
* [2]: https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.1.20.pdf
* [3]: https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.1.30.pdf
*/
"#ifdef GL_ES\n"
"precision highp float;\n"
"#endif\n"
"\n"
"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);
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 = std::make_unique<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);
emit contextReady(context());
}
void QtVLCWidget::paintGL()
{
QOpenGLFunctions *GL = context()->functions();
QOpenGLFramebufferObject *fbo = mVLC->getVideoFrame();
if (fbo != nullptr && GL != nullptr)
{
m_program->bind();
GL->glClearColor(1.0, 0.5, 0.0, 1.0);
GL->glActiveTexture(GL_TEXTURE0);
GL->glBindTexture(GL_TEXTURE_2D, fbo->texture());
vertexBuffer.bind();
m_program->setAttributeArray("position", (const QVector2D *)nullptr, sizeof(GLfloat)*2);
m_program->enableAttributeArray("position");
vertexIndexBuffer.bind();
GL->glDrawElements(
GL_TRIANGLE_STRIP, /* mode */
4, /* count */
GL_UNSIGNED_SHORT, /* type */
(void*)0 /* element array buffer offset */
);
m_program->disableAttributeArray("position");
}
}
void QtVLCWidget::resizeGL(int w, int h)
{
Q_UNUSED(w);
Q_UNUSED(h);
/* TODO */
}