VirtualBox

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

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

Audio/Validation Kit: Sketched out setting the system's master volume on Windows via WASAPI (not enabled yet) [SCM fix]. ​bugref:10008

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