VirtualBox

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

Last change on this file since 91483 was 91178, checked in by vboxsync, 3 years ago

Audio/Validation Kit: Added some more audio test verification options utility functions. ​bugref:10008

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 99.4 KB
Line 
1/* $Id: AudioTest.cpp 91178 2021-09-09 11:56:14Z 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#define LOG_GROUP LOG_GROUP_AUDIO_TEST
48#include <VBox/log.h>
49
50#include <VBox/version.h>
51#include <VBox/vmm/pdmaudioifs.h>
52#include <VBox/vmm/pdmaudioinline.h>
53
54#include "AudioTest.h"
55
56
57/*********************************************************************************************************************************
58* Defines *
59*********************************************************************************************************************************/
60/** The test manifest file name. */
61#define AUDIOTEST_MANIFEST_FILE_STR "vkat_manifest.ini"
62/** The current test manifest version. */
63#define AUDIOTEST_MANIFEST_VER 1
64/** Audio test archive default suffix.
65 * According to IPRT terminology this always contains the dot. */
66#define AUDIOTEST_ARCHIVE_SUFF_STR ".tar.gz"
67
68/** Test manifest header name. */
69#define AUDIOTEST_SEC_HDR_STR "header"
70/** Maximum section name length (in UTF-8 characters). */
71#define AUDIOTEST_MAX_SEC_LEN 128
72/** Maximum object name length (in UTF-8 characters). */
73#define AUDIOTEST_MAX_OBJ_LEN 128
74
75
76/*********************************************************************************************************************************
77* Structures and Typedefs *
78*********************************************************************************************************************************/
79/**
80 * Enumeration for an audio test object type.
81 */
82typedef enum AUDIOTESTOBJTYPE
83{
84 /** Unknown / invalid, do not use. */
85 AUDIOTESTOBJTYPE_UNKNOWN = 0,
86 /** The test object is a file. */
87 AUDIOTESTOBJTYPE_FILE,
88 /** The usual 32-bit hack. */
89 AUDIOTESTOBJTYPE_32BIT_HACK = 0x7fffffff
90} AUDIOTESTOBJTYPE;
91
92/**
93 * Structure for keeping an audio test object file.
94 */
95typedef struct AUDIOTESTOBJFILE
96{
97 /** File handle. */
98 RTFILE hFile;
99 /** Total size (in bytes). */
100 size_t cbSize;
101} AUDIOTESTOBJFILE;
102/** Pointer to an audio test object file. */
103typedef AUDIOTESTOBJFILE *PAUDIOTESTOBJFILE;
104
105/**
106 * Enumeration for an audio test object meta data type.
107 */
108typedef enum AUDIOTESTOBJMETADATATYPE
109{
110 /** Unknown / invalid, do not use. */
111 AUDIOTESTOBJMETADATATYPE_INVALID = 0,
112 /** Meta data is an UTF-8 string. */
113 AUDIOTESTOBJMETADATATYPE_STRING,
114 /** The usual 32-bit hack. */
115 AUDIOTESTOBJMETADATATYPE_32BIT_HACK = 0x7fffffff
116} AUDIOTESTOBJMETADATATYPE;
117
118/**
119 * Structure for keeping a meta data block.
120 */
121typedef struct AUDIOTESTOBJMETA
122{
123 /** List node. */
124 RTLISTNODE Node;
125 /** Meta data type. */
126 AUDIOTESTOBJMETADATATYPE enmType;
127 /** Meta data block. */
128 void *pvMeta;
129 /** Size (in bytes) of \a pvMeta. */
130 size_t cbMeta;
131} AUDIOTESTOBJMETA;
132/** Pointer to an audio test object file. */
133typedef AUDIOTESTOBJMETA *PAUDIOTESTOBJMETA;
134
135/**
136 * Structure for keeping a single audio test object.
137 *
138 * A test object is data which is needed in order to perform and verify one or
139 * more audio test case(s).
140 */
141typedef struct AUDIOTESTOBJINT
142{
143 /** List node. */
144 RTLISTNODE Node;
145 /** Pointer to test set this handle is bound to. */
146 PAUDIOTESTSET pSet;
147 /** As we only support .INI-style files for now, this only has the object's section name in it. */
148 /** @todo Make this more generic (union, ++). */
149 char szSec[AUDIOTEST_MAX_SEC_LEN];
150 /** The UUID of the object.
151 * Used to identify an object within a test set. */
152 RTUUID Uuid;
153 /** Number of references to this test object. */
154 uint32_t cRefs;
155 /** Name of the test object.
156 * Must not contain a path and has to be able to serialize to disk. */
157 char szName[64];
158 /** The object type. */
159 AUDIOTESTOBJTYPE enmType;
160 /** Meta data list. */
161 RTLISTANCHOR lstMeta;
162 /** Union for holding the object type-specific data. */
163 union
164 {
165 AUDIOTESTOBJFILE File;
166 };
167} AUDIOTESTOBJINT;
168/** Pointer to an audio test object. */
169typedef AUDIOTESTOBJINT *PAUDIOTESTOBJINT;
170
171/**
172 * Structure for keeping an audio test verification job.
173 */
174typedef struct AUDIOTESTVERIFYJOB
175{
176 /** Pointer to set A. */
177 PAUDIOTESTSET pSetA;
178 /** Pointer to set B. */
179 PAUDIOTESTSET pSetB;
180 /** Pointer to the error description to use. */
181 PAUDIOTESTERRORDESC pErr;
182 /** Zero-based index of current test being verified. */
183 uint32_t idxTest;
184 /** The verification options to use. */
185 AUDIOTESTVERIFYOPTS Opts;
186 /** PCM properties to use for verification. */
187 PDMAUDIOPCMPROPS PCMProps;
188} AUDIOTESTVERIFYJOB;
189/** Pointer to an audio test verification job. */
190typedef AUDIOTESTVERIFYJOB *PAUDIOTESTVERIFYJOB;
191
192
193/*********************************************************************************************************************************
194* Global Variables *
195*********************************************************************************************************************************/
196/** Well-known frequency selection test tones. */
197static const double s_aAudioTestToneFreqsHz[] =
198{
199 349.2282 /*F4*/,
200 440.0000 /*A4*/,
201 523.2511 /*C5*/,
202 698.4565 /*F5*/,
203 880.0000 /*A5*/,
204 1046.502 /*C6*/,
205 1174.659 /*D6*/,
206 1396.913 /*F6*/,
207 1760.0000 /*A6*/
208};
209
210
211/*********************************************************************************************************************************
212* Internal Functions *
213*********************************************************************************************************************************/
214static int audioTestObjClose(PAUDIOTESTOBJINT pObj);
215static void audioTestObjFinalize(PAUDIOTESTOBJINT pObj);
216static void audioTestObjInit(PAUDIOTESTOBJINT pObj);
217
218
219/**
220 * Initializes a test tone with a specific frequency (in Hz).
221 *
222 * @returns Used tone frequency (in Hz).
223 * @param pTone Pointer to test tone to initialize.
224 * @param pProps PCM properties to use for the test tone.
225 * @param dbFreq Frequency (in Hz) to initialize tone with.
226 * When set to 0.0, a random frequency will be chosen.
227 */
228double AudioTestToneInit(PAUDIOTESTTONE pTone, PPDMAUDIOPCMPROPS pProps, double dbFreq)
229{
230 if (dbFreq == 0.0)
231 dbFreq = AudioTestToneGetRandomFreq();
232
233 pTone->rdFreqHz = dbFreq;
234 pTone->rdFixed = 2.0 * M_PI * pTone->rdFreqHz / PDMAudioPropsHz(pProps);
235 pTone->uSample = 0;
236
237 memcpy(&pTone->Props, pProps, sizeof(PDMAUDIOPCMPROPS));
238
239 pTone->enmType = AUDIOTESTTONETYPE_SINE; /* Only type implemented so far. */
240
241 return dbFreq;
242}
243
244/**
245 * Initializes a test tone by picking a random but well-known frequency (in Hz).
246 *
247 * @returns Randomly picked tone frequency (in Hz).
248 * @param pTone Pointer to test tone to initialize.
249 * @param pProps PCM properties to use for the test tone.
250 */
251double AudioTestToneInitRandom(PAUDIOTESTTONE pTone, PPDMAUDIOPCMPROPS pProps)
252{
253 return AudioTestToneInit(pTone, pProps,
254 /* Pick a frequency from our selection, so that every time a recording starts
255 * we'll hopfully generate a different note. */
256 0.0);
257}
258
259/**
260 * Writes (and iterates) a given test tone to an output buffer.
261 *
262 * @returns VBox status code.
263 * @param pTone Pointer to test tone to write.
264 * @param pvBuf Pointer to output buffer to write test tone to.
265 * @param cbBuf Size (in bytes) of output buffer.
266 * @param pcbWritten How many bytes were written on success.
267 */
268int AudioTestToneGenerate(PAUDIOTESTTONE pTone, void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
269{
270 /*
271 * Clear the buffer first so we don't need to think about additional channels.
272 */
273 uint32_t cFrames = PDMAudioPropsBytesToFrames(&pTone->Props, cbBuf);
274
275 /* Input cbBuf not necessarily is aligned to the frames, so re-calculate it. */
276 const uint32_t cbToWrite = PDMAudioPropsFramesToBytes(&pTone->Props, cFrames);
277
278 PDMAudioPropsClearBuffer(&pTone->Props, pvBuf, cbBuf, cFrames);
279
280 /*
281 * Generate the select sin wave in the first channel:
282 */
283 uint32_t const cbFrame = PDMAudioPropsFrameSize(&pTone->Props);
284 double const rdFixed = pTone->rdFixed;
285 uint64_t iSrcFrame = pTone->uSample;
286 switch (PDMAudioPropsSampleSize(&pTone->Props))
287 {
288 case 1:
289 /* untested */
290 if (PDMAudioPropsIsSigned(&pTone->Props))
291 {
292 int8_t *piSample = (int8_t *)pvBuf;
293 while (cFrames-- > 0)
294 {
295 *piSample = (int8_t)(126 /*Amplitude*/ * sin(rdFixed * iSrcFrame));
296 iSrcFrame++;
297 piSample += cbFrame;
298 }
299 }
300 else
301 {
302 /* untested */
303 uint8_t *pbSample = (uint8_t *)pvBuf;
304 while (cFrames-- > 0)
305 {
306 *pbSample = (uint8_t)(126 /*Amplitude*/ * sin(rdFixed * iSrcFrame) + 0x80);
307 iSrcFrame++;
308 pbSample += cbFrame;
309 }
310 }
311 break;
312
313 case 2:
314 if (PDMAudioPropsIsSigned(&pTone->Props))
315 {
316 int16_t *piSample = (int16_t *)pvBuf;
317 while (cFrames-- > 0)
318 {
319 *piSample = (int16_t)(32760 /*Amplitude*/ * sin(rdFixed * iSrcFrame));
320 iSrcFrame++;
321 piSample = (int16_t *)((uint8_t *)piSample + cbFrame);
322 }
323 }
324 else
325 {
326 /* untested */
327 uint16_t *puSample = (uint16_t *)pvBuf;
328 while (cFrames-- > 0)
329 {
330 *puSample = (uint16_t)(32760 /*Amplitude*/ * sin(rdFixed * iSrcFrame) + 0x8000);
331 iSrcFrame++;
332 puSample = (uint16_t *)((uint8_t *)puSample + cbFrame);
333 }
334 }
335 break;
336
337 case 4:
338 /* untested */
339 if (PDMAudioPropsIsSigned(&pTone->Props))
340 {
341 int32_t *piSample = (int32_t *)pvBuf;
342 while (cFrames-- > 0)
343 {
344 *piSample = (int32_t)((32760 << 16) /*Amplitude*/ * sin(rdFixed * iSrcFrame));
345 iSrcFrame++;
346 piSample = (int32_t *)((uint8_t *)piSample + cbFrame);
347 }
348 }
349 else
350 {
351 uint32_t *puSample = (uint32_t *)pvBuf;
352 while (cFrames-- > 0)
353 {
354 *puSample = (uint32_t)((32760 << 16) /*Amplitude*/ * sin(rdFixed * iSrcFrame) + UINT32_C(0x80000000));
355 iSrcFrame++;
356 puSample = (uint32_t *)((uint8_t *)puSample + cbFrame);
357 }
358 }
359 break;
360
361 default:
362 AssertFailedReturn(VERR_NOT_SUPPORTED);
363 }
364
365 pTone->uSample = iSrcFrame;
366
367 if (pcbWritten)
368 *pcbWritten = cbToWrite;
369
370 return VINF_SUCCESS;
371}
372
373/**
374 * Returns a random test tone frequency.
375 */
376double AudioTestToneGetRandomFreq(void)
377{
378 return s_aAudioTestToneFreqsHz[RTRandU32Ex(0, RT_ELEMENTS(s_aAudioTestToneFreqsHz) - 1)];
379}
380
381/**
382 * Finds the next audible *or* silent audio sample and returns its offset.
383 *
384 * @returns Offset (in bytes) of the next found sample.
385 * @param hFile File handle of file to search in.
386 * @param fFindSilence Whether to search for a silent sample or not (i.e. audible).
387 * What a silent sample is depends on \a pToneParms PCM parameters.
388 * @param uOff Absolute offset (in bytes) to start searching from.
389 * @param pToneParms Tone parameters to use.
390 */
391static uint64_t audioTestToneFileFind(RTFILE hFile, bool fFindSilence, uint64_t uOff, PAUDIOTESTTONEPARMS pToneParms)
392{
393 int rc = RTFileSeek(hFile, uOff, RTFILE_SEEK_BEGIN, NULL);
394 AssertRCReturn(rc, 0);
395
396 uint64_t offFound = 0;
397 int64_t abSample[_16K];
398
399 size_t cbRead;
400 for (;;)
401 {
402 rc = RTFileRead(hFile, &abSample, sizeof(abSample), &cbRead);
403 if ( RT_FAILURE(rc)
404 || !cbRead)
405 break;
406
407 Assert(PDMAudioPropsIsSizeAligned(&pToneParms->Props, (uint32_t)cbRead));
408
409 size_t const cbFrame = PDMAudioPropsFrameSize(&pToneParms->Props);
410
411 for (size_t i = 0; i < cbRead / cbFrame; i += cbFrame) /** @todo Slow as heck, but works for now. */
412 {
413 bool const fIsSilence = PDMAudioPropsIsBufferSilence(&pToneParms->Props, (const uint8_t *)abSample + i, cbFrame);
414 if (fIsSilence == fFindSilence)
415 {
416 offFound += cbFrame;
417 }
418 else
419 break;
420 }
421 }
422
423 return offFound;
424}
425
426/**
427 * Generates a tag.
428 *
429 * @returns VBox status code.
430 * @param pszTag The output buffer.
431 * @param cbTag The size of the output buffer.
432 * AUDIOTEST_TAG_MAX is a good size.
433 */
434int AudioTestGenTag(char *pszTag, size_t cbTag)
435{
436 RTUUID UUID;
437 int rc = RTUuidCreate(&UUID);
438 AssertRCReturn(rc, rc);
439 rc = RTUuidToStr(&UUID, pszTag, cbTag);
440 AssertRCReturn(rc, rc);
441 return rc;
442}
443
444/**
445 * Return the tag to use in the given buffer, generating one if needed.
446 *
447 * @returns VBox status code.
448 * @param pszTag The output buffer.
449 * @param cbTag The size of the output buffer.
450 * AUDIOTEST_TAG_MAX is a good size.
451 * @param pszTagUser User specified tag, optional.
452 */
453static int audioTestCopyOrGenTag(char *pszTag, size_t cbTag, const char *pszTagUser)
454{
455 if (pszTagUser && *pszTagUser)
456 return RTStrCopy(pszTag, cbTag, pszTagUser);
457 return AudioTestGenTag(pszTag, cbTag);
458}
459
460
461/**
462 * Creates a new path (directory) for a specific audio test set tag.
463 *
464 * @returns VBox status code.
465 * @param pszPath On input, specifies the absolute base path where to create the test set path.
466 * On output this specifies the absolute path created.
467 * @param cbPath Size (in bytes) of \a pszPath.
468 * @param pszTag Tag to use for path creation.
469 *
470 * @note Can be used multiple times with the same tag; a sub directory with an ISO time string will be used
471 * on each call.
472 */
473int AudioTestPathCreate(char *pszPath, size_t cbPath, const char *pszTag)
474{
475 char szTag[AUDIOTEST_TAG_MAX];
476 int rc = audioTestCopyOrGenTag(szTag, sizeof(szTag), pszTag);
477 AssertRCReturn(rc, rc);
478
479 char szName[RT_ELEMENTS(AUDIOTEST_PATH_PREFIX_STR) + AUDIOTEST_TAG_MAX + 4];
480 if (RTStrPrintf2(szName, sizeof(szName), "%s-%s", AUDIOTEST_PATH_PREFIX_STR, szTag) < 0)
481 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
482
483 rc = RTPathAppend(pszPath, cbPath, szName);
484 AssertRCReturn(rc, rc);
485
486#ifndef DEBUG /* Makes debugging easier to have a deterministic directory. */
487 char szTime[64];
488 RTTIMESPEC time;
489 if (!RTTimeSpecToString(RTTimeNow(&time), szTime, sizeof(szTime)))
490 return VERR_BUFFER_UNDERFLOW;
491
492 /* Colons aren't allowed in windows filenames, so change to dashes. */
493 char *pszColon;
494 while ((pszColon = strchr(szTime, ':')) != NULL)
495 *pszColon = '-';
496
497 rc = RTPathAppend(pszPath, cbPath, szTime);
498 AssertRCReturn(rc, rc);
499#endif
500
501 return RTDirCreateFullPath(pszPath, RTFS_UNIX_IRWXU);
502}
503
504DECLINLINE(int) audioTestManifestWriteData(PAUDIOTESTSET pSet, const void *pvData, size_t cbData)
505{
506 /** @todo Use RTIniFileWrite once its implemented. */
507 return RTFileWrite(pSet->f.hFile, pvData, cbData, NULL);
508}
509
510/**
511 * Writes string data to a test set manifest.
512 *
513 * @returns VBox status code.
514 * @param pSet Test set to write manifest for.
515 * @param pszFormat Format string to write.
516 * @param args Variable arguments for \a pszFormat.
517 */
518static int audioTestManifestWriteV(PAUDIOTESTSET pSet, const char *pszFormat, va_list args)
519{
520 /** @todo r=bird: Use RTStrmOpen + RTStrmPrintf instead of this slow
521 * do-it-all-yourself stuff. */
522 char *psz = NULL;
523 if (RTStrAPrintfV(&psz, pszFormat, args) == -1)
524 return VERR_NO_MEMORY;
525 AssertPtrReturn(psz, VERR_NO_MEMORY);
526
527 int rc = audioTestManifestWriteData(pSet, psz, strlen(psz));
528 AssertRC(rc);
529
530 RTStrFree(psz);
531
532 return rc;
533}
534
535/**
536 * Writes a string to a test set manifest.
537 * Convenience function.
538 *
539 * @returns VBox status code.
540 * @param pSet Test set to write manifest for.
541 * @param pszFormat Format string to write.
542 * @param ... Variable arguments for \a pszFormat. Optional.
543 */
544static int audioTestManifestWrite(PAUDIOTESTSET pSet, const char *pszFormat, ...)
545{
546 va_list va;
547 va_start(va, pszFormat);
548
549 int rc = audioTestManifestWriteV(pSet, pszFormat, va);
550 AssertRC(rc);
551
552 va_end(va);
553
554 return rc;
555}
556
557/**
558 * Returns the current read/write offset (in bytes) of the opened manifest file.
559 *
560 * @returns Current read/write offset (in bytes).
561 * @param pSet Set to return offset for.
562 * Must have an opened manifest file.
563 */
564DECLINLINE(uint64_t) audioTestManifestGetOffsetAbs(PAUDIOTESTSET pSet)
565{
566 AssertReturn(RTFileIsValid(pSet->f.hFile), 0);
567 return RTFileTell(pSet->f.hFile);
568}
569
570/**
571 * Writes a section header to a test set manifest.
572 *
573 * @returns VBox status code.
574 * @param pSet Test set to write manifest for.
575 * @param pszSection Format string of section to write.
576 * @param ... Variable arguments for \a pszSection. Optional.
577 */
578static int audioTestManifestWriteSectionHdr(PAUDIOTESTSET pSet, const char *pszSection, ...)
579{
580 va_list va;
581 va_start(va, pszSection);
582
583 /** @todo Keep it as simple as possible for now. Improve this later. */
584 int rc = audioTestManifestWrite(pSet, "[%N]\n", pszSection, &va);
585
586 va_end(va);
587
588 return rc;
589}
590
591/**
592 * Initializes an audio test set, internal function.
593 *
594 * @param pSet Test set to initialize.
595 */
596static void audioTestSetInitInternal(PAUDIOTESTSET pSet)
597{
598 pSet->f.hFile = NIL_RTFILE;
599
600 RTListInit(&pSet->lstObj);
601 pSet->cObj = 0;
602
603 RTListInit(&pSet->lstTest);
604 pSet->cTests = 0;
605 pSet->cTestsRunning = 0;
606 pSet->offTestCount = 0;
607 pSet->pTestCur = NULL;
608 pSet->cObj = 0;
609 pSet->offObjCount = 0;
610 pSet->cTotalFailures = 0;
611}
612
613/**
614 * Returns whether a test set's manifest file is open (and thus ready) or not.
615 *
616 * @returns \c true if open (and ready), or \c false if not.
617 * @retval VERR_
618 * @param pSet Test set to return open status for.
619 */
620static bool audioTestManifestIsOpen(PAUDIOTESTSET pSet)
621{
622 if ( pSet->enmMode == AUDIOTESTSETMODE_TEST
623 && pSet->f.hFile != NIL_RTFILE)
624 return true;
625 else if ( pSet->enmMode == AUDIOTESTSETMODE_VERIFY
626 && pSet->f.hIniFile != NIL_RTINIFILE)
627 return true;
628
629 return false;
630}
631
632/**
633 * Initializes an audio test error description.
634 *
635 * @param pErr Test error description to initialize.
636 */
637static void audioTestErrorDescInit(PAUDIOTESTERRORDESC pErr)
638{
639 RTListInit(&pErr->List);
640 pErr->cErrors = 0;
641}
642
643/**
644 * Destroys an audio test error description.
645 *
646 * @param pErr Test error description to destroy.
647 */
648void AudioTestErrorDescDestroy(PAUDIOTESTERRORDESC pErr)
649{
650 if (!pErr)
651 return;
652
653 PAUDIOTESTERRORENTRY pErrEntry, pErrEntryNext;
654 RTListForEachSafe(&pErr->List, pErrEntry, pErrEntryNext, AUDIOTESTERRORENTRY, Node)
655 {
656 RTListNodeRemove(&pErrEntry->Node);
657
658 RTMemFree(pErrEntry);
659 }
660
661 pErr->cErrors = 0;
662}
663
664/**
665 * Returns the the number of errors of an audio test error description.
666 *
667 * @returns Error count.
668 * @param pErr Test error description to return error count for.
669 */
670uint32_t AudioTestErrorDescCount(PCAUDIOTESTERRORDESC pErr)
671{
672 return pErr->cErrors;
673}
674
675/**
676 * Returns if an audio test error description contains any errors or not.
677 *
678 * @returns \c true if it contains errors, or \c false if not.
679 * @param pErr Test error description to return error status for.
680 */
681bool AudioTestErrorDescFailed(PCAUDIOTESTERRORDESC pErr)
682{
683 if (pErr->cErrors)
684 {
685 Assert(!RTListIsEmpty(&pErr->List));
686 return true;
687 }
688
689 return false;
690}
691
692/**
693 * Adds a single error entry to an audio test error description, va_list version.
694 *
695 * @returns VBox status code.
696 * @param pErr Test error description to add entry for.
697 * @param idxTest Index of failing test (zero-based).
698 * @param rc Result code of entry to add.
699 * @param pszFormat Error description format string to add.
700 * @param va Optional format arguments of \a pszDesc to add.
701 */
702static int audioTestErrorDescAddV(PAUDIOTESTERRORDESC pErr, uint32_t idxTest, int rc, const char *pszFormat, va_list va)
703{
704 PAUDIOTESTERRORENTRY pEntry = (PAUDIOTESTERRORENTRY)RTMemAlloc(sizeof(AUDIOTESTERRORENTRY));
705 AssertPtrReturn(pEntry, VERR_NO_MEMORY);
706
707 char *pszDescTmp;
708 if (RTStrAPrintfV(&pszDescTmp, pszFormat, va) < 0)
709 AssertFailedReturn(VERR_NO_MEMORY);
710
711 const ssize_t cch = RTStrPrintf2(pEntry->szDesc, sizeof(pEntry->szDesc), "Test #%RU32 %s: %s",
712 idxTest, RT_FAILURE(rc) ? "failed" : "info", pszDescTmp);
713 RTStrFree(pszDescTmp);
714 AssertReturn(cch > 0, VERR_BUFFER_OVERFLOW);
715
716 pEntry->rc = rc;
717
718 RTListAppend(&pErr->List, &pEntry->Node);
719
720 if (RT_FAILURE(rc))
721 pErr->cErrors++;
722
723 return VINF_SUCCESS;
724}
725
726/**
727 * Adds a single error entry to an audio test error description.
728 *
729 * @returns VBox status code.
730 * @param pErr Test error description to add entry for.
731 * @param idxTest Index of failing test (zero-based).
732 * @param pszFormat Error description format string to add.
733 * @param ... Optional format arguments of \a pszDesc to add.
734 */
735static int audioTestErrorDescAddError(PAUDIOTESTERRORDESC pErr, uint32_t idxTest, const char *pszFormat, ...)
736{
737 va_list va;
738 va_start(va, pszFormat);
739
740 int rc = audioTestErrorDescAddV(pErr, idxTest, VERR_GENERAL_FAILURE /** @todo Fudge! */, pszFormat, va);
741
742 va_end(va);
743 return rc;
744}
745
746/**
747 * Adds a single info entry to an audio test error description, va_list version.
748 *
749 * @returns VBox status code.
750 * @param pErr Test error description to add entry for.
751 * @param idxTest Index of failing test (zero-based).
752 * @param pszFormat Error description format string to add.
753 * @param ... Optional format arguments of \a pszDesc to add.
754 */
755static int audioTestErrorDescAddInfo(PAUDIOTESTERRORDESC pErr, uint32_t idxTest, const char *pszFormat, ...)
756{
757 va_list va;
758 va_start(va, pszFormat);
759
760 int rc = audioTestErrorDescAddV(pErr, idxTest, VINF_SUCCESS, pszFormat, va);
761
762 va_end(va);
763 return rc;
764}
765
766#if 0
767static int audioTestErrorDescAddRc(PAUDIOTESTERRORDESC pErr, int rc, const char *pszFormat, ...)
768{
769 va_list va;
770 va_start(va, pszFormat);
771
772 int rc2 = audioTestErrorDescAddV(pErr, rc, pszFormat, va);
773
774 va_end(va);
775 return rc2;
776}
777#endif
778
779/**
780 * Retrieves the temporary directory.
781 *
782 * @returns VBox status code.
783 * @param pszPath Where to return the absolute path of the created directory on success.
784 * @param cbPath Size (in bytes) of \a pszPath.
785 */
786int AudioTestPathGetTemp(char *pszPath, size_t cbPath)
787{
788 int rc = RTEnvGetEx(RTENV_DEFAULT, "TESTBOX_PATH_SCRATCH", pszPath, cbPath, NULL);
789 if (RT_FAILURE(rc))
790 {
791 rc = RTPathTemp(pszPath, cbPath);
792 AssertRCReturn(rc, rc);
793 }
794
795 return rc;
796}
797
798/**
799 * Creates a new temporary directory with a specific (test) tag.
800 *
801 * @returns VBox status code.
802 * @param pszPath Where to return the absolute path of the created directory on success.
803 * @param cbPath Size (in bytes) of \a pszPath.
804 * @param pszTag Tag name to use for directory creation.
805 *
806 * @note Can be used multiple times with the same tag; a sub directory with an ISO time string will be used
807 * on each call.
808 */
809int AudioTestPathCreateTemp(char *pszPath, size_t cbPath, const char *pszTag)
810{
811 AssertReturn(pszTag && strlen(pszTag) <= AUDIOTEST_TAG_MAX, VERR_INVALID_PARAMETER);
812
813 char szTemp[RTPATH_MAX];
814 int rc = AudioTestPathGetTemp(szTemp, sizeof(szTemp));
815 AssertRCReturn(rc, rc);
816
817 rc = AudioTestPathCreate(szTemp, sizeof(szTemp), pszTag);
818 AssertRCReturn(rc, rc);
819
820 return RTStrCopy(pszPath, cbPath, szTemp);
821}
822
823/**
824 * Gets a value as string.
825 *
826 * @returns VBox status code.
827 * @param pObj Object handle to get value for.
828 * @param pszKey Key to get value from.
829 * @param pszVal Where to return the value on success.
830 * @param cbVal Size (in bytes) of \a pszVal.
831 */
832static int audioTestObjGetStr(PAUDIOTESTOBJINT pObj, const char *pszKey, char *pszVal, size_t cbVal)
833{
834 /** @todo For now we only support .INI-style files. */
835 AssertPtrReturn(pObj->pSet, VERR_WRONG_ORDER);
836 return RTIniFileQueryValue(pObj->pSet->f.hIniFile, pObj->szSec, pszKey, pszVal, cbVal, NULL);
837}
838
839/**
840 * Gets a value as boolean.
841 *
842 * @returns VBox status code.
843 * @param pObj Object handle to get value for.
844 * @param pszKey Key to get value from.
845 * @param pbVal Where to return the value on success.
846 */
847static int audioTestObjGetBool(PAUDIOTESTOBJINT pObj, const char *pszKey, bool *pbVal)
848{
849 char szVal[_1K];
850 int rc = audioTestObjGetStr(pObj, pszKey, szVal, sizeof(szVal));
851 if (RT_SUCCESS(rc))
852 *pbVal = (RTStrICmp(szVal, "true") == 0)
853 || (RTStrICmp(szVal, "1") == 0) ? true : false;
854
855 return rc;
856}
857
858/**
859 * Gets a value as uint8_t.
860 *
861 * @returns VBox status code.
862 * @param pObj Object handle to get value for.
863 * @param pszKey Key to get value from.
864 * @param puVal Where to return the value on success.
865 */
866static int audioTestObjGetUInt8(PAUDIOTESTOBJINT pObj, const char *pszKey, uint8_t *puVal)
867{
868 char szVal[_1K];
869 int rc = audioTestObjGetStr(pObj, pszKey, szVal, sizeof(szVal));
870 if (RT_SUCCESS(rc))
871 *puVal = RTStrToUInt8(szVal);
872
873 return rc;
874}
875
876/**
877 * Gets a value as uint32_t.
878 *
879 * @returns VBox status code.
880 * @param pObj Object handle to get value for.
881 * @param pszKey Key to get value from.
882 * @param puVal Where to return the value on success.
883 */
884static int audioTestObjGetUInt32(PAUDIOTESTOBJINT pObj, const char *pszKey, uint32_t *puVal)
885{
886 char szVal[_1K];
887 int rc = audioTestObjGetStr(pObj, pszKey, szVal, sizeof(szVal));
888 if (RT_SUCCESS(rc))
889 *puVal = RTStrToUInt32(szVal);
890
891 return rc;
892}
893
894/**
895 * Returns the absolute path of a given audio test set object.
896 *
897 * @returns VBox status code.
898 * @param pSet Test set the object contains.
899 * @param pszPathAbs Where to return the absolute path on success.
900 * @param cbPathAbs Size (in bytes) of \a pszPathAbs.
901 * @param pszObjName Name of the object to create absolute path for.
902 */
903DECLINLINE(int) audioTestSetGetObjPath(PAUDIOTESTSET pSet, char *pszPathAbs, size_t cbPathAbs, const char *pszObjName)
904{
905 return RTPathJoin(pszPathAbs, cbPathAbs, pSet->szPathAbs, pszObjName);
906}
907
908/**
909 * Returns the tag of a test set.
910 *
911 * @returns Test set tag.
912 * @param pSet Test set to return tag for.
913 */
914const char *AudioTestSetGetTag(PAUDIOTESTSET pSet)
915{
916 return pSet->szTag;
917}
918
919/**
920 * Returns the total number of registered tests.
921 *
922 * @returns Total number of registered tests.
923 * @param pSet Test set to return value for.
924 */
925uint32_t AudioTestSetGetTestsTotal(PAUDIOTESTSET pSet)
926{
927 return pSet->cTests;
928}
929
930/**
931 * Returns the total number of (still) running tests.
932 *
933 * @returns Total number of (still) running tests.
934 * @param pSet Test set to return value for.
935 */
936uint32_t AudioTestSetGetTestsRunning(PAUDIOTESTSET pSet)
937{
938 return pSet->cTestsRunning;
939}
940
941/**
942 * Returns the total number of test failures occurred.
943 *
944 * @returns Total number of test failures occurred.
945 * @param pSet Test set to return value for.
946 */
947uint32_t AudioTestSetGetTotalFailures(PAUDIOTESTSET pSet)
948{
949 return pSet->cTotalFailures;
950}
951
952/**
953 * Creates a new audio test set.
954 *
955 * @returns VBox status code.
956 * @param pSet Test set to create.
957 * @param pszPath Where to store the set set data. If NULL, the
958 * temporary directory will be used.
959 * @param pszTag Tag name to use for this test set.
960 */
961int AudioTestSetCreate(PAUDIOTESTSET pSet, const char *pszPath, const char *pszTag)
962{
963 audioTestSetInitInternal(pSet);
964
965 int rc = audioTestCopyOrGenTag(pSet->szTag, sizeof(pSet->szTag), pszTag);
966 AssertRCReturn(rc, rc);
967
968 /*
969 * Test set directory.
970 */
971 if (pszPath)
972 {
973 rc = RTPathAbs(pszPath, pSet->szPathAbs, sizeof(pSet->szPathAbs));
974 AssertRCReturn(rc, rc);
975
976 rc = AudioTestPathCreate(pSet->szPathAbs, sizeof(pSet->szPathAbs), pSet->szTag);
977 }
978 else
979 rc = AudioTestPathCreateTemp(pSet->szPathAbs, sizeof(pSet->szPathAbs), pSet->szTag);
980 AssertRCReturn(rc, rc);
981
982 /*
983 * Create the manifest file.
984 */
985 char szTmp[RTPATH_MAX];
986 rc = RTPathJoin(szTmp, sizeof(szTmp), pSet->szPathAbs, AUDIOTEST_MANIFEST_FILE_STR);
987 AssertRCReturn(rc, rc);
988
989 rc = RTFileOpen(&pSet->f.hFile, szTmp, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
990 AssertRCReturn(rc, rc);
991
992 rc = audioTestManifestWriteSectionHdr(pSet, "header");
993 AssertRCReturn(rc, rc);
994
995 rc = audioTestManifestWrite(pSet, "magic=vkat_ini\n"); /* VKAT Manifest, .INI-style. */
996 AssertRCReturn(rc, rc);
997 rc = audioTestManifestWrite(pSet, "ver=%d\n", AUDIOTEST_MANIFEST_VER);
998 AssertRCReturn(rc, rc);
999 rc = audioTestManifestWrite(pSet, "tag=%s\n", pSet->szTag);
1000 AssertRCReturn(rc, rc);
1001
1002 AssertCompile(sizeof(szTmp) > RTTIME_STR_LEN);
1003 RTTIMESPEC Now;
1004 rc = audioTestManifestWrite(pSet, "date_created=%s\n", RTTimeSpecToString(RTTimeNow(&Now), szTmp, sizeof(szTmp)));
1005 AssertRCReturn(rc, rc);
1006
1007 RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp)); /* do NOT return on failure. */
1008 rc = audioTestManifestWrite(pSet, "os_product=%s\n", szTmp);
1009 AssertRCReturn(rc, rc);
1010
1011 RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp)); /* do NOT return on failure. */
1012 rc = audioTestManifestWrite(pSet, "os_rel=%s\n", szTmp);
1013 AssertRCReturn(rc, rc);
1014
1015 RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp)); /* do NOT return on failure. */
1016 rc = audioTestManifestWrite(pSet, "os_ver=%s\n", szTmp);
1017 AssertRCReturn(rc, rc);
1018
1019 rc = audioTestManifestWrite(pSet, "vbox_ver=%s r%u %s (%s %s)\n",
1020 VBOX_VERSION_STRING, RTBldCfgRevision(), RTBldCfgTargetDotArch(), __DATE__, __TIME__);
1021 AssertRCReturn(rc, rc);
1022
1023 rc = audioTestManifestWrite(pSet, "test_count=");
1024 AssertRCReturn(rc, rc);
1025 pSet->offTestCount = audioTestManifestGetOffsetAbs(pSet);
1026 rc = audioTestManifestWrite(pSet, "0000\n"); /* A bit messy, but does the trick for now. */
1027 AssertRCReturn(rc, rc);
1028
1029 rc = audioTestManifestWrite(pSet, "obj_count=");
1030 AssertRCReturn(rc, rc);
1031 pSet->offObjCount = audioTestManifestGetOffsetAbs(pSet);
1032 rc = audioTestManifestWrite(pSet, "0000\n"); /* A bit messy, but does the trick for now. */
1033 AssertRCReturn(rc, rc);
1034
1035 pSet->enmMode = AUDIOTESTSETMODE_TEST;
1036
1037 return rc;
1038}
1039
1040/**
1041 * Destroys a test set.
1042 *
1043 * @returns VBox status code.
1044 * @param pSet Test set to destroy.
1045 */
1046int AudioTestSetDestroy(PAUDIOTESTSET pSet)
1047{
1048 if (!pSet)
1049 return VINF_SUCCESS;
1050
1051 AssertReturn(pSet->cTestsRunning == 0, VERR_WRONG_ORDER); /* Make sure no tests sill are running. */
1052
1053 int rc = AudioTestSetClose(pSet);
1054 if (RT_FAILURE(rc))
1055 return rc;
1056
1057 PAUDIOTESTOBJINT pObj, pObjNext;
1058 RTListForEachSafe(&pSet->lstObj, pObj, pObjNext, AUDIOTESTOBJINT, Node)
1059 {
1060 rc = audioTestObjClose(pObj);
1061 if (RT_SUCCESS(rc))
1062 {
1063 PAUDIOTESTOBJMETA pMeta, pMetaNext;
1064 RTListForEachSafe(&pObj->lstMeta, pMeta, pMetaNext, AUDIOTESTOBJMETA, Node)
1065 {
1066 switch (pMeta->enmType)
1067 {
1068 case AUDIOTESTOBJMETADATATYPE_STRING:
1069 {
1070 RTStrFree((char *)pMeta->pvMeta);
1071 break;
1072 }
1073
1074 default:
1075 AssertFailed();
1076 break;
1077 }
1078
1079 RTListNodeRemove(&pMeta->Node);
1080 RTMemFree(pMeta);
1081 }
1082
1083 RTListNodeRemove(&pObj->Node);
1084 RTMemFree(pObj);
1085
1086 Assert(pSet->cObj);
1087 pSet->cObj--;
1088 }
1089 else
1090 break;
1091 }
1092
1093 if (RT_FAILURE(rc))
1094 return rc;
1095
1096 Assert(pSet->cObj == 0);
1097
1098 PAUDIOTESTENTRY pEntry, pEntryNext;
1099 RTListForEachSafe(&pSet->lstTest, pEntry, pEntryNext, AUDIOTESTENTRY, Node)
1100 {
1101 RTListNodeRemove(&pEntry->Node);
1102 RTMemFree(pEntry);
1103
1104 Assert(pSet->cTests);
1105 pSet->cTests--;
1106 }
1107
1108 if (RT_FAILURE(rc))
1109 return rc;
1110
1111 Assert(pSet->cTests == 0);
1112
1113 return rc;
1114}
1115
1116/**
1117 * Opens an existing audio test set.
1118 *
1119 * @returns VBox status code.
1120 * @param pSet Test set to open.
1121 * @param pszPath Absolute path of the test set to open.
1122 */
1123int AudioTestSetOpen(PAUDIOTESTSET pSet, const char *pszPath)
1124{
1125 audioTestSetInitInternal(pSet);
1126
1127 char szManifest[RTPATH_MAX];
1128 int rc = RTPathJoin(szManifest, sizeof(szManifest), pszPath, AUDIOTEST_MANIFEST_FILE_STR);
1129 AssertRCReturn(rc, rc);
1130
1131 RTVFSFILE hVfsFile;
1132 rc = RTVfsFileOpenNormal(szManifest, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hVfsFile);
1133 if (RT_FAILURE(rc))
1134 return rc;
1135
1136 rc = RTIniFileCreateFromVfsFile(&pSet->f.hIniFile, hVfsFile, RTINIFILE_F_READONLY);
1137 RTVfsFileRelease(hVfsFile);
1138 AssertRCReturn(rc, rc);
1139
1140 rc = RTStrCopy(pSet->szPathAbs, sizeof(pSet->szPathAbs), pszPath);
1141 AssertRCReturn(rc, rc);
1142
1143 pSet->enmMode = AUDIOTESTSETMODE_VERIFY;
1144
1145 return rc;
1146}
1147
1148/**
1149 * Closes an opened audio test set.
1150 *
1151 * @returns VBox status code.
1152 * @param pSet Test set to close.
1153 */
1154int AudioTestSetClose(PAUDIOTESTSET pSet)
1155{
1156 AssertPtrReturn(pSet, VERR_INVALID_POINTER);
1157
1158 if (!audioTestManifestIsOpen(pSet))
1159 return VINF_SUCCESS;
1160
1161 int rc;
1162
1163 if (pSet->enmMode == AUDIOTESTSETMODE_TEST)
1164 {
1165 /* Update number of bound test objects. */
1166 PAUDIOTESTENTRY pTest;
1167 RTListForEach(&pSet->lstTest, pTest, AUDIOTESTENTRY, Node)
1168 {
1169 rc = RTFileSeek(pSet->f.hFile, pTest->offObjCount, RTFILE_SEEK_BEGIN, NULL);
1170 AssertRCReturn(rc, rc);
1171 rc = audioTestManifestWrite(pSet, "%04RU32", pTest->cObj);
1172 AssertRCReturn(rc, rc);
1173 }
1174
1175 /*
1176 * Update number of ran tests.
1177 */
1178 rc = RTFileSeek(pSet->f.hFile, pSet->offObjCount, RTFILE_SEEK_BEGIN, NULL);
1179 AssertRCReturn(rc, rc);
1180 rc = audioTestManifestWrite(pSet, "%04RU32", pSet->cObj);
1181 AssertRCReturn(rc, rc);
1182
1183 /*
1184 * Update number of ran tests.
1185 */
1186 rc = RTFileSeek(pSet->f.hFile, pSet->offTestCount, RTFILE_SEEK_BEGIN, NULL);
1187 AssertRCReturn(rc, rc);
1188 rc = audioTestManifestWrite(pSet, "%04RU32", pSet->cTests);
1189 AssertRCReturn(rc, rc);
1190
1191 /*
1192 * Serialize all registered test objects.
1193 */
1194 rc = RTFileSeek(pSet->f.hFile, 0, RTFILE_SEEK_END, NULL);
1195 AssertRCReturn(rc, rc);
1196
1197 PAUDIOTESTOBJINT pObj;
1198 RTListForEach(&pSet->lstObj, pObj, AUDIOTESTOBJINT, Node)
1199 {
1200 rc = audioTestManifestWrite(pSet, "\n");
1201 AssertRCReturn(rc, rc);
1202 char szUuid[AUDIOTEST_MAX_SEC_LEN];
1203 rc = RTUuidToStr(&pObj->Uuid, szUuid, sizeof(szUuid));
1204 AssertRCReturn(rc, rc);
1205 rc = audioTestManifestWriteSectionHdr(pSet, "obj_%s", szUuid);
1206 AssertRCReturn(rc, rc);
1207 rc = audioTestManifestWrite(pSet, "obj_type=%RU32\n", pObj->enmType);
1208 AssertRCReturn(rc, rc);
1209 rc = audioTestManifestWrite(pSet, "obj_name=%s\n", pObj->szName);
1210 AssertRCReturn(rc, rc);
1211
1212 switch (pObj->enmType)
1213 {
1214 case AUDIOTESTOBJTYPE_FILE:
1215 {
1216 rc = audioTestManifestWrite(pSet, "obj_size=%RU64\n", pObj->File.cbSize);
1217 AssertRCReturn(rc, rc);
1218 break;
1219 }
1220
1221 default:
1222 AssertFailed();
1223 break;
1224 }
1225
1226 /*
1227 * Write all meta data.
1228 */
1229 PAUDIOTESTOBJMETA pMeta;
1230 RTListForEach(&pObj->lstMeta, pMeta, AUDIOTESTOBJMETA, Node)
1231 {
1232 switch (pMeta->enmType)
1233 {
1234 case AUDIOTESTOBJMETADATATYPE_STRING:
1235 {
1236 rc = audioTestManifestWrite(pSet, (const char *)pMeta->pvMeta);
1237 AssertRCReturn(rc, rc);
1238 break;
1239 }
1240
1241 default:
1242 AssertFailed();
1243 break;
1244 }
1245 }
1246 }
1247
1248 rc = RTFileClose(pSet->f.hFile);
1249 if (RT_SUCCESS(rc))
1250 pSet->f.hFile = NIL_RTFILE;
1251 }
1252 else if (pSet->enmMode == AUDIOTESTSETMODE_VERIFY)
1253 {
1254 RTIniFileRelease(pSet->f.hIniFile);
1255 pSet->f.hIniFile = NIL_RTINIFILE;
1256
1257 rc = VINF_SUCCESS;
1258 }
1259 else
1260 AssertFailedStmt(rc = VERR_NOT_SUPPORTED);
1261
1262 return rc;
1263}
1264
1265/**
1266 * Physically wipes all related test set files off the disk.
1267 *
1268 * @returns VBox status code.
1269 * @param pSet Test set to wipe.
1270 */
1271int AudioTestSetWipe(PAUDIOTESTSET pSet)
1272{
1273 AssertPtrReturn(pSet, VERR_INVALID_POINTER);
1274
1275 int rc = VINF_SUCCESS;
1276 char szFilePath[RTPATH_MAX];
1277
1278 PAUDIOTESTOBJINT pObj;
1279 RTListForEach(&pSet->lstObj, pObj, AUDIOTESTOBJINT, Node)
1280 {
1281 int rc2 = audioTestObjClose(pObj);
1282 if (RT_SUCCESS(rc2))
1283 {
1284 rc2 = audioTestSetGetObjPath(pSet, szFilePath, sizeof(szFilePath), pObj->szName);
1285 if (RT_SUCCESS(rc2))
1286 rc2 = RTFileDelete(szFilePath);
1287 }
1288
1289 if (RT_SUCCESS(rc))
1290 rc = rc2;
1291 /* Keep going. */
1292 }
1293
1294 if (RT_SUCCESS(rc))
1295 {
1296 rc = RTPathJoin(szFilePath, sizeof(szFilePath), pSet->szPathAbs, AUDIOTEST_MANIFEST_FILE_STR);
1297 if (RT_SUCCESS(rc))
1298 rc = RTFileDelete(szFilePath);
1299 }
1300
1301 /* Remove the (hopefully now empty) directory. Otherwise let this fail. */
1302 if (RT_SUCCESS(rc))
1303 rc = RTDirRemove(pSet->szPathAbs);
1304
1305 return rc;
1306}
1307
1308/**
1309 * Creates and registers a new audio test object to the current running test.
1310 *
1311 * @returns VBox status code.
1312 * @param pSet Test set to create and register new object for.
1313 * @param pszName Name of new object to create.
1314 * @param pObj Where to return the pointer to the newly created object on success.
1315 */
1316int AudioTestSetObjCreateAndRegister(PAUDIOTESTSET pSet, const char *pszName, PAUDIOTESTOBJ pObj)
1317{
1318 AssertReturn(pSet->cTestsRunning == 1, VERR_WRONG_ORDER); /* No test nesting allowed. */
1319
1320 AssertPtrReturn(pszName, VERR_INVALID_POINTER);
1321
1322 PAUDIOTESTOBJINT pThis = (PAUDIOTESTOBJINT)RTMemAlloc(sizeof(AUDIOTESTOBJINT));
1323 AssertPtrReturn(pThis, VERR_NO_MEMORY);
1324
1325 audioTestObjInit(pThis);
1326
1327 if (RTStrPrintf2(pThis->szName, sizeof(pThis->szName), "%04RU32-%s", pSet->cObj, pszName) <= 0)
1328 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
1329
1330 /** @todo Generalize this function more once we have more object types. */
1331
1332 char szObjPathAbs[RTPATH_MAX];
1333 int rc = audioTestSetGetObjPath(pSet, szObjPathAbs, sizeof(szObjPathAbs), pThis->szName);
1334 if (RT_SUCCESS(rc))
1335 {
1336 rc = RTFileOpen(&pThis->File.hFile, szObjPathAbs, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
1337 if (RT_SUCCESS(rc))
1338 {
1339 pThis->enmType = AUDIOTESTOBJTYPE_FILE;
1340 pThis->cRefs = 1; /* Currently only 1:1 mapping. */
1341
1342 RTListAppend(&pSet->lstObj, &pThis->Node);
1343 pSet->cObj++;
1344
1345 /* Generate + set an UUID for the object and assign it to the current test. */
1346 rc = RTUuidCreate(&pThis->Uuid);
1347 AssertRCReturn(rc, rc);
1348 char szUuid[AUDIOTEST_MAX_OBJ_LEN];
1349 rc = RTUuidToStr(&pThis->Uuid, szUuid, sizeof(szUuid));
1350 AssertRCReturn(rc, rc);
1351
1352 rc = audioTestManifestWrite(pSet, "obj%RU32_uuid=%s\n", pSet->pTestCur->cObj, szUuid);
1353 AssertRCReturn(rc, rc);
1354
1355 AssertPtr(pSet->pTestCur);
1356 pSet->pTestCur->cObj++;
1357
1358 *pObj = pThis;
1359 }
1360 }
1361
1362 if (RT_FAILURE(rc))
1363 RTMemFree(pThis);
1364
1365 return rc;
1366}
1367
1368/**
1369 * Writes to a created audio test object.
1370 *
1371 * @returns VBox status code.
1372 * @param hObj Handle to the audio test object to write to.
1373 * @param pvBuf Pointer to data to write.
1374 * @param cbBuf Size (in bytes) of \a pvBuf to write.
1375 */
1376int AudioTestObjWrite(AUDIOTESTOBJ hObj, const void *pvBuf, size_t cbBuf)
1377{
1378 AUDIOTESTOBJINT *pThis = hObj;
1379
1380 /** @todo Generalize this function more once we have more object types. */
1381 AssertReturn(pThis->enmType == AUDIOTESTOBJTYPE_FILE, VERR_INVALID_PARAMETER);
1382
1383 return RTFileWrite(pThis->File.hFile, pvBuf, cbBuf, NULL);
1384}
1385
1386/**
1387 * Adds meta data to a test object as a string, va_list version.
1388 *
1389 * @returns VBox status code.
1390 * @param pObj Test object to add meta data for.
1391 * @param pszFormat Format string to add.
1392 * @param va Variable arguments list to use for the format string.
1393 */
1394static int audioTestObjAddMetadataStrV(PAUDIOTESTOBJINT pObj, const char *pszFormat, va_list va)
1395{
1396 PAUDIOTESTOBJMETA pMeta = (PAUDIOTESTOBJMETA)RTMemAlloc(sizeof(AUDIOTESTOBJMETA));
1397 AssertPtrReturn(pMeta, VERR_NO_MEMORY);
1398
1399 pMeta->pvMeta = RTStrAPrintf2V(pszFormat, va);
1400 AssertPtrReturn(pMeta->pvMeta, VERR_BUFFER_OVERFLOW);
1401 pMeta->cbMeta = RTStrNLen((const char *)pMeta->pvMeta, RTSTR_MAX);
1402
1403 pMeta->enmType = AUDIOTESTOBJMETADATATYPE_STRING;
1404
1405 RTListAppend(&pObj->lstMeta, &pMeta->Node);
1406
1407 return VINF_SUCCESS;
1408}
1409
1410/**
1411 * Adds meta data to a test object as a string.
1412 *
1413 * @returns VBox status code.
1414 * @param hObj Handle to the test object to add meta data for.
1415 * @param pszFormat Format string to add.
1416 * @param ... Variable arguments for the format string.
1417 */
1418int AudioTestObjAddMetadataStr(AUDIOTESTOBJ hObj, const char *pszFormat, ...)
1419{
1420 AUDIOTESTOBJINT *pThis = hObj;
1421
1422 va_list va;
1423
1424 va_start(va, pszFormat);
1425 int rc = audioTestObjAddMetadataStrV(pThis, pszFormat, va);
1426 va_end(va);
1427
1428 return rc;
1429}
1430
1431/**
1432 * Closes an opened audio test object.
1433 *
1434 * @returns VBox status code.
1435 * @param hObj Handle to the audio test object to close.
1436 */
1437int AudioTestObjClose(AUDIOTESTOBJ hObj)
1438{
1439 AUDIOTESTOBJINT *pThis = hObj;
1440
1441 if (!pThis)
1442 return VINF_SUCCESS;
1443
1444 audioTestObjFinalize(pThis);
1445
1446 return audioTestObjClose(pThis);
1447}
1448
1449/**
1450 * Begins a new test of a test set.
1451 *
1452 * @returns VBox status code.
1453 * @param pSet Test set to begin new test for.
1454 * @param pszDesc Test description.
1455 * @param pParms Test parameters to use.
1456 * @param ppEntry Where to return the new test
1457 */
1458int AudioTestSetTestBegin(PAUDIOTESTSET pSet, const char *pszDesc, PAUDIOTESTPARMS pParms, PAUDIOTESTENTRY *ppEntry)
1459{
1460 AssertReturn(pSet->cTestsRunning == 0, VERR_WRONG_ORDER); /* No test nesting allowed. */
1461
1462 PAUDIOTESTENTRY pEntry = (PAUDIOTESTENTRY)RTMemAllocZ(sizeof(AUDIOTESTENTRY));
1463 AssertPtrReturn(pEntry, VERR_NO_MEMORY);
1464
1465 int rc = RTStrCopy(pEntry->szDesc, sizeof(pEntry->szDesc), pszDesc);
1466 AssertRCReturn(rc, rc);
1467
1468 memcpy(&pEntry->Parms, pParms, sizeof(AUDIOTESTPARMS));
1469
1470 pEntry->pParent = pSet;
1471 pEntry->rc = VERR_IPE_UNINITIALIZED_STATUS;
1472
1473 rc = audioTestManifestWrite(pSet, "\n");
1474 AssertRCReturn(rc, rc);
1475
1476 rc = audioTestManifestWriteSectionHdr(pSet, "test_%04RU32", pSet->cTests);
1477 AssertRCReturn(rc, rc);
1478 rc = audioTestManifestWrite(pSet, "test_desc=%s\n", pszDesc);
1479 AssertRCReturn(rc, rc);
1480 rc = audioTestManifestWrite(pSet, "test_type=%RU32\n", pParms->enmType);
1481 AssertRCReturn(rc, rc);
1482 rc = audioTestManifestWrite(pSet, "test_delay_ms=%RU32\n", pParms->msDelay);
1483 AssertRCReturn(rc, rc);
1484 rc = audioTestManifestWrite(pSet, "audio_direction=%s\n", PDMAudioDirGetName(pParms->enmDir));
1485 AssertRCReturn(rc, rc);
1486
1487 rc = audioTestManifestWrite(pSet, "obj_count=");
1488 AssertRCReturn(rc, rc);
1489 pEntry->offObjCount = audioTestManifestGetOffsetAbs(pSet);
1490 rc = audioTestManifestWrite(pSet, "0000\n"); /* A bit messy, but does the trick for now. */
1491 AssertRCReturn(rc, rc);
1492
1493 switch (pParms->enmType)
1494 {
1495 case AUDIOTESTTYPE_TESTTONE_PLAY:
1496 RT_FALL_THROUGH();
1497 case AUDIOTESTTYPE_TESTTONE_RECORD:
1498 {
1499 rc = audioTestManifestWrite(pSet, "tone_freq_hz=%RU16\n", (uint16_t)pParms->TestTone.dbFreqHz);
1500 AssertRCReturn(rc, rc);
1501 rc = audioTestManifestWrite(pSet, "tone_prequel_ms=%RU32\n", pParms->TestTone.msPrequel);
1502 AssertRCReturn(rc, rc);
1503 rc = audioTestManifestWrite(pSet, "tone_duration_ms=%RU32\n", pParms->TestTone.msDuration);
1504 AssertRCReturn(rc, rc);
1505 rc = audioTestManifestWrite(pSet, "tone_sequel_ms=%RU32\n", pParms->TestTone.msSequel);
1506 AssertRCReturn(rc, rc);
1507 rc = audioTestManifestWrite(pSet, "tone_volume_percent=%RU32\n", pParms->TestTone.uVolumePercent);
1508 AssertRCReturn(rc, rc);
1509 rc = audioTestManifestWrite(pSet, "tone_pcm_hz=%RU32\n", PDMAudioPropsHz(&pParms->TestTone.Props));
1510 AssertRCReturn(rc, rc);
1511 rc = audioTestManifestWrite(pSet, "tone_pcm_channels=%RU8\n", PDMAudioPropsChannels(&pParms->TestTone.Props));
1512 AssertRCReturn(rc, rc);
1513 rc = audioTestManifestWrite(pSet, "tone_pcm_bits=%RU8\n", PDMAudioPropsSampleBits(&pParms->TestTone.Props));
1514 AssertRCReturn(rc, rc);
1515 rc = audioTestManifestWrite(pSet, "tone_pcm_is_signed=%RTbool\n", PDMAudioPropsIsSigned(&pParms->TestTone.Props));
1516 AssertRCReturn(rc, rc);
1517 break;
1518 }
1519
1520 default:
1521 AssertFailed();
1522 break;
1523 }
1524
1525 RTListAppend(&pSet->lstTest, &pEntry->Node);
1526
1527 pSet->cTests++;
1528 pSet->cTestsRunning++;
1529 pSet->pTestCur = pEntry;
1530
1531 *ppEntry = pEntry;
1532
1533 return rc;
1534}
1535
1536/**
1537 * Marks a running test as failed.
1538 *
1539 * @returns VBox status code.
1540 * @param pEntry Test to mark.
1541 * @param rc Error code.
1542 * @param pszErr Error description.
1543 */
1544int AudioTestSetTestFailed(PAUDIOTESTENTRY pEntry, int rc, const char *pszErr)
1545{
1546 AssertReturn(pEntry->pParent->cTestsRunning == 1, VERR_WRONG_ORDER); /* No test nesting allowed. */
1547 AssertReturn(pEntry->rc == VERR_IPE_UNINITIALIZED_STATUS, VERR_WRONG_ORDER);
1548
1549 pEntry->rc = rc;
1550
1551 int rc2 = audioTestManifestWrite(pEntry->pParent, "error_rc=%RI32\n", rc);
1552 AssertRCReturn(rc2, rc2);
1553 rc2 = audioTestManifestWrite(pEntry->pParent, "error_desc=%s\n", pszErr);
1554 AssertRCReturn(rc2, rc2);
1555
1556 pEntry->pParent->cTestsRunning--;
1557 pEntry->pParent->pTestCur = NULL;
1558
1559 return rc2;
1560}
1561
1562/**
1563 * Marks a running test as successfully done.
1564 *
1565 * @returns VBox status code.
1566 * @param pEntry Test to mark.
1567 */
1568int AudioTestSetTestDone(PAUDIOTESTENTRY pEntry)
1569{
1570 AssertReturn(pEntry->pParent->cTestsRunning == 1, VERR_WRONG_ORDER); /* No test nesting allowed. */
1571 AssertReturn(pEntry->rc == VERR_IPE_UNINITIALIZED_STATUS, VERR_WRONG_ORDER);
1572
1573 pEntry->rc = VINF_SUCCESS;
1574
1575 int rc2 = audioTestManifestWrite(pEntry->pParent, "error_rc=%RI32\n", VINF_SUCCESS);
1576 AssertRCReturn(rc2, rc2);
1577
1578 pEntry->pParent->cTestsRunning--;
1579 pEntry->pParent->pTestCur = NULL;
1580
1581 return rc2;
1582}
1583
1584/**
1585 * Returns whether a test is still running or not.
1586 *
1587 * @returns \c true if test is still running, or \c false if not.
1588 * @param pEntry Test to get running status for.
1589 */
1590bool AudioTestSetTestIsRunning(PAUDIOTESTENTRY pEntry)
1591{
1592 return (pEntry->rc == VERR_IPE_UNINITIALIZED_STATUS);
1593}
1594
1595/**
1596 * Packs a closed audio test so that it's ready for transmission.
1597 *
1598 * @returns VBox status code.
1599 * @param pSet Test set to pack.
1600 * @param pszOutDir Directory where to store the packed test set.
1601 * @param pszFileName Where to return the final name of the packed test set. Optional and can be NULL.
1602 * @param cbFileName Size (in bytes) of \a pszFileName.
1603 */
1604int AudioTestSetPack(PAUDIOTESTSET pSet, const char *pszOutDir, char *pszFileName, size_t cbFileName)
1605{
1606 AssertPtrReturn(pSet, VERR_INVALID_POINTER);
1607 AssertReturn(!pszFileName || cbFileName, VERR_INVALID_PARAMETER);
1608 AssertReturn(!audioTestManifestIsOpen(pSet), VERR_WRONG_ORDER);
1609
1610 AssertMsgReturn(pSet->cTests, ("No tests run yet"), VERR_INVALID_STATE);
1611 AssertMsgReturn(pSet->cTestsRunning == 0 , ("Some tests are still running"), VERR_INVALID_STATE);
1612
1613 /** @todo Check and deny if \a pszOutDir is part of the set's path. */
1614
1615 int rc = RTDirCreateFullPath(pszOutDir, 0755);
1616 if (RT_FAILURE(rc))
1617 return rc;
1618
1619 char szOutName[RT_ELEMENTS(AUDIOTEST_PATH_PREFIX_STR) + AUDIOTEST_TAG_MAX + 16];
1620 if (RTStrPrintf2(szOutName, sizeof(szOutName), "%s-%s%s",
1621 AUDIOTEST_PATH_PREFIX_STR, pSet->szTag, AUDIOTEST_ARCHIVE_SUFF_STR) <= 0)
1622 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
1623
1624 char szOutPath[RTPATH_MAX];
1625 rc = RTPathJoin(szOutPath, sizeof(szOutPath), pszOutDir, szOutName);
1626 AssertRCReturn(rc, rc);
1627
1628 const char *apszArgs[10];
1629 unsigned cArgs = 0;
1630
1631 apszArgs[cArgs++] = "vkat";
1632 apszArgs[cArgs++] = "--create";
1633 apszArgs[cArgs++] = "--gzip";
1634 apszArgs[cArgs++] = "--directory";
1635 apszArgs[cArgs++] = pSet->szPathAbs;
1636 apszArgs[cArgs++] = "--file";
1637 apszArgs[cArgs++] = szOutPath;
1638 apszArgs[cArgs++] = ".";
1639
1640 RTEXITCODE rcExit = RTZipTarCmd(cArgs, (char **)apszArgs);
1641 if (rcExit != RTEXITCODE_SUCCESS)
1642 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1643
1644 if (RT_SUCCESS(rc))
1645 {
1646 if (pszFileName)
1647 rc = RTStrCopy(pszFileName, cbFileName, szOutPath);
1648 }
1649
1650 return rc;
1651}
1652
1653/**
1654 * Returns whether a test set archive is packed (as .tar.gz by default) or
1655 * a plain directory.
1656 *
1657 * @returns \c true if packed (as .tar.gz), or \c false if not (directory).
1658 * @param pszPath Path to return packed staus for.
1659 */
1660bool AudioTestSetIsPacked(const char *pszPath)
1661{
1662 /** @todo Improve this, good enough for now. */
1663 return (RTStrIStr(pszPath, AUDIOTEST_ARCHIVE_SUFF_STR) != NULL);
1664}
1665
1666/**
1667 * Returns whether a test set has running (active) tests or not.
1668 *
1669 * @returns \c true if it has running tests, or \c false if not.
1670 * @param pSet Test set to return status for.
1671 */
1672bool AudioTestSetIsRunning(PAUDIOTESTSET pSet)
1673{
1674 return (pSet->cTestsRunning > 0);
1675}
1676
1677/**
1678 * Unpacks a formerly packed audio test set.
1679 *
1680 * @returns VBox status code.
1681 * @param pszFile Test set file to unpack. Must contain the absolute path.
1682 * @param pszOutDir Directory where to unpack the test set into.
1683 * If the directory does not exist it will be created.
1684 */
1685int AudioTestSetUnpack(const char *pszFile, const char *pszOutDir)
1686{
1687 AssertReturn(pszFile && pszOutDir, VERR_INVALID_PARAMETER);
1688
1689 int rc = VINF_SUCCESS;
1690
1691 if (!RTDirExists(pszOutDir))
1692 {
1693 rc = RTDirCreateFullPath(pszOutDir, 0755);
1694 if (RT_FAILURE(rc))
1695 return rc;
1696 }
1697
1698 const char *apszArgs[8];
1699 unsigned cArgs = 0;
1700
1701 apszArgs[cArgs++] = "vkat";
1702 apszArgs[cArgs++] = "--extract";
1703 apszArgs[cArgs++] = "--gunzip";
1704 apszArgs[cArgs++] = "--directory";
1705 apszArgs[cArgs++] = pszOutDir;
1706 apszArgs[cArgs++] = "--file";
1707 apszArgs[cArgs++] = pszFile;
1708
1709 RTEXITCODE rcExit = RTZipTarCmd(cArgs, (char **)apszArgs);
1710 if (rcExit != RTEXITCODE_SUCCESS)
1711 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1712
1713 return rc;
1714}
1715
1716/**
1717 * Retrieves an object handle of a specific test set section.
1718 *
1719 * @returns VBox status code.
1720 * @param pSet Test set the section contains.
1721 * @param pszSec Name of section to retrieve object handle for.
1722 * @param phSec Where to store the object handle on success.
1723 */
1724static int audioTestSetGetSection(PAUDIOTESTSET pSet, const char *pszSec, PAUDIOTESTOBJINT phSec)
1725{
1726 int rc = RTStrCopy(phSec->szSec, sizeof(phSec->szSec), pszSec);
1727 if (RT_FAILURE(rc))
1728 return rc;
1729
1730 phSec->pSet = pSet;
1731
1732 /** @todo Check for section existence. */
1733 RT_NOREF(pSet);
1734
1735 return VINF_SUCCESS;
1736}
1737
1738/**
1739 * Retrieves an object handle of a specific test.
1740 *
1741 * @returns VBox status code.
1742 * @param pSet Test set the test contains.
1743 * @param idxTst Index of test to retrieve the object handle for.
1744 * @param phTst Where to store the object handle on success.
1745 */
1746static int audioTestSetGetTest(PAUDIOTESTSET pSet, uint32_t idxTst, PAUDIOTESTOBJINT phTst)
1747{
1748 char szSec[AUDIOTEST_MAX_SEC_LEN];
1749 if (RTStrPrintf2(szSec, sizeof(szSec), "test_%04RU32", idxTst) <= 0)
1750 return VERR_BUFFER_OVERFLOW;
1751
1752 return audioTestSetGetSection(pSet, szSec, phTst);
1753}
1754
1755/**
1756 * Initializes a test object.
1757 *
1758 * @param pObj Object to initialize.
1759 */
1760static void audioTestObjInit(PAUDIOTESTOBJINT pObj)
1761{
1762 RT_BZERO(pObj, sizeof(AUDIOTESTOBJINT));
1763
1764 pObj->cRefs = 1;
1765
1766 RTListInit(&pObj->lstMeta);
1767}
1768
1769/**
1770 * Retrieves a child object of a specific parent object.
1771 *
1772 * @returns VBox status code.
1773 * @param pParent Parent object the child object contains.
1774 * @param idxObj Index of object to retrieve the object handle for.
1775 * @param pObj Where to store the object handle on success.
1776 */
1777static int audioTestObjGetChild(PAUDIOTESTOBJINT pParent, uint32_t idxObj, PAUDIOTESTOBJINT pObj)
1778{
1779 char szObj[AUDIOTEST_MAX_SEC_LEN];
1780 if (RTStrPrintf2(szObj, sizeof(szObj), "obj%RU32_uuid", idxObj) <= 0)
1781 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
1782
1783 char szUuid[AUDIOTEST_MAX_SEC_LEN];
1784 int rc = audioTestObjGetStr(pParent, szObj, szUuid, sizeof(szUuid));
1785 if (RT_SUCCESS(rc))
1786 {
1787 audioTestObjInit(pObj);
1788
1789 AssertReturn(RTStrPrintf2(pObj->szSec, sizeof(pObj->szSec), "obj_%s", szUuid) > 0, VERR_BUFFER_OVERFLOW);
1790
1791 /** @todo Check test section existence. */
1792
1793 pObj->pSet = pParent->pSet;
1794 }
1795
1796 return rc;
1797}
1798
1799/**
1800 * Verifies a value of a test verification job.
1801 *
1802 * @returns VBox status code.
1803 * @returns Error if the verification failed and test verification job has fKeepGoing not set.
1804 * @param pVerJob Verification job to verify value for.
1805 * @param pObjA Object handle A to verify value for.
1806 * @param pObjB Object handle B to verify value for.
1807 * @param pszKey Key to verify.
1808 * @param pszVal Value to verify.
1809 * @param pszErrFmt Error format string in case the verification failed.
1810 * @param ... Variable aruments for error format string.
1811 */
1812static int audioTestVerifyValue(PAUDIOTESTVERIFYJOB pVerJob,
1813 PAUDIOTESTOBJINT pObjA, PAUDIOTESTOBJINT pObjB, const char *pszKey, const char *pszVal, const char *pszErrFmt, ...)
1814{
1815 va_list va;
1816 va_start(va, pszErrFmt);
1817
1818 char szValA[_1K];
1819 int rc = audioTestObjGetStr(pObjA, pszKey, szValA, sizeof(szValA));
1820 if (RT_SUCCESS(rc))
1821 {
1822 char szValB[_1K];
1823 rc = audioTestObjGetStr(pObjB, pszKey, szValB, sizeof(szValB));
1824 if (RT_SUCCESS(rc))
1825 {
1826 if (RTStrCmp(szValA, szValB))
1827 {
1828 int rc2 = audioTestErrorDescAddError(pVerJob->pErr, pVerJob->idxTest,
1829 "Values are not equal ('%s' vs. '%s')", szValA, szValB);
1830 AssertRC(rc2);
1831 rc = VERR_WRONG_TYPE; /** @todo Fudge! */
1832 }
1833
1834 if (pszVal)
1835 {
1836 if (RTStrCmp(szValA, pszVal))
1837 {
1838 int rc2 = audioTestErrorDescAddError(pVerJob->pErr, pVerJob->idxTest,
1839 "Values don't match expected value (got '%s', expected '%s')", szValA, pszVal);
1840 AssertRC(rc2);
1841 rc = VERR_WRONG_TYPE; /** @todo Fudge! */
1842 }
1843 }
1844 }
1845 }
1846
1847 if (RT_FAILURE(rc))
1848 {
1849 int rc2 = audioTestErrorDescAddV(pVerJob->pErr, pVerJob->idxTest, rc, pszErrFmt, va);
1850 AssertRC(rc2);
1851 }
1852
1853 va_end(va);
1854
1855 return pVerJob->Opts.fKeepGoing ? VINF_SUCCESS : rc;
1856}
1857
1858/**
1859 * Opens an existing audio test object.
1860 *
1861 * @returns VBox status code.
1862 * @param pObj Object to open.
1863 */
1864static int audioTestObjOpen(PAUDIOTESTOBJINT pObj)
1865{
1866 AssertReturn(pObj->enmType == AUDIOTESTOBJTYPE_UNKNOWN, VERR_WRONG_ORDER);
1867
1868 char szFileName[AUDIOTEST_MAX_SEC_LEN];
1869 int rc = audioTestObjGetStr(pObj, "obj_name", szFileName, sizeof(szFileName));
1870 if (RT_SUCCESS(rc))
1871 {
1872 char szFilePath[RTPATH_MAX];
1873 rc = RTPathJoin(szFilePath, sizeof(szFilePath), pObj->pSet->szPathAbs, szFileName);
1874 if (RT_SUCCESS(rc))
1875 {
1876 /** @todo Check "obj_type". */
1877
1878 rc = RTFileOpen(&pObj->File.hFile, szFilePath, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
1879 if (RT_SUCCESS(rc))
1880 {
1881 int rc2 = RTStrCopy(pObj->szName, sizeof(pObj->szName), szFileName);
1882 AssertRC(rc2);
1883
1884 pObj->enmType = AUDIOTESTOBJTYPE_FILE;
1885 }
1886 }
1887 }
1888 return rc;
1889}
1890
1891/**
1892 * Closes an audio test set object.
1893 *
1894 * @returns VBox status code.
1895 * @param pObj Object to close.
1896 */
1897static int audioTestObjClose(PAUDIOTESTOBJINT pObj)
1898{
1899 int rc;
1900
1901 /** @todo Generalize this function more once we have more object types. */
1902 AssertReturn(pObj->enmType == AUDIOTESTOBJTYPE_FILE, VERR_INVALID_PARAMETER);
1903
1904 if (RTFileIsValid(pObj->File.hFile))
1905 {
1906 rc = RTFileClose(pObj->File.hFile);
1907 if (RT_SUCCESS(rc))
1908 pObj->File.hFile = NIL_RTFILE;
1909 }
1910 else
1911 rc = VINF_SUCCESS;
1912
1913 return rc;
1914}
1915
1916/**
1917 * Finalizes an audio test set object.
1918 *
1919 * @param pObj Test object to finalize.
1920 */
1921static void audioTestObjFinalize(PAUDIOTESTOBJINT pObj)
1922{
1923 /** @todo Generalize this function more once we have more object types. */
1924 AssertReturnVoid(pObj->enmType == AUDIOTESTOBJTYPE_FILE);
1925
1926 if (RTFileIsValid(pObj->File.hFile))
1927 pObj->File.cbSize = RTFileTell(pObj->File.hFile);
1928}
1929
1930/**
1931 * Retrieves tone PCM properties of an object.
1932 *
1933 * @returns VBox status code.
1934 * @param pObj Object to retrieve PCM properties for.
1935 * @param pProps Where to store the PCM properties on success.
1936 */
1937static int audioTestObjGetTonePcmProps(PAUDIOTESTOBJINT pObj, PPDMAUDIOPCMPROPS pProps)
1938{
1939 int rc;
1940 uint32_t uHz;
1941 rc = audioTestObjGetUInt32(pObj, "tone_pcm_hz", &uHz);
1942 AssertRCReturn(rc, rc);
1943 uint8_t cBits;
1944 rc = audioTestObjGetUInt8(pObj, "tone_pcm_bits", &cBits);
1945 AssertRCReturn(rc, rc);
1946 uint8_t cChan;
1947 rc = audioTestObjGetUInt8(pObj, "tone_pcm_channels", &cChan);
1948 AssertRCReturn(rc, rc);
1949 bool fSigned;
1950 rc = audioTestObjGetBool(pObj, "tone_pcm_is_signed", &fSigned);
1951 AssertRCReturn(rc, rc);
1952
1953 PDMAudioPropsInit(pProps, (cBits / 8), fSigned, cChan, uHz);
1954
1955 return VINF_SUCCESS;
1956}
1957
1958/**
1959 * Structure for keeping file comparison parameters for one file.
1960 */
1961typedef struct AUDIOTESTFILECMPPARMS
1962{
1963 /** File handle to file to compare. */
1964 RTFILE hFile;
1965 /** Absolute offset (in bytes) to start comparing.
1966 * Ignored when set to 0. */
1967 uint64_t offStart;
1968 /** Size (in bytes) of area to compare.
1969 * Starts at \a offStart. */
1970 uint64_t cbSize;
1971} AUDIOTESTFILECMPPARMS;
1972/** Pointer to file comparison parameters for one file. */
1973typedef AUDIOTESTFILECMPPARMS *PAUDIOTESTFILECMPPARMS;
1974
1975/**
1976 * Finds differences in two audio test files by binary comparing chunks.
1977 *
1978 * @returns Number of differences. 0 means they are equal (but not necessarily identical).
1979 * @param pVerJob Verification job to verify PCM data for.
1980 * @param pCmpA File comparison parameters to file A to compare file B with.
1981 * @param pCmpB File comparison parameters to file B to compare file A with.
1982 * @param pToneParms Tone parameters to use for comparison.
1983 */
1984static uint32_t audioTestFilesFindDiffsBinary(PAUDIOTESTVERIFYJOB pVerJob,
1985 PAUDIOTESTFILECMPPARMS pCmpA, PAUDIOTESTFILECMPPARMS pCmpB,
1986 PAUDIOTESTTONEPARMS pToneParms)
1987{
1988 uint8_t auBufA[_4K];
1989 uint8_t auBufB[_4K];
1990
1991 int rc = RTFileSeek(pCmpA->hFile, pCmpA->offStart, RTFILE_SEEK_BEGIN, NULL);
1992 AssertRC(rc);
1993
1994 rc = RTFileSeek(pCmpB->hFile, pCmpB->offStart, RTFILE_SEEK_BEGIN, NULL);
1995 AssertRC(rc);
1996
1997 RT_NOREF(pToneParms);
1998 uint32_t const cbChunkSize = 4; //PDMAudioPropsMilliToBytes(&pToneParms->Props, 5 /* ms */);
1999
2000 uint64_t offCur = 0;
2001 uint64_t offLastDiff = 0;
2002 uint32_t cDiffs = 0;
2003 uint64_t cbToCompare = RT_MIN(pCmpA->cbSize, pCmpB->cbSize);
2004 while (cbToCompare)
2005 {
2006 size_t cbReadA;
2007 rc = RTFileRead(pCmpA->hFile, auBufA, RT_MIN(cbToCompare, cbChunkSize), &cbReadA);
2008 AssertRCBreak(rc);
2009 size_t cbReadB;
2010 rc = RTFileRead(pCmpB->hFile, auBufB, RT_MIN(cbToCompare, cbChunkSize), &cbReadB);
2011 AssertRCBreak(rc);
2012 AssertBreakStmt(cbReadA == cbReadB, rc = VERR_INVALID_PARAMETER); /** @todo Find a better rc. */
2013
2014 if (memcmp(auBufA, auBufB, RT_MIN(cbReadA, cbReadB)) != 0)
2015 {
2016 if (offLastDiff == 0) /* No consequitive different chunk? Count as new then. */
2017 {
2018 cDiffs++;
2019 offLastDiff = offCur;
2020 }
2021 }
2022 else /* Reset and count next difference as new then. */
2023 {
2024 if (cDiffs)
2025 {
2026 int rc2 = audioTestErrorDescAddInfo(pVerJob->pErr, pVerJob->idxTest, "Chunks differ: A [%RU64-%RU64] vs. B [%RU64-%RU64] (%RU64 bytes, %RU64ms)",
2027 pCmpA->offStart + offLastDiff, pCmpA->offStart + offCur,
2028 pCmpB->offStart + offLastDiff, pCmpB->offStart + offCur, offCur - offLastDiff,
2029 PDMAudioPropsBytesToMilli(&pToneParms->Props, offCur - offLastDiff));
2030 AssertRC(rc2);
2031 }
2032 offLastDiff = 0;
2033 }
2034
2035 AssertBreakStmt(cbToCompare >= cbReadA, VERR_INTERNAL_ERROR);
2036 cbToCompare -= cbReadA;
2037 offCur += cbReadA;
2038 }
2039
2040 return cDiffs;
2041}
2042
2043#define CHECK_RC_MAYBE_RET(a_rc, a_pVerJob) \
2044 if (RT_FAILURE(a_rc)) \
2045 { \
2046 if (!a_pVerJob->Opts.fKeepGoing) \
2047 return VINF_SUCCESS; \
2048 }
2049
2050#define CHECK_RC_MSG_MAYBE_RET(a_rc, a_pVerJob, a_Msg) \
2051 if (RT_FAILURE(a_rc)) \
2052 { \
2053 int rc3 = audioTestErrorDescAddError(a_pVerJob->pErr, a_pVerJob->idxTest, a_Msg); \
2054 AssertRC(rc3); \
2055 if (!a_pVerJob->Opts.fKeepGoing) \
2056 return VINF_SUCCESS; \
2057 }
2058
2059#define CHECK_RC_MSG_VA_MAYBE_RET(a_rc, a_pVerJob, a_Msg, ...) \
2060 if (RT_FAILURE(a_rc)) \
2061 { \
2062 int rc3 = audioTestErrorDescAddError(a_pVerJob->pErr, a_pVerJob->idxTest, a_Msg, __VA_ARGS__); \
2063 AssertRC(rc3); \
2064 if (!a_pVerJob->Opts.fKeepGoing) \
2065 return VINF_SUCCESS; \
2066
2067/**
2068 * Does the actual PCM data verification of a test tone.
2069 *
2070 * @returns VBox status code.
2071 * @param pVerJob Verification job to verify PCM data for.
2072 * @param phTestA Test handle A of test to verify PCM data for.
2073 * @param phTestB Test handle B of test to verify PCM data for.
2074 */
2075static int audioTestVerifyTestToneData(PAUDIOTESTVERIFYJOB pVerJob, PAUDIOTESTOBJINT phTestA, PAUDIOTESTOBJINT phTestB)
2076{
2077 int rc;
2078
2079 /** @todo For now ASSUME that we only have one object per test. */
2080
2081 AUDIOTESTOBJINT ObjA;
2082 rc = audioTestObjGetChild(phTestA, 0 /* idxObj */, &ObjA);
2083 CHECK_RC_MSG_MAYBE_RET(rc, pVerJob, "Unable to get object A");
2084
2085 rc = audioTestObjOpen(&ObjA);
2086 CHECK_RC_MSG_MAYBE_RET(rc, pVerJob, "Unable to open object A");
2087
2088 AUDIOTESTOBJINT ObjB;
2089 rc = audioTestObjGetChild(phTestB, 0 /* idxObj */, &ObjB);
2090 CHECK_RC_MSG_MAYBE_RET(rc, pVerJob, "Unable to get object B");
2091
2092 rc = audioTestObjOpen(&ObjB);
2093 CHECK_RC_MSG_MAYBE_RET(rc, pVerJob, "Unable to open object B");
2094
2095 /*
2096 * Start with most obvious methods first.
2097 */
2098 uint64_t cbSizeA, cbSizeB;
2099 rc = RTFileQuerySize(ObjA.File.hFile, &cbSizeA);
2100 AssertRCReturn(rc, rc);
2101 rc = RTFileQuerySize(ObjB.File.hFile, &cbSizeB);
2102 AssertRCReturn(rc, rc);
2103
2104 if (!cbSizeA)
2105 {
2106 int rc2 = audioTestErrorDescAddError(pVerJob->pErr, pVerJob->idxTest, "File '%s' is empty", ObjA.szName);
2107 AssertRC(rc2);
2108 }
2109
2110 if (!cbSizeB)
2111 {
2112 int rc2 = audioTestErrorDescAddError(pVerJob->pErr, pVerJob->idxTest, "File '%s' is empty", ObjB.szName);
2113 AssertRC(rc2);
2114 }
2115
2116 if (cbSizeA != cbSizeB)
2117 {
2118 size_t const cbDiffAbs = cbSizeA > cbSizeB ? cbSizeA - cbSizeB : cbSizeB - cbSizeA;
2119
2120 int rc2 = audioTestErrorDescAddInfo(pVerJob->pErr, pVerJob->idxTest, "File '%s' is %zu bytes (%RU64ms)",
2121 ObjA.szName, cbSizeA, PDMAudioPropsBytesToMilli(&pVerJob->PCMProps, cbSizeA));
2122 AssertRC(rc2);
2123 rc2 = audioTestErrorDescAddInfo(pVerJob->pErr, pVerJob->idxTest, "File '%s' is %zu bytes (%RU64ms)",
2124 ObjB.szName, cbSizeB, PDMAudioPropsBytesToMilli(&pVerJob->PCMProps, cbSizeB));
2125 AssertRC(rc2);
2126
2127 uint8_t const uSizeDiffPercentAbs
2128 = cbSizeA > cbSizeB ? 100 - ((cbSizeB * 100) / cbSizeA) : 100 - ((cbSizeA * 100) / cbSizeB);
2129
2130 if (uSizeDiffPercentAbs > pVerJob->Opts.uMaxSizePercent)
2131 {
2132 rc2 = audioTestErrorDescAddError(pVerJob->pErr, pVerJob->idxTest,
2133 "File '%s' is %RU8%% (%zu bytes, %RU64ms) %s than '%s' (threshold is %RU8%%)",
2134 ObjA.szName,
2135 uSizeDiffPercentAbs,
2136 cbDiffAbs, PDMAudioPropsBytesToMilli(&pVerJob->PCMProps, (uint32_t)cbDiffAbs),
2137 cbSizeA > cbSizeB ? "bigger" : "smaller",
2138 ObjB.szName, pVerJob->Opts.uMaxSizePercent);
2139 AssertRC(rc2);
2140 }
2141 }
2142
2143 /** @todo For now we only support comparison of data which do have identical PCM properties! */
2144
2145 AUDIOTESTTONEPARMS ToneParmsA;
2146 RT_ZERO(ToneParmsA);
2147 ToneParmsA.Props = pVerJob->PCMProps;
2148
2149 AUDIOTESTFILECMPPARMS FileA;
2150 RT_ZERO(FileA);
2151 FileA.hFile = ObjA.File.hFile;
2152 FileA.offStart = audioTestToneFileFind(ObjA.File.hFile, true /* fFindSilence */, 0 /* uOff */, &ToneParmsA);
2153 FileA.cbSize = RT_MIN(audioTestToneFileFind(ObjA.File.hFile, false /* fFindSilence */, FileA.offStart, &ToneParmsA),
2154 cbSizeA);
2155
2156 AUDIOTESTTONEPARMS ToneParmsB;
2157 RT_ZERO(ToneParmsB);
2158 ToneParmsB.Props = pVerJob->PCMProps;
2159
2160 AUDIOTESTFILECMPPARMS FileB;
2161 RT_ZERO(FileB);
2162 FileB.hFile = ObjB.File.hFile;
2163 FileB.offStart = audioTestToneFileFind(ObjB.File.hFile, true /* fFindSilence */, 0 /* uOff */, &ToneParmsB);
2164 FileB.cbSize = RT_MIN(audioTestToneFileFind(ObjB.File.hFile, false /* fFindSilence */, FileB.offStart, &ToneParmsB),
2165 cbSizeB);
2166
2167 int rc2 = audioTestErrorDescAddInfo(pVerJob->pErr, pVerJob->idxTest, "File A ('%s'): cbOff=%RU64 cbSize=%RU64, cbFileSize=%RU64\n",
2168 ObjA.szName, FileA.offStart, FileA.cbSize, cbSizeA);
2169 AssertRC(rc2);
2170
2171 rc = audioTestErrorDescAddInfo(pVerJob->pErr, pVerJob->idxTest, "File B ('%s'): cbOff=%RU64, cbSize=%RU64, cbFileSize=%RU64\n",
2172 ObjB.szName, FileB.offStart, FileB.cbSize, cbSizeB);
2173 AssertRC(rc2);
2174
2175 uint32_t const cDiffs = audioTestFilesFindDiffsBinary(pVerJob, &FileA, &FileB, &ToneParmsA);
2176
2177 if (cDiffs > pVerJob->Opts.cMaxDiff)
2178 {
2179 rc2 = audioTestErrorDescAddError(pVerJob->pErr, pVerJob->idxTest,
2180 "Files '%s' and '%s' have too many different chunks (%RU32 vs. %RU32)",
2181 ObjA.szName, ObjB.szName, cDiffs, pVerJob->Opts.cMaxDiff);
2182 AssertRC(rc2);
2183 }
2184
2185 if (AudioTestErrorDescFailed(pVerJob->pErr))
2186 {
2187 rc2 = audioTestErrorDescAddInfo(pVerJob->pErr, pVerJob->idxTest, "Files '%s' and '%s' do not match",
2188 ObjA.szName, ObjB.szName);
2189 AssertRC(rc2);
2190 }
2191
2192 rc = audioTestObjClose(&ObjA);
2193 AssertRCReturn(rc, rc);
2194 rc = audioTestObjClose(&ObjB);
2195 AssertRCReturn(rc, rc);
2196
2197 return rc;
2198}
2199
2200/**
2201 * Verifies a test tone test.
2202 *
2203 * @returns VBox status code.
2204 * @returns Error if the verification failed and test verification job has fKeepGoing not set.
2205 * @retval VERR_
2206 * @param pVerJob Verification job to verify test tone for.
2207 * @param phTestA Test handle of test tone A to verify tone B with.
2208 * @param phTestB Test handle of test tone B to verify tone A with.*
2209 */
2210static int audioTestVerifyTestTone(PAUDIOTESTVERIFYJOB pVerJob, PAUDIOTESTOBJINT phTestA, PAUDIOTESTOBJINT phTestB)
2211{
2212 int rc;
2213
2214 /*
2215 * Verify test parameters.
2216 * More important items have precedence.
2217 */
2218 rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "error_rc", "0", "Test was reported as failed");
2219 CHECK_RC_MAYBE_RET(rc, pVerJob);
2220 rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "obj_count", NULL, "Object counts don't match");
2221 CHECK_RC_MAYBE_RET(rc, pVerJob);
2222 rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "tone_freq_hz", NULL, "Tone frequency doesn't match");
2223 CHECK_RC_MAYBE_RET(rc, pVerJob);
2224 rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "tone_prequel_ms", NULL, "Tone prequel (ms) doesn't match");
2225 CHECK_RC_MAYBE_RET(rc, pVerJob);
2226 rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "tone_duration_ms", NULL, "Tone duration (ms) doesn't match");
2227 CHECK_RC_MAYBE_RET(rc, pVerJob);
2228 rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "tone_sequel_ms", NULL, "Tone sequel (ms) doesn't match");
2229 CHECK_RC_MAYBE_RET(rc, pVerJob);
2230 rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "tone_volume_percent", NULL, "Tone volume (percent) doesn't match");
2231 CHECK_RC_MAYBE_RET(rc, pVerJob);
2232 rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "tone_pcm_hz", NULL, "Tone PCM Hz doesn't match");
2233 CHECK_RC_MAYBE_RET(rc, pVerJob);
2234 rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "tone_pcm_channels", NULL, "Tone PCM channels don't match");
2235 CHECK_RC_MAYBE_RET(rc, pVerJob);
2236 rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "tone_pcm_bits", NULL, "Tone PCM bits don't match");
2237 CHECK_RC_MAYBE_RET(rc, pVerJob);
2238 rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "tone_pcm_is_signed", NULL, "Tone PCM signed bit doesn't match");
2239 CHECK_RC_MAYBE_RET(rc, pVerJob);
2240
2241 rc = audioTestObjGetTonePcmProps(phTestA, &pVerJob->PCMProps);
2242 CHECK_RC_MAYBE_RET(rc, pVerJob);
2243
2244 /*
2245 * Now the fun stuff, PCM data analysis.
2246 */
2247 rc = audioTestVerifyTestToneData(pVerJob, phTestA, phTestB);
2248 if (RT_FAILURE(rc))
2249 {
2250 int rc2 = audioTestErrorDescAddError(pVerJob->pErr, pVerJob->idxTest, "Verififcation of test tone data failed\n");
2251 AssertRC(rc2);
2252 }
2253
2254 return VINF_SUCCESS;
2255}
2256
2257/**
2258 * Verifies an opened audio test set, extended version.
2259 *
2260 * @returns VBox status code.
2261 * @param pSetA Test set A to verify.
2262 * @param pSetB Test set to verify test set A with.
2263 * @param pOpts Verification options to use.
2264 * @param pErrDesc Where to return the test verification errors.
2265 *
2266 * @note Test verification errors have to be checked for errors, regardless of the
2267 * actual return code.
2268 * @note Uses the standard verification options. Use AudioTestSetVerifyEx() to specify
2269 * own options.
2270 */
2271int AudioTestSetVerifyEx(PAUDIOTESTSET pSetA, PAUDIOTESTSET pSetB, PAUDIOTESTVERIFYOPTS pOpts, PAUDIOTESTERRORDESC pErrDesc)
2272{
2273 AssertPtrReturn(pSetA, VERR_INVALID_POINTER);
2274 AssertPtrReturn(pSetB, VERR_INVALID_POINTER);
2275 AssertReturn(audioTestManifestIsOpen(pSetA), VERR_WRONG_ORDER);
2276 AssertReturn(audioTestManifestIsOpen(pSetB), VERR_WRONG_ORDER);
2277 AssertPtrReturn(pOpts, VERR_INVALID_POINTER);
2278
2279 /* We ASSUME the caller has not init'd pErrDesc. */
2280 audioTestErrorDescInit(pErrDesc);
2281
2282 AUDIOTESTVERIFYJOB VerJob;
2283 RT_ZERO(VerJob);
2284 VerJob.pErr = pErrDesc;
2285 VerJob.pSetA = pSetA;
2286 VerJob.pSetB = pSetB;
2287
2288 memcpy(&VerJob.Opts, pOpts, sizeof(AUDIOTESTVERIFYOPTS));
2289
2290 PAUDIOTESTVERIFYJOB pVerJob = &VerJob;
2291
2292 int rc;
2293
2294 /*
2295 * Compare obvious values first.
2296 */
2297 AUDIOTESTOBJINT hHdrA;
2298 rc = audioTestSetGetSection(pVerJob->pSetA, AUDIOTEST_SEC_HDR_STR, &hHdrA);
2299 CHECK_RC_MAYBE_RET(rc, pVerJob);
2300
2301 AUDIOTESTOBJINT hHdrB;
2302 rc = audioTestSetGetSection(pVerJob->pSetB, AUDIOTEST_SEC_HDR_STR, &hHdrB);
2303 CHECK_RC_MAYBE_RET(rc, pVerJob);
2304
2305 rc = audioTestVerifyValue(&VerJob, &hHdrA, &hHdrB, "magic", "vkat_ini", "Manifest magic wrong");
2306 CHECK_RC_MAYBE_RET(rc, pVerJob);
2307 rc = audioTestVerifyValue(&VerJob, &hHdrA, &hHdrB, "ver", "1" , "Manifest version wrong");
2308 CHECK_RC_MAYBE_RET(rc, pVerJob);
2309 rc = audioTestVerifyValue(&VerJob, &hHdrA, &hHdrB, "tag", NULL, "Manifest tags don't match");
2310 CHECK_RC_MAYBE_RET(rc, pVerJob);
2311 rc = audioTestVerifyValue(&VerJob, &hHdrA, &hHdrB, "test_count", NULL, "Test counts don't match");
2312 CHECK_RC_MAYBE_RET(rc, pVerJob);
2313 rc = audioTestVerifyValue(&VerJob, &hHdrA, &hHdrB, "obj_count", NULL, "Object counts don't match");
2314 CHECK_RC_MAYBE_RET(rc, pVerJob);
2315
2316 /*
2317 * Compare ran tests.
2318 */
2319 uint32_t cTests;
2320 rc = audioTestObjGetUInt32(&hHdrA, "test_count", &cTests);
2321 AssertRCReturn(rc, rc);
2322
2323 for (uint32_t i = 0; i < cTests; i++)
2324 {
2325 VerJob.idxTest = i;
2326
2327 AUDIOTESTOBJINT hTestA;
2328 rc = audioTestSetGetTest(VerJob.pSetA, i, &hTestA);
2329 CHECK_RC_MSG_MAYBE_RET(rc, pVerJob, "Test A not found");
2330
2331 AUDIOTESTOBJINT hTestB;
2332 rc = audioTestSetGetTest(VerJob.pSetB, i, &hTestB);
2333 CHECK_RC_MSG_MAYBE_RET(rc, pVerJob, "Test B not found");
2334
2335 AUDIOTESTTYPE enmTestTypeA = AUDIOTESTTYPE_INVALID;
2336 rc = audioTestObjGetUInt32(&hTestA, "test_type", (uint32_t *)&enmTestTypeA);
2337 CHECK_RC_MSG_MAYBE_RET(rc, pVerJob, "Test type A not found");
2338
2339 AUDIOTESTTYPE enmTestTypeB = AUDIOTESTTYPE_INVALID;
2340 rc = audioTestObjGetUInt32(&hTestB, "test_type", (uint32_t *)&enmTestTypeB);
2341 CHECK_RC_MSG_MAYBE_RET(rc, pVerJob, "Test type B not found");
2342
2343 switch (enmTestTypeA)
2344 {
2345 case AUDIOTESTTYPE_TESTTONE_PLAY:
2346 {
2347 if (enmTestTypeB == AUDIOTESTTYPE_TESTTONE_RECORD)
2348 rc = audioTestVerifyTestTone(&VerJob, &hTestA, &hTestB);
2349 else
2350 rc = audioTestErrorDescAddError(pErrDesc, i, "Playback test types don't match (set A=%#x, set B=%#x)",
2351 enmTestTypeA, enmTestTypeB);
2352 break;
2353 }
2354
2355 case AUDIOTESTTYPE_TESTTONE_RECORD:
2356 {
2357 if (enmTestTypeB == AUDIOTESTTYPE_TESTTONE_PLAY)
2358 rc = audioTestVerifyTestTone(&VerJob, &hTestB, &hTestA);
2359 else
2360 rc = audioTestErrorDescAddError(pErrDesc, i, "Recording test types don't match (set A=%#x, set B=%#x)",
2361 enmTestTypeA, enmTestTypeB);
2362 break;
2363 }
2364
2365 case AUDIOTESTTYPE_INVALID:
2366 rc = VERR_INVALID_PARAMETER;
2367 break;
2368
2369 default:
2370 rc = VERR_NOT_IMPLEMENTED;
2371 break;
2372 }
2373
2374 AssertRC(rc);
2375 }
2376
2377 /* Only return critical stuff not related to actual testing here. */
2378 return VINF_SUCCESS;
2379}
2380
2381/**
2382 * Initializes audio test verification options in a strict manner.
2383 *
2384 * @param pOpts Verification options to initialize.
2385 */
2386void AudioTestSetVerifyOptsInitStrict(PAUDIOTESTVERIFYOPTS pOpts)
2387{
2388 RT_BZERO(pOpts, sizeof(AUDIOTESTVERIFYOPTS));
2389
2390 pOpts->fKeepGoing = true;
2391 pOpts->cMaxDiff = 0; /* By default we're very strict and consider any diff as being erroneous. */
2392 pOpts->uMaxSizePercent = 0; /* Ditto for size difference. */
2393}
2394
2395/**
2396 * Initializes audio test verification options with default values (strict!).
2397 *
2398 * @param pOpts Verification options to initialize.
2399 */
2400void AudioTestSetVerifyOptsInit(PAUDIOTESTVERIFYOPTS pOpts)
2401{
2402 AudioTestSetVerifyOptsInitStrict(pOpts);
2403}
2404
2405/**
2406 * Returns whether two audio test verification options are equal.
2407 *
2408 * @returns \c true if equal, or \c false if not.
2409 * @param pOptsA Options A to compare.
2410 * @param pOptsB Options B to compare Options A with.
2411 */
2412bool AudioTestSetVerifyOptsAreEqual(PAUDIOTESTVERIFYOPTS pOptsA, PAUDIOTESTVERIFYOPTS pOptsB)
2413{
2414 if (pOptsA == pOptsB)
2415 return true;
2416
2417 return ( pOptsA->cMaxDiff == pOptsB->cMaxDiff
2418 && pOptsA->fKeepGoing == pOptsB->fKeepGoing
2419 && pOptsA->uMaxDiffPercent == pOptsB->uMaxDiffPercent
2420 && pOptsA->uMaxSizePercent == pOptsB->uMaxSizePercent);
2421}
2422
2423/**
2424 * Verifies an opened audio test set.
2425 *
2426 * @returns VBox status code.
2427 * @param pSetA Test set A to verify.
2428 * @param pSetB Test set to verify test set A with.
2429 * @param pErrDesc Where to return the test verification errors.
2430 *
2431 * @note Test verification errors have to be checked for errors, regardless of the
2432 * actual return code.
2433 * @note Uses the standard verification options (strict!).
2434 * Use AudioTestSetVerifyEx() to specify own options.
2435 */
2436int AudioTestSetVerify(PAUDIOTESTSET pSetA, PAUDIOTESTSET pSetB, PAUDIOTESTERRORDESC pErrDesc)
2437{
2438 AUDIOTESTVERIFYOPTS Opts;
2439 AudioTestSetVerifyOptsInitStrict(&Opts);
2440
2441 return AudioTestSetVerifyEx(pSetA,pSetB, &Opts, pErrDesc);
2442}
2443
2444#undef CHECK_RC_MAYBE_RET
2445#undef CHECK_RC_MSG_MAYBE_RET
2446
2447
2448/*********************************************************************************************************************************
2449* WAVE File Reader. *
2450*********************************************************************************************************************************/
2451
2452/**
2453 * Counts the number of set bits in @a fMask.
2454 */
2455static unsigned audioTestWaveCountBits(uint32_t fMask)
2456{
2457 unsigned cBits = 0;
2458 while (fMask)
2459 {
2460 if (fMask & 1)
2461 cBits++;
2462 fMask >>= 1;
2463 }
2464 return cBits;
2465}
2466
2467/**
2468 * Opens a wave (.WAV) file for reading.
2469 *
2470 * @returns VBox status code.
2471 * @param pszFile The file to open.
2472 * @param pWaveFile The open wave file structure to fill in on success.
2473 * @param pErrInfo Where to return addition error details on failure.
2474 */
2475int AudioTestWaveFileOpen(const char *pszFile, PAUDIOTESTWAVEFILE pWaveFile, PRTERRINFO pErrInfo)
2476{
2477 pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC_DEAD;
2478 RT_ZERO(pWaveFile->Props);
2479 pWaveFile->hFile = NIL_RTFILE;
2480 int rc = RTFileOpen(&pWaveFile->hFile, pszFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
2481 if (RT_FAILURE(rc))
2482 return RTErrInfoSet(pErrInfo, rc, "RTFileOpen failed");
2483 uint64_t cbFile = 0;
2484 rc = RTFileQuerySize(pWaveFile->hFile, &cbFile);
2485 if (RT_SUCCESS(rc))
2486 {
2487 union
2488 {
2489 uint8_t ab[512];
2490 struct
2491 {
2492 RTRIFFHDR Hdr;
2493 union
2494 {
2495 RTRIFFWAVEFMTCHUNK Fmt;
2496 RTRIFFWAVEFMTEXTCHUNK FmtExt;
2497 } u;
2498 } Wave;
2499 RTRIFFLIST List;
2500 RTRIFFCHUNK Chunk;
2501 RTRIFFWAVEDATACHUNK Data;
2502 } uBuf;
2503
2504 rc = RTFileRead(pWaveFile->hFile, &uBuf.Wave, sizeof(uBuf.Wave), NULL);
2505 if (RT_SUCCESS(rc))
2506 {
2507 rc = VERR_VFS_UNKNOWN_FORMAT;
2508 if ( uBuf.Wave.Hdr.uMagic == RTRIFFHDR_MAGIC
2509 && uBuf.Wave.Hdr.uFileType == RTRIFF_FILE_TYPE_WAVE
2510 && uBuf.Wave.u.Fmt.Chunk.uMagic == RTRIFFWAVEFMT_MAGIC
2511 && uBuf.Wave.u.Fmt.Chunk.cbChunk >= sizeof(uBuf.Wave.u.Fmt.Data))
2512 {
2513 if (uBuf.Wave.Hdr.cbFile != cbFile - sizeof(RTRIFFCHUNK))
2514 RTErrInfoSetF(pErrInfo, rc, "File size mismatch: %#x, actual %#RX64 (ignored)",
2515 uBuf.Wave.Hdr.cbFile, cbFile - sizeof(RTRIFFCHUNK));
2516 rc = VERR_VFS_BOGUS_FORMAT;
2517 if ( uBuf.Wave.u.Fmt.Data.uFormatTag != RTRIFFWAVEFMT_TAG_PCM
2518 && uBuf.Wave.u.Fmt.Data.uFormatTag != RTRIFFWAVEFMT_TAG_EXTENSIBLE)
2519 RTErrInfoSetF(pErrInfo, rc, "Unsupported uFormatTag value: %#x (expected %#x or %#x)",
2520 uBuf.Wave.u.Fmt.Data.uFormatTag, RTRIFFWAVEFMT_TAG_PCM, RTRIFFWAVEFMT_TAG_EXTENSIBLE);
2521 else if ( uBuf.Wave.u.Fmt.Data.cBitsPerSample != 8
2522 && uBuf.Wave.u.Fmt.Data.cBitsPerSample != 16
2523 /* && uBuf.Wave.u.Fmt.Data.cBitsPerSample != 24 - not supported by our stack */
2524 && uBuf.Wave.u.Fmt.Data.cBitsPerSample != 32)
2525 RTErrInfoSetF(pErrInfo, rc, "Unsupported cBitsPerSample value: %u", uBuf.Wave.u.Fmt.Data.cBitsPerSample);
2526 else if ( uBuf.Wave.u.Fmt.Data.cChannels < 1
2527 || uBuf.Wave.u.Fmt.Data.cChannels >= 16)
2528 RTErrInfoSetF(pErrInfo, rc, "Unsupported cChannels value: %u (expected 1..15)", uBuf.Wave.u.Fmt.Data.cChannels);
2529 else if ( uBuf.Wave.u.Fmt.Data.uHz < 4096
2530 || uBuf.Wave.u.Fmt.Data.uHz > 768000)
2531 RTErrInfoSetF(pErrInfo, rc, "Unsupported uHz value: %u (expected 4096..768000)", uBuf.Wave.u.Fmt.Data.uHz);
2532 else if (uBuf.Wave.u.Fmt.Data.cbFrame != uBuf.Wave.u.Fmt.Data.cChannels * uBuf.Wave.u.Fmt.Data.cBitsPerSample / 8)
2533 RTErrInfoSetF(pErrInfo, rc, "Invalid cbFrame value: %u (expected %u)", uBuf.Wave.u.Fmt.Data.cbFrame,
2534 uBuf.Wave.u.Fmt.Data.cChannels * uBuf.Wave.u.Fmt.Data.cBitsPerSample / 8);
2535 else if (uBuf.Wave.u.Fmt.Data.cbRate != uBuf.Wave.u.Fmt.Data.cbFrame * uBuf.Wave.u.Fmt.Data.uHz)
2536 RTErrInfoSetF(pErrInfo, rc, "Invalid cbRate value: %u (expected %u)", uBuf.Wave.u.Fmt.Data.cbRate,
2537 uBuf.Wave.u.Fmt.Data.cbFrame * uBuf.Wave.u.Fmt.Data.uHz);
2538 else if ( uBuf.Wave.u.Fmt.Data.uFormatTag == RTRIFFWAVEFMT_TAG_EXTENSIBLE
2539 && uBuf.Wave.u.FmtExt.Data.cbExtra < RTRIFFWAVEFMTEXT_EXTRA_SIZE)
2540 RTErrInfoSetF(pErrInfo, rc, "Invalid cbExtra value: %#x (expected at least %#x)",
2541 uBuf.Wave.u.FmtExt.Data.cbExtra, RTRIFFWAVEFMTEXT_EXTRA_SIZE);
2542 else if ( uBuf.Wave.u.Fmt.Data.uFormatTag == RTRIFFWAVEFMT_TAG_EXTENSIBLE
2543 && audioTestWaveCountBits(uBuf.Wave.u.FmtExt.Data.fChannelMask) != uBuf.Wave.u.Fmt.Data.cChannels)
2544 RTErrInfoSetF(pErrInfo, rc, "fChannelMask does not match cChannels: %#x (%u bits set) vs %u channels",
2545 uBuf.Wave.u.FmtExt.Data.fChannelMask,
2546 audioTestWaveCountBits(uBuf.Wave.u.FmtExt.Data.fChannelMask), uBuf.Wave.u.Fmt.Data.cChannels);
2547 else if ( uBuf.Wave.u.Fmt.Data.uFormatTag == RTRIFFWAVEFMT_TAG_EXTENSIBLE
2548 && RTUuidCompareStr(&uBuf.Wave.u.FmtExt.Data.SubFormat, RTRIFFWAVEFMTEXT_SUBTYPE_PCM) != 0)
2549 RTErrInfoSetF(pErrInfo, rc, "SubFormat is not PCM: %RTuuid (expected %s)",
2550 &uBuf.Wave.u.FmtExt.Data.SubFormat, RTRIFFWAVEFMTEXT_SUBTYPE_PCM);
2551 else
2552 {
2553 /*
2554 * Copy out the data we need from the file format structure.
2555 */
2556 PDMAudioPropsInit(&pWaveFile->Props, uBuf.Wave.u.Fmt.Data.cBitsPerSample / 8, true /*fSigned*/,
2557 uBuf.Wave.u.Fmt.Data.cChannels, uBuf.Wave.u.Fmt.Data.uHz);
2558 pWaveFile->offSamples = sizeof(RTRIFFHDR) + sizeof(RTRIFFCHUNK) + uBuf.Wave.u.Fmt.Chunk.cbChunk;
2559
2560 /*
2561 * Pick up channel assignments if present.
2562 */
2563 if (uBuf.Wave.u.Fmt.Data.uFormatTag == RTRIFFWAVEFMT_TAG_EXTENSIBLE)
2564 {
2565 static unsigned const s_cStdIds = (unsigned)PDMAUDIOCHANNELID_END_STANDARD
2566 - (unsigned)PDMAUDIOCHANNELID_FIRST_STANDARD;
2567 unsigned iCh = 0;
2568 for (unsigned idCh = 0; idCh < 32 && iCh < uBuf.Wave.u.Fmt.Data.cChannels; idCh++)
2569 if (uBuf.Wave.u.FmtExt.Data.fChannelMask & RT_BIT_32(idCh))
2570 {
2571 pWaveFile->Props.aidChannels[iCh] = idCh < s_cStdIds
2572 ? idCh + (unsigned)PDMAUDIOCHANNELID_FIRST_STANDARD
2573 : (unsigned)PDMAUDIOCHANNELID_UNKNOWN;
2574 iCh++;
2575 }
2576 }
2577
2578 /*
2579 * Find the 'data' chunk with the audio samples.
2580 *
2581 * There can be INFO lists both preceeding this and succeeding
2582 * it, containing IART and other things we can ignored. Thus
2583 * we read a list header here rather than just a chunk header,
2584 * since it doesn't matter if we read 4 bytes extra as
2585 * AudioTestWaveFileRead uses RTFileReadAt anyway.
2586 */
2587 rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
2588 for (uint32_t i = 0;
2589 i < 128
2590 && RT_SUCCESS(rc)
2591 && uBuf.Chunk.uMagic != RTRIFFWAVEDATACHUNK_MAGIC
2592 && (uint64_t)uBuf.Chunk.cbChunk + sizeof(RTRIFFCHUNK) * 2 <= cbFile - pWaveFile->offSamples;
2593 i++)
2594 {
2595 if ( uBuf.List.uMagic == RTRIFFLIST_MAGIC
2596 && uBuf.List.uListType == RTRIFFLIST_TYPE_INFO)
2597 { /*skip*/ }
2598 else if (uBuf.Chunk.uMagic == RTRIFFPADCHUNK_MAGIC)
2599 { /*skip*/ }
2600 else
2601 break;
2602 pWaveFile->offSamples += sizeof(RTRIFFCHUNK) + uBuf.Chunk.cbChunk;
2603 rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
2604 }
2605 if (RT_SUCCESS(rc))
2606 {
2607 pWaveFile->offSamples += sizeof(uBuf.Data.Chunk);
2608 pWaveFile->cbSamples = (uint32_t)cbFile - pWaveFile->offSamples;
2609
2610 rc = VERR_VFS_BOGUS_FORMAT;
2611 if ( uBuf.Data.Chunk.uMagic == RTRIFFWAVEDATACHUNK_MAGIC
2612 && uBuf.Data.Chunk.cbChunk <= pWaveFile->cbSamples
2613 && PDMAudioPropsIsSizeAligned(&pWaveFile->Props, uBuf.Data.Chunk.cbChunk))
2614 {
2615 pWaveFile->cbSamples = uBuf.Data.Chunk.cbChunk;
2616
2617 /*
2618 * We're good!
2619 */
2620 pWaveFile->offCur = 0;
2621 pWaveFile->fReadMode = true;
2622 pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC;
2623 return VINF_SUCCESS;
2624 }
2625
2626 RTErrInfoSetF(pErrInfo, rc, "Bad data header: uMagic=%#x (expected %#x), cbChunk=%#x (max %#RX64, align %u)",
2627 uBuf.Data.Chunk.uMagic, RTRIFFWAVEDATACHUNK_MAGIC,
2628 uBuf.Data.Chunk.cbChunk, pWaveFile->cbSamples, PDMAudioPropsFrameSize(&pWaveFile->Props));
2629 }
2630 else
2631 RTErrInfoSet(pErrInfo, rc, "Failed to read data header");
2632 }
2633 }
2634 else
2635 RTErrInfoSetF(pErrInfo, rc, "Bad file header: uMagic=%#x (vs. %#x), uFileType=%#x (vs %#x), uFmtMagic=%#x (vs %#x) cbFmtChunk=%#x (min %#x)",
2636 uBuf.Wave.Hdr.uMagic, RTRIFFHDR_MAGIC, uBuf.Wave.Hdr.uFileType, RTRIFF_FILE_TYPE_WAVE,
2637 uBuf.Wave.u.Fmt.Chunk.uMagic, RTRIFFWAVEFMT_MAGIC,
2638 uBuf.Wave.u.Fmt.Chunk.cbChunk, sizeof(uBuf.Wave.u.Fmt.Data));
2639 }
2640 else
2641 rc = RTErrInfoSet(pErrInfo, rc, "Failed to read file header");
2642 }
2643 else
2644 rc = RTErrInfoSet(pErrInfo, rc, "Failed to query file size");
2645
2646 RTFileClose(pWaveFile->hFile);
2647 pWaveFile->hFile = NIL_RTFILE;
2648 return rc;
2649}
2650
2651
2652/**
2653 * Creates a new wave file.
2654 *
2655 * @returns VBox status code.
2656 * @param pszFile The filename.
2657 * @param pProps The audio format properties.
2658 * @param pWaveFile The wave file structure to fill in on success.
2659 * @param pErrInfo Where to return addition error details on failure.
2660 */
2661int AudioTestWaveFileCreate(const char *pszFile, PCPDMAUDIOPCMPROPS pProps, PAUDIOTESTWAVEFILE pWaveFile, PRTERRINFO pErrInfo)
2662{
2663 /*
2664 * Construct the file header first (we'll do some input validation
2665 * here, so better do it before creating the file).
2666 */
2667 struct
2668 {
2669 RTRIFFHDR Hdr;
2670 RTRIFFWAVEFMTEXTCHUNK FmtExt;
2671 RTRIFFCHUNK Data;
2672 } FileHdr;
2673
2674 FileHdr.Hdr.uMagic = RTRIFFHDR_MAGIC;
2675 FileHdr.Hdr.cbFile = 0; /* need to update this later */
2676 FileHdr.Hdr.uFileType = RTRIFF_FILE_TYPE_WAVE;
2677 FileHdr.FmtExt.Chunk.uMagic = RTRIFFWAVEFMT_MAGIC;
2678 FileHdr.FmtExt.Chunk.cbChunk = sizeof(RTRIFFWAVEFMTEXTCHUNK) - sizeof(RTRIFFCHUNK);
2679 FileHdr.FmtExt.Data.Core.uFormatTag = RTRIFFWAVEFMT_TAG_EXTENSIBLE;
2680 FileHdr.FmtExt.Data.Core.cChannels = PDMAudioPropsChannels(pProps);
2681 FileHdr.FmtExt.Data.Core.uHz = PDMAudioPropsHz(pProps);
2682 FileHdr.FmtExt.Data.Core.cbRate = PDMAudioPropsFramesToBytes(pProps, PDMAudioPropsHz(pProps));
2683 FileHdr.FmtExt.Data.Core.cbFrame = PDMAudioPropsFrameSize(pProps);
2684 FileHdr.FmtExt.Data.Core.cBitsPerSample = PDMAudioPropsSampleBits(pProps);
2685 FileHdr.FmtExt.Data.cbExtra = sizeof(FileHdr.FmtExt.Data) - sizeof(FileHdr.FmtExt.Data.Core);
2686 FileHdr.FmtExt.Data.cValidBitsPerSample = PDMAudioPropsSampleBits(pProps);
2687 FileHdr.FmtExt.Data.fChannelMask = 0;
2688 for (uintptr_t idxCh = 0; idxCh < FileHdr.FmtExt.Data.Core.cChannels; idxCh++)
2689 {
2690 PDMAUDIOCHANNELID const idCh = (PDMAUDIOCHANNELID)pProps->aidChannels[idxCh];
2691 if ( idCh >= PDMAUDIOCHANNELID_FIRST_STANDARD
2692 && idCh < PDMAUDIOCHANNELID_END_STANDARD)
2693 {
2694 if (!(FileHdr.FmtExt.Data.fChannelMask & RT_BIT_32(idCh - PDMAUDIOCHANNELID_FIRST_STANDARD)))
2695 FileHdr.FmtExt.Data.fChannelMask |= RT_BIT_32(idCh - PDMAUDIOCHANNELID_FIRST_STANDARD);
2696 else
2697 return RTErrInfoSetF(pErrInfo, VERR_INVALID_PARAMETER, "Channel #%u repeats channel ID %d", idxCh, idCh);
2698 }
2699 else
2700 return RTErrInfoSetF(pErrInfo, VERR_INVALID_PARAMETER, "Invalid channel ID %d for channel #%u", idCh, idxCh);
2701 }
2702
2703 RTUUID UuidTmp;
2704 int rc = RTUuidFromStr(&UuidTmp, RTRIFFWAVEFMTEXT_SUBTYPE_PCM);
2705 AssertRCReturn(rc, rc);
2706 FileHdr.FmtExt.Data.SubFormat = UuidTmp; /* (64-bit field maybe unaligned) */
2707
2708 FileHdr.Data.uMagic = RTRIFFWAVEDATACHUNK_MAGIC;
2709 FileHdr.Data.cbChunk = 0; /* need to update this later */
2710
2711 /*
2712 * Create the file and write the header.
2713 */
2714 pWaveFile->hFile = NIL_RTFILE;
2715 rc = RTFileOpen(&pWaveFile->hFile, pszFile, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
2716 if (RT_FAILURE(rc))
2717 return RTErrInfoSet(pErrInfo, rc, "RTFileOpen failed");
2718
2719 rc = RTFileWrite(pWaveFile->hFile, &FileHdr, sizeof(FileHdr), NULL);
2720 if (RT_SUCCESS(rc))
2721 {
2722 /*
2723 * Initialize the wave file structure.
2724 */
2725 pWaveFile->fReadMode = false;
2726 pWaveFile->offCur = 0;
2727 pWaveFile->offSamples = 0;
2728 pWaveFile->cbSamples = 0;
2729 pWaveFile->Props = *pProps;
2730 pWaveFile->offSamples = RTFileTell(pWaveFile->hFile);
2731 if (pWaveFile->offSamples != UINT32_MAX)
2732 {
2733 pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC;
2734 return VINF_SUCCESS;
2735 }
2736 rc = RTErrInfoSet(pErrInfo, VERR_SEEK, "RTFileTell failed");
2737 }
2738 else
2739 RTErrInfoSet(pErrInfo, rc, "RTFileWrite failed writing header");
2740
2741 RTFileClose(pWaveFile->hFile);
2742 pWaveFile->hFile = NIL_RTFILE;
2743 pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC_DEAD;
2744
2745 RTFileDelete(pszFile);
2746 return rc;
2747}
2748
2749
2750/**
2751 * Closes a wave file.
2752 */
2753int AudioTestWaveFileClose(PAUDIOTESTWAVEFILE pWaveFile)
2754{
2755 AssertReturn(pWaveFile->u32Magic == AUDIOTESTWAVEFILE_MAGIC, VERR_INVALID_MAGIC);
2756 int rcRet = VINF_SUCCESS;
2757 int rc;
2758
2759 /*
2760 * Update the size fields if writing.
2761 */
2762 if (!pWaveFile->fReadMode)
2763 {
2764 uint64_t cbFile = RTFileTell(pWaveFile->hFile);
2765 if (cbFile != UINT64_MAX)
2766 {
2767 uint32_t cbFile32 = cbFile - sizeof(RTRIFFCHUNK);
2768 rc = RTFileWriteAt(pWaveFile->hFile, RT_OFFSETOF(RTRIFFHDR, cbFile), &cbFile32, sizeof(cbFile32), NULL);
2769 AssertRCStmt(rc, rcRet = rc);
2770
2771 uint32_t cbSamples = cbFile - pWaveFile->offSamples;
2772 rc = RTFileWriteAt(pWaveFile->hFile, pWaveFile->offSamples - sizeof(uint32_t), &cbSamples, sizeof(cbSamples), NULL);
2773 AssertRCStmt(rc, rcRet = rc);
2774 }
2775 else
2776 rcRet = VERR_SEEK;
2777 }
2778
2779 /*
2780 * Close it.
2781 */
2782 rc = RTFileClose(pWaveFile->hFile);
2783 AssertRCStmt(rc, rcRet = rc);
2784
2785 pWaveFile->hFile = NIL_RTFILE;
2786 pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC_DEAD;
2787 return rcRet;
2788}
2789
2790/**
2791 * Reads samples from a wave file.
2792 *
2793 * @returns VBox status code. See RTVfsFileRead for EOF status handling.
2794 * @param pWaveFile The file to read from.
2795 * @param pvBuf Where to put the samples.
2796 * @param cbBuf How much to read at most.
2797 * @param pcbRead Where to return the actual number of bytes read,
2798 * optional.
2799 */
2800int AudioTestWaveFileRead(PAUDIOTESTWAVEFILE pWaveFile, void *pvBuf, size_t cbBuf, size_t *pcbRead)
2801{
2802 AssertReturn(pWaveFile->u32Magic == AUDIOTESTWAVEFILE_MAGIC, VERR_INVALID_MAGIC);
2803 AssertReturn(pWaveFile->fReadMode, VERR_ACCESS_DENIED);
2804
2805 bool fEofAdjusted;
2806 if (pWaveFile->offCur + cbBuf <= pWaveFile->cbSamples)
2807 fEofAdjusted = false;
2808 else if (pcbRead)
2809 {
2810 fEofAdjusted = true;
2811 cbBuf = pWaveFile->cbSamples - pWaveFile->offCur;
2812 }
2813 else
2814 return VERR_EOF;
2815
2816 int rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples + pWaveFile->offCur, pvBuf, cbBuf, pcbRead);
2817 if (RT_SUCCESS(rc))
2818 {
2819 if (pcbRead)
2820 {
2821 pWaveFile->offCur += (uint32_t)*pcbRead;
2822 if (fEofAdjusted || cbBuf > *pcbRead)
2823 rc = VINF_EOF;
2824 else if (!cbBuf && pWaveFile->offCur == pWaveFile->cbSamples)
2825 rc = VINF_EOF;
2826 }
2827 else
2828 pWaveFile->offCur += (uint32_t)cbBuf;
2829 }
2830 return rc;
2831}
2832
2833
2834/**
2835 * Writes samples to a wave file.
2836 *
2837 * @returns VBox status code.
2838 * @param pWaveFile The file to write to.
2839 * @param pvBuf The samples to write.
2840 * @param cbBuf How many bytes of samples to write.
2841 */
2842int AudioTestWaveFileWrite(PAUDIOTESTWAVEFILE pWaveFile, const void *pvBuf, size_t cbBuf)
2843{
2844 AssertReturn(pWaveFile->u32Magic == AUDIOTESTWAVEFILE_MAGIC, VERR_INVALID_MAGIC);
2845 AssertReturn(!pWaveFile->fReadMode, VERR_ACCESS_DENIED);
2846
2847 pWaveFile->cbSamples += (uint32_t)cbBuf;
2848 return RTFileWrite(pWaveFile->hFile, pvBuf, cbBuf, NULL);
2849}
2850
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