VirtualBox

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

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

scm --update-copyright-year

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

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