31ebdba4ab9602e351f03fad8f615e5027b2775e
[src/app-framework-binder.git] / plugins / audio / audio-pulse.c
1 /*
2  * Copyright (C) 2016 "IoT.bzh"
3  * Author "Manuel Bachmann"
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
19 #define _GNU_SOURCE
20 #include <stdio.h>
21
22 #include "audio-api.h"
23 #include "audio-pulse.h"
24
25 PUBLIC unsigned char _pulse_init (const char *name, audioCtxHandleT *ctx) {
26
27     pa_mainloop *pa_loop;
28     pa_mainloop_api *pa_api;
29     pa_context *pa_context;
30     pa_simple *pa;
31     pa_sample_spec *pa_spec;
32     struct timeval tv_start, tv_now;
33     int ret, error, i;
34
35     pa_loop = pa_mainloop_new ();
36     pa_api = pa_mainloop_get_api (pa_loop);
37     pa_context = pa_context_new (pa_api, "afb-audio-plugin");
38
39     /* allocate the global array if it hasn't been done */
40     if (!dev_ctx_p)
41         dev_ctx_p = (dev_ctx_pulse_T**) malloc (sizeof(dev_ctx_pulse_T));
42
43     /* create a temporary device, to be held until sink gets discovered */
44     dev_ctx_pulse_T *dev_ctx_p_t = (dev_ctx_pulse_T*) malloc (sizeof(dev_ctx_pulse_T));
45     dev_ctx_p_t->sink_name = NULL;
46     dev_ctx_p_t->card_name = (char**) malloc (sizeof(char*));
47     dev_ctx_p_t->card_name[0] = strdup (name);
48     dev_ctx_p_t->pa_loop = pa_loop;
49     dev_ctx_p_t->pa_context = pa_context;
50
51     pa_context_set_state_callback (pa_context, _pulse_context_cb, (void*)dev_ctx_p_t);
52     pa_context_connect (pa_context, NULL, 0, NULL);
53
54     /* 1 second should be sufficient to retrieve sink info */
55     gettimeofday (&tv_start, NULL);
56     gettimeofday (&tv_now, NULL);
57     while (tv_now.tv_sec - tv_start.tv_sec <= 1) {
58         pa_mainloop_iterate (pa_loop, 0, &ret);
59
60         if (ret == -1) {
61             if (verbose) fprintf (stderr, "Stopping PulseAudio backend...\n");
62             return 0;
63         }
64         if (ret >= 0) {
65             /* found a matching sink from callback */
66             if (verbose) fprintf (stderr, "Success : using sink n.%d\n", error);
67             ctx->audio_dev = (void*)dev_ctx_p[ret];
68             break;
69         }
70         gettimeofday (&tv_now, NULL);
71     }
72     /* fail if we found no matching sink */
73     if (!ctx->audio_dev)
74       return 0;
75
76     /* make the client context aware of current card state */
77     ctx->mute = (unsigned char)dev_ctx_p[ret]->mute;
78     ctx->channels = (unsigned int)dev_ctx_p[ret]->volume.channels;
79     for (i = 0; i < ctx->channels; i++)
80         ctx->volume[i] = dev_ctx_p[ret]->volume.values[i];
81     ctx->idx = ret;
82
83     /* open matching sink for playback */
84     pa_spec = (pa_sample_spec*) malloc (sizeof(pa_sample_spec));
85     pa_spec->format = PA_SAMPLE_S16LE;
86     pa_spec->rate = 22050;
87     pa_spec->channels = (uint8_t)ctx->channels;
88
89     if (!(pa = pa_simple_new (NULL, "afb-audio-plugin", PA_STREAM_PLAYBACK, dev_ctx_p[ret]->sink_name,
90                               "afb-audio-output", pa_spec, NULL, NULL, &error))) {
91         fprintf (stderr, "Error opening PulseAudio sink %s : %s\n",
92                           dev_ctx_p[ret]->sink_name, pa_strerror(error));
93         return 0;
94     }
95     dev_ctx_p[ret]->pa = pa;
96     free (pa_spec);
97
98     client_count++;
99
100     if (verbose) fprintf (stderr, "Successfully initialized PulseAudio backend.\n");
101
102     return 1;
103 }
104
105 PUBLIC void _pulse_free (audioCtxHandleT *ctx) {
106
107     int num, i;
108
109     client_count--;
110     if (client_count > 0) return;
111
112     for (num = 0; num < (sizeof(dev_ctx_p)/sizeof(dev_ctx_pulse_T)); num++) {
113
114          for (i = 0; num < (sizeof(dev_ctx_p[num]->card_name)/sizeof(char*)); i++) {
115              free (dev_ctx_p[num]->card_name[i]);
116              dev_ctx_p[num]->card_name[i] = NULL;
117          }
118          pa_context_disconnect (dev_ctx_p[num]->pa_context);
119          pa_context_unref (dev_ctx_p[num]->pa_context);
120          pa_mainloop_free (dev_ctx_p[num]->pa_loop);
121          pa_simple_free (dev_ctx_p[num]->pa);
122          free (dev_ctx_p[num]->sink_name);
123          dev_ctx_p[num]->pa_context = NULL;
124          dev_ctx_p[num]->pa_loop = NULL;
125          dev_ctx_p[num]->pa = NULL;
126          dev_ctx_p[num]->sink_name = NULL;
127          free (dev_ctx_p[num]);
128     }
129 }
130
131 PUBLIC void _pulse_play (audioCtxHandleT *ctx) {
132
133     dev_ctx_pulse_T* dev_ctx_p_c = (dev_ctx_pulse_T*)ctx->audio_dev;
134
135     if (!dev_ctx_p_c || dev_ctx_p_c->thr_should_run || access (AUDIO_BUFFER, F_OK) == -1)
136         return;
137
138     dev_ctx_p_c->thr_should_run = 1;
139     dev_ctx_p_c->thr_finished = 0;
140     pthread_create (&dev_ctx_p_c->thr, NULL, _pulse_play_thread_fn, (void*)dev_ctx_p_c);
141 }
142
143 PUBLIC void _pulse_stop (audioCtxHandleT *ctx) {
144
145     dev_ctx_pulse_T* dev_ctx_p_c = (dev_ctx_pulse_T*)ctx->audio_dev;
146
147     if (!dev_ctx_p_c || !dev_ctx_p_c->thr_should_run)
148         return;
149
150     dev_ctx_p_c->thr_should_run = 0;
151     while (!dev_ctx_p_c->thr_finished)
152         usleep(100000);
153     pthread_join (dev_ctx_p_c->thr, NULL);
154 }
155
156 PUBLIC unsigned int _pulse_get_volume (audioCtxHandleT *ctx, unsigned int channel) {
157
158     dev_ctx_pulse_T* dev_ctx_p_c = (dev_ctx_pulse_T*)ctx->audio_dev;
159
160     if (!dev_ctx_p_c)
161         return 0;
162
163     _pulse_refresh_sink (dev_ctx_p_c);
164
165     return dev_ctx_p_c->volume.values[channel];
166 }
167
168 PUBLIC void _pulse_set_volume (audioCtxHandleT *ctx, unsigned int channel, unsigned int vol) {
169
170     dev_ctx_pulse_T* dev_ctx_p_c = (dev_ctx_pulse_T*)ctx->audio_dev;
171     struct pa_cvolume volume;
172
173     if (!dev_ctx_p_c)
174         return;
175
176     volume = dev_ctx_p_c->volume;
177     volume.values[channel] = vol;
178
179     pa_context_set_sink_volume_by_name (dev_ctx_p_c->pa_context, dev_ctx_p_c->sink_name,
180                                         &volume, NULL, NULL);
181     _pulse_refresh_sink (dev_ctx_p_c);
182 }
183
184 PUBLIC void _pulse_set_volume_all (audioCtxHandleT *ctx, unsigned int vol) {
185
186     dev_ctx_pulse_T* dev_ctx_p_c = (dev_ctx_pulse_T*)ctx->audio_dev;
187     struct pa_cvolume volume;
188
189     if (!dev_ctx_p_c)
190         return;
191
192     pa_cvolume_init (&volume);
193     pa_cvolume_set (&volume, dev_ctx_p_c->volume.channels, vol);
194
195     pa_context_set_sink_volume_by_name (dev_ctx_p_c->pa_context, dev_ctx_p_c->sink_name,
196                                         &volume, NULL, NULL);
197     _pulse_refresh_sink (dev_ctx_p_c);
198 }
199
200 PUBLIC unsigned char _pulse_get_mute (audioCtxHandleT *ctx) {
201
202     dev_ctx_pulse_T* dev_ctx_p_c = (dev_ctx_pulse_T*)ctx->audio_dev;
203
204     if (!dev_ctx_p_c)
205         return 0;
206
207     _pulse_refresh_sink (dev_ctx_p_c);
208
209     return (unsigned char)dev_ctx_p_c->mute;
210 }
211
212 PUBLIC void _pulse_set_mute (audioCtxHandleT *ctx, unsigned char mute) {
213
214     dev_ctx_pulse_T* dev_ctx_p_c = (dev_ctx_pulse_T*)ctx->audio_dev;
215
216     if (!dev_ctx_p_c)
217         return;
218
219     pa_context_set_sink_mute_by_name (dev_ctx_p_c->pa_context, dev_ctx_p_c->sink_name,
220                                       (int)mute, NULL, NULL);
221     _pulse_refresh_sink (dev_ctx_p_c);
222 }
223
224  /* ---- LOCAL HELPER FUNCTIONS ---- */
225
226 void _pulse_refresh_sink (dev_ctx_pulse_T* dev_ctx_p_c) {
227
228     dev_ctx_p_c->refresh = 1;
229
230     pa_context_get_sink_info_by_name (dev_ctx_p_c->pa_context, dev_ctx_p_c->sink_name,
231                                       _pulse_sink_info_cb, (void*)dev_ctx_p_c);
232
233     while (dev_ctx_p_c->refresh)
234         pa_mainloop_iterate (dev_ctx_p_c->pa_loop, 0, NULL);
235 }
236
237 void _pulse_enumerate_cards () {
238
239     void **cards, **card;
240     char *name, *found, *alsa_name, *card_name;
241     int new_info, i, num = 0;
242
243     /* allocate the global alsa array */
244     alsa_info = (alsa_info_T**) malloc (sizeof(alsa_info_T));
245     alsa_info[0] = (alsa_info_T*) malloc (sizeof(alsa_info_T));
246     alsa_info[0]->device = NULL;
247     alsa_info[0]->synonyms = NULL;
248
249     /* we use ALSA to enumerate cards */
250     snd_device_name_hint (-1, "pcm", &cards);
251     card = cards;
252
253     for (; *card != NULL; card++) {
254         name = snd_device_name_get_hint (*card, "NAME");
255         new_info = 1;
256
257         /* alsa name is before ':' (if ':' misses, then it has no card) */
258         found = strstr (name, ":");
259         if (!found) continue;
260         /* */
261         alsa_name = (char*) malloc (found-name+1);
262         strncpy (alsa_name, name, found-name);
263         alsa_name[found-name] = '\0';
264
265         /* card name is the invariant between "CARD=" and ',' */
266         found = strstr (name, "CARD=");
267         if (!found) continue;
268         /* */
269         found += 5;
270         card_name = strdup (found);
271         found = strstr (card_name, ",");
272         if (found) card_name[found-card_name] = '\0';
273
274         /* was the card name already listed in the global alsa array ? */
275         for (i = 0; i < (sizeof(alsa_info)/sizeof(alsa_info_T)); i++) {
276
277             if (alsa_info[i]->device &&
278                 !strcmp (alsa_info[i]->device, card_name)) {
279                 /* it was ; add the alsa name as a new synonym */
280                 asprintf (&alsa_info[i]->synonyms, "%s:%s", alsa_info[i]->synonyms, alsa_name);
281                 new_info = 0;
282                 break;
283             }            
284         }
285         /* it was not ; create it */
286         if (new_info) {
287             alsa_info = (alsa_info_T**) realloc (alsa_info, (num+1)*sizeof(alsa_info_T));
288             alsa_info[num]->device = strdup (card_name);
289             asprintf (&alsa_info[num]->synonyms, ":%s", alsa_name);
290             num++;
291         }
292         free (alsa_name);
293         free (card_name);
294     }
295 }
296
297 char** _pulse_find_cards (const char *name) {
298
299     char **cards = NULL;
300     char *needle, *found, *next;
301     int num, i = 0;
302
303     if (!alsa_info)
304       _pulse_enumerate_cards ();
305
306     asprintf (&needle, ":%s", name);
307
308     for (num = 0; num < (sizeof(alsa_info)/sizeof(alsa_info_T)); num++) {
309
310         found = strstr (alsa_info[num]->synonyms, needle);
311         while (found) {
312             /* if next character is not ':' or '\0', we are wrong */
313             if ((found[strlen(name)] != ':') && (found[strlen(name)] != '\0')) {
314                 found = strstr (found+1, needle);
315                 continue;
316             }
317             /* found it ; now return all the "synonym" cards */
318             found = strstr (alsa_info[num]->synonyms, ":");
319             while (found) {
320                 next = strstr (found+1, ":");
321                 cards = (char**) realloc (cards, (i+1)*sizeof(char*));
322                 cards[i] = (char*) malloc (next-found+1);
323                 strncpy (cards[i], found+1, next-found);
324                 cards[i][next-found] = '\0';
325                 i++;
326             }
327         }
328     }
329     free (needle);
330
331     return cards;
332 }
333
334  /* ---- LOCAL CALLBACK FUNCTIONS ---- */
335
336 STATIC void _pulse_context_cb (pa_context *context, void *data) {
337
338     pa_context_state_t state = pa_context_get_state (context);
339     dev_ctx_pulse_T *dev_ctx_p_t = (dev_ctx_pulse_T *)data;
340
341     if (state == PA_CONTEXT_FAILED) {
342         fprintf (stderr, "Could not connect to PulseAudio !\n");
343         pa_mainloop_quit (dev_ctx_p_t->pa_loop, -1);
344     }
345
346     if (state == PA_CONTEXT_READY)
347         pa_context_get_sink_info_list (context, _pulse_sink_list_cb, (void*)dev_ctx_p_t);
348 }
349
350 STATIC void _pulse_sink_list_cb (pa_context *context, const pa_sink_info *info,
351                                  int eol, void *data) {
352
353     dev_ctx_pulse_T *dev_ctx_p_t = (dev_ctx_pulse_T *)data;
354     const char *device_string;
355     char **cards;
356     int num, i;
357
358     if (eol != 0)
359         return;
360
361     /* ignore sinks with no cards */
362     if (!pa_proplist_contains (info->proplist, "device.string"))
363         return;
364
365     device_string = pa_proplist_gets (info->proplist, "device.string");
366
367     /* was a sink with similar name already found ? */
368     for (num = 0; num < (sizeof(dev_ctx_p)/sizeof(dev_ctx_pulse_T)); num++) {
369         if (dev_ctx_p[num]->sink_name &&
370            !strcmp (dev_ctx_p[num]->sink_name, info->name)) {
371
372             /* yet it was, did it have the required card ? */
373             cards = dev_ctx_p[num]->card_name;
374             for (i = 0; i < (sizeof(cards)/sizeof(char*)); i++) {
375                 if (!strcmp (cards[i], dev_ctx_p_t->card_name[0])) {
376                     /* it did : stop there and succeed */
377                     if (verbose) fprintf (stderr, "Found matching sink : %s\n", info->name);
378                     pa_mainloop_quit (dev_ctx_p_t->pa_loop, num);
379                 }
380             }
381             /* it did not, ignore and return */
382             return;
383         }
384     }
385     num++;
386
387     /* new sink, find all the cards it manages, fail if none */
388     cards = _pulse_find_cards (device_string);
389     if (!cards) return;
390
391     /* everything is well, register it in global array */
392     dev_ctx_p_t->sink_name = strdup (info->name);
393     dev_ctx_p_t->card_name = cards;
394     dev_ctx_p_t->mute = info->mute;
395     dev_ctx_p_t->volume = info->volume;
396     dev_ctx_p_t->thr_should_run = 0;
397     dev_ctx_p_t->thr_finished = 0;
398     dev_ctx_p[num] = dev_ctx_p_t;
399
400     /* does this new sink have the card we are looking for ? */ /* TODO : factorize this */
401     for (i = 0; i < (sizeof(cards)/sizeof(char*)); i++) {
402         if (!strcmp (cards[i], dev_ctx_p_t->card_name[0])) {
403              /* it did : stop there and succeed */
404              if (verbose) fprintf (stderr, "Found matching sink : %s\n", info->name);
405              pa_mainloop_quit (dev_ctx_p_t->pa_loop, num);
406         }
407     }
408 }
409
410 STATIC void _pulse_sink_info_cb (pa_context *context, const pa_sink_info *info,
411                                  int eol, void *data) {
412
413     dev_ctx_pulse_T *dev_ctx_p_c = (dev_ctx_pulse_T *)data;
414
415     if (eol != 0)
416         return;
417
418     dev_ctx_p_c->refresh = 0;
419     dev_ctx_p_c->mute = info->mute;
420     dev_ctx_p_c->volume = info->volume;
421 }
422
423  /* ---- LOCAL THREADED FUNCTIONS ---- */
424
425 STATIC void* _pulse_play_thread_fn (void *ctx) {
426
427     dev_ctx_pulse_T *dev_ctx_p_c = (dev_ctx_pulse_T *)ctx;
428     FILE *file = NULL;
429     char *buf = NULL;
430     long size;
431     int error;
432
433     file = fopen (AUDIO_BUFFER, "rb");
434
435     while (dev_ctx_p_c->thr_should_run && file && (access (AUDIO_BUFFER, F_OK) != -1) ) {
436         fseek (file, 0, SEEK_END);
437         size = ftell (file);
438         buf = (char*) realloc (buf, size * sizeof(char));
439
440         fseek (file, 0, SEEK_SET);
441         fread (buf, 1, size, file);
442         fflush (file);
443
444         if (pa_simple_write (dev_ctx_p_c->pa, buf, size*2, &error) < 0)
445             fprintf (stderr, "Error writing to PulseAudio : %s\n", pa_strerror (error));
446         /* pa_simple_drain (dev_ctx_p_c->pa); */
447     }
448     if (buf) free(buf);
449     if (file) fclose(file);
450
451     dev_ctx_p_c->thr_finished = 1;
452     return 0;
453 }