VirtualBox

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

最後變更 在這個檔案從96407是 96407,由 vboxsync 提交於 3 年 前

scm copyright and license note update

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette