VirtualBox

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

Last change on this file was 103566, checked in by vboxsync, 3 months ago

Audio/VKAT: Bugfixes for AudioTestBeaconAddConsecutive().

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

© 2023 Oracle
ContactPrivacy policyTerms of Use