chiark / gitweb /
7908e6d0839749521b23d1eb681c72776167a742
[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/thread-mainloop.h>
27 #include <pulse/stream.h>
28
29 #include "mem.h"
30 #include "log.h"
31 #include "uaudio.h"
32 #include "configuration.h"
33
34 static const char *const pulseaudio_options[] = {
35   "application",
36   NULL
37 };
38
39 static pa_threaded_mainloop *loop;
40 static pa_context *ctx;
41 static pa_stream *str;
42
43 #define PAERRSTR pa_strerror(pa_context_errno(ctx))
44
45 /** @brief Callback: wake up main loop when the context is ready. */
46 static void cb_ctxstate(attribute((unused)) pa_context *xctx,
47                         attribute((unused)) void *p) {
48   pa_context_state_t st = pa_context_get_state(ctx);
49   switch(st) {
50   case PA_CONTEXT_READY:
51     pa_threaded_mainloop_signal(loop, 0);
52     break;
53   default:
54     if(!PA_CONTEXT_IS_GOOD(st))
55       disorder_fatal(0, "pulseaudio failed: %s", PAERRSTR);
56   }
57 }
58
59 /** @brief Callback: wake up main loop when the stream is ready. */
60 static void cb_strstate(attribute((unused)) pa_stream *xstr,
61                         attribute((unused)) void *p) {
62   pa_stream_state_t st = pa_stream_get_state(str);
63   switch(st) {
64   case PA_STREAM_READY:
65     pa_threaded_mainloop_signal(loop, 0);
66     break;
67   default:
68     if(!PA_STREAM_IS_GOOD(st))
69       disorder_fatal(0, "pulseaudio failed: %s", PAERRSTR);
70   }
71 }
72
73 /** @brief Callback: wake up main loop when there's output buffer space. */
74 static void cb_wakeup(attribute((unused)) pa_stream *xstr,
75                       attribute((unused)) size_t sz,
76                       attribute((unused)) void *p)
77   { pa_threaded_mainloop_signal(loop, 0); }
78
79 /** @brief Open the PulseAudio sound device */
80 static void pulseaudio_open() {
81   /* Much of the following is cribbed from the PulseAudio `simple' source. */
82
83   pa_sample_spec ss;
84
85   /* Set up the sample format. */
86   ss.format = -1;
87   switch(uaudio_bits) {
88   case 8: if(!uaudio_signed) ss.format = PA_SAMPLE_U8; break;
89   case 16: if(uaudio_signed) ss.format = PA_SAMPLE_S16NE; break;
90   case 32: if(uaudio_signed) ss.format = PA_SAMPLE_S32NE; break;
91   }
92   if(ss.format == -1)
93     disorder_fatal(0, "unsupported uaudio format (%d, %d)",
94                    uaudio_bits, uaudio_signed);
95   ss.channels = uaudio_channels;
96   ss.rate = uaudio_rate;
97
98   /* Create the random PulseAudio pieces. */
99   loop = pa_threaded_mainloop_new();
100   if(!loop) disorder_fatal(0, "failed to create pulseaudio main loop");
101   pa_threaded_mainloop_lock(loop);
102   ctx = pa_context_new(pa_threaded_mainloop_get_api(loop),
103                        uaudio_get("application", "DisOrder"));
104   if(!ctx) disorder_fatal(0, "failed to create pulseaudio context");
105   pa_context_set_state_callback(ctx, cb_ctxstate, 0);
106   if(pa_context_connect(ctx, 0, 0, 0) < 0)
107     disorder_fatal(0, "failed to connect to pulseaudio server: %s",
108                    PAERRSTR);
109
110   /* Set the main loop going. */
111   if(pa_threaded_mainloop_start(loop) < 0)
112     disorder_fatal(0, "failed to start pulseaudio main loop");
113   while(pa_context_get_state(ctx) != PA_CONTEXT_READY)
114     pa_threaded_mainloop_wait(loop);
115
116   /* Set up my stream. */
117   str = pa_stream_new(ctx, "DisOrder", &ss, 0);
118   if(!str)
119     disorder_fatal(0, "failed to create pulseaudio stream: %s", PAERRSTR);
120   pa_stream_set_write_callback(str, cb_wakeup, 0);
121   if(pa_stream_connect_playback(str, 0, 0,
122                                 PA_STREAM_ADJUST_LATENCY,
123                                 0, 0))
124     disorder_fatal(0, "failed to connect pulseaudio stream for playback: %s",
125                    PAERRSTR);
126   pa_stream_set_state_callback(str, cb_strstate, 0);
127
128   /* Wait until the stream is ready. */
129   while(pa_stream_get_state(str) != PA_STREAM_READY)
130     pa_threaded_mainloop_wait(loop);
131
132   /* All done. */
133   pa_threaded_mainloop_unlock(loop);
134 }
135
136 /** @brief Close the PulseAudio sound device */
137 static void pulseaudio_close(void) {
138   if(loop) pa_threaded_mainloop_stop(loop);
139   if(str) { pa_stream_unref(str); str = 0; }
140   if(ctx) { pa_context_disconnect(ctx); pa_context_unref(ctx); ctx = 0; }
141   if(loop) { pa_threaded_mainloop_free(loop); loop = 0; }
142 }
143
144 /** @brief Actually play sound via PulseAudio */
145 static size_t pulseaudio_play(void *buffer, size_t samples,
146                               attribute((unused)) unsigned flags) {
147   unsigned char *p = buffer;
148   size_t n, sz = samples*uaudio_sample_size;
149
150   pa_threaded_mainloop_lock(loop);
151   while(sz) {
152
153     /* Wait until some output space becomes available. */
154     while(!(n = pa_stream_writable_size(str)))
155       pa_threaded_mainloop_wait(loop);
156     if(n > sz) n = sz;
157     if(pa_stream_write(str, p, n, 0, 0, PA_SEEK_RELATIVE) < 0)
158       disorder_fatal(0, "failed to write pulseaudio data: %s", PAERRSTR);
159     p += n; sz -= n;
160   }
161   pa_threaded_mainloop_unlock(loop);
162   return samples;
163 }
164
165 static void pulseaudio_start(uaudio_callback *callback,
166                              void *userdata) {
167   pulseaudio_open();
168   uaudio_thread_start(callback, userdata, pulseaudio_play,
169                       32 / uaudio_sample_size,
170                       4096 / uaudio_sample_size,
171                       0);
172 }
173
174 static void pulseaudio_stop(void) {
175   uaudio_thread_stop();
176   pulseaudio_close();
177 }
178
179 static void pulseaudio_open_mixer(void) {
180   disorder_error(0, "no pulseaudio mixer support yet");
181 }
182
183 static void pulseaudio_close_mixer(void) {
184 }
185
186 static void pulseaudio_get_volume(int *left, int *right) {
187   *left = *right = 0;
188 }
189
190 static void pulseaudio_set_volume(int *left, int *right) {
191   *left = *right = 0;
192 }
193
194 static void pulseaudio_configure(void) {
195 }
196
197 const struct uaudio uaudio_pulseaudio = {
198   .name = "pulseaudio",
199   .options = pulseaudio_options,
200   .start = pulseaudio_start,
201   .stop = pulseaudio_stop,
202   .activate = uaudio_thread_activate,
203   .deactivate = uaudio_thread_deactivate,
204   .open_mixer = pulseaudio_open_mixer,
205   .close_mixer = pulseaudio_close_mixer,
206   .get_volume = pulseaudio_get_volume,
207   .set_volume = pulseaudio_set_volume,
208   .configure = pulseaudio_configure,
209   .flags = UAUDIO_API_CLIENT,
210 };
211
212 #endif
213
214 /*
215 Local Variables:
216 c-basic-offset:2
217 comment-column:40
218 fill-column:79
219 indent-tabs-mode:nil
220 End:
221 */