From 98a35bf06923861927febf0491499aa1e78a2560 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 15 Feb 2010 19:38:21 +0100 Subject: add tool to verify dB data of a mixer element --- dbverify.c | 349 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 349 insertions(+) create mode 100644 dbverify.c diff --git a/dbverify.c b/dbverify.c new file mode 100644 index 0000000..6b7121b --- /dev/null +++ b/dbverify.c @@ -0,0 +1,349 @@ +/*-*- 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; +} -- cgit