VirtualBox

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

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

Audio/Validation Kit: Some more assertions for audioTestToneFileFind(). ​bugref:10008

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