VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/utils/audio/vkatCmdGeneric.cpp@ 91453

Last change on this file since 91453 was 91453, checked in by vboxsync, 3 years ago

Audio/Validation Kit: Implemented optional test tone options "--tone-freq" and "--tone-dur" to specify a defined frequency and duration. ​bugref:10008

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
  • Property svn:mergeinfo set to (toggle deleted branches)
    /branches/VBox-3.0/src/VBox/ValidationKit/utils/audio/vkat.cpp58652,​70973
    /branches/VBox-3.2/src/VBox/ValidationKit/utils/audio/vkat.cpp66309,​66318
    /branches/VBox-4.0/src/VBox/ValidationKit/utils/audio/vkat.cpp70873
    /branches/VBox-4.1/src/VBox/ValidationKit/utils/audio/vkat.cpp74233,​78414,​78691,​81841,​82127,​85941,​85944-85947,​85949-85950,​85953,​86701,​86728,​87009
    /branches/VBox-4.2/src/VBox/ValidationKit/utils/audio/vkat.cpp86229-86230,​86234,​86529,​91503-91504,​91506-91508,​91510,​91514-91515,​91521,​108112,​108114,​108127
    /branches/VBox-4.3/src/VBox/ValidationKit/utils/audio/vkat.cpp89714,​91223,​93628-93629,​94066,​94839,​94897,​95154,​95164,​95167,​95295,​95338,​95353-95354,​95356,​95367,​95451,​95475,​95477,​95480,​95507,​95640,​95659,​95661,​95663,​98913-98914
    /branches/VBox-4.3/trunk/src/VBox/ValidationKit/utils/audio/vkat.cpp91223
    /branches/VBox-5.0/src/VBox/ValidationKit/utils/audio/vkat.cpp104938,​104943,​104950,​104987-104988,​104990,​106453
    /branches/VBox-5.1/src/VBox/ValidationKit/utils/audio/vkat.cpp112367,​116543,​116550,​116568,​116573
    /branches/VBox-5.2/src/VBox/ValidationKit/utils/audio/vkat.cpp119536,​120083,​120099,​120213,​120221,​120239,​123597-123598,​123600-123601,​123755,​124263,​124273,​124277-124279,​124284-124286,​124288-124290,​125768,​125779-125780,​125812,​127158-127159,​127162-127167,​127180
    /branches/VBox-6.0/src/VBox/ValidationKit/utils/audio/vkat.cpp130474-130475,​130477,​130479,​131352
    /branches/VBox-6.1/src/VBox/ValidationKit/utils/audio/vkat.cpp141521,​141567-141568,​141588-141590,​141592-141595,​141652,​141920
    /branches/aeichner/vbox-chromium-cleanup/src/VBox/ValidationKit/utils/audio/vkat.cpp129818-129851,​129853-129861,​129871-129872,​129876,​129880,​129882,​130013-130015,​130094-130095
    /branches/andy/draganddrop/src/VBox/ValidationKit/utils/audio/vkat.cpp90781-91268
    /branches/andy/guestctrl20/src/VBox/ValidationKit/utils/audio/vkat.cpp78916,​78930
    /branches/andy/pdmaudio/src/VBox/ValidationKit/utils/audio/vkat.cpp94582,​94641,​94654,​94688,​94778,​94783,​94816,​95197,​95215-95216,​95250,​95279,​95505-95506,​95543,​95694,​96323,​96470-96471,​96582,​96587,​96802-96803,​96817,​96904,​96967,​96999,​97020-97021,​97025,​97050,​97099
    /branches/bird/hardenedwindows/src/VBox/ValidationKit/utils/audio/vkat.cpp92692-94610
    /branches/dsen/gui/src/VBox/ValidationKit/utils/audio/vkat.cpp79076-79078,​79089,​79109-79110,​79112-79113,​79127-79130,​79134,​79141,​79151,​79155,​79157-79159,​79193,​79197
    /branches/dsen/gui2/src/VBox/ValidationKit/utils/audio/vkat.cpp79224,​79228,​79233,​79235,​79258,​79262-79263,​79273,​79341,​79345,​79354,​79357,​79387-79388,​79559-79569,​79572-79573,​79578,​79581-79582,​79590-79591,​79598-79599,​79602-79603,​79605-79606,​79632,​79635,​79637,​79644
    /branches/dsen/gui3/src/VBox/ValidationKit/utils/audio/vkat.cpp79645-79692
File size: 42.2 KB
Line 
1/* $Id: vkatCmdGeneric.cpp 91453 2021-09-29 09:43:40Z vboxsync $ */
2/** @file
3 * Validation Kit Audio Test (VKAT) utility for testing and validating the audio stack.
4 */
5
6/*
7 * Copyright (C) 2021 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#include <iprt/errcore.h>
32#include <iprt/message.h>
33#include <iprt/rand.h>
34#include <iprt/test.h>
35
36#include "vkatInternal.h"
37
38
39/*********************************************************************************************************************************
40* Command: enum *
41*********************************************************************************************************************************/
42
43
44
45/**
46 * Long option values for the 'enum' command.
47 */
48enum
49{
50 VKAT_ENUM_OPT_PROBE_BACKENDS = 900
51};
52
53/**
54 * Options for 'enum'.
55 */
56static const RTGETOPTDEF g_aCmdEnumOptions[] =
57{
58 { "--backend", 'b', RTGETOPT_REQ_STRING },
59 { "--probe-backends", VKAT_ENUM_OPT_PROBE_BACKENDS, RTGETOPT_REQ_NOTHING }
60};
61
62
63/** The 'enum' command option help. */
64static DECLCALLBACK(const char *) audioTestCmdEnumHelp(PCRTGETOPTDEF pOpt)
65{
66 switch (pOpt->iShort)
67 {
68 case 'b': return "The audio backend to use.";
69 case VKAT_ENUM_OPT_PROBE_BACKENDS: return "Specifies whether to probe all (available) backends until a working one is found\n"
70 " Default: false";
71 default: return NULL;
72 }
73}
74
75/**
76 * The 'enum' command handler.
77 *
78 * @returns Program exit code.
79 * @param pGetState RTGetOpt state.
80 */
81static DECLCALLBACK(RTEXITCODE) audioTestCmdEnumHandler(PRTGETOPTSTATE pGetState)
82{
83 /*
84 * Parse options.
85 */
86 /* Option values: */
87 PCPDMDRVREG pDrvReg = AudioTestGetDefaultBackend();
88 bool fProbeBackends = false;
89
90 /* Argument processing loop: */
91 int ch;
92 RTGETOPTUNION ValueUnion;
93 while ((ch = RTGetOpt(pGetState, &ValueUnion)) != 0)
94 {
95 switch (ch)
96 {
97 case 'b':
98 pDrvReg = AudioTestFindBackendOpt(ValueUnion.psz);
99 if (pDrvReg == NULL)
100 return RTEXITCODE_SYNTAX;
101 break;
102
103 case VKAT_ENUM_OPT_PROBE_BACKENDS:
104 fProbeBackends = ValueUnion.f;
105 break;
106
107 AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion);
108
109 default:
110 return RTGetOptPrintError(ch, &ValueUnion);
111 }
112 }
113
114 int rc;
115
116 AUDIOTESTDRVSTACK DrvStack;
117 if (fProbeBackends)
118 rc = audioTestDriverStackProbe(&DrvStack, pDrvReg,
119 true /* fEnabledIn */, true /* fEnabledOut */, false /* fWithDrvAudio */);
120 else
121 rc = audioTestDriverStackInitEx(&DrvStack, pDrvReg,
122 true /* fEnabledIn */, true /* fEnabledOut */, false /* fWithDrvAudio */);
123 if (RT_FAILURE(rc))
124 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unable to init driver stack: %Rrc\n", rc);
125
126 /*
127 * Do the enumeration.
128 */
129 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
130
131 if (DrvStack.pIHostAudio->pfnGetDevices)
132 {
133 PDMAUDIOHOSTENUM Enum;
134 rc = DrvStack.pIHostAudio->pfnGetDevices(DrvStack.pIHostAudio, &Enum);
135 if (RT_SUCCESS(rc))
136 {
137 RTPrintf("Found %u device%s\n", Enum.cDevices, Enum.cDevices != 1 ? "s" : "");
138
139 PPDMAUDIOHOSTDEV pHostDev;
140 RTListForEach(&Enum.LstDevices, pHostDev, PDMAUDIOHOSTDEV, ListEntry)
141 {
142 RTPrintf("\nDevice \"%s\":\n", pHostDev->pszName);
143
144 char szFlags[PDMAUDIOHOSTDEV_MAX_FLAGS_STRING_LEN];
145 if (pHostDev->cMaxInputChannels && !pHostDev->cMaxOutputChannels && pHostDev->enmUsage == PDMAUDIODIR_IN)
146 RTPrintf(" Input: max %u channels (%s)\n",
147 pHostDev->cMaxInputChannels, PDMAudioHostDevFlagsToString(szFlags, pHostDev->fFlags));
148 else if (!pHostDev->cMaxInputChannels && pHostDev->cMaxOutputChannels && pHostDev->enmUsage == PDMAUDIODIR_OUT)
149 RTPrintf(" Output: max %u channels (%s)\n",
150 pHostDev->cMaxOutputChannels, PDMAudioHostDevFlagsToString(szFlags, pHostDev->fFlags));
151 else
152 RTPrintf(" %s: max %u output channels, max %u input channels (%s)\n",
153 PDMAudioDirGetName(pHostDev->enmUsage), pHostDev->cMaxOutputChannels,
154 pHostDev->cMaxInputChannels, PDMAudioHostDevFlagsToString(szFlags, pHostDev->fFlags));
155
156 if (pHostDev->pszId && *pHostDev->pszId)
157 RTPrintf(" ID: \"%s\"\n", pHostDev->pszId);
158 }
159
160 PDMAudioHostEnumDelete(&Enum);
161 }
162 else
163 rcExit = RTMsgErrorExitFailure("Enumeration failed: %Rrc\n", rc);
164 }
165 else
166 rcExit = RTMsgErrorExitFailure("Enumeration not supported by backend '%s'\n", pDrvReg->szName);
167 audioTestDriverStackDelete(&DrvStack);
168
169 return RTEXITCODE_SUCCESS;
170}
171
172
173/**
174 * Command table entry for 'enum'.
175 */
176const VKATCMD g_CmdEnum =
177{
178 "enum",
179 audioTestCmdEnumHandler,
180 "Enumerates audio devices.",
181 g_aCmdEnumOptions,
182 RT_ELEMENTS(g_aCmdEnumOptions),
183 audioTestCmdEnumHelp,
184 false /* fNeedsTransport */
185};
186
187
188
189
190/*********************************************************************************************************************************
191* Command: play *
192*********************************************************************************************************************************/
193
194/**
195 * Structure holding additional playback options.
196 */
197typedef struct AUDIOTESTPLAYOPTS
198{
199 /** Whether to use the audio connector or not. */
200 bool fWithDrvAudio;
201 /** Whether to use a mixing buffer or not. */
202 bool fWithMixer;
203 /** Buffer size (in ms). */
204 uint32_t cMsBufferSize;
205 /** Pre-buffering size (in ms). */
206 uint32_t cMsPreBuffer;
207 /** Scheduling (in ms). */
208 uint32_t cMsSchedulingHint;
209 /** Audio vlume to use (in percent). */
210 uint8_t uVolumePercent;
211 /** PCM audio properties to use. */
212 PDMAUDIOPCMPROPS Props;
213} AUDIOTESTPLAYOPTS;
214/** Pointer to additional playback options. */
215typedef AUDIOTESTPLAYOPTS *PAUDIOTESTPLAYOPTS;
216
217/**
218 * Worker for audioTestPlayOne implementing the play loop.
219 */
220static RTEXITCODE audioTestPlayOneInner(PAUDIOTESTDRVMIXSTREAM pMix, PAUDIOTESTWAVEFILE pWaveFile,
221 PCPDMAUDIOSTREAMCFG pCfgAcq, const char *pszFile)
222{
223 uint32_t const cbPreBuffer = PDMAudioPropsFramesToBytes(pMix->pProps, pCfgAcq->Backend.cFramesPreBuffering);
224 uint64_t const nsStarted = RTTimeNanoTS();
225 uint64_t nsDonePreBuffering = 0;
226
227 /*
228 * Transfer data as quickly as we're allowed.
229 */
230 uint8_t abSamples[16384];
231 uint32_t const cbSamplesAligned = PDMAudioPropsFloorBytesToFrame(pMix->pProps, sizeof(abSamples));
232 uint64_t offStream = 0;
233 while (!g_fTerminate)
234 {
235 /* Read a chunk from the wave file. */
236 size_t cbSamples = 0;
237 int rc = AudioTestWaveFileRead(pWaveFile, abSamples, cbSamplesAligned, &cbSamples);
238 if (RT_SUCCESS(rc) && cbSamples > 0)
239 {
240 /* Pace ourselves a little. */
241 if (offStream >= cbPreBuffer)
242 {
243 if (!nsDonePreBuffering)
244 nsDonePreBuffering = RTTimeNanoTS();
245 uint64_t const cNsWritten = PDMAudioPropsBytesToNano64(pMix->pProps, offStream - cbPreBuffer);
246 uint64_t const cNsElapsed = RTTimeNanoTS() - nsStarted;
247 if (cNsWritten > cNsElapsed + RT_NS_10MS)
248 RTThreadSleep((cNsWritten - cNsElapsed - RT_NS_10MS / 2) / RT_NS_1MS);
249 }
250
251 /* Transfer the data to the audio stream. */
252 for (uint32_t offSamples = 0; offSamples < cbSamples;)
253 {
254 uint32_t const cbCanWrite = AudioTestMixStreamGetWritable(pMix);
255 if (cbCanWrite > 0)
256 {
257 uint32_t const cbToPlay = RT_MIN(cbCanWrite, (uint32_t)cbSamples - offSamples);
258 uint32_t cbPlayed = 0;
259 rc = AudioTestMixStreamPlay(pMix, &abSamples[offSamples], cbToPlay, &cbPlayed);
260 if (RT_SUCCESS(rc))
261 {
262 if (cbPlayed)
263 {
264 offSamples += cbPlayed;
265 offStream += cbPlayed;
266 }
267 else
268 return RTMsgErrorExitFailure("Played zero bytes - %#x bytes reported playable!\n", cbCanWrite);
269 }
270 else
271 return RTMsgErrorExitFailure("Failed to play %#x bytes: %Rrc\n", cbToPlay, rc);
272 }
273 else if (AudioTestMixStreamIsOkay(pMix))
274 RTThreadSleep(RT_MIN(RT_MAX(1, pCfgAcq->Device.cMsSchedulingHint), 256));
275 else
276 return RTMsgErrorExitFailure("Stream is not okay!\n");
277 }
278 }
279 else if (RT_SUCCESS(rc) && cbSamples == 0)
280 break;
281 else
282 return RTMsgErrorExitFailure("Error reading wav file '%s': %Rrc", pszFile, rc);
283 }
284
285 /*
286 * Drain the stream.
287 */
288 if (g_uVerbosity > 0)
289 RTMsgInfo("%'RU64 ns: Draining...\n", RTTimeNanoTS() - nsStarted);
290 int rc = AudioTestMixStreamDrain(pMix, true /*fSync*/);
291 if (RT_SUCCESS(rc))
292 {
293 if (g_uVerbosity > 0)
294 RTMsgInfo("%'RU64 ns: Done\n", RTTimeNanoTS() - nsStarted);
295 }
296 else
297 return RTMsgErrorExitFailure("Draining failed: %Rrc", rc);
298
299 return RTEXITCODE_SUCCESS;
300}
301
302/**
303 * Worker for audioTestCmdPlayHandler that plays one file.
304 */
305static RTEXITCODE audioTestPlayOne(const char *pszFile, PCPDMDRVREG pDrvReg, const char *pszDevId,
306 PAUDIOTESTPLAYOPTS pOpts)
307{
308 char szTmp[128];
309
310 /*
311 * First we must open the file and determin the format.
312 */
313 RTERRINFOSTATIC ErrInfo;
314 AUDIOTESTWAVEFILE WaveFile;
315 int rc = AudioTestWaveFileOpen(pszFile, &WaveFile, RTErrInfoInitStatic(&ErrInfo));
316 if (RT_FAILURE(rc))
317 return RTMsgErrorExitFailure("Failed to open '%s': %Rrc%#RTeim", pszFile, rc, &ErrInfo.Core);
318
319 if (g_uVerbosity > 0)
320 {
321 RTMsgInfo("Opened '%s' for playing\n", pszFile);
322 RTMsgInfo("Format: %s\n", PDMAudioPropsToString(&WaveFile.Props, szTmp, sizeof(szTmp)));
323 RTMsgInfo("Size: %'RU32 bytes / %#RX32 / %'RU32 frames / %'RU64 ns\n",
324 WaveFile.cbSamples, WaveFile.cbSamples,
325 PDMAudioPropsBytesToFrames(&WaveFile.Props, WaveFile.cbSamples),
326 PDMAudioPropsBytesToNano(&WaveFile.Props, WaveFile.cbSamples));
327 }
328
329 /*
330 * Construct the driver stack.
331 */
332 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
333 AUDIOTESTDRVSTACK DrvStack;
334 rc = audioTestDriverStackInit(&DrvStack, pDrvReg, pOpts->fWithDrvAudio);
335 if (RT_SUCCESS(rc))
336 {
337 /*
338 * Set the output device if one is specified.
339 */
340 rc = audioTestDriverStackSetDevice(&DrvStack, PDMAUDIODIR_OUT, pszDevId);
341 if (RT_SUCCESS(rc))
342 {
343 /*
344 * Open a stream for the output.
345 */
346 uint8_t const cChannels = PDMAudioPropsChannels(&pOpts->Props);
347
348 PDMAUDIOPCMPROPS ReqProps = WaveFile.Props;
349 if (cChannels != 0 && PDMAudioPropsChannels(&ReqProps) != cChannels)
350 PDMAudioPropsSetChannels(&ReqProps, cChannels);
351
352 uint8_t const cbSample = PDMAudioPropsSampleSize(&pOpts->Props);
353 if (cbSample != 0)
354 PDMAudioPropsSetSampleSize(&ReqProps, cbSample);
355
356 uint32_t const uHz = PDMAudioPropsHz(&pOpts->Props);
357 if (uHz != 0)
358 ReqProps.uHz = uHz;
359
360 PDMAUDIOSTREAMCFG CfgAcq;
361 PPDMAUDIOSTREAM pStream = NULL;
362 rc = audioTestDriverStackStreamCreateOutput(&DrvStack, &ReqProps, pOpts->cMsBufferSize,
363 pOpts->cMsPreBuffer, pOpts->cMsSchedulingHint, &pStream, &CfgAcq);
364 if (RT_SUCCESS(rc))
365 {
366 /*
367 * Automatically enable the mixer if the wave file and the
368 * output parameters doesn't match.
369 */
370 if ( !pOpts->fWithMixer
371 && ( !PDMAudioPropsAreEqual(&WaveFile.Props, &pStream->Cfg.Props)
372 || pOpts->uVolumePercent != 100)
373 )
374 {
375 RTMsgInfo("Enabling the mixer buffer.\n");
376 pOpts->fWithMixer = true;
377 }
378
379 /*
380 * Create a mixer wrapper. This is just a thin wrapper if fWithMixer
381 * is false, otherwise it's doing mixing, resampling and recoding.
382 */
383 AUDIOTESTDRVMIXSTREAM Mix;
384 rc = AudioTestMixStreamInit(&Mix, &DrvStack, pStream, pOpts->fWithMixer ? &WaveFile.Props : NULL, 100 /*ms*/);
385 if (RT_SUCCESS(rc))
386 {
387 if (g_uVerbosity > 0)
388 RTMsgInfo("Stream: %s cbBackend=%#RX32%s\n",
389 PDMAudioPropsToString(&pStream->Cfg.Props, szTmp, sizeof(szTmp)),
390 pStream->cbBackend, pOpts->fWithMixer ? " mixed" : "");
391
392 if (pOpts->fWithMixer)
393 AudioTestMixStreamSetVolume(&Mix, pOpts->uVolumePercent);
394
395 /*
396 * Enable the stream and start playing.
397 */
398 rc = AudioTestMixStreamEnable(&Mix);
399 if (RT_SUCCESS(rc))
400 rcExit = audioTestPlayOneInner(&Mix, &WaveFile, &CfgAcq, pszFile);
401 else
402 rcExit = RTMsgErrorExitFailure("Enabling the output stream failed: %Rrc", rc);
403
404 /*
405 * Clean up.
406 */
407 AudioTestMixStreamTerm(&Mix);
408 }
409 audioTestDriverStackStreamDestroy(&DrvStack, pStream);
410 }
411 else
412 rcExit = RTMsgErrorExitFailure("Creating output stream failed: %Rrc", rc);
413 }
414 else
415 rcExit = RTMsgErrorExitFailure("Failed to set output device to '%s': %Rrc", pszDevId, rc);
416 audioTestDriverStackDelete(&DrvStack);
417 }
418 else
419 rcExit = RTMsgErrorExitFailure("Driver stack construction failed: %Rrc", rc);
420 AudioTestWaveFileClose(&WaveFile);
421 return rcExit;
422}
423
424/**
425 * Worker for audioTestCmdPlayHandler that plays one test tone.
426 */
427static RTEXITCODE audioTestPlayTestToneOne(PAUDIOTESTTONEPARMS pToneParms,
428 PCPDMDRVREG pDrvReg, const char *pszDevId,
429 PAUDIOTESTPLAYOPTS pOpts)
430{
431 char szTmp[128];
432
433 AUDIOTESTSTREAM TstStream;
434 RT_ZERO(TstStream);
435
436 /*
437 * Construct the driver stack.
438 */
439 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
440 AUDIOTESTDRVSTACK DrvStack;
441 int rc = audioTestDriverStackInit(&DrvStack, pDrvReg, pOpts->fWithDrvAudio);
442 if (RT_SUCCESS(rc))
443 {
444 /*
445 * Set the output device if one is specified.
446 */
447 rc = audioTestDriverStackSetDevice(&DrvStack, PDMAUDIODIR_OUT, pszDevId);
448 if (RT_SUCCESS(rc))
449 {
450 /*
451 * Open a stream for the output.
452 */
453 uint8_t const cChannels = PDMAudioPropsChannels(&pOpts->Props);
454
455 PDMAUDIOPCMPROPS ReqProps = pToneParms->Props;
456 if (cChannels != 0 && PDMAudioPropsChannels(&ReqProps) != cChannels)
457 PDMAudioPropsSetChannels(&ReqProps, cChannels);
458
459 uint8_t const cbSample = PDMAudioPropsSampleSize(&pOpts->Props);
460 if (cbSample != 0)
461 PDMAudioPropsSetSampleSize(&ReqProps, cbSample);
462
463 uint32_t const uHz = PDMAudioPropsHz(&pOpts->Props);
464 if (uHz != 0)
465 ReqProps.uHz = uHz;
466
467 rc = audioTestDriverStackStreamCreateOutput(&DrvStack, &ReqProps, pOpts->cMsBufferSize,
468 pOpts->cMsPreBuffer, pOpts->cMsSchedulingHint, &TstStream.pStream, &TstStream.Cfg);
469 if (RT_SUCCESS(rc))
470 {
471 /*
472 * Automatically enable the mixer if the wave file and the
473 * output parameters doesn't match.
474 */
475 if ( !pOpts->fWithMixer
476 && ( !PDMAudioPropsAreEqual(&pToneParms->Props, &TstStream.pStream->Cfg.Props)
477 || pToneParms->uVolumePercent != 100)
478 )
479 {
480 RTMsgInfo("Enabling the mixer buffer.\n");
481 pOpts->fWithMixer = true;
482 }
483
484 /*
485 * Create a mixer wrapper. This is just a thin wrapper if fWithMixer
486 * is false, otherwise it's doing mixing, resampling and recoding.
487 */
488 rc = AudioTestMixStreamInit(&TstStream.Mix, &DrvStack, TstStream.pStream,
489 pOpts->fWithMixer ? &pToneParms->Props : NULL, 100 /*ms*/);
490 if (RT_SUCCESS(rc))
491 {
492 if (g_uVerbosity > 0)
493 RTMsgInfo("Stream: %s cbBackend=%#RX32%s\n",
494 PDMAudioPropsToString(&TstStream.pStream->Cfg.Props, szTmp, sizeof(szTmp)),
495 TstStream.pStream->cbBackend, pOpts->fWithMixer ? " mixed" : "");
496
497 /*
498 * Enable the stream and start playing.
499 */
500 rc = AudioTestMixStreamEnable(&TstStream.Mix);
501 if (RT_SUCCESS(rc))
502 {
503 if (pOpts->fWithMixer)
504 AudioTestMixStreamSetVolume(&TstStream.Mix, pToneParms->uVolumePercent);
505
506 rc = audioTestPlayTone(NULL /* pTstEnv */, &TstStream, pToneParms);
507 if (RT_SUCCESS(rc))
508 rcExit = RTEXITCODE_SUCCESS;
509 }
510 else
511 rcExit = RTMsgErrorExitFailure("Enabling the output stream failed: %Rrc", rc);
512
513 /*
514 * Clean up.
515 */
516 AudioTestMixStreamTerm(&TstStream.Mix);
517 }
518 audioTestDriverStackStreamDestroy(&DrvStack, TstStream.pStream);
519 }
520 else
521 rcExit = RTMsgErrorExitFailure("Creating output stream failed: %Rrc", rc);
522 }
523 else
524 rcExit = RTMsgErrorExitFailure("Failed to set output device to '%s': %Rrc", pszDevId, rc);
525 audioTestDriverStackDelete(&DrvStack);
526 }
527 else
528 rcExit = RTMsgErrorExitFailure("Driver stack construction failed: %Rrc", rc);
529 return rcExit;
530}
531
532
533/**
534 * Long option values for the 'play' command.
535 */
536enum
537{
538 VKAT_PLAY_OPT_TONE_DUR = 900,
539 VKAT_PLAY_OPT_TONE_FREQ,
540 VKAT_PLAY_OPT_VOL
541};
542
543
544/**
545 * Options for 'play'.
546 */
547static const RTGETOPTDEF g_aCmdPlayOptions[] =
548{
549 { "--backend", 'b', RTGETOPT_REQ_STRING },
550 { "--channels", 'c', RTGETOPT_REQ_UINT8 },
551 { "--hz", 'f', RTGETOPT_REQ_UINT32 },
552 { "--frequency", 'f', RTGETOPT_REQ_UINT32 },
553 { "--sample-size", 'z', RTGETOPT_REQ_UINT8 },
554 { "--test-tone", 't', RTGETOPT_REQ_NOTHING },
555 { "--tone-dur", VKAT_PLAY_OPT_TONE_DUR, RTGETOPT_REQ_UINT32 },
556 { "--tone-freq", VKAT_PLAY_OPT_TONE_FREQ, RTGETOPT_REQ_UINT32 },
557 { "--output-device", 'o', RTGETOPT_REQ_STRING },
558 { "--with-drv-audio", 'd', RTGETOPT_REQ_NOTHING },
559 { "--with-mixer", 'm', RTGETOPT_REQ_NOTHING },
560 { "--vol", VKAT_PLAY_OPT_VOL, RTGETOPT_REQ_UINT8 }
561};
562
563
564/** The 'play' command option help. */
565static DECLCALLBACK(const char *) audioTestCmdPlayHelp(PCRTGETOPTDEF pOpt)
566{
567 switch (pOpt->iShort)
568 {
569 case 'b': return "The audio backend to use";
570 case 'c': return "Number of backend output channels";
571 case 'd': return "Go via DrvAudio instead of directly interfacing with the backend";
572 case 'f': return "Output frequency (Hz)";
573 case 'z': return "Output sample size (bits)";
574 case 't': return "Plays a test tone. Can be specified multiple times";
575 case 'm': return "Go via the mixer";
576 case 'o': return "The ID of the output device to use";
577 case VKAT_PLAY_OPT_TONE_DUR: return "Test tone duration (ms)";
578 case VKAT_PLAY_OPT_TONE_FREQ: return "Test tone frequency (Hz)";
579 case VKAT_PLAY_OPT_VOL: return "Volume (in percent, 0-100) to use";
580 default: return NULL;
581 }
582}
583
584
585/**
586 * The 'play' command handler.
587 *
588 * @returns Program exit code.
589 * @param pGetState RTGetOpt state.
590 */
591static DECLCALLBACK(RTEXITCODE) audioTestCmdPlayHandler(PRTGETOPTSTATE pGetState)
592{
593 /* Option values: */
594 PCPDMDRVREG pDrvReg = AudioTestGetDefaultBackend();
595 const char *pszDevId = NULL;
596 uint32_t cTestTones = 0;
597 uint8_t cbSample = 0;
598 uint8_t cChannels = 0;
599 uint32_t uHz = 0;
600
601 AUDIOTESTPLAYOPTS PlayOpts;
602 RT_ZERO(PlayOpts);
603
604 PlayOpts.uVolumePercent = 100; /* Use maximum volume by default. */
605
606 AUDIOTESTTONEPARMS ToneParms;
607 RT_ZERO(ToneParms);
608
609 ToneParms.dbFreqHz = AudioTestToneGetRandomFreq();
610 ToneParms.msDuration = RTRandU32Ex(0, RT_MS_10SEC); /** @todo Probably a bit too long, but let's see. */
611
612 /* Argument processing loop: */
613 int ch;
614 RTGETOPTUNION ValueUnion;
615 while ((ch = RTGetOpt(pGetState, &ValueUnion)) != 0)
616 {
617 switch (ch)
618 {
619 case 'b':
620 pDrvReg = AudioTestFindBackendOpt(ValueUnion.psz);
621 if (pDrvReg == NULL)
622 return RTEXITCODE_SYNTAX;
623 break;
624
625 case 'c':
626 cChannels = ValueUnion.u8;
627 break;
628
629 case 'd':
630 PlayOpts.fWithDrvAudio = true;
631 break;
632
633 case 'f':
634 uHz = ValueUnion.u32;
635 break;
636
637 case 'm':
638 PlayOpts.fWithMixer = true;
639 break;
640
641 case 'o':
642 pszDevId = ValueUnion.psz;
643 break;
644
645 case 't':
646 cTestTones++;
647 break;
648
649 case 'z':
650 cbSample = ValueUnion.u8 / 8;
651 break;
652
653 case VKAT_PLAY_OPT_TONE_DUR:
654 ToneParms.msDuration = ValueUnion.u32;
655 break;
656
657 case VKAT_PLAY_OPT_TONE_FREQ:
658 ToneParms.dbFreqHz = ValueUnion.u32;
659 break;
660
661 case VKAT_PLAY_OPT_VOL:
662 PlayOpts.uVolumePercent = ValueUnion.u8;
663 if (PlayOpts.uVolumePercent > 100)
664 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid volume (0-100)");
665 break;
666
667 case VINF_GETOPT_NOT_OPTION:
668 {
669 if (cTestTones)
670 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Playing test tones (-t) cannot be combined with playing files");
671
672 /* Use some sane defaults if no PCM props are set by the user. */
673 PDMAudioPropsInit(&PlayOpts.Props,
674 cbSample ? cbSample : 2 /* 16-bit */, true /* fSigned */,
675 cChannels ? cChannels : 2 /* Stereo */, uHz ? uHz : 44100);
676
677 RTEXITCODE rcExit = audioTestPlayOne(ValueUnion.psz, pDrvReg, pszDevId, &PlayOpts);
678 if (rcExit != RTEXITCODE_SUCCESS)
679 return rcExit;
680 break;
681 }
682
683 AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion);
684
685 default:
686 return RTGetOptPrintError(ch, &ValueUnion);
687 }
688 }
689
690 while (cTestTones--)
691 {
692 /* Use some sane defaults if no PCM props are set by the user. */
693 PDMAudioPropsInit(&ToneParms.Props,
694 cbSample ? cbSample : 2 /* 16-bit */, true /* fSigned */,
695 cChannels ? cChannels : 2 /* Stereo */, uHz ? uHz : 44100);
696
697 ToneParms.uVolumePercent = PlayOpts.uVolumePercent;
698
699 RTEXITCODE rcExit = audioTestPlayTestToneOne(&ToneParms, pDrvReg, pszDevId, &PlayOpts);
700 if (rcExit != RTEXITCODE_SUCCESS)
701 return rcExit;
702 }
703
704 return RTEXITCODE_SUCCESS;
705}
706
707
708/**
709 * Command table entry for 'play'.
710 */
711const VKATCMD g_CmdPlay =
712{
713 "play",
714 audioTestCmdPlayHandler,
715 "Plays one or more wave files.",
716 g_aCmdPlayOptions,
717 RT_ELEMENTS(g_aCmdPlayOptions),
718 audioTestCmdPlayHelp,
719 false /* fNeedsTransport */
720};
721
722
723/*********************************************************************************************************************************
724* Command: rec *
725*********************************************************************************************************************************/
726
727/**
728 * Worker for audioTestRecOne implementing the recording loop.
729 */
730static RTEXITCODE audioTestRecOneInner(PAUDIOTESTDRVMIXSTREAM pMix, PAUDIOTESTWAVEFILE pWaveFile,
731 PCPDMAUDIOSTREAMCFG pCfgAcq, uint64_t cMaxFrames, const char *pszFile)
732{
733 int rc;
734 uint64_t const nsStarted = RTTimeNanoTS();
735
736 /*
737 * Transfer data as quickly as we're allowed.
738 */
739 uint8_t abSamples[16384];
740 uint32_t const cbSamplesAligned = PDMAudioPropsFloorBytesToFrame(pMix->pProps, sizeof(abSamples));
741 uint64_t cFramesCapturedTotal = 0;
742 while (!g_fTerminate && cFramesCapturedTotal < cMaxFrames)
743 {
744 /*
745 * Anything we can read?
746 */
747 uint32_t const cbCanRead = AudioTestMixStreamGetReadable(pMix);
748 if (cbCanRead)
749 {
750 uint32_t const cbToRead = RT_MIN(cbCanRead, cbSamplesAligned);
751 uint32_t cbCaptured = 0;
752 rc = AudioTestMixStreamCapture(pMix, abSamples, cbToRead, &cbCaptured);
753 if (RT_SUCCESS(rc))
754 {
755 if (cbCaptured)
756 {
757 uint32_t cFramesCaptured = PDMAudioPropsBytesToFrames(pMix->pProps, cbCaptured);
758 if (cFramesCaptured + cFramesCaptured < cMaxFrames)
759 { /* likely */ }
760 else
761 {
762 cFramesCaptured = cMaxFrames - cFramesCaptured;
763 cbCaptured = PDMAudioPropsFramesToBytes(pMix->pProps, cFramesCaptured);
764 }
765
766 rc = AudioTestWaveFileWrite(pWaveFile, abSamples, cbCaptured);
767 if (RT_SUCCESS(rc))
768 cFramesCapturedTotal += cFramesCaptured;
769 else
770 return RTMsgErrorExitFailure("Error writing to '%s': %Rrc", pszFile, rc);
771 }
772 else
773 return RTMsgErrorExitFailure("Captured zero bytes - %#x bytes reported readable!\n", cbCanRead);
774 }
775 else
776 return RTMsgErrorExitFailure("Failed to capture %#x bytes: %Rrc (%#x available)\n", cbToRead, rc, cbCanRead);
777 }
778 else if (AudioTestMixStreamIsOkay(pMix))
779 RTThreadSleep(RT_MIN(RT_MAX(1, pCfgAcq->Device.cMsSchedulingHint), 256));
780 else
781 return RTMsgErrorExitFailure("Stream is not okay!\n");
782 }
783
784 /*
785 * Disable the stream.
786 */
787 rc = AudioTestMixStreamDisable(pMix);
788 if (RT_SUCCESS(rc) && g_uVerbosity > 0)
789 RTMsgInfo("%'RU64 ns: Stopped after recording %RU64 frames%s\n", RTTimeNanoTS() - nsStarted, cFramesCapturedTotal,
790 g_fTerminate ? " - Ctrl-C" : ".");
791 else if (RT_FAILURE(rc))
792 return RTMsgErrorExitFailure("Disabling stream failed: %Rrc", rc);
793
794 return RTEXITCODE_SUCCESS;
795}
796
797
798/**
799 * Worker for audioTestCmdRecHandler that recs one file.
800 */
801static RTEXITCODE audioTestRecOne(const char *pszFile, uint8_t cWaveChannels, uint8_t cbWaveSample, uint32_t uWaveHz,
802 PCPDMDRVREG pDrvReg, const char *pszDevId, uint32_t cMsBufferSize,
803 uint32_t cMsPreBuffer, uint32_t cMsSchedulingHint,
804 uint8_t cChannels, uint8_t cbSample, uint32_t uHz, bool fWithDrvAudio, bool fWithMixer,
805 uint64_t cMaxFrames, uint64_t cNsMaxDuration)
806{
807 /*
808 * Construct the driver stack.
809 */
810 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
811 AUDIOTESTDRVSTACK DrvStack;
812 int rc = audioTestDriverStackInit(&DrvStack, pDrvReg, fWithDrvAudio);
813 if (RT_SUCCESS(rc))
814 {
815 /*
816 * Set the input device if one is specified.
817 */
818 rc = audioTestDriverStackSetDevice(&DrvStack, PDMAUDIODIR_IN, pszDevId);
819 if (RT_SUCCESS(rc))
820 {
821 /*
822 * Create an input stream.
823 */
824 PDMAUDIOPCMPROPS ReqProps;
825 PDMAudioPropsInit(&ReqProps,
826 cbSample ? cbSample : cbWaveSample ? cbWaveSample : 2,
827 true /*fSigned*/,
828 cChannels ? cChannels : cWaveChannels ? cWaveChannels : 2,
829 uHz ? uHz : uWaveHz ? uWaveHz : 44100);
830 PDMAUDIOSTREAMCFG CfgAcq;
831 PPDMAUDIOSTREAM pStream = NULL;
832 rc = audioTestDriverStackStreamCreateInput(&DrvStack, &ReqProps, cMsBufferSize,
833 cMsPreBuffer, cMsSchedulingHint, &pStream, &CfgAcq);
834 if (RT_SUCCESS(rc))
835 {
836 /*
837 * Determine the wave file properties. If it differs from the stream
838 * properties, make sure the mixer is enabled.
839 */
840 PDMAUDIOPCMPROPS WaveProps;
841 PDMAudioPropsInit(&WaveProps,
842 cbWaveSample ? cbWaveSample : PDMAudioPropsSampleSize(&CfgAcq.Props),
843 true /*fSigned*/,
844 cWaveChannels ? cWaveChannels : PDMAudioPropsChannels(&CfgAcq.Props),
845 uWaveHz ? uWaveHz : PDMAudioPropsHz(&CfgAcq.Props));
846 if (!fWithMixer && !PDMAudioPropsAreEqual(&WaveProps, &CfgAcq.Props))
847 {
848 RTMsgInfo("Enabling the mixer buffer.\n");
849 fWithMixer = true;
850 }
851
852 /* Console the max duration into frames now that we've got the wave file format. */
853 if (cMaxFrames != UINT64_MAX && cNsMaxDuration != UINT64_MAX)
854 {
855 uint64_t cMaxFrames2 = PDMAudioPropsNanoToBytes64(&WaveProps, cNsMaxDuration);
856 cMaxFrames = RT_MAX(cMaxFrames, cMaxFrames2);
857 }
858 else if (cNsMaxDuration != UINT64_MAX)
859 cMaxFrames = PDMAudioPropsNanoToBytes64(&WaveProps, cNsMaxDuration);
860
861 /*
862 * Create a mixer wrapper. This is just a thin wrapper if fWithMixer
863 * is false, otherwise it's doing mixing, resampling and recoding.
864 */
865 AUDIOTESTDRVMIXSTREAM Mix;
866 rc = AudioTestMixStreamInit(&Mix, &DrvStack, pStream, fWithMixer ? &WaveProps : NULL, 100 /*ms*/);
867 if (RT_SUCCESS(rc))
868 {
869 char szTmp[128];
870 if (g_uVerbosity > 0)
871 RTMsgInfo("Stream: %s cbBackend=%#RX32%s\n",
872 PDMAudioPropsToString(&pStream->Cfg.Props, szTmp, sizeof(szTmp)),
873 pStream->cbBackend, fWithMixer ? " mixed" : "");
874
875 /*
876 * Open the wave output file.
877 */
878 AUDIOTESTWAVEFILE WaveFile;
879 RTERRINFOSTATIC ErrInfo;
880 rc = AudioTestWaveFileCreate(pszFile, &WaveProps, &WaveFile, RTErrInfoInitStatic(&ErrInfo));
881 if (RT_SUCCESS(rc))
882 {
883 if (g_uVerbosity > 0)
884 {
885 RTMsgInfo("Opened '%s' for playing\n", pszFile);
886 RTMsgInfo("Format: %s\n", PDMAudioPropsToString(&WaveFile.Props, szTmp, sizeof(szTmp)));
887 }
888
889 /*
890 * Enable the stream and start recording.
891 */
892 rc = AudioTestMixStreamEnable(&Mix);
893 if (RT_SUCCESS(rc))
894 rcExit = audioTestRecOneInner(&Mix, &WaveFile, &CfgAcq, cMaxFrames, pszFile);
895 else
896 rcExit = RTMsgErrorExitFailure("Enabling the input stream failed: %Rrc", rc);
897 if (rcExit != RTEXITCODE_SUCCESS)
898 AudioTestMixStreamDisable(&Mix);
899
900 /*
901 * Clean up.
902 */
903 rc = AudioTestWaveFileClose(&WaveFile);
904 if (RT_FAILURE(rc))
905 rcExit = RTMsgErrorExitFailure("Error closing '%s': %Rrc", pszFile, rc);
906 }
907 else
908 rcExit = RTMsgErrorExitFailure("Failed to open '%s': %Rrc%#RTeim", pszFile, rc, &ErrInfo.Core.pszMsg);
909
910 AudioTestMixStreamTerm(&Mix);
911 }
912 audioTestDriverStackStreamDestroy(&DrvStack, pStream);
913 }
914 else
915 rcExit = RTMsgErrorExitFailure("Creating output stream failed: %Rrc", rc);
916 }
917 else
918 rcExit = RTMsgErrorExitFailure("Failed to set output device to '%s': %Rrc", pszDevId, rc);
919 audioTestDriverStackDelete(&DrvStack);
920 }
921 else
922 rcExit = RTMsgErrorExitFailure("Driver stack construction failed: %Rrc", rc);
923 return rcExit;
924}
925
926
927/**
928 * Options for 'rec'.
929 */
930static const RTGETOPTDEF g_aCmdRecOptions[] =
931{
932 { "--backend", 'b', RTGETOPT_REQ_STRING },
933 { "--channels", 'c', RTGETOPT_REQ_UINT8 },
934 { "--hz", 'f', RTGETOPT_REQ_UINT32 },
935 { "--frequency", 'f', RTGETOPT_REQ_UINT32 },
936 { "--sample-size", 'z', RTGETOPT_REQ_UINT8 },
937 { "--input-device", 'i', RTGETOPT_REQ_STRING },
938 { "--wav-channels", 'C', RTGETOPT_REQ_UINT8 },
939 { "--wav-hz", 'F', RTGETOPT_REQ_UINT32 },
940 { "--wav-frequency", 'F', RTGETOPT_REQ_UINT32 },
941 { "--wav-sample-size", 'Z', RTGETOPT_REQ_UINT8 },
942 { "--with-drv-audio", 'd', RTGETOPT_REQ_NOTHING },
943 { "--with-mixer", 'm', RTGETOPT_REQ_NOTHING },
944 { "--max-frames", 'r', RTGETOPT_REQ_UINT64 },
945 { "--max-sec", 's', RTGETOPT_REQ_UINT64 },
946 { "--max-seconds", 's', RTGETOPT_REQ_UINT64 },
947 { "--max-ms", 't', RTGETOPT_REQ_UINT64 },
948 { "--max-milliseconds", 't', RTGETOPT_REQ_UINT64 },
949 { "--max-ns", 'T', RTGETOPT_REQ_UINT64 },
950 { "--max-nanoseconds", 'T', RTGETOPT_REQ_UINT64 },
951};
952
953
954/** The 'rec' command option help. */
955static DECLCALLBACK(const char *) audioTestCmdRecHelp(PCRTGETOPTDEF pOpt)
956{
957 switch (pOpt->iShort)
958 {
959 case 'b': return "The audio backend to use.";
960 case 'c': return "Number of backend input channels";
961 case 'C': return "Number of wave-file channels";
962 case 'd': return "Go via DrvAudio instead of directly interfacing with the backend.";
963 case 'f': return "Input frequency (Hz)";
964 case 'F': return "Wave-file frequency (Hz)";
965 case 'z': return "Input sample size (bits)";
966 case 'Z': return "Wave-file sample size (bits)";
967 case 'm': return "Go via the mixer.";
968 case 'i': return "The ID of the input device to use.";
969 case 'r': return "Max recording duration in frames.";
970 case 's': return "Max recording duration in seconds.";
971 case 't': return "Max recording duration in milliseconds.";
972 case 'T': return "Max recording duration in nanoseconds.";
973 default: return NULL;
974 }
975}
976
977
978/**
979 * The 'rec' command handler.
980 *
981 * @returns Program exit code.
982 * @param pGetState RTGetOpt state.
983 */
984static DECLCALLBACK(RTEXITCODE) audioTestCmdRecHandler(PRTGETOPTSTATE pGetState)
985{
986 /* Option values: */
987 PCPDMDRVREG pDrvReg = AudioTestGetDefaultBackend();
988 uint32_t cMsBufferSize = UINT32_MAX;
989 uint32_t cMsPreBuffer = UINT32_MAX;
990 uint32_t cMsSchedulingHint = UINT32_MAX;
991 const char *pszDevId = NULL;
992 bool fWithDrvAudio = false;
993 bool fWithMixer = false;
994 uint8_t cbSample = 0;
995 uint8_t cChannels = 0;
996 uint32_t uHz = 0;
997 uint8_t cbWaveSample = 0;
998 uint8_t cWaveChannels = 0;
999 uint32_t uWaveHz = 0;
1000 uint64_t cMaxFrames = UINT64_MAX;
1001 uint64_t cNsMaxDuration = UINT64_MAX;
1002
1003 /* Argument processing loop: */
1004 int ch;
1005 RTGETOPTUNION ValueUnion;
1006 while ((ch = RTGetOpt(pGetState, &ValueUnion)) != 0)
1007 {
1008 switch (ch)
1009 {
1010 case 'b':
1011 pDrvReg = AudioTestFindBackendOpt(ValueUnion.psz);
1012 if (pDrvReg == NULL)
1013 return RTEXITCODE_SYNTAX;
1014 break;
1015
1016 case 'c':
1017 cChannels = ValueUnion.u8;
1018 break;
1019
1020 case 'C':
1021 cWaveChannels = ValueUnion.u8;
1022 break;
1023
1024 case 'd':
1025 fWithDrvAudio = true;
1026 break;
1027
1028 case 'f':
1029 uHz = ValueUnion.u32;
1030 break;
1031
1032 case 'F':
1033 uWaveHz = ValueUnion.u32;
1034 break;
1035
1036 case 'i':
1037 pszDevId = ValueUnion.psz;
1038 break;
1039
1040 case 'm':
1041 fWithMixer = true;
1042 break;
1043
1044 case 'r':
1045 cMaxFrames = ValueUnion.u64;
1046 break;
1047
1048 case 's':
1049 cNsMaxDuration = ValueUnion.u64 >= UINT64_MAX / RT_NS_1SEC ? UINT64_MAX : ValueUnion.u64 * RT_NS_1SEC;
1050 break;
1051
1052 case 't':
1053 cNsMaxDuration = ValueUnion.u64 >= UINT64_MAX / RT_NS_1MS ? UINT64_MAX : ValueUnion.u64 * RT_NS_1MS;
1054 break;
1055
1056 case 'T':
1057 cNsMaxDuration = ValueUnion.u64;
1058 break;
1059
1060 case 'z':
1061 cbSample = ValueUnion.u8 / 8;
1062 break;
1063
1064 case 'Z':
1065 cbWaveSample = ValueUnion.u8 / 8;
1066 break;
1067
1068 case VINF_GETOPT_NOT_OPTION:
1069 {
1070 RTEXITCODE rcExit = audioTestRecOne(ValueUnion.psz, cWaveChannels, cbWaveSample, uWaveHz,
1071 pDrvReg, pszDevId, cMsBufferSize, cMsPreBuffer, cMsSchedulingHint,
1072 cChannels, cbSample, uHz, fWithDrvAudio, fWithMixer,
1073 cMaxFrames, cNsMaxDuration);
1074 if (rcExit != RTEXITCODE_SUCCESS)
1075 return rcExit;
1076 break;
1077 }
1078
1079 AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion);
1080
1081 default:
1082 return RTGetOptPrintError(ch, &ValueUnion);
1083 }
1084 }
1085 return RTEXITCODE_SUCCESS;
1086}
1087
1088
1089/**
1090 * Command table entry for 'rec'.
1091 */
1092const VKATCMD g_CmdRec =
1093{
1094 "rec",
1095 audioTestCmdRecHandler,
1096 "Records audio to a wave file.",
1097 g_aCmdRecOptions,
1098 RT_ELEMENTS(g_aCmdRecOptions),
1099 audioTestCmdRecHelp,
1100 false /* fNeedsTransport */
1101};
1102
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