diff options
Diffstat (limited to 'subprojects/gst-plugins-good/ext/qt/qtitem.cc')
-rw-r--r-- | subprojects/gst-plugins-good/ext/qt/qtitem.cc | 815 |
1 files changed, 815 insertions, 0 deletions
diff --git a/subprojects/gst-plugins-good/ext/qt/qtitem.cc b/subprojects/gst-plugins-good/ext/qt/qtitem.cc new file mode 100644 index 0000000000..4ac349ea0f --- /dev/null +++ b/subprojects/gst-plugins-good/ext/qt/qtitem.cc @@ -0,0 +1,815 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters <matthew@centricular.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> + +#include <gst/video/video.h> +#include "qtitem.h" +#include "gstqsgtexture.h" +#include "gstqtglutility.h" + +#include <QtCore/QMutexLocker> +#include <QtCore/QPointer> +#include <QtGui/QGuiApplication> +#include <QtQuick/QQuickWindow> +#include <QtQuick/QSGSimpleTextureNode> + +/** + * SECTION:QtGLVideoItem + * @short_description: a Qt5 QtQuick item that renders GStreamer video #GstBuffers + * + * #QtGLVideoItem is an #QQuickItem that renders GStreamer video buffers. + */ + +#define GST_CAT_DEFAULT qt_item_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +#define DEFAULT_FORCE_ASPECT_RATIO TRUE +#define DEFAULT_PAR_N 0 +#define DEFAULT_PAR_D 1 + +enum +{ + PROP_0, + PROP_FORCE_ASPECT_RATIO, + PROP_PIXEL_ASPECT_RATIO, +}; + +struct _QtGLVideoItemPrivate +{ + GMutex lock; + + /* properties */ + gboolean force_aspect_ratio; + gint par_n, par_d; + + GWeakRef sink; + + gint display_width; + gint display_height; + + gboolean negotiated; + GstBuffer *buffer; + GstCaps *caps; + GstVideoInfo v_info; + + gboolean initted; + GstGLDisplay *display; + QOpenGLContext *qt_context; + GstGLContext *other_context; + GstGLContext *context; + + /* buffers with textures that were bound by QML */ + GQueue bound_buffers; + /* buffers that were previously bound but in the meantime a new one was + * bound so this one is most likely not used anymore + * FIXME: Ideally we would use fences for this but there seems to be no + * way to reliably "try wait" on a fence */ + GQueue potentially_unbound_buffers; +}; + +QtGLVideoItem::QtGLVideoItem() +{ + static gsize _debug; + + if (g_once_init_enter (&_debug)) { + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "qtglwidget", 0, "Qt GL Widget"); + g_once_init_leave (&_debug, 1); + } + + this->setFlag (QQuickItem::ItemHasContents, true); + + this->priv = g_new0 (QtGLVideoItemPrivate, 1); + + this->priv->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO; + this->priv->par_n = DEFAULT_PAR_N; + this->priv->par_d = DEFAULT_PAR_D; + + this->priv->initted = FALSE; + + g_mutex_init (&this->priv->lock); + + g_weak_ref_init (&priv->sink, NULL); + + this->priv->display = gst_qt_get_gl_display(TRUE); + + connect(this, SIGNAL(windowChanged(QQuickWindow*)), this, + SLOT(handleWindowChanged(QQuickWindow*))); + + this->proxy = QSharedPointer<QtGLVideoItemInterface>(new QtGLVideoItemInterface(this)); + + setFlag(ItemHasContents, true); + setAcceptedMouseButtons(Qt::AllButtons); + setAcceptHoverEvents(true); + + GST_DEBUG ("%p init Qt Video Item", this); +} + +QtGLVideoItem::~QtGLVideoItem() +{ + GstBuffer *tmp_buffer; + + /* Before destroying the priv info, make sure + * no qmlglsink's will call in again, and that + * any ongoing calls are done by invalidating the proxy + * pointer */ + GST_INFO ("%p Destroying QtGLVideoItem and invalidating the proxy %p", this, proxy.data()); + proxy->invalidateRef(); + proxy.clear(); + + g_mutex_clear (&this->priv->lock); + if (this->priv->context) + gst_object_unref(this->priv->context); + if (this->priv->other_context) + gst_object_unref(this->priv->other_context); + if (this->priv->display) + gst_object_unref(this->priv->display); + + while ((tmp_buffer = (GstBuffer*) g_queue_pop_head (&this->priv->potentially_unbound_buffers))) { + GST_TRACE ("old buffer %p should be unbound now, unreffing", tmp_buffer); + gst_buffer_unref (tmp_buffer); + } + while ((tmp_buffer = (GstBuffer*) g_queue_pop_head (&this->priv->bound_buffers))) { + GST_TRACE ("old buffer %p should be unbound now, unreffing", tmp_buffer); + gst_buffer_unref (tmp_buffer); + } + + gst_buffer_replace (&this->priv->buffer, NULL); + + gst_caps_replace (&this->priv->caps, NULL); + + g_weak_ref_clear (&this->priv->sink); + + g_free (this->priv); + this->priv = NULL; +} + +void +QtGLVideoItem::setDAR(gint num, gint den) +{ + this->priv->par_n = num; + this->priv->par_d = den; +} + +void +QtGLVideoItem::getDAR(gint * num, gint * den) +{ + if (num) + *num = this->priv->par_n; + if (den) + *den = this->priv->par_d; +} + +void +QtGLVideoItem::setForceAspectRatio(bool force_aspect_ratio) +{ + this->priv->force_aspect_ratio = !!force_aspect_ratio; + + emit forceAspectRatioChanged(force_aspect_ratio); +} + +bool +QtGLVideoItem::getForceAspectRatio() +{ + return this->priv->force_aspect_ratio; +} + +bool +QtGLVideoItem::itemInitialized() +{ + return this->priv->initted; +} + +QSGNode * +QtGLVideoItem::updatePaintNode(QSGNode * oldNode, + UpdatePaintNodeData * updatePaintNodeData) +{ + GstBuffer *old_buffer; + gboolean was_bound = FALSE; + + if (!this->priv->initted) + return oldNode; + + QSGSimpleTextureNode *texNode = static_cast<QSGSimpleTextureNode *> (oldNode); + GstVideoRectangle src, dst, result; + GstQSGTexture *tex; + + g_mutex_lock (&this->priv->lock); + + if (gst_gl_context_get_current() == NULL) + gst_gl_context_activate (this->priv->other_context, TRUE); + + GST_TRACE ("%p updatePaintNode", this); + + if (!this->priv->caps) { + g_mutex_unlock (&this->priv->lock); + return NULL; + } + + if (!texNode) { + texNode = new QSGSimpleTextureNode (); + texNode->setOwnsTexture (true); + texNode->setTexture (new GstQSGTexture ()); + } + + tex = static_cast<GstQSGTexture *> (texNode->texture()); + + if ((old_buffer = tex->getBuffer(&was_bound))) { + if (old_buffer == this->priv->buffer) { + /* same buffer */ + gst_buffer_unref (old_buffer); + } else if (!was_bound) { + GST_TRACE ("old buffer %p was not bound yet, unreffing", old_buffer); + gst_buffer_unref (old_buffer); + } else { + GstBuffer *tmp_buffer; + + GST_TRACE ("old buffer %p was bound, queueing up for later", old_buffer); + /* Unref all buffers that were previously not bound anymore. At least + * one more buffer was bound in the meantime so this one is most likely + * not in use anymore. */ + while ((tmp_buffer = (GstBuffer*) g_queue_pop_head (&this->priv->potentially_unbound_buffers))) { + GST_TRACE ("old buffer %p should be unbound now, unreffing", tmp_buffer); + gst_buffer_unref (tmp_buffer); + } + + /* Move previous bound buffers to the next queue. We now know that + * another buffer was bound in the meantime and will free them on + * the next iteration above. */ + while ((tmp_buffer = (GstBuffer*) g_queue_pop_head (&this->priv->bound_buffers))) { + GST_TRACE ("old buffer %p is potentially unbound now", tmp_buffer); + g_queue_push_tail (&this->priv->potentially_unbound_buffers, tmp_buffer); + } + g_queue_push_tail (&this->priv->bound_buffers, old_buffer); + } + old_buffer = NULL; + } + + tex->setCaps (this->priv->caps); + tex->setBuffer (this->priv->buffer); + texNode->markDirty(QSGNode::DirtyMaterial); + + if (this->priv->force_aspect_ratio) { + src.w = this->priv->display_width; + src.h = this->priv->display_height; + + dst.x = boundingRect().x(); + dst.y = boundingRect().y(); + dst.w = boundingRect().width(); + dst.h = boundingRect().height(); + + gst_video_sink_center_rect (src, dst, &result, TRUE); + } else { + result.x = boundingRect().x(); + result.y = boundingRect().y(); + result.w = boundingRect().width(); + result.h = boundingRect().height(); + } + + texNode->setRect (QRectF (result.x, result.y, result.w, result.h)); + + g_mutex_unlock (&this->priv->lock); + + return texNode; +} + +/* This method has to be invoked with the the priv->lock taken */ +void +QtGLVideoItem::fitStreamToAllocatedSize(GstVideoRectangle * result) +{ + if (this->priv->force_aspect_ratio) { + GstVideoRectangle src, dst; + + src.x = 0; + src.y = 0; + src.w = this->priv->display_width; + src.h = this->priv->display_height; + + dst.x = 0; + dst.y = 0; + dst.w = width(); + dst.h = height(); + + gst_video_sink_center_rect (src, dst, result, TRUE); + } else { + result->x = 0; + result->y = 0; + result->w = width(); + result->h = height(); + } +} + +/* This method has to be invoked with the the priv->lock taken */ +QPointF +QtGLVideoItem::mapPointToStreamSize(QPointF pos) +{ + gdouble stream_width, stream_height; + GstVideoRectangle result; + double stream_x, stream_y; + double x, y; + + fitStreamToAllocatedSize(&result); + + stream_width = (gdouble) GST_VIDEO_INFO_WIDTH (&this->priv->v_info); + stream_height = (gdouble) GST_VIDEO_INFO_HEIGHT (&this->priv->v_info); + x = pos.x(); + y = pos.y(); + + /* from display coordinates to stream coordinates */ + if (result.w > 0) + stream_x = (x - result.x) / result.w * stream_width; + else + stream_x = 0.; + + /* clip to stream size */ + stream_x = CLAMP(stream_x, 0., stream_width); + + /* same for y-axis */ + if (result.h > 0) + stream_y = (y - result.y) / result.h * stream_height; + else + stream_y = 0.; + + stream_y = CLAMP(stream_y, 0., stream_height); + GST_TRACE ("transform %fx%f into %fx%f", x, y, stream_x, stream_y); + + return QPointF(stream_x, stream_y); +} + +void +QtGLVideoItem::wheelEvent(QWheelEvent * event) +{ + g_mutex_lock (&this->priv->lock); + QPoint delta = event->angleDelta(); + GstElement *element = GST_ELEMENT_CAST (g_weak_ref_get (&this->priv->sink)); + + if (element != NULL) { +#if (QT_VERSION >= QT_VERSION_CHECK (5, 14, 0)) + auto position = event->position(); +#else + auto position = *event; +#endif + gst_navigation_send_mouse_scroll_event (GST_NAVIGATION (element), + position.x(), position.y(), delta.x(), delta.y()); + g_object_unref (element); + } + g_mutex_unlock (&this->priv->lock); +} + +void +QtGLVideoItem::hoverEnterEvent(QHoverEvent *) +{ + mouseHovering = true; +} + +void +QtGLVideoItem::hoverLeaveEvent(QHoverEvent *) +{ + mouseHovering = false; +} + +void +QtGLVideoItem::hoverMoveEvent(QHoverEvent * event) +{ + if (!mouseHovering) + return; + + quint32 button = !!mousePressedButton; + + g_mutex_lock (&this->priv->lock); + + /* can't do anything when we don't have input format */ + if (!this->priv->negotiated) { + g_mutex_unlock (&this->priv->lock); + return; + } + + if (event->pos() != event->oldPos()) { + QPointF pos = mapPointToStreamSize(event->pos()); + GstElement *element = GST_ELEMENT_CAST (g_weak_ref_get (&this->priv->sink)); + + if (element != NULL) { + gst_navigation_send_mouse_event (GST_NAVIGATION (element), "mouse-move", + button, pos.x(), pos.y()); + g_object_unref (element); + } + } + g_mutex_unlock (&this->priv->lock); +} + +void +QtGLVideoItem::sendMouseEvent(QMouseEvent * event, const gchar * type) +{ + quint32 button = 0; + + switch (event->button()) { + case Qt::LeftButton: + button = 1; + break; + case Qt::RightButton: + button = 2; + break; + default: + break; + } + + mousePressedButton = button; + + g_mutex_lock (&this->priv->lock); + + /* can't do anything when we don't have input format */ + if (!this->priv->negotiated) { + g_mutex_unlock (&this->priv->lock); + return; + } + + QPointF pos = mapPointToStreamSize(event->pos()); + gchar* event_type = g_strconcat ("mouse-button-", type, NULL); + GstElement *element = GST_ELEMENT_CAST (g_weak_ref_get (&this->priv->sink)); + + if (element != NULL) { + gst_navigation_send_mouse_event (GST_NAVIGATION (element), event_type, + button, pos.x(), pos.y()); + g_object_unref (element); + } + + g_free (event_type); + g_mutex_unlock (&this->priv->lock); +} + +void +QtGLVideoItem::mousePressEvent(QMouseEvent * event) +{ + forceActiveFocus(); + sendMouseEvent(event, "press"); +} + +void +QtGLVideoItem::mouseReleaseEvent(QMouseEvent * event) +{ + sendMouseEvent(event, "release"); +} + +static void +_reset (QtGLVideoItem * qt_item) +{ + GstBuffer *tmp_buffer; + + gst_buffer_replace (&qt_item->priv->buffer, NULL); + + gst_caps_replace (&qt_item->priv->caps, NULL); + + qt_item->priv->negotiated = FALSE; + + while ((tmp_buffer = (GstBuffer*) g_queue_pop_head (&qt_item->priv->potentially_unbound_buffers))) { + GST_TRACE ("old buffer %p should be unbound now, unreffing", tmp_buffer); + gst_buffer_unref (tmp_buffer); + } + while ((tmp_buffer = (GstBuffer*) g_queue_pop_head (&qt_item->priv->bound_buffers))) { + GST_TRACE ("old buffer %p should be unbound now, unreffing", tmp_buffer); + gst_buffer_unref (tmp_buffer); + } +} + +void +QtGLVideoItemInterface::setSink (GstElement * sink) +{ + QMutexLocker locker(&lock); + if (qt_item == NULL) + return; + + g_mutex_lock (&qt_item->priv->lock); + g_weak_ref_set (&qt_item->priv->sink, sink); + g_mutex_unlock (&qt_item->priv->lock); +} + +void +QtGLVideoItemInterface::setBuffer (GstBuffer * buffer) +{ + QMutexLocker locker(&lock); + + if (qt_item == NULL) { + GST_WARNING ("%p actual item is NULL. setBuffer call ignored", this); + return; + } + + if (!qt_item->priv->negotiated) { + GST_WARNING ("%p Got buffer on unnegotiated QtGLVideoItem. Dropping", this); + return; + } + + g_mutex_lock (&qt_item->priv->lock); + + gst_buffer_replace (&qt_item->priv->buffer, buffer); + + QMetaObject::invokeMethod(qt_item, "update", Qt::QueuedConnection); + + g_mutex_unlock (&qt_item->priv->lock); +} + +void +QtGLVideoItem::onSceneGraphInitialized () +{ + if (this->window() == NULL) + return; + + GST_DEBUG ("%p scene graph initialization with Qt GL context %p", this, + this->window()->openglContext ()); + + if (this->priv->qt_context == this->window()->openglContext ()) + return; + + this->priv->qt_context = this->window()->openglContext (); + if (this->priv->qt_context == NULL) { + g_assert_not_reached (); + return; + } + + this->priv->initted = gst_qt_get_gl_wrapcontext (this->priv->display, + &this->priv->other_context, &this->priv->context); + + GST_DEBUG ("%p created wrapped GL context %" GST_PTR_FORMAT, this, + this->priv->other_context); + + emit itemInitializedChanged(); +} + +void +QtGLVideoItem::onSceneGraphInvalidated () +{ + GST_FIXME ("%p scene graph invalidated", this); +} + +/** + * Retrieve and populate the GL context information from the current + * OpenGL context. + */ +gboolean +QtGLVideoItemInterface::initWinSys () +{ + QMutexLocker locker(&lock); + + GError *error = NULL; + + if (qt_item == NULL) + return FALSE; + + g_mutex_lock (&qt_item->priv->lock); + + if (qt_item->priv->display && qt_item->priv->qt_context + && qt_item->priv->other_context && qt_item->priv->context) { + /* already have the necessary state */ + g_mutex_unlock (&qt_item->priv->lock); + return TRUE; + } + + if (!GST_IS_GL_DISPLAY (qt_item->priv->display)) { + GST_ERROR ("%p failed to retrieve display connection %" GST_PTR_FORMAT, + qt_item, qt_item->priv->display); + g_mutex_unlock (&qt_item->priv->lock); + return FALSE; + } + + if (!GST_IS_GL_CONTEXT (qt_item->priv->other_context)) { + GST_ERROR ("%p failed to retrieve wrapped context %" GST_PTR_FORMAT, qt_item, + qt_item->priv->other_context); + g_mutex_unlock (&qt_item->priv->lock); + return FALSE; + } + + qt_item->priv->context = gst_gl_context_new (qt_item->priv->display); + + if (!qt_item->priv->context) { + g_mutex_unlock (&qt_item->priv->lock); + return FALSE; + } + + if (!gst_gl_context_create (qt_item->priv->context, qt_item->priv->other_context, + &error)) { + GST_ERROR ("%s", error->message); + g_mutex_unlock (&qt_item->priv->lock); + return FALSE; + } + + g_mutex_unlock (&qt_item->priv->lock); + return TRUE; +} + +void +QtGLVideoItem::handleWindowChanged (QQuickWindow * win) +{ + if (win) { + if (win->isSceneGraphInitialized ()) + win->scheduleRenderJob (new RenderJob (std:: + bind (&QtGLVideoItem::onSceneGraphInitialized, this)), + QQuickWindow::BeforeSynchronizingStage); + else + connect (win, SIGNAL (sceneGraphInitialized ()), this, + SLOT (onSceneGraphInitialized ()), Qt::DirectConnection); + + connect (win, SIGNAL (sceneGraphInvalidated ()), this, + SLOT (onSceneGraphInvalidated ()), Qt::DirectConnection); + } else { + this->priv->qt_context = NULL; + this->priv->initted = FALSE; + } +} + +static gboolean +_calculate_par (QtGLVideoItem * widget, GstVideoInfo * info) +{ + gboolean ok; + gint width, height; + gint par_n, par_d; + gint display_par_n, display_par_d; + guint display_ratio_num, display_ratio_den; + + width = GST_VIDEO_INFO_WIDTH (info); + height = GST_VIDEO_INFO_HEIGHT (info); + + par_n = GST_VIDEO_INFO_PAR_N (info); + par_d = GST_VIDEO_INFO_PAR_D (info); + + if (!par_n) + par_n = 1; + + /* get display's PAR */ + if (widget->priv->par_n != 0 && widget->priv->par_d != 0) { + display_par_n = widget->priv->par_n; + display_par_d = widget->priv->par_d; + } else { + display_par_n = 1; + display_par_d = 1; + } + + ok = gst_video_calculate_display_ratio (&display_ratio_num, + &display_ratio_den, width, height, par_n, par_d, display_par_n, + display_par_d); + + if (!ok) + return FALSE; + + widget->setImplicitWidth (width); + widget->setImplicitHeight (height); + + GST_LOG ("%p PAR: %u/%u DAR:%u/%u", widget, par_n, par_d, display_par_n, + display_par_d); + + if (height % display_ratio_den == 0) { + GST_DEBUG ("%p keeping video height", widget); + widget->priv->display_width = (guint) + gst_util_uint64_scale_int (height, display_ratio_num, + display_ratio_den); + widget->priv->display_height = height; + } else if (width % display_ratio_num == 0) { + GST_DEBUG ("%p keeping video width", widget); + widget->priv->display_width = width; + widget->priv->display_height = (guint) + gst_util_uint64_scale_int (width, display_ratio_den, display_ratio_num); + } else { + GST_DEBUG ("%p approximating while keeping video height", widget); + widget->priv->display_width = (guint) + gst_util_uint64_scale_int (height, display_ratio_num, + display_ratio_den); + widget->priv->display_height = height; + } + GST_DEBUG ("%p scaling to %dx%d", widget, widget->priv->display_width, + widget->priv->display_height); + + return TRUE; +} + +gboolean +QtGLVideoItemInterface::setCaps (GstCaps * caps) +{ + QMutexLocker locker(&lock); + GstVideoInfo v_info; + + g_return_val_if_fail (GST_IS_CAPS (caps), FALSE); + g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE); + + if (qt_item == NULL) + return FALSE; + + if (qt_item->priv->caps && gst_caps_is_equal_fixed (qt_item->priv->caps, caps)) + return TRUE; + + if (!gst_video_info_from_caps (&v_info, caps)) + return FALSE; + + g_mutex_lock (&qt_item->priv->lock); + + _reset (qt_item); + + gst_caps_replace (&qt_item->priv->caps, caps); + + if (!_calculate_par (qt_item, &v_info)) { + g_mutex_unlock (&qt_item->priv->lock); + return FALSE; + } + + qt_item->priv->v_info = v_info; + qt_item->priv->negotiated = TRUE; + + g_mutex_unlock (&qt_item->priv->lock); + + return TRUE; +} + +GstGLContext * +QtGLVideoItemInterface::getQtContext () +{ + QMutexLocker locker(&lock); + + if (!qt_item || !qt_item->priv->other_context) + return NULL; + + return (GstGLContext *) gst_object_ref (qt_item->priv->other_context); +} + +GstGLContext * +QtGLVideoItemInterface::getContext () +{ + QMutexLocker locker(&lock); + + if (!qt_item || !qt_item->priv->context) + return NULL; + + return (GstGLContext *) gst_object_ref (qt_item->priv->context); +} + +GstGLDisplay * +QtGLVideoItemInterface::getDisplay() +{ + QMutexLocker locker(&lock); + + if (!qt_item || !qt_item->priv->display) + return NULL; + + return (GstGLDisplay *) gst_object_ref (qt_item->priv->display); +} + +void +QtGLVideoItemInterface::setDAR(gint num, gint den) +{ + QMutexLocker locker(&lock); + if (!qt_item) + return; + qt_item->setDAR(num, den); +} + +void +QtGLVideoItemInterface::getDAR(gint * num, gint * den) +{ + QMutexLocker locker(&lock); + if (!qt_item) + return; + qt_item->getDAR (num, den); +} + +void +QtGLVideoItemInterface::setForceAspectRatio(bool force_aspect_ratio) +{ + QMutexLocker locker(&lock); + if (!qt_item) + return; + qt_item->setForceAspectRatio(force_aspect_ratio); +} + +bool +QtGLVideoItemInterface::getForceAspectRatio() +{ + QMutexLocker locker(&lock); + if (!qt_item) + return FALSE; + return qt_item->getForceAspectRatio(); +} + +void +QtGLVideoItemInterface::invalidateRef() +{ + QMutexLocker locker(&lock); + qt_item = NULL; +} + |