VirtualBox

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

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

scm --update-copyright-year

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

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