2 * This file is part of DisOrder.
3 * Copyright (C) 2013 Richard Kettlewell, 2018 Mark Wooding
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.
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.
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/>.
18 /** @file lib/uaudio-pulseaudio.c
19 * @brief Support for PulseAudio backend */
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>
33 #include "configuration.h"
35 static const char *const pulseaudio_options
[] = {
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
;
46 #define PAERRSTR pa_strerror(pa_context_errno(ctx))
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
);
53 case PA_CONTEXT_READY
:
54 pa_threaded_mainloop_signal(loop
, 0);
57 if(!PA_CONTEXT_IS_GOOD(st
))
58 disorder_fatal(0, "pulseaudio failed: %s", PAERRSTR
);
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
);
68 pa_threaded_mainloop_signal(loop
, 0);
71 if(!PA_STREAM_IS_GOOD(st
))
72 disorder_fatal(0, "pulseaudio failed: %s", PAERRSTR
);
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); }
87 /** @brief Callback: wake up main loop when a simple operation completes. */
88 static void cb_success(attribute((unused
)) pa_context
*xctx
,
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);
95 /** @brief Open the PulseAudio sound device */
96 static void pulseaudio_open() {
97 /* Much of the following is cribbed from the PulseAudio `simple' source. */
101 /* Set up the sample format. */
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;
109 disorder_fatal(0, "unsupported uaudio format (%d, %d)",
110 uaudio_bits
, uaudio_signed
);
111 ss
.channels
= uaudio_channels
;
112 ss
.rate
= uaudio_rate
;
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",
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
);
132 /* Set up my stream. */
133 str
= pa_stream_new(ctx
, "DisOrder", &ss
, 0);
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
,
140 disorder_fatal(0, "failed to connect pulseaudio stream for playback: %s",
142 pa_stream_set_state_callback(str
, cb_strstate
, 0);
144 /* Wait until the stream is ready. */
145 while(pa_stream_get_state(str
) != PA_STREAM_READY
)
146 pa_threaded_mainloop_wait(loop
);
149 strix
= pa_stream_get_index(str
);
150 pa_threaded_mainloop_unlock(loop
);
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; }
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
;
167 pa_threaded_mainloop_lock(loop
);
170 /* Wait until some output space becomes available. */
171 while(!(n
= pa_stream_writable_size(str
)))
172 pa_threaded_mainloop_wait(loop
);
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
);
178 pa_threaded_mainloop_unlock(loop
);
182 static void pulseaudio_start(uaudio_callback
*callback
,
185 uaudio_thread_start(callback
, userdata
, pulseaudio_play
,
186 32 / uaudio_sample_size
,
187 4096 / uaudio_sample_size
,
191 static void pulseaudio_stop(void) {
192 uaudio_thread_stop();
196 static void pulseaudio_open_mixer(void) {
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",
207 static void pulseaudio_close_mixer(void) {
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
;
221 disorder_fatal(0, "failed to read own pulseaudio sink-input volume: %s",
224 gv
->vol
= info
->volume
;
226 } else if (!gv
->donep
)
227 disorder_fatal(0, "no answer reading own pulseaudio sink-input volume");
230 pa_threaded_mainloop_signal(loop
, 0);
234 static void pulseaudio_get_volume(int *left
, int *right
) {
239 pa_threaded_mainloop_lock(loop
);
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
);
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;
252 *left
= 100.0*l
/PA_VOLUME_NORM
+ 0.5;
253 *right
= 100.0*r
/PA_VOLUME_NORM
+ 0.5;
256 static void pulseaudio_set_volume(int *left
, int *right
) {
262 l
= *left
*PA_VOLUME_NORM
/100.0 + 0.5;
263 r
= *right
*PA_VOLUME_NORM
/100.0 + 0.5;
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;
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
);
279 pulseaudio_get_volume(left
, right
);
282 static void pulseaudio_configure(void) {
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
,