Implement volume updates for pipewire output
This commit is contained in:
parent
31151cec3c
commit
b941a7df83
|
@ -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 = {
|
||||
|
|
|
@ -41,6 +41,9 @@
|
|||
#include <spa/param/audio/format-utils.h>
|
||||
#include <spa/param/props.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
|
||||
#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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue