VirtualBox

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

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

ValKit/AudioTest: Added a 'rec' command. bugref:10008

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