summaryrefslogtreecommitdiffstats
path: root/src/multimedia
diff options
context:
space:
mode:
Diffstat (limited to 'src/multimedia')
-rw-r--r--src/multimedia/CMakeLists.txt59
-rw-r--r--src/multimedia/gstreamer/gstreamer.pri64
-rw-r--r--src/multimedia/gstreamer/qgstappsrc.cpp236
-rw-r--r--src/multimedia/gstreamer/qgstappsrc_p.h121
-rw-r--r--src/multimedia/gstreamer/qgstcodecsinfo.cpp256
-rw-r--r--src/multimedia/gstreamer/qgstcodecsinfo_p.h96
-rw-r--r--src/multimedia/gstreamer/qgstreameraudioinputselector.cpp126
-rw-r--r--src/multimedia/gstreamer/qgstreameraudioinputselector_p.h86
-rw-r--r--src/multimedia/gstreamer/qgstreameraudioprobecontrol.cpp97
-rw-r--r--src/multimedia/gstreamer/qgstreameraudioprobecontrol_p.h90
-rw-r--r--src/multimedia/gstreamer/qgstreamerbufferprobe.cpp121
-rw-r--r--src/multimedia/gstreamer/qgstreamerbufferprobe_p.h92
-rw-r--r--src/multimedia/gstreamer/qgstreamerbushelper.cpp204
-rw-r--r--src/multimedia/gstreamer/qgstreamerbushelper_p.h104
-rw-r--r--src/multimedia/gstreamer/qgstreamermessage.cpp93
-rw-r--r--src/multimedia/gstreamer/qgstreamermessage_p.h84
-rw-r--r--src/multimedia/gstreamer/qgstreamerplayercontrol.cpp527
-rw-r--r--src/multimedia/gstreamer/qgstreamerplayercontrol_p.h136
-rw-r--r--src/multimedia/gstreamer/qgstreamerplayersession.cpp1774
-rw-r--r--src/multimedia/gstreamer/qgstreamerplayersession_p.h277
-rw-r--r--src/multimedia/gstreamer/qgstreamervideoinputdevicecontrol.cpp108
-rw-r--r--src/multimedia/gstreamer/qgstreamervideoinputdevicecontrol_p.h95
-rw-r--r--src/multimedia/gstreamer/qgstreamervideooverlay.cpp605
-rw-r--r--src/multimedia/gstreamer/qgstreamervideooverlay_p.h127
-rw-r--r--src/multimedia/gstreamer/qgstreamervideoprobecontrol.cpp117
-rw-r--r--src/multimedia/gstreamer/qgstreamervideoprobecontrol_p.h96
-rw-r--r--src/multimedia/gstreamer/qgstreamervideorenderer.cpp128
-rw-r--r--src/multimedia/gstreamer/qgstreamervideorenderer_p.h94
-rw-r--r--src/multimedia/gstreamer/qgstreamervideorendererinterface.cpp44
-rw-r--r--src/multimedia/gstreamer/qgstreamervideorendererinterface_p.h86
-rw-r--r--src/multimedia/gstreamer/qgstreamervideowindow.cpp179
-rw-r--r--src/multimedia/gstreamer/qgstreamervideowindow_p.h127
-rw-r--r--src/multimedia/gstreamer/qgstutils.cpp1150
-rw-r--r--src/multimedia/gstreamer/qgstutils_p.h157
-rw-r--r--src/multimedia/gstreamer/qgstvideobuffer.cpp119
-rw-r--r--src/multimedia/gstreamer/qgstvideobuffer_p.h89
-rw-r--r--src/multimedia/gstreamer/qgstvideorendererplugin.cpp59
-rw-r--r--src/multimedia/gstreamer/qgstvideorendererplugin_p.h112
-rw-r--r--src/multimedia/gstreamer/qgstvideorenderersink.cpp803
-rw-r--r--src/multimedia/gstreamer/qgstvideorenderersink_p.h199
-rw-r--r--src/multimedia/multimedia.pro1
41 files changed, 9138 insertions, 0 deletions
diff --git a/src/multimedia/CMakeLists.txt b/src/multimedia/CMakeLists.txt
index 114ac3b6e..a912453b1 100644
--- a/src/multimedia/CMakeLists.txt
+++ b/src/multimedia/CMakeLists.txt
@@ -243,3 +243,62 @@ qt_extend_target(Multimedia CONDITION QT_FEATURE_alsa
qt_add_docs(Multimedia
doc/qtmultimedia.qdocconf
)
+
+qt_extend_target(Multimedia CONDITION QT_FEATURE_gstreamer
+ SOURCES
+ gstreamer/qgstcodecsinfo.cpp qgstcodecsinfo_p.h
+ gstreamer/qgstreameraudioinputselector.cpp gstreamer/qgstreameraudioinputselector_p.h
+ gstreamer/qgstreameraudioprobecontrol.cpp gstreamer/qgstreameraudioprobecontrol_p.h
+ gstreamer/qgstreamerbufferprobe.cpp gstreamer/qgstreamerbufferprobe_p.h
+ gstreamer/qgstreamerbushelper.cpp gstreamer/qgstreamerbushelper_p.h
+ gstreamer/qgstreamermessage.cpp gstreamer/qgstreamermessage_p.h
+ gstreamer/qgstreamerplayercontrol.cpp gstreamer/qgstreamerplayercontrol_p.h
+ gstreamer/qgstreamerplayersession.cpp gstreamer/qgstreamerplayersession_p.h
+ gstreamer/qgstreamervideoinputdevicecontrol.cpp gstreamer/qgstreamervideoinputdevicecontrol_p.h
+ gstreamer/qgstreamervideooverlay.cpp gstreamer/qgstreamervideooverlay_p.h
+ gstreamer/qgstreamervideoprobecontrol.cpp gstreamer/qgstreamervideoprobecontrol_p.h
+ gstreamer/qgstreamervideorenderer.cpp gstreamer/qgstreamervideorenderer_p.h
+ gstreamer/qgstreamervideorendererinterface.cpp gstreamer/qgstreamervideorendererinterface_p.h
+ gstreamer/qgstreamervideowindow.cpp gstreamer/qgstreamervideowindow_p.h
+ gstreamer/qgstutils.cpp gstreamer/qgstutils_p.h
+ gstreamer/qgstvideobuffer.cpp gstreamer/qgstvideobuffer_p.h
+ gstreamer/qgstvideorendererplugin.cpp gstreamer/qgstvideorendererplugin_p.h
+ gstreamer/qgstvideorenderersink.cpp gstreamer/qgstvideorenderersink_p.h
+ DEFINES
+ GLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_26
+ PUBLIC_LIBRARIES
+ gstreamer
+)
+
+qt_extend_target(Multimedia CONDITION QT_FEATURE_gstreamer_gl
+ PUBLIC_LIBRARIES
+ gstreamer_gl
+)
+
+qt_extend_target(Multimedia CONDITION QT_FEATURE_gstreamer_app
+ SOURCES
+ gstreamer/qgstappsrc.cpp gstreamer/qgstappsrc_p.h # special case
+ PUBLIC_LIBRARIES
+ gstreamer_app
+)
+
+qt_extend_target(Multimedia CONDITION QT_FEATURE_gstreamer AND ANDROID
+ LIBRARIES
+ # Remove: L$ENV{GSTREAMER_ROOT_ANDROID}/armv7/lib
+ # Remove: Wl,--no-whole-archive
+ # Remove: Wl,--whole-archive
+ WrapIconv::WrapIconv
+ ffi
+ glib-2.0
+ gmodule-2.0
+ gobject-2.0
+ gstapp-1.0
+ gstaudio-1.0
+ gstbase-1.0
+ gstpbutils-1.0
+ gstreamer-1.0
+ gsttag-1.0
+ gstvideo-1.0
+ intl
+ orc-0.4
+)
diff --git a/src/multimedia/gstreamer/gstreamer.pri b/src/multimedia/gstreamer/gstreamer.pri
new file mode 100644
index 000000000..5a9ec0d67
--- /dev/null
+++ b/src/multimedia/gstreamer/gstreamer.pri
@@ -0,0 +1,64 @@
+DEFINES += GLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_26
+
+qtConfig(alsa): \
+ QMAKE_USE += alsa
+
+QMAKE_USE += gstreamer
+
+HEADERS += \
+ gstreamer/qgstreamerbushelper_p.h \
+ gstreamer/qgstreamermessage_p.h \
+ gstreamer/qgstutils_p.h \
+ gstreamer/qgstvideobuffer_p.h \
+ gstreamer/qgstreamerbufferprobe_p.h \
+ gstreamer/qgstreamervideorendererinterface_p.h \
+ gstreamer/qgstreameraudioinputselector_p.h \
+ gstreamer/qgstreamervideorenderer_p.h \
+ gstreamer/qgstreamervideoinputdevicecontrol_p.h \
+ gstreamer/qgstcodecsinfo_p.h \
+ gstreamer/qgstreamervideoprobecontrol_p.h \
+ gstreamer/qgstreameraudioprobecontrol_p.h \
+ gstreamer/qgstreamervideowindow_p.h \
+ gstreamer/qgstreamervideooverlay_p.h \
+ gstreamer/qgstreamerplayersession_p.h \
+ gstreamer/qgstreamerplayercontrol_p.h \
+ gstreamer/qgstvideorendererplugin_p.h \
+ gstreamer/qgstvideorenderersink_p.h
+
+SOURCES += \
+ gstreamer/qgstreamerbushelper.cpp \
+ gstreamer/qgstreamermessage.cpp \
+ gstreamer/qgstutils.cpp \
+ gstreamer/qgstvideobuffer.cpp \
+ gstreamer/qgstreamerbufferprobe.cpp \
+ gstreamer/qgstreamervideorendererinterface.cpp \
+ gstreamer/qgstreameraudioinputselector.cpp \
+ gstreamer/qgstreamervideorenderer.cpp \
+ gstreamer/qgstreamervideoinputdevicecontrol.cpp \
+ gstreamer/qgstcodecsinfo.cpp \
+ gstreamer/qgstreamervideoprobecontrol.cpp \
+ gstreamer/qgstreameraudioprobecontrol.cpp \
+ gstreamer/qgstreamervideowindow.cpp \
+ gstreamer/qgstreamervideooverlay.cpp \
+ gstreamer/qgstreamerplayersession.cpp \
+ gstreamer/qgstreamerplayercontrol.cpp \
+ gstreamer/qgstvideorendererplugin.cpp \
+ gstreamer/qgstvideorenderersink.cpp
+
+qtConfig(gstreamer_gl): QMAKE_USE += gstreamer_gl
+
+qtConfig(gstreamer_app) {
+ QMAKE_USE += gstreamer_app
+ PRIVATE_HEADERS += gstreamer/qgstappsrc_p.h
+ SOURCES += gstreamer/qgstappsrc.cpp
+}
+
+android {
+ LIBS_PRIVATE += \
+ -L$$(GSTREAMER_ROOT_ANDROID)/armv7/lib \
+ -Wl,--whole-archive \
+ -lgstapp-1.0 -lgstreamer-1.0 -lgstaudio-1.0 -lgsttag-1.0 -lgstvideo-1.0 -lgstbase-1.0 -lgstpbutils-1.0 \
+ -lgobject-2.0 -lgmodule-2.0 -lglib-2.0 -lffi -lintl -liconv -lorc-0.4 \
+ -Wl,--no-whole-archive
+}
+
diff --git a/src/multimedia/gstreamer/qgstappsrc.cpp b/src/multimedia/gstreamer/qgstappsrc.cpp
new file mode 100644
index 000000000..e8ec63bb9
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstappsrc.cpp
@@ -0,0 +1,236 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QDebug>
+
+#include "qgstappsrc_p.h"
+
+QGstAppSrc::QGstAppSrc(QObject *parent)
+ : QObject(parent)
+{
+ m_callbacks.need_data = &QGstAppSrc::on_need_data;
+ m_callbacks.enough_data = &QGstAppSrc::on_enough_data;
+ m_callbacks.seek_data = &QGstAppSrc::on_seek_data;
+}
+
+QGstAppSrc::~QGstAppSrc()
+{
+ if (m_appSrc)
+ gst_object_unref(G_OBJECT(m_appSrc));
+}
+
+bool QGstAppSrc::setup(GstElement* appsrc)
+{
+ if (m_appSrc) {
+ gst_object_unref(G_OBJECT(m_appSrc));
+ m_appSrc = 0;
+ }
+
+ if (!appsrc || !m_stream)
+ return false;
+
+ m_appSrc = GST_APP_SRC(appsrc);
+ gst_object_ref(G_OBJECT(m_appSrc));
+ gst_app_src_set_callbacks(m_appSrc, (GstAppSrcCallbacks*)&m_callbacks, this, (GDestroyNotify)&QGstAppSrc::destroy_notify);
+
+ g_object_get(G_OBJECT(m_appSrc), "max-bytes", &m_maxBytes, nullptr);
+
+ if (m_sequential)
+ m_streamType = GST_APP_STREAM_TYPE_STREAM;
+ else
+ m_streamType = GST_APP_STREAM_TYPE_RANDOM_ACCESS;
+ gst_app_src_set_stream_type(m_appSrc, m_streamType);
+ gst_app_src_set_size(m_appSrc, (m_sequential) ? -1 : m_stream->size());
+
+ return true;
+}
+
+void QGstAppSrc::setStream(QIODevice *stream)
+{
+ if (m_stream) {
+ disconnect(m_stream, SIGNAL(readyRead()), this, SLOT(onDataReady()));
+ disconnect(m_stream, SIGNAL(destroyed()), this, SLOT(streamDestroyed()));
+ m_stream = 0;
+ }
+
+ if (m_appSrc) {
+ gst_object_unref(G_OBJECT(m_appSrc));
+ m_appSrc = 0;
+ }
+
+ m_dataRequestSize = ~0;
+ m_dataRequested = false;
+ m_enoughData = false;
+ m_forceData = false;
+ m_sequential = false;
+ m_maxBytes = 0;
+
+ if (stream) {
+ m_stream = stream;
+ connect(m_stream, SIGNAL(destroyed()), SLOT(streamDestroyed()));
+ connect(m_stream, SIGNAL(readyRead()), this, SLOT(onDataReady()));
+ m_sequential = m_stream->isSequential();
+ }
+}
+
+QIODevice *QGstAppSrc::stream() const
+{
+ return m_stream;
+}
+
+GstAppSrc *QGstAppSrc::element()
+{
+ return m_appSrc;
+}
+
+void QGstAppSrc::onDataReady()
+{
+ if (!m_enoughData) {
+ m_dataRequested = true;
+ pushDataToAppSrc();
+ }
+}
+
+void QGstAppSrc::streamDestroyed()
+{
+ if (sender() == m_stream) {
+ m_stream = 0;
+ sendEOS();
+ }
+}
+
+void QGstAppSrc::pushDataToAppSrc()
+{
+ if (!isStreamValid() || !m_appSrc)
+ return;
+
+ if (m_dataRequested && !m_enoughData) {
+ qint64 size;
+ if (m_dataRequestSize == ~0u)
+ size = qMin(m_stream->bytesAvailable(), queueSize());
+ else
+ size = qMin(m_stream->bytesAvailable(), (qint64)m_dataRequestSize);
+
+ if (size) {
+ GstBuffer* buffer = gst_buffer_new_and_alloc(size);
+
+ GstMapInfo mapInfo;
+ gst_buffer_map(buffer, &mapInfo, GST_MAP_WRITE);
+ void* bufferData = mapInfo.data;
+
+ buffer->offset = m_stream->pos();
+ qint64 bytesRead = m_stream->read((char*)bufferData, size);
+ buffer->offset_end = buffer->offset + bytesRead - 1;
+
+ gst_buffer_unmap(buffer, &mapInfo);
+
+ if (bytesRead > 0) {
+ m_dataRequested = false;
+ m_enoughData = false;
+ GstFlowReturn ret = gst_app_src_push_buffer (GST_APP_SRC (element()), buffer);
+ if (ret == GST_FLOW_ERROR) {
+ qWarning()<<"appsrc: push buffer error";
+ } else if (ret == GST_FLOW_FLUSHING) {
+ qWarning()<<"appsrc: push buffer wrong state";
+ }
+ }
+ } else if (!m_sequential) {
+ sendEOS();
+ }
+ } else if (m_stream->atEnd() && !m_sequential) {
+ sendEOS();
+ }
+}
+
+bool QGstAppSrc::doSeek(qint64 value)
+{
+ if (isStreamValid())
+ return stream()->seek(value);
+ return false;
+}
+
+
+gboolean QGstAppSrc::on_seek_data(GstAppSrc *element, guint64 arg0, gpointer userdata)
+{
+ Q_UNUSED(element);
+ QGstAppSrc *self = reinterpret_cast<QGstAppSrc*>(userdata);
+ if (self && self->isStreamValid()) {
+ if (!self->stream()->isSequential())
+ QMetaObject::invokeMethod(self, "doSeek", Qt::AutoConnection, Q_ARG(qint64, arg0));
+ }
+ else
+ return false;
+
+ return true;
+}
+
+void QGstAppSrc::on_enough_data(GstAppSrc *element, gpointer userdata)
+{
+ Q_UNUSED(element);
+ QGstAppSrc *self = reinterpret_cast<QGstAppSrc*>(userdata);
+ if (self)
+ self->enoughData() = true;
+}
+
+void QGstAppSrc::on_need_data(GstAppSrc *element, guint arg0, gpointer userdata)
+{
+ Q_UNUSED(element);
+ QGstAppSrc *self = reinterpret_cast<QGstAppSrc*>(userdata);
+ if (self) {
+ self->dataRequested() = true;
+ self->enoughData() = false;
+ self->dataRequestSize()= arg0;
+ QMetaObject::invokeMethod(self, "pushDataToAppSrc", Qt::AutoConnection);
+ }
+}
+
+void QGstAppSrc::destroy_notify(gpointer data)
+{
+ Q_UNUSED(data);
+}
+
+void QGstAppSrc::sendEOS()
+{
+ if (!m_appSrc)
+ return;
+
+ gst_app_src_end_of_stream(GST_APP_SRC(m_appSrc));
+ if (isStreamValid() && !stream()->isSequential())
+ stream()->reset();
+}
diff --git a/src/multimedia/gstreamer/qgstappsrc_p.h b/src/multimedia/gstreamer/qgstappsrc_p.h
new file mode 100644
index 000000000..9c443c0c4
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstappsrc_p.h
@@ -0,0 +1,121 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QGSTAPPSRC_H
+#define QGSTAPPSRC_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtmultimediaglobal_p.h>
+#include <QtCore/qobject.h>
+#include <QtCore/qiodevice.h>
+
+#include <gst/gst.h>
+#include <gst/app/gstappsrc.h>
+
+#if GST_VERSION_MAJOR < 1
+#include <gst/app/gstappbuffer.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+class Q_MULTIMEDIA_EXPORT QGstAppSrc : public QObject
+{
+ Q_OBJECT
+public:
+ QGstAppSrc(QObject *parent = 0);
+ ~QGstAppSrc();
+
+ bool setup(GstElement *);
+
+ void setStream(QIODevice *);
+ QIODevice *stream() const;
+
+ GstAppSrc *element();
+
+ qint64 queueSize() const { return m_maxBytes; }
+
+ bool& enoughData() { return m_enoughData; }
+ bool& dataRequested() { return m_dataRequested; }
+ unsigned int& dataRequestSize() { return m_dataRequestSize; }
+
+ bool isStreamValid() const
+ {
+ return m_stream != 0 &&
+ m_stream->isOpen();
+ }
+
+private slots:
+ void pushDataToAppSrc();
+ bool doSeek(qint64);
+ void onDataReady();
+
+ void streamDestroyed();
+private:
+ static gboolean on_seek_data(GstAppSrc *element, guint64 arg0, gpointer userdata);
+ static void on_enough_data(GstAppSrc *element, gpointer userdata);
+ static void on_need_data(GstAppSrc *element, uint arg0, gpointer userdata);
+ static void destroy_notify(gpointer data);
+
+ void sendEOS();
+
+ QIODevice *m_stream = nullptr;
+ GstAppSrc *m_appSrc = nullptr;
+ bool m_sequential = false;
+ GstAppStreamType m_streamType = GST_APP_STREAM_TYPE_RANDOM_ACCESS;
+ GstAppSrcCallbacks m_callbacks;
+ qint64 m_maxBytes = 0;
+ unsigned int m_dataRequestSize = ~0;
+ bool m_dataRequested = false;
+ bool m_enoughData = false;
+ bool m_forceData = false;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/gstreamer/qgstcodecsinfo.cpp b/src/multimedia/gstreamer/qgstcodecsinfo.cpp
new file mode 100644
index 000000000..bbf78124d
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstcodecsinfo.cpp
@@ -0,0 +1,256 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qgstcodecsinfo_p.h"
+#include "qgstutils_p.h"
+#include <QtCore/qset.h>
+
+#include <gst/pbutils/pbutils.h>
+
+static QSet<QString> streamTypes(GstElementFactory *factory, GstPadDirection direction)
+{
+ QSet<QString> types;
+ const GList *pads = gst_element_factory_get_static_pad_templates(factory);
+ for (const GList *pad = pads; pad; pad = g_list_next(pad)) {
+ GstStaticPadTemplate *templ = reinterpret_cast<GstStaticPadTemplate *>(pad->data);
+ if (templ->direction == direction) {
+ GstCaps *caps = gst_static_caps_get(&templ->static_caps);
+ for (uint i = 0; i < gst_caps_get_size(caps); ++i) {
+ GstStructure *structure = gst_caps_get_structure(caps, i);
+ types.insert(QString::fromUtf8(gst_structure_get_name(structure)));
+ }
+ gst_caps_unref(caps);
+ }
+ }
+
+ return types;
+}
+
+QGstCodecsInfo::QGstCodecsInfo(QGstCodecsInfo::ElementType elementType)
+{
+ updateCodecs(elementType);
+ for (auto &codec : supportedCodecs()) {
+ GstElementFactory *factory = gst_element_factory_find(codecElement(codec).constData());
+ if (factory) {
+ GstPadDirection direction = elementType == Muxer ? GST_PAD_SINK : GST_PAD_SRC;
+ m_streamTypes.insert(codec, streamTypes(factory, direction));
+ gst_object_unref(GST_OBJECT(factory));
+ }
+ }
+}
+
+QStringList QGstCodecsInfo::supportedCodecs() const
+{
+ return m_codecs;
+}
+
+QString QGstCodecsInfo::codecDescription(const QString &codec) const
+{
+ return m_codecInfo.value(codec).description;
+}
+
+QByteArray QGstCodecsInfo::codecElement(const QString &codec) const
+
+{
+ return m_codecInfo.value(codec).elementName;
+}
+
+QStringList QGstCodecsInfo::codecOptions(const QString &codec) const
+{
+ QStringList options;
+
+ QByteArray elementName = m_codecInfo.value(codec).elementName;
+ if (elementName.isEmpty())
+ return options;
+
+ GstElement *element = gst_element_factory_make(elementName, nullptr);
+ if (element) {
+ guint numProperties;
+ GParamSpec **properties = g_object_class_list_properties(G_OBJECT_GET_CLASS(element),
+ &numProperties);
+ for (guint j = 0; j < numProperties; ++j) {
+ GParamSpec *property = properties[j];
+ // ignore some properties
+ if (strcmp(property->name, "name") == 0 || strcmp(property->name, "parent") == 0)
+ continue;
+
+ options.append(QLatin1String(property->name));
+ }
+ g_free(properties);
+ gst_object_unref(element);
+ }
+
+ return options;
+}
+
+void QGstCodecsInfo::updateCodecs(ElementType elementType)
+{
+ m_codecs.clear();
+ m_codecInfo.clear();
+
+ GList *elements = elementFactories(elementType);
+
+ QSet<QByteArray> fakeEncoderMimeTypes;
+ fakeEncoderMimeTypes << "unknown/unknown"
+ << "audio/x-raw-int" << "audio/x-raw-float"
+ << "video/x-raw-yuv" << "video/x-raw-rgb";
+
+ QSet<QByteArray> fieldsToAdd;
+ fieldsToAdd << "mpegversion" << "layer" << "layout" << "raversion"
+ << "wmaversion" << "wmvversion" << "variant" << "systemstream";
+
+ GList *element = elements;
+ while (element) {
+ GstElementFactory *factory = (GstElementFactory *)element->data;
+ element = element->next;
+
+ const GList *padTemplates = gst_element_factory_get_static_pad_templates(factory);
+ while (padTemplates) {
+ GstStaticPadTemplate *padTemplate = (GstStaticPadTemplate *)padTemplates->data;
+ padTemplates = padTemplates->next;
+
+ if (padTemplate->direction == GST_PAD_SRC) {
+ GstCaps *caps = gst_static_caps_get(&padTemplate->static_caps);
+ for (uint i=0; i<gst_caps_get_size(caps); i++) {
+ const GstStructure *structure = gst_caps_get_structure(caps, i);
+
+ //skip "fake" encoders
+ if (fakeEncoderMimeTypes.contains(gst_structure_get_name(structure)))
+ continue;
+
+ GstStructure *newStructure = qt_gst_structure_new_empty(gst_structure_get_name(structure));
+
+ //add structure fields to distinguish between formats with similar mime types,
+ //like audio/mpeg
+ for (int j=0; j<gst_structure_n_fields(structure); j++) {
+ const gchar* fieldName = gst_structure_nth_field_name(structure, j);
+ if (fieldsToAdd.contains(fieldName)) {
+ const GValue *value = gst_structure_get_value(structure, fieldName);
+ GType valueType = G_VALUE_TYPE(value);
+
+ //don't add values of range type,
+ //gst_pb_utils_get_codec_description complains about not fixed caps
+
+ if (valueType != GST_TYPE_INT_RANGE && valueType != GST_TYPE_DOUBLE_RANGE &&
+ valueType != GST_TYPE_FRACTION_RANGE && valueType != GST_TYPE_LIST &&
+ valueType != GST_TYPE_ARRAY)
+ gst_structure_set_value(newStructure, fieldName, value);
+ }
+ }
+
+ GstCaps *newCaps = gst_caps_new_full(newStructure, nullptr);
+
+ gchar *capsString = gst_caps_to_string(newCaps);
+ QString codec = QLatin1String(capsString);
+ if (capsString)
+ g_free(capsString);
+ GstRank rank = GstRank(gst_plugin_feature_get_rank(GST_PLUGIN_FEATURE(factory)));
+
+ // If two elements provide the same codec, use the highest ranked one
+ QMap<QString, CodecInfo>::const_iterator it = m_codecInfo.constFind(codec);
+ if (it == m_codecInfo.constEnd() || it->rank < rank) {
+ if (it == m_codecInfo.constEnd())
+ m_codecs.append(codec);
+
+ CodecInfo info;
+ info.elementName = gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(factory));
+
+ gchar *description = gst_pb_utils_get_codec_description(newCaps);
+ info.description = QString::fromUtf8(description);
+ if (description)
+ g_free(description);
+
+ info.rank = rank;
+
+ m_codecInfo.insert(codec, info);
+ }
+
+ gst_caps_unref(newCaps);
+ }
+ gst_caps_unref(caps);
+ }
+ }
+ }
+
+ gst_plugin_feature_list_free(elements);
+}
+
+GList *QGstCodecsInfo::elementFactories(ElementType elementType) const
+{
+ GstElementFactoryListType gstElementType = 0;
+ switch (elementType) {
+ case AudioEncoder:
+ gstElementType = GST_ELEMENT_FACTORY_TYPE_AUDIO_ENCODER;
+ break;
+ case VideoEncoder:
+ gstElementType = GST_ELEMENT_FACTORY_TYPE_VIDEO_ENCODER;
+ break;
+ case Muxer:
+ gstElementType = GST_ELEMENT_FACTORY_TYPE_MUXER;
+ break;
+ }
+
+ GList *list = gst_element_factory_list_get_elements(gstElementType, GST_RANK_MARGINAL);
+ if (elementType == AudioEncoder) {
+ // Manually add "audioconvert" to the list
+ // to allow linking with various containers.
+ auto factory = gst_element_factory_find("audioconvert");
+ if (factory)
+ list = g_list_prepend(list, factory);
+ }
+
+ return list;
+}
+
+QSet<QString> QGstCodecsInfo::supportedStreamTypes(const QString &codec) const
+{
+ return m_streamTypes.value(codec);
+}
+
+QStringList QGstCodecsInfo::supportedCodecs(const QSet<QString> &types) const
+{
+ QStringList result;
+ for (auto &candidate : supportedCodecs()) {
+ auto candidateTypes = supportedStreamTypes(candidate);
+ if (candidateTypes.intersects(types))
+ result << candidate;
+ }
+
+ return result;
+}
diff --git a/src/multimedia/gstreamer/qgstcodecsinfo_p.h b/src/multimedia/gstreamer/qgstcodecsinfo_p.h
new file mode 100644
index 000000000..7f4415e69
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstcodecsinfo_p.h
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QGSTCODECSINFO_H
+#define QGSTCODECSINFO_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtmultimediaglobal_p.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qstringlist.h>
+#include <QSet>
+
+#include <gst/gst.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_MULTIMEDIA_EXPORT QGstCodecsInfo
+{
+public:
+ enum ElementType { AudioEncoder, VideoEncoder, Muxer };
+
+ struct CodecInfo {
+ QString description;
+ QByteArray elementName;
+ GstRank rank;
+ };
+
+ QGstCodecsInfo(ElementType elementType);
+
+ QStringList supportedCodecs() const;
+ QString codecDescription(const QString &codec) const;
+ QByteArray codecElement(const QString &codec) const;
+ QStringList codecOptions(const QString &codec) const;
+ QSet<QString> supportedStreamTypes(const QString &codec) const;
+ QStringList supportedCodecs(const QSet<QString> &types) const;
+
+private:
+ void updateCodecs(ElementType elementType);
+ GList *elementFactories(ElementType elementType) const;
+
+ QStringList m_codecs;
+ QMap<QString, CodecInfo> m_codecInfo;
+ QMap<QString, QSet<QString>> m_streamTypes;
+};
+
+Q_DECLARE_TYPEINFO(QGstCodecsInfo::CodecInfo, Q_MOVABLE_TYPE);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/gstreamer/qgstreameraudioinputselector.cpp b/src/multimedia/gstreamer/qgstreameraudioinputselector.cpp
new file mode 100644
index 000000000..dede1f661
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreameraudioinputselector.cpp
@@ -0,0 +1,126 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtMultimedia/private/qtmultimediaglobal_p.h>
+#include "qgstreameraudioinputselector_p.h"
+
+#include <QtCore/QDir>
+#include <QtCore/QDebug>
+
+#include <gst/gst.h>
+
+#include "qgstutils_p.h"
+
+QGstreamerAudioInputSelector::QGstreamerAudioInputSelector(QObject *parent)
+ :QAudioInputSelectorControl(parent)
+{
+ update();
+}
+
+QGstreamerAudioInputSelector::~QGstreamerAudioInputSelector()
+{
+}
+
+QList<QString> QGstreamerAudioInputSelector::availableInputs() const
+{
+ return m_names;
+}
+
+QString QGstreamerAudioInputSelector::inputDescription(const QString& name) const
+{
+ QString desc;
+
+ for (int i = 0; i < m_names.size(); i++) {
+ if (m_names.at(i).compare(name) == 0) {
+ desc = m_descriptions.at(i);
+ break;
+ }
+ }
+ return desc;
+}
+
+QString QGstreamerAudioInputSelector::defaultInput() const
+{
+ return m_defaultInput;
+}
+
+QString QGstreamerAudioInputSelector::activeInput() const
+{
+ return m_audioInput;
+}
+
+void QGstreamerAudioInputSelector::setActiveInput(const QString& name)
+{
+ if (m_audioInput.compare(name) != 0) {
+ m_audioInput = name;
+ emit activeInputChanged(name);
+ }
+}
+
+void QGstreamerAudioInputSelector::update()
+{
+ QGstUtils::initializeGst();
+
+ m_names.clear();
+ m_descriptions.clear();
+
+ const auto sources = QGstUtils::audioSources();
+ for (auto *d : sources) {
+ auto *properties = gst_device_get_properties(d);
+ if (properties) {
+ auto *desc = gst_device_get_display_name(d);
+ QString description = QString::fromUtf8(desc);
+ g_free(desc);
+ if (description.contains(u"Monitor")) // ### is there a better way to skip those?
+ continue;
+ m_descriptions << description;
+
+ auto *name = gst_structure_get_string(properties, "sysfs.path");
+ m_names << QString::fromLatin1(name);
+ gboolean def;
+ if (gst_structure_get_boolean(properties, "is-default", &def) && def)
+ m_defaultInput = QString::fromLatin1(name);
+
+ gst_structure_free(properties);
+ }
+ }
+
+ if (m_names.size() > 0)
+ m_audioInput = m_names.at(0);
+}
diff --git a/src/multimedia/gstreamer/qgstreameraudioinputselector_p.h b/src/multimedia/gstreamer/qgstreameraudioinputselector_p.h
new file mode 100644
index 000000000..3b258ba3a
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreameraudioinputselector_p.h
@@ -0,0 +1,86 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QGSTREAMERAUDIOINPUTSELECTOR_H
+#define QGSTREAMERAUDIOINPUTSELECTOR_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtmultimediaglobal_p.h>
+#include <qaudioinputselectorcontrol.h>
+#include <QtCore/qstringlist.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_MULTIMEDIA_EXPORT QGstreamerAudioInputSelector : public QAudioInputSelectorControl
+{
+Q_OBJECT
+public:
+ QGstreamerAudioInputSelector(QObject *parent);
+ ~QGstreamerAudioInputSelector();
+
+ QList<QString> availableInputs() const override;
+ QString inputDescription(const QString &name) const override;
+ QString defaultInput() const override;
+ QString activeInput() const override;
+
+public Q_SLOTS:
+ void setActiveInput(const QString &name) override;
+
+private:
+ void update();
+
+ QString m_defaultInput;
+ QString m_audioInput;
+ QList<QString> m_names;
+ QList<QString> m_descriptions;
+};
+
+QT_END_NAMESPACE
+
+#endif // QGSTREAMERAUDIOINPUTSELECTOR_H
diff --git a/src/multimedia/gstreamer/qgstreameraudioprobecontrol.cpp b/src/multimedia/gstreamer/qgstreameraudioprobecontrol.cpp
new file mode 100644
index 000000000..8b0415bde
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreameraudioprobecontrol.cpp
@@ -0,0 +1,97 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qgstreameraudioprobecontrol_p.h"
+#include <private/qgstutils_p.h>
+
+QGstreamerAudioProbeControl::QGstreamerAudioProbeControl(QObject *parent)
+ : QMediaAudioProbeControl(parent)
+{
+}
+
+QGstreamerAudioProbeControl::~QGstreamerAudioProbeControl()
+{
+}
+
+void QGstreamerAudioProbeControl::probeCaps(GstCaps *caps)
+{
+ QAudioFormat format = QGstUtils::audioFormatForCaps(caps);
+
+ QMutexLocker locker(&m_bufferMutex);
+ m_format = format;
+}
+
+bool QGstreamerAudioProbeControl::probeBuffer(GstBuffer *buffer)
+{
+ qint64 position = GST_BUFFER_TIMESTAMP(buffer);
+ position = position >= 0
+ ? position / G_GINT64_CONSTANT(1000) // microseconds
+ : -1;
+
+ QByteArray data;
+ GstMapInfo info;
+ if (gst_buffer_map(buffer, &info, GST_MAP_READ)) {
+ data = QByteArray(reinterpret_cast<const char *>(info.data), info.size);
+ gst_buffer_unmap(buffer, &info);
+ } else {
+ return true;
+ }
+
+ QMutexLocker locker(&m_bufferMutex);
+ if (m_format.isValid()) {
+ if (!m_pendingBuffer.isValid())
+ QMetaObject::invokeMethod(this, "bufferProbed", Qt::QueuedConnection);
+ m_pendingBuffer = QAudioBuffer(data, m_format, position);
+ }
+
+ return true;
+}
+
+void QGstreamerAudioProbeControl::bufferProbed()
+{
+ QAudioBuffer audioBuffer;
+ {
+ QMutexLocker locker(&m_bufferMutex);
+ if (!m_pendingBuffer.isValid())
+ return;
+ audioBuffer = m_pendingBuffer;
+ m_pendingBuffer = QAudioBuffer();
+ }
+ emit audioBufferProbed(audioBuffer);
+}
diff --git a/src/multimedia/gstreamer/qgstreameraudioprobecontrol_p.h b/src/multimedia/gstreamer/qgstreameraudioprobecontrol_p.h
new file mode 100644
index 000000000..b641929cd
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreameraudioprobecontrol_p.h
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QGSTREAMERAUDIOPROBECONTROL_H
+#define QGSTREAMERAUDIOPROBECONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtmultimediaglobal_p.h>
+#include <gst/gst.h>
+#include <qmediaaudioprobecontrol.h>
+#include <QtCore/qmutex.h>
+#include <qaudiobuffer.h>
+#include <qshareddata.h>
+
+#include <private/qgstreamerbufferprobe_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_MULTIMEDIA_EXPORT QGstreamerAudioProbeControl
+ : public QMediaAudioProbeControl
+ , public QGstreamerBufferProbe
+ , public QSharedData
+{
+ Q_OBJECT
+public:
+ explicit QGstreamerAudioProbeControl(QObject *parent);
+ virtual ~QGstreamerAudioProbeControl();
+
+protected:
+ void probeCaps(GstCaps *caps) override;
+ bool probeBuffer(GstBuffer *buffer) override;
+
+private slots:
+ void bufferProbed();
+
+private:
+ QAudioBuffer m_pendingBuffer;
+ QAudioFormat m_format;
+ QMutex m_bufferMutex;
+};
+
+QT_END_NAMESPACE
+
+#endif // QGSTREAMERAUDIOPROBECONTROL_H
diff --git a/src/multimedia/gstreamer/qgstreamerbufferprobe.cpp b/src/multimedia/gstreamer/qgstreamerbufferprobe.cpp
new file mode 100644
index 000000000..230807466
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamerbufferprobe.cpp
@@ -0,0 +1,121 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 Jolla Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qgstreamerbufferprobe_p.h"
+#include "qgstutils_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QGstreamerBufferProbe::QGstreamerBufferProbe(Flags flags)
+ : m_flags(flags)
+{
+}
+
+QGstreamerBufferProbe::~QGstreamerBufferProbe()
+{
+}
+
+void QGstreamerBufferProbe::addProbeToPad(GstPad *pad, bool downstream)
+{
+ if (GstCaps *caps = qt_gst_pad_get_current_caps(pad)) {
+ probeCaps(caps);
+ gst_caps_unref(caps);
+ }
+ if (m_flags & ProbeCaps) {
+ m_capsProbeId = gst_pad_add_probe(
+ pad,
+ downstream
+ ? GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM
+ : GST_PAD_PROBE_TYPE_EVENT_UPSTREAM,
+ capsProbe,
+ this,
+ nullptr);
+ }
+ if (m_flags & ProbeBuffers) {
+ m_bufferProbeId = gst_pad_add_probe(
+ pad, GST_PAD_PROBE_TYPE_BUFFER, bufferProbe, this, nullptr);
+ }
+}
+
+void QGstreamerBufferProbe::removeProbeFromPad(GstPad *pad)
+{
+ if (m_capsProbeId != -1) {
+ gst_pad_remove_probe(pad, m_capsProbeId);
+ m_capsProbeId = -1;
+ }
+ if (m_bufferProbeId != -1) {
+ gst_pad_remove_probe(pad, m_bufferProbeId);
+ m_bufferProbeId = -1;
+ }
+}
+
+void QGstreamerBufferProbe::probeCaps(GstCaps *)
+{
+}
+
+bool QGstreamerBufferProbe::probeBuffer(GstBuffer *)
+{
+ return true;
+}
+
+GstPadProbeReturn QGstreamerBufferProbe::capsProbe(GstPad *, GstPadProbeInfo *info, gpointer user_data)
+{
+ QGstreamerBufferProbe * const control = static_cast<QGstreamerBufferProbe *>(user_data);
+
+ if (GstEvent * const event = gst_pad_probe_info_get_event(info)) {
+ if (GST_EVENT_TYPE(event) == GST_EVENT_CAPS) {
+ GstCaps *caps;
+ gst_event_parse_caps(event, &caps);
+
+ control->probeCaps(caps);
+ }
+ }
+ return GST_PAD_PROBE_OK;
+}
+
+GstPadProbeReturn QGstreamerBufferProbe::bufferProbe(
+ GstPad *, GstPadProbeInfo *info, gpointer user_data)
+{
+ QGstreamerBufferProbe * const control = static_cast<QGstreamerBufferProbe *>(user_data);
+ if (GstBuffer * const buffer = gst_pad_probe_info_get_buffer(info))
+ return control->probeBuffer(buffer) ? GST_PAD_PROBE_OK : GST_PAD_PROBE_DROP;
+ return GST_PAD_PROBE_OK;
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/gstreamer/qgstreamerbufferprobe_p.h b/src/multimedia/gstreamer/qgstreamerbufferprobe_p.h
new file mode 100644
index 000000000..5d66f7320
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamerbufferprobe_p.h
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 Jolla Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QGSTREAMERBUFFERPROBE_H
+#define QGSTREAMERBUFFERPROBE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtmultimediaglobal_p.h>
+#include <gst/gst.h>
+
+#include <QtCore/qglobal.h>
+
+
+QT_BEGIN_NAMESPACE
+
+class Q_MULTIMEDIA_EXPORT QGstreamerBufferProbe
+{
+public:
+ enum Flags
+ {
+ ProbeCaps = 0x01,
+ ProbeBuffers = 0x02,
+ ProbeAll = ProbeCaps | ProbeBuffers
+ };
+
+ explicit QGstreamerBufferProbe(Flags flags = ProbeAll);
+ virtual ~QGstreamerBufferProbe();
+
+ void addProbeToPad(GstPad *pad, bool downstream = true);
+ void removeProbeFromPad(GstPad *pad);
+
+protected:
+ virtual void probeCaps(GstCaps *caps);
+ virtual bool probeBuffer(GstBuffer *buffer);
+
+private:
+ static GstPadProbeReturn capsProbe(GstPad *pad, GstPadProbeInfo *info, gpointer user_data);
+ static GstPadProbeReturn bufferProbe(GstPad *pad, GstPadProbeInfo *info, gpointer user_data);
+ int m_capsProbeId = -1;
+ int m_bufferProbeId = -1;
+ const Flags m_flags;
+};
+
+QT_END_NAMESPACE
+
+#endif // QGSTREAMERAUDIOPROBECONTROL_H
diff --git a/src/multimedia/gstreamer/qgstreamerbushelper.cpp b/src/multimedia/gstreamer/qgstreamerbushelper.cpp
new file mode 100644
index 000000000..2eb038dfa
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamerbushelper.cpp
@@ -0,0 +1,204 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore/qmap.h>
+#include <QtCore/qtimer.h>
+#include <QtCore/qmutex.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qabstracteventdispatcher.h>
+#include <QtCore/qcoreapplication.h>
+
+#include "qgstreamerbushelper_p.h"
+
+QT_BEGIN_NAMESPACE
+
+
+class QGstreamerBusHelperPrivate : public QObject
+{
+ Q_OBJECT
+public:
+ QGstreamerBusHelperPrivate(QGstreamerBusHelper *parent, GstBus* bus) :
+ QObject(parent),
+ m_tag(0),
+ m_bus(bus),
+ m_helper(parent),
+ m_intervalTimer(nullptr)
+ {
+ // glib event loop can be disabled either by env variable or QT_NO_GLIB define, so check the dispacher
+ QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher();
+ const bool hasGlib = dispatcher && dispatcher->inherits("QEventDispatcherGlib");
+ if (!hasGlib) {
+ m_intervalTimer = new QTimer(this);
+ m_intervalTimer->setInterval(250);
+ connect(m_intervalTimer, SIGNAL(timeout()), SLOT(interval()));
+ m_intervalTimer->start();
+ } else {
+ m_tag = gst_bus_add_watch_full(bus, G_PRIORITY_DEFAULT, busCallback, this, nullptr);
+ }
+ }
+
+ ~QGstreamerBusHelperPrivate()
+ {
+ m_helper = 0;
+ delete m_intervalTimer;
+
+ if (m_tag)
+ gst_bus_remove_watch(m_bus);
+ }
+
+ GstBus* bus() const { return m_bus; }
+
+private slots:
+ void interval()
+ {
+ GstMessage* message;
+ while ((message = gst_bus_poll(m_bus, GST_MESSAGE_ANY, 0)) != 0) {
+ processMessage(message);
+ gst_message_unref(message);
+ }
+ }
+
+private:
+ void processMessage(GstMessage* message)
+ {
+ QGstreamerMessage msg(message);
+ doProcessMessage(msg);
+ }
+
+ void queueMessage(GstMessage* message)
+ {
+ QGstreamerMessage msg(message);
+ QMetaObject::invokeMethod(this, "doProcessMessage", Qt::QueuedConnection,
+ Q_ARG(QGstreamerMessage, msg));
+ }
+
+ static gboolean busCallback(GstBus *bus, GstMessage *message, gpointer data)
+ {
+ Q_UNUSED(bus);
+ reinterpret_cast<QGstreamerBusHelperPrivate*>(data)->queueMessage(message);
+ return TRUE;
+ }
+
+ guint m_tag;
+ GstBus* m_bus;
+ QGstreamerBusHelper* m_helper;
+ QTimer* m_intervalTimer;
+
+private slots:
+ void doProcessMessage(const QGstreamerMessage& msg)
+ {
+ for (QGstreamerBusMessageFilter *filter : qAsConst(busFilters)) {
+ if (filter->processBusMessage(msg))
+ break;
+ }
+ emit m_helper->message(msg);
+ }
+
+public:
+ QMutex filterMutex;
+ QList<QGstreamerSyncMessageFilter*> syncFilters;
+ QList<QGstreamerBusMessageFilter*> busFilters;
+};
+
+
+static GstBusSyncReply syncGstBusFilter(GstBus* bus, GstMessage* message, QGstreamerBusHelperPrivate *d)
+{
+ Q_UNUSED(bus);
+ QMutexLocker lock(&d->filterMutex);
+
+ for (QGstreamerSyncMessageFilter *filter : qAsConst(d->syncFilters)) {
+ if (filter->processSyncMessage(QGstreamerMessage(message))) {
+ gst_message_unref(message);
+ return GST_BUS_DROP;
+ }
+ }
+
+ return GST_BUS_PASS;
+}
+
+
+/*!
+ \class QGstreamerBusHelper
+ \internal
+*/
+
+QGstreamerBusHelper::QGstreamerBusHelper(GstBus* bus, QObject* parent):
+ QObject(parent)
+{
+ d = new QGstreamerBusHelperPrivate(this, bus);
+ gst_bus_set_sync_handler(bus, (GstBusSyncHandler)syncGstBusFilter, d, 0);
+ gst_object_ref(GST_OBJECT(bus));
+}
+
+QGstreamerBusHelper::~QGstreamerBusHelper()
+{
+ gst_bus_set_sync_handler(d->bus(), 0, 0, 0);
+ gst_object_unref(GST_OBJECT(d->bus()));
+}
+
+void QGstreamerBusHelper::installMessageFilter(QObject *filter)
+{
+ auto syncFilter = qobject_cast<QGstreamerSyncMessageFilter*>(filter);
+ if (syncFilter) {
+ QMutexLocker lock(&d->filterMutex);
+ if (!d->syncFilters.contains(syncFilter))
+ d->syncFilters.append(syncFilter);
+ }
+
+ auto busFilter = qobject_cast<QGstreamerBusMessageFilter*>(filter);
+ if (busFilter && !d->busFilters.contains(busFilter))
+ d->busFilters.append(busFilter);
+}
+
+void QGstreamerBusHelper::removeMessageFilter(QObject *filter)
+{
+ auto syncFilter = qobject_cast<QGstreamerSyncMessageFilter*>(filter);
+ if (syncFilter) {
+ QMutexLocker lock(&d->filterMutex);
+ d->syncFilters.removeAll(syncFilter);
+ }
+
+ auto busFilter = qobject_cast<QGstreamerBusMessageFilter*>(filter);
+ if (busFilter)
+ d->busFilters.removeAll(busFilter);
+}
+
+QT_END_NAMESPACE
+
+#include "qgstreamerbushelper.moc"
diff --git a/src/multimedia/gstreamer/qgstreamerbushelper_p.h b/src/multimedia/gstreamer/qgstreamerbushelper_p.h
new file mode 100644
index 000000000..01d3ed826
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamerbushelper_p.h
@@ -0,0 +1,104 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QGSTREAMERBUSHELPER_P_H
+#define QGSTREAMERBUSHELPER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtmultimediaglobal_p.h>
+#include <QObject>
+
+#include "qgstreamermessage_p.h"
+
+#include <gst/gst.h>
+
+QT_BEGIN_NAMESPACE
+
+class QGstreamerSyncMessageFilter {
+public:
+ //returns true if message was processed and should be dropped, false otherwise
+ virtual bool processSyncMessage(const QGstreamerMessage &message) = 0;
+};
+#define QGstreamerSyncMessageFilter_iid "org.qt-project.qt.gstreamersyncmessagefilter/5.0"
+Q_DECLARE_INTERFACE(QGstreamerSyncMessageFilter, QGstreamerSyncMessageFilter_iid)
+
+
+class QGstreamerBusMessageFilter {
+public:
+ //returns true if message was processed and should be dropped, false otherwise
+ virtual bool processBusMessage(const QGstreamerMessage &message) = 0;
+};
+#define QGstreamerBusMessageFilter_iid "org.qt-project.qt.gstreamerbusmessagefilter/5.0"
+Q_DECLARE_INTERFACE(QGstreamerBusMessageFilter, QGstreamerBusMessageFilter_iid)
+
+
+class QGstreamerBusHelperPrivate;
+
+class Q_MULTIMEDIA_EXPORT QGstreamerBusHelper : public QObject
+{
+ Q_OBJECT
+ friend class QGstreamerBusHelperPrivate;
+
+public:
+ QGstreamerBusHelper(GstBus* bus, QObject* parent = 0);
+ ~QGstreamerBusHelper();
+
+ void installMessageFilter(QObject *filter);
+ void removeMessageFilter(QObject *filter);
+
+signals:
+ void message(QGstreamerMessage const& message);
+
+private:
+ QGstreamerBusHelperPrivate *d = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/gstreamer/qgstreamermessage.cpp b/src/multimedia/gstreamer/qgstreamermessage.cpp
new file mode 100644
index 000000000..7191565e1
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamermessage.cpp
@@ -0,0 +1,93 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <gst/gst.h>
+
+#include "qgstreamermessage_p.h"
+
+QT_BEGIN_NAMESPACE
+
+static int wuchi = qRegisterMetaType<QGstreamerMessage>();
+
+
+/*!
+ \class QGstreamerMessage
+ \internal
+*/
+
+QGstreamerMessage::QGstreamerMessage(GstMessage* message):
+ m_message(message)
+{
+ gst_message_ref(m_message);
+}
+
+QGstreamerMessage::QGstreamerMessage(QGstreamerMessage const& m):
+ m_message(m.m_message)
+{
+ gst_message_ref(m_message);
+}
+
+
+QGstreamerMessage::~QGstreamerMessage()
+{
+ if (m_message != 0)
+ gst_message_unref(m_message);
+}
+
+GstMessage* QGstreamerMessage::rawMessage() const
+{
+ return m_message;
+}
+
+QGstreamerMessage& QGstreamerMessage::operator=(QGstreamerMessage const& rhs)
+{
+ if (rhs.m_message != m_message) {
+ if (rhs.m_message != 0)
+ gst_message_ref(rhs.m_message);
+
+ if (m_message != 0)
+ gst_message_unref(m_message);
+
+ m_message = rhs.m_message;
+ }
+
+ return *this;
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/gstreamer/qgstreamermessage_p.h b/src/multimedia/gstreamer/qgstreamermessage_p.h
new file mode 100644
index 000000000..d552731d2
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamermessage_p.h
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QGSTREAMERMESSAGE_P_H
+#define QGSTREAMERMESSAGE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtmultimediaglobal_p.h>
+#include <QMetaType>
+
+#include <gst/gst.h>
+
+QT_BEGIN_NAMESPACE
+
+// Required for QDoc workaround
+class QString;
+
+class Q_MULTIMEDIA_EXPORT QGstreamerMessage
+{
+public:
+ QGstreamerMessage() = default;
+ QGstreamerMessage(GstMessage* message);
+ QGstreamerMessage(QGstreamerMessage const& m);
+ ~QGstreamerMessage();
+
+ GstMessage* rawMessage() const;
+
+ QGstreamerMessage& operator=(QGstreamerMessage const& rhs);
+
+private:
+ GstMessage* m_message = nullptr;
+};
+
+QT_END_NAMESPACE
+
+Q_DECLARE_METATYPE(QGstreamerMessage);
+
+#endif
diff --git a/src/multimedia/gstreamer/qgstreamerplayercontrol.cpp b/src/multimedia/gstreamer/qgstreamerplayercontrol.cpp
new file mode 100644
index 000000000..8ad2e0e93
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamerplayercontrol.cpp
@@ -0,0 +1,527 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <private/qgstreamerplayercontrol_p.h>
+#include <private/qgstreamerplayersession_p.h>
+
+#include <QtCore/qdir.h>
+#include <QtCore/qsocketnotifier.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qdebug.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+//#define DEBUG_PLAYBIN
+
+QT_BEGIN_NAMESPACE
+
+QGstreamerPlayerControl::QGstreamerPlayerControl(QGstreamerPlayerSession *session, QObject *parent)
+ : QMediaPlayerControl(parent)
+ , m_session(session)
+{
+ connect(m_session, &QGstreamerPlayerSession::positionChanged, this, &QGstreamerPlayerControl::positionChanged);
+ connect(m_session, &QGstreamerPlayerSession::durationChanged, this, &QGstreamerPlayerControl::durationChanged);
+ connect(m_session, &QGstreamerPlayerSession::mutedStateChanged, this, &QGstreamerPlayerControl::mutedChanged);
+ connect(m_session, &QGstreamerPlayerSession::volumeChanged, this, &QGstreamerPlayerControl::volumeChanged);
+ connect(m_session, &QGstreamerPlayerSession::stateChanged, this, &QGstreamerPlayerControl::updateSessionState);
+ connect(m_session, &QGstreamerPlayerSession::bufferingProgressChanged, this, &QGstreamerPlayerControl::setBufferProgress);
+ connect(m_session, &QGstreamerPlayerSession::playbackFinished, this, &QGstreamerPlayerControl::processEOS);
+ connect(m_session, &QGstreamerPlayerSession::audioAvailableChanged, this, &QGstreamerPlayerControl::audioAvailableChanged);
+ connect(m_session, &QGstreamerPlayerSession::videoAvailableChanged, this, &QGstreamerPlayerControl::videoAvailableChanged);
+ connect(m_session, &QGstreamerPlayerSession::seekableChanged, this, &QGstreamerPlayerControl::seekableChanged);
+ connect(m_session, &QGstreamerPlayerSession::error, this, &QGstreamerPlayerControl::error);
+ connect(m_session, &QGstreamerPlayerSession::invalidMedia, this, &QGstreamerPlayerControl::handleInvalidMedia);
+ connect(m_session, &QGstreamerPlayerSession::playbackRateChanged, this, &QGstreamerPlayerControl::playbackRateChanged);
+}
+
+QGstreamerPlayerControl::~QGstreamerPlayerControl()
+{
+}
+
+qint64 QGstreamerPlayerControl::position() const
+{
+ if (m_mediaStatus == QMediaPlayer::EndOfMedia)
+ return m_session->duration();
+
+ return m_pendingSeekPosition != -1 ? m_pendingSeekPosition : m_session->position();
+}
+
+qint64 QGstreamerPlayerControl::duration() const
+{
+ return m_session->duration();
+}
+
+QMediaPlayer::State QGstreamerPlayerControl::state() const
+{
+ return m_currentState;
+}
+
+QMediaPlayer::MediaStatus QGstreamerPlayerControl::mediaStatus() const
+{
+ return m_mediaStatus;
+}
+
+int QGstreamerPlayerControl::bufferStatus() const
+{
+ if (m_bufferProgress == -1)
+ return m_session->state() == QMediaPlayer::StoppedState ? 0 : 100;
+
+ return m_bufferProgress;
+}
+
+int QGstreamerPlayerControl::volume() const
+{
+ return m_session->volume();
+}
+
+bool QGstreamerPlayerControl::isMuted() const
+{
+ return m_session->isMuted();
+}
+
+bool QGstreamerPlayerControl::isSeekable() const
+{
+ return m_session->isSeekable();
+}
+
+QMediaTimeRange QGstreamerPlayerControl::availablePlaybackRanges() const
+{
+ return m_session->availablePlaybackRanges();
+}
+
+qreal QGstreamerPlayerControl::playbackRate() const
+{
+ return m_session->playbackRate();
+}
+
+void QGstreamerPlayerControl::setPlaybackRate(qreal rate)
+{
+ m_session->setPlaybackRate(rate);
+}
+
+void QGstreamerPlayerControl::setPosition(qint64 pos)
+{
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO << pos/1000.0;
+#endif
+
+ pushState();
+
+ if (m_mediaStatus == QMediaPlayer::EndOfMedia) {
+ m_mediaStatus = QMediaPlayer::LoadedMedia;
+ }
+
+ if (m_currentState == QMediaPlayer::StoppedState) {
+ m_pendingSeekPosition = pos;
+ emit positionChanged(m_pendingSeekPosition);
+ } else if (m_session->isSeekable()) {
+ m_session->showPrerollFrames(true);
+ m_session->seek(pos);
+ m_pendingSeekPosition = -1;
+ } else if (m_session->state() == QMediaPlayer::StoppedState) {
+ m_pendingSeekPosition = pos;
+ emit positionChanged(m_pendingSeekPosition);
+ } else if (m_pendingSeekPosition != -1) {
+ m_pendingSeekPosition = -1;
+ emit positionChanged(m_pendingSeekPosition);
+ }
+
+ popAndNotifyState();
+}
+
+void QGstreamerPlayerControl::play()
+{
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO;
+#endif
+ //m_userRequestedState is needed to know that we need to resume playback when resource-policy
+ //regranted the resources after lost, since m_currentState will become paused when resources are
+ //lost.
+ m_userRequestedState = QMediaPlayer::PlayingState;
+ playOrPause(QMediaPlayer::PlayingState);
+}
+
+void QGstreamerPlayerControl::pause()
+{
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO;
+#endif
+ m_userRequestedState = QMediaPlayer::PausedState;
+ // If the playback has not been started yet but pause is requested.
+ // Seek to the beginning to show first frame.
+ if (m_pendingSeekPosition == -1 && m_session->position() == 0)
+ m_pendingSeekPosition = 0;
+
+ playOrPause(QMediaPlayer::PausedState);
+}
+
+void QGstreamerPlayerControl::playOrPause(QMediaPlayer::State newState)
+{
+ if (m_mediaStatus == QMediaPlayer::NoMedia)
+ return;
+
+ pushState();
+
+ if (m_setMediaPending) {
+ m_mediaStatus = QMediaPlayer::LoadingMedia;
+ setMedia(m_currentResource, m_stream);
+ }
+
+ if (m_mediaStatus == QMediaPlayer::EndOfMedia && m_pendingSeekPosition == -1) {
+ m_pendingSeekPosition = 0;
+ }
+
+ // show prerolled frame if switching from stopped state
+ if (m_pendingSeekPosition == -1) {
+ m_session->showPrerollFrames(true);
+ } else if (m_session->state() == QMediaPlayer::StoppedState) {
+ // Don't evaluate the next two conditions.
+ } else if (m_session->isSeekable()) {
+ m_session->pause();
+ m_session->showPrerollFrames(true);
+ m_session->seek(m_pendingSeekPosition);
+ m_pendingSeekPosition = -1;
+ } else {
+ m_pendingSeekPosition = -1;
+ }
+
+ bool ok = false;
+
+ //To prevent displaying the first video frame when playback is resumed
+ //the pipeline is paused instead of playing, seeked to requested position,
+ //and after seeking is finished (position updated) playback is restarted
+ //with show-preroll-frame enabled.
+ if (newState == QMediaPlayer::PlayingState && m_pendingSeekPosition == -1)
+ ok = m_session->play();
+ else
+ ok = m_session->pause();
+
+ if (!ok)
+ newState = QMediaPlayer::StoppedState;
+
+ if (m_mediaStatus == QMediaPlayer::InvalidMedia)
+ m_mediaStatus = QMediaPlayer::LoadingMedia;
+
+ m_currentState = newState;
+
+ if (m_mediaStatus == QMediaPlayer::EndOfMedia || m_mediaStatus == QMediaPlayer::LoadedMedia) {
+ if (m_bufferProgress == -1 || m_bufferProgress == 100)
+ m_mediaStatus = QMediaPlayer::BufferedMedia;
+ else
+ m_mediaStatus = QMediaPlayer::BufferingMedia;
+ }
+
+ popAndNotifyState();
+
+ emit positionChanged(position());
+}
+
+void QGstreamerPlayerControl::stop()
+{
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO;
+#endif
+ m_userRequestedState = QMediaPlayer::StoppedState;
+
+ pushState();
+
+ if (m_currentState != QMediaPlayer::StoppedState) {
+ m_currentState = QMediaPlayer::StoppedState;
+ m_session->showPrerollFrames(false); // stop showing prerolled frames in stop state
+ // Since gst is not going to send GST_STATE_PAUSED
+ // when pipeline is already paused,
+ // needs to update media status directly.
+ if (m_session->state() == QMediaPlayer::PausedState)
+ updateMediaStatus();
+ else
+ m_session->pause();
+
+ if (m_mediaStatus != QMediaPlayer::EndOfMedia) {
+ m_pendingSeekPosition = 0;
+ emit positionChanged(position());
+ }
+ }
+
+ popAndNotifyState();
+}
+
+void QGstreamerPlayerControl::setVolume(int volume)
+{
+ m_session->setVolume(volume);
+}
+
+void QGstreamerPlayerControl::setMuted(bool muted)
+{
+ m_session->setMuted(muted);
+}
+
+QMediaContent QGstreamerPlayerControl::media() const
+{
+ return m_currentResource;
+}
+
+const QIODevice *QGstreamerPlayerControl::mediaStream() const
+{
+ return m_stream;
+}
+
+void QGstreamerPlayerControl::setMedia(const QMediaContent &content, QIODevice *stream)
+{
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO;
+#endif
+
+ pushState();
+
+ m_currentState = QMediaPlayer::StoppedState;
+ QMediaContent oldMedia = m_currentResource;
+ m_pendingSeekPosition = -1;
+ m_session->showPrerollFrames(false); // do not show prerolled frames until pause() or play() explicitly called
+ m_setMediaPending = false;
+
+ m_session->stop();
+
+ bool userStreamValid = false;
+
+ if (m_bufferProgress != -1) {
+ m_bufferProgress = -1;
+ emit bufferStatusChanged(0);
+ }
+
+ m_currentResource = content;
+ m_stream = stream;
+
+ QNetworkRequest request = content.request();
+
+ if (m_stream)
+ userStreamValid = stream->isOpen() && m_stream->isReadable();
+
+#if !QT_CONFIG(gstreamer_app)
+ m_session->loadFromUri(request);
+#else
+ if (m_stream) {
+ if (userStreamValid){
+ m_session->loadFromStream(request, m_stream);
+ } else {
+ m_mediaStatus = QMediaPlayer::InvalidMedia;
+ emit error(QMediaPlayer::FormatError, tr("Attempting to play invalid user stream"));
+ popAndNotifyState();
+ return;
+ }
+ } else
+ m_session->loadFromUri(request);
+#endif
+
+#if QT_CONFIG(gstreamer_app)
+ if (!request.url().isEmpty() || userStreamValid) {
+#else
+ if (!request.url().isEmpty()) {
+#endif
+ m_mediaStatus = QMediaPlayer::LoadingMedia;
+ m_session->pause();
+ } else {
+ m_mediaStatus = QMediaPlayer::NoMedia;
+ setBufferProgress(0);
+ }
+
+ if (m_currentResource != oldMedia)
+ emit mediaChanged(m_currentResource);
+
+ emit positionChanged(position());
+
+ popAndNotifyState();
+}
+
+void QGstreamerPlayerControl::setVideoOutput(QObject *output)
+{
+ m_session->setVideoRenderer(output);
+}
+
+bool QGstreamerPlayerControl::isAudioAvailable() const
+{
+ return m_session->isAudioAvailable();
+}
+
+bool QGstreamerPlayerControl::isVideoAvailable() const
+{
+ return m_session->isVideoAvailable();
+}
+
+void QGstreamerPlayerControl::updateSessionState(QMediaPlayer::State state)
+{
+ pushState();
+
+ if (state == QMediaPlayer::StoppedState) {
+ m_session->showPrerollFrames(false);
+ m_currentState = QMediaPlayer::StoppedState;
+ }
+
+ if (state == QMediaPlayer::PausedState && m_currentState != QMediaPlayer::StoppedState) {
+ if (m_pendingSeekPosition != -1 && m_session->isSeekable()) {
+ m_session->showPrerollFrames(true);
+ m_session->seek(m_pendingSeekPosition);
+ }
+ m_pendingSeekPosition = -1;
+
+ if (m_currentState == QMediaPlayer::PlayingState) {
+ if (m_bufferProgress == -1 || m_bufferProgress == 100)
+ m_session->play();
+ }
+ }
+
+ updateMediaStatus();
+
+ popAndNotifyState();
+}
+
+void QGstreamerPlayerControl::updateMediaStatus()
+{
+ //EndOfMedia status should be kept, until reset by pause, play or setMedia
+ if (m_mediaStatus == QMediaPlayer::EndOfMedia)
+ return;
+
+ pushState();
+ QMediaPlayer::MediaStatus oldStatus = m_mediaStatus;
+
+ switch (m_session->state()) {
+ case QMediaPlayer::StoppedState:
+ if (m_currentResource.isNull())
+ m_mediaStatus = QMediaPlayer::NoMedia;
+ else if (oldStatus != QMediaPlayer::InvalidMedia)
+ m_mediaStatus = QMediaPlayer::LoadingMedia;
+ break;
+
+ case QMediaPlayer::PlayingState:
+ case QMediaPlayer::PausedState:
+ if (m_currentState == QMediaPlayer::StoppedState) {
+ m_mediaStatus = QMediaPlayer::LoadedMedia;
+ } else {
+ if (m_bufferProgress == -1 || m_bufferProgress == 100)
+ m_mediaStatus = QMediaPlayer::BufferedMedia;
+ else
+ m_mediaStatus = QMediaPlayer::StalledMedia;
+ }
+ break;
+ }
+
+ popAndNotifyState();
+}
+
+void QGstreamerPlayerControl::processEOS()
+{
+ pushState();
+ m_mediaStatus = QMediaPlayer::EndOfMedia;
+ emit positionChanged(position());
+ m_session->endOfMediaReset();
+
+ if (m_currentState != QMediaPlayer::StoppedState) {
+ m_currentState = QMediaPlayer::StoppedState;
+ m_session->showPrerollFrames(false); // stop showing prerolled frames in stop state
+ }
+
+ popAndNotifyState();
+}
+
+void QGstreamerPlayerControl::setBufferProgress(int progress)
+{
+ if (m_bufferProgress == progress || m_mediaStatus == QMediaPlayer::NoMedia)
+ return;
+
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO << progress;
+#endif
+ m_bufferProgress = progress;
+
+ if (m_currentState == QMediaPlayer::PlayingState &&
+ m_bufferProgress == 100 &&
+ m_session->state() != QMediaPlayer::PlayingState)
+ m_session->play();
+
+ if (!m_session->isLiveSource() && m_bufferProgress < 100 &&
+ (m_session->state() == QMediaPlayer::PlayingState ||
+ m_session->pendingState() == QMediaPlayer::PlayingState))
+ m_session->pause();
+
+ updateMediaStatus();
+
+ emit bufferStatusChanged(m_bufferProgress);
+}
+
+void QGstreamerPlayerControl::handleInvalidMedia()
+{
+ pushState();
+ m_mediaStatus = QMediaPlayer::InvalidMedia;
+ m_currentState = QMediaPlayer::StoppedState;
+ m_setMediaPending = true;
+ popAndNotifyState();
+}
+
+void QGstreamerPlayerControl::pushState()
+{
+ m_stateStack.push(m_currentState);
+ m_mediaStatusStack.push(m_mediaStatus);
+}
+
+void QGstreamerPlayerControl::popAndNotifyState()
+{
+ Q_ASSERT(!m_stateStack.isEmpty());
+
+ QMediaPlayer::State oldState = m_stateStack.pop();
+ QMediaPlayer::MediaStatus oldMediaStatus = m_mediaStatusStack.pop();
+
+ if (m_stateStack.isEmpty()) {
+ if (m_mediaStatus != oldMediaStatus) {
+#ifdef DEBUG_PLAYBIN
+ qDebug() << "Media status changed:" << m_mediaStatus;
+#endif
+ emit mediaStatusChanged(m_mediaStatus);
+ }
+
+ if (m_currentState != oldState) {
+#ifdef DEBUG_PLAYBIN
+ qDebug() << "State changed:" << m_currentState;
+#endif
+ emit stateChanged(m_currentState);
+ }
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/gstreamer/qgstreamerplayercontrol_p.h b/src/multimedia/gstreamer/qgstreamerplayercontrol_p.h
new file mode 100644
index 000000000..875d0153b
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamerplayercontrol_p.h
@@ -0,0 +1,136 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QGSTREAMERPLAYERCONTROL_P_H
+#define QGSTREAMERPLAYERCONTROL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qstack.h>
+#include <qmediaplayercontrol.h>
+#include <private/qtmultimediaglobal_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QGstreamerPlayerSession;
+class Q_MULTIMEDIA_EXPORT QGstreamerPlayerControl : public QMediaPlayerControl
+{
+ Q_OBJECT
+
+public:
+ QGstreamerPlayerControl(QGstreamerPlayerSession *session, QObject *parent = 0);
+ ~QGstreamerPlayerControl();
+
+ QGstreamerPlayerSession *session() { return m_session; }
+
+ QMediaPlayer::State state() const override;
+ QMediaPlayer::MediaStatus mediaStatus() const override;
+
+ qint64 position() const override;
+ qint64 duration() const override;
+
+ int bufferStatus() const override;
+
+ int volume() const override;
+ bool isMuted() const override;
+
+ bool isAudioAvailable() const override;
+ bool isVideoAvailable() const override;
+ void setVideoOutput(QObject *output);
+
+ bool isSeekable() const override;
+ QMediaTimeRange availablePlaybackRanges() const override;
+
+ qreal playbackRate() const override;
+ void setPlaybackRate(qreal rate) override;
+
+ QMediaContent media() const override;
+ const QIODevice *mediaStream() const override;
+ void setMedia(const QMediaContent&, QIODevice *) override;
+
+public Q_SLOTS:
+ void setPosition(qint64 pos) override;
+
+ void play() override;
+ void pause() override;
+ void stop() override;
+
+ void setVolume(int volume) override;
+ void setMuted(bool muted) override;
+
+private Q_SLOTS:
+ void updateSessionState(QMediaPlayer::State state);
+ void updateMediaStatus();
+ void processEOS();
+ void setBufferProgress(int progress);
+
+ void handleInvalidMedia();
+
+private:
+ void playOrPause(QMediaPlayer::State state);
+
+ void pushState();
+ void popAndNotifyState();
+
+ QGstreamerPlayerSession *m_session = nullptr;
+ QMediaPlayer::State m_userRequestedState = QMediaPlayer::StoppedState;
+ QMediaPlayer::State m_currentState = QMediaPlayer::StoppedState;
+ QMediaPlayer::MediaStatus m_mediaStatus = QMediaPlayer::NoMedia;
+ QStack<QMediaPlayer::State> m_stateStack;
+ QStack<QMediaPlayer::MediaStatus> m_mediaStatusStack;
+
+ int m_bufferProgress = -1;
+ qint64 m_pendingSeekPosition = -1;
+ bool m_setMediaPending = false;
+ QMediaContent m_currentResource;
+ QIODevice *m_stream = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/gstreamer/qgstreamerplayersession.cpp b/src/multimedia/gstreamer/qgstreamerplayersession.cpp
new file mode 100644
index 000000000..2729097b5
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamerplayersession.cpp
@@ -0,0 +1,1774 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <private/qgstreamerplayersession_p.h>
+#include <private/qgstreamerbushelper_p.h>
+
+#include <private/qgstreameraudioprobecontrol_p.h>
+#include <private/qgstreamervideoprobecontrol_p.h>
+#include <private/qgstreamervideorendererinterface_p.h>
+#include <private/qgstutils_p.h>
+#include <private/qgstvideorenderersink_p.h>
+
+#include <gst/gstvalue.h>
+#include <gst/base/gstbasesrc.h>
+
+#include <QtMultimedia/qmediametadata.h>
+#include <QtCore/qdatetime.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qsize.h>
+#include <QtCore/qtimer.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qstandardpaths.h>
+#include <qvideorenderercontrol.h>
+#include <QUrlQuery>
+
+//#define DEBUG_PLAYBIN
+
+QT_BEGIN_NAMESPACE
+
+static bool usePlaybinVolume()
+{
+ static enum { Yes, No, Unknown } status = Unknown;
+ if (status == Unknown) {
+ QByteArray v = qgetenv("QT_GSTREAMER_USE_PLAYBIN_VOLUME");
+ bool value = !v.isEmpty() && v != "0" && v != "false";
+ if (value)
+ status = Yes;
+ else
+ status = No;
+ }
+ return status == Yes;
+}
+
+typedef enum {
+ GST_PLAY_FLAG_VIDEO = 0x00000001,
+ GST_PLAY_FLAG_AUDIO = 0x00000002,
+ GST_PLAY_FLAG_TEXT = 0x00000004,
+ GST_PLAY_FLAG_VIS = 0x00000008,
+ GST_PLAY_FLAG_SOFT_VOLUME = 0x00000010,
+ GST_PLAY_FLAG_NATIVE_AUDIO = 0x00000020,
+ GST_PLAY_FLAG_NATIVE_VIDEO = 0x00000040,
+ GST_PLAY_FLAG_DOWNLOAD = 0x00000080,
+ GST_PLAY_FLAG_BUFFERING = 0x000000100
+} GstPlayFlags;
+
+QGstreamerPlayerSession::QGstreamerPlayerSession(QObject *parent)
+ : QObject(parent)
+{
+ initPlaybin();
+}
+
+void QGstreamerPlayerSession::initPlaybin()
+{
+ m_playbin = gst_element_factory_make(QT_GSTREAMER_PLAYBIN_ELEMENT_NAME, nullptr);
+ if (m_playbin) {
+ //GST_PLAY_FLAG_NATIVE_VIDEO omits configuration of ffmpegcolorspace and videoscale,
+ //since those elements are included in the video output bin when necessary.
+ int flags = GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_AUDIO;
+ QByteArray envFlags = qgetenv("QT_GSTREAMER_PLAYBIN_FLAGS");
+ if (!envFlags.isEmpty()) {
+ flags |= envFlags.toInt();
+ }
+ g_object_set(G_OBJECT(m_playbin), "flags", flags, nullptr);
+
+ const QByteArray envAudioSink = qgetenv("QT_GSTREAMER_PLAYBIN_AUDIOSINK");
+ GstElement *audioSink = gst_element_factory_make(envAudioSink.isEmpty() ? "autoaudiosink" : envAudioSink, "audiosink");
+ if (audioSink) {
+ if (usePlaybinVolume()) {
+ m_audioSink = audioSink;
+ m_volumeElement = m_playbin;
+ } else {
+ m_volumeElement = gst_element_factory_make("volume", "volumeelement");
+ if (m_volumeElement) {
+ m_audioSink = gst_bin_new("audio-output-bin");
+
+ gst_bin_add_many(GST_BIN(m_audioSink), m_volumeElement, audioSink, nullptr);
+ gst_element_link(m_volumeElement, audioSink);
+
+ GstPad *pad = gst_element_get_static_pad(m_volumeElement, "sink");
+ gst_element_add_pad(GST_ELEMENT(m_audioSink), gst_ghost_pad_new("sink", pad));
+ gst_object_unref(GST_OBJECT(pad));
+ } else {
+ m_audioSink = audioSink;
+ m_volumeElement = m_playbin;
+ }
+ }
+
+ g_object_set(G_OBJECT(m_playbin), "audio-sink", m_audioSink, nullptr);
+ addAudioBufferProbe();
+ }
+ }
+
+ static const auto convDesc = qEnvironmentVariable("QT_GSTREAMER_PLAYBIN_CONVERT");
+ GError *err = nullptr;
+ auto convPipeline = !convDesc.isEmpty() ? convDesc.toLatin1().constData() : "identity";
+ auto convElement = gst_parse_launch(convPipeline, &err);
+ if (err) {
+ qWarning() << "Error:" << convDesc << ":" << QLatin1String(err->message);
+ g_clear_error(&err);
+ }
+ m_videoIdentity = convElement;
+
+ m_nullVideoSink = gst_element_factory_make("fakesink", nullptr);
+ g_object_set(G_OBJECT(m_nullVideoSink), "sync", true, nullptr);
+ gst_object_ref(GST_OBJECT(m_nullVideoSink));
+
+ m_videoOutputBin = gst_bin_new("video-output-bin");
+ // might not get a parent, take ownership to avoid leak
+ qt_gst_object_ref_sink(GST_OBJECT(m_videoOutputBin));
+
+ GstElement *videoOutputSink = m_videoIdentity;
+#if QT_CONFIG(gstreamer_gl)
+ if (QGstUtils::useOpenGL()) {
+ videoOutputSink = gst_element_factory_make("glupload", nullptr);
+ GstElement *colorConvert = gst_element_factory_make("glcolorconvert", nullptr);
+ gst_bin_add_many(GST_BIN(m_videoOutputBin), videoOutputSink, colorConvert, m_videoIdentity, m_nullVideoSink, nullptr);
+ gst_element_link_many(videoOutputSink, colorConvert, m_videoIdentity, nullptr);
+ } else {
+ gst_bin_add_many(GST_BIN(m_videoOutputBin), m_videoIdentity, m_nullVideoSink, nullptr);
+ }
+#else
+ gst_bin_add_many(GST_BIN(m_videoOutputBin), m_videoIdentity, m_nullVideoSink, nullptr);
+#endif
+ gst_element_link(m_videoIdentity, m_nullVideoSink);
+
+ m_videoSink = m_nullVideoSink;
+
+ // add ghostpads
+ GstPad *pad = gst_element_get_static_pad(videoOutputSink, "sink");
+ gst_element_add_pad(GST_ELEMENT(m_videoOutputBin), gst_ghost_pad_new("sink", pad));
+ gst_object_unref(GST_OBJECT(pad));
+
+ if (m_playbin != 0) {
+ // Sort out messages
+ setBus(gst_element_get_bus(m_playbin));
+
+ g_object_set(G_OBJECT(m_playbin), "video-sink", m_videoOutputBin, nullptr);
+
+ g_signal_connect(G_OBJECT(m_playbin), "notify::source", G_CALLBACK(playbinNotifySource), this);
+ g_signal_connect(G_OBJECT(m_playbin), "element-added", G_CALLBACK(handleElementAdded), this);
+
+ if (usePlaybinVolume()) {
+ updateVolume();
+ updateMuted();
+ g_signal_connect(G_OBJECT(m_playbin), "notify::volume", G_CALLBACK(handleVolumeChange), this);
+ g_signal_connect(G_OBJECT(m_playbin), "notify::mute", G_CALLBACK(handleMutedChange), this);
+ }
+
+ g_signal_connect(G_OBJECT(m_playbin), "video-changed", G_CALLBACK(handleStreamsChange), this);
+ g_signal_connect(G_OBJECT(m_playbin), "audio-changed", G_CALLBACK(handleStreamsChange), this);
+ g_signal_connect(G_OBJECT(m_playbin), "text-changed", G_CALLBACK(handleStreamsChange), this);
+
+#if QT_CONFIG(gstreamer_app)
+ g_signal_connect(G_OBJECT(m_playbin), "deep-notify::source", G_CALLBACK(configureAppSrcElement), this);
+#endif
+
+ m_pipeline = m_playbin;
+ gst_object_ref(GST_OBJECT(m_pipeline));
+ }
+}
+
+QGstreamerPlayerSession::~QGstreamerPlayerSession()
+{
+ if (m_pipeline) {
+ stop();
+
+ removeVideoBufferProbe();
+ removeAudioBufferProbe();
+
+ delete m_busHelper;
+ m_busHelper = nullptr;
+ resetElements();
+ }
+}
+
+template <class T>
+static inline void resetGstObject(T *&obj, T *v = nullptr)
+{
+ if (obj)
+ gst_object_unref(GST_OBJECT(obj));
+
+ obj = v;
+}
+
+void QGstreamerPlayerSession::resetElements()
+{
+ setBus(nullptr);
+ resetGstObject(m_playbin);
+ resetGstObject(m_pipeline);
+ resetGstObject(m_nullVideoSink);
+ resetGstObject(m_videoOutputBin);
+
+ m_audioSink = nullptr;
+ m_volumeElement = nullptr;
+ m_videoIdentity = nullptr;
+ m_pendingVideoSink = nullptr;
+ m_videoSink = nullptr;
+}
+
+GstElement *QGstreamerPlayerSession::playbin() const
+{
+ return m_playbin;
+}
+
+#if QT_CONFIG(gstreamer_app)
+void QGstreamerPlayerSession::configureAppSrcElement(GObject* object, GObject *orig, GParamSpec *pspec, QGstreamerPlayerSession* self)
+{
+ Q_UNUSED(object);
+ Q_UNUSED(pspec);
+
+ if (!self->appsrc())
+ return;
+
+ GstElement *appsrc;
+ g_object_get(orig, "source", &appsrc, nullptr);
+
+ if (!self->appsrc()->setup(appsrc))
+ qWarning()<<"Could not setup appsrc element";
+
+ g_object_unref(G_OBJECT(appsrc));
+}
+#endif
+
+void QGstreamerPlayerSession::loadFromStream(const QNetworkRequest &request, QIODevice *appSrcStream)
+{
+#if QT_CONFIG(gstreamer_app)
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO;
+#endif
+ m_request = request;
+ m_duration = 0;
+ m_lastPosition = 0;
+
+ if (!m_appSrc)
+ m_appSrc = new QGstAppSrc(this);
+ m_appSrc->setStream(appSrcStream);
+
+ if (!parsePipeline() && m_playbin) {
+ m_tags.clear();
+ emit tagsChanged();
+
+ g_object_set(G_OBJECT(m_playbin), "uri", "appsrc://", nullptr);
+
+ if (!m_streamTypes.isEmpty()) {
+ m_streamProperties.clear();
+ m_streamTypes.clear();
+
+ emit streamsChanged();
+ }
+ }
+#endif
+}
+
+void QGstreamerPlayerSession::loadFromUri(const QNetworkRequest &request)
+{
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO << request.url();
+#endif
+ m_request = request;
+ m_duration = 0;
+ m_lastPosition = 0;
+
+#if QT_CONFIG(gstreamer_app)
+ if (m_appSrc) {
+ m_appSrc->deleteLater();
+ m_appSrc = 0;
+ }
+#endif
+
+ if (!parsePipeline() && m_playbin) {
+ m_tags.clear();
+ emit tagsChanged();
+
+ g_object_set(G_OBJECT(m_playbin), "uri", m_request.url().toEncoded().constData(), nullptr);
+
+ if (!m_streamTypes.isEmpty()) {
+ m_streamProperties.clear();
+ m_streamTypes.clear();
+
+ emit streamsChanged();
+ }
+ }
+}
+
+bool QGstreamerPlayerSession::parsePipeline()
+{
+ if (m_request.url().scheme() != QLatin1String("gst-pipeline")) {
+ if (!m_playbin) {
+ resetElements();
+ initPlaybin();
+ updateVideoRenderer();
+ }
+ return false;
+ }
+
+ // Set current surface to video sink before creating a pipeline.
+ auto renderer = qobject_cast<QVideoRendererControl *>(m_videoOutput);
+ if (renderer)
+ QGstVideoRendererSink::setSurface(renderer->surface());
+
+ QString url = m_request.url().toString(QUrl::RemoveScheme);
+ QString desc = QUrl::fromPercentEncoding(url.toLatin1().constData());
+ GError *err = nullptr;
+ GstElement *pipeline = gst_parse_launch(desc.toLatin1().constData(), &err);
+ if (err) {
+ auto errstr = QLatin1String(err->message);
+ qWarning() << "Error:" << desc << ":" << errstr;
+ emit error(QMediaPlayer::FormatError, errstr);
+ g_clear_error(&err);
+ }
+
+ return setPipeline(pipeline);
+}
+
+static void gst_foreach(GstIterator *it, const std::function<bool(GstElement *)> &cmp)
+{
+ GValue value = G_VALUE_INIT;
+ while (gst_iterator_next (it, &value) == GST_ITERATOR_OK) {
+ auto child = static_cast<GstElement*>(g_value_get_object(&value));
+ if (cmp(child))
+ break;
+ }
+
+ gst_iterator_free(it);
+ g_value_unset(&value);
+}
+
+bool QGstreamerPlayerSession::setPipeline(GstElement *pipeline)
+{
+ GstBus *bus = pipeline ? gst_element_get_bus(pipeline) : nullptr;
+ if (!bus)
+ return false;
+
+ if (m_playbin)
+ gst_element_set_state(m_playbin, GST_STATE_NULL);
+
+ resetElements();
+ setBus(bus);
+ m_pipeline = pipeline;
+
+ if (m_renderer) {
+ gst_foreach(gst_bin_iterate_sinks(GST_BIN(pipeline)),
+ [this](GstElement *child) {
+ if (qstrcmp(GST_OBJECT_NAME(child), "qtvideosink") == 0) {
+ m_renderer->setVideoSink(child);
+ return true;
+ }
+ return false;
+ });
+ }
+
+#if QT_CONFIG(gstreamer_app)
+ if (m_appSrc) {
+ gst_foreach(gst_bin_iterate_sources(GST_BIN(pipeline)),
+ [this](GstElement *child) {
+ if (qstrcmp(qt_gst_element_get_factory_name(child), "appsrc") == 0) {
+ m_appSrc->setup(child);
+ return true;
+ }
+ return false;
+ });
+ }
+#endif
+
+ emit pipelineChanged();
+ return true;
+}
+
+void QGstreamerPlayerSession::setBus(GstBus *bus)
+{
+ resetGstObject(m_bus, bus);
+
+ // It might still accept gst messages.
+ if (m_busHelper)
+ m_busHelper->deleteLater();
+ m_busHelper = nullptr;
+
+ if (!m_bus)
+ return;
+
+ m_busHelper = new QGstreamerBusHelper(m_bus, this);
+ m_busHelper->installMessageFilter(this);
+
+ if (m_videoOutput)
+ m_busHelper->installMessageFilter(m_videoOutput);
+}
+
+qint64 QGstreamerPlayerSession::duration() const
+{
+ return m_duration;
+}
+
+qint64 QGstreamerPlayerSession::position() const
+{
+ gint64 position = 0;
+
+ if (m_pipeline && qt_gst_element_query_position(m_pipeline, GST_FORMAT_TIME, &position))
+ m_lastPosition = position / 1000000;
+ return m_lastPosition;
+}
+
+qreal QGstreamerPlayerSession::playbackRate() const
+{
+ return m_playbackRate;
+}
+
+void QGstreamerPlayerSession::setPlaybackRate(qreal rate)
+{
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO << rate;
+#endif
+ if (!qFuzzyCompare(m_playbackRate, rate)) {
+ m_playbackRate = rate;
+ if (m_pipeline && m_seekable) {
+ qint64 from = rate > 0 ? position() : 0;
+ qint64 to = rate > 0 ? duration() : position();
+ gst_element_seek(m_pipeline, rate, GST_FORMAT_TIME,
+ GstSeekFlags(GST_SEEK_FLAG_FLUSH),
+ GST_SEEK_TYPE_SET, from * 1000000,
+ GST_SEEK_TYPE_SET, to * 1000000);
+ }
+ emit playbackRateChanged(m_playbackRate);
+ }
+}
+
+QMediaTimeRange QGstreamerPlayerSession::availablePlaybackRanges() const
+{
+ QMediaTimeRange ranges;
+
+ if (duration() <= 0)
+ return ranges;
+
+ //GST_FORMAT_TIME would be more appropriate, but unfortunately it's not supported.
+ //with GST_FORMAT_PERCENT media is treated as encoded with constant bitrate.
+ GstQuery* query = gst_query_new_buffering(GST_FORMAT_PERCENT);
+
+ if (!gst_element_query(m_pipeline, query)) {
+ gst_query_unref(query);
+ return ranges;
+ }
+
+ gint64 rangeStart = 0;
+ gint64 rangeStop = 0;
+ for (guint index = 0; index < gst_query_get_n_buffering_ranges(query); index++) {
+ if (gst_query_parse_nth_buffering_range(query, index, &rangeStart, &rangeStop))
+ ranges.addInterval(rangeStart * duration() / 100,
+ rangeStop * duration() / 100);
+ }
+
+ gst_query_unref(query);
+
+ if (ranges.isEmpty() && !isLiveSource() && isSeekable())
+ ranges.addInterval(0, duration());
+
+#ifdef DEBUG_PLAYBIN
+ qDebug() << ranges;
+#endif
+
+ return ranges;
+}
+
+int QGstreamerPlayerSession::activeStream(QMediaStreamsControl::StreamType streamType) const
+{
+ int streamNumber = -1;
+ if (m_playbin) {
+ switch (streamType) {
+ case QMediaStreamsControl::AudioStream:
+ g_object_get(G_OBJECT(m_playbin), "current-audio", &streamNumber, nullptr);
+ break;
+ case QMediaStreamsControl::VideoStream:
+ g_object_get(G_OBJECT(m_playbin), "current-video", &streamNumber, nullptr);
+ break;
+ case QMediaStreamsControl::SubPictureStream:
+ g_object_get(G_OBJECT(m_playbin), "current-text", &streamNumber, nullptr);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (streamNumber >= 0)
+ streamNumber += m_playbin2StreamOffset.value(streamType,0);
+
+ return streamNumber;
+}
+
+void QGstreamerPlayerSession::setActiveStream(QMediaStreamsControl::StreamType streamType, int streamNumber)
+{
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO << streamType << streamNumber;
+#endif
+
+ if (streamNumber >= 0)
+ streamNumber -= m_playbin2StreamOffset.value(streamType,0);
+
+ if (m_playbin) {
+ switch (streamType) {
+ case QMediaStreamsControl::AudioStream:
+ g_object_set(G_OBJECT(m_playbin), "current-audio", streamNumber, nullptr);
+ break;
+ case QMediaStreamsControl::VideoStream:
+ g_object_set(G_OBJECT(m_playbin), "current-video", streamNumber, nullptr);
+ break;
+ case QMediaStreamsControl::SubPictureStream:
+ g_object_set(G_OBJECT(m_playbin), "current-text", streamNumber, nullptr);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+int QGstreamerPlayerSession::volume() const
+{
+ return m_volume;
+}
+
+bool QGstreamerPlayerSession::isMuted() const
+{
+ return m_muted;
+}
+
+bool QGstreamerPlayerSession::isAudioAvailable() const
+{
+ return m_audioAvailable;
+}
+
+static GstPadProbeReturn block_pad_cb(GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
+{
+ Q_UNUSED(pad);
+ Q_UNUSED(info);
+ Q_UNUSED(user_data);
+ return GST_PAD_PROBE_OK;
+}
+
+void QGstreamerPlayerSession::updateVideoRenderer()
+{
+#ifdef DEBUG_PLAYBIN
+ qDebug() << "Video sink has chaged, reload video output";
+#endif
+
+ if (m_videoOutput)
+ setVideoRenderer(m_videoOutput);
+}
+
+void QGstreamerPlayerSession::setVideoRenderer(QObject *videoOutput)
+{
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO;
+#endif
+ if (m_videoOutput != videoOutput) {
+ if (m_videoOutput) {
+ disconnect(m_videoOutput, SIGNAL(sinkChanged()),
+ this, SLOT(updateVideoRenderer()));
+ disconnect(m_videoOutput, SIGNAL(readyChanged(bool)),
+ this, SLOT(updateVideoRenderer()));
+
+ m_busHelper->removeMessageFilter(m_videoOutput);
+ }
+
+ m_videoOutput = videoOutput;
+
+ if (m_videoOutput) {
+ connect(m_videoOutput, SIGNAL(sinkChanged()),
+ this, SLOT(updateVideoRenderer()));
+ connect(m_videoOutput, SIGNAL(readyChanged(bool)),
+ this, SLOT(updateVideoRenderer()));
+
+ m_busHelper->installMessageFilter(m_videoOutput);
+ }
+ }
+
+ m_renderer = qobject_cast<QGstreamerVideoRendererInterface*>(videoOutput);
+ emit rendererChanged();
+
+ // No sense to continue if custom pipeline requested.
+ if (!m_playbin)
+ return;
+
+ GstElement *videoSink = 0;
+ if (m_renderer && m_renderer->isReady())
+ videoSink = m_renderer->videoSink();
+
+ if (!videoSink)
+ videoSink = m_nullVideoSink;
+
+#ifdef DEBUG_PLAYBIN
+ qDebug() << "Set video output:" << videoOutput;
+ qDebug() << "Current sink:" << (m_videoSink ? GST_ELEMENT_NAME(m_videoSink) : "") << m_videoSink
+ << "pending:" << (m_pendingVideoSink ? GST_ELEMENT_NAME(m_pendingVideoSink) : "") << m_pendingVideoSink
+ << "new sink:" << (videoSink ? GST_ELEMENT_NAME(videoSink) : "") << videoSink;
+#endif
+
+ if (m_pendingVideoSink == videoSink ||
+ (m_pendingVideoSink == 0 && m_videoSink == videoSink)) {
+#ifdef DEBUG_PLAYBIN
+ qDebug() << "Video sink has not changed, skip video output reconfiguration";
+#endif
+ return;
+ }
+
+#ifdef DEBUG_PLAYBIN
+ qDebug() << "Reconfigure video output";
+#endif
+
+ if (m_state == QMediaPlayer::StoppedState) {
+#ifdef DEBUG_PLAYBIN
+ qDebug() << "The pipeline has not started yet, pending state:" << m_pendingState;
+#endif
+ //the pipeline has not started yet
+ flushVideoProbes();
+ m_pendingVideoSink = 0;
+ gst_element_set_state(m_videoSink, GST_STATE_NULL);
+ gst_element_set_state(m_playbin, GST_STATE_NULL);
+
+ removeVideoBufferProbe();
+
+ gst_bin_remove(GST_BIN(m_videoOutputBin), m_videoSink);
+
+ m_videoSink = videoSink;
+
+ gst_bin_add(GST_BIN(m_videoOutputBin), m_videoSink);
+
+ bool linked = gst_element_link(m_videoIdentity, m_videoSink);
+ if (!linked)
+ qWarning() << "Linking video output element failed";
+
+ if (g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "show-preroll-frame") != 0) {
+ gboolean value = m_displayPrerolledFrame;
+ g_object_set(G_OBJECT(m_videoSink), "show-preroll-frame", value, nullptr);
+ }
+
+ addVideoBufferProbe();
+
+ switch (m_pendingState) {
+ case QMediaPlayer::PausedState:
+ gst_element_set_state(m_playbin, GST_STATE_PAUSED);
+ break;
+ case QMediaPlayer::PlayingState:
+ gst_element_set_state(m_playbin, GST_STATE_PLAYING);
+ break;
+ default:
+ break;
+ }
+
+ resumeVideoProbes();
+
+ } else {
+ if (m_pendingVideoSink) {
+#ifdef DEBUG_PLAYBIN
+ qDebug() << "already waiting for pad to be blocked, just change the pending sink";
+#endif
+ m_pendingVideoSink = videoSink;
+ return;
+ }
+
+ m_pendingVideoSink = videoSink;
+
+#ifdef DEBUG_PLAYBIN
+ qDebug() << "Blocking the video output pad...";
+#endif
+
+ //block pads, async to avoid locking in paused state
+ GstPad *srcPad = gst_element_get_static_pad(m_videoIdentity, "src");
+ this->pad_probe_id = gst_pad_add_probe(srcPad, (GstPadProbeType)(GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BLOCKING), block_pad_cb, this, nullptr);
+ gst_object_unref(GST_OBJECT(srcPad));
+
+ //Unpause the sink to avoid waiting until the buffer is processed
+ //while the sink is paused. The pad will be blocked as soon as the current
+ //buffer is processed.
+ if (m_state == QMediaPlayer::PausedState) {
+#ifdef DEBUG_PLAYBIN
+ qDebug() << "Starting video output to avoid blocking in paused state...";
+#endif
+ gst_element_set_state(m_videoSink, GST_STATE_PLAYING);
+ }
+ }
+}
+
+void QGstreamerPlayerSession::finishVideoOutputChange()
+{
+ if (!m_playbin || !m_pendingVideoSink)
+ return;
+
+#ifdef DEBUG_PLAYBIN
+ qDebug() << "finishVideoOutputChange" << m_pendingVideoSink;
+#endif
+
+ GstPad *srcPad = gst_element_get_static_pad(m_videoIdentity, "src");
+
+ if (!gst_pad_is_blocked(srcPad)) {
+ //pad is not blocked, it's possible to swap outputs only in the null state
+ qWarning() << "Pad is not blocked yet, could not switch video sink";
+ GstState identityElementState = GST_STATE_NULL;
+ gst_element_get_state(m_videoIdentity, &identityElementState, nullptr, GST_CLOCK_TIME_NONE);
+ if (identityElementState != GST_STATE_NULL) {
+ gst_object_unref(GST_OBJECT(srcPad));
+ return; //can't change vo yet, received async call from the previous change
+ }
+ }
+
+ if (m_pendingVideoSink == m_videoSink) {
+ qDebug() << "Abort, no change";
+ //video output was change back to the current one,
+ //no need to torment the pipeline, just unblock the pad
+ if (gst_pad_is_blocked(srcPad))
+ gst_pad_remove_probe(srcPad, this->pad_probe_id);
+
+ m_pendingVideoSink = 0;
+ gst_object_unref(GST_OBJECT(srcPad));
+ return;
+ }
+
+ gst_element_set_state(m_videoSink, GST_STATE_NULL);
+ gst_element_unlink(m_videoIdentity, m_videoSink);
+
+ removeVideoBufferProbe();
+
+ gst_bin_remove(GST_BIN(m_videoOutputBin), m_videoSink);
+
+ m_videoSink = m_pendingVideoSink;
+ m_pendingVideoSink = 0;
+
+ gst_bin_add(GST_BIN(m_videoOutputBin), m_videoSink);
+
+ addVideoBufferProbe();
+
+ bool linked = gst_element_link(m_videoIdentity, m_videoSink);
+
+ if (!linked)
+ qWarning() << "Linking video output element failed";
+
+#ifdef DEBUG_PLAYBIN
+ qDebug() << "notify the video connector it has to emit a new segment message...";
+#endif
+
+ GstState state = GST_STATE_VOID_PENDING;
+
+ switch (m_pendingState) {
+ case QMediaPlayer::StoppedState:
+ state = GST_STATE_NULL;
+ break;
+ case QMediaPlayer::PausedState:
+ state = GST_STATE_PAUSED;
+ break;
+ case QMediaPlayer::PlayingState:
+ state = GST_STATE_PLAYING;
+ break;
+ }
+
+ gst_element_set_state(m_videoSink, state);
+
+ if (state == GST_STATE_NULL)
+ flushVideoProbes();
+
+ // Set state change that was deferred due the video output
+ // change being pending
+ gst_element_set_state(m_playbin, state);
+
+ if (state != GST_STATE_NULL)
+ resumeVideoProbes();
+
+ //don't have to wait here, it will unblock eventually
+ if (gst_pad_is_blocked(srcPad))
+ gst_pad_remove_probe(srcPad, this->pad_probe_id);
+
+ gst_object_unref(GST_OBJECT(srcPad));
+
+}
+
+bool QGstreamerPlayerSession::isVideoAvailable() const
+{
+ return m_videoAvailable;
+}
+
+bool QGstreamerPlayerSession::isSeekable() const
+{
+ return m_seekable;
+}
+
+bool QGstreamerPlayerSession::play()
+{
+ static bool dumpDot = qEnvironmentVariableIsSet("GST_DEBUG_DUMP_DOT_DIR");
+ if (dumpDot)
+ gst_debug_bin_to_dot_file_with_ts(GST_BIN(m_pipeline), GstDebugGraphDetails(GST_DEBUG_GRAPH_SHOW_ALL), "gst.play");
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO;
+#endif
+
+ m_everPlayed = false;
+ if (m_pipeline) {
+ m_pendingState = QMediaPlayer::PlayingState;
+ if (gst_element_set_state(m_pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
+ qWarning() << "GStreamer; Unable to play -" << m_request.url().toString();
+ m_pendingState = m_state = QMediaPlayer::StoppedState;
+ emit stateChanged(m_state);
+ } else {
+ resumeVideoProbes();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool QGstreamerPlayerSession::pause()
+{
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO;
+#endif
+ if (m_pipeline) {
+ m_pendingState = QMediaPlayer::PausedState;
+ if (m_pendingVideoSink != 0)
+ return true;
+
+ if (gst_element_set_state(m_pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) {
+ qWarning() << "GStreamer; Unable to pause -" << m_request.url().toString();
+ m_pendingState = m_state = QMediaPlayer::StoppedState;
+ emit stateChanged(m_state);
+ } else {
+ resumeVideoProbes();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void QGstreamerPlayerSession::stop()
+{
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO;
+#endif
+ m_everPlayed = false;
+ if (m_pipeline) {
+
+ if (m_renderer)
+ m_renderer->stopRenderer();
+
+ flushVideoProbes();
+ gst_element_set_state(m_pipeline, GST_STATE_NULL);
+
+ m_lastPosition = 0;
+ QMediaPlayer::State oldState = m_state;
+ m_pendingState = m_state = QMediaPlayer::StoppedState;
+
+ finishVideoOutputChange();
+
+ //we have to do it here, since gstreamer will not emit bus messages any more
+ setSeekable(false);
+ if (oldState != m_state)
+ emit stateChanged(m_state);
+ }
+}
+
+bool QGstreamerPlayerSession::seek(qint64 ms)
+{
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO << ms;
+#endif
+ //seek locks when the video output sink is changing and pad is blocked
+ if (m_pipeline && !m_pendingVideoSink && m_state != QMediaPlayer::StoppedState && m_seekable) {
+ ms = qMax(ms,qint64(0));
+ qint64 from = m_playbackRate > 0 ? ms : 0;
+ qint64 to = m_playbackRate > 0 ? duration() : ms;
+
+ bool isSeeking = gst_element_seek(m_pipeline, m_playbackRate, GST_FORMAT_TIME,
+ GstSeekFlags(GST_SEEK_FLAG_FLUSH),
+ GST_SEEK_TYPE_SET, from * 1000000,
+ GST_SEEK_TYPE_SET, to * 1000000);
+ if (isSeeking)
+ m_lastPosition = ms;
+
+ return isSeeking;
+ }
+
+ return false;
+}
+
+void QGstreamerPlayerSession::setVolume(int volume)
+{
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO << volume;
+#endif
+
+ if (m_volume != volume) {
+ m_volume = volume;
+
+ if (m_volumeElement)
+ g_object_set(G_OBJECT(m_volumeElement), "volume", m_volume / 100.0, nullptr);
+
+ emit volumeChanged(m_volume);
+ }
+}
+
+void QGstreamerPlayerSession::setMuted(bool muted)
+{
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO << muted;
+#endif
+ if (m_muted != muted) {
+ m_muted = muted;
+
+ if (m_volumeElement)
+ g_object_set(G_OBJECT(m_volumeElement), "mute", m_muted ? TRUE : FALSE, nullptr);
+
+ emit mutedStateChanged(m_muted);
+ }
+}
+
+
+void QGstreamerPlayerSession::setSeekable(bool seekable)
+{
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO << seekable;
+#endif
+ if (seekable != m_seekable) {
+ m_seekable = seekable;
+ emit seekableChanged(m_seekable);
+ }
+}
+
+bool QGstreamerPlayerSession::processBusMessage(const QGstreamerMessage &message)
+{
+ GstMessage* gm = message.rawMessage();
+ if (gm) {
+ //tag message comes from elements inside playbin, not from playbin itself
+ if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_TAG) {
+ GstTagList *tag_list;
+ gst_message_parse_tag(gm, &tag_list);
+
+ QMap<QByteArray, QVariant> newTags = QGstUtils::gstTagListToMap(tag_list);
+ QMap<QByteArray, QVariant>::const_iterator it = newTags.constBegin();
+ for ( ; it != newTags.constEnd(); ++it)
+ m_tags.insert(it.key(), it.value()); // overwrite existing tags
+
+ gst_tag_list_free(tag_list);
+
+ emit tagsChanged();
+ } else if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_DURATION) {
+ updateDuration();
+ }
+
+#ifdef DEBUG_PLAYBIN
+ if (m_sourceType == MMSSrc && qstrcmp(GST_OBJECT_NAME(GST_MESSAGE_SRC(gm)), "source") == 0) {
+ qDebug() << "Message from MMSSrc: " << GST_MESSAGE_TYPE(gm);
+ } else if (m_sourceType == RTSPSrc && qstrcmp(GST_OBJECT_NAME(GST_MESSAGE_SRC(gm)), "source") == 0) {
+ qDebug() << "Message from RTSPSrc: " << GST_MESSAGE_TYPE(gm);
+ } else {
+ qDebug() << "Message from " << GST_OBJECT_NAME(GST_MESSAGE_SRC(gm)) << ":" << GST_MESSAGE_TYPE(gm);
+ }
+#endif
+
+ if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_BUFFERING) {
+ int progress = 0;
+ gst_message_parse_buffering(gm, &progress);
+ emit bufferingProgressChanged(progress);
+ }
+
+ bool handlePlaybin2 = false;
+ if (GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_pipeline)) {
+ switch (GST_MESSAGE_TYPE(gm)) {
+ case GST_MESSAGE_STATE_CHANGED:
+ {
+ GstState oldState;
+ GstState newState;
+ GstState pending;
+
+ gst_message_parse_state_changed(gm, &oldState, &newState, &pending);
+
+#ifdef DEBUG_PLAYBIN
+ static QStringList states = {
+ QStringLiteral("GST_STATE_VOID_PENDING"), QStringLiteral("GST_STATE_NULL"),
+ QStringLiteral("GST_STATE_READY"), QStringLiteral("GST_STATE_PAUSED"),
+ QStringLiteral("GST_STATE_PLAYING") };
+
+ qDebug() << QStringLiteral("state changed: old: %1 new: %2 pending: %3") \
+ .arg(states[oldState]) \
+ .arg(states[newState]) \
+ .arg(states[pending]);
+#endif
+
+ switch (newState) {
+ case GST_STATE_VOID_PENDING:
+ case GST_STATE_NULL:
+ setSeekable(false);
+ finishVideoOutputChange();
+ if (m_state != QMediaPlayer::StoppedState)
+ emit stateChanged(m_state = QMediaPlayer::StoppedState);
+ break;
+ case GST_STATE_READY:
+ setSeekable(false);
+ if (m_state != QMediaPlayer::StoppedState)
+ emit stateChanged(m_state = QMediaPlayer::StoppedState);
+ break;
+ case GST_STATE_PAUSED:
+ {
+ QMediaPlayer::State prevState = m_state;
+ m_state = QMediaPlayer::PausedState;
+
+ //check for seekable
+ if (oldState == GST_STATE_READY) {
+ if (m_sourceType == SoupHTTPSrc || m_sourceType == MMSSrc) {
+ //since udpsrc is a live source, it is not applicable here
+ m_everPlayed = true;
+ }
+
+ getStreamsInfo();
+ updateVideoResolutionTag();
+
+ //gstreamer doesn't give a reliable indication the duration
+ //information is ready, GST_MESSAGE_DURATION is not sent by most elements
+ //the duration is queried up to 5 times with increasing delay
+ m_durationQueries = 5;
+ // This should also update the seekable flag.
+ updateDuration();
+
+ if (!qFuzzyCompare(m_playbackRate, qreal(1.0))) {
+ qreal rate = m_playbackRate;
+ m_playbackRate = 1.0;
+ setPlaybackRate(rate);
+ }
+ }
+
+ if (m_state != prevState)
+ emit stateChanged(m_state);
+
+ break;
+ }
+ case GST_STATE_PLAYING:
+ m_everPlayed = true;
+ if (m_state != QMediaPlayer::PlayingState) {
+ emit stateChanged(m_state = QMediaPlayer::PlayingState);
+
+ // For rtsp streams duration information might not be available
+ // until playback starts.
+ if (m_duration <= 0) {
+ m_durationQueries = 5;
+ updateDuration();
+ }
+ }
+
+ break;
+ }
+ }
+ break;
+
+ case GST_MESSAGE_EOS:
+ emit playbackFinished();
+ break;
+
+ case GST_MESSAGE_TAG:
+ case GST_MESSAGE_STREAM_STATUS:
+ case GST_MESSAGE_UNKNOWN:
+ break;
+ case GST_MESSAGE_ERROR: {
+ GError *err;
+ gchar *debug;
+ gst_message_parse_error(gm, &err, &debug);
+ if (err->domain == GST_STREAM_ERROR && err->code == GST_STREAM_ERROR_CODEC_NOT_FOUND)
+ processInvalidMedia(QMediaPlayer::FormatError, tr("Cannot play stream of type: <unknown>"));
+ else
+ processInvalidMedia(QMediaPlayer::ResourceError, QString::fromUtf8(err->message));
+ qWarning() << "Error:" << QString::fromUtf8(err->message);
+ g_error_free(err);
+ g_free(debug);
+ }
+ break;
+ case GST_MESSAGE_WARNING:
+ {
+ GError *err;
+ gchar *debug;
+ gst_message_parse_warning (gm, &err, &debug);
+ qWarning() << "Warning:" << QString::fromUtf8(err->message);
+ g_error_free (err);
+ g_free (debug);
+ }
+ break;
+ case GST_MESSAGE_INFO:
+#ifdef DEBUG_PLAYBIN
+ {
+ GError *err;
+ gchar *debug;
+ gst_message_parse_info (gm, &err, &debug);
+ qDebug() << "Info:" << QString::fromUtf8(err->message);
+ g_error_free (err);
+ g_free (debug);
+ }
+#endif
+ break;
+ case GST_MESSAGE_BUFFERING:
+ case GST_MESSAGE_STATE_DIRTY:
+ case GST_MESSAGE_STEP_DONE:
+ case GST_MESSAGE_CLOCK_PROVIDE:
+ case GST_MESSAGE_CLOCK_LOST:
+ case GST_MESSAGE_NEW_CLOCK:
+ case GST_MESSAGE_STRUCTURE_CHANGE:
+ case GST_MESSAGE_APPLICATION:
+ case GST_MESSAGE_ELEMENT:
+ break;
+ case GST_MESSAGE_SEGMENT_START:
+ {
+ const GstStructure *structure = gst_message_get_structure(gm);
+ qint64 position = g_value_get_int64(gst_structure_get_value(structure, "position"));
+ position /= 1000000;
+ m_lastPosition = position;
+ emit positionChanged(position);
+ }
+ break;
+ case GST_MESSAGE_SEGMENT_DONE:
+ break;
+ case GST_MESSAGE_LATENCY:
+ case GST_MESSAGE_ASYNC_START:
+ break;
+ case GST_MESSAGE_ASYNC_DONE:
+ {
+ gint64 position = 0;
+ if (qt_gst_element_query_position(m_pipeline, GST_FORMAT_TIME, &position)) {
+ position /= 1000000;
+ m_lastPosition = position;
+ emit positionChanged(position);
+ }
+ break;
+ }
+ case GST_MESSAGE_REQUEST_STATE:
+ case GST_MESSAGE_ANY:
+ break;
+ default:
+ break;
+ }
+ } else if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ERROR) {
+ GError *err;
+ gchar *debug;
+ gst_message_parse_error(gm, &err, &debug);
+ // If the source has given up, so do we.
+ if (qstrcmp(GST_OBJECT_NAME(GST_MESSAGE_SRC(gm)), "source") == 0) {
+ bool everPlayed = m_everPlayed;
+ // Try and differentiate network related resource errors from the others
+ if (!m_request.url().isRelative() && m_request.url().scheme().compare(QLatin1String("file"), Qt::CaseInsensitive) != 0 ) {
+ if (everPlayed ||
+ (err->domain == GST_RESOURCE_ERROR && (
+ err->code == GST_RESOURCE_ERROR_BUSY ||
+ err->code == GST_RESOURCE_ERROR_OPEN_READ ||
+ err->code == GST_RESOURCE_ERROR_READ ||
+ err->code == GST_RESOURCE_ERROR_SEEK ||
+ err->code == GST_RESOURCE_ERROR_SYNC))) {
+ processInvalidMedia(QMediaPlayer::NetworkError, QString::fromUtf8(err->message));
+ } else {
+ processInvalidMedia(QMediaPlayer::ResourceError, QString::fromUtf8(err->message));
+ }
+ }
+ else
+ processInvalidMedia(QMediaPlayer::ResourceError, QString::fromUtf8(err->message));
+ } else if (err->domain == GST_STREAM_ERROR
+ && (err->code == GST_STREAM_ERROR_DECRYPT || err->code == GST_STREAM_ERROR_DECRYPT_NOKEY)) {
+ processInvalidMedia(QMediaPlayer::AccessDeniedError, QString::fromUtf8(err->message));
+ } else {
+ handlePlaybin2 = true;
+ }
+ if (!handlePlaybin2)
+ qWarning() << "Error:" << QString::fromUtf8(err->message);
+ g_error_free(err);
+ g_free(debug);
+ } else if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ELEMENT
+ && qstrcmp(GST_OBJECT_NAME(GST_MESSAGE_SRC(gm)), "source") == 0
+ && m_sourceType == UDPSrc
+ && gst_structure_has_name(gst_message_get_structure(gm), "GstUDPSrcTimeout")) {
+ //since udpsrc will not generate an error for the timeout event,
+ //we need to process its element message here and treat it as an error.
+ processInvalidMedia(m_everPlayed ? QMediaPlayer::NetworkError : QMediaPlayer::ResourceError,
+ tr("UDP source timeout"));
+ } else {
+ handlePlaybin2 = true;
+ }
+
+ if (handlePlaybin2) {
+ if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_WARNING) {
+ GError *err;
+ gchar *debug;
+ gst_message_parse_warning(gm, &err, &debug);
+ if (err->domain == GST_STREAM_ERROR && err->code == GST_STREAM_ERROR_CODEC_NOT_FOUND)
+ emit error(int(QMediaPlayer::FormatError), tr("Cannot play stream of type: <unknown>"));
+ // GStreamer shows warning for HTTP playlists
+ if (err && err->message)
+ qWarning() << "Warning:" << QString::fromUtf8(err->message);
+ g_error_free(err);
+ g_free(debug);
+ } else if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ERROR) {
+ GError *err;
+ gchar *debug;
+ gst_message_parse_error(gm, &err, &debug);
+
+ // Nearly all errors map to ResourceError
+ QMediaPlayer::Error qerror = QMediaPlayer::ResourceError;
+ if (err->domain == GST_STREAM_ERROR
+ && (err->code == GST_STREAM_ERROR_DECRYPT
+ || err->code == GST_STREAM_ERROR_DECRYPT_NOKEY)) {
+ qerror = QMediaPlayer::AccessDeniedError;
+ }
+ processInvalidMedia(qerror, QString::fromUtf8(err->message));
+ if (err && err->message)
+ qWarning() << "Error:" << QString::fromUtf8(err->message);
+
+ g_error_free(err);
+ g_free(debug);
+ }
+ }
+ }
+
+ return false;
+}
+
+void QGstreamerPlayerSession::getStreamsInfo()
+{
+ if (!m_playbin)
+ return;
+
+ QList< QMap<QString,QVariant> > oldProperties = m_streamProperties;
+ QList<QMediaStreamsControl::StreamType> oldTypes = m_streamTypes;
+ QMap<QMediaStreamsControl::StreamType, int> oldOffset = m_playbin2StreamOffset;
+
+ //check if video is available:
+ bool haveAudio = false;
+ bool haveVideo = false;
+ m_streamProperties.clear();
+ m_streamTypes.clear();
+ m_playbin2StreamOffset.clear();
+
+ gint audioStreamsCount = 0;
+ gint videoStreamsCount = 0;
+ gint textStreamsCount = 0;
+
+ g_object_get(G_OBJECT(m_playbin), "n-audio", &audioStreamsCount, nullptr);
+ g_object_get(G_OBJECT(m_playbin), "n-video", &videoStreamsCount, nullptr);
+ g_object_get(G_OBJECT(m_playbin), "n-text", &textStreamsCount, nullptr);
+
+ haveAudio = audioStreamsCount > 0;
+ haveVideo = videoStreamsCount > 0;
+
+ m_playbin2StreamOffset[QMediaStreamsControl::AudioStream] = 0;
+ m_playbin2StreamOffset[QMediaStreamsControl::VideoStream] = audioStreamsCount;
+ m_playbin2StreamOffset[QMediaStreamsControl::SubPictureStream] = audioStreamsCount+videoStreamsCount;
+
+ for (int i=0; i<audioStreamsCount; i++)
+ m_streamTypes.append(QMediaStreamsControl::AudioStream);
+
+ for (int i=0; i<videoStreamsCount; i++)
+ m_streamTypes.append(QMediaStreamsControl::VideoStream);
+
+ for (int i=0; i<textStreamsCount; i++)
+ m_streamTypes.append(QMediaStreamsControl::SubPictureStream);
+
+ for (int i=0; i<m_streamTypes.count(); i++) {
+ QMediaStreamsControl::StreamType streamType = m_streamTypes[i];
+ QMap<QString, QVariant> streamProperties;
+
+ int streamIndex = i - m_playbin2StreamOffset[streamType];
+
+ GstTagList *tags = 0;
+ switch (streamType) {
+ case QMediaStreamsControl::AudioStream:
+ g_signal_emit_by_name(G_OBJECT(m_playbin), "get-audio-tags", streamIndex, &tags);
+ break;
+ case QMediaStreamsControl::VideoStream:
+ g_signal_emit_by_name(G_OBJECT(m_playbin), "get-video-tags", streamIndex, &tags);
+ break;
+ case QMediaStreamsControl::SubPictureStream:
+ g_signal_emit_by_name(G_OBJECT(m_playbin), "get-text-tags", streamIndex, &tags);
+ break;
+ default:
+ break;
+ }
+ if (tags && GST_IS_TAG_LIST(tags)) {
+ gchar *languageCode = 0;
+ if (gst_tag_list_get_string(tags, GST_TAG_LANGUAGE_CODE, &languageCode))
+ streamProperties[QMediaMetaData::Language] = QString::fromUtf8(languageCode);
+
+ //qDebug() << "language for setream" << i << QString::fromUtf8(languageCode);
+ g_free (languageCode);
+ gst_tag_list_free(tags);
+ }
+
+ m_streamProperties.append(streamProperties);
+ }
+
+ bool emitAudioChanged = (haveAudio != m_audioAvailable);
+ bool emitVideoChanged = (haveVideo != m_videoAvailable);
+
+ m_audioAvailable = haveAudio;
+ m_videoAvailable = haveVideo;
+
+ if (emitAudioChanged) {
+ emit audioAvailableChanged(m_audioAvailable);
+ }
+ if (emitVideoChanged) {
+ emit videoAvailableChanged(m_videoAvailable);
+ }
+
+ if (oldProperties != m_streamProperties || oldTypes != m_streamTypes || oldOffset != m_playbin2StreamOffset)
+ emit streamsChanged();
+}
+
+void QGstreamerPlayerSession::updateVideoResolutionTag()
+{
+ if (!m_videoIdentity)
+ return;
+
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO;
+#endif
+ QSize size;
+ QSize aspectRatio;
+ GstPad *pad = gst_element_get_static_pad(m_videoIdentity, "src");
+ GstCaps *caps = qt_gst_pad_get_current_caps(pad);
+
+ if (caps) {
+ const GstStructure *structure = gst_caps_get_structure(caps, 0);
+ gst_structure_get_int(structure, "width", &size.rwidth());
+ gst_structure_get_int(structure, "height", &size.rheight());
+
+ gint aspectNum = 0;
+ gint aspectDenum = 0;
+ if (!size.isEmpty() && gst_structure_get_fraction(
+ structure, "pixel-aspect-ratio", &aspectNum, &aspectDenum)) {
+ if (aspectDenum > 0)
+ aspectRatio = QSize(aspectNum, aspectDenum);
+ }
+ gst_caps_unref(caps);
+ }
+
+ gst_object_unref(GST_OBJECT(pad));
+
+ QSize currentSize = m_tags.value("resolution").toSize();
+ QSize currentAspectRatio = m_tags.value("pixel-aspect-ratio").toSize();
+
+ if (currentSize != size || currentAspectRatio != aspectRatio) {
+ if (aspectRatio.isEmpty())
+ m_tags.remove("pixel-aspect-ratio");
+
+ if (size.isEmpty()) {
+ m_tags.remove("resolution");
+ } else {
+ m_tags.insert("resolution", QVariant(size));
+ if (!aspectRatio.isEmpty())
+ m_tags.insert("pixel-aspect-ratio", QVariant(aspectRatio));
+ }
+
+ emit tagsChanged();
+ }
+}
+
+void QGstreamerPlayerSession::updateDuration()
+{
+ gint64 gstDuration = 0;
+ int duration = 0;
+
+ if (m_pipeline && qt_gst_element_query_duration(m_pipeline, GST_FORMAT_TIME, &gstDuration))
+ duration = gstDuration / 1000000;
+
+ if (m_duration != duration) {
+ m_duration = duration;
+ emit durationChanged(m_duration);
+ }
+
+ gboolean seekable = false;
+ if (m_duration > 0) {
+ m_durationQueries = 0;
+ GstQuery *query = gst_query_new_seeking(GST_FORMAT_TIME);
+ if (gst_element_query(m_pipeline, query))
+ gst_query_parse_seeking(query, 0, &seekable, 0, 0);
+ gst_query_unref(query);
+ }
+ setSeekable(seekable);
+
+ if (m_durationQueries > 0) {
+ //increase delay between duration requests
+ int delay = 25 << (5 - m_durationQueries);
+ QTimer::singleShot(delay, this, SLOT(updateDuration()));
+ m_durationQueries--;
+ }
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO << m_duration;
+#endif
+}
+
+void QGstreamerPlayerSession::playbinNotifySource(GObject *o, GParamSpec *p, gpointer d)
+{
+ Q_UNUSED(p);
+
+ GstElement *source = 0;
+ g_object_get(o, "source", &source, nullptr);
+ if (source == 0)
+ return;
+
+#ifdef DEBUG_PLAYBIN
+ qDebug() << "Playbin source added:" << G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(source));
+#endif
+
+ // Set Headers
+ const QByteArray userAgentString("User-Agent");
+
+ QGstreamerPlayerSession *self = reinterpret_cast<QGstreamerPlayerSession *>(d);
+
+ // User-Agent - special case, souphhtpsrc will always set something, even if
+ // defined in extra-headers
+ if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "user-agent") != 0) {
+ g_object_set(G_OBJECT(source), "user-agent",
+ self->m_request.rawHeader(userAgentString).constData(), nullptr);
+ }
+
+ // The rest
+ if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "extra-headers") != 0) {
+ GstStructure *extras = qt_gst_structure_new_empty("extras");
+
+ const auto rawHeaderList = self->m_request.rawHeaderList();
+ for (const QByteArray &rawHeader : rawHeaderList) {
+ if (rawHeader == userAgentString) // Filter User-Agent
+ continue;
+
+ GValue headerValue;
+
+ memset(&headerValue, 0, sizeof(GValue));
+ g_value_init(&headerValue, G_TYPE_STRING);
+
+ g_value_set_string(&headerValue,
+ self->m_request.rawHeader(rawHeader).constData());
+
+ gst_structure_set_value(extras, rawHeader.constData(), &headerValue);
+ }
+
+ if (gst_structure_n_fields(extras) > 0)
+ g_object_set(G_OBJECT(source), "extra-headers", extras, nullptr);
+
+ gst_structure_free(extras);
+ }
+
+ //set timeout property to 30 seconds
+ const int timeout = 30;
+ if (qstrcmp(G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(source)), "GstUDPSrc") == 0) {
+ quint64 convertedTimeout = timeout;
+ // Gst 1.x -> nanosecond
+ convertedTimeout *= 1000000000;
+ g_object_set(G_OBJECT(source), "timeout", convertedTimeout, nullptr);
+ self->m_sourceType = UDPSrc;
+ //The udpsrc is always a live source.
+ self->m_isLiveSource = true;
+
+ QUrlQuery query(self->m_request.url());
+ const QString var = QLatin1String("udpsrc.caps");
+ if (query.hasQueryItem(var)) {
+ GstCaps *caps = gst_caps_from_string(query.queryItemValue(var).toLatin1().constData());
+ g_object_set(G_OBJECT(source), "caps", caps, nullptr);
+ gst_caps_unref(caps);
+ }
+ } else if (qstrcmp(G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(source)), "GstSoupHTTPSrc") == 0) {
+ //souphttpsrc timeout unit = second
+ g_object_set(G_OBJECT(source), "timeout", guint(timeout), nullptr);
+ self->m_sourceType = SoupHTTPSrc;
+ //since gst_base_src_is_live is not reliable, so we check the source property directly
+ gboolean isLive = false;
+ g_object_get(G_OBJECT(source), "is-live", &isLive, nullptr);
+ self->m_isLiveSource = isLive;
+ } else if (qstrcmp(G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(source)), "GstMMSSrc") == 0) {
+ self->m_sourceType = MMSSrc;
+ self->m_isLiveSource = gst_base_src_is_live(GST_BASE_SRC(source));
+ g_object_set(G_OBJECT(source), "tcp-timeout", G_GUINT64_CONSTANT(timeout*1000000), nullptr);
+ } else if (qstrcmp(G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(source)), "GstRTSPSrc") == 0) {
+ //rtspsrc acts like a live source and will therefore only generate data in the PLAYING state.
+ self->m_sourceType = RTSPSrc;
+ self->m_isLiveSource = true;
+ g_object_set(G_OBJECT(source), "buffer-mode", 1, nullptr);
+ } else {
+ self->m_sourceType = UnknownSrc;
+ self->m_isLiveSource = gst_base_src_is_live(GST_BASE_SRC(source));
+ }
+
+#ifdef DEBUG_PLAYBIN
+ if (self->m_isLiveSource)
+ qDebug() << "Current source is a live source";
+ else
+ qDebug() << "Current source is a non-live source";
+#endif
+
+ if (self->m_videoSink)
+ g_object_set(G_OBJECT(self->m_videoSink), "sync", !self->m_isLiveSource, nullptr);
+
+ gst_object_unref(source);
+}
+
+bool QGstreamerPlayerSession::isLiveSource() const
+{
+ return m_isLiveSource;
+}
+
+void QGstreamerPlayerSession::handleVolumeChange(GObject *o, GParamSpec *p, gpointer d)
+{
+ Q_UNUSED(o);
+ Q_UNUSED(p);
+ QGstreamerPlayerSession *session = reinterpret_cast<QGstreamerPlayerSession *>(d);
+ QMetaObject::invokeMethod(session, "updateVolume", Qt::QueuedConnection);
+}
+
+void QGstreamerPlayerSession::updateVolume()
+{
+ double volume = 1.0;
+ g_object_get(m_playbin, "volume", &volume, nullptr);
+
+ if (m_volume != int(volume*100 + 0.5)) {
+ m_volume = int(volume*100 + 0.5);
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO << m_volume;
+#endif
+ emit volumeChanged(m_volume);
+ }
+}
+
+void QGstreamerPlayerSession::handleMutedChange(GObject *o, GParamSpec *p, gpointer d)
+{
+ Q_UNUSED(o);
+ Q_UNUSED(p);
+ QGstreamerPlayerSession *session = reinterpret_cast<QGstreamerPlayerSession *>(d);
+ QMetaObject::invokeMethod(session, "updateMuted", Qt::QueuedConnection);
+}
+
+void QGstreamerPlayerSession::updateMuted()
+{
+ gboolean muted = FALSE;
+ g_object_get(G_OBJECT(m_playbin), "mute", &muted, nullptr);
+ if (m_muted != muted) {
+ m_muted = muted;
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO << m_muted;
+#endif
+ emit mutedStateChanged(muted);
+ }
+}
+
+GstAutoplugSelectResult QGstreamerPlayerSession::handleAutoplugSelect(GstBin *bin, GstPad *pad, GstCaps *caps, GstElementFactory *factory, QGstreamerPlayerSession *session)
+{
+ Q_UNUSED(bin);
+ Q_UNUSED(pad);
+ Q_UNUSED(caps);
+
+ GstAutoplugSelectResult res = GST_AUTOPLUG_SELECT_TRY;
+
+ // if VAAPI is available and can be used to decode but the current video sink cannot handle
+ // the decoded format, don't use it
+ const gchar *factoryName = gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(factory));
+ if (g_str_has_prefix(factoryName, "vaapi")) {
+ GstPad *sinkPad = gst_element_get_static_pad(session->m_videoSink, "sink");
+ GstCaps *sinkCaps = gst_pad_query_caps(sinkPad, nullptr);
+
+ if (!gst_element_factory_can_src_any_caps(factory, sinkCaps))
+ res = GST_AUTOPLUG_SELECT_SKIP;
+
+ gst_object_unref(sinkPad);
+ gst_caps_unref(sinkCaps);
+ }
+
+ return res;
+}
+
+void QGstreamerPlayerSession::handleElementAdded(GstBin *bin, GstElement *element, QGstreamerPlayerSession *session)
+{
+ Q_UNUSED(bin);
+ //we have to configure queue2 element to enable media downloading
+ //and reporting available ranges,
+ //but it's added dynamically to playbin2
+
+ gchar *elementName = gst_element_get_name(element);
+
+ if (g_str_has_prefix(elementName, "queue2")) {
+ // Disable on-disk buffering.
+ g_object_set(G_OBJECT(element), "temp-template", nullptr, nullptr);
+ } else if (g_str_has_prefix(elementName, "uridecodebin") ||
+ g_str_has_prefix(elementName, "decodebin")) {
+ //listen for queue2 element added to uridecodebin/decodebin2 as well.
+ //Don't touch other bins since they may have unrelated queues
+ g_signal_connect(element, "element-added",
+ G_CALLBACK(handleElementAdded), session);
+ }
+
+ g_free(elementName);
+}
+
+void QGstreamerPlayerSession::handleStreamsChange(GstBin *bin, gpointer user_data)
+{
+ Q_UNUSED(bin);
+
+ QGstreamerPlayerSession* session = reinterpret_cast<QGstreamerPlayerSession*>(user_data);
+ QMetaObject::invokeMethod(session, "getStreamsInfo", Qt::QueuedConnection);
+}
+
+//doing proper operations when detecting an invalidMedia: change media status before signal the erorr
+void QGstreamerPlayerSession::processInvalidMedia(QMediaPlayer::Error errorCode, const QString& errorString)
+{
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO;
+#endif
+ emit invalidMedia();
+ stop();
+ emit error(int(errorCode), errorString);
+}
+
+void QGstreamerPlayerSession::showPrerollFrames(bool enabled)
+{
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO << enabled;
+#endif
+ if (enabled != m_displayPrerolledFrame && m_videoSink &&
+ g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "show-preroll-frame") != 0) {
+
+ gboolean value = enabled;
+ g_object_set(G_OBJECT(m_videoSink), "show-preroll-frame", value, nullptr);
+ m_displayPrerolledFrame = enabled;
+ }
+}
+
+void QGstreamerPlayerSession::addProbe(QGstreamerVideoProbeControl* probe)
+{
+ Q_ASSERT(!m_videoProbe);
+ m_videoProbe = probe;
+ addVideoBufferProbe();
+}
+
+void QGstreamerPlayerSession::removeProbe(QGstreamerVideoProbeControl* probe)
+{
+ Q_ASSERT(m_videoProbe == probe);
+ removeVideoBufferProbe();
+ m_videoProbe = 0;
+}
+
+void QGstreamerPlayerSession::addProbe(QGstreamerAudioProbeControl* probe)
+{
+ Q_ASSERT(!m_audioProbe);
+ m_audioProbe = probe;
+ addAudioBufferProbe();
+}
+
+void QGstreamerPlayerSession::removeProbe(QGstreamerAudioProbeControl* probe)
+{
+ Q_ASSERT(m_audioProbe == probe);
+ removeAudioBufferProbe();
+ m_audioProbe = 0;
+}
+
+// This function is similar to stop(),
+// but does not set m_everPlayed, m_lastPosition,
+// and setSeekable() values.
+void QGstreamerPlayerSession::endOfMediaReset()
+{
+ if (m_renderer)
+ m_renderer->stopRenderer();
+
+ flushVideoProbes();
+ gst_element_set_state(m_pipeline, GST_STATE_PAUSED);
+
+ QMediaPlayer::State oldState = m_state;
+ m_pendingState = m_state = QMediaPlayer::StoppedState;
+
+ finishVideoOutputChange();
+
+ if (oldState != m_state)
+ emit stateChanged(m_state);
+}
+
+void QGstreamerPlayerSession::removeVideoBufferProbe()
+{
+ if (!m_videoProbe)
+ return;
+
+ GstPad *pad = gst_element_get_static_pad(m_videoSink, "sink");
+ if (pad) {
+ m_videoProbe->removeProbeFromPad(pad);
+ gst_object_unref(GST_OBJECT(pad));
+ }
+}
+
+void QGstreamerPlayerSession::addVideoBufferProbe()
+{
+ if (!m_videoProbe)
+ return;
+
+ GstPad *pad = gst_element_get_static_pad(m_videoSink, "sink");
+ if (pad) {
+ m_videoProbe->addProbeToPad(pad);
+ gst_object_unref(GST_OBJECT(pad));
+ }
+}
+
+void QGstreamerPlayerSession::removeAudioBufferProbe()
+{
+ if (!m_audioProbe)
+ return;
+
+ GstPad *pad = gst_element_get_static_pad(m_audioSink, "sink");
+ if (pad) {
+ m_audioProbe->removeProbeFromPad(pad);
+ gst_object_unref(GST_OBJECT(pad));
+ }
+}
+
+void QGstreamerPlayerSession::addAudioBufferProbe()
+{
+ if (!m_audioProbe)
+ return;
+
+ GstPad *pad = gst_element_get_static_pad(m_audioSink, "sink");
+ if (pad) {
+ m_audioProbe->addProbeToPad(pad);
+ gst_object_unref(GST_OBJECT(pad));
+ }
+}
+
+void QGstreamerPlayerSession::flushVideoProbes()
+{
+ if (m_videoProbe)
+ m_videoProbe->startFlushing();
+}
+
+void QGstreamerPlayerSession::resumeVideoProbes()
+{
+ if (m_videoProbe)
+ m_videoProbe->stopFlushing();
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/gstreamer/qgstreamerplayersession_p.h b/src/multimedia/gstreamer/qgstreamerplayersession_p.h
new file mode 100644
index 000000000..f7d09ed3d
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamerplayersession_p.h
@@ -0,0 +1,277 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QGSTREAMERPLAYERSESSION_P_H
+#define QGSTREAMERPLAYERSESSION_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtMultimedia/private/qtmultimediaglobal_p.h>
+#include <QObject>
+#include <QtCore/qmutex.h>
+#include <QtNetwork/qnetworkrequest.h>
+#include <private/qgstreamerplayercontrol_p.h>
+#include <private/qgstreamerbushelper_p.h>
+#include <qmediaplayer.h>
+#include <qmediastreamscontrol.h>
+#include <qaudioformat.h>
+
+#if QT_CONFIG(gstreamer_app)
+#include <private/qgstappsrc_p.h>
+#endif
+
+#include <gst/gst.h>
+
+QT_BEGIN_NAMESPACE
+
+class QGstreamerBusHelper;
+class QGstreamerMessage;
+
+class QGstreamerVideoRendererInterface;
+class QGstreamerVideoProbeControl;
+class QGstreamerAudioProbeControl;
+
+typedef enum {
+ GST_AUTOPLUG_SELECT_TRY,
+ GST_AUTOPLUG_SELECT_EXPOSE,
+ GST_AUTOPLUG_SELECT_SKIP
+} GstAutoplugSelectResult;
+
+class Q_MULTIMEDIA_EXPORT QGstreamerPlayerSession
+ : public QObject
+ , public QGstreamerBusMessageFilter
+{
+Q_OBJECT
+Q_INTERFACES(QGstreamerBusMessageFilter)
+
+public:
+ QGstreamerPlayerSession(QObject *parent);
+ virtual ~QGstreamerPlayerSession();
+
+ GstElement *playbin() const;
+ GstElement *pipeline() const { return m_pipeline; }
+ QGstreamerBusHelper *bus() const { return m_busHelper; }
+
+ QNetworkRequest request() const;
+
+ QMediaPlayer::State state() const { return m_state; }
+ QMediaPlayer::State pendingState() const { return m_pendingState; }
+
+ qint64 duration() const;
+ qint64 position() const;
+
+ int volume() const;
+ bool isMuted() const;
+
+ bool isAudioAvailable() const;
+
+ void setVideoRenderer(QObject *renderer);
+ QGstreamerVideoRendererInterface *renderer() const { return m_renderer; }
+ bool isVideoAvailable() const;
+
+ bool isSeekable() const;
+
+ qreal playbackRate() const;
+ void setPlaybackRate(qreal rate);
+
+ QMediaTimeRange availablePlaybackRanges() const;
+
+ QMap<QByteArray ,QVariant> tags() const { return m_tags; }
+ QMap<QString,QVariant> streamProperties(int streamNumber) const { return m_streamProperties[streamNumber]; }
+ int streamCount() const { return m_streamProperties.count(); }
+ QMediaStreamsControl::StreamType streamType(int streamNumber) { return m_streamTypes.value(streamNumber, QMediaStreamsControl::UnknownStream); }
+
+ int activeStream(QMediaStreamsControl::StreamType streamType) const;
+ void setActiveStream(QMediaStreamsControl::StreamType streamType, int streamNumber);
+
+ bool processBusMessage(const QGstreamerMessage &message) override;
+
+#if QT_CONFIG(gstreamer_app)
+ QGstAppSrc *appsrc() const { return m_appSrc; }
+ static void configureAppSrcElement(GObject*, GObject*, GParamSpec*,QGstreamerPlayerSession* _this);
+#endif
+
+ bool isLiveSource() const;
+
+ void addProbe(QGstreamerVideoProbeControl* probe);
+ void removeProbe(QGstreamerVideoProbeControl* probe);
+
+ void addProbe(QGstreamerAudioProbeControl* probe);
+ void removeProbe(QGstreamerAudioProbeControl* probe);
+
+ void endOfMediaReset();
+
+public slots:
+ void loadFromUri(const QNetworkRequest &url);
+ void loadFromStream(const QNetworkRequest &url, QIODevice *stream);
+ bool play();
+ bool pause();
+ void stop();
+
+ bool seek(qint64 pos);
+
+ void setVolume(int volume);
+ void setMuted(bool muted);
+
+ void showPrerollFrames(bool enabled);
+
+signals:
+ void durationChanged(qint64 duration);
+ void positionChanged(qint64 position);
+ void stateChanged(QMediaPlayer::State state);
+ void volumeChanged(int volume);
+ void mutedStateChanged(bool muted);
+ void audioAvailableChanged(bool audioAvailable);
+ void videoAvailableChanged(bool videoAvailable);
+ void bufferingProgressChanged(int percentFilled);
+ void playbackFinished();
+ void tagsChanged();
+ void streamsChanged();
+ void seekableChanged(bool);
+ void error(int error, const QString &errorString);
+ void invalidMedia();
+ void playbackRateChanged(qreal);
+ void rendererChanged();
+ void pipelineChanged();
+
+private slots:
+ void getStreamsInfo();
+ void setSeekable(bool);
+ void finishVideoOutputChange();
+ void updateVideoRenderer();
+ void updateVideoResolutionTag();
+ void updateVolume();
+ void updateMuted();
+ void updateDuration();
+
+private:
+ static void playbinNotifySource(GObject *o, GParamSpec *p, gpointer d);
+ static void handleVolumeChange(GObject *o, GParamSpec *p, gpointer d);
+ static void handleMutedChange(GObject *o, GParamSpec *p, gpointer d);
+ static void handleElementAdded(GstBin *bin, GstElement *element, QGstreamerPlayerSession *session);
+ static void handleStreamsChange(GstBin *bin, gpointer user_data);
+ static GstAutoplugSelectResult handleAutoplugSelect(GstBin *bin, GstPad *pad, GstCaps *caps, GstElementFactory *factory, QGstreamerPlayerSession *session);
+
+ void processInvalidMedia(QMediaPlayer::Error errorCode, const QString& errorString);
+
+ void removeVideoBufferProbe();
+ void addVideoBufferProbe();
+ void removeAudioBufferProbe();
+ void addAudioBufferProbe();
+ void flushVideoProbes();
+ void resumeVideoProbes();
+ bool parsePipeline();
+ bool setPipeline(GstElement *pipeline);
+ void resetElements();
+ void initPlaybin();
+ void setBus(GstBus *bus);
+
+ QNetworkRequest m_request;
+ QMediaPlayer::State m_state = QMediaPlayer::StoppedState;
+ QMediaPlayer::State m_pendingState = QMediaPlayer::StoppedState;
+ QGstreamerBusHelper *m_busHelper = nullptr;
+ GstElement *m_playbin = nullptr;
+ GstElement *m_pipeline = nullptr;
+
+ GstElement *m_videoSink = nullptr;
+
+ GstElement *m_videoOutputBin = nullptr;
+ GstElement *m_videoIdentity = nullptr;
+ GstElement *m_pendingVideoSink = nullptr;
+ GstElement *m_nullVideoSink = nullptr;
+
+ GstElement *m_audioSink = nullptr;
+ GstElement *m_volumeElement = nullptr;
+
+ GstBus *m_bus = nullptr;
+ QObject *m_videoOutput = nullptr;
+ QGstreamerVideoRendererInterface *m_renderer = nullptr;
+
+#if QT_CONFIG(gstreamer_app)
+ QGstAppSrc *m_appSrc = nullptr;
+#endif
+
+ QMap<QByteArray, QVariant> m_tags;
+ QList< QMap<QString,QVariant> > m_streamProperties;
+ QList<QMediaStreamsControl::StreamType> m_streamTypes;
+ QMap<QMediaStreamsControl::StreamType, int> m_playbin2StreamOffset;
+
+ QGstreamerVideoProbeControl *m_videoProbe = nullptr;
+ QGstreamerAudioProbeControl *m_audioProbe = nullptr;
+
+ int m_volume = 100;
+ qreal m_playbackRate = 1.0;
+ bool m_muted = false;
+ bool m_audioAvailable = false;
+ bool m_videoAvailable = false;
+ bool m_seekable = false;
+
+ mutable qint64 m_lastPosition = 0;
+ qint64 m_duration = 0;
+ int m_durationQueries = 0;
+
+ bool m_displayPrerolledFrame = true;
+
+ enum SourceType
+ {
+ UnknownSrc,
+ SoupHTTPSrc,
+ UDPSrc,
+ MMSSrc,
+ RTSPSrc,
+ };
+ SourceType m_sourceType = UnknownSrc;
+ bool m_everPlayed = false;
+ bool m_isLiveSource = false;
+
+ gulong pad_probe_id = 0;
+};
+
+QT_END_NAMESPACE
+
+#endif // QGSTREAMERPLAYERSESSION_H
diff --git a/src/multimedia/gstreamer/qgstreamervideoinputdevicecontrol.cpp b/src/multimedia/gstreamer/qgstreamervideoinputdevicecontrol.cpp
new file mode 100644
index 000000000..088b97101
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamervideoinputdevicecontrol.cpp
@@ -0,0 +1,108 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qgstreamervideoinputdevicecontrol_p.h"
+
+#include <QtCore/QDir>
+#include <QtCore/QDebug>
+
+#include <private/qgstutils_p.h>
+
+QGstreamerVideoInputDeviceControl::QGstreamerVideoInputDeviceControl(QObject *parent)
+ : QVideoDeviceSelectorControl(parent)
+{
+}
+
+QGstreamerVideoInputDeviceControl::QGstreamerVideoInputDeviceControl(
+ GstElementFactory *factory, QObject *parent)
+ : QVideoDeviceSelectorControl(parent)
+ , m_factory(factory)
+{
+ if (m_factory)
+ gst_object_ref(GST_OBJECT(m_factory));
+}
+
+QGstreamerVideoInputDeviceControl::~QGstreamerVideoInputDeviceControl()
+{
+ if (m_factory)
+ gst_object_unref(GST_OBJECT(m_factory));
+}
+
+int QGstreamerVideoInputDeviceControl::deviceCount() const
+{
+ return QGstUtils::enumerateCameras().count();
+}
+
+QString QGstreamerVideoInputDeviceControl::deviceName(int index) const
+{
+ return QGstUtils::enumerateCameras().value(index).name;
+}
+
+QString QGstreamerVideoInputDeviceControl::deviceDescription(int index) const
+{
+ return QGstUtils::enumerateCameras().value(index).description;
+}
+
+QCamera::Position QGstreamerVideoInputDeviceControl::cameraPosition(int index) const
+{
+ return QGstUtils::enumerateCameras().value(index).position;
+}
+
+int QGstreamerVideoInputDeviceControl::cameraOrientation(int index) const
+{
+ return QGstUtils::enumerateCameras().value(index).orientation;
+}
+
+int QGstreamerVideoInputDeviceControl::defaultDevice() const
+{
+ return 0;
+}
+
+int QGstreamerVideoInputDeviceControl::selectedDevice() const
+{
+ return m_selectedDevice;
+}
+
+void QGstreamerVideoInputDeviceControl::setSelectedDevice(int index)
+{
+ // Always update selected device and proxy it to clients
+ m_selectedDevice = index;
+ emit selectedDeviceChanged(index);
+ emit selectedDeviceChanged(deviceName(index));
+}
diff --git a/src/multimedia/gstreamer/qgstreamervideoinputdevicecontrol_p.h b/src/multimedia/gstreamer/qgstreamervideoinputdevicecontrol_p.h
new file mode 100644
index 000000000..632b6dbb4
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamervideoinputdevicecontrol_p.h
@@ -0,0 +1,95 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QGSTREAMERVIDEOINPUTDEVICECONTROL_H
+#define QGSTREAMERVIDEOINPUTDEVICECONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtmultimediaglobal_p.h>
+#include <qvideodeviceselectorcontrol.h>
+#include <QtCore/qstringlist.h>
+
+#include <gst/gst.h>
+#include <qcamera.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_MULTIMEDIA_EXPORT QGstreamerVideoInputDeviceControl : public QVideoDeviceSelectorControl
+{
+Q_OBJECT
+public:
+ QGstreamerVideoInputDeviceControl(QObject *parent);
+ QGstreamerVideoInputDeviceControl(GstElementFactory *factory, QObject *parent);
+ ~QGstreamerVideoInputDeviceControl();
+
+ int deviceCount() const override;
+
+ QString deviceName(int index) const override;
+ QString deviceDescription(int index) const override;
+ QCamera::Position cameraPosition(int index) const override;
+ int cameraOrientation(int index) const override;
+
+ int defaultDevice() const override;
+ int selectedDevice() const override;
+
+ static QString primaryCamera() { return tr("Main camera"); }
+ static QString secondaryCamera() { return tr("Front camera"); }
+
+public Q_SLOTS:
+ void setSelectedDevice(int index) override;
+
+private:
+ GstElementFactory *m_factory = nullptr;
+
+ int m_selectedDevice = 0;
+};
+
+QT_END_NAMESPACE
+
+#endif // QGSTREAMERAUDIOINPUTDEVICECONTROL_H
diff --git a/src/multimedia/gstreamer/qgstreamervideooverlay.cpp b/src/multimedia/gstreamer/qgstreamervideooverlay.cpp
new file mode 100644
index 000000000..df3229736
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamervideooverlay.cpp
@@ -0,0 +1,605 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qgstreamervideooverlay_p.h"
+
+#include <QtGui/qguiapplication.h>
+#include "qgstutils_p.h"
+
+#include <gst/video/videooverlay.h>
+
+#include <QtMultimedia/private/qtmultimediaglobal_p.h>
+
+QT_BEGIN_NAMESPACE
+
+struct ElementMap
+{
+ const char *qtPlatform;
+ const char *gstreamerElement;
+};
+
+// Ordered by descending priority
+static constexpr ElementMap elementMap[] =
+{
+#if QT_CONFIG(gstreamer_gl)
+ { "xcb", "glimagesink" },
+#endif
+ { "xcb", "vaapisink" },
+ { "xcb", "xvimagesink" },
+ { "xcb", "ximagesink" }
+};
+
+class QGstreamerSinkProperties
+{
+public:
+ virtual ~QGstreamerSinkProperties()
+ {
+ }
+
+ virtual bool hasShowPrerollFrame() const = 0;
+ virtual void reset() = 0;
+ virtual int brightness() const = 0;
+ virtual bool setBrightness(int brightness) = 0;
+ virtual int contrast() const = 0;
+ virtual bool setContrast(int contrast) = 0;
+ virtual int hue() const = 0;
+ virtual bool setHue(int hue) = 0;
+ virtual int saturation() const = 0;
+ virtual bool setSaturation(int saturation) = 0;
+ virtual Qt::AspectRatioMode aspectRatioMode() const = 0;
+ virtual void setAspectRatioMode(Qt::AspectRatioMode mode) = 0;
+};
+
+class QXVImageSinkProperties : public QGstreamerSinkProperties
+{
+public:
+ QXVImageSinkProperties(GstElement *sink)
+ : m_videoSink(sink)
+ {
+ m_hasForceAspectRatio = g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "force-aspect-ratio");
+ m_hasBrightness = g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "brightness");
+ m_hasContrast = g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "contrast");
+ m_hasHue = g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "hue");
+ m_hasSaturation = g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "saturation");
+ m_hasShowPrerollFrame = g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "show-preroll-frame");
+ }
+
+ bool hasShowPrerollFrame() const override
+ {
+ return m_hasShowPrerollFrame;
+ }
+
+ void reset() override
+ {
+ setAspectRatioMode(m_aspectRatioMode);
+ setBrightness(m_brightness);
+ setContrast(m_contrast);
+ setHue(m_hue);
+ setSaturation(m_saturation);
+ }
+
+ int brightness() const override
+ {
+ int brightness = 0;
+ if (m_hasBrightness)
+ g_object_get(G_OBJECT(m_videoSink), "brightness", &brightness, nullptr);
+
+ return brightness / 10;
+ }
+
+ bool setBrightness(int brightness) override
+ {
+ m_brightness = brightness;
+ if (m_hasBrightness)
+ g_object_set(G_OBJECT(m_videoSink), "brightness", brightness * 10, nullptr);
+
+ return m_hasBrightness;
+ }
+
+ int contrast() const override
+ {
+ int contrast = 0;
+ if (m_hasContrast)
+ g_object_get(G_OBJECT(m_videoSink), "contrast", &contrast, nullptr);
+
+ return contrast / 10;
+ }
+
+ bool setContrast(int contrast) override
+ {
+ m_contrast = contrast;
+ if (m_hasContrast)
+ g_object_set(G_OBJECT(m_videoSink), "contrast", contrast * 10, nullptr);
+
+ return m_hasContrast;
+ }
+
+ int hue() const override
+ {
+ int hue = 0;
+ if (m_hasHue)
+ g_object_get(G_OBJECT(m_videoSink), "hue", &hue, nullptr);
+
+ return hue / 10;
+ }
+
+ bool setHue(int hue) override
+ {
+ m_hue = hue;
+ if (m_hasHue)
+ g_object_set(G_OBJECT(m_videoSink), "hue", hue * 10, nullptr);
+
+ return m_hasHue;
+ }
+
+ int saturation() const override
+ {
+ int saturation = 0;
+ if (m_hasSaturation)
+ g_object_get(G_OBJECT(m_videoSink), "saturation", &saturation, nullptr);
+
+ return saturation / 10;
+ }
+
+ bool setSaturation(int saturation) override
+ {
+ m_saturation = saturation;
+ if (m_hasSaturation)
+ g_object_set(G_OBJECT(m_videoSink), "saturation", saturation * 10, nullptr);
+
+ return m_hasSaturation;
+ }
+
+ Qt::AspectRatioMode aspectRatioMode() const override
+ {
+ Qt::AspectRatioMode mode = Qt::KeepAspectRatio;
+ if (m_hasForceAspectRatio) {
+ gboolean forceAR = false;
+ g_object_get(G_OBJECT(m_videoSink), "force-aspect-ratio", &forceAR, nullptr);
+ if (!forceAR)
+ mode = Qt::IgnoreAspectRatio;
+ }
+
+ return mode;
+ }
+
+ void setAspectRatioMode(Qt::AspectRatioMode mode) override
+ {
+ m_aspectRatioMode = mode;
+ if (m_hasForceAspectRatio) {
+ g_object_set(G_OBJECT(m_videoSink),
+ "force-aspect-ratio",
+ (mode == Qt::KeepAspectRatio),
+ nullptr);
+ }
+ }
+
+protected:
+
+ GstElement *m_videoSink = nullptr;
+ bool m_hasForceAspectRatio = false;
+ bool m_hasBrightness = false;
+ bool m_hasContrast = false;
+ bool m_hasHue = false;
+ bool m_hasSaturation = false;
+ bool m_hasShowPrerollFrame = false;
+ Qt::AspectRatioMode m_aspectRatioMode = Qt::KeepAspectRatio;
+ int m_brightness = 0;
+ int m_contrast = 0;
+ int m_hue = 0;
+ int m_saturation = 0;
+};
+
+class QVaapiSinkProperties : public QXVImageSinkProperties
+{
+public:
+ QVaapiSinkProperties(GstElement *sink)
+ : QXVImageSinkProperties(sink)
+ {
+ // Set default values.
+ m_contrast = 1;
+ m_saturation = 1;
+ }
+
+ int brightness() const override
+ {
+ gfloat brightness = 0;
+ if (m_hasBrightness)
+ g_object_get(G_OBJECT(m_videoSink), "brightness", &brightness, nullptr);
+
+ return brightness * 100; // [-1,1] -> [-100,100]
+ }
+
+ bool setBrightness(int brightness) override
+ {
+ m_brightness = brightness;
+ if (m_hasBrightness) {
+ gfloat v = brightness / 100.0; // [-100,100] -> [-1,1]
+ g_object_set(G_OBJECT(m_videoSink), "brightness", v, nullptr);
+ }
+
+ return m_hasBrightness;
+ }
+
+ int contrast() const override
+ {
+ gfloat contrast = 1;
+ if (m_hasContrast)
+ g_object_get(G_OBJECT(m_videoSink), "contrast", &contrast, nullptr);
+
+ return (contrast - 1) * 100; // [0,2] -> [-100,100]
+ }
+
+ bool setContrast(int contrast) override
+ {
+ m_contrast = contrast;
+ if (m_hasContrast) {
+ gfloat v = (contrast / 100.0) + 1; // [-100,100] -> [0,2]
+ g_object_set(G_OBJECT(m_videoSink), "contrast", v, nullptr);
+ }
+
+ return m_hasContrast;
+ }
+
+ int hue() const override
+ {
+ gfloat hue = 0;
+ if (m_hasHue)
+ g_object_get(G_OBJECT(m_videoSink), "hue", &hue, nullptr);
+
+ return hue / 180 * 100; // [-180,180] -> [-100,100]
+ }
+
+ bool setHue(int hue) override
+ {
+ m_hue = hue;
+ if (m_hasHue) {
+ gfloat v = hue / 100.0 * 180; // [-100,100] -> [-180,180]
+ g_object_set(G_OBJECT(m_videoSink), "hue", v, nullptr);
+ }
+
+ return m_hasHue;
+ }
+
+ int saturation() const override
+ {
+ gfloat saturation = 1;
+ if (m_hasSaturation)
+ g_object_get(G_OBJECT(m_videoSink), "saturation", &saturation, nullptr);
+
+ return (saturation - 1) * 100; // [0,2] -> [-100,100]
+ }
+
+ bool setSaturation(int saturation) override
+ {
+ m_saturation = saturation;
+ if (m_hasSaturation) {
+ gfloat v = (saturation / 100.0) + 1; // [-100,100] -> [0,2]
+ g_object_set(G_OBJECT(m_videoSink), "saturation", v, nullptr);
+ }
+
+ return m_hasSaturation;
+ }
+};
+
+static bool qt_gst_element_is_functioning(GstElement *element)
+{
+ GstStateChangeReturn ret = gst_element_set_state(element, GST_STATE_READY);
+ if (ret == GST_STATE_CHANGE_SUCCESS) {
+ gst_element_set_state(element, GST_STATE_NULL);
+ return true;
+ }
+
+ return false;
+}
+
+static GstElement *findBestVideoSink()
+{
+ GstElement *choice = 0;
+ QString platform = QGuiApplication::platformName();
+
+ // We need a native window ID to use the GstVideoOverlay interface.
+ // Bail out if the Qt platform plugin in use cannot provide a sensible WId.
+ if (platform != QLatin1String("xcb"))
+ return 0;
+
+ // First, try some known video sinks, depending on the Qt platform plugin in use.
+ for (auto i : elementMap) {
+#if QT_CONFIG(gstreamer_gl)
+ if (!QGstUtils::useOpenGL() && qstrcmp(i.gstreamerElement, "glimagesink") == 0)
+ continue;
+#endif
+ if (platform == QLatin1String(i.qtPlatform)
+ && (choice = gst_element_factory_make(i.gstreamerElement, nullptr))) {
+
+ if (qt_gst_element_is_functioning(choice))
+ return choice;
+
+ gst_object_unref(choice);
+ choice = 0;
+ }
+ }
+
+ // If none of the known video sinks are available, try to find one that implements the
+ // GstVideoOverlay interface and has autoplugging rank.
+ GList *list = qt_gst_video_sinks();
+ for (GList *item = list; item != nullptr; item = item->next) {
+ GstElementFactory *f = GST_ELEMENT_FACTORY(item->data);
+
+ if (!gst_element_factory_has_interface(f, QT_GSTREAMER_VIDEOOVERLAY_INTERFACE_NAME))
+ continue;
+
+ if (GstElement *el = gst_element_factory_create(f, nullptr)) {
+ if (qt_gst_element_is_functioning(el)) {
+ choice = el;
+ break;
+ }
+
+ gst_object_unref(el);
+ }
+ }
+
+ gst_plugin_feature_list_free(list);
+
+ return choice;
+}
+
+QGstreamerVideoOverlay::QGstreamerVideoOverlay(QObject *parent, const QByteArray &elementName)
+ : QObject(parent)
+ , QGstreamerBufferProbe(QGstreamerBufferProbe::ProbeCaps)
+{
+ GstElement *sink = nullptr;
+ if (!elementName.isEmpty())
+ sink = gst_element_factory_make(elementName.constData(), nullptr);
+ else
+ sink = findBestVideoSink();
+
+ setVideoSink(sink);
+}
+
+QGstreamerVideoOverlay::~QGstreamerVideoOverlay()
+{
+ if (m_videoSink) {
+ delete m_sinkProperties;
+ GstPad *pad = gst_element_get_static_pad(m_videoSink, "sink");
+ removeProbeFromPad(pad);
+ gst_object_unref(GST_OBJECT(pad));
+ gst_object_unref(GST_OBJECT(m_videoSink));
+ }
+}
+
+GstElement *QGstreamerVideoOverlay::videoSink() const
+{
+ return m_videoSink;
+}
+
+void QGstreamerVideoOverlay::setVideoSink(GstElement *sink)
+{
+ if (!sink)
+ return;
+
+ if (m_videoSink)
+ gst_object_unref(GST_OBJECT(m_videoSink));
+
+ m_videoSink = sink;
+ qt_gst_object_ref_sink(GST_OBJECT(m_videoSink));
+
+ GstPad *pad = gst_element_get_static_pad(m_videoSink, "sink");
+ addProbeToPad(pad);
+ gst_object_unref(GST_OBJECT(pad));
+
+ QString sinkName(QLatin1String(GST_OBJECT_NAME(sink)));
+ bool isVaapi = sinkName.startsWith(QLatin1String("vaapisink"));
+ delete m_sinkProperties;
+ m_sinkProperties = isVaapi ? new QVaapiSinkProperties(sink) : new QXVImageSinkProperties(sink);
+
+ if (m_sinkProperties->hasShowPrerollFrame())
+ g_signal_connect(m_videoSink, "notify::show-preroll-frame",
+ G_CALLBACK(showPrerollFrameChanged), this);
+}
+
+QSize QGstreamerVideoOverlay::nativeVideoSize() const
+{
+ return m_nativeVideoSize;
+}
+
+void QGstreamerVideoOverlay::setWindowHandle(WId id)
+{
+ m_windowId = id;
+
+ if (isActive())
+ setWindowHandle_helper(id);
+}
+
+void QGstreamerVideoOverlay::setWindowHandle_helper(WId id)
+{
+ if (m_videoSink && GST_IS_VIDEO_OVERLAY(m_videoSink)) {
+ gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(m_videoSink), id);
+
+ // Properties need to be reset when changing the winId.
+ m_sinkProperties->reset();
+ }
+}
+
+void QGstreamerVideoOverlay::expose()
+{
+ if (!isActive())
+ return;
+
+ if (m_videoSink && GST_IS_VIDEO_OVERLAY(m_videoSink))
+ gst_video_overlay_expose(GST_VIDEO_OVERLAY(m_videoSink));
+}
+
+void QGstreamerVideoOverlay::setRenderRectangle(const QRect &rect)
+{
+ int x = -1;
+ int y = -1;
+ int w = -1;
+ int h = -1;
+
+ if (!rect.isEmpty()) {
+ x = rect.x();
+ y = rect.y();
+ w = rect.width();
+ h = rect.height();
+ }
+
+ if (m_videoSink && GST_IS_VIDEO_OVERLAY(m_videoSink))
+ gst_video_overlay_set_render_rectangle(GST_VIDEO_OVERLAY(m_videoSink), x, y, w, h);
+}
+
+bool QGstreamerVideoOverlay::processSyncMessage(const QGstreamerMessage &message)
+{
+ GstMessage* gm = message.rawMessage();
+
+ if (gm && (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ELEMENT) &&
+ gst_structure_has_name(gst_message_get_structure(gm), "prepare-window-handle")) {
+ setWindowHandle_helper(m_windowId);
+ return true;
+ }
+
+ return false;
+}
+
+bool QGstreamerVideoOverlay::processBusMessage(const QGstreamerMessage &message)
+{
+ GstMessage* gm = message.rawMessage();
+
+ if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_STATE_CHANGED &&
+ GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_videoSink)) {
+
+ updateIsActive();
+ }
+
+ return false;
+}
+
+void QGstreamerVideoOverlay::probeCaps(GstCaps *caps)
+{
+ QSize size = QGstUtils::capsCorrectedResolution(caps);
+ if (size != m_nativeVideoSize) {
+ m_nativeVideoSize = size;
+ emit nativeVideoSizeChanged();
+ }
+}
+
+bool QGstreamerVideoOverlay::isActive() const
+{
+ return m_isActive;
+}
+
+void QGstreamerVideoOverlay::updateIsActive()
+{
+ if (!m_videoSink)
+ return;
+
+ GstState state = GST_STATE(m_videoSink);
+ gboolean showPreroll = true;
+
+ if (m_sinkProperties->hasShowPrerollFrame())
+ g_object_get(G_OBJECT(m_videoSink), "show-preroll-frame", &showPreroll, nullptr);
+
+ bool newIsActive = (state == GST_STATE_PLAYING || (state == GST_STATE_PAUSED && showPreroll));
+
+ if (newIsActive != m_isActive) {
+ m_isActive = newIsActive;
+ emit activeChanged();
+ }
+}
+
+void QGstreamerVideoOverlay::showPrerollFrameChanged(GObject *, GParamSpec *, QGstreamerVideoOverlay *overlay)
+{
+ overlay->updateIsActive();
+}
+
+Qt::AspectRatioMode QGstreamerVideoOverlay::aspectRatioMode() const
+{
+ return m_sinkProperties->aspectRatioMode();
+}
+
+void QGstreamerVideoOverlay::setAspectRatioMode(Qt::AspectRatioMode mode)
+{
+ m_sinkProperties->setAspectRatioMode(mode);
+}
+
+int QGstreamerVideoOverlay::brightness() const
+{
+ return m_sinkProperties->brightness();
+}
+
+void QGstreamerVideoOverlay::setBrightness(int brightness)
+{
+ if (m_sinkProperties->setBrightness(brightness))
+ emit brightnessChanged(brightness);
+}
+
+int QGstreamerVideoOverlay::contrast() const
+{
+ return m_sinkProperties->contrast();
+}
+
+void QGstreamerVideoOverlay::setContrast(int contrast)
+{
+ if (m_sinkProperties->setContrast(contrast))
+ emit contrastChanged(contrast);
+}
+
+int QGstreamerVideoOverlay::hue() const
+{
+ return m_sinkProperties->hue();
+}
+
+void QGstreamerVideoOverlay::setHue(int hue)
+{
+ if (m_sinkProperties->setHue(hue))
+ emit hueChanged(hue);
+}
+
+int QGstreamerVideoOverlay::saturation() const
+{
+ return m_sinkProperties->saturation();
+}
+
+void QGstreamerVideoOverlay::setSaturation(int saturation)
+{
+ if (m_sinkProperties->setSaturation(saturation))
+ emit saturationChanged(saturation);
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/gstreamer/qgstreamervideooverlay_p.h b/src/multimedia/gstreamer/qgstreamervideooverlay_p.h
new file mode 100644
index 000000000..883da8a4d
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamervideooverlay_p.h
@@ -0,0 +1,127 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QGSTREAMERVIDEOOVERLAY_P_H
+#define QGSTREAMERVIDEOOVERLAY_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qgstreamerbushelper_p.h>
+#include <private/qgstreamerbufferprobe_p.h>
+#include <QtGui/qwindowdefs.h>
+#include <QtCore/qsize.h>
+
+QT_BEGIN_NAMESPACE
+
+class QGstreamerSinkProperties;
+class Q_MULTIMEDIA_EXPORT QGstreamerVideoOverlay
+ : public QObject
+ , public QGstreamerSyncMessageFilter
+ , public QGstreamerBusMessageFilter
+ , private QGstreamerBufferProbe
+{
+ Q_OBJECT
+ Q_INTERFACES(QGstreamerSyncMessageFilter QGstreamerBusMessageFilter)
+public:
+ explicit QGstreamerVideoOverlay(QObject *parent = 0, const QByteArray &elementName = QByteArray());
+ virtual ~QGstreamerVideoOverlay();
+
+ GstElement *videoSink() const;
+ void setVideoSink(GstElement *);
+ QSize nativeVideoSize() const;
+
+ void setWindowHandle(WId id);
+ void expose();
+ void setRenderRectangle(const QRect &rect);
+
+ bool isActive() const;
+
+ Qt::AspectRatioMode aspectRatioMode() const;
+ void setAspectRatioMode(Qt::AspectRatioMode mode);
+
+ int brightness() const;
+ void setBrightness(int brightness);
+
+ int contrast() const;
+ void setContrast(int contrast);
+
+ int hue() const;
+ void setHue(int hue);
+
+ int saturation() const;
+ void setSaturation(int saturation);
+
+ bool processSyncMessage(const QGstreamerMessage &message) override;
+ bool processBusMessage(const QGstreamerMessage &message) override;
+
+Q_SIGNALS:
+ void nativeVideoSizeChanged();
+ void activeChanged();
+ void brightnessChanged(int brightness);
+ void contrastChanged(int contrast);
+ void hueChanged(int hue);
+ void saturationChanged(int saturation);
+
+private:
+ void setWindowHandle_helper(WId id);
+ void updateIsActive();
+ void probeCaps(GstCaps *caps) override;
+ static void showPrerollFrameChanged(GObject *, GParamSpec *, QGstreamerVideoOverlay *);
+
+ GstElement *m_videoSink = nullptr;
+ QSize m_nativeVideoSize;
+ bool m_isActive = false;
+
+ QGstreamerSinkProperties *m_sinkProperties = nullptr;
+ WId m_windowId = 0;
+};
+
+QT_END_NAMESPACE
+
+#endif // QGSTREAMERVIDEOOVERLAY_P_H
+
diff --git a/src/multimedia/gstreamer/qgstreamervideoprobecontrol.cpp b/src/multimedia/gstreamer/qgstreamervideoprobecontrol.cpp
new file mode 100644
index 000000000..3d587eb2c
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamervideoprobecontrol.cpp
@@ -0,0 +1,117 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qgstreamervideoprobecontrol_p.h"
+
+#include "qgstutils_p.h"
+#include <private/qgstvideobuffer_p.h>
+
+QGstreamerVideoProbeControl::QGstreamerVideoProbeControl(QObject *parent)
+ : QMediaVideoProbeControl(parent)
+{
+}
+
+QGstreamerVideoProbeControl::~QGstreamerVideoProbeControl()
+{
+}
+
+void QGstreamerVideoProbeControl::startFlushing()
+{
+ m_flushing = true;
+
+ {
+ QMutexLocker locker(&m_frameMutex);
+ m_pendingFrame = QVideoFrame();
+ }
+
+ // only emit flush if at least one frame was probed
+ if (m_frameProbed)
+ emit flush();
+}
+
+void QGstreamerVideoProbeControl::stopFlushing()
+{
+ m_flushing = false;
+}
+
+void QGstreamerVideoProbeControl::probeCaps(GstCaps *caps)
+{
+ GstVideoInfo videoInfo;
+ QVideoSurfaceFormat format = QGstUtils::formatForCaps(caps, &videoInfo);
+
+ QMutexLocker locker(&m_frameMutex);
+ m_videoInfo = videoInfo;
+ m_format = format;
+}
+
+bool QGstreamerVideoProbeControl::probeBuffer(GstBuffer *buffer)
+{
+ QMutexLocker locker(&m_frameMutex);
+
+ if (m_flushing || !m_format.isValid())
+ return true;
+
+ QVideoFrame frame(
+ new QGstVideoBuffer(buffer, m_videoInfo),
+ m_format.frameSize(),
+ m_format.pixelFormat());
+
+ QGstUtils::setFrameTimeStamps(&frame, buffer);
+
+ m_frameProbed = true;
+
+ if (!m_pendingFrame.isValid())
+ QMetaObject::invokeMethod(this, "frameProbed", Qt::QueuedConnection);
+ m_pendingFrame = frame;
+
+ return true;
+}
+
+void QGstreamerVideoProbeControl::frameProbed()
+{
+ QVideoFrame frame;
+ {
+ QMutexLocker locker(&m_frameMutex);
+ if (!m_pendingFrame.isValid())
+ return;
+ frame = m_pendingFrame;
+ m_pendingFrame = QVideoFrame();
+ }
+ emit videoFrameProbed(frame);
+}
diff --git a/src/multimedia/gstreamer/qgstreamervideoprobecontrol_p.h b/src/multimedia/gstreamer/qgstreamervideoprobecontrol_p.h
new file mode 100644
index 000000000..0c4b734b2
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamervideoprobecontrol_p.h
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QGSTREAMERVIDEOPROBECONTROL_H
+#define QGSTREAMERVIDEOPROBECONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtmultimediaglobal_p.h>
+#include <gst/gst.h>
+#include <gst/video/video.h>
+#include <qmediavideoprobecontrol.h>
+#include <QtCore/qmutex.h>
+#include <qvideoframe.h>
+#include <qvideosurfaceformat.h>
+
+#include <private/qgstreamerbufferprobe_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_MULTIMEDIA_EXPORT QGstreamerVideoProbeControl
+ : public QMediaVideoProbeControl
+ , public QGstreamerBufferProbe
+ , public QSharedData
+{
+ Q_OBJECT
+public:
+ explicit QGstreamerVideoProbeControl(QObject *parent);
+ virtual ~QGstreamerVideoProbeControl();
+
+ void probeCaps(GstCaps *caps) override;
+ bool probeBuffer(GstBuffer *buffer) override;
+
+ void startFlushing();
+ void stopFlushing();
+
+private slots:
+ void frameProbed();
+
+private:
+ QVideoSurfaceFormat m_format;
+ QVideoFrame m_pendingFrame;
+ QMutex m_frameMutex;
+ GstVideoInfo m_videoInfo;
+ bool m_flushing = false;
+ bool m_frameProbed = false; // true if at least one frame was probed
+};
+
+QT_END_NAMESPACE
+
+#endif // QGSTREAMERVIDEOPROBECONTROL_H
diff --git a/src/multimedia/gstreamer/qgstreamervideorenderer.cpp b/src/multimedia/gstreamer/qgstreamervideorenderer.cpp
new file mode 100644
index 000000000..c6ca935a4
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamervideorenderer.cpp
@@ -0,0 +1,128 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qgstreamervideorenderer_p.h"
+#include <private/qgstvideorenderersink_p.h>
+#include <private/qgstutils_p.h>
+#include <qabstractvideosurface.h>
+#include <QtCore/qdebug.h>
+
+#include <gst/gst.h>
+
+static inline void resetSink(GstElement *&element, GstElement *v = nullptr)
+{
+ if (element)
+ gst_object_unref(GST_OBJECT(element));
+
+ if (v)
+ qt_gst_object_ref_sink(GST_OBJECT(v));
+
+ element = v;
+}
+
+QGstreamerVideoRenderer::QGstreamerVideoRenderer(QObject *parent)
+ : QVideoRendererControl(parent)
+{
+}
+
+QGstreamerVideoRenderer::~QGstreamerVideoRenderer()
+{
+ resetSink(m_videoSink);
+}
+
+void QGstreamerVideoRenderer::setVideoSink(GstElement *sink)
+{
+ if (!sink)
+ return;
+
+ resetSink(m_videoSink, sink);
+ emit sinkChanged();
+}
+
+GstElement *QGstreamerVideoRenderer::videoSink()
+{
+ if (!m_videoSink && m_surface) {
+ auto sink = reinterpret_cast<GstElement *>(QGstVideoRendererSink::createSink(m_surface));
+ resetSink(m_videoSink, sink);
+ }
+
+ return m_videoSink;
+}
+
+void QGstreamerVideoRenderer::stopRenderer()
+{
+ if (m_surface)
+ m_surface->stop();
+}
+
+QAbstractVideoSurface *QGstreamerVideoRenderer::surface() const
+{
+ return m_surface;
+}
+
+void QGstreamerVideoRenderer::setSurface(QAbstractVideoSurface *surface)
+{
+ if (m_surface != surface) {
+ resetSink(m_videoSink);
+
+ if (m_surface) {
+ disconnect(m_surface.data(), SIGNAL(supportedFormatsChanged()),
+ this, SLOT(handleFormatChange()));
+ }
+
+ bool wasReady = isReady();
+
+ m_surface = surface;
+
+ if (m_surface) {
+ connect(m_surface.data(), SIGNAL(supportedFormatsChanged()),
+ this, SLOT(handleFormatChange()));
+ }
+
+ if (wasReady != isReady())
+ emit readyChanged(isReady());
+
+ emit sinkChanged();
+ }
+}
+
+void QGstreamerVideoRenderer::handleFormatChange()
+{
+ setVideoSink(nullptr);
+}
diff --git a/src/multimedia/gstreamer/qgstreamervideorenderer_p.h b/src/multimedia/gstreamer/qgstreamervideorenderer_p.h
new file mode 100644
index 000000000..10d2c8e2e
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamervideorenderer_p.h
@@ -0,0 +1,94 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QGSTREAMERVIDEORENDERER_H
+#define QGSTREAMERVIDEORENDERER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtmultimediaglobal_p.h>
+#include <private/qgstvideorenderersink_p.h>
+#include <qvideorenderercontrol.h>
+#include <qabstractvideosurface.h>
+
+#include "qgstreamervideorendererinterface_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class Q_MULTIMEDIA_EXPORT QGstreamerVideoRenderer : public QVideoRendererControl, public QGstreamerVideoRendererInterface
+{
+ Q_OBJECT
+ Q_INTERFACES(QGstreamerVideoRendererInterface)
+public:
+ QGstreamerVideoRenderer(QObject *parent = 0);
+ virtual ~QGstreamerVideoRenderer();
+
+ QAbstractVideoSurface *surface() const override;
+ void setSurface(QAbstractVideoSurface *surface) override;
+
+ GstElement *videoSink() override;
+ void setVideoSink(GstElement *) override;
+
+ void stopRenderer() override;
+ bool isReady() const override { return m_surface != 0; }
+
+signals:
+ void sinkChanged();
+ void readyChanged(bool);
+
+private slots:
+ void handleFormatChange();
+
+private:
+ GstElement *m_videoSink = nullptr;
+ QPointer<QAbstractVideoSurface> m_surface;
+};
+
+QT_END_NAMESPACE
+
+#endif // QGSTREAMERVIDEORENDRER_H
diff --git a/src/multimedia/gstreamer/qgstreamervideorendererinterface.cpp b/src/multimedia/gstreamer/qgstreamervideorendererinterface.cpp
new file mode 100644
index 000000000..ae7de06f1
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamervideorendererinterface.cpp
@@ -0,0 +1,44 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qgstreamervideorendererinterface_p.h"
+
+QGstreamerVideoRendererInterface::~QGstreamerVideoRendererInterface()
+{
+}
diff --git a/src/multimedia/gstreamer/qgstreamervideorendererinterface_p.h b/src/multimedia/gstreamer/qgstreamervideorendererinterface_p.h
new file mode 100644
index 000000000..af163c1b5
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamervideorendererinterface_p.h
@@ -0,0 +1,86 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QGSTREAMERVIDEOOUTPUTCONTROL_H
+#define QGSTREAMERVIDEOOUTPUTCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtmultimediaglobal_p.h>
+#include <QtCore/qobject.h>
+#include <gst/gst.h>
+
+
+QT_BEGIN_NAMESPACE
+
+class Q_MULTIMEDIA_EXPORT QGstreamerVideoRendererInterface
+{
+public:
+ virtual ~QGstreamerVideoRendererInterface();
+ virtual GstElement *videoSink() = 0;
+ virtual void setVideoSink(GstElement *) {};
+
+ //stopRenderer() is called when the renderer element is stopped.
+ //it can be reimplemented when video renderer can't detect
+ //changes to NULL state but has to free video resources.
+ virtual void stopRenderer() {}
+
+ //the video output is configured, usually after the first paint event
+ //(winId is known,
+ virtual bool isReady() const { return true; }
+
+ //signals:
+ //void sinkChanged();
+ //void readyChanged(bool);
+};
+
+#define QGstreamerVideoRendererInterface_iid "org.qt-project.qt.gstreamervideorenderer/5.0"
+Q_DECLARE_INTERFACE(QGstreamerVideoRendererInterface, QGstreamerVideoRendererInterface_iid)
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/gstreamer/qgstreamervideowindow.cpp b/src/multimedia/gstreamer/qgstreamervideowindow.cpp
new file mode 100644
index 000000000..e7e3c5044
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamervideowindow.cpp
@@ -0,0 +1,179 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qgstreamervideowindow_p.h"
+#include <private/qgstutils_p.h>
+
+#include <QtCore/qdebug.h>
+
+QGstreamerVideoWindow::QGstreamerVideoWindow(QObject *parent, const QByteArray &elementName)
+ : QVideoWindowControl(parent)
+ , m_videoOverlay(this, !elementName.isEmpty() ? elementName : qgetenv("QT_GSTREAMER_WINDOW_VIDEOSINK"))
+{
+ connect(&m_videoOverlay, &QGstreamerVideoOverlay::nativeVideoSizeChanged,
+ this, &QGstreamerVideoWindow::nativeSizeChanged);
+ connect(&m_videoOverlay, &QGstreamerVideoOverlay::brightnessChanged,
+ this, &QGstreamerVideoWindow::brightnessChanged);
+ connect(&m_videoOverlay, &QGstreamerVideoOverlay::contrastChanged,
+ this, &QGstreamerVideoWindow::contrastChanged);
+ connect(&m_videoOverlay, &QGstreamerVideoOverlay::hueChanged,
+ this, &QGstreamerVideoWindow::hueChanged);
+ connect(&m_videoOverlay, &QGstreamerVideoOverlay::saturationChanged,
+ this, &QGstreamerVideoWindow::saturationChanged);
+}
+
+QGstreamerVideoWindow::~QGstreamerVideoWindow()
+{
+}
+
+GstElement *QGstreamerVideoWindow::videoSink()
+{
+ return m_videoOverlay.videoSink();
+}
+
+WId QGstreamerVideoWindow::winId() const
+{
+ return m_windowId;
+}
+
+void QGstreamerVideoWindow::setWinId(WId id)
+{
+ if (m_windowId == id)
+ return;
+
+ WId oldId = m_windowId;
+ m_videoOverlay.setWindowHandle(m_windowId = id);
+
+ if (!oldId)
+ emit readyChanged(true);
+
+ if (!id)
+ emit readyChanged(false);
+}
+
+bool QGstreamerVideoWindow::processSyncMessage(const QGstreamerMessage &message)
+{
+ return m_videoOverlay.processSyncMessage(message);
+}
+
+bool QGstreamerVideoWindow::processBusMessage(const QGstreamerMessage &message)
+{
+ return m_videoOverlay.processBusMessage(message);
+}
+
+QRect QGstreamerVideoWindow::displayRect() const
+{
+ return m_displayRect;
+}
+
+void QGstreamerVideoWindow::setDisplayRect(const QRect &rect)
+{
+ m_videoOverlay.setRenderRectangle(m_displayRect = rect);
+ repaint();
+}
+
+Qt::AspectRatioMode QGstreamerVideoWindow::aspectRatioMode() const
+{
+ return m_videoOverlay.aspectRatioMode();
+}
+
+void QGstreamerVideoWindow::setAspectRatioMode(Qt::AspectRatioMode mode)
+{
+ m_videoOverlay.setAspectRatioMode(mode);
+}
+
+void QGstreamerVideoWindow::repaint()
+{
+ m_videoOverlay.expose();
+}
+
+int QGstreamerVideoWindow::brightness() const
+{
+ return m_videoOverlay.brightness();
+}
+
+void QGstreamerVideoWindow::setBrightness(int brightness)
+{
+ m_videoOverlay.setBrightness(brightness);
+}
+
+int QGstreamerVideoWindow::contrast() const
+{
+ return m_videoOverlay.contrast();
+}
+
+void QGstreamerVideoWindow::setContrast(int contrast)
+{
+ m_videoOverlay.setContrast(contrast);
+}
+
+int QGstreamerVideoWindow::hue() const
+{
+ return m_videoOverlay.hue();
+}
+
+void QGstreamerVideoWindow::setHue(int hue)
+{
+ m_videoOverlay.setHue(hue);
+}
+
+int QGstreamerVideoWindow::saturation() const
+{
+ return m_videoOverlay.saturation();
+}
+
+void QGstreamerVideoWindow::setSaturation(int saturation)
+{
+ m_videoOverlay.setSaturation(saturation);
+}
+
+bool QGstreamerVideoWindow::isFullScreen() const
+{
+ return m_fullScreen;
+}
+
+void QGstreamerVideoWindow::setFullScreen(bool fullScreen)
+{
+ emit fullScreenChanged(m_fullScreen = fullScreen);
+}
+
+QSize QGstreamerVideoWindow::nativeSize() const
+{
+ return m_videoOverlay.nativeVideoSize();
+}
diff --git a/src/multimedia/gstreamer/qgstreamervideowindow_p.h b/src/multimedia/gstreamer/qgstreamervideowindow_p.h
new file mode 100644
index 000000000..cae656347
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstreamervideowindow_p.h
@@ -0,0 +1,127 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QGSTREAMERVIDEOWINDOW_H
+#define QGSTREAMERVIDEOWINDOW_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtmultimediaglobal_p.h>
+#include <qvideowindowcontrol.h>
+
+#include "qgstreamervideorendererinterface_p.h"
+#include <private/qgstreamerbushelper_p.h>
+#include <private/qgstreamervideooverlay_p.h>
+#include <QtGui/qcolor.h>
+
+QT_BEGIN_NAMESPACE
+class QAbstractVideoSurface;
+
+class Q_MULTIMEDIA_EXPORT QGstreamerVideoWindow :
+ public QVideoWindowControl,
+ public QGstreamerVideoRendererInterface,
+ public QGstreamerSyncMessageFilter,
+ public QGstreamerBusMessageFilter
+{
+ Q_OBJECT
+ Q_INTERFACES(QGstreamerVideoRendererInterface QGstreamerSyncMessageFilter QGstreamerBusMessageFilter)
+public:
+ explicit QGstreamerVideoWindow(QObject *parent = 0, const QByteArray &elementName = QByteArray());
+ ~QGstreamerVideoWindow();
+
+ WId winId() const override;
+ void setWinId(WId id) override;
+
+ QRect displayRect() const override;
+ void setDisplayRect(const QRect &rect) override;
+
+ bool isFullScreen() const override;
+ void setFullScreen(bool fullScreen) override;
+
+ QSize nativeSize() const override;
+
+ Qt::AspectRatioMode aspectRatioMode() const override;
+ void setAspectRatioMode(Qt::AspectRatioMode mode) override;
+
+ void repaint() override;
+
+ int brightness() const override;
+ void setBrightness(int brightness) override;
+
+ int contrast() const override;
+ void setContrast(int contrast) override;
+
+ int hue() const override;
+ void setHue(int hue) override;
+
+ int saturation() const override;
+ void setSaturation(int saturation) override;
+
+ QAbstractVideoSurface *surface() const;
+
+ GstElement *videoSink() override;
+
+ bool processSyncMessage(const QGstreamerMessage &message) override;
+ bool processBusMessage(const QGstreamerMessage &message) override;
+ bool isReady() const override { return m_windowId != 0; }
+
+signals:
+ void sinkChanged();
+ void readyChanged(bool);
+
+private:
+ QGstreamerVideoOverlay m_videoOverlay;
+ WId m_windowId = 0;
+ QRect m_displayRect;
+ bool m_fullScreen = false;
+ mutable QColor m_colorKey = QColor::Invalid;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/gstreamer/qgstutils.cpp b/src/multimedia/gstreamer/qgstutils.cpp
new file mode 100644
index 000000000..8891aad87
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstutils.cpp
@@ -0,0 +1,1150 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtMultimedia/private/qtmultimediaglobal_p.h>
+#include "qgstutils_p.h"
+
+#include <QtCore/qdatetime.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qvariant.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qsize.h>
+#include <QtCore/qset.h>
+#include <QtCore/qstringlist.h>
+#include <QtGui/qimage.h>
+#include <qaudioformat.h>
+#include <QtCore/qelapsedtimer.h>
+#include <QtMultimedia/qvideosurfaceformat.h>
+#include <private/qmultimediautils_p.h>
+
+#include <gst/audio/audio.h>
+#include <gst/video/video.h>
+
+template<typename T, int N> static int lengthOf(const T (&)[N]) { return N; }
+
+#include "qgstreamervideoinputdevicecontrol_p.h"
+
+QT_BEGIN_NAMESPACE
+
+//internal
+static void addTagToMap(const GstTagList *list,
+ const gchar *tag,
+ gpointer user_data)
+{
+ QMap<QByteArray, QVariant> *map = reinterpret_cast<QMap<QByteArray, QVariant>* >(user_data);
+
+ GValue val;
+ val.g_type = 0;
+ gst_tag_list_copy_value(&val,list,tag);
+
+ switch( G_VALUE_TYPE(&val) ) {
+ case G_TYPE_STRING:
+ {
+ const gchar *str_value = g_value_get_string(&val);
+ map->insert(QByteArray(tag), QString::fromUtf8(str_value));
+ break;
+ }
+ case G_TYPE_INT:
+ map->insert(QByteArray(tag), g_value_get_int(&val));
+ break;
+ case G_TYPE_UINT:
+ map->insert(QByteArray(tag), g_value_get_uint(&val));
+ break;
+ case G_TYPE_LONG:
+ map->insert(QByteArray(tag), qint64(g_value_get_long(&val)));
+ break;
+ case G_TYPE_BOOLEAN:
+ map->insert(QByteArray(tag), g_value_get_boolean(&val));
+ break;
+ case G_TYPE_CHAR:
+#if GLIB_CHECK_VERSION(2,32,0)
+ map->insert(QByteArray(tag), g_value_get_schar(&val));
+#else
+ map->insert(QByteArray(tag), g_value_get_char(&val));
+#endif
+ break;
+ case G_TYPE_DOUBLE:
+ map->insert(QByteArray(tag), g_value_get_double(&val));
+ break;
+ default:
+ // GST_TYPE_DATE is a function, not a constant, so pull it out of the switch
+ if (G_VALUE_TYPE(&val) == G_TYPE_DATE) {
+ const GDate *date = (const GDate *)g_value_get_boxed(&val);
+ if (g_date_valid(date)) {
+ int year = g_date_get_year(date);
+ int month = g_date_get_month(date);
+ int day = g_date_get_day(date);
+ map->insert(QByteArray(tag), QDate(year,month,day));
+ if (!map->contains("year"))
+ map->insert("year", year);
+ }
+ } else if (G_VALUE_TYPE(&val) == GST_TYPE_DATE_TIME) {
+ const GstDateTime *dateTime = (const GstDateTime *)g_value_get_boxed(&val);
+ int year = gst_date_time_has_year(dateTime) ? gst_date_time_get_year(dateTime) : 0;
+ int month = gst_date_time_has_month(dateTime) ? gst_date_time_get_month(dateTime) : 0;
+ int day = gst_date_time_has_day(dateTime) ? gst_date_time_get_day(dateTime) : 0;
+ if (gst_date_time_has_time(dateTime)) {
+ int hour = gst_date_time_get_hour(dateTime);
+ int minute = gst_date_time_get_minute(dateTime);
+ int second = gst_date_time_get_second(dateTime);
+ float tz = gst_date_time_get_time_zone_offset(dateTime);
+ QDateTime dateTime(QDate(year, month, day), QTime(hour, minute, second),
+ Qt::OffsetFromUTC, tz * 60 * 60);
+ map->insert(QByteArray(tag), dateTime);
+ } else if (year > 0 && month > 0 && day > 0) {
+ map->insert(QByteArray(tag), QDate(year,month,day));
+ }
+ if (!map->contains("year") && year > 0)
+ map->insert("year", year);
+ } else if (G_VALUE_TYPE(&val) == GST_TYPE_SAMPLE) {
+ GstSample *sample = (GstSample *)g_value_get_boxed(&val);
+ GstCaps* caps = gst_sample_get_caps(sample);
+ if (caps && !gst_caps_is_empty(caps)) {
+ GstStructure *structure = gst_caps_get_structure(caps, 0);
+ const gchar *name = gst_structure_get_name(structure);
+ if (QByteArray(name).startsWith("image/")) {
+ GstBuffer *buffer = gst_sample_get_buffer(sample);
+ if (buffer) {
+ GstMapInfo info;
+ gst_buffer_map(buffer, &info, GST_MAP_READ);
+ map->insert(QByteArray(tag), QImage::fromData(info.data, info.size, name));
+ gst_buffer_unmap(buffer, &info);
+ }
+ }
+ }
+ } else if (G_VALUE_TYPE(&val) == GST_TYPE_FRACTION) {
+ int nom = gst_value_get_fraction_numerator(&val);
+ int denom = gst_value_get_fraction_denominator(&val);
+
+ if (denom > 0) {
+ map->insert(QByteArray(tag), double(nom)/denom);
+ }
+ }
+ break;
+ }
+
+ g_value_unset(&val);
+}
+
+/*!
+ \class QGstUtils
+ \internal
+*/
+
+/*!
+ Convert GstTagList structure to QMap<QByteArray, QVariant>.
+
+ Mapping to int, bool, char, string, fractions and date are supported.
+ Fraction values are converted to doubles.
+*/
+QMap<QByteArray, QVariant> QGstUtils::gstTagListToMap(const GstTagList *tags)
+{
+ QMap<QByteArray, QVariant> res;
+ gst_tag_list_foreach(tags, addTagToMap, &res);
+
+ return res;
+}
+
+/*!
+ Returns resolution of \a caps.
+ If caps doesn't have a valid size, an empty QSize is returned.
+*/
+QSize QGstUtils::capsResolution(const GstCaps *caps)
+{
+ if (gst_caps_get_size(caps) == 0)
+ return QSize();
+
+ return structureResolution(gst_caps_get_structure(caps, 0));
+}
+
+/*!
+ Returns aspect ratio corrected resolution of \a caps.
+ If caps doesn't have a valid size, an empty QSize is returned.
+*/
+QSize QGstUtils::capsCorrectedResolution(const GstCaps *caps)
+{
+ QSize size;
+
+ if (caps) {
+ size = capsResolution(caps);
+
+ gint aspectNum = 0;
+ gint aspectDenum = 0;
+ if (!size.isEmpty() && gst_structure_get_fraction(
+ gst_caps_get_structure(caps, 0), "pixel-aspect-ratio", &aspectNum, &aspectDenum)) {
+ if (aspectDenum > 0)
+ size.setWidth(size.width()*aspectNum/aspectDenum);
+ }
+ }
+
+ return size;
+}
+
+
+namespace {
+
+struct AudioFormat
+{
+ GstAudioFormat format;
+ QAudioFormat::SampleType sampleType;
+ QAudioFormat::Endian byteOrder;
+ int sampleSize;
+};
+static const AudioFormat qt_audioLookup[] =
+{
+ { GST_AUDIO_FORMAT_S8 , QAudioFormat::SignedInt , QAudioFormat::LittleEndian, 8 },
+ { GST_AUDIO_FORMAT_U8 , QAudioFormat::UnSignedInt, QAudioFormat::LittleEndian, 8 },
+ { GST_AUDIO_FORMAT_S16LE, QAudioFormat::SignedInt , QAudioFormat::LittleEndian, 16 },
+ { GST_AUDIO_FORMAT_S16BE, QAudioFormat::SignedInt , QAudioFormat::BigEndian , 16 },
+ { GST_AUDIO_FORMAT_U16LE, QAudioFormat::UnSignedInt, QAudioFormat::LittleEndian, 16 },
+ { GST_AUDIO_FORMAT_U16BE, QAudioFormat::UnSignedInt, QAudioFormat::BigEndian , 16 },
+ { GST_AUDIO_FORMAT_S32LE, QAudioFormat::SignedInt , QAudioFormat::LittleEndian, 32 },
+ { GST_AUDIO_FORMAT_S32BE, QAudioFormat::SignedInt , QAudioFormat::BigEndian , 32 },
+ { GST_AUDIO_FORMAT_U32LE, QAudioFormat::UnSignedInt, QAudioFormat::LittleEndian, 32 },
+ { GST_AUDIO_FORMAT_U32BE, QAudioFormat::UnSignedInt, QAudioFormat::BigEndian , 32 },
+ { GST_AUDIO_FORMAT_S24LE, QAudioFormat::SignedInt , QAudioFormat::LittleEndian, 24 },
+ { GST_AUDIO_FORMAT_S24BE, QAudioFormat::SignedInt , QAudioFormat::BigEndian , 24 },
+ { GST_AUDIO_FORMAT_U24LE, QAudioFormat::UnSignedInt, QAudioFormat::LittleEndian, 24 },
+ { GST_AUDIO_FORMAT_U24BE, QAudioFormat::UnSignedInt, QAudioFormat::BigEndian , 24 },
+ { GST_AUDIO_FORMAT_F32LE, QAudioFormat::Float , QAudioFormat::LittleEndian, 32 },
+ { GST_AUDIO_FORMAT_F32BE, QAudioFormat::Float , QAudioFormat::BigEndian , 32 },
+ { GST_AUDIO_FORMAT_F64LE, QAudioFormat::Float , QAudioFormat::LittleEndian, 64 },
+ { GST_AUDIO_FORMAT_F64BE, QAudioFormat::Float , QAudioFormat::BigEndian , 64 }
+};
+
+}
+
+/*!
+ Returns audio format for caps.
+ If caps doesn't have a valid audio format, an empty QAudioFormat is returned.
+*/
+
+QAudioFormat QGstUtils::audioFormatForCaps(const GstCaps *caps)
+{
+ QAudioFormat format;
+ GstAudioInfo info;
+ if (gst_audio_info_from_caps(&info, caps)) {
+ for (int i = 0; i < lengthOf(qt_audioLookup); ++i) {
+ if (qt_audioLookup[i].format != info.finfo->format)
+ continue;
+
+ format.setSampleType(qt_audioLookup[i].sampleType);
+ format.setByteOrder(qt_audioLookup[i].byteOrder);
+ format.setSampleSize(qt_audioLookup[i].sampleSize);
+ format.setSampleRate(info.rate);
+ format.setChannelCount(info.channels);
+ format.setCodec(QStringLiteral("audio/pcm"));
+
+ return format;
+ }
+ }
+
+ return format;
+}
+
+/*
+ Returns audio format for a sample.
+ If the buffer doesn't have a valid audio format, an empty QAudioFormat is returned.
+*/
+QAudioFormat QGstUtils::audioFormatForSample(GstSample *sample)
+{
+ GstCaps* caps = gst_sample_get_caps(sample);
+ if (!caps)
+ return QAudioFormat();
+
+ return QGstUtils::audioFormatForCaps(caps);
+}
+
+/*!
+ Builds GstCaps for an audio format.
+ Returns 0 if the audio format is not valid.
+ Caller must unref GstCaps.
+*/
+
+GstCaps *QGstUtils::capsForAudioFormat(const QAudioFormat &format)
+{
+ if (!format.isValid())
+ return 0;
+
+ const QAudioFormat::SampleType sampleType = format.sampleType();
+ const QAudioFormat::Endian byteOrder = format.byteOrder();
+ const int sampleSize = format.sampleSize();
+
+ for (int i = 0; i < lengthOf(qt_audioLookup); ++i) {
+ if (qt_audioLookup[i].sampleType != sampleType
+ || qt_audioLookup[i].byteOrder != byteOrder
+ || qt_audioLookup[i].sampleSize != sampleSize) {
+ continue;
+ }
+
+ return gst_caps_new_simple(
+ "audio/x-raw",
+ "format" , G_TYPE_STRING, gst_audio_format_to_string(qt_audioLookup[i].format),
+ "rate" , G_TYPE_INT , format.sampleRate(),
+ "channels", G_TYPE_INT , format.channelCount(),
+ nullptr);
+ }
+ return 0;
+}
+
+static QSet<GstDevice *> m_videoSources;
+static QSet<GstDevice *> m_audioSources;
+static QSet<GstDevice *> m_audioSinks;
+
+static void addDevice(GstDevice *device)
+{
+ gchar *type = gst_device_get_device_class(device);
+// qDebug() << "adding device:" << device << type << gst_device_get_display_name(device);
+ gst_object_ref(device);
+ if (!strcmp(type, "Video/Source"))
+ m_videoSources.insert(device);
+ else if (!strcmp(type, "Audio/Source"))
+ m_audioSources.insert(device);
+ else if (!strcmp(type, "Audio/Sink"))
+ m_audioSinks.insert(device);
+ else
+ gst_object_unref(device);
+ g_free(type);
+}
+
+static void removeDevice(GstDevice *device)
+{
+// qDebug() << "removing device:" << device << gst_device_get_display_name(device);
+ if (m_videoSources.remove(device) ||
+ m_audioSources.remove(device) ||
+ m_audioSinks.remove(device))
+ gst_object_unref(device);
+}
+
+static gboolean deviceMonitor(GstBus *, GstMessage *message, gpointer)
+{
+ GstDevice *device = nullptr;
+
+ switch (GST_MESSAGE_TYPE (message)) {
+ case GST_MESSAGE_DEVICE_ADDED:
+ gst_message_parse_device_added (message, &device);
+ addDevice(device);
+ break;
+ case GST_MESSAGE_DEVICE_REMOVED:
+ gst_message_parse_device_removed (message, &device);
+ removeDevice(device);
+ break;
+ default:
+ break;
+ }
+ if (device)
+ gst_object_unref (device);
+
+ return G_SOURCE_CONTINUE;
+}
+
+void setupDeviceMonitor()
+{
+ GstDeviceMonitor *monitor;
+ GstBus *bus;
+
+ monitor = gst_device_monitor_new();
+
+ bus = gst_device_monitor_get_bus(monitor);
+ gst_bus_add_watch(bus, deviceMonitor, NULL);
+ gst_object_unref(bus);
+
+ gst_device_monitor_add_filter (monitor, "Video/Source", NULL);
+ gst_device_monitor_add_filter (monitor, "Audio/Source", NULL);
+ gst_device_monitor_add_filter (monitor, "Audio/Sink", NULL);
+
+ auto devices = gst_device_monitor_get_devices(monitor);
+
+ while (devices) {
+ GstDevice *device = static_cast<GstDevice *>(devices->data);
+ addDevice(device);
+ gst_object_unref(device);
+ devices = g_list_delete_link(devices, devices);
+ }
+
+ gst_device_monitor_start(monitor);
+}
+
+
+void QGstUtils::initializeGst()
+{
+ static bool initialized = false;
+ if (!initialized) {
+ initialized = true;
+ gst_init(nullptr, nullptr);
+ setupDeviceMonitor();
+ }
+}
+
+namespace {
+ const char* getCodecAlias(const QString &codec)
+ {
+ if (codec.startsWith(QLatin1String("avc1.")))
+ return "video/x-h264";
+
+ if (codec.startsWith(QLatin1String("mp4a.")))
+ return "audio/mpeg4";
+
+ if (codec.startsWith(QLatin1String("mp4v.20.")))
+ return "video/mpeg4";
+
+ if (codec == QLatin1String("samr"))
+ return "audio/amr";
+
+ return 0;
+ }
+
+ const char* getMimeTypeAlias(const QString &mimeType)
+ {
+ if (mimeType == QLatin1String("video/mp4"))
+ return "video/mpeg4";
+
+ if (mimeType == QLatin1String("audio/mp4"))
+ return "audio/mpeg4";
+
+ if (mimeType == QLatin1String("video/ogg")
+ || mimeType == QLatin1String("audio/ogg"))
+ return "application/ogg";
+
+ return 0;
+ }
+}
+
+QMultimedia::SupportEstimate QGstUtils::hasSupport(const QString &mimeType,
+ const QStringList &codecs,
+ const QSet<QString> &supportedMimeTypeSet)
+{
+ if (supportedMimeTypeSet.isEmpty())
+ return QMultimedia::NotSupported;
+
+ QString mimeTypeLowcase = mimeType.toLower();
+ bool containsMimeType = supportedMimeTypeSet.contains(mimeTypeLowcase);
+ if (!containsMimeType) {
+ const char* mimeTypeAlias = getMimeTypeAlias(mimeTypeLowcase);
+ containsMimeType = supportedMimeTypeSet.contains(QLatin1String(mimeTypeAlias));
+ if (!containsMimeType) {
+ containsMimeType = supportedMimeTypeSet.contains(QLatin1String("video/") + mimeTypeLowcase)
+ || supportedMimeTypeSet.contains(QLatin1String("video/x-") + mimeTypeLowcase)
+ || supportedMimeTypeSet.contains(QLatin1String("audio/") + mimeTypeLowcase)
+ || supportedMimeTypeSet.contains(QLatin1String("audio/x-") + mimeTypeLowcase);
+ }
+ }
+
+ int supportedCodecCount = 0;
+ for (const QString &codec : codecs) {
+ QString codecLowcase = codec.toLower();
+ const char* codecAlias = getCodecAlias(codecLowcase);
+ if (codecAlias) {
+ if (supportedMimeTypeSet.contains(QLatin1String(codecAlias)))
+ supportedCodecCount++;
+ } else if (supportedMimeTypeSet.contains(QLatin1String("video/") + codecLowcase)
+ || supportedMimeTypeSet.contains(QLatin1String("video/x-") + codecLowcase)
+ || supportedMimeTypeSet.contains(QLatin1String("audio/") + codecLowcase)
+ || supportedMimeTypeSet.contains(QLatin1String("audio/x-") + codecLowcase)) {
+ supportedCodecCount++;
+ }
+ }
+ if (supportedCodecCount > 0 && supportedCodecCount == codecs.size())
+ return QMultimedia::ProbablySupported;
+
+ if (supportedCodecCount == 0 && !containsMimeType)
+ return QMultimedia::NotSupported;
+
+ return QMultimedia::MaybeSupported;
+}
+
+QList<QGstUtils::CameraInfo> QGstUtils::enumerateCameras()
+{
+ initializeGst();
+
+ QList<CameraInfo> devices;
+
+ for (auto *d : qAsConst(m_videoSources)) {
+ auto *properties = gst_device_get_properties(d);
+ if (properties) {
+ CameraInfo info;
+ auto *desc = gst_device_get_display_name(d);
+ info.description = QString::fromUtf8(desc);
+ g_free(desc);
+
+ auto *name = gst_structure_get_string(properties, "device.path");
+ info.name = QString::fromUtf8(name);
+ info.driver = gst_structure_get_string(properties, "v4l2.device.driver");
+ info.orientation = 0;
+ info.position = QCamera::UnspecifiedPosition;
+ gst_structure_free(properties);
+
+ devices.append(info);
+ }
+ }
+ return devices;
+}
+
+QList<QByteArray> QGstUtils::cameraDevices()
+{
+ QList<QByteArray> devices;
+
+ const auto cameras = enumerateCameras();
+ devices.reserve(cameras.size());
+ for (const CameraInfo &camera : cameras)
+ devices.append(camera.name.toUtf8());
+
+ return devices;
+}
+
+QString QGstUtils::cameraDescription(const QString &device)
+{
+ const auto cameras = enumerateCameras();
+ for (const CameraInfo &camera : cameras) {
+ if (camera.name == device)
+ return camera.description;
+ }
+ return QString();
+}
+
+QCamera::Position QGstUtils::cameraPosition(const QString &device)
+{
+ const auto cameras = enumerateCameras();
+ for (const CameraInfo &camera : cameras) {
+ if (camera.name == device)
+ return camera.position;
+ }
+ return QCamera::UnspecifiedPosition;
+}
+
+int QGstUtils::cameraOrientation(const QString &device)
+{
+ const auto cameras = enumerateCameras();
+ for (const CameraInfo &camera : cameras) {
+ if (camera.name == device)
+ return camera.orientation;
+ }
+ return 0;
+}
+
+QByteArray QGstUtils::cameraDriver(const QString &device)
+{
+ const auto cameras = enumerateCameras();
+ for (const CameraInfo &camera : cameras) {
+ if (camera.name == device)
+ return camera.driver;
+ }
+ return QByteArray();
+}
+
+
+const QSet<GstDevice *> &QGstUtils::audioSources()
+{
+ return m_audioSources;
+}
+
+const QSet<GstDevice *> &QGstUtils::audioSinks()
+{
+ return m_audioSinks;
+}
+
+QSet<QString> QGstUtils::supportedMimeTypes(bool (*isValidFactory)(GstElementFactory *factory))
+{
+ QSet<QString> supportedMimeTypes;
+
+ //enumerate supported mime types
+ gst_init(nullptr, nullptr);
+
+ GstRegistry *registry = gst_registry_get();
+ GList *orig_plugins = gst_registry_get_plugin_list(registry);
+ for (GList *plugins = orig_plugins; plugins; plugins = g_list_next(plugins)) {
+ GstPlugin *plugin = (GstPlugin *) (plugins->data);
+ if (GST_OBJECT_FLAG_IS_SET(GST_OBJECT(plugin), GST_PLUGIN_FLAG_BLACKLISTED))
+ continue;
+
+ GList *orig_features = gst_registry_get_feature_list_by_plugin(
+ registry, gst_plugin_get_name(plugin));
+ for (GList *features = orig_features; features; features = g_list_next(features)) {
+ if (G_UNLIKELY(features->data == nullptr))
+ continue;
+
+ GstPluginFeature *feature = GST_PLUGIN_FEATURE(features->data);
+ GstElementFactory *factory;
+
+ if (GST_IS_TYPE_FIND_FACTORY(feature)) {
+ QString name(QLatin1String(gst_plugin_feature_get_name(feature)));
+ if (name.contains(QLatin1Char('/'))) //filter out any string without '/' which is obviously not a mime type
+ supportedMimeTypes.insert(name.toLower());
+ continue;
+ } else if (!GST_IS_ELEMENT_FACTORY (feature)
+ || !(factory = GST_ELEMENT_FACTORY(gst_plugin_feature_load(feature)))) {
+ continue;
+ } else if (!isValidFactory(factory)) {
+ // Do nothing
+ } else for (const GList *pads = gst_element_factory_get_static_pad_templates(factory);
+ pads;
+ pads = g_list_next(pads)) {
+ GstStaticPadTemplate *padtemplate = static_cast<GstStaticPadTemplate *>(pads->data);
+
+ if (padtemplate->direction == GST_PAD_SINK && padtemplate->static_caps.string) {
+ GstCaps *caps = gst_static_caps_get(&padtemplate->static_caps);
+ if (gst_caps_is_any(caps) || gst_caps_is_empty(caps)) {
+ } else for (guint i = 0; i < gst_caps_get_size(caps); i++) {
+ GstStructure *structure = gst_caps_get_structure(caps, i);
+ QString nameLowcase = QString::fromLatin1(gst_structure_get_name(structure)).toLower();
+
+ supportedMimeTypes.insert(nameLowcase);
+ if (nameLowcase.contains(QLatin1String("mpeg"))) {
+ //Because mpeg version number is only included in the detail
+ //description, it is necessary to manually extract this information
+ //in order to match the mime type of mpeg4.
+ const GValue *value = gst_structure_get_value(structure, "mpegversion");
+ if (value) {
+ gchar *str = gst_value_serialize(value);
+ QString versions = QLatin1String(str);
+ const QStringList elements = versions.split(QRegularExpression(QLatin1String("\\D+")), Qt::SkipEmptyParts);
+ for (const QString &e : elements)
+ supportedMimeTypes.insert(nameLowcase + e);
+ g_free(str);
+ }
+ }
+ }
+ }
+ }
+ gst_object_unref(factory);
+ }
+ gst_plugin_feature_list_free(orig_features);
+ }
+ gst_plugin_list_free (orig_plugins);
+
+#if defined QT_SUPPORTEDMIMETYPES_DEBUG
+ QStringList list = supportedMimeTypes.toList();
+ list.sort();
+ if (qgetenv("QT_DEBUG_PLUGINS").toInt() > 0) {
+ for (const QString &type : qAsConst(list))
+ qDebug() << type;
+ }
+#endif
+ return supportedMimeTypes;
+}
+
+namespace {
+
+struct ColorFormat { QImage::Format imageFormat; GstVideoFormat gstFormat; };
+static const ColorFormat qt_colorLookup[] =
+{
+ { QImage::Format_RGBX8888, GST_VIDEO_FORMAT_RGBx },
+ { QImage::Format_RGBA8888, GST_VIDEO_FORMAT_RGBA },
+ { QImage::Format_RGB888 , GST_VIDEO_FORMAT_RGB },
+ { QImage::Format_RGB16 , GST_VIDEO_FORMAT_RGB16 }
+};
+
+}
+
+QImage QGstUtils::bufferToImage(GstBuffer *buffer, const GstVideoInfo &videoInfo)
+{
+ QImage img;
+
+ GstVideoInfo info = videoInfo;
+ GstVideoFrame frame;
+ if (!gst_video_frame_map(&frame, &info, buffer, GST_MAP_READ))
+ return img;
+
+ if (videoInfo.finfo->format == GST_VIDEO_FORMAT_I420) {
+ const int width = videoInfo.width;
+ const int height = videoInfo.height;
+
+ const int stride[] = { frame.info.stride[0], frame.info.stride[1], frame.info.stride[2] };
+ const uchar *data[] = {
+ static_cast<const uchar *>(frame.data[0]),
+ static_cast<const uchar *>(frame.data[1]),
+ static_cast<const uchar *>(frame.data[2])
+ };
+ img = QImage(width/2, height/2, QImage::Format_RGB32);
+
+ for (int y=0; y<height; y+=2) {
+ const uchar *yLine = data[0] + (y * stride[0]);
+ const uchar *uLine = data[1] + (y * stride[1] / 2);
+ const uchar *vLine = data[2] + (y * stride[2] / 2);
+
+ for (int x=0; x<width; x+=2) {
+ const qreal Y = 1.164*(yLine[x]-16);
+ const int U = uLine[x/2]-128;
+ const int V = vLine[x/2]-128;
+
+ int b = qBound(0, int(Y + 2.018*U), 255);
+ int g = qBound(0, int(Y - 0.813*V - 0.391*U), 255);
+ int r = qBound(0, int(Y + 1.596*V), 255);
+
+ img.setPixel(x/2,y/2,qRgb(r,g,b));
+ }
+ }
+ } else for (int i = 0; i < lengthOf(qt_colorLookup); ++i) {
+ if (qt_colorLookup[i].gstFormat != videoInfo.finfo->format)
+ continue;
+
+ const QImage image(
+ static_cast<const uchar *>(frame.data[0]),
+ videoInfo.width,
+ videoInfo.height,
+ frame.info.stride[0],
+ qt_colorLookup[i].imageFormat);
+ img = image;
+ img.detach();
+
+ break;
+ }
+
+ gst_video_frame_unmap(&frame);
+
+ return img;
+}
+
+
+namespace {
+
+struct VideoFormat
+{
+ QVideoFrame::PixelFormat pixelFormat;
+ GstVideoFormat gstFormat;
+};
+
+static const VideoFormat qt_videoFormatLookup[] =
+{
+ { QVideoFrame::Format_YUV420P, GST_VIDEO_FORMAT_I420 },
+ { QVideoFrame::Format_YUV422P, GST_VIDEO_FORMAT_Y42B },
+ { QVideoFrame::Format_YV12 , GST_VIDEO_FORMAT_YV12 },
+ { QVideoFrame::Format_UYVY , GST_VIDEO_FORMAT_UYVY },
+ { QVideoFrame::Format_YUYV , GST_VIDEO_FORMAT_YUY2 },
+ { QVideoFrame::Format_NV12 , GST_VIDEO_FORMAT_NV12 },
+ { QVideoFrame::Format_NV21 , GST_VIDEO_FORMAT_NV21 },
+ { QVideoFrame::Format_AYUV444, GST_VIDEO_FORMAT_AYUV },
+#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
+ { QVideoFrame::Format_RGB32 , GST_VIDEO_FORMAT_BGRx },
+ { QVideoFrame::Format_BGR32 , GST_VIDEO_FORMAT_RGBx },
+ { QVideoFrame::Format_ARGB32, GST_VIDEO_FORMAT_BGRA },
+ { QVideoFrame::Format_ABGR32, GST_VIDEO_FORMAT_RGBA },
+ { QVideoFrame::Format_BGRA32, GST_VIDEO_FORMAT_ARGB },
+#else
+ { QVideoFrame::Format_RGB32 , GST_VIDEO_FORMAT_xRGB },
+ { QVideoFrame::Format_BGR32 , GST_VIDEO_FORMAT_xBGR },
+ { QVideoFrame::Format_ARGB32, GST_VIDEO_FORMAT_ARGB },
+ { QVideoFrame::Format_ABGR32, GST_VIDEO_FORMAT_ABGR },
+ { QVideoFrame::Format_BGRA32, GST_VIDEO_FORMAT_BGRA },
+#endif
+ { QVideoFrame::Format_RGB24 , GST_VIDEO_FORMAT_RGB },
+ { QVideoFrame::Format_BGR24 , GST_VIDEO_FORMAT_BGR },
+ { QVideoFrame::Format_RGB565, GST_VIDEO_FORMAT_RGB16 }
+};
+
+static int indexOfVideoFormat(QVideoFrame::PixelFormat format)
+{
+ for (int i = 0; i < lengthOf(qt_videoFormatLookup); ++i)
+ if (qt_videoFormatLookup[i].pixelFormat == format)
+ return i;
+
+ return -1;
+}
+
+static int indexOfVideoFormat(GstVideoFormat format)
+{
+ for (int i = 0; i < lengthOf(qt_videoFormatLookup); ++i)
+ if (qt_videoFormatLookup[i].gstFormat == format)
+ return i;
+
+ return -1;
+}
+
+}
+
+QVideoSurfaceFormat QGstUtils::formatForCaps(
+ GstCaps *caps, GstVideoInfo *info, QAbstractVideoBuffer::HandleType handleType)
+{
+ GstVideoInfo vidInfo;
+ GstVideoInfo *infoPtr = info ? info : &vidInfo;
+
+ if (gst_video_info_from_caps(infoPtr, caps)) {
+ int index = indexOfVideoFormat(infoPtr->finfo->format);
+
+ if (index != -1) {
+ QVideoSurfaceFormat format(
+ QSize(infoPtr->width, infoPtr->height),
+ qt_videoFormatLookup[index].pixelFormat,
+ handleType);
+
+ if (infoPtr->fps_d > 0)
+ format.setFrameRate(qreal(infoPtr->fps_n) / infoPtr->fps_d);
+
+ if (infoPtr->par_d > 0)
+ format.setPixelAspectRatio(infoPtr->par_n, infoPtr->par_d);
+
+ return format;
+ }
+ }
+ return QVideoSurfaceFormat();
+}
+
+GstCaps *QGstUtils::capsForFormats(const QList<QVideoFrame::PixelFormat> &formats)
+{
+ GstCaps *caps = gst_caps_new_empty();
+
+ for (QVideoFrame::PixelFormat format : formats) {
+ int index = indexOfVideoFormat(format);
+
+ if (index != -1) {
+ gst_caps_append_structure(caps, gst_structure_new(
+ "video/x-raw",
+ "format" , G_TYPE_STRING, gst_video_format_to_string(qt_videoFormatLookup[index].gstFormat),
+ nullptr));
+ }
+ }
+
+ gst_caps_set_simple(
+ caps,
+ "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, INT_MAX, 1,
+ "width" , GST_TYPE_INT_RANGE, 1, INT_MAX,
+ "height" , GST_TYPE_INT_RANGE, 1, INT_MAX,
+ nullptr);
+
+ return caps;
+}
+
+void QGstUtils::setFrameTimeStamps(QVideoFrame *frame, GstBuffer *buffer)
+{
+ // GStreamer uses nanoseconds, Qt uses microseconds
+ qint64 startTime = GST_BUFFER_TIMESTAMP(buffer);
+ if (startTime >= 0) {
+ frame->setStartTime(startTime/G_GINT64_CONSTANT (1000));
+
+ qint64 duration = GST_BUFFER_DURATION(buffer);
+ if (duration >= 0)
+ frame->setEndTime((startTime + duration)/G_GINT64_CONSTANT (1000));
+ }
+}
+
+void QGstUtils::setMetaData(GstElement *element, const QMap<QByteArray, QVariant> &data)
+{
+ if (!GST_IS_TAG_SETTER(element))
+ return;
+
+ gst_tag_setter_reset_tags(GST_TAG_SETTER(element));
+
+ for (auto it = data.cbegin(), end = data.cend(); it != end; ++it) {
+ const QString tagName = QString::fromLatin1(it.key());
+ const QVariant &tagValue = it.value();
+
+ switch (tagValue.typeId()) {
+ case QMetaType::QString:
+ gst_tag_setter_add_tags(GST_TAG_SETTER(element),
+ GST_TAG_MERGE_REPLACE,
+ tagName.toUtf8().constData(),
+ tagValue.toString().toUtf8().constData(),
+ nullptr);
+ break;
+ case QMetaType::Int:
+ case QMetaType::LongLong:
+ gst_tag_setter_add_tags(GST_TAG_SETTER(element),
+ GST_TAG_MERGE_REPLACE,
+ tagName.toUtf8().constData(),
+ tagValue.toInt(),
+ nullptr);
+ break;
+ case QMetaType::Double:
+ gst_tag_setter_add_tags(GST_TAG_SETTER(element),
+ GST_TAG_MERGE_REPLACE,
+ tagName.toUtf8().constData(),
+ tagValue.toDouble(),
+ nullptr);
+ break;
+ case QMetaType::QDateTime: {
+ QDateTime date = tagValue.toDateTime().toLocalTime();
+ gst_tag_setter_add_tags(GST_TAG_SETTER(element),
+ GST_TAG_MERGE_REPLACE,
+ tagName.toUtf8().constData(),
+ gst_date_time_new_local_time(
+ date.date().year(), date.date().month(), date.date().day(),
+ date.time().hour(), date.time().minute(), date.time().second()),
+ nullptr);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+}
+
+void QGstUtils::setMetaData(GstBin *bin, const QMap<QByteArray, QVariant> &data)
+{
+ GstIterator *elements = gst_bin_iterate_all_by_interface(bin, GST_TYPE_TAG_SETTER);
+ GValue item = G_VALUE_INIT;
+ while (gst_iterator_next(elements, &item) == GST_ITERATOR_OK) {
+ GstElement * const element = GST_ELEMENT(g_value_get_object(&item));
+ setMetaData(element, data);
+ }
+ gst_iterator_free(elements);
+}
+
+
+GstCaps *QGstUtils::videoFilterCaps()
+{
+ const char *caps =
+ "video/x-raw(ANY);"
+ "image/jpeg;"
+ "video/x-h264";
+ static GstStaticCaps staticCaps = GST_STATIC_CAPS(caps);
+
+ return gst_caps_make_writable(gst_static_caps_get(&staticCaps));
+}
+
+QSize QGstUtils::structureResolution(const GstStructure *s)
+{
+ QSize size;
+
+ int w, h;
+ if (s && gst_structure_get_int(s, "width", &w) && gst_structure_get_int(s, "height", &h)) {
+ size.rwidth() = w;
+ size.rheight() = h;
+ }
+
+ return size;
+}
+
+QVideoFrame::PixelFormat QGstUtils::structurePixelFormat(const GstStructure *structure)
+{
+ QVideoFrame::PixelFormat pixelFormat = QVideoFrame::Format_Invalid;
+
+ if (!structure)
+ return pixelFormat;
+
+ if (gst_structure_has_name(structure, "video/x-raw")) {
+ const gchar *s = gst_structure_get_string(structure, "format");
+ if (s) {
+ GstVideoFormat format = gst_video_format_from_string(s);
+ int index = indexOfVideoFormat(format);
+
+ if (index != -1)
+ pixelFormat = qt_videoFormatLookup[index].pixelFormat;
+ }
+ }
+
+ return pixelFormat;
+}
+
+QSize QGstUtils::structurePixelAspectRatio(const GstStructure *s)
+{
+ QSize ratio(1, 1);
+
+ gint aspectNum = 0;
+ gint aspectDenum = 0;
+ if (s && gst_structure_get_fraction(s, "pixel-aspect-ratio", &aspectNum, &aspectDenum)) {
+ if (aspectDenum > 0) {
+ ratio.rwidth() = aspectNum;
+ ratio.rheight() = aspectDenum;
+ }
+ }
+
+ return ratio;
+}
+
+QPair<qreal, qreal> QGstUtils::structureFrameRateRange(const GstStructure *s)
+{
+ QPair<qreal, qreal> rate;
+
+ if (!s)
+ return rate;
+
+ int n, d;
+ if (gst_structure_get_fraction(s, "framerate", &n, &d)) {
+ rate.second = qreal(n) / d;
+ rate.first = rate.second;
+ } else if (gst_structure_get_fraction(s, "max-framerate", &n, &d)) {
+ rate.second = qreal(n) / d;
+ if (gst_structure_get_fraction(s, "min-framerate", &n, &d))
+ rate.first = qreal(n) / d;
+ else
+ rate.first = qreal(1);
+ }
+
+ return rate;
+}
+
+typedef QMap<QString, QString> FileExtensionMap;
+Q_GLOBAL_STATIC(FileExtensionMap, fileExtensionMap)
+
+QString QGstUtils::fileExtensionForMimeType(const QString &mimeType)
+{
+ if (fileExtensionMap->isEmpty()) {
+ //extension for containers hard to guess from mimetype
+ fileExtensionMap->insert(QStringLiteral("video/x-matroska"), QLatin1String("mkv"));
+ fileExtensionMap->insert(QStringLiteral("video/quicktime"), QLatin1String("mov"));
+ fileExtensionMap->insert(QStringLiteral("video/x-msvideo"), QLatin1String("avi"));
+ fileExtensionMap->insert(QStringLiteral("video/msvideo"), QLatin1String("avi"));
+ fileExtensionMap->insert(QStringLiteral("audio/mpeg"), QLatin1String("mp3"));
+ fileExtensionMap->insert(QStringLiteral("application/x-shockwave-flash"), QLatin1String("swf"));
+ fileExtensionMap->insert(QStringLiteral("application/x-pn-realmedia"), QLatin1String("rm"));
+ }
+
+ //for container names like avi instead of video/x-msvideo, use it as extension
+ if (!mimeType.contains(QLatin1Char('/')))
+ return mimeType;
+
+ QString format = mimeType.left(mimeType.indexOf(QLatin1Char(',')));
+ QString extension = fileExtensionMap->value(format);
+
+ if (!extension.isEmpty() || format.isEmpty())
+ return extension;
+
+ QRegularExpression rx(QStringLiteral("[-/]([\\w]+)$"));
+ QRegularExpressionMatch match = rx.match(format);
+
+ if (match.hasMatch())
+ extension = match.captured(1);
+
+ return extension;
+}
+
+QVariant QGstUtils::fromGStreamerOrientation(const QVariant &value)
+{
+ // Note gstreamer tokens either describe the counter clockwise rotation of the
+ // image or the clockwise transform to apply to correct the image. The orientation
+ // value returned is the clockwise rotation of the image.
+ const QString token = value.toString();
+ if (token == QStringLiteral("rotate-90"))
+ return 270;
+ if (token == QStringLiteral("rotate-180"))
+ return 180;
+ if (token == QStringLiteral("rotate-270"))
+ return 90;
+ return 0;
+}
+
+QVariant QGstUtils::toGStreamerOrientation(const QVariant &value)
+{
+ switch (value.toInt()) {
+ case 90:
+ return QStringLiteral("rotate-270");
+ case 180:
+ return QStringLiteral("rotate-180");
+ case 270:
+ return QStringLiteral("rotate-90");
+ default:
+ return QStringLiteral("rotate-0");
+ }
+}
+
+bool QGstUtils::useOpenGL()
+{
+ static bool result = qEnvironmentVariableIntValue("QT_GSTREAMER_USE_OPENGL_PLUGIN");
+ return result;
+}
+
+void qt_gst_object_ref_sink(gpointer object)
+{
+ gst_object_ref_sink(object);
+}
+
+GstCaps *qt_gst_pad_get_current_caps(GstPad *pad)
+{
+ return gst_pad_get_current_caps(pad);
+}
+
+GstCaps *qt_gst_pad_get_caps(GstPad *pad)
+{
+ return gst_pad_query_caps(pad, nullptr);
+}
+
+GstStructure *qt_gst_structure_new_empty(const char *name)
+{
+ return gst_structure_new_empty(name);
+}
+
+gboolean qt_gst_element_query_position(GstElement *element, GstFormat format, gint64 *cur)
+{
+ return gst_element_query_position(element, format, cur);
+}
+
+gboolean qt_gst_element_query_duration(GstElement *element, GstFormat format, gint64 *cur)
+{
+ return gst_element_query_duration(element, format, cur);
+}
+
+GstCaps *qt_gst_caps_normalize(GstCaps *caps)
+{
+ // gst_caps_normalize() takes ownership of the argument in 1.0
+ return gst_caps_normalize(caps);
+}
+
+const gchar *qt_gst_element_get_factory_name(GstElement *element)
+{
+ const gchar *name = 0;
+ const GstElementFactory *factory = 0;
+
+ if (element && (factory = gst_element_get_factory(element)))
+ name = gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(factory));
+
+ return name;
+}
+
+gboolean qt_gst_caps_can_intersect(const GstCaps * caps1, const GstCaps * caps2)
+{
+ return gst_caps_can_intersect(caps1, caps2);
+}
+
+GList *qt_gst_video_sinks()
+{
+ GList *list = nullptr;
+
+ list = gst_element_factory_list_get_elements(GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO,
+ GST_RANK_MARGINAL);
+
+ return list;
+}
+
+void qt_gst_util_double_to_fraction(gdouble src, gint *dest_n, gint *dest_d)
+{
+ gst_util_double_to_fraction(src, dest_n, dest_d);
+}
+
+QDebug operator <<(QDebug debug, GstCaps *caps)
+{
+ if (caps) {
+ gchar *string = gst_caps_to_string(caps);
+ debug = debug << string;
+ g_free(string);
+ }
+ return debug;
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/gstreamer/qgstutils_p.h b/src/multimedia/gstreamer/qgstutils_p.h
new file mode 100644
index 000000000..853064707
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstutils_p.h
@@ -0,0 +1,157 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QGSTUTILS_P_H
+#define QGSTUTILS_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtmultimediaglobal_p.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qset.h>
+#include <gst/gst.h>
+#include <gst/video/video.h>
+#include <qaudioformat.h>
+#include <qcamera.h>
+#include <qabstractvideobuffer.h>
+#include <qvideoframe.h>
+#include <QDebug>
+
+# define QT_GSTREAMER_PLAYBIN_ELEMENT_NAME "playbin"
+# define QT_GSTREAMER_CAMERABIN_ELEMENT_NAME "camerabin"
+# define QT_GSTREAMER_COLORCONVERSION_ELEMENT_NAME "videoconvert"
+# define QT_GSTREAMER_RAW_AUDIO_MIME "audio/x-raw"
+# define QT_GSTREAMER_VIDEOOVERLAY_INTERFACE_NAME "GstVideoOverlay"
+
+QT_BEGIN_NAMESPACE
+
+class QSize;
+class QVariant;
+class QByteArray;
+class QImage;
+class QVideoSurfaceFormat;
+
+namespace QGstUtils {
+ struct Q_MULTIMEDIA_EXPORT CameraInfo
+ {
+ QString name;
+ QString description;
+ int orientation;
+ QCamera::Position position;
+ QByteArray driver;
+ };
+
+ Q_MULTIMEDIA_EXPORT QMap<QByteArray, QVariant> gstTagListToMap(const GstTagList *list);
+
+ Q_MULTIMEDIA_EXPORT QSize capsResolution(const GstCaps *caps);
+ Q_MULTIMEDIA_EXPORT QSize capsCorrectedResolution(const GstCaps *caps);
+ Q_MULTIMEDIA_EXPORT QAudioFormat audioFormatForCaps(const GstCaps *caps);
+ Q_MULTIMEDIA_EXPORT QAudioFormat audioFormatForSample(GstSample *sample);
+ Q_MULTIMEDIA_EXPORT GstCaps *capsForAudioFormat(const QAudioFormat &format);
+ Q_MULTIMEDIA_EXPORT void initializeGst();
+ Q_MULTIMEDIA_EXPORT QMultimedia::SupportEstimate hasSupport(const QString &mimeType,
+ const QStringList &codecs,
+ const QSet<QString> &supportedMimeTypeSet);
+
+ Q_MULTIMEDIA_EXPORT QList<CameraInfo> enumerateCameras();
+ Q_MULTIMEDIA_EXPORT QList<QByteArray> cameraDevices();
+ Q_MULTIMEDIA_EXPORT QString cameraDescription(const QString &device);
+ Q_MULTIMEDIA_EXPORT QCamera::Position cameraPosition(const QString &device);
+ Q_MULTIMEDIA_EXPORT int cameraOrientation(const QString &device);
+ Q_MULTIMEDIA_EXPORT QByteArray cameraDriver(const QString &device);
+
+ Q_MULTIMEDIA_EXPORT const QSet<GstDevice *> &audioSources();
+ Q_MULTIMEDIA_EXPORT const QSet<GstDevice *> &audioSinks();
+
+ Q_MULTIMEDIA_EXPORT QSet<QString> supportedMimeTypes(bool (*isValidFactory)(GstElementFactory *factory));
+
+ Q_MULTIMEDIA_EXPORT QImage bufferToImage(GstBuffer *buffer, const GstVideoInfo &info);
+ Q_MULTIMEDIA_EXPORT QVideoSurfaceFormat formatForCaps(
+ GstCaps *caps,
+ GstVideoInfo *info = 0,
+ QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle);
+
+ Q_MULTIMEDIA_EXPORT GstCaps *capsForFormats(const QList<QVideoFrame::PixelFormat> &formats);
+ void setFrameTimeStamps(QVideoFrame *frame, GstBuffer *buffer);
+
+ Q_MULTIMEDIA_EXPORT void setMetaData(GstElement *element, const QMap<QByteArray, QVariant> &data);
+ Q_MULTIMEDIA_EXPORT void setMetaData(GstBin *bin, const QMap<QByteArray, QVariant> &data);
+
+ Q_MULTIMEDIA_EXPORT GstCaps *videoFilterCaps();
+
+ Q_MULTIMEDIA_EXPORT QSize structureResolution(const GstStructure *s);
+ Q_MULTIMEDIA_EXPORT QVideoFrame::PixelFormat structurePixelFormat(const GstStructure *s);
+ Q_MULTIMEDIA_EXPORT QSize structurePixelAspectRatio(const GstStructure *s);
+ Q_MULTIMEDIA_EXPORT QPair<qreal, qreal> structureFrameRateRange(const GstStructure *s);
+
+ Q_MULTIMEDIA_EXPORT QString fileExtensionForMimeType(const QString &mimeType);
+
+ Q_MULTIMEDIA_EXPORT QVariant fromGStreamerOrientation(const QVariant &value);
+ Q_MULTIMEDIA_EXPORT QVariant toGStreamerOrientation(const QVariant &value);
+
+ Q_MULTIMEDIA_EXPORT bool useOpenGL();
+}
+
+Q_MULTIMEDIA_EXPORT void qt_gst_object_ref_sink(gpointer object);
+Q_MULTIMEDIA_EXPORT GstCaps *qt_gst_pad_get_current_caps(GstPad *pad);
+Q_MULTIMEDIA_EXPORT GstCaps *qt_gst_pad_get_caps(GstPad *pad);
+Q_MULTIMEDIA_EXPORT GstStructure *qt_gst_structure_new_empty(const char *name);
+Q_MULTIMEDIA_EXPORT gboolean qt_gst_element_query_position(GstElement *element, GstFormat format, gint64 *cur);
+Q_MULTIMEDIA_EXPORT gboolean qt_gst_element_query_duration(GstElement *element, GstFormat format, gint64 *cur);
+Q_MULTIMEDIA_EXPORT GstCaps *qt_gst_caps_normalize(GstCaps *caps);
+Q_MULTIMEDIA_EXPORT const gchar *qt_gst_element_get_factory_name(GstElement *element);
+Q_MULTIMEDIA_EXPORT gboolean qt_gst_caps_can_intersect(const GstCaps * caps1, const GstCaps * caps2);
+Q_MULTIMEDIA_EXPORT GList *qt_gst_video_sinks();
+Q_MULTIMEDIA_EXPORT void qt_gst_util_double_to_fraction(gdouble src, gint *dest_n, gint *dest_d);
+
+Q_MULTIMEDIA_EXPORT QDebug operator <<(QDebug debug, GstCaps *caps);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/gstreamer/qgstvideobuffer.cpp b/src/multimedia/gstreamer/qgstvideobuffer.cpp
new file mode 100644
index 000000000..58738ffa0
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstvideobuffer.cpp
@@ -0,0 +1,119 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qgstvideobuffer_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QGstVideoBuffer::QGstVideoBuffer(GstBuffer *buffer, const GstVideoInfo &info)
+ : QAbstractVideoBuffer(NoHandle)
+ , m_videoInfo(info)
+ , m_buffer(buffer)
+{
+ gst_buffer_ref(m_buffer);
+}
+
+QGstVideoBuffer::QGstVideoBuffer(GstBuffer *buffer, const GstVideoInfo &info,
+ QGstVideoBuffer::HandleType handleType,
+ const QVariant &handle)
+ : QAbstractVideoBuffer(handleType)
+ , m_videoInfo(info)
+ , m_buffer(buffer)
+ , m_handle(handle)
+{
+ gst_buffer_ref(m_buffer);
+}
+
+QGstVideoBuffer::~QGstVideoBuffer()
+{
+ unmap();
+
+ gst_buffer_unref(m_buffer);
+}
+
+
+QAbstractVideoBuffer::MapMode QGstVideoBuffer::mapMode() const
+{
+ return m_mode;
+}
+
+QAbstractVideoBuffer::MapData QGstVideoBuffer::map(MapMode mode)
+{
+ const GstMapFlags flags = GstMapFlags(((mode & ReadOnly) ? GST_MAP_READ : 0)
+ | ((mode & WriteOnly) ? GST_MAP_WRITE : 0));
+
+ MapData mapData;
+ if (mode == NotMapped || m_mode != NotMapped)
+ return mapData;
+
+ if (m_videoInfo.finfo->n_planes == 0) { // Encoded
+ if (gst_buffer_map(m_buffer, &m_frame.map[0], flags)) {
+ mapData.nBytes = m_frame.map[0].size;
+ mapData.nPlanes = 1;
+ mapData.bytesPerLine[0] = -1;
+ mapData.data[0] = static_cast<uchar *>(m_frame.map[0].data);
+
+ m_mode = mode;
+ }
+ } else if (gst_video_frame_map(&m_frame, &m_videoInfo, m_buffer, flags)) {
+ mapData.nBytes = m_frame.info.size;
+ mapData.nPlanes = m_frame.info.finfo->n_planes;
+
+ for (guint i = 0; i < m_frame.info.finfo->n_planes; ++i) {
+ mapData.bytesPerLine[i] = m_frame.info.stride[i];
+ mapData.data[i] = static_cast<uchar *>(m_frame.data[i]);
+ }
+
+ m_mode = mode;
+ }
+ return mapData;
+}
+
+void QGstVideoBuffer::unmap()
+{
+ if (m_mode != NotMapped) {
+ if (m_videoInfo.finfo->n_planes == 0)
+ gst_buffer_unmap(m_buffer, &m_frame.map[0]);
+ else
+ gst_video_frame_unmap(&m_frame);
+ }
+ m_mode = NotMapped;
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/gstreamer/qgstvideobuffer_p.h b/src/multimedia/gstreamer/qgstvideobuffer_p.h
new file mode 100644
index 000000000..11a217f51
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstvideobuffer_p.h
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QGSTVIDEOBUFFER_P_H
+#define QGSTVIDEOBUFFER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtmultimediaglobal_p.h>
+#include <qabstractvideobuffer.h>
+#include <QtCore/qvariant.h>
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_MULTIMEDIA_EXPORT QGstVideoBuffer : public QAbstractVideoBuffer
+{
+public:
+ QGstVideoBuffer(GstBuffer *buffer, const GstVideoInfo &info);
+ QGstVideoBuffer(GstBuffer *buffer, const GstVideoInfo &info,
+ HandleType handleType, const QVariant &handle);
+
+ ~QGstVideoBuffer();
+
+ GstBuffer *buffer() const { return m_buffer; }
+ MapMode mapMode() const override;
+
+ MapData map(MapMode mode) override;
+ void unmap() override;
+
+ QVariant handle() const override { return m_handle; }
+private:
+ GstVideoInfo m_videoInfo;
+ GstVideoFrame m_frame;
+ GstBuffer *m_buffer = nullptr;
+ MapMode m_mode = NotMapped;
+ QVariant m_handle;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/gstreamer/qgstvideorendererplugin.cpp b/src/multimedia/gstreamer/qgstvideorendererplugin.cpp
new file mode 100644
index 000000000..1b63cbfa8
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstvideorendererplugin.cpp
@@ -0,0 +1,59 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 Jolla Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qgstvideorendererplugin_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QGstVideoRendererPlugin::QGstVideoRendererPlugin(QObject *parent) :
+ QObject(parent)
+{
+}
+
+QGstVideoRenderer::~QGstVideoRenderer() {}
+
+QGstVideoRendererInterface::~QGstVideoRendererInterface() {}
+
+QGstVideoRendererPlugin::~QGstVideoRendererPlugin() {}
+
+QT_END_NAMESPACE
+
+#include "moc_qgstvideorendererplugin_p.cpp"
+
+
diff --git a/src/multimedia/gstreamer/qgstvideorendererplugin_p.h b/src/multimedia/gstreamer/qgstvideorendererplugin_p.h
new file mode 100644
index 000000000..d6f13062e
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstvideorendererplugin_p.h
@@ -0,0 +1,112 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 Jolla Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QGSTVIDEORENDERERPLUGIN_P_H
+#define QGSTVIDEORENDERERPLUGIN_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtmultimediaglobal_p.h>
+#include <qabstractvideobuffer.h>
+#include <qvideosurfaceformat.h>
+#include <QtCore/qobject.h>
+#include <QtCore/qplugin.h>
+
+#include <gst/gst.h>
+
+QT_BEGIN_NAMESPACE
+
+class QAbstractVideoSurface;
+
+#ifndef Q_MULTIMEDIA_EXPORT
+#error XXX
+#endif
+
+class Q_MULTIMEDIA_EXPORT QGstVideoRenderer
+{
+public:
+ virtual ~QGstVideoRenderer();
+
+ virtual GstCaps *getCaps(QAbstractVideoSurface *surface) = 0;
+ virtual bool start(QAbstractVideoSurface *surface, GstCaps *caps) = 0;
+ virtual void stop(QAbstractVideoSurface *surface) = 0; // surface may be null if unexpectedly deleted.
+ virtual bool proposeAllocation(GstQuery *query) = 0; // may be called from a thread.
+
+ virtual bool present(QAbstractVideoSurface *surface, GstBuffer *buffer) = 0;
+ virtual void flush(QAbstractVideoSurface *surface) = 0; // surface may be null if unexpectedly deleted.
+};
+
+/*
+ Abstract interface for video buffers allocation.
+*/
+class Q_MULTIMEDIA_EXPORT QGstVideoRendererInterface
+{
+public:
+ virtual ~QGstVideoRendererInterface();
+
+ virtual QGstVideoRenderer *createRenderer() = 0;
+};
+
+#define QGstVideoRendererInterface_iid "org.qt-project.qt.gstvideorenderer/5.4"
+Q_DECLARE_INTERFACE(QGstVideoRendererInterface, QGstVideoRendererInterface_iid)
+
+class Q_MULTIMEDIA_EXPORT QGstVideoRendererPlugin : public QObject, public QGstVideoRendererInterface
+{
+ Q_OBJECT
+ Q_INTERFACES(QGstVideoRendererInterface)
+public:
+ explicit QGstVideoRendererPlugin(QObject *parent = 0);
+ virtual ~QGstVideoRendererPlugin();
+
+ QGstVideoRenderer *createRenderer() override = 0;
+
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/gstreamer/qgstvideorenderersink.cpp b/src/multimedia/gstreamer/qgstvideorenderersink.cpp
new file mode 100644
index 000000000..0f930cf02
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstvideorenderersink.cpp
@@ -0,0 +1,803 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 Jolla Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <qabstractvideosurface.h>
+#include <qvideoframe.h>
+#include <QDebug>
+#include <QMap>
+#include <QThread>
+#include <QEvent>
+#include <QCoreApplication>
+
+#include <private/qfactoryloader_p.h>
+#include "qgstvideobuffer_p.h"
+
+#include "qgstvideorenderersink_p.h"
+
+#include <gst/video/video.h>
+
+#include "qgstutils_p.h"
+
+#if QT_CONFIG(gstreamer_gl)
+#include <QOpenGLContext>
+#include <QGuiApplication>
+#include <QWindow>
+#include <qpa/qplatformnativeinterface.h>
+
+#include <gst/gl/gstglconfig.h>
+
+#if GST_GL_HAVE_WINDOW_X11
+# include <gst/gl/x11/gstgldisplay_x11.h>
+#endif
+#if GST_GL_HAVE_PLATFORM_EGL
+# include <gst/gl/egl/gstgldisplay_egl.h>
+#endif
+#if GST_GL_HAVE_WINDOW_WAYLAND
+# include <gst/gl/wayland/gstgldisplay_wayland.h>
+#endif
+#endif // #if QT_CONFIG(gstreamer_gl)
+
+//#define DEBUG_VIDEO_SURFACE_SINK
+
+QT_BEGIN_NAMESPACE
+
+QGstDefaultVideoRenderer::QGstDefaultVideoRenderer()
+{
+}
+
+QGstDefaultVideoRenderer::~QGstDefaultVideoRenderer()
+{
+}
+
+GstCaps *QGstDefaultVideoRenderer::getCaps(QAbstractVideoSurface *surface)
+{
+#if QT_CONFIG(gstreamer_gl)
+ if (QGstUtils::useOpenGL()) {
+ m_handleType = QAbstractVideoBuffer::GLTextureHandle;
+ auto formats = surface->supportedPixelFormats(m_handleType);
+ // Even if the surface does not support gl textures,
+ // glupload will be added to the pipeline and GLMemory will be requested.
+ // This will lead to upload data to gl textures
+ // and download it when the buffer will be used within rendering.
+ if (formats.isEmpty()) {
+ m_handleType = QAbstractVideoBuffer::NoHandle;
+ formats = surface->supportedPixelFormats(m_handleType);
+ }
+
+ GstCaps *caps = QGstUtils::capsForFormats(formats);
+ for (guint i = 0; i < gst_caps_get_size(caps); ++i)
+ gst_caps_set_features(caps, i, gst_caps_features_from_string("memory:GLMemory"));
+
+ return caps;
+ }
+#endif
+ return QGstUtils::capsForFormats(surface->supportedPixelFormats(QAbstractVideoBuffer::NoHandle));
+}
+
+bool QGstDefaultVideoRenderer::start(QAbstractVideoSurface *surface, GstCaps *caps)
+{
+ m_flushed = true;
+ m_format = QGstUtils::formatForCaps(caps, &m_videoInfo, m_handleType);
+
+ return m_format.isValid() && surface->start(m_format);
+}
+
+void QGstDefaultVideoRenderer::stop(QAbstractVideoSurface *surface)
+{
+ m_flushed = true;
+ if (surface)
+ surface->stop();
+}
+
+bool QGstDefaultVideoRenderer::present(QAbstractVideoSurface *surface, GstBuffer *buffer)
+{
+ m_flushed = false;
+
+ QGstVideoBuffer *videoBuffer = nullptr;
+#if QT_CONFIG(gstreamer_gl)
+ if (m_format.handleType() == QAbstractVideoBuffer::GLTextureHandle) {
+ GstGLMemory *glmem = GST_GL_MEMORY_CAST(gst_buffer_peek_memory(buffer, 0));
+ guint textureId = gst_gl_memory_get_texture_id(glmem);
+ videoBuffer = new QGstVideoBuffer(buffer, m_videoInfo, m_format.handleType(), textureId);
+ }
+#endif
+
+ if (!videoBuffer)
+ videoBuffer = new QGstVideoBuffer(buffer, m_videoInfo);
+
+ auto meta = gst_buffer_get_video_crop_meta (buffer);
+ if (meta) {
+ QRect vp(meta->x, meta->y, meta->width, meta->height);
+ if (m_format.viewport() != vp) {
+#ifdef DEBUG_VIDEO_SURFACE_SINK
+ qDebug() << Q_FUNC_INFO << " Update viewport on Metadata: [" << meta->height << "x" << meta->width << " | " << meta->x << "x" << meta->y << "]";
+#endif
+ //Update viewport if data is not the same
+ m_format.setViewport(vp);
+ surface->start(m_format);
+ }
+ }
+
+ QVideoFrame frame(
+ videoBuffer,
+ m_format.frameSize(),
+ m_format.pixelFormat());
+ QGstUtils::setFrameTimeStamps(&frame, buffer);
+
+ return surface->present(frame);
+}
+
+void QGstDefaultVideoRenderer::flush(QAbstractVideoSurface *surface)
+{
+ if (surface && !m_flushed)
+ surface->present(QVideoFrame());
+ m_flushed = true;
+}
+
+bool QGstDefaultVideoRenderer::proposeAllocation(GstQuery *)
+{
+ return true;
+}
+
+Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, rendererLoader,
+ (QGstVideoRendererInterface_iid, QLatin1String("video/gstvideorenderer"), Qt::CaseInsensitive))
+
+QVideoSurfaceGstDelegate::QVideoSurfaceGstDelegate(QAbstractVideoSurface *surface)
+ : m_surface(surface)
+{
+ int i = 0;
+ while (QObject *instance = rendererLoader->instance(i)) {
+ auto plugin = qobject_cast<QGstVideoRendererInterface*>(instance);
+ if (QGstVideoRenderer *renderer = plugin ? plugin->createRenderer() : nullptr)
+ m_renderers.append(renderer);
+ ++i;
+ }
+
+ m_renderers.append(new QGstDefaultVideoRenderer);
+ updateSupportedFormats();
+ connect(m_surface, SIGNAL(supportedFormatsChanged()), this, SLOT(updateSupportedFormats()));
+}
+
+QVideoSurfaceGstDelegate::~QVideoSurfaceGstDelegate()
+{
+ qDeleteAll(m_renderers);
+
+ if (m_surfaceCaps)
+ gst_caps_unref(m_surfaceCaps);
+ if (m_startCaps)
+ gst_caps_unref(m_startCaps);
+#if QT_CONFIG(gstreamer_gl)
+ if (m_gstGLDisplayContext)
+ gst_object_unref(m_gstGLDisplayContext);
+#endif
+}
+
+GstCaps *QVideoSurfaceGstDelegate::caps()
+{
+ QMutexLocker locker(&m_mutex);
+
+ gst_caps_ref(m_surfaceCaps);
+
+ return m_surfaceCaps;
+}
+
+bool QVideoSurfaceGstDelegate::start(GstCaps *caps)
+{
+ QMutexLocker locker(&m_mutex);
+
+ if (m_activeRenderer) {
+ m_flush = true;
+ m_stop = true;
+ }
+
+ if (m_startCaps)
+ gst_caps_unref(m_startCaps);
+ m_startCaps = caps;
+ gst_caps_ref(m_startCaps);
+
+ /*
+ Waiting for start() to be invoked in the main thread may block
+ if gstreamer blocks the main thread until this call is finished.
+ This situation is rare and usually caused by setState(Null)
+ while pipeline is being prerolled.
+
+ The proper solution to this involves controlling gstreamer pipeline from
+ other thread than video surface.
+
+ Currently start() fails if wait() timed out.
+ */
+ if (!waitForAsyncEvent(&locker, &m_setupCondition, 1000) && m_startCaps) {
+ qWarning() << "Failed to start video surface due to main thread blocked.";
+ gst_caps_unref(m_startCaps);
+ m_startCaps = 0;
+ }
+
+ return m_activeRenderer != 0;
+}
+
+void QVideoSurfaceGstDelegate::stop()
+{
+ QMutexLocker locker(&m_mutex);
+
+ if (!m_activeRenderer)
+ return;
+
+ m_flush = true;
+ m_stop = true;
+
+ if (m_startCaps) {
+ gst_caps_unref(m_startCaps);
+ m_startCaps = 0;
+ }
+
+ waitForAsyncEvent(&locker, &m_setupCondition, 500);
+}
+
+void QVideoSurfaceGstDelegate::unlock()
+{
+ QMutexLocker locker(&m_mutex);
+
+ m_setupCondition.wakeAll();
+ m_renderCondition.wakeAll();
+}
+
+bool QVideoSurfaceGstDelegate::proposeAllocation(GstQuery *query)
+{
+ QMutexLocker locker(&m_mutex);
+
+ if (QGstVideoRenderer *pool = m_activeRenderer) {
+ locker.unlock();
+
+ return pool->proposeAllocation(query);
+ }
+
+ return false;
+}
+
+void QVideoSurfaceGstDelegate::flush()
+{
+ QMutexLocker locker(&m_mutex);
+
+ m_flush = true;
+ m_renderBuffer = 0;
+ m_renderCondition.wakeAll();
+
+ notify();
+}
+
+GstFlowReturn QVideoSurfaceGstDelegate::render(GstBuffer *buffer)
+{
+ QMutexLocker locker(&m_mutex);
+
+ m_renderReturn = GST_FLOW_OK;
+ m_renderBuffer = buffer;
+
+ waitForAsyncEvent(&locker, &m_renderCondition, 300);
+
+ m_renderBuffer = 0;
+
+ return m_renderReturn;
+}
+
+#if QT_CONFIG(gstreamer_gl)
+static GstGLContext *gstGLDisplayContext(QAbstractVideoSurface *surface)
+{
+ auto glContext = qobject_cast<QOpenGLContext*>(surface->property("GLContext").value<QObject*>());
+ // Context is not ready yet.
+ if (!glContext)
+ return nullptr;
+
+ GstGLDisplay *display = nullptr;
+ const QString platform = QGuiApplication::platformName();
+ const char *contextName = "eglcontext";
+ GstGLPlatform glPlatform = GST_GL_PLATFORM_EGL;
+ QPlatformNativeInterface *pni = QGuiApplication::platformNativeInterface();
+
+#if GST_GL_HAVE_WINDOW_X11
+ if (platform == QLatin1String("xcb")) {
+ if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL) {
+ contextName = "glxcontext";
+ glPlatform = GST_GL_PLATFORM_GLX;
+ }
+
+ display = (GstGLDisplay *)gst_gl_display_x11_new_with_display(
+ (Display *)pni->nativeResourceForIntegration("display"));
+ }
+#endif
+
+#if GST_GL_HAVE_PLATFORM_EGL
+ if (!display && platform == QLatin1String("eglfs")) {
+ display = (GstGLDisplay *)gst_gl_display_egl_new_with_egl_display(
+ pni->nativeResourceForIntegration("egldisplay"));
+ }
+#endif
+
+#if GST_GL_HAVE_WINDOW_WAYLAND
+ if (!display && platform.startsWith(QLatin1String("wayland"))) {
+ const char *displayName = (platform == QLatin1String("wayland"))
+ ? "display" : "egldisplay";
+
+ display = (GstGLDisplay *)gst_gl_display_wayland_new_with_display(
+ (struct wl_display *)pni->nativeResourceForIntegration(displayName));
+ }
+#endif
+
+ if (!display) {
+ qWarning() << "Could not create GstGLDisplay";
+ return nullptr;
+ }
+
+ void *nativeContext = pni->nativeResourceForContext(contextName, glContext);
+ if (!nativeContext)
+ qWarning() << "Could not find resource for" << contextName;
+
+ GstGLContext *appContext = gst_gl_context_new_wrapped(display, (guintptr)nativeContext, glPlatform, GST_GL_API_ANY);
+ if (!appContext)
+ qWarning() << "Could not create wrappped context for platform:" << glPlatform;
+
+ GstGLContext *displayContext = nullptr;
+ GError *error = nullptr;
+ gst_gl_display_create_context(display, appContext, &displayContext, &error);
+ if (error) {
+ qWarning() << "Could not create display context:" << error->message;
+ g_clear_error(&error);
+ }
+
+ if (appContext)
+ gst_object_unref(appContext);
+
+ gst_object_unref(display);
+
+ return displayContext;
+}
+#endif // #if QT_CONFIG(gstreamer_gl)
+
+bool QVideoSurfaceGstDelegate::query(GstQuery *query)
+{
+#if QT_CONFIG(gstreamer_gl)
+ if (GST_QUERY_TYPE(query) == GST_QUERY_CONTEXT) {
+ const gchar *type;
+ gst_query_parse_context_type(query, &type);
+
+ if (strcmp(type, "gst.gl.local_context") != 0)
+ return false;
+
+ if (!m_gstGLDisplayContext)
+ m_gstGLDisplayContext = gstGLDisplayContext(m_surface);
+
+ // No context yet.
+ if (!m_gstGLDisplayContext)
+ return false;
+
+ GstContext *context = nullptr;
+ gst_query_parse_context(query, &context);
+ context = context ? gst_context_copy(context) : gst_context_new(type, FALSE);
+ GstStructure *structure = gst_context_writable_structure(context);
+ gst_structure_set(structure, "context", GST_TYPE_GL_CONTEXT, m_gstGLDisplayContext, nullptr);
+ gst_query_set_context(query, context);
+ gst_context_unref(context);
+
+ return m_gstGLDisplayContext;
+ }
+#else
+ Q_UNUSED(query);
+#endif
+ return false;
+}
+
+bool QVideoSurfaceGstDelegate::event(QEvent *event)
+{
+ if (event->type() == QEvent::UpdateRequest) {
+ QMutexLocker locker(&m_mutex);
+
+ if (m_notified) {
+ while (handleEvent(&locker)) {}
+ m_notified = false;
+ }
+ return true;
+ }
+
+ return QObject::event(event);
+}
+
+bool QVideoSurfaceGstDelegate::handleEvent(QMutexLocker<QMutex> *locker)
+{
+ if (m_flush) {
+ m_flush = false;
+ if (m_activeRenderer) {
+ locker->unlock();
+
+ m_activeRenderer->flush(m_surface);
+ }
+ } else if (m_stop) {
+ m_stop = false;
+
+ if (QGstVideoRenderer * const activePool = m_activeRenderer) {
+ m_activeRenderer = 0;
+ locker->unlock();
+
+ activePool->stop(m_surface);
+
+ locker->relock();
+ }
+ } else if (m_startCaps) {
+ Q_ASSERT(!m_activeRenderer);
+
+ GstCaps * const startCaps = m_startCaps;
+ m_startCaps = 0;
+
+ if (m_renderer && m_surface) {
+ locker->unlock();
+
+ const bool started = m_renderer->start(m_surface, startCaps);
+
+ locker->relock();
+
+ m_activeRenderer = started
+ ? m_renderer
+ : 0;
+ } else if (QGstVideoRenderer * const activePool = m_activeRenderer) {
+ m_activeRenderer = 0;
+ locker->unlock();
+
+ activePool->stop(m_surface);
+
+ locker->relock();
+ }
+
+ gst_caps_unref(startCaps);
+ } else if (m_renderBuffer) {
+ GstBuffer *buffer = m_renderBuffer;
+ m_renderBuffer = 0;
+ m_renderReturn = GST_FLOW_ERROR;
+
+ if (m_activeRenderer && m_surface) {
+ gst_buffer_ref(buffer);
+
+ locker->unlock();
+
+ const bool rendered = m_activeRenderer->present(m_surface, buffer);
+
+ gst_buffer_unref(buffer);
+
+ locker->relock();
+
+ if (rendered)
+ m_renderReturn = GST_FLOW_OK;
+ }
+
+ m_renderCondition.wakeAll();
+ } else {
+ m_setupCondition.wakeAll();
+
+ return false;
+ }
+ return true;
+}
+
+void QVideoSurfaceGstDelegate::notify()
+{
+ if (!m_notified) {
+ m_notified = true;
+ QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest));
+ }
+}
+
+bool QVideoSurfaceGstDelegate::waitForAsyncEvent(
+ QMutexLocker<QMutex> *locker, QWaitCondition *condition, unsigned long time)
+{
+ if (QThread::currentThread() == thread()) {
+ while (handleEvent(locker)) {}
+ m_notified = false;
+
+ return true;
+ }
+
+ notify();
+
+ return condition->wait(&m_mutex, time);
+}
+
+void QVideoSurfaceGstDelegate::updateSupportedFormats()
+{
+ if (m_surfaceCaps) {
+ gst_caps_unref(m_surfaceCaps);
+ m_surfaceCaps = 0;
+ }
+
+ for (QGstVideoRenderer *pool : qAsConst(m_renderers)) {
+ if (GstCaps *caps = pool->getCaps(m_surface)) {
+ if (gst_caps_is_empty(caps)) {
+ gst_caps_unref(caps);
+ continue;
+ }
+
+ if (m_surfaceCaps)
+ gst_caps_unref(m_surfaceCaps);
+
+ m_renderer = pool;
+ m_surfaceCaps = caps;
+ break;
+ }
+ }
+}
+
+static GstVideoSinkClass *sink_parent_class;
+static QAbstractVideoSurface *current_surface;
+
+#define VO_SINK(s) QGstVideoRendererSink *sink(reinterpret_cast<QGstVideoRendererSink *>(s))
+
+QGstVideoRendererSink *QGstVideoRendererSink::createSink(QAbstractVideoSurface *surface)
+{
+ setSurface(surface);
+ QGstVideoRendererSink *sink = reinterpret_cast<QGstVideoRendererSink *>(
+ g_object_new(QGstVideoRendererSink::get_type(), 0));
+
+ g_signal_connect(G_OBJECT(sink), "notify::show-preroll-frame", G_CALLBACK(handleShowPrerollChange), sink);
+
+ return sink;
+}
+
+void QGstVideoRendererSink::setSurface(QAbstractVideoSurface *surface)
+{
+ current_surface = surface;
+ get_type();
+}
+
+GType QGstVideoRendererSink::get_type()
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo info =
+ {
+ sizeof(QGstVideoRendererSinkClass), // class_size
+ base_init, // base_init
+ nullptr, // base_finalize
+ class_init, // class_init
+ nullptr, // class_finalize
+ nullptr, // class_data
+ sizeof(QGstVideoRendererSink), // instance_size
+ 0, // n_preallocs
+ instance_init, // instance_init
+ 0 // value_table
+ };
+
+ type = g_type_register_static(
+ GST_TYPE_VIDEO_SINK, "QGstVideoRendererSink", &info, GTypeFlags(0));
+
+ // Register the sink type to be used in custom piplines.
+ // When surface is ready the sink can be used.
+ gst_element_register(nullptr, "qtvideosink", GST_RANK_PRIMARY, type);
+ }
+
+ return type;
+}
+
+void QGstVideoRendererSink::class_init(gpointer g_class, gpointer class_data)
+{
+ Q_UNUSED(class_data);
+
+ sink_parent_class = reinterpret_cast<GstVideoSinkClass *>(g_type_class_peek_parent(g_class));
+
+ GstVideoSinkClass *video_sink_class = reinterpret_cast<GstVideoSinkClass *>(g_class);
+ video_sink_class->show_frame = QGstVideoRendererSink::show_frame;
+
+ GstBaseSinkClass *base_sink_class = reinterpret_cast<GstBaseSinkClass *>(g_class);
+ base_sink_class->get_caps = QGstVideoRendererSink::get_caps;
+ base_sink_class->set_caps = QGstVideoRendererSink::set_caps;
+ base_sink_class->propose_allocation = QGstVideoRendererSink::propose_allocation;
+ base_sink_class->stop = QGstVideoRendererSink::stop;
+ base_sink_class->unlock = QGstVideoRendererSink::unlock;
+ base_sink_class->query = QGstVideoRendererSink::query;
+
+ GstElementClass *element_class = reinterpret_cast<GstElementClass *>(g_class);
+ element_class->change_state = QGstVideoRendererSink::change_state;
+ gst_element_class_set_metadata(element_class,
+ "Qt built-in video renderer sink",
+ "Sink/Video",
+ "Qt default built-in video renderer sink",
+ "The Qt Company");
+
+ GObjectClass *object_class = reinterpret_cast<GObjectClass *>(g_class);
+ object_class->finalize = QGstVideoRendererSink::finalize;
+}
+
+void QGstVideoRendererSink::base_init(gpointer g_class)
+{
+ static GstStaticPadTemplate sink_pad_template = GST_STATIC_PAD_TEMPLATE(
+ "sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS(
+ "video/x-raw, "
+ "framerate = (fraction) [ 0, MAX ], "
+ "width = (int) [ 1, MAX ], "
+ "height = (int) [ 1, MAX ]"));
+
+ gst_element_class_add_pad_template(
+ GST_ELEMENT_CLASS(g_class), gst_static_pad_template_get(&sink_pad_template));
+}
+
+struct NullSurface : QAbstractVideoSurface
+{
+ NullSurface(QObject *parent = nullptr) : QAbstractVideoSurface(parent) { }
+
+ QList<QVideoFrame::PixelFormat> supportedPixelFormats(QAbstractVideoBuffer::HandleType) const override
+ {
+ return QList<QVideoFrame::PixelFormat>() << QVideoFrame::Format_RGB32;
+ }
+
+ bool present(const QVideoFrame &) override
+ {
+ return true;
+ }
+};
+
+void QGstVideoRendererSink::instance_init(GTypeInstance *instance, gpointer g_class)
+{
+ Q_UNUSED(g_class);
+ VO_SINK(instance);
+
+ if (!current_surface) {
+ qWarning() << "Using qtvideosink element without video surface";
+ static NullSurface nullSurface;
+ current_surface = &nullSurface;
+ }
+
+ sink->delegate = new QVideoSurfaceGstDelegate(current_surface);
+ sink->delegate->moveToThread(current_surface->thread());
+ current_surface = nullptr;
+}
+
+void QGstVideoRendererSink::finalize(GObject *object)
+{
+ VO_SINK(object);
+
+ delete sink->delegate;
+
+ // Chain up
+ G_OBJECT_CLASS(sink_parent_class)->finalize(object);
+}
+
+void QGstVideoRendererSink::handleShowPrerollChange(GObject *o, GParamSpec *p, gpointer d)
+{
+ Q_UNUSED(o);
+ Q_UNUSED(p);
+ QGstVideoRendererSink *sink = reinterpret_cast<QGstVideoRendererSink *>(d);
+
+ gboolean showPrerollFrame = true; // "show-preroll-frame" property is true by default
+ g_object_get(G_OBJECT(sink), "show-preroll-frame", &showPrerollFrame, nullptr);
+
+ if (!showPrerollFrame) {
+ GstState state = GST_STATE_VOID_PENDING;
+ GstClockTime timeout = 10000000; // 10 ms
+ gst_element_get_state(GST_ELEMENT(sink), &state, nullptr, timeout);
+ // show-preroll-frame being set to 'false' while in GST_STATE_PAUSED means
+ // the QMediaPlayer was stopped from the paused state.
+ // We need to flush the current frame.
+ if (state == GST_STATE_PAUSED)
+ sink->delegate->flush();
+ }
+}
+
+GstStateChangeReturn QGstVideoRendererSink::change_state(
+ GstElement *element, GstStateChange transition)
+{
+ QGstVideoRendererSink *sink = reinterpret_cast<QGstVideoRendererSink *>(element);
+
+ gboolean showPrerollFrame = true; // "show-preroll-frame" property is true by default
+ g_object_get(G_OBJECT(element), "show-preroll-frame", &showPrerollFrame, nullptr);
+
+ // If show-preroll-frame is 'false' when transitioning from GST_STATE_PLAYING to
+ // GST_STATE_PAUSED, it means the QMediaPlayer was stopped.
+ // We need to flush the current frame.
+ if (transition == GST_STATE_CHANGE_PLAYING_TO_PAUSED && !showPrerollFrame)
+ sink->delegate->flush();
+
+ return GST_ELEMENT_CLASS(sink_parent_class)->change_state(element, transition);
+}
+
+GstCaps *QGstVideoRendererSink::get_caps(GstBaseSink *base, GstCaps *filter)
+{
+ VO_SINK(base);
+
+ GstCaps *caps = sink->delegate->caps();
+ GstCaps *unfiltered = caps;
+ if (filter) {
+ caps = gst_caps_intersect(unfiltered, filter);
+ gst_caps_unref(unfiltered);
+ }
+
+ return caps;
+}
+
+gboolean QGstVideoRendererSink::set_caps(GstBaseSink *base, GstCaps *caps)
+{
+ VO_SINK(base);
+
+#ifdef DEBUG_VIDEO_SURFACE_SINK
+ qDebug() << "set_caps:";
+ qDebug() << caps;
+#endif
+
+ if (!caps) {
+ sink->delegate->stop();
+
+ return TRUE;
+ } else if (sink->delegate->start(caps)) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+gboolean QGstVideoRendererSink::propose_allocation(GstBaseSink *base, GstQuery *query)
+{
+ VO_SINK(base);
+ return sink->delegate->proposeAllocation(query);
+}
+
+gboolean QGstVideoRendererSink::stop(GstBaseSink *base)
+{
+ VO_SINK(base);
+ sink->delegate->stop();
+ return TRUE;
+}
+
+gboolean QGstVideoRendererSink::unlock(GstBaseSink *base)
+{
+ VO_SINK(base);
+ sink->delegate->unlock();
+ return TRUE;
+}
+
+GstFlowReturn QGstVideoRendererSink::show_frame(GstVideoSink *base, GstBuffer *buffer)
+{
+ VO_SINK(base);
+ return sink->delegate->render(buffer);
+}
+
+gboolean QGstVideoRendererSink::query(GstBaseSink *base, GstQuery *query)
+{
+ VO_SINK(base);
+ if (sink->delegate->query(query))
+ return TRUE;
+
+ return GST_BASE_SINK_CLASS(sink_parent_class)->query(base, query);
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/gstreamer/qgstvideorenderersink_p.h b/src/multimedia/gstreamer/qgstvideorenderersink_p.h
new file mode 100644
index 000000000..0b05bbe83
--- /dev/null
+++ b/src/multimedia/gstreamer/qgstvideorenderersink_p.h
@@ -0,0 +1,199 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 Jolla Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QGSTVIDEORENDERERSINK_P_H
+#define QGSTVIDEORENDERERSINK_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtMultimedia/private/qtmultimediaglobal_p.h>
+#include <gst/video/gstvideosink.h>
+#include <gst/video/video.h>
+
+#include <QtCore/qlist.h>
+#include <QtCore/qmutex.h>
+#include <QtCore/qqueue.h>
+#include <QtCore/qpointer.h>
+#include <QtCore/qwaitcondition.h>
+#include <qvideosurfaceformat.h>
+#include <qvideoframe.h>
+#include <qabstractvideobuffer.h>
+
+#include "qgstvideorendererplugin_p.h"
+
+#include "qgstvideorendererplugin_p.h"
+
+#if QT_CONFIG(gstreamer_gl)
+#ifndef GST_USE_UNSTABLE_API
+#define GST_USE_UNSTABLE_API
+#endif
+#include <gst/gl/gl.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+class QAbstractVideoSurface;
+
+class QGstDefaultVideoRenderer : public QGstVideoRenderer
+{
+public:
+ QGstDefaultVideoRenderer();
+ ~QGstDefaultVideoRenderer();
+
+ GstCaps *getCaps(QAbstractVideoSurface *surface) override;
+ bool start(QAbstractVideoSurface *surface, GstCaps *caps) override;
+ void stop(QAbstractVideoSurface *surface) override;
+
+ bool proposeAllocation(GstQuery *query) override;
+
+ bool present(QAbstractVideoSurface *surface, GstBuffer *buffer) override;
+ void flush(QAbstractVideoSurface *surface) override;
+
+private:
+ QVideoSurfaceFormat m_format;
+ GstVideoInfo m_videoInfo;
+ bool m_flushed = true;
+ QAbstractVideoBuffer::HandleType m_handleType = QAbstractVideoBuffer::NoHandle;
+};
+
+class QVideoSurfaceGstDelegate : public QObject
+{
+ Q_OBJECT
+public:
+ QVideoSurfaceGstDelegate(QAbstractVideoSurface *surface);
+ ~QVideoSurfaceGstDelegate();
+
+ GstCaps *caps();
+
+ bool start(GstCaps *caps);
+ void stop();
+ void unlock();
+ bool proposeAllocation(GstQuery *query);
+
+ void flush();
+
+ GstFlowReturn render(GstBuffer *buffer);
+
+ bool event(QEvent *event) override;
+ bool query(GstQuery *query);
+
+private slots:
+ bool handleEvent(QMutexLocker<QMutex> *locker);
+ void updateSupportedFormats();
+
+private:
+ void notify();
+ bool waitForAsyncEvent(QMutexLocker<QMutex> *locker, QWaitCondition *condition, unsigned long time);
+
+ QPointer<QAbstractVideoSurface> m_surface;
+
+ QMutex m_mutex;
+ QWaitCondition m_setupCondition;
+ QWaitCondition m_renderCondition;
+ GstFlowReturn m_renderReturn = GST_FLOW_OK;
+ QList<QGstVideoRenderer *> m_renderers;
+ QGstVideoRenderer *m_renderer = nullptr;
+ QGstVideoRenderer *m_activeRenderer = nullptr;
+
+ GstCaps *m_surfaceCaps = nullptr;
+ GstCaps *m_startCaps = nullptr;
+ GstBuffer *m_renderBuffer = nullptr;
+#if QT_CONFIG(gstreamer_gl)
+ GstGLContext *m_gstGLDisplayContext = nullptr;
+#endif
+
+ bool m_notified = false;
+ bool m_stop = false;
+ bool m_flush = false;
+};
+
+class Q_MULTIMEDIA_EXPORT QGstVideoRendererSink
+{
+public:
+ GstVideoSink parent;
+
+ static QGstVideoRendererSink *createSink(QAbstractVideoSurface *surface);
+ static void setSurface(QAbstractVideoSurface *surface);
+
+private:
+ static GType get_type();
+ static void class_init(gpointer g_class, gpointer class_data);
+ static void base_init(gpointer g_class);
+ static void instance_init(GTypeInstance *instance, gpointer g_class);
+
+ static void finalize(GObject *object);
+
+ static void handleShowPrerollChange(GObject *o, GParamSpec *p, gpointer d);
+
+ static GstStateChangeReturn change_state(GstElement *element, GstStateChange transition);
+
+ static GstCaps *get_caps(GstBaseSink *sink, GstCaps *filter);
+ static gboolean set_caps(GstBaseSink *sink, GstCaps *caps);
+
+ static gboolean propose_allocation(GstBaseSink *sink, GstQuery *query);
+
+ static gboolean stop(GstBaseSink *sink);
+
+ static gboolean unlock(GstBaseSink *sink);
+
+ static GstFlowReturn show_frame(GstVideoSink *sink, GstBuffer *buffer);
+ static gboolean query(GstBaseSink *element, GstQuery *query);
+
+private:
+ QVideoSurfaceGstDelegate *delegate = nullptr;
+};
+
+
+class QGstVideoRendererSinkClass
+{
+public:
+ GstVideoSinkClass parent_class;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/multimedia.pro b/src/multimedia/multimedia.pro
index e0874c4aa..7ddcbcc61 100644
--- a/src/multimedia/multimedia.pro
+++ b/src/multimedia/multimedia.pro
@@ -74,6 +74,7 @@ MODULE_WINRT_CAPABILITIES_DEVICE += \
webcam
qtConfig(gstreamer) {
+ include(gstreamer/gstreamer.pri)
ANDROID_LIB_DEPENDENCIES += \
plugins/mediaservice/libgstcamerabin.so \
plugins/mediaservice/libgstmediacapture.so \