From 6d20a812549f312e7a3f6dcc60a2e8c4221dca80 Mon Sep 17 00:00:00 2001 From: Lars Knoll Date: Sun, 14 Feb 2021 21:54:37 +0100 Subject: Start rewriting the gstreamer player backend Use our QGst* wrappers to simplify the code base. Those classes are significantly easier to use than the Gstreamer C API. The new implementation doesn't use playbin, but does recreate a similar pipeline as playbin uses. This simplifies the Qt integration as we can now deal with a directly defined pipeline and don't have to insert our own elements into the pre-existing pipeline at the right moment. Handling of multiple streams is missing right now, but that had never been implemented in QMediaPlayer before neither. Change-Id: I77a2df246e0ab20b307d0c45645f896bf127d756 Reviewed-by: Doris Verria Reviewed-by: Lars Knoll --- examples/multimediawidgets/player/main.cpp | 2 +- src/multimedia/platform/gstreamer/common/qgst_p.h | 25 + .../platform/gstreamer/common/qgstappsrc.cpp | 24 +- .../platform/gstreamer/common/qgstappsrc_p.h | 4 + .../gstreamer/common/qgstreamermediaplayer.cpp | 836 +++++++++++++-------- .../gstreamer/common/qgstreamermediaplayer_p.h | 76 +- .../gstreamer/common/qgstreamermessage_p.h | 6 +- 7 files changed, 613 insertions(+), 360 deletions(-) diff --git a/examples/multimediawidgets/player/main.cpp b/examples/multimediawidgets/player/main.cpp index 4e2b9b6b3..cc4418a1d 100644 --- a/examples/multimediawidgets/player/main.cpp +++ b/examples/multimediawidgets/player/main.cpp @@ -82,7 +82,7 @@ int main(int argc, char *argv[]) if (!parser.positionalArguments().isEmpty() && player.isPlayerAvailable()) { QList urls; for (auto &a: parser.positionalArguments()) - urls.append(QUrl::fromUserInput(a, QDir::currentPath(), QUrl::AssumeLocalFile)); + urls.append(QUrl::fromUserInput(a, QDir::currentPath())); player.addToPlaylist(urls); } diff --git a/src/multimedia/platform/gstreamer/common/qgst_p.h b/src/multimedia/platform/gstreamer/common/qgst_p.h index 5e95ebe0c..dab5219b9 100644 --- a/src/multimedia/platform/gstreamer/common/qgst_p.h +++ b/src/multimedia/platform/gstreamer/common/qgst_p.h @@ -276,6 +276,8 @@ public: const char *name() const { return GST_OBJECT_NAME(m_object); } }; +class QGstElement; + class QGstPad : public QGstObject { public: @@ -292,6 +294,9 @@ public: bool isLinked() const { return gst_pad_is_linked(pad()); } bool link(const QGstPad &sink) const { return gst_pad_link(pad(), sink.pad()) == GST_PAD_LINK_OK; } + bool unlink(const QGstPad &sink) const { return gst_pad_unlink(pad(), sink.pad()); } + QGstPad peer() const { return QGstPad(gst_pad_get_peer(pad()), HasRef); } + inline QGstElement parent() const; GstPad *pad() const { return GST_PAD_CAST(object()); } @@ -332,9 +337,12 @@ public: { return gst_element_link_many(element(), n1.element(), n2.element(), n3.element(), nullptr); } bool link(const QGstElement &n1, const QGstElement &n2, const QGstElement &n3, const QGstElement &n4) { return gst_element_link_many(element(), n1.element(), n2.element(), n3.element(), n4.element(), nullptr); } + bool link(const QGstElement &n1, const QGstElement &n2, const QGstElement &n3, const QGstElement &n4, const QGstElement &n5) + { return gst_element_link_many(element(), n1.element(), n2.element(), n3.element(), n4.element(), n5.element(), nullptr); } QGstPad staticPad(const char *name) { return QGstPad(gst_element_get_static_pad(element(), name), HasRef); } QGstPad getRequestPad(const char *name) { return QGstPad(gst_element_get_request_pad(element(), name), HasRef); } + void releaseRequestPad(const QGstPad &pad) { return gst_element_release_request_pad(element(), pad.pad()); } GstState state() const { @@ -384,10 +392,25 @@ public: g_signal_connect (element(), "pad-added", G_CALLBACK(Impl::callback), instance); } + template + void onPadRemoved(T *instance) { + struct Impl { + static void callback(GstElement *e, GstPad *pad, gpointer userData) { + (static_cast(userData)->*Member)(QGstElement(e, NeedsRef), QGstPad(pad, NeedsRef)); + }; + }; + + g_signal_connect (element(), "pad-removed", G_CALLBACK(Impl::callback), instance); + } GstElement *element() const { return GST_ELEMENT_CAST(m_object); } }; +inline QGstElement QGstPad::parent() const +{ + return QGstElement(gst_pad_get_parent_element(pad()), HasRef); +} + class QGstBin : public QGstElement { public: @@ -412,6 +435,8 @@ public: { gst_bin_add_many(bin(), e1.element(), e2.element(), e3.element(), e4.element(), nullptr); } void add(const QGstElement &e1, const QGstElement &e2, const QGstElement &e3, const QGstElement &e4, const QGstElement &e5) { gst_bin_add_many(bin(), e1.element(), e2.element(), e3.element(), e4.element(), e5.element(), nullptr); } + void add(const QGstElement &e1, const QGstElement &e2, const QGstElement &e3, const QGstElement &e4, const QGstElement &e5, const QGstElement &e6) + { gst_bin_add_many(bin(), e1.element(), e2.element(), e3.element(), e4.element(), e5.element(), e6.element(), nullptr); } void remove(const QGstElement &element) { gst_bin_remove(bin(), element.element()); } diff --git a/src/multimedia/platform/gstreamer/common/qgstappsrc.cpp b/src/multimedia/platform/gstreamer/common/qgstappsrc.cpp index 9ba6fc09b..834a29c61 100644 --- a/src/multimedia/platform/gstreamer/common/qgstappsrc.cpp +++ b/src/multimedia/platform/gstreamer/common/qgstappsrc.cpp @@ -41,6 +41,10 @@ #include "qgstappsrc_p.h" #include "qgstutils_p.h" +#include "qnetworkreply.h" +#include "qloggingcategory.h" + +Q_LOGGING_CATEGORY(qLcAppSrc, "qt.multimedia.appsrc") QGstAppSrc::QGstAppSrc(QObject *parent) : QObject(parent) @@ -84,10 +88,12 @@ bool QGstAppSrc::setup(GstElement* appsrc) g_object_set(m_appSrc, "caps", caps, nullptr); g_object_set(m_appSrc, "format", GST_FORMAT_TIME, nullptr); } else { - qWarning() << "QGstAppSrc: Invalid caps"; + qCWarning(qLcAppSrc) << "Invalid caps"; } } + m_networkReply = qobject_cast(m_stream); + return true; } @@ -131,6 +137,7 @@ GstAppSrc *QGstAppSrc::element() void QGstAppSrc::onDataReady() { + qCDebug(qLcAppSrc) << "onDataReady" << m_stream->bytesAvailable() << m_stream->size(); if (!m_enoughData) { m_dataRequested = true; pushDataToAppSrc(); @@ -147,10 +154,11 @@ void QGstAppSrc::streamDestroyed() void QGstAppSrc::pushDataToAppSrc() { + qCDebug(qLcAppSrc) << "pushData" << m_stream->bytesAvailable(); if ((!isStreamValid() && !m_buffer) || !m_appSrc) return; - if (m_stream->atEnd()) { + if (m_stream->atEnd() && (!m_networkReply || !m_networkReply->isRunning())) { sendEOS(); return; } @@ -176,15 +184,20 @@ void QGstAppSrc::pushDataToAppSrc() gst_buffer_map(buffer, &mapInfo, GST_MAP_WRITE); void* bufferData = mapInfo.data; - buffer->offset = m_stream->pos(); + if (m_sequential) + buffer->offset = bytesReadSoFar; + else + buffer->offset = m_stream->pos(); qint64 bytesRead; if (m_buffer) bytesRead = m_buffer->read((char*)bufferData, size); else bytesRead = m_stream->read((char*)bufferData, size); buffer->offset_end = buffer->offset + bytesRead - 1; + bytesReadSoFar += bytesRead; gst_buffer_unmap(buffer, &mapInfo); + qCDebug(qLcAppSrc) << "pushing bytes into gstreamer" << m_stream->pos() << buffer->offset << bytesRead; if (bytesRead > 0) { m_dataRequested = false; @@ -198,7 +211,7 @@ void QGstAppSrc::pushDataToAppSrc() } } - if (m_stream->atEnd()) + if (m_stream->atEnd() && (!m_networkReply || !m_networkReply->isRunning())) sendEOS(); } @@ -229,6 +242,7 @@ gboolean QGstAppSrc::on_seek_data(GstAppSrc *element, guint64 arg0, gpointer use void QGstAppSrc::on_enough_data(GstAppSrc *element, gpointer userdata) { + qCDebug(qLcAppSrc) << "on_enough_data"; Q_UNUSED(element); QGstAppSrc *self = static_cast(userdata); if (self) @@ -237,6 +251,7 @@ void QGstAppSrc::on_enough_data(GstAppSrc *element, gpointer userdata) void QGstAppSrc::on_need_data(GstAppSrc *element, guint arg0, gpointer userdata) { + qCDebug(qLcAppSrc) << "on_need_data requesting bytes" << arg0; Q_UNUSED(element); QGstAppSrc *self = static_cast(userdata); if (self) { @@ -254,6 +269,7 @@ void QGstAppSrc::destroy_notify(gpointer data) void QGstAppSrc::sendEOS() { + qCDebug(qLcAppSrc) << "sending EOS"; if (!m_appSrc) return; diff --git a/src/multimedia/platform/gstreamer/common/qgstappsrc_p.h b/src/multimedia/platform/gstreamer/common/qgstappsrc_p.h index 29603eb98..3350c8a45 100644 --- a/src/multimedia/platform/gstreamer/common/qgstappsrc_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstappsrc_p.h @@ -63,6 +63,8 @@ QT_BEGIN_NAMESPACE +class QNetworkReply; + class Q_MULTIMEDIA_EXPORT QGstAppSrc : public QObject { Q_OBJECT @@ -119,6 +121,7 @@ private: void sendEOS(); QIODevice *m_stream = nullptr; + QNetworkReply *m_networkReply = nullptr; QRingBuffer *m_buffer = nullptr; QAudioFormat m_format; @@ -127,6 +130,7 @@ private: GstAppStreamType m_streamType = GST_APP_STREAM_TYPE_RANDOM_ACCESS; GstAppSrcCallbacks m_callbacks; qint64 m_maxBytes = 0; + qint64 bytesReadSoFar = 0; unsigned int m_dataRequestSize = ~0; bool m_dataRequested = false; bool m_enoughData = false; diff --git a/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp b/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp index 2a4fb269f..2e7df3c91 100644 --- a/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp +++ b/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp @@ -40,59 +40,82 @@ #include #include #include +#include +#include +#include +#include #include #include #include #include #include +#include +#include +#include #include #include #include -//#define DEBUG_PLAYBIN +Q_LOGGING_CATEGORY(qLcMediaPlayer, "qt.multimedia.player") QT_BEGIN_NAMESPACE QGstreamerMediaPlayer::QGstreamerMediaPlayer(QObject *parent) : QPlatformMediaPlayer(parent) { - m_session = new QGstreamerPlayerSession(this); - connect(m_session, &QGstreamerPlayerSession::positionChanged, this, &QGstreamerMediaPlayer::positionChanged); - connect(m_session, &QGstreamerPlayerSession::durationChanged, this, &QGstreamerMediaPlayer::durationChanged); - connect(m_session, &QGstreamerPlayerSession::mutedStateChanged, this, &QGstreamerMediaPlayer::mutedChanged); - connect(m_session, &QGstreamerPlayerSession::volumeChanged, this, &QGstreamerMediaPlayer::volumeChanged); - connect(m_session, &QGstreamerPlayerSession::stateChanged, this, &QGstreamerMediaPlayer::updateSessionState); - connect(m_session, &QGstreamerPlayerSession::bufferingProgressChanged, this, &QGstreamerMediaPlayer::setBufferProgress); - connect(m_session, &QGstreamerPlayerSession::playbackFinished, this, &QGstreamerMediaPlayer::processEOS); - connect(m_session, &QGstreamerPlayerSession::audioAvailableChanged, this, &QGstreamerMediaPlayer::audioAvailableChanged); - connect(m_session, &QGstreamerPlayerSession::videoAvailableChanged, this, &QGstreamerMediaPlayer::videoAvailableChanged); - connect(m_session, &QGstreamerPlayerSession::seekableChanged, this, &QGstreamerMediaPlayer::seekableChanged); - connect(m_session, &QGstreamerPlayerSession::error, this, &QGstreamerMediaPlayer::error); - connect(m_session, &QGstreamerPlayerSession::invalidMedia, this, &QGstreamerMediaPlayer::handleInvalidMedia); - connect(m_session, &QGstreamerPlayerSession::playbackRateChanged, this, &QGstreamerMediaPlayer::playbackRateChanged); - connect(m_session, &QGstreamerPlayerSession::metaDataChanged, this, &QGstreamerMediaPlayer::metaDataChanged); -} - -QGstreamerMediaPlayer::~QGstreamerMediaPlayer() = default; + audioInputSelector = QGstElement("input-selector", "audioInputSelector"); + audioQueue = QGstElement("queue", "audioQueue"); + audioConvert = QGstElement("audioconvert", "audioConvert"); + audioResample = QGstElement("audioresample", "audioResample"); + audioVolume = QGstElement("volume", "volume"); + audioSink = QGstElement("autoaudiosink", "autoAudioSink"); + playerPipeline.add(audioInputSelector, audioQueue, audioConvert, audioResample, audioVolume, audioSink); + audioInputSelector.link(audioQueue, audioConvert, audioResample, audioVolume, audioSink); + + videoInputSelector = QGstElement("input-selector", "videoInputSelector"); + videoQueue = QGstElement("queue", "videoQueue"); + videoConvert = QGstElement("videoconvert", "videoConvert"); + videoScale = QGstElement("videoscale", "videoScale"); + playerPipeline.add(videoInputSelector, videoQueue, videoConvert, videoScale); + videoInputSelector.link(videoQueue, videoConvert, videoScale); + + subTitleInputSelector = QGstElement("input-selector", "subTitleInputSelector"); + playerPipeline.add(subTitleInputSelector); + + playerPipeline.setState(GST_STATE_NULL); + + busHelper = new QGstreamerBusHelper(playerPipeline.bus().bus(), this); + qRegisterMetaType(); + connect(busHelper, &QGstreamerBusHelper::message, this, &QGstreamerMediaPlayer::busMessage); +} + +QGstreamerMediaPlayer::~QGstreamerMediaPlayer() +{ + playerPipeline.setStateSync(GST_STATE_NULL); + if (ownStream) + delete m_stream; + if (networkManager) + delete networkManager; +} qint64 QGstreamerMediaPlayer::position() const { - if (m_mediaStatus == QMediaPlayer::EndOfMedia) - return m_session->duration(); + if (playerPipeline.isNull()) + return 0; - return m_pendingSeekPosition != -1 ? m_pendingSeekPosition : m_session->position(); + return playerPipeline.position()/1e6; } qint64 QGstreamerMediaPlayer::duration() const { - return m_session->duration(); + return m_duration; } QMediaPlayer::State QGstreamerMediaPlayer::state() const { - return m_currentState; + return m_state; } QMediaPlayer::MediaStatus QGstreamerMediaPlayer::mediaStatus() const @@ -102,447 +125,596 @@ QMediaPlayer::MediaStatus QGstreamerMediaPlayer::mediaStatus() const int QGstreamerMediaPlayer::bufferStatus() const { - if (m_bufferProgress == -1) - return m_session->state() == QMediaPlayer::StoppedState ? 0 : 100; - return m_bufferProgress; } int QGstreamerMediaPlayer::volume() const { - return m_session->volume(); + return m_volume; } bool QGstreamerMediaPlayer::isMuted() const { - return m_session->isMuted(); + return m_muted; } bool QGstreamerMediaPlayer::isSeekable() const { - return m_session->isSeekable(); + return true; } QMediaTimeRange QGstreamerMediaPlayer::availablePlaybackRanges() const { - return m_session->availablePlaybackRanges(); + return QMediaTimeRange(); } qreal QGstreamerMediaPlayer::playbackRate() const { - return m_session->playbackRate(); + return m_playbackRate; } void QGstreamerMediaPlayer::setPlaybackRate(qreal rate) { - m_session->setPlaybackRate(rate); + if (rate == m_playbackRate) + return; + m_playbackRate = rate; + playerPipeline.seek(playerPipeline.position(), m_playbackRate); + emit playbackRateChanged(rate); } void QGstreamerMediaPlayer::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(); + qCDebug(qLcMediaPlayer) << Q_FUNC_INFO << pos/1000.0; + playerPipeline.seek(pos*1e6, m_playbackRate); } void QGstreamerMediaPlayer::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); + int ret = playerPipeline.setState(GST_STATE_PLAYING); + if (ret == GST_STATE_CHANGE_FAILURE) + qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the playing state."; + m_state = QMediaPlayer::PlayingState; + emit stateChanged(m_state); } void QGstreamerMediaPlayer::pause() { -#ifdef DEBUG_PLAYBIN - qDebug() << Q_FUNC_INFO; -#endif - m_userRequestedState = QMediaPlayer::PausedState; - // If the playback has not been started yet but pause is requested. - // Seek to the beginning to show first frame. - if (m_pendingSeekPosition == -1 && m_session->position() == 0) - m_pendingSeekPosition = 0; + int ret = playerPipeline.setState(GST_STATE_PAUSED); + if (ret == GST_STATE_CHANGE_FAILURE) + qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the paused state."; + m_state = QMediaPlayer::PausedState; + emit stateChanged(m_state); +} - playOrPause(QMediaPlayer::PausedState); +void QGstreamerMediaPlayer::stop() +{ + int ret = playerPipeline.setState(GST_STATE_NULL); + if (ret == GST_STATE_CHANGE_FAILURE) + qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the stopped state."; + m_state = QMediaPlayer::StoppedState; + emit stateChanged(m_state); } -void QGstreamerMediaPlayer::playOrPause(QMediaPlayer::State newState) +void QGstreamerMediaPlayer::setVolume(int vol) { - if (m_mediaStatus == QMediaPlayer::NoMedia) + if (vol == m_volume) return; + m_volume = vol; + audioVolume.set("volume", vol/100.); + emit volumeChanged(m_volume); +} - pushState(); - - if (m_setMediaPending) { - m_mediaStatus = QMediaPlayer::LoadingMedia; - setMedia(m_currentResource, m_stream); - } - - if (m_mediaStatus == QMediaPlayer::EndOfMedia && m_pendingSeekPosition == -1) { - m_pendingSeekPosition = 0; - } - - // show prerolled frame if switching from stopped state - if (m_pendingSeekPosition == -1) { - m_session->showPrerollFrames(true); - } else if (m_session->state() == QMediaPlayer::StoppedState) { - // Don't evaluate the next two conditions. - } else if (m_session->isSeekable()) { - m_session->pause(); - m_session->showPrerollFrames(true); - m_session->seek(m_pendingSeekPosition); - m_pendingSeekPosition = -1; - } else { - m_pendingSeekPosition = -1; - } +void QGstreamerMediaPlayer::setMuted(bool muted) +{ + if (muted == m_muted) + return; + m_muted = muted; + audioVolume.set("mute", muted); + emit mutedChanged(muted); +} - bool ok = false; +void QGstreamerMediaPlayer::busMessage(const QGstreamerMessage &message) +{ + if (message.isNull()) + return; - //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(); + qCDebug(qLcMediaPlayer) << "received bus message from" << message.source().name() << message.type(); + if (message.source() != playerPipeline && message.source() != decoder) + return; - if (!ok) - newState = QMediaPlayer::StoppedState; + GstMessage* gm = message.rawMessage(); + //tag message comes from elements inside playbin, not from playbin itself + switch (message.type()) { + case GST_MESSAGE_TAG: { + GstTagList *tag_list; + gst_message_parse_tag(gm, &tag_list); - if (m_mediaStatus == QMediaPlayer::InvalidMedia) - m_mediaStatus = QMediaPlayer::LoadingMedia; + m_metaData = QGstreamerMetaData::fromGstTagList(tag_list); - m_currentState = newState; + gst_tag_list_free(tag_list); - 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; + emit metaDataChanged(); + break; } + case GST_MESSAGE_ASYNC_DONE: + case GST_MESSAGE_DURATION_CHANGED: { + qint64 d = playerPipeline.duration()/1e6; + qCDebug(qLcMediaPlayer) << " duration changed message" << d; + if (d != m_duration) { + m_duration = d; + emit durationChanged(duration()); + } + return; + } + case GST_MESSAGE_EOS: + stop(); + // anything to do here? + break; + case GST_MESSAGE_BUFFERING: { + int progress = 0; + gst_message_parse_buffering(gm, &progress); + m_bufferProgress = progress; + emit bufferStatusChanged(progress); + break; + } + case GST_MESSAGE_STATE_CHANGED: { + GstState oldState; + GstState newState; + GstState pending; - popAndNotifyState(); - - emit positionChanged(position()); -} + gst_message_parse_state_changed(gm, &oldState, &newState, &pending); -void QGstreamerMediaPlayer::stop() -{ #ifdef DEBUG_PLAYBIN - qDebug() << Q_FUNC_INFO; + static QStringList states = { + QStringLiteral("GST_STATE_VOID_PENDING"), QStringLiteral("GST_STATE_NULL"), + QStringLiteral("GST_STATE_READY"), QStringLiteral("GST_STATE_PAUSED"), + QStringLiteral("GST_STATE_PLAYING") }; + + qDebug() << QStringLiteral("state changed: old: %1 new: %2 pending: %3") \ + .arg(states[oldState]) \ + .arg(states[newState]) \ + .arg(states[pending]); #endif - m_userRequestedState = QMediaPlayer::StoppedState; - - pushState(); - - if (m_currentState != QMediaPlayer::StoppedState) { - m_currentState = QMediaPlayer::StoppedState; - m_session->showPrerollFrames(false); // stop showing prerolled frames in stop state - // Since gst is not going to send GST_STATE_PAUSED - // when pipeline is already paused, - // needs to update media status directly. - if (m_session->state() == QMediaPlayer::PausedState) - updateMediaStatus(); - else - m_session->pause(); - if (m_mediaStatus != QMediaPlayer::EndOfMedia) { - m_pendingSeekPosition = 0; - emit positionChanged(position()); + switch (newState) { + case GST_STATE_VOID_PENDING: + case GST_STATE_NULL: + 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) { +// getStreamsInfo(); +// updateVideoResolutionTag(); + + if (!qFuzzyCompare(m_playbackRate, qreal(1.0))) + playerPipeline.seek(playerPipeline.position(), m_playbackRate); + } + + if (m_state != prevState) + emit stateChanged(m_state); + + break; + } + case GST_STATE_PLAYING: + if (m_state != QMediaPlayer::PlayingState) + emit stateChanged(m_state = QMediaPlayer::PlayingState); + break; + } + 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) + emit error(QMediaPlayer::FormatError, tr("Cannot play stream of type: ")); + else + emit error(QMediaPlayer::ResourceError, 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); + qCWarning(qLcMediaPlayer) << "Warning:" << QString::fromUtf8(err->message); + g_error_free (err); + g_free (debug); + break; + } + case GST_MESSAGE_INFO: { + if (qLcMediaPlayer().isDebugEnabled()) { + GError *err; + gchar *debug; + gst_message_parse_info (gm, &err, &debug); + qCDebug(qLcMediaPlayer) << "Info:" << QString::fromUtf8(err->message); + g_error_free (err); + g_free (debug); } + 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; + emit positionChanged(position); + } + default: + break; } - popAndNotifyState(); +#if 0 + } 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: ")); + // 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); + } + } +#endif } -void QGstreamerMediaPlayer::setVolume(int volume) +QUrl QGstreamerMediaPlayer::media() const { - m_session->setVolume(volume); + return m_url; } -void QGstreamerMediaPlayer::setMuted(bool muted) +const QIODevice *QGstreamerMediaPlayer::mediaStream() const { - m_session->setMuted(muted); + return m_stream; } -QUrl QGstreamerMediaPlayer::media() const +void QGstreamerMediaPlayer::decoderPadAdded(const QGstElement &src, const QGstPad &pad) { - return m_currentResource; + if (src != decoder) + return; + + auto caps = pad.currentCaps(); + auto type = caps.at(0).name(); + qCDebug(qLcMediaPlayer) << "Received new pad" << pad.name() << "from" << src.name() << "type" << type; + + QGstElement selector; + if (type.startsWith("video/x-raw")) { + selector = videoInputSelector; + } else if (type.startsWith("audio/x-raw")) { + selector = audioInputSelector; + } else if (type.startsWith("text/")) { + selector = subTitleInputSelector; + } else { + qCWarning(qLcMediaPlayer) << "Failed to add fake sink to unknown pad." << pad.name() << type; + return; + } + QGstPad sinkPad = selector.getRequestPad("sink_%u"); + if (!pad.link(sinkPad)) + qCWarning(qLcMediaPlayer) << "Failed to link video pads."; + decoderOutputMap.insert(pad.name(), sinkPad); } -const QIODevice *QGstreamerMediaPlayer::mediaStream() const +void QGstreamerMediaPlayer::decoderPadRemoved(const QGstElement &src, const QGstPad &pad) { - return m_stream; + qCDebug(qLcMediaPlayer) << "Removed pad" << pad.name() << "from" << src.name(); + QGstPad peer = decoderOutputMap.value(pad.name()); + if (peer.isNull()) + return; + QGstElement peerParent = peer.parent(); + qCDebug(qLcMediaPlayer) << " was linked to pad" << peer.name() << "from" << peerParent.name(); + peerParent.releaseRequestPad(peer); } void QGstreamerMediaPlayer::setMedia(const QUrl &content, QIODevice *stream) { -#ifdef DEBUG_PLAYBIN - qDebug() << Q_FUNC_INFO; -#endif - - pushState(); - - m_currentState = QMediaPlayer::StoppedState; - QUrl oldMedia = m_currentResource; - m_pendingSeekPosition = -1; - m_session->showPrerollFrames(false); // do not show prerolled frames until pause() or play() explicitly called - m_setMediaPending = false; - - m_session->stop(); - - bool userStreamValid = false; + qCDebug(qLcMediaPlayer) << Q_FUNC_INFO << "setting location to" << content; - if (m_bufferProgress != -1) { - m_bufferProgress = -1; - emit bufferStatusChanged(0); - } + int ret = playerPipeline.setStateSync(GST_STATE_NULL); + if (ret == GST_STATE_CHANGE_FAILURE) + qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the stopped state."; - m_currentResource = content; + m_url = content; m_stream = stream; - QNetworkRequest request(content); - - if (m_stream) - userStreamValid = stream->isOpen() && m_stream->isReadable(); + if (!src.isNull()) + playerPipeline.remove(src); + if (!decoder.isNull()) + playerPipeline.remove(decoder); + src = QGstElement(); + decoder = QGstElement(); -#if !QT_CONFIG(gstreamer_app) - m_session->loadFromUri(request); -#else if (m_stream) { - if (userStreamValid){ - m_session->loadFromStream(request, m_stream); - } else { - m_mediaStatus = QMediaPlayer::InvalidMedia; - emit error(QMediaPlayer::FormatError, tr("Attempting to play invalid user stream")); - popAndNotifyState(); - return; - } - } else - m_session->loadFromUri(request); -#endif - -#if QT_CONFIG(gstreamer_app) - if (!request.url().isEmpty() || userStreamValid) { -#else - if (!request.url().isEmpty()) { -#endif - m_mediaStatus = QMediaPlayer::LoadingMedia; - m_session->pause(); + if (!m_appSrc) + m_appSrc = new QGstAppSrc(this); + src = QGstElement("appsrc", "appsrc"); + decoder = QGstElement("decodebin", "decoder"); + playerPipeline.add(src, decoder); + src.link(decoder); + + m_appSrc->setStream(m_stream); + m_appSrc->setup(src.element()); } else { - m_mediaStatus = QMediaPlayer::NoMedia; - setBufferProgress(0); + // use uridecodebin + decoder = QGstElement("uridecodebin", "uridecoder"); + playerPipeline.add(decoder); + + decoder.set("uri", content.toEncoded().constData()); + if (m_bufferProgress != -1) { + m_bufferProgress = -1; + emit bufferStatusChanged(0); + } + } + decoder.onPadAdded<&QGstreamerMediaPlayer::decoderPadAdded>(this); + decoder.onPadRemoved<&QGstreamerMediaPlayer::decoderPadRemoved>(this); + + if (m_state == QMediaPlayer::PausedState) { + int ret = playerPipeline.setState(GST_STATE_PAUSED); + if (ret == GST_STATE_CHANGE_FAILURE) + qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the paused state."; + } else if (m_state == QMediaPlayer::PlayingState) { + int ret = playerPipeline.setState(GST_STATE_PLAYING); + if (ret == GST_STATE_CHANGE_FAILURE) + qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the playing state."; } emit positionChanged(position()); - - popAndNotifyState(); } bool QGstreamerMediaPlayer::setAudioOutput(const QAudioDeviceInfo &info) { - m_session->setAudioOutputDevice(info); + if (info == m_audioOutput) + return true; + qCDebug(qLcMediaPlayer) << "setAudioOutput" << info.description() << info.isNull(); + m_audioOutput = info; + + if (m_state == QMediaPlayer::StoppedState) + return changeAudioOutput(); + + auto pad = audioVolume.staticPad("src"); + pad.addProbe<&QGstreamerMediaPlayer::prepareAudioOutputChange>(this, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM); + return true; } -QAudioDeviceInfo QGstreamerMediaPlayer::audioOutput() const +bool QGstreamerMediaPlayer::changeAudioOutput() { - return m_session->audioOutputDevice(); -} + qCDebug(qLcMediaPlayer) << "Changing audio output"; + QGstElement newSink; + auto *deviceInfo = static_cast(m_audioOutput.handle()); + if (deviceInfo && deviceInfo->gstDevice) + newSink = QGstElement(gst_device_create_element(deviceInfo->gstDevice , "audiosink"), QGstElement::NeedsRef); -QMediaMetaData QGstreamerMediaPlayer::metaData() const -{ - return m_session->metaData(); -} + if (newSink.isNull()) + newSink = QGstElement("autoaudiosink", "audiosink"); -void QGstreamerMediaPlayer::setVideoSurface(QAbstractVideoSurface *surface) -{ - m_session->setVideoRenderer(surface); + playerPipeline.remove(audioSink); + audioSink = newSink; + playerPipeline.add(audioSink); + audioVolume.link(audioSink); + + return true; } -QMediaStreamsControl *QGstreamerMediaPlayer::mediaStreams() +void QGstreamerMediaPlayer::prepareAudioOutputChange(const QGstPad &/*pad*/) { - if (!m_streamsControl) - m_streamsControl = new QGstreamerStreamsControl(m_session, this); - return m_streamsControl; + qCDebug(qLcMediaPlayer) << "Reconfiguring audio output"; + + Q_ASSERT(m_state != QMediaPlayer::StoppedState); + + auto state = playerPipeline.state(); + if (state == GST_STATE_PLAYING) + playerPipeline.setStateSync(GST_STATE_PAUSED); + audioSink.setStateSync(GST_STATE_NULL); + changeAudioOutput(); + audioSink.setStateSync(GST_STATE_PAUSED); + if (state == GST_STATE_PLAYING) + playerPipeline.setStateSync(state); + + GST_DEBUG_BIN_TO_DOT_FILE(playerPipeline.bin(), GST_DEBUG_GRAPH_SHOW_ALL, "newAudio.dot"); } -bool QGstreamerMediaPlayer::isAudioAvailable() const +QAudioDeviceInfo QGstreamerMediaPlayer::audioOutput() const { - return m_session->isAudioAvailable(); + return m_audioOutput; } -bool QGstreamerMediaPlayer::isVideoAvailable() const +QMediaMetaData QGstreamerMediaPlayer::metaData() const { - return m_session->isVideoAvailable(); +// return m_session->metaData(); + return QMediaMetaData(); } -void QGstreamerMediaPlayer::updateSessionState(QMediaPlayer::State state) +void QGstreamerMediaPlayer::updateVideoSink() { - pushState(); + qCDebug(qLcMediaPlayer) << "Video sink has changed, reload video output"; - if (state == QMediaPlayer::StoppedState) { - m_session->showPrerollFrames(false); - m_currentState = QMediaPlayer::StoppedState; - } + QGstElement newSink; + if (m_videoOutput && m_videoOutput->isReady()) + newSink = QGstElement(m_videoOutput->videoSink(), QGstObject::NeedsRef); - 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 (newSink.isNull()) + newSink = QGstElement("fakesink", "fakevideosink"); - if (m_currentState == QMediaPlayer::PlayingState) { - if (m_bufferProgress == -1 || m_bufferProgress == 100) - m_session->play(); - } - } - - updateMediaStatus(); - - popAndNotifyState(); -} - -void QGstreamerMediaPlayer::updateMediaStatus() -{ - //EndOfMedia status should be kept, until reset by pause, play or setMedia - if (m_mediaStatus == QMediaPlayer::EndOfMedia) + if (newSink == videoSink) return; - pushState(); - QMediaPlayer::MediaStatus oldStatus = m_mediaStatus; + qCDebug(qLcMediaPlayer) << "Reconfiguring video output"; - switch (m_session->state()) { - case QMediaPlayer::StoppedState: - if (m_currentResource.isEmpty()) - m_mediaStatus = QMediaPlayer::NoMedia; - else if (oldStatus != QMediaPlayer::InvalidMedia) - m_mediaStatus = QMediaPlayer::LoadingMedia; - break; + if (m_state == QMediaPlayer::StoppedState) { + qCDebug(qLcMediaPlayer) << "The pipeline has not started yet"; - 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; + //the pipeline has not started yet + playerPipeline.setState(GST_STATE_NULL); + if (!videoSink.isNull()) { + videoSink.setState(GST_STATE_NULL); + playerPipeline.remove(videoSink); } - break; - } - - popAndNotifyState(); -} - -void QGstreamerMediaPlayer::processEOS() -{ - pushState(); - m_mediaStatus = QMediaPlayer::EndOfMedia; - emit positionChanged(position()); - m_session->endOfMediaReset(); + videoSink = newSink; + playerPipeline.add(videoSink); + if (!videoScale.link(videoSink)) + qCWarning(qLcMediaPlayer) << "Linking new video output failed"; + + if (g_object_class_find_property(G_OBJECT_GET_CLASS(videoSink.object()), "show-preroll-frame") != nullptr) + videoSink.set("show-preroll-frame", true); + +// 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; +// } - if (m_currentState != QMediaPlayer::StoppedState) { - m_currentState = QMediaPlayer::StoppedState; - m_session->showPrerollFrames(false); // stop showing prerolled frames in stop state + } else { +// if (m_pendingVideoSink) { +//#ifdef DEBUG_PLAYBIN +// qDebug() << "already waiting for pad to be blocked, just change the pending sink"; +//#endif +// m_pendingVideoSink = videoSink; +// return; +// } + +// m_pendingVideoSink = videoSink; + +//#ifdef DEBUG_PLAYBIN +// qDebug() << "Blocking the video output pad..."; +//#endif + +// //block pads, async to avoid locking in paused state +// GstPad *srcPad = gst_element_get_static_pad(m_videoIdentity, "src"); +// this->pad_probe_id = gst_pad_add_probe(srcPad, (GstPadProbeType)(GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BLOCKING), block_pad_cb, this, nullptr); +// gst_object_unref(GST_OBJECT(srcPad)); + +// //Unpause the sink to avoid waiting until the buffer is processed +// //while the sink is paused. The pad will be blocked as soon as the current +// //buffer is processed. +// if (m_state == QMediaPlayer::PausedState) { +//#ifdef DEBUG_PLAYBIN +// qDebug() << "Starting video output to avoid blocking in paused state..."; +//#endif +// gst_element_set_state(m_videoSink, GST_STATE_PLAYING); +// } } - - popAndNotifyState(); } -void QGstreamerMediaPlayer::setBufferProgress(int progress) +void QGstreamerMediaPlayer::setSeekable(bool seekable) { - if (m_bufferProgress == progress || m_mediaStatus == QMediaPlayer::NoMedia) + qCDebug(qLcMediaPlayer) << Q_FUNC_INFO << seekable; + if (seekable == m_seekable) return; + m_seekable = seekable; + emit seekableChanged(m_seekable); +} +void QGstreamerMediaPlayer::setVideoSurface(QAbstractVideoSurface *surface) +{ + if (!m_videoOutput) { + m_videoOutput = new QGstreamerVideoRenderer; #ifdef DEBUG_PLAYBIN - qDebug() << Q_FUNC_INFO << progress; + qDebug() << Q_FUNC_INFO; #endif - m_bufferProgress = progress; - - if (m_currentState == QMediaPlayer::PlayingState && - m_bufferProgress == 100 && - m_session->state() != QMediaPlayer::PlayingState) - m_session->play(); - - if (!m_session->isLiveSource() && m_bufferProgress < 100 && - (m_session->state() == QMediaPlayer::PlayingState || - m_session->pendingState() == QMediaPlayer::PlayingState)) - m_session->pause(); - - updateMediaStatus(); + connect(m_videoOutput, SIGNAL(sinkChanged()), + this, SLOT(updateVideoRenderer())); + } - emit bufferStatusChanged(m_bufferProgress); + m_videoOutput->setSurface(surface); + updateVideoSink(); } -void QGstreamerMediaPlayer::handleInvalidMedia() +QMediaStreamsControl *QGstreamerMediaPlayer::mediaStreams() { - pushState(); - m_mediaStatus = QMediaPlayer::InvalidMedia; - m_currentState = QMediaPlayer::StoppedState; - m_setMediaPending = true; - popAndNotifyState(); +// if (!m_streamsControl) +// m_streamsControl = new QGstreamerStreamsControl(m_session, this); + return m_streamsControl; } -void QGstreamerMediaPlayer::pushState() +bool QGstreamerMediaPlayer::isAudioAvailable() const { - m_stateStack.push(m_currentState); - m_mediaStatusStack.push(m_mediaStatus); +// return m_session->isAudioAvailable(); + return true; } -void QGstreamerMediaPlayer::popAndNotifyState() +bool QGstreamerMediaPlayer::isVideoAvailable() const { - 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); - } - } +// return m_session->isVideoAvailable(); + return true; } QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer_p.h b/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer_p.h index 48c7f8c37..07746293b 100644 --- a/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer_p.h @@ -55,12 +55,17 @@ #include #include #include -#include +#include QT_BEGIN_NAMESPACE +class QNetworkAccessManager; class QGstreamerPlayerSession; class QGstreamerStreamsControl; +class QGstreamerVideoRenderer; +class QGstreamerBusHelper; +class QGstreamerMessage; +class QGstAppSrc; class Q_MULTIMEDIA_EXPORT QGstreamerMediaPlayer : public QPlatformMediaPlayer { @@ -70,8 +75,6 @@ public: QGstreamerMediaPlayer(QObject *parent = 0); ~QGstreamerMediaPlayer(); - QGstreamerPlayerSession *session() { return m_session; } - QMediaPlayer::State state() const override; QMediaPlayer::MediaStatus mediaStatus() const override; @@ -117,34 +120,63 @@ public Q_SLOTS: void setVolume(int volume) override; void setMuted(bool muted) override; -private Q_SLOTS: - void updateSessionState(QMediaPlayer::State state); - void updateMediaStatus(); - void processEOS(); - void setBufferProgress(int progress); - - void handleInvalidMedia(); + void busMessage(const QGstreamerMessage& message); private: - void playOrPause(QMediaPlayer::State state); - - void pushState(); - void popAndNotifyState(); + friend class QGstreamerStreamsControl; + void decoderPadAdded(const QGstElement &src, const QGstPad &pad); + void decoderPadRemoved(const QGstElement &src, const QGstPad &pad); + void prepareAudioOutputChange(const QGstPad &pad); + bool changeAudioOutput(); + void updateVideoSink(); + void setSeekable(bool seekable); - QGstreamerPlayerSession *m_session = nullptr; QGstreamerStreamsControl *m_streamsControl = nullptr; + QMediaMetaData m_metaData; - QMediaPlayer::State m_userRequestedState = QMediaPlayer::StoppedState; - QMediaPlayer::State m_currentState = QMediaPlayer::StoppedState; + QMediaPlayer::State m_state = QMediaPlayer::StoppedState; QMediaPlayer::MediaStatus m_mediaStatus = QMediaPlayer::NoMedia; - QStack m_stateStack; - QStack m_mediaStatusStack; int m_bufferProgress = -1; - qint64 m_pendingSeekPosition = -1; - bool m_setMediaPending = false; - QUrl m_currentResource; + QUrl m_url; + QNetworkAccessManager *networkManager = nullptr; QIODevice *m_stream = nullptr; + bool ownStream = false; + + int m_volume = 100.; + bool m_muted = false; + double m_playbackRate = 1.; + bool m_seekable = false; + qint64 m_duration = 0; + + QAudioDeviceInfo m_audioOutput; + QAbstractVideoSurface *m_videoSurface = nullptr; + QGstreamerVideoRenderer *m_videoOutput = nullptr; + + QGstreamerBusHelper *busHelper; + QGstAppSrc *m_appSrc; + + // Gst elements + QGstPipeline playerPipeline; + QGstElement src; + QGstElement decoder; + QGstElement audioInputSelector; + QGstElement videoInputSelector; + QGstElement subTitleInputSelector; +// QGstElement streamSynchronizer; + + QGstElement audioQueue; + QGstElement audioConvert; + QGstElement audioResample; + QGstElement audioVolume; + QGstElement audioSink; + + QGstElement videoQueue; + QGstElement videoConvert; + QGstElement videoScale; + QGstElement videoSink; + + QHash decoderOutputMap; }; QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/common/qgstreamermessage_p.h b/src/multimedia/platform/gstreamer/common/qgstreamermessage_p.h index e6d9fa08c..b6ba8df26 100644 --- a/src/multimedia/platform/gstreamer/common/qgstreamermessage_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstreamermessage_p.h @@ -52,7 +52,7 @@ // #include -#include +#include QT_BEGIN_NAMESPACE @@ -67,6 +67,10 @@ public: QGstreamerMessage(QGstreamerMessage const& m); ~QGstreamerMessage(); + bool isNull() const { return !m_message; } + GstMessageType type() const { return GST_MESSAGE_TYPE(m_message); } + QGstObject source() const { return QGstObject(GST_MESSAGE_SRC(m_message), QGstObject::NeedsRef); } + GstMessage* rawMessage() const; QGstreamerMessage& operator=(QGstreamerMessage const& rhs); -- cgit v1.2.3