VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/DrvAudio.cpp@ 76702

Last change on this file since 76702 was 76702, checked in by vboxsync, 6 years ago

Audio/DrvAudio: Allow custom buffering sizes being as big as the pre-buffering sizes.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 126.3 KB
Line 
1/* $Id: DrvAudio.cpp 76702 2019-01-08 11:46:08Z vboxsync $ */
2/** @file
3 * Intermediate audio driver header.
4 *
5 * @remarks Intermediate audio driver for connecting the audio device emulation
6 * with the host backend.
7 */
8
9/*
10 * Copyright (C) 2006-2019 Oracle Corporation
11 *
12 * This file is part of VirtualBox Open Source Edition (OSE), as
13 * available from http://www.virtualbox.org. This file is free software;
14 * you can redistribute it and/or modify it under the terms of the GNU
15 * General Public License (GPL) as published by the Free Software
16 * Foundation, in version 2 as it comes in the "COPYING" file of the
17 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
18 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
19 */
20
21#define LOG_GROUP LOG_GROUP_DRV_AUDIO
22#include <VBox/log.h>
23#include <VBox/vmm/pdm.h>
24#include <VBox/err.h>
25#include <VBox/vmm/mm.h>
26#include <VBox/vmm/pdmaudioifs.h>
27
28#include <iprt/alloc.h>
29#include <iprt/asm-math.h>
30#include <iprt/assert.h>
31#include <iprt/circbuf.h>
32#include <iprt/string.h>
33#include <iprt/uuid.h>
34
35#include "VBoxDD.h"
36
37#include <ctype.h>
38#include <stdlib.h>
39
40#include "DrvAudio.h"
41#include "AudioMixBuffer.h"
42
43#ifdef VBOX_WITH_AUDIO_ENUM
44static int drvAudioDevicesEnumerateInternal(PDRVAUDIO pThis, bool fLog, PPDMAUDIODEVICEENUM pDevEnum);
45#endif
46
47static DECLCALLBACK(int) drvAudioStreamDestroy(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream);
48static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd);
49static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd);
50static int drvAudioStreamCreateInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq);
51static int drvAudioStreamDestroyInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream);
52static void drvAudioStreamFree(PPDMAUDIOSTREAM pStream);
53static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream);
54static int drvAudioStreamInitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgHost, PPDMAUDIOSTREAMCFG pCfgGuest);
55static int drvAudioStreamIterateInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream);
56static int drvAudioStreamReInitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream);
57static void drvAudioStreamDropInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream);
58static void drvAudioStreamResetInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream);
59
60#ifndef VBOX_AUDIO_TESTCASE
61
62# if 0 /* unused */
63
64static PDMAUDIOFMT drvAudioGetConfFormat(PCFGMNODE pCfgHandle, const char *pszKey,
65 PDMAUDIOFMT enmDefault, bool *pfDefault)
66{
67 if ( pCfgHandle == NULL
68 || pszKey == NULL)
69 {
70 *pfDefault = true;
71 return enmDefault;
72 }
73
74 char *pszValue = NULL;
75 int rc = CFGMR3QueryStringAlloc(pCfgHandle, pszKey, &pszValue);
76 if (RT_FAILURE(rc))
77 {
78 *pfDefault = true;
79 return enmDefault;
80 }
81
82 PDMAUDIOFMT fmt = DrvAudioHlpStrToAudFmt(pszValue);
83 if (fmt == PDMAUDIOFMT_INVALID)
84 {
85 *pfDefault = true;
86 return enmDefault;
87 }
88
89 *pfDefault = false;
90 return fmt;
91}
92
93static int drvAudioGetConfInt(PCFGMNODE pCfgHandle, const char *pszKey,
94 int iDefault, bool *pfDefault)
95{
96
97 if ( pCfgHandle == NULL
98 || pszKey == NULL)
99 {
100 *pfDefault = true;
101 return iDefault;
102 }
103
104 uint64_t u64Data = 0;
105 int rc = CFGMR3QueryInteger(pCfgHandle, pszKey, &u64Data);
106 if (RT_FAILURE(rc))
107 {
108 *pfDefault = true;
109 return iDefault;
110
111 }
112
113 *pfDefault = false;
114 return u64Data;
115}
116
117static const char *drvAudioGetConfStr(PCFGMNODE pCfgHandle, const char *pszKey,
118 const char *pszDefault, bool *pfDefault)
119{
120 if ( pCfgHandle == NULL
121 || pszKey == NULL)
122 {
123 *pfDefault = true;
124 return pszDefault;
125 }
126
127 char *pszValue = NULL;
128 int rc = CFGMR3QueryStringAlloc(pCfgHandle, pszKey, &pszValue);
129 if (RT_FAILURE(rc))
130 {
131 *pfDefault = true;
132 return pszDefault;
133 }
134
135 *pfDefault = false;
136 return pszValue;
137}
138
139# endif /* unused */
140
141#ifdef LOG_ENABLED
142/**
143 * Converts an audio stream status to a string.
144 *
145 * @returns Stringified stream status flags. Must be free'd with RTStrFree().
146 * "NONE" if no flags set.
147 * @param fStatus Stream status flags to convert.
148 */
149static char *dbgAudioStreamStatusToStr(PDMAUDIOSTREAMSTS fStatus)
150{
151#define APPEND_FLAG_TO_STR(_aFlag) \
152 if (fStatus & PDMAUDIOSTREAMSTS_FLAG_##_aFlag) \
153 { \
154 if (pszFlags) \
155 { \
156 rc2 = RTStrAAppend(&pszFlags, " "); \
157 if (RT_FAILURE(rc2)) \
158 break; \
159 } \
160 \
161 rc2 = RTStrAAppend(&pszFlags, #_aFlag); \
162 if (RT_FAILURE(rc2)) \
163 break; \
164 } \
165
166 char *pszFlags = NULL;
167 int rc2 = VINF_SUCCESS;
168
169 do
170 {
171 APPEND_FLAG_TO_STR(INITIALIZED );
172 APPEND_FLAG_TO_STR(ENABLED );
173 APPEND_FLAG_TO_STR(PAUSED );
174 APPEND_FLAG_TO_STR(PENDING_DISABLE);
175 APPEND_FLAG_TO_STR(PENDING_REINIT );
176 } while (0);
177
178 if (!pszFlags)
179 rc2 = RTStrAAppend(&pszFlags, "NONE");
180
181 if ( RT_FAILURE(rc2)
182 && pszFlags)
183 {
184 RTStrFree(pszFlags);
185 pszFlags = NULL;
186 }
187
188#undef APPEND_FLAG_TO_STR
189
190 return pszFlags;
191}
192#endif /* defined(VBOX_STRICT) || defined(LOG_ENABLED) */
193
194# if 0 /* unused */
195static int drvAudioProcessOptions(PCFGMNODE pCfgHandle, const char *pszPrefix, audio_option *paOpts, size_t cOpts)
196{
197 AssertPtrReturn(pCfgHandle, VERR_INVALID_POINTER);
198 AssertPtrReturn(pszPrefix, VERR_INVALID_POINTER);
199 /* oaOpts and cOpts are optional. */
200
201 PCFGMNODE pCfgChildHandle = NULL;
202 PCFGMNODE pCfgChildChildHandle = NULL;
203
204 /* If pCfgHandle is NULL, let NULL be passed to get int and get string functions..
205 * The getter function will return default values.
206 */
207 if (pCfgHandle != NULL)
208 {
209 /* If its audio general setting, need to traverse to one child node.
210 * /Devices/ichac97/0/LUN#0/Config/Audio
211 */
212 if(!strncmp(pszPrefix, "AUDIO", 5)) /** @todo Use a \#define */
213 {
214 pCfgChildHandle = CFGMR3GetFirstChild(pCfgHandle);
215 if(pCfgChildHandle)
216 pCfgHandle = pCfgChildHandle;
217 }
218 else
219 {
220 /* If its driver specific configuration , then need to traverse two level deep child
221 * child nodes. for eg. in case of DirectSoundConfiguration item
222 * /Devices/ichac97/0/LUN#0/Config/Audio/DirectSoundConfig
223 */
224 pCfgChildHandle = CFGMR3GetFirstChild(pCfgHandle);
225 if (pCfgChildHandle)
226 {
227 pCfgChildChildHandle = CFGMR3GetFirstChild(pCfgChildHandle);
228 if (pCfgChildChildHandle)
229 pCfgHandle = pCfgChildChildHandle;
230 }
231 }
232 }
233
234 for (size_t i = 0; i < cOpts; i++)
235 {
236 audio_option *pOpt = &paOpts[i];
237 if (!pOpt->valp)
238 {
239 LogFlowFunc(("Option value pointer for `%s' is not set\n", pOpt->name));
240 continue;
241 }
242
243 bool fUseDefault;
244
245 switch (pOpt->tag)
246 {
247 case AUD_OPT_BOOL:
248 case AUD_OPT_INT:
249 {
250 int *intp = (int *)pOpt->valp;
251 *intp = drvAudioGetConfInt(pCfgHandle, pOpt->name, *intp, &fUseDefault);
252
253 break;
254 }
255
256 case AUD_OPT_FMT:
257 {
258 PDMAUDIOFMT *fmtp = (PDMAUDIOFMT *)pOpt->valp;
259 *fmtp = drvAudioGetConfFormat(pCfgHandle, pOpt->name, *fmtp, &fUseDefault);
260
261 break;
262 }
263
264 case AUD_OPT_STR:
265 {
266 const char **strp = (const char **)pOpt->valp;
267 *strp = drvAudioGetConfStr(pCfgHandle, pOpt->name, *strp, &fUseDefault);
268
269 break;
270 }
271
272 default:
273 LogFlowFunc(("Bad value tag for option `%s' - %d\n", pOpt->name, pOpt->tag));
274 fUseDefault = false;
275 break;
276 }
277
278 if (!pOpt->overridenp)
279 pOpt->overridenp = &pOpt->overriden;
280
281 *pOpt->overridenp = !fUseDefault;
282 }
283
284 return VINF_SUCCESS;
285}
286# endif /* unused */
287#endif /* !VBOX_AUDIO_TESTCASE */
288
289/**
290 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamControl}
291 */
292static DECLCALLBACK(int) drvAudioStreamControl(PPDMIAUDIOCONNECTOR pInterface,
293 PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
294{
295 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
296
297 if (!pStream)
298 return VINF_SUCCESS;
299
300 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
301
302 int rc = RTCritSectEnter(&pThis->CritSect);
303 if (RT_FAILURE(rc))
304 return rc;
305
306 LogFlowFunc(("[%s] enmStreamCmd=%s\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd)));
307
308 rc = drvAudioStreamControlInternal(pThis, pStream, enmStreamCmd);
309
310 int rc2 = RTCritSectLeave(&pThis->CritSect);
311 if (RT_SUCCESS(rc))
312 rc = rc2;
313
314 return rc;
315}
316
317/**
318 * Controls an audio stream.
319 *
320 * @returns IPRT status code.
321 * @param pThis Pointer to driver instance.
322 * @param pStream Stream to control.
323 * @param enmStreamCmd Control command.
324 */
325static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
326{
327 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
328 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
329
330 LogFunc(("[%s] enmStreamCmd=%s\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd)));
331
332#ifdef LOG_ENABLED
333 char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus);
334 LogFlowFunc(("fStatus=%s\n", pszStreamSts));
335 RTStrFree(pszStreamSts);
336#endif /* LOG_ENABLED */
337
338 int rc = VINF_SUCCESS;
339
340 switch (enmStreamCmd)
341 {
342 case PDMAUDIOSTREAMCMD_ENABLE:
343 {
344 if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED))
345 {
346 /* Is a pending disable outstanding? Then disable first. */
347 if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE)
348 rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
349
350 if (RT_SUCCESS(rc))
351 rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_ENABLE);
352
353 if (RT_SUCCESS(rc))
354 pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_ENABLED;
355 }
356 break;
357 }
358
359 case PDMAUDIOSTREAMCMD_DISABLE:
360 {
361 if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED)
362 {
363 /*
364 * For playback (output) streams first mark the host stream as pending disable,
365 * so that the rest of the remaining audio data will be played first before
366 * closing the stream.
367 */
368 if (pStream->enmDir == PDMAUDIODIR_OUT)
369 {
370 LogFunc(("[%s] Pending disable/pause\n", pStream->szName));
371 pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE;
372 }
373
374 /* Can we close the host stream as well (not in pending disable mode)? */
375 if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE))
376 {
377 rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
378 if (RT_SUCCESS(rc))
379 drvAudioStreamResetInternal(pThis, pStream);
380 }
381 }
382 break;
383 }
384
385 case PDMAUDIOSTREAMCMD_PAUSE:
386 {
387 if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED))
388 {
389 rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_PAUSE);
390 if (RT_SUCCESS(rc))
391 pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_PAUSED;
392 }
393 break;
394 }
395
396 case PDMAUDIOSTREAMCMD_RESUME:
397 {
398 if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED)
399 {
400 rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_RESUME);
401 if (RT_SUCCESS(rc))
402 pStream->fStatus &= ~PDMAUDIOSTREAMSTS_FLAG_PAUSED;
403 }
404 break;
405 }
406
407 case PDMAUDIOSTREAMCMD_DROP:
408 {
409 rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DROP);
410 if (RT_SUCCESS(rc))
411 {
412 drvAudioStreamDropInternal(pThis, pStream);
413 }
414 break;
415 }
416
417 default:
418 rc = VERR_NOT_IMPLEMENTED;
419 break;
420 }
421
422 if (RT_FAILURE(rc))
423 LogFunc(("[%s] Failed with %Rrc\n", pStream->szName, rc));
424
425 return rc;
426}
427
428/**
429 * Controls a stream's backend.
430 * If the stream has no backend available, VERR_NOT_FOUND is returned.
431 *
432 * @returns IPRT status code.
433 * @param pThis Pointer to driver instance.
434 * @param pStream Stream to control.
435 * @param enmStreamCmd Control command.
436 */
437static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
438{
439 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
440 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
441
442#ifdef LOG_ENABLED
443 char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus);
444 LogFlowFunc(("[%s] enmStreamCmd=%s, fStatus=%s\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd), pszStreamSts));
445 RTStrFree(pszStreamSts);
446#endif /* LOG_ENABLED */
447
448 if (!pThis->pHostDrvAudio) /* If not lower driver is configured, bail out. */
449 return VINF_SUCCESS;
450
451 LogRel2(("Audio: %s stream '%s'\n", DrvAudioHlpStreamCmdToStr(enmStreamCmd), pStream->szName));
452
453 int rc = VINF_SUCCESS;
454
455 switch (enmStreamCmd)
456 {
457 case PDMAUDIOSTREAMCMD_ENABLE:
458 {
459 if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED))
460 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_ENABLE);
461 break;
462 }
463
464 case PDMAUDIOSTREAMCMD_DISABLE:
465 {
466 if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED)
467 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_DISABLE);
468 break;
469 }
470
471 case PDMAUDIOSTREAMCMD_PAUSE:
472 {
473 /* Only pause if the stream is enabled. */
474 if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED))
475 break;
476
477 if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED))
478 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_PAUSE);
479 break;
480 }
481
482 case PDMAUDIOSTREAMCMD_RESUME:
483 {
484 /* Only need to resume if the stream is enabled. */
485 if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED))
486 break;
487
488 if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED)
489 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_RESUME);
490 break;
491 }
492
493 case PDMAUDIOSTREAMCMD_DRAIN:
494 {
495 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_DRAIN);
496 break;
497 }
498
499 case PDMAUDIOSTREAMCMD_DROP:
500 {
501 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_DROP);
502 break;
503 }
504
505 default:
506 {
507 AssertMsgFailed(("Command %RU32 not implemented\n", enmStreamCmd));
508 rc = VERR_NOT_IMPLEMENTED;
509 break;
510 }
511 }
512
513 if (RT_FAILURE(rc))
514 {
515 if ( rc != VERR_NOT_IMPLEMENTED
516 && rc != VERR_NOT_SUPPORTED)
517 LogRel(("Audio: %s stream '%s' failed with %Rrc\n", DrvAudioHlpStreamCmdToStr(enmStreamCmd), pStream->szName, rc));
518
519 LogFunc(("[%s] %s failed with %Rrc\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd), rc));
520 }
521
522 return rc;
523}
524
525/**
526 * Initializes an audio stream with a given host and guest stream configuration.
527 *
528 * @returns IPRT status code.
529 * @param pThis Pointer to driver instance.
530 * @param pStream Stream to initialize.
531 * @param pCfgHost Stream configuration to use for the host side (backend).
532 * @param pCfgGuest Stream configuration to use for the guest side.
533 */
534static int drvAudioStreamInitInternal(PDRVAUDIO pThis,
535 PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgHost, PPDMAUDIOSTREAMCFG pCfgGuest)
536{
537 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
538 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
539 AssertPtrReturn(pCfgHost, VERR_INVALID_POINTER);
540 AssertPtrReturn(pCfgGuest, VERR_INVALID_POINTER);
541
542 /*
543 * Init host stream.
544 */
545
546 /* Set the host's default audio data layout. */
547 pCfgHost->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;
548
549#ifdef DEBUG
550 LogFunc(("[%s] Requested host format:\n", pStream->szName));
551 DrvAudioHlpStreamCfgPrint(pCfgHost);
552#endif
553
554 LogRel2(("Audio: Creating stream '%s'\n", pStream->szName));
555 LogRel2(("Audio: Guest %s format for '%s': %RU32Hz, %RU8%s, %RU8 %s\n",
556 pCfgGuest->enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStream->szName,
557 pCfgGuest->Props.uHz, pCfgGuest->Props.cBytes * 8, pCfgGuest->Props.fSigned ? "S" : "U",
558 pCfgGuest->Props.cChannels, pCfgGuest->Props.cChannels == 1 ? "Channel" : "Channels"));
559 LogRel2(("Audio: Requested host %s format for '%s': %RU32Hz, %RU8%s, %RU8 %s\n",
560 pCfgHost->enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStream->szName,
561 pCfgHost->Props.uHz, pCfgHost->Props.cBytes * 8, pCfgHost->Props.fSigned ? "S" : "U",
562 pCfgHost->Props.cChannels, pCfgHost->Props.cChannels == 1 ? "Channel" : "Channels"));
563
564 PDMAUDIOSTREAMCFG CfgHostAcq;
565 int rc = drvAudioStreamCreateInternalBackend(pThis, pStream, pCfgHost, &CfgHostAcq);
566 if (RT_FAILURE(rc))
567 return rc;
568
569#ifdef DEBUG
570 LogFunc(("[%s] Acquired host format:\n", pStream->szName));
571 DrvAudioHlpStreamCfgPrint(&CfgHostAcq);
572#endif
573
574 LogRel2(("Audio: Acquired host %s format for '%s': %RU32Hz, %RU8%s, %RU8 %s\n",
575 CfgHostAcq.enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStream->szName,
576 CfgHostAcq.Props.uHz, CfgHostAcq.Props.cBytes * 8, CfgHostAcq.Props.fSigned ? "S" : "U",
577 CfgHostAcq.Props.cChannels, CfgHostAcq.Props.cChannels == 1 ? "Channel" : "Channels"));
578
579 /* Let the user know if the backend changed some of the tweakable values. */
580 if (CfgHostAcq.Backend.cfBufferSize != pCfgHost->Backend.cfBufferSize)
581 LogRel2(("Audio: Backend changed buffer size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",
582 DrvAudioHlpFramesToMilli(pCfgHost->Backend.cfBufferSize, &pCfgHost->Props), pCfgHost->Backend.cfBufferSize,
583 DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfBufferSize, &CfgHostAcq.Props), CfgHostAcq.Backend.cfBufferSize));
584
585 if (CfgHostAcq.Backend.cfPeriod != pCfgHost->Backend.cfPeriod)
586 LogRel2(("Audio: Backend changed period size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",
587 DrvAudioHlpFramesToMilli(pCfgHost->Backend.cfPeriod, &pCfgHost->Props), pCfgHost->Backend.cfPeriod,
588 DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfPeriod, &CfgHostAcq.Props), CfgHostAcq.Backend.cfPeriod));
589
590 if (CfgHostAcq.Backend.cfPreBuf != pCfgHost->Backend.cfPreBuf)
591 LogRel2(("Audio: Backend changed pre-buffering size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",
592 DrvAudioHlpFramesToMilli(pCfgHost->Backend.cfPreBuf, &pCfgHost->Props), pCfgHost->Backend.cfPreBuf,
593 DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfPreBuf, &CfgHostAcq.Props), CfgHostAcq.Backend.cfPreBuf));
594 /*
595 * Configure host buffers.
596 */
597
598 /* Check if the backend did return sane values and correct if necessary.
599 * Should never happen with our own backends, but you never know ... */
600 if (CfgHostAcq.Backend.cfBufferSize < CfgHostAcq.Backend.cfPreBuf)
601 {
602 LogRel2(("Audio: Warning: Pre-buffering size (%RU32 frames) of stream '%s' does not match buffer size (%RU32 frames), "
603 "setting pre-buffering size to %RU32 frames\n",
604 CfgHostAcq.Backend.cfPreBuf, pStream->szName, CfgHostAcq.Backend.cfBufferSize, CfgHostAcq.Backend.cfBufferSize));
605 CfgHostAcq.Backend.cfPreBuf = CfgHostAcq.Backend.cfBufferSize;
606 }
607
608 if (CfgHostAcq.Backend.cfPeriod > CfgHostAcq.Backend.cfBufferSize)
609 {
610 LogRel2(("Audio: Warning: Period size (%RU32 frames) of stream '%s' does not match buffer size (%RU32 frames), setting to %RU32 frames\n",
611 CfgHostAcq.Backend.cfPeriod, pStream->szName, CfgHostAcq.Backend.cfBufferSize, CfgHostAcq.Backend.cfBufferSize));
612 CfgHostAcq.Backend.cfPeriod = CfgHostAcq.Backend.cfBufferSize;
613 }
614
615 uint64_t msBufferSize = DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfBufferSize, &CfgHostAcq.Props);
616
617 LogRel2(("Audio: Buffer size of stream '%s' is %RU64ms (%RU32 frames)\n",
618 pStream->szName, msBufferSize, CfgHostAcq.Backend.cfBufferSize));
619
620 /* If no own pre-buffer is set, let the backend choose. */
621 uint64_t msPreBuf = DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfPreBuf, &CfgHostAcq.Props);
622 LogRel2(("Audio: Pre-buffering size of stream '%s' is %RU64ms (%RU32 frames)\n",
623 pStream->szName, msPreBuf, CfgHostAcq.Backend.cfPreBuf));
624
625 /* Make sure the configured buffer size by the backend at least can hold the configured latency. */
626 const uint32_t msPeriod = DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfPeriod, &CfgHostAcq.Props);
627
628 LogRel2(("Audio: Period size of stream '%s' is %RU64ms (%RU32 frames)\n",
629 pStream->szName, msPeriod, CfgHostAcq.Backend.cfPeriod));
630
631 /* Destroy any former mixing buffer. */
632 AudioMixBufDestroy(&pStream->Host.MixBuf);
633
634 /* Make sure to (re-)set the host buffer's shift size. */
635 CfgHostAcq.Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(CfgHostAcq.Props.cBytes, CfgHostAcq.Props.cChannels);
636
637 rc = AudioMixBufInit(&pStream->Host.MixBuf, pStream->szName, &CfgHostAcq.Props, CfgHostAcq.Backend.cfBufferSize);
638 AssertRC(rc);
639
640 /* Make a copy of the acquired host stream configuration. */
641 rc = DrvAudioHlpStreamCfgCopy(&pStream->Host.Cfg, &CfgHostAcq);
642 AssertRC(rc);
643
644 /*
645 * Init guest stream.
646 */
647
648 if (pCfgGuest->Device.uSchedulingHintMs)
649 LogRel2(("Audio: Stream '%s' got a scheduling hint of %RU32ms (%RU32 bytes)\n",
650 pStream->szName, pCfgGuest->Device.uSchedulingHintMs,
651 DrvAudioHlpMilliToBytes(pCfgGuest->Device.uSchedulingHintMs, &pCfgGuest->Props)));
652
653 /* Destroy any former mixing buffer. */
654 AudioMixBufDestroy(&pStream->Guest.MixBuf);
655
656 /* Set the guests's default audio data layout. */
657 pCfgGuest->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;
658
659 /* Make sure to (re-)set the guest buffer's shift size. */
660 pCfgGuest->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgGuest->Props.cBytes, pCfgGuest->Props.cChannels);
661
662 rc = AudioMixBufInit(&pStream->Guest.MixBuf, pStream->szName, &pCfgGuest->Props, CfgHostAcq.Backend.cfBufferSize);
663 AssertRC(rc);
664
665 /* Make a copy of the guest stream configuration. */
666 rc = DrvAudioHlpStreamCfgCopy(&pStream->Guest.Cfg, pCfgGuest);
667 AssertRC(rc);
668
669 if (RT_FAILURE(rc))
670 LogRel(("Audio: Creating stream '%s' failed with %Rrc\n", pStream->szName, rc));
671
672 if (pCfgGuest->enmDir == PDMAUDIODIR_IN)
673 {
674 /* Host (Parent) -> Guest (Child). */
675 rc = AudioMixBufLinkTo(&pStream->Host.MixBuf, &pStream->Guest.MixBuf);
676 AssertRC(rc);
677 }
678 else
679 {
680 /* Guest (Parent) -> Host (Child). */
681 rc = AudioMixBufLinkTo(&pStream->Guest.MixBuf, &pStream->Host.MixBuf);
682 AssertRC(rc);
683 }
684
685#ifdef VBOX_WITH_STATISTICS
686 char szStatName[255];
687
688 if (pCfgGuest->enmDir == PDMAUDIODIR_IN)
689 {
690 RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/BytesElapsed", pStream->szName);
691 PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->In.Stats.BytesElapsed,
692 szStatName, STAMUNIT_BYTES, "Elapsed bytes read.");
693
694 RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/BytesRead", pStream->szName);
695 PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->In.Stats.BytesTotalRead,
696 szStatName, STAMUNIT_BYTES, "Total bytes read.");
697
698 RTStrPrintf(szStatName, sizeof(szStatName), "Host/%s/FramesCaptured", pStream->szName);
699 PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->In.Stats.FramesCaptured,
700 szStatName, STAMUNIT_COUNT, "Total frames captured.");
701 }
702 else if (pCfgGuest->enmDir == PDMAUDIODIR_OUT)
703 {
704 RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/BytesElapsed", pStream->szName);
705 PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->Out.Stats.BytesElapsed,
706 szStatName, STAMUNIT_BYTES, "Elapsed bytes written.");
707
708 RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/BytesWritten", pStream->szName);
709 PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->Out.Stats.BytesTotalWritten,
710 szStatName, STAMUNIT_BYTES, "Total bytes written.");
711
712 RTStrPrintf(szStatName, sizeof(szStatName), "Host/%s/FramesPlayed", pStream->szName);
713 PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->Out.Stats.FramesPlayed,
714 szStatName, STAMUNIT_COUNT, "Total frames played.");
715 }
716 else
717 AssertFailed();
718#endif
719
720 LogFlowFunc(("[%s] Returning %Rrc\n", pStream->szName, rc));
721 return rc;
722}
723
724/**
725 * Frees an audio stream and its allocated resources.
726 *
727 * @param pStream Audio stream to free.
728 * After this call the pointer will not be valid anymore.
729 */
730static void drvAudioStreamFree(PPDMAUDIOSTREAM pStream)
731{
732 if (!pStream)
733 return;
734
735 LogFunc(("[%s]\n", pStream->szName));
736
737 if (pStream->pvBackend)
738 {
739 Assert(pStream->cbBackend);
740 RTMemFree(pStream->pvBackend);
741 pStream->pvBackend = NULL;
742 }
743
744 RTMemFree(pStream);
745 pStream = NULL;
746}
747
748#ifdef VBOX_WITH_AUDIO_CALLBACKS
749/**
750 * Schedules a re-initialization of all current audio streams.
751 * The actual re-initialization will happen at some later point in time.
752 *
753 * @returns IPRT status code.
754 * @param pThis Pointer to driver instance.
755 */
756static int drvAudioScheduleReInitInternal(PDRVAUDIO pThis)
757{
758 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
759
760 LogFunc(("\n"));
761
762 /* Mark all host streams to re-initialize. */
763 PPDMAUDIOSTREAM pStream;
764 RTListForEach(&pThis->lstStreams, pStream, PDMAUDIOSTREAM, Node)
765 pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_PENDING_REINIT;
766
767# ifdef VBOX_WITH_AUDIO_ENUM
768 /* Re-enumerate all host devices as soon as possible. */
769 pThis->fEnumerateDevices = true;
770# endif
771
772 return VINF_SUCCESS;
773}
774#endif /* VBOX_WITH_AUDIO_CALLBACKS */
775
776/**
777 * Re-initializes an audio stream with its existing host and guest stream configuration.
778 * This might be the case if the backend told us we need to re-initialize because something
779 * on the host side has changed.
780 *
781 * @returns IPRT status code.
782 * @param pThis Pointer to driver instance.
783 * @param pStream Stream to re-initialize.
784 */
785static int drvAudioStreamReInitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream)
786{
787 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
788 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
789
790 LogFlowFunc(("[%s]\n", pStream->szName));
791
792 /*
793 * Gather current stream status.
794 */
795 bool fIsEnabled = RT_BOOL(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED); /* Stream is enabled? */
796
797 /*
798 * Destroy and re-create stream on backend side.
799 */
800 int rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
801 if (RT_SUCCESS(rc))
802 {
803 rc = drvAudioStreamDestroyInternalBackend(pThis, pStream);
804 if (RT_SUCCESS(rc))
805 {
806 rc = drvAudioStreamCreateInternalBackend(pThis, pStream, &pStream->Host.Cfg, NULL /* pCfgAcq */);
807 /** @todo Validate (re-)acquired configuration with pStream->Host.Cfg? */
808 }
809 }
810
811 /* Drop all old data. */
812 drvAudioStreamDropInternal(pThis, pStream);
813
814 /*
815 * Restore previous stream state.
816 */
817 if (fIsEnabled)
818 rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_ENABLE);
819
820 if (RT_FAILURE(rc))
821 LogRel(("Audio: Re-initializing stream '%s' failed with %Rrc\n", pStream->szName, rc));
822
823 LogFunc(("[%s] Returning %Rrc\n", pStream->szName, rc));
824 return rc;
825}
826
827/**
828 * Drops all audio data (and associated state) of a stream.
829 *
830 * @param pThis Pointer to driver instance.
831 * @param pStream Stream to drop data for.
832 */
833static void drvAudioStreamDropInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream)
834{
835 RT_NOREF(pThis);
836
837 LogFunc(("[%s]\n", pStream->szName));
838
839 AudioMixBufReset(&pStream->Guest.MixBuf);
840 AudioMixBufReset(&pStream->Host.MixBuf);
841
842 pStream->tsLastIteratedNs = 0;
843 pStream->tsLastPlayedCapturedNs = 0;
844 pStream->tsLastReadWrittenNs = 0;
845
846 pStream->fThresholdReached = false;
847}
848
849/**
850 * Resets a given audio stream.
851 *
852 * @param pThis Pointer to driver instance.
853 * @param pStream Stream to reset.
854 */
855static void drvAudioStreamResetInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream)
856{
857 drvAudioStreamDropInternal(pThis, pStream);
858
859 LogFunc(("[%s]\n", pStream->szName));
860
861 pStream->fStatus = PDMAUDIOSTREAMSTS_FLAG_INITIALIZED;
862
863#ifdef VBOX_WITH_STATISTICS
864 /*
865 * Reset statistics.
866 */
867 if (pStream->enmDir == PDMAUDIODIR_IN)
868 {
869 STAM_COUNTER_RESET(&pStream->In.Stats.BytesElapsed);
870 STAM_COUNTER_RESET(&pStream->In.Stats.BytesTotalRead);
871 STAM_COUNTER_RESET(&pStream->In.Stats.FramesCaptured);
872 }
873 else if (pStream->enmDir == PDMAUDIODIR_OUT)
874 {
875 STAM_COUNTER_RESET(&pStream->Out.Stats.BytesElapsed);
876 STAM_COUNTER_RESET(&pStream->Out.Stats.BytesTotalWritten);
877 STAM_COUNTER_RESET(&pStream->Out.Stats.FramesPlayed);
878 }
879 else
880 AssertFailed();
881#endif
882}
883
884/**
885 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamWrite}
886 */
887static DECLCALLBACK(int) drvAudioStreamWrite(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream,
888 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
889{
890 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
891 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
892 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
893 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
894 /* pcbWritten is optional. */
895
896 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
897
898 AssertMsg(pStream->enmDir == PDMAUDIODIR_OUT,
899 ("Stream '%s' is not an output stream and therefore cannot be written to (direction is '%s')\n",
900 pStream->szName, DrvAudioHlpAudDirToStr(pStream->enmDir)));
901
902 AssertMsg(DrvAudioHlpBytesIsAligned(cbBuf, &pStream->Guest.Cfg.Props),
903 ("Stream '%s' got a non-frame-aligned write (%RU32 bytes)\n", pStream->szName, cbBuf));
904
905 uint32_t cbWrittenTotal = 0;
906
907 int rc = RTCritSectEnter(&pThis->CritSect);
908 if (RT_FAILURE(rc))
909 return rc;
910
911#ifdef VBOX_WITH_STATISTICS
912 STAM_PROFILE_ADV_START(&pThis->Stats.DelayOut, out);
913#endif
914
915#ifdef LOG_ENABLED
916 char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus);
917 AssertPtr(pszStreamSts);
918#endif
919
920 /* Whether to discard the incoming data or not. */
921 bool fToBitBucket = false;
922
923 do
924 {
925 if ( !pThis->Out.fEnabled
926 || !DrvAudioHlpStreamStatusIsReady(pStream->fStatus))
927 {
928 rc = VERR_AUDIO_STREAM_NOT_READY;
929 break;
930 }
931
932 if (pThis->pHostDrvAudio)
933 {
934 /* If the backend's stream is not writable, all written data goes to /dev/null. */
935 if (!DrvAudioHlpStreamStatusCanWrite(
936 pThis->pHostDrvAudio->pfnStreamGetStatus(pThis->pHostDrvAudio, pStream->pvBackend)))
937 {
938 fToBitBucket = true;
939 break;
940 }
941 }
942
943 const uint32_t cbFree = AudioMixBufFreeBytes(&pStream->Host.MixBuf);
944 if (cbFree < cbBuf)
945 LogRel2(("Audio: Lost audio output (%RU64ms, %RU32 free but needs %RU32) due to full host stream buffer '%s'\n",
946 DrvAudioHlpBytesToMilli(cbBuf - cbFree, &pStream->Host.Cfg.Props), cbFree, cbBuf, pStream->szName));
947
948 uint32_t cbToWrite = RT_MIN(cbBuf, cbFree);
949 if (!cbToWrite)
950 {
951 rc = VERR_BUFFER_OVERFLOW;
952 break;
953 }
954
955 /* We use the guest side mixing buffer as an intermediate buffer to do some
956 * (first) processing (if needed), so always write the incoming data at offset 0. */
957 uint32_t cfGstWritten = 0;
958 rc = AudioMixBufWriteAt(&pStream->Guest.MixBuf, 0 /* offFrames */, pvBuf, cbToWrite, &cfGstWritten);
959 if ( RT_FAILURE(rc)
960 || !cfGstWritten)
961 {
962 AssertMsgFailed(("[%s] Write failed: cbToWrite=%RU32, cfWritten=%RU32, rc=%Rrc\n",
963 pStream->szName, cbToWrite, cfGstWritten, rc));
964 break;
965 }
966
967 if (pThis->Out.Cfg.Dbg.fEnabled)
968 DrvAudioHlpFileWrite(pStream->Out.Dbg.pFileStreamWrite, pvBuf, cbToWrite, 0 /* fFlags */);
969
970#ifdef VBOX_WITH_STATISTICS
971 STAM_COUNTER_ADD(&pThis->Stats.TotalFramesWritten, cfGstWritten);
972#endif
973 uint32_t cfGstMixed = 0;
974 if (cfGstWritten)
975 {
976 int rc2 = AudioMixBufMixToParentEx(&pStream->Guest.MixBuf, 0 /* cSrcOffset */, cfGstWritten /* cSrcFrames */,
977 &cfGstMixed /* pcSrcMixed */);
978 if (RT_FAILURE(rc2))
979 {
980 AssertMsgFailed(("[%s] Mixing failed: cbToWrite=%RU32, cfWritten=%RU32, cfMixed=%RU32, rc=%Rrc\n",
981 pStream->szName, cbToWrite, cfGstWritten, cfGstMixed, rc2));
982 }
983 else
984 {
985 Log3Func(("[%s] Writing %RU32 frames (%RU64ms), now filled with %RU64ms -- %RU8%%\n",
986 pStream->szName, cfGstWritten, DrvAudioHlpFramesToMilli(cfGstWritten, &pStream->Guest.Cfg.Props),
987 DrvAudioHlpFramesToMilli(AudioMixBufUsed(&pStream->Host.MixBuf), &pStream->Host.Cfg.Props),
988 AudioMixBufUsed(&pStream->Host.MixBuf) * 100 / AudioMixBufSize(&pStream->Host.MixBuf)));
989
990 pStream->tsLastReadWrittenNs = RTTimeNanoTS();
991#ifdef DEBUG
992 pStream->Out.Dbg.cfWrittenSinceLastPlay += AUDIOMIXBUF_F2F_RATIO(&pStream->Guest.MixBuf, cfGstMixed);
993#endif
994 /* Keep going. */
995 }
996
997 if (RT_SUCCESS(rc))
998 rc = rc2;
999
1000 cbWrittenTotal = AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfGstWritten);
1001
1002#ifdef VBOX_WITH_STATISTICS
1003 STAM_COUNTER_ADD(&pThis->Stats.TotalFramesMixedOut, cfGstMixed);
1004 Assert(cfGstWritten >= cfGstMixed);
1005 STAM_COUNTER_ADD(&pThis->Stats.TotalFramesLostOut, cfGstWritten - cfGstMixed);
1006 STAM_COUNTER_ADD(&pThis->Stats.TotalBytesWritten, cbWrittenTotal);
1007 STAM_COUNTER_ADD(&pStream->Out.Stats.BytesTotalWritten, cbWrittenTotal);
1008#endif
1009 }
1010
1011 Log3Func(("[%s] Dbg: cbBuf=%RU32, cbToWrite=%RU32, cfHstUsed=%RU32, cfHstfLive=%RU32, cfGstWritten=%RU32, "
1012 "cfGstMixed=%RU32, cbWrittenTotal=%RU32, rc=%Rrc\n",
1013 pStream->szName, cbBuf, cbToWrite, AudioMixBufUsed(&pStream->Host.MixBuf),
1014 AudioMixBufLive(&pStream->Host.MixBuf), cfGstWritten, cfGstMixed, cbWrittenTotal, rc));
1015
1016 } while (0);
1017
1018#ifdef LOG_ENABLED
1019 RTStrFree(pszStreamSts);
1020#endif
1021
1022 int rc2 = RTCritSectLeave(&pThis->CritSect);
1023 if (RT_SUCCESS(rc))
1024 rc = rc2;
1025
1026 if (RT_SUCCESS(rc))
1027 {
1028 if (fToBitBucket)
1029 {
1030 Log3Func(("[%s] Backend stream not ready (yet), discarding written data\n", pStream->szName));
1031 cbWrittenTotal = cbBuf; /* Report all data as being written to the caller. */
1032 }
1033
1034 if (pcbWritten)
1035 *pcbWritten = cbWrittenTotal;
1036 }
1037
1038 return rc;
1039}
1040
1041/**
1042 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRetain}
1043 */
1044static DECLCALLBACK(uint32_t) drvAudioStreamRetain(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
1045{
1046 AssertPtrReturn(pInterface, UINT32_MAX);
1047 AssertPtrReturn(pStream, UINT32_MAX);
1048
1049 NOREF(pInterface);
1050
1051 return ++pStream->cRefs;
1052}
1053
1054/**
1055 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRelease}
1056 */
1057static DECLCALLBACK(uint32_t) drvAudioStreamRelease(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
1058{
1059 AssertPtrReturn(pInterface, UINT32_MAX);
1060 AssertPtrReturn(pStream, UINT32_MAX);
1061
1062 NOREF(pInterface);
1063
1064 if (pStream->cRefs > 1) /* 1 reference always is kept by this audio driver. */
1065 pStream->cRefs--;
1066
1067 return pStream->cRefs;
1068}
1069
1070/**
1071 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamIterate}
1072 */
1073static DECLCALLBACK(int) drvAudioStreamIterate(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
1074{
1075 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1076 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1077
1078 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
1079
1080 int rc = RTCritSectEnter(&pThis->CritSect);
1081 if (RT_FAILURE(rc))
1082 return rc;
1083
1084 rc = drvAudioStreamIterateInternal(pThis, pStream);
1085
1086 int rc2 = RTCritSectLeave(&pThis->CritSect);
1087 if (RT_SUCCESS(rc))
1088 rc = rc2;
1089
1090 if (RT_FAILURE(rc))
1091 LogFlowFuncLeaveRC(rc);
1092
1093 return rc;
1094}
1095
1096/**
1097 * Does one iteration of an audio stream.
1098 * This function gives the backend the chance of iterating / altering data and
1099 * does the actual mixing between the guest <-> host mixing buffers.
1100 *
1101 * @returns IPRT status code.
1102 * @param pThis Pointer to driver instance.
1103 * @param pStream Stream to iterate.
1104 */
1105static int drvAudioStreamIterateInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream)
1106{
1107 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1108
1109 if (!pThis->pHostDrvAudio)
1110 return VINF_SUCCESS;
1111
1112 if (!pStream)
1113 return VINF_SUCCESS;
1114
1115 int rc;
1116
1117 /* Is the stream scheduled for re-initialization? Do so now. */
1118 if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_REINIT)
1119 {
1120#ifdef VBOX_WITH_AUDIO_ENUM
1121 if (pThis->fEnumerateDevices)
1122 {
1123 /* Re-enumerate all host devices. */
1124 drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */);
1125
1126 pThis->fEnumerateDevices = false;
1127 }
1128#endif /* VBOX_WITH_AUDIO_ENUM */
1129
1130 /* Remove the pending re-init flag in any case, regardless whether the actual re-initialization succeeded
1131 * or not. If it failed, the backend needs to notify us again to try again at some later point in time. */
1132 pStream->fStatus &= ~PDMAUDIOSTREAMSTS_FLAG_PENDING_REINIT;
1133
1134 rc = drvAudioStreamReInitInternal(pThis, pStream);
1135 if (RT_FAILURE(rc))
1136 return rc;
1137 }
1138
1139#ifdef LOG_ENABLED
1140 char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus);
1141 Log3Func(("[%s] fStatus=%s\n", pStream->szName, pszStreamSts));
1142 RTStrFree(pszStreamSts);
1143#endif /* LOG_ENABLED */
1144
1145 /* Not enabled or paused? Skip iteration. */
1146 if ( !(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED)
1147 || (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED))
1148 {
1149 return VINF_SUCCESS;
1150 }
1151
1152 /* Whether to try closing a pending to close stream. */
1153 bool fTryClosePending = false;
1154
1155 do
1156 {
1157 rc = pThis->pHostDrvAudio->pfnStreamIterate(pThis->pHostDrvAudio, pStream->pvBackend);
1158 if (RT_FAILURE(rc))
1159 break;
1160
1161 if (pStream->enmDir == PDMAUDIODIR_OUT)
1162 {
1163 /* No audio frames to transfer from guest to host (anymore)?
1164 * Then try closing this stream if marked so in the next block. */
1165 const uint32_t cfLive = AudioMixBufLive(&pStream->Host.MixBuf);
1166 fTryClosePending = cfLive == 0;
1167 Log3Func(("[%s] fTryClosePending=%RTbool, cfLive=%RU32\n", pStream->szName, fTryClosePending, cfLive));
1168 }
1169
1170 /* Has the host stream marked as pending to disable?
1171 * Try disabling the stream then. */
1172 if ( pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE
1173 && fTryClosePending)
1174 {
1175 /* Tell the backend to drain the stream, that is, play the remaining (buffered) data
1176 * on the backend side. */
1177 rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DRAIN);
1178 if (rc == VERR_NOT_SUPPORTED) /* Not all backends support draining. */
1179 rc = VINF_SUCCESS;
1180
1181 if (RT_SUCCESS(rc))
1182 {
1183 if (pThis->pHostDrvAudio->pfnStreamGetPending) /* Optional to implement. */
1184 {
1185 const uint32_t cxPending = pThis->pHostDrvAudio->pfnStreamGetPending(pThis->pHostDrvAudio, pStream->pvBackend);
1186 Log3Func(("[%s] cxPending=%RU32\n", pStream->szName, cxPending));
1187
1188 /* Only try close pending if no audio data is pending on the backend-side anymore. */
1189 fTryClosePending = cxPending == 0;
1190 }
1191
1192 if (fTryClosePending)
1193 {
1194 LogFunc(("[%s] Closing pending stream\n", pStream->szName));
1195 rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
1196 if (RT_SUCCESS(rc))
1197 {
1198 pStream->fStatus &= ~(PDMAUDIOSTREAMSTS_FLAG_ENABLED | PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE);
1199 drvAudioStreamDropInternal(pThis, pStream);
1200 }
1201 else
1202 LogFunc(("[%s] Backend vetoed against closing pending input stream, rc=%Rrc\n", pStream->szName, rc));
1203 }
1204 }
1205 }
1206
1207 } while (0);
1208
1209 /* Update timestamps. */
1210 pStream->tsLastIteratedNs = RTTimeNanoTS();
1211
1212 if (RT_FAILURE(rc))
1213 LogFunc(("[%s] Failed with %Rrc\n", pStream->szName, rc));
1214
1215 return rc;
1216}
1217
1218/**
1219 * Plays an audio host output stream which has been configured for non-interleaved (layout) data.
1220 *
1221 * @return IPRT status code.
1222 * @param pThis Pointer to driver instance.
1223 * @param pStream Stream to play.
1224 * @param cfToPlay Number of audio frames to play.
1225 * @param pcfPlayed Returns number of audio frames played. Optional.
1226 */
1227static int drvAudioStreamPlayNonInterleaved(PDRVAUDIO pThis,
1228 PPDMAUDIOSTREAM pStream, uint32_t cfToPlay, uint32_t *pcfPlayed)
1229{
1230 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1231 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1232 /* pcfPlayed is optional. */
1233
1234 if (!cfToPlay)
1235 {
1236 if (pcfPlayed)
1237 *pcfPlayed = 0;
1238 return VINF_SUCCESS;
1239 }
1240
1241 /* Sanity. */
1242 Assert(pStream->enmDir == PDMAUDIODIR_OUT);
1243 Assert(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED);
1244
1245 int rc = VINF_SUCCESS;
1246
1247 uint32_t cfPlayedTotal = 0;
1248
1249 uint8_t auBuf[256]; /** @todo Get rid of this here. */
1250
1251 uint32_t cfLeft = cfToPlay;
1252 uint32_t cbChunk = sizeof(auBuf);
1253
1254 while (cfLeft)
1255 {
1256 uint32_t cfRead = 0;
1257 rc = AudioMixBufAcquireReadBlock(&pStream->Host.MixBuf,
1258 auBuf, RT_MIN(cbChunk, AUDIOMIXBUF_F2B(&pStream->Host.MixBuf, cfLeft)),
1259 &cfRead);
1260 if (RT_FAILURE(rc))
1261 break;
1262
1263 uint32_t cbRead = AUDIOMIXBUF_F2B(&pStream->Host.MixBuf, cfRead);
1264 Assert(cbRead <= cbChunk);
1265
1266 uint32_t cfPlayed = 0;
1267 uint32_t cbPlayed = 0;
1268 rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStream->pvBackend,
1269 auBuf, cbRead, &cbPlayed);
1270 if ( RT_SUCCESS(rc)
1271 && cbPlayed)
1272 {
1273 if (pThis->Out.Cfg.Dbg.fEnabled)
1274 DrvAudioHlpFileWrite(pStream->Out.Dbg.pFilePlayNonInterleaved, auBuf, cbPlayed, 0 /* fFlags */);
1275
1276 if (cbRead != cbPlayed)
1277 LogRel2(("Audio: Host stream '%s' played wrong amount (%RU32 bytes read but played %RU32)\n",
1278 pStream->szName, cbRead, cbPlayed));
1279
1280 cfPlayed = AUDIOMIXBUF_B2F(&pStream->Host.MixBuf, cbPlayed);
1281 cfPlayedTotal += cfPlayed;
1282
1283 Assert(cfLeft >= cfPlayed);
1284 cfLeft -= cfPlayed;
1285 }
1286
1287 AudioMixBufReleaseReadBlock(&pStream->Host.MixBuf, cfPlayed);
1288
1289 if (RT_FAILURE(rc))
1290 break;
1291 }
1292
1293 Log3Func(("[%s] Played %RU32/%RU32 frames, rc=%Rrc\n", pStream->szName, cfPlayedTotal, cfToPlay, rc));
1294
1295 if (RT_SUCCESS(rc))
1296 {
1297 if (pcfPlayed)
1298 *pcfPlayed = cfPlayedTotal;
1299 }
1300
1301 return rc;
1302}
1303
1304/**
1305 * Plays an audio host output stream which has been configured for raw audio (layout) data.
1306 *
1307 * @return IPRT status code.
1308 * @param pThis Pointer to driver instance.
1309 * @param pStream Stream to play.
1310 * @param cfToPlay Number of audio frames to play.
1311 * @param pcfPlayed Returns number of audio frames played. Optional.
1312 */
1313static int drvAudioStreamPlayRaw(PDRVAUDIO pThis,
1314 PPDMAUDIOSTREAM pStream, uint32_t cfToPlay, uint32_t *pcfPlayed)
1315{
1316 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1317 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1318 /* pcfPlayed is optional. */
1319
1320 /* Sanity. */
1321 Assert(pStream->enmDir == PDMAUDIODIR_OUT);
1322 Assert(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW);
1323
1324 if (!cfToPlay)
1325 {
1326 if (pcfPlayed)
1327 *pcfPlayed = 0;
1328 return VINF_SUCCESS;
1329 }
1330
1331 int rc = VINF_SUCCESS;
1332
1333 uint32_t cfPlayedTotal = 0;
1334
1335 PDMAUDIOFRAME aFrameBuf[_4K]; /** @todo Get rid of this here. */
1336
1337 uint32_t cfLeft = cfToPlay;
1338 while (cfLeft)
1339 {
1340 uint32_t cfRead = 0;
1341 rc = AudioMixBufPeek(&pStream->Host.MixBuf, cfLeft, aFrameBuf,
1342 RT_MIN(cfLeft, RT_ELEMENTS(aFrameBuf)), &cfRead);
1343
1344 if (RT_SUCCESS(rc))
1345 {
1346 if (cfRead)
1347 {
1348 uint32_t cfPlayed;
1349
1350 /* Note: As the stream layout is RPDMAUDIOSTREAMLAYOUT_RAW, operate on audio frames
1351 * rather on bytes. */
1352 Assert(cfRead <= RT_ELEMENTS(aFrameBuf));
1353 rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStream->pvBackend,
1354 aFrameBuf, cfRead, &cfPlayed);
1355 if ( RT_FAILURE(rc)
1356 || !cfPlayed)
1357 {
1358 break;
1359 }
1360
1361 cfPlayedTotal += cfPlayed;
1362 Assert(cfPlayedTotal <= cfToPlay);
1363
1364 Assert(cfLeft >= cfRead);
1365 cfLeft -= cfRead;
1366 }
1367 else
1368 {
1369 if (rc == VINF_AUDIO_MORE_DATA_AVAILABLE) /* Do another peeking round if there is more data available. */
1370 continue;
1371
1372 break;
1373 }
1374 }
1375 else if (RT_FAILURE(rc))
1376 break;
1377 }
1378
1379 Log3Func(("[%s] Played %RU32/%RU32 frames, rc=%Rrc\n", pStream->szName, cfPlayedTotal, cfToPlay, rc));
1380
1381 if (RT_SUCCESS(rc))
1382 {
1383 if (pcfPlayed)
1384 *pcfPlayed = cfPlayedTotal;
1385
1386 }
1387
1388 return rc;
1389}
1390
1391/**
1392 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamPlay}
1393 */
1394static DECLCALLBACK(int) drvAudioStreamPlay(PPDMIAUDIOCONNECTOR pInterface,
1395 PPDMAUDIOSTREAM pStream, uint32_t *pcFramesPlayed)
1396{
1397 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1398 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1399 /* pcFramesPlayed is optional. */
1400
1401 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
1402
1403 int rc = RTCritSectEnter(&pThis->CritSect);
1404 if (RT_FAILURE(rc))
1405 return rc;
1406
1407 AssertMsg(pStream->enmDir == PDMAUDIODIR_OUT,
1408 ("Stream '%s' is not an output stream and therefore cannot be played back (direction is 0x%x)\n",
1409 pStream->szName, pStream->enmDir));
1410
1411 uint32_t cfPlayedTotal = 0;
1412
1413 PDMAUDIOSTREAMSTS stsStream = pStream->fStatus;
1414#ifdef LOG_ENABLED
1415 char *pszStreamSts = dbgAudioStreamStatusToStr(stsStream);
1416 Log3Func(("[%s] Start fStatus=%s\n", pStream->szName, pszStreamSts));
1417 RTStrFree(pszStreamSts);
1418#endif /* LOG_ENABLED */
1419
1420 do
1421 {
1422 if (!pThis->pHostDrvAudio)
1423 {
1424 rc = VERR_PDM_NO_ATTACHED_DRIVER;
1425 break;
1426 }
1427
1428 if ( !pThis->Out.fEnabled
1429 || !DrvAudioHlpStreamStatusIsReady(stsStream))
1430 {
1431 rc = VERR_AUDIO_STREAM_NOT_READY;
1432 break;
1433 }
1434
1435 const uint32_t cfLive = AudioMixBufLive(&pStream->Host.MixBuf);
1436#ifdef LOG_ENABLED
1437 const uint8_t uLivePercent = (100 * cfLive) / AudioMixBufSize(&pStream->Host.MixBuf);
1438#endif
1439 const uint64_t tsDeltaPlayedCapturedNs = RTTimeNanoTS() - pStream->tsLastPlayedCapturedNs;
1440 const uint32_t cfPassedReal = DrvAudioHlpNanoToFrames(tsDeltaPlayedCapturedNs, &pStream->Host.Cfg.Props);
1441
1442 const uint32_t cfPeriod = pStream->Host.Cfg.Backend.cfPeriod;
1443
1444 Log3Func(("[%s] Last played %RU64ns (%RU64ms), filled with %RU64ms (%RU8%%) total (fThresholdReached=%RTbool)\n",
1445 pStream->szName, tsDeltaPlayedCapturedNs, tsDeltaPlayedCapturedNs / RT_NS_1MS_64,
1446 DrvAudioHlpFramesToMilli(cfLive, &pStream->Host.Cfg.Props), uLivePercent, pStream->fThresholdReached));
1447
1448 /* Has the treshold been reached (e.g. are we in playing stage) and we now have less live samples
1449 * then time which has been passed? Then enter the buffering stage again. */
1450 if ( pStream->fThresholdReached
1451 && cfLive < cfPassedReal)
1452 {
1453 LogRel2(("Audio: Buffer underrun for stream '%s' occurred (%RU64ms passed but only %RU64ms in buffer)\n",
1454 pStream->szName,
1455 DrvAudioHlpFramesToMilli(cfPassedReal, &pStream->Host.Cfg.Props),
1456 DrvAudioHlpFramesToMilli(cfLive, &pStream->Host.Cfg.Props)));
1457
1458 /* Enter buffering stage again. */
1459 pStream->fThresholdReached = false;
1460 }
1461
1462 bool fDoPlay = pStream->fThresholdReached;
1463 bool fJustStarted = false;
1464 if (!fDoPlay)
1465 {
1466 /* Did we reach the backend's playback (pre-buffering) threshold? Can be 0 if no threshold set. */
1467 if (cfLive >= pStream->Host.Cfg.Backend.cfPreBuf)
1468 {
1469 LogRel2(("Audio: Stream '%s' buffering complete\n", pStream->szName));
1470 Log3Func(("[%s] Dbg: Buffering complete\n", pStream->szName));
1471 fDoPlay = true;
1472 }
1473 /* Some audio files are shorter than the pre-buffering level (e.g. the "click" Explorer sounds on some Windows guests),
1474 * so make sure that we also play those by checking if the stream already is pending disable mode, even if we didn't
1475 * hit the pre-buffering watermark yet.
1476 *
1477 * To reproduce, use "Windows Navigation Start.wav" on Windows 7 (2824 samples). */
1478 else if ( cfLive
1479 && pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE)
1480 {
1481 LogRel2(("Audio: Stream '%s' buffering complete (short sound)\n", pStream->szName));
1482 Log3Func(("[%s] Dbg: Buffering complete (short)\n", pStream->szName));
1483 fDoPlay = true;
1484 }
1485
1486 if (fDoPlay)
1487 {
1488 pStream->fThresholdReached = true;
1489 fJustStarted = true;
1490 LogRel2(("Audio: Stream '%s' started playing\n", pStream->szName));
1491 Log3Func(("[%s] Dbg: started playing\n", pStream->szName));
1492 }
1493 else /* Not yet, so still buffering audio data. */
1494 LogRel2(("Audio: Stream '%s' is buffering (%RU8%% complete)\n",
1495 pStream->szName, (100 * cfLive) / pStream->Host.Cfg.Backend.cfPreBuf));
1496 }
1497
1498 if (fDoPlay)
1499 {
1500 uint32_t cfWritable = PDMAUDIOPCMPROPS_B2F(&pStream->Host.Cfg.Props,
1501 pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStream->pvBackend));
1502
1503 uint32_t cfToPlay = 0;
1504 if (fJustStarted)
1505 cfToPlay = RT_MIN(cfWritable, cfPeriod);
1506
1507 if (!cfToPlay)
1508 {
1509 /* Did we reach/pass (in real time) the device scheduling slot?
1510 * Play as much as we can write to the backend then. */
1511 if (cfPassedReal >= DrvAudioHlpMilliToFrames(pStream->Guest.Cfg.Device.uSchedulingHintMs, &pStream->Host.Cfg.Props))
1512 cfToPlay = cfWritable;
1513 }
1514
1515 if (cfToPlay > cfLive) /* Don't try to play more than available. */
1516 cfToPlay = cfLive;
1517#ifdef DEBUG
1518 Log3Func(("[%s] Playing %RU32 frames (%RU64ms), now filled with %RU64ms -- %RU8%%\n",
1519 pStream->szName, cfToPlay, DrvAudioHlpFramesToMilli(cfToPlay, &pStream->Host.Cfg.Props),
1520 DrvAudioHlpFramesToMilli(AudioMixBufUsed(&pStream->Host.MixBuf), &pStream->Host.Cfg.Props),
1521 AudioMixBufUsed(&pStream->Host.MixBuf) * 100 / AudioMixBufSize(&pStream->Host.MixBuf)));
1522#endif
1523 if (cfToPlay)
1524 {
1525 if (pThis->pHostDrvAudio->pfnStreamPlayBegin)
1526 pThis->pHostDrvAudio->pfnStreamPlayBegin(pThis->pHostDrvAudio, pStream->pvBackend);
1527
1528 if (RT_LIKELY(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED))
1529 {
1530 rc = drvAudioStreamPlayNonInterleaved(pThis, pStream, cfToPlay, &cfPlayedTotal);
1531 }
1532 else if (pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW)
1533 {
1534 rc = drvAudioStreamPlayRaw(pThis, pStream, cfToPlay, &cfPlayedTotal);
1535 }
1536 else
1537 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
1538
1539 if (pThis->pHostDrvAudio->pfnStreamPlayEnd)
1540 pThis->pHostDrvAudio->pfnStreamPlayEnd(pThis->pHostDrvAudio, pStream->pvBackend);
1541
1542 pStream->tsLastPlayedCapturedNs = RTTimeNanoTS();
1543#ifdef DEBUG
1544 Log3Func(("[%s] Dbg: cfWrittenSinceLastPlay=%RU64 (%RU64ms)\n",
1545 pStream->szName,
1546 pStream->Out.Dbg.cfWrittenSinceLastPlay, DrvAudioHlpFramesToMilli(pStream->Out.Dbg.cfWrittenSinceLastPlay, &pStream->Host.Cfg.Props)));
1547 pStream->Out.Dbg.cfWrittenSinceLastPlay = 0;
1548#endif
1549 }
1550
1551 Log3Func(("[%s] Dbg: fJustStarted=%RTbool, cfSched=%RU32 (%RU64ms), cfPassedReal=%RU32 (%RU64ms), "
1552 "cfLive=%RU32 (%RU64ms), cfPeriod=%RU32 (%RU64ms), cfWritable=%RU32 (%RU64ms), "
1553 "-> cfToPlay=%RU32 (%RU64ms), cfPlayed=%RU32 (%RU64ms)\n",
1554 pStream->szName, fJustStarted,
1555 DrvAudioHlpMilliToFrames(pStream->Guest.Cfg.Device.uSchedulingHintMs, &pStream->Host.Cfg.Props),
1556 pStream->Guest.Cfg.Device.uSchedulingHintMs,
1557 cfPassedReal, DrvAudioHlpFramesToMilli(cfPassedReal, &pStream->Host.Cfg.Props),
1558 cfLive, DrvAudioHlpFramesToMilli(cfLive, &pStream->Host.Cfg.Props),
1559 cfPeriod, DrvAudioHlpFramesToMilli(cfPeriod, &pStream->Host.Cfg.Props),
1560 cfWritable, DrvAudioHlpFramesToMilli(cfWritable, &pStream->Host.Cfg.Props),
1561 cfToPlay, DrvAudioHlpFramesToMilli(cfToPlay, &pStream->Host.Cfg.Props),
1562 cfPlayedTotal, DrvAudioHlpFramesToMilli(cfPlayedTotal, &pStream->Host.Cfg.Props)));
1563 }
1564
1565 if (RT_SUCCESS(rc))
1566 {
1567 AudioMixBufFinish(&pStream->Host.MixBuf, cfPlayedTotal);
1568
1569#ifdef VBOX_WITH_STATISTICS
1570 STAM_COUNTER_ADD (&pThis->Stats.TotalFramesOut, cfPlayedTotal);
1571 STAM_PROFILE_ADV_STOP(&pThis->Stats.DelayOut, out);
1572 STAM_COUNTER_ADD (&pStream->Out.Stats.FramesPlayed, cfPlayedTotal);
1573#endif
1574 }
1575
1576 } while (0);
1577
1578#ifdef LOG_ENABLED
1579 uint32_t cfLive = AudioMixBufLive(&pStream->Host.MixBuf);
1580 pszStreamSts = dbgAudioStreamStatusToStr(stsStream);
1581 Log3Func(("[%s] End fStatus=%s, cfLive=%RU32, cfPlayedTotal=%RU32, rc=%Rrc\n",
1582 pStream->szName, pszStreamSts, cfLive, cfPlayedTotal, rc));
1583 RTStrFree(pszStreamSts);
1584#endif /* LOG_ENABLED */
1585
1586 int rc2 = RTCritSectLeave(&pThis->CritSect);
1587 if (RT_SUCCESS(rc))
1588 rc = rc2;
1589
1590 if (RT_SUCCESS(rc))
1591 {
1592 if (pcFramesPlayed)
1593 *pcFramesPlayed = cfPlayedTotal;
1594 }
1595
1596 if (RT_FAILURE(rc))
1597 LogFlowFunc(("[%s] Failed with %Rrc\n", pStream->szName, rc));
1598
1599 return rc;
1600}
1601
1602/**
1603 * Captures non-interleaved input from a host stream.
1604 *
1605 * @returns IPRT status code.
1606 * @param pThis Driver instance.
1607 * @param pStream Stream to capture from.
1608 * @param pcfCaptured Number of (host) audio frames captured. Optional.
1609 */
1610static int drvAudioStreamCaptureNonInterleaved(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, uint32_t *pcfCaptured)
1611{
1612 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1613 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1614 /* pcfCaptured is optional. */
1615
1616 /* Sanity. */
1617 Assert(pStream->enmDir == PDMAUDIODIR_IN);
1618 Assert(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED);
1619
1620 int rc = VINF_SUCCESS;
1621
1622 uint32_t cfCapturedTotal = 0;
1623
1624 AssertPtr(pThis->pHostDrvAudio->pfnStreamGetReadable);
1625
1626 uint8_t auBuf[_1K]; /** @todo Get rid of this. */
1627 uint32_t cbBuf = sizeof(auBuf);
1628
1629 uint32_t cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStream->pvBackend);
1630 if (!cbReadable)
1631 Log2Func(("[%s] No readable data available\n", pStream->szName));
1632
1633 uint32_t cbFree = AudioMixBufFreeBytes(&pStream->Guest.MixBuf); /* Parent */
1634 if (!cbFree)
1635 Log2Func(("[%s] Buffer full\n", pStream->szName));
1636
1637 if (cbReadable > cbFree) /* More data readable than we can store at the moment? Limit. */
1638 cbReadable = cbFree;
1639
1640 while (cbReadable)
1641 {
1642 uint32_t cbCaptured;
1643 rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStream->pvBackend,
1644 auBuf, RT_MIN(cbReadable, cbBuf), &cbCaptured);
1645 if (RT_FAILURE(rc))
1646 {
1647 int rc2 = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
1648 AssertRC(rc2);
1649
1650 break;
1651 }
1652
1653 Assert(cbCaptured <= cbBuf);
1654 if (cbCaptured > cbBuf) /* Paranoia. */
1655 cbCaptured = cbBuf;
1656
1657 if (!cbCaptured) /* Nothing captured? Take a shortcut. */
1658 break;
1659
1660 /* We use the host side mixing buffer as an intermediate buffer to do some
1661 * (first) processing (if needed), so always write the incoming data at offset 0. */
1662 uint32_t cfHstWritten = 0;
1663 rc = AudioMixBufWriteAt(&pStream->Host.MixBuf, 0 /* offFrames */, auBuf, cbCaptured, &cfHstWritten);
1664 if ( RT_FAILURE(rc)
1665 || !cfHstWritten)
1666 {
1667 AssertMsgFailed(("[%s] Write failed: cbCaptured=%RU32, cfHstWritten=%RU32, rc=%Rrc\n",
1668 pStream->szName, cbCaptured, cfHstWritten, rc));
1669 break;
1670 }
1671
1672 if (pThis->In.Cfg.Dbg.fEnabled)
1673 DrvAudioHlpFileWrite(pStream->In.Dbg.pFileCaptureNonInterleaved, auBuf, cbCaptured, 0 /* fFlags */);
1674
1675 uint32_t cfHstMixed = 0;
1676 if (cfHstWritten)
1677 {
1678 int rc2 = AudioMixBufMixToParentEx(&pStream->Host.MixBuf, 0 /* cSrcOffset */, cfHstWritten /* cSrcFrames */,
1679 &cfHstMixed /* pcSrcMixed */);
1680 Log3Func(("[%s] cbCaptured=%RU32, cfWritten=%RU32, cfMixed=%RU32, rc=%Rrc\n",
1681 pStream->szName, cbCaptured, cfHstWritten, cfHstMixed, rc2));
1682 AssertRC(rc2);
1683 }
1684
1685 Assert(cbReadable >= cbCaptured);
1686 cbReadable -= cbCaptured;
1687 cfCapturedTotal += cfHstMixed;
1688 }
1689
1690 if (RT_SUCCESS(rc))
1691 {
1692 if (cfCapturedTotal)
1693 Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStream->szName, cfCapturedTotal, rc));
1694 }
1695 else
1696 LogFunc(("[%s] Capturing failed with rc=%Rrc\n", pStream->szName, rc));
1697
1698 if (pcfCaptured)
1699 *pcfCaptured = cfCapturedTotal;
1700
1701 return rc;
1702}
1703
1704/**
1705 * Captures raw input from a host stream.
1706 * Raw input means that the backend directly operates on PDMAUDIOFRAME structs without
1707 * no data layout processing done in between.
1708 *
1709 * Needed for e.g. the VRDP audio backend (in Main).
1710 *
1711 * @returns IPRT status code.
1712 * @param pThis Driver instance.
1713 * @param pStream Stream to capture from.
1714 * @param pcfCaptured Number of (host) audio frames captured. Optional.
1715 */
1716static int drvAudioStreamCaptureRaw(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, uint32_t *pcfCaptured)
1717{
1718 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1719 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1720 /* pcfCaptured is optional. */
1721
1722 /* Sanity. */
1723 Assert(pStream->enmDir == PDMAUDIODIR_IN);
1724 Assert(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW);
1725
1726 int rc = VINF_SUCCESS;
1727
1728 uint32_t cfCapturedTotal = 0;
1729
1730 AssertPtr(pThis->pHostDrvAudio->pfnStreamGetReadable);
1731
1732 /* Note: Raw means *audio frames*, not bytes! */
1733 uint32_t cfReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStream->pvBackend);
1734 if (!cfReadable)
1735 Log2Func(("[%s] No readable data available\n", pStream->szName));
1736
1737 const uint32_t cfFree = AudioMixBufFree(&pStream->Guest.MixBuf); /* Parent */
1738 if (!cfFree)
1739 Log2Func(("[%s] Buffer full\n", pStream->szName));
1740
1741 if (cfReadable > cfFree) /* More data readable than we can store at the moment? Limit. */
1742 cfReadable = cfFree;
1743
1744 while (cfReadable)
1745 {
1746 PPDMAUDIOFRAME paFrames;
1747 uint32_t cfWritable;
1748 rc = AudioMixBufPeekMutable(&pStream->Host.MixBuf, cfReadable, &paFrames, &cfWritable);
1749 if ( RT_FAILURE(rc)
1750 || !cfWritable)
1751 {
1752 break;
1753 }
1754
1755 uint32_t cfCaptured;
1756 rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStream->pvBackend,
1757 paFrames, cfWritable, &cfCaptured);
1758 if (RT_FAILURE(rc))
1759 {
1760 int rc2 = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
1761 AssertRC(rc2);
1762
1763 break;
1764 }
1765
1766 Assert(cfCaptured <= cfWritable);
1767 if (cfCaptured > cfWritable) /* Paranoia. */
1768 cfCaptured = cfWritable;
1769
1770 Assert(cfReadable >= cfCaptured);
1771 cfReadable -= cfCaptured;
1772 cfCapturedTotal += cfCaptured;
1773 }
1774
1775 Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStream->szName, cfCapturedTotal, rc));
1776
1777 if (pcfCaptured)
1778 *pcfCaptured = cfCapturedTotal;
1779
1780 return rc;
1781}
1782
1783/**
1784 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCapture}
1785 */
1786static DECLCALLBACK(int) drvAudioStreamCapture(PPDMIAUDIOCONNECTOR pInterface,
1787 PPDMAUDIOSTREAM pStream, uint32_t *pcFramesCaptured)
1788{
1789 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
1790
1791 int rc = RTCritSectEnter(&pThis->CritSect);
1792 if (RT_FAILURE(rc))
1793 return rc;
1794
1795 AssertMsg(pStream->enmDir == PDMAUDIODIR_IN,
1796 ("Stream '%s' is not an input stream and therefore cannot be captured (direction is 0x%x)\n",
1797 pStream->szName, pStream->enmDir));
1798
1799 uint32_t cfCaptured = 0;
1800
1801#ifdef LOG_ENABLED
1802 char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus);
1803 Log3Func(("[%s] fStatus=%s\n", pStream->szName, pszStreamSts));
1804 RTStrFree(pszStreamSts);
1805#endif /* LOG_ENABLED */
1806
1807 do
1808 {
1809 if (!pThis->pHostDrvAudio)
1810 {
1811 rc = VERR_PDM_NO_ATTACHED_DRIVER;
1812 break;
1813 }
1814
1815 if ( !pThis->In.fEnabled
1816 || !DrvAudioHlpStreamStatusCanRead(pStream->fStatus))
1817 {
1818 rc = VERR_AUDIO_STREAM_NOT_READY;
1819 break;
1820 }
1821
1822 /*
1823 * Do the actual capturing.
1824 */
1825
1826 if (pThis->pHostDrvAudio->pfnStreamCaptureBegin)
1827 pThis->pHostDrvAudio->pfnStreamCaptureBegin(pThis->pHostDrvAudio, pStream->pvBackend);
1828
1829 if (RT_LIKELY(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED))
1830 {
1831 rc = drvAudioStreamCaptureNonInterleaved(pThis, pStream, &cfCaptured);
1832 }
1833 else if (pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW)
1834 {
1835 rc = drvAudioStreamCaptureRaw(pThis, pStream, &cfCaptured);
1836 }
1837 else
1838 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
1839
1840 if (pThis->pHostDrvAudio->pfnStreamCaptureEnd)
1841 pThis->pHostDrvAudio->pfnStreamCaptureEnd(pThis->pHostDrvAudio, pStream->pvBackend);
1842
1843 if (RT_SUCCESS(rc))
1844 {
1845 Log3Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStream->szName, cfCaptured, rc));
1846
1847#ifdef VBOX_WITH_STATISTICS
1848 STAM_COUNTER_ADD(&pThis->Stats.TotalFramesIn, cfCaptured);
1849 STAM_COUNTER_ADD(&pStream->In.Stats.FramesCaptured, cfCaptured);
1850#endif
1851 }
1852 else if (RT_UNLIKELY(RT_FAILURE(rc)))
1853 {
1854 LogRel(("Audio: Capturing stream '%s' failed with %Rrc\n", pStream->szName, rc));
1855 }
1856
1857 } while (0);
1858
1859 if (pcFramesCaptured)
1860 *pcFramesCaptured = cfCaptured;
1861
1862 int rc2 = RTCritSectLeave(&pThis->CritSect);
1863 if (RT_SUCCESS(rc))
1864 rc = rc2;
1865
1866 if (RT_FAILURE(rc))
1867 LogFlowFuncLeaveRC(rc);
1868
1869 return rc;
1870}
1871
1872#ifdef VBOX_WITH_AUDIO_CALLBACKS
1873/**
1874 * Duplicates an audio callback.
1875 *
1876 * @returns Pointer to duplicated callback, or NULL on failure.
1877 * @param pCB Callback to duplicate.
1878 */
1879static PPDMAUDIOCBRECORD drvAudioCallbackDuplicate(PPDMAUDIOCBRECORD pCB)
1880{
1881 AssertPtrReturn(pCB, NULL);
1882
1883 PPDMAUDIOCBRECORD pCBCopy = (PPDMAUDIOCBRECORD)RTMemDup((void *)pCB, sizeof(PDMAUDIOCBRECORD));
1884 if (!pCBCopy)
1885 return NULL;
1886
1887 if (pCB->pvCtx)
1888 {
1889 pCBCopy->pvCtx = RTMemDup(pCB->pvCtx, pCB->cbCtx);
1890 if (!pCBCopy->pvCtx)
1891 {
1892 RTMemFree(pCBCopy);
1893 return NULL;
1894 }
1895
1896 pCBCopy->cbCtx = pCB->cbCtx;
1897 }
1898
1899 return pCBCopy;
1900}
1901
1902/**
1903 * Destroys a given callback.
1904 *
1905 * @param pCB Callback to destroy.
1906 */
1907static void drvAudioCallbackDestroy(PPDMAUDIOCBRECORD pCB)
1908{
1909 if (!pCB)
1910 return;
1911
1912 RTListNodeRemove(&pCB->Node);
1913 if (pCB->pvCtx)
1914 {
1915 Assert(pCB->cbCtx);
1916 RTMemFree(pCB->pvCtx);
1917 }
1918 RTMemFree(pCB);
1919}
1920
1921/**
1922 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnRegisterCallbacks}
1923 */
1924static DECLCALLBACK(int) drvAudioRegisterCallbacks(PPDMIAUDIOCONNECTOR pInterface,
1925 PPDMAUDIOCBRECORD paCallbacks, size_t cCallbacks)
1926{
1927 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1928 AssertPtrReturn(paCallbacks, VERR_INVALID_POINTER);
1929 AssertReturn(cCallbacks, VERR_INVALID_PARAMETER);
1930
1931 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
1932
1933 int rc = RTCritSectEnter(&pThis->CritSect);
1934 if (RT_FAILURE(rc))
1935 return rc;
1936
1937 for (size_t i = 0; i < cCallbacks; i++)
1938 {
1939 PPDMAUDIOCBRECORD pCB = drvAudioCallbackDuplicate(&paCallbacks[i]);
1940 if (!pCB)
1941 {
1942 rc = VERR_NO_MEMORY;
1943 break;
1944 }
1945
1946 switch (pCB->enmSource)
1947 {
1948 case PDMAUDIOCBSOURCE_DEVICE:
1949 {
1950 switch (pCB->Device.enmType)
1951 {
1952 case PDMAUDIODEVICECBTYPE_DATA_INPUT:
1953 RTListAppend(&pThis->In.lstCB, &pCB->Node);
1954 break;
1955
1956 case PDMAUDIODEVICECBTYPE_DATA_OUTPUT:
1957 RTListAppend(&pThis->Out.lstCB, &pCB->Node);
1958 break;
1959
1960 default:
1961 AssertMsgFailed(("Not supported\n"));
1962 break;
1963 }
1964
1965 break;
1966 }
1967
1968 default:
1969 AssertMsgFailed(("Not supported\n"));
1970 break;
1971 }
1972 }
1973
1974 /** @todo Undo allocations on error. */
1975
1976 int rc2 = RTCritSectLeave(&pThis->CritSect);
1977 if (RT_SUCCESS(rc))
1978 rc = rc2;
1979
1980 return rc;
1981}
1982#endif /* VBOX_WITH_AUDIO_CALLBACKS */
1983
1984#ifdef VBOX_WITH_AUDIO_CALLBACKS
1985/**
1986 * Backend callback implementation.
1987 *
1988 * Important: No calls back to the backend within this function, as the backend
1989 * might hold any locks / critical sections while executing this callback.
1990 * Will result in some ugly deadlocks (or at least locking order violations) then.
1991 *
1992 * @copydoc FNPDMHOSTAUDIOCALLBACK
1993 */
1994static DECLCALLBACK(int) drvAudioBackendCallback(PPDMDRVINS pDrvIns,
1995 PDMAUDIOBACKENDCBTYPE enmType, void *pvUser, size_t cbUser)
1996{
1997 AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
1998 RT_NOREF(pvUser, cbUser);
1999 /* pvUser and cbUser are optional. */
2000
2001 /* Get the upper driver (PDMIAUDIOCONNECTOR). */
2002 AssertPtr(pDrvIns->pUpBase);
2003 PPDMIAUDIOCONNECTOR pInterface = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIAUDIOCONNECTOR);
2004 AssertPtr(pInterface);
2005 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
2006
2007 int rc = RTCritSectEnter(&pThis->CritSect);
2008 AssertRCReturn(rc, rc);
2009
2010 LogFunc(("pThis=%p, enmType=%RU32, pvUser=%p, cbUser=%zu\n", pThis, enmType, pvUser, cbUser));
2011
2012 switch (enmType)
2013 {
2014 case PDMAUDIOBACKENDCBTYPE_DEVICES_CHANGED:
2015 LogRel(("Audio: Device configuration of driver '%s' has changed\n", pThis->szName));
2016 rc = drvAudioScheduleReInitInternal(pThis);
2017 break;
2018
2019 default:
2020 AssertMsgFailed(("Not supported\n"));
2021 break;
2022 }
2023
2024 int rc2 = RTCritSectLeave(&pThis->CritSect);
2025 if (RT_SUCCESS(rc))
2026 rc = rc2;
2027
2028 LogFlowFunc(("Returning %Rrc\n", rc));
2029 return rc;
2030}
2031#endif /* VBOX_WITH_AUDIO_CALLBACKS */
2032
2033#ifdef VBOX_WITH_AUDIO_ENUM
2034/**
2035 * Enumerates all host audio devices.
2036 * This functionality might not be implemented by all backends and will return VERR_NOT_SUPPORTED
2037 * if not being supported.
2038 *
2039 * @returns IPRT status code.
2040 * @param pThis Driver instance to be called.
2041 * @param fLog Whether to print the enumerated device to the release log or not.
2042 * @param pDevEnum Where to store the device enumeration.
2043 */
2044static int drvAudioDevicesEnumerateInternal(PDRVAUDIO pThis, bool fLog, PPDMAUDIODEVICEENUM pDevEnum)
2045{
2046 int rc;
2047
2048 /*
2049 * If the backend supports it, do a device enumeration.
2050 */
2051 if (pThis->pHostDrvAudio->pfnGetDevices)
2052 {
2053 PDMAUDIODEVICEENUM DevEnum;
2054 rc = pThis->pHostDrvAudio->pfnGetDevices(pThis->pHostDrvAudio, &DevEnum);
2055 if (RT_SUCCESS(rc))
2056 {
2057 if (fLog)
2058 LogRel(("Audio: Found %RU16 devices for driver '%s'\n", DevEnum.cDevices, pThis->szName));
2059
2060 PPDMAUDIODEVICE pDev;
2061 RTListForEach(&DevEnum.lstDevices, pDev, PDMAUDIODEVICE, Node)
2062 {
2063 if (fLog)
2064 {
2065 char *pszFlags = DrvAudioHlpAudDevFlagsToStrA(pDev->fFlags);
2066
2067 LogRel(("Audio: Device '%s':\n", pDev->szName));
2068 LogRel(("Audio: \tUsage = %s\n", DrvAudioHlpAudDirToStr(pDev->enmUsage)));
2069 LogRel(("Audio: \tFlags = %s\n", pszFlags ? pszFlags : "<NONE>"));
2070 LogRel(("Audio: \tInput channels = %RU8\n", pDev->cMaxInputChannels));
2071 LogRel(("Audio: \tOutput channels = %RU8\n", pDev->cMaxOutputChannels));
2072
2073 if (pszFlags)
2074 RTStrFree(pszFlags);
2075 }
2076 }
2077
2078 if (pDevEnum)
2079 rc = DrvAudioHlpDeviceEnumCopy(pDevEnum, &DevEnum);
2080
2081 DrvAudioHlpDeviceEnumFree(&DevEnum);
2082 }
2083 else
2084 {
2085 if (fLog)
2086 LogRel(("Audio: Device enumeration for driver '%s' failed with %Rrc\n", pThis->szName, rc));
2087 /* Not fatal. */
2088 }
2089 }
2090 else
2091 {
2092 rc = VERR_NOT_SUPPORTED;
2093
2094 if (fLog)
2095 LogRel2(("Audio: Host driver '%s' does not support audio device enumeration, skipping\n", pThis->szName));
2096 }
2097
2098 LogFunc(("Returning %Rrc\n", rc));
2099 return rc;
2100}
2101#endif /* VBOX_WITH_AUDIO_ENUM */
2102
2103/**
2104 * Initializes the host backend and queries its initial configuration.
2105 * If the host backend fails, VERR_AUDIO_BACKEND_INIT_FAILED will be returned.
2106 *
2107 * Note: As this routine is called when attaching to the device LUN in the
2108 * device emulation, we either check for success or VERR_AUDIO_BACKEND_INIT_FAILED.
2109 * Everything else is considered as fatal and must be handled separately in
2110 * the device emulation!
2111 *
2112 * @return IPRT status code.
2113 * @param pThis Driver instance to be called.
2114 * @param pCfgHandle CFGM configuration handle to use for this driver.
2115 */
2116static int drvAudioHostInit(PDRVAUDIO pThis, PCFGMNODE pCfgHandle)
2117{
2118 /* pCfgHandle is optional. */
2119 NOREF(pCfgHandle);
2120 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
2121
2122 LogFlowFuncEnter();
2123
2124 AssertPtr(pThis->pHostDrvAudio);
2125 int rc = pThis->pHostDrvAudio->pfnInit(pThis->pHostDrvAudio);
2126 if (RT_FAILURE(rc))
2127 {
2128 LogRel(("Audio: Initialization of host driver '%s' failed with %Rrc\n", pThis->szName, rc));
2129 return VERR_AUDIO_BACKEND_INIT_FAILED;
2130 }
2131
2132 /*
2133 * Get the backend configuration.
2134 */
2135 rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, &pThis->BackendCfg);
2136 if (RT_FAILURE(rc))
2137 {
2138 LogRel(("Audio: Getting configuration for driver '%s' failed with %Rrc\n", pThis->szName, rc));
2139 return VERR_AUDIO_BACKEND_INIT_FAILED;
2140 }
2141
2142 pThis->In.cStreamsFree = pThis->BackendCfg.cMaxStreamsIn;
2143 pThis->Out.cStreamsFree = pThis->BackendCfg.cMaxStreamsOut;
2144
2145 LogFlowFunc(("cStreamsFreeIn=%RU8, cStreamsFreeOut=%RU8\n", pThis->In.cStreamsFree, pThis->Out.cStreamsFree));
2146
2147 LogRel2(("Audio: Host driver '%s' supports %RU32 input streams and %RU32 output streams at once\n",
2148 pThis->szName,
2149 /* Clamp for logging. Unlimited streams are defined by UINT32_MAX. */
2150 RT_MIN(64, pThis->In.cStreamsFree), RT_MIN(64, pThis->Out.cStreamsFree)));
2151
2152#ifdef VBOX_WITH_AUDIO_ENUM
2153 int rc2 = drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */);
2154 if (rc2 != VERR_NOT_SUPPORTED) /* Some backends don't implement device enumeration. */
2155 AssertRC(rc2);
2156
2157 RT_NOREF(rc2);
2158 /* Ignore rc. */
2159#endif
2160
2161#ifdef VBOX_WITH_AUDIO_CALLBACKS
2162 /*
2163 * If the backend supports it, offer a callback to this connector.
2164 */
2165 if (pThis->pHostDrvAudio->pfnSetCallback)
2166 {
2167 rc2 = pThis->pHostDrvAudio->pfnSetCallback(pThis->pHostDrvAudio, drvAudioBackendCallback);
2168 if (RT_FAILURE(rc2))
2169 LogRel(("Audio: Error registering callback for host driver '%s', rc=%Rrc\n", pThis->szName, rc2));
2170 /* Not fatal. */
2171 }
2172#endif
2173
2174 LogFlowFuncLeave();
2175 return VINF_SUCCESS;
2176}
2177
2178/**
2179 * Handles state changes for all audio streams.
2180 *
2181 * @param pDrvIns Pointer to driver instance.
2182 * @param enmCmd Stream command to set for all streams.
2183 */
2184static void drvAudioStateHandler(PPDMDRVINS pDrvIns, PDMAUDIOSTREAMCMD enmCmd)
2185{
2186 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
2187 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
2188
2189 LogFlowFunc(("enmCmd=%s\n", DrvAudioHlpStreamCmdToStr(enmCmd)));
2190
2191 int rc2 = RTCritSectEnter(&pThis->CritSect);
2192 AssertRC(rc2);
2193
2194 if (pThis->pHostDrvAudio)
2195 {
2196 PPDMAUDIOSTREAM pStream;
2197 RTListForEach(&pThis->lstStreams, pStream, PDMAUDIOSTREAM, Node)
2198 drvAudioStreamControlInternal(pThis, pStream, enmCmd);
2199 }
2200
2201 rc2 = RTCritSectLeave(&pThis->CritSect);
2202 AssertRC(rc2);
2203}
2204
2205/**
2206 * Retrieves an audio configuration from the specified CFGM node.
2207 *
2208 * @return VBox status code.
2209 * @param pThis Driver instance to be called.
2210 * @param pCfg Where to store the retrieved audio configuration to.
2211 * @param pNode Where to get the audio configuration from.
2212 */
2213static int drvAudioGetCfgFromCFGM(PDRVAUDIO pThis, PDRVAUDIOCFG pCfg, PCFGMNODE pNode)
2214{
2215 RT_NOREF(pThis);
2216
2217 /* Debug stuff. */
2218 CFGMR3QueryBoolDef(pNode, "DebugEnabled", &pCfg->Dbg.fEnabled, false);
2219 int rc2 = CFGMR3QueryString(pNode, "DebugPathOut", pCfg->Dbg.szPathOut, sizeof(pCfg->Dbg.szPathOut));
2220 if ( RT_FAILURE(rc2)
2221 || !strlen(pCfg->Dbg.szPathOut))
2222 {
2223 RTStrPrintf(pCfg->Dbg.szPathOut, sizeof(pCfg->Dbg.szPathOut), VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH);
2224 }
2225
2226 if (pCfg->Dbg.fEnabled)
2227 LogRel(("Audio: Debugging for driver '%s' enabled (audio data written to '%s')\n", pThis->szName, pCfg->Dbg.szPathOut));
2228
2229 /* Buffering stuff. */
2230 CFGMR3QueryU32Def(pNode, "PeriodSizeMs", &pCfg->uPeriodSizeMs, 0);
2231 CFGMR3QueryU32Def(pNode, "BufferSizeMs", &pCfg->uBufferSizeMs, 0);
2232 CFGMR3QueryU32Def(pNode, "PreBufferSizeMs", &pCfg->uPreBufSizeMs, UINT32_MAX /* No custom value set */);
2233
2234 LogFunc(("pCfg=%p, uPeriodSizeMs=%RU32, uBufferSizeMs=%RU32, uPreBufSizeMs=%RU32\n",
2235 pCfg, pCfg->uPeriodSizeMs, pCfg->uBufferSizeMs, pCfg->uPreBufSizeMs));
2236
2237 return VINF_SUCCESS;
2238}
2239
2240/**
2241 * Intializes an audio driver instance.
2242 *
2243 * @returns IPRT status code.
2244 * @param pDrvIns Pointer to driver instance.
2245 * @param pCfgHandle CFGM handle to use for configuration.
2246 */
2247static int drvAudioInit(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
2248{
2249 AssertPtrReturn(pCfgHandle, VERR_INVALID_POINTER);
2250 AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
2251
2252 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
2253
2254 int rc = RTCritSectInit(&pThis->CritSect);
2255 AssertRCReturn(rc, rc);
2256
2257 /*
2258 * Configure driver from CFGM.
2259 */
2260#ifdef DEBUG
2261 CFGMR3Dump(pCfgHandle);
2262#endif
2263
2264 pThis->fTerminate = false;
2265 pThis->pCFGMNode = pCfgHandle;
2266
2267 int rc2 = CFGMR3QueryString(pThis->pCFGMNode, "DriverName", pThis->szName, sizeof(pThis->szName));
2268 if (RT_FAILURE(rc2))
2269 RTStrPrintf(pThis->szName, sizeof(pThis->szName), "Untitled");
2270
2271 /* By default we don't enable anything if wrongly / not set-up. */
2272 CFGMR3QueryBoolDef(pThis->pCFGMNode, "InputEnabled", &pThis->In.fEnabled, false);
2273 CFGMR3QueryBoolDef(pThis->pCFGMNode, "OutputEnabled", &pThis->Out.fEnabled, false);
2274
2275 LogRel2(("Audio: Verbose logging for driver '%s' enabled\n", pThis->szName));
2276
2277 LogRel2(("Audio: Initial status for driver '%s' is: input is %s, output is %s\n",
2278 pThis->szName, pThis->In.fEnabled ? "enabled" : "disabled", pThis->Out.fEnabled ? "enabled" : "disabled"));
2279
2280 /*
2281 * Load configurations.
2282 */
2283 rc = drvAudioGetCfgFromCFGM(pThis, &pThis->In.Cfg, pThis->pCFGMNode);
2284 if (RT_SUCCESS(rc))
2285 rc = drvAudioGetCfgFromCFGM(pThis, &pThis->Out.Cfg, pThis->pCFGMNode);
2286
2287 LogFunc(("[%s] rc=%Rrc\n", pThis->szName, rc));
2288 return rc;
2289}
2290
2291/**
2292 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRead}
2293 */
2294static DECLCALLBACK(int) drvAudioStreamRead(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream,
2295 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
2296{
2297 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
2298 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
2299
2300 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2301 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2302 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
2303 /* pcbRead is optional. */
2304
2305 AssertMsg(pStream->enmDir == PDMAUDIODIR_IN,
2306 ("Stream '%s' is not an input stream and therefore cannot be read from (direction is 0x%x)\n",
2307 pStream->szName, pStream->enmDir));
2308
2309 uint32_t cbReadTotal = 0;
2310
2311 int rc = RTCritSectEnter(&pThis->CritSect);
2312 if (RT_FAILURE(rc))
2313 return rc;
2314
2315 do
2316 {
2317 if ( !pThis->In.fEnabled
2318 || !DrvAudioHlpStreamStatusCanRead(pStream->fStatus))
2319 {
2320 rc = VERR_AUDIO_STREAM_NOT_READY;
2321 break;
2322 }
2323
2324 /*
2325 * Read from the parent buffer (that is, the guest buffer) which
2326 * should have the audio data in the format the guest needs.
2327 */
2328 uint32_t cfReadTotal = 0;
2329
2330 const uint32_t cfBuf = AUDIOMIXBUF_B2F(&pStream->Guest.MixBuf, cbBuf);
2331
2332 uint32_t cfToRead = RT_MIN(cfBuf, AudioMixBufLive(&pStream->Guest.MixBuf));
2333 while (cfToRead)
2334 {
2335 uint32_t cfRead;
2336 rc = AudioMixBufAcquireReadBlock(&pStream->Guest.MixBuf,
2337 (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadTotal),
2338 AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfToRead), &cfRead);
2339 if (RT_FAILURE(rc))
2340 break;
2341
2342#ifdef VBOX_WITH_STATISTICS
2343 const uint32_t cbRead = AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfRead);
2344
2345 STAM_COUNTER_ADD(&pThis->Stats.TotalBytesRead, cbRead);
2346 STAM_COUNTER_ADD(&pStream->In.Stats.BytesTotalRead, cbRead);
2347#endif
2348 Assert(cfToRead >= cfRead);
2349 cfToRead -= cfRead;
2350
2351 cfReadTotal += cfRead;
2352
2353 AudioMixBufReleaseReadBlock(&pStream->Guest.MixBuf, cfRead);
2354 }
2355
2356 if (cfReadTotal)
2357 {
2358 if (pThis->In.Cfg.Dbg.fEnabled)
2359 DrvAudioHlpFileWrite(pStream->In.Dbg.pFileStreamRead,
2360 pvBuf, AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadTotal), 0 /* fFlags */);
2361
2362 AudioMixBufFinish(&pStream->Guest.MixBuf, cfReadTotal);
2363 }
2364
2365 /* If we were not able to read as much data as requested, fill up the returned
2366 * data with silence.
2367 *
2368 * This is needed to keep the device emulation DMA transfers up and running at a constant rate. */
2369 if (cfReadTotal < cfBuf)
2370 {
2371 Log3Func(("[%s] Filling in silence (%RU64ms / %RU64ms)\n", pStream->szName,
2372 DrvAudioHlpFramesToMilli(cfBuf - cfReadTotal, &pStream->Guest.Cfg.Props),
2373 DrvAudioHlpFramesToMilli(cfBuf, &pStream->Guest.Cfg.Props)));
2374
2375 DrvAudioHlpClearBuf(&pStream->Guest.Cfg.Props,
2376 (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadTotal),
2377 AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfBuf - cfReadTotal),
2378 cfBuf - cfReadTotal);
2379
2380 cfReadTotal = cfBuf;
2381 }
2382
2383 cbReadTotal = AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadTotal);
2384
2385 pStream->tsLastReadWrittenNs = RTTimeNanoTS();
2386
2387 Log3Func(("[%s] fEnabled=%RTbool, cbReadTotal=%RU32, rc=%Rrc\n", pStream->szName, pThis->In.fEnabled, cbReadTotal, rc));
2388
2389 } while (0);
2390
2391
2392 int rc2 = RTCritSectLeave(&pThis->CritSect);
2393 if (RT_SUCCESS(rc))
2394 rc = rc2;
2395
2396 if (RT_SUCCESS(rc))
2397 {
2398 if (pcbRead)
2399 *pcbRead = cbReadTotal;
2400 }
2401
2402 return rc;
2403}
2404
2405/**
2406 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCreate}
2407 */
2408static DECLCALLBACK(int) drvAudioStreamCreate(PPDMIAUDIOCONNECTOR pInterface,
2409 PPDMAUDIOSTREAMCFG pCfgHost, PPDMAUDIOSTREAMCFG pCfgGuest,
2410 PPDMAUDIOSTREAM *ppStream)
2411{
2412 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2413 AssertPtrReturn(pCfgHost, VERR_INVALID_POINTER);
2414 AssertPtrReturn(pCfgGuest, VERR_INVALID_POINTER);
2415 AssertPtrReturn(ppStream, VERR_INVALID_POINTER);
2416
2417 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
2418
2419 int rc = RTCritSectEnter(&pThis->CritSect);
2420 if (RT_FAILURE(rc))
2421 return rc;
2422
2423 LogFlowFunc(("Host=%s, Guest=%s\n", pCfgHost->szName, pCfgGuest->szName));
2424#ifdef DEBUG
2425 DrvAudioHlpStreamCfgPrint(pCfgHost);
2426 DrvAudioHlpStreamCfgPrint(pCfgGuest);
2427#endif
2428
2429 PPDMAUDIOSTREAM pStream = NULL;
2430
2431#define RC_BREAK(x) { rc = x; break; }
2432
2433 do
2434 {
2435 if ( !DrvAudioHlpStreamCfgIsValid(pCfgHost)
2436 || !DrvAudioHlpStreamCfgIsValid(pCfgGuest))
2437 {
2438 RC_BREAK(VERR_INVALID_PARAMETER);
2439 }
2440
2441 /* Make sure that both configurations actually intend the same thing. */
2442 if (pCfgHost->enmDir != pCfgGuest->enmDir)
2443 {
2444 AssertMsgFailed(("Stream configuration directions do not match\n"));
2445 RC_BREAK(VERR_INVALID_PARAMETER);
2446 }
2447
2448 /* Note: cbHstStrm will contain the size of the data the backend needs to operate on. */
2449 size_t cbHstStrm;
2450 if (pCfgHost->enmDir == PDMAUDIODIR_IN)
2451 {
2452 if (!pThis->In.cStreamsFree)
2453 {
2454 LogFlowFunc(("Maximum number of host input streams reached\n"));
2455 RC_BREAK(VERR_AUDIO_NO_FREE_INPUT_STREAMS);
2456 }
2457
2458 cbHstStrm = pThis->BackendCfg.cbStreamIn;
2459 }
2460 else /* Out */
2461 {
2462 if (!pThis->Out.cStreamsFree)
2463 {
2464 LogFlowFunc(("Maximum number of host output streams reached\n"));
2465 RC_BREAK(VERR_AUDIO_NO_FREE_OUTPUT_STREAMS);
2466 }
2467
2468 cbHstStrm = pThis->BackendCfg.cbStreamOut;
2469 }
2470
2471 /*
2472 * Allocate and initialize common state.
2473 */
2474
2475 pStream = (PPDMAUDIOSTREAM)RTMemAllocZ(sizeof(PDMAUDIOSTREAM));
2476 AssertPtrBreakStmt(pStream, rc = VERR_NO_MEMORY);
2477
2478 /* Retrieve host driver name for easier identification. */
2479 AssertPtr(pThis->pHostDrvAudio);
2480 PPDMDRVINS pDrvAudioInst = PDMIBASE_2_PDMDRV(pThis->pDrvIns->pDownBase);
2481 AssertPtr(pDrvAudioInst);
2482 AssertPtr(pDrvAudioInst->pReg);
2483
2484 Assert(pDrvAudioInst->pReg->szName[0] != '\0');
2485 RTStrPrintf(pStream->szName, RT_ELEMENTS(pStream->szName), "[%s] %s",
2486 pDrvAudioInst->pReg->szName[0] != '\0' ? pDrvAudioInst->pReg->szName : "Untitled",
2487 pCfgHost->szName[0] != '\0' ? pCfgHost->szName : "<Untitled>");
2488
2489 pStream->enmDir = pCfgHost->enmDir;
2490
2491 /*
2492 * Allocate and init backend-specific data.
2493 */
2494
2495 if (cbHstStrm) /* High unlikely that backends do not have an own space for data, but better check. */
2496 {
2497 pStream->pvBackend = RTMemAllocZ(cbHstStrm);
2498 AssertPtrBreakStmt(pStream->pvBackend, rc = VERR_NO_MEMORY);
2499
2500 pStream->cbBackend = cbHstStrm;
2501 }
2502
2503 /*
2504 * Try to init the rest.
2505 */
2506
2507 rc = drvAudioStreamInitInternal(pThis, pStream, pCfgHost, pCfgGuest);
2508 if (RT_FAILURE(rc))
2509 break;
2510
2511 } while (0);
2512
2513#undef RC_BREAK
2514
2515 if (RT_FAILURE(rc))
2516 {
2517 if (pStream)
2518 {
2519 int rc2 = drvAudioStreamUninitInternal(pThis, pStream);
2520 if (RT_SUCCESS(rc2))
2521 {
2522 drvAudioStreamFree(pStream);
2523 pStream = NULL;
2524 }
2525 }
2526 }
2527 else
2528 {
2529 /* Append the stream to our stream list. */
2530 RTListAppend(&pThis->lstStreams, &pStream->Node);
2531
2532 /* Set initial reference counts. */
2533 pStream->cRefs = 1;
2534
2535 if (pCfgHost->enmDir == PDMAUDIODIR_IN)
2536 {
2537 if (pThis->In.Cfg.Dbg.fEnabled)
2538 {
2539 char szFile[RTPATH_MAX + 1];
2540
2541 int rc2 = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), pThis->In.Cfg.Dbg.szPathOut, "CaptureNonInterleaved",
2542 pThis->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE);
2543 if (RT_SUCCESS(rc2))
2544 {
2545 rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE,
2546 &pStream->In.Dbg.pFileCaptureNonInterleaved);
2547 if (RT_SUCCESS(rc2))
2548 rc2 = DrvAudioHlpFileOpen(pStream->In.Dbg.pFileCaptureNonInterleaved, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS,
2549 &pStream->Host.Cfg.Props);
2550 }
2551
2552 if (RT_SUCCESS(rc2))
2553 {
2554 rc2 = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), pThis->In.Cfg.Dbg.szPathOut, "StreamRead",
2555 pThis->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE);
2556 if (RT_SUCCESS(rc2))
2557 {
2558 rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE,
2559 &pStream->In.Dbg.pFileStreamRead);
2560 if (RT_SUCCESS(rc2))
2561 rc2 = DrvAudioHlpFileOpen(pStream->In.Dbg.pFileStreamRead, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS,
2562 &pStream->Host.Cfg.Props);
2563 }
2564 }
2565 }
2566
2567 if (pThis->In.cStreamsFree)
2568 pThis->In.cStreamsFree--;
2569 }
2570 else /* Out */
2571 {
2572 if (pThis->Out.Cfg.Dbg.fEnabled)
2573 {
2574 char szFile[RTPATH_MAX + 1];
2575
2576 int rc2 = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), pThis->Out.Cfg.Dbg.szPathOut, "PlayNonInterleaved",
2577 pThis->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE);
2578 if (RT_SUCCESS(rc2))
2579 {
2580 rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE,
2581 &pStream->Out.Dbg.pFilePlayNonInterleaved);
2582 if (RT_SUCCESS(rc2))
2583 rc = DrvAudioHlpFileOpen(pStream->Out.Dbg.pFilePlayNonInterleaved, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS,
2584 &pStream->Host.Cfg.Props);
2585 }
2586
2587 if (RT_SUCCESS(rc2))
2588 {
2589 rc2 = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), pThis->Out.Cfg.Dbg.szPathOut, "StreamWrite",
2590 pThis->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE);
2591 if (RT_SUCCESS(rc2))
2592 {
2593 rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE,
2594 &pStream->Out.Dbg.pFileStreamWrite);
2595 if (RT_SUCCESS(rc2))
2596 rc2 = DrvAudioHlpFileOpen(pStream->Out.Dbg.pFileStreamWrite, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS,
2597 &pStream->Host.Cfg.Props);
2598 }
2599 }
2600 }
2601
2602 if (pThis->Out.cStreamsFree)
2603 pThis->Out.cStreamsFree--;
2604 }
2605
2606#ifdef VBOX_WITH_STATISTICS
2607 STAM_COUNTER_ADD(&pThis->Stats.TotalStreamsCreated, 1);
2608#endif
2609 *ppStream = pStream;
2610 }
2611
2612 int rc2 = RTCritSectLeave(&pThis->CritSect);
2613 if (RT_SUCCESS(rc))
2614 rc = rc2;
2615
2616 LogFlowFuncLeaveRC(rc);
2617 return rc;
2618}
2619
2620/**
2621 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnEnable}
2622 */
2623static DECLCALLBACK(int) drvAudioEnable(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir, bool fEnable)
2624{
2625 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2626
2627 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
2628
2629 int rc = RTCritSectEnter(&pThis->CritSect);
2630 if (RT_FAILURE(rc))
2631 return rc;
2632
2633 bool *pfEnabled;
2634 if (enmDir == PDMAUDIODIR_IN)
2635 pfEnabled = &pThis->In.fEnabled;
2636 else if (enmDir == PDMAUDIODIR_OUT)
2637 pfEnabled = &pThis->Out.fEnabled;
2638 else
2639 AssertFailedReturn(VERR_INVALID_PARAMETER);
2640
2641 if (fEnable != *pfEnabled)
2642 {
2643 LogRel(("Audio: %s %s for driver '%s'\n",
2644 fEnable ? "Enabling" : "Disabling", enmDir == PDMAUDIODIR_IN ? "input" : "output", pThis->szName));
2645
2646 PPDMAUDIOSTREAM pStream;
2647 RTListForEach(&pThis->lstStreams, pStream, PDMAUDIOSTREAM, Node)
2648 {
2649 if (pStream->enmDir != enmDir) /* Skip unwanted streams. */
2650 continue;
2651
2652 int rc2 = drvAudioStreamControlInternal(pThis, pStream,
2653 fEnable ? PDMAUDIOSTREAMCMD_ENABLE : PDMAUDIOSTREAMCMD_DISABLE);
2654 if (RT_FAILURE(rc2))
2655 LogRel(("Audio: Failed to %s %s stream '%s', rc=%Rrc\n",
2656 fEnable ? "enable" : "disable", enmDir == PDMAUDIODIR_IN ? "input" : "output", pStream->szName, rc2));
2657
2658 if (RT_SUCCESS(rc))
2659 rc = rc2;
2660
2661 /* Keep going. */
2662 }
2663
2664 *pfEnabled = fEnable;
2665 }
2666
2667 int rc3 = RTCritSectLeave(&pThis->CritSect);
2668 if (RT_SUCCESS(rc))
2669 rc = rc3;
2670
2671 LogFlowFuncLeaveRC(rc);
2672 return rc;
2673}
2674
2675/**
2676 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnIsEnabled}
2677 */
2678static DECLCALLBACK(bool) drvAudioIsEnabled(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir)
2679{
2680 AssertPtrReturn(pInterface, false);
2681
2682 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
2683
2684 int rc2 = RTCritSectEnter(&pThis->CritSect);
2685 if (RT_FAILURE(rc2))
2686 return false;
2687
2688 bool *pfEnabled;
2689 if (enmDir == PDMAUDIODIR_IN)
2690 pfEnabled = &pThis->In.fEnabled;
2691 else if (enmDir == PDMAUDIODIR_OUT)
2692 pfEnabled = &pThis->Out.fEnabled;
2693 else
2694 AssertFailedReturn(false);
2695
2696 const bool fIsEnabled = *pfEnabled;
2697
2698 rc2 = RTCritSectLeave(&pThis->CritSect);
2699 AssertRC(rc2);
2700
2701 return fIsEnabled;
2702}
2703
2704/**
2705 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetConfig}
2706 */
2707static DECLCALLBACK(int) drvAudioGetConfig(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOBACKENDCFG pCfg)
2708{
2709 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2710 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
2711
2712 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
2713
2714 int rc = RTCritSectEnter(&pThis->CritSect);
2715 if (RT_FAILURE(rc))
2716 return rc;
2717
2718 if (pThis->pHostDrvAudio)
2719 {
2720 if (pThis->pHostDrvAudio->pfnGetConfig)
2721 rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, pCfg);
2722 else
2723 rc = VERR_NOT_SUPPORTED;
2724 }
2725 else
2726 rc = VERR_PDM_NO_ATTACHED_DRIVER;
2727
2728 int rc2 = RTCritSectLeave(&pThis->CritSect);
2729 if (RT_SUCCESS(rc))
2730 rc = rc2;
2731
2732 LogFlowFuncLeaveRC(rc);
2733 return rc;
2734}
2735
2736/**
2737 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetStatus}
2738 */
2739static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioGetStatus(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir)
2740{
2741 AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
2742
2743 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
2744
2745 PDMAUDIOBACKENDSTS backendSts = PDMAUDIOBACKENDSTS_UNKNOWN;
2746
2747 int rc = RTCritSectEnter(&pThis->CritSect);
2748 if (RT_SUCCESS(rc))
2749 {
2750 if (pThis->pHostDrvAudio)
2751 {
2752 if (pThis->pHostDrvAudio->pfnGetStatus)
2753 backendSts = pThis->pHostDrvAudio->pfnGetStatus(pThis->pHostDrvAudio, enmDir);
2754 }
2755 else
2756 backendSts = PDMAUDIOBACKENDSTS_NOT_ATTACHED;
2757
2758 int rc2 = RTCritSectLeave(&pThis->CritSect);
2759 if (RT_SUCCESS(rc))
2760 rc = rc2;
2761 }
2762
2763 LogFlowFuncLeaveRC(rc);
2764 return backendSts;
2765}
2766
2767/**
2768 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetReadable}
2769 */
2770static DECLCALLBACK(uint32_t) drvAudioStreamGetReadable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
2771{
2772 AssertPtrReturn(pInterface, 0);
2773 AssertPtrReturn(pStream, 0);
2774
2775 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
2776
2777 int rc2 = RTCritSectEnter(&pThis->CritSect);
2778 AssertRC(rc2);
2779
2780 AssertMsg(pStream->enmDir == PDMAUDIODIR_IN, ("Can't read from a non-input stream\n"));
2781
2782 uint32_t cbReadable = 0;
2783
2784 if ( pThis->pHostDrvAudio
2785 && DrvAudioHlpStreamStatusCanRead(pStream->fStatus))
2786 {
2787 const uint32_t cfReadable = AudioMixBufLive(&pStream->Guest.MixBuf);
2788
2789 cbReadable = AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadable);
2790
2791 if (!cbReadable)
2792 {
2793 /*
2794 * If nothing is readable, check if the stream on the backend side is ready to be read from.
2795 * If it isn't, return the number of bytes readable since the last read from this stream.
2796 *
2797 * This is needed for backends (e.g. VRDE) which do not provide any input data in certain
2798 * situations, but the device emulation needs input data to keep the DMA transfers moving.
2799 * Reading the actual data from a stream then will return silence then.
2800 */
2801 if (!DrvAudioHlpStreamStatusCanRead(
2802 pThis->pHostDrvAudio->pfnStreamGetStatus(pThis->pHostDrvAudio, pStream->pvBackend)))
2803 {
2804 cbReadable = DrvAudioHlpNanoToBytes(RTTimeNanoTS() - pStream->tsLastReadWrittenNs,
2805 &pStream->Host.Cfg.Props);
2806 Log3Func(("[%s] Backend stream not ready, returning silence\n", pStream->szName));
2807 }
2808 }
2809
2810 /* Make sure to align the readable size to the guest's frame size. */
2811 cbReadable = DrvAudioHlpBytesAlign(cbReadable, &pStream->Guest.Cfg.Props);
2812 }
2813
2814 Log3Func(("[%s] cbReadable=%RU32 (%RU64ms)\n",
2815 pStream->szName, cbReadable, DrvAudioHlpBytesToMilli(cbReadable, &pStream->Host.Cfg.Props)));
2816
2817 rc2 = RTCritSectLeave(&pThis->CritSect);
2818 AssertRC(rc2);
2819
2820 /* Return bytes instead of audio frames. */
2821 return cbReadable;
2822}
2823
2824/**
2825 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetWritable}
2826 */
2827static DECLCALLBACK(uint32_t) drvAudioStreamGetWritable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
2828{
2829 AssertPtrReturn(pInterface, 0);
2830 AssertPtrReturn(pStream, 0);
2831
2832 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
2833
2834 int rc2 = RTCritSectEnter(&pThis->CritSect);
2835 AssertRC(rc2);
2836
2837 AssertMsg(pStream->enmDir == PDMAUDIODIR_OUT, ("Can't write to a non-output stream\n"));
2838
2839 uint32_t cbWritable = 0;
2840
2841 /* Note: We don't propage the backend stream's status to the outside -- it's the job of this
2842 * audio connector to make sense of it. */
2843 if (DrvAudioHlpStreamStatusCanWrite(pStream->fStatus))
2844 {
2845 cbWritable = AudioMixBufFreeBytes(&pStream->Host.MixBuf);
2846
2847 /* Make sure to align the writable size to the host's frame size. */
2848 cbWritable = DrvAudioHlpBytesAlign(cbWritable, &pStream->Host.Cfg.Props);
2849 }
2850
2851 Log3Func(("[%s] cbWritable=%RU32 (%RU64ms)\n",
2852 pStream->szName, cbWritable, DrvAudioHlpBytesToMilli(cbWritable, &pStream->Host.Cfg.Props)));
2853
2854 rc2 = RTCritSectLeave(&pThis->CritSect);
2855 AssertRC(rc2);
2856
2857 return cbWritable;
2858}
2859
2860/**
2861 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetStatus}
2862 */
2863static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvAudioStreamGetStatus(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
2864{
2865 AssertPtrReturn(pInterface, false);
2866
2867 if (!pStream)
2868 return PDMAUDIOSTREAMSTS_FLAG_NONE;
2869
2870 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
2871
2872 int rc2 = RTCritSectEnter(&pThis->CritSect);
2873 AssertRC(rc2);
2874
2875 PDMAUDIOSTREAMSTS stsStream = pStream->fStatus;
2876
2877#ifdef LOG_ENABLED
2878 char *pszStreamSts = dbgAudioStreamStatusToStr(stsStream);
2879 Log3Func(("[%s] %s\n", pStream->szName, pszStreamSts));
2880 RTStrFree(pszStreamSts);
2881#endif
2882
2883 rc2 = RTCritSectLeave(&pThis->CritSect);
2884 AssertRC(rc2);
2885
2886 return stsStream;
2887}
2888
2889/**
2890 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamSetVolume}
2891 */
2892static DECLCALLBACK(int) drvAudioStreamSetVolume(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, PPDMAUDIOVOLUME pVol)
2893{
2894 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2895 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2896 AssertPtrReturn(pVol, VERR_INVALID_POINTER);
2897
2898 LogFlowFunc(("[%s] volL=%RU32, volR=%RU32, fMute=%RTbool\n", pStream->szName, pVol->uLeft, pVol->uRight, pVol->fMuted));
2899
2900 AudioMixBufSetVolume(&pStream->Guest.MixBuf, pVol);
2901 AudioMixBufSetVolume(&pStream->Host.MixBuf, pVol);
2902
2903 return VINF_SUCCESS;
2904}
2905
2906/**
2907 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamDestroy}
2908 */
2909static DECLCALLBACK(int) drvAudioStreamDestroy(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
2910{
2911 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2912 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2913
2914 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
2915
2916 int rc = RTCritSectEnter(&pThis->CritSect);
2917 AssertRC(rc);
2918
2919 LogRel2(("Audio: Destroying stream '%s'\n", pStream->szName));
2920
2921 LogFlowFunc(("[%s] cRefs=%RU32\n", pStream->szName, pStream->cRefs));
2922 if (pStream->cRefs > 1)
2923 rc = VERR_WRONG_ORDER;
2924
2925 if (RT_SUCCESS(rc))
2926 {
2927 rc = drvAudioStreamUninitInternal(pThis, pStream);
2928 if (RT_FAILURE(rc))
2929 LogRel(("Audio: Uninitializing stream '%s' failed with %Rrc\n", pStream->szName, rc));
2930 }
2931
2932 if (RT_SUCCESS(rc))
2933 {
2934 if (pStream->enmDir == PDMAUDIODIR_IN)
2935 {
2936 pThis->In.cStreamsFree++;
2937 }
2938 else /* Out */
2939 {
2940 pThis->Out.cStreamsFree++;
2941 }
2942
2943 RTListNodeRemove(&pStream->Node);
2944
2945 drvAudioStreamFree(pStream);
2946 pStream = NULL;
2947 }
2948
2949 int rc2 = RTCritSectLeave(&pThis->CritSect);
2950 if (RT_SUCCESS(rc))
2951 rc = rc2;
2952
2953 LogFlowFuncLeaveRC(rc);
2954 return rc;
2955}
2956
2957/**
2958 * Creates an audio stream on the backend side.
2959 *
2960 * @returns IPRT status code.
2961 * @param pThis Pointer to driver instance.
2962 * @param pStream Audio stream to create the backend side for.
2963 * @param pCfgReq Requested audio stream configuration to use for stream creation.
2964 * @param pCfgAcq Acquired audio stream configuration returned by the backend.
2965 */
2966static int drvAudioStreamCreateInternalBackend(PDRVAUDIO pThis,
2967 PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
2968{
2969 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
2970 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2971 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
2972 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
2973
2974 AssertMsg((pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_INITIALIZED) == 0,
2975 ("Stream '%s' already initialized in backend\n", pStream->szName));
2976
2977 /* Get the right configuration for the stream to be created. */
2978 PDRVAUDIOCFG pDrvCfg = pCfgReq->enmDir == PDMAUDIODIR_IN ? &pThis->In.Cfg : &pThis->Out.Cfg;
2979
2980 /* Fill in the tweakable parameters into the requested host configuration.
2981 * All parameters in principle can be changed and returned by the backend via the acquired configuration. */
2982
2983 /*
2984 * Period size
2985 */
2986 if (pDrvCfg->uPeriodSizeMs)
2987 pCfgReq->Backend.cfPeriod = DrvAudioHlpMilliToFrames(pDrvCfg->uPeriodSizeMs, &pCfgReq->Props);
2988 else /* Set default period size. */
2989 pCfgReq->Backend.cfPeriod = DrvAudioHlpMilliToFrames(20 /* ms */, &pCfgReq->Props);
2990
2991 LogRel2(("Audio: Using %s period size (%RU64ms, %RU32 frames) for stream '%s'\n",
2992 pDrvCfg->uPeriodSizeMs ? "custom" : "default", DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfPeriod, &pCfgReq->Props),
2993 pCfgReq->Backend.cfPeriod, pStream->szName));
2994
2995 /*
2996 * Buffer size
2997 */
2998 if (pDrvCfg->uBufferSizeMs)
2999 pCfgReq->Backend.cfBufferSize = DrvAudioHlpMilliToFrames(pDrvCfg->uBufferSizeMs, &pCfgReq->Props);
3000 else /* Set default buffer size. */
3001 pCfgReq->Backend.cfBufferSize = DrvAudioHlpMilliToFrames(100 /* ms */, &pCfgReq->Props);
3002
3003 LogRel2(("Audio: Using %s buffer size (%RU64ms, %RU32 frames) for stream '%s'\n",
3004 pDrvCfg->uBufferSizeMs ? "custom" : "default", DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props),
3005 pCfgReq->Backend.cfBufferSize, pStream->szName));
3006
3007 /*
3008 * Pre-buffering size
3009 */
3010 if (pDrvCfg->uPreBufSizeMs != UINT32_MAX)
3011 {
3012 if (!pDrvCfg->uPreBufSizeMs) /* Pre-buffering is set to disabled. */
3013 LogRel2(("Audio: Using custom pre-buffering (disabled) for stream '%s'\n", pStream->szName));
3014 pCfgReq->Backend.cfPreBuf = DrvAudioHlpMilliToFrames(pDrvCfg->uPreBufSizeMs, &pCfgReq->Props);
3015 }
3016 else /* Set default pre-buffering size. */
3017 pCfgReq->Backend.cfPreBuf = pCfgReq->Backend.cfPeriod * 2;
3018
3019 LogRel2(("Audio: Using %s pre-buffering size (%RU64ms, %RU32 frames) for stream '%s'\n",
3020 pDrvCfg->uPreBufSizeMs != UINT32_MAX ? "custom" : "default", DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfPreBuf, &pCfgReq->Props),
3021 pCfgReq->Backend.cfPreBuf, pStream->szName));
3022
3023 /*
3024 * Validate input.
3025 */
3026 if (pCfgReq->Backend.cfBufferSize < pCfgReq->Backend.cfPeriod)
3027 {
3028 LogRel(("Audio: Error for stream '%s': Buffering size (%RU64ms) must not be smaller than the period size (%RU64ms)\n",
3029 pStream->szName, DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props),
3030 DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfPeriod, &pCfgReq->Props)));
3031 return VERR_INVALID_PARAMETER;
3032 }
3033
3034 if ( pCfgReq->Backend.cfPreBuf != UINT32_MAX /* Custom pre-buffering set? */
3035 && pCfgReq->Backend.cfPreBuf)
3036 {
3037 if (pCfgReq->Backend.cfBufferSize < pCfgReq->Backend.cfPreBuf)
3038 {
3039 LogRel(("Audio: Error for stream '%s': Buffering size (%RU64ms) must not be smaller than the pre-buffering size (%RU64ms)\n",
3040 pStream->szName, DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfPreBuf, &pCfgReq->Props),
3041 DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props)));
3042 return VERR_INVALID_PARAMETER;
3043 }
3044 }
3045
3046 /* Make the acquired host configuration the requested host configuration initially,
3047 * in case the backend does not report back an acquired configuration. */
3048 int rc = DrvAudioHlpStreamCfgCopy(pCfgAcq, pCfgReq);
3049 if (RT_FAILURE(rc))
3050 {
3051 LogRel(("Audio: Creating stream '%s' with an invalid backend configuration not possible, skipping\n",
3052 pStream->szName));
3053 return rc;
3054 }
3055
3056 rc = pThis->pHostDrvAudio->pfnStreamCreate(pThis->pHostDrvAudio, pStream->pvBackend, pCfgReq, pCfgAcq);
3057 if (RT_FAILURE(rc))
3058 {
3059 if (rc == VERR_NOT_SUPPORTED)
3060 LogRel2(("Audio: Creating stream '%s' in backend not supported\n", pStream->szName));
3061 else if (rc == VERR_AUDIO_STREAM_COULD_NOT_CREATE)
3062 LogRel2(("Audio: Stream '%s' could not be created in backend because of missing hardware / drivers\n", pStream->szName));
3063 else
3064 LogRel(("Audio: Creating stream '%s' in backend failed with %Rrc\n", pStream->szName, rc));
3065
3066 return rc;
3067 }
3068
3069 /* Validate acquired configuration. */
3070 if (!DrvAudioHlpStreamCfgIsValid(pCfgAcq))
3071 {
3072 LogRel(("Audio: Creating stream '%s' returned an invalid backend configuration, skipping\n", pStream->szName));
3073 return VERR_INVALID_PARAMETER;
3074 }
3075
3076 /* Let the user know that the backend changed one of the values requested above. */
3077 if (pCfgAcq->Backend.cfBufferSize != pCfgReq->Backend.cfBufferSize)
3078 LogRel2(("Audio: Buffer size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n",
3079 pStream->szName, DrvAudioHlpFramesToMilli(pCfgAcq->Backend.cfBufferSize, &pCfgAcq->Props), pCfgAcq->Backend.cfBufferSize));
3080
3081 if (pCfgAcq->Backend.cfPeriod != pCfgReq->Backend.cfPeriod)
3082 LogRel2(("Audio: Period size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n",
3083 pStream->szName, DrvAudioHlpFramesToMilli(pCfgAcq->Backend.cfPeriod, &pCfgAcq->Props), pCfgAcq->Backend.cfPeriod));
3084
3085 if (pCfgAcq->Backend.cfPreBuf != pCfgReq->Backend.cfPreBuf)
3086 LogRel2(("Audio: Pre-buffering size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n",
3087 pStream->szName, DrvAudioHlpFramesToMilli(pCfgAcq->Backend.cfPreBuf, &pCfgAcq->Props), pCfgAcq->Backend.cfPreBuf));
3088
3089 /* Sanity for detecting buggy backends. */
3090 AssertMsgReturn(pCfgAcq->Backend.cfPeriod < pCfgAcq->Backend.cfBufferSize,
3091 ("Acquired period size must be smaller than buffer size\n"),
3092 VERR_INVALID_PARAMETER);
3093 AssertMsgReturn(pCfgAcq->Backend.cfPreBuf < pCfgAcq->Backend.cfBufferSize,
3094 ("Acquired pre-buffering size must be smaller than buffer size -- this otherwise will lead to buffer overruns\n"),
3095 VERR_INVALID_PARAMETER);
3096
3097 pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_INITIALIZED;
3098
3099 return VINF_SUCCESS;
3100}
3101
3102/**
3103 * Calls the backend to give it the chance to destroy its part of the audio stream.
3104 *
3105 * @returns IPRT status code.
3106 * @param pThis Pointer to driver instance.
3107 * @param pStream Audio stream destruct backend for.
3108 */
3109static int drvAudioStreamDestroyInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream)
3110{
3111 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
3112 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
3113
3114 int rc = VINF_SUCCESS;
3115
3116#ifdef LOG_ENABLED
3117 char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus);
3118 LogFunc(("[%s] fStatus=%s\n", pStream->szName, pszStreamSts));
3119#endif
3120
3121 if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_INITIALIZED)
3122 {
3123 AssertPtr(pStream->pvBackend);
3124
3125 /* Check if the pointer to the host audio driver is still valid.
3126 * It can be NULL if we were called in drvAudioDestruct, for example. */
3127 if (pThis->pHostDrvAudio)
3128 rc = pThis->pHostDrvAudio->pfnStreamDestroy(pThis->pHostDrvAudio, pStream->pvBackend);
3129
3130 pStream->fStatus &= ~PDMAUDIOSTREAMSTS_FLAG_INITIALIZED;
3131 }
3132
3133#ifdef LOG_ENABLED
3134 RTStrFree(pszStreamSts);
3135#endif
3136
3137 LogFlowFunc(("[%s] Returning %Rrc\n", pStream->szName, rc));
3138 return rc;
3139}
3140
3141/**
3142 * Uninitializes an audio stream.
3143 *
3144 * @returns IPRT status code.
3145 * @param pThis Pointer to driver instance.
3146 * @param pStream Pointer to audio stream to uninitialize.
3147 */
3148static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream)
3149{
3150 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
3151 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
3152
3153 LogFlowFunc(("[%s] cRefs=%RU32\n", pStream->szName, pStream->cRefs));
3154
3155 if (pStream->cRefs > 1)
3156 return VERR_WRONG_ORDER;
3157
3158 int rc = drvAudioStreamControlInternal(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
3159 if (RT_SUCCESS(rc))
3160 rc = drvAudioStreamDestroyInternalBackend(pThis, pStream);
3161
3162 /* Destroy mixing buffers. */
3163 AudioMixBufDestroy(&pStream->Guest.MixBuf);
3164 AudioMixBufDestroy(&pStream->Host.MixBuf);
3165
3166 if (RT_SUCCESS(rc))
3167 {
3168#ifdef LOG_ENABLED
3169 if (pStream->fStatus != PDMAUDIOSTREAMSTS_FLAG_NONE)
3170 {
3171 char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus);
3172 LogFunc(("[%s] Warning: Still has %s set when uninitializing\n", pStream->szName, pszStreamSts));
3173 RTStrFree(pszStreamSts);
3174 }
3175#endif
3176 pStream->fStatus = PDMAUDIOSTREAMSTS_FLAG_NONE;
3177 }
3178
3179 if (pStream->enmDir == PDMAUDIODIR_IN)
3180 {
3181#ifdef VBOX_WITH_STATISTICS
3182 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->In.Stats.BytesElapsed);
3183 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->In.Stats.BytesTotalRead);
3184 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->In.Stats.FramesCaptured);
3185#endif
3186 if (pThis->In.Cfg.Dbg.fEnabled)
3187 {
3188 DrvAudioHlpFileDestroy(pStream->In.Dbg.pFileCaptureNonInterleaved);
3189 pStream->In.Dbg.pFileCaptureNonInterleaved = NULL;
3190
3191 DrvAudioHlpFileDestroy(pStream->In.Dbg.pFileStreamRead);
3192 pStream->In.Dbg.pFileStreamRead = NULL;
3193 }
3194 }
3195 else if (pStream->enmDir == PDMAUDIODIR_OUT)
3196 {
3197#ifdef VBOX_WITH_STATISTICS
3198 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->Out.Stats.BytesElapsed);
3199 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->Out.Stats.BytesTotalWritten);
3200 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->Out.Stats.FramesPlayed);
3201#endif
3202 if (pThis->Out.Cfg.Dbg.fEnabled)
3203 {
3204 DrvAudioHlpFileDestroy(pStream->Out.Dbg.pFilePlayNonInterleaved);
3205 pStream->Out.Dbg.pFilePlayNonInterleaved = NULL;
3206
3207 DrvAudioHlpFileDestroy(pStream->Out.Dbg.pFileStreamWrite);
3208 pStream->Out.Dbg.pFileStreamWrite = NULL;
3209 }
3210 }
3211 else
3212 AssertFailed();
3213
3214 LogFlowFunc(("Returning %Rrc\n", rc));
3215 return rc;
3216}
3217
3218/**
3219 * Does the actual backend driver attaching and queries the backend's interface.
3220 *
3221 * @return VBox status code.
3222 * @param pThis Pointer to driver instance.
3223 * @param fFlags Attach flags; see PDMDrvHlpAttach().
3224 */
3225static int drvAudioDoAttachInternal(PDRVAUDIO pThis, uint32_t fFlags)
3226{
3227 Assert(pThis->pHostDrvAudio == NULL); /* No nested attaching. */
3228
3229 /*
3230 * Attach driver below and query its connector interface.
3231 */
3232 PPDMIBASE pDownBase;
3233 int rc = PDMDrvHlpAttach(pThis->pDrvIns, fFlags, &pDownBase);
3234 if (RT_SUCCESS(rc))
3235 {
3236 pThis->pHostDrvAudio = PDMIBASE_QUERY_INTERFACE(pDownBase, PDMIHOSTAUDIO);
3237 if (!pThis->pHostDrvAudio)
3238 {
3239 LogRel(("Audio: Failed to query interface for underlying host driver '%s'\n", pThis->szName));
3240 rc = PDMDRV_SET_ERROR(pThis->pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW,
3241 N_("Host audio backend missing or invalid"));
3242 }
3243 }
3244
3245 if (RT_SUCCESS(rc))
3246 {
3247 /*
3248 * If everything went well, initialize the lower driver.
3249 */
3250 AssertPtr(pThis->pCFGMNode);
3251 rc = drvAudioHostInit(pThis, pThis->pCFGMNode);
3252 }
3253
3254 LogFunc(("[%s] rc=%Rrc\n", pThis->szName, rc));
3255 return rc;
3256}
3257
3258
3259/********************************************************************/
3260
3261/**
3262 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
3263 */
3264static DECLCALLBACK(void *) drvAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
3265{
3266 LogFlowFunc(("pInterface=%p, pszIID=%s\n", pInterface, pszIID));
3267
3268 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
3269 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3270
3271 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
3272 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIAUDIOCONNECTOR, &pThis->IAudioConnector);
3273
3274 return NULL;
3275}
3276
3277/**
3278 * Power Off notification.
3279 *
3280 * @param pDrvIns The driver instance data.
3281 */
3282static DECLCALLBACK(void) drvAudioPowerOff(PPDMDRVINS pDrvIns)
3283{
3284 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3285
3286 LogFlowFuncEnter();
3287
3288 if (!pThis->pHostDrvAudio) /* If not lower driver is configured, bail out. */
3289 return;
3290
3291 /* Just destroy the host stream on the backend side.
3292 * The rest will either be destructed by the device emulation or
3293 * in drvAudioDestruct(). */
3294 PPDMAUDIOSTREAM pStream;
3295 RTListForEach(&pThis->lstStreams, pStream, PDMAUDIOSTREAM, Node)
3296 {
3297 drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
3298 drvAudioStreamDestroyInternalBackend(pThis, pStream);
3299 }
3300
3301 /*
3302 * Last call for the driver below us.
3303 * Let it know that we reached end of life.
3304 */
3305 if (pThis->pHostDrvAudio->pfnShutdown)
3306 pThis->pHostDrvAudio->pfnShutdown(pThis->pHostDrvAudio);
3307
3308 pThis->pHostDrvAudio = NULL;
3309
3310 LogFlowFuncLeave();
3311}
3312
3313/**
3314 * Constructs an audio driver instance.
3315 *
3316 * @copydoc FNPDMDRVCONSTRUCT
3317 */
3318static DECLCALLBACK(int) drvAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
3319{
3320 LogFlowFunc(("pDrvIns=%#p, pCfgHandle=%#p, fFlags=%x\n", pDrvIns, pCfg, fFlags));
3321
3322 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3323 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
3324
3325 RTListInit(&pThis->lstStreams);
3326#ifdef VBOX_WITH_AUDIO_CALLBACKS
3327 RTListInit(&pThis->In.lstCB);
3328 RTListInit(&pThis->Out.lstCB);
3329#endif
3330
3331 /*
3332 * Init the static parts.
3333 */
3334 pThis->pDrvIns = pDrvIns;
3335 /* IBase. */
3336 pDrvIns->IBase.pfnQueryInterface = drvAudioQueryInterface;
3337 /* IAudioConnector. */
3338 pThis->IAudioConnector.pfnEnable = drvAudioEnable;
3339 pThis->IAudioConnector.pfnIsEnabled = drvAudioIsEnabled;
3340 pThis->IAudioConnector.pfnGetConfig = drvAudioGetConfig;
3341 pThis->IAudioConnector.pfnGetStatus = drvAudioGetStatus;
3342 pThis->IAudioConnector.pfnStreamCreate = drvAudioStreamCreate;
3343 pThis->IAudioConnector.pfnStreamDestroy = drvAudioStreamDestroy;
3344 pThis->IAudioConnector.pfnStreamRetain = drvAudioStreamRetain;
3345 pThis->IAudioConnector.pfnStreamRelease = drvAudioStreamRelease;
3346 pThis->IAudioConnector.pfnStreamControl = drvAudioStreamControl;
3347 pThis->IAudioConnector.pfnStreamRead = drvAudioStreamRead;
3348 pThis->IAudioConnector.pfnStreamWrite = drvAudioStreamWrite;
3349 pThis->IAudioConnector.pfnStreamIterate = drvAudioStreamIterate;
3350 pThis->IAudioConnector.pfnStreamGetReadable = drvAudioStreamGetReadable;
3351 pThis->IAudioConnector.pfnStreamGetWritable = drvAudioStreamGetWritable;
3352 pThis->IAudioConnector.pfnStreamGetStatus = drvAudioStreamGetStatus;
3353 pThis->IAudioConnector.pfnStreamSetVolume = drvAudioStreamSetVolume;
3354 pThis->IAudioConnector.pfnStreamPlay = drvAudioStreamPlay;
3355 pThis->IAudioConnector.pfnStreamCapture = drvAudioStreamCapture;
3356#ifdef VBOX_WITH_AUDIO_CALLBACKS
3357 pThis->IAudioConnector.pfnRegisterCallbacks = drvAudioRegisterCallbacks;
3358#endif
3359
3360 int rc = drvAudioInit(pDrvIns, pCfg);
3361 if (RT_SUCCESS(rc))
3362 {
3363#ifdef VBOX_WITH_STATISTICS
3364 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalStreamsActive, "TotalStreamsActive",
3365 STAMUNIT_COUNT, "Total active audio streams.");
3366 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalStreamsCreated, "TotalStreamsCreated",
3367 STAMUNIT_COUNT, "Total created audio streams.");
3368 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesRead, "TotalFramesRead",
3369 STAMUNIT_COUNT, "Total frames read by device emulation.");
3370 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesWritten, "TotalFramesWritten",
3371 STAMUNIT_COUNT, "Total frames written by device emulation ");
3372 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesMixedIn, "TotalFramesMixedIn",
3373 STAMUNIT_COUNT, "Total input frames mixed.");
3374 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesMixedOut, "TotalFramesMixedOut",
3375 STAMUNIT_COUNT, "Total output frames mixed.");
3376 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesLostIn, "TotalFramesLostIn",
3377 STAMUNIT_COUNT, "Total input frames lost.");
3378 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesLostOut, "TotalFramesLostOut",
3379 STAMUNIT_COUNT, "Total output frames lost.");
3380 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesOut, "TotalFramesOut",
3381 STAMUNIT_COUNT, "Total frames played by backend.");
3382 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesIn, "TotalFramesIn",
3383 STAMUNIT_COUNT, "Total frames captured by backend.");
3384 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalBytesRead, "TotalBytesRead",
3385 STAMUNIT_BYTES, "Total bytes read.");
3386 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalBytesWritten, "TotalBytesWritten",
3387 STAMUNIT_BYTES, "Total bytes written.");
3388
3389 PDMDrvHlpSTAMRegProfileAdvEx(pDrvIns, &pThis->Stats.DelayIn, "DelayIn",
3390 STAMUNIT_NS_PER_CALL, "Profiling of input data processing.");
3391 PDMDrvHlpSTAMRegProfileAdvEx(pDrvIns, &pThis->Stats.DelayOut, "DelayOut",
3392 STAMUNIT_NS_PER_CALL, "Profiling of output data processing.");
3393#endif
3394 }
3395
3396 rc = drvAudioDoAttachInternal(pThis, fFlags);
3397 if (RT_FAILURE(rc))
3398 {
3399 /* No lower attached driver (yet)? Not a failure, might get attached later at runtime, just skip. */
3400 if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
3401 rc = VINF_SUCCESS;
3402 }
3403
3404 LogFlowFuncLeaveRC(rc);
3405 return rc;
3406}
3407
3408/**
3409 * Destructs an audio driver instance.
3410 *
3411 * @copydoc FNPDMDRVDESTRUCT
3412 */
3413static DECLCALLBACK(void) drvAudioDestruct(PPDMDRVINS pDrvIns)
3414{
3415 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
3416 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3417
3418 LogFlowFuncEnter();
3419
3420 int rc2;
3421
3422 if (RTCritSectIsInitialized(&pThis->CritSect))
3423 {
3424 rc2 = RTCritSectEnter(&pThis->CritSect);
3425 AssertRC(rc2);
3426 }
3427
3428 /*
3429 * Note: No calls here to the driver below us anymore,
3430 * as PDM already has destroyed it.
3431 * If you need to call something from the host driver,
3432 * do this in drvAudioPowerOff() instead.
3433 */
3434
3435 /* Thus, NULL the pointer to the host audio driver first,
3436 * so that routines like drvAudioStreamDestroyInternal() don't call the driver(s) below us anymore. */
3437 pThis->pHostDrvAudio = NULL;
3438
3439 PPDMAUDIOSTREAM pStream, pStreamNext;
3440 RTListForEachSafe(&pThis->lstStreams, pStream, pStreamNext, PDMAUDIOSTREAM, Node)
3441 {
3442 rc2 = drvAudioStreamUninitInternal(pThis, pStream);
3443 if (RT_SUCCESS(rc2))
3444 {
3445 RTListNodeRemove(&pStream->Node);
3446
3447 drvAudioStreamFree(pStream);
3448 pStream = NULL;
3449 }
3450 }
3451
3452 /* Sanity. */
3453 Assert(RTListIsEmpty(&pThis->lstStreams));
3454
3455#ifdef VBOX_WITH_AUDIO_CALLBACKS
3456 /*
3457 * Destroy callbacks, if any.
3458 */
3459 PPDMAUDIOCBRECORD pCB, pCBNext;
3460 RTListForEachSafe(&pThis->In.lstCB, pCB, pCBNext, PDMAUDIOCBRECORD, Node)
3461 drvAudioCallbackDestroy(pCB);
3462
3463 RTListForEachSafe(&pThis->Out.lstCB, pCB, pCBNext, PDMAUDIOCBRECORD, Node)
3464 drvAudioCallbackDestroy(pCB);
3465#endif
3466
3467 if (RTCritSectIsInitialized(&pThis->CritSect))
3468 {
3469 rc2 = RTCritSectLeave(&pThis->CritSect);
3470 AssertRC(rc2);
3471
3472 rc2 = RTCritSectDelete(&pThis->CritSect);
3473 AssertRC(rc2);
3474 }
3475
3476#ifdef VBOX_WITH_STATISTICS
3477 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalStreamsActive);
3478 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalStreamsCreated);
3479 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesRead);
3480 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesWritten);
3481 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesMixedIn);
3482 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesMixedOut);
3483 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesLostIn);
3484 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesLostOut);
3485 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesOut);
3486 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesIn);
3487 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalBytesRead);
3488 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalBytesWritten);
3489 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.DelayIn);
3490 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.DelayOut);
3491#endif
3492
3493 LogFlowFuncLeave();
3494}
3495
3496/**
3497 * Suspend notification.
3498 *
3499 * @param pDrvIns The driver instance data.
3500 */
3501static DECLCALLBACK(void) drvAudioSuspend(PPDMDRVINS pDrvIns)
3502{
3503 drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_PAUSE);
3504}
3505
3506/**
3507 * Resume notification.
3508 *
3509 * @param pDrvIns The driver instance data.
3510 */
3511static DECLCALLBACK(void) drvAudioResume(PPDMDRVINS pDrvIns)
3512{
3513 drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_RESUME);
3514}
3515
3516/**
3517 * Attach notification.
3518 *
3519 * @param pDrvIns The driver instance data.
3520 * @param fFlags Attach flags.
3521 */
3522static DECLCALLBACK(int) drvAudioAttach(PPDMDRVINS pDrvIns, uint32_t fFlags)
3523{
3524 RT_NOREF(fFlags);
3525
3526 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
3527 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3528
3529 int rc2 = RTCritSectEnter(&pThis->CritSect);
3530 AssertRC(rc2);
3531
3532 LogFunc(("%s\n", pThis->szName));
3533
3534 int rc = drvAudioDoAttachInternal(pThis, fFlags);
3535
3536 rc2 = RTCritSectLeave(&pThis->CritSect);
3537 if (RT_SUCCESS(rc))
3538 rc = rc2;
3539
3540 return rc;
3541}
3542
3543/**
3544 * Detach notification.
3545 *
3546 * @param pDrvIns The driver instance data.
3547 * @param fFlags Detach flags.
3548 */
3549static DECLCALLBACK(void) drvAudioDetach(PPDMDRVINS pDrvIns, uint32_t fFlags)
3550{
3551 RT_NOREF(fFlags);
3552
3553 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
3554 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3555
3556 int rc2 = RTCritSectEnter(&pThis->CritSect);
3557 AssertRC(rc2);
3558
3559 pThis->pHostDrvAudio = NULL;
3560
3561 LogFunc(("%s\n", pThis->szName));
3562
3563 rc2 = RTCritSectLeave(&pThis->CritSect);
3564 AssertRC(rc2);
3565}
3566
3567/**
3568 * Audio driver registration record.
3569 */
3570const PDMDRVREG g_DrvAUDIO =
3571{
3572 /* u32Version */
3573 PDM_DRVREG_VERSION,
3574 /* szName */
3575 "AUDIO",
3576 /* szRCMod */
3577 "",
3578 /* szR0Mod */
3579 "",
3580 /* pszDescription */
3581 "Audio connector driver",
3582 /* fFlags */
3583 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
3584 /* fClass */
3585 PDM_DRVREG_CLASS_AUDIO,
3586 /* cMaxInstances */
3587 UINT32_MAX,
3588 /* cbInstance */
3589 sizeof(DRVAUDIO),
3590 /* pfnConstruct */
3591 drvAudioConstruct,
3592 /* pfnDestruct */
3593 drvAudioDestruct,
3594 /* pfnRelocate */
3595 NULL,
3596 /* pfnIOCtl */
3597 NULL,
3598 /* pfnPowerOn */
3599 NULL,
3600 /* pfnReset */
3601 NULL,
3602 /* pfnSuspend */
3603 drvAudioSuspend,
3604 /* pfnResume */
3605 drvAudioResume,
3606 /* pfnAttach */
3607 drvAudioAttach,
3608 /* pfnDetach */
3609 drvAudioDetach,
3610 /* pfnPowerOff */
3611 drvAudioPowerOff,
3612 /* pfnSoftReset */
3613 NULL,
3614 /* u32EndVersion */
3615 PDM_DRVREG_VERSION
3616};
3617
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