VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/AudioTest.cpp@ 88985

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

Audio/ValKit: More hacking on the test set validation / error handling code. bugref:10008

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 15.9 KB
Line 
1/* $Id: AudioTest.cpp 88985 2021-05-11 16:31:00Z vboxsync $ */
2/** @file
3 * Audio testing routines.
4 * Common code which is being used by the ValidationKit and the debug / ValdikationKit audio driver(s).
5 */
6
7/*
8 * Copyright (C) 2021 Oracle Corporation
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 */
18
19
20/*********************************************************************************************************************************
21* Header Files *
22*********************************************************************************************************************************/
23
24#include <VBox/vmm/pdmaudioifs.h>
25#include <VBox/vmm/pdmaudioinline.h>
26
27#include <iprt/dir.h>
28#include <iprt/file.h>
29#include <iprt/inifile.h>
30#include <iprt/list.h>
31#include <iprt/rand.h>
32#include <iprt/uuid.h>
33#include <iprt/vfs.h>
34
35#define _USE_MATH_DEFINES
36#include <math.h> /* sin, M_PI */
37
38#include "AudioTest.h"
39
40
41/*********************************************************************************************************************************
42* Defines *
43*********************************************************************************************************************************/
44/** The test manifest file. */
45#define AUDIOTEST_MANIFEST_FILE_STR "vkat_manifest.ini"
46#define AUDIOTEST_MANIFEST_VER 1
47
48#define AUDIOTEST_INI_SEC_HDR_STR "header"
49
50/*********************************************************************************************************************************
51* Structures and Typedefs *
52*********************************************************************************************************************************/
53
54
55/*********************************************************************************************************************************
56* Global Variables *
57*********************************************************************************************************************************/
58/** Well-known frequency selection test tones. */
59static const double s_aAudioTestToneFreqsHz[] =
60{
61 349.2282 /*F4*/,
62 440.0000 /*A4*/,
63 523.2511 /*C5*/,
64 698.4565 /*F5*/,
65 880.0000 /*A5*/,
66 1046.502 /*C6*/,
67 1174.659 /*D6*/,
68 1396.913 /*F6*/,
69 1760.0000 /*A6*/
70};
71
72/**
73 * Initializes a test tone by picking a random but well-known frequency (in Hz).
74 *
75 * @returns Randomly picked frequency (in Hz).
76 * @param pTone Pointer to test tone to initialize.
77 * @param pProps PCM properties to use for the test tone.
78 */
79double AudioTestToneInitRandom(PAUDIOTESTTONE pTone, PPDMAUDIOPCMPROPS pProps)
80{
81 /* Pick a frequency from our selection, so that every time a recording starts
82 * we'll hopfully generate a different note. */
83 pTone->rdFreqHz = s_aAudioTestToneFreqsHz[RTRandU32Ex(0, RT_ELEMENTS(s_aAudioTestToneFreqsHz) - 1)];
84 pTone->rdFixed = 2.0 * M_PI * pTone->rdFreqHz / PDMAudioPropsHz(pProps);
85 pTone->uSample = 0;
86
87 memcpy(&pTone->Props, pProps, sizeof(PDMAUDIOPCMPROPS));
88
89 return pTone->rdFreqHz;
90}
91
92/**
93 * Writes (and iterates) a given test tone to an output buffer.
94 *
95 * @returns VBox status code.
96 * @param pTone Pointer to test tone to write.
97 * @param pvBuf Pointer to output buffer to write test tone to.
98 * @param cbBuf Size (in bytes) of output buffer.
99 * @param pcbWritten How many bytes were written on success.
100 */
101int AudioTestToneWrite(PAUDIOTESTTONE pTone, void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
102{
103 /*
104 * Clear the buffer first so we don't need to thing about additional channels.
105 */
106 uint32_t cFrames = PDMAudioPropsBytesToFrames(&pTone->Props, cbBuf);
107 PDMAudioPropsClearBuffer(&pTone->Props, pvBuf, cbBuf, cFrames);
108
109 /*
110 * Generate the select sin wave in the first channel:
111 */
112 uint32_t const cbFrame = PDMAudioPropsFrameSize(&pTone->Props);
113 double const rdFixed = pTone->rdFixed;
114 uint64_t iSrcFrame = pTone->uSample;
115 switch (PDMAudioPropsSampleSize(&pTone->Props))
116 {
117 case 1:
118 /* untested */
119 if (PDMAudioPropsIsSigned(&pTone->Props))
120 {
121 int8_t *piSample = (int8_t *)pvBuf;
122 while (cFrames-- > 0)
123 {
124 *piSample = (int8_t)(126 /*Amplitude*/ * sin(rdFixed * iSrcFrame));
125 iSrcFrame++;
126 piSample += cbFrame;
127 }
128 }
129 else
130 {
131 /* untested */
132 uint8_t *pbSample = (uint8_t *)pvBuf;
133 while (cFrames-- > 0)
134 {
135 *pbSample = (uint8_t)(126 /*Amplitude*/ * sin(rdFixed * iSrcFrame) + 0x80);
136 iSrcFrame++;
137 pbSample += cbFrame;
138 }
139 }
140 break;
141
142 case 2:
143 if (PDMAudioPropsIsSigned(&pTone->Props))
144 {
145 int16_t *piSample = (int16_t *)pvBuf;
146 while (cFrames-- > 0)
147 {
148 *piSample = (int16_t)(32760 /*Amplitude*/ * sin(rdFixed * iSrcFrame));
149 iSrcFrame++;
150 piSample = (int16_t *)((uint8_t *)piSample + cbFrame);
151 }
152 }
153 else
154 {
155 /* untested */
156 uint16_t *puSample = (uint16_t *)pvBuf;
157 while (cFrames-- > 0)
158 {
159 *puSample = (uint16_t)(32760 /*Amplitude*/ * sin(rdFixed * iSrcFrame) + 0x8000);
160 iSrcFrame++;
161 puSample = (uint16_t *)((uint8_t *)puSample + cbFrame);
162 }
163 }
164 break;
165
166 case 4:
167 /* untested */
168 if (PDMAudioPropsIsSigned(&pTone->Props))
169 {
170 int32_t *piSample = (int32_t *)pvBuf;
171 while (cFrames-- > 0)
172 {
173 *piSample = (int32_t)((32760 << 16) /*Amplitude*/ * sin(rdFixed * iSrcFrame));
174 iSrcFrame++;
175 piSample = (int32_t *)((uint8_t *)piSample + cbFrame);
176 }
177 }
178 else
179 {
180 uint32_t *puSample = (uint32_t *)pvBuf;
181 while (cFrames-- > 0)
182 {
183 *puSample = (uint32_t)((32760 << 16) /*Amplitude*/ * sin(rdFixed * iSrcFrame) + UINT32_C(0x80000000));
184 iSrcFrame++;
185 puSample = (uint32_t *)((uint8_t *)puSample + cbFrame);
186 }
187 }
188 break;
189
190 default:
191 AssertFailedReturn(VERR_NOT_SUPPORTED);
192 }
193
194 pTone->uSample = iSrcFrame;
195
196 if (pcbWritten)
197 *pcbWritten = PDMAudioPropsFramesToBytes(&pTone->Props, cFrames);
198
199 return VINF_SUCCESS;
200}
201
202/**
203 * Initializes an audio test tone parameters struct with random values.
204 * @param pToneParams Test tone parameters to initialize.
205 * @param pProps PCM properties to use for the test tone.
206 */
207int AudioTestToneParamsInitRandom(PAUDIOTESTTONEPARMS pToneParams, PPDMAUDIOPCMPROPS pProps)
208{
209 AssertReturn(PDMAudioPropsAreValid(pProps), VERR_INVALID_PARAMETER);
210
211 memcpy(&pToneParams->Props, pProps, sizeof(PDMAUDIOPCMPROPS));
212
213 /** @todo Make this a bit more sophisticated later, e.g. muting and prequel/sequel are not very balanced. */
214
215 pToneParams->msPrequel = RTRandU32Ex(0, RT_MS_5SEC);
216 pToneParams->msDuration = RTRandU32Ex(0, RT_MS_30SEC); /** @todo Probably a bit too long, but let's see. */
217 pToneParams->msSequel = RTRandU32Ex(0, RT_MS_5SEC);
218 pToneParams->uVolumePercent = RTRandU32Ex(0, 100);
219
220 return VINF_SUCCESS;
221}
222
223int AudioTestPathCreate(char *pszPath, size_t cbPath, const char *pszTag)
224{
225 int rc;
226
227 char szTag[RTUUID_STR_LENGTH + 1];
228 if (pszTag)
229 {
230 rc = RTStrCopy(szTag, sizeof(szTag), pszTag);
231 AssertRCReturn(rc, rc);
232 }
233 else /* Create an UUID if no tag is specified. */
234 {
235 RTUUID UUID;
236 rc = RTUuidCreate(&UUID);
237 AssertRCReturn(rc, rc);
238 rc = RTUuidToStr(&UUID, szTag, sizeof(szTag));
239 AssertRCReturn(rc, rc);
240 }
241
242 char szName[128];
243 if (RTStrPrintf2(szName, sizeof(szName), "%s%s", AUDIOTEST_PATH_PREFIX_STR, szTag) < 0)
244 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
245
246 rc = RTPathAppend(pszPath, cbPath, szName);
247 AssertRCReturn(rc, rc);
248
249 char szTime[64];
250 RTTIMESPEC time;
251 if (!RTTimeSpecToString(RTTimeNow(&time), szTime, sizeof(szTime)))
252 return VERR_BUFFER_UNDERFLOW;
253
254 rc = RTPathAppend(pszPath, cbPath, szTime);
255 AssertRCReturn(rc, rc);
256
257 return RTDirCreateFullPath(pszPath, RTFS_UNIX_IRWXU);
258}
259
260static int audioTestManifestWriteLn(PAUDIOTESTSET pSet, const char *pszFormat, ...)
261{
262 va_list va;
263 va_start(va, pszFormat);
264
265 char *psz = NULL;
266 RTStrAPrintfV(&psz, pszFormat, va);
267 AssertPtr(psz);
268
269 /** @todo Use RTIniFileWrite once its implemented. */
270 int rc = RTFileWrite(pSet->f.hFile, psz, strlen(psz), NULL);
271 AssertRC(rc);
272 rc = RTFileWrite(pSet->f.hFile, "\n", strlen("\n"), NULL);
273 AssertRC(rc);
274
275 RTStrFree(psz);
276 va_end(va);
277
278 return rc;
279}
280
281static void audioTestSetInitInternal(PAUDIOTESTSET pSet)
282{
283 pSet->f.hFile = NIL_RTFILE;
284}
285
286static bool audioTestManifestIsOpen(PAUDIOTESTSET pSet)
287{
288 if ( pSet->enmMode == AUDIOTESTSETMODE_TEST
289 && pSet->f.hFile != NIL_RTFILE)
290 return true;
291 else if ( pSet->enmMode == AUDIOTESTSETMODE_VERIFY
292 && pSet->f.hIniFile != NIL_RTINIFILE)
293 return true;
294
295 return false;
296}
297
298static void audioTestErrorDescInit(PAUDIOTESTERRORDESC pErr)
299{
300 RTListInit(&pErr->List);
301 pErr->cErrors = 0;
302}
303
304void AudioTestErrorDescDestroy(PAUDIOTESTERRORDESC pErr)
305{
306 if (!pErr)
307 return;
308
309 PAUDIOTESTERRORENTRY pErrEntry, pErrEntryNext;
310 RTListForEachSafe(&pErr->List, pErrEntry, pErrEntryNext, AUDIOTESTERRORENTRY, Node)
311 {
312 RTListNodeRemove(&pErrEntry->Node);
313
314 RTMemFree(pErrEntry);
315
316 Assert(pErr->cErrors);
317 pErr->cErrors--;
318 }
319
320 Assert(pErr->cErrors == 0);
321}
322
323bool AudioTestErrorDescFailed(PAUDIOTESTERRORDESC pErr)
324{
325 if (pErr->cErrors)
326 {
327 Assert(!RTListIsEmpty(&pErr->List));
328 return true;
329 }
330
331 return false;
332}
333
334static int audioTestErrorDescAddV(PAUDIOTESTERRORDESC pErr, int rc, const char *pszFormat, va_list args)
335{
336 PAUDIOTESTERRORENTRY pEntry = (PAUDIOTESTERRORENTRY)RTMemAlloc(sizeof(AUDIOTESTERRORENTRY));
337 AssertReturn(pEntry, VERR_NO_MEMORY);
338
339 if (RTStrPrintf2V(pEntry->szDesc, sizeof(pEntry->szDesc), pszFormat, args) < 0)
340 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
341
342 pEntry->rc = rc;
343
344 RTListAppend(&pErr->List, &pEntry->Node);
345
346 pErr->cErrors++;
347
348 return VINF_SUCCESS;
349}
350
351static int audioTestErrorDescAdd(PAUDIOTESTERRORDESC pErr, const char *pszFormat, ...)
352{
353 va_list va;
354 va_start(va, pszFormat);
355
356 int rc = audioTestErrorDescAddV(pErr, VERR_GENERAL_FAILURE /** @todo Fudge! */, pszFormat, va);
357
358 va_end(va);
359 return rc;
360}
361
362#if 0
363static int audioTestErrorDescAddRc(PAUDIOTESTERRORDESC pErr, int rc, const char *pszFormat, ...)
364{
365 va_list va;
366 va_start(va, pszFormat);
367
368 int rc2 = audioTestErrorDescAddV(pErr, rc, pszFormat, va);
369
370 va_end(va);
371 return rc2;
372}
373#endif
374
375int AudioTestPathCreateTemp(char *pszPath, size_t cbPath, const char *pszTag)
376{
377 int rc = RTPathTemp(pszPath, cbPath);
378 AssertRCReturn(rc, rc);
379 rc = AudioTestPathCreate(pszPath, cbPath, pszTag);
380 AssertRCReturn(rc, rc);
381
382 return rc;
383}
384
385int AudioTestSetCreate(PAUDIOTESTSET pSet, const char *pszPath, const char *pszTag)
386{
387 int rc;
388
389 audioTestSetInitInternal(pSet);
390
391 if (pszPath)
392 {
393 rc = RTStrCopy(pSet->szPathAbs, sizeof(pSet->szPathAbs), pszPath);
394 AssertRCReturn(rc, rc);
395
396 rc = AudioTestPathCreate(pSet->szPathAbs, sizeof(pSet->szPathAbs), pszTag);
397 }
398 else
399 rc = AudioTestPathCreateTemp(pSet->szPathAbs, sizeof(pSet->szPathAbs), pszTag);
400 AssertRCReturn(rc, rc);
401
402 if (RT_SUCCESS(rc))
403 {
404 char szManifest[RTPATH_MAX];
405 rc = RTStrCopy(szManifest, sizeof(szManifest), pszPath);
406 AssertRCReturn(rc, rc);
407
408 rc = RTPathAppend(szManifest, sizeof(szManifest), AUDIOTEST_MANIFEST_FILE_STR);
409 AssertRCReturn(rc, rc);
410
411 rc = RTFileOpen(&pSet->f.hFile, szManifest,
412 RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
413 AssertRCReturn(rc, rc);
414
415 rc = audioTestManifestWriteLn(pSet, "magic=vkat_ini"); /* VKAT Manifest, .INI-style. */
416 AssertRCReturn(rc, rc);
417 rc = audioTestManifestWriteLn(pSet, "ver=%d", AUDIOTEST_MANIFEST_VER);
418 AssertRCReturn(rc, rc);
419 rc = audioTestManifestWriteLn(pSet, "tag=%s", pszTag);
420 AssertRCReturn(rc, rc);
421
422 char szTime[64];
423 RTTIMESPEC time;
424 if (!RTTimeSpecToString(RTTimeNow(&time), szTime, sizeof(szTime)))
425 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
426
427 rc = audioTestManifestWriteLn(pSet, "date_created=%s", szTime);
428 AssertRCReturn(rc, rc);
429
430 /** @todo Add more stuff here, like hostname, ++? */
431
432 pSet->enmMode = AUDIOTESTSETMODE_TEST;
433 }
434
435 return rc;
436}
437
438void AudioTestSetDestroy(PAUDIOTESTSET pSet)
439{
440 if (!pSet)
441 return;
442
443 if (RTFileIsValid(pSet->f.hFile))
444 {
445 RTFileClose(pSet->f.hFile);
446 pSet->f.hFile = NIL_RTFILE;
447 }
448}
449
450int AudioTestSetOpen(PAUDIOTESTSET pSet, const char *pszPath)
451{
452 audioTestSetInitInternal(pSet);
453
454 char szManifest[RTPATH_MAX];
455 int rc = RTStrCopy(szManifest, sizeof(szManifest), pszPath);
456 AssertRCReturn(rc, rc);
457
458 rc = RTPathAppend(szManifest, sizeof(szManifest), AUDIOTEST_MANIFEST_FILE_STR);
459 AssertRCReturn(rc, rc);
460
461 RTVFSFILE hVfsFile;
462 rc = RTVfsFileOpenNormal(szManifest, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hVfsFile);
463 if (RT_FAILURE(rc))
464 return rc;
465
466 rc = RTIniFileCreateFromVfsFile(&pSet->f.hIniFile, hVfsFile, RTINIFILE_F_READONLY);
467 RTVfsFileRelease(hVfsFile);
468 AssertRCReturn(rc, rc);
469
470 pSet->enmMode = AUDIOTESTSETMODE_VERIFY;
471
472 return rc;
473}
474
475void AudioTestSetClose(PAUDIOTESTSET pSet)
476{
477 AudioTestSetDestroy(pSet);
478}
479
480int AudioTestSetPack(PAUDIOTESTSET pSet, const char *pszOutDir)
481{
482 RT_NOREF(pSet, pszOutDir);
483
484 AssertReturn(audioTestManifestIsOpen(pSet), VERR_WRONG_ORDER);
485 // RTZipTarCmd()
486
487 return VERR_NOT_IMPLEMENTED;
488}
489
490int AudioTestSetUnpack(const char *pszFile, const char *pszOutDir)
491{
492 RT_NOREF(pszFile, pszOutDir);
493
494 // RTZipTarCmd()
495
496 return VERR_NOT_IMPLEMENTED;
497}
498
499int AudioTestSetVerify(PAUDIOTESTSET pSet, const char *pszTag, PAUDIOTESTERRORDESC pErrDesc)
500{
501 AssertReturn(audioTestManifestIsOpen(pSet), VERR_WRONG_ORDER);
502
503 /* We ASSUME the caller has not init'd pErrDesc. */
504 audioTestErrorDescInit(pErrDesc);
505
506 char szVal[_1K]; /** @todo Enough, too much? */
507
508 int rc2 = RTIniFileQueryValue(pSet->f.hIniFile, AUDIOTEST_INI_SEC_HDR_STR, "tag", szVal, sizeof(szVal), NULL);
509 if ( RT_FAILURE(rc2)
510 || RTStrICmp(pszTag, szVal))
511 audioTestErrorDescAdd(pErrDesc, "Tag '%s' does not match with manifest's tag '%s'", pszTag, szVal);
512
513 /* Only return critical stuff not related to actual testing here. */
514 return VINF_SUCCESS;
515}
516
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette