[igt-dev] [PATCH i-g-t] tests/kms_chamelium: add dp-audio test
Simon Ser
simon.ser at intel.com
Fri Apr 5 12:15:07 UTC 2019
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).
Signed-off-by: Simon Ser <simon.ser at intel.com>
---
lib/igt.h | 1 +
lib/igt_alsa.c | 16 +-
lib/igt_audio.c | 285 +++++++++++++------
lib/igt_audio.h | 12 +-
lib/igt_chamelium.c | 101 +++++++
lib/igt_chamelium.h | 11 +
lib/igt_chamelium_stream.c | 549 +++++++++++++++++++++++++++++++++++++
lib/igt_chamelium_stream.h | 69 +++++
lib/meson.build | 1 +
meson.build | 50 ++--
meson_options.txt | 6 -
tests/audio.c | 193 -------------
tests/kms_chamelium.c | 280 ++++++++++++++++++-
tests/meson.build | 7 -
14 files changed, 1246 insertions(+), 335 deletions(-)
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/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..4427811f 100644
--- a/lib/igt_alsa.c
+++ b/lib/igt_alsa.c
@@ -553,16 +553,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 +613,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_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_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..76bdeebc
--- /dev/null
+++ b/lib/igt_chamelium_stream.c
@@ -0,0 +1,549 @@
+/*
+ * 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 at 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 = {};
+
+ 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;
+}
+
+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);
+}
+
+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);
+}
+
+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;
+}
+
+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;
+}
+
+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..91284a45
--- /dev/null
+++ b/lib/igt_chamelium_stream.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright © 2019 Intel
+ *
+ * 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 at 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);
+/** 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);
+/** Receives one audio page from the streaming server.
+ *
+ * If non-NULL, page_count will be set to the dumped page number.
+ *
+ * In "best effort" mode, some pages can be dropped. This can be detected via
+ * the page count.
+ *
+ * buf must either point to a dynamically allocated memory region or NULL.
+ * buf_len must be the length of the region, or 0 if NULL. buf_len will be set
+ * to the size of the page. The cller 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);
+bool chamelium_stream_stop_realtime_audio(struct chamelium_stream *client);
+
+#endif
diff --git a/lib/meson.build b/lib/meson.build
index 89de06e6..e808dfe5 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -103,6 +103,7 @@ endif
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..5e020370 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,30 @@ 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
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 chamelium.found()
+ config.set('HAVE_CHAMELIUM', 1)
+ chameliuminfo = 'Yes'
+ 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 at 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..9dfb2bd7 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,270 @@ 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;
+ struct chamelium_stream *stream;
+
+ 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);
+
+ stream = chamelium_stream_init();
+ igt_assert(stream);
+
+ 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);
+
+ chamelium_stream_deinit(stream);
+
+ 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 +1302,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 +1573,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..158ad395 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -245,13 +245,6 @@ if _build_chamelium and chamelium.found()
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
More information about the igt-dev
mailing list