All of lore.kernel.org
 help / color / mirror / Atom feed
* [igt-dev] [PATCH i-g-t v4 0/5] tests/kms_chamelium: add dp-audio test
@ 2019-04-11 12:36 Simon Ser
  2019-04-11 12:36 ` [igt-dev] [PATCH i-g-t v4 1/5] " Simon Ser
                   ` (6 more replies)
  0 siblings, 7 replies; 13+ messages in thread
From: Simon Ser @ 2019-04-11 12:36 UTC (permalink / raw)
  To: igt-dev

Changes from v3:
- Fixed author for patches 2-4 and add references to Chameleon bugs in
  commit messages (patches themselves haven't been changed)
- Add patch 5 to use different sampling rates

Simon Ser (5):
  tests/kms_chamelium: add dp-audio test
  tests/kms_chamelium: capture audio data in real-time
  tests/kms_chamelium: test we receive a signal from both audio channels
  tests/kms_chamelium: test audio channels are not mixed up
  tests/kms_chamelium: run audio test with multiple sampling rates

 docs/audio.txt             |  45 ---
 docs/chamelium.txt         |  32 ++-
 lib/igt.h                  |   1 +
 lib/igt_alsa.c             |  83 +++++-
 lib/igt_alsa.h             |   1 +
 lib/igt_audio.c            | 406 +++++++++++++++++---------
 lib/igt_audio.h            |  19 +-
 lib/igt_aux.c              |  31 ++
 lib/igt_aux.h              |   1 +
 lib/igt_chamelium.c        | 161 +++++++++++
 lib/igt_chamelium.h        |  17 ++
 lib/igt_chamelium_stream.c | 575 +++++++++++++++++++++++++++++++++++++
 lib/igt_chamelium_stream.h |  50 ++++
 lib/meson.build            |   5 +-
 meson.build                |  52 ++--
 meson_options.txt          |   6 -
 tests/audio.c              | 193 -------------
 tests/kms_chamelium.c      | 352 ++++++++++++++++++++++-
 tests/meson.build          |   9 +-
 19 files changed, 1589 insertions(+), 450 deletions(-)
 delete mode 100644 docs/audio.txt
 create mode 100644 lib/igt_chamelium_stream.c
 create mode 100644 lib/igt_chamelium_stream.h
 delete mode 100644 tests/audio.c

-- 
2.21.0

_______________________________________________
igt-dev mailing list
igt-dev@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/igt-dev

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [igt-dev] [PATCH i-g-t v4 1/5] tests/kms_chamelium: add dp-audio test
  2019-04-11 12:36 [igt-dev] [PATCH i-g-t v4 0/5] tests/kms_chamelium: add dp-audio test Simon Ser
@ 2019-04-11 12:36 ` Simon Ser
  2019-04-16 12:02   ` Martin Peres
  2019-04-11 12:36 ` [igt-dev] [PATCH i-g-t v4 2/5] tests/kms_chamelium: capture audio data in real-time Simon Ser
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 13+ messages in thread
From: Simon Ser @ 2019-04-11 12:36 UTC (permalink / raw)
  To: igt-dev

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 depends on the following Chameleon bugs:

- https://crbug.com/948060
- https://crbug.com/950857

Signed-off-by: Simon Ser <simon.ser@intel.com>
---
 docs/audio.txt             |  45 ---
 docs/chamelium.txt         |  32 +-
 lib/igt.h                  |   1 +
 lib/igt_alsa.c             |  42 ++-
 lib/igt_alsa.h             |   1 +
 lib/igt_audio.c            | 285 ++++++++++++------
 lib/igt_audio.h            |  12 +-
 lib/igt_aux.c              |  31 ++
 lib/igt_aux.h              |   1 +
 lib/igt_chamelium.c        | 101 +++++++
 lib/igt_chamelium.h        |  11 +
 lib/igt_chamelium_stream.c | 592 +++++++++++++++++++++++++++++++++++++
 lib/igt_chamelium_stream.h |  52 ++++
 lib/meson.build            |   5 +-
 meson.build                |  52 ++--
 meson_options.txt          |   6 -
 tests/audio.c              | 193 ------------
 tests/kms_chamelium.c      | 276 ++++++++++++++++-
 tests/meson.build          |   9 +-
 19 files changed, 1358 insertions(+), 389 deletions(-)
 delete mode 100644 docs/audio.txt
 create mode 100644 lib/igt_chamelium_stream.c
 create mode 100644 lib/igt_chamelium_stream.h
 delete mode 100644 tests/audio.c

diff --git a/docs/audio.txt b/docs/audio.txt
deleted file mode 100644
index 158ad5d1..00000000
--- a/docs/audio.txt
+++ /dev/null
@@ -1,45 +0,0 @@
-Audio Support in IGT
-====================
-
-This document provides information and instructions about audio support in IGT.
-
-Introduction
-------------
-
-The audio test is aimed at testing the audio features of display connectors,
-such as HDMI.
-
-Test setup
-----------
-
-The setup required for the audio test consists of using an HDMI-VGA adapter with
-an audio-out 3.5 mm jack to extract the audio from the HDMI interface.
-The audio-out jack is connected back to the device-under-test's line-in.
-
-Depending on the behavior of the adapter, it may be necessary to connect a
-ghost VGA dongle to it (in order to emulate a connected display) to enable the
-audio output. There are guides available detailing how to build these.
-
-When executed, the test will automatically send the test audio signal to all
-ALSA audio HDMI outputs and record from the standard ALSA capture device.
-
-Configuration
--------------
-
-In order to deploy the test, ALSA controls have to be configured to set the
-ALSA capture source to line-in. On Intel x86 systems, this can be achieved
-with the following calls to the amixer utility:
-# amixer sset Line 31 on
-# amixer sset "Input Source" Line
-
-It is then useful to store the ALSA state permanently with the alsactl utility:
-# alsactl store
-
-These settings can be restored with the alsactl utility:
-# alsactl restore
-
-It is desirable to ensure that the alsa-restore and alsa-state systemd services
-are enabled to do this job automatically, especially in the case of an
-automated testing system:
-# systemctl enable alsa-restore
-# systemctl enable alsa-state
diff --git a/docs/chamelium.txt b/docs/chamelium.txt
index 0cabcdc6..316dd607 100644
--- a/docs/chamelium.txt
+++ b/docs/chamelium.txt
@@ -139,6 +139,23 @@ $ make remote-install CHAMELEON_HOST=192.168.72.1
 
 The process requires the Chamelium to be connected to the Internet to succeed.
 
+Audio Capture
+-------------
+
+The Chamelium supports audio capture. IGT tests take advantage of the
+Chamelium streaming server to download audio samples from the Chamelium.
+
+IGT needs direct access to audio devices through ALSA, so PulseAudio needs to
+be stopped (otherwise audio tests will automatically get skipped). To make sure
+PulseAudio isn't running:
+
+- Edit /etc/pulse/client.conf and add autospawn=no
+- Run `pulseaudio --kill` (if it succeeds, it means PulseAudio was running)
+- Make sure a DE that automatically spawns PulseAudio isn't running
+
+In case a test fails, the raw captured audio files will be dumped in a WAV
+file.
+
 Contributing Changes to the Daemon
 ----------------------------------
 
@@ -146,10 +163,11 @@ Contributions to the Chamelium daemon, just like any contribution to ChromiumOS,
 are submitted and reviewed at: https://chromium-review.googlesource.com/
 
 The ChromiumOS project provides an extensive developer guide:
-https://www.chromium.org/chromium-os/developer-guide that assumes running within
-the ChromiumOS build system. Since this is likely not the case for contributing
-to the Chamelium daemon, only the part about uploading changes is relevant:
-https://www.chromium.org/chromium-os/developer-guide#TOC-Upload-your-changes-and-get-a-code-review
+https://chromium.googlesource.com/chromiumos/docs/+/master/developer_guide.md
+It that assumes running within the ChromiumOS build system. Since this is
+likely not the case for contributing to the Chamelium daemon, only the part
+about uploading changes is relevant:
+https://chromium.googlesource.com/chromiumos/docs/+/master/developer_guide.md#Upload-your-changes-and-get-a-code-review
 
 Most of the process is about using the Gerrit web interface for submitting and
 having the change reviewed and not forgetting the Change-Id, TEST= and BUG=
@@ -162,7 +180,7 @@ Support for the Chamelium platform in IGT is found in the following places:
 * lib/igt_chamelium.c: library with Chamelium-related helpers
 * tests/kms_chamelium.c: sub-tests using the Chamelium
 
-As of late August 2017, the following features are tested by IGT:
+As of early April 2019, the following features are tested by IGT:
 * Pixel-by-pixel frame integrity tests for DP and HDMI
 * Error-trend-based frame integrity tests for VGA
 * CRC-based frame integrity tests for DP and HDMI
@@ -173,6 +191,7 @@ As of late August 2017, the following features are tested by IGT:
   each interface or combined
 * EDID display identifier integrity check for all interfaces
 * EDID display identifier change during suspend for all interfaces
+* Audio Fourier-based tests for DP at 48KHz
 
 Future Developments
 -------------------
@@ -180,7 +199,8 @@ Future Developments
 With the current generation of the hardware platform, support for testing a
 number of additional display features could be included as future developments,
 including:
-* Audio capture from HDMI and DP
+* Audio capture from HDMI, with multiple channels and with other playback
+  sampling rates
 * High-bandwidth Digital Content Protection (HDCP) streaming to the display
 * Remote control forwarding (CEC) sent from the display
 * YUV colorspace for HDMI, instead of RGB
diff --git a/lib/igt.h b/lib/igt.h
index 6654a659..5852d557 100644
--- a/lib/igt.h
+++ b/lib/igt.h
@@ -43,6 +43,7 @@
 #include "igt_stats.h"
 #ifdef HAVE_CHAMELIUM
 #include "igt_chamelium.h"
+#include "igt_chamelium_stream.h"
 #endif
 #include "instdone.h"
 #include "intel_batchbuffer.h"
diff --git a/lib/igt_alsa.c b/lib/igt_alsa.c
index bb6682cc..456c0c85 100644
--- a/lib/igt_alsa.c
+++ b/lib/igt_alsa.c
@@ -26,9 +26,11 @@
 
 #include "config.h"
 
+#include <limits.h>
 #include <alsa/asoundlib.h>
 
 #include "igt_alsa.h"
+#include "igt_aux.h"
 #include "igt_core.h"
 
 #define HANDLES_MAX	8
@@ -61,6 +63,25 @@ struct alsa {
 	int input_samples_trigger;
 };
 
+/**
+ * alsa_has_exclusive_access:
+ * Check whether ALSA has exclusive access to audio devices. Fails if
+ * PulseAudio is running.
+ */
+bool alsa_has_exclusive_access(void)
+{
+	if (igt_is_process_running("pulseaudio")) {
+		igt_warn("It seems that PulseAudio is running. Audio tests "
+			 "need direct access to audio devices, so PulseAudio "
+			 "needs to be stopped. You can do so by running "
+			 "`pulseaudio --kill`. Also make sure to add "
+			 "autospawn=no to /etc/pulse/client.conf\n");
+		return false;
+	}
+
+	return true;
+}
+
 static void alsa_error_handler(const char *file, int line, const char *function,
 			       int err, const char *fmt, ...)
 {
@@ -78,6 +99,11 @@ struct alsa *alsa_init(void)
 {
 	struct alsa *alsa;
 
+	if (!alsa_has_exclusive_access()) {
+		igt_warn("alsa doesn't have exclusive access to audio devices\n");
+		return NULL;
+	}
+
 	alsa = malloc(sizeof(struct alsa));
 	memset(alsa, 0, sizeof(struct alsa));
 
@@ -553,16 +579,20 @@ int alsa_run(struct alsa *alsa, int duration_ms)
 					if (ret < 0) {
 						ret = snd_pcm_recover(handle,
 								      ret, 0);
-						if (ret < 0)
+						if (ret < 0) {
+							igt_debug("snd_pcm_recover after snd_pcm_writei failed");
 							goto complete;
+						}
 					}
 
 					output_counts[i] += ret;
 				} else if (output_counts[i] < output_trigger &&
 					   ret < 0) {
 					ret = snd_pcm_recover(handle, ret, 0);
-					if (ret < 0)
+					if (ret < 0) {
+						igt_debug("snd_pcm_recover failed");
 						goto complete;
+					}
 				}
 			}
 
@@ -609,16 +639,20 @@ int alsa_run(struct alsa *alsa, int duration_ms)
 					ret = 0;
 				} else if (ret < 0) {
 					ret = snd_pcm_recover(handle, ret, 0);
-					if (ret < 0)
+					if (ret < 0) {
+						igt_debug("snd_pcm_recover after snd_pcm_readi failed");
 						goto complete;
+					}
 				}
 
 				input_count += ret;
 				input_total += ret;
 			} else if (input_count < input_trigger && ret < 0) {
 				ret = snd_pcm_recover(handle, ret, 0);
-				if (ret < 0)
+				if (ret < 0) {
+					igt_debug("snd_pcm_recover failed");
 					goto complete;
+				}
 			}
 		}
 	} while (!reached);
diff --git a/lib/igt_alsa.h b/lib/igt_alsa.h
index 50795130..5c804b46 100644
--- a/lib/igt_alsa.h
+++ b/lib/igt_alsa.h
@@ -33,6 +33,7 @@
 
 struct alsa;
 
+bool alsa_has_exclusive_access(void);
 struct alsa *alsa_init(void);
 int alsa_open_output(struct alsa *alsa, const char *device_name);
 int alsa_open_input(struct alsa *alsa, const char *device_name);
diff --git a/lib/igt_audio.c b/lib/igt_audio.c
index a0592d53..4cc9bdf0 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,214 @@ 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 amplitude_len = data_len / 2 + 1;
+	double amplitude[amplitude_len];
 	bool detected[signal->freqs_count];
-	int threshold;
-	bool above;
-	int error;
-	int freq = 0;
-	int max;
-	int c, i, j;
-
-	/* Allowed error in Hz due to FFT step. */
-	error = sampling_rate / frames;
+	int ret, epsilon, freq, max_freq;
+	double max, threshold;
+	size_t i, j;
+	bool above, success;
+
+	/* Allowed error in Hz due to FFT step */
+	epsilon = sampling_rate / data_len;
+	igt_debug("allowed freq. error: %d Hz\n", epsilon);
+
+	ret = gsl_fft_real_radix2_transform(data, 1, data_len);
+	igt_assert(ret == 0);
+
+	/* 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 amplitude is encoded as the magnitude of the complex number and
+	 * the phase is encoded as its angle.
+	 */
+	max = 0;
+	amplitude[0] = data[0];
+	for (i = 1; i < amplitude_len - 1; i++) {
+		amplitude[i] = hypot(data[i], data[data_len - i]);
+		if (amplitude[i] > max)
+			max = amplitude[i];
+	}
+	amplitude[amplitude_len - 1] = data[data_len / 2];
 
-	for (c = 0; c < channels; c++) {
-		for (i = 0; i < frames; i++)
-			data[i] = (double) buffer[i * channels + c];
+	for (i = 0; i < signal->freqs_count; i++)
+		detected[i] = false;
 
-		gsl_fft_real_radix2_transform(data, 1, frames);
+	/* We want to detect peaks above a given threshold. */
+	threshold = max / 2;
+	success = true;
+	above = false;
+	max = 0;
+	max_freq = -1;
+	for (i = 0; i < amplitude_len; i++) {
+		freq = sampling_rate * i / data_len;
 
-		max = 0;
+		if (amplitude[i] > threshold)
+			above = true;
 
-		for (i = 0; i < frames / 2; i++) {
-			amplitude[i] = hypot(data[i], data[frames - i]);
-			if (amplitude[i] > max)
-				max = amplitude[i];
+		if (!above) {
+			continue;
 		}
 
-		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;
-					}
+		/* 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 (amplitude[i] < threshold) {
+			for (j = 0; j < signal->freqs_count; j++) {
+				if (signal->freqs[j].freq >
+				    max_freq - epsilon &&
+				    signal->freqs[j].freq <
+				    max_freq + epsilon) {
+					detected[j] = true;
+					igt_debug("Frequency %d detected\n",
+						  max_freq);
+					break;
 				}
+			}
 
-				if (amplitude[i] > max) {
-					max = amplitude[i];
-					freq = sampling_rate * i / frames;
-				}
+			/* We haven't generated this frequency, but we detected
+			 * it. */
+			if (j == signal->freqs_count) {
+				igt_debug("Detected additional frequency: %d\n",
+					  max_freq);
+				success = false;
 			}
+
+			above = false;
+			max = 0;
+			max_freq = -1;
 		}
 
-		for (i = 0; i < signal->freqs_count; i++) {
-			if (!detected[i]) {
-				igt_debug("Missing frequency: %d\n",
-					  signal->freqs[i].freq);
-				return false;
-			}
+		if (amplitude[i] > max) {
+			max = amplitude[i];
+			max_freq = freq;
+		}
+	}
+
+	/* 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;
 		}
 	}
 
-	return true;
+	return success;
+}
+
+/**
+ * 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;
+
+	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;
+}
+
+/**
+ * Creates a new WAV file. sample_rate is in Hz. If path is not NULL, it will
+ * be set to the new file path (the caller is responsible for free-ing it).
+ *
+ * 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.
+ */
+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 fd;
 }
diff --git a/lib/igt_audio.h b/lib/igt_audio.h
index b3b658a4..4aa43e69 100644
--- a/lib/igt_audio.h
+++ b/lib/igt_audio.h
@@ -30,6 +30,7 @@
 #include "config.h"
 
 #include <stdbool.h>
+#include <stdint.h>
 
 struct audio_signal;
 
@@ -37,8 +38,13 @@ struct audio_signal *audio_signal_init(int channels, int sampling_rate);
 int audio_signal_add_frequency(struct audio_signal *signal, int frequency);
 void audio_signal_synthesize(struct audio_signal *signal);
 void audio_signal_clean(struct audio_signal *signal);
-void audio_signal_fill(struct audio_signal *signal, short *buffer, int frames);
-bool audio_signal_detect(struct audio_signal *signal, int channels,
-			 int sampling_rate, short *buffer, int frames);
+void audio_signal_fill(struct audio_signal *signal, int16_t *buffer, int frames);
+bool audio_signal_detect(struct audio_signal *signal, int sampling_rate,
+			 double *data, size_t data_len);
+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);
+int audio_create_wav_file_s32_le(const char *qualifier, uint32_t sample_rate,
+				 uint16_t channels, char **path);
 
 #endif
diff --git a/lib/igt_aux.c b/lib/igt_aux.c
index 05528352..95ceb845 100644
--- a/lib/igt_aux.c
+++ b/lib/igt_aux.c
@@ -1259,6 +1259,37 @@ void igt_set_module_param_int(const char *name, int val)
 	igt_set_module_param(name, str);
 }
 
+/**
+ * igt_is_process_running:
+ * @comm: Name of process in the form found in /proc/pid/comm (limited to 15
+ * chars)
+ *
+ * Returns: true in case the process has been found, false otherwise.
+ *
+ * This function checks in the process table for an entry with the name @comm.
+ */
+int igt_is_process_running(const char *comm)
+{
+	PROCTAB *proc;
+	proc_t *proc_info;
+	bool found = false;
+
+	proc = openproc(PROC_FILLCOM | PROC_FILLSTAT | PROC_FILLARG);
+	igt_assert(proc != NULL);
+
+	while ((proc_info = readproc(proc, NULL))) {
+		if (!strncasecmp(proc_info->cmd, comm, sizeof(proc_info->cmd))) {
+			freeproc(proc_info);
+			found = true;
+			break;
+		}
+		freeproc(proc_info);
+	}
+
+	closeproc(proc);
+	return found;
+}
+
 /**
  * igt_terminate_process:
  * @sig: Signal to send
diff --git a/lib/igt_aux.h b/lib/igt_aux.h
index 55392790..dbd88b67 100644
--- a/lib/igt_aux.h
+++ b/lib/igt_aux.h
@@ -279,6 +279,7 @@ bool igt_allow_unlimited_files(void);
 void igt_set_module_param(const char *name, const char *val);
 void igt_set_module_param_int(const char *name, int val);
 
+int igt_is_process_running(const char *comm);
 int igt_terminate_process(int sig, const char *comm);
 void igt_lsof(const char *dpath);
 
diff --git a/lib/igt_chamelium.c b/lib/igt_chamelium.c
index 02cc9b2c..7c9030d1 100644
--- a/lib/igt_chamelium.c
+++ b/lib/igt_chamelium.c
@@ -218,6 +218,12 @@ void chamelium_destroy_frame_dump(struct chamelium_frame_dump *dump)
 	free(dump);
 }
 
+void chamelium_destroy_audio_file(struct chamelium_audio_file *audio_file)
+{
+	free(audio_file->path);
+	free(audio_file);
+}
+
 struct fsm_monitor_args {
 	struct chamelium *chamelium;
 	struct chamelium_port *port;
@@ -924,6 +930,101 @@ int chamelium_get_captured_frame_count(struct chamelium *chamelium)
 	return ret;
 }
 
+/**
+ * chamelium_start_capturing_audio:
+ * @chamelium: the Chamelium instance
+ * @port: the port to capture audio from (it must support audio)
+ * @save_to_file: whether the captured audio data should be saved to a file on
+ * the Chamelium device
+ *
+ * Starts capturing audio from a Chamelium port. To stop the capture, use
+ * #chamelium_stop_capturing_audio. To retrieve the audio data, either use the
+ * stream server or enable @save_to_file (the latter is mainly useful for
+ * debugging purposes).
+ *
+ * It isn't possible to capture audio from multiple ports at the same time.
+ */
+void chamelium_start_capturing_audio(struct chamelium *chamelium,
+				    struct chamelium_port *port,
+				    bool save_to_file)
+{
+	xmlrpc_value *res;
+
+	res = chamelium_rpc(chamelium, port, "StartCapturingAudio", "(ib)",
+			    port->id, save_to_file);
+	xmlrpc_DECREF(res);
+}
+
+static void audio_format_from_xml(struct chamelium *chamelium,
+				  xmlrpc_value *res, int *rate, int *channels)
+{
+	xmlrpc_value *res_type, *res_rate, *res_sample_format, *res_channel;
+	char *type, *sample_format;
+
+	xmlrpc_struct_find_value(&chamelium->env, res, "file_type", &res_type);
+	xmlrpc_struct_find_value(&chamelium->env, res, "rate", &res_rate);
+	xmlrpc_struct_find_value(&chamelium->env, res, "sample_format", &res_sample_format);
+	xmlrpc_struct_find_value(&chamelium->env, res, "channel", &res_channel);
+
+	xmlrpc_read_string(&chamelium->env, res_type, (const char **) &type);
+	igt_assert(strcmp(type, "raw") == 0);
+	free(type);
+
+	xmlrpc_read_string(&chamelium->env, res_sample_format, (const char **) &sample_format);
+	igt_assert(strcmp(sample_format, "S32_LE") == 0);
+	free(sample_format);
+
+	xmlrpc_read_int(&chamelium->env, res_rate, rate);
+	xmlrpc_read_int(&chamelium->env, res_channel, channels);
+
+	xmlrpc_DECREF(res_channel);
+	xmlrpc_DECREF(res_sample_format);
+	xmlrpc_DECREF(res_rate);
+	xmlrpc_DECREF(res_type);
+}
+
+/**
+ * chamelium_stop_capturing_audio:
+ * @chamelium: the Chamelium instance
+ * @port: the port from which audio is being captured
+ *
+ * Stops capturing audio from a Chamelium port. If
+ * #chamelium_start_capturing_audio has been called with @save_to_file enabled,
+ * this function will return a #chamelium_audio_file struct containing details
+ * about the audio file. Once the caller is done with the struct, they should
+ * release it with #chamelium_destroy_audio_file.
+ */
+struct chamelium_audio_file *chamelium_stop_capturing_audio(struct chamelium *chamelium,
+							    struct chamelium_port *port)
+{
+	xmlrpc_value *res, *res_path, *res_props;
+	struct chamelium_audio_file *file = NULL;
+	char *path;
+
+	res = chamelium_rpc(chamelium, NULL, "StopCapturingAudio", "(i)",
+			    port->id);
+	xmlrpc_array_read_item(&chamelium->env, res, 0, &res_path);
+	xmlrpc_array_read_item(&chamelium->env, res, 1, &res_props);
+
+	xmlrpc_read_string(&chamelium->env, res_path, (const char **) &path);
+
+	if (strlen(path) > 0) {
+		file = calloc(1, sizeof(*file));
+		file->path = path;
+
+		audio_format_from_xml(chamelium, res_props,
+				      &file->rate, &file->channels);
+	} else {
+		free(path);
+	}
+
+	xmlrpc_DECREF(res_props);
+	xmlrpc_DECREF(res_path);
+	xmlrpc_DECREF(res);
+
+	return file;
+}
+
 static pixman_image_t *convert_frame_format(pixman_image_t *src,
 					    int format)
 {
diff --git a/lib/igt_chamelium.h b/lib/igt_chamelium.h
index 233ead85..047f8c5d 100644
--- a/lib/igt_chamelium.h
+++ b/lib/igt_chamelium.h
@@ -53,6 +53,12 @@ enum chamelium_check {
 	CHAMELIUM_CHECK_CRC,
 };
 
+struct chamelium_audio_file {
+	char *path;
+	int rate; /* Hz */
+	int channels;
+};
+
 struct chamelium *chamelium_init(int drm_fd);
 void chamelium_deinit(struct chamelium *chamelium);
 void chamelium_reset(struct chamelium *chamelium);
@@ -100,6 +106,10 @@ void chamelium_start_capture(struct chamelium *chamelium,
 void chamelium_stop_capture(struct chamelium *chamelium, int frame_count);
 void chamelium_capture(struct chamelium *chamelium, struct chamelium_port *port,
 		       int x, int y, int w, int h, int frame_count);
+void chamelium_start_capturing_audio(struct chamelium *chamelium,
+				    struct chamelium_port *port, bool save_to_file);
+struct chamelium_audio_file *chamelium_stop_capturing_audio(struct chamelium *chamelium,
+							    struct chamelium_port *port);
 igt_crc_t *chamelium_read_captured_crcs(struct chamelium *chamelium,
 					int *frame_count);
 struct chamelium_frame_dump *chamelium_read_captured_frame(struct chamelium *chamelium,
@@ -131,5 +141,6 @@ void chamelium_assert_frame_match_or_dump(struct chamelium *chamelium,
 void chamelium_crop_analog_frame(struct chamelium_frame_dump *dump, int width,
 				 int height);
 void chamelium_destroy_frame_dump(struct chamelium_frame_dump *dump);
+void chamelium_destroy_audio_file(struct chamelium_audio_file *audio_file);
 
 #endif /* IGT_CHAMELIUM_H */
diff --git a/lib/igt_chamelium_stream.c b/lib/igt_chamelium_stream.c
new file mode 100644
index 00000000..9e1ba1ca
--- /dev/null
+++ b/lib/igt_chamelium_stream.c
@@ -0,0 +1,592 @@
+/*
+ * Copyright © 2019 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: Simon Ser <simon.ser@intel.com>
+ */
+
+#include "config.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "igt_chamelium_stream.h"
+#include "igt_core.h"
+#include "igt_rc.h"
+
+#define STREAM_PORT 9994
+#define STREAM_VERSION_MAJOR 1
+#define STREAM_VERSION_MINOR 0
+
+enum stream_error {
+	STREAM_ERROR_NONE = 0,
+	STREAM_ERROR_COMMAND = 1,
+	STREAM_ERROR_ARGUMENT = 2,
+	STREAM_ERROR_EXISTS = 3,
+	STREAM_ERROR_VIDEO_MEM_OVERFLOW_STOP = 4,
+	STREAM_ERROR_VIDEO_MEM_OVERFLOW_DROP = 5,
+	STREAM_ERROR_AUDIO_MEM_OVERFLOW_STOP = 6,
+	STREAM_ERROR_AUDIO_MEM_OVERFLOW_DROP = 7,
+	STREAM_ERROR_NO_MEM = 8,
+};
+
+enum stream_message_kind {
+	STREAM_MESSAGE_REQUEST = 0,
+	STREAM_MESSAGE_RESPONSE = 1,
+	STREAM_MESSAGE_DATA = 2,
+};
+
+enum stream_message_type {
+	STREAM_MESSAGE_RESET = 0,
+	STREAM_MESSAGE_GET_VERSION = 1,
+	STREAM_MESSAGE_VIDEO_STREAM = 2,
+	STREAM_MESSAGE_SHRINK_VIDEO = 3,
+	STREAM_MESSAGE_VIDEO_FRAME = 4,
+	STREAM_MESSAGE_DUMP_REALTIME_VIDEO = 5,
+	STREAM_MESSAGE_STOP_DUMP_VIDEO = 6,
+	STREAM_MESSAGE_DUMP_REALTIME_AUDIO = 7,
+	STREAM_MESSAGE_STOP_DUMP_AUDIO = 8,
+};
+
+struct chamelium_stream {
+	char *host;
+	unsigned int port;
+
+	int fd;
+};
+
+static const char *stream_error_str(enum stream_error err)
+{
+	switch (err) {
+	case STREAM_ERROR_NONE:
+		return "no error";
+	case STREAM_ERROR_COMMAND:
+		return "invalid command";
+	case STREAM_ERROR_ARGUMENT:
+		return "invalid arguments";
+	case STREAM_ERROR_EXISTS:
+		return "dump already started";
+	case STREAM_ERROR_VIDEO_MEM_OVERFLOW_STOP:
+		return "video dump stopped after overflow";
+	case STREAM_ERROR_VIDEO_MEM_OVERFLOW_DROP:
+		return "video frame dropped after overflow";
+	case STREAM_ERROR_AUDIO_MEM_OVERFLOW_STOP:
+		return "audio dump stoppred after overflow";
+	case STREAM_ERROR_AUDIO_MEM_OVERFLOW_DROP:
+		return "audio page dropped after overflow";
+	case STREAM_ERROR_NO_MEM:
+		return "out of memory";
+	}
+	return "unknown error";
+}
+
+/**
+ * The Chamelium URL is specified in the configuration file. We need to extract
+ * the host to connect to the stream server.
+ */
+static char *parse_url_host(const char *url)
+{
+	static const char prefix[] = "http://";
+	char *colon;
+
+	if (strstr(url, prefix) != url)
+		return NULL;
+	url += strlen(prefix);
+
+	colon = strchr(url, ':');
+	if (!colon)
+		return NULL;
+
+	return strndup(url, colon - url);
+}
+
+static bool chamelium_stream_read_config(struct chamelium_stream *client)
+{
+	GError *error = NULL;
+	gchar *chamelium_url;
+
+	if (!igt_key_file) {
+		igt_warn("No configuration file available for chamelium\n");
+		return false;
+	}
+
+	chamelium_url = g_key_file_get_string(igt_key_file, "Chamelium", "URL",
+					      &error);
+	if (!chamelium_url) {
+		igt_warn("Couldn't read Chamelium URL from config file: %s\n",
+			 error->message);
+		return false;
+	}
+
+	client->host = parse_url_host(chamelium_url);
+	if (!client->host) {
+		igt_warn("Invalid Chamelium URL in config file: %s\n",
+			 chamelium_url);
+		return false;
+	}
+	client->port = STREAM_PORT;
+
+	return true;
+}
+
+static bool chamelium_stream_connect(struct chamelium_stream *client)
+{
+	int ret;
+	char port_str[16];
+	struct addrinfo hints = {};
+	struct addrinfo *results, *ai;
+	struct timeval tv = {};
+
+	igt_debug("Connecting to Chamelium stream server: tcp://%s:%u\n",
+		  client->host, client->port);
+
+	snprintf(port_str, sizeof(port_str), "%u", client->port);
+
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+	ret = getaddrinfo(client->host, port_str, &hints, &results);
+	if (ret != 0) {
+		igt_warn("getaddrinfo failed: %s\n", gai_strerror(ret));
+		return false;
+	}
+
+	client->fd = -1;
+	for (ai = results; ai != NULL; ai = ai->ai_next) {
+		client->fd = socket(ai->ai_family, ai->ai_socktype,
+				    ai->ai_protocol);
+		if (client->fd == -1)
+			continue;
+
+		if (connect(client->fd, ai->ai_addr, ai->ai_addrlen) == -1) {
+			close(client->fd);
+			client->fd = -1;
+			continue;
+		}
+
+		break;
+	}
+
+	freeaddrinfo(results);
+
+	if (client->fd < 0) {
+		igt_warn("Failed to connect to Chamelium stream server\n");
+		return false;
+	}
+
+	/* Set a read and write timeout of 5 seconds. */
+	tv.tv_sec = 5;
+	setsockopt(client->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+	setsockopt(client->fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
+
+	return true;
+}
+
+static bool read_whole(int fd, void *buf, size_t buf_len)
+{
+	ssize_t ret;
+	size_t n = 0;
+	char *ptr;
+
+	while (n < buf_len) {
+		ptr = (char *) buf + n;
+		ret = read(fd, ptr, buf_len - n);
+		if (ret < 0) {
+			igt_warn("read failed: %s\n", strerror(errno));
+			return false;
+		} else if (ret == 0) {
+			igt_warn("short read\n");
+			return false;
+		}
+		n += ret;
+	}
+
+	return true;
+}
+
+static bool write_whole(int fd, void *buf, size_t buf_len)
+{
+	ssize_t ret;
+	size_t n = 0;
+	char *ptr;
+
+	while (n < buf_len) {
+		ptr = (char *) buf + n;
+		ret = write(fd, ptr, buf_len - n);
+		if (ret < 0) {
+			igt_warn("write failed: %s\n", strerror(errno));
+			return false;
+		} else if (ret == 0) {
+			igt_warn("short write\n");
+			return false;
+		}
+		n += ret;
+	}
+
+	return true;
+}
+
+static bool read_and_discard(int fd, size_t len)
+{
+	char buf[1024];
+	size_t n;
+
+	while (len > 0) {
+		n = len;
+		if (n > sizeof(buf))
+			n = sizeof(buf);
+
+		if (!read_whole(fd, buf, n))
+			return false;
+
+		len -= n;
+	}
+
+	return true;
+}
+
+/** Read a message header from the socket.
+ *
+ * The header is laid out as follows:
+ * - u16: message type
+ * - u16: error code
+ * - u32: message length
+ */
+static bool chamelium_stream_read_header(struct chamelium_stream *client,
+					 enum stream_message_kind *kind,
+					 enum stream_message_type *type,
+					 enum stream_error *err,
+					 size_t *len)
+{
+	uint16_t _type;
+	char buf[8];
+
+	if (!read_whole(client->fd, buf, sizeof(buf)))
+		return false;
+
+	_type = ntohs(*(uint16_t *) &buf[0]);
+	*type = _type & 0xFF;
+	*kind = _type >> 8;
+	*err = ntohs(*(uint16_t *) &buf[2]);
+	*len = ntohl(*(uint32_t *) &buf[4]);
+
+	//igt_debug("received message: kind=%d type=%d err=%d len=%zu\n",
+	//	  *kind, *type, *err, *len);
+
+	return true;
+}
+
+static bool chamelium_stream_write_header(struct chamelium_stream *client,
+					  enum stream_message_type type,
+					  enum stream_error err,
+					  size_t len)
+{
+	char buf[8];
+	uint16_t _type;
+
+	_type = type | (STREAM_MESSAGE_REQUEST << 8);
+
+	*(uint16_t *) &buf[0] = htons(_type);
+	*(uint16_t *) &buf[2] = htons(err);
+	*(uint32_t *) &buf[4] = htonl(len);
+
+	return write_whole(client->fd, buf, sizeof(buf));
+}
+
+static bool chamelium_stream_read_response(struct chamelium_stream *client,
+					   enum stream_message_type type,
+					   void *buf, size_t buf_len)
+{
+	enum stream_message_kind read_kind;
+	enum stream_message_type read_type;
+	enum stream_error read_err;
+	size_t read_len;
+
+	if (!chamelium_stream_read_header(client, &read_kind, &read_type,
+					  &read_err, &read_len))
+		return false;
+
+	if (read_kind != STREAM_MESSAGE_RESPONSE) {
+		igt_warn("Expected a response, got kind %d\n", read_kind);
+		return false;
+	}
+	if (read_type != type) {
+		igt_warn("Expected message type %d, got %d\n",
+			 type, read_type);
+		return false;
+	}
+	if (read_err != STREAM_ERROR_NONE) {
+		igt_warn("Received error: %s (%d)\n",
+			 stream_error_str(read_err), read_err);
+		return false;
+	}
+	if (buf_len != read_len) {
+		igt_warn("Received invalid message body size "
+			 "(got %zu bytes, want %zu bytes)\n",
+			 read_len, buf_len);
+		return false;
+	}
+
+	return read_whole(client->fd, buf, buf_len);
+}
+
+static bool chamelium_stream_write_request(struct chamelium_stream *client,
+					   enum stream_message_type type,
+					   void *buf, size_t buf_len)
+{
+	if (!chamelium_stream_write_header(client, type, STREAM_ERROR_NONE,
+					   buf_len))
+		return false;
+
+	if (buf_len == 0)
+		return true;
+
+	return write_whole(client->fd, buf, buf_len);
+}
+
+static bool chamelium_stream_call(struct chamelium_stream *client,
+				  enum stream_message_type type,
+				  void *req_buf, size_t req_len,
+				  void *resp_buf, size_t resp_len)
+{
+	if (!chamelium_stream_write_request(client, type, req_buf, req_len))
+		return false;
+
+	return chamelium_stream_read_response(client, type, resp_buf, resp_len);
+}
+
+static bool chamelium_stream_check_version(struct chamelium_stream *client)
+{
+	char resp[2];
+	uint8_t major, minor;
+
+	if (!chamelium_stream_call(client, STREAM_MESSAGE_GET_VERSION,
+				   NULL, 0, resp, sizeof(resp)))
+		return false;
+
+	major = resp[0];
+	minor = resp[1];
+	if (major != STREAM_VERSION_MAJOR || minor < STREAM_VERSION_MINOR) {
+		igt_warn("Version mismatch (want %d.%d, got %d.%d)\n",
+			 STREAM_VERSION_MAJOR, STREAM_VERSION_MINOR,
+			 major, minor);
+		return false;
+	}
+
+	return true;
+}
+
+/**
+ * chamelium_stream_dump_realtime_audio:
+ *
+ * Starts audio capture. The caller can then call
+ * #chamelium_stream_receive_realtime_audio to receive audio pages.
+ */
+bool chamelium_stream_dump_realtime_audio(struct chamelium_stream *client,
+					  enum chamelium_stream_realtime_mode mode)
+{
+	char req[1];
+
+	igt_debug("Starting real-time audio capture\n");
+
+	req[0] = mode;
+	return chamelium_stream_call(client, STREAM_MESSAGE_DUMP_REALTIME_AUDIO,
+				     req, sizeof(req), NULL, 0);
+}
+
+/**
+ * chamelium_stream_receive_realtime_audio:
+ * @page_count: if non-NULL, will be set to the dumped page number
+ * @buf: must either point to a dynamically allocated memory region or NULL
+ * @buf_len: number of elements of *@buf, for zero if @buf is NULL
+ *
+ * Receives one audio page from the streaming server.
+ *
+ * In "best effort" mode, some pages can be dropped. This can be detected via
+ * the page count.
+ *
+ * buf_len will be set to the size of the page. The caller is responsible for
+ * calling free(3) on *buf.
+ */
+bool chamelium_stream_receive_realtime_audio(struct chamelium_stream *client,
+					     size_t *page_count,
+					     int32_t **buf, size_t *buf_len)
+{
+	enum stream_message_kind kind;
+	enum stream_message_type type;
+	enum stream_error err;
+	size_t body_len;
+	char page_count_buf[4];
+	int32_t *ptr;
+
+	while (true) {
+		if (!chamelium_stream_read_header(client, &kind, &type,
+						  &err, &body_len))
+			return false;
+
+		if (kind != STREAM_MESSAGE_DATA) {
+			igt_warn("Expected a data message, got kind %d\n", kind);
+			return false;
+		}
+		if (type != STREAM_MESSAGE_DUMP_REALTIME_AUDIO) {
+			igt_warn("Expected real-time audio dump message, "
+				 "got type %d\n", type);
+			return false;
+		}
+
+		if (err == STREAM_ERROR_NONE)
+			break;
+		else if (err != STREAM_ERROR_AUDIO_MEM_OVERFLOW_DROP) {
+			igt_warn("Received error: %s (%d)\n",
+				 stream_error_str(err), err);
+			return false;
+		}
+
+		igt_debug("Dropped an audio page because of an overflow\n");
+		igt_assert(body_len == 0);
+	}
+
+	igt_assert(body_len >= sizeof(page_count_buf));
+
+	if (!read_whole(client->fd, page_count_buf, sizeof(page_count_buf)))
+		return false;
+	if (page_count)
+		*page_count = ntohl(*(uint32_t *) &page_count_buf[0]);
+	body_len -= sizeof(page_count_buf);
+
+	igt_assert(body_len % sizeof(int32_t) == 0);
+	if (*buf_len * sizeof(int32_t) != body_len) {
+		ptr = realloc(*buf, body_len);
+		if (!ptr) {
+			igt_warn("realloc failed: %s\n", strerror(errno));
+			return false;
+		}
+		*buf = ptr;
+		*buf_len = body_len / sizeof(int32_t);
+	}
+
+	return read_whole(client->fd, *buf, body_len);
+}
+
+/**
+ * chamelium_stream_stop_realtime_audio:
+ *
+ * Stops real-time audio capture. This also drops any buffered audio pages.
+ * The caller shouldn't call #chamelium_stream_receive_realtime_audio after
+ * stopping audio capture.
+ */
+bool chamelium_stream_stop_realtime_audio(struct chamelium_stream *client)
+{
+	enum stream_message_kind kind;
+	enum stream_message_type type;
+	enum stream_error err;
+	size_t len;
+
+	igt_debug("Stopping real-time audio capture\n");
+
+	if (!chamelium_stream_write_request(client,
+					    STREAM_MESSAGE_STOP_DUMP_AUDIO,
+					    NULL, 0))
+		return false;
+
+	while (true) {
+		if (!chamelium_stream_read_header(client, &kind, &type,
+						  &err, &len))
+			return false;
+
+		if (kind == STREAM_MESSAGE_RESPONSE)
+			break;
+
+		if (!read_and_discard(client->fd, len))
+			return false;
+	}
+
+	if (type != STREAM_MESSAGE_STOP_DUMP_AUDIO) {
+		igt_warn("Unexpected response type %d\n", type);
+		return false;
+	}
+	if (err != STREAM_ERROR_NONE) {
+		igt_warn("Received error: %s (%d)\n",
+			 stream_error_str(err), err);
+		return false;
+	}
+	if (len != 0) {
+		igt_warn("Expected an empty response, got %zu bytes\n", len);
+		return false;
+	}
+
+	return true;
+}
+
+/**
+ * chamelium_stream_audio_format:
+ *
+ * Gets the format used for audio pages.
+ *
+ * Data will always be captured in raw pages of S32_LE elements. This function
+ * exposes the sampling rate and the number of channels.
+ */
+void chamelium_stream_audio_format(struct chamelium_stream *stream,
+				   int *rate, int *channels)
+{
+	/* TODO: the Chamelium streaming server doesn't expose those yet.
+	 * Just hardcode the values for now. */
+	*rate = 48000;
+	*channels = 8;
+}
+
+/**
+ * chamelium_stream_init:
+ *
+ * Connects to the Chamelium streaming server.
+ */
+struct chamelium_stream *chamelium_stream_init(void)
+{
+	struct chamelium_stream *client;
+
+	client = calloc(1, sizeof(*client));
+
+	if (!chamelium_stream_read_config(client))
+		goto error_client;
+	if (!chamelium_stream_connect(client))
+		goto error_client;
+	if (!chamelium_stream_check_version(client))
+		goto error_fd;
+
+	return client;
+
+error_fd:
+	close(client->fd);
+error_client:
+	free(client);
+	return NULL;
+}
+
+void chamelium_stream_deinit(struct chamelium_stream *client)
+{
+	if (close(client->fd) != 0)
+		igt_warn("close failed: %s\n", strerror(errno));
+	free(client);
+}
diff --git a/lib/igt_chamelium_stream.h b/lib/igt_chamelium_stream.h
new file mode 100644
index 00000000..de4e9931
--- /dev/null
+++ b/lib/igt_chamelium_stream.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright © 2019 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: Simon Ser <simon.ser@intel.com>
+ */
+
+#ifndef IGT_CHAMELIUM_STREAM_H
+#define IGT_CHAMELIUM_STREAM_H
+
+#include "config.h"
+
+enum chamelium_stream_realtime_mode {
+	CHAMELIUM_STREAM_REALTIME_NONE = 0,
+	/* stop dumping when overflow */
+	CHAMELIUM_STREAM_REALTIME_STOP_WHEN_OVERFLOW = 1,
+	/* drop data on overflow */
+	CHAMELIUM_STREAM_REALTIME_BEST_EFFORT = 2,
+};
+
+struct chamelium_stream;
+
+struct chamelium_stream *chamelium_stream_init(void);
+void chamelium_stream_deinit(struct chamelium_stream *client);
+bool chamelium_stream_dump_realtime_audio(struct chamelium_stream *client,
+					  enum chamelium_stream_realtime_mode mode);
+void chamelium_stream_audio_format(struct chamelium_stream *stream,
+				   int *rate, int *channels);
+bool chamelium_stream_receive_realtime_audio(struct chamelium_stream *client,
+					     size_t *page_count,
+					     int32_t **buf, size_t *buf_len);
+bool chamelium_stream_stop_realtime_audio(struct chamelium_stream *client);
+
+#endif
diff --git a/lib/meson.build b/lib/meson.build
index a8462933..eead0afb 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -92,7 +92,7 @@ if valgrind.found()
 endif
 
 if gsl.found()
-	lib_deps += [ gsl ]
+	lib_deps += gsl
 	lib_sources += [ 'igt_frame.c', 'igt_audio.c' ]
 endif
 
@@ -101,9 +101,10 @@ if alsa.found()
 	lib_sources += 'igt_alsa.c'
 endif
 
-if chamelium.found()
+if chamelium_found
 	lib_deps += chamelium
 	lib_sources += 'igt_chamelium.c'
+	lib_sources += 'igt_chamelium_stream.c'
 endif
 
 srcdir = join_paths(meson.source_root(), 'tests')
diff --git a/meson.build b/meson.build
index 557400a5..be6dff9d 100644
--- a/meson.build
+++ b/meson.build
@@ -64,8 +64,6 @@ _build_overlay = false
 _overlay_required = false
 _build_man = false
 _man_required = false
-_build_audio = false
-_audio_required = false
 _build_chamelium = false
 _chamelium_required = false
 _build_docs = false
@@ -79,7 +77,6 @@ build_overlay = get_option('build_overlay')
 overlay_backends = get_option('overlay_backends')
 build_man = get_option('build_man')
 with_valgrind = get_option('with_valgrind')
-build_audio = get_option('build_audio')
 build_chamelium = get_option('build_chamelium')
 build_docs = get_option('build_docs')
 build_tests = get_option('build_tests')
@@ -91,8 +88,6 @@ _build_overlay = build_overlay != 'false'
 _overlay_required = build_overlay == 'true'
 _build_man = build_man != 'false'
 _man_required = build_man == 'true'
-_build_audio = build_audio != 'false'
-_audio_required = build_audio == 'true'
 _build_chamelium = build_chamelium != 'false'
 _chamelium_required = build_chamelium == 'true'
 _build_docs = build_docs != 'false'
@@ -166,26 +161,6 @@ cairo = dependency('cairo', version : '>1.12.0', required : true)
 libudev = dependency('libudev', required : true)
 glib = dependency('glib-2.0', required : true)
 
-gsl = null_dep
-alsa = null_dep
-if _build_audio or _build_chamelium
-	gsl = dependency('gsl', required : _audio_required or _chamelium_required)
-endif
-if _build_audio
-	alsa = dependency('alsa', required : _audio_required)
-endif
-
-audioinfo = 'No'
-if _build_audio and alsa.found() and gsl.found()
-	audioinfo = 'Yes'
-else
-	if _audio_required
-		error('Cannot build audio test due to missing dependencies')
-	endif
-	_build_audio = false
-endif
-build_info += 'Build audio test: ' + audioinfo
-
 xmlrpc = dependency('xmlrpc', required : false)
 xmlrpc_util = dependency('xmlrpc_util', required : false)
 xmlrpc_client = dependency('xmlrpc_client', required : false)
@@ -197,21 +172,32 @@ if not xmlrpc.found() and xmlrpc_cmd.found()
 
 	if libs_cmd.returncode() == 0 and cflags_cmd.returncode() == 0
 		xmlrpc = declare_dependency(compile_args: cflags_cmd.stdout().strip().split(),
-					   link_args : libs_cmd.stdout().strip().split())
+					    link_args : libs_cmd.stdout().strip().split())
 		xmlrpc_util = declare_dependency()
 		xmlrpc_client = declare_dependency()
 	endif
 endif
 
+gsl = null_dep
+alsa = null_dep
 chamelium = null_dep
+chamelium_found = false # TODO: use a disabler object instead
 chameliuminfo = 'No'
-if _build_chamelium and gsl.found() and xmlrpc.found() and xmlrpc_util.found() and xmlrpc_client.found()
-	chamelium = declare_dependency(dependencies : [ xmlrpc,
-							xmlrpc_util, xmlrpc_client])
-	config.set('HAVE_CHAMELIUM', 1)
-	chameliuminfo = 'Yes'
-elif _chamelium_required
-	error('Cannot build chamelium test due to missing dependencies')
+if _build_chamelium
+	gsl = dependency('gsl', required : _chamelium_required)
+	alsa = dependency('alsa', required : _chamelium_required)
+	chamelium = declare_dependency(dependencies : [
+		xmlrpc,
+		xmlrpc_util,
+		xmlrpc_client,
+		gsl,
+		alsa,
+	], required : _chamelium_required)
+	if xmlrpc.found() and xmlrpc_util.found() and xmlrpc_client.found() and gsl.found() and alsa.found()
+		config.set('HAVE_CHAMELIUM', 1)
+		chameliuminfo = 'Yes'
+		chamelium_found = true
+	endif
 endif
 build_info += 'Build Chamelium test: ' + chameliuminfo
 
diff --git a/meson_options.txt b/meson_options.txt
index 0cd3b350..888efe56 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -10,12 +10,6 @@ option('overlay_backends',
        choices : [ 'auto', 'x', 'xv' ],
        description : 'Overlay backends to enable')
 
-option('build_audio',
-       type : 'combo',
-       value : 'auto',
-       choices : ['auto', 'true', 'false'],
-       description : 'Build audio test')
-
 option('build_chamelium',
        type : 'combo',
        value : 'auto',
diff --git a/tests/audio.c b/tests/audio.c
deleted file mode 100644
index 560876a3..00000000
--- a/tests/audio.c
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * 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 <paul.kocialkowski@linux.intel.com>
- */
-
-#include "config.h"
-#include "igt.h"
-
-#define PLAYBACK_CHANNELS	2
-#define PLAYBACK_FRAMES		1024
-
-#define CAPTURE_SAMPLE_RATE	48000
-#define CAPTURE_CHANNELS	2
-#define CAPTURE_DEVICE_NAME	"default"
-#define CAPTURE_FRAMES		2048
-
-#define RUN_TIMEOUT		2000
-
-struct test_data {
-	struct alsa *alsa;
-	struct audio_signal *signal;
-
-	int streak;
-};
-
-static int sampling_rates[] = {
-	32000,
-	44100,
-	48000,
-	88200,
-	96000,
-	176400,
-	192000,
-};
-
-static int sampling_rates_count = sizeof(sampling_rates) / sizeof(int);
-
-static int test_frequencies[] = {
-	300,
-	600,
-	1200,
-	80000,
-	10000,
-};
-
-static int test_frequencies_count = sizeof(test_frequencies) / sizeof(int);
-
-static int output_callback(void *data, short *buffer, int frames)
-{
-	struct test_data *test_data = (struct test_data *) data;
-
-	audio_signal_fill(test_data->signal, buffer, frames);
-
-	return 0;
-}
-
-static int input_callback(void *data, short *buffer, int frames)
-{
-	struct test_data *test_data = (struct test_data *) data;
-	bool detect;
-
-	detect = audio_signal_detect(test_data->signal, CAPTURE_CHANNELS,
-				     CAPTURE_SAMPLE_RATE, buffer, frames);
-	if (detect)
-		test_data->streak++;
-	else
-		test_data->streak = 0;
-
-	/* A streak of 3 gives confidence that the signal is good. */
-	if (test_data->streak == 3)
-		return 1;
-
-	return 0;
-}
-
-static void test_integrity(const char *device_name)
-{
-	struct test_data data;
-	int sampling_rate;
-	bool run = false;
-	bool test;
-	int i, j;
-	int ret;
-
-	data.alsa = alsa_init();
-	igt_assert(data.alsa);
-
-	ret = alsa_open_input(data.alsa, CAPTURE_DEVICE_NAME);
-	igt_assert(ret >= 0);
-
-	alsa_configure_input(data.alsa, CAPTURE_CHANNELS,
-			     CAPTURE_SAMPLE_RATE);
-
-	alsa_register_input_callback(data.alsa, input_callback, &data,
-				     CAPTURE_FRAMES);
-
-	for (i = 0; i < sampling_rates_count; i++) {
-		ret = alsa_open_output(data.alsa, device_name);
-		igt_assert(ret >= 0);
-
-		sampling_rate = sampling_rates[i];
-
-		test = alsa_test_output_configuration(data.alsa,
-						      PLAYBACK_CHANNELS,
-						      sampling_rate);
-		if (!test) {
-			alsa_close_output(data.alsa);
-			continue;
-		}
-
-		igt_debug("Testing with sampling rate %d\n", sampling_rate);
-
-		alsa_configure_output(data.alsa, PLAYBACK_CHANNELS,
-				       sampling_rate);
-
-		data.signal = audio_signal_init(PLAYBACK_CHANNELS,
-						sampling_rate);
-		igt_assert(data.signal);
-
-		for (j = 0; j < test_frequencies_count; j++)
-			audio_signal_add_frequency(data.signal,
-						   test_frequencies[j]);
-
-		audio_signal_synthesize(data.signal);
-
-		alsa_register_output_callback(data.alsa, output_callback,
-					      &data, PLAYBACK_FRAMES);
-
-		data.streak = 0;
-
-		ret = alsa_run(data.alsa, RUN_TIMEOUT);
-		igt_assert(ret > 0);
-
-		audio_signal_clean(data.signal);
-		free(data.signal);
-
-		alsa_close_output(data.alsa);
-
-		run = true;
-	}
-
-	/* Make sure we tested at least one frequency */
-	igt_assert(run);
-
-	alsa_close_input(data.alsa);
-	free(data.alsa);
-}
-
-static void test_suspend_resume_integrity(const char *device_name,
-					  enum igt_suspend_state state,
-					  enum igt_suspend_test test)
-{
-	test_integrity(device_name);
-
-	igt_system_suspend_autoresume(state, test);
-
-	test_integrity(device_name);
-}
-
-igt_main
-{
-	igt_subtest("hdmi-integrity")
-		test_integrity("HDMI");
-
-	igt_subtest("hdmi-integrity-after-suspend")
-		test_suspend_resume_integrity("HDMI", SUSPEND_STATE_MEM,
-					      SUSPEND_TEST_NONE);
-
-	igt_subtest("hdmi-integrity-after-hibernate")
-		test_suspend_resume_integrity("HDMI", SUSPEND_STATE_DISK,
-					      SUSPEND_TEST_DEVICES);
-}
diff --git a/tests/kms_chamelium.c b/tests/kms_chamelium.c
index 2dc1049d..2974ff69 100644
--- a/tests/kms_chamelium.c
+++ b/tests/kms_chamelium.c
@@ -413,7 +413,7 @@ test_suspend_resume_edid_change(data_t *data, struct chamelium_port *port,
 
 static igt_output_t *
 prepare_output(data_t *data,
-	       struct chamelium_port *port)
+	       struct chamelium_port *port, bool set_edid)
 {
 	igt_display_t *display = &data->display;
 	igt_output_t *output;
@@ -428,7 +428,8 @@ prepare_output(data_t *data,
 	/* The chamelium's default EDID has a lot of resolutions, way more then
 	 * we need to test
 	 */
-	chamelium_port_set_edid(data->chamelium, port, data->edid_id);
+	if (set_edid)
+		chamelium_port_set_edid(data->chamelium, port, data->edid_id);
 
 	chamelium_plug(data->chamelium, port);
 	wait_for_connector(data, port, DRM_MODE_CONNECTED);
@@ -613,7 +614,7 @@ static void test_display_one_mode(data_t *data, struct chamelium_port *port,
 
 	reset_state(data, port);
 
-	output = prepare_output(data, port);
+	output = prepare_output(data, port, true);
 	connector = chamelium_port_get_connector(data->chamelium, port, false);
 	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
 	igt_assert(primary);
@@ -644,7 +645,7 @@ static void test_display_all_modes(data_t *data, struct chamelium_port *port,
 
 	reset_state(data, port);
 
-	output = prepare_output(data, port);
+	output = prepare_output(data, port, true);
 	connector = chamelium_port_get_connector(data->chamelium, port, false);
 	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
 	igt_assert(primary);
@@ -679,7 +680,7 @@ test_display_frame_dump(data_t *data, struct chamelium_port *port)
 
 	reset_state(data, port);
 
-	output = prepare_output(data, port);
+	output = prepare_output(data, port, true);
 	connector = chamelium_port_get_connector(data->chamelium, port, false);
 	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
 	igt_assert(primary);
@@ -710,6 +711,266 @@ test_display_frame_dump(data_t *data, struct chamelium_port *port)
 	drmModeFreeConnector(connector);
 }
 
+
+/* Playback parameters control the audio signal we synthesize and send */
+#define PLAYBACK_CHANNELS 2
+#define PLAYBACK_SAMPLES 1024
+
+/* Capture paremeters control the audio signal we receive */
+#define CAPTURE_SAMPLES 2048
+
+#define AUDIO_DURATION 2000 /* ms */
+/* A streak of 3 gives confidence that the signal is good. */
+#define MIN_STREAK 3
+
+/* TODO: Chamelium only supports 48KHz for now */
+static int sampling_rates[] = {
+/*	32000, */
+/*	44100, */
+	48000,
+/*	88200, */
+/*	96000, */
+/*	176400, */
+/*	192000, */
+};
+
+static int sampling_rates_count = sizeof(sampling_rates) / sizeof(int);
+
+static int test_frequencies[] = {
+	300,
+	600,
+	1200,
+	80000,
+	10000,
+};
+
+static int test_frequencies_count = sizeof(test_frequencies) / sizeof(int);
+
+static int
+output_callback(void *data, short *buffer, int frames)
+{
+	struct audio_signal *signal = (struct audio_signal *) data;
+
+	audio_signal_fill(signal, buffer, frames);
+
+	return 0;
+}
+
+static bool
+do_test_display_audio(data_t *data, struct chamelium_port *port,
+		      struct alsa *alsa, int playback_channels,
+		      int playback_rate)
+{
+	int ret, capture_rate, capture_channels, msec;
+	struct chamelium_audio_file *audio_file;
+	struct chamelium_stream *stream;
+	enum chamelium_stream_realtime_mode stream_mode;
+	struct audio_signal *signal;
+	int32_t *recv, *buf;
+	double *channel;
+	size_t i, streak, page_count;
+	size_t recv_len, buf_len, buf_cap, buf_size, channel_len;
+	bool ok;
+	char dump_suffix[64];
+	char *dump_path = NULL;
+	int dump_fd = -1;
+
+	if (!alsa_test_output_configuration(alsa, playback_channels,
+					    playback_rate))
+		return false;
+
+	igt_debug("Testing with playback sampling rate %d\n", playback_rate);
+	alsa_configure_output(alsa, playback_channels, playback_rate);
+
+	chamelium_start_capturing_audio(data->chamelium, port, false);
+
+	stream = chamelium_stream_init();
+	igt_assert(stream);
+
+	stream_mode = CHAMELIUM_STREAM_REALTIME_STOP_WHEN_OVERFLOW;
+	ok = chamelium_stream_dump_realtime_audio(stream, stream_mode);
+	igt_assert(ok);
+
+	chamelium_stream_audio_format(stream, &capture_rate, &capture_channels);
+
+	if (igt_frame_dump_is_enabled()) {
+		snprintf(dump_suffix, sizeof(dump_suffix), "capture-%dch-%d",
+			 playback_channels, playback_rate);
+
+		dump_fd = audio_create_wav_file_s32_le(dump_suffix,
+						       capture_rate,
+						       capture_channels,
+						       &dump_path);
+		igt_assert(dump_fd >= 0);
+	}
+
+	signal = audio_signal_init(playback_channels, playback_rate);
+	igt_assert(signal);
+
+	for (i = 0; i < test_frequencies_count; i++)
+		audio_signal_add_frequency(signal, test_frequencies[i]);
+	audio_signal_synthesize(signal);
+
+	alsa_register_output_callback(alsa, output_callback, signal,
+				      PLAYBACK_SAMPLES);
+
+	/* TODO: detect signal in real-time */
+	ret = alsa_run(alsa, AUDIO_DURATION);
+	igt_assert(ret == 0);
+
+	alsa_close_output(alsa);
+
+	/* Needs to be a multiple of 128, because that's the number of samples
+	 * we get per channel each time we receive an audio page from the
+	 * Chamelium device. */
+	channel_len = CAPTURE_SAMPLES;
+	channel = malloc(sizeof(double) * channel_len);
+
+	buf_cap = capture_channels * channel_len;
+	buf = malloc(sizeof(int32_t) * buf_cap);
+	buf_len = 0;
+
+	recv = NULL;
+	recv_len = 0;
+
+	streak = 0;
+	msec = 0;
+	i = 0;
+	while (streak < MIN_STREAK && msec < AUDIO_DURATION) {
+		ok = chamelium_stream_receive_realtime_audio(stream,
+							     &page_count,
+							     &recv, &recv_len);
+		igt_assert(ok);
+
+		memcpy(&buf[buf_len], recv, recv_len * sizeof(int32_t));
+		buf_len += recv_len;
+
+		if (buf_len < buf_cap)
+			continue;
+		igt_assert(buf_len == buf_cap);
+
+		if (dump_fd >= 0) {
+			buf_size = buf_len * sizeof(int32_t);
+			igt_assert(write(dump_fd, buf, buf_size) == buf_size);
+		}
+
+		/* TODO: check other channels too, not just the first one */
+		audio_extract_channel_s32_le(channel, channel_len, buf, buf_len,
+					     capture_channels, 0);
+
+		msec = i * channel_len / (double) capture_rate * 1000;
+		igt_debug("Detecting audio signal, t=%d msec\n", msec);
+
+		if (audio_signal_detect(signal, capture_rate, channel,
+					channel_len))
+			streak++;
+		else
+			streak = 0;
+
+		buf_len = 0;
+		i++;
+	}
+
+	if (dump_fd >= 0) {
+		close(dump_fd);
+		if (streak == MIN_STREAK) {
+			/* Test succeeded, no need to keep the captured data */
+			unlink(dump_path);
+		} else
+			igt_debug("Saved captured audio data to %s\n", dump_path);
+		free(dump_path);
+	}
+
+	free(recv);
+	free(buf);
+	free(channel);
+
+	ok = chamelium_stream_stop_realtime_audio(stream);
+	igt_assert(ok);
+
+	audio_file = chamelium_stop_capturing_audio(data->chamelium,
+						    port);
+	if (audio_file) {
+		igt_debug("Audio file saved on the Chamelium in %s\n",
+			  audio_file->path);
+		chamelium_destroy_audio_file(audio_file);
+	}
+
+	audio_signal_clean(signal);
+	free(signal);
+
+	chamelium_stream_deinit(stream);
+
+	igt_assert(streak == MIN_STREAK);
+	return true;
+}
+
+static void
+test_display_audio(data_t *data, struct chamelium_port *port,
+		   const char *audio_device)
+{
+	bool run = false;
+	struct alsa *alsa;
+	int ret;
+	igt_output_t *output;
+	igt_plane_t *primary;
+	struct igt_fb fb;
+	drmModeModeInfo *mode;
+	drmModeConnector *connector;
+	int fb_id, i;
+
+	igt_require(alsa_has_exclusive_access());
+
+	alsa = alsa_init();
+	igt_assert(alsa);
+
+	reset_state(data, port);
+
+	/* Use the default Chamelium EDID for this test, as the base IGT EDID
+	 * doesn't advertise audio support (see drm_detect_monitor_audio in
+	 * the kernel tree). */
+	output = prepare_output(data, port, false);
+	connector = chamelium_port_get_connector(data->chamelium, port, false);
+	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
+	igt_assert(primary);
+
+	igt_assert(connector->count_modes > 0);
+	mode = &connector->modes[0];
+
+	fb_id = igt_create_color_pattern_fb(data->drm_fd,
+					    mode->hdisplay, mode->vdisplay,
+					    DRM_FORMAT_XRGB8888,
+					    LOCAL_DRM_FORMAT_MOD_NONE,
+					    0, 0, 0, &fb);
+	igt_assert(fb_id > 0);
+
+	/* Enable the output because the receiver won't try to receive audio if
+	 * it doesn't receive video. */
+	enable_output(data, port, output, mode, &fb);
+
+	for (i = 0; i < sampling_rates_count; i++) {
+		ret = alsa_open_output(alsa, audio_device);
+		igt_assert(ret >= 0);
+
+		/* TODO: playback on all 8 available channels */
+		run |= do_test_display_audio(data, port, alsa,
+					     PLAYBACK_CHANNELS,
+					     sampling_rates[i]);
+
+		alsa_close_output(alsa);
+	}
+
+	/* Make sure we tested at least one frequency. */
+	igt_assert(run);
+
+	igt_remove_fb(data->drm_fd, &fb);
+
+	drmModeFreeConnector(connector);
+
+	free(alsa);
+}
+
+
 static void select_tiled_modifier(igt_plane_t *plane, uint32_t width,
 				  uint32_t height, uint32_t format,
 				  uint64_t *modifier)
@@ -1037,7 +1298,7 @@ static void test_display_planes_random(data_t *data,
 	reset_state(data, port);
 
 	/* Find the connector and pipe. */
-	output = prepare_output(data, port);
+	output = prepare_output(data, port, true);
 
 	mode = igt_output_get_mode(output);
 
@@ -1308,6 +1569,9 @@ igt_main
 
 		connector_subtest("dp-frame-dump", DisplayPort)
 			test_display_frame_dump(&data, port);
+
+		connector_subtest("dp-audio", DisplayPort)
+			test_display_audio(&data, port, "HDMI");
 	}
 
 	igt_subtest_group {
diff --git a/tests/meson.build b/tests/meson.build
index 5167a6cc..5d3eed82 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -238,20 +238,13 @@ if libdrm_nouveau.found()
 	test_deps += libdrm_nouveau
 endif
 
-if _build_chamelium and chamelium.found()
+if chamelium_found
 	test_progs += [
 		'kms_chamelium',
 	]
 	test_deps += chamelium
 endif
 
-if _build_audio and alsa.found() and gsl.found()
-	test_progs += [
-		'audio',
-	]
-	test_deps += alsa
-endif
-
 test_executables = []
 test_list = []
 
-- 
2.21.0

_______________________________________________
igt-dev mailing list
igt-dev@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/igt-dev

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [igt-dev] [PATCH i-g-t v4 2/5] tests/kms_chamelium: capture audio data in real-time
  2019-04-11 12:36 [igt-dev] [PATCH i-g-t v4 0/5] tests/kms_chamelium: add dp-audio test Simon Ser
  2019-04-11 12:36 ` [igt-dev] [PATCH i-g-t v4 1/5] " Simon Ser
@ 2019-04-11 12:36 ` Simon Ser
  2019-04-11 12:36 ` [igt-dev] [PATCH i-g-t v4 3/5] tests/kms_chamelium: test we receive a signal from both audio channels Simon Ser
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 13+ messages in thread
From: Simon Ser @ 2019-04-11 12:36 UTC (permalink / raw)
  To: igt-dev

Before this patch, the audio test first sends an audio signal for 2s, and then
checks whether the captured signal matches.

This patch makes it so we send and check the signal in parallel. Thus we can
stop the test as soon as we receive the correct signal. This saves ~2s per
audio test.

Signed-off-by: Simon Ser <simon.ser@intel.com>
---
 lib/igt_alsa.c        | 15 +++++++-------
 tests/kms_chamelium.c | 47 +++++++++++++++++++++++++++++++++----------
 2 files changed, 43 insertions(+), 19 deletions(-)

diff --git a/lib/igt_alsa.c b/lib/igt_alsa.c
index 456c0c85..cc34e3d1 100644
--- a/lib/igt_alsa.c
+++ b/lib/igt_alsa.c
@@ -497,7 +497,8 @@ void alsa_register_input_callback(struct alsa *alsa,
 /**
  * alsa_run:
  * @alsa: The target alsa structure
- * @duration_ms: The maximum duration of the run in milliseconds
+ * @duration_ms: The maximum duration of the run in milliseconds, or -1 for an
+ * infinite duration.
  *
  * Run ALSA playback and capture on the input and output devices for at
  * most @duration_ms milliseconds, calling the registered callbacks when needed.
@@ -545,7 +546,7 @@ int alsa_run(struct alsa *alsa, int duration_ms)
 	do {
 		reached = true;
 
-		if (output_total < output_limit) {
+		if (output_limit < 0 || output_total < output_limit) {
 			reached = false;
 
 			if (!output_ready) {
@@ -607,7 +608,8 @@ int alsa_run(struct alsa *alsa, int duration_ms)
 
 		}
 
-		if (alsa->input_callback && input_total < input_limit) {
+		if (alsa->input_callback &&
+		    (input_limit < 0 || input_total < input_limit)) {
 			reached = false;
 
 			if (input_count == input_trigger) {
@@ -660,11 +662,8 @@ int alsa_run(struct alsa *alsa, int duration_ms)
 	ret = 0;
 
 complete:
-	if (output_buffer)
-		free(output_buffer);
-
-	if (input_buffer)
-		free(input_buffer);
+	free(output_buffer);
+	free(input_buffer);
 
 	return ret;
 }
diff --git a/tests/kms_chamelium.c b/tests/kms_chamelium.c
index 2974ff69..44010534 100644
--- a/tests/kms_chamelium.c
+++ b/tests/kms_chamelium.c
@@ -29,7 +29,9 @@
 #include "igt_vc4.h"
 
 #include <fcntl.h>
+#include <pthread.h>
 #include <string.h>
+#include <stdatomic.h>
 
 typedef struct {
 	struct chamelium *chamelium;
@@ -719,7 +721,7 @@ test_display_frame_dump(data_t *data, struct chamelium_port *port)
 /* Capture paremeters control the audio signal we receive */
 #define CAPTURE_SAMPLES 2048
 
-#define AUDIO_DURATION 2000 /* ms */
+#define AUDIO_TIMEOUT 2000 /* ms */
 /* A streak of 3 gives confidence that the signal is good. */
 #define MIN_STREAK 3
 
@@ -746,14 +748,28 @@ static int test_frequencies[] = {
 
 static int test_frequencies_count = sizeof(test_frequencies) / sizeof(int);
 
+struct audio_state {
+	struct audio_signal *signal;
+	atomic_bool run;
+};
+
 static int
-output_callback(void *data, short *buffer, int frames)
+audio_output_callback(void *data, short *buffer, int frames)
 {
-	struct audio_signal *signal = (struct audio_signal *) data;
+	struct audio_state *state = data;
+
+	audio_signal_fill(state->signal, buffer, frames);
 
-	audio_signal_fill(signal, buffer, frames);
+	return state->run ? 0 : -1;
+}
+
+static void *
+run_audio_thread(void *data)
+{
+	struct alsa *alsa = data;
 
-	return 0;
+	alsa_run(alsa, -1);
+	return NULL;
 }
 
 static bool
@@ -774,6 +790,8 @@ do_test_display_audio(data_t *data, struct chamelium_port *port,
 	char dump_suffix[64];
 	char *dump_path = NULL;
 	int dump_fd = -1;
+	pthread_t thread;
+	struct audio_state state = {};
 
 	if (!alsa_test_output_configuration(alsa, playback_channels,
 					    playback_rate))
@@ -811,15 +829,15 @@ do_test_display_audio(data_t *data, struct chamelium_port *port,
 		audio_signal_add_frequency(signal, test_frequencies[i]);
 	audio_signal_synthesize(signal);
 
-	alsa_register_output_callback(alsa, output_callback, signal,
+	state.signal = signal;
+	state.run = true;
+	alsa_register_output_callback(alsa, audio_output_callback, &state,
 				      PLAYBACK_SAMPLES);
 
-	/* TODO: detect signal in real-time */
-	ret = alsa_run(alsa, AUDIO_DURATION);
+	/* Start playing audio */
+	ret = pthread_create(&thread, NULL, run_audio_thread, alsa);
 	igt_assert(ret == 0);
 
-	alsa_close_output(alsa);
-
 	/* Needs to be a multiple of 128, because that's the number of samples
 	 * we get per channel each time we receive an audio page from the
 	 * Chamelium device. */
@@ -836,7 +854,7 @@ do_test_display_audio(data_t *data, struct chamelium_port *port,
 	streak = 0;
 	msec = 0;
 	i = 0;
-	while (streak < MIN_STREAK && msec < AUDIO_DURATION) {
+	while (streak < MIN_STREAK && msec < AUDIO_TIMEOUT) {
 		ok = chamelium_stream_receive_realtime_audio(stream,
 							     &page_count,
 							     &recv, &recv_len);
@@ -871,6 +889,13 @@ do_test_display_audio(data_t *data, struct chamelium_port *port,
 		i++;
 	}
 
+	igt_debug("Stopping audio playback\n");
+	state.run = false;
+	ret = pthread_join(thread, NULL);
+	igt_assert(ret == 0);
+
+	alsa_close_output(alsa);
+
 	if (dump_fd >= 0) {
 		close(dump_fd);
 		if (streak == MIN_STREAK) {
-- 
2.21.0

_______________________________________________
igt-dev mailing list
igt-dev@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/igt-dev

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [igt-dev] [PATCH i-g-t v4 3/5] tests/kms_chamelium: test we receive a signal from both audio channels
  2019-04-11 12:36 [igt-dev] [PATCH i-g-t v4 0/5] tests/kms_chamelium: add dp-audio test Simon Ser
  2019-04-11 12:36 ` [igt-dev] [PATCH i-g-t v4 1/5] " Simon Ser
  2019-04-11 12:36 ` [igt-dev] [PATCH i-g-t v4 2/5] tests/kms_chamelium: capture audio data in real-time Simon Ser
@ 2019-04-11 12:36 ` Simon Ser
  2019-04-11 12:36 ` [igt-dev] [PATCH i-g-t v4 4/5] tests/kms_chamelium: test audio channels are not mixed up Simon Ser
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 13+ messages in thread
From: Simon Ser @ 2019-04-11 12:36 UTC (permalink / raw)
  To: igt-dev

This commit updates the audio test to make sure we receive a signal from both
audio channels. However this commit doesn't check that left and right channels
are not swapped. Such a check requires some more work (because the Chamelium
device does swap left and right channels) and will be implemented in a future
commit.

This commit adds a new channel argument to audio_signal_add_frequency, to add
a frequency to a single channel only.

Some light refactoring has been performed (a proper audio_signal_deinit
function has been introduced) and logging has been improved.

Signed-off-by: Simon Ser <simon.ser@intel.com>
---
 lib/igt_alsa.c        |  26 +++++++--
 lib/igt_audio.c       | 121 +++++++++++++++++++++++++-----------------
 lib/igt_audio.h       |  11 ++--
 tests/kms_chamelium.c |  56 +++++++++++--------
 4 files changed, 138 insertions(+), 76 deletions(-)

diff --git a/lib/igt_alsa.c b/lib/igt_alsa.c
index cc34e3d1..e1848301 100644
--- a/lib/igt_alsa.c
+++ b/lib/igt_alsa.c
@@ -182,6 +182,8 @@ static char *alsa_resolve_indentifier(const char *device_name, int skip)
 					continue;
 				}
 
+				igt_debug("Matched device \"%s\"\n", pcm_name);
+
 				snprintf(name, sizeof(name), "hw:%d,%d", card,
 					 dev);
 
@@ -329,6 +331,9 @@ static bool alsa_test_configuration(snd_pcm_t *handle, int channels,
 {
 	snd_pcm_hw_params_t *params;
 	int ret;
+	unsigned int min_channels, max_channels;
+	unsigned int min_rate, max_rate;
+	int min_rate_dir, max_rate_dir;
 
 	snd_pcm_hw_params_alloca(&params);
 
@@ -337,12 +342,24 @@ static bool alsa_test_configuration(snd_pcm_t *handle, int channels,
 		return false;
 
 	ret = snd_pcm_hw_params_test_rate(handle, params, sampling_rate, 0);
-	if (ret < 0)
+	if (ret < 0) {
+		snd_pcm_hw_params_get_rate_min(params, &min_rate, &min_rate_dir);
+		snd_pcm_hw_params_get_rate_max(params, &max_rate, &max_rate_dir);
+		igt_debug("Output device supports rates between %u and %u, "
+			  "requested %d\n",
+			  min_rate, max_rate, sampling_rate);
 		return false;
+	}
 
 	ret = snd_pcm_hw_params_test_channels(handle, params, channels);
-	if (ret < 0)
+	if (ret < 0) {
+		snd_pcm_hw_params_get_channels_min(params, &min_channels);
+		snd_pcm_hw_params_get_channels_max(params, &max_channels);
+		igt_debug("Output device supports between %u and "
+			  "%u channels, requested %d\n",
+			  min_channels, max_channels, channels);
 		return false;
+	}
 
 	return true;
 }
@@ -409,13 +426,16 @@ void alsa_configure_output(struct alsa *alsa, int channels,
 	snd_pcm_t *handle;
 	int ret;
 	int i;
+	int soft_resample = 0; /* Don't allow ALSA to resample */
+	unsigned int latency = 0;
 
 	for (i = 0; i < alsa->output_handles_count; i++) {
 		handle = alsa->output_handles[i];
 
 		ret = snd_pcm_set_params(handle, SND_PCM_FORMAT_S16_LE,
 					 SND_PCM_ACCESS_RW_INTERLEAVED,
-					 channels, sampling_rate, 0, 0);
+					 channels, sampling_rate,
+					 soft_resample, latency);
 		igt_assert(ret >= 0);
 	}
 
diff --git a/lib/igt_audio.c b/lib/igt_audio.c
index 4cc9bdf0..ba8152f3 100644
--- a/lib/igt_audio.c
+++ b/lib/igt_audio.c
@@ -35,7 +35,7 @@
 #include "igt_audio.h"
 #include "igt_core.h"
 
-#define FREQS_MAX	8
+#define FREQS_MAX 64
 
 /**
  * SECTION:igt_audio
@@ -49,9 +49,10 @@
 
 struct audio_signal_freq {
 	int freq;
+	int channel;
 
-	short *period;
-	int frames;
+	int16_t *period;
+	size_t period_len;
 	int offset;
 };
 
@@ -60,7 +61,7 @@ struct audio_signal {
 	int sampling_rate;
 
 	struct audio_signal_freq freqs[FREQS_MAX];
-	int freqs_count;
+	size_t freqs_count;
 };
 
 /**
@@ -89,21 +90,28 @@ struct audio_signal *audio_signal_init(int channels, int sampling_rate)
  * 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 audio_signal_add_frequency(struct audio_signal *signal, int frequency,
+			       int channel)
 {
-	int index = signal->freqs_count;
+	size_t index = signal->freqs_count;
+	struct audio_signal_freq *freq;
 
-	if (index == FREQS_MAX)
-		return -1;
+	igt_assert(index < FREQS_MAX);
+	igt_assert(channel < signal->channels);
 
 	/* Stay within the Nyquist–Shannon sampling theorem. */
-	if (frequency > signal->sampling_rate / 2)
+	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
@@ -111,11 +119,14 @@ int audio_signal_add_frequency(struct audio_signal *signal, int frequency)
 	 */
 	frequency = signal->sampling_rate / (signal->sampling_rate / frequency);
 
-	igt_debug("Adding test frequency %d\n", 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[index].freq = frequency;
-	signal->freqs[index].frames = 0;
-	signal->freqs[index].offset = 0;
 	signal->freqs_count++;
 
 	return 0;
@@ -133,20 +144,17 @@ void audio_signal_synthesize(struct audio_signal *signal)
 {
 	int16_t *period;
 	double value;
-	int frames;
+	size_t period_len;
 	int freq;
 	int i, j;
 
-	if (signal->freqs_count == 0)
-		return;
-
 	for (i = 0; i < signal->freqs_count; i++) {
 		freq = signal->freqs[i].freq;
-		frames = signal->sampling_rate / freq;
+		period_len = signal->sampling_rate / freq;
 
-		period = calloc(1, frames * sizeof(short));
+		period = calloc(1, period_len * sizeof(int16_t));
 
-		for (j = 0; j < frames; j++) {
+		for (j = 0; j < period_len; j++) {
 			value = 2.0 * M_PI * freq / signal->sampling_rate * j;
 			value = sin(value) * INT16_MAX / signal->freqs_count;
 
@@ -154,26 +162,34 @@ void audio_signal_synthesize(struct audio_signal *signal)
 		}
 
 		signal->freqs[i].period = period;
-		signal->freqs[i].frames = frames;
+		signal->freqs[i].period_len = period_len;
 	}
 }
 
 /**
- * audio_signal_synthesize:
+ * audio_signal_deinit:
+ *
+ * Release the signal.
+ */
+void audio_signal_deinit(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_clean(struct audio_signal *signal)
+void audio_signal_reset(struct audio_signal *signal)
 {
-	int i;
+	size_t i;
 
 	for (i = 0; i < signal->freqs_count; i++) {
-		if (signal->freqs[i].period)
-			free(signal->freqs[i].period);
-
-		memset(&signal->freqs[i], 0, sizeof(struct audio_signal_freq));
+		free(signal->freqs[i].period);
 	}
 
 	signal->freqs_count = 0;
@@ -183,44 +199,45 @@ void audio_signal_clean(struct audio_signal *signal)
  * audio_signal_fill:
  * @signal: The target signal structure
  * @buffer: The target buffer to fill
- * @frames: The number of frames to fill
+ * @samples: The number of samples to fill
  *
- * Fill the requested number of frames to the target buffer with the audio
+ * Fill the requested number of samples to the target buffer with the audio
  * signal data (in interleaved S16_LE format), at the requested sampling rate
  * and number of channels.
  */
-void audio_signal_fill(struct audio_signal *signal, int16_t *buffer, int frames)
+void audio_signal_fill(struct audio_signal *signal, int16_t *buffer,
+		       size_t buffer_len)
 {
 	int16_t *destination, *source;
+	struct audio_signal_freq *freq;
 	int total;
-	int freq_frames;
-	int freq_offset;
 	int count;
 	int i, j, k;
 
-	memset(buffer, 0, sizeof(int16_t) * signal->channels * frames);
+	memset(buffer, 0, sizeof(int16_t) * signal->channels * buffer_len);
 
 	for (i = 0; i < signal->freqs_count; i++) {
+		freq = &signal->freqs[i];
 		total = 0;
 
-		while (total < frames) {
-			freq_frames = signal->freqs[i].frames;
-			freq_offset = signal->freqs[i].offset;
+		igt_assert(freq->period);
 
-			source = signal->freqs[i].period + freq_offset;
+		while (total < buffer_len) {
+			source = freq->period + freq->offset;
 			destination = buffer + total * signal->channels;
 
-			count = freq_frames - freq_offset;
-			if (count > (frames - total))
-				count = frames - total;
+			count = freq->period_len - freq->offset;
+			if (count > buffer_len - total)
+				count = buffer_len - total;
 
-			freq_offset += count;
-			freq_offset %= freq_frames;
-
-			signal->freqs[i].offset = freq_offset;
+			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;
 					destination[j * signal->channels + k] += source[j];
 				}
 			}
@@ -237,11 +254,11 @@ void audio_signal_fill(struct audio_signal *signal, int16_t *buffer, int frames)
  * sampling_rate is given in Hz. data_len is the number of elements in data.
  */
 bool audio_signal_detect(struct audio_signal *signal, int sampling_rate,
-			 double *data, size_t data_len)
+			 int channel, double *data, size_t data_len)
 {
 	size_t amplitude_len = data_len / 2 + 1;
 	double amplitude[amplitude_len];
-	bool detected[signal->freqs_count];
+	bool detected[FREQS_MAX];
 	int ret, epsilon, freq, max_freq;
 	double max, threshold;
 	size_t i, j;
@@ -249,7 +266,7 @@ bool audio_signal_detect(struct audio_signal *signal, int sampling_rate,
 
 	/* Allowed error in Hz due to FFT step */
 	epsilon = sampling_rate / data_len;
-	igt_debug("allowed freq. error: %d Hz\n", epsilon);
+	igt_debug("Allowed freq. error: %d Hz\n", epsilon);
 
 	ret = gsl_fft_real_radix2_transform(data, 1, data_len);
 	igt_assert(ret == 0);
@@ -295,6 +312,10 @@ bool audio_signal_detect(struct audio_signal *signal, int sampling_rate,
 		 * invalid. */
 		if (amplitude[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 >
 				    max_freq - epsilon &&
 				    signal->freqs[j].freq <
@@ -327,6 +348,10 @@ bool audio_signal_detect(struct audio_signal *signal, int sampling_rate,
 
 	/* 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);
diff --git a/lib/igt_audio.h b/lib/igt_audio.h
index 4aa43e69..fe26bb57 100644
--- a/lib/igt_audio.h
+++ b/lib/igt_audio.h
@@ -35,12 +35,15 @@
 struct audio_signal;
 
 struct audio_signal *audio_signal_init(int channels, int sampling_rate);
-int audio_signal_add_frequency(struct audio_signal *signal, int frequency);
+void audio_signal_deinit(struct audio_signal *signal);
+int audio_signal_add_frequency(struct audio_signal *signal, int frequency,
+			       int channel);
 void audio_signal_synthesize(struct audio_signal *signal);
-void audio_signal_clean(struct audio_signal *signal);
-void audio_signal_fill(struct audio_signal *signal, int16_t *buffer, int frames);
+void audio_signal_reset(struct audio_signal *signal);
+void audio_signal_fill(struct audio_signal *signal, int16_t *buffer,
+		       size_t buffer_len);
 bool audio_signal_detect(struct audio_signal *signal, int sampling_rate,
-			 double *data, size_t data_len);
+			 int channel, double *data, size_t data_len);
 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);
diff --git a/tests/kms_chamelium.c b/tests/kms_chamelium.c
index 44010534..12ab9997 100644
--- a/tests/kms_chamelium.c
+++ b/tests/kms_chamelium.c
@@ -777,16 +777,16 @@ do_test_display_audio(data_t *data, struct chamelium_port *port,
 		      struct alsa *alsa, int playback_channels,
 		      int playback_rate)
 {
-	int ret, capture_rate, capture_channels, msec;
+	int ret, capture_rate, capture_channels, msec, freq;
 	struct chamelium_audio_file *audio_file;
 	struct chamelium_stream *stream;
 	enum chamelium_stream_realtime_mode stream_mode;
 	struct audio_signal *signal;
 	int32_t *recv, *buf;
 	double *channel;
-	size_t i, streak, page_count;
+	size_t i, j, streak, page_count;
 	size_t recv_len, buf_len, buf_cap, buf_size, channel_len;
-	bool ok;
+	bool ok, success;
 	char dump_suffix[64];
 	char *dump_path = NULL;
 	int dump_fd = -1;
@@ -794,10 +794,15 @@ do_test_display_audio(data_t *data, struct chamelium_port *port,
 	struct audio_state state = {};
 
 	if (!alsa_test_output_configuration(alsa, playback_channels,
-					    playback_rate))
+					    playback_rate)) {
+		igt_debug("Skipping test with sample rate %d and %d channels "
+			  "because selected output devices don't support this "
+			  "configuration\n", playback_rate, playback_channels);
 		return false;
+	}
 
-	igt_debug("Testing with playback sampling rate %d\n", playback_rate);
+	igt_debug("Testing with playback sampling rate %d and %d channels\n",
+		  playback_rate, playback_channels);
 	alsa_configure_output(alsa, playback_channels, playback_rate);
 
 	chamelium_start_capturing_audio(data->chamelium, port, false);
@@ -825,8 +830,12 @@ do_test_display_audio(data_t *data, struct chamelium_port *port,
 	signal = audio_signal_init(playback_channels, playback_rate);
 	igt_assert(signal);
 
-	for (i = 0; i < test_frequencies_count; i++)
-		audio_signal_add_frequency(signal, test_frequencies[i]);
+	for (i = 0; i < test_frequencies_count; i++) {
+		for (j = 0; j < playback_channels; j++) {
+			freq = test_frequencies[i];
+			audio_signal_add_frequency(signal, freq, j);
+		}
+	}
 	audio_signal_synthesize(signal);
 
 	state.signal = signal;
@@ -851,10 +860,11 @@ do_test_display_audio(data_t *data, struct chamelium_port *port,
 	recv = NULL;
 	recv_len = 0;
 
+	success = false;
 	streak = 0;
 	msec = 0;
 	i = 0;
-	while (streak < MIN_STREAK && msec < AUDIO_TIMEOUT) {
+	while (!success && msec < AUDIO_TIMEOUT) {
 		ok = chamelium_stream_receive_realtime_audio(stream,
 							     &page_count,
 							     &recv, &recv_len);
@@ -872,21 +882,27 @@ do_test_display_audio(data_t *data, struct chamelium_port *port,
 			igt_assert(write(dump_fd, buf, buf_size) == buf_size);
 		}
 
-		/* TODO: check other channels too, not just the first one */
-		audio_extract_channel_s32_le(channel, channel_len, buf, buf_len,
-					     capture_channels, 0);
-
 		msec = i * channel_len / (double) capture_rate * 1000;
 		igt_debug("Detecting audio signal, t=%d msec\n", msec);
 
-		if (audio_signal_detect(signal, capture_rate, channel,
-					channel_len))
-			streak++;
-		else
-			streak = 0;
+		for (j = 0; j < playback_channels; j++) {
+			igt_debug("Processing channel %zu\n", j);
+
+			audio_extract_channel_s32_le(channel, channel_len,
+						     buf, buf_len,
+						     capture_channels, j);
+
+			if (audio_signal_detect(signal, capture_rate, j,
+						channel, channel_len))
+				streak++;
+			else
+				streak = 0;
+		}
 
 		buf_len = 0;
 		i++;
+
+		success = streak == MIN_STREAK * playback_channels;
 	}
 
 	igt_debug("Stopping audio playback\n");
@@ -921,12 +937,10 @@ do_test_display_audio(data_t *data, struct chamelium_port *port,
 		chamelium_destroy_audio_file(audio_file);
 	}
 
-	audio_signal_clean(signal);
-	free(signal);
-
+	audio_signal_deinit(signal);
 	chamelium_stream_deinit(stream);
 
-	igt_assert(streak == MIN_STREAK);
+	igt_assert(success);
 	return true;
 }
 
-- 
2.21.0

_______________________________________________
igt-dev mailing list
igt-dev@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/igt-dev

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [igt-dev] [PATCH i-g-t v4 4/5] tests/kms_chamelium: test audio channels are not mixed up
  2019-04-11 12:36 [igt-dev] [PATCH i-g-t v4 0/5] tests/kms_chamelium: add dp-audio test Simon Ser
                   ` (2 preceding siblings ...)
  2019-04-11 12:36 ` [igt-dev] [PATCH i-g-t v4 3/5] tests/kms_chamelium: test we receive a signal from both audio channels Simon Ser
@ 2019-04-11 12:36 ` Simon Ser
  2019-04-11 12:36 ` [igt-dev] [PATCH i-g-t v4 5/5] tests/kms_chamelium: run audio test with multiple sampling rates Simon Ser
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 13+ messages in thread
From: Simon Ser @ 2019-04-11 12:36 UTC (permalink / raw)
  To: igt-dev

Send a different signal to each channel and make sure captured audio channels
are not swapped or mixed up.

The Chamelium device has a bug and already swaps the left and right channels.
For this reason, clients need to retrieve the Chamelium channel mapping and
accomodate for this. See https://crbug.com/950922 for a discussion about this.

Signed-off-by: Simon Ser <simon.ser@intel.com>
---
 lib/igt_chamelium.c   | 34 ++++++++++++++++++++++++++++++++++
 lib/igt_chamelium.h   |  3 +++
 tests/kms_chamelium.c | 37 +++++++++++++++++++++++++++++++------
 3 files changed, 68 insertions(+), 6 deletions(-)

diff --git a/lib/igt_chamelium.c b/lib/igt_chamelium.c
index 7c9030d1..6ac7b722 100644
--- a/lib/igt_chamelium.c
+++ b/lib/igt_chamelium.c
@@ -930,6 +930,40 @@ int chamelium_get_captured_frame_count(struct chamelium *chamelium)
 	return ret;
 }
 
+/**
+ * chamelium_get_audio_channel_mapping:
+ * @chamelium: the Chamelium instance
+ * @port: the audio port
+ * @mapping: will be filled with the channel mapping
+ *
+ * Obtains the channel mapping for an audio port.
+ *
+ * Audio channels are not guaranteed not to be swapped. Users can use the
+ * channel mapping to match an input channel to a capture channel.
+ *
+ * The mapping contains one element per capture channel. Each element indicates
+ * which input channel the capture channel is mapped to. As a special case, -1
+ * means that the channel isn't mapped.
+ */
+void chamelium_get_audio_channel_mapping(struct chamelium *chamelium,
+					 struct chamelium_port *port,
+					 int mapping[static 8])
+{
+	xmlrpc_value *res, *res_channel;
+	int res_len, i;
+
+	res = chamelium_rpc(chamelium, port, "GetAudioChannelMapping", "(i)",
+			    port->id);
+	res_len = xmlrpc_array_size(&chamelium->env, res);
+	igt_assert(res_len == 8);
+	for (i = 0; i < res_len; i++) {
+		xmlrpc_array_read_item(&chamelium->env, res, i, &res_channel);
+		xmlrpc_read_int(&chamelium->env, res_channel, &mapping[i]);
+		xmlrpc_DECREF(res_channel);
+	}
+	xmlrpc_DECREF(res);
+}
+
 /**
  * chamelium_start_capturing_audio:
  * @chamelium: the Chamelium instance
diff --git a/lib/igt_chamelium.h b/lib/igt_chamelium.h
index 047f8c5d..728d16ea 100644
--- a/lib/igt_chamelium.h
+++ b/lib/igt_chamelium.h
@@ -106,6 +106,9 @@ void chamelium_start_capture(struct chamelium *chamelium,
 void chamelium_stop_capture(struct chamelium *chamelium, int frame_count);
 void chamelium_capture(struct chamelium *chamelium, struct chamelium_port *port,
 		       int x, int y, int w, int h, int frame_count);
+void chamelium_get_audio_channel_mapping(struct chamelium *chamelium,
+					 struct chamelium_port *port,
+					 int mapping[static 8]);
 void chamelium_start_capturing_audio(struct chamelium *chamelium,
 				    struct chamelium_port *port, bool save_to_file);
 struct chamelium_audio_file *chamelium_stop_capturing_audio(struct chamelium *chamelium,
diff --git a/tests/kms_chamelium.c b/tests/kms_chamelium.c
index 12ab9997..3e4f7473 100644
--- a/tests/kms_chamelium.c
+++ b/tests/kms_chamelium.c
@@ -777,7 +777,7 @@ do_test_display_audio(data_t *data, struct chamelium_port *port,
 		      struct alsa *alsa, int playback_channels,
 		      int playback_rate)
 {
-	int ret, capture_rate, capture_channels, msec, freq;
+	int ret, capture_rate, capture_channels, msec, freq, step;
 	struct chamelium_audio_file *audio_file;
 	struct chamelium_stream *stream;
 	enum chamelium_stream_realtime_mode stream_mode;
@@ -792,16 +792,17 @@ do_test_display_audio(data_t *data, struct chamelium_port *port,
 	int dump_fd = -1;
 	pthread_t thread;
 	struct audio_state state = {};
+	int channel_mapping[8], capture_chan;
 
 	if (!alsa_test_output_configuration(alsa, playback_channels,
 					    playback_rate)) {
-		igt_debug("Skipping test with sample rate %d and %d channels "
+		igt_debug("Skipping test with sample rate %d Hz and %d channels "
 			  "because selected output devices don't support this "
 			  "configuration\n", playback_rate, playback_channels);
 		return false;
 	}
 
-	igt_debug("Testing with playback sampling rate %d and %d channels\n",
+	igt_debug("Testing with playback sampling rate %d Hz and %d channels\n",
 		  playback_rate, playback_channels);
 	alsa_configure_output(alsa, playback_channels, playback_rate);
 
@@ -830,9 +831,15 @@ do_test_display_audio(data_t *data, struct chamelium_port *port,
 	signal = audio_signal_init(playback_channels, playback_rate);
 	igt_assert(signal);
 
+	/* We'll choose different frequencies per channel to make sure they are
+	 * independent from each other. To do so, we'll add a different offset
+	 * to the base frequencies for each channel. We need to choose a big
+	 * enough offset so that we're sure to detect mixed up channels.
+	 */
+	step = 2 * capture_rate / CAPTURE_SAMPLES;
 	for (i = 0; i < test_frequencies_count; i++) {
 		for (j = 0; j < playback_channels; j++) {
-			freq = test_frequencies[i];
+			freq = test_frequencies[i] + j * step;
 			audio_signal_add_frequency(signal, freq, j);
 		}
 	}
@@ -847,6 +854,20 @@ do_test_display_audio(data_t *data, struct chamelium_port *port,
 	ret = pthread_create(&thread, NULL, run_audio_thread, alsa);
 	igt_assert(ret == 0);
 
+	chamelium_get_audio_channel_mapping(data->chamelium, port,
+					    channel_mapping);
+	/* Make sure we can capture all channels we send. */
+	for (i = 0; i < playback_channels; i++) {
+		ok = false;
+		for (j = 0; j < capture_channels; j++) {
+			if (channel_mapping[j] == i) {
+				ok = true;
+				break;
+			}
+		}
+		igt_assert(ok);
+	}
+
 	/* Needs to be a multiple of 128, because that's the number of samples
 	 * we get per channel each time we receive an audio page from the
 	 * Chamelium device. */
@@ -886,11 +907,15 @@ do_test_display_audio(data_t *data, struct chamelium_port *port,
 		igt_debug("Detecting audio signal, t=%d msec\n", msec);
 
 		for (j = 0; j < playback_channels; j++) {
-			igt_debug("Processing channel %zu\n", j);
+			capture_chan = channel_mapping[j];
+			igt_assert(capture_chan >= 0);
+			igt_debug("Processing channel %zu (captured as "
+				  "channel %d)\n", j, capture_chan);
 
 			audio_extract_channel_s32_le(channel, channel_len,
 						     buf, buf_len,
-						     capture_channels, j);
+						     capture_channels,
+						     capture_chan);
 
 			if (audio_signal_detect(signal, capture_rate, j,
 						channel, channel_len))
-- 
2.21.0

_______________________________________________
igt-dev mailing list
igt-dev@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/igt-dev

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [igt-dev] [PATCH i-g-t v4 5/5] tests/kms_chamelium: run audio test with multiple sampling rates
  2019-04-11 12:36 [igt-dev] [PATCH i-g-t v4 0/5] tests/kms_chamelium: add dp-audio test Simon Ser
                   ` (3 preceding siblings ...)
  2019-04-11 12:36 ` [igt-dev] [PATCH i-g-t v4 4/5] tests/kms_chamelium: test audio channels are not mixed up Simon Ser
@ 2019-04-11 12:36 ` Simon Ser
  2019-04-11 16:43 ` [igt-dev] ✓ Fi.CI.BAT: success for tests/kms_chamelium: add dp-audio test (rev4) Patchwork
  2019-04-11 21:28 ` [igt-dev] ✓ Fi.CI.IGT: " Patchwork
  6 siblings, 0 replies; 13+ messages in thread
From: Simon Ser @ 2019-04-11 12:36 UTC (permalink / raw)
  To: igt-dev

The audio test is now run multiple times with a variety of playback sampling
rates.

We now query the capture audio format from the Chamelium XML-RPC API instead of
hardcoding it.

One limitation is that we need to start sendting an audio signal before being
able to query the capture audio format. However we need the capture sample rate
to decide which frequencies we generate. For now we use the playback rate and
check that it's the same as the capture rate.

Another limitation is that the DP receiver reports an unknown sampling rate
during the 41.1KHz test. In this case we assume the capture rate is the same as
the playback rate. We'll fail later anyway if this assumption is incorrect
since we check the signal we receive.

Chameleon bug: https://crbug.com/950913

Signed-off-by: Simon Ser <simon.ser@intel.com>
---
 lib/igt_chamelium.c        | 80 +++++++++++++++++++++++++-------------
 lib/igt_chamelium.h        |  3 ++
 lib/igt_chamelium_stream.c | 17 --------
 lib/igt_chamelium_stream.h |  2 -
 tests/kms_chamelium.c      | 56 +++++++++++++++-----------
 5 files changed, 90 insertions(+), 68 deletions(-)

diff --git a/lib/igt_chamelium.c b/lib/igt_chamelium.c
index 6ac7b722..ffc68f35 100644
--- a/lib/igt_chamelium.c
+++ b/lib/igt_chamelium.c
@@ -964,31 +964,6 @@ void chamelium_get_audio_channel_mapping(struct chamelium *chamelium,
 	xmlrpc_DECREF(res);
 }
 
-/**
- * chamelium_start_capturing_audio:
- * @chamelium: the Chamelium instance
- * @port: the port to capture audio from (it must support audio)
- * @save_to_file: whether the captured audio data should be saved to a file on
- * the Chamelium device
- *
- * Starts capturing audio from a Chamelium port. To stop the capture, use
- * #chamelium_stop_capturing_audio. To retrieve the audio data, either use the
- * stream server or enable @save_to_file (the latter is mainly useful for
- * debugging purposes).
- *
- * It isn't possible to capture audio from multiple ports at the same time.
- */
-void chamelium_start_capturing_audio(struct chamelium *chamelium,
-				    struct chamelium_port *port,
-				    bool save_to_file)
-{
-	xmlrpc_value *res;
-
-	res = chamelium_rpc(chamelium, port, "StartCapturingAudio", "(ib)",
-			    port->id, save_to_file);
-	xmlrpc_DECREF(res);
-}
-
 static void audio_format_from_xml(struct chamelium *chamelium,
 				  xmlrpc_value *res, int *rate, int *channels)
 {
@@ -1008,8 +983,10 @@ static void audio_format_from_xml(struct chamelium *chamelium,
 	igt_assert(strcmp(sample_format, "S32_LE") == 0);
 	free(sample_format);
 
-	xmlrpc_read_int(&chamelium->env, res_rate, rate);
-	xmlrpc_read_int(&chamelium->env, res_channel, channels);
+	if (rate)
+		xmlrpc_read_int(&chamelium->env, res_rate, rate);
+	if (channels)
+		xmlrpc_read_int(&chamelium->env, res_channel, channels);
 
 	xmlrpc_DECREF(res_channel);
 	xmlrpc_DECREF(res_sample_format);
@@ -1017,6 +994,55 @@ static void audio_format_from_xml(struct chamelium *chamelium,
 	xmlrpc_DECREF(res_type);
 }
 
+/**
+ * chamelium_get_audio_format:
+ * @chamelium: the Chamelium instance
+ * @port: the audio port
+ * @rate: if non-NULL, will be set to the sample rate in Hz
+ * @channels: if non-NULL, will be set to the number of channels
+ *
+ * Obtains the audio format of the captured data. Users should start sending an
+ * audio signal to the Chamelium device prior to calling this function.
+ *
+ * The captured data is guaranteed to be in the S32_LE format.
+ */
+void chamelium_get_audio_format(struct chamelium *chamelium,
+				struct chamelium_port *port,
+				int *rate, int *channels)
+{
+	xmlrpc_value *res;
+
+	res = chamelium_rpc(chamelium, port, "GetAudioFormat", "(i)",
+			    port->id);
+	audio_format_from_xml(chamelium, res, rate, channels);
+	xmlrpc_DECREF(res);
+}
+
+/**
+ * chamelium_start_capturing_audio:
+ * @chamelium: the Chamelium instance
+ * @port: the port to capture audio from (it must support audio)
+ * @save_to_file: whether the captured audio data should be saved to a file on
+ * the Chamelium device
+ *
+ * Starts capturing audio from a Chamelium port. To stop the capture, use
+ * #chamelium_stop_capturing_audio. To retrieve the audio data, either use the
+ * stream server or enable @save_to_file (the latter is mainly useful for
+ * debugging purposes).
+ *
+ * It isn't possible to capture audio from multiple ports at the same time.
+ */
+void chamelium_start_capturing_audio(struct chamelium *chamelium,
+				    struct chamelium_port *port,
+				    bool save_to_file)
+{
+	xmlrpc_value *res;
+
+	res = chamelium_rpc(chamelium, port, "StartCapturingAudio", "(ib)",
+			    port->id, save_to_file);
+	xmlrpc_DECREF(res);
+}
+
 /**
  * chamelium_stop_capturing_audio:
  * @chamelium: the Chamelium instance
diff --git a/lib/igt_chamelium.h b/lib/igt_chamelium.h
index 728d16ea..f47b84cb 100644
--- a/lib/igt_chamelium.h
+++ b/lib/igt_chamelium.h
@@ -109,6 +109,9 @@ void chamelium_capture(struct chamelium *chamelium, struct chamelium_port *port,
 void chamelium_get_audio_channel_mapping(struct chamelium *chamelium,
 					 struct chamelium_port *port,
 					 int mapping[static 8]);
+void chamelium_get_audio_format(struct chamelium *chamelium,
+				struct chamelium_port *port,
+				int *rate, int *channels);
 void chamelium_start_capturing_audio(struct chamelium *chamelium,
 				    struct chamelium_port *port, bool save_to_file);
 struct chamelium_audio_file *chamelium_stop_capturing_audio(struct chamelium *chamelium,
diff --git a/lib/igt_chamelium_stream.c b/lib/igt_chamelium_stream.c
index 9e1ba1ca..a7b09c08 100644
--- a/lib/igt_chamelium_stream.c
+++ b/lib/igt_chamelium_stream.c
@@ -540,23 +540,6 @@ bool chamelium_stream_stop_realtime_audio(struct chamelium_stream *client)
 	return true;
 }
 
-/**
- * chamelium_stream_audio_format:
- *
- * Gets the format used for audio pages.
- *
- * Data will always be captured in raw pages of S32_LE elements. This function
- * exposes the sampling rate and the number of channels.
- */
-void chamelium_stream_audio_format(struct chamelium_stream *stream,
-				   int *rate, int *channels)
-{
-	/* TODO: the Chamelium streaming server doesn't expose those yet.
-	 * Just hardcode the values for now. */
-	*rate = 48000;
-	*channels = 8;
-}
-
 /**
  * chamelium_stream_init:
  *
diff --git a/lib/igt_chamelium_stream.h b/lib/igt_chamelium_stream.h
index de4e9931..3e1c5d14 100644
--- a/lib/igt_chamelium_stream.h
+++ b/lib/igt_chamelium_stream.h
@@ -42,8 +42,6 @@ struct chamelium_stream *chamelium_stream_init(void);
 void chamelium_stream_deinit(struct chamelium_stream *client);
 bool chamelium_stream_dump_realtime_audio(struct chamelium_stream *client,
 					  enum chamelium_stream_realtime_mode mode);
-void chamelium_stream_audio_format(struct chamelium_stream *stream,
-				   int *rate, int *channels);
 bool chamelium_stream_receive_realtime_audio(struct chamelium_stream *client,
 					     size_t *page_count,
 					     int32_t **buf, size_t *buf_len);
diff --git a/tests/kms_chamelium.c b/tests/kms_chamelium.c
index 3e4f7473..a40c56eb 100644
--- a/tests/kms_chamelium.c
+++ b/tests/kms_chamelium.c
@@ -725,15 +725,14 @@ test_display_frame_dump(data_t *data, struct chamelium_port *port)
 /* A streak of 3 gives confidence that the signal is good. */
 #define MIN_STREAK 3
 
-/* TODO: Chamelium only supports 48KHz for now */
 static int sampling_rates[] = {
-/*	32000, */
-/*	44100, */
+	32000,
+	44100,
 	48000,
-/*	88200, */
-/*	96000, */
-/*	176400, */
-/*	192000, */
+	88200,
+	96000,
+	176400,
+	192000,
 };
 
 static int sampling_rates_count = sizeof(sampling_rates) / sizeof(int);
@@ -815,19 +814,6 @@ do_test_display_audio(data_t *data, struct chamelium_port *port,
 	ok = chamelium_stream_dump_realtime_audio(stream, stream_mode);
 	igt_assert(ok);
 
-	chamelium_stream_audio_format(stream, &capture_rate, &capture_channels);
-
-	if (igt_frame_dump_is_enabled()) {
-		snprintf(dump_suffix, sizeof(dump_suffix), "capture-%dch-%d",
-			 playback_channels, playback_rate);
-
-		dump_fd = audio_create_wav_file_s32_le(dump_suffix,
-						       capture_rate,
-						       capture_channels,
-						       &dump_path);
-		igt_assert(dump_fd >= 0);
-	}
-
 	signal = audio_signal_init(playback_channels, playback_rate);
 	igt_assert(signal);
 
@@ -835,8 +821,12 @@ do_test_display_audio(data_t *data, struct chamelium_port *port,
 	 * independent from each other. To do so, we'll add a different offset
 	 * to the base frequencies for each channel. We need to choose a big
 	 * enough offset so that we're sure to detect mixed up channels.
+	 *
+	 * Note that we assume capture_rate == playback_rate. We'll assert this
+	 * later on. We cannot retrieve the capture rate before starting
+	 * playing audio, so we don't really have the choice.
 	 */
-	step = 2 * capture_rate / CAPTURE_SAMPLES;
+	step = 2 * playback_rate / CAPTURE_SAMPLES;
 	for (i = 0; i < test_frequencies_count; i++) {
 		for (j = 0; j < playback_channels; j++) {
 			freq = test_frequencies[i] + j * step;
@@ -854,6 +844,17 @@ do_test_display_audio(data_t *data, struct chamelium_port *port,
 	ret = pthread_create(&thread, NULL, run_audio_thread, alsa);
 	igt_assert(ret == 0);
 
+	/* Only after we've started playing audio, we can retrieve the capture
+	 * format used by the Chamelium device. */
+	chamelium_get_audio_format(data->chamelium, port,
+				   &capture_rate, &capture_channels);
+	if (capture_rate == 0) {
+		igt_debug("Audio receiver doesn't indicate the capture "
+			 "sampling rate, assuming it's %d Hz\n", playback_rate);
+		capture_rate = playback_rate;
+	} else
+		igt_assert(capture_rate == playback_rate);
+
 	chamelium_get_audio_channel_mapping(data->chamelium, port,
 					    channel_mapping);
 	/* Make sure we can capture all channels we send. */
@@ -868,6 +869,17 @@ do_test_display_audio(data_t *data, struct chamelium_port *port,
 		igt_assert(ok);
 	}
 
+	if (igt_frame_dump_is_enabled()) {
+		snprintf(dump_suffix, sizeof(dump_suffix), "capture-%dch-%d",
+			 playback_channels, playback_rate);
+
+		dump_fd = audio_create_wav_file_s32_le(dump_suffix,
+						       capture_rate,
+						       capture_channels,
+						       &dump_path);
+		igt_assert(dump_fd >= 0);
+	}
+
 	/* Needs to be a multiple of 128, because that's the number of samples
 	 * we get per channel each time we receive an audio page from the
 	 * Chamelium device. */
@@ -939,7 +951,7 @@ do_test_display_audio(data_t *data, struct chamelium_port *port,
 
 	if (dump_fd >= 0) {
 		close(dump_fd);
-		if (streak == MIN_STREAK) {
+		if (success) {
 			/* Test succeeded, no need to keep the captured data */
 			unlink(dump_path);
 		} else
-- 
2.21.0

_______________________________________________
igt-dev mailing list
igt-dev@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/igt-dev

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [igt-dev] ✓ Fi.CI.BAT: success for tests/kms_chamelium: add dp-audio test (rev4)
  2019-04-11 12:36 [igt-dev] [PATCH i-g-t v4 0/5] tests/kms_chamelium: add dp-audio test Simon Ser
                   ` (4 preceding siblings ...)
  2019-04-11 12:36 ` [igt-dev] [PATCH i-g-t v4 5/5] tests/kms_chamelium: run audio test with multiple sampling rates Simon Ser
@ 2019-04-11 16:43 ` Patchwork
  2019-04-11 21:28 ` [igt-dev] ✓ Fi.CI.IGT: " Patchwork
  6 siblings, 0 replies; 13+ messages in thread
From: Patchwork @ 2019-04-11 16:43 UTC (permalink / raw)
  To: Simon Ser; +Cc: igt-dev

== Series Details ==

Series: tests/kms_chamelium: add dp-audio test (rev4)
URL   : https://patchwork.freedesktop.org/series/59058/
State : success

== Summary ==

CI Bug Log - changes from CI_DRM_5911 -> IGTPW_2847
====================================================

Summary
-------

  **SUCCESS**

  No regressions found.

  External URL: https://patchwork.freedesktop.org/api/1.0/series/59058/revisions/4/mbox/

Known issues
------------

  Here are the changes found in IGTPW_2847 that come from known issues:

### IGT changes ###

#### Issues hit ####

  * igt@amdgpu/amd_basic@semaphore:
    - fi-kbl-7500u:       NOTRUN -> SKIP [fdo#109271] +26

  * igt@gem_exec_suspend@basic-s4-devices:
    - fi-blb-e6850:       PASS -> INCOMPLETE [fdo#107718]

  * igt@i915_selftest@live_execlists:
    - fi-apl-guc:         PASS -> INCOMPLETE [fdo#103927] / [fdo#109720]

  * igt@runner@aborted:
    - fi-apl-guc:         NOTRUN -> FAIL [fdo#108622] / [fdo#109720]

  
#### Possible fixes ####

  * igt@i915_selftest@live_hangcheck:
    - fi-bxt-dsi:         INCOMPLETE [fdo#103927] -> PASS

  * igt@kms_frontbuffer_tracking@basic:
    - fi-byt-clapper:     FAIL [fdo#103167] -> PASS

  * igt@kms_pipe_crc_basic@read-crc-pipe-a:
    - fi-byt-clapper:     FAIL [fdo#103191] -> PASS

  
  [fdo#103167]: https://bugs.freedesktop.org/show_bug.cgi?id=103167
  [fdo#103191]: https://bugs.freedesktop.org/show_bug.cgi?id=103191
  [fdo#103927]: https://bugs.freedesktop.org/show_bug.cgi?id=103927
  [fdo#107718]: https://bugs.freedesktop.org/show_bug.cgi?id=107718
  [fdo#108622]: https://bugs.freedesktop.org/show_bug.cgi?id=108622
  [fdo#109271]: https://bugs.freedesktop.org/show_bug.cgi?id=109271
  [fdo#109720]: https://bugs.freedesktop.org/show_bug.cgi?id=109720


Participating hosts (51 -> 45)
------------------------------

  Missing    (6): fi-ilk-m540 fi-hsw-4200u fi-byt-squawks fi-bsw-cyan fi-ctg-p8600 fi-bdw-samus 


Build changes
-------------

    * IGT: IGT_4943 -> IGTPW_2847

  CI_DRM_5911: b11c9dc17f8b821e3f1ffc3df17f401c12a22669 @ git://anongit.freedesktop.org/gfx-ci/linux
  IGTPW_2847: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_2847/
  IGT_4943: 5941f371b0fe25084d4b1c49882faa8d41d44c9f @ git://anongit.freedesktop.org/xorg/app/intel-gpu-tools



== Testlist changes ==

-igt@kms_chamelium@common-hpd-after-hibernate
-igt@kms_chamelium@common-hpd-after-suspend
-igt@kms_chamelium@dp-crc-fast
-igt@kms_chamelium@dp-crc-multiple
-igt@kms_chamelium@dp-crc-single
-igt@kms_chamelium@dp-edid-change-during-hibernate
-igt@kms_chamelium@dp-edid-change-during-suspend
-igt@kms_chamelium@dp-edid-read
-igt@kms_chamelium@dp-frame-dump
-igt@kms_chamelium@dp-hpd
-igt@kms_chamelium@dp-hpd-after-hibernate
-igt@kms_chamelium@dp-hpd-after-suspend
-igt@kms_chamelium@dp-hpd-fast
-igt@kms_chamelium@dp-hpd-storm
-igt@kms_chamelium@dp-hpd-storm-disable
-igt@kms_chamelium@hdmi-cmp-nv12
-igt@kms_chamelium@hdmi-cmp-nv16
-igt@kms_chamelium@hdmi-cmp-nv21
-igt@kms_chamelium@hdmi-cmp-nv61
-igt@kms_chamelium@hdmi-cmp-planes-random
-igt@kms_chamelium@hdmi-cmp-yu12
-igt@kms_chamelium@hdmi-cmp-yu16
-igt@kms_chamelium@hdmi-cmp-yv12
-igt@kms_chamelium@hdmi-cmp-yv16
-igt@kms_chamelium@hdmi-crc-abgr8888
-igt@kms_chamelium@hdmi-crc-argb1555
-igt@kms_chamelium@hdmi-crc-argb8888
-igt@kms_chamelium@hdmi-crc-bgr565
-igt@kms_chamelium@hdmi-crc-bgr888
-igt@kms_chamelium@hdmi-crc-fast
-igt@kms_chamelium@hdmi-crc-multiple
-igt@kms_chamelium@hdmi-crc-planes-random
-igt@kms_chamelium@hdmi-crc-rgb565
-igt@kms_chamelium@hdmi-crc-rgb888
-igt@kms_chamelium@hdmi-crc-single
-igt@kms_chamelium@hdmi-crc-xbgr8888
-igt@kms_chamelium@hdmi-crc-xrgb1555
-igt@kms_chamelium@hdmi-crc-xrgb8888
-igt@kms_chamelium@hdmi-edid-change-during-hibernate
-igt@kms_chamelium@hdmi-edid-change-during-suspend
-igt@kms_chamelium@hdmi-edid-read
-igt@kms_chamelium@hdmi-frame-dump
-igt@kms_chamelium@hdmi-hpd
-igt@kms_chamelium@hdmi-hpd-after-hibernate
-igt@kms_chamelium@hdmi-hpd-after-suspend
-igt@kms_chamelium@hdmi-hpd-fast
-igt@kms_chamelium@hdmi-hpd-storm
-igt@kms_chamelium@hdmi-hpd-storm-disable
-igt@kms_chamelium@vga-edid-read
-igt@kms_chamelium@vga-frame-dump
-igt@kms_chamelium@vga-hpd
-igt@kms_chamelium@vga-hpd-after-hibernate
-igt@kms_chamelium@vga-hpd-after-suspend
-igt@kms_chamelium@vga-hpd-fast
-igt@kms_chamelium@vga-hpd-without-ddc

== Logs ==

For more details see: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_2847/
_______________________________________________
igt-dev mailing list
igt-dev@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/igt-dev

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [igt-dev] ✓ Fi.CI.IGT: success for tests/kms_chamelium: add dp-audio test (rev4)
  2019-04-11 12:36 [igt-dev] [PATCH i-g-t v4 0/5] tests/kms_chamelium: add dp-audio test Simon Ser
                   ` (5 preceding siblings ...)
  2019-04-11 16:43 ` [igt-dev] ✓ Fi.CI.BAT: success for tests/kms_chamelium: add dp-audio test (rev4) Patchwork
@ 2019-04-11 21:28 ` Patchwork
  6 siblings, 0 replies; 13+ messages in thread
From: Patchwork @ 2019-04-11 21:28 UTC (permalink / raw)
  To: Simon Ser; +Cc: igt-dev

== Series Details ==

Series: tests/kms_chamelium: add dp-audio test (rev4)
URL   : https://patchwork.freedesktop.org/series/59058/
State : success

== Summary ==

CI Bug Log - changes from CI_DRM_5911_full -> IGTPW_2847_full
====================================================

Summary
-------

  **SUCCESS**

  No regressions found.

  External URL: https://patchwork.freedesktop.org/api/1.0/series/59058/revisions/4/mbox/

Known issues
------------

  Here are the changes found in IGTPW_2847_full that come from known issues:

### IGT changes ###

#### Issues hit ####

  * igt@gem_bad_reloc@negative-reloc-bsd2:
    - shard-iclb:         NOTRUN -> SKIP [fdo#109276] +8

  * igt@gem_exec_create@madvise:
    - shard-apl:          PASS -> DMESG-WARN [fdo#110376]

  * igt@gem_mocs_settings@mocs-rc6-render:
    - shard-snb:          NOTRUN -> SKIP [fdo#109271] +34

  * igt@gem_mocs_settings@mocs-reset-dirty-render:
    - shard-iclb:         NOTRUN -> SKIP [fdo#110206]

  * igt@gem_pwrite@huge-gtt-random:
    - shard-iclb:         NOTRUN -> SKIP [fdo#109290] +1

  * igt@gem_stolen@stolen-pread:
    - shard-iclb:         NOTRUN -> SKIP [fdo#109277] +1

  * igt@i915_pm_lpsp@screens-disabled:
    - shard-iclb:         NOTRUN -> SKIP [fdo#109301]

  * igt@i915_pm_rc6_residency@rc6-accuracy:
    - shard-snb:          PASS -> SKIP [fdo#109271]

  * igt@kms_atomic_transition@1x-modeset-transitions-nonblocking:
    - shard-apl:          PASS -> FAIL [fdo#109660]
    - shard-kbl:          PASS -> FAIL [fdo#109660]

  * igt@kms_atomic_transition@5x-modeset-transitions-nonblocking-fencing:
    - shard-hsw:          NOTRUN -> SKIP [fdo#109271] / [fdo#109278] +2

  * igt@kms_atomic_transition@6x-modeset-transitions-nonblocking:
    - shard-apl:          NOTRUN -> SKIP [fdo#109271] / [fdo#109278] +7

  * igt@kms_atomic_transition@6x-modeset-transitions-nonblocking-fencing:
    - shard-glk:          NOTRUN -> SKIP [fdo#109271] / [fdo#109278] +1

  * igt@kms_available_modes_crc@available_mode_test_crc:
    - shard-iclb:         NOTRUN -> FAIL [fdo#106641]

  * igt@kms_busy@extended-modeset-hang-oldfb-render-d:
    - shard-kbl:          NOTRUN -> SKIP [fdo#109271] / [fdo#109278]

  * igt@kms_busy@extended-pageflip-hang-oldfb-render-e:
    - shard-iclb:         NOTRUN -> SKIP [fdo#109278] +1

  * igt@kms_busy@extended-pageflip-modeset-hang-oldfb-render-c:
    - shard-snb:          NOTRUN -> SKIP [fdo#109271] / [fdo#109278] +3

  * igt@kms_ccs@pipe-c-missing-ccs-buffer:
    - shard-apl:          NOTRUN -> SKIP [fdo#109271] +90

  * igt@kms_content_protection@legacy:
    - shard-apl:          NOTRUN -> FAIL [fdo#110321] / [fdo#110336]

  * igt@kms_cursor_crc@cursor-256x256-suspend:
    - shard-kbl:          PASS -> DMESG-WARN [fdo#108566] +4

  * igt@kms_cursor_crc@cursor-512x170-random:
    - shard-iclb:         NOTRUN -> SKIP [fdo#109279] +1

  * igt@kms_cursor_crc@cursor-64x21-onscreen:
    - shard-kbl:          PASS -> FAIL [fdo#103232]
    - shard-apl:          PASS -> FAIL [fdo#103232]

  * igt@kms_cursor_legacy@cursora-vs-flipb-varying-size:
    - shard-iclb:         NOTRUN -> SKIP [fdo#109274] +2

  * igt@kms_dp_dsc@basic-dsc-enable-edp:
    - shard-iclb:         PASS -> SKIP [fdo#109349]

  * igt@kms_fbcon_fbt@psr:
    - shard-iclb:         NOTRUN -> FAIL [fdo#103833]

  * igt@kms_frontbuffer_tracking@fbc-1p-primscrn-pri-indfb-draw-pwrite:
    - shard-iclb:         NOTRUN -> FAIL [fdo#103167]

  * igt@kms_frontbuffer_tracking@fbc-1p-rte:
    - shard-iclb:         PASS -> FAIL [fdo#103167] / [fdo#110378]

  * igt@kms_frontbuffer_tracking@fbcpsr-1p-offscren-pri-indfb-draw-blt:
    - shard-iclb:         PASS -> FAIL [fdo#109247] +11

  * igt@kms_frontbuffer_tracking@fbcpsr-1p-primscrn-cur-indfb-draw-render:
    - shard-iclb:         PASS -> FAIL [fdo#103167] +6

  * igt@kms_frontbuffer_tracking@fbcpsr-2p-primscrn-pri-shrfb-draw-pwrite:
    - shard-iclb:         NOTRUN -> SKIP [fdo#109280] +8

  * igt@kms_lease@atomic_implicit_crtc:
    - shard-snb:          NOTRUN -> FAIL [fdo#110279]

  * igt@kms_lease@setcrtc_implicit_plane:
    - shard-apl:          NOTRUN -> FAIL [fdo#110281]

  * igt@kms_pipe_crc_basic@suspend-read-crc-pipe-a:
    - shard-apl:          PASS -> DMESG-WARN [fdo#108566] +1

  * igt@kms_plane@pixel-format-pipe-c-planes:
    - shard-glk:          PASS -> SKIP [fdo#109271]

  * igt@kms_plane_alpha_blend@pipe-a-alpha-opaque-fb:
    - shard-glk:          NOTRUN -> FAIL [fdo#108145]

  * igt@kms_plane_alpha_blend@pipe-b-alpha-basic:
    - shard-apl:          NOTRUN -> FAIL [fdo#108145] +2

  * igt@kms_plane_alpha_blend@pipe-c-alpha-opaque-fb:
    - shard-kbl:          NOTRUN -> FAIL [fdo#108145]

  * igt@kms_plane_lowres@pipe-a-tiling-y:
    - shard-iclb:         PASS -> FAIL [fdo#103166]

  * igt@kms_plane_lowres@pipe-a-tiling-yf:
    - shard-hsw:          NOTRUN -> SKIP [fdo#109271] +16

  * igt@kms_plane_scaling@pipe-a-scaler-with-pixel-format:
    - shard-glk:          PASS -> SKIP [fdo#109271] / [fdo#109278]

  * igt@kms_psr@cursor_plane_onoff:
    - shard-kbl:          NOTRUN -> SKIP [fdo#109271] +20

  * igt@kms_psr@psr2_dpms:
    - shard-iclb:         NOTRUN -> SKIP [fdo#109441]

  * igt@kms_psr@psr2_sprite_plane_move:
    - shard-iclb:         PASS -> SKIP [fdo#109441] +3

  * igt@kms_psr@sprite_blt:
    - shard-iclb:         PASS -> FAIL [fdo#107383] / [fdo#110215] +1

  * igt@kms_sysfs_edid_timing:
    - shard-iclb:         PASS -> FAIL [fdo#100047]

  * igt@prime_nv_test@nv_write_i915_cpu_mmap_read:
    - shard-iclb:         NOTRUN -> SKIP [fdo#109291]

  * igt@prime_vgem@sync-bsd1:
    - shard-glk:          NOTRUN -> SKIP [fdo#109271] +10

  * igt@v3d_get_bo_offset@create-get-offsets:
    - shard-iclb:         NOTRUN -> SKIP [fdo#109315]

  
#### Possible fixes ####

  * igt@gem_eio@in-flight-suspend:
    - shard-apl:          DMESG-WARN [fdo#108566] -> PASS +3

  * igt@gem_ppgtt@blt-vs-render-ctxn:
    - shard-hsw:          INCOMPLETE [fdo#103540] -> PASS

  * igt@i915_selftest@live_workarounds:
    - shard-iclb:         DMESG-FAIL [fdo#108954] -> PASS

  * igt@kms_frontbuffer_tracking@fbcpsr-1p-offscren-pri-indfb-draw-pwrite:
    - shard-iclb:         FAIL [fdo#109247] -> PASS +13

  * igt@kms_frontbuffer_tracking@fbcpsr-1p-pri-indfb-multidraw:
    - shard-iclb:         FAIL [fdo#103167] -> PASS +4

  * igt@kms_plane@pixel-format-pipe-c-planes-source-clamping:
    - shard-glk:          SKIP [fdo#109271] -> PASS

  * igt@kms_plane_lowres@pipe-a-tiling-x:
    - shard-iclb:         FAIL [fdo#103166] -> PASS

  * igt@kms_psr@cursor_mmap_gtt:
    - shard-iclb:         FAIL [fdo#107383] / [fdo#110215] -> PASS +2

  * igt@kms_psr@psr2_sprite_mmap_gtt:
    - shard-iclb:         SKIP [fdo#109441] -> PASS +2

  * igt@kms_rotation_crc@primary-yf-tiled-reflect-x-0:
    - shard-iclb:         INCOMPLETE [fdo#110026] / [fdo#110040 ] -> PASS

  * igt@kms_setmode@basic:
    - shard-apl:          FAIL [fdo#99912] -> PASS

  * igt@kms_universal_plane@universal-plane-pipe-b-functional:
    - shard-glk:          FAIL [fdo#110037] -> PASS
    - shard-kbl:          FAIL [fdo#110037] -> PASS

  
#### Warnings ####

  * igt@gem_tiled_swapping@non-threaded:
    - shard-iclb:         FAIL [fdo#108686] -> DMESG-WARN [fdo#108686]

  * igt@i915_selftest@live_contexts:
    - shard-iclb:         INCOMPLETE [fdo#108569] -> DMESG-FAIL [fdo#108569]

  
  [fdo#100047]: https://bugs.freedesktop.org/show_bug.cgi?id=100047
  [fdo#103166]: https://bugs.freedesktop.org/show_bug.cgi?id=103166
  [fdo#103167]: https://bugs.freedesktop.org/show_bug.cgi?id=103167
  [fdo#103232]: https://bugs.freedesktop.org/show_bug.cgi?id=103232
  [fdo#103540]: https://bugs.freedesktop.org/show_bug.cgi?id=103540
  [fdo#103833]: https://bugs.freedesktop.org/show_bug.cgi?id=103833
  [fdo#106641]: https://bugs.freedesktop.org/show_bug.cgi?id=106641
  [fdo#107383]: https://bugs.freedesktop.org/show_bug.cgi?id=107383
  [fdo#108145]: https://bugs.freedesktop.org/show_bug.cgi?id=108145
  [fdo#108566]: https://bugs.freedesktop.org/show_bug.cgi?id=108566
  [fdo#108569]: https://bugs.freedesktop.org/show_bug.cgi?id=108569
  [fdo#108686]: https://bugs.freedesktop.org/show_bug.cgi?id=108686
  [fdo#108954]: https://bugs.freedesktop.org/show_bug.cgi?id=108954
  [fdo#109247]: https://bugs.freedesktop.org/show_bug.cgi?id=109247
  [fdo#109271]: https://bugs.freedesktop.org/show_bug.cgi?id=109271
  [fdo#109274]: https://bugs.freedesktop.org/show_bug.cgi?id=109274
  [fdo#109276]: https://bugs.freedesktop.org/show_bug.cgi?id=109276
  [fdo#109277]: https://bugs.freedesktop.org/show_bug.cgi?id=109277
  [fdo#109278]: https://bugs.freedesktop.org/show_bug.cgi?id=109278
  [fdo#109279]: https://bugs.freedesktop.org/show_bug.cgi?id=109279
  [fdo#109280]: https://bugs.freedesktop.org/show_bug.cgi?id=109280
  [fdo#109290]: https://bugs.freedesktop.org/show_bug.cgi?id=109290
  [fdo#109291]: https://bugs.freedesktop.org/show_bug.cgi?id=109291
  [fdo#109301]: https://bugs.freedesktop.org/show_bug.cgi?id=109301
  [fdo#109315]: https://bugs.freedesktop.org/show_bug.cgi?id=109315
  [fdo#109349]: https://bugs.freedesktop.org/show_bug.cgi?id=109349
  [fdo#109441]: https://bugs.freedesktop.org/show_bug.cgi?id=109441
  [fdo#109660]: https://bugs.freedesktop.org/show_bug.cgi?id=109660
  [fdo#110026]: https://bugs.freedesktop.org/show_bug.cgi?id=110026
  [fdo#110037]: https://bugs.freedesktop.org/show_bug.cgi?id=110037
  [fdo#110040 ]: https://bugs.freedesktop.org/show_bug.cgi?id=110040 
  [fdo#110206]: https://bugs.freedesktop.org/show_bug.cgi?id=110206
  [fdo#110215]: https://bugs.freedesktop.org/show_bug.cgi?id=110215
  [fdo#110279]: https://bugs.freedesktop.org/show_bug.cgi?id=110279
  [fdo#110281]: https://bugs.freedesktop.org/show_bug.cgi?id=110281
  [fdo#110321]: https://bugs.freedesktop.org/show_bug.cgi?id=110321
  [fdo#110336]: https://bugs.freedesktop.org/show_bug.cgi?id=110336
  [fdo#110376]: https://bugs.freedesktop.org/show_bug.cgi?id=110376
  [fdo#110378]: https://bugs.freedesktop.org/show_bug.cgi?id=110378
  [fdo#99912]: https://bugs.freedesktop.org/show_bug.cgi?id=99912


Participating hosts (10 -> 6)
------------------------------

  Missing    (4): pig-skl-6260u shard-skl pig-hsw-4770r pig-glk-j5005 


Build changes
-------------

    * IGT: IGT_4943 -> IGTPW_2847
    * Piglit: piglit_4509 -> None

  CI_DRM_5911: b11c9dc17f8b821e3f1ffc3df17f401c12a22669 @ git://anongit.freedesktop.org/gfx-ci/linux
  IGTPW_2847: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_2847/
  IGT_4943: 5941f371b0fe25084d4b1c49882faa8d41d44c9f @ git://anongit.freedesktop.org/xorg/app/intel-gpu-tools
  piglit_4509: fdc5a4ca11124ab8413c7988896eec4c97336694 @ git://anongit.freedesktop.org/piglit

== Logs ==

For more details see: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_2847/
_______________________________________________
igt-dev mailing list
igt-dev@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/igt-dev

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [igt-dev] [PATCH i-g-t v4 1/5] tests/kms_chamelium: add dp-audio test
  2019-04-11 12:36 ` [igt-dev] [PATCH i-g-t v4 1/5] " Simon Ser
@ 2019-04-16 12:02   ` Martin Peres
  2019-04-17  8:40     ` Ser, Simon
  0 siblings, 1 reply; 13+ messages in thread
From: Martin Peres @ 2019-04-16 12:02 UTC (permalink / raw)
  To: Simon Ser, igt-dev

On 11/04/2019 15:36, Simon Ser wrote:
> 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 depends on the following Chameleon bugs:
> 
> - https://crbug.com/948060
> - https://crbug.com/950857

Seems like your fixes landed already! Great!

> 
> Signed-off-by: Simon Ser <simon.ser@intel.com>
> ---
>  docs/audio.txt             |  45 ---
>  docs/chamelium.txt         |  32 +-
>  lib/igt.h                  |   1 +
>  lib/igt_alsa.c             |  42 ++-
>  lib/igt_alsa.h             |   1 +
>  lib/igt_audio.c            | 285 ++++++++++++------
>  lib/igt_audio.h            |  12 +-
>  lib/igt_aux.c              |  31 ++
>  lib/igt_aux.h              |   1 +
>  lib/igt_chamelium.c        | 101 +++++++
>  lib/igt_chamelium.h        |  11 +
>  lib/igt_chamelium_stream.c | 592 +++++++++++++++++++++++++++++++++++++
>  lib/igt_chamelium_stream.h |  52 ++++
>  lib/meson.build            |   5 +-
>  meson.build                |  52 ++--
>  meson_options.txt          |   6 -
>  tests/audio.c              | 193 ------------
>  tests/kms_chamelium.c      | 276 ++++++++++++++++-
>  tests/meson.build          |   9 +-
>  19 files changed, 1358 insertions(+), 389 deletions(-)
>  delete mode 100644 docs/audio.txt
>  create mode 100644 lib/igt_chamelium_stream.c
>  create mode 100644 lib/igt_chamelium_stream.h
>  delete mode 100644 tests/audio.c
> 
> diff --git a/docs/audio.txt b/docs/audio.txt
> deleted file mode 100644
> index 158ad5d1..00000000
> --- a/docs/audio.txt
> +++ /dev/null
> @@ -1,45 +0,0 @@
> -Audio Support in IGT
> -====================
> -
> -This document provides information and instructions about audio support in IGT.
> -
> -Introduction
> -------------
> -
> -The audio test is aimed at testing the audio features of display connectors,
> -such as HDMI.
> -
> -Test setup
> -----------
> -
> -The setup required for the audio test consists of using an HDMI-VGA adapter with
> -an audio-out 3.5 mm jack to extract the audio from the HDMI interface.
> -The audio-out jack is connected back to the device-under-test's line-in.
> -
> -Depending on the behavior of the adapter, it may be necessary to connect a
> -ghost VGA dongle to it (in order to emulate a connected display) to enable the
> -audio output. There are guides available detailing how to build these.
> -
> -When executed, the test will automatically send the test audio signal to all
> -ALSA audio HDMI outputs and record from the standard ALSA capture device.
> -
> -Configuration
> --------------
> -
> -In order to deploy the test, ALSA controls have to be configured to set the
> -ALSA capture source to line-in. On Intel x86 systems, this can be achieved
> -with the following calls to the amixer utility:
> -# amixer sset Line 31 on
> -# amixer sset "Input Source" Line
> -
> -It is then useful to store the ALSA state permanently with the alsactl utility:
> -# alsactl store
> -
> -These settings can be restored with the alsactl utility:
> -# alsactl restore
> -
> -It is desirable to ensure that the alsa-restore and alsa-state systemd services
> -are enabled to do this job automatically, especially in the case of an
> -automated testing system:
> -# systemctl enable alsa-restore
> -# systemctl enable alsa-state
> diff --git a/docs/chamelium.txt b/docs/chamelium.txt
> index 0cabcdc6..316dd607 100644
> --- a/docs/chamelium.txt
> +++ b/docs/chamelium.txt
> @@ -139,6 +139,23 @@ $ make remote-install CHAMELEON_HOST=192.168.72.1
>  
>  The process requires the Chamelium to be connected to the Internet to succeed.
>  
> +Audio Capture
> +-------------
> +
> +The Chamelium supports audio capture. IGT tests take advantage of the
> +Chamelium streaming server to download audio samples from the Chamelium.
> +
> +IGT needs direct access to audio devices through ALSA, so PulseAudio needs to
> +be stopped (otherwise audio tests will automatically get skipped). To make sure
> +PulseAudio isn't running:
> +
> +- Edit /etc/pulse/client.conf and add autospawn=no
> +- Run `pulseaudio --kill` (if it succeeds, it means PulseAudio was running)
> +- Make sure a DE that automatically spawns PulseAudio isn't running
> +
> +In case a test fails, the raw captured audio files will be dumped in a WAV
> +file.
> +
>  Contributing Changes to the Daemon
>  ----------------------------------
>  
> @@ -146,10 +163,11 @@ Contributions to the Chamelium daemon, just like any contribution to ChromiumOS,
>  are submitted and reviewed at: https://chromium-review.googlesource.com/
>  
>  The ChromiumOS project provides an extensive developer guide:
> -https://www.chromium.org/chromium-os/developer-guide that assumes running within
> -the ChromiumOS build system. Since this is likely not the case for contributing
> -to the Chamelium daemon, only the part about uploading changes is relevant:
> -https://www.chromium.org/chromium-os/developer-guide#TOC-Upload-your-changes-and-get-a-code-review
> +https://chromium.googlesource.com/chromiumos/docs/+/master/developer_guide.md
> +It that assumes running within the ChromiumOS build system. Since this is

The word "that" seems out of place.

> +likely not the case for contributing to the Chamelium daemon, only the part
> +about uploading changes is relevant:
> +https://chromium.googlesource.com/chromiumos/docs/+/master/developer_guide.md#Upload-your-changes-and-get-a-code-review
>  
>  Most of the process is about using the Gerrit web interface for submitting and
>  having the change reviewed and not forgetting the Change-Id, TEST= and BUG=
> @@ -162,7 +180,7 @@ Support for the Chamelium platform in IGT is found in the following places:
>  * lib/igt_chamelium.c: library with Chamelium-related helpers
>  * tests/kms_chamelium.c: sub-tests using the Chamelium
>  
> -As of late August 2017, the following features are tested by IGT:
> +As of early April 2019, the following features are tested by IGT:
>  * Pixel-by-pixel frame integrity tests for DP and HDMI
>  * Error-trend-based frame integrity tests for VGA
>  * CRC-based frame integrity tests for DP and HDMI
> @@ -173,6 +191,7 @@ As of late August 2017, the following features are tested by IGT:
>    each interface or combined
>  * EDID display identifier integrity check for all interfaces
>  * EDID display identifier change during suspend for all interfaces
> +* Audio Fourier-based tests for DP at 48KHz

Maybe an extra patch will be needed to update the list of
capabilities... or we just implement them :p

>  
>  Future Developments
>  -------------------
> @@ -180,7 +199,8 @@ Future Developments
>  With the current generation of the hardware platform, support for testing a
>  number of additional display features could be included as future developments,
>  including:
> -* Audio capture from HDMI and DP
> +* Audio capture from HDMI, with multiple channels and with other playback
> +  sampling rates

Don't remove DP since it does not yet have the features you are talking
about.

>  * High-bandwidth Digital Content Protection (HDCP) streaming to the display
>  * Remote control forwarding (CEC) sent from the display
>  * YUV colorspace for HDMI, instead of RGB
> diff --git a/lib/igt.h b/lib/igt.h
> index 6654a659..5852d557 100644
> --- a/lib/igt.h
> +++ b/lib/igt.h
> @@ -43,6 +43,7 @@
>  #include "igt_stats.h"
>  #ifdef HAVE_CHAMELIUM
>  #include "igt_chamelium.h"
> +#include "igt_chamelium_stream.h"
>  #endif
>  #include "instdone.h"
>  #include "intel_batchbuffer.h"
> diff --git a/lib/igt_alsa.c b/lib/igt_alsa.c
> index bb6682cc..456c0c85 100644
> --- a/lib/igt_alsa.c
> +++ b/lib/igt_alsa.c
> @@ -26,9 +26,11 @@
>  
>  #include "config.h"
>  
> +#include <limits.h>
>  #include <alsa/asoundlib.h>
>  
>  #include "igt_alsa.h"
> +#include "igt_aux.h"
>  #include "igt_core.h"
>  
>  #define HANDLES_MAX	8
> @@ -61,6 +63,25 @@ struct alsa {
>  	int input_samples_trigger;
>  };
>  
> +/**
> + * alsa_has_exclusive_access:
> + * Check whether ALSA has exclusive access to audio devices. Fails if
> + * PulseAudio is running.
> + */
> +bool alsa_has_exclusive_access(void)
> +{
> +	if (igt_is_process_running("pulseaudio")) {
> +		igt_warn("It seems that PulseAudio is running. Audio tests "
> +			 "need direct access to audio devices, so PulseAudio "
> +			 "needs to be stopped. You can do so by running "
> +			 "`pulseaudio --kill`. Also make sure to add "
> +			 "autospawn=no to /etc/pulse/client.conf\n");
> +		return false;
> +	}
> +
> +	return true;
> +}
> +
>  static void alsa_error_handler(const char *file, int line, const char *function,
>  			       int err, const char *fmt, ...)
>  {
> @@ -78,6 +99,11 @@ struct alsa *alsa_init(void)
>  {
>  	struct alsa *alsa;
>  
> +	if (!alsa_has_exclusive_access()) {
> +		igt_warn("alsa doesn't have exclusive access to audio devices\n");

How about folding the second warn in alsa_has_exclusive_access()? It
would improve the chances of having a consistent string to grep on in
cibuglog in case other users decide to use the function.

> +		return NULL;
> +	}
> +
>  	alsa = malloc(sizeof(struct alsa));
>  	memset(alsa, 0, sizeof(struct alsa));
>  
> @@ -553,16 +579,20 @@ int alsa_run(struct alsa *alsa, int duration_ms)
>  					if (ret < 0) {
>  						ret = snd_pcm_recover(handle,
>  								      ret, 0);
> -						if (ret < 0)
> +						if (ret < 0) {
> +							igt_debug("snd_pcm_recover after snd_pcm_writei failed");
>  							goto complete;
> +						}
>  					}
>  
>  					output_counts[i] += ret;
>  				} else if (output_counts[i] < output_trigger &&
>  					   ret < 0) {
>  					ret = snd_pcm_recover(handle, ret, 0);
> -					if (ret < 0)
> +					if (ret < 0) {
> +						igt_debug("snd_pcm_recover failed");
>  						goto complete;
> +					}
>  				}
>  			}
>  
> @@ -609,16 +639,20 @@ int alsa_run(struct alsa *alsa, int duration_ms)
>  					ret = 0;
>  				} else if (ret < 0) {
>  					ret = snd_pcm_recover(handle, ret, 0);
> -					if (ret < 0)
> +					if (ret < 0) {
> +						igt_debug("snd_pcm_recover after snd_pcm_readi failed");
>  						goto complete;
> +					}
>  				}
>  
>  				input_count += ret;
>  				input_total += ret;
>  			} else if (input_count < input_trigger && ret < 0) {
>  				ret = snd_pcm_recover(handle, ret, 0);
> -				if (ret < 0)
> +				if (ret < 0) {
> +					igt_debug("snd_pcm_recover failed");
>  					goto complete;
> +				}
>  			}
>  		}
>  	} while (!reached);
> diff --git a/lib/igt_alsa.h b/lib/igt_alsa.h
> index 50795130..5c804b46 100644
> --- a/lib/igt_alsa.h
> +++ b/lib/igt_alsa.h
> @@ -33,6 +33,7 @@
>  
>  struct alsa;
>  
> +bool alsa_has_exclusive_access(void);
>  struct alsa *alsa_init(void);
>  int alsa_open_output(struct alsa *alsa, const char *device_name);
>  int alsa_open_input(struct alsa *alsa, const char *device_name);
> diff --git a/lib/igt_audio.c b/lib/igt_audio.c
> index a0592d53..4cc9bdf0 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;

Seems like all these alsa changes should be in their own patches so as
you could explain why these changes are needed.

Are you afraid that some platforms would not have SHORT == INT16?

If these changes would have required changes in code that you would
change anyway right after, then at least explain the changes in the
commit log :)

>  		}
>  
>  		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,214 @@ 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 amplitude_len = data_len / 2 + 1;
> +	double amplitude[amplitude_len];

bin_power?

>  	bool detected[signal->freqs_count];
> -	int threshold;
> -	bool above;
> -	int error;
> -	int freq = 0;
> -	int max;
> -	int c, i, j;
> -
> -	/* Allowed error in Hz due to FFT step. */
> -	error = sampling_rate / frames;
> +	int ret, epsilon, freq, max_freq;
> +	double max, threshold;
> +	size_t i, j;
> +	bool above, success;
> +
> +	/* Allowed error in Hz due to FFT step */
> +	epsilon = sampling_rate / data_len;

freq_accuracy would be a nicer name.

> +	igt_debug("allowed freq. error: %d Hz\n", epsilon);
> +
> +	ret = gsl_fft_real_radix2_transform(data, 1, data_len);
> +	igt_assert(ret == 0);
> +
> +	/* 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 amplitude is encoded as the magnitude of the complex number and
> +	 * the phase is encoded as its angle.
> +	 */

Thanks for documenting the idiosyncrasies of GSL!

/* compute the power received at every bin of the FFT, and record the
 * maximum power received as a way to normalize all the others.
 */

> +	max = 0;
> +	amplitude[0] = data[0];
> +	for (i = 1; i < amplitude_len - 1; i++) {
> +		amplitude[i] = hypot(data[i], data[data_len - i]);
> +		if (amplitude[i] > max)
> +			max = amplitude[i];
> +	}
> +	amplitude[amplitude_len - 1] = data[data_len / 2];

What's the coding style of IGT wrt spaces vs tabs?

>  
> -	for (c = 0; c < channels; c++) {
> -		for (i = 0; i < frames; i++)
> -			data[i] = (double) buffer[i * channels + c];
> +	for (i = 0; i < signal->freqs_count; i++)
> +		detected[i] = false;

This could have been a bitfield, but meh!

>  
> -		gsl_fft_real_radix2_transform(data, 1, frames);
> +	/* We want to detect peaks above a given threshold. */

/* 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;
> +	max = 0;
> +	max_freq = -1;

local_max = 0;
local_max_freq = -1;

> +	for (i = 0; i < amplitude_len; i++) {
> +		freq = sampling_rate * i / data_len;
>  
> -		max = 0;
> +		if (amplitude[i] > threshold)
> +			above = true;
>  
> -		for (i = 0; i < frames / 2; i++) {
> -			amplitude[i] = hypot(data[i], data[frames - i]);
> -			if (amplitude[i] > max)
> -				max = amplitude[i];
> +		if (!above) {
> +			continue;
>  		}
>  
> -		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;
> -					}
> +		/* 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 (amplitude[i] < threshold) {
> +			for (j = 0; j < signal->freqs_count; j++) {
> +				if (signal->freqs[j].freq >
> +				    max_freq - epsilon &&
> +				    signal->freqs[j].freq <
> +				    max_freq + epsilon) {
> +					detected[j] = true;
> +					igt_debug("Frequency %d detected\n",
> +						  max_freq);
> +					break;
>  				}
> +			}
>  
> -				if (amplitude[i] > max) {
> -					max = amplitude[i];
> -					freq = sampling_rate * i / frames;
> -				}
> +			/* We haven't generated this frequency, but we detected
> +			 * it. */
> +			if (j == signal->freqs_count) {
> +				igt_debug("Detected additional frequency: %d\n",
> +					  max_freq);
> +				success = false;
>  			}
> +
> +			above = false;
> +			max = 0;
> +			max_freq = -1;
>  		}
>  
> -		for (i = 0; i < signal->freqs_count; i++) {
> -			if (!detected[i]) {
> -				igt_debug("Missing frequency: %d\n",
> -					  signal->freqs[i].freq);
> -				return false;
> -			}
> +		if (amplitude[i] > max) {
> +			max = amplitude[i];
> +			max_freq = freq;
> +		}
> +	}
> +
> +	/* 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;
>  		}
>  	}
>  
> -	return true;
> +	return success;
> +}
> +
> +/**
> + * 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;
> +
> +	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;
> +}
> +
> +/**
> + * Creates a new WAV file. sample_rate is in Hz. If path is not NULL, it will
> + * be set to the new file path (the caller is responsible for free-ing it).
> + *
> + * 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.
> + */
> +int audio_create_wav_file_s32_le(const char *qualifier, uint32_t sample_rate,
> +				 uint16_t channels, char **path)

What is qualifier?

Maybe we can rename that to channel_count?

> +{
> +	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 fd;
>  }
> diff --git a/lib/igt_audio.h b/lib/igt_audio.h
> index b3b658a4..4aa43e69 100644
> --- a/lib/igt_audio.h
> +++ b/lib/igt_audio.h
> @@ -30,6 +30,7 @@
>  #include "config.h"
>  
>  #include <stdbool.h>
> +#include <stdint.h>
>  
>  struct audio_signal;
>  
> @@ -37,8 +38,13 @@ struct audio_signal *audio_signal_init(int channels, int sampling_rate);
>  int audio_signal_add_frequency(struct audio_signal *signal, int frequency);
>  void audio_signal_synthesize(struct audio_signal *signal);
>  void audio_signal_clean(struct audio_signal *signal);
> -void audio_signal_fill(struct audio_signal *signal, short *buffer, int frames);
> -bool audio_signal_detect(struct audio_signal *signal, int channels,
> -			 int sampling_rate, short *buffer, int frames);
> +void audio_signal_fill(struct audio_signal *signal, int16_t *buffer, int frames);
> +bool audio_signal_detect(struct audio_signal *signal, int sampling_rate,
> +			 double *data, size_t data_len);
> +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);
> +int audio_create_wav_file_s32_le(const char *qualifier, uint32_t sample_rate,
> +				 uint16_t channels, char **path);
>  
>  #endif
> diff --git a/lib/igt_aux.c b/lib/igt_aux.c
> index 05528352..95ceb845 100644
> --- a/lib/igt_aux.c
> +++ b/lib/igt_aux.c
> @@ -1259,6 +1259,37 @@ void igt_set_module_param_int(const char *name, int val)
>  	igt_set_module_param(name, str);
>  }
>  
> +/**
> + * igt_is_process_running:
> + * @comm: Name of process in the form found in /proc/pid/comm (limited to 15
> + * chars)
> + *
> + * Returns: true in case the process has been found, false otherwise.
> + *
> + * This function checks in the process table for an entry with the name @comm.
> + */
> +int igt_is_process_running(const char *comm)
> +{
> +	PROCTAB *proc;
> +	proc_t *proc_info;
> +	bool found = false;
> +
> +	proc = openproc(PROC_FILLCOM | PROC_FILLSTAT | PROC_FILLARG);

Seems like you can drop FILLARG:

From man:
       PROC_FILLARG
            equivalent to PROC_FILLCOM

> +	igt_assert(proc != NULL);
> +
> +	while ((proc_info = readproc(proc, NULL))) {
> +		if (!strncasecmp(proc_info->cmd, comm, sizeof(proc_info->cmd))) {
> +			freeproc(proc_info);
> +			found = true;
> +			break;
> +		}
> +		freeproc(proc_info);
> +	}
> +
> +	closeproc(proc);
> +	return found;
> +}
> +
>  /**
>   * igt_terminate_process:
>   * @sig: Signal to send
> diff --git a/lib/igt_aux.h b/lib/igt_aux.h
> index 55392790..dbd88b67 100644
> --- a/lib/igt_aux.h
> +++ b/lib/igt_aux.h
> @@ -279,6 +279,7 @@ bool igt_allow_unlimited_files(void);
>  void igt_set_module_param(const char *name, const char *val);
>  void igt_set_module_param_int(const char *name, int val);
>  
> +int igt_is_process_running(const char *comm);
>  int igt_terminate_process(int sig, const char *comm);
>  void igt_lsof(const char *dpath);
>  
> diff --git a/lib/igt_chamelium.c b/lib/igt_chamelium.c
> index 02cc9b2c..7c9030d1 100644
> --- a/lib/igt_chamelium.c
> +++ b/lib/igt_chamelium.c
> @@ -218,6 +218,12 @@ void chamelium_destroy_frame_dump(struct chamelium_frame_dump *dump)
>  	free(dump);
>  }
>  
> +void chamelium_destroy_audio_file(struct chamelium_audio_file *audio_file)
> +{
> +	free(audio_file->path);
> +	free(audio_file);
> +}
> +
>  struct fsm_monitor_args {
>  	struct chamelium *chamelium;
>  	struct chamelium_port *port;
> @@ -924,6 +930,101 @@ int chamelium_get_captured_frame_count(struct chamelium *chamelium)
>  	return ret;
>  }
>  
> +/**
> + * chamelium_start_capturing_audio:
> + * @chamelium: the Chamelium instance
> + * @port: the port to capture audio from (it must support audio)
> + * @save_to_file: whether the captured audio data should be saved to a file on
> + * the Chamelium device
> + *
> + * Starts capturing audio from a Chamelium port. To stop the capture, use
> + * #chamelium_stop_capturing_audio. To retrieve the audio data, either use the
> + * stream server or enable @save_to_file (the latter is mainly useful for
> + * debugging purposes).
> + *
> + * It isn't possible to capture audio from multiple ports at the same time.
> + */
> +void chamelium_start_capturing_audio(struct chamelium *chamelium,
> +				    struct chamelium_port *port,
> +				    bool save_to_file)
> +{
> +	xmlrpc_value *res;
> +
> +	res = chamelium_rpc(chamelium, port, "StartCapturingAudio", "(ib)",
> +			    port->id, save_to_file);
> +	xmlrpc_DECREF(res);
> +}
> +
> +static void audio_format_from_xml(struct chamelium *chamelium,
> +				  xmlrpc_value *res, int *rate, int *channels)
> +{
> +	xmlrpc_value *res_type, *res_rate, *res_sample_format, *res_channel;
> +	char *type, *sample_format;
> +
> +	xmlrpc_struct_find_value(&chamelium->env, res, "file_type", &res_type);
> +	xmlrpc_struct_find_value(&chamelium->env, res, "rate", &res_rate);
> +	xmlrpc_struct_find_value(&chamelium->env, res, "sample_format", &res_sample_format);
> +	xmlrpc_struct_find_value(&chamelium->env, res, "channel", &res_channel);
> +
> +	xmlrpc_read_string(&chamelium->env, res_type, (const char **) &type);
> +	igt_assert(strcmp(type, "raw") == 0);
> +	free(type);
> +
> +	xmlrpc_read_string(&chamelium->env, res_sample_format, (const char **) &sample_format);
> +	igt_assert(strcmp(sample_format, "S32_LE") == 0);
> +	free(sample_format);
> +
> +	xmlrpc_read_int(&chamelium->env, res_rate, rate);
> +	xmlrpc_read_int(&chamelium->env, res_channel, channels);
> +
> +	xmlrpc_DECREF(res_channel);
> +	xmlrpc_DECREF(res_sample_format);
> +	xmlrpc_DECREF(res_rate);
> +	xmlrpc_DECREF(res_type);
> +}
> +
> +/**
> + * chamelium_stop_capturing_audio:
> + * @chamelium: the Chamelium instance
> + * @port: the port from which audio is being captured
> + *
> + * Stops capturing audio from a Chamelium port. If
> + * #chamelium_start_capturing_audio has been called with @save_to_file enabled,
> + * this function will return a #chamelium_audio_file struct containing details
> + * about the audio file. Once the caller is done with the struct, they should
> + * release it with #chamelium_destroy_audio_file.
> + */
> +struct chamelium_audio_file *chamelium_stop_capturing_audio(struct chamelium *chamelium,
> +							    struct chamelium_port *port)
> +{
> +	xmlrpc_value *res, *res_path, *res_props;
> +	struct chamelium_audio_file *file = NULL;
> +	char *path;
> +
> +	res = chamelium_rpc(chamelium, NULL, "StopCapturingAudio", "(i)",
> +			    port->id);
> +	xmlrpc_array_read_item(&chamelium->env, res, 0, &res_path);
> +	xmlrpc_array_read_item(&chamelium->env, res, 1, &res_props);
> +
> +	xmlrpc_read_string(&chamelium->env, res_path, (const char **) &path);
> +
> +	if (strlen(path) > 0) {
> +		file = calloc(1, sizeof(*file));
> +		file->path = path;
> +
> +		audio_format_from_xml(chamelium, res_props,
> +				      &file->rate, &file->channels);
> +	} else {
> +		free(path);
> +	}
> +
> +	xmlrpc_DECREF(res_props);
> +	xmlrpc_DECREF(res_path);
> +	xmlrpc_DECREF(res);
> +
> +	return file;
> +}
> +
>  static pixman_image_t *convert_frame_format(pixman_image_t *src,
>  					    int format)
>  {
> diff --git a/lib/igt_chamelium.h b/lib/igt_chamelium.h
> index 233ead85..047f8c5d 100644
> --- a/lib/igt_chamelium.h
> +++ b/lib/igt_chamelium.h
> @@ -53,6 +53,12 @@ enum chamelium_check {
>  	CHAMELIUM_CHECK_CRC,
>  };
>  
> +struct chamelium_audio_file {
> +	char *path;
> +	int rate; /* Hz */
> +	int channels;
> +};
> +
>  struct chamelium *chamelium_init(int drm_fd);
>  void chamelium_deinit(struct chamelium *chamelium);
>  void chamelium_reset(struct chamelium *chamelium);
> @@ -100,6 +106,10 @@ void chamelium_start_capture(struct chamelium *chamelium,
>  void chamelium_stop_capture(struct chamelium *chamelium, int frame_count);
>  void chamelium_capture(struct chamelium *chamelium, struct chamelium_port *port,
>  		       int x, int y, int w, int h, int frame_count);
> +void chamelium_start_capturing_audio(struct chamelium *chamelium,
> +				    struct chamelium_port *port, bool save_to_file);
> +struct chamelium_audio_file *chamelium_stop_capturing_audio(struct chamelium *chamelium,
> +							    struct chamelium_port *port);
>  igt_crc_t *chamelium_read_captured_crcs(struct chamelium *chamelium,
>  					int *frame_count);
>  struct chamelium_frame_dump *chamelium_read_captured_frame(struct chamelium *chamelium,
> @@ -131,5 +141,6 @@ void chamelium_assert_frame_match_or_dump(struct chamelium *chamelium,
>  void chamelium_crop_analog_frame(struct chamelium_frame_dump *dump, int width,
>  				 int height);
>  void chamelium_destroy_frame_dump(struct chamelium_frame_dump *dump);
> +void chamelium_destroy_audio_file(struct chamelium_audio_file *audio_file);
>  
>  #endif /* IGT_CHAMELIUM_H */
> diff --git a/lib/igt_chamelium_stream.c b/lib/igt_chamelium_stream.c
> new file mode 100644
> index 00000000..9e1ba1ca
> --- /dev/null
> +++ b/lib/igt_chamelium_stream.c
> @@ -0,0 +1,592 @@
> +/*
> + * Copyright © 2019 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: Simon Ser <simon.ser@intel.com>
> + */
> +
> +#include "config.h"
> +
> +#include <arpa/inet.h>
> +#include <errno.h>
> +#include <netdb.h>
> +#include <stdbool.h>
> +#include <stdlib.h>
> +#include <sys/types.h>
> +#include <sys/socket.h>
> +
> +#include "igt_chamelium_stream.h"
> +#include "igt_core.h"
> +#include "igt_rc.h"
> +
> +#define STREAM_PORT 9994
> +#define STREAM_VERSION_MAJOR 1
> +#define STREAM_VERSION_MINOR 0
> +
> +enum stream_error {
> +	STREAM_ERROR_NONE = 0,
> +	STREAM_ERROR_COMMAND = 1,
> +	STREAM_ERROR_ARGUMENT = 2,
> +	STREAM_ERROR_EXISTS = 3,
> +	STREAM_ERROR_VIDEO_MEM_OVERFLOW_STOP = 4,
> +	STREAM_ERROR_VIDEO_MEM_OVERFLOW_DROP = 5,
> +	STREAM_ERROR_AUDIO_MEM_OVERFLOW_STOP = 6,
> +	STREAM_ERROR_AUDIO_MEM_OVERFLOW_DROP = 7,
> +	STREAM_ERROR_NO_MEM = 8,
> +};
> +
> +enum stream_message_kind {
> +	STREAM_MESSAGE_REQUEST = 0,
> +	STREAM_MESSAGE_RESPONSE = 1,
> +	STREAM_MESSAGE_DATA = 2,
> +};
> +
> +enum stream_message_type {
> +	STREAM_MESSAGE_RESET = 0,
> +	STREAM_MESSAGE_GET_VERSION = 1,
> +	STREAM_MESSAGE_VIDEO_STREAM = 2,
> +	STREAM_MESSAGE_SHRINK_VIDEO = 3,
> +	STREAM_MESSAGE_VIDEO_FRAME = 4,
> +	STREAM_MESSAGE_DUMP_REALTIME_VIDEO = 5,
> +	STREAM_MESSAGE_STOP_DUMP_VIDEO = 6,
> +	STREAM_MESSAGE_DUMP_REALTIME_AUDIO = 7,
> +	STREAM_MESSAGE_STOP_DUMP_AUDIO = 8,
> +};
> +
> +struct chamelium_stream {
> +	char *host;
> +	unsigned int port;
> +
> +	int fd;
> +};
> +
> +static const char *stream_error_str(enum stream_error err)
> +{
> +	switch (err) {
> +	case STREAM_ERROR_NONE:
> +		return "no error";
> +	case STREAM_ERROR_COMMAND:
> +		return "invalid command";
> +	case STREAM_ERROR_ARGUMENT:
> +		return "invalid arguments";
> +	case STREAM_ERROR_EXISTS:
> +		return "dump already started";
> +	case STREAM_ERROR_VIDEO_MEM_OVERFLOW_STOP:
> +		return "video dump stopped after overflow";
> +	case STREAM_ERROR_VIDEO_MEM_OVERFLOW_DROP:
> +		return "video frame dropped after overflow";
> +	case STREAM_ERROR_AUDIO_MEM_OVERFLOW_STOP:
> +		return "audio dump stoppred after overflow";
> +	case STREAM_ERROR_AUDIO_MEM_OVERFLOW_DROP:
> +		return "audio page dropped after overflow";
> +	case STREAM_ERROR_NO_MEM:
> +		return "out of memory";
> +	}
> +	return "unknown error";
> +}
> +
> +/**
> + * The Chamelium URL is specified in the configuration file. We need to extract
> + * the host to connect to the stream server.
> + */
> +static char *parse_url_host(const char *url)
> +{
> +	static const char prefix[] = "http://";
> +	char *colon;
> +
> +	if (strstr(url, prefix) != url)
> +		return NULL;
> +	url += strlen(prefix);
> +
> +	colon = strchr(url, ':');
> +	if (!colon)
> +		return NULL;
> +
> +	return strndup(url, colon - url);
> +}
> +
> +static bool chamelium_stream_read_config(struct chamelium_stream *client)
> +{
> +	GError *error = NULL;
> +	gchar *chamelium_url;
> +
> +	if (!igt_key_file) {
> +		igt_warn("No configuration file available for chamelium\n");
> +		return false;
> +	}
> +
> +	chamelium_url = g_key_file_get_string(igt_key_file, "Chamelium", "URL",
> +					      &error);
> +	if (!chamelium_url) {
> +		igt_warn("Couldn't read Chamelium URL from config file: %s\n",
> +			 error->message);
> +		return false;
> +	}
> +
> +	client->host = parse_url_host(chamelium_url);
> +	if (!client->host) {
> +		igt_warn("Invalid Chamelium URL in config file: %s\n",
> +			 chamelium_url);
> +		return false;
> +	}
> +	client->port = STREAM_PORT;
> +
> +	return true;
> +}
> +
> +static bool chamelium_stream_connect(struct chamelium_stream *client)
> +{
> +	int ret;
> +	char port_str[16];
> +	struct addrinfo hints = {};
> +	struct addrinfo *results, *ai;
> +	struct timeval tv = {};
> +
> +	igt_debug("Connecting to Chamelium stream server: tcp://%s:%u\n",
> +		  client->host, client->port);
> +
> +	snprintf(port_str, sizeof(port_str), "%u", client->port);
> +
> +	hints.ai_family = AF_UNSPEC;
> +	hints.ai_socktype = SOCK_STREAM;
> +	ret = getaddrinfo(client->host, port_str, &hints, &results);
> +	if (ret != 0) {
> +		igt_warn("getaddrinfo failed: %s\n", gai_strerror(ret));
> +		return false;
> +	}
> +
> +	client->fd = -1;
> +	for (ai = results; ai != NULL; ai = ai->ai_next) {
> +		client->fd = socket(ai->ai_family, ai->ai_socktype,
> +				    ai->ai_protocol);
> +		if (client->fd == -1)
> +			continue;
> +
> +		if (connect(client->fd, ai->ai_addr, ai->ai_addrlen) == -1) {
> +			close(client->fd);
> +			client->fd = -1;
> +			continue;
> +		}
> +
> +		break;
> +	}
> +
> +	freeaddrinfo(results);
> +
> +	if (client->fd < 0) {
> +		igt_warn("Failed to connect to Chamelium stream server\n");
> +		return false;
> +	}
> +
> +	/* Set a read and write timeout of 5 seconds. */
> +	tv.tv_sec = 5;
> +	setsockopt(client->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
> +	setsockopt(client->fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
> +
> +	return true;
> +}
> +
> +static bool read_whole(int fd, void *buf, size_t buf_len)
> +{
> +	ssize_t ret;
> +	size_t n = 0;
> +	char *ptr;
> +
> +	while (n < buf_len) {
> +		ptr = (char *) buf + n;
> +		ret = read(fd, ptr, buf_len - n);
> +		if (ret < 0) {
> +			igt_warn("read failed: %s\n", strerror(errno));
> +			return false;
> +		} else if (ret == 0) {
> +			igt_warn("short read\n");
> +			return false;
> +		}
> +		n += ret;
> +	}
> +
> +	return true;
> +}
> +
> +static bool write_whole(int fd, void *buf, size_t buf_len)
> +{
> +	ssize_t ret;
> +	size_t n = 0;
> +	char *ptr;
> +
> +	while (n < buf_len) {
> +		ptr = (char *) buf + n;
> +		ret = write(fd, ptr, buf_len - n);
> +		if (ret < 0) {
> +			igt_warn("write failed: %s\n", strerror(errno));
> +			return false;
> +		} else if (ret == 0) {
> +			igt_warn("short write\n");
> +			return false;
> +		}
> +		n += ret;
> +	}
> +
> +	return true;
> +}
> +
> +static bool read_and_discard(int fd, size_t len)
> +{
> +	char buf[1024];
> +	size_t n;
> +
> +	while (len > 0) {
> +		n = len;
> +		if (n > sizeof(buf))
> +			n = sizeof(buf);
> +
> +		if (!read_whole(fd, buf, n))
> +			return false;
> +
> +		len -= n;
> +	}
> +
> +	return true;
> +}
> +
> +/** Read a message header from the socket.
> + *
> + * The header is laid out as follows:
> + * - u16: message type
> + * - u16: error code
> + * - u32: message length
> + */
> +static bool chamelium_stream_read_header(struct chamelium_stream *client,
> +					 enum stream_message_kind *kind,
> +					 enum stream_message_type *type,
> +					 enum stream_error *err,
> +					 size_t *len)
> +{
> +	uint16_t _type;
> +	char buf[8];
> +
> +	if (!read_whole(client->fd, buf, sizeof(buf)))
> +		return false;
> +
> +	_type = ntohs(*(uint16_t *) &buf[0]);
> +	*type = _type & 0xFF;
> +	*kind = _type >> 8;
> +	*err = ntohs(*(uint16_t *) &buf[2]);
> +	*len = ntohl(*(uint32_t *) &buf[4]);
> +
> +	//igt_debug("received message: kind=%d type=%d err=%d len=%zu\n",
> +	//	  *kind, *type, *err, *len);

You probably want to keep this debug message. If not, then comment it
properly with /* */ or delete it.

> +
> +	return true;
> +}
> +
> +static bool chamelium_stream_write_header(struct chamelium_stream *client,
> +					  enum stream_message_type type,
> +					  enum stream_error err,
> +					  size_t len)
> +{
> +	char buf[8];
> +	uint16_t _type;
> +
> +	_type = type | (STREAM_MESSAGE_REQUEST << 8);
> +
> +	*(uint16_t *) &buf[0] = htons(_type);
> +	*(uint16_t *) &buf[2] = htons(err);
> +	*(uint32_t *) &buf[4] = htonl(len);
> +
> +	return write_whole(client->fd, buf, sizeof(buf));
> +}
> +
> +static bool chamelium_stream_read_response(struct chamelium_stream *client,
> +					   enum stream_message_type type,
> +					   void *buf, size_t buf_len)
> +{
> +	enum stream_message_kind read_kind;
> +	enum stream_message_type read_type;
> +	enum stream_error read_err;
> +	size_t read_len;
> +
> +	if (!chamelium_stream_read_header(client, &read_kind, &read_type,
> +					  &read_err, &read_len))
> +		return false;
> +
> +	if (read_kind != STREAM_MESSAGE_RESPONSE) {
> +		igt_warn("Expected a response, got kind %d\n", read_kind);
> +		return false;
> +	}
> +	if (read_type != type) {
> +		igt_warn("Expected message type %d, got %d\n",
> +			 type, read_type);
> +		return false;
> +	}
> +	if (read_err != STREAM_ERROR_NONE) {
> +		igt_warn("Received error: %s (%d)\n",
> +			 stream_error_str(read_err), read_err);
> +		return false;
> +	}
> +	if (buf_len != read_len) {
> +		igt_warn("Received invalid message body size "
> +			 "(got %zu bytes, want %zu bytes)\n",
> +			 read_len, buf_len);
> +		return false;
> +	}
> +
> +	return read_whole(client->fd, buf, buf_len);
> +}
> +
> +static bool chamelium_stream_write_request(struct chamelium_stream *client,
> +					   enum stream_message_type type,
> +					   void *buf, size_t buf_len)
> +{
> +	if (!chamelium_stream_write_header(client, type, STREAM_ERROR_NONE,
> +					   buf_len))
> +		return false;
> +
> +	if (buf_len == 0)
> +		return true;
> +
> +	return write_whole(client->fd, buf, buf_len);
> +}
> +
> +static bool chamelium_stream_call(struct chamelium_stream *client,
> +				  enum stream_message_type type,
> +				  void *req_buf, size_t req_len,
> +				  void *resp_buf, size_t resp_len)
> +{
> +	if (!chamelium_stream_write_request(client, type, req_buf, req_len))
> +		return false;
> +
> +	return chamelium_stream_read_response(client, type, resp_buf, resp_len);
> +}
> +
> +static bool chamelium_stream_check_version(struct chamelium_stream *client)
> +{
> +	char resp[2];
> +	uint8_t major, minor;
> +
> +	if (!chamelium_stream_call(client, STREAM_MESSAGE_GET_VERSION,
> +				   NULL, 0, resp, sizeof(resp)))
> +		return false;
> +
> +	major = resp[0];
> +	minor = resp[1];
> +	if (major != STREAM_VERSION_MAJOR || minor < STREAM_VERSION_MINOR) {
> +		igt_warn("Version mismatch (want %d.%d, got %d.%d)\n",
> +			 STREAM_VERSION_MAJOR, STREAM_VERSION_MINOR,
> +			 major, minor);
> +		return false;
> +	}
> +
> +	return true;
> +}
> +
> +/**
> + * chamelium_stream_dump_realtime_audio:
> + *
> + * Starts audio capture. The caller can then call
> + * #chamelium_stream_receive_realtime_audio to receive audio pages.
> + */
> +bool chamelium_stream_dump_realtime_audio(struct chamelium_stream *client,
> +					  enum chamelium_stream_realtime_mode mode)
> +{
> +	char req[1];
> +
> +	igt_debug("Starting real-time audio capture\n");
> +
> +	req[0] = mode;
> +	return chamelium_stream_call(client, STREAM_MESSAGE_DUMP_REALTIME_AUDIO,
> +				     req, sizeof(req), NULL, 0);
> +}
> +
> +/**
> + * chamelium_stream_receive_realtime_audio:
> + * @page_count: if non-NULL, will be set to the dumped page number
> + * @buf: must either point to a dynamically allocated memory region or NULL
> + * @buf_len: number of elements of *@buf, for zero if @buf is NULL
> + *
> + * Receives one audio page from the streaming server.
> + *
> + * In "best effort" mode, some pages can be dropped. This can be detected via
> + * the page count.
> + *
> + * buf_len will be set to the size of the page. The caller is responsible for
> + * calling free(3) on *buf.
> + */
> +bool chamelium_stream_receive_realtime_audio(struct chamelium_stream *client,
> +					     size_t *page_count,
> +					     int32_t **buf, size_t *buf_len)
> +{
> +	enum stream_message_kind kind;
> +	enum stream_message_type type;
> +	enum stream_error err;
> +	size_t body_len;
> +	char page_count_buf[4];
> +	int32_t *ptr;
> +
> +	while (true) {
> +		if (!chamelium_stream_read_header(client, &kind, &type,
> +						  &err, &body_len))
> +			return false;
> +
> +		if (kind != STREAM_MESSAGE_DATA) {
> +			igt_warn("Expected a data message, got kind %d\n", kind);
> +			return false;
> +		}
> +		if (type != STREAM_MESSAGE_DUMP_REALTIME_AUDIO) {
> +			igt_warn("Expected real-time audio dump message, "
> +				 "got type %d\n", type);
> +			return false;
> +		}
> +
> +		if (err == STREAM_ERROR_NONE)
> +			break;
> +		else if (err != STREAM_ERROR_AUDIO_MEM_OVERFLOW_DROP) {
> +			igt_warn("Received error: %s (%d)\n",
> +				 stream_error_str(err), err);
> +			return false;
> +		}
> +
> +		igt_debug("Dropped an audio page because of an overflow\n");
> +		igt_assert(body_len == 0);
> +	}
> +
> +	igt_assert(body_len >= sizeof(page_count_buf));
> +
> +	if (!read_whole(client->fd, page_count_buf, sizeof(page_count_buf)))
> +		return false;
> +	if (page_count)
> +		*page_count = ntohl(*(uint32_t *) &page_count_buf[0]);
> +	body_len -= sizeof(page_count_buf);
> +
> +	igt_assert(body_len % sizeof(int32_t) == 0);
> +	if (*buf_len * sizeof(int32_t) != body_len) {
> +		ptr = realloc(*buf, body_len);
> +		if (!ptr) {
> +			igt_warn("realloc failed: %s\n", strerror(errno));
> +			return false;
> +		}
> +		*buf = ptr;
> +		*buf_len = body_len / sizeof(int32_t);
> +	}
> +
> +	return read_whole(client->fd, *buf, body_len);
> +}
> +
> +/**
> + * chamelium_stream_stop_realtime_audio:
> + *
> + * Stops real-time audio capture. This also drops any buffered audio pages.
> + * The caller shouldn't call #chamelium_stream_receive_realtime_audio after
> + * stopping audio capture.
> + */
> +bool chamelium_stream_stop_realtime_audio(struct chamelium_stream *client)
> +{
> +	enum stream_message_kind kind;
> +	enum stream_message_type type;
> +	enum stream_error err;
> +	size_t len;
> +
> +	igt_debug("Stopping real-time audio capture\n");
> +
> +	if (!chamelium_stream_write_request(client,
> +					    STREAM_MESSAGE_STOP_DUMP_AUDIO,
> +					    NULL, 0))
> +		return false;
> +
> +	while (true) {
> +		if (!chamelium_stream_read_header(client, &kind, &type,
> +						  &err, &len))
> +			return false;
> +
> +		if (kind == STREAM_MESSAGE_RESPONSE)
> +			break;
> +
> +		if (!read_and_discard(client->fd, len))
> +			return false;
> +	}
> +
> +	if (type != STREAM_MESSAGE_STOP_DUMP_AUDIO) {
> +		igt_warn("Unexpected response type %d\n", type);
> +		return false;
> +	}
> +	if (err != STREAM_ERROR_NONE) {
> +		igt_warn("Received error: %s (%d)\n",
> +			 stream_error_str(err), err);
> +		return false;
> +	}
> +	if (len != 0) {
> +		igt_warn("Expected an empty response, got %zu bytes\n", len);
> +		return false;
> +	}
> +
> +	return true;
> +}
> +
> +/**
> + * chamelium_stream_audio_format:
> + *
> + * Gets the format used for audio pages.
> + *
> + * Data will always be captured in raw pages of S32_LE elements. This function
> + * exposes the sampling rate and the number of channels.
> + */
> +void chamelium_stream_audio_format(struct chamelium_stream *stream,
> +				   int *rate, int *channels)
> +{
> +	/* TODO: the Chamelium streaming server doesn't expose those yet.
> +	 * Just hardcode the values for now. */
> +	*rate = 48000;
> +	*channels = 8;
> +}
> +
> +/**
> + * chamelium_stream_init:
> + *
> + * Connects to the Chamelium streaming server.
> + */
> +struct chamelium_stream *chamelium_stream_init(void)
> +{
> +	struct chamelium_stream *client;
> +
> +	client = calloc(1, sizeof(*client));
> +
> +	if (!chamelium_stream_read_config(client))
> +		goto error_client;
> +	if (!chamelium_stream_connect(client))
> +		goto error_client;
> +	if (!chamelium_stream_check_version(client))
> +		goto error_fd;
> +
> +	return client;
> +
> +error_fd:
> +	close(client->fd);
> +error_client:
> +	free(client);
> +	return NULL;
> +}
> +
> +void chamelium_stream_deinit(struct chamelium_stream *client)
> +{
> +	if (close(client->fd) != 0)
> +		igt_warn("close failed: %s\n", strerror(errno));
> +	free(client);
> +}
> diff --git a/lib/igt_chamelium_stream.h b/lib/igt_chamelium_stream.h
> new file mode 100644
> index 00000000..de4e9931
> --- /dev/null
> +++ b/lib/igt_chamelium_stream.h
> @@ -0,0 +1,52 @@
> +/*
> + * Copyright © 2019 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: Simon Ser <simon.ser@intel.com>
> + */
> +
> +#ifndef IGT_CHAMELIUM_STREAM_H
> +#define IGT_CHAMELIUM_STREAM_H
> +
> +#include "config.h"
> +
> +enum chamelium_stream_realtime_mode {
> +	CHAMELIUM_STREAM_REALTIME_NONE = 0,
> +	/* stop dumping when overflow */
> +	CHAMELIUM_STREAM_REALTIME_STOP_WHEN_OVERFLOW = 1,
> +	/* drop data on overflow */
> +	CHAMELIUM_STREAM_REALTIME_BEST_EFFORT = 2,
> +};
> +
> +struct chamelium_stream;
> +
> +struct chamelium_stream *chamelium_stream_init(void);
> +void chamelium_stream_deinit(struct chamelium_stream *client);
> +bool chamelium_stream_dump_realtime_audio(struct chamelium_stream *client,
> +					  enum chamelium_stream_realtime_mode mode);
> +void chamelium_stream_audio_format(struct chamelium_stream *stream,
> +				   int *rate, int *channels);
> +bool chamelium_stream_receive_realtime_audio(struct chamelium_stream *client,
> +					     size_t *page_count,
> +					     int32_t **buf, size_t *buf_len);
> +bool chamelium_stream_stop_realtime_audio(struct chamelium_stream *client);
> +
> +#endif
> diff --git a/lib/meson.build b/lib/meson.build
> index a8462933..eead0afb 100644
> --- a/lib/meson.build
> +++ b/lib/meson.build
> @@ -92,7 +92,7 @@ if valgrind.found()
>  endif
>  
>  if gsl.found()
> -	lib_deps += [ gsl ]
> +	lib_deps += gsl
>  	lib_sources += [ 'igt_frame.c', 'igt_audio.c' ]
>  endif
>  
> @@ -101,9 +101,10 @@ if alsa.found()
>  	lib_sources += 'igt_alsa.c'
>  endif
>  
> -if chamelium.found()
> +if chamelium_found
>  	lib_deps += chamelium
>  	lib_sources += 'igt_chamelium.c'
> +	lib_sources += 'igt_chamelium_stream.c'
>  endif
>  
>  srcdir = join_paths(meson.source_root(), 'tests')
> diff --git a/meson.build b/meson.build
> index 557400a5..be6dff9d 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -64,8 +64,6 @@ _build_overlay = false
>  _overlay_required = false
>  _build_man = false
>  _man_required = false
> -_build_audio = false
> -_audio_required = false
>  _build_chamelium = false
>  _chamelium_required = false
>  _build_docs = false
> @@ -79,7 +77,6 @@ build_overlay = get_option('build_overlay')
>  overlay_backends = get_option('overlay_backends')
>  build_man = get_option('build_man')
>  with_valgrind = get_option('with_valgrind')
> -build_audio = get_option('build_audio')
>  build_chamelium = get_option('build_chamelium')
>  build_docs = get_option('build_docs')
>  build_tests = get_option('build_tests')
> @@ -91,8 +88,6 @@ _build_overlay = build_overlay != 'false'
>  _overlay_required = build_overlay == 'true'
>  _build_man = build_man != 'false'
>  _man_required = build_man == 'true'
> -_build_audio = build_audio != 'false'
> -_audio_required = build_audio == 'true'
>  _build_chamelium = build_chamelium != 'false'
>  _chamelium_required = build_chamelium == 'true'
>  _build_docs = build_docs != 'false'
> @@ -166,26 +161,6 @@ cairo = dependency('cairo', version : '>1.12.0', required : true)
>  libudev = dependency('libudev', required : true)
>  glib = dependency('glib-2.0', required : true)
>  
> -gsl = null_dep
> -alsa = null_dep
> -if _build_audio or _build_chamelium
> -	gsl = dependency('gsl', required : _audio_required or _chamelium_required)
> -endif
> -if _build_audio
> -	alsa = dependency('alsa', required : _audio_required)
> -endif
> -
> -audioinfo = 'No'
> -if _build_audio and alsa.found() and gsl.found()
> -	audioinfo = 'Yes'
> -else
> -	if _audio_required
> -		error('Cannot build audio test due to missing dependencies')
> -	endif
> -	_build_audio = false
> -endif
> -build_info += 'Build audio test: ' + audioinfo
> -
>  xmlrpc = dependency('xmlrpc', required : false)
>  xmlrpc_util = dependency('xmlrpc_util', required : false)
>  xmlrpc_client = dependency('xmlrpc_client', required : false)
> @@ -197,21 +172,32 @@ if not xmlrpc.found() and xmlrpc_cmd.found()
>  
>  	if libs_cmd.returncode() == 0 and cflags_cmd.returncode() == 0
>  		xmlrpc = declare_dependency(compile_args: cflags_cmd.stdout().strip().split(),
> -					   link_args : libs_cmd.stdout().strip().split())
> +					    link_args : libs_cmd.stdout().strip().split())
>  		xmlrpc_util = declare_dependency()
>  		xmlrpc_client = declare_dependency()
>  	endif
>  endif
>  
> +gsl = null_dep
> +alsa = null_dep
>  chamelium = null_dep
> +chamelium_found = false # TODO: use a disabler object instead
>  chameliuminfo = 'No'
> -if _build_chamelium and gsl.found() and xmlrpc.found() and xmlrpc_util.found() and xmlrpc_client.found()
> -	chamelium = declare_dependency(dependencies : [ xmlrpc,
> -							xmlrpc_util, xmlrpc_client])
> -	config.set('HAVE_CHAMELIUM', 1)
> -	chameliuminfo = 'Yes'
> -elif _chamelium_required
> -	error('Cannot build chamelium test due to missing dependencies')
> +if _build_chamelium
> +	gsl = dependency('gsl', required : _chamelium_required)
> +	alsa = dependency('alsa', required : _chamelium_required)
> +	chamelium = declare_dependency(dependencies : [
> +		xmlrpc,
> +		xmlrpc_util,
> +		xmlrpc_client,
> +		gsl,
> +		alsa,
> +	], required : _chamelium_required)
> +	if xmlrpc.found() and xmlrpc_util.found() and xmlrpc_client.found() and gsl.found() and alsa.found()
> +		config.set('HAVE_CHAMELIUM', 1)
> +		chameliuminfo = 'Yes'
> +		chamelium_found = true
> +	endif
>  endif
>  build_info += 'Build Chamelium test: ' + chameliuminfo
>  
> diff --git a/meson_options.txt b/meson_options.txt
> index 0cd3b350..888efe56 100644
> --- a/meson_options.txt
> +++ b/meson_options.txt
> @@ -10,12 +10,6 @@ option('overlay_backends',
>         choices : [ 'auto', 'x', 'xv' ],
>         description : 'Overlay backends to enable')
>  
> -option('build_audio',
> -       type : 'combo',
> -       value : 'auto',
> -       choices : ['auto', 'true', 'false'],
> -       description : 'Build audio test')
> -
>  option('build_chamelium',
>         type : 'combo',
>         value : 'auto',
> diff --git a/tests/audio.c b/tests/audio.c
> deleted file mode 100644
> index 560876a3..00000000
> --- a/tests/audio.c
> +++ /dev/null
> @@ -1,193 +0,0 @@
> -/*
> - * 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 <paul.kocialkowski@linux.intel.com>
> - */
> -
> -#include "config.h"
> -#include "igt.h"
> -
> -#define PLAYBACK_CHANNELS	2
> -#define PLAYBACK_FRAMES		1024
> -
> -#define CAPTURE_SAMPLE_RATE	48000
> -#define CAPTURE_CHANNELS	2
> -#define CAPTURE_DEVICE_NAME	"default"
> -#define CAPTURE_FRAMES		2048
> -
> -#define RUN_TIMEOUT		2000
> -
> -struct test_data {
> -	struct alsa *alsa;
> -	struct audio_signal *signal;
> -
> -	int streak;
> -};
> -
> -static int sampling_rates[] = {
> -	32000,
> -	44100,
> -	48000,
> -	88200,
> -	96000,
> -	176400,
> -	192000,
> -};
> -
> -static int sampling_rates_count = sizeof(sampling_rates) / sizeof(int);
> -
> -static int test_frequencies[] = {
> -	300,
> -	600,
> -	1200,
> -	80000,
> -	10000,
> -};
> -
> -static int test_frequencies_count = sizeof(test_frequencies) / sizeof(int);
> -
> -static int output_callback(void *data, short *buffer, int frames)
> -{
> -	struct test_data *test_data = (struct test_data *) data;
> -
> -	audio_signal_fill(test_data->signal, buffer, frames);
> -
> -	return 0;
> -}
> -
> -static int input_callback(void *data, short *buffer, int frames)
> -{
> -	struct test_data *test_data = (struct test_data *) data;
> -	bool detect;
> -
> -	detect = audio_signal_detect(test_data->signal, CAPTURE_CHANNELS,
> -				     CAPTURE_SAMPLE_RATE, buffer, frames);
> -	if (detect)
> -		test_data->streak++;
> -	else
> -		test_data->streak = 0;
> -
> -	/* A streak of 3 gives confidence that the signal is good. */
> -	if (test_data->streak == 3)
> -		return 1;
> -
> -	return 0;
> -}
> -
> -static void test_integrity(const char *device_name)
> -{
> -	struct test_data data;
> -	int sampling_rate;
> -	bool run = false;
> -	bool test;
> -	int i, j;
> -	int ret;
> -
> -	data.alsa = alsa_init();
> -	igt_assert(data.alsa);
> -
> -	ret = alsa_open_input(data.alsa, CAPTURE_DEVICE_NAME);
> -	igt_assert(ret >= 0);
> -
> -	alsa_configure_input(data.alsa, CAPTURE_CHANNELS,
> -			     CAPTURE_SAMPLE_RATE);
> -
> -	alsa_register_input_callback(data.alsa, input_callback, &data,
> -				     CAPTURE_FRAMES);
> -
> -	for (i = 0; i < sampling_rates_count; i++) {
> -		ret = alsa_open_output(data.alsa, device_name);
> -		igt_assert(ret >= 0);
> -
> -		sampling_rate = sampling_rates[i];
> -
> -		test = alsa_test_output_configuration(data.alsa,
> -						      PLAYBACK_CHANNELS,
> -						      sampling_rate);
> -		if (!test) {
> -			alsa_close_output(data.alsa);
> -			continue;
> -		}
> -
> -		igt_debug("Testing with sampling rate %d\n", sampling_rate);
> -
> -		alsa_configure_output(data.alsa, PLAYBACK_CHANNELS,
> -				       sampling_rate);
> -
> -		data.signal = audio_signal_init(PLAYBACK_CHANNELS,
> -						sampling_rate);
> -		igt_assert(data.signal);
> -
> -		for (j = 0; j < test_frequencies_count; j++)
> -			audio_signal_add_frequency(data.signal,
> -						   test_frequencies[j]);
> -
> -		audio_signal_synthesize(data.signal);
> -
> -		alsa_register_output_callback(data.alsa, output_callback,
> -					      &data, PLAYBACK_FRAMES);
> -
> -		data.streak = 0;
> -
> -		ret = alsa_run(data.alsa, RUN_TIMEOUT);
> -		igt_assert(ret > 0);
> -
> -		audio_signal_clean(data.signal);
> -		free(data.signal);
> -
> -		alsa_close_output(data.alsa);
> -
> -		run = true;
> -	}
> -
> -	/* Make sure we tested at least one frequency */
> -	igt_assert(run);
> -
> -	alsa_close_input(data.alsa);
> -	free(data.alsa);
> -}
> -
> -static void test_suspend_resume_integrity(const char *device_name,
> -					  enum igt_suspend_state state,
> -					  enum igt_suspend_test test)
> -{
> -	test_integrity(device_name);
> -
> -	igt_system_suspend_autoresume(state, test);
> -
> -	test_integrity(device_name);
> -}
> -
> -igt_main
> -{
> -	igt_subtest("hdmi-integrity")
> -		test_integrity("HDMI");
> -
> -	igt_subtest("hdmi-integrity-after-suspend")
> -		test_suspend_resume_integrity("HDMI", SUSPEND_STATE_MEM,
> -					      SUSPEND_TEST_NONE);
> -
> -	igt_subtest("hdmi-integrity-after-hibernate")
> -		test_suspend_resume_integrity("HDMI", SUSPEND_STATE_DISK,
> -					      SUSPEND_TEST_DEVICES);
> -}
> diff --git a/tests/kms_chamelium.c b/tests/kms_chamelium.c
> index 2dc1049d..2974ff69 100644
> --- a/tests/kms_chamelium.c
> +++ b/tests/kms_chamelium.c
> @@ -413,7 +413,7 @@ test_suspend_resume_edid_change(data_t *data, struct chamelium_port *port,
>  
>  static igt_output_t *
>  prepare_output(data_t *data,
> -	       struct chamelium_port *port)
> +	       struct chamelium_port *port, bool set_edid)
>  {
>  	igt_display_t *display = &data->display;
>  	igt_output_t *output;
> @@ -428,7 +428,8 @@ prepare_output(data_t *data,
>  	/* The chamelium's default EDID has a lot of resolutions, way more then
>  	 * we need to test
>  	 */
> -	chamelium_port_set_edid(data->chamelium, port, data->edid_id);
> +	if (set_edid)
> +		chamelium_port_set_edid(data->chamelium, port, data->edid_id);
>  
>  	chamelium_plug(data->chamelium, port);
>  	wait_for_connector(data, port, DRM_MODE_CONNECTED);
> @@ -613,7 +614,7 @@ static void test_display_one_mode(data_t *data, struct chamelium_port *port,
>  
>  	reset_state(data, port);
>  
> -	output = prepare_output(data, port);
> +	output = prepare_output(data, port, true);
>  	connector = chamelium_port_get_connector(data->chamelium, port, false);
>  	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
>  	igt_assert(primary);
> @@ -644,7 +645,7 @@ static void test_display_all_modes(data_t *data, struct chamelium_port *port,
>  
>  	reset_state(data, port);
>  
> -	output = prepare_output(data, port);
> +	output = prepare_output(data, port, true);
>  	connector = chamelium_port_get_connector(data->chamelium, port, false);
>  	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
>  	igt_assert(primary);
> @@ -679,7 +680,7 @@ test_display_frame_dump(data_t *data, struct chamelium_port *port)
>  
>  	reset_state(data, port);
>  
> -	output = prepare_output(data, port);
> +	output = prepare_output(data, port, true);
>  	connector = chamelium_port_get_connector(data->chamelium, port, false);
>  	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
>  	igt_assert(primary);
> @@ -710,6 +711,266 @@ test_display_frame_dump(data_t *data, struct chamelium_port *port)
>  	drmModeFreeConnector(connector);
>  }
>  
> +
> +/* Playback parameters control the audio signal we synthesize and send */
> +#define PLAYBACK_CHANNELS 2
> +#define PLAYBACK_SAMPLES 1024
> +
> +/* Capture paremeters control the audio signal we receive */
> +#define CAPTURE_SAMPLES 2048
> +
> +#define AUDIO_DURATION 2000 /* ms */
> +/* A streak of 3 gives confidence that the signal is good. */
> +#define MIN_STREAK 3
> +
> +/* TODO: Chamelium only supports 48KHz for now */
> +static int sampling_rates[] = {
> +/*	32000, */
> +/*	44100, */
> +	48000,
> +/*	88200, */
> +/*	96000, */
> +/*	176400, */
> +/*	192000, */
> +};
> +
> +static int sampling_rates_count = sizeof(sampling_rates) / sizeof(int);
> +
> +static int test_frequencies[] = {
> +	300,
> +	600,
> +	1200,
> +	80000,
> +	10000,
> +};
> +
> +static int test_frequencies_count = sizeof(test_frequencies) / sizeof(int);
> +
> +static int
> +output_callback(void *data, short *buffer, int frames)
> +{
> +	struct audio_signal *signal = (struct audio_signal *) data;
> +
> +	audio_signal_fill(signal, buffer, frames);
> +
> +	return 0;
> +}
> +
> +static bool
> +do_test_display_audio(data_t *data, struct chamelium_port *port,
> +		      struct alsa *alsa, int playback_channels,
> +		      int playback_rate)
> +{
> +	int ret, capture_rate, capture_channels, msec;
> +	struct chamelium_audio_file *audio_file;
> +	struct chamelium_stream *stream;
> +	enum chamelium_stream_realtime_mode stream_mode;
> +	struct audio_signal *signal;
> +	int32_t *recv, *buf;
> +	double *channel;
> +	size_t i, streak, page_count;
> +	size_t recv_len, buf_len, buf_cap, buf_size, channel_len;
> +	bool ok;
> +	char dump_suffix[64];
> +	char *dump_path = NULL;
> +	int dump_fd = -1;
> +
> +	if (!alsa_test_output_configuration(alsa, playback_channels,
> +					    playback_rate))
> +		return false;
> +
> +	igt_debug("Testing with playback sampling rate %d\n", playback_rate);
> +	alsa_configure_output(alsa, playback_channels, playback_rate);
> +
> +	chamelium_start_capturing_audio(data->chamelium, port, false);
> +
> +	stream = chamelium_stream_init();
> +	igt_assert(stream);
> +
> +	stream_mode = CHAMELIUM_STREAM_REALTIME_STOP_WHEN_OVERFLOW;
> +	ok = chamelium_stream_dump_realtime_audio(stream, stream_mode);
> +	igt_assert(ok);
> +
> +	chamelium_stream_audio_format(stream, &capture_rate, &capture_channels);
> +
> +	if (igt_frame_dump_is_enabled()) {
> +		snprintf(dump_suffix, sizeof(dump_suffix), "capture-%dch-%d",
> +			 playback_channels, playback_rate);
> +
> +		dump_fd = audio_create_wav_file_s32_le(dump_suffix,
> +						       capture_rate,
> +						       capture_channels,
> +						       &dump_path);
> +		igt_assert(dump_fd >= 0);
> +	}
> +
> +	signal = audio_signal_init(playback_channels, playback_rate);
> +	igt_assert(signal);
> +
> +	for (i = 0; i < test_frequencies_count; i++)
> +		audio_signal_add_frequency(signal, test_frequencies[i]);
> +	audio_signal_synthesize(signal);
> +
> +	alsa_register_output_callback(alsa, output_callback, signal,
> +				      PLAYBACK_SAMPLES);
> +
> +	/* TODO: detect signal in real-time */
> +	ret = alsa_run(alsa, AUDIO_DURATION);
> +	igt_assert(ret == 0);
> +
> +	alsa_close_output(alsa);
> +
> +	/* Needs to be a multiple of 128, because that's the number of samples
> +	 * we get per channel each time we receive an audio page from the
> +	 * Chamelium device. */
> +	channel_len = CAPTURE_SAMPLES;
> +	channel = malloc(sizeof(double) * channel_len);
> +
> +	buf_cap = capture_channels * channel_len;
> +	buf = malloc(sizeof(int32_t) * buf_cap);
> +	buf_len = 0;
> +
> +	recv = NULL;
> +	recv_len = 0;
> +
> +	streak = 0;
> +	msec = 0;
> +	i = 0;
> +	while (streak < MIN_STREAK && msec < AUDIO_DURATION) {
> +		ok = chamelium_stream_receive_realtime_audio(stream,
> +							     &page_count,
> +							     &recv, &recv_len);
> +		igt_assert(ok);
> +
> +		memcpy(&buf[buf_len], recv, recv_len * sizeof(int32_t));
> +		buf_len += recv_len;
> +
> +		if (buf_len < buf_cap)
> +			continue;
> +		igt_assert(buf_len == buf_cap);
> +
> +		if (dump_fd >= 0) {
> +			buf_size = buf_len * sizeof(int32_t);
> +			igt_assert(write(dump_fd, buf, buf_size) == buf_size);
> +		}
> +
> +		/* TODO: check other channels too, not just the first one */
> +		audio_extract_channel_s32_le(channel, channel_len, buf, buf_len,
> +					     capture_channels, 0);
> +
> +		msec = i * channel_len / (double) capture_rate * 1000;
> +		igt_debug("Detecting audio signal, t=%d msec\n", msec);
> +
> +		if (audio_signal_detect(signal, capture_rate, channel,
> +					channel_len))
> +			streak++;
> +		else
> +			streak = 0;
> +
> +		buf_len = 0;
> +		i++;
> +	}
> +
> +	if (dump_fd >= 0) {
> +		close(dump_fd);
> +		if (streak == MIN_STREAK) {
> +			/* Test succeeded, no need to keep the captured data */
> +			unlink(dump_path);
> +		} else
> +			igt_debug("Saved captured audio data to %s\n", dump_path);
> +		free(dump_path);
> +	}
> +
> +	free(recv);
> +	free(buf);
> +	free(channel);
> +
> +	ok = chamelium_stream_stop_realtime_audio(stream);
> +	igt_assert(ok);
> +
> +	audio_file = chamelium_stop_capturing_audio(data->chamelium,
> +						    port);
> +	if (audio_file) {
> +		igt_debug("Audio file saved on the Chamelium in %s\n",
> +			  audio_file->path);
> +		chamelium_destroy_audio_file(audio_file);
> +	}

I would suggest to only dump this file on failure, not when having a
success.

> +
> +	audio_signal_clean(signal);
> +	free(signal);
> +
> +	chamelium_stream_deinit(stream);
> +
> +	igt_assert(streak == MIN_STREAK);
> +	return true;
> +}
> +
> +static void
> +test_display_audio(data_t *data, struct chamelium_port *port,
> +		   const char *audio_device)
> +{
> +	bool run = false;
> +	struct alsa *alsa;
> +	int ret;
> +	igt_output_t *output;
> +	igt_plane_t *primary;
> +	struct igt_fb fb;
> +	drmModeModeInfo *mode;
> +	drmModeConnector *connector;
> +	int fb_id, i;
> +
> +	igt_require(alsa_has_exclusive_access());
> +
> +	alsa = alsa_init();
> +	igt_assert(alsa);
> +
> +	reset_state(data, port);
> +
> +	/* Use the default Chamelium EDID for this test, as the base IGT EDID
> +	 * doesn't advertise audio support (see drm_detect_monitor_audio in
> +	 * the kernel tree). */
> +	output = prepare_output(data, port, false);
> +	connector = chamelium_port_get_connector(data->chamelium, port, false);
> +	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> +	igt_assert(primary);
> +
> +	igt_assert(connector->count_modes > 0);
> +	mode = &connector->modes[0];
> +
> +	fb_id = igt_create_color_pattern_fb(data->drm_fd,
> +					    mode->hdisplay, mode->vdisplay,
> +					    DRM_FORMAT_XRGB8888,
> +					    LOCAL_DRM_FORMAT_MOD_NONE,
> +					    0, 0, 0, &fb);
> +	igt_assert(fb_id > 0);
> +
> +	/* Enable the output because the receiver won't try to receive audio if
> +	 * it doesn't receive video. */
Maybe move the above comment to above igt_assert(connector->count_modes
> 0); ?
> +	enable_output(data, port, output, mode, &fb);
> +
> +	for (i = 0; i < sampling_rates_count; i++) {
> +		ret = alsa_open_output(alsa, audio_device);
> +		igt_assert(ret >= 0);
> +
> +		/* TODO: playback on all 8 available channels */
> +		run |= do_test_display_audio(data, port, alsa,
> +					     PLAYBACK_CHANNELS,
> +					     sampling_rates[i]);
> +
> +		alsa_close_output(alsa);
> +	}
> +
> +	/* Make sure we tested at least one frequency. */
> +	igt_assert(run);
> +
> +	igt_remove_fb(data->drm_fd, &fb);
> +
> +	drmModeFreeConnector(connector);
> +
> +	free(alsa);
> +}
> +
> +
>  static void select_tiled_modifier(igt_plane_t *plane, uint32_t width,
>  				  uint32_t height, uint32_t format,
>  				  uint64_t *modifier)
> @@ -1037,7 +1298,7 @@ static void test_display_planes_random(data_t *data,
>  	reset_state(data, port);
>  
>  	/* Find the connector and pipe. */
> -	output = prepare_output(data, port);
> +	output = prepare_output(data, port, true);
>  
>  	mode = igt_output_get_mode(output);
>  
> @@ -1308,6 +1569,9 @@ igt_main
>  
>  		connector_subtest("dp-frame-dump", DisplayPort)
>  			test_display_frame_dump(&data, port);
> +
> +		connector_subtest("dp-audio", DisplayPort)
> +			test_display_audio(&data, port, "HDMI");
>  	}
>  
>  	igt_subtest_group {
> diff --git a/tests/meson.build b/tests/meson.build
> index 5167a6cc..5d3eed82 100644
> --- a/tests/meson.build
> +++ b/tests/meson.build
> @@ -238,20 +238,13 @@ if libdrm_nouveau.found()
>  	test_deps += libdrm_nouveau
>  endif
>  
> -if _build_chamelium and chamelium.found()
> +if chamelium_found
>  	test_progs += [
>  		'kms_chamelium',
>  	]
>  	test_deps += chamelium
>  endif
>  
> -if _build_audio and alsa.found() and gsl.found()
> -	test_progs += [
> -		'audio',
> -	]
> -	test_deps += alsa
> -endif
> -
>  test_executables = []
>  test_list = []
>  
> 

Well... that was a mouthful! For the next patches, please split stuff
more aggressively, even when a patch would only be introducing dead code.

With these changed:

Reviewed-by: Martin Peres <martin.peres@linux.intel.com>

Martin
_______________________________________________
igt-dev mailing list
igt-dev@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/igt-dev

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [igt-dev] [PATCH i-g-t v4 1/5] tests/kms_chamelium: add dp-audio test
  2019-04-16 12:02   ` Martin Peres
@ 2019-04-17  8:40     ` Ser, Simon
  2019-04-17 12:17       ` Martin Peres
  0 siblings, 1 reply; 13+ messages in thread
From: Ser, Simon @ 2019-04-17  8:40 UTC (permalink / raw)
  To: igt-dev, martin.peres

On Tue, 2019-04-16 at 15:02 +0300, Martin Peres wrote:
> On 11/04/2019 15:36, Simon Ser wrote:
> > 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 depends on the following Chameleon bugs:
> > 
> > - https://crbug.com/948060
> > - https://crbug.com/950857
> 
> Seems like your fixes landed already! Great!
> 
> > Signed-off-by: Simon Ser <simon.ser@intel.com>
> > ---
> >  docs/audio.txt             |  45 ---
> >  docs/chamelium.txt         |  32 +-
> >  lib/igt.h                  |   1 +
> >  lib/igt_alsa.c             |  42 ++-
> >  lib/igt_alsa.h             |   1 +
> >  lib/igt_audio.c            | 285 ++++++++++++------
> >  lib/igt_audio.h            |  12 +-
> >  lib/igt_aux.c              |  31 ++
> >  lib/igt_aux.h              |   1 +
> >  lib/igt_chamelium.c        | 101 +++++++
> >  lib/igt_chamelium.h        |  11 +
> >  lib/igt_chamelium_stream.c | 592 +++++++++++++++++++++++++++++++++++++
> >  lib/igt_chamelium_stream.h |  52 ++++
> >  lib/meson.build            |   5 +-
> >  meson.build                |  52 ++--
> >  meson_options.txt          |   6 -
> >  tests/audio.c              | 193 ------------
> >  tests/kms_chamelium.c      | 276 ++++++++++++++++-
> >  tests/meson.build          |   9 +-
> >  19 files changed, 1358 insertions(+), 389 deletions(-)
> >  delete mode 100644 docs/audio.txt
> >  create mode 100644 lib/igt_chamelium_stream.c
> >  create mode 100644 lib/igt_chamelium_stream.h
> >  delete mode 100644 tests/audio.c
> > 
> > diff --git a/docs/audio.txt b/docs/audio.txt
> > deleted file mode 100644
> > index 158ad5d1..00000000
> > --- a/docs/audio.txt
> > +++ /dev/null
> > @@ -1,45 +0,0 @@
> > -Audio Support in IGT
> > -====================
> > -
> > -This document provides information and instructions about audio support in IGT.
> > -
> > -Introduction
> > -------------
> > -
> > -The audio test is aimed at testing the audio features of display connectors,
> > -such as HDMI.
> > -
> > -Test setup
> > -----------
> > -
> > -The setup required for the audio test consists of using an HDMI-VGA adapter with
> > -an audio-out 3.5 mm jack to extract the audio from the HDMI interface.
> > -The audio-out jack is connected back to the device-under-test's line-in.
> > -
> > -Depending on the behavior of the adapter, it may be necessary to connect a
> > -ghost VGA dongle to it (in order to emulate a connected display) to enable the
> > -audio output. There are guides available detailing how to build these.
> > -
> > -When executed, the test will automatically send the test audio signal to all
> > -ALSA audio HDMI outputs and record from the standard ALSA capture device.
> > -
> > -Configuration
> > --------------
> > -
> > -In order to deploy the test, ALSA controls have to be configured to set the
> > -ALSA capture source to line-in. On Intel x86 systems, this can be achieved
> > -with the following calls to the amixer utility:
> > -# amixer sset Line 31 on
> > -# amixer sset "Input Source" Line
> > -
> > -It is then useful to store the ALSA state permanently with the alsactl utility:
> > -# alsactl store
> > -
> > -These settings can be restored with the alsactl utility:
> > -# alsactl restore
> > -
> > -It is desirable to ensure that the alsa-restore and alsa-state systemd services
> > -are enabled to do this job automatically, especially in the case of an
> > -automated testing system:
> > -# systemctl enable alsa-restore
> > -# systemctl enable alsa-state
> > diff --git a/docs/chamelium.txt b/docs/chamelium.txt
> > index 0cabcdc6..316dd607 100644
> > --- a/docs/chamelium.txt
> > +++ b/docs/chamelium.txt
> > @@ -139,6 +139,23 @@ $ make remote-install CHAMELEON_HOST=192.168.72.1
> >  
> >  The process requires the Chamelium to be connected to the Internet to succeed.
> >  
> > +Audio Capture
> > +-------------
> > +
> > +The Chamelium supports audio capture. IGT tests take advantage of the
> > +Chamelium streaming server to download audio samples from the Chamelium.
> > +
> > +IGT needs direct access to audio devices through ALSA, so PulseAudio needs to
> > +be stopped (otherwise audio tests will automatically get skipped). To make sure
> > +PulseAudio isn't running:
> > +
> > +- Edit /etc/pulse/client.conf and add autospawn=no
> > +- Run `pulseaudio --kill` (if it succeeds, it means PulseAudio was running)
> > +- Make sure a DE that automatically spawns PulseAudio isn't running
> > +
> > +In case a test fails, the raw captured audio files will be dumped in a WAV
> > +file.
> > +
> >  Contributing Changes to the Daemon
> >  ----------------------------------
> >  
> > @@ -146,10 +163,11 @@ Contributions to the Chamelium daemon, just like any contribution to ChromiumOS,
> >  are submitted and reviewed at: https://chromium-review.googlesource.com/
> >  
> >  The ChromiumOS project provides an extensive developer guide:
> > -https://www.chromium.org/chromium-os/developer-guide that assumes running within
> > -the ChromiumOS build system. Since this is likely not the case for contributing
> > -to the Chamelium daemon, only the part about uploading changes is relevant:
> > -https://www.chromium.org/chromium-os/developer-guide#TOC-Upload-your-changes-and-get-a-code-review
> > +https://chromium.googlesource.com/chromiumos/docs/+/master/developer_guide.md
> > +It that assumes running within the ChromiumOS build system. Since this is
> 
> The word "that" seems out of place.
> 
> > +likely not the case for contributing to the Chamelium daemon, only the part
> > +about uploading changes is relevant:
> > +https://chromium.googlesource.com/chromiumos/docs/+/master/developer_guide.md#Upload-your-changes-and-get-a-code-review
> >  
> >  Most of the process is about using the Gerrit web interface for submitting and
> >  having the change reviewed and not forgetting the Change-Id, TEST= and BUG=
> > @@ -162,7 +180,7 @@ Support for the Chamelium platform in IGT is found in the following places:
> >  * lib/igt_chamelium.c: library with Chamelium-related helpers
> >  * tests/kms_chamelium.c: sub-tests using the Chamelium
> >  
> > -As of late August 2017, the following features are tested by IGT:
> > +As of early April 2019, the following features are tested by IGT:
> >  * Pixel-by-pixel frame integrity tests for DP and HDMI
> >  * Error-trend-based frame integrity tests for VGA
> >  * CRC-based frame integrity tests for DP and HDMI
> > @@ -173,6 +191,7 @@ As of late August 2017, the following features are tested by IGT:
> >    each interface or combined
> >  * EDID display identifier integrity check for all interfaces
> >  * EDID display identifier change during suspend for all interfaces
> > +* Audio Fourier-based tests for DP at 48KHz
> 
> Maybe an extra patch will be needed to update the list of
> capabilities... or we just implement them :p
> > 
> >  
> >  Future Developments
> >  -------------------
> > @@ -180,7 +199,8 @@ Future Developments
> >  With the current generation of the hardware platform, support for testing a
> >  number of additional display features could be included as future developments,
> >  including:
> > -* Audio capture from HDMI and DP
> > +* Audio capture from HDMI, with multiple channels and with other playback
> > +  sampling rates
> 
> Don't remove DP since it does not yet have the features you are talking
> about.

Hmm. The meaning of that comma was a logical "and". Instead, I'll just
split this into multiple bullet points.

> >  * High-bandwidth Digital Content Protection (HDCP) streaming to the display
> >  * Remote control forwarding (CEC) sent from the display
> >  * YUV colorspace for HDMI, instead of RGB
> > diff --git a/lib/igt.h b/lib/igt.h
> > index 6654a659..5852d557 100644
> > --- a/lib/igt.h
> > +++ b/lib/igt.h
> > @@ -43,6 +43,7 @@
> >  #include "igt_stats.h"
> >  #ifdef HAVE_CHAMELIUM
> >  #include "igt_chamelium.h"
> > +#include "igt_chamelium_stream.h"
> >  #endif
> >  #include "instdone.h"
> >  #include "intel_batchbuffer.h"
> > diff --git a/lib/igt_alsa.c b/lib/igt_alsa.c
> > index bb6682cc..456c0c85 100644
> > --- a/lib/igt_alsa.c
> > +++ b/lib/igt_alsa.c
> > @@ -26,9 +26,11 @@
> >  
> >  #include "config.h"
> >  
> > +#include <limits.h>
> >  #include <alsa/asoundlib.h>
> >  
> >  #include "igt_alsa.h"
> > +#include "igt_aux.h"
> >  #include "igt_core.h"
> >  
> >  #define HANDLES_MAX	8
> > @@ -61,6 +63,25 @@ struct alsa {
> >  	int input_samples_trigger;
> >  };
> >  
> > +/**
> > + * alsa_has_exclusive_access:
> > + * Check whether ALSA has exclusive access to audio devices. Fails if
> > + * PulseAudio is running.
> > + */
> > +bool alsa_has_exclusive_access(void)
> > +{
> > +	if (igt_is_process_running("pulseaudio")) {
> > +		igt_warn("It seems that PulseAudio is running. Audio tests "
> > +			 "need direct access to audio devices, so PulseAudio "
> > +			 "needs to be stopped. You can do so by running "
> > +			 "`pulseaudio --kill`. Also make sure to add "
> > +			 "autospawn=no to /etc/pulse/client.conf\n");
> > +		return false;
> > +	}
> > +
> > +	return true;
> > +}
> > +
> >  static void alsa_error_handler(const char *file, int line, const char *function,
> >  			       int err, const char *fmt, ...)
> >  {
> > @@ -78,6 +99,11 @@ struct alsa *alsa_init(void)
> >  {
> >  	struct alsa *alsa;
> >  
> > +	if (!alsa_has_exclusive_access()) {
> > +		igt_warn("alsa doesn't have exclusive access to audio devices\n");
> 
> How about folding the second warn in alsa_has_exclusive_access()? It
> would improve the chances of having a consistent string to grep on in
> cibuglog in case other users decide to use the function.
> 
> > +		return NULL;
> > +	}
> > +
> >  	alsa = malloc(sizeof(struct alsa));
> >  	memset(alsa, 0, sizeof(struct alsa));
> >  
> > @@ -553,16 +579,20 @@ int alsa_run(struct alsa *alsa, int duration_ms)
> >  					if (ret < 0) {
> >  						ret = snd_pcm_recover(handle,
> >  								      ret, 0);
> > -						if (ret < 0)
> > +						if (ret < 0) {
> > +							igt_debug("snd_pcm_recover after snd_pcm_writei failed");
> >  							goto complete;
> > +						}
> >  					}
> >  
> >  					output_counts[i] += ret;
> >  				} else if (output_counts[i] < output_trigger &&
> >  					   ret < 0) {
> >  					ret = snd_pcm_recover(handle, ret, 0);
> > -					if (ret < 0)
> > +					if (ret < 0) {
> > +						igt_debug("snd_pcm_recover failed");
> >  						goto complete;
> > +					}
> >  				}
> >  			}
> >  
> > @@ -609,16 +639,20 @@ int alsa_run(struct alsa *alsa, int duration_ms)
> >  					ret = 0;
> >  				} else if (ret < 0) {
> >  					ret = snd_pcm_recover(handle, ret, 0);
> > -					if (ret < 0)
> > +					if (ret < 0) {
> > +						igt_debug("snd_pcm_recover after snd_pcm_readi failed");
> >  						goto complete;
> > +					}
> >  				}
> >  
> >  				input_count += ret;
> >  				input_total += ret;
> >  			} else if (input_count < input_trigger && ret < 0) {
> >  				ret = snd_pcm_recover(handle, ret, 0);
> > -				if (ret < 0)
> > +				if (ret < 0) {
> > +					igt_debug("snd_pcm_recover failed");
> >  					goto complete;
> > +				}
> >  			}
> >  		}
> >  	} while (!reached);
> > diff --git a/lib/igt_alsa.h b/lib/igt_alsa.h
> > index 50795130..5c804b46 100644
> > --- a/lib/igt_alsa.h
> > +++ b/lib/igt_alsa.h
> > @@ -33,6 +33,7 @@
> >  
> >  struct alsa;
> >  
> > +bool alsa_has_exclusive_access(void);
> >  struct alsa *alsa_init(void);
> >  int alsa_open_output(struct alsa *alsa, const char *device_name);
> >  int alsa_open_input(struct alsa *alsa, const char *device_name);
> > diff --git a/lib/igt_audio.c b/lib/igt_audio.c
> > index a0592d53..4cc9bdf0 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;
> 
> Seems like all these alsa changes should be in their own patches so as
> you could explain why these changes are needed.
> 
> Are you afraid that some platforms would not have SHORT == INT16?
> 
> If these changes would have required changes in code that you would
> change anyway right after, then at least explain the changes in the
> commit log :)

Yeah, sorry about this. I just completely forgot about this change when
writing the commit log. TBH when writing this patch I was mainly
concerned about getting something working _at all_, and it took quite a
few days. :P

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.

I can extract these changes to an individual commit if needed.

> >  		}
> >  
> >  		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,214 @@ 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 amplitude_len = data_len / 2 + 1;
> > +	double amplitude[amplitude_len];
> 
> bin_power?

Hmm, "amplitude" is used too it seems [1]. But bin_power LGTM, let's
switch to this wording.

[1]: https://en.wikipedia.org/wiki/Fast_Fourier_transform#/media/File:FFT_of_Cosine_Summation_Function.png

> >  	bool detected[signal->freqs_count];
> > -	int threshold;
> > -	bool above;
> > -	int error;
> > -	int freq = 0;
> > -	int max;
> > -	int c, i, j;
> > -
> > -	/* Allowed error in Hz due to FFT step. */
> > -	error = sampling_rate / frames;
> > +	int ret, epsilon, freq, max_freq;
> > +	double max, threshold;
> > +	size_t i, j;
> > +	bool above, success;
> > +
> > +	/* Allowed error in Hz due to FFT step */
> > +	epsilon = sampling_rate / data_len;
> 
> freq_accuracy would be a nicer name.

Agreed.

> > +	igt_debug("allowed freq. error: %d Hz\n", epsilon);
> > +
> > +	ret = gsl_fft_real_radix2_transform(data, 1, data_len);
> > +	igt_assert(ret == 0);
> > +
> > +	/* 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 amplitude is encoded as the magnitude of the complex number and
> > +	 * the phase is encoded as its angle.
> > +	 */
> 
> Thanks for documenting the idiosyncrasies of GSL!
> 
> /* compute the power received at every bin of the FFT, and record the
>  * maximum power received as a way to normalize all the others.
>  */
> 
> > +	max = 0;
> > +	amplitude[0] = data[0];
> > +	for (i = 1; i < amplitude_len - 1; i++) {
> > +		amplitude[i] = hypot(data[i], data[data_len - i]);
> > +		if (amplitude[i] > max)
> > +			max = amplitude[i];
> > +	}
> > +	amplitude[amplitude_len - 1] = data[data_len / 2];
> 
> What's the coding style of IGT wrt spaces vs tabs?

Always use tabs? IDGI, I only see tabs in there. I'm probably missing
something.

> >  
> > -	for (c = 0; c < channels; c++) {
> > -		for (i = 0; i < frames; i++)
> > -			data[i] = (double) buffer[i * channels + c];
> > +	for (i = 0; i < signal->freqs_count; i++)
> > +		detected[i] = false;
> 
> This could have been a bitfield, but meh!

Right. Still have to be a little careful because in the future we'll
probably test 8 channels with a couple of different frequencies on each
(we test 5 frequencies ATM, so that would be 40 total), so it could
somewhat approach a 64 frequencies limit.

> >  
> > -		gsl_fft_real_radix2_transform(data, 1, frames);
> > +	/* We want to detect peaks above a given threshold. */
> 
> /* 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;
> > +	max = 0;
> > +	max_freq = -1;
> 
> local_max = 0;
> local_max_freq = -1;

That's better indeed.

> > +	for (i = 0; i < amplitude_len; i++) {
> > +		freq = sampling_rate * i / data_len;
> >  
> > -		max = 0;
> > +		if (amplitude[i] > threshold)
> > +			above = true;
> >  
> > -		for (i = 0; i < frames / 2; i++) {
> > -			amplitude[i] = hypot(data[i], data[frames - i]);
> > -			if (amplitude[i] > max)
> > -				max = amplitude[i];
> > +		if (!above) {
> > +			continue;
> >  		}
> >  
> > -		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;
> > -					}
> > +		/* 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 (amplitude[i] < threshold) {
> > +			for (j = 0; j < signal->freqs_count; j++) {
> > +				if (signal->freqs[j].freq >
> > +				    max_freq - epsilon &&
> > +				    signal->freqs[j].freq <
> > +				    max_freq + epsilon) {
> > +					detected[j] = true;
> > +					igt_debug("Frequency %d detected\n",
> > +						  max_freq);
> > +					break;
> >  				}
> > +			}
> >  
> > -				if (amplitude[i] > max) {
> > -					max = amplitude[i];
> > -					freq = sampling_rate * i / frames;
> > -				}
> > +			/* We haven't generated this frequency, but we detected
> > +			 * it. */
> > +			if (j == signal->freqs_count) {
> > +				igt_debug("Detected additional frequency: %d\n",
> > +					  max_freq);
> > +				success = false;
> >  			}
> > +
> > +			above = false;
> > +			max = 0;
> > +			max_freq = -1;
> >  		}
> >  
> > -		for (i = 0; i < signal->freqs_count; i++) {
> > -			if (!detected[i]) {
> > -				igt_debug("Missing frequency: %d\n",
> > -					  signal->freqs[i].freq);
> > -				return false;
> > -			}
> > +		if (amplitude[i] > max) {
> > +			max = amplitude[i];
> > +			max_freq = freq;
> > +		}
> > +	}
> > +
> > +	/* 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;
> >  		}
> >  	}
> >  
> > -	return true;
> > +	return success;
> > +}
> > +
> > +/**
> > + * 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;
> > +
> > +	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;
> > +}
> > +
> > +/**
> > + * Creates a new WAV file. sample_rate is in Hz. If path is not NULL, it will
> > + * be set to the new file path (the caller is responsible for free-ing it).
> > + *
> > + * 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.
> > + */
> > +int audio_create_wav_file_s32_le(const char *qualifier, uint32_t sample_rate,
> > +				 uint16_t channels, char **path)
> 
> What is qualifier?

This is taken from igt_write_frame_to_png. I guess this could use some
more docs.

/**
 * 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.
 */

> Maybe we can rename that to channel_count?

I want to shove in the playback frequency too in the future, so I'd
rather not.

> > +{
> > +	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 fd;
> >  }
> > diff --git a/lib/igt_audio.h b/lib/igt_audio.h
> > index b3b658a4..4aa43e69 100644
> > --- a/lib/igt_audio.h
> > +++ b/lib/igt_audio.h
> > @@ -30,6 +30,7 @@
> >  #include "config.h"
> >  
> >  #include <stdbool.h>
> > +#include <stdint.h>
> >  
> >  struct audio_signal;
> >  
> > @@ -37,8 +38,13 @@ struct audio_signal *audio_signal_init(int channels, int sampling_rate);
> >  int audio_signal_add_frequency(struct audio_signal *signal, int frequency);
> >  void audio_signal_synthesize(struct audio_signal *signal);
> >  void audio_signal_clean(struct audio_signal *signal);
> > -void audio_signal_fill(struct audio_signal *signal, short *buffer, int frames);
> > -bool audio_signal_detect(struct audio_signal *signal, int channels,
> > -			 int sampling_rate, short *buffer, int frames);
> > +void audio_signal_fill(struct audio_signal *signal, int16_t *buffer, int frames);
> > +bool audio_signal_detect(struct audio_signal *signal, int sampling_rate,
> > +			 double *data, size_t data_len);
> > +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);
> > +int audio_create_wav_file_s32_le(const char *qualifier, uint32_t sample_rate,
> > +				 uint16_t channels, char **path);
> >  
> >  #endif
> > diff --git a/lib/igt_aux.c b/lib/igt_aux.c
> > index 05528352..95ceb845 100644
> > --- a/lib/igt_aux.c
> > +++ b/lib/igt_aux.c
> > @@ -1259,6 +1259,37 @@ void igt_set_module_param_int(const char *name, int val)
> >  	igt_set_module_param(name, str);
> >  }
> >  
> > +/**
> > + * igt_is_process_running:
> > + * @comm: Name of process in the form found in /proc/pid/comm (limited to 15
> > + * chars)
> > + *
> > + * Returns: true in case the process has been found, false otherwise.
> > + *
> > + * This function checks in the process table for an entry with the name @comm.
> > + */
> > +int igt_is_process_running(const char *comm)
> > +{
> > +	PROCTAB *proc;
> > +	proc_t *proc_info;
> > +	bool found = false;
> > +
> > +	proc = openproc(PROC_FILLCOM | PROC_FILLSTAT | PROC_FILLARG);
> 
> Seems like you can drop FILLARG:
> 
> From man:
>        PROC_FILLARG
>             equivalent to PROC_FILLCOM

Good catch

> > +	igt_assert(proc != NULL);
> > +
> > +	while ((proc_info = readproc(proc, NULL))) {
> > +		if (!strncasecmp(proc_info->cmd, comm, sizeof(proc_info->cmd))) {
> > +			freeproc(proc_info);
> > +			found = true;
> > +			break;
> > +		}
> > +		freeproc(proc_info);
> > +	}
> > +
> > +	closeproc(proc);
> > +	return found;
> > +}
> > +
> >  /**
> >   * igt_terminate_process:
> >   * @sig: Signal to send
> > diff --git a/lib/igt_aux.h b/lib/igt_aux.h
> > index 55392790..dbd88b67 100644
> > --- a/lib/igt_aux.h
> > +++ b/lib/igt_aux.h
> > @@ -279,6 +279,7 @@ bool igt_allow_unlimited_files(void);
> >  void igt_set_module_param(const char *name, const char *val);
> >  void igt_set_module_param_int(const char *name, int val);
> >  
> > +int igt_is_process_running(const char *comm);
> >  int igt_terminate_process(int sig, const char *comm);
> >  void igt_lsof(const char *dpath);
> >  
> > diff --git a/lib/igt_chamelium.c b/lib/igt_chamelium.c
> > index 02cc9b2c..7c9030d1 100644
> > --- a/lib/igt_chamelium.c
> > +++ b/lib/igt_chamelium.c
> > @@ -218,6 +218,12 @@ void chamelium_destroy_frame_dump(struct chamelium_frame_dump *dump)
> >  	free(dump);
> >  }
> >  
> > +void chamelium_destroy_audio_file(struct chamelium_audio_file *audio_file)
> > +{
> > +	free(audio_file->path);
> > +	free(audio_file);
> > +}
> > +
> >  struct fsm_monitor_args {
> >  	struct chamelium *chamelium;
> >  	struct chamelium_port *port;
> > @@ -924,6 +930,101 @@ int chamelium_get_captured_frame_count(struct chamelium *chamelium)
> >  	return ret;
> >  }
> >  
> > +/**
> > + * chamelium_start_capturing_audio:
> > + * @chamelium: the Chamelium instance
> > + * @port: the port to capture audio from (it must support audio)
> > + * @save_to_file: whether the captured audio data should be saved to a file on
> > + * the Chamelium device
> > + *
> > + * Starts capturing audio from a Chamelium port. To stop the capture, use
> > + * #chamelium_stop_capturing_audio. To retrieve the audio data, either use the
> > + * stream server or enable @save_to_file (the latter is mainly useful for
> > + * debugging purposes).
> > + *
> > + * It isn't possible to capture audio from multiple ports at the same time.
> > + */
> > +void chamelium_start_capturing_audio(struct chamelium *chamelium,
> > +				    struct chamelium_port *port,
> > +				    bool save_to_file)
> > +{
> > +	xmlrpc_value *res;
> > +
> > +	res = chamelium_rpc(chamelium, port, "StartCapturingAudio", "(ib)",
> > +			    port->id, save_to_file);
> > +	xmlrpc_DECREF(res);
> > +}
> > +
> > +static void audio_format_from_xml(struct chamelium *chamelium,
> > +				  xmlrpc_value *res, int *rate, int *channels)
> > +{
> > +	xmlrpc_value *res_type, *res_rate, *res_sample_format, *res_channel;
> > +	char *type, *sample_format;
> > +
> > +	xmlrpc_struct_find_value(&chamelium->env, res, "file_type", &res_type);
> > +	xmlrpc_struct_find_value(&chamelium->env, res, "rate", &res_rate);
> > +	xmlrpc_struct_find_value(&chamelium->env, res, "sample_format", &res_sample_format);
> > +	xmlrpc_struct_find_value(&chamelium->env, res, "channel", &res_channel);
> > +
> > +	xmlrpc_read_string(&chamelium->env, res_type, (const char **) &type);
> > +	igt_assert(strcmp(type, "raw") == 0);
> > +	free(type);
> > +
> > +	xmlrpc_read_string(&chamelium->env, res_sample_format, (const char **) &sample_format);
> > +	igt_assert(strcmp(sample_format, "S32_LE") == 0);
> > +	free(sample_format);
> > +
> > +	xmlrpc_read_int(&chamelium->env, res_rate, rate);
> > +	xmlrpc_read_int(&chamelium->env, res_channel, channels);
> > +
> > +	xmlrpc_DECREF(res_channel);
> > +	xmlrpc_DECREF(res_sample_format);
> > +	xmlrpc_DECREF(res_rate);
> > +	xmlrpc_DECREF(res_type);
> > +}
> > +
> > +/**
> > + * chamelium_stop_capturing_audio:
> > + * @chamelium: the Chamelium instance
> > + * @port: the port from which audio is being captured
> > + *
> > + * Stops capturing audio from a Chamelium port. If
> > + * #chamelium_start_capturing_audio has been called with @save_to_file enabled,
> > + * this function will return a #chamelium_audio_file struct containing details
> > + * about the audio file. Once the caller is done with the struct, they should
> > + * release it with #chamelium_destroy_audio_file.
> > + */
> > +struct chamelium_audio_file *chamelium_stop_capturing_audio(struct chamelium *chamelium,
> > +							    struct chamelium_port *port)
> > +{
> > +	xmlrpc_value *res, *res_path, *res_props;
> > +	struct chamelium_audio_file *file = NULL;
> > +	char *path;
> > +
> > +	res = chamelium_rpc(chamelium, NULL, "StopCapturingAudio", "(i)",
> > +			    port->id);
> > +	xmlrpc_array_read_item(&chamelium->env, res, 0, &res_path);
> > +	xmlrpc_array_read_item(&chamelium->env, res, 1, &res_props);
> > +
> > +	xmlrpc_read_string(&chamelium->env, res_path, (const char **) &path);
> > +
> > +	if (strlen(path) > 0) {
> > +		file = calloc(1, sizeof(*file));
> > +		file->path = path;
> > +
> > +		audio_format_from_xml(chamelium, res_props,
> > +				      &file->rate, &file->channels);
> > +	} else {
> > +		free(path);
> > +	}
> > +
> > +	xmlrpc_DECREF(res_props);
> > +	xmlrpc_DECREF(res_path);
> > +	xmlrpc_DECREF(res);
> > +
> > +	return file;
> > +}
> > +
> >  static pixman_image_t *convert_frame_format(pixman_image_t *src,
> >  					    int format)
> >  {
> > diff --git a/lib/igt_chamelium.h b/lib/igt_chamelium.h
> > index 233ead85..047f8c5d 100644
> > --- a/lib/igt_chamelium.h
> > +++ b/lib/igt_chamelium.h
> > @@ -53,6 +53,12 @@ enum chamelium_check {
> >  	CHAMELIUM_CHECK_CRC,
> >  };
> >  
> > +struct chamelium_audio_file {
> > +	char *path;
> > +	int rate; /* Hz */
> > +	int channels;
> > +};
> > +
> >  struct chamelium *chamelium_init(int drm_fd);
> >  void chamelium_deinit(struct chamelium *chamelium);
> >  void chamelium_reset(struct chamelium *chamelium);
> > @@ -100,6 +106,10 @@ void chamelium_start_capture(struct chamelium *chamelium,
> >  void chamelium_stop_capture(struct chamelium *chamelium, int frame_count);
> >  void chamelium_capture(struct chamelium *chamelium, struct chamelium_port *port,
> >  		       int x, int y, int w, int h, int frame_count);
> > +void chamelium_start_capturing_audio(struct chamelium *chamelium,
> > +				    struct chamelium_port *port, bool save_to_file);
> > +struct chamelium_audio_file *chamelium_stop_capturing_audio(struct chamelium *chamelium,
> > +							    struct chamelium_port *port);
> >  igt_crc_t *chamelium_read_captured_crcs(struct chamelium *chamelium,
> >  					int *frame_count);
> >  struct chamelium_frame_dump *chamelium_read_captured_frame(struct chamelium *chamelium,
> > @@ -131,5 +141,6 @@ void chamelium_assert_frame_match_or_dump(struct chamelium *chamelium,
> >  void chamelium_crop_analog_frame(struct chamelium_frame_dump *dump, int width,
> >  				 int height);
> >  void chamelium_destroy_frame_dump(struct chamelium_frame_dump *dump);
> > +void chamelium_destroy_audio_file(struct chamelium_audio_file *audio_file);
> >  
> >  #endif /* IGT_CHAMELIUM_H */
> > diff --git a/lib/igt_chamelium_stream.c b/lib/igt_chamelium_stream.c
> > new file mode 100644
> > index 00000000..9e1ba1ca
> > --- /dev/null
> > +++ b/lib/igt_chamelium_stream.c
> > @@ -0,0 +1,592 @@
> > +/*
> > + * Copyright © 2019 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: Simon Ser <simon.ser@intel.com>
> > + */
> > +
> > +#include "config.h"
> > +
> > +#include <arpa/inet.h>
> > +#include <errno.h>
> > +#include <netdb.h>
> > +#include <stdbool.h>
> > +#include <stdlib.h>
> > +#include <sys/types.h>
> > +#include <sys/socket.h>
> > +
> > +#include "igt_chamelium_stream.h"
> > +#include "igt_core.h"
> > +#include "igt_rc.h"
> > +
> > +#define STREAM_PORT 9994
> > +#define STREAM_VERSION_MAJOR 1
> > +#define STREAM_VERSION_MINOR 0
> > +
> > +enum stream_error {
> > +	STREAM_ERROR_NONE = 0,
> > +	STREAM_ERROR_COMMAND = 1,
> > +	STREAM_ERROR_ARGUMENT = 2,
> > +	STREAM_ERROR_EXISTS = 3,
> > +	STREAM_ERROR_VIDEO_MEM_OVERFLOW_STOP = 4,
> > +	STREAM_ERROR_VIDEO_MEM_OVERFLOW_DROP = 5,
> > +	STREAM_ERROR_AUDIO_MEM_OVERFLOW_STOP = 6,
> > +	STREAM_ERROR_AUDIO_MEM_OVERFLOW_DROP = 7,
> > +	STREAM_ERROR_NO_MEM = 8,
> > +};
> > +
> > +enum stream_message_kind {
> > +	STREAM_MESSAGE_REQUEST = 0,
> > +	STREAM_MESSAGE_RESPONSE = 1,
> > +	STREAM_MESSAGE_DATA = 2,
> > +};
> > +
> > +enum stream_message_type {
> > +	STREAM_MESSAGE_RESET = 0,
> > +	STREAM_MESSAGE_GET_VERSION = 1,
> > +	STREAM_MESSAGE_VIDEO_STREAM = 2,
> > +	STREAM_MESSAGE_SHRINK_VIDEO = 3,
> > +	STREAM_MESSAGE_VIDEO_FRAME = 4,
> > +	STREAM_MESSAGE_DUMP_REALTIME_VIDEO = 5,
> > +	STREAM_MESSAGE_STOP_DUMP_VIDEO = 6,
> > +	STREAM_MESSAGE_DUMP_REALTIME_AUDIO = 7,
> > +	STREAM_MESSAGE_STOP_DUMP_AUDIO = 8,
> > +};
> > +
> > +struct chamelium_stream {
> > +	char *host;
> > +	unsigned int port;
> > +
> > +	int fd;
> > +};
> > +
> > +static const char *stream_error_str(enum stream_error err)
> > +{
> > +	switch (err) {
> > +	case STREAM_ERROR_NONE:
> > +		return "no error";
> > +	case STREAM_ERROR_COMMAND:
> > +		return "invalid command";
> > +	case STREAM_ERROR_ARGUMENT:
> > +		return "invalid arguments";
> > +	case STREAM_ERROR_EXISTS:
> > +		return "dump already started";
> > +	case STREAM_ERROR_VIDEO_MEM_OVERFLOW_STOP:
> > +		return "video dump stopped after overflow";
> > +	case STREAM_ERROR_VIDEO_MEM_OVERFLOW_DROP:
> > +		return "video frame dropped after overflow";
> > +	case STREAM_ERROR_AUDIO_MEM_OVERFLOW_STOP:
> > +		return "audio dump stoppred after overflow";
> > +	case STREAM_ERROR_AUDIO_MEM_OVERFLOW_DROP:
> > +		return "audio page dropped after overflow";
> > +	case STREAM_ERROR_NO_MEM:
> > +		return "out of memory";
> > +	}
> > +	return "unknown error";
> > +}
> > +
> > +/**
> > + * The Chamelium URL is specified in the configuration file. We need to extract
> > + * the host to connect to the stream server.
> > + */
> > +static char *parse_url_host(const char *url)
> > +{
> > +	static const char prefix[] = "http://";
> > +	char *colon;
> > +
> > +	if (strstr(url, prefix) != url)
> > +		return NULL;
> > +	url += strlen(prefix);
> > +
> > +	colon = strchr(url, ':');
> > +	if (!colon)
> > +		return NULL;
> > +
> > +	return strndup(url, colon - url);
> > +}
> > +
> > +static bool chamelium_stream_read_config(struct chamelium_stream *client)
> > +{
> > +	GError *error = NULL;
> > +	gchar *chamelium_url;
> > +
> > +	if (!igt_key_file) {
> > +		igt_warn("No configuration file available for chamelium\n");
> > +		return false;
> > +	}
> > +
> > +	chamelium_url = g_key_file_get_string(igt_key_file, "Chamelium", "URL",
> > +					      &error);
> > +	if (!chamelium_url) {
> > +		igt_warn("Couldn't read Chamelium URL from config file: %s\n",
> > +			 error->message);
> > +		return false;
> > +	}
> > +
> > +	client->host = parse_url_host(chamelium_url);
> > +	if (!client->host) {
> > +		igt_warn("Invalid Chamelium URL in config file: %s\n",
> > +			 chamelium_url);
> > +		return false;
> > +	}
> > +	client->port = STREAM_PORT;
> > +
> > +	return true;
> > +}
> > +
> > +static bool chamelium_stream_connect(struct chamelium_stream *client)
> > +{
> > +	int ret;
> > +	char port_str[16];
> > +	struct addrinfo hints = {};
> > +	struct addrinfo *results, *ai;
> > +	struct timeval tv = {};
> > +
> > +	igt_debug("Connecting to Chamelium stream server: tcp://%s:%u\n",
> > +		  client->host, client->port);
> > +
> > +	snprintf(port_str, sizeof(port_str), "%u", client->port);
> > +
> > +	hints.ai_family = AF_UNSPEC;
> > +	hints.ai_socktype = SOCK_STREAM;
> > +	ret = getaddrinfo(client->host, port_str, &hints, &results);
> > +	if (ret != 0) {
> > +		igt_warn("getaddrinfo failed: %s\n", gai_strerror(ret));
> > +		return false;
> > +	}
> > +
> > +	client->fd = -1;
> > +	for (ai = results; ai != NULL; ai = ai->ai_next) {
> > +		client->fd = socket(ai->ai_family, ai->ai_socktype,
> > +				    ai->ai_protocol);
> > +		if (client->fd == -1)
> > +			continue;
> > +
> > +		if (connect(client->fd, ai->ai_addr, ai->ai_addrlen) == -1) {
> > +			close(client->fd);
> > +			client->fd = -1;
> > +			continue;
> > +		}
> > +
> > +		break;
> > +	}
> > +
> > +	freeaddrinfo(results);
> > +
> > +	if (client->fd < 0) {
> > +		igt_warn("Failed to connect to Chamelium stream server\n");
> > +		return false;
> > +	}
> > +
> > +	/* Set a read and write timeout of 5 seconds. */
> > +	tv.tv_sec = 5;
> > +	setsockopt(client->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
> > +	setsockopt(client->fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
> > +
> > +	return true;
> > +}
> > +
> > +static bool read_whole(int fd, void *buf, size_t buf_len)
> > +{
> > +	ssize_t ret;
> > +	size_t n = 0;
> > +	char *ptr;
> > +
> > +	while (n < buf_len) {
> > +		ptr = (char *) buf + n;
> > +		ret = read(fd, ptr, buf_len - n);
> > +		if (ret < 0) {
> > +			igt_warn("read failed: %s\n", strerror(errno));
> > +			return false;
> > +		} else if (ret == 0) {
> > +			igt_warn("short read\n");
> > +			return false;
> > +		}
> > +		n += ret;
> > +	}
> > +
> > +	return true;
> > +}
> > +
> > +static bool write_whole(int fd, void *buf, size_t buf_len)
> > +{
> > +	ssize_t ret;
> > +	size_t n = 0;
> > +	char *ptr;
> > +
> > +	while (n < buf_len) {
> > +		ptr = (char *) buf + n;
> > +		ret = write(fd, ptr, buf_len - n);
> > +		if (ret < 0) {
> > +			igt_warn("write failed: %s\n", strerror(errno));
> > +			return false;
> > +		} else if (ret == 0) {
> > +			igt_warn("short write\n");
> > +			return false;
> > +		}
> > +		n += ret;
> > +	}
> > +
> > +	return true;
> > +}
> > +
> > +static bool read_and_discard(int fd, size_t len)
> > +{
> > +	char buf[1024];
> > +	size_t n;
> > +
> > +	while (len > 0) {
> > +		n = len;
> > +		if (n > sizeof(buf))
> > +			n = sizeof(buf);
> > +
> > +		if (!read_whole(fd, buf, n))
> > +			return false;
> > +
> > +		len -= n;
> > +	}
> > +
> > +	return true;
> > +}
> > +
> > +/** Read a message header from the socket.
> > + *
> > + * The header is laid out as follows:
> > + * - u16: message type
> > + * - u16: error code
> > + * - u32: message length
> > + */
> > +static bool chamelium_stream_read_header(struct chamelium_stream *client,
> > +					 enum stream_message_kind *kind,
> > +					 enum stream_message_type *type,
> > +					 enum stream_error *err,
> > +					 size_t *len)
> > +{
> > +	uint16_t _type;
> > +	char buf[8];
> > +
> > +	if (!read_whole(client->fd, buf, sizeof(buf)))
> > +		return false;
> > +
> > +	_type = ntohs(*(uint16_t *) &buf[0]);
> > +	*type = _type & 0xFF;
> > +	*kind = _type >> 8;
> > +	*err = ntohs(*(uint16_t *) &buf[2]);
> > +	*len = ntohl(*(uint32_t *) &buf[4]);
> > +
> > +	//igt_debug("received message: kind=%d type=%d err=%d len=%zu\n",
> > +	//	  *kind, *type, *err, *len);
> 
> You probably want to keep this debug message. If not, then comment it
> properly with /* */ or delete it.

Ah, seems like I forgot to remove it. I don't think we want to keep it,
because it's extremely chatty when receiving audio pages.

> > +
> > +	return true;
> > +}
> > +
> > +static bool chamelium_stream_write_header(struct chamelium_stream *client,
> > +					  enum stream_message_type type,
> > +					  enum stream_error err,
> > +					  size_t len)
> > +{
> > +	char buf[8];
> > +	uint16_t _type;
> > +
> > +	_type = type | (STREAM_MESSAGE_REQUEST << 8);
> > +
> > +	*(uint16_t *) &buf[0] = htons(_type);
> > +	*(uint16_t *) &buf[2] = htons(err);
> > +	*(uint32_t *) &buf[4] = htonl(len);
> > +
> > +	return write_whole(client->fd, buf, sizeof(buf));
> > +}
> > +
> > +static bool chamelium_stream_read_response(struct chamelium_stream *client,
> > +					   enum stream_message_type type,
> > +					   void *buf, size_t buf_len)
> > +{
> > +	enum stream_message_kind read_kind;
> > +	enum stream_message_type read_type;
> > +	enum stream_error read_err;
> > +	size_t read_len;
> > +
> > +	if (!chamelium_stream_read_header(client, &read_kind, &read_type,
> > +					  &read_err, &read_len))
> > +		return false;
> > +
> > +	if (read_kind != STREAM_MESSAGE_RESPONSE) {
> > +		igt_warn("Expected a response, got kind %d\n", read_kind);
> > +		return false;
> > +	}
> > +	if (read_type != type) {
> > +		igt_warn("Expected message type %d, got %d\n",
> > +			 type, read_type);
> > +		return false;
> > +	}
> > +	if (read_err != STREAM_ERROR_NONE) {
> > +		igt_warn("Received error: %s (%d)\n",
> > +			 stream_error_str(read_err), read_err);
> > +		return false;
> > +	}
> > +	if (buf_len != read_len) {
> > +		igt_warn("Received invalid message body size "
> > +			 "(got %zu bytes, want %zu bytes)\n",
> > +			 read_len, buf_len);
> > +		return false;
> > +	}
> > +
> > +	return read_whole(client->fd, buf, buf_len);
> > +}
> > +
> > +static bool chamelium_stream_write_request(struct chamelium_stream *client,
> > +					   enum stream_message_type type,
> > +					   void *buf, size_t buf_len)
> > +{
> > +	if (!chamelium_stream_write_header(client, type, STREAM_ERROR_NONE,
> > +					   buf_len))
> > +		return false;
> > +
> > +	if (buf_len == 0)
> > +		return true;
> > +
> > +	return write_whole(client->fd, buf, buf_len);
> > +}
> > +
> > +static bool chamelium_stream_call(struct chamelium_stream *client,
> > +				  enum stream_message_type type,
> > +				  void *req_buf, size_t req_len,
> > +				  void *resp_buf, size_t resp_len)
> > +{
> > +	if (!chamelium_stream_write_request(client, type, req_buf, req_len))
> > +		return false;
> > +
> > +	return chamelium_stream_read_response(client, type, resp_buf, resp_len);
> > +}
> > +
> > +static bool chamelium_stream_check_version(struct chamelium_stream *client)
> > +{
> > +	char resp[2];
> > +	uint8_t major, minor;
> > +
> > +	if (!chamelium_stream_call(client, STREAM_MESSAGE_GET_VERSION,
> > +				   NULL, 0, resp, sizeof(resp)))
> > +		return false;
> > +
> > +	major = resp[0];
> > +	minor = resp[1];
> > +	if (major != STREAM_VERSION_MAJOR || minor < STREAM_VERSION_MINOR) {
> > +		igt_warn("Version mismatch (want %d.%d, got %d.%d)\n",
> > +			 STREAM_VERSION_MAJOR, STREAM_VERSION_MINOR,
> > +			 major, minor);
> > +		return false;
> > +	}
> > +
> > +	return true;
> > +}
> > +
> > +/**
> > + * chamelium_stream_dump_realtime_audio:
> > + *
> > + * Starts audio capture. The caller can then call
> > + * #chamelium_stream_receive_realtime_audio to receive audio pages.
> > + */
> > +bool chamelium_stream_dump_realtime_audio(struct chamelium_stream *client,
> > +					  enum chamelium_stream_realtime_mode mode)
> > +{
> > +	char req[1];
> > +
> > +	igt_debug("Starting real-time audio capture\n");
> > +
> > +	req[0] = mode;
> > +	return chamelium_stream_call(client, STREAM_MESSAGE_DUMP_REALTIME_AUDIO,
> > +				     req, sizeof(req), NULL, 0);
> > +}
> > +
> > +/**
> > + * chamelium_stream_receive_realtime_audio:
> > + * @page_count: if non-NULL, will be set to the dumped page number
> > + * @buf: must either point to a dynamically allocated memory region or NULL
> > + * @buf_len: number of elements of *@buf, for zero if @buf is NULL
> > + *
> > + * Receives one audio page from the streaming server.
> > + *
> > + * In "best effort" mode, some pages can be dropped. This can be detected via
> > + * the page count.
> > + *
> > + * buf_len will be set to the size of the page. The caller is responsible for
> > + * calling free(3) on *buf.
> > + */
> > +bool chamelium_stream_receive_realtime_audio(struct chamelium_stream *client,
> > +					     size_t *page_count,
> > +					     int32_t **buf, size_t *buf_len)
> > +{
> > +	enum stream_message_kind kind;
> > +	enum stream_message_type type;
> > +	enum stream_error err;
> > +	size_t body_len;
> > +	char page_count_buf[4];
> > +	int32_t *ptr;
> > +
> > +	while (true) {
> > +		if (!chamelium_stream_read_header(client, &kind, &type,
> > +						  &err, &body_len))
> > +			return false;
> > +
> > +		if (kind != STREAM_MESSAGE_DATA) {
> > +			igt_warn("Expected a data message, got kind %d\n", kind);
> > +			return false;
> > +		}
> > +		if (type != STREAM_MESSAGE_DUMP_REALTIME_AUDIO) {
> > +			igt_warn("Expected real-time audio dump message, "
> > +				 "got type %d\n", type);
> > +			return false;
> > +		}
> > +
> > +		if (err == STREAM_ERROR_NONE)
> > +			break;
> > +		else if (err != STREAM_ERROR_AUDIO_MEM_OVERFLOW_DROP) {
> > +			igt_warn("Received error: %s (%d)\n",
> > +				 stream_error_str(err), err);
> > +			return false;
> > +		}
> > +
> > +		igt_debug("Dropped an audio page because of an overflow\n");
> > +		igt_assert(body_len == 0);
> > +	}
> > +
> > +	igt_assert(body_len >= sizeof(page_count_buf));
> > +
> > +	if (!read_whole(client->fd, page_count_buf, sizeof(page_count_buf)))
> > +		return false;
> > +	if (page_count)
> > +		*page_count = ntohl(*(uint32_t *) &page_count_buf[0]);
> > +	body_len -= sizeof(page_count_buf);
> > +
> > +	igt_assert(body_len % sizeof(int32_t) == 0);
> > +	if (*buf_len * sizeof(int32_t) != body_len) {
> > +		ptr = realloc(*buf, body_len);
> > +		if (!ptr) {
> > +			igt_warn("realloc failed: %s\n", strerror(errno));
> > +			return false;
> > +		}
> > +		*buf = ptr;
> > +		*buf_len = body_len / sizeof(int32_t);
> > +	}
> > +
> > +	return read_whole(client->fd, *buf, body_len);
> > +}
> > +
> > +/**
> > + * chamelium_stream_stop_realtime_audio:
> > + *
> > + * Stops real-time audio capture. This also drops any buffered audio pages.
> > + * The caller shouldn't call #chamelium_stream_receive_realtime_audio after
> > + * stopping audio capture.
> > + */
> > +bool chamelium_stream_stop_realtime_audio(struct chamelium_stream *client)
> > +{
> > +	enum stream_message_kind kind;
> > +	enum stream_message_type type;
> > +	enum stream_error err;
> > +	size_t len;
> > +
> > +	igt_debug("Stopping real-time audio capture\n");
> > +
> > +	if (!chamelium_stream_write_request(client,
> > +					    STREAM_MESSAGE_STOP_DUMP_AUDIO,
> > +					    NULL, 0))
> > +		return false;
> > +
> > +	while (true) {
> > +		if (!chamelium_stream_read_header(client, &kind, &type,
> > +						  &err, &len))
> > +			return false;
> > +
> > +		if (kind == STREAM_MESSAGE_RESPONSE)
> > +			break;
> > +
> > +		if (!read_and_discard(client->fd, len))
> > +			return false;
> > +	}
> > +
> > +	if (type != STREAM_MESSAGE_STOP_DUMP_AUDIO) {
> > +		igt_warn("Unexpected response type %d\n", type);
> > +		return false;
> > +	}
> > +	if (err != STREAM_ERROR_NONE) {
> > +		igt_warn("Received error: %s (%d)\n",
> > +			 stream_error_str(err), err);
> > +		return false;
> > +	}
> > +	if (len != 0) {
> > +		igt_warn("Expected an empty response, got %zu bytes\n", len);
> > +		return false;
> > +	}
> > +
> > +	return true;
> > +}
> > +
> > +/**
> > + * chamelium_stream_audio_format:
> > + *
> > + * Gets the format used for audio pages.
> > + *
> > + * Data will always be captured in raw pages of S32_LE elements. This function
> > + * exposes the sampling rate and the number of channels.
> > + */
> > +void chamelium_stream_audio_format(struct chamelium_stream *stream,
> > +				   int *rate, int *channels)
> > +{
> > +	/* TODO: the Chamelium streaming server doesn't expose those yet.
> > +	 * Just hardcode the values for now. */
> > +	*rate = 48000;
> > +	*channels = 8;
> > +}
> > +
> > +/**
> > + * chamelium_stream_init:
> > + *
> > + * Connects to the Chamelium streaming server.
> > + */
> > +struct chamelium_stream *chamelium_stream_init(void)
> > +{
> > +	struct chamelium_stream *client;
> > +
> > +	client = calloc(1, sizeof(*client));
> > +
> > +	if (!chamelium_stream_read_config(client))
> > +		goto error_client;
> > +	if (!chamelium_stream_connect(client))
> > +		goto error_client;
> > +	if (!chamelium_stream_check_version(client))
> > +		goto error_fd;
> > +
> > +	return client;
> > +
> > +error_fd:
> > +	close(client->fd);
> > +error_client:
> > +	free(client);
> > +	return NULL;
> > +}
> > +
> > +void chamelium_stream_deinit(struct chamelium_stream *client)
> > +{
> > +	if (close(client->fd) != 0)
> > +		igt_warn("close failed: %s\n", strerror(errno));
> > +	free(client);
> > +}
> > diff --git a/lib/igt_chamelium_stream.h b/lib/igt_chamelium_stream.h
> > new file mode 100644
> > index 00000000..de4e9931
> > --- /dev/null
> > +++ b/lib/igt_chamelium_stream.h
> > @@ -0,0 +1,52 @@
> > +/*
> > + * Copyright © 2019 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: Simon Ser <simon.ser@intel.com>
> > + */
> > +
> > +#ifndef IGT_CHAMELIUM_STREAM_H
> > +#define IGT_CHAMELIUM_STREAM_H
> > +
> > +#include "config.h"
> > +
> > +enum chamelium_stream_realtime_mode {
> > +	CHAMELIUM_STREAM_REALTIME_NONE = 0,
> > +	/* stop dumping when overflow */
> > +	CHAMELIUM_STREAM_REALTIME_STOP_WHEN_OVERFLOW = 1,
> > +	/* drop data on overflow */
> > +	CHAMELIUM_STREAM_REALTIME_BEST_EFFORT = 2,
> > +};
> > +
> > +struct chamelium_stream;
> > +
> > +struct chamelium_stream *chamelium_stream_init(void);
> > +void chamelium_stream_deinit(struct chamelium_stream *client);
> > +bool chamelium_stream_dump_realtime_audio(struct chamelium_stream *client,
> > +					  enum chamelium_stream_realtime_mode mode);
> > +void chamelium_stream_audio_format(struct chamelium_stream *stream,
> > +				   int *rate, int *channels);
> > +bool chamelium_stream_receive_realtime_audio(struct chamelium_stream *client,
> > +					     size_t *page_count,
> > +					     int32_t **buf, size_t *buf_len);
> > +bool chamelium_stream_stop_realtime_audio(struct chamelium_stream *client);
> > +
> > +#endif
> > diff --git a/lib/meson.build b/lib/meson.build
> > index a8462933..eead0afb 100644
> > --- a/lib/meson.build
> > +++ b/lib/meson.build
> > @@ -92,7 +92,7 @@ if valgrind.found()
> >  endif
> >  
> >  if gsl.found()
> > -	lib_deps += [ gsl ]
> > +	lib_deps += gsl
> >  	lib_sources += [ 'igt_frame.c', 'igt_audio.c' ]
> >  endif
> >  
> > @@ -101,9 +101,10 @@ if alsa.found()
> >  	lib_sources += 'igt_alsa.c'
> >  endif
> >  
> > -if chamelium.found()
> > +if chamelium_found
> >  	lib_deps += chamelium
> >  	lib_sources += 'igt_chamelium.c'
> > +	lib_sources += 'igt_chamelium_stream.c'
> >  endif
> >  
> >  srcdir = join_paths(meson.source_root(), 'tests')
> > diff --git a/meson.build b/meson.build
> > index 557400a5..be6dff9d 100644
> > --- a/meson.build
> > +++ b/meson.build
> > @@ -64,8 +64,6 @@ _build_overlay = false
> >  _overlay_required = false
> >  _build_man = false
> >  _man_required = false
> > -_build_audio = false
> > -_audio_required = false
> >  _build_chamelium = false
> >  _chamelium_required = false
> >  _build_docs = false
> > @@ -79,7 +77,6 @@ build_overlay = get_option('build_overlay')
> >  overlay_backends = get_option('overlay_backends')
> >  build_man = get_option('build_man')
> >  with_valgrind = get_option('with_valgrind')
> > -build_audio = get_option('build_audio')
> >  build_chamelium = get_option('build_chamelium')
> >  build_docs = get_option('build_docs')
> >  build_tests = get_option('build_tests')
> > @@ -91,8 +88,6 @@ _build_overlay = build_overlay != 'false'
> >  _overlay_required = build_overlay == 'true'
> >  _build_man = build_man != 'false'
> >  _man_required = build_man == 'true'
> > -_build_audio = build_audio != 'false'
> > -_audio_required = build_audio == 'true'
> >  _build_chamelium = build_chamelium != 'false'
> >  _chamelium_required = build_chamelium == 'true'
> >  _build_docs = build_docs != 'false'
> > @@ -166,26 +161,6 @@ cairo = dependency('cairo', version : '>1.12.0', required : true)
> >  libudev = dependency('libudev', required : true)
> >  glib = dependency('glib-2.0', required : true)
> >  
> > -gsl = null_dep
> > -alsa = null_dep
> > -if _build_audio or _build_chamelium
> > -	gsl = dependency('gsl', required : _audio_required or _chamelium_required)
> > -endif
> > -if _build_audio
> > -	alsa = dependency('alsa', required : _audio_required)
> > -endif
> > -
> > -audioinfo = 'No'
> > -if _build_audio and alsa.found() and gsl.found()
> > -	audioinfo = 'Yes'
> > -else
> > -	if _audio_required
> > -		error('Cannot build audio test due to missing dependencies')
> > -	endif
> > -	_build_audio = false
> > -endif
> > -build_info += 'Build audio test: ' + audioinfo
> > -
> >  xmlrpc = dependency('xmlrpc', required : false)
> >  xmlrpc_util = dependency('xmlrpc_util', required : false)
> >  xmlrpc_client = dependency('xmlrpc_client', required : false)
> > @@ -197,21 +172,32 @@ if not xmlrpc.found() and xmlrpc_cmd.found()
> >  
> >  	if libs_cmd.returncode() == 0 and cflags_cmd.returncode() == 0
> >  		xmlrpc = declare_dependency(compile_args: cflags_cmd.stdout().strip().split(),
> > -					   link_args : libs_cmd.stdout().strip().split())
> > +					    link_args : libs_cmd.stdout().strip().split())
> >  		xmlrpc_util = declare_dependency()
> >  		xmlrpc_client = declare_dependency()
> >  	endif
> >  endif
> >  
> > +gsl = null_dep
> > +alsa = null_dep
> >  chamelium = null_dep
> > +chamelium_found = false # TODO: use a disabler object instead
> >  chameliuminfo = 'No'
> > -if _build_chamelium and gsl.found() and xmlrpc.found() and xmlrpc_util.found() and xmlrpc_client.found()
> > -	chamelium = declare_dependency(dependencies : [ xmlrpc,
> > -							xmlrpc_util, xmlrpc_client])
> > -	config.set('HAVE_CHAMELIUM', 1)
> > -	chameliuminfo = 'Yes'
> > -elif _chamelium_required
> > -	error('Cannot build chamelium test due to missing dependencies')
> > +if _build_chamelium
> > +	gsl = dependency('gsl', required : _chamelium_required)
> > +	alsa = dependency('alsa', required : _chamelium_required)
> > +	chamelium = declare_dependency(dependencies : [
> > +		xmlrpc,
> > +		xmlrpc_util,
> > +		xmlrpc_client,
> > +		gsl,
> > +		alsa,
> > +	], required : _chamelium_required)
> > +	if xmlrpc.found() and xmlrpc_util.found() and xmlrpc_client.found() and gsl.found() and alsa.found()
> > +		config.set('HAVE_CHAMELIUM', 1)
> > +		chameliuminfo = 'Yes'
> > +		chamelium_found = true
> > +	endif
> >  endif
> >  build_info += 'Build Chamelium test: ' + chameliuminfo
> >  
> > diff --git a/meson_options.txt b/meson_options.txt
> > index 0cd3b350..888efe56 100644
> > --- a/meson_options.txt
> > +++ b/meson_options.txt
> > @@ -10,12 +10,6 @@ option('overlay_backends',
> >         choices : [ 'auto', 'x', 'xv' ],
> >         description : 'Overlay backends to enable')
> >  
> > -option('build_audio',
> > -       type : 'combo',
> > -       value : 'auto',
> > -       choices : ['auto', 'true', 'false'],
> > -       description : 'Build audio test')
> > -
> >  option('build_chamelium',
> >         type : 'combo',
> >         value : 'auto',
> > diff --git a/tests/audio.c b/tests/audio.c
> > deleted file mode 100644
> > index 560876a3..00000000
> > --- a/tests/audio.c
> > +++ /dev/null
> > @@ -1,193 +0,0 @@
> > -/*
> > - * 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 <paul.kocialkowski@linux.intel.com>
> > - */
> > -
> > -#include "config.h"
> > -#include "igt.h"
> > -
> > -#define PLAYBACK_CHANNELS	2
> > -#define PLAYBACK_FRAMES		1024
> > -
> > -#define CAPTURE_SAMPLE_RATE	48000
> > -#define CAPTURE_CHANNELS	2
> > -#define CAPTURE_DEVICE_NAME	"default"
> > -#define CAPTURE_FRAMES		2048
> > -
> > -#define RUN_TIMEOUT		2000
> > -
> > -struct test_data {
> > -	struct alsa *alsa;
> > -	struct audio_signal *signal;
> > -
> > -	int streak;
> > -};
> > -
> > -static int sampling_rates[] = {
> > -	32000,
> > -	44100,
> > -	48000,
> > -	88200,
> > -	96000,
> > -	176400,
> > -	192000,
> > -};
> > -
> > -static int sampling_rates_count = sizeof(sampling_rates) / sizeof(int);
> > -
> > -static int test_frequencies[] = {
> > -	300,
> > -	600,
> > -	1200,
> > -	80000,
> > -	10000,
> > -};
> > -
> > -static int test_frequencies_count = sizeof(test_frequencies) / sizeof(int);
> > -
> > -static int output_callback(void *data, short *buffer, int frames)
> > -{
> > -	struct test_data *test_data = (struct test_data *) data;
> > -
> > -	audio_signal_fill(test_data->signal, buffer, frames);
> > -
> > -	return 0;
> > -}
> > -
> > -static int input_callback(void *data, short *buffer, int frames)
> > -{
> > -	struct test_data *test_data = (struct test_data *) data;
> > -	bool detect;
> > -
> > -	detect = audio_signal_detect(test_data->signal, CAPTURE_CHANNELS,
> > -				     CAPTURE_SAMPLE_RATE, buffer, frames);
> > -	if (detect)
> > -		test_data->streak++;
> > -	else
> > -		test_data->streak = 0;
> > -
> > -	/* A streak of 3 gives confidence that the signal is good. */
> > -	if (test_data->streak == 3)
> > -		return 1;
> > -
> > -	return 0;
> > -}
> > -
> > -static void test_integrity(const char *device_name)
> > -{
> > -	struct test_data data;
> > -	int sampling_rate;
> > -	bool run = false;
> > -	bool test;
> > -	int i, j;
> > -	int ret;
> > -
> > -	data.alsa = alsa_init();
> > -	igt_assert(data.alsa);
> > -
> > -	ret = alsa_open_input(data.alsa, CAPTURE_DEVICE_NAME);
> > -	igt_assert(ret >= 0);
> > -
> > -	alsa_configure_input(data.alsa, CAPTURE_CHANNELS,
> > -			     CAPTURE_SAMPLE_RATE);
> > -
> > -	alsa_register_input_callback(data.alsa, input_callback, &data,
> > -				     CAPTURE_FRAMES);
> > -
> > -	for (i = 0; i < sampling_rates_count; i++) {
> > -		ret = alsa_open_output(data.alsa, device_name);
> > -		igt_assert(ret >= 0);
> > -
> > -		sampling_rate = sampling_rates[i];
> > -
> > -		test = alsa_test_output_configuration(data.alsa,
> > -						      PLAYBACK_CHANNELS,
> > -						      sampling_rate);
> > -		if (!test) {
> > -			alsa_close_output(data.alsa);
> > -			continue;
> > -		}
> > -
> > -		igt_debug("Testing with sampling rate %d\n", sampling_rate);
> > -
> > -		alsa_configure_output(data.alsa, PLAYBACK_CHANNELS,
> > -				       sampling_rate);
> > -
> > -		data.signal = audio_signal_init(PLAYBACK_CHANNELS,
> > -						sampling_rate);
> > -		igt_assert(data.signal);
> > -
> > -		for (j = 0; j < test_frequencies_count; j++)
> > -			audio_signal_add_frequency(data.signal,
> > -						   test_frequencies[j]);
> > -
> > -		audio_signal_synthesize(data.signal);
> > -
> > -		alsa_register_output_callback(data.alsa, output_callback,
> > -					      &data, PLAYBACK_FRAMES);
> > -
> > -		data.streak = 0;
> > -
> > -		ret = alsa_run(data.alsa, RUN_TIMEOUT);
> > -		igt_assert(ret > 0);
> > -
> > -		audio_signal_clean(data.signal);
> > -		free(data.signal);
> > -
> > -		alsa_close_output(data.alsa);
> > -
> > -		run = true;
> > -	}
> > -
> > -	/* Make sure we tested at least one frequency */
> > -	igt_assert(run);
> > -
> > -	alsa_close_input(data.alsa);
> > -	free(data.alsa);
> > -}
> > -
> > -static void test_suspend_resume_integrity(const char *device_name,
> > -					  enum igt_suspend_state state,
> > -					  enum igt_suspend_test test)
> > -{
> > -	test_integrity(device_name);
> > -
> > -	igt_system_suspend_autoresume(state, test);
> > -
> > -	test_integrity(device_name);
> > -}
> > -
> > -igt_main
> > -{
> > -	igt_subtest("hdmi-integrity")
> > -		test_integrity("HDMI");
> > -
> > -	igt_subtest("hdmi-integrity-after-suspend")
> > -		test_suspend_resume_integrity("HDMI", SUSPEND_STATE_MEM,
> > -					      SUSPEND_TEST_NONE);
> > -
> > -	igt_subtest("hdmi-integrity-after-hibernate")
> > -		test_suspend_resume_integrity("HDMI", SUSPEND_STATE_DISK,
> > -					      SUSPEND_TEST_DEVICES);
> > -}
> > diff --git a/tests/kms_chamelium.c b/tests/kms_chamelium.c
> > index 2dc1049d..2974ff69 100644
> > --- a/tests/kms_chamelium.c
> > +++ b/tests/kms_chamelium.c
> > @@ -413,7 +413,7 @@ test_suspend_resume_edid_change(data_t *data, struct chamelium_port *port,
> >  
> >  static igt_output_t *
> >  prepare_output(data_t *data,
> > -	       struct chamelium_port *port)
> > +	       struct chamelium_port *port, bool set_edid)
> >  {
> >  	igt_display_t *display = &data->display;
> >  	igt_output_t *output;
> > @@ -428,7 +428,8 @@ prepare_output(data_t *data,
> >  	/* The chamelium's default EDID has a lot of resolutions, way more then
> >  	 * we need to test
> >  	 */
> > -	chamelium_port_set_edid(data->chamelium, port, data->edid_id);
> > +	if (set_edid)
> > +		chamelium_port_set_edid(data->chamelium, port, data->edid_id);
> >  
> >  	chamelium_plug(data->chamelium, port);
> >  	wait_for_connector(data, port, DRM_MODE_CONNECTED);
> > @@ -613,7 +614,7 @@ static void test_display_one_mode(data_t *data, struct chamelium_port *port,
> >  
> >  	reset_state(data, port);
> >  
> > -	output = prepare_output(data, port);
> > +	output = prepare_output(data, port, true);
> >  	connector = chamelium_port_get_connector(data->chamelium, port, false);
> >  	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> >  	igt_assert(primary);
> > @@ -644,7 +645,7 @@ static void test_display_all_modes(data_t *data, struct chamelium_port *port,
> >  
> >  	reset_state(data, port);
> >  
> > -	output = prepare_output(data, port);
> > +	output = prepare_output(data, port, true);
> >  	connector = chamelium_port_get_connector(data->chamelium, port, false);
> >  	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> >  	igt_assert(primary);
> > @@ -679,7 +680,7 @@ test_display_frame_dump(data_t *data, struct chamelium_port *port)
> >  
> >  	reset_state(data, port);
> >  
> > -	output = prepare_output(data, port);
> > +	output = prepare_output(data, port, true);
> >  	connector = chamelium_port_get_connector(data->chamelium, port, false);
> >  	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> >  	igt_assert(primary);
> > @@ -710,6 +711,266 @@ test_display_frame_dump(data_t *data, struct chamelium_port *port)
> >  	drmModeFreeConnector(connector);
> >  }
> >  
> > +
> > +/* Playback parameters control the audio signal we synthesize and send */
> > +#define PLAYBACK_CHANNELS 2
> > +#define PLAYBACK_SAMPLES 1024
> > +
> > +/* Capture paremeters control the audio signal we receive */
> > +#define CAPTURE_SAMPLES 2048
> > +
> > +#define AUDIO_DURATION 2000 /* ms */
> > +/* A streak of 3 gives confidence that the signal is good. */
> > +#define MIN_STREAK 3
> > +
> > +/* TODO: Chamelium only supports 48KHz for now */
> > +static int sampling_rates[] = {
> > +/*	32000, */
> > +/*	44100, */
> > +	48000,
> > +/*	88200, */
> > +/*	96000, */
> > +/*	176400, */
> > +/*	192000, */
> > +};
> > +
> > +static int sampling_rates_count = sizeof(sampling_rates) / sizeof(int);
> > +
> > +static int test_frequencies[] = {
> > +	300,
> > +	600,
> > +	1200,
> > +	80000,
> > +	10000,
> > +};
> > +
> > +static int test_frequencies_count = sizeof(test_frequencies) / sizeof(int);
> > +
> > +static int
> > +output_callback(void *data, short *buffer, int frames)
> > +{
> > +	struct audio_signal *signal = (struct audio_signal *) data;
> > +
> > +	audio_signal_fill(signal, buffer, frames);
> > +
> > +	return 0;
> > +}
> > +
> > +static bool
> > +do_test_display_audio(data_t *data, struct chamelium_port *port,
> > +		      struct alsa *alsa, int playback_channels,
> > +		      int playback_rate)
> > +{
> > +	int ret, capture_rate, capture_channels, msec;
> > +	struct chamelium_audio_file *audio_file;
> > +	struct chamelium_stream *stream;
> > +	enum chamelium_stream_realtime_mode stream_mode;
> > +	struct audio_signal *signal;
> > +	int32_t *recv, *buf;
> > +	double *channel;
> > +	size_t i, streak, page_count;
> > +	size_t recv_len, buf_len, buf_cap, buf_size, channel_len;
> > +	bool ok;
> > +	char dump_suffix[64];
> > +	char *dump_path = NULL;
> > +	int dump_fd = -1;
> > +
> > +	if (!alsa_test_output_configuration(alsa, playback_channels,
> > +					    playback_rate))
> > +		return false;
> > +
> > +	igt_debug("Testing with playback sampling rate %d\n", playback_rate);
> > +	alsa_configure_output(alsa, playback_channels, playback_rate);
> > +
> > +	chamelium_start_capturing_audio(data->chamelium, port, false);
> > +
> > +	stream = chamelium_stream_init();
> > +	igt_assert(stream);
> > +
> > +	stream_mode = CHAMELIUM_STREAM_REALTIME_STOP_WHEN_OVERFLOW;
> > +	ok = chamelium_stream_dump_realtime_audio(stream, stream_mode);
> > +	igt_assert(ok);
> > +
> > +	chamelium_stream_audio_format(stream, &capture_rate, &capture_channels);
> > +
> > +	if (igt_frame_dump_is_enabled()) {
> > +		snprintf(dump_suffix, sizeof(dump_suffix), "capture-%dch-%d",
> > +			 playback_channels, playback_rate);
> > +
> > +		dump_fd = audio_create_wav_file_s32_le(dump_suffix,
> > +						       capture_rate,
> > +						       capture_channels,
> > +						       &dump_path);
> > +		igt_assert(dump_fd >= 0);
> > +	}
> > +
> > +	signal = audio_signal_init(playback_channels, playback_rate);
> > +	igt_assert(signal);
> > +
> > +	for (i = 0; i < test_frequencies_count; i++)
> > +		audio_signal_add_frequency(signal, test_frequencies[i]);
> > +	audio_signal_synthesize(signal);
> > +
> > +	alsa_register_output_callback(alsa, output_callback, signal,
> > +				      PLAYBACK_SAMPLES);
> > +
> > +	/* TODO: detect signal in real-time */
> > +	ret = alsa_run(alsa, AUDIO_DURATION);
> > +	igt_assert(ret == 0);
> > +
> > +	alsa_close_output(alsa);
> > +
> > +	/* Needs to be a multiple of 128, because that's the number of samples
> > +	 * we get per channel each time we receive an audio page from the
> > +	 * Chamelium device. */
> > +	channel_len = CAPTURE_SAMPLES;
> > +	channel = malloc(sizeof(double) * channel_len);
> > +
> > +	buf_cap = capture_channels * channel_len;
> > +	buf = malloc(sizeof(int32_t) * buf_cap);
> > +	buf_len = 0;
> > +
> > +	recv = NULL;
> > +	recv_len = 0;
> > +
> > +	streak = 0;
> > +	msec = 0;
> > +	i = 0;
> > +	while (streak < MIN_STREAK && msec < AUDIO_DURATION) {
> > +		ok = chamelium_stream_receive_realtime_audio(stream,
> > +							     &page_count,
> > +							     &recv, &recv_len);
> > +		igt_assert(ok);
> > +
> > +		memcpy(&buf[buf_len], recv, recv_len * sizeof(int32_t));
> > +		buf_len += recv_len;
> > +
> > +		if (buf_len < buf_cap)
> > +			continue;
> > +		igt_assert(buf_len == buf_cap);
> > +
> > +		if (dump_fd >= 0) {
> > +			buf_size = buf_len * sizeof(int32_t);
> > +			igt_assert(write(dump_fd, buf, buf_size) == buf_size);
> > +		}
> > +
> > +		/* TODO: check other channels too, not just the first one */
> > +		audio_extract_channel_s32_le(channel, channel_len, buf, buf_len,
> > +					     capture_channels, 0);
> > +
> > +		msec = i * channel_len / (double) capture_rate * 1000;
> > +		igt_debug("Detecting audio signal, t=%d msec\n", msec);
> > +
> > +		if (audio_signal_detect(signal, capture_rate, channel,
> > +					channel_len))
> > +			streak++;
> > +		else
> > +			streak = 0;
> > +
> > +		buf_len = 0;
> > +		i++;
> > +	}
> > +
> > +	if (dump_fd >= 0) {
> > +		close(dump_fd);
> > +		if (streak == MIN_STREAK) {
> > +			/* Test succeeded, no need to keep the captured data */
> > +			unlink(dump_path);
> > +		} else
> > +			igt_debug("Saved captured audio data to %s\n", dump_path);
> > +		free(dump_path);
> > +	}
> > +
> > +	free(recv);
> > +	free(buf);
> > +	free(channel);
> > +
> > +	ok = chamelium_stream_stop_realtime_audio(stream);
> > +	igt_assert(ok);
> > +
> > +	audio_file = chamelium_stop_capturing_audio(data->chamelium,
> > +						    port);
> > +	if (audio_file) {
> > +		igt_debug("Audio file saved on the Chamelium in %s\n",
> > +			  audio_file->path);
> > +		chamelium_destroy_audio_file(audio_file);
> > +	}
> 
> I would suggest to only dump this file on failure, not when having a
> success.

1. We can't decide this after-the-fact: we can only decide whether we
   dump or not before starting the capture.
2. There are two kinds of audio dumps: local (on the DUT, see dump_fd) 
   and remote (on the Chamelium, see the last param of 
   chamelium_start_capturing_audio). If the file has been dumped on the
   Chamelium, chamelium_stop_capturing_audio will return the audio file
   details. It's sometimes useful to enable Chamelium dumps for
   debugging purposes.

> > +
> > +	audio_signal_clean(signal);
> > +	free(signal);
> > +
> > +	chamelium_stream_deinit(stream);
> > +
> > +	igt_assert(streak == MIN_STREAK);
> > +	return true;
> > +}
> > +
> > +static void
> > +test_display_audio(data_t *data, struct chamelium_port *port,
> > +		   const char *audio_device)
> > +{
> > +	bool run = false;
> > +	struct alsa *alsa;
> > +	int ret;
> > +	igt_output_t *output;
> > +	igt_plane_t *primary;
> > +	struct igt_fb fb;
> > +	drmModeModeInfo *mode;
> > +	drmModeConnector *connector;
> > +	int fb_id, i;
> > +
> > +	igt_require(alsa_has_exclusive_access());
> > +
> > +	alsa = alsa_init();
> > +	igt_assert(alsa);
> > +
> > +	reset_state(data, port);
> > +
> > +	/* Use the default Chamelium EDID for this test, as the base IGT EDID
> > +	 * doesn't advertise audio support (see drm_detect_monitor_audio in
> > +	 * the kernel tree). */
> > +	output = prepare_output(data, port, false);
> > +	connector = chamelium_port_get_connector(data->chamelium, port, false);
> > +	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
> > +	igt_assert(primary);
> > +
> > +	igt_assert(connector->count_modes > 0);
> > +	mode = &connector->modes[0];
> > +
> > +	fb_id = igt_create_color_pattern_fb(data->drm_fd,
> > +					    mode->hdisplay, mode->vdisplay,
> > +					    DRM_FORMAT_XRGB8888,
> > +					    LOCAL_DRM_FORMAT_MOD_NONE,
> > +					    0, 0, 0, &fb);
> > +	igt_assert(fb_id > 0);
> > +
> > +	/* Enable the output because the receiver won't try to receive audio if
> > +	 * it doesn't receive video. */
> Maybe move the above comment to above igt_assert(connector->count_modes > 0); ?

Ack

> > +	enable_output(data, port, output, mode, &fb);
> > +
> > +	for (i = 0; i < sampling_rates_count; i++) {
> > +		ret = alsa_open_output(alsa, audio_device);
> > +		igt_assert(ret >= 0);
> > +
> > +		/* TODO: playback on all 8 available channels */
> > +		run |= do_test_display_audio(data, port, alsa,
> > +					     PLAYBACK_CHANNELS,
> > +					     sampling_rates[i]);
> > +
> > +		alsa_close_output(alsa);
> > +	}
> > +
> > +	/* Make sure we tested at least one frequency. */
> > +	igt_assert(run);
> > +
> > +	igt_remove_fb(data->drm_fd, &fb);
> > +
> > +	drmModeFreeConnector(connector);
> > +
> > +	free(alsa);
> > +}
> > +
> > +
> >  static void select_tiled_modifier(igt_plane_t *plane, uint32_t width,
> >  				  uint32_t height, uint32_t format,
> >  				  uint64_t *modifier)
> > @@ -1037,7 +1298,7 @@ static void test_display_planes_random(data_t *data,
> >  	reset_state(data, port);
> >  
> >  	/* Find the connector and pipe. */
> > -	output = prepare_output(data, port);
> > +	output = prepare_output(data, port, true);
> >  
> >  	mode = igt_output_get_mode(output);
> >  
> > @@ -1308,6 +1569,9 @@ igt_main
> >  
> >  		connector_subtest("dp-frame-dump", DisplayPort)
> >  			test_display_frame_dump(&data, port);
> > +
> > +		connector_subtest("dp-audio", DisplayPort)
> > +			test_display_audio(&data, port, "HDMI");
> >  	}
> >  
> >  	igt_subtest_group {
> > diff --git a/tests/meson.build b/tests/meson.build
> > index 5167a6cc..5d3eed82 100644
> > --- a/tests/meson.build
> > +++ b/tests/meson.build
> > @@ -238,20 +238,13 @@ if libdrm_nouveau.found()
> >  	test_deps += libdrm_nouveau
> >  endif
> >  
> > -if _build_chamelium and chamelium.found()
> > +if chamelium_found
> >  	test_progs += [
> >  		'kms_chamelium',
> >  	]
> >  	test_deps += chamelium
> >  endif
> >  
> > -if _build_audio and alsa.found() and gsl.found()
> > -	test_progs += [
> > -		'audio',
> > -	]
> > -	test_deps += alsa
> > -endif
> > -
> >  test_executables = []
> >  test_list = []
> >  
> > 
> 
> Well... that was a mouthful! For the next patches, please split stuff
> more aggressively, even when a patch would only be introducing dead code.

Yes, I promise not to do this again. :S

Thanks for the review!

> With these changed:
> 
> Reviewed-by: Martin Peres <martin.peres@linux.intel.com>
> 
> Martin
_______________________________________________
igt-dev mailing list
igt-dev@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/igt-dev

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [igt-dev] [PATCH i-g-t v4 1/5] tests/kms_chamelium: add dp-audio test
  2019-04-17  8:40     ` Ser, Simon
@ 2019-04-17 12:17       ` Martin Peres
  2019-04-17 12:57         ` Ser, Simon
  0 siblings, 1 reply; 13+ messages in thread
From: Martin Peres @ 2019-04-17 12:17 UTC (permalink / raw)
  To: Ser, Simon, igt-dev



On 17/04/2019 11:40, Ser, Simon wrote:
> On Tue, 2019-04-16 at 15:02 +0300, Martin Peres wrote:
>> On 11/04/2019 15:36, Simon Ser wrote:
>>> 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 depends on the following Chameleon bugs:
>>>
>>> - https://crbug.com/948060
>>> - https://crbug.com/950857
>>
>> Seems like your fixes landed already! Great!
>>
>>> Signed-off-by: Simon Ser <simon.ser@intel.com>
>>> ---
>>>  docs/audio.txt             |  45 ---
>>>  docs/chamelium.txt         |  32 +-
>>>  lib/igt.h                  |   1 +
>>>  lib/igt_alsa.c             |  42 ++-
>>>  lib/igt_alsa.h             |   1 +
>>>  lib/igt_audio.c            | 285 ++++++++++++------
>>>  lib/igt_audio.h            |  12 +-
>>>  lib/igt_aux.c              |  31 ++
>>>  lib/igt_aux.h              |   1 +
>>>  lib/igt_chamelium.c        | 101 +++++++
>>>  lib/igt_chamelium.h        |  11 +
>>>  lib/igt_chamelium_stream.c | 592 +++++++++++++++++++++++++++++++++++++
>>>  lib/igt_chamelium_stream.h |  52 ++++
>>>  lib/meson.build            |   5 +-
>>>  meson.build                |  52 ++--
>>>  meson_options.txt          |   6 -
>>>  tests/audio.c              | 193 ------------
>>>  tests/kms_chamelium.c      | 276 ++++++++++++++++-
>>>  tests/meson.build          |   9 +-
>>>  19 files changed, 1358 insertions(+), 389 deletions(-)
>>>  delete mode 100644 docs/audio.txt
>>>  create mode 100644 lib/igt_chamelium_stream.c
>>>  create mode 100644 lib/igt_chamelium_stream.h
>>>  delete mode 100644 tests/audio.c
>>>
>>> diff --git a/docs/audio.txt b/docs/audio.txt
>>> deleted file mode 100644
>>> index 158ad5d1..00000000
>>> --- a/docs/audio.txt
>>> +++ /dev/null
>>> @@ -1,45 +0,0 @@
>>> -Audio Support in IGT
>>> -====================
>>> -
>>> -This document provides information and instructions about audio support in IGT.
>>> -
>>> -Introduction
>>> -------------
>>> -
>>> -The audio test is aimed at testing the audio features of display connectors,
>>> -such as HDMI.
>>> -
>>> -Test setup
>>> -----------
>>> -
>>> -The setup required for the audio test consists of using an HDMI-VGA adapter with
>>> -an audio-out 3.5 mm jack to extract the audio from the HDMI interface.
>>> -The audio-out jack is connected back to the device-under-test's line-in.
>>> -
>>> -Depending on the behavior of the adapter, it may be necessary to connect a
>>> -ghost VGA dongle to it (in order to emulate a connected display) to enable the
>>> -audio output. There are guides available detailing how to build these.
>>> -
>>> -When executed, the test will automatically send the test audio signal to all
>>> -ALSA audio HDMI outputs and record from the standard ALSA capture device.
>>> -
>>> -Configuration
>>> --------------
>>> -
>>> -In order to deploy the test, ALSA controls have to be configured to set the
>>> -ALSA capture source to line-in. On Intel x86 systems, this can be achieved
>>> -with the following calls to the amixer utility:
>>> -# amixer sset Line 31 on
>>> -# amixer sset "Input Source" Line
>>> -
>>> -It is then useful to store the ALSA state permanently with the alsactl utility:
>>> -# alsactl store
>>> -
>>> -These settings can be restored with the alsactl utility:
>>> -# alsactl restore
>>> -
>>> -It is desirable to ensure that the alsa-restore and alsa-state systemd services
>>> -are enabled to do this job automatically, especially in the case of an
>>> -automated testing system:
>>> -# systemctl enable alsa-restore
>>> -# systemctl enable alsa-state
>>> diff --git a/docs/chamelium.txt b/docs/chamelium.txt
>>> index 0cabcdc6..316dd607 100644
>>> --- a/docs/chamelium.txt
>>> +++ b/docs/chamelium.txt
>>> @@ -139,6 +139,23 @@ $ make remote-install CHAMELEON_HOST=192.168.72.1
>>>  
>>>  The process requires the Chamelium to be connected to the Internet to succeed.
>>>  
>>> +Audio Capture
>>> +-------------
>>> +
>>> +The Chamelium supports audio capture. IGT tests take advantage of the
>>> +Chamelium streaming server to download audio samples from the Chamelium.
>>> +
>>> +IGT needs direct access to audio devices through ALSA, so PulseAudio needs to
>>> +be stopped (otherwise audio tests will automatically get skipped). To make sure
>>> +PulseAudio isn't running:
>>> +
>>> +- Edit /etc/pulse/client.conf and add autospawn=no
>>> +- Run `pulseaudio --kill` (if it succeeds, it means PulseAudio was running)
>>> +- Make sure a DE that automatically spawns PulseAudio isn't running
>>> +
>>> +In case a test fails, the raw captured audio files will be dumped in a WAV
>>> +file.
>>> +
>>>  Contributing Changes to the Daemon
>>>  ----------------------------------
>>>  
>>> @@ -146,10 +163,11 @@ Contributions to the Chamelium daemon, just like any contribution to ChromiumOS,
>>>  are submitted and reviewed at: https://chromium-review.googlesource.com/
>>>  
>>>  The ChromiumOS project provides an extensive developer guide:
>>> -https://www.chromium.org/chromium-os/developer-guide that assumes running within
>>> -the ChromiumOS build system. Since this is likely not the case for contributing
>>> -to the Chamelium daemon, only the part about uploading changes is relevant:
>>> -https://www.chromium.org/chromium-os/developer-guide#TOC-Upload-your-changes-and-get-a-code-review
>>> +https://chromium.googlesource.com/chromiumos/docs/+/master/developer_guide.md
>>> +It that assumes running within the ChromiumOS build system. Since this is
>>
>> The word "that" seems out of place.
>>
>>> +likely not the case for contributing to the Chamelium daemon, only the part
>>> +about uploading changes is relevant:
>>> +https://chromium.googlesource.com/chromiumos/docs/+/master/developer_guide.md#Upload-your-changes-and-get-a-code-review
>>>  
>>>  Most of the process is about using the Gerrit web interface for submitting and
>>>  having the change reviewed and not forgetting the Change-Id, TEST= and BUG=
>>> @@ -162,7 +180,7 @@ Support for the Chamelium platform in IGT is found in the following places:
>>>  * lib/igt_chamelium.c: library with Chamelium-related helpers
>>>  * tests/kms_chamelium.c: sub-tests using the Chamelium
>>>  
>>> -As of late August 2017, the following features are tested by IGT:
>>> +As of early April 2019, the following features are tested by IGT:
>>>  * Pixel-by-pixel frame integrity tests for DP and HDMI
>>>  * Error-trend-based frame integrity tests for VGA
>>>  * CRC-based frame integrity tests for DP and HDMI
>>> @@ -173,6 +191,7 @@ As of late August 2017, the following features are tested by IGT:
>>>    each interface or combined
>>>  * EDID display identifier integrity check for all interfaces
>>>  * EDID display identifier change during suspend for all interfaces
>>> +* Audio Fourier-based tests for DP at 48KHz
>>
>> Maybe an extra patch will be needed to update the list of
>> capabilities... or we just implement them :p
>>>
>>>  
>>>  Future Developments
>>>  -------------------
>>> @@ -180,7 +199,8 @@ Future Developments
>>>  With the current generation of the hardware platform, support for testing a
>>>  number of additional display features could be included as future developments,
>>>  including:
>>> -* Audio capture from HDMI and DP
>>> +* Audio capture from HDMI, with multiple channels and with other playback
>>> +  sampling rates
>>
>> Don't remove DP since it does not yet have the features you are talking
>> about.
> 
> Hmm. The meaning of that comma was a logical "and". Instead, I'll just
> split this into multiple bullet points.
> 
>>>  * High-bandwidth Digital Content Protection (HDCP) streaming to the display
>>>  * Remote control forwarding (CEC) sent from the display
>>>  * YUV colorspace for HDMI, instead of RGB
>>> diff --git a/lib/igt.h b/lib/igt.h
>>> index 6654a659..5852d557 100644
>>> --- a/lib/igt.h
>>> +++ b/lib/igt.h
>>> @@ -43,6 +43,7 @@
>>>  #include "igt_stats.h"
>>>  #ifdef HAVE_CHAMELIUM
>>>  #include "igt_chamelium.h"
>>> +#include "igt_chamelium_stream.h"
>>>  #endif
>>>  #include "instdone.h"
>>>  #include "intel_batchbuffer.h"
>>> diff --git a/lib/igt_alsa.c b/lib/igt_alsa.c
>>> index bb6682cc..456c0c85 100644
>>> --- a/lib/igt_alsa.c
>>> +++ b/lib/igt_alsa.c
>>> @@ -26,9 +26,11 @@
>>>  
>>>  #include "config.h"
>>>  
>>> +#include <limits.h>
>>>  #include <alsa/asoundlib.h>
>>>  
>>>  #include "igt_alsa.h"
>>> +#include "igt_aux.h"
>>>  #include "igt_core.h"
>>>  
>>>  #define HANDLES_MAX	8
>>> @@ -61,6 +63,25 @@ struct alsa {
>>>  	int input_samples_trigger;
>>>  };
>>>  
>>> +/**
>>> + * alsa_has_exclusive_access:
>>> + * Check whether ALSA has exclusive access to audio devices. Fails if
>>> + * PulseAudio is running.
>>> + */
>>> +bool alsa_has_exclusive_access(void)
>>> +{
>>> +	if (igt_is_process_running("pulseaudio")) {
>>> +		igt_warn("It seems that PulseAudio is running. Audio tests "
>>> +			 "need direct access to audio devices, so PulseAudio "
>>> +			 "needs to be stopped. You can do so by running "
>>> +			 "`pulseaudio --kill`. Also make sure to add "
>>> +			 "autospawn=no to /etc/pulse/client.conf\n");
>>> +		return false;
>>> +	}
>>> +
>>> +	return true;
>>> +}
>>> +
>>>  static void alsa_error_handler(const char *file, int line, const char *function,
>>>  			       int err, const char *fmt, ...)
>>>  {
>>> @@ -78,6 +99,11 @@ struct alsa *alsa_init(void)
>>>  {
>>>  	struct alsa *alsa;
>>>  
>>> +	if (!alsa_has_exclusive_access()) {
>>> +		igt_warn("alsa doesn't have exclusive access to audio devices\n");
>>
>> How about folding the second warn in alsa_has_exclusive_access()? It
>> would improve the chances of having a consistent string to grep on in
>> cibuglog in case other users decide to use the function.
>>
>>> +		return NULL;
>>> +	}
>>> +
>>>  	alsa = malloc(sizeof(struct alsa));
>>>  	memset(alsa, 0, sizeof(struct alsa));
>>>  
>>> @@ -553,16 +579,20 @@ int alsa_run(struct alsa *alsa, int duration_ms)
>>>  					if (ret < 0) {
>>>  						ret = snd_pcm_recover(handle,
>>>  								      ret, 0);
>>> -						if (ret < 0)
>>> +						if (ret < 0) {
>>> +							igt_debug("snd_pcm_recover after snd_pcm_writei failed");
>>>  							goto complete;
>>> +						}
>>>  					}
>>>  
>>>  					output_counts[i] += ret;
>>>  				} else if (output_counts[i] < output_trigger &&
>>>  					   ret < 0) {
>>>  					ret = snd_pcm_recover(handle, ret, 0);
>>> -					if (ret < 0)
>>> +					if (ret < 0) {
>>> +						igt_debug("snd_pcm_recover failed");
>>>  						goto complete;
>>> +					}
>>>  				}
>>>  			}
>>>  
>>> @@ -609,16 +639,20 @@ int alsa_run(struct alsa *alsa, int duration_ms)
>>>  					ret = 0;
>>>  				} else if (ret < 0) {
>>>  					ret = snd_pcm_recover(handle, ret, 0);
>>> -					if (ret < 0)
>>> +					if (ret < 0) {
>>> +						igt_debug("snd_pcm_recover after snd_pcm_readi failed");
>>>  						goto complete;
>>> +					}
>>>  				}
>>>  
>>>  				input_count += ret;
>>>  				input_total += ret;
>>>  			} else if (input_count < input_trigger && ret < 0) {
>>>  				ret = snd_pcm_recover(handle, ret, 0);
>>> -				if (ret < 0)
>>> +				if (ret < 0) {
>>> +					igt_debug("snd_pcm_recover failed");
>>>  					goto complete;
>>> +				}
>>>  			}
>>>  		}
>>>  	} while (!reached);
>>> diff --git a/lib/igt_alsa.h b/lib/igt_alsa.h
>>> index 50795130..5c804b46 100644
>>> --- a/lib/igt_alsa.h
>>> +++ b/lib/igt_alsa.h
>>> @@ -33,6 +33,7 @@
>>>  
>>>  struct alsa;
>>>  
>>> +bool alsa_has_exclusive_access(void);
>>>  struct alsa *alsa_init(void);
>>>  int alsa_open_output(struct alsa *alsa, const char *device_name);
>>>  int alsa_open_input(struct alsa *alsa, const char *device_name);
>>> diff --git a/lib/igt_audio.c b/lib/igt_audio.c
>>> index a0592d53..4cc9bdf0 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;
>>
>> Seems like all these alsa changes should be in their own patches so as
>> you could explain why these changes are needed.
>>
>> Are you afraid that some platforms would not have SHORT == INT16?
>>
>> If these changes would have required changes in code that you would
>> change anyway right after, then at least explain the changes in the
>> commit log :)
> 
> Yeah, sorry about this. I just completely forgot about this change when
> writing the commit log. TBH when writing this patch I was mainly
> concerned about getting something working _at all_, and it took quite a
> few days. :P
> 
> 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.
> 
> I can extract these changes to an individual commit if needed.

Just write it in the commit message.

> 
>>>  		}
>>>  
>>>  		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,214 @@ 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 amplitude_len = data_len / 2 + 1;
>>> +	double amplitude[amplitude_len];
>>
>> bin_power?
> 
> Hmm, "amplitude" is used too it seems [1]. But bin_power LGTM, let's
> switch to this wording.
> 
> [1]: https://en.wikipedia.org/wiki/Fast_Fourier_transform#/media/File:FFT_of_Cosine_Summation_Function.png

Then keep amplitude, that's ok!

> 
>>>  	bool detected[signal->freqs_count];
>>> -	int threshold;
>>> -	bool above;
>>> -	int error;
>>> -	int freq = 0;
>>> -	int max;
>>> -	int c, i, j;
>>> -
>>> -	/* Allowed error in Hz due to FFT step. */
>>> -	error = sampling_rate / frames;
>>> +	int ret, epsilon, freq, max_freq;
>>> +	double max, threshold;
>>> +	size_t i, j;
>>> +	bool above, success;
>>> +
>>> +	/* Allowed error in Hz due to FFT step */
>>> +	epsilon = sampling_rate / data_len;
>>
>> freq_accuracy would be a nicer name.
> 
> Agreed.
> 
>>> +	igt_debug("allowed freq. error: %d Hz\n", epsilon);
>>> +
>>> +	ret = gsl_fft_real_radix2_transform(data, 1, data_len);
>>> +	igt_assert(ret == 0);
>>> +
>>> +	/* 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 amplitude is encoded as the magnitude of the complex number and
>>> +	 * the phase is encoded as its angle.
>>> +	 */
>>
>> Thanks for documenting the idiosyncrasies of GSL!
>>
>> /* compute the power received at every bin of the FFT, and record the
>>  * maximum power received as a way to normalize all the others.
>>  */
>>
>>> +	max = 0;
>>> +	amplitude[0] = data[0];
>>> +	for (i = 1; i < amplitude_len - 1; i++) {
>>> +		amplitude[i] = hypot(data[i], data[data_len - i]);
>>> +		if (amplitude[i] > max)
>>> +			max = amplitude[i];
>>> +	}
>>> +	amplitude[amplitude_len - 1] = data[data_len / 2];
>>
>> What's the coding style of IGT wrt spaces vs tabs?
> 
> Always use tabs? IDGI, I only see tabs in there. I'm probably missing
> something.

Yeah, Arek agrees :)

> 
>>>  
>>> -	for (c = 0; c < channels; c++) {
>>> -		for (i = 0; i < frames; i++)
>>> -			data[i] = (double) buffer[i * channels + c];
>>> +	for (i = 0; i < signal->freqs_count; i++)
>>> +		detected[i] = false;
>>
>> This could have been a bitfield, but meh!
> 
> Right. Still have to be a little careful because in the future we'll
> probably test 8 channels with a couple of different frequencies on each
> (we test 5 frequencies ATM, so that would be 40 total), so it could
> somewhat approach a 64 frequencies limit.

agreed. Don't worry about it.

> 
>>>  
>>> -		gsl_fft_real_radix2_transform(data, 1, frames);
>>> +	/* We want to detect peaks above a given threshold. */
>>
>> /* 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;
>>> +	max = 0;
>>> +	max_freq = -1;
>>
>> local_max = 0;
>> local_max_freq = -1;
> 
> That's better indeed.
> 
>>> +	for (i = 0; i < amplitude_len; i++) {
>>> +		freq = sampling_rate * i / data_len;
>>>  
>>> -		max = 0;
>>> +		if (amplitude[i] > threshold)
>>> +			above = true;
>>>  
>>> -		for (i = 0; i < frames / 2; i++) {
>>> -			amplitude[i] = hypot(data[i], data[frames - i]);
>>> -			if (amplitude[i] > max)
>>> -				max = amplitude[i];
>>> +		if (!above) {
>>> +			continue;
>>>  		}
>>>  
>>> -		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;
>>> -					}
>>> +		/* 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 (amplitude[i] < threshold) {
>>> +			for (j = 0; j < signal->freqs_count; j++) {
>>> +				if (signal->freqs[j].freq >
>>> +				    max_freq - epsilon &&
>>> +				    signal->freqs[j].freq <
>>> +				    max_freq + epsilon) {
>>> +					detected[j] = true;
>>> +					igt_debug("Frequency %d detected\n",
>>> +						  max_freq);
>>> +					break;
>>>  				}
>>> +			}
>>>  
>>> -				if (amplitude[i] > max) {
>>> -					max = amplitude[i];
>>> -					freq = sampling_rate * i / frames;
>>> -				}
>>> +			/* We haven't generated this frequency, but we detected
>>> +			 * it. */
>>> +			if (j == signal->freqs_count) {
>>> +				igt_debug("Detected additional frequency: %d\n",
>>> +					  max_freq);
>>> +				success = false;
>>>  			}
>>> +
>>> +			above = false;
>>> +			max = 0;
>>> +			max_freq = -1;
>>>  		}
>>>  
>>> -		for (i = 0; i < signal->freqs_count; i++) {
>>> -			if (!detected[i]) {
>>> -				igt_debug("Missing frequency: %d\n",
>>> -					  signal->freqs[i].freq);
>>> -				return false;
>>> -			}
>>> +		if (amplitude[i] > max) {
>>> +			max = amplitude[i];
>>> +			max_freq = freq;
>>> +		}
>>> +	}
>>> +
>>> +	/* 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;
>>>  		}
>>>  	}
>>>  
>>> -	return true;
>>> +	return success;
>>> +}
>>> +
>>> +/**
>>> + * 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;
>>> +
>>> +	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;
>>> +}
>>> +
>>> +/**
>>> + * Creates a new WAV file. sample_rate is in Hz. If path is not NULL, it will
>>> + * be set to the new file path (the caller is responsible for free-ing it).
>>> + *
>>> + * 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.
>>> + */
>>> +int audio_create_wav_file_s32_le(const char *qualifier, uint32_t sample_rate,
>>> +				 uint16_t channels, char **path)
>>
>> What is qualifier?
> 
> This is taken from igt_write_frame_to_png. I guess this could use some
> more docs.
> 
> /**
>  * 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.
>  */

Looks good!

> 
>> Maybe we can rename that to channel_count?
> 
> I want to shove in the playback frequency too in the future, so I'd
> rather not.

ok!

> 
>>> +{
>>> +	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 fd;
>>>  }
>>> diff --git a/lib/igt_audio.h b/lib/igt_audio.h
>>> index b3b658a4..4aa43e69 100644
>>> --- a/lib/igt_audio.h
>>> +++ b/lib/igt_audio.h
>>> @@ -30,6 +30,7 @@
>>>  #include "config.h"
>>>  
>>>  #include <stdbool.h>
>>> +#include <stdint.h>
>>>  
>>>  struct audio_signal;
>>>  
>>> @@ -37,8 +38,13 @@ struct audio_signal *audio_signal_init(int channels, int sampling_rate);
>>>  int audio_signal_add_frequency(struct audio_signal *signal, int frequency);
>>>  void audio_signal_synthesize(struct audio_signal *signal);
>>>  void audio_signal_clean(struct audio_signal *signal);
>>> -void audio_signal_fill(struct audio_signal *signal, short *buffer, int frames);
>>> -bool audio_signal_detect(struct audio_signal *signal, int channels,
>>> -			 int sampling_rate, short *buffer, int frames);
>>> +void audio_signal_fill(struct audio_signal *signal, int16_t *buffer, int frames);
>>> +bool audio_signal_detect(struct audio_signal *signal, int sampling_rate,
>>> +			 double *data, size_t data_len);
>>> +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);
>>> +int audio_create_wav_file_s32_le(const char *qualifier, uint32_t sample_rate,
>>> +				 uint16_t channels, char **path);
>>>  
>>>  #endif
>>> diff --git a/lib/igt_aux.c b/lib/igt_aux.c
>>> index 05528352..95ceb845 100644
>>> --- a/lib/igt_aux.c
>>> +++ b/lib/igt_aux.c
>>> @@ -1259,6 +1259,37 @@ void igt_set_module_param_int(const char *name, int val)
>>>  	igt_set_module_param(name, str);
>>>  }
>>>  
>>> +/**
>>> + * igt_is_process_running:
>>> + * @comm: Name of process in the form found in /proc/pid/comm (limited to 15
>>> + * chars)
>>> + *
>>> + * Returns: true in case the process has been found, false otherwise.
>>> + *
>>> + * This function checks in the process table for an entry with the name @comm.
>>> + */
>>> +int igt_is_process_running(const char *comm)
>>> +{
>>> +	PROCTAB *proc;
>>> +	proc_t *proc_info;
>>> +	bool found = false;
>>> +
>>> +	proc = openproc(PROC_FILLCOM | PROC_FILLSTAT | PROC_FILLARG);
>>
>> Seems like you can drop FILLARG:
>>
>> From man:
>>        PROC_FILLARG
>>             equivalent to PROC_FILLCOM
> 
> Good catch
> 
>>> +	igt_assert(proc != NULL);
>>> +
>>> +	while ((proc_info = readproc(proc, NULL))) {
>>> +		if (!strncasecmp(proc_info->cmd, comm, sizeof(proc_info->cmd))) {
>>> +			freeproc(proc_info);
>>> +			found = true;
>>> +			break;
>>> +		}
>>> +		freeproc(proc_info);
>>> +	}
>>> +
>>> +	closeproc(proc);
>>> +	return found;
>>> +}
>>> +
>>>  /**
>>>   * igt_terminate_process:
>>>   * @sig: Signal to send
>>> diff --git a/lib/igt_aux.h b/lib/igt_aux.h
>>> index 55392790..dbd88b67 100644
>>> --- a/lib/igt_aux.h
>>> +++ b/lib/igt_aux.h
>>> @@ -279,6 +279,7 @@ bool igt_allow_unlimited_files(void);
>>>  void igt_set_module_param(const char *name, const char *val);
>>>  void igt_set_module_param_int(const char *name, int val);
>>>  
>>> +int igt_is_process_running(const char *comm);
>>>  int igt_terminate_process(int sig, const char *comm);
>>>  void igt_lsof(const char *dpath);
>>>  
>>> diff --git a/lib/igt_chamelium.c b/lib/igt_chamelium.c
>>> index 02cc9b2c..7c9030d1 100644
>>> --- a/lib/igt_chamelium.c
>>> +++ b/lib/igt_chamelium.c
>>> @@ -218,6 +218,12 @@ void chamelium_destroy_frame_dump(struct chamelium_frame_dump *dump)
>>>  	free(dump);
>>>  }
>>>  
>>> +void chamelium_destroy_audio_file(struct chamelium_audio_file *audio_file)
>>> +{
>>> +	free(audio_file->path);
>>> +	free(audio_file);
>>> +}
>>> +
>>>  struct fsm_monitor_args {
>>>  	struct chamelium *chamelium;
>>>  	struct chamelium_port *port;
>>> @@ -924,6 +930,101 @@ int chamelium_get_captured_frame_count(struct chamelium *chamelium)
>>>  	return ret;
>>>  }
>>>  
>>> +/**
>>> + * chamelium_start_capturing_audio:
>>> + * @chamelium: the Chamelium instance
>>> + * @port: the port to capture audio from (it must support audio)
>>> + * @save_to_file: whether the captured audio data should be saved to a file on
>>> + * the Chamelium device
>>> + *
>>> + * Starts capturing audio from a Chamelium port. To stop the capture, use
>>> + * #chamelium_stop_capturing_audio. To retrieve the audio data, either use the
>>> + * stream server or enable @save_to_file (the latter is mainly useful for
>>> + * debugging purposes).
>>> + *
>>> + * It isn't possible to capture audio from multiple ports at the same time.
>>> + */
>>> +void chamelium_start_capturing_audio(struct chamelium *chamelium,
>>> +				    struct chamelium_port *port,
>>> +				    bool save_to_file)
>>> +{
>>> +	xmlrpc_value *res;
>>> +
>>> +	res = chamelium_rpc(chamelium, port, "StartCapturingAudio", "(ib)",
>>> +			    port->id, save_to_file);
>>> +	xmlrpc_DECREF(res);
>>> +}
>>> +
>>> +static void audio_format_from_xml(struct chamelium *chamelium,
>>> +				  xmlrpc_value *res, int *rate, int *channels)
>>> +{
>>> +	xmlrpc_value *res_type, *res_rate, *res_sample_format, *res_channel;
>>> +	char *type, *sample_format;
>>> +
>>> +	xmlrpc_struct_find_value(&chamelium->env, res, "file_type", &res_type);
>>> +	xmlrpc_struct_find_value(&chamelium->env, res, "rate", &res_rate);
>>> +	xmlrpc_struct_find_value(&chamelium->env, res, "sample_format", &res_sample_format);
>>> +	xmlrpc_struct_find_value(&chamelium->env, res, "channel", &res_channel);
>>> +
>>> +	xmlrpc_read_string(&chamelium->env, res_type, (const char **) &type);
>>> +	igt_assert(strcmp(type, "raw") == 0);
>>> +	free(type);
>>> +
>>> +	xmlrpc_read_string(&chamelium->env, res_sample_format, (const char **) &sample_format);
>>> +	igt_assert(strcmp(sample_format, "S32_LE") == 0);
>>> +	free(sample_format);
>>> +
>>> +	xmlrpc_read_int(&chamelium->env, res_rate, rate);
>>> +	xmlrpc_read_int(&chamelium->env, res_channel, channels);
>>> +
>>> +	xmlrpc_DECREF(res_channel);
>>> +	xmlrpc_DECREF(res_sample_format);
>>> +	xmlrpc_DECREF(res_rate);
>>> +	xmlrpc_DECREF(res_type);
>>> +}
>>> +
>>> +/**
>>> + * chamelium_stop_capturing_audio:
>>> + * @chamelium: the Chamelium instance
>>> + * @port: the port from which audio is being captured
>>> + *
>>> + * Stops capturing audio from a Chamelium port. If
>>> + * #chamelium_start_capturing_audio has been called with @save_to_file enabled,
>>> + * this function will return a #chamelium_audio_file struct containing details
>>> + * about the audio file. Once the caller is done with the struct, they should
>>> + * release it with #chamelium_destroy_audio_file.
>>> + */
>>> +struct chamelium_audio_file *chamelium_stop_capturing_audio(struct chamelium *chamelium,
>>> +							    struct chamelium_port *port)
>>> +{
>>> +	xmlrpc_value *res, *res_path, *res_props;
>>> +	struct chamelium_audio_file *file = NULL;
>>> +	char *path;
>>> +
>>> +	res = chamelium_rpc(chamelium, NULL, "StopCapturingAudio", "(i)",
>>> +			    port->id);
>>> +	xmlrpc_array_read_item(&chamelium->env, res, 0, &res_path);
>>> +	xmlrpc_array_read_item(&chamelium->env, res, 1, &res_props);
>>> +
>>> +	xmlrpc_read_string(&chamelium->env, res_path, (const char **) &path);
>>> +
>>> +	if (strlen(path) > 0) {
>>> +		file = calloc(1, sizeof(*file));
>>> +		file->path = path;
>>> +
>>> +		audio_format_from_xml(chamelium, res_props,
>>> +				      &file->rate, &file->channels);
>>> +	} else {
>>> +		free(path);
>>> +	}
>>> +
>>> +	xmlrpc_DECREF(res_props);
>>> +	xmlrpc_DECREF(res_path);
>>> +	xmlrpc_DECREF(res);
>>> +
>>> +	return file;
>>> +}
>>> +
>>>  static pixman_image_t *convert_frame_format(pixman_image_t *src,
>>>  					    int format)
>>>  {
>>> diff --git a/lib/igt_chamelium.h b/lib/igt_chamelium.h
>>> index 233ead85..047f8c5d 100644
>>> --- a/lib/igt_chamelium.h
>>> +++ b/lib/igt_chamelium.h
>>> @@ -53,6 +53,12 @@ enum chamelium_check {
>>>  	CHAMELIUM_CHECK_CRC,
>>>  };
>>>  
>>> +struct chamelium_audio_file {
>>> +	char *path;
>>> +	int rate; /* Hz */
>>> +	int channels;
>>> +};
>>> +
>>>  struct chamelium *chamelium_init(int drm_fd);
>>>  void chamelium_deinit(struct chamelium *chamelium);
>>>  void chamelium_reset(struct chamelium *chamelium);
>>> @@ -100,6 +106,10 @@ void chamelium_start_capture(struct chamelium *chamelium,
>>>  void chamelium_stop_capture(struct chamelium *chamelium, int frame_count);
>>>  void chamelium_capture(struct chamelium *chamelium, struct chamelium_port *port,
>>>  		       int x, int y, int w, int h, int frame_count);
>>> +void chamelium_start_capturing_audio(struct chamelium *chamelium,
>>> +				    struct chamelium_port *port, bool save_to_file);
>>> +struct chamelium_audio_file *chamelium_stop_capturing_audio(struct chamelium *chamelium,
>>> +							    struct chamelium_port *port);
>>>  igt_crc_t *chamelium_read_captured_crcs(struct chamelium *chamelium,
>>>  					int *frame_count);
>>>  struct chamelium_frame_dump *chamelium_read_captured_frame(struct chamelium *chamelium,
>>> @@ -131,5 +141,6 @@ void chamelium_assert_frame_match_or_dump(struct chamelium *chamelium,
>>>  void chamelium_crop_analog_frame(struct chamelium_frame_dump *dump, int width,
>>>  				 int height);
>>>  void chamelium_destroy_frame_dump(struct chamelium_frame_dump *dump);
>>> +void chamelium_destroy_audio_file(struct chamelium_audio_file *audio_file);
>>>  
>>>  #endif /* IGT_CHAMELIUM_H */
>>> diff --git a/lib/igt_chamelium_stream.c b/lib/igt_chamelium_stream.c
>>> new file mode 100644
>>> index 00000000..9e1ba1ca
>>> --- /dev/null
>>> +++ b/lib/igt_chamelium_stream.c
>>> @@ -0,0 +1,592 @@
>>> +/*
>>> + * Copyright © 2019 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: Simon Ser <simon.ser@intel.com>
>>> + */
>>> +
>>> +#include "config.h"
>>> +
>>> +#include <arpa/inet.h>
>>> +#include <errno.h>
>>> +#include <netdb.h>
>>> +#include <stdbool.h>
>>> +#include <stdlib.h>
>>> +#include <sys/types.h>
>>> +#include <sys/socket.h>
>>> +
>>> +#include "igt_chamelium_stream.h"
>>> +#include "igt_core.h"
>>> +#include "igt_rc.h"
>>> +
>>> +#define STREAM_PORT 9994
>>> +#define STREAM_VERSION_MAJOR 1
>>> +#define STREAM_VERSION_MINOR 0
>>> +
>>> +enum stream_error {
>>> +	STREAM_ERROR_NONE = 0,
>>> +	STREAM_ERROR_COMMAND = 1,
>>> +	STREAM_ERROR_ARGUMENT = 2,
>>> +	STREAM_ERROR_EXISTS = 3,
>>> +	STREAM_ERROR_VIDEO_MEM_OVERFLOW_STOP = 4,
>>> +	STREAM_ERROR_VIDEO_MEM_OVERFLOW_DROP = 5,
>>> +	STREAM_ERROR_AUDIO_MEM_OVERFLOW_STOP = 6,
>>> +	STREAM_ERROR_AUDIO_MEM_OVERFLOW_DROP = 7,
>>> +	STREAM_ERROR_NO_MEM = 8,
>>> +};
>>> +
>>> +enum stream_message_kind {
>>> +	STREAM_MESSAGE_REQUEST = 0,
>>> +	STREAM_MESSAGE_RESPONSE = 1,
>>> +	STREAM_MESSAGE_DATA = 2,
>>> +};
>>> +
>>> +enum stream_message_type {
>>> +	STREAM_MESSAGE_RESET = 0,
>>> +	STREAM_MESSAGE_GET_VERSION = 1,
>>> +	STREAM_MESSAGE_VIDEO_STREAM = 2,
>>> +	STREAM_MESSAGE_SHRINK_VIDEO = 3,
>>> +	STREAM_MESSAGE_VIDEO_FRAME = 4,
>>> +	STREAM_MESSAGE_DUMP_REALTIME_VIDEO = 5,
>>> +	STREAM_MESSAGE_STOP_DUMP_VIDEO = 6,
>>> +	STREAM_MESSAGE_DUMP_REALTIME_AUDIO = 7,
>>> +	STREAM_MESSAGE_STOP_DUMP_AUDIO = 8,
>>> +};
>>> +
>>> +struct chamelium_stream {
>>> +	char *host;
>>> +	unsigned int port;
>>> +
>>> +	int fd;
>>> +};
>>> +
>>> +static const char *stream_error_str(enum stream_error err)
>>> +{
>>> +	switch (err) {
>>> +	case STREAM_ERROR_NONE:
>>> +		return "no error";
>>> +	case STREAM_ERROR_COMMAND:
>>> +		return "invalid command";
>>> +	case STREAM_ERROR_ARGUMENT:
>>> +		return "invalid arguments";
>>> +	case STREAM_ERROR_EXISTS:
>>> +		return "dump already started";
>>> +	case STREAM_ERROR_VIDEO_MEM_OVERFLOW_STOP:
>>> +		return "video dump stopped after overflow";
>>> +	case STREAM_ERROR_VIDEO_MEM_OVERFLOW_DROP:
>>> +		return "video frame dropped after overflow";
>>> +	case STREAM_ERROR_AUDIO_MEM_OVERFLOW_STOP:
>>> +		return "audio dump stoppred after overflow";
>>> +	case STREAM_ERROR_AUDIO_MEM_OVERFLOW_DROP:
>>> +		return "audio page dropped after overflow";
>>> +	case STREAM_ERROR_NO_MEM:
>>> +		return "out of memory";
>>> +	}
>>> +	return "unknown error";
>>> +}
>>> +
>>> +/**
>>> + * The Chamelium URL is specified in the configuration file. We need to extract
>>> + * the host to connect to the stream server.
>>> + */
>>> +static char *parse_url_host(const char *url)
>>> +{
>>> +	static const char prefix[] = "http://";
>>> +	char *colon;
>>> +
>>> +	if (strstr(url, prefix) != url)
>>> +		return NULL;
>>> +	url += strlen(prefix);
>>> +
>>> +	colon = strchr(url, ':');
>>> +	if (!colon)
>>> +		return NULL;
>>> +
>>> +	return strndup(url, colon - url);
>>> +}
>>> +
>>> +static bool chamelium_stream_read_config(struct chamelium_stream *client)
>>> +{
>>> +	GError *error = NULL;
>>> +	gchar *chamelium_url;
>>> +
>>> +	if (!igt_key_file) {
>>> +		igt_warn("No configuration file available for chamelium\n");
>>> +		return false;
>>> +	}
>>> +
>>> +	chamelium_url = g_key_file_get_string(igt_key_file, "Chamelium", "URL",
>>> +					      &error);
>>> +	if (!chamelium_url) {
>>> +		igt_warn("Couldn't read Chamelium URL from config file: %s\n",
>>> +			 error->message);
>>> +		return false;
>>> +	}
>>> +
>>> +	client->host = parse_url_host(chamelium_url);
>>> +	if (!client->host) {
>>> +		igt_warn("Invalid Chamelium URL in config file: %s\n",
>>> +			 chamelium_url);
>>> +		return false;
>>> +	}
>>> +	client->port = STREAM_PORT;
>>> +
>>> +	return true;
>>> +}
>>> +
>>> +static bool chamelium_stream_connect(struct chamelium_stream *client)
>>> +{
>>> +	int ret;
>>> +	char port_str[16];
>>> +	struct addrinfo hints = {};
>>> +	struct addrinfo *results, *ai;
>>> +	struct timeval tv = {};
>>> +
>>> +	igt_debug("Connecting to Chamelium stream server: tcp://%s:%u\n",
>>> +		  client->host, client->port);
>>> +
>>> +	snprintf(port_str, sizeof(port_str), "%u", client->port);
>>> +
>>> +	hints.ai_family = AF_UNSPEC;
>>> +	hints.ai_socktype = SOCK_STREAM;
>>> +	ret = getaddrinfo(client->host, port_str, &hints, &results);
>>> +	if (ret != 0) {
>>> +		igt_warn("getaddrinfo failed: %s\n", gai_strerror(ret));
>>> +		return false;
>>> +	}
>>> +
>>> +	client->fd = -1;
>>> +	for (ai = results; ai != NULL; ai = ai->ai_next) {
>>> +		client->fd = socket(ai->ai_family, ai->ai_socktype,
>>> +				    ai->ai_protocol);
>>> +		if (client->fd == -1)
>>> +			continue;
>>> +
>>> +		if (connect(client->fd, ai->ai_addr, ai->ai_addrlen) == -1) {
>>> +			close(client->fd);
>>> +			client->fd = -1;
>>> +			continue;
>>> +		}
>>> +
>>> +		break;
>>> +	}
>>> +
>>> +	freeaddrinfo(results);
>>> +
>>> +	if (client->fd < 0) {
>>> +		igt_warn("Failed to connect to Chamelium stream server\n");
>>> +		return false;
>>> +	}
>>> +
>>> +	/* Set a read and write timeout of 5 seconds. */
>>> +	tv.tv_sec = 5;
>>> +	setsockopt(client->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
>>> +	setsockopt(client->fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
>>> +
>>> +	return true;
>>> +}
>>> +
>>> +static bool read_whole(int fd, void *buf, size_t buf_len)
>>> +{
>>> +	ssize_t ret;
>>> +	size_t n = 0;
>>> +	char *ptr;
>>> +
>>> +	while (n < buf_len) {
>>> +		ptr = (char *) buf + n;
>>> +		ret = read(fd, ptr, buf_len - n);
>>> +		if (ret < 0) {
>>> +			igt_warn("read failed: %s\n", strerror(errno));
>>> +			return false;
>>> +		} else if (ret == 0) {
>>> +			igt_warn("short read\n");
>>> +			return false;
>>> +		}
>>> +		n += ret;
>>> +	}
>>> +
>>> +	return true;
>>> +}
>>> +
>>> +static bool write_whole(int fd, void *buf, size_t buf_len)
>>> +{
>>> +	ssize_t ret;
>>> +	size_t n = 0;
>>> +	char *ptr;
>>> +
>>> +	while (n < buf_len) {
>>> +		ptr = (char *) buf + n;
>>> +		ret = write(fd, ptr, buf_len - n);
>>> +		if (ret < 0) {
>>> +			igt_warn("write failed: %s\n", strerror(errno));
>>> +			return false;
>>> +		} else if (ret == 0) {
>>> +			igt_warn("short write\n");
>>> +			return false;
>>> +		}
>>> +		n += ret;
>>> +	}
>>> +
>>> +	return true;
>>> +}
>>> +
>>> +static bool read_and_discard(int fd, size_t len)
>>> +{
>>> +	char buf[1024];
>>> +	size_t n;
>>> +
>>> +	while (len > 0) {
>>> +		n = len;
>>> +		if (n > sizeof(buf))
>>> +			n = sizeof(buf);
>>> +
>>> +		if (!read_whole(fd, buf, n))
>>> +			return false;
>>> +
>>> +		len -= n;
>>> +	}
>>> +
>>> +	return true;
>>> +}
>>> +
>>> +/** Read a message header from the socket.
>>> + *
>>> + * The header is laid out as follows:
>>> + * - u16: message type
>>> + * - u16: error code
>>> + * - u32: message length
>>> + */
>>> +static bool chamelium_stream_read_header(struct chamelium_stream *client,
>>> +					 enum stream_message_kind *kind,
>>> +					 enum stream_message_type *type,
>>> +					 enum stream_error *err,
>>> +					 size_t *len)
>>> +{
>>> +	uint16_t _type;
>>> +	char buf[8];
>>> +
>>> +	if (!read_whole(client->fd, buf, sizeof(buf)))
>>> +		return false;
>>> +
>>> +	_type = ntohs(*(uint16_t *) &buf[0]);
>>> +	*type = _type & 0xFF;
>>> +	*kind = _type >> 8;
>>> +	*err = ntohs(*(uint16_t *) &buf[2]);
>>> +	*len = ntohl(*(uint32_t *) &buf[4]);
>>> +
>>> +	//igt_debug("received message: kind=%d type=%d err=%d len=%zu\n",
>>> +	//	  *kind, *type, *err, *len);
>>
>> You probably want to keep this debug message. If not, then comment it
>> properly with /* */ or delete it.
> 
> Ah, seems like I forgot to remove it. I don't think we want to keep it,
> because it's extremely chatty when receiving audio pages.

ok!

> 
>>> +
>>> +	return true;
>>> +}
>>> +
>>> +static bool chamelium_stream_write_header(struct chamelium_stream *client,
>>> +					  enum stream_message_type type,
>>> +					  enum stream_error err,
>>> +					  size_t len)
>>> +{
>>> +	char buf[8];
>>> +	uint16_t _type;
>>> +
>>> +	_type = type | (STREAM_MESSAGE_REQUEST << 8);
>>> +
>>> +	*(uint16_t *) &buf[0] = htons(_type);
>>> +	*(uint16_t *) &buf[2] = htons(err);
>>> +	*(uint32_t *) &buf[4] = htonl(len);
>>> +
>>> +	return write_whole(client->fd, buf, sizeof(buf));
>>> +}
>>> +
>>> +static bool chamelium_stream_read_response(struct chamelium_stream *client,
>>> +					   enum stream_message_type type,
>>> +					   void *buf, size_t buf_len)
>>> +{
>>> +	enum stream_message_kind read_kind;
>>> +	enum stream_message_type read_type;
>>> +	enum stream_error read_err;
>>> +	size_t read_len;
>>> +
>>> +	if (!chamelium_stream_read_header(client, &read_kind, &read_type,
>>> +					  &read_err, &read_len))
>>> +		return false;
>>> +
>>> +	if (read_kind != STREAM_MESSAGE_RESPONSE) {
>>> +		igt_warn("Expected a response, got kind %d\n", read_kind);
>>> +		return false;
>>> +	}
>>> +	if (read_type != type) {
>>> +		igt_warn("Expected message type %d, got %d\n",
>>> +			 type, read_type);
>>> +		return false;
>>> +	}
>>> +	if (read_err != STREAM_ERROR_NONE) {
>>> +		igt_warn("Received error: %s (%d)\n",
>>> +			 stream_error_str(read_err), read_err);
>>> +		return false;
>>> +	}
>>> +	if (buf_len != read_len) {
>>> +		igt_warn("Received invalid message body size "
>>> +			 "(got %zu bytes, want %zu bytes)\n",
>>> +			 read_len, buf_len);
>>> +		return false;
>>> +	}
>>> +
>>> +	return read_whole(client->fd, buf, buf_len);
>>> +}
>>> +
>>> +static bool chamelium_stream_write_request(struct chamelium_stream *client,
>>> +					   enum stream_message_type type,
>>> +					   void *buf, size_t buf_len)
>>> +{
>>> +	if (!chamelium_stream_write_header(client, type, STREAM_ERROR_NONE,
>>> +					   buf_len))
>>> +		return false;
>>> +
>>> +	if (buf_len == 0)
>>> +		return true;
>>> +
>>> +	return write_whole(client->fd, buf, buf_len);
>>> +}
>>> +
>>> +static bool chamelium_stream_call(struct chamelium_stream *client,
>>> +				  enum stream_message_type type,
>>> +				  void *req_buf, size_t req_len,
>>> +				  void *resp_buf, size_t resp_len)
>>> +{
>>> +	if (!chamelium_stream_write_request(client, type, req_buf, req_len))
>>> +		return false;
>>> +
>>> +	return chamelium_stream_read_response(client, type, resp_buf, resp_len);
>>> +}
>>> +
>>> +static bool chamelium_stream_check_version(struct chamelium_stream *client)
>>> +{
>>> +	char resp[2];
>>> +	uint8_t major, minor;
>>> +
>>> +	if (!chamelium_stream_call(client, STREAM_MESSAGE_GET_VERSION,
>>> +				   NULL, 0, resp, sizeof(resp)))
>>> +		return false;
>>> +
>>> +	major = resp[0];
>>> +	minor = resp[1];
>>> +	if (major != STREAM_VERSION_MAJOR || minor < STREAM_VERSION_MINOR) {
>>> +		igt_warn("Version mismatch (want %d.%d, got %d.%d)\n",
>>> +			 STREAM_VERSION_MAJOR, STREAM_VERSION_MINOR,
>>> +			 major, minor);
>>> +		return false;
>>> +	}
>>> +
>>> +	return true;
>>> +}
>>> +
>>> +/**
>>> + * chamelium_stream_dump_realtime_audio:
>>> + *
>>> + * Starts audio capture. The caller can then call
>>> + * #chamelium_stream_receive_realtime_audio to receive audio pages.
>>> + */
>>> +bool chamelium_stream_dump_realtime_audio(struct chamelium_stream *client,
>>> +					  enum chamelium_stream_realtime_mode mode)
>>> +{
>>> +	char req[1];
>>> +
>>> +	igt_debug("Starting real-time audio capture\n");
>>> +
>>> +	req[0] = mode;
>>> +	return chamelium_stream_call(client, STREAM_MESSAGE_DUMP_REALTIME_AUDIO,
>>> +				     req, sizeof(req), NULL, 0);
>>> +}
>>> +
>>> +/**
>>> + * chamelium_stream_receive_realtime_audio:
>>> + * @page_count: if non-NULL, will be set to the dumped page number
>>> + * @buf: must either point to a dynamically allocated memory region or NULL
>>> + * @buf_len: number of elements of *@buf, for zero if @buf is NULL
>>> + *
>>> + * Receives one audio page from the streaming server.
>>> + *
>>> + * In "best effort" mode, some pages can be dropped. This can be detected via
>>> + * the page count.
>>> + *
>>> + * buf_len will be set to the size of the page. The caller is responsible for
>>> + * calling free(3) on *buf.
>>> + */
>>> +bool chamelium_stream_receive_realtime_audio(struct chamelium_stream *client,
>>> +					     size_t *page_count,
>>> +					     int32_t **buf, size_t *buf_len)
>>> +{
>>> +	enum stream_message_kind kind;
>>> +	enum stream_message_type type;
>>> +	enum stream_error err;
>>> +	size_t body_len;
>>> +	char page_count_buf[4];
>>> +	int32_t *ptr;
>>> +
>>> +	while (true) {
>>> +		if (!chamelium_stream_read_header(client, &kind, &type,
>>> +						  &err, &body_len))
>>> +			return false;
>>> +
>>> +		if (kind != STREAM_MESSAGE_DATA) {
>>> +			igt_warn("Expected a data message, got kind %d\n", kind);
>>> +			return false;
>>> +		}
>>> +		if (type != STREAM_MESSAGE_DUMP_REALTIME_AUDIO) {
>>> +			igt_warn("Expected real-time audio dump message, "
>>> +				 "got type %d\n", type);
>>> +			return false;
>>> +		}
>>> +
>>> +		if (err == STREAM_ERROR_NONE)
>>> +			break;
>>> +		else if (err != STREAM_ERROR_AUDIO_MEM_OVERFLOW_DROP) {
>>> +			igt_warn("Received error: %s (%d)\n",
>>> +				 stream_error_str(err), err);
>>> +			return false;
>>> +		}
>>> +
>>> +		igt_debug("Dropped an audio page because of an overflow\n");
>>> +		igt_assert(body_len == 0);
>>> +	}
>>> +
>>> +	igt_assert(body_len >= sizeof(page_count_buf));
>>> +
>>> +	if (!read_whole(client->fd, page_count_buf, sizeof(page_count_buf)))
>>> +		return false;
>>> +	if (page_count)
>>> +		*page_count = ntohl(*(uint32_t *) &page_count_buf[0]);
>>> +	body_len -= sizeof(page_count_buf);
>>> +
>>> +	igt_assert(body_len % sizeof(int32_t) == 0);
>>> +	if (*buf_len * sizeof(int32_t) != body_len) {
>>> +		ptr = realloc(*buf, body_len);
>>> +		if (!ptr) {
>>> +			igt_warn("realloc failed: %s\n", strerror(errno));
>>> +			return false;
>>> +		}
>>> +		*buf = ptr;
>>> +		*buf_len = body_len / sizeof(int32_t);
>>> +	}
>>> +
>>> +	return read_whole(client->fd, *buf, body_len);
>>> +}
>>> +
>>> +/**
>>> + * chamelium_stream_stop_realtime_audio:
>>> + *
>>> + * Stops real-time audio capture. This also drops any buffered audio pages.
>>> + * The caller shouldn't call #chamelium_stream_receive_realtime_audio after
>>> + * stopping audio capture.
>>> + */
>>> +bool chamelium_stream_stop_realtime_audio(struct chamelium_stream *client)
>>> +{
>>> +	enum stream_message_kind kind;
>>> +	enum stream_message_type type;
>>> +	enum stream_error err;
>>> +	size_t len;
>>> +
>>> +	igt_debug("Stopping real-time audio capture\n");
>>> +
>>> +	if (!chamelium_stream_write_request(client,
>>> +					    STREAM_MESSAGE_STOP_DUMP_AUDIO,
>>> +					    NULL, 0))
>>> +		return false;
>>> +
>>> +	while (true) {
>>> +		if (!chamelium_stream_read_header(client, &kind, &type,
>>> +						  &err, &len))
>>> +			return false;
>>> +
>>> +		if (kind == STREAM_MESSAGE_RESPONSE)
>>> +			break;
>>> +
>>> +		if (!read_and_discard(client->fd, len))
>>> +			return false;
>>> +	}
>>> +
>>> +	if (type != STREAM_MESSAGE_STOP_DUMP_AUDIO) {
>>> +		igt_warn("Unexpected response type %d\n", type);
>>> +		return false;
>>> +	}
>>> +	if (err != STREAM_ERROR_NONE) {
>>> +		igt_warn("Received error: %s (%d)\n",
>>> +			 stream_error_str(err), err);
>>> +		return false;
>>> +	}
>>> +	if (len != 0) {
>>> +		igt_warn("Expected an empty response, got %zu bytes\n", len);
>>> +		return false;
>>> +	}
>>> +
>>> +	return true;
>>> +}
>>> +
>>> +/**
>>> + * chamelium_stream_audio_format:
>>> + *
>>> + * Gets the format used for audio pages.
>>> + *
>>> + * Data will always be captured in raw pages of S32_LE elements. This function
>>> + * exposes the sampling rate and the number of channels.
>>> + */
>>> +void chamelium_stream_audio_format(struct chamelium_stream *stream,
>>> +				   int *rate, int *channels)
>>> +{
>>> +	/* TODO: the Chamelium streaming server doesn't expose those yet.
>>> +	 * Just hardcode the values for now. */
>>> +	*rate = 48000;
>>> +	*channels = 8;
>>> +}
>>> +
>>> +/**
>>> + * chamelium_stream_init:
>>> + *
>>> + * Connects to the Chamelium streaming server.
>>> + */
>>> +struct chamelium_stream *chamelium_stream_init(void)
>>> +{
>>> +	struct chamelium_stream *client;
>>> +
>>> +	client = calloc(1, sizeof(*client));
>>> +
>>> +	if (!chamelium_stream_read_config(client))
>>> +		goto error_client;
>>> +	if (!chamelium_stream_connect(client))
>>> +		goto error_client;
>>> +	if (!chamelium_stream_check_version(client))
>>> +		goto error_fd;
>>> +
>>> +	return client;
>>> +
>>> +error_fd:
>>> +	close(client->fd);
>>> +error_client:
>>> +	free(client);
>>> +	return NULL;
>>> +}
>>> +
>>> +void chamelium_stream_deinit(struct chamelium_stream *client)
>>> +{
>>> +	if (close(client->fd) != 0)
>>> +		igt_warn("close failed: %s\n", strerror(errno));
>>> +	free(client);
>>> +}
>>> diff --git a/lib/igt_chamelium_stream.h b/lib/igt_chamelium_stream.h
>>> new file mode 100644
>>> index 00000000..de4e9931
>>> --- /dev/null
>>> +++ b/lib/igt_chamelium_stream.h
>>> @@ -0,0 +1,52 @@
>>> +/*
>>> + * Copyright © 2019 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: Simon Ser <simon.ser@intel.com>
>>> + */
>>> +
>>> +#ifndef IGT_CHAMELIUM_STREAM_H
>>> +#define IGT_CHAMELIUM_STREAM_H
>>> +
>>> +#include "config.h"
>>> +
>>> +enum chamelium_stream_realtime_mode {
>>> +	CHAMELIUM_STREAM_REALTIME_NONE = 0,
>>> +	/* stop dumping when overflow */
>>> +	CHAMELIUM_STREAM_REALTIME_STOP_WHEN_OVERFLOW = 1,
>>> +	/* drop data on overflow */
>>> +	CHAMELIUM_STREAM_REALTIME_BEST_EFFORT = 2,
>>> +};
>>> +
>>> +struct chamelium_stream;
>>> +
>>> +struct chamelium_stream *chamelium_stream_init(void);
>>> +void chamelium_stream_deinit(struct chamelium_stream *client);
>>> +bool chamelium_stream_dump_realtime_audio(struct chamelium_stream *client,
>>> +					  enum chamelium_stream_realtime_mode mode);
>>> +void chamelium_stream_audio_format(struct chamelium_stream *stream,
>>> +				   int *rate, int *channels);
>>> +bool chamelium_stream_receive_realtime_audio(struct chamelium_stream *client,
>>> +					     size_t *page_count,
>>> +					     int32_t **buf, size_t *buf_len);
>>> +bool chamelium_stream_stop_realtime_audio(struct chamelium_stream *client);
>>> +
>>> +#endif
>>> diff --git a/lib/meson.build b/lib/meson.build
>>> index a8462933..eead0afb 100644
>>> --- a/lib/meson.build
>>> +++ b/lib/meson.build
>>> @@ -92,7 +92,7 @@ if valgrind.found()
>>>  endif
>>>  
>>>  if gsl.found()
>>> -	lib_deps += [ gsl ]
>>> +	lib_deps += gsl
>>>  	lib_sources += [ 'igt_frame.c', 'igt_audio.c' ]
>>>  endif
>>>  
>>> @@ -101,9 +101,10 @@ if alsa.found()
>>>  	lib_sources += 'igt_alsa.c'
>>>  endif
>>>  
>>> -if chamelium.found()
>>> +if chamelium_found
>>>  	lib_deps += chamelium
>>>  	lib_sources += 'igt_chamelium.c'
>>> +	lib_sources += 'igt_chamelium_stream.c'
>>>  endif
>>>  
>>>  srcdir = join_paths(meson.source_root(), 'tests')
>>> diff --git a/meson.build b/meson.build
>>> index 557400a5..be6dff9d 100644
>>> --- a/meson.build
>>> +++ b/meson.build
>>> @@ -64,8 +64,6 @@ _build_overlay = false
>>>  _overlay_required = false
>>>  _build_man = false
>>>  _man_required = false
>>> -_build_audio = false
>>> -_audio_required = false
>>>  _build_chamelium = false
>>>  _chamelium_required = false
>>>  _build_docs = false
>>> @@ -79,7 +77,6 @@ build_overlay = get_option('build_overlay')
>>>  overlay_backends = get_option('overlay_backends')
>>>  build_man = get_option('build_man')
>>>  with_valgrind = get_option('with_valgrind')
>>> -build_audio = get_option('build_audio')
>>>  build_chamelium = get_option('build_chamelium')
>>>  build_docs = get_option('build_docs')
>>>  build_tests = get_option('build_tests')
>>> @@ -91,8 +88,6 @@ _build_overlay = build_overlay != 'false'
>>>  _overlay_required = build_overlay == 'true'
>>>  _build_man = build_man != 'false'
>>>  _man_required = build_man == 'true'
>>> -_build_audio = build_audio != 'false'
>>> -_audio_required = build_audio == 'true'
>>>  _build_chamelium = build_chamelium != 'false'
>>>  _chamelium_required = build_chamelium == 'true'
>>>  _build_docs = build_docs != 'false'
>>> @@ -166,26 +161,6 @@ cairo = dependency('cairo', version : '>1.12.0', required : true)
>>>  libudev = dependency('libudev', required : true)
>>>  glib = dependency('glib-2.0', required : true)
>>>  
>>> -gsl = null_dep
>>> -alsa = null_dep
>>> -if _build_audio or _build_chamelium
>>> -	gsl = dependency('gsl', required : _audio_required or _chamelium_required)
>>> -endif
>>> -if _build_audio
>>> -	alsa = dependency('alsa', required : _audio_required)
>>> -endif
>>> -
>>> -audioinfo = 'No'
>>> -if _build_audio and alsa.found() and gsl.found()
>>> -	audioinfo = 'Yes'
>>> -else
>>> -	if _audio_required
>>> -		error('Cannot build audio test due to missing dependencies')
>>> -	endif
>>> -	_build_audio = false
>>> -endif
>>> -build_info += 'Build audio test: ' + audioinfo
>>> -
>>>  xmlrpc = dependency('xmlrpc', required : false)
>>>  xmlrpc_util = dependency('xmlrpc_util', required : false)
>>>  xmlrpc_client = dependency('xmlrpc_client', required : false)
>>> @@ -197,21 +172,32 @@ if not xmlrpc.found() and xmlrpc_cmd.found()
>>>  
>>>  	if libs_cmd.returncode() == 0 and cflags_cmd.returncode() == 0
>>>  		xmlrpc = declare_dependency(compile_args: cflags_cmd.stdout().strip().split(),
>>> -					   link_args : libs_cmd.stdout().strip().split())
>>> +					    link_args : libs_cmd.stdout().strip().split())
>>>  		xmlrpc_util = declare_dependency()
>>>  		xmlrpc_client = declare_dependency()
>>>  	endif
>>>  endif
>>>  
>>> +gsl = null_dep
>>> +alsa = null_dep
>>>  chamelium = null_dep
>>> +chamelium_found = false # TODO: use a disabler object instead
>>>  chameliuminfo = 'No'
>>> -if _build_chamelium and gsl.found() and xmlrpc.found() and xmlrpc_util.found() and xmlrpc_client.found()
>>> -	chamelium = declare_dependency(dependencies : [ xmlrpc,
>>> -							xmlrpc_util, xmlrpc_client])
>>> -	config.set('HAVE_CHAMELIUM', 1)
>>> -	chameliuminfo = 'Yes'
>>> -elif _chamelium_required
>>> -	error('Cannot build chamelium test due to missing dependencies')
>>> +if _build_chamelium
>>> +	gsl = dependency('gsl', required : _chamelium_required)
>>> +	alsa = dependency('alsa', required : _chamelium_required)
>>> +	chamelium = declare_dependency(dependencies : [
>>> +		xmlrpc,
>>> +		xmlrpc_util,
>>> +		xmlrpc_client,
>>> +		gsl,
>>> +		alsa,
>>> +	], required : _chamelium_required)
>>> +	if xmlrpc.found() and xmlrpc_util.found() and xmlrpc_client.found() and gsl.found() and alsa.found()
>>> +		config.set('HAVE_CHAMELIUM', 1)
>>> +		chameliuminfo = 'Yes'
>>> +		chamelium_found = true
>>> +	endif
>>>  endif
>>>  build_info += 'Build Chamelium test: ' + chameliuminfo
>>>  
>>> diff --git a/meson_options.txt b/meson_options.txt
>>> index 0cd3b350..888efe56 100644
>>> --- a/meson_options.txt
>>> +++ b/meson_options.txt
>>> @@ -10,12 +10,6 @@ option('overlay_backends',
>>>         choices : [ 'auto', 'x', 'xv' ],
>>>         description : 'Overlay backends to enable')
>>>  
>>> -option('build_audio',
>>> -       type : 'combo',
>>> -       value : 'auto',
>>> -       choices : ['auto', 'true', 'false'],
>>> -       description : 'Build audio test')
>>> -
>>>  option('build_chamelium',
>>>         type : 'combo',
>>>         value : 'auto',
>>> diff --git a/tests/audio.c b/tests/audio.c
>>> deleted file mode 100644
>>> index 560876a3..00000000
>>> --- a/tests/audio.c
>>> +++ /dev/null
>>> @@ -1,193 +0,0 @@
>>> -/*
>>> - * 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 <paul.kocialkowski@linux.intel.com>
>>> - */
>>> -
>>> -#include "config.h"
>>> -#include "igt.h"
>>> -
>>> -#define PLAYBACK_CHANNELS	2
>>> -#define PLAYBACK_FRAMES		1024
>>> -
>>> -#define CAPTURE_SAMPLE_RATE	48000
>>> -#define CAPTURE_CHANNELS	2
>>> -#define CAPTURE_DEVICE_NAME	"default"
>>> -#define CAPTURE_FRAMES		2048
>>> -
>>> -#define RUN_TIMEOUT		2000
>>> -
>>> -struct test_data {
>>> -	struct alsa *alsa;
>>> -	struct audio_signal *signal;
>>> -
>>> -	int streak;
>>> -};
>>> -
>>> -static int sampling_rates[] = {
>>> -	32000,
>>> -	44100,
>>> -	48000,
>>> -	88200,
>>> -	96000,
>>> -	176400,
>>> -	192000,
>>> -};
>>> -
>>> -static int sampling_rates_count = sizeof(sampling_rates) / sizeof(int);
>>> -
>>> -static int test_frequencies[] = {
>>> -	300,
>>> -	600,
>>> -	1200,
>>> -	80000,
>>> -	10000,
>>> -};
>>> -
>>> -static int test_frequencies_count = sizeof(test_frequencies) / sizeof(int);
>>> -
>>> -static int output_callback(void *data, short *buffer, int frames)
>>> -{
>>> -	struct test_data *test_data = (struct test_data *) data;
>>> -
>>> -	audio_signal_fill(test_data->signal, buffer, frames);
>>> -
>>> -	return 0;
>>> -}
>>> -
>>> -static int input_callback(void *data, short *buffer, int frames)
>>> -{
>>> -	struct test_data *test_data = (struct test_data *) data;
>>> -	bool detect;
>>> -
>>> -	detect = audio_signal_detect(test_data->signal, CAPTURE_CHANNELS,
>>> -				     CAPTURE_SAMPLE_RATE, buffer, frames);
>>> -	if (detect)
>>> -		test_data->streak++;
>>> -	else
>>> -		test_data->streak = 0;
>>> -
>>> -	/* A streak of 3 gives confidence that the signal is good. */
>>> -	if (test_data->streak == 3)
>>> -		return 1;
>>> -
>>> -	return 0;
>>> -}
>>> -
>>> -static void test_integrity(const char *device_name)
>>> -{
>>> -	struct test_data data;
>>> -	int sampling_rate;
>>> -	bool run = false;
>>> -	bool test;
>>> -	int i, j;
>>> -	int ret;
>>> -
>>> -	data.alsa = alsa_init();
>>> -	igt_assert(data.alsa);
>>> -
>>> -	ret = alsa_open_input(data.alsa, CAPTURE_DEVICE_NAME);
>>> -	igt_assert(ret >= 0);
>>> -
>>> -	alsa_configure_input(data.alsa, CAPTURE_CHANNELS,
>>> -			     CAPTURE_SAMPLE_RATE);
>>> -
>>> -	alsa_register_input_callback(data.alsa, input_callback, &data,
>>> -				     CAPTURE_FRAMES);
>>> -
>>> -	for (i = 0; i < sampling_rates_count; i++) {
>>> -		ret = alsa_open_output(data.alsa, device_name);
>>> -		igt_assert(ret >= 0);
>>> -
>>> -		sampling_rate = sampling_rates[i];
>>> -
>>> -		test = alsa_test_output_configuration(data.alsa,
>>> -						      PLAYBACK_CHANNELS,
>>> -						      sampling_rate);
>>> -		if (!test) {
>>> -			alsa_close_output(data.alsa);
>>> -			continue;
>>> -		}
>>> -
>>> -		igt_debug("Testing with sampling rate %d\n", sampling_rate);
>>> -
>>> -		alsa_configure_output(data.alsa, PLAYBACK_CHANNELS,
>>> -				       sampling_rate);
>>> -
>>> -		data.signal = audio_signal_init(PLAYBACK_CHANNELS,
>>> -						sampling_rate);
>>> -		igt_assert(data.signal);
>>> -
>>> -		for (j = 0; j < test_frequencies_count; j++)
>>> -			audio_signal_add_frequency(data.signal,
>>> -						   test_frequencies[j]);
>>> -
>>> -		audio_signal_synthesize(data.signal);
>>> -
>>> -		alsa_register_output_callback(data.alsa, output_callback,
>>> -					      &data, PLAYBACK_FRAMES);
>>> -
>>> -		data.streak = 0;
>>> -
>>> -		ret = alsa_run(data.alsa, RUN_TIMEOUT);
>>> -		igt_assert(ret > 0);
>>> -
>>> -		audio_signal_clean(data.signal);
>>> -		free(data.signal);
>>> -
>>> -		alsa_close_output(data.alsa);
>>> -
>>> -		run = true;
>>> -	}
>>> -
>>> -	/* Make sure we tested at least one frequency */
>>> -	igt_assert(run);
>>> -
>>> -	alsa_close_input(data.alsa);
>>> -	free(data.alsa);
>>> -}
>>> -
>>> -static void test_suspend_resume_integrity(const char *device_name,
>>> -					  enum igt_suspend_state state,
>>> -					  enum igt_suspend_test test)
>>> -{
>>> -	test_integrity(device_name);
>>> -
>>> -	igt_system_suspend_autoresume(state, test);
>>> -
>>> -	test_integrity(device_name);
>>> -}
>>> -
>>> -igt_main
>>> -{
>>> -	igt_subtest("hdmi-integrity")
>>> -		test_integrity("HDMI");
>>> -
>>> -	igt_subtest("hdmi-integrity-after-suspend")
>>> -		test_suspend_resume_integrity("HDMI", SUSPEND_STATE_MEM,
>>> -					      SUSPEND_TEST_NONE);
>>> -
>>> -	igt_subtest("hdmi-integrity-after-hibernate")
>>> -		test_suspend_resume_integrity("HDMI", SUSPEND_STATE_DISK,
>>> -					      SUSPEND_TEST_DEVICES);
>>> -}
>>> diff --git a/tests/kms_chamelium.c b/tests/kms_chamelium.c
>>> index 2dc1049d..2974ff69 100644
>>> --- a/tests/kms_chamelium.c
>>> +++ b/tests/kms_chamelium.c
>>> @@ -413,7 +413,7 @@ test_suspend_resume_edid_change(data_t *data, struct chamelium_port *port,
>>>  
>>>  static igt_output_t *
>>>  prepare_output(data_t *data,
>>> -	       struct chamelium_port *port)
>>> +	       struct chamelium_port *port, bool set_edid)
>>>  {
>>>  	igt_display_t *display = &data->display;
>>>  	igt_output_t *output;
>>> @@ -428,7 +428,8 @@ prepare_output(data_t *data,
>>>  	/* The chamelium's default EDID has a lot of resolutions, way more then
>>>  	 * we need to test
>>>  	 */
>>> -	chamelium_port_set_edid(data->chamelium, port, data->edid_id);
>>> +	if (set_edid)
>>> +		chamelium_port_set_edid(data->chamelium, port, data->edid_id);
>>>  
>>>  	chamelium_plug(data->chamelium, port);
>>>  	wait_for_connector(data, port, DRM_MODE_CONNECTED);
>>> @@ -613,7 +614,7 @@ static void test_display_one_mode(data_t *data, struct chamelium_port *port,
>>>  
>>>  	reset_state(data, port);
>>>  
>>> -	output = prepare_output(data, port);
>>> +	output = prepare_output(data, port, true);
>>>  	connector = chamelium_port_get_connector(data->chamelium, port, false);
>>>  	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
>>>  	igt_assert(primary);
>>> @@ -644,7 +645,7 @@ static void test_display_all_modes(data_t *data, struct chamelium_port *port,
>>>  
>>>  	reset_state(data, port);
>>>  
>>> -	output = prepare_output(data, port);
>>> +	output = prepare_output(data, port, true);
>>>  	connector = chamelium_port_get_connector(data->chamelium, port, false);
>>>  	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
>>>  	igt_assert(primary);
>>> @@ -679,7 +680,7 @@ test_display_frame_dump(data_t *data, struct chamelium_port *port)
>>>  
>>>  	reset_state(data, port);
>>>  
>>> -	output = prepare_output(data, port);
>>> +	output = prepare_output(data, port, true);
>>>  	connector = chamelium_port_get_connector(data->chamelium, port, false);
>>>  	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
>>>  	igt_assert(primary);
>>> @@ -710,6 +711,266 @@ test_display_frame_dump(data_t *data, struct chamelium_port *port)
>>>  	drmModeFreeConnector(connector);
>>>  }
>>>  
>>> +
>>> +/* Playback parameters control the audio signal we synthesize and send */
>>> +#define PLAYBACK_CHANNELS 2
>>> +#define PLAYBACK_SAMPLES 1024
>>> +
>>> +/* Capture paremeters control the audio signal we receive */
>>> +#define CAPTURE_SAMPLES 2048
>>> +
>>> +#define AUDIO_DURATION 2000 /* ms */
>>> +/* A streak of 3 gives confidence that the signal is good. */
>>> +#define MIN_STREAK 3
>>> +
>>> +/* TODO: Chamelium only supports 48KHz for now */
>>> +static int sampling_rates[] = {
>>> +/*	32000, */
>>> +/*	44100, */
>>> +	48000,
>>> +/*	88200, */
>>> +/*	96000, */
>>> +/*	176400, */
>>> +/*	192000, */
>>> +};
>>> +
>>> +static int sampling_rates_count = sizeof(sampling_rates) / sizeof(int);
>>> +
>>> +static int test_frequencies[] = {
>>> +	300,
>>> +	600,
>>> +	1200,
>>> +	80000,
>>> +	10000,
>>> +};
>>> +
>>> +static int test_frequencies_count = sizeof(test_frequencies) / sizeof(int);
>>> +
>>> +static int
>>> +output_callback(void *data, short *buffer, int frames)
>>> +{
>>> +	struct audio_signal *signal = (struct audio_signal *) data;
>>> +
>>> +	audio_signal_fill(signal, buffer, frames);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static bool
>>> +do_test_display_audio(data_t *data, struct chamelium_port *port,
>>> +		      struct alsa *alsa, int playback_channels,
>>> +		      int playback_rate)
>>> +{
>>> +	int ret, capture_rate, capture_channels, msec;
>>> +	struct chamelium_audio_file *audio_file;
>>> +	struct chamelium_stream *stream;
>>> +	enum chamelium_stream_realtime_mode stream_mode;
>>> +	struct audio_signal *signal;
>>> +	int32_t *recv, *buf;
>>> +	double *channel;
>>> +	size_t i, streak, page_count;
>>> +	size_t recv_len, buf_len, buf_cap, buf_size, channel_len;
>>> +	bool ok;
>>> +	char dump_suffix[64];
>>> +	char *dump_path = NULL;
>>> +	int dump_fd = -1;
>>> +
>>> +	if (!alsa_test_output_configuration(alsa, playback_channels,
>>> +					    playback_rate))
>>> +		return false;
>>> +
>>> +	igt_debug("Testing with playback sampling rate %d\n", playback_rate);
>>> +	alsa_configure_output(alsa, playback_channels, playback_rate);
>>> +
>>> +	chamelium_start_capturing_audio(data->chamelium, port, false);
>>> +
>>> +	stream = chamelium_stream_init();
>>> +	igt_assert(stream);
>>> +
>>> +	stream_mode = CHAMELIUM_STREAM_REALTIME_STOP_WHEN_OVERFLOW;
>>> +	ok = chamelium_stream_dump_realtime_audio(stream, stream_mode);
>>> +	igt_assert(ok);
>>> +
>>> +	chamelium_stream_audio_format(stream, &capture_rate, &capture_channels);
>>> +
>>> +	if (igt_frame_dump_is_enabled()) {
>>> +		snprintf(dump_suffix, sizeof(dump_suffix), "capture-%dch-%d",
>>> +			 playback_channels, playback_rate);
>>> +
>>> +		dump_fd = audio_create_wav_file_s32_le(dump_suffix,
>>> +						       capture_rate,
>>> +						       capture_channels,
>>> +						       &dump_path);
>>> +		igt_assert(dump_fd >= 0);
>>> +	}
>>> +
>>> +	signal = audio_signal_init(playback_channels, playback_rate);
>>> +	igt_assert(signal);
>>> +
>>> +	for (i = 0; i < test_frequencies_count; i++)
>>> +		audio_signal_add_frequency(signal, test_frequencies[i]);
>>> +	audio_signal_synthesize(signal);
>>> +
>>> +	alsa_register_output_callback(alsa, output_callback, signal,
>>> +				      PLAYBACK_SAMPLES);
>>> +
>>> +	/* TODO: detect signal in real-time */
>>> +	ret = alsa_run(alsa, AUDIO_DURATION);
>>> +	igt_assert(ret == 0);
>>> +
>>> +	alsa_close_output(alsa);
>>> +
>>> +	/* Needs to be a multiple of 128, because that's the number of samples
>>> +	 * we get per channel each time we receive an audio page from the
>>> +	 * Chamelium device. */
>>> +	channel_len = CAPTURE_SAMPLES;
>>> +	channel = malloc(sizeof(double) * channel_len);
>>> +
>>> +	buf_cap = capture_channels * channel_len;
>>> +	buf = malloc(sizeof(int32_t) * buf_cap);
>>> +	buf_len = 0;
>>> +
>>> +	recv = NULL;
>>> +	recv_len = 0;
>>> +
>>> +	streak = 0;
>>> +	msec = 0;
>>> +	i = 0;
>>> +	while (streak < MIN_STREAK && msec < AUDIO_DURATION) {
>>> +		ok = chamelium_stream_receive_realtime_audio(stream,
>>> +							     &page_count,
>>> +							     &recv, &recv_len);
>>> +		igt_assert(ok);
>>> +
>>> +		memcpy(&buf[buf_len], recv, recv_len * sizeof(int32_t));
>>> +		buf_len += recv_len;
>>> +
>>> +		if (buf_len < buf_cap)
>>> +			continue;
>>> +		igt_assert(buf_len == buf_cap);
>>> +
>>> +		if (dump_fd >= 0) {
>>> +			buf_size = buf_len * sizeof(int32_t);
>>> +			igt_assert(write(dump_fd, buf, buf_size) == buf_size);
>>> +		}
>>> +
>>> +		/* TODO: check other channels too, not just the first one */
>>> +		audio_extract_channel_s32_le(channel, channel_len, buf, buf_len,
>>> +					     capture_channels, 0);
>>> +
>>> +		msec = i * channel_len / (double) capture_rate * 1000;
>>> +		igt_debug("Detecting audio signal, t=%d msec\n", msec);
>>> +
>>> +		if (audio_signal_detect(signal, capture_rate, channel,
>>> +					channel_len))
>>> +			streak++;
>>> +		else
>>> +			streak = 0;
>>> +
>>> +		buf_len = 0;
>>> +		i++;
>>> +	}
>>> +
>>> +	if (dump_fd >= 0) {
>>> +		close(dump_fd);
>>> +		if (streak == MIN_STREAK) {
>>> +			/* Test succeeded, no need to keep the captured data */
>>> +			unlink(dump_path);
>>> +		} else
>>> +			igt_debug("Saved captured audio data to %s\n", dump_path);
>>> +		free(dump_path);
>>> +	}
>>> +
>>> +	free(recv);
>>> +	free(buf);
>>> +	free(channel);
>>> +
>>> +	ok = chamelium_stream_stop_realtime_audio(stream);
>>> +	igt_assert(ok);
>>> +
>>> +	audio_file = chamelium_stop_capturing_audio(data->chamelium,
>>> +						    port);
>>> +	if (audio_file) {
>>> +		igt_debug("Audio file saved on the Chamelium in %s\n",
>>> +			  audio_file->path);
>>> +		chamelium_destroy_audio_file(audio_file);
>>> +	}
>>
>> I would suggest to only dump this file on failure, not when having a
>> success.
> 
> 1. We can't decide this after-the-fact: we can only decide whether we
>    dump or not before starting the capture.
> 2. There are two kinds of audio dumps: local (on the DUT, see dump_fd) 
>    and remote (on the Chamelium, see the last param of 
>    chamelium_start_capturing_audio). If the file has been dumped on the
>    Chamelium, chamelium_stop_capturing_audio will return the audio file
>    details. It's sometimes useful to enable Chamelium dumps for
>    debugging purposes.

Of course! Sorry for the confusion! Where are we dumping the generated
and received WAVs when the test is failing then? Is that a TODO?

> 
>>> +
>>> +	audio_signal_clean(signal);
>>> +	free(signal);
>>> +
>>> +	chamelium_stream_deinit(stream);
>>> +
>>> +	igt_assert(streak == MIN_STREAK);
>>> +	return true;
>>> +}
>>> +
>>> +static void
>>> +test_display_audio(data_t *data, struct chamelium_port *port,
>>> +		   const char *audio_device)
>>> +{
>>> +	bool run = false;
>>> +	struct alsa *alsa;
>>> +	int ret;
>>> +	igt_output_t *output;
>>> +	igt_plane_t *primary;
>>> +	struct igt_fb fb;
>>> +	drmModeModeInfo *mode;
>>> +	drmModeConnector *connector;
>>> +	int fb_id, i;
>>> +
>>> +	igt_require(alsa_has_exclusive_access());
>>> +
>>> +	alsa = alsa_init();
>>> +	igt_assert(alsa);
>>> +
>>> +	reset_state(data, port);
>>> +
>>> +	/* Use the default Chamelium EDID for this test, as the base IGT EDID
>>> +	 * doesn't advertise audio support (see drm_detect_monitor_audio in
>>> +	 * the kernel tree). */
>>> +	output = prepare_output(data, port, false);
>>> +	connector = chamelium_port_get_connector(data->chamelium, port, false);
>>> +	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
>>> +	igt_assert(primary);
>>> +
>>> +	igt_assert(connector->count_modes > 0);
>>> +	mode = &connector->modes[0];
>>> +
>>> +	fb_id = igt_create_color_pattern_fb(data->drm_fd,
>>> +					    mode->hdisplay, mode->vdisplay,
>>> +					    DRM_FORMAT_XRGB8888,
>>> +					    LOCAL_DRM_FORMAT_MOD_NONE,
>>> +					    0, 0, 0, &fb);
>>> +	igt_assert(fb_id > 0);
>>> +
>>> +	/* Enable the output because the receiver won't try to receive audio if
>>> +	 * it doesn't receive video. */
>> Maybe move the above comment to above igt_assert(connector->count_modes > 0); ?
> 
> Ack
> 
>>> +	enable_output(data, port, output, mode, &fb);
>>> +
>>> +	for (i = 0; i < sampling_rates_count; i++) {
>>> +		ret = alsa_open_output(alsa, audio_device);
>>> +		igt_assert(ret >= 0);
>>> +
>>> +		/* TODO: playback on all 8 available channels */
>>> +		run |= do_test_display_audio(data, port, alsa,
>>> +					     PLAYBACK_CHANNELS,
>>> +					     sampling_rates[i]);
>>> +
>>> +		alsa_close_output(alsa);
>>> +	}
>>> +
>>> +	/* Make sure we tested at least one frequency. */
>>> +	igt_assert(run);
>>> +
>>> +	igt_remove_fb(data->drm_fd, &fb);
>>> +
>>> +	drmModeFreeConnector(connector);
>>> +
>>> +	free(alsa);
>>> +}
>>> +
>>> +
>>>  static void select_tiled_modifier(igt_plane_t *plane, uint32_t width,
>>>  				  uint32_t height, uint32_t format,
>>>  				  uint64_t *modifier)
>>> @@ -1037,7 +1298,7 @@ static void test_display_planes_random(data_t *data,
>>>  	reset_state(data, port);
>>>  
>>>  	/* Find the connector and pipe. */
>>> -	output = prepare_output(data, port);
>>> +	output = prepare_output(data, port, true);
>>>  
>>>  	mode = igt_output_get_mode(output);
>>>  
>>> @@ -1308,6 +1569,9 @@ igt_main
>>>  
>>>  		connector_subtest("dp-frame-dump", DisplayPort)
>>>  			test_display_frame_dump(&data, port);
>>> +
>>> +		connector_subtest("dp-audio", DisplayPort)
>>> +			test_display_audio(&data, port, "HDMI");
>>>  	}
>>>  
>>>  	igt_subtest_group {
>>> diff --git a/tests/meson.build b/tests/meson.build
>>> index 5167a6cc..5d3eed82 100644
>>> --- a/tests/meson.build
>>> +++ b/tests/meson.build
>>> @@ -238,20 +238,13 @@ if libdrm_nouveau.found()
>>>  	test_deps += libdrm_nouveau
>>>  endif
>>>  
>>> -if _build_chamelium and chamelium.found()
>>> +if chamelium_found
>>>  	test_progs += [
>>>  		'kms_chamelium',
>>>  	]
>>>  	test_deps += chamelium
>>>  endif
>>>  
>>> -if _build_audio and alsa.found() and gsl.found()
>>> -	test_progs += [
>>> -		'audio',
>>> -	]
>>> -	test_deps += alsa
>>> -endif
>>> -
>>>  test_executables = []
>>>  test_list = []
>>>  
>>>
>>
>> Well... that was a mouthful! For the next patches, please split stuff
>> more aggressively, even when a patch would only be introducing dead code.
> 
> Yes, I promise not to do this again. :S
> 
> Thanks for the review!

You're welcome!

Feel free to land this patch with the modifications you proposed /
agreed to change.

> 
>> With these changed:
>>
>> Reviewed-by: Martin Peres <martin.peres@linux.intel.com>
>>
>> Martin
_______________________________________________
igt-dev mailing list
igt-dev@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/igt-dev

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [igt-dev] [PATCH i-g-t v4 1/5] tests/kms_chamelium: add dp-audio test
  2019-04-17 12:17       ` Martin Peres
@ 2019-04-17 12:57         ` Ser, Simon
  2019-04-25  9:52           ` Martin Peres
  0 siblings, 1 reply; 13+ messages in thread
From: Ser, Simon @ 2019-04-17 12:57 UTC (permalink / raw)
  To: igt-dev, martin.peres

On Wed, 2019-04-17 at 15:17 +0300, Martin Peres wrote:
> > > > +	if (dump_fd >= 0) {
> > > > +		close(dump_fd);
> > > > +		if (streak == MIN_STREAK) {
> > > > +			/* Test succeeded, no need to keep the captured data */
> > > > +			unlink(dump_path);
> > > > +		} else
> > > > +			igt_debug("Saved captured audio data to %s\n", dump_path);
> > > > +		free(dump_path);
> > > > +	}
> > > > +
> > > > +	free(recv);
> > > > +	free(buf);
> > > > +	free(channel);
> > > > +
> > > > +	ok = chamelium_stream_stop_realtime_audio(stream);
> > > > +	igt_assert(ok);
> > > > +
> > > > +	audio_file = chamelium_stop_capturing_audio(data->chamelium,
> > > > +						    port);
> > > > +	if (audio_file) {
> > > > +		igt_debug("Audio file saved on the Chamelium in %s\n",
> > > > +			  audio_file->path);
> > > > +		chamelium_destroy_audio_file(audio_file);
> > > > +	}
> > > 
> > > I would suggest to only dump this file on failure, not when having a
> > > success.
> > 
> > 1. We can't decide this after-the-fact: we can only decide whether we
> >    dump or not before starting the capture.
> > 2. There are two kinds of audio dumps: local (on the DUT, see dump_fd) 
> >    and remote (on the Chamelium, see the last param of 
> >    chamelium_start_capturing_audio). If the file has been dumped on the
> >    Chamelium, chamelium_stop_capturing_audio will return the audio file
> >    details. It's sometimes useful to enable Chamelium dumps for
> >    debugging purposes.
> 
> Of course! Sorry for the confusion! Where are we dumping the generated
> and received WAVs when the test is failing then? Is that a TODO?

So, this is done a little earlier (see the quoted code above). We do
also dump audio data for tests that succeed, but unlink the file in
that case. This allows us not to keep all of the captured data in
memory (dumps are generally worth a couple of MiB) and to keep the code
simple (no dynamic memory allocation). But honestly I'm not feeling
strongly about this and I'm open to change it.
_______________________________________________
igt-dev mailing list
igt-dev@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/igt-dev

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [igt-dev] [PATCH i-g-t v4 1/5] tests/kms_chamelium: add dp-audio test
  2019-04-17 12:57         ` Ser, Simon
@ 2019-04-25  9:52           ` Martin Peres
  0 siblings, 0 replies; 13+ messages in thread
From: Martin Peres @ 2019-04-25  9:52 UTC (permalink / raw)
  To: Ser, Simon, igt-dev

On 17/04/2019 15:57, Ser, Simon wrote:
> On Wed, 2019-04-17 at 15:17 +0300, Martin Peres wrote:
>>>>> +	if (dump_fd >= 0) {
>>>>> +		close(dump_fd);
>>>>> +		if (streak == MIN_STREAK) {
>>>>> +			/* Test succeeded, no need to keep the captured data */
>>>>> +			unlink(dump_path);
>>>>> +		} else
>>>>> +			igt_debug("Saved captured audio data to %s\n", dump_path);
>>>>> +		free(dump_path);
>>>>> +	}
>>>>> +
>>>>> +	free(recv);
>>>>> +	free(buf);
>>>>> +	free(channel);
>>>>> +
>>>>> +	ok = chamelium_stream_stop_realtime_audio(stream);
>>>>> +	igt_assert(ok);
>>>>> +
>>>>> +	audio_file = chamelium_stop_capturing_audio(data->chamelium,
>>>>> +						    port);
>>>>> +	if (audio_file) {
>>>>> +		igt_debug("Audio file saved on the Chamelium in %s\n",
>>>>> +			  audio_file->path);
>>>>> +		chamelium_destroy_audio_file(audio_file);
>>>>> +	}
>>>>
>>>> I would suggest to only dump this file on failure, not when having a
>>>> success.
>>>
>>> 1. We can't decide this after-the-fact: we can only decide whether we
>>>    dump or not before starting the capture.
>>> 2. There are two kinds of audio dumps: local (on the DUT, see dump_fd) 
>>>    and remote (on the Chamelium, see the last param of 
>>>    chamelium_start_capturing_audio). If the file has been dumped on the
>>>    Chamelium, chamelium_stop_capturing_audio will return the audio file
>>>    details. It's sometimes useful to enable Chamelium dumps for
>>>    debugging purposes.
>>
>> Of course! Sorry for the confusion! Where are we dumping the generated
>> and received WAVs when the test is failing then? Is that a TODO?
> 
> So, this is done a little earlier (see the quoted code above). We do
> also dump audio data for tests that succeed, but unlink the file in
> that case. This allows us not to keep all of the captured data in
> memory (dumps are generally worth a couple of MiB) and to keep the code
> simple (no dynamic memory allocation). But honestly I'm not feeling
> strongly about this and I'm open to change it.
> 

OK, so we dump the received the WAV file and we also dump which
frequencies we expected. That should be sufficient!

When IGT learns to store external files in the .json, we'll have to
revisit the test to embed these new files.

Reviewed-by: Martin Peres <martin.peres@linux.intel.com>
_______________________________________________
igt-dev mailing list
igt-dev@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/igt-dev

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2019-04-25  9:52 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-04-11 12:36 [igt-dev] [PATCH i-g-t v4 0/5] tests/kms_chamelium: add dp-audio test Simon Ser
2019-04-11 12:36 ` [igt-dev] [PATCH i-g-t v4 1/5] " Simon Ser
2019-04-16 12:02   ` Martin Peres
2019-04-17  8:40     ` Ser, Simon
2019-04-17 12:17       ` Martin Peres
2019-04-17 12:57         ` Ser, Simon
2019-04-25  9:52           ` Martin Peres
2019-04-11 12:36 ` [igt-dev] [PATCH i-g-t v4 2/5] tests/kms_chamelium: capture audio data in real-time Simon Ser
2019-04-11 12:36 ` [igt-dev] [PATCH i-g-t v4 3/5] tests/kms_chamelium: test we receive a signal from both audio channels Simon Ser
2019-04-11 12:36 ` [igt-dev] [PATCH i-g-t v4 4/5] tests/kms_chamelium: test audio channels are not mixed up Simon Ser
2019-04-11 12:36 ` [igt-dev] [PATCH i-g-t v4 5/5] tests/kms_chamelium: run audio test with multiple sampling rates Simon Ser
2019-04-11 16:43 ` [igt-dev] ✓ Fi.CI.BAT: success for tests/kms_chamelium: add dp-audio test (rev4) Patchwork
2019-04-11 21:28 ` [igt-dev] ✓ Fi.CI.IGT: " Patchwork

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.