summaryrefslogtreecommitdiffstats
path: root/src/multimedia/platform/gstreamer
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@qt.io>2021-04-30 17:20:52 +0200
committerLars Knoll <lars.knoll@qt.io>2021-05-07 11:10:15 +0000
commit6f0de93bd6e41a0a0393d07b20a6f4af90193ab0 (patch)
tree62fda1bddcf08b5e7065e5ba3eed0a5b322da62a /src/multimedia/platform/gstreamer
parent7b965a3d68423ab612e6ef9219a528b73fcaa2d9 (diff)
Pass the remaining mediaplayerbackend autotests
* Add a notification mechanism to the pipeline so the video output knows when QMediaPlayer is in a stopped state (using QProperty), as we want the pipeline to be in a paused state internally, but not get preroll frames if we're in paused externally. * Properly emit metadataChanged() signals, and ensure we're in GST_STATE_PAUSED before trying a seek. Change-Id: I1cad557e648f82909a63cba8d6144df8476524f5 Reviewed-by: Doris Verria <doris.verria@qt.io> Reviewed-by: Lars Knoll <lars.knoll@qt.io>
Diffstat (limited to 'src/multimedia/platform/gstreamer')
-rw-r--r--src/multimedia/platform/gstreamer/common/qgst_p.h9
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstpipeline.cpp13
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstpipeline_p.h7
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp63
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstreamervideooutput.cpp18
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstreamervideooutput_p.h5
6 files changed, 93 insertions, 22 deletions
diff --git a/src/multimedia/platform/gstreamer/common/qgst_p.h b/src/multimedia/platform/gstreamer/common/qgst_p.h
index 0f42bab41..14fd2bd46 100644
--- a/src/multimedia/platform/gstreamer/common/qgst_p.h
+++ b/src/multimedia/platform/gstreamer/common/qgst_p.h
@@ -451,6 +451,15 @@ public:
#endif
return change == GST_STATE_CHANGE_SUCCESS;
}
+ bool finishStateChange()
+ {
+ auto change = gst_element_get_state(element(), nullptr, nullptr, 10000*1e6 /*nano seconds*/);
+#ifndef QT_NO_DEBUG
+ if (change != GST_STATE_CHANGE_SUCCESS && change != GST_STATE_CHANGE_NO_PREROLL)
+ qWarning() << "Could finish change state of" << name();
+#endif
+ return change == GST_STATE_CHANGE_SUCCESS;
+ }
void lockState(bool locked) { gst_element_set_locked_state(element(), locked); }
bool isStateLocked() const { return gst_element_is_locked_state(element()); }
diff --git a/src/multimedia/platform/gstreamer/common/qgstpipeline.cpp b/src/multimedia/platform/gstreamer/common/qgstpipeline.cpp
index 40aab13b2..d3f75b612 100644
--- a/src/multimedia/platform/gstreamer/common/qgstpipeline.cpp
+++ b/src/multimedia/platform/gstreamer/common/qgstpipeline.cpp
@@ -43,6 +43,7 @@
#include <QtCore/qlist.h>
#include <QtCore/qabstracteventdispatcher.h>
#include <QtCore/qcoreapplication.h>
+#include <QtCore/qproperty.h>
#include "qgstpipeline_p.h"
#include "qgstreamermessage_p.h"
@@ -52,7 +53,7 @@ QT_BEGIN_NAMESPACE
class QGstPipelinePrivate : public QObject
{
Q_OBJECT
- friend class QGstreamerBusHelperPrivate;
+public:
int m_ref = 0;
guint m_tag = 0;
@@ -61,8 +62,8 @@ class QGstPipelinePrivate : public QObject
QMutex filterMutex;
QList<QGstreamerSyncMessageFilter*> syncFilters;
QList<QGstreamerBusMessageFilter*> busFilters;
+ QProperty<bool> inStoppedState;
-public:
QGstPipelinePrivate(GstBus* bus, QObject* parent = 0);
~QGstPipelinePrivate();
@@ -130,7 +131,8 @@ private:
QGstPipelinePrivate::QGstPipelinePrivate(GstBus* bus, QObject* parent)
: QObject(parent),
- m_bus(bus)
+ m_bus(bus),
+ inStoppedState(true)
{
gst_object_ref(GST_OBJECT(bus));
@@ -227,6 +229,11 @@ QGstPipeline::~QGstPipeline()
d->deref();
}
+QProperty<bool> *QGstPipeline::inStoppedState()
+{
+ return &d->inStoppedState;
+}
+
void QGstPipeline::installMessageFilter(QGstreamerSyncMessageFilter *filter)
{
d->installMessageFilter(filter);
diff --git a/src/multimedia/platform/gstreamer/common/qgstpipeline_p.h b/src/multimedia/platform/gstreamer/common/qgstpipeline_p.h
index f6e7a7333..e3bc4164a 100644
--- a/src/multimedia/platform/gstreamer/common/qgstpipeline_p.h
+++ b/src/multimedia/platform/gstreamer/common/qgstpipeline_p.h
@@ -85,6 +85,13 @@ public:
QGstPipeline(GstPipeline *p);
~QGstPipeline();
+ // This is needed to help us avoid sending QVideoFrames or audio buffers to the
+ // application while we're prerolling the pipeline.
+ // QMediaPlayer is still in a stopped state, while we put the gstreamer pipeline into a
+ // Paused state so that we can get the required metadata of the stream and also have a fast
+ // transition to play.
+ QProperty<bool> *inStoppedState();
+
void installMessageFilter(QGstreamerSyncMessageFilter *filter);
void removeMessageFilter(QGstreamerSyncMessageFilter *filter);
void installMessageFilter(QGstreamerBusMessageFilter *filter);
diff --git a/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp b/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp
index b9e16359d..7f1e5d5ce 100644
--- a/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp
+++ b/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp
@@ -39,7 +39,7 @@
#include <private/qgstreamermediaplayer_p.h>
#include <private/qgstreamervideorenderer_p.h>
-#include <private/qgstreamerbushelper_p.h>
+#include <private/qgstpipeline_p.h>
#include <private/qgstreamermetadata_p.h>
#include <private/qgstreamerformatinfo_p.h>
#include <private/qgstreameraudiooutput_p.h>
@@ -156,17 +156,23 @@ void QGstreamerMediaPlayer::setPlaybackRate(qreal rate)
void QGstreamerMediaPlayer::setPosition(qint64 pos)
{
- qCDebug(qLcMediaPlayer) << Q_FUNC_INFO << pos/1000.0;
+ qint64 currentPos = playerPipeline.position()/1e6;
+ if (pos == currentPos)
+ return;
+ playerPipeline.finishStateChange();
playerPipeline.seek(pos*1e6, m_playbackRate);
+ qCDebug(qLcMediaPlayer) << Q_FUNC_INFO << pos << playerPipeline.position()/1e6;
if (mediaStatus() == QMediaPlayer::EndOfMedia)
mediaStatusChanged(QMediaPlayer::LoadedMedia);
+ positionChanged(pos);
}
void QGstreamerMediaPlayer::play()
{
- if (m_url.isEmpty())
+ if (state() == QMediaPlayer::PlayingState || m_url.isEmpty())
return;
+ *playerPipeline.inStoppedState() = false;
if (mediaStatus() == QMediaPlayer::EndOfMedia) {
playerPipeline.seek(0, m_playbackRate);
updatePosition();
@@ -176,16 +182,22 @@ void QGstreamerMediaPlayer::play()
int ret = playerPipeline.setState(GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE)
qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the playing state.";
+ if (mediaStatus() == QMediaPlayer::LoadedMedia)
+ mediaStatusChanged(QMediaPlayer::BufferedMedia);
emit stateChanged(QMediaPlayer::PlayingState);
positionUpdateTimer.start(100);
}
void QGstreamerMediaPlayer::pause()
{
- if (m_url.isEmpty())
+ if (state() == QMediaPlayer::PausedState || m_url.isEmpty())
return;
positionUpdateTimer.stop();
+ if (*playerPipeline.inStoppedState()) {
+ *playerPipeline.inStoppedState() = false;
+ playerPipeline.seek(playerPipeline.position(), m_playbackRate);
+ }
int ret = playerPipeline.setState(GST_STATE_PAUSED);
if (ret == GST_STATE_CHANGE_FAILURE)
qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the paused state.";
@@ -199,12 +211,15 @@ void QGstreamerMediaPlayer::pause()
void QGstreamerMediaPlayer::stop()
{
+ if (state() == QMediaPlayer::StoppedState)
+ return;
stopOrEOS(false);
}
void QGstreamerMediaPlayer::stopOrEOS(bool eos)
{
positionUpdateTimer.stop();
+ *playerPipeline.inStoppedState() = true;
bool ret = playerPipeline.setStateSync(GST_STATE_PAUSED);
if (!ret)
qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the stopped state.";
@@ -517,8 +532,6 @@ void QGstreamerMediaPlayer::setMedia(const QUrl &content, QIODevice *stream)
m_url = content;
m_stream = stream;
- m_metaData.clear();
- m_duration = 0;
if (!src.isNull())
playerPipeline.remove(src);
@@ -528,13 +541,22 @@ void QGstreamerMediaPlayer::setMedia(const QUrl &content, QIODevice *stream)
decoder = QGstElement();
removeAllOutputs();
- if (content.isEmpty()) {
- stateChanged(QMediaPlayer::StoppedState);
+ if (m_duration != 0) {
+ m_duration = 0;
+ durationChanged(0);
+ }
+ stateChanged(QMediaPlayer::StoppedState);
+ if (position() != 0)
positionChanged(0);
- mediaStatusChanged(QMediaPlayer::NoMedia);
- return;
+ mediaStatusChanged(QMediaPlayer::NoMedia);
+ if (!m_metaData.isEmpty()) {
+ m_metaData.clear();
+ metaDataChanged();
}
+ if (content.isEmpty())
+ return;
+
if (m_stream) {
if (!m_appSrc)
m_appSrc = new QGstAppSrc(this);
@@ -621,8 +643,10 @@ static QGstStructure endOfChain(const QGstStructure &s)
void QGstreamerMediaPlayer::parseStreamsAndMetadata()
{
qCDebug(qLcMediaPlayer) << "============== parse topology ============";
- if (topology.isNull())
+ if (topology.isNull()) {
+ qCDebug(qLcMediaPlayer) << " null topology";
return;
+ }
auto caps = topology["caps"].toCaps();
auto structure = caps.at(0);
auto fileFormat = QGstreamerFormatInfo::fileFormatForCaps(structure);
@@ -641,8 +665,11 @@ void QGstreamerMediaPlayer::parseStreamsAndMetadata()
auto demux = endOfChain(topology);
auto next = demux["next"];
- if (!next.isList())
+ if (!next.isList()) {
+ qCDebug(qLcMediaPlayer) << " no additional streams";
+ emit metaDataChanged();
return;
+ }
// collect stream info
int size = next.listSize();
@@ -669,13 +696,13 @@ void QGstreamerMediaPlayer::parseStreamsAndMetadata()
}
QGstPad sinkPad = inputSelector[VideoStream].getObject("active-pad");
- if (sinkPad.isNull())
- return;
- bool hasTags = g_object_class_find_property (G_OBJECT_GET_CLASS (sinkPad.object()), "tags") != NULL;
+ if (!sinkPad.isNull()) {
+ bool hasTags = g_object_class_find_property (G_OBJECT_GET_CLASS (sinkPad.object()), "tags") != NULL;
- GstTagList *tl = nullptr;
- g_object_get(sinkPad.object(), "tags", &tl, nullptr);
- qCDebug(qLcMediaPlayer) << " tags=" << hasTags << (tl ? gst_tag_list_to_string(tl) : "(null)");
+ GstTagList *tl = nullptr;
+ g_object_get(sinkPad.object(), "tags", &tl, nullptr);
+ qCDebug(qLcMediaPlayer) << " tags=" << hasTags << (tl ? gst_tag_list_to_string(tl) : "(null)");
+ }
qCDebug(qLcMediaPlayer) << "============== end parse topology ============";
diff --git a/src/multimedia/platform/gstreamer/common/qgstreamervideooutput.cpp b/src/multimedia/platform/gstreamer/common/qgstreamervideooutput.cpp
index 57753f53d..f9bf9f6c2 100644
--- a/src/multimedia/platform/gstreamer/common/qgstreamervideooutput.cpp
+++ b/src/multimedia/platform/gstreamer/common/qgstreamervideooutput.cpp
@@ -83,6 +83,12 @@ void QGstreamerVideoOutput::setVideoSink(QVideoSink *sink)
sinkChanged();
}
+void QGstreamerVideoOutput::setPipeline(const QGstPipeline &pipeline)
+{
+ gstPipeline = pipeline;
+ stoppedStateHandler = gstPipeline.inStoppedState()->onValueChanged(std::function([this]() {updatePrerollFrame();}));
+}
+
void QGstreamerVideoOutput::setIsPreview()
{
// configures the queue to be fast and lightweight for camera preview
@@ -116,13 +122,24 @@ void QGstreamerVideoOutput::updateVideoSink(const QGstElement &sink)
pad.addProbe<&QGstreamerVideoOutput::prepareVideoOutputChange>(this, GstPadProbeType(GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BLOCKING));
}
+void QGstreamerVideoOutput::updatePrerollFrame()
+{
+ bool stopped = true;
+ if (!gstPipeline.isNull() && !*gstPipeline.inStoppedState())
+ stopped = false;
+ if (!isFakeSink)
+ videoSink.set("show-preroll-frame", !stopped);
+}
+
void QGstreamerVideoOutput::sinkChanged()
{
QGstElement gstSink;
if (m_videoWindow) {
gstSink = m_videoWindow->gstSink();
+ isFakeSink = false;
} else {
gstSink = QGstElement("fakesink", "fakevideosink");
+ isFakeSink = true;
}
qDebug() << "sinkChanged" << gstSink.name();
updateVideoSink(gstSink);
@@ -140,6 +157,7 @@ void QGstreamerVideoOutput::changeVideoOutput()
videoSink.setState(GST_STATE_NULL);
gstVideoOutput.remove(videoSink);
videoSink = newVideoSink;
+ updatePrerollFrame();
videoConvert.link(videoSink);
GstEvent *event = gst_event_new_reconfigure();
gst_element_send_event(videoSink.element(), event);
diff --git a/src/multimedia/platform/gstreamer/common/qgstreamervideooutput_p.h b/src/multimedia/platform/gstreamer/common/qgstreamervideooutput_p.h
index a8ed8960c..16ac73b78 100644
--- a/src/multimedia/platform/gstreamer/common/qgstreamervideooutput_p.h
+++ b/src/multimedia/platform/gstreamer/common/qgstreamervideooutput_p.h
@@ -74,13 +74,14 @@ public:
void setVideoSink(QVideoSink *sink);
- void setPipeline(const QGstPipeline &pipeline) { gstPipeline = pipeline; }
+ void setPipeline(const QGstPipeline &pipeline);
QGstElement gstElement() const { return gstVideoOutput; }
void setIsPreview();
void updateVideoSink(const QGstElement &sink);
+ void updatePrerollFrame();
public slots:
void sinkChanged();
void changeVideoOutput();
@@ -90,6 +91,8 @@ private:
QVideoSink *m_videoSink = nullptr;
QPointer<QGstreamerVideoSink> m_videoWindow;
+ std::optional<QPropertyChangeHandler<std::function<void()>>> stoppedStateHandler;
+ bool isFakeSink = true;
// Gst elements
QGstPipeline gstPipeline;