VirtualBox

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

Last change on this file since 89455 was 89442, checked in by vboxsync, 4 years ago

ValKit/AudioTest: Added some comments to the play command. bugref:10008

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

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