VirtualBox

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

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

Audio/Validation Kit: Factored out general I/O options into AUDIOTESTIOOPTS to also support setting the master volume besides the test tone volume, as function parameter lists already were too big. Also uniformed the command line volume arguments a little ("--vol vs. --volume" everywhere). ​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: 41.8 KB
Line 
1/* $Id: vkatCmdGeneric.cpp 91632 2021-10-08 06:59:29Z 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 "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 * Worker for audioTestPlayOne implementing the play loop.
196 */
197static RTEXITCODE audioTestPlayOneInner(PAUDIOTESTDRVMIXSTREAM pMix, PAUDIOTESTWAVEFILE pWaveFile,
198 PCPDMAUDIOSTREAMCFG pCfgAcq, const char *pszFile)
199{
200 uint32_t const cbPreBuffer = PDMAudioPropsFramesToBytes(pMix->pProps, pCfgAcq->Backend.cFramesPreBuffering);
201 uint64_t const nsStarted = RTTimeNanoTS();
202 uint64_t nsDonePreBuffering = 0;
203
204 /*
205 * Transfer data as quickly as we're allowed.
206 */
207 uint8_t abSamples[16384];
208 uint32_t const cbSamplesAligned = PDMAudioPropsFloorBytesToFrame(pMix->pProps, sizeof(abSamples));
209 uint64_t offStream = 0;
210 while (!g_fTerminate)
211 {
212 /* Read a chunk from the wave file. */
213 size_t cbSamples = 0;
214 int rc = AudioTestWaveFileRead(pWaveFile, abSamples, cbSamplesAligned, &cbSamples);
215 if (RT_SUCCESS(rc) && cbSamples > 0)
216 {
217 /* Pace ourselves a little. */
218 if (offStream >= cbPreBuffer)
219 {
220 if (!nsDonePreBuffering)
221 nsDonePreBuffering = RTTimeNanoTS();
222 uint64_t const cNsWritten = PDMAudioPropsBytesToNano64(pMix->pProps, offStream - cbPreBuffer);
223 uint64_t const cNsElapsed = RTTimeNanoTS() - nsStarted;
224 if (cNsWritten > cNsElapsed + RT_NS_10MS)
225 RTThreadSleep((cNsWritten - cNsElapsed - RT_NS_10MS / 2) / RT_NS_1MS);
226 }
227
228 /* Transfer the data to the audio stream. */
229 for (uint32_t offSamples = 0; offSamples < cbSamples;)
230 {
231 uint32_t const cbCanWrite = AudioTestMixStreamGetWritable(pMix);
232 if (cbCanWrite > 0)
233 {
234 uint32_t const cbToPlay = RT_MIN(cbCanWrite, (uint32_t)cbSamples - offSamples);
235 uint32_t cbPlayed = 0;
236 rc = AudioTestMixStreamPlay(pMix, &abSamples[offSamples], cbToPlay, &cbPlayed);
237 if (RT_SUCCESS(rc))
238 {
239 if (cbPlayed)
240 {
241 offSamples += cbPlayed;
242 offStream += cbPlayed;
243 }
244 else
245 return RTMsgErrorExitFailure("Played zero bytes - %#x bytes reported playable!\n", cbCanWrite);
246 }
247 else
248 return RTMsgErrorExitFailure("Failed to play %#x bytes: %Rrc\n", cbToPlay, rc);
249 }
250 else if (AudioTestMixStreamIsOkay(pMix))
251 RTThreadSleep(RT_MIN(RT_MAX(1, pCfgAcq->Device.cMsSchedulingHint), 256));
252 else
253 return RTMsgErrorExitFailure("Stream is not okay!\n");
254 }
255 }
256 else if (RT_SUCCESS(rc) && cbSamples == 0)
257 break;
258 else
259 return RTMsgErrorExitFailure("Error reading wav file '%s': %Rrc", pszFile, rc);
260 }
261
262 /*
263 * Drain the stream.
264 */
265 if (g_uVerbosity > 0)
266 RTMsgInfo("%'RU64 ns: Draining...\n", RTTimeNanoTS() - nsStarted);
267 int rc = AudioTestMixStreamDrain(pMix, true /*fSync*/);
268 if (RT_SUCCESS(rc))
269 {
270 if (g_uVerbosity > 0)
271 RTMsgInfo("%'RU64 ns: Done\n", RTTimeNanoTS() - nsStarted);
272 }
273 else
274 return RTMsgErrorExitFailure("Draining failed: %Rrc", rc);
275
276 return RTEXITCODE_SUCCESS;
277}
278
279/**
280 * Worker for audioTestCmdPlayHandler that plays one file.
281 */
282static RTEXITCODE audioTestPlayOne(const char *pszFile, PCPDMDRVREG pDrvReg, const char *pszDevId,
283 PAUDIOTESTIOOPTS pIoOpts)
284{
285 char szTmp[128];
286
287 /*
288 * First we must open the file and determin the format.
289 */
290 RTERRINFOSTATIC ErrInfo;
291 AUDIOTESTWAVEFILE WaveFile;
292 int rc = AudioTestWaveFileOpen(pszFile, &WaveFile, RTErrInfoInitStatic(&ErrInfo));
293 if (RT_FAILURE(rc))
294 return RTMsgErrorExitFailure("Failed to open '%s': %Rrc%#RTeim", pszFile, rc, &ErrInfo.Core);
295
296 if (g_uVerbosity > 0)
297 {
298 RTMsgInfo("Opened '%s' for playing\n", pszFile);
299 RTMsgInfo("Format: %s\n", PDMAudioPropsToString(&WaveFile.Props, szTmp, sizeof(szTmp)));
300 RTMsgInfo("Size: %'RU32 bytes / %#RX32 / %'RU32 frames / %'RU64 ns\n",
301 WaveFile.cbSamples, WaveFile.cbSamples,
302 PDMAudioPropsBytesToFrames(&WaveFile.Props, WaveFile.cbSamples),
303 PDMAudioPropsBytesToNano(&WaveFile.Props, WaveFile.cbSamples));
304 }
305
306 /*
307 * Construct the driver stack.
308 */
309 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
310 AUDIOTESTDRVSTACK DrvStack;
311 rc = audioTestDriverStackInit(&DrvStack, pDrvReg, pIoOpts->fWithDrvAudio);
312 if (RT_SUCCESS(rc))
313 {
314 /*
315 * Set the output device if one is specified.
316 */
317 rc = audioTestDriverStackSetDevice(&DrvStack, PDMAUDIODIR_OUT, pszDevId);
318 if (RT_SUCCESS(rc))
319 {
320 /*
321 * Open a stream for the output.
322 */
323 uint8_t const cChannels = PDMAudioPropsChannels(&pIoOpts->Props);
324
325 PDMAUDIOPCMPROPS ReqProps = WaveFile.Props;
326 if (cChannels != 0 && PDMAudioPropsChannels(&ReqProps) != cChannels)
327 PDMAudioPropsSetChannels(&ReqProps, cChannels);
328
329 uint8_t const cbSample = PDMAudioPropsSampleSize(&pIoOpts->Props);
330 if (cbSample != 0)
331 PDMAudioPropsSetSampleSize(&ReqProps, cbSample);
332
333 uint32_t const uHz = PDMAudioPropsHz(&pIoOpts->Props);
334 if (uHz != 0)
335 ReqProps.uHz = uHz;
336
337 PDMAUDIOSTREAMCFG CfgAcq;
338 PPDMAUDIOSTREAM pStream = NULL;
339 rc = audioTestDriverStackStreamCreateOutput(&DrvStack, &ReqProps, pIoOpts->cMsBufferSize,
340 pIoOpts->cMsPreBuffer, pIoOpts->cMsSchedulingHint, &pStream, &CfgAcq);
341 if (RT_SUCCESS(rc))
342 {
343 /*
344 * Automatically enable the mixer if the wave file and the
345 * output parameters doesn't match.
346 */
347 if ( !pIoOpts->fWithMixer
348 && ( !PDMAudioPropsAreEqual(&WaveFile.Props, &pStream->Cfg.Props)
349 || pIoOpts->uVolumePercent != 100)
350 )
351 {
352 RTMsgInfo("Enabling the mixer buffer.\n");
353 pIoOpts->fWithMixer = true;
354 }
355
356 /*
357 * Create a mixer wrapper. This is just a thin wrapper if fWithMixer
358 * is false, otherwise it's doing mixing, resampling and recoding.
359 */
360 AUDIOTESTDRVMIXSTREAM Mix;
361 rc = AudioTestMixStreamInit(&Mix, &DrvStack, pStream, pIoOpts->fWithMixer ? &WaveFile.Props : NULL, 100 /*ms*/);
362 if (RT_SUCCESS(rc))
363 {
364 if (g_uVerbosity > 0)
365 RTMsgInfo("Stream: %s cbBackend=%#RX32%s\n",
366 PDMAudioPropsToString(&pStream->Cfg.Props, szTmp, sizeof(szTmp)),
367 pStream->cbBackend, pIoOpts->fWithMixer ? " mixed" : "");
368
369 if (pIoOpts->fWithMixer)
370 AudioTestMixStreamSetVolume(&Mix, pIoOpts->uVolumePercent);
371
372 /*
373 * Enable the stream and start playing.
374 */
375 rc = AudioTestMixStreamEnable(&Mix);
376 if (RT_SUCCESS(rc))
377 rcExit = audioTestPlayOneInner(&Mix, &WaveFile, &CfgAcq, pszFile);
378 else
379 rcExit = RTMsgErrorExitFailure("Enabling the output stream failed: %Rrc", rc);
380
381 /*
382 * Clean up.
383 */
384 AudioTestMixStreamTerm(&Mix);
385 }
386 audioTestDriverStackStreamDestroy(&DrvStack, pStream);
387 }
388 else
389 rcExit = RTMsgErrorExitFailure("Creating output stream failed: %Rrc", rc);
390 }
391 else
392 rcExit = RTMsgErrorExitFailure("Failed to set output device to '%s': %Rrc", pszDevId, rc);
393 audioTestDriverStackDelete(&DrvStack);
394 }
395 else
396 rcExit = RTMsgErrorExitFailure("Driver stack construction failed: %Rrc", rc);
397 AudioTestWaveFileClose(&WaveFile);
398 return rcExit;
399}
400
401/**
402 * Worker for audioTestCmdPlayHandler that plays one test tone.
403 */
404static RTEXITCODE audioTestPlayTestToneOne(PAUDIOTESTTONEPARMS pToneParms,
405 PCPDMDRVREG pDrvReg, const char *pszDevId,
406 PAUDIOTESTIOOPTS pIoOpts)
407{
408 char szTmp[128];
409
410 AUDIOTESTSTREAM TstStream;
411 RT_ZERO(TstStream);
412
413 /*
414 * Construct the driver stack.
415 */
416 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
417 AUDIOTESTDRVSTACK DrvStack;
418 int rc = audioTestDriverStackInit(&DrvStack, pDrvReg, pIoOpts->fWithDrvAudio);
419 if (RT_SUCCESS(rc))
420 {
421 /*
422 * Set the output device if one is specified.
423 */
424 rc = audioTestDriverStackSetDevice(&DrvStack, PDMAUDIODIR_OUT, pszDevId);
425 if (RT_SUCCESS(rc))
426 {
427 /*
428 * Open a stream for the output.
429 */
430 uint8_t const cChannels = PDMAudioPropsChannels(&pIoOpts->Props);
431
432 PDMAUDIOPCMPROPS ReqProps = pToneParms->Props;
433 if (cChannels != 0 && PDMAudioPropsChannels(&ReqProps) != cChannels)
434 PDMAudioPropsSetChannels(&ReqProps, cChannels);
435
436 uint8_t const cbSample = PDMAudioPropsSampleSize(&pIoOpts->Props);
437 if (cbSample != 0)
438 PDMAudioPropsSetSampleSize(&ReqProps, cbSample);
439
440 uint32_t const uHz = PDMAudioPropsHz(&pIoOpts->Props);
441 if (uHz != 0)
442 ReqProps.uHz = uHz;
443
444 rc = audioTestDriverStackStreamCreateOutput(&DrvStack, &ReqProps, pIoOpts->cMsBufferSize,
445 pIoOpts->cMsPreBuffer, pIoOpts->cMsSchedulingHint, &TstStream.pStream, &TstStream.Cfg);
446 if (RT_SUCCESS(rc))
447 {
448 /*
449 * Automatically enable the mixer if the wave file and the
450 * output parameters doesn't match.
451 */
452 if ( !pIoOpts->fWithMixer
453 && ( !PDMAudioPropsAreEqual(&pToneParms->Props, &TstStream.pStream->Cfg.Props)
454 || pToneParms->uVolumePercent != 100)
455 )
456 {
457 RTMsgInfo("Enabling the mixer buffer.\n");
458 pIoOpts->fWithMixer = true;
459 }
460
461 /*
462 * Create a mixer wrapper. This is just a thin wrapper if fWithMixer
463 * is false, otherwise it's doing mixing, resampling and recoding.
464 */
465 rc = AudioTestMixStreamInit(&TstStream.Mix, &DrvStack, TstStream.pStream,
466 pIoOpts->fWithMixer ? &pToneParms->Props : NULL, 100 /*ms*/);
467 if (RT_SUCCESS(rc))
468 {
469 if (g_uVerbosity > 0)
470 RTMsgInfo("Stream: %s cbBackend=%#RX32%s\n",
471 PDMAudioPropsToString(&TstStream.pStream->Cfg.Props, szTmp, sizeof(szTmp)),
472 TstStream.pStream->cbBackend, pIoOpts->fWithMixer ? " mixed" : "");
473
474 /*
475 * Enable the stream and start playing.
476 */
477 rc = AudioTestMixStreamEnable(&TstStream.Mix);
478 if (RT_SUCCESS(rc))
479 {
480 if (pIoOpts->fWithMixer)
481 AudioTestMixStreamSetVolume(&TstStream.Mix, pToneParms->uVolumePercent);
482
483 rc = audioTestPlayTone(pIoOpts, NULL /* pTstEnv */, &TstStream, pToneParms);
484 if (RT_SUCCESS(rc))
485 rcExit = RTEXITCODE_SUCCESS;
486 }
487 else
488 rcExit = RTMsgErrorExitFailure("Enabling the output stream failed: %Rrc", rc);
489
490 /*
491 * Clean up.
492 */
493 AudioTestMixStreamTerm(&TstStream.Mix);
494 }
495 audioTestDriverStackStreamDestroy(&DrvStack, TstStream.pStream);
496 }
497 else
498 rcExit = RTMsgErrorExitFailure("Creating output stream failed: %Rrc", rc);
499 }
500 else
501 rcExit = RTMsgErrorExitFailure("Failed to set output device to '%s': %Rrc", pszDevId, rc);
502 audioTestDriverStackDelete(&DrvStack);
503 }
504 else
505 rcExit = RTMsgErrorExitFailure("Driver stack construction failed: %Rrc", rc);
506 return rcExit;
507}
508
509
510/**
511 * Long option values for the 'play' command.
512 */
513enum
514{
515 VKAT_PLAY_OPT_TONE_DUR = 900,
516 VKAT_PLAY_OPT_TONE_FREQ,
517 VKAT_PLAY_OPT_TONE_VOL,
518 VKAT_PLAY_OPT_VOL
519};
520
521
522/**
523 * Options for 'play'.
524 */
525static const RTGETOPTDEF g_aCmdPlayOptions[] =
526{
527 { "--backend", 'b', RTGETOPT_REQ_STRING },
528 { "--channels", 'c', RTGETOPT_REQ_UINT8 },
529 { "--hz", 'f', RTGETOPT_REQ_UINT32 },
530 { "--frequency", 'f', RTGETOPT_REQ_UINT32 },
531 { "--sample-size", 'z', RTGETOPT_REQ_UINT8 },
532 { "--test-tone", 't', RTGETOPT_REQ_NOTHING },
533 { "--tone-dur", VKAT_PLAY_OPT_TONE_DUR, RTGETOPT_REQ_UINT32 },
534 { "--tone-freq", VKAT_PLAY_OPT_TONE_FREQ, RTGETOPT_REQ_UINT32 },
535 { "--tone-vol", VKAT_PLAY_OPT_TONE_VOL, RTGETOPT_REQ_UINT32 },
536 { "--output-device", 'o', RTGETOPT_REQ_STRING },
537 { "--with-drv-audio", 'd', RTGETOPT_REQ_NOTHING },
538 { "--with-mixer", 'm', RTGETOPT_REQ_NOTHING },
539 { "--vol", VKAT_PLAY_OPT_VOL, RTGETOPT_REQ_UINT8 }
540};
541
542
543/** The 'play' command option help. */
544static DECLCALLBACK(const char *) audioTestCmdPlayHelp(PCRTGETOPTDEF pOpt)
545{
546 switch (pOpt->iShort)
547 {
548 case 'b': return "The audio backend to use";
549 case 'c': return "Number of backend output channels";
550 case 'd': return "Go via DrvAudio instead of directly interfacing with the backend";
551 case 'f': return "Output frequency (Hz)";
552 case 'z': return "Output sample size (bits)";
553 case 't': return "Plays a test tone. Can be specified multiple times";
554 case 'm': return "Go via the mixer";
555 case 'o': return "The ID of the output device to use";
556 case VKAT_PLAY_OPT_TONE_DUR: return "Test tone duration (ms)";
557 case VKAT_PLAY_OPT_TONE_FREQ: return "Test tone frequency (Hz)";
558 case VKAT_PLAY_OPT_TONE_VOL: return "Test tone volume (percent)";
559 case VKAT_PLAY_OPT_VOL: return "Playback volume (percent)";
560 default: return NULL;
561 }
562}
563
564
565/**
566 * The 'play' command handler.
567 *
568 * @returns Program exit code.
569 * @param pGetState RTGetOpt state.
570 */
571static DECLCALLBACK(RTEXITCODE) audioTestCmdPlayHandler(PRTGETOPTSTATE pGetState)
572{
573 /* Option values: */
574 PCPDMDRVREG pDrvReg = AudioTestGetDefaultBackend();
575 const char *pszDevId = NULL;
576 uint32_t cTestTones = 0;
577 uint8_t cbSample = 0;
578 uint8_t cChannels = 0;
579 uint32_t uHz = 0;
580
581 AUDIOTESTIOOPTS IoOpts;
582 audioTestIoOptsInitDefaults(&IoOpts);
583
584 AUDIOTESTTONEPARMS ToneParms;
585 audioTestToneParmsInit(&ToneParms);
586
587 /* Argument processing loop: */
588 int ch;
589 RTGETOPTUNION ValueUnion;
590 while ((ch = RTGetOpt(pGetState, &ValueUnion)) != 0)
591 {
592 switch (ch)
593 {
594 case 'b':
595 pDrvReg = AudioTestFindBackendOpt(ValueUnion.psz);
596 if (pDrvReg == NULL)
597 return RTEXITCODE_SYNTAX;
598 break;
599
600 case 'c':
601 cChannels = ValueUnion.u8;
602 break;
603
604 case 'd':
605 IoOpts.fWithDrvAudio = true;
606 break;
607
608 case 'f':
609 uHz = ValueUnion.u32;
610 break;
611
612 case 'm':
613 IoOpts.fWithMixer = true;
614 break;
615
616 case 'o':
617 pszDevId = ValueUnion.psz;
618 break;
619
620 case 't':
621 cTestTones++;
622 break;
623
624 case 'z':
625 cbSample = ValueUnion.u8 / 8;
626 break;
627
628 case VKAT_PLAY_OPT_TONE_DUR:
629 ToneParms.msDuration = ValueUnion.u32;
630 break;
631
632 case VKAT_PLAY_OPT_TONE_FREQ:
633 ToneParms.dbFreqHz = ValueUnion.u32;
634 break;
635
636 case VKAT_PLAY_OPT_TONE_VOL:
637 ToneParms.uVolumePercent = ValueUnion.u8;
638 if (ToneParms.uVolumePercent > 100)
639 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid tonevolume (0-100)");
640 break;
641
642 case VKAT_PLAY_OPT_VOL:
643 IoOpts.uVolumePercent = ValueUnion.u8;
644 if (IoOpts.uVolumePercent > 100)
645 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid playback volume (0-100)");
646 break;
647
648 case VINF_GETOPT_NOT_OPTION:
649 {
650 if (cTestTones)
651 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Playing test tones (-t) cannot be combined with playing files");
652
653 /* Set new (override standard) I/O PCM properties if set by the user. */
654 PDMAudioPropsInit(&IoOpts.Props,
655 cbSample ? cbSample : 2 /* 16-bit */, true /* fSigned */,
656 cChannels ? cChannels : 2 /* Stereo */, uHz ? uHz : 44100);
657
658 RTEXITCODE rcExit = audioTestPlayOne(ValueUnion.psz, pDrvReg, pszDevId, &IoOpts);
659 if (rcExit != RTEXITCODE_SUCCESS)
660 return rcExit;
661 break;
662 }
663
664 AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion);
665
666 default:
667 return RTGetOptPrintError(ch, &ValueUnion);
668 }
669 }
670
671 while (cTestTones--)
672 {
673 /* Use some sane defaults if no PCM props are set by the user. */
674 PDMAudioPropsInit(&ToneParms.Props,
675 cbSample ? cbSample : 2 /* 16-bit */, true /* fSigned */,
676 cChannels ? cChannels : 2 /* Stereo */, uHz ? uHz : 44100);
677
678 RTEXITCODE rcExit = audioTestPlayTestToneOne(&ToneParms, pDrvReg, pszDevId, &IoOpts);
679 if (rcExit != RTEXITCODE_SUCCESS)
680 return rcExit;
681 }
682
683 return RTEXITCODE_SUCCESS;
684}
685
686
687/**
688 * Command table entry for 'play'.
689 */
690const VKATCMD g_CmdPlay =
691{
692 "play",
693 audioTestCmdPlayHandler,
694 "Plays one or more wave files.",
695 g_aCmdPlayOptions,
696 RT_ELEMENTS(g_aCmdPlayOptions),
697 audioTestCmdPlayHelp,
698 false /* fNeedsTransport */
699};
700
701
702/*********************************************************************************************************************************
703* Command: rec *
704*********************************************************************************************************************************/
705
706/**
707 * Worker for audioTestRecOne implementing the recording loop.
708 */
709static RTEXITCODE audioTestRecOneInner(PAUDIOTESTDRVMIXSTREAM pMix, PAUDIOTESTWAVEFILE pWaveFile,
710 PCPDMAUDIOSTREAMCFG pCfgAcq, uint64_t cMaxFrames, const char *pszFile)
711{
712 int rc;
713 uint64_t const nsStarted = RTTimeNanoTS();
714
715 /*
716 * Transfer data as quickly as we're allowed.
717 */
718 uint8_t abSamples[16384];
719 uint32_t const cbSamplesAligned = PDMAudioPropsFloorBytesToFrame(pMix->pProps, sizeof(abSamples));
720 uint64_t cFramesCapturedTotal = 0;
721 while (!g_fTerminate && cFramesCapturedTotal < cMaxFrames)
722 {
723 /*
724 * Anything we can read?
725 */
726 uint32_t const cbCanRead = AudioTestMixStreamGetReadable(pMix);
727 if (cbCanRead)
728 {
729 uint32_t const cbToRead = RT_MIN(cbCanRead, cbSamplesAligned);
730 uint32_t cbCaptured = 0;
731 rc = AudioTestMixStreamCapture(pMix, abSamples, cbToRead, &cbCaptured);
732 if (RT_SUCCESS(rc))
733 {
734 if (cbCaptured)
735 {
736 uint32_t cFramesCaptured = PDMAudioPropsBytesToFrames(pMix->pProps, cbCaptured);
737 if (cFramesCaptured + cFramesCaptured < cMaxFrames)
738 { /* likely */ }
739 else
740 {
741 cFramesCaptured = cMaxFrames - cFramesCaptured;
742 cbCaptured = PDMAudioPropsFramesToBytes(pMix->pProps, cFramesCaptured);
743 }
744
745 rc = AudioTestWaveFileWrite(pWaveFile, abSamples, cbCaptured);
746 if (RT_SUCCESS(rc))
747 cFramesCapturedTotal += cFramesCaptured;
748 else
749 return RTMsgErrorExitFailure("Error writing to '%s': %Rrc", pszFile, rc);
750 }
751 else
752 return RTMsgErrorExitFailure("Captured zero bytes - %#x bytes reported readable!\n", cbCanRead);
753 }
754 else
755 return RTMsgErrorExitFailure("Failed to capture %#x bytes: %Rrc (%#x available)\n", cbToRead, rc, cbCanRead);
756 }
757 else if (AudioTestMixStreamIsOkay(pMix))
758 RTThreadSleep(RT_MIN(RT_MAX(1, pCfgAcq->Device.cMsSchedulingHint), 256));
759 else
760 return RTMsgErrorExitFailure("Stream is not okay!\n");
761 }
762
763 /*
764 * Disable the stream.
765 */
766 rc = AudioTestMixStreamDisable(pMix);
767 if (RT_SUCCESS(rc) && g_uVerbosity > 0)
768 RTMsgInfo("%'RU64 ns: Stopped after recording %RU64 frames%s\n", RTTimeNanoTS() - nsStarted, cFramesCapturedTotal,
769 g_fTerminate ? " - Ctrl-C" : ".");
770 else if (RT_FAILURE(rc))
771 return RTMsgErrorExitFailure("Disabling stream failed: %Rrc", rc);
772
773 return RTEXITCODE_SUCCESS;
774}
775
776
777/**
778 * Worker for audioTestCmdRecHandler that recs one file.
779 */
780static RTEXITCODE audioTestRecOne(const char *pszFile, uint8_t cWaveChannels, uint8_t cbWaveSample, uint32_t uWaveHz,
781 PCPDMDRVREG pDrvReg, const char *pszDevId, PAUDIOTESTIOOPTS pIoOpts,
782 uint64_t cMaxFrames, uint64_t cNsMaxDuration)
783{
784 /*
785 * Construct the driver stack.
786 */
787 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
788 AUDIOTESTDRVSTACK DrvStack;
789 int rc = audioTestDriverStackInit(&DrvStack, pDrvReg, pIoOpts->fWithDrvAudio);
790 if (RT_SUCCESS(rc))
791 {
792 /*
793 * Set the input device if one is specified.
794 */
795 rc = audioTestDriverStackSetDevice(&DrvStack, PDMAUDIODIR_IN, pszDevId);
796 if (RT_SUCCESS(rc))
797 {
798 /*
799 * Create an input stream.
800 */
801 PDMAUDIOPCMPROPS ReqProps;
802 PDMAudioPropsInit(&ReqProps,
803 pIoOpts->Props.cbSampleX ? pIoOpts->Props.cbSampleX : cbWaveSample ? cbWaveSample : 2,
804 pIoOpts->Props.fSigned,
805 pIoOpts->Props.cChannelsX ? pIoOpts->Props.cChannelsX : cWaveChannels ? cWaveChannels : 2,
806 pIoOpts->Props.uHz ? pIoOpts->Props.uHz : uWaveHz ? uWaveHz : 44100);
807
808 PDMAUDIOSTREAMCFG CfgAcq;
809 PPDMAUDIOSTREAM pStream = NULL;
810 rc = audioTestDriverStackStreamCreateInput(&DrvStack, &ReqProps, pIoOpts->cMsBufferSize,
811 pIoOpts->cMsPreBuffer, pIoOpts->cMsSchedulingHint, &pStream, &CfgAcq);
812 if (RT_SUCCESS(rc))
813 {
814 /*
815 * Determine the wave file properties. If it differs from the stream
816 * properties, make sure the mixer is enabled.
817 */
818 PDMAUDIOPCMPROPS WaveProps;
819 PDMAudioPropsInit(&WaveProps,
820 cbWaveSample ? cbWaveSample : PDMAudioPropsSampleSize(&CfgAcq.Props),
821 true /*fSigned*/,
822 cWaveChannels ? cWaveChannels : PDMAudioPropsChannels(&CfgAcq.Props),
823 uWaveHz ? uWaveHz : PDMAudioPropsHz(&CfgAcq.Props));
824 if (!pIoOpts->fWithMixer && !PDMAudioPropsAreEqual(&WaveProps, &CfgAcq.Props))
825 {
826 RTMsgInfo("Enabling the mixer buffer.\n");
827 pIoOpts->fWithMixer = true;
828 }
829
830 /* Console the max duration into frames now that we've got the wave file format. */
831 if (cMaxFrames != UINT64_MAX && cNsMaxDuration != UINT64_MAX)
832 {
833 uint64_t cMaxFrames2 = PDMAudioPropsNanoToBytes64(&WaveProps, cNsMaxDuration);
834 cMaxFrames = RT_MAX(cMaxFrames, cMaxFrames2);
835 }
836 else if (cNsMaxDuration != UINT64_MAX)
837 cMaxFrames = PDMAudioPropsNanoToBytes64(&WaveProps, cNsMaxDuration);
838
839 /*
840 * Create a mixer wrapper. This is just a thin wrapper if fWithMixer
841 * is false, otherwise it's doing mixing, resampling and recoding.
842 */
843 AUDIOTESTDRVMIXSTREAM Mix;
844 rc = AudioTestMixStreamInit(&Mix, &DrvStack, pStream, pIoOpts->fWithMixer ? &WaveProps : NULL, 100 /*ms*/);
845 if (RT_SUCCESS(rc))
846 {
847 char szTmp[128];
848 if (g_uVerbosity > 0)
849 RTMsgInfo("Stream: %s cbBackend=%#RX32%s\n",
850 PDMAudioPropsToString(&pStream->Cfg.Props, szTmp, sizeof(szTmp)),
851 pStream->cbBackend, pIoOpts->fWithMixer ? " mixed" : "");
852
853 /*
854 * Open the wave output file.
855 */
856 AUDIOTESTWAVEFILE WaveFile;
857 RTERRINFOSTATIC ErrInfo;
858 rc = AudioTestWaveFileCreate(pszFile, &WaveProps, &WaveFile, RTErrInfoInitStatic(&ErrInfo));
859 if (RT_SUCCESS(rc))
860 {
861 if (g_uVerbosity > 0)
862 {
863 RTMsgInfo("Opened '%s' for playing\n", pszFile);
864 RTMsgInfo("Format: %s\n", PDMAudioPropsToString(&WaveFile.Props, szTmp, sizeof(szTmp)));
865 }
866
867 /*
868 * Enable the stream and start recording.
869 */
870 rc = AudioTestMixStreamEnable(&Mix);
871 if (RT_SUCCESS(rc))
872 rcExit = audioTestRecOneInner(&Mix, &WaveFile, &CfgAcq, cMaxFrames, pszFile);
873 else
874 rcExit = RTMsgErrorExitFailure("Enabling the input stream failed: %Rrc", rc);
875 if (rcExit != RTEXITCODE_SUCCESS)
876 AudioTestMixStreamDisable(&Mix);
877
878 /*
879 * Clean up.
880 */
881 rc = AudioTestWaveFileClose(&WaveFile);
882 if (RT_FAILURE(rc))
883 rcExit = RTMsgErrorExitFailure("Error closing '%s': %Rrc", pszFile, rc);
884 }
885 else
886 rcExit = RTMsgErrorExitFailure("Failed to open '%s': %Rrc%#RTeim", pszFile, rc, &ErrInfo.Core.pszMsg);
887
888 AudioTestMixStreamTerm(&Mix);
889 }
890 audioTestDriverStackStreamDestroy(&DrvStack, pStream);
891 }
892 else
893 rcExit = RTMsgErrorExitFailure("Creating output stream failed: %Rrc", rc);
894 }
895 else
896 rcExit = RTMsgErrorExitFailure("Failed to set output device to '%s': %Rrc", pszDevId, rc);
897 audioTestDriverStackDelete(&DrvStack);
898 }
899 else
900 rcExit = RTMsgErrorExitFailure("Driver stack construction failed: %Rrc", rc);
901 return rcExit;
902}
903
904
905/**
906 * Options for 'rec'.
907 */
908static const RTGETOPTDEF g_aCmdRecOptions[] =
909{
910 { "--backend", 'b', RTGETOPT_REQ_STRING },
911 { "--channels", 'c', RTGETOPT_REQ_UINT8 },
912 { "--hz", 'f', RTGETOPT_REQ_UINT32 },
913 { "--frequency", 'f', RTGETOPT_REQ_UINT32 },
914 { "--sample-size", 'z', RTGETOPT_REQ_UINT8 },
915 { "--input-device", 'i', RTGETOPT_REQ_STRING },
916 { "--wav-channels", 'C', RTGETOPT_REQ_UINT8 },
917 { "--wav-hz", 'F', RTGETOPT_REQ_UINT32 },
918 { "--wav-frequency", 'F', RTGETOPT_REQ_UINT32 },
919 { "--wav-sample-size", 'Z', RTGETOPT_REQ_UINT8 },
920 { "--with-drv-audio", 'd', RTGETOPT_REQ_NOTHING },
921 { "--with-mixer", 'm', RTGETOPT_REQ_NOTHING },
922 { "--max-frames", 'r', RTGETOPT_REQ_UINT64 },
923 { "--max-sec", 's', RTGETOPT_REQ_UINT64 },
924 { "--max-seconds", 's', RTGETOPT_REQ_UINT64 },
925 { "--max-ms", 't', RTGETOPT_REQ_UINT64 },
926 { "--max-milliseconds", 't', RTGETOPT_REQ_UINT64 },
927 { "--max-ns", 'T', RTGETOPT_REQ_UINT64 },
928 { "--max-nanoseconds", 'T', RTGETOPT_REQ_UINT64 },
929};
930
931
932/** The 'rec' command option help. */
933static DECLCALLBACK(const char *) audioTestCmdRecHelp(PCRTGETOPTDEF pOpt)
934{
935 switch (pOpt->iShort)
936 {
937 case 'b': return "The audio backend to use.";
938 case 'c': return "Number of backend input channels";
939 case 'C': return "Number of wave-file channels";
940 case 'd': return "Go via DrvAudio instead of directly interfacing with the backend.";
941 case 'f': return "Input frequency (Hz)";
942 case 'F': return "Wave-file frequency (Hz)";
943 case 'z': return "Input sample size (bits)";
944 case 'Z': return "Wave-file sample size (bits)";
945 case 'm': return "Go via the mixer.";
946 case 'i': return "The ID of the input device to use.";
947 case 'r': return "Max recording duration in frames.";
948 case 's': return "Max recording duration in seconds.";
949 case 't': return "Max recording duration in milliseconds.";
950 case 'T': return "Max recording duration in nanoseconds.";
951 default: return NULL;
952 }
953}
954
955
956/**
957 * The 'rec' command handler.
958 *
959 * @returns Program exit code.
960 * @param pGetState RTGetOpt state.
961 */
962static DECLCALLBACK(RTEXITCODE) audioTestCmdRecHandler(PRTGETOPTSTATE pGetState)
963{
964 /* Option values: */
965 PCPDMDRVREG pDrvReg = AudioTestGetDefaultBackend();
966 const char *pszDevId = NULL;
967 uint8_t cbSample = 0;
968 uint8_t cChannels = 0;
969 uint32_t uHz = 0;
970 uint8_t cbWaveSample = 0;
971 uint8_t cWaveChannels = 0;
972 uint32_t uWaveHz = 0;
973 uint64_t cMaxFrames = UINT64_MAX;
974 uint64_t cNsMaxDuration = UINT64_MAX;
975
976 AUDIOTESTIOOPTS IoOpts;
977 audioTestIoOptsInitDefaults(&IoOpts);
978
979 /* Argument processing loop: */
980 int ch;
981 RTGETOPTUNION ValueUnion;
982 while ((ch = RTGetOpt(pGetState, &ValueUnion)) != 0)
983 {
984 switch (ch)
985 {
986 case 'b':
987 pDrvReg = AudioTestFindBackendOpt(ValueUnion.psz);
988 if (pDrvReg == NULL)
989 return RTEXITCODE_SYNTAX;
990 break;
991
992 case 'c':
993 cChannels = ValueUnion.u8;
994 break;
995
996 case 'C':
997 cWaveChannels = ValueUnion.u8;
998 break;
999
1000 case 'd':
1001 IoOpts.fWithDrvAudio = true;
1002 break;
1003
1004 case 'f':
1005 uHz = ValueUnion.u32;
1006 break;
1007
1008 case 'F':
1009 uWaveHz = ValueUnion.u32;
1010 break;
1011
1012 case 'i':
1013 pszDevId = ValueUnion.psz;
1014 break;
1015
1016 case 'm':
1017 IoOpts.fWithMixer = true;
1018 break;
1019
1020 case 'r':
1021 cMaxFrames = ValueUnion.u64;
1022 break;
1023
1024 case 's':
1025 cNsMaxDuration = ValueUnion.u64 >= UINT64_MAX / RT_NS_1SEC ? UINT64_MAX : ValueUnion.u64 * RT_NS_1SEC;
1026 break;
1027
1028 case 't':
1029 cNsMaxDuration = ValueUnion.u64 >= UINT64_MAX / RT_NS_1MS ? UINT64_MAX : ValueUnion.u64 * RT_NS_1MS;
1030 break;
1031
1032 case 'T':
1033 cNsMaxDuration = ValueUnion.u64;
1034 break;
1035
1036 case 'z':
1037 cbSample = ValueUnion.u8 / 8;
1038 break;
1039
1040 case 'Z':
1041 cbWaveSample = ValueUnion.u8 / 8;
1042 break;
1043
1044 case VINF_GETOPT_NOT_OPTION:
1045 {
1046 if ( cbSample
1047 || cChannels
1048 || uHz)
1049 {
1050 /* Set new (override standard) I/O PCM properties if set by the user. */
1051 PDMAudioPropsInit(&IoOpts.Props,
1052 cbSample ? cbSample : 2 /* 16-bit */, true /* fSigned */,
1053 cChannels ? cChannels : 2 /* Stereo */, uHz ? uHz : 44100);
1054 }
1055
1056 RTEXITCODE rcExit = audioTestRecOne(ValueUnion.psz, cWaveChannels, cbWaveSample, uWaveHz,
1057 pDrvReg, pszDevId, &IoOpts,
1058 cMaxFrames, cNsMaxDuration);
1059 if (rcExit != RTEXITCODE_SUCCESS)
1060 return rcExit;
1061 break;
1062 }
1063
1064 AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion);
1065
1066 default:
1067 return RTGetOptPrintError(ch, &ValueUnion);
1068 }
1069 }
1070 return RTEXITCODE_SUCCESS;
1071}
1072
1073
1074/**
1075 * Command table entry for 'rec'.
1076 */
1077const VKATCMD g_CmdRec =
1078{
1079 "rec",
1080 audioTestCmdRecHandler,
1081 "Records audio to a wave file.",
1082 g_aCmdRecOptions,
1083 RT_ELEMENTS(g_aCmdRecOptions),
1084 audioTestCmdRecHelp,
1085 false /* fNeedsTransport */
1086};
1087
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