You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

557 lines
13 KiB

#include "emix.h"
#include <alsa/asoundlib.h>
#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";