VirtualBox

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

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

Audio/ValKit: Implemented first first (primitive) binary PCM data comparison. bugref:10008

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