VirtualBox

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

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

ValKit/AudioTest: DrvAudio doesn't do any push back worth speaking of if the device hasn't finished initializing the stream, so pace ourselves a little when playing. Reduced unnecessary includes in the driver stack file. bugref:10008

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