PulseMixerPlugin.cxx 4.58 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2017 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 "config.h"
21
#include "PulseMixerPlugin.hxx"
22
#include "lib/pulse/Domain.hxx"
23
#include "lib/pulse/LogError.hxx"
24
#include "lib/pulse/LockGuard.hxx"
Max Kellermann's avatar
Max Kellermann committed
25
#include "mixer/MixerInternal.hxx"
26
#include "mixer/Listener.hxx"
27
#include "output/plugins/PulseOutputPlugin.hxx"
David Guibert's avatar
David Guibert committed
28

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

34 35
#include <stdexcept>

36
#include <assert.h>
37

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

	bool online;
42
	struct pa_cvolume volume;
David Guibert's avatar
David Guibert committed
43

44
public:
45 46 47
	PulseMixer(PulseOutput &_output, MixerListener &_listener)
		:Mixer(pulse_mixer_plugin, _listener),
		 output(_output), online(false)
48 49
	{
	}
50 51 52 53 54 55

	virtual ~PulseMixer();

	void Offline();
	void VolumeCallback(const pa_sink_input_info *i, int eol);
	void Update(pa_context *context, pa_stream *stream);
56
	int GetVolumeInternal();
57 58

	/* virtual methods from class Mixer */
59
	void Open() override {
60 61
	}

62
	void Close() noexcept override {
63 64
	}

65 66
	int GetVolume() override;
	void SetVolume(unsigned volume) override;
David Guibert's avatar
David Guibert committed
67 68
};

69 70
void
PulseMixer::Offline()
71
{
72
	if (!online)
73
		return;
74

75
	online = false;
76

77
	listener.OnMixerVolumeChanged(*this, -1);
78 79
}

80 81
inline void
PulseMixer::VolumeCallback(const pa_sink_input_info *i, int eol)
David Guibert's avatar
David Guibert committed
82
{
83
	if (eol)
David Guibert's avatar
David Guibert committed
84 85
		return;

86
	if (i == nullptr) {
87
		Offline();
David Guibert's avatar
David Guibert committed
88 89
		return;
	}
90

91 92
	online = true;
	volume = i->volume;
93

94
	listener.OnMixerVolumeChanged(*this, GetVolumeInternal());
David Guibert's avatar
David Guibert committed
95 96
}

97 98 99 100
/**
 * Callback invoked by pulse_mixer_update().  Receives the new mixer
 * value.
 */
David Guibert's avatar
David Guibert committed
101
static void
102 103
pulse_mixer_volume_cb(gcc_unused pa_context *context, const pa_sink_input_info *i,
		      int eol, void *userdata)
David Guibert's avatar
David Guibert committed
104
{
105 106 107
	PulseMixer *pm = (PulseMixer *)userdata;
	pm->VolumeCallback(i, eol);
}
David Guibert's avatar
David Guibert committed
108

109 110 111
inline void
PulseMixer::Update(pa_context *context, pa_stream *stream)
{
112 113
	assert(context != nullptr);
	assert(stream != nullptr);
114
	assert(pa_stream_get_state(stream) == PA_STREAM_READY);
115

116 117 118 119
	pa_operation *o =
		pa_context_get_sink_input_info(context,
					       pa_stream_get_index(stream),
					       pulse_mixer_volume_cb, this);
120
	if (o == nullptr) {
121 122
		LogPulseError(context,
			      "pa_context_get_sink_input_info() failed");
123
		Offline();
David Guibert's avatar
David Guibert committed
124 125
		return;
	}
126

127 128
	pa_operation_unref(o);
}
129

130
void
131
pulse_mixer_on_connect(gcc_unused PulseMixer &pm,
132
		       struct pa_context *context)
133
{
134 135
	pa_operation *o;

136
	assert(context != nullptr);
137

138 139
	o = pa_context_subscribe(context,
				 (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT,
140 141
				 nullptr, nullptr);
	if (o == nullptr) {
142 143
		LogPulseError(context,
			      "pa_context_subscribe() failed");
144
		return;
145
	}
146

147
	pa_operation_unref(o);
David Guibert's avatar
David Guibert committed
148 149
}

150
void
151
pulse_mixer_on_disconnect(PulseMixer &pm)
David Guibert's avatar
David Guibert committed
152
{
153
	pm.Offline();
David Guibert's avatar
David Guibert committed
154 155
}

156
void
157
pulse_mixer_on_change(PulseMixer &pm,
158
		      struct pa_context *context, struct pa_stream *stream)
David Guibert's avatar
David Guibert committed
159
{
160
	pm.Update(context, stream);
David Guibert's avatar
David Guibert committed
161 162
}

163
static Mixer *
164
pulse_mixer_init(gcc_unused EventLoop &event_loop, AudioOutput &ao,
165
		 MixerListener &listener,
166
		 gcc_unused const ConfigBlock &block)
David Guibert's avatar
David Guibert committed
167
{
168
	PulseOutput &po = (PulseOutput &)ao;
169
	PulseMixer *pm = new PulseMixer(po, listener);
David Guibert's avatar
David Guibert committed
170

171
	pulse_output_set_mixer(po, *pm);
David Guibert's avatar
David Guibert committed
172

173
	return pm;
174 175
}

176
PulseMixer::~PulseMixer()
177
{
178
	pulse_output_clear_mixer(output, *this);
David Guibert's avatar
David Guibert committed
179 180
}

181
int
182
PulseMixer::GetVolume()
David Guibert's avatar
David Guibert committed
183
{
184
	Pulse::LockGuard lock(pulse_output_get_mainloop(output));
185

186
	return GetVolumeInternal();
David Guibert's avatar
David Guibert committed
187 188
}

189 190 191 192
/**
 * Pulse mainloop lock must be held by caller
 */
int
193
PulseMixer::GetVolumeInternal()
194 195 196 197 198 199
{
	return online ?
		(int)((100 * (pa_cvolume_avg(&volume) + 1)) / PA_VOLUME_NORM)
		: -1;
}

200 201
void
PulseMixer::SetVolume(unsigned new_volume)
David Guibert's avatar
David Guibert committed
202
{
203
	Pulse::LockGuard lock(pulse_output_get_mainloop(output));
204

205 206
	if (!online)
		throw std::runtime_error("disconnected");
David Guibert's avatar
David Guibert committed
207

208 209
	struct pa_cvolume cvolume;
	pa_cvolume_set(&cvolume, volume.channels,
210
		       (new_volume * PA_VOLUME_NORM + 50) / 100);
211 212
	pulse_output_set_volume(output, &cvolume);
	volume = cvolume;
David Guibert's avatar
David Guibert committed
213 214
}

215
const MixerPlugin pulse_mixer_plugin = {
216 217
	pulse_mixer_init,
	false,
David Guibert's avatar
David Guibert committed
218
};