VirtualBox

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

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

Audio/VKAT: Implemented "--probe-backends" commands to auto-probe all (available) backends. Needed for Linux hosts/guests which might or might not have certain backends installed/enabled; also nice for regression testing of backends which aren't available on certain platforms. bugref:10008

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

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