Refactor gstreamer engine code, equalizer and fix stereo balancer
This commit is contained in:
parent
d033b79af4
commit
834877c503
@ -200,12 +200,13 @@ void Player::Init() {
|
|||||||
connect(engine_.get(), SIGNAL(MetaData(Engine::SimpleMetaBundle)), SLOT(EngineMetadataReceived(Engine::SimpleMetaBundle)));
|
connect(engine_.get(), SIGNAL(MetaData(Engine::SimpleMetaBundle)), SLOT(EngineMetadataReceived(Engine::SimpleMetaBundle)));
|
||||||
|
|
||||||
// Equalizer
|
// Equalizer
|
||||||
qLog(Debug) << "Creating equalizer";
|
connect(equalizer_, SIGNAL(StereoBalancerEnabledChanged(bool)), app_->player()->engine(), SLOT(SetStereoBalancerEnabled(bool)));
|
||||||
connect(equalizer_, SIGNAL(ParametersChanged(int,QList<int>)), app_->player()->engine(), SLOT(SetEqualizerParameters(int,QList<int>)));
|
connect(equalizer_, SIGNAL(StereoBalanceChanged(float)), app_->player()->engine(), SLOT(SetStereoBalance(float)));
|
||||||
connect(equalizer_, SIGNAL(EnabledChanged(bool)), app_->player()->engine(), SLOT(SetEqualizerEnabled(bool)));
|
connect(equalizer_, SIGNAL(EqualizerEnabledChanged(bool)), app_->player()->engine(), SLOT(SetEqualizerEnabled(bool)));
|
||||||
connect(equalizer_, SIGNAL(StereoBalanceChanged(bool, float)), app_->player()->engine(), SLOT(SetStereoBalance(bool, float)));
|
connect(equalizer_, SIGNAL(EqualizerParametersChanged(int, QList<int>)), app_->player()->engine(), SLOT(SetEqualizerParameters(int, QList<int>)));
|
||||||
|
|
||||||
engine_->SetStereoBalance(equalizer_->is_stereo_balancer_enabled(), equalizer_->stereo_balance());
|
engine_->SetStereoBalancerEnabled(equalizer_->is_stereo_balancer_enabled());
|
||||||
|
engine_->SetStereoBalance(equalizer_->stereo_balance());
|
||||||
engine_->SetEqualizerEnabled(equalizer_->is_equalizer_enabled());
|
engine_->SetEqualizerEnabled(equalizer_->is_equalizer_enabled());
|
||||||
engine_->SetEqualizerParameters(equalizer_->preamp_value(), equalizer_->gain_values());
|
engine_->SetEqualizerParameters(equalizer_->preamp_value(), equalizer_->gain_values());
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ bool Engine::Base::Play(const QUrl &stream_url, const QUrl &original_url, TrackC
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::Base::SetVolume(uint value) {
|
void Engine::Base::SetVolume(const uint value) {
|
||||||
|
|
||||||
volume_ = value;
|
volume_ = value;
|
||||||
SetVolumeSW(MakeVolumeLogarithmic(value));
|
SetVolumeSW(MakeVolumeLogarithmic(value));
|
||||||
|
@ -102,13 +102,13 @@ public:
|
|||||||
void SetVolume(const uint value);
|
void SetVolume(const uint value);
|
||||||
static uint MakeVolumeLogarithmic(const uint volume);
|
static uint MakeVolumeLogarithmic(const uint volume);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
virtual void ReloadSettings();
|
virtual void ReloadSettings();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void EmitAboutToEnd();
|
void EmitAboutToEnd();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
// Simple accessors
|
// Simple accessors
|
||||||
EngineType type() const { return type_; }
|
EngineType type() const { return type_; }
|
||||||
@ -126,9 +126,10 @@ public:
|
|||||||
QVariant device() { return device_; }
|
QVariant device() { return device_; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
virtual void SetStereoBalancerEnabled(const bool) {}
|
||||||
|
virtual void SetStereoBalance(const float) {}
|
||||||
virtual void SetEqualizerEnabled(const bool) {}
|
virtual void SetEqualizerEnabled(const bool) {}
|
||||||
virtual void SetEqualizerParameters(const int preamp, const QList<int> &bandGains) { Q_UNUSED(preamp); Q_UNUSED(bandGains); }
|
virtual void SetEqualizerParameters(const int, const QList<int>&) {}
|
||||||
virtual void SetStereoBalance(const bool enabled, const float value) { Q_UNUSED(enabled); Q_UNUSED(value); }
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
// Emitted when crossfading is enabled and the track is crossfade_duration_ away from finishing
|
// Emitted when crossfading is enabled and the track is crossfade_duration_ away from finishing
|
||||||
@ -199,7 +200,7 @@ public:
|
|||||||
qint64 fadeout_pause_duration_;
|
qint64 fadeout_pause_duration_;
|
||||||
qint64 fadeout_pause_duration_nanosec_;
|
qint64 fadeout_pause_duration_nanosec_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool about_to_end_emitted_;
|
bool about_to_end_emitted_;
|
||||||
Q_DISABLE_COPY(Base)
|
Q_DISABLE_COPY(Base)
|
||||||
|
|
||||||
|
@ -85,6 +85,8 @@ GstEngine::GstEngine(TaskManager *task_manager)
|
|||||||
latest_buffer_(nullptr),
|
latest_buffer_(nullptr),
|
||||||
stereo_balancer_enabled_(false),
|
stereo_balancer_enabled_(false),
|
||||||
stereo_balance_(0.0f),
|
stereo_balance_(0.0f),
|
||||||
|
equalizer_enabled_(false),
|
||||||
|
equalizer_preamp_(0),
|
||||||
seek_timer_(new QTimer(this)),
|
seek_timer_(new QTimer(this)),
|
||||||
timer_id_(-1),
|
timer_id_(-1),
|
||||||
next_element_id_(0),
|
next_element_id_(0),
|
||||||
@ -103,12 +105,15 @@ GstEngine::GstEngine(TaskManager *task_manager)
|
|||||||
}
|
}
|
||||||
|
|
||||||
GstEngine::~GstEngine() {
|
GstEngine::~GstEngine() {
|
||||||
|
|
||||||
EnsureInitialised();
|
EnsureInitialised();
|
||||||
current_pipeline_.reset();
|
current_pipeline_.reset();
|
||||||
|
|
||||||
if (latest_buffer_) {
|
if (latest_buffer_) {
|
||||||
gst_buffer_unref(latest_buffer_);
|
gst_buffer_unref(latest_buffer_);
|
||||||
latest_buffer_ = nullptr;
|
latest_buffer_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GstEngine::Init() {
|
bool GstEngine::Init() {
|
||||||
@ -175,7 +180,7 @@ bool GstEngine::Load(const QUrl &stream_url, const QUrl &original_url, Engine::T
|
|||||||
current_pipeline_ = pipeline;
|
current_pipeline_ = pipeline;
|
||||||
|
|
||||||
SetVolume(volume_);
|
SetVolume(volume_);
|
||||||
SetStereoBalance(stereo_balancer_enabled_, stereo_balance_);
|
SetStereoBalance(stereo_balance_);
|
||||||
SetEqualizerParameters(equalizer_preamp_, equalizer_gains_);
|
SetEqualizerParameters(equalizer_preamp_, equalizer_gains_);
|
||||||
|
|
||||||
// Maybe fade in this track
|
// Maybe fade in this track
|
||||||
@ -425,11 +430,25 @@ void GstEngine::ConsumeBuffer(GstBuffer *buffer, const int pipeline_id, const QS
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GstEngine::SetStereoBalancerEnabled(const bool enabled) {
|
||||||
|
|
||||||
|
stereo_balancer_enabled_ = enabled;
|
||||||
|
if (current_pipeline_) current_pipeline_->set_stereo_balancer_enabled(enabled);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void GstEngine::SetStereoBalance(const float value) {
|
||||||
|
|
||||||
|
stereo_balance_ = value;
|
||||||
|
if (current_pipeline_) current_pipeline_->SetStereoBalance(value);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void GstEngine::SetEqualizerEnabled(const bool enabled) {
|
void GstEngine::SetEqualizerEnabled(const bool enabled) {
|
||||||
|
|
||||||
equalizer_enabled_ = enabled;
|
equalizer_enabled_ = enabled;
|
||||||
|
if (current_pipeline_) current_pipeline_->set_equalizer_enabled(enabled);
|
||||||
|
|
||||||
if (current_pipeline_) current_pipeline_->SetEqualizerEnabled(enabled);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEngine::SetEqualizerParameters(const int preamp, const QList<int> &band_gains) {
|
void GstEngine::SetEqualizerParameters(const int preamp, const QList<int> &band_gains) {
|
||||||
@ -437,27 +456,22 @@ void GstEngine::SetEqualizerParameters(const int preamp, const QList<int> &band_
|
|||||||
equalizer_preamp_ = preamp;
|
equalizer_preamp_ = preamp;
|
||||||
equalizer_gains_ = band_gains;
|
equalizer_gains_ = band_gains;
|
||||||
|
|
||||||
if (current_pipeline_)
|
if (current_pipeline_) current_pipeline_->SetEqualizerParams(preamp, band_gains);
|
||||||
current_pipeline_->SetEqualizerParams(preamp, band_gains);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void GstEngine::SetStereoBalance(const bool enabled, const float value) {
|
|
||||||
|
|
||||||
stereo_balance_ = value;
|
|
||||||
|
|
||||||
if (current_pipeline_) current_pipeline_->SetStereoBalance(enabled, value);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEngine::AddBufferConsumer(GstBufferConsumer *consumer) {
|
void GstEngine::AddBufferConsumer(GstBufferConsumer *consumer) {
|
||||||
|
|
||||||
buffer_consumers_ << consumer;
|
buffer_consumers_ << consumer;
|
||||||
if (current_pipeline_) current_pipeline_->AddBufferConsumer(consumer);
|
if (current_pipeline_) current_pipeline_->AddBufferConsumer(consumer);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEngine::RemoveBufferConsumer(GstBufferConsumer *consumer) {
|
void GstEngine::RemoveBufferConsumer(GstBufferConsumer *consumer) {
|
||||||
|
|
||||||
buffer_consumers_.removeAll(consumer);
|
buffer_consumers_.removeAll(consumer);
|
||||||
if (current_pipeline_) current_pipeline_->RemoveBufferConsumer(consumer);
|
if (current_pipeline_) current_pipeline_->RemoveBufferConsumer(consumer);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEngine::timerEvent(QTimerEvent *e) {
|
void GstEngine::timerEvent(QTimerEvent *e) {
|
||||||
@ -745,11 +759,12 @@ shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline() {
|
|||||||
|
|
||||||
shared_ptr<GstEnginePipeline> ret(new GstEnginePipeline(this));
|
shared_ptr<GstEnginePipeline> ret(new GstEnginePipeline(this));
|
||||||
ret->set_output_device(output_, device_);
|
ret->set_output_device(output_, device_);
|
||||||
ret->set_volume_control(volume_control_);
|
ret->set_volume_enabled(volume_control_);
|
||||||
|
ret->set_stereo_balancer_enabled(stereo_balancer_enabled_);
|
||||||
|
ret->set_equalizer_enabled(equalizer_enabled_);
|
||||||
ret->set_replaygain(rg_enabled_, rg_mode_, rg_preamp_, rg_compression_);
|
ret->set_replaygain(rg_enabled_, rg_mode_, rg_preamp_, rg_compression_);
|
||||||
ret->set_buffer_duration_nanosec(buffer_duration_nanosec_);
|
ret->set_buffer_duration_nanosec(buffer_duration_nanosec_);
|
||||||
ret->set_buffer_min_fill(buffer_min_fill_);
|
ret->set_buffer_min_fill(buffer_min_fill_);
|
||||||
ret->SetEqualizerEnabled(equalizer_enabled_);
|
|
||||||
|
|
||||||
ret->AddBufferConsumer(this);
|
ret->AddBufferConsumer(this);
|
||||||
for (GstBufferConsumer *consumer : buffer_consumers_) {
|
for (GstBufferConsumer *consumer : buffer_consumers_) {
|
||||||
|
@ -95,15 +95,18 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
|
|||||||
public slots:
|
public slots:
|
||||||
void ReloadSettings();
|
void ReloadSettings();
|
||||||
|
|
||||||
/** Set whether equalizer is enabled */
|
// Set whether stereo balancer is enabled
|
||||||
|
void SetStereoBalancerEnabled(const bool enabled);
|
||||||
|
|
||||||
|
// Set Stereo balance, range -1.0f..1.0f
|
||||||
|
void SetStereoBalance(const float value);
|
||||||
|
|
||||||
|
// Set whether equalizer is enabled
|
||||||
void SetEqualizerEnabled(const bool);
|
void SetEqualizerEnabled(const bool);
|
||||||
|
|
||||||
/** Set equalizer preamp and gains, range -100..100. Gains are 10 values. */
|
// Set equalizer preamp and gains, range -100..100. Gains are 10 values.
|
||||||
void SetEqualizerParameters(const int preamp, const QList<int> &bandGains);
|
void SetEqualizerParameters(const int preamp, const QList<int> &bandGains);
|
||||||
|
|
||||||
/** Set Stereo balance, range -1.0f..1.0f */
|
|
||||||
void SetStereoBalance(const bool enabled, const float value);
|
|
||||||
|
|
||||||
void AddBufferConsumer(GstBufferConsumer *consumer);
|
void AddBufferConsumer(GstBufferConsumer *consumer);
|
||||||
void RemoveBufferConsumer(GstBufferConsumer *consumer);
|
void RemoveBufferConsumer(GstBufferConsumer *consumer);
|
||||||
|
|
||||||
@ -173,6 +176,7 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
|
|||||||
bool stereo_balancer_enabled_;
|
bool stereo_balancer_enabled_;
|
||||||
float stereo_balance_;
|
float stereo_balance_;
|
||||||
|
|
||||||
|
bool equalizer_enabled_;
|
||||||
int equalizer_preamp_;
|
int equalizer_preamp_;
|
||||||
QList<int> equalizer_gains_;
|
QList<int> equalizer_gains_;
|
||||||
|
|
||||||
|
@ -67,12 +67,12 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine)
|
|||||||
engine_(engine),
|
engine_(engine),
|
||||||
id_(sId++),
|
id_(sId++),
|
||||||
valid_(false),
|
valid_(false),
|
||||||
volume_control_(true),
|
volume_enabled_(true),
|
||||||
stereo_balance_enabled_(false),
|
stereo_balancer_enabled_(false),
|
||||||
stereo_balance_(0.0f),
|
|
||||||
eq_enabled_(false),
|
eq_enabled_(false),
|
||||||
eq_preamp_(0),
|
|
||||||
rg_enabled_(false),
|
rg_enabled_(false),
|
||||||
|
stereo_balance_(0.0f),
|
||||||
|
eq_preamp_(0),
|
||||||
rg_mode_(0),
|
rg_mode_(0),
|
||||||
rg_preamp_(0.0),
|
rg_preamp_(0.0),
|
||||||
rg_compression_(true),
|
rg_compression_(true),
|
||||||
@ -89,9 +89,10 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine)
|
|||||||
pipeline_is_initialised_(false),
|
pipeline_is_initialised_(false),
|
||||||
pipeline_is_connected_(false),
|
pipeline_is_connected_(false),
|
||||||
pending_seek_nanosec_(-1),
|
pending_seek_nanosec_(-1),
|
||||||
|
last_known_position_ns_(0),
|
||||||
next_uri_set_(false),
|
next_uri_set_(false),
|
||||||
volume_percent_(100),
|
volume_percent_(100),
|
||||||
volume_modifier_(1.0),
|
volume_modifier_(1.0f),
|
||||||
use_fudge_timer_(false),
|
use_fudge_timer_(false),
|
||||||
pipeline_(nullptr),
|
pipeline_(nullptr),
|
||||||
audiobin_(nullptr),
|
audiobin_(nullptr),
|
||||||
@ -101,9 +102,9 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine)
|
|||||||
equalizer_(nullptr),
|
equalizer_(nullptr),
|
||||||
equalizer_preamp_(nullptr),
|
equalizer_preamp_(nullptr),
|
||||||
discoverer_(nullptr),
|
discoverer_(nullptr),
|
||||||
about_to_finish_cb_id_(-1),
|
|
||||||
pad_added_cb_id_(-1),
|
pad_added_cb_id_(-1),
|
||||||
notify_source_cb_id_(-1),
|
notify_source_cb_id_(-1),
|
||||||
|
about_to_finish_cb_id_(-1),
|
||||||
bus_cb_id_(-1),
|
bus_cb_id_(-1),
|
||||||
discovery_finished_cb_id_(-1),
|
discovery_finished_cb_id_(-1),
|
||||||
discovery_discovered_cb_id_(-1)
|
discovery_discovered_cb_id_(-1)
|
||||||
@ -131,15 +132,15 @@ GstEnginePipeline::~GstEnginePipeline() {
|
|||||||
|
|
||||||
if (pipeline_) {
|
if (pipeline_) {
|
||||||
|
|
||||||
if (about_to_finish_cb_id_ != -1)
|
|
||||||
g_signal_handler_disconnect(G_OBJECT(pipeline_), about_to_finish_cb_id_);
|
|
||||||
|
|
||||||
if (pad_added_cb_id_ != -1)
|
if (pad_added_cb_id_ != -1)
|
||||||
g_signal_handler_disconnect(G_OBJECT(pipeline_), pad_added_cb_id_);
|
g_signal_handler_disconnect(G_OBJECT(pipeline_), pad_added_cb_id_);
|
||||||
|
|
||||||
if (notify_source_cb_id_ != -1)
|
if (notify_source_cb_id_ != -1)
|
||||||
g_signal_handler_disconnect(G_OBJECT(pipeline_), notify_source_cb_id_);
|
g_signal_handler_disconnect(G_OBJECT(pipeline_), notify_source_cb_id_);
|
||||||
|
|
||||||
|
if (about_to_finish_cb_id_ != -1)
|
||||||
|
g_signal_handler_disconnect(G_OBJECT(pipeline_), about_to_finish_cb_id_);
|
||||||
|
|
||||||
gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(pipeline_)), nullptr, nullptr, nullptr);
|
gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(pipeline_)), nullptr, nullptr, nullptr);
|
||||||
|
|
||||||
if (bus_cb_id_ != -1)
|
if (bus_cb_id_ != -1)
|
||||||
@ -159,13 +160,22 @@ void GstEnginePipeline::set_output_device(const QString &output, const QVariant
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEnginePipeline::set_volume_control(bool volume_control) {
|
void GstEnginePipeline::set_volume_enabled(const bool enabled) {
|
||||||
|
volume_enabled_ = enabled;
|
||||||
volume_control_ = volume_control;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEnginePipeline::set_replaygain(bool enabled, int mode, float preamp, bool compression) {
|
void GstEnginePipeline::set_stereo_balancer_enabled(const bool enabled) {
|
||||||
|
stereo_balancer_enabled_ = enabled;
|
||||||
|
if (!enabled) stereo_balance_ = 0.0f;
|
||||||
|
if (pipeline_) UpdateStereoBalance();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GstEnginePipeline::set_equalizer_enabled(const bool enabled) {
|
||||||
|
eq_enabled_ = enabled;
|
||||||
|
if (pipeline_) UpdateEqualizer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GstEnginePipeline::set_replaygain(const bool enabled, const int mode, const float preamp, const bool compression) {
|
||||||
|
|
||||||
rg_enabled_ = enabled;
|
rg_enabled_ = enabled;
|
||||||
rg_mode_ = mode;
|
rg_mode_ = mode;
|
||||||
@ -182,6 +192,45 @@ void GstEnginePipeline::set_buffer_min_fill(int percent) {
|
|||||||
buffer_min_fill_ = percent;
|
buffer_min_fill_ = percent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GstEnginePipeline::InitFromUrl(const QByteArray &stream_url, const QUrl original_url, const qint64 end_nanosec) {
|
||||||
|
|
||||||
|
stream_url_ = stream_url;
|
||||||
|
original_url_ = original_url;
|
||||||
|
end_offset_nanosec_ = end_nanosec;
|
||||||
|
|
||||||
|
pipeline_ = engine_->CreateElement("playbin");
|
||||||
|
if (!pipeline_) return false;
|
||||||
|
|
||||||
|
g_object_set(G_OBJECT(pipeline_), "uri", stream_url.constData(), nullptr);
|
||||||
|
|
||||||
|
gint flags;
|
||||||
|
g_object_get(G_OBJECT(pipeline_), "flags", &flags, nullptr);
|
||||||
|
flags |= 0x00000002;
|
||||||
|
flags &= ~0x00000001;
|
||||||
|
g_object_set(G_OBJECT(pipeline_), "flags", flags, nullptr);
|
||||||
|
|
||||||
|
pad_added_cb_id_ = CHECKED_GCONNECT(G_OBJECT(pipeline_), "pad-added", &NewPadCallback, this);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!InitAudioBin()) return false;
|
||||||
|
|
||||||
|
// Set playbin's sink to be our custom audio-sink.
|
||||||
|
g_object_set(GST_OBJECT(pipeline_), "audio-sink", audiobin_, nullptr);
|
||||||
|
pipeline_is_connected_ = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
bool GstEnginePipeline::InitAudioBin() {
|
bool GstEnginePipeline::InitAudioBin() {
|
||||||
|
|
||||||
gst_segment_init(&last_playbin_segment_, GST_FORMAT_TIME);
|
gst_segment_init(&last_playbin_segment_, GST_FORMAT_TIME);
|
||||||
@ -239,12 +288,12 @@ bool GstEnginePipeline::InitAudioBin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create the volume elements if it's enabled.
|
// Create the volume elements if it's enabled.
|
||||||
if (volume_control_) {
|
if (volume_enabled_) {
|
||||||
volume_ = engine_->CreateElement("volume", audiobin_);
|
volume_ = engine_->CreateElement("volume", audiobin_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the stereo balancer elements if it's enabled.
|
// Create the stereo balancer elements if it's enabled.
|
||||||
if (stereo_balance_enabled_) {
|
if (stereo_balancer_enabled_) {
|
||||||
audiopanorama_ = engine_->CreateElement("audiopanorama", audiobin_, false);
|
audiopanorama_ = engine_->CreateElement("audiopanorama", audiobin_, false);
|
||||||
// Set the stereo balance.
|
// Set the stereo balance.
|
||||||
if (audiopanorama_) g_object_set(G_OBJECT(audiopanorama_), "panorama", stereo_balance_, nullptr);
|
if (audiopanorama_) g_object_set(G_OBJECT(audiopanorama_), "panorama", stereo_balance_, nullptr);
|
||||||
@ -293,13 +342,13 @@ bool GstEnginePipeline::InitAudioBin() {
|
|||||||
GstElement *eventprobe = audioqueue_;
|
GstElement *eventprobe = audioqueue_;
|
||||||
GstElement *rgvolume = nullptr;
|
GstElement *rgvolume = nullptr;
|
||||||
GstElement *rglimiter = nullptr;
|
GstElement *rglimiter = nullptr;
|
||||||
GstElement *audioconverter2 = nullptr;
|
GstElement *rgconverter = nullptr;
|
||||||
if (rg_enabled_) {
|
if (rg_enabled_) {
|
||||||
rgvolume = engine_->CreateElement("rgvolume", audiobin_, false);
|
rgvolume = engine_->CreateElement("rgvolume", audiobin_, false);
|
||||||
rglimiter = engine_->CreateElement("rglimiter", audiobin_, false);
|
rglimiter = engine_->CreateElement("rglimiter", audiobin_, false);
|
||||||
audioconverter2 = engine_->CreateElement("audioconvert", audiobin_, false);
|
rgconverter = engine_->CreateElement("audioconvert", audiobin_, false);
|
||||||
if (rgvolume && rglimiter && audioconverter2) {
|
if (rgvolume && rglimiter && rgconverter) {
|
||||||
eventprobe = audioconverter2;
|
eventprobe = rgconverter;
|
||||||
// Set replaygain settings
|
// Set replaygain settings
|
||||||
g_object_set(G_OBJECT(rgvolume), "album-mode", rg_mode_, nullptr);
|
g_object_set(G_OBJECT(rgvolume), "album-mode", rg_mode_, nullptr);
|
||||||
g_object_set(G_OBJECT(rgvolume), "pre-amp", double(rg_preamp_), nullptr);
|
g_object_set(G_OBJECT(rgvolume), "pre-amp", double(rg_preamp_), nullptr);
|
||||||
@ -335,9 +384,9 @@ bool GstEnginePipeline::InitAudioBin() {
|
|||||||
GstElement *next = audioqueue_; // The next element to link from.
|
GstElement *next = audioqueue_; // The next element to link from.
|
||||||
|
|
||||||
// Link replaygain elements if enabled.
|
// Link replaygain elements if enabled.
|
||||||
if (rg_enabled_ && rgvolume && rglimiter && audioconverter2) {
|
if (rg_enabled_ && rgvolume && rglimiter && rgconverter) {
|
||||||
gst_element_link_many(next, rgvolume, rglimiter, audioconverter2, nullptr);
|
gst_element_link_many(next, rgvolume, rglimiter, rgconverter, nullptr);
|
||||||
next = audioconverter2;
|
next = rgconverter;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Link equalizer elements if enabled.
|
// Link equalizer elements if enabled.
|
||||||
@ -347,20 +396,19 @@ bool GstEnginePipeline::InitAudioBin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Link equalizer elements if enabled.
|
// Link equalizer elements if enabled.
|
||||||
if (stereo_balance_enabled_ && audiopanorama_) {
|
if (stereo_balancer_enabled_ && audiopanorama_) {
|
||||||
gst_element_link(next, audiopanorama_);
|
gst_element_link(next, audiopanorama_);
|
||||||
next = audiopanorama_;
|
next = audiopanorama_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Link volume elements if enabled.
|
// Link volume elements if enabled.
|
||||||
if (volume_control_ && volume_) {
|
if (volume_enabled_ && volume_) {
|
||||||
gst_element_link(next, volume_);
|
gst_element_link(next, volume_);
|
||||||
next = volume_;
|
next = volume_;
|
||||||
}
|
}
|
||||||
|
|
||||||
gst_element_link(next, audioconverter);
|
gst_element_link(next, audioconverter);
|
||||||
|
|
||||||
// Let the audio output of the tee autonegotiate the bit depth and format.
|
|
||||||
GstCaps *caps = gst_caps_new_empty_simple("audio/x-raw");
|
GstCaps *caps = gst_caps_new_empty_simple("audio/x-raw");
|
||||||
gst_element_link_filtered(audioconverter, audiosink, caps);
|
gst_element_link_filtered(audioconverter, audiosink, caps);
|
||||||
gst_caps_unref(caps);
|
gst_caps_unref(caps);
|
||||||
@ -386,42 +434,225 @@ bool GstEnginePipeline::InitAudioBin() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GstEnginePipeline::InitFromUrl(const QByteArray &stream_url, const QUrl original_url, const qint64 end_nanosec) {
|
GstPadProbeReturn GstEnginePipeline::EventHandoffCallback(GstPad*, GstPadProbeInfo *info, gpointer self) {
|
||||||
|
|
||||||
stream_url_ = stream_url;
|
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
||||||
original_url_ = original_url;
|
|
||||||
end_offset_nanosec_ = end_nanosec;
|
|
||||||
|
|
||||||
pipeline_ = engine_->CreateElement("playbin");
|
GstEvent *e = gst_pad_probe_info_get_event(info);
|
||||||
if (!pipeline_) return false;
|
|
||||||
|
|
||||||
g_object_set(G_OBJECT(pipeline_), "uri", stream_url.constData(), nullptr);
|
qLog(Debug) << instance->id() << "event" << GST_EVENT_TYPE_NAME(e);
|
||||||
|
|
||||||
gint flags;
|
switch (GST_EVENT_TYPE(e)) {
|
||||||
g_object_get(G_OBJECT(pipeline_), "flags", &flags, nullptr);
|
case GST_EVENT_SEGMENT:
|
||||||
flags |= 0x00000002;
|
if (!instance->segment_start_received_) {
|
||||||
flags &= ~0x00000001;
|
// The segment start time is used to calculate the proper offset of data buffers from the start of the stream
|
||||||
g_object_set(G_OBJECT(pipeline_), "flags", flags, nullptr);
|
const GstSegment *segment = nullptr;
|
||||||
|
gst_event_parse_segment(e, &segment);
|
||||||
|
instance->segment_start_ = segment->start;
|
||||||
|
instance->segment_start_received_ = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
about_to_finish_cb_id_ = CHECKED_GCONNECT(G_OBJECT(pipeline_), "about-to-finish", &AboutToFinishCallback, this);
|
default:
|
||||||
pad_added_cb_id_ = CHECKED_GCONNECT(G_OBJECT(pipeline_), "pad-added", &NewPadCallback, this);
|
break;
|
||||||
notify_source_cb_id_ = CHECKED_GCONNECT(G_OBJECT(pipeline_), "notify::source", &SourceSetupCallback, this);
|
|
||||||
|
|
||||||
// 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_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!InitAudioBin()) return false;
|
return GST_PAD_PROBE_OK;
|
||||||
|
|
||||||
// Set playbin's sink to be our costum audio-sink.
|
}
|
||||||
g_object_set(GST_OBJECT(pipeline_), "audio-sink", audiobin_, nullptr);
|
|
||||||
pipeline_is_connected_ = true;
|
|
||||||
|
|
||||||
return true;
|
void GstEnginePipeline::SourceSetupCallback(GstPlayBin *bin, GParamSpec *, gpointer self) {
|
||||||
|
|
||||||
|
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
||||||
|
|
||||||
|
GstElement *element = nullptr;
|
||||||
|
g_object_get(bin, "source", &element, nullptr);
|
||||||
|
if (!element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_object_class_find_property(G_OBJECT_GET_CLASS(element), "device") && !instance->source_device().isEmpty()) {
|
||||||
|
// Gstreamer is not able to handle device in URL (referring to Gstreamer documentation, this might be added in the future).
|
||||||
|
// Despite that, for now we include device inside URL: we decompose it during Init and set device here, when this callback is called.
|
||||||
|
g_object_set(element, "device", instance->source_device().toLocal8Bit().constData(), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_object_class_find_property(G_OBJECT_GET_CLASS(element), "user-agent")) {
|
||||||
|
QString user_agent = QString("%1 %2").arg(QCoreApplication::applicationName(), QCoreApplication::applicationVersion());
|
||||||
|
g_object_set(element, "user-agent", user_agent.toUtf8().constData(), nullptr);
|
||||||
|
g_object_set(element, "ssl-strict", FALSE, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the pipeline was buffering we stop that now.
|
||||||
|
if (instance->buffering_) {
|
||||||
|
instance->buffering_ = false;
|
||||||
|
emit instance->BufferingFinished();
|
||||||
|
instance->SetState(GST_STATE_PLAYING);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_object_unref(element);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void GstEnginePipeline::NewPadCallback(GstElement*, GstPad *pad, gpointer self) {
|
||||||
|
|
||||||
|
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
||||||
|
|
||||||
|
GstPad *const audiopad = gst_element_get_static_pad(instance->audiobin_, "sink");
|
||||||
|
|
||||||
|
// Link playbin's sink pad to audiobin's src pad.
|
||||||
|
if (GST_PAD_IS_LINKED(audiopad)) {
|
||||||
|
qLog(Warning) << instance->id() << "audiopad is already linked, unlinking old pad";
|
||||||
|
gst_pad_unlink(audiopad, GST_PAD_PEER(audiopad));
|
||||||
|
}
|
||||||
|
|
||||||
|
gst_pad_link(pad, audiopad);
|
||||||
|
gst_object_unref(audiopad);
|
||||||
|
|
||||||
|
// Offset the timestamps on all the buffers coming out of the playbin so they line up exactly with the end of the last buffer from the old playbin.
|
||||||
|
// "Running time" is the time since the last flushing seek.
|
||||||
|
GstClockTime running_time = gst_segment_to_running_time(&instance->last_playbin_segment_, GST_FORMAT_TIME, instance->last_playbin_segment_.position);
|
||||||
|
gst_pad_set_offset(pad, running_time);
|
||||||
|
|
||||||
|
// Add a probe to the pad so we can update last_playbin_segment_.
|
||||||
|
gst_pad_add_probe(pad, static_cast<GstPadProbeType>(GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH), PlaybinProbe, instance, nullptr);
|
||||||
|
|
||||||
|
instance->pipeline_is_connected_ = true;
|
||||||
|
if (instance->pending_seek_nanosec_ != -1 && instance->pipeline_is_initialised_) {
|
||||||
|
QMetaObject::invokeMethod(instance, "Seek", Qt::QueuedConnection, Q_ARG(qint64, instance->pending_seek_nanosec_));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
GstPadProbeReturn GstEnginePipeline::PlaybinProbe(GstPad *pad, GstPadProbeInfo *info, gpointer data) {
|
||||||
|
|
||||||
|
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(data);
|
||||||
|
|
||||||
|
const GstPadProbeType info_type = GST_PAD_PROBE_INFO_TYPE(info);
|
||||||
|
|
||||||
|
if (info_type & GST_PAD_PROBE_TYPE_BUFFER) {
|
||||||
|
// The playbin produced a buffer. Record its end time, so we can offset the buffers produced by the next playbin when transitioning to the next song.
|
||||||
|
GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER(info);
|
||||||
|
|
||||||
|
GstClockTime timestamp = GST_BUFFER_TIMESTAMP(buffer);
|
||||||
|
GstClockTime duration = GST_BUFFER_DURATION(buffer);
|
||||||
|
if (timestamp == GST_CLOCK_TIME_NONE) {
|
||||||
|
timestamp = instance->last_playbin_segment_.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duration != GST_CLOCK_TIME_NONE) {
|
||||||
|
timestamp += duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->last_playbin_segment_.position = timestamp;
|
||||||
|
}
|
||||||
|
else if (info_type & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM) {
|
||||||
|
GstEvent *event = GST_PAD_PROBE_INFO_EVENT(info);
|
||||||
|
GstEventType event_type = GST_EVENT_TYPE(event);
|
||||||
|
|
||||||
|
if (event_type == GST_EVENT_SEGMENT) {
|
||||||
|
// A new segment started, we need to save this to calculate running time offsets later.
|
||||||
|
gst_event_copy_segment(event, &instance->last_playbin_segment_);
|
||||||
|
}
|
||||||
|
else if (event_type == GST_EVENT_FLUSH_START) {
|
||||||
|
// A flushing seek resets the running time to 0, so remove any offset we set on this pad before.
|
||||||
|
gst_pad_set_offset(pad, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return GST_PAD_PROBE_OK;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
GstPadProbeReturn GstEnginePipeline::HandoffCallback(GstPad *pad, GstPadProbeInfo *info, gpointer self) {
|
||||||
|
|
||||||
|
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
||||||
|
|
||||||
|
GstCaps *caps = gst_pad_get_current_caps(pad);
|
||||||
|
GstStructure *structure = gst_caps_get_structure(caps, 0);
|
||||||
|
QString format = QString(gst_structure_get_string(structure, "format"));
|
||||||
|
int channels = 0;
|
||||||
|
int rate = 0;
|
||||||
|
gst_structure_get_int(structure, "channels", &channels);
|
||||||
|
gst_structure_get_int(structure, "rate", &rate);
|
||||||
|
|
||||||
|
GstBuffer *buf = gst_pad_probe_info_get_buffer(info);
|
||||||
|
GstBuffer *buf16 = nullptr;
|
||||||
|
|
||||||
|
if (format.startsWith("S32")) {
|
||||||
|
|
||||||
|
GstMapInfo map_info;
|
||||||
|
gst_buffer_map(buf, &map_info, GST_MAP_READ);
|
||||||
|
|
||||||
|
int32_t *s = (int32_t*) map_info.data;
|
||||||
|
int samples = (map_info.size / sizeof(int32_t)) / channels;
|
||||||
|
int buf16_size = samples * sizeof(int16_t) * channels;
|
||||||
|
int16_t *d = (int16_t*) g_malloc(buf16_size);
|
||||||
|
memset(d, 0, buf16_size);
|
||||||
|
for (int i = 0 ; i < (samples * 2) ; ++i) {
|
||||||
|
d[i] = (int16_t) (s[i] >> 16);
|
||||||
|
}
|
||||||
|
gst_buffer_unmap(buf, &map_info);
|
||||||
|
buf16 = gst_buffer_new_wrapped(d, buf16_size);
|
||||||
|
GST_BUFFER_DURATION(buf16) = GST_FRAMES_TO_CLOCK_TIME(samples * sizeof(int16_t) * channels, rate);
|
||||||
|
buf = buf16;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<GstBufferConsumer*> consumers;
|
||||||
|
{
|
||||||
|
QMutexLocker l(&instance->buffer_consumers_mutex_);
|
||||||
|
consumers = instance->buffer_consumers_;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (GstBufferConsumer *consumer : consumers) {
|
||||||
|
gst_buffer_ref(buf);
|
||||||
|
consumer->ConsumeBuffer(buf, instance->id(), format);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf16) {
|
||||||
|
gst_buffer_unref(buf16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the end time of this buffer so we can stop playback if it's after the end time of this song.
|
||||||
|
if (instance->end_offset_nanosec_ > 0) {
|
||||||
|
quint64 start_time = GST_BUFFER_TIMESTAMP(buf) - instance->segment_start_;
|
||||||
|
quint64 duration = GST_BUFFER_DURATION(buf);
|
||||||
|
quint64 end_time = start_time + duration;
|
||||||
|
|
||||||
|
if (end_time > instance->end_offset_nanosec_) {
|
||||||
|
if (instance->has_next_valid_url() && instance->next_stream_url_ == instance->stream_url_ && instance->next_beginning_offset_nanosec_ == instance->end_offset_nanosec_) {
|
||||||
|
// The "next" song is actually the next segment of this file - so cheat and keep on playing, but just tell the Engine we've moved on.
|
||||||
|
instance->end_offset_nanosec_ = instance->next_end_offset_nanosec_;
|
||||||
|
instance->next_stream_url_.clear();
|
||||||
|
instance->next_original_url_.clear();
|
||||||
|
instance->next_beginning_offset_nanosec_ = 0;
|
||||||
|
instance->next_end_offset_nanosec_ = 0;
|
||||||
|
|
||||||
|
// GstEngine will try to seek to the start of the new section, but we're already there so ignore it.
|
||||||
|
instance->ignore_next_seek_ = true;
|
||||||
|
emit instance->EndOfStreamReached(instance->id(), true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// There's no next song
|
||||||
|
emit instance->EndOfStreamReached(instance->id(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return GST_PAD_PROBE_OK;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void GstEnginePipeline::AboutToFinishCallback(GstPlayBin*, gpointer self) {
|
||||||
|
|
||||||
|
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
||||||
|
|
||||||
|
if (instance->has_next_valid_url() && !instance->next_uri_set_) {
|
||||||
|
// Set the next uri. When the current song ends it will be played automatically and a STREAM_START message is send to the bus.
|
||||||
|
// When the next uri is not playable an error message is send when the pipeline goes to PLAY (or PAUSE) state or immediately if it is currently in PLAY state.
|
||||||
|
instance->next_uri_set_ = true;
|
||||||
|
g_object_set(G_OBJECT(instance->pipeline_), "uri", instance->next_stream_url_.constData(), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -710,230 +941,8 @@ void GstEnginePipeline::BufferingMessageReceived(GstMessage *msg) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEnginePipeline::NewPadCallback(GstElement*, GstPad *pad, gpointer self) {
|
|
||||||
|
|
||||||
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
|
||||||
if (!instance) return;
|
|
||||||
|
|
||||||
GstPad *const audiopad = gst_element_get_static_pad(instance->audiobin_, "sink");
|
|
||||||
|
|
||||||
// Link playbin's sink pad to audiobin's src pad.
|
|
||||||
if (GST_PAD_IS_LINKED(audiopad)) {
|
|
||||||
qLog(Warning) << instance->id() << "audiopad is already linked, unlinking old pad";
|
|
||||||
gst_pad_unlink(audiopad, GST_PAD_PEER(audiopad));
|
|
||||||
}
|
|
||||||
|
|
||||||
gst_pad_link(pad, audiopad);
|
|
||||||
gst_object_unref(audiopad);
|
|
||||||
|
|
||||||
// Offset the timestamps on all the buffers coming out of the playbin so they line up exactly with the end of the last buffer from the old playbin.
|
|
||||||
// "Running time" is the time since the last flushing seek.
|
|
||||||
GstClockTime running_time = gst_segment_to_running_time(&instance->last_playbin_segment_, GST_FORMAT_TIME, instance->last_playbin_segment_.position);
|
|
||||||
gst_pad_set_offset(pad, running_time);
|
|
||||||
|
|
||||||
// Add a probe to the pad so we can update last_playbin_segment_.
|
|
||||||
gst_pad_add_probe(pad, static_cast<GstPadProbeType>(GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH), PlaybinProbe, instance, nullptr);
|
|
||||||
|
|
||||||
instance->pipeline_is_connected_ = true;
|
|
||||||
if (instance->pending_seek_nanosec_ != -1 && instance->pipeline_is_initialised_) {
|
|
||||||
QMetaObject::invokeMethod(instance, "Seek", Qt::QueuedConnection, Q_ARG(qint64, instance->pending_seek_nanosec_));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
GstPadProbeReturn GstEnginePipeline::PlaybinProbe(GstPad *pad, GstPadProbeInfo *info, gpointer data) {
|
|
||||||
|
|
||||||
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(data);
|
|
||||||
|
|
||||||
const GstPadProbeType info_type = GST_PAD_PROBE_INFO_TYPE(info);
|
|
||||||
|
|
||||||
if (info_type & GST_PAD_PROBE_TYPE_BUFFER) {
|
|
||||||
// The playbin produced a buffer. Record its end time, so we can offset the buffers produced by the next playbin when transitioning to the next song.
|
|
||||||
GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER(info);
|
|
||||||
|
|
||||||
GstClockTime timestamp = GST_BUFFER_TIMESTAMP(buffer);
|
|
||||||
GstClockTime duration = GST_BUFFER_DURATION(buffer);
|
|
||||||
if (timestamp == GST_CLOCK_TIME_NONE) {
|
|
||||||
timestamp = instance->last_playbin_segment_.position;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (duration != GST_CLOCK_TIME_NONE) {
|
|
||||||
timestamp += duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
instance->last_playbin_segment_.position = timestamp;
|
|
||||||
}
|
|
||||||
else if (info_type & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM) {
|
|
||||||
GstEvent *event = GST_PAD_PROBE_INFO_EVENT(info);
|
|
||||||
GstEventType event_type = GST_EVENT_TYPE(event);
|
|
||||||
|
|
||||||
if (event_type == GST_EVENT_SEGMENT) {
|
|
||||||
// A new segment started, we need to save this to calculate running time offsets later.
|
|
||||||
gst_event_copy_segment(event, &instance->last_playbin_segment_);
|
|
||||||
}
|
|
||||||
else if (event_type == GST_EVENT_FLUSH_START) {
|
|
||||||
// A flushing seek resets the running time to 0, so remove any offset we set on this pad before.
|
|
||||||
gst_pad_set_offset(pad, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return GST_PAD_PROBE_OK;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
GstPadProbeReturn GstEnginePipeline::HandoffCallback(GstPad *pad, GstPadProbeInfo *info, gpointer self) {
|
|
||||||
|
|
||||||
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
|
||||||
|
|
||||||
GstCaps *caps = gst_pad_get_current_caps(pad);
|
|
||||||
GstStructure *structure = gst_caps_get_structure(caps, 0);
|
|
||||||
QString format = QString(gst_structure_get_string(structure, "format"));
|
|
||||||
int channels = 0;
|
|
||||||
int rate = 0;
|
|
||||||
gst_structure_get_int(structure, "channels", &channels);
|
|
||||||
gst_structure_get_int(structure, "rate", &rate);
|
|
||||||
|
|
||||||
GstBuffer *buf = gst_pad_probe_info_get_buffer(info);
|
|
||||||
GstBuffer *buf16 = nullptr;
|
|
||||||
|
|
||||||
if (format.startsWith("S32")) {
|
|
||||||
|
|
||||||
GstMapInfo map_info;
|
|
||||||
gst_buffer_map(buf, &map_info, GST_MAP_READ);
|
|
||||||
|
|
||||||
int32_t *s = (int32_t*) map_info.data;
|
|
||||||
int samples = (map_info.size / sizeof(int32_t)) / channels;
|
|
||||||
int buf16_size = samples * sizeof(int16_t) * channels;
|
|
||||||
int16_t *d = (int16_t*) g_malloc(buf16_size);
|
|
||||||
memset(d, 0, buf16_size);
|
|
||||||
for (int i = 0 ; i < (samples * 2) ; ++i) {
|
|
||||||
d[i] = (int16_t) (s[i] >> 16);
|
|
||||||
}
|
|
||||||
gst_buffer_unmap(buf, &map_info);
|
|
||||||
buf16 = gst_buffer_new_wrapped(d, buf16_size);
|
|
||||||
GST_BUFFER_DURATION(buf16) = GST_FRAMES_TO_CLOCK_TIME(samples * sizeof(int16_t) * channels, rate);
|
|
||||||
buf = buf16;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<GstBufferConsumer*> consumers;
|
|
||||||
{
|
|
||||||
QMutexLocker l(&instance->buffer_consumers_mutex_);
|
|
||||||
consumers = instance->buffer_consumers_;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (GstBufferConsumer *consumer : consumers) {
|
|
||||||
gst_buffer_ref(buf);
|
|
||||||
consumer->ConsumeBuffer(buf, instance->id(), format);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buf16) {
|
|
||||||
gst_buffer_unref(buf16);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the end time of this buffer so we can stop playback if it's after the end time of this song.
|
|
||||||
if (instance->end_offset_nanosec_ > 0) {
|
|
||||||
quint64 start_time = GST_BUFFER_TIMESTAMP(buf) - instance->segment_start_;
|
|
||||||
quint64 duration = GST_BUFFER_DURATION(buf);
|
|
||||||
quint64 end_time = start_time + duration;
|
|
||||||
|
|
||||||
if (end_time > instance->end_offset_nanosec_) {
|
|
||||||
if (instance->has_next_valid_url() && instance->next_stream_url_ == instance->stream_url_ && instance->next_beginning_offset_nanosec_ == instance->end_offset_nanosec_) {
|
|
||||||
// The "next" song is actually the next segment of this file - so cheat and keep on playing, but just tell the Engine we've moved on.
|
|
||||||
instance->end_offset_nanosec_ = instance->next_end_offset_nanosec_;
|
|
||||||
instance->next_stream_url_.clear();
|
|
||||||
instance->next_original_url_.clear();
|
|
||||||
instance->next_beginning_offset_nanosec_ = 0;
|
|
||||||
instance->next_end_offset_nanosec_ = 0;
|
|
||||||
|
|
||||||
// GstEngine will try to seek to the start of the new section, but we're already there so ignore it.
|
|
||||||
instance->ignore_next_seek_ = true;
|
|
||||||
emit instance->EndOfStreamReached(instance->id(), true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// There's no next song
|
|
||||||
emit instance->EndOfStreamReached(instance->id(), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return GST_PAD_PROBE_OK;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
GstPadProbeReturn GstEnginePipeline::EventHandoffCallback(GstPad*, GstPadProbeInfo *info, gpointer self) {
|
|
||||||
|
|
||||||
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
|
||||||
|
|
||||||
GstEvent *e = gst_pad_probe_info_get_event(info);
|
|
||||||
|
|
||||||
qLog(Debug) << instance->id() << "event" << GST_EVENT_TYPE_NAME(e);
|
|
||||||
|
|
||||||
switch (GST_EVENT_TYPE(e)) {
|
|
||||||
case GST_EVENT_SEGMENT:
|
|
||||||
if (!instance->segment_start_received_) {
|
|
||||||
// The segment start time is used to calculate the proper offset of data buffers from the start of the stream
|
|
||||||
const GstSegment *segment = nullptr;
|
|
||||||
gst_event_parse_segment(e, &segment);
|
|
||||||
instance->segment_start_ = segment->start;
|
|
||||||
instance->segment_start_received_ = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GST_PAD_PROBE_OK;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void GstEnginePipeline::AboutToFinishCallback(GstPlayBin*, gpointer self) {
|
|
||||||
|
|
||||||
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
|
||||||
|
|
||||||
if (instance->has_next_valid_url() && !instance->next_uri_set_) {
|
|
||||||
// Set the next uri. When the current song ends it will be played automatically and a STREAM_START message is send to the bus.
|
|
||||||
// When the next uri is not playable an error message is send when the pipeline goes to PLAY (or PAUSE) state or immediately if it is currently in PLAY state.
|
|
||||||
instance->next_uri_set_ = true;
|
|
||||||
g_object_set(G_OBJECT(instance->pipeline_), "uri", instance->next_stream_url_.constData(), nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void GstEnginePipeline::SourceSetupCallback(GstPlayBin *bin, GParamSpec *, gpointer self) {
|
|
||||||
|
|
||||||
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
|
||||||
|
|
||||||
GstElement *element;
|
|
||||||
g_object_get(bin, "source", &element, nullptr);
|
|
||||||
if (!element) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (g_object_class_find_property(G_OBJECT_GET_CLASS(element), "device") && !instance->source_device().isEmpty()) {
|
|
||||||
// Gstreamer is not able to handle device in URL (referring to Gstreamer documentation, this might be added in the future).
|
|
||||||
// Despite that, for now we include device inside URL: we decompose it during Init and set device here, when this callback is called.
|
|
||||||
g_object_set(element, "device", instance->source_device().toLocal8Bit().constData(), nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (g_object_class_find_property(G_OBJECT_GET_CLASS(element), "user-agent")) {
|
|
||||||
QString user_agent = QString("%1 %2").arg(QCoreApplication::applicationName(), QCoreApplication::applicationVersion());
|
|
||||||
g_object_set(element, "user-agent", user_agent.toUtf8().constData(), nullptr);
|
|
||||||
g_object_set(element, "ssl-strict", FALSE, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the pipeline was buffering we stop that now.
|
|
||||||
if (instance->buffering_) {
|
|
||||||
instance->buffering_ = false;
|
|
||||||
emit instance->BufferingFinished();
|
|
||||||
instance->SetState(GST_STATE_PLAYING);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_object_unref(element);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
qint64 GstEnginePipeline::position() const {
|
qint64 GstEnginePipeline::position() const {
|
||||||
|
|
||||||
if (pipeline_is_initialised_)
|
if (pipeline_is_initialised_)
|
||||||
gst_element_query_position(pipeline_, GST_FORMAT_TIME, &last_known_position_ns_);
|
gst_element_query_position(pipeline_, GST_FORMAT_TIME, &last_known_position_ns_);
|
||||||
|
|
||||||
@ -942,6 +951,7 @@ qint64 GstEnginePipeline::position() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
qint64 GstEnginePipeline::length() const {
|
qint64 GstEnginePipeline::length() const {
|
||||||
|
|
||||||
gint64 value = 0;
|
gint64 value = 0;
|
||||||
gst_element_query_duration(pipeline_, GST_FORMAT_TIME, &value);
|
gst_element_query_duration(pipeline_, GST_FORMAT_TIME, &value);
|
||||||
|
|
||||||
@ -977,8 +987,6 @@ bool GstEnginePipeline::Seek(const qint64 nanosec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (next_uri_set_) {
|
if (next_uri_set_) {
|
||||||
qDebug() << "MYTODO: gstenginepipeline.seek: seeking after Transition";
|
|
||||||
|
|
||||||
pending_seek_nanosec_ = nanosec;
|
pending_seek_nanosec_ = nanosec;
|
||||||
SetState(GST_STATE_READY);
|
SetState(GST_STATE_READY);
|
||||||
return true;
|
return true;
|
||||||
@ -993,7 +1001,6 @@ bool GstEnginePipeline::Seek(const qint64 nanosec) {
|
|||||||
void GstEnginePipeline::SetVolume(const int percent) {
|
void GstEnginePipeline::SetVolume(const int percent) {
|
||||||
|
|
||||||
if (!volume_) return;
|
if (!volume_) return;
|
||||||
|
|
||||||
volume_percent_ = percent;
|
volume_percent_ = percent;
|
||||||
UpdateVolume();
|
UpdateVolume();
|
||||||
|
|
||||||
@ -1015,23 +1022,18 @@ void GstEnginePipeline::UpdateVolume() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEnginePipeline::SetStereoBalance(const bool enabled, const float value) {
|
void GstEnginePipeline::SetStereoBalance(const float value) {
|
||||||
|
|
||||||
stereo_balance_enabled_ = enabled;
|
stereo_balance_ = value;
|
||||||
if (enabled) {
|
|
||||||
stereo_balance_ = value;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
stereo_balance_ = 0.0f;
|
|
||||||
}
|
|
||||||
UpdateStereoBalance();
|
UpdateStereoBalance();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEnginePipeline::SetEqualizerEnabled(bool enabled) {
|
void GstEnginePipeline::UpdateStereoBalance() {
|
||||||
|
|
||||||
eq_enabled_ = enabled;
|
if (audiopanorama_) {
|
||||||
UpdateEqualizer();
|
g_object_set(G_OBJECT(audiopanorama_), "panorama", stereo_balance_, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1043,12 +1045,6 @@ void GstEnginePipeline::SetEqualizerParams(const int preamp, const QList<int>& b
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEnginePipeline::UpdateStereoBalance() {
|
|
||||||
if (audiopanorama_) {
|
|
||||||
g_object_set(G_OBJECT(audiopanorama_), "panorama", stereo_balance_, nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GstEnginePipeline::UpdateEqualizer() {
|
void GstEnginePipeline::UpdateEqualizer() {
|
||||||
|
|
||||||
if (!equalizer_ || !equalizer_preamp_) return;
|
if (!equalizer_ || !equalizer_preamp_) return;
|
||||||
@ -1168,10 +1164,9 @@ void GstEnginePipeline::SetNextUrl(const QByteArray &stream_url, const QUrl &ori
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEnginePipeline::StreamDiscovered(GstDiscoverer *, GstDiscovererInfo *info, GError *, gpointer self) {
|
void GstEnginePipeline::StreamDiscovered(GstDiscoverer*, GstDiscovererInfo *info, GError*, gpointer self) {
|
||||||
|
|
||||||
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
|
||||||
if (!instance) return;
|
|
||||||
|
|
||||||
QString discovered_url(gst_discoverer_info_get_uri(info));
|
QString discovered_url(gst_discoverer_info_get_uri(info));
|
||||||
|
|
||||||
|
@ -69,7 +69,9 @@ class GstEnginePipeline : public QObject {
|
|||||||
|
|
||||||
// Call these setters before Init
|
// Call these setters before Init
|
||||||
void set_output_device(const QString &sink, const QVariant &device);
|
void set_output_device(const QString &sink, const QVariant &device);
|
||||||
void set_volume_control(const bool volume_control);
|
void set_volume_enabled(const bool enabled);
|
||||||
|
void set_stereo_balancer_enabled(const bool enabled);
|
||||||
|
void set_equalizer_enabled(const bool enabled);
|
||||||
void set_replaygain(const bool enabled, const int mode, const float preamp, const bool compression);
|
void set_replaygain(const bool enabled, const int mode, const float preamp, const bool compression);
|
||||||
void set_buffer_duration_nanosec(qint64 duration_nanosec);
|
void set_buffer_duration_nanosec(qint64 duration_nanosec);
|
||||||
void set_buffer_min_fill(int percent);
|
void set_buffer_min_fill(int percent);
|
||||||
@ -85,10 +87,10 @@ class GstEnginePipeline : public QObject {
|
|||||||
// Control the music playback
|
// Control the music playback
|
||||||
QFuture<GstStateChangeReturn> SetState(const GstState state);
|
QFuture<GstStateChangeReturn> SetState(const GstState state);
|
||||||
Q_INVOKABLE bool Seek(const qint64 nanosec);
|
Q_INVOKABLE bool Seek(const qint64 nanosec);
|
||||||
void SetEqualizerEnabled(const bool enabled);
|
|
||||||
void SetEqualizerParams(const int preamp, const QList<int> &band_gains);
|
|
||||||
void SetVolume(const int percent);
|
void SetVolume(const int percent);
|
||||||
void SetStereoBalance(const bool enabled, const float value);
|
void SetStereoBalance(const float value);
|
||||||
|
void SetEqualizerParams(const int preamp, const QList<int> &band_gains);
|
||||||
|
|
||||||
void StartFader(const qint64 duration_nanosec, const QTimeLine::Direction direction = QTimeLine::Forward, const QTimeLine::CurveShape shape = QTimeLine::LinearCurve, const bool use_fudge_timer = true);
|
void StartFader(const qint64 duration_nanosec, const QTimeLine::Direction direction = QTimeLine::Forward, const QTimeLine::CurveShape shape = QTimeLine::LinearCurve, const bool use_fudge_timer = true);
|
||||||
|
|
||||||
// If this is set then it will be loaded automatically when playback finishes for gapless playback
|
// If this is set then it will be loaded automatically when playback finishes for gapless playback
|
||||||
@ -101,6 +103,7 @@ class GstEnginePipeline : public QObject {
|
|||||||
QByteArray stream_url() const { return stream_url_; }
|
QByteArray stream_url() const { return stream_url_; }
|
||||||
QUrl original_url() const { return original_url_; }
|
QUrl original_url() const { return original_url_; }
|
||||||
bool is_valid() const { return valid_; }
|
bool is_valid() const { return valid_; }
|
||||||
|
|
||||||
// Please note that this method (unlike GstEngine's.position()) is multiple-section media unaware.
|
// Please note that this method (unlike GstEngine's.position()) is multiple-section media unaware.
|
||||||
qint64 position() const;
|
qint64 position() const;
|
||||||
// Please note that this method (unlike GstEngine's.length()) is multiple-section media unaware.
|
// Please note that this method (unlike GstEngine's.length()) is multiple-section media unaware.
|
||||||
@ -119,7 +122,7 @@ class GstEnginePipeline : public QObject {
|
|||||||
public slots:
|
public slots:
|
||||||
void SetVolumeModifier(qreal mod);
|
void SetVolumeModifier(qreal mod);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void EndOfStreamReached(const int pipeline_id, const bool has_next_track);
|
void EndOfStreamReached(const int pipeline_id, const bool has_next_track);
|
||||||
void MetadataFound(const int pipeline_id, const Engine::SimpleMetaBundle &bundle);
|
void MetadataFound(const int pipeline_id, const Engine::SimpleMetaBundle &bundle);
|
||||||
// This indicates an error, delegated from GStreamer, in the pipeline.
|
// This indicates an error, delegated from GStreamer, in the pipeline.
|
||||||
@ -135,17 +138,21 @@ signals:
|
|||||||
void timerEvent(QTimerEvent*);
|
void timerEvent(QTimerEvent*);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool InitAudioBin();
|
||||||
|
|
||||||
// Static callbacks. The GstEnginePipeline instance is passed in the last argument.
|
// Static callbacks. The GstEnginePipeline instance is passed in the last argument.
|
||||||
|
static GstPadProbeReturn EventHandoffCallback(GstPad*, GstPadProbeInfo*, gpointer);
|
||||||
|
static void SourceSetupCallback(GstPlayBin*, GParamSpec* pspec, gpointer);
|
||||||
|
static void NewPadCallback(GstElement*, GstPad*, gpointer);
|
||||||
|
static GstPadProbeReturn PlaybinProbe(GstPad*, GstPadProbeInfo*, gpointer);
|
||||||
|
static GstPadProbeReturn HandoffCallback(GstPad*, GstPadProbeInfo*, gpointer);
|
||||||
|
static void AboutToFinishCallback(GstPlayBin*, gpointer);
|
||||||
static GstBusSyncReply BusCallbackSync(GstBus*, GstMessage*, gpointer);
|
static GstBusSyncReply BusCallbackSync(GstBus*, GstMessage*, gpointer);
|
||||||
static gboolean BusCallback(GstBus*, GstMessage*, gpointer);
|
static gboolean BusCallback(GstBus*, GstMessage*, gpointer);
|
||||||
static void NewPadCallback(GstElement*, GstPad*, gpointer);
|
|
||||||
static GstPadProbeReturn HandoffCallback(GstPad*, GstPadProbeInfo*, gpointer);
|
|
||||||
static GstPadProbeReturn SourceHandoffCallback(GstPad*, GstPadProbeInfo*, gpointer);
|
|
||||||
static GstPadProbeReturn EventHandoffCallback(GstPad*, GstPadProbeInfo*, gpointer);
|
|
||||||
static void AboutToFinishCallback(GstPlayBin*, gpointer);
|
|
||||||
static GstPadProbeReturn PlaybinProbe(GstPad*, GstPadProbeInfo*, gpointer);
|
|
||||||
static void SourceSetupCallback(GstPlayBin*, GParamSpec* pspec, gpointer);
|
|
||||||
static void TaskEnterCallback(GstTask*, GThread*, gpointer);
|
static void TaskEnterCallback(GstTask*, GThread*, gpointer);
|
||||||
|
static void StreamDiscovered(GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, gpointer instance);
|
||||||
|
static void StreamDiscoveryFinished(GstDiscoverer *discoverer, gpointer instance);
|
||||||
|
static QString GSTdiscovererErrorMessage(GstDiscovererResult result);
|
||||||
|
|
||||||
void TagMessageReceived(GstMessage*);
|
void TagMessageReceived(GstMessage*);
|
||||||
void ErrorMessageReceived(GstMessage*);
|
void ErrorMessageReceived(GstMessage*);
|
||||||
@ -158,15 +165,9 @@ signals:
|
|||||||
QString ParseStrTag(GstTagList *list, const char *tag) const;
|
QString ParseStrTag(GstTagList *list, const char *tag) const;
|
||||||
guint ParseUIntTag(GstTagList *list, const char *tag) const;
|
guint ParseUIntTag(GstTagList *list, const char *tag) const;
|
||||||
|
|
||||||
bool InitAudioBin();
|
|
||||||
|
|
||||||
void UpdateVolume();
|
void UpdateVolume();
|
||||||
void UpdateEqualizer();
|
|
||||||
void UpdateStereoBalance();
|
void UpdateStereoBalance();
|
||||||
|
void UpdateEqualizer();
|
||||||
static void StreamDiscovered(GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, gpointer instance);
|
|
||||||
static void StreamDiscoveryFinished(GstDiscoverer *discoverer, gpointer instance);
|
|
||||||
static QString GSTdiscovererErrorMessage(GstDiscovererResult result);
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void FaderTimelineFinished();
|
void FaderTimelineFinished();
|
||||||
@ -191,21 +192,21 @@ signals:
|
|||||||
bool valid_;
|
bool valid_;
|
||||||
QString output_;
|
QString output_;
|
||||||
QVariant device_;
|
QVariant device_;
|
||||||
bool volume_control_;
|
bool volume_enabled_;
|
||||||
|
bool stereo_balancer_enabled_;
|
||||||
|
bool eq_enabled_;
|
||||||
|
bool rg_enabled_;
|
||||||
|
|
||||||
// Stereo balance.
|
// Stereo balance:
|
||||||
// From -1.0 - 1.0
|
// From -1.0 - 1.0
|
||||||
// -1.0 is left, 1.0 is right.
|
// -1.0 is left, 1.0 is right.
|
||||||
bool stereo_balance_enabled_;
|
|
||||||
float stereo_balance_;
|
float stereo_balance_;
|
||||||
|
|
||||||
// Equalizer
|
// Equalizer
|
||||||
bool eq_enabled_;
|
|
||||||
int eq_preamp_;
|
int eq_preamp_;
|
||||||
QList<int> eq_band_gains_;
|
QList<int> eq_band_gains_;
|
||||||
|
|
||||||
// ReplayGain
|
// ReplayGain
|
||||||
bool rg_enabled_;
|
|
||||||
int rg_mode_;
|
int rg_mode_;
|
||||||
float rg_preamp_;
|
float rg_preamp_;
|
||||||
bool rg_compression_;
|
bool rg_compression_;
|
||||||
@ -276,9 +277,9 @@ signals:
|
|||||||
GstElement *equalizer_preamp_;
|
GstElement *equalizer_preamp_;
|
||||||
GstDiscoverer *discoverer_;
|
GstDiscoverer *discoverer_;
|
||||||
|
|
||||||
int about_to_finish_cb_id_;
|
|
||||||
int pad_added_cb_id_;
|
int pad_added_cb_id_;
|
||||||
int notify_source_cb_id_;
|
int notify_source_cb_id_;
|
||||||
|
int about_to_finish_cb_id_;
|
||||||
int bus_cb_id_;
|
int bus_cb_id_;
|
||||||
int discovery_finished_cb_id_;
|
int discovery_finished_cb_id_;
|
||||||
int discovery_discovered_cb_id_;
|
int discovery_discovered_cb_id_;
|
||||||
|
@ -74,16 +74,14 @@ Equalizer::Equalizer(QWidget *parent)
|
|||||||
// Must be done before the signals are connected
|
// Must be done before the signals are connected
|
||||||
ReloadSettings();
|
ReloadSettings();
|
||||||
|
|
||||||
connect(ui_->enable, SIGNAL(toggled(bool)), SIGNAL(EnabledChanged(bool)));
|
connect(ui_->enable_equalizer, SIGNAL(toggled(bool)), SLOT(EqualizerEnabledChangedSlot(bool)));
|
||||||
connect(ui_->enable, SIGNAL(toggled(bool)), ui_->slider_container, SLOT(setEnabled(bool)));
|
|
||||||
connect(ui_->enable, SIGNAL(toggled(bool)), SLOT(Save()));
|
|
||||||
connect(ui_->preset, SIGNAL(currentIndexChanged(int)), SLOT(PresetChanged(int)));
|
connect(ui_->preset, SIGNAL(currentIndexChanged(int)), SLOT(PresetChanged(int)));
|
||||||
connect(ui_->preset_save, SIGNAL(clicked()), SLOT(SavePreset()));
|
connect(ui_->preset_save, SIGNAL(clicked()), SLOT(SavePreset()));
|
||||||
connect(ui_->preset_del, SIGNAL(clicked()), SLOT(DelPreset()));
|
connect(ui_->preset_del, SIGNAL(clicked()), SLOT(DelPreset()));
|
||||||
|
|
||||||
connect(ui_->enable_stereo_balancer, SIGNAL(toggled(bool)), SLOT(Save()));
|
connect(ui_->enable_stereo_balancer, SIGNAL(toggled(bool)), SLOT(StereoBalancerEnabledChangedSlot(bool)));
|
||||||
connect(ui_->enable_stereo_balancer, SIGNAL(toggled(bool)), ui_->balance_slider, SLOT(setEnabled(bool)));
|
connect(ui_->stereo_balance_slider, SIGNAL(valueChanged(int)), SLOT(StereoBalanceSliderChanged(int)));
|
||||||
connect(ui_->balance_slider, SIGNAL(valueChanged(int)), SLOT(StereoSliderChanged(int)));
|
|
||||||
|
|
||||||
QShortcut *close = new QShortcut(QKeySequence::Close, this);
|
QShortcut *close = new QShortcut(QKeySequence::Close, this);
|
||||||
connect(close, SIGNAL(activated()), SLOT(close()));
|
connect(close, SIGNAL(activated()), SLOT(close()));
|
||||||
@ -119,16 +117,16 @@ void Equalizer::ReloadSettings() {
|
|||||||
if (selected_index != -1) ui_->preset->setCurrentIndex(selected_index);
|
if (selected_index != -1) ui_->preset->setCurrentIndex(selected_index);
|
||||||
|
|
||||||
// Enabled?
|
// Enabled?
|
||||||
ui_->enable->setChecked(s.value("enabled", false).toBool());
|
ui_->enable_equalizer->setChecked(s.value("enabled", false).toBool());
|
||||||
ui_->slider_container->setEnabled(ui_->enable->isChecked());
|
ui_->slider_container->setEnabled(ui_->enable_equalizer->isChecked());
|
||||||
|
|
||||||
ui_->enable_stereo_balancer->setChecked(s.value("enable_stereo_balancer", false).toBool());
|
ui_->enable_stereo_balancer->setChecked(s.value("enable_stereo_balancer", false).toBool());
|
||||||
ui_->slider_label_layout->setEnabled(ui_->enable_stereo_balancer->isChecked());
|
ui_->slider_label_layout->setEnabled(ui_->enable_stereo_balancer->isChecked());
|
||||||
ui_->balance_slider->setEnabled(ui_->enable_stereo_balancer->isChecked());
|
ui_->stereo_balance_slider->setEnabled(ui_->enable_stereo_balancer->isChecked());
|
||||||
|
|
||||||
int stereo_balance = s.value("stereo_balance", 0).toInt();
|
int stereo_balance = s.value("stereo_balance", 0).toInt();
|
||||||
ui_->balance_slider->setValue(stereo_balance);
|
ui_->stereo_balance_slider->setValue(stereo_balance);
|
||||||
StereoSliderChanged(stereo_balance);
|
StereoBalanceSliderChanged(stereo_balance);
|
||||||
|
|
||||||
PresetChanged(selected_preset);
|
PresetChanged(selected_preset);
|
||||||
|
|
||||||
@ -191,7 +189,7 @@ void Equalizer::PresetChanged(const QString& name) {
|
|||||||
for (int i = 0; i < kBands; ++i) gain_[i]->set_value(p.gain[i]);
|
for (int i = 0; i < kBands; ++i) gain_[i]->set_value(p.gain[i]);
|
||||||
loading_ = false;
|
loading_ = false;
|
||||||
|
|
||||||
ParametersChanged();
|
EqualizerParametersChangedSlot();
|
||||||
Save();
|
Save();
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -241,7 +239,7 @@ EqualizerSlider *Equalizer::AddSlider(const QString &label) {
|
|||||||
|
|
||||||
EqualizerSlider *ret = new EqualizerSlider(label, ui_->slider_container);
|
EqualizerSlider *ret = new EqualizerSlider(label, ui_->slider_container);
|
||||||
ui_->slider_container->layout()->addWidget(ret);
|
ui_->slider_container->layout()->addWidget(ret);
|
||||||
connect(ret, SIGNAL(ValueChanged(int)), SLOT(ParametersChanged()));
|
connect(ret, SIGNAL(ValueChanged(int)), SLOT(EqualizerParametersChangedSlot()));
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
@ -252,7 +250,7 @@ bool Equalizer::is_stereo_balancer_enabled() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Equalizer::is_equalizer_enabled() const {
|
bool Equalizer::is_equalizer_enabled() const {
|
||||||
return ui_->enable->isChecked();
|
return ui_->enable_equalizer->isChecked();
|
||||||
}
|
}
|
||||||
|
|
||||||
int Equalizer::preamp_value() const {
|
int Equalizer::preamp_value() const {
|
||||||
@ -279,13 +277,41 @@ Equalizer::Params Equalizer::current_params() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float Equalizer::stereo_balance() const {
|
float Equalizer::stereo_balance() const {
|
||||||
return qBound(-1.0f, ui_->balance_slider->value() / 100.0f, 1.0f);
|
return qBound(-1.0f, ui_->stereo_balance_slider->value() / 100.0f, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Equalizer::ParametersChanged() {
|
void Equalizer::StereoBalancerEnabledChangedSlot(const bool enabled) {
|
||||||
if (loading_) return;
|
|
||||||
|
if (!enabled) {
|
||||||
|
ui_->stereo_balance_slider->setValue(0);
|
||||||
|
emit StereoBalanceChanged(stereo_balance());
|
||||||
|
}
|
||||||
|
ui_->stereo_balance_slider->setEnabled(enabled);
|
||||||
|
emit StereoBalancerEnabledChanged(enabled);
|
||||||
|
Save();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Equalizer::StereoBalanceSliderChanged(int) {
|
||||||
|
|
||||||
|
emit StereoBalanceChanged(stereo_balance());
|
||||||
|
Save();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Equalizer::EqualizerEnabledChangedSlot(const bool enabled) {
|
||||||
|
|
||||||
|
emit EqualizerEnabledChanged(enabled);
|
||||||
|
ui_->slider_container->setEnabled(enabled);
|
||||||
|
Save();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Equalizer::EqualizerParametersChangedSlot() {
|
||||||
|
|
||||||
|
if (loading_) return;
|
||||||
|
emit EqualizerParametersChanged(preamp_value(), gain_values());
|
||||||
|
|
||||||
emit ParametersChanged(preamp_value(), gain_values());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Equalizer::Save() {
|
void Equalizer::Save() {
|
||||||
@ -307,16 +333,14 @@ void Equalizer::Save() {
|
|||||||
s.setValue("selected_preset", ui_->preset->itemData(ui_->preset->currentIndex()).toString());
|
s.setValue("selected_preset", ui_->preset->itemData(ui_->preset->currentIndex()).toString());
|
||||||
|
|
||||||
// Enabled?
|
// Enabled?
|
||||||
s.setValue("enabled", ui_->enable->isChecked());
|
s.setValue("enabled", ui_->enable_equalizer->isChecked());
|
||||||
|
|
||||||
s.setValue("enable_stereo_balancer", ui_->enable_stereo_balancer->isChecked());
|
s.setValue("enable_stereo_balancer", ui_->enable_stereo_balancer->isChecked());
|
||||||
s.setValue("stereo_balance", ui_->balance_slider->value());
|
s.setValue("stereo_balance", ui_->stereo_balance_slider->value());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Equalizer::closeEvent(QCloseEvent *e) {
|
void Equalizer::closeEvent(QCloseEvent*) {
|
||||||
|
|
||||||
Q_UNUSED(e);
|
|
||||||
|
|
||||||
QString name = ui_->preset->currentText();
|
QString name = ui_->preset->currentText();
|
||||||
if (!presets_.contains(name)) return;
|
if (!presets_.contains(name)) return;
|
||||||
@ -331,8 +355,7 @@ Equalizer::Params::Params() : preamp(0) {
|
|||||||
for (int i = 0; i < Equalizer::kBands; ++i) gain[i] = 0;
|
for (int i = 0; i < Equalizer::kBands; ++i) gain[i] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Equalizer::Params::Params(int g0, int g1, int g2, int g3, int g4, int g5, int g6, int g7, int g8, int g9, int pre)
|
Equalizer::Params::Params(int g0, int g1, int g2, int g3, int g4, int g5, int g6, int g7, int g8, int g9, int pre) : preamp(pre) {
|
||||||
: preamp(pre) {
|
|
||||||
gain[0] = g0;
|
gain[0] = g0;
|
||||||
gain[1] = g1;
|
gain[1] = g1;
|
||||||
gain[2] = g2;
|
gain[2] = g2;
|
||||||
@ -357,12 +380,6 @@ bool Equalizer::Params::operator !=(const Equalizer::Params& other) const {
|
|||||||
return ! (*this == other);
|
return ! (*this == other);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Equalizer::StereoSliderChanged(int value) {
|
|
||||||
Q_UNUSED(value);
|
|
||||||
emit StereoBalanceChanged(is_stereo_balancer_enabled(), stereo_balance());
|
|
||||||
Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
QDataStream &operator<<(QDataStream& s, const Equalizer::Params& p) {
|
QDataStream &operator<<(QDataStream& s, const Equalizer::Params& p) {
|
||||||
s << p.preamp;
|
s << p.preamp;
|
||||||
for (int i = 0; i < Equalizer::kBands; ++i) s << p.gain[i];
|
for (int i = 0; i < Equalizer::kBands; ++i) s << p.gain[i];
|
||||||
|
@ -69,21 +69,24 @@ class Equalizer : public QDialog {
|
|||||||
float stereo_balance() const;
|
float stereo_balance() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void EnabledChanged(bool enabled);
|
void StereoBalancerEnabledChanged(const bool enabled);
|
||||||
void ParametersChanged(int preamp, const QList<int> &band_gains);
|
void StereoBalanceChanged(const float balance);
|
||||||
void StereoBalanceChanged(bool enabled, float balance);
|
void EqualizerEnabledChanged(const bool enabled);
|
||||||
|
void EqualizerParametersChanged(const int preamp, const QList<int> &band_gains);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void closeEvent(QCloseEvent *);
|
void closeEvent(QCloseEvent*);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void ParametersChanged();
|
void StereoBalancerEnabledChangedSlot(const bool enabled);
|
||||||
|
void StereoBalanceSliderChanged(const int value);
|
||||||
|
void EqualizerEnabledChangedSlot(const bool enabled);
|
||||||
|
void EqualizerParametersChangedSlot();
|
||||||
void PresetChanged(const QString &name);
|
void PresetChanged(const QString &name);
|
||||||
void PresetChanged(int index);
|
void PresetChanged(int index);
|
||||||
void SavePreset();
|
void SavePreset();
|
||||||
void DelPreset();
|
void DelPreset();
|
||||||
void Save();
|
void Save();
|
||||||
void StereoSliderChanged(int value);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EqualizerSlider *AddSlider(const QString &label);
|
EqualizerSlider *AddSlider(const QString &label);
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="enable">
|
<widget class="QCheckBox" name="enable_equalizer">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Enable equalizer</string>
|
<string>Enable equalizer</string>
|
||||||
</property>
|
</property>
|
||||||
@ -133,7 +133,7 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QSlider" name="balance_slider">
|
<widget class="QSlider" name="stereo_balance_slider">
|
||||||
<property name="minimum">
|
<property name="minimum">
|
||||||
<number>-100</number>
|
<number>-100</number>
|
||||||
</property>
|
</property>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user