VirtualBox

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

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

Audio/ValKit: Added AudioTestToneInit() [Doxygen fix]. bugref:10008

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