VirtualBox

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

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

Audio/ValKit: More code for audio verification. bugref:10008

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 32.9 KB
Line 
1/* $Id: vkatCommon.cpp 89890 2021-06-24 15:56:05Z vboxsync $ */
2/** @file
3 * Validation Kit Audio Test (VKAT) - Self test code.
4 */
5
6/*
7 * Copyright (C) 2021 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31
32#include <iprt/ctype.h>
33#include <iprt/dir.h>
34#include <iprt/errcore.h>
35#include <iprt/getopt.h>
36#include <iprt/message.h>
37#include <iprt/rand.h>
38#include <iprt/test.h>
39
40#include "Audio/AudioHlp.h"
41#include "Audio/AudioTest.h"
42#include "Audio/AudioTestService.h"
43#include "Audio/AudioTestServiceClient.h"
44
45#include "vkatInternal.h"
46
47
48/*********************************************************************************************************************************
49* Defined Constants And Macros *
50*********************************************************************************************************************************/
51/**
52 * Structure for keeping a user context for the test service callbacks.
53 */
54typedef struct ATSCALLBACKCTX
55{
56 /** The test environment bound to this context. */
57 PAUDIOTESTENV pTstEnv;
58 /** Absolute path to the packed up test set archive.
59 * Keep it simple for now and only support one (open) archive at a time. */
60 char szTestSetArchive[RTPATH_MAX];
61 /** File handle to the (opened) test set archive for reading. */
62 RTFILE hTestSetArchive;
63} ATSCALLBACKCTX;
64typedef ATSCALLBACKCTX *PATSCALLBACKCTX;
65
66
67/*********************************************************************************************************************************
68* Internal Functions *
69*********************************************************************************************************************************/
70static int audioTestStreamInit(PAUDIOTESTDRVSTACK pDrvStack, PAUDIOTESTSTREAM pStream, PDMAUDIODIR enmDir, PCPDMAUDIOPCMPROPS pProps, bool fWithMixer, uint32_t cMsBufferSize, uint32_t cMsPreBuffer, uint32_t cMsSchedulingHint);
71static int audioTestStreamDestroy(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream);
72static int audioTestDevicesEnumerateAndCheck(PAUDIOTESTENV pTstEnv, const char *pszDev, PPDMAUDIOHOSTDEV *ppDev);
73
74
75/*********************************************************************************************************************************
76* Device enumeration + handling. *
77*********************************************************************************************************************************/
78
79/**
80 * Enumerates audio devices and optionally searches for a specific device.
81 *
82 * @returns VBox status code.
83 * @param pTstEnv Test env to use for enumeration.
84 * @param pszDev Device name to search for. Can be NULL if the default device shall be used.
85 * @param ppDev Where to return the pointer of the device enumeration of \a pTstEnv when a
86 * specific device was found.
87 */
88static int audioTestDevicesEnumerateAndCheck(PAUDIOTESTENV pTstEnv, const char *pszDev, PPDMAUDIOHOSTDEV *ppDev)
89{
90#ifdef DEBUG_andy
91 return VINF_SUCCESS;
92#endif
93
94 RTTestSubF(g_hTest, "Enumerating audio devices and checking for device '%s'", pszDev ? pszDev : "<Default>");
95
96 if (!pTstEnv->DrvStack.pIHostAudio->pfnGetDevices)
97 {
98 RTTestSkipped(g_hTest, "Backend does not support device enumeration, skipping");
99 return VINF_NOT_SUPPORTED;
100 }
101
102 Assert(pszDev == NULL || ppDev);
103
104 if (ppDev)
105 *ppDev = NULL;
106
107 int rc = pTstEnv->DrvStack.pIHostAudio->pfnGetDevices(pTstEnv->DrvStack.pIHostAudio, &pTstEnv->DevEnum);
108 if (RT_SUCCESS(rc))
109 {
110 PPDMAUDIOHOSTDEV pDev;
111 RTListForEach(&pTstEnv->DevEnum.LstDevices, pDev, PDMAUDIOHOSTDEV, ListEntry)
112 {
113 char szFlags[PDMAUDIOHOSTDEV_MAX_FLAGS_STRING_LEN];
114 if (pDev->pszId)
115 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Device '%s' (ID '%s'):\n", pDev->pszName, pDev->pszId);
116 else
117 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Device '%s':\n", pDev->pszName);
118 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Usage = %s\n", PDMAudioDirGetName(pDev->enmUsage));
119 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Flags = %s\n", PDMAudioHostDevFlagsToString(szFlags, pDev->fFlags));
120 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Input channels = %RU8\n", pDev->cMaxInputChannels);
121 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Output channels = %RU8\n", pDev->cMaxOutputChannels);
122
123 if ( pszDev
124 && !RTStrCmp(pDev->pszName, pszDev))
125 {
126 *ppDev = pDev;
127 }
128 }
129 }
130 else
131 RTTestFailed(g_hTest, "Enumerating audio devices failed with %Rrc", rc);
132
133 RTTestSubDone(g_hTest);
134
135 if ( pszDev
136 && *ppDev == NULL)
137 {
138 RTTestFailed(g_hTest, "Audio device '%s' not found", pszDev);
139 return VERR_NOT_FOUND;
140 }
141
142 return VINF_SUCCESS;
143}
144
145static int audioTestStreamInit(PAUDIOTESTDRVSTACK pDrvStack, PAUDIOTESTSTREAM pStream,
146 PDMAUDIODIR enmDir, PCPDMAUDIOPCMPROPS pProps, bool fWithMixer,
147 uint32_t cMsBufferSize, uint32_t cMsPreBuffer, uint32_t cMsSchedulingHint)
148{
149 int rc;
150
151 if (enmDir == PDMAUDIODIR_IN)
152 rc = audioTestDriverStackStreamCreateInput(pDrvStack, pProps, cMsBufferSize,
153 cMsPreBuffer, cMsSchedulingHint, &pStream->pStream, &pStream->Cfg);
154 else if (enmDir == PDMAUDIODIR_OUT)
155 rc = audioTestDriverStackStreamCreateOutput(pDrvStack, pProps, cMsBufferSize,
156 cMsPreBuffer, cMsSchedulingHint, &pStream->pStream, &pStream->Cfg);
157 else
158 rc = VERR_NOT_SUPPORTED;
159
160 if (RT_SUCCESS(rc))
161 {
162 if (!pDrvStack->pIAudioConnector)
163 {
164 pStream->pBackend = &((PAUDIOTESTDRVSTACKSTREAM)pStream->pStream)->Backend;
165 }
166 else
167 pStream->pBackend = NULL;
168
169 /*
170 * Automatically enable the mixer if the PCM properties don't match.
171 */
172 if ( !fWithMixer
173 && !PDMAudioPropsAreEqual(pProps, &pStream->Cfg.Props))
174 {
175 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enabling stream mixer\n");
176 fWithMixer = true;
177 }
178
179 rc = AudioTestMixStreamInit(&pStream->Mix, pDrvStack, pStream->pStream,
180 fWithMixer ? pProps : NULL, 100 /* ms */); /** @todo Configure mixer buffer? */
181 }
182
183 if (RT_FAILURE(rc))
184 RTTestFailed(g_hTest, "Initializing %s stream failed with %Rrc", enmDir == PDMAUDIODIR_IN ? "input" : "output", rc);
185
186 return rc;
187}
188
189/**
190 * Destroys an audio test stream.
191 *
192 * @returns VBox status code.
193 * @param pTstEnv Test environment the stream to destroy contains.
194 * @param pStream Audio stream to destroy.
195 */
196static int audioTestStreamDestroy(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream)
197{
198 int rc = VINF_SUCCESS;
199 if (pStream && pStream->pStream)
200 {
201 /** @todo Anything else to do here, e.g. test if there are left over samples or some such? */
202
203 audioTestDriverStackStreamDestroy(&pTstEnv->DrvStack, pStream->pStream);
204 pStream->pStream = NULL;
205 pStream->pBackend = NULL;
206 }
207
208 AudioTestMixStreamTerm(&pStream->Mix);
209
210 return rc;
211}
212
213
214/*********************************************************************************************************************************
215* Test Primitives *
216*********************************************************************************************************************************/
217
218/**
219 * Returns a random scheduling hint (in ms).
220 */
221DECLINLINE(uint32_t) audioTestEnvGetRandomSchedulingHint(void)
222{
223 static const unsigned s_aSchedulingHintsMs[] =
224 {
225 10,
226 25,
227 50,
228 100,
229 200,
230 250
231 };
232
233 return s_aSchedulingHintsMs[RTRandU32Ex(0, RT_ELEMENTS(s_aSchedulingHintsMs) - 1)];
234}
235
236/**
237 * Plays a test tone on a specific audio test stream.
238 *
239 * @returns VBox status code.
240 * @param pTstEnv Test environment to use for running the test.
241 * @param pStream Stream to use for playing the tone.
242 * @param pParms Tone parameters to use.
243 *
244 * @note Blocking function.
245 */
246static int audioTestPlayTone(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PAUDIOTESTTONEPARMS pParms)
247{
248 AUDIOTESTTONE TstTone;
249 AudioTestToneInit(&TstTone, &pStream->Cfg.Props, pParms->dbFreqHz);
250
251 const char *pcszPathOut = pTstEnv->Set.szPathAbs;
252
253 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Playing test tone (tone frequency is %RU16Hz, %RU32ms)\n", (uint16_t)pParms->dbFreqHz, pParms->msDuration);
254 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Using %RU32ms stream scheduling hint\n", pStream->Cfg.Device.cMsSchedulingHint);
255 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Writing to '%s'\n", pcszPathOut);
256
257 /** @todo Use .WAV here? */
258 PAUDIOTESTOBJ pObj;
259 int rc = AudioTestSetObjCreateAndRegister(&pTstEnv->Set, "guest-tone-play.pcm", &pObj);
260 AssertRCReturn(rc, rc);
261
262 rc = AudioTestMixStreamEnable(&pStream->Mix);
263 if ( RT_SUCCESS(rc)
264 && AudioTestMixStreamIsOkay(&pStream->Mix))
265 {
266 uint8_t abBuf[_4K];
267
268 uint32_t cbToPlayTotal = PDMAudioPropsMilliToBytes(&pStream->Cfg.Props, pParms->msDuration);
269 AssertStmt(cbToPlayTotal, rc = VERR_INVALID_PARAMETER);
270
271 RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Playing %RU32 bytes total\n", cbToPlayTotal);
272
273 AudioTestSetObjAddMetadataStr(pObj, "stream_to_play_bytes=%RU32\n", cbToPlayTotal);
274 AudioTestSetObjAddMetadataStr(pObj, "stream_period_size_frames=%RU32\n", pStream->Cfg.Backend.cFramesPeriod);
275 AudioTestSetObjAddMetadataStr(pObj, "stream_buffer_size_frames=%RU32\n", pStream->Cfg.Backend.cFramesBufferSize);
276 AudioTestSetObjAddMetadataStr(pObj, "stream_prebuf_size_frames=%RU32\n", pStream->Cfg.Backend.cFramesPreBuffering);
277 /* Note: This mostly is provided by backend (e.g. PulseAudio / ALSA / ++) and
278 * has nothing to do with the device emulation scheduling hint. */
279 AudioTestSetObjAddMetadataStr(pObj, "device_scheduling_hint_ms=%RU32\n", pStream->Cfg.Device.cMsSchedulingHint);
280
281 while (cbToPlayTotal)
282 {
283 uint32_t cbPlayed = 0;
284 uint32_t const cbCanWrite = AudioTestMixStreamGetWritable(&pStream->Mix);
285 if (cbCanWrite)
286 {
287 uint32_t const cbToGenerate = RT_MIN(RT_MIN(cbToPlayTotal, sizeof(abBuf)), cbCanWrite);
288 uint32_t cbToPlay;
289 rc = AudioTestToneGenerate(&TstTone, abBuf, cbToGenerate, &cbToPlay);
290 if (RT_SUCCESS(rc))
291 {
292 RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Playing %RU32 bytes ...\n", cbToPlay);
293
294 /* Write stuff to disk before trying to play it. Help analysis later. */
295 rc = AudioTestSetObjWrite(pObj, abBuf, cbToPlay);
296 if (RT_SUCCESS(rc))
297 rc = AudioTestMixStreamPlay(&pStream->Mix, abBuf, cbToPlay, &cbPlayed);
298 }
299
300 if (RT_FAILURE(rc))
301 break;
302 }
303 else if (AudioTestMixStreamIsOkay(&pStream->Mix))
304 RTThreadSleep(RT_MIN(RT_MAX(1, pStream->Cfg.Device.cMsSchedulingHint), 256));
305 else
306 AssertFailedBreakStmt(rc = VERR_AUDIO_STREAM_NOT_READY);
307
308 Assert(cbToPlayTotal >= cbPlayed);
309 cbToPlayTotal -= cbPlayed;
310 }
311
312 if (cbToPlayTotal != 0)
313 RTTestFailed(g_hTest, "Playback ended unexpectedly (%RU32 bytes left)\n", cbToPlayTotal);
314 }
315 else
316 rc = VERR_AUDIO_STREAM_NOT_READY;
317
318 int rc2 = AudioTestSetObjClose(pObj);
319 if (RT_SUCCESS(rc))
320 rc = rc2;
321
322 if (RT_FAILURE(rc))
323 RTTestFailed(g_hTest, "Playing tone failed with %Rrc\n", rc);
324
325 return rc;
326}
327
328/**
329 * Records a test tone from a specific audio test stream.
330 *
331 * @returns VBox status code.
332 * @param pTstEnv Test environment to use for running the test.
333 * @param pStream Stream to use for recording the tone.
334 * @param pParms Tone parameters to use.
335 *
336 * @note Blocking function.
337 */
338static int audioTestRecordTone(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PAUDIOTESTTONEPARMS pParms)
339{
340 const char *pcszPathOut = pTstEnv->Set.szPathAbs;
341
342 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Recording test tone (for %RU32ms)\n", pParms->msDuration);
343 RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Writing to '%s'\n", pcszPathOut);
344
345 /** @todo Use .WAV here? */
346 PAUDIOTESTOBJ pObj;
347 int rc = AudioTestSetObjCreateAndRegister(&pTstEnv->Set, "guest-tone-rec.pcm", &pObj);
348 AssertRCReturn(rc, rc);
349
350 if (audioTestDriverStackStreamIsOkay(&pTstEnv->DrvStack, pStream->pStream))
351 {
352 const uint32_t cbPerSched = PDMAudioPropsMilliToBytes(&pParms->Props, pTstEnv->cMsSchedulingHint);
353 AssertStmt(cbPerSched, rc = VERR_INVALID_PARAMETER);
354 uint32_t cbToRead = PDMAudioPropsMilliToBytes(&pParms->Props, pParms->msDuration);
355 AssertStmt(cbToRead, rc = VERR_INVALID_PARAMETER);
356
357 if (RT_SUCCESS(rc))
358 {
359 AudioTestSetObjAddMetadataStr(pObj, "buffer_size_ms=%RU32\n", pTstEnv->cMsBufferSize);
360 AudioTestSetObjAddMetadataStr(pObj, "prebuf_size_ms=%RU32\n", pTstEnv->cMsPreBuffer);
361 AudioTestSetObjAddMetadataStr(pObj, "scheduling_hint_ms=%RU32\n", pTstEnv->cMsSchedulingHint);
362
363 uint8_t abBuf[_4K];
364
365 while (cbToRead)
366 {
367 const uint32_t cbChunk = RT_MIN(cbToRead, RT_MIN(cbPerSched, sizeof(abBuf)));
368
369 uint32_t cbRead = 0;
370 rc = audioTestDriverStackStreamCapture(&pTstEnv->DrvStack, pStream->pStream, (void *)abBuf, cbChunk, &cbRead);
371 if (RT_SUCCESS(rc))
372 rc = AudioTestSetObjWrite(pObj, abBuf, cbRead);
373
374 if (RT_FAILURE(rc))
375 break;
376
377 RTThreadSleep(pTstEnv->cMsSchedulingHint);
378
379 Assert(cbToRead >= cbRead);
380 cbToRead -= cbRead;
381 }
382 }
383 }
384 else
385 rc = VERR_AUDIO_STREAM_NOT_READY;
386
387 int rc2 = AudioTestSetObjClose(pObj);
388 if (RT_SUCCESS(rc))
389 rc = rc2;
390
391 if (RT_FAILURE(rc))
392 RTTestFailed(g_hTest, "Recording tone done failed with %Rrc\n", rc);
393
394 return rc;
395}
396
397
398/*********************************************************************************************************************************
399* ATS Callback Implementations *
400*********************************************************************************************************************************/
401
402/** @copydoc ATSCALLBACKS::pfnTestSetBegin
403 *
404 * @note Runs as part of the guest ATS.
405 */
406static DECLCALLBACK(int) audioTestGstAtsTestSetBeginCallback(void const *pvUser, const char *pszTag)
407{
408 PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser;
409 PAUDIOTESTENV pTstEnv = pCtx->pTstEnv;
410
411 RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Beginning test set '%s' in '%s'\n", pszTag, pTstEnv->szPathTemp);
412
413 return AudioTestSetCreate(&pTstEnv->Set, pTstEnv->szPathTemp, pszTag);
414}
415
416/** @copydoc ATSCALLBACKS::pfnTestSetEnd
417 *
418 * @note Runs as part of the guest ATS.
419 */
420static DECLCALLBACK(int) audioTestGstAtsTestSetEndCallback(void const *pvUser, const char *pszTag)
421{
422 PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser;
423 PAUDIOTESTENV pTstEnv = pCtx->pTstEnv;
424
425 RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Ending test set '%s'\n", pszTag);
426
427 /* Pack up everything to be ready for transmission. */
428 return audioTestEnvPrologue(pTstEnv, true /* fPack */, pCtx->szTestSetArchive, sizeof(pCtx->szTestSetArchive));
429}
430
431/** @copydoc ATSCALLBACKS::pfnTonePlay
432 *
433 * @note Runs as part of the guest ATS.
434 */
435static DECLCALLBACK(int) audioTestGstAtsTonePlayCallback(void const *pvUser, PAUDIOTESTTONEPARMS pToneParms)
436{
437 PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser;
438 PAUDIOTESTENV pTstEnv = pCtx->pTstEnv;
439
440 AUDIOTESTTONE TstTone;
441 AudioTestToneInitRandom(&TstTone, &pToneParms->Props);
442
443 const PAUDIOTESTSTREAM pTstStream = &pTstEnv->aStreams[0]; /** @todo Make this dynamic. */
444
445 int rc = audioTestStreamInit(&pTstEnv->DrvStack, pTstStream, PDMAUDIODIR_OUT, &pTstEnv->Props, false /* fWithMixer */,
446 pTstEnv->cMsBufferSize, pTstEnv->cMsPreBuffer, pTstEnv->cMsSchedulingHint);
447 if (RT_SUCCESS(rc))
448 {
449 AUDIOTESTPARMS TstParms;
450 RT_ZERO(TstParms);
451 TstParms.enmType = AUDIOTESTTYPE_TESTTONE_PLAY;
452 TstParms.enmDir = PDMAUDIODIR_OUT;
453 TstParms.TestTone = *pToneParms;
454
455 PAUDIOTESTENTRY pTst;
456 rc = AudioTestSetTestBegin(&pTstEnv->Set, "Playing test tone", &TstParms, &pTst);
457 if (RT_SUCCESS(rc))
458 {
459 rc = audioTestPlayTone(pTstEnv, pTstStream, pToneParms);
460 if (RT_SUCCESS(rc))
461 {
462 AudioTestSetTestDone(pTst);
463 }
464 else
465 AudioTestSetTestFailed(pTst, rc, "Playing tone failed");
466 }
467
468 int rc2 = audioTestStreamDestroy(pTstEnv, pTstStream);
469 if (RT_SUCCESS(rc))
470 rc = rc2;
471 }
472 else
473 RTTestFailed(g_hTest, "Error creating output stream, rc=%Rrc\n", rc);
474
475 return rc;
476}
477
478/** @copydoc ATSCALLBACKS::pfnToneRecord */
479static DECLCALLBACK(int) audioTestGstAtsToneRecordCallback(void const *pvUser, PAUDIOTESTTONEPARMS pToneParms)
480{
481 PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser;
482 PAUDIOTESTENV pTstEnv = pCtx->pTstEnv;
483
484 const PAUDIOTESTSTREAM pTstStream = &pTstEnv->aStreams[0]; /** @todo Make this dynamic. */
485
486 int rc = audioTestStreamInit(&pTstEnv->DrvStack, pTstStream, PDMAUDIODIR_IN, &pTstEnv->Props, false /* fWithMixer */,
487 pTstEnv->cMsBufferSize, pTstEnv->cMsPreBuffer, pTstEnv->cMsSchedulingHint);
488 if (RT_SUCCESS(rc))
489 {
490 AUDIOTESTPARMS TstParms;
491 RT_ZERO(TstParms);
492 TstParms.enmType = AUDIOTESTTYPE_TESTTONE_RECORD;
493 TstParms.enmDir = PDMAUDIODIR_IN;
494 TstParms.Props = pToneParms->Props;
495 TstParms.TestTone = *pToneParms;
496
497 PAUDIOTESTENTRY pTst;
498 rc = AudioTestSetTestBegin(&pTstEnv->Set, "Recording test tone from host", &TstParms, &pTst);
499 if (RT_SUCCESS(rc))
500 {
501 rc = audioTestRecordTone(pTstEnv, pTstStream, pToneParms);
502 if (RT_SUCCESS(rc))
503 {
504 AudioTestSetTestDone(pTst);
505 }
506 else
507 AudioTestSetTestFailed(pTst, rc, "Recording tone failed");
508 }
509
510 int rc2 = audioTestStreamDestroy(pTstEnv, pTstStream);
511 if (RT_SUCCESS(rc))
512 rc = rc2;
513 }
514 else
515 RTTestFailed(g_hTest, "Error creating input stream, rc=%Rrc\n", rc);
516
517 return rc;
518}
519
520/** @copydoc ATSCALLBACKS::pfnTestSetSendBegin */
521static DECLCALLBACK(int) audioTestGstAtsTestSetSendBeginCallback(void const *pvUser, const char *pszTag)
522{
523 RT_NOREF(pszTag);
524
525 PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser;
526
527 if (!RTFileExists(pCtx->szTestSetArchive)) /* Has the archive successfully been created yet? */
528 return VERR_WRONG_ORDER;
529
530 int rc = RTFileOpen(&pCtx->hTestSetArchive, pCtx->szTestSetArchive, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
531 if (RT_SUCCESS(rc))
532 {
533 uint64_t uSize;
534 rc = RTFileQuerySize(pCtx->hTestSetArchive, &uSize);
535 if (RT_SUCCESS(rc))
536 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Sending test set '%s' (%zu bytes)\n", pCtx->szTestSetArchive, uSize);
537 }
538
539 return rc;
540}
541
542/** @copydoc ATSCALLBACKS::pfnTestSetSendRead */
543static DECLCALLBACK(int) audioTestGstAtsTestSetSendReadCallback(void const *pvUser,
544 const char *pszTag, void *pvBuf, size_t cbBuf, size_t *pcbRead)
545{
546 RT_NOREF(pszTag);
547
548 PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser;
549
550 return RTFileRead(pCtx->hTestSetArchive, pvBuf, cbBuf, pcbRead);
551}
552
553/** @copydoc ATSCALLBACKS::pfnTestSetSendEnd */
554static DECLCALLBACK(int) audioTestGstAtsTestSetSendEndCallback(void const *pvUser, const char *pszTag)
555{
556 RT_NOREF(pszTag);
557
558 PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser;
559
560 int rc = RTFileClose(pCtx->hTestSetArchive);
561 if (RT_SUCCESS(rc))
562 {
563 pCtx->hTestSetArchive = NIL_RTFILE;
564 }
565
566 return rc;
567}
568
569
570/*********************************************************************************************************************************
571* Implementation of audio test environment handling *
572*********************************************************************************************************************************/
573
574int audioTestEnvConnectToHostAts(PAUDIOTESTENV pTstEnv,
575 const char *pszHostTcpAddr, uint32_t uHostTcpPort)
576{
577 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Connecting to host ATS at %s:%RU32 ...\n",
578 (pszHostTcpAddr && *pszHostTcpAddr) ? pszHostTcpAddr : ATS_TCP_HOST_DEFAULT_ADDR_STR,
579 uHostTcpPort ? uHostTcpPort : ATS_TCP_HOST_DEFAULT_PORT);
580
581 int rc = AudioTestSvcClientConnect(&pTstEnv->u.Host.AtsClValKit, pszHostTcpAddr, uHostTcpPort);
582 if (RT_FAILURE(rc))
583 {
584 RTTestFailed(g_hTest, "Connecting to host ATS failed with %Rrc\n", rc);
585 return rc;
586 }
587
588 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Connected to host ATS\n");
589 return rc;
590}
591
592int audioTestEnvConnectToGuestAts(PAUDIOTESTENV pTstEnv,
593 const char *pszGuestTcpAddr, uint32_t uGuestTcpPort)
594{
595 RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Connecting to guest ATS at %s:%RU32 ...\n",
596 (pszGuestTcpAddr && *pszGuestTcpAddr) ? pszGuestTcpAddr : "127.0.0.1",
597 uGuestTcpPort ? uGuestTcpPort : ATS_TCP_GUEST_DEFAULT_PORT);
598
599 int rc = AudioTestSvcClientConnect(&pTstEnv->u.Host.AtsClGuest, pszGuestTcpAddr, uGuestTcpPort);
600 if (RT_FAILURE(rc))
601 {
602 RTTestFailed(g_hTest, "Connecting to guest ATS failed with %Rrc\n", rc);
603 return rc;
604 }
605
606 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Connected to guest ATS\n");
607 return rc;
608}
609
610/**
611 * Initializes an audio test environment.
612 *
613 * @param pTstEnv Audio test environment to initialize.
614 * @param pDrvReg Audio driver to use.
615 * @param fWithDrvAudio Whether to include DrvAudio in the stack or not.
616 * @param pszHostTcpAddr Host ATS TCP/IP address to connect to.
617 * If NULL, ATS_TCP_HOST_DEFAULT_ADDR_STR will be used.
618 * @param uHostTcpPort Host ATS TCP/IP port to connect to.
619 * If 0, ATS_TCP_HOST_DEFAULT_PORT will be used.
620 * @param pszGuestTcpAddr Guest ATS TCP/IP address to connect to.
621 * If NULL, any address (0.0.0.0) will be used.
622 * @param uGuestTcpPort Guest ATS TCP/IP port to connect to.
623 * If 0, ATS_TCP_GUEST_DEFAULT_PORT will be used.
624 */
625int audioTestEnvInit(PAUDIOTESTENV pTstEnv,
626 PCPDMDRVREG pDrvReg, bool fWithDrvAudio,
627 const char *pszHostTcpAddr, uint32_t uHostTcpPort,
628 const char *pszGuestTcpAddr, uint32_t uGuestTcpPort)
629{
630 int rc = VINF_SUCCESS;
631
632 /*
633 * Set sane defaults if not already set.
634 */
635 if (!RTStrNLen(pTstEnv->szTag, sizeof(pTstEnv->szTag)))
636 {
637 rc = AudioTestGenTag(pTstEnv->szTag, sizeof(pTstEnv->szTag));
638 AssertRCReturn(rc, rc);
639 }
640
641 if (!RTStrNLen(pTstEnv->szPathTemp, sizeof(pTstEnv->szPathTemp)))
642 {
643 rc = AudioTestPathGetTemp(pTstEnv->szPathTemp, sizeof(pTstEnv->szPathTemp));
644 AssertRCReturn(rc, rc);
645 }
646
647 if (!RTStrNLen(pTstEnv->szPathOut, sizeof(pTstEnv->szPathOut)))
648 {
649 rc = RTPathJoin(pTstEnv->szPathOut, sizeof(pTstEnv->szPathOut), pTstEnv->szPathTemp, "vkat-temp");
650 AssertRCReturn(rc, rc);
651 }
652
653 /* Go with the platform's default backend if nothing else is set. */
654 if (!pDrvReg)
655 pDrvReg = AudioTestGetDefaultBackend();
656
657 if (!uHostTcpPort)
658 uHostTcpPort = ATS_TCP_HOST_DEFAULT_PORT;
659
660 if (!uGuestTcpPort)
661 uGuestTcpPort = ATS_TCP_GUEST_DEFAULT_PORT;
662
663 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Initializing environment for mode '%s'\n", pTstEnv->enmMode == AUDIOTESTMODE_HOST ? "host" : "guest");
664 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Using backend '%s'\n", pDrvReg->szName);
665 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Using tag '%s'\n", pTstEnv->szTag);
666 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Output directory is '%s'\n", pTstEnv->szPathOut);
667 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Temp directory is '%s'\n", pTstEnv->szPathTemp);
668
669 if (!pTstEnv->cMsBufferSize)
670 pTstEnv->cMsBufferSize = UINT32_MAX;
671 if (!pTstEnv->cMsPreBuffer)
672 pTstEnv->cMsPreBuffer = UINT32_MAX;
673 if (!pTstEnv->cMsSchedulingHint)
674 pTstEnv->cMsSchedulingHint = UINT32_MAX;
675
676 PDMAudioHostEnumInit(&pTstEnv->DevEnum);
677
678 bool fUseDriverStack = false; /* Whether to init + use the audio driver stack or not. */
679
680 /* In regular testing mode only the guest mode needs initializing the driver stack. */
681 if (pTstEnv->enmMode == AUDIOTESTMODE_GUEST)
682 fUseDriverStack = true;
683
684 /* When running in self-test mode, the host mode also needs to initialize the stack in order to
685 * to run the Valdation Kit audio driver ATS (no "real" VBox involved). */
686 if (pTstEnv->enmMode == AUDIOTESTMODE_HOST && pTstEnv->fSelftest)
687 fUseDriverStack = true;
688
689 if (fUseDriverStack)
690 {
691 rc = audioTestDriverStackInitEx(&pTstEnv->DrvStack, pDrvReg,
692 true /* fEnabledIn */, true /* fEnabledOut */, fWithDrvAudio);
693 if (RT_FAILURE(rc))
694 return rc;
695
696 PPDMAUDIOHOSTDEV pDev;
697 rc = audioTestDevicesEnumerateAndCheck(pTstEnv, pTstEnv->szDev, &pDev);
698 if (RT_FAILURE(rc))
699 return rc;
700 }
701
702 char szPathTemp[RTPATH_MAX];
703 if ( !strlen(pTstEnv->szPathTemp)
704 || !strlen(pTstEnv->szPathOut))
705 rc = RTPathTemp(szPathTemp, sizeof(szPathTemp));
706
707 if ( RT_SUCCESS(rc)
708 && !strlen(pTstEnv->szPathTemp))
709 rc = RTPathJoin(pTstEnv->szPathTemp, sizeof(pTstEnv->szPathTemp), szPathTemp, "vkat-temp");
710
711 if (RT_SUCCESS(rc))
712 {
713 rc = RTDirCreate(pTstEnv->szPathTemp, RTFS_UNIX_IRWXU, 0 /* fFlags */);
714 if (rc == VERR_ALREADY_EXISTS)
715 rc = VINF_SUCCESS;
716 }
717
718 if ( RT_SUCCESS(rc)
719 && !strlen(pTstEnv->szPathOut))
720 rc = RTPathJoin(pTstEnv->szPathOut, sizeof(pTstEnv->szPathOut), szPathTemp, "vkat");
721
722 if (RT_SUCCESS(rc))
723 {
724 rc = RTDirCreate(pTstEnv->szPathOut, RTFS_UNIX_IRWXU, 0 /* fFlags */);
725 if (rc == VERR_ALREADY_EXISTS)
726 rc = VINF_SUCCESS;
727 }
728
729 if (RT_FAILURE(rc))
730 return rc;
731
732 /** @todo Implement NAT mode like we do for TxS later? */
733 if (pTstEnv->enmMode == AUDIOTESTMODE_GUEST)
734 {
735 ATSCALLBACKCTX Ctx;
736 Ctx.pTstEnv = pTstEnv;
737
738 ATSCALLBACKS Callbacks;
739 RT_ZERO(Callbacks);
740 Callbacks.pfnTestSetBegin = audioTestGstAtsTestSetBeginCallback;
741 Callbacks.pfnTestSetEnd = audioTestGstAtsTestSetEndCallback;
742 Callbacks.pfnTonePlay = audioTestGstAtsTonePlayCallback;
743 Callbacks.pfnToneRecord = audioTestGstAtsToneRecordCallback;
744 Callbacks.pfnTestSetSendBegin = audioTestGstAtsTestSetSendBeginCallback;
745 Callbacks.pfnTestSetSendRead = audioTestGstAtsTestSetSendReadCallback;
746 Callbacks.pfnTestSetSendEnd = audioTestGstAtsTestSetSendEndCallback;
747 Callbacks.pvUser = &Ctx;
748
749 /*
750 * Start the ATS (Audio Test Service) on the guest side.
751 * That service then will perform playback and recording operations on the guest, triggered from the host.
752 *
753 * When running this in self-test mode, that service also can be run on the host if nothing else is specified.
754 * Note that we have to bind to "0.0.0.0" by default so that the host can connect to it.
755 */
756 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Starting guest ATS at %s:%RU32...\n",
757 (pszGuestTcpAddr && *pszGuestTcpAddr) ? pszGuestTcpAddr : "0.0.0.0", uGuestTcpPort);
758 rc = AudioTestSvcInit(&pTstEnv->u.Guest.Srv,
759 (pszGuestTcpAddr && *pszGuestTcpAddr) ? pszGuestTcpAddr : "0.0.0.0", uGuestTcpPort, &Callbacks);
760 if (RT_SUCCESS(rc))
761 rc = AudioTestSvcStart(&pTstEnv->u.Guest.Srv);
762
763 if (RT_FAILURE(rc))
764 RTTestFailed(g_hTest, "Starting ATS failed with %Rrc\n", rc);
765 }
766 else /* Host mode */
767 {
768 rc = audioTestEnvConnectToHostAts(pTstEnv, pszHostTcpAddr, uHostTcpPort);
769 if (RT_SUCCESS(rc))
770 rc = audioTestEnvConnectToGuestAts(pTstEnv, pszGuestTcpAddr, uGuestTcpPort);
771 }
772
773 if ( RT_FAILURE(rc)
774 && fUseDriverStack)
775 audioTestDriverStackDelete(&pTstEnv->DrvStack);
776
777 return rc;
778}
779
780/**
781 * Destroys an audio test environment.
782 *
783 * @param pTstEnv Audio test environment to destroy.
784 */
785void audioTestEnvDestroy(PAUDIOTESTENV pTstEnv)
786{
787 if (!pTstEnv)
788 return;
789
790 PDMAudioHostEnumDelete(&pTstEnv->DevEnum);
791
792 for (unsigned i = 0; i < RT_ELEMENTS(pTstEnv->aStreams); i++)
793 {
794 int rc2 = audioTestStreamDestroy(pTstEnv, &pTstEnv->aStreams[i]);
795 if (RT_FAILURE(rc2))
796 RTTestFailed(g_hTest, "Stream destruction for stream #%u failed with %Rrc\n", i, rc2);
797 }
798
799 /* Try cleaning up a bit. */
800 RTDirRemove(pTstEnv->szPathTemp);
801 RTDirRemove(pTstEnv->szPathOut);
802
803 audioTestDriverStackDelete(&pTstEnv->DrvStack);
804}
805
806/**
807 * Closes, packs up and destroys a test environment.
808 *
809 * @returns VBox status code.
810 * @param pTstEnv Test environment to handle.
811 * @param fPack Whether to pack the test set up before destroying / wiping it.
812 * @param pszPackFile Where to store the packed test set file on success. Can be NULL if \a fPack is \c false.
813 * @param cbPackFile Size (in bytes) of \a pszPackFile. Can be 0 if \a fPack is \c false.
814 */
815int audioTestEnvPrologue(PAUDIOTESTENV pTstEnv, bool fPack, char *pszPackFile, size_t cbPackFile)
816{
817 /* Close the test set first. */
818 AudioTestSetClose(&pTstEnv->Set);
819
820 int rc = VINF_SUCCESS;
821
822 if (fPack)
823 {
824 /* Before destroying the test environment, pack up the test set so
825 * that it's ready for transmission. */
826 rc = AudioTestSetPack(&pTstEnv->Set, pTstEnv->szPathOut, pszPackFile, cbPackFile);
827 if (RT_SUCCESS(rc))
828 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test set packed up to '%s'\n", pszPackFile);
829 }
830
831 if (!g_fDrvAudioDebug) /* Don't wipe stuff when debugging. Can be useful for introspecting data. */
832 /* ignore rc */ AudioTestSetWipe(&pTstEnv->Set);
833
834 AudioTestSetDestroy(&pTstEnv->Set);
835
836 if (RT_FAILURE(rc))
837 RTTestFailed(g_hTest, "Test set prologue failed with %Rrc\n", rc);
838
839 return rc;
840}
841
842/**
843 * Initializes an audio test parameters set.
844 *
845 * @param pTstParms Test parameters set to initialize.
846 */
847void audioTestParmsInit(PAUDIOTESTPARMS pTstParms)
848{
849 RT_ZERO(*pTstParms);
850}
851
852/**
853 * Destroys an audio test parameters set.
854 *
855 * @param pTstParms Test parameters set to destroy.
856 */
857void audioTestParmsDestroy(PAUDIOTESTPARMS pTstParms)
858{
859 if (!pTstParms)
860 return;
861
862 return;
863}
864
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