VirtualBox

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

Last change on this file since 74473 was 74058, checked in by vboxsync, 7 years ago

Audio/DrvAudio: Added some more host driver (backend) checks / return values.

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

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