VirtualBox

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

Last change on this file since 96608 was 96608, checked in by vboxsync, 2 years ago

ValKit/Audio: Added a 'backends' command for listing available backends. Made --help after a command result in info about just that command rather than loads of irrelevant info about other commands. Likewise, just list the commands when none is given or we don't recognizes the one specified. [scm] 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: 44.2 KB
Line 
1/* $Id: vkatCmdGeneric.cpp 96608 2022-09-05 22:44:36Z vboxsync $ */
2/** @file
3 * Validation Kit Audio Test (VKAT) utility for testing and validating the audio stack.
4 */
5
6/*
7 * Copyright (C) 2021-2022 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, pDrvReg,
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