VirtualBox

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

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

ValKit/AudioTest: Use the driver stack functions for creating and destroying streams. The other ones doesn't work for the WasAPI backend (pfnStreamInitAsync). bugref:10008

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