diff options
Diffstat (limited to 'src/multimedia/gstreamer')
39 files changed, 9078 insertions, 0 deletions
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 |