VirtualBox

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

Last change on this file since 88158 was 88157, checked in by vboxsync, 4 years ago

DrvAudio: Logging fixes. bugref:9890

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