VirtualBox

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

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

AudioTest: Can't have ':' from the timestamp in a filename on windows. duh. bugref:10008

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