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:
parent
2e602a276a
commit
0335d57a0f
@ -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)
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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_;
|
||||
|
@ -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");
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user