VirtualBox

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

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

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