VirtualBox

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

Last change on this file since 89892 was 89643, checked in by vboxsync, 4 years ago

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