2 * Copyright (C) 2016 "IoT.bzh"
3 * Author "Manuel Bachmann"
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
21 #include "audio-api.h"
22 #include "audio-pulse.h"
24 static struct alsa_info **alsa_info = NULL;
25 static struct dev_ctx_pulse **dev_ctx_p = NULL;
26 static unsigned int client_count = 0;
29 unsigned char _pulse_init (const char *name, audioCtxHandleT *ctx) {
32 pa_mainloop_api *pa_api;
33 pa_context *pa_context;
35 pa_sample_spec *pa_spec;
36 struct timeval tv_start, tv_now;
39 pa_loop = pa_mainloop_new ();
40 pa_api = pa_mainloop_get_api (pa_loop);
41 pa_context = pa_context_new (pa_api, "afb-audio-plugin");
43 /* allocate the global array if it hasn't been done */
45 dev_ctx_p = (dev_ctx_pulse_T**) malloc (sizeof(dev_ctx_pulse_T*));
47 /* create a temporary device, to be held until sink gets discovered */
48 dev_ctx_pulse_T *dev_ctx_p_t = (dev_ctx_pulse_T*) malloc (sizeof(dev_ctx_pulse_T));
49 dev_ctx_p_t->sink_name = NULL;
50 dev_ctx_p_t->card_name = (char**) malloc (sizeof(char*));
51 dev_ctx_p_t->card_name[0] = strdup (name);
52 dev_ctx_p_t->pa_loop = pa_loop;
53 dev_ctx_p_t->pa_context = pa_context;
55 pa_context_set_state_callback (pa_context, _pulse_context_cb, (void*)dev_ctx_p_t);
56 pa_context_connect (pa_context, NULL, 0, NULL);
58 /* 1 second should be sufficient to retrieve sink info */
59 gettimeofday (&tv_start, NULL);
60 gettimeofday (&tv_now, NULL);
61 while (tv_now.tv_sec - tv_start.tv_sec <= 2) {
62 pa_mainloop_iterate (pa_loop, 0, &ret);
65 fprintf (stderr, "Stopping PulseAudio backend...\n");
69 /* 0 and >100 are returned by PulseAudio itself */
70 if ((ret > 0)&&(ret < 100)) {
71 /* found a matching sink from callback */
72 fprintf (stderr, "Success : using sink n.%d\n", ret-1);
73 ctx->audio_dev = (void*)dev_ctx_p[ret-1];
76 gettimeofday (&tv_now, NULL);
78 /* fail if we found no matching sink */
82 /* make the client context aware of current card state */
83 ctx->mute = (unsigned char)dev_ctx_p[ret-1]->mute;
84 ctx->channels = (unsigned int)dev_ctx_p[ret-1]->volume.channels;
85 for (i = 0; i < ctx->channels; i++)
86 ctx->volume[i] = dev_ctx_p[ret-1]->volume.values[i];
89 /* open matching sink for playback */
90 pa_spec = (pa_sample_spec*) malloc (sizeof(pa_sample_spec));
91 pa_spec->format = PA_SAMPLE_S16LE;
92 pa_spec->rate = 22050;
93 pa_spec->channels = (uint8_t)ctx->channels;
95 if (!(pa = pa_simple_new (NULL, "afb-audio-plugin", PA_STREAM_PLAYBACK, dev_ctx_p[ret-1]->sink_name,
96 "afb-audio-output", pa_spec, NULL, NULL, &error))) {
97 fprintf (stderr, "Error opening PulseAudio sink %s : %s\n",
98 dev_ctx_p[ret-1]->sink_name, pa_strerror(error));
101 dev_ctx_p[ret-1]->pa = pa;
106 fprintf (stderr, "Successfully initialized PulseAudio backend.\n");
111 void _pulse_free (audioCtxHandleT *ctx) {
116 if (client_count > 0) return;
118 for (num = 0; num < (sizeof(dev_ctx_p)/sizeof(dev_ctx_pulse_T*)); num++) {
120 for (i = 0; num < (sizeof(dev_ctx_p[num]->card_name)/sizeof(char*)); i++) {
121 free (dev_ctx_p[num]->card_name[i]);
122 dev_ctx_p[num]->card_name[i] = NULL;
124 pa_context_disconnect (dev_ctx_p[num]->pa_context);
125 pa_context_unref (dev_ctx_p[num]->pa_context);
126 pa_mainloop_free (dev_ctx_p[num]->pa_loop);
127 pa_simple_free (dev_ctx_p[num]->pa);
128 free (dev_ctx_p[num]->sink_name);
129 dev_ctx_p[num]->pa_context = NULL;
130 dev_ctx_p[num]->pa_loop = NULL;
131 dev_ctx_p[num]->pa = NULL;
132 dev_ctx_p[num]->sink_name = NULL;
133 free (dev_ctx_p[num]);
137 void _pulse_play (audioCtxHandleT *ctx) {
139 dev_ctx_pulse_T* dev_ctx_p_c = (dev_ctx_pulse_T*)ctx->audio_dev;
141 if (!dev_ctx_p_c || dev_ctx_p_c->thr_should_run || access (AUDIO_BUFFER, F_OK) == -1)
144 dev_ctx_p_c->thr_should_run = 1;
145 dev_ctx_p_c->thr_finished = 0;
146 pthread_create (&dev_ctx_p_c->thr, NULL, _pulse_play_thread_fn, (void*)dev_ctx_p_c);
149 void _pulse_stop (audioCtxHandleT *ctx) {
151 dev_ctx_pulse_T* dev_ctx_p_c = (dev_ctx_pulse_T*)ctx->audio_dev;
153 if (!dev_ctx_p_c || !dev_ctx_p_c->thr_should_run)
156 dev_ctx_p_c->thr_should_run = 0;
157 while (!dev_ctx_p_c->thr_finished)
159 pthread_join (dev_ctx_p_c->thr, NULL);
162 unsigned int _pulse_get_volume (audioCtxHandleT *ctx, unsigned int channel) {
164 dev_ctx_pulse_T* dev_ctx_p_c = (dev_ctx_pulse_T*)ctx->audio_dev;
169 _pulse_refresh_sink (dev_ctx_p_c);
171 return dev_ctx_p_c->volume.values[channel];
174 void _pulse_set_volume (audioCtxHandleT *ctx, unsigned int channel, unsigned int vol) {
176 dev_ctx_pulse_T* dev_ctx_p_c = (dev_ctx_pulse_T*)ctx->audio_dev;
177 struct pa_cvolume volume;
182 volume = dev_ctx_p_c->volume;
183 volume.values[channel] = vol;
185 pa_context_set_sink_volume_by_name (dev_ctx_p_c->pa_context, dev_ctx_p_c->sink_name,
186 &volume, NULL, NULL);
187 _pulse_refresh_sink (dev_ctx_p_c);
190 void _pulse_set_volume_all (audioCtxHandleT *ctx, unsigned int vol) {
192 dev_ctx_pulse_T* dev_ctx_p_c = (dev_ctx_pulse_T*)ctx->audio_dev;
193 struct pa_cvolume volume;
198 pa_cvolume_init (&volume);
199 pa_cvolume_set (&volume, dev_ctx_p_c->volume.channels, vol);
201 pa_context_set_sink_volume_by_name (dev_ctx_p_c->pa_context, dev_ctx_p_c->sink_name,
202 &volume, NULL, NULL);
203 _pulse_refresh_sink (dev_ctx_p_c);
206 unsigned char _pulse_get_mute (audioCtxHandleT *ctx) {
208 dev_ctx_pulse_T* dev_ctx_p_c = (dev_ctx_pulse_T*)ctx->audio_dev;
213 _pulse_refresh_sink (dev_ctx_p_c);
215 return (unsigned char)dev_ctx_p_c->mute;
218 void _pulse_set_mute (audioCtxHandleT *ctx, unsigned char mute) {
220 dev_ctx_pulse_T* dev_ctx_p_c = (dev_ctx_pulse_T*)ctx->audio_dev;
225 pa_context_set_sink_mute_by_name (dev_ctx_p_c->pa_context, dev_ctx_p_c->sink_name,
226 (int)mute, NULL, NULL);
227 _pulse_refresh_sink (dev_ctx_p_c);
230 /* ---- LOCAL HELPER FUNCTIONS ---- */
232 void _pulse_refresh_sink (dev_ctx_pulse_T* dev_ctx_p_c) {
234 dev_ctx_p_c->refresh = 1;
236 pa_context_get_sink_info_by_name (dev_ctx_p_c->pa_context, dev_ctx_p_c->sink_name,
237 _pulse_sink_info_cb, (void*)dev_ctx_p_c);
239 while (dev_ctx_p_c->refresh)
240 pa_mainloop_iterate (dev_ctx_p_c->pa_loop, 0, NULL);
243 void _pulse_enumerate_cards () {
245 void **cards, **card;
246 char *name, *found, *alsa_name, *card_name;
247 int new_info, i, num = 0;
249 /* allocate the global alsa array */
250 alsa_info = (alsa_info_T**) malloc (sizeof(alsa_info_T*));
251 alsa_info[0] = (alsa_info_T*) malloc (sizeof(alsa_info_T));
252 alsa_info[0]->device = NULL;
253 alsa_info[0]->synonyms = NULL;
255 /* we use ALSA to enumerate cards */
256 snd_device_name_hint (-1, "pcm", &cards);
259 for (; *card != NULL; card++) {
260 name = snd_device_name_get_hint (*card, "NAME");
263 /* alsa name is before ':' (if ':' misses, then it has no card) */
264 found = strstr (name, ":");
265 if (!found) continue;
267 alsa_name = (char*) malloc (found-name+1);
268 strncpy (alsa_name, name, found-name);
269 alsa_name[found-name] = '\0';
271 /* card name is the invariant between "CARD=" and ',' */
272 found = strstr (name, "CARD=");
273 if (!found) continue;
276 card_name = strdup (found);
277 found = strstr (card_name, ",");
278 if (found) card_name[found-card_name] = '\0';
280 /* was the card name already listed in the global alsa array ? */
281 for (i = 0; i < (sizeof(alsa_info)/sizeof(alsa_info_T*)); i++) {
283 if (alsa_info[i]->device &&
284 !strcmp (alsa_info[i]->device, card_name)) {
285 /* it was ; add the alsa name as a new synonym */
286 asprintf (&alsa_info[i]->synonyms, "%s:%s", alsa_info[i]->synonyms, alsa_name);
291 /* it was not ; create it */
293 alsa_info = (alsa_info_T**) realloc (alsa_info, (num+1)*sizeof(alsa_info_T*));
294 alsa_info[num]->device = strdup (card_name);
295 asprintf (&alsa_info[num]->synonyms, ":%s", alsa_name);
303 char** _pulse_find_cards (const char *name) {
306 char *needle, *found, *next;
310 _pulse_enumerate_cards ();
312 asprintf (&needle, ":%s", name);
314 for (num = 0; num < (sizeof(alsa_info)/sizeof(alsa_info_T*)); num++) {
316 found = strstr (alsa_info[num]->synonyms, needle);
318 /* if next character is not ':' or '\0', we are wrong */
319 if ((found[strlen(name)+1] != ':') && (found[strlen(name)+1] != '\0')) {
320 found = strstr (found+1, needle);
323 /* found it ; now return all the "synonym" cards */
324 found = strstr (alsa_info[num]->synonyms, ":");
326 next = strstr (found+1, ":");
328 cards = (char**) realloc (cards, (i+1)*sizeof(char*));
329 cards[i] = (char*) malloc (next-found+1);
330 strncpy (cards[i], found+1, next-found);
331 cards[i][next-found-1] = '\0';
341 /* ---- LOCAL CALLBACK FUNCTIONS ---- */
343 void _pulse_context_cb (pa_context *context, void *data) {
345 pa_context_state_t state = pa_context_get_state (context);
346 dev_ctx_pulse_T *dev_ctx_p_t = (dev_ctx_pulse_T *)data;
348 if (state == PA_CONTEXT_FAILED) {
349 fprintf (stderr, "Could not connect to PulseAudio !\n");
350 pa_mainloop_quit (dev_ctx_p_t->pa_loop, -1);
353 if (state == PA_CONTEXT_READY)
354 pa_context_get_sink_info_list (context, _pulse_sink_list_cb, (void*)dev_ctx_p_t);
357 void _pulse_sink_list_cb (pa_context *context, const pa_sink_info *info,
358 int eol, void *data) {
360 dev_ctx_pulse_T *dev_ctx_p_t = (dev_ctx_pulse_T *)data;
361 const char *device_string;
362 char *device, *found;
369 device_string = pa_proplist_gets (info->proplist, "device.string");
370 /* ignore sinks with no cards */
374 /* was a sink with similar name already found ? */
375 for (num = 0; num < (sizeof(dev_ctx_p)/sizeof(dev_ctx_pulse_T*)); num++) {
376 if (dev_ctx_p[num]->sink_name &&
377 !strcmp (dev_ctx_p[num]->sink_name, info->name)) {
379 /* yet it was, did it have the required card ? */
380 cards = dev_ctx_p[num]->card_name;
381 for (i = 0; i < (sizeof(cards)/sizeof(char*)); i++) {
382 if (!strcmp (cards[i], dev_ctx_p_t->card_name[0])) {
383 /* it did : stop there and succeed */
384 fprintf (stderr, "Found matching sink : %s\n", info->name);
385 /* we return num+1 because '0' is already used */
386 pa_mainloop_quit (dev_ctx_p_t->pa_loop, num+1);
389 /* it did not, ignore and return */
395 /* remove ending ":0",":1"... in device name */
396 device = strdup (device_string);
397 found = strstr (device, ":");
398 if (found) device[found-device] = '\0';
400 /* new sink, find all the cards it manages, fail if none */
401 cards = _pulse_find_cards (device);
406 /* everything is well, register it in global array */
407 dev_ctx_p_t->sink_name = strdup (info->name);
408 dev_ctx_p_t->card_name = cards;
409 dev_ctx_p_t->mute = info->mute;
410 dev_ctx_p_t->volume = info->volume;
411 dev_ctx_p_t->thr_should_run = 0;
412 dev_ctx_p_t->thr_finished = 0;
413 dev_ctx_p[num] = dev_ctx_p_t;
415 /* does this new sink have the card we are looking for ? */ /* TODO : factorize this */
416 for (i = 0; i < (sizeof(cards)/sizeof(char*)); i++) {
417 if (!strcmp (cards[i], dev_ctx_p_t->card_name[0])) {
418 /* it did : stop there and succeed */
419 fprintf (stderr, "Found matching sink : %s\n", info->name);
420 /* we return num+1 because '0' is already used */
421 pa_mainloop_quit (dev_ctx_p_t->pa_loop, num+1);
426 void _pulse_sink_info_cb (pa_context *context, const pa_sink_info *info,
427 int eol, void *data) {
429 dev_ctx_pulse_T *dev_ctx_p_c = (dev_ctx_pulse_T *)data;
434 dev_ctx_p_c->refresh = 0;
435 dev_ctx_p_c->mute = info->mute;
436 dev_ctx_p_c->volume = info->volume;
439 /* ---- LOCAL THREADED FUNCTIONS ---- */
441 void* _pulse_play_thread_fn (void *ctx) {
443 dev_ctx_pulse_T *dev_ctx_p_c = (dev_ctx_pulse_T *)ctx;
449 file = fopen (AUDIO_BUFFER, "rb");
451 while (dev_ctx_p_c->thr_should_run && file && (access (AUDIO_BUFFER, F_OK) != -1) ) {
452 fseek (file, 0, SEEK_END);
454 buf = (char*) realloc (buf, size * sizeof(char));
456 fseek (file, 0, SEEK_SET);
457 fread (buf, 1, size, file);
460 if (pa_simple_write (dev_ctx_p_c->pa, buf, size*2, &error) < 0)
461 fprintf (stderr, "Error writing to PulseAudio : %s\n", pa_strerror (error));
462 /* pa_simple_drain (dev_ctx_p_c->pa); */
465 if (file) fclose(file);
467 dev_ctx_p_c->thr_finished = 1;