VirtualBox

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

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

Audio/ValKit: Implemented unpacking and verification of packed audio test archives. bugref:10008

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 46.1 KB
Line 
1/* $Id: AudioTest.cpp 89139 2021-05-18 13:29:24Z 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 rc = RTPathAppend(pszPath, cbPath, szTime);
315 AssertRCReturn(rc, rc);
316
317 return RTDirCreateFullPath(pszPath, RTFS_UNIX_IRWXU);
318}
319
320DECLINLINE(int) audioTestManifestWriteData(PAUDIOTESTSET pSet, const void *pvData, size_t cbData)
321{
322 /** @todo Use RTIniFileWrite once its implemented. */
323 return RTFileWrite(pSet->f.hFile, pvData, cbData, NULL);
324}
325
326/**
327 * Writes string data to a test set manifest.
328 *
329 * @returns VBox status code.
330 * @param pSet Test set to write manifest for.
331 * @param pszFormat Format string to write.
332 * @param args Variable arguments for \a pszFormat.
333 */
334static int audioTestManifestWriteV(PAUDIOTESTSET pSet, const char *pszFormat, va_list args)
335{
336 char *psz = NULL;
337 if (RTStrAPrintfV(&psz, pszFormat, args) == -1)
338 return VERR_NO_MEMORY;
339 AssertPtrReturn(psz, VERR_NO_MEMORY);
340
341 int rc = audioTestManifestWriteData(pSet, psz, strlen(psz));
342 AssertRC(rc);
343
344 RTStrFree(psz);
345
346 return rc;
347}
348
349/**
350 * Writes a string to a test set manifest.
351 * Convenience function.
352 *
353 * @returns VBox status code.
354 * @param pSet Test set to write manifest for.
355 * @param pszFormat Format string to write.
356 * @param ... Variable arguments for \a pszFormat. Optional.
357 */
358static int audioTestManifestWrite(PAUDIOTESTSET pSet, const char *pszFormat, ...)
359{
360 va_list va;
361 va_start(va, pszFormat);
362
363 int rc = audioTestManifestWriteV(pSet, pszFormat, va);
364 AssertRC(rc);
365
366 va_end(va);
367
368 return rc;
369}
370
371/**
372 * Returns the current read/write offset (in bytes) of the opened manifest file.
373 *
374 * @returns Current read/write offset (in bytes).
375 * @param pSet Set to return offset for.
376 * Must have an opened manifest file.
377 */
378DECLINLINE(uint64_t) audioTestManifestGetOffsetAbs(PAUDIOTESTSET pSet)
379{
380 AssertReturn(RTFileIsValid(pSet->f.hFile), 0);
381 return RTFileTell(pSet->f.hFile);
382}
383
384/**
385 * Writes a section header to a test set manifest.
386 *
387 * @returns VBox status code.
388 * @param pSet Test set to write manifest for.
389 * @param pszSection Format string of section to write.
390 * @param ... Variable arguments for \a pszSection. Optional.
391 */
392static int audioTestManifestWriteSectionHdr(PAUDIOTESTSET pSet, const char *pszSection, ...)
393{
394 va_list va;
395 va_start(va, pszSection);
396
397 /** @todo Keep it as simple as possible for now. Improve this later. */
398 int rc = audioTestManifestWrite(pSet, "[");
399 AssertRC(rc);
400
401 rc = audioTestManifestWriteV(pSet, pszSection, va);
402 AssertRC(rc);
403
404 rc = audioTestManifestWrite(pSet, "]\n");
405 AssertRC(rc);
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 Absolute path to use for the test set's temporary directory.
607 * If NULL, the OS' 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 if (pszPath)
618 {
619 rc = RTStrCopy(pSet->szPathAbs, sizeof(pSet->szPathAbs), pszPath);
620 AssertRCReturn(rc, rc);
621
622 rc = AudioTestPathCreate(pSet->szPathAbs, sizeof(pSet->szPathAbs), pSet->szTag);
623 }
624 else
625 rc = AudioTestPathCreateTemp(pSet->szPathAbs, sizeof(pSet->szPathAbs), pSet->szTag);
626 AssertRCReturn(rc, rc);
627
628 if (RT_SUCCESS(rc))
629 {
630 char szManifest[RTPATH_MAX];
631 rc = RTStrCopy(szManifest, sizeof(szManifest), pSet->szPathAbs);
632 AssertRCReturn(rc, rc);
633
634 rc = RTPathAppend(szManifest, sizeof(szManifest), AUDIOTEST_MANIFEST_FILE_STR);
635 AssertRCReturn(rc, rc);
636
637 rc = RTFileOpen(&pSet->f.hFile, szManifest,
638 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 char szVal[64];
652 RTTIMESPEC time;
653 if (!RTTimeSpecToString(RTTimeNow(&time), szVal, sizeof(szVal)))
654 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
655
656 rc = audioTestManifestWrite(pSet, "date_created=%s\n", szVal);
657 AssertRCReturn(rc, rc);
658
659 rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szVal, sizeof(szVal));
660 AssertRCReturn(rc, rc);
661 rc = audioTestManifestWrite(pSet, "os_product=%s\n", szVal);
662 AssertRCReturn(rc, rc);
663 rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szVal, sizeof(szVal));
664 AssertRCReturn(rc, rc);
665 rc = audioTestManifestWrite(pSet, "os_rel=%s\n", szVal);
666 AssertRCReturn(rc, rc);
667 rc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szVal, sizeof(szVal));
668 AssertRCReturn(rc, rc);
669 rc = audioTestManifestWrite(pSet, "os_ver=%s\n", szVal);
670 AssertRCReturn(rc, rc);
671 rc = audioTestManifestWrite(pSet, "vbox_ver=%s r%u %s (%s %s)\n",
672 VBOX_VERSION_STRING, RTBldCfgRevision(),
673 RTBldCfgTargetDotArch(), __DATE__, __TIME__);
674 AssertRCReturn(rc, rc);
675
676 rc = audioTestManifestWrite(pSet, "test_count=");
677 AssertRCReturn(rc, rc);
678 pSet->offTestCount = audioTestManifestGetOffsetAbs(pSet);
679 rc = audioTestManifestWrite(pSet, "0000\n"); /* A bit messy, but does the trick for now. */
680 AssertRCReturn(rc, rc);
681
682 pSet->enmMode = AUDIOTESTSETMODE_TEST;
683 }
684
685 return rc;
686}
687
688/**
689 * Destroys a test set.
690 *
691 * @returns VBox status code.
692 * @param pSet Test set to destroy.
693 */
694int AudioTestSetDestroy(PAUDIOTESTSET pSet)
695{
696 if (!pSet)
697 return VINF_SUCCESS;
698
699 AssertReturn(pSet->cTestsRunning == 0, VERR_WRONG_ORDER); /* Make sure no tests sill are running. */
700
701 int rc = AudioTestSetClose(pSet);
702 if (RT_FAILURE(rc))
703 return rc;
704
705 PAUDIOTESTOBJ pObj, pObjNext;
706 RTListForEachSafe(&pSet->lstObj, pObj, pObjNext, AUDIOTESTOBJ, Node)
707 {
708 rc = AudioTestSetObjClose(pObj);
709 if (RT_SUCCESS(rc))
710 {
711 RTListNodeRemove(&pObj->Node);
712 RTMemFree(pObj);
713
714 Assert(pSet->cObj);
715 pSet->cObj--;
716 }
717 else
718 break;
719 }
720
721 if (RT_FAILURE(rc))
722 return rc;
723
724 Assert(pSet->cObj == 0);
725
726 PAUDIOTESTENTRY pEntry, pEntryNext;
727 RTListForEachSafe(&pSet->lstTest, pEntry, pEntryNext, AUDIOTESTENTRY, Node)
728 {
729 RTListNodeRemove(&pEntry->Node);
730 RTMemFree(pEntry);
731
732 Assert(pSet->cTests);
733 pSet->cTests--;
734 }
735
736 if (RT_FAILURE(rc))
737 return rc;
738
739 Assert(pSet->cTests == 0);
740
741 return rc;
742}
743
744/**
745 * Opens an existing audio test set.
746 *
747 * @returns VBox status code.
748 * @param pSet Test set to open.
749 * @param pszPath Absolute path of the test set to open.
750 */
751int AudioTestSetOpen(PAUDIOTESTSET pSet, const char *pszPath)
752{
753 audioTestSetInitInternal(pSet);
754
755 char szManifest[RTPATH_MAX];
756 int rc = RTPathJoin(szManifest, sizeof(szManifest), pszPath, AUDIOTEST_MANIFEST_FILE_STR);
757 AssertRCReturn(rc, rc);
758
759 RTVFSFILE hVfsFile;
760 rc = RTVfsFileOpenNormal(szManifest, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hVfsFile);
761 if (RT_FAILURE(rc))
762 return rc;
763
764 rc = RTIniFileCreateFromVfsFile(&pSet->f.hIniFile, hVfsFile, RTINIFILE_F_READONLY);
765 RTVfsFileRelease(hVfsFile);
766 AssertRCReturn(rc, rc);
767
768 pSet->enmMode = AUDIOTESTSETMODE_VERIFY;
769
770 return rc;
771}
772
773/**
774 * Closes an opened audio test set.
775 *
776 * @returns VBox status code.
777 * @param pSet Test set to close.
778 */
779int AudioTestSetClose(PAUDIOTESTSET pSet)
780{
781 if (!pSet)
782 return VINF_SUCCESS;
783
784 if (!RTFileIsValid(pSet->f.hFile))
785 return VINF_SUCCESS;
786
787 /* Update number of ran tests. */
788 int rc = RTFileSeek(pSet->f.hFile, pSet->offTestCount, RTFILE_SEEK_BEGIN, NULL);
789 AssertRCReturn(rc, rc);
790 rc = audioTestManifestWrite(pSet, "%04RU32", pSet->cTests);
791 AssertRCReturn(rc, rc);
792
793 RTFileClose(pSet->f.hFile);
794 pSet->f.hFile = NIL_RTFILE;
795
796 return rc;
797}
798
799/**
800 * Physically wipes all related test set files off the disk.
801 *
802 * @returns VBox status code.
803 * @param pSet Test set to wipe.
804 */
805int AudioTestSetWipe(PAUDIOTESTSET pSet)
806{
807 AssertPtrReturn(pSet, VERR_INVALID_POINTER);
808
809 int rc = VINF_SUCCESS;
810 char szFilePath[RTPATH_MAX];
811
812 PAUDIOTESTOBJ pObj;
813 RTListForEach(&pSet->lstObj, pObj, AUDIOTESTOBJ, Node)
814 {
815 int rc2 = AudioTestSetObjClose(pObj);
816 if (RT_SUCCESS(rc2))
817 {
818 rc2 = audioTestSetGetObjPath(pSet, szFilePath, sizeof(szFilePath), pObj->szName);
819 if (RT_SUCCESS(rc2))
820 rc2 = RTFileDelete(szFilePath);
821 }
822
823 if (RT_SUCCESS(rc))
824 rc = rc2;
825 /* Keep going. */
826 }
827
828 if (RT_SUCCESS(rc))
829 {
830 rc = RTPathJoin(szFilePath, sizeof(szFilePath), pSet->szPathAbs, AUDIOTEST_MANIFEST_FILE_STR);
831 if (RT_SUCCESS(rc))
832 rc = RTFileDelete(szFilePath);
833 }
834
835 /* Remove the (hopefully now empty) directory. Otherwise let this fail. */
836 if (RT_SUCCESS(rc))
837 rc = RTDirRemove(pSet->szPathAbs);
838
839 return rc;
840}
841
842/**
843 * Creates and registers a new audio test object to a test set.
844 *
845 * @returns VBox status code.
846 * @param pSet Test set to create and register new object for.
847 * @param pszName Name of new object to create.
848 * @param ppObj Where to return the pointer to the newly created object on success.
849 */
850int AudioTestSetObjCreateAndRegister(PAUDIOTESTSET pSet, const char *pszName, PAUDIOTESTOBJ *ppObj)
851{
852 AssertPtrReturn(pszName, VERR_INVALID_POINTER);
853
854 PAUDIOTESTOBJ pObj = (PAUDIOTESTOBJ)RTMemAlloc(sizeof(AUDIOTESTOBJ));
855 AssertPtrReturn(pObj, VERR_NO_MEMORY);
856
857 if (RTStrPrintf2(pObj->szName, sizeof(pObj->szName), "%04RU32-%s", pSet->cObj, pszName) <= 0)
858 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
859
860 /** @todo Generalize this function more once we have more object types. */
861
862 char szObjPathAbs[RTPATH_MAX];
863 int rc = audioTestSetGetObjPath(pSet, szObjPathAbs, sizeof(szObjPathAbs), pObj->szName);
864 if (RT_SUCCESS(rc))
865 {
866 rc = RTFileOpen(&pObj->File.hFile, szObjPathAbs, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
867 if (RT_SUCCESS(rc))
868 {
869 pObj->enmType = AUDIOTESTOBJTYPE_FILE;
870
871 RTListAppend(&pSet->lstObj, &pObj->Node);
872 pSet->cObj++;
873
874 *ppObj = pObj;
875 }
876 }
877
878 if (RT_FAILURE(rc))
879 RTMemFree(pObj);
880
881 return rc;
882}
883
884/**
885 * Writes to a created audio test object.
886 *
887 * @returns VBox status code.
888 * @param pObj Audio test object to write to.
889 * @param pvBuf Pointer to data to write.
890 * @param cbBuf Size (in bytes) of \a pvBuf to write.
891 */
892int AudioTestSetObjWrite(PAUDIOTESTOBJ pObj, void *pvBuf, size_t cbBuf)
893{
894 /** @todo Generalize this function more once we have more object types. */
895 AssertReturn(pObj->enmType == AUDIOTESTOBJTYPE_FILE, VERR_INVALID_PARAMETER);
896
897 return RTFileWrite(pObj->File.hFile, pvBuf, cbBuf, NULL);
898}
899
900/**
901 * Closes an opened audio test object.
902 *
903 * @returns VBox status code.
904 * @param pObj Audio test object to close.
905 */
906int AudioTestSetObjClose(PAUDIOTESTOBJ pObj)
907{
908 if (!pObj)
909 return VINF_SUCCESS;
910
911 /** @todo Generalize this function more once we have more object types. */
912 AssertReturn(pObj->enmType == AUDIOTESTOBJTYPE_FILE, VERR_INVALID_PARAMETER);
913
914 int rc = VINF_SUCCESS;
915
916 if (RTFileIsValid(pObj->File.hFile))
917 {
918 rc = RTFileClose(pObj->File.hFile);
919 pObj->File.hFile = NIL_RTFILE;
920 }
921
922 return rc;
923}
924
925int AudioTestSetTestBegin(PAUDIOTESTSET pSet, const char *pszDesc, PAUDIOTESTPARMS pParms, PAUDIOTESTENTRY *ppEntry)
926{
927 AssertReturn(pSet->cTestsRunning == 0, VERR_WRONG_ORDER); /* No test nesting allowed. */
928
929 PAUDIOTESTENTRY pEntry = (PAUDIOTESTENTRY)RTMemAllocZ(sizeof(AUDIOTESTENTRY));
930 AssertPtrReturn(pEntry, VERR_NO_MEMORY);
931
932 int rc = RTStrCopy(pEntry->szDesc, sizeof(pEntry->szDesc), pszDesc);
933 AssertRCReturn(rc, rc);
934
935 memcpy(&pEntry->Parms, pParms, sizeof(AUDIOTESTPARMS));
936 pEntry->pParent = pSet;
937
938 rc = audioTestManifestWrite(pSet, "\n");
939 AssertRCReturn(rc, rc);
940
941 rc = audioTestManifestWriteSectionHdr(pSet, "test%04RU32", pSet->cTests);
942 AssertRCReturn(rc, rc);
943 rc = audioTestManifestWrite(pSet, "test_desc=%s\n", pszDesc);
944 AssertRCReturn(rc, rc);
945 rc = audioTestManifestWrite(pSet, "test_type=%RU32\n", pParms->enmType);
946 AssertRCReturn(rc, rc);
947 rc = audioTestManifestWrite(pSet, "test_iterations=%RU32\n", pParms->cIterations);
948 AssertRCReturn(rc, rc);
949 rc = audioTestManifestWrite(pSet, "test_delay_ms=%RU32\n", pParms->msDelay);
950 AssertRCReturn(rc, rc);
951 rc = audioTestManifestWrite(pSet, "audio_direction=%s\n", PDMAudioDirGetName(pParms->enmDir));
952 AssertRCReturn(rc, rc);
953
954 switch (pParms->enmType)
955 {
956 case AUDIOTESTTYPE_TESTTONE:
957 {
958 rc = audioTestManifestWrite(pSet, "tone_prequel_ms=%RU32\n", pParms->TestTone.msPrequel);
959 AssertRCReturn(rc, rc);
960 rc = audioTestManifestWrite(pSet, "tone_duration_ms=%RU32\n", pParms->TestTone.msDuration);
961 AssertRCReturn(rc, rc);
962 rc = audioTestManifestWrite(pSet, "tone_sequel_ms=%RU32\n", pParms->TestTone.msSequel);
963 AssertRCReturn(rc, rc);
964 rc = audioTestManifestWrite(pSet, "tone_volume_percent=%RU32\n", pParms->TestTone.uVolumePercent);
965 AssertRCReturn(rc, rc);
966 rc = audioTestManifestWrite(pSet, "tone_pcm_hz=%RU32\n", PDMAudioPropsHz(&pParms->TestTone.Props));
967 AssertRCReturn(rc, rc);
968 rc = audioTestManifestWrite(pSet, "tone_pcm_channels=%RU8\n", PDMAudioPropsChannels(&pParms->TestTone.Props));
969 AssertRCReturn(rc, rc);
970 rc = audioTestManifestWrite(pSet, "tone_pcm_bits=%RU8\n", PDMAudioPropsSampleBits(&pParms->TestTone.Props));
971 AssertRCReturn(rc, rc);
972 rc = audioTestManifestWrite(pSet, "tone_pcm_is_signed=%RTbool\n", PDMAudioPropsIsSigned(&pParms->TestTone.Props));
973 AssertRCReturn(rc, rc);
974 break;
975 }
976
977 default:
978 AssertFailed();
979 break;
980 }
981
982 RTListAppend(&pSet->lstTest, &pEntry->Node);
983 pSet->cTests++;
984 pSet->cTestsRunning++;
985
986 *ppEntry = pEntry;
987
988 return rc;
989}
990
991int AudioTestSetTestFailed(PAUDIOTESTENTRY pEntry, int rc, const char *pszErr)
992{
993 AssertReturn(pEntry->pParent->cTestsRunning == 1, VERR_WRONG_ORDER); /* No test nesting allowed. */
994 AssertReturn(pEntry->rc == VINF_SUCCESS, VERR_WRONG_ORDER);
995
996 pEntry->rc = rc;
997
998 int rc2 = audioTestManifestWrite(pEntry->pParent, "error_rc=%RI32\n", rc);
999 AssertRCReturn(rc2, rc2);
1000 rc2 = audioTestManifestWrite(pEntry->pParent, "error_desc=%s", pszErr);
1001 AssertRCReturn(rc2, rc2);
1002
1003 pEntry->pParent->cTestsRunning--;
1004
1005 return rc2;
1006}
1007
1008int AudioTestSetTestDone(PAUDIOTESTENTRY pEntry)
1009{
1010 AssertReturn(pEntry->pParent->cTestsRunning == 1, VERR_WRONG_ORDER); /* No test nesting allowed. */
1011 AssertReturn(pEntry->rc == VINF_SUCCESS, VERR_WRONG_ORDER);
1012
1013 int rc2 = audioTestManifestWrite(pEntry->pParent, "error_rc=%RI32\n", VINF_SUCCESS);
1014 AssertRCReturn(rc2, rc2);
1015
1016 pEntry->pParent->cTestsRunning--;
1017
1018 return rc2;
1019}
1020
1021/**
1022 * Packs an audio test so that it's ready for transmission.
1023 *
1024 * @returns VBox status code.
1025 * @param pSet Test set to pack.
1026 * @param pszOutDir Directory where to store the packed test set.
1027 * @param pszFileName Where to return the final name of the packed test set. Optional and can be NULL.
1028 * @param cbFileName Size (in bytes) of \a pszFileName.
1029 */
1030int AudioTestSetPack(PAUDIOTESTSET pSet, const char *pszOutDir, char *pszFileName, size_t cbFileName)
1031{
1032 AssertReturn(!pszFileName || cbFileName, VERR_INVALID_PARAMETER);
1033 AssertReturn(audioTestManifestIsOpen(pSet), VERR_WRONG_ORDER);
1034
1035 /** @todo Check and deny if \a pszOutDir is part of the set's path. */
1036
1037 char szOutName[RT_ELEMENTS(AUDIOTEST_PATH_PREFIX_STR) + AUDIOTEST_TAG_MAX + 16];
1038 if (RTStrPrintf2(szOutName, sizeof(szOutName), "%s-%s%s",
1039 AUDIOTEST_PATH_PREFIX_STR, pSet->szTag, AUDIOTEST_ARCHIVE_SUFF_STR) <= 0)
1040 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
1041
1042 char szOutPath[RTPATH_MAX];
1043 int rc = RTPathJoin(szOutPath, sizeof(szOutPath), pszOutDir, szOutName);
1044 AssertRCReturn(rc, rc);
1045
1046 const char *apszArgs[10];
1047 unsigned cArgs = 0;
1048
1049 apszArgs[cArgs++] = "AudioTest";
1050 apszArgs[cArgs++] = "--create";
1051 apszArgs[cArgs++] = "--gzip";
1052 apszArgs[cArgs++] = "--directory";
1053 apszArgs[cArgs++] = pSet->szPathAbs;
1054 apszArgs[cArgs++] = "--file";
1055 apszArgs[cArgs++] = szOutPath;
1056 apszArgs[cArgs++] = ".";
1057
1058 RTEXITCODE rcExit = RTZipTarCmd(cArgs, (char **)apszArgs);
1059 if (rcExit != RTEXITCODE_SUCCESS)
1060 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1061
1062 if (RT_SUCCESS(rc))
1063 {
1064 if (pszFileName)
1065 rc = RTStrCopy(pszFileName, cbFileName, szOutPath);
1066 }
1067
1068 return rc;
1069}
1070
1071/**
1072 * Returns whether a test set archive is packed (as .tar.gz by default) or
1073 * a plain directory.
1074 *
1075 * @returns \c true if packed (as .tar.gz), or \c false if not (directory).
1076 * @param pszPath Path to return packed staus for.
1077 */
1078bool AudioTestSetIsPacked(const char *pszPath)
1079{
1080 /** @todo Improve this, good enough for now. */
1081 return (RTStrIStr(pszPath, AUDIOTEST_ARCHIVE_SUFF_STR) != NULL);
1082}
1083
1084/**
1085 * Unpacks a formerly packed audio test set.
1086 *
1087 * @returns VBox status code.
1088 * @param pszFile Test set file to unpack. Must contain the absolute path.
1089 * @param pszOutDir Directory where to unpack the test set into.
1090 * If the directory does not exist it will be created.
1091 */
1092int AudioTestSetUnpack(const char *pszFile, const char *pszOutDir)
1093{
1094 AssertReturn(pszFile && pszOutDir, VERR_INVALID_PARAMETER);
1095
1096 int rc = VINF_SUCCESS;
1097
1098 if (!RTDirExists(pszOutDir))
1099 {
1100 rc = RTDirCreateFullPath(pszOutDir, 0755);
1101 if (RT_FAILURE(rc))
1102 return rc;
1103 }
1104
1105 const char *apszArgs[8];
1106 unsigned cArgs = 0;
1107
1108 apszArgs[cArgs++] = "AudioTest";
1109 apszArgs[cArgs++] = "--extract";
1110 apszArgs[cArgs++] = "--gunzip";
1111 apszArgs[cArgs++] = "--directory";
1112 apszArgs[cArgs++] = pszOutDir;
1113 apszArgs[cArgs++] = "--file";
1114 apszArgs[cArgs++] = pszFile;
1115
1116 RTEXITCODE rcExit = RTZipTarCmd(cArgs, (char **)apszArgs);
1117 if (rcExit != RTEXITCODE_SUCCESS)
1118 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1119
1120 return rc;
1121}
1122
1123/**
1124 * Verifies an opened audio test set.
1125 *
1126 * @returns VBox status code.
1127 * @param pSet Test set to verify.
1128 * @param pszTag Tag to use for verification purpose.
1129 * @param pErrDesc Where to return the test verification errors.
1130 *
1131 * @note Test verification errors have to be checked for errors, regardless of the
1132 * actual return code.
1133 */
1134int AudioTestSetVerify(PAUDIOTESTSET pSet, const char *pszTag, PAUDIOTESTERRORDESC pErrDesc)
1135{
1136 AssertReturn(audioTestManifestIsOpen(pSet), VERR_WRONG_ORDER);
1137
1138 /* We ASSUME the caller has not init'd pErrDesc. */
1139 audioTestErrorDescInit(pErrDesc);
1140
1141 char szVal[_1K]; /** @todo Enough, too much? */
1142
1143 int rc2 = RTIniFileQueryValue(pSet->f.hIniFile, AUDIOTEST_INI_SEC_HDR_STR, "tag", szVal, sizeof(szVal), NULL);
1144 if ( RT_FAILURE(rc2)
1145 || RTStrICmp(pszTag, szVal))
1146 audioTestErrorDescAdd(pErrDesc, "Tag '%s' does not match with manifest's tag '%s'", pszTag, szVal);
1147
1148 /* Only return critical stuff not related to actual testing here. */
1149 return VINF_SUCCESS;
1150}
1151
1152
1153/*********************************************************************************************************************************
1154* WAVE File Reader. *
1155*********************************************************************************************************************************/
1156/**
1157 * Opens a wave (.WAV) file for reading.
1158 *
1159 * @returns VBox status code.
1160 * @param pszFile The file to open.
1161 * @param pWaveFile The open wave file structure to fill in on success.
1162 */
1163int AudioTestWaveFileOpen(const char *pszFile, PAUDIOTESTWAVEFILE pWaveFile)
1164{
1165 RT_ZERO(pWaveFile->Props);
1166 pWaveFile->hFile = NIL_RTFILE;
1167 int rc = RTFileOpen(&pWaveFile->hFile, pszFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
1168 if (RT_FAILURE(rc))
1169 return rc;
1170 uint64_t cbFile = 0;
1171 rc = RTFileQuerySize(pWaveFile->hFile, &cbFile);
1172 if (RT_SUCCESS(rc))
1173 {
1174 union
1175 {
1176 uint8_t ab[512];
1177 struct
1178 {
1179 RTRIFFHDR Hdr;
1180 RTRIFFWAVEFMTCHUNK Fmt;
1181 } Wave;
1182 RTRIFFLIST List;
1183 RTRIFFCHUNK Chunk;
1184 RTRIFFWAVEDATACHUNK Data;
1185 } uBuf;
1186
1187 rc = RTFileRead(pWaveFile->hFile, &uBuf.Wave, sizeof(uBuf.Wave), NULL);
1188 if (RT_SUCCESS(rc))
1189 {
1190 rc = VERR_VFS_UNKNOWN_FORMAT;
1191 if ( uBuf.Wave.Hdr.uMagic == RTRIFFHDR_MAGIC
1192 && uBuf.Wave.Hdr.uFileType == RTRIFF_FILE_TYPE_WAVE
1193 && uBuf.Wave.Fmt.Chunk.uMagic == RTRIFFWAVEFMT_MAGIC
1194 && uBuf.Wave.Fmt.Chunk.cbChunk >= sizeof(uBuf.Wave.Fmt.Data))
1195 {
1196 if (uBuf.Wave.Hdr.cbFile != cbFile - sizeof(RTRIFFCHUNK))
1197 RTMsgWarning("%s: File size mismatch: %#x, actual %#RX64 (ignored)",
1198 pszFile, uBuf.Wave.Hdr.cbFile, cbFile - sizeof(RTRIFFCHUNK));
1199 rc = VERR_VFS_BOGUS_FORMAT;
1200 if (uBuf.Wave.Fmt.Data.uFormatTag != RTRIFFWAVEFMT_TAG_PCM)
1201 RTMsgError("%s: Unsupported uFormatTag value: %u (expected 1)", pszFile, uBuf.Wave.Fmt.Data.uFormatTag);
1202 else if ( uBuf.Wave.Fmt.Data.cBitsPerSample != 8
1203 && uBuf.Wave.Fmt.Data.cBitsPerSample != 16
1204 && uBuf.Wave.Fmt.Data.cBitsPerSample != 32)
1205 RTMsgError("%s: Unsupported cBitsPerSample value: %u", pszFile, uBuf.Wave.Fmt.Data.cBitsPerSample);
1206 else if ( uBuf.Wave.Fmt.Data.cChannels < 1
1207 || uBuf.Wave.Fmt.Data.cChannels >= 16)
1208 RTMsgError("%s: Unsupported cChannels value: %u (expected 1..15)", pszFile, uBuf.Wave.Fmt.Data.cChannels);
1209 else if ( uBuf.Wave.Fmt.Data.uHz < 4096
1210 || uBuf.Wave.Fmt.Data.uHz > 768000)
1211 RTMsgError("%s: Unsupported uHz value: %u (expected 4096..768000)", pszFile, uBuf.Wave.Fmt.Data.uHz);
1212 else if (uBuf.Wave.Fmt.Data.cbFrame != uBuf.Wave.Fmt.Data.cChannels * uBuf.Wave.Fmt.Data.cBitsPerSample / 8)
1213 RTMsgError("%s: Invalid cbFrame value: %u (expected %u)", pszFile, uBuf.Wave.Fmt.Data.cbFrame,
1214 uBuf.Wave.Fmt.Data.cChannels * uBuf.Wave.Fmt.Data.cBitsPerSample / 8);
1215 else if (uBuf.Wave.Fmt.Data.cbRate != uBuf.Wave.Fmt.Data.cbFrame * uBuf.Wave.Fmt.Data.uHz)
1216 RTMsgError("%s: Invalid cbRate value: %u (expected %u)", pszFile, uBuf.Wave.Fmt.Data.cbRate,
1217 uBuf.Wave.Fmt.Data.cbFrame * uBuf.Wave.Fmt.Data.uHz);
1218 else
1219 {
1220 /*
1221 * Copy out the data we need from the file format structure.
1222 */
1223 PDMAudioPropsInit(&pWaveFile->Props, uBuf.Wave.Fmt.Data.cBitsPerSample / 8, true /*fSigned*/,
1224 uBuf.Wave.Fmt.Data.cChannels, uBuf.Wave.Fmt.Data.uHz);
1225 pWaveFile->offSamples = sizeof(RTRIFFHDR) + sizeof(RTRIFFCHUNK) + uBuf.Wave.Fmt.Chunk.cbChunk;
1226
1227 /*
1228 * Find the 'data' chunk with the audio samples.
1229 *
1230 * There can be INFO lists both preceeding this and succeeding
1231 * it, containing IART and other things we can ignored. Thus
1232 * we read a list header here rather than just a chunk header,
1233 * since it doesn't matter if we read 4 bytes extra as
1234 * AudioTestWaveFileRead uses RTFileReadAt anyway.
1235 */
1236 rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
1237 for (uint32_t i = 0;
1238 i < 128
1239 && RT_SUCCESS(rc)
1240 && uBuf.Chunk.uMagic != RTRIFFWAVEDATACHUNK_MAGIC
1241 && (uint64_t)uBuf.Chunk.cbChunk + sizeof(RTRIFFCHUNK) * 2 <= cbFile - pWaveFile->offSamples;
1242 i++)
1243 {
1244 if ( uBuf.List.uMagic == RTRIFFLIST_MAGIC
1245 && uBuf.List.uListType == RTRIFFLIST_TYPE_INFO)
1246 { /*skip*/ }
1247 else if (uBuf.Chunk.uMagic == RTRIFFPADCHUNK_MAGIC)
1248 { /*skip*/ }
1249 else
1250 break;
1251 pWaveFile->offSamples += sizeof(RTRIFFCHUNK) + uBuf.Chunk.cbChunk;
1252 rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
1253 }
1254 if (RT_SUCCESS(rc))
1255 {
1256 pWaveFile->offSamples += sizeof(uBuf.Data.Chunk);
1257 pWaveFile->cbSamples = (uint32_t)cbFile - pWaveFile->offSamples;
1258
1259 rc = VERR_VFS_BOGUS_FORMAT;
1260 if ( uBuf.Data.Chunk.uMagic == RTRIFFWAVEDATACHUNK_MAGIC
1261 && uBuf.Data.Chunk.cbChunk <= pWaveFile->cbSamples
1262 && PDMAudioPropsIsSizeAligned(&pWaveFile->Props, uBuf.Data.Chunk.cbChunk))
1263 {
1264 pWaveFile->cbSamples = uBuf.Data.Chunk.cbChunk;
1265
1266 /*
1267 * We're good!
1268 */
1269 pWaveFile->offCur = 0;
1270 return VINF_SUCCESS;
1271 }
1272
1273 RTMsgError("%s: Bad data header: uMagic=%#x (expected %#x), cbChunk=%#x (max %#RX64, align %u)",
1274 pszFile, uBuf.Data.Chunk.uMagic, RTRIFFWAVEDATACHUNK_MAGIC,
1275 uBuf.Data.Chunk.cbChunk, pWaveFile->cbSamples, PDMAudioPropsFrameSize(&pWaveFile->Props));
1276 }
1277 else
1278 RTMsgError("%s: Failed to read data header: %Rrc", pszFile, rc);
1279 }
1280 }
1281 else
1282 RTMsgError("%s: Bad file header: uMagic=%#x (vs. %#x), uFileType=%#x (vs %#x), uFmtMagic=%#x (vs %#x) cbFmtChunk=%#x (min %#x)",
1283 pszFile, uBuf.Wave.Hdr.uMagic, RTRIFFHDR_MAGIC, uBuf.Wave.Hdr.uFileType, RTRIFF_FILE_TYPE_WAVE,
1284 uBuf.Wave.Fmt.Chunk.uMagic, RTRIFFWAVEFMT_MAGIC,
1285 uBuf.Wave.Fmt.Chunk.cbChunk, sizeof(uBuf.Wave.Fmt.Data));
1286 }
1287 else
1288 RTMsgError("%s: Failed to read file header: %Rrc", pszFile, rc);
1289 }
1290 else
1291 RTMsgError("%s: Failed to query file size: %Rrc", pszFile, rc);
1292
1293 RTFileClose(pWaveFile->hFile);
1294 pWaveFile->hFile = NIL_RTFILE;
1295 return rc;
1296}
1297
1298/**
1299 * Closes a wave file.
1300 */
1301void AudioTestWaveFileClose(PAUDIOTESTWAVEFILE pWaveFile)
1302{
1303 RTFileClose(pWaveFile->hFile);
1304 pWaveFile->hFile = NIL_RTFILE;
1305}
1306
1307/**
1308 * Reads samples from a wave file.
1309 *
1310 * @returns VBox status code. See RTVfsFileRead for EOF status handling.
1311 * @param pWaveFile The file to read from.
1312 * @param pvBuf Where to put the samples.
1313 * @param cbBuf How much to read at most.
1314 * @param pcbRead Where to return the actual number of bytes read,
1315 * optional.
1316 */
1317int AudioTestWaveFileRead(PAUDIOTESTWAVEFILE pWaveFile, void *pvBuf, size_t cbBuf, size_t *pcbRead)
1318{
1319 bool fEofAdjusted;
1320 if (pWaveFile->offCur + cbBuf <= pWaveFile->cbSamples)
1321 fEofAdjusted = false;
1322 else if (pcbRead)
1323 {
1324 fEofAdjusted = true;
1325 cbBuf = pWaveFile->cbSamples - pWaveFile->offCur;
1326 }
1327 else
1328 return VERR_EOF;
1329
1330 int rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples + pWaveFile->offCur, pvBuf, cbBuf, pcbRead);
1331 if (RT_SUCCESS(rc))
1332 {
1333 if (pcbRead)
1334 {
1335 pWaveFile->offCur += (uint32_t)*pcbRead;
1336 if (fEofAdjusted || cbBuf > *pcbRead)
1337 rc = VINF_EOF;
1338 else if (!cbBuf && pWaveFile->offCur == pWaveFile->cbSamples)
1339 rc = VINF_EOF;
1340 }
1341 else
1342 pWaveFile->offCur += (uint32_t)cbBuf;
1343 }
1344 return rc;
1345}
1346
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