Implement volume updates for pipewire output
This commit is contained in:
		| @@ -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); | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Nicolai Syvertsen
					Nicolai Syvertsen