diff --git a/src/engine/gstengine.cpp b/src/engine/gstengine.cpp index 85c7c57d6..827c595bd 100644 --- a/src/engine/gstengine.cpp +++ b/src/engine/gstengine.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -53,6 +54,7 @@ #include "core/logging.h" #include "core/taskmanager.h" #include "core/timeconstants.h" +#include "core/signalchecker.h" #include "enginebase.h" #include "enginetype.h" #include "gstengine.h" @@ -71,9 +73,12 @@ const char *GstEngine::kAVDTPSink = "avdtpsink"; const char *GstEngine::InterAudiosink = "interaudiosink"; const char *GstEngine::kDirectSoundSink = "directsoundsink"; const char *GstEngine::kOSXAudioSink = "osxaudiosink"; +const int GstEngine::kDiscoveryTimeoutS = 10; GstEngine::GstEngine(TaskManager *task_manager) : task_manager_(task_manager), + gst_startup_(nullptr), + discoverer_(nullptr), buffering_task_id_(-1), latest_buffer_(nullptr), stereo_balancer_enabled_(false), @@ -86,7 +91,10 @@ GstEngine::GstEngine(TaskManager *task_manager) is_fading_out_to_pause_(false), has_faded_out_(false), scope_chunk_(0), - have_new_buffer_(false) { + have_new_buffer_(false), + discovery_finished_cb_id_(-1), + discovery_discovered_cb_id_(-1) + { type_ = Engine::GStreamer; seek_timer_->setSingleShot(true); @@ -107,6 +115,19 @@ GstEngine::~GstEngine() { latest_buffer_ = nullptr; } + if (discoverer_) { + + if (discovery_discovered_cb_id_ != -1) + g_signal_handler_disconnect(G_OBJECT(discoverer_), discovery_discovered_cb_id_); + if (discovery_finished_cb_id_ != -1) + g_signal_handler_disconnect(G_OBJECT(discoverer_), discovery_finished_cb_id_); + + gst_discoverer_stop(discoverer_); + g_object_unref(discoverer_); + discoverer_ = nullptr; + + } + } bool GstEngine::Init() { @@ -141,8 +162,17 @@ void GstEngine::StartPreloading(const QUrl &stream_url, const QUrl &original_url QByteArray gst_url = FixupUrl(stream_url); // No crossfading, so we can just queue the new URL in the existing pipeline and get gapless playback (hopefully) - if (current_pipeline_) + if (current_pipeline_) { current_pipeline_->SetNextUrl(gst_url, original_url, beginning_nanosec, force_stop_at_end ? end_nanosec : 0); + // Add request to discover the stream +#ifdef Q_OS_LINUX + if (discoverer_) { + if (!gst_discoverer_discover_uri_async(discoverer_, gst_url.toStdString().c_str())) { + qLog(Error) << "Failed to start stream discovery for" << gst_url; + } + } +#endif + } } @@ -180,6 +210,27 @@ bool GstEngine::Load(const QUrl &stream_url, const QUrl &original_url, Engine::T if (crossfade) current_pipeline_->StartFader(fadeout_duration_nanosec_, QTimeLine::Forward); + // Setting up stream discoverer +#ifdef Q_OS_LINUX + if (!discoverer_) { + discoverer_ = gst_discoverer_new(kDiscoveryTimeoutS * GST_SECOND, nullptr); + if (discoverer_) { + discovery_discovered_cb_id_ = CHECKED_GCONNECT(G_OBJECT(discoverer_), "discovered", &StreamDiscovered, this); + discovery_finished_cb_id_ = CHECKED_GCONNECT(G_OBJECT(discoverer_), "finished", &StreamDiscoveryFinished, this); + gst_discoverer_start(discoverer_); + } + } +#endif + + // Add request to discover the stream +#ifdef Q_OS_LINUX + if (discoverer_) { + if (!gst_discoverer_discover_uri_async(discoverer_, gst_url.toStdString().c_str())) { + qLog(Error) << "Failed to start stream discovery for" << gst_url; + } + } +#endif + return true; } @@ -845,3 +896,69 @@ void GstEngine::UpdateScope(const int chunk_length) { } } + +void GstEngine::StreamDiscovered(GstDiscoverer*, GstDiscovererInfo *info, GError*, gpointer self) { + + GstEngine *instance = reinterpret_cast(self); + if (!instance->current_pipeline_) return; + + QString discovered_url(gst_discoverer_info_get_uri(info)); + + GstDiscovererResult result = gst_discoverer_info_get_result(info); + if (result != GST_DISCOVERER_OK) { + QString error_message = GSTdiscovererErrorMessage(result); + qLog(Error) << QString("Stream discovery for %1 failed: %2").arg(discovered_url).arg(error_message); + return; + } + + GList *audio_streams = gst_discoverer_info_get_audio_streams(info); + if (audio_streams) { + + GstDiscovererStreamInfo *stream_info = reinterpret_cast(g_list_first(audio_streams)->data); + + Engine::SimpleMetaBundle bundle; + if (discovered_url == instance->current_pipeline_->stream_url()) { + bundle.url = instance->current_pipeline_->original_url(); + } + else if (discovered_url == instance->current_pipeline_->next_stream_url()) { + bundle.url = instance->current_pipeline_->next_original_url(); + } + bundle.stream_url = QUrl(discovered_url); + bundle.samplerate = gst_discoverer_audio_info_get_sample_rate(GST_DISCOVERER_AUDIO_INFO(stream_info)); + bundle.bitdepth = gst_discoverer_audio_info_get_depth(GST_DISCOVERER_AUDIO_INFO(stream_info)); + bundle.bitrate = gst_discoverer_audio_info_get_bitrate(GST_DISCOVERER_AUDIO_INFO(stream_info)) / 1000; + + GstCaps *caps = gst_discoverer_stream_info_get_caps(stream_info); + gchar *codec_description = gst_pb_utils_get_codec_description(caps); + QString filetype_description = (codec_description ? QString(codec_description) : QString("Unknown")); + g_free(codec_description); + + gst_caps_unref(caps); + gst_discoverer_stream_info_list_free(audio_streams); + + bundle.filetype = Song::FiletypeByDescription(filetype_description); + qLog(Info) << "Got stream info for" << discovered_url + ":" << filetype_description; + + emit instance->MetaData(bundle); + + } + else { + qLog(Error) << "Could not detect an audio stream in" << discovered_url; + } + +} + +void GstEngine::StreamDiscoveryFinished(GstDiscoverer*, gpointer) {} + +QString GstEngine::GSTdiscovererErrorMessage(GstDiscovererResult result) { + + switch (result) { + case GST_DISCOVERER_URI_INVALID: return "The URI is invalid"; + case GST_DISCOVERER_TIMEOUT: return "The discovery timed-out"; + case GST_DISCOVERER_BUSY: return "The discoverer was already discovering a file"; + case GST_DISCOVERER_MISSING_PLUGINS: return "Some plugins are missing for full discovery"; + case GST_DISCOVERER_ERROR: + default: return "An error happened and the GError is set"; + } + +} diff --git a/src/engine/gstengine.h b/src/engine/gstengine.h index 2cd2790fd..9ac8686b3 100644 --- a/src/engine/gstengine.h +++ b/src/engine/gstengine.h @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -126,19 +127,6 @@ class GstEngine : public Engine::Base, public GstBufferConsumer { void BufferingFinished(); private: - static const char *kAutoSink; - static const char *kALSASink; - static const char *kOpenALSASink; - static const char *kOSSSink; - static const char *kOSS4Sink; - static const char *kJackAudioSink; - static const char *kPulseSink; - static const char *kA2DPSink; - static const char *kAVDTPSink; - static const char *InterAudiosink; - static const char *kDirectSoundSink; - static const char *kOSXAudioSink; - PluginDetailsList GetPluginList(const QString &classname) const; QByteArray FixupUrl(const QUrl &url); @@ -153,13 +141,32 @@ class GstEngine : public Engine::Base, public GstBufferConsumer { void UpdateScope(int chunk_length); + static void StreamDiscovered(GstDiscoverer*, GstDiscovererInfo *info, GError*, gpointer self); + static void StreamDiscoveryFinished(GstDiscoverer*, gpointer); + static QString GSTdiscovererErrorMessage(GstDiscovererResult result); + private: + static const char *kAutoSink; + static const char *kALSASink; + static const char *kOpenALSASink; + static const char *kOSSSink; + static const char *kOSS4Sink; + static const char *kJackAudioSink; + static const char *kPulseSink; + static const char *kA2DPSink; + static const char *kAVDTPSink; + static const char *InterAudiosink; + static const char *kDirectSoundSink; + static const char *kOSXAudioSink; + static const int kDiscoveryTimeoutS; static const qint64 kTimerIntervalNanosec = 1000 * kNsecPerMsec; // 1s static const qint64 kPreloadGapNanosec = 5000 * kNsecPerMsec; // 5s static const qint64 kSeekDelayNanosec = 100 * kNsecPerMsec; // 100msec TaskManager *task_manager_; GstStartup *gst_startup_; + GstDiscoverer *discoverer_; + int buffering_task_id_; std::shared_ptr current_pipeline_; @@ -197,6 +204,9 @@ class GstEngine : public Engine::Base, public GstBufferConsumer { int scope_chunks_; QString buffer_format_; + int discovery_finished_cb_id_; + int discovery_discovered_cb_id_; + }; #endif /* GSTENGINE_H */ diff --git a/src/engine/gstenginepipeline.cpp b/src/engine/gstenginepipeline.cpp index 9c44470dc..d49143efc 100644 --- a/src/engine/gstenginepipeline.cpp +++ b/src/engine/gstenginepipeline.cpp @@ -28,7 +28,6 @@ #include #include #include -#include #include #include @@ -59,7 +58,6 @@ const int GstEnginePipeline::kGstStateTimeoutNanosecs = 10000000; const int GstEnginePipeline::kFaderFudgeMsec = 2000; -const int GstEnginePipeline::kDiscoveryTimeoutS = 10; const int GstEnginePipeline::kEqBandCount = 10; const int GstEnginePipeline::kEqBandFrequencies[] = { 60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000 }; @@ -107,13 +105,10 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine) audiopanorama_(nullptr), equalizer_(nullptr), equalizer_preamp_(nullptr), - discoverer_(nullptr), pad_added_cb_id_(-1), notify_source_cb_id_(-1), about_to_finish_cb_id_(-1), bus_cb_id_(-1), - discovery_finished_cb_id_(-1), - discovery_discovered_cb_id_(-1), unsupported_analyzer_(false) { @@ -127,18 +122,6 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine) GstEnginePipeline::~GstEnginePipeline() { - if (discoverer_) { - - if (discovery_discovered_cb_id_ != -1) - g_signal_handler_disconnect(G_OBJECT(discoverer_), discovery_discovered_cb_id_); - if (discovery_finished_cb_id_ != -1) - g_signal_handler_disconnect(G_OBJECT(discoverer_), discovery_finished_cb_id_); - - g_object_unref(discoverer_); - discoverer_ = nullptr; - - } - if (pipeline_) { if (pad_added_cb_id_ != -1) @@ -234,16 +217,6 @@ bool GstEnginePipeline::InitFromUrl(const QByteArray &stream_url, const QUrl ori 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); -#ifdef Q_OS_LINUX - // Setting up a discoverer - discoverer_ = gst_discoverer_new(kDiscoveryTimeoutS * GST_SECOND, nullptr); - if (discoverer_) { - discovery_discovered_cb_id_ = CHECKED_GCONNECT(G_OBJECT(discoverer_), "discovered", &StreamDiscovered, this); - discovery_finished_cb_id_ = CHECKED_GCONNECT(G_OBJECT(discoverer_), "finished", &StreamDiscoveryFinished, this); - gst_discoverer_start(discoverer_); - } -#endif - if (!InitAudioBin()) return false; // Set playbin's sink to be our custom audio-sink. @@ -442,15 +415,6 @@ bool GstEnginePipeline::InitAudioBin() { bus_cb_id_ = gst_bus_add_watch(bus, BusCallback, this); gst_object_unref(bus); - // Add request to discover the stream -#ifdef Q_OS_LINUX - if (discoverer_) { - if (!gst_discoverer_discover_uri_async(discoverer_, stream_url_.toStdString().c_str())) { - qLog(Error) << "Failed to start stream discovery for" << stream_url_; - } - } -#endif - unsupported_analyzer_ = false; return true; @@ -1005,16 +969,6 @@ void GstEnginePipeline::StateChangedMessageReceived(GstMessage *msg) { next_uri_set_ = false; g_object_set(G_OBJECT(pipeline_), "uri", stream_url_.constData(), nullptr); SetState(GST_STATE_PLAYING); - - // Add request to discover the stream -#ifdef Q_OS_LINUX - if (discoverer_) { - if (!gst_discoverer_discover_uri_async(discoverer_, stream_url_.toStdString().c_str())) { - qLog(Error) << "Failed to start stream discovery for" << stream_url_; - } - } -#endif - } } @@ -1263,78 +1217,4 @@ void GstEnginePipeline::SetNextUrl(const QByteArray &stream_url, const QUrl &ori next_beginning_offset_nanosec_ = beginning_nanosec; next_end_offset_nanosec_ = end_nanosec; -#ifdef Q_OS_LINUX - // Add request to discover the stream - if (discoverer_) { - if (!gst_discoverer_discover_uri_async(discoverer_, next_stream_url_.toStdString().c_str())) { - qLog(Error) << "Failed to start stream discovery for" << next_stream_url_; - } - } -#endif - -} - -void GstEnginePipeline::StreamDiscovered(GstDiscoverer*, GstDiscovererInfo *info, GError*, gpointer self) { - - GstEnginePipeline *instance = reinterpret_cast(self); - - QString discovered_url(gst_discoverer_info_get_uri(info)); - - GstDiscovererResult result = gst_discoverer_info_get_result(info); - if (result != GST_DISCOVERER_OK) { - QString error_message = GSTdiscovererErrorMessage(result); - qLog(Error) << QString("Stream discovery for %1 failed: %2").arg(discovered_url).arg(error_message); - return; - } - - GList *audio_streams = gst_discoverer_info_get_audio_streams(info); - if (audio_streams) { - - GstDiscovererStreamInfo *stream_info = reinterpret_cast(g_list_first(audio_streams)->data); - - Engine::SimpleMetaBundle bundle; - if (discovered_url == instance->stream_url_) { - bundle.url = instance->original_url_; - } - else if (discovered_url == instance->next_stream_url_) { - bundle.url = instance->next_original_url_; - } - bundle.stream_url = QUrl(discovered_url); - bundle.samplerate = gst_discoverer_audio_info_get_sample_rate(GST_DISCOVERER_AUDIO_INFO(stream_info)); - bundle.bitdepth = gst_discoverer_audio_info_get_depth(GST_DISCOVERER_AUDIO_INFO(stream_info)); - bundle.bitrate = gst_discoverer_audio_info_get_bitrate(GST_DISCOVERER_AUDIO_INFO(stream_info)) / 1000; - - GstCaps *caps = gst_discoverer_stream_info_get_caps(stream_info); - gchar *codec_description = gst_pb_utils_get_codec_description(caps); - QString filetype_description = (codec_description ? QString(codec_description) : QString("Unknown")); - g_free(codec_description); - - gst_caps_unref(caps); - gst_discoverer_stream_info_list_free(audio_streams); - - bundle.filetype = Song::FiletypeByDescription(filetype_description); - qLog(Info) << "Got stream info for" << discovered_url + ":" << filetype_description; - - emit instance->MetadataFound(instance->id(), bundle); - - } - else { - qLog(Error) << "Could not detect an audio stream in" << discovered_url; - } - -} - -void GstEnginePipeline::StreamDiscoveryFinished(GstDiscoverer*, gpointer) {} - -QString GstEnginePipeline::GSTdiscovererErrorMessage(GstDiscovererResult result) { - - switch (result) { - case GST_DISCOVERER_URI_INVALID: return "The URI is invalid"; - case GST_DISCOVERER_TIMEOUT: return "The discovery timed-out"; - case GST_DISCOVERER_BUSY: return "The discoverer was already discovering a file"; - case GST_DISCOVERER_MISSING_PLUGINS: return "Some plugins are missing for full discovery"; - case GST_DISCOVERER_ERROR: - default: return "An error happened and the GError is set"; - } - } diff --git a/src/engine/gstenginepipeline.h b/src/engine/gstenginepipeline.h index 3af36afa2..85fa84ffe 100644 --- a/src/engine/gstenginepipeline.h +++ b/src/engine/gstenginepipeline.h @@ -29,7 +29,6 @@ #include #include #include -#include #include #include @@ -100,7 +99,9 @@ class GstEnginePipeline : public QObject { // Get information about the music playback QByteArray stream_url() const { return stream_url_; } + QByteArray next_stream_url() const { return next_stream_url_; } QUrl original_url() const { return original_url_; } + QUrl next_original_url() const { return next_original_url_; } bool is_valid() const { return valid_; } // Please note that this method (unlike GstEngine's.position()) is multiple-section media unaware. @@ -149,9 +150,6 @@ class GstEnginePipeline : public QObject { static GstBusSyncReply BusCallbackSync(GstBus*, GstMessage*, gpointer); static gboolean BusCallback(GstBus*, GstMessage*, gpointer); static void TaskEnterCallback(GstTask*, GThread*, gpointer); - static void StreamDiscovered(GstDiscoverer*, GstDiscovererInfo *info, GError*, gpointer self); - static void StreamDiscoveryFinished(GstDiscoverer*, gpointer); - static QString GSTdiscovererErrorMessage(GstDiscovererResult result); void TagMessageReceived(GstMessage*); void ErrorMessageReceived(GstMessage*); @@ -174,7 +172,6 @@ class GstEnginePipeline : public QObject { private: static const int kGstStateTimeoutNanosecs; static const int kFaderFudgeMsec; - static const int kDiscoveryTimeoutS; static const int kEqBandCount; static const int kEqBandFrequencies[]; @@ -275,14 +272,11 @@ class GstEnginePipeline : public QObject { GstElement *audiopanorama_; GstElement *equalizer_; GstElement *equalizer_preamp_; - GstDiscoverer *discoverer_; int pad_added_cb_id_; int notify_source_cb_id_; int about_to_finish_cb_id_; int bus_cb_id_; - int discovery_finished_cb_id_; - int discovery_discovered_cb_id_; QThreadPool set_state_threadpool_;