chiark / gitweb /
configure.ac: Use Automake `silent-rules' by default.
[disorder] / lib / uaudio-pulseaudio.c
CommitLineData
de0ef46e
RK
1/*
2 * This file is part of DisOrder.
c645bbf0 3 * Copyright (C) 2013 Richard Kettlewell, 2018 Mark Wooding
de0ef46e
RK
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18/** @file lib/uaudio-pulseaudio.c
19 * @brief Support for PulseAudio backend */
20#include "common.h"
21
22#if HAVE_PULSEAUDIO
23
c645bbf0 24#include <pulse/context.h>
de0ef46e 25#include <pulse/error.h>
2504f269 26#include <pulse/introspect.h>
c645bbf0
MW
27#include <pulse/thread-mainloop.h>
28#include <pulse/stream.h>
de0ef46e
RK
29
30#include "mem.h"
31#include "log.h"
32#include "uaudio.h"
33#include "configuration.h"
34
35static const char *const pulseaudio_options[] = {
36 "application",
37 NULL
38};
39
c645bbf0
MW
40static pa_threaded_mainloop *loop;
41static pa_context *ctx;
42static pa_stream *str;
2504f269
MW
43static pa_channel_map cmap;
44static uint32_t strix;
c645bbf0
MW
45
46#define PAERRSTR pa_strerror(pa_context_errno(ctx))
47
48/** @brief Callback: wake up main loop when the context is ready. */
49static void cb_ctxstate(attribute((unused)) pa_context *xctx,
50 attribute((unused)) void *p) {
51 pa_context_state_t st = pa_context_get_state(ctx);
52 switch(st) {
53 case PA_CONTEXT_READY:
54 pa_threaded_mainloop_signal(loop, 0);
55 break;
56 default:
57 if(!PA_CONTEXT_IS_GOOD(st))
58 disorder_fatal(0, "pulseaudio failed: %s", PAERRSTR);
59 }
60}
61
62/** @brief Callback: wake up main loop when the stream is ready. */
63static void cb_strstate(attribute((unused)) pa_stream *xstr,
64 attribute((unused)) void *p) {
65 pa_stream_state_t st = pa_stream_get_state(str);
66 switch(st) {
67 case PA_STREAM_READY:
68 pa_threaded_mainloop_signal(loop, 0);
69 break;
70 default:
71 if(!PA_STREAM_IS_GOOD(st))
72 disorder_fatal(0, "pulseaudio failed: %s", PAERRSTR);
73 }
74}
75
76/** @brief Callback: wake up main loop when there's output buffer space. */
77static void cb_wakeup(attribute((unused)) pa_stream *xstr,
78 attribute((unused)) size_t sz,
79 attribute((unused)) void *p)
80 { pa_threaded_mainloop_signal(loop, 0); }
de0ef46e 81
2504f269
MW
82struct simpleop {
83 const char *what;
84 int donep;
85};
86
87/** @brief Callback: wake up main loop when a simple operation completes. */
88static void cb_success(attribute((unused)) pa_context *xctx,
89 int winp, void *p) {
90 struct simpleop *sop = p;
91 if(!winp) disorder_fatal(0, "%s failed: %s", sop->what, PAERRSTR);
92 sop->donep = 1; pa_threaded_mainloop_signal(loop, 0);
93}
94
de0ef46e
RK
95/** @brief Open the PulseAudio sound device */
96static void pulseaudio_open() {
c645bbf0
MW
97 /* Much of the following is cribbed from the PulseAudio `simple' source. */
98
de0ef46e 99 pa_sample_spec ss;
c645bbf0
MW
100
101 /* Set up the sample format. */
de0ef46e
RK
102 ss.format = -1;
103 switch(uaudio_bits) {
c645bbf0
MW
104 case 8: if(!uaudio_signed) ss.format = PA_SAMPLE_U8; break;
105 case 16: if(uaudio_signed) ss.format = PA_SAMPLE_S16NE; break;
106 case 32: if(uaudio_signed) ss.format = PA_SAMPLE_S32NE; break;
de0ef46e
RK
107 }
108 if(ss.format == -1)
109 disorder_fatal(0, "unsupported uaudio format (%d, %d)",
110 uaudio_bits, uaudio_signed);
111 ss.channels = uaudio_channels;
112 ss.rate = uaudio_rate;
c645bbf0
MW
113
114 /* Create the random PulseAudio pieces. */
115 loop = pa_threaded_mainloop_new();
116 if(!loop) disorder_fatal(0, "failed to create pulseaudio main loop");
117 pa_threaded_mainloop_lock(loop);
118 ctx = pa_context_new(pa_threaded_mainloop_get_api(loop),
119 uaudio_get("application", "DisOrder"));
120 if(!ctx) disorder_fatal(0, "failed to create pulseaudio context");
121 pa_context_set_state_callback(ctx, cb_ctxstate, 0);
122 if(pa_context_connect(ctx, 0, 0, 0) < 0)
123 disorder_fatal(0, "failed to connect to pulseaudio server: %s",
124 PAERRSTR);
125
126 /* Set the main loop going. */
127 if(pa_threaded_mainloop_start(loop) < 0)
128 disorder_fatal(0, "failed to start pulseaudio main loop");
129 while(pa_context_get_state(ctx) != PA_CONTEXT_READY)
130 pa_threaded_mainloop_wait(loop);
131
132 /* Set up my stream. */
133 str = pa_stream_new(ctx, "DisOrder", &ss, 0);
134 if(!str)
135 disorder_fatal(0, "failed to create pulseaudio stream: %s", PAERRSTR);
136 pa_stream_set_write_callback(str, cb_wakeup, 0);
137 if(pa_stream_connect_playback(str, 0, 0,
138 PA_STREAM_ADJUST_LATENCY,
139 0, 0))
140 disorder_fatal(0, "failed to connect pulseaudio stream for playback: %s",
141 PAERRSTR);
142 pa_stream_set_state_callback(str, cb_strstate, 0);
143
144 /* Wait until the stream is ready. */
145 while(pa_stream_get_state(str) != PA_STREAM_READY)
146 pa_threaded_mainloop_wait(loop);
147
148 /* All done. */
2504f269 149 strix = pa_stream_get_index(str);
c645bbf0 150 pa_threaded_mainloop_unlock(loop);
de0ef46e
RK
151}
152
153/** @brief Close the PulseAudio sound device */
154static void pulseaudio_close(void) {
c645bbf0
MW
155 if(loop) pa_threaded_mainloop_stop(loop);
156 if(str) { pa_stream_unref(str); str = 0; }
157 if(ctx) { pa_context_disconnect(ctx); pa_context_unref(ctx); ctx = 0; }
158 if(loop) { pa_threaded_mainloop_free(loop); loop = 0; }
de0ef46e
RK
159}
160
161/** @brief Actually play sound via PulseAudio */
162static size_t pulseaudio_play(void *buffer, size_t samples,
c645bbf0
MW
163 attribute((unused)) unsigned flags) {
164 unsigned char *p = buffer;
165 size_t n, sz = samples*uaudio_sample_size;
166
167 pa_threaded_mainloop_lock(loop);
168 while(sz) {
169
170 /* Wait until some output space becomes available. */
171 while(!(n = pa_stream_writable_size(str)))
172 pa_threaded_mainloop_wait(loop);
173 if(n > sz) n = sz;
174 if(pa_stream_write(str, p, n, 0, 0, PA_SEEK_RELATIVE) < 0)
175 disorder_fatal(0, "failed to write pulseaudio data: %s", PAERRSTR);
176 p += n; sz -= n;
177 }
178 pa_threaded_mainloop_unlock(loop);
de0ef46e
RK
179 return samples;
180}
181
182static void pulseaudio_start(uaudio_callback *callback,
183 void *userdata) {
184 pulseaudio_open();
185 uaudio_thread_start(callback, userdata, pulseaudio_play,
186 32 / uaudio_sample_size,
187 4096 / uaudio_sample_size,
188 0);
189}
190
191static void pulseaudio_stop(void) {
192 uaudio_thread_stop();
193 pulseaudio_close();
194}
195
196static void pulseaudio_open_mixer(void) {
2504f269
MW
197 if(!str)
198 disorder_fatal(0, "won't open pulseaudio mixer with no stream open");
199 switch(uaudio_channels) {
200 case 1: pa_channel_map_init_mono(&cmap); break;
201 case 2: pa_channel_map_init_stereo(&cmap); break;
202 default: disorder_error(0, "no pulseaudio mixer support for %d channels",
203 uaudio_channels);
204 }
de0ef46e
RK
205}
206
207static void pulseaudio_close_mixer(void) {
208}
209
2504f269
MW
210struct getvol {
211 pa_cvolume vol;
212 int donep;
213};
214
215/** @brief Callback: pick out our stream's volume. */
216static void cb_getvol(attribute((unused)) pa_context *xctx,
217 const pa_sink_input_info *info, int flag, void *p) {
218 struct getvol *gv = p;
219
220 if(flag < 0)
221 disorder_fatal(0, "failed to read own pulseaudio sink-input volume: %s",
222 PAERRSTR);
223 else if(!flag) {
224 gv->vol = info->volume;
225 gv->donep = -1;
226 } else if (!gv->donep)
227 disorder_fatal(0, "no answer reading own pulseaudio sink-input volume");
228 else {
229 gv->donep = 1;
230 pa_threaded_mainloop_signal(loop, 0);
231 }
232}
233
de0ef46e 234static void pulseaudio_get_volume(int *left, int *right) {
2504f269
MW
235 pa_operation *op;
236 struct getvol gv;
237 double l, r;
238
239 pa_threaded_mainloop_lock(loop);
240 gv.donep = 0;
241 op = pa_context_get_sink_input_info(ctx, strix, cb_getvol, &gv);
242 while(gv.donep <= 0) pa_threaded_mainloop_wait(loop);
243 pa_threaded_mainloop_unlock(loop);
244 pa_operation_unref(op);
245
246 switch(uaudio_channels) {
247 case 1: l = r = gv.vol.values[0]; break;
248 case 2: l = gv.vol.values[0]; r = gv.vol.values[1]; break;
249 default: l = r = 0; break;
250 }
251
252 *left = 100.0*l/PA_VOLUME_NORM + 0.5;
253 *right = 100.0*r/PA_VOLUME_NORM + 0.5;
de0ef46e
RK
254}
255
256static void pulseaudio_set_volume(int *left, int *right) {
2504f269
MW
257 pa_operation *op;
258 struct simpleop sop;
259 pa_cvolume vol;
260 double r, l;
261
262 l = *left*PA_VOLUME_NORM/100.0 + 0.5;
263 r = *right*PA_VOLUME_NORM/100.0 + 0.5;
264
265 pa_cvolume_init(&vol); vol.channels = uaudio_channels;
266 switch(uaudio_channels) {
267 case 1: if(r < l) r = l; vol.values[0] = vol.values[1] = r; break;
268 case 2: vol.values[0] = l; vol.values[1] = r; break;
269 default: return;
270 }
271
272 pa_threaded_mainloop_lock(loop);
273 sop.what = "set pulseaudio volume"; sop.donep = 0;
274 op = pa_context_set_sink_input_volume(ctx, strix, &vol, cb_success, &sop);
275 while(!sop.donep) pa_threaded_mainloop_wait(loop);
276 pa_threaded_mainloop_unlock(loop);
277 pa_operation_unref(op);
278
279 pulseaudio_get_volume(left, right);
de0ef46e
RK
280}
281
282static void pulseaudio_configure(void) {
283}
284
285const struct uaudio uaudio_pulseaudio = {
286 .name = "pulseaudio",
287 .options = pulseaudio_options,
288 .start = pulseaudio_start,
289 .stop = pulseaudio_stop,
290 .activate = uaudio_thread_activate,
291 .deactivate = uaudio_thread_deactivate,
292 .open_mixer = pulseaudio_open_mixer,
293 .close_mixer = pulseaudio_close_mixer,
294 .get_volume = pulseaudio_get_volume,
295 .set_volume = pulseaudio_set_volume,
296 .configure = pulseaudio_configure,
297 .flags = UAUDIO_API_CLIENT,
298};
299
300#endif
301
302/*
303Local Variables:
304c-basic-offset:2
305comment-column:40
306fill-column:79
307indent-tabs-mode:nil
308End:
309*/