From 980d61a5834b934e57b37e172f94d3f7a3cc0cb5 Mon Sep 17 00:00:00 2001 From: David Sansome Date: Sat, 8 May 2010 17:39:12 +0000 Subject: [PATCH] If we're not crossfading, keep the same pipeline when changing tracks and just swap out the gstreamer source - this should allow for completely gapless playback. --- src/engines/gstengine.cpp | 39 +++++++++++++----- src/engines/gstengine.h | 2 +- src/engines/gstenginepipeline.cpp | 68 +++++++++++++++++++++---------- src/engines/gstenginepipeline.h | 16 +++++++- src/player.cpp | 2 +- 5 files changed, 91 insertions(+), 36 deletions(-) diff --git a/src/engines/gstengine.cpp b/src/engines/gstengine.cpp index f40fca8ba..de0f3dd75 100644 --- a/src/engines/gstengine.cpp +++ b/src/engines/gstengine.cpp @@ -350,16 +350,25 @@ void GstEngine::UpdateScope() { } void GstEngine::StartPreloading(const QUrl& url) { - preload_pipeline_ = CreatePipeline(url); - if (!preload_pipeline_) - return; + if (autocrossfade_enabled_) { + // Have to create a new pipeline so we can crossfade between the two - // We don't want to get metadata messages before the track starts playing - - // we reconnect this in GstEngine::Load - disconnect(preload_pipeline_.get(), SIGNAL(MetadataFound(Engine::SimpleMetaBundle)), this, 0); + preload_pipeline_ = CreatePipeline(url); + if (!preload_pipeline_) + return; - preloaded_url_ = url; - preload_pipeline_->SetState(GST_STATE_PAUSED); + // We don't want to get metadata messages before the track starts playing - + // we reconnect this in GstEngine::Load + disconnect(preload_pipeline_.get(), SIGNAL(MetadataFound(Engine::SimpleMetaBundle)), this, 0); + + preloaded_url_ = url; + preload_pipeline_->SetState(GST_STATE_PAUSED); + } else { + // No crossfading, so we can just queue the new URL in the existing + // pipeline and get gapless playback (hopefully) + if (current_pipeline_) + current_pipeline_->SetNextUrl(url); + } } bool GstEngine::Load(const QUrl& url, Engine::TrackChangeType change) { @@ -375,6 +384,12 @@ bool GstEngine::Load(const QUrl& url, Engine::TrackChangeType change) { ((crossfade_enabled_ && change == Engine::Manual) || (autocrossfade_enabled_ && change == Engine::Auto)); + if (!crossfade && current_pipeline_ && current_pipeline_->url() == url) { + // We're not crossfading, and the pipeline is already playing the URI we + // want, so just do nothing. + return true; + } + shared_ptr pipeline; if (preload_pipeline_ && preloaded_url_ == url) { pipeline = preload_pipeline_; @@ -545,8 +560,10 @@ void GstEngine::HandlePipelineError(const QString& message) { } -void GstEngine::EndOfStreamReached() { - current_pipeline_.reset(); +void GstEngine::EndOfStreamReached(bool has_next_track) { + if (!has_next_track) + current_pipeline_.reset(); + ClearScopeBuffers(); emit TrackEnded(); } @@ -603,7 +620,7 @@ shared_ptr GstEngine::CreatePipeline(const QUrl& url) { ret->set_forwards_buffers(true); ret->set_output_device(sink_, device_); - connect(ret.get(), SIGNAL(EndOfStreamReached()), SLOT(EndOfStreamReached())); + connect(ret.get(), SIGNAL(EndOfStreamReached(bool)), SLOT(EndOfStreamReached(bool))); connect(ret.get(), SIGNAL(BufferFound(GstBuffer*)), SLOT(AddBufferToScope(GstBuffer*))); connect(ret.get(), SIGNAL(Error(QString)), SLOT(HandlePipelineError(QString))); connect(ret.get(), SIGNAL(MetadataFound(Engine::SimpleMetaBundle)), diff --git a/src/engines/gstengine.h b/src/engines/gstengine.h index 7aab33766..8fdd15e02 100644 --- a/src/engines/gstengine.h +++ b/src/engines/gstengine.h @@ -97,7 +97,7 @@ class GstEngine : public Engine::Base { void timerEvent(QTimerEvent*); private slots: - void EndOfStreamReached(); + void EndOfStreamReached(bool has_next_track); void HandlePipelineError(const QString& message); void NewMetaData(const Engine::SimpleMetaBundle& bundle); void AddBufferToScope(GstBuffer* buf); diff --git a/src/engines/gstenginepipeline.cpp b/src/engines/gstenginepipeline.cpp index 4c276bbaa..38d01c7bf 100644 --- a/src/engines/gstenginepipeline.cpp +++ b/src/engines/gstenginepipeline.cpp @@ -36,8 +36,7 @@ GstEnginePipeline::GstEnginePipeline(GstEngine* engine) equalizer_(NULL), volume_(NULL), audioscale_(NULL), - audiosink_(NULL), - event_cb_id_(0) + audiosink_(NULL) { } @@ -46,8 +45,36 @@ void GstEnginePipeline::set_output_device(const QString &sink, const QString &de device_ = device; } +bool GstEnginePipeline::StopUriDecodeBin(gpointer bin) { + gst_element_set_state(GST_ELEMENT(bin), GST_STATE_NULL); + return false; // So it doesn't get called again +} + +bool GstEnginePipeline::ReplaceDecodeBin(const QUrl& url) { + GstElement* new_bin = engine_->CreateElement("uridecodebin"); + if (!new_bin) return false; + + // Destroy the old one, if any + if (uridecodebin_) { + gst_bin_remove(GST_BIN(pipeline_), uridecodebin_); + + // Set its state to NULL later in the main thread + g_idle_add(GSourceFunc(StopUriDecodeBin), uridecodebin_); + } + + uridecodebin_ = new_bin; + gst_bin_add(GST_BIN(pipeline_), uridecodebin_); + + g_object_set(G_OBJECT(uridecodebin_), "uri", url.toEncoded().constData(), NULL); + g_signal_connect(G_OBJECT(uridecodebin_), "pad-added", G_CALLBACK(NewPadCallback), this); + g_signal_connect(G_OBJECT(uridecodebin_), "drained", G_CALLBACK(SourceDrainedCallback), this); + + return true; +} + bool GstEnginePipeline::Init(const QUrl &url) { pipeline_ = gst_pipeline_new("pipeline"); + url_ = url; // Here we create all the parts of the gstreamer pipeline - from the source // to the sink. The parts of the pipeline are split up into bins: @@ -59,16 +86,7 @@ bool GstEnginePipeline::Init(const QUrl &url) { // audiosink // Decode bin - if (!(uridecodebin_ = engine_->CreateElement("uridecodebin", pipeline_))) { return false; } - g_object_set(G_OBJECT(uridecodebin_), "uri", url.toEncoded().constData(), NULL); - g_signal_connect(G_OBJECT(uridecodebin_), "pad-added", G_CALLBACK(NewPadCallback), this); - - // Does some stuff with ghost pads - /*GstPad* pad = gst_element_get_pad(uridecodebin_, "sink"); - if (pad) { - event_cb_id_ = gst_pad_add_event_probe (pad, G_CALLBACK(EventCallback), this); - gst_object_unref(pad); - }*/ + if (!ReplaceDecodeBin(url)) return false; // Audio bin audiobin_ = gst_bin_new("audiobin"); @@ -119,13 +137,6 @@ bool GstEnginePipeline::Init(const QUrl &url) { } GstEnginePipeline::~GstEnginePipeline() { - // We don't want an EOS signal from the decodebin - if (uridecodebin_) { - GstPad *p = gst_element_get_pad(uridecodebin_, "sink"); - if (p) - gst_pad_remove_event_probe(p, event_cb_id_); - } - if (pipeline_) { gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(pipeline_)), NULL, NULL); g_source_remove(bus_cb_id_); @@ -158,7 +169,7 @@ GstBusSyncReply GstEnginePipeline::BusCallbackSync(GstBus*, GstMessage* msg, gpo GstEnginePipeline* instance = reinterpret_cast(self); switch (GST_MESSAGE_TYPE(msg)) { case GST_MESSAGE_EOS: - emit instance->EndOfStreamReached(); + emit instance->EndOfStreamReached(false); break; case GST_MESSAGE_TAG: @@ -249,7 +260,7 @@ void GstEnginePipeline::EventCallback(GstPad*, GstEvent* event, gpointer self) { switch(event->type) { case GST_EVENT_EOS: - emit instance->EndOfStreamReached(); + emit instance->EndOfStreamReached(false); break; default: @@ -257,6 +268,21 @@ void GstEnginePipeline::EventCallback(GstPad*, GstEvent* event, gpointer self) { } } +void GstEnginePipeline::SourceDrainedCallback(GstURIDecodeBin* bin, gpointer self) { + GstEnginePipeline* instance = reinterpret_cast(self); + + if (instance->next_url_.isValid()) { + instance->ReplaceDecodeBin(instance->next_url_); + gst_element_set_state(instance->uridecodebin_, GST_STATE_PLAYING); + + instance->url_ = instance->next_url_; + instance->next_url_ = QUrl(); + + // This just tells the UI that we've moved on to the next song + emit instance->EndOfStreamReached(true); + } +} + qint64 GstEnginePipeline::position() const { GstFormat fmt = GST_FORMAT_TIME; gint64 value = 0; diff --git a/src/engines/gstenginepipeline.h b/src/engines/gstenginepipeline.h index 7b43ddc4a..53c9fa385 100644 --- a/src/engines/gstenginepipeline.h +++ b/src/engines/gstenginepipeline.h @@ -27,6 +27,8 @@ class GstEngine; +struct GstURIDecodeBin; + class GstEnginePipeline : public QObject { Q_OBJECT @@ -51,7 +53,12 @@ class GstEnginePipeline : public QObject { QTimeLine::Direction direction = QTimeLine::Forward, QTimeLine::CurveShape shape = QTimeLine::LinearCurve); + // If this is set then it will be loaded automatically when playback finishes + // for gapless playback + void SetNextUrl(const QUrl& url) { next_url_ = url; } + // Get information about the music playback + QUrl url() const { return url_; } bool is_valid() const { return valid_; } qint64 position() const; qint64 length() const; @@ -61,7 +68,7 @@ class GstEnginePipeline : public QObject { void SetVolumeModifier(qreal mod); signals: - void EndOfStreamReached(); + void EndOfStreamReached(bool has_next_track); void MetadataFound(const Engine::SimpleMetaBundle& bundle); void BufferFound(GstBuffer* buf); void Error(const QString& message); @@ -75,11 +82,14 @@ class GstEnginePipeline : public QObject { static void NewPadCallback(GstElement*, GstPad*, gpointer); static bool HandoffCallback(GstPad*, GstBuffer*, gpointer); static void EventCallback(GstPad*, GstEvent*, gpointer); + static void SourceDrainedCallback(GstURIDecodeBin*, gpointer); + static bool StopUriDecodeBin(gpointer bin); void TagMessageReceived(GstMessage*); QString ParseTag(GstTagList* list, const char* tag) const; void ErrorMessageReceived(GstMessage*); void UpdateVolume(); + bool ReplaceDecodeBin(const QUrl& url); private: static const int kGstStateTimeoutNanosecs = 10000000; @@ -91,6 +101,9 @@ class GstEnginePipeline : public QObject { QString device_; bool forwards_buffers_; + QUrl url_; + QUrl next_url_; + int volume_percent_; qreal volume_modifier_; @@ -111,7 +124,6 @@ class GstEnginePipeline : public QObject { GstElement* audioscale_; GstElement* audiosink_; - uint event_cb_id_; uint bus_cb_id_; }; diff --git a/src/player.cpp b/src/player.cpp index 7abab2766..66cfaa490 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -156,7 +156,7 @@ void Player::NextInternal(Engine::TrackChangeType change) { return; } - NextItem(Engine::Manual); + NextItem(change); } void Player::NextItem(Engine::TrackChangeType change) {