diff options
Diffstat (limited to 'lib/igt_audio.c')
-rw-r--r-- | lib/igt_audio.c | 313 |
1 files changed, 227 insertions, 86 deletions
diff --git a/lib/igt_audio.c b/lib/igt_audio.c index a0592d53..7624f565 100644 --- a/lib/igt_audio.c +++ b/lib/igt_audio.c @@ -26,8 +26,11 @@ #include "config.h" -#include <math.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" @@ -128,7 +131,7 @@ int audio_signal_add_frequency(struct audio_signal *signal, int frequency) */ void audio_signal_synthesize(struct audio_signal *signal) { - short *period; + int16_t *period; double value; int frames; int freq; @@ -145,9 +148,9 @@ void audio_signal_synthesize(struct audio_signal *signal) for (j = 0; j < frames; j++) { value = 2.0 * M_PI * freq / signal->sampling_rate * j; - value = sin(value) * SHRT_MAX / signal->freqs_count; + value = sin(value) * INT16_MAX / signal->freqs_count; - period[j] = (short) value; + period[j] = (int16_t) value; } signal->freqs[i].period = period; @@ -186,17 +189,16 @@ void audio_signal_clean(struct audio_signal *signal) * signal data (in interleaved S16_LE format), at the requested sampling rate * and number of channels. */ -void audio_signal_fill(struct audio_signal *signal, short *buffer, int frames) +void audio_signal_fill(struct audio_signal *signal, int16_t *buffer, int frames) { - short *destination; - short *source; + int16_t *destination, *source; int total; int freq_frames; int freq_offset; int count; int i, j, k; - memset(buffer, 0, sizeof(short) * signal->channels * frames); + memset(buffer, 0, sizeof(int16_t) * signal->channels * frames); for (i = 0; i < signal->freqs_count; i++) { total = 0; @@ -229,97 +231,236 @@ void audio_signal_fill(struct audio_signal *signal, short *buffer, int frames) } /** - * audio_signal_detect: - * @signal: The target signal structure - * @channels: The input data's number of channels - * @sampling_rate: The input data's sampling rate - * @buffer: The input data's buffer - * @frames: The input data's number of frames - * - * Detect that the frequencies specified in @signal, and only those, are - * present in the input data. The input data's format is required to be S16_LE. + * Checks that frequencies specified in signal, and only those, are included + * in the input data. * - * Returns: A boolean indicating whether the detection was successful + * sampling_rate is given in Hz. data_len is the number of elements in data. */ -bool audio_signal_detect(struct audio_signal *signal, int channels, - int sampling_rate, short *buffer, int frames) +bool audio_signal_detect(struct audio_signal *signal, int sampling_rate, + double *data, size_t data_len) { - double data[frames]; - int amplitude[frames / 2]; + size_t bin_power_len = data_len / 2 + 1; + double bin_power[bin_power_len]; bool detected[signal->freqs_count]; - int threshold; - bool above; - int error; - int freq = 0; - int max; - int c, i, j; + int ret, freq_accuracy, freq, local_max_freq; + double max, local_max, threshold; + size_t i, j; + bool above, success; + + /* 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); + igt_assert(ret == 0); + + /* Compute the power received by every bin of the FFT, and record the + * maximum power received as a way to normalize all the others. + * + * 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. + */ + max = 0; + bin_power[0] = data[0]; + for (i = 1; i < bin_power_len - 1; i++) { + bin_power[i] = hypot(data[i], data[data_len - i]); + if (bin_power[i] > max) + max = bin_power[i]; + } + bin_power[bin_power_len - 1] = data[data_len / 2]; + + 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; + } - /* Allowed error in Hz due to FFT step. */ - error = sampling_rate / frames; + /* 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].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; + } + } - for (c = 0; c < channels; c++) { - for (i = 0; i < frames; i++) - data[i] = (double) buffer[i * channels + c]; + /* 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; + } - gsl_fft_real_radix2_transform(data, 1, frames); + above = false; + local_max = 0; + local_max_freq = -1; + } - max = 0; + if (bin_power[i] > local_max) { + local_max = bin_power[i]; + local_max_freq = freq; + } + } - for (i = 0; i < frames / 2; i++) { - amplitude[i] = hypot(data[i], data[frames - i]); - if (amplitude[i] > max) - max = amplitude[i]; + /* Check that all frequencies we generated have been detected. */ + for (i = 0; i < signal->freqs_count; i++) { + if (!detected[i]) { + igt_debug("Missing frequency: %d\n", + signal->freqs[i].freq); + success = false; } + } - for (i = 0; i < signal->freqs_count; i++) - detected[i] = false; - - threshold = max / 2; - above = false; - max = 0; - - for (i = 0; i < frames / 2; i++) { - if (amplitude[i] > threshold) - above = true; - - if (above) { - if (amplitude[i] < threshold) { - above = false; - max = 0; - - for (j = 0; j < signal->freqs_count; j++) { - if (signal->freqs[j].freq > - freq - error && - signal->freqs[j].freq < - freq + error) { - detected[j] = true; - break; - } - } - - /* Detected frequency was not generated. */ - if (j == signal->freqs_count) { - igt_debug("Detected additional frequency: %d\n", - freq); - return false; - } - } + return success; +} - if (amplitude[i] > max) { - max = amplitude[i]; - freq = sampling_rate * i / frames; - } - } - } +/** + * Extracts a single channel from a multi-channel S32_LE input buffer. + */ +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; - for (i = 0; i < signal->freqs_count; i++) { - if (!detected[i]) { - igt_debug("Missing frequency: %d\n", - signal->freqs[i].freq); - return false; - } - } + igt_assert(channel < n_channels); + igt_assert(src_len % n_channels == 0); + dst_len = src_len / n_channels; + igt_assert(dst_len <= dst_cap); + for (i = 0; i < dst_len; i++) + dst[i] = (double) src[i * n_channels + channel]; + + return dst_len; +} + +#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); + 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 true; + return fd; } |