VirtualBox

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

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

Audio/ValKit: More validation code. bugref:10008

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 72.2 KB
Line 
1/* $Id: AudioTest.cpp 89711 2021-06-15 15:08:23Z 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/env.h>
30#include <iprt/file.h>
31#include <iprt/formats/riff.h>
32#include <iprt/inifile.h>
33#include <iprt/list.h>
34#include <iprt/message.h> /** @todo Get rid of this once we have own log hooks. */
35#include <iprt/rand.h>
36#include <iprt/system.h>
37#include <iprt/uuid.h>
38#include <iprt/vfs.h>
39#include <iprt/zip.h>
40
41#define _USE_MATH_DEFINES
42#include <math.h> /* sin, M_PI */
43
44#include <VBox/version.h>
45#include <VBox/vmm/pdmaudioifs.h>
46#include <VBox/vmm/pdmaudioinline.h>
47
48#include "AudioTest.h"
49
50
51/*********************************************************************************************************************************
52* Defines *
53*********************************************************************************************************************************/
54/** The test manifest file name. */
55#define AUDIOTEST_MANIFEST_FILE_STR "vkat_manifest.ini"
56/** The current test manifest version. */
57#define AUDIOTEST_MANIFEST_VER 1
58/** Audio test archive default suffix.
59 * According to IPRT terminology this always contains the dot. */
60#define AUDIOTEST_ARCHIVE_SUFF_STR ".tar.gz"
61
62/** Test manifest header name. */
63#define AUDIOTEST_INI_SEC_HDR_STR "header"
64
65
66/*********************************************************************************************************************************
67* Structures and Typedefs *
68*********************************************************************************************************************************/
69/**
70 * Structure for keeping an audio test verification job.
71 */
72typedef struct AUDIOTESTVERIFYJOB
73{
74 /** Pointer to set A. */
75 PAUDIOTESTSET pSetA;
76 /** Pointer to set B. */
77 PAUDIOTESTSET pSetB;
78 /** Pointer to the error description to use. */
79 PAUDIOTESTERRORDESC pErr;
80 /** Zero-based index of current test being verified. */
81 uint32_t idxTest;
82 /** Flag indicating whether to keep going after an error has occurred. */
83 bool fKeepGoing;
84} AUDIOTESTVERIFYJOB;
85/** Pointer to an audio test verification job. */
86typedef AUDIOTESTVERIFYJOB *PAUDIOTESTVERIFYJOB;
87
88
89/*********************************************************************************************************************************
90* Global Variables *
91*********************************************************************************************************************************/
92/** Well-known frequency selection test tones. */
93static const double s_aAudioTestToneFreqsHz[] =
94{
95 349.2282 /*F4*/,
96 440.0000 /*A4*/,
97 523.2511 /*C5*/,
98 698.4565 /*F5*/,
99 880.0000 /*A5*/,
100 1046.502 /*C6*/,
101 1174.659 /*D6*/,
102 1396.913 /*F6*/,
103 1760.0000 /*A6*/
104};
105
106/**
107 * Returns a random test tone frequency.
108 */
109DECLINLINE(double) audioTestToneGetRandomFreq(void)
110{
111 return s_aAudioTestToneFreqsHz[RTRandU32Ex(0, RT_ELEMENTS(s_aAudioTestToneFreqsHz) - 1)];
112}
113
114/**
115 * Initializes a test tone with a specific frequency (in Hz).
116 *
117 * @returns Used tone frequency (in Hz).
118 * @param pTone Pointer to test tone to initialize.
119 * @param pProps PCM properties to use for the test tone.
120 * @param dbFreq Frequency (in Hz) to initialize tone with.
121 * When set to 0.0, a random frequency will be chosen.
122 */
123double AudioTestToneInit(PAUDIOTESTTONE pTone, PPDMAUDIOPCMPROPS pProps, double dbFreq)
124{
125 if (dbFreq == 0.0)
126 dbFreq = audioTestToneGetRandomFreq();
127
128 pTone->rdFreqHz = dbFreq;
129 pTone->rdFixed = 2.0 * M_PI * pTone->rdFreqHz / PDMAudioPropsHz(pProps);
130 pTone->uSample = 0;
131
132 memcpy(&pTone->Props, pProps, sizeof(PDMAUDIOPCMPROPS));
133
134 pTone->enmType = AUDIOTESTTONETYPE_SINE; /* Only type implemented so far. */
135
136 return dbFreq;
137}
138
139/**
140 * Initializes a test tone by picking a random but well-known frequency (in Hz).
141 *
142 * @returns Randomly picked tone frequency (in Hz).
143 * @param pTone Pointer to test tone to initialize.
144 * @param pProps PCM properties to use for the test tone.
145 */
146double AudioTestToneInitRandom(PAUDIOTESTTONE pTone, PPDMAUDIOPCMPROPS pProps)
147{
148 return AudioTestToneInit(pTone, pProps,
149 /* Pick a frequency from our selection, so that every time a recording starts
150 * we'll hopfully generate a different note. */
151 0.0);
152}
153
154/**
155 * Writes (and iterates) a given test tone to an output buffer.
156 *
157 * @returns VBox status code.
158 * @param pTone Pointer to test tone to write.
159 * @param pvBuf Pointer to output buffer to write test tone to.
160 * @param cbBuf Size (in bytes) of output buffer.
161 * @param pcbWritten How many bytes were written on success.
162 */
163int AudioTestToneGenerate(PAUDIOTESTTONE pTone, void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
164{
165 /*
166 * Clear the buffer first so we don't need to think about additional channels.
167 */
168 uint32_t cFrames = PDMAudioPropsBytesToFrames(&pTone->Props, cbBuf);
169
170 /* Input cbBuf not necessarily is aligned to the frames, so re-calculate it. */
171 const uint32_t cbToWrite = PDMAudioPropsFramesToBytes(&pTone->Props, cFrames);
172
173 PDMAudioPropsClearBuffer(&pTone->Props, pvBuf, cbBuf, cFrames);
174
175 /*
176 * Generate the select sin wave in the first channel:
177 */
178 uint32_t const cbFrame = PDMAudioPropsFrameSize(&pTone->Props);
179 double const rdFixed = pTone->rdFixed;
180 uint64_t iSrcFrame = pTone->uSample;
181 switch (PDMAudioPropsSampleSize(&pTone->Props))
182 {
183 case 1:
184 /* untested */
185 if (PDMAudioPropsIsSigned(&pTone->Props))
186 {
187 int8_t *piSample = (int8_t *)pvBuf;
188 while (cFrames-- > 0)
189 {
190 *piSample = (int8_t)(126 /*Amplitude*/ * sin(rdFixed * iSrcFrame));
191 iSrcFrame++;
192 piSample += cbFrame;
193 }
194 }
195 else
196 {
197 /* untested */
198 uint8_t *pbSample = (uint8_t *)pvBuf;
199 while (cFrames-- > 0)
200 {
201 *pbSample = (uint8_t)(126 /*Amplitude*/ * sin(rdFixed * iSrcFrame) + 0x80);
202 iSrcFrame++;
203 pbSample += cbFrame;
204 }
205 }
206 break;
207
208 case 2:
209 if (PDMAudioPropsIsSigned(&pTone->Props))
210 {
211 int16_t *piSample = (int16_t *)pvBuf;
212 while (cFrames-- > 0)
213 {
214 *piSample = (int16_t)(32760 /*Amplitude*/ * sin(rdFixed * iSrcFrame));
215 iSrcFrame++;
216 piSample = (int16_t *)((uint8_t *)piSample + cbFrame);
217 }
218 }
219 else
220 {
221 /* untested */
222 uint16_t *puSample = (uint16_t *)pvBuf;
223 while (cFrames-- > 0)
224 {
225 *puSample = (uint16_t)(32760 /*Amplitude*/ * sin(rdFixed * iSrcFrame) + 0x8000);
226 iSrcFrame++;
227 puSample = (uint16_t *)((uint8_t *)puSample + cbFrame);
228 }
229 }
230 break;
231
232 case 4:
233 /* untested */
234 if (PDMAudioPropsIsSigned(&pTone->Props))
235 {
236 int32_t *piSample = (int32_t *)pvBuf;
237 while (cFrames-- > 0)
238 {
239 *piSample = (int32_t)((32760 << 16) /*Amplitude*/ * sin(rdFixed * iSrcFrame));
240 iSrcFrame++;
241 piSample = (int32_t *)((uint8_t *)piSample + cbFrame);
242 }
243 }
244 else
245 {
246 uint32_t *puSample = (uint32_t *)pvBuf;
247 while (cFrames-- > 0)
248 {
249 *puSample = (uint32_t)((32760 << 16) /*Amplitude*/ * sin(rdFixed * iSrcFrame) + UINT32_C(0x80000000));
250 iSrcFrame++;
251 puSample = (uint32_t *)((uint8_t *)puSample + cbFrame);
252 }
253 }
254 break;
255
256 default:
257 AssertFailedReturn(VERR_NOT_SUPPORTED);
258 }
259
260 pTone->uSample = iSrcFrame;
261
262 if (pcbWritten)
263 *pcbWritten = cbToWrite;
264
265 return VINF_SUCCESS;
266}
267
268/**
269 * Initializes an audio test tone parameters struct with random values.
270 * @param pToneParams Test tone parameters to initialize.
271 * @param pProps PCM properties to use for the test tone.
272 */
273int AudioTestToneParamsInitRandom(PAUDIOTESTTONEPARMS pToneParams, PPDMAUDIOPCMPROPS pProps)
274{
275 AssertReturn(PDMAudioPropsAreValid(pProps), VERR_INVALID_PARAMETER);
276
277 memcpy(&pToneParams->Props, pProps, sizeof(PDMAUDIOPCMPROPS));
278
279 /** @todo Make this a bit more sophisticated later, e.g. muting and prequel/sequel are not very balanced. */
280
281 pToneParams->dbFreqHz = audioTestToneGetRandomFreq();
282 pToneParams->msPrequel = RTRandU32Ex(0, RT_MS_5SEC);
283#ifdef DEBUG_andy
284 pToneParams->msDuration = RTRandU32Ex(0, RT_MS_1SEC);
285#else
286 pToneParams->msDuration = RTRandU32Ex(0, RT_MS_10SEC); /** @todo Probably a bit too long, but let's see. */
287#endif
288 pToneParams->msSequel = RTRandU32Ex(0, RT_MS_5SEC);
289 pToneParams->uVolumePercent = RTRandU32Ex(0, 100);
290
291 return VINF_SUCCESS;
292}
293
294/**
295 * Generates a tag.
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 */
302int AudioTestGenTag(char *pszTag, size_t cbTag)
303{
304 RTUUID UUID;
305 int rc = RTUuidCreate(&UUID);
306 AssertRCReturn(rc, rc);
307 rc = RTUuidToStr(&UUID, pszTag, cbTag);
308 AssertRCReturn(rc, rc);
309 return rc;
310}
311
312/**
313 * Return the tag to use in the given buffer, generating one if needed.
314 *
315 * @returns VBox status code.
316 * @param pszTag The output buffer.
317 * @param cbTag The size of the output buffer.
318 * AUDIOTEST_TAG_MAX is a good size.
319 * @param pszTagUser User specified tag, optional.
320 */
321static int audioTestCopyOrGenTag(char *pszTag, size_t cbTag, const char *pszTagUser)
322{
323 if (pszTagUser && *pszTagUser)
324 return RTStrCopy(pszTag, cbTag, pszTagUser);
325 return AudioTestGenTag(pszTag, cbTag);
326}
327
328
329/**
330 * Creates a new path (directory) for a specific audio test set tag.
331 *
332 * @returns VBox status code.
333 * @param pszPath On input, specifies the absolute base path where to create the test set path.
334 * On output this specifies the absolute path created.
335 * @param cbPath Size (in bytes) of \a pszPath.
336 * @param pszTag Tag to use for path creation.
337 *
338 * @note Can be used multiple times with the same tag; a sub directory with an ISO time string will be used
339 * on each call.
340 */
341int AudioTestPathCreate(char *pszPath, size_t cbPath, const char *pszTag)
342{
343 char szTag[AUDIOTEST_TAG_MAX];
344 int rc = audioTestCopyOrGenTag(szTag, sizeof(szTag), pszTag);
345 AssertRCReturn(rc, rc);
346
347 char szName[RT_ELEMENTS(AUDIOTEST_PATH_PREFIX_STR) + AUDIOTEST_TAG_MAX + 4];
348 if (RTStrPrintf2(szName, sizeof(szName), "%s-%s", AUDIOTEST_PATH_PREFIX_STR, szTag) < 0)
349 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
350
351 rc = RTPathAppend(pszPath, cbPath, szName);
352 AssertRCReturn(rc, rc);
353
354#ifndef DEBUG /* Makes debugging easier to have a deterministic directory. */
355 char szTime[64];
356 RTTIMESPEC time;
357 if (!RTTimeSpecToString(RTTimeNow(&time), szTime, sizeof(szTime)))
358 return VERR_BUFFER_UNDERFLOW;
359
360 /* Colons aren't allowed in windows filenames, so change to dashes. */
361 char *pszColon;
362 while ((pszColon = strchr(szTime, ':')) != NULL)
363 *pszColon = '-';
364
365 rc = RTPathAppend(pszPath, cbPath, szTime);
366 AssertRCReturn(rc, rc);
367#endif
368
369 return RTDirCreateFullPath(pszPath, RTFS_UNIX_IRWXU);
370}
371
372DECLINLINE(int) audioTestManifestWriteData(PAUDIOTESTSET pSet, const void *pvData, size_t cbData)
373{
374 /** @todo Use RTIniFileWrite once its implemented. */
375 return RTFileWrite(pSet->f.hFile, pvData, cbData, NULL);
376}
377
378/**
379 * Writes string data to a test set manifest.
380 *
381 * @returns VBox status code.
382 * @param pSet Test set to write manifest for.
383 * @param pszFormat Format string to write.
384 * @param args Variable arguments for \a pszFormat.
385 */
386static int audioTestManifestWriteV(PAUDIOTESTSET pSet, const char *pszFormat, va_list args)
387{
388 /** @todo r=bird: Use RTStrmOpen + RTStrmPrintf instead of this slow
389 * do-it-all-yourself stuff. */
390 char *psz = NULL;
391 if (RTStrAPrintfV(&psz, pszFormat, args) == -1)
392 return VERR_NO_MEMORY;
393 AssertPtrReturn(psz, VERR_NO_MEMORY);
394
395 int rc = audioTestManifestWriteData(pSet, psz, strlen(psz));
396 AssertRC(rc);
397
398 RTStrFree(psz);
399
400 return rc;
401}
402
403/**
404 * Writes a string to a test set manifest.
405 * Convenience function.
406 *
407 * @returns VBox status code.
408 * @param pSet Test set to write manifest for.
409 * @param pszFormat Format string to write.
410 * @param ... Variable arguments for \a pszFormat. Optional.
411 */
412static int audioTestManifestWrite(PAUDIOTESTSET pSet, const char *pszFormat, ...)
413{
414 va_list va;
415 va_start(va, pszFormat);
416
417 int rc = audioTestManifestWriteV(pSet, pszFormat, va);
418 AssertRC(rc);
419
420 va_end(va);
421
422 return rc;
423}
424
425/**
426 * Returns the current read/write offset (in bytes) of the opened manifest file.
427 *
428 * @returns Current read/write offset (in bytes).
429 * @param pSet Set to return offset for.
430 * Must have an opened manifest file.
431 */
432DECLINLINE(uint64_t) audioTestManifestGetOffsetAbs(PAUDIOTESTSET pSet)
433{
434 AssertReturn(RTFileIsValid(pSet->f.hFile), 0);
435 return RTFileTell(pSet->f.hFile);
436}
437
438/**
439 * Writes a section header to a test set manifest.
440 *
441 * @returns VBox status code.
442 * @param pSet Test set to write manifest for.
443 * @param pszSection Format string of section to write.
444 * @param ... Variable arguments for \a pszSection. Optional.
445 */
446static int audioTestManifestWriteSectionHdr(PAUDIOTESTSET pSet, const char *pszSection, ...)
447{
448 va_list va;
449 va_start(va, pszSection);
450
451 /** @todo Keep it as simple as possible for now. Improve this later. */
452 int rc = audioTestManifestWrite(pSet, "[%N]\n", pszSection, &va);
453
454 va_end(va);
455
456 return rc;
457}
458
459/**
460 * Initializes an audio test set, internal function.
461 *
462 * @param pSet Test set to initialize.
463 */
464static void audioTestSetInitInternal(PAUDIOTESTSET pSet)
465{
466 pSet->f.hFile = NIL_RTFILE;
467
468 RTListInit(&pSet->lstObj);
469 pSet->cObj = 0;
470
471 RTListInit(&pSet->lstTest);
472 pSet->cTests = 0;
473 pSet->cTestsRunning = 0;
474 pSet->offTestCount = 0;
475 pSet->pTestCur = NULL;
476 pSet->cObj = 0;
477 pSet->offObjCount = 0;
478 pSet->cTotalFailures = 0;
479}
480
481/**
482 * Returns whether a test set's manifest file is open (and thus ready) or not.
483 *
484 * @returns \c true if open (and ready), or \c false if not.
485 * @retval VERR_
486 * @param pSet Test set to return open status for.
487 */
488static bool audioTestManifestIsOpen(PAUDIOTESTSET pSet)
489{
490 if ( pSet->enmMode == AUDIOTESTSETMODE_TEST
491 && pSet->f.hFile != NIL_RTFILE)
492 return true;
493 else if ( pSet->enmMode == AUDIOTESTSETMODE_VERIFY
494 && pSet->f.hIniFile != NIL_RTINIFILE)
495 return true;
496
497 return false;
498}
499
500/**
501 * Initializes an audio test error description.
502 *
503 * @param pErr Test error description to initialize.
504 */
505static void audioTestErrorDescInit(PAUDIOTESTERRORDESC pErr)
506{
507 RTListInit(&pErr->List);
508 pErr->cErrors = 0;
509}
510
511/**
512 * Destroys an audio test error description.
513 *
514 * @param pErr Test error description to destroy.
515 */
516void AudioTestErrorDescDestroy(PAUDIOTESTERRORDESC pErr)
517{
518 if (!pErr)
519 return;
520
521 PAUDIOTESTERRORENTRY pErrEntry, pErrEntryNext;
522 RTListForEachSafe(&pErr->List, pErrEntry, pErrEntryNext, AUDIOTESTERRORENTRY, Node)
523 {
524 RTListNodeRemove(&pErrEntry->Node);
525
526 RTMemFree(pErrEntry);
527
528 Assert(pErr->cErrors);
529 pErr->cErrors--;
530 }
531
532 Assert(pErr->cErrors == 0);
533}
534
535/**
536 * Returns the the number of errors of an audio test error description.
537 *
538 * @returns Error count.
539 * @param pErr Test error description to return error count for.
540 */
541uint32_t AudioTestErrorDescCount(PCAUDIOTESTERRORDESC pErr)
542{
543 return pErr->cErrors;
544}
545
546/**
547 * Returns if an audio test error description contains any errors or not.
548 *
549 * @returns \c true if it contains errors, or \c false if not.
550 * @param pErr Test error description to return error status for.
551 */
552bool AudioTestErrorDescFailed(PCAUDIOTESTERRORDESC pErr)
553{
554 if (pErr->cErrors)
555 {
556 Assert(!RTListIsEmpty(&pErr->List));
557 return true;
558 }
559
560 return false;
561}
562
563/**
564 * Adds a single error entry to an audio test error description, va_list version.
565 *
566 * @returns VBox status code.
567 * @param pErr Test error description to add entry for.
568 * @param idxTest Index of failing test (zero-based).
569 * @param rc Result code of entry to add.
570 * @param pszDesc Error description format string to add.
571 * @param args Optional format arguments of \a pszDesc to add.
572 */
573static int audioTestErrorDescAddV(PAUDIOTESTERRORDESC pErr, uint32_t idxTest, int rc, const char *pszDesc, va_list args)
574{
575 PAUDIOTESTERRORENTRY pEntry = (PAUDIOTESTERRORENTRY)RTMemAlloc(sizeof(AUDIOTESTERRORENTRY));
576 AssertPtrReturn(pEntry, VERR_NO_MEMORY);
577
578 char *pszDescTmp;
579 if (RTStrAPrintf(&pszDescTmp, pszDesc, args) < 0)
580 AssertFailedReturn(VERR_NO_MEMORY);
581
582 const ssize_t cch = RTStrPrintf2(pEntry->szDesc, sizeof(pEntry->szDesc), "Test #%RU32 failed: %s", idxTest, pszDescTmp);
583 RTStrFree(pszDescTmp);
584 AssertReturn(cch > 0, VERR_BUFFER_OVERFLOW);
585
586 pEntry->rc = rc;
587
588 RTListAppend(&pErr->List, &pEntry->Node);
589
590 pErr->cErrors++;
591
592 return VINF_SUCCESS;
593}
594
595/**
596 * Adds a single error entry to an audio test error description, va_list version.
597 *
598 * @returns VBox status code.
599 * @param pErr Test error description to add entry for.
600 * @param idxTest Index of failing test (zero-based).
601 * @param pszDesc Error description format string to add.
602 * @param ... Optional format arguments of \a pszDesc to add.
603 */
604static int audioTestErrorDescAdd(PAUDIOTESTERRORDESC pErr, uint32_t idxTest, const char *pszDesc, ...)
605{
606 va_list va;
607 va_start(va, pszDesc);
608
609 int rc = audioTestErrorDescAddV(pErr, idxTest, VERR_GENERAL_FAILURE /** @todo Fudge! */, pszDesc, va);
610
611 va_end(va);
612 return rc;
613}
614
615#if 0
616static int audioTestErrorDescAddRc(PAUDIOTESTERRORDESC pErr, int rc, const char *pszFormat, ...)
617{
618 va_list va;
619 va_start(va, pszFormat);
620
621 int rc2 = audioTestErrorDescAddV(pErr, rc, pszFormat, va);
622
623 va_end(va);
624 return rc2;
625}
626#endif
627
628/**
629 * Retrieves the temporary directory.
630 *
631 * @returns VBox status code.
632 * @param pszPath Where to return the absolute path of the created directory on success.
633 * @param cbPath Size (in bytes) of \a pszPath.
634 */
635int AudioTestPathGetTemp(char *pszPath, size_t cbPath)
636{
637 int rc = RTEnvGetEx(RTENV_DEFAULT, "TESTBOX_PATH_SCRATCH", pszPath, cbPath, NULL);
638 if (RT_FAILURE(rc))
639 {
640 rc = RTPathTemp(pszPath, cbPath);
641 AssertRCReturn(rc, rc);
642 }
643
644 return rc;
645}
646
647/**
648 * Creates a new temporary directory with a specific (test) tag.
649 *
650 * @returns VBox status code.
651 * @param pszPath Where to return the absolute path of the created directory on success.
652 * @param cbPath Size (in bytes) of \a pszPath.
653 * @param pszTag Tag name to use for directory creation.
654 *
655 * @note Can be used multiple times with the same tag; a sub directory with an ISO time string will be used
656 * on each call.
657 */
658int AudioTestPathCreateTemp(char *pszPath, size_t cbPath, const char *pszTag)
659{
660 AssertReturn(pszTag && strlen(pszTag) <= AUDIOTEST_TAG_MAX, VERR_INVALID_PARAMETER);
661
662 char szTemp[RTPATH_MAX];
663 int rc = AudioTestPathGetTemp(szTemp, sizeof(szTemp));
664 AssertRCReturn(rc, rc);
665
666 rc = AudioTestPathCreate(szTemp, sizeof(szTemp), pszTag);
667 AssertRCReturn(rc, rc);
668
669 return RTStrCopy(pszPath, cbPath, szTemp);
670}
671
672/**
673 * Returns the absolute path of a given audio test set object.
674 *
675 * @returns VBox status code.
676 * @param pSet Test set the object contains.
677 * @param pszPathAbs Where to return the absolute path on success.
678 * @param cbPathAbs Size (in bytes) of \a pszPathAbs.
679 * @param pszObjName Name of the object to create absolute path for.
680 */
681DECLINLINE(int) audioTestSetGetObjPath(PAUDIOTESTSET pSet, char *pszPathAbs, size_t cbPathAbs, const char *pszObjName)
682{
683 return RTPathJoin(pszPathAbs, cbPathAbs, pSet->szPathAbs, pszObjName);
684}
685
686/**
687 * Returns the tag of a test set.
688 *
689 * @returns Test set tag.
690 * @param pSet Test set to return tag for.
691 */
692const char *AudioTestSetGetTag(PAUDIOTESTSET pSet)
693{
694 return pSet->szTag;
695}
696
697/**
698 * Creates a new audio test set.
699 *
700 * @returns VBox status code.
701 * @param pSet Test set to create.
702 * @param pszPath Where to store the set set data. If NULL, the
703 * temporary directory will be used.
704 * @param pszTag Tag name to use for this test set.
705 */
706int AudioTestSetCreate(PAUDIOTESTSET pSet, const char *pszPath, const char *pszTag)
707{
708 audioTestSetInitInternal(pSet);
709
710 int rc = audioTestCopyOrGenTag(pSet->szTag, sizeof(pSet->szTag), pszTag);
711 AssertRCReturn(rc, rc);
712
713 /*
714 * Test set directory.
715 */
716 if (pszPath)
717 {
718 rc = RTPathAbs(pszPath, pSet->szPathAbs, sizeof(pSet->szPathAbs));
719 AssertRCReturn(rc, rc);
720
721 rc = AudioTestPathCreate(pSet->szPathAbs, sizeof(pSet->szPathAbs), pSet->szTag);
722 }
723 else
724 rc = AudioTestPathCreateTemp(pSet->szPathAbs, sizeof(pSet->szPathAbs), pSet->szTag);
725 AssertRCReturn(rc, rc);
726
727 /*
728 * Create the manifest file.
729 */
730 char szTmp[RTPATH_MAX];
731 rc = RTPathJoin(szTmp, sizeof(szTmp), pSet->szPathAbs, AUDIOTEST_MANIFEST_FILE_STR);
732 AssertRCReturn(rc, rc);
733
734 rc = RTFileOpen(&pSet->f.hFile, szTmp, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
735 AssertRCReturn(rc, rc);
736
737 rc = audioTestManifestWriteSectionHdr(pSet, "header");
738 AssertRCReturn(rc, rc);
739
740 rc = audioTestManifestWrite(pSet, "magic=vkat_ini\n"); /* VKAT Manifest, .INI-style. */
741 AssertRCReturn(rc, rc);
742 rc = audioTestManifestWrite(pSet, "ver=%d\n", AUDIOTEST_MANIFEST_VER);
743 AssertRCReturn(rc, rc);
744 rc = audioTestManifestWrite(pSet, "tag=%s\n", pSet->szTag);
745 AssertRCReturn(rc, rc);
746
747 AssertCompile(sizeof(szTmp) > RTTIME_STR_LEN);
748 RTTIMESPEC Now;
749 rc = audioTestManifestWrite(pSet, "date_created=%s\n", RTTimeSpecToString(RTTimeNow(&Now), szTmp, sizeof(szTmp)));
750 AssertRCReturn(rc, rc);
751
752 RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp)); /* do NOT return on failure. */
753 rc = audioTestManifestWrite(pSet, "os_product=%s\n", szTmp);
754 AssertRCReturn(rc, rc);
755
756 RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp)); /* do NOT return on failure. */
757 rc = audioTestManifestWrite(pSet, "os_rel=%s\n", szTmp);
758 AssertRCReturn(rc, rc);
759
760 RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp)); /* do NOT return on failure. */
761 rc = audioTestManifestWrite(pSet, "os_ver=%s\n", szTmp);
762 AssertRCReturn(rc, rc);
763
764 rc = audioTestManifestWrite(pSet, "vbox_ver=%s r%u %s (%s %s)\n",
765 VBOX_VERSION_STRING, RTBldCfgRevision(), RTBldCfgTargetDotArch(), __DATE__, __TIME__);
766 AssertRCReturn(rc, rc);
767
768 rc = audioTestManifestWrite(pSet, "test_count=");
769 AssertRCReturn(rc, rc);
770 pSet->offTestCount = audioTestManifestGetOffsetAbs(pSet);
771 rc = audioTestManifestWrite(pSet, "0000\n"); /* A bit messy, but does the trick for now. */
772 AssertRCReturn(rc, rc);
773
774 rc = audioTestManifestWrite(pSet, "obj_count=");
775 AssertRCReturn(rc, rc);
776 pSet->offObjCount = audioTestManifestGetOffsetAbs(pSet);
777 rc = audioTestManifestWrite(pSet, "0000\n"); /* A bit messy, but does the trick for now. */
778 AssertRCReturn(rc, rc);
779
780 pSet->enmMode = AUDIOTESTSETMODE_TEST;
781
782 return rc;
783}
784
785/**
786 * Destroys a test set.
787 *
788 * @returns VBox status code.
789 * @param pSet Test set to destroy.
790 */
791int AudioTestSetDestroy(PAUDIOTESTSET pSet)
792{
793 if (!pSet)
794 return VINF_SUCCESS;
795
796 AssertReturn(pSet->cTestsRunning == 0, VERR_WRONG_ORDER); /* Make sure no tests sill are running. */
797
798 int rc = AudioTestSetClose(pSet);
799 if (RT_FAILURE(rc))
800 return rc;
801
802 PAUDIOTESTOBJ pObj, pObjNext;
803 RTListForEachSafe(&pSet->lstObj, pObj, pObjNext, AUDIOTESTOBJ, Node)
804 {
805 rc = AudioTestSetObjClose(pObj);
806 if (RT_SUCCESS(rc))
807 {
808 PAUDIOTESTOBJMETA pMeta, pMetaNext;
809 RTListForEachSafe(&pObj->lstMeta, pMeta, pMetaNext, AUDIOTESTOBJMETA, Node)
810 {
811 switch (pMeta->enmType)
812 {
813 case AUDIOTESTOBJMETADATATYPE_STRING:
814 {
815 RTStrFree((char *)pMeta->pvMeta);
816 break;
817 }
818
819 default:
820 AssertFailed();
821 break;
822 }
823
824 RTListNodeRemove(&pMeta->Node);
825 RTMemFree(pMeta);
826 }
827
828 RTListNodeRemove(&pObj->Node);
829 RTMemFree(pObj);
830
831 Assert(pSet->cObj);
832 pSet->cObj--;
833 }
834 else
835 break;
836 }
837
838 if (RT_FAILURE(rc))
839 return rc;
840
841 Assert(pSet->cObj == 0);
842
843 PAUDIOTESTENTRY pEntry, pEntryNext;
844 RTListForEachSafe(&pSet->lstTest, pEntry, pEntryNext, AUDIOTESTENTRY, Node)
845 {
846 RTListNodeRemove(&pEntry->Node);
847 RTMemFree(pEntry);
848
849 Assert(pSet->cTests);
850 pSet->cTests--;
851 }
852
853 if (RT_FAILURE(rc))
854 return rc;
855
856 Assert(pSet->cTests == 0);
857
858 return rc;
859}
860
861/**
862 * Opens an existing audio test set.
863 *
864 * @returns VBox status code.
865 * @param pSet Test set to open.
866 * @param pszPath Absolute path of the test set to open.
867 */
868int AudioTestSetOpen(PAUDIOTESTSET pSet, const char *pszPath)
869{
870 audioTestSetInitInternal(pSet);
871
872 char szManifest[RTPATH_MAX];
873 int rc = RTPathJoin(szManifest, sizeof(szManifest), pszPath, AUDIOTEST_MANIFEST_FILE_STR);
874 AssertRCReturn(rc, rc);
875
876 RTVFSFILE hVfsFile;
877 rc = RTVfsFileOpenNormal(szManifest, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hVfsFile);
878 if (RT_FAILURE(rc))
879 return rc;
880
881 rc = RTIniFileCreateFromVfsFile(&pSet->f.hIniFile, hVfsFile, RTINIFILE_F_READONLY);
882 RTVfsFileRelease(hVfsFile);
883 AssertRCReturn(rc, rc);
884
885 pSet->enmMode = AUDIOTESTSETMODE_VERIFY;
886
887 return rc;
888}
889
890/**
891 * Closes an opened audio test set.
892 *
893 * @returns VBox status code.
894 * @param pSet Test set to close.
895 */
896int AudioTestSetClose(PAUDIOTESTSET pSet)
897{
898 if (!pSet)
899 return VINF_SUCCESS;
900
901 if (!RTFileIsValid(pSet->f.hFile))
902 return VINF_SUCCESS;
903
904 int rc;
905
906 /* Update number of bound test objects. */
907 PAUDIOTESTENTRY pTest;
908 RTListForEach(&pSet->lstTest, pTest, AUDIOTESTENTRY, Node)
909 {
910 rc = RTFileSeek(pSet->f.hFile, pTest->offObjCount, RTFILE_SEEK_BEGIN, NULL);
911 AssertRCReturn(rc, rc);
912 rc = audioTestManifestWrite(pSet, "%04RU32", pTest->cObj);
913 AssertRCReturn(rc, rc);
914 }
915
916 /*
917 * Update number of ran tests.
918 */
919 rc = RTFileSeek(pSet->f.hFile, pSet->offObjCount, RTFILE_SEEK_BEGIN, NULL);
920 AssertRCReturn(rc, rc);
921 rc = audioTestManifestWrite(pSet, "%04RU32", pSet->cObj);
922 AssertRCReturn(rc, rc);
923
924 /*
925 * Update number of ran tests.
926 */
927 rc = RTFileSeek(pSet->f.hFile, pSet->offTestCount, RTFILE_SEEK_BEGIN, NULL);
928 AssertRCReturn(rc, rc);
929 rc = audioTestManifestWrite(pSet, "%04RU32", pSet->cTests);
930 AssertRCReturn(rc, rc);
931
932 /*
933 * Serialize all registered test objects.
934 */
935 rc = RTFileSeek(pSet->f.hFile, 0, RTFILE_SEEK_END, NULL);
936 AssertRCReturn(rc, rc);
937
938 PAUDIOTESTOBJ pObj;
939 RTListForEach(&pSet->lstObj, pObj, AUDIOTESTOBJ, Node)
940 {
941 rc = audioTestManifestWrite(pSet, "\n");
942 AssertRCReturn(rc, rc);
943 char szUuid[64];
944 rc = RTUuidToStr(&pObj->Uuid, szUuid, sizeof(szUuid));
945 AssertRCReturn(rc, rc);
946 rc = audioTestManifestWriteSectionHdr(pSet, "obj_%s", szUuid);
947 AssertRCReturn(rc, rc);
948 rc = audioTestManifestWrite(pSet, "obj_type=%RU32\n", pObj->enmType);
949 AssertRCReturn(rc, rc);
950 rc = audioTestManifestWrite(pSet, "obj_name=%s\n", pObj->szName);
951 AssertRCReturn(rc, rc);
952
953 switch (pObj->enmType)
954 {
955 case AUDIOTESTOBJTYPE_FILE:
956 {
957 rc = audioTestManifestWrite(pSet, "obj_size=%RU64\n", pObj->File.cbSize);
958 AssertRCReturn(rc, rc);
959 break;
960 }
961
962 default:
963 AssertFailed();
964 break;
965 }
966
967 /*
968 * Write all meta data.
969 */
970 PAUDIOTESTOBJMETA pMeta;
971 RTListForEach(&pObj->lstMeta, pMeta, AUDIOTESTOBJMETA, Node)
972 {
973 switch (pMeta->enmType)
974 {
975 case AUDIOTESTOBJMETADATATYPE_STRING:
976 {
977 rc = audioTestManifestWrite(pSet, (const char *)pMeta->pvMeta);
978 AssertRCReturn(rc, rc);
979 break;
980 }
981
982 default:
983 AssertFailed();
984 break;
985 }
986 }
987 }
988
989 RTFileClose(pSet->f.hFile);
990 pSet->f.hFile = NIL_RTFILE;
991
992 return rc;
993}
994
995/**
996 * Physically wipes all related test set files off the disk.
997 *
998 * @returns VBox status code.
999 * @param pSet Test set to wipe.
1000 */
1001int AudioTestSetWipe(PAUDIOTESTSET pSet)
1002{
1003 AssertPtrReturn(pSet, VERR_INVALID_POINTER);
1004
1005 int rc = VINF_SUCCESS;
1006 char szFilePath[RTPATH_MAX];
1007
1008 PAUDIOTESTOBJ pObj;
1009 RTListForEach(&pSet->lstObj, pObj, AUDIOTESTOBJ, Node)
1010 {
1011 int rc2 = AudioTestSetObjClose(pObj);
1012 if (RT_SUCCESS(rc2))
1013 {
1014 rc2 = audioTestSetGetObjPath(pSet, szFilePath, sizeof(szFilePath), pObj->szName);
1015 if (RT_SUCCESS(rc2))
1016 rc2 = RTFileDelete(szFilePath);
1017 }
1018
1019 if (RT_SUCCESS(rc))
1020 rc = rc2;
1021 /* Keep going. */
1022 }
1023
1024 if (RT_SUCCESS(rc))
1025 {
1026 rc = RTPathJoin(szFilePath, sizeof(szFilePath), pSet->szPathAbs, AUDIOTEST_MANIFEST_FILE_STR);
1027 if (RT_SUCCESS(rc))
1028 rc = RTFileDelete(szFilePath);
1029 }
1030
1031 /* Remove the (hopefully now empty) directory. Otherwise let this fail. */
1032 if (RT_SUCCESS(rc))
1033 rc = RTDirRemove(pSet->szPathAbs);
1034
1035 return rc;
1036}
1037
1038/**
1039 * Creates and registers a new audio test object to the current running test.
1040 *
1041 * @returns VBox status code.
1042 * @param pSet Test set to create and register new object for.
1043 * @param pszName Name of new object to create.
1044 * @param ppObj Where to return the pointer to the newly created object on success.
1045 */
1046int AudioTestSetObjCreateAndRegister(PAUDIOTESTSET pSet, const char *pszName, PAUDIOTESTOBJ *ppObj)
1047{
1048 AssertReturn(pSet->cTestsRunning == 1, VERR_WRONG_ORDER); /* No test nesting allowed. */
1049
1050 AssertPtrReturn(pszName, VERR_INVALID_POINTER);
1051
1052 PAUDIOTESTOBJ pObj = (PAUDIOTESTOBJ)RTMemAlloc(sizeof(AUDIOTESTOBJ));
1053 AssertPtrReturn(pObj, VERR_NO_MEMORY);
1054
1055 RTListInit(&pObj->lstMeta);
1056
1057 if (RTStrPrintf2(pObj->szName, sizeof(pObj->szName), "%04RU32-%s", pSet->cObj, pszName) <= 0)
1058 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
1059
1060 /** @todo Generalize this function more once we have more object types. */
1061
1062 char szObjPathAbs[RTPATH_MAX];
1063 int rc = audioTestSetGetObjPath(pSet, szObjPathAbs, sizeof(szObjPathAbs), pObj->szName);
1064 if (RT_SUCCESS(rc))
1065 {
1066 rc = RTFileOpen(&pObj->File.hFile, szObjPathAbs, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
1067 if (RT_SUCCESS(rc))
1068 {
1069 pObj->enmType = AUDIOTESTOBJTYPE_FILE;
1070 pObj->cRefs = 1; /* Currently only 1:1 mapping. */
1071
1072 RTListAppend(&pSet->lstObj, &pObj->Node);
1073 pSet->cObj++;
1074
1075 /* Generate + set an UUID for the object and assign it to the current test. */
1076 rc = RTUuidCreate(&pObj->Uuid);
1077 AssertRCReturn(rc, rc);
1078 char szUuid[64];
1079 rc = RTUuidToStr(&pObj->Uuid, szUuid, sizeof(szUuid));
1080 AssertRCReturn(rc, rc);
1081
1082 rc = audioTestManifestWrite(pSet, "obj%RU32_uuid=%s\n", pSet->pTestCur->cObj, szUuid);
1083 AssertRCReturn(rc, rc);
1084
1085 AssertPtr(pSet->pTestCur);
1086 pSet->pTestCur->cObj++;
1087
1088 *ppObj = pObj;
1089 }
1090 }
1091
1092 if (RT_FAILURE(rc))
1093 RTMemFree(pObj);
1094
1095 return rc;
1096}
1097
1098/**
1099 * Writes to a created audio test object.
1100 *
1101 * @returns VBox status code.
1102 * @param pObj Audio test object to write to.
1103 * @param pvBuf Pointer to data to write.
1104 * @param cbBuf Size (in bytes) of \a pvBuf to write.
1105 */
1106int AudioTestSetObjWrite(PAUDIOTESTOBJ pObj, const void *pvBuf, size_t cbBuf)
1107{
1108 /** @todo Generalize this function more once we have more object types. */
1109 AssertReturn(pObj->enmType == AUDIOTESTOBJTYPE_FILE, VERR_INVALID_PARAMETER);
1110
1111 return RTFileWrite(pObj->File.hFile, pvBuf, cbBuf, NULL);
1112}
1113
1114/**
1115 * Adds meta data to a test object as a string, va_list version.
1116 *
1117 * @returns VBox status code.
1118 * @param pObj Test object to add meta data for.
1119 * @param pszFormat Format string to add.
1120 * @param va Variable arguments list to use for the format string.
1121 */
1122static int audioTestSetObjAddMetadataStrV(PAUDIOTESTOBJ pObj, const char *pszFormat, va_list va)
1123{
1124 PAUDIOTESTOBJMETA pMeta = (PAUDIOTESTOBJMETA)RTMemAlloc(sizeof(AUDIOTESTOBJMETA));
1125 AssertPtrReturn(pMeta, VERR_NO_MEMORY);
1126
1127 pMeta->pvMeta = RTStrAPrintf2V(pszFormat, va);
1128 AssertPtrReturn(pMeta->pvMeta, VERR_BUFFER_OVERFLOW);
1129 pMeta->cbMeta = RTStrNLen((const char *)pMeta->pvMeta, RTSTR_MAX);
1130
1131 pMeta->enmType = AUDIOTESTOBJMETADATATYPE_STRING;
1132
1133 RTListAppend(&pObj->lstMeta, &pMeta->Node);
1134
1135 return VINF_SUCCESS;
1136}
1137
1138/**
1139 * Adds meta data to a test object as a string.
1140 *
1141 * @returns VBox status code.
1142 * @param pObj Test object to add meta data for.
1143 * @param pszFormat Format string to add.
1144 * @param ... Variable arguments for the format string.
1145 */
1146int AudioTestSetObjAddMetadataStr(PAUDIOTESTOBJ pObj, const char *pszFormat, ...)
1147{
1148 va_list va;
1149
1150 va_start(va, pszFormat);
1151 int rc = audioTestSetObjAddMetadataStrV(pObj, pszFormat, va);
1152 va_end(va);
1153
1154 return rc;
1155}
1156
1157/**
1158 * Closes an opened audio test object.
1159 *
1160 * @returns VBox status code.
1161 * @param pObj Audio test object to close.
1162 */
1163int AudioTestSetObjClose(PAUDIOTESTOBJ pObj)
1164{
1165 if (!pObj)
1166 return VINF_SUCCESS;
1167
1168 /** @todo Generalize this function more once we have more object types. */
1169 AssertReturn(pObj->enmType == AUDIOTESTOBJTYPE_FILE, VERR_INVALID_PARAMETER);
1170
1171 int rc = VINF_SUCCESS;
1172
1173 if (RTFileIsValid(pObj->File.hFile))
1174 {
1175 pObj->File.cbSize = RTFileTell(pObj->File.hFile);
1176
1177 rc = RTFileClose(pObj->File.hFile);
1178 pObj->File.hFile = NIL_RTFILE;
1179 }
1180
1181 return rc;
1182}
1183
1184/**
1185 * Begins a new test of a test set.
1186 *
1187 * @returns VBox status code.
1188 * @param pSet Test set to begin new test for.
1189 * @param pszDesc Test description.
1190 * @param pParms Test parameters to use.
1191 * @param ppEntry Where to return the new test handle.
1192 */
1193int AudioTestSetTestBegin(PAUDIOTESTSET pSet, const char *pszDesc, PAUDIOTESTPARMS pParms, PAUDIOTESTENTRY *ppEntry)
1194{
1195 AssertReturn(pSet->cTestsRunning == 0, VERR_WRONG_ORDER); /* No test nesting allowed. */
1196
1197 PAUDIOTESTENTRY pEntry = (PAUDIOTESTENTRY)RTMemAllocZ(sizeof(AUDIOTESTENTRY));
1198 AssertPtrReturn(pEntry, VERR_NO_MEMORY);
1199
1200 int rc = RTStrCopy(pEntry->szDesc, sizeof(pEntry->szDesc), pszDesc);
1201 AssertRCReturn(rc, rc);
1202
1203 memcpy(&pEntry->Parms, pParms, sizeof(AUDIOTESTPARMS));
1204 pEntry->pParent = pSet;
1205
1206 rc = audioTestManifestWrite(pSet, "\n");
1207 AssertRCReturn(rc, rc);
1208
1209 rc = audioTestManifestWriteSectionHdr(pSet, "test_%04RU32", pSet->cTests);
1210 AssertRCReturn(rc, rc);
1211 rc = audioTestManifestWrite(pSet, "test_desc=%s\n", pszDesc);
1212 AssertRCReturn(rc, rc);
1213 rc = audioTestManifestWrite(pSet, "test_type=%RU32\n", pParms->enmType);
1214 AssertRCReturn(rc, rc);
1215 rc = audioTestManifestWrite(pSet, "test_delay_ms=%RU32\n", pParms->msDelay);
1216 AssertRCReturn(rc, rc);
1217 rc = audioTestManifestWrite(pSet, "audio_direction=%s\n", PDMAudioDirGetName(pParms->enmDir));
1218 AssertRCReturn(rc, rc);
1219
1220 rc = audioTestManifestWrite(pSet, "obj_count=");
1221 AssertRCReturn(rc, rc);
1222 pEntry->offObjCount = audioTestManifestGetOffsetAbs(pSet);
1223 rc = audioTestManifestWrite(pSet, "0000\n"); /* A bit messy, but does the trick for now. */
1224 AssertRCReturn(rc, rc);
1225
1226 switch (pParms->enmType)
1227 {
1228 case AUDIOTESTTYPE_TESTTONE_PLAY:
1229 RT_FALL_THROUGH();
1230 case AUDIOTESTTYPE_TESTTONE_RECORD:
1231 {
1232 rc = audioTestManifestWrite(pSet, "tone_freq_hz=%RU16\n", (uint16_t)pParms->TestTone.dbFreqHz);
1233 AssertRCReturn(rc, rc);
1234 rc = audioTestManifestWrite(pSet, "tone_prequel_ms=%RU32\n", pParms->TestTone.msPrequel);
1235 AssertRCReturn(rc, rc);
1236 rc = audioTestManifestWrite(pSet, "tone_duration_ms=%RU32\n", pParms->TestTone.msDuration);
1237 AssertRCReturn(rc, rc);
1238 rc = audioTestManifestWrite(pSet, "tone_sequel_ms=%RU32\n", pParms->TestTone.msSequel);
1239 AssertRCReturn(rc, rc);
1240 rc = audioTestManifestWrite(pSet, "tone_volume_percent=%RU32\n", pParms->TestTone.uVolumePercent);
1241 AssertRCReturn(rc, rc);
1242 rc = audioTestManifestWrite(pSet, "tone_pcm_hz=%RU32\n", PDMAudioPropsHz(&pParms->TestTone.Props));
1243 AssertRCReturn(rc, rc);
1244 rc = audioTestManifestWrite(pSet, "tone_pcm_channels=%RU8\n", PDMAudioPropsChannels(&pParms->TestTone.Props));
1245 AssertRCReturn(rc, rc);
1246 rc = audioTestManifestWrite(pSet, "tone_pcm_bits=%RU8\n", PDMAudioPropsSampleBits(&pParms->TestTone.Props));
1247 AssertRCReturn(rc, rc);
1248 rc = audioTestManifestWrite(pSet, "tone_pcm_is_signed=%RTbool\n", PDMAudioPropsIsSigned(&pParms->TestTone.Props));
1249 AssertRCReturn(rc, rc);
1250 break;
1251 }
1252
1253 default:
1254 AssertFailed();
1255 break;
1256 }
1257
1258 RTListAppend(&pSet->lstTest, &pEntry->Node);
1259 pSet->cTests++;
1260 pSet->cTestsRunning++;
1261 pSet->pTestCur = pEntry;
1262
1263 *ppEntry = pEntry;
1264
1265 return rc;
1266}
1267
1268/**
1269 * Marks a running test as failed.
1270 *
1271 * @returns VBox status code.
1272 * @param pEntry Test to mark.
1273 * @param rc Error code.
1274 * @param pszErr Error description.
1275 */
1276int AudioTestSetTestFailed(PAUDIOTESTENTRY pEntry, int rc, const char *pszErr)
1277{
1278 AssertReturn(pEntry->pParent->cTestsRunning == 1, VERR_WRONG_ORDER); /* No test nesting allowed. */
1279 AssertReturn(pEntry->rc == VINF_SUCCESS, VERR_WRONG_ORDER);
1280
1281 pEntry->rc = rc;
1282
1283 int rc2 = audioTestManifestWrite(pEntry->pParent, "error_rc=%RI32\n", rc);
1284 AssertRCReturn(rc2, rc2);
1285 rc2 = audioTestManifestWrite(pEntry->pParent, "error_desc=%s\n", pszErr);
1286 AssertRCReturn(rc2, rc2);
1287
1288 pEntry->pParent->cTestsRunning--;
1289 pEntry->pParent->pTestCur = NULL;
1290
1291 return rc2;
1292}
1293
1294/**
1295 * Marks a running test as successfully done.
1296 *
1297 * @returns VBox status code.
1298 * @param pEntry Test to mark.
1299 */
1300int AudioTestSetTestDone(PAUDIOTESTENTRY pEntry)
1301{
1302 AssertReturn(pEntry->pParent->cTestsRunning == 1, VERR_WRONG_ORDER); /* No test nesting allowed. */
1303 AssertReturn(pEntry->rc == VINF_SUCCESS, VERR_WRONG_ORDER);
1304
1305 int rc2 = audioTestManifestWrite(pEntry->pParent, "error_rc=%RI32\n", VINF_SUCCESS);
1306 AssertRCReturn(rc2, rc2);
1307
1308 pEntry->pParent->cTestsRunning--;
1309 pEntry->pParent->pTestCur = NULL;
1310
1311 return rc2;
1312}
1313
1314/**
1315 * Packs a closed audio test so that it's ready for transmission.
1316 *
1317 * @returns VBox status code.
1318 * @param pSet Test set to pack.
1319 * @param pszOutDir Directory where to store the packed test set.
1320 * @param pszFileName Where to return the final name of the packed test set. Optional and can be NULL.
1321 * @param cbFileName Size (in bytes) of \a pszFileName.
1322 */
1323int AudioTestSetPack(PAUDIOTESTSET pSet, const char *pszOutDir, char *pszFileName, size_t cbFileName)
1324{
1325 AssertReturn(!pszFileName || cbFileName, VERR_INVALID_PARAMETER);
1326 AssertReturn(!audioTestManifestIsOpen(pSet), VERR_WRONG_ORDER);
1327
1328 /** @todo Check and deny if \a pszOutDir is part of the set's path. */
1329
1330 int rc = RTDirCreateFullPath(pszOutDir, 0755);
1331 if (RT_FAILURE(rc))
1332 return rc;
1333
1334 char szOutName[RT_ELEMENTS(AUDIOTEST_PATH_PREFIX_STR) + AUDIOTEST_TAG_MAX + 16];
1335 if (RTStrPrintf2(szOutName, sizeof(szOutName), "%s-%s%s",
1336 AUDIOTEST_PATH_PREFIX_STR, pSet->szTag, AUDIOTEST_ARCHIVE_SUFF_STR) <= 0)
1337 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
1338
1339 char szOutPath[RTPATH_MAX];
1340 rc = RTPathJoin(szOutPath, sizeof(szOutPath), pszOutDir, szOutName);
1341 AssertRCReturn(rc, rc);
1342
1343 const char *apszArgs[10];
1344 unsigned cArgs = 0;
1345
1346 apszArgs[cArgs++] = "vkat";
1347 apszArgs[cArgs++] = "--create";
1348 apszArgs[cArgs++] = "--gzip";
1349 apszArgs[cArgs++] = "--directory";
1350 apszArgs[cArgs++] = pSet->szPathAbs;
1351 apszArgs[cArgs++] = "--file";
1352 apszArgs[cArgs++] = szOutPath;
1353 apszArgs[cArgs++] = ".";
1354
1355 RTEXITCODE rcExit = RTZipTarCmd(cArgs, (char **)apszArgs);
1356 if (rcExit != RTEXITCODE_SUCCESS)
1357 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1358
1359 if (RT_SUCCESS(rc))
1360 {
1361 if (pszFileName)
1362 rc = RTStrCopy(pszFileName, cbFileName, szOutPath);
1363 }
1364
1365 return rc;
1366}
1367
1368/**
1369 * Returns whether a test set archive is packed (as .tar.gz by default) or
1370 * a plain directory.
1371 *
1372 * @returns \c true if packed (as .tar.gz), or \c false if not (directory).
1373 * @param pszPath Path to return packed staus for.
1374 */
1375bool AudioTestSetIsPacked(const char *pszPath)
1376{
1377 /** @todo Improve this, good enough for now. */
1378 return (RTStrIStr(pszPath, AUDIOTEST_ARCHIVE_SUFF_STR) != NULL);
1379}
1380
1381/**
1382 * Unpacks a formerly packed audio test set.
1383 *
1384 * @returns VBox status code.
1385 * @param pszFile Test set file to unpack. Must contain the absolute path.
1386 * @param pszOutDir Directory where to unpack the test set into.
1387 * If the directory does not exist it will be created.
1388 */
1389int AudioTestSetUnpack(const char *pszFile, const char *pszOutDir)
1390{
1391 AssertReturn(pszFile && pszOutDir, VERR_INVALID_PARAMETER);
1392
1393 int rc = VINF_SUCCESS;
1394
1395 if (!RTDirExists(pszOutDir))
1396 {
1397 rc = RTDirCreateFullPath(pszOutDir, 0755);
1398 if (RT_FAILURE(rc))
1399 return rc;
1400 }
1401
1402 const char *apszArgs[8];
1403 unsigned cArgs = 0;
1404
1405 apszArgs[cArgs++] = "vkat";
1406 apszArgs[cArgs++] = "--extract";
1407 apszArgs[cArgs++] = "--gunzip";
1408 apszArgs[cArgs++] = "--directory";
1409 apszArgs[cArgs++] = pszOutDir;
1410 apszArgs[cArgs++] = "--file";
1411 apszArgs[cArgs++] = pszFile;
1412
1413 RTEXITCODE rcExit = RTZipTarCmd(cArgs, (char **)apszArgs);
1414 if (rcExit != RTEXITCODE_SUCCESS)
1415 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1416
1417 return rc;
1418}
1419
1420/**
1421 * Gets a value as string.
1422 *
1423 * @returns VBox status code.
1424 * @param pSet Test set to get value from.
1425 * @param pszSec Section to get value from.
1426 * @param pszKey Key to get value from.
1427 * @param pszVal Where to return the value on success.
1428 * @param cbVal Size (in bytes) of \a pszVal.
1429 */
1430static int audioTestGetValueStr(PAUDIOTESTSET pSet,
1431 const char *pszSec, const char *pszKey, char *pszVal, size_t cbVal)
1432{
1433 return RTIniFileQueryValue(pSet->f.hIniFile, pszSec, pszKey, pszVal, cbVal, NULL);
1434}
1435
1436/**
1437 * Gets a value as uint32_t.
1438 *
1439 * @returns VBox status code.
1440 * @param pSet Test set to get value from.
1441 * @param pszSec Section to get value from.
1442 * @param pszKey Key to get value from.
1443 * @param puVal Where to return the value on success.
1444 */
1445static int audioTestGetValueUInt32(PAUDIOTESTSET pSet,
1446 const char *pszSec, const char *pszKey, uint32_t *puVal)
1447{
1448 char szVal[_1K];
1449 int rc = audioTestGetValueStr(pSet, pszSec, pszKey, szVal, sizeof(szVal));
1450 if (RT_SUCCESS(rc))
1451 *puVal = RTStrToUInt32(szVal);
1452
1453 return rc;
1454}
1455
1456/**
1457 * Verifies a value of a test verification job.
1458 *
1459 * @returns VBox status code.
1460 * @returns Error if the verification failed and test verification job has fKeepGoing not set.
1461 * @param pVerify Verification job to verify value for.
1462 * @param pszSec Section of key / value to verify.
1463 * @param pszKey Key to verify.
1464 * @param pszVal Value to verify.
1465 * @param pszErrFmt Error format string in case the verification failed.
1466 * @param ... Variable aruments for error format string.
1467 */
1468static int audioTestVerifyValue(PAUDIOTESTVERIFYJOB pVerify,
1469 const char *pszSec, const char *pszKey, const char *pszVal, const char *pszErrFmt, ...)
1470{
1471 va_list va;
1472 va_start(va, pszErrFmt);
1473
1474 char szValA[_1K];
1475 int rc = audioTestGetValueStr(pVerify->pSetA, pszSec, pszKey, szValA, sizeof(szValA));
1476 if (RT_SUCCESS(rc))
1477 {
1478 char szValB[_1K];
1479 rc = audioTestGetValueStr(pVerify->pSetB, pszSec, pszKey, szValB, sizeof(szValB));
1480 if (RT_SUCCESS(rc))
1481 {
1482 if (RTStrCmp(szValA, szValB))
1483 rc = VERR_WRONG_TYPE; /** @todo Fudge! */
1484
1485 if (pszVal)
1486 {
1487 if (RTStrCmp(szValA, pszVal))
1488 rc = VERR_WRONG_TYPE; /** @todo Fudge! */
1489 }
1490 }
1491 }
1492
1493 if (RT_FAILURE(rc))
1494 {
1495 int rc2 = audioTestErrorDescAddV(pVerify->pErr, pVerify->idxTest, rc, pszErrFmt, va);
1496 AssertRC(rc2);
1497 }
1498
1499 va_end(va);
1500
1501 return pVerify->fKeepGoing ? VINF_SUCCESS : rc;
1502}
1503
1504/**
1505 * Verifies a test tone test.
1506 *
1507 * @returns VBox status code.
1508 * @returns Error if the verification failed and test verification job has fKeepGoing not set.
1509 * @retval VERR_
1510 * @param pVerify Verification job to verify test tone for.
1511 * @param pszSec Section of test tone to verify.
1512 * @param pSetPlay Test set which did the playing part.
1513 * @param pSetRecord Test set which did the recording part.
1514 */
1515static int audioTestVerifyTestTone(PAUDIOTESTVERIFYJOB pVerify, const char *pszSec, PAUDIOTESTSET pSetPlay, PAUDIOTESTSET pSetRecord)
1516{
1517 RT_NOREF(pSetPlay, pSetRecord);
1518
1519 int rc;
1520
1521 /*
1522 * Verify test parameters.
1523 * More important items have precedence.
1524 */
1525 rc = audioTestVerifyValue(pVerify, pszSec, "error_rc", "0", "Test was reported as failed");
1526 AssertRCReturn(rc, rc);
1527 rc = audioTestVerifyValue(pVerify, pszSec, "obj_count", NULL, "Object counts don't match");
1528 AssertRCReturn(rc, rc);
1529 rc = audioTestVerifyValue(pVerify, pszSec, "tone_freq_hz", NULL, "Tone frequency doesn't match");
1530 AssertRCReturn(rc, rc);
1531 rc = audioTestVerifyValue(pVerify, pszSec, "tone_prequel_ms", NULL, "Tone prequel (ms) doesn't match");
1532 AssertRCReturn(rc, rc);
1533 rc = audioTestVerifyValue(pVerify, pszSec, "tone_duration_ms", NULL, "Tone duration (ms) doesn't match");
1534 AssertRCReturn(rc, rc);
1535 rc = audioTestVerifyValue(pVerify, pszSec, "tone_sequel_ms", NULL, "Tone sequel (ms) doesn't match");
1536 AssertRCReturn(rc, rc);
1537 rc = audioTestVerifyValue(pVerify, pszSec, "tone_volume_percent", NULL, "Tone volume (percent) doesn't match");
1538 AssertRCReturn(rc, rc);
1539 rc = audioTestVerifyValue(pVerify, pszSec, "tone_pcm_hz", NULL, "Tone PCM Hz doesn't match");
1540 AssertRCReturn(rc, rc);
1541 rc = audioTestVerifyValue(pVerify, pszSec, "tone_pcm_channels", NULL, "Tone PCM channels don't match");
1542 AssertRCReturn(rc, rc);
1543 rc = audioTestVerifyValue(pVerify, pszSec, "tone_pcm_bits", NULL, "Tone PCM bits don't match");
1544 AssertRCReturn(rc, rc);
1545 rc = audioTestVerifyValue(pVerify, pszSec, "tone_pcm_is_signed", NULL, "Tone PCM signed bit doesn't match");
1546 AssertRCReturn(rc, rc);
1547
1548 return VINF_SUCCESS;
1549}
1550
1551/**
1552 * Verifies an opened audio test set.
1553 *
1554 * @returns VBox status code.
1555 * @param pSetA Test set A to verify.
1556 * @param pSetB Test set to verify test set A with.
1557 * @param pErrDesc Where to return the test verification errors.
1558 *
1559 * @note Test verification errors have to be checked for errors, regardless of the
1560 * actual return code.
1561 */
1562int AudioTestSetVerify(PAUDIOTESTSET pSetA, PAUDIOTESTSET pSetB, PAUDIOTESTERRORDESC pErrDesc)
1563{
1564 AssertReturn(audioTestManifestIsOpen(pSetA), VERR_WRONG_ORDER);
1565 AssertReturn(audioTestManifestIsOpen(pSetB), VERR_WRONG_ORDER);
1566
1567 /* We ASSUME the caller has not init'd pErrDesc. */
1568 audioTestErrorDescInit(pErrDesc);
1569
1570 AUDIOTESTVERIFYJOB VerJob;
1571 RT_ZERO(VerJob);
1572 VerJob.pErr = pErrDesc;
1573 VerJob.pSetA = pSetA;
1574 VerJob.pSetB = pSetB;
1575 VerJob.fKeepGoing = true;
1576
1577 int rc;
1578
1579 /*
1580 * Compare obvious values first.
1581 */
1582 rc = audioTestVerifyValue(&VerJob, "header", "magic", "vkat_ini", "Manifest magic wrong");
1583 AssertRCReturn(rc, rc);
1584 rc = audioTestVerifyValue(&VerJob, "header", "ver", "1" , "Manifest version wrong");
1585 AssertRCReturn(rc, rc);
1586 rc = audioTestVerifyValue(&VerJob, "header", "tag", NULL, "Manifest tags don't match");
1587 AssertRCReturn(rc, rc);
1588 rc = audioTestVerifyValue(&VerJob, "header", "test_count", NULL, "Test counts don't match");
1589 AssertRCReturn(rc, rc);
1590 rc = audioTestVerifyValue(&VerJob, "header", "obj_count", NULL, "Object counts don't match");
1591 AssertRCReturn(rc, rc);
1592
1593 if ( pErrDesc->cErrors
1594 && !VerJob.fKeepGoing)
1595 return VINF_SUCCESS;
1596
1597 /*
1598 * Compare ran tests.
1599 */
1600 uint32_t cTests;
1601 rc = audioTestGetValueUInt32(VerJob.pSetA, "header", "test_count", &cTests);
1602 AssertRCReturn(rc, rc);
1603
1604 for (uint32_t i = 0; i < cTests; i++)
1605 {
1606 VerJob.idxTest = i;
1607
1608 char szSec[64];
1609 RTStrPrintf(szSec, sizeof(szSec), "test_%04RU32", i);
1610
1611 AUDIOTESTTYPE enmTestTypeA;
1612 audioTestGetValueUInt32(VerJob.pSetA, szSec, "test_type", (uint32_t *)&enmTestTypeA);
1613 AUDIOTESTTYPE enmTestTypeB;
1614 audioTestGetValueUInt32(VerJob.pSetB, szSec, "test_type", (uint32_t *)&enmTestTypeB);
1615
1616 switch (enmTestTypeA)
1617 {
1618 case AUDIOTESTTYPE_TESTTONE_PLAY:
1619 {
1620 if (enmTestTypeB == AUDIOTESTTYPE_TESTTONE_RECORD)
1621 {
1622 rc = audioTestVerifyTestTone(&VerJob, szSec, VerJob.pSetA, VerJob.pSetB);
1623 }
1624 else
1625 rc = audioTestErrorDescAdd(pErrDesc, i, "Playback test types don't match (set A=%#x, set B=%#x)",
1626 enmTestTypeA, enmTestTypeB);
1627 break;
1628 }
1629
1630 case AUDIOTESTTYPE_TESTTONE_RECORD:
1631 {
1632 if (enmTestTypeB != AUDIOTESTTYPE_TESTTONE_PLAY)
1633 {
1634 rc = audioTestVerifyTestTone(&VerJob, szSec, VerJob.pSetB, VerJob.pSetA);
1635 }
1636 else
1637 rc = audioTestErrorDescAdd(pErrDesc, i, "Recording test types don't match (set A=%#x, set B=%#x)",
1638 enmTestTypeA, enmTestTypeB);
1639 break;
1640 }
1641
1642 case AUDIOTESTTYPE_INVALID:
1643 rc = VERR_INVALID_PARAMETER;
1644 break;
1645
1646 default:
1647 rc = VERR_NOT_IMPLEMENTED;
1648 break;
1649 }
1650
1651 AssertRC(rc);
1652 }
1653
1654#undef VERIFY_VALUE
1655
1656 /* Only return critical stuff not related to actual testing here. */
1657 return VINF_SUCCESS;
1658}
1659
1660
1661/*********************************************************************************************************************************
1662* WAVE File Reader. *
1663*********************************************************************************************************************************/
1664
1665/**
1666 * Counts the number of set bits in @a fMask.
1667 */
1668static unsigned audioTestWaveCountBits(uint32_t fMask)
1669{
1670 unsigned cBits = 0;
1671 while (fMask)
1672 {
1673 if (fMask & 1)
1674 cBits++;
1675 fMask >>= 1;
1676 }
1677 return cBits;
1678}
1679
1680/**
1681 * Opens a wave (.WAV) file for reading.
1682 *
1683 * @returns VBox status code.
1684 * @param pszFile The file to open.
1685 * @param pWaveFile The open wave file structure to fill in on success.
1686 * @param pErrInfo Where to return addition error details on failure.
1687 */
1688int AudioTestWaveFileOpen(const char *pszFile, PAUDIOTESTWAVEFILE pWaveFile, PRTERRINFO pErrInfo)
1689{
1690 pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC_DEAD;
1691 RT_ZERO(pWaveFile->Props);
1692 pWaveFile->hFile = NIL_RTFILE;
1693 int rc = RTFileOpen(&pWaveFile->hFile, pszFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
1694 if (RT_FAILURE(rc))
1695 return RTErrInfoSet(pErrInfo, rc, "RTFileOpen failed");
1696 uint64_t cbFile = 0;
1697 rc = RTFileQuerySize(pWaveFile->hFile, &cbFile);
1698 if (RT_SUCCESS(rc))
1699 {
1700 union
1701 {
1702 uint8_t ab[512];
1703 struct
1704 {
1705 RTRIFFHDR Hdr;
1706 union
1707 {
1708 RTRIFFWAVEFMTCHUNK Fmt;
1709 RTRIFFWAVEFMTEXTCHUNK FmtExt;
1710 } u;
1711 } Wave;
1712 RTRIFFLIST List;
1713 RTRIFFCHUNK Chunk;
1714 RTRIFFWAVEDATACHUNK Data;
1715 } uBuf;
1716
1717 rc = RTFileRead(pWaveFile->hFile, &uBuf.Wave, sizeof(uBuf.Wave), NULL);
1718 if (RT_SUCCESS(rc))
1719 {
1720 rc = VERR_VFS_UNKNOWN_FORMAT;
1721 if ( uBuf.Wave.Hdr.uMagic == RTRIFFHDR_MAGIC
1722 && uBuf.Wave.Hdr.uFileType == RTRIFF_FILE_TYPE_WAVE
1723 && uBuf.Wave.u.Fmt.Chunk.uMagic == RTRIFFWAVEFMT_MAGIC
1724 && uBuf.Wave.u.Fmt.Chunk.cbChunk >= sizeof(uBuf.Wave.u.Fmt.Data))
1725 {
1726 if (uBuf.Wave.Hdr.cbFile != cbFile - sizeof(RTRIFFCHUNK))
1727 RTErrInfoSetF(pErrInfo, rc, "File size mismatch: %#x, actual %#RX64 (ignored)",
1728 uBuf.Wave.Hdr.cbFile, cbFile - sizeof(RTRIFFCHUNK));
1729 rc = VERR_VFS_BOGUS_FORMAT;
1730 if ( uBuf.Wave.u.Fmt.Data.uFormatTag != RTRIFFWAVEFMT_TAG_PCM
1731 && uBuf.Wave.u.Fmt.Data.uFormatTag != RTRIFFWAVEFMT_TAG_EXTENSIBLE)
1732 RTErrInfoSetF(pErrInfo, rc, "Unsupported uFormatTag value: %#x (expected %#x or %#x)",
1733 uBuf.Wave.u.Fmt.Data.uFormatTag, RTRIFFWAVEFMT_TAG_PCM, RTRIFFWAVEFMT_TAG_EXTENSIBLE);
1734 else if ( uBuf.Wave.u.Fmt.Data.cBitsPerSample != 8
1735 && uBuf.Wave.u.Fmt.Data.cBitsPerSample != 16
1736 /* && uBuf.Wave.u.Fmt.Data.cBitsPerSample != 24 - not supported by our stack */
1737 && uBuf.Wave.u.Fmt.Data.cBitsPerSample != 32)
1738 RTErrInfoSetF(pErrInfo, rc, "Unsupported cBitsPerSample value: %u", uBuf.Wave.u.Fmt.Data.cBitsPerSample);
1739 else if ( uBuf.Wave.u.Fmt.Data.cChannels < 1
1740 || uBuf.Wave.u.Fmt.Data.cChannels >= 16)
1741 RTErrInfoSetF(pErrInfo, rc, "Unsupported cChannels value: %u (expected 1..15)", uBuf.Wave.u.Fmt.Data.cChannels);
1742 else if ( uBuf.Wave.u.Fmt.Data.uHz < 4096
1743 || uBuf.Wave.u.Fmt.Data.uHz > 768000)
1744 RTErrInfoSetF(pErrInfo, rc, "Unsupported uHz value: %u (expected 4096..768000)", uBuf.Wave.u.Fmt.Data.uHz);
1745 else if (uBuf.Wave.u.Fmt.Data.cbFrame != uBuf.Wave.u.Fmt.Data.cChannels * uBuf.Wave.u.Fmt.Data.cBitsPerSample / 8)
1746 RTErrInfoSetF(pErrInfo, rc, "Invalid cbFrame value: %u (expected %u)", uBuf.Wave.u.Fmt.Data.cbFrame,
1747 uBuf.Wave.u.Fmt.Data.cChannels * uBuf.Wave.u.Fmt.Data.cBitsPerSample / 8);
1748 else if (uBuf.Wave.u.Fmt.Data.cbRate != uBuf.Wave.u.Fmt.Data.cbFrame * uBuf.Wave.u.Fmt.Data.uHz)
1749 RTErrInfoSetF(pErrInfo, rc, "Invalid cbRate value: %u (expected %u)", uBuf.Wave.u.Fmt.Data.cbRate,
1750 uBuf.Wave.u.Fmt.Data.cbFrame * uBuf.Wave.u.Fmt.Data.uHz);
1751 else if ( uBuf.Wave.u.Fmt.Data.uFormatTag == RTRIFFWAVEFMT_TAG_EXTENSIBLE
1752 && uBuf.Wave.u.FmtExt.Data.cbExtra < RTRIFFWAVEFMTEXT_EXTRA_SIZE)
1753 RTErrInfoSetF(pErrInfo, rc, "Invalid cbExtra value: %#x (expected at least %#x)",
1754 uBuf.Wave.u.FmtExt.Data.cbExtra, RTRIFFWAVEFMTEXT_EXTRA_SIZE);
1755 else if ( uBuf.Wave.u.Fmt.Data.uFormatTag == RTRIFFWAVEFMT_TAG_EXTENSIBLE
1756 && audioTestWaveCountBits(uBuf.Wave.u.FmtExt.Data.fChannelMask) != uBuf.Wave.u.Fmt.Data.cChannels)
1757 RTErrInfoSetF(pErrInfo, rc, "fChannelMask does not match cChannels: %#x (%u bits set) vs %u channels",
1758 uBuf.Wave.u.FmtExt.Data.fChannelMask,
1759 audioTestWaveCountBits(uBuf.Wave.u.FmtExt.Data.fChannelMask), uBuf.Wave.u.Fmt.Data.cChannels);
1760 else if ( uBuf.Wave.u.Fmt.Data.uFormatTag == RTRIFFWAVEFMT_TAG_EXTENSIBLE
1761 && RTUuidCompareStr(&uBuf.Wave.u.FmtExt.Data.SubFormat, RTRIFFWAVEFMTEXT_SUBTYPE_PCM) != 0)
1762 RTErrInfoSetF(pErrInfo, rc, "SubFormat is not PCM: %RTuuid (expected %s)",
1763 &uBuf.Wave.u.FmtExt.Data.SubFormat, RTRIFFWAVEFMTEXT_SUBTYPE_PCM);
1764 else
1765 {
1766 /*
1767 * Copy out the data we need from the file format structure.
1768 */
1769 PDMAudioPropsInit(&pWaveFile->Props, uBuf.Wave.u.Fmt.Data.cBitsPerSample / 8, true /*fSigned*/,
1770 uBuf.Wave.u.Fmt.Data.cChannels, uBuf.Wave.u.Fmt.Data.uHz);
1771 pWaveFile->offSamples = sizeof(RTRIFFHDR) + sizeof(RTRIFFCHUNK) + uBuf.Wave.u.Fmt.Chunk.cbChunk;
1772
1773 /*
1774 * Pick up channel assignments if present.
1775 */
1776 if (uBuf.Wave.u.Fmt.Data.uFormatTag == RTRIFFWAVEFMT_TAG_EXTENSIBLE)
1777 {
1778 static unsigned const s_cStdIds = (unsigned)PDMAUDIOCHANNELID_END_STANDARD
1779 - (unsigned)PDMAUDIOCHANNELID_FIRST_STANDARD;
1780 unsigned iCh = 0;
1781 for (unsigned idCh = 0; idCh < 32 && iCh < uBuf.Wave.u.Fmt.Data.cChannels; idCh++)
1782 if (uBuf.Wave.u.FmtExt.Data.fChannelMask & RT_BIT_32(idCh))
1783 {
1784 pWaveFile->Props.aidChannels[iCh] = idCh < s_cStdIds
1785 ? idCh + (unsigned)PDMAUDIOCHANNELID_FIRST_STANDARD
1786 : (unsigned)PDMAUDIOCHANNELID_UNKNOWN;
1787 iCh++;
1788 }
1789 }
1790
1791 /*
1792 * Find the 'data' chunk with the audio samples.
1793 *
1794 * There can be INFO lists both preceeding this and succeeding
1795 * it, containing IART and other things we can ignored. Thus
1796 * we read a list header here rather than just a chunk header,
1797 * since it doesn't matter if we read 4 bytes extra as
1798 * AudioTestWaveFileRead uses RTFileReadAt anyway.
1799 */
1800 rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
1801 for (uint32_t i = 0;
1802 i < 128
1803 && RT_SUCCESS(rc)
1804 && uBuf.Chunk.uMagic != RTRIFFWAVEDATACHUNK_MAGIC
1805 && (uint64_t)uBuf.Chunk.cbChunk + sizeof(RTRIFFCHUNK) * 2 <= cbFile - pWaveFile->offSamples;
1806 i++)
1807 {
1808 if ( uBuf.List.uMagic == RTRIFFLIST_MAGIC
1809 && uBuf.List.uListType == RTRIFFLIST_TYPE_INFO)
1810 { /*skip*/ }
1811 else if (uBuf.Chunk.uMagic == RTRIFFPADCHUNK_MAGIC)
1812 { /*skip*/ }
1813 else
1814 break;
1815 pWaveFile->offSamples += sizeof(RTRIFFCHUNK) + uBuf.Chunk.cbChunk;
1816 rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
1817 }
1818 if (RT_SUCCESS(rc))
1819 {
1820 pWaveFile->offSamples += sizeof(uBuf.Data.Chunk);
1821 pWaveFile->cbSamples = (uint32_t)cbFile - pWaveFile->offSamples;
1822
1823 rc = VERR_VFS_BOGUS_FORMAT;
1824 if ( uBuf.Data.Chunk.uMagic == RTRIFFWAVEDATACHUNK_MAGIC
1825 && uBuf.Data.Chunk.cbChunk <= pWaveFile->cbSamples
1826 && PDMAudioPropsIsSizeAligned(&pWaveFile->Props, uBuf.Data.Chunk.cbChunk))
1827 {
1828 pWaveFile->cbSamples = uBuf.Data.Chunk.cbChunk;
1829
1830 /*
1831 * We're good!
1832 */
1833 pWaveFile->offCur = 0;
1834 pWaveFile->fReadMode = true;
1835 pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC;
1836 return VINF_SUCCESS;
1837 }
1838
1839 RTErrInfoSetF(pErrInfo, rc, "Bad data header: uMagic=%#x (expected %#x), cbChunk=%#x (max %#RX64, align %u)",
1840 uBuf.Data.Chunk.uMagic, RTRIFFWAVEDATACHUNK_MAGIC,
1841 uBuf.Data.Chunk.cbChunk, pWaveFile->cbSamples, PDMAudioPropsFrameSize(&pWaveFile->Props));
1842 }
1843 else
1844 RTErrInfoSet(pErrInfo, rc, "Failed to read data header");
1845 }
1846 }
1847 else
1848 RTErrInfoSetF(pErrInfo, rc, "Bad file header: uMagic=%#x (vs. %#x), uFileType=%#x (vs %#x), uFmtMagic=%#x (vs %#x) cbFmtChunk=%#x (min %#x)",
1849 uBuf.Wave.Hdr.uMagic, RTRIFFHDR_MAGIC, uBuf.Wave.Hdr.uFileType, RTRIFF_FILE_TYPE_WAVE,
1850 uBuf.Wave.u.Fmt.Chunk.uMagic, RTRIFFWAVEFMT_MAGIC,
1851 uBuf.Wave.u.Fmt.Chunk.cbChunk, sizeof(uBuf.Wave.u.Fmt.Data));
1852 }
1853 else
1854 rc = RTErrInfoSet(pErrInfo, rc, "Failed to read file header");
1855 }
1856 else
1857 rc = RTErrInfoSet(pErrInfo, rc, "Failed to query file size");
1858
1859 RTFileClose(pWaveFile->hFile);
1860 pWaveFile->hFile = NIL_RTFILE;
1861 return rc;
1862}
1863
1864
1865/**
1866 * Creates a new wave file.
1867 *
1868 * @returns VBox status code.
1869 * @param pszFile The filename.
1870 * @param pProps The audio format properties.
1871 * @param pWaveFile The wave file structure to fill in on success.
1872 * @param pErrInfo Where to return addition error details on failure.
1873 */
1874int AudioTestWaveFileCreate(const char *pszFile, PCPDMAUDIOPCMPROPS pProps, PAUDIOTESTWAVEFILE pWaveFile, PRTERRINFO pErrInfo)
1875{
1876 /*
1877 * Construct the file header first (we'll do some input validation
1878 * here, so better do it before creating the file).
1879 */
1880 struct
1881 {
1882 RTRIFFHDR Hdr;
1883 RTRIFFWAVEFMTEXTCHUNK FmtExt;
1884 RTRIFFCHUNK Data;
1885 } FileHdr;
1886
1887 FileHdr.Hdr.uMagic = RTRIFFHDR_MAGIC;
1888 FileHdr.Hdr.cbFile = 0; /* need to update this later */
1889 FileHdr.Hdr.uFileType = RTRIFF_FILE_TYPE_WAVE;
1890 FileHdr.FmtExt.Chunk.uMagic = RTRIFFWAVEFMT_MAGIC;
1891 FileHdr.FmtExt.Chunk.cbChunk = sizeof(RTRIFFWAVEFMTEXTCHUNK) - sizeof(RTRIFFCHUNK);
1892 FileHdr.FmtExt.Data.Core.uFormatTag = RTRIFFWAVEFMT_TAG_EXTENSIBLE;
1893 FileHdr.FmtExt.Data.Core.cChannels = PDMAudioPropsChannels(pProps);
1894 FileHdr.FmtExt.Data.Core.uHz = PDMAudioPropsHz(pProps);
1895 FileHdr.FmtExt.Data.Core.cbRate = PDMAudioPropsFramesToBytes(pProps, PDMAudioPropsHz(pProps));
1896 FileHdr.FmtExt.Data.Core.cbFrame = PDMAudioPropsFrameSize(pProps);
1897 FileHdr.FmtExt.Data.Core.cBitsPerSample = PDMAudioPropsSampleBits(pProps);
1898 FileHdr.FmtExt.Data.cbExtra = sizeof(FileHdr.FmtExt.Data) - sizeof(FileHdr.FmtExt.Data.Core);
1899 FileHdr.FmtExt.Data.cValidBitsPerSample = PDMAudioPropsSampleBits(pProps);
1900 FileHdr.FmtExt.Data.fChannelMask = 0;
1901 for (uintptr_t idxCh = 0; idxCh < FileHdr.FmtExt.Data.Core.cChannels; idxCh++)
1902 {
1903 PDMAUDIOCHANNELID const idCh = (PDMAUDIOCHANNELID)pProps->aidChannels[idxCh];
1904 if ( idCh >= PDMAUDIOCHANNELID_FIRST_STANDARD
1905 && idCh < PDMAUDIOCHANNELID_END_STANDARD)
1906 {
1907 if (!(FileHdr.FmtExt.Data.fChannelMask & RT_BIT_32(idCh - PDMAUDIOCHANNELID_FIRST_STANDARD)))
1908 FileHdr.FmtExt.Data.fChannelMask |= RT_BIT_32(idCh - PDMAUDIOCHANNELID_FIRST_STANDARD);
1909 else
1910 return RTErrInfoSetF(pErrInfo, VERR_INVALID_PARAMETER, "Channel #%u repeats channel ID %d", idxCh, idCh);
1911 }
1912 else
1913 return RTErrInfoSetF(pErrInfo, VERR_INVALID_PARAMETER, "Invalid channel ID %d for channel #%u", idCh, idxCh);
1914 }
1915
1916 RTUUID UuidTmp;
1917 int rc = RTUuidFromStr(&UuidTmp, RTRIFFWAVEFMTEXT_SUBTYPE_PCM);
1918 AssertRCReturn(rc, rc);
1919 FileHdr.FmtExt.Data.SubFormat = UuidTmp; /* (64-bit field maybe unaligned) */
1920
1921 FileHdr.Data.uMagic = RTRIFFWAVEDATACHUNK_MAGIC;
1922 FileHdr.Data.cbChunk = 0; /* need to update this later */
1923
1924 /*
1925 * Create the file and write the header.
1926 */
1927 pWaveFile->hFile = NIL_RTFILE;
1928 rc = RTFileOpen(&pWaveFile->hFile, pszFile, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
1929 if (RT_FAILURE(rc))
1930 return RTErrInfoSet(pErrInfo, rc, "RTFileOpen failed");
1931
1932 rc = RTFileWrite(pWaveFile->hFile, &FileHdr, sizeof(FileHdr), NULL);
1933 if (RT_SUCCESS(rc))
1934 {
1935 /*
1936 * Initialize the wave file structure.
1937 */
1938 pWaveFile->fReadMode = false;
1939 pWaveFile->offCur = 0;
1940 pWaveFile->offSamples = 0;
1941 pWaveFile->cbSamples = 0;
1942 pWaveFile->Props = *pProps;
1943 pWaveFile->offSamples = RTFileTell(pWaveFile->hFile);
1944 if (pWaveFile->offSamples != UINT32_MAX)
1945 {
1946 pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC;
1947 return VINF_SUCCESS;
1948 }
1949 rc = RTErrInfoSet(pErrInfo, VERR_SEEK, "RTFileTell failed");
1950 }
1951 else
1952 RTErrInfoSet(pErrInfo, rc, "RTFileWrite failed writing header");
1953
1954 RTFileClose(pWaveFile->hFile);
1955 pWaveFile->hFile = NIL_RTFILE;
1956 pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC_DEAD;
1957
1958 RTFileDelete(pszFile);
1959 return rc;
1960}
1961
1962
1963/**
1964 * Closes a wave file.
1965 */
1966int AudioTestWaveFileClose(PAUDIOTESTWAVEFILE pWaveFile)
1967{
1968 AssertReturn(pWaveFile->u32Magic == AUDIOTESTWAVEFILE_MAGIC, VERR_INVALID_MAGIC);
1969 int rcRet = VINF_SUCCESS;
1970 int rc;
1971
1972 /*
1973 * Update the size fields if writing.
1974 */
1975 if (!pWaveFile->fReadMode)
1976 {
1977 uint64_t cbFile = RTFileTell(pWaveFile->hFile);
1978 if (cbFile != UINT64_MAX)
1979 {
1980 uint32_t cbFile32 = cbFile - sizeof(RTRIFFCHUNK);
1981 rc = RTFileWriteAt(pWaveFile->hFile, RT_OFFSETOF(RTRIFFHDR, cbFile), &cbFile32, sizeof(cbFile32), NULL);
1982 AssertRCStmt(rc, rcRet = rc);
1983
1984 uint32_t cbSamples = cbFile - pWaveFile->offSamples;
1985 rc = RTFileWriteAt(pWaveFile->hFile, pWaveFile->offSamples - sizeof(uint32_t), &cbSamples, sizeof(cbSamples), NULL);
1986 AssertRCStmt(rc, rcRet = rc);
1987 }
1988 else
1989 rcRet = VERR_SEEK;
1990 }
1991
1992 /*
1993 * Close it.
1994 */
1995 rc = RTFileClose(pWaveFile->hFile);
1996 AssertRCStmt(rc, rcRet = rc);
1997
1998 pWaveFile->hFile = NIL_RTFILE;
1999 pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC_DEAD;
2000 return rcRet;
2001}
2002
2003/**
2004 * Reads samples from a wave file.
2005 *
2006 * @returns VBox status code. See RTVfsFileRead for EOF status handling.
2007 * @param pWaveFile The file to read from.
2008 * @param pvBuf Where to put the samples.
2009 * @param cbBuf How much to read at most.
2010 * @param pcbRead Where to return the actual number of bytes read,
2011 * optional.
2012 */
2013int AudioTestWaveFileRead(PAUDIOTESTWAVEFILE pWaveFile, void *pvBuf, size_t cbBuf, size_t *pcbRead)
2014{
2015 AssertReturn(pWaveFile->u32Magic == AUDIOTESTWAVEFILE_MAGIC, VERR_INVALID_MAGIC);
2016 AssertReturn(pWaveFile->fReadMode, VERR_ACCESS_DENIED);
2017
2018 bool fEofAdjusted;
2019 if (pWaveFile->offCur + cbBuf <= pWaveFile->cbSamples)
2020 fEofAdjusted = false;
2021 else if (pcbRead)
2022 {
2023 fEofAdjusted = true;
2024 cbBuf = pWaveFile->cbSamples - pWaveFile->offCur;
2025 }
2026 else
2027 return VERR_EOF;
2028
2029 int rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples + pWaveFile->offCur, pvBuf, cbBuf, pcbRead);
2030 if (RT_SUCCESS(rc))
2031 {
2032 if (pcbRead)
2033 {
2034 pWaveFile->offCur += (uint32_t)*pcbRead;
2035 if (fEofAdjusted || cbBuf > *pcbRead)
2036 rc = VINF_EOF;
2037 else if (!cbBuf && pWaveFile->offCur == pWaveFile->cbSamples)
2038 rc = VINF_EOF;
2039 }
2040 else
2041 pWaveFile->offCur += (uint32_t)cbBuf;
2042 }
2043 return rc;
2044}
2045
2046
2047/**
2048 * Writes samples to a wave file.
2049 *
2050 * @returns VBox status code.
2051 * @param pWaveFile The file to write to.
2052 * @param pvBuf The samples to write.
2053 * @param cbBuf How many bytes of samples to write.
2054 */
2055int AudioTestWaveFileWrite(PAUDIOTESTWAVEFILE pWaveFile, const void *pvBuf, size_t cbBuf)
2056{
2057 AssertReturn(pWaveFile->u32Magic == AUDIOTESTWAVEFILE_MAGIC, VERR_INVALID_MAGIC);
2058 AssertReturn(!pWaveFile->fReadMode, VERR_ACCESS_DENIED);
2059
2060 pWaveFile->cbSamples += (uint32_t)cbBuf;
2061 return RTFileWrite(pWaveFile->hFile, pvBuf, cbBuf, NULL);
2062}
2063
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