diff options
Diffstat (limited to 'backends/alsa/alsa-stream-output-control.c')
-rw-r--r-- | backends/alsa/alsa-stream-output-control.c | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/backends/alsa/alsa-stream-output-control.c b/backends/alsa/alsa-stream-output-control.c new file mode 100644 index 0000000..5a3e6b3 --- /dev/null +++ b/backends/alsa/alsa-stream-output-control.c @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <glib.h> +#include <glib-object.h> +#include <alsa/asoundlib.h> + +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#include "alsa-element.h" +#include "alsa-stream-control.h" +#include "alsa-stream-output-control.h" + +static void alsa_stream_output_control_class_init (AlsaStreamOutputControlClass *klass); +static void alsa_stream_output_control_init (AlsaStreamOutputControl *control); + +G_DEFINE_TYPE (AlsaStreamOutputControl, alsa_stream_output_control, ALSA_TYPE_STREAM_CONTROL) + +static gboolean alsa_stream_output_control_load (AlsaStreamControl *control); + +static gboolean alsa_stream_output_control_set_mute (AlsaStreamControl *control, + gboolean mute); + +static gboolean alsa_stream_output_control_set_volume (AlsaStreamControl *control, + guint volume); + +static gboolean alsa_stream_output_control_set_channel_volume (AlsaStreamControl *control, + snd_mixer_selem_channel_id_t channel, + guint volume); + +static gboolean alsa_stream_output_control_get_volume_from_decibel (AlsaStreamControl *control, + gdouble decibel, + guint *volume); + +static gboolean alsa_stream_output_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_output_control_class_init (AlsaStreamOutputControlClass *klass) +{ + AlsaStreamControlClass *control_class; + + control_class = ALSA_STREAM_CONTROL_CLASS (klass); + + control_class->load = alsa_stream_output_control_load; + control_class->set_mute = alsa_stream_output_control_set_mute; + control_class->set_volume = alsa_stream_output_control_set_volume; + control_class->set_channel_volume = alsa_stream_output_control_set_channel_volume; + control_class->get_volume_from_decibel = alsa_stream_output_control_get_volume_from_decibel; + control_class->get_decibel_from_volume = alsa_stream_output_control_get_decibel_from_volume; +} + +static void +alsa_stream_output_control_init (AlsaStreamOutputControl *control) +{ +} + +AlsaStreamControl * +alsa_stream_output_control_new (const gchar *name, + const gchar *label, + MateMixerStreamControlRole role) +{ + return g_object_new (ALSA_TYPE_STREAM_OUTPUT_CONTROL, + "name", name, + "label", label, + "role", role, + NULL); +} + +static gboolean +alsa_stream_output_control_load (AlsaStreamControl *control) +{ + AlsaControlData data; + snd_mixer_elem_t *el; + + g_return_val_if_fail (ALSA_IS_STREAM_OUTPUT_CONTROL (control), FALSE); + + el = alsa_element_get_snd_element (ALSA_ELEMENT (control)); + if G_UNLIKELY (el == NULL) + return FALSE; + + /* Expect that the element has a volume control */ + if G_UNLIKELY (snd_mixer_selem_has_playback_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_playback_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_output_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_playback_switch_all (el, !mute); + if (ret < 0) { + g_warning ("Failed to set playback switch: %s", snd_strerror (ret)); + return FALSE; + } + return TRUE; +} + +static gboolean +alsa_stream_output_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_playback_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_output_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 channels, the volume may still be "joined" and + * set all the channels by itself */ + ret = snd_mixer_selem_set_playback_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_output_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_playback_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_output_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_playback_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_playback_volume_range (el, &min, &max); + if G_UNLIKELY (ret < 0) { + g_warning ("Failed to read playback 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_playback_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_playback_volume_joined (el); + + if (data->switch_usable == TRUE) + data->switch_joined = snd_mixer_selem_has_playback_switch_joined (el); + + if (snd_mixer_selem_is_playback_mono (el) == 1) { + /* Special handling for single channel controls */ + ret = snd_mixer_selem_get_playback_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 playback volume: %s", snd_strerror (ret)); + } + + if (data->switch_usable == TRUE) { + gint value; + + ret = snd_mixer_selem_get_playback_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_playback_channel (el, channel) == 0) + continue; + + if (data->switch_usable == TRUE) { + gint value; + + ret = snd_mixer_selem_get_playback_switch (el, channel, &value); + if (ret == 0) + data->m[channel] = !value; + } + + ret = snd_mixer_selem_get_playback_volume (el, channel, &volume); + if (ret < 0) { + g_warning ("Failed to read playback 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; + } + } +} |