VirtualBox

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

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

ValKit/AudioTest: Removed PDMAUDIOSTREAMCFG::enmLayout and PDMAUDIOSTREAMLAYOUT. bugref:10008

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