Commit | Line | Data |
---|---|---|
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 | ||
35 | static const char *const pulseaudio_options[] = { | |
36 | "application", | |
37 | NULL | |
38 | }; | |
39 | ||
c645bbf0 MW |
40 | static pa_threaded_mainloop *loop; |
41 | static pa_context *ctx; | |
42 | static pa_stream *str; | |
2504f269 MW |
43 | static pa_channel_map cmap; |
44 | static 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. */ | |
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); } | |
de0ef46e | 81 | |
2504f269 MW |
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 | ||
de0ef46e RK |
95 | /** @brief Open the PulseAudio sound device */ |
96 | static 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 */ | |
154 | static 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 */ | |
162 | static 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 | ||
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) { | |
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 | ||
207 | static void pulseaudio_close_mixer(void) { | |
208 | } | |
209 | ||
2504f269 MW |
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 | ||
de0ef46e | 234 | static 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 | ||
256 | static 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 | ||
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 | */ |