VirtualBox

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

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

Audio/ValKit: Use frame size instead of sample size in audioTestToneFileFind() [Windows build fix]. bugref:10008

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

© 2023 Oracle
ContactPrivacy policyTerms of Use