2015-06-08 05:00:23 -07:00
|
|
|
#include "emix.h"
|
|
|
|
#include <alsa/asoundlib.h>
|
|
|
|
|
|
|
|
#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, max, vol;
|
|
|
|
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 = 100 + 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);
|
|
|
|
|
2015-06-13 23:01:44 -07:00
|
|
|
*v = ((vol + min) * divide) / range;
|
2015-06-08 05:00:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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 = 100 + min;
|
|
|
|
range = max - min;
|
|
|
|
if (range < 1)
|
|
|
|
return;
|
|
|
|
|
2015-06-13 23:01:44 -07:00
|
|
|
vol = ((v * range) / divide) - min;
|
2015-06-08 05:00:23 -07:00
|
|
|
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);
|
|
|
|
_alsa_volume_create(&sink->sink.volume, channels);
|
|
|
|
sink->hw_name = eina_stringshare_add(hw_name);
|
|
|
|
sink->channels = 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);
|
|
|
|
_alsa_volume_create(&source->source.volume, channels);
|
|
|
|
source->hw_name = eina_stringshare_add(hw_name);
|
|
|
|
source->channels = 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))
|
|
|
|
{
|
|
|
|
//check if its a source or a sink
|
|
|
|
if (snd_mixer_selem_has_capture_volume(elem))
|
|
|
|
tmp_source = eina_list_append(tmp_source, elem);
|
|
|
|
else
|
|
|
|
tmp_sink = eina_list_append(tmp_sink, elem);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static Emix_Backend
|
|
|
|
_alsa_backend =
|
|
|
|
{
|
|
|
|
_alsa_init,
|
|
|
|
_alsa_shutdown,
|
|
|
|
_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";
|