From c3ce6cff729c04258ed0a68fd62fdadbbd2425e7 Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Sat, 16 Oct 2021 21:28:56 +0200 Subject: [PATCH] GstEngine: Move CreateElement() to GstEnginePipeline --- src/engine/gstengine.cpp | 31 ++-- src/engine/gstengine.h | 2 - src/engine/gstenginepipeline.cpp | 253 ++++++++++++++++++++++--------- src/engine/gstenginepipeline.h | 16 +- 4 files changed, 195 insertions(+), 107 deletions(-) diff --git a/src/engine/gstengine.cpp b/src/engine/gstengine.cpp index a3dc17ca4..f0777c8a4 100644 --- a/src/engine/gstengine.cpp +++ b/src/engine/gstengine.cpp @@ -91,7 +91,6 @@ GstEngine::GstEngine(TaskManager *task_manager, QObject *parent) waiting_to_seek_(false), seek_pos_(0), timer_id_(-1), - next_element_id_(0), is_fading_out_to_pause_(false), has_faded_out_(false), scope_chunk_(0), @@ -448,25 +447,6 @@ void GstEngine::ReloadSettings() { } -GstElement *GstEngine::CreateElement(const QString &factoryName, GstElement *bin, const bool showerror) { - - // Make a unique name - QString name = factoryName + "-" + QString::number(next_element_id_++); - - GstElement *element = gst_element_factory_make(factoryName.toUtf8().constData(), name.toUtf8().constData()); - if (!element) { - if (showerror) emit Error(QString("GStreamer could not create the element: %1.").arg(factoryName)); - else qLog(Error) << "GStreamer could not create the element:" << factoryName; - emit StateChanged(Engine::Error); - emit FatalError(); - return nullptr; - } - - if (bin) gst_bin_add(GST_BIN(bin), element); - - return element; -} - void GstEngine::ConsumeBuffer(GstBuffer *buffer, const int pipeline_id, const QString &format) { // Schedule this to run in the GUI thread. The buffer gets added to the queue and unreffed by UpdateScope. @@ -810,7 +790,7 @@ std::shared_ptr GstEngine::CreatePipeline() { EnsureInitialized(); - std::shared_ptr ret = std::make_shared(this); + std::shared_ptr ret = std::make_shared(); ret->set_output_device(output_, device_); ret->set_volume_enabled(volume_control_); ret->set_stereo_balancer_enabled(stereo_balancer_enabled_); @@ -841,7 +821,14 @@ std::shared_ptr GstEngine::CreatePipeline() { std::shared_ptr GstEngine::CreatePipeline(const QByteArray &gst_url, const QUrl &original_url, const qint64 end_nanosec) { std::shared_ptr ret = CreatePipeline(); - if (!ret->InitFromUrl(gst_url, original_url, end_nanosec)) ret.reset(); + QString error; + if (!ret->InitFromUrl(gst_url, original_url, end_nanosec, error)) { + ret.reset(); + emit Error(error); + emit StateChanged(Engine::Error); + emit FatalError(); + } + return ret; } diff --git a/src/engine/gstengine.h b/src/engine/gstengine.h index ba30946fe..c82c6a2f4 100644 --- a/src/engine/gstengine.h +++ b/src/engine/gstengine.h @@ -88,7 +88,6 @@ class GstEngine : public Engine::Base, public GstBufferConsumer { void SetStartup(GstStartup *gst_startup) { gst_startup_ = gst_startup; } void EnsureInitialized() { gst_startup_->EnsureInitialized(); } - GstElement *CreateElement(const QString &factoryName, GstElement *bin = nullptr, const bool showerror = true); void ConsumeBuffer(GstBuffer *buffer, const int pipeline_id, const QString &format) override; public slots: @@ -191,7 +190,6 @@ class GstEngine : public Engine::Base, public GstBufferConsumer { quint64 seek_pos_; int timer_id_; - int next_element_id_; bool is_fading_out_to_pause_; bool has_faded_out_; diff --git a/src/engine/gstenginepipeline.cpp b/src/engine/gstenginepipeline.cpp index d2ab1cf7a..1b6235ca6 100644 --- a/src/engine/gstenginepipeline.cpp +++ b/src/engine/gstenginepipeline.cpp @@ -65,9 +65,8 @@ const int GstEnginePipeline::kEqBandFrequencies[] = { 60, 170, 310, 600, 1000, 3 int GstEnginePipeline::sId = 1; -GstEnginePipeline::GstEnginePipeline(GstEngine *engine, QObject *parent) +GstEnginePipeline::GstEnginePipeline(QObject *parent) : QObject(parent), - engine_(engine), id_(sId++), valid_(false), volume_enabled_(true), @@ -210,16 +209,39 @@ void GstEnginePipeline::set_channels(const bool enabled, const int channels) { channels_ = channels; } -bool GstEnginePipeline::InitFromUrl(const QByteArray &stream_url, const QUrl &original_url, const qint64 end_nanosec) { +GstElement *GstEnginePipeline::CreateElement(const QString &factory_name, const QString &name, GstElement *bin, QString &error) { + + QString unique_name = QString("pipeline") + "-" + QString::number(id_) + "-" + (name.isEmpty() ? factory_name : name); + + GstElement *element = gst_element_factory_make(factory_name.toUtf8().constData(), unique_name.toUtf8().constData()); + if (!element) { + qLog(Error) << "GStreamer could not create the element" << factory_name << "with name" << unique_name; + error = QString("GStreamer could not create the element %1 with name %2.").arg(factory_name).arg(unique_name); + } + + if (bin && element) gst_bin_add(GST_BIN(bin), element); + + return element; + +} + +bool GstEnginePipeline::InitFromUrl(const QByteArray &stream_url, const QUrl &original_url, const qint64 end_nanosec, QString &error) { stream_url_ = stream_url; original_url_ = original_url; end_offset_nanosec_ = end_nanosec; - pipeline_ = engine_->CreateElement("playbin"); + pipeline_ = CreateElement("playbin", "pipeline", nullptr, error); if (!pipeline_) return false; - g_object_set(G_OBJECT(pipeline_), "uri", stream_url.constData(), nullptr); + pad_added_cb_id_ = CHECKED_GCONNECT(G_OBJECT(pipeline_), "pad-added", &NewPadCallback, this); + notify_source_cb_id_ = CHECKED_GCONNECT(G_OBJECT(pipeline_), "notify::source", &SourceSetupCallback, this); + about_to_finish_cb_id_ = CHECKED_GCONNECT(G_OBJECT(pipeline_), "about-to-finish", &AboutToFinishCallback, this); + + if (!InitAudioBin(error)) return false; + + // Set playbin's sink to be our custom audio-sink. + g_object_set(GST_OBJECT(pipeline_), "audio-sink", audiobin_, nullptr); gint flags = 0; g_object_get(G_OBJECT(pipeline_), "flags", &flags, nullptr); @@ -231,23 +253,17 @@ bool GstEnginePipeline::InitFromUrl(const QByteArray &stream_url, const QUrl &or else { flags &= ~0x00000010; } + g_object_set(G_OBJECT(pipeline_), "flags", flags, nullptr); + g_object_set(G_OBJECT(pipeline_), "uri", stream_url.constData(), nullptr); - pad_added_cb_id_ = CHECKED_GCONNECT(G_OBJECT(pipeline_), "pad-added", &NewPadCallback, this); - notify_source_cb_id_ = CHECKED_GCONNECT(G_OBJECT(pipeline_), "notify::source", &SourceSetupCallback, this); - about_to_finish_cb_id_ = CHECKED_GCONNECT(G_OBJECT(pipeline_), "about-to-finish", &AboutToFinishCallback, this); - - if (!InitAudioBin()) return false; - - // Set playbin's sink to be our custom audio-sink. - g_object_set(GST_OBJECT(pipeline_), "audio-sink", audiobin_, nullptr); pipeline_is_connected_ = true; return true; } -bool GstEnginePipeline::InitAudioBin() { +bool GstEnginePipeline::InitAudioBin(QString &error) { gst_segment_init(&last_playbin_segment_, GST_FORMAT_TIME); @@ -256,7 +272,7 @@ bool GstEnginePipeline::InitAudioBin() { if (!audiobin_) return false; // Create the sink - GstElement *audiosink = engine_->CreateElement(output_, audiobin_); + GstElement *audiosink = CreateElement(output_, output_, audiobin_, error); if (!audiosink) { gst_object_unref(GST_OBJECT(audiobin_)); return false; @@ -309,10 +325,15 @@ bool GstEnginePipeline::InitAudioBin() { // Create all the other elements - audioqueue_ = engine_->CreateElement("queue2", audiobin_); - GstElement *audioconverter = engine_->CreateElement("audioconvert", audiobin_); + audioqueue_ = CreateElement("queue2", "audioqueue", audiobin_, error); + if (!audioqueue_) { + gst_object_unref(GST_OBJECT(audiobin_)); + audiobin_ = nullptr; + return false; + } - if (!audioqueue_ || !audioconverter) { + GstElement *audioconverter = CreateElement("audioconvert", "audioconverter", audiobin_, error); + if (!audioconverter) { gst_object_unref(GST_OBJECT(audiobin_)); audiobin_ = nullptr; return false; @@ -320,20 +341,40 @@ bool GstEnginePipeline::InitAudioBin() { // Create the volume elements if it's enabled. if (volume_enabled_) { - volume_ = engine_->CreateElement("volume", audiobin_); + volume_ = CreateElement("volume", "volume", audiobin_, error); + if (!volume_) { + gst_object_unref(GST_OBJECT(audiobin_)); + audiobin_ = nullptr; + return false; + } } // Create the stereo balancer elements if it's enabled. if (stereo_balancer_enabled_) { - audiopanorama_ = engine_->CreateElement("audiopanorama", audiobin_, false); + audiopanorama_ = CreateElement("audiopanorama", "audiopanorama", audiobin_, error); + if (!audiopanorama_) { + gst_object_unref(GST_OBJECT(audiobin_)); + audiobin_ = nullptr; + return false; + } // Set the stereo balance. - if (audiopanorama_) g_object_set(G_OBJECT(audiopanorama_), "panorama", stereo_balance_, nullptr); + g_object_set(G_OBJECT(audiopanorama_), "panorama", stereo_balance_, nullptr); } // Create the equalizer elements if it's enabled. if (eq_enabled_) { - equalizer_preamp_ = engine_->CreateElement("volume", audiobin_, false); - equalizer_ = engine_->CreateElement("equalizer-nbands", audiobin_, false); + equalizer_preamp_ = CreateElement("volume", "equalizer_preamp", audiobin_, error); + if (!equalizer_preamp_) { + gst_object_unref(GST_OBJECT(audiobin_)); + audiobin_ = nullptr; + return false; + } + equalizer_ = CreateElement("equalizer-nbands", "equalizer_nbands", audiobin_, error); + if (!equalizer_) { + gst_object_unref(GST_OBJECT(audiobin_)); + audiobin_ = nullptr; + return false; + } // Setting the equalizer bands: // // GStreamer's GstIirEqualizerNBands sets up shelve filters for the first and last bands as corner cases. @@ -341,32 +382,36 @@ bool GstEnginePipeline::InitAudioBin() { // As a workaround, we create two dummy bands at both ends of the spectrum. // This causes the actual first and last adjustable bands to be implemented using band-pass filters. - if (equalizer_) { - g_object_set(G_OBJECT(equalizer_), "num-bands", 10 + 2, nullptr); + g_object_set(G_OBJECT(equalizer_), "num-bands", 10 + 2, nullptr); - // Dummy first band (bandwidth 0, cutting below 20Hz): - GstObject *first_band = GST_OBJECT(gst_child_proxy_get_child_by_index(GST_CHILD_PROXY(equalizer_), 0)); + // Dummy first band (bandwidth 0, cutting below 20Hz): + GstObject *first_band = GST_OBJECT(gst_child_proxy_get_child_by_index(GST_CHILD_PROXY(equalizer_), 0)); + if (first_band) { g_object_set(G_OBJECT(first_band), "freq", 20.0, "bandwidth", 0, "gain", 0.0F, nullptr); g_object_unref(G_OBJECT(first_band)); + } - // Dummy last band (bandwidth 0, cutting over 20KHz): - GstObject *last_band = GST_OBJECT(gst_child_proxy_get_child_by_index(GST_CHILD_PROXY(equalizer_), kEqBandCount + 1)); + // Dummy last band (bandwidth 0, cutting over 20KHz): + GstObject *last_band = GST_OBJECT(gst_child_proxy_get_child_by_index(GST_CHILD_PROXY(equalizer_), kEqBandCount + 1)); + if (last_band) { g_object_set(G_OBJECT(last_band), "freq", 20000.0, "bandwidth", 0, "gain", 0.0F, nullptr); g_object_unref(G_OBJECT(last_band)); + } - int last_band_frequency = 0; - for (int i = 0; i < kEqBandCount; ++i) { - const int index_in_eq = i + 1; - GstObject *band = GST_OBJECT(gst_child_proxy_get_child_by_index(GST_CHILD_PROXY(equalizer_), index_in_eq)); - + int last_band_frequency = 0; + for (int i = 0; i < kEqBandCount; ++i) { + const int index_in_eq = i + 1; + GstObject *band = GST_OBJECT(gst_child_proxy_get_child_by_index(GST_CHILD_PROXY(equalizer_), index_in_eq)); + if (band) { const float frequency = static_cast(kEqBandFrequencies[i]); const float bandwidth = frequency - static_cast(last_band_frequency); last_band_frequency = static_cast(frequency); - g_object_set(G_OBJECT(band), "freq", frequency, "bandwidth", bandwidth, "gain", 0.0F, nullptr); g_object_unref(G_OBJECT(band)); } - } + + } // for + } // Create the replaygain elements if it's enabled. @@ -375,29 +420,49 @@ bool GstEnginePipeline::InitAudioBin() { GstElement *rglimiter = nullptr; GstElement *rgconverter = nullptr; if (rg_enabled_) { - rgvolume = engine_->CreateElement("rgvolume", audiobin_, false); - rglimiter = engine_->CreateElement("rglimiter", audiobin_, false); - rgconverter = engine_->CreateElement("audioconvert", audiobin_, false); - if (rgvolume && rglimiter && rgconverter) { - eventprobe = rgconverter; - // Set replaygain settings - g_object_set(G_OBJECT(rgvolume), "album-mode", rg_mode_, nullptr); - g_object_set(G_OBJECT(rgvolume), "pre-amp", rg_preamp_, nullptr); - g_object_set(G_OBJECT(rgvolume), "fallback-gain", rg_fallbackgain_, nullptr); - g_object_set(G_OBJECT(rglimiter), "enabled", static_cast(rg_compression_), nullptr); + rgvolume = CreateElement("rgvolume", "rgvolume", audiobin_, error); + if (!rgvolume) { + gst_object_unref(GST_OBJECT(audiobin_)); + audiobin_ = nullptr; + return false; + } + rglimiter = CreateElement("rglimiter", "rglimiter", audiobin_, error); + if (!rglimiter) { + gst_object_unref(GST_OBJECT(audiobin_)); + audiobin_ = nullptr; + return false; + } + rgconverter = CreateElement("audioconvert", "rgconverter", audiobin_, error); + if (!rgconverter) { + gst_object_unref(GST_OBJECT(audiobin_)); + audiobin_ = nullptr; + return false; + } + eventprobe = rgconverter; + // Set replaygain settings + g_object_set(G_OBJECT(rgvolume), "album-mode", rg_mode_, nullptr); + g_object_set(G_OBJECT(rgvolume), "pre-amp", rg_preamp_, nullptr); + g_object_set(G_OBJECT(rgvolume), "fallback-gain", rg_fallbackgain_, nullptr); + g_object_set(G_OBJECT(rglimiter), "enabled", static_cast(rg_compression_), nullptr); + } + + { // Create a pad on the outside of the audiobin and connect it to the pad of the first element. + GstPad *pad = gst_element_get_static_pad(audioqueue_, "sink"); + if (pad) { + gst_element_add_pad(audiobin_, gst_ghost_pad_new("sink", pad)); + gst_object_unref(pad); } } - // Create a pad on the outside of the audiobin and connect it to the pad of the first element. - GstPad *pad = gst_element_get_static_pad(audioqueue_, "sink"); - gst_element_add_pad(audiobin_, gst_ghost_pad_new("sink", pad)); - gst_object_unref(pad); - // Add a data probe on the src pad of the audioconvert element for our scope. // We do it here because we want pre-equalized and pre-volume samples so that our visualization are not be affected by them. - pad = gst_element_get_static_pad(eventprobe, "src"); - gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, &EventHandoffCallback, this, nullptr); - gst_object_unref(pad); + { + GstPad *pad = gst_element_get_static_pad(eventprobe, "src"); + if (pad) { + gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, &EventHandoffCallback, this, nullptr); + gst_object_unref(pad); + } + } // Set the buffer duration. // We set this on this queue instead of the playbin because setting it on the playbin only affects network sources. @@ -419,47 +484,87 @@ bool GstEnginePipeline::InitAudioBin() { // Link replaygain elements if enabled. if (rg_enabled_ && rgvolume && rglimiter && rgconverter) { - gst_element_link_many(next, rgvolume, rglimiter, rgconverter, nullptr); + if (!gst_element_link_many(next, rgvolume, rglimiter, rgconverter, nullptr)) { + gst_object_unref(GST_OBJECT(audiobin_)); + audiobin_ = nullptr; + error = "gst_element_link_many() failed."; + return false; + } next = rgconverter; } // Link equalizer elements if enabled. if (eq_enabled_ && equalizer_ && equalizer_preamp_) { - gst_element_link_many(next, equalizer_preamp_, equalizer_, nullptr); + if (!gst_element_link_many(next, equalizer_preamp_, equalizer_, nullptr)) { + gst_object_unref(GST_OBJECT(audiobin_)); + audiobin_ = nullptr; + error = "gst_element_link_many() failed."; + return false; + } next = equalizer_; } // Link stereo balancer elements if enabled. if (stereo_balancer_enabled_ && audiopanorama_) { - gst_element_link(next, audiopanorama_); + if (!gst_element_link(next, audiopanorama_)) { + gst_object_unref(GST_OBJECT(audiobin_)); + audiobin_ = nullptr; + error = "gst_element_link() failed."; + return false; + } next = audiopanorama_; } // Link volume elements if enabled. if (volume_enabled_ && volume_) { - gst_element_link(next, volume_); + if (!gst_element_link(next, volume_)) { + gst_object_unref(GST_OBJECT(audiobin_)); + audiobin_ = nullptr; + error = "gst_element_link() failed."; + return false; + } next = volume_; } - gst_element_link(next, audioconverter); - - GstCaps *caps = gst_caps_new_empty_simple("audio/x-raw"); - if (channels_enabled_ && channels_ > 0) { - qLog(Debug) << "Setting channels to" << channels_; - gst_caps_set_simple(caps, "channels", G_TYPE_INT, channels_, nullptr); + if (!gst_element_link(next, audioconverter)) { + gst_object_unref(GST_OBJECT(audiobin_)); + audiobin_ = nullptr; + error = "gst_element_link() failed."; + return false; } - gst_element_link_filtered(audioconverter, audiosink, caps); - gst_caps_unref(caps); - // Add probes and handlers. - pad = gst_element_get_static_pad(audioqueue_, "src"); - gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_BUFFER, HandoffCallback, this, nullptr); - gst_object_unref(pad); + { + GstCaps *caps = gst_caps_new_empty_simple("audio/x-raw"); + if (!caps) { + gst_object_unref(GST_OBJECT(audiobin_)); + audiobin_ = nullptr; + error = "gst_caps_new_empty_simple() failed."; + return false; + } + if (channels_enabled_ && channels_ > 0) { + qLog(Debug) << "Setting channels to" << channels_; + gst_caps_set_simple(caps, "channels", G_TYPE_INT, channels_, nullptr); + } + gst_element_link_filtered(audioconverter, audiosink, caps); + gst_caps_unref(caps); + } - GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline_)); - gst_bus_set_sync_handler(bus, BusCallbackSync, this, nullptr); - gst_bus_add_watch(bus, BusCallback, this); - gst_object_unref(bus); + { // Add probes and handlers. + GstPad *pad = gst_element_get_static_pad(audioqueue_, "src"); + if (pad) { + gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_BUFFER, HandoffCallback, this, nullptr); + gst_object_unref(pad); + } + } + + { + GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline_)); + if (bus) { + gst_bus_set_sync_handler(bus, BusCallbackSync, this, nullptr); + gst_bus_add_watch(bus, BusCallback, this); + gst_object_unref(bus); + } + } logged_unsupported_analyzer_format_ = false; diff --git a/src/engine/gstenginepipeline.h b/src/engine/gstenginepipeline.h index 55a28a96b..e6ca6ea27 100644 --- a/src/engine/gstenginepipeline.h +++ b/src/engine/gstenginepipeline.h @@ -45,7 +45,6 @@ #include class QTimerEvent; -class GstEngine; class GstBufferConsumer; namespace Engine { @@ -57,7 +56,7 @@ class GstEnginePipeline : public QObject { Q_OBJECT public: - explicit GstEnginePipeline(GstEngine *engine, QObject *parent = nullptr); + explicit GstEnginePipeline(QObject *parent = nullptr); ~GstEnginePipeline() override; // Globally unique across all pipelines. @@ -76,7 +75,7 @@ class GstEnginePipeline : public QObject { void set_channels(const bool enabled, const int channels); // Creates the pipeline, returns false on error - bool InitFromUrl(const QByteArray &stream_url, const QUrl &original_url, const qint64 end_nanosec); + bool InitFromUrl(const QByteArray &stream_url, const QUrl &original_url, const qint64 end_nanosec, QString &error); // GstBufferConsumers get fed audio data. Thread-safe. void AddBufferConsumer(GstBufferConsumer *consumer); @@ -124,11 +123,11 @@ class GstEnginePipeline : public QObject { void SetVolumeModifier(qreal mod); signals: + void Error(int pipeline_id, QString message, const int domain, const int error_code); + void EndOfStreamReached(int pipeline_id, bool has_next_track); void MetadataFound(int pipeline_id, const Engine::SimpleMetaBundle &bundle); - // This indicates an error, delegated from GStreamer, in the pipeline. - // The message, domain and error_code are related to GStreamer's GError. - void Error(int pipeline_id, QString message, int domain, int error_code); + void FaderFinished(); void BufferingStarted(); @@ -139,7 +138,8 @@ class GstEnginePipeline : public QObject { void timerEvent(QTimerEvent*) override; private: - bool InitAudioBin(); + GstElement *CreateElement(const QString &factory_name, const QString &name, GstElement *bin, QString &error); + bool InitAudioBin(QString &error); // Static callbacks. The GstEnginePipeline instance is passed in the last argument. static GstPadProbeReturn EventHandoffCallback(GstPad*, GstPadProbeInfo*, gpointer); @@ -176,8 +176,6 @@ class GstEnginePipeline : public QObject { static const int kEqBandCount; static const int kEqBandFrequencies[]; - GstEngine *engine_; - // Using == to compare two pipelines is a bad idea, because new ones often get created in the same address as old ones. This ID will be unique for each pipeline. // Threading warning: access to the static ID field isn't protected by a mutex because all pipeline creation is currently done in the main thread. static int sId;