VirtualBox

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

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

AudioTest.cpp: Added missing EOF check in AudioTestWaveFileRead. bugref:10008

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 39.4 KB
Line 
1/* $Id: AudioTest.cpp 89125 2021-05-17 23:19:51Z 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 <package-generated.h>
25#include "product-generated.h"
26
27#include <iprt/buildconfig.h>
28#include <iprt/dir.h>
29#include <iprt/file.h>
30#include <iprt/formats/riff.h>
31#include <iprt/inifile.h>
32#include <iprt/list.h>
33#include <iprt/message.h> /** @todo Get rid of this once we have own log hooks. */
34#include <iprt/rand.h>
35#include <iprt/system.h>
36#include <iprt/uuid.h>
37#include <iprt/vfs.h>
38#include <iprt/zip.h>
39
40#define _USE_MATH_DEFINES
41#include <math.h> /* sin, M_PI */
42
43#include <VBox/version.h>
44#include <VBox/vmm/pdmaudioifs.h>
45#include <VBox/vmm/pdmaudioinline.h>
46
47#include "AudioTest.h"
48
49
50/*********************************************************************************************************************************
51* Defines *
52*********************************************************************************************************************************/
53/** The test manifest file name. */
54#define AUDIOTEST_MANIFEST_FILE_STR "vkat_manifest.ini"
55/** The current test manifest version. */
56#define AUDIOTEST_MANIFEST_VER 1
57
58/** Test manifest header name. */
59#define AUDIOTEST_INI_SEC_HDR_STR "header"
60
61
62/*********************************************************************************************************************************
63* Structures and Typedefs *
64*********************************************************************************************************************************/
65
66
67/*********************************************************************************************************************************
68* Global Variables *
69*********************************************************************************************************************************/
70/** Well-known frequency selection test tones. */
71static const double s_aAudioTestToneFreqsHz[] =
72{
73 349.2282 /*F4*/,
74 440.0000 /*A4*/,
75 523.2511 /*C5*/,
76 698.4565 /*F5*/,
77 880.0000 /*A5*/,
78 1046.502 /*C6*/,
79 1174.659 /*D6*/,
80 1396.913 /*F6*/,
81 1760.0000 /*A6*/
82};
83
84/**
85 * Initializes a test tone by picking a random but well-known frequency (in Hz).
86 *
87 * @returns Randomly picked frequency (in Hz).
88 * @param pTone Pointer to test tone to initialize.
89 * @param pProps PCM properties to use for the test tone.
90 */
91double AudioTestToneInitRandom(PAUDIOTESTTONE pTone, PPDMAUDIOPCMPROPS pProps)
92{
93 /* Pick a frequency from our selection, so that every time a recording starts
94 * we'll hopfully generate a different note. */
95 pTone->rdFreqHz = s_aAudioTestToneFreqsHz[RTRandU32Ex(0, RT_ELEMENTS(s_aAudioTestToneFreqsHz) - 1)];
96 pTone->rdFixed = 2.0 * M_PI * pTone->rdFreqHz / PDMAudioPropsHz(pProps);
97 pTone->uSample = 0;
98
99 memcpy(&pTone->Props, pProps, sizeof(PDMAUDIOPCMPROPS));
100
101 pTone->enmType = AUDIOTESTTONETYPE_SINE; /* Only type implemented so far. */
102
103 return pTone->rdFreqHz;
104}
105
106/**
107 * Writes (and iterates) a given test tone to an output buffer.
108 *
109 * @returns VBox status code.
110 * @param pTone Pointer to test tone to write.
111 * @param pvBuf Pointer to output buffer to write test tone to.
112 * @param cbBuf Size (in bytes) of output buffer.
113 * @param pcbWritten How many bytes were written on success.
114 */
115int AudioTestToneGenerate(PAUDIOTESTTONE pTone, void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
116{
117 /*
118 * Clear the buffer first so we don't need to think about additional channels.
119 */
120 uint32_t cFrames = PDMAudioPropsBytesToFrames(&pTone->Props, cbBuf);
121
122 /* Input cbBuf not necessarily is aligned to the frames, so re-calculate it. */
123 const uint32_t cbToWrite = PDMAudioPropsFramesToBytes(&pTone->Props, cFrames);
124
125 PDMAudioPropsClearBuffer(&pTone->Props, pvBuf, cbBuf, cFrames);
126
127 /*
128 * Generate the select sin wave in the first channel:
129 */
130 uint32_t const cbFrame = PDMAudioPropsFrameSize(&pTone->Props);
131 double const rdFixed = pTone->rdFixed;
132 uint64_t iSrcFrame = pTone->uSample;
133 switch (PDMAudioPropsSampleSize(&pTone->Props))
134 {
135 case 1:
136 /* untested */
137 if (PDMAudioPropsIsSigned(&pTone->Props))
138 {
139 int8_t *piSample = (int8_t *)pvBuf;
140 while (cFrames-- > 0)
141 {
142 *piSample = (int8_t)(126 /*Amplitude*/ * sin(rdFixed * iSrcFrame));
143 iSrcFrame++;
144 piSample += cbFrame;
145 }
146 }
147 else
148 {
149 /* untested */
150 uint8_t *pbSample = (uint8_t *)pvBuf;
151 while (cFrames-- > 0)
152 {
153 *pbSample = (uint8_t)(126 /*Amplitude*/ * sin(rdFixed * iSrcFrame) + 0x80);
154 iSrcFrame++;
155 pbSample += cbFrame;
156 }
157 }
158 break;
159
160 case 2:
161 if (PDMAudioPropsIsSigned(&pTone->Props))
162 {
163 int16_t *piSample = (int16_t *)pvBuf;
164 while (cFrames-- > 0)
165 {
166 *piSample = (int16_t)(32760 /*Amplitude*/ * sin(rdFixed * iSrcFrame));
167 iSrcFrame++;
168 piSample = (int16_t *)((uint8_t *)piSample + cbFrame);
169 }
170 }
171 else
172 {
173 /* untested */
174 uint16_t *puSample = (uint16_t *)pvBuf;
175 while (cFrames-- > 0)
176 {
177 *puSample = (uint16_t)(32760 /*Amplitude*/ * sin(rdFixed * iSrcFrame) + 0x8000);
178 iSrcFrame++;
179 puSample = (uint16_t *)((uint8_t *)puSample + cbFrame);
180 }
181 }
182 break;
183
184 case 4:
185 /* untested */
186 if (PDMAudioPropsIsSigned(&pTone->Props))
187 {
188 int32_t *piSample = (int32_t *)pvBuf;
189 while (cFrames-- > 0)
190 {
191 *piSample = (int32_t)((32760 << 16) /*Amplitude*/ * sin(rdFixed * iSrcFrame));
192 iSrcFrame++;
193 piSample = (int32_t *)((uint8_t *)piSample + cbFrame);
194 }
195 }
196 else
197 {
198 uint32_t *puSample = (uint32_t *)pvBuf;
199 while (cFrames-- > 0)
200 {
201 *puSample = (uint32_t)((32760 << 16) /*Amplitude*/ * sin(rdFixed * iSrcFrame) + UINT32_C(0x80000000));
202 iSrcFrame++;
203 puSample = (uint32_t *)((uint8_t *)puSample + cbFrame);
204 }
205 }
206 break;
207
208 default:
209 AssertFailedReturn(VERR_NOT_SUPPORTED);
210 }
211
212 pTone->uSample = iSrcFrame;
213
214 if (pcbWritten)
215 *pcbWritten = cbToWrite;
216
217 return VINF_SUCCESS;
218}
219
220/**
221 * Initializes an audio test tone parameters struct with random values.
222 * @param pToneParams Test tone parameters to initialize.
223 * @param pProps PCM properties to use for the test tone.
224 */
225int AudioTestToneParamsInitRandom(PAUDIOTESTTONEPARMS pToneParams, PPDMAUDIOPCMPROPS pProps)
226{
227 AssertReturn(PDMAudioPropsAreValid(pProps), VERR_INVALID_PARAMETER);
228
229 memcpy(&pToneParams->Props, pProps, sizeof(PDMAUDIOPCMPROPS));
230
231 /** @todo Make this a bit more sophisticated later, e.g. muting and prequel/sequel are not very balanced. */
232
233 pToneParams->msPrequel = RTRandU32Ex(0, RT_MS_5SEC);
234#ifdef DEBUG_andy
235 pToneParams->msDuration = RTRandU32Ex(0, RT_MS_1SEC);
236#else
237 pToneParams->msDuration = RTRandU32Ex(0, RT_MS_10SEC); /** @todo Probably a bit too long, but let's see. */
238#endif
239 pToneParams->msSequel = RTRandU32Ex(0, RT_MS_5SEC);
240 pToneParams->uVolumePercent = RTRandU32Ex(0, 100);
241
242 return VINF_SUCCESS;
243}
244
245/**
246 * Generates a tag.
247 *
248 * @returns VBox status code.
249 * @param pszTag The output buffer.
250 * @param cbTag The size of the output buffer.
251 * AUDIOTEST_TAG_MAX is a good size.
252 */
253int AudioTestGenTag(char *pszTag, size_t cbTag)
254{
255 RTUUID UUID;
256 int rc = RTUuidCreate(&UUID);
257 AssertRCReturn(rc, rc);
258 rc = RTUuidToStr(&UUID, pszTag, cbTag);
259 AssertRCReturn(rc, rc);
260 return rc;
261}
262
263
264/**
265 * Return the tag to use in the given buffer, generating one if needed.
266 *
267 * @returns VBox status code.
268 * @param pszTag The output buffer.
269 * @param cbTag The size of the output buffer.
270 * AUDIOTEST_TAG_MAX is a good size.
271 * @param pszTagUser User specified tag, optional.
272 */
273int AudioTestCopyOrGenTag(char *pszTag, size_t cbTag, const char *pszTagUser)
274{
275 if (pszTagUser && *pszTagUser)
276 return RTStrCopy(pszTag, cbTag, pszTagUser);
277 return AudioTestGenTag(pszTag, cbTag);
278}
279
280
281/**
282 * Creates a new path (directory) for a specific audio test set tag.
283 *
284 * @returns VBox status code.
285 * @param pszPath On input, specifies the absolute base path where to create the test set path.
286 * On output this specifies the absolute path created.
287 * @param cbPath Size (in bytes) of \a pszPath.
288 * @param pszTag Tag to use for path creation.
289 *
290 * @note Can be used multiple times with the same tag; a sub directory with an ISO time string will be used
291 * on each call.
292 */
293int AudioTestPathCreate(char *pszPath, size_t cbPath, const char *pszTag)
294{
295 char szTag[AUDIOTEST_TAG_MAX];
296 int rc = AudioTestCopyOrGenTag(szTag, sizeof(szTag), pszTag);
297 AssertRCReturn(rc, rc);
298
299 char szName[RT_ELEMENTS(AUDIOTEST_PATH_PREFIX_STR) + AUDIOTEST_TAG_MAX + 4];
300 if (RTStrPrintf2(szName, sizeof(szName), "%s-%s", AUDIOTEST_PATH_PREFIX_STR, szTag) < 0)
301 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
302
303 rc = RTPathAppend(pszPath, cbPath, szName);
304 AssertRCReturn(rc, rc);
305
306 char szTime[64];
307 RTTIMESPEC time;
308 if (!RTTimeSpecToString(RTTimeNow(&time), szTime, sizeof(szTime)))
309 return VERR_BUFFER_UNDERFLOW;
310
311 rc = RTPathAppend(pszPath, cbPath, szTime);
312 AssertRCReturn(rc, rc);
313
314 return RTDirCreateFullPath(pszPath, RTFS_UNIX_IRWXU);
315}
316
317/**
318 * Writes string data to a test set manifest.
319 *
320 * @returns VBox status code.
321 * @param pSet Test set to write manifest for.
322 * @param pszFormat Format string to write.
323 * @param args Variable arguments for \a pszFormat.
324 */
325static int audioTestManifestWriteV(PAUDIOTESTSET pSet, const char *pszFormat, va_list args)
326{
327 char *psz = NULL;
328 RTStrAPrintfV(&psz, pszFormat, args);
329 AssertPtr(psz);
330
331 /** @todo Use RTIniFileWrite once its implemented. */
332 int rc = RTFileWrite(pSet->f.hFile, psz, strlen(psz), NULL);
333 AssertRC(rc);
334
335 RTStrFree(psz);
336
337 return rc;
338}
339
340/**
341 * Writes a terminated string line to a test set manifest.
342 * Convenience function.
343 *
344 * @returns VBox status code.
345 * @param pSet Test set to write manifest for.
346 * @param pszFormat Format string to write.
347 * @param ... Variable arguments for \a pszFormat. Optional.
348 */
349static int audioTestManifestWriteLn(PAUDIOTESTSET pSet, const char *pszFormat, ...)
350{
351 va_list va;
352 va_start(va, pszFormat);
353
354 int rc = audioTestManifestWriteV(pSet, pszFormat, va);
355 AssertRC(rc);
356
357 va_end(va);
358
359 /** @todo Keep it as simple as possible for now. Improve this later. */
360 rc = RTFileWrite(pSet->f.hFile, "\n", strlen("\n"), NULL);
361 AssertRC(rc);
362
363 return rc;
364}
365
366/**
367 * Writes a section entry to a test set manifest.
368 *
369 * @returns VBox status code.
370 * @param pSet Test set to write manifest for.
371 * @param pszSection Format string of section to write.
372 * @param ... Variable arguments for \a pszSection. Optional.
373 */
374static int audioTestManifestWriteSection(PAUDIOTESTSET pSet, const char *pszSection, ...)
375{
376 va_list va;
377 va_start(va, pszSection);
378
379 /** @todo Keep it as simple as possible for now. Improve this later. */
380 int rc = RTFileWrite(pSet->f.hFile, "[", strlen("["), NULL);
381 AssertRC(rc);
382
383 rc = audioTestManifestWriteV(pSet, pszSection, va);
384 AssertRC(rc);
385
386 rc = RTFileWrite(pSet->f.hFile, "]\n", strlen("]\n"), NULL);
387 AssertRC(rc);
388
389 va_end(va);
390
391 return rc;
392}
393
394/**
395 * Initializes an audio test set, internal function.
396 * @param pSet Test set to initialize.
397 */
398static void audioTestSetInitInternal(PAUDIOTESTSET pSet)
399{
400 pSet->f.hFile = NIL_RTFILE;
401
402 RTListInit(&pSet->lstObj);
403}
404
405/**
406 * Returns whether a test set's manifest file is open (and thus ready) or not.
407 *
408 * @returns \c true if open (and ready), or \c false if not.
409 * @retval VERR_
410 * @param pSet Test set to return open status for.
411 */
412static bool audioTestManifestIsOpen(PAUDIOTESTSET pSet)
413{
414 if ( pSet->enmMode == AUDIOTESTSETMODE_TEST
415 && pSet->f.hFile != NIL_RTFILE)
416 return true;
417 else if ( pSet->enmMode == AUDIOTESTSETMODE_VERIFY
418 && pSet->f.hIniFile != NIL_RTINIFILE)
419 return true;
420
421 return false;
422}
423
424/**
425 * Initializes an audio test error description.
426 *
427 * @param pErr Test error description to initialize.
428 */
429static void audioTestErrorDescInit(PAUDIOTESTERRORDESC pErr)
430{
431 RTListInit(&pErr->List);
432 pErr->cErrors = 0;
433}
434
435/**
436 * Destroys an audio test error description.
437 *
438 * @param pErr Test error description to destroy.
439 */
440void AudioTestErrorDescDestroy(PAUDIOTESTERRORDESC pErr)
441{
442 if (!pErr)
443 return;
444
445 PAUDIOTESTERRORENTRY pErrEntry, pErrEntryNext;
446 RTListForEachSafe(&pErr->List, pErrEntry, pErrEntryNext, AUDIOTESTERRORENTRY, Node)
447 {
448 RTListNodeRemove(&pErrEntry->Node);
449
450 RTMemFree(pErrEntry);
451
452 Assert(pErr->cErrors);
453 pErr->cErrors--;
454 }
455
456 Assert(pErr->cErrors == 0);
457}
458
459/**
460 * Returns if an audio test error description contains any errors or not.
461 *
462 * @returns \c true if it contains errors, or \c false if not.
463 *
464 * @param pErr Test error description to return error status for.
465 */
466bool AudioTestErrorDescFailed(PAUDIOTESTERRORDESC pErr)
467{
468 if (pErr->cErrors)
469 {
470 Assert(!RTListIsEmpty(&pErr->List));
471 return true;
472 }
473
474 return false;
475}
476
477/**
478 * Adds a single error entry to an audio test error description, va_list version.
479 *
480 * @returns VBox status code.
481 * @param pErr Test error description to add entry for.
482 * @param rc Result code of entry to add.
483 * @param pszDesc Error description format string to add.
484 * @param args Optional format arguments of \a pszDesc to add.
485 */
486static int audioTestErrorDescAddV(PAUDIOTESTERRORDESC pErr, int rc, const char *pszDesc, va_list args)
487{
488 PAUDIOTESTERRORENTRY pEntry = (PAUDIOTESTERRORENTRY)RTMemAlloc(sizeof(AUDIOTESTERRORENTRY));
489 AssertReturn(pEntry, VERR_NO_MEMORY);
490
491 if (RTStrPrintf2V(pEntry->szDesc, sizeof(pEntry->szDesc), pszDesc, args) < 0)
492 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
493
494 pEntry->rc = rc;
495
496 RTListAppend(&pErr->List, &pEntry->Node);
497
498 pErr->cErrors++;
499
500 return VINF_SUCCESS;
501}
502
503/**
504 * Adds a single error entry to an audio test error description, va_list version.
505 *
506 * @returns VBox status code.
507 * @param pErr Test error description to add entry for.
508 * @param pszDesc Error description format string to add.
509 * @param ... Optional format arguments of \a pszDesc to add.
510 */
511static int audioTestErrorDescAdd(PAUDIOTESTERRORDESC pErr, const char *pszDesc, ...)
512{
513 va_list va;
514 va_start(va, pszDesc);
515
516 int rc = audioTestErrorDescAddV(pErr, VERR_GENERAL_FAILURE /** @todo Fudge! */, pszDesc, va);
517
518 va_end(va);
519 return rc;
520}
521
522#if 0
523static int audioTestErrorDescAddRc(PAUDIOTESTERRORDESC pErr, int rc, const char *pszFormat, ...)
524{
525 va_list va;
526 va_start(va, pszFormat);
527
528 int rc2 = audioTestErrorDescAddV(pErr, rc, pszFormat, va);
529
530 va_end(va);
531 return rc2;
532}
533#endif
534
535/**
536 * Creates a new temporary directory with a specific (test) tag.
537 *
538 * @returns VBox status code.
539 * @param pszPath Where to return the absolute path of the created directory on success.
540 * @param cbPath Size (in bytes) of \a pszPath.
541 * @param pszTag Tag name to use for directory creation.
542 *
543 * @note Can be used multiple times with the same tag; a sub directory with an ISO time string will be used
544 * on each call.
545 */
546int AudioTestPathCreateTemp(char *pszPath, size_t cbPath, const char *pszTag)
547{
548 AssertReturn(pszTag && strlen(pszTag) <= AUDIOTEST_TAG_MAX, VERR_INVALID_PARAMETER);
549
550 char szPath[RTPATH_MAX];
551
552 int rc = RTPathTemp(szPath, sizeof(szPath));
553 AssertRCReturn(rc, rc);
554 rc = AudioTestPathCreate(szPath, sizeof(szPath), pszTag);
555 AssertRCReturn(rc, rc);
556
557 return RTStrCopy(pszPath, cbPath, szPath);
558}
559
560/**
561 * Returns the absolute path of a given audio test set object.
562 *
563 * @returns VBox status code.
564 * @param pSet Test set the object contains.
565 * @param pszPathAbs Where to return the absolute path on success.
566 * @param cbPathAbs Size (in bytes) of \a pszPathAbs.
567 * @param pszObjName Name of the object to create absolute path for.
568 */
569DECLINLINE(int) audioTestSetGetObjPath(PAUDIOTESTSET pSet, char *pszPathAbs, size_t cbPathAbs, const char *pszObjName)
570{
571 return RTPathJoin(pszPathAbs, cbPathAbs, pSet->szPathAbs, pszObjName);
572}
573
574/**
575 * Creates a new audio test set.
576 *
577 * @returns VBox status code.
578 * @param pSet Test set to create.
579 * @param pszPath Absolute path to use for the test set's temporary directory.
580 * If NULL, the OS' temporary directory will be used.
581 * @param pszTag Tag name to use for this test set.
582 */
583int AudioTestSetCreate(PAUDIOTESTSET pSet, const char *pszPath, const char *pszTag)
584{
585 audioTestSetInitInternal(pSet);
586
587 int rc = AudioTestCopyOrGenTag(pSet->szTag, sizeof(pSet->szTag), pszTag);
588 AssertRCReturn(rc, rc);
589
590 if (pszPath)
591 {
592 rc = RTStrCopy(pSet->szPathAbs, sizeof(pSet->szPathAbs), pszPath);
593 AssertRCReturn(rc, rc);
594
595 rc = AudioTestPathCreate(pSet->szPathAbs, sizeof(pSet->szPathAbs), pSet->szTag);
596 }
597 else
598 rc = AudioTestPathCreateTemp(pSet->szPathAbs, sizeof(pSet->szPathAbs), pSet->szTag);
599 AssertRCReturn(rc, rc);
600
601 if (RT_SUCCESS(rc))
602 {
603 char szManifest[RTPATH_MAX];
604 rc = RTStrCopy(szManifest, sizeof(szManifest), pSet->szPathAbs);
605 AssertRCReturn(rc, rc);
606
607 rc = RTPathAppend(szManifest, sizeof(szManifest), AUDIOTEST_MANIFEST_FILE_STR);
608 AssertRCReturn(rc, rc);
609
610 rc = RTFileOpen(&pSet->f.hFile, szManifest,
611 RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
612 AssertRCReturn(rc, rc);
613
614 rc = audioTestManifestWriteSection(pSet, "header");
615 AssertRCReturn(rc, rc);
616
617 rc = audioTestManifestWriteLn(pSet, "magic=vkat_ini"); /* VKAT Manifest, .INI-style. */
618 AssertRCReturn(rc, rc);
619 rc = audioTestManifestWriteLn(pSet, "ver=%d", AUDIOTEST_MANIFEST_VER);
620 AssertRCReturn(rc, rc);
621 rc = audioTestManifestWriteLn(pSet, "tag=%s", pSet->szTag);
622 AssertRCReturn(rc, rc);
623
624 char szVal[64];
625 RTTIMESPEC time;
626 if (!RTTimeSpecToString(RTTimeNow(&time), szVal, sizeof(szVal)))
627 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
628
629 rc = audioTestManifestWriteLn(pSet, "date_created=%s", szVal);
630 AssertRCReturn(rc, rc);
631
632 rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szVal, sizeof(szVal));
633 AssertRCReturn(rc, rc);
634 rc = audioTestManifestWriteLn(pSet, "os_product=%s", szVal);
635 AssertRCReturn(rc, rc);
636 rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szVal, sizeof(szVal));
637 AssertRCReturn(rc, rc);
638 rc = audioTestManifestWriteLn(pSet, "os_rel=%s", szVal);
639 AssertRCReturn(rc, rc);
640 rc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szVal, sizeof(szVal));
641 AssertRCReturn(rc, rc);
642 rc = audioTestManifestWriteLn(pSet, "os_ver=%s", szVal);
643 AssertRCReturn(rc, rc);
644
645 rc = audioTestManifestWriteLn(pSet, "vbox_ver=%s r%u %s (%s %s)",
646 VBOX_VERSION_STRING, RTBldCfgRevision(),
647 RTBldCfgTargetDotArch(), __DATE__, __TIME__);
648 AssertRCReturn(rc, rc);
649
650 pSet->enmMode = AUDIOTESTSETMODE_TEST;
651 }
652
653 return rc;
654}
655
656/**
657 * Destroys a test set.
658 *
659 * @returns VBox status code.
660 * @param pSet Test set to destroy.
661 */
662int AudioTestSetDestroy(PAUDIOTESTSET pSet)
663{
664 if (!pSet)
665 return VINF_SUCCESS;
666
667 int rc = VINF_SUCCESS;
668
669 PAUDIOTESTOBJ pObj, pObjNext;
670 RTListForEachSafe(&pSet->lstObj, pObj, pObjNext, AUDIOTESTOBJ, Node)
671 {
672 rc = AudioTestSetObjClose(pObj);
673 if (RT_SUCCESS(rc))
674 {
675 RTListNodeRemove(&pObj->Node);
676 RTMemFree(pObj);
677
678 Assert(pSet->cObj);
679 pSet->cObj--;
680 }
681 else
682 break;
683 }
684
685 if (RT_FAILURE(rc))
686 return rc;
687
688 Assert(pSet->cObj == 0);
689
690 if (RTFileIsValid(pSet->f.hFile))
691 {
692 RTFileClose(pSet->f.hFile);
693 pSet->f.hFile = NIL_RTFILE;
694 }
695
696 return rc;
697}
698
699/**
700 * Opens an existing audio test set.
701 *
702 * @returns VBox status code.
703 * @param pSet Test set to open.
704 * @param pszPath Absolute path of the test set to open.
705 */
706int AudioTestSetOpen(PAUDIOTESTSET pSet, const char *pszPath)
707{
708 audioTestSetInitInternal(pSet);
709
710 char szManifest[RTPATH_MAX];
711 int rc = RTPathJoin(szManifest, sizeof(szManifest), pszPath, AUDIOTEST_MANIFEST_FILE_STR);
712 AssertRCReturn(rc, rc);
713
714 RTVFSFILE hVfsFile;
715 rc = RTVfsFileOpenNormal(szManifest, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hVfsFile);
716 if (RT_FAILURE(rc))
717 return rc;
718
719 rc = RTIniFileCreateFromVfsFile(&pSet->f.hIniFile, hVfsFile, RTINIFILE_F_READONLY);
720 RTVfsFileRelease(hVfsFile);
721 AssertRCReturn(rc, rc);
722
723 pSet->enmMode = AUDIOTESTSETMODE_VERIFY;
724
725 return rc;
726}
727
728/**
729 * Closes an opened audio test set.
730 *
731 * @param pSet Test set to close.
732 */
733void AudioTestSetClose(PAUDIOTESTSET pSet)
734{
735 AudioTestSetDestroy(pSet);
736}
737
738/**
739 * Physically wipes all related test set files off the disk.
740 *
741 * @returns VBox status code.
742 * @param pSet Test set to wipe.
743 */
744int AudioTestSetWipe(PAUDIOTESTSET pSet)
745{
746 AssertPtrReturn(pSet, VERR_INVALID_POINTER);
747
748 int rc = VINF_SUCCESS;
749 char szFilePath[RTPATH_MAX];
750
751 PAUDIOTESTOBJ pObj;
752 RTListForEach(&pSet->lstObj, pObj, AUDIOTESTOBJ, Node)
753 {
754 int rc2 = AudioTestSetObjClose(pObj);
755 if (RT_SUCCESS(rc2))
756 {
757 rc2 = audioTestSetGetObjPath(pSet, szFilePath, sizeof(szFilePath), pObj->szName);
758 if (RT_SUCCESS(rc2))
759 rc2 = RTFileDelete(szFilePath);
760 }
761
762 if (RT_SUCCESS(rc))
763 rc = rc2;
764 /* Keep going. */
765 }
766
767 if (RT_SUCCESS(rc))
768 {
769 rc = RTPathJoin(szFilePath, sizeof(szFilePath), pSet->szPathAbs, AUDIOTEST_MANIFEST_FILE_STR);
770 if (RT_SUCCESS(rc))
771 rc = RTFileDelete(szFilePath);
772 }
773
774 /* Remove the (hopefully now empty) directory. Otherwise let this fail. */
775 if (RT_SUCCESS(rc))
776 rc = RTDirRemove(pSet->szPathAbs);
777
778 return rc;
779}
780
781/**
782 * Creates and registers a new audio test object to a test set.
783 *
784 * @returns VBox status code.
785 * @param pSet Test set to create and register new object for.
786 * @param pszName Name of new object to create.
787 * @param ppObj Where to return the pointer to the newly created object on success.
788 */
789int AudioTestSetObjCreateAndRegister(PAUDIOTESTSET pSet, const char *pszName, PAUDIOTESTOBJ *ppObj)
790{
791 AssertPtrReturn(pszName, VERR_INVALID_POINTER);
792
793 PAUDIOTESTOBJ pObj = (PAUDIOTESTOBJ)RTMemAlloc(sizeof(AUDIOTESTOBJ));
794 AssertPtrReturn(pObj, VERR_NO_MEMORY);
795
796 if (RTStrPrintf2(pObj->szName, sizeof(pObj->szName), "%04RU32-%s", pSet->cObj, pszName) <= 0)
797 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
798
799 /** @todo Generalize this function more once we have more object types. */
800
801 char szObjPathAbs[RTPATH_MAX];
802 int rc = audioTestSetGetObjPath(pSet, szObjPathAbs, sizeof(szObjPathAbs), pObj->szName);
803 if (RT_SUCCESS(rc))
804 {
805 rc = RTFileOpen(&pObj->File.hFile, szObjPathAbs, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
806 if (RT_SUCCESS(rc))
807 {
808 pObj->enmType = AUDIOTESTOBJTYPE_FILE;
809
810 RTListAppend(&pSet->lstObj, &pObj->Node);
811 pSet->cObj++;
812
813 *ppObj = pObj;
814 }
815 }
816
817 if (RT_FAILURE(rc))
818 RTMemFree(pObj);
819
820 return rc;
821}
822
823/**
824 * Writes to a created audio test object.
825 *
826 * @returns VBox status code.
827 * @param pObj Audio test object to write to.
828 * @param pvBuf Pointer to data to write.
829 * @param cbBuf Size (in bytes) of \a pvBuf to write.
830 */
831int AudioTestSetObjWrite(PAUDIOTESTOBJ pObj, void *pvBuf, size_t cbBuf)
832{
833 /** @todo Generalize this function more once we have more object types. */
834 AssertReturn(pObj->enmType == AUDIOTESTOBJTYPE_FILE, VERR_INVALID_PARAMETER);
835
836 return RTFileWrite(pObj->File.hFile, pvBuf, cbBuf, NULL);
837}
838
839/**
840 * Closes an opened audio test object.
841 *
842 * @returns VBox status code.
843 * @param pObj Audio test object to close.
844 */
845int AudioTestSetObjClose(PAUDIOTESTOBJ pObj)
846{
847 if (!pObj)
848 return VINF_SUCCESS;
849
850 /** @todo Generalize this function more once we have more object types. */
851 AssertReturn(pObj->enmType == AUDIOTESTOBJTYPE_FILE, VERR_INVALID_PARAMETER);
852
853 int rc = VINF_SUCCESS;
854
855 if (RTFileIsValid(pObj->File.hFile))
856 {
857 rc = RTFileClose(pObj->File.hFile);
858 pObj->File.hFile = NIL_RTFILE;
859 }
860
861 return rc;
862}
863
864/**
865 * Packs an audio test so that it's ready for transmission.
866 *
867 * @returns VBox status code.
868 * @param pSet Test set to pack.
869 * @param pszOutDir Directory where to store the packed test set.
870 * @param pszFileName Where to return the final name of the packed test set. Optional and can be NULL.
871 * @param cbFileName Size (in bytes) of \a pszFileName.
872 */
873int AudioTestSetPack(PAUDIOTESTSET pSet, const char *pszOutDir, char *pszFileName, size_t cbFileName)
874{
875 AssertReturn(!pszFileName || cbFileName, VERR_INVALID_PARAMETER);
876 AssertReturn(audioTestManifestIsOpen(pSet), VERR_WRONG_ORDER);
877
878 /** @todo Check and deny if \a pszOutDir is part of the set's path. */
879
880 char szOutName[RT_ELEMENTS(AUDIOTEST_PATH_PREFIX_STR) + AUDIOTEST_TAG_MAX + 16];
881 if (RTStrPrintf2(szOutName, sizeof(szOutName), "%s-%s.tar.gz", AUDIOTEST_PATH_PREFIX_STR, pSet->szTag) <= 0)
882 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
883
884 char szOutPath[RTPATH_MAX];
885 int rc = RTPathJoin(szOutPath, sizeof(szOutPath), pszOutDir, szOutName);
886 AssertRCReturn(rc, rc);
887
888 const char *apszArgs[10];
889 unsigned cArgs = 0;
890
891 apszArgs[cArgs++] = "AudioTest";
892 apszArgs[cArgs++] = "--create";
893 apszArgs[cArgs++] = "--gzip";
894 apszArgs[cArgs++] = "--directory";
895 apszArgs[cArgs++] = pSet->szPathAbs;
896 apszArgs[cArgs++] = "--file";
897 apszArgs[cArgs++] = szOutPath;
898 apszArgs[cArgs++] = ".";
899
900 RTEXITCODE rcExit = RTZipTarCmd(cArgs, (char **)apszArgs);
901 if (rcExit != RTEXITCODE_SUCCESS)
902 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
903
904 if (RT_SUCCESS(rc))
905 {
906 if (pszFileName)
907 rc = RTStrCopy(pszFileName, cbFileName, szOutPath);
908 }
909
910 return rc;
911}
912
913/**
914 * Unpacks a formerly packed audio test set.
915 *
916 * @returns VBox status code.
917 * @param pszFile Test set file to unpack.
918 * @param pszOutDir Directory where to unpack the test set into.
919 * If the directory does not exist it will be created.
920 */
921int AudioTestSetUnpack(const char *pszFile, const char *pszOutDir)
922{
923 RT_NOREF(pszFile, pszOutDir);
924
925 // RTZipTarCmd()
926
927 return VERR_NOT_IMPLEMENTED;
928}
929
930/**
931 * Verifies an opened audio test set.
932 *
933 * @returns VBox status code.
934 * @param pSet Test set to verify.
935 * @param pszTag Tag to use for verification purpose.
936 * @param pErrDesc Where to return the test verification errors.
937 *
938 * @note Test verification errors have to be checked for errors, regardless of the
939 * actual return code.
940 */
941int AudioTestSetVerify(PAUDIOTESTSET pSet, const char *pszTag, PAUDIOTESTERRORDESC pErrDesc)
942{
943 AssertReturn(audioTestManifestIsOpen(pSet), VERR_WRONG_ORDER);
944
945 /* We ASSUME the caller has not init'd pErrDesc. */
946 audioTestErrorDescInit(pErrDesc);
947
948 char szVal[_1K]; /** @todo Enough, too much? */
949
950 int rc2 = RTIniFileQueryValue(pSet->f.hIniFile, AUDIOTEST_INI_SEC_HDR_STR, "tag", szVal, sizeof(szVal), NULL);
951 if ( RT_FAILURE(rc2)
952 || RTStrICmp(pszTag, szVal))
953 audioTestErrorDescAdd(pErrDesc, "Tag '%s' does not match with manifest's tag '%s'", pszTag, szVal);
954
955 /* Only return critical stuff not related to actual testing here. */
956 return VINF_SUCCESS;
957}
958
959
960/*********************************************************************************************************************************
961* WAVE File Reader. *
962*********************************************************************************************************************************/
963/**
964 * Opens a wave (.WAV) file for reading.
965 *
966 * @returns VBox status code.
967 * @param pszFile The file to open.
968 * @param pWaveFile The open wave file structure to fill in on success.
969 */
970int AudioTestWaveFileOpen(const char *pszFile, PAUDIOTESTWAVEFILE pWaveFile)
971{
972 RT_ZERO(pWaveFile->Props);
973 pWaveFile->hFile = NIL_RTFILE;
974 int rc = RTFileOpen(&pWaveFile->hFile, pszFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
975 if (RT_FAILURE(rc))
976 return rc;
977 uint64_t cbFile = 0;
978 rc = RTFileQuerySize(pWaveFile->hFile, &cbFile);
979 if (RT_SUCCESS(rc))
980 {
981 union
982 {
983 uint8_t ab[512];
984 struct
985 {
986 RTRIFFHDR Hdr;
987 RTRIFFWAVEFMTCHUNK Fmt;
988 } Wave;
989 RTRIFFLIST List;
990 RTRIFFCHUNK Chunk;
991 RTRIFFWAVEDATACHUNK Data;
992 } uBuf;
993
994 rc = RTFileRead(pWaveFile->hFile, &uBuf.Wave, sizeof(uBuf.Wave), NULL);
995 if (RT_SUCCESS(rc))
996 {
997 rc = VERR_VFS_UNKNOWN_FORMAT;
998 if ( uBuf.Wave.Hdr.uMagic == RTRIFFHDR_MAGIC
999 && uBuf.Wave.Hdr.uFileType == RTRIFF_FILE_TYPE_WAVE
1000 && uBuf.Wave.Fmt.Chunk.uMagic == RTRIFFWAVEFMT_MAGIC
1001 && uBuf.Wave.Fmt.Chunk.cbChunk >= sizeof(uBuf.Wave.Fmt.Data))
1002 {
1003 if (uBuf.Wave.Hdr.cbFile != cbFile - sizeof(RTRIFFCHUNK))
1004 RTMsgWarning("%s: File size mismatch: %#x, actual %#RX64 (ignored)",
1005 pszFile, uBuf.Wave.Hdr.cbFile, cbFile - sizeof(RTRIFFCHUNK));
1006 rc = VERR_VFS_BOGUS_FORMAT;
1007 if (uBuf.Wave.Fmt.Data.uFormatTag != RTRIFFWAVEFMT_TAG_PCM)
1008 RTMsgError("%s: Unsupported uFormatTag value: %u (expected 1)", pszFile, uBuf.Wave.Fmt.Data.uFormatTag);
1009 else if ( uBuf.Wave.Fmt.Data.cBitsPerSample != 8
1010 && uBuf.Wave.Fmt.Data.cBitsPerSample != 16
1011 && uBuf.Wave.Fmt.Data.cBitsPerSample != 32)
1012 RTMsgError("%s: Unsupported cBitsPerSample value: %u", pszFile, uBuf.Wave.Fmt.Data.cBitsPerSample);
1013 else if ( uBuf.Wave.Fmt.Data.cChannels < 1
1014 || uBuf.Wave.Fmt.Data.cChannels >= 16)
1015 RTMsgError("%s: Unsupported cChannels value: %u (expected 1..15)", pszFile, uBuf.Wave.Fmt.Data.cChannels);
1016 else if ( uBuf.Wave.Fmt.Data.uHz < 4096
1017 || uBuf.Wave.Fmt.Data.uHz > 768000)
1018 RTMsgError("%s: Unsupported uHz value: %u (expected 4096..768000)", pszFile, uBuf.Wave.Fmt.Data.uHz);
1019 else if (uBuf.Wave.Fmt.Data.cbFrame != uBuf.Wave.Fmt.Data.cChannels * uBuf.Wave.Fmt.Data.cBitsPerSample / 8)
1020 RTMsgError("%s: Invalid cbFrame value: %u (expected %u)", pszFile, uBuf.Wave.Fmt.Data.cbFrame,
1021 uBuf.Wave.Fmt.Data.cChannels * uBuf.Wave.Fmt.Data.cBitsPerSample / 8);
1022 else if (uBuf.Wave.Fmt.Data.cbRate != uBuf.Wave.Fmt.Data.cbFrame * uBuf.Wave.Fmt.Data.uHz)
1023 RTMsgError("%s: Invalid cbRate value: %u (expected %u)", pszFile, uBuf.Wave.Fmt.Data.cbRate,
1024 uBuf.Wave.Fmt.Data.cbFrame * uBuf.Wave.Fmt.Data.uHz);
1025 else
1026 {
1027 /*
1028 * Copy out the data we need from the file format structure.
1029 */
1030 PDMAudioPropsInit(&pWaveFile->Props, uBuf.Wave.Fmt.Data.cBitsPerSample / 8, true /*fSigned*/,
1031 uBuf.Wave.Fmt.Data.cChannels, uBuf.Wave.Fmt.Data.uHz);
1032 pWaveFile->offSamples = sizeof(RTRIFFHDR) + sizeof(RTRIFFCHUNK) + uBuf.Wave.Fmt.Chunk.cbChunk;
1033
1034 /*
1035 * Find the 'data' chunk with the audio samples.
1036 *
1037 * There can be INFO lists both preceeding this and succeeding
1038 * it, containing IART and other things we can ignored. Thus
1039 * we read a list header here rather than just a chunk header,
1040 * since it doesn't matter if we read 4 bytes extra as
1041 * AudioTestWaveFileRead uses RTFileReadAt anyway.
1042 */
1043 rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
1044 for (uint32_t i = 0;
1045 i < 128
1046 && RT_SUCCESS(rc)
1047 && uBuf.Chunk.uMagic != RTRIFFWAVEDATACHUNK_MAGIC
1048 && (uint64_t)uBuf.Chunk.cbChunk + sizeof(RTRIFFCHUNK) * 2 <= cbFile - pWaveFile->offSamples;
1049 i++)
1050 {
1051 if ( uBuf.List.uMagic == RTRIFFLIST_MAGIC
1052 && uBuf.List.uListType == RTRIFFLIST_TYPE_INFO)
1053 { /*skip*/ }
1054 else if (uBuf.Chunk.uMagic == RTRIFFPADCHUNK_MAGIC)
1055 { /*skip*/ }
1056 else
1057 break;
1058 pWaveFile->offSamples += sizeof(RTRIFFCHUNK) + uBuf.Chunk.cbChunk;
1059 rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
1060 }
1061 if (RT_SUCCESS(rc))
1062 {
1063 pWaveFile->offSamples += sizeof(uBuf.Data.Chunk);
1064 pWaveFile->cbSamples = (uint32_t)cbFile - pWaveFile->offSamples;
1065
1066 rc = VERR_VFS_BOGUS_FORMAT;
1067 if ( uBuf.Data.Chunk.uMagic == RTRIFFWAVEDATACHUNK_MAGIC
1068 && uBuf.Data.Chunk.cbChunk <= pWaveFile->cbSamples
1069 && PDMAudioPropsIsSizeAligned(&pWaveFile->Props, uBuf.Data.Chunk.cbChunk))
1070 {
1071 pWaveFile->cbSamples = uBuf.Data.Chunk.cbChunk;
1072
1073 /*
1074 * We're good!
1075 */
1076 pWaveFile->offCur = 0;
1077 return VINF_SUCCESS;
1078 }
1079
1080 RTMsgError("%s: Bad data header: uMagic=%#x (expected %#x), cbChunk=%#x (max %#RX64, align %u)",
1081 pszFile, uBuf.Data.Chunk.uMagic, RTRIFFWAVEDATACHUNK_MAGIC,
1082 uBuf.Data.Chunk.cbChunk, pWaveFile->cbSamples, PDMAudioPropsFrameSize(&pWaveFile->Props));
1083 }
1084 else
1085 RTMsgError("%s: Failed to read data header: %Rrc", pszFile, rc);
1086 }
1087 }
1088 else
1089 RTMsgError("%s: Bad file header: uMagic=%#x (vs. %#x), uFileType=%#x (vs %#x), uFmtMagic=%#x (vs %#x) cbFmtChunk=%#x (min %#x)",
1090 pszFile, uBuf.Wave.Hdr.uMagic, RTRIFFHDR_MAGIC, uBuf.Wave.Hdr.uFileType, RTRIFF_FILE_TYPE_WAVE,
1091 uBuf.Wave.Fmt.Chunk.uMagic, RTRIFFWAVEFMT_MAGIC,
1092 uBuf.Wave.Fmt.Chunk.cbChunk, sizeof(uBuf.Wave.Fmt.Data));
1093 }
1094 else
1095 RTMsgError("%s: Failed to read file header: %Rrc", pszFile, rc);
1096 }
1097 else
1098 RTMsgError("%s: Failed to query file size: %Rrc", pszFile, rc);
1099
1100 RTFileClose(pWaveFile->hFile);
1101 pWaveFile->hFile = NIL_RTFILE;
1102 return rc;
1103}
1104
1105/**
1106 * Closes a wave file.
1107 */
1108void AudioTestWaveFileClose(PAUDIOTESTWAVEFILE pWaveFile)
1109{
1110 RTFileClose(pWaveFile->hFile);
1111 pWaveFile->hFile = NIL_RTFILE;
1112}
1113
1114/**
1115 * Reads samples from a wave file.
1116 *
1117 * @returns VBox status code. See RTVfsFileRead for EOF status handling.
1118 * @param pWaveFile The file to read from.
1119 * @param pvBuf Where to put the samples.
1120 * @param cbBuf How much to read at most.
1121 * @param pcbRead Where to return the actual number of bytes read,
1122 * optional.
1123 */
1124int AudioTestWaveFileRead(PAUDIOTESTWAVEFILE pWaveFile, void *pvBuf, size_t cbBuf, size_t *pcbRead)
1125{
1126 bool fEofAdjusted;
1127 if (pWaveFile->offCur + cbBuf <= pWaveFile->cbSamples)
1128 fEofAdjusted = false;
1129 else if (pcbRead)
1130 {
1131 fEofAdjusted = true;
1132 cbBuf = pWaveFile->cbSamples - pWaveFile->offCur;
1133 }
1134 else
1135 return VERR_EOF;
1136
1137 int rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples + pWaveFile->offCur, pvBuf, cbBuf, pcbRead);
1138 if (RT_SUCCESS(rc))
1139 {
1140 if (pcbRead)
1141 {
1142 pWaveFile->offCur += (uint32_t)*pcbRead;
1143 if (fEofAdjusted || cbBuf > *pcbRead)
1144 rc = VINF_EOF;
1145 else if (!cbBuf && pWaveFile->offCur == pWaveFile->cbSamples)
1146 rc = VINF_EOF;
1147 }
1148 else
1149 pWaveFile->offCur += (uint32_t)cbBuf;
1150 }
1151 return rc;
1152}
1153
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