/*-*- Mode: C; c-file-style: "linux"; indent-tabs-mode: nil; c-basic-offset: 8 -*-*/ #include #include #include 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 float *generate_silence(unsigned n_samples) { float *r; if (!(r = calloc(n_samples, sizeof(float)))) return NULL; return r; } static snd_pcm_t *open_device(const char *name, snd_pcm_stream_t stream, unsigned *rate) { snd_pcm_t *d = NULL; int r; snd_pcm_hw_params_t *hw; snd_output_t *output = NULL; int dir = 0; snd_pcm_uframes_t t; snd_pcm_hw_params_alloca(&hw); if ((r = snd_pcm_open(&d, name, stream, SND_PCM_NONBLOCK)) < 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); return d; finish: if (d) snd_pcm_close(d); if (output) snd_output_close(output); return NULL; } static double compute_level(const float *buffer, float *sum, unsigned n_samples, unsigned iteration) { unsigned i; double max = 0.0; for (i = 0; i < n_samples; i++) { sum[i] += buffer[i]; if (fabs(sum[i]) > max) max = fabs(sum[i]); } return max / (double) iteration; } static int prompt(const char *t) { char r[64]; fputs(t, stderr); if (!fgets(r, sizeof(r), stdin)) return 0; return 1; } static double linear_to_dB(double f) { return 20.0 * log10(f); } int main(int argc, char *argv[]) { int ret = 1; float *signal = NULL, *silence = NULL, *buffer = NULL, *sum = NULL, *current = NULL; snd_pcm_t *input = NULL, *output = NULL; unsigned rate = 48000; unsigned rindex = 0, windex = 0; double frequency = 440.0, amplitude = 0.5, noise_level_dB = -58.0, initial_level_max = 0.97, initial_level_min = 0.8, reference_level; unsigned iteration = 0; int icount, ocount; struct pollfd *pollfd; int e; unsigned needed_iterations = 0; int skip = 1; FILE *log = NULL; int level_count = 0; if (argc != 3) { fprintf(stderr, "Need to specify device and output log file.\n"); goto finish; } if (!(log = fopen(argv[2], "w"))) { fprintf(stderr, "Failed to open log file %s: %s\n", argv[2], strerror(errno)); goto finish; } if (!(output = open_device(argv[1], SND_PCM_STREAM_PLAYBACK, &rate))) goto finish; if (!(input = open_device(argv[1], SND_PCM_STREAM_CAPTURE, &rate))) goto finish; if (!(silence = generate_silence(rate))) { fprintf(stderr, "Failed to generate silence.\n"); goto finish; } if (!(signal = generate_signal(rate, amplitude, frequency))) { fprintf(stderr, "Failed to generate signal.\n"); goto finish; } if (!(buffer = malloc(rate * sizeof(float)))) { fprintf(stderr, "Failed to allocate buffer.\n"); goto finish; } if (!(sum = calloc(rate, sizeof(float)))) { fprintf(stderr, "Failed to allocate sum buffer.\n"); goto finish; } if ((icount = snd_pcm_poll_descriptors_count(input)) <= 0 || (ocount = snd_pcm_poll_descriptors_count(output)) <= 0) { fprintf(stderr, "Failed to determine number of pollfd descriptors.\n"); goto finish; } if (!(pollfd = calloc(icount + ocount, sizeof(struct pollfd)))) { fprintf(stderr, "Failed to allocate pollfd array.\n"); goto finish; } if ((e = snd_pcm_prepare(input)) < 0 || (e = snd_pcm_prepare(output)) < 0) { fprintf(stderr, "snd_pcm_prepare() failed: %s\n", snd_strerror(e)); goto finish; } if ((e = snd_pcm_start(input)) < 0) { fprintf(stderr, "snd_pcm_start() failed: %s\n", snd_strerror(e)); goto finish; } current = silence; prompt("Please set your control to the highest volume possible and press return.\n"); fprintf(stderr, "Measuring noise level.\n"); for (;;) { snd_pcm_sframes_t n; unsigned short irevents, orevents; if (snd_pcm_poll_descriptors(input, pollfd, icount) != icount || snd_pcm_poll_descriptors(output, pollfd+icount, ocount) != ocount) { fprintf(stderr, "ALSA changed its mind about number of file descriptors."); goto finish; } if (poll(pollfd, icount+ocount, -1) < 0) { fprintf(stderr, "poll() failed: %s\n", strerror(errno)); goto finish; } if ((e = snd_pcm_poll_descriptors_revents(input, pollfd, icount, &irevents)) < 0 || (e = snd_pcm_poll_descriptors_revents(output, pollfd+icount, ocount, &orevents)) < 0) { fprintf(stderr, "Cannot get revents: %s\n", snd_strerror(e)); goto finish; } /* printf("%u out %i in\n", orevents, irevents); */ if (orevents) { if ((n = snd_pcm_writei(output, current + windex, rate - windex)) < 0) { fprintf(stderr, "Got %s while writing.\n", snd_strerror(n)); if (snd_pcm_recover(output, n, 0) >= 0) { fprintf(stderr, "recovered\n"); continue; } if (snd_pcm_prepare(output) >= 0) { fprintf(stderr, "prepared\n"); continue; } if (snd_pcm_start(output) >= 0) { fprintf(stderr, "restarted\n"); continue; } if (n != -EAGAIN) { fprintf(stderr, "Cannot write samples: %s\n", snd_strerror(n)); goto finish; } n = 0; } windex += n; if (windex == rate) windex = 0; } if (irevents) { if ((n = snd_pcm_readi(input, buffer + rindex, rate - rindex)) < 0) { fprintf(stderr, "Got %s while reading.\n", snd_strerror(n)); if (snd_pcm_start(input) >= 0) { fprintf(stderr, "restarted\n"); continue; } if (snd_pcm_recover(input, n, 0) >= 0) { fprintf(stderr, "recovered\n"); snd_pcm_start(input); continue; } if (snd_pcm_prepare(input) >= 0) { fprintf(stderr, "prepared\n"); continue; } if (n != -EAGAIN) { fprintf(stderr, "Cannot read samples: %s\n", snd_strerror(n)); goto finish; } n = 0; } rindex += n; if (rindex == rate) { rindex = 0; if (skip) skip = 0; else { double level; iteration++; level = compute_level(buffer, sum, rate, iteration); fprintf(stderr, "Iteration %u, level is %g (%g dB).\n", iteration, level, linear_to_dB(level)); if (needed_iterations == 0) { if (linear_to_dB(level) < noise_level_dB) { fprintf(stderr, "%u iterations necessary to push noise level below %g dB\n", iteration, noise_level_dB); needed_iterations = iteration; current = signal; windex = 0; skip = 1; iteration = 0; memset(sum, 0, rate * sizeof(float)); fprintf(stderr, "Generating signal.\n"); } } else { if (iteration >= needed_iterations) { if (level_count <= 0) { if (level < initial_level_min || level > initial_level_max) { fprintf(stderr, "Volume level measured (%g) was too high or too low.\n" "Please adjust mixer so that initial level is between %g and %g.\n" "Test run canceled.\n", level, initial_level_min, initial_level_max); goto finish; } reference_level = level; } fprintf(log, "%i\t%g\t%g\t%g\t%g\n", level_count, level, linear_to_dB(level), level/reference_level, linear_to_dB(level/reference_level)); fflush(log); level_count++; windex = 0; skip = 1; iteration = 0; memset(sum, 0, rate * sizeof(float)); if (!(prompt("Please reduce volume by one step and press return. Press C-D when finished.\n"))) break; } } } } } } ret = 0; finish: if (input) snd_pcm_close(input); if (output) snd_pcm_close(output); if (log) fclose(log); free(signal); free(silence); free(buffer); free(sum); return ret; }