summaryrefslogtreecommitdiff
path: root/lib/igt_audio.c
diff options
context:
space:
mode:
authorSimon Ser <simon.ser@intel.com>2019-04-23 16:04:52 +0300
committerArkadiusz Hiler <arkadiusz.hiler@intel.com>2019-04-25 13:07:15 +0300
commit311baff151f90c1db6f57ee9515216b4f9da5db7 (patch)
treecff58efbdacda05b06e7d88a2ab3d68816687eb4 /lib/igt_audio.c
parent11e10bc575516c56978640fcc697c27f277c660a (diff)
tests/kms_chamelium: add dp-audio test
This new test ensures DisplayPort audio works by using the Chamelium. It enables the DisplayPort output and sends an audio signal containing a set of frequencies we choose to all HDMI/DisplayPort audio devices. It starts recording audio on the Chamelium device and uses the stream server to retrieve captured audio pages. It then checks that the capture audio signal contains the frequencies we sent, and only those, by computing a FFT. A new library has been added to libigt to communicate with the stream server. It implements a simple custom TCP protocol. In case the test fails, a WAV file with the captured data is saved on disk. Right now the test has a few limitations: - Only the first channel is checked - IGT only generates audio with a single sampling rate (48 KHz) - Audio data is not captured in real-time These limitations will be lifted in future patches. PulseAudio must not run during the tests since ALSA is used directly. To ensure this, edit /etc/pulse/client.conf and add `autospawn=no`. Then run `pulseaudio --kill`. This commit deletes the existing audio tests. They weren't run and required an exotic configuration (HDMI audio splitter, dummy HDMI sink and a line-in port on the DUT). This patch also changes lib/igt_audio to use uint16_t instead of short. The rationale is: - The standard says a short is at least 16 bit wide, but a short can be larger (in practice it won't happen, but better use types correctly) - It makes it clearer that the audio format is S16_LE, since "16" is in the type name. This patch depends on the following Chameleon bugs: - https://crbug.com/948060 - https://crbug.com/950857 Signed-off-by: Simon Ser <simon.ser@intel.com> Reviewed-by: Martin Peres <martin.peres@linux.intel.com>
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;
}