Improve pipeline buffering: on an underrun event playback will now pause until the buffer is completely full. Fixes audio stuttering on when listening to radio streams on slow networks.

This commit is contained in:
David Sansome 2012-01-23 14:58:10 +00:00
parent 2e602a276a
commit 0335d57a0f
7 changed files with 93 additions and 11 deletions

View File

@ -38,11 +38,12 @@
using boost::shared_ptr;
Player::Player(PlaylistManagerInterface* playlists, QObject* parent)
Player::Player(PlaylistManagerInterface* playlists, TaskManager* task_manager,
QObject* parent)
: PlayerInterface(parent),
playlists_(playlists),
lastfm_(NULL),
engine_(new GstEngine),
engine_(new GstEngine(task_manager)),
stream_change_type_(Engine::First),
last_state_(Engine::Empty),
volume_before_mute_(50)

View File

@ -34,6 +34,7 @@ class LastFMService;
class MainWindow;
class PlaylistManagerInterface;
class Settings;
class TaskManager;
class PlayerInterface : public QObject {
@ -107,7 +108,8 @@ class Player : public PlayerInterface {
Q_OBJECT
public:
Player(PlaylistManagerInterface* playlists, QObject* parent = 0);
Player(PlaylistManagerInterface* playlists, TaskManager* task_manager,
QObject* parent = 0);
~Player();
void Init();

View File

@ -25,6 +25,7 @@
#include "gstengine.h"
#include "gstenginepipeline.h"
#include "core/logging.h"
#include "core/taskmanager.h"
#include "core/utilities.h"
#ifdef HAVE_IMOBILEDEVICE
@ -67,8 +68,10 @@ const char* GstEngine::kEnterprisePipeline =
"audiotestsrc wave=5 ! "
"audiocheblimit mode=0 cutoff=120";
GstEngine::GstEngine()
GstEngine::GstEngine(TaskManager* task_manager)
: Engine::Base(),
task_manager_(task_manager),
buffering_task_id_(-1),
delayq_(g_queue_new()),
current_sample_(0),
equalizer_enabled_(false),
@ -369,6 +372,7 @@ bool GstEngine::Load(const QUrl& url, Engine::TrackChangeFlags change,
if (crossfade)
StartFadeout();
BufferingFinished();
current_pipeline_ = pipeline;
SetVolume(volume_);
@ -396,7 +400,7 @@ void GstEngine::StartFadeout() {
bool GstEngine::Play(quint64 offset_nanosec) {
EnsureInitialised();
if (!current_pipeline_)
if (!current_pipeline_ || current_pipeline_->is_buffering())
return false;
QFuture<GstStateChangeReturn> future = current_pipeline_->SetState(GST_STATE_PLAYING);
@ -432,6 +436,7 @@ void GstEngine::PlayDone() {
// Failure - give up
qLog(Warning) << "Could not set thread to PLAYING.";
current_pipeline_.reset();
BufferingFinished();
return;
}
@ -460,6 +465,7 @@ void GstEngine::Stop() {
StartFadeout();
current_pipeline_.reset();
BufferingFinished();
emit StateChanged(Engine::Empty);
}
@ -469,7 +475,7 @@ void GstEngine::FadeoutFinished() {
}
void GstEngine::Pause() {
if (!current_pipeline_)
if (!current_pipeline_ || current_pipeline_->is_buffering())
return;
if ( current_pipeline_->state() == GST_STATE_PLAYING ) {
@ -481,7 +487,7 @@ void GstEngine::Pause() {
}
void GstEngine::Unpause() {
if (!current_pipeline_)
if (!current_pipeline_ || current_pipeline_->is_buffering())
return;
if ( current_pipeline_->state() == GST_STATE_PAUSED ) {
@ -589,6 +595,7 @@ void GstEngine::HandlePipelineError(int pipeline_id, const QString& message,
current_pipeline_.reset();
BufferingFinished();
emit StateChanged(Engine::Empty);
// unable to play media stream with this url
emit InvalidSongRequested(url_);
@ -610,8 +617,10 @@ void GstEngine::EndOfStreamReached(int pipeline_id, bool has_next_track) {
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id)
return;
if (!has_next_track)
if (!has_next_track) {
current_pipeline_.reset();
BufferingFinished();
}
ClearScopeBuffers();
emit TrackEnded();
}
@ -690,6 +699,9 @@ shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline() {
connect(ret.get(), SIGNAL(MetadataFound(int, Engine::SimpleMetaBundle)),
SLOT(NewMetaData(int, Engine::SimpleMetaBundle)));
connect(ret.get(), SIGNAL(destroyed()), SLOT(ClearScopeBuffers()));
connect(ret.get(), SIGNAL(BufferingStarted()), SLOT(BufferingStarted()));
connect(ret.get(), SIGNAL(BufferingProgress(int)), SLOT(BufferingProgress(int)));
connect(ret.get(), SIGNAL(BufferingFinished()), SLOT(BufferingFinished()));
return ret;
}
@ -828,3 +840,23 @@ void GstEngine::SetBackgroundStreamVolume(int id, int volume) {
Q_ASSERT(pipeline);
pipeline->SetVolume(volume);
}
void GstEngine::BufferingStarted() {
if (buffering_task_id_ != -1) {
task_manager_->SetTaskFinished(buffering_task_id_);
}
buffering_task_id_ = task_manager_->StartTask(tr("Buffering"));
task_manager_->SetTaskProgress(buffering_task_id_, 0, 100);
}
void GstEngine::BufferingProgress(int percent) {
task_manager_->SetTaskProgress(buffering_task_id_, percent, 100);
}
void GstEngine::BufferingFinished() {
if (buffering_task_id_ != -1) {
task_manager_->SetTaskFinished(buffering_task_id_);
buffering_task_id_ = -1;
}
}

View File

@ -41,6 +41,7 @@ class QTimer;
class QTimerEvent;
class GstEnginePipeline;
class TaskManager;
/**
* @class GstEngine
@ -51,7 +52,7 @@ class GstEngine : public Engine::Base, public BufferConsumer {
Q_OBJECT
public:
GstEngine();
GstEngine(TaskManager* task_manager);
~GstEngine();
struct PluginDetails {
@ -125,6 +126,10 @@ class GstEngine : public Engine::Base, public BufferConsumer {
void BackgroundStreamPlayDone();
void PlayDone();
void BufferingStarted();
void BufferingProgress(int percent);
void BufferingFinished();
private:
typedef QPair<quint64, int> PlayFutureWatcherArg;
typedef BoundFutureWatcher<GstStateChangeReturn, PlayFutureWatcherArg> PlayFutureWatcher;
@ -155,6 +160,9 @@ class GstEngine : public Engine::Base, public BufferConsumer {
static const char* kHypnotoadPipeline;
static const char* kEnterprisePipeline;
TaskManager* task_manager_;
int buffering_task_id_;
QFuture<void> initialising_;
QString sink_;

View File

@ -60,6 +60,7 @@ GstEnginePipeline::GstEnginePipeline(GstEngine* engine)
rg_preamp_(0.0),
rg_compression_(true),
buffer_duration_nanosec_(1 * kNsecPerSec),
buffering_(false),
end_offset_nanosec_(-1),
next_beginning_offset_nanosec_(-1),
next_end_offset_nanosec_(-1),
@ -227,7 +228,7 @@ bool GstEnginePipeline::Init() {
GstElement *tee, *probe_queue, *probe_converter, *probe_sink, *audio_queue,
*convert;
queue_ = engine_->CreateElement("queue", audiobin_);
queue_ = engine_->CreateElement("queue2", audiobin_);
audioconvert_ = engine_->CreateElement("audioconvert", audiobin_);
tee = engine_->CreateElement("tee", audiobin_);
@ -309,6 +310,7 @@ bool GstEnginePipeline::Init() {
// decode bin (in ReplaceDecodeBin()) because setting it on the decode bin
// only affects network sources.
g_object_set(G_OBJECT(queue_), "max-size-time", buffer_duration_nanosec_, NULL);
g_object_set(G_OBJECT(queue_), "use-buffering", true, NULL);
gst_element_link(queue_, audioconvert_);
@ -463,6 +465,10 @@ GstBusSyncReply GstEnginePipeline::BusCallbackSync(GstBus*, GstMessage* msg, gpo
instance->StateChangedMessageReceived(msg);
break;
case GST_MESSAGE_BUFFERING:
instance->BufferingMessageReceived(msg);
break;
default:
break;
}
@ -562,6 +568,27 @@ void GstEnginePipeline::StateChangedMessageReceived(GstMessage* msg) {
}
}
void GstEnginePipeline::BufferingMessageReceived(GstMessage* msg) {
int percent = 0;
gst_message_parse_buffering(msg, &percent);
const GstState current_state = state();
if (percent == 0 && current_state == GST_STATE_PLAYING && !buffering_) {
buffering_ = true;
emit BufferingStarted();
gst_element_set_state(pipeline_, GST_STATE_PAUSED);
} else if (percent == 100 && buffering_) {
buffering_ = false;
emit BufferingFinished();
gst_element_set_state(pipeline_, GST_STATE_PLAYING);
} else if (buffering_) {
emit BufferingProgress(percent);
}
}
void GstEnginePipeline::NewPadCallback(GstElement*, GstPad* pad, gpointer self) {
GstEnginePipeline* instance = reinterpret_cast<GstEnginePipeline*>(self);
GstPad* const audiopad = gst_element_get_pad(instance->audiobin_, "sink");

View File

@ -34,6 +34,7 @@ class GstElementDeleter;
class GstEngine;
class BufferConsumer;
struct GstQueue;
struct GstURIDecodeBin;
class GstEnginePipeline : public QObject {
@ -89,6 +90,10 @@ class GstEnginePipeline : public QObject {
GstState state() const;
qint64 segment_start() const { return segment_start_; }
// Don't allow the user to change the playback state (playing/paused) while
// the pipeline is buffering.
bool is_buffering() const { return buffering_; }
QUrl redirect_url() const { return redirect_url_; }
QString source_device() const { return source_device_; }
@ -104,6 +109,10 @@ class GstEnginePipeline : public QObject {
void Error(int pipeline_id, const QString& message, int domain, int error_code);
void FaderFinished();
void BufferingStarted();
void BufferingProgress(int percent);
void BufferingFinished();
protected:
void timerEvent(QTimerEvent *);
@ -122,6 +131,7 @@ class GstEnginePipeline : public QObject {
void ErrorMessageReceived(GstMessage*);
void ElementMessageReceived(GstMessage*);
void StateChangedMessageReceived(GstMessage*);
void BufferingMessageReceived(GstMessage*);
QString ParseTag(GstTagList* list, const char* tag) const;
@ -182,7 +192,9 @@ class GstEnginePipeline : public QObject {
int rg_mode_;
float rg_preamp_;
bool rg_compression_;
quint64 buffer_duration_nanosec_;
bool buffering_;
// The URL that is currently playing, and the URL that is to be preloaded
// when the current track is close to finishing.

View File

@ -397,7 +397,7 @@ int main(int argc, char *argv[]) {
database->Start(true);
TaskManager task_manager;
PlaylistManager playlists(&task_manager, NULL);
Player player(&playlists);
Player player(&playlists, &task_manager);
GlobalSearch global_search;
InternetModel internet_model(database.get(), &task_manager, &player,
&cover_providers, &global_search, NULL);