summaryrefslogtreecommitdiffstats
path: root/src/gsttools
diff options
context:
space:
mode:
Diffstat (limited to 'src/gsttools')
-rw-r--r--src/gsttools/gsttools.pro8
-rw-r--r--src/gsttools/qgstreamerplayercontrol.cpp631
-rw-r--r--src/gsttools/qgstreamerplayersession.cpp1921
-rw-r--r--src/gsttools/qgstreamervideooverlay.cpp47
-rw-r--r--src/gsttools/qgstreamervideorenderer.cpp46
-rw-r--r--src/gsttools/qgstreamervideowidget.cpp5
-rw-r--r--src/gsttools/qgstvideorenderersink.cpp23
7 files changed, 2639 insertions, 42 deletions
diff --git a/src/gsttools/gsttools.pro b/src/gsttools/gsttools.pro
index f5e3fd96f..ca111b3f2 100644
--- a/src/gsttools/gsttools.pro
+++ b/src/gsttools/gsttools.pro
@@ -36,7 +36,9 @@ PRIVATE_HEADERS += \
qgstreameraudioprobecontrol_p.h \
qgstreamervideowindow_p.h \
qgstreamervideooverlay_p.h \
- qgsttools_global_p.h
+ qgsttools_global_p.h \
+ qgstreamerplayersession_p.h \
+ qgstreamerplayercontrol_p.h
SOURCES += \
qgstreamerbushelper.cpp \
@@ -52,7 +54,9 @@ SOURCES += \
qgstreamervideoprobecontrol.cpp \
qgstreameraudioprobecontrol.cpp \
qgstreamervideowindow.cpp \
- qgstreamervideooverlay.cpp
+ qgstreamervideooverlay.cpp \
+ qgstreamerplayersession.cpp \
+ qgstreamerplayercontrol.cpp
qtHaveModule(widgets) {
QT += multimediawidgets
diff --git a/src/gsttools/qgstreamerplayercontrol.cpp b/src/gsttools/qgstreamerplayercontrol.cpp
new file mode 100644
index 000000000..053e5e408
--- /dev/null
+++ b/src/gsttools/qgstreamerplayercontrol.cpp
@@ -0,0 +1,631 @@
+/****************************************************************************
+**
+** 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 <private/qmediaplaylistnavigator_p.h>
+#include <private/qmediaresourcepolicy_p.h>
+#include <private/qmediaresourceset_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>
+#include <unistd.h>
+
+//#define DEBUG_PLAYBIN
+
+QT_BEGIN_NAMESPACE
+
+QGstreamerPlayerControl::QGstreamerPlayerControl(QGstreamerPlayerSession *session, QObject *parent)
+ : QMediaPlayerControl(parent)
+ , m_session(session)
+ , m_userRequestedState(QMediaPlayer::StoppedState)
+ , m_currentState(QMediaPlayer::StoppedState)
+ , m_mediaStatus(QMediaPlayer::NoMedia)
+ , m_bufferProgress(-1)
+ , m_pendingSeekPosition(-1)
+ , m_setMediaPending(false)
+ , m_stream(0)
+{
+ m_resources = QMediaResourcePolicy::createResourceSet<QMediaPlayerResourceSetInterface>();
+ Q_ASSERT(m_resources);
+
+ connect(m_session, SIGNAL(positionChanged(qint64)),
+ this, SIGNAL(positionChanged(qint64)));
+ connect(m_session, SIGNAL(durationChanged(qint64)),
+ this, SIGNAL(durationChanged(qint64)));
+ connect(m_session, SIGNAL(mutedStateChanged(bool)),
+ this, SIGNAL(mutedChanged(bool)));
+ connect(m_session, SIGNAL(volumeChanged(int)),
+ this, SIGNAL(volumeChanged(int)));
+ connect(m_session, SIGNAL(stateChanged(QMediaPlayer::State)),
+ this, SLOT(updateSessionState(QMediaPlayer::State)));
+ connect(m_session,SIGNAL(bufferingProgressChanged(int)),
+ this, SLOT(setBufferProgress(int)));
+ connect(m_session, SIGNAL(playbackFinished()),
+ this, SLOT(processEOS()));
+ connect(m_session, SIGNAL(audioAvailableChanged(bool)),
+ this, SIGNAL(audioAvailableChanged(bool)));
+ connect(m_session, SIGNAL(videoAvailableChanged(bool)),
+ this, SIGNAL(videoAvailableChanged(bool)));
+ connect(m_session, SIGNAL(seekableChanged(bool)),
+ this, SIGNAL(seekableChanged(bool)));
+ connect(m_session, SIGNAL(error(int,QString)),
+ this, SIGNAL(error(int,QString)));
+ connect(m_session, SIGNAL(invalidMedia()),
+ this, SLOT(handleInvalidMedia()));
+ connect(m_session, SIGNAL(playbackRateChanged(qreal)),
+ this, SIGNAL(playbackRateChanged(qreal)));
+
+ connect(m_resources, SIGNAL(resourcesGranted()), SLOT(handleResourcesGranted()));
+ //denied signal should be queued to have correct state update process,
+ //since in playOrPause, when acquire is call on resource set, it may trigger a resourcesDenied signal immediately,
+ //so handleResourcesDenied should be processed later, otherwise it will be overwritten by state update later in playOrPause.
+ connect(m_resources, SIGNAL(resourcesDenied()), this, SLOT(handleResourcesDenied()), Qt::QueuedConnection);
+ connect(m_resources, SIGNAL(resourcesLost()), SLOT(handleResourcesLost()));
+}
+
+QGstreamerPlayerControl::~QGstreamerPlayerControl()
+{
+ QMediaResourcePolicy::destroyResourceSet(m_resources);
+}
+
+QMediaPlayerResourceSetInterface* QGstreamerPlayerControl::resources() const
+{
+ return m_resources;
+}
+
+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;
+ } else
+ 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;
+
+ 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;
+ }
+
+ if (!m_resources->isGranted())
+ m_resources->acquire();
+
+ if (m_resources->isGranted()) {
+ // 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 if (m_resources->isGranted())
+ 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 = 0;
+ m_session->showPrerollFrames(false); // do not show prerolled frames until pause() or play() explicitly called
+ m_setMediaPending = false;
+
+ if (!content.isNull() || stream) {
+ if (!m_resources->isGranted())
+ m_resources->acquire();
+ } else {
+ m_resources->release();
+ }
+
+ m_session->stop();
+
+ bool userStreamValid = false;
+
+ if (m_bufferProgress != -1) {
+ m_bufferProgress = -1;
+ emit bufferStatusChanged(0);
+ }
+
+ m_currentResource = content;
+ m_stream = stream;
+
+ QNetworkRequest request;
+
+ if (m_stream) {
+ userStreamValid = stream->isOpen() && m_stream->isReadable();
+ request = content.canonicalRequest();
+ } else if (!content.isNull()) {
+ request = content.canonicalRequest();
+ }
+
+#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"));
+ if (m_currentState != QMediaPlayer::PlayingState)
+ m_resources->release();
+ 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());
+
+ if (content.isNull() && !stream)
+ m_resources->release();
+
+ 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)
+ m_session->play();
+ }
+
+ updateMediaStatus();
+
+ popAndNotifyState();
+}
+
+void QGstreamerPlayerControl::updateMediaStatus()
+{
+ 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;
+ }
+
+ if (m_currentState == QMediaPlayer::PlayingState && !m_resources->isGranted())
+ m_mediaStatus = QMediaPlayer::StalledMedia;
+
+ //EndOfMedia status should be kept, until reset by pause, play or setMedia
+ if (oldStatus == QMediaPlayer::EndOfMedia)
+ m_mediaStatus = QMediaPlayer::EndOfMedia;
+
+ 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_resources->isGranted()) {
+ 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::handleResourcesGranted()
+{
+ pushState();
+
+ //This may be triggered when there is an auto resume
+ //from resource-policy, we need to take action according to m_userRequestedState
+ //rather than m_currentState
+ m_currentState = m_userRequestedState;
+ if (m_currentState != QMediaPlayer::StoppedState)
+ playOrPause(m_currentState);
+ else
+ updateMediaStatus();
+
+ popAndNotifyState();
+}
+
+void QGstreamerPlayerControl::handleResourcesLost()
+{
+ //on resource lost the pipeline should be paused
+ //player status is changed to paused
+ pushState();
+ QMediaPlayer::State oldState = m_currentState;
+
+ m_session->pause();
+
+ if (oldState != QMediaPlayer::StoppedState )
+ m_currentState = QMediaPlayer::PausedState;
+
+ popAndNotifyState();
+}
+
+void QGstreamerPlayerControl::handleResourcesDenied()
+{
+ //on resource denied the pipeline should stay paused
+ //player status is changed to paused
+ pushState();
+
+ if (m_currentState != QMediaPlayer::StoppedState )
+ m_currentState = QMediaPlayer::PausedState;
+
+ 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/gsttools/qgstreamerplayersession.cpp b/src/gsttools/qgstreamerplayersession.cpp
new file mode 100644
index 000000000..485556275
--- /dev/null
+++ b/src/gsttools/qgstreamerplayersession.cpp
@@ -0,0 +1,1921 @@
+/****************************************************************************
+**
+** 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>
+#if !GST_CHECK_VERSION(1,0,0)
+#include <private/gstvideoconnector_p.h>
+#endif
+#include <private/qgstutils_p.h>
+#include <private/qgstutils_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>
+
+//#define DEBUG_PLAYBIN
+//#define DEBUG_VO_BIN_DUMP
+
+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;
+
+#if !GST_CHECK_VERSION(1,0,0)
+#define DEFAULT_RAW_CAPS \
+ "video/x-raw-yuv; " \
+ "video/x-raw-rgb; " \
+ "video/x-raw-gray; " \
+ "video/x-surface; " \
+ "video/x-android-buffer; " \
+ "audio/x-raw-int; " \
+ "audio/x-raw-float; " \
+ "text/plain; " \
+ "text/x-pango-markup; " \
+ "video/x-dvd-subpicture; " \
+ "subpicture/x-pgs"
+
+static GstStaticCaps static_RawCaps = GST_STATIC_CAPS(DEFAULT_RAW_CAPS);
+#endif
+
+QGstreamerPlayerSession::QGstreamerPlayerSession(QObject *parent)
+ :QObject(parent),
+ m_state(QMediaPlayer::StoppedState),
+ m_pendingState(QMediaPlayer::StoppedState),
+ m_busHelper(0),
+ m_videoSink(0),
+#if !GST_CHECK_VERSION(1,0,0)
+ m_usingColorspaceElement(false),
+#endif
+ m_pendingVideoSink(0),
+ m_nullVideoSink(0),
+ m_audioSink(0),
+ m_volumeElement(0),
+ m_bus(0),
+ m_videoOutput(0),
+ m_renderer(0),
+#if QT_CONFIG(gstreamer_app)
+ m_appSrc(0),
+#endif
+ m_videoProbe(0),
+ m_audioProbe(0),
+ m_volume(100),
+ m_playbackRate(1.0),
+ m_muted(false),
+ m_audioAvailable(false),
+ m_videoAvailable(false),
+ m_seekable(false),
+ m_lastPosition(0),
+ m_duration(0),
+ m_durationQueries(0),
+ m_displayPrerolledFrame(true),
+ m_sourceType(UnknownSrc),
+ m_everPlayed(false),
+ m_isLiveSource(false)
+{
+ m_playbin = gst_element_factory_make(QT_GSTREAMER_PLAYBIN_ELEMENT_NAME, NULL);
+ 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();
+#if !GST_CHECK_VERSION(1,0,0)
+ } else {
+ flags |= GST_PLAY_FLAG_NATIVE_VIDEO;
+#endif
+ }
+ g_object_set(G_OBJECT(m_playbin), "flags", flags, NULL);
+
+ 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, NULL);
+ 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, NULL);
+ addAudioBufferProbe();
+ }
+ }
+
+#if GST_CHECK_VERSION(1,0,0)
+ m_videoIdentity = gst_element_factory_make("identity", NULL); // floating ref
+#else
+ m_videoIdentity = GST_ELEMENT(g_object_new(gst_video_connector_get_type(), 0)); // floating ref
+ g_signal_connect(G_OBJECT(m_videoIdentity), "connection-failed", G_CALLBACK(insertColorSpaceElement), (gpointer)this);
+ m_colorSpace = gst_element_factory_make(QT_GSTREAMER_COLORCONVERSION_ELEMENT_NAME, "ffmpegcolorspace-vo");
+
+ // might not get a parent, take ownership to avoid leak
+ qt_gst_object_ref_sink(GST_OBJECT(m_colorSpace));
+#endif
+
+ m_nullVideoSink = gst_element_factory_make("fakesink", NULL);
+ g_object_set(G_OBJECT(m_nullVideoSink), "sync", true, NULL);
+ 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));
+ gst_bin_add_many(GST_BIN(m_videoOutputBin), m_videoIdentity, m_nullVideoSink, NULL);
+ gst_element_link(m_videoIdentity, m_nullVideoSink);
+
+ m_videoSink = m_nullVideoSink;
+
+ // add ghostpads
+ GstPad *pad = gst_element_get_static_pad(m_videoIdentity,"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
+ m_bus = gst_element_get_bus(m_playbin);
+ m_busHelper = new QGstreamerBusHelper(m_bus, this);
+ m_busHelper->installMessageFilter(this);
+
+ g_object_set(G_OBJECT(m_playbin), "video-sink", m_videoOutputBin, NULL);
+
+ 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;
+ gst_object_unref(GST_OBJECT(m_bus));
+ if (m_playbin)
+ gst_object_unref(GST_OBJECT(m_playbin));
+ gst_object_unref(GST_OBJECT(m_pipeline));
+#if !GST_CHECK_VERSION(1,0,0)
+ gst_object_unref(GST_OBJECT(m_colorSpace));
+#endif
+ gst_object_unref(GST_OBJECT(m_nullVideoSink));
+ gst_object_unref(GST_OBJECT(m_videoOutputBin));
+ }
+}
+
+GstElement *QGstreamerPlayerSession::playbin() const
+{
+ return m_playbin;
+}
+
+void QGstreamerPlayerSession::setPipeline(GstElement *pipeline)
+{
+ GstBus *bus = pipeline ? gst_element_get_bus(pipeline) : nullptr;
+ if (!bus)
+ return;
+
+ gst_object_unref(GST_OBJECT(m_pipeline));
+ m_pipeline = pipeline;
+ gst_object_unref(GST_OBJECT(m_bus));
+ m_bus = bus;
+ delete m_busHelper;
+ m_busHelper = new QGstreamerBusHelper(m_bus, this);
+ m_busHelper->installMessageFilter(this);
+
+ if (m_videoOutput)
+ m_busHelper->installMessageFilter(m_videoOutput);
+
+ if (m_playbin) {
+ gst_element_set_state(m_playbin, GST_STATE_NULL);
+ gst_object_unref(GST_OBJECT(m_playbin));
+ }
+
+ m_playbin = nullptr;
+ m_volumeElement = nullptr;
+ m_videoIdentity = nullptr;
+}
+
+#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, NULL);
+
+ 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 (m_playbin) {
+ m_tags.clear();
+ emit tagsChanged();
+
+ g_object_set(G_OBJECT(m_playbin), "uri", "appsrc://", NULL);
+
+ 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 (m_playbin) {
+ m_tags.clear();
+ emit tagsChanged();
+
+ g_object_set(G_OBJECT(m_playbin), "uri", m_request.url().toEncoded().constData(), NULL);
+
+ if (!m_streamTypes.isEmpty()) {
+ m_streamProperties.clear();
+ m_streamTypes.clear();
+
+ emit streamsChanged();
+ }
+ }
+}
+
+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) {
+ gst_element_seek(m_pipeline, rate, GST_FORMAT_TIME,
+ GstSeekFlags(GST_SEEK_FLAG_FLUSH),
+ GST_SEEK_TYPE_NONE,0,
+ GST_SEEK_TYPE_NONE,0 );
+ }
+ emit playbackRateChanged(m_playbackRate);
+ }
+}
+
+QMediaTimeRange QGstreamerPlayerSession::availablePlaybackRanges() const
+{
+ QMediaTimeRange ranges;
+
+ if (duration() <= 0)
+ return ranges;
+
+#if GST_CHECK_VERSION(0, 10, 31)
+ //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);
+#endif
+
+ 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, NULL);
+ break;
+ case QMediaStreamsControl::VideoStream:
+ g_object_get(G_OBJECT(m_playbin), "current-video", &streamNumber, NULL);
+ break;
+ case QMediaStreamsControl::SubPictureStream:
+ g_object_get(G_OBJECT(m_playbin), "current-text", &streamNumber, NULL);
+ 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, NULL);
+ break;
+ case QMediaStreamsControl::VideoStream:
+ g_object_set(G_OBJECT(m_playbin), "current-video", streamNumber, NULL);
+ break;
+ case QMediaStreamsControl::SubPictureStream:
+ g_object_set(G_OBJECT(m_playbin), "current-text", streamNumber, NULL);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+int QGstreamerPlayerSession::volume() const
+{
+ return m_volume;
+}
+
+bool QGstreamerPlayerSession::isMuted() const
+{
+ return m_muted;
+}
+
+bool QGstreamerPlayerSession::isAudioAvailable() const
+{
+ return m_audioAvailable;
+}
+
+#if GST_CHECK_VERSION(1,0,0)
+static GstPadProbeReturn block_pad_cb(GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
+#else
+static void block_pad_cb(GstPad *pad, gboolean blocked, gpointer user_data)
+#endif
+{
+ Q_UNUSED(pad);
+#if GST_CHECK_VERSION(1,0,0)
+ Q_UNUSED(info);
+ Q_UNUSED(user_data);
+ return GST_PAD_PROBE_OK;
+#else
+#ifdef DEBUG_PLAYBIN
+ qDebug() << "block_pad_cb, blocked:" << blocked;
+#endif
+ if (blocked && user_data) {
+ QGstreamerPlayerSession *session = reinterpret_cast<QGstreamerPlayerSession*>(user_data);
+ QMetaObject::invokeMethod(session, "finishVideoOutputChange", Qt::QueuedConnection);
+ }
+#endif
+}
+
+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);
+ }
+ }
+
+ QGstreamerVideoRendererInterface* renderer = qobject_cast<QGstreamerVideoRendererInterface*>(videoOutput);
+
+ m_renderer = renderer;
+
+ // If custom pipeline is considered to use video sink from the renderer
+ // need to create the pipeline when the renderer is ready.
+ emit rendererChanged();
+
+ // No sense to continue if custom pipeline requested.
+ if (!m_playbin)
+ return;
+
+#ifdef DEBUG_VO_BIN_DUMP
+ gst_debug_bin_to_dot_file_with_ts(GST_BIN(m_playbin),
+ GstDebugGraphDetails(GST_DEBUG_GRAPH_SHOW_ALL /* GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE | GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS | GST_DEBUG_GRAPH_SHOW_STATES*/),
+ "playbin_set");
+#endif
+
+ 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);
+
+#if !GST_CHECK_VERSION(1,0,0)
+ if (m_usingColorspaceElement) {
+ gst_element_unlink(m_colorSpace, m_videoSink);
+ gst_bin_remove(GST_BIN(m_videoOutputBin), m_colorSpace);
+ } else {
+ gst_element_unlink(m_videoIdentity, m_videoSink);
+ }
+#endif
+
+ 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 !GST_CHECK_VERSION(1,0,0)
+ m_usingColorspaceElement = false;
+ if (!linked) {
+ m_usingColorspaceElement = true;
+#ifdef DEBUG_PLAYBIN
+ qDebug() << "Failed to connect video output, inserting the colorspace element.";
+#endif
+ gst_bin_add(GST_BIN(m_videoOutputBin), m_colorSpace);
+ linked = gst_element_link_many(m_videoIdentity, m_colorSpace, m_videoSink, NULL);
+ }
+#endif
+
+ 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, NULL);
+ }
+
+ 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");
+#if GST_CHECK_VERSION(1,0,0)
+ this->pad_probe_id = gst_pad_add_probe(srcPad, (GstPadProbeType)(GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BLOCKING), block_pad_cb, this, NULL);
+#else
+ gst_pad_set_blocked_async(srcPad, true, &block_pad_cb, this);
+#endif
+ 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, NULL, 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))
+#if GST_CHECK_VERSION(1,0,0)
+ gst_pad_remove_probe(srcPad, this->pad_probe_id);
+#else
+ gst_pad_set_blocked_async(srcPad, false, &block_pad_cb, 0);
+#endif
+
+ m_pendingVideoSink = 0;
+ gst_object_unref(GST_OBJECT(srcPad));
+ return;
+ }
+
+#if !GST_CHECK_VERSION(1,0,0)
+ if (m_usingColorspaceElement) {
+ gst_element_set_state(m_colorSpace, GST_STATE_NULL);
+ gst_element_set_state(m_videoSink, GST_STATE_NULL);
+
+ gst_element_unlink(m_colorSpace, m_videoSink);
+ gst_bin_remove(GST_BIN(m_videoOutputBin), m_colorSpace);
+ } else {
+#else
+ {
+#endif
+ 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 !GST_CHECK_VERSION(1,0,0)
+ m_usingColorspaceElement = false;
+ if (!linked) {
+ m_usingColorspaceElement = true;
+#ifdef DEBUG_PLAYBIN
+ qDebug() << "Failed to connect video output, inserting the colorspace element.";
+#endif
+ gst_bin_add(GST_BIN(m_videoOutputBin), m_colorSpace);
+ linked = gst_element_link_many(m_videoIdentity, m_colorSpace, m_videoSink, NULL);
+ }
+#endif
+
+ 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
+
+#if !GST_CHECK_VERSION(1,0,0)
+ //it's necessary to send a new segment event just before
+ //the first buffer pushed to the new sink
+ g_signal_emit_by_name(m_videoIdentity,
+ "resend-new-segment",
+ true //emit connection-failed signal
+ //to have a chance to insert colorspace element
+ );
+#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;
+ }
+
+#if !GST_CHECK_VERSION(1,0,0)
+ if (m_usingColorspaceElement)
+ gst_element_set_state(m_colorSpace, state);
+#endif
+
+ 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))
+#if GST_CHECK_VERSION(1,0,0)
+ gst_pad_remove_probe(srcPad, this->pad_probe_id);
+#else
+ gst_pad_set_blocked_async(srcPad, false, &block_pad_cb, 0);
+#endif
+
+ gst_object_unref(GST_OBJECT(srcPad));
+
+#ifdef DEBUG_VO_BIN_DUMP
+ gst_debug_bin_to_dot_file_with_ts(GST_BIN(m_playbin),
+ GstDebugGraphDetails(GST_DEBUG_GRAPH_SHOW_ALL /* | GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE | GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS | GST_DEBUG_GRAPH_SHOW_STATES */),
+ "playbin_finish");
+#endif
+}
+
+#if !GST_CHECK_VERSION(1,0,0)
+
+void QGstreamerPlayerSession::insertColorSpaceElement(GstElement *element, gpointer data)
+{
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO;
+#endif
+ Q_UNUSED(element);
+ QGstreamerPlayerSession* session = reinterpret_cast<QGstreamerPlayerSession*>(data);
+
+ if (session->m_usingColorspaceElement)
+ return;
+ session->m_usingColorspaceElement = true;
+
+#ifdef DEBUG_PLAYBIN
+ qDebug() << "Failed to connect video output, inserting the colorspace elemnt.";
+ qDebug() << "notify the video connector it has to emit a new segment message...";
+#endif
+ //it's necessary to send a new segment event just before
+ //the first buffer pushed to the new sink
+ g_signal_emit_by_name(session->m_videoIdentity,
+ "resend-new-segment",
+ false // don't emit connection-failed signal
+ );
+
+ gst_element_unlink(session->m_videoIdentity, session->m_videoSink);
+ gst_bin_add(GST_BIN(session->m_videoOutputBin), session->m_colorSpace);
+ gst_element_link_many(session->m_videoIdentity, session->m_colorSpace, session->m_videoSink, NULL);
+
+ GstState state = GST_STATE_VOID_PENDING;
+
+ switch (session->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(session->m_colorSpace, state);
+}
+
+#endif
+
+bool QGstreamerPlayerSession::isVideoAvailable() const
+{
+ return m_videoAvailable;
+}
+
+bool QGstreamerPlayerSession::isSeekable() const
+{
+ return m_seekable;
+}
+
+bool QGstreamerPlayerSession::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));
+ gint64 position = ms * 1000000;
+ bool isSeeking = gst_element_seek(m_pipeline,
+ m_playbackRate,
+ GST_FORMAT_TIME,
+ GstSeekFlags(GST_SEEK_FLAG_FLUSH),
+ GST_SEEK_TYPE_SET,
+ position,
+ GST_SEEK_TYPE_NONE,
+ 0);
+ 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, NULL);
+
+ 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, NULL);
+
+ 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
+ QStringList states;
+ states << "GST_STATE_VOID_PENDING" << "GST_STATE_NULL" << "GST_STATE_READY" << "GST_STATE_PAUSED" << "GST_STATE_PLAYING";
+
+ qDebug() << QString("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:
+#if GST_CHECK_VERSION(0,10,13)
+ 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;
+ }
+#if GST_CHECK_VERSION(0,10,23)
+ case GST_MESSAGE_REQUEST_STATE:
+#endif
+#endif
+ 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, NULL);
+ g_object_get(G_OBJECT(m_playbin), "n-video", &videoStreamsCount, NULL);
+ g_object_get(G_OBJECT(m_playbin), "n-text", &textStreamsCount, NULL);
+
+ 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 GST_CHECK_VERSION(1,0,0)
+ if (tags && GST_IS_TAG_LIST(tags)) {
+#else
+ if (tags && gst_is_tag_list(tags)) {
+#endif
+ 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, NULL);
+ 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(), NULL);
+ }
+
+ // 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;
+ else {
+ 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, NULL);
+
+ 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;
+#if GST_CHECK_VERSION(1,0,0)
+ // Gst 1.x -> nanosecond
+ convertedTimeout *= 1000000000;
+#else
+ // Gst 0.10 -> microsecond
+ convertedTimeout *= 1000000;
+#endif
+ g_object_set(G_OBJECT(source), "timeout", convertedTimeout, NULL);
+ self->m_sourceType = UDPSrc;
+ //The udpsrc is always a live source.
+ self->m_isLiveSource = true;
+ } 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), NULL);
+ 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, NULL);
+ 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), NULL);
+ } 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, NULL);
+ } 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, NULL);
+
+ 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, NULL);
+
+ 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, NULL);
+ if (m_muted != muted) {
+ m_muted = muted;
+#ifdef DEBUG_PLAYBIN
+ qDebug() << Q_FUNC_INFO << m_muted;
+#endif
+ emit mutedStateChanged(muted);
+ }
+}
+
+#if !GST_CHECK_VERSION(0, 10, 33)
+static gboolean factory_can_src_any_caps (GstElementFactory *factory, const GstCaps *caps)
+{
+ GList *templates;
+
+ g_return_val_if_fail(factory != NULL, FALSE);
+ g_return_val_if_fail(caps != NULL, FALSE);
+
+ templates = factory->staticpadtemplates;
+
+ while (templates) {
+ GstStaticPadTemplate *templ = (GstStaticPadTemplate *)templates->data;
+
+ if (templ->direction == GST_PAD_SRC) {
+ GstCaps *templcaps = gst_static_caps_get(&templ->static_caps);
+
+ if (qt_gst_caps_can_intersect(caps, templcaps)) {
+ gst_caps_unref(templcaps);
+ return TRUE;
+ }
+ gst_caps_unref(templcaps);
+ }
+ templates = g_list_next(templates);
+ }
+
+ return FALSE;
+}
+#endif
+
+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");
+#if GST_CHECK_VERSION(1,0,0)
+ GstCaps *sinkCaps = gst_pad_query_caps(sinkPad, NULL);
+#else
+ GstCaps *sinkCaps = gst_pad_get_caps(sinkPad);
+#endif
+
+#if !GST_CHECK_VERSION(0, 10, 33)
+ if (!factory_can_src_any_caps(factory, sinkCaps))
+#else
+ if (!gst_element_factory_can_src_any_caps(factory, sinkCaps))
+#endif
+ 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", NULL, NULL);
+ } else if (g_str_has_prefix(elementName, "uridecodebin") ||
+#if GST_CHECK_VERSION(1,0,0)
+ g_str_has_prefix(elementName, "decodebin")) {
+#else
+ g_str_has_prefix(elementName, "decodebin2")) {
+ if (g_str_has_prefix(elementName, "uridecodebin")) {
+ // Add video/x-surface (VAAPI) to default raw formats
+ g_object_set(G_OBJECT(element), "caps", gst_static_caps_get(&static_RawCaps), NULL);
+ // listen for uridecodebin autoplug-select to skip VAAPI usage when the current
+ // video sink doesn't support it
+ g_signal_connect(element, "autoplug-select", G_CALLBACK(handleAutoplugSelect), session);
+ }
+#endif
+ //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, NULL);
+ 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_NULL);
+
+ 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/gsttools/qgstreamervideooverlay.cpp b/src/gsttools/qgstreamervideooverlay.cpp
index de4f255d5..1f3e28549 100644
--- a/src/gsttools/qgstreamervideooverlay.cpp
+++ b/src/gsttools/qgstreamervideooverlay.cpp
@@ -379,27 +379,13 @@ QGstreamerVideoOverlay::QGstreamerVideoOverlay(QObject *parent, const QByteArray
: QObject(parent)
, QGstreamerBufferProbe(QGstreamerBufferProbe::ProbeCaps)
{
+ GstElement *sink = nullptr;
if (!elementName.isEmpty())
- m_videoSink = gst_element_factory_make(elementName.constData(), NULL);
+ sink = gst_element_factory_make(elementName.constData(), NULL);
else
- m_videoSink = findBestVideoSink();
+ sink = findBestVideoSink();
- if (m_videoSink) {
- qt_gst_object_ref_sink(GST_OBJECT(m_videoSink)); //Take ownership
-
- GstPad *pad = gst_element_get_static_pad(m_videoSink, "sink");
- addProbeToPad(pad);
- gst_object_unref(GST_OBJECT(pad));
-
- QString sinkName(QLatin1String(GST_OBJECT_NAME(m_videoSink)));
- bool isVaapi = sinkName.startsWith(QLatin1String("vaapisink"));
- m_sinkProperties = isVaapi ? new QVaapiSinkProperties(m_videoSink) : new QXVImageSinkProperties(m_videoSink);
-
- if (m_sinkProperties->hasShowPrerollFrame()) {
- g_signal_connect(m_videoSink, "notify::show-preroll-frame",
- G_CALLBACK(showPrerollFrameChanged), this);
- }
- }
+ setVideoSink(sink);
}
QGstreamerVideoOverlay::~QGstreamerVideoOverlay()
@@ -418,6 +404,31 @@ 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;
diff --git a/src/gsttools/qgstreamervideorenderer.cpp b/src/gsttools/qgstreamervideorenderer.cpp
index 412257739..1b5cc8caf 100644
--- a/src/gsttools/qgstreamervideorenderer.cpp
+++ b/src/gsttools/qgstreamervideorenderer.cpp
@@ -45,25 +45,44 @@
#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),m_videoSink(0), m_surface(0)
+ : QVideoRendererControl(parent)
{
}
QGstreamerVideoRenderer::~QGstreamerVideoRenderer()
{
- if (m_videoSink)
- gst_object_unref(GST_OBJECT(m_videoSink));
+ 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) {
- m_videoSink = QVideoSurfaceGstSink::createSink(m_surface);
- qt_gst_object_ref_sink(GST_OBJECT(m_videoSink)); //Take ownership
+ auto sink = reinterpret_cast<GstElement *>(QVideoSurfaceGstSink::createSink(m_surface));
+ resetSink(m_videoSink, sink);
}
- return reinterpret_cast<GstElement*>(m_videoSink);
+ return m_videoSink;
}
void QGstreamerVideoRenderer::stopRenderer()
@@ -80,11 +99,7 @@ QAbstractVideoSurface *QGstreamerVideoRenderer::surface() const
void QGstreamerVideoRenderer::setSurface(QAbstractVideoSurface *surface)
{
if (m_surface != surface) {
- //qDebug() << Q_FUNC_INFO << surface;
- if (m_videoSink)
- gst_object_unref(GST_OBJECT(m_videoSink));
-
- m_videoSink = 0;
+ resetSink(m_videoSink);
if (m_surface) {
disconnect(m_surface.data(), SIGNAL(supportedFormatsChanged()),
@@ -98,6 +113,7 @@ void QGstreamerVideoRenderer::setSurface(QAbstractVideoSurface *surface)
if (m_surface) {
connect(m_surface.data(), SIGNAL(supportedFormatsChanged()),
this, SLOT(handleFormatChange()));
+ QGstVideoRendererSink::setSurface(m_surface);
}
if (wasReady != isReady())
@@ -109,11 +125,5 @@ void QGstreamerVideoRenderer::setSurface(QAbstractVideoSurface *surface)
void QGstreamerVideoRenderer::handleFormatChange()
{
- //qDebug() << "Supported formats list has changed, reload video output";
-
- if (m_videoSink)
- gst_object_unref(GST_OBJECT(m_videoSink));
-
- m_videoSink = 0;
- emit sinkChanged();
+ setVideoSink(nullptr);
}
diff --git a/src/gsttools/qgstreamervideowidget.cpp b/src/gsttools/qgstreamervideowidget.cpp
index 792df4243..633f39fa2 100644
--- a/src/gsttools/qgstreamervideowidget.cpp
+++ b/src/gsttools/qgstreamervideowidget.cpp
@@ -135,6 +135,11 @@ GstElement *QGstreamerVideoWidgetControl::videoSink()
return m_videoOverlay.videoSink();
}
+void QGstreamerVideoWidgetControl::setVideoSink(GstElement *sink)
+{
+ m_videoOverlay.setVideoSink(sink);
+}
+
void QGstreamerVideoWidgetControl::onOverlayActiveChanged()
{
updateWidgetAttributes();
diff --git a/src/gsttools/qgstvideorenderersink.cpp b/src/gsttools/qgstvideorenderersink.cpp
index 4c73c26a3..09fdd42a6 100644
--- a/src/gsttools/qgstvideorenderersink.cpp
+++ b/src/gsttools/qgstvideorenderersink.cpp
@@ -394,21 +394,27 @@ void QVideoSurfaceGstDelegate::updateSupportedFormats()
}
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));
- sink->delegate = new QVideoSurfaceGstDelegate(surface);
-
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;
@@ -430,6 +436,10 @@ GType QGstVideoRendererSink::get_type()
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;
@@ -453,6 +463,11 @@ void QGstVideoRendererSink::class_init(gpointer g_class, gpointer class_data)
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;
@@ -476,8 +491,8 @@ void QGstVideoRendererSink::instance_init(GTypeInstance *instance, gpointer g_cl
VO_SINK(instance);
Q_UNUSED(g_class);
-
- sink->delegate = 0;
+ sink->delegate = new QVideoSurfaceGstDelegate(current_surface);
+ sink->delegate->moveToThread(current_surface->thread());
}
void QGstVideoRendererSink::finalize(GObject *object)