chiark / gitweb /
lib/uaudio-pulseaudio.c: Add volume control support.
[disorder] / lib / uaudio-pulseaudio.c
1 /*
2  * This file is part of DisOrder.
3  * Copyright (C) 2013 Richard Kettlewell, 2018 Mark Wooding
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
24 #include <pulse/context.h>
25 #include <pulse/error.h>
26 #include <pulse/introspect.h>
27 #include <pulse/thread-mainloop.h>
28 #include <pulse/stream.h>
29
30 #include "mem.h"
31 #include "log.h"
32 #include "uaudio.h"
33 #include "configuration.h"
34
35 static const char *const pulseaudio_options[] = {
36   "application",
37   NULL
38 };
39
40 static pa_threaded_mainloop *loop;
41 static pa_context *ctx;
42 static pa_stream *str;
43 static pa_channel_map cmap;
44 static uint32_t strix;
45
46 #define PAERRSTR pa_strerror(pa_context_errno(ctx))
47
48 /** @brief Callback: wake up main loop when the context is ready. */
49 static 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. */
63 static 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. */
77 static 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); }
81
82 struct simpleop {
83   const char *what;
84   int donep;
85 };
86
87 /** @brief Callback: wake up main loop when a simple operation completes. */
88 static 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
95 /** @brief Open the PulseAudio sound device */
96 static void pulseaudio_open() {
97   /* Much of the following is cribbed from the PulseAudio `simple' source. */
98
99   pa_sample_spec ss;
100
101   /* Set up the sample format. */
102   ss.format = -1;
103   switch(uaudio_bits) {
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;
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;
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. */
149   strix = pa_stream_get_index(str);
150   pa_threaded_mainloop_unlock(loop);
151 }
152
153 /** @brief Close the PulseAudio sound device */
154 static void pulseaudio_close(void) {
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; }
159 }
160
161 /** @brief Actually play sound via PulseAudio */
162 static size_t pulseaudio_play(void *buffer, size_t samples,
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);
179   return samples;
180 }
181
182 static 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
191 static void pulseaudio_stop(void) {
192   uaudio_thread_stop();
193   pulseaudio_close();
194 }
195
196 static void pulseaudio_open_mixer(void) {
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   }
205 }
206
207 static void pulseaudio_close_mixer(void) {
208 }
209
210 struct getvol {
211   pa_cvolume vol;
212   int donep;
213 };
214
215 /** @brief Callback: pick out our stream's volume. */
216 static 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
234 static void pulseaudio_get_volume(int *left, int *right) {
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;
254 }
255
256 static void pulseaudio_set_volume(int *left, int *right) {
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);
280 }
281
282 static void pulseaudio_configure(void) {
283 }
284
285 const 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 /*
303 Local Variables:
304 c-basic-offset:2
305 comment-column:40
306 fill-column:79
307 indent-tabs-mode:nil
308 End:
309 */