VirtualBox

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

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

Audio/ValKit: Don't crash if "--debug-audio-path" is not set. bugref:10008

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

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