VirtualBox

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

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

Audio/ValKit: More code for completely self-contained (self) testing. bugref:10008

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 64.5 KB
Line 
1/* $Id: AudioTest.cpp 89541 2021-06-07 09:26:07Z 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
71/*********************************************************************************************************************************
72* Global Variables *
73*********************************************************************************************************************************/
74/** Well-known frequency selection test tones. */
75static const double s_aAudioTestToneFreqsHz[] =
76{
77 349.2282 /*F4*/,
78 440.0000 /*A4*/,
79 523.2511 /*C5*/,
80 698.4565 /*F5*/,
81 880.0000 /*A5*/,
82 1046.502 /*C6*/,
83 1174.659 /*D6*/,
84 1396.913 /*F6*/,
85 1760.0000 /*A6*/
86};
87
88/**
89 * Returns a random test tone frequency.
90 */
91DECLINLINE(double) audioTestToneGetRandomFreq(void)
92{
93 return s_aAudioTestToneFreqsHz[RTRandU32Ex(0, RT_ELEMENTS(s_aAudioTestToneFreqsHz) - 1)];
94}
95
96/**
97 * Initializes a test tone with a specific frequency (in Hz).
98 *
99 * @returns Used tone frequency (in Hz).
100 * @param pTone Pointer to test tone to initialize.
101 * @param pProps PCM properties to use for the test tone.
102 * @param dbFreq Frequency (in Hz) to initialize tone with.
103 * When set to 0.0, a random frequency will be chosen.
104 */
105double AudioTestToneInit(PAUDIOTESTTONE pTone, PPDMAUDIOPCMPROPS pProps, double dbFreq)
106{
107 if (dbFreq == 0.0)
108 dbFreq = audioTestToneGetRandomFreq();
109
110 pTone->rdFreqHz = dbFreq;
111 pTone->rdFixed = 2.0 * M_PI * pTone->rdFreqHz / PDMAudioPropsHz(pProps);
112 pTone->uSample = 0;
113
114 memcpy(&pTone->Props, pProps, sizeof(PDMAUDIOPCMPROPS));
115
116 pTone->enmType = AUDIOTESTTONETYPE_SINE; /* Only type implemented so far. */
117
118 return dbFreq;
119}
120
121/**
122 * Initializes a test tone by picking a random but well-known frequency (in Hz).
123 *
124 * @returns Randomly picked tone frequency (in Hz).
125 * @param pTone Pointer to test tone to initialize.
126 * @param pProps PCM properties to use for the test tone.
127 */
128double AudioTestToneInitRandom(PAUDIOTESTTONE pTone, PPDMAUDIOPCMPROPS pProps)
129{
130 return AudioTestToneInit(pTone, pProps,
131 /* Pick a frequency from our selection, so that every time a recording starts
132 * we'll hopfully generate a different note. */
133 0.0);
134}
135
136/**
137 * Writes (and iterates) a given test tone to an output buffer.
138 *
139 * @returns VBox status code.
140 * @param pTone Pointer to test tone to write.
141 * @param pvBuf Pointer to output buffer to write test tone to.
142 * @param cbBuf Size (in bytes) of output buffer.
143 * @param pcbWritten How many bytes were written on success.
144 */
145int AudioTestToneGenerate(PAUDIOTESTTONE pTone, void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
146{
147 /*
148 * Clear the buffer first so we don't need to think about additional channels.
149 */
150 uint32_t cFrames = PDMAudioPropsBytesToFrames(&pTone->Props, cbBuf);
151
152 /* Input cbBuf not necessarily is aligned to the frames, so re-calculate it. */
153 const uint32_t cbToWrite = PDMAudioPropsFramesToBytes(&pTone->Props, cFrames);
154
155 PDMAudioPropsClearBuffer(&pTone->Props, pvBuf, cbBuf, cFrames);
156
157 /*
158 * Generate the select sin wave in the first channel:
159 */
160 uint32_t const cbFrame = PDMAudioPropsFrameSize(&pTone->Props);
161 double const rdFixed = pTone->rdFixed;
162 uint64_t iSrcFrame = pTone->uSample;
163 switch (PDMAudioPropsSampleSize(&pTone->Props))
164 {
165 case 1:
166 /* untested */
167 if (PDMAudioPropsIsSigned(&pTone->Props))
168 {
169 int8_t *piSample = (int8_t *)pvBuf;
170 while (cFrames-- > 0)
171 {
172 *piSample = (int8_t)(126 /*Amplitude*/ * sin(rdFixed * iSrcFrame));
173 iSrcFrame++;
174 piSample += cbFrame;
175 }
176 }
177 else
178 {
179 /* untested */
180 uint8_t *pbSample = (uint8_t *)pvBuf;
181 while (cFrames-- > 0)
182 {
183 *pbSample = (uint8_t)(126 /*Amplitude*/ * sin(rdFixed * iSrcFrame) + 0x80);
184 iSrcFrame++;
185 pbSample += cbFrame;
186 }
187 }
188 break;
189
190 case 2:
191 if (PDMAudioPropsIsSigned(&pTone->Props))
192 {
193 int16_t *piSample = (int16_t *)pvBuf;
194 while (cFrames-- > 0)
195 {
196 *piSample = (int16_t)(32760 /*Amplitude*/ * sin(rdFixed * iSrcFrame));
197 iSrcFrame++;
198 piSample = (int16_t *)((uint8_t *)piSample + cbFrame);
199 }
200 }
201 else
202 {
203 /* untested */
204 uint16_t *puSample = (uint16_t *)pvBuf;
205 while (cFrames-- > 0)
206 {
207 *puSample = (uint16_t)(32760 /*Amplitude*/ * sin(rdFixed * iSrcFrame) + 0x8000);
208 iSrcFrame++;
209 puSample = (uint16_t *)((uint8_t *)puSample + cbFrame);
210 }
211 }
212 break;
213
214 case 4:
215 /* untested */
216 if (PDMAudioPropsIsSigned(&pTone->Props))
217 {
218 int32_t *piSample = (int32_t *)pvBuf;
219 while (cFrames-- > 0)
220 {
221 *piSample = (int32_t)((32760 << 16) /*Amplitude*/ * sin(rdFixed * iSrcFrame));
222 iSrcFrame++;
223 piSample = (int32_t *)((uint8_t *)piSample + cbFrame);
224 }
225 }
226 else
227 {
228 uint32_t *puSample = (uint32_t *)pvBuf;
229 while (cFrames-- > 0)
230 {
231 *puSample = (uint32_t)((32760 << 16) /*Amplitude*/ * sin(rdFixed * iSrcFrame) + UINT32_C(0x80000000));
232 iSrcFrame++;
233 puSample = (uint32_t *)((uint8_t *)puSample + cbFrame);
234 }
235 }
236 break;
237
238 default:
239 AssertFailedReturn(VERR_NOT_SUPPORTED);
240 }
241
242 pTone->uSample = iSrcFrame;
243
244 if (pcbWritten)
245 *pcbWritten = cbToWrite;
246
247 return VINF_SUCCESS;
248}
249
250/**
251 * Initializes an audio test tone parameters struct with random values.
252 * @param pToneParams Test tone parameters to initialize.
253 * @param pProps PCM properties to use for the test tone.
254 */
255int AudioTestToneParamsInitRandom(PAUDIOTESTTONEPARMS pToneParams, PPDMAUDIOPCMPROPS pProps)
256{
257 AssertReturn(PDMAudioPropsAreValid(pProps), VERR_INVALID_PARAMETER);
258
259 memcpy(&pToneParams->Props, pProps, sizeof(PDMAUDIOPCMPROPS));
260
261 /** @todo Make this a bit more sophisticated later, e.g. muting and prequel/sequel are not very balanced. */
262
263 pToneParams->dbFreqHz = audioTestToneGetRandomFreq();
264 pToneParams->msPrequel = RTRandU32Ex(0, RT_MS_5SEC);
265#ifdef DEBUG_andy
266 pToneParams->msDuration = RTRandU32Ex(0, RT_MS_1SEC);
267#else
268 pToneParams->msDuration = RTRandU32Ex(0, RT_MS_10SEC); /** @todo Probably a bit too long, but let's see. */
269#endif
270 pToneParams->msSequel = RTRandU32Ex(0, RT_MS_5SEC);
271 pToneParams->uVolumePercent = RTRandU32Ex(0, 100);
272
273 return VINF_SUCCESS;
274}
275
276/**
277 * Generates a tag.
278 *
279 * @returns VBox status code.
280 * @param pszTag The output buffer.
281 * @param cbTag The size of the output buffer.
282 * AUDIOTEST_TAG_MAX is a good size.
283 */
284int AudioTestGenTag(char *pszTag, size_t cbTag)
285{
286 RTUUID UUID;
287 int rc = RTUuidCreate(&UUID);
288 AssertRCReturn(rc, rc);
289 rc = RTUuidToStr(&UUID, pszTag, cbTag);
290 AssertRCReturn(rc, rc);
291 return rc;
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 */
303static int 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 AssertPtrReturn(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 * Retrieves the temporary directory.
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 */
600int AudioTestPathGetTemp(char *pszPath, size_t cbPath)
601{
602 int rc = RTEnvGetEx(RTENV_DEFAULT, "TESTBOX_PATH_SCRATCH", pszPath, cbPath, NULL);
603 if (RT_FAILURE(rc))
604 {
605 rc = RTPathTemp(pszPath, cbPath);
606 AssertRCReturn(rc, rc);
607 }
608
609 return rc;
610}
611
612/**
613 * Creates a new temporary directory with a specific (test) tag.
614 *
615 * @returns VBox status code.
616 * @param pszPath Where to return the absolute path of the created directory on success.
617 * @param cbPath Size (in bytes) of \a pszPath.
618 * @param pszTag Tag name to use for directory creation.
619 *
620 * @note Can be used multiple times with the same tag; a sub directory with an ISO time string will be used
621 * on each call.
622 */
623int AudioTestPathCreateTemp(char *pszPath, size_t cbPath, const char *pszTag)
624{
625 AssertReturn(pszTag && strlen(pszTag) <= AUDIOTEST_TAG_MAX, VERR_INVALID_PARAMETER);
626
627 char szTemp[RTPATH_MAX];
628 int rc = AudioTestPathGetTemp(szTemp, sizeof(szTemp));
629 AssertRCReturn(rc, rc);
630
631 rc = AudioTestPathCreate(szTemp, sizeof(szTemp), pszTag);
632 AssertRCReturn(rc, rc);
633
634 return RTStrCopy(pszPath, cbPath, szTemp);
635}
636
637/**
638 * Returns the absolute path of a given audio test set object.
639 *
640 * @returns VBox status code.
641 * @param pSet Test set the object contains.
642 * @param pszPathAbs Where to return the absolute path on success.
643 * @param cbPathAbs Size (in bytes) of \a pszPathAbs.
644 * @param pszObjName Name of the object to create absolute path for.
645 */
646DECLINLINE(int) audioTestSetGetObjPath(PAUDIOTESTSET pSet, char *pszPathAbs, size_t cbPathAbs, const char *pszObjName)
647{
648 return RTPathJoin(pszPathAbs, cbPathAbs, pSet->szPathAbs, pszObjName);
649}
650
651/**
652 * Returns the tag of a test set.
653 *
654 * @returns Test set tag.
655 * @param pSet Test set to return tag for.
656 */
657const char *AudioTestSetGetTag(PAUDIOTESTSET pSet)
658{
659 return pSet->szTag;
660}
661
662/**
663 * Creates a new audio test set.
664 *
665 * @returns VBox status code.
666 * @param pSet Test set to create.
667 * @param pszPath Where to store the set set data. If NULL, the
668 * temporary directory will be used.
669 * @param pszTag Tag name to use for this test set.
670 */
671int AudioTestSetCreate(PAUDIOTESTSET pSet, const char *pszPath, const char *pszTag)
672{
673 audioTestSetInitInternal(pSet);
674
675 int rc = audioTestCopyOrGenTag(pSet->szTag, sizeof(pSet->szTag), pszTag);
676 AssertRCReturn(rc, rc);
677
678 /*
679 * Test set directory.
680 */
681 if (pszPath)
682 {
683 rc = RTPathAbs(pszPath, pSet->szPathAbs, sizeof(pSet->szPathAbs));
684 AssertRCReturn(rc, rc);
685
686 rc = AudioTestPathCreate(pSet->szPathAbs, sizeof(pSet->szPathAbs), pSet->szTag);
687 }
688 else
689 rc = AudioTestPathCreateTemp(pSet->szPathAbs, sizeof(pSet->szPathAbs), pSet->szTag);
690 AssertRCReturn(rc, rc);
691
692 /*
693 * Create the manifest file.
694 */
695 char szTmp[RTPATH_MAX];
696 rc = RTPathJoin(szTmp, sizeof(szTmp), pSet->szPathAbs, AUDIOTEST_MANIFEST_FILE_STR);
697 AssertRCReturn(rc, rc);
698
699 rc = RTFileOpen(&pSet->f.hFile, szTmp, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
700 AssertRCReturn(rc, rc);
701
702 rc = audioTestManifestWriteSectionHdr(pSet, "header");
703 AssertRCReturn(rc, rc);
704
705 rc = audioTestManifestWrite(pSet, "magic=vkat_ini\n"); /* VKAT Manifest, .INI-style. */
706 AssertRCReturn(rc, rc);
707 rc = audioTestManifestWrite(pSet, "ver=%d\n", AUDIOTEST_MANIFEST_VER);
708 AssertRCReturn(rc, rc);
709 rc = audioTestManifestWrite(pSet, "tag=%s\n", pSet->szTag);
710 AssertRCReturn(rc, rc);
711
712 AssertCompile(sizeof(szTmp) > RTTIME_STR_LEN);
713 RTTIMESPEC Now;
714 rc = audioTestManifestWrite(pSet, "date_created=%s\n", RTTimeSpecToString(RTTimeNow(&Now), szTmp, sizeof(szTmp)));
715 AssertRCReturn(rc, rc);
716
717 RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp)); /* do NOT return on failure. */
718 rc = audioTestManifestWrite(pSet, "os_product=%s\n", szTmp);
719 AssertRCReturn(rc, rc);
720
721 RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp)); /* do NOT return on failure. */
722 rc = audioTestManifestWrite(pSet, "os_rel=%s\n", szTmp);
723 AssertRCReturn(rc, rc);
724
725 RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp)); /* do NOT return on failure. */
726 rc = audioTestManifestWrite(pSet, "os_ver=%s\n", szTmp);
727 AssertRCReturn(rc, rc);
728
729 rc = audioTestManifestWrite(pSet, "vbox_ver=%s r%u %s (%s %s)\n",
730 VBOX_VERSION_STRING, RTBldCfgRevision(), RTBldCfgTargetDotArch(), __DATE__, __TIME__);
731 AssertRCReturn(rc, rc);
732
733 rc = audioTestManifestWrite(pSet, "test_count=");
734 AssertRCReturn(rc, rc);
735 pSet->offTestCount = audioTestManifestGetOffsetAbs(pSet);
736 rc = audioTestManifestWrite(pSet, "0000\n"); /* A bit messy, but does the trick for now. */
737 AssertRCReturn(rc, rc);
738
739 rc = audioTestManifestWrite(pSet, "obj_count=");
740 AssertRCReturn(rc, rc);
741 pSet->offObjCount = audioTestManifestGetOffsetAbs(pSet);
742 rc = audioTestManifestWrite(pSet, "0000\n"); /* A bit messy, but does the trick for now. */
743 AssertRCReturn(rc, rc);
744
745 pSet->enmMode = AUDIOTESTSETMODE_TEST;
746
747 return rc;
748}
749
750/**
751 * Destroys a test set.
752 *
753 * @returns VBox status code.
754 * @param pSet Test set to destroy.
755 */
756int AudioTestSetDestroy(PAUDIOTESTSET pSet)
757{
758 if (!pSet)
759 return VINF_SUCCESS;
760
761 AssertReturn(pSet->cTestsRunning == 0, VERR_WRONG_ORDER); /* Make sure no tests sill are running. */
762
763 int rc = AudioTestSetClose(pSet);
764 if (RT_FAILURE(rc))
765 return rc;
766
767 PAUDIOTESTOBJ pObj, pObjNext;
768 RTListForEachSafe(&pSet->lstObj, pObj, pObjNext, AUDIOTESTOBJ, Node)
769 {
770 rc = AudioTestSetObjClose(pObj);
771 if (RT_SUCCESS(rc))
772 {
773 PAUDIOTESTOBJMETA pMeta, pMetaNext;
774 RTListForEachSafe(&pObj->lstMeta, pMeta, pMetaNext, AUDIOTESTOBJMETA, Node)
775 {
776 switch (pMeta->enmType)
777 {
778 case AUDIOTESTOBJMETADATATYPE_STRING:
779 {
780 RTStrFree((char *)pMeta->pvMeta);
781 break;
782 }
783
784 default:
785 AssertFailed();
786 break;
787 }
788
789 RTListNodeRemove(&pMeta->Node);
790 RTMemFree(pMeta);
791 }
792
793 RTListNodeRemove(&pObj->Node);
794 RTMemFree(pObj);
795
796 Assert(pSet->cObj);
797 pSet->cObj--;
798 }
799 else
800 break;
801 }
802
803 if (RT_FAILURE(rc))
804 return rc;
805
806 Assert(pSet->cObj == 0);
807
808 PAUDIOTESTENTRY pEntry, pEntryNext;
809 RTListForEachSafe(&pSet->lstTest, pEntry, pEntryNext, AUDIOTESTENTRY, Node)
810 {
811 RTListNodeRemove(&pEntry->Node);
812 RTMemFree(pEntry);
813
814 Assert(pSet->cTests);
815 pSet->cTests--;
816 }
817
818 if (RT_FAILURE(rc))
819 return rc;
820
821 Assert(pSet->cTests == 0);
822
823 return rc;
824}
825
826/**
827 * Opens an existing audio test set.
828 *
829 * @returns VBox status code.
830 * @param pSet Test set to open.
831 * @param pszPath Absolute path of the test set to open.
832 */
833int AudioTestSetOpen(PAUDIOTESTSET pSet, const char *pszPath)
834{
835 audioTestSetInitInternal(pSet);
836
837 char szManifest[RTPATH_MAX];
838 int rc = RTPathJoin(szManifest, sizeof(szManifest), pszPath, AUDIOTEST_MANIFEST_FILE_STR);
839 AssertRCReturn(rc, rc);
840
841 RTVFSFILE hVfsFile;
842 rc = RTVfsFileOpenNormal(szManifest, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hVfsFile);
843 if (RT_FAILURE(rc))
844 return rc;
845
846 rc = RTIniFileCreateFromVfsFile(&pSet->f.hIniFile, hVfsFile, RTINIFILE_F_READONLY);
847 RTVfsFileRelease(hVfsFile);
848 AssertRCReturn(rc, rc);
849
850 pSet->enmMode = AUDIOTESTSETMODE_VERIFY;
851
852 return rc;
853}
854
855/**
856 * Closes an opened audio test set.
857 *
858 * @returns VBox status code.
859 * @param pSet Test set to close.
860 */
861int AudioTestSetClose(PAUDIOTESTSET pSet)
862{
863 if (!pSet)
864 return VINF_SUCCESS;
865
866 if (!RTFileIsValid(pSet->f.hFile))
867 return VINF_SUCCESS;
868
869 int rc;
870
871 /* Update number of bound test objects. */
872 PAUDIOTESTENTRY pTest;
873 RTListForEach(&pSet->lstTest, pTest, AUDIOTESTENTRY, Node)
874 {
875 rc = RTFileSeek(pSet->f.hFile, pTest->offObjCount, RTFILE_SEEK_BEGIN, NULL);
876 AssertRCReturn(rc, rc);
877 rc = audioTestManifestWrite(pSet, "%04RU32", pTest->cObj);
878 AssertRCReturn(rc, rc);
879 }
880
881 /*
882 * Update number of ran tests.
883 */
884 rc = RTFileSeek(pSet->f.hFile, pSet->offObjCount, RTFILE_SEEK_BEGIN, NULL);
885 AssertRCReturn(rc, rc);
886 rc = audioTestManifestWrite(pSet, "%04RU32", pSet->cObj);
887 AssertRCReturn(rc, rc);
888
889 /*
890 * Update number of ran tests.
891 */
892 rc = RTFileSeek(pSet->f.hFile, pSet->offTestCount, RTFILE_SEEK_BEGIN, NULL);
893 AssertRCReturn(rc, rc);
894 rc = audioTestManifestWrite(pSet, "%04RU32", pSet->cTests);
895 AssertRCReturn(rc, rc);
896
897 /*
898 * Serialize all registered test objects.
899 */
900 rc = RTFileSeek(pSet->f.hFile, 0, RTFILE_SEEK_END, NULL);
901 AssertRCReturn(rc, rc);
902
903 PAUDIOTESTOBJ pObj;
904 RTListForEach(&pSet->lstObj, pObj, AUDIOTESTOBJ, Node)
905 {
906 rc = audioTestManifestWrite(pSet, "\n");
907 AssertRCReturn(rc, rc);
908 char szUuid[64];
909 rc = RTUuidToStr(&pObj->Uuid, szUuid, sizeof(szUuid));
910 AssertRCReturn(rc, rc);
911 rc = audioTestManifestWriteSectionHdr(pSet, "obj_%s", szUuid);
912 AssertRCReturn(rc, rc);
913 rc = audioTestManifestWrite(pSet, "obj_type=%RU32\n", pObj->enmType);
914 AssertRCReturn(rc, rc);
915 rc = audioTestManifestWrite(pSet, "obj_name=%s\n", pObj->szName);
916 AssertRCReturn(rc, rc);
917
918 switch (pObj->enmType)
919 {
920 case AUDIOTESTOBJTYPE_FILE:
921 {
922 rc = audioTestManifestWrite(pSet, "obj_size=%RU64\n", pObj->File.cbSize);
923 AssertRCReturn(rc, rc);
924 break;
925 }
926
927 default:
928 AssertFailed();
929 break;
930 }
931
932 /*
933 * Write all meta data.
934 */
935 PAUDIOTESTOBJMETA pMeta;
936 RTListForEach(&pObj->lstMeta, pMeta, AUDIOTESTOBJMETA, Node)
937 {
938 switch (pMeta->enmType)
939 {
940 case AUDIOTESTOBJMETADATATYPE_STRING:
941 {
942 rc = audioTestManifestWrite(pSet, (const char *)pMeta->pvMeta);
943 AssertRCReturn(rc, rc);
944 break;
945 }
946
947 default:
948 AssertFailed();
949 break;
950 }
951 }
952 }
953
954 RTFileClose(pSet->f.hFile);
955 pSet->f.hFile = NIL_RTFILE;
956
957 return rc;
958}
959
960/**
961 * Physically wipes all related test set files off the disk.
962 *
963 * @returns VBox status code.
964 * @param pSet Test set to wipe.
965 */
966int AudioTestSetWipe(PAUDIOTESTSET pSet)
967{
968 AssertPtrReturn(pSet, VERR_INVALID_POINTER);
969
970 int rc = VINF_SUCCESS;
971 char szFilePath[RTPATH_MAX];
972
973 PAUDIOTESTOBJ pObj;
974 RTListForEach(&pSet->lstObj, pObj, AUDIOTESTOBJ, Node)
975 {
976 int rc2 = AudioTestSetObjClose(pObj);
977 if (RT_SUCCESS(rc2))
978 {
979 rc2 = audioTestSetGetObjPath(pSet, szFilePath, sizeof(szFilePath), pObj->szName);
980 if (RT_SUCCESS(rc2))
981 rc2 = RTFileDelete(szFilePath);
982 }
983
984 if (RT_SUCCESS(rc))
985 rc = rc2;
986 /* Keep going. */
987 }
988
989 if (RT_SUCCESS(rc))
990 {
991 rc = RTPathJoin(szFilePath, sizeof(szFilePath), pSet->szPathAbs, AUDIOTEST_MANIFEST_FILE_STR);
992 if (RT_SUCCESS(rc))
993 rc = RTFileDelete(szFilePath);
994 }
995
996 /* Remove the (hopefully now empty) directory. Otherwise let this fail. */
997 if (RT_SUCCESS(rc))
998 rc = RTDirRemove(pSet->szPathAbs);
999
1000 return rc;
1001}
1002
1003/**
1004 * Creates and registers a new audio test object to the current running test.
1005 *
1006 * @returns VBox status code.
1007 * @param pSet Test set to create and register new object for.
1008 * @param pszName Name of new object to create.
1009 * @param ppObj Where to return the pointer to the newly created object on success.
1010 */
1011int AudioTestSetObjCreateAndRegister(PAUDIOTESTSET pSet, const char *pszName, PAUDIOTESTOBJ *ppObj)
1012{
1013 AssertReturn(pSet->cTestsRunning == 1, VERR_WRONG_ORDER); /* No test nesting allowed. */
1014
1015 AssertPtrReturn(pszName, VERR_INVALID_POINTER);
1016
1017 PAUDIOTESTOBJ pObj = (PAUDIOTESTOBJ)RTMemAlloc(sizeof(AUDIOTESTOBJ));
1018 AssertPtrReturn(pObj, VERR_NO_MEMORY);
1019
1020 RTListInit(&pObj->lstMeta);
1021
1022 if (RTStrPrintf2(pObj->szName, sizeof(pObj->szName), "%04RU32-%s", pSet->cObj, pszName) <= 0)
1023 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
1024
1025 /** @todo Generalize this function more once we have more object types. */
1026
1027 char szObjPathAbs[RTPATH_MAX];
1028 int rc = audioTestSetGetObjPath(pSet, szObjPathAbs, sizeof(szObjPathAbs), pObj->szName);
1029 if (RT_SUCCESS(rc))
1030 {
1031 rc = RTFileOpen(&pObj->File.hFile, szObjPathAbs, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
1032 if (RT_SUCCESS(rc))
1033 {
1034 pObj->enmType = AUDIOTESTOBJTYPE_FILE;
1035 pObj->cRefs = 1; /* Currently only 1:1 mapping. */
1036
1037 RTListAppend(&pSet->lstObj, &pObj->Node);
1038 pSet->cObj++;
1039
1040 /* Generate + set an UUID for the object and assign it to the current test. */
1041 rc = RTUuidCreate(&pObj->Uuid);
1042 AssertRCReturn(rc, rc);
1043 char szUuid[64];
1044 rc = RTUuidToStr(&pObj->Uuid, szUuid, sizeof(szUuid));
1045 AssertRCReturn(rc, rc);
1046
1047 rc = audioTestManifestWrite(pSet, "obj%RU32_uuid=%s\n", pSet->pTestCur->cObj, szUuid);
1048 AssertRCReturn(rc, rc);
1049
1050 AssertPtr(pSet->pTestCur);
1051 pSet->pTestCur->cObj++;
1052
1053 *ppObj = pObj;
1054 }
1055 }
1056
1057 if (RT_FAILURE(rc))
1058 RTMemFree(pObj);
1059
1060 return rc;
1061}
1062
1063/**
1064 * Writes to a created audio test object.
1065 *
1066 * @returns VBox status code.
1067 * @param pObj Audio test object to write to.
1068 * @param pvBuf Pointer to data to write.
1069 * @param cbBuf Size (in bytes) of \a pvBuf to write.
1070 */
1071int AudioTestSetObjWrite(PAUDIOTESTOBJ pObj, const void *pvBuf, size_t cbBuf)
1072{
1073 /** @todo Generalize this function more once we have more object types. */
1074 AssertReturn(pObj->enmType == AUDIOTESTOBJTYPE_FILE, VERR_INVALID_PARAMETER);
1075
1076 return RTFileWrite(pObj->File.hFile, pvBuf, cbBuf, NULL);
1077}
1078
1079/**
1080 * Adds meta data to a test object as a string, va_list version.
1081 *
1082 * @returns VBox status code.
1083 * @param pObj Test object to add meta data for.
1084 * @param pszFormat Format string to add.
1085 * @param va Variable arguments list to use for the format string.
1086 */
1087static int audioTestSetObjAddMetadataStrV(PAUDIOTESTOBJ pObj, const char *pszFormat, va_list va)
1088{
1089 PAUDIOTESTOBJMETA pMeta = (PAUDIOTESTOBJMETA)RTMemAlloc(sizeof(AUDIOTESTOBJMETA));
1090 AssertPtrReturn(pMeta, VERR_NO_MEMORY);
1091
1092 pMeta->pvMeta = RTStrAPrintf2V(pszFormat, va);
1093 AssertPtrReturn(pMeta->pvMeta, VERR_BUFFER_OVERFLOW);
1094 pMeta->cbMeta = RTStrNLen((const char *)pMeta->pvMeta, RTSTR_MAX);
1095
1096 pMeta->enmType = AUDIOTESTOBJMETADATATYPE_STRING;
1097
1098 RTListAppend(&pObj->lstMeta, &pMeta->Node);
1099
1100 return VINF_SUCCESS;
1101}
1102
1103/**
1104 * Adds meta data to a test object as a string.
1105 *
1106 * @returns VBox status code.
1107 * @param pObj Test object to add meta data for.
1108 * @param pszFormat Format string to add.
1109 * @param ... Variable arguments for the format string.
1110 */
1111int AudioTestSetObjAddMetadataStr(PAUDIOTESTOBJ pObj, const char *pszFormat, ...)
1112{
1113 va_list va;
1114
1115 va_start(va, pszFormat);
1116 int rc = audioTestSetObjAddMetadataStrV(pObj, pszFormat, va);
1117 va_end(va);
1118
1119 return rc;
1120}
1121
1122/**
1123 * Closes an opened audio test object.
1124 *
1125 * @returns VBox status code.
1126 * @param pObj Audio test object to close.
1127 */
1128int AudioTestSetObjClose(PAUDIOTESTOBJ pObj)
1129{
1130 if (!pObj)
1131 return VINF_SUCCESS;
1132
1133 /** @todo Generalize this function more once we have more object types. */
1134 AssertReturn(pObj->enmType == AUDIOTESTOBJTYPE_FILE, VERR_INVALID_PARAMETER);
1135
1136 int rc = VINF_SUCCESS;
1137
1138 if (RTFileIsValid(pObj->File.hFile))
1139 {
1140 pObj->File.cbSize = RTFileTell(pObj->File.hFile);
1141
1142 rc = RTFileClose(pObj->File.hFile);
1143 pObj->File.hFile = NIL_RTFILE;
1144 }
1145
1146 return rc;
1147}
1148
1149/**
1150 * Begins a new test of a test set.
1151 *
1152 * @returns VBox status code.
1153 * @param pSet Test set to begin new test for.
1154 * @param pszDesc Test description.
1155 * @param pParms Test parameters to use.
1156 * @param ppEntry Where to return the new test handle.
1157 */
1158int AudioTestSetTestBegin(PAUDIOTESTSET pSet, const char *pszDesc, PAUDIOTESTPARMS pParms, PAUDIOTESTENTRY *ppEntry)
1159{
1160 AssertReturn(pSet->cTestsRunning == 0, VERR_WRONG_ORDER); /* No test nesting allowed. */
1161
1162 PAUDIOTESTENTRY pEntry = (PAUDIOTESTENTRY)RTMemAllocZ(sizeof(AUDIOTESTENTRY));
1163 AssertPtrReturn(pEntry, VERR_NO_MEMORY);
1164
1165 int rc = RTStrCopy(pEntry->szDesc, sizeof(pEntry->szDesc), pszDesc);
1166 AssertRCReturn(rc, rc);
1167
1168 memcpy(&pEntry->Parms, pParms, sizeof(AUDIOTESTPARMS));
1169 pEntry->pParent = pSet;
1170
1171 rc = audioTestManifestWrite(pSet, "\n");
1172 AssertRCReturn(rc, rc);
1173
1174 rc = audioTestManifestWriteSectionHdr(pSet, "test_%04RU32", pSet->cTests);
1175 AssertRCReturn(rc, rc);
1176 rc = audioTestManifestWrite(pSet, "test_desc=%s\n", pszDesc);
1177 AssertRCReturn(rc, rc);
1178 rc = audioTestManifestWrite(pSet, "test_type=%RU32\n", pParms->enmType);
1179 AssertRCReturn(rc, rc);
1180 rc = audioTestManifestWrite(pSet, "test_delay_ms=%RU32\n", pParms->msDelay);
1181 AssertRCReturn(rc, rc);
1182 rc = audioTestManifestWrite(pSet, "audio_direction=%s\n", PDMAudioDirGetName(pParms->enmDir));
1183 AssertRCReturn(rc, rc);
1184
1185 rc = audioTestManifestWrite(pSet, "obj_count=");
1186 AssertRCReturn(rc, rc);
1187 pEntry->offObjCount = audioTestManifestGetOffsetAbs(pSet);
1188 rc = audioTestManifestWrite(pSet, "0000\n"); /* A bit messy, but does the trick for now. */
1189 AssertRCReturn(rc, rc);
1190
1191 switch (pParms->enmType)
1192 {
1193 case AUDIOTESTTYPE_TESTTONE_PLAY:
1194 RT_FALL_THROUGH();
1195 case AUDIOTESTTYPE_TESTTONE_RECORD:
1196 {
1197 rc = audioTestManifestWrite(pSet, "tone_freq_hz=%RU16\n", (uint16_t)pParms->TestTone.dbFreqHz);
1198 AssertRCReturn(rc, rc);
1199 rc = audioTestManifestWrite(pSet, "tone_prequel_ms=%RU32\n", pParms->TestTone.msPrequel);
1200 AssertRCReturn(rc, rc);
1201 rc = audioTestManifestWrite(pSet, "tone_duration_ms=%RU32\n", pParms->TestTone.msDuration);
1202 AssertRCReturn(rc, rc);
1203 rc = audioTestManifestWrite(pSet, "tone_sequel_ms=%RU32\n", pParms->TestTone.msSequel);
1204 AssertRCReturn(rc, rc);
1205 rc = audioTestManifestWrite(pSet, "tone_volume_percent=%RU32\n", pParms->TestTone.uVolumePercent);
1206 AssertRCReturn(rc, rc);
1207 rc = audioTestManifestWrite(pSet, "tone_pcm_hz=%RU32\n", PDMAudioPropsHz(&pParms->TestTone.Props));
1208 AssertRCReturn(rc, rc);
1209 rc = audioTestManifestWrite(pSet, "tone_pcm_channels=%RU8\n", PDMAudioPropsChannels(&pParms->TestTone.Props));
1210 AssertRCReturn(rc, rc);
1211 rc = audioTestManifestWrite(pSet, "tone_pcm_bits=%RU8\n", PDMAudioPropsSampleBits(&pParms->TestTone.Props));
1212 AssertRCReturn(rc, rc);
1213 rc = audioTestManifestWrite(pSet, "tone_pcm_is_signed=%RTbool\n", PDMAudioPropsIsSigned(&pParms->TestTone.Props));
1214 AssertRCReturn(rc, rc);
1215 break;
1216 }
1217
1218 default:
1219 AssertFailed();
1220 break;
1221 }
1222
1223 RTListAppend(&pSet->lstTest, &pEntry->Node);
1224 pSet->cTests++;
1225 pSet->cTestsRunning++;
1226 pSet->pTestCur = pEntry;
1227
1228 *ppEntry = pEntry;
1229
1230 return rc;
1231}
1232
1233/**
1234 * Marks a running test as failed.
1235 *
1236 * @returns VBox status code.
1237 * @param pEntry Test to mark.
1238 * @param rc Error code.
1239 * @param pszErr Error description.
1240 */
1241int AudioTestSetTestFailed(PAUDIOTESTENTRY pEntry, int rc, const char *pszErr)
1242{
1243 AssertReturn(pEntry->pParent->cTestsRunning == 1, VERR_WRONG_ORDER); /* No test nesting allowed. */
1244 AssertReturn(pEntry->rc == VINF_SUCCESS, VERR_WRONG_ORDER);
1245
1246 pEntry->rc = rc;
1247
1248 int rc2 = audioTestManifestWrite(pEntry->pParent, "error_rc=%RI32\n", rc);
1249 AssertRCReturn(rc2, rc2);
1250 rc2 = audioTestManifestWrite(pEntry->pParent, "error_desc=%s\n", pszErr);
1251 AssertRCReturn(rc2, rc2);
1252
1253 pEntry->pParent->cTestsRunning--;
1254 pEntry->pParent->pTestCur = NULL;
1255
1256 return rc2;
1257}
1258
1259/**
1260 * Marks a running test as successfully done.
1261 *
1262 * @returns VBox status code.
1263 * @param pEntry Test to mark.
1264 */
1265int AudioTestSetTestDone(PAUDIOTESTENTRY pEntry)
1266{
1267 AssertReturn(pEntry->pParent->cTestsRunning == 1, VERR_WRONG_ORDER); /* No test nesting allowed. */
1268 AssertReturn(pEntry->rc == VINF_SUCCESS, VERR_WRONG_ORDER);
1269
1270 int rc2 = audioTestManifestWrite(pEntry->pParent, "error_rc=%RI32\n", VINF_SUCCESS);
1271 AssertRCReturn(rc2, rc2);
1272
1273 pEntry->pParent->cTestsRunning--;
1274 pEntry->pParent->pTestCur = NULL;
1275
1276 return rc2;
1277}
1278
1279/**
1280 * Packs a closed audio test so that it's ready for transmission.
1281 *
1282 * @returns VBox status code.
1283 * @param pSet Test set to pack.
1284 * @param pszOutDir Directory where to store the packed test set.
1285 * @param pszFileName Where to return the final name of the packed test set. Optional and can be NULL.
1286 * @param cbFileName Size (in bytes) of \a pszFileName.
1287 */
1288int AudioTestSetPack(PAUDIOTESTSET pSet, const char *pszOutDir, char *pszFileName, size_t cbFileName)
1289{
1290 AssertReturn(!pszFileName || cbFileName, VERR_INVALID_PARAMETER);
1291 AssertReturn(!audioTestManifestIsOpen(pSet), VERR_WRONG_ORDER);
1292
1293 /** @todo Check and deny if \a pszOutDir is part of the set's path. */
1294
1295 int rc = RTDirCreateFullPath(pszOutDir, 0755);
1296 if (RT_FAILURE(rc))
1297 return rc;
1298
1299 char szOutName[RT_ELEMENTS(AUDIOTEST_PATH_PREFIX_STR) + AUDIOTEST_TAG_MAX + 16];
1300 if (RTStrPrintf2(szOutName, sizeof(szOutName), "%s-%s%s",
1301 AUDIOTEST_PATH_PREFIX_STR, pSet->szTag, AUDIOTEST_ARCHIVE_SUFF_STR) <= 0)
1302 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
1303
1304 char szOutPath[RTPATH_MAX];
1305 rc = RTPathJoin(szOutPath, sizeof(szOutPath), pszOutDir, szOutName);
1306 AssertRCReturn(rc, rc);
1307
1308 const char *apszArgs[10];
1309 unsigned cArgs = 0;
1310
1311 apszArgs[cArgs++] = "vkat";
1312 apszArgs[cArgs++] = "--create";
1313 apszArgs[cArgs++] = "--gzip";
1314 apszArgs[cArgs++] = "--directory";
1315 apszArgs[cArgs++] = pSet->szPathAbs;
1316 apszArgs[cArgs++] = "--file";
1317 apszArgs[cArgs++] = szOutPath;
1318 apszArgs[cArgs++] = ".";
1319
1320 RTEXITCODE rcExit = RTZipTarCmd(cArgs, (char **)apszArgs);
1321 if (rcExit != RTEXITCODE_SUCCESS)
1322 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1323
1324 if (RT_SUCCESS(rc))
1325 {
1326 if (pszFileName)
1327 rc = RTStrCopy(pszFileName, cbFileName, szOutPath);
1328 }
1329
1330 return rc;
1331}
1332
1333/**
1334 * Returns whether a test set archive is packed (as .tar.gz by default) or
1335 * a plain directory.
1336 *
1337 * @returns \c true if packed (as .tar.gz), or \c false if not (directory).
1338 * @param pszPath Path to return packed staus for.
1339 */
1340bool AudioTestSetIsPacked(const char *pszPath)
1341{
1342 /** @todo Improve this, good enough for now. */
1343 return (RTStrIStr(pszPath, AUDIOTEST_ARCHIVE_SUFF_STR) != NULL);
1344}
1345
1346/**
1347 * Unpacks a formerly packed audio test set.
1348 *
1349 * @returns VBox status code.
1350 * @param pszFile Test set file to unpack. Must contain the absolute path.
1351 * @param pszOutDir Directory where to unpack the test set into.
1352 * If the directory does not exist it will be created.
1353 */
1354int AudioTestSetUnpack(const char *pszFile, const char *pszOutDir)
1355{
1356 AssertReturn(pszFile && pszOutDir, VERR_INVALID_PARAMETER);
1357
1358 int rc = VINF_SUCCESS;
1359
1360 if (!RTDirExists(pszOutDir))
1361 {
1362 rc = RTDirCreateFullPath(pszOutDir, 0755);
1363 if (RT_FAILURE(rc))
1364 return rc;
1365 }
1366
1367 const char *apszArgs[8];
1368 unsigned cArgs = 0;
1369
1370 apszArgs[cArgs++] = "vkat";
1371 apszArgs[cArgs++] = "--extract";
1372 apszArgs[cArgs++] = "--gunzip";
1373 apszArgs[cArgs++] = "--directory";
1374 apszArgs[cArgs++] = pszOutDir;
1375 apszArgs[cArgs++] = "--file";
1376 apszArgs[cArgs++] = pszFile;
1377
1378 RTEXITCODE rcExit = RTZipTarCmd(cArgs, (char **)apszArgs);
1379 if (rcExit != RTEXITCODE_SUCCESS)
1380 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1381
1382 return rc;
1383}
1384
1385static int audioTestVerifyIniValue(PAUDIOTESTSET pSetA, PAUDIOTESTSET pSetB,
1386 const char *pszSec, const char *pszKey, const char *pszVal)
1387{
1388 char szValA[_1K];
1389 int rc = RTIniFileQueryValue(pSetA->f.hIniFile, pszSec, pszKey, szValA, sizeof(szValA), NULL);
1390 if (RT_FAILURE(rc))
1391 return rc;
1392 char szValB[_1K];
1393 rc = RTIniFileQueryValue(pSetB->f.hIniFile, pszSec, pszKey, szValB, sizeof(szValB), NULL);
1394 if (RT_FAILURE(rc))
1395 return rc;
1396
1397 if (RTStrCmp(szValA, szValB))
1398 return VERR_WRONG_TYPE; /** @todo Fudge! */
1399
1400 if (pszVal)
1401 {
1402 if (RTStrCmp(szValA, pszVal))
1403 return VERR_WRONG_TYPE; /** @todo Fudge! */
1404 }
1405
1406 return rc;
1407}
1408
1409/**
1410 * Verifies an opened audio test set.
1411 *
1412 * @returns VBox status code.
1413 * @param pSetA Test set A to verify.
1414 * @param pSetB Test set to verify test set A with.
1415 * @param pErrDesc Where to return the test verification errors.
1416 *
1417 * @note Test verification errors have to be checked for errors, regardless of the
1418 * actual return code.
1419 */
1420int AudioTestSetVerify(PAUDIOTESTSET pSetA, PAUDIOTESTSET pSetB, PAUDIOTESTERRORDESC pErrDesc)
1421{
1422 AssertReturn(audioTestManifestIsOpen(pSetA), VERR_WRONG_ORDER);
1423 AssertReturn(audioTestManifestIsOpen(pSetB), VERR_WRONG_ORDER);
1424
1425 /* We ASSUME the caller has not init'd pErrDesc. */
1426 audioTestErrorDescInit(pErrDesc);
1427
1428 int rc;
1429
1430#define VERIFY_VALUE(a_Sec, a_Key, a_Val, ...) \
1431 rc = audioTestVerifyIniValue(pSetA, pSetB, a_Sec, a_Key, a_Val); \
1432 if (RT_FAILURE(rc)) \
1433 return audioTestErrorDescAdd(pErrDesc, (__VA_ARGS__));
1434
1435 /*
1436 * Compare obvious values first.
1437 */
1438 VERIFY_VALUE("header", "magic", "vkat_ini", "Manifest magic wrong");
1439 VERIFY_VALUE("header", "ver", "1" , "Manifest version wrong");
1440 VERIFY_VALUE("header", "tag", NULL, "Manifest tags don't match");
1441 VERIFY_VALUE("header", "test_count", NULL, "Test counts don't match");
1442 VERIFY_VALUE("header", "obj_count", NULL, "Object counts don't match");
1443
1444#undef VERIFY_VALUE
1445
1446 /* Only return critical stuff not related to actual testing here. */
1447 return VINF_SUCCESS;
1448}
1449
1450
1451/*********************************************************************************************************************************
1452* WAVE File Reader. *
1453*********************************************************************************************************************************/
1454
1455/**
1456 * Counts the number of set bits in @a fMask.
1457 */
1458static unsigned audioTestWaveCountBits(uint32_t fMask)
1459{
1460 unsigned cBits = 0;
1461 while (fMask)
1462 {
1463 if (fMask & 1)
1464 cBits++;
1465 fMask >>= 1;
1466 }
1467 return cBits;
1468}
1469
1470/**
1471 * Opens a wave (.WAV) file for reading.
1472 *
1473 * @returns VBox status code.
1474 * @param pszFile The file to open.
1475 * @param pWaveFile The open wave file structure to fill in on success.
1476 * @param pErrInfo Where to return addition error details on failure.
1477 */
1478int AudioTestWaveFileOpen(const char *pszFile, PAUDIOTESTWAVEFILE pWaveFile, PRTERRINFO pErrInfo)
1479{
1480 pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC_DEAD;
1481 RT_ZERO(pWaveFile->Props);
1482 pWaveFile->hFile = NIL_RTFILE;
1483 int rc = RTFileOpen(&pWaveFile->hFile, pszFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
1484 if (RT_FAILURE(rc))
1485 return RTErrInfoSet(pErrInfo, rc, "RTFileOpen failed");
1486 uint64_t cbFile = 0;
1487 rc = RTFileQuerySize(pWaveFile->hFile, &cbFile);
1488 if (RT_SUCCESS(rc))
1489 {
1490 union
1491 {
1492 uint8_t ab[512];
1493 struct
1494 {
1495 RTRIFFHDR Hdr;
1496 union
1497 {
1498 RTRIFFWAVEFMTCHUNK Fmt;
1499 RTRIFFWAVEFMTEXTCHUNK FmtExt;
1500 } u;
1501 } Wave;
1502 RTRIFFLIST List;
1503 RTRIFFCHUNK Chunk;
1504 RTRIFFWAVEDATACHUNK Data;
1505 } uBuf;
1506
1507 rc = RTFileRead(pWaveFile->hFile, &uBuf.Wave, sizeof(uBuf.Wave), NULL);
1508 if (RT_SUCCESS(rc))
1509 {
1510 rc = VERR_VFS_UNKNOWN_FORMAT;
1511 if ( uBuf.Wave.Hdr.uMagic == RTRIFFHDR_MAGIC
1512 && uBuf.Wave.Hdr.uFileType == RTRIFF_FILE_TYPE_WAVE
1513 && uBuf.Wave.u.Fmt.Chunk.uMagic == RTRIFFWAVEFMT_MAGIC
1514 && uBuf.Wave.u.Fmt.Chunk.cbChunk >= sizeof(uBuf.Wave.u.Fmt.Data))
1515 {
1516 if (uBuf.Wave.Hdr.cbFile != cbFile - sizeof(RTRIFFCHUNK))
1517 RTErrInfoSetF(pErrInfo, rc, "File size mismatch: %#x, actual %#RX64 (ignored)",
1518 uBuf.Wave.Hdr.cbFile, cbFile - sizeof(RTRIFFCHUNK));
1519 rc = VERR_VFS_BOGUS_FORMAT;
1520 if ( uBuf.Wave.u.Fmt.Data.uFormatTag != RTRIFFWAVEFMT_TAG_PCM
1521 && uBuf.Wave.u.Fmt.Data.uFormatTag != RTRIFFWAVEFMT_TAG_EXTENSIBLE)
1522 RTErrInfoSetF(pErrInfo, rc, "Unsupported uFormatTag value: %#x (expected %#x or %#x)",
1523 uBuf.Wave.u.Fmt.Data.uFormatTag, RTRIFFWAVEFMT_TAG_PCM, RTRIFFWAVEFMT_TAG_EXTENSIBLE);
1524 else if ( uBuf.Wave.u.Fmt.Data.cBitsPerSample != 8
1525 && uBuf.Wave.u.Fmt.Data.cBitsPerSample != 16
1526 /* && uBuf.Wave.u.Fmt.Data.cBitsPerSample != 24 - not supported by our stack */
1527 && uBuf.Wave.u.Fmt.Data.cBitsPerSample != 32)
1528 RTErrInfoSetF(pErrInfo, rc, "Unsupported cBitsPerSample value: %u", uBuf.Wave.u.Fmt.Data.cBitsPerSample);
1529 else if ( uBuf.Wave.u.Fmt.Data.cChannels < 1
1530 || uBuf.Wave.u.Fmt.Data.cChannels >= 16)
1531 RTErrInfoSetF(pErrInfo, rc, "Unsupported cChannels value: %u (expected 1..15)", uBuf.Wave.u.Fmt.Data.cChannels);
1532 else if ( uBuf.Wave.u.Fmt.Data.uHz < 4096
1533 || uBuf.Wave.u.Fmt.Data.uHz > 768000)
1534 RTErrInfoSetF(pErrInfo, rc, "Unsupported uHz value: %u (expected 4096..768000)", uBuf.Wave.u.Fmt.Data.uHz);
1535 else if (uBuf.Wave.u.Fmt.Data.cbFrame != uBuf.Wave.u.Fmt.Data.cChannels * uBuf.Wave.u.Fmt.Data.cBitsPerSample / 8)
1536 RTErrInfoSetF(pErrInfo, rc, "Invalid cbFrame value: %u (expected %u)", uBuf.Wave.u.Fmt.Data.cbFrame,
1537 uBuf.Wave.u.Fmt.Data.cChannels * uBuf.Wave.u.Fmt.Data.cBitsPerSample / 8);
1538 else if (uBuf.Wave.u.Fmt.Data.cbRate != uBuf.Wave.u.Fmt.Data.cbFrame * uBuf.Wave.u.Fmt.Data.uHz)
1539 RTErrInfoSetF(pErrInfo, rc, "Invalid cbRate value: %u (expected %u)", uBuf.Wave.u.Fmt.Data.cbRate,
1540 uBuf.Wave.u.Fmt.Data.cbFrame * uBuf.Wave.u.Fmt.Data.uHz);
1541 else if ( uBuf.Wave.u.Fmt.Data.uFormatTag == RTRIFFWAVEFMT_TAG_EXTENSIBLE
1542 && uBuf.Wave.u.FmtExt.Data.cbExtra < RTRIFFWAVEFMTEXT_EXTRA_SIZE)
1543 RTErrInfoSetF(pErrInfo, rc, "Invalid cbExtra value: %#x (expected at least %#x)",
1544 uBuf.Wave.u.FmtExt.Data.cbExtra, RTRIFFWAVEFMTEXT_EXTRA_SIZE);
1545 else if ( uBuf.Wave.u.Fmt.Data.uFormatTag == RTRIFFWAVEFMT_TAG_EXTENSIBLE
1546 && audioTestWaveCountBits(uBuf.Wave.u.FmtExt.Data.fChannelMask) != uBuf.Wave.u.Fmt.Data.cChannels)
1547 RTErrInfoSetF(pErrInfo, rc, "fChannelMask does not match cChannels: %#x (%u bits set) vs %u channels",
1548 uBuf.Wave.u.FmtExt.Data.fChannelMask,
1549 audioTestWaveCountBits(uBuf.Wave.u.FmtExt.Data.fChannelMask), uBuf.Wave.u.Fmt.Data.cChannels);
1550 else if ( uBuf.Wave.u.Fmt.Data.uFormatTag == RTRIFFWAVEFMT_TAG_EXTENSIBLE
1551 && RTUuidCompareStr(&uBuf.Wave.u.FmtExt.Data.SubFormat, RTRIFFWAVEFMTEXT_SUBTYPE_PCM) != 0)
1552 RTErrInfoSetF(pErrInfo, rc, "SubFormat is not PCM: %RTuuid (expected %s)",
1553 &uBuf.Wave.u.FmtExt.Data.SubFormat, RTRIFFWAVEFMTEXT_SUBTYPE_PCM);
1554 else
1555 {
1556 /*
1557 * Copy out the data we need from the file format structure.
1558 */
1559 PDMAudioPropsInit(&pWaveFile->Props, uBuf.Wave.u.Fmt.Data.cBitsPerSample / 8, true /*fSigned*/,
1560 uBuf.Wave.u.Fmt.Data.cChannels, uBuf.Wave.u.Fmt.Data.uHz);
1561 pWaveFile->offSamples = sizeof(RTRIFFHDR) + sizeof(RTRIFFCHUNK) + uBuf.Wave.u.Fmt.Chunk.cbChunk;
1562
1563 /*
1564 * Pick up channel assignments if present.
1565 */
1566 if (uBuf.Wave.u.Fmt.Data.uFormatTag == RTRIFFWAVEFMT_TAG_EXTENSIBLE)
1567 {
1568 static unsigned const s_cStdIds = (unsigned)PDMAUDIOCHANNELID_END_STANDARD
1569 - (unsigned)PDMAUDIOCHANNELID_FIRST_STANDARD;
1570 unsigned iCh = 0;
1571 for (unsigned idCh = 0; idCh < 32 && iCh < uBuf.Wave.u.Fmt.Data.cChannels; idCh++)
1572 if (uBuf.Wave.u.FmtExt.Data.fChannelMask & RT_BIT_32(idCh))
1573 {
1574 pWaveFile->Props.aidChannels[iCh] = idCh < s_cStdIds
1575 ? idCh + (unsigned)PDMAUDIOCHANNELID_FIRST_STANDARD
1576 : (unsigned)PDMAUDIOCHANNELID_UNKNOWN;
1577 iCh++;
1578 }
1579 }
1580
1581 /*
1582 * Find the 'data' chunk with the audio samples.
1583 *
1584 * There can be INFO lists both preceeding this and succeeding
1585 * it, containing IART and other things we can ignored. Thus
1586 * we read a list header here rather than just a chunk header,
1587 * since it doesn't matter if we read 4 bytes extra as
1588 * AudioTestWaveFileRead uses RTFileReadAt anyway.
1589 */
1590 rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
1591 for (uint32_t i = 0;
1592 i < 128
1593 && RT_SUCCESS(rc)
1594 && uBuf.Chunk.uMagic != RTRIFFWAVEDATACHUNK_MAGIC
1595 && (uint64_t)uBuf.Chunk.cbChunk + sizeof(RTRIFFCHUNK) * 2 <= cbFile - pWaveFile->offSamples;
1596 i++)
1597 {
1598 if ( uBuf.List.uMagic == RTRIFFLIST_MAGIC
1599 && uBuf.List.uListType == RTRIFFLIST_TYPE_INFO)
1600 { /*skip*/ }
1601 else if (uBuf.Chunk.uMagic == RTRIFFPADCHUNK_MAGIC)
1602 { /*skip*/ }
1603 else
1604 break;
1605 pWaveFile->offSamples += sizeof(RTRIFFCHUNK) + uBuf.Chunk.cbChunk;
1606 rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
1607 }
1608 if (RT_SUCCESS(rc))
1609 {
1610 pWaveFile->offSamples += sizeof(uBuf.Data.Chunk);
1611 pWaveFile->cbSamples = (uint32_t)cbFile - pWaveFile->offSamples;
1612
1613 rc = VERR_VFS_BOGUS_FORMAT;
1614 if ( uBuf.Data.Chunk.uMagic == RTRIFFWAVEDATACHUNK_MAGIC
1615 && uBuf.Data.Chunk.cbChunk <= pWaveFile->cbSamples
1616 && PDMAudioPropsIsSizeAligned(&pWaveFile->Props, uBuf.Data.Chunk.cbChunk))
1617 {
1618 pWaveFile->cbSamples = uBuf.Data.Chunk.cbChunk;
1619
1620 /*
1621 * We're good!
1622 */
1623 pWaveFile->offCur = 0;
1624 pWaveFile->fReadMode = true;
1625 pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC;
1626 return VINF_SUCCESS;
1627 }
1628
1629 RTErrInfoSetF(pErrInfo, rc, "Bad data header: uMagic=%#x (expected %#x), cbChunk=%#x (max %#RX64, align %u)",
1630 uBuf.Data.Chunk.uMagic, RTRIFFWAVEDATACHUNK_MAGIC,
1631 uBuf.Data.Chunk.cbChunk, pWaveFile->cbSamples, PDMAudioPropsFrameSize(&pWaveFile->Props));
1632 }
1633 else
1634 RTErrInfoSet(pErrInfo, rc, "Failed to read data header");
1635 }
1636 }
1637 else
1638 RTErrInfoSetF(pErrInfo, rc, "Bad file header: uMagic=%#x (vs. %#x), uFileType=%#x (vs %#x), uFmtMagic=%#x (vs %#x) cbFmtChunk=%#x (min %#x)",
1639 uBuf.Wave.Hdr.uMagic, RTRIFFHDR_MAGIC, uBuf.Wave.Hdr.uFileType, RTRIFF_FILE_TYPE_WAVE,
1640 uBuf.Wave.u.Fmt.Chunk.uMagic, RTRIFFWAVEFMT_MAGIC,
1641 uBuf.Wave.u.Fmt.Chunk.cbChunk, sizeof(uBuf.Wave.u.Fmt.Data));
1642 }
1643 else
1644 rc = RTErrInfoSet(pErrInfo, rc, "Failed to read file header");
1645 }
1646 else
1647 rc = RTErrInfoSet(pErrInfo, rc, "Failed to query file size");
1648
1649 RTFileClose(pWaveFile->hFile);
1650 pWaveFile->hFile = NIL_RTFILE;
1651 return rc;
1652}
1653
1654
1655/**
1656 * Creates a new wave file.
1657 *
1658 * @returns VBox status code.
1659 * @param pszFile The filename.
1660 * @param pProps The audio format properties.
1661 * @param pWaveFile The wave file structure to fill in on success.
1662 * @param pErrInfo Where to return addition error details on failure.
1663 */
1664int AudioTestWaveFileCreate(const char *pszFile, PCPDMAUDIOPCMPROPS pProps, PAUDIOTESTWAVEFILE pWaveFile, PRTERRINFO pErrInfo)
1665{
1666 /*
1667 * Construct the file header first (we'll do some input validation
1668 * here, so better do it before creating the file).
1669 */
1670 struct
1671 {
1672 RTRIFFHDR Hdr;
1673 RTRIFFWAVEFMTEXTCHUNK FmtExt;
1674 RTRIFFCHUNK Data;
1675 } FileHdr;
1676
1677 FileHdr.Hdr.uMagic = RTRIFFHDR_MAGIC;
1678 FileHdr.Hdr.cbFile = 0; /* need to update this later */
1679 FileHdr.Hdr.uFileType = RTRIFF_FILE_TYPE_WAVE;
1680 FileHdr.FmtExt.Chunk.uMagic = RTRIFFWAVEFMT_MAGIC;
1681 FileHdr.FmtExt.Chunk.cbChunk = sizeof(RTRIFFWAVEFMTEXTCHUNK) - sizeof(RTRIFFCHUNK);
1682 FileHdr.FmtExt.Data.Core.uFormatTag = RTRIFFWAVEFMT_TAG_EXTENSIBLE;
1683 FileHdr.FmtExt.Data.Core.cChannels = PDMAudioPropsChannels(pProps);
1684 FileHdr.FmtExt.Data.Core.uHz = PDMAudioPropsHz(pProps);
1685 FileHdr.FmtExt.Data.Core.cbRate = PDMAudioPropsFramesToBytes(pProps, PDMAudioPropsHz(pProps));
1686 FileHdr.FmtExt.Data.Core.cbFrame = PDMAudioPropsFrameSize(pProps);
1687 FileHdr.FmtExt.Data.Core.cBitsPerSample = PDMAudioPropsSampleBits(pProps);
1688 FileHdr.FmtExt.Data.cbExtra = sizeof(FileHdr.FmtExt.Data) - sizeof(FileHdr.FmtExt.Data.Core);
1689 FileHdr.FmtExt.Data.cValidBitsPerSample = PDMAudioPropsSampleBits(pProps);
1690 FileHdr.FmtExt.Data.fChannelMask = 0;
1691 for (uintptr_t idxCh = 0; idxCh < FileHdr.FmtExt.Data.Core.cChannels; idxCh++)
1692 {
1693 PDMAUDIOCHANNELID const idCh = (PDMAUDIOCHANNELID)pProps->aidChannels[idxCh];
1694 if ( idCh >= PDMAUDIOCHANNELID_FIRST_STANDARD
1695 && idCh < PDMAUDIOCHANNELID_END_STANDARD)
1696 {
1697 if (!(FileHdr.FmtExt.Data.fChannelMask & RT_BIT_32(idCh - PDMAUDIOCHANNELID_FIRST_STANDARD)))
1698 FileHdr.FmtExt.Data.fChannelMask |= RT_BIT_32(idCh - PDMAUDIOCHANNELID_FIRST_STANDARD);
1699 else
1700 return RTErrInfoSetF(pErrInfo, VERR_INVALID_PARAMETER, "Channel #%u repeats channel ID %d", idxCh, idCh);
1701 }
1702 else
1703 return RTErrInfoSetF(pErrInfo, VERR_INVALID_PARAMETER, "Invalid channel ID %d for channel #%u", idCh, idxCh);
1704 }
1705
1706 RTUUID UuidTmp;
1707 int rc = RTUuidFromStr(&UuidTmp, RTRIFFWAVEFMTEXT_SUBTYPE_PCM);
1708 AssertRCReturn(rc, rc);
1709 FileHdr.FmtExt.Data.SubFormat = UuidTmp; /* (64-bit field maybe unaligned) */
1710
1711 FileHdr.Data.uMagic = RTRIFFWAVEDATACHUNK_MAGIC;
1712 FileHdr.Data.cbChunk = 0; /* need to update this later */
1713
1714 /*
1715 * Create the file and write the header.
1716 */
1717 pWaveFile->hFile = NIL_RTFILE;
1718 rc = RTFileOpen(&pWaveFile->hFile, pszFile, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
1719 if (RT_FAILURE(rc))
1720 return RTErrInfoSet(pErrInfo, rc, "RTFileOpen failed");
1721
1722 rc = RTFileWrite(pWaveFile->hFile, &FileHdr, sizeof(FileHdr), NULL);
1723 if (RT_SUCCESS(rc))
1724 {
1725 /*
1726 * Initialize the wave file structure.
1727 */
1728 pWaveFile->fReadMode = false;
1729 pWaveFile->offCur = 0;
1730 pWaveFile->offSamples = 0;
1731 pWaveFile->cbSamples = 0;
1732 pWaveFile->Props = *pProps;
1733 pWaveFile->offSamples = RTFileTell(pWaveFile->hFile);
1734 if (pWaveFile->offSamples != UINT32_MAX)
1735 {
1736 pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC;
1737 return VINF_SUCCESS;
1738 }
1739 rc = RTErrInfoSet(pErrInfo, VERR_SEEK, "RTFileTell failed");
1740 }
1741 else
1742 RTErrInfoSet(pErrInfo, rc, "RTFileWrite failed writing header");
1743
1744 RTFileClose(pWaveFile->hFile);
1745 pWaveFile->hFile = NIL_RTFILE;
1746 pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC_DEAD;
1747
1748 RTFileDelete(pszFile);
1749 return rc;
1750}
1751
1752
1753/**
1754 * Closes a wave file.
1755 */
1756int AudioTestWaveFileClose(PAUDIOTESTWAVEFILE pWaveFile)
1757{
1758 AssertReturn(pWaveFile->u32Magic == AUDIOTESTWAVEFILE_MAGIC, VERR_INVALID_MAGIC);
1759 int rcRet = VINF_SUCCESS;
1760 int rc;
1761
1762 /*
1763 * Update the size fields if writing.
1764 */
1765 if (!pWaveFile->fReadMode)
1766 {
1767 uint64_t cbFile = RTFileTell(pWaveFile->hFile);
1768 if (cbFile != UINT64_MAX)
1769 {
1770 uint32_t cbFile32 = cbFile - sizeof(RTRIFFCHUNK);
1771 rc = RTFileWriteAt(pWaveFile->hFile, RT_OFFSETOF(RTRIFFHDR, cbFile), &cbFile32, sizeof(cbFile32), NULL);
1772 AssertRCStmt(rc, rcRet = rc);
1773
1774 uint32_t cbSamples = cbFile - pWaveFile->offSamples;
1775 rc = RTFileWriteAt(pWaveFile->hFile, pWaveFile->offSamples - sizeof(uint32_t), &cbSamples, sizeof(cbSamples), NULL);
1776 AssertRCStmt(rc, rcRet = rc);
1777 }
1778 else
1779 rcRet = VERR_SEEK;
1780 }
1781
1782 /*
1783 * Close it.
1784 */
1785 rc = RTFileClose(pWaveFile->hFile);
1786 AssertRCStmt(rc, rcRet = rc);
1787
1788 pWaveFile->hFile = NIL_RTFILE;
1789 pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC_DEAD;
1790 return rcRet;
1791}
1792
1793/**
1794 * Reads samples from a wave file.
1795 *
1796 * @returns VBox status code. See RTVfsFileRead for EOF status handling.
1797 * @param pWaveFile The file to read from.
1798 * @param pvBuf Where to put the samples.
1799 * @param cbBuf How much to read at most.
1800 * @param pcbRead Where to return the actual number of bytes read,
1801 * optional.
1802 */
1803int AudioTestWaveFileRead(PAUDIOTESTWAVEFILE pWaveFile, void *pvBuf, size_t cbBuf, size_t *pcbRead)
1804{
1805 AssertReturn(pWaveFile->u32Magic == AUDIOTESTWAVEFILE_MAGIC, VERR_INVALID_MAGIC);
1806 AssertReturn(pWaveFile->fReadMode, VERR_ACCESS_DENIED);
1807
1808 bool fEofAdjusted;
1809 if (pWaveFile->offCur + cbBuf <= pWaveFile->cbSamples)
1810 fEofAdjusted = false;
1811 else if (pcbRead)
1812 {
1813 fEofAdjusted = true;
1814 cbBuf = pWaveFile->cbSamples - pWaveFile->offCur;
1815 }
1816 else
1817 return VERR_EOF;
1818
1819 int rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples + pWaveFile->offCur, pvBuf, cbBuf, pcbRead);
1820 if (RT_SUCCESS(rc))
1821 {
1822 if (pcbRead)
1823 {
1824 pWaveFile->offCur += (uint32_t)*pcbRead;
1825 if (fEofAdjusted || cbBuf > *pcbRead)
1826 rc = VINF_EOF;
1827 else if (!cbBuf && pWaveFile->offCur == pWaveFile->cbSamples)
1828 rc = VINF_EOF;
1829 }
1830 else
1831 pWaveFile->offCur += (uint32_t)cbBuf;
1832 }
1833 return rc;
1834}
1835
1836
1837/**
1838 * Writes samples to a wave file.
1839 *
1840 * @returns VBox status code.
1841 * @param pWaveFile The file to write to.
1842 * @param pvBuf The samples to write.
1843 * @param cbBuf How many bytes of samples to write.
1844 */
1845int AudioTestWaveFileWrite(PAUDIOTESTWAVEFILE pWaveFile, const void *pvBuf, size_t cbBuf)
1846{
1847 AssertReturn(pWaveFile->u32Magic == AUDIOTESTWAVEFILE_MAGIC, VERR_INVALID_MAGIC);
1848 AssertReturn(!pWaveFile->fReadMode, VERR_ACCESS_DENIED);
1849
1850 pWaveFile->cbSamples += (uint32_t)cbBuf;
1851 return RTFileWrite(pWaveFile->hFile, pvBuf, cbBuf, NULL);
1852}
1853
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