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> |
c645bbf0 MW |
26 | #include <pulse/thread-mainloop.h> |
27 | #include <pulse/stream.h> | |
de0ef46e RK |
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 | ||
c645bbf0 MW |
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); } | |
de0ef46e RK |
78 | |
79 | /** @brief Open the PulseAudio sound device */ | |
80 | static void pulseaudio_open() { | |
c645bbf0 MW |
81 | /* Much of the following is cribbed from the PulseAudio `simple' source. */ |
82 | ||
de0ef46e | 83 | pa_sample_spec ss; |
c645bbf0 MW |
84 | |
85 | /* Set up the sample format. */ | |
de0ef46e RK |
86 | ss.format = -1; |
87 | switch(uaudio_bits) { | |
c645bbf0 MW |
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; | |
de0ef46e RK |
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; | |
c645bbf0 MW |
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); | |
de0ef46e RK |
134 | } |
135 | ||
136 | /** @brief Close the PulseAudio sound device */ | |
137 | static void pulseaudio_close(void) { | |
c645bbf0 MW |
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; } | |
de0ef46e RK |
142 | } |
143 | ||
144 | /** @brief Actually play sound via PulseAudio */ | |
145 | static size_t pulseaudio_play(void *buffer, size_t samples, | |
c645bbf0 MW |
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); | |
de0ef46e RK |
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 | */ |