VirtualBox

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

最後變更 在這個檔案從89089是 89082,由 vboxsync 提交於 4 年 前

AudioTest/Wave: Skip loop. bugref:10008

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 37.6 KB
 
1/* $Id: AudioTest.cpp 89082 2021-05-17 09:29:53Z vboxsync $ */
2/** @file
3 * Audio testing routines.
4 * Common code which is being used by the ValidationKit and the debug / ValdikationKit audio driver(s).
5 */
6
7/*
8 * Copyright (C) 2021 Oracle Corporation
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.alldomusa.eu.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 */
18
19
20/*********************************************************************************************************************************
21* Header Files *
22*********************************************************************************************************************************/
23
24#include <package-generated.h>
25#include "product-generated.h"
26
27#include <iprt/buildconfig.h>
28#include <iprt/dir.h>
29#include <iprt/file.h>
30#include <iprt/formats/riff.h>
31#include <iprt/inifile.h>
32#include <iprt/list.h>
33#include <iprt/message.h> /** @todo Get rid of this once we have own log hooks. */
34#include <iprt/rand.h>
35#include <iprt/system.h>
36#include <iprt/uuid.h>
37#include <iprt/vfs.h>
38#include <iprt/zip.h>
39
40#define _USE_MATH_DEFINES
41#include <math.h> /* sin, M_PI */
42
43#include <VBox/version.h>
44#include <VBox/vmm/pdmaudioifs.h>
45#include <VBox/vmm/pdmaudioinline.h>
46
47#include "AudioTest.h"
48
49
50/*********************************************************************************************************************************
51* Defines *
52*********************************************************************************************************************************/
53/** The test manifest file name. */
54#define AUDIOTEST_MANIFEST_FILE_STR "vkat_manifest.ini"
55/** The current test manifest version. */
56#define AUDIOTEST_MANIFEST_VER 1
57
58/** Test manifest header name. */
59#define AUDIOTEST_INI_SEC_HDR_STR "header"
60
61
62/*********************************************************************************************************************************
63* Structures and Typedefs *
64*********************************************************************************************************************************/
65
66
67/*********************************************************************************************************************************
68* Global Variables *
69*********************************************************************************************************************************/
70/** Well-known frequency selection test tones. */
71static const double s_aAudioTestToneFreqsHz[] =
72{
73 349.2282 /*F4*/,
74 440.0000 /*A4*/,
75 523.2511 /*C5*/,
76 698.4565 /*F5*/,
77 880.0000 /*A5*/,
78 1046.502 /*C6*/,
79 1174.659 /*D6*/,
80 1396.913 /*F6*/,
81 1760.0000 /*A6*/
82};
83
84/**
85 * Initializes a test tone by picking a random but well-known frequency (in Hz).
86 *
87 * @returns Randomly picked frequency (in Hz).
88 * @param pTone Pointer to test tone to initialize.
89 * @param pProps PCM properties to use for the test tone.
90 */
91double AudioTestToneInitRandom(PAUDIOTESTTONE pTone, PPDMAUDIOPCMPROPS pProps)
92{
93 /* Pick a frequency from our selection, so that every time a recording starts
94 * we'll hopfully generate a different note. */
95 pTone->rdFreqHz = s_aAudioTestToneFreqsHz[RTRandU32Ex(0, RT_ELEMENTS(s_aAudioTestToneFreqsHz) - 1)];
96 pTone->rdFixed = 2.0 * M_PI * pTone->rdFreqHz / PDMAudioPropsHz(pProps);
97 pTone->uSample = 0;
98
99 memcpy(&pTone->Props, pProps, sizeof(PDMAUDIOPCMPROPS));
100
101 pTone->enmType = AUDIOTESTTONETYPE_SINE; /* Only type implemented so far. */
102
103 return pTone->rdFreqHz;
104}
105
106/**
107 * Writes (and iterates) a given test tone to an output buffer.
108 *
109 * @returns VBox status code.
110 * @param pTone Pointer to test tone to write.
111 * @param pvBuf Pointer to output buffer to write test tone to.
112 * @param cbBuf Size (in bytes) of output buffer.
113 * @param pcbWritten How many bytes were written on success.
114 */
115int AudioTestToneGenerate(PAUDIOTESTTONE pTone, void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
116{
117 /*
118 * Clear the buffer first so we don't need to think about additional channels.
119 */
120 uint32_t cFrames = PDMAudioPropsBytesToFrames(&pTone->Props, cbBuf);
121
122 /* Input cbBuf not necessarily is aligned to the frames, so re-calculate it. */
123 const uint32_t cbToWrite = PDMAudioPropsFramesToBytes(&pTone->Props, cFrames);
124
125 PDMAudioPropsClearBuffer(&pTone->Props, pvBuf, cbBuf, cFrames);
126
127 /*
128 * Generate the select sin wave in the first channel:
129 */
130 uint32_t const cbFrame = PDMAudioPropsFrameSize(&pTone->Props);
131 double const rdFixed = pTone->rdFixed;
132 uint64_t iSrcFrame = pTone->uSample;
133 switch (PDMAudioPropsSampleSize(&pTone->Props))
134 {
135 case 1:
136 /* untested */
137 if (PDMAudioPropsIsSigned(&pTone->Props))
138 {
139 int8_t *piSample = (int8_t *)pvBuf;
140 while (cFrames-- > 0)
141 {
142 *piSample = (int8_t)(126 /*Amplitude*/ * sin(rdFixed * iSrcFrame));
143 iSrcFrame++;
144 piSample += cbFrame;
145 }
146 }
147 else
148 {
149 /* untested */
150 uint8_t *pbSample = (uint8_t *)pvBuf;
151 while (cFrames-- > 0)
152 {
153 *pbSample = (uint8_t)(126 /*Amplitude*/ * sin(rdFixed * iSrcFrame) + 0x80);
154 iSrcFrame++;
155 pbSample += cbFrame;
156 }
157 }
158 break;
159
160 case 2:
161 if (PDMAudioPropsIsSigned(&pTone->Props))
162 {
163 int16_t *piSample = (int16_t *)pvBuf;
164 while (cFrames-- > 0)
165 {
166 *piSample = (int16_t)(32760 /*Amplitude*/ * sin(rdFixed * iSrcFrame));
167 iSrcFrame++;
168 piSample = (int16_t *)((uint8_t *)piSample + cbFrame);
169 }
170 }
171 else
172 {
173 /* untested */
174 uint16_t *puSample = (uint16_t *)pvBuf;
175 while (cFrames-- > 0)
176 {
177 *puSample = (uint16_t)(32760 /*Amplitude*/ * sin(rdFixed * iSrcFrame) + 0x8000);
178 iSrcFrame++;
179 puSample = (uint16_t *)((uint8_t *)puSample + cbFrame);
180 }
181 }
182 break;
183
184 case 4:
185 /* untested */
186 if (PDMAudioPropsIsSigned(&pTone->Props))
187 {
188 int32_t *piSample = (int32_t *)pvBuf;
189 while (cFrames-- > 0)
190 {
191 *piSample = (int32_t)((32760 << 16) /*Amplitude*/ * sin(rdFixed * iSrcFrame));
192 iSrcFrame++;
193 piSample = (int32_t *)((uint8_t *)piSample + cbFrame);
194 }
195 }
196 else
197 {
198 uint32_t *puSample = (uint32_t *)pvBuf;
199 while (cFrames-- > 0)
200 {
201 *puSample = (uint32_t)((32760 << 16) /*Amplitude*/ * sin(rdFixed * iSrcFrame) + UINT32_C(0x80000000));
202 iSrcFrame++;
203 puSample = (uint32_t *)((uint8_t *)puSample + cbFrame);
204 }
205 }
206 break;
207
208 default:
209 AssertFailedReturn(VERR_NOT_SUPPORTED);
210 }
211
212 pTone->uSample = iSrcFrame;
213
214 if (pcbWritten)
215 *pcbWritten = cbToWrite;
216
217 return VINF_SUCCESS;
218}
219
220/**
221 * Initializes an audio test tone parameters struct with random values.
222 * @param pToneParams Test tone parameters to initialize.
223 * @param pProps PCM properties to use for the test tone.
224 */
225int AudioTestToneParamsInitRandom(PAUDIOTESTTONEPARMS pToneParams, PPDMAUDIOPCMPROPS pProps)
226{
227 AssertReturn(PDMAudioPropsAreValid(pProps), VERR_INVALID_PARAMETER);
228
229 memcpy(&pToneParams->Props, pProps, sizeof(PDMAUDIOPCMPROPS));
230
231 /** @todo Make this a bit more sophisticated later, e.g. muting and prequel/sequel are not very balanced. */
232
233 pToneParams->msPrequel = RTRandU32Ex(0, RT_MS_5SEC);
234#ifdef DEBUG_andy
235 pToneParams->msDuration = RTRandU32Ex(0, RT_MS_1SEC);
236#else
237 pToneParams->msDuration = RTRandU32Ex(0, RT_MS_10SEC); /** @todo Probably a bit too long, but let's see. */
238#endif
239 pToneParams->msSequel = RTRandU32Ex(0, RT_MS_5SEC);
240 pToneParams->uVolumePercent = RTRandU32Ex(0, 100);
241
242 return VINF_SUCCESS;
243}
244
245/**
246 * Generates a tag.
247 *
248 * @returns VBox status code.
249 * @param pszTag The output buffer.
250 * @param cbTag The size of the output buffer.
251 * AUDIOTEST_TAG_MAX is a good size.
252 */
253int AudioTestGenTag(char *pszTag, size_t cbTag)
254{
255 RTUUID UUID;
256 int rc = RTUuidCreate(&UUID);
257 AssertRCReturn(rc, rc);
258 rc = RTUuidToStr(&UUID, pszTag, cbTag);
259 AssertRCReturn(rc, rc);
260 return rc;
261}
262
263
264/**
265 * Return the tag to use in the given buffer, generating one if needed.
266 *
267 * @returns VBox status code.
268 * @param pszTag The output buffer.
269 * @param cbTag The size of the output buffer.
270 * AUDIOTEST_TAG_MAX is a good size.
271 * @param pszTagUser User specified tag, optional.
272 */
273int AudioTestCopyOrGenTag(char *pszTag, size_t cbTag, const char *pszTagUser)
274{
275 if (pszTagUser && *pszTagUser)
276 return RTStrCopy(pszTag, cbTag, pszTagUser);
277 return AudioTestGenTag(pszTag, cbTag);
278}
279
280
281/**
282 * Creates a new path (directory) for a specific audio test set tag.
283 *
284 * @returns VBox status code.
285 * @param pszPath On input, specifies the absolute base path where to create the test set path.
286 * On output this specifies the absolute path created.
287 * @param cbPath Size (in bytes) of \a pszPath.
288 * @param pszTag Tag to use for path creation.
289 *
290 * @note Can be used multiple times with the same tag; a sub directory with an ISO time string will be used
291 * on each call.
292 */
293int AudioTestPathCreate(char *pszPath, size_t cbPath, const char *pszTag)
294{
295 char szTag[AUDIOTEST_TAG_MAX];
296 int rc = AudioTestCopyOrGenTag(szTag, sizeof(szTag), pszTag);
297 AssertRCReturn(rc, rc);
298
299 char szName[RT_ELEMENTS(AUDIOTEST_PATH_PREFIX_STR) + AUDIOTEST_TAG_MAX + 4];
300 if (RTStrPrintf2(szName, sizeof(szName), "%s-%s", AUDIOTEST_PATH_PREFIX_STR, szTag) < 0)
301 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
302
303 rc = RTPathAppend(pszPath, cbPath, szName);
304 AssertRCReturn(rc, rc);
305
306 char szTime[64];
307 RTTIMESPEC time;
308 if (!RTTimeSpecToString(RTTimeNow(&time), szTime, sizeof(szTime)))
309 return VERR_BUFFER_UNDERFLOW;
310
311 rc = RTPathAppend(pszPath, cbPath, szTime);
312 AssertRCReturn(rc, rc);
313
314 return RTDirCreateFullPath(pszPath, RTFS_UNIX_IRWXU);
315}
316
317/**
318 * Writes string data to a test set manifest.
319 *
320 * @returns VBox status code.
321 * @param pSet Test set to write manifest for.
322 * @param pszFormat Format string to write.
323 * @param args Variable arguments for \a pszFormat.
324 */
325static int audioTestManifestWriteV(PAUDIOTESTSET pSet, const char *pszFormat, va_list args)
326{
327 char *psz = NULL;
328 RTStrAPrintfV(&psz, pszFormat, args);
329 AssertPtr(psz);
330
331 /** @todo Use RTIniFileWrite once its implemented. */
332 int rc = RTFileWrite(pSet->f.hFile, psz, strlen(psz), NULL);
333 AssertRC(rc);
334
335 RTStrFree(psz);
336
337 return rc;
338}
339
340/**
341 * Writes a terminated string line to a test set manifest.
342 * Convenience function.
343 *
344 * @returns VBox status code.
345 * @param pSet Test set to write manifest for.
346 * @param pszFormat Format string to write.
347 * @param ... Variable arguments for \a pszFormat. Optional.
348 */
349static int audioTestManifestWriteLn(PAUDIOTESTSET pSet, const char *pszFormat, ...)
350{
351 va_list va;
352 va_start(va, pszFormat);
353
354 int rc = audioTestManifestWriteV(pSet, pszFormat, va);
355 AssertRC(rc);
356
357 va_end(va);
358
359 /** @todo Keep it as simple as possible for now. Improve this later. */
360 rc = RTFileWrite(pSet->f.hFile, "\n", strlen("\n"), NULL);
361 AssertRC(rc);
362
363 return rc;
364}
365
366/**
367 * Writes a section entry to a test set manifest.
368 *
369 * @returns VBox status code.
370 * @param pSet Test set to write manifest for.
371 * @param pszSection Format string of section to write.
372 * @param ... Variable arguments for \a pszSection. Optional.
373 */
374static int audioTestManifestWriteSection(PAUDIOTESTSET pSet, const char *pszSection, ...)
375{
376 va_list va;
377 va_start(va, pszSection);
378
379 /** @todo Keep it as simple as possible for now. Improve this later. */
380 int rc = RTFileWrite(pSet->f.hFile, "[", strlen("["), NULL);
381 AssertRC(rc);
382
383 rc = audioTestManifestWriteV(pSet, pszSection, va);
384 AssertRC(rc);
385
386 rc = RTFileWrite(pSet->f.hFile, "]\n", strlen("]\n"), NULL);
387 AssertRC(rc);
388
389 va_end(va);
390
391 return rc;
392}
393
394/**
395 * Initializes an audio test set, internal function.
396 * @param pSet Test set to initialize.
397 */
398static void audioTestSetInitInternal(PAUDIOTESTSET pSet)
399{
400 pSet->f.hFile = NIL_RTFILE;
401
402 RTListInit(&pSet->lstObj);
403}
404
405/**
406 * Returns whether a test set's manifest file is open (and thus ready) or not.
407 *
408 * @returns \c true if open (and ready), or \c false if not.
409 * @retval VERR_
410 * @param pSet Test set to return open status for.
411 */
412static bool audioTestManifestIsOpen(PAUDIOTESTSET pSet)
413{
414 if ( pSet->enmMode == AUDIOTESTSETMODE_TEST
415 && pSet->f.hFile != NIL_RTFILE)
416 return true;
417 else if ( pSet->enmMode == AUDIOTESTSETMODE_VERIFY
418 && pSet->f.hIniFile != NIL_RTINIFILE)
419 return true;
420
421 return false;
422}
423
424/**
425 * Initializes an audio test error description.
426 *
427 * @param pErr Test error description to initialize.
428 */
429static void audioTestErrorDescInit(PAUDIOTESTERRORDESC pErr)
430{
431 RTListInit(&pErr->List);
432 pErr->cErrors = 0;
433}
434
435/**
436 * Destroys an audio test error description.
437 *
438 * @param pErr Test error description to destroy.
439 */
440void AudioTestErrorDescDestroy(PAUDIOTESTERRORDESC pErr)
441{
442 if (!pErr)
443 return;
444
445 PAUDIOTESTERRORENTRY pErrEntry, pErrEntryNext;
446 RTListForEachSafe(&pErr->List, pErrEntry, pErrEntryNext, AUDIOTESTERRORENTRY, Node)
447 {
448 RTListNodeRemove(&pErrEntry->Node);
449
450 RTMemFree(pErrEntry);
451
452 Assert(pErr->cErrors);
453 pErr->cErrors--;
454 }
455
456 Assert(pErr->cErrors == 0);
457}
458
459/**
460 * Returns if an audio test error description contains any errors or not.
461 *
462 * @returns \c true if it contains errors, or \c false if not.
463 *
464 * @param pErr Test error description to return error status for.
465 */
466bool AudioTestErrorDescFailed(PAUDIOTESTERRORDESC pErr)
467{
468 if (pErr->cErrors)
469 {
470 Assert(!RTListIsEmpty(&pErr->List));
471 return true;
472 }
473
474 return false;
475}
476
477/**
478 * Adds a single error entry to an audio test error description, va_list version.
479 *
480 * @returns VBox status code.
481 * @param pErr Test error description to add entry for.
482 * @param rc Result code of entry to add.
483 * @param pszDesc Error description format string to add.
484 * @param args Optional format arguments of \a pszDesc to add.
485 */
486static int audioTestErrorDescAddV(PAUDIOTESTERRORDESC pErr, int rc, const char *pszDesc, va_list args)
487{
488 PAUDIOTESTERRORENTRY pEntry = (PAUDIOTESTERRORENTRY)RTMemAlloc(sizeof(AUDIOTESTERRORENTRY));
489 AssertReturn(pEntry, VERR_NO_MEMORY);
490
491 if (RTStrPrintf2V(pEntry->szDesc, sizeof(pEntry->szDesc), pszDesc, args) < 0)
492 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
493
494 pEntry->rc = rc;
495
496 RTListAppend(&pErr->List, &pEntry->Node);
497
498 pErr->cErrors++;
499
500 return VINF_SUCCESS;
501}
502
503/**
504 * Adds a single error entry to an audio test error description, va_list version.
505 *
506 * @returns VBox status code.
507 * @param pErr Test error description to add entry for.
508 * @param pszDesc Error description format string to add.
509 * @param ... Optional format arguments of \a pszDesc to add.
510 */
511static int audioTestErrorDescAdd(PAUDIOTESTERRORDESC pErr, const char *pszDesc, ...)
512{
513 va_list va;
514 va_start(va, pszDesc);
515
516 int rc = audioTestErrorDescAddV(pErr, VERR_GENERAL_FAILURE /** @todo Fudge! */, pszDesc, va);
517
518 va_end(va);
519 return rc;
520}
521
522#if 0
523static int audioTestErrorDescAddRc(PAUDIOTESTERRORDESC pErr, int rc, const char *pszFormat, ...)
524{
525 va_list va;
526 va_start(va, pszFormat);
527
528 int rc2 = audioTestErrorDescAddV(pErr, rc, pszFormat, va);
529
530 va_end(va);
531 return rc2;
532}
533#endif
534
535/**
536 * Creates a new temporary directory with a specific (test) tag.
537 *
538 * @returns VBox status code.
539 * @param pszPath Where to return the absolute path of the created directory on success.
540 * @param cbPath Size (in bytes) of \a pszPath.
541 * @param pszTag Tag name to use for directory creation.
542 *
543 * @note Can be used multiple times with the same tag; a sub directory with an ISO time string will be used
544 * on each call.
545 */
546int AudioTestPathCreateTemp(char *pszPath, size_t cbPath, const char *pszTag)
547{
548 AssertReturn(pszTag && strlen(pszTag) <= AUDIOTEST_TAG_MAX, VERR_INVALID_PARAMETER);
549
550 char szPath[RTPATH_MAX];
551
552 int rc = RTPathTemp(szPath, sizeof(szPath));
553 AssertRCReturn(rc, rc);
554 rc = AudioTestPathCreate(szPath, sizeof(szPath), pszTag);
555 AssertRCReturn(rc, rc);
556
557 return RTStrCopy(pszPath, cbPath, szPath);
558}
559
560/**
561 * Creates a new audio test set.
562 *
563 * @returns VBox status code.
564 * @param pSet Test set to create.
565 * @param pszPath Absolute path to use for the test set's temporary directory.
566 * If NULL, the OS' temporary directory will be used.
567 * @param pszTag Tag name to use for this test set.
568 */
569int AudioTestSetCreate(PAUDIOTESTSET pSet, const char *pszPath, const char *pszTag)
570{
571 audioTestSetInitInternal(pSet);
572
573 int rc = AudioTestCopyOrGenTag(pSet->szTag, sizeof(pSet->szTag), pszTag);
574 AssertRCReturn(rc, rc);
575
576 if (pszPath)
577 {
578 rc = RTStrCopy(pSet->szPathAbs, sizeof(pSet->szPathAbs), pszPath);
579 AssertRCReturn(rc, rc);
580
581 rc = AudioTestPathCreate(pSet->szPathAbs, sizeof(pSet->szPathAbs), pSet->szTag);
582 }
583 else
584 rc = AudioTestPathCreateTemp(pSet->szPathAbs, sizeof(pSet->szPathAbs), pSet->szTag);
585 AssertRCReturn(rc, rc);
586
587 if (RT_SUCCESS(rc))
588 {
589 char szManifest[RTPATH_MAX];
590 rc = RTStrCopy(szManifest, sizeof(szManifest), pSet->szPathAbs);
591 AssertRCReturn(rc, rc);
592
593 rc = RTPathAppend(szManifest, sizeof(szManifest), AUDIOTEST_MANIFEST_FILE_STR);
594 AssertRCReturn(rc, rc);
595
596 rc = RTFileOpen(&pSet->f.hFile, szManifest,
597 RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
598 AssertRCReturn(rc, rc);
599
600 rc = audioTestManifestWriteSection(pSet, "header");
601 AssertRCReturn(rc, rc);
602
603 rc = audioTestManifestWriteLn(pSet, "magic=vkat_ini"); /* VKAT Manifest, .INI-style. */
604 AssertRCReturn(rc, rc);
605 rc = audioTestManifestWriteLn(pSet, "ver=%d", AUDIOTEST_MANIFEST_VER);
606 AssertRCReturn(rc, rc);
607 rc = audioTestManifestWriteLn(pSet, "tag=%s", pSet->szTag);
608 AssertRCReturn(rc, rc);
609
610 char szVal[64];
611 RTTIMESPEC time;
612 if (!RTTimeSpecToString(RTTimeNow(&time), szVal, sizeof(szVal)))
613 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
614
615 rc = audioTestManifestWriteLn(pSet, "date_created=%s", szVal);
616 AssertRCReturn(rc, rc);
617
618 rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szVal, sizeof(szVal));
619 AssertRCReturn(rc, rc);
620 rc = audioTestManifestWriteLn(pSet, "os_product=%s", szVal);
621 AssertRCReturn(rc, rc);
622 rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szVal, sizeof(szVal));
623 AssertRCReturn(rc, rc);
624 rc = audioTestManifestWriteLn(pSet, "os_rel=%s", szVal);
625 AssertRCReturn(rc, rc);
626 rc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szVal, sizeof(szVal));
627 AssertRCReturn(rc, rc);
628 rc = audioTestManifestWriteLn(pSet, "os_ver=%s", szVal);
629 AssertRCReturn(rc, rc);
630
631 rc = audioTestManifestWriteLn(pSet, "vbox_ver=%s r%u %s (%s %s)",
632 VBOX_VERSION_STRING, RTBldCfgRevision(),
633 RTBldCfgTargetDotArch(), __DATE__, __TIME__);
634 AssertRCReturn(rc, rc);
635
636 pSet->enmMode = AUDIOTESTSETMODE_TEST;
637 }
638
639 return rc;
640}
641
642/**
643 * Destroys a test set.
644 *
645 * @returns VBox status code.
646 * @param pSet Test set to destroy.
647 */
648int AudioTestSetDestroy(PAUDIOTESTSET pSet)
649{
650 if (!pSet)
651 return VINF_SUCCESS;
652
653 int rc = VINF_SUCCESS;
654
655 PAUDIOTESTOBJ pObj, pObjNext;
656 RTListForEachSafe(&pSet->lstObj, pObj, pObjNext, AUDIOTESTOBJ, Node)
657 {
658 rc = AudioTestSetObjClose(pObj);
659 if (RT_SUCCESS(rc))
660 {
661 RTListNodeRemove(&pObj->Node);
662 RTMemFree(pObj);
663
664 Assert(pSet->cObj);
665 pSet->cObj--;
666 }
667 else
668 break;
669 }
670
671 if (RT_FAILURE(rc))
672 return rc;
673
674 Assert(pSet->cObj == 0);
675
676 if (RTFileIsValid(pSet->f.hFile))
677 {
678 RTFileClose(pSet->f.hFile);
679 pSet->f.hFile = NIL_RTFILE;
680 }
681
682 return rc;
683}
684
685/**
686 * Opens an existing audio test set.
687 *
688 * @returns VBox status code.
689 * @param pSet Test set to open.
690 * @param pszPath Absolute path of the test set to open.
691 */
692int AudioTestSetOpen(PAUDIOTESTSET pSet, const char *pszPath)
693{
694 audioTestSetInitInternal(pSet);
695
696 char szManifest[RTPATH_MAX];
697 int rc = RTPathJoin(szManifest, sizeof(szManifest), pszPath, AUDIOTEST_MANIFEST_FILE_STR);
698 AssertRCReturn(rc, rc);
699
700 RTVFSFILE hVfsFile;
701 rc = RTVfsFileOpenNormal(szManifest, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hVfsFile);
702 if (RT_FAILURE(rc))
703 return rc;
704
705 rc = RTIniFileCreateFromVfsFile(&pSet->f.hIniFile, hVfsFile, RTINIFILE_F_READONLY);
706 RTVfsFileRelease(hVfsFile);
707 AssertRCReturn(rc, rc);
708
709 pSet->enmMode = AUDIOTESTSETMODE_VERIFY;
710
711 return rc;
712}
713
714/**
715 * Closes an opened audio test set.
716 *
717 * @param pSet Test set to close.
718 */
719void AudioTestSetClose(PAUDIOTESTSET pSet)
720{
721 AudioTestSetDestroy(pSet);
722}
723
724/**
725 * Physically wipes all related test set files off the disk.
726 *
727 * @param pSet Test set to wipe.
728 */
729void AudioTestSetWipe(PAUDIOTESTSET pSet)
730{
731 RT_NOREF(pSet);
732}
733
734/**
735 * Creates and registers a new audio test object to a test set.
736 *
737 * @returns VBox status code.
738 * @param pSet Test set to create and register new object for.
739 * @param pszName Name of new object to create.
740 * @param ppObj Where to return the pointer to the newly created object on success.
741 */
742int AudioTestSetObjCreateAndRegister(PAUDIOTESTSET pSet, const char *pszName, PAUDIOTESTOBJ *ppObj)
743{
744 AssertPtrReturn(pszName, VERR_INVALID_POINTER);
745
746 PAUDIOTESTOBJ pObj = (PAUDIOTESTOBJ)RTMemAlloc(sizeof(AUDIOTESTOBJ));
747 AssertPtrReturn(pObj, VERR_NO_MEMORY);
748
749 if (RTStrPrintf2(pObj->szName, sizeof(pObj->szName), "%04RU32-%s", pSet->cObj, pszName) <= 0)
750 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
751
752 /** @todo Generalize this function more once we have more object types. */
753
754 char szFilePath[RTPATH_MAX];
755 int rc = RTPathJoin(szFilePath, sizeof(szFilePath), pSet->szPathAbs, pObj->szName);
756 AssertRCReturn(rc, rc);
757
758 rc = RTFileOpen(&pObj->File.hFile, szFilePath, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
759 if (RT_SUCCESS(rc))
760 {
761 pObj->enmType = AUDIOTESTOBJTYPE_FILE;
762
763 RTListAppend(&pSet->lstObj, &pObj->Node);
764 pSet->cObj++;
765
766 *ppObj = pObj;
767 }
768
769 if (RT_FAILURE(rc))
770 RTMemFree(pObj);
771
772 return rc;
773}
774
775/**
776 * Writes to a created audio test object.
777 *
778 * @returns VBox status code.
779 * @param pObj Audio test object to write to.
780 * @param pvBuf Pointer to data to write.
781 * @param cbBuf Size (in bytes) of \a pvBuf to write.
782 */
783int AudioTestSetObjWrite(PAUDIOTESTOBJ pObj, void *pvBuf, size_t cbBuf)
784{
785 /** @todo Generalize this function more once we have more object types. */
786 AssertReturn(pObj->enmType == AUDIOTESTOBJTYPE_FILE, VERR_INVALID_PARAMETER);
787
788 return RTFileWrite(pObj->File.hFile, pvBuf, cbBuf, NULL);
789}
790
791/**
792 * Closes an opened audio test object.
793 *
794 * @returns VBox status code.
795 * @param pObj Audio test object to close.
796 */
797int AudioTestSetObjClose(PAUDIOTESTOBJ pObj)
798{
799 if (!pObj)
800 return VINF_SUCCESS;
801
802 /** @todo Generalize this function more once we have more object types. */
803 AssertReturn(pObj->enmType == AUDIOTESTOBJTYPE_FILE, VERR_INVALID_PARAMETER);
804
805 int rc = VINF_SUCCESS;
806
807 if (RTFileIsValid(pObj->File.hFile))
808 {
809 rc = RTFileClose(pObj->File.hFile);
810 pObj->File.hFile = NIL_RTFILE;
811 }
812
813 return rc;
814}
815
816/**
817 * Packs an audio test so that it's ready for transmission.
818 *
819 * @returns VBox status code.
820 * @param pSet Test set to pack.
821 * @param pszOutDir Directory where to store the packed test set.
822 * @param pszFileName Where to return the final name of the packed test set. Optional and can be NULL.
823 * @param cbFileName Size (in bytes) of \a pszFileName.
824 */
825int AudioTestSetPack(PAUDIOTESTSET pSet, const char *pszOutDir, char *pszFileName, size_t cbFileName)
826{
827 AssertReturn(!pszFileName || cbFileName, VERR_INVALID_PARAMETER);
828 AssertReturn(audioTestManifestIsOpen(pSet), VERR_WRONG_ORDER);
829
830 /** @todo Check and deny if \a pszOutDir is part of the set's path. */
831
832 char szOutName[RT_ELEMENTS(AUDIOTEST_PATH_PREFIX_STR) + AUDIOTEST_TAG_MAX + 16];
833 if (RTStrPrintf2(szOutName, sizeof(szOutName), "%s-%s.tar.gz", AUDIOTEST_PATH_PREFIX_STR, pSet->szTag) <= 0)
834 AssertFailedReturn(VERR_BUFFER_OVERFLOW);
835
836 char szOutPath[RTPATH_MAX];
837 int rc = RTPathJoin(szOutPath, sizeof(szOutPath), pszOutDir, szOutName);
838 AssertRCReturn(rc, rc);
839
840 const char *apszArgs[10];
841 unsigned cArgs = 0;
842
843 apszArgs[cArgs++] = "AudioTest";
844 apszArgs[cArgs++] = "--create";
845 apszArgs[cArgs++] = "--gzip";
846 apszArgs[cArgs++] = "--directory";
847 apszArgs[cArgs++] = pSet->szPathAbs;
848 apszArgs[cArgs++] = "--file";
849 apszArgs[cArgs++] = szOutPath;
850 apszArgs[cArgs++] = ".";
851
852 RTEXITCODE rcExit = RTZipTarCmd(cArgs, (char **)apszArgs);
853 if (rcExit != RTEXITCODE_SUCCESS)
854 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
855
856 if (RT_SUCCESS(rc))
857 {
858 if (pszFileName)
859 rc = RTStrCopy(pszFileName, cbFileName, szOutPath);
860 }
861
862 return rc;
863}
864
865/**
866 * Unpacks a formerly packed audio test set.
867 *
868 * @returns VBox status code.
869 * @param pszFile Test set file to unpack.
870 * @param pszOutDir Directory where to unpack the test set into.
871 * If the directory does not exist it will be created.
872 */
873int AudioTestSetUnpack(const char *pszFile, const char *pszOutDir)
874{
875 RT_NOREF(pszFile, pszOutDir);
876
877 // RTZipTarCmd()
878
879 return VERR_NOT_IMPLEMENTED;
880}
881
882/**
883 * Verifies an opened audio test set.
884 *
885 * @returns VBox status code.
886 * @param pSet Test set to verify.
887 * @param pszTag Tag to use for verification purpose.
888 * @param pErrDesc Where to return the test verification errors.
889 *
890 * @note Test verification errors have to be checked for errors, regardless of the
891 * actual return code.
892 */
893int AudioTestSetVerify(PAUDIOTESTSET pSet, const char *pszTag, PAUDIOTESTERRORDESC pErrDesc)
894{
895 AssertReturn(audioTestManifestIsOpen(pSet), VERR_WRONG_ORDER);
896
897 /* We ASSUME the caller has not init'd pErrDesc. */
898 audioTestErrorDescInit(pErrDesc);
899
900 char szVal[_1K]; /** @todo Enough, too much? */
901
902 int rc2 = RTIniFileQueryValue(pSet->f.hIniFile, AUDIOTEST_INI_SEC_HDR_STR, "tag", szVal, sizeof(szVal), NULL);
903 if ( RT_FAILURE(rc2)
904 || RTStrICmp(pszTag, szVal))
905 audioTestErrorDescAdd(pErrDesc, "Tag '%s' does not match with manifest's tag '%s'", pszTag, szVal);
906
907 /* Only return critical stuff not related to actual testing here. */
908 return VINF_SUCCESS;
909}
910
911
912/*********************************************************************************************************************************
913* WAVE File Reader. *
914*********************************************************************************************************************************/
915/**
916 * Opens a wave (.WAV) file for reading.
917 *
918 * @returns VBox status code.
919 * @param pszFile The file to open.
920 * @param pWaveFile The open wave file structure to fill in on success.
921 */
922int AudioTestWaveFileOpen(const char *pszFile, PAUDIOTESTWAVEFILE pWaveFile)
923{
924 RT_ZERO(pWaveFile->Props);
925 pWaveFile->hFile = NIL_RTFILE;
926 int rc = RTFileOpen(&pWaveFile->hFile, pszFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
927 if (RT_FAILURE(rc))
928 return rc;
929 uint64_t cbFile = 0;
930 rc = RTFileQuerySize(pWaveFile->hFile, &cbFile);
931 if (RT_SUCCESS(rc))
932 {
933 union
934 {
935 uint8_t ab[512];
936 struct
937 {
938 RTRIFFHDR Hdr;
939 RTRIFFWAVEFMTCHUNK Fmt;
940 } Wave;
941 RTRIFFLIST List;
942 RTRIFFCHUNK Chunk;
943 RTRIFFWAVEDATACHUNK Data;
944 } uBuf;
945
946 rc = RTFileRead(pWaveFile->hFile, &uBuf.Wave, sizeof(uBuf.Wave), NULL);
947 if (RT_SUCCESS(rc))
948 {
949 rc = VERR_VFS_UNKNOWN_FORMAT;
950 if ( uBuf.Wave.Hdr.uMagic == RTRIFFHDR_MAGIC
951 && uBuf.Wave.Hdr.uFileType == RTRIFF_FILE_TYPE_WAVE
952 && uBuf.Wave.Fmt.Chunk.uMagic == RTRIFFWAVEFMT_MAGIC
953 && uBuf.Wave.Fmt.Chunk.cbChunk >= sizeof(uBuf.Wave.Fmt.Data))
954 {
955 if (uBuf.Wave.Hdr.cbFile != cbFile - sizeof(RTRIFFCHUNK))
956 RTMsgWarning("%s: File size mismatch: %#x, actual %#RX64 (ignored)",
957 pszFile, uBuf.Wave.Hdr.cbFile, cbFile - sizeof(RTRIFFCHUNK));
958 rc = VERR_VFS_BOGUS_FORMAT;
959 if (uBuf.Wave.Fmt.Data.uFormatTag != RTRIFFWAVEFMT_TAG_PCM)
960 RTMsgError("%s: Unsupported uFormatTag value: %u (expected 1)", pszFile, uBuf.Wave.Fmt.Data.uFormatTag);
961 else if ( uBuf.Wave.Fmt.Data.cBitsPerSample != 8
962 && uBuf.Wave.Fmt.Data.cBitsPerSample != 16
963 && uBuf.Wave.Fmt.Data.cBitsPerSample != 32)
964 RTMsgError("%s: Unsupported cBitsPerSample value: %u", pszFile, uBuf.Wave.Fmt.Data.cBitsPerSample);
965 else if ( uBuf.Wave.Fmt.Data.cChannels < 1
966 || uBuf.Wave.Fmt.Data.cChannels >= 16)
967 RTMsgError("%s: Unsupported cChannels value: %u (expected 1..15)", pszFile, uBuf.Wave.Fmt.Data.cChannels);
968 else if ( uBuf.Wave.Fmt.Data.uHz < 4096
969 || uBuf.Wave.Fmt.Data.uHz > 768000)
970 RTMsgError("%s: Unsupported uHz value: %u (expected 4096..768000)", pszFile, uBuf.Wave.Fmt.Data.uHz);
971 else if (uBuf.Wave.Fmt.Data.cbFrame != uBuf.Wave.Fmt.Data.cChannels * uBuf.Wave.Fmt.Data.cBitsPerSample / 8)
972 RTMsgError("%s: Invalid cbFrame value: %u (expected %u)", pszFile, uBuf.Wave.Fmt.Data.cbFrame,
973 uBuf.Wave.Fmt.Data.cChannels * uBuf.Wave.Fmt.Data.cBitsPerSample / 8);
974 else if (uBuf.Wave.Fmt.Data.cbRate != uBuf.Wave.Fmt.Data.cbFrame * uBuf.Wave.Fmt.Data.uHz)
975 RTMsgError("%s: Invalid cbRate value: %u (expected %u)", pszFile, uBuf.Wave.Fmt.Data.cbRate,
976 uBuf.Wave.Fmt.Data.cbFrame * uBuf.Wave.Fmt.Data.uHz);
977 else
978 {
979 /*
980 * Copy out the data we need from the file format structure.
981 */
982 PDMAudioPropsInit(&pWaveFile->Props, uBuf.Wave.Fmt.Data.cBitsPerSample / 8, true /*fSigned*/,
983 uBuf.Wave.Fmt.Data.cChannels, uBuf.Wave.Fmt.Data.uHz);
984 pWaveFile->offSamples = sizeof(RTRIFFHDR) + sizeof(RTRIFFCHUNK) + uBuf.Wave.Fmt.Chunk.cbChunk;
985
986 /*
987 * Find the 'data' chunk with the audio samples.
988 *
989 * There can be INFO lists both preceeding this and succeeding
990 * it, containing IART and other things we can ignored. Thus
991 * we read a list header here rather than just a chunk header,
992 * since it doesn't matter if we read 4 bytes extra as
993 * AudioTestWaveFileRead uses RTFileReadAt anyway.
994 */
995 rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
996 for (uint32_t i = 0;
997 i < 128
998 && RT_SUCCESS(rc)
999 && uBuf.Chunk.uMagic != RTRIFFWAVEDATACHUNK_MAGIC
1000 && (uint64_t)uBuf.Chunk.cbChunk + sizeof(RTRIFFCHUNK) * 2 <= cbFile - pWaveFile->offSamples;
1001 i++)
1002 {
1003 if ( uBuf.List.uMagic == RTRIFFLIST_MAGIC
1004 && uBuf.List.uListType == RTRIFFLIST_TYPE_INFO)
1005 { /*skip*/ }
1006 else if (uBuf.Chunk.uMagic == RTRIFFPADCHUNK_MAGIC)
1007 { /*skip*/ }
1008 else
1009 break;
1010 pWaveFile->offSamples += sizeof(RTRIFFCHUNK) + uBuf.Chunk.cbChunk;
1011 rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
1012 }
1013 if (RT_SUCCESS(rc))
1014 {
1015 pWaveFile->offSamples += sizeof(uBuf.Data.Chunk);
1016 pWaveFile->cbSamples = (uint32_t)cbFile - pWaveFile->offSamples;
1017
1018 rc = VERR_VFS_BOGUS_FORMAT;
1019 if ( uBuf.Data.Chunk.uMagic == RTRIFFWAVEDATACHUNK_MAGIC
1020 && uBuf.Data.Chunk.cbChunk <= pWaveFile->cbSamples
1021 && PDMAudioPropsIsSizeAligned(&pWaveFile->Props, uBuf.Data.Chunk.cbChunk))
1022 {
1023 pWaveFile->cbSamples = uBuf.Data.Chunk.cbChunk;
1024
1025 /*
1026 * We're good!
1027 */
1028 pWaveFile->offCur = 0;
1029 return VINF_SUCCESS;
1030 }
1031
1032 RTMsgError("%s: Bad data header: uMagic=%#x (expected %#x), cbChunk=%#x (max %#RX64, align %u)",
1033 pszFile, uBuf.Data.Chunk.uMagic, RTRIFFWAVEDATACHUNK_MAGIC,
1034 uBuf.Data.Chunk.cbChunk, pWaveFile->cbSamples, PDMAudioPropsFrameSize(&pWaveFile->Props));
1035 }
1036 else
1037 RTMsgError("%s: Failed to read data header: %Rrc", pszFile, rc);
1038 }
1039 }
1040 else
1041 RTMsgError("%s: Bad file header: uMagic=%#x (vs. %#x), uFileType=%#x (vs %#x), uFmtMagic=%#x (vs %#x) cbFmtChunk=%#x (min %#x)",
1042 pszFile, uBuf.Wave.Hdr.uMagic, RTRIFFHDR_MAGIC, uBuf.Wave.Hdr.uFileType, RTRIFF_FILE_TYPE_WAVE,
1043 uBuf.Wave.Fmt.Chunk.uMagic, RTRIFFWAVEFMT_MAGIC,
1044 uBuf.Wave.Fmt.Chunk.cbChunk, sizeof(uBuf.Wave.Fmt.Data));
1045 }
1046 else
1047 RTMsgError("%s: Failed to read file header: %Rrc", pszFile, rc);
1048 }
1049 else
1050 RTMsgError("%s: Failed to query file size: %Rrc", pszFile, rc);
1051
1052 RTFileClose(pWaveFile->hFile);
1053 pWaveFile->hFile = NIL_RTFILE;
1054 return rc;
1055}
1056
1057/**
1058 * Closes a wave file.
1059 */
1060void AudioTestWaveFileClose(PAUDIOTESTWAVEFILE pWaveFile)
1061{
1062 RTFileClose(pWaveFile->hFile);
1063 pWaveFile->hFile = NIL_RTFILE;
1064}
1065
1066/**
1067 * Reads samples from a wave file.
1068 *
1069 * @returns VBox status code. See RTVfsFileRead for EOF status handling.
1070 * @param pWaveFile The file to read from.
1071 * @param pvBuf Where to put the samples.
1072 * @param cbBuf How much to read at most.
1073 * @param pcbRead Where to return the actual number of bytes read,
1074 * optional.
1075 */
1076int AudioTestWaveFileRead(PAUDIOTESTWAVEFILE pWaveFile, void *pvBuf, size_t cbBuf, size_t *pcbRead)
1077{
1078 int rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offCur, pvBuf, cbBuf, pcbRead);
1079 if (RT_SUCCESS(rc))
1080 {
1081 if (pcbRead)
1082 {
1083 pWaveFile->offCur += (uint32_t)*pcbRead;
1084 if (cbBuf > *pcbRead)
1085 rc = VINF_EOF;
1086 else if (!cbBuf && pWaveFile->offCur == pWaveFile->cbSamples)
1087 rc = VINF_EOF;
1088 }
1089 else
1090 pWaveFile->offCur += (uint32_t)cbBuf;
1091 }
1092 return rc;
1093}
1094
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

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