VirtualBox

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

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

AudioTest.cpp: Build fix (gcc (GCC) 10.2.1 20201125 (Red Hat 10.2.1-9)). bugref:10008

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