/*-*- Mode: C; c-file-style: "linux"; indent-tabs-mode: nil; c-basic-offset: 8 -*-*/ #include #include #include static double linear_to_dB(double v) { return 20.0 * log10(v); } static double dB_to_linear(double v) { return pow(10.0, v / 20.0); } static float *generate_signal(unsigned n_samples, double amplitude, double frequency) { float *r; unsigned i; if (!(r = malloc(n_samples * sizeof(float)))) return NULL; for (i = 0; i < n_samples; i++) r[i] = amplitude * sin(((double) i*frequency*M_PI*2)/(double) n_samples); return r; } static snd_pcm_t *open_pcm(const char *name, unsigned *rate) { snd_pcm_t *d = NULL; int r; snd_pcm_hw_params_t *hw; snd_pcm_uframes_t t; snd_output_t *output = NULL; int dir = 1; snd_pcm_hw_params_alloca(&hw); printf("Opening %s for PCM.\n", name); if ((r = snd_pcm_open(&d, name, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { fprintf(stderr, "Cannot open audio device %s: %s\n", name, snd_strerror(r)); goto finish; } if ((r = snd_pcm_hw_params_any(d, hw)) < 0) { fprintf(stderr, "Cannot initialize hardware parameters: %s\n", snd_strerror(r)); goto finish; } if ((r = snd_pcm_hw_params_set_access(d, hw, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { fprintf(stderr, "Cannot set access type: %s\n", snd_strerror(r)); goto finish; } if ((r = snd_pcm_hw_params_set_format(d, hw, SND_PCM_FORMAT_FLOAT_LE)) < 0) { fprintf(stderr, "Cannot set sample format: %s\n", snd_strerror(r)); goto finish; } if ((r = snd_pcm_hw_params_set_rate_near(d, hw, rate, &dir)) < 0) { fprintf(stderr, "Cannot set sample rate: %s\n", snd_strerror(r)); goto finish; } if ((r = snd_pcm_hw_params_set_channels(d, hw, 1)) < 0) { fprintf(stderr, "Cannot set channel count: %s\n", snd_strerror(r)); goto finish; } t = *rate; if ((r = snd_pcm_hw_params_set_buffer_size_near(d, hw, &t)) < 0) { fprintf(stderr, "Cannot set buffer size: %s\n", snd_strerror(r)); goto finish; } if ((r = snd_pcm_hw_params(d, hw)) < 0) { fprintf(stderr, "Cannot set parameters: %s\n", snd_strerror(r)); goto finish; } if ((r = snd_output_stdio_attach(&output, stderr, 0)) < 0) { fprintf(stderr, "Cannot attach to stderr: %s\n", snd_strerror(r)); goto finish; } if ((r = snd_pcm_dump(d, output)) < 0) { fprintf(stderr, "Cannot dump status: %s\n", snd_strerror(r)); goto finish; } snd_output_close(output); if ((r = snd_pcm_prepare(d)) < 0) { fprintf(stderr, "Preparing failed: %s\n", snd_strerror(r)); goto finish; } return d; finish: if (d) snd_pcm_close(d); if (output) snd_output_close(output); return NULL; } static int play_pcm(snd_pcm_t *pcm, float *samples, unsigned n_samples) { snd_pcm_sframes_t t; while (n_samples > 0) { if ((t = snd_pcm_writei(pcm, samples, n_samples)) <= 0) { int r; if ((r = snd_pcm_recover(pcm, t, 0)) >= 0) continue; fprintf(stderr, "Failed to write samples: %s\n", snd_strerror(r)); return r; } n_samples -= t; samples += t; } /* snd_pcm_drain(pcm); */ return 0; } static snd_mixer_t* open_mixer(const char *name) { snd_mixer_t *d; int r; printf("Opening %s for control.\n", name); if ((r = snd_mixer_open(&d, 0)) < 0) { fprintf(stderr, "Cannot open mixer device %s: %s\n", name, snd_strerror(r)); goto finish; } if ((r = snd_mixer_attach(d, name)) < 0) { fprintf(stderr, "Unable to attach to mixer %s: %s\n", name, snd_strerror(r)); goto finish; } if ((r = snd_mixer_selem_register(d, NULL, NULL)) < 0) { fprintf(stderr, "Unable to register mixer: %s\n", snd_strerror(r)); goto finish; } if ((r = snd_mixer_load(d)) < 0) { fprintf(stderr, "Unable to load mixer: %s\n", snd_strerror(r)); goto finish; } return d; finish: if (d) snd_mixer_close(d); return NULL; } static snd_mixer_elem_t* find_element( snd_mixer_t *m, const char *name, long *min_discrete, long *max_discrete) { snd_mixer_elem_t *selem; snd_mixer_selem_id_t *sid; long min_dB, max_dB; int r; snd_mixer_selem_id_alloca(&sid); snd_mixer_selem_id_set_name(sid, name); snd_mixer_selem_id_set_index(sid, 0); if (!(selem = snd_mixer_find_selem(m, sid))) { fprintf(stderr, "Unable to find element %s.\n", name); return NULL; } if (!snd_mixer_selem_has_playback_volume(selem)) { fprintf(stderr, "Element %s does not control playback volume.\n", name); return NULL; } if ((r = snd_mixer_selem_get_playback_volume_range(selem, min_discrete, max_discrete)) < 0) { fprintf(stderr, "Failed to read element range data from %s: %s\n", name, snd_strerror(r)); return NULL; } if ((r = snd_mixer_selem_get_playback_dB_range(selem, &min_dB, &max_dB)) < 0) { fprintf(stderr, "Failed to read element dB data from %s: %s\n", name, snd_strerror(r)); return NULL; } printf("Using element '%s'.\n" "Element volume range is %li..%li (%0.2f..%0.2f dB).\n", snd_mixer_selem_get_name(selem), *min_discrete, *max_discrete, (double) min_dB / 100.0, (double) max_dB / 100.0); return selem; } int main(int argc, char *argv[]) { snd_pcm_t *pcm = NULL; snd_mixer_t *mixer = NULL; snd_mixer_elem_t *selem = NULL; char pcm_name[64], ctl_name[64]; int ret = 1, r; unsigned rate; long min_discrete, max_discrete, step1, step2, step1_alsa_dB, step2_alsa_dB; double step1_dB, step2_dB, attenuation; float *signal1 = NULL, *signal2 = NULL; if (argc < 3 || argc > 5) { fprintf(stderr, "Wrong number of arguments:\n" "%s CARD ELEMENT [VOLUME1] [VOLUME2]\n", argv[0]); return 0; } snprintf(pcm_name, sizeof(pcm_name)-1, "plughw:%s", argv[1]); snprintf(ctl_name, sizeof(ctl_name)-1, "hw:%s", argv[1]); pcm_name[sizeof(pcm_name)-1] = ctl_name[sizeof(ctl_name)-1] = 0; rate = 44100; if (!(pcm = open_pcm(pcm_name, &rate))) goto finish; if (!(mixer = open_mixer(ctl_name))) goto finish; if (!(selem = find_element(mixer, argv[2], &min_discrete, &max_discrete))) goto finish; if (argc >= 4) { char *e = NULL; errno = 0; step1 = strtol(argv[3], &e, 0); if (errno != 0 || !e || *e) { fprintf(stderr, "Failed to parse volume step #1: %s\n", argv[3]); goto finish; } } else step1 = min_discrete; if (argc >= 5) { char *e = NULL; errno = 0; step2 = strtol(argv[4], &e, 0); if (errno != 0 || !e || *e) { fprintf(stderr, "Failed to parse volume step #2: %s\n", argv[4]); goto finish; } } else step2 = max_discrete; if (step1 > step2) { long u = step1; u = step2; step2 = step1; step1 = u; } if (step1 < min_discrete) step1 = min_discrete; if (step1 > max_discrete) step1 = max_discrete; if (step2 < min_discrete) step2 = min_discrete; if (step2 > max_discrete) step2 = max_discrete; printf("Testing volume steps %li vs. %li.\n", step1, step2); if ((r = snd_mixer_selem_ask_playback_vol_dB(selem, step1, &step1_alsa_dB)) < 0 || (r = snd_mixer_selem_ask_playback_vol_dB(selem, step2, &step2_alsa_dB)) < 0) { fprintf(stderr, "Failed to query dB data for volume steps: %s\n", snd_strerror(r)); goto finish; } step1_dB = (double) step1_alsa_dB / 100.0; step2_dB = (double) step2_alsa_dB / 100.0; printf("Testing dB steps %0.2f vs. %0.2f.\n", step1_dB, step2_dB); attenuation = dB_to_linear(step1_dB - step2_dB); printf("Attenuation factor is %0.4f (%0.2f dB).\n", attenuation, linear_to_dB(attenuation)); if (!(signal1 = generate_signal(rate, 1.0, 440)) | !(signal2 = generate_signal(rate, attenuation, 440))) { fprintf(stderr, "Failed to generate test signal.\n"); goto finish; } for (;;) { printf("Playing 1s of audio at amplitude %0.4f, mixer set to %li (%0.2f dB).\n", 1.0, step1, step1_dB); if ((r = snd_mixer_selem_set_playback_volume_all(selem, step1)) < 0) { fprintf(stderr, "Failed to set element volume step: %s\n", snd_strerror(r)); goto finish; } if (play_pcm(pcm, signal1, rate) < 0) goto finish; printf("Playing 1s of audio at amplitude %0.4f, mixer set to %li (%0.2f dB).\n", attenuation, step2, step2_dB); if ((r = snd_mixer_selem_set_playback_volume_all(selem, step2)) < 0) { fprintf(stderr, "Failed to set element volume step: %s\n", snd_strerror(r)); goto finish; } if (play_pcm(pcm, signal2, rate) < 0) goto finish; printf("Looping. Press C-c to quit.\n"); } ret = 0; finish: free(signal1); free(signal2); if (pcm) snd_pcm_close(pcm); if (mixer) snd_mixer_close(mixer); return ret; }