diff options
author | Lars Knoll <lars.knoll@qt.io> | 2021-04-30 17:20:52 +0200 |
---|---|---|
committer | Lars Knoll <lars.knoll@qt.io> | 2021-05-07 11:10:15 +0000 |
commit | 6f0de93bd6e41a0a0393d07b20a6f4af90193ab0 (patch) | |
tree | 62fda1bddcf08b5e7065e5ba3eed0a5b322da62a /src/multimedia/platform/gstreamer | |
parent | 7b965a3d68423ab612e6ef9219a528b73fcaa2d9 (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')
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; |