summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/audio.txt45
-rw-r--r--docs/chamelium.txt34
-rw-r--r--lib/igt.h1
-rw-r--r--lib/igt_alsa.c42
-rw-r--r--lib/igt_alsa.h1
-rw-r--r--lib/igt_audio.c313
-rw-r--r--lib/igt_audio.h12
-rw-r--r--lib/igt_aux.c31
-rw-r--r--lib/igt_aux.h1
-rw-r--r--lib/igt_chamelium.c101
-rw-r--r--lib/igt_chamelium.h11
-rw-r--r--lib/igt_chamelium_stream.c589
-rw-r--r--lib/igt_chamelium_stream.h52
-rw-r--r--lib/meson.build5
-rw-r--r--meson.build52
-rw-r--r--meson_options.txt6
-rw-r--r--tests/audio.c193
-rw-r--r--tests/kms_chamelium.c276
-rw-r--r--tests/meson.build9
19 files changed, 1382 insertions, 392 deletions
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..5cc85d6e 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 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,10 @@ 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
+* Check all channels are independent from each other
+* Playback using more than 2 channels, different sampling rates and different
+ sample sizes
* 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..22089881 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,26 @@ 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("alsa doesn't have exclusive access to audio devices\n");
+ 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 +100,10 @@ struct alsa *alsa_init(void)
{
struct alsa *alsa;
+ if (!alsa_has_exclusive_access()) {
+ 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..7624f565 100644
--- a/lib/igt_audio.c
+++ b/lib/igt_audio.c
@@ -26,8 +26,11 @@
#include "config.h"
-#include <math.h>
+#include <errno.h>
+#include <fcntl.h>
#include <gsl/gsl_fft_real.h>
+#include <math.h>
+#include <unistd.h>
#include "igt_audio.h"
#include "igt_core.h"
@@ -128,7 +131,7 @@ int audio_signal_add_frequency(struct audio_signal *signal, int frequency)
*/
void audio_signal_synthesize(struct audio_signal *signal)
{
- short *period;
+ int16_t *period;
double value;
int frames;
int freq;
@@ -145,9 +148,9 @@ void audio_signal_synthesize(struct audio_signal *signal)
for (j = 0; j < frames; j++) {
value = 2.0 * M_PI * freq / signal->sampling_rate * j;
- value = sin(value) * SHRT_MAX / signal->freqs_count;
+ value = sin(value) * INT16_MAX / signal->freqs_count;
- period[j] = (short) value;
+ period[j] = (int16_t) value;
}
signal->freqs[i].period = period;
@@ -186,17 +189,16 @@ void audio_signal_clean(struct audio_signal *signal)
* signal data (in interleaved S16_LE format), at the requested sampling rate
* and number of channels.
*/
-void audio_signal_fill(struct audio_signal *signal, short *buffer, int frames)
+void audio_signal_fill(struct audio_signal *signal, int16_t *buffer, int frames)
{
- short *destination;
- short *source;
+ int16_t *destination, *source;
int total;
int freq_frames;
int freq_offset;
int count;
int i, j, k;
- memset(buffer, 0, sizeof(short) * signal->channels * frames);
+ memset(buffer, 0, sizeof(int16_t) * signal->channels * frames);
for (i = 0; i < signal->freqs_count; i++) {
total = 0;
@@ -229,97 +231,236 @@ void audio_signal_fill(struct audio_signal *signal, short *buffer, int frames)
}
/**
- * audio_signal_detect:
- * @signal: The target signal structure
- * @channels: The input data's number of channels
- * @sampling_rate: The input data's sampling rate
- * @buffer: The input data's buffer
- * @frames: The input data's number of frames
- *
- * Detect that the frequencies specified in @signal, and only those, are
- * present in the input data. The input data's format is required to be S16_LE.
+ * Checks that frequencies specified in signal, and only those, are included
+ * in the input data.
*
- * Returns: A boolean indicating whether the detection was successful
+ * sampling_rate is given in Hz. data_len is the number of elements in data.
*/
-bool audio_signal_detect(struct audio_signal *signal, int channels,
- int sampling_rate, short *buffer, int frames)
+bool audio_signal_detect(struct audio_signal *signal, int sampling_rate,
+ double *data, size_t data_len)
{
- double data[frames];
- int amplitude[frames / 2];
+ size_t bin_power_len = data_len / 2 + 1;
+ double bin_power[bin_power_len];
bool detected[signal->freqs_count];
- int threshold;
- bool above;
- int error;
- int freq = 0;
- int max;
- int c, i, j;
+ int ret, freq_accuracy, freq, local_max_freq;
+ double max, local_max, threshold;
+ size_t i, j;
+ bool above, success;
+
+ /* Allowed error in Hz due to FFT step */
+ freq_accuracy = sampling_rate / data_len;
+ igt_debug("Allowed freq. error: %d Hz\n", freq_accuracy);
+
+ ret = gsl_fft_real_radix2_transform(data, 1, data_len);
+ igt_assert(ret == 0);
+
+ /* Compute the power received by every bin of the FFT, and record the
+ * maximum power received as a way to normalize all the others.
+ *
+ * For i < data_len / 2, the real part of the i-th term is stored at
+ * data[i] and its imaginary part is stored at data[data_len - i].
+ * i = 0 and i = data_len / 2 are special cases, they are purely real
+ * so their imaginary part isn't stored.
+ *
+ * The power is encoded as the magnitude of the complex number and the
+ * phase is encoded as its angle.
+ */
+ max = 0;
+ bin_power[0] = data[0];
+ for (i = 1; i < bin_power_len - 1; i++) {
+ bin_power[i] = hypot(data[i], data[data_len - i]);
+ if (bin_power[i] > max)
+ max = bin_power[i];
+ }
+ bin_power[bin_power_len - 1] = data[data_len / 2];
+
+ for (i = 0; i < signal->freqs_count; i++)
+ detected[i] = false;
+
+ /* Do a linear search through the FFT bins' power to find the the local
+ * maximums that exceed half of the absolute maximum that we previously
+ * calculated.
+ *
+ * Since the frequencies might not be perfectly aligned with the bins of
+ * the FFT, we need to find the local maximum across some consecutive
+ * bins. Once the power returns under the power threshold, we compare
+ * the frequency of the bin that received the maximum power to the
+ * expected frequencies. If found, we mark this frequency as such,
+ * otherwise we warn that an unexpected frequency was found.
+ */
+ threshold = max / 2;
+ success = true;
+ above = false;
+ local_max = 0;
+ local_max_freq = -1;
+ for (i = 0; i < bin_power_len; i++) {
+ freq = sampling_rate * i / data_len;
+
+ if (bin_power[i] > threshold)
+ above = true;
+
+ if (!above) {
+ continue;
+ }
- /* Allowed error in Hz due to FFT step. */
- error = sampling_rate / frames;
+ /* If we were above the threshold and we're not anymore, it's
+ * time to decide whether the peak frequency is correct or
+ * invalid. */
+ if (bin_power[i] < threshold) {
+ for (j = 0; j < signal->freqs_count; j++) {
+ if (signal->freqs[j].freq >
+ local_max_freq - freq_accuracy &&
+ signal->freqs[j].freq <
+ local_max_freq + freq_accuracy) {
+ detected[j] = true;
+ igt_debug("Frequency %d detected\n",
+ local_max_freq);
+ break;
+ }
+ }
- for (c = 0; c < channels; c++) {
- for (i = 0; i < frames; i++)
- data[i] = (double) buffer[i * channels + c];
+ /* We haven't generated this frequency, but we detected
+ * it. */
+ if (j == signal->freqs_count) {
+ igt_debug("Detected additional frequency: %d\n",
+ local_max_freq);
+ success = false;
+ }
- gsl_fft_real_radix2_transform(data, 1, frames);
+ above = false;
+ local_max = 0;
+ local_max_freq = -1;
+ }
- max = 0;
+ if (bin_power[i] > local_max) {
+ local_max = bin_power[i];
+ local_max_freq = freq;
+ }
+ }
- for (i = 0; i < frames / 2; i++) {
- amplitude[i] = hypot(data[i], data[frames - i]);
- if (amplitude[i] > max)
- max = amplitude[i];
+ /* Check that all frequencies we generated have been detected. */
+ for (i = 0; i < signal->freqs_count; i++) {
+ if (!detected[i]) {
+ igt_debug("Missing frequency: %d\n",
+ signal->freqs[i].freq);
+ success = false;
}
+ }
- for (i = 0; i < signal->freqs_count; i++)
- detected[i] = false;
-
- threshold = max / 2;
- above = false;
- max = 0;
-
- for (i = 0; i < frames / 2; i++) {
- if (amplitude[i] > threshold)
- above = true;
-
- if (above) {
- if (amplitude[i] < threshold) {
- above = false;
- max = 0;
-
- for (j = 0; j < signal->freqs_count; j++) {
- if (signal->freqs[j].freq >
- freq - error &&
- signal->freqs[j].freq <
- freq + error) {
- detected[j] = true;
- break;
- }
- }
-
- /* Detected frequency was not generated. */
- if (j == signal->freqs_count) {
- igt_debug("Detected additional frequency: %d\n",
- freq);
- return false;
- }
- }
+ return success;
+}
- if (amplitude[i] > max) {
- max = amplitude[i];
- freq = sampling_rate * i / frames;
- }
- }
- }
+/**
+ * Extracts a single channel from a multi-channel S32_LE input buffer.
+ */
+size_t audio_extract_channel_s32_le(double *dst, size_t dst_cap,
+ int32_t *src, size_t src_len,
+ int n_channels, int channel)
+{
+ size_t dst_len, i;
- for (i = 0; i < signal->freqs_count; i++) {
- if (!detected[i]) {
- igt_debug("Missing frequency: %d\n",
- signal->freqs[i].freq);
- return false;
- }
- }
+ igt_assert(channel < n_channels);
+ igt_assert(src_len % n_channels == 0);
+ dst_len = src_len / n_channels;
+ igt_assert(dst_len <= dst_cap);
+ for (i = 0; i < dst_len; i++)
+ dst[i] = (double) src[i * n_channels + channel];
+
+ return dst_len;
+}
+
+#define RIFF_TAG "RIFF"
+#define WAVE_TAG "WAVE"
+#define FMT_TAG "fmt "
+#define DATA_TAG "data"
+
+static void
+append_to_buffer(char *dst, size_t *i, const void *src, size_t src_size)
+{
+ memcpy(&dst[*i], src, src_size);
+ *i += src_size;
+}
+
+/**
+ * audio_create_wav_file_s32_le:
+ * @qualifier: the basename of the file (the test name will be prepended, and
+ * the file extension will be appended)
+ * @sample_rate: the sample rate in Hz
+ * @channels: the number of channels
+ * @path: if non-NULL, will be set to a pointer to the new file path (the
+ * caller is responsible for free-ing it)
+ *
+ * Creates a new WAV file.
+ *
+ * After calling this function, the caller is expected to write S32_LE PCM data
+ * to the returned file descriptor.
+ *
+ * See http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html for
+ * a WAV file format specification.
+ *
+ * Returns: a file descriptor to the newly created file, or -1 on error.
+ */
+int audio_create_wav_file_s32_le(const char *qualifier, uint32_t sample_rate,
+ uint16_t channels, char **path)
+{
+ char _path[PATH_MAX];
+ const char *test_name, *subtest_name;
+ int fd;
+ char header[44];
+ size_t i = 0;
+ uint32_t file_size, chunk_size, byte_rate;
+ uint16_t format, block_align, bits_per_sample;
+
+ test_name = igt_test_name();
+ subtest_name = igt_subtest_name();
+
+ igt_assert(igt_frame_dump_path);
+ snprintf(_path, sizeof(_path), "%s/audio-%s-%s-%s.wav",
+ igt_frame_dump_path, test_name, subtest_name, qualifier);
+
+ if (path)
+ *path = strdup(_path);
+
+ igt_debug("Dumping %s audio to %s\n", qualifier, _path);
+ fd = open(_path, O_WRONLY | O_CREAT | O_TRUNC);
+ if (fd < 0) {
+ igt_warn("open failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ /* File header */
+ file_size = UINT32_MAX; /* unknown file size */
+ append_to_buffer(header, &i, RIFF_TAG, strlen(RIFF_TAG));
+ append_to_buffer(header, &i, &file_size, sizeof(file_size));
+ append_to_buffer(header, &i, WAVE_TAG, strlen(WAVE_TAG));
+
+ /* Format chunk */
+ chunk_size = 16;
+ format = 1; /* PCM */
+ bits_per_sample = 32; /* S32_LE */
+ byte_rate = sample_rate * channels * bits_per_sample / 8;
+ block_align = channels * bits_per_sample / 8;
+ append_to_buffer(header, &i, FMT_TAG, strlen(FMT_TAG));
+ append_to_buffer(header, &i, &chunk_size, sizeof(chunk_size));
+ append_to_buffer(header, &i, &format, sizeof(format));
+ append_to_buffer(header, &i, &channels, sizeof(channels));
+ append_to_buffer(header, &i, &sample_rate, sizeof(sample_rate));
+ append_to_buffer(header, &i, &byte_rate, sizeof(byte_rate));
+ append_to_buffer(header, &i, &block_align, sizeof(block_align));
+ append_to_buffer(header, &i, &bits_per_sample, sizeof(bits_per_sample));
+
+ /* Data chunk */
+ chunk_size = UINT32_MAX; /* unknown chunk size */
+ append_to_buffer(header, &i, DATA_TAG, strlen(DATA_TAG));
+ append_to_buffer(header, &i, &chunk_size, sizeof(chunk_size));
+
+ igt_assert(i == sizeof(header));
+
+ if (write(fd, header, sizeof(header)) != sizeof(header)) {
+ igt_warn("write failed: %s'n", strerror(errno));
+ close(fd);
+ return -1;
}
- return true;
+ return fd;
}
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..3dd68d95 100644
--- a/lib/igt_aux.c
+++ b/lib/igt_aux.c
@@ -1260,6 +1260,37 @@ void igt_set_module_param_int(const char *name, int val)
}
/**
+ * 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);
+ 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
* @comm: Name of process in the form found in /proc/pid/comm (limited to 15
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..68ddb217
--- /dev/null
+++ b/lib/igt_chamelium_stream.c
@@ -0,0 +1,589 @@
+/*
+ * 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]);
+
+ 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 97f701c7..e0b9cf51 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -94,7 +94,7 @@ if valgrind.found()
endif
if gsl.found()
- lib_deps += [ gsl ]
+ lib_deps += gsl
lib_sources += [ 'igt_frame.c', 'igt_audio.c' ]
endif
@@ -103,9 +103,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..a712250a 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);
+
+ /* Enable the output because the receiver won't try to receive audio if
+ * it doesn't receive video. */
+ 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_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 e3c8b07f..711979b4 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -239,20 +239,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 = []