VirtualBox

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

最後變更 在這個檔案從89984是 89984,由 vboxsync 提交於 4 年 前

Audio/ValKit: More code for test (set) verification. bugref:10008

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette