VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/utils/audio/vkatCommon.cpp@ 90802

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

Audio/ValKit: Decoupled the driver stack from the test environment, so that two test environments can use the same driver stack -- needed for self-tests and also is a cleaner solution [Doxygen fix]. bugref:10008

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 39.3 KB
Line 
1/* $Id: vkatCommon.cpp 90767 2021-08-20 17:33:14Z vboxsync $ */
2/** @file
3 * Validation Kit Audio Test (VKAT) - Self test code.
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
32#include <iprt/ctype.h>
33#include <iprt/dir.h>
34#include <iprt/errcore.h>
35#include <iprt/getopt.h>
36#include <iprt/message.h>
37#include <iprt/rand.h>
38#include <iprt/test.h>
39
40#include "Audio/AudioHlp.h"
41#include "Audio/AudioTest.h"
42#include "Audio/AudioTestService.h"
43#include "Audio/AudioTestServiceClient.h"
44
45#include "vkatInternal.h"
46
47
48/*********************************************************************************************************************************
49* Defined Constants And Macros *
50*********************************************************************************************************************************/
51/**
52 * Structure for keeping a user context for the test service callbacks.
53 */
54typedef struct ATSCALLBACKCTX
55{
56 /** The test environment bound to this context. */
57 PAUDIOTESTENV pTstEnv;
58 /** Absolute path to the packed up test set archive.
59 * Keep it simple for now and only support one (open) archive at a time. */
60 char szTestSetArchive[RTPATH_MAX];
61 /** File handle to the (opened) test set archive for reading. */
62 RTFILE hTestSetArchive;
63} ATSCALLBACKCTX;
64typedef ATSCALLBACKCTX *PATSCALLBACKCTX;
65
66
67/*********************************************************************************************************************************
68* Internal Functions *
69*********************************************************************************************************************************/
70static int audioTestStreamInit(PAUDIOTESTDRVSTACK pDrvStack, PAUDIOTESTSTREAM pStream, PDMAUDIODIR enmDir, PCPDMAUDIOPCMPROPS pProps, bool fWithMixer, uint32_t cMsBufferSize, uint32_t cMsPreBuffer, uint32_t cMsSchedulingHint);
71static int audioTestStreamDestroy(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream);
72
73
74/*********************************************************************************************************************************
75* Device enumeration + handling. *
76*********************************************************************************************************************************/
77
78/**
79 * Enumerates audio devices and optionally searches for a specific device.
80 *
81 * @returns VBox status code.
82 * @param pDrvStack Driver stack to use for enumeration.
83 * @param pszDev Device name to search for. Can be NULL if the default device shall be used.
84 * @param ppDev Where to return the pointer of the device enumeration of \a pTstEnv when a
85 * specific device was found.
86 */
87int audioTestDevicesEnumerateAndCheck(PAUDIOTESTDRVSTACK pDrvStack, const char *pszDev, PPDMAUDIOHOSTDEV *ppDev)
88{
89 RTTestSubF(g_hTest, "Enumerating audio devices and checking for device '%s'", pszDev && *pszDev ? pszDev : "<Default>");
90
91 if (!pDrvStack->pIHostAudio->pfnGetDevices)
92 {
93 RTTestSkipped(g_hTest, "Backend does not support device enumeration, skipping");
94 return VINF_NOT_SUPPORTED;
95 }
96
97 Assert(pszDev == NULL || ppDev);
98
99 if (ppDev)
100 *ppDev = NULL;
101
102 int rc = pDrvStack->pIHostAudio->pfnGetDevices(pDrvStack->pIHostAudio, &pDrvStack->DevEnum);
103 if (RT_SUCCESS(rc))
104 {
105 PPDMAUDIOHOSTDEV pDev;
106 RTListForEach(&pDrvStack->DevEnum.LstDevices, pDev, PDMAUDIOHOSTDEV, ListEntry)
107 {
108 char szFlags[PDMAUDIOHOSTDEV_MAX_FLAGS_STRING_LEN];
109 if (pDev->pszId)
110 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Device '%s' (ID '%s'):\n", pDev->pszName, pDev->pszId);
111 else
112 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Device '%s':\n", pDev->pszName);
113 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Usage = %s\n", PDMAudioDirGetName(pDev->enmUsage));
114 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Flags = %s\n", PDMAudioHostDevFlagsToString(szFlags, pDev->fFlags));
115 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Input channels = %RU8\n", pDev->cMaxInputChannels);
116 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Output channels = %RU8\n", pDev->cMaxOutputChannels);
117
118 if ( (pszDev && *pszDev)
119 && !RTStrCmp(pDev->pszName, pszDev))
120 {
121 *ppDev = pDev;
122 }
123 }
124 }
125 else
126 RTTestFailed(g_hTest, "Enumerating audio devices failed with %Rrc", rc);
127
128 if (RT_SUCCESS(rc))
129 {
130 if ( (pszDev && *pszDev)
131 && *ppDev == NULL)
132 {
133 RTTestFailed(g_hTest, "Audio device '%s' not found", pszDev);
134 rc = VERR_NOT_FOUND;
135 }
136 }
137
138 RTTestSubDone(g_hTest);
139 return rc;
140}
141
142static int audioTestStreamInit(PAUDIOTESTDRVSTACK pDrvStack, PAUDIOTESTSTREAM pStream,
143 PDMAUDIODIR enmDir, PCPDMAUDIOPCMPROPS pProps, bool fWithMixer,
144 uint32_t cMsBufferSize, uint32_t cMsPreBuffer, uint32_t cMsSchedulingHint)
145{
146 int rc;
147
148 if (enmDir == PDMAUDIODIR_IN)
149 rc = audioTestDriverStackStreamCreateInput(pDrvStack, pProps, cMsBufferSize,
150 cMsPreBuffer, cMsSchedulingHint, &pStream->pStream, &pStream->Cfg);
151 else if (enmDir == PDMAUDIODIR_OUT)
152 rc = audioTestDriverStackStreamCreateOutput(pDrvStack, pProps, cMsBufferSize,
153 cMsPreBuffer, cMsSchedulingHint, &pStream->pStream, &pStream->Cfg);
154 else
155 rc = VERR_NOT_SUPPORTED;
156
157 if (RT_SUCCESS(rc))
158 {
159 if (!pDrvStack->pIAudioConnector)
160 {
161 pStream->pBackend = &((PAUDIOTESTDRVSTACKSTREAM)pStream->pStream)->Backend;
162 }
163 else
164 pStream->pBackend = NULL;
165
166 /*
167 * Automatically enable the mixer if the PCM properties don't match.
168 */
169 if ( !fWithMixer
170 && !PDMAudioPropsAreEqual(pProps, &pStream->Cfg.Props))
171 {
172 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enabling stream mixer\n");
173 fWithMixer = true;
174 }
175
176 rc = AudioTestMixStreamInit(&pStream->Mix, pDrvStack, pStream->pStream,
177 fWithMixer ? pProps : NULL, 100 /* ms */); /** @todo Configure mixer buffer? */
178 }
179
180 if (RT_FAILURE(rc))
181 RTTestFailed(g_hTest, "Initializing %s stream failed with %Rrc", enmDir == PDMAUDIODIR_IN ? "input" : "output", rc);
182
183 return rc;
184}
185
186/**
187 * Destroys an audio test stream.
188 *
189 * @returns VBox status code.
190 * @param pTstEnv Test environment the stream to destroy contains.
191 * @param pStream Audio stream to destroy.
192 */
193static int audioTestStreamDestroy(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream)
194{
195 int rc = VINF_SUCCESS;
196 if (pStream && pStream->pStream)
197 {
198 /** @todo Anything else to do here, e.g. test if there are left over samples or some such? */
199
200 audioTestDriverStackStreamDestroy(pTstEnv->pDrvStack, pStream->pStream);
201 pStream->pStream = NULL;
202 pStream->pBackend = NULL;
203 }
204
205 AudioTestMixStreamTerm(&pStream->Mix);
206
207 return rc;
208}
209
210
211/*********************************************************************************************************************************
212* Test Primitives *
213*********************************************************************************************************************************/
214
215#if 0 /* Unused */
216/**
217 * Returns a random scheduling hint (in ms).
218 */
219DECLINLINE(uint32_t) audioTestEnvGetRandomSchedulingHint(void)
220{
221 static const unsigned s_aSchedulingHintsMs[] =
222 {
223 10,
224 25,
225 50,
226 100,
227 200,
228 250
229 };
230
231 return s_aSchedulingHintsMs[RTRandU32Ex(0, RT_ELEMENTS(s_aSchedulingHintsMs) - 1)];
232}
233#endif
234
235/**
236 * Plays a test tone on a specific audio test stream.
237 *
238 * @returns VBox status code.
239 * @param pTstEnv Test environment to use for running the test.
240 * Optional and can be NULL (for simple playback only).
241 * @param pStream Stream to use for playing the tone.
242 * @param pParms Tone parameters to use.
243 *
244 * @note Blocking function.
245 */
246int audioTestPlayTone(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PAUDIOTESTTONEPARMS pParms)
247{
248 AUDIOTESTTONE TstTone;
249 AudioTestToneInit(&TstTone, &pStream->Cfg.Props, pParms->dbFreqHz);
250
251 char const *pcszPathOut = NULL;
252 if (pTstEnv)
253 pcszPathOut = pTstEnv->Set.szPathAbs;
254
255 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Playing test tone (tone frequency is %RU16Hz, %RU32ms)\n", (uint16_t)pParms->dbFreqHz, pParms->msDuration);
256 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Using %RU32ms stream scheduling hint\n", pStream->Cfg.Device.cMsSchedulingHint);
257 if (pcszPathOut)
258 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Writing to '%s'\n", pcszPathOut);
259
260 int rc;
261
262 /** @todo Use .WAV here? */
263 AUDIOTESTOBJ Obj;
264 RT_ZERO(Obj); /* Shut up MSVC. */
265 if (pTstEnv)
266 {
267 rc = AudioTestSetObjCreateAndRegister(&pTstEnv->Set, "guest-tone-play.pcm", &Obj);
268 AssertRCReturn(rc, rc);
269 }
270
271 rc = AudioTestMixStreamEnable(&pStream->Mix);
272 if ( RT_SUCCESS(rc)
273 && AudioTestMixStreamIsOkay(&pStream->Mix))
274 {
275 uint8_t abBuf[_4K];
276
277 uint32_t cbToPlayTotal = PDMAudioPropsMilliToBytes(&pStream->Cfg.Props, pParms->msDuration);
278 AssertStmt(cbToPlayTotal, rc = VERR_INVALID_PARAMETER);
279
280 RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Playing %RU32 bytes total\n", cbToPlayTotal);
281
282 if (pTstEnv)
283 {
284 AudioTestObjAddMetadataStr(Obj, "stream_to_play_bytes=%RU32\n", cbToPlayTotal);
285 AudioTestObjAddMetadataStr(Obj, "stream_period_size_frames=%RU32\n", pStream->Cfg.Backend.cFramesPeriod);
286 AudioTestObjAddMetadataStr(Obj, "stream_buffer_size_frames=%RU32\n", pStream->Cfg.Backend.cFramesBufferSize);
287 AudioTestObjAddMetadataStr(Obj, "stream_prebuf_size_frames=%RU32\n", pStream->Cfg.Backend.cFramesPreBuffering);
288 /* Note: This mostly is provided by backend (e.g. PulseAudio / ALSA / ++) and
289 * has nothing to do with the device emulation scheduling hint. */
290 AudioTestObjAddMetadataStr(Obj, "device_scheduling_hint_ms=%RU32\n", pStream->Cfg.Device.cMsSchedulingHint);
291 }
292
293 PAUDIOTESTDRVMIXSTREAM pMix = &pStream->Mix;
294
295 uint32_t const cbPreBuffer = PDMAudioPropsFramesToBytes(pMix->pProps, pStream->Cfg.Backend.cFramesPreBuffering);
296 uint64_t const nsStarted = RTTimeNanoTS();
297 uint64_t nsDonePreBuffering = 0;
298
299 uint64_t offStream = 0;
300
301 while (cbToPlayTotal)
302 {
303 /* Pace ourselves a little. */
304 if (offStream >= cbPreBuffer)
305 {
306 if (!nsDonePreBuffering)
307 nsDonePreBuffering = RTTimeNanoTS();
308 uint64_t const cNsWritten = PDMAudioPropsBytesToNano64(pMix->pProps, offStream - cbPreBuffer);
309 uint64_t const cNsElapsed = RTTimeNanoTS() - nsStarted;
310 if (cNsWritten > cNsElapsed + RT_NS_10MS)
311 RTThreadSleep((cNsWritten - cNsElapsed - RT_NS_10MS / 2) / RT_NS_1MS);
312 }
313
314 uint32_t cbPlayed = 0;
315 uint32_t const cbCanWrite = AudioTestMixStreamGetWritable(&pStream->Mix);
316 if (cbCanWrite)
317 {
318 uint32_t const cbToGenerate = RT_MIN(RT_MIN(cbToPlayTotal, sizeof(abBuf)), cbCanWrite);
319 uint32_t cbToPlay;
320 rc = AudioTestToneGenerate(&TstTone, abBuf, cbToGenerate, &cbToPlay);
321 if (RT_SUCCESS(rc))
322 {
323 if (pTstEnv)
324 {
325 /* Write stuff to disk before trying to play it. Help analysis later. */
326 rc = AudioTestObjWrite(Obj, abBuf, cbToPlay);
327 }
328 if (RT_SUCCESS(rc))
329 {
330 rc = AudioTestMixStreamPlay(&pStream->Mix, abBuf, cbToPlay, &cbPlayed);
331 if (RT_SUCCESS(rc))
332 {
333 offStream += cbPlayed;
334 }
335 }
336 }
337
338 if (RT_FAILURE(rc))
339 break;
340 }
341 else if (AudioTestMixStreamIsOkay(&pStream->Mix))
342 RTThreadSleep(RT_MIN(RT_MAX(1, pStream->Cfg.Device.cMsSchedulingHint), 256));
343 else
344 AssertFailedBreakStmt(rc = VERR_AUDIO_STREAM_NOT_READY);
345
346 Assert(cbToPlayTotal >= cbPlayed);
347 cbToPlayTotal -= cbPlayed;
348 }
349
350 if (RT_SUCCESS(rc))
351 rc = AudioTestMixStreamDrain(&pStream->Mix, true /*fSync*/);
352
353 if (cbToPlayTotal != 0)
354 RTTestFailed(g_hTest, "Playback ended unexpectedly (%RU32 bytes left)\n", cbToPlayTotal);
355 }
356 else
357 rc = VERR_AUDIO_STREAM_NOT_READY;
358
359 if (pTstEnv)
360 {
361 int rc2 = AudioTestObjClose(Obj);
362 if (RT_SUCCESS(rc))
363 rc = rc2;
364 }
365
366 if (RT_FAILURE(rc))
367 RTTestFailed(g_hTest, "Playing tone failed with %Rrc\n", rc);
368
369 return rc;
370}
371
372/**
373 * Records a test tone from a specific audio test stream.
374 *
375 * @returns VBox status code.
376 * @param pTstEnv Test environment to use for running the test.
377 * @param pStream Stream to use for recording the tone.
378 * @param pParms Tone parameters to use.
379 *
380 * @note Blocking function.
381 */
382static int audioTestRecordTone(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PAUDIOTESTTONEPARMS pParms)
383{
384 const char *pcszPathOut = pTstEnv->Set.szPathAbs;
385
386 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Recording test tone (tone frequency is %RU16Hz, %RU32ms)\n", (uint16_t)pParms->dbFreqHz, pParms->msDuration);
387 RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Writing to '%s'\n", pcszPathOut);
388
389 /** @todo Use .WAV here? */
390 AUDIOTESTOBJ Obj;
391 int rc = AudioTestSetObjCreateAndRegister(&pTstEnv->Set, "guest-tone-rec.pcm", &Obj);
392 AssertRCReturn(rc, rc);
393
394 PAUDIOTESTDRVMIXSTREAM pMix = &pStream->Mix;
395
396 rc = AudioTestMixStreamEnable(pMix);
397 if (RT_SUCCESS(rc))
398 {
399 uint64_t cbToRecTotal = PDMAudioPropsMilliToBytes(&pStream->Cfg.Props, pParms->msDuration);
400
401 RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Recording %RU32 bytes total\n", cbToRecTotal);
402
403 AudioTestObjAddMetadataStr(Obj, "stream_to_record_bytes=%RU32\n", cbToRecTotal);
404 AudioTestObjAddMetadataStr(Obj, "stream_buffer_size_ms=%RU32\n", pTstEnv->cMsBufferSize);
405 AudioTestObjAddMetadataStr(Obj, "stream_prebuf_size_ms=%RU32\n", pTstEnv->cMsPreBuffer);
406 /* Note: This mostly is provided by backend (e.g. PulseAudio / ALSA / ++) and
407 * has nothing to do with the device emulation scheduling hint. */
408 AudioTestObjAddMetadataStr(Obj, "device_scheduling_hint_ms=%RU32\n", pTstEnv->cMsSchedulingHint);
409
410 uint8_t abSamples[16384];
411 uint32_t const cbSamplesAligned = PDMAudioPropsFloorBytesToFrame(pMix->pProps, sizeof(abSamples));
412 uint64_t cbRecTotal = 0;
413 while (!g_fTerminate && cbRecTotal < cbToRecTotal)
414 {
415 /*
416 * Anything we can read?
417 */
418 uint32_t const cbCanRead = AudioTestMixStreamGetReadable(pMix);
419 if (cbCanRead)
420 {
421 uint32_t const cbToRead = RT_MIN(cbCanRead, cbSamplesAligned);
422 uint32_t cbRecorded = 0;
423 rc = AudioTestMixStreamCapture(pMix, abSamples, cbToRead, &cbRecorded);
424 if (RT_SUCCESS(rc))
425 {
426 if (cbRecorded)
427 {
428 rc = AudioTestObjWrite(Obj, abSamples, cbRecorded);
429 if (RT_SUCCESS(rc))
430 {
431 cbRecTotal += cbRecorded;
432
433 /** @todo Clamp result? */
434 }
435 }
436 }
437 }
438 else if (AudioTestMixStreamIsOkay(pMix))
439 RTThreadSleep(RT_MIN(RT_MAX(1, pTstEnv->cMsSchedulingHint), 256));
440
441 if (RT_FAILURE(rc))
442 break;
443 }
444
445 int rc2 = AudioTestMixStreamDisable(pMix);
446 if (RT_SUCCESS(rc))
447 rc = rc2;
448 }
449
450 int rc2 = AudioTestObjClose(Obj);
451 if (RT_SUCCESS(rc))
452 rc = rc2;
453
454 if (RT_FAILURE(rc))
455 RTTestFailed(g_hTest, "Recording tone done failed with %Rrc\n", rc);
456
457 return rc;
458}
459
460
461/*********************************************************************************************************************************
462* ATS Callback Implementations *
463*********************************************************************************************************************************/
464
465/** @copydoc ATSCALLBACKS::pfnTestSetBegin
466 *
467 * @note Runs as part of the guest ATS.
468 */
469static DECLCALLBACK(int) audioTestGstAtsTestSetBeginCallback(void const *pvUser, const char *pszTag)
470{
471 PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser;
472 PAUDIOTESTENV pTstEnv = pCtx->pTstEnv;
473
474 RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Beginning test set '%s' in '%s'\n", pszTag, pTstEnv->szPathTemp);
475
476 return AudioTestSetCreate(&pTstEnv->Set, pTstEnv->szPathTemp, pszTag);
477}
478
479/** @copydoc ATSCALLBACKS::pfnTestSetEnd
480 *
481 * @note Runs as part of the guest ATS.
482 */
483static DECLCALLBACK(int) audioTestGstAtsTestSetEndCallback(void const *pvUser, const char *pszTag)
484{
485 PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser;
486 PAUDIOTESTENV pTstEnv = pCtx->pTstEnv;
487
488 RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Ending test set '%s'\n", pszTag);
489
490 /* Pack up everything to be ready for transmission. */
491 return audioTestEnvPrologue(pTstEnv, true /* fPack */, pCtx->szTestSetArchive, sizeof(pCtx->szTestSetArchive));
492}
493
494/** @copydoc ATSCALLBACKS::pfnTonePlay
495 *
496 * @note Runs as part of the guest ATS.
497 */
498static DECLCALLBACK(int) audioTestGstAtsTonePlayCallback(void const *pvUser, PAUDIOTESTTONEPARMS pToneParms)
499{
500 PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser;
501 PAUDIOTESTENV pTstEnv = pCtx->pTstEnv;
502
503 const PAUDIOTESTSTREAM pTstStream = &pTstEnv->aStreams[0]; /** @todo Make this dynamic. */
504
505 int rc = audioTestStreamInit(pTstEnv->pDrvStack, pTstStream, PDMAUDIODIR_OUT, &pTstEnv->Props, false /* fWithMixer */,
506 pTstEnv->cMsBufferSize, pTstEnv->cMsPreBuffer, pTstEnv->cMsSchedulingHint);
507 if (RT_SUCCESS(rc))
508 {
509 AUDIOTESTPARMS TstParms;
510 RT_ZERO(TstParms);
511 TstParms.enmType = AUDIOTESTTYPE_TESTTONE_PLAY;
512 TstParms.enmDir = PDMAUDIODIR_OUT;
513 TstParms.TestTone = *pToneParms;
514
515 PAUDIOTESTENTRY pTst;
516 rc = AudioTestSetTestBegin(&pTstEnv->Set, "Playing test tone", &TstParms, &pTst);
517 if (RT_SUCCESS(rc))
518 {
519 rc = audioTestPlayTone(pTstEnv, pTstStream, pToneParms);
520 if (RT_SUCCESS(rc))
521 {
522 AudioTestSetTestDone(pTst);
523 }
524 else
525 AudioTestSetTestFailed(pTst, rc, "Playing tone failed");
526 }
527
528 int rc2 = audioTestStreamDestroy(pTstEnv, pTstStream);
529 if (RT_SUCCESS(rc))
530 rc = rc2;
531 }
532 else
533 RTTestFailed(g_hTest, "Error creating output stream, rc=%Rrc\n", rc);
534
535 return rc;
536}
537
538/** @copydoc ATSCALLBACKS::pfnToneRecord */
539static DECLCALLBACK(int) audioTestGstAtsToneRecordCallback(void const *pvUser, PAUDIOTESTTONEPARMS pToneParms)
540{
541 PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser;
542 PAUDIOTESTENV pTstEnv = pCtx->pTstEnv;
543
544 const PAUDIOTESTSTREAM pTstStream = &pTstEnv->aStreams[0]; /** @todo Make this dynamic. */
545
546 int rc = audioTestStreamInit(pTstEnv->pDrvStack, pTstStream, PDMAUDIODIR_IN, &pTstEnv->Props, false /* fWithMixer */,
547 pTstEnv->cMsBufferSize, pTstEnv->cMsPreBuffer, pTstEnv->cMsSchedulingHint);
548 if (RT_SUCCESS(rc))
549 {
550 AUDIOTESTPARMS TstParms;
551 RT_ZERO(TstParms);
552 TstParms.enmType = AUDIOTESTTYPE_TESTTONE_RECORD;
553 TstParms.enmDir = PDMAUDIODIR_IN;
554 TstParms.Props = pToneParms->Props;
555 TstParms.TestTone = *pToneParms;
556
557 PAUDIOTESTENTRY pTst;
558 rc = AudioTestSetTestBegin(&pTstEnv->Set, "Recording test tone from host", &TstParms, &pTst);
559 if (RT_SUCCESS(rc))
560 {
561 rc = audioTestRecordTone(pTstEnv, pTstStream, pToneParms);
562 if (RT_SUCCESS(rc))
563 {
564 AudioTestSetTestDone(pTst);
565 }
566 else
567 AudioTestSetTestFailed(pTst, rc, "Recording tone failed");
568 }
569
570 int rc2 = audioTestStreamDestroy(pTstEnv, pTstStream);
571 if (RT_SUCCESS(rc))
572 rc = rc2;
573 }
574 else
575 RTTestFailed(g_hTest, "Error creating input stream, rc=%Rrc\n", rc);
576
577 return rc;
578}
579
580/** @copydoc ATSCALLBACKS::pfnTestSetSendBegin */
581static DECLCALLBACK(int) audioTestGstAtsTestSetSendBeginCallback(void const *pvUser, const char *pszTag)
582{
583 RT_NOREF(pszTag);
584
585 PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser;
586
587 if (!RTFileExists(pCtx->szTestSetArchive)) /* Has the archive successfully been created yet? */
588 return VERR_WRONG_ORDER;
589
590 int rc = RTFileOpen(&pCtx->hTestSetArchive, pCtx->szTestSetArchive, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
591 if (RT_SUCCESS(rc))
592 {
593 uint64_t uSize;
594 rc = RTFileQuerySize(pCtx->hTestSetArchive, &uSize);
595 if (RT_SUCCESS(rc))
596 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Sending test set '%s' (%zu bytes)\n", pCtx->szTestSetArchive, uSize);
597 }
598
599 return rc;
600}
601
602/** @copydoc ATSCALLBACKS::pfnTestSetSendRead */
603static DECLCALLBACK(int) audioTestGstAtsTestSetSendReadCallback(void const *pvUser,
604 const char *pszTag, void *pvBuf, size_t cbBuf, size_t *pcbRead)
605{
606 RT_NOREF(pszTag);
607
608 PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser;
609
610 return RTFileRead(pCtx->hTestSetArchive, pvBuf, cbBuf, pcbRead);
611}
612
613/** @copydoc ATSCALLBACKS::pfnTestSetSendEnd */
614static DECLCALLBACK(int) audioTestGstAtsTestSetSendEndCallback(void const *pvUser, const char *pszTag)
615{
616 RT_NOREF(pszTag);
617
618 PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser;
619
620 int rc = RTFileClose(pCtx->hTestSetArchive);
621 if (RT_SUCCESS(rc))
622 {
623 pCtx->hTestSetArchive = NIL_RTFILE;
624 }
625
626 return rc;
627}
628
629
630/*********************************************************************************************************************************
631* Implementation of audio test environment handling *
632*********************************************************************************************************************************/
633
634/**
635 * Connects an ATS client via TCP/IP to a peer.
636 *
637 * @returns VBox status code.
638 * @param pTstEnv Test environment to use.
639 * @param pClient Client to connect.
640 * @param pszWhat Hint of what to connect to where.
641 * @param pszTcpBindAddr TCP/IP bind address. Optionl and can be NULL.
642 * Server mode will be disabled then.
643 * @param uTcpBindPort TCP/IP bind port. Optionl and can be 0.
644 * Server mode will be disabled then. *
645 * @param pszTcpConnectAddr TCP/IP connect address. Optionl and can be NULL.
646 * Client mode will be disabled then.
647 * @param uTcpConnectPort TCP/IP connect port. Optionl and can be 0.
648 * Client mode will be disabled then.
649 */
650int audioTestEnvConnectViaTcp(PAUDIOTESTENV pTstEnv, PATSCLIENT pClient, const char *pszWhat,
651 const char *pszTcpBindAddr, uint16_t uTcpBindPort,
652 const char *pszTcpConnectAddr, uint16_t uTcpConnectPort)
653{
654 RT_NOREF(pTstEnv);
655
656 RTGETOPTUNION Val;
657 RT_ZERO(Val);
658
659 int rc;
660
661 if ( !pszTcpBindAddr
662 || !uTcpBindPort)
663 {
664 Val.psz = "client";
665 }
666 else if ( !pszTcpConnectAddr
667 || !uTcpConnectPort)
668 {
669 Val.psz = "server";
670 }
671 else
672 Val.psz = "both";
673
674 rc = AudioTestSvcClientHandleOption(pClient, ATSTCPOPT_MODE, &Val);
675 AssertRCReturn(rc, rc);
676
677 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Connecting %s (connection mode '%s') ...\n", pszWhat, Val.psz);
678
679 if ( !RTStrCmp(Val.psz, "client")
680 || !RTStrCmp(Val.psz, "both"))
681 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Connecting to %s:%RU32\n", pszTcpConnectAddr, uTcpConnectPort);
682
683 if ( !RTStrCmp(Val.psz, "server")
684 || !RTStrCmp(Val.psz, "both"))
685 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Listening at %s:%RU32\n", pszTcpBindAddr ? pszTcpBindAddr : "<None>", uTcpBindPort);
686
687 if (pszTcpBindAddr)
688 {
689 Val.psz = pszTcpBindAddr;
690 rc = AudioTestSvcClientHandleOption(pClient, ATSTCPOPT_BIND_ADDRESS, &Val);
691 AssertRCReturn(rc, rc);
692 }
693
694 if (uTcpBindPort)
695 {
696 Val.u16 = uTcpBindPort;
697 rc = AudioTestSvcClientHandleOption(pClient, ATSTCPOPT_BIND_PORT, &Val);
698 AssertRCReturn(rc, rc);
699 }
700
701 if (pszTcpConnectAddr)
702 {
703 Val.psz = pszTcpConnectAddr;
704 rc = AudioTestSvcClientHandleOption(pClient, ATSTCPOPT_CONNECT_ADDRESS, &Val);
705 AssertRCReturn(rc, rc);
706 }
707
708 if (uTcpConnectPort)
709 {
710 Val.u16 = uTcpConnectPort;
711 rc = AudioTestSvcClientHandleOption(pClient, ATSTCPOPT_CONNECT_PORT, &Val);
712 AssertRCReturn(rc, rc);
713 }
714
715 rc = AudioTestSvcClientConnect(pClient);
716 if (RT_FAILURE(rc))
717 {
718 RTTestFailed(g_hTest, "Connecting %s failed with %Rrc\n", pszWhat, rc);
719 return rc;
720 }
721
722 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Successfully connected %s\n", pszWhat);
723 return rc;
724}
725
726/**
727 * Configures and starts an ATS TCP/IP server.
728 *
729 * @returns VBox status code.
730 * @param pSrv ATS server instance to configure and start.
731 * @param pCallbacks ATS callback table to use.
732 * @param pszDesc Hint of server type which is being started.
733 * @param pszTcpBindAddr TCP/IP bind address. Optionl and can be NULL.
734 * Server mode will be disabled then.
735 * @param uTcpBindPort TCP/IP bind port. Optionl and can be 0.
736 * Server mode will be disabled then. *
737 * @param pszTcpConnectAddr TCP/IP connect address. Optionl and can be NULL.
738 * Client mode will be disabled then.
739 * @param uTcpConnectPort TCP/IP connect port. Optionl and can be 0.
740 * Client mode will be disabled then.
741 */
742int audioTestEnvConfigureAndStartTcpServer(PATSSERVER pSrv, PCATSCALLBACKS pCallbacks, const char *pszDesc,
743 const char *pszTcpBindAddr, uint16_t uTcpBindPort,
744 const char *pszTcpConnectAddr, uint16_t uTcpConnectPort)
745{
746 RTGETOPTUNION Val;
747 RT_ZERO(Val);
748
749 if (pszTcpBindAddr)
750 {
751 Val.psz = pszTcpBindAddr;
752 AudioTestSvcHandleOption(pSrv, ATSTCPOPT_BIND_ADDRESS, &Val);
753 }
754
755 if (uTcpBindPort)
756 {
757 Val.u16 = uTcpBindPort;
758 AudioTestSvcHandleOption(pSrv, ATSTCPOPT_BIND_PORT, &Val);
759 }
760
761 if (pszTcpConnectAddr)
762 {
763 Val.psz = pszTcpConnectAddr;
764 AudioTestSvcHandleOption(pSrv, ATSTCPOPT_CONNECT_ADDRESS, &Val);
765 }
766
767 if (uTcpConnectPort)
768 {
769 Val.u16 = uTcpConnectPort;
770 AudioTestSvcHandleOption(pSrv, ATSTCPOPT_CONNECT_PORT, &Val);
771 }
772
773 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Starting server for %s at %s:%RU32 ...\n",
774 pszDesc, pszTcpBindAddr[0] ? pszTcpBindAddr : "0.0.0.0", uTcpBindPort);
775 if (pszTcpConnectAddr[0])
776 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Trying %s to connect as client to %s:%RU32 ...\n",
777 pszDesc, pszTcpConnectAddr[0] ? pszTcpConnectAddr : "0.0.0.0", uTcpConnectPort);
778
779 int rc = AudioTestSvcInit(pSrv, pCallbacks);
780 if (RT_SUCCESS(rc))
781 rc = AudioTestSvcStart(pSrv);
782
783 if (RT_FAILURE(rc))
784 RTTestFailed(g_hTest, "Starting server for %s failed with %Rrc\n", pszDesc, rc);
785
786 return rc;
787}
788
789/**
790 * Initializes an audio test environment.
791 *
792 * @returns VBox status code.
793 * @param pTstEnv Audio test environment to initialize.
794 * @param pDrvStack Driver stack to use.
795 */
796int audioTestEnvInit(PAUDIOTESTENV pTstEnv, PAUDIOTESTDRVSTACK pDrvStack)
797{
798 int rc = VINF_SUCCESS;
799
800 pTstEnv->pDrvStack = pDrvStack;
801
802 /*
803 * Set sane defaults if not already set.
804 */
805 if (!RTStrNLen(pTstEnv->szTag, sizeof(pTstEnv->szTag)))
806 {
807 rc = AudioTestGenTag(pTstEnv->szTag, sizeof(pTstEnv->szTag));
808 AssertRCReturn(rc, rc);
809 }
810
811 if (!RTStrNLen(pTstEnv->szPathTemp, sizeof(pTstEnv->szPathTemp)))
812 {
813 rc = AudioTestPathGetTemp(pTstEnv->szPathTemp, sizeof(pTstEnv->szPathTemp));
814 AssertRCReturn(rc, rc);
815 }
816
817 if (!RTStrNLen(pTstEnv->szPathOut, sizeof(pTstEnv->szPathOut)))
818 {
819 rc = RTPathJoin(pTstEnv->szPathOut, sizeof(pTstEnv->szPathOut), pTstEnv->szPathTemp, "vkat-temp");
820 AssertRCReturn(rc, rc);
821 }
822
823 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Initializing environment for mode '%s'\n", pTstEnv->enmMode == AUDIOTESTMODE_HOST ? "host" : "guest");
824 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Using tag '%s'\n", pTstEnv->szTag);
825 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Output directory is '%s'\n", pTstEnv->szPathOut);
826 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Temp directory is '%s'\n", pTstEnv->szPathTemp);
827
828 if (!pTstEnv->cMsBufferSize)
829 pTstEnv->cMsBufferSize = UINT32_MAX;
830 if (!pTstEnv->cMsPreBuffer)
831 pTstEnv->cMsPreBuffer = UINT32_MAX;
832 if (!pTstEnv->cMsSchedulingHint)
833 pTstEnv->cMsSchedulingHint = UINT32_MAX;
834
835 char szPathTemp[RTPATH_MAX];
836 if ( !strlen(pTstEnv->szPathTemp)
837 || !strlen(pTstEnv->szPathOut))
838 rc = RTPathTemp(szPathTemp, sizeof(szPathTemp));
839
840 if ( RT_SUCCESS(rc)
841 && !strlen(pTstEnv->szPathTemp))
842 rc = RTPathJoin(pTstEnv->szPathTemp, sizeof(pTstEnv->szPathTemp), szPathTemp, "vkat-temp");
843
844 if (RT_SUCCESS(rc))
845 {
846 rc = RTDirCreate(pTstEnv->szPathTemp, RTFS_UNIX_IRWXU, 0 /* fFlags */);
847 if (rc == VERR_ALREADY_EXISTS)
848 rc = VINF_SUCCESS;
849 }
850
851 if ( RT_SUCCESS(rc)
852 && !strlen(pTstEnv->szPathOut))
853 rc = RTPathJoin(pTstEnv->szPathOut, sizeof(pTstEnv->szPathOut), szPathTemp, "vkat");
854
855 if (RT_SUCCESS(rc))
856 {
857 rc = RTDirCreate(pTstEnv->szPathOut, RTFS_UNIX_IRWXU, 0 /* fFlags */);
858 if (rc == VERR_ALREADY_EXISTS)
859 rc = VINF_SUCCESS;
860 }
861
862 if (RT_FAILURE(rc))
863 return rc;
864
865 if (pTstEnv->enmMode == AUDIOTESTMODE_GUEST)
866 {
867 ATSCALLBACKCTX Ctx;
868 Ctx.pTstEnv = pTstEnv;
869
870 ATSCALLBACKS Callbacks;
871 RT_ZERO(Callbacks);
872 Callbacks.pfnTestSetBegin = audioTestGstAtsTestSetBeginCallback;
873 Callbacks.pfnTestSetEnd = audioTestGstAtsTestSetEndCallback;
874 Callbacks.pfnTonePlay = audioTestGstAtsTonePlayCallback;
875 Callbacks.pfnToneRecord = audioTestGstAtsToneRecordCallback;
876 Callbacks.pfnTestSetSendBegin = audioTestGstAtsTestSetSendBeginCallback;
877 Callbacks.pfnTestSetSendRead = audioTestGstAtsTestSetSendReadCallback;
878 Callbacks.pfnTestSetSendEnd = audioTestGstAtsTestSetSendEndCallback;
879 Callbacks.pvUser = &Ctx;
880
881 if (!pTstEnv->u.Guest.TcpOpts.uTcpBindPort)
882 pTstEnv->u.Guest.TcpOpts.uTcpBindPort = ATS_TCP_DEF_BIND_PORT_GUEST;
883
884 if (!pTstEnv->u.Guest.TcpOpts.szTcpBindAddr[0])
885 RTStrCopy(pTstEnv->u.Guest.TcpOpts.szTcpBindAddr, sizeof(pTstEnv->u.Guest.TcpOpts.szTcpBindAddr), "0.0.0.0");
886
887 if (!pTstEnv->u.Guest.TcpOpts.uTcpConnectPort)
888 pTstEnv->u.Guest.TcpOpts.uTcpConnectPort = ATS_TCP_DEF_CONNECT_PORT_GUEST;
889
890 if (!pTstEnv->u.Guest.TcpOpts.szTcpConnectAddr[0])
891 RTStrCopy(pTstEnv->u.Guest.TcpOpts.szTcpConnectAddr, sizeof(pTstEnv->u.Guest.TcpOpts.szTcpConnectAddr), "10.0.2.2");
892
893 /*
894 * Start the ATS (Audio Test Service) on the guest side.
895 * That service then will perform playback and recording operations on the guest, triggered from the host.
896 *
897 * When running this in self-test mode, that service also can be run on the host if nothing else is specified.
898 * Note that we have to bind to "0.0.0.0" by default so that the host can connect to it.
899 */
900 rc = audioTestEnvConfigureAndStartTcpServer(&pTstEnv->u.Guest.Srv, &Callbacks, "Guest ATS",
901 pTstEnv->u.Guest.TcpOpts.szTcpBindAddr, pTstEnv->u.Guest.TcpOpts.uTcpBindPort,
902 pTstEnv->u.Guest.TcpOpts.szTcpConnectAddr, pTstEnv->u.Guest.TcpOpts.uTcpConnectPort);
903
904 }
905 else /* Host mode */
906 {
907
908 ATSCALLBACKCTX Ctx;
909 Ctx.pTstEnv = pTstEnv;
910
911 ATSCALLBACKS Callbacks;
912 RT_ZERO(Callbacks);
913 Callbacks.pvUser = &Ctx;
914
915 if (!pTstEnv->u.Host.TcpOpts.uTcpBindPort)
916 pTstEnv->u.Host.TcpOpts.uTcpBindPort = ATS_TCP_DEF_BIND_PORT_HOST;
917
918 if (!pTstEnv->u.Host.TcpOpts.szTcpBindAddr[0])
919 RTStrCopy(pTstEnv->u.Host.TcpOpts.szTcpBindAddr, sizeof(pTstEnv->u.Host.TcpOpts.szTcpBindAddr), "0.0.0.0");
920
921 if (!pTstEnv->u.Host.TcpOpts.uTcpConnectPort)
922 pTstEnv->u.Host.TcpOpts.uTcpConnectPort = ATS_TCP_DEF_CONNECT_PORT_HOST_PORT_FWD;
923
924 if (!pTstEnv->u.Host.TcpOpts.szTcpConnectAddr[0])
925 RTStrCopy(pTstEnv->u.Host.TcpOpts.szTcpConnectAddr, sizeof(pTstEnv->u.Host.TcpOpts.szTcpConnectAddr),
926 ATS_TCP_DEF_CONNECT_HOST_ADDR_STR); /** @todo Get VM IP? Needs port forwarding. */
927
928 /* We need to start a server on the host so that VMs configured with NAT networking
929 * can connect to it as well. */
930 rc = AudioTestSvcClientCreate(&pTstEnv->u.Host.AtsClGuest);
931 if (RT_SUCCESS(rc))
932 rc = audioTestEnvConnectViaTcp(pTstEnv, &pTstEnv->u.Host.AtsClGuest,
933 "Host -> Guest ATS",
934 pTstEnv->u.Host.TcpOpts.szTcpBindAddr, pTstEnv->u.Host.TcpOpts.uTcpBindPort,
935 pTstEnv->u.Host.TcpOpts.szTcpConnectAddr, pTstEnv->u.Host.TcpOpts.uTcpConnectPort);
936 if (RT_SUCCESS(rc))
937 {
938 if (!pTstEnv->ValKitTcpOpts.uTcpConnectPort)
939 pTstEnv->ValKitTcpOpts.uTcpConnectPort = ATS_TCP_DEF_CONNECT_PORT_VALKIT;
940
941 if (!pTstEnv->ValKitTcpOpts.szTcpConnectAddr[0])
942 RTStrCopy(pTstEnv->ValKitTcpOpts.szTcpConnectAddr, sizeof(pTstEnv->ValKitTcpOpts.szTcpConnectAddr),
943 ATS_TCP_DEF_CONNECT_HOST_ADDR_STR);
944
945 rc = AudioTestSvcClientCreate(&pTstEnv->u.Host.AtsClValKit);
946 if (RT_SUCCESS(rc))
947 rc = audioTestEnvConnectViaTcp(pTstEnv, &pTstEnv->u.Host.AtsClValKit,
948 "Host -> Validation Kit Host Audio Driver ATS",
949 pTstEnv->ValKitTcpOpts.szTcpBindAddr, pTstEnv->ValKitTcpOpts.uTcpBindPort,
950 pTstEnv->ValKitTcpOpts.szTcpConnectAddr, pTstEnv->ValKitTcpOpts.uTcpConnectPort);
951 }
952 }
953
954 return rc;
955}
956
957/**
958 * Destroys an audio test environment.
959 *
960 * @param pTstEnv Audio test environment to destroy.
961 */
962void audioTestEnvDestroy(PAUDIOTESTENV pTstEnv)
963{
964 if (!pTstEnv)
965 return;
966
967 for (unsigned i = 0; i < RT_ELEMENTS(pTstEnv->aStreams); i++)
968 {
969 int rc2 = audioTestStreamDestroy(pTstEnv, &pTstEnv->aStreams[i]);
970 if (RT_FAILURE(rc2))
971 RTTestFailed(g_hTest, "Stream destruction for stream #%u failed with %Rrc\n", i, rc2);
972 }
973
974 /* Try cleaning up a bit. */
975 RTDirRemove(pTstEnv->szPathTemp);
976 RTDirRemove(pTstEnv->szPathOut);
977
978 pTstEnv->pDrvStack = NULL;
979}
980
981/**
982 * Closes, packs up and destroys a test environment.
983 *
984 * @returns VBox status code.
985 * @param pTstEnv Test environment to handle.
986 * @param fPack Whether to pack the test set up before destroying / wiping it.
987 * @param pszPackFile Where to store the packed test set file on success. Can be NULL if \a fPack is \c false.
988 * @param cbPackFile Size (in bytes) of \a pszPackFile. Can be 0 if \a fPack is \c false.
989 */
990int audioTestEnvPrologue(PAUDIOTESTENV pTstEnv, bool fPack, char *pszPackFile, size_t cbPackFile)
991{
992 /* Close the test set first. */
993 AudioTestSetClose(&pTstEnv->Set);
994
995 int rc = VINF_SUCCESS;
996
997 if (fPack)
998 {
999 /* Before destroying the test environment, pack up the test set so
1000 * that it's ready for transmission. */
1001 rc = AudioTestSetPack(&pTstEnv->Set, pTstEnv->szPathOut, pszPackFile, cbPackFile);
1002 if (RT_SUCCESS(rc))
1003 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test set packed up to '%s'\n", pszPackFile);
1004 }
1005
1006 if (!g_fDrvAudioDebug) /* Don't wipe stuff when debugging. Can be useful for introspecting data. */
1007 /* ignore rc */ AudioTestSetWipe(&pTstEnv->Set);
1008
1009 AudioTestSetDestroy(&pTstEnv->Set);
1010
1011 if (RT_FAILURE(rc))
1012 RTTestFailed(g_hTest, "Test set prologue failed with %Rrc\n", rc);
1013
1014 return rc;
1015}
1016
1017/**
1018 * Initializes an audio test parameters set.
1019 *
1020 * @param pTstParms Test parameters set to initialize.
1021 */
1022void audioTestParmsInit(PAUDIOTESTPARMS pTstParms)
1023{
1024 RT_ZERO(*pTstParms);
1025}
1026
1027/**
1028 * Destroys an audio test parameters set.
1029 *
1030 * @param pTstParms Test parameters set to destroy.
1031 */
1032void audioTestParmsDestroy(PAUDIOTESTPARMS pTstParms)
1033{
1034 if (!pTstParms)
1035 return;
1036
1037 return;
1038}
1039
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