VirtualBox

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

Last change on this file since 92009 was 91912, checked in by vboxsync, 3 years ago

Audio/Validation Kit: Cleaned up the test tone validation code a bit. ​bugref:10008

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