#include "emix.h" #include #define MAX_VOLUME 100 #define ERR(...) EINA_LOG_ERR(__VA_ARGS__) #define DBG(...) EINA_LOG_DBG(__VA_ARGS__) #define WRN(...) EINA_LOG_WARN(__VA_ARGS__) typedef struct _Context { Emix_Event_Cb cb; const void *userdata; Eina_List *sinks; Eina_List *sources; Eina_List *cards; } Context; static Context *ctx = NULL; typedef struct _Alsa_Emix_Sink { Emix_Sink sink; const char *hw_name; Eina_List *channels; } Alsa_Emix_Sink; typedef struct _Alsa_Emix_Source { Emix_Source source; const char *hw_name; Eina_List *channels; } Alsa_Emix_Source; /* * TODO problems: * * - mono stereo problem... */ /* * util functions */ static int _alsa_mixer_sink_changed_cb(snd_mixer_t *ctl, unsigned int mask EINA_UNUSED, snd_mixer_elem_t *elem EINA_UNUSED) { Alsa_Emix_Sink *sink = snd_mixer_get_callback_private(ctl); if (ctx->cb) ctx->cb((void *)ctx->userdata, EMIX_SINK_CHANGED_EVENT, (Emix_Sink *)sink); return 0; } static int _alsa_mixer_source_changed_cb(snd_mixer_t *ctl, unsigned int mask EINA_UNUSED, snd_mixer_elem_t *elem EINA_UNUSED) { Alsa_Emix_Source *source = snd_mixer_get_callback_private(ctl); if (ctx->cb) ctx->cb((void *)ctx->userdata, EMIX_SOURCE_CHANGED_EVENT, (Emix_Source *)source); return 0; } static void _alsa_channel_volume_get(snd_mixer_elem_t *channel, int *v, Eina_Bool capture) { long int min = 0, max = 100, vol = 0; int range, divide; if (capture) snd_mixer_selem_get_capture_volume_range(channel, &min, &max); else snd_mixer_selem_get_playback_volume_range(channel, &min, &max); divide = MAX_VOLUME + min; if (divide == 0) { divide = 1; min++; } range = max - min; if (range < 1) return; if (capture) snd_mixer_selem_get_capture_volume(channel, 0, &vol); else snd_mixer_selem_get_playback_volume(channel, 0, &vol); *v = ((vol + min) * divide) / range; } static void _alsa_channel_volume_set(snd_mixer_elem_t *channel, int v, Eina_Bool capture) { long int vol, min, max, divide, range; snd_mixer_selem_get_playback_volume_range(channel, &min, &max); divide = MAX_VOLUME + min; range = max - min; if (range < 1) return; vol = ((v * range) / divide) - min; if (!capture) snd_mixer_selem_set_playback_volume_all(channel, vol); else snd_mixer_selem_set_capture_volume_all(channel, vol); } /* * This will append a new device to the cards and call the ecore event for * a new device! */ static snd_mixer_t * _alsa_card_create(char *addr) { snd_mixer_t *control; if (snd_mixer_open(&control, 0) < 0) goto error_open; if (snd_mixer_attach(control, addr) < 0) goto error_load; if (snd_mixer_selem_register(control, NULL, NULL) < 0) goto error_load; if (snd_mixer_load(control)) goto error_load; return control; error_load: snd_mixer_close(control); error_open: return NULL; } static void _alsa_volume_create(Emix_Volume *volume, Eina_List *channels) { unsigned int i = 0, count = eina_list_count(channels); Eina_List *l; snd_mixer_elem_t *elem; volume->channel_count = count; volume->volumes = calloc(count, sizeof(int)); EINA_LIST_FOREACH(channels, l, elem) { _alsa_channel_volume_get(elem, &(volume->volumes[i]), EINA_FALSE); i++; } } static void _alsa_sink_mute_get(Alsa_Emix_Sink *as) { int i = 0; snd_mixer_elem_t *elem; elem = eina_list_data_get(as->channels); snd_mixer_selem_get_playback_switch(elem, 0, &i); as->sink.mute = !i; } static void _alsa_sources_mute_get(Alsa_Emix_Source *as) { int i = 0; snd_mixer_elem_t *elem; elem = eina_list_data_get(as->channels); snd_mixer_selem_get_capture_switch(elem, 0, &i); as->source.mute = !i; } static Alsa_Emix_Sink* _alsa_device_sink_create(const char *name, const char* hw_name, Eina_List *channels) { Alsa_Emix_Sink *sink; if (!(sink = calloc(1, sizeof(Alsa_Emix_Sink)))) { ERR("Allocation Failed"); return NULL; } sink->sink.name = eina_stringshare_add(name); sink->hw_name = eina_stringshare_add(hw_name); sink->channels = channels; _alsa_volume_create(&sink->sink.volume, channels); _alsa_sink_mute_get(sink); if (ctx->cb) { ctx->cb((void *)ctx->userdata, EMIX_SINK_ADDED_EVENT, (Emix_Sink *)sink); } ctx->sinks = eina_list_append(ctx->sinks, sink); return sink; } static Alsa_Emix_Source* _alsa_device_source_create(const char *name, const char* hw_name, Eina_List *channels) { Alsa_Emix_Source *source; if (!(source = calloc(1, sizeof(Alsa_Emix_Source)))) { ERR("Allocation Failed"); return NULL; } source->source.name = eina_stringshare_add(name); source->hw_name = eina_stringshare_add(hw_name); source->channels = channels; _alsa_volume_create(&source->source.volume, channels); _alsa_sources_mute_get(source); if (ctx->cb) ctx->cb((void *)ctx->userdata, EMIX_SOURCE_ADDED_EVENT, (Emix_Sink *)source); ctx->sources = eina_list_append(ctx->sources, source); return source; } static void _alsa_device_sink_free(Alsa_Emix_Sink *sink) { eina_stringshare_del(sink->hw_name); eina_stringshare_del(sink->sink.name); free(sink->sink.volume.volumes); free(sink); } static void _alsa_device_source_free(Alsa_Emix_Source *source) { eina_stringshare_del(source->hw_name); eina_stringshare_del(source->source.name); free(source->source.volume.volumes); free(source); } static char* _alsa_cards_name_get(char *name) { snd_ctl_t *control; snd_ctl_card_info_t *hw_info; char *result = NULL; snd_ctl_card_info_alloca(&hw_info); if (snd_ctl_open(&control, name, 0) < 0) { ERR("Failed to open device"); goto err; } if (snd_ctl_card_info(control, hw_info) < 0) { ERR("Failed to get card information"); goto err_open; } result = strdup(snd_ctl_card_info_get_name(hw_info)); err_open: snd_ctl_close(control); err: return result; } static void _alsa_cards_refresh(void) { int err, card_num = -1; Eina_List *tmp_source = NULL, *tmp_sink = NULL; while (((err = snd_card_next(&card_num)) == 0) && (card_num >= 0)) { char buf[PATH_MAX]; char *device_name; snd_mixer_t *mixer; snd_mixer_elem_t *elem; Alsa_Emix_Source *source; Alsa_Emix_Sink *sink; source = NULL; sink = NULL; tmp_source = NULL; tmp_sink = NULL; //generate card addr snprintf(buf, sizeof(buf), "hw:%d", card_num); //save the addr to see if there are missing devices in the cache list mixer = _alsa_card_create(buf); ctx->cards = eina_list_append(ctx->cards, mixer); //get elements of the device elem = snd_mixer_first_elem(mixer); for (; elem; elem = snd_mixer_elem_next(elem)) { long min = 0, max = 0; if (strncmp(snd_mixer_selem_get_name(elem), "Master", sizeof("Master") - 1)) continue; //check if its a source or a sink snd_mixer_selem_get_capture_volume_range(elem, &min, &max); DBG("Mixer element %p of dev %d has range %ld %ld", elem, card_num, min, max); if (min < max) { tmp_source = eina_list_append(tmp_source, elem); continue; } min = max = 0; snd_mixer_selem_get_playback_volume_range(elem, &min, &max); DBG("Mixer element %p of dev %d has range %ld %ld", elem, card_num, min, max); if (min < max) { tmp_sink = eina_list_append(tmp_sink, elem); continue; } } device_name = _alsa_cards_name_get(buf); //create the sinks / sources if (tmp_sink) { sink = _alsa_device_sink_create(device_name, buf, tmp_sink); snd_mixer_set_callback(mixer, _alsa_mixer_sink_changed_cb); snd_mixer_set_callback_private(mixer, sink); } if (tmp_source) { source = _alsa_device_source_create(device_name, buf, tmp_source); snd_mixer_set_callback(mixer, _alsa_mixer_source_changed_cb); snd_mixer_set_callback_private(mixer, source); } if (device_name) free(device_name); } } static Eina_Bool _alsa_init(Emix_Event_Cb cb, const void *data) { EINA_SAFETY_ON_NULL_RETURN_VAL(cb, EINA_FALSE); if (!ctx) ctx = calloc(1, sizeof(Context)); EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_FALSE); ctx->cb = cb; ctx->userdata = data; _alsa_cards_refresh(); //call the event because the backend is now ready to use ctx->cb((void *)ctx->userdata, EMIX_READY_EVENT, NULL); return EINA_TRUE; } static void _alsa_shutdown(void) { Alsa_Emix_Sink *sink; Alsa_Emix_Source *source; snd_mixer_t *mixer; EINA_SAFETY_ON_NULL_RETURN(ctx); EINA_LIST_FREE(ctx->sinks, sink) _alsa_device_sink_free(sink); EINA_LIST_FREE(ctx->sources, source) _alsa_device_source_free(source); EINA_LIST_FREE(ctx->cards, mixer) snd_mixer_close(mixer); free(ctx); ctx = NULL; } static const Eina_List* _alsa_sources_get(void) { EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL); return ctx->sources; } static void _alsa_sources_mute_set(Emix_Source *source, Eina_Bool mute) { Alsa_Emix_Source *s = (Alsa_Emix_Source*) source; Eina_List *node; snd_mixer_elem_t *elem; EINA_SAFETY_ON_FALSE_RETURN((ctx && source)); EINA_LIST_FOREACH(s->channels, node, elem) { if (!snd_mixer_selem_has_capture_switch(elem)) continue; if (snd_mixer_selem_set_capture_switch_all(elem, !mute) < 0) ERR("Failed to mute device\n"); } source->mute = mute; if (ctx->cb) ctx->cb((void *)ctx->userdata, EMIX_SOURCE_CHANGED_EVENT, (Emix_Source *)source); } static void _alsa_sources_volume_set(Emix_Source *source, Emix_Volume v) { Alsa_Emix_Source *s = (Alsa_Emix_Source*) source; unsigned int i; snd_mixer_elem_t *elem; EINA_SAFETY_ON_FALSE_RETURN((ctx && source)); if (v.channel_count != eina_list_count(s->channels)) { ERR("Volume struct doesnt have the same length than the channels"); return; } for (i = 0; i < v.channel_count; i++ ) { elem = eina_list_nth(s->channels, i); _alsa_channel_volume_set(elem, v.volumes[i], EINA_FALSE); s->source.volume.volumes[i] = v.volumes[i]; } if (ctx->cb) ctx->cb((void *)ctx->userdata, EMIX_SOURCE_CHANGED_EVENT, (Emix_Source *)s); } static const Eina_List* _alsa_sinks_get(void) { EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL); //FIXME fork or just return? /* Eina_List *result = NULL; Eina_List *node; void *data; EINA_LIST_FOREACH(ctx->sinks, node, data) { result = eina_list_append(result, data); }*/ return ctx->sinks; } static Eina_Bool _alsa_support(void) { return EINA_FALSE; } static void _alsa_sink_mute_set(Emix_Sink *sink, Eina_Bool mute) { Alsa_Emix_Sink *as = (Alsa_Emix_Sink*) sink; Eina_List *node; snd_mixer_elem_t *elem; EINA_SAFETY_ON_FALSE_RETURN((ctx && sink)); EINA_LIST_FOREACH(as->channels, node, elem) { if (!snd_mixer_selem_has_playback_switch(elem)) continue; if (snd_mixer_selem_set_playback_switch_all(elem, !mute) < 0) ERR("Failed to set mute(%d) device(%p)", mute, elem); } sink->mute = mute; if (ctx->cb) ctx->cb((void *)ctx->userdata, EMIX_SINK_CHANGED_EVENT, (Emix_Sink *)sink); } static void _alsa_sink_volume_set(Emix_Sink *sink, Emix_Volume v) { Alsa_Emix_Sink *s = (Alsa_Emix_Sink *)sink; unsigned int i; snd_mixer_elem_t *elem; EINA_SAFETY_ON_FALSE_RETURN((ctx && sink)); if (v.channel_count != eina_list_count(s->channels)) { ERR("Volume struct doesnt have the same length than the channels"); return; } for (i = 0; i < v.channel_count; i++ ) { elem = eina_list_nth(s->channels, i); _alsa_channel_volume_set(elem, v.volumes[i], EINA_FALSE); s->sink.volume.volumes[i] = v.volumes[i]; } if (ctx->cb) ctx->cb((void *)ctx->userdata, EMIX_SINK_CHANGED_EVENT, (Emix_Sink *)s); } static int _max_volume(void) { return MAX_VOLUME; } static Emix_Backend _alsa_backend = { _alsa_init, _alsa_shutdown, _max_volume, _alsa_sinks_get, _alsa_support, /*default support*/ NULL, /*get*/ NULL, /*set*/ _alsa_sink_mute_set, /*mute_set*/ _alsa_sink_volume_set, /*volume_set*/ NULL, /* port set */ _alsa_support, /*change support*/ NULL, /*sink input get*/ NULL,/*sink input mute set*/ NULL,/*sink input volume set*/ NULL,/*sink input sink change*/ _alsa_sources_get,/*source*/ _alsa_sources_mute_set,/* source mute set */ _alsa_sources_volume_set, /* source volume set */ NULL /* advanced options */ }; E_API Emix_Backend * emix_backend_alsa_get(void) { return &_alsa_backend; } E_API const char *emix_backend_alsa_name = "ALSA";