tests: start manual audio backend test
Start a simple test program that will exercise the QEMU audio APIs. It is meant to run manually for now, as it accesses the sound system and produces sound by default, and also runs for a few seconds. We may want to make it silent or use the "none" (noaudio) backend by default though, so it can run as part of the automated test suite. Reviewed-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Marc-André Lureau committed
Jan 19, 2026 at 13:42 UTC
3220b38a8d7da828de6c84e144da79dc09fc42a9
8 files changed
+702
-6
MAINTAINERS
+1
@@ -3036,6 +3036,7 @@ X: audio/sndioaudio.c
3036
X: audio/spiceaudio.c
3037
F: include/qemu/audio*.h
3038
F: qapi/audio.json
3039
+F: tests/audio/
3040
3041
ALSA Audio backend
3042
M: Gerd Hoffmann <kraxel@redhat.com>
audio/meson.build
+1
-1
@@ -32,7 +32,7 @@ endforeach
32
if dbus_display
33
module_ss = ss.source_set()
34
module_ss.add(when: [gio, pixman],
35
- if_true: [dbus_display1, files('dbusaudio.c')])
35
+ if_true: [dbus_display1_h, files('dbusaudio.c')])
36
audio_modules += {'dbus': module_ss}
37
endif
38
meson.build
+12
-5
@@ -3879,6 +3879,7 @@ target_modules += { 'accel' : { 'qtest': qtest_module_ss }}
3879
modinfo_collect = find_program('scripts/modinfo-collect.py')
3880
modinfo_generate = find_program('scripts/modinfo-generate.py')
3881
modinfo_files = []
3882
+audio_modinfo_files = []
3883
3884
block_mods = []
3885
system_mods = []
@@ -3906,15 +3907,21 @@ foreach d, list : modules
3907
install: true,
3908
install_dir: qemu_moddir)
3909
if module_ss.sources() != []
3909
- modinfo_files += custom_target(d + '-' + m + '.modinfo',
3910
- output: d + '-' + m + '.modinfo',
3911
- input: sl.extract_all_objects(recursive: true),
3912
- capture: true,
3913
- command: [modinfo_collect, '@INPUT@'])
3910
+ modinfo = custom_target(d + '-' + m + '.modinfo',
3911
+ output: d + '-' + m + '.modinfo',
3912
+ input: sl.extract_all_objects(recursive: true),
3913
+ capture: true,
3914
+ command: [modinfo_collect, '@INPUT@'])
3915
+ modinfo_files += modinfo
3916
+ if d == 'audio'
3917
+ audio_modinfo_files += modinfo
3918
+ endif
3919
endif
3920
else
3921
if d == 'block'
3922
block_ss.add_all(module_ss)
3923
+ elif d == 'audio'
3924
+ audio_ss.add_all(module_ss)
3925
else
3926
system_ss.add_all(module_ss)
3927
endif
tests/audio/audio-stubs.c
new
+62
@@ -0,0 +1,62 @@
1
+/* SPDX-License-Identifier: GPL-2.0-or-later */
2
+
3
+/*
4
+ * Stubs for audio test - provides missing functions for standalone audio test
5
+ */
6
+
7
+#include "qemu/osdep.h"
8
+#include "qemu/dbus.h"
9
+#include "ui/qemu-spice-module.h"
10
+#include "ui/dbus-module.h"
11
+#include "system/replay.h"
12
+#include "system/runstate.h"
13
+
14
+int using_spice;
15
+int using_dbus_display;
16
+
17
+struct QemuSpiceOps qemu_spice;
18
+
19
+GQuark dbus_display_error_quark(void)
20
+{
21
+ return g_quark_from_static_string("dbus-display-error-quark");
22
+}
23
+
24
+#ifdef WIN32
25
+/* from ui/dbus.h */
26
+bool
27
+dbus_win32_import_socket(GDBusMethodInvocation *invocation,
28
+ GVariant *arg_listener, int *socket);
29
+
30
+bool
31
+dbus_win32_import_socket(GDBusMethodInvocation *invocation,
32
+ GVariant *arg_listener, int *socket)
33
+{
34
+ return true;
35
+}
36
+#endif
37
+
38
+void replay_audio_in(size_t *recorded, st_sample *samples,
39
+ size_t *wpos, size_t size)
40
+{
41
+}
42
+
43
+void replay_audio_out(size_t *played)
44
+{
45
+}
46
+
47
+static int dummy_vmse;
48
+
49
+VMChangeStateEntry *qemu_add_vm_change_state_handler(VMChangeStateHandler *cb,
50
+ void *opaque)
51
+{
52
+ return (VMChangeStateEntry *)&dummy_vmse;
53
+}
54
+
55
+void qemu_del_vm_change_state_handler(VMChangeStateEntry *e)
56
+{
57
+}
58
+
59
+bool runstate_is_running(void)
60
+{
61
+ return true;
62
+}
tests/audio/meson.build
new
+23
@@ -0,0 +1,23 @@
1
+# SPDX-License-Identifier: GPL-2.0-or-later
2
+
3
+if not have_system
4
+ subdir_done()
5
+endif
6
+
7
+modinfo_dep = not_found
8
+if enable_modules
9
+ modinfo_src = custom_target('modinfo.c',
10
+ output: 'modinfo.c',
11
+ input: audio_modinfo_files,
12
+ command: [modinfo_generate, '--skip-missing-deps', '@INPUT@'],
13
+ capture: true)
14
+
15
+ modinfo_lib = static_library('modinfo.c', modinfo_src)
16
+ modinfo_dep = declare_dependency(link_with: modinfo_lib)
17
+endif
18
+
19
+# manual audio test - not part of automated test suite
20
+# as it relies on audio system
21
+executable('test-audio',
22
+ sources: [files('test-audio.c', 'audio-stubs.c'), dbus_display1],
23
+ dependencies: [audio, qemuutil, modinfo_dep, gio, spice, sdl])
tests/audio/test-audio.c
new
+599
@@ -0,0 +1,599 @@
1
+/* SPDX-License-Identifier: GPL-2.0-or-later */
2
+
3
+#include "qemu/osdep.h"
4
+#include "qemu/config-file.h"
5
+#include "qemu/cutils.h"
6
+#include "qemu/help_option.h"
7
+#include "qemu/module.h"
8
+#include "qemu/main-loop.h"
9
+#include "qemu/audio.h"
10
+#include "qemu/log.h"
11
+#include "qapi/error.h"
12
+#include "trace/control.h"
13
+#include "glib.h"
14
+
15
+#include "audio/audio_int.h"
16
+
17
+#include <math.h>
18
+#ifdef CONFIG_SDL
19
+/*
20
+ * SDL insists on wrapping the main() function with its own implementation on
21
+ * some platforms; it does so via a macro that renames our main function, so
22
+ * <SDL.h> must be #included here even with no SDL code called from this file.
23
+ */
24
+#include <SDL.h>
25
+#endif
26
+
27
+#define SAMPLE_RATE 44100
28
+#define CHANNELS 2
29
+#define DURATION_SECS 2
30
+#define FREQUENCY 440.0
31
+#define BUFFER_FRAMES 1024
32
+#define TIMEOUT_SECS (DURATION_SECS + 1)
33
+
34
+/* Command-line options */
35
+static gchar *opt_audiodev;
36
+static gchar *opt_trace;
37
+
38
+static GOptionEntry test_options[] = {
39
+ { "audiodev", 'a', 0, G_OPTION_ARG_STRING, &opt_audiodev,
40
+ "Audio device spec (e.g., none or pa,out.buffer-length=50000)", "DEV" },
41
+ { "trace", 'T', 0, G_OPTION_ARG_STRING, &opt_trace,
42
+ "Trace options (e.g., 'pw_*')", "TRACE" },
43
+ { NULL }
44
+};
45
+
46
+#define TEST_AUDIODEV_ID "test"
47
+
48
+typedef struct TestSineState {
49
+ AudioBackend *be;
50
+ SWVoiceOut *voice;
51
+ int64_t total_frames;
52
+ int64_t frames_written;
53
+} TestSineState;
54
+
55
+/* Default audio settings for tests */
56
+static const struct audsettings default_test_settings = {
57
+ .freq = SAMPLE_RATE,
58
+ .nchannels = CHANNELS,
59
+ .fmt = AUDIO_FORMAT_S16,
60
+ .endianness = 0,
61
+};
62
+
63
+static void dummy_audio_callback(void *opaque, int avail)
64
+{
65
+}
66
+
67
+static AudioBackend *get_test_audio_backend(void)
68
+{
69
+ AudioBackend *be;
70
+ Error *err = NULL;
71
+
72
+ if (opt_audiodev) {
73
+ be = audio_be_by_name(TEST_AUDIODEV_ID, &err);
74
+ } else {
75
+ be = audio_get_default_audio_be(&err);
76
+ }
77
+
78
+ if (err) {
79
+ g_error("%s", error_get_pretty(err));
80
+ error_free(err);
81
+ exit(1);
82
+ }
83
+ g_assert_nonnull(be);
84
+ return be;
85
+}
86
+
87
+/*
88
+ * Helper functions for opening test voices with default settings.
89
+ * These reduce boilerplate in test functions.
90
+ */
91
+static SWVoiceOut *open_test_voice_out(AudioBackend *be, const char *name,
92
+ void *opaque, audio_callback_fn cb)
93
+{
94
+ struct audsettings as = default_test_settings;
95
+ SWVoiceOut *voice;
96
+
97
+ voice = AUD_open_out(be, NULL, name, opaque, cb, &as);
98
+ g_assert_nonnull(voice);
99
+ return voice;
100
+}
101
+
102
+static SWVoiceIn *open_test_voice_in(AudioBackend *be, const char *name,
103
+ void *opaque, audio_callback_fn cb)
104
+{
105
+ struct audsettings as = default_test_settings;
106
+
107
+ return AUD_open_in(be, NULL, name, opaque, cb, &as);
108
+}
109
+
110
+/*
111
+ * Generate 440Hz sine wave samples into buffer.
112
+ */
113
+static void generate_sine_samples(int16_t *buffer, int frames,
114
+ int64_t start_frame)
115
+{
116
+ for (int i = 0; i < frames; i++) {
117
+ double t = (double)(start_frame + i) / SAMPLE_RATE;
118
+ double sample = sin(2.0 * M_PI * FREQUENCY * t);
119
+ int16_t s = (int16_t)(sample * 32767.0);
120
+
121
+ buffer[i * 2] = s; /* left channel */
122
+ buffer[i * 2 + 1] = s; /* right channel */
123
+ }
124
+}
125
+
126
+static void test_sine_callback(void *opaque, int avail)
127
+{
128
+ TestSineState *s = opaque;
129
+ int16_t buffer[BUFFER_FRAMES * CHANNELS];
130
+ int frames_remaining;
131
+ int frames_to_write;
132
+ size_t bytes_written;
133
+
134
+ frames_remaining = s->total_frames - s->frames_written;
135
+ if (frames_remaining <= 0) {
136
+ return;
137
+ }
138
+
139
+ frames_to_write = avail / (sizeof(int16_t) * CHANNELS);
140
+ frames_to_write = MIN(frames_to_write, BUFFER_FRAMES);
141
+ frames_to_write = MIN(frames_to_write, frames_remaining);
142
+
143
+ generate_sine_samples(buffer, frames_to_write, s->frames_written);
144
+
145
+ bytes_written = AUD_write(s->voice, buffer,
146
+ frames_to_write * sizeof(int16_t) * CHANNELS);
147
+ s->frames_written += bytes_written / (sizeof(int16_t) * CHANNELS);
148
+}
149
+
150
+
151
+static void test_audio_out_sine_wave(void)
152
+{
153
+ TestSineState state = {0};
154
+ int64_t start_time;
155
+ int64_t elapsed_ms;
156
+
157
+ state.be = get_test_audio_backend();
158
+ state.total_frames = SAMPLE_RATE * DURATION_SECS;
159
+ state.frames_written = 0;
160
+
161
+ g_test_message("Opening audio output...");
162
+ state.voice = open_test_voice_out(state.be, "test-sine",
163
+ &state, test_sine_callback);
164
+
165
+ g_test_message("Playing 440Hz sine wave for %d seconds...", DURATION_SECS);
166
+ AUD_set_active_out(state.voice, true);
167
+
168
+ /*
169
+ * Run the audio subsystem until all frames are written or timeout.
170
+ */
171
+ start_time = g_get_monotonic_time();
172
+ while (state.frames_written < state.total_frames) {
173
+ audio_run(state.be, "test");
174
+ main_loop_wait(true);
175
+
176
+ elapsed_ms = (g_get_monotonic_time() - start_time) / 1000;
177
+ if (elapsed_ms > TIMEOUT_SECS * 1000) {
178
+ g_test_message("Timeout waiting for audio to complete");
179
+ break;
180
+ }
181
+
182
+ g_usleep(G_USEC_PER_SEC / 100); /* 10ms */
183
+ }
184
+
185
+ g_test_message("Wrote %" PRId64 " frames (%.2f seconds)",
186
+ state.frames_written,
187
+ (double)state.frames_written / SAMPLE_RATE);
188
+
189
+ g_assert_cmpint(state.frames_written, ==, state.total_frames);
190
+
191
+ AUD_set_active_out(state.voice, false);
192
+ AUD_close_out(state.be, state.voice);
193
+}
194
+
195
+static void test_audio_prio_list(void)
196
+{
197
+ g_autofree gchar *backends = NULL;
198
+ GString *str = g_string_new(NULL);
199
+ bool has_none = false;
200
+
201
+ for (int i = 0; audio_prio_list[i]; i++) {
202
+ if (i > 0) {
203
+ g_string_append_c(str, ' ');
204
+ }
205
+ g_string_append(str, audio_prio_list[i]);
206
+
207
+ if (g_strcmp0(audio_prio_list[i], "none") == 0) {
208
+ has_none = true;
209
+ }
210
+ }
211
+
212
+ backends = g_string_free(str, FALSE);
213
+ g_test_message("Available backends: %s", backends);
214
+
215
+ /* The 'none' backend should always be available */
216
+ g_assert_true(has_none);
217
+}
218
+
219
+static void test_audio_out_active_state(void)
220
+{
221
+ AudioBackend *be;
222
+ SWVoiceOut *voice;
223
+
224
+ be = get_test_audio_backend();
225
+ voice = open_test_voice_out(be, "test-active", NULL, dummy_audio_callback);
226
+
227
+ g_assert_false(AUD_is_active_out(voice));
228
+
229
+ AUD_set_active_out(voice, true);
230
+ g_assert_true(AUD_is_active_out(voice));
231
+
232
+ AUD_set_active_out(voice, false);
233
+ g_assert_false(AUD_is_active_out(voice));
234
+
235
+ AUD_close_out(be, voice);
236
+}
237
+
238
+static void test_audio_out_buffer_size(void)
239
+{
240
+ AudioBackend *be;
241
+ SWVoiceOut *voice;
242
+ int buffer_size;
243
+
244
+ be = get_test_audio_backend();
245
+ voice = open_test_voice_out(be, "test-buffer", NULL, dummy_audio_callback);
246
+
247
+ buffer_size = AUD_get_buffer_size_out(voice);
248
+ g_test_message("Buffer size: %d bytes", buffer_size);
249
+ g_assert_cmpint(buffer_size, >, 0);
250
+
251
+ AUD_close_out(be, voice);
252
+
253
+ g_assert_cmpint(AUD_get_buffer_size_out(NULL), ==, 0);
254
+}
255
+
256
+static void test_audio_out_volume(void)
257
+{
258
+ AudioBackend *be;
259
+ SWVoiceOut *voice;
260
+ Volume vol;
261
+
262
+ be = get_test_audio_backend();
263
+ voice = open_test_voice_out(be, "test-volume", NULL, dummy_audio_callback);
264
+
265
+ vol = (Volume){ .mute = false, .channels = 2, .vol = {255, 255} };
266
+ AUD_set_volume_out(voice, &vol);
267
+
268
+ vol = (Volume){ .mute = true, .channels = 2, .vol = {255, 255} };
269
+ AUD_set_volume_out(voice, &vol);
270
+
271
+ vol = (Volume){ .mute = false, .channels = 2, .vol = {128, 128} };
272
+ AUD_set_volume_out(voice, &vol);
273
+
274
+ AUD_close_out(be, voice);
275
+}
276
+
277
+static void test_audio_in_active_state(void)
278
+{
279
+ AudioBackend *be;
280
+ SWVoiceIn *voice;
281
+
282
+ be = get_test_audio_backend();
283
+ voice = open_test_voice_in(be, "test-in-active", NULL, dummy_audio_callback);
284
+ if (!voice) {
285
+ g_test_skip("The backend may not support input");
286
+ return;
287
+ }
288
+
289
+ g_assert_false(AUD_is_active_in(voice));
290
+
291
+ AUD_set_active_in(voice, true);
292
+ g_assert_true(AUD_is_active_in(voice));
293
+
294
+ AUD_set_active_in(voice, false);
295
+ g_assert_false(AUD_is_active_in(voice));
296
+
297
+ AUD_close_in(be, voice);
298
+}
299
+
300
+static void test_audio_in_volume(void)
301
+{
302
+ AudioBackend *be;
303
+ SWVoiceIn *voice;
304
+ Volume vol;
305
+
306
+ be = get_test_audio_backend();
307
+ voice = open_test_voice_in(be, "test-in-volume", NULL, dummy_audio_callback);
308
+ if (!voice) {
309
+ g_test_skip("The backend may not support input");
310
+ return;
311
+ }
312
+
313
+ vol = (Volume){ .mute = false, .channels = 2, .vol = {255, 255} };
314
+ AUD_set_volume_in(voice, &vol);
315
+
316
+ vol = (Volume){ .mute = true, .channels = 2, .vol = {255, 255} };
317
+ AUD_set_volume_in(voice, &vol);
318
+
319
+ AUD_close_in(be, voice);
320
+}
321
+
322
+
323
+/* Capture test state */
324
+#define CAPTURE_BUFFER_FRAMES (SAMPLE_RATE / 10) /* 100ms of audio */
325
+#define CAPTURE_BUFFER_SIZE (CAPTURE_BUFFER_FRAMES * CHANNELS * sizeof(int16_t))
326
+
327
+typedef struct TestCaptureState {
328
+ bool notify_called;
329
+ bool capture_called;
330
+ bool destroy_called;
331
+ audcnotification_e last_notify;
332
+ int16_t *captured_samples;
333
+ size_t captured_bytes;
334
+ size_t capture_buffer_size;
335
+} TestCaptureState;
336
+
337
+static void test_capture_notify(void *opaque, audcnotification_e cmd)
338
+{
339
+ TestCaptureState *s = opaque;
340
+ s->notify_called = true;
341
+ s->last_notify = cmd;
342
+}
343
+
344
+static void test_capture_capture(void *opaque, const void *buf, int size)
345
+{
346
+ TestCaptureState *s = opaque;
347
+ size_t bytes_to_copy;
348
+
349
+ s->capture_called = true;
350
+
351
+ if (!s->captured_samples || s->captured_bytes >= s->capture_buffer_size) {
352
+ return;
353
+ }
354
+
355
+ bytes_to_copy = MIN(size, s->capture_buffer_size - s->captured_bytes);
356
+ memcpy((uint8_t *)s->captured_samples + s->captured_bytes, buf, bytes_to_copy);
357
+ s->captured_bytes += bytes_to_copy;
358
+}
359
+
360
+static void test_capture_destroy(void *opaque)
361
+{
362
+ TestCaptureState *s = opaque;
363
+ s->destroy_called = true;
364
+}
365
+
366
+/*
367
+ * Compare captured audio with expected sine wave.
368
+ * Returns the number of matching samples (within tolerance).
369
+ */
370
+static int compare_sine_samples(const int16_t *captured, int frames,
371
+ int64_t start_frame, int tolerance)
372
+{
373
+ int matching = 0;
374
+
375
+ for (int i = 0; i < frames; i++) {
376
+ double t = (double)(start_frame + i) / SAMPLE_RATE;
377
+ double sample = sin(2.0 * M_PI * FREQUENCY * t);
378
+ int16_t expected = (int16_t)(sample * 32767.0);
379
+
380
+ /* Check left channel */
381
+ if (abs(captured[i * 2] - expected) <= tolerance) {
382
+ matching++;
383
+ }
384
+ /* Check right channel */
385
+ if (abs(captured[i * 2 + 1] - expected) <= tolerance) {
386
+ matching++;
387
+ }
388
+ }
389
+
390
+ return matching;
391
+}
392
+
393
+static void test_audio_capture(void)
394
+{
395
+ AudioBackend *be;
396
+ CaptureVoiceOut *cap;
397
+ SWVoiceOut *voice;
398
+ TestCaptureState state = {0};
399
+ TestSineState sine_state = {0};
400
+ struct audsettings as = default_test_settings;
401
+ struct audio_capture_ops ops = {
402
+ .notify = test_capture_notify,
403
+ .capture = test_capture_capture,
404
+ .destroy = test_capture_destroy,
405
+ };
406
+ int64_t start_time;
407
+ int64_t elapsed_ms;
408
+ int captured_frames;
409
+ int matching_samples;
410
+ int total_samples;
411
+ double match_ratio;
412
+
413
+ be = get_test_audio_backend();
414
+
415
+ state.captured_samples = g_malloc0(CAPTURE_BUFFER_SIZE);
416
+ state.captured_bytes = 0;
417
+ state.capture_buffer_size = CAPTURE_BUFFER_SIZE;
418
+
419
+ cap = AUD_add_capture(be, &as, &ops, &state);
420
+ g_assert_nonnull(cap);
421
+
422
+ sine_state.be = be;
423
+ sine_state.total_frames = CAPTURE_BUFFER_FRAMES;
424
+ sine_state.frames_written = 0;
425
+
426
+ voice = open_test_voice_out(be, "test-capture-sine",
427
+ &sine_state, test_sine_callback);
428
+ sine_state.voice = voice;
429
+
430
+ AUD_set_active_out(voice, true);
431
+
432
+ start_time = g_get_monotonic_time();
433
+ while (sine_state.frames_written < sine_state.total_frames ||
434
+ state.captured_bytes < CAPTURE_BUFFER_SIZE) {
435
+ audio_run(be, "test-capture");
436
+ main_loop_wait(true);
437
+
438
+ elapsed_ms = (g_get_monotonic_time() - start_time) / 1000;
439
+ if (elapsed_ms > 1000) { /* 1 second timeout */
440
+ break;
441
+ }
442
+
443
+ g_usleep(G_USEC_PER_SEC / 1000); /* 1ms */
444
+ }
445
+
446
+ g_test_message("Wrote %" PRId64 " frames, captured %zu bytes",
447
+ sine_state.frames_written, state.captured_bytes);
448
+
449
+ g_assert_true(state.capture_called);
450
+ g_assert_cmpuint(state.captured_bytes, >, 0);
451
+
452
+ /* Compare captured data with expected sine wave */
453
+ captured_frames = state.captured_bytes / (CHANNELS * sizeof(int16_t));
454
+ if (captured_frames > 0) {
455
+ /*
456
+ * Allow some tolerance due to mixing/conversion.
457
+ * The tolerance accounts for potential rounding differences.
458
+ */
459
+ matching_samples = compare_sine_samples(state.captured_samples,
460
+ captured_frames, 0, 100);
461
+ total_samples = captured_frames * CHANNELS;
462
+ match_ratio = (double)matching_samples / total_samples;
463
+
464
+ g_test_message("Captured %d frames, %d/%d samples match (%.1f%%)",
465
+ captured_frames, matching_samples, total_samples,
466
+ match_ratio * 100.0);
467
+
468
+ /*
469
+ * Expect at least 90% of samples to match within tolerance.
470
+ * Some variation is expected due to mixing engine processing.
471
+ */
472
+ g_assert_cmpfloat(match_ratio, >=, 0.9);
473
+ }
474
+
475
+ AUD_set_active_out(voice, false);
476
+ AUD_close_out(be, voice);
477
+
478
+ AUD_del_capture(cap, &state);
479
+ g_assert_true(state.destroy_called);
480
+
481
+ g_free(state.captured_samples);
482
+}
483
+
484
+static void test_audio_null_handling(void)
485
+{
486
+ uint8_t buffer[64];
487
+
488
+ /* AUD_is_active_out/in(NULL) should return false */
489
+ g_assert_false(AUD_is_active_out(NULL));
490
+ g_assert_false(AUD_is_active_in(NULL));
491
+
492
+ /* AUD_get_buffer_size_out(NULL) should return 0 */
493
+ g_assert_cmpint(AUD_get_buffer_size_out(NULL), ==, 0);
494
+
495
+ /* AUD_write/read(NULL, ...) should return size (no-op) */
496
+ g_assert_cmpuint(AUD_write(NULL, buffer, sizeof(buffer)), ==,
497
+ sizeof(buffer));
498
+ g_assert_cmpuint(AUD_read(NULL, buffer, sizeof(buffer)), ==,
499
+ sizeof(buffer));
500
+
501
+ /* These should not crash */
502
+ AUD_set_active_out(NULL, true);
503
+ AUD_set_active_out(NULL, false);
504
+ AUD_set_active_in(NULL, true);
505
+ AUD_set_active_in(NULL, false);
506
+}
507
+
508
+static void test_audio_multiple_voices(void)
509
+{
510
+ AudioBackend *be;
511
+ SWVoiceOut *out1, *out2;
512
+ SWVoiceIn *in1;
513
+
514
+ be = get_test_audio_backend();
515
+ out1 = open_test_voice_out(be, "test-multi-out1", NULL, dummy_audio_callback);
516
+ out2 = open_test_voice_out(be, "test-multi-out2", NULL, dummy_audio_callback);
517
+ in1 = open_test_voice_in(be, "test-multi-in1", NULL, dummy_audio_callback);
518
+
519
+ AUD_set_active_out(out1, true);
520
+ AUD_set_active_out(out2, true);
521
+ AUD_set_active_in(in1, true);
522
+
523
+ g_assert_true(AUD_is_active_out(out1));
524
+ g_assert_true(AUD_is_active_out(out2));
525
+ if (in1) {
526
+ g_assert_true(AUD_is_active_in(in1));
527
+ }
528
+
529
+ AUD_set_active_out(out1, false);
530
+ AUD_set_active_out(out2, false);
531
+ AUD_set_active_in(in1, false);
532
+
533
+ AUD_close_in(be, in1);
534
+ AUD_close_out(be, out2);
535
+ AUD_close_out(be, out1);
536
+}
537
+
538
+int main(int argc, char **argv)
539
+{
540
+ GOptionContext *context;
541
+ g_autoptr(GError) error = NULL;
542
+ g_autofree gchar *dir = NULL;
543
+ int ret;
544
+
545
+ context = g_option_context_new("- QEMU audio test");
546
+ g_option_context_add_main_entries(context, test_options, NULL);
547
+
548
+ if (!g_option_context_parse(context, &argc, &argv, &error)) {
549
+ g_printerr("Option parsing failed: %s\n", error->message);
550
+ return 1;
551
+ }
552
+ g_option_context_free(context);
553
+
554
+ g_test_init(&argc, &argv, NULL);
555
+
556
+ module_call_init(MODULE_INIT_TRACE);
557
+ qemu_add_opts(&qemu_trace_opts);
558
+ if (opt_trace) {
559
+ trace_opt_parse(opt_trace);
560
+ qemu_set_log(LOG_TRACE, &error_fatal);
561
+ }
562
+ trace_init_backends();
563
+ trace_init_file();
564
+
565
+ dir = g_test_build_filename(G_TEST_BUILT, "..", "..", NULL);
566
+ g_setenv("QEMU_MODULE_DIR", dir, true);
567
+ qemu_init_exec_dir(argv[0]);
568
+ module_call_init(MODULE_INIT_QOM);
569
+ module_init_info(qemu_modinfo);
570
+
571
+ qemu_init_main_loop(&error_abort);
572
+ if (opt_audiodev) {
573
+ g_autofree gchar *spec = is_help_option(opt_audiodev) ?
574
+ opt_audiodev : g_strdup_printf("%s,id=%s", opt_audiodev, TEST_AUDIODEV_ID);
575
+ audio_parse_option(spec);
576
+ }
577
+ audio_create_default_audiodevs();
578
+ audio_init_audiodevs();
579
+
580
+ g_test_add_func("/audio/prio-list", test_audio_prio_list);
581
+
582
+ g_test_add_func("/audio/out/active-state", test_audio_out_active_state);
583
+ g_test_add_func("/audio/out/sine-wave", test_audio_out_sine_wave);
584
+ g_test_add_func("/audio/out/buffer-size", test_audio_out_buffer_size);
585
+ g_test_add_func("/audio/out/volume", test_audio_out_volume);
586
+ g_test_add_func("/audio/out/capture", test_audio_capture);
587
+
588
+ g_test_add_func("/audio/in/active-state", test_audio_in_active_state);
589
+ g_test_add_func("/audio/in/volume", test_audio_in_volume);
590
+
591
+ g_test_add_func("/audio/null-handling", test_audio_null_handling);
592
+ g_test_add_func("/audio/multiple-voices", test_audio_multiple_voices);
593
+
594
+ ret = g_test_run();
595
+
596
+ audio_cleanup();
597
+
598
+ return ret;
599
+}
tests/meson.build
+1
@@ -77,6 +77,7 @@ if 'CONFIG_TCG' in config_all_accel
77
subdir('tcg/plugins')
78
endif
79
80
+subdir('audio')
81
subdir('unit')
82
subdir('qapi-schema')
83
subdir('qtest')
ui/meson.build
+3
@@ -70,6 +70,7 @@ if opengl.found()
70
ui_modules += {'egl-headless' : egl_headless_ss}
71
endif
72
73
+dbus_display1 = files()
74
if dbus_display
75
dbus_ss = ss.source_set()
76
env = environment()
@@ -88,6 +89,8 @@ if dbus_display
89
'--interface-prefix', 'org.qemu.',
90
'--c-namespace', 'QemuDBus',
91
'--generate-c-code', '@BASENAME@'])
92
+ dbus_display1_h = declare_dependency(dependencies: [gio],
93
+ sources: dbus_display1[0])
94
dbus_ss.add(when: gio,
95
if_true: [files(
96
'dbus-chardev.c',