double getnormvolume(snd_mixer_elem_t *elem) { long min=0, max=0, value = 0; double normalized=0, min_norm = 0; int err; err = snd_mixer_selem_get_playback_dB_range(elem, &min, &max); if (err < 0 || min >= max) { err = snd_mixer_selem_get_playback_volume_range(elem, &min, &max); if (err < 0 || min == max) return 0; err = snd_mixer_selem_get_playback_volume(elem, channel, &value); if (err < 0) return 0; return (value - min) / (double)(max - min); } err = snd_mixer_selem_get_playback_dB(elem, channel, &value); if (err < 0) return 0; if (use_linear_dB_scale(min, max)) return (value - min) / (double)(max - min); normalized = exp10((value - max) / 6000.0); if (min != SND_CTL_TLV_DB_GAIN_MUTE) { min_norm = exp10((min - max) / 6000.0); normalized = (normalized - min_norm) / (1 - min_norm); } return normalized; }
/** * Adjusts the current volume and sends a notification (if enabled). * * @param vol new volume value * @param dir select direction (-1 = accurate or first bellow, 0 = accurate, * 1 = accurate or first above) * @param notify whether to send notification * @return 0 on success otherwise negative error code */ int setvol(int vol, int dir, gboolean notify) { long min = 0, max = 0, value; int cur_perc = getvol(); double dvol = 0.01 * vol; int err = snd_mixer_selem_get_playback_dB_range(elem, &min, &max); if (err < 0 || min >= max || !normalize_vol()) { err = snd_mixer_selem_get_playback_volume_range(elem, &min, &max); value = lrint_dir(dvol * (max - min), dir) + min; snd_mixer_selem_set_playback_volume_all(elem, value); if (enable_noti && notify && cur_perc != getvol()) do_notify_volume(getvol(), FALSE); // intentionally set twice return snd_mixer_selem_set_playback_volume_all(elem, value); } if (use_linear_dB_scale(min, max)) { value = lrint_dir(dvol * (max - min), dir) + min; return snd_mixer_selem_set_playback_dB_all(elem, value, dir); } if (min != SND_CTL_TLV_DB_GAIN_MUTE) { double min_norm = exp10((min - max) / 6000.0); dvol = dvol * (1 - min_norm) + min_norm; } value = lrint_dir(6000.0 * log10(dvol), dir) + max; snd_mixer_selem_set_playback_dB_all(elem, value, dir); if (enable_noti && notify && cur_perc != getvol()) do_notify_volume(getvol(), FALSE); // intentionally set twice return snd_mixer_selem_set_playback_dB_all(elem, value, dir); }
static gint _j4status_alsa_section_elem_callback(snd_mixer_elem_t *elem, guint mask) { J4statusAlsaSection *section = snd_mixer_elem_get_callback_private(elem); if ( mask == SND_CTL_EVENT_MASK_REMOVE ) return 0; if ( mask & SND_CTL_EVENT_MASK_INFO ) { glong min, max; int error; error = snd_mixer_selem_get_playback_dB_range(elem, &min, &max); if ( ( error == 0 ) && ( min < max ) ) section->use_dB = TRUE; else { section->use_dB = FALSE; snd_mixer_selem_get_playback_volume_range(elem, &min, &max); } section->min = min; section->max = max; } if ( mask & SND_CTL_EVENT_MASK_VALUE ) _j4status_alsa_section_update(section, elem); return 0; }
static int init(int argc, char **argv) { const char *str; int value; int hardware_mixer = 0; config.audio_backend_latency_offset = 0; // this is the default for ALSA config.audio_backend_buffer_desired_length = 6615; // default for alsa with a software mixer // get settings from settings file first, allow them to be overridden by command line options if (config.cfg != NULL) { /* Get the desired buffer size setting. */ if (config_lookup_int(config.cfg, "alsa.audio_backend_buffer_desired_length_software", &value)) { if ((value < 0) || (value > 66150)) die("Invalid alsa audio backend buffer desired length (software) \"%d\". It should be between 0 and " "66150, default is 6615", value); else { config.audio_backend_buffer_desired_length = value; } } /* Get the latency offset. */ if (config_lookup_int(config.cfg, "alsa.audio_backend_latency_offset", &value)) { if ((value < -66150) || (value > 66150)) die("Invalid alsa audio backend buffer latency offset \"%d\". It should be between -66150 and +66150, default is 0", value); else config.audio_backend_latency_offset = value; } /* Get the Output Device Name. */ if (config_lookup_string(config.cfg, "alsa.output_device", &str)) { alsa_out_dev = (char *)str; } /* Get the Mixer Type setting. */ if (config_lookup_string(config.cfg, "alsa.mixer_type", &str)) { inform("The alsa mixer_type setting is deprecated and has been ignored. FYI, using the \"mixer_control_name\" setting automatically chooses a hardware mixer."); } /* Get the Mixer Device Name. */ if (config_lookup_string(config.cfg, "alsa.mixer_device", &str)) { alsa_mix_dev = (char *)str; } /* Get the Mixer Control Name. */ if (config_lookup_string(config.cfg, "alsa.mixer_control_name", &str)) { alsa_mix_ctrl = (char *)str; hardware_mixer = 1; } } optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour argv--; // so we shift the arguments to satisfy getopt() argc++; // some platforms apparently require optreset = 1; - which? int opt; while ((opt = getopt(argc, argv, "d:t:m:c:i:")) > 0) { switch (opt) { case 'd': alsa_out_dev = optarg; break; case 't': inform("The alsa backend -t option is deprecated and has been ignored. FYI, using the -c option automatically chooses a hardware mixer."); break; case 'm': alsa_mix_dev = optarg; break; case 'c': alsa_mix_ctrl = optarg; hardware_mixer = 1; break; case 'i': alsa_mix_index = strtol(optarg, NULL, 10); break; default: help(); die("Invalid audio option -%c specified", opt); } } if (optind < argc) die("Invalid audio argument: %s", argv[optind]); debug(1,"Output device name is \"%s\".",alsa_out_dev); if (!hardware_mixer) return 0; if (alsa_mix_dev == NULL) alsa_mix_dev = alsa_out_dev; int ret = 0; snd_mixer_selem_id_alloca(&alsa_mix_sid); snd_mixer_selem_id_set_index(alsa_mix_sid, alsa_mix_index); snd_mixer_selem_id_set_name(alsa_mix_sid, alsa_mix_ctrl); if ((snd_mixer_open(&alsa_mix_handle, 0)) < 0) die("Failed to open mixer"); debug(1,"Mixer device name is \"%s\".",alsa_mix_dev); if ((snd_mixer_attach(alsa_mix_handle, alsa_mix_dev)) < 0) die("Failed to attach mixer"); if ((snd_mixer_selem_register(alsa_mix_handle, NULL, NULL)) < 0) die("Failed to register mixer element"); ret = snd_mixer_load(alsa_mix_handle); if (ret < 0) die("Failed to load mixer element"); debug(1,"Mixer Control name is \"%s\".",alsa_mix_ctrl); alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid); if (!alsa_mix_elem) die("Failed to find mixer element"); if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) < 0) debug(1, "Can't read mixer's [linear] min and max volumes."); else { if (snd_mixer_selem_get_playback_dB_range (alsa_mix_elem, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) { audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff audio_alsa.parameters = ¶meters; // likewise the parameters stuff if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) { // Raspberry Pi does this debug(1, "Lowest dB value is a mute."); if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1, &alsa_mix_mindb) == 0) debug(1, "Can't get dB value corresponding to a \"volume\" of 1."); } debug(1, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0); if (config.volume_range_db) { long suggested_alsa_min_db = alsa_mix_maxdb - (long)trunc(config.volume_range_db*100); if (suggested_alsa_min_db > alsa_mix_mindb) alsa_mix_mindb = suggested_alsa_min_db; else inform("The volume_range_db setting, %f is greater than the native range of the mixer %f, so it is ignored.",config.volume_range_db,(alsa_mix_maxdb-alsa_mix_mindb)/100.0); }else{ if(config.isset_volume_min_db){ long suggested_alsa_min_db = (long) trunc(config.volume_min_db * 100); if(suggested_alsa_min_db > alsa_mix_mindb){ alsa_mix_mindb = suggested_alsa_min_db; } } if(config.isset_volume_max_db){ long suggested_alsa_max_db = (long) trunc(config.volume_max_db * 100); if(suggested_alsa_max_db < alsa_mix_maxdb){ alsa_mix_maxdb = suggested_alsa_max_db; } } } } else { // use the linear scale and do the db conversion ourselves debug(1, "note: the hardware mixer specified -- \"%s\" -- does not have a dB volume scale, so it can't be used.",alsa_mix_ctrl); /* debug(1, "Min and max volumes are %d and %d.",alsa_mix_minv,alsa_mix_maxv); alsa_mix_maxdb = 0; if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0)) alsa_mix_mindb = -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0)); else if (alsa_mix_maxv!=0) alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0); audio_alsa.volume = &linear_volume; // insert the linear volume function audio_alsa.parameters = ¶meters; // likewise the parameters stuff debug(1,"Max and min dB calculated are %d and %d.",alsa_mix_maxdb,alsa_mix_mindb); */ } } if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) { has_mute = 1; debug(1, "Has mute ability."); } return 0; }
static void set_mixer(const char *device, const char *mixer, int mixer_index, bool setmax, float ldB, float rdB) { int err; long nleft, nright; long min, max; snd_mixer_t *handle; snd_mixer_selem_id_t *sid; snd_mixer_elem_t* elem; if ((err = snd_mixer_open(&handle, 0)) < 0) { LOG_ERROR("open error: %s", snd_strerror(err)); return; } if ((err = snd_mixer_attach(handle, device)) < 0) { LOG_ERROR("attach error: %s", snd_strerror(err)); snd_mixer_close(handle); return; } if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { LOG_ERROR("register error: %s", snd_strerror(err)); snd_mixer_close(handle); return; } if ((err = snd_mixer_load(handle)) < 0) { LOG_ERROR("load error: %s", snd_strerror(err)); snd_mixer_close(handle); return; } snd_mixer_selem_id_alloca(&sid); snd_mixer_selem_id_set_index(sid, mixer_index); snd_mixer_selem_id_set_name(sid, mixer); if ((elem = snd_mixer_find_selem(handle, sid)) == NULL) { LOG_ERROR("error find selem %s", mixer); snd_mixer_close(handle); return; } if (snd_mixer_selem_has_playback_switch(elem)) { snd_mixer_selem_set_playback_switch_all(elem, 1); // unmute } err = snd_mixer_selem_get_playback_dB_range(elem, &min, &max); if (err < 0 || max - min < 1000) { // unable to get db range or range is less than 10dB - ignore and set using raw values if ((err = snd_mixer_selem_get_playback_volume_range(elem, &min, &max)) < 0) { LOG_ERROR("unable to get volume raw range"); } else { long lraw, rraw; if (setmax) { lraw = rraw = max; } else { lraw = ((ldB > -MINVOL_DB ? MINVOL_DB + floor(ldB) : 0) / MINVOL_DB * (max-min)) + min; rraw = ((rdB > -MINVOL_DB ? MINVOL_DB + floor(rdB) : 0) / MINVOL_DB * (max-min)) + min; } LOG_DEBUG("setting vol raw [%ld..%ld]", min, max); if ((err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, lraw)) < 0) { LOG_ERROR("error setting left volume: %s", snd_strerror(err)); } if ((err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, rraw)) < 0) { LOG_ERROR("error setting right volume: %s", snd_strerror(err)); } } } else { // set db directly LOG_DEBUG("setting vol dB [%ld..%ld]", min, max); if (setmax) { // set to 0dB if available as this should be max volume for music recored at max pcm values if (max >= 0 && min <= 0) { ldB = rdB = 0; } else { ldB = rdB = max; } } if ((err = snd_mixer_selem_set_playback_dB(elem, SND_MIXER_SCHN_FRONT_LEFT, 100 * ldB, 1)) < 0) { LOG_ERROR("error setting left volume: %s", snd_strerror(err)); } if ((err = snd_mixer_selem_set_playback_dB(elem, SND_MIXER_SCHN_FRONT_RIGHT, 100 * rdB, 1)) < 0) { LOG_ERROR("error setting right volume: %s", snd_strerror(err)); } } if ((err = snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &nleft)) < 0) { LOG_ERROR("error getting left vol: %s", snd_strerror(err)); } if ((err = snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, &nright)) < 0) { LOG_ERROR("error getting right vol: %s", snd_strerror(err)); } LOG_DEBUG("%s left: %3.1fdB -> %ld right: %3.1fdB -> %ld", mixer, ldB, nleft, rdB, nright); snd_mixer_close(handle); }