VirtualBox

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

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

Audio/Validation Kit: Play a pre + post beacon before + after the actual test tone with exactly 1024 audio frames. ​bugref:10008

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