| /* |
| * Copyright © 2017 Intel Corporation |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| * and/or sell copies of the Software, and to permit persons to whom the |
| * Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice (including the next |
| * paragraph) shall be included in all copies or substantial portions of the |
| * Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| * IN THE SOFTWARE. |
| * |
| * Authors: |
| * Paul Kocialkowski <[email protected]> |
| */ |
| |
| #include "config.h" |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <gsl/gsl_fft_real.h> |
| #include <math.h> |
| #include <unistd.h> |
| |
| #include "igt_audio.h" |
| #include "igt_core.h" |
| |
| #define FREQS_MAX 64 |
| #define CHANNELS_MAX 8 |
| #define SYNTHESIZE_AMPLITUDE 0.9 |
| #define SYNTHESIZE_ACCURACY 0.2 |
| /** MIN_FREQ: minimum frequency that audio_signal can generate. |
| * |
| * To make sure the audio signal doesn't contain noise, #audio_signal_detect |
| * checks that low frequencies have a power lower than #NOISE_THRESHOLD. |
| * However if too-low frequencies are generated, noise detection can fail. |
| * |
| * This value should be at least 100Hz plus one bin. Best is not to change this |
| * value. |
| */ |
| #define MIN_FREQ 200 /* Hz */ |
| #define NOISE_THRESHOLD 0.0005 |
| |
| /** |
| * SECTION:igt_audio |
| * @short_description: Library for audio-related tests |
| * @title: Audio |
| * @include: igt_audio.h |
| * |
| * This library contains helpers for audio-related tests. More specifically, |
| * it allows generating additions of sine signals as well as detecting them. |
| */ |
| |
| struct audio_signal_freq { |
| int freq; |
| int channel; |
| |
| double *period; |
| size_t period_len; |
| int offset; |
| }; |
| |
| struct audio_signal { |
| int channels; |
| int sampling_rate; |
| |
| struct audio_signal_freq freqs[FREQS_MAX]; |
| size_t freqs_count; |
| }; |
| |
| /** |
| * audio_signal_init: |
| * @channels: The number of channels to use for the signal |
| * @sampling_rate: The sampling rate to use for the signal |
| * |
| * Allocate and initialize an audio signal structure with the given parameters. |
| * |
| * Returns: A newly-allocated audio signal structure |
| */ |
| struct audio_signal *audio_signal_init(int channels, int sampling_rate) |
| { |
| struct audio_signal *signal; |
| |
| igt_assert(channels > 0); |
| igt_assert(channels <= CHANNELS_MAX); |
| |
| signal = calloc(1, sizeof(struct audio_signal)); |
| signal->sampling_rate = sampling_rate; |
| signal->channels = channels; |
| return signal; |
| } |
| |
| /** |
| * audio_signal_add_frequency: |
| * @signal: The target signal structure |
| * @frequency: The frequency to add to the signal |
| * @channel: The channel to add this frequency to, or -1 to add it to all |
| * channels |
| * |
| * Add a frequency to the signal. |
| * |
| * Returns: An integer equal to zero for success and negative for failure |
| */ |
| int audio_signal_add_frequency(struct audio_signal *signal, int frequency, |
| int channel) |
| { |
| size_t index = signal->freqs_count; |
| struct audio_signal_freq *freq; |
| |
| igt_assert(index < FREQS_MAX); |
| igt_assert(channel < signal->channels); |
| igt_assert(frequency >= MIN_FREQ); |
| |
| /* Stay within the Nyquist–Shannon sampling theorem. */ |
| if (frequency > signal->sampling_rate / 2) { |
| igt_debug("Skipping frequency %d: too high for a %d Hz " |
| "sampling rate\n", frequency, signal->sampling_rate); |
| return -1; |
| } |
| |
| /* Clip the frequency to an integer multiple of the sampling rate. |
| * This to be able to store a full period of it and use that for |
| * signal generation, instead of recurrent calls to sin(). |
| */ |
| frequency = signal->sampling_rate / (signal->sampling_rate / frequency); |
| |
| igt_debug("Adding test frequency %d to channel %d\n", |
| frequency, channel); |
| |
| freq = &signal->freqs[index]; |
| memset(freq, 0, sizeof(*freq)); |
| freq->freq = frequency; |
| freq->channel = channel; |
| |
| signal->freqs_count++; |
| |
| return 0; |
| } |
| |
| /** |
| * audio_signal_synthesize: |
| * @signal: The target signal structure |
| * |
| * Synthesize the data tables for the audio signal, that can later be used |
| * to fill audio buffers. The resources allocated by this function must be |
| * freed with a call to audio_signal_clean when the signal is no longer used. |
| */ |
| void audio_signal_synthesize(struct audio_signal *signal) |
| { |
| double *period; |
| double value; |
| size_t period_len; |
| int freq; |
| int i, j; |
| |
| for (i = 0; i < signal->freqs_count; i++) { |
| freq = signal->freqs[i].freq; |
| period_len = signal->sampling_rate / freq; |
| |
| period = calloc(period_len, sizeof(double)); |
| |
| for (j = 0; j < period_len; j++) { |
| value = 2.0 * M_PI * freq / signal->sampling_rate * j; |
| value = sin(value) * SYNTHESIZE_AMPLITUDE; |
| |
| period[j] = value; |
| } |
| |
| signal->freqs[i].period = period; |
| signal->freqs[i].period_len = period_len; |
| } |
| } |
| |
| /** |
| * audio_signal_fini: |
| * |
| * Release the signal. |
| */ |
| void audio_signal_fini(struct audio_signal *signal) |
| { |
| audio_signal_reset(signal); |
| free(signal); |
| } |
| |
| /** |
| * audio_signal_reset: |
| * @signal: The target signal structure |
| * |
| * Free the resources allocated by audio_signal_synthesize and remove |
| * the previously-added frequencies. |
| */ |
| void audio_signal_reset(struct audio_signal *signal) |
| { |
| size_t i; |
| |
| for (i = 0; i < signal->freqs_count; i++) { |
| free(signal->freqs[i].period); |
| } |
| |
| signal->freqs_count = 0; |
| } |
| |
| static size_t audio_signal_count_freqs(struct audio_signal *signal, int channel) |
| { |
| size_t n, i; |
| struct audio_signal_freq *freq; |
| |
| n = 0; |
| for (i = 0; i < signal->freqs_count; i++) { |
| freq = &signal->freqs[i]; |
| if (freq->channel < 0 || freq->channel == channel) |
| n++; |
| } |
| |
| return n; |
| } |
| |
| /** audio_sanity_check: |
| * |
| * Make sure our generated signal is not messed up. In particular, make sure |
| * the maximum reaches a reasonable value but doesn't exceed our |
| * SYNTHESIZE_AMPLITUDE limit. Same for the minimum. |
| * |
| * We want the signal to be powerful enough to be able to hear something. We |
| * want the signal not to reach 1.0 so that we're sure it won't get capped by |
| * the audio card or the receiver. |
| */ |
| static void audio_sanity_check(double *samples, size_t samples_len) |
| { |
| size_t i; |
| double min = 0, max = 0; |
| |
| for (i = 0; i < samples_len; i++) { |
| if (samples[i] < min) |
| min = samples[i]; |
| if (samples[i] > max) |
| max = samples[i]; |
| } |
| |
| igt_assert(-SYNTHESIZE_AMPLITUDE <= min); |
| igt_assert(min <= -SYNTHESIZE_AMPLITUDE + SYNTHESIZE_ACCURACY); |
| igt_assert(SYNTHESIZE_AMPLITUDE - SYNTHESIZE_ACCURACY <= max); |
| igt_assert(max <= SYNTHESIZE_AMPLITUDE); |
| } |
| |
| /** |
| * audio_signal_fill: |
| * @signal: The target signal structure |
| * @buffer: The target buffer to fill |
| * @samples: The number of samples to fill |
| * |
| * Fill the requested number of samples to the target buffer with the audio |
| * signal data (in interleaved double format), at the requested sampling rate |
| * and number of channels. |
| * |
| * Each sample is normalized (ie. between 0 and 1). |
| */ |
| void audio_signal_fill(struct audio_signal *signal, double *buffer, |
| size_t samples) |
| { |
| double *dst, *src; |
| struct audio_signal_freq *freq; |
| int total; |
| int count; |
| int i, j, k; |
| size_t freqs_per_channel[CHANNELS_MAX]; |
| |
| memset(buffer, 0, sizeof(double) * signal->channels * samples); |
| |
| for (i = 0; i < signal->channels; i++) { |
| freqs_per_channel[i] = audio_signal_count_freqs(signal, i); |
| igt_assert(freqs_per_channel[i] > 0); |
| } |
| |
| for (i = 0; i < signal->freqs_count; i++) { |
| freq = &signal->freqs[i]; |
| total = 0; |
| |
| igt_assert(freq->period); |
| |
| while (total < samples) { |
| src = freq->period + freq->offset; |
| dst = buffer + total * signal->channels; |
| |
| count = freq->period_len - freq->offset; |
| if (count > samples - total) |
| count = samples - total; |
| |
| freq->offset += count; |
| freq->offset %= freq->period_len; |
| |
| for (j = 0; j < count; j++) { |
| for (k = 0; k < signal->channels; k++) { |
| if (freq->channel >= 0 && |
| freq->channel != k) |
| continue; |
| dst[j * signal->channels + k] += |
| src[j] / freqs_per_channel[k]; |
| } |
| } |
| |
| total += count; |
| } |
| } |
| |
| audio_sanity_check(buffer, signal->channels * samples); |
| } |
| |
| /* See https://en.wikipedia.org/wiki/Window_function#Hann_and_Hamming_windows */ |
| static double hann_window(double v, size_t i, size_t N) |
| { |
| return v * 0.5 * (1 - cos(2.0 * M_PI * (double) i / (double) N)); |
| } |
| |
| /** |
| * Checks that frequencies specified in signal, and only those, are included |
| * in the input data. |
| * |
| * sampling_rate is given in Hz. samples_len is the number of elements in |
| * samples. |
| */ |
| bool audio_signal_detect(struct audio_signal *signal, int sampling_rate, |
| int channel, const double *samples, size_t samples_len) |
| { |
| double *data; |
| size_t data_len = samples_len; |
| size_t bin_power_len = data_len / 2 + 1; |
| double bin_power[bin_power_len]; |
| bool detected[FREQS_MAX]; |
| int ret, freq_accuracy, freq, local_max_freq; |
| double max, local_max, threshold; |
| size_t i, j; |
| bool above, success; |
| |
| /* gsl will mutate the array in-place, so make a copy */ |
| data = malloc(samples_len * sizeof(double)); |
| memcpy(data, samples, samples_len * sizeof(double)); |
| |
| /* Apply a Hann window to the input signal, to reduce frequency leaks |
| * due to the endpoints of the signal being discontinuous. |
| * |
| * For more info: |
| * - https://download.ni.com/evaluation/pxi/Understanding%20FFTs%20and%20Windowing.pdf |
| * - https://en.wikipedia.org/wiki/Window_function |
| */ |
| for (i = 0; i < data_len; i++) |
| data[i] = hann_window(data[i], i, data_len); |
| |
| /* Allowed error in Hz due to FFT step */ |
| freq_accuracy = sampling_rate / data_len; |
| igt_debug("Allowed freq. error: %d Hz\n", freq_accuracy); |
| |
| ret = gsl_fft_real_radix2_transform(data, 1, data_len); |
| if (ret != 0) { |
| free(data); |
| igt_assert(0); |
| } |
| |
| /* Compute the power received by every bin of the FFT. |
| * |
| * For i < data_len / 2, the real part of the i-th term is stored at |
| * data[i] and its imaginary part is stored at data[data_len - i]. |
| * i = 0 and i = data_len / 2 are special cases, they are purely real |
| * so their imaginary part isn't stored. |
| * |
| * The power is encoded as the magnitude of the complex number and the |
| * phase is encoded as its angle. |
| */ |
| bin_power[0] = data[0]; |
| for (i = 1; i < bin_power_len - 1; i++) { |
| bin_power[i] = hypot(data[i], data[data_len - i]); |
| } |
| bin_power[bin_power_len - 1] = data[data_len / 2]; |
| |
| /* Normalize the power */ |
| for (i = 0; i < bin_power_len; i++) |
| bin_power[i] = 2 * bin_power[i] / data_len; |
| |
| /* Detect noise with a threshold on the power of low frequencies */ |
| for (i = 0; i < bin_power_len; i++) { |
| freq = sampling_rate * i / data_len; |
| if (freq > MIN_FREQ - 100) |
| break; |
| if (bin_power[i] > NOISE_THRESHOLD) { |
| igt_debug("Noise level too high: freq=%d power=%f\n", |
| freq, bin_power[i]); |
| return false; |
| } |
| } |
| |
| /* Record the maximum power received as a way to normalize all the |
| * others. */ |
| max = NAN; |
| for (i = 0; i < bin_power_len; i++) { |
| if (isnan(max) || bin_power[i] > max) |
| max = bin_power[i]; |
| } |
| |
| for (i = 0; i < signal->freqs_count; i++) |
| detected[i] = false; |
| |
| /* Do a linear search through the FFT bins' power to find the the local |
| * maximums that exceed half of the absolute maximum that we previously |
| * calculated. |
| * |
| * Since the frequencies might not be perfectly aligned with the bins of |
| * the FFT, we need to find the local maximum across some consecutive |
| * bins. Once the power returns under the power threshold, we compare |
| * the frequency of the bin that received the maximum power to the |
| * expected frequencies. If found, we mark this frequency as such, |
| * otherwise we warn that an unexpected frequency was found. |
| */ |
| threshold = max / 2; |
| success = true; |
| above = false; |
| local_max = 0; |
| local_max_freq = -1; |
| for (i = 0; i < bin_power_len; i++) { |
| freq = sampling_rate * i / data_len; |
| |
| if (bin_power[i] > threshold) |
| above = true; |
| |
| if (!above) { |
| continue; |
| } |
| |
| /* If we were above the threshold and we're not anymore, it's |
| * time to decide whether the peak frequency is correct or |
| * invalid. */ |
| if (bin_power[i] < threshold) { |
| for (j = 0; j < signal->freqs_count; j++) { |
| if (signal->freqs[j].channel >= 0 && |
| signal->freqs[j].channel != channel) |
| continue; |
| |
| if (signal->freqs[j].freq > |
| local_max_freq - freq_accuracy && |
| signal->freqs[j].freq < |
| local_max_freq + freq_accuracy) { |
| detected[j] = true; |
| igt_debug("Frequency %d detected\n", |
| local_max_freq); |
| break; |
| } |
| } |
| |
| /* We haven't generated this frequency, but we detected |
| * it. */ |
| if (j == signal->freqs_count) { |
| igt_debug("Detected additional frequency: %d\n", |
| local_max_freq); |
| success = false; |
| } |
| |
| above = false; |
| local_max = 0; |
| local_max_freq = -1; |
| } |
| |
| if (bin_power[i] > local_max) { |
| local_max = bin_power[i]; |
| local_max_freq = freq; |
| } |
| } |
| |
| /* Check that all frequencies we generated have been detected. */ |
| for (i = 0; i < signal->freqs_count; i++) { |
| if (signal->freqs[i].channel >= 0 && |
| signal->freqs[i].channel != channel) |
| continue; |
| |
| if (!detected[i]) { |
| igt_debug("Missing frequency: %d\n", |
| signal->freqs[i].freq); |
| success = false; |
| } |
| } |
| |
| free(data); |
| |
| return success; |
| } |
| |
| /** |
| * audio_extract_channel_s32_le: extracts a single channel from a multi-channel |
| * S32_LE input buffer. |
| * |
| * If dst_cap is zero, no copy is performed. This can be used to compute the |
| * minimum required capacity. |
| * |
| * Returns: the number of samples extracted. |
| */ |
| size_t audio_extract_channel_s32_le(double *dst, size_t dst_cap, |
| int32_t *src, size_t src_len, |
| int n_channels, int channel) |
| { |
| size_t dst_len, i; |
| |
| igt_assert(channel < n_channels); |
| igt_assert(src_len % n_channels == 0); |
| dst_len = src_len / n_channels; |
| if (dst_cap == 0) |
| return dst_len; |
| |
| igt_assert(dst_len <= dst_cap); |
| for (i = 0; i < dst_len; i++) |
| dst[i] = (double) src[i * n_channels + channel] / INT32_MAX; |
| |
| return dst_len; |
| } |
| |
| static void audio_convert_to_s16_le(int16_t *dst, double *src, size_t len) |
| { |
| size_t i; |
| |
| for (i = 0; i < len; ++i) |
| dst[i] = INT16_MAX * src[i]; |
| } |
| |
| static void audio_convert_to_s24_le(int32_t *dst, double *src, size_t len) |
| { |
| size_t i; |
| |
| for (i = 0; i < len; ++i) |
| dst[i] = 0x7FFFFF * src[i]; |
| } |
| |
| static void audio_convert_to_s32_le(int32_t *dst, double *src, size_t len) |
| { |
| size_t i; |
| |
| for (i = 0; i < len; ++i) |
| dst[i] = INT32_MAX * src[i]; |
| } |
| |
| void audio_convert_to(void *dst, double *src, size_t len, |
| snd_pcm_format_t format) |
| { |
| switch (format) { |
| case SND_PCM_FORMAT_S16_LE: |
| audio_convert_to_s16_le(dst, src, len); |
| break; |
| case SND_PCM_FORMAT_S24_LE: |
| audio_convert_to_s24_le(dst, src, len); |
| break; |
| case SND_PCM_FORMAT_S32_LE: |
| audio_convert_to_s32_le(dst, src, len); |
| break; |
| default: |
| assert(false); /* unreachable */ |
| } |
| } |
| |
| #define RIFF_TAG "RIFF" |
| #define WAVE_TAG "WAVE" |
| #define FMT_TAG "fmt " |
| #define DATA_TAG "data" |
| |
| static void |
| append_to_buffer(char *dst, size_t *i, const void *src, size_t src_size) |
| { |
| memcpy(&dst[*i], src, src_size); |
| *i += src_size; |
| } |
| |
| /** |
| * audio_create_wav_file_s32_le: |
| * @qualifier: the basename of the file (the test name will be prepended, and |
| * the file extension will be appended) |
| * @sample_rate: the sample rate in Hz |
| * @channels: the number of channels |
| * @path: if non-NULL, will be set to a pointer to the new file path (the |
| * caller is responsible for free-ing it) |
| * |
| * Creates a new WAV file. |
| * |
| * After calling this function, the caller is expected to write S32_LE PCM data |
| * to the returned file descriptor. |
| * |
| * See http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html for |
| * a WAV file format specification. |
| * |
| * Returns: a file descriptor to the newly created file, or -1 on error. |
| */ |
| int audio_create_wav_file_s32_le(const char *qualifier, uint32_t sample_rate, |
| uint16_t channels, char **path) |
| { |
| char _path[PATH_MAX]; |
| const char *test_name, *subtest_name; |
| int fd; |
| char header[44]; |
| size_t i = 0; |
| uint32_t file_size, chunk_size, byte_rate; |
| uint16_t format, block_align, bits_per_sample; |
| |
| test_name = igt_test_name(); |
| subtest_name = igt_subtest_name(); |
| |
| igt_assert(igt_frame_dump_path); |
| snprintf(_path, sizeof(_path), "%s/audio-%s-%s-%s.wav", |
| igt_frame_dump_path, test_name, subtest_name, qualifier); |
| |
| if (path) |
| *path = strdup(_path); |
| |
| igt_debug("Dumping %s audio to %s\n", qualifier, _path); |
| fd = open(_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); |
| if (fd < 0) { |
| igt_warn("open failed: %s\n", strerror(errno)); |
| return -1; |
| } |
| |
| /* File header */ |
| file_size = UINT32_MAX; /* unknown file size */ |
| append_to_buffer(header, &i, RIFF_TAG, strlen(RIFF_TAG)); |
| append_to_buffer(header, &i, &file_size, sizeof(file_size)); |
| append_to_buffer(header, &i, WAVE_TAG, strlen(WAVE_TAG)); |
| |
| /* Format chunk */ |
| chunk_size = 16; |
| format = 1; /* PCM */ |
| bits_per_sample = 32; /* S32_LE */ |
| byte_rate = sample_rate * channels * bits_per_sample / 8; |
| block_align = channels * bits_per_sample / 8; |
| append_to_buffer(header, &i, FMT_TAG, strlen(FMT_TAG)); |
| append_to_buffer(header, &i, &chunk_size, sizeof(chunk_size)); |
| append_to_buffer(header, &i, &format, sizeof(format)); |
| append_to_buffer(header, &i, &channels, sizeof(channels)); |
| append_to_buffer(header, &i, &sample_rate, sizeof(sample_rate)); |
| append_to_buffer(header, &i, &byte_rate, sizeof(byte_rate)); |
| append_to_buffer(header, &i, &block_align, sizeof(block_align)); |
| append_to_buffer(header, &i, &bits_per_sample, sizeof(bits_per_sample)); |
| |
| /* Data chunk */ |
| chunk_size = UINT32_MAX; /* unknown chunk size */ |
| append_to_buffer(header, &i, DATA_TAG, strlen(DATA_TAG)); |
| append_to_buffer(header, &i, &chunk_size, sizeof(chunk_size)); |
| |
| igt_assert(i == sizeof(header)); |
| |
| if (write(fd, header, sizeof(header)) != sizeof(header)) { |
| igt_warn("write failed: %s'n", strerror(errno)); |
| close(fd); |
| return -1; |
| } |
| |
| return fd; |
| } |