PulseMixerPlugin.cxx 5.39 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2021 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13
 * http://www.musicpd.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
14 15 16 17
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 19
 */

20
#include "PulseMixerPlugin.hxx"
21
#include "lib/pulse/LogError.hxx"
22
#include "lib/pulse/LockGuard.hxx"
Max Kellermann's avatar
Max Kellermann committed
23
#include "mixer/MixerInternal.hxx"
24
#include "mixer/Listener.hxx"
25
#include "output/plugins/PulseOutputPlugin.hxx"
26 27 28
#include "util/NumberParser.hxx"
#include "util/RuntimeError.hxx"
#include "config/Block.hxx"
David Guibert's avatar
David Guibert committed
29

30 31 32 33 34
#include <pulse/context.h>
#include <pulse/introspect.h>
#include <pulse/stream.h>
#include <pulse/subscribe.h>

35
#include <cassert>
36 37
#include <stdexcept>

38
class PulseMixer final : public Mixer {
39
	PulseOutput &output;
40

41 42
	const float volume_scale_factor;

43
	bool online = false;
44

45
	struct pa_cvolume volume;
David Guibert's avatar
David Guibert committed
46

47
public:
48 49
	PulseMixer(PulseOutput &_output, MixerListener &_listener,
		   double _volume_scale_factor)
50
		:Mixer(pulse_mixer_plugin, _listener),
51
		 output(_output),
Rosen Penev's avatar
Rosen Penev committed
52
		 volume_scale_factor(float(_volume_scale_factor))
53 54
	{
	}
55

56
	~PulseMixer() override;
57

58 59 60
	PulseMixer(const PulseMixer &) = delete;
	PulseMixer &operator=(const PulseMixer &) = delete;

61 62 63
	void Offline();
	void VolumeCallback(const pa_sink_input_info *i, int eol);
	void Update(pa_context *context, pa_stream *stream);
64
	int GetVolumeInternal();
65 66

	/* virtual methods from class Mixer */
67
	void Open() override {
68 69
	}

70
	void Close() noexcept override {
71 72
	}

73 74
	int GetVolume() override;
	void SetVolume(unsigned volume) override;
David Guibert's avatar
David Guibert committed
75 76
};

77 78
void
PulseMixer::Offline()
79
{
80
	if (!online)
81
		return;
82

83
	online = false;
84

85
	listener.OnMixerVolumeChanged(*this, -1);
86 87
}

88 89
inline void
PulseMixer::VolumeCallback(const pa_sink_input_info *i, int eol)
David Guibert's avatar
David Guibert committed
90
{
91
	if (eol)
David Guibert's avatar
David Guibert committed
92 93
		return;

94
	if (i == nullptr) {
95
		Offline();
David Guibert's avatar
David Guibert committed
96 97
		return;
	}
98

99 100
	online = true;
	volume = i->volume;
101

102
	listener.OnMixerVolumeChanged(*this, GetVolumeInternal());
David Guibert's avatar
David Guibert committed
103 104
}

105 106 107 108
/**
 * Callback invoked by pulse_mixer_update().  Receives the new mixer
 * value.
 */
David Guibert's avatar
David Guibert committed
109
static void
Rosen Penev's avatar
Rosen Penev committed
110
pulse_mixer_volume_cb([[maybe_unused]] pa_context *context, const pa_sink_input_info *i,
111
		      int eol, void *userdata)
David Guibert's avatar
David Guibert committed
112
{
Max Kellermann's avatar
Max Kellermann committed
113
	auto *pm = (PulseMixer *)userdata;
114 115
	pm->VolumeCallback(i, eol);
}
David Guibert's avatar
David Guibert committed
116

117 118 119
inline void
PulseMixer::Update(pa_context *context, pa_stream *stream)
{
120 121
	assert(context != nullptr);
	assert(stream != nullptr);
122
	assert(pa_stream_get_state(stream) == PA_STREAM_READY);
123

124 125 126 127
	pa_operation *o =
		pa_context_get_sink_input_info(context,
					       pa_stream_get_index(stream),
					       pulse_mixer_volume_cb, this);
128
	if (o == nullptr) {
129 130
		LogPulseError(context,
			      "pa_context_get_sink_input_info() failed");
131
		Offline();
David Guibert's avatar
David Guibert committed
132 133
		return;
	}
134

135 136
	pa_operation_unref(o);
}
137

138
void
Rosen Penev's avatar
Rosen Penev committed
139
pulse_mixer_on_connect([[maybe_unused]] PulseMixer &pm,
140
		       struct pa_context *context)
141
{
142 143
	pa_operation *o;

144
	assert(context != nullptr);
145

146 147
	o = pa_context_subscribe(context,
				 (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT,
148 149
				 nullptr, nullptr);
	if (o == nullptr) {
150 151
		LogPulseError(context,
			      "pa_context_subscribe() failed");
152
		return;
153
	}
154

155
	pa_operation_unref(o);
David Guibert's avatar
David Guibert committed
156 157
}

158
void
159
pulse_mixer_on_disconnect(PulseMixer &pm)
David Guibert's avatar
David Guibert committed
160
{
161
	pm.Offline();
David Guibert's avatar
David Guibert committed
162 163
}

164
void
165
pulse_mixer_on_change(PulseMixer &pm,
166
		      struct pa_context *context, struct pa_stream *stream)
David Guibert's avatar
David Guibert committed
167
{
168
	pm.Update(context, stream);
David Guibert's avatar
David Guibert committed
169 170
}

171 172 173 174 175 176 177 178
static float
parse_volume_scale_factor(const char *value) {
	if (value == nullptr)
		return 1.0;

	char *endptr;
	float factor = ParseFloat(value, &endptr);

Rosen Penev's avatar
Rosen Penev committed
179
	if (endptr == value || *endptr != '\0' || factor < 0.5f || factor > 5.0f)
180
		throw FormatRuntimeError("\"%s\" is not a number in the "
181 182 183 184 185 186
					 "range 0.5 to 5.0",
					 value);

	return factor;
}

187
static Mixer *
Rosen Penev's avatar
Rosen Penev committed
188
pulse_mixer_init([[maybe_unused]] EventLoop &event_loop, AudioOutput &ao,
189
		 MixerListener &listener,
190
		 const ConfigBlock &block)
David Guibert's avatar
David Guibert committed
191
{
Max Kellermann's avatar
Max Kellermann committed
192
	auto &po = (PulseOutput &)ao;
193
	float scale = parse_volume_scale_factor(block.GetBlockValue("scale_volume"));
Rosen Penev's avatar
Rosen Penev committed
194
	auto *pm = new PulseMixer(po, listener, (double)scale);
David Guibert's avatar
David Guibert committed
195

196
	pulse_output_set_mixer(po, *pm);
David Guibert's avatar
David Guibert committed
197

198
	return pm;
199 200
}

201
PulseMixer::~PulseMixer()
202
{
203
	pulse_output_clear_mixer(output, *this);
David Guibert's avatar
David Guibert committed
204 205
}

206
int
207
PulseMixer::GetVolume()
David Guibert's avatar
David Guibert committed
208
{
209
	Pulse::LockGuard lock(pulse_output_get_mainloop(output));
210

211
	return GetVolumeInternal();
David Guibert's avatar
David Guibert committed
212 213
}

214 215 216 217
/**
 * Pulse mainloop lock must be held by caller
 */
int
218
PulseMixer::GetVolumeInternal()
219
{
Rosen Penev's avatar
Rosen Penev committed
220
	auto max_pa_volume = pa_volume_t(volume_scale_factor * PA_VOLUME_NORM);
221
	return online ?
222
		(int)((100 * (pa_cvolume_avg(&volume) + 1)) / max_pa_volume)
223 224 225
		: -1;
}

226 227
void
PulseMixer::SetVolume(unsigned new_volume)
David Guibert's avatar
David Guibert committed
228
{
229
	Pulse::LockGuard lock(pulse_output_get_mainloop(output));
230

231 232
	if (!online)
		throw std::runtime_error("disconnected");
David Guibert's avatar
David Guibert committed
233

Rosen Penev's avatar
Rosen Penev committed
234
	auto max_pa_volume = pa_volume_t(volume_scale_factor * PA_VOLUME_NORM);
235

236 237
	struct pa_cvolume cvolume;
	pa_cvolume_set(&cvolume, volume.channels,
238
		       (new_volume * max_pa_volume + 50) / 100);
239 240
	pulse_output_set_volume(output, &cvolume);
	volume = cvolume;
David Guibert's avatar
David Guibert committed
241 242
}

243
const MixerPlugin pulse_mixer_plugin = {
244 245
	pulse_mixer_init,
	false,
David Guibert's avatar
David Guibert committed
246
};