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.

This commit is contained in:
David Sansome 2010-05-08 17:39:12 +00:00
parent 8ea3213f09
commit 980d61a583
5 changed files with 91 additions and 36 deletions

View File

@ -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<GstEnginePipeline> 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<GstEnginePipeline> 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)),

View File

@ -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);

View File

@ -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<GstEnginePipeline*>(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<GstEnginePipeline*>(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;

View File

@ -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_;
};

View File

@ -156,7 +156,7 @@ void Player::NextInternal(Engine::TrackChangeType change) {
return;
}
NextItem(Engine::Manual);
NextItem(change);
}
void Player::NextItem(Engine::TrackChangeType change) {