VirtualBox

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

Last change on this file since 108870 was 107909, checked in by vboxsync, 3 months ago

Audio/VKAT: Added optional parameter '--drvhost-cache-enabled <true|false>' to disable caching code for host backends which support this. Defaults to enabled (as before). bugref:10844

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

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