VirtualBox

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

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

Audio/ValKit: More infrastructure code [build fix]. bugref:10008

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