VirtualBox

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

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

Audio/ValKit: Implemented handling for meta data of test (set) objects. bugref:10008

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 49.7 KB
Line 
1/* $Id: AudioTest.cpp 89316 2021-05-27 12:31:34Z 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/** Audio test archive default suffix.
58 * According to IPRT terminology this always contains the dot. */
59#define AUDIOTEST_ARCHIVE_SUFF_STR ".tar.gz"
60
61/** Test manifest header name. */
62#define AUDIOTEST_INI_SEC_HDR_STR "header"
63
64
65/*********************************************************************************************************************************
66* Structures and Typedefs *
67*********************************************************************************************************************************/
68
69
70/*********************************************************************************************************************************
71* Global Variables *
72*********************************************************************************************************************************/
73/** Well-known frequency selection test tones. */
74static const double s_aAudioTestToneFreqsHz[] =
75{
76 349.2282 /*F4*/,
77 440.0000 /*A4*/,
78 523.2511 /*C5*/,
79 698.4565 /*F5*/,
80 880.0000 /*A5*/,
81 1046.502 /*C6*/,
82 1174.659 /*D6*/,
83 1396.913 /*F6*/,
84 1760.0000 /*A6*/
85};
86
87/**
88 * Returns a random test tone frequency.
89 */
90DECLINLINE(double) audioTestToneGetRandomFreq(void)
91{
92 return s_aAudioTestToneFreqsHz[RTRandU32Ex(0, RT_ELEMENTS(s_aAudioTestToneFreqsHz) - 1)];
93}
94
95/**
96 * Initializes a test tone with a specific frequency (in Hz).
97 *
98 * @returns Used tone frequency (in Hz).
99 * @param pTone Pointer to test tone to initialize.
100 * @param pProps PCM properties to use for the test tone.
101 * @param dbFreq Frequency (in Hz) to initialize tone with.
102 * When set to 0.0, a random frequency will be chosen.
103 */
104double AudioTestToneInit(PAUDIOTESTTONE pTone, PPDMAUDIOPCMPROPS pProps, double dbFreq)
105{
106 if (dbFreq == 0.0)
107 dbFreq = audioTestToneGetRandomFreq();
108
109 pTone->rdFreqHz = dbFreq;
110 pTone->rdFixed = 2.0 * M_PI * pTone->rdFreqHz / PDMAudioPropsHz(pProps);
111 pTone->uSample = 0;
112
113 memcpy(&pTone->Props, pProps, sizeof(PDMAUDIOPCMPROPS));
114
115 pTone->enmType = AUDIOTESTTONETYPE_SINE; /* Only type implemented so far. */
116
117 return dbFreq;
118}
119
120/**
121 * Initializes a test tone by picking a random but well-known frequency (in Hz).
122 *
123 * @returns Randomly picked tone frequency (in Hz).
124 * @param pTone Pointer to test tone to initialize.
125 * @param pProps PCM properties to use for the test tone.
126 */
127double AudioTestToneInitRandom(PAUDIOTESTTONE pTone, PPDMAUDIOPCMPROPS pProps)
128{
129 return AudioTestToneInit(pTone, pProps,
130 /* Pick a frequency from our selection, so that every time a recording starts
131 * we'll hopfully generate a different note. */
132 0.0);
133}
134
135/**
136 * Writes (and iterates) a given test tone to an output buffer.
137 *
138 * @returns VBox status code.
139 * @param pTone Pointer to test tone to write.
140 * @param pvBuf Pointer to output buffer to write test tone to.
141 * @param cbBuf Size (in bytes) of output buffer.
142 * @param pcbWritten How many bytes were written on success.
143 */
144int AudioTestToneGenerate(PAUDIOTESTTONE pTone, void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
145{
146 /*
147 * Clear the buffer first so we don't need to think about additional channels.
148 */
149 uint32_t cFrames = PDMAudioPropsBytesToFrames(&pTone->Props, cbBuf);
150
151 /* Input cbBuf not necessarily is aligned to the frames, so re-calculate it. */
152 const uint32_t cbToWrite = PDMAudioPropsFramesToBytes(&pTone->Props, cFrames);
153
154 PDMAudioPropsClearBuffer(&pTone->Props, pvBuf, cbBuf, cFrames);
155
156 /*
157 * Generate the select sin wave in the first channel:
158 */
159 uint32_t const cbFrame = PDMAudioPropsFrameSize(&pTone->Props);
160 double const rdFixed = pTone->rdFixed;
161 uint64_t iSrcFrame = pTone->uSample;
162 switch (PDMAudioPropsSampleSize(&pTone->Props))
163 {
164 case 1:
165 /* untested */
166 if (PDMAudioPropsIsSigned(&pTone->Props))
167 {
168 int8_t *piSample = (int8_t *)pvBuf;
169 while (cFrames-- > 0)
170 {
171 *piSample = (int8_t)(126 /*Amplitude*/ * sin(rdFixed * iSrcFrame));
172 iSrcFrame++;
173 piSample += cbFrame;
174 }
175 }
176 else
177 {
178 /* untested */
179 uint8_t *pbSample = (uint8_t *)pvBuf;
180 while (cFrames-- > 0)
181 {
182 *pbSample = (uint8_t)(126 /*Amplitude*/ * sin(rdFixed * iSrcFrame) + 0x80);
183 iSrcFrame++;
184 pbSample += cbFrame;
185 }
186 }
187 break;
188
189 case 2:
190 if (PDMAudioPropsIsSigned(&pTone->Props))
191 {
192 int16_t *piSample = (int16_t *)pvBuf;
193 while (cFrames-- > 0)
194 {
195 *piSample = (int16_t)(32760 /*Amplitude*/ * sin(rdFixed * iSrcFrame));
196 iSrcFrame++;
197 piSample = (int16_t *)((uint8_t *)piSample + cbFrame);
198 }
199 }
200 else
201 {
202 /* untested */
203 uint16_t *puSample = (uint16_t *)pvBuf;
204 while (cFrames-- > 0)
205 {
206 *puSample = (uint16_t)(32760 /*Amplitude*/ * sin(rdFixed * iSrcFrame) + 0x8000);
207 iSrcFrame++;
208 puSample = (uint16_t *)((uint8_t *)puSample + cbFrame);
209 }
210 }
211 break;
212
213 case 4:
214 /* untested */
215 if (PDMAudioPropsIsSigned(&pTone->Props))
216 {
217 int32_t *piSample = (int32_t *)pvBuf;
218 while (cFrames-- > 0)
219 {
220 *piSample = (int32_t)((32760 << 16) /*Amplitude*/ * sin(rdFixed * iSrcFrame));
221 iSrcFrame++;
222 piSample = (int32_t *)((uint8_t *)piSample + cbFrame);
223 }
224 }
225 else
226 {
227 uint32_t *puSample = (uint32_t *)pvBuf;
228 while (cFrames-- > 0)
229 {
230 *puSample = (uint32_t)((32760 << 16) /*Amplitude*/ * sin(rdFixed * iSrcFrame) + UINT32_C(0x80000000));
231 iSrcFrame++;
232 puSample = (uint32_t *)((uint8_t *)puSample + cbFrame);
233 }
234 }
235 break;
236
237 default:
238 AssertFailedReturn(VERR_NOT_SUPPORTED);
239 }
240
241 pTone->uSample = iSrcFrame;
242
243 if (pcbWritten)
244 *pcbWritten = cbToWrite;
245
246 return VINF_SUCCESS;
247}
248
249/**
250 * Initializes an audio test tone parameters struct with random values.
251 * @param pToneParams Test tone parameters to initialize.
252 * @param pProps PCM properties to use for the test tone.
253 */
254int AudioTestToneParamsInitRandom(PAUDIOTESTTONEPARMS pToneParams, PPDMAUDIOPCMPROPS pProps)
255{
256 AssertReturn(PDMAudioPropsAreValid(pProps), VERR_INVALID_PARAMETER);
257
258 memcpy(&pToneParams->Props, pProps, sizeof(PDMAUDIOPCMPROPS));
259
260 /** @todo Make this a bit more sophisticated later, e.g. muting and prequel/sequel are not very balanced. */
261
262 pToneParams->dbFreqHz = audioTestToneGetRandomFreq();
263 pToneParams->msPrequel = RTRandU32Ex(0, RT_MS_5SEC);
264#ifdef DEBUG_andy
265 pToneParams->msDuration = RTRandU32Ex(0, RT_MS_1SEC);
266#else
267 pToneParams->msDuration = RTRandU32Ex(0, RT_MS_10SEC); /** @todo Probably a bit too long, but let's see. */
268#endif
269 pToneParams->msSequel = RTRandU32Ex(0, RT_MS_5SEC);
270 pToneParams->uVolumePercent = RTRandU32Ex(0, 100);
271
272 return VINF_SUCCESS;
273}
274
275/**
276 * Generates a tag.
277 *
278 * @returns VBox status code.
279 * @param pszTag The output buffer.
280 * @param cbTag The size of the output buffer.
281 * AUDIOTEST_TAG_MAX is a good size.
282 */
283int AudioTestGenTag(char *pszTag, size_t cbTag)
284{
285 RTUUID UUID;
286 int rc = RTUuidCreate(&UUID);
287 AssertRCReturn(rc, rc);
288 rc = RTUuidToStr(&UUID, pszTag, cbTag);
289 AssertRCReturn(rc, rc);
290 return rc;
291}
292
293
294/**
295 * Return the tag to use in the given buffer, generating one if needed.
296 *
297 * @returns VBox status code.
298 * @param pszTag The output buffer.
299 * @param cbTag The size of the output buffer.
300 * AUDIOTEST_TAG_MAX is a good size.
301 * @param pszTagUser User specified tag, optional.
302 */
303int AudioTestCopyOrGenTag(char *pszTag, size_t cbTag, const char *pszTagUser)
304{
305 if (pszTagUser && *pszTagUser)
306 return RTStrCopy(pszTag, cbTag, pszTagUser);
307 return AudioTestGenTag(pszTag, cbTag);
308}
309
310
311/**
312 * Creates a new path (directory) for a specific audio test set tag.
313 *
314 * @returns VBox status code.
315 * @param pszPath On input, specifies the absolute base path where to create the test set path.
316 * On output this specifies the absolute path created.
317 * @param cbPath Size (in bytes) of \a pszPath.
318 * @param pszTag Tag to use for path creation.
319 *
320 * @note Can be used multiple times with the same tag; a sub directory with an ISO time string will be used
321 * on each call.
322 */
323int AudioTestPathCreate(char *pszPath, size_t cbPath, const char *pszTag)
324{
325 char szTag[AUDIOTEST_TAG_MAX];
326 int rc = AudioTestCopyOrGenTag(szTag, sizeof(szTag), pszTag);
327 AssertRCReturn(rc, rc);
328
329 char szName[RT_ELEMENTS(AUDIOTEST_PATH_PREFIX_STR) + AUDIOTEST_TAG_MAX + 4];
330 if (RTStrPrintf2(szName, sizeof(szName), "%s-%s", AUDIOTEST_PATH_PREFIX_STR, szTag) < 0)
331 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
332
333 rc = RTPathAppend(pszPath, cbPath, szName);
334 AssertRCReturn(rc, rc);
335
336#ifndef DEBUG /* Makes debugging easier to have a deterministic directory. */
337 char szTime[64];
338 RTTIMESPEC time;
339 if (!RTTimeSpecToString(RTTimeNow(&time), szTime, sizeof(szTime)))
340 return VERR_BUFFER_UNDERFLOW;
341
342 /* Colons aren't allowed in windows filenames, so change to dashes. */
343 char *pszColon;
344 while ((pszColon = strchr(szTime, ':')) != NULL)
345 *pszColon = '-';
346
347 rc = RTPathAppend(pszPath, cbPath, szTime);
348 AssertRCReturn(rc, rc);
349#endif
350
351 return RTDirCreateFullPath(pszPath, RTFS_UNIX_IRWXU);
352}
353
354DECLINLINE(int) audioTestManifestWriteData(PAUDIOTESTSET pSet, const void *pvData, size_t cbData)
355{
356 /** @todo Use RTIniFileWrite once its implemented. */
357 return RTFileWrite(pSet->f.hFile, pvData, cbData, NULL);
358}
359
360/**
361 * Writes string data to a test set manifest.
362 *
363 * @returns VBox status code.
364 * @param pSet Test set to write manifest for.
365 * @param pszFormat Format string to write.
366 * @param args Variable arguments for \a pszFormat.
367 */
368static int audioTestManifestWriteV(PAUDIOTESTSET pSet, const char *pszFormat, va_list args)
369{
370 /** @todo r=bird: Use RTStrmOpen + RTStrmPrintf instead of this slow
371 * do-it-all-yourself stuff. */
372 char *psz = NULL;
373 if (RTStrAPrintfV(&psz, pszFormat, args) == -1)
374 return VERR_NO_MEMORY;
375 AssertPtrReturn(psz, VERR_NO_MEMORY);
376
377 int rc = audioTestManifestWriteData(pSet, psz, strlen(psz));
378 AssertRC(rc);
379
380 RTStrFree(psz);
381
382 return rc;
383}
384
385/**
386 * Writes a string to a test set manifest.
387 * Convenience function.
388 *
389 * @returns VBox status code.
390 * @param pSet Test set to write manifest for.
391 * @param pszFormat Format string to write.
392 * @param ... Variable arguments for \a pszFormat. Optional.
393 */
394static int audioTestManifestWrite(PAUDIOTESTSET pSet, const char *pszFormat, ...)
395{
396 va_list va;
397 va_start(va, pszFormat);
398
399 int rc = audioTestManifestWriteV(pSet, pszFormat, va);
400 AssertRC(rc);
401
402 va_end(va);
403
404 return rc;
405}
406
407/**
408 * Returns the current read/write offset (in bytes) of the opened manifest file.
409 *
410 * @returns Current read/write offset (in bytes).
411 * @param pSet Set to return offset for.
412 * Must have an opened manifest file.
413 */
414DECLINLINE(uint64_t) audioTestManifestGetOffsetAbs(PAUDIOTESTSET pSet)
415{
416 AssertReturn(RTFileIsValid(pSet->f.hFile), 0);
417 return RTFileTell(pSet->f.hFile);
418}
419
420/**
421 * Writes a section header to a test set manifest.
422 *
423 * @returns VBox status code.
424 * @param pSet Test set to write manifest for.
425 * @param pszSection Format string of section to write.
426 * @param ... Variable arguments for \a pszSection. Optional.
427 */
428static int audioTestManifestWriteSectionHdr(PAUDIOTESTSET pSet, const char *pszSection, ...)
429{
430 va_list va;
431 va_start(va, pszSection);
432
433 /** @todo Keep it as simple as possible for now. Improve this later. */
434 int rc = audioTestManifestWrite(pSet, "[%N]\n", pszSection, &va);
435
436 va_end(va);
437
438 return rc;
439}
440
441/**
442 * Initializes an audio test set, internal function.
443 *
444 * @param pSet Test set to initialize.
445 */
446static void audioTestSetInitInternal(PAUDIOTESTSET pSet)
447{
448 pSet->f.hFile = NIL_RTFILE;
449
450 RTListInit(&pSet->lstObj);
451 pSet->cObj = 0;
452
453 RTListInit(&pSet->lstTest);
454 pSet->cTests = 0;
455 pSet->cTestsRunning = 0;
456 pSet->offTestCount = 0;
457 pSet->pTestCur = NULL;
458 pSet->cObj = 0;
459 pSet->offObjCount = 0;
460 pSet->cTotalFailures = 0;
461}
462
463/**
464 * Returns whether a test set's manifest file is open (and thus ready) or not.
465 *
466 * @returns \c true if open (and ready), or \c false if not.
467 * @retval VERR_
468 * @param pSet Test set to return open status for.
469 */
470static bool audioTestManifestIsOpen(PAUDIOTESTSET pSet)
471{
472 if ( pSet->enmMode == AUDIOTESTSETMODE_TEST
473 && pSet->f.hFile != NIL_RTFILE)
474 return true;
475 else if ( pSet->enmMode == AUDIOTESTSETMODE_VERIFY
476 && pSet->f.hIniFile != NIL_RTINIFILE)
477 return true;
478
479 return false;
480}
481
482/**
483 * Initializes an audio test error description.
484 *
485 * @param pErr Test error description to initialize.
486 */
487static void audioTestErrorDescInit(PAUDIOTESTERRORDESC pErr)
488{
489 RTListInit(&pErr->List);
490 pErr->cErrors = 0;
491}
492
493/**
494 * Destroys an audio test error description.
495 *
496 * @param pErr Test error description to destroy.
497 */
498void AudioTestErrorDescDestroy(PAUDIOTESTERRORDESC pErr)
499{
500 if (!pErr)
501 return;
502
503 PAUDIOTESTERRORENTRY pErrEntry, pErrEntryNext;
504 RTListForEachSafe(&pErr->List, pErrEntry, pErrEntryNext, AUDIOTESTERRORENTRY, Node)
505 {
506 RTListNodeRemove(&pErrEntry->Node);
507
508 RTMemFree(pErrEntry);
509
510 Assert(pErr->cErrors);
511 pErr->cErrors--;
512 }
513
514 Assert(pErr->cErrors == 0);
515}
516
517/**
518 * Returns if an audio test error description contains any errors or not.
519 *
520 * @returns \c true if it contains errors, or \c false if not.
521 *
522 * @param pErr Test error description to return error status for.
523 */
524bool AudioTestErrorDescFailed(PAUDIOTESTERRORDESC pErr)
525{
526 if (pErr->cErrors)
527 {
528 Assert(!RTListIsEmpty(&pErr->List));
529 return true;
530 }
531
532 return false;
533}
534
535/**
536 * Adds a single error entry to an audio test error description, va_list version.
537 *
538 * @returns VBox status code.
539 * @param pErr Test error description to add entry for.
540 * @param rc Result code of entry to add.
541 * @param pszDesc Error description format string to add.
542 * @param args Optional format arguments of \a pszDesc to add.
543 */
544static int audioTestErrorDescAddV(PAUDIOTESTERRORDESC pErr, int rc, const char *pszDesc, va_list args)
545{
546 PAUDIOTESTERRORENTRY pEntry = (PAUDIOTESTERRORENTRY)RTMemAlloc(sizeof(AUDIOTESTERRORENTRY));
547 AssertReturn(pEntry, VERR_NO_MEMORY);
548
549 if (RTStrPrintf2V(pEntry->szDesc, sizeof(pEntry->szDesc), pszDesc, args) < 0)
550 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
551
552 pEntry->rc = rc;
553
554 RTListAppend(&pErr->List, &pEntry->Node);
555
556 pErr->cErrors++;
557
558 return VINF_SUCCESS;
559}
560
561/**
562 * Adds a single error entry to an audio test error description, va_list version.
563 *
564 * @returns VBox status code.
565 * @param pErr Test error description to add entry for.
566 * @param pszDesc Error description format string to add.
567 * @param ... Optional format arguments of \a pszDesc to add.
568 */
569static int audioTestErrorDescAdd(PAUDIOTESTERRORDESC pErr, const char *pszDesc, ...)
570{
571 va_list va;
572 va_start(va, pszDesc);
573
574 int rc = audioTestErrorDescAddV(pErr, VERR_GENERAL_FAILURE /** @todo Fudge! */, pszDesc, va);
575
576 va_end(va);
577 return rc;
578}
579
580#if 0
581static int audioTestErrorDescAddRc(PAUDIOTESTERRORDESC pErr, int rc, const char *pszFormat, ...)
582{
583 va_list va;
584 va_start(va, pszFormat);
585
586 int rc2 = audioTestErrorDescAddV(pErr, rc, pszFormat, va);
587
588 va_end(va);
589 return rc2;
590}
591#endif
592
593/**
594 * Creates a new temporary directory with a specific (test) tag.
595 *
596 * @returns VBox status code.
597 * @param pszPath Where to return the absolute path of the created directory on success.
598 * @param cbPath Size (in bytes) of \a pszPath.
599 * @param pszTag Tag name to use for directory creation.
600 *
601 * @note Can be used multiple times with the same tag; a sub directory with an ISO time string will be used
602 * on each call.
603 */
604int AudioTestPathCreateTemp(char *pszPath, size_t cbPath, const char *pszTag)
605{
606 AssertReturn(pszTag && strlen(pszTag) <= AUDIOTEST_TAG_MAX, VERR_INVALID_PARAMETER);
607
608 char szPath[RTPATH_MAX];
609
610 int rc = RTPathTemp(szPath, sizeof(szPath));
611 AssertRCReturn(rc, rc);
612 rc = AudioTestPathCreate(szPath, sizeof(szPath), pszTag);
613 AssertRCReturn(rc, rc);
614
615 return RTStrCopy(pszPath, cbPath, szPath);
616}
617
618/**
619 * Returns the absolute path of a given audio test set object.
620 *
621 * @returns VBox status code.
622 * @param pSet Test set the object contains.
623 * @param pszPathAbs Where to return the absolute path on success.
624 * @param cbPathAbs Size (in bytes) of \a pszPathAbs.
625 * @param pszObjName Name of the object to create absolute path for.
626 */
627DECLINLINE(int) audioTestSetGetObjPath(PAUDIOTESTSET pSet, char *pszPathAbs, size_t cbPathAbs, const char *pszObjName)
628{
629 return RTPathJoin(pszPathAbs, cbPathAbs, pSet->szPathAbs, pszObjName);
630}
631
632/**
633 * Creates a new audio test set.
634 *
635 * @returns VBox status code.
636 * @param pSet Test set to create.
637 * @param pszPath Where to store the set set data. If NULL, the
638 * temporary directory will be used.
639 * @param pszTag Tag name to use for this test set.
640 */
641int AudioTestSetCreate(PAUDIOTESTSET pSet, const char *pszPath, const char *pszTag)
642{
643 audioTestSetInitInternal(pSet);
644
645 int rc = AudioTestCopyOrGenTag(pSet->szTag, sizeof(pSet->szTag), pszTag);
646 AssertRCReturn(rc, rc);
647
648 /*
649 * Test set directory.
650 */
651 if (pszPath)
652 {
653 rc = RTPathAbs(pszPath, pSet->szPathAbs, sizeof(pSet->szPathAbs));
654 AssertRCReturn(rc, rc);
655
656 rc = AudioTestPathCreate(pSet->szPathAbs, sizeof(pSet->szPathAbs), pSet->szTag);
657 }
658 else
659 rc = AudioTestPathCreateTemp(pSet->szPathAbs, sizeof(pSet->szPathAbs), pSet->szTag);
660 AssertRCReturn(rc, rc);
661
662 /*
663 * Create the manifest file.
664 */
665 char szTmp[RTPATH_MAX];
666 rc = RTPathJoin(szTmp, sizeof(szTmp), pSet->szPathAbs, AUDIOTEST_MANIFEST_FILE_STR);
667 AssertRCReturn(rc, rc);
668
669 rc = RTFileOpen(&pSet->f.hFile, szTmp, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
670 AssertRCReturn(rc, rc);
671
672 rc = audioTestManifestWriteSectionHdr(pSet, "header");
673 AssertRCReturn(rc, rc);
674
675 rc = audioTestManifestWrite(pSet, "magic=vkat_ini\n"); /* VKAT Manifest, .INI-style. */
676 AssertRCReturn(rc, rc);
677 rc = audioTestManifestWrite(pSet, "ver=%d\n", AUDIOTEST_MANIFEST_VER);
678 AssertRCReturn(rc, rc);
679 rc = audioTestManifestWrite(pSet, "tag=%s\n", pSet->szTag);
680 AssertRCReturn(rc, rc);
681
682 AssertCompile(sizeof(szTmp) > RTTIME_STR_LEN);
683 RTTIMESPEC Now;
684 rc = audioTestManifestWrite(pSet, "date_created=%s\n", RTTimeSpecToString(RTTimeNow(&Now), szTmp, sizeof(szTmp)));
685 AssertRCReturn(rc, rc);
686
687 RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp)); /* do NOT return on failure. */
688 rc = audioTestManifestWrite(pSet, "os_product=%s\n", szTmp);
689 AssertRCReturn(rc, rc);
690
691 RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp)); /* do NOT return on failure. */
692 rc = audioTestManifestWrite(pSet, "os_rel=%s\n", szTmp);
693 AssertRCReturn(rc, rc);
694
695 RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp)); /* do NOT return on failure. */
696 rc = audioTestManifestWrite(pSet, "os_ver=%s\n", szTmp);
697 AssertRCReturn(rc, rc);
698
699 rc = audioTestManifestWrite(pSet, "vbox_ver=%s r%u %s (%s %s)\n",
700 VBOX_VERSION_STRING, RTBldCfgRevision(), RTBldCfgTargetDotArch(), __DATE__, __TIME__);
701 AssertRCReturn(rc, rc);
702
703 rc = audioTestManifestWrite(pSet, "test_count=");
704 AssertRCReturn(rc, rc);
705 pSet->offTestCount = audioTestManifestGetOffsetAbs(pSet);
706 rc = audioTestManifestWrite(pSet, "0000\n"); /* A bit messy, but does the trick for now. */
707 AssertRCReturn(rc, rc);
708
709 rc = audioTestManifestWrite(pSet, "obj_count=");
710 AssertRCReturn(rc, rc);
711 pSet->offObjCount = audioTestManifestGetOffsetAbs(pSet);
712 rc = audioTestManifestWrite(pSet, "0000\n"); /* A bit messy, but does the trick for now. */
713 AssertRCReturn(rc, rc);
714
715 pSet->enmMode = AUDIOTESTSETMODE_TEST;
716
717 return rc;
718}
719
720/**
721 * Destroys a test set.
722 *
723 * @returns VBox status code.
724 * @param pSet Test set to destroy.
725 */
726int AudioTestSetDestroy(PAUDIOTESTSET pSet)
727{
728 if (!pSet)
729 return VINF_SUCCESS;
730
731 AssertReturn(pSet->cTestsRunning == 0, VERR_WRONG_ORDER); /* Make sure no tests sill are running. */
732
733 int rc = AudioTestSetClose(pSet);
734 if (RT_FAILURE(rc))
735 return rc;
736
737 PAUDIOTESTOBJ pObj, pObjNext;
738 RTListForEachSafe(&pSet->lstObj, pObj, pObjNext, AUDIOTESTOBJ, Node)
739 {
740 rc = AudioTestSetObjClose(pObj);
741 if (RT_SUCCESS(rc))
742 {
743 RTListNodeRemove(&pObj->Node);
744 RTMemFree(pObj);
745
746 Assert(pSet->cObj);
747 pSet->cObj--;
748 }
749 else
750 break;
751 }
752
753 if (RT_FAILURE(rc))
754 return rc;
755
756 Assert(pSet->cObj == 0);
757
758 PAUDIOTESTENTRY pEntry, pEntryNext;
759 RTListForEachSafe(&pSet->lstTest, pEntry, pEntryNext, AUDIOTESTENTRY, Node)
760 {
761 RTListNodeRemove(&pEntry->Node);
762 RTMemFree(pEntry);
763
764 Assert(pSet->cTests);
765 pSet->cTests--;
766 }
767
768 if (RT_FAILURE(rc))
769 return rc;
770
771 Assert(pSet->cTests == 0);
772
773 return rc;
774}
775
776/**
777 * Opens an existing audio test set.
778 *
779 * @returns VBox status code.
780 * @param pSet Test set to open.
781 * @param pszPath Absolute path of the test set to open.
782 */
783int AudioTestSetOpen(PAUDIOTESTSET pSet, const char *pszPath)
784{
785 audioTestSetInitInternal(pSet);
786
787 char szManifest[RTPATH_MAX];
788 int rc = RTPathJoin(szManifest, sizeof(szManifest), pszPath, AUDIOTEST_MANIFEST_FILE_STR);
789 AssertRCReturn(rc, rc);
790
791 RTVFSFILE hVfsFile;
792 rc = RTVfsFileOpenNormal(szManifest, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hVfsFile);
793 if (RT_FAILURE(rc))
794 return rc;
795
796 rc = RTIniFileCreateFromVfsFile(&pSet->f.hIniFile, hVfsFile, RTINIFILE_F_READONLY);
797 RTVfsFileRelease(hVfsFile);
798 AssertRCReturn(rc, rc);
799
800 pSet->enmMode = AUDIOTESTSETMODE_VERIFY;
801
802 return rc;
803}
804
805/**
806 * Closes an opened audio test set.
807 *
808 * @returns VBox status code.
809 * @param pSet Test set to close.
810 */
811int AudioTestSetClose(PAUDIOTESTSET pSet)
812{
813 if (!pSet)
814 return VINF_SUCCESS;
815
816 if (!RTFileIsValid(pSet->f.hFile))
817 return VINF_SUCCESS;
818
819 int rc;
820
821 /* Update number of bound test objects. */
822 PAUDIOTESTENTRY pTest;
823 RTListForEach(&pSet->lstTest, pTest, AUDIOTESTENTRY, Node)
824 {
825 rc = RTFileSeek(pSet->f.hFile, pTest->offObjCount, RTFILE_SEEK_BEGIN, NULL);
826 AssertRCReturn(rc, rc);
827 rc = audioTestManifestWrite(pSet, "%04RU32", pTest->cObj);
828 AssertRCReturn(rc, rc);
829 }
830
831 /*
832 * Update number of ran tests.
833 */
834 rc = RTFileSeek(pSet->f.hFile, pSet->offObjCount, RTFILE_SEEK_BEGIN, NULL);
835 AssertRCReturn(rc, rc);
836 rc = audioTestManifestWrite(pSet, "%04RU32", pSet->cObj);
837 AssertRCReturn(rc, rc);
838
839 /*
840 * Update number of ran tests.
841 */
842 rc = RTFileSeek(pSet->f.hFile, pSet->offTestCount, RTFILE_SEEK_BEGIN, NULL);
843 AssertRCReturn(rc, rc);
844 rc = audioTestManifestWrite(pSet, "%04RU32", pSet->cTests);
845 AssertRCReturn(rc, rc);
846
847 /*
848 * Serialize all registered test objects.
849 */
850 rc = RTFileSeek(pSet->f.hFile, 0, RTFILE_SEEK_END, NULL);
851 AssertRCReturn(rc, rc);
852
853 PAUDIOTESTOBJ pObj;
854 RTListForEach(&pSet->lstObj, pObj, AUDIOTESTOBJ, Node)
855 {
856 rc = audioTestManifestWrite(pSet, "\n");
857 AssertRCReturn(rc, rc);
858 char szUuid[64];
859 rc = RTUuidToStr(&pObj->Uuid, szUuid, sizeof(szUuid));
860 AssertRCReturn(rc, rc);
861 rc = audioTestManifestWriteSectionHdr(pSet, "obj_%s", szUuid);
862 AssertRCReturn(rc, rc);
863 rc = audioTestManifestWrite(pSet, "obj_type=%RU32\n", pObj->enmType);
864 AssertRCReturn(rc, rc);
865 rc = audioTestManifestWrite(pSet, "obj_name=%s\n", pObj->szName);
866 AssertRCReturn(rc, rc);
867 }
868
869 RTFileClose(pSet->f.hFile);
870 pSet->f.hFile = NIL_RTFILE;
871
872 return rc;
873}
874
875/**
876 * Physically wipes all related test set files off the disk.
877 *
878 * @returns VBox status code.
879 * @param pSet Test set to wipe.
880 */
881int AudioTestSetWipe(PAUDIOTESTSET pSet)
882{
883 AssertPtrReturn(pSet, VERR_INVALID_POINTER);
884
885 int rc = VINF_SUCCESS;
886 char szFilePath[RTPATH_MAX];
887
888 PAUDIOTESTOBJ pObj;
889 RTListForEach(&pSet->lstObj, pObj, AUDIOTESTOBJ, Node)
890 {
891 int rc2 = AudioTestSetObjClose(pObj);
892 if (RT_SUCCESS(rc2))
893 {
894 rc2 = audioTestSetGetObjPath(pSet, szFilePath, sizeof(szFilePath), pObj->szName);
895 if (RT_SUCCESS(rc2))
896 rc2 = RTFileDelete(szFilePath);
897 }
898
899 if (RT_SUCCESS(rc))
900 rc = rc2;
901 /* Keep going. */
902 }
903
904 if (RT_SUCCESS(rc))
905 {
906 rc = RTPathJoin(szFilePath, sizeof(szFilePath), pSet->szPathAbs, AUDIOTEST_MANIFEST_FILE_STR);
907 if (RT_SUCCESS(rc))
908 rc = RTFileDelete(szFilePath);
909 }
910
911 /* Remove the (hopefully now empty) directory. Otherwise let this fail. */
912 if (RT_SUCCESS(rc))
913 rc = RTDirRemove(pSet->szPathAbs);
914
915 return rc;
916}
917
918/**
919 * Creates and registers a new audio test object to the current running test.
920 *
921 * @returns VBox status code.
922 * @param pSet Test set to create and register new object for.
923 * @param pszName Name of new object to create.
924 * @param ppObj Where to return the pointer to the newly created object on success.
925 */
926int AudioTestSetObjCreateAndRegister(PAUDIOTESTSET pSet, const char *pszName, PAUDIOTESTOBJ *ppObj)
927{
928 AssertReturn(pSet->cTestsRunning == 1, VERR_WRONG_ORDER); /* No test nesting allowed. */
929
930 AssertPtrReturn(pszName, VERR_INVALID_POINTER);
931
932 PAUDIOTESTOBJ pObj = (PAUDIOTESTOBJ)RTMemAlloc(sizeof(AUDIOTESTOBJ));
933 AssertPtrReturn(pObj, VERR_NO_MEMORY);
934
935 if (RTStrPrintf2(pObj->szName, sizeof(pObj->szName), "%04RU32-%s", pSet->cObj, pszName) <= 0)
936 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
937
938 /** @todo Generalize this function more once we have more object types. */
939
940 char szObjPathAbs[RTPATH_MAX];
941 int rc = audioTestSetGetObjPath(pSet, szObjPathAbs, sizeof(szObjPathAbs), pObj->szName);
942 if (RT_SUCCESS(rc))
943 {
944 rc = RTFileOpen(&pObj->File.hFile, szObjPathAbs, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
945 if (RT_SUCCESS(rc))
946 {
947 pObj->enmType = AUDIOTESTOBJTYPE_FILE;
948 pObj->cRefs = 1; /* Currently only 1:1 mapping. */
949
950 RTListAppend(&pSet->lstObj, &pObj->Node);
951 pSet->cObj++;
952
953 /* Generate + set an UUID for the object and assign it to the current test. */
954 rc = RTUuidCreate(&pObj->Uuid);
955 AssertRCReturn(rc, rc);
956 char szUuid[64];
957 rc = RTUuidToStr(&pObj->Uuid, szUuid, sizeof(szUuid));
958 AssertRCReturn(rc, rc);
959
960 rc = audioTestManifestWrite(pSet, "obj%RU32_uuid=%s\n", pSet->pTestCur->cObj, szUuid);
961 AssertRCReturn(rc, rc);
962
963 AssertPtr(pSet->pTestCur);
964 pSet->pTestCur->cObj++;
965
966 *ppObj = pObj;
967 }
968 }
969
970 if (RT_FAILURE(rc))
971 RTMemFree(pObj);
972
973 return rc;
974}
975
976/**
977 * Writes to a created audio test object.
978 *
979 * @returns VBox status code.
980 * @param pObj Audio test object to write to.
981 * @param pvBuf Pointer to data to write.
982 * @param cbBuf Size (in bytes) of \a pvBuf to write.
983 */
984int AudioTestSetObjWrite(PAUDIOTESTOBJ pObj, void *pvBuf, size_t cbBuf)
985{
986 /** @todo Generalize this function more once we have more object types. */
987 AssertReturn(pObj->enmType == AUDIOTESTOBJTYPE_FILE, VERR_INVALID_PARAMETER);
988
989 return RTFileWrite(pObj->File.hFile, pvBuf, cbBuf, NULL);
990}
991
992/**
993 * Closes an opened audio test object.
994 *
995 * @returns VBox status code.
996 * @param pObj Audio test object to close.
997 */
998int AudioTestSetObjClose(PAUDIOTESTOBJ pObj)
999{
1000 if (!pObj)
1001 return VINF_SUCCESS;
1002
1003 /** @todo Generalize this function more once we have more object types. */
1004 AssertReturn(pObj->enmType == AUDIOTESTOBJTYPE_FILE, VERR_INVALID_PARAMETER);
1005
1006 int rc = VINF_SUCCESS;
1007
1008 if (RTFileIsValid(pObj->File.hFile))
1009 {
1010 rc = RTFileClose(pObj->File.hFile);
1011 pObj->File.hFile = NIL_RTFILE;
1012 }
1013
1014 return rc;
1015}
1016
1017int AudioTestSetTestBegin(PAUDIOTESTSET pSet, const char *pszDesc, PAUDIOTESTPARMS pParms, PAUDIOTESTENTRY *ppEntry)
1018{
1019 AssertReturn(pSet->cTestsRunning == 0, VERR_WRONG_ORDER); /* No test nesting allowed. */
1020
1021 PAUDIOTESTENTRY pEntry = (PAUDIOTESTENTRY)RTMemAllocZ(sizeof(AUDIOTESTENTRY));
1022 AssertPtrReturn(pEntry, VERR_NO_MEMORY);
1023
1024 int rc = RTStrCopy(pEntry->szDesc, sizeof(pEntry->szDesc), pszDesc);
1025 AssertRCReturn(rc, rc);
1026
1027 memcpy(&pEntry->Parms, pParms, sizeof(AUDIOTESTPARMS));
1028 pEntry->pParent = pSet;
1029
1030 rc = audioTestManifestWrite(pSet, "\n");
1031 AssertRCReturn(rc, rc);
1032
1033 rc = audioTestManifestWriteSectionHdr(pSet, "test_%04RU32", pSet->cTests);
1034 AssertRCReturn(rc, rc);
1035 rc = audioTestManifestWrite(pSet, "test_desc=%s\n", pszDesc);
1036 AssertRCReturn(rc, rc);
1037 rc = audioTestManifestWrite(pSet, "test_type=%RU32\n", pParms->enmType);
1038 AssertRCReturn(rc, rc);
1039 rc = audioTestManifestWrite(pSet, "test_delay_ms=%RU32\n", pParms->msDelay);
1040 AssertRCReturn(rc, rc);
1041 rc = audioTestManifestWrite(pSet, "audio_direction=%s\n", PDMAudioDirGetName(pParms->enmDir));
1042 AssertRCReturn(rc, rc);
1043
1044 rc = audioTestManifestWrite(pSet, "obj_count=");
1045 AssertRCReturn(rc, rc);
1046 pEntry->offObjCount = audioTestManifestGetOffsetAbs(pSet);
1047 rc = audioTestManifestWrite(pSet, "0000\n"); /* A bit messy, but does the trick for now. */
1048 AssertRCReturn(rc, rc);
1049
1050 switch (pParms->enmType)
1051 {
1052 case AUDIOTESTTYPE_TESTTONE_PLAY:
1053 RT_FALL_THROUGH();
1054 case AUDIOTESTTYPE_TESTTONE_RECORD:
1055 {
1056 rc = audioTestManifestWrite(pSet, "tone_freq_hz=%RU16\n", (uint16_t)pParms->TestTone.dbFreqHz);
1057 AssertRCReturn(rc, rc);
1058 rc = audioTestManifestWrite(pSet, "tone_prequel_ms=%RU32\n", pParms->TestTone.msPrequel);
1059 AssertRCReturn(rc, rc);
1060 rc = audioTestManifestWrite(pSet, "tone_duration_ms=%RU32\n", pParms->TestTone.msDuration);
1061 AssertRCReturn(rc, rc);
1062 rc = audioTestManifestWrite(pSet, "tone_sequel_ms=%RU32\n", pParms->TestTone.msSequel);
1063 AssertRCReturn(rc, rc);
1064 rc = audioTestManifestWrite(pSet, "tone_volume_percent=%RU32\n", pParms->TestTone.uVolumePercent);
1065 AssertRCReturn(rc, rc);
1066 rc = audioTestManifestWrite(pSet, "tone_pcm_hz=%RU32\n", PDMAudioPropsHz(&pParms->TestTone.Props));
1067 AssertRCReturn(rc, rc);
1068 rc = audioTestManifestWrite(pSet, "tone_pcm_channels=%RU8\n", PDMAudioPropsChannels(&pParms->TestTone.Props));
1069 AssertRCReturn(rc, rc);
1070 rc = audioTestManifestWrite(pSet, "tone_pcm_bits=%RU8\n", PDMAudioPropsSampleBits(&pParms->TestTone.Props));
1071 AssertRCReturn(rc, rc);
1072 rc = audioTestManifestWrite(pSet, "tone_pcm_is_signed=%RTbool\n", PDMAudioPropsIsSigned(&pParms->TestTone.Props));
1073 AssertRCReturn(rc, rc);
1074 break;
1075 }
1076
1077 default:
1078 AssertFailed();
1079 break;
1080 }
1081
1082 RTListAppend(&pSet->lstTest, &pEntry->Node);
1083 pSet->cTests++;
1084 pSet->cTestsRunning++;
1085 pSet->pTestCur = pEntry;
1086
1087 *ppEntry = pEntry;
1088
1089 return rc;
1090}
1091
1092int AudioTestSetTestFailed(PAUDIOTESTENTRY pEntry, int rc, const char *pszErr)
1093{
1094 AssertReturn(pEntry->pParent->cTestsRunning == 1, VERR_WRONG_ORDER); /* No test nesting allowed. */
1095 AssertReturn(pEntry->rc == VINF_SUCCESS, VERR_WRONG_ORDER);
1096
1097 pEntry->rc = rc;
1098
1099 int rc2 = audioTestManifestWrite(pEntry->pParent, "error_rc=%RI32\n", rc);
1100 AssertRCReturn(rc2, rc2);
1101 rc2 = audioTestManifestWrite(pEntry->pParent, "error_desc=%s\n", pszErr);
1102 AssertRCReturn(rc2, rc2);
1103
1104 pEntry->pParent->cTestsRunning--;
1105 pEntry->pParent->pTestCur = NULL;
1106
1107 return rc2;
1108}
1109
1110int AudioTestSetTestDone(PAUDIOTESTENTRY pEntry)
1111{
1112 AssertReturn(pEntry->pParent->cTestsRunning == 1, VERR_WRONG_ORDER); /* No test nesting allowed. */
1113 AssertReturn(pEntry->rc == VINF_SUCCESS, VERR_WRONG_ORDER);
1114
1115 int rc2 = audioTestManifestWrite(pEntry->pParent, "error_rc=%RI32\n", VINF_SUCCESS);
1116 AssertRCReturn(rc2, rc2);
1117
1118 pEntry->pParent->cTestsRunning--;
1119 pEntry->pParent->pTestCur = NULL;
1120
1121 return rc2;
1122}
1123
1124/**
1125 * Packs an audio test so that it's ready for transmission.
1126 *
1127 * @returns VBox status code.
1128 * @param pSet Test set to pack.
1129 * @param pszOutDir Directory where to store the packed test set.
1130 * @param pszFileName Where to return the final name of the packed test set. Optional and can be NULL.
1131 * @param cbFileName Size (in bytes) of \a pszFileName.
1132 */
1133int AudioTestSetPack(PAUDIOTESTSET pSet, const char *pszOutDir, char *pszFileName, size_t cbFileName)
1134{
1135 AssertReturn(!pszFileName || cbFileName, VERR_INVALID_PARAMETER);
1136 AssertReturn(audioTestManifestIsOpen(pSet), VERR_WRONG_ORDER);
1137
1138 /** @todo Check and deny if \a pszOutDir is part of the set's path. */
1139
1140 char szOutName[RT_ELEMENTS(AUDIOTEST_PATH_PREFIX_STR) + AUDIOTEST_TAG_MAX + 16];
1141 if (RTStrPrintf2(szOutName, sizeof(szOutName), "%s-%s%s",
1142 AUDIOTEST_PATH_PREFIX_STR, pSet->szTag, AUDIOTEST_ARCHIVE_SUFF_STR) <= 0)
1143 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
1144
1145 char szOutPath[RTPATH_MAX];
1146 int rc = RTPathJoin(szOutPath, sizeof(szOutPath), pszOutDir, szOutName);
1147 AssertRCReturn(rc, rc);
1148
1149 const char *apszArgs[10];
1150 unsigned cArgs = 0;
1151
1152 apszArgs[cArgs++] = "AudioTest";
1153 apszArgs[cArgs++] = "--create";
1154 apszArgs[cArgs++] = "--gzip";
1155 apszArgs[cArgs++] = "--directory";
1156 apszArgs[cArgs++] = pSet->szPathAbs;
1157 apszArgs[cArgs++] = "--file";
1158 apszArgs[cArgs++] = szOutPath;
1159 apszArgs[cArgs++] = ".";
1160
1161 RTEXITCODE rcExit = RTZipTarCmd(cArgs, (char **)apszArgs);
1162 if (rcExit != RTEXITCODE_SUCCESS)
1163 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1164
1165 if (RT_SUCCESS(rc))
1166 {
1167 if (pszFileName)
1168 rc = RTStrCopy(pszFileName, cbFileName, szOutPath);
1169 }
1170
1171 return rc;
1172}
1173
1174/**
1175 * Returns whether a test set archive is packed (as .tar.gz by default) or
1176 * a plain directory.
1177 *
1178 * @returns \c true if packed (as .tar.gz), or \c false if not (directory).
1179 * @param pszPath Path to return packed staus for.
1180 */
1181bool AudioTestSetIsPacked(const char *pszPath)
1182{
1183 /** @todo Improve this, good enough for now. */
1184 return (RTStrIStr(pszPath, AUDIOTEST_ARCHIVE_SUFF_STR) != NULL);
1185}
1186
1187/**
1188 * Unpacks a formerly packed audio test set.
1189 *
1190 * @returns VBox status code.
1191 * @param pszFile Test set file to unpack. Must contain the absolute path.
1192 * @param pszOutDir Directory where to unpack the test set into.
1193 * If the directory does not exist it will be created.
1194 */
1195int AudioTestSetUnpack(const char *pszFile, const char *pszOutDir)
1196{
1197 AssertReturn(pszFile && pszOutDir, VERR_INVALID_PARAMETER);
1198
1199 int rc = VINF_SUCCESS;
1200
1201 if (!RTDirExists(pszOutDir))
1202 {
1203 rc = RTDirCreateFullPath(pszOutDir, 0755);
1204 if (RT_FAILURE(rc))
1205 return rc;
1206 }
1207
1208 const char *apszArgs[8];
1209 unsigned cArgs = 0;
1210
1211 apszArgs[cArgs++] = "AudioTest";
1212 apszArgs[cArgs++] = "--extract";
1213 apszArgs[cArgs++] = "--gunzip";
1214 apszArgs[cArgs++] = "--directory";
1215 apszArgs[cArgs++] = pszOutDir;
1216 apszArgs[cArgs++] = "--file";
1217 apszArgs[cArgs++] = pszFile;
1218
1219 RTEXITCODE rcExit = RTZipTarCmd(cArgs, (char **)apszArgs);
1220 if (rcExit != RTEXITCODE_SUCCESS)
1221 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1222
1223 return rc;
1224}
1225
1226/**
1227 * Verifies an opened audio test set.
1228 *
1229 * @returns VBox status code.
1230 * @param pSet Test set to verify.
1231 * @param pszTag Tag to use for verification purpose.
1232 * @param pErrDesc Where to return the test verification errors.
1233 *
1234 * @note Test verification errors have to be checked for errors, regardless of the
1235 * actual return code.
1236 */
1237int AudioTestSetVerify(PAUDIOTESTSET pSet, const char *pszTag, PAUDIOTESTERRORDESC pErrDesc)
1238{
1239 AssertReturn(audioTestManifestIsOpen(pSet), VERR_WRONG_ORDER);
1240
1241 /* We ASSUME the caller has not init'd pErrDesc. */
1242 audioTestErrorDescInit(pErrDesc);
1243
1244 char szVal[_1K]; /** @todo Enough, too much? */
1245
1246 int rc2 = RTIniFileQueryValue(pSet->f.hIniFile, AUDIOTEST_INI_SEC_HDR_STR, "tag", szVal, sizeof(szVal), NULL);
1247 if ( RT_FAILURE(rc2)
1248 || RTStrICmp(pszTag, szVal))
1249 audioTestErrorDescAdd(pErrDesc, "Tag '%s' does not match with manifest's tag '%s'", pszTag, szVal);
1250
1251 /* Only return critical stuff not related to actual testing here. */
1252 return VINF_SUCCESS;
1253}
1254
1255
1256/*********************************************************************************************************************************
1257* WAVE File Reader. *
1258*********************************************************************************************************************************/
1259/**
1260 * Opens a wave (.WAV) file for reading.
1261 *
1262 * @returns VBox status code.
1263 * @param pszFile The file to open.
1264 * @param pWaveFile The open wave file structure to fill in on success.
1265 */
1266int AudioTestWaveFileOpen(const char *pszFile, PAUDIOTESTWAVEFILE pWaveFile)
1267{
1268 RT_ZERO(pWaveFile->Props);
1269 pWaveFile->hFile = NIL_RTFILE;
1270 int rc = RTFileOpen(&pWaveFile->hFile, pszFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
1271 if (RT_FAILURE(rc))
1272 return rc;
1273 uint64_t cbFile = 0;
1274 rc = RTFileQuerySize(pWaveFile->hFile, &cbFile);
1275 if (RT_SUCCESS(rc))
1276 {
1277 union
1278 {
1279 uint8_t ab[512];
1280 struct
1281 {
1282 RTRIFFHDR Hdr;
1283 RTRIFFWAVEFMTCHUNK Fmt;
1284 } Wave;
1285 RTRIFFLIST List;
1286 RTRIFFCHUNK Chunk;
1287 RTRIFFWAVEDATACHUNK Data;
1288 } uBuf;
1289
1290 rc = RTFileRead(pWaveFile->hFile, &uBuf.Wave, sizeof(uBuf.Wave), NULL);
1291 if (RT_SUCCESS(rc))
1292 {
1293 rc = VERR_VFS_UNKNOWN_FORMAT;
1294 if ( uBuf.Wave.Hdr.uMagic == RTRIFFHDR_MAGIC
1295 && uBuf.Wave.Hdr.uFileType == RTRIFF_FILE_TYPE_WAVE
1296 && uBuf.Wave.Fmt.Chunk.uMagic == RTRIFFWAVEFMT_MAGIC
1297 && uBuf.Wave.Fmt.Chunk.cbChunk >= sizeof(uBuf.Wave.Fmt.Data))
1298 {
1299 if (uBuf.Wave.Hdr.cbFile != cbFile - sizeof(RTRIFFCHUNK))
1300 RTMsgWarning("%s: File size mismatch: %#x, actual %#RX64 (ignored)",
1301 pszFile, uBuf.Wave.Hdr.cbFile, cbFile - sizeof(RTRIFFCHUNK));
1302 rc = VERR_VFS_BOGUS_FORMAT;
1303 if (uBuf.Wave.Fmt.Data.uFormatTag != RTRIFFWAVEFMT_TAG_PCM)
1304 RTMsgError("%s: Unsupported uFormatTag value: %u (expected 1)", pszFile, uBuf.Wave.Fmt.Data.uFormatTag);
1305 else if ( uBuf.Wave.Fmt.Data.cBitsPerSample != 8
1306 && uBuf.Wave.Fmt.Data.cBitsPerSample != 16
1307 && uBuf.Wave.Fmt.Data.cBitsPerSample != 32)
1308 RTMsgError("%s: Unsupported cBitsPerSample value: %u", pszFile, uBuf.Wave.Fmt.Data.cBitsPerSample);
1309 else if ( uBuf.Wave.Fmt.Data.cChannels < 1
1310 || uBuf.Wave.Fmt.Data.cChannels >= 16)
1311 RTMsgError("%s: Unsupported cChannels value: %u (expected 1..15)", pszFile, uBuf.Wave.Fmt.Data.cChannels);
1312 else if ( uBuf.Wave.Fmt.Data.uHz < 4096
1313 || uBuf.Wave.Fmt.Data.uHz > 768000)
1314 RTMsgError("%s: Unsupported uHz value: %u (expected 4096..768000)", pszFile, uBuf.Wave.Fmt.Data.uHz);
1315 else if (uBuf.Wave.Fmt.Data.cbFrame != uBuf.Wave.Fmt.Data.cChannels * uBuf.Wave.Fmt.Data.cBitsPerSample / 8)
1316 RTMsgError("%s: Invalid cbFrame value: %u (expected %u)", pszFile, uBuf.Wave.Fmt.Data.cbFrame,
1317 uBuf.Wave.Fmt.Data.cChannels * uBuf.Wave.Fmt.Data.cBitsPerSample / 8);
1318 else if (uBuf.Wave.Fmt.Data.cbRate != uBuf.Wave.Fmt.Data.cbFrame * uBuf.Wave.Fmt.Data.uHz)
1319 RTMsgError("%s: Invalid cbRate value: %u (expected %u)", pszFile, uBuf.Wave.Fmt.Data.cbRate,
1320 uBuf.Wave.Fmt.Data.cbFrame * uBuf.Wave.Fmt.Data.uHz);
1321 else
1322 {
1323 /*
1324 * Copy out the data we need from the file format structure.
1325 */
1326 PDMAudioPropsInit(&pWaveFile->Props, uBuf.Wave.Fmt.Data.cBitsPerSample / 8, true /*fSigned*/,
1327 uBuf.Wave.Fmt.Data.cChannels, uBuf.Wave.Fmt.Data.uHz);
1328 pWaveFile->offSamples = sizeof(RTRIFFHDR) + sizeof(RTRIFFCHUNK) + uBuf.Wave.Fmt.Chunk.cbChunk;
1329
1330 /*
1331 * Find the 'data' chunk with the audio samples.
1332 *
1333 * There can be INFO lists both preceeding this and succeeding
1334 * it, containing IART and other things we can ignored. Thus
1335 * we read a list header here rather than just a chunk header,
1336 * since it doesn't matter if we read 4 bytes extra as
1337 * AudioTestWaveFileRead uses RTFileReadAt anyway.
1338 */
1339 rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
1340 for (uint32_t i = 0;
1341 i < 128
1342 && RT_SUCCESS(rc)
1343 && uBuf.Chunk.uMagic != RTRIFFWAVEDATACHUNK_MAGIC
1344 && (uint64_t)uBuf.Chunk.cbChunk + sizeof(RTRIFFCHUNK) * 2 <= cbFile - pWaveFile->offSamples;
1345 i++)
1346 {
1347 if ( uBuf.List.uMagic == RTRIFFLIST_MAGIC
1348 && uBuf.List.uListType == RTRIFFLIST_TYPE_INFO)
1349 { /*skip*/ }
1350 else if (uBuf.Chunk.uMagic == RTRIFFPADCHUNK_MAGIC)
1351 { /*skip*/ }
1352 else
1353 break;
1354 pWaveFile->offSamples += sizeof(RTRIFFCHUNK) + uBuf.Chunk.cbChunk;
1355 rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
1356 }
1357 if (RT_SUCCESS(rc))
1358 {
1359 pWaveFile->offSamples += sizeof(uBuf.Data.Chunk);
1360 pWaveFile->cbSamples = (uint32_t)cbFile - pWaveFile->offSamples;
1361
1362 rc = VERR_VFS_BOGUS_FORMAT;
1363 if ( uBuf.Data.Chunk.uMagic == RTRIFFWAVEDATACHUNK_MAGIC
1364 && uBuf.Data.Chunk.cbChunk <= pWaveFile->cbSamples
1365 && PDMAudioPropsIsSizeAligned(&pWaveFile->Props, uBuf.Data.Chunk.cbChunk))
1366 {
1367 pWaveFile->cbSamples = uBuf.Data.Chunk.cbChunk;
1368
1369 /*
1370 * We're good!
1371 */
1372 pWaveFile->offCur = 0;
1373 return VINF_SUCCESS;
1374 }
1375
1376 RTMsgError("%s: Bad data header: uMagic=%#x (expected %#x), cbChunk=%#x (max %#RX64, align %u)",
1377 pszFile, uBuf.Data.Chunk.uMagic, RTRIFFWAVEDATACHUNK_MAGIC,
1378 uBuf.Data.Chunk.cbChunk, pWaveFile->cbSamples, PDMAudioPropsFrameSize(&pWaveFile->Props));
1379 }
1380 else
1381 RTMsgError("%s: Failed to read data header: %Rrc", pszFile, rc);
1382 }
1383 }
1384 else
1385 RTMsgError("%s: Bad file header: uMagic=%#x (vs. %#x), uFileType=%#x (vs %#x), uFmtMagic=%#x (vs %#x) cbFmtChunk=%#x (min %#x)",
1386 pszFile, uBuf.Wave.Hdr.uMagic, RTRIFFHDR_MAGIC, uBuf.Wave.Hdr.uFileType, RTRIFF_FILE_TYPE_WAVE,
1387 uBuf.Wave.Fmt.Chunk.uMagic, RTRIFFWAVEFMT_MAGIC,
1388 uBuf.Wave.Fmt.Chunk.cbChunk, sizeof(uBuf.Wave.Fmt.Data));
1389 }
1390 else
1391 RTMsgError("%s: Failed to read file header: %Rrc", pszFile, rc);
1392 }
1393 else
1394 RTMsgError("%s: Failed to query file size: %Rrc", pszFile, rc);
1395
1396 RTFileClose(pWaveFile->hFile);
1397 pWaveFile->hFile = NIL_RTFILE;
1398 return rc;
1399}
1400
1401/**
1402 * Closes a wave file.
1403 */
1404void AudioTestWaveFileClose(PAUDIOTESTWAVEFILE pWaveFile)
1405{
1406 RTFileClose(pWaveFile->hFile);
1407 pWaveFile->hFile = NIL_RTFILE;
1408}
1409
1410/**
1411 * Reads samples from a wave file.
1412 *
1413 * @returns VBox status code. See RTVfsFileRead for EOF status handling.
1414 * @param pWaveFile The file to read from.
1415 * @param pvBuf Where to put the samples.
1416 * @param cbBuf How much to read at most.
1417 * @param pcbRead Where to return the actual number of bytes read,
1418 * optional.
1419 */
1420int AudioTestWaveFileRead(PAUDIOTESTWAVEFILE pWaveFile, void *pvBuf, size_t cbBuf, size_t *pcbRead)
1421{
1422 bool fEofAdjusted;
1423 if (pWaveFile->offCur + cbBuf <= pWaveFile->cbSamples)
1424 fEofAdjusted = false;
1425 else if (pcbRead)
1426 {
1427 fEofAdjusted = true;
1428 cbBuf = pWaveFile->cbSamples - pWaveFile->offCur;
1429 }
1430 else
1431 return VERR_EOF;
1432
1433 int rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples + pWaveFile->offCur, pvBuf, cbBuf, pcbRead);
1434 if (RT_SUCCESS(rc))
1435 {
1436 if (pcbRead)
1437 {
1438 pWaveFile->offCur += (uint32_t)*pcbRead;
1439 if (fEofAdjusted || cbBuf > *pcbRead)
1440 rc = VINF_EOF;
1441 else if (!cbBuf && pWaveFile->offCur == pWaveFile->cbSamples)
1442 rc = VINF_EOF;
1443 }
1444 else
1445 pWaveFile->offCur += (uint32_t)cbBuf;
1446 }
1447 return rc;
1448}
1449
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