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(const PipeWireMixer &) = delete;
|
||||||
PipeWireMixer &operator=(const PipeWireMixer &) = delete;
|
PipeWireMixer &operator=(const PipeWireMixer &) = delete;
|
||||||
|
|
||||||
@ -82,7 +84,14 @@ pipewire_mixer_init([[maybe_unused]] EventLoop &event_loop, AudioOutput &ao,
|
|||||||
const ConfigBlock &)
|
const ConfigBlock &)
|
||||||
{
|
{
|
||||||
auto &po = (PipeWireOutput &)ao;
|
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 = {
|
const MixerPlugin pipewire_mixer_plugin = {
|
||||||
|
@ -41,6 +41,9 @@
|
|||||||
#include <spa/param/audio/format-utils.h>
|
#include <spa/param/audio/format-utils.h>
|
||||||
#include <spa/param/props.h>
|
#include <spa/param/props.h>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
#endif
|
#endif
|
||||||
@ -75,6 +78,9 @@ class PipeWireOutput final : AudioOutput {
|
|||||||
|
|
||||||
float volume = 1.0;
|
float volume = 1.0;
|
||||||
|
|
||||||
|
PipeWireMixer *mixer = nullptr;
|
||||||
|
int channels;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The active sample format, needed for PcmSilence().
|
* The active sample format, needed for PcmSilence().
|
||||||
*/
|
*/
|
||||||
@ -124,11 +130,21 @@ public:
|
|||||||
events.state_changed = StateChanged;
|
events.state_changed = StateChanged;
|
||||||
events.process = Process;
|
events.process = Process;
|
||||||
events.drained = Drained;
|
events.drained = Drained;
|
||||||
|
events.control_info = ControlInfo;
|
||||||
|
events.param_changed = ParamChanged;
|
||||||
return events;
|
return events;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetVolume(float volume);
|
void SetVolume(float volume);
|
||||||
|
|
||||||
|
void SetMixer(PipeWireMixer &_mixer);
|
||||||
|
|
||||||
|
void ClearMixer([[maybe_unused]] PipeWireMixer &old_mixer) {
|
||||||
|
assert(mixer == &old_mixer);
|
||||||
|
|
||||||
|
mixer = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void CheckThrowError() {
|
void CheckThrowError() {
|
||||||
if (disconnected)
|
if (disconnected)
|
||||||
@ -163,6 +179,46 @@ private:
|
|||||||
o.Drained();
|
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 */
|
/* virtual methods from class AudioOutput */
|
||||||
void Enable() override;
|
void Enable() override;
|
||||||
void Disable() noexcept override;
|
void Disable() noexcept override;
|
||||||
@ -214,11 +270,20 @@ PipeWireOutput::SetVolume(float _volume)
|
|||||||
{
|
{
|
||||||
const PipeWire::ThreadLoopLock lock(thread_loop);
|
const PipeWire::ThreadLoopLock lock(thread_loop);
|
||||||
|
|
||||||
if (stream != nullptr && !restore_volume &&
|
float newvol = _volume*_volume*_volume;
|
||||||
pw_stream_set_control(stream,
|
|
||||||
SPA_PROP_volume, 1, &_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)
|
0) != 0)
|
||||||
throw std::runtime_error("pw_stream_set_control() failed");
|
throw std::runtime_error("pw_stream_set_control() failed");
|
||||||
|
}
|
||||||
|
|
||||||
volume = _volume;
|
volume = _volume;
|
||||||
}
|
}
|
||||||
@ -404,6 +469,7 @@ PipeWireOutput::Open(AudioFormat &audio_format)
|
|||||||
|
|
||||||
frame_size = audio_format.GetFrameSize();
|
frame_size = audio_format.GetFrameSize();
|
||||||
sample_format = audio_format.format;
|
sample_format = audio_format.format;
|
||||||
|
channels = audio_format.channels;
|
||||||
interrupted = false;
|
interrupted = false;
|
||||||
|
|
||||||
/* allocate a ring buffer of 0.5 seconds */
|
/* allocate a ring buffer of 0.5 seconds */
|
||||||
@ -451,14 +517,6 @@ PipeWireOutput::StateChanged(enum pw_stream_state state,
|
|||||||
if (!was_disconnected && disconnected)
|
if (!was_disconnected && disconnected)
|
||||||
pw_thread_loop_signal(thread_loop, false);
|
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
|
inline void
|
||||||
@ -585,6 +643,28 @@ PipeWireOutput::Pause() noexcept
|
|||||||
return true;
|
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 = {
|
const struct AudioOutputPlugin pipewire_output_plugin = {
|
||||||
"pipewire",
|
"pipewire",
|
||||||
nullptr,
|
nullptr,
|
||||||
|
@ -21,9 +21,16 @@
|
|||||||
#define MPD_PIPEWIRE_OUTPUT_PLUGIN_HXX
|
#define MPD_PIPEWIRE_OUTPUT_PLUGIN_HXX
|
||||||
|
|
||||||
class PipeWireOutput;
|
class PipeWireOutput;
|
||||||
|
class PipeWireMixer;
|
||||||
|
|
||||||
extern const struct AudioOutputPlugin pipewire_output_plugin;
|
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
|
void
|
||||||
pipewire_output_set_volume(PipeWireOutput &output, float volume);
|
pipewire_output_set_volume(PipeWireOutput &output, float volume);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user