summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/lib/backends/alsa/alsa.c520
1 files changed, 504 insertions, 16 deletions
diff --git a/src/lib/backends/alsa/alsa.c b/src/lib/backends/alsa/alsa.c
index 77f7fb0..1f3ce68 100644
--- a/src/lib/backends/alsa/alsa.c
+++ b/src/lib/backends/alsa/alsa.c
@@ -1,15 +1,410 @@
1#include <Eina.h> 1#include <Eina.h>
2#include <emix.h> 2#include <emix.h>
3#include <alsa/asoundlib.h>
4#include <poll.h>
5
6#define ERR(...) EINA_LOG_ERR(__VA_ARGS__)
7#define DBG(...) EINA_LOG_DBG(__VA_ARGS__)
8#define WRN(...) EINA_LOG_WARN(__VA_ARGS__)
3 9
4typedef struct _Context { 10typedef struct _Context {
5 sink_event_cb sink_cb; 11 sink_event_cb sink_cb;
6 sink_input_event_cb sink_input_cb; 12 sink_input_event_cb sink_input_cb;
7 source_event_cb source_cb; 13 source_event_cb source_cb;
8 disconnect_event_cb disconnect_cb; 14 disconnect_event_cb disconnect_cb;
15 Eina_List *sinks; /*list of sinks*/
16 Eina_List *sources; /*list of sources*/
17 Eina_List *cards; /*list of names which are recognized*/
9} Context; 18} Context;
10
11static Context *ctx = NULL; 19static Context *ctx = NULL;
12 20
21typedef struct _Alsa_Emix_Sink
22{
23 Emix_Sink sink;
24 snd_mixer_t *handle;
25 const char *hw_name;
26 Eina_List *channels;
27} Alsa_Emix_Sink;
28
29typedef struct _Alsa_Emix_Source
30{
31 Emix_Source source;
32 snd_mixer_t *handle;
33 const char *hw_name;
34 Eina_List *channels;
35} Alsa_Emix_Source;
36/*
37 * TODO problems:
38 *
39 * - at the remove event, who is freeing everything at the end?
40 * - what if a few channels are muted and others arent ?
41 *
42 * - mono stereo problem...
43 */
44
45/*
46 * util functions
47 */
48
49static int
50_alsa_mixer_changed_cb(snd_mixer_t *ctl, unsigned int mask EINA_UNUSED, snd_mixer_elem_t
51*elem)
52{
53 Eina_Array *arr = snd_mixer_get_callback_private(ctl);
54 if (snd_mixer_selem_has_capture_volume(elem))
55 ctx->source_cb(eina_array_data_get(arr, 1), EMIX_EVENT_CHANGED);
56 else
57 ctx->sink_cb(eina_array_data_get(arr, 0), EMIX_EVENT_CHANGED);
58 return 0;
59}
60
61static void
62_alsa_channel_volume_get(snd_mixer_elem_t *channel, int *v, Eina_Bool capture)
63{
64 long int min, max, lvol, rvol;
65 int range;
66
67 if (capture)
68 snd_mixer_selem_get_capture_volume_range(channel, &min, &max);
69 else
70 snd_mixer_selem_get_playback_volume_range(channel, &min, &max);
71
72 range = max - min;
73
74 if (range < 1)
75 return;
76
77 if (capture)
78 {
79 snd_mixer_selem_get_capture_volume(channel, 0, &lvol);
80 if (!snd_mixer_selem_is_capture_mono(channel))
81 snd_mixer_selem_get_capture_volume(channel, 1, &rvol);
82 }
83 else
84 {
85 snd_mixer_selem_get_playback_volume(channel, 0, &lvol);
86 if (!snd_mixer_selem_is_playback_mono(channel))
87 snd_mixer_selem_get_playback_volume(channel, 1, &rvol);
88 }
89
90 *v = lvol;
91}
92
93static void
94_alsa_channel_volume_set(snd_mixer_elem_t *channel, int v, Eina_Bool capture)
95{
96 int range, divide;
97 long int l, r, min, max;
98 snd_mixer_selem_get_playback_volume_range(channel, &min, &max);
99 divide = 100 + min;
100
101 if (divide == 0)
102 {
103 divide = 1;
104 min ++;
105 }
106 range = max -min;
107 if (range < 1)
108 return;
109 l = (((range * v) + (range / 2)) / divide) - min;
110 r = (((range * v) + (range / 2)) / divide) - min;
111 if (!capture)
112 {
113 snd_mixer_selem_set_playback_volume(channel, 0, l);
114 if (!snd_mixer_selem_is_playback_mono(channel))
115 snd_mixer_selem_set_playback_volume(channel, 1, r);
116 }
117 else
118 {
119 snd_mixer_selem_set_capture_volume(channel, 0, l);
120 if (!snd_mixer_selem_is_capture_mono(channel))
121 snd_mixer_selem_set_capture_volume(channel, 1, r);
122 }
123}
124
125
126/*
127 * This will append a new device to the cards and call the ecore event for a new device!
128 */
129
130snd_mixer_t*
131_alsa_card_create(char *addr)
132{
133 snd_mixer_t *control;
134
135 if (snd_mixer_open(&control, 0) < 0)
136 goto error_open;
137 if (snd_mixer_attach(control, addr) < 0)
138 goto error_load;
139 if (snd_mixer_selem_register(control, NULL, NULL) < 0)
140 goto error_load;
141 if (snd_mixer_load(control))
142 goto error_load;
143
144 return control;
145
146error_load:
147 snd_mixer_close(control);
148error_open:
149 return NULL;
150}
151
152static void
153_alsa_card_free(snd_mixer_t *mixer)
154{
155 snd_mixer_close(mixer);
156}
157
158static Eina_Bool
159_search_card(char *name)
160{
161 char *card_name;
162 Eina_List *node;
163
164 EINA_LIST_FOREACH(ctx->cards, node, card_name)
165 {
166 if (!strcmp(card_name, name))
167 return EINA_TRUE;
168 }
169 return EINA_FALSE;
170}
171
172static void
173_alsa_volume_create(Emix_Volume *volume, Eina_List *channels)
174{
175 unsigned int i;
176
177 volume->channel_count = eina_list_count(channels);
178 volume->volumes = calloc(eina_list_count(channels), sizeof(int));
179
180 for (i = 0; i < eina_list_count(channels); i++)
181 {
182 _alsa_channel_volume_get((snd_mixer_elem_t*) eina_list_nth(channels, i),
183 &(volume->volumes[i]),
184 EINA_FALSE);
185 }
186
187}
188
189static void
190_alsa_volume_free(Emix_Volume volume)
191{
192 free(volume.volumes);
193}
194
195static Alsa_Emix_Sink*
196_alsa_device_sink_create(snd_mixer_t *mixer, const char *name, const char* hw_name, Eina_List *channels)
197{
198 Alsa_Emix_Sink *sink;
199
200 if (!(sink = calloc(1, sizeof(Alsa_Emix_Sink))))
201 {
202 ERR("Allocation Failed");
203 return NULL;
204 }
205 sink->sink.name = eina_stringshare_add(name);
206 _alsa_volume_create(&sink->sink.volume, channels);
207 sink->hw_name = eina_stringshare_add(hw_name);
208 sink->handle = mixer;
209 sink->channels = channels;
210 ctx->sink_cb((Emix_Sink*) sink, EMIX_EVENT_ADDED);
211 ctx->sinks = eina_list_append(ctx->sinks, sink);
212 //TODO set mute
213 return sink;
214}
215
216static Alsa_Emix_Source*
217_alsa_device_source_create(snd_mixer_t *mixer, const char *name, const char* hw_name, Eina_List *channels)
218{
219 Alsa_Emix_Source *source;
220
221 if (!(source = calloc(1, sizeof(Alsa_Emix_Sink))))
222 {
223 ERR("Allocation Failed");
224 return NULL;
225 }
226 source->source.name = eina_stringshare_add(name);
227 _alsa_volume_create(&source->source.volume, channels);
228 source->hw_name = eina_stringshare_add(hw_name);
229 source->handle = mixer;
230 source->channels = channels;
231 ctx->source_cb((Emix_Source*) source, EMIX_EVENT_ADDED);
232 ctx->sources = eina_list_append(ctx->sources, source);
233 //TODO set mute
234 return source;
235}
236
237static void
238_alsa_device_sink_free(void *emix)
239{
240 Alsa_Emix_Sink *sink = emix;
241 eina_stringshare_del(sink->hw_name);
242 eina_stringshare_del(sink->sink.name);
243 _alsa_volume_free(sink->sink.volume);
244 free(sink);
245}
246
247static void
248_alsa_device_source_free(void *emix)
249{
250 Alsa_Emix_Source *source = emix;
251 eina_stringshare_del(source->hw_name);
252 eina_stringshare_del(source->source.name);
253 _alsa_volume_free(source->source.volume);
254 free(source);
255}
256
257
258static void
259_alsa_device_sink_remove(char* card_name, snd_mixer_t **mixer)
260{
261 Eina_List *node;
262 Alsa_Emix_Sink *sink;
263 EINA_LIST_FOREACH(ctx->sinks, node, sink)
264 {
265 if (!strcmp(sink->hw_name, card_name))
266 {
267 ctx->sink_cb((Emix_Sink*) sink, EMIX_EVENT_REMOVED);
268 *mixer = sink->handle;
269 _alsa_device_sink_free(sink);
270 return;
271 }
272 }
273}
274
275static void
276_alsa_device_source_remove(char* card_name, snd_mixer_t **mixer)
277{
278 Eina_List *node;
279 Alsa_Emix_Source *source;
280 EINA_LIST_FOREACH(ctx->sources, node, source)
281 {
282 if (!strcmp(source->hw_name, card_name))
283 {
284 ctx->source_cb((Emix_Source*) source, EMIX_EVENT_REMOVED);
285 *mixer = source->handle;
286 _alsa_device_source_free(source);
287 return;
288 }
289 }
290}
291
292/*
293 * TODO rework
294 */
295static const char*
296_alsa_cards_name_get(char *name)
297{
298 snd_ctl_t *control;
299 snd_ctl_card_info_t *hw_info;
300
301 snd_ctl_card_info_alloca(&hw_info);
302
303 snd_ctl_open(&control, name, 0);
304 snd_ctl_card_info(control, hw_info);
305 snd_ctl_close(control);
306
307 return eina_stringshare_add(snd_ctl_card_info_get_name(hw_info));
308}
309
310static void
311_alsa_cards_refresh(void)
312{
313 int err, card_num = -1;
314 Eina_List *tmp = NULL, *tmp_source = NULL, *tmp_sink = NULL;
315
316 while (((err = snd_card_next(&card_num)) == 0) && (card_num >= 0))
317 {
318 char buf[PATH_MAX];
319 const char *device_name;
320 snd_mixer_t *mixer;
321 snd_mixer_elem_t *elem;
322 Alsa_Emix_Source *source;
323 Alsa_Emix_Sink *sink;
324
325 source = NULL;
326 sink = NULL;
327 tmp_source = NULL;
328 tmp_sink = NULL;
329
330 //generate card addr
331 snprintf(buf, sizeof(buf), "hw:%d", card_num);
332 //save the addr to see if there are missing devices in the cache list
333 tmp = eina_list_append(tmp, eina_stringshare_add(buf));
334 //is this card new?
335 if (_search_card(buf))
336 continue;
337 //yes..
338 //create mixer
339 mixer = _alsa_card_create(buf);
340
341 ctx->cards = eina_list_append(ctx->cards, eina_stringshare_add(buf));
342 //get elements of the device
343 elem = snd_mixer_first_elem(mixer);
344 for (; elem; elem = snd_mixer_elem_next(elem))
345 {
346 //check if its a source or a sink
347 if (snd_mixer_selem_has_capture_volume(elem))
348 tmp_source = eina_list_append(tmp_source, elem);
349 else
350 tmp_sink = eina_list_append(tmp_sink, elem);
351 }
352 //get the complete device name
353 device_name = _alsa_cards_name_get(buf);
354 //create the sinks / sources
355 if (tmp_sink)
356 sink = _alsa_device_sink_create(mixer,
357 device_name,
358 buf,
359 tmp_sink);
360 if (tmp_source)
361 source = _alsa_device_source_create(mixer,
362 device_name,
363 buf,
364 tmp_source);
365 eina_stringshare_del(device_name);
366 //set callbacks for changes
367 snd_mixer_set_callback(mixer, _alsa_mixer_changed_cb);
368 Eina_Array *arr = eina_array_new(2);
369 eina_array_push(arr, sink);
370 eina_array_push(arr, source);
371 snd_mixer_set_callback_private(mixer, arr);
372 }
373
374 // Check if a card was removed
375 char *card_name;
376 Eina_List *node, *node2;
377 const char *name;
378 snd_mixer_t *mixer = NULL, *mixer2 = NULL;
379
380 EINA_LIST_FOREACH(ctx->cards, node, card_name)
381 {
382 Eina_Bool found = EINA_FALSE;
383 //search in the tmp list for this entry
384 EINA_LIST_FOREACH(tmp, node2, name)
385 {
386 if (!strcmp(card_name, name))
387 {
388 found = EINA_TRUE;
389 break;
390 }
391 }
392 if (!found)
393 {
394 ctx->cards = eina_list_remove(ctx->cards, card_name);
395 _alsa_device_sink_remove(card_name, &mixer);
396 _alsa_device_source_remove(card_name, &mixer2);
397 if (!mixer)
398 mixer = mixer2;
399 _alsa_card_free(mixer);
400 eina_stringshare_del(card_name);
401 }
402 }
403 //cleanup
404 EINA_LIST_FREE(tmp, card_name)
405 eina_stringshare_del(card_name);
406}
407
13static void 408static void
14_alsa_init(sink_event_cb sink_cb, sink_input_event_cb input_cb, 409_alsa_init(sink_event_cb sink_cb, sink_input_event_cb input_cb,
15 source_event_cb source_cb, disconnect_event_cb disconnect_cb) 410 source_event_cb source_cb, disconnect_event_cb disconnect_cb)
@@ -21,6 +416,7 @@ _alsa_init(sink_event_cb sink_cb, sink_input_event_cb input_cb,
21 ctx->sink_input_cb = input_cb; 416 ctx->sink_input_cb = input_cb;
22 ctx->source_cb = source_cb; 417 ctx->source_cb = source_cb;
23 ctx->disconnect_cb = disconnect_cb; 418 ctx->disconnect_cb = disconnect_cb;
419 _alsa_cards_refresh();
24} 420}
25 421
26static void 422static void
@@ -30,25 +426,117 @@ _alsa_shutdown(void)
30 ctx = NULL; 426 ctx = NULL;
31} 427}
32 428
429static const Eina_List*
430_alsa_sources_get(void)
431{
432 return ctx->sources;
433}
434
435static void
436_alsa_sources_mute_set(Emix_Source *source, Eina_Bool mute)
437{
438 Alsa_Emix_Source *s = (Alsa_Emix_Source*) source;
439 Eina_List *node;
440 snd_mixer_elem_t *elem;
441 EINA_LIST_FOREACH(s->channels, node, elem)
442 {
443 if (snd_mixer_selem_set_playback_switch_all(elem, !mute) < 0)
444 ERR("Failed to mute device\n");
445 }
446}
447
448static void
449_alsa_sources_volume_set(Emix_Source *source, Emix_Volume v)
450{
451 Alsa_Emix_Source *s = (Alsa_Emix_Source*) source;
452 unsigned int i;
453 snd_mixer_elem_t *elem;
454
455 if (v.channel_count != eina_list_count(s->channels))
456 {
457 ERR("Volume struct doesnt have the same length than the channels");
458 return;
459 }
460
461 for (i = 0; i < v.channel_count; i++ )
462 {
463 elem = eina_list_nth(s->channels, i);
464 _alsa_channel_volume_set(elem, v.volumes[i], EINA_FALSE);
465 }
466}
467
468
469static const Eina_List*
470_alsa_sinks_get(void)
471{
472 //FIXME fork or just return?
473/* Eina_List *result = NULL;
474 Eina_List *node;
475 void *data;
476 EINA_LIST_FOREACH(ctx->sinks, node, data)
477 {
478 result = eina_list_append(result, data);
479 }*/
480 return ctx->sinks;
481}
482
483static Eina_Bool
484_alsa_support(void)
485{
486 return EINA_FALSE;
487}
488
489static void
490_alsa_sink_mute_set(Emix_Sink *sink, Eina_Bool mute)
491{
492 Alsa_Emix_Sink *as = (Alsa_Emix_Sink*) sink;
493 Eina_List *node;
494 snd_mixer_elem_t *elem;
495 EINA_LIST_FOREACH(as->channels, node, elem)
496 {
497 if (snd_mixer_selem_set_playback_switch_all(elem, !mute) < 0)
498 ERR("Failed to mute device\n");
499 }
500}
501static void
502_alsa_sink_volume_set(Emix_Sink *sink, Emix_Volume v)
503{
504 Alsa_Emix_Sink *s = (Alsa_Emix_Sink*) sink;
505 unsigned int i;
506 snd_mixer_elem_t *elem;
507
508 if (v.channel_count != eina_list_count(s->channels))
509 {
510 ERR("Volume struct doesnt have the same length than the channels");
511 return;
512 }
513
514 for (i = 0; i < v.channel_count; i++ )
515 {
516 elem = eina_list_nth(s->channels, i);
517 _alsa_channel_volume_set(elem, v.volumes[i], EINA_FALSE);
518 }
519}
520
33static Emix_Backend 521static Emix_Backend
34_alsa_backend = { 522_alsa_backend = {
35 _alsa_init, 523 _alsa_init,
36 _alsa_shutdown, 524 _alsa_shutdown,
37 NULL, 525 _alsa_sinks_get,
38 NULL, 526 _alsa_support, /*default support*/
39 NULL, 527 NULL, /*get*/
40 NULL, 528 NULL, /*set*/
41 NULL, 529 _alsa_sink_mute_set, /*mute_set*/
42 NULL, 530 _alsa_sink_volume_set, /*volume_set*/
43 NULL, 531 NULL, /* port set */
44 NULL, 532 _alsa_support, /*change support*/
45 NULL, 533 NULL, /*sink input get*/
46 NULL, 534 NULL,/*sink input mute set*/
47 NULL, 535 NULL,/*sink input volume set*/
48 NULL, 536 NULL,/*sink input sink change*/
49 NULL, 537 _alsa_sources_get,/*source*/
50 NULL, 538 _alsa_sources_mute_set,/* source mute set */
51 NULL 539 _alsa_sources_volume_set /* source volume set */
52}; 540};
53 541
54EAPI Emix_Backend * 542EAPI Emix_Backend *