VirtualBox

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

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

ValKit/Audio: Added switches for enabling and controlling the DrvAudio debug output. bugref:10008

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 86.4 KB
Line 
1/* $Id: vkat.cpp 89091 2021-05-17 11:11:09Z 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/errcore.h>
34#include <iprt/initterm.h>
35#include <iprt/getopt.h>
36#include <iprt/message.h>
37#include <iprt/path.h>
38#include <iprt/process.h>
39#include <iprt/rand.h>
40#include <iprt/stream.h>
41#include <iprt/string.h>
42#include <iprt/uuid.h>
43#include <iprt/test.h>
44
45#include <package-generated.h>
46#include "product-generated.h"
47
48#include <VBox/version.h>
49
50/**
51 * Internal driver instance data
52 * @note This must be put here as it's needed before pdmdrv.h is included.
53 */
54typedef struct PDMDRVINSINT
55{
56 /** The stack the drive belongs to. */
57 struct AUDIOTESTDRVSTACK *pStack;
58} PDMDRVINSINT;
59#define PDMDRVINSINT_DECLARED
60
61#include <VBox/vmm/pdmaudioinline.h>
62#include <VBox/vmm/pdmaudiohostenuminline.h>
63
64#include "../../../Devices/Audio/AudioHlp.h"
65#include "../../../Devices/Audio/AudioTest.h"
66#include "VBoxDD.h"
67
68
69/*********************************************************************************************************************************
70* Defined Constants And Macros *
71*********************************************************************************************************************************/
72/** For use in the option switch to handle common options. */
73#define AUDIO_TEST_COMMON_OPTION_CASES(a_ValueUnion) \
74 case 'q': \
75 g_uVerbosity = 0; \
76 break; \
77 \
78 case 'v': \
79 g_uVerbosity++; \
80 break; \
81 \
82 case 'V': \
83 return audioTestVersion(); \
84 \
85 case 'h': \
86 return audioTestUsage(g_pStdOut); \
87 \
88 case AUDIO_TEST_OPT_CMN_DEBUG_AUDIO_ENABLE: \
89 g_fDrvAudioDebug = true; \
90 break; \
91 \
92 case AUDIO_TEST_OPT_CMN_DEBUG_AUDIO_PATH: \
93 g_pszDrvAudioDebug = (a_ValueUnion).psz; \
94 break
95
96
97/*********************************************************************************************************************************
98* Structures and Typedefs *
99*********************************************************************************************************************************/
100struct AUDIOTESTENV;
101/** Pointer a audio test environment. */
102typedef AUDIOTESTENV *PAUDIOTESTENV;
103
104struct AUDIOTESTDESC;
105/** Pointer a audio test descriptor. */
106typedef AUDIOTESTDESC *PAUDIOTESTDESC;
107
108/**
109 * Callback to set up the test parameters for a specific test.
110 *
111 * @returns IPRT status code.
112 * @retval VINF_SUCCESS if setting the parameters up succeeded. Any other error code
113 * otherwise indicating the kind of error.
114 * @param pszTest Test name.
115 * @param pTstParmsAcq The audio test parameters to set up.
116 */
117typedef DECLCALLBACKTYPE(int, FNAUDIOTESTSETUP,(PAUDIOTESTENV pTstEnv, PAUDIOTESTDESC pTstDesc, PAUDIOTESTPARMS pTstParmsAcq, void **ppvCtx));
118/** Pointer to an audio test setup callback. */
119typedef FNAUDIOTESTSETUP *PFNAUDIOTESTSETUP;
120
121typedef DECLCALLBACKTYPE(int, FNAUDIOTESTEXEC,(PAUDIOTESTENV pTstEnv, void *pvCtx, PAUDIOTESTPARMS pTstParms));
122/** Pointer to an audio test exec callback. */
123typedef FNAUDIOTESTEXEC *PFNAUDIOTESTEXEC;
124
125typedef DECLCALLBACKTYPE(int, FNAUDIOTESTDESTROY,(PAUDIOTESTENV pTstEnv, void *pvCtx));
126/** Pointer to an audio test destroy callback. */
127typedef FNAUDIOTESTDESTROY *PFNAUDIOTESTDESTROY;
128
129/** Maximum audio streams a test environment can handle. */
130#define AUDIOTESTENV_MAX_STREAMS 8
131
132/**
133 * Audio test environment parameters.
134 * Not necessarily bound to a specific test (can be reused).
135 */
136typedef struct AUDIOTESTENV
137{
138 /** Output path for storing the test environment's final test files. */
139 char szPathOut[RTPATH_MAX];
140 /** Temporary path for this test environment. */
141 char szPathTemp[RTPATH_MAX];
142 /** The host (backend) driver to use. */
143 PPDMIHOSTAUDIO pDrvAudio;
144 /** The current (last) audio device enumeration to use. */
145 PDMAUDIOHOSTENUM DevEnum;
146 /** Audio stream. */
147 AUDIOTESTSTREAM aStreams[AUDIOTESTENV_MAX_STREAMS];
148 /** The audio test set to use. */
149 AUDIOTESTSET Set;
150} AUDIOTESTENV;
151
152/**
153 * Audio test descriptor.
154 */
155typedef struct AUDIOTESTDESC
156{
157 /** (Sort of) Descriptive test name. */
158 const char *pszName;
159 /** Flag whether the test is excluded. */
160 bool fExcluded;
161 /** The setup callback. */
162 PFNAUDIOTESTSETUP pfnSetup;
163 /** The exec callback. */
164 PFNAUDIOTESTEXEC pfnExec;
165 /** The destruction callback. */
166 PFNAUDIOTESTDESTROY pfnDestroy;
167} AUDIOTESTDESC;
168
169
170/**
171 * Audio driver stack.
172 *
173 * This can be just be backend driver alone or DrvAudio with a backend.
174 * @todo add automatic resampling via mixer so we can test more of the audio
175 * stack used by the device emulations.
176 */
177typedef struct AUDIOTESTDRVSTACK
178{
179 /** The device registration record for the backend. */
180 PCPDMDRVREG pDrvReg;
181 /** The backend driver instance. */
182 PPDMDRVINS pDrvBackendIns;
183 /** The backend's audio interface. */
184 PPDMIHOSTAUDIO pIHostAudio;
185
186 /** The DrvAudio instance. */
187 PPDMDRVINS pDrvAudioIns;
188 /** This is NULL if we don't use DrvAudio. */
189 PPDMIAUDIOCONNECTOR pIAudioConnector;
190} AUDIOTESTDRVSTACK;
191/** Pointer to an audio driver stack. */
192typedef AUDIOTESTDRVSTACK *PAUDIOTESTDRVSTACK;
193
194/**
195 * Backend-only stream structure.
196 */
197typedef struct AUDIOTESTDRVSTACKSTREAM
198{
199 /** The public stream data. */
200 PDMAUDIOSTREAM Core;
201 /** The acquired config. */
202 PDMAUDIOSTREAMCFG Cfg;
203 /** The backend data (variable size). */
204 PDMAUDIOBACKENDSTREAM Backend;
205} AUDIOTESTDRVSTACKSTREAM;
206/** Pointer to a backend-only stream structure. */
207typedef AUDIOTESTDRVSTACKSTREAM *PAUDIOTESTDRVSTACKSTREAM;
208
209
210/*********************************************************************************************************************************
211* Internal Functions *
212*********************************************************************************************************************************/
213static int audioTestCombineParms(PAUDIOTESTPARMS pBaseParms, PAUDIOTESTPARMS pOverrideParms);
214static int audioTestPlayTone(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PAUDIOTESTTONEPARMS pParms);
215static int audioTestStreamDestroy(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream);
216
217static int audioTestDrvConstruct(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg,
218 PPDMDRVINS pParentDrvIns, PPPDMDRVINS ppDrvIns);
219
220static RTEXITCODE audioTestUsage(PRTSTREAM pStrm);
221static RTEXITCODE audioTestVersion(void);
222
223
224/*********************************************************************************************************************************
225* Global Variables *
226*********************************************************************************************************************************/
227/**
228 * Common long options values.
229 */
230enum
231{
232 AUDIO_TEST_OPT_CMN_DEBUG_AUDIO_ENABLE = 256,
233 AUDIO_TEST_OPT_CMN_DEBUG_AUDIO_PATH
234};
235
236/**
237 * Long option values for the 'test' command.
238 */
239enum
240{
241 VKAT_TEST_OPT_COUNT = 900,
242 VKAT_TEST_OPT_DEV,
243 VKAT_TEST_OPT_OUTDIR,
244 VKAT_TEST_OPT_PAUSE,
245 VKAT_TEST_OPT_PCM_HZ,
246 VKAT_TEST_OPT_PCM_BIT,
247 VKAT_TEST_OPT_PCM_CHAN,
248 VKAT_TEST_OPT_PCM_SIGNED,
249 VKAT_TEST_OPT_TAG,
250 VKAT_TEST_OPT_TEMPDIR,
251 VKAT_TEST_OPT_VOL
252};
253
254/**
255 * Long option values for the 'verify' command.
256 */
257enum
258{
259 VKAT_VERIFY_OPT_TAG = 900
260};
261
262/**
263 * Common command line parameters.
264 */
265static const RTGETOPTDEF g_aCmdCommonOptions[] =
266{
267 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
268 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
269 { "--debug-audio", AUDIO_TEST_OPT_CMN_DEBUG_AUDIO_ENABLE, RTGETOPT_REQ_NOTHING },
270 { "--debug-audio-path", AUDIO_TEST_OPT_CMN_DEBUG_AUDIO_PATH, RTGETOPT_REQ_STRING },
271};
272
273/**
274 * Command line parameters for test mode.
275 */
276static const RTGETOPTDEF g_aCmdTestOptions[] =
277{
278 { "--backend", 'b', RTGETOPT_REQ_STRING },
279 { "--exclude", 'e', RTGETOPT_REQ_UINT32 },
280 { "--exclude-all", 'a', RTGETOPT_REQ_NOTHING },
281 { "--include", 'i', RTGETOPT_REQ_UINT32 },
282 { "--outdir", VKAT_TEST_OPT_OUTDIR, RTGETOPT_REQ_STRING },
283 { "--count", VKAT_TEST_OPT_COUNT, RTGETOPT_REQ_UINT32 },
284 { "--device", VKAT_TEST_OPT_DEV, RTGETOPT_REQ_STRING },
285 { "--pause", VKAT_TEST_OPT_PAUSE, RTGETOPT_REQ_UINT32 },
286 { "--pcm-bit", VKAT_TEST_OPT_PCM_BIT, RTGETOPT_REQ_UINT8 },
287 { "--pcm-chan", VKAT_TEST_OPT_PCM_CHAN, RTGETOPT_REQ_UINT8 },
288 { "--pcm-hz", VKAT_TEST_OPT_PCM_HZ, RTGETOPT_REQ_UINT16 },
289 { "--pcm-signed", VKAT_TEST_OPT_PCM_SIGNED, RTGETOPT_REQ_BOOL },
290 { "--tag", VKAT_TEST_OPT_TAG, RTGETOPT_REQ_STRING },
291 { "--tempdir", VKAT_TEST_OPT_TEMPDIR, RTGETOPT_REQ_STRING },
292 { "--volume", VKAT_TEST_OPT_VOL, RTGETOPT_REQ_UINT8 }
293};
294
295/**
296 * Command line parameters for verification mode.
297 */
298static const RTGETOPTDEF g_aCmdVerifyOptions[] =
299{
300 { "--tag", VKAT_VERIFY_OPT_TAG, RTGETOPT_REQ_STRING }
301};
302
303/**
304 * Backends.
305 *
306 * @note The first backend in the array is the default one for the platform.
307 */
308static struct
309{
310 /** The driver registration structure. */
311 PCPDMDRVREG pDrvReg;
312 /** The backend name.
313 * Aliases are implemented by having multiple entries for the same backend. */
314 const char *pszName;
315} const g_aBackends[] =
316{
317#if defined(VBOX_WITH_AUDIO_ALSA) && defined(RT_OS_LINUX)
318 { &g_DrvHostALSAAudio, "alsa" },
319#endif
320#ifdef VBOX_WITH_AUDIO_PULSE
321 { &g_DrvHostPulseAudio, "pulseaudio" },
322 { &g_DrvHostPulseAudio, "pulse" },
323 { &g_DrvHostPulseAudio, "pa" },
324#endif
325#ifdef VBOX_WITH_AUDIO_OSS
326 { &g_DrvHostOSSAudio, "oss" },
327#endif
328#if defined(RT_OS_DARWIN)
329 { &g_DrvHostCoreAudio, "coreaudio" },
330 { &g_DrvHostCoreAudio, "core" },
331 { &g_DrvHostCoreAudio, "ca" },
332#endif
333#if defined(RT_OS_WINDOWS)
334 { &g_DrvHostAudioWas, "wasapi" },
335 { &g_DrvHostAudioWas, "was" },
336 { &g_DrvHostDSound, "directsound" },
337 { &g_DrvHostDSound, "dsound" },
338 { &g_DrvHostDSound, "ds" }
339#endif
340};
341AssertCompile(sizeof(g_aBackends) > 0 /* port me */);
342
343/** The test handle. */
344static RTTEST g_hTest;
345/** The current verbosity level. */
346static unsigned g_uVerbosity = 0;
347/** DrvAudio: Enable debug (or not). */
348static bool g_fDrvAudioDebug = 0;
349/** DrvAudio: The debug output path. */
350static const char *g_pszDrvAudioDebug = NULL;
351
352
353/*********************************************************************************************************************************
354* Fake PDM Driver Handling. *
355*********************************************************************************************************************************/
356
357/** @name Driver Fakes/Stubs
358 *
359 * @note The VMM functions defined here will turn into driver helpers before
360 * long, as the drivers aren't supposed to import directly from the VMM in
361 * the future.
362 *
363 * @{ */
364
365VMMR3DECL(PCFGMNODE) CFGMR3GetChild(PCFGMNODE pNode, const char *pszPath)
366{
367 RT_NOREF(pNode, pszPath);
368 return NULL;
369}
370
371VMMR3DECL(int) CFGMR3QueryString(PCFGMNODE pNode, const char *pszName, char *pszString, size_t cchString)
372{
373 if (pNode != NULL)
374 {
375 PCPDMDRVREG pDrvReg = (PCPDMDRVREG)pNode;
376 if (g_uVerbosity > 2)
377 RTPrintf("debug: CFGMR3QueryString([%s], %s, %p, %#x)\n", pDrvReg->szName, pszName, pszString, cchString);
378
379 if ( ( strcmp(pDrvReg->szName, "PulseAudio") == 0
380 || strcmp(pDrvReg->szName, "HostAudioWas") == 0)
381 && strcmp(pszName, "VmName") == 0)
382 return RTStrCopy(pszString, cchString, "vkat");
383
384 if ( strcmp(pDrvReg->szName, "HostAudioWas") == 0
385 && strcmp(pszName, "VmUuid") == 0)
386 return RTStrCopy(pszString, cchString, "794c9192-d045-4f28-91ed-46253ac9998e");
387 }
388 else if (g_uVerbosity > 2)
389 RTPrintf("debug: CFGMR3QueryString(%p, %s, %p, %#x)\n", pNode, pszName, pszString, cchString);
390
391 return VERR_CFGM_VALUE_NOT_FOUND;
392}
393
394VMMR3DECL(int) CFGMR3QueryStringAlloc(PCFGMNODE pNode, const char *pszName, char **ppszString)
395{
396 char szStr[128];
397 int rc = CFGMR3QueryString(pNode, pszName, szStr, sizeof(szStr));
398 if (RT_SUCCESS(rc))
399 *ppszString = RTStrDup(szStr);
400
401 return rc;
402}
403
404VMMR3DECL(int) CFGMR3QueryStringDef(PCFGMNODE pNode, const char *pszName, char *pszString, size_t cchString, const char *pszDef)
405{
406 PCPDMDRVREG pDrvReg = (PCPDMDRVREG)pNode;
407 if (RT_VALID_PTR(pDrvReg))
408 {
409 const char *pszRet = pszDef;
410 if ( strcmp(pDrvReg->szName, "AUDIO") == 0
411 && strcmp(pszName, "DebugPathOut") == 0)
412 pszRet = g_pszDrvAudioDebug;
413
414 int rc = RTStrCopy(pszString, cchString, pszRet);
415
416 if (g_uVerbosity > 2)
417 RTPrintf("debug: CFGMR3QueryStringDef([%s], %s, %p, %#x, %s) -> '%s' + %Rrc\n",
418 pDrvReg->szName, pszName, pszString, cchString, pszDef, pszRet, rc);
419 return rc;
420 }
421
422 if (g_uVerbosity > 2)
423 RTPrintf("debug: CFGMR3QueryStringDef(%p, %s, %p, %#x, %s)\n", pNode, pszName, pszString, cchString, pszDef);
424 return RTStrCopy(pszString, cchString, pszDef);
425}
426
427VMMR3DECL(int) CFGMR3QueryBoolDef(PCFGMNODE pNode, const char *pszName, bool *pf, bool fDef)
428{
429 PCPDMDRVREG pDrvReg = (PCPDMDRVREG)pNode;
430 if (RT_VALID_PTR(pDrvReg))
431 {
432 *pf = fDef;
433 if ( strcmp(pDrvReg->szName, "AUDIO") == 0
434 && strcmp(pszName, "DebugEnabled") == 0)
435 *pf = g_fDrvAudioDebug;
436
437 if (g_uVerbosity > 2)
438 RTPrintf("debug: CFGMR3QueryBoolDef([%s], %s, %p, %RTbool) -> %RTbool\n", pDrvReg->szName, pszName, pf, fDef, *pf);
439 return VINF_SUCCESS;
440 }
441 *pf = fDef;
442 return VINF_SUCCESS;
443}
444
445VMMR3DECL(int) CFGMR3QueryU8(PCFGMNODE pNode, const char *pszName, uint8_t *pu8)
446{
447 RT_NOREF(pNode, pszName, pu8);
448 return VERR_CFGM_VALUE_NOT_FOUND;
449}
450
451VMMR3DECL(int) CFGMR3QueryU32(PCFGMNODE pNode, const char *pszName, uint32_t *pu32)
452{
453 RT_NOREF(pNode, pszName, pu32);
454 return VERR_CFGM_VALUE_NOT_FOUND;
455}
456
457VMMR3DECL(int) CFGMR3ValidateConfig(PCFGMNODE pNode, const char *pszNode,
458 const char *pszValidValues, const char *pszValidNodes,
459 const char *pszWho, uint32_t uInstance)
460{
461 RT_NOREF(pNode, pszNode, pszValidValues, pszValidNodes, pszWho, uInstance);
462 return VINF_SUCCESS;
463}
464
465/** @} */
466
467/** @name Driver Helper Fakes
468 * @{ */
469
470static DECLCALLBACK(int) audioTestDrvHlp_Attach(PPDMDRVINS pDrvIns, uint32_t fFlags, PPDMIBASE *ppBaseInterface)
471{
472 /* DrvAudio must be allowed to attach the backend driver (paranoid
473 backend drivers may call us to check that nothing is attached). */
474 if (strcmp(pDrvIns->pReg->szName, "AUDIO") == 0)
475 {
476 PAUDIOTESTDRVSTACK pDrvStack = pDrvIns->Internal.s.pStack;
477 AssertReturn(pDrvStack->pDrvBackendIns == NULL, VERR_PDM_DRIVER_ALREADY_ATTACHED);
478
479 if (g_uVerbosity > 1)
480 RTMsgInfo("Attaching backend '%s' to DrvAudio...\n", pDrvStack->pDrvReg->szName);
481 int rc = audioTestDrvConstruct(pDrvStack, pDrvStack->pDrvReg, pDrvIns, &pDrvStack->pDrvBackendIns);
482 if (RT_SUCCESS(rc))
483 {
484 if (ppBaseInterface)
485 *ppBaseInterface = &pDrvStack->pDrvBackendIns->IBase;
486 }
487 else
488 RTMsgError("Failed to attach backend: %Rrc", rc);
489 return rc;
490 }
491 RT_NOREF(fFlags);
492 return VERR_PDM_NO_ATTACHED_DRIVER;
493}
494
495static DECLCALLBACK(void) audioTestDrvHlp_STAMRegisterF(PPDMDRVINS pDrvIns, void *pvSample, STAMTYPE enmType,
496 STAMVISIBILITY enmVisibility, STAMUNIT enmUnit, const char *pszDesc,
497 const char *pszName, ...)
498{
499 RT_NOREF(pDrvIns, pvSample, enmType, enmVisibility, enmUnit, pszDesc, pszName);
500}
501
502static DECLCALLBACK(void) audioTestDrvHlp_STAMRegisterV(PPDMDRVINS pDrvIns, void *pvSample, STAMTYPE enmType,
503 STAMVISIBILITY enmVisibility, STAMUNIT enmUnit, const char *pszDesc,
504 const char *pszName, va_list args)
505{
506 RT_NOREF(pDrvIns, pvSample, enmType, enmVisibility, enmUnit, pszDesc, pszName, args);
507}
508
509static DECLCALLBACK(int) audioTestDrvHlp_STAMDeregister(PPDMDRVINS pDrvIns, void *pvSample)
510{
511 RT_NOREF(pDrvIns, pvSample);
512 return VINF_SUCCESS;
513}
514
515static DECLCALLBACK(int) audioTestDrvHlp_STAMDeregisterByPrefix(PPDMDRVINS pDrvIns, const char *pszPrefix)
516{
517 RT_NOREF(pDrvIns, pszPrefix);
518 return VINF_SUCCESS;
519}
520
521/**
522 * Get the driver helpers.
523 */
524static const PDMDRVHLPR3 *audioTestFakeGetDrvHlp(void)
525{
526 /*
527 * Note! No initializer for s_DrvHlp (also why it's not a file global).
528 * We do not want to have to update this code every time PDMDRVHLPR3
529 * grows new entries or are otherwise modified. Only when the
530 * entries used by the audio driver changes do we want to change
531 * our code.
532 */
533 static PDMDRVHLPR3 s_DrvHlp;
534 if (s_DrvHlp.u32Version != PDM_DRVHLPR3_VERSION)
535 {
536 s_DrvHlp.u32Version = PDM_DRVHLPR3_VERSION;
537 s_DrvHlp.u32TheEnd = PDM_DRVHLPR3_VERSION;
538 s_DrvHlp.pfnAttach = audioTestDrvHlp_Attach;
539 s_DrvHlp.pfnSTAMRegisterF = audioTestDrvHlp_STAMRegisterF;
540 s_DrvHlp.pfnSTAMRegisterV = audioTestDrvHlp_STAMRegisterV;
541 s_DrvHlp.pfnSTAMDeregister = audioTestDrvHlp_STAMDeregister;
542 s_DrvHlp.pfnSTAMDeregisterByPrefix = audioTestDrvHlp_STAMDeregisterByPrefix;
543 }
544 return &s_DrvHlp;
545}
546
547/** @} */
548
549
550/**
551 * Implementation of PDMIBASE::pfnQueryInterface for a fake device above
552 * DrvAudio.
553 */
554static DECLCALLBACK(void *) audioTestFakeDeviceIBaseQueryInterface(PPDMIBASE pInterface, const char *pszIID)
555{
556 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, pInterface);
557 RTMsgWarning("audioTestFakeDeviceIBaseQueryInterface: Unknown interface: %s\n", pszIID);
558 return NULL;
559}
560
561/** IBase interface for a fake device above DrvAudio. */
562static PDMIBASE g_AudioTestFakeDeviceIBase = { audioTestFakeDeviceIBaseQueryInterface };
563
564
565static DECLCALLBACK(int) audioTestIHostAudioPort_DoOnWorkerThread(PPDMIHOSTAUDIOPORT pInterface, PPDMAUDIOBACKENDSTREAM pStream,
566 uintptr_t uUser, void *pvUser)
567{
568 RT_NOREF(pInterface, pStream, uUser, pvUser);
569 RTMsgWarning("audioTestIHostAudioPort_DoOnWorkerThread was called\n");
570 return VERR_NOT_IMPLEMENTED;
571}
572
573DECLCALLBACK(void) audioTestIHostAudioPort_NotifyDeviceChanged(PPDMIHOSTAUDIOPORT pInterface, PDMAUDIODIR enmDir, void *pvUser)
574{
575 RT_NOREF(pInterface, enmDir, pvUser);
576 RTMsgWarning("audioTestIHostAudioPort_NotifyDeviceChanged was called\n");
577}
578
579static DECLCALLBACK(void) audioTestIHostAudioPort_StreamNotifyPreparingDeviceSwitch(PPDMIHOSTAUDIOPORT pInterface,
580 PPDMAUDIOBACKENDSTREAM pStream)
581{
582 RT_NOREF(pInterface, pStream);
583 RTMsgWarning("audioTestIHostAudioPort_StreamNotifyPreparingDeviceSwitch was called\n");
584}
585
586static DECLCALLBACK(void) audioTestIHostAudioPort_StreamNotifyDeviceChanged(PPDMIHOSTAUDIOPORT pInterface,
587 PPDMAUDIOBACKENDSTREAM pStream, bool fReInit)
588{
589 RT_NOREF(pInterface, pStream, fReInit);
590 RTMsgWarning("audioTestIHostAudioPort_StreamNotifyDeviceChanged was called\n");
591}
592
593static DECLCALLBACK(void) audioTestIHostAudioPort_NotifyDevicesChanged(PPDMIHOSTAUDIOPORT pInterface)
594{
595 RT_NOREF(pInterface);
596 RTMsgWarning("audioTestIHostAudioPort_NotifyDevicesChanged was called\n");
597}
598
599static PDMIHOSTAUDIOPORT g_AudioTestIHostAudioPort =
600{
601 audioTestIHostAudioPort_DoOnWorkerThread,
602 audioTestIHostAudioPort_NotifyDeviceChanged,
603 audioTestIHostAudioPort_StreamNotifyPreparingDeviceSwitch,
604 audioTestIHostAudioPort_StreamNotifyDeviceChanged,
605 audioTestIHostAudioPort_NotifyDevicesChanged,
606};
607
608/**
609 * Implementation of PDMIBASE::pfnQueryInterface for a fake DrvAudio above a
610 * backend.
611 */
612static DECLCALLBACK(void *) audioTestFakeDrvAudioIBaseQueryInterface(PPDMIBASE pInterface, const char *pszIID)
613{
614 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, pInterface);
615 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIOPORT, &g_AudioTestIHostAudioPort);
616 RTMsgWarning("audioTestFakeDrvAudioIBaseQueryInterface: Unknown interface: %s\n", pszIID);
617 return NULL;
618}
619
620/** IBase interface for a fake DrvAudio above a lonesome backend. */
621static PDMIBASE g_AudioTestFakeDrvAudioIBase = { audioTestFakeDrvAudioIBaseQueryInterface };
622
623
624
625/**
626 * Constructs a PDM audio driver instance.
627 *
628 * @returns VBox status code.
629 * @param pDrvStack The stack this is associated with.
630 * @param pDrvReg PDM driver registration record to use for construction.
631 * @param pParentDrvIns The parent driver (if any).
632 * @param ppDrvIns Where to return the driver instance structure.
633 */
634static int audioTestDrvConstruct(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, PPDMDRVINS pParentDrvIns,
635 PPPDMDRVINS ppDrvIns)
636{
637 /* The destruct function must have valid data to work with. */
638 *ppDrvIns = NULL;
639
640 /*
641 * Check registration structure validation (doesn't need to be too
642 * thorough, PDM check it in detail on every VM startup).
643 */
644 AssertPtrReturn(pDrvReg, VERR_INVALID_POINTER);
645 RTMsgInfo("Initializing backend '%s' ...\n", pDrvReg->szName);
646 AssertPtrReturn(pDrvReg->pfnConstruct, VERR_INVALID_PARAMETER);
647
648 /*
649 * Create the instance data structure.
650 */
651 PPDMDRVINS pDrvIns = (PPDMDRVINS)RTMemAllocZVar(RT_UOFFSETOF_DYN(PDMDRVINS, achInstanceData[pDrvReg->cbInstance]));
652 RTTEST_CHECK_RET(g_hTest, pDrvIns, VERR_NO_MEMORY);
653
654 pDrvIns->u32Version = PDM_DRVINS_VERSION;
655 pDrvIns->iInstance = 0;
656 pDrvIns->pHlpR3 = audioTestFakeGetDrvHlp();
657 pDrvIns->pvInstanceDataR3 = &pDrvIns->achInstanceData[0];
658 pDrvIns->pReg = pDrvReg;
659 pDrvIns->pCfg = (PCFGMNODE)pDrvReg;
660 pDrvIns->Internal.s.pStack = pDrvStack;
661 pDrvIns->pUpBase = NULL;
662 pDrvIns->pDownBase = NULL;
663 if (pParentDrvIns)
664 {
665 Assert(pParentDrvIns->pDownBase == NULL);
666 pParentDrvIns->pDownBase = &pDrvIns->IBase;
667 pDrvIns->pUpBase = &pParentDrvIns->IBase;
668 }
669 else if (strcmp(pDrvReg->szName, "AUDIO") == 0)
670 pDrvIns->pUpBase = &g_AudioTestFakeDeviceIBase;
671 else
672 pDrvIns->pUpBase = &g_AudioTestFakeDrvAudioIBase;
673
674 /*
675 * Invoke the constructor.
676 */
677 int rc = pDrvReg->pfnConstruct(pDrvIns, pDrvIns->pCfg, 0 /*fFlags*/);
678 if (RT_SUCCESS(rc))
679 {
680 *ppDrvIns = pDrvIns;
681 return VINF_SUCCESS;
682 }
683
684 RTTestFailed(g_hTest, "Failed to construct audio driver '%s': %Rrc", pDrvReg->szName, rc);
685 if (pDrvReg->pfnDestruct)
686 pDrvReg->pfnDestruct(pDrvIns);
687 RTMemFree(pDrvIns);
688 return rc;
689}
690
691/**
692 * Destructs a PDM audio driver instance.
693 *
694 * @param pDrvIns Driver instance to destruct.
695 */
696static void audioTestDrvDestruct(PPDMDRVINS pDrvIns)
697{
698 if (pDrvIns)
699 {
700 Assert(pDrvIns->u32Version == PDM_DRVINS_VERSION);
701
702 if (pDrvIns->pReg->pfnDestruct)
703 pDrvIns->pReg->pfnDestruct(pDrvIns);
704
705 pDrvIns->u32Version = 0;
706 pDrvIns->pReg = NULL;
707 RTMemFree(pDrvIns);
708 }
709}
710
711/**
712 * Sends the PDM driver a power off notification.
713 *
714 * @param pDrvIns Driver instance to notify.
715 */
716static void audioTestDrvNotifyPowerOff(PPDMDRVINS pDrvIns)
717{
718 if (pDrvIns)
719 {
720 Assert(pDrvIns->u32Version == PDM_DRVINS_VERSION);
721 if (pDrvIns->pReg->pfnPowerOff)
722 pDrvIns->pReg->pfnPowerOff(pDrvIns);
723 }
724}
725
726/**
727 * Deletes a driver stack.
728 *
729 * This will power off and destroy the drivers.
730 */
731static void audioTestDriverStackDelete(PAUDIOTESTDRVSTACK pDrvStack)
732{
733 /*
734 * Do power off notifications (top to bottom).
735 */
736 audioTestDrvNotifyPowerOff(pDrvStack->pDrvAudioIns);
737 audioTestDrvNotifyPowerOff(pDrvStack->pDrvBackendIns);
738
739 /*
740 * Drivers are destroyed from bottom to top (closest to the device).
741 */
742 audioTestDrvDestruct(pDrvStack->pDrvBackendIns);
743 pDrvStack->pDrvBackendIns = NULL;
744 pDrvStack->pIHostAudio = NULL;
745
746 audioTestDrvDestruct(pDrvStack->pDrvAudioIns);
747 pDrvStack->pDrvAudioIns = NULL;
748 pDrvStack->pIAudioConnector = NULL;
749}
750
751/**
752 * Initializes a driver stack.
753 *
754 * @returns VBox status code.
755 * @param pDrvStack The driver stack to initialize.
756 * @param pDrvReg The backend driver to use.
757 * @param fWithDrvAudio Whether to inlcude DrvAudio in the stack or not.
758 */
759static int audioTestDriverStackInit(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, bool fWithDrvAudio)
760{
761 RT_ZERO(*pDrvStack);
762 pDrvStack->pDrvReg = pDrvReg;
763
764 int rc;
765 if (!fWithDrvAudio)
766 rc = audioTestDrvConstruct(pDrvStack, pDrvReg, NULL /*pParentDrvIns*/, &pDrvStack->pDrvBackendIns);
767 else
768 {
769 rc = audioTestDrvConstruct(pDrvStack, &g_DrvAUDIO, NULL /*pParentDrvIns*/, &pDrvStack->pDrvAudioIns);
770 if (RT_SUCCESS(rc))
771 {
772 Assert(pDrvStack->pDrvAudioIns);
773 PPDMIBASE const pIBase = &pDrvStack->pDrvAudioIns->IBase;
774 pDrvStack->pIAudioConnector = (PPDMIAUDIOCONNECTOR)pIBase->pfnQueryInterface(pIBase, PDMIAUDIOCONNECTOR_IID);
775 if (pDrvStack->pIAudioConnector)
776 {
777 /* Both input and output is disabled by default. Fix that: */
778 rc = pDrvStack->pIAudioConnector->pfnEnable(pDrvStack->pIAudioConnector, PDMAUDIODIR_OUT, true);
779 if (RT_SUCCESS(rc))
780 rc = pDrvStack->pIAudioConnector->pfnEnable(pDrvStack->pIAudioConnector, PDMAUDIODIR_IN, true);
781 if (RT_FAILURE(rc))
782 {
783 RTTestFailed(g_hTest, "Failed to enabled input and output: %Rrc", rc);
784 audioTestDriverStackDelete(pDrvStack);
785 }
786 }
787 else
788 {
789 RTTestFailed(g_hTest, "Failed to query PDMIAUDIOCONNECTOR");
790 audioTestDriverStackDelete(pDrvStack);
791 rc = VERR_PDM_MISSING_INTERFACE;
792 }
793 }
794 }
795
796 /*
797 * Get the IHostAudio interface and check that the host driver is working.
798 */
799 if (RT_SUCCESS(rc))
800 {
801 PPDMIBASE const pIBase = &pDrvStack->pDrvBackendIns->IBase;
802 pDrvStack->pIHostAudio = (PPDMIHOSTAUDIO)pIBase->pfnQueryInterface(pIBase, PDMIHOSTAUDIO_IID);
803 if (pDrvStack->pIHostAudio)
804 {
805 PDMAUDIOBACKENDSTS enmStatus = pDrvStack->pIHostAudio->pfnGetStatus(pDrvStack->pIHostAudio, PDMAUDIODIR_OUT);
806 if (enmStatus == PDMAUDIOBACKENDSTS_RUNNING)
807 return VINF_SUCCESS;
808
809 RTTestFailed(g_hTest, "Expected backend status RUNNING, got %d instead", enmStatus);
810 }
811 else
812 RTTestFailed(g_hTest, "Failed to query PDMIHOSTAUDIO for '%s'", pDrvReg->szName);
813 audioTestDriverStackDelete(pDrvStack);
814 }
815
816 return rc;
817}
818
819/**
820 * Creates an output stream.
821 *
822 * @returns VBox status code.
823 * @param pDrvStack The audio driver stack to create it via.
824 * @param pProps The audio properties to use.
825 * @param cMsBufferSize The buffer size in milliseconds.
826 * @param cMsPreBuffer The pre-buffering amount in milliseconds.
827 * @param cMsSchedulingHint The scheduling hint in milliseconds.
828 * @param ppStream Where to return the stream pointer on success.
829 */
830static int audioTestDriverStackStreamCreateOutput(PAUDIOTESTDRVSTACK pDrvStack, PCPDMAUDIOPCMPROPS pProps,
831 uint32_t cMsBufferSize, uint32_t cMsPreBuffer, uint32_t cMsSchedulingHint,
832 PPDMAUDIOSTREAM *ppStream)
833{
834 char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX + 16];
835 *ppStream = NULL;
836
837 /*
838 * Calculate the stream config.
839 */
840 PDMAUDIOSTREAMCFG CfgReq;
841 int rc = PDMAudioStrmCfgInitWithProps(&CfgReq, pProps);
842 AssertRC(rc);
843 CfgReq.enmDir = PDMAUDIODIR_OUT;
844 CfgReq.u.enmDst = PDMAUDIOPLAYBACKDST_FRONT;
845 CfgReq.enmLayout = PDMAUDIOSTREAMLAYOUT_INTERLEAVED;
846 CfgReq.Device.cMsSchedulingHint = cMsSchedulingHint == UINT32_MAX || cMsSchedulingHint == 0
847 ? 10 : cMsSchedulingHint;
848 if (pDrvStack->pIAudioConnector && (cMsBufferSize == UINT32_MAX || cMsBufferSize == 0))
849 CfgReq.Backend.cFramesBufferSize = 0; /* DrvAudio picks the default */
850 else
851 CfgReq.Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(pProps,
852 cMsBufferSize == UINT32_MAX || cMsBufferSize == 0
853 ? 300 : cMsBufferSize);
854 if (cMsPreBuffer == UINT32_MAX)
855 CfgReq.Backend.cFramesPreBuffering = pDrvStack->pIAudioConnector ? UINT32_MAX /*DrvAudo picks the default */
856 : CfgReq.Backend.cFramesBufferSize * 2 / 3;
857 else
858 CfgReq.Backend.cFramesPreBuffering = PDMAudioPropsMilliToFrames(pProps, cMsPreBuffer);
859 if ( CfgReq.Backend.cFramesPreBuffering >= CfgReq.Backend.cFramesBufferSize + 16
860 && !pDrvStack->pIAudioConnector /*DrvAudio deals with it*/ )
861 {
862 RTMsgWarning("Cannot pre-buffer %#x frames with only %#x frames of buffer!",
863 CfgReq.Backend.cFramesPreBuffering, CfgReq.Backend.cFramesBufferSize);
864 CfgReq.Backend.cFramesPreBuffering = CfgReq.Backend.cFramesBufferSize > 16
865 ? CfgReq.Backend.cFramesBufferSize - 16 : 0;
866 }
867
868 static uint32_t s_idxStream = 0;
869 uint32_t const idxStream = s_idxStream++;
870 RTStrPrintf(CfgReq.szName, sizeof(CfgReq.szName), "out-%u", idxStream);
871
872 if (pDrvStack->pIAudioConnector)
873 {
874 /*
875 * DrvAudio does most of the work here.
876 */
877 PDMAUDIOSTREAMCFG CfgGst = CfgReq;
878 rc = pDrvStack->pIAudioConnector->pfnStreamCreate(pDrvStack->pIAudioConnector, PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF,
879 &CfgReq, &CfgGst, ppStream);
880 if (RT_SUCCESS(rc))
881 {
882 RTMsgInfo("Created backend stream: %s\n", PDMAudioStrmCfgToString(&CfgReq, szTmp, sizeof(szTmp)));
883 return rc;
884 }
885 RTTestFailed(g_hTest, "pfnStreamCreate failed: %Rrc", rc);
886 }
887 else
888 {
889 /*
890 * Get the config so we can see how big the PDMAUDIOBACKENDSTREAM
891 * structure actually is for this backend.
892 */
893 PDMAUDIOBACKENDCFG BackendCfg;
894 rc = pDrvStack->pIHostAudio->pfnGetConfig(pDrvStack->pIHostAudio, &BackendCfg);
895 if (RT_SUCCESS(rc))
896 {
897 if (BackendCfg.cbStream >= sizeof(PDMAUDIOBACKENDSTREAM))
898 {
899 /*
900 * Allocate and initialize the stream.
901 */
902 uint32_t const cbStream = sizeof(AUDIOTESTDRVSTACKSTREAM) - sizeof(PDMAUDIOBACKENDSTREAM) + BackendCfg.cbStream;
903 PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)RTMemAllocZVar(cbStream);
904 if (pStreamAt)
905 {
906 pStreamAt->Core.uMagic = PDMAUDIOSTREAM_MAGIC;
907 pStreamAt->Core.enmDir = PDMAUDIODIR_OUT;
908 pStreamAt->Core.cbBackend = cbStream;
909 pStreamAt->Core.Props = CfgReq.Props;
910 RTStrPrintf(pStreamAt->Core.szName, sizeof(pStreamAt->Core.szName), "out-%u", idxStream);
911
912 pStreamAt->Backend.uMagic = PDMAUDIOBACKENDSTREAM_MAGIC;
913 pStreamAt->Backend.pStream = &pStreamAt->Core;
914
915 /*
916 * Call the backend to create the stream.
917 */
918 pStreamAt->Cfg = CfgReq;
919
920 rc = pDrvStack->pIHostAudio->pfnStreamCreate(pDrvStack->pIHostAudio, &pStreamAt->Backend,
921 &CfgReq, &pStreamAt->Cfg);
922 if (RT_SUCCESS(rc))
923 {
924 pStreamAt->Core.Props = pStreamAt->Cfg.Props;
925 if (g_uVerbosity > 1)
926 RTMsgInfo("Created backend stream: %s\n",
927 PDMAudioStrmCfgToString(&pStreamAt->Cfg, szTmp, sizeof(szTmp)));
928
929 /* Return if stream is ready: */
930 if (rc == VINF_SUCCESS)
931 {
932 *ppStream = &pStreamAt->Core;
933 return VINF_SUCCESS;
934 }
935 if (rc == VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED)
936 {
937 /*
938 * Do async init right here and now.
939 */
940 rc = pDrvStack->pIHostAudio->pfnStreamInitAsync(pDrvStack->pIHostAudio, &pStreamAt->Backend,
941 false /*fDestroyed*/);
942 if (RT_SUCCESS(rc))
943 {
944 *ppStream = &pStreamAt->Core;
945 return VINF_SUCCESS;
946 }
947
948 RTTestFailed(g_hTest, "pfnStreamInitAsync failed: %Rrc\n", rc);
949 }
950 else
951 {
952 RTTestFailed(g_hTest, "pfnStreamCreate returned unexpected info status: %Rrc", rc);
953 rc = VERR_IPE_UNEXPECTED_INFO_STATUS;
954 }
955 pDrvStack->pIHostAudio->pfnStreamDestroy(pDrvStack->pIHostAudio, &pStreamAt->Backend);
956 }
957 else
958 RTTestFailed(g_hTest, "pfnStreamCreate failed: %Rrc\n", rc);
959 }
960 else
961 {
962 RTTestFailed(g_hTest, "Out of memory!\n");
963 rc = VERR_NO_MEMORY;
964 }
965 }
966 else
967 {
968 RTTestFailed(g_hTest, "cbStream=%#x is too small, min %#zx!\n", BackendCfg.cbStream, sizeof(PDMAUDIOBACKENDSTREAM));
969 rc = VERR_OUT_OF_RANGE;
970 }
971 }
972 else
973 RTTestFailed(g_hTest, "pfnGetConfig failed: %Rrc\n", rc);
974 }
975 return rc;
976}
977
978/**
979 * Destroys a stream.
980 */
981static void audioTestDriverStackStreamDestroy(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream)
982{
983 if (pStream)
984 {
985 if (pDrvStack->pIAudioConnector)
986 {
987 int rc = pDrvStack->pIAudioConnector->pfnStreamDestroy(pDrvStack->pIAudioConnector, pStream);
988 if (RT_FAILURE(rc))
989 RTTestFailed(g_hTest, "pfnStreamDestroy failed: %Rrc", rc);
990 }
991 else
992 {
993 PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
994 int rc = pDrvStack->pIHostAudio->pfnStreamDestroy(pDrvStack->pIHostAudio, &pStreamAt->Backend);
995 if (RT_SUCCESS(rc))
996 {
997 pStreamAt->Core.uMagic = ~PDMAUDIOSTREAM_MAGIC;
998 pStreamAt->Backend.uMagic = ~PDMAUDIOBACKENDSTREAM_MAGIC;
999 RTMemFree(pStreamAt);
1000 }
1001 else
1002 RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamDestroy failed: %Rrc", rc);
1003 }
1004 }
1005}
1006
1007/**
1008 * Enables a stream.
1009 */
1010static int audioTestDriverStackStreamEnable(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream)
1011{
1012 int rc;
1013 if (pDrvStack->pIAudioConnector)
1014 {
1015 rc = pDrvStack->pIAudioConnector->pfnStreamControl(pDrvStack->pIAudioConnector, pStream, PDMAUDIOSTREAMCMD_ENABLE);
1016 if (RT_FAILURE(rc))
1017 RTTestFailed(g_hTest, "pfnStreamControl/ENABLE failed: %Rrc", rc);
1018 }
1019 else
1020 {
1021 PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
1022 rc = pDrvStack->pIHostAudio->pfnStreamControl(pDrvStack->pIHostAudio, &pStreamAt->Backend, PDMAUDIOSTREAMCMD_ENABLE);
1023 if (RT_FAILURE(rc))
1024 RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamControl/ENABLE failed: %Rrc", rc);
1025 }
1026 return rc;
1027}
1028
1029/**
1030 * Drains an output stream.
1031 */
1032static int audioTestDriverStackStreamDrain(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream, bool fSync)
1033{
1034 int rc;
1035 if (pDrvStack->pIAudioConnector)
1036 {
1037 /*
1038 * Issue the drain request.
1039 */
1040 rc = pDrvStack->pIAudioConnector->pfnStreamControl(pDrvStack->pIAudioConnector, pStream, PDMAUDIOSTREAMCMD_DRAIN);
1041 if (RT_SUCCESS(rc) && fSync)
1042 {
1043 /*
1044 * This is a synchronous drain, so wait for the driver to change state to inactive.
1045 */
1046 PDMAUDIOSTREAMSTATE enmState;
1047 while ( (enmState = pDrvStack->pIAudioConnector->pfnStreamGetState(pDrvStack->pIAudioConnector, pStream))
1048 >= PDMAUDIOSTREAMSTATE_ENABLED)
1049 {
1050 RTThreadSleep(2);
1051 rc = pDrvStack->pIAudioConnector->pfnStreamIterate(pDrvStack->pIAudioConnector, pStream);
1052 if (RT_FAILURE(rc))
1053 {
1054 RTTestFailed(g_hTest, "pfnStreamIterate/DRAIN failed: %Rrc", rc);
1055 break;
1056 }
1057 }
1058 if (enmState != PDMAUDIOSTREAMSTATE_INACTIVE)
1059 {
1060 RTTestFailed(g_hTest, "Stream state not INACTIVE after draing: %s", PDMAudioStreamStateGetName(enmState));
1061 rc = VERR_AUDIO_STREAM_NOT_READY;
1062 }
1063 }
1064 else if (RT_FAILURE(rc))
1065 RTTestFailed(g_hTest, "pfnStreamControl/ENABLE failed: %Rrc", rc);
1066 }
1067 else
1068 {
1069 /*
1070 * Issue the drain request.
1071 */
1072 PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
1073 rc = pDrvStack->pIHostAudio->pfnStreamControl(pDrvStack->pIHostAudio, &pStreamAt->Backend, PDMAUDIOSTREAMCMD_DRAIN);
1074 if (RT_SUCCESS(rc) && fSync)
1075 {
1076 /*
1077 * This is a synchronous drain, so wait for the driver to change state to inactive.
1078 */
1079 PDMHOSTAUDIOSTREAMSTATE enmHostState;
1080 while ( (enmHostState = pDrvStack->pIHostAudio->pfnStreamGetState(pDrvStack->pIHostAudio, &pStreamAt->Backend))
1081 == PDMHOSTAUDIOSTREAMSTATE_DRAINING)
1082 {
1083 RTThreadSleep(2);
1084 uint32_t cbWritten = UINT32_MAX;
1085 rc = pDrvStack->pIHostAudio->pfnStreamPlay(pDrvStack->pIHostAudio, &pStreamAt->Backend,
1086 NULL /*pvBuf*/, 0 /*cbBuf*/, &cbWritten);
1087 if (RT_FAILURE(rc))
1088 {
1089 RTTestFailed(g_hTest, "pfnStreamPlay/DRAIN failed: %Rrc", rc);
1090 break;
1091 }
1092 if (cbWritten != 0)
1093 {
1094 RTTestFailed(g_hTest, "pfnStreamPlay/DRAIN did not set cbWritten to zero: %#x", cbWritten);
1095 rc = VERR_MISSING;
1096 break;
1097 }
1098 }
1099 if (enmHostState != PDMHOSTAUDIOSTREAMSTATE_OKAY)
1100 {
1101 RTTestFailed(g_hTest, "Stream state not OKAY after draing: %s", PDMHostAudioStreamStateGetName(enmHostState));
1102 rc = VERR_AUDIO_STREAM_NOT_READY;
1103 }
1104 }
1105 else if (RT_FAILURE(rc))
1106 RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamControl/ENABLE failed: %Rrc", rc);
1107 }
1108 return rc;
1109}
1110
1111/**
1112 * Checks if the stream is okay.
1113 * @returns true if okay, false if not.
1114 */
1115static bool audioTestDriverStackStreamIsOkay(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream)
1116{
1117 /*
1118 * Get the stream status and check if it means is okay or not.
1119 */
1120 bool fRc = false;
1121 if (pDrvStack->pIAudioConnector)
1122 {
1123 PDMAUDIOSTREAMSTATE enmState = pDrvStack->pIAudioConnector->pfnStreamGetState(pDrvStack->pIAudioConnector, pStream);
1124 switch (enmState)
1125 {
1126 case PDMAUDIOSTREAMSTATE_NOT_WORKING:
1127 case PDMAUDIOSTREAMSTATE_NEED_REINIT:
1128 break;
1129 case PDMAUDIOSTREAMSTATE_INACTIVE:
1130 case PDMAUDIOSTREAMSTATE_ENABLED:
1131 case PDMAUDIOSTREAMSTATE_ENABLED_READABLE:
1132 case PDMAUDIOSTREAMSTATE_ENABLED_WRITABLE:
1133 fRc = true;
1134 break;
1135 /* no default */
1136 case PDMAUDIOSTREAMSTATE_INVALID:
1137 case PDMAUDIOSTREAMSTATE_END:
1138 case PDMAUDIOSTREAMSTATE_32BIT_HACK:
1139 break;
1140 }
1141 }
1142 else
1143 {
1144 PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
1145 PDMHOSTAUDIOSTREAMSTATE enmHostState = pDrvStack->pIHostAudio->pfnStreamGetState(pDrvStack->pIHostAudio,
1146 &pStreamAt->Backend);
1147 switch (enmHostState)
1148 {
1149 case PDMHOSTAUDIOSTREAMSTATE_INITIALIZING:
1150 case PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING:
1151 break;
1152 case PDMHOSTAUDIOSTREAMSTATE_OKAY:
1153 case PDMHOSTAUDIOSTREAMSTATE_DRAINING:
1154 case PDMHOSTAUDIOSTREAMSTATE_INACTIVE:
1155 fRc = true;
1156 break;
1157 /* no default */
1158 case PDMHOSTAUDIOSTREAMSTATE_INVALID:
1159 case PDMHOSTAUDIOSTREAMSTATE_END:
1160 case PDMHOSTAUDIOSTREAMSTATE_32BIT_HACK:
1161 break;
1162 }
1163 }
1164 return fRc;
1165}
1166
1167/**
1168 * Gets the number of bytes it's currently possible to write to the stream.
1169 */
1170static uint32_t audioTestDriverStackStreamGetWritable(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream)
1171{
1172 uint32_t cbWritable;
1173 if (pDrvStack->pIAudioConnector)
1174 cbWritable = pDrvStack->pIAudioConnector->pfnStreamGetWritable(pDrvStack->pIAudioConnector, pStream);
1175 else
1176 {
1177 PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
1178 cbWritable = pDrvStack->pIHostAudio->pfnStreamGetWritable(pDrvStack->pIHostAudio, &pStreamAt->Backend);
1179 }
1180 return cbWritable;
1181}
1182
1183/**
1184 * Tries to play the @a cbBuf bytes of samples in @a pvBuf.
1185 */
1186static int audioTestDriverStackStreamPlay(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream,
1187 void const *pvBuf, uint32_t cbBuf, uint32_t *pcbPlayed)
1188{
1189 int rc;
1190 if (pDrvStack->pIAudioConnector)
1191 {
1192 rc = pDrvStack->pIAudioConnector->pfnStreamPlay(pDrvStack->pIAudioConnector, pStream, pvBuf, cbBuf, pcbPlayed);
1193 if (RT_FAILURE(rc))
1194 RTTestFailed(g_hTest, "pfnStreamPlay(,,,%#x) failed: %Rrc", cbBuf, rc);
1195 }
1196 else
1197 {
1198 PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
1199 rc = pDrvStack->pIHostAudio->pfnStreamPlay(pDrvStack->pIHostAudio, &pStreamAt->Backend, pvBuf, cbBuf, pcbPlayed);
1200 if (RT_FAILURE(rc))
1201 RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamPlay(,,,%#x) failed: %Rrc", cbBuf, rc);
1202 }
1203 return rc;
1204}
1205
1206
1207/*********************************************************************************************************************************
1208* Implementation of Something *
1209*********************************************************************************************************************************/
1210
1211/**
1212 * Initializes an audio test environment.
1213 *
1214 * @param pTstEnv Audio test environment to initialize.
1215 * @param pDrvAudio Audio driver to use.
1216 * @param pszPathOut Output path to use. If NULL, the system's temp directory will be used.
1217 * @param pszPathTemp Temporary path to use. If NULL, the system's temp directory will be used.
1218 * @param pszTag Tag name to use. If NULL, a generated UUID will be used.
1219 */
1220static int audioTestEnvInit(PAUDIOTESTENV pTstEnv, PPDMIHOSTAUDIO pDrvAudio, const char *pszTag)
1221{
1222 pTstEnv->pDrvAudio = pDrvAudio;
1223 PDMAudioHostEnumInit(&pTstEnv->DevEnum);
1224
1225 int rc = VINF_SUCCESS;
1226
1227 char szPathTemp[RTPATH_MAX];
1228 if ( !strlen(pTstEnv->szPathTemp)
1229 || !strlen(pTstEnv->szPathOut))
1230 rc = RTPathTemp(szPathTemp, sizeof(szPathTemp));
1231
1232 if ( RT_SUCCESS(rc)
1233 && !strlen(pTstEnv->szPathTemp))
1234 rc = RTPathJoin(pTstEnv->szPathTemp, sizeof(pTstEnv->szPathTemp), szPathTemp, "vkat-temp");
1235
1236 if ( RT_SUCCESS(rc)
1237 && !strlen(pTstEnv->szPathOut))
1238 rc = RTPathJoin(pTstEnv->szPathOut, sizeof(pTstEnv->szPathOut), szPathTemp, "vkat");
1239
1240 if (RT_SUCCESS(rc))
1241 rc = AudioTestSetCreate(&pTstEnv->Set, pTstEnv->szPathTemp, pszTag);
1242
1243 if (RT_SUCCESS(rc))
1244 {
1245 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Using tag '%s'\n", pszTag);
1246 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Output directory is '%s'\n", pTstEnv->szPathOut);
1247 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Temp directory is '%s'\n", pTstEnv->szPathTemp);
1248 }
1249
1250 return rc;
1251}
1252
1253/**
1254 * Destroys an audio test environment.
1255 *
1256 * @param pTstEnv Audio test environment to destroy.
1257 */
1258static void audioTestEnvDestroy(PAUDIOTESTENV pTstEnv)
1259{
1260 if (!pTstEnv)
1261 return;
1262
1263 PDMAudioHostEnumDelete(&pTstEnv->DevEnum);
1264
1265 for (unsigned i = 0; i < RT_ELEMENTS(pTstEnv->aStreams); i++)
1266 {
1267 int rc2 = audioTestStreamDestroy(pTstEnv, &pTstEnv->aStreams[i]);
1268 if (RT_FAILURE(rc2))
1269 RTTestFailed(g_hTest, "Stream destruction for stream #%u failed with %Rrc\n", i, rc2);
1270 }
1271
1272 AudioTestSetDestroy(&pTstEnv->Set);
1273}
1274
1275/**
1276 * Initializes an audio test parameters set.
1277 *
1278 * @param pTstParms Test parameters set to initialize.
1279 */
1280static void audioTestParmsInit(PAUDIOTESTPARMS pTstParms)
1281{
1282 RT_ZERO(*pTstParms);
1283}
1284
1285/**
1286 * Destroys an audio test parameters set.
1287 *
1288 * @param pTstParms Test parameters set to destroy.
1289 */
1290static void audioTestParmsDestroy(PAUDIOTESTPARMS pTstParms)
1291{
1292 if (!pTstParms)
1293 return;
1294
1295 return;
1296}
1297
1298
1299/*********************************************************************************************************************************
1300* Some other stuff, you name it. *
1301*********************************************************************************************************************************/
1302
1303/**
1304 * Enumerates audio devices and optionally searches for a specific device.
1305 *
1306 * @returns VBox status code.
1307 * @param pTstEnv Test env to use for enumeration.
1308 * @param pszDev Device name to search for. Can be NULL if the default device shall be used.
1309 * @param ppDev Where to return the pointer of the device enumeration of \a pTstEnv when a
1310 * specific device was found.
1311 */
1312static int audioTestDevicesEnumerateAndCheck(PAUDIOTESTENV pTstEnv, const char *pszDev, PPDMAUDIOHOSTDEV *ppDev)
1313{
1314 RTTestSubF(g_hTest, "Enumerating audio devices and checking for device '%s'", pszDev ? pszDev : "<Default>");
1315
1316 if (!pTstEnv->pDrvAudio->pfnGetDevices)
1317 {
1318 RTTestSkipped(g_hTest, "Backend does not support device enumeration, skipping");
1319 return VINF_NOT_SUPPORTED;
1320 }
1321
1322 Assert(pszDev == NULL || ppDev);
1323
1324 if (ppDev)
1325 *ppDev = NULL;
1326
1327 int rc = pTstEnv->pDrvAudio->pfnGetDevices(pTstEnv->pDrvAudio, &pTstEnv->DevEnum);
1328 if (RT_SUCCESS(rc))
1329 {
1330 PPDMAUDIOHOSTDEV pDev;
1331 RTListForEach(&pTstEnv->DevEnum.LstDevices, pDev, PDMAUDIOHOSTDEV, ListEntry)
1332 {
1333 char szFlags[PDMAUDIOHOSTDEV_MAX_FLAGS_STRING_LEN];
1334 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Device '%s':\n", pDev->szName);
1335 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Usage = %s\n", PDMAudioDirGetName(pDev->enmUsage));
1336 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Flags = %s\n", PDMAudioHostDevFlagsToString(szFlags, pDev->fFlags));
1337 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Input channels = %RU8\n", pDev->cMaxInputChannels);
1338 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Output channels = %RU8\n", pDev->cMaxOutputChannels);
1339
1340 if ( pszDev
1341 && !RTStrCmp(pDev->szName, pszDev))
1342 {
1343 *ppDev = pDev;
1344 }
1345 }
1346 }
1347 else
1348 RTTestFailed(g_hTest, "Enumerating audio devices failed with %Rrc", rc);
1349
1350 RTTestSubDone(g_hTest);
1351
1352 if ( pszDev
1353 && *ppDev == NULL)
1354 {
1355 RTTestFailed(g_hTest, "Audio device '%s' not found", pszDev);
1356 return VERR_NOT_FOUND;
1357 }
1358
1359 return VINF_SUCCESS;
1360}
1361
1362/**
1363 * Opens an audio device.
1364 *
1365 * @returns VBox status code.
1366 * @param pDev Audio device to open.
1367 */
1368static int audioTestDeviceOpen(PPDMAUDIOHOSTDEV pDev)
1369{
1370 int rc = VINF_SUCCESS;
1371
1372 RTTestSubF(g_hTest, "Opening audio device '%s' ...", pDev->szName);
1373
1374 /** @todo Detect + open device here. */
1375
1376 RTTestSubDone(g_hTest);
1377
1378 return rc;
1379}
1380
1381/**
1382 * Closes an audio device.
1383 *
1384 * @returns VBox status code.
1385 * @param pDev Audio device to close.
1386 */
1387static int audioTestDeviceClose(PPDMAUDIOHOSTDEV pDev)
1388{
1389 int rc = VINF_SUCCESS;
1390
1391 RTTestSubF(g_hTest, "Closing audio device '%s' ...", pDev->szName);
1392
1393 /** @todo Close device here. */
1394
1395 RTTestSubDone(g_hTest);
1396
1397 return rc;
1398}
1399
1400/**
1401 * Creates an audio test stream.
1402 *
1403 * @returns VBox status code.
1404 * @param pTstEnv Test environment to use for creating the stream.
1405 * @param pStream Audio stream to create.
1406 * @param pCfg Stream configuration to use for creation.
1407 */
1408static int audioTestStreamCreate(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PPDMAUDIOSTREAMCFG pCfg)
1409{
1410 PDMAUDIOSTREAMCFG CfgAcq;
1411
1412 int rc = PDMAudioStrmCfgCopy(&CfgAcq, pCfg);
1413 AssertRC(rc); /* Cannot fail. */
1414
1415 rc = pTstEnv->pDrvAudio->pfnStreamCreate(pTstEnv->pDrvAudio, &pStream->Backend, pCfg, &CfgAcq);
1416 if (RT_FAILURE(rc))
1417 return rc;
1418
1419 /* Do the async init in a synchronous way for now here. */
1420 if (rc == VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED)
1421 rc = pTstEnv->pDrvAudio->pfnStreamInitAsync(pTstEnv->pDrvAudio, &pStream->Backend, false /* fDestroyed */);
1422
1423 if (RT_SUCCESS(rc))
1424 pStream->fCreated = true;
1425
1426 return rc;
1427}
1428
1429/**
1430 * Destroys an audio test stream.
1431 *
1432 * @returns VBox status code.
1433 * @param pTstEnv Test environment the stream to destroy contains.
1434 * @param pStream Audio stream to destroy.
1435 */
1436static int audioTestStreamDestroy(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream)
1437{
1438 if (!pStream)
1439 return VINF_SUCCESS;
1440
1441 if (!pStream->fCreated)
1442 return VINF_SUCCESS;
1443
1444 /** @todo Anything else to do here, e.g. test if there are left over samples or some such? */
1445
1446 int rc = pTstEnv->pDrvAudio->pfnStreamDestroy(pTstEnv->pDrvAudio, &pStream->Backend);
1447 if (RT_SUCCESS(rc))
1448 RT_BZERO(pStream, sizeof(PDMAUDIOBACKENDSTREAM));
1449
1450 return rc;
1451}
1452
1453/**
1454 * Creates an audio default output test stream.
1455 * Convenience function.
1456 *
1457 * @returns VBox status code.
1458 * @param pTstEnv Test environment to use for creating the stream.
1459 * @param pStream Audio stream to create.
1460 * @param pProps PCM properties to use for creation.
1461 */
1462static int audioTestCreateStreamDefaultOut(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PPDMAUDIOPCMPROPS pProps)
1463{
1464 PDMAUDIOSTREAMCFG Cfg;
1465 int rc = PDMAudioStrmCfgInitWithProps(&Cfg, pProps);
1466 AssertRC(rc); /* Cannot fail. */
1467
1468 Cfg.enmDir = PDMAUDIODIR_OUT;
1469 Cfg.u.enmDst = PDMAUDIOPLAYBACKDST_FRONT;
1470 Cfg.enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;
1471
1472 Cfg.Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(pProps, 300);
1473 Cfg.Backend.cFramesPreBuffering = PDMAudioPropsMilliToFrames(pProps, 200);
1474 Cfg.Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(pProps, 10);
1475 Cfg.Device.cMsSchedulingHint = 10;
1476
1477 return audioTestStreamCreate(pTstEnv, pStream, &Cfg);
1478}
1479
1480/**
1481 * Plays a test tone on a specific audio test stream.
1482 *
1483 * @returns VBox status code.
1484 * @param pTstEnv Test environment to use for running the test.
1485 * @param pStream Stream to use for playing the tone.
1486 * @param pParms Tone parameters to use.
1487 *
1488 * @note Blocking function.
1489 */
1490static int audioTestPlayTone(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PAUDIOTESTTONEPARMS pParms)
1491{
1492 AUDIOTESTTONE TstTone;
1493 AudioTestToneInitRandom(&TstTone, &pParms->Props);
1494
1495 const char *pcszPathOut = pTstEnv->Set.szPathAbs;
1496
1497 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Playing test tone (tone frequency is %RU16Hz, %RU32ms)\n", TstTone.rdFreqHz, pParms->msDuration);
1498 RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Writing to '%s'\n", pcszPathOut);
1499
1500 /** @todo Use .WAV here? */
1501 PAUDIOTESTOBJ pObj;
1502 int rc = AudioTestSetObjCreateAndRegister(&pTstEnv->Set, "tone.pcm", &pObj);
1503 AssertRCReturn(rc, rc);
1504
1505 PDMHOSTAUDIOSTREAMSTATE enmState = pTstEnv->pDrvAudio->pfnStreamGetState(pTstEnv->pDrvAudio, &pStream->Backend);
1506 if (enmState == PDMHOSTAUDIOSTREAMSTATE_OKAY)
1507 {
1508 uint32_t cbBuf;
1509 uint8_t abBuf[_4K];
1510
1511 const uint64_t tsStartMs = RTTimeMilliTS();
1512 const uint16_t cSchedulingMs = RTRandU32Ex(10, 80); /* Chose a random scheduling (in ms). */
1513 const uint32_t cbPerMs = PDMAudioPropsMilliToBytes(&pParms->Props, cSchedulingMs);
1514
1515 do
1516 {
1517 rc = AudioTestToneGenerate(&TstTone, abBuf, RT_MIN(cbPerMs, sizeof(abBuf)), &cbBuf);
1518 if (RT_SUCCESS(rc))
1519 {
1520 /* Write stuff to disk before trying to play it. Help analysis later. */
1521 rc = AudioTestSetObjWrite(pObj, abBuf, cbBuf);
1522 if (RT_SUCCESS(rc))
1523 {
1524 uint32_t cbWritten;
1525 rc = pTstEnv->pDrvAudio->pfnStreamPlay(pTstEnv->pDrvAudio, &pStream->Backend, abBuf, cbBuf, &cbWritten);
1526 }
1527 }
1528
1529 if (RTTimeMilliTS() - tsStartMs >= pParms->msDuration)
1530 break;
1531
1532 if (RT_FAILURE(rc))
1533 break;
1534
1535 RTThreadSleep(cSchedulingMs);
1536
1537 } while (RT_SUCCESS(rc));
1538 }
1539 else
1540 rc = VERR_AUDIO_STREAM_NOT_READY;
1541
1542 int rc2 = AudioTestSetObjClose(pObj);
1543 if (RT_SUCCESS(rc))
1544 rc = rc2;
1545
1546 return rc;
1547}
1548
1549/**
1550 * Overrides audio test base parameters with another set.
1551 *
1552 * @returns VBox status code.
1553 * @param pBaseParms Base parameters to override.
1554 * @param pOverrideParms Override parameters to use for overriding the base parameters.
1555 *
1556 * @note Overriding a parameter depends on its type / default values.
1557 */
1558static int audioTestCombineParms(PAUDIOTESTPARMS pBaseParms, PAUDIOTESTPARMS pOverrideParms)
1559{
1560 RT_NOREF(pBaseParms, pOverrideParms);
1561
1562 /** @todo Implement parameter overriding. */
1563 return VERR_NOT_IMPLEMENTED;
1564}
1565
1566
1567/*********************************************************************************************************************************
1568* Test callbacks *
1569*********************************************************************************************************************************/
1570
1571/**
1572 * @copydoc FNAUDIOTESTSETUP
1573 */
1574static DECLCALLBACK(int) audioTestPlayToneSetup(PAUDIOTESTENV pTstEnv, PAUDIOTESTDESC pTstDesc, PAUDIOTESTPARMS pTstParmsAcq, void **ppvCtx)
1575{
1576 RT_NOREF(pTstEnv, pTstDesc, ppvCtx);
1577
1578 PDMAudioPropsInit(&pTstParmsAcq->TestTone.Props, 16 /* bit */ / 8, true /* fSigned */, 2 /* Channels */, 44100 /* Hz */);
1579
1580#ifdef DEBUG_andy
1581 pTstParmsAcq->cIterations = RTRandU32Ex(1, 1);
1582#endif
1583 pTstParmsAcq->cIterations = RTRandU32Ex(1, 10);
1584 pTstParmsAcq->idxCurrent = 0;
1585
1586 return VINF_SUCCESS;
1587}
1588
1589/**
1590 * @copydoc FNAUDIOTESTEXEC
1591 */
1592static DECLCALLBACK(int) audioTestPlayToneExec(PAUDIOTESTENV pTstEnv, void *pvCtx, PAUDIOTESTPARMS pTstParms)
1593{
1594 RT_NOREF(pvCtx);
1595
1596 int rc = VINF_SUCCESS;
1597
1598 PAUDIOTESTSTREAM pStream = &pTstEnv->aStreams[0];
1599
1600 for (uint32_t i = 0; i < pTstParms->cIterations; i++)
1601 {
1602 AudioTestToneParamsInitRandom(&pTstParms->TestTone, &pTstParms->TestTone.Props);
1603 rc = audioTestCreateStreamDefaultOut(pTstEnv, pStream, &pTstParms->TestTone.Props);
1604 if (RT_SUCCESS(rc))
1605 rc = audioTestPlayTone(pTstEnv, pStream, &pTstParms->TestTone);
1606
1607 int rc2 = audioTestStreamDestroy(pTstEnv, pStream);
1608 if (RT_SUCCESS(rc))
1609 rc = rc2;
1610 }
1611
1612 return rc;
1613}
1614
1615/**
1616 * @copydoc FNAUDIOTESTDESTROY
1617 */
1618static DECLCALLBACK(int) audioTestPlayToneDestroy(PAUDIOTESTENV pTstEnv, void *pvCtx)
1619{
1620 RT_NOREF(pTstEnv, pvCtx);
1621
1622 return VINF_SUCCESS;
1623}
1624
1625
1626/*********************************************************************************************************************************
1627* Test execution *
1628*********************************************************************************************************************************/
1629
1630static AUDIOTESTDESC g_aTests[] =
1631{
1632 /* pszTest fExcluded pfnSetup */
1633 { "PlayTone", false, audioTestPlayToneSetup, audioTestPlayToneExec, audioTestPlayToneDestroy }
1634};
1635
1636/**
1637 * Runs one specific audio test.
1638 *
1639 * @returns VBox status code.
1640 * @param pTstEnv Test environment to use for running the test.
1641 * @param pTstDesc Test to run.
1642 * @param uSeq Test sequence # in case there are more tests.
1643 * @param pOverrideParms Test parameters for overriding the actual test parameters. Optional.
1644 */
1645static int audioTestOne(PAUDIOTESTENV pTstEnv, PAUDIOTESTDESC pTstDesc,
1646 unsigned uSeq, PAUDIOTESTPARMS pOverrideParms)
1647{
1648 RT_NOREF(uSeq);
1649
1650 int rc;
1651
1652 AUDIOTESTPARMS TstParms;
1653 audioTestParmsInit(&TstParms);
1654
1655 RTTestSub(g_hTest, pTstDesc->pszName);
1656
1657 if (pTstDesc->fExcluded)
1658 {
1659 RTTestSkipped(g_hTest, "Excluded from list");
1660 return VINF_SUCCESS;
1661 }
1662
1663 void *pvCtx = NULL; /* Test-specific opaque context. Optional and can be NULL. */
1664
1665 if (pTstDesc->pfnSetup)
1666 {
1667 rc = pTstDesc->pfnSetup(pTstEnv, pTstDesc, &TstParms, &pvCtx);
1668 if (RT_FAILURE(rc))
1669 {
1670 RTTestFailed(g_hTest, "Test setup failed\n");
1671 return rc;
1672 }
1673 }
1674
1675 audioTestCombineParms(&TstParms, pOverrideParms);
1676
1677 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%u: %RU32 iterations\n", uSeq, TstParms.cIterations);
1678
1679 if (strlen(TstParms.Dev.szName)) /** @todo Refine this check. */
1680 rc = audioTestDeviceOpen(&TstParms.Dev);
1681
1682 AssertPtr(pTstDesc->pfnExec);
1683 rc = pTstDesc->pfnExec(pTstEnv, pvCtx, &TstParms);
1684
1685 RTTestSubDone(g_hTest);
1686
1687 if (pTstDesc->pfnDestroy)
1688 {
1689 int rc2 = pTstDesc->pfnDestroy(pTstEnv, pvCtx);
1690 if (RT_SUCCESS(rc))
1691 rc = rc2;
1692
1693 if (RT_FAILURE(rc2))
1694 RTTestFailed(g_hTest, "Test destruction failed\n");
1695 }
1696
1697 rc = audioTestDeviceClose(&TstParms.Dev);
1698
1699 audioTestParmsDestroy(&TstParms);
1700
1701 return rc;
1702}
1703
1704/**
1705 * Runs all specified tests in a row.
1706 *
1707 * @returns VBox status code.
1708 * @param pTstEnv Test environment to use for running all tests.
1709 * @param pOverrideParms Test parameters for (some / all) specific test parameters. Optional.
1710 */
1711static int audioTestWorker(PAUDIOTESTENV pTstEnv, PAUDIOTESTPARMS pOverrideParms)
1712{
1713 int rc = VINF_SUCCESS;
1714
1715 unsigned uSeq = 0;
1716 for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++)
1717 {
1718 int rc2 = audioTestOne(pTstEnv, &g_aTests[i], uSeq, pOverrideParms);
1719 if (RT_SUCCESS(rc))
1720 rc = rc2;
1721
1722 if (!g_aTests[i].fExcluded)
1723 uSeq++;
1724 }
1725
1726 return rc;
1727}
1728
1729/** Option help for the 'test' command. */
1730static DECLCALLBACK(const char *) audioTestCmdTestHelp(PCRTGETOPTDEF pOpt)
1731{
1732 switch (pOpt->iShort)
1733 {
1734 case 'd': return "Use the specified audio device";
1735 case 'e': return "Exclude the given test id from the list";
1736 case 'a': return "Exclude all tests from the list (useful to enable single tests later with --include)";
1737 case 'i': return "Include the given test id in the list";
1738 }
1739 return NULL;
1740}
1741
1742/**
1743 * Main (entry) function for the testing functionality of VKAT.
1744 *
1745 * @returns Program exit code.
1746 * @param pGetState RTGetOpt state.
1747 */
1748static DECLCALLBACK(RTEXITCODE) audioTestMain(PRTGETOPTSTATE pGetState)
1749{
1750 AUDIOTESTENV TstEnv;
1751 RT_ZERO(TstEnv);
1752
1753 AUDIOTESTPARMS TstCust;
1754 audioTestParmsInit(&TstCust);
1755
1756 const char *pszDevice = NULL; /* Custom device to use. Can be NULL if not being used. */
1757 const char *pszTag = NULL; /* Custom tag to use. Can be NULL if not being used. */
1758 PCPDMDRVREG pDrvReg = g_aBackends[0].pDrvReg;
1759
1760 int rc;
1761 RTGETOPTUNION ValueUnion;
1762 while ((rc = RTGetOpt(pGetState, &ValueUnion)))
1763 {
1764 switch (rc)
1765 {
1766 case 'a':
1767 for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++)
1768 g_aTests[i].fExcluded = true;
1769 break;
1770
1771 case 'b':
1772 pDrvReg = NULL;
1773 for (uintptr_t i = 0; i < RT_ELEMENTS(g_aBackends); i++)
1774 if ( strcmp(ValueUnion.psz, g_aBackends[i].pszName) == 0
1775 || strcmp(ValueUnion.psz, g_aBackends[i].pDrvReg->szName) == 0)
1776 {
1777 pDrvReg = g_aBackends[i].pDrvReg;
1778 break;
1779 }
1780 if (pDrvReg == NULL)
1781 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown backend: '%s'", ValueUnion.psz);
1782 break;
1783
1784 case 'e':
1785 if (ValueUnion.u32 >= RT_ELEMENTS(g_aTests))
1786 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid test number %u passed to --exclude", ValueUnion.u32);
1787 g_aTests[ValueUnion.u32].fExcluded = true;
1788 break;
1789
1790 case 'i':
1791 if (ValueUnion.u32 >= RT_ELEMENTS(g_aTests))
1792 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid test number %u passed to --include", ValueUnion.u32);
1793 g_aTests[ValueUnion.u32].fExcluded = false;
1794 break;
1795
1796 case VKAT_TEST_OPT_COUNT:
1797 break;
1798
1799 case VKAT_TEST_OPT_DEV:
1800 pszDevice = ValueUnion.psz;
1801 break;
1802
1803 case VKAT_TEST_OPT_PAUSE:
1804 break;
1805
1806 case VKAT_TEST_OPT_OUTDIR:
1807 rc = RTStrCopy(TstEnv.szPathOut, sizeof(TstEnv.szPathOut), ValueUnion.psz);
1808 if (RT_FAILURE(rc))
1809 return RTMsgErrorExitFailure("Failed to copy out directory: %Rrc", rc);
1810 break;
1811
1812 case VKAT_TEST_OPT_PCM_BIT:
1813 /** @todo r=bird: the X suffix means: "fingers off!" */
1814 TstCust.TestTone.Props.cbSampleX = ValueUnion.u8 / 8 /* bit */;
1815 break;
1816
1817 case VKAT_TEST_OPT_PCM_CHAN:
1818 /** @todo r=bird: the X suffix means: "fingers off!" */
1819 TstCust.TestTone.Props.cChannelsX = ValueUnion.u8;
1820 break;
1821
1822 case VKAT_TEST_OPT_PCM_HZ:
1823 TstCust.TestTone.Props.uHz = ValueUnion.u32;
1824 break;
1825
1826 case VKAT_TEST_OPT_PCM_SIGNED:
1827 TstCust.TestTone.Props.fSigned = ValueUnion.f;
1828 break;
1829
1830 case VKAT_TEST_OPT_TAG:
1831 pszTag = ValueUnion.psz;
1832 break;
1833
1834 case VKAT_TEST_OPT_TEMPDIR:
1835 rc = RTStrCopy(TstEnv.szPathTemp, sizeof(TstEnv.szPathTemp), ValueUnion.psz);
1836 if (RT_FAILURE(rc))
1837 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Temp dir invalid, rc=%Rrc", rc);
1838 break;
1839
1840 case VKAT_TEST_OPT_VOL:
1841 TstCust.TestTone.uVolumePercent = ValueUnion.u8;
1842 break;
1843
1844 AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion);
1845
1846 default:
1847 return RTGetOptPrintError(rc, &ValueUnion);
1848 }
1849 }
1850
1851 /*
1852 * Start testing.
1853 */
1854 RTTestBanner(g_hTest);
1855
1856 AUDIOTESTDRVSTACK DrvStack;
1857 rc = audioTestDriverStackInit(&DrvStack, pDrvReg, false /*fWithDrvAudio*/);
1858 if (RT_SUCCESS(rc))
1859 {
1860 /* For now all tests have the same test environment. */
1861 /** @todo bake the DrvStack into the test env make make it more flexible so
1862 * we can also test with/without DrvAudio (need option above). */
1863 rc = audioTestEnvInit(&TstEnv, DrvStack.pIHostAudio, pszTag);
1864 if (RT_SUCCESS(rc))
1865 {
1866 PPDMAUDIOHOSTDEV pDev;
1867 rc = audioTestDevicesEnumerateAndCheck(&TstEnv, pszDevice, &pDev);
1868 if (RT_SUCCESS(rc))
1869 audioTestWorker(&TstEnv, &TstCust);
1870
1871 /* Before destroying the test environment, pack up the test set so
1872 * that it's ready for transmission. */
1873 char szFileOut[RTPATH_MAX];
1874 rc = AudioTestSetPack(&TstEnv.Set, TstEnv.szPathOut, szFileOut, sizeof(szFileOut));
1875 if (RT_SUCCESS(rc))
1876 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test set packed up to '%s'\n", szFileOut);
1877
1878 /* Clean up. */
1879 AudioTestSetWipe(&TstEnv.Set);
1880
1881 audioTestEnvDestroy(&TstEnv);
1882 }
1883 audioTestDriverStackDelete(&DrvStack);
1884 }
1885
1886 audioTestParmsDestroy(&TstCust);
1887
1888 if (RT_FAILURE(rc)) /* Let us know that something went wrong in case we forgot to mention it. */
1889 RTTestFailed(g_hTest, "Tested failed with %Rrc\n", rc);
1890
1891 /*
1892 * Print summary and exit.
1893 */
1894 return RTTestSummaryAndDestroy(g_hTest);
1895}
1896
1897
1898/*********************************************************************************************************************************
1899* Command: verify *
1900*********************************************************************************************************************************/
1901
1902/**
1903 * Verifies one single test set.
1904 *
1905 * @returns VBox status code.
1906 * @param pszPath Absolute path to test set.
1907 * @param pszTag Tag of test set to verify. Optional and can be NULL.
1908 */
1909static int audioVerifyOne(const char *pszPath, const char *pszTag)
1910{
1911 RTTestSubF(g_hTest, "Verifying test set (tag '%s') ...", pszTag ? pszTag : "default");
1912
1913 AUDIOTESTSET tstSet;
1914 int rc = AudioTestSetOpen(&tstSet, pszPath);
1915 if (RT_SUCCESS(rc))
1916 {
1917 AUDIOTESTERRORDESC errDesc;
1918 rc = AudioTestSetVerify(&tstSet, pszTag ? pszTag : "default", &errDesc);
1919 if (RT_SUCCESS(rc))
1920 {
1921 if (AudioTestErrorDescFailed(&errDesc))
1922 {
1923 /** @todo Use some AudioTestErrorXXX API for enumeration here later. */
1924 PAUDIOTESTERRORENTRY pErrEntry;
1925 RTListForEach(&errDesc.List, pErrEntry, AUDIOTESTERRORENTRY, Node)
1926 {
1927 RTTestFailed(g_hTest, pErrEntry->szDesc);
1928 }
1929 }
1930 else
1931 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Verification successful");
1932
1933 AudioTestErrorDescDestroy(&errDesc);
1934 }
1935 else
1936 RTTestFailed(g_hTest, "Verification failed with %Rrc", rc);
1937
1938 AudioTestSetClose(&tstSet);
1939 }
1940 else
1941 RTTestFailed(g_hTest, "Opening test set '%s' (tag '%s') failed, rc=%Rrc\n", pszPath, pszTag, rc);
1942
1943 RTTestSubDone(g_hTest);
1944
1945 return rc;
1946}
1947
1948/**
1949 * Main (entry) function for the verification functionality of VKAT.
1950 *
1951 * @returns Program exit code.
1952 * @param pGetState RTGetOpt state.
1953 */
1954static DECLCALLBACK(RTEXITCODE) audioVerifyMain(PRTGETOPTSTATE pGetState)
1955{
1956 /*
1957 * Parse options and process arguments.
1958 */
1959 const char *pszTag = NULL; /* Custom tag to use. Can be NULL if not being used. */
1960 unsigned iTestSet = 0;
1961
1962 int rc;
1963 RTGETOPTUNION ValueUnion;
1964 while ((rc = RTGetOpt(pGetState, &ValueUnion)))
1965 {
1966 switch (rc)
1967 {
1968 case VKAT_VERIFY_OPT_TAG:
1969 pszTag = ValueUnion.psz;
1970 if (g_uVerbosity > 0)
1971 RTMsgInfo("Using tag '%s'\n", pszTag);
1972 break;
1973
1974 case VINF_GETOPT_NOT_OPTION:
1975 if (iTestSet == 0)
1976 RTTestBanner(g_hTest);
1977 audioVerifyOne(ValueUnion.psz, pszTag);
1978 iTestSet++;
1979 break;
1980
1981 AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion);
1982
1983 default:
1984 return RTGetOptPrintError(rc, &ValueUnion);
1985 }
1986 }
1987
1988 /*
1989 * If no paths given, default to the current directory.
1990 */
1991 if (iTestSet == 0)
1992 {
1993 if (iTestSet == 0)
1994 RTTestBanner(g_hTest);
1995 char szDirCur[RTPATH_MAX];
1996 rc = RTPathGetCurrent(szDirCur, sizeof(szDirCur));
1997 audioVerifyOne(RT_SUCCESS(rc) ? szDirCur : ".", pszTag);
1998 }
1999
2000 /*
2001 * Print summary and exit.
2002 */
2003 return RTTestSummaryAndDestroy(g_hTest);
2004}
2005
2006
2007/*********************************************************************************************************************************
2008* Command: play *
2009*********************************************************************************************************************************/
2010
2011/**
2012 * Worker for audioTestCmdPlayHandler that plays one file.
2013 */
2014static RTEXITCODE audioTestPlayOne(const char *pszFile, PCPDMDRVREG pDrvReg, uint32_t cMsBufferSize,
2015 uint32_t cMsPreBuffer, uint32_t cMsSchedulingHint, bool fWithDrvAudio)
2016{
2017 /*
2018 * First we must open the file and determin the format.
2019 */
2020 AUDIOTESTWAVEFILE WaveFile;
2021 int rc = AudioTestWaveFileOpen(pszFile, &WaveFile);
2022 if (RT_FAILURE(rc))
2023 return RTMsgErrorExitFailure("Failed to open '%s': %Rrc", pszFile, rc);
2024
2025 if (g_uVerbosity > 0)
2026 {
2027 char szTmp[128];
2028 RTMsgInfo("Opened '%s' for playing\n", pszFile);
2029 RTMsgInfo("Format: %s\n", PDMAudioPropsToString(&WaveFile.Props, szTmp, sizeof(szTmp)));
2030 RTMsgInfo("Size: %'RU32 bytes / %#RX32 / %'RU32 frames / %'RU64 ns\n",
2031 WaveFile.cbSamples, WaveFile.cbSamples,
2032 PDMAudioPropsBytesToFrames(&WaveFile.Props, WaveFile.cbSamples),
2033 PDMAudioPropsBytesToNano(&WaveFile.Props, WaveFile.cbSamples));
2034 }
2035
2036 /*
2037 * Construct the driver stack.
2038 */
2039 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
2040 AUDIOTESTDRVSTACK DrvStack;
2041 rc = audioTestDriverStackInit(&DrvStack, pDrvReg, fWithDrvAudio);
2042 if (RT_SUCCESS(rc))
2043 {
2044 /*
2045 * Open a stream for the output.
2046 */
2047 PPDMAUDIOSTREAM pStream = NULL;
2048 rc = audioTestDriverStackStreamCreateOutput(&DrvStack, &WaveFile.Props, cMsBufferSize,
2049 cMsPreBuffer, cMsSchedulingHint, &pStream);
2050 if (RT_SUCCESS(rc))
2051 {
2052 rc = audioTestDriverStackStreamEnable(&DrvStack, pStream);
2053 if (RT_SUCCESS(rc))
2054 {
2055 uint64_t const nsStarted = RTTimeNanoTS();
2056
2057 /*
2058 * Transfer data as quickly as we're allowed.
2059 */
2060 for (;;)
2061 {
2062 /* Read a chunk from the wave file. */
2063 uint8_t abSamples[16384];
2064 size_t cbSamples = 0;
2065 rc = AudioTestWaveFileRead(&WaveFile, abSamples, sizeof(abSamples), &cbSamples);
2066 if (RT_SUCCESS(rc) && cbSamples > 0)
2067 {
2068 /* Transfer the data to the audio stream. */
2069 for (uint32_t offSamples = 0; offSamples < cbSamples;)
2070 {
2071 uint32_t const cbCanWrite = audioTestDriverStackStreamGetWritable(&DrvStack, pStream);
2072 if (cbCanWrite > 0)
2073 {
2074 uint32_t const cbToPlay = RT_MIN(cbCanWrite, (uint32_t)cbSamples - offSamples);
2075 uint32_t cbPlayed = 0;
2076 rc = audioTestDriverStackStreamPlay(&DrvStack, pStream, &abSamples[offSamples],
2077 cbToPlay, &cbPlayed);
2078 if (RT_SUCCESS(rc))
2079 {
2080 if (cbPlayed)
2081 offSamples += cbPlayed;
2082 else
2083 {
2084 rcExit = RTMsgErrorExitFailure("Played zero out of %#x bytes - %#x bytes reported playable!\n",
2085 cbToPlay, cbCanWrite);
2086 break;
2087 }
2088 }
2089 else
2090 {
2091 rcExit = RTMsgErrorExitFailure("Failed to play %#x bytes: %Rrc\n", cbToPlay, rc);
2092 break;
2093 }
2094 }
2095 else if (audioTestDriverStackStreamIsOkay(&DrvStack, pStream))
2096 RTThreadSleep(RT_MIN(RT_MAX(1, cMsSchedulingHint), 256));
2097 else
2098 {
2099 rcExit = RTMsgErrorExitFailure("Stream is not okay!\n");
2100 break;
2101 }
2102 }
2103 }
2104 else if (RT_SUCCESS(rc) && cbSamples == 0)
2105 {
2106 rcExit = RTEXITCODE_SUCCESS;
2107 break;
2108 }
2109 else
2110 {
2111 rcExit = RTMsgErrorExitFailure("Error reading wav file '%s': %Rrc", pszFile, rc);
2112 break;
2113 }
2114 }
2115
2116 /*
2117 * Drain the stream.
2118 */
2119 if (rcExit == RTEXITCODE_SUCCESS)
2120 {
2121 if (g_uVerbosity > 0)
2122 RTMsgInfo("%'RU64 ns: Draining...\n", RTTimeNanoTS() - nsStarted);
2123 rc = audioTestDriverStackStreamDrain(&DrvStack, pStream, true /*fSync*/);
2124 if (RT_SUCCESS(rc))
2125 {
2126 if (g_uVerbosity > 0)
2127 RTMsgInfo("%'RU64 ns: Done\n", RTTimeNanoTS() - nsStarted);
2128 }
2129 else
2130 rcExit = RTMsgErrorExitFailure("Draining failed: %Rrc", rc);
2131 }
2132 }
2133 else
2134 rcExit = RTMsgErrorExitFailure("Enabling the output stream failed: %Rrc", rc);
2135 audioTestDriverStackStreamDestroy(&DrvStack, pStream);
2136 }
2137 else
2138 rcExit = RTMsgErrorExitFailure("Creating output stream failed: %Rrc", rc);
2139 audioTestDriverStackDelete(&DrvStack);
2140 }
2141 else
2142 rcExit = RTMsgErrorExitFailure("Driver stack construction failed: %Rrc", rc);
2143 AudioTestWaveFileClose(&WaveFile);
2144 return rcExit;
2145}
2146
2147/**
2148 * Command line parameters for test mode.
2149 */
2150static const RTGETOPTDEF g_aCmdPlayOptions[] =
2151{
2152 { "--backend", 'b', RTGETOPT_REQ_STRING },
2153 { "--with-drv-audio", 'd', RTGETOPT_REQ_NOTHING },
2154};
2155
2156/** the 'play' command option help. */
2157static DECLCALLBACK(const char *) audioTestCmdPlayHelp(PCRTGETOPTDEF pOpt)
2158{
2159 switch (pOpt->iShort)
2160 {
2161 case 'b': return "The audio backend to use.";
2162 case 'd': return "Go via DrvAudio instead of directly interfacing with the backend.";
2163 default: return NULL;
2164 }
2165}
2166
2167/**
2168 * The 'play' command handler.
2169 *
2170 * @returns Program exit code.
2171 * @param pGetState RTGetOpt state.
2172 */
2173static DECLCALLBACK(RTEXITCODE) audioTestCmdPlayHandler(PRTGETOPTSTATE pGetState)
2174{
2175 /* Option values: */
2176 PCPDMDRVREG pDrvReg = g_aBackends[0].pDrvReg;
2177 uint32_t cMsBufferSize = UINT32_MAX;
2178 uint32_t cMsPreBuffer = UINT32_MAX;
2179 uint32_t cMsSchedulingHint = UINT32_MAX;
2180 bool fWithDrvAudio = false;
2181
2182 /* Argument processing loop: */
2183 int rc;
2184 RTGETOPTUNION ValueUnion;
2185 while ((rc = RTGetOpt(pGetState, &ValueUnion)) != 0)
2186 {
2187 switch (rc)
2188 {
2189 case 'b':
2190 pDrvReg = NULL;
2191 for (uintptr_t i = 0; i < RT_ELEMENTS(g_aBackends); i++)
2192 if ( strcmp(ValueUnion.psz, g_aBackends[i].pszName) == 0
2193 || strcmp(ValueUnion.psz, g_aBackends[i].pDrvReg->szName) == 0)
2194 {
2195 pDrvReg = g_aBackends[i].pDrvReg;
2196 break;
2197 }
2198 if (pDrvReg == NULL)
2199 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown backend: '%s'", ValueUnion.psz);
2200 break;
2201
2202 case 'd':
2203 fWithDrvAudio = true;
2204 break;
2205
2206 case VINF_GETOPT_NOT_OPTION:
2207 {
2208 RTEXITCODE rcExit = audioTestPlayOne(ValueUnion.psz, pDrvReg, cMsBufferSize, cMsPreBuffer,
2209 cMsSchedulingHint, fWithDrvAudio);
2210 if (rcExit != RTEXITCODE_SUCCESS)
2211 return rcExit;
2212 break;
2213 }
2214
2215 AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion);
2216
2217 default:
2218 return RTGetOptPrintError(rc, &ValueUnion);
2219 }
2220 }
2221 return RTEXITCODE_SUCCESS;
2222}
2223
2224
2225/**
2226 * Commands.
2227 */
2228static struct
2229{
2230 /** The command name. */
2231 const char *pszCommand;
2232 /** The command handler. */
2233 DECLCALLBACKMEMBER(RTEXITCODE, pfnHandler,(PRTGETOPTSTATE pGetState));
2234
2235 /** Command description. */
2236 const char *pszDesc;
2237 /** Options array. */
2238 PCRTGETOPTDEF paOptions;
2239 /** Number of options in the option array. */
2240 size_t cOptions;
2241 /** Gets help for an option. */
2242 DECLCALLBACKMEMBER(const char *, pfnOptionHelp,(PCRTGETOPTDEF pOpt));
2243} const g_aCommands[] =
2244{
2245 {
2246 "test", audioTestMain,
2247 "Does some kind of testing, I guess...",
2248 g_aCmdTestOptions, RT_ELEMENTS(g_aCmdTestOptions), audioTestCmdTestHelp
2249 },
2250 {
2251 "verify", audioVerifyMain,
2252 "Verfies something, I guess...",
2253 g_aCmdVerifyOptions, RT_ELEMENTS(g_aCmdVerifyOptions), NULL,
2254 },
2255 {
2256 "play", audioTestCmdPlayHandler,
2257 "Plays one or more wave files.",
2258 g_aCmdPlayOptions, RT_ELEMENTS(g_aCmdPlayOptions), audioTestCmdPlayHelp,
2259 },
2260};
2261
2262/**
2263 * Shows tool usage text.
2264 */
2265static RTEXITCODE audioTestUsage(PRTSTREAM pStrm)
2266{
2267 RTStrmPrintf(pStrm, "usage: %s [global options] <command> [command-options]\n",
2268 RTPathFilename(RTProcExecutablePath()));
2269 RTStrmPrintf(pStrm,
2270 "\n"
2271 "Global Options:\n"
2272 " --debug-audio\n"
2273 " Enables DrvAudio debugging.\n"
2274 " --debug-audio-path=<path>\n"
2275 " Tells DrvAudio where to put its debug output (wav-files).\n"
2276 " -q, --quiet\n"
2277 " Sets verbosity to zero.\n"
2278 " -v, --verbose\n"
2279 " Increase verbosity.\n"
2280 " -V, --version\n"
2281 " Displays version.\n"
2282 " -h, -?, --help\n"
2283 " Displays help.\n"
2284 );
2285
2286 for (uintptr_t iCmd = 0; iCmd < RT_ELEMENTS(g_aCommands); iCmd++)
2287 {
2288 RTStrmPrintf(pStrm,
2289 "\n"
2290 "Command '%s':\n"
2291 " %s\n"
2292 "Options for '%s':\n",
2293 g_aCommands[iCmd].pszCommand, g_aCommands[iCmd].pszDesc, g_aCommands[iCmd].pszCommand);
2294 PCRTGETOPTDEF const paOptions = g_aCommands[iCmd].paOptions;
2295 for (unsigned i = 0; i < g_aCommands[iCmd].cOptions; i++)
2296 {
2297 if (RT_C_IS_PRINT(paOptions[i].iShort))
2298 RTStrmPrintf(pStrm, " -%c, %s\n", paOptions[i].iShort, paOptions[i].pszLong);
2299 else
2300 RTStrmPrintf(pStrm, " %s\n", paOptions[i].pszLong);
2301
2302 const char *pszHelp = NULL;
2303 if (g_aCommands[iCmd].pfnOptionHelp)
2304 pszHelp = g_aCommands[iCmd].pfnOptionHelp(&paOptions[i]);
2305 if (pszHelp)
2306 RTStrmPrintf(pStrm, " %s\n", pszHelp);
2307 }
2308 }
2309 return RTEXITCODE_SUCCESS;
2310}
2311
2312/**
2313 * Shows tool version.
2314 */
2315static RTEXITCODE audioTestVersion(void)
2316{
2317 RTPrintf("v0.0.1\n");
2318 return RTEXITCODE_SUCCESS;
2319}
2320
2321/**
2322 * Shows the logo.
2323 *
2324 * @param pStream Output stream to show logo on.
2325 */
2326static void audioTestShowLogo(PRTSTREAM pStream)
2327{
2328 RTStrmPrintf(pStream, VBOX_PRODUCT " VKAT (Validation Kit Audio Test) Version " VBOX_VERSION_STRING " - r%s\n"
2329 "(C) " VBOX_C_YEAR " " VBOX_VENDOR "\n"
2330 "All rights reserved.\n\n", RTBldCfgRevisionStr());
2331}
2332
2333int main(int argc, char **argv)
2334{
2335 /*
2336 * Init IPRT and globals.
2337 */
2338 RTEXITCODE rcExit = RTTestInitAndCreate("AudioTest", &g_hTest);
2339 if (rcExit != RTEXITCODE_SUCCESS)
2340 return rcExit;
2341
2342 /*
2343 * Process common options.
2344 */
2345 RTGETOPTSTATE GetState;
2346 int rc = RTGetOptInit(&GetState, argc, argv, g_aCmdCommonOptions,
2347 RT_ELEMENTS(g_aCmdCommonOptions), 1 /*idxFirst*/, 0 /*fFlags - must not sort! */);
2348 AssertRCReturn(rc, RTEXITCODE_INIT);
2349
2350 RTGETOPTUNION ValueUnion;
2351 while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0)
2352 {
2353 switch (rc)
2354 {
2355 case 'q':
2356 g_uVerbosity = 0;
2357 break;
2358
2359 case 'v':
2360 g_uVerbosity++;
2361 break;
2362
2363 case 'V':
2364 return audioTestVersion();
2365
2366 case 'h':
2367 audioTestShowLogo(g_pStdOut);
2368 return audioTestUsage(g_pStdOut);
2369
2370 case VINF_GETOPT_NOT_OPTION:
2371 {
2372 for (uintptr_t i = 0; i < RT_ELEMENTS(g_aCommands); i++)
2373 if (strcmp(ValueUnion.psz, g_aCommands[i].pszCommand) == 0)
2374 {
2375 size_t const cCombinedOptions = g_aCommands[i].cOptions + RT_ELEMENTS(g_aCmdCommonOptions);
2376 PRTGETOPTDEF paCombinedOptions = (PRTGETOPTDEF)RTMemAlloc(cCombinedOptions * sizeof(RTGETOPTDEF));
2377 if (paCombinedOptions)
2378 {
2379 memcpy(paCombinedOptions, g_aCmdCommonOptions, sizeof(g_aCmdCommonOptions));
2380 memcpy(&paCombinedOptions[RT_ELEMENTS(g_aCmdCommonOptions)],
2381 g_aCommands[i].paOptions, g_aCommands[i].cOptions * sizeof(RTGETOPTDEF));
2382
2383 rc = RTGetOptInit(&GetState, argc, argv, paCombinedOptions, cCombinedOptions,
2384 GetState.iNext /*idxFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2385 if (RT_SUCCESS(rc))
2386 {
2387
2388 rcExit = g_aCommands[i].pfnHandler(&GetState);
2389 RTMemFree(paCombinedOptions);
2390 return rcExit;
2391 }
2392 return RTMsgErrorExitFailure("RTGetOptInit failed for '%s': %Rrc", ValueUnion.psz, rc);
2393 }
2394 return RTMsgErrorExitFailure("Out of memory!");
2395 }
2396 RTMsgError("Unknown command '%s'!\n", ValueUnion.psz);
2397 audioTestUsage(g_pStdErr);
2398 return RTEXITCODE_SYNTAX;
2399 }
2400
2401 default:
2402 return RTGetOptPrintError(rc, &ValueUnion);
2403 }
2404 }
2405
2406 RTMsgError("No command specified!\n");
2407 audioTestUsage(g_pStdErr);
2408 return RTEXITCODE_SYNTAX;
2409}
2410
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