diff --git a/src/core/player.cpp b/src/core/player.cpp index 3b58e48a7..732b9f254 100644 --- a/src/core/player.cpp +++ b/src/core/player.cpp @@ -200,12 +200,13 @@ void Player::Init() { connect(engine_.get(), SIGNAL(MetaData(Engine::SimpleMetaBundle)), SLOT(EngineMetadataReceived(Engine::SimpleMetaBundle))); // Equalizer - qLog(Debug) << "Creating equalizer"; - connect(equalizer_, SIGNAL(ParametersChanged(int,QList)), app_->player()->engine(), SLOT(SetEqualizerParameters(int,QList))); - connect(equalizer_, SIGNAL(EnabledChanged(bool)), app_->player()->engine(), SLOT(SetEqualizerEnabled(bool))); - connect(equalizer_, SIGNAL(StereoBalanceChanged(bool, float)), app_->player()->engine(), SLOT(SetStereoBalance(bool, float))); + connect(equalizer_, SIGNAL(StereoBalancerEnabledChanged(bool)), app_->player()->engine(), SLOT(SetStereoBalancerEnabled(bool))); + connect(equalizer_, SIGNAL(StereoBalanceChanged(float)), app_->player()->engine(), SLOT(SetStereoBalance(float))); + connect(equalizer_, SIGNAL(EqualizerEnabledChanged(bool)), app_->player()->engine(), SLOT(SetEqualizerEnabled(bool))); + connect(equalizer_, SIGNAL(EqualizerParametersChanged(int, QList)), app_->player()->engine(), SLOT(SetEqualizerParameters(int, QList))); - 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_->SetEqualizerParameters(equalizer_->preamp_value(), equalizer_->gain_values()); diff --git a/src/engine/enginebase.cpp b/src/engine/enginebase.cpp index 21c825c13..5f5a5f21d 100644 --- a/src/engine/enginebase.cpp +++ b/src/engine/enginebase.cpp @@ -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; SetVolumeSW(MakeVolumeLogarithmic(value)); diff --git a/src/engine/enginebase.h b/src/engine/enginebase.h index 370f52199..9ae024828 100644 --- a/src/engine/enginebase.h +++ b/src/engine/enginebase.h @@ -102,13 +102,13 @@ public: void SetVolume(const uint value); static uint MakeVolumeLogarithmic(const uint volume); -public slots: + public slots: virtual void ReloadSettings(); -protected: + protected: void EmitAboutToEnd(); -public: + public: // Simple accessors EngineType type() const { return type_; } @@ -126,9 +126,10 @@ public: QVariant device() { return device_; } public slots: + virtual void SetStereoBalancerEnabled(const bool) {} + virtual void SetStereoBalance(const float) {} virtual void SetEqualizerEnabled(const bool) {} - virtual void SetEqualizerParameters(const int preamp, const QList &bandGains) { Q_UNUSED(preamp); Q_UNUSED(bandGains); } - virtual void SetStereoBalance(const bool enabled, const float value) { Q_UNUSED(enabled); Q_UNUSED(value); } + virtual void SetEqualizerParameters(const int, const QList&) {} signals: // 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_nanosec_; -private: + private: bool about_to_end_emitted_; Q_DISABLE_COPY(Base) diff --git a/src/engine/gstengine.cpp b/src/engine/gstengine.cpp index 6cf817e19..f04d5d703 100644 --- a/src/engine/gstengine.cpp +++ b/src/engine/gstengine.cpp @@ -85,6 +85,8 @@ GstEngine::GstEngine(TaskManager *task_manager) latest_buffer_(nullptr), stereo_balancer_enabled_(false), stereo_balance_(0.0f), + equalizer_enabled_(false), + equalizer_preamp_(0), seek_timer_(new QTimer(this)), timer_id_(-1), next_element_id_(0), @@ -103,12 +105,15 @@ GstEngine::GstEngine(TaskManager *task_manager) } GstEngine::~GstEngine() { + EnsureInitialised(); current_pipeline_.reset(); + if (latest_buffer_) { gst_buffer_unref(latest_buffer_); latest_buffer_ = nullptr; } + } bool GstEngine::Init() { @@ -175,7 +180,7 @@ bool GstEngine::Load(const QUrl &stream_url, const QUrl &original_url, Engine::T current_pipeline_ = pipeline; SetVolume(volume_); - SetStereoBalance(stereo_balancer_enabled_, stereo_balance_); + SetStereoBalance(stereo_balance_); SetEqualizerParameters(equalizer_preamp_, equalizer_gains_); // 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) { 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 &band_gains) { @@ -437,27 +456,22 @@ void GstEngine::SetEqualizerParameters(const int preamp, const QList &band_ equalizer_preamp_ = preamp; equalizer_gains_ = band_gains; - if (current_pipeline_) - 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); + if (current_pipeline_) current_pipeline_->SetEqualizerParams(preamp, band_gains); } void GstEngine::AddBufferConsumer(GstBufferConsumer *consumer) { + buffer_consumers_ << consumer; if (current_pipeline_) current_pipeline_->AddBufferConsumer(consumer); + } void GstEngine::RemoveBufferConsumer(GstBufferConsumer *consumer) { + buffer_consumers_.removeAll(consumer); if (current_pipeline_) current_pipeline_->RemoveBufferConsumer(consumer); + } void GstEngine::timerEvent(QTimerEvent *e) { @@ -745,11 +759,12 @@ shared_ptr GstEngine::CreatePipeline() { shared_ptr ret(new GstEnginePipeline(this)); 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_buffer_duration_nanosec(buffer_duration_nanosec_); ret->set_buffer_min_fill(buffer_min_fill_); - ret->SetEqualizerEnabled(equalizer_enabled_); ret->AddBufferConsumer(this); for (GstBufferConsumer *consumer : buffer_consumers_) { diff --git a/src/engine/gstengine.h b/src/engine/gstengine.h index b305e8345..3579a4372 100644 --- a/src/engine/gstengine.h +++ b/src/engine/gstengine.h @@ -95,15 +95,18 @@ class GstEngine : public Engine::Base, public GstBufferConsumer { public slots: 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); - /** 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 &bandGains); - /** Set Stereo balance, range -1.0f..1.0f */ - void SetStereoBalance(const bool enabled, const float value); - void AddBufferConsumer(GstBufferConsumer *consumer); void RemoveBufferConsumer(GstBufferConsumer *consumer); @@ -173,6 +176,7 @@ class GstEngine : public Engine::Base, public GstBufferConsumer { bool stereo_balancer_enabled_; float stereo_balance_; + bool equalizer_enabled_; int equalizer_preamp_; QList equalizer_gains_; diff --git a/src/engine/gstenginepipeline.cpp b/src/engine/gstenginepipeline.cpp index 1bdc34517..893e600ef 100644 --- a/src/engine/gstenginepipeline.cpp +++ b/src/engine/gstenginepipeline.cpp @@ -67,12 +67,12 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine) engine_(engine), id_(sId++), valid_(false), - volume_control_(true), - stereo_balance_enabled_(false), - stereo_balance_(0.0f), + volume_enabled_(true), + stereo_balancer_enabled_(false), eq_enabled_(false), - eq_preamp_(0), rg_enabled_(false), + stereo_balance_(0.0f), + eq_preamp_(0), rg_mode_(0), rg_preamp_(0.0), rg_compression_(true), @@ -89,9 +89,10 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine) pipeline_is_initialised_(false), pipeline_is_connected_(false), pending_seek_nanosec_(-1), + last_known_position_ns_(0), next_uri_set_(false), volume_percent_(100), - volume_modifier_(1.0), + volume_modifier_(1.0f), use_fudge_timer_(false), pipeline_(nullptr), audiobin_(nullptr), @@ -101,9 +102,9 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine) equalizer_(nullptr), equalizer_preamp_(nullptr), discoverer_(nullptr), - about_to_finish_cb_id_(-1), pad_added_cb_id_(-1), notify_source_cb_id_(-1), + about_to_finish_cb_id_(-1), bus_cb_id_(-1), discovery_finished_cb_id_(-1), discovery_discovered_cb_id_(-1) @@ -131,15 +132,15 @@ GstEnginePipeline::~GstEnginePipeline() { 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) g_signal_handler_disconnect(G_OBJECT(pipeline_), pad_added_cb_id_); if (notify_source_cb_id_ != -1) 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); 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) { - - volume_control_ = volume_control; - +void GstEnginePipeline::set_volume_enabled(const bool enabled) { + volume_enabled_ = enabled; } -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_mode_ = mode; @@ -182,6 +192,45 @@ void GstEnginePipeline::set_buffer_min_fill(int 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() { gst_segment_init(&last_playbin_segment_, GST_FORMAT_TIME); @@ -239,12 +288,12 @@ bool GstEnginePipeline::InitAudioBin() { } // Create the volume elements if it's enabled. - if (volume_control_) { + if (volume_enabled_) { volume_ = engine_->CreateElement("volume", audiobin_); } // Create the stereo balancer elements if it's enabled. - if (stereo_balance_enabled_) { + if (stereo_balancer_enabled_) { audiopanorama_ = engine_->CreateElement("audiopanorama", audiobin_, false); // Set the stereo balance. if (audiopanorama_) g_object_set(G_OBJECT(audiopanorama_), "panorama", stereo_balance_, nullptr); @@ -293,13 +342,13 @@ bool GstEnginePipeline::InitAudioBin() { GstElement *eventprobe = audioqueue_; GstElement *rgvolume = nullptr; GstElement *rglimiter = nullptr; - GstElement *audioconverter2 = nullptr; + GstElement *rgconverter = nullptr; if (rg_enabled_) { rgvolume = engine_->CreateElement("rgvolume", audiobin_, false); rglimiter = engine_->CreateElement("rglimiter", audiobin_, false); - audioconverter2 = engine_->CreateElement("audioconvert", audiobin_, false); - if (rgvolume && rglimiter && audioconverter2) { - eventprobe = audioconverter2; + rgconverter = engine_->CreateElement("audioconvert", audiobin_, false); + if (rgvolume && rglimiter && rgconverter) { + eventprobe = rgconverter; // Set replaygain settings g_object_set(G_OBJECT(rgvolume), "album-mode", rg_mode_, 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. // Link replaygain elements if enabled. - if (rg_enabled_ && rgvolume && rglimiter && audioconverter2) { - gst_element_link_many(next, rgvolume, rglimiter, audioconverter2, nullptr); - next = audioconverter2; + if (rg_enabled_ && rgvolume && rglimiter && rgconverter) { + gst_element_link_many(next, rgvolume, rglimiter, rgconverter, nullptr); + next = rgconverter; } // Link equalizer elements if enabled. @@ -347,20 +396,19 @@ bool GstEnginePipeline::InitAudioBin() { } // Link equalizer elements if enabled. - if (stereo_balance_enabled_ && audiopanorama_) { + if (stereo_balancer_enabled_ && audiopanorama_) { gst_element_link(next, audiopanorama_); next = audiopanorama_; } // Link volume elements if enabled. - if (volume_control_ && volume_) { + if (volume_enabled_ && volume_) { gst_element_link(next, volume_); next = volume_; } 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"); gst_element_link_filtered(audioconverter, audiosink, 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; - original_url_ = original_url; - end_offset_nanosec_ = end_nanosec; + GstEnginePipeline *instance = reinterpret_cast(self); - pipeline_ = engine_->CreateElement("playbin"); - if (!pipeline_) return false; + GstEvent *e = gst_pad_probe_info_get_event(info); - g_object_set(G_OBJECT(pipeline_), "uri", stream_url.constData(), nullptr); + qLog(Debug) << instance->id() << "event" << GST_EVENT_TYPE_NAME(e); - gint flags; - g_object_get(G_OBJECT(pipeline_), "flags", &flags, nullptr); - flags |= 0x00000002; - flags &= ~0x00000001; - g_object_set(G_OBJECT(pipeline_), "flags", flags, nullptr); + 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; - about_to_finish_cb_id_ = CHECKED_GCONNECT(G_OBJECT(pipeline_), "about-to-finish", &AboutToFinishCallback, this); - 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); - - // 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_); + default: + break; } - 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(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(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(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(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(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 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(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(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(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(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(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 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(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(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(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 { + if (pipeline_is_initialised_) gst_element_query_position(pipeline_, GST_FORMAT_TIME, &last_known_position_ns_); @@ -942,6 +951,7 @@ qint64 GstEnginePipeline::position() const { } qint64 GstEnginePipeline::length() const { + gint64 value = 0; gst_element_query_duration(pipeline_, GST_FORMAT_TIME, &value); @@ -977,8 +987,6 @@ bool GstEnginePipeline::Seek(const qint64 nanosec) { } if (next_uri_set_) { - qDebug() << "MYTODO: gstenginepipeline.seek: seeking after Transition"; - pending_seek_nanosec_ = nanosec; SetState(GST_STATE_READY); return true; @@ -993,7 +1001,6 @@ bool GstEnginePipeline::Seek(const qint64 nanosec) { void GstEnginePipeline::SetVolume(const int percent) { if (!volume_) return; - volume_percent_ = percent; 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; - if (enabled) { - stereo_balance_ = value; - } - else { - stereo_balance_ = 0.0f; - } + stereo_balance_ = value; UpdateStereoBalance(); } -void GstEnginePipeline::SetEqualizerEnabled(bool enabled) { +void GstEnginePipeline::UpdateStereoBalance() { - eq_enabled_ = enabled; - UpdateEqualizer(); + if (audiopanorama_) { + g_object_set(G_OBJECT(audiopanorama_), "panorama", stereo_balance_, nullptr); + } } @@ -1043,12 +1045,6 @@ void GstEnginePipeline::SetEqualizerParams(const int preamp, const QList& b } -void GstEnginePipeline::UpdateStereoBalance() { - if (audiopanorama_) { - g_object_set(G_OBJECT(audiopanorama_), "panorama", stereo_balance_, nullptr); - } -} - void GstEnginePipeline::UpdateEqualizer() { 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(self); - if (!instance) return; QString discovered_url(gst_discoverer_info_get_uri(info)); diff --git a/src/engine/gstenginepipeline.h b/src/engine/gstenginepipeline.h index d84d0e36c..29c964ed6 100644 --- a/src/engine/gstenginepipeline.h +++ b/src/engine/gstenginepipeline.h @@ -69,7 +69,9 @@ class GstEnginePipeline : public QObject { // Call these setters before Init 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_buffer_duration_nanosec(qint64 duration_nanosec); void set_buffer_min_fill(int percent); @@ -85,10 +87,10 @@ class GstEnginePipeline : public QObject { // Control the music playback QFuture SetState(const GstState state); Q_INVOKABLE bool Seek(const qint64 nanosec); - void SetEqualizerEnabled(const bool enabled); - void SetEqualizerParams(const int preamp, const QList &band_gains); 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 &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); // 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_; } QUrl original_url() const { return original_url_; } bool is_valid() const { return valid_; } + // Please note that this method (unlike GstEngine's.position()) is multiple-section media unaware. qint64 position() const; // Please note that this method (unlike GstEngine's.length()) is multiple-section media unaware. @@ -119,7 +122,7 @@ class GstEnginePipeline : public QObject { public slots: void SetVolumeModifier(qreal mod); -signals: + signals: void EndOfStreamReached(const int pipeline_id, const bool has_next_track); void MetadataFound(const int pipeline_id, const Engine::SimpleMetaBundle &bundle); // This indicates an error, delegated from GStreamer, in the pipeline. @@ -135,17 +138,21 @@ signals: void timerEvent(QTimerEvent*); private: + bool InitAudioBin(); + // 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 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 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 ErrorMessageReceived(GstMessage*); @@ -158,15 +165,9 @@ signals: QString ParseStrTag(GstTagList *list, const char *tag) const; guint ParseUIntTag(GstTagList *list, const char *tag) const; - bool InitAudioBin(); - void UpdateVolume(); - void UpdateEqualizer(); void UpdateStereoBalance(); - - static void StreamDiscovered(GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, gpointer instance); - static void StreamDiscoveryFinished(GstDiscoverer *discoverer, gpointer instance); - static QString GSTdiscovererErrorMessage(GstDiscovererResult result); + void UpdateEqualizer(); private slots: void FaderTimelineFinished(); @@ -191,21 +192,21 @@ signals: bool valid_; QString output_; 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 // -1.0 is left, 1.0 is right. - bool stereo_balance_enabled_; float stereo_balance_; // Equalizer - bool eq_enabled_; int eq_preamp_; QList eq_band_gains_; // ReplayGain - bool rg_enabled_; int rg_mode_; float rg_preamp_; bool rg_compression_; @@ -276,9 +277,9 @@ signals: GstElement *equalizer_preamp_; GstDiscoverer *discoverer_; - int about_to_finish_cb_id_; int pad_added_cb_id_; int notify_source_cb_id_; + int about_to_finish_cb_id_; int bus_cb_id_; int discovery_finished_cb_id_; int discovery_discovered_cb_id_; diff --git a/src/equalizer/equalizer.cpp b/src/equalizer/equalizer.cpp index cdf1ab495..f85c1efad 100644 --- a/src/equalizer/equalizer.cpp +++ b/src/equalizer/equalizer.cpp @@ -74,16 +74,14 @@ Equalizer::Equalizer(QWidget *parent) // Must be done before the signals are connected ReloadSettings(); - connect(ui_->enable, SIGNAL(toggled(bool)), SIGNAL(EnabledChanged(bool))); - connect(ui_->enable, SIGNAL(toggled(bool)), ui_->slider_container, SLOT(setEnabled(bool))); - connect(ui_->enable, SIGNAL(toggled(bool)), SLOT(Save())); + connect(ui_->enable_equalizer, SIGNAL(toggled(bool)), SLOT(EqualizerEnabledChangedSlot(bool))); + connect(ui_->preset, SIGNAL(currentIndexChanged(int)), SLOT(PresetChanged(int))); connect(ui_->preset_save, SIGNAL(clicked()), SLOT(SavePreset())); 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)), ui_->balance_slider, SLOT(setEnabled(bool))); - connect(ui_->balance_slider, SIGNAL(valueChanged(int)), SLOT(StereoSliderChanged(int))); + connect(ui_->enable_stereo_balancer, SIGNAL(toggled(bool)), SLOT(StereoBalancerEnabledChangedSlot(bool))); + connect(ui_->stereo_balance_slider, SIGNAL(valueChanged(int)), SLOT(StereoBalanceSliderChanged(int))); QShortcut *close = new QShortcut(QKeySequence::Close, this); connect(close, SIGNAL(activated()), SLOT(close())); @@ -119,16 +117,16 @@ void Equalizer::ReloadSettings() { if (selected_index != -1) ui_->preset->setCurrentIndex(selected_index); // Enabled? - ui_->enable->setChecked(s.value("enabled", false).toBool()); - ui_->slider_container->setEnabled(ui_->enable->isChecked()); + ui_->enable_equalizer->setChecked(s.value("enabled", false).toBool()); + ui_->slider_container->setEnabled(ui_->enable_equalizer->isChecked()); ui_->enable_stereo_balancer->setChecked(s.value("enable_stereo_balancer", false).toBool()); 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(); - ui_->balance_slider->setValue(stereo_balance); - StereoSliderChanged(stereo_balance); + ui_->stereo_balance_slider->setValue(stereo_balance); + StereoBalanceSliderChanged(stereo_balance); 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]); loading_ = false; - ParametersChanged(); + EqualizerParametersChangedSlot(); Save(); } @@ -241,7 +239,7 @@ EqualizerSlider *Equalizer::AddSlider(const QString &label) { EqualizerSlider *ret = new EqualizerSlider(label, ui_->slider_container); ui_->slider_container->layout()->addWidget(ret); - connect(ret, SIGNAL(ValueChanged(int)), SLOT(ParametersChanged())); + connect(ret, SIGNAL(ValueChanged(int)), SLOT(EqualizerParametersChangedSlot())); return ret; @@ -252,7 +250,7 @@ bool Equalizer::is_stereo_balancer_enabled() const { } bool Equalizer::is_equalizer_enabled() const { - return ui_->enable->isChecked(); + return ui_->enable_equalizer->isChecked(); } int Equalizer::preamp_value() const { @@ -279,13 +277,41 @@ Equalizer::Params Equalizer::current_params() 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() { - if (loading_) return; +void Equalizer::StereoBalancerEnabledChangedSlot(const bool enabled) { + + 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() { @@ -307,16 +333,14 @@ void Equalizer::Save() { s.setValue("selected_preset", ui_->preset->itemData(ui_->preset->currentIndex()).toString()); // 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("stereo_balance", ui_->balance_slider->value()); + s.setValue("stereo_balance", ui_->stereo_balance_slider->value()); } -void Equalizer::closeEvent(QCloseEvent *e) { - - Q_UNUSED(e); +void Equalizer::closeEvent(QCloseEvent*) { QString name = ui_->preset->currentText(); 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; } -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) { +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) { gain[0] = g0; gain[1] = g1; gain[2] = g2; @@ -357,12 +380,6 @@ bool Equalizer::Params::operator !=(const Equalizer::Params& other) const { 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) { s << p.preamp; for (int i = 0; i < Equalizer::kBands; ++i) s << p.gain[i]; diff --git a/src/equalizer/equalizer.h b/src/equalizer/equalizer.h index 36fd36790..4d539d016 100644 --- a/src/equalizer/equalizer.h +++ b/src/equalizer/equalizer.h @@ -69,21 +69,24 @@ class Equalizer : public QDialog { float stereo_balance() const; signals: - void EnabledChanged(bool enabled); - void ParametersChanged(int preamp, const QList &band_gains); - void StereoBalanceChanged(bool enabled, float balance); + void StereoBalancerEnabledChanged(const bool enabled); + void StereoBalanceChanged(const float balance); + void EqualizerEnabledChanged(const bool enabled); + void EqualizerParametersChanged(const int preamp, const QList &band_gains); protected: - void closeEvent(QCloseEvent *); + void closeEvent(QCloseEvent*); 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(int index); void SavePreset(); void DelPreset(); void Save(); - void StereoSliderChanged(int value); private: EqualizerSlider *AddSlider(const QString &label); diff --git a/src/equalizer/equalizer.ui b/src/equalizer/equalizer.ui index 1e79d6619..3b152e29e 100644 --- a/src/equalizer/equalizer.ui +++ b/src/equalizer/equalizer.ui @@ -61,7 +61,7 @@ - + Enable equalizer @@ -133,7 +133,7 @@ - + -100