VirtualBox

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

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

Audio/ValKit: More test data for the test manifest. bugref:10008

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