chiark / gitweb /
lib/uaudio-pulseaudio.c: Rewrite using the asynchronous API.
[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>
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
34static const char *const pulseaudio_options[] = {
35 "application",
36 NULL
37};
38
c645bbf0
MW
39static pa_threaded_mainloop *loop;
40static pa_context *ctx;
41static 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. */
46static 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. */
60static 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. */
74static 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 */
80static 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 */
137static 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 */
145static 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
165static 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
174static void pulseaudio_stop(void) {
175 uaudio_thread_stop();
176 pulseaudio_close();
177}
178
179static void pulseaudio_open_mixer(void) {
180 disorder_error(0, "no pulseaudio mixer support yet");
181}
182
183static void pulseaudio_close_mixer(void) {
184}
185
186static void pulseaudio_get_volume(int *left, int *right) {
187 *left = *right = 0;
188}
189
190static void pulseaudio_set_volume(int *left, int *right) {
191 *left = *right = 0;
192}
193
194static void pulseaudio_configure(void) {
195}
196
197const 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/*
215Local Variables:
216c-basic-offset:2
217comment-column:40
218fill-column:79
219indent-tabs-mode:nil
220End:
221*/