VirtualBox

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

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

Audio/ValKit: Added AudioTestPathGetTemp(). 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 89468 2021-06-02 13:18:28Z 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/**
296 * Return the tag to use in the given buffer, generating one if needed.
297 *
298 * @returns VBox status code.
299 * @param pszTag The output buffer.
300 * @param cbTag The size of the output buffer.
301 * AUDIOTEST_TAG_MAX is a good size.
302 * @param pszTagUser User specified tag, optional.
303 */
304int AudioTestCopyOrGenTag(char *pszTag, size_t cbTag, const char *pszTagUser)
305{
306 if (pszTagUser && *pszTagUser)
307 return RTStrCopy(pszTag, cbTag, pszTagUser);
308 return AudioTestGenTag(pszTag, cbTag);
309}
310
311
312/**
313 * Creates a new path (directory) for a specific audio test set tag.
314 *
315 * @returns VBox status code.
316 * @param pszPath On input, specifies the absolute base path where to create the test set path.
317 * On output this specifies the absolute path created.
318 * @param cbPath Size (in bytes) of \a pszPath.
319 * @param pszTag Tag to use for path creation.
320 *
321 * @note Can be used multiple times with the same tag; a sub directory with an ISO time string will be used
322 * on each call.
323 */
324int AudioTestPathCreate(char *pszPath, size_t cbPath, const char *pszTag)
325{
326 char szTag[AUDIOTEST_TAG_MAX];
327 int rc = AudioTestCopyOrGenTag(szTag, sizeof(szTag), pszTag);
328 AssertRCReturn(rc, rc);
329
330 char szName[RT_ELEMENTS(AUDIOTEST_PATH_PREFIX_STR) + AUDIOTEST_TAG_MAX + 4];
331 if (RTStrPrintf2(szName, sizeof(szName), "%s-%s", AUDIOTEST_PATH_PREFIX_STR, szTag) < 0)
332 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
333
334 rc = RTPathAppend(pszPath, cbPath, szName);
335 AssertRCReturn(rc, rc);
336
337#ifndef DEBUG /* Makes debugging easier to have a deterministic directory. */
338 char szTime[64];
339 RTTIMESPEC time;
340 if (!RTTimeSpecToString(RTTimeNow(&time), szTime, sizeof(szTime)))
341 return VERR_BUFFER_UNDERFLOW;
342
343 /* Colons aren't allowed in windows filenames, so change to dashes. */
344 char *pszColon;
345 while ((pszColon = strchr(szTime, ':')) != NULL)
346 *pszColon = '-';
347
348 rc = RTPathAppend(pszPath, cbPath, szTime);
349 AssertRCReturn(rc, rc);
350#endif
351
352 return RTDirCreateFullPath(pszPath, RTFS_UNIX_IRWXU);
353}
354
355DECLINLINE(int) audioTestManifestWriteData(PAUDIOTESTSET pSet, const void *pvData, size_t cbData)
356{
357 /** @todo Use RTIniFileWrite once its implemented. */
358 return RTFileWrite(pSet->f.hFile, pvData, cbData, NULL);
359}
360
361/**
362 * Writes string data to a test set manifest.
363 *
364 * @returns VBox status code.
365 * @param pSet Test set to write manifest for.
366 * @param pszFormat Format string to write.
367 * @param args Variable arguments for \a pszFormat.
368 */
369static int audioTestManifestWriteV(PAUDIOTESTSET pSet, const char *pszFormat, va_list args)
370{
371 /** @todo r=bird: Use RTStrmOpen + RTStrmPrintf instead of this slow
372 * do-it-all-yourself stuff. */
373 char *psz = NULL;
374 if (RTStrAPrintfV(&psz, pszFormat, args) == -1)
375 return VERR_NO_MEMORY;
376 AssertPtrReturn(psz, VERR_NO_MEMORY);
377
378 int rc = audioTestManifestWriteData(pSet, psz, strlen(psz));
379 AssertRC(rc);
380
381 RTStrFree(psz);
382
383 return rc;
384}
385
386/**
387 * Writes a string to a test set manifest.
388 * Convenience function.
389 *
390 * @returns VBox status code.
391 * @param pSet Test set to write manifest for.
392 * @param pszFormat Format string to write.
393 * @param ... Variable arguments for \a pszFormat. Optional.
394 */
395static int audioTestManifestWrite(PAUDIOTESTSET pSet, const char *pszFormat, ...)
396{
397 va_list va;
398 va_start(va, pszFormat);
399
400 int rc = audioTestManifestWriteV(pSet, pszFormat, va);
401 AssertRC(rc);
402
403 va_end(va);
404
405 return rc;
406}
407
408/**
409 * Returns the current read/write offset (in bytes) of the opened manifest file.
410 *
411 * @returns Current read/write offset (in bytes).
412 * @param pSet Set to return offset for.
413 * Must have an opened manifest file.
414 */
415DECLINLINE(uint64_t) audioTestManifestGetOffsetAbs(PAUDIOTESTSET pSet)
416{
417 AssertReturn(RTFileIsValid(pSet->f.hFile), 0);
418 return RTFileTell(pSet->f.hFile);
419}
420
421/**
422 * Writes a section header to a test set manifest.
423 *
424 * @returns VBox status code.
425 * @param pSet Test set to write manifest for.
426 * @param pszSection Format string of section to write.
427 * @param ... Variable arguments for \a pszSection. Optional.
428 */
429static int audioTestManifestWriteSectionHdr(PAUDIOTESTSET pSet, const char *pszSection, ...)
430{
431 va_list va;
432 va_start(va, pszSection);
433
434 /** @todo Keep it as simple as possible for now. Improve this later. */
435 int rc = audioTestManifestWrite(pSet, "[%N]\n", pszSection, &va);
436
437 va_end(va);
438
439 return rc;
440}
441
442/**
443 * Initializes an audio test set, internal function.
444 *
445 * @param pSet Test set to initialize.
446 */
447static void audioTestSetInitInternal(PAUDIOTESTSET pSet)
448{
449 pSet->f.hFile = NIL_RTFILE;
450
451 RTListInit(&pSet->lstObj);
452 pSet->cObj = 0;
453
454 RTListInit(&pSet->lstTest);
455 pSet->cTests = 0;
456 pSet->cTestsRunning = 0;
457 pSet->offTestCount = 0;
458 pSet->pTestCur = NULL;
459 pSet->cObj = 0;
460 pSet->offObjCount = 0;
461 pSet->cTotalFailures = 0;
462}
463
464/**
465 * Returns whether a test set's manifest file is open (and thus ready) or not.
466 *
467 * @returns \c true if open (and ready), or \c false if not.
468 * @retval VERR_
469 * @param pSet Test set to return open status for.
470 */
471static bool audioTestManifestIsOpen(PAUDIOTESTSET pSet)
472{
473 if ( pSet->enmMode == AUDIOTESTSETMODE_TEST
474 && pSet->f.hFile != NIL_RTFILE)
475 return true;
476 else if ( pSet->enmMode == AUDIOTESTSETMODE_VERIFY
477 && pSet->f.hIniFile != NIL_RTINIFILE)
478 return true;
479
480 return false;
481}
482
483/**
484 * Initializes an audio test error description.
485 *
486 * @param pErr Test error description to initialize.
487 */
488static void audioTestErrorDescInit(PAUDIOTESTERRORDESC pErr)
489{
490 RTListInit(&pErr->List);
491 pErr->cErrors = 0;
492}
493
494/**
495 * Destroys an audio test error description.
496 *
497 * @param pErr Test error description to destroy.
498 */
499void AudioTestErrorDescDestroy(PAUDIOTESTERRORDESC pErr)
500{
501 if (!pErr)
502 return;
503
504 PAUDIOTESTERRORENTRY pErrEntry, pErrEntryNext;
505 RTListForEachSafe(&pErr->List, pErrEntry, pErrEntryNext, AUDIOTESTERRORENTRY, Node)
506 {
507 RTListNodeRemove(&pErrEntry->Node);
508
509 RTMemFree(pErrEntry);
510
511 Assert(pErr->cErrors);
512 pErr->cErrors--;
513 }
514
515 Assert(pErr->cErrors == 0);
516}
517
518/**
519 * Returns if an audio test error description contains any errors or not.
520 *
521 * @returns \c true if it contains errors, or \c false if not.
522 *
523 * @param pErr Test error description to return error status for.
524 */
525bool AudioTestErrorDescFailed(PAUDIOTESTERRORDESC pErr)
526{
527 if (pErr->cErrors)
528 {
529 Assert(!RTListIsEmpty(&pErr->List));
530 return true;
531 }
532
533 return false;
534}
535
536/**
537 * Adds a single error entry to an audio test error description, va_list version.
538 *
539 * @returns VBox status code.
540 * @param pErr Test error description to add entry for.
541 * @param rc Result code of entry to add.
542 * @param pszDesc Error description format string to add.
543 * @param args Optional format arguments of \a pszDesc to add.
544 */
545static int audioTestErrorDescAddV(PAUDIOTESTERRORDESC pErr, int rc, const char *pszDesc, va_list args)
546{
547 PAUDIOTESTERRORENTRY pEntry = (PAUDIOTESTERRORENTRY)RTMemAlloc(sizeof(AUDIOTESTERRORENTRY));
548 AssertPtrReturn(pEntry, VERR_NO_MEMORY);
549
550 if (RTStrPrintf2V(pEntry->szDesc, sizeof(pEntry->szDesc), pszDesc, args) < 0)
551 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
552
553 pEntry->rc = rc;
554
555 RTListAppend(&pErr->List, &pEntry->Node);
556
557 pErr->cErrors++;
558
559 return VINF_SUCCESS;
560}
561
562/**
563 * Adds a single error entry to an audio test error description, va_list version.
564 *
565 * @returns VBox status code.
566 * @param pErr Test error description to add entry for.
567 * @param pszDesc Error description format string to add.
568 * @param ... Optional format arguments of \a pszDesc to add.
569 */
570static int audioTestErrorDescAdd(PAUDIOTESTERRORDESC pErr, const char *pszDesc, ...)
571{
572 va_list va;
573 va_start(va, pszDesc);
574
575 int rc = audioTestErrorDescAddV(pErr, VERR_GENERAL_FAILURE /** @todo Fudge! */, pszDesc, va);
576
577 va_end(va);
578 return rc;
579}
580
581#if 0
582static int audioTestErrorDescAddRc(PAUDIOTESTERRORDESC pErr, int rc, const char *pszFormat, ...)
583{
584 va_list va;
585 va_start(va, pszFormat);
586
587 int rc2 = audioTestErrorDescAddV(pErr, rc, pszFormat, va);
588
589 va_end(va);
590 return rc2;
591}
592#endif
593
594/**
595 * Retrieves the temporary directory.
596 *
597 * @returns VBox status code.
598 * @param pszPath Where to return the absolute path of the created directory on success.
599 * @param cbPath Size (in bytes) of \a pszPath.
600 */
601int AudioTestPathGetTemp(char *pszPath, size_t cbPath)
602{
603 int rc = RTEnvGetEx(RTENV_DEFAULT, "TESTBOX_PATH_SCRATCH", pszPath, cbPath, NULL);
604 if (RT_FAILURE(rc))
605 {
606 rc = RTPathTemp(pszPath, cbPath);
607 AssertRCReturn(rc, rc);
608 }
609
610 return rc;
611}
612
613/**
614 * Creates a new temporary directory with a specific (test) tag.
615 *
616 * @returns VBox status code.
617 * @param pszPath Where to return the absolute path of the created directory on success.
618 * @param cbPath Size (in bytes) of \a pszPath.
619 * @param pszTag Tag name to use for directory creation.
620 *
621 * @note Can be used multiple times with the same tag; a sub directory with an ISO time string will be used
622 * on each call.
623 */
624int AudioTestPathCreateTemp(char *pszPath, size_t cbPath, const char *pszTag)
625{
626 AssertReturn(pszTag && strlen(pszTag) <= AUDIOTEST_TAG_MAX, VERR_INVALID_PARAMETER);
627
628 char szTemp[RTPATH_MAX];
629 int rc = AudioTestPathGetTemp(szTemp, sizeof(szTemp));
630 AssertRCReturn(rc, rc);
631
632 rc = AudioTestPathCreate(szTemp, sizeof(szTemp), pszTag);
633 AssertRCReturn(rc, rc);
634
635 return RTStrCopy(pszPath, cbPath, szTemp);
636}
637
638/**
639 * Returns the absolute path of a given audio test set object.
640 *
641 * @returns VBox status code.
642 * @param pSet Test set the object contains.
643 * @param pszPathAbs Where to return the absolute path on success.
644 * @param cbPathAbs Size (in bytes) of \a pszPathAbs.
645 * @param pszObjName Name of the object to create absolute path for.
646 */
647DECLINLINE(int) audioTestSetGetObjPath(PAUDIOTESTSET pSet, char *pszPathAbs, size_t cbPathAbs, const char *pszObjName)
648{
649 return RTPathJoin(pszPathAbs, cbPathAbs, pSet->szPathAbs, pszObjName);
650}
651
652/**
653 * Returns the tag of a test set.
654 *
655 * @returns Test set tag.
656 * @param pSet Test set to return tag for.
657 */
658const char *AudioTestSetGetTag(PAUDIOTESTSET pSet)
659{
660 return pSet->szTag;
661}
662
663/**
664 * Creates a new audio test set.
665 *
666 * @returns VBox status code.
667 * @param pSet Test set to create.
668 * @param pszPath Where to store the set set data. If NULL, the
669 * temporary directory will be used.
670 * @param pszTag Tag name to use for this test set.
671 */
672int AudioTestSetCreate(PAUDIOTESTSET pSet, const char *pszPath, const char *pszTag)
673{
674 audioTestSetInitInternal(pSet);
675
676 int rc = AudioTestCopyOrGenTag(pSet->szTag, sizeof(pSet->szTag), pszTag);
677 AssertRCReturn(rc, rc);
678
679 /*
680 * Test set directory.
681 */
682 if (pszPath)
683 {
684 rc = RTPathAbs(pszPath, pSet->szPathAbs, sizeof(pSet->szPathAbs));
685 AssertRCReturn(rc, rc);
686
687 rc = AudioTestPathCreate(pSet->szPathAbs, sizeof(pSet->szPathAbs), pSet->szTag);
688 }
689 else
690 rc = AudioTestPathCreateTemp(pSet->szPathAbs, sizeof(pSet->szPathAbs), pSet->szTag);
691 AssertRCReturn(rc, rc);
692
693 /*
694 * Create the manifest file.
695 */
696 char szTmp[RTPATH_MAX];
697 rc = RTPathJoin(szTmp, sizeof(szTmp), pSet->szPathAbs, AUDIOTEST_MANIFEST_FILE_STR);
698 AssertRCReturn(rc, rc);
699
700 rc = RTFileOpen(&pSet->f.hFile, szTmp, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
701 AssertRCReturn(rc, rc);
702
703 rc = audioTestManifestWriteSectionHdr(pSet, "header");
704 AssertRCReturn(rc, rc);
705
706 rc = audioTestManifestWrite(pSet, "magic=vkat_ini\n"); /* VKAT Manifest, .INI-style. */
707 AssertRCReturn(rc, rc);
708 rc = audioTestManifestWrite(pSet, "ver=%d\n", AUDIOTEST_MANIFEST_VER);
709 AssertRCReturn(rc, rc);
710 rc = audioTestManifestWrite(pSet, "tag=%s\n", pSet->szTag);
711 AssertRCReturn(rc, rc);
712
713 AssertCompile(sizeof(szTmp) > RTTIME_STR_LEN);
714 RTTIMESPEC Now;
715 rc = audioTestManifestWrite(pSet, "date_created=%s\n", RTTimeSpecToString(RTTimeNow(&Now), szTmp, sizeof(szTmp)));
716 AssertRCReturn(rc, rc);
717
718 RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp)); /* do NOT return on failure. */
719 rc = audioTestManifestWrite(pSet, "os_product=%s\n", szTmp);
720 AssertRCReturn(rc, rc);
721
722 RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp)); /* do NOT return on failure. */
723 rc = audioTestManifestWrite(pSet, "os_rel=%s\n", szTmp);
724 AssertRCReturn(rc, rc);
725
726 RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp)); /* do NOT return on failure. */
727 rc = audioTestManifestWrite(pSet, "os_ver=%s\n", szTmp);
728 AssertRCReturn(rc, rc);
729
730 rc = audioTestManifestWrite(pSet, "vbox_ver=%s r%u %s (%s %s)\n",
731 VBOX_VERSION_STRING, RTBldCfgRevision(), RTBldCfgTargetDotArch(), __DATE__, __TIME__);
732 AssertRCReturn(rc, rc);
733
734 rc = audioTestManifestWrite(pSet, "test_count=");
735 AssertRCReturn(rc, rc);
736 pSet->offTestCount = audioTestManifestGetOffsetAbs(pSet);
737 rc = audioTestManifestWrite(pSet, "0000\n"); /* A bit messy, but does the trick for now. */
738 AssertRCReturn(rc, rc);
739
740 rc = audioTestManifestWrite(pSet, "obj_count=");
741 AssertRCReturn(rc, rc);
742 pSet->offObjCount = audioTestManifestGetOffsetAbs(pSet);
743 rc = audioTestManifestWrite(pSet, "0000\n"); /* A bit messy, but does the trick for now. */
744 AssertRCReturn(rc, rc);
745
746 pSet->enmMode = AUDIOTESTSETMODE_TEST;
747
748 return rc;
749}
750
751/**
752 * Destroys a test set.
753 *
754 * @returns VBox status code.
755 * @param pSet Test set to destroy.
756 */
757int AudioTestSetDestroy(PAUDIOTESTSET pSet)
758{
759 if (!pSet)
760 return VINF_SUCCESS;
761
762 AssertReturn(pSet->cTestsRunning == 0, VERR_WRONG_ORDER); /* Make sure no tests sill are running. */
763
764 int rc = AudioTestSetClose(pSet);
765 if (RT_FAILURE(rc))
766 return rc;
767
768 PAUDIOTESTOBJ pObj, pObjNext;
769 RTListForEachSafe(&pSet->lstObj, pObj, pObjNext, AUDIOTESTOBJ, Node)
770 {
771 rc = AudioTestSetObjClose(pObj);
772 if (RT_SUCCESS(rc))
773 {
774 PAUDIOTESTOBJMETA pMeta, pMetaNext;
775 RTListForEachSafe(&pObj->lstMeta, pMeta, pMetaNext, AUDIOTESTOBJMETA, Node)
776 {
777 switch (pMeta->enmType)
778 {
779 case AUDIOTESTOBJMETADATATYPE_STRING:
780 {
781 RTStrFree((char *)pMeta->pvMeta);
782 break;
783 }
784
785 default:
786 AssertFailed();
787 break;
788 }
789
790 RTListNodeRemove(&pMeta->Node);
791 RTMemFree(pMeta);
792 }
793
794 RTListNodeRemove(&pObj->Node);
795 RTMemFree(pObj);
796
797 Assert(pSet->cObj);
798 pSet->cObj--;
799 }
800 else
801 break;
802 }
803
804 if (RT_FAILURE(rc))
805 return rc;
806
807 Assert(pSet->cObj == 0);
808
809 PAUDIOTESTENTRY pEntry, pEntryNext;
810 RTListForEachSafe(&pSet->lstTest, pEntry, pEntryNext, AUDIOTESTENTRY, Node)
811 {
812 RTListNodeRemove(&pEntry->Node);
813 RTMemFree(pEntry);
814
815 Assert(pSet->cTests);
816 pSet->cTests--;
817 }
818
819 if (RT_FAILURE(rc))
820 return rc;
821
822 Assert(pSet->cTests == 0);
823
824 return rc;
825}
826
827/**
828 * Opens an existing audio test set.
829 *
830 * @returns VBox status code.
831 * @param pSet Test set to open.
832 * @param pszPath Absolute path of the test set to open.
833 */
834int AudioTestSetOpen(PAUDIOTESTSET pSet, const char *pszPath)
835{
836 audioTestSetInitInternal(pSet);
837
838 char szManifest[RTPATH_MAX];
839 int rc = RTPathJoin(szManifest, sizeof(szManifest), pszPath, AUDIOTEST_MANIFEST_FILE_STR);
840 AssertRCReturn(rc, rc);
841
842 RTVFSFILE hVfsFile;
843 rc = RTVfsFileOpenNormal(szManifest, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hVfsFile);
844 if (RT_FAILURE(rc))
845 return rc;
846
847 rc = RTIniFileCreateFromVfsFile(&pSet->f.hIniFile, hVfsFile, RTINIFILE_F_READONLY);
848 RTVfsFileRelease(hVfsFile);
849 AssertRCReturn(rc, rc);
850
851 pSet->enmMode = AUDIOTESTSETMODE_VERIFY;
852
853 return rc;
854}
855
856/**
857 * Closes an opened audio test set.
858 *
859 * @returns VBox status code.
860 * @param pSet Test set to close.
861 */
862int AudioTestSetClose(PAUDIOTESTSET pSet)
863{
864 if (!pSet)
865 return VINF_SUCCESS;
866
867 if (!RTFileIsValid(pSet->f.hFile))
868 return VINF_SUCCESS;
869
870 int rc;
871
872 /* Update number of bound test objects. */
873 PAUDIOTESTENTRY pTest;
874 RTListForEach(&pSet->lstTest, pTest, AUDIOTESTENTRY, Node)
875 {
876 rc = RTFileSeek(pSet->f.hFile, pTest->offObjCount, RTFILE_SEEK_BEGIN, NULL);
877 AssertRCReturn(rc, rc);
878 rc = audioTestManifestWrite(pSet, "%04RU32", pTest->cObj);
879 AssertRCReturn(rc, rc);
880 }
881
882 /*
883 * Update number of ran tests.
884 */
885 rc = RTFileSeek(pSet->f.hFile, pSet->offObjCount, RTFILE_SEEK_BEGIN, NULL);
886 AssertRCReturn(rc, rc);
887 rc = audioTestManifestWrite(pSet, "%04RU32", pSet->cObj);
888 AssertRCReturn(rc, rc);
889
890 /*
891 * Update number of ran tests.
892 */
893 rc = RTFileSeek(pSet->f.hFile, pSet->offTestCount, RTFILE_SEEK_BEGIN, NULL);
894 AssertRCReturn(rc, rc);
895 rc = audioTestManifestWrite(pSet, "%04RU32", pSet->cTests);
896 AssertRCReturn(rc, rc);
897
898 /*
899 * Serialize all registered test objects.
900 */
901 rc = RTFileSeek(pSet->f.hFile, 0, RTFILE_SEEK_END, NULL);
902 AssertRCReturn(rc, rc);
903
904 PAUDIOTESTOBJ pObj;
905 RTListForEach(&pSet->lstObj, pObj, AUDIOTESTOBJ, Node)
906 {
907 rc = audioTestManifestWrite(pSet, "\n");
908 AssertRCReturn(rc, rc);
909 char szUuid[64];
910 rc = RTUuidToStr(&pObj->Uuid, szUuid, sizeof(szUuid));
911 AssertRCReturn(rc, rc);
912 rc = audioTestManifestWriteSectionHdr(pSet, "obj_%s", szUuid);
913 AssertRCReturn(rc, rc);
914 rc = audioTestManifestWrite(pSet, "obj_type=%RU32\n", pObj->enmType);
915 AssertRCReturn(rc, rc);
916 rc = audioTestManifestWrite(pSet, "obj_name=%s\n", pObj->szName);
917 AssertRCReturn(rc, rc);
918
919 switch (pObj->enmType)
920 {
921 case AUDIOTESTOBJTYPE_FILE:
922 {
923 rc = audioTestManifestWrite(pSet, "obj_size=%RU64\n", pObj->File.cbSize);
924 AssertRCReturn(rc, rc);
925 break;
926 }
927
928 default:
929 AssertFailed();
930 break;
931 }
932
933 /*
934 * Write all meta data.
935 */
936 PAUDIOTESTOBJMETA pMeta;
937 RTListForEach(&pObj->lstMeta, pMeta, AUDIOTESTOBJMETA, Node)
938 {
939 switch (pMeta->enmType)
940 {
941 case AUDIOTESTOBJMETADATATYPE_STRING:
942 {
943 rc = audioTestManifestWrite(pSet, (const char *)pMeta->pvMeta);
944 AssertRCReturn(rc, rc);
945 break;
946 }
947
948 default:
949 AssertFailed();
950 break;
951 }
952 }
953 }
954
955 RTFileClose(pSet->f.hFile);
956 pSet->f.hFile = NIL_RTFILE;
957
958 return rc;
959}
960
961/**
962 * Physically wipes all related test set files off the disk.
963 *
964 * @returns VBox status code.
965 * @param pSet Test set to wipe.
966 */
967int AudioTestSetWipe(PAUDIOTESTSET pSet)
968{
969 AssertPtrReturn(pSet, VERR_INVALID_POINTER);
970
971 int rc = VINF_SUCCESS;
972 char szFilePath[RTPATH_MAX];
973
974 PAUDIOTESTOBJ pObj;
975 RTListForEach(&pSet->lstObj, pObj, AUDIOTESTOBJ, Node)
976 {
977 int rc2 = AudioTestSetObjClose(pObj);
978 if (RT_SUCCESS(rc2))
979 {
980 rc2 = audioTestSetGetObjPath(pSet, szFilePath, sizeof(szFilePath), pObj->szName);
981 if (RT_SUCCESS(rc2))
982 rc2 = RTFileDelete(szFilePath);
983 }
984
985 if (RT_SUCCESS(rc))
986 rc = rc2;
987 /* Keep going. */
988 }
989
990 if (RT_SUCCESS(rc))
991 {
992 rc = RTPathJoin(szFilePath, sizeof(szFilePath), pSet->szPathAbs, AUDIOTEST_MANIFEST_FILE_STR);
993 if (RT_SUCCESS(rc))
994 rc = RTFileDelete(szFilePath);
995 }
996
997 /* Remove the (hopefully now empty) directory. Otherwise let this fail. */
998 if (RT_SUCCESS(rc))
999 rc = RTDirRemove(pSet->szPathAbs);
1000
1001 return rc;
1002}
1003
1004/**
1005 * Creates and registers a new audio test object to the current running test.
1006 *
1007 * @returns VBox status code.
1008 * @param pSet Test set to create and register new object for.
1009 * @param pszName Name of new object to create.
1010 * @param ppObj Where to return the pointer to the newly created object on success.
1011 */
1012int AudioTestSetObjCreateAndRegister(PAUDIOTESTSET pSet, const char *pszName, PAUDIOTESTOBJ *ppObj)
1013{
1014 AssertReturn(pSet->cTestsRunning == 1, VERR_WRONG_ORDER); /* No test nesting allowed. */
1015
1016 AssertPtrReturn(pszName, VERR_INVALID_POINTER);
1017
1018 PAUDIOTESTOBJ pObj = (PAUDIOTESTOBJ)RTMemAlloc(sizeof(AUDIOTESTOBJ));
1019 AssertPtrReturn(pObj, VERR_NO_MEMORY);
1020
1021 RTListInit(&pObj->lstMeta);
1022
1023 if (RTStrPrintf2(pObj->szName, sizeof(pObj->szName), "%04RU32-%s", pSet->cObj, pszName) <= 0)
1024 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
1025
1026 /** @todo Generalize this function more once we have more object types. */
1027
1028 char szObjPathAbs[RTPATH_MAX];
1029 int rc = audioTestSetGetObjPath(pSet, szObjPathAbs, sizeof(szObjPathAbs), pObj->szName);
1030 if (RT_SUCCESS(rc))
1031 {
1032 rc = RTFileOpen(&pObj->File.hFile, szObjPathAbs, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
1033 if (RT_SUCCESS(rc))
1034 {
1035 pObj->enmType = AUDIOTESTOBJTYPE_FILE;
1036 pObj->cRefs = 1; /* Currently only 1:1 mapping. */
1037
1038 RTListAppend(&pSet->lstObj, &pObj->Node);
1039 pSet->cObj++;
1040
1041 /* Generate + set an UUID for the object and assign it to the current test. */
1042 rc = RTUuidCreate(&pObj->Uuid);
1043 AssertRCReturn(rc, rc);
1044 char szUuid[64];
1045 rc = RTUuidToStr(&pObj->Uuid, szUuid, sizeof(szUuid));
1046 AssertRCReturn(rc, rc);
1047
1048 rc = audioTestManifestWrite(pSet, "obj%RU32_uuid=%s\n", pSet->pTestCur->cObj, szUuid);
1049 AssertRCReturn(rc, rc);
1050
1051 AssertPtr(pSet->pTestCur);
1052 pSet->pTestCur->cObj++;
1053
1054 *ppObj = pObj;
1055 }
1056 }
1057
1058 if (RT_FAILURE(rc))
1059 RTMemFree(pObj);
1060
1061 return rc;
1062}
1063
1064/**
1065 * Writes to a created audio test object.
1066 *
1067 * @returns VBox status code.
1068 * @param pObj Audio test object to write to.
1069 * @param pvBuf Pointer to data to write.
1070 * @param cbBuf Size (in bytes) of \a pvBuf to write.
1071 */
1072int AudioTestSetObjWrite(PAUDIOTESTOBJ pObj, const void *pvBuf, size_t cbBuf)
1073{
1074 /** @todo Generalize this function more once we have more object types. */
1075 AssertReturn(pObj->enmType == AUDIOTESTOBJTYPE_FILE, VERR_INVALID_PARAMETER);
1076
1077 return RTFileWrite(pObj->File.hFile, pvBuf, cbBuf, NULL);
1078}
1079
1080/**
1081 * Adds meta data to a test object as a string, va_list version.
1082 *
1083 * @returns VBox status code.
1084 * @param pObj Test object to add meta data for.
1085 * @param pszFormat Format string to add.
1086 * @param va Variable arguments list to use for the format string.
1087 */
1088static int audioTestSetObjAddMetadataStrV(PAUDIOTESTOBJ pObj, const char *pszFormat, va_list va)
1089{
1090 PAUDIOTESTOBJMETA pMeta = (PAUDIOTESTOBJMETA)RTMemAlloc(sizeof(AUDIOTESTOBJMETA));
1091 AssertPtrReturn(pMeta, VERR_NO_MEMORY);
1092
1093 pMeta->pvMeta = RTStrAPrintf2V(pszFormat, va);
1094 AssertPtrReturn(pMeta->pvMeta, VERR_BUFFER_OVERFLOW);
1095 pMeta->cbMeta = RTStrNLen((const char *)pMeta->pvMeta, RTSTR_MAX);
1096
1097 pMeta->enmType = AUDIOTESTOBJMETADATATYPE_STRING;
1098
1099 RTListAppend(&pObj->lstMeta, &pMeta->Node);
1100
1101 return VINF_SUCCESS;
1102}
1103
1104/**
1105 * Adds meta data to a test object as a string.
1106 *
1107 * @returns VBox status code.
1108 * @param pObj Test object to add meta data for.
1109 * @param pszFormat Format string to add.
1110 * @param ... Variable arguments for the format string.
1111 */
1112int AudioTestSetObjAddMetadataStr(PAUDIOTESTOBJ pObj, const char *pszFormat, ...)
1113{
1114 va_list va;
1115
1116 va_start(va, pszFormat);
1117 int rc = audioTestSetObjAddMetadataStrV(pObj, pszFormat, va);
1118 va_end(va);
1119
1120 return rc;
1121}
1122
1123/**
1124 * Closes an opened audio test object.
1125 *
1126 * @returns VBox status code.
1127 * @param pObj Audio test object to close.
1128 */
1129int AudioTestSetObjClose(PAUDIOTESTOBJ pObj)
1130{
1131 if (!pObj)
1132 return VINF_SUCCESS;
1133
1134 /** @todo Generalize this function more once we have more object types. */
1135 AssertReturn(pObj->enmType == AUDIOTESTOBJTYPE_FILE, VERR_INVALID_PARAMETER);
1136
1137 int rc = VINF_SUCCESS;
1138
1139 if (RTFileIsValid(pObj->File.hFile))
1140 {
1141 pObj->File.cbSize = RTFileTell(pObj->File.hFile);
1142
1143 rc = RTFileClose(pObj->File.hFile);
1144 pObj->File.hFile = NIL_RTFILE;
1145 }
1146
1147 return rc;
1148}
1149
1150/**
1151 * Begins a new test of a test set.
1152 *
1153 * @returns VBox status code.
1154 * @param pSet Test set to begin new test for.
1155 * @param pszDesc Test description.
1156 * @param pParms Test parameters to use.
1157 * @param ppEntry Where to return the new test handle.
1158 */
1159int AudioTestSetTestBegin(PAUDIOTESTSET pSet, const char *pszDesc, PAUDIOTESTPARMS pParms, PAUDIOTESTENTRY *ppEntry)
1160{
1161 AssertReturn(pSet->cTestsRunning == 0, VERR_WRONG_ORDER); /* No test nesting allowed. */
1162
1163 PAUDIOTESTENTRY pEntry = (PAUDIOTESTENTRY)RTMemAllocZ(sizeof(AUDIOTESTENTRY));
1164 AssertPtrReturn(pEntry, VERR_NO_MEMORY);
1165
1166 int rc = RTStrCopy(pEntry->szDesc, sizeof(pEntry->szDesc), pszDesc);
1167 AssertRCReturn(rc, rc);
1168
1169 memcpy(&pEntry->Parms, pParms, sizeof(AUDIOTESTPARMS));
1170 pEntry->pParent = pSet;
1171
1172 rc = audioTestManifestWrite(pSet, "\n");
1173 AssertRCReturn(rc, rc);
1174
1175 rc = audioTestManifestWriteSectionHdr(pSet, "test_%04RU32", pSet->cTests);
1176 AssertRCReturn(rc, rc);
1177 rc = audioTestManifestWrite(pSet, "test_desc=%s\n", pszDesc);
1178 AssertRCReturn(rc, rc);
1179 rc = audioTestManifestWrite(pSet, "test_type=%RU32\n", pParms->enmType);
1180 AssertRCReturn(rc, rc);
1181 rc = audioTestManifestWrite(pSet, "test_delay_ms=%RU32\n", pParms->msDelay);
1182 AssertRCReturn(rc, rc);
1183 rc = audioTestManifestWrite(pSet, "audio_direction=%s\n", PDMAudioDirGetName(pParms->enmDir));
1184 AssertRCReturn(rc, rc);
1185
1186 rc = audioTestManifestWrite(pSet, "obj_count=");
1187 AssertRCReturn(rc, rc);
1188 pEntry->offObjCount = audioTestManifestGetOffsetAbs(pSet);
1189 rc = audioTestManifestWrite(pSet, "0000\n"); /* A bit messy, but does the trick for now. */
1190 AssertRCReturn(rc, rc);
1191
1192 switch (pParms->enmType)
1193 {
1194 case AUDIOTESTTYPE_TESTTONE_PLAY:
1195 RT_FALL_THROUGH();
1196 case AUDIOTESTTYPE_TESTTONE_RECORD:
1197 {
1198 rc = audioTestManifestWrite(pSet, "tone_freq_hz=%RU16\n", (uint16_t)pParms->TestTone.dbFreqHz);
1199 AssertRCReturn(rc, rc);
1200 rc = audioTestManifestWrite(pSet, "tone_prequel_ms=%RU32\n", pParms->TestTone.msPrequel);
1201 AssertRCReturn(rc, rc);
1202 rc = audioTestManifestWrite(pSet, "tone_duration_ms=%RU32\n", pParms->TestTone.msDuration);
1203 AssertRCReturn(rc, rc);
1204 rc = audioTestManifestWrite(pSet, "tone_sequel_ms=%RU32\n", pParms->TestTone.msSequel);
1205 AssertRCReturn(rc, rc);
1206 rc = audioTestManifestWrite(pSet, "tone_volume_percent=%RU32\n", pParms->TestTone.uVolumePercent);
1207 AssertRCReturn(rc, rc);
1208 rc = audioTestManifestWrite(pSet, "tone_pcm_hz=%RU32\n", PDMAudioPropsHz(&pParms->TestTone.Props));
1209 AssertRCReturn(rc, rc);
1210 rc = audioTestManifestWrite(pSet, "tone_pcm_channels=%RU8\n", PDMAudioPropsChannels(&pParms->TestTone.Props));
1211 AssertRCReturn(rc, rc);
1212 rc = audioTestManifestWrite(pSet, "tone_pcm_bits=%RU8\n", PDMAudioPropsSampleBits(&pParms->TestTone.Props));
1213 AssertRCReturn(rc, rc);
1214 rc = audioTestManifestWrite(pSet, "tone_pcm_is_signed=%RTbool\n", PDMAudioPropsIsSigned(&pParms->TestTone.Props));
1215 AssertRCReturn(rc, rc);
1216 break;
1217 }
1218
1219 default:
1220 AssertFailed();
1221 break;
1222 }
1223
1224 RTListAppend(&pSet->lstTest, &pEntry->Node);
1225 pSet->cTests++;
1226 pSet->cTestsRunning++;
1227 pSet->pTestCur = pEntry;
1228
1229 *ppEntry = pEntry;
1230
1231 return rc;
1232}
1233
1234/**
1235 * Marks a running test as failed.
1236 *
1237 * @returns VBox status code.
1238 * @param pEntry Test to mark.
1239 * @param rc Error code.
1240 * @param pszErr Error description.
1241 */
1242int AudioTestSetTestFailed(PAUDIOTESTENTRY pEntry, int rc, const char *pszErr)
1243{
1244 AssertReturn(pEntry->pParent->cTestsRunning == 1, VERR_WRONG_ORDER); /* No test nesting allowed. */
1245 AssertReturn(pEntry->rc == VINF_SUCCESS, VERR_WRONG_ORDER);
1246
1247 pEntry->rc = rc;
1248
1249 int rc2 = audioTestManifestWrite(pEntry->pParent, "error_rc=%RI32\n", rc);
1250 AssertRCReturn(rc2, rc2);
1251 rc2 = audioTestManifestWrite(pEntry->pParent, "error_desc=%s\n", pszErr);
1252 AssertRCReturn(rc2, rc2);
1253
1254 pEntry->pParent->cTestsRunning--;
1255 pEntry->pParent->pTestCur = NULL;
1256
1257 return rc2;
1258}
1259
1260/**
1261 * Marks a running test as successfully done.
1262 *
1263 * @returns VBox status code.
1264 * @param pEntry Test to mark.
1265 */
1266int AudioTestSetTestDone(PAUDIOTESTENTRY pEntry)
1267{
1268 AssertReturn(pEntry->pParent->cTestsRunning == 1, VERR_WRONG_ORDER); /* No test nesting allowed. */
1269 AssertReturn(pEntry->rc == VINF_SUCCESS, VERR_WRONG_ORDER);
1270
1271 int rc2 = audioTestManifestWrite(pEntry->pParent, "error_rc=%RI32\n", VINF_SUCCESS);
1272 AssertRCReturn(rc2, rc2);
1273
1274 pEntry->pParent->cTestsRunning--;
1275 pEntry->pParent->pTestCur = NULL;
1276
1277 return rc2;
1278}
1279
1280/**
1281 * Packs a closed audio test so that it's ready for transmission.
1282 *
1283 * @returns VBox status code.
1284 * @param pSet Test set to pack.
1285 * @param pszOutDir Directory where to store the packed test set.
1286 * @param pszFileName Where to return the final name of the packed test set. Optional and can be NULL.
1287 * @param cbFileName Size (in bytes) of \a pszFileName.
1288 */
1289int AudioTestSetPack(PAUDIOTESTSET pSet, const char *pszOutDir, char *pszFileName, size_t cbFileName)
1290{
1291 AssertReturn(!pszFileName || cbFileName, VERR_INVALID_PARAMETER);
1292 AssertReturn(!audioTestManifestIsOpen(pSet), VERR_WRONG_ORDER);
1293
1294 /** @todo Check and deny if \a pszOutDir is part of the set's path. */
1295
1296 int rc = RTDirCreateFullPath(pszOutDir, 0755);
1297 if (RT_FAILURE(rc))
1298 return rc;
1299
1300 char szOutName[RT_ELEMENTS(AUDIOTEST_PATH_PREFIX_STR) + AUDIOTEST_TAG_MAX + 16];
1301 if (RTStrPrintf2(szOutName, sizeof(szOutName), "%s-%s%s",
1302 AUDIOTEST_PATH_PREFIX_STR, pSet->szTag, AUDIOTEST_ARCHIVE_SUFF_STR) <= 0)
1303 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
1304
1305 char szOutPath[RTPATH_MAX];
1306 rc = RTPathJoin(szOutPath, sizeof(szOutPath), pszOutDir, szOutName);
1307 AssertRCReturn(rc, rc);
1308
1309 const char *apszArgs[10];
1310 unsigned cArgs = 0;
1311
1312 apszArgs[cArgs++] = "vkat";
1313 apszArgs[cArgs++] = "--create";
1314 apszArgs[cArgs++] = "--gzip";
1315 apszArgs[cArgs++] = "--directory";
1316 apszArgs[cArgs++] = pSet->szPathAbs;
1317 apszArgs[cArgs++] = "--file";
1318 apszArgs[cArgs++] = szOutPath;
1319 apszArgs[cArgs++] = ".";
1320
1321 RTEXITCODE rcExit = RTZipTarCmd(cArgs, (char **)apszArgs);
1322 if (rcExit != RTEXITCODE_SUCCESS)
1323 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1324
1325 if (RT_SUCCESS(rc))
1326 {
1327 if (pszFileName)
1328 rc = RTStrCopy(pszFileName, cbFileName, szOutPath);
1329 }
1330
1331 return rc;
1332}
1333
1334/**
1335 * Returns whether a test set archive is packed (as .tar.gz by default) or
1336 * a plain directory.
1337 *
1338 * @returns \c true if packed (as .tar.gz), or \c false if not (directory).
1339 * @param pszPath Path to return packed staus for.
1340 */
1341bool AudioTestSetIsPacked(const char *pszPath)
1342{
1343 /** @todo Improve this, good enough for now. */
1344 return (RTStrIStr(pszPath, AUDIOTEST_ARCHIVE_SUFF_STR) != NULL);
1345}
1346
1347/**
1348 * Unpacks a formerly packed audio test set.
1349 *
1350 * @returns VBox status code.
1351 * @param pszFile Test set file to unpack. Must contain the absolute path.
1352 * @param pszOutDir Directory where to unpack the test set into.
1353 * If the directory does not exist it will be created.
1354 */
1355int AudioTestSetUnpack(const char *pszFile, const char *pszOutDir)
1356{
1357 AssertReturn(pszFile && pszOutDir, VERR_INVALID_PARAMETER);
1358
1359 int rc = VINF_SUCCESS;
1360
1361 if (!RTDirExists(pszOutDir))
1362 {
1363 rc = RTDirCreateFullPath(pszOutDir, 0755);
1364 if (RT_FAILURE(rc))
1365 return rc;
1366 }
1367
1368 const char *apszArgs[8];
1369 unsigned cArgs = 0;
1370
1371 apszArgs[cArgs++] = "vkat";
1372 apszArgs[cArgs++] = "--extract";
1373 apszArgs[cArgs++] = "--gunzip";
1374 apszArgs[cArgs++] = "--directory";
1375 apszArgs[cArgs++] = pszOutDir;
1376 apszArgs[cArgs++] = "--file";
1377 apszArgs[cArgs++] = pszFile;
1378
1379 RTEXITCODE rcExit = RTZipTarCmd(cArgs, (char **)apszArgs);
1380 if (rcExit != RTEXITCODE_SUCCESS)
1381 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1382
1383 return rc;
1384}
1385
1386static int audioTestVerifyIniValue(PAUDIOTESTSET pSetA, PAUDIOTESTSET pSetB,
1387 const char *pszSec, const char *pszKey, const char *pszVal)
1388{
1389 char szValA[_1K];
1390 int rc = RTIniFileQueryValue(pSetA->f.hIniFile, pszSec, pszKey, szValA, sizeof(szValA), NULL);
1391 if (RT_FAILURE(rc))
1392 return rc;
1393 char szValB[_1K];
1394 rc = RTIniFileQueryValue(pSetB->f.hIniFile, pszSec, pszKey, szValB, sizeof(szValB), NULL);
1395 if (RT_FAILURE(rc))
1396 return rc;
1397
1398 if (RTStrCmp(szValA, szValB))
1399 return VERR_WRONG_TYPE; /** @todo Fudge! */
1400
1401 if (pszVal)
1402 {
1403 if (RTStrCmp(szValA, pszVal))
1404 return VERR_WRONG_TYPE; /** @todo Fudge! */
1405 }
1406
1407 return rc;
1408}
1409
1410/**
1411 * Verifies an opened audio test set.
1412 *
1413 * @returns VBox status code.
1414 * @param pSetA Test set A to verify.
1415 * @param pSetB Test set to verify test set A with.
1416 * @param pErrDesc Where to return the test verification errors.
1417 *
1418 * @note Test verification errors have to be checked for errors, regardless of the
1419 * actual return code.
1420 */
1421int AudioTestSetVerify(PAUDIOTESTSET pSetA, PAUDIOTESTSET pSetB, PAUDIOTESTERRORDESC pErrDesc)
1422{
1423 AssertReturn(audioTestManifestIsOpen(pSetA), VERR_WRONG_ORDER);
1424 AssertReturn(audioTestManifestIsOpen(pSetB), VERR_WRONG_ORDER);
1425
1426 /* We ASSUME the caller has not init'd pErrDesc. */
1427 audioTestErrorDescInit(pErrDesc);
1428
1429 int rc;
1430
1431#define VERIFY_VALUE(a_Sec, a_Key, a_Val, ...) \
1432 rc = audioTestVerifyIniValue(pSetA, pSetB, a_Sec, a_Key, a_Val); \
1433 if (RT_FAILURE(rc)) \
1434 return audioTestErrorDescAdd(pErrDesc, (__VA_ARGS__));
1435
1436 /*
1437 * Compare obvious values first.
1438 */
1439 VERIFY_VALUE("header", "magic", "vkat_ini", "Manifest magic wrong");
1440 VERIFY_VALUE("header", "ver", "1" , "Manifest version wrong");
1441 VERIFY_VALUE("header", "tag", NULL, "Manifest tags don't match");
1442 VERIFY_VALUE("header", "test_count", NULL, "Test counts don't match");
1443 VERIFY_VALUE("header", "obj_count", NULL, "Object counts don't match");
1444
1445#undef VERIFY_VALUE
1446
1447 /* Only return critical stuff not related to actual testing here. */
1448 return VINF_SUCCESS;
1449}
1450
1451
1452/*********************************************************************************************************************************
1453* WAVE File Reader. *
1454*********************************************************************************************************************************/
1455
1456/**
1457 * Counts the number of set bits in @a fMask.
1458 */
1459static unsigned audioTestWaveCountBits(uint32_t fMask)
1460{
1461 unsigned cBits = 0;
1462 while (fMask)
1463 {
1464 if (fMask & 1)
1465 cBits++;
1466 fMask >>= 1;
1467 }
1468 return cBits;
1469}
1470
1471/**
1472 * Opens a wave (.WAV) file for reading.
1473 *
1474 * @returns VBox status code.
1475 * @param pszFile The file to open.
1476 * @param pWaveFile The open wave file structure to fill in on success.
1477 * @param pErrInfo Where to return addition error details on failure.
1478 */
1479int AudioTestWaveFileOpen(const char *pszFile, PAUDIOTESTWAVEFILE pWaveFile, PRTERRINFO pErrInfo)
1480{
1481 pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC_DEAD;
1482 RT_ZERO(pWaveFile->Props);
1483 pWaveFile->hFile = NIL_RTFILE;
1484 int rc = RTFileOpen(&pWaveFile->hFile, pszFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
1485 if (RT_FAILURE(rc))
1486 return RTErrInfoSet(pErrInfo, rc, "RTFileOpen failed");
1487 uint64_t cbFile = 0;
1488 rc = RTFileQuerySize(pWaveFile->hFile, &cbFile);
1489 if (RT_SUCCESS(rc))
1490 {
1491 union
1492 {
1493 uint8_t ab[512];
1494 struct
1495 {
1496 RTRIFFHDR Hdr;
1497 union
1498 {
1499 RTRIFFWAVEFMTCHUNK Fmt;
1500 RTRIFFWAVEFMTEXTCHUNK FmtExt;
1501 } u;
1502 } Wave;
1503 RTRIFFLIST List;
1504 RTRIFFCHUNK Chunk;
1505 RTRIFFWAVEDATACHUNK Data;
1506 } uBuf;
1507
1508 rc = RTFileRead(pWaveFile->hFile, &uBuf.Wave, sizeof(uBuf.Wave), NULL);
1509 if (RT_SUCCESS(rc))
1510 {
1511 rc = VERR_VFS_UNKNOWN_FORMAT;
1512 if ( uBuf.Wave.Hdr.uMagic == RTRIFFHDR_MAGIC
1513 && uBuf.Wave.Hdr.uFileType == RTRIFF_FILE_TYPE_WAVE
1514 && uBuf.Wave.u.Fmt.Chunk.uMagic == RTRIFFWAVEFMT_MAGIC
1515 && uBuf.Wave.u.Fmt.Chunk.cbChunk >= sizeof(uBuf.Wave.u.Fmt.Data))
1516 {
1517 if (uBuf.Wave.Hdr.cbFile != cbFile - sizeof(RTRIFFCHUNK))
1518 RTErrInfoSetF(pErrInfo, rc, "File size mismatch: %#x, actual %#RX64 (ignored)",
1519 uBuf.Wave.Hdr.cbFile, cbFile - sizeof(RTRIFFCHUNK));
1520 rc = VERR_VFS_BOGUS_FORMAT;
1521 if ( uBuf.Wave.u.Fmt.Data.uFormatTag != RTRIFFWAVEFMT_TAG_PCM
1522 && uBuf.Wave.u.Fmt.Data.uFormatTag != RTRIFFWAVEFMT_TAG_EXTENSIBLE)
1523 RTErrInfoSetF(pErrInfo, rc, "Unsupported uFormatTag value: %#x (expected %#x or %#x)",
1524 uBuf.Wave.u.Fmt.Data.uFormatTag, RTRIFFWAVEFMT_TAG_PCM, RTRIFFWAVEFMT_TAG_EXTENSIBLE);
1525 else if ( uBuf.Wave.u.Fmt.Data.cBitsPerSample != 8
1526 && uBuf.Wave.u.Fmt.Data.cBitsPerSample != 16
1527 /* && uBuf.Wave.u.Fmt.Data.cBitsPerSample != 24 - not supported by our stack */
1528 && uBuf.Wave.u.Fmt.Data.cBitsPerSample != 32)
1529 RTErrInfoSetF(pErrInfo, rc, "Unsupported cBitsPerSample value: %u", uBuf.Wave.u.Fmt.Data.cBitsPerSample);
1530 else if ( uBuf.Wave.u.Fmt.Data.cChannels < 1
1531 || uBuf.Wave.u.Fmt.Data.cChannels >= 16)
1532 RTErrInfoSetF(pErrInfo, rc, "Unsupported cChannels value: %u (expected 1..15)", uBuf.Wave.u.Fmt.Data.cChannels);
1533 else if ( uBuf.Wave.u.Fmt.Data.uHz < 4096
1534 || uBuf.Wave.u.Fmt.Data.uHz > 768000)
1535 RTErrInfoSetF(pErrInfo, rc, "Unsupported uHz value: %u (expected 4096..768000)", uBuf.Wave.u.Fmt.Data.uHz);
1536 else if (uBuf.Wave.u.Fmt.Data.cbFrame != uBuf.Wave.u.Fmt.Data.cChannels * uBuf.Wave.u.Fmt.Data.cBitsPerSample / 8)
1537 RTErrInfoSetF(pErrInfo, rc, "Invalid cbFrame value: %u (expected %u)", uBuf.Wave.u.Fmt.Data.cbFrame,
1538 uBuf.Wave.u.Fmt.Data.cChannels * uBuf.Wave.u.Fmt.Data.cBitsPerSample / 8);
1539 else if (uBuf.Wave.u.Fmt.Data.cbRate != uBuf.Wave.u.Fmt.Data.cbFrame * uBuf.Wave.u.Fmt.Data.uHz)
1540 RTErrInfoSetF(pErrInfo, rc, "Invalid cbRate value: %u (expected %u)", uBuf.Wave.u.Fmt.Data.cbRate,
1541 uBuf.Wave.u.Fmt.Data.cbFrame * uBuf.Wave.u.Fmt.Data.uHz);
1542 else if ( uBuf.Wave.u.Fmt.Data.uFormatTag == RTRIFFWAVEFMT_TAG_EXTENSIBLE
1543 && uBuf.Wave.u.FmtExt.Data.cbExtra < RTRIFFWAVEFMTEXT_EXTRA_SIZE)
1544 RTErrInfoSetF(pErrInfo, rc, "Invalid cbExtra value: %#x (expected at least %#x)",
1545 uBuf.Wave.u.FmtExt.Data.cbExtra, RTRIFFWAVEFMTEXT_EXTRA_SIZE);
1546 else if ( uBuf.Wave.u.Fmt.Data.uFormatTag == RTRIFFWAVEFMT_TAG_EXTENSIBLE
1547 && audioTestWaveCountBits(uBuf.Wave.u.FmtExt.Data.fChannelMask) != uBuf.Wave.u.Fmt.Data.cChannels)
1548 RTErrInfoSetF(pErrInfo, rc, "fChannelMask does not match cChannels: %#x (%u bits set) vs %u channels",
1549 uBuf.Wave.u.FmtExt.Data.fChannelMask,
1550 audioTestWaveCountBits(uBuf.Wave.u.FmtExt.Data.fChannelMask), uBuf.Wave.u.Fmt.Data.cChannels);
1551 else if ( uBuf.Wave.u.Fmt.Data.uFormatTag == RTRIFFWAVEFMT_TAG_EXTENSIBLE
1552 && RTUuidCompareStr(&uBuf.Wave.u.FmtExt.Data.SubFormat, RTRIFFWAVEFMTEXT_SUBTYPE_PCM) != 0)
1553 RTErrInfoSetF(pErrInfo, rc, "SubFormat is not PCM: %RTuuid (expected %s)",
1554 &uBuf.Wave.u.FmtExt.Data.SubFormat, RTRIFFWAVEFMTEXT_SUBTYPE_PCM);
1555 else
1556 {
1557 /*
1558 * Copy out the data we need from the file format structure.
1559 */
1560 PDMAudioPropsInit(&pWaveFile->Props, uBuf.Wave.u.Fmt.Data.cBitsPerSample / 8, true /*fSigned*/,
1561 uBuf.Wave.u.Fmt.Data.cChannels, uBuf.Wave.u.Fmt.Data.uHz);
1562 pWaveFile->offSamples = sizeof(RTRIFFHDR) + sizeof(RTRIFFCHUNK) + uBuf.Wave.u.Fmt.Chunk.cbChunk;
1563
1564 /*
1565 * Pick up channel assignments if present.
1566 */
1567 if (uBuf.Wave.u.Fmt.Data.uFormatTag == RTRIFFWAVEFMT_TAG_EXTENSIBLE)
1568 {
1569 static unsigned const s_cStdIds = (unsigned)PDMAUDIOCHANNELID_END_STANDARD
1570 - (unsigned)PDMAUDIOCHANNELID_FIRST_STANDARD;
1571 unsigned iCh = 0;
1572 for (unsigned idCh = 0; idCh < 32 && iCh < uBuf.Wave.u.Fmt.Data.cChannels; idCh++)
1573 if (uBuf.Wave.u.FmtExt.Data.fChannelMask & RT_BIT_32(idCh))
1574 {
1575 pWaveFile->Props.aidChannels[iCh] = idCh < s_cStdIds
1576 ? idCh + (unsigned)PDMAUDIOCHANNELID_FIRST_STANDARD
1577 : (unsigned)PDMAUDIOCHANNELID_UNKNOWN;
1578 iCh++;
1579 }
1580 }
1581
1582 /*
1583 * Find the 'data' chunk with the audio samples.
1584 *
1585 * There can be INFO lists both preceeding this and succeeding
1586 * it, containing IART and other things we can ignored. Thus
1587 * we read a list header here rather than just a chunk header,
1588 * since it doesn't matter if we read 4 bytes extra as
1589 * AudioTestWaveFileRead uses RTFileReadAt anyway.
1590 */
1591 rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
1592 for (uint32_t i = 0;
1593 i < 128
1594 && RT_SUCCESS(rc)
1595 && uBuf.Chunk.uMagic != RTRIFFWAVEDATACHUNK_MAGIC
1596 && (uint64_t)uBuf.Chunk.cbChunk + sizeof(RTRIFFCHUNK) * 2 <= cbFile - pWaveFile->offSamples;
1597 i++)
1598 {
1599 if ( uBuf.List.uMagic == RTRIFFLIST_MAGIC
1600 && uBuf.List.uListType == RTRIFFLIST_TYPE_INFO)
1601 { /*skip*/ }
1602 else if (uBuf.Chunk.uMagic == RTRIFFPADCHUNK_MAGIC)
1603 { /*skip*/ }
1604 else
1605 break;
1606 pWaveFile->offSamples += sizeof(RTRIFFCHUNK) + uBuf.Chunk.cbChunk;
1607 rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
1608 }
1609 if (RT_SUCCESS(rc))
1610 {
1611 pWaveFile->offSamples += sizeof(uBuf.Data.Chunk);
1612 pWaveFile->cbSamples = (uint32_t)cbFile - pWaveFile->offSamples;
1613
1614 rc = VERR_VFS_BOGUS_FORMAT;
1615 if ( uBuf.Data.Chunk.uMagic == RTRIFFWAVEDATACHUNK_MAGIC
1616 && uBuf.Data.Chunk.cbChunk <= pWaveFile->cbSamples
1617 && PDMAudioPropsIsSizeAligned(&pWaveFile->Props, uBuf.Data.Chunk.cbChunk))
1618 {
1619 pWaveFile->cbSamples = uBuf.Data.Chunk.cbChunk;
1620
1621 /*
1622 * We're good!
1623 */
1624 pWaveFile->offCur = 0;
1625 pWaveFile->fReadMode = true;
1626 pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC;
1627 return VINF_SUCCESS;
1628 }
1629
1630 RTErrInfoSetF(pErrInfo, rc, "Bad data header: uMagic=%#x (expected %#x), cbChunk=%#x (max %#RX64, align %u)",
1631 uBuf.Data.Chunk.uMagic, RTRIFFWAVEDATACHUNK_MAGIC,
1632 uBuf.Data.Chunk.cbChunk, pWaveFile->cbSamples, PDMAudioPropsFrameSize(&pWaveFile->Props));
1633 }
1634 else
1635 RTErrInfoSet(pErrInfo, rc, "Failed to read data header");
1636 }
1637 }
1638 else
1639 RTErrInfoSetF(pErrInfo, rc, "Bad file header: uMagic=%#x (vs. %#x), uFileType=%#x (vs %#x), uFmtMagic=%#x (vs %#x) cbFmtChunk=%#x (min %#x)",
1640 uBuf.Wave.Hdr.uMagic, RTRIFFHDR_MAGIC, uBuf.Wave.Hdr.uFileType, RTRIFF_FILE_TYPE_WAVE,
1641 uBuf.Wave.u.Fmt.Chunk.uMagic, RTRIFFWAVEFMT_MAGIC,
1642 uBuf.Wave.u.Fmt.Chunk.cbChunk, sizeof(uBuf.Wave.u.Fmt.Data));
1643 }
1644 else
1645 rc = RTErrInfoSet(pErrInfo, rc, "Failed to read file header");
1646 }
1647 else
1648 rc = RTErrInfoSet(pErrInfo, rc, "Failed to query file size");
1649
1650 RTFileClose(pWaveFile->hFile);
1651 pWaveFile->hFile = NIL_RTFILE;
1652 return rc;
1653}
1654
1655
1656/**
1657 * Creates a new wave file.
1658 *
1659 * @returns VBox status code.
1660 * @param pszFile The filename.
1661 * @param pProps The audio format properties.
1662 * @param pWaveFile The wave file structure to fill in on success.
1663 * @param pErrInfo Where to return addition error details on failure.
1664 */
1665int AudioTestWaveFileCreate(const char *pszFile, PCPDMAUDIOPCMPROPS pProps, PAUDIOTESTWAVEFILE pWaveFile, PRTERRINFO pErrInfo)
1666{
1667 /*
1668 * Construct the file header first (we'll do some input validation
1669 * here, so better do it before creating the file).
1670 */
1671 struct
1672 {
1673 RTRIFFHDR Hdr;
1674 RTRIFFWAVEFMTEXTCHUNK FmtExt;
1675 RTRIFFCHUNK Data;
1676 } FileHdr;
1677
1678 FileHdr.Hdr.uMagic = RTRIFFHDR_MAGIC;
1679 FileHdr.Hdr.cbFile = 0; /* need to update this later */
1680 FileHdr.Hdr.uFileType = RTRIFF_FILE_TYPE_WAVE;
1681 FileHdr.FmtExt.Chunk.uMagic = RTRIFFWAVEFMT_MAGIC;
1682 FileHdr.FmtExt.Chunk.cbChunk = sizeof(RTRIFFWAVEFMTEXTCHUNK) - sizeof(RTRIFFCHUNK);
1683 FileHdr.FmtExt.Data.Core.uFormatTag = RTRIFFWAVEFMT_TAG_EXTENSIBLE;
1684 FileHdr.FmtExt.Data.Core.cChannels = PDMAudioPropsChannels(pProps);
1685 FileHdr.FmtExt.Data.Core.uHz = PDMAudioPropsHz(pProps);
1686 FileHdr.FmtExt.Data.Core.cbRate = PDMAudioPropsFramesToBytes(pProps, PDMAudioPropsHz(pProps));
1687 FileHdr.FmtExt.Data.Core.cbFrame = PDMAudioPropsFrameSize(pProps);
1688 FileHdr.FmtExt.Data.Core.cBitsPerSample = PDMAudioPropsSampleBits(pProps);
1689 FileHdr.FmtExt.Data.cbExtra = sizeof(FileHdr.FmtExt.Data) - sizeof(FileHdr.FmtExt.Data.Core);
1690 FileHdr.FmtExt.Data.cValidBitsPerSample = PDMAudioPropsSampleBits(pProps);
1691 FileHdr.FmtExt.Data.fChannelMask = 0;
1692 for (uintptr_t idxCh = 0; idxCh < FileHdr.FmtExt.Data.Core.cChannels; idxCh++)
1693 {
1694 PDMAUDIOCHANNELID const idCh = (PDMAUDIOCHANNELID)pProps->aidChannels[idxCh];
1695 if ( idCh >= PDMAUDIOCHANNELID_FIRST_STANDARD
1696 && idCh < PDMAUDIOCHANNELID_END_STANDARD)
1697 {
1698 if (!(FileHdr.FmtExt.Data.fChannelMask & RT_BIT_32(idCh - PDMAUDIOCHANNELID_FIRST_STANDARD)))
1699 FileHdr.FmtExt.Data.fChannelMask |= RT_BIT_32(idCh - PDMAUDIOCHANNELID_FIRST_STANDARD);
1700 else
1701 return RTErrInfoSetF(pErrInfo, VERR_INVALID_PARAMETER, "Channel #%u repeats channel ID %d", idxCh, idCh);
1702 }
1703 else
1704 return RTErrInfoSetF(pErrInfo, VERR_INVALID_PARAMETER, "Invalid channel ID %d for channel #%u", idCh, idxCh);
1705 }
1706
1707 RTUUID UuidTmp;
1708 int rc = RTUuidFromStr(&UuidTmp, RTRIFFWAVEFMTEXT_SUBTYPE_PCM);
1709 AssertRCReturn(rc, rc);
1710 FileHdr.FmtExt.Data.SubFormat = UuidTmp; /* (64-bit field maybe unaligned) */
1711
1712 FileHdr.Data.uMagic = RTRIFFWAVEDATACHUNK_MAGIC;
1713 FileHdr.Data.cbChunk = 0; /* need to update this later */
1714
1715 /*
1716 * Create the file and write the header.
1717 */
1718 pWaveFile->hFile = NIL_RTFILE;
1719 rc = RTFileOpen(&pWaveFile->hFile, pszFile, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
1720 if (RT_FAILURE(rc))
1721 return RTErrInfoSet(pErrInfo, rc, "RTFileOpen failed");
1722
1723 rc = RTFileWrite(pWaveFile->hFile, &FileHdr, sizeof(FileHdr), NULL);
1724 if (RT_SUCCESS(rc))
1725 {
1726 /*
1727 * Initialize the wave file structure.
1728 */
1729 pWaveFile->fReadMode = false;
1730 pWaveFile->offCur = 0;
1731 pWaveFile->offSamples = 0;
1732 pWaveFile->cbSamples = 0;
1733 pWaveFile->Props = *pProps;
1734 pWaveFile->offSamples = RTFileTell(pWaveFile->hFile);
1735 if (pWaveFile->offSamples != UINT32_MAX)
1736 {
1737 pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC;
1738 return VINF_SUCCESS;
1739 }
1740 rc = RTErrInfoSet(pErrInfo, VERR_SEEK, "RTFileTell failed");
1741 }
1742 else
1743 RTErrInfoSet(pErrInfo, rc, "RTFileWrite failed writing header");
1744
1745 RTFileClose(pWaveFile->hFile);
1746 pWaveFile->hFile = NIL_RTFILE;
1747 pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC_DEAD;
1748
1749 RTFileDelete(pszFile);
1750 return rc;
1751}
1752
1753
1754/**
1755 * Closes a wave file.
1756 */
1757int AudioTestWaveFileClose(PAUDIOTESTWAVEFILE pWaveFile)
1758{
1759 AssertReturn(pWaveFile->u32Magic == AUDIOTESTWAVEFILE_MAGIC, VERR_INVALID_MAGIC);
1760 int rcRet = VINF_SUCCESS;
1761 int rc;
1762
1763 /*
1764 * Update the size fields if writing.
1765 */
1766 if (!pWaveFile->fReadMode)
1767 {
1768 uint64_t cbFile = RTFileTell(pWaveFile->hFile);
1769 if (cbFile != UINT64_MAX)
1770 {
1771 uint32_t cbFile32 = cbFile - sizeof(RTRIFFCHUNK);
1772 rc = RTFileWriteAt(pWaveFile->hFile, RT_OFFSETOF(RTRIFFHDR, cbFile), &cbFile32, sizeof(cbFile32), NULL);
1773 AssertRCStmt(rc, rcRet = rc);
1774
1775 uint32_t cbSamples = cbFile - pWaveFile->offSamples;
1776 rc = RTFileWriteAt(pWaveFile->hFile, pWaveFile->offSamples - sizeof(uint32_t), &cbSamples, sizeof(cbSamples), NULL);
1777 AssertRCStmt(rc, rcRet = rc);
1778 }
1779 else
1780 rcRet = VERR_SEEK;
1781 }
1782
1783 /*
1784 * Close it.
1785 */
1786 rc = RTFileClose(pWaveFile->hFile);
1787 AssertRCStmt(rc, rcRet = rc);
1788
1789 pWaveFile->hFile = NIL_RTFILE;
1790 pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC_DEAD;
1791 return rcRet;
1792}
1793
1794/**
1795 * Reads samples from a wave file.
1796 *
1797 * @returns VBox status code. See RTVfsFileRead for EOF status handling.
1798 * @param pWaveFile The file to read from.
1799 * @param pvBuf Where to put the samples.
1800 * @param cbBuf How much to read at most.
1801 * @param pcbRead Where to return the actual number of bytes read,
1802 * optional.
1803 */
1804int AudioTestWaveFileRead(PAUDIOTESTWAVEFILE pWaveFile, void *pvBuf, size_t cbBuf, size_t *pcbRead)
1805{
1806 AssertReturn(pWaveFile->u32Magic == AUDIOTESTWAVEFILE_MAGIC, VERR_INVALID_MAGIC);
1807 AssertReturn(pWaveFile->fReadMode, VERR_ACCESS_DENIED);
1808
1809 bool fEofAdjusted;
1810 if (pWaveFile->offCur + cbBuf <= pWaveFile->cbSamples)
1811 fEofAdjusted = false;
1812 else if (pcbRead)
1813 {
1814 fEofAdjusted = true;
1815 cbBuf = pWaveFile->cbSamples - pWaveFile->offCur;
1816 }
1817 else
1818 return VERR_EOF;
1819
1820 int rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples + pWaveFile->offCur, pvBuf, cbBuf, pcbRead);
1821 if (RT_SUCCESS(rc))
1822 {
1823 if (pcbRead)
1824 {
1825 pWaveFile->offCur += (uint32_t)*pcbRead;
1826 if (fEofAdjusted || cbBuf > *pcbRead)
1827 rc = VINF_EOF;
1828 else if (!cbBuf && pWaveFile->offCur == pWaveFile->cbSamples)
1829 rc = VINF_EOF;
1830 }
1831 else
1832 pWaveFile->offCur += (uint32_t)cbBuf;
1833 }
1834 return rc;
1835}
1836
1837
1838/**
1839 * Writes samples to a wave file.
1840 *
1841 * @returns VBox status code.
1842 * @param pWaveFile The file to write to.
1843 * @param pvBuf The samples to write.
1844 * @param cbBuf How many bytes of samples to write.
1845 */
1846int AudioTestWaveFileWrite(PAUDIOTESTWAVEFILE pWaveFile, const void *pvBuf, size_t cbBuf)
1847{
1848 AssertReturn(pWaveFile->u32Magic == AUDIOTESTWAVEFILE_MAGIC, VERR_INVALID_MAGIC);
1849 AssertReturn(!pWaveFile->fReadMode, VERR_ACCESS_DENIED);
1850
1851 pWaveFile->cbSamples += (uint32_t)cbBuf;
1852 return RTFileWrite(pWaveFile->hFile, pvBuf, cbBuf, NULL);
1853}
1854
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