diff options
author | George Kiagiadakis <george.kiagiadakis@collabora.com> | 2013-10-21 14:15:41 +0200 |
---|---|---|
committer | George Kiagiadakis <george.kiagiadakis@collabora.com> | 2013-10-28 13:17:27 +0100 |
commit | b459cd8d32d70da017cef13528864e77bbf80c72 (patch) | |
tree | 4c1e1a8041986e4b92abd670153f424a92652ad2 | |
parent | 48ea384835dd8dd75bfab196da56df274a161b0b (diff) |
qtvideosink: add a qtquick2 video sink, based on patches by Benjamin Federau
-rw-r--r-- | CMakeLists.txt | 5 | ||||
-rw-r--r-- | elements/gstqtvideosink/CMakeLists.txt | 42 | ||||
-rw-r--r-- | elements/gstqtvideosink/delegates/basedelegate.h | 1 | ||||
-rw-r--r-- | elements/gstqtvideosink/delegates/qtquick2videosinkdelegate.cpp | 106 | ||||
-rw-r--r-- | elements/gstqtvideosink/delegates/qtquick2videosinkdelegate.h | 32 | ||||
-rw-r--r-- | elements/gstqtvideosink/delegates/qtvideosinkdelegate.cpp | 8 | ||||
-rw-r--r-- | elements/gstqtvideosink/gstqtquick2videosink.cpp | 484 | ||||
-rw-r--r-- | elements/gstqtvideosink/gstqtquick2videosink.h | 57 | ||||
-rw-r--r-- | elements/gstqtvideosink/gstqtvideosinkplugin.cpp | 9 | ||||
-rw-r--r-- | elements/gstqtvideosink/painters/videomaterial.cpp | 450 | ||||
-rw-r--r-- | elements/gstqtvideosink/painters/videomaterial.h | 78 | ||||
-rw-r--r-- | elements/gstqtvideosink/painters/videonode.cpp | 112 | ||||
-rw-r--r-- | elements/gstqtvideosink/painters/videonode.h | 50 | ||||
-rw-r--r-- | elements/gstqtvideosink/utils/utils.h | 8 |
14 files changed, 1423 insertions, 19 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index ccc4226..3cc1b9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ include(MacroLogFeature) set(Qt4_MIN_VERSION 4.7) set(Qt5_MIN_VERSION 5.0.0) -find_package(Qt4or5 COMPONENTS Core Gui Widgets OPTIONAL_COMPONENTS OpenGL Quick1 Test) +find_package(Qt4or5 COMPONENTS Core Gui Widgets OPTIONAL_COMPONENTS OpenGL Quick1 Quick2 Test) macro_log_feature(Qt4or5_FOUND "Qt" "Required for building everything" "http://qt-project.org/" TRUE "${Qt4or5_MIN_VERSION}") macro_log_feature(Qt4or5_OpenGL_FOUND "QtOpenGL" @@ -30,6 +30,9 @@ macro_log_feature(Qt4or5_OpenGL_FOUND "QtOpenGL" macro_log_feature(Qt4or5_Quick1_FOUND "QtQuick1 (QtDeclarative)" "Required for building QtQuick1 support" "http://qt-project.org/" FALSE "${Qt4or5_MIN_VERSION}") +macro_log_feature(Qt4or5_Quick2_FOUND "QtQuick2 (QtQuick)" + "Required for building QtQuick2 support" + "http://qt-project.org/" FALSE "${Qt4or5_MIN_VERSION}") if (QTGSTREAMER_TESTS) macro_log_feature(Qt4or5_Test_FOUND "QtTest" "Required for building unit tests" diff --git a/elements/gstqtvideosink/CMakeLists.txt b/elements/gstqtvideosink/CMakeLists.txt index 901cb94..ddb3da9 100644 --- a/elements/gstqtvideosink/CMakeLists.txt +++ b/elements/gstqtvideosink/CMakeLists.txt @@ -1,6 +1,8 @@ glib2_genmarshal(gstqtvideosinkmarshal VOID:POINTER,FLOAT,FLOAT,FLOAT,FLOAT VOID:POINTER,DOUBLE,DOUBLE,DOUBLE,DOUBLE + POINTER:POINTER,FLOAT,FLOAT,FLOAT,FLOAT + POINTER:POINTER,DOUBLE,DOUBLE,DOUBLE,DOUBLE ) set(GstQtVideoSink_SRCS @@ -21,8 +23,22 @@ set(GstQtVideoSink_SRCS ${CMAKE_CURRENT_BINARY_DIR}/gstqtvideosinkmarshal.c ) +if (Qt4or5_Quick2_FOUND AND (OPENGL_FOUND OR OPENGLES2_FOUND)) + set(GstQtVideoSink_SRCS + ${GstQtVideoSink_SRCS} + painters/videomaterial.cpp + painters/videonode.cpp + + delegates/qtquick2videosinkdelegate.cpp + + gstqtquick2videosink.cpp + ) + set(GstQtVideoSink_LINK_OPENGL TRUE) +endif() + if (Qt4or5_OpenGL_FOUND AND (OPENGL_FOUND OR OPENGLES2_FOUND)) - set(GstQtVideoSink_GL_SRCS + set(GstQtVideoSink_SRCS + ${GstQtVideoSink_SRCS} painters/openglsurfacepainter.cpp gstqtglvideosinkbase.cpp gstqtglvideosink.cpp @@ -30,14 +46,7 @@ if (Qt4or5_OpenGL_FOUND AND (OPENGL_FOUND OR OPENGLES2_FOUND)) set(GstQtVideoSink_test_GL_SRCS painters/openglsurfacepainter.cpp ) - - if (OPENGLES2_FOUND) - set(GstQtVideoSink_GL_LIBS ${OPENGLES2_LIBRARY}) - include_directories(${OPENGLES2_INCLUDE_DIR}) - else() - set(GstQtVideoSink_GL_LIBS ${OPENGL_gl_LIBRARY}) - include_directories(${OPENGL_INCLUDE_DIR}) - endif() + set(GstQtVideoSink_LINK_OPENGL TRUE) else() add_definitions(-DGST_QT_VIDEO_SINK_NO_OPENGL) endif() @@ -48,7 +57,17 @@ add_definitions( -DQWIDGETVIDEOSINK_NAME="${QWIDGETVIDEOSINK_NAME}" ) -add_library(gst${QTVIDEOSINK_NAME} MODULE ${GstQtVideoSink_SRCS} ${GstQtVideoSink_GL_SRCS}) +if (GstQtVideoSink_LINK_OPENGL) + if (OPENGLES2_FOUND) + set(GstQtVideoSink_GL_LIBS ${OPENGLES2_LIBRARY}) + include_directories(${OPENGLES2_INCLUDE_DIR}) + else() + set(GstQtVideoSink_GL_LIBS ${OPENGL_gl_LIBRARY}) + include_directories(${OPENGL_INCLUDE_DIR}) + endif() +endif() + +add_library(gst${QTVIDEOSINK_NAME} MODULE ${GstQtVideoSink_SRCS}) target_link_libraries(gst${QTVIDEOSINK_NAME} ${GOBJECT_LIBRARIES} ${GSTREAMER_LIBRARY} @@ -58,6 +77,9 @@ target_link_libraries(gst${QTVIDEOSINK_NAME} ${GstQtVideoSink_GL_LIBS} ) qt4or5_use_modules(gst${QTVIDEOSINK_NAME} Core Gui Widgets) +if (Qt4or5_Quick2_FOUND AND (OPENGL_FOUND OR OPENGLES2_FOUND)) + qt4or5_use_modules(gst${QTVIDEOSINK_NAME} Quick2) +endif() if (Qt4or5_OpenGL_FOUND AND (OPENGL_FOUND OR OPENGLES2_FOUND)) qt4or5_use_modules(gst${QTVIDEOSINK_NAME} OpenGL) endif() diff --git a/elements/gstqtvideosink/delegates/basedelegate.h b/elements/gstqtvideosink/delegates/basedelegate.h index 490240c..1e02f3f 100644 --- a/elements/gstqtvideosink/delegates/basedelegate.h +++ b/elements/gstqtvideosink/delegates/basedelegate.h @@ -20,6 +20,7 @@ #include <gst/gst.h> +#include "../gstqtvideosinkplugin.h" //for debug category #include "../utils/bufferformat.h" #include "../utils/utils.h" diff --git a/elements/gstqtvideosink/delegates/qtquick2videosinkdelegate.cpp b/elements/gstqtvideosink/delegates/qtquick2videosinkdelegate.cpp new file mode 100644 index 0000000..f9db08b --- /dev/null +++ b/elements/gstqtvideosink/delegates/qtquick2videosinkdelegate.cpp @@ -0,0 +1,106 @@ +/* + Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). <qt-info@nokia.com> + Copyright (C) 2011-2013 Collabora Ltd. <info@collabora.com> + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation. + + This program 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "qtquick2videosinkdelegate.h" +#include "../painters/videonode.h" + +QtQuick2VideoSinkDelegate::QtQuick2VideoSinkDelegate(GstElement *sink, QObject *parent) + : BaseDelegate(sink, parent) +{ +} + +QSGNode* QtQuick2VideoSinkDelegate::updateNode(QSGNode *node, const QRectF & targetArea) +{ + GST_TRACE_OBJECT(m_sink, "updateNode called"); + bool sgnodeFormatChanged = false; + + VideoNode *vnode = dynamic_cast<VideoNode*>(node); + if (!vnode) { + GST_INFO_OBJECT(m_sink, "creating new VideoNode"); + vnode = new VideoNode; + } + + if (!m_buffer) { + if (vnode->materialType() != VideoNode::MaterialTypeSolidBlack) { + vnode->setMaterialTypeSolidBlack(); + sgnodeFormatChanged = true; + } + if (sgnodeFormatChanged || targetArea != m_areas.targetArea) { + m_areas.targetArea = targetArea; + vnode->updateGeometry(m_areas); + } + } else { + BufferFormat format = m_formatDirty ? + BufferFormat::fromCaps(GST_BUFFER_CAPS(m_buffer)) : m_bufferFormat; + + //change format before geometry, so that we change QSGGeometry as well + if (m_formatDirty) { + vnode->changeFormat(format); + sgnodeFormatChanged = true; + } + + //recalculate the video area if needed + QReadLocker forceAspectRatioLocker(&m_forceAspectRatioLock); + if (sgnodeFormatChanged || targetArea != m_areas.targetArea || m_forceAspectRatioDirty) { + m_forceAspectRatioDirty = false; + + QReadLocker pixelAspectRatioLocker(&m_pixelAspectRatioLock); + Qt::AspectRatioMode aspectRatioMode = m_forceAspectRatio ? + Qt::KeepAspectRatio : Qt::IgnoreAspectRatio; + m_areas.calculate(targetArea, format.frameSize(), + format.pixelAspectRatio(), m_pixelAspectRatio, + aspectRatioMode); + pixelAspectRatioLocker.unlock(); + + GST_LOG_OBJECT(m_sink, + "Recalculated paint areas: " + "Frame size: " QSIZE_FORMAT ", " + "target area: " QRECTF_FORMAT ", " + "video area: " QRECTF_FORMAT ", " + "black1: " QRECTF_FORMAT ", " + "black2: " QRECTF_FORMAT, + QSIZE_FORMAT_ARGS(format.frameSize()), + QRECTF_FORMAT_ARGS(m_areas.targetArea), + QRECTF_FORMAT_ARGS(m_areas.videoArea), + QRECTF_FORMAT_ARGS(m_areas.blackArea1), + QRECTF_FORMAT_ARGS(m_areas.blackArea2) + ); + + vnode->updateGeometry(m_areas); + } + forceAspectRatioLocker.unlock(); + + if (m_formatDirty) { + m_bufferFormat = format; + m_formatDirty = false; + + //make sure to update the colors after changing material + m_colorsDirty = true; + } + + QReadLocker colorsLocker(&m_colorsLock); + if (m_colorsDirty) { + vnode->updateColors(m_brightness, m_contrast, m_hue, m_saturation); + m_colorsDirty = false; + } + colorsLocker.unlock(); + + vnode->setCurrentFrame(m_buffer); + } + + return vnode; +} diff --git a/elements/gstqtvideosink/delegates/qtquick2videosinkdelegate.h b/elements/gstqtvideosink/delegates/qtquick2videosinkdelegate.h new file mode 100644 index 0000000..b596658 --- /dev/null +++ b/elements/gstqtvideosink/delegates/qtquick2videosinkdelegate.h @@ -0,0 +1,32 @@ +/* + Copyright (C) 2013 Collabora Ltd. <info@collabora.com> + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation. + + This program 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef QTQUICK2VIDEOSINKDELEGATE_H +#define QTQUICK2VIDEOSINKDELEGATE_H + +#include "basedelegate.h" +#include <QtQuick/QSGNode> + +class QtQuick2VideoSinkDelegate : public BaseDelegate +{ + Q_OBJECT +public: + explicit QtQuick2VideoSinkDelegate(GstElement * sink, QObject * parent = 0); + + QSGNode *updateNode(QSGNode *node, const QRectF & targetArea); +}; + +#endif // QTQUICK2VIDEOSINKDELEGATE_H diff --git a/elements/gstqtvideosink/delegates/qtvideosinkdelegate.cpp b/elements/gstqtvideosink/delegates/qtvideosinkdelegate.cpp index b7769d4..1bc7b45 100644 --- a/elements/gstqtvideosink/delegates/qtvideosinkdelegate.cpp +++ b/elements/gstqtvideosink/delegates/qtvideosinkdelegate.cpp @@ -22,14 +22,6 @@ #include <QStack> #include <QPainter> -#define QSIZE_FORMAT "(%d x %d)" -#define QSIZE_FORMAT_ARGS(size) \ - size.width(), size.height() -#define QRECTF_FORMAT "(x: %f, y: %f, w: %f, h: %f)" -#define QRECTF_FORMAT_ARGS(rect) \ - (float) rect.x(), (float) rect.y(), (float) rect.width(), (float) rect.height() - - QtVideoSinkDelegate::QtVideoSinkDelegate(GstElement *sink, QObject *parent) : BaseDelegate(sink, parent) , m_painter(0) diff --git a/elements/gstqtvideosink/gstqtquick2videosink.cpp b/elements/gstqtvideosink/gstqtquick2videosink.cpp new file mode 100644 index 0000000..156f7ba --- /dev/null +++ b/elements/gstqtvideosink/gstqtquick2videosink.cpp @@ -0,0 +1,484 @@ +/* + Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). <qt-info@nokia.com> + Copyright (C) 2011-2013 Collabora Ltd. <info@collabora.com> + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation. + + This program 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "gstqtquick2videosink.h" +#include "gstqtvideosinkplugin.h" +#include "gstqtvideosinkmarshal.h" +#include "delegates/qtquick2videosinkdelegate.h" + +#include <gst/interfaces/colorbalance.h> + +#include <cstring> +#include <QCoreApplication> + +#define GST_QT_QUICK2_VIDEO_SINK_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_QT_QUICK2_VIDEO_SINK, GstQtQuick2VideoSinkPrivate)) + +struct _GstQtQuick2VideoSinkPrivate +{ + QtQuick2VideoSinkDelegate *delegate; + GList *channels_list; + bool formatDirty; +}; + +static void gst_qt_quick2_video_sink_init_interfaces (GType g_define_type_id); + +GST_BOILERPLATE_FULL (GstQtQuick2VideoSink, gst_qt_quick2_video_sink, + GstVideoSink, GST_TYPE_VIDEO_SINK, + gst_qt_quick2_video_sink_init_interfaces); + +enum { + PROP_0, + PROP_PIXEL_ASPECT_RATIO, + PROP_FORCE_ASPECT_RATIO, + PROP_CONTRAST, + PROP_BRIGHTNESS, + PROP_HUE, + PROP_SATURATION, +}; + +enum { + ACTION_UPDATE_NODE, + SIGNAL_UPDATE, + LAST_SIGNAL +}; + +static guint s_signals[LAST_SIGNAL] = { 0 }; + +const char * const s_colorbalance_labels[] = { + "contrast", "brightness", "hue", "saturation" +}; + +//index for s_colorbalance_labels +enum { + LABEL_CONTRAST = 0, + LABEL_BRIGHTNESS, + LABEL_HUE, + LABEL_SATURATION, + LABEL_LAST +}; + +static void +gst_qt_quick2_video_sink_init (GstQtQuick2VideoSink *self, + GstQtQuick2VideoSinkClass *klass) +{ + Q_UNUSED(klass); + self->priv = GST_QT_QUICK2_VIDEO_SINK_GET_PRIVATE (self); + + // delegate + self->priv->delegate = new QtQuick2VideoSinkDelegate(GST_ELEMENT(self)); + self->priv->formatDirty = true; + + // colorbalance + GstColorBalanceChannel *channel; + self->priv->channels_list = NULL; + + for (int i=0; i < LABEL_LAST; i++) { + channel = GST_COLOR_BALANCE_CHANNEL(g_object_new(GST_TYPE_COLOR_BALANCE_CHANNEL, NULL)); + channel->label = g_strdup(s_colorbalance_labels[i]); + channel->min_value = -100; + channel->max_value = 100; + + self->priv->channels_list = g_list_append(self->priv->channels_list, channel); + } +} + +static void +gst_qt_quick2_video_sink_finalize (GObject *gobject) +{ + GstQtQuick2VideoSink *self = GST_QT_QUICK2_VIDEO_SINK (gobject); + + delete self->priv->delegate; + self->priv->delegate = 0; + + while (self->priv->channels_list) { + GstColorBalanceChannel *channel = + GST_COLOR_BALANCE_CHANNEL(self->priv->channels_list->data); + g_object_unref(channel); + self->priv->channels_list = g_list_next(self->priv->channels_list); + } + + g_list_free(self->priv->channels_list); + + G_OBJECT_CLASS (parent_class)->finalize (gobject); +} + +static void +gst_qt_quick2_video_sink_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GstQtQuick2VideoSink *self = GST_QT_QUICK2_VIDEO_SINK (object); + + switch (property_id) { + case PROP_PIXEL_ASPECT_RATIO: + { + GValue tmp; + std::memset(&tmp, 0, sizeof(GValue)); + g_value_init(&tmp, GST_TYPE_FRACTION); + if (g_value_transform(value, &tmp)) { + int n = gst_value_get_fraction_numerator(&tmp); + int d = gst_value_get_fraction_denominator(&tmp); + self->priv->delegate->setPixelAspectRatio(Fraction(n, d)); + } else { + GST_WARNING_OBJECT(object, "Could not transform string to aspect ratio"); + } + g_value_unset(&tmp); + break; + } + case PROP_FORCE_ASPECT_RATIO: + self->priv->delegate->setForceAspectRatio(g_value_get_boolean(value)); + break; + case PROP_CONTRAST: + self->priv->delegate->setContrast(g_value_get_int(value)); + break; + case PROP_BRIGHTNESS: + self->priv->delegate->setBrightness(g_value_get_int(value)); + break; + case PROP_HUE: + self->priv->delegate->setHue(g_value_get_int(value)); + break; + case PROP_SATURATION: + self->priv->delegate->setSaturation(g_value_get_int(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gst_qt_quick2_video_sink_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GstQtQuick2VideoSink *self = GST_QT_QUICK2_VIDEO_SINK (object); + + switch (property_id) { + case PROP_PIXEL_ASPECT_RATIO: + { + GValue tmp; + Fraction par = self->priv->delegate->pixelAspectRatio(); + std::memset(&tmp, 0, sizeof(GValue)); + g_value_init(&tmp, GST_TYPE_FRACTION); + gst_value_set_fraction(&tmp, par.numerator, par.denominator); + g_value_transform(&tmp, value); + g_value_unset(&tmp); + break; + } + case PROP_FORCE_ASPECT_RATIO: + g_value_set_boolean(value, self->priv->delegate->forceAspectRatio()); + break; + case PROP_CONTRAST: + g_value_set_int(value, self->priv->delegate->contrast()); + break; + case PROP_BRIGHTNESS: + g_value_set_int(value, self->priv->delegate->brightness()); + break; + case PROP_HUE: + g_value_set_int(value, self->priv->delegate->hue()); + break; + case PROP_SATURATION: + g_value_set_int(value, self->priv->delegate->saturation()); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static GstStateChangeReturn +gst_qt_quick2_video_sink_change_state(GstElement *element, + GstStateChange transition) +{ + GstQtQuick2VideoSink *self = GST_QT_QUICK2_VIDEO_SINK (element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + self->priv->delegate->setActive(true); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + self->priv->delegate->setActive(false); + break; + default: + break; + } + + return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); +} + +static gboolean +gst_qt_quick2_video_sink_set_caps(GstBaseSink *sink, GstCaps *caps) +{ + GstQtQuick2VideoSink *self = GST_QT_QUICK2_VIDEO_SINK (sink); + + GST_LOG_OBJECT(self, "new caps %" GST_PTR_FORMAT, caps); + self->priv->formatDirty = true; + return TRUE; +} + +static GstFlowReturn +gst_qt_quick2_video_sink_show_frame(GstVideoSink *sink, GstBuffer *buffer) +{ + GstQtQuick2VideoSink *self = GST_QT_QUICK2_VIDEO_SINK (sink); + + GST_TRACE_OBJECT(self, "Posting new buffer (%"GST_PTR_FORMAT") for rendering. " + "Format dirty: %d", buffer, (int)self->priv->formatDirty); + + QCoreApplication::postEvent(self->priv->delegate, + new BaseDelegate::BufferEvent(buffer, self->priv->formatDirty)); + + self->priv->formatDirty = false; + return GST_FLOW_OK; +} + +//------------------------------ + +static gpointer +gst_qt_quick2_video_sink_update_node(GstQtQuick2VideoSink *self, gpointer node, + qreal x, qreal y, qreal w, qreal h) +{ + return self->priv->delegate->updateNode(static_cast<QSGNode*>(node), + QRectF(x, y, w, h)); +} + +//------------------------------ + +static gboolean +gst_qt_quick2_video_sink_interface_supported(GstImplementsInterface *iface, GType type) +{ + Q_UNUSED(iface); + return type == GST_TYPE_COLOR_BALANCE; +} + +static void +gst_qt_quick2_video_sink_implementsiface_init(GstImplementsInterfaceClass *klass, gpointer data) +{ + Q_UNUSED(data); + klass->supported = gst_qt_quick2_video_sink_interface_supported; +} + +//------------------------------ + +static const GList * +gst_qt_quick2_video_sink_colorbalance_list_channels(GstColorBalance *balance) +{ + return GST_QT_QUICK2_VIDEO_SINK (balance)->priv->channels_list; +} + +static void +gst_qt_quick2_video_sink_colorbalance_set_value(GstColorBalance *balance, + GstColorBalanceChannel *channel, gint value) +{ + GstQtQuick2VideoSink *self = GST_QT_QUICK2_VIDEO_SINK (balance); + + if (!qstrcmp(channel->label, s_colorbalance_labels[LABEL_CONTRAST])) { + self->priv->delegate->setContrast(value); + } else if (!qstrcmp(channel->label, s_colorbalance_labels[LABEL_BRIGHTNESS])) { + self->priv->delegate->setBrightness(value); + } else if (!qstrcmp(channel->label, s_colorbalance_labels[LABEL_HUE])) { + self->priv->delegate->setHue(value); + } else if (!qstrcmp(channel->label, s_colorbalance_labels[LABEL_SATURATION])) { + self->priv->delegate->setSaturation(value); + } else { + GST_WARNING_OBJECT(self, "Unknown colorbalance channel %s", channel->label); + } +} + +static gint +gst_qt_quick2_video_sink_colorbalance_get_value(GstColorBalance *balance, + GstColorBalanceChannel *channel) +{ + GstQtQuick2VideoSink *self = GST_QT_QUICK2_VIDEO_SINK (balance); + + if (!qstrcmp(channel->label, s_colorbalance_labels[LABEL_CONTRAST])) { + return self->priv->delegate->contrast(); + } else if (!qstrcmp(channel->label, s_colorbalance_labels[LABEL_BRIGHTNESS])) { + return self->priv->delegate->brightness(); + } else if (!qstrcmp(channel->label, s_colorbalance_labels[LABEL_HUE])) { + return self->priv->delegate->hue(); + } else if (!qstrcmp(channel->label, s_colorbalance_labels[LABEL_SATURATION])) { + return self->priv->delegate->saturation(); + } else { + GST_WARNING_OBJECT(self, "Unknown colorbalance channel %s", channel->label); + } + + return 0; +} + +static void +gst_qt_quick2_video_sink_colorbalance_init(GstColorBalanceClass *klass, gpointer data) +{ + Q_UNUSED(data); + GST_COLOR_BALANCE_TYPE(klass) = GST_COLOR_BALANCE_HARDWARE; + klass->list_channels = gst_qt_quick2_video_sink_colorbalance_list_channels; + klass->set_value = gst_qt_quick2_video_sink_colorbalance_set_value; + klass->get_value = gst_qt_quick2_video_sink_colorbalance_get_value; +} + +//------------------------------ + +static void +gst_qt_quick2_video_sink_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); + + static GstVideoFormat supportedFormats[] = { + GST_VIDEO_FORMAT_BGRA, + GST_VIDEO_FORMAT_BGRx, + GST_VIDEO_FORMAT_ARGB, + GST_VIDEO_FORMAT_xRGB, + GST_VIDEO_FORMAT_RGB, + GST_VIDEO_FORMAT_RGB16, + GST_VIDEO_FORMAT_BGR, + GST_VIDEO_FORMAT_v308, + GST_VIDEO_FORMAT_AYUV, + GST_VIDEO_FORMAT_YV12, + GST_VIDEO_FORMAT_I420 + }; + + GstCaps *caps = gst_caps_new_empty(); + for (uint i = 0; i < sizeof(supportedFormats) / sizeof(GstVideoFormat); i++) { + gst_caps_append(caps, BufferFormat::newTemplateCaps(supportedFormats[i])); + } + + GstPadTemplate *pad_tmpl = gst_pad_template_new ("sink", + GST_PAD_SINK, GST_PAD_ALWAYS, caps); + gst_element_class_add_pad_template(element_class, pad_tmpl); + + gst_element_class_set_details_simple(element_class, + "QtQuick2 video sink", "Sink/Video", + "A video sink that can draw on a QQuickItem", + "George Kiagiadakis <george.kiagiadakis@collabora.com>"); +} + +static void +gst_qt_quick2_video_sink_class_init (GstQtQuick2VideoSinkClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = gst_qt_quick2_video_sink_finalize; + gobject_class->set_property = gst_qt_quick2_video_sink_set_property; + gobject_class->get_property = gst_qt_quick2_video_sink_get_property; + + GstElementClass *element_class = GST_ELEMENT_CLASS(klass); + element_class->change_state = gst_qt_quick2_video_sink_change_state; + + GstBaseSinkClass *base_sink_class = GST_BASE_SINK_CLASS(klass); + base_sink_class->set_caps = gst_qt_quick2_video_sink_set_caps; + + GstVideoSinkClass *video_sink_class = GST_VIDEO_SINK_CLASS(klass); + video_sink_class->show_frame = gst_qt_quick2_video_sink_show_frame; + + GstQtQuick2VideoSinkClass *qtquick2_class = GST_QT_QUICK2_VIDEO_SINK_CLASS(klass); + qtquick2_class->update_node = gst_qt_quick2_video_sink_update_node; + + /** + * GstQtQuick2VideoSink::pixel-aspect-ratio + * + * The pixel aspect ratio of the display device. + **/ + g_object_class_install_property(gobject_class, PROP_PIXEL_ASPECT_RATIO, + g_param_spec_string("pixel-aspect-ratio", "Pixel aspect ratio", + "The pixel aspect ratio of the display device", + "1/1", static_cast<GParamFlags>(G_PARAM_READWRITE))); + + /** + * GstQtQuick2VideoSink::force-aspect-ratio + * + * If set to TRUE, the sink will scale the video respecting its original aspect ratio + * and any remaining space will be filled with black. + * If set to FALSE, the sink will scale the video to fit the whole drawing area. + **/ + g_object_class_install_property(gobject_class, PROP_FORCE_ASPECT_RATIO, + g_param_spec_boolean("force-aspect-ratio", "Force aspect ratio", + "When enabled, scaling will respect original aspect ratio", + FALSE, static_cast<GParamFlags>(G_PARAM_READWRITE))); + + g_object_class_install_property(gobject_class, PROP_CONTRAST, + g_param_spec_int("contrast", "Contrast", "The contrast of the video", + -100, 100, 0, static_cast<GParamFlags>(G_PARAM_READWRITE))); + + g_object_class_install_property(gobject_class, PROP_BRIGHTNESS, + g_param_spec_int("brightness", "Brightness", "The brightness of the video", + -100, 100, 0, static_cast<GParamFlags>(G_PARAM_READWRITE))); + + g_object_class_install_property(gobject_class, PROP_HUE, + g_param_spec_int("hue", "Hue", "The hue of the video", + -100, 100, 0, static_cast<GParamFlags>(G_PARAM_READWRITE))); + + g_object_class_install_property(gobject_class, PROP_SATURATION, + g_param_spec_int("saturation", "Saturation", "The saturation of the video", + -100, 100, 0, static_cast<GParamFlags>(G_PARAM_READWRITE))); + + + /** + * GstQtQuick2VideoSink::update-node + * @node: The QSGNode to update + * @x: The x coordinate of the target area rectangle + * @y: The y coordinate of the target area rectangle + * @width: The width of the target area rectangle + * @height: The height of the target area rectangle + * @returns: The updated QGSNode + * + * This is an action signal that you can call from your QQuickItem subclass + * inside its updateNode function to render the video. It takes a QSGNode* + * and the item's area rectangle as arguments. You should schedule to call + * this function to repaint the surface whenever the ::update signal is + * emited. + * + * Note that the x,y,width and height arguments are actually qreal. + * This means that on architectures like arm they will be float instead + * of double. You should cast the arguments to qreal if they are not + * already when emitting this signal. + */ + s_signals[ACTION_UPDATE_NODE] = + g_signal_new("update-node", G_TYPE_FROM_CLASS(klass), + static_cast<GSignalFlags>(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION), + G_STRUCT_OFFSET(GstQtQuick2VideoSinkClass, update_node), + NULL, NULL, + qRealIsDouble() ? + g_cclosure_user_marshal_POINTER__POINTER_DOUBLE_DOUBLE_DOUBLE_DOUBLE : + g_cclosure_user_marshal_POINTER__POINTER_FLOAT_FLOAT_FLOAT_FLOAT, + G_TYPE_POINTER, 5, + G_TYPE_POINTER, G_TYPE_QREAL, G_TYPE_QREAL, G_TYPE_QREAL, G_TYPE_QREAL); + + /** + * GstQtQuick2VideoSink::update + * + * This signal is emited when the surface should be repainted. It should + * be connected to QQuickItem::update(). + */ + s_signals[SIGNAL_UPDATE] = + g_signal_new("update", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + g_type_class_add_private (klass, sizeof (GstQtQuick2VideoSinkPrivate)); +} + +static void +gst_qt_quick2_video_sink_init_interfaces (GType g_define_type_id) +{ + G_IMPLEMENT_INTERFACE (GST_TYPE_IMPLEMENTS_INTERFACE, + gst_qt_quick2_video_sink_implementsiface_init); + G_IMPLEMENT_INTERFACE (GST_TYPE_COLOR_BALANCE, + gst_qt_quick2_video_sink_colorbalance_init); +} diff --git a/elements/gstqtvideosink/gstqtquick2videosink.h b/elements/gstqtvideosink/gstqtquick2videosink.h new file mode 100644 index 0000000..e5122d5 --- /dev/null +++ b/elements/gstqtvideosink/gstqtquick2videosink.h @@ -0,0 +1,57 @@ +/* + Copyright (C) 2013 Collabora Ltd. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __GST_QT_QUICK2_VIDEO_SINK_H__ +#define __GST_QT_QUICK2_VIDEO_SINK_H__ + +#include <gst/video/gstvideosink.h> +#include <QtGlobal> + +#define GST_TYPE_QT_QUICK2_VIDEO_SINK \ + (gst_qt_quick2_video_sink_get_type ()) +#define GST_QT_QUICK2_VIDEO_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_QT_QUICK2_VIDEO_SINK, GstQtQuick2VideoSink)) +#define GST_IS_QT_QUICK2_VIDEO_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_QT_QUICK2_VIDEO_SINK)) +#define GST_QT_QUICK2_VIDEO_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_QT_QUICK2_VIDEO_SINK, GstQtQuick2VideoSinkClass)) +#define GST_IS_QT_QUICK2_VIDEO_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_QT_QUICK2_VIDEO_SINK)) +#define GST_QT_QUICK2_VIDEO_SINK_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_QT_QUICK2_VIDEO_SINK, GstQtQuick2VideoSinkClass)) + +typedef struct _GstQtQuick2VideoSink GstQtQuick2VideoSink; +typedef struct _GstQtQuick2VideoSinkClass GstQtQuick2VideoSinkClass; +typedef struct _GstQtQuick2VideoSinkPrivate GstQtQuick2VideoSinkPrivate; + +struct _GstQtQuick2VideoSink +{ + GstVideoSink parent_instance; + GstQtQuick2VideoSinkPrivate *priv; +}; + +struct _GstQtQuick2VideoSinkClass +{ + GstVideoSinkClass parent_class; + + gpointer (*update_node)(GstQtQuick2VideoSink *self, + gpointer node, qreal x, qreal y, qreal w, qreal h); +}; + +GType gst_qt_quick2_video_sink_get_type (void); + +#endif /* __GST_QT_QUICK2_VIDEO_SINK_H__ */ diff --git a/elements/gstqtvideosink/gstqtvideosinkplugin.cpp b/elements/gstqtvideosink/gstqtvideosinkplugin.cpp index 7a804d3..6b2e3e5 100644 --- a/elements/gstqtvideosink/gstqtvideosinkplugin.cpp +++ b/elements/gstqtvideosink/gstqtvideosinkplugin.cpp @@ -20,6 +20,10 @@ #include "gstqtglvideosink.h" #include "gstqwidgetvideosink.h" +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) +# include "gstqtquick2videosink.h" +#endif + GST_DEBUG_CATEGORY(gst_qt_video_sink_debug); /* entry point to initialize the plug-in */ @@ -37,6 +41,11 @@ static gboolean plugin_init(GstPlugin *plugin) gst_element_register(plugin, QWIDGETVIDEOSINK_NAME, GST_RANK_NONE, GST_TYPE_QWIDGET_VIDEO_SINK); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + gst_element_register(plugin, "qtquick2videosink", + GST_RANK_NONE, GST_TYPE_QT_QUICK2_VIDEO_SINK); +#endif + return TRUE; } diff --git a/elements/gstqtvideosink/painters/videomaterial.cpp b/elements/gstqtvideosink/painters/videomaterial.cpp new file mode 100644 index 0000000..385891f --- /dev/null +++ b/elements/gstqtvideosink/painters/videomaterial.cpp @@ -0,0 +1,450 @@ +/* + Copyright (C) 2011-2013 Collabora Ltd. <info@collabora.com> + Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). + Copyright (C) 2013 basysKom GmbH <info@basyskom.com> + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation. + + This program 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "videomaterial.h" + +#include <qmath.h> +#include <QOpenGLContext> +#include <QOpenGLFunctions> +#include <QtQuick/QSGMaterialShader> + +static const char * const qtvideosink_glsl_vertexShader = + "uniform highp mat4 qt_Matrix; \n" + "attribute highp vec4 qt_VertexPosition; \n" + "attribute highp vec2 qt_VertexTexCoord; \n" + "varying highp vec2 qt_TexCoord; \n" + "void main() { \n" + " qt_TexCoord = qt_VertexTexCoord; \n" + " gl_Position = qt_Matrix * qt_VertexPosition; \n" + "}"; + +inline const char * const qtvideosink_glsl_bgrxFragmentShader() +{ + return + "uniform sampler2D rgbTexture;\n" + "uniform lowp float opacity;\n" + "uniform mediump mat4 colorMatrix;\n" + "varying highp vec2 qt_TexCoord;\n" + "void main(void)\n" + "{\n" + " highp vec4 color = vec4(texture2D(rgbTexture, qt_TexCoord.st).bgr, 1.0);\n" + " gl_FragColor = colorMatrix * color * opacity;\n" + "}\n"; +} + +inline const char * const qtvideosink_glsl_xrgbFragmentShader() +{ + return + "uniform sampler2D rgbTexture;\n" + "uniform lowp float opacity;\n" + "uniform mediump mat4 colorMatrix;\n" + "varying highp vec2 qt_TexCoord;\n" + "void main(void)\n" + "{\n" + " highp vec4 color = vec4(texture2D(rgbTexture, qt_TexCoord.st).gba, 1.0);\n" + " gl_FragColor = colorMatrix * color * opacity;\n" + "}\n"; +} + +inline const char * const qtvideosink_glsl_rgbxFragmentShader() +{ + return + "uniform sampler2D rgbTexture;\n" + "uniform lowp float opacity;\n" + "uniform mediump mat4 colorMatrix;\n" + "varying highp vec2 qt_TexCoord;\n" + "void main(void)\n" + "{\n" + " highp vec4 color = vec4(texture2D(rgbTexture, qt_TexCoord.st).rgb, 1.0);\n" + " gl_FragColor = colorMatrix * color * opacity;\n" + "}\n"; +} + +inline const char * const qtvideosink_glsl_yuvPlanarFragmentShader() +{ + return + "uniform sampler2D yTexture;\n" + "uniform sampler2D uTexture;\n" + "uniform sampler2D vTexture;\n" + "uniform mediump mat4 colorMatrix;\n" + "uniform lowp float opacity;\n" + "varying highp vec2 qt_TexCoord;\n" + "void main(void)\n" + "{\n" + " highp vec4 color = vec4(\n" + " texture2D(yTexture, qt_TexCoord.st).r,\n" + " texture2D(uTexture, qt_TexCoord.st).r,\n" + " texture2D(vTexture, qt_TexCoord.st).r,\n" + " 1.0);\n" + " gl_FragColor = colorMatrix * color * opacity;\n" + "}\n"; +} + +class VideoMaterialShader : public QSGMaterialShader +{ +public: + virtual void updateState(const RenderState &state, + QSGMaterial *newMaterial, QSGMaterial *oldMaterial) + { + Q_UNUSED(oldMaterial); + + VideoMaterial *material = static_cast<VideoMaterial *>(newMaterial); + if (m_id_rgbTexture) { + program()->setUniformValue(m_id_rgbTexture, 0); + } else { + program()->setUniformValue(m_id_yTexture, 0); + program()->setUniformValue(m_id_uTexture, 1); + program()->setUniformValue(m_id_vTexture, 2); + } + + if (state.isOpacityDirty()) { + material->setFlag(QSGMaterial::Blending, + qFuzzyCompare(state.opacity(), 1.0f) ? false : true); + program()->setUniformValue(m_id_opacity, GLfloat(state.opacity())); + } + + if (state.isMatrixDirty()) + program()->setUniformValue(m_id_matrix, state.combinedMatrix()); + + program()->setUniformValue(m_id_colorMatrix, material->m_colorMatrix); + + material->bind(); + } + + virtual char const *const *attributeNames() const { + static const char *names[] = { + "qt_VertexPosition", + "qt_VertexTexCoord", + 0 + }; + return names; + } + +protected: + virtual void initialize() { + m_id_matrix = program()->uniformLocation("qt_Matrix"); + m_id_rgbTexture = program()->uniformLocation("rgbTexture"); + m_id_yTexture = program()->uniformLocation("yTexture"); + m_id_uTexture = program()->uniformLocation("uTexture"); + m_id_vTexture = program()->uniformLocation("vTexture"); + m_id_colorMatrix = program()->uniformLocation("colorMatrix"); + m_id_opacity = program()->uniformLocation("opacity"); + } + + virtual const char *vertexShader() const { + return qtvideosink_glsl_vertexShader; + } + + int m_id_matrix; + int m_id_rgbTexture; + int m_id_yTexture; + int m_id_uTexture; + int m_id_vTexture; + int m_id_colorMatrix; + int m_id_opacity; +}; + +template <const char * const (*FragmentShader)()> +class VideoMaterialShaderImpl : public VideoMaterialShader +{ +protected: + virtual const char *fragmentShader() const { + return FragmentShader(); + } +}; + +template <const char * const (*FragmentShader)()> +class VideoMaterialImpl : public VideoMaterial +{ +public: + virtual QSGMaterialType *type() const { + static QSGMaterialType theType; + return &theType; + } + + virtual QSGMaterialShader *createShader() const { + return new VideoMaterialShaderImpl<FragmentShader>; + } +}; + +VideoMaterial *VideoMaterial::create(const BufferFormat & format) +{ + VideoMaterial *material = NULL; + + switch (format.videoFormat()) { + // BGRx + case GST_VIDEO_FORMAT_BGRx: + case GST_VIDEO_FORMAT_BGRA: + material = new VideoMaterialImpl<qtvideosink_glsl_bgrxFragmentShader>; + material->initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); + break; + case GST_VIDEO_FORMAT_BGR: + material = new VideoMaterialImpl<qtvideosink_glsl_bgrxFragmentShader>; + material->initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, format.frameSize()); + break; + + // xRGB + case GST_VIDEO_FORMAT_xRGB: + case GST_VIDEO_FORMAT_ARGB: + case GST_VIDEO_FORMAT_AYUV: + material = new VideoMaterialImpl<qtvideosink_glsl_xrgbFragmentShader>; + material->initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); + break; + + // RGBx + case GST_VIDEO_FORMAT_RGB: + case GST_VIDEO_FORMAT_v308: + material = new VideoMaterialImpl<qtvideosink_glsl_rgbxFragmentShader>; + material->initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, format.frameSize()); + break; + case GST_VIDEO_FORMAT_RGB16: + material = new VideoMaterialImpl<qtvideosink_glsl_rgbxFragmentShader>; + material->initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, format.frameSize()); + break; + + // YUV 420 planar + case GST_VIDEO_FORMAT_I420: + case GST_VIDEO_FORMAT_YV12: + material = new VideoMaterialImpl<qtvideosink_glsl_yuvPlanarFragmentShader>; + material->initYuv420PTextureInfo( + (format.videoFormat() == GST_VIDEO_FORMAT_YV12) /* uvSwapped */, + format.frameSize()); + break; + + default: + Q_ASSERT(false); + break; + } + + material->init(format.colorMatrix()); + return material; +} + +VideoMaterial::VideoMaterial() : + m_frame(0), + m_textureCount(0), + m_format(GST_VIDEO_FORMAT_UNKNOWN), + m_textureFormat(0), + m_textureInternalFormat(0), + m_textureType(0), + m_colorMatrixType(GST_VIDEO_COLOR_MATRIX_UNKNOWN) +{ + memset(m_textureIds, 0, sizeof(m_textureIds)); + setFlag(Blending, false); +} + +VideoMaterial::~VideoMaterial() +{ + if (!m_textureSize.isEmpty()) + glDeleteTextures(m_textureCount, m_textureIds); +} + +int VideoMaterial::compare(const QSGMaterial *other) const +{ + const VideoMaterial *m = static_cast<const VideoMaterial *>(other); + int d = m_textureIds[0] - m->m_textureIds[0]; + if (d || m_textureCount == 1) + return d; + else if ((d = m_textureIds[1] - m->m_textureIds[1]) != 0) + return d; + else + return m_textureIds[2] - m->m_textureIds[2]; +} + +void VideoMaterial::initRgbTextureInfo( + GLenum internalFormat, GLuint format, GLenum type, const QSize &size) +{ +#ifndef QT_OPENGL_ES + //make sure we get 8 bits per component, at least on the desktop GL where we can + switch(internalFormat) { + case GL_RGBA: + internalFormat = GL_RGBA8; + break; + case GL_RGB: + internalFormat = GL_RGB8; + break; + default: + break; + } +#endif + + m_textureInternalFormat = internalFormat; + m_textureFormat = format; + m_textureType = type; + m_textureCount = 1; + m_textureWidths[0] = size.width(); + m_textureHeights[0] = size.height(); + m_textureOffsets[0] = 0; +} + +void VideoMaterial::initYuv420PTextureInfo(bool uvSwapped, const QSize &size) +{ + int bytesPerLine = (size.width() + 3) & ~3; + int bytesPerLine2 = (size.width() / 2 + 3) & ~3; + + m_textureInternalFormat = GL_LUMINANCE; + m_textureFormat = GL_LUMINANCE; + m_textureType = GL_UNSIGNED_BYTE; + m_textureCount = 3; + m_textureWidths[0] = bytesPerLine; + m_textureHeights[0] = size.height(); + m_textureOffsets[0] = 0; + m_textureWidths[1] = bytesPerLine2; + m_textureHeights[1] = size.height() / 2; + m_textureOffsets[1] = bytesPerLine * size.height(); + m_textureWidths[2] = bytesPerLine2; + m_textureHeights[2] = size.height() / 2; + m_textureOffsets[2] = bytesPerLine * size.height() + bytesPerLine2 * size.height()/2; + + if (uvSwapped) + qSwap (m_textureOffsets[1], m_textureOffsets[2]); +} + +void VideoMaterial::init(GstVideoColorMatrix colorMatrixType) +{ + glGenTextures(m_textureCount, m_textureIds); + m_colorMatrixType = colorMatrixType; + updateColors(0, 0, 0, 0); +} + +void VideoMaterial::setCurrentFrame(GstBuffer *buffer) +{ + QMutexLocker lock(&m_frameMutex); + gst_buffer_replace(&m_frame, buffer); +} + +void VideoMaterial::updateColors(int brightness, int contrast, int hue, int saturation) +{ + const qreal b = brightness / 200.0; + const qreal c = contrast / 100.0 + 1.0; + const qreal h = hue / 100.0; + const qreal s = saturation / 100.0 + 1.0; + + const qreal cosH = qCos(M_PI * h); + const qreal sinH = qSin(M_PI * h); + + const qreal h11 = 0.787 * cosH - 0.213 * sinH + 0.213; + const qreal h21 = -0.213 * cosH + 0.143 * sinH + 0.213; + const qreal h31 = -0.213 * cosH - 0.787 * sinH + 0.213; + + const qreal h12 = -0.715 * cosH - 0.715 * sinH + 0.715; + const qreal h22 = 0.285 * cosH + 0.140 * sinH + 0.715; + const qreal h32 = -0.715 * cosH + 0.715 * sinH + 0.715; + + const qreal h13 = -0.072 * cosH + 0.928 * sinH + 0.072; + const qreal h23 = -0.072 * cosH - 0.283 * sinH + 0.072; + const qreal h33 = 0.928 * cosH + 0.072 * sinH + 0.072; + + const qreal sr = (1.0 - s) * 0.3086; + const qreal sg = (1.0 - s) * 0.6094; + const qreal sb = (1.0 - s) * 0.0820; + + const qreal sr_s = sr + s; + const qreal sg_s = sg + s; + const qreal sb_s = sr + s; + + const float m4 = (s + sr + sg + sb) * (0.5 - 0.5 * c + b); + + m_colorMatrix(0, 0) = c * (sr_s * h11 + sg * h21 + sb * h31); + m_colorMatrix(0, 1) = c * (sr_s * h12 + sg * h22 + sb * h32); + m_colorMatrix(0, 2) = c * (sr_s * h13 + sg * h23 + sb * h33); + m_colorMatrix(0, 3) = m4; + + m_colorMatrix(1, 0) = c * (sr * h11 + sg_s * h21 + sb * h31); + m_colorMatrix(1, 1) = c * (sr * h12 + sg_s * h22 + sb * h32); + m_colorMatrix(1, 2) = c * (sr * h13 + sg_s * h23 + sb * h33); + m_colorMatrix(1, 3) = m4; + + m_colorMatrix(2, 0) = c * (sr * h11 + sg * h21 + sb_s * h31); + m_colorMatrix(2, 1) = c * (sr * h12 + sg * h22 + sb_s * h32); + m_colorMatrix(2, 2) = c * (sr * h13 + sg * h23 + sb_s * h33); + m_colorMatrix(2, 3) = m4; + + m_colorMatrix(3, 0) = 0.0; + m_colorMatrix(3, 1) = 0.0; + m_colorMatrix(3, 2) = 0.0; + m_colorMatrix(3, 3) = 1.0; + + switch (m_colorMatrixType) { + case GST_VIDEO_COLOR_MATRIX_BT709: + m_colorMatrix *= QMatrix4x4( + 1.164, 0.000, 1.793, -0.5727, + 1.164, -0.534, -0.213, 0.3007, + 1.164, 2.115, 0.000, -1.1302, + 0.0, 0.000, 0.000, 1.0000); + break; + case GST_VIDEO_COLOR_MATRIX_BT601: + m_colorMatrix *= QMatrix4x4( + 1.164, 0.000, 1.596, -0.8708, + 1.164, -0.392, -0.813, 0.5296, + 1.164, 2.017, 0.000, -1.081, + 0.0, 0.000, 0.000, 1.0000); + break; + default: + break; + } +} + +void VideoMaterial::bind() +{ + QOpenGLFunctions *functions = QOpenGLContext::currentContext()->functions(); + GstBuffer *frame = NULL; + + m_frameMutex.lock(); + if (m_frame) + frame = gst_buffer_ref(m_frame); + m_frameMutex.unlock(); + + if (frame) { + const quint8 *data = GST_BUFFER_DATA (frame); + functions->glActiveTexture(GL_TEXTURE1); + bindTexture(1, data); + functions->glActiveTexture(GL_TEXTURE2); + bindTexture(2, data); + functions->glActiveTexture(GL_TEXTURE0); // Finish with 0 as default texture unit + bindTexture(0, data); + gst_buffer_unref(frame); + } else { + functions->glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, m_textureIds[1]); + functions->glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, m_textureIds[2]); + functions->glActiveTexture(GL_TEXTURE0); // Finish with 0 as default texture unit + glBindTexture(GL_TEXTURE_2D, m_textureIds[0]); + } +} + +void VideoMaterial::bindTexture(int i, const quint8 *data) +{ + glBindTexture(GL_TEXTURE_2D, m_textureIds[i]); + glTexImage2D( + GL_TEXTURE_2D, + 0, + m_textureInternalFormat, + m_textureWidths[i], + m_textureHeights[i], + 0, + m_textureFormat, + m_textureType, + data + m_textureOffsets[i]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +} + diff --git a/elements/gstqtvideosink/painters/videomaterial.h b/elements/gstqtvideosink/painters/videomaterial.h new file mode 100644 index 0000000..90a6379 --- /dev/null +++ b/elements/gstqtvideosink/painters/videomaterial.h @@ -0,0 +1,78 @@ +/* + Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). + Copyright (C) 2013 basysKom GmbH <info@basyskom.com> + Copyright (C) 2013 Collabora Ltd. <info@collabora.com> + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation. + + This program 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef VIDEOMATERIAL_H +#define VIDEOMATERIAL_H + +#include "../utils/bufferformat.h" +#include <QSize> +#include <QMutex> +#include <QMatrix4x4> + +#include <QtQuick/QSGMaterial> + +class VideoMaterialShader; + +class VideoMaterial : public QSGMaterial +{ +public: + static VideoMaterial *create(const BufferFormat & format); + + virtual ~VideoMaterial(); + + virtual int compare(const QSGMaterial *other) const; + + void setCurrentFrame(GstBuffer *buffer); + void updateColors(int brightness, int contrast, int hue, int saturation); + + void bind(); + +protected: + VideoMaterial(); + void initRgbTextureInfo(GLenum internalFormat, GLuint format, + GLenum type, const QSize &size); + void initYuv420PTextureInfo(bool uvSwapped, const QSize &size); + void init(GstVideoColorMatrix colorMatrixType); + +private: + void bindTexture(int i, const quint8 *data); + + + GstBuffer *m_frame; + QMutex m_frameMutex; + + static const int Num_Texture_IDs = 3; + int m_textureCount; + GLuint m_textureIds[Num_Texture_IDs]; + int m_textureWidths[Num_Texture_IDs]; + int m_textureHeights[Num_Texture_IDs]; + int m_textureOffsets[Num_Texture_IDs]; + QSize m_textureSize; + + GstVideoFormat m_format; + GLenum m_textureFormat; + GLuint m_textureInternalFormat; + GLenum m_textureType; + + QMatrix4x4 m_colorMatrix; + GstVideoColorMatrix m_colorMatrixType; + + friend class VideoMaterialShader; +}; + +#endif // VIDEOMATERIAL_H diff --git a/elements/gstqtvideosink/painters/videonode.cpp b/elements/gstqtvideosink/painters/videonode.cpp new file mode 100644 index 0000000..2afb3e5 --- /dev/null +++ b/elements/gstqtvideosink/painters/videonode.cpp @@ -0,0 +1,112 @@ +/* + Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). + Copyright (C) 2013 basysKom GmbH <info@basyskom.com> + Copyright (C) 2013 Collabora Ltd. <info@collabora.com> + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation. + + This program 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "videonode.h" +#include "videomaterial.h" + +#include <QtQuick/QSGFlatColorMaterial> + +VideoNode::VideoNode() + : QSGGeometryNode() +{ + setFlags(OwnsGeometry | OwnsMaterial, true); + setMaterialTypeSolidBlack(); +} + +void VideoNode::changeFormat(const BufferFormat & format) +{ + setMaterial(VideoMaterial::create(format)); + setGeometry(0); + m_materialType = MaterialTypeVideo; +} + +void VideoNode::setMaterialTypeSolidBlack() +{ + QSGFlatColorMaterial *m = new QSGFlatColorMaterial; + m->setColor(Qt::black); + setMaterial(m); + setGeometry(0); + m_materialType = MaterialTypeSolidBlack; +} + +void VideoNode::setCurrentFrame(GstBuffer* buffer) +{ + Q_ASSERT (m_materialType == MaterialTypeVideo); + static_cast<VideoMaterial*>(material())->setCurrentFrame(buffer); + markDirty(DirtyMaterial); +} + +void VideoNode::updateColors(int brightness, int contrast, int hue, int saturation) +{ + Q_ASSERT (m_materialType == MaterialTypeVideo); + static_cast<VideoMaterial*>(material())->updateColors(brightness, contrast, hue, saturation); + markDirty(DirtyMaterial); +} + +/* Helpers */ +template <typename V> +static inline void setGeom(V *v, const QPointF &p) +{ + v->x = p.x(); + v->y = p.y(); +} + +static inline void setTex(QSGGeometry::TexturedPoint2D *v, const QPointF &p) +{ + v->tx = p.x(); + v->ty = p.y(); +} + +void VideoNode::updateGeometry(const PaintAreas & areas) +{ + QSGGeometry *g = geometry(); + + if (m_materialType == MaterialTypeVideo) { + if (!g) + g = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4); + + QSGGeometry::TexturedPoint2D *v = g->vertexDataAsTexturedPoint2D(); + + // Set geometry first + setGeom(v + 0, areas.videoArea.topLeft()); + setGeom(v + 1, areas.videoArea.bottomLeft()); + setGeom(v + 2, areas.videoArea.topRight()); + setGeom(v + 3, areas.videoArea.bottomRight()); + + // and then texture coordinates + setTex(v + 0, areas.sourceRect.topLeft()); + setTex(v + 1, areas.sourceRect.bottomLeft()); + setTex(v + 2, areas.sourceRect.topRight()); + setTex(v + 3, areas.sourceRect.bottomRight()); + } else { + if (!g) + g = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 4); + + QSGGeometry::Point2D *v = g->vertexDataAsPoint2D(); + + setGeom(v + 0, areas.videoArea.topLeft()); + setGeom(v + 1, areas.videoArea.bottomLeft()); + setGeom(v + 2, areas.videoArea.topRight()); + setGeom(v + 3, areas.videoArea.bottomRight()); + } + + if (!geometry()) + setGeometry(g); + + markDirty(DirtyGeometry); +} diff --git a/elements/gstqtvideosink/painters/videonode.h b/elements/gstqtvideosink/painters/videonode.h new file mode 100644 index 0000000..ed9d36f --- /dev/null +++ b/elements/gstqtvideosink/painters/videonode.h @@ -0,0 +1,50 @@ +/* + Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). + Copyright (C) 2013 basysKom GmbH <info@basyskom.com> + Copyright (C) 2013 Collabora Ltd. <info@collabora.com> + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation. + + This program 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef VIDEONODE_H +#define VIDEONODE_H + +#include "../utils/bufferformat.h" + +#include <QtQuick/QSGGeometryNode> + +class VideoNode : public QSGGeometryNode +{ +public: + VideoNode(); + + enum MaterialType { + MaterialTypeVideo, + MaterialTypeSolidBlack + }; + + MaterialType materialType() const { return m_materialType; } + + void changeFormat(const BufferFormat &format); + void setMaterialTypeSolidBlack(); + + void setCurrentFrame(GstBuffer *buffer); + void updateColors(int brightness, int contrast, int hue, int saturation); + + void updateGeometry(const PaintAreas & areas); + +private: + MaterialType m_materialType; +}; + +#endif // VIDEONODE_H diff --git a/elements/gstqtvideosink/utils/utils.h b/elements/gstqtvideosink/utils/utils.h index 6e16c45..f1528e6 100644 --- a/elements/gstqtvideosink/utils/utils.h +++ b/elements/gstqtvideosink/utils/utils.h @@ -20,6 +20,14 @@ #include <QSize> #include <QMetaType> +// utilities for GST_DEBUG +#define QSIZE_FORMAT "(%d x %d)" +#define QSIZE_FORMAT_ARGS(size) \ + size.width(), size.height() +#define QRECTF_FORMAT "(x: %f, y: %f, w: %f, h: %f)" +#define QRECTF_FORMAT_ARGS(rect) \ + (float) rect.x(), (float) rect.y(), (float) rect.width(), (float) rect.height() + struct Fraction { inline Fraction() {} |