VirtualBox

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

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

Audio/ValKit: More code for the audio test execution service (ATS), client side. bugref:10008

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