chiark / gitweb /
Imported Debian patch 1.0.0-6
[e16] / src / sound_pa.c
1 /*
2  * Copyright (C) 2008-2009 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 "E.h"
24 #if defined(HAVE_SOUND) && defined(HAVE_SOUND_PA)
25 #include "sound.h"
26 #include <fcntl.h>
27 #include <pulse/pulseaudio.h>
28 #include <sys/stat.h>
29
30 #ifdef USE_MODULES
31 #define Estrdup strdup
32 #endif
33
34 #define DEBUG_PA 0
35 #if DEBUG_PA
36 #define D2printf(fmt...) if(EDebug(EDBUG_TYPE_SOUND)>1)Eprintf(fmt)
37 #define D3printf(fmt...) if(EDebug(EDBUG_TYPE_SOUND)>2)Eprintf(fmt)
38 #define D4printf(fmt...) if(EDebug(EDBUG_TYPE_SOUND)>3)Eprintf(fmt)
39 #else
40 #define D2printf(fmt...)
41 #define D3printf(fmt...)
42 #define D4printf(fmt...)
43 #endif
44
45 struct _sample {
46    SoundSampleData     ssd;
47    char               *name;
48    unsigned int        written;
49 };
50
51 static pa_mainloop *pa_mloop = NULL;
52 static pa_mainloop_api *mainloop_api = NULL;
53 static pa_context  *pa_ctx = NULL;
54
55 static pa_stream   *sample_stream = NULL;
56 static size_t       sample_length = 0;
57 static int          pa_block = 0;
58
59 static void         _sound_pa_Exit(void);
60
61 static int
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\n", __func__, err, rc);
73         if (err <= 0)
74            break;
75      }
76
77    if (err < 0)
78       _sound_pa_Exit();
79
80    D3printf("%s: end\n", __func__);
81    return err;
82 }
83
84 static void
85 context_op_callback(pa_context * pac __UNUSED__, int success __UNUSED__,
86                     void *userdata __UNUSED__)
87 {
88    D2printf("%s: succ=%d %s\n", __func__, success,
89             (success) ? "" : pa_strerror(pa_context_errno(pac)));
90    pa_block = 0;
91 }
92
93 static void
94 context_drain_complete(pa_context * pac __UNUSED__, void *userdata __UNUSED__)
95 {
96    D2printf("%s\n", __func__);
97 }
98
99 static void
100 context_drain(pa_context * pac)
101 {
102    pa_operation       *op;
103
104    D2printf("%s\n", __func__);
105    op = pa_context_drain(pac, context_drain_complete, NULL);
106    if (op)
107       pa_operation_unref(op);
108    pa_block = 0;
109 }
110
111 static void
112 stream_state_callback(pa_stream * pas, void *userdata __UNUSED__)
113 {
114    D2printf("%s: state=%d\n", __func__, pa_stream_get_state(pas));
115    switch (pa_stream_get_state(pas))
116      {
117      case PA_STREAM_CREATING:   /* 1 */
118      case PA_STREAM_READY:      /* 2 */
119         break;
120
121      case PA_STREAM_TERMINATED: /* 4 */
122         context_drain(pa_stream_get_context(pas));
123         break;
124
125      case PA_STREAM_FAILED:     /* 3 */
126      default:
127         Eprintf("PA failure: %s\n",
128                 pa_strerror(pa_context_errno(pa_stream_get_context(pas))));
129         pa_block = 0;
130         break;
131      }
132 }
133
134 static void
135 stream_write_callback(pa_stream * pas, size_t length, void *userdata)
136 {
137    D2printf("%s: state=%d length=%d\n", __func__, pa_stream_get_state(pas),
138             length);
139    Sample             *s = userdata;
140    unsigned int        left;
141
142    left = s->ssd.size - s->written;
143    length = (left > length) ? length : left;
144    pa_stream_write(pas, s->ssd.data, length, NULL, 0, PA_SEEK_RELATIVE);
145    s->written += length;
146
147    D2printf("%s: size=%d written=%d\n", __func__, s->ssd.size, s->written);
148    if (s->written >= s->ssd.size)
149      {
150         pa_stream_set_write_callback(pas, NULL, NULL);
151         pa_stream_finish_upload(pas);
152      }
153 }
154
155 static void
156 context_state_callback(pa_context * pac, void *userdata __UNUSED__)
157 {
158    D2printf("%s: state=%d\n", __func__, pa_context_get_state(pac));
159    switch (pa_context_get_state(pac))
160      {
161      case PA_CONTEXT_CONNECTING:        /* 1 */
162      case PA_CONTEXT_AUTHORIZING:       /* 2 */
163      case PA_CONTEXT_SETTING_NAME:      /* 3 */
164         break;
165
166      case PA_CONTEXT_READY:     /* 4 */
167         pa_block = 0;
168         break;
169
170      case PA_CONTEXT_TERMINATED:        /* 6 */
171         break;
172
173      case PA_CONTEXT_FAILED:    /* 5 */
174      default:
175         Eprintf("PA failure: %s\n", pa_strerror(pa_context_errno(pac)));
176         pa_mainloop_quit(pa_mloop, 1);
177         break;
178      }
179 }
180
181 static void
182 _sound_pa_Destroy(Sample * s)
183 {
184    pa_operation       *op;
185
186    D2printf("%s beg: %s\n", __func__, s ? s->name : "?");
187    if (!s)
188       return;
189
190    if (pa_ctx && s->name)
191      {
192         op =
193            pa_context_remove_sample(pa_ctx, s->name, context_op_callback, NULL);
194         if (op)
195            pa_operation_unref(op);
196         dispatch(-1);
197      }
198    D2printf("%s end\n", __func__);
199
200    _EFREE(s->name);
201    _EFREE(s->ssd.data);
202    Efree(s);
203 }
204
205 static Sample      *
206 _sound_pa_Load(const char *file)
207 {
208    Sample             *s;
209    pa_sample_spec      sample_spec;
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    sample_length = s->ssd.size;
245
246    sample_stream = pa_stream_new(pa_ctx, s->name, &sample_spec, NULL);
247    if (!sample_stream)
248       goto bail_out;
249    pa_stream_set_state_callback(sample_stream, stream_state_callback, NULL);
250    pa_stream_set_write_callback(sample_stream, stream_write_callback, s);
251    pa_stream_connect_upload(sample_stream, sample_length);
252
253    err = dispatch(-1);
254    if (err)
255       goto bail_out;
256
257    _EFREE(s->ssd.data);
258
259    return s;
260
261  bail_out:
262    _sound_pa_Destroy(s);
263    return NULL;
264 }
265
266 static void
267 _sound_pa_Play(Sample * s)
268 {
269    pa_operation       *op;
270
271    D2printf("%s beg: %s\n", __func__, s ? s->name : "?");
272    if (!pa_ctx || !s)
273       return;
274
275    op = pa_context_play_sample(pa_ctx, s->name, NULL, PA_VOLUME_NORM,
276                                context_op_callback, NULL);
277    if (op)
278       pa_operation_unref(op);
279    dispatch(-1);
280    D2printf("%s end\n", __func__);
281 }
282
283 static void
284 _sound_pa_Exit(void)
285 {
286    D2printf("%s\n", __func__);
287 #if 0
288    if (stream)
289       pa_stream_unref(stream);
290 #endif
291
292    if (pa_ctx)
293      {
294         pa_context_disconnect(pa_ctx);
295         pa_context_unref(pa_ctx);
296         pa_ctx = NULL;
297      }
298
299    if (pa_mloop)
300      {
301         pa_mainloop_quit(pa_mloop, 0);
302         pa_mainloop_free(pa_mloop);
303         pa_mloop = NULL;
304      }
305 }
306
307 static int
308 _sound_pa_Init(void)
309 {
310    int                 err;
311
312    /* Set up a new main loop */
313    pa_mloop = pa_mainloop_new();
314    if (!pa_mloop)
315      {
316         Eprintf("pa_mainloop_new() failed.\n");
317         goto quit;
318      }
319
320    mainloop_api = pa_mainloop_get_api(pa_mloop);
321
322    /* Create a new connection context */
323    pa_ctx = pa_context_new(mainloop_api, "e16");
324    if (!pa_ctx)
325      {
326         Eprintf("pa_context_new() failed.\n");
327         goto quit;
328      }
329
330    pa_context_set_state_callback(pa_ctx, context_state_callback, NULL);
331
332    /* Connect the context */
333    err = pa_context_connect(pa_ctx, NULL, 0, NULL);
334    if (err)
335       Eprintf("pa_context_connect(): %s\n", pa_strerror(err));
336
337    err = dispatch(-1);
338    if (err)
339       goto quit;
340
341  done:
342    return pa_ctx == NULL;
343  quit:
344    _sound_pa_Exit();
345    goto done;
346 }
347
348 __EXPORT__ extern const SoundOps SoundOps_pa;
349 const SoundOps      SoundOps_pa = {
350    _sound_pa_Init, _sound_pa_Exit, _sound_pa_Load, _sound_pa_Destroy,
351    _sound_pa_Play,
352 };
353
354 #endif /* HAVE_SOUND && HAVE_SOUND_PA */