VirtualBox

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

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

Audio/ValKit: Simplified code a bit by removing unnecessary parameters. bugref:10008

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

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