VirtualBox

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

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

Audio/Validation Kit: Relaxed checks when calling AudioTestDestroy() on a test set which still has running (unfinished) tests. We now close all stuff we can and let it pack up all we got by now. The test set verification will then try to work on this state later then. bugref:10008

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