Adds 2017 to copyrights
[src/app-framework-binder.git] / bindings / audio / audio-alsa.c
1 /*
2  * Copyright (C) 2015, 2016, 2017 "IoT.bzh"
3  * Author "Manuel Bachmann"
4  *
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
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
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.
16  */
17
18 #include "audio-api.h"
19 #include "audio-alsa.h"
20
21 snd_mixer_selem_channel_id_t SCHANNELS[8] = {
22  SND_MIXER_SCHN_FRONT_LEFT,
23  SND_MIXER_SCHN_FRONT_RIGHT,
24  SND_MIXER_SCHN_FRONT_CENTER,
25  SND_MIXER_SCHN_REAR_LEFT,
26  SND_MIXER_SCHN_REAR_RIGHT,
27  SND_MIXER_SCHN_REAR_CENTER,
28  SND_MIXER_SCHN_SIDE_LEFT,
29  SND_MIXER_SCHN_SIDE_RIGHT
30 };
31
32 static struct dev_ctx_alsa **dev_ctx_a = NULL;
33
34
35 unsigned char _alsa_init (const char *name, audioCtxHandleT *ctx) {
36
37     snd_pcm_t *dev;
38     snd_pcm_hw_params_t *params;
39     snd_mixer_t *mixer;
40     snd_mixer_selem_id_t *mixer_sid;
41     snd_mixer_elem_t *mixer_elm;
42     snd_mixer_elem_t *mixer_elm_m;
43     unsigned int rate = 22050;
44     long vol, vol_min, vol_max;
45     int num, i;
46
47     if (snd_pcm_open (&dev, name, SND_PCM_STREAM_PLAYBACK, 0) < 0) {
48         fprintf (stderr, "ALSA backend could not open card '%s'\n", name);
49         return 0;
50     }
51
52     snd_pcm_hw_params_malloc (&params);
53     snd_pcm_hw_params_any (dev, params);
54     snd_pcm_hw_params_set_access (dev, params, SND_PCM_ACCESS_RW_INTERLEAVED);
55     snd_pcm_hw_params_set_format (dev, params, SND_PCM_FORMAT_S16_LE);
56     snd_pcm_hw_params_set_rate_near (dev, params, &rate, 0);
57     snd_pcm_hw_params_set_channels (dev, params, ctx->channels);
58     if (snd_pcm_hw_params (dev, params) < 0) {
59         snd_pcm_hw_params_free (params);
60         fprintf (stderr, "ALSA backend could set channels on card '%s'\n", name);
61         return 0;
62     }
63     snd_pcm_prepare (dev);
64
65     snd_mixer_open (&mixer, 0);
66     if (snd_mixer_attach (mixer, name) < 0) {
67         snd_pcm_hw_params_free (params);
68         fprintf (stderr, "ALSA backend could not open mixer for card '%s'\n", name);
69         return 0;
70     }
71     snd_mixer_selem_register (mixer, NULL, NULL);
72     snd_mixer_load (mixer);
73
74     snd_mixer_selem_id_alloca (&mixer_sid);
75     snd_mixer_selem_id_set_index (mixer_sid, 0);
76     snd_mixer_selem_id_set_name (mixer_sid, "Master");
77
78     mixer_elm = snd_mixer_find_selem (mixer, mixer_sid);
79     mixer_elm_m = NULL;
80
81     if (!mixer_elm) {
82         /* no "Master" mixer ; we are probably on a board... search ! */
83         for (mixer_elm = snd_mixer_first_elem (mixer); mixer_elm != NULL;
84              mixer_elm = snd_mixer_elem_next (mixer_elm)) {
85             if (snd_mixer_elem_info (mixer_elm) < 0)
86                 continue;
87             snd_mixer_selem_get_id (mixer_elm, mixer_sid);
88             if (strstr (snd_mixer_selem_id_get_name (mixer_sid), "DVC Out")) {
89
90                 /* this is Porter... let us found the specific mute switch */
91                 snd_mixer_selem_id_set_index (mixer_sid, 0);
92                 snd_mixer_selem_id_set_name (mixer_sid, "DVC Out Mute");
93                 mixer_elm_m = snd_mixer_find_selem (mixer, mixer_sid);
94
95                 break;
96             }
97         }
98     }
99
100     if (mixer_elm) {
101         snd_mixer_selem_get_playback_volume_range (mixer_elm, &vol_min, &vol_max);
102         snd_mixer_selem_get_playback_volume (mixer_elm, SND_MIXER_SCHN_FRONT_LEFT, &vol);
103     }
104
105     /* allocate the global array if it hasn't been done */
106     if (!dev_ctx_a) {
107         dev_ctx_a = (dev_ctx_alsa_T**) malloc (sizeof(dev_ctx_alsa_T*));
108         dev_ctx_a[0] = (dev_ctx_alsa_T*) malloc (sizeof(dev_ctx_alsa_T));
109         dev_ctx_a[0]->name = NULL;
110         dev_ctx_a[0]->dev = NULL;
111     }
112
113     /* is a card with similar name already opened ? */
114     for (num = 0; num < (sizeof(dev_ctx_a)/sizeof(dev_ctx_alsa_T*)); num++) {
115         if (dev_ctx_a[num]->name &&
116            !strcmp (dev_ctx_a[num]->name, name)) {
117             fprintf (stderr, "Card '%s' already locked by other ALSA backend session\n", name);
118             return 0;
119         }
120     }
121     num--;
122
123     /* it's not... let us add it to the global array */
124     dev_ctx_a = (dev_ctx_alsa_T**) realloc (dev_ctx_a, (num+1)*sizeof(dev_ctx_alsa_T*));
125     if (!dev_ctx_a[num])
126         dev_ctx_a[num] = (dev_ctx_alsa_T*) malloc (sizeof(dev_ctx_alsa_T));
127     dev_ctx_a[num]->name = strdup (name);
128     dev_ctx_a[num]->dev = dev;
129     dev_ctx_a[num]->params = params;
130     dev_ctx_a[num]->mixer_elm = mixer_elm;
131     dev_ctx_a[num]->mixer_elm_m = mixer_elm_m;
132     dev_ctx_a[num]->vol_max = vol_max;
133     dev_ctx_a[num]->vol = vol;
134     dev_ctx_a[num]->thr_should_run = 0;
135     dev_ctx_a[num]->thr_finished = 0;
136
137     /* make the client context aware of current card state */
138     for (i = 0; i < 8; i++)
139         ctx->volume[i] = _alsa_get_volume (num, i);
140     ctx->mute = _alsa_get_mute (num);
141     ctx->idx = num;
142     ctx->name = strdup (name);
143
144     fprintf (stderr, "Successfully initialized ALSA backend.\n");
145
146     return 1;
147 }
148
149 void _alsa_free (const char *name) {
150
151     int num;
152
153     for (num = 0; num < (sizeof(dev_ctx_a)/sizeof(dev_ctx_alsa_T*)); num++) {
154         if (dev_ctx_a[num]->name &&
155            !strcmp (dev_ctx_a[num]->name, name)) {
156             snd_pcm_close (dev_ctx_a[num]->dev);
157             snd_pcm_hw_params_free (dev_ctx_a[num]->params);
158             free (dev_ctx_a[num]->name);
159             dev_ctx_a[num]->dev = NULL;
160             dev_ctx_a[num]->name = NULL;
161             free (dev_ctx_a[num]);
162             return;
163         }
164     }
165 }
166
167 void _alsa_play (int num) {
168
169     if (!dev_ctx_a || !dev_ctx_a[num] || dev_ctx_a[num]->thr_should_run ||
170         access (AUDIO_BUFFER, F_OK) == -1)
171         return;
172
173     dev_ctx_a[num]->thr_should_run = 1;
174     dev_ctx_a[num]->thr_finished = 0;
175     pthread_create (&dev_ctx_a[num]->thr, NULL, _alsa_play_thread_fn, (void*)dev_ctx_a[num]);
176 }
177
178 void _alsa_stop (int num) {
179
180     if (!dev_ctx_a || !dev_ctx_a[num] || !dev_ctx_a[num]->thr_should_run)
181         return;
182
183     /* stop the "while" loop in thread */
184     dev_ctx_a[num]->thr_should_run = 0;
185
186     while (!dev_ctx_a[num]->thr_finished)
187         usleep(100000);
188
189     pthread_join (dev_ctx_a[num]->thr, NULL);
190 }
191
192 unsigned int _alsa_get_volume (int num, unsigned int channel) {
193
194     if (!dev_ctx_a || !dev_ctx_a[num] || !dev_ctx_a[num]->mixer_elm)
195         return 0;
196
197     snd_mixer_selem_get_playback_volume (dev_ctx_a[num]->mixer_elm, SCHANNELS[channel], &dev_ctx_a[num]->vol);
198
199     return (unsigned int)(dev_ctx_a[num]->vol*100)/dev_ctx_a[num]->vol_max;
200 }
201
202 void _alsa_set_volume (int num, unsigned int channel, unsigned int vol) {
203
204     if (!dev_ctx_a || !dev_ctx_a[num] || !dev_ctx_a[num]->mixer_elm ||
205         vol > 100)
206         return;
207
208     snd_mixer_selem_set_playback_volume (dev_ctx_a[num]->mixer_elm, SCHANNELS[channel], (vol*dev_ctx_a[num]->vol_max)/100);
209 }
210
211 void _alsa_set_volume_all (int num, unsigned int vol) {
212
213     if (!dev_ctx_a || !dev_ctx_a[num] || !dev_ctx_a[num]->mixer_elm ||
214         vol > 100)
215
216     fflush (stdout); /* seems to force this logic to apply quickly */
217     snd_mixer_selem_set_playback_volume_all (dev_ctx_a[num]->mixer_elm, (vol*dev_ctx_a[num]->vol_max)/100);
218 }
219
220 unsigned char _alsa_get_mute (int num) {
221
222     int mute = 0;
223     snd_mixer_elem_t *elm_m;
224
225     if (!dev_ctx_a || !dev_ctx_a[num] || !dev_ctx_a[num]->mixer_elm)
226         return 0;
227
228     dev_ctx_a[num]->mixer_elm_m ? (elm_m = dev_ctx_a[num]->mixer_elm_m) :
229                                   (elm_m = dev_ctx_a[num]->mixer_elm);
230
231     if (snd_mixer_selem_has_playback_switch (elm_m)) {
232         snd_mixer_selem_get_playback_switch (elm_m, SND_MIXER_SCHN_FRONT_LEFT, &mute);
233         snd_mixer_selem_get_playback_switch (elm_m, SND_MIXER_SCHN_FRONT_RIGHT, &mute);
234     }
235
236     if (dev_ctx_a[num]->mixer_elm_m)
237         return (unsigned char)mute;
238     else
239         return (unsigned char)!mute;
240 }
241
242 void _alsa_set_mute (int num, unsigned char tomute) {
243
244     snd_mixer_elem_t *elm_m;
245     int mute;
246
247     if (!dev_ctx_a || !dev_ctx_a[num] || !dev_ctx_a[num]->mixer_elm || 1 < tomute)
248         return;
249
250     if (dev_ctx_a[num]->mixer_elm_m) {
251         elm_m = dev_ctx_a[num]->mixer_elm_m;
252         mute = (int)!tomute;
253     } else {
254         elm_m = dev_ctx_a[num]->mixer_elm;
255         mute = (int)tomute;
256     }
257
258     if (snd_mixer_selem_has_playback_switch (elm_m))
259         snd_mixer_selem_set_playback_switch_all (elm_m, !mute);
260 }
261
262 void _alsa_set_rate (int num, unsigned int rate) {
263
264     if (!dev_ctx_a || !dev_ctx_a[num])
265         return;
266
267     snd_pcm_hw_params_set_rate_near (dev_ctx_a[num]->dev, dev_ctx_a[num]->params, &rate, 0);
268 }
269
270 void _alsa_set_channels (int num, unsigned int channels) {
271
272     if (!dev_ctx_a || !dev_ctx_a[num])
273         return;
274
275     snd_pcm_hw_params_set_channels (dev_ctx_a[num]->dev, dev_ctx_a[num]->params, channels);
276 }
277
278  /* ---- LOCAL THREADED FUNCTIONS ---- */
279
280 void* _alsa_play_thread_fn (void *ctx) {
281
282     dev_ctx_alsa_T *dev_ctx_a = (dev_ctx_alsa_T *)ctx;
283     FILE *file = NULL;
284     char *buf = NULL;
285     long size;
286     int frames, res;
287
288     file = fopen (AUDIO_BUFFER, "rb");
289
290     while (dev_ctx_a->thr_should_run && file && (access (AUDIO_BUFFER, F_OK) != -1) ) {
291         fseek (file, 0, SEEK_END);
292         size = ftell (file);
293         buf = (char*) realloc (buf, size * sizeof(char));
294         frames = (size * sizeof(char)) / 4;
295
296         fseek (file, 0, SEEK_SET);
297         fread (buf, 1, size, file);
298         fflush (file);
299
300         if ((res = snd_pcm_writei (dev_ctx_a->dev, buf, frames)) != frames) {
301             snd_pcm_recover (dev_ctx_a->dev, res, 0);
302             snd_pcm_prepare (dev_ctx_a->dev);
303         }
304         /* snd_pcm_drain (dev_ctx->dev); */
305     }
306     if (buf) free(buf);
307     if (file) fclose(file);
308
309     dev_ctx_a->thr_finished = 1;
310     return 0;
311 }