VirtualBox

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

Last change on this file since 104620 was 103352, checked in by vboxsync, 7 months ago

Audio/VKAT: Skip probing the same backend again when an alias for it exists.

  • 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: 44.1 KB
Line 
1/* $Id: vkatCmdGeneric.cpp 103352 2024-02-14 13:29:07Z vboxsync $ */
2/** @file
3 * Validation Kit Audio Test (VKAT) utility for testing and validating the audio stack.
4 */
5
6/*
7 * Copyright (C) 2021-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * The contents of this file may alternatively be used under the terms
26 * of the Common Development and Distribution License Version 1.0
27 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
28 * in the VirtualBox distribution, in which case the provisions of the
29 * CDDL are applicable instead of those of the GPL.
30 *
31 * You may elect to license modified versions of this file under the
32 * terms and conditions of either the GPL or the CDDL or both.
33 *
34 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
35 */
36
37
38/*********************************************************************************************************************************
39* Header Files *
40*********************************************************************************************************************************/
41#include <iprt/errcore.h>
42#include <iprt/message.h>
43#include <iprt/rand.h>
44#include <iprt/test.h>
45
46#include "vkatInternal.h"
47
48
49/*********************************************************************************************************************************
50* Command: backends *
51*********************************************************************************************************************************/
52
53/**
54 * Options for 'backends'.
55 */
56static const RTGETOPTDEF g_aCmdBackendsOptions[] =
57{
58 { "--dummy", 'd', RTGETOPT_REQ_NOTHING }, /* just a placeholder */
59};
60
61
62/** The 'backends' command option help. */
63static DECLCALLBACK(const char *) audioTestCmdBackendsHelp(PCRTGETOPTDEF pOpt)
64{
65 RT_NOREF(pOpt);
66 return NULL;
67}
68
69/**
70 * The 'backends' command handler.
71 *
72 * @returns Program exit code.
73 * @param pGetState RTGetOpt state.
74 */
75static DECLCALLBACK(RTEXITCODE) audioTestCmdBackendsHandler(PRTGETOPTSTATE pGetState)
76{
77 /*
78 * Parse options.
79 */
80 int ch;
81 RTGETOPTUNION ValueUnion;
82 while ((ch = RTGetOpt(pGetState, &ValueUnion)) != 0)
83 {
84 switch (ch)
85 {
86 AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion, &g_CmdBackends);
87
88 default:
89 return RTGetOptPrintError(ch, &ValueUnion);
90 }
91 }
92
93 /*
94 * List the backends.
95 */
96 RTPrintf("Backends (%u):\n", g_cBackends);
97 for (size_t i = 0; i < g_cBackends; i++)
98 RTPrintf(" %12s - %s\n", g_aBackends[i].pszName, g_aBackends[i].pDrvReg->pszDescription);
99
100 return RTEXITCODE_SUCCESS;
101}
102
103
104/**
105 * Command table entry for 'backends'.
106 */
107const VKATCMD g_CmdBackends =
108{
109 /* .pszCommand = */ "backends",
110 /* .pfnHandler = */ audioTestCmdBackendsHandler,
111 /* .pszDesc = */ "Lists the compiled in audio backends.",
112 /* .paOptions = */ g_aCmdBackendsOptions,
113 /* .cOptions = */ 0 /*RT_ELEMENTS(g_aCmdBackendsOptions)*/,
114 /* .pfnOptionHelp = */ audioTestCmdBackendsHelp,
115 /* .fNeedsTransport = */ false
116};
117
118
119/*********************************************************************************************************************************
120* Command: enum *
121*********************************************************************************************************************************/
122
123
124
125/**
126 * Long option values for the 'enum' command.
127 */
128enum
129{
130 VKAT_ENUM_OPT_PROBE_BACKENDS = 900
131};
132
133/**
134 * Options for 'enum'.
135 */
136static const RTGETOPTDEF g_aCmdEnumOptions[] =
137{
138 { "--backend", 'b', RTGETOPT_REQ_STRING },
139 { "--probe-backends", VKAT_ENUM_OPT_PROBE_BACKENDS, RTGETOPT_REQ_NOTHING }
140};
141
142
143/** The 'enum' command option help. */
144static DECLCALLBACK(const char *) audioTestCmdEnumHelp(PCRTGETOPTDEF pOpt)
145{
146 switch (pOpt->iShort)
147 {
148 case 'b': return "The audio backend to use";
149 case VKAT_ENUM_OPT_PROBE_BACKENDS: return "Probes all (available) backends until a working one is found";
150 default: return NULL;
151 }
152}
153
154/**
155 * The 'enum' command handler.
156 *
157 * @returns Program exit code.
158 * @param pGetState RTGetOpt state.
159 */
160static DECLCALLBACK(RTEXITCODE) audioTestCmdEnumHandler(PRTGETOPTSTATE pGetState)
161{
162 /*
163 * Parse options.
164 */
165 /* Option values: */
166 PCPDMDRVREG pDrvReg = AudioTestGetDefaultBackend();
167 bool fProbeBackends = false;
168
169 /* Argument processing loop: */
170 int ch;
171 RTGETOPTUNION ValueUnion;
172 while ((ch = RTGetOpt(pGetState, &ValueUnion)) != 0)
173 {
174 switch (ch)
175 {
176 case 'b':
177 pDrvReg = AudioTestFindBackendOpt(ValueUnion.psz);
178 if (pDrvReg == NULL)
179 return RTEXITCODE_SYNTAX;
180 break;
181
182 case VKAT_ENUM_OPT_PROBE_BACKENDS:
183 fProbeBackends = true;
184 break;
185
186 AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion, &g_CmdEnum);
187
188 default:
189 return RTGetOptPrintError(ch, &ValueUnion);
190 }
191 }
192
193 int rc;
194
195 AUDIOTESTDRVSTACK DrvStack;
196 if (fProbeBackends)
197 rc = audioTestDriverStackProbe(&DrvStack,
198 true /* fEnabledIn */, true /* fEnabledOut */, false /* fWithDrvAudio */);
199 else
200 rc = audioTestDriverStackInitEx(&DrvStack, pDrvReg,
201 true /* fEnabledIn */, true /* fEnabledOut */, false /* fWithDrvAudio */);
202 if (RT_FAILURE(rc))
203 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unable to init driver stack: %Rrc\n", rc);
204
205 /*
206 * Do the enumeration.
207 */
208 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
209
210 if (DrvStack.pIHostAudio->pfnGetDevices)
211 {
212 PDMAUDIOHOSTENUM Enum;
213 rc = DrvStack.pIHostAudio->pfnGetDevices(DrvStack.pIHostAudio, &Enum);
214 if (RT_SUCCESS(rc))
215 {
216 RTPrintf("Found %u device%s\n", Enum.cDevices, Enum.cDevices != 1 ? "s" : "");
217
218 PPDMAUDIOHOSTDEV pHostDev;
219 RTListForEach(&Enum.LstDevices, pHostDev, PDMAUDIOHOSTDEV, ListEntry)
220 {
221 RTPrintf("\nDevice \"%s\":\n", pHostDev->pszName);
222
223 char szFlags[PDMAUDIOHOSTDEV_MAX_FLAGS_STRING_LEN];
224 if (pHostDev->cMaxInputChannels && !pHostDev->cMaxOutputChannels && pHostDev->enmUsage == PDMAUDIODIR_IN)
225 RTPrintf(" Input: max %u channels (%s)\n",
226 pHostDev->cMaxInputChannels, PDMAudioHostDevFlagsToString(szFlags, pHostDev->fFlags));
227 else if (!pHostDev->cMaxInputChannels && pHostDev->cMaxOutputChannels && pHostDev->enmUsage == PDMAUDIODIR_OUT)
228 RTPrintf(" Output: max %u channels (%s)\n",
229 pHostDev->cMaxOutputChannels, PDMAudioHostDevFlagsToString(szFlags, pHostDev->fFlags));
230 else
231 RTPrintf(" %s: max %u output channels, max %u input channels (%s)\n",
232 PDMAudioDirGetName(pHostDev->enmUsage), pHostDev->cMaxOutputChannels,
233 pHostDev->cMaxInputChannels, PDMAudioHostDevFlagsToString(szFlags, pHostDev->fFlags));
234
235 if (pHostDev->pszId && *pHostDev->pszId)
236 RTPrintf(" ID: \"%s\"\n", pHostDev->pszId);
237 }
238
239 PDMAudioHostEnumDelete(&Enum);
240 }
241 else
242 rcExit = RTMsgErrorExitFailure("Enumeration failed: %Rrc\n", rc);
243 }
244 else
245 rcExit = RTMsgErrorExitFailure("Enumeration not supported by backend '%s'\n", pDrvReg->szName);
246 audioTestDriverStackDelete(&DrvStack);
247
248 return RTEXITCODE_SUCCESS;
249}
250
251
252/**
253 * Command table entry for 'enum'.
254 */
255const VKATCMD g_CmdEnum =
256{
257 "enum",
258 audioTestCmdEnumHandler,
259 "Enumerates audio devices.",
260 g_aCmdEnumOptions,
261 RT_ELEMENTS(g_aCmdEnumOptions),
262 audioTestCmdEnumHelp,
263 false /* fNeedsTransport */
264};
265
266
267
268
269/*********************************************************************************************************************************
270* Command: play *
271*********************************************************************************************************************************/
272
273/**
274 * Worker for audioTestPlayOne implementing the play loop.
275 */
276static RTEXITCODE audioTestPlayOneInner(PAUDIOTESTDRVMIXSTREAM pMix, PAUDIOTESTWAVEFILE pWaveFile,
277 PCPDMAUDIOSTREAMCFG pCfgAcq, const char *pszFile)
278{
279 uint32_t const cbPreBuffer = PDMAudioPropsFramesToBytes(pMix->pProps, pCfgAcq->Backend.cFramesPreBuffering);
280 uint64_t const nsStarted = RTTimeNanoTS();
281 uint64_t nsDonePreBuffering = 0;
282
283 /*
284 * Transfer data as quickly as we're allowed.
285 */
286 uint8_t abSamples[16384];
287 uint32_t const cbSamplesAligned = PDMAudioPropsFloorBytesToFrame(pMix->pProps, sizeof(abSamples));
288 uint64_t offStream = 0;
289 while (!g_fTerminate)
290 {
291 /* Read a chunk from the wave file. */
292 size_t cbSamples = 0;
293 int rc = AudioTestWaveFileRead(pWaveFile, abSamples, cbSamplesAligned, &cbSamples);
294 if (RT_SUCCESS(rc) && cbSamples > 0)
295 {
296 /* Pace ourselves a little. */
297 if (offStream >= cbPreBuffer)
298 {
299 if (!nsDonePreBuffering)
300 nsDonePreBuffering = RTTimeNanoTS();
301 uint64_t const cNsWritten = PDMAudioPropsBytesToNano64(pMix->pProps, offStream - cbPreBuffer);
302 uint64_t const cNsElapsed = RTTimeNanoTS() - nsStarted;
303 if (cNsWritten > cNsElapsed + RT_NS_10MS)
304 RTThreadSleep((cNsWritten - cNsElapsed - RT_NS_10MS / 2) / RT_NS_1MS);
305 }
306
307 /* Transfer the data to the audio stream. */
308 for (uint32_t offSamples = 0; offSamples < cbSamples;)
309 {
310 uint32_t const cbCanWrite = AudioTestMixStreamGetWritable(pMix);
311 if (cbCanWrite > 0)
312 {
313 uint32_t const cbToPlay = RT_MIN(cbCanWrite, (uint32_t)cbSamples - offSamples);
314 uint32_t cbPlayed = 0;
315 rc = AudioTestMixStreamPlay(pMix, &abSamples[offSamples], cbToPlay, &cbPlayed);
316 if (RT_SUCCESS(rc))
317 {
318 if (cbPlayed)
319 {
320 offSamples += cbPlayed;
321 offStream += cbPlayed;
322 }
323 else
324 return RTMsgErrorExitFailure("Played zero bytes - %#x bytes reported playable!\n", cbCanWrite);
325 }
326 else
327 return RTMsgErrorExitFailure("Failed to play %#x bytes: %Rrc\n", cbToPlay, rc);
328 }
329 else if (AudioTestMixStreamIsOkay(pMix))
330 RTThreadSleep(RT_MIN(RT_MAX(1, pCfgAcq->Device.cMsSchedulingHint), 256));
331 else
332 return RTMsgErrorExitFailure("Stream is not okay!\n");
333 }
334 }
335 else if (RT_SUCCESS(rc) && cbSamples == 0)
336 break;
337 else
338 return RTMsgErrorExitFailure("Error reading wav file '%s': %Rrc", pszFile, rc);
339 }
340
341 /*
342 * Drain the stream.
343 */
344 if (g_uVerbosity > 0)
345 RTMsgInfo("%'RU64 ns: Draining...\n", RTTimeNanoTS() - nsStarted);
346 int rc = AudioTestMixStreamDrain(pMix, true /*fSync*/);
347 if (RT_SUCCESS(rc))
348 {
349 if (g_uVerbosity > 0)
350 RTMsgInfo("%'RU64 ns: Done\n", RTTimeNanoTS() - nsStarted);
351 }
352 else
353 return RTMsgErrorExitFailure("Draining failed: %Rrc", rc);
354
355 return RTEXITCODE_SUCCESS;
356}
357
358/**
359 * Worker for audioTestCmdPlayHandler that plays one file.
360 */
361static RTEXITCODE audioTestPlayOne(const char *pszFile, PCPDMDRVREG pDrvReg, const char *pszDevId,
362 PAUDIOTESTIOOPTS pIoOpts)
363{
364 char szTmp[128];
365
366 /*
367 * First we must open the file and determin the format.
368 */
369 RTERRINFOSTATIC ErrInfo;
370 AUDIOTESTWAVEFILE WaveFile;
371 int rc = AudioTestWaveFileOpen(pszFile, &WaveFile, RTErrInfoInitStatic(&ErrInfo));
372 if (RT_FAILURE(rc))
373 return RTMsgErrorExitFailure("Failed to open '%s': %Rrc%#RTeim", pszFile, rc, &ErrInfo.Core);
374
375 if (g_uVerbosity > 0)
376 {
377 RTMsgInfo("Opened '%s' for playing\n", pszFile);
378 RTMsgInfo("Format: %s\n", PDMAudioPropsToString(&WaveFile.Props, szTmp, sizeof(szTmp)));
379 RTMsgInfo("Size: %'RU32 bytes / %#RX32 / %'RU32 frames / %'RU64 ns\n",
380 WaveFile.cbSamples, WaveFile.cbSamples,
381 PDMAudioPropsBytesToFrames(&WaveFile.Props, WaveFile.cbSamples),
382 PDMAudioPropsBytesToNano(&WaveFile.Props, WaveFile.cbSamples));
383 }
384
385 /*
386 * Construct the driver stack.
387 */
388 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
389 AUDIOTESTDRVSTACK DrvStack;
390 rc = audioTestDriverStackInit(&DrvStack, pDrvReg, pIoOpts->fWithDrvAudio);
391 if (RT_SUCCESS(rc))
392 {
393 /*
394 * Set the output device if one is specified.
395 */
396 rc = audioTestDriverStackSetDevice(&DrvStack, PDMAUDIODIR_OUT, pszDevId);
397 if (RT_SUCCESS(rc))
398 {
399 /*
400 * Open a stream for the output.
401 */
402 uint8_t const cChannels = PDMAudioPropsChannels(&pIoOpts->Props);
403
404 PDMAUDIOPCMPROPS ReqProps = WaveFile.Props;
405 if (cChannels != 0 && PDMAudioPropsChannels(&ReqProps) != cChannels)
406 PDMAudioPropsSetChannels(&ReqProps, cChannels);
407
408 uint8_t const cbSample = PDMAudioPropsSampleSize(&pIoOpts->Props);
409 if (cbSample != 0)
410 PDMAudioPropsSetSampleSize(&ReqProps, cbSample);
411
412 uint32_t const uHz = PDMAudioPropsHz(&pIoOpts->Props);
413 if (uHz != 0)
414 ReqProps.uHz = uHz;
415
416 PDMAUDIOSTREAMCFG CfgAcq;
417 PPDMAUDIOSTREAM pStream = NULL;
418 rc = audioTestDriverStackStreamCreateOutput(&DrvStack, &ReqProps, pIoOpts->cMsBufferSize,
419 pIoOpts->cMsPreBuffer, pIoOpts->cMsSchedulingHint, &pStream, &CfgAcq);
420 if (RT_SUCCESS(rc))
421 {
422 /*
423 * Automatically enable the mixer if the wave file and the
424 * output parameters doesn't match.
425 */
426 if ( !pIoOpts->fWithMixer
427 && ( !PDMAudioPropsAreEqual(&WaveFile.Props, &pStream->Cfg.Props)
428 || pIoOpts->uVolumePercent != 100)
429 )
430 {
431 RTMsgInfo("Enabling the mixer buffer.\n");
432 pIoOpts->fWithMixer = true;
433 }
434
435 /*
436 * Create a mixer wrapper. This is just a thin wrapper if fWithMixer
437 * is false, otherwise it's doing mixing, resampling and recoding.
438 */
439 AUDIOTESTDRVMIXSTREAM Mix;
440 rc = AudioTestMixStreamInit(&Mix, &DrvStack, pStream, pIoOpts->fWithMixer ? &WaveFile.Props : NULL, 100 /*ms*/);
441 if (RT_SUCCESS(rc))
442 {
443 if (g_uVerbosity > 0)
444 RTMsgInfo("Stream: %s cbBackend=%#RX32%s\n",
445 PDMAudioPropsToString(&pStream->Cfg.Props, szTmp, sizeof(szTmp)),
446 pStream->cbBackend, pIoOpts->fWithMixer ? " mixed" : "");
447
448 if (pIoOpts->fWithMixer)
449 AudioTestMixStreamSetVolume(&Mix, pIoOpts->uVolumePercent);
450
451 /*
452 * Enable the stream and start playing.
453 */
454 rc = AudioTestMixStreamEnable(&Mix);
455 if (RT_SUCCESS(rc))
456 rcExit = audioTestPlayOneInner(&Mix, &WaveFile, &CfgAcq, pszFile);
457 else
458 rcExit = RTMsgErrorExitFailure("Enabling the output stream failed: %Rrc", rc);
459
460 /*
461 * Clean up.
462 */
463 AudioTestMixStreamTerm(&Mix);
464 }
465 audioTestDriverStackStreamDestroy(&DrvStack, pStream);
466 pStream = NULL;
467 }
468 else
469 rcExit = RTMsgErrorExitFailure("Creating output stream failed: %Rrc", rc);
470 }
471 else
472 rcExit = RTMsgErrorExitFailure("Failed to set output device to '%s': %Rrc", pszDevId, rc);
473 audioTestDriverStackDelete(&DrvStack);
474 }
475 else
476 rcExit = RTMsgErrorExitFailure("Driver stack construction failed: %Rrc", rc);
477 AudioTestWaveFileClose(&WaveFile);
478 return rcExit;
479}
480
481/**
482 * Worker for audioTestCmdPlayHandler that plays one test tone.
483 */
484static RTEXITCODE audioTestPlayTestToneOne(PAUDIOTESTTONEPARMS pToneParms,
485 PCPDMDRVREG pDrvReg, const char *pszDevId,
486 PAUDIOTESTIOOPTS pIoOpts)
487{
488 char szTmp[128];
489
490 AUDIOTESTSTREAM TstStream;
491 RT_ZERO(TstStream);
492
493 /*
494 * Construct the driver stack.
495 */
496 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
497 AUDIOTESTDRVSTACK DrvStack;
498 int rc = audioTestDriverStackInit(&DrvStack, pDrvReg, pIoOpts->fWithDrvAudio);
499 if (RT_SUCCESS(rc))
500 {
501 /*
502 * Set the output device if one is specified.
503 */
504 rc = audioTestDriverStackSetDevice(&DrvStack, PDMAUDIODIR_OUT, pszDevId);
505 if (RT_SUCCESS(rc))
506 {
507 /*
508 * Open a stream for the output.
509 */
510 uint8_t const cChannels = PDMAudioPropsChannels(&pIoOpts->Props);
511
512 PDMAUDIOPCMPROPS ReqProps = pToneParms->Props;
513 if (cChannels != 0 && PDMAudioPropsChannels(&ReqProps) != cChannels)
514 PDMAudioPropsSetChannels(&ReqProps, cChannels);
515
516 uint8_t const cbSample = PDMAudioPropsSampleSize(&pIoOpts->Props);
517 if (cbSample != 0)
518 PDMAudioPropsSetSampleSize(&ReqProps, cbSample);
519
520 uint32_t const uHz = PDMAudioPropsHz(&pIoOpts->Props);
521 if (uHz != 0)
522 ReqProps.uHz = uHz;
523
524 rc = audioTestDriverStackStreamCreateOutput(&DrvStack, &ReqProps, pIoOpts->cMsBufferSize,
525 pIoOpts->cMsPreBuffer, pIoOpts->cMsSchedulingHint, &TstStream.pStream, &TstStream.Cfg);
526 if (RT_SUCCESS(rc))
527 {
528 /*
529 * Automatically enable the mixer if the wave file and the
530 * output parameters doesn't match.
531 */
532 if ( !pIoOpts->fWithMixer
533 && ( !PDMAudioPropsAreEqual(&pToneParms->Props, &TstStream.pStream->Cfg.Props)
534 || pToneParms->uVolumePercent != 100)
535 )
536 {
537 RTMsgInfo("Enabling the mixer buffer.\n");
538 pIoOpts->fWithMixer = true;
539 }
540
541 /*
542 * Create a mixer wrapper. This is just a thin wrapper if fWithMixer
543 * is false, otherwise it's doing mixing, resampling and recoding.
544 */
545 rc = AudioTestMixStreamInit(&TstStream.Mix, &DrvStack, TstStream.pStream,
546 pIoOpts->fWithMixer ? &pToneParms->Props : NULL, 100 /*ms*/);
547 if (RT_SUCCESS(rc))
548 {
549 if (g_uVerbosity > 0)
550 RTMsgInfo("Stream: %s cbBackend=%#RX32%s\n",
551 PDMAudioPropsToString(&TstStream.pStream->Cfg.Props, szTmp, sizeof(szTmp)),
552 TstStream.pStream->cbBackend, pIoOpts->fWithMixer ? " mixed" : "");
553
554 /*
555 * Enable the stream and start playing.
556 */
557 rc = AudioTestMixStreamEnable(&TstStream.Mix);
558 if (RT_SUCCESS(rc))
559 {
560 if (pIoOpts->fWithMixer)
561 AudioTestMixStreamSetVolume(&TstStream.Mix, pToneParms->uVolumePercent);
562
563 rc = audioTestPlayTone(pIoOpts, NULL /* pTstEnv */, &TstStream, pToneParms);
564 if (RT_SUCCESS(rc))
565 rcExit = RTEXITCODE_SUCCESS;
566 }
567 else
568 rcExit = RTMsgErrorExitFailure("Enabling the output stream failed: %Rrc", rc);
569
570 /*
571 * Clean up.
572 */
573 AudioTestMixStreamTerm(&TstStream.Mix);
574 }
575 audioTestDriverStackStreamDestroy(&DrvStack, TstStream.pStream);
576 TstStream.pStream = NULL;
577 }
578 else
579 rcExit = RTMsgErrorExitFailure("Creating output stream failed: %Rrc", rc);
580 }
581 else
582 rcExit = RTMsgErrorExitFailure("Failed to set output device to '%s': %Rrc", pszDevId, rc);
583 audioTestDriverStackDelete(&DrvStack);
584 }
585 else
586 rcExit = RTMsgErrorExitFailure("Driver stack construction failed: %Rrc", rc);
587 return rcExit;
588}
589
590
591/**
592 * Long option values for the 'play' command.
593 */
594enum
595{
596 VKAT_PLAY_OPT_TONE_DUR = 900,
597 VKAT_PLAY_OPT_TONE_FREQ,
598 VKAT_PLAY_OPT_TONE_VOL,
599 VKAT_PLAY_OPT_VOL
600};
601
602
603/**
604 * Options for 'play'.
605 */
606static const RTGETOPTDEF g_aCmdPlayOptions[] =
607{
608 { "--backend", 'b', RTGETOPT_REQ_STRING },
609 { "--channels", 'c', RTGETOPT_REQ_UINT8 },
610 { "--hz", 'f', RTGETOPT_REQ_UINT32 },
611 { "--frequency", 'f', RTGETOPT_REQ_UINT32 },
612 { "--sample-size", 'z', RTGETOPT_REQ_UINT8 },
613 { "--test-tone", 't', RTGETOPT_REQ_NOTHING },
614 { "--tone-dur", VKAT_PLAY_OPT_TONE_DUR, RTGETOPT_REQ_UINT32 },
615 { "--tone-freq", VKAT_PLAY_OPT_TONE_FREQ, RTGETOPT_REQ_UINT32 },
616 { "--tone-vol", VKAT_PLAY_OPT_TONE_VOL, RTGETOPT_REQ_UINT32 },
617 { "--output-device", 'o', RTGETOPT_REQ_STRING },
618 { "--with-drv-audio", 'd', RTGETOPT_REQ_NOTHING },
619 { "--with-mixer", 'm', RTGETOPT_REQ_NOTHING },
620 { "--vol", VKAT_PLAY_OPT_VOL, RTGETOPT_REQ_UINT8 }
621};
622
623
624/** The 'play' command option help. */
625static DECLCALLBACK(const char *) audioTestCmdPlayHelp(PCRTGETOPTDEF pOpt)
626{
627 switch (pOpt->iShort)
628 {
629 case 'b': return "The audio backend to use";
630 case 'c': return "Number of backend output channels";
631 case 'd': return "Go via DrvAudio instead of directly interfacing with the backend";
632 case 'f': return "Output frequency (Hz)";
633 case 'z': return "Output sample size (bits)";
634 case 't': return "Plays a test tone. Can be specified multiple times";
635 case 'm': return "Go via the mixer";
636 case 'o': return "The ID of the output device to use";
637 case VKAT_PLAY_OPT_TONE_DUR: return "Test tone duration (ms)";
638 case VKAT_PLAY_OPT_TONE_FREQ: return "Test tone frequency (Hz)";
639 case VKAT_PLAY_OPT_TONE_VOL: return "Test tone volume (percent)";
640 case VKAT_PLAY_OPT_VOL: return "Playback volume (percent)";
641 default: return NULL;
642 }
643}
644
645
646/**
647 * The 'play' command handler.
648 *
649 * @returns Program exit code.
650 * @param pGetState RTGetOpt state.
651 */
652static DECLCALLBACK(RTEXITCODE) audioTestCmdPlayHandler(PRTGETOPTSTATE pGetState)
653{
654 /* Option values: */
655 PCPDMDRVREG pDrvReg = AudioTestGetDefaultBackend();
656 const char *pszDevId = NULL;
657 uint32_t cTestTones = 0;
658 uint8_t cbSample = 0;
659 uint8_t cChannels = 0;
660 uint32_t uHz = 0;
661
662 AUDIOTESTIOOPTS IoOpts;
663 audioTestIoOptsInitDefaults(&IoOpts);
664
665 AUDIOTESTTONEPARMS ToneParms;
666 audioTestToneParmsInit(&ToneParms);
667
668 /* Argument processing loop: */
669 int ch;
670 RTGETOPTUNION ValueUnion;
671 while ((ch = RTGetOpt(pGetState, &ValueUnion)) != 0)
672 {
673 switch (ch)
674 {
675 case 'b':
676 pDrvReg = AudioTestFindBackendOpt(ValueUnion.psz);
677 if (pDrvReg == NULL)
678 return RTEXITCODE_SYNTAX;
679 break;
680
681 case 'c':
682 cChannels = ValueUnion.u8;
683 break;
684
685 case 'd':
686 IoOpts.fWithDrvAudio = true;
687 break;
688
689 case 'f':
690 uHz = ValueUnion.u32;
691 break;
692
693 case 'm':
694 IoOpts.fWithMixer = true;
695 break;
696
697 case 'o':
698 pszDevId = ValueUnion.psz;
699 break;
700
701 case 't':
702 cTestTones++;
703 break;
704
705 case 'z':
706 cbSample = ValueUnion.u8 / 8;
707 break;
708
709 case VKAT_PLAY_OPT_TONE_DUR:
710 ToneParms.msDuration = ValueUnion.u32;
711 break;
712
713 case VKAT_PLAY_OPT_TONE_FREQ:
714 ToneParms.dbFreqHz = ValueUnion.u32;
715 break;
716
717 case VKAT_PLAY_OPT_TONE_VOL:
718 ToneParms.uVolumePercent = ValueUnion.u8;
719 if (ToneParms.uVolumePercent > 100)
720 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid tonevolume (0-100)");
721 break;
722
723 case VKAT_PLAY_OPT_VOL:
724 IoOpts.uVolumePercent = ValueUnion.u8;
725 if (IoOpts.uVolumePercent > 100)
726 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid playback volume (0-100)");
727 break;
728
729 case VINF_GETOPT_NOT_OPTION:
730 {
731 if (cTestTones)
732 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Playing test tones (-t) cannot be combined with playing files");
733
734 /* Set new (override standard) I/O PCM properties if set by the user. */
735 PDMAudioPropsInit(&IoOpts.Props,
736 cbSample ? cbSample : 2 /* 16-bit */, true /* fSigned */,
737 cChannels ? cChannels : 2 /* Stereo */, uHz ? uHz : 44100);
738
739 RTEXITCODE rcExit = audioTestPlayOne(ValueUnion.psz, pDrvReg, pszDevId, &IoOpts);
740 if (rcExit != RTEXITCODE_SUCCESS)
741 return rcExit;
742 break;
743 }
744
745 AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion, &g_CmdPlay);
746
747 default:
748 return RTGetOptPrintError(ch, &ValueUnion);
749 }
750 }
751
752 while (cTestTones--)
753 {
754 /* Use some sane defaults if no PCM props are set by the user. */
755 PDMAudioPropsInit(&ToneParms.Props,
756 cbSample ? cbSample : 2 /* 16-bit */, true /* fSigned */,
757 cChannels ? cChannels : 2 /* Stereo */, uHz ? uHz : 44100);
758
759 RTEXITCODE rcExit = audioTestPlayTestToneOne(&ToneParms, pDrvReg, pszDevId, &IoOpts);
760 if (rcExit != RTEXITCODE_SUCCESS)
761 return rcExit;
762 }
763
764 return RTEXITCODE_SUCCESS;
765}
766
767
768/**
769 * Command table entry for 'play'.
770 */
771const VKATCMD g_CmdPlay =
772{
773 "play",
774 audioTestCmdPlayHandler,
775 "Plays one or more wave files.",
776 g_aCmdPlayOptions,
777 RT_ELEMENTS(g_aCmdPlayOptions),
778 audioTestCmdPlayHelp,
779 false /* fNeedsTransport */
780};
781
782
783/*********************************************************************************************************************************
784* Command: rec *
785*********************************************************************************************************************************/
786
787/**
788 * Worker for audioTestRecOne implementing the recording loop.
789 */
790static RTEXITCODE audioTestRecOneInner(PAUDIOTESTDRVMIXSTREAM pMix, PAUDIOTESTWAVEFILE pWaveFile,
791 PCPDMAUDIOSTREAMCFG pCfgAcq, uint64_t cMaxFrames, const char *pszFile)
792{
793 int rc;
794 uint64_t const nsStarted = RTTimeNanoTS();
795
796 /*
797 * Transfer data as quickly as we're allowed.
798 */
799 uint8_t abSamples[16384];
800 uint32_t const cbSamplesAligned = PDMAudioPropsFloorBytesToFrame(pMix->pProps, sizeof(abSamples));
801 uint64_t cFramesCapturedTotal = 0;
802 while (!g_fTerminate && cFramesCapturedTotal < cMaxFrames)
803 {
804 /*
805 * Anything we can read?
806 */
807 uint32_t const cbCanRead = AudioTestMixStreamGetReadable(pMix);
808 if (cbCanRead)
809 {
810 uint32_t const cbToRead = RT_MIN(cbCanRead, cbSamplesAligned);
811 uint32_t cbCaptured = 0;
812 rc = AudioTestMixStreamCapture(pMix, abSamples, cbToRead, &cbCaptured);
813 if (RT_SUCCESS(rc))
814 {
815 if (cbCaptured)
816 {
817 uint32_t cFramesCaptured = PDMAudioPropsBytesToFrames(pMix->pProps, cbCaptured);
818 if (cFramesCaptured + cFramesCaptured < cMaxFrames)
819 { /* likely */ }
820 else
821 {
822 cFramesCaptured = cMaxFrames - cFramesCaptured;
823 cbCaptured = PDMAudioPropsFramesToBytes(pMix->pProps, cFramesCaptured);
824 }
825
826 rc = AudioTestWaveFileWrite(pWaveFile, abSamples, cbCaptured);
827 if (RT_SUCCESS(rc))
828 cFramesCapturedTotal += cFramesCaptured;
829 else
830 return RTMsgErrorExitFailure("Error writing to '%s': %Rrc", pszFile, rc);
831 }
832 else
833 return RTMsgErrorExitFailure("Captured zero bytes - %#x bytes reported readable!\n", cbCanRead);
834 }
835 else
836 return RTMsgErrorExitFailure("Failed to capture %#x bytes: %Rrc (%#x available)\n", cbToRead, rc, cbCanRead);
837 }
838 else if (AudioTestMixStreamIsOkay(pMix))
839 RTThreadSleep(RT_MIN(RT_MAX(1, pCfgAcq->Device.cMsSchedulingHint), 256));
840 else
841 return RTMsgErrorExitFailure("Stream is not okay!\n");
842 }
843
844 /*
845 * Disable the stream.
846 */
847 rc = AudioTestMixStreamDisable(pMix);
848 if (RT_SUCCESS(rc) && g_uVerbosity > 0)
849 RTMsgInfo("%'RU64 ns: Stopped after recording %RU64 frames%s\n", RTTimeNanoTS() - nsStarted, cFramesCapturedTotal,
850 g_fTerminate ? " - Ctrl-C" : ".");
851 else if (RT_FAILURE(rc))
852 return RTMsgErrorExitFailure("Disabling stream failed: %Rrc", rc);
853
854 return RTEXITCODE_SUCCESS;
855}
856
857
858/**
859 * Worker for audioTestCmdRecHandler that recs one file.
860 */
861static RTEXITCODE audioTestRecOne(const char *pszFile, uint8_t cWaveChannels, uint8_t cbWaveSample, uint32_t uWaveHz,
862 PCPDMDRVREG pDrvReg, const char *pszDevId, PAUDIOTESTIOOPTS pIoOpts,
863 uint64_t cMaxFrames, uint64_t cNsMaxDuration)
864{
865 /*
866 * Construct the driver stack.
867 */
868 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
869 AUDIOTESTDRVSTACK DrvStack;
870 int rc = audioTestDriverStackInit(&DrvStack, pDrvReg, pIoOpts->fWithDrvAudio);
871 if (RT_SUCCESS(rc))
872 {
873 /*
874 * Set the input device if one is specified.
875 */
876 rc = audioTestDriverStackSetDevice(&DrvStack, PDMAUDIODIR_IN, pszDevId);
877 if (RT_SUCCESS(rc))
878 {
879 /*
880 * Create an input stream.
881 */
882 PDMAUDIOPCMPROPS ReqProps;
883 PDMAudioPropsInit(&ReqProps,
884 pIoOpts->Props.cbSampleX ? pIoOpts->Props.cbSampleX : cbWaveSample ? cbWaveSample : 2,
885 pIoOpts->Props.fSigned,
886 pIoOpts->Props.cChannelsX ? pIoOpts->Props.cChannelsX : cWaveChannels ? cWaveChannels : 2,
887 pIoOpts->Props.uHz ? pIoOpts->Props.uHz : uWaveHz ? uWaveHz : 44100);
888
889 PDMAUDIOSTREAMCFG CfgAcq;
890 PPDMAUDIOSTREAM pStream = NULL;
891 rc = audioTestDriverStackStreamCreateInput(&DrvStack, &ReqProps, pIoOpts->cMsBufferSize,
892 pIoOpts->cMsPreBuffer, pIoOpts->cMsSchedulingHint, &pStream, &CfgAcq);
893 if (RT_SUCCESS(rc))
894 {
895 /*
896 * Determine the wave file properties. If it differs from the stream
897 * properties, make sure the mixer is enabled.
898 */
899 PDMAUDIOPCMPROPS WaveProps;
900 PDMAudioPropsInit(&WaveProps,
901 cbWaveSample ? cbWaveSample : PDMAudioPropsSampleSize(&CfgAcq.Props),
902 true /*fSigned*/,
903 cWaveChannels ? cWaveChannels : PDMAudioPropsChannels(&CfgAcq.Props),
904 uWaveHz ? uWaveHz : PDMAudioPropsHz(&CfgAcq.Props));
905 if (!pIoOpts->fWithMixer && !PDMAudioPropsAreEqual(&WaveProps, &CfgAcq.Props))
906 {
907 RTMsgInfo("Enabling the mixer buffer.\n");
908 pIoOpts->fWithMixer = true;
909 }
910
911 /* Console the max duration into frames now that we've got the wave file format. */
912 if (cMaxFrames != UINT64_MAX && cNsMaxDuration != UINT64_MAX)
913 {
914 uint64_t cMaxFrames2 = PDMAudioPropsNanoToBytes64(&WaveProps, cNsMaxDuration);
915 cMaxFrames = RT_MAX(cMaxFrames, cMaxFrames2);
916 }
917 else if (cNsMaxDuration != UINT64_MAX)
918 cMaxFrames = PDMAudioPropsNanoToBytes64(&WaveProps, cNsMaxDuration);
919
920 /*
921 * Create a mixer wrapper. This is just a thin wrapper if fWithMixer
922 * is false, otherwise it's doing mixing, resampling and recoding.
923 */
924 AUDIOTESTDRVMIXSTREAM Mix;
925 rc = AudioTestMixStreamInit(&Mix, &DrvStack, pStream, pIoOpts->fWithMixer ? &WaveProps : NULL, 100 /*ms*/);
926 if (RT_SUCCESS(rc))
927 {
928 char szTmp[128];
929 if (g_uVerbosity > 0)
930 RTMsgInfo("Stream: %s cbBackend=%#RX32%s\n",
931 PDMAudioPropsToString(&pStream->Cfg.Props, szTmp, sizeof(szTmp)),
932 pStream->cbBackend, pIoOpts->fWithMixer ? " mixed" : "");
933
934 /*
935 * Open the wave output file.
936 */
937 AUDIOTESTWAVEFILE WaveFile;
938 RTERRINFOSTATIC ErrInfo;
939 rc = AudioTestWaveFileCreate(pszFile, &WaveProps, &WaveFile, RTErrInfoInitStatic(&ErrInfo));
940 if (RT_SUCCESS(rc))
941 {
942 if (g_uVerbosity > 0)
943 {
944 RTMsgInfo("Opened '%s' for playing\n", pszFile);
945 RTMsgInfo("Format: %s\n", PDMAudioPropsToString(&WaveFile.Props, szTmp, sizeof(szTmp)));
946 }
947
948 /*
949 * Enable the stream and start recording.
950 */
951 rc = AudioTestMixStreamEnable(&Mix);
952 if (RT_SUCCESS(rc))
953 rcExit = audioTestRecOneInner(&Mix, &WaveFile, &CfgAcq, cMaxFrames, pszFile);
954 else
955 rcExit = RTMsgErrorExitFailure("Enabling the input stream failed: %Rrc", rc);
956 if (rcExit != RTEXITCODE_SUCCESS)
957 AudioTestMixStreamDisable(&Mix);
958
959 /*
960 * Clean up.
961 */
962 rc = AudioTestWaveFileClose(&WaveFile);
963 if (RT_FAILURE(rc))
964 rcExit = RTMsgErrorExitFailure("Error closing '%s': %Rrc", pszFile, rc);
965 }
966 else
967 rcExit = RTMsgErrorExitFailure("Failed to open '%s': %Rrc%#RTeim", pszFile, rc, &ErrInfo.Core.pszMsg);
968
969 AudioTestMixStreamTerm(&Mix);
970 }
971 audioTestDriverStackStreamDestroy(&DrvStack, pStream);
972 pStream = NULL;
973 }
974 else
975 rcExit = RTMsgErrorExitFailure("Creating output stream failed: %Rrc", rc);
976 }
977 else
978 rcExit = RTMsgErrorExitFailure("Failed to set output device to '%s': %Rrc", pszDevId, rc);
979 audioTestDriverStackDelete(&DrvStack);
980 }
981 else
982 rcExit = RTMsgErrorExitFailure("Driver stack construction failed: %Rrc", rc);
983 return rcExit;
984}
985
986
987/**
988 * Options for 'rec'.
989 */
990static const RTGETOPTDEF g_aCmdRecOptions[] =
991{
992 { "--backend", 'b', RTGETOPT_REQ_STRING },
993 { "--channels", 'c', RTGETOPT_REQ_UINT8 },
994 { "--hz", 'f', RTGETOPT_REQ_UINT32 },
995 { "--frequency", 'f', RTGETOPT_REQ_UINT32 },
996 { "--sample-size", 'z', RTGETOPT_REQ_UINT8 },
997 { "--input-device", 'i', RTGETOPT_REQ_STRING },
998 { "--wav-channels", 'C', RTGETOPT_REQ_UINT8 },
999 { "--wav-hz", 'F', RTGETOPT_REQ_UINT32 },
1000 { "--wav-frequency", 'F', RTGETOPT_REQ_UINT32 },
1001 { "--wav-sample-size", 'Z', RTGETOPT_REQ_UINT8 },
1002 { "--with-drv-audio", 'd', RTGETOPT_REQ_NOTHING },
1003 { "--with-mixer", 'm', RTGETOPT_REQ_NOTHING },
1004 { "--max-frames", 'r', RTGETOPT_REQ_UINT64 },
1005 { "--max-sec", 's', RTGETOPT_REQ_UINT64 },
1006 { "--max-seconds", 's', RTGETOPT_REQ_UINT64 },
1007 { "--max-ms", 't', RTGETOPT_REQ_UINT64 },
1008 { "--max-milliseconds", 't', RTGETOPT_REQ_UINT64 },
1009 { "--max-ns", 'T', RTGETOPT_REQ_UINT64 },
1010 { "--max-nanoseconds", 'T', RTGETOPT_REQ_UINT64 },
1011};
1012
1013
1014/** The 'rec' command option help. */
1015static DECLCALLBACK(const char *) audioTestCmdRecHelp(PCRTGETOPTDEF pOpt)
1016{
1017 switch (pOpt->iShort)
1018 {
1019 case 'b': return "The audio backend to use.";
1020 case 'c': return "Number of backend input channels";
1021 case 'C': return "Number of wave-file channels";
1022 case 'd': return "Go via DrvAudio instead of directly interfacing with the backend.";
1023 case 'f': return "Input frequency (Hz)";
1024 case 'F': return "Wave-file frequency (Hz)";
1025 case 'z': return "Input sample size (bits)";
1026 case 'Z': return "Wave-file sample size (bits)";
1027 case 'm': return "Go via the mixer.";
1028 case 'i': return "The ID of the input device to use.";
1029 case 'r': return "Max recording duration in frames.";
1030 case 's': return "Max recording duration in seconds.";
1031 case 't': return "Max recording duration in milliseconds.";
1032 case 'T': return "Max recording duration in nanoseconds.";
1033 default: return NULL;
1034 }
1035}
1036
1037
1038/**
1039 * The 'rec' command handler.
1040 *
1041 * @returns Program exit code.
1042 * @param pGetState RTGetOpt state.
1043 */
1044static DECLCALLBACK(RTEXITCODE) audioTestCmdRecHandler(PRTGETOPTSTATE pGetState)
1045{
1046 /* Option values: */
1047 PCPDMDRVREG pDrvReg = AudioTestGetDefaultBackend();
1048 const char *pszDevId = NULL;
1049 uint8_t cbSample = 0;
1050 uint8_t cChannels = 0;
1051 uint32_t uHz = 0;
1052 uint8_t cbWaveSample = 0;
1053 uint8_t cWaveChannels = 0;
1054 uint32_t uWaveHz = 0;
1055 uint64_t cMaxFrames = UINT64_MAX;
1056 uint64_t cNsMaxDuration = UINT64_MAX;
1057
1058 AUDIOTESTIOOPTS IoOpts;
1059 audioTestIoOptsInitDefaults(&IoOpts);
1060
1061 /* Argument processing loop: */
1062 int ch;
1063 RTGETOPTUNION ValueUnion;
1064 while ((ch = RTGetOpt(pGetState, &ValueUnion)) != 0)
1065 {
1066 switch (ch)
1067 {
1068 case 'b':
1069 pDrvReg = AudioTestFindBackendOpt(ValueUnion.psz);
1070 if (pDrvReg == NULL)
1071 return RTEXITCODE_SYNTAX;
1072 break;
1073
1074 case 'c':
1075 cChannels = ValueUnion.u8;
1076 break;
1077
1078 case 'C':
1079 cWaveChannels = ValueUnion.u8;
1080 break;
1081
1082 case 'd':
1083 IoOpts.fWithDrvAudio = true;
1084 break;
1085
1086 case 'f':
1087 uHz = ValueUnion.u32;
1088 break;
1089
1090 case 'F':
1091 uWaveHz = ValueUnion.u32;
1092 break;
1093
1094 case 'i':
1095 pszDevId = ValueUnion.psz;
1096 break;
1097
1098 case 'm':
1099 IoOpts.fWithMixer = true;
1100 break;
1101
1102 case 'r':
1103 cMaxFrames = ValueUnion.u64;
1104 break;
1105
1106 case 's':
1107 cNsMaxDuration = ValueUnion.u64 >= UINT64_MAX / RT_NS_1SEC ? UINT64_MAX : ValueUnion.u64 * RT_NS_1SEC;
1108 break;
1109
1110 case 't':
1111 cNsMaxDuration = ValueUnion.u64 >= UINT64_MAX / RT_NS_1MS ? UINT64_MAX : ValueUnion.u64 * RT_NS_1MS;
1112 break;
1113
1114 case 'T':
1115 cNsMaxDuration = ValueUnion.u64;
1116 break;
1117
1118 case 'z':
1119 cbSample = ValueUnion.u8 / 8;
1120 break;
1121
1122 case 'Z':
1123 cbWaveSample = ValueUnion.u8 / 8;
1124 break;
1125
1126 case VINF_GETOPT_NOT_OPTION:
1127 {
1128 if ( cbSample
1129 || cChannels
1130 || uHz)
1131 {
1132 /* Set new (override standard) I/O PCM properties if set by the user. */
1133 PDMAudioPropsInit(&IoOpts.Props,
1134 cbSample ? cbSample : 2 /* 16-bit */, true /* fSigned */,
1135 cChannels ? cChannels : 2 /* Stereo */, uHz ? uHz : 44100);
1136 }
1137
1138 RTEXITCODE rcExit = audioTestRecOne(ValueUnion.psz, cWaveChannels, cbWaveSample, uWaveHz,
1139 pDrvReg, pszDevId, &IoOpts,
1140 cMaxFrames, cNsMaxDuration);
1141 if (rcExit != RTEXITCODE_SUCCESS)
1142 return rcExit;
1143 break;
1144 }
1145
1146 AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion, &g_CmdRec);
1147
1148 default:
1149 return RTGetOptPrintError(ch, &ValueUnion);
1150 }
1151 }
1152 return RTEXITCODE_SUCCESS;
1153}
1154
1155
1156/**
1157 * Command table entry for 'rec'.
1158 */
1159const VKATCMD g_CmdRec =
1160{
1161 "rec",
1162 audioTestCmdRecHandler,
1163 "Records audio to a wave file.",
1164 g_aCmdRecOptions,
1165 RT_ELEMENTS(g_aCmdRecOptions),
1166 audioTestCmdRecHelp,
1167 false /* fNeedsTransport */
1168};
1169
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