diff --git a/src/mixer/plugins/PipeWireMixerPlugin.cxx b/src/mixer/plugins/PipeWireMixerPlugin.cxx index 2a8bb3a6c..e8c95baee 100644 --- a/src/mixer/plugins/PipeWireMixerPlugin.cxx +++ b/src/mixer/plugins/PipeWireMixerPlugin.cxx @@ -37,6 +37,8 @@ public: { } + ~PipeWireMixer() override; + PipeWireMixer(const PipeWireMixer &) = delete; PipeWireMixer &operator=(const PipeWireMixer &) = delete; @@ -82,7 +84,14 @@ pipewire_mixer_init([[maybe_unused]] EventLoop &event_loop, AudioOutput &ao, const ConfigBlock &) { auto &po = (PipeWireOutput &)ao; - return new PipeWireMixer(po, listener); + auto *pm = new PipeWireMixer(po, listener); + pipewire_output_set_mixer(po, *pm); + return pm; +} + +PipeWireMixer::~PipeWireMixer() +{ + pipewire_output_clear_mixer(output, *this); } const MixerPlugin pipewire_mixer_plugin = { diff --git a/src/output/plugins/PipeWireOutputPlugin.cxx b/src/output/plugins/PipeWireOutputPlugin.cxx index db7c36823..0ebb9c802 100644 --- a/src/output/plugins/PipeWireOutputPlugin.cxx +++ b/src/output/plugins/PipeWireOutputPlugin.cxx @@ -41,6 +41,9 @@ #include #include +#include +#include + #ifdef __GNUC__ #pragma GCC diagnostic pop #endif @@ -75,6 +78,9 @@ class PipeWireOutput final : AudioOutput { float volume = 1.0; + PipeWireMixer *mixer = nullptr; + int channels; + /** * The active sample format, needed for PcmSilence(). */ @@ -124,11 +130,21 @@ public: events.state_changed = StateChanged; events.process = Process; events.drained = Drained; + events.control_info = ControlInfo; + events.param_changed = ParamChanged; return events; } void SetVolume(float volume); + void SetMixer(PipeWireMixer &_mixer); + + void ClearMixer([[maybe_unused]] PipeWireMixer &old_mixer) { + assert(mixer == &old_mixer); + + mixer = nullptr; + } + private: void CheckThrowError() { if (disconnected) @@ -163,6 +179,46 @@ private: o.Drained(); } + void ControlInfo(const struct pw_stream_control *control) { + float sum = 0; + unsigned c; + for (c = 0; c < control->n_values; c++) + sum += control->values[c]; + + sum /= control->n_values; + + if (mixer != nullptr) + pipewire_mixer_on_change(*mixer, std::cbrt(sum)); + + pw_thread_loop_signal(thread_loop, false); + } + + static void ControlInfo(void *data, + [[maybe_unused]] uint32_t id, + const struct pw_stream_control *control) noexcept { + auto &o = *(PipeWireOutput *)data; + if (StringIsEqual(control->name, "Channel Volumes")) + o.ControlInfo(control); + } + + void ParamChanged() { + if (restore_volume) { + SetVolume(volume); + restore_volume = false; + } + } + + static void ParamChanged(void *data, + uint32_t id, + const struct spa_pod *param) + { + if (id != SPA_PARAM_Format || param == NULL) + return; + + auto &o = *(PipeWireOutput *)data; + o.ParamChanged(); + } + /* virtual methods from class AudioOutput */ void Enable() override; void Disable() noexcept override; @@ -214,11 +270,20 @@ PipeWireOutput::SetVolume(float _volume) { const PipeWire::ThreadLoopLock lock(thread_loop); - if (stream != nullptr && !restore_volume && - pw_stream_set_control(stream, - SPA_PROP_volume, 1, &_volume, + float newvol = _volume*_volume*_volume; + + if (stream != nullptr && !restore_volume) { + float vol[SPA_AUDIO_MAX_CHANNELS]; + int i; + + for (i = 0; i < channels; i++) { + vol[i] = newvol; + } + if (pw_stream_set_control(stream, + SPA_PROP_channelVolumes, channels, vol, 0) != 0) - throw std::runtime_error("pw_stream_set_control() failed"); + throw std::runtime_error("pw_stream_set_control() failed"); + } volume = _volume; } @@ -404,6 +469,7 @@ PipeWireOutput::Open(AudioFormat &audio_format) frame_size = audio_format.GetFrameSize(); sample_format = audio_format.format; + channels = audio_format.channels; interrupted = false; /* allocate a ring buffer of 0.5 seconds */ @@ -451,14 +517,6 @@ PipeWireOutput::StateChanged(enum pw_stream_state state, if (!was_disconnected && disconnected) pw_thread_loop_signal(thread_loop, false); - if (state == PW_STREAM_STATE_STREAMING && restore_volume) { - /* restore the last known volume after creating a new - pw_stream */ - restore_volume = false; - pw_stream_set_control(stream, - SPA_PROP_volume, 1, &volume, - 0); - } } inline void @@ -585,6 +643,28 @@ PipeWireOutput::Pause() noexcept return true; } +inline void +PipeWireOutput::SetMixer(PipeWireMixer &_mixer) +{ + assert(mixer == nullptr); + + mixer = &_mixer; + + // TODO: Check if context and stream is ready and trigger a volume update... +} + +void +pipewire_output_set_mixer(PipeWireOutput &po, PipeWireMixer &pm) +{ + po.SetMixer(pm); +} + +void +pipewire_output_clear_mixer(PipeWireOutput &po, PipeWireMixer &pm) +{ + po.ClearMixer(pm); +} + const struct AudioOutputPlugin pipewire_output_plugin = { "pipewire", nullptr, diff --git a/src/output/plugins/PipeWireOutputPlugin.hxx b/src/output/plugins/PipeWireOutputPlugin.hxx index 9e8cd2c23..43125d767 100644 --- a/src/output/plugins/PipeWireOutputPlugin.hxx +++ b/src/output/plugins/PipeWireOutputPlugin.hxx @@ -21,9 +21,16 @@ #define MPD_PIPEWIRE_OUTPUT_PLUGIN_HXX class PipeWireOutput; +class PipeWireMixer; extern const struct AudioOutputPlugin pipewire_output_plugin; +void +pipewire_output_set_mixer(PipeWireOutput &po, PipeWireMixer &pm); + +void +pipewire_output_clear_mixer(PipeWireOutput &po, PipeWireMixer &pm); + void pipewire_output_set_volume(PipeWireOutput &output, float volume);