1 /*
2  * Copyright (C) 2008-2021 Kim Woelders
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to
6  * deal in the Software without restriction, including without limitation the
7  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8  * sell copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies of the Software, its documentation and marketing & publicity
13  * materials, and acknowledgment shall be given in the documentation, materials
14  * and software packages that this Software was used.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19  * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22  */
23 #include "config.h"
24 
25 #include <fcntl.h>
26 #include <pulse/pulseaudio.h>
27 #include <sys/stat.h>
28 
29 #include "sound.h"
30 #include "util.h"
31 
32 #ifdef USE_MODULES
33 #define Estrdup strdup
34 #endif
35 
36 #define DEBUG_PA 0
37 #if DEBUG_PA
38 #define D2printf(fmt...) if(EDebug(EDBUG_TYPE_SOUND)>1)Eprintf(fmt)
39 #define D3printf(fmt...) if(EDebug(EDBUG_TYPE_SOUND)>2)Eprintf(fmt)
40 #define D4printf(fmt...) if(EDebug(EDBUG_TYPE_SOUND)>3)Eprintf(fmt)
41 #else
42 #define D2printf(fmt...)
43 #define D3printf(fmt...)
44 #define D4printf(fmt...)
45 #endif
46 
47 struct _sample {
48    SoundSampleData     ssd;
49    char               *name;
50    unsigned int        written;
51 };
52 
53 static pa_mainloop *pa_mloop = NULL;
54 static pa_mainloop_api *mainloop_api = NULL;
55 static pa_context  *pa_ctx = NULL;
56 
57 static int          pa_block = 0;
58 
59 static void         _sound_pulse_Exit(void);
60 
61 static int
dispatch(int block)62 dispatch(int block)
63 {
64    int                 err, rc;
65 
66    D3printf("%s: beg\n", __func__);
67    rc = 1234;
68    pa_block = block;
69    for (;;)
70      {
71 	err = pa_mainloop_iterate(pa_mloop, pa_block, &rc);
72 	D4printf("%s: run err=%d rc=%d block=%d\n", __func__,
73 		 err, rc, pa_block);
74 	if (err < 0 || (err == 0 && !pa_block))
75 	   break;
76      }
77 
78    if (err < 0)
79       _sound_pulse_Exit();
80 
81    D3printf("%s: end\n", __func__);
82    return err;
83 }
84 
85 static void
context_op_callback(pa_context * pac __UNUSED__,int success __UNUSED__,void * userdata __UNUSED__)86 context_op_callback(pa_context * pac __UNUSED__, int success __UNUSED__,
87 		    void *userdata __UNUSED__)
88 {
89    D2printf("%s: succ=%d %s\n", __func__, success,
90 	    (success) ? "" : pa_strerror(pa_context_errno(pac)));
91    pa_block = 0;
92 }
93 
94 static void
context_drain_complete(pa_context * pac __UNUSED__,void * userdata __UNUSED__)95 context_drain_complete(pa_context * pac __UNUSED__, void *userdata __UNUSED__)
96 {
97    D2printf("%s\n", __func__);
98 }
99 
100 static void
context_drain(pa_context * pac)101 context_drain(pa_context * pac)
102 {
103    pa_operation       *op;
104 
105    D2printf("%s\n", __func__);
106    op = pa_context_drain(pac, context_drain_complete, NULL);
107    if (op)
108       pa_operation_unref(op);
109    pa_block = 0;
110 }
111 
112 static void
stream_state_callback(pa_stream * pas,void * userdata __UNUSED__)113 stream_state_callback(pa_stream * pas, void *userdata __UNUSED__)
114 {
115    D2printf("%s: state=%d\n", __func__, pa_stream_get_state(pas));
116    switch (pa_stream_get_state(pas))
117      {
118      case PA_STREAM_CREATING:	/* 1 */
119      case PA_STREAM_READY:	/* 2 */
120 	break;
121 
122      case PA_STREAM_TERMINATED:	/* 4 */
123 	context_drain(pa_stream_get_context(pas));
124 	break;
125 
126      case PA_STREAM_FAILED:	/* 3 */
127      default:
128 	Eprintf("PA failure: %s\n",
129 		pa_strerror(pa_context_errno(pa_stream_get_context(pas))));
130 	pa_block = 0;
131 	break;
132      }
133 }
134 
135 static void
stream_write_callback(pa_stream * pas,size_t length,void * userdata)136 stream_write_callback(pa_stream * pas, size_t length, void *userdata)
137 {
138    D2printf("%s: state=%d length=%u\n", __func__, pa_stream_get_state(pas),
139 	    (unsigned int)length);
140    Sample             *s = (Sample *) userdata;
141    unsigned int        left;
142 
143    left = s->ssd.size - s->written;
144    length = (left > length) ? length : left;
145    pa_stream_write(pas, s->ssd.data, length, NULL, 0, PA_SEEK_RELATIVE);
146    s->written += length;
147 
148    D2printf("%s: size=%d written=%d\n", __func__, s->ssd.size, s->written);
149    if (s->written >= s->ssd.size)
150      {
151 	pa_stream_set_write_callback(pas, NULL, NULL);
152 	pa_stream_finish_upload(pas);
153      }
154 }
155 
156 static void
context_state_callback(pa_context * pac,void * userdata __UNUSED__)157 context_state_callback(pa_context * pac, void *userdata __UNUSED__)
158 {
159    D2printf("%s: state=%d\n", __func__, pa_context_get_state(pac));
160    switch (pa_context_get_state(pac))
161      {
162      case PA_CONTEXT_CONNECTING:	/* 1 */
163      case PA_CONTEXT_AUTHORIZING:	/* 2 */
164      case PA_CONTEXT_SETTING_NAME:	/* 3 */
165 	break;
166 
167      case PA_CONTEXT_READY:	/* 4 */
168 	pa_block = 0;
169 	break;
170 
171      case PA_CONTEXT_TERMINATED:	/* 6 */
172 	break;
173 
174      case PA_CONTEXT_FAILED:	/* 5 */
175      default:
176 	Eprintf("PA failure: %s\n", pa_strerror(pa_context_errno(pac)));
177 	pa_mainloop_quit(pa_mloop, 1);
178 	break;
179      }
180 }
181 
182 static void
_sound_pulse_Destroy(Sample * s)183 _sound_pulse_Destroy(Sample * s)
184 {
185    pa_operation       *op;
186 
187    D2printf("%s beg: %s\n", __func__, s->name);
188 
189    if (pa_ctx && s->name)
190      {
191 	op =
192 	   pa_context_remove_sample(pa_ctx, s->name, context_op_callback, NULL);
193 	if (op)
194 	   pa_operation_unref(op);
195 	dispatch(-1);
196      }
197    D2printf("%s end\n", __func__);
198 
199    Efree(s->name);
200    Efree(s->ssd.data);
201    Efree(s);
202 }
203 
204 static Sample      *
_sound_pulse_Load(const char * file)205 _sound_pulse_Load(const char *file)
206 {
207    Sample             *s;
208    pa_sample_spec      sample_spec;
209    pa_stream          *sample_stream = NULL;
210    int                 err;
211    char               *p;
212 
213    if (!pa_ctx)
214       return NULL;
215 
216    s = ECALLOC(Sample, 1);
217    if (!s)
218       return NULL;
219 
220    err = SoundSampleGetData(file, &s->ssd);
221    if (err)
222      {
223 	Efree(s);
224 	return NULL;
225      }
226    s->name = Estrdup(file);
227    if (!s->name)
228       goto bail_out;
229    for (p = s->name; *p != '\0'; p++)
230       if (*p == '/')
231 	 *p = '_';
232 
233    switch (s->ssd.bit_per_sample)
234      {
235      case 8:
236 	sample_spec.format = PA_SAMPLE_U8;
237 	break;
238      default:
239 	sample_spec.format = PA_SAMPLE_S16NE;
240 	break;
241      }
242    sample_spec.rate = s->ssd.rate;
243    sample_spec.channels = s->ssd.channels;
244 
245    sample_stream = pa_stream_new(pa_ctx, s->name, &sample_spec, NULL);
246    if (!sample_stream)
247       goto bail_out;
248    pa_stream_set_state_callback(sample_stream, stream_state_callback, NULL);
249    pa_stream_set_write_callback(sample_stream, stream_write_callback, s);
250    pa_stream_connect_upload(sample_stream, s->ssd.size);
251 
252    err = dispatch(-1);
253    if (err)
254       goto bail_out;
255 
256    EFREE_NULL(s->ssd.data);
257    pa_stream_unref(sample_stream);
258 
259    return s;
260 
261  bail_out:
262    if (sample_stream)
263       pa_stream_unref(sample_stream);
264    _sound_pulse_Destroy(s);
265    return NULL;
266 }
267 
268 static void
_sound_pulse_Play(Sample * s)269 _sound_pulse_Play(Sample * s)
270 {
271    pa_operation       *op;
272 
273    D2printf("%s beg: %s\n", __func__, s->name);
274    if (!pa_ctx)
275       return;
276 
277    op = pa_context_play_sample(pa_ctx, s->name, NULL, PA_VOLUME_NORM,
278 			       context_op_callback, NULL);
279    if (op)
280       pa_operation_unref(op);
281    dispatch(-1);
282    D2printf("%s end\n", __func__);
283 }
284 
285 static void
_sound_pulse_Exit(void)286 _sound_pulse_Exit(void)
287 {
288    D2printf("%s\n", __func__);
289 #if 0
290    if (stream)
291       pa_stream_unref(stream);
292 #endif
293 
294    if (pa_ctx)
295      {
296 	pa_context_disconnect(pa_ctx);
297 	pa_context_unref(pa_ctx);
298 	pa_ctx = NULL;
299      }
300 
301    if (pa_mloop)
302      {
303 	pa_mainloop_quit(pa_mloop, 0);
304 	pa_mainloop_free(pa_mloop);
305 	pa_mloop = NULL;
306      }
307 }
308 
309 static int
_sound_pulse_Init(void)310 _sound_pulse_Init(void)
311 {
312    int                 err;
313 
314    /* Set up a new main loop */
315    pa_mloop = pa_mainloop_new();
316    if (!pa_mloop)
317      {
318 	Eprintf("pa_mainloop_new() failed.\n");
319 	goto quit;
320      }
321 
322    mainloop_api = pa_mainloop_get_api(pa_mloop);
323 
324    /* Create a new connection context */
325    pa_ctx = pa_context_new(mainloop_api, "e16");
326    if (!pa_ctx)
327      {
328 	Eprintf("pa_context_new() failed.\n");
329 	goto quit;
330      }
331 
332    pa_context_set_state_callback(pa_ctx, context_state_callback, NULL);
333 
334    /* Connect the context */
335    err = pa_context_connect(pa_ctx, NULL, PA_CONTEXT_NOFLAGS, NULL);
336    if (err)
337       Eprintf("pa_context_connect(): %s\n", pa_strerror(err));
338 
339    err = dispatch(-1);
340    if (err)
341       goto quit;
342 
343  done:
344    return !pa_ctx;
345  quit:
346    _sound_pulse_Exit();
347    goto done;
348 }
349 
350 __EXPORT__ extern const SoundOps SoundOps_pulse;
351 
352 const SoundOps      SoundOps_pulse = {
353    _sound_pulse_Init, _sound_pulse_Exit,
354    _sound_pulse_Load, _sound_pulse_Destroy, _sound_pulse_Play,
355 };
356