From b92ec71810594b692d674978a3416f7f3a3bc5a3 Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Sat, 3 Dec 2022 03:46:59 +0100 Subject: [PATCH] Use system volume when possible Fixes #1037 --- src/core/mainwindow.cpp | 7 +- src/core/mpris2.cpp | 9 +- src/core/mpris2.h | 2 +- src/core/player.cpp | 80 ++++++++------ src/core/player.h | 19 ++-- src/engine/enginebase.cpp | 14 +-- src/engine/enginebase.h | 6 +- src/engine/gstengine.cpp | 6 +- src/engine/gstengine.h | 2 +- src/engine/gstenginepipeline.cpp | 149 ++++++++++++++++++--------- src/engine/gstenginepipeline.h | 12 ++- src/settings/backendsettingspage.cpp | 4 +- src/widgets/volumeslider.cpp | 16 +-- src/widgets/volumeslider.h | 6 +- 14 files changed, 203 insertions(+), 129 deletions(-) diff --git a/src/core/mainwindow.cpp b/src/core/mainwindow.cpp index c19828d70..d0263135c 100644 --- a/src/core/mainwindow.cpp +++ b/src/core/mainwindow.cpp @@ -400,7 +400,7 @@ MainWindow::MainWindow(Application *app, std::shared_ptr tray_ic app_->player()->Init(); EngineChanged(app_->player()->engine()->type()); const uint volume = app_->player()->GetVolume(); - ui_->volume->SetValueFromVolume(volume); + ui_->volume->SetValue(volume); VolumeChanged(volume); // Models @@ -583,7 +583,7 @@ MainWindow::MainWindow(Application *app, std::shared_ptr tray_ic ui_->stop_button->setMenu(stop_menu); // Player connections - QObject::connect(ui_->volume, &VolumeSlider::valueChanged, app_->player(), &Player::SetVolumeFromValue); + QObject::connect(ui_->volume, &VolumeSlider::valueChanged, app_->player(), &Player::SetVolumeFromSlider); QObject::connect(app_->player(), &Player::EngineChanged, this, &MainWindow::EngineChanged); QObject::connect(app_->player(), &Player::Error, this, &MainWindow::ShowErrorDialog); @@ -606,7 +606,7 @@ MainWindow::MainWindow(Application *app, std::shared_ptr tray_ic QObject::connect(app_->player(), &Player::Stopped, osd_, &OSDBase::Stopped); QObject::connect(app_->player(), &Player::PlaylistFinished, osd_, &OSDBase::PlaylistFinished); QObject::connect(app_->player(), &Player::VolumeChanged, osd_, &OSDBase::VolumeChanged); - QObject::connect(app_->player(), &Player::VolumeChanged, ui_->volume, &VolumeSlider::SetValueFromVolume); + QObject::connect(app_->player(), &Player::VolumeChanged, ui_->volume, &VolumeSlider::SetValue); QObject::connect(app_->player(), &Player::ForceShowOSD, this, &MainWindow::ForceShowOSD); QObject::connect(app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, this, &MainWindow::SongChanged); @@ -1201,6 +1201,7 @@ void MainWindow::SaveSettings() { SaveGeometry(); SavePlaybackStatus(); + app_->player()->SaveVolume(); ui_->tabs->SaveSettings(kSettingsGroup); ui_->playlist->view()->SaveSettings(); app_->scrobbler()->WriteCache(); diff --git a/src/core/mpris2.cpp b/src/core/mpris2.cpp index 22efe0a48..dee33371a 100644 --- a/src/core/mpris2.cpp +++ b/src/core/mpris2.cpp @@ -22,6 +22,7 @@ #include "config.h" #include +#include #include #include @@ -175,7 +176,9 @@ void Mpris2::EngineStateChanged(Engine::State newState) { } -void Mpris2::VolumeChanged() { EmitNotification("Volume"); } +void Mpris2::VolumeChanged() { + EmitNotification("Volume"); +} void Mpris2::ShuffleModeChanged() { EmitNotification("Shuffle"); } @@ -411,8 +414,8 @@ double Mpris2::Volume() const { return app_->player()->GetVolume() / 100.0; } -void Mpris2::SetVolume(const double value) { - app_->player()->SetVolume(static_cast(std::max(std::min(lround(value * 100.0), 100L), 0L))); +void Mpris2::SetVolume(const double volume) { + app_->player()->SetVolume(static_cast(qBound(0L, lround(volume * 100.0), 100L))); } qint64 Mpris2::Position() const { diff --git a/src/core/mpris2.h b/src/core/mpris2.h index 27dcfaed6..b6eea29ce 100644 --- a/src/core/mpris2.h +++ b/src/core/mpris2.h @@ -145,7 +145,7 @@ class Mpris2 : public QObject { void SetShuffle(bool enable); QVariantMap Metadata() const; double Volume() const; - void SetVolume(const double value); + void SetVolume(const double volume); qint64 Position() const; double MaximumRate() const; double MinimumRate() const; diff --git a/src/core/player.cpp b/src/core/player.cpp index 7e40a0ac3..2d4c20ed0 100644 --- a/src/core/player.cpp +++ b/src/core/player.cpp @@ -83,29 +83,24 @@ Player::Player(Application *app, QObject *parent) autoscroll_(Playlist::AutoScroll_Maybe), last_state_(Engine::Empty), nb_errors_received_(0), + volume_(100), volume_before_mute_(100), last_pressed_previous_(QDateTime::currentDateTime()), continue_on_error_(false), greyout_(true), menu_previousmode_(BehaviourSettingsPage::PreviousBehaviour_DontRestart), seek_step_sec_(10), - volume_control_(true), play_offset_nanosec_(0) { - settings_.beginGroup(kSettingsGroup); - QSettings s; s.beginGroup(BackendSettingsPage::kSettingsGroup); Engine::EngineType enginetype = Engine::EngineTypeFromName(s.value("engine", EngineName(Engine::GStreamer)).toString().toLower()); s.endGroup(); + CreateEngine(enginetype); } -Player::~Player() { - settings_.endGroup(); -} - Engine::EngineType Player::CreateEngine(Engine::EngineType enginetype) { Engine::EngineType use_enginetype(Engine::None); @@ -181,6 +176,7 @@ void Player::Init() { QObject::connect(engine_.get(), &EngineBase::TrackAboutToEnd, this, &Player::TrackAboutToEnd); QObject::connect(engine_.get(), &EngineBase::TrackEnded, this, &Player::TrackEnded); QObject::connect(engine_.get(), &EngineBase::MetaData, this, &Player::EngineMetadataReceived); + QObject::connect(engine_.get(), &EngineBase::VolumeChanged, this, &Player::SetVolumeFromEngine); // Equalizer QObject::connect(equalizer_, &Equalizer::StereoBalancerEnabledChanged, app_->player()->engine(), &EngineBase::SetStereoBalancerEnabled); @@ -193,17 +189,10 @@ void Player::Init() { engine_->SetEqualizerEnabled(equalizer_->is_equalizer_enabled()); engine_->SetEqualizerParameters(equalizer_->preamp_value(), equalizer_->gain_values()); - s.beginGroup(BackendSettingsPage::kSettingsGroup); - volume_control_ = s.value("volume_control", true).toBool(); - s.endGroup(); - - if (volume_control_) { - int volume = settings_.value("volume", 100).toInt(); - SetVolume(volume); - } - ReloadSettings(); + LoadVolume(); + } void Player::ReloadSettings() { @@ -220,15 +209,30 @@ void Player::ReloadSettings() { seek_step_sec_ = s.value("seek_step_sec", 10).toInt(); s.endGroup(); - s.beginGroup(BackendSettingsPage::kSettingsGroup); - bool volume_control = s.value("volume_control", true).toBool(); - if (!volume_control && GetVolume() != 100) SetVolume(100); - s.endGroup(); - engine_->ReloadSettings(); } +void Player::LoadVolume() { + + QSettings s; + s.beginGroup(kSettingsGroup); + const uint volume = s.value("volume", 100).toInt(); + s.endGroup(); + + SetVolume(volume); + +} + +void Player::SaveVolume() { + + QSettings s; + s.beginGroup(kSettingsGroup); + s.setValue("volume", volume_); + s.endGroup(); + +} + void Player::HandleLoadResult(const UrlHandler::LoadResult &result) { if (loading_async_.contains(result.original_url_)) { @@ -641,23 +645,35 @@ uint Player::GetVolume() const { } -void Player::SetVolumeFromValue(const int value) { +void Player::SetVolumeFromSlider(const int value) { - SetVolume(static_cast(std::max(0, value))); + const uint volume = static_cast(qBound(0, value, 100)); + if (volume != volume_) { + engine_->SetVolume(volume); + emit VolumeChanged(volume_); + } + +} + +void Player::SetVolumeFromEngine(const uint volume) { + + const uint new_volume = qBound(0U, volume, 100U); + if (new_volume != volume_) { + volume_ = new_volume; + emit VolumeChanged(volume_); + } } void Player::SetVolume(const uint volume) { - uint old_volume = engine_->volume(); - uint new_volume = qBound(0U, volume, 100U); - settings_.setValue("volume", new_volume); - engine_->SetVolume(new_volume); - - if (new_volume != old_volume) { - emit VolumeChanged(new_volume); + const uint new_volume = qBound(0U, volume, 100U); + if (new_volume != volume_) { + engine_->SetVolume(volume); + volume_ = new_volume; + emit VolumeChanged(volume_); } - + } void Player::VolumeUp() { @@ -794,8 +810,6 @@ PlaylistItemPtr Player::GetItemAt(const int pos) const { void Player::Mute() { - if (!volume_control_) return; - const uint current_volume = engine_->volume(); if (current_volume == 0) { diff --git a/src/core/player.h b/src/core/player.h index 23ff78dbf..9f8dc8c4b 100644 --- a/src/core/player.h +++ b/src/core/player.h @@ -32,7 +32,6 @@ #include #include #include -#include #include "urlhandler.h" #include "engine/engine_fwd.h" @@ -71,6 +70,8 @@ class PlayerInterface : public QObject { public slots: virtual void ReloadSettings() = 0; + virtual void LoadVolume() = 0; + virtual void SaveVolume() = 0; // Manual track change to the specified track virtual void PlayAt(const int index, const quint64 offset_nanosec, Engine::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform = false) = 0; @@ -84,7 +85,8 @@ class PlayerInterface : public QObject { virtual void Next() = 0; virtual void Previous() = 0; virtual void PlayPlaylist(const QString &playlist_name) = 0; - virtual void SetVolumeFromValue(const int value) = 0; + virtual void SetVolumeFromEngine(const uint volume) = 0; + virtual void SetVolumeFromSlider(const int value) = 0; virtual void SetVolume(const uint volume) = 0; virtual void VolumeUp() = 0; virtual void VolumeDown() = 0; @@ -133,7 +135,6 @@ class Player : public PlayerInterface { public: explicit Player(Application *app, QObject *parent); - ~Player() override; static const char *kSettingsGroup; @@ -159,6 +160,8 @@ class Player : public PlayerInterface { public slots: void ReloadSettings() override; + void LoadVolume() override; + void SaveVolume() override; void PlayAt(const int index, const quint64 offset_nanosec, Engine::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform = false) override; void PlayPause(const quint64 offset_nanosec = 0, const Playlist::AutoScroll autoscroll = Playlist::AutoScroll_Always) override; @@ -167,8 +170,9 @@ class Player : public PlayerInterface { void Next() override; void Previous() override; void PlayPlaylist(const QString &playlist_name) override; - void SetVolumeFromValue(const int value) override; - void SetVolume(const uint value) override; + void SetVolumeFromSlider(const int value) override; + void SetVolumeFromEngine(const uint volume) override; + void SetVolume(const uint volume) override; void VolumeUp() override; void VolumeDown() override; void SeekTo(const quint64 seconds) override; @@ -225,8 +229,6 @@ class Player : public PlayerInterface { AnalyzerContainer *analyzer_; Equalizer *equalizer_; - QSettings settings_; - PlaylistItemPtr current_item_; Engine::TrackChangeFlags stream_change_type_; @@ -237,6 +239,7 @@ class Player : public PlayerInterface { QMap url_handlers_; QList loading_async_; + uint volume_; uint volume_before_mute_; QDateTime last_pressed_previous_; @@ -245,8 +248,6 @@ class Player : public PlayerInterface { BehaviourSettingsPage::PreviousBehaviour menu_previousmode_; int seek_step_sec_; - bool volume_control_; - QDateTime pause_time_; quint64 play_offset_nanosec_; diff --git a/src/engine/enginebase.cpp b/src/engine/enginebase.cpp index 85d508945..0b60ecda5 100644 --- a/src/engine/enginebase.cpp +++ b/src/engine/enginebase.cpp @@ -99,16 +99,18 @@ bool Engine::Base::Play(const QUrl &stream_url, const QUrl &original_url, const } -void Engine::Base::SetVolume(const uint value) { +void Engine::Base::UpdateVolume(const uint volume) { - volume_ = value; - SetVolumeSW(MakeVolumeLogarithmic(value)); + volume_ = volume; + emit VolumeChanged(volume); } -uint Engine::Base::MakeVolumeLogarithmic(const uint volume) { - // We're using a logarithmic function to make the volume ramp more natural. - return static_cast(100 - 100.0 * std::log10((100 - volume) * 0.09 + 1.0)); +void Engine::Base::SetVolume(const uint volume) { + + volume_ = volume; + SetVolumeSW(volume); + } void Engine::Base::ReloadSettings() { diff --git a/src/engine/enginebase.h b/src/engine/enginebase.h index 345f9b9a9..c59604674 100644 --- a/src/engine/enginebase.h +++ b/src/engine/enginebase.h @@ -97,11 +97,11 @@ class Base : public QObject { // Plays a media stream represented with the URL 'u' from the given 'beginning' to the given 'end' (usually from 0 to a song's length). // Both markers should be passed in nanoseconds. 'end' can be negative, indicating that the real length of 'u' stream is unknown. bool Play(const QUrl &stream_url, const QUrl &original_url, const TrackChangeFlags flags, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const quint64 offset_nanosec); - void SetVolume(const uint value); - static uint MakeVolumeLogarithmic(const uint volume); + void SetVolume(const uint volume); public slots: virtual void ReloadSettings(); + void UpdateVolume(const uint volume); protected: void EmitAboutToEnd(); @@ -153,6 +153,8 @@ class Base : public QObject { // Always use the state from event, because it's not guaranteed that immediate subsequent call to state() won't return a stale value. void StateChanged(Engine::State); + void VolumeChanged(uint volume); + protected: struct PluginDetails { diff --git a/src/engine/gstengine.cpp b/src/engine/gstengine.cpp index 44449c73a..1691dd729 100644 --- a/src/engine/gstengine.cpp +++ b/src/engine/gstengine.cpp @@ -340,8 +340,8 @@ void GstEngine::Seek(const quint64 offset_nanosec) { } } -void GstEngine::SetVolumeSW(const uint percent) { - if (current_pipeline_) current_pipeline_->SetVolume(percent); +void GstEngine::SetVolumeSW(const uint volume) { + if (current_pipeline_) current_pipeline_->SetVolume(volume); } qint64 GstEngine::position_nanosec() const { @@ -802,6 +802,7 @@ std::shared_ptr GstEngine::CreatePipeline() { ret->set_proxy_settings(proxy_address_, proxy_authentication_, proxy_user_, proxy_pass_); ret->set_channels(channels_enabled_, channels_); ret->set_bs2b_enabled(bs2b_enabled_); + ret->set_fading_enabled(fadeout_enabled_ || autocrossfade_enabled_ || fadeout_pause_enabled_); ret->AddBufferConsumer(this); for (GstBufferConsumer *consumer : buffer_consumers_) { @@ -814,6 +815,7 @@ std::shared_ptr GstEngine::CreatePipeline() { QObject::connect(ret.get(), &GstEnginePipeline::BufferingStarted, this, &GstEngine::BufferingStarted); QObject::connect(ret.get(), &GstEnginePipeline::BufferingProgress, this, &GstEngine::BufferingProgress); QObject::connect(ret.get(), &GstEnginePipeline::BufferingFinished, this, &GstEngine::BufferingFinished); + QObject::connect(ret.get(), &GstEnginePipeline::VolumeChanged, this, &EngineBase::UpdateVolume); return ret; diff --git a/src/engine/gstengine.h b/src/engine/gstengine.h index 6601a343a..622733341 100644 --- a/src/engine/gstengine.h +++ b/src/engine/gstengine.h @@ -72,7 +72,7 @@ class GstEngine : public Engine::Base, public GstBufferConsumer { void Seek(const quint64 offset_nanosec) override; protected: - void SetVolumeSW(const uint percent) override; + void SetVolumeSW(const uint volume) override; public: qint64 position_nanosec() const override; diff --git a/src/engine/gstenginepipeline.cpp b/src/engine/gstenginepipeline.cpp index 884f7b04f..cee30f9a0 100644 --- a/src/engine/gstenginepipeline.cpp +++ b/src/engine/gstenginepipeline.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -70,6 +71,7 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent) stereo_balancer_enabled_(false), eq_enabled_(false), rg_enabled_(false), + fading_enabled_(false), stereo_balance_(0.0F), eq_preamp_(0), rg_mode_(0), @@ -97,18 +99,19 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent) last_known_position_ns_(0), next_uri_set_(false), volume_percent_(100), - volume_modifier_(1.0F), use_fudge_timer_(false), pipeline_(nullptr), audiobin_(nullptr), audioqueue_(nullptr), volume_(nullptr), + volume_fading_(nullptr), audiopanorama_(nullptr), equalizer_(nullptr), equalizer_preamp_(nullptr), pad_added_cb_id_(-1), notify_source_cb_id_(-1), about_to_finish_cb_id_(-1), + notify_volume_cb_id_(-1), logged_unsupported_analyzer_format_(false) { eq_band_gains_.reserve(kEqBandCount); @@ -139,6 +142,10 @@ GstEnginePipeline::~GstEnginePipeline() { g_signal_handler_disconnect(G_OBJECT(pipeline_), about_to_finish_cb_id_); } + if (notify_volume_cb_id_ != -1) { + g_signal_handler_disconnect(G_OBJECT(volume_), notify_volume_cb_id_); + } + GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline_)); if (bus) { gst_bus_remove_watch(bus); @@ -218,6 +225,10 @@ void GstEnginePipeline::set_bs2b_enabled(const bool enabled) { bs2b_enabled_ = enabled; } +void GstEnginePipeline::set_fading_enabled(const bool enabled) { + fading_enabled_ = enabled; +} + GstElement *GstEnginePipeline::CreateElement(const QString &factory_name, const QString &name, GstElement *bin, QString &error) const { QString unique_name = QString("pipeline") + "-" + QString::number(id_) + "-" + (name.isEmpty() ? factory_name : name); @@ -249,6 +260,10 @@ bool GstEnginePipeline::InitFromUrl(const QByteArray &stream_url, const QUrl &or if (!InitAudioBin(error)) return false; + if (volume_) { + notify_volume_cb_id_ = CHECKED_GCONNECT(G_OBJECT(volume_), "notify::volume", &VolumeCallback, this); + } + // Set playbin's sink to be our custom audio-sink. g_object_set(GST_OBJECT(pipeline_), "audio-sink", audiobin_, nullptr); @@ -256,14 +271,9 @@ bool GstEnginePipeline::InitFromUrl(const QByteArray &stream_url, const QUrl &or g_object_get(G_OBJECT(pipeline_), "flags", &flags, nullptr); flags |= 0x00000002; flags &= ~0x00000001; - if (volume_enabled_) { - flags |= 0x00000010; - } - else { - flags &= ~0x00000010; - } - + flags &= ~0x00000010; g_object_set(G_OBJECT(pipeline_), "flags", flags, nullptr); + g_object_set(G_OBJECT(pipeline_), "uri", stream_url.constData(), nullptr); pipeline_is_connected_ = true; @@ -394,6 +404,11 @@ bool GstEnginePipeline::InitAudioBin(QString &error) { } + if (g_object_class_find_property(G_OBJECT_GET_CLASS(audiosink), "volume")) { + qLog(Debug) << output_ << "has volume element, enabling system volume synchronization."; + volume_ = audiosink; + } + // Create all the other elements audioqueue_ = CreateElement("queue2", "audioqueue", audiobin_, error); @@ -411,9 +426,20 @@ bool GstEnginePipeline::InitAudioBin(QString &error) { } // Create the volume elements if it's enabled. - if (volume_enabled_) { - volume_ = CreateElement("volume", "volume", audiobin_, error); - if (!volume_) { + GstElement *swvolume = nullptr; + if (volume_enabled_ && !volume_) { + swvolume = CreateElement("volume", "volume_sw", audiobin_, error); + if (!swvolume) { + gst_object_unref(GST_OBJECT(audiobin_)); + audiobin_ = nullptr; + return false; + } + volume_ = swvolume; + } + + if (fading_enabled_) { + volume_fading_ = CreateElement("volume", "volume_fading", audiobin_, error); + if (!volume_fading_) { gst_object_unref(GST_OBJECT(audiobin_)); audiobin_ = nullptr; return false; @@ -561,69 +587,80 @@ bool GstEnginePipeline::InitAudioBin(QString &error) { // Link all elements - GstElement *next = audioqueue_; // The next element to link from. + if (!gst_element_link(audioqueue_, audioconverter)) { + gst_object_unref(GST_OBJECT(audiobin_)); + audiobin_ = nullptr; + error = "gst_element_link() failed."; + return false; + } + + GstElement *element_link = audioconverter; // The next element to link from. // Link replaygain elements if enabled. if (rg_enabled_ && rgvolume && rglimiter && rgconverter) { - if (!gst_element_link_many(next, rgvolume, rglimiter, rgconverter, nullptr)) { + if (!gst_element_link_many(element_link, rgvolume, rglimiter, rgconverter, nullptr)) { gst_object_unref(GST_OBJECT(audiobin_)); audiobin_ = nullptr; error = "gst_element_link_many() failed."; return false; } - next = rgconverter; + element_link = rgconverter; } // Link equalizer elements if enabled. if (eq_enabled_ && equalizer_ && equalizer_preamp_) { - if (!gst_element_link_many(next, equalizer_preamp_, equalizer_, nullptr)) { + if (!gst_element_link_many(element_link, equalizer_preamp_, equalizer_, nullptr)) { gst_object_unref(GST_OBJECT(audiobin_)); audiobin_ = nullptr; error = "gst_element_link_many() failed."; return false; } - next = equalizer_; + element_link = equalizer_; } // Link stereo balancer elements if enabled. if (stereo_balancer_enabled_ && audiopanorama_) { - if (!gst_element_link(next, audiopanorama_)) { + if (!gst_element_link(element_link, audiopanorama_)) { gst_object_unref(GST_OBJECT(audiobin_)); audiobin_ = nullptr; error = "gst_element_link() failed."; return false; } - next = audiopanorama_; + element_link = audiopanorama_; } - // Link volume elements if enabled. - if (volume_enabled_ && volume_) { - if (!gst_element_link(next, volume_)) { + // Link software volume element if enabled. + if (volume_enabled_ && swvolume) { + if (!gst_element_link(element_link, swvolume)) { gst_object_unref(GST_OBJECT(audiobin_)); audiobin_ = nullptr; error = "gst_element_link() failed."; return false; } - next = volume_; + element_link = swvolume; + } + + // Link fading volume element if enabled. + if (fading_enabled_ && volume_fading_) { + if (!gst_element_link(element_link, volume_fading_)) { + gst_object_unref(GST_OBJECT(audiobin_)); + audiobin_ = nullptr; + error = "gst_element_link() failed."; + return false; + } + element_link = volume_fading_; } // Link bs2b element if enabled. if (bs2b_enabled_ && bs2b) { qLog(Debug) << "Enabling bs2b"; - if (!gst_element_link(next, bs2b)) { + if (!gst_element_link(element_link, bs2b)) { gst_object_unref(GST_OBJECT(audiobin_)); audiobin_ = nullptr; error = "gst_element_link() failed."; return false; } - next = bs2b; - } - - if (!gst_element_link(next, audioconverter)) { - gst_object_unref(GST_OBJECT(audiobin_)); - audiobin_ = nullptr; - error = "gst_element_link() failed."; - return false; + element_link = bs2b; } { @@ -638,12 +675,12 @@ bool GstEnginePipeline::InitAudioBin(QString &error) { qLog(Debug) << "Setting channels to" << channels_; gst_caps_set_simple(caps, "channels", G_TYPE_INT, channels_, nullptr); } - gst_element_link_filtered(audioconverter, audiosink, caps); + gst_element_link_filtered(element_link, audiosink, caps); gst_caps_unref(caps); } { // Add probes and handlers. - GstPad *pad = gst_element_get_static_pad(audioqueue_, "src"); + GstPad *pad = gst_element_get_static_pad(audioconverter, "src"); if (pad) { gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_BUFFER, HandoffCallback, this, nullptr); gst_object_unref(pad); @@ -738,6 +775,21 @@ void GstEnginePipeline::SourceSetupCallback(GstPlayBin *bin, GParamSpec*, gpoint } +void GstEnginePipeline::VolumeCallback(GstElement*, GParamSpec*, gpointer self) { + + GstEnginePipeline *instance = reinterpret_cast(self); + + gdouble volume = 0; + g_object_get(G_OBJECT(instance->volume_), "volume", &volume, nullptr); + + const uint volume_percent = static_cast(qBound(0L, lround(qBound(0.0, gst_stream_volume_convert_volume(GST_STREAM_VOLUME_FORMAT_LINEAR, GST_STREAM_VOLUME_FORMAT_CUBIC, volume), 1.0) / 0.01), 100L)); + if (volume_percent != instance->volume_percent_) { + instance->volume_percent_ = volume_percent; + emit instance->VolumeChanged(volume_percent); + } + +} + void GstEnginePipeline::NewPadCallback(GstElement*, GstPad *pad, gpointer self) { GstEnginePipeline *instance = reinterpret_cast(self); @@ -1355,27 +1407,22 @@ bool GstEnginePipeline::Seek(const qint64 nanosec) { } -void GstEnginePipeline::SetVolume(const uint percent) { +void GstEnginePipeline::SetVolume(const uint volume_percent) { - if (!volume_) return; - volume_percent_ = percent; - UpdateVolume(); + if (volume_) { + const double volume = gst_stream_volume_convert_volume(GST_STREAM_VOLUME_FORMAT_CUBIC, GST_STREAM_VOLUME_FORMAT_LINEAR, static_cast(volume_percent) * 0.01); + g_object_set(G_OBJECT(volume_), "volume", volume, nullptr); + } + + volume_percent_ = volume_percent; } -void GstEnginePipeline::SetVolumeModifier(const qreal mod) { +void GstEnginePipeline::SetFaderVolume(const qreal volume) { - if (!volume_) return; - volume_modifier_ = mod; - UpdateVolume(); - -} - -void GstEnginePipeline::UpdateVolume() { - - if (!volume_) return; - double vol = static_cast(volume_percent_) * static_cast(0.01) * volume_modifier_; - g_object_set(G_OBJECT(volume_), "volume", vol, nullptr); + if (volume_fading_) { + g_object_set(G_OBJECT(volume_fading_), "volume", volume, nullptr); + } } @@ -1454,7 +1501,7 @@ void GstEnginePipeline::StartFader(const qint64 duration_nanosec, const QTimeLin } timeline->deleteLater(); }); - QObject::connect(fader_.get(), &QTimeLine::valueChanged, this, &GstEnginePipeline::SetVolumeModifier); + QObject::connect(fader_.get(), &QTimeLine::valueChanged, this, &GstEnginePipeline::SetFaderVolume); QObject::connect(fader_.get(), &QTimeLine::finished, this, &GstEnginePipeline::FaderTimelineFinished); fader_->setDirection(direction); fader_->setEasingCurve(shape); @@ -1464,7 +1511,7 @@ void GstEnginePipeline::StartFader(const qint64 duration_nanosec, const QTimeLin fader_fudge_timer_.stop(); use_fudge_timer_ = use_fudge_timer; - SetVolumeModifier(fader_->currentValue()); + SetFaderVolume(fader_->currentValue()); } diff --git a/src/engine/gstenginepipeline.h b/src/engine/gstenginepipeline.h index 62cc80b9c..069e53fac 100644 --- a/src/engine/gstenginepipeline.h +++ b/src/engine/gstenginepipeline.h @@ -74,6 +74,7 @@ class GstEnginePipeline : public QObject { void set_proxy_settings(const QString &address, const bool authentication, const QString &user, const QString &pass); void set_channels(const bool enabled, const int channels); void set_bs2b_enabled(const bool enabled); + void set_fading_enabled(const bool enabled); // Creates the pipeline, returns false on error bool InitFromUrl(const QByteArray &stream_url, const QUrl &original_url, const qint64 end_nanosec, QString &error); @@ -86,7 +87,7 @@ class GstEnginePipeline : public QObject { // Control the music playback QFuture SetState(const GstState state); Q_INVOKABLE bool Seek(const qint64 nanosec); - void SetVolume(const uint percent); + void SetVolume(const uint volume_percent); void SetStereoBalance(const float value); void SetEqualizerParams(const int preamp, const QList &band_gains); @@ -121,7 +122,7 @@ class GstEnginePipeline : public QObject { QString source_device() const { return source_device_; } public slots: - void SetVolumeModifier(qreal mod); + void SetFaderVolume(qreal mod); signals: void Error(int pipeline_id, int domain, int error_code, QString message, QString debug); @@ -129,6 +130,7 @@ class GstEnginePipeline : public QObject { void EndOfStreamReached(int pipeline_id, bool has_next_track); void MetadataFound(int pipeline_id, const Engine::SimpleMetaBundle &bundle); + void VolumeChanged(uint volume); void FaderFinished(); void BufferingStarted(); @@ -145,6 +147,7 @@ class GstEnginePipeline : public QObject { // 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 VolumeCallback(GstElement*, GParamSpec*, gpointer self); static void NewPadCallback(GstElement*, GstPad*, gpointer); static GstPadProbeReturn PlaybinProbe(GstPad*, GstPadProbeInfo*, gpointer); static GstPadProbeReturn HandoffCallback(GstPad*, GstPadProbeInfo*, gpointer); @@ -164,7 +167,6 @@ class GstEnginePipeline : public QObject { static QString ParseStrTag(GstTagList *list, const char *tag); static guint ParseUIntTag(GstTagList *list, const char *tag); - void UpdateVolume(); void UpdateStereoBalance(); void UpdateEqualizer(); @@ -190,6 +192,7 @@ class GstEnginePipeline : public QObject { bool stereo_balancer_enabled_; bool eq_enabled_; bool rg_enabled_; + bool fading_enabled_; // Stereo balance: // From -1.0 - 1.0 @@ -271,7 +274,6 @@ class GstEnginePipeline : public QObject { bool next_uri_set_; uint volume_percent_; - qreal volume_modifier_; std::shared_ptr fader_; QBasicTimer fader_fudge_timer_; @@ -281,6 +283,7 @@ class GstEnginePipeline : public QObject { GstElement *audiobin_; GstElement *audioqueue_; GstElement *volume_; + GstElement *volume_fading_; GstElement *audiopanorama_; GstElement *equalizer_; GstElement *equalizer_preamp_; @@ -288,6 +291,7 @@ class GstEnginePipeline : public QObject { int pad_added_cb_id_; int notify_source_cb_id_; int about_to_finish_cb_id_; + int notify_volume_cb_id_; QThreadPool set_state_threadpool_; diff --git a/src/settings/backendsettingspage.cpp b/src/settings/backendsettingspage.cpp index e0bf84859..d02fb084d 100644 --- a/src/settings/backendsettingspage.cpp +++ b/src/settings/backendsettingspage.cpp @@ -94,7 +94,6 @@ BackendSettingsPage::BackendSettingsPage(SettingsDialog *dialog, QWidget *parent QObject::connect(ui_->checkbox_fadeout_stop, &QCheckBox::toggled, this, &BackendSettingsPage::FadingOptionsChanged); QObject::connect(ui_->checkbox_fadeout_cross, &QCheckBox::toggled, this, &BackendSettingsPage::FadingOptionsChanged); QObject::connect(ui_->checkbox_fadeout_auto, &QCheckBox::toggled, this, &BackendSettingsPage::FadingOptionsChanged); - QObject::connect(ui_->checkbox_volume_control, &QCheckBox::toggled, this, &BackendSettingsPage::FadingOptionsChanged); QObject::connect(ui_->checkbox_channels, &QCheckBox::toggled, ui_->widget_channels, &QSpinBox::setEnabled); QObject::connect(ui_->button_buffer_defaults, &QPushButton::clicked, this, &BackendSettingsPage::BufferDefaults); @@ -800,8 +799,7 @@ void BackendSettingsPage::FadingOptionsChanged() { EngineBase::OutputDetails output = ui_->combobox_output->itemData(ui_->combobox_output->currentIndex()).value(); if (engine()->type() == Engine::GStreamer && - !(engine()->ALSADeviceSupport(output.name) && !ui_->lineedit_device->text().isEmpty() && (ui_->lineedit_device->text().contains(QRegularExpression("^hw:.*")) || ui_->lineedit_device->text().contains(QRegularExpression("^plughw:.*")))) && - ui_->checkbox_volume_control->isChecked()) { + !(engine()->ALSADeviceSupport(output.name) && !ui_->lineedit_device->text().isEmpty() && (ui_->lineedit_device->text().contains(QRegularExpression("^hw:.*")) || ui_->lineedit_device->text().contains(QRegularExpression("^plughw:.*"))))) { ui_->groupbox_fading->setEnabled(true); } else { diff --git a/src/widgets/volumeslider.cpp b/src/widgets/volumeslider.cpp index e0f79c3de..c49664ce6 100644 --- a/src/widgets/volumeslider.cpp +++ b/src/widgets/volumeslider.cpp @@ -68,7 +68,7 @@ void SliderSlider::wheelEvent(QWheelEvent *e) { QSlider::setValue(nval); - emit sliderReleased(value()); + emit SliderReleased(value()); } @@ -133,7 +133,7 @@ void SliderSlider::mousePressEvent(QMouseEvent *e) { void SliderSlider::mouseReleaseEvent(QMouseEvent*) { if (!outside_ && QSlider::value() != prev_value_) { - emit sliderReleased(value()); + emit SliderReleased(value()); } sliding_ = false; @@ -141,7 +141,7 @@ void SliderSlider::mouseReleaseEvent(QMouseEvent*) { } -void SliderSlider::SetValueFromVolume(const uint value) { +void SliderSlider::SetValue(const uint value) { setValue(static_cast(value)); @@ -298,21 +298,21 @@ void VolumeSlider::contextMenuEvent(QContextMenuEvent *e) { QAction *ret = menu.exec(mapToGlobal(e->pos())); if (ret) { - QSlider::setValue(values[ret]); // clazy:exclude=skipped-base-method - emit sliderReleased(values[ret]); + QSlider::setValue(values[ret]); + emit SliderReleased(values[ret]); } } void VolumeSlider::slideEvent(QMouseEvent *e) { - QSlider::setValue(QStyle::sliderValueFromPosition(minimum(), maximum(), e->pos().x(), width() - 2)); // clazy:exclude=skipped-base-method + QSlider::setValue(QStyle::sliderValueFromPosition(minimum(), maximum(), e->pos().x(), width() - 2)); } void VolumeSlider::wheelEvent(QWheelEvent *e) { const int step = e->angleDelta().y() / (e->angleDelta().x() == 0 ? 30 : -30); - QSlider::setValue(SliderSlider::value() + step); // clazy:exclude=skipped-base-method - emit sliderReleased(value()); + QSlider::setValue(SliderSlider::value() + step); + emit SliderReleased(value()); } diff --git a/src/widgets/volumeslider.h b/src/widgets/volumeslider.h index 9b74bdfc7..a0305bbd0 100644 --- a/src/widgets/volumeslider.h +++ b/src/widgets/volumeslider.h @@ -44,15 +44,15 @@ class SliderSlider : public QSlider { public: explicit SliderSlider(const Qt::Orientation, QWidget*, const int max = 0); - virtual void SetValueFromVolume(const uint value); + virtual void SetValue(const uint value); virtual void setValue(int value); // WARNING non-virtual - and thus only really intended for internal use this is a major flaw in the class presently, however it suits our current needs fine int value() const { return adjustValue(QSlider::value()); } signals: - // we emit this when the user has specifically changed the slider so connect to it if valueChanged() is too generic Qt also emits valueChanged(int) - void sliderReleased(int); // clazy:exclude=overloaded-signal + // We emit this when the user has specifically changed the slider so connect to it if valueChanged() is too generic Qt also emits valueChanged(int) + void SliderReleased(int); protected: void wheelEvent(QWheelEvent*) override;