VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/utils/audio/vkat.cpp@ 89409

Last change on this file since 89409 was 89403, checked in by vboxsync, 4 years ago

Audio/ValKit: A bit of include path tweaking for VKAT. bugref:10008

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 75.6 KB
Line 
1/* $Id: vkat.cpp 89403 2021-05-31 13:47:54Z vboxsync $ */
2/** @file
3 * Validation Kit Audio Test (VKAT) utility for testing and validating the audio stack.
4 */
5
6/*
7 * Copyright (C) 2021 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#include <iprt/buildconfig.h>
32#include <iprt/ctype.h>
33#include <iprt/dir.h>
34#include <iprt/errcore.h>
35#include <iprt/initterm.h>
36#include <iprt/getopt.h>
37#include <iprt/message.h>
38#include <iprt/path.h>
39#include <iprt/process.h>
40#include <iprt/rand.h>
41#include <iprt/stream.h>
42#include <iprt/string.h>
43#include <iprt/uuid.h>
44#include <iprt/test.h>
45
46#include <package-generated.h>
47#include "product-generated.h"
48
49#include <VBox/version.h>
50#include <VBox/log.h>
51
52#ifdef RT_OS_WINDOWS
53# include <iprt/win/windows.h> /* for CoInitializeEx */
54#endif
55
56/**
57 * Internal driver instance data
58 * @note This must be put here as it's needed before pdmdrv.h is included.
59 */
60typedef struct PDMDRVINSINT
61{
62 /** The stack the drive belongs to. */
63 struct AUDIOTESTDRVSTACK *pStack;
64} PDMDRVINSINT;
65#define PDMDRVINSINT_DECLARED
66
67#include <VBox/vmm/pdmaudioinline.h>
68#include <VBox/vmm/pdmaudiohostenuminline.h>
69
70#include "Audio/AudioHlp.h"
71#include "Audio/AudioTest.h"
72#include "Audio/AudioTestService.h"
73#include "Audio/AudioTestServiceClient.h"
74
75#include "VBoxDD.h"
76
77#include "vkatInternal.h"
78
79
80/*********************************************************************************************************************************
81* Defined Constants And Macros *
82*********************************************************************************************************************************/
83/** For use in the option switch to handle common options. */
84#define AUDIO_TEST_COMMON_OPTION_CASES(a_ValueUnion) \
85 case 'q': \
86 g_uVerbosity = 0; \
87 if (g_pRelLogger) \
88 RTLogGroupSettings(g_pRelLogger, "all=0 all.e"); \
89 break; \
90 \
91 case 'v': \
92 g_uVerbosity++; \
93 if (g_pRelLogger) \
94 RTLogGroupSettings(g_pRelLogger, g_uVerbosity == 1 ? "all.e.l" : g_uVerbosity == 2 ? "all.e.l.f" : "all=~0"); \
95 break; \
96 \
97 case 'V': \
98 return audioTestVersion(); \
99 \
100 case 'h': \
101 return audioTestUsage(g_pStdOut); \
102 \
103 case AUDIO_TEST_OPT_CMN_DEBUG_AUDIO_ENABLE: \
104 g_fDrvAudioDebug = true; \
105 break; \
106 \
107 case AUDIO_TEST_OPT_CMN_DEBUG_AUDIO_PATH: \
108 g_pszDrvAudioDebug = (a_ValueUnion).psz; \
109 break
110
111
112/*********************************************************************************************************************************
113* Structures and Typedefs *
114*********************************************************************************************************************************/
115/**
116 * Enumeration specifying the current audio test mode.
117 */
118typedef enum AUDIOTESTMODE
119{
120 /** Unknown mode. */
121 AUDIOTESTMODE_UNKNOWN = 0,
122 /** VKAT is running on the guest side. */
123 AUDIOTESTMODE_GUEST,
124 /** VKAT is running on the host side. */
125 AUDIOTESTMODE_HOST
126} AUDIOTESTMODE;
127
128struct AUDIOTESTENV;
129/** Pointer a audio test environment. */
130typedef AUDIOTESTENV *PAUDIOTESTENV;
131
132struct AUDIOTESTDESC;
133/** Pointer a audio test descriptor. */
134typedef AUDIOTESTDESC *PAUDIOTESTDESC;
135
136/**
137 * Callback to set up the test parameters for a specific test.
138 *
139 * @returns IPRT status code.
140 * @retval VINF_SUCCESS if setting the parameters up succeeded. Any other error code
141 * otherwise indicating the kind of error.
142 * @param pszTest Test name.
143 * @param pTstParmsAcq The audio test parameters to set up.
144 */
145typedef DECLCALLBACKTYPE(int, FNAUDIOTESTSETUP,(PAUDIOTESTENV pTstEnv, PAUDIOTESTDESC pTstDesc, PAUDIOTESTPARMS pTstParmsAcq, void **ppvCtx));
146/** Pointer to an audio test setup callback. */
147typedef FNAUDIOTESTSETUP *PFNAUDIOTESTSETUP;
148
149typedef DECLCALLBACKTYPE(int, FNAUDIOTESTEXEC,(PAUDIOTESTENV pTstEnv, void *pvCtx, PAUDIOTESTPARMS pTstParms));
150/** Pointer to an audio test exec callback. */
151typedef FNAUDIOTESTEXEC *PFNAUDIOTESTEXEC;
152
153typedef DECLCALLBACKTYPE(int, FNAUDIOTESTDESTROY,(PAUDIOTESTENV pTstEnv, void *pvCtx));
154/** Pointer to an audio test destroy callback. */
155typedef FNAUDIOTESTDESTROY *PFNAUDIOTESTDESTROY;
156
157/**
158 * Structure for keeping an audio test audio stream.
159 */
160typedef struct AUDIOTESTSTREAM
161{
162 /** The PDM stream. */
163 PPDMAUDIOSTREAM pStream;
164 /** The backend stream. */
165 PPDMAUDIOBACKENDSTREAM pBackend;
166 /** The stream config. */
167 PDMAUDIOSTREAMCFG Cfg;
168} AUDIOTESTSTREAM;
169/** Pointer to audio test stream. */
170typedef AUDIOTESTSTREAM *PAUDIOTESTSTREAM;
171
172/**
173 * Audio test environment parameters.
174 * Not necessarily bound to a specific test (can be reused).
175 */
176typedef struct AUDIOTESTENV
177{
178 /** Audio testing mode. */
179 AUDIOTESTMODE enmMode;
180 /** Output path for storing the test environment's final test files. */
181 char szPathOut[RTPATH_MAX];
182 /** Temporary path for this test environment. */
183 char szPathTemp[RTPATH_MAX];
184 /** The audio test driver stack. */
185 AUDIOTESTDRVSTACK DrvStack;
186 /** The current (last) audio device enumeration to use. */
187 PDMAUDIOHOSTENUM DevEnum;
188 /** Audio stream. */
189 AUDIOTESTSTREAM aStreams[AUDIOTESTENV_MAX_STREAMS];
190 /** The audio test set to use. */
191 AUDIOTESTSET Set;
192 union
193 {
194 struct
195 {
196 /** ATS instance to use. */
197 ATSSERVER Srv;
198 } Guest;
199 struct
200 {
201 ATSCLIENT Client;
202 } Host;
203 } u;
204} AUDIOTESTENV;
205
206/**
207 * Audio test descriptor.
208 */
209typedef struct AUDIOTESTDESC
210{
211 /** (Sort of) Descriptive test name. */
212 const char *pszName;
213 /** Flag whether the test is excluded. */
214 bool fExcluded;
215 /** The setup callback. */
216 PFNAUDIOTESTSETUP pfnSetup;
217 /** The exec callback. */
218 PFNAUDIOTESTEXEC pfnExec;
219 /** The destruction callback. */
220 PFNAUDIOTESTDESTROY pfnDestroy;
221} AUDIOTESTDESC;
222
223/**
224 * Structure for keeping a user context for the test service callbacks.
225 */
226typedef struct ATSCALLBACKCTX
227{
228 PAUDIOTESTENV pTstEnv;
229} ATSCALLBACKCTX;
230typedef ATSCALLBACKCTX *PATSCALLBACKCTX;
231
232
233/*********************************************************************************************************************************
234* Internal Functions *
235*********************************************************************************************************************************/
236static int audioTestCombineParms(PAUDIOTESTPARMS pBaseParms, PAUDIOTESTPARMS pOverrideParms);
237static int audioTestPlayTone(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PAUDIOTESTTONEPARMS pParms);
238static int audioTestStreamDestroy(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream);
239
240static RTEXITCODE audioTestUsage(PRTSTREAM pStrm);
241static RTEXITCODE audioTestVersion(void);
242
243
244/*********************************************************************************************************************************
245* Global Variables *
246*********************************************************************************************************************************/
247/**
248 * Common long options values.
249 */
250enum
251{
252 AUDIO_TEST_OPT_CMN_DEBUG_AUDIO_ENABLE = 256,
253 AUDIO_TEST_OPT_CMN_DEBUG_AUDIO_PATH
254};
255
256/**
257 * Long option values for the 'test' command.
258 */
259enum
260{
261 VKAT_TEST_OPT_COUNT = 900,
262 VKAT_TEST_OPT_DEV,
263 VKAT_TEST_OPT_ATS_ADDR,
264 VKAT_TEST_OPT_ATS_PORT,
265 VKAT_TEST_OPT_MODE,
266 VKAT_TEST_OPT_OUTDIR,
267 VKAT_TEST_OPT_PAUSE,
268 VKAT_TEST_OPT_PCM_HZ,
269 VKAT_TEST_OPT_PCM_BIT,
270 VKAT_TEST_OPT_PCM_CHAN,
271 VKAT_TEST_OPT_PCM_SIGNED,
272 VKAT_TEST_OPT_TAG,
273 VKAT_TEST_OPT_TEMPDIR,
274 VKAT_TEST_OPT_VOL
275};
276
277/**
278 * Long option values for the 'verify' command.
279 */
280enum
281{
282 VKAT_VERIFY_OPT_TAG = 900
283};
284
285/**
286 * Long option values for the 'selftest' command.
287 */
288enum
289{
290 VKAT_SELFTEST_OPT_ATS_HOST = 900
291};
292
293/**
294 * Common command line parameters.
295 */
296static const RTGETOPTDEF g_aCmdCommonOptions[] =
297{
298 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
299 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
300 { "--debug-audio", AUDIO_TEST_OPT_CMN_DEBUG_AUDIO_ENABLE, RTGETOPT_REQ_NOTHING },
301 { "--debug-audio-path", AUDIO_TEST_OPT_CMN_DEBUG_AUDIO_PATH, RTGETOPT_REQ_STRING },
302};
303
304/**
305 * Command line parameters for test mode.
306 */
307static const RTGETOPTDEF g_aCmdTestOptions[] =
308{
309 { "--backend", 'b', RTGETOPT_REQ_STRING },
310 { "--drvaudio", 'd', RTGETOPT_REQ_NOTHING },
311 { "--exclude", 'e', RTGETOPT_REQ_UINT32 },
312 { "--exclude-all", 'a', RTGETOPT_REQ_NOTHING },
313 { "--mode", VKAT_TEST_OPT_MODE, RTGETOPT_REQ_STRING },
314 { "--ats-address", VKAT_TEST_OPT_ATS_ADDR, RTGETOPT_REQ_STRING },
315 { "--ats-port", VKAT_TEST_OPT_ATS_PORT, RTGETOPT_REQ_UINT32 },
316 { "--include", 'i', RTGETOPT_REQ_UINT32 },
317 { "--outdir", VKAT_TEST_OPT_OUTDIR, RTGETOPT_REQ_STRING },
318 { "--count", VKAT_TEST_OPT_COUNT, RTGETOPT_REQ_UINT32 },
319 { "--device", VKAT_TEST_OPT_DEV, RTGETOPT_REQ_STRING },
320 { "--pause", VKAT_TEST_OPT_PAUSE, RTGETOPT_REQ_UINT32 },
321 { "--pcm-bit", VKAT_TEST_OPT_PCM_BIT, RTGETOPT_REQ_UINT8 },
322 { "--pcm-chan", VKAT_TEST_OPT_PCM_CHAN, RTGETOPT_REQ_UINT8 },
323 { "--pcm-hz", VKAT_TEST_OPT_PCM_HZ, RTGETOPT_REQ_UINT16 },
324 { "--pcm-signed", VKAT_TEST_OPT_PCM_SIGNED, RTGETOPT_REQ_BOOL },
325 { "--tag", VKAT_TEST_OPT_TAG, RTGETOPT_REQ_STRING },
326 { "--tempdir", VKAT_TEST_OPT_TEMPDIR, RTGETOPT_REQ_STRING },
327 { "--volume", VKAT_TEST_OPT_VOL, RTGETOPT_REQ_UINT8 }
328};
329
330/**
331 * Command line parameters for verification mode.
332 */
333static const RTGETOPTDEF g_aCmdVerifyOptions[] =
334{
335 { "--tag", VKAT_VERIFY_OPT_TAG, RTGETOPT_REQ_STRING }
336};
337
338/**
339 * Backends.
340 *
341 * @note The first backend in the array is the default one for the platform.
342 */
343static struct
344{
345 /** The driver registration structure. */
346 PCPDMDRVREG pDrvReg;
347 /** The backend name.
348 * Aliases are implemented by having multiple entries for the same backend. */
349 const char *pszName;
350} const g_aBackends[] =
351{
352#if defined(VBOX_WITH_AUDIO_ALSA) && defined(RT_OS_LINUX)
353 { &g_DrvHostALSAAudio, "alsa" },
354#endif
355#ifdef VBOX_WITH_AUDIO_PULSE
356 { &g_DrvHostPulseAudio, "pulseaudio" },
357 { &g_DrvHostPulseAudio, "pulse" },
358 { &g_DrvHostPulseAudio, "pa" },
359#endif
360#ifdef VBOX_WITH_AUDIO_OSS
361 { &g_DrvHostOSSAudio, "oss" },
362#endif
363#if defined(RT_OS_DARWIN)
364 { &g_DrvHostCoreAudio, "coreaudio" },
365 { &g_DrvHostCoreAudio, "core" },
366 { &g_DrvHostCoreAudio, "ca" },
367#endif
368#if defined(RT_OS_WINDOWS)
369 { &g_DrvHostAudioWas, "wasapi" },
370 { &g_DrvHostAudioWas, "was" },
371 { &g_DrvHostDSound, "directsound" },
372 { &g_DrvHostDSound, "dsound" },
373 { &g_DrvHostDSound, "ds" },
374#endif
375 { &g_DrvHostValidationKitAudio, "valkit" }
376};
377AssertCompile(sizeof(g_aBackends) > 0 /* port me */);
378
379static volatile bool g_fTerminated = false;
380/** The release logger. */
381static PRTLOGGER g_pRelLogger = NULL;
382
383
384/** The test handle. */
385RTTEST g_hTest;
386/** The current verbosity level. */
387unsigned g_uVerbosity = 0;
388/** DrvAudio: Enable debug (or not). */
389bool g_fDrvAudioDebug = 0;
390/** DrvAudio: The debug output path. */
391const char *g_pszDrvAudioDebug = NULL;
392
393
394/*********************************************************************************************************************************
395* ATS Callback Implementations *
396*********************************************************************************************************************************/
397
398/**
399 * Note: Called within server (client serving) thread.
400 */
401static DECLCALLBACK(int) audioTestSvcTonePlayCallback(void const *pvUser, PPDMAUDIOSTREAMCFG pStreamCfg, PAUDIOTESTTONEPARMS pToneParms)
402{
403 PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser;
404 PAUDIOTESTENV pTstEnv = pCtx->pTstEnv;
405
406 AUDIOTESTTONE TstTone;
407 AudioTestToneInitRandom(&TstTone, &pStreamCfg->Props);
408
409 int rc;
410
411 const PPDMAUDIOSTREAM pStream = pTstEnv->aStreams[0].pStream; /** @todo Make this dynamic. */
412
413 if (audioTestDriverStackStreamIsOkay(&pTstEnv->DrvStack, pStream))
414 {
415 uint32_t cbBuf;
416 uint8_t abBuf[_4K];
417
418 const uint64_t tsStartMs = RTTimeMilliTS();
419 const uint16_t cSchedulingMs = RTRandU32Ex(10, 80); /* Chose a random scheduling (in ms). */
420 const uint32_t cbPerMs = PDMAudioPropsMilliToBytes(&pStream->Props, cSchedulingMs);
421
422 do
423 {
424 rc = AudioTestToneGenerate(&TstTone, abBuf, RT_MIN(cbPerMs, sizeof(abBuf)), &cbBuf);
425 if (RT_SUCCESS(rc))
426 {
427 uint32_t cbWritten;
428 rc = audioTestDriverStackStreamPlay(&pTstEnv->DrvStack, pStream, abBuf, cbBuf, &cbWritten);
429 }
430
431 if (RTTimeMilliTS() - tsStartMs >= pToneParms->msDuration)
432 break;
433
434 if (RT_FAILURE(rc))
435 break;
436
437 RTThreadSleep(cSchedulingMs);
438
439 } while (RT_SUCCESS(rc));
440 }
441 else
442 rc = VERR_AUDIO_STREAM_NOT_READY;
443
444 return rc;
445}
446
447
448/*********************************************************************************************************************************
449* Implementation of audio test environment handling *
450*********************************************************************************************************************************/
451
452/**
453 * Initializes an audio test environment.
454 *
455 * @param pTstEnv Audio test environment to initialize.
456 * @param pDrvReg Audio driver to use.
457 * @param fWithDrvAudio Whether to include DrvAudio in the stack or not.
458 * @param pszTag Tag name to use. If NULL, a generated UUID will be used.
459 * @param pszTcpAddr TCP/IP address to connect to.
460 * @param uTcpPort TCP/IP port to connect to.
461 */
462static int audioTestEnvInit(PAUDIOTESTENV pTstEnv,
463 PCPDMDRVREG pDrvReg, bool fWithDrvAudio, const char *pszTag,
464 const char *pszTcpAddr, uint32_t uTcpPort)
465{
466 PDMAudioHostEnumInit(&pTstEnv->DevEnum);
467
468 int rc = audioTestDriverStackInit(&pTstEnv->DrvStack, pDrvReg, fWithDrvAudio);
469 if (RT_FAILURE(rc))
470 return rc;
471
472 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test mode is '%s'\n", pTstEnv->enmMode == AUDIOTESTMODE_HOST ? "host" : "guest");
473 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Using tag '%s'\n", pszTag);
474 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Output directory is '%s'\n", pTstEnv->szPathOut);
475 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Temp directory is '%s'\n", pTstEnv->szPathTemp);
476
477 char szPathTemp[RTPATH_MAX];
478 if ( !strlen(pTstEnv->szPathTemp)
479 || !strlen(pTstEnv->szPathOut))
480 rc = RTPathTemp(szPathTemp, sizeof(szPathTemp));
481
482 if ( RT_SUCCESS(rc)
483 && !strlen(pTstEnv->szPathTemp))
484 rc = RTPathJoin(pTstEnv->szPathTemp, sizeof(pTstEnv->szPathTemp), szPathTemp, "vkat-temp");
485
486 if ( RT_SUCCESS(rc)
487 && !strlen(pTstEnv->szPathOut))
488 rc = RTPathJoin(pTstEnv->szPathOut, sizeof(pTstEnv->szPathOut), szPathTemp, "vkat");
489
490 if (RT_SUCCESS(rc))
491 rc = AudioTestSetCreate(&pTstEnv->Set, pTstEnv->szPathTemp, pszTag);
492
493 if (RT_FAILURE(rc))
494 return rc;
495
496 /** @todo Implement NAT mode like we do for TxS later? */
497 if (pTstEnv->enmMode == AUDIOTESTMODE_GUEST)
498 {
499 ATSCALLBACKCTX Ctx;
500 Ctx.pTstEnv = pTstEnv;
501
502 ATSCALLBACKS Callbacks;
503 Callbacks.pfnTonePlay = audioTestSvcTonePlayCallback;
504 Callbacks.pvUser = &Ctx;
505
506 RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Starting ATS ...\n");
507 rc = AudioTestSvcInit(&pTstEnv->u.Guest.Srv, &Callbacks);
508 if (RT_SUCCESS(rc))
509 rc = AudioTestSvcStart(&pTstEnv->u.Guest.Srv);
510
511 if (RT_FAILURE(rc))
512 {
513 RTTestFailed(g_hTest, "Initializing ATS failed with %Rrc\n", rc);
514 return rc;
515 }
516
517 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "ATS running\n");
518
519 while (!g_fTerminated) /** @todo Implement signal handling. */
520 {
521 RTThreadSleep(100);
522 }
523
524 RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Shutting down ATS ...\n");
525
526 int rc2 = AudioTestSvcShutdown(&pTstEnv->u.Guest.Srv);
527 if (RT_SUCCESS(rc))
528 rc = rc2;
529 }
530 else
531 {
532 RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Connecting to ATS at %s:%RU32 ...\n", pszTcpAddr, uTcpPort);
533 rc = AudioTestSvcClientConnect(&pTstEnv->u.Host.Client, pszTcpAddr, uTcpPort);
534 if (RT_FAILURE(rc))
535 {
536 RTTestFailed(g_hTest, "Connecting to ATS failed with %Rrc\n", rc);
537 return rc;
538 }
539 }
540
541 audioTestDriverStackDelete(&pTstEnv->DrvStack);
542
543 return rc;
544}
545
546/**
547 * Destroys an audio test environment.
548 *
549 * @param pTstEnv Audio test environment to destroy.
550 */
551static void audioTestEnvDestroy(PAUDIOTESTENV pTstEnv)
552{
553 if (!pTstEnv)
554 return;
555
556 PDMAudioHostEnumDelete(&pTstEnv->DevEnum);
557
558 for (unsigned i = 0; i < RT_ELEMENTS(pTstEnv->aStreams); i++)
559 {
560 int rc2 = audioTestStreamDestroy(pTstEnv, &pTstEnv->aStreams[i]);
561 if (RT_FAILURE(rc2))
562 RTTestFailed(g_hTest, "Stream destruction for stream #%u failed with %Rrc\n", i, rc2);
563 }
564
565 AudioTestSetDestroy(&pTstEnv->Set);
566 audioTestDriverStackDelete(&pTstEnv->DrvStack);
567}
568
569/**
570 * Initializes an audio test parameters set.
571 *
572 * @param pTstParms Test parameters set to initialize.
573 */
574static void audioTestParmsInit(PAUDIOTESTPARMS pTstParms)
575{
576 RT_ZERO(*pTstParms);
577}
578
579/**
580 * Destroys an audio test parameters set.
581 *
582 * @param pTstParms Test parameters set to destroy.
583 */
584static void audioTestParmsDestroy(PAUDIOTESTPARMS pTstParms)
585{
586 if (!pTstParms)
587 return;
588
589 return;
590}
591
592
593/*********************************************************************************************************************************
594* Device enumeration + handling. *
595*********************************************************************************************************************************/
596
597/**
598 * Enumerates audio devices and optionally searches for a specific device.
599 *
600 * @returns VBox status code.
601 * @param pTstEnv Test env to use for enumeration.
602 * @param pszDev Device name to search for. Can be NULL if the default device shall be used.
603 * @param ppDev Where to return the pointer of the device enumeration of \a pTstEnv when a
604 * specific device was found.
605 */
606static int audioTestDevicesEnumerateAndCheck(PAUDIOTESTENV pTstEnv, const char *pszDev, PPDMAUDIOHOSTDEV *ppDev)
607{
608 RTTestSubF(g_hTest, "Enumerating audio devices and checking for device '%s'", pszDev ? pszDev : "<Default>");
609
610 if (!pTstEnv->DrvStack.pIHostAudio->pfnGetDevices)
611 {
612 RTTestSkipped(g_hTest, "Backend does not support device enumeration, skipping");
613 return VINF_NOT_SUPPORTED;
614 }
615
616 Assert(pszDev == NULL || ppDev);
617
618 if (ppDev)
619 *ppDev = NULL;
620
621 int rc = pTstEnv->DrvStack.pIHostAudio->pfnGetDevices(pTstEnv->DrvStack.pIHostAudio, &pTstEnv->DevEnum);
622 if (RT_SUCCESS(rc))
623 {
624 PPDMAUDIOHOSTDEV pDev;
625 RTListForEach(&pTstEnv->DevEnum.LstDevices, pDev, PDMAUDIOHOSTDEV, ListEntry)
626 {
627 char szFlags[PDMAUDIOHOSTDEV_MAX_FLAGS_STRING_LEN];
628 if (pDev->pszId)
629 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Device '%s' (ID '%s'):\n", pDev->szName, pDev->pszId);
630 else
631 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Device '%s':\n", pDev->szName);
632 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Usage = %s\n", PDMAudioDirGetName(pDev->enmUsage));
633 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Flags = %s\n", PDMAudioHostDevFlagsToString(szFlags, pDev->fFlags));
634 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Input channels = %RU8\n", pDev->cMaxInputChannels);
635 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Output channels = %RU8\n", pDev->cMaxOutputChannels);
636
637 if ( pszDev
638 && !RTStrCmp(pDev->szName, pszDev))
639 {
640 *ppDev = pDev;
641 }
642 }
643 }
644 else
645 RTTestFailed(g_hTest, "Enumerating audio devices failed with %Rrc", rc);
646
647 RTTestSubDone(g_hTest);
648
649 if ( pszDev
650 && *ppDev == NULL)
651 {
652 RTTestFailed(g_hTest, "Audio device '%s' not found", pszDev);
653 return VERR_NOT_FOUND;
654 }
655
656 return VINF_SUCCESS;
657}
658
659/**
660 * Opens an audio device.
661 *
662 * @returns VBox status code.
663 * @param pDev Audio device to open.
664 */
665static int audioTestDeviceOpen(PPDMAUDIOHOSTDEV pDev)
666{
667 int rc = VINF_SUCCESS;
668
669 RTTestSubF(g_hTest, "Opening audio device '%s' ...", pDev->szName);
670
671 /** @todo Detect + open device here. */
672
673 RTTestSubDone(g_hTest);
674
675 return rc;
676}
677
678/**
679 * Closes an audio device.
680 *
681 * @returns VBox status code.
682 * @param pDev Audio device to close.
683 */
684static int audioTestDeviceClose(PPDMAUDIOHOSTDEV pDev)
685{
686 int rc = VINF_SUCCESS;
687
688 RTTestSubF(g_hTest, "Closing audio device '%s' ...", pDev->szName);
689
690 /** @todo Close device here. */
691
692 RTTestSubDone(g_hTest);
693
694 return rc;
695}
696
697/**
698 * Destroys an audio test stream.
699 *
700 * @returns VBox status code.
701 * @param pTstEnv Test environment the stream to destroy contains.
702 * @param pStream Audio stream to destroy.
703 */
704static int audioTestStreamDestroy(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream)
705{
706 int rc = VINF_SUCCESS;
707 if (pStream && pStream->pStream)
708 {
709 /** @todo Anything else to do here, e.g. test if there are left over samples or some such? */
710
711 audioTestDriverStackStreamDestroy(&pTstEnv->DrvStack, pStream->pStream);
712 pStream->pStream = NULL;
713 pStream->pBackend = NULL;
714 }
715
716 return rc;
717}
718
719/**
720 * Creates an audio default input (recording) test stream.
721 * Convenience function.
722 *
723 * @returns VBox status code.
724 * @param pTstEnv Test environment to use for creating the stream.
725 * @param pStream Audio stream to create.
726 * @param pProps PCM properties to use for creation.
727 */
728static int audioTestCreateStreamDefaultIn(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PPDMAUDIOPCMPROPS pProps)
729{
730 pStream->pBackend = NULL;
731 int rc = audioTestDriverStackStreamCreateInput(&pTstEnv->DrvStack, pProps, 300 /*cMsBufferSize*/, 150 /*cMsPreBuffer*/,
732 10 /*cMsSchedulingHint*/, &pStream->pStream, &pStream->Cfg);
733 if (RT_SUCCESS(rc) && !pTstEnv->DrvStack.pIAudioConnector)
734 pStream->pBackend = &((PAUDIOTESTDRVSTACKSTREAM)pStream->pStream)->Backend;
735 return rc;
736}
737
738/**
739 * Records a test tone from a specific audio test stream.
740 *
741 * @returns VBox status code.
742 * @param pTstEnv Test environment to use for running the test.
743 * @param pStream Stream to use for recording the tone.
744 * @param pParms Tone parameters to use.
745 *
746 * @note Blocking function.
747 */
748static int audioTestRecordTone(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PAUDIOTESTTONEPARMS pParms)
749{
750 const char *pcszPathOut = pTstEnv->Set.szPathAbs;
751
752 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Recording test tone (for %RU32ms)\n", pParms->msDuration);
753 RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Writing to '%s'\n", pcszPathOut);
754
755 /** @todo Use .WAV here? */
756 PAUDIOTESTOBJ pObj;
757 int rc = AudioTestSetObjCreateAndRegister(&pTstEnv->Set, "tone-rec.pcm", &pObj);
758 AssertRCReturn(rc, rc);
759
760 if (audioTestDriverStackStreamIsOkay(&pTstEnv->DrvStack, pStream->pStream))
761 {
762 uint8_t abBuf[_4K];
763
764 const uint64_t tsStartMs = RTTimeMilliTS();
765 const uint16_t cSchedulingMs = RTRandU32Ex(10, 80); /* Choose a random scheduling (in ms). */
766
767 do
768 {
769 uint32_t cbRead = 0;
770 rc = audioTestDriverStackStreamCapture(&pTstEnv->DrvStack, pStream->pStream, (void *)abBuf, sizeof(abBuf), &cbRead);
771 if (RT_SUCCESS(rc))
772 rc = AudioTestSetObjWrite(pObj, abBuf, cbRead);
773
774 if (RT_FAILURE(rc))
775 break;
776
777 if (RTTimeMilliTS() - tsStartMs >= pParms->msDuration)
778 break;
779
780 RTThreadSleep(cSchedulingMs);
781
782 } while (RT_SUCCESS(rc));
783 }
784 else
785 rc = VERR_AUDIO_STREAM_NOT_READY;
786
787 int rc2 = AudioTestSetObjClose(pObj);
788 if (RT_SUCCESS(rc))
789 rc = rc2;
790
791 return rc;
792}
793
794/**
795 * Creates an audio default output (playback) test stream.
796 * Convenience function.
797 *
798 * @returns VBox status code.
799 * @param pTstEnv Test environment to use for creating the stream.
800 * @param pStream Audio stream to create.
801 * @param pProps PCM properties to use for creation.
802 */
803static int audioTestCreateStreamDefaultOut(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PPDMAUDIOPCMPROPS pProps)
804{
805 pStream->pBackend = NULL;
806 int rc = audioTestDriverStackStreamCreateOutput(&pTstEnv->DrvStack, pProps, 300 /*cMsBufferSize*/, 200 /*cMsPreBuffer*/,
807 10 /*cMsSchedulingHint*/, &pStream->pStream, &pStream->Cfg);
808 if (RT_SUCCESS(rc) && !pTstEnv->DrvStack.pIAudioConnector)
809 pStream->pBackend = &((PAUDIOTESTDRVSTACKSTREAM)pStream->pStream)->Backend;
810 return rc;
811}
812
813/**
814 * Plays a test tone on a specific audio test stream.
815 *
816 * @returns VBox status code.
817 * @param pTstEnv Test environment to use for running the test.
818 * @param pStream Stream to use for playing the tone.
819 * @param pParms Tone parameters to use.
820 *
821 * @note Blocking function.
822 */
823static int audioTestPlayTone(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PAUDIOTESTTONEPARMS pParms)
824{
825 AUDIOTESTTONE TstTone;
826 AudioTestToneInit(&TstTone, &pParms->Props, pParms->dbFreqHz);
827
828 const char *pcszPathOut = pTstEnv->Set.szPathAbs;
829
830 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Playing test tone (tone frequency is %RU16Hz, %RU32ms)\n", (uint16_t)pParms->dbFreqHz, pParms->msDuration);
831 RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Writing to '%s'\n", pcszPathOut);
832
833 /** @todo Use .WAV here? */
834 PAUDIOTESTOBJ pObj;
835 int rc = AudioTestSetObjCreateAndRegister(&pTstEnv->Set, "tone-play.pcm", &pObj);
836 AssertRCReturn(rc, rc);
837
838 if (audioTestDriverStackStreamIsOkay(&pTstEnv->DrvStack, pStream->pStream))
839 {
840 uint32_t cbBuf;
841 uint8_t abBuf[_4K];
842
843 const uint64_t tsStartMs = RTTimeMilliTS();
844 const uint16_t cSchedulingMs = RTRandU32Ex(10, 80); /* Choose a random scheduling (in ms). */
845 const uint32_t cbPerMs = PDMAudioPropsMilliToBytes(&pParms->Props, cSchedulingMs);
846
847 do
848 {
849 rc = AudioTestToneGenerate(&TstTone, abBuf, RT_MIN(cbPerMs, sizeof(abBuf)), &cbBuf);
850 if (RT_SUCCESS(rc))
851 {
852 /* Write stuff to disk before trying to play it. Help analysis later. */
853 rc = AudioTestSetObjWrite(pObj, abBuf, cbBuf);
854 if (RT_SUCCESS(rc))
855 {
856 uint32_t cbWritten;
857 rc = audioTestDriverStackStreamPlay(&pTstEnv->DrvStack, pStream->pStream,
858 abBuf, cbBuf, &cbWritten);
859 }
860 }
861
862 if (RTTimeMilliTS() - tsStartMs >= pParms->msDuration)
863 break;
864
865 if (RT_FAILURE(rc))
866 break;
867
868 RTThreadSleep(cSchedulingMs);
869
870 } while (RT_SUCCESS(rc));
871 }
872 else
873 rc = VERR_AUDIO_STREAM_NOT_READY;
874
875 int rc2 = AudioTestSetObjClose(pObj);
876 if (RT_SUCCESS(rc))
877 rc = rc2;
878
879 return rc;
880}
881
882/**
883 * Overrides audio test base parameters with another set.
884 *
885 * @returns VBox status code.
886 * @param pBaseParms Base parameters to override.
887 * @param pOverrideParms Override parameters to use for overriding the base parameters.
888 *
889 * @note Overriding a parameter depends on its type / default values.
890 */
891static int audioTestCombineParms(PAUDIOTESTPARMS pBaseParms, PAUDIOTESTPARMS pOverrideParms)
892{
893 RT_NOREF(pBaseParms, pOverrideParms);
894
895 /** @todo Implement parameter overriding. */
896 return VERR_NOT_IMPLEMENTED;
897}
898
899
900/*********************************************************************************************************************************
901* Test callbacks *
902*********************************************************************************************************************************/
903
904/**
905 * @copydoc FNAUDIOTESTSETUP
906 */
907static DECLCALLBACK(int) audioTestPlayToneSetup(PAUDIOTESTENV pTstEnv, PAUDIOTESTDESC pTstDesc, PAUDIOTESTPARMS pTstParmsAcq, void **ppvCtx)
908{
909 RT_NOREF(pTstEnv, pTstDesc, ppvCtx);
910
911 pTstParmsAcq->enmType = AUDIOTESTTYPE_TESTTONE_PLAY;
912
913 PDMAudioPropsInit(&pTstParmsAcq->Props, 16 /* bit */ / 8, true /* fSigned */, 2 /* Channels */, 44100 /* Hz */);
914
915 pTstParmsAcq->enmDir = PDMAUDIODIR_OUT;
916#ifdef DEBUG
917 pTstParmsAcq->cIterations = 2;
918#else
919 pTstParmsAcq->cIterations = RTRandU32Ex(1, 10);
920#endif
921 pTstParmsAcq->idxCurrent = 0;
922
923 return VINF_SUCCESS;
924}
925
926/**
927 * @copydoc FNAUDIOTESTEXEC
928 */
929static DECLCALLBACK(int) audioTestPlayToneExec(PAUDIOTESTENV pTstEnv, void *pvCtx, PAUDIOTESTPARMS pTstParms)
930{
931 RT_NOREF(pvCtx);
932
933 int rc = VINF_SUCCESS;
934
935 PAUDIOTESTSTREAM pStream = &pTstEnv->aStreams[0];
936
937 for (uint32_t i = 0; i < pTstParms->cIterations; i++)
938 {
939 AudioTestToneParamsInitRandom(&pTstParms->TestTone, &pTstParms->Props);
940
941 PAUDIOTESTENTRY pTst;
942 rc = AudioTestSetTestBegin(&pTstEnv->Set, "Playing test tone", pTstParms, &pTst);
943 if (RT_SUCCESS(rc))
944 {
945 rc = audioTestCreateStreamDefaultOut(pTstEnv, pStream, &pTstParms->TestTone.Props);
946 if (RT_SUCCESS(rc))
947 {
948 rc = audioTestPlayTone(pTstEnv, pStream, &pTstParms->TestTone);
949 }
950
951 int rc2 = audioTestStreamDestroy(pTstEnv, pStream);
952 if (RT_SUCCESS(rc))
953 rc = rc2;
954
955 if (RT_SUCCESS(rc))
956 {
957 AudioTestSetTestDone(pTst);
958 }
959 else
960 AudioTestSetTestFailed(pTst, rc, "Playing test tone failed");
961 }
962
963 if (RT_FAILURE(rc))
964 RTTestFailed(g_hTest, "Playing tone failed\n");
965 }
966
967 return rc;
968}
969
970/**
971 * @copydoc FNAUDIOTESTDESTROY
972 */
973static DECLCALLBACK(int) audioTestPlayToneDestroy(PAUDIOTESTENV pTstEnv, void *pvCtx)
974{
975 RT_NOREF(pTstEnv, pvCtx);
976
977 return VINF_SUCCESS;
978}
979
980/**
981 * @copydoc FNAUDIOTESTSETUP
982 */
983static DECLCALLBACK(int) audioTestRecordToneSetup(PAUDIOTESTENV pTstEnv, PAUDIOTESTDESC pTstDesc, PAUDIOTESTPARMS pTstParmsAcq, void **ppvCtx)
984{
985 RT_NOREF(pTstEnv, pTstDesc, ppvCtx);
986
987 pTstParmsAcq->enmType = AUDIOTESTTYPE_TESTTONE_RECORD;
988
989 RT_ZERO(pTstParmsAcq->TestTone);
990 PDMAudioPropsInit(&pTstParmsAcq->TestTone.Props, 16 /* bit */ / 8, true /* fSigned */, 2 /* Channels */, 44100 /* Hz */);
991
992 pTstParmsAcq->enmDir = PDMAUDIODIR_IN;
993#ifdef DEBUG
994 pTstParmsAcq->cIterations = 2;
995#else
996 pTstParmsAcq->cIterations = RTRandU32Ex(1, 10);
997#endif
998 pTstParmsAcq->idxCurrent = 0;
999
1000 return VINF_SUCCESS;
1001}
1002
1003/**
1004 * @copydoc FNAUDIOTESTEXEC
1005 */
1006static DECLCALLBACK(int) audioTestRecordToneExec(PAUDIOTESTENV pTstEnv, void *pvCtx, PAUDIOTESTPARMS pTstParms)
1007{
1008 RT_NOREF(pvCtx);
1009
1010 int rc = VINF_SUCCESS;
1011
1012 PAUDIOTESTSTREAM pStream = &pTstEnv->aStreams[0];
1013
1014 for (uint32_t i = 0; i < pTstParms->cIterations; i++)
1015 {
1016 pTstParms->TestTone.msDuration = RTRandU32Ex(50 /* ms */, RT_MS_10SEC); /** @todo Record even longer? */
1017
1018 PAUDIOTESTENTRY pTst;
1019 rc = AudioTestSetTestBegin(&pTstEnv->Set, "Recording test tone", pTstParms, &pTst);
1020 if (RT_SUCCESS(rc))
1021 {
1022 /** @todo For now we're (re-)creating the recording stream for each iteration. Change that to be random. */
1023 rc = audioTestCreateStreamDefaultIn(pTstEnv, pStream, &pTstParms->TestTone.Props);
1024 if (RT_SUCCESS(rc))
1025 rc = audioTestRecordTone(pTstEnv, pStream, &pTstParms->TestTone);
1026
1027 int rc2 = audioTestStreamDestroy(pTstEnv, pStream);
1028 if (RT_SUCCESS(rc))
1029 rc = rc2;
1030
1031 if (RT_SUCCESS(rc))
1032 {
1033 AudioTestSetTestDone(pTst);
1034 }
1035 else
1036 AudioTestSetTestFailed(pTst, rc, "Recording test tone failed");
1037 }
1038
1039 if (RT_FAILURE(rc))
1040 RTTestFailed(g_hTest, "Recording tone failed\n");
1041 }
1042
1043 return rc;
1044}
1045
1046/**
1047 * @copydoc FNAUDIOTESTDESTROY
1048 */
1049static DECLCALLBACK(int) audioTestRecordToneDestroy(PAUDIOTESTENV pTstEnv, void *pvCtx)
1050{
1051 RT_NOREF(pTstEnv, pvCtx);
1052
1053 return VINF_SUCCESS;
1054}
1055
1056
1057/*********************************************************************************************************************************
1058* Test execution *
1059*********************************************************************************************************************************/
1060
1061static AUDIOTESTDESC g_aTests[] =
1062{
1063 /* pszTest fExcluded pfnSetup */
1064 { "PlayTone", false, audioTestPlayToneSetup, audioTestPlayToneExec, audioTestPlayToneDestroy },
1065 { "RecordTone", false, audioTestRecordToneSetup, audioTestRecordToneExec, audioTestRecordToneDestroy }
1066};
1067
1068/**
1069 * Runs one specific audio test.
1070 *
1071 * @returns VBox status code.
1072 * @param pTstEnv Test environment to use for running the test.
1073 * @param pTstDesc Test to run.
1074 * @param uSeq Test sequence # in case there are more tests.
1075 * @param pOverrideParms Test parameters for overriding the actual test parameters. Optional.
1076 */
1077static int audioTestOne(PAUDIOTESTENV pTstEnv, PAUDIOTESTDESC pTstDesc,
1078 unsigned uSeq, PAUDIOTESTPARMS pOverrideParms)
1079{
1080 RT_NOREF(uSeq);
1081
1082 int rc;
1083
1084 AUDIOTESTPARMS TstParms;
1085 audioTestParmsInit(&TstParms);
1086
1087 RTTestSub(g_hTest, pTstDesc->pszName);
1088
1089 if (pTstDesc->fExcluded)
1090 {
1091 RTTestSkipped(g_hTest, "Excluded from list");
1092 return VINF_SUCCESS;
1093 }
1094
1095 void *pvCtx = NULL; /* Test-specific opaque context. Optional and can be NULL. */
1096
1097 if (pTstDesc->pfnSetup)
1098 {
1099 rc = pTstDesc->pfnSetup(pTstEnv, pTstDesc, &TstParms, &pvCtx);
1100 if (RT_FAILURE(rc))
1101 {
1102 RTTestFailed(g_hTest, "Test setup failed\n");
1103 return rc;
1104 }
1105 }
1106
1107 audioTestCombineParms(&TstParms, pOverrideParms);
1108
1109 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%u: %RU32 iterations\n", uSeq, TstParms.cIterations);
1110
1111 if (strlen(TstParms.Dev.szName)) /** @todo Refine this check. */
1112 rc = audioTestDeviceOpen(&TstParms.Dev);
1113
1114 AssertPtr(pTstDesc->pfnExec);
1115 rc = pTstDesc->pfnExec(pTstEnv, pvCtx, &TstParms);
1116
1117 RTTestSubDone(g_hTest);
1118
1119 if (pTstDesc->pfnDestroy)
1120 {
1121 int rc2 = pTstDesc->pfnDestroy(pTstEnv, pvCtx);
1122 if (RT_SUCCESS(rc))
1123 rc = rc2;
1124
1125 if (RT_FAILURE(rc2))
1126 RTTestFailed(g_hTest, "Test destruction failed\n");
1127 }
1128
1129 rc = audioTestDeviceClose(&TstParms.Dev);
1130
1131 audioTestParmsDestroy(&TstParms);
1132
1133 return rc;
1134}
1135
1136/**
1137 * Runs all specified tests in a row.
1138 *
1139 * @returns VBox status code.
1140 * @param pTstEnv Test environment to use for running all tests.
1141 * @param pOverrideParms Test parameters for (some / all) specific test parameters. Optional.
1142 */
1143static int audioTestWorker(PAUDIOTESTENV pTstEnv, PAUDIOTESTPARMS pOverrideParms)
1144{
1145 int rc = VINF_SUCCESS;
1146
1147 unsigned uSeq = 0;
1148 for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++)
1149 {
1150 int rc2 = audioTestOne(pTstEnv, &g_aTests[i], uSeq, pOverrideParms);
1151 if (RT_SUCCESS(rc))
1152 rc = rc2;
1153
1154 if (!g_aTests[i].fExcluded)
1155 uSeq++;
1156 }
1157
1158 return rc;
1159}
1160
1161/** Option help for the 'test' command. */
1162static DECLCALLBACK(const char *) audioTestCmdTestHelp(PCRTGETOPTDEF pOpt)
1163{
1164 switch (pOpt->iShort)
1165 {
1166 case 'd': return "Go via DrvAudio instead of directly interfacing with the backend.";
1167 case VKAT_TEST_OPT_DEV: return "Use the specified audio device";
1168 case 'e': return "Exclude the given test id from the list";
1169 case 'a': return "Exclude all tests from the list (useful to enable single tests later with --include)";
1170 case 'i': return "Include the given test id in the list";
1171 }
1172 return NULL;
1173}
1174
1175/**
1176 * Main (entry) function for the testing functionality of VKAT.
1177 *
1178 * @returns Program exit code.
1179 * @param pGetState RTGetOpt state.
1180 */
1181static DECLCALLBACK(RTEXITCODE) audioTestMain(PRTGETOPTSTATE pGetState)
1182{
1183 AUDIOTESTENV TstEnv;
1184 RT_ZERO(TstEnv);
1185
1186 AUDIOTESTPARMS TstCust;
1187 audioTestParmsInit(&TstCust);
1188
1189 const char *pszDevice = NULL; /* Custom device to use. Can be NULL if not being used. */
1190 const char *pszTag = NULL; /* Custom tag to use. Can be NULL if not being used. */
1191 PCPDMDRVREG pDrvReg = g_aBackends[0].pDrvReg;
1192 bool fWithDrvAudio = false;
1193 uint8_t cPcmSampleBit = 0;
1194 uint8_t cPcmChannels = 0;
1195 uint32_t uPcmHz = 0;
1196 bool fPcmSigned = true;
1197 const char *pszTcpAddr = NULL;
1198 uint16_t uTcpPort = 0;
1199
1200 int rc;
1201 RTGETOPTUNION ValueUnion;
1202 while ((rc = RTGetOpt(pGetState, &ValueUnion)))
1203 {
1204 switch (rc)
1205 {
1206 case 'a':
1207 for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++)
1208 g_aTests[i].fExcluded = true;
1209 break;
1210
1211 case 'b':
1212 pDrvReg = NULL;
1213 for (uintptr_t i = 0; i < RT_ELEMENTS(g_aBackends); i++)
1214 if ( strcmp(ValueUnion.psz, g_aBackends[i].pszName) == 0
1215 || strcmp(ValueUnion.psz, g_aBackends[i].pDrvReg->szName) == 0)
1216 {
1217 pDrvReg = g_aBackends[i].pDrvReg;
1218 break;
1219 }
1220 if (pDrvReg == NULL)
1221 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown backend: '%s'", ValueUnion.psz);
1222 break;
1223
1224 case 'd':
1225 fWithDrvAudio = true;
1226 break;
1227
1228 case 'e':
1229 if (ValueUnion.u32 >= RT_ELEMENTS(g_aTests))
1230 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid test number %u passed to --exclude", ValueUnion.u32);
1231 g_aTests[ValueUnion.u32].fExcluded = true;
1232 break;
1233
1234 case VKAT_TEST_OPT_ATS_ADDR:
1235 if (TstEnv.enmMode == AUDIOTESTMODE_UNKNOWN)
1236 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Must specify a test mode first!");
1237 pszTcpAddr = ValueUnion.psz;
1238 break;
1239
1240 case VKAT_TEST_OPT_ATS_PORT:
1241 if (TstEnv.enmMode == AUDIOTESTMODE_UNKNOWN)
1242 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Must specify a test mode first!");
1243 uTcpPort = ValueUnion.u32;
1244 break;
1245
1246 case VKAT_TEST_OPT_MODE:
1247 if (TstEnv.enmMode != AUDIOTESTMODE_UNKNOWN)
1248 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Test mode (guest / host) already specified");
1249 TstEnv.enmMode = RTStrICmp(ValueUnion.psz, "guest") == 0 ? AUDIOTESTMODE_GUEST : AUDIOTESTMODE_HOST;
1250 break;
1251
1252 case 'i':
1253 if (ValueUnion.u32 >= RT_ELEMENTS(g_aTests))
1254 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid test number %u passed to --include", ValueUnion.u32);
1255 g_aTests[ValueUnion.u32].fExcluded = false;
1256 break;
1257
1258 case VKAT_TEST_OPT_COUNT:
1259 return RTMsgErrorExitFailure("Not yet implemented!");
1260
1261 case VKAT_TEST_OPT_DEV:
1262 pszDevice = ValueUnion.psz;
1263 break;
1264
1265 case VKAT_TEST_OPT_PAUSE:
1266 return RTMsgErrorExitFailure("Not yet implemented!");
1267
1268 case VKAT_TEST_OPT_OUTDIR:
1269 rc = RTStrCopy(TstEnv.szPathOut, sizeof(TstEnv.szPathOut), ValueUnion.psz);
1270 if (RT_FAILURE(rc))
1271 return RTMsgErrorExitFailure("Failed to copy out directory: %Rrc", rc);
1272 break;
1273
1274 case VKAT_TEST_OPT_PCM_BIT:
1275 cPcmSampleBit = ValueUnion.u8;
1276 break;
1277
1278 case VKAT_TEST_OPT_PCM_CHAN:
1279 cPcmChannels = ValueUnion.u8;
1280 break;
1281
1282 case VKAT_TEST_OPT_PCM_HZ:
1283 uPcmHz = ValueUnion.u32;
1284 break;
1285
1286 case VKAT_TEST_OPT_PCM_SIGNED:
1287 fPcmSigned = ValueUnion.f;
1288 break;
1289
1290 case VKAT_TEST_OPT_TAG:
1291 pszTag = ValueUnion.psz;
1292 break;
1293
1294 case VKAT_TEST_OPT_TEMPDIR:
1295 rc = RTStrCopy(TstEnv.szPathTemp, sizeof(TstEnv.szPathTemp), ValueUnion.psz);
1296 if (RT_FAILURE(rc))
1297 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Temp dir invalid, rc=%Rrc", rc);
1298 break;
1299
1300 case VKAT_TEST_OPT_VOL:
1301 TstCust.TestTone.uVolumePercent = ValueUnion.u8;
1302 break;
1303
1304 AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion);
1305
1306 default:
1307 return RTGetOptPrintError(rc, &ValueUnion);
1308 }
1309 }
1310
1311 /*
1312 * Start testing.
1313 */
1314 RTTestBanner(g_hTest);
1315
1316 /* Initialize the custom test parameters with sensible defaults if nothing else is given. */
1317 PDMAudioPropsInit(&TstCust.TestTone.Props,
1318 cPcmSampleBit ? cPcmSampleBit / 8 : 2 /* 16-bit */, fPcmSigned, cPcmChannels ? cPcmChannels : 2,
1319 uPcmHz ? uPcmHz : 44100);
1320
1321 if (TstEnv.enmMode == AUDIOTESTMODE_UNKNOWN)
1322 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No test mode specified!\n");
1323
1324 if (TstEnv.enmMode == AUDIOTESTMODE_HOST)
1325 {
1326 /* Use the default port is none is specified. */
1327 if (!uTcpPort)
1328 uTcpPort = ATS_DEFAULT_PORT;
1329
1330 if (!pszTcpAddr)
1331 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "--ats-address missing\n");
1332 }
1333
1334 /* For now all tests have the same test environment. */
1335 rc = audioTestEnvInit(&TstEnv, pDrvReg, fWithDrvAudio, pszTag, pszTcpAddr, uTcpPort);
1336 if (RT_SUCCESS(rc))
1337 {
1338 PPDMAUDIOHOSTDEV pDev;
1339 rc = audioTestDevicesEnumerateAndCheck(&TstEnv, pszDevice, &pDev);
1340 if (RT_SUCCESS(rc))
1341 audioTestWorker(&TstEnv, &TstCust);
1342
1343 /* Before destroying the test environment, pack up the test set so
1344 * that it's ready for transmission. */
1345 char szFileOut[RTPATH_MAX];
1346 rc = AudioTestSetPack(&TstEnv.Set, TstEnv.szPathOut, szFileOut, sizeof(szFileOut));
1347 if (RT_SUCCESS(rc))
1348 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test set packed up to '%s'\n", szFileOut);
1349
1350#ifndef DEBUG_andy
1351 /* Clean up. */
1352 AudioTestSetClose(&TstEnv.Set); /* wipe fails on windows if the manifest file is open*/
1353
1354 int rc2 = AudioTestSetWipe(&TstEnv.Set);
1355 AssertRC(rc2); /* Annoying, but not test-critical. */
1356#endif
1357 audioTestEnvDestroy(&TstEnv);
1358 }
1359
1360 audioTestParmsDestroy(&TstCust);
1361
1362 if (RT_FAILURE(rc)) /* Let us know that something went wrong in case we forgot to mention it. */
1363 RTTestFailed(g_hTest, "Tested failed with %Rrc\n", rc);
1364
1365 /*
1366 * Print summary and exit.
1367 */
1368 return RTTestSummaryAndDestroy(g_hTest);
1369}
1370
1371
1372/*********************************************************************************************************************************
1373* Command: verify *
1374*********************************************************************************************************************************/
1375
1376static int audioVerifyOpenTestSet(const char *pszPathSet, PAUDIOTESTSET pSet)
1377{
1378 int rc;
1379
1380 char szPathExtracted[RTPATH_MAX];
1381
1382 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Opening test set '%s'\n", pszPathSet);
1383
1384 const bool fPacked = AudioTestSetIsPacked(pszPathSet);
1385 if (fPacked)
1386 {
1387 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test set is an archive and needs to be unpacked\n");
1388
1389 char szPathTemp[RTPATH_MAX];
1390 rc = RTPathTemp(szPathTemp, sizeof(szPathTemp));
1391 if (RT_SUCCESS(rc))
1392 {
1393 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Using temporary directory '%s'\n", szPathTemp);
1394
1395 rc = RTPathJoin(szPathExtracted, sizeof(szPathExtracted), szPathTemp, "vkat-XXXX");
1396 if (RT_SUCCESS(rc))
1397 {
1398 rc = RTDirCreateTemp(szPathExtracted, 0755);
1399 if (RT_SUCCESS(rc))
1400 {
1401 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Unpacking archive to '%s'\n", szPathExtracted);
1402 rc = AudioTestSetUnpack(pszPathSet, szPathExtracted);
1403 if (RT_SUCCESS(rc))
1404 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Archive successfully unpacked\n");
1405 }
1406 }
1407 }
1408 }
1409 else
1410 rc = VINF_SUCCESS;
1411
1412 if (RT_SUCCESS(rc))
1413 rc = AudioTestSetOpen(pSet, fPacked ? szPathExtracted : pszPathSet);
1414
1415 if (RT_FAILURE(rc))
1416 RTTestFailed(g_hTest, "Unable to open / unpack test set archive: %Rrc", rc);
1417
1418 return rc;
1419}
1420
1421/**
1422 * Verifies one single test set.
1423 *
1424 * @returns VBox status code.
1425 * @param pszPathSetA Absolute path to test set A.
1426 * @param pszPathSetB Absolute path to test set B.
1427 */
1428static int audioVerifyOne(const char *pszPathSetA, const char *pszPathSetB)
1429{
1430 RTTestSubF(g_hTest, "Verifying");
1431 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Verifying test set '%s' with test set '%s'\n", pszPathSetA, pszPathSetB);
1432
1433 AUDIOTESTSET SetA, SetB;
1434 int rc = audioVerifyOpenTestSet(pszPathSetA, &SetA);
1435 if (RT_SUCCESS(rc))
1436 rc = audioVerifyOpenTestSet(pszPathSetB, &SetB);
1437
1438 if (RT_SUCCESS(rc))
1439 {
1440 AUDIOTESTERRORDESC errDesc;
1441 rc = AudioTestSetVerify(&SetA, &SetB, &errDesc);
1442 if (RT_SUCCESS(rc))
1443 {
1444 if (AudioTestErrorDescFailed(&errDesc))
1445 {
1446 /** @todo Use some AudioTestErrorXXX API for enumeration here later. */
1447 PAUDIOTESTERRORENTRY pErrEntry;
1448 RTListForEach(&errDesc.List, pErrEntry, AUDIOTESTERRORENTRY, Node)
1449 RTTestFailed(g_hTest, pErrEntry->szDesc);
1450 }
1451 else
1452 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Verification successful\n");
1453
1454 AudioTestErrorDescDestroy(&errDesc);
1455 }
1456 else
1457 RTTestFailed(g_hTest, "Verification failed with %Rrc", rc);
1458 }
1459
1460 AudioTestSetClose(&SetA);
1461 AudioTestSetClose(&SetB);
1462
1463 RTTestSubDone(g_hTest);
1464
1465 return rc;
1466}
1467
1468/**
1469 * Main (entry) function for the verification functionality of VKAT.
1470 *
1471 * @returns Program exit code.
1472 * @param pGetState RTGetOpt state.
1473 */
1474static DECLCALLBACK(RTEXITCODE) audioVerifyMain(PRTGETOPTSTATE pGetState)
1475{
1476 /*
1477 * Parse options and process arguments.
1478 */
1479 char *pszSetA = NULL;
1480 char *pszSetB = NULL;
1481 unsigned iTestSet = 0;
1482
1483 int rc;
1484 RTGETOPTUNION ValueUnion;
1485 while ((rc = RTGetOpt(pGetState, &ValueUnion)))
1486 {
1487 switch (rc)
1488 {
1489 case VKAT_VERIFY_OPT_TAG:
1490 break;
1491
1492 case VINF_GETOPT_NOT_OPTION:
1493 {
1494 char **ppszSet = iTestSet == 0 ? &pszSetA : &pszSetB;
1495
1496 if (iTestSet == 0)
1497 RTTestBanner(g_hTest);
1498
1499 *ppszSet = RTStrDup(ValueUnion.psz);
1500 AssertPtrReturn(*ppszSet, RTEXITCODE_FAILURE);
1501
1502 iTestSet++;
1503 break;
1504 }
1505
1506 AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion);
1507
1508 default:
1509 return RTGetOptPrintError(rc, &ValueUnion);
1510 }
1511 }
1512
1513 if (!iTestSet)
1514 return RTMsgErrorExitFailure("At least one test set must be specified");
1515
1516 if (iTestSet > 2)
1517 return RTMsgErrorExitFailure("Only two test sets can be verified at one time");
1518
1519 /*
1520 * If only test set A is given, default to the current directory
1521 * for test set B.
1522 */
1523 if (iTestSet == 1)
1524 {
1525 char szDirCur[RTPATH_MAX];
1526 rc = RTPathGetCurrent(szDirCur, sizeof(szDirCur));
1527 if (RT_SUCCESS(rc))
1528 {
1529 Assert(pszSetB == NULL);
1530 pszSetB = RTStrDup(szDirCur);
1531 AssertPtrReturn(pszSetB, RTEXITCODE_FAILURE);
1532 }
1533 else
1534 RTTestFailed(g_hTest, "Failed to retrieve current directory: %Rrc", rc);
1535 }
1536
1537 if (RT_SUCCESS(rc))
1538 rc = audioVerifyOne(pszSetA, pszSetB);
1539
1540 RTStrFree(pszSetA);
1541 RTStrFree(pszSetB);
1542
1543 /*
1544 * Print summary and exit.
1545 */
1546 return RTTestSummaryAndDestroy(g_hTest);
1547}
1548
1549
1550/*********************************************************************************************************************************
1551* Command: play *
1552*********************************************************************************************************************************/
1553
1554/**
1555 * Worker for audioTestCmdPlayHandler that plays one file.
1556 */
1557static RTEXITCODE audioTestPlayOne(const char *pszFile, PCPDMDRVREG pDrvReg, const char *pszDevId, uint32_t cMsBufferSize,
1558 uint32_t cMsPreBuffer, uint32_t cMsSchedulingHint, bool fWithDrvAudio)
1559{
1560 /*
1561 * First we must open the file and determin the format.
1562 */
1563 RTERRINFOSTATIC ErrInfo;
1564 AUDIOTESTWAVEFILE WaveFile;
1565 int rc = AudioTestWaveFileOpen(pszFile, &WaveFile, RTErrInfoInitStatic(&ErrInfo));
1566 if (RT_FAILURE(rc))
1567 return RTMsgErrorExitFailure("Failed to open '%s': %Rrc%#RTeim", pszFile, rc, &ErrInfo.Core);
1568
1569 if (g_uVerbosity > 0)
1570 {
1571 char szTmp[128];
1572 RTMsgInfo("Opened '%s' for playing\n", pszFile);
1573 RTMsgInfo("Format: %s\n", PDMAudioPropsToString(&WaveFile.Props, szTmp, sizeof(szTmp)));
1574 RTMsgInfo("Size: %'RU32 bytes / %#RX32 / %'RU32 frames / %'RU64 ns\n",
1575 WaveFile.cbSamples, WaveFile.cbSamples,
1576 PDMAudioPropsBytesToFrames(&WaveFile.Props, WaveFile.cbSamples),
1577 PDMAudioPropsBytesToNano(&WaveFile.Props, WaveFile.cbSamples));
1578 }
1579
1580 /*
1581 * Construct the driver stack.
1582 */
1583 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
1584 AUDIOTESTDRVSTACK DrvStack;
1585 rc = audioTestDriverStackInit(&DrvStack, pDrvReg, fWithDrvAudio);
1586 if (RT_SUCCESS(rc))
1587 {
1588 /*
1589 * Set the output device if one is specified.
1590 */
1591 rc = audioTestDriverStackSetDevice(&DrvStack, PDMAUDIODIR_OUT, pszDevId);
1592 if (RT_SUCCESS(rc))
1593 {
1594 /*
1595 * Open a stream for the output.
1596 */
1597 PDMAUDIOSTREAMCFG CfgAcq;
1598 PPDMAUDIOSTREAM pStream = NULL;
1599 rc = audioTestDriverStackStreamCreateOutput(&DrvStack, &WaveFile.Props, cMsBufferSize,
1600 cMsPreBuffer, cMsSchedulingHint, &pStream, &CfgAcq);
1601 if (RT_SUCCESS(rc))
1602 {
1603 rc = audioTestDriverStackStreamEnable(&DrvStack, pStream);
1604 if (RT_SUCCESS(rc))
1605 {
1606 uint64_t const nsStarted = RTTimeNanoTS();
1607
1608 /*
1609 * Transfer data as quickly as we're allowed.
1610 */
1611 for (;;)
1612 {
1613 /* Read a chunk from the wave file. */
1614 uint8_t abSamples[16384];
1615 size_t cbSamples = 0;
1616 rc = AudioTestWaveFileRead(&WaveFile, abSamples, sizeof(abSamples), &cbSamples);
1617 if (RT_SUCCESS(rc) && cbSamples > 0)
1618 {
1619 /* Transfer the data to the audio stream. */
1620 for (uint32_t offSamples = 0; offSamples < cbSamples;)
1621 {
1622 uint32_t const cbCanWrite = audioTestDriverStackStreamGetWritable(&DrvStack, pStream);
1623 if (cbCanWrite > 0)
1624 {
1625 uint32_t const cbToPlay = RT_MIN(cbCanWrite, (uint32_t)cbSamples - offSamples);
1626 uint32_t cbPlayed = 0;
1627 rc = audioTestDriverStackStreamPlay(&DrvStack, pStream, &abSamples[offSamples],
1628 cbToPlay, &cbPlayed);
1629 if (RT_SUCCESS(rc))
1630 {
1631 if (cbPlayed)
1632 offSamples += cbPlayed;
1633 else
1634 {
1635 rcExit = RTMsgErrorExitFailure("Played zero out of %#x bytes - %#x bytes reported playable!\n",
1636 cbToPlay, cbCanWrite);
1637 break;
1638 }
1639 }
1640 else
1641 {
1642 rcExit = RTMsgErrorExitFailure("Failed to play %#x bytes: %Rrc\n", cbToPlay, rc);
1643 break;
1644 }
1645 }
1646 else if (audioTestDriverStackStreamIsOkay(&DrvStack, pStream))
1647 RTThreadSleep(RT_MIN(RT_MAX(1, CfgAcq.Device.cMsSchedulingHint), 256));
1648 else
1649 {
1650 rcExit = RTMsgErrorExitFailure("Stream is not okay!\n");
1651 break;
1652 }
1653 }
1654 }
1655 else if (RT_SUCCESS(rc) && cbSamples == 0)
1656 {
1657 rcExit = RTEXITCODE_SUCCESS;
1658 break;
1659 }
1660 else
1661 {
1662 rcExit = RTMsgErrorExitFailure("Error reading wav file '%s': %Rrc", pszFile, rc);
1663 break;
1664 }
1665 }
1666
1667 /*
1668 * Drain the stream.
1669 */
1670 if (rcExit == RTEXITCODE_SUCCESS)
1671 {
1672 if (g_uVerbosity > 0)
1673 RTMsgInfo("%'RU64 ns: Draining...\n", RTTimeNanoTS() - nsStarted);
1674 rc = audioTestDriverStackStreamDrain(&DrvStack, pStream, true /*fSync*/);
1675 if (RT_SUCCESS(rc))
1676 {
1677 if (g_uVerbosity > 0)
1678 RTMsgInfo("%'RU64 ns: Done\n", RTTimeNanoTS() - nsStarted);
1679 }
1680 else
1681 rcExit = RTMsgErrorExitFailure("Draining failed: %Rrc", rc);
1682 }
1683 }
1684 else
1685 rcExit = RTMsgErrorExitFailure("Enabling the output stream failed: %Rrc", rc);
1686 audioTestDriverStackStreamDestroy(&DrvStack, pStream);
1687 }
1688 else
1689 rcExit = RTMsgErrorExitFailure("Creating output stream failed: %Rrc", rc);
1690 }
1691 else
1692 rcExit = RTMsgErrorExitFailure("Failed to set output device to '%s': %Rrc", pszDevId, rc);
1693 audioTestDriverStackDelete(&DrvStack);
1694 }
1695 else
1696 rcExit = RTMsgErrorExitFailure("Driver stack construction failed: %Rrc", rc);
1697 AudioTestWaveFileClose(&WaveFile);
1698 return rcExit;
1699}
1700
1701/**
1702 * Command line parameters for test mode.
1703 */
1704static const RTGETOPTDEF g_aCmdPlayOptions[] =
1705{
1706 { "--backend", 'b', RTGETOPT_REQ_STRING },
1707 { "--output-device", 'o', RTGETOPT_REQ_STRING },
1708 { "--with-drv-audio", 'd', RTGETOPT_REQ_NOTHING },
1709};
1710
1711/** the 'play' command option help. */
1712static DECLCALLBACK(const char *) audioTestCmdPlayHelp(PCRTGETOPTDEF pOpt)
1713{
1714 switch (pOpt->iShort)
1715 {
1716 case 'b': return "The audio backend to use.";
1717 case 'd': return "Go via DrvAudio instead of directly interfacing with the backend.";
1718 case 'o': return "The ID of the output device to use.";
1719 default: return NULL;
1720 }
1721}
1722
1723/**
1724 * The 'play' command handler.
1725 *
1726 * @returns Program exit code.
1727 * @param pGetState RTGetOpt state.
1728 */
1729static DECLCALLBACK(RTEXITCODE) audioTestCmdPlayHandler(PRTGETOPTSTATE pGetState)
1730{
1731 /* Option values: */
1732 PCPDMDRVREG pDrvReg = g_aBackends[0].pDrvReg;
1733 uint32_t cMsBufferSize = UINT32_MAX;
1734 uint32_t cMsPreBuffer = UINT32_MAX;
1735 uint32_t cMsSchedulingHint = UINT32_MAX;
1736 const char *pszDevId = NULL;
1737 bool fWithDrvAudio = false;
1738
1739 /* Argument processing loop: */
1740 int rc;
1741 RTGETOPTUNION ValueUnion;
1742 while ((rc = RTGetOpt(pGetState, &ValueUnion)) != 0)
1743 {
1744 switch (rc)
1745 {
1746 case 'b':
1747 pDrvReg = NULL;
1748 for (uintptr_t i = 0; i < RT_ELEMENTS(g_aBackends); i++)
1749 if ( strcmp(ValueUnion.psz, g_aBackends[i].pszName) == 0
1750 || strcmp(ValueUnion.psz, g_aBackends[i].pDrvReg->szName) == 0)
1751 {
1752 pDrvReg = g_aBackends[i].pDrvReg;
1753 break;
1754 }
1755 if (pDrvReg == NULL)
1756 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown backend: '%s'", ValueUnion.psz);
1757 break;
1758
1759 case 'd':
1760 fWithDrvAudio = true;
1761 break;
1762
1763 case 'o':
1764 pszDevId = ValueUnion.psz;
1765 break;
1766
1767 case VINF_GETOPT_NOT_OPTION:
1768 {
1769 RTEXITCODE rcExit = audioTestPlayOne(ValueUnion.psz, pDrvReg, pszDevId, cMsBufferSize, cMsPreBuffer,
1770 cMsSchedulingHint, fWithDrvAudio);
1771 if (rcExit != RTEXITCODE_SUCCESS)
1772 return rcExit;
1773 break;
1774 }
1775
1776 AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion);
1777
1778 default:
1779 return RTGetOptPrintError(rc, &ValueUnion);
1780 }
1781 }
1782 return RTEXITCODE_SUCCESS;
1783}
1784
1785/**
1786 * Command line parameters for self-test mode.
1787 */
1788static const RTGETOPTDEF g_aCmdSelftestOptions[] =
1789{
1790 { "--ats-host", VKAT_SELFTEST_OPT_ATS_HOST, RTGETOPT_REQ_STRING },
1791 { "--backend", 'b', RTGETOPT_REQ_STRING },
1792 { "--with-drv-audio", 'd', RTGETOPT_REQ_NOTHING },
1793};
1794
1795/** the 'selftest' command option help. */
1796static DECLCALLBACK(const char *) audioTestCmdSelftestHelp(PCRTGETOPTDEF pOpt)
1797{
1798 switch (pOpt->iShort)
1799 {
1800 case 'b': return "The audio backend to use.";
1801 case 'd': return "Go via DrvAudio instead of directly interfacing with the backend.";
1802 default: return NULL;
1803 }
1804}
1805
1806/**
1807 * Tests the Audio Test Service (ATS).
1808 *
1809 * @returns VBox status code.
1810 * @param pDrvReg Backend driver to use.
1811 * @param pszAdr Address of ATS server to connect to.
1812 * If NULL, an own (local) ATS server will be created.
1813 */
1814static int audioTestDoSelftestAts(PCPDMDRVREG pDrvReg, const char *pszAdr)
1815{
1816 AUDIOTESTENV TstEnv;
1817 int rc = audioTestDriverStackInit(&TstEnv.DrvStack, pDrvReg, true /* fWithDrvAudio */);
1818 if (RT_SUCCESS(rc))
1819 {
1820 /** @todo Make stream parameters configurable. */
1821 PDMAUDIOPCMPROPS Props;
1822 PDMAudioPropsInit(&Props, 16 /* bit */ / 8, true /* fSigned */, 2 /* Channels */, 44100 /* Hz */);
1823
1824 PDMAUDIOSTREAMCFG CfgAcq;
1825 PPDMAUDIOSTREAM pStream = NULL;
1826 rc = audioTestDriverStackStreamCreateOutput(&TstEnv.DrvStack, &Props,
1827 UINT32_MAX /* cMsBufferSize */,
1828 UINT32_MAX /* cMsPreBuffer */,
1829 UINT32_MAX /* cMsSchedulingHint */, &pStream, &CfgAcq);
1830 if (RT_SUCCESS(rc))
1831 {
1832 rc = audioTestDriverStackStreamEnable(&TstEnv.DrvStack, pStream);
1833 if (RT_SUCCESS(rc))
1834 {
1835 ATSCALLBACKCTX Ctx;
1836 Ctx.pTstEnv = &TstEnv;
1837
1838 ATSCALLBACKS Callbacks;
1839 Callbacks.pfnTonePlay = audioTestSvcTonePlayCallback;
1840 Callbacks.pvUser = &Ctx;
1841
1842 /* Start an own ATS instance if no address to connect was specified. */
1843 ATSSERVER Srv;
1844 if (pszAdr == NULL)
1845 {
1846 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Starting ATS ...\n");
1847
1848 rc = AudioTestSvcInit(&Srv, &Callbacks);
1849 if (RT_SUCCESS(rc))
1850 {
1851 rc = AudioTestSvcStart(&Srv);
1852 if (RT_SUCCESS(rc))
1853 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "ATS running\n");
1854 }
1855 }
1856 else
1857 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Connecting to ATS at '%s' ...\n", pszAdr);
1858
1859 if (RT_SUCCESS(rc))
1860 {
1861 ATSCLIENT Conn;
1862 rc = AudioTestSvcClientConnect(&Conn, NULL, 0);
1863 if (RT_SUCCESS(rc))
1864 {
1865 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Connected to ATS, testing ...\n");
1866
1867 /* Do the bare minimum here to get a test tone out. */
1868 AUDIOTESTTONEPARMS ToneParms;
1869 RT_ZERO(ToneParms);
1870 ToneParms.msDuration = RTRandU32Ex(250, 1000 * 5);
1871 memcpy(&ToneParms.Props, &CfgAcq.Props, sizeof(PDMAUDIOPCMPROPS));
1872
1873 rc = AudioTestSvcClientTonePlay(&Conn, &CfgAcq, &ToneParms);
1874
1875 int rc2 = AudioTestSvcClientClose(&Conn);
1876 if (RT_SUCCESS(rc))
1877 rc = rc2;
1878 }
1879 else
1880 RTTestFailed(g_hTest, "Connecting to ATS failed, rc=%Rrc\n", rc);
1881
1882 int rc2 = AudioTestSvcShutdown(&Srv);
1883 if (RT_SUCCESS(rc))
1884 rc = rc2;
1885 }
1886
1887 if (pszAdr == NULL)
1888 {
1889 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Shutting down ATS ...\n");
1890
1891 int rc2 = AudioTestSvcDestroy(&Srv);
1892 if (RT_SUCCESS(rc))
1893 rc = rc2;
1894 }
1895 }
1896 }
1897 }
1898
1899 if (RT_FAILURE(rc))
1900 RTTestFailed(g_hTest, "Testing ATS failed with %Rrc\n", rc);
1901
1902 return rc;
1903}
1904
1905/**
1906 * Main function for performing the self-tests.
1907 *
1908 * @returns VBox status code.
1909 * @param pDrvReg Backend driver to use.
1910 * @param pszAtsAdr Address of ATS server to connect to.
1911 * If NULL, an own (local) ATS server will be created.
1912 */
1913static int audioTestDoSelftest(PCPDMDRVREG pDrvReg, const char *pszAtsAdr)
1914{
1915 int rc = audioTestDoSelftestAts(pDrvReg, pszAtsAdr);
1916 if (RT_FAILURE(rc))
1917 RTTestFailed(g_hTest, "Self-test failed with: %Rrc", rc);
1918
1919 return rc;
1920}
1921
1922/**
1923 * The 'selftest' command handler.
1924 *
1925 * @returns Program exit code.
1926 * @param pGetState RTGetOpt state.
1927 */
1928static DECLCALLBACK(RTEXITCODE) audioTestCmdSelftestHandler(PRTGETOPTSTATE pGetState)
1929{
1930 /* Option values: */
1931 PCPDMDRVREG pDrvReg = g_aBackends[0].pDrvReg;
1932 bool fWithDrvAudio = false;
1933 char *pszAtsAddr = NULL;
1934
1935 /* Argument processing loop: */
1936 int rc;
1937 RTGETOPTUNION ValueUnion;
1938 while ((rc = RTGetOpt(pGetState, &ValueUnion)) != 0)
1939 {
1940 switch (rc)
1941 {
1942 case VKAT_SELFTEST_OPT_ATS_HOST:
1943 {
1944 pszAtsAddr = RTStrDup(ValueUnion.psz);
1945 break;
1946 }
1947
1948 case 'b':
1949 {
1950 pDrvReg = NULL;
1951 for (uintptr_t i = 0; i < RT_ELEMENTS(g_aBackends); i++)
1952 if ( strcmp(ValueUnion.psz, g_aBackends[i].pszName) == 0
1953 || strcmp(ValueUnion.psz, g_aBackends[i].pDrvReg->szName) == 0)
1954 {
1955 pDrvReg = g_aBackends[i].pDrvReg;
1956 break;
1957 }
1958 if (pDrvReg == NULL)
1959 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown backend: '%s'", ValueUnion.psz);
1960 break;
1961 }
1962
1963 case 'd':
1964 {
1965 fWithDrvAudio = true;
1966 break;
1967 }
1968
1969 case VINF_GETOPT_NOT_OPTION:
1970 {
1971 break;
1972 }
1973
1974 AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion);
1975
1976 default:
1977 return RTGetOptPrintError(rc, &ValueUnion);
1978 }
1979 }
1980
1981 audioTestDoSelftest(pDrvReg, pszAtsAddr);
1982
1983 RTStrFree(pszAtsAddr);
1984
1985 /*
1986 * Print summary and exit.
1987 */
1988 return RTTestSummaryAndDestroy(g_hTest);
1989}
1990
1991
1992/**
1993 * Commands.
1994 */
1995static struct
1996{
1997 /** The command name. */
1998 const char *pszCommand;
1999 /** The command handler. */
2000 DECLCALLBACKMEMBER(RTEXITCODE, pfnHandler,(PRTGETOPTSTATE pGetState));
2001
2002 /** Command description. */
2003 const char *pszDesc;
2004 /** Options array. */
2005 PCRTGETOPTDEF paOptions;
2006 /** Number of options in the option array. */
2007 size_t cOptions;
2008 /** Gets help for an option. */
2009 DECLCALLBACKMEMBER(const char *, pfnOptionHelp,(PCRTGETOPTDEF pOpt));
2010} const g_aCommands[] =
2011{
2012 {
2013 "test", audioTestMain,
2014 "Runs audio tests and creates an audio test set.",
2015 g_aCmdTestOptions, RT_ELEMENTS(g_aCmdTestOptions), audioTestCmdTestHelp
2016 },
2017 {
2018 "verify", audioVerifyMain,
2019 "Verifies a formerly created audio test set.",
2020 g_aCmdVerifyOptions, RT_ELEMENTS(g_aCmdVerifyOptions), NULL,
2021 },
2022 {
2023 "play", audioTestCmdPlayHandler,
2024 "Plays one or more wave files.",
2025 g_aCmdPlayOptions, RT_ELEMENTS(g_aCmdPlayOptions), audioTestCmdPlayHelp,
2026 },
2027 {
2028 "selftest", audioTestCmdSelftestHandler,
2029 "Performs self-tests.",
2030 g_aCmdSelftestOptions, RT_ELEMENTS(g_aCmdSelftestOptions), audioTestCmdSelftestHelp,
2031 }
2032};
2033
2034/**
2035 * Shows tool usage text.
2036 */
2037static RTEXITCODE audioTestUsage(PRTSTREAM pStrm)
2038{
2039 RTStrmPrintf(pStrm, "usage: %s [global options] <command> [command-options]\n",
2040 RTPathFilename(RTProcExecutablePath()));
2041 RTStrmPrintf(pStrm,
2042 "\n"
2043 "Global Options:\n"
2044 " --debug-audio\n"
2045 " Enables DrvAudio debugging.\n"
2046 " --debug-audio-path=<path>\n"
2047 " Tells DrvAudio where to put its debug output (wav-files).\n"
2048 " -q, --quiet\n"
2049 " Sets verbosity to zero.\n"
2050 " -v, --verbose\n"
2051 " Increase verbosity.\n"
2052 " -V, --version\n"
2053 " Displays version.\n"
2054 " -h, -?, --help\n"
2055 " Displays help.\n"
2056 );
2057
2058 for (uintptr_t iCmd = 0; iCmd < RT_ELEMENTS(g_aCommands); iCmd++)
2059 {
2060 RTStrmPrintf(pStrm,
2061 "\n"
2062 "Command '%s':\n"
2063 " %s\n"
2064 "Options for '%s':\n",
2065 g_aCommands[iCmd].pszCommand, g_aCommands[iCmd].pszDesc, g_aCommands[iCmd].pszCommand);
2066 PCRTGETOPTDEF const paOptions = g_aCommands[iCmd].paOptions;
2067 for (unsigned i = 0; i < g_aCommands[iCmd].cOptions; i++)
2068 {
2069 if (RT_C_IS_PRINT(paOptions[i].iShort))
2070 RTStrmPrintf(pStrm, " -%c, %s\n", paOptions[i].iShort, paOptions[i].pszLong);
2071 else
2072 RTStrmPrintf(pStrm, " %s\n", paOptions[i].pszLong);
2073
2074 const char *pszHelp = NULL;
2075 if (g_aCommands[iCmd].pfnOptionHelp)
2076 pszHelp = g_aCommands[iCmd].pfnOptionHelp(&paOptions[i]);
2077 if (pszHelp)
2078 RTStrmPrintf(pStrm, " %s\n", pszHelp);
2079 }
2080 }
2081 return RTEXITCODE_SUCCESS;
2082}
2083
2084/**
2085 * Shows tool version.
2086 */
2087static RTEXITCODE audioTestVersion(void)
2088{
2089 RTPrintf("v0.0.1\n");
2090 return RTEXITCODE_SUCCESS;
2091}
2092
2093/**
2094 * Shows the logo.
2095 *
2096 * @param pStream Output stream to show logo on.
2097 */
2098static void audioTestShowLogo(PRTSTREAM pStream)
2099{
2100 RTStrmPrintf(pStream, VBOX_PRODUCT " VKAT (Validation Kit Audio Test) Version " VBOX_VERSION_STRING " - r%s\n"
2101 "(C) " VBOX_C_YEAR " " VBOX_VENDOR "\n"
2102 "All rights reserved.\n\n", RTBldCfgRevisionStr());
2103}
2104
2105int main(int argc, char **argv)
2106{
2107 /*
2108 * Init IPRT and globals.
2109 */
2110 RTEXITCODE rcExit = RTTestInitAndCreate("AudioTest", &g_hTest);
2111 if (rcExit != RTEXITCODE_SUCCESS)
2112 return rcExit;
2113
2114#ifdef RT_OS_WINDOWS
2115 HRESULT hrc = CoInitializeEx(NULL /*pReserved*/, COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY | COINIT_DISABLE_OLE1DDE);
2116 if (FAILED(hrc))
2117 RTMsgWarning("CoInitializeEx failed: %#x", hrc);
2118#endif
2119
2120 /*
2121 * Configure release logging to go to stderr.
2122 */
2123 static const char * const g_apszLogGroups[] = VBOX_LOGGROUP_NAMES;
2124 int rc = RTLogCreate(&g_pRelLogger, RTLOGFLAGS_PREFIX_THREAD, "all.e.l", "VKAT_RELEASE_LOG",
2125 RT_ELEMENTS(g_apszLogGroups), g_apszLogGroups, RTLOGDEST_STDERR, "vkat-release.log");
2126 if (RT_SUCCESS(rc))
2127 RTLogRelSetDefaultInstance(g_pRelLogger);
2128 else
2129 RTMsgWarning("Failed to create release logger: %Rrc", rc);
2130
2131 /*
2132 * Process common options.
2133 */
2134 RTGETOPTSTATE GetState;
2135 rc = RTGetOptInit(&GetState, argc, argv, g_aCmdCommonOptions,
2136 RT_ELEMENTS(g_aCmdCommonOptions), 1 /*idxFirst*/, 0 /*fFlags - must not sort! */);
2137 AssertRCReturn(rc, RTEXITCODE_INIT);
2138
2139 RTGETOPTUNION ValueUnion;
2140 while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0)
2141 {
2142 switch (rc)
2143 {
2144 case 'q':
2145 g_uVerbosity = 0;
2146 if (g_pRelLogger)
2147 RTLogGroupSettings(g_pRelLogger, "all=0 all.e");
2148 break;
2149
2150 case 'v':
2151 g_uVerbosity++;
2152 if (g_pRelLogger)
2153 RTLogGroupSettings(g_pRelLogger, g_uVerbosity == 1 ? "all.e.l" : g_uVerbosity == 2 ? "all.e.l.f" : "all=~0");
2154 break;
2155
2156 case 'V':
2157 return audioTestVersion();
2158
2159 case 'h':
2160 audioTestShowLogo(g_pStdOut);
2161 return audioTestUsage(g_pStdOut);
2162
2163 case VINF_GETOPT_NOT_OPTION:
2164 {
2165 for (uintptr_t i = 0; i < RT_ELEMENTS(g_aCommands); i++)
2166 if (strcmp(ValueUnion.psz, g_aCommands[i].pszCommand) == 0)
2167 {
2168 size_t const cCombinedOptions = g_aCommands[i].cOptions + RT_ELEMENTS(g_aCmdCommonOptions);
2169 PRTGETOPTDEF paCombinedOptions = (PRTGETOPTDEF)RTMemAlloc(cCombinedOptions * sizeof(RTGETOPTDEF));
2170 if (paCombinedOptions)
2171 {
2172 memcpy(paCombinedOptions, g_aCmdCommonOptions, sizeof(g_aCmdCommonOptions));
2173 memcpy(&paCombinedOptions[RT_ELEMENTS(g_aCmdCommonOptions)],
2174 g_aCommands[i].paOptions, g_aCommands[i].cOptions * sizeof(RTGETOPTDEF));
2175
2176 rc = RTGetOptInit(&GetState, argc, argv, paCombinedOptions, cCombinedOptions,
2177 GetState.iNext /*idxFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2178 if (RT_SUCCESS(rc))
2179 {
2180
2181 rcExit = g_aCommands[i].pfnHandler(&GetState);
2182 RTMemFree(paCombinedOptions);
2183 return rcExit;
2184 }
2185 return RTMsgErrorExitFailure("RTGetOptInit failed for '%s': %Rrc", ValueUnion.psz, rc);
2186 }
2187 return RTMsgErrorExitFailure("Out of memory!");
2188 }
2189 RTMsgError("Unknown command '%s'!\n", ValueUnion.psz);
2190 audioTestUsage(g_pStdErr);
2191 return RTEXITCODE_SYNTAX;
2192 }
2193
2194 default:
2195 return RTGetOptPrintError(rc, &ValueUnion);
2196 }
2197 }
2198
2199 RTMsgError("No command specified!\n");
2200 audioTestUsage(g_pStdErr);
2201 return RTEXITCODE_SYNTAX;
2202}
2203
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette