VirtualBox

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

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

Audio/DrvAudio: Got rid of more big stack-allocated stuff [build fix]. ticketoem2ref:36

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