VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/utils/audio/vkatDriverStack.cpp@ 92002

最後變更 在這個檔案從92002是 91842,由 vboxsync 提交於 3 年 前

Audio/VKAT: Fixes for audioTestDriverStackStreamDestroy(). ​bugref:10008

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 57.6 KB
 
1/* $Id: vkatDriverStack.cpp 91842 2021-10-19 14:44:02Z vboxsync $ */
2/** @file
3 * Validation Kit Audio Test (VKAT) - Driver stack code.
4 */
5
6/*
7 * Copyright (C) 2021 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.alldomusa.eu.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#define LOG_GROUP LOG_GROUP_AUDIO_TEST
32#include <iprt/log.h>
33
34#include <iprt/errcore.h>
35#include <iprt/message.h>
36#include <iprt/stream.h>
37#include <iprt/string.h>
38#include <iprt/uuid.h>
39#include <iprt/test.h>
40
41
42/**
43 * Internal driver instance data
44 * @note This must be put here as it's needed before pdmdrv.h is included.
45 */
46typedef struct PDMDRVINSINT
47{
48 /** The stack the drive belongs to. */
49 struct AUDIOTESTDRVSTACK *pStack;
50} PDMDRVINSINT;
51#define PDMDRVINSINT_DECLARED
52
53#include "vkatInternal.h"
54#include "VBoxDD.h"
55
56
57
58/*********************************************************************************************************************************
59* Fake PDM Driver Handling. *
60*********************************************************************************************************************************/
61
62/** @name Driver Fakes/Stubs
63 *
64 * @note The VMM functions defined here will turn into driver helpers before
65 * long, as the drivers aren't supposed to import directly from the VMM in
66 * the future.
67 *
68 * @{ */
69
70VMMR3DECL(PCFGMNODE) CFGMR3GetChild(PCFGMNODE pNode, const char *pszPath)
71{
72 RT_NOREF(pNode, pszPath);
73 return NULL;
74}
75
76
77VMMR3DECL(int) CFGMR3QueryString(PCFGMNODE pNode, const char *pszName, char *pszString, size_t cchString)
78{
79 if (pNode != NULL)
80 {
81 PCPDMDRVREG pDrvReg = (PCPDMDRVREG)pNode;
82 if (g_uVerbosity > 2)
83 RTPrintf("debug: CFGMR3QueryString([%s], %s, %p, %#x)\n", pDrvReg->szName, pszName, pszString, cchString);
84
85 if ( ( strcmp(pDrvReg->szName, "PulseAudio") == 0
86 || strcmp(pDrvReg->szName, "HostAudioWas") == 0)
87 && strcmp(pszName, "VmName") == 0)
88 return RTStrCopy(pszString, cchString, "vkat");
89
90 if ( strcmp(pDrvReg->szName, "HostAudioWas") == 0
91 && strcmp(pszName, "VmUuid") == 0)
92 return RTStrCopy(pszString, cchString, "794c9192-d045-4f28-91ed-46253ac9998e");
93 }
94 else if (g_uVerbosity > 2)
95 RTPrintf("debug: CFGMR3QueryString(%p, %s, %p, %#x)\n", pNode, pszName, pszString, cchString);
96
97 return VERR_CFGM_VALUE_NOT_FOUND;
98}
99
100
101VMMR3DECL(int) CFGMR3QueryStringAlloc(PCFGMNODE pNode, const char *pszName, char **ppszString)
102{
103 char szStr[128];
104 int rc = CFGMR3QueryString(pNode, pszName, szStr, sizeof(szStr));
105 if (RT_SUCCESS(rc))
106 *ppszString = RTStrDup(szStr);
107
108 return rc;
109}
110
111
112VMMR3DECL(void) MMR3HeapFree(void *pv)
113{
114 /* counterpart to CFGMR3QueryStringAlloc */
115 RTStrFree((char *)pv);
116}
117
118
119VMMR3DECL(int) CFGMR3QueryStringDef(PCFGMNODE pNode, const char *pszName, char *pszString, size_t cchString, const char *pszDef)
120{
121 PCPDMDRVREG pDrvReg = (PCPDMDRVREG)pNode;
122 if (RT_VALID_PTR(pDrvReg))
123 {
124 const char *pszRet = pszDef;
125 if ( g_pszDrvAudioDebug
126 && strcmp(pDrvReg->szName, "AUDIO") == 0
127 && strcmp(pszName, "DebugPathOut") == 0)
128 pszRet = g_pszDrvAudioDebug;
129
130 int rc = RTStrCopy(pszString, cchString, pszRet);
131
132 if (g_uVerbosity > 2)
133 RTPrintf("debug: CFGMR3QueryStringDef([%s], %s, %p, %#x, %s) -> '%s' + %Rrc\n",
134 pDrvReg->szName, pszName, pszString, cchString, pszDef, pszRet, rc);
135 return rc;
136 }
137
138 if (g_uVerbosity > 2)
139 RTPrintf("debug: CFGMR3QueryStringDef(%p, %s, %p, %#x, %s)\n", pNode, pszName, pszString, cchString, pszDef);
140 return RTStrCopy(pszString, cchString, pszDef);
141}
142
143
144VMMR3DECL(int) CFGMR3QueryBoolDef(PCFGMNODE pNode, const char *pszName, bool *pf, bool fDef)
145{
146 PCPDMDRVREG pDrvReg = (PCPDMDRVREG)pNode;
147 if (RT_VALID_PTR(pDrvReg))
148 {
149 *pf = fDef;
150 if ( strcmp(pDrvReg->szName, "AUDIO") == 0
151 && strcmp(pszName, "DebugEnabled") == 0)
152 *pf = g_fDrvAudioDebug;
153
154 if (g_uVerbosity > 2)
155 RTPrintf("debug: CFGMR3QueryBoolDef([%s], %s, %p, %RTbool) -> %RTbool\n", pDrvReg->szName, pszName, pf, fDef, *pf);
156 return VINF_SUCCESS;
157 }
158 *pf = fDef;
159 return VINF_SUCCESS;
160}
161
162
163VMMR3DECL(int) CFGMR3QueryU8(PCFGMNODE pNode, const char *pszName, uint8_t *pu8)
164{
165 RT_NOREF(pNode, pszName, pu8);
166 return VERR_CFGM_VALUE_NOT_FOUND;
167}
168
169
170VMMR3DECL(int) CFGMR3QueryU32(PCFGMNODE pNode, const char *pszName, uint32_t *pu32)
171{
172 RT_NOREF(pNode, pszName, pu32);
173 return VERR_CFGM_VALUE_NOT_FOUND;
174}
175
176
177VMMR3DECL(int) CFGMR3ValidateConfig(PCFGMNODE pNode, const char *pszNode,
178 const char *pszValidValues, const char *pszValidNodes,
179 const char *pszWho, uint32_t uInstance)
180{
181 RT_NOREF(pNode, pszNode, pszValidValues, pszValidNodes, pszWho, uInstance);
182 return VINF_SUCCESS;
183}
184
185/** @} */
186
187/** @name Driver Helper Fakes
188 * @{ */
189
190static DECLCALLBACK(int) audioTestDrvHlp_Attach(PPDMDRVINS pDrvIns, uint32_t fFlags, PPDMIBASE *ppBaseInterface)
191{
192 /* DrvAudio must be allowed to attach the backend driver (paranoid
193 backend drivers may call us to check that nothing is attached). */
194 if (strcmp(pDrvIns->pReg->szName, "AUDIO") == 0)
195 {
196 PAUDIOTESTDRVSTACK pDrvStack = pDrvIns->Internal.s.pStack;
197 AssertReturn(pDrvStack->pDrvBackendIns == NULL, VERR_PDM_DRIVER_ALREADY_ATTACHED);
198
199 if (g_uVerbosity > 1)
200 RTMsgInfo("Attaching backend '%s' to DrvAudio...\n", pDrvStack->pDrvReg->szName);
201 int rc = audioTestDrvConstruct(pDrvStack, pDrvStack->pDrvReg, pDrvIns, &pDrvStack->pDrvBackendIns);
202 if (RT_SUCCESS(rc))
203 {
204 if (ppBaseInterface)
205 *ppBaseInterface = &pDrvStack->pDrvBackendIns->IBase;
206 }
207 else
208 RTMsgError("Failed to attach backend: %Rrc", rc);
209 return rc;
210 }
211 RT_NOREF(fFlags);
212 return VERR_PDM_NO_ATTACHED_DRIVER;
213}
214
215
216static DECLCALLBACK(void) audioTestDrvHlp_STAMRegister(PPDMDRVINS pDrvIns, void *pvSample, STAMTYPE enmType, const char *pszName,
217 STAMUNIT enmUnit, const char *pszDesc)
218{
219 RT_NOREF(pDrvIns, pvSample, enmType, pszName, enmUnit, pszDesc);
220}
221
222
223static DECLCALLBACK(void) audioTestDrvHlp_STAMRegisterF(PPDMDRVINS pDrvIns, void *pvSample, STAMTYPE enmType,
224 STAMVISIBILITY enmVisibility, STAMUNIT enmUnit, const char *pszDesc,
225 const char *pszName, ...)
226{
227 RT_NOREF(pDrvIns, pvSample, enmType, enmVisibility, enmUnit, pszDesc, pszName);
228}
229
230
231static DECLCALLBACK(void) audioTestDrvHlp_STAMRegisterV(PPDMDRVINS pDrvIns, void *pvSample, STAMTYPE enmType,
232 STAMVISIBILITY enmVisibility, STAMUNIT enmUnit, const char *pszDesc,
233 const char *pszName, va_list args)
234{
235 RT_NOREF(pDrvIns, pvSample, enmType, enmVisibility, enmUnit, pszDesc, pszName, args);
236}
237
238
239static DECLCALLBACK(int) audioTestDrvHlp_STAMDeregister(PPDMDRVINS pDrvIns, void *pvSample)
240{
241 RT_NOREF(pDrvIns, pvSample);
242 return VINF_SUCCESS;
243}
244
245
246static DECLCALLBACK(int) audioTestDrvHlp_STAMDeregisterByPrefix(PPDMDRVINS pDrvIns, const char *pszPrefix)
247{
248 RT_NOREF(pDrvIns, pszPrefix);
249 return VINF_SUCCESS;
250}
251
252/**
253 * Get the driver helpers.
254 */
255static const PDMDRVHLPR3 *audioTestFakeGetDrvHlp(void)
256{
257 /*
258 * Note! No initializer for s_DrvHlp (also why it's not a file global).
259 * We do not want to have to update this code every time PDMDRVHLPR3
260 * grows new entries or are otherwise modified. Only when the
261 * entries used by the audio driver changes do we want to change
262 * our code.
263 */
264 static PDMDRVHLPR3 s_DrvHlp;
265 if (s_DrvHlp.u32Version != PDM_DRVHLPR3_VERSION)
266 {
267 s_DrvHlp.u32Version = PDM_DRVHLPR3_VERSION;
268 s_DrvHlp.u32TheEnd = PDM_DRVHLPR3_VERSION;
269 s_DrvHlp.pfnAttach = audioTestDrvHlp_Attach;
270 s_DrvHlp.pfnSTAMRegister = audioTestDrvHlp_STAMRegister;
271 s_DrvHlp.pfnSTAMRegisterF = audioTestDrvHlp_STAMRegisterF;
272 s_DrvHlp.pfnSTAMRegisterV = audioTestDrvHlp_STAMRegisterV;
273 s_DrvHlp.pfnSTAMDeregister = audioTestDrvHlp_STAMDeregister;
274 s_DrvHlp.pfnSTAMDeregisterByPrefix = audioTestDrvHlp_STAMDeregisterByPrefix;
275 }
276 return &s_DrvHlp;
277}
278
279/** @} */
280
281
282/**
283 * Implementation of PDMIBASE::pfnQueryInterface for a fake device above
284 * DrvAudio.
285 */
286static DECLCALLBACK(void *) audioTestFakeDeviceIBaseQueryInterface(PPDMIBASE pInterface, const char *pszIID)
287{
288 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, pInterface);
289 RTMsgWarning("audioTestFakeDeviceIBaseQueryInterface: Unknown interface: %s\n", pszIID);
290 return NULL;
291}
292
293/** IBase interface for a fake device above DrvAudio. */
294static PDMIBASE g_AudioTestFakeDeviceIBase = { audioTestFakeDeviceIBaseQueryInterface };
295
296
297static DECLCALLBACK(int) audioTestIHostAudioPort_DoOnWorkerThread(PPDMIHOSTAUDIOPORT pInterface, PPDMAUDIOBACKENDSTREAM pStream,
298 uintptr_t uUser, void *pvUser)
299{
300 RT_NOREF(pInterface, pStream, uUser, pvUser);
301 RTMsgWarning("audioTestIHostAudioPort_DoOnWorkerThread was called\n");
302 return VERR_NOT_IMPLEMENTED;
303}
304
305
306DECLCALLBACK(void) audioTestIHostAudioPort_NotifyDeviceChanged(PPDMIHOSTAUDIOPORT pInterface, PDMAUDIODIR enmDir, void *pvUser)
307{
308 RT_NOREF(pInterface, enmDir, pvUser);
309 RTMsgWarning("audioTestIHostAudioPort_NotifyDeviceChanged was called\n");
310}
311
312
313static DECLCALLBACK(void) audioTestIHostAudioPort_StreamNotifyPreparingDeviceSwitch(PPDMIHOSTAUDIOPORT pInterface,
314 PPDMAUDIOBACKENDSTREAM pStream)
315{
316 RT_NOREF(pInterface, pStream);
317 RTMsgWarning("audioTestIHostAudioPort_StreamNotifyPreparingDeviceSwitch was called\n");
318}
319
320
321static DECLCALLBACK(void) audioTestIHostAudioPort_StreamNotifyDeviceChanged(PPDMIHOSTAUDIOPORT pInterface,
322 PPDMAUDIOBACKENDSTREAM pStream, bool fReInit)
323{
324 RT_NOREF(pInterface, pStream, fReInit);
325 RTMsgWarning("audioTestIHostAudioPort_StreamNotifyDeviceChanged was called\n");
326}
327
328
329static DECLCALLBACK(void) audioTestIHostAudioPort_NotifyDevicesChanged(PPDMIHOSTAUDIOPORT pInterface)
330{
331 RT_NOREF(pInterface);
332 RTMsgWarning("audioTestIHostAudioPort_NotifyDevicesChanged was called\n");
333}
334
335
336static PDMIHOSTAUDIOPORT g_AudioTestIHostAudioPort =
337{
338 audioTestIHostAudioPort_DoOnWorkerThread,
339 audioTestIHostAudioPort_NotifyDeviceChanged,
340 audioTestIHostAudioPort_StreamNotifyPreparingDeviceSwitch,
341 audioTestIHostAudioPort_StreamNotifyDeviceChanged,
342 audioTestIHostAudioPort_NotifyDevicesChanged,
343};
344
345
346/**
347 * Implementation of PDMIBASE::pfnQueryInterface for a fake DrvAudio above a
348 * backend.
349 */
350static DECLCALLBACK(void *) audioTestFakeDrvAudioIBaseQueryInterface(PPDMIBASE pInterface, const char *pszIID)
351{
352 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, pInterface);
353 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIOPORT, &g_AudioTestIHostAudioPort);
354 RTMsgWarning("audioTestFakeDrvAudioIBaseQueryInterface: Unknown interface: %s\n", pszIID);
355 return NULL;
356}
357
358
359/** IBase interface for a fake DrvAudio above a lonesome backend. */
360static PDMIBASE g_AudioTestFakeDrvAudioIBase = { audioTestFakeDrvAudioIBaseQueryInterface };
361
362
363
364/**
365 * Constructs a PDM audio driver instance.
366 *
367 * @returns VBox status code.
368 * @param pDrvStack The stack this is associated with.
369 * @param pDrvReg PDM driver registration record to use for construction.
370 * @param pParentDrvIns The parent driver (if any).
371 * @param ppDrvIns Where to return the driver instance structure.
372 */
373int audioTestDrvConstruct(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, PPDMDRVINS pParentDrvIns,
374 PPPDMDRVINS ppDrvIns)
375{
376 /* The destruct function must have valid data to work with. */
377 *ppDrvIns = NULL;
378
379 /*
380 * Check registration structure validation (doesn't need to be too
381 * thorough, PDM check it in detail on every VM startup).
382 */
383 AssertPtrReturn(pDrvReg, VERR_INVALID_POINTER);
384 RTMsgInfo("Initializing backend '%s' ...\n", pDrvReg->szName);
385 AssertPtrReturn(pDrvReg->pfnConstruct, VERR_INVALID_PARAMETER);
386
387 /*
388 * Create the instance data structure.
389 */
390 PPDMDRVINS pDrvIns = (PPDMDRVINS)RTMemAllocZVar(RT_UOFFSETOF_DYN(PDMDRVINS, achInstanceData[pDrvReg->cbInstance]));
391 RTTEST_CHECK_RET(g_hTest, pDrvIns, VERR_NO_MEMORY);
392
393 pDrvIns->u32Version = PDM_DRVINS_VERSION;
394 pDrvIns->iInstance = 0;
395 pDrvIns->pHlpR3 = audioTestFakeGetDrvHlp();
396 pDrvIns->pvInstanceDataR3 = &pDrvIns->achInstanceData[0];
397 pDrvIns->pReg = pDrvReg;
398 pDrvIns->pCfg = (PCFGMNODE)pDrvReg;
399 pDrvIns->Internal.s.pStack = pDrvStack;
400 pDrvIns->pUpBase = NULL;
401 pDrvIns->pDownBase = NULL;
402 if (pParentDrvIns)
403 {
404 Assert(pParentDrvIns->pDownBase == NULL);
405 pParentDrvIns->pDownBase = &pDrvIns->IBase;
406 pDrvIns->pUpBase = &pParentDrvIns->IBase;
407 }
408 else if (strcmp(pDrvReg->szName, "AUDIO") == 0)
409 pDrvIns->pUpBase = &g_AudioTestFakeDeviceIBase;
410 else
411 pDrvIns->pUpBase = &g_AudioTestFakeDrvAudioIBase;
412
413 /*
414 * Invoke the constructor.
415 */
416 int rc = pDrvReg->pfnConstruct(pDrvIns, pDrvIns->pCfg, 0 /*fFlags*/);
417 if (RT_SUCCESS(rc))
418 {
419 *ppDrvIns = pDrvIns;
420 return VINF_SUCCESS;
421 }
422
423 if (pDrvReg->pfnDestruct)
424 pDrvReg->pfnDestruct(pDrvIns);
425 RTMemFree(pDrvIns);
426 return rc;
427}
428
429
430/**
431 * Destructs a PDM audio driver instance.
432 *
433 * @param pDrvIns Driver instance to destruct.
434 */
435static void audioTestDrvDestruct(PPDMDRVINS pDrvIns)
436{
437 if (pDrvIns)
438 {
439 Assert(pDrvIns->u32Version == PDM_DRVINS_VERSION);
440
441 if (pDrvIns->pReg->pfnDestruct)
442 pDrvIns->pReg->pfnDestruct(pDrvIns);
443
444 pDrvIns->u32Version = 0;
445 pDrvIns->pReg = NULL;
446 RTMemFree(pDrvIns);
447 }
448}
449
450
451/**
452 * Sends the PDM driver a power off notification.
453 *
454 * @param pDrvIns Driver instance to notify.
455 */
456static void audioTestDrvNotifyPowerOff(PPDMDRVINS pDrvIns)
457{
458 if (pDrvIns)
459 {
460 Assert(pDrvIns->u32Version == PDM_DRVINS_VERSION);
461 if (pDrvIns->pReg->pfnPowerOff)
462 pDrvIns->pReg->pfnPowerOff(pDrvIns);
463 }
464}
465
466
467/**
468 * Deletes a driver stack.
469 *
470 * This will power off and destroy the drivers.
471 */
472void audioTestDriverStackDelete(PAUDIOTESTDRVSTACK pDrvStack)
473{
474 /*
475 * Do power off notifications (top to bottom).
476 */
477 audioTestDrvNotifyPowerOff(pDrvStack->pDrvAudioIns);
478 audioTestDrvNotifyPowerOff(pDrvStack->pDrvBackendIns);
479
480 /*
481 * Drivers are destroyed from bottom to top (closest to the device).
482 */
483 audioTestDrvDestruct(pDrvStack->pDrvBackendIns);
484 pDrvStack->pDrvBackendIns = NULL;
485 pDrvStack->pIHostAudio = NULL;
486
487 audioTestDrvDestruct(pDrvStack->pDrvAudioIns);
488 pDrvStack->pDrvAudioIns = NULL;
489 pDrvStack->pIAudioConnector = NULL;
490
491 PDMAudioHostEnumDelete(&pDrvStack->DevEnum);
492}
493
494
495/**
496 * Initializes a driver stack, extended version.
497 *
498 * @returns VBox status code.
499 * @param pDrvStack The driver stack to initialize.
500 * @param pDrvReg The backend driver to use.
501 * @param fEnabledIn Whether input is enabled or not on creation time.
502 * @param fEnabledOut Whether output is enabled or not on creation time.
503 * @param fWithDrvAudio Whether to include DrvAudio in the stack or not.
504 */
505int audioTestDriverStackInitEx(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, bool fEnabledIn, bool fEnabledOut, bool fWithDrvAudio)
506{
507 int rc;
508
509 RT_ZERO(*pDrvStack);
510 pDrvStack->pDrvReg = pDrvReg;
511
512 PDMAudioHostEnumInit(&pDrvStack->DevEnum);
513
514 if (!fWithDrvAudio)
515 rc = audioTestDrvConstruct(pDrvStack, pDrvReg, NULL /*pParentDrvIns*/, &pDrvStack->pDrvBackendIns);
516 else
517 {
518 rc = audioTestDrvConstruct(pDrvStack, &g_DrvAUDIO, NULL /*pParentDrvIns*/, &pDrvStack->pDrvAudioIns);
519 if (RT_SUCCESS(rc))
520 {
521 Assert(pDrvStack->pDrvAudioIns);
522 PPDMIBASE const pIBase = &pDrvStack->pDrvAudioIns->IBase;
523 pDrvStack->pIAudioConnector = (PPDMIAUDIOCONNECTOR)pIBase->pfnQueryInterface(pIBase, PDMIAUDIOCONNECTOR_IID);
524 if (pDrvStack->pIAudioConnector)
525 {
526 /* Both input and output is disabled by default. */
527 if (fEnabledIn)
528 rc = pDrvStack->pIAudioConnector->pfnEnable(pDrvStack->pIAudioConnector, PDMAUDIODIR_IN, true);
529
530 if (RT_SUCCESS(rc))
531 {
532 if (fEnabledOut)
533 rc = pDrvStack->pIAudioConnector->pfnEnable(pDrvStack->pIAudioConnector, PDMAUDIODIR_OUT, true);
534 }
535
536 if (RT_FAILURE(rc))
537 {
538 RTTestFailed(g_hTest, "Failed to enabled input and output: %Rrc", rc);
539 audioTestDriverStackDelete(pDrvStack);
540 }
541 }
542 else
543 {
544 RTTestFailed(g_hTest, "Failed to query PDMIAUDIOCONNECTOR");
545 audioTestDriverStackDelete(pDrvStack);
546 rc = VERR_PDM_MISSING_INTERFACE;
547 }
548 }
549 }
550
551 /*
552 * Get the IHostAudio interface and check that the host driver is working.
553 */
554 if (RT_SUCCESS(rc))
555 {
556 PPDMIBASE const pIBase = &pDrvStack->pDrvBackendIns->IBase;
557 pDrvStack->pIHostAudio = (PPDMIHOSTAUDIO)pIBase->pfnQueryInterface(pIBase, PDMIHOSTAUDIO_IID);
558 if (pDrvStack->pIHostAudio)
559 {
560 PDMAUDIOBACKENDSTS enmStatus = pDrvStack->pIHostAudio->pfnGetStatus(pDrvStack->pIHostAudio, PDMAUDIODIR_OUT);
561 if (enmStatus == PDMAUDIOBACKENDSTS_RUNNING)
562 return VINF_SUCCESS;
563
564 RTTestFailed(g_hTest, "Expected backend status RUNNING, got %d instead", enmStatus);
565 }
566 else
567 RTTestFailed(g_hTest, "Failed to query PDMIHOSTAUDIO for '%s'", pDrvReg->szName);
568 audioTestDriverStackDelete(pDrvStack);
569 }
570
571 return rc;
572}
573
574
575/**
576 * Initializes a driver stack.
577 *
578 * @returns VBox status code.
579 * @param pDrvStack The driver stack to initialize.
580 * @param pDrvReg The backend driver to use.
581 * @param fEnabledIn Whether input is enabled or not on creation time.
582 * @param fEnabledOut Whether output is enabled or not on creation time.
583 * @param fWithDrvAudio Whether to include DrvAudio in the stack or not.
584 */
585int audioTestDriverStackInit(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, bool fWithDrvAudio)
586{
587 return audioTestDriverStackInitEx(pDrvStack, pDrvReg, true /* fEnabledIn */, true /* fEnabledOut */, fWithDrvAudio);
588}
589
590/**
591 * Initializes a driver stack by probing all backends in the order of appearance
592 * in the backends description table.
593 *
594 * @returns VBox status code.
595 * @param pDrvStack The driver stack to initialize.
596 * @param pDrvReg The backend driver to use.
597 * @param fEnabledIn Whether input is enabled or not on creation time.
598 * @param fEnabledOut Whether output is enabled or not on creation time.
599 * @param fWithDrvAudio Whether to include DrvAudio in the stack or not.
600 */
601int audioTestDriverStackProbe(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, bool fEnabledIn, bool fEnabledOut, bool fWithDrvAudio)
602{
603 int rc = VERR_IPE_UNINITIALIZED_STATUS; /* Shut up MSVC. */
604
605 for (size_t i = 0; i < g_cBackends; i++)
606 {
607 pDrvReg = g_aBackends[i].pDrvReg;
608 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Probing for backend '%s' ...\n", g_aBackends[i].pszName);
609
610 rc = audioTestDriverStackInitEx(pDrvStack, pDrvReg, fEnabledIn, fEnabledOut, fWithDrvAudio); /** @todo Make in/out configurable, too. */
611 if (RT_SUCCESS(rc))
612 {
613 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Probing backend '%s' successful\n", g_aBackends[i].pszName);
614 return rc;
615 }
616
617 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Probing backend '%s' failed with %Rrc, trying next one\n",
618 g_aBackends[i].pszName, rc);
619 continue;
620 }
621
622 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Probing all backends failed\n");
623 return rc;
624}
625
626/**
627 * Wrapper around PDMIHOSTAUDIO::pfnSetDevice.
628 */
629int audioTestDriverStackSetDevice(PAUDIOTESTDRVSTACK pDrvStack, PDMAUDIODIR enmDir, const char *pszDevId)
630{
631 int rc;
632 if ( pDrvStack->pIHostAudio
633 && pDrvStack->pIHostAudio->pfnSetDevice)
634 rc = pDrvStack->pIHostAudio->pfnSetDevice(pDrvStack->pIHostAudio, enmDir, pszDevId);
635 else if (!pszDevId || *pszDevId)
636 rc = VINF_SUCCESS;
637 else
638 rc = VERR_INVALID_FUNCTION;
639 return rc;
640}
641
642
643/**
644 * Common stream creation code.
645 *
646 * @returns VBox status code.
647 * @param pDrvStack The audio driver stack to create it via.
648 * @param pCfgReq The requested config.
649 * @param ppStream Where to return the stream pointer on success.
650 * @param pCfgAcq Where to return the actual (well, not necessarily when
651 * using DrvAudio, but probably the same) stream config on
652 * success (not used as input).
653 */
654static int audioTestDriverStackStreamCreate(PAUDIOTESTDRVSTACK pDrvStack, PCPDMAUDIOSTREAMCFG pCfgReq,
655 PPDMAUDIOSTREAM *ppStream, PPDMAUDIOSTREAMCFG pCfgAcq)
656{
657 char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX + 16];
658 int rc;
659 *ppStream = NULL;
660
661 if (pDrvStack->pIAudioConnector)
662 {
663 /*
664 * DrvAudio does most of the work here.
665 */
666 rc = pDrvStack->pIAudioConnector->pfnStreamCreate(pDrvStack->pIAudioConnector, 0 /*fFlags*/, pCfgReq, ppStream);
667 if (RT_SUCCESS(rc))
668 {
669 *pCfgAcq = (*ppStream)->Cfg;
670 RTMsgInfo("Created backend stream: %s\n", PDMAudioStrmCfgToString(pCfgReq, szTmp, sizeof(szTmp)));
671 return rc;
672 }
673 RTTestFailed(g_hTest, "pfnStreamCreate failed: %Rrc", rc);
674 }
675 else
676 {
677 /*
678 * Get the config so we can see how big the PDMAUDIOBACKENDSTREAM
679 * structure actually is for this backend.
680 */
681 PDMAUDIOBACKENDCFG BackendCfg;
682 rc = pDrvStack->pIHostAudio->pfnGetConfig(pDrvStack->pIHostAudio, &BackendCfg);
683 if (RT_SUCCESS(rc))
684 {
685 if (BackendCfg.cbStream >= sizeof(PDMAUDIOBACKENDSTREAM))
686 {
687 /*
688 * Allocate and initialize the stream.
689 */
690 uint32_t const cbStream = sizeof(AUDIOTESTDRVSTACKSTREAM) - sizeof(PDMAUDIOBACKENDSTREAM) + BackendCfg.cbStream;
691 PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)RTMemAllocZVar(cbStream);
692 if (pStreamAt)
693 {
694 pStreamAt->Core.uMagic = PDMAUDIOSTREAM_MAGIC;
695 pStreamAt->Core.Cfg = *pCfgReq;
696 pStreamAt->Core.cbBackend = cbStream;
697
698 pStreamAt->Backend.uMagic = PDMAUDIOBACKENDSTREAM_MAGIC;
699 pStreamAt->Backend.pStream = &pStreamAt->Core;
700
701 /*
702 * Call the backend to create the stream.
703 */
704 rc = pDrvStack->pIHostAudio->pfnStreamCreate(pDrvStack->pIHostAudio, &pStreamAt->Backend,
705 pCfgReq, &pStreamAt->Core.Cfg);
706 if (RT_SUCCESS(rc))
707 {
708 if (g_uVerbosity > 1)
709 RTMsgInfo("Created backend stream: %s\n",
710 PDMAudioStrmCfgToString(&pStreamAt->Core.Cfg, szTmp, sizeof(szTmp)));
711
712 /* Return if stream is ready: */
713 if (rc == VINF_SUCCESS)
714 {
715 *ppStream = &pStreamAt->Core;
716 *pCfgAcq = pStreamAt->Core.Cfg;
717 return VINF_SUCCESS;
718 }
719 if (rc == VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED)
720 {
721 /*
722 * Do async init right here and now.
723 */
724 rc = pDrvStack->pIHostAudio->pfnStreamInitAsync(pDrvStack->pIHostAudio, &pStreamAt->Backend,
725 false /*fDestroyed*/);
726 if (RT_SUCCESS(rc))
727 {
728 *ppStream = &pStreamAt->Core;
729 *pCfgAcq = pStreamAt->Core.Cfg;
730 return VINF_SUCCESS;
731 }
732
733 RTTestFailed(g_hTest, "pfnStreamInitAsync failed: %Rrc\n", rc);
734 }
735 else
736 {
737 RTTestFailed(g_hTest, "pfnStreamCreate returned unexpected info status: %Rrc", rc);
738 rc = VERR_IPE_UNEXPECTED_INFO_STATUS;
739 }
740 pDrvStack->pIHostAudio->pfnStreamDestroy(pDrvStack->pIHostAudio, &pStreamAt->Backend, true /*fImmediate*/);
741 }
742 else
743 RTTestFailed(g_hTest, "pfnStreamCreate failed: %Rrc\n", rc);
744 }
745 else
746 {
747 RTTestFailed(g_hTest, "Out of memory!\n");
748 rc = VERR_NO_MEMORY;
749 }
750 RTMemFree(pStreamAt);
751 }
752 else
753 {
754 RTTestFailed(g_hTest, "cbStream=%#x is too small, min %#zx!\n", BackendCfg.cbStream, sizeof(PDMAUDIOBACKENDSTREAM));
755 rc = VERR_OUT_OF_RANGE;
756 }
757 }
758 else
759 RTTestFailed(g_hTest, "pfnGetConfig failed: %Rrc\n", rc);
760 }
761 return rc;
762}
763
764
765/**
766 * Creates an output stream.
767 *
768 * @returns VBox status code.
769 * @param pDrvStack The audio driver stack to create it via.
770 * @param pProps The audio properties to use.
771 * @param cMsBufferSize The buffer size in milliseconds.
772 * @param cMsPreBuffer The pre-buffering amount in milliseconds.
773 * @param cMsSchedulingHint The scheduling hint in milliseconds.
774 * @param ppStream Where to return the stream pointer on success.
775 * @param pCfgAcq Where to return the actual (well, not
776 * necessarily when using DrvAudio, but probably
777 * the same) stream config on success (not used as
778 * input).
779 */
780int audioTestDriverStackStreamCreateOutput(PAUDIOTESTDRVSTACK pDrvStack, PCPDMAUDIOPCMPROPS pProps,
781 uint32_t cMsBufferSize, uint32_t cMsPreBuffer, uint32_t cMsSchedulingHint,
782 PPDMAUDIOSTREAM *ppStream, PPDMAUDIOSTREAMCFG pCfgAcq)
783{
784 /*
785 * Calculate the stream config.
786 */
787 PDMAUDIOSTREAMCFG CfgReq;
788 int rc = PDMAudioStrmCfgInitWithProps(&CfgReq, pProps);
789 AssertRC(rc);
790 CfgReq.enmDir = PDMAUDIODIR_OUT;
791 CfgReq.enmPath = PDMAUDIOPATH_OUT_FRONT;
792 CfgReq.Device.cMsSchedulingHint = cMsSchedulingHint == UINT32_MAX || cMsSchedulingHint == 0
793 ? 10 : cMsSchedulingHint;
794 if (pDrvStack->pIAudioConnector && (cMsBufferSize == UINT32_MAX || cMsBufferSize == 0))
795 CfgReq.Backend.cFramesBufferSize = 0; /* DrvAudio picks the default */
796 else
797 CfgReq.Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(pProps,
798 cMsBufferSize == UINT32_MAX || cMsBufferSize == 0
799 ? 300 : cMsBufferSize);
800 if (cMsPreBuffer == UINT32_MAX)
801 CfgReq.Backend.cFramesPreBuffering = pDrvStack->pIAudioConnector ? UINT32_MAX /*DrvAudo picks the default */
802 : CfgReq.Backend.cFramesBufferSize * 2 / 3;
803 else
804 CfgReq.Backend.cFramesPreBuffering = PDMAudioPropsMilliToFrames(pProps, cMsPreBuffer);
805 if ( CfgReq.Backend.cFramesPreBuffering >= CfgReq.Backend.cFramesBufferSize + 16
806 && !pDrvStack->pIAudioConnector /*DrvAudio deals with it*/ )
807 {
808 RTMsgWarning("Cannot pre-buffer %#x frames with only %#x frames of buffer!",
809 CfgReq.Backend.cFramesPreBuffering, CfgReq.Backend.cFramesBufferSize);
810 CfgReq.Backend.cFramesPreBuffering = CfgReq.Backend.cFramesBufferSize > 16
811 ? CfgReq.Backend.cFramesBufferSize - 16 : 0;
812 }
813
814 static uint32_t s_idxStream = 0;
815 uint32_t const idxStream = s_idxStream++;
816 RTStrPrintf(CfgReq.szName, sizeof(CfgReq.szName), "out-%u", idxStream);
817
818 /*
819 * Call common code to do the actual work.
820 */
821 return audioTestDriverStackStreamCreate(pDrvStack, &CfgReq, ppStream, pCfgAcq);
822}
823
824
825/**
826 * Creates an input stream.
827 *
828 * @returns VBox status code.
829 * @param pDrvStack The audio driver stack to create it via.
830 * @param pProps The audio properties to use.
831 * @param cMsBufferSize The buffer size in milliseconds.
832 * @param cMsPreBuffer The pre-buffering amount in milliseconds.
833 * @param cMsSchedulingHint The scheduling hint in milliseconds.
834 * @param ppStream Where to return the stream pointer on success.
835 * @param pCfgAcq Where to return the actual (well, not
836 * necessarily when using DrvAudio, but probably
837 * the same) stream config on success (not used as
838 * input).
839 */
840int audioTestDriverStackStreamCreateInput(PAUDIOTESTDRVSTACK pDrvStack, PCPDMAUDIOPCMPROPS pProps,
841 uint32_t cMsBufferSize, uint32_t cMsPreBuffer, uint32_t cMsSchedulingHint,
842 PPDMAUDIOSTREAM *ppStream, PPDMAUDIOSTREAMCFG pCfgAcq)
843{
844 /*
845 * Calculate the stream config.
846 */
847 PDMAUDIOSTREAMCFG CfgReq;
848 int rc = PDMAudioStrmCfgInitWithProps(&CfgReq, pProps);
849 AssertRC(rc);
850 CfgReq.enmDir = PDMAUDIODIR_IN;
851 CfgReq.enmPath = PDMAUDIOPATH_IN_LINE;
852 CfgReq.Device.cMsSchedulingHint = cMsSchedulingHint == UINT32_MAX || cMsSchedulingHint == 0
853 ? 10 : cMsSchedulingHint;
854 if (pDrvStack->pIAudioConnector && (cMsBufferSize == UINT32_MAX || cMsBufferSize == 0))
855 CfgReq.Backend.cFramesBufferSize = 0; /* DrvAudio picks the default */
856 else
857 CfgReq.Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(pProps,
858 cMsBufferSize == UINT32_MAX || cMsBufferSize == 0
859 ? 300 : cMsBufferSize);
860 if (cMsPreBuffer == UINT32_MAX)
861 CfgReq.Backend.cFramesPreBuffering = pDrvStack->pIAudioConnector ? UINT32_MAX /*DrvAudio picks the default */
862 : CfgReq.Backend.cFramesBufferSize / 2;
863 else
864 CfgReq.Backend.cFramesPreBuffering = PDMAudioPropsMilliToFrames(pProps, cMsPreBuffer);
865 if ( CfgReq.Backend.cFramesPreBuffering >= CfgReq.Backend.cFramesBufferSize + 16 /** @todo way to little */
866 && !pDrvStack->pIAudioConnector /*DrvAudio deals with it*/ )
867 {
868 RTMsgWarning("Cannot pre-buffer %#x frames with only %#x frames of buffer!",
869 CfgReq.Backend.cFramesPreBuffering, CfgReq.Backend.cFramesBufferSize);
870 CfgReq.Backend.cFramesPreBuffering = CfgReq.Backend.cFramesBufferSize > 16
871 ? CfgReq.Backend.cFramesBufferSize - 16 : 0;
872 }
873
874 static uint32_t s_idxStream = 0;
875 uint32_t const idxStream = s_idxStream++;
876 RTStrPrintf(CfgReq.szName, sizeof(CfgReq.szName), "in-%u", idxStream);
877
878 /*
879 * Call common code to do the actual work.
880 */
881 return audioTestDriverStackStreamCreate(pDrvStack, &CfgReq, ppStream, pCfgAcq);
882}
883
884
885/**
886 * Destroys a stream.
887 *
888 * @param pDrvStack Driver stack the stream to destroy is assigned to.
889 * @param pStream Stream to destroy. Pointer will be NULL (invalid) after successful return.
890 */
891void audioTestDriverStackStreamDestroy(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream)
892{
893 if (!pStream)
894 return;
895
896 if (pDrvStack->pIAudioConnector)
897 {
898 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Destroying stream '%s' (IAudioConnector) ...\n", pStream->Cfg.szName);
899 int rc = pDrvStack->pIAudioConnector->pfnStreamDestroy(pDrvStack->pIAudioConnector, pStream, true /*fImmediate*/);
900 if (RT_FAILURE(rc))
901 RTTestFailed(g_hTest, "pfnStreamDestroy failed: %Rrc", rc);
902 }
903 else
904 {
905 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Destroying stream '%s' (IHostAudio) ...\n", pStream->Cfg.szName);
906 PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
907 int rc = pDrvStack->pIHostAudio->pfnStreamDestroy(pDrvStack->pIHostAudio, &pStreamAt->Backend, true /*fImmediate*/);
908 if (RT_SUCCESS(rc))
909 {
910 pStreamAt->Core.uMagic = ~PDMAUDIOSTREAM_MAGIC;
911 pStreamAt->Backend.uMagic = ~PDMAUDIOBACKENDSTREAM_MAGIC;
912
913 RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Destroying stream '%s' done\n", pStream->Cfg.szName);
914
915 RTMemFree(pStreamAt);
916
917 pStreamAt = NULL;
918 pStream = NULL;
919 }
920 else
921 RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamDestroy failed: %Rrc", rc);
922 }
923}
924
925
926/**
927 * Enables a stream.
928 */
929int audioTestDriverStackStreamEnable(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream)
930{
931 int rc;
932 if (pDrvStack->pIAudioConnector)
933 {
934 rc = pDrvStack->pIAudioConnector->pfnStreamControl(pDrvStack->pIAudioConnector, pStream, PDMAUDIOSTREAMCMD_ENABLE);
935 if (RT_FAILURE(rc))
936 RTTestFailed(g_hTest, "pfnStreamControl/ENABLE failed: %Rrc", rc);
937 }
938 else
939 {
940 PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
941 rc = pDrvStack->pIHostAudio->pfnStreamEnable(pDrvStack->pIHostAudio, &pStreamAt->Backend);
942 if (RT_FAILURE(rc))
943 RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamEnable failed: %Rrc", rc);
944 }
945 return rc;
946}
947
948
949/**
950 * Disables a stream.
951 */
952int AudioTestDriverStackStreamDisable(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream)
953{
954 int rc;
955 if (pDrvStack->pIAudioConnector)
956 {
957 rc = pDrvStack->pIAudioConnector->pfnStreamControl(pDrvStack->pIAudioConnector, pStream, PDMAUDIOSTREAMCMD_DISABLE);
958 if (RT_FAILURE(rc))
959 RTTestFailed(g_hTest, "pfnStreamControl/DISABLE failed: %Rrc", rc);
960 }
961 else
962 {
963 PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
964 rc = pDrvStack->pIHostAudio->pfnStreamDisable(pDrvStack->pIHostAudio, &pStreamAt->Backend);
965 if (RT_FAILURE(rc))
966 RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamDisable failed: %Rrc", rc);
967 }
968 return rc;
969}
970
971
972/**
973 * Drains an output stream.
974 */
975int audioTestDriverStackStreamDrain(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream, bool fSync)
976{
977 int rc;
978 if (pDrvStack->pIAudioConnector)
979 {
980 /*
981 * Issue the drain request.
982 */
983 rc = pDrvStack->pIAudioConnector->pfnStreamControl(pDrvStack->pIAudioConnector, pStream, PDMAUDIOSTREAMCMD_DRAIN);
984 if (RT_SUCCESS(rc) && fSync)
985 {
986 /*
987 * This is a synchronous drain, so wait for the driver to change state to inactive.
988 */
989 PDMAUDIOSTREAMSTATE enmState;
990 while ( (enmState = pDrvStack->pIAudioConnector->pfnStreamGetState(pDrvStack->pIAudioConnector, pStream))
991 >= PDMAUDIOSTREAMSTATE_ENABLED)
992 {
993 RTThreadSleep(2);
994 rc = pDrvStack->pIAudioConnector->pfnStreamIterate(pDrvStack->pIAudioConnector, pStream);
995 if (RT_FAILURE(rc))
996 {
997 RTTestFailed(g_hTest, "pfnStreamIterate/DRAIN failed: %Rrc", rc);
998 break;
999 }
1000 }
1001 if (enmState != PDMAUDIOSTREAMSTATE_INACTIVE)
1002 {
1003 RTTestFailed(g_hTest, "Stream state not INACTIVE after draining: %s", PDMAudioStreamStateGetName(enmState));
1004 rc = VERR_AUDIO_STREAM_NOT_READY;
1005 }
1006 }
1007 else if (RT_FAILURE(rc))
1008 RTTestFailed(g_hTest, "pfnStreamControl/ENABLE failed: %Rrc", rc);
1009 }
1010 else
1011 {
1012 /*
1013 * Issue the drain request.
1014 */
1015 PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
1016 rc = pDrvStack->pIHostAudio->pfnStreamDrain(pDrvStack->pIHostAudio, &pStreamAt->Backend);
1017 if (RT_SUCCESS(rc) && fSync)
1018 {
1019 RTMSINTERVAL const msTimeout = RT_MS_5MIN; /* 5 minutes should be really enough for draining our stuff. */
1020 uint64_t const tsStart = RTTimeMilliTS();
1021
1022 /*
1023 * This is a synchronous drain, so wait for the driver to change state to inactive.
1024 */
1025 PDMHOSTAUDIOSTREAMSTATE enmHostState;
1026 while ( (enmHostState = pDrvStack->pIHostAudio->pfnStreamGetState(pDrvStack->pIHostAudio, &pStreamAt->Backend))
1027 == PDMHOSTAUDIOSTREAMSTATE_DRAINING)
1028 {
1029 RTThreadSleep(2);
1030 uint32_t cbWritten = UINT32_MAX;
1031 rc = pDrvStack->pIHostAudio->pfnStreamPlay(pDrvStack->pIHostAudio, &pStreamAt->Backend,
1032 NULL /*pvBuf*/, 0 /*cbBuf*/, &cbWritten);
1033 if (RT_FAILURE(rc))
1034 {
1035 RTTestFailed(g_hTest, "pfnStreamPlay/DRAIN failed: %Rrc", rc);
1036 break;
1037 }
1038 if (cbWritten != 0)
1039 {
1040 RTTestFailed(g_hTest, "pfnStreamPlay/DRAIN did not set cbWritten to zero: %#x", cbWritten);
1041 rc = VERR_MISSING;
1042 break;
1043 }
1044
1045 /* Fail-safe for audio stacks and/or implementations which mess up draining. */
1046 if (RTTimeMilliTS() - tsStart > msTimeout)
1047 {
1048 RTTestFailed(g_hTest, "Draining stream took too long (timeout is %RU32ms), giving up", msTimeout);
1049 break;
1050 }
1051 }
1052 if (enmHostState != PDMHOSTAUDIOSTREAMSTATE_OKAY)
1053 {
1054 RTTestFailed(g_hTest, "Stream state not OKAY after draining: %s", PDMHostAudioStreamStateGetName(enmHostState));
1055 rc = VERR_AUDIO_STREAM_NOT_READY;
1056 }
1057 }
1058 else if (RT_FAILURE(rc))
1059 RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamControl/ENABLE failed: %Rrc", rc);
1060 }
1061 return rc;
1062}
1063
1064
1065/**
1066 * Checks if the stream is okay.
1067 * @returns true if okay, false if not.
1068 */
1069bool audioTestDriverStackStreamIsOkay(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream)
1070{
1071 /*
1072 * Get the stream status and check if it means is okay or not.
1073 */
1074 bool fRc = false;
1075 if (pDrvStack->pIAudioConnector)
1076 {
1077 PDMAUDIOSTREAMSTATE enmState = pDrvStack->pIAudioConnector->pfnStreamGetState(pDrvStack->pIAudioConnector, pStream);
1078 switch (enmState)
1079 {
1080 case PDMAUDIOSTREAMSTATE_NOT_WORKING:
1081 case PDMAUDIOSTREAMSTATE_NEED_REINIT:
1082 break;
1083 case PDMAUDIOSTREAMSTATE_INACTIVE:
1084 case PDMAUDIOSTREAMSTATE_ENABLED:
1085 case PDMAUDIOSTREAMSTATE_ENABLED_READABLE:
1086 case PDMAUDIOSTREAMSTATE_ENABLED_WRITABLE:
1087 fRc = true;
1088 break;
1089 /* no default */
1090 case PDMAUDIOSTREAMSTATE_INVALID:
1091 case PDMAUDIOSTREAMSTATE_END:
1092 case PDMAUDIOSTREAMSTATE_32BIT_HACK:
1093 break;
1094 }
1095 }
1096 else
1097 {
1098 PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
1099 PDMHOSTAUDIOSTREAMSTATE enmHostState = pDrvStack->pIHostAudio->pfnStreamGetState(pDrvStack->pIHostAudio,
1100 &pStreamAt->Backend);
1101 switch (enmHostState)
1102 {
1103 case PDMHOSTAUDIOSTREAMSTATE_INITIALIZING:
1104 case PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING:
1105 break;
1106 case PDMHOSTAUDIOSTREAMSTATE_OKAY:
1107 case PDMHOSTAUDIOSTREAMSTATE_DRAINING:
1108 case PDMHOSTAUDIOSTREAMSTATE_INACTIVE:
1109 fRc = true;
1110 break;
1111 /* no default */
1112 case PDMHOSTAUDIOSTREAMSTATE_INVALID:
1113 case PDMHOSTAUDIOSTREAMSTATE_END:
1114 case PDMHOSTAUDIOSTREAMSTATE_32BIT_HACK:
1115 break;
1116 }
1117 }
1118 return fRc;
1119}
1120
1121
1122/**
1123 * Gets the number of bytes it's currently possible to write to the stream.
1124 */
1125uint32_t audioTestDriverStackStreamGetWritable(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream)
1126{
1127 uint32_t cbWritable;
1128 if (pDrvStack->pIAudioConnector)
1129 cbWritable = pDrvStack->pIAudioConnector->pfnStreamGetWritable(pDrvStack->pIAudioConnector, pStream);
1130 else
1131 {
1132 PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
1133 cbWritable = pDrvStack->pIHostAudio->pfnStreamGetWritable(pDrvStack->pIHostAudio, &pStreamAt->Backend);
1134 }
1135 return cbWritable;
1136}
1137
1138
1139/**
1140 * Tries to play the @a cbBuf bytes of samples in @a pvBuf.
1141 */
1142int audioTestDriverStackStreamPlay(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream,
1143 void const *pvBuf, uint32_t cbBuf, uint32_t *pcbPlayed)
1144{
1145 int rc;
1146 if (pDrvStack->pIAudioConnector)
1147 {
1148 rc = pDrvStack->pIAudioConnector->pfnStreamPlay(pDrvStack->pIAudioConnector, pStream, pvBuf, cbBuf, pcbPlayed);
1149 if (RT_FAILURE(rc))
1150 RTTestFailed(g_hTest, "pfnStreamPlay(,,,%#x,) failed: %Rrc", cbBuf, rc);
1151 }
1152 else
1153 {
1154 PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
1155 rc = pDrvStack->pIHostAudio->pfnStreamPlay(pDrvStack->pIHostAudio, &pStreamAt->Backend, pvBuf, cbBuf, pcbPlayed);
1156 if (RT_FAILURE(rc))
1157 RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamPlay(,,,%#x,) failed: %Rrc", cbBuf, rc);
1158 }
1159 return rc;
1160}
1161
1162
1163/**
1164 * Gets the number of bytes it's currently possible to write to the stream.
1165 */
1166uint32_t audioTestDriverStackStreamGetReadable(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream)
1167{
1168 uint32_t cbReadable;
1169 if (pDrvStack->pIAudioConnector)
1170 cbReadable = pDrvStack->pIAudioConnector->pfnStreamGetReadable(pDrvStack->pIAudioConnector, pStream);
1171 else
1172 {
1173 PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
1174 cbReadable = pDrvStack->pIHostAudio->pfnStreamGetReadable(pDrvStack->pIHostAudio, &pStreamAt->Backend);
1175 }
1176 return cbReadable;
1177}
1178
1179
1180/**
1181 * Tries to capture @a cbBuf bytes of samples in @a pvBuf.
1182 */
1183int audioTestDriverStackStreamCapture(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream,
1184 void *pvBuf, uint32_t cbBuf, uint32_t *pcbCaptured)
1185{
1186 int rc;
1187 if (pDrvStack->pIAudioConnector)
1188 {
1189 rc = pDrvStack->pIAudioConnector->pfnStreamCapture(pDrvStack->pIAudioConnector, pStream, pvBuf, cbBuf, pcbCaptured);
1190 if (RT_FAILURE(rc))
1191 RTTestFailed(g_hTest, "pfnStreamCapture(,,,%#x,) failed: %Rrc", cbBuf, rc);
1192 }
1193 else
1194 {
1195 PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
1196 rc = pDrvStack->pIHostAudio->pfnStreamCapture(pDrvStack->pIHostAudio, &pStreamAt->Backend, pvBuf, cbBuf, pcbCaptured);
1197 if (RT_FAILURE(rc))
1198 RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamCapture(,,,%#x,) failed: %Rrc", cbBuf, rc);
1199 }
1200 return rc;
1201}
1202
1203
1204/*********************************************************************************************************************************
1205* Mixed streams *
1206*********************************************************************************************************************************/
1207
1208/**
1209 * Initializing mixing for a stream.
1210 *
1211 * This can be used as a do-nothing wrapper for the stack.
1212 *
1213 * @returns VBox status code.
1214 * @param pMix The mixing state.
1215 * @param pStream The stream to mix to/from.
1216 * @param pProps The mixer properties. Pass NULL for no mixing, just
1217 * wrap the driver stack functionality.
1218 * @param cMsBuffer The buffer size.
1219 */
1220int AudioTestMixStreamInit(PAUDIOTESTDRVMIXSTREAM pMix, PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream,
1221 PCPDMAUDIOPCMPROPS pProps, uint32_t cMsBuffer)
1222{
1223 RT_ZERO(*pMix);
1224
1225 AssertReturn(pDrvStack, VERR_INVALID_PARAMETER);
1226 AssertReturn(pStream, VERR_INVALID_PARAMETER);
1227
1228 pMix->pDrvStack = pDrvStack;
1229 pMix->pStream = pStream;
1230 if (!pProps)
1231 {
1232 pMix->pProps = &pStream->Cfg.Props;
1233 return VINF_SUCCESS;
1234 }
1235
1236 /*
1237 * Okay, we're doing mixing so we need to set up the mixer buffer
1238 * and associated states.
1239 */
1240 pMix->fDoMixing = true;
1241 int rc = AudioMixBufInit(&pMix->MixBuf, "mixer", pProps, PDMAudioPropsMilliToFrames(pProps, cMsBuffer));
1242 if (RT_SUCCESS(rc))
1243 {
1244 pMix->pProps = &pMix->MixBuf.Props;
1245
1246 if (pStream->Cfg.enmDir == PDMAUDIODIR_IN)
1247 {
1248 rc = AudioMixBufInitPeekState(&pMix->MixBuf, &pMix->PeekState, &pMix->MixBuf.Props);
1249 if (RT_SUCCESS(rc))
1250 {
1251 rc = AudioMixBufInitWriteState(&pMix->MixBuf, &pMix->WriteState, &pStream->Cfg.Props);
1252 if (RT_SUCCESS(rc))
1253 return rc;
1254 }
1255 }
1256 else if (pStream->Cfg.enmDir == PDMAUDIODIR_OUT)
1257 {
1258 rc = AudioMixBufInitWriteState(&pMix->MixBuf, &pMix->WriteState, &pMix->MixBuf.Props);
1259 if (RT_SUCCESS(rc))
1260 {
1261 rc = AudioMixBufInitPeekState(&pMix->MixBuf, &pMix->PeekState, &pStream->Cfg.Props);
1262 if (RT_SUCCESS(rc))
1263 return rc;
1264 }
1265 }
1266 else
1267 {
1268 RTTestFailed(g_hTest, "Bogus stream direction!");
1269 rc = VERR_INVALID_STATE;
1270 }
1271 }
1272 else
1273 RTTestFailed(g_hTest, "AudioMixBufInit failed: %Rrc", rc);
1274 RT_ZERO(*pMix);
1275 return rc;
1276}
1277
1278
1279/**
1280 * Terminate mixing (leaves the stream untouched).
1281 *
1282 * @param pMix The mixing state.
1283 */
1284void AudioTestMixStreamTerm(PAUDIOTESTDRVMIXSTREAM pMix)
1285{
1286 if (pMix->fDoMixing)
1287 {
1288 AudioMixBufTerm(&pMix->MixBuf);
1289 pMix->pStream = NULL;
1290 }
1291 RT_ZERO(*pMix);
1292}
1293
1294
1295/**
1296 * Worker that transports data between the mixer buffer and the drivers.
1297 *
1298 * @returns VBox status code.
1299 * @param pMix The mixer stream setup to do transfers for.
1300 */
1301static int audioTestMixStreamTransfer(PAUDIOTESTDRVMIXSTREAM pMix)
1302{
1303 uint8_t abBuf[16384];
1304 if (pMix->pStream->Cfg.enmDir == PDMAUDIODIR_IN)
1305 {
1306 /*
1307 * Try fill up the mixer buffer as much as possible.
1308 *
1309 * Slight fun part is that we have to calculate conversion
1310 * ratio and be rather pessimistic about it.
1311 */
1312 uint32_t const cbBuf = PDMAudioPropsFloorBytesToFrame(&pMix->pStream->Cfg.Props, sizeof(abBuf));
1313 for (;;)
1314 {
1315 /*
1316 * Figure out how much we can move in this iteration.
1317 */
1318 uint32_t cDstFrames = AudioMixBufFree(&pMix->MixBuf);
1319 if (!cDstFrames)
1320 break;
1321
1322 uint32_t cbReadable = audioTestDriverStackStreamGetReadable(pMix->pDrvStack, pMix->pStream);
1323 if (!cbReadable)
1324 break;
1325
1326 uint32_t cbToRead;
1327 if (PDMAudioPropsHz(&pMix->pStream->Cfg.Props) == PDMAudioPropsHz(&pMix->MixBuf.Props))
1328 cbToRead = PDMAudioPropsFramesToBytes(&pMix->pStream->Cfg.Props, cDstFrames);
1329 else
1330 cbToRead = PDMAudioPropsFramesToBytes(&pMix->pStream->Cfg.Props,
1331 (uint64_t)cDstFrames * PDMAudioPropsHz(&pMix->pStream->Cfg.Props)
1332 / PDMAudioPropsHz(&pMix->MixBuf.Props));
1333 cbToRead = RT_MIN(cbToRead, RT_MIN(cbReadable, cbBuf));
1334 if (!cbToRead)
1335 break;
1336
1337 /*
1338 * Get the data.
1339 */
1340 uint32_t cbCaptured = 0;
1341 int rc = audioTestDriverStackStreamCapture(pMix->pDrvStack, pMix->pStream, abBuf, cbToRead, &cbCaptured);
1342 if (RT_FAILURE(rc))
1343 return rc;
1344 Assert(cbCaptured == cbToRead);
1345 AssertBreak(cbCaptured > 0);
1346
1347 /*
1348 * Feed it to the mixer.
1349 */
1350 uint32_t cDstFramesWritten = 0;
1351 if ((abBuf[0] >> 4) & 1) /* some cheap random */
1352 AudioMixBufWrite(&pMix->MixBuf, &pMix->WriteState, abBuf, cbCaptured,
1353 0 /*offDstFrame*/, cDstFrames, &cDstFramesWritten);
1354 else
1355 {
1356 AudioMixBufSilence(&pMix->MixBuf, &pMix->WriteState, 0 /*offFrame*/, cDstFrames);
1357 AudioMixBufBlend(&pMix->MixBuf, &pMix->WriteState, abBuf, cbCaptured,
1358 0 /*offDstFrame*/, cDstFrames, &cDstFramesWritten);
1359 }
1360 AudioMixBufCommit(&pMix->MixBuf, cDstFramesWritten);
1361 }
1362 }
1363 else
1364 {
1365 /*
1366 * The goal here is to empty the mixer buffer by transfering all
1367 * the data to the drivers.
1368 */
1369 uint32_t const cbBuf = PDMAudioPropsFloorBytesToFrame(&pMix->MixBuf.Props, sizeof(abBuf));
1370 for (;;)
1371 {
1372 uint32_t cFrames = AudioMixBufUsed(&pMix->MixBuf);
1373 if (!cFrames)
1374 break;
1375
1376 uint32_t cbWritable = audioTestDriverStackStreamGetWritable(pMix->pDrvStack, pMix->pStream);
1377 if (!cbWritable)
1378 break;
1379
1380 uint32_t cSrcFramesPeeked;
1381 uint32_t cbDstPeeked;
1382 AudioMixBufPeek(&pMix->MixBuf, 0 /*offSrcFrame*/, cFrames, &cSrcFramesPeeked,
1383 &pMix->PeekState, abBuf, RT_MIN(cbBuf, cbWritable), &cbDstPeeked);
1384 AudioMixBufAdvance(&pMix->MixBuf, cSrcFramesPeeked);
1385
1386 if (!cbDstPeeked)
1387 break;
1388
1389 uint32_t offBuf = 0;
1390 while (offBuf < cbDstPeeked)
1391 {
1392 uint32_t cbPlayed = 0;
1393 int rc = audioTestDriverStackStreamPlay(pMix->pDrvStack, pMix->pStream,
1394 &abBuf[offBuf], cbDstPeeked - offBuf, &cbPlayed);
1395 if (RT_FAILURE(rc))
1396 return rc;
1397 if (!cbPlayed)
1398 RTThreadSleep(1);
1399 offBuf += cbPlayed;
1400 }
1401 }
1402 }
1403 return VINF_SUCCESS;
1404}
1405
1406
1407/**
1408 * Same as audioTestDriverStackStreamEnable.
1409 */
1410int AudioTestMixStreamEnable(PAUDIOTESTDRVMIXSTREAM pMix)
1411{
1412 return audioTestDriverStackStreamEnable(pMix->pDrvStack, pMix->pStream);
1413}
1414
1415
1416/**
1417 * Same as audioTestDriverStackStreamDrain.
1418 */
1419int AudioTestMixStreamDrain(PAUDIOTESTDRVMIXSTREAM pMix, bool fSync)
1420{
1421 /*
1422 * If we're mixing, we must first make sure the buffer is empty.
1423 */
1424 if (pMix->fDoMixing)
1425 {
1426 audioTestMixStreamTransfer(pMix);
1427 while (AudioMixBufUsed(&pMix->MixBuf) > 0)
1428 {
1429 RTThreadSleep(1);
1430 audioTestMixStreamTransfer(pMix);
1431 }
1432 }
1433
1434 /*
1435 * Then we do the regular work.
1436 */
1437 return audioTestDriverStackStreamDrain(pMix->pDrvStack, pMix->pStream, fSync);
1438}
1439
1440/**
1441 * Same as audioTestDriverStackStreamDisable.
1442 */
1443int AudioTestMixStreamDisable(PAUDIOTESTDRVMIXSTREAM pMix)
1444{
1445 return AudioTestDriverStackStreamDisable(pMix->pDrvStack, pMix->pStream);
1446}
1447
1448
1449/**
1450 * Same as audioTestDriverStackStreamIsOkay.
1451 */
1452bool AudioTestMixStreamIsOkay(PAUDIOTESTDRVMIXSTREAM pMix)
1453{
1454 return audioTestDriverStackStreamIsOkay(pMix->pDrvStack, pMix->pStream);
1455}
1456
1457
1458/**
1459 * Same as audioTestDriverStackStreamGetWritable
1460 */
1461uint32_t AudioTestMixStreamGetWritable(PAUDIOTESTDRVMIXSTREAM pMix)
1462{
1463 if (!pMix->fDoMixing)
1464 return audioTestDriverStackStreamGetWritable(pMix->pDrvStack, pMix->pStream);
1465 uint32_t cbRet = AudioMixBufFreeBytes(&pMix->MixBuf);
1466 if (!cbRet)
1467 {
1468 audioTestMixStreamTransfer(pMix);
1469 cbRet = AudioMixBufFreeBytes(&pMix->MixBuf);
1470 }
1471 return cbRet;
1472}
1473
1474
1475
1476
1477/**
1478 * Same as audioTestDriverStackStreamPlay.
1479 */
1480int AudioTestMixStreamPlay(PAUDIOTESTDRVMIXSTREAM pMix, void const *pvBuf, uint32_t cbBuf, uint32_t *pcbPlayed)
1481{
1482 if (!pMix->fDoMixing)
1483 return audioTestDriverStackStreamPlay(pMix->pDrvStack, pMix->pStream, pvBuf, cbBuf, pcbPlayed);
1484
1485 *pcbPlayed = 0;
1486
1487 int rc = audioTestMixStreamTransfer(pMix);
1488 if (RT_FAILURE(rc))
1489 return rc;
1490
1491 uint32_t const cbFrame = PDMAudioPropsFrameSize(&pMix->MixBuf.Props);
1492 while (cbBuf >= cbFrame)
1493 {
1494 uint32_t const cFrames = AudioMixBufFree(&pMix->MixBuf);
1495 if (!cFrames)
1496 break;
1497 uint32_t cbToWrite = PDMAudioPropsFramesToBytes(&pMix->MixBuf.Props, cFrames);
1498 cbToWrite = RT_MIN(cbToWrite, cbBuf);
1499 cbToWrite = PDMAudioPropsFloorBytesToFrame(&pMix->MixBuf.Props, cbToWrite);
1500
1501 uint32_t cFramesWritten = 0;
1502 AudioMixBufWrite(&pMix->MixBuf, &pMix->WriteState, pvBuf, cbToWrite, 0 /*offDstFrame*/, cFrames, &cFramesWritten);
1503 Assert(cFramesWritten == PDMAudioPropsBytesToFrames(&pMix->MixBuf.Props, cbToWrite));
1504 AudioMixBufCommit(&pMix->MixBuf, cFramesWritten);
1505
1506 *pcbPlayed += cbToWrite;
1507 cbBuf -= cbToWrite;
1508 pvBuf = (uint8_t const *)pvBuf + cbToWrite;
1509
1510 rc = audioTestMixStreamTransfer(pMix);
1511 if (RT_FAILURE(rc))
1512 return *pcbPlayed ? VINF_SUCCESS : rc;
1513 }
1514
1515 return VINF_SUCCESS;
1516}
1517
1518
1519/**
1520 * Same as audioTestDriverStackStreamGetReadable
1521 */
1522uint32_t AudioTestMixStreamGetReadable(PAUDIOTESTDRVMIXSTREAM pMix)
1523{
1524 if (!pMix->fDoMixing)
1525 return audioTestDriverStackStreamGetReadable(pMix->pDrvStack, pMix->pStream);
1526
1527 audioTestMixStreamTransfer(pMix);
1528 uint32_t cbRet = AudioMixBufUsedBytes(&pMix->MixBuf);
1529 return cbRet;
1530}
1531
1532
1533
1534
1535/**
1536 * Same as audioTestDriverStackStreamCapture.
1537 */
1538int AudioTestMixStreamCapture(PAUDIOTESTDRVMIXSTREAM pMix, void *pvBuf, uint32_t cbBuf, uint32_t *pcbCaptured)
1539{
1540 if (!pMix->fDoMixing)
1541 return audioTestDriverStackStreamCapture(pMix->pDrvStack, pMix->pStream, pvBuf, cbBuf, pcbCaptured);
1542
1543 *pcbCaptured = 0;
1544
1545 int rc = audioTestMixStreamTransfer(pMix);
1546 if (RT_FAILURE(rc))
1547 return rc;
1548
1549 uint32_t const cbFrame = PDMAudioPropsFrameSize(&pMix->MixBuf.Props);
1550 while (cbBuf >= cbFrame)
1551 {
1552 uint32_t const cFrames = AudioMixBufUsed(&pMix->MixBuf);
1553 if (!cFrames)
1554 break;
1555 uint32_t cbToRead = PDMAudioPropsFramesToBytes(&pMix->MixBuf.Props, cFrames);
1556 cbToRead = RT_MIN(cbToRead, cbBuf);
1557 cbToRead = PDMAudioPropsFloorBytesToFrame(&pMix->MixBuf.Props, cbToRead);
1558
1559 uint32_t cFramesPeeked = 0;
1560 uint32_t cbPeeked = 0;
1561 AudioMixBufPeek(&pMix->MixBuf, 0 /*offSrcFrame*/, cFrames, &cFramesPeeked, &pMix->PeekState, pvBuf, cbToRead, &cbPeeked);
1562 Assert(cFramesPeeked == PDMAudioPropsBytesToFrames(&pMix->MixBuf.Props, cbPeeked));
1563 AudioMixBufAdvance(&pMix->MixBuf, cFramesPeeked);
1564
1565 *pcbCaptured += cbToRead;
1566 cbBuf -= cbToRead;
1567 pvBuf = (uint8_t *)pvBuf + cbToRead;
1568
1569 rc = audioTestMixStreamTransfer(pMix);
1570 if (RT_FAILURE(rc))
1571 return *pcbCaptured ? VINF_SUCCESS : rc;
1572 }
1573
1574 return VINF_SUCCESS;
1575}
1576
1577/**
1578 * Sets the volume of a mixing stream.
1579 *
1580 * @param pMix Mixing stream to set volume for.
1581 * @param uVolumePercent Volume to set (in percent, 0-100).
1582 */
1583void AudioTestMixStreamSetVolume(PAUDIOTESTDRVMIXSTREAM pMix, uint8_t uVolumePercent)
1584{
1585 AssertReturnVoid(pMix->fDoMixing);
1586
1587 uint8_t const uVol = (PDMAUDIO_VOLUME_MAX / 100) * uVolumePercent;
1588
1589 PDMAUDIOVOLUME Vol;
1590 RT_ZERO(Vol);
1591 for (size_t i = 0; i < RT_ELEMENTS(Vol.auChannels); i++)
1592 Vol.auChannels[i] = uVol;
1593 AudioMixBufSetVolume(&pMix->MixBuf, &Vol);
1594}
1595
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

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