VirtualBox

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

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

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