/* * Copyright (C) 2014 Michal Ratajsky * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the licence, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, see . */ #include #include #include #include #include #include "alsa-constants.h" #include "alsa-element.h" #include "alsa-stream-control.h" #include "alsa-stream-input-control.h" static void alsa_stream_input_control_class_init (AlsaStreamInputControlClass *klass); static void alsa_stream_input_control_init (AlsaStreamInputControl *control); G_DEFINE_TYPE (AlsaStreamInputControl, alsa_stream_input_control, ALSA_TYPE_STREAM_CONTROL) static gboolean alsa_stream_input_control_load (AlsaStreamControl *control); static gboolean alsa_stream_input_control_set_mute (AlsaStreamControl *control, gboolean mute); static gboolean alsa_stream_input_control_set_volume (AlsaStreamControl *control, guint volume); static gboolean alsa_stream_input_control_set_channel_volume (AlsaStreamControl *control, snd_mixer_selem_channel_id_t channel, guint volume); static gboolean alsa_stream_input_control_get_volume_from_decibel (AlsaStreamControl *control, gdouble decibel, guint *volume); static gboolean alsa_stream_input_control_get_decibel_from_volume (AlsaStreamControl *control, guint volume, gdouble *decibel); static void read_volume_data (snd_mixer_elem_t *el, AlsaControlData *data); static void alsa_stream_input_control_class_init (AlsaStreamInputControlClass *klass) { AlsaStreamControlClass *control_class; control_class = ALSA_STREAM_CONTROL_CLASS (klass); control_class->load = alsa_stream_input_control_load; control_class->set_mute = alsa_stream_input_control_set_mute; control_class->set_volume = alsa_stream_input_control_set_volume; control_class->set_channel_volume = alsa_stream_input_control_set_channel_volume; control_class->get_volume_from_decibel = alsa_stream_input_control_get_volume_from_decibel; control_class->get_decibel_from_volume = alsa_stream_input_control_get_decibel_from_volume; } static void alsa_stream_input_control_init (AlsaStreamInputControl *control) { } AlsaStreamControl * alsa_stream_input_control_new (const gchar *name, const gchar *label, MateMixerStreamControlRole role, AlsaStream *stream) { return g_object_new (ALSA_TYPE_STREAM_INPUT_CONTROL, "name", name, "label", label, "role", role, "stream", stream, NULL); } static gboolean alsa_stream_input_control_load (AlsaStreamControl *control) { AlsaControlData data; snd_mixer_elem_t *el; g_return_val_if_fail (ALSA_IS_STREAM_INPUT_CONTROL (control), FALSE); el = alsa_element_get_snd_element (ALSA_ELEMENT (control)); if G_UNLIKELY (el == NULL) return FALSE; if G_UNLIKELY (snd_mixer_selem_has_capture_volume (el) == 0 && snd_mixer_selem_has_common_volume (el) == 0) { g_warn_if_reached (); return FALSE; } memset (&data, 0, sizeof (AlsaControlData)); /* We model any control switch as mute */ if (snd_mixer_selem_has_capture_switch (el) == 1 || snd_mixer_selem_has_common_switch (el) == 1) data.switch_usable = TRUE; data.active = snd_mixer_selem_is_active (el); /* Read the volume data but do not error out if it fails, since ALSA reports * the control to have a volume, expect the control to match what we need - slider * with an optional mute toggle. * If it fails to read the volume data, just treat it as a volumeless control */ read_volume_data (el, &data); alsa_stream_control_set_data (control, &data); return TRUE; } static gboolean alsa_stream_input_control_set_mute (AlsaStreamControl *control, gboolean mute) { snd_mixer_elem_t *el; gint ret; g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (control), FALSE); el = alsa_element_get_snd_element (ALSA_ELEMENT (control)); if G_UNLIKELY (el == NULL) return FALSE; /* Set the switch for all channels */ ret = snd_mixer_selem_set_capture_switch_all (el, !mute); if (ret < 0) { g_warning ("Failed to set capture switch: %s", snd_strerror (ret)); return FALSE; } return TRUE; } static gboolean alsa_stream_input_control_set_volume (AlsaStreamControl *control, guint volume) { snd_mixer_elem_t *el; gint ret; g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (control), FALSE); el = alsa_element_get_snd_element (ALSA_ELEMENT (control)); if G_UNLIKELY (el == NULL) return FALSE; /* Set the volume for all channels */ ret = snd_mixer_selem_set_capture_volume_all (el, volume); if (ret < 0) { g_warning ("Failed to set volume: %s", snd_strerror (ret)); return FALSE; } return TRUE; } static gboolean alsa_stream_input_control_set_channel_volume (AlsaStreamControl *control, snd_mixer_selem_channel_id_t channel, guint volume) { snd_mixer_elem_t *el; gint ret; g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (control), FALSE); el = alsa_element_get_snd_element (ALSA_ELEMENT (control)); if G_UNLIKELY (el == NULL) return FALSE; /* Set the volume for a single channel */ ret = snd_mixer_selem_set_capture_volume (el, channel, volume); if (ret < 0) { g_warning ("Failed to set channel volume: %s", snd_strerror (ret)); return FALSE; } return TRUE; } static gboolean alsa_stream_input_control_get_volume_from_decibel (AlsaStreamControl *control, gdouble decibel, guint *volume) { snd_mixer_elem_t *el; glong value; gint ret; g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (control), FALSE); el = alsa_element_get_snd_element (ALSA_ELEMENT (control)); if G_UNLIKELY (el == NULL) return FALSE; ret = snd_mixer_selem_ask_capture_dB_vol (el, (glong) (decibel * 100), 0, &value); if (ret < 0) { g_warning ("Failed to convert volume: %s", snd_strerror (ret)); return FALSE; } *volume = value; return TRUE; } static gboolean alsa_stream_input_control_get_decibel_from_volume (AlsaStreamControl *control, guint volume, gdouble *decibel) { snd_mixer_elem_t *el; glong value; gint ret; g_return_val_if_fail (ALSA_IS_STREAM_CONTROL (control), FALSE); el = alsa_element_get_snd_element (ALSA_ELEMENT (control)); if G_UNLIKELY (el == NULL) return FALSE; ret = snd_mixer_selem_ask_capture_vol_dB (el, (glong) volume, &value); if (ret < 0) { g_warning ("Failed to convert volume: %s", snd_strerror (ret)); return FALSE; } *decibel = value / 100.0; return TRUE; } static void read_volume_data (snd_mixer_elem_t *el, AlsaControlData *data) { glong volume; glong min, max; gint ret; gint i; /* Read volume ranges, this call should never fail on valid input */ ret = snd_mixer_selem_get_capture_volume_range (el, &min, &max); if G_UNLIKELY (ret < 0) { g_warning ("Failed to read capture volume range: %s", snd_strerror (ret)); return; } data->min = (guint) min; data->max = (guint) max; /* This fails when decibels are not supported */ ret = snd_mixer_selem_get_capture_dB_range (el, &min, &max); if (ret == 0) { data->min_decibel = min / 100.0; data->max_decibel = max / 100.0; } else data->min_decibel = data->max_decibel = -MATE_MIXER_INFINITY; for (i = 0; i < MATE_MIXER_CHANNEL_MAX; i++) data->v[i] = data->min; data->volume = data->min; data->volume_joined = snd_mixer_selem_has_capture_volume_joined (el); if (data->switch_usable == TRUE) data->switch_joined = snd_mixer_selem_has_capture_switch_joined (el); if (snd_mixer_selem_is_capture_mono (el) == 1) { /* Special handling for single channel controls */ ret = snd_mixer_selem_get_capture_volume (el, SND_MIXER_SCHN_MONO, &volume); if (ret == 0) { data->channels = 1; data->c[0] = MATE_MIXER_CHANNEL_MONO; data->v[0] = data->volume = (guint) volume; } else { g_warning ("Failed to read capture volume: %s", snd_strerror (ret)); } if (data->switch_usable == TRUE) { gint value; ret = snd_mixer_selem_get_capture_switch (el, SND_MIXER_SCHN_MONO, &value); if G_LIKELY (ret == 0) data->m[0] = !value; } } else { snd_mixer_selem_channel_id_t channel; /* We use numeric channel indices, but ALSA only works with channel * positions, go over all the positions supported by ALSA and create * a list of channels */ for (channel = 0; channel < SND_MIXER_SCHN_LAST; channel++) { if (snd_mixer_selem_has_capture_channel (el, channel) == 0) continue; if (data->switch_usable == TRUE) { gint value; ret = snd_mixer_selem_get_capture_switch (el, channel, &value); if (ret == 0) data->m[channel] = !value; } ret = snd_mixer_selem_get_capture_volume (el, channel, &volume); if (ret < 0) { g_warning ("Failed to read capture volume: %s", snd_strerror (ret)); continue; } data->channels++; /* The single value volume is the highest channel volume */ if (data->volume < volume) data->volume = volume; data->c[channel] = alsa_channel_map_from[channel]; data->v[channel] = (guint) volume; } } }