From 72101558b3afc40ef41132e66ce789b92929e911 Mon Sep 17 00:00:00 2001 From: VaL Doroshchuk Date: Thu, 25 Apr 2019 12:24:15 +0200 Subject: Gstreamer: Fix deadlock when state is requested in ASYNC mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When new media is set to the player, updating of "show-preroll-frame" property from the video sink is also requested: g_object_set(G_OBJECT(m_videoSink), "show-preroll-frame", value, NULL); This produces emitting of a signal: g_signal_connect(G_OBJECT(sink), "notify::show-preroll-frame", G_CALLBACK(handleShowPrerollChange), sink); Inside handleShowPrerollChange() the state of the video sink is requested with infinite timeout: gst_element_get_state(GST_ELEMENT(sink), &state, NULL, GST_CLOCK_TIME_NONE); In case if the video sink performed an ASYNC state change, means changing of the state is pending and need to wait, this function will block up for infinite timeout. But probably changing of the state is requested (and should be performed) on the same thread where it is waiting for this change, it produces a deadlock. Changing timeout to 10ms to avoid this block. Fixes: QTBUG-72468 Change-Id: I06235ccfb8f76423f65ed192d5e8de6e60723e72 Reviewed-by: Christian Strømme --- src/gsttools/qgstvideorenderersink.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/gsttools') diff --git a/src/gsttools/qgstvideorenderersink.cpp b/src/gsttools/qgstvideorenderersink.cpp index 09fdd42a6..c3a7a5988 100644 --- a/src/gsttools/qgstvideorenderersink.cpp +++ b/src/gsttools/qgstvideorenderersink.cpp @@ -516,7 +516,8 @@ void QGstVideoRendererSink::handleShowPrerollChange(GObject *o, GParamSpec *p, g if (!showPrerollFrame) { GstState state = GST_STATE_VOID_PENDING; - gst_element_get_state(GST_ELEMENT(sink), &state, NULL, GST_CLOCK_TIME_NONE); + GstClockTime timeout = 10000000; // 10 ms + gst_element_get_state(GST_ELEMENT(sink), &state, NULL, timeout); // show-preroll-frame being set to 'false' while in GST_STATE_PAUSED means // the QMediaPlayer was stopped from the paused state. // We need to flush the current frame. -- cgit v1.2.3 From 7aeebc07b0566b2cf18f3f1f5656eac4d6386aaf Mon Sep 17 00:00:00 2001 From: VaL Doroshchuk Date: Mon, 15 Apr 2019 15:21:08 +0200 Subject: Gstreamer: Allow streams in custom pipelines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduced pushing buffers from QIODevice to appsrc gstreamer element: player->setMedia("gst-pipeline: appsrc ! ...", io_device); Also ported to gst 0.10. Change-Id: I1a84d22c0d5c56fe433d494413c5ab23da7c6bf3 Reviewed-by: Christian Strømme --- src/gsttools/qgstreamerplayersession.cpp | 110 ++++++++++++++++++++----------- 1 file changed, 70 insertions(+), 40 deletions(-) (limited to 'src/gsttools') diff --git a/src/gsttools/qgstreamerplayersession.cpp b/src/gsttools/qgstreamerplayersession.cpp index 1e099fc89..bd28afb91 100644 --- a/src/gsttools/qgstreamerplayersession.cpp +++ b/src/gsttools/qgstreamerplayersession.cpp @@ -306,7 +306,7 @@ void QGstreamerPlayerSession::loadFromStream(const QNetworkRequest &request, QIO m_appSrc = new QGstAppSrc(this); m_appSrc->setStream(appSrcStream); - if (m_playbin) { + if (!parsePipeline() && m_playbin) { m_tags.clear(); emit tagsChanged(); @@ -338,28 +338,7 @@ void QGstreamerPlayerSession::loadFromUri(const QNetworkRequest &request) } #endif - if (m_request.url().scheme() == QLatin1String("gst-pipeline")) { - // Set current surface to video sink before creating a pipeline. - auto renderer = qobject_cast(m_videoOutput); - if (renderer) - QVideoSurfaceGstSink::setSurface(renderer->surface()); - - QString url = m_request.url().toString(QUrl::RemoveScheme); - QString pipeline = QUrl::fromPercentEncoding(url.toLatin1().constData()); - GError *err = nullptr; - GstElement *element = gst_parse_launch(pipeline.toLatin1().constData(), &err); - if (err) { - auto errstr = QLatin1String(err->message); - qWarning() << "Error:" << pipeline << ":" << errstr; - emit error(QMediaPlayer::FormatError, errstr); - g_clear_error(&err); - } - - setPipeline(element); - return; - } - - if (m_playbin) { + if (!parsePipeline() && m_playbin) { m_tags.clear(); emit tagsChanged(); @@ -374,11 +353,55 @@ void QGstreamerPlayerSession::loadFromUri(const QNetworkRequest &request) } } -void QGstreamerPlayerSession::setPipeline(GstElement *pipeline) +bool QGstreamerPlayerSession::parsePipeline() +{ + if (m_request.url().scheme() != QLatin1String("gst-pipeline")) + return false; + + // Set current surface to video sink before creating a pipeline. + auto renderer = qobject_cast(m_videoOutput); + if (renderer) + QVideoSurfaceGstSink::setSurface(renderer->surface()); + + QString url = m_request.url().toString(QUrl::RemoveScheme); + QString desc = QUrl::fromPercentEncoding(url.toLatin1().constData()); + GError *err = nullptr; + GstElement *pipeline = gst_parse_launch(desc.toLatin1().constData(), &err); + if (err) { + auto errstr = QLatin1String(err->message); + qWarning() << "Error:" << desc << ":" << errstr; + emit error(QMediaPlayer::FormatError, errstr); + g_clear_error(&err); + } + + return setPipeline(pipeline); +} + +static void gst_foreach(GstIterator *it, const std::function &cmp) +{ +#if GST_CHECK_VERSION(1,0,0) + GValue value = G_VALUE_INIT; + while (gst_iterator_next (it, &value) == GST_ITERATOR_OK) { + auto child = static_cast(g_value_get_object(&value)); +#else + GstElement *child = nullptr; + while (gst_iterator_next(it, reinterpret_cast(&child)) == GST_ITERATOR_OK) { +#endif + if (cmp(child)) + break; + } + + gst_iterator_free(it); +#if GST_CHECK_VERSION(1,0,0) + g_value_unset(&value); +#endif +} + +bool QGstreamerPlayerSession::setPipeline(GstElement *pipeline) { GstBus *bus = pipeline ? gst_element_get_bus(pipeline) : nullptr; if (!bus) - return; + return false; gst_object_unref(GST_OBJECT(m_pipeline)); m_pipeline = pipeline; @@ -401,24 +424,31 @@ void QGstreamerPlayerSession::setPipeline(GstElement *pipeline) m_videoIdentity = nullptr; if (m_renderer) { - auto it = gst_bin_iterate_sinks(GST_BIN(pipeline)); -#if GST_CHECK_VERSION(1,0,0) - GValue data = { 0, 0 }; - while (gst_iterator_next (it, &data) == GST_ITERATOR_OK) { - auto child = static_cast(g_value_get_object(&data)); -#else - GstElement *child = nullptr; - while (gst_iterator_next(it, reinterpret_cast(&child)) == GST_ITERATOR_OK) { -#endif - if (QLatin1String(GST_OBJECT_NAME(child)) == QLatin1String("qtvideosink")) { - m_renderer->setVideoSink(child); - break; - } - } - gst_iterator_free(it); + gst_foreach(gst_bin_iterate_sinks(GST_BIN(pipeline)), + [this](GstElement *child) { + if (qstrcmp(GST_OBJECT_NAME(child), "qtvideosink") == 0) { + m_renderer->setVideoSink(child); + return true; + } + return false; + }); } +#if QT_CONFIG(gstreamer_app) + if (m_appSrc) { + gst_foreach(gst_bin_iterate_sources(GST_BIN(pipeline)), + [this](GstElement *child) { + if (qstrcmp(qt_gst_element_get_factory_name(child), "appsrc") == 0) { + m_appSrc->setup(child); + return true; + } + return false; + }); + } +#endif + emit pipelineChanged(); + return true; } qint64 QGstreamerPlayerSession::duration() const -- cgit v1.2.3 From 7a4478a4a411df9f21e1499d2301049f9045b7d6 Mon Sep 17 00:00:00 2001 From: VaL Doroshchuk Date: Mon, 20 May 2019 12:49:28 +0200 Subject: GStreamer: Fix crash when the bus helper is destroyed Called deleteLater() on the bus helper to ensure the cleanup is handled gracefully. QGstreamerBusHelperPrivate::doProcessMessage is called on a message from gst, which it calls QGstreamerPlayerSession::processBusMessage where new media is set and current helper object is destroyed. When for loop is ended in doProcessMessage, current object is freed and produces a crash. Change-Id: Ic9cde97c284406450d4723f1f7f71fa1ea2c0648 Reviewed-by: Andy Shaw --- src/gsttools/qgstreamerplayersession.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/gsttools') diff --git a/src/gsttools/qgstreamerplayersession.cpp b/src/gsttools/qgstreamerplayersession.cpp index bd28afb91..5c9eebca2 100644 --- a/src/gsttools/qgstreamerplayersession.cpp +++ b/src/gsttools/qgstreamerplayersession.cpp @@ -407,7 +407,7 @@ bool QGstreamerPlayerSession::setPipeline(GstElement *pipeline) m_pipeline = pipeline; gst_object_unref(GST_OBJECT(m_bus)); m_bus = bus; - delete m_busHelper; + m_busHelper->deleteLater(); m_busHelper = new QGstreamerBusHelper(m_bus, this); m_busHelper->installMessageFilter(this); -- cgit v1.2.3 From 80cc653364fe330dfa4bf310f98d98d4cef0698b Mon Sep 17 00:00:00 2001 From: VaL Doroshchuk Date: Mon, 20 May 2019 10:37:32 +0200 Subject: GStreamer: Dump dot file if GST_DEBUG_DUMP_DOT_DIR is set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dump dot file on play only. Change-Id: Ide7fe0cad56f06a89604cf40b59b858c9c9d09f2 Reviewed-by: Christian Strømme --- src/gsttools/qgstreamerplayersession.cpp | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) (limited to 'src/gsttools') diff --git a/src/gsttools/qgstreamerplayersession.cpp b/src/gsttools/qgstreamerplayersession.cpp index 5c9eebca2..56f78cb9f 100644 --- a/src/gsttools/qgstreamerplayersession.cpp +++ b/src/gsttools/qgstreamerplayersession.cpp @@ -63,7 +63,6 @@ #include //#define DEBUG_PLAYBIN -//#define DEBUG_VO_BIN_DUMP QT_BEGIN_NAMESPACE @@ -659,12 +658,6 @@ void QGstreamerPlayerSession::setVideoRenderer(QObject *videoOutput) 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(); @@ -925,11 +918,6 @@ void QGstreamerPlayerSession::finishVideoOutputChange() 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) @@ -992,6 +980,9 @@ bool QGstreamerPlayerSession::isSeekable() const bool QGstreamerPlayerSession::play() { + static bool dumpDot = qEnvironmentVariableIsSet("GST_DEBUG_DUMP_DOT_DIR"); + if (dumpDot) + gst_debug_bin_to_dot_file_with_ts(GST_BIN(m_pipeline), GstDebugGraphDetails(GST_DEBUG_GRAPH_SHOW_ALL), "session.play"); #ifdef DEBUG_PLAYBIN qDebug() << Q_FUNC_INFO; #endif -- cgit v1.2.3 From 2b34e3355c8943f41c84f39ad9a838f6edb80429 Mon Sep 17 00:00:00 2001 From: VaL Doroshchuk Date: Thu, 2 May 2019 09:42:39 +0200 Subject: Gstreamer: Pass GstUDPSrc's caps via "udpsrc.caps=" url's query item MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sometimes it is needed to configure udpsrc element to play a stream, means to set some caps to GstUDPSrc element. But currently there are no any ways to pass such caps. Added parsing of the requested url to find "udpsrc.caps" query item and to use it as the caps for udpsrc source element. It allows to show streams by passing caps within url. E.g. if the stream is created using $ gst-launch-1.0 v4l2src ! videoconvert ! video/x-raw,format=I420,width=800,height=600 ! \ jpegenc ! rtpjpegpay ! udpsink host=127.0.0.1 port=5001 it could be shown via QMediaPlayer like: MediaPlayer { source: "udp://127.0.0.1:5001/?udpsrc.caps=application/x-rtp,media=video,clock-rate=90000,encoding=JPEG,payload=26" } Change-Id: I6f9c20c6004a34bce5fd1d0073311b7c62a8010f Reviewed-by: Christian Strømme --- src/gsttools/qgstreamerplayersession.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'src/gsttools') diff --git a/src/gsttools/qgstreamerplayersession.cpp b/src/gsttools/qgstreamerplayersession.cpp index 56f78cb9f..5ede8a1c9 100644 --- a/src/gsttools/qgstreamerplayersession.cpp +++ b/src/gsttools/qgstreamerplayersession.cpp @@ -61,6 +61,7 @@ #include #include #include +#include //#define DEBUG_PLAYBIN @@ -1658,6 +1659,14 @@ void QGstreamerPlayerSession::playbinNotifySource(GObject *o, GParamSpec *p, gpo self->m_sourceType = UDPSrc; //The udpsrc is always a live source. self->m_isLiveSource = true; + + QUrlQuery query(self->m_request.url()); + const QString var = QLatin1String("udpsrc.caps"); + if (query.hasQueryItem(var)) { + GstCaps *caps = gst_caps_from_string(query.queryItemValue(var).toLatin1().constData()); + g_object_set(G_OBJECT(source), "caps", caps, NULL); + gst_caps_unref(caps); + } } else if (qstrcmp(G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(source)), "GstSoupHTTPSrc") == 0) { //souphttpsrc timeout unit = second g_object_set(G_OBJECT(source), "timeout", guint(timeout), NULL); -- cgit v1.2.3