summaryrefslogtreecommitdiff
path: root/lib/igt_audio.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/igt_audio.c')
-rw-r--r--lib/igt_audio.c313
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;
}