VirtualBox

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

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

DrvAudio: Working on support for asynchronous stream backend init and smoother device switch. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 172.7 KB
Line 
1/* $Id: DrvAudio.cpp 88761 2021-04-29 01:00:32Z vboxsync $ */
2/** @file
3 * Intermediate audio driver - Connects the audio device emulation with the host backend.
4 */
5
6/*
7 * Copyright (C) 2006-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_DRV_AUDIO
23#include <VBox/log.h>
24#include <VBox/vmm/pdm.h>
25#include <VBox/err.h>
26#include <VBox/vmm/mm.h>
27#include <VBox/vmm/pdmaudioifs.h>
28#include <VBox/vmm/pdmaudioinline.h>
29#include <VBox/vmm/pdmaudiohostenuminline.h>
30
31#include <iprt/alloc.h>
32#include <iprt/asm-math.h>
33#include <iprt/assert.h>
34#include <iprt/circbuf.h>
35#include <iprt/string.h>
36#include <iprt/uuid.h>
37
38#include "VBoxDD.h"
39
40#include <ctype.h>
41#include <stdlib.h>
42
43#include "AudioHlp.h"
44#include "AudioMixBuffer.h"
45
46
47/*********************************************************************************************************************************
48* Structures and Typedefs *
49*********************************************************************************************************************************/
50/**
51 * Audio stream context.
52 *
53 * Needed for separating data from the guest and host side (per stream).
54 */
55typedef struct DRVAUDIOSTREAMCTX
56{
57 /** The stream's audio configuration. */
58 PDMAUDIOSTREAMCFG Cfg;
59 /** This stream's mixing buffer. */
60 AUDIOMIXBUF MixBuf;
61} DRVAUDIOSTREAMCTX;
62
63/**
64 * Play state of a stream wrt backend.
65 */
66typedef enum DRVAUDIOPLAYSTATE
67{
68 /** Invalid zero value. */
69 DRVAUDIOPLAYSTATE_INVALID = 0,
70 /** No playback or pre-buffering. */
71 DRVAUDIOPLAYSTATE_NOPLAY,
72 /** Playing w/o any prebuffering. */
73 DRVAUDIOPLAYSTATE_PLAY,
74 /** Parallel pre-buffering prior to a device switch (i.e. we're outputting to
75 * the old device and pre-buffering the same data in parallel). */
76 DRVAUDIOPLAYSTATE_PLAY_PREBUF,
77 /** Initial pre-buffering or the pre-buffering for a device switch (if it
78 * the device setup took less time than filling up the pre-buffer). */
79 DRVAUDIOPLAYSTATE_PREBUF,
80 /** The device initialization is taking too long, pre-buffering wraps around
81 * and drops samples. */
82 DRVAUDIOPLAYSTATE_PREBUF_OVERDUE,
83 /** Same as play-prebuf, but we don't have a working output device any more. */
84 DRVAUDIOPLAYSTATE_PREBUF_SWITCHING,
85 /** Working on committing the pre-buffered data.
86 * We'll typically leave this state immediately and go to PLAY, however if
87 * the backend cannot handle all the pre-buffered data at once, we'll stay
88 * here till it does. */
89 DRVAUDIOPLAYSTATE_PREBUF_COMMITTING,
90 /** End of valid values. */
91 DRVAUDIOPLAYSTATE_END
92} DRVAUDIOPLAYSTATE;
93
94
95/**
96 * Extended stream structure.
97 */
98typedef struct DRVAUDIOSTREAM
99{
100 /** The publicly visible bit. */
101 PDMAUDIOSTREAM Core;
102
103 /** Just an extra magic to verify that we allocated the stream rather than some
104 * faked up stuff from the device (DRVAUDIOSTREAM_MAGIC). */
105 uintptr_t uMagic;
106
107 /** List entry in DRVAUDIO::lstStreams. */
108 RTLISTNODE ListEntry;
109
110 /** Data to backend-specific stream data.
111 * This data block will be casted by the backend to access its backend-dependent data.
112 *
113 * That way the backends do not have access to the audio connector's data. */
114 PPDMAUDIOBACKENDSTREAM pBackend;
115
116 /** Do not use the mixing buffers (Guest::MixBuf, Host::MixBuf). */
117 bool fNoMixBufs;
118 bool afPadding[3];
119
120 /** Number of (re-)tries while re-initializing the stream. */
121 uint32_t cTriesReInit;
122
123 /** The backend status at the last play or capture call.
124 * This is used to detect state changes. */
125 uint32_t fLastBackendStatus;
126
127 /** The guest side of the stream. */
128 DRVAUDIOSTREAMCTX Guest;
129 /** The host side of the stream. */
130 DRVAUDIOSTREAMCTX Host;
131
132
133 /** Timestamp (in ns) since last trying to re-initialize.
134 * Might be 0 if has not been tried yet. */
135 uint64_t nsLastReInit;
136 /** Timestamp (in ns) since last iteration. */
137 uint64_t nsLastIterated;
138 /** Timestamp (in ns) since last playback / capture. */
139 uint64_t nsLastPlayedCaptured;
140 /** Timestamp (in ns) since last read (input streams) or
141 * write (output streams). */
142 uint64_t nsLastReadWritten;
143 /** Internal stream position (as per pfnStreamWrite/Read). */
144 uint64_t offInternal;
145
146
147 /** Union for input/output specifics depending on enmDir. */
148 union
149 {
150 /**
151 * The specifics for an audio input stream.
152 */
153 struct
154 {
155 struct
156 {
157 /** File for writing stream reads. */
158 PAUDIOHLPFILE pFileStreamRead;
159 /** File for writing non-interleaved captures. */
160 PAUDIOHLPFILE pFileCaptureNonInterleaved;
161 } Dbg;
162 struct
163 {
164 STAMCOUNTER TotalFramesCaptured;
165 STAMCOUNTER AvgFramesCaptured;
166 STAMCOUNTER TotalTimesCaptured;
167 STAMCOUNTER TotalFramesRead;
168 STAMCOUNTER AvgFramesRead;
169 STAMCOUNTER TotalTimesRead;
170 } Stats;
171 } In;
172
173 /**
174 * The specifics for an audio output stream.
175 */
176 struct
177 {
178 struct
179 {
180 /** File for writing stream writes. */
181 PAUDIOHLPFILE pFileStreamWrite;
182 /** File for writing stream playback. */
183 PAUDIOHLPFILE pFilePlayNonInterleaved;
184 } Dbg;
185 struct
186 {
187 uint32_t cbBackendWritableBefore;
188 uint32_t cbBackendWritableAfter;
189 } Stats;
190
191 /** Space for pre-buffering. */
192 uint8_t *pbPreBuf;
193 /** The size of the pre-buffer allocation (in bytes). */
194 uint32_t cbPreBufAlloc;
195 /** The current pre-buffering read offset. */
196 uint32_t offPreBuf;
197 /** Number of bytes we've prebuffered. */
198 uint32_t cbPreBuffered;
199 /** The pre-buffering threshold expressed in bytes. */
200 uint32_t cbPreBufThreshold;
201 /** The play state. */
202 DRVAUDIOPLAYSTATE enmPlayState;
203 } Out;
204 } RT_UNION_NM(u);
205} DRVAUDIOSTREAM;
206/** Pointer to an extended stream structure. */
207typedef DRVAUDIOSTREAM *PDRVAUDIOSTREAM;
208
209/** Value for DRVAUDIOSTREAM::uMagic (Johann Sebastian Bach). */
210#define DRVAUDIOSTREAM_MAGIC UINT32_C(0x16850331)
211/** Value for DRVAUDIOSTREAM::uMagic after destruction */
212#define DRVAUDIOSTREAM_MAGIC_DEAD UINT32_C(0x17500728)
213
214
215/**
216 * Audio driver configuration data, tweakable via CFGM.
217 */
218typedef struct DRVAUDIOCFG
219{
220 /** PCM properties to use. */
221 PDMAUDIOPCMPROPS Props;
222 /** Whether using signed sample data or not.
223 * Needed in order to know whether there is a custom value set in CFGM or not.
224 * By default set to UINT8_MAX if not set to a custom value. */
225 uint8_t uSigned;
226 /** Whether swapping endianess of sample data or not.
227 * Needed in order to know whether there is a custom value set in CFGM or not.
228 * By default set to UINT8_MAX if not set to a custom value. */
229 uint8_t uSwapEndian;
230 /** Configures the period size (in ms).
231 * This value reflects the time in between each hardware interrupt on the
232 * backend (host) side. */
233 uint32_t uPeriodSizeMs;
234 /** Configures the (ring) buffer size (in ms). Often is a multiple of uPeriodMs. */
235 uint32_t uBufferSizeMs;
236 /** Configures the pre-buffering size (in ms).
237 * Time needed in buffer before the stream becomes active (pre buffering).
238 * The bigger this value is, the more latency for the stream will occur.
239 * Set to 0 to disable pre-buffering completely.
240 * By default set to UINT32_MAX if not set to a custom value. */
241 uint32_t uPreBufSizeMs;
242 /** The driver's debugging configuration. */
243 struct
244 {
245 /** Whether audio debugging is enabled or not. */
246 bool fEnabled;
247 /** Where to store the debugging files. */
248 char szPathOut[RTPATH_MAX];
249 } Dbg;
250} DRVAUDIOCFG, *PDRVAUDIOCFG;
251
252/**
253 * Audio driver instance data.
254 *
255 * @implements PDMIAUDIOCONNECTOR
256 */
257typedef struct DRVAUDIO
258{
259 /** Friendly name of the driver. */
260 char szName[64];
261 /** Critical section for serializing access.
262 * @todo r=bird: This needs to be split up and introduce stream-level locking so
263 * that different AIO threads can work in parallel (e.g. input &
264 * output, or two output streams). Maybe put a critect in
265 * PDMAUDIOSTREAM? */
266 RTCRITSECT CritSect;
267 /** Shutdown indicator. */
268 bool fTerminate;
269#ifdef VBOX_WITH_AUDIO_ENUM
270 /** Flag indicating that we need to enumerate and log of the host
271 * audio devices again. */
272 bool fEnumerateDevices;
273#endif
274 /** Our audio connector interface. */
275 PDMIAUDIOCONNECTOR IAudioConnector;
276 /** Interface used by the host backend. */
277 PDMIAUDIONOTIFYFROMHOST IAudioNotifyFromHost;
278 /** Pointer to the driver instance. */
279 PPDMDRVINS pDrvIns;
280 /** Pointer to audio driver below us. */
281 PPDMIHOSTAUDIO pHostDrvAudio;
282 /** List of audio streams (DRVAUDIOSTREAM). */
283 RTLISTANCHOR lstStreams;
284 /** Audio configuration settings retrieved from the backend. */
285 PDMAUDIOBACKENDCFG BackendCfg;
286 struct
287 {
288 /** Whether this driver's input streams are enabled or not.
289 * This flag overrides all the attached stream statuses. */
290 bool fEnabled;
291 /** Max. number of free input streams.
292 * UINT32_MAX for unlimited streams. */
293 uint32_t cStreamsFree;
294 /** The driver's input confguration (tweakable via CFGM). */
295 DRVAUDIOCFG Cfg;
296 } In;
297 struct
298 {
299 /** Whether this driver's output streams are enabled or not.
300 * This flag overrides all the attached stream statuses. */
301 bool fEnabled;
302 /** Max. number of free output streams.
303 * UINT32_MAX for unlimited streams. */
304 uint32_t cStreamsFree;
305 /** The driver's output confguration (tweakable via CFGM). */
306 DRVAUDIOCFG Cfg;
307 /** Number of times underruns triggered re-(pre-)buffering. */
308 STAMCOUNTER StatsReBuffering;
309 } Out;
310
311 /** Handle to the disable-iteration timer. */
312 TMTIMERHANDLE hTimer;
313 /** Set if hTimer is armed. */
314 bool volatile fTimerArmed;
315 /** Unique name for the the disable-iteration timer. */
316 char szTimerName[23];
317
318#ifdef VBOX_WITH_STATISTICS
319 /** Statistics. */
320 struct
321 {
322 STAMCOUNTER TotalStreamsActive;
323 STAMCOUNTER TotalStreamsCreated;
324 STAMCOUNTER TotalFramesRead;
325 STAMCOUNTER TotalFramesIn;
326 STAMCOUNTER TotalBytesRead;
327 } Stats;
328#endif
329} DRVAUDIO;
330/** Pointer to the instance data of an audio driver. */
331typedef DRVAUDIO *PDRVAUDIO;
332
333
334/*********************************************************************************************************************************
335* Internal Functions *
336*********************************************************************************************************************************/
337static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd);
338static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd);
339static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx);
340static int drvAudioStreamIterateInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx);
341
342
343#ifdef LOG_ENABLED
344
345/** Buffer size for dbgAudioStreamStatusToStr. */
346# define DRVAUDIO_STATUS_STR_MAX sizeof("INITIALIZED ENABLED PAUSED PENDING_DISABLED NEED_REINIT PREPARING_SWITCH 0x12345678")
347
348/**
349 * Converts an audio stream status to a string.
350 *
351 * @returns pszDst
352 * @param pszDst Buffer to convert into, at least minimum size is
353 * DRVAUDIO_STATUS_STR_MAX.
354 * @param fStatus Stream status flags to convert.
355 */
356static const char *dbgAudioStreamStatusToStr(char pszDst[DRVAUDIO_STATUS_STR_MAX], uint32_t fStatus)
357{
358 static const struct
359 {
360 const char *pszMnemonic;
361 uint32_t cchMnemnonic;
362 uint32_t fFlag;
363 } s_aFlags[] =
364 {
365 { RT_STR_TUPLE("INITIALIZED "), PDMAUDIOSTREAM_STS_INITIALIZED },
366 { RT_STR_TUPLE("ENABLED "), PDMAUDIOSTREAM_STS_ENABLED },
367 { RT_STR_TUPLE("PAUSED "), PDMAUDIOSTREAM_STS_PAUSED },
368 { RT_STR_TUPLE("PENDING_DISABLE "), PDMAUDIOSTREAM_STS_PENDING_DISABLE },
369 { RT_STR_TUPLE("NEED_REINIT "), PDMAUDIOSTREAM_STS_NEED_REINIT },
370 { RT_STR_TUPLE("PREPARING_SWITCH "), PDMAUDIOSTREAM_STS_PREPARING_SWITCH },
371 };
372 if (!fStatus)
373 strcpy(pszDst, "NONE");
374 else
375 {
376 char *psz = pszDst;
377 for (size_t i = 0; i < RT_ELEMENTS(s_aFlags); i++)
378 if (fStatus & s_aFlags[i].fFlag)
379 {
380 memcpy(psz, s_aFlags[i].pszMnemonic, s_aFlags[i].cchMnemnonic);
381 psz += s_aFlags[i].cchMnemnonic;
382 fStatus &= ~s_aFlags[i].fFlag;
383 if (!fStatus)
384 break;
385 }
386 if (fStatus == 0)
387 psz[-1] = '\0';
388 else
389 psz += RTStrPrintf(psz, DRVAUDIO_STATUS_STR_MAX - (psz - pszDst), "%#x", fStatus);
390 Assert((uintptr_t)(psz - pszDst) <= DRVAUDIO_STATUS_STR_MAX);
391 }
392 return pszDst;
393}
394
395#endif /* defined(LOG_ENABLED) */
396
397/**
398 * Get pre-buffer state name string.
399 */
400static const char *drvAudioPlayStateName(DRVAUDIOPLAYSTATE enmState)
401{
402 switch (enmState)
403 {
404 case DRVAUDIOPLAYSTATE_INVALID: return "INVALID";
405 case DRVAUDIOPLAYSTATE_NOPLAY: return "NOPLAY";
406 case DRVAUDIOPLAYSTATE_PLAY: return "PLAY";
407 case DRVAUDIOPLAYSTATE_PLAY_PREBUF: return "PLAY_PREBUF";
408 case DRVAUDIOPLAYSTATE_PREBUF: return "PREBUF";
409 case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE: return "PREBUF_OVERDUE";
410 case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING: return "PREBUF_SWITCHING";
411 case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING: return "PREBUF_COMMITTING";
412 case DRVAUDIOPLAYSTATE_END:
413 break;
414 }
415 return "BAD";
416}
417
418
419/**
420 * Wrapper around PDMIHOSTAUDIO::pfnStreamGetStatus and checks the result.
421 *
422 * @returns PDMAUDIOSTREAM_STS_XXX
423 * @param pThis Pointer to the DrvAudio instance data.
424 * @param pStreamEx The stream to get the backend status for.
425 */
426DECLINLINE(uint32_t) drvAudioStreamGetBackendStatus(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
427{
428 Assert(pThis->pHostDrvAudio);
429 uint32_t fBackendStatus = pThis->pHostDrvAudio->pfnStreamGetStatus(pThis->pHostDrvAudio, pStreamEx->pBackend);
430 PDMAUDIOSTREAM_STS_ASSERT_VALID_BACKEND(fBackendStatus);
431 return fBackendStatus;
432}
433
434
435#ifdef VBOX_WITH_AUDIO_ENUM
436/**
437 * Enumerates all host audio devices.
438 *
439 * This functionality might not be implemented by all backends and will return
440 * VERR_NOT_SUPPORTED if not being supported.
441 *
442 * @note Must not hold the driver's critical section!
443 *
444 * @returns VBox status code.
445 * @param pThis Driver instance to be called.
446 * @param fLog Whether to print the enumerated device to the release log or not.
447 * @param pDevEnum Where to store the device enumeration.
448 *
449 * @remarks This is currently ONLY used for release logging.
450 */
451static int drvAudioDevicesEnumerateInternal(PDRVAUDIO pThis, bool fLog, PPDMAUDIOHOSTENUM pDevEnum)
452{
453 AssertReturn(!RTCritSectIsOwner(&pThis->CritSect), VERR_WRONG_ORDER);
454
455 int rc;
456
457 /*
458 * If the backend supports it, do a device enumeration.
459 */
460 if (pThis->pHostDrvAudio->pfnGetDevices)
461 {
462 PDMAUDIOHOSTENUM DevEnum;
463 rc = pThis->pHostDrvAudio->pfnGetDevices(pThis->pHostDrvAudio, &DevEnum);
464 if (RT_SUCCESS(rc))
465 {
466 if (fLog)
467 LogRel(("Audio: Found %RU16 devices for driver '%s'\n", DevEnum.cDevices, pThis->szName));
468
469 PPDMAUDIOHOSTDEV pDev;
470 RTListForEach(&DevEnum.LstDevices, pDev, PDMAUDIOHOSTDEV, ListEntry)
471 {
472 if (fLog)
473 {
474 char szFlags[PDMAUDIOHOSTDEV_MAX_FLAGS_STRING_LEN];
475 LogRel(("Audio: Device '%s':\n", pDev->szName));
476 LogRel(("Audio: Usage = %s\n", PDMAudioDirGetName(pDev->enmUsage)));
477 LogRel(("Audio: Flags = %s\n", PDMAudioHostDevFlagsToString(szFlags, pDev->fFlags)));
478 LogRel(("Audio: Input channels = %RU8\n", pDev->cMaxInputChannels));
479 LogRel(("Audio: Output channels = %RU8\n", pDev->cMaxOutputChannels));
480 }
481 }
482
483 if (pDevEnum)
484 rc = PDMAudioHostEnumCopy(pDevEnum, &DevEnum, PDMAUDIODIR_INVALID /*all*/, true /*fOnlyCoreData*/);
485
486 PDMAudioHostEnumDelete(&DevEnum);
487 }
488 else
489 {
490 if (fLog)
491 LogRel(("Audio: Device enumeration for driver '%s' failed with %Rrc\n", pThis->szName, rc));
492 /* Not fatal. */
493 }
494 }
495 else
496 {
497 rc = VERR_NOT_SUPPORTED;
498
499 if (fLog)
500 LogRel2(("Audio: Host driver '%s' does not support audio device enumeration, skipping\n", pThis->szName));
501 }
502
503 LogFunc(("Returning %Rrc\n", rc));
504 return rc;
505}
506#endif /* VBOX_WITH_AUDIO_ENUM */
507
508
509/*********************************************************************************************************************************
510* PDMIAUDIOCONNECTOR *
511*********************************************************************************************************************************/
512
513/**
514 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnEnable}
515 */
516static DECLCALLBACK(int) drvAudioEnable(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir, bool fEnable)
517{
518 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
519 AssertPtr(pThis);
520
521 bool *pfEnabled;
522 if (enmDir == PDMAUDIODIR_IN)
523 pfEnabled = &pThis->In.fEnabled;
524 else if (enmDir == PDMAUDIODIR_OUT)
525 pfEnabled = &pThis->Out.fEnabled;
526 else
527 AssertFailedReturn(VERR_INVALID_PARAMETER);
528
529 int rc = RTCritSectEnter(&pThis->CritSect);
530 AssertRCReturn(rc, rc);
531
532 if (fEnable != *pfEnabled)
533 {
534 LogRel(("Audio: %s %s for driver '%s'\n",
535 fEnable ? "Enabling" : "Disabling", enmDir == PDMAUDIODIR_IN ? "input" : "output", pThis->szName));
536
537 /* Update the status first, as this will be checked for in drvAudioStreamControlInternalBackend() below. */
538 *pfEnabled = fEnable;
539
540 PDRVAUDIOSTREAM pStreamEx;
541 RTListForEach(&pThis->lstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
542 {
543 if (pStreamEx->Core.enmDir != enmDir) /* Skip unwanted streams. */
544 continue;
545
546 /* Note: Only enable / disable the backend, do *not* change the stream's internal status.
547 * Callers (device emulation, mixer, ...) from outside will not see any status or behavior change,
548 * to not confuse the rest of the state machine.
549 *
550 * When disabling:
551 * - playing back audo data would go to /dev/null
552 * - recording audio data would return silence instead
553 *
554 * See @bugref{9882}.
555 */
556 int rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx,
557 fEnable ? PDMAUDIOSTREAMCMD_ENABLE : PDMAUDIOSTREAMCMD_DISABLE);
558 if (RT_FAILURE(rc2))
559 {
560 if (rc2 == VERR_AUDIO_STREAM_NOT_READY)
561 LogRel(("Audio: Stream '%s' not available\n", pStreamEx->Core.szName));
562 else
563 LogRel(("Audio: Failed to %s %s stream '%s', rc=%Rrc\n", fEnable ? "enable" : "disable",
564 enmDir == PDMAUDIODIR_IN ? "input" : "output", pStreamEx->Core.szName, rc2));
565 }
566 else
567 {
568 /* When (re-)enabling a stream, clear the disabled warning bit again. */
569 if (fEnable)
570 pStreamEx->Core.fWarningsShown &= ~PDMAUDIOSTREAM_WARN_FLAGS_DISABLED;
571 }
572
573 if (RT_SUCCESS(rc))
574 rc = rc2;
575
576 /* Keep going. */
577 }
578 }
579
580 RTCritSectLeave(&pThis->CritSect);
581 LogFlowFuncLeaveRC(rc);
582 return rc;
583}
584
585
586/**
587 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnIsEnabled}
588 */
589static DECLCALLBACK(bool) drvAudioIsEnabled(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir)
590{
591 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
592 AssertPtr(pThis);
593 int rc = RTCritSectEnter(&pThis->CritSect);
594 AssertRCReturn(rc, false);
595
596 bool fEnabled;
597 if (enmDir == PDMAUDIODIR_IN)
598 fEnabled = pThis->In.fEnabled;
599 else if (enmDir == PDMAUDIODIR_OUT)
600 fEnabled = pThis->Out.fEnabled;
601 else
602 AssertFailedStmt(fEnabled = false);
603
604 RTCritSectLeave(&pThis->CritSect);
605 return fEnabled;
606}
607
608
609/**
610 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetConfig}
611 */
612static DECLCALLBACK(int) drvAudioGetConfig(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOBACKENDCFG pCfg)
613{
614 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
615 AssertPtr(pThis);
616 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
617 int rc = RTCritSectEnter(&pThis->CritSect);
618 AssertRCReturn(rc, rc);
619
620 if (pThis->pHostDrvAudio)
621 {
622 if (pThis->pHostDrvAudio->pfnGetConfig)
623 rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, pCfg);
624 else
625 rc = VERR_NOT_SUPPORTED;
626 }
627 else
628 rc = VERR_PDM_NO_ATTACHED_DRIVER;
629
630 RTCritSectLeave(&pThis->CritSect);
631 LogFlowFuncLeaveRC(rc);
632 return rc;
633}
634
635
636/**
637 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetStatus}
638 */
639static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioGetStatus(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir)
640{
641 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
642 AssertPtr(pThis);
643 int rc = RTCritSectEnter(&pThis->CritSect);
644 AssertRCReturn(rc, PDMAUDIOBACKENDSTS_UNKNOWN);
645
646 PDMAUDIOBACKENDSTS fBackendStatus;
647 if (pThis->pHostDrvAudio)
648 {
649 if (pThis->pHostDrvAudio->pfnGetStatus)
650 fBackendStatus = pThis->pHostDrvAudio->pfnGetStatus(pThis->pHostDrvAudio, enmDir);
651 else
652 fBackendStatus = PDMAUDIOBACKENDSTS_UNKNOWN;
653 }
654 else
655 fBackendStatus = PDMAUDIOBACKENDSTS_NOT_ATTACHED;
656
657 RTCritSectLeave(&pThis->CritSect);
658 LogFlowFunc(("LEAVE - %#x\n", fBackendStatus));
659 return fBackendStatus;
660}
661
662
663/**
664 * Frees an audio stream and its allocated resources.
665 *
666 * @param pStreamEx Audio stream to free. After this call the pointer will
667 * not be valid anymore.
668 */
669static void drvAudioStreamFree(PDRVAUDIOSTREAM pStreamEx)
670{
671 if (pStreamEx)
672 {
673 LogFunc(("[%s]\n", pStreamEx->Core.szName));
674 Assert(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC);
675 Assert(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC);
676
677 pStreamEx->Core.uMagic = ~PDMAUDIOSTREAM_MAGIC;
678 pStreamEx->pBackend = NULL;
679 pStreamEx->uMagic = DRVAUDIOSTREAM_MAGIC_DEAD;
680
681 RTMemFree(pStreamEx);
682 }
683}
684
685
686/**
687 * Adjusts the request stream configuration, applying our settings.
688 *
689 * This also does some basic validations.
690 *
691 * Used by both the stream creation and stream configuration hinting code.
692 *
693 * @returns VBox status code.
694 * @param pThis Pointer to the DrvAudio instance data.
695 * @param pCfgReq The request configuration that should be adjusted.
696 * @param pszName Stream name to use when logging warnings and errors.
697 */
698static int drvAudioStreamAdjustConfig(PDRVAUDIO pThis, PPDMAUDIOSTREAMCFG pCfgReq, const char *pszName)
699{
700 /* Get the right configuration for the stream to be created. */
701 PDRVAUDIOCFG pDrvCfg = pCfgReq->enmDir == PDMAUDIODIR_IN ? &pThis->In.Cfg : &pThis->Out.Cfg;
702
703 /* Fill in the tweakable parameters into the requested host configuration.
704 * All parameters in principle can be changed and returned by the backend via the acquired configuration. */
705
706 /*
707 * PCM
708 */
709 if (PDMAudioPropsSampleSize(&pDrvCfg->Props) != 0) /* Anything set via custom extra-data? */
710 {
711 PDMAudioPropsSetSampleSize(&pCfgReq->Props, PDMAudioPropsSampleSize(&pDrvCfg->Props));
712 LogRel2(("Audio: Using custom sample size of %RU8 bytes for stream '%s'\n",
713 PDMAudioPropsSampleSize(&pCfgReq->Props), pszName));
714 }
715
716 if (pDrvCfg->Props.uHz) /* Anything set via custom extra-data? */
717 {
718 pCfgReq->Props.uHz = pDrvCfg->Props.uHz;
719 LogRel2(("Audio: Using custom Hz rate %RU32 for stream '%s'\n", pCfgReq->Props.uHz, pszName));
720 }
721
722 if (pDrvCfg->uSigned != UINT8_MAX) /* Anything set via custom extra-data? */
723 {
724 pCfgReq->Props.fSigned = RT_BOOL(pDrvCfg->uSigned);
725 LogRel2(("Audio: Using custom %s sample format for stream '%s'\n",
726 pCfgReq->Props.fSigned ? "signed" : "unsigned", pszName));
727 }
728
729 if (pDrvCfg->uSwapEndian != UINT8_MAX) /* Anything set via custom extra-data? */
730 {
731 pCfgReq->Props.fSwapEndian = RT_BOOL(pDrvCfg->uSwapEndian);
732 LogRel2(("Audio: Using custom %s endianess for samples of stream '%s'\n",
733 pCfgReq->Props.fSwapEndian ? "swapped" : "original", pszName));
734 }
735
736 if (PDMAudioPropsChannels(&pDrvCfg->Props) != 0) /* Anything set via custom extra-data? */
737 {
738 PDMAudioPropsSetChannels(&pCfgReq->Props, PDMAudioPropsChannels(&pDrvCfg->Props));
739 LogRel2(("Audio: Using custom %RU8 channel(s) for stream '%s'\n", PDMAudioPropsChannels(&pDrvCfg->Props), pszName));
740 }
741
742 /* Validate PCM properties. */
743 if (!AudioHlpPcmPropsAreValid(&pCfgReq->Props))
744 {
745 LogRel(("Audio: Invalid custom PCM properties set for stream '%s', cannot create stream\n", pszName));
746 return VERR_INVALID_PARAMETER;
747 }
748
749 /*
750 * Period size
751 */
752 const char *pszWhat = "device-specific";
753 if (pDrvCfg->uPeriodSizeMs)
754 {
755 pCfgReq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgReq->Props, pDrvCfg->uPeriodSizeMs);
756 pszWhat = "custom";
757 }
758
759 if (!pCfgReq->Backend.cFramesPeriod) /* Set default period size if nothing explicitly is set. */
760 {
761 pCfgReq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgReq->Props, 150 /*ms*/);
762 pszWhat = "default";
763 }
764
765 LogRel2(("Audio: Using %s period size %RU64 ms / %RU32 frames for stream '%s'\n",
766 pszWhat, PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesPeriod),
767 pCfgReq->Backend.cFramesPeriod, pszName));
768
769 /*
770 * Buffer size
771 */
772 pszWhat = "device-specific";
773 if (pDrvCfg->uBufferSizeMs)
774 {
775 pCfgReq->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfgReq->Props, pDrvCfg->uBufferSizeMs);
776 pszWhat = "custom";
777 }
778
779 if (!pCfgReq->Backend.cFramesBufferSize) /* Set default buffer size if nothing explicitly is set. */
780 {
781 pCfgReq->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfgReq->Props, 300 /*ms*/);
782 pszWhat = "default";
783 }
784
785 LogRel2(("Audio: Using %s buffer size %RU64 ms / %RU32 frames for stream '%s'\n",
786 pszWhat, PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize),
787 pCfgReq->Backend.cFramesBufferSize, pszName));
788
789 /*
790 * Pre-buffering size
791 */
792 pszWhat = "device-specific";
793 if (pDrvCfg->uPreBufSizeMs != UINT32_MAX) /* Anything set via global / per-VM extra-data? */
794 {
795 pCfgReq->Backend.cFramesPreBuffering = PDMAudioPropsMilliToFrames(&pCfgReq->Props, pDrvCfg->uPreBufSizeMs);
796 pszWhat = "custom";
797 }
798 else /* No, then either use the default or device-specific settings (if any). */
799 {
800 if (pCfgReq->Backend.cFramesPreBuffering == UINT32_MAX) /* Set default pre-buffering size if nothing explicitly is set. */
801 {
802 /* Pre-buffer 66% of the buffer. */
803 pCfgReq->Backend.cFramesPreBuffering = pCfgReq->Backend.cFramesBufferSize * 2 / 3;
804 pszWhat = "default";
805 }
806 }
807
808 LogRel2(("Audio: Using %s pre-buffering size %RU64 ms / %RU32 frames for stream '%s'\n",
809 pszWhat, PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesPreBuffering),
810 pCfgReq->Backend.cFramesPreBuffering, pszName));
811
812 /*
813 * Validate input.
814 */
815 if (pCfgReq->Backend.cFramesBufferSize < pCfgReq->Backend.cFramesPeriod)
816 {
817 LogRel(("Audio: Error for stream '%s': Buffering size (%RU64ms) must not be smaller than the period size (%RU64ms)\n",
818 pszName, PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize),
819 PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesPeriod)));
820 return VERR_INVALID_PARAMETER;
821 }
822
823 if ( pCfgReq->Backend.cFramesPreBuffering != UINT32_MAX /* Custom pre-buffering set? */
824 && pCfgReq->Backend.cFramesPreBuffering)
825 {
826 if (pCfgReq->Backend.cFramesBufferSize < pCfgReq->Backend.cFramesPreBuffering)
827 {
828 LogRel(("Audio: Error for stream '%s': Buffering size (%RU64ms) must not be smaller than the pre-buffering size (%RU64ms)\n",
829 pszName, PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesPreBuffering),
830 PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize)));
831 return VERR_INVALID_PARAMETER;
832 }
833 }
834
835 return VINF_SUCCESS;
836}
837
838
839/**
840 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamConfigHint}
841 */
842static DECLCALLBACK(void) drvAudioStreamConfigHint(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAMCFG pCfg)
843{
844 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
845 AssertReturnVoid(pCfg->enmDir == PDMAUDIODIR_IN || pCfg->enmDir == PDMAUDIODIR_OUT);
846
847 int rc = RTCritSectEnter(&pThis->CritSect); /** @todo Reconsider the locking for DrvAudio */
848 AssertRCReturnVoid(rc);
849
850 /*
851 * Don't do anything unless the backend has a pfnStreamConfigHint method
852 * and the direction is currently enabled.
853 */
854 if ( pThis->pHostDrvAudio
855 && pThis->pHostDrvAudio->pfnStreamConfigHint)
856 {
857 if (pCfg->enmDir == PDMAUDIODIR_OUT ? pThis->Out.fEnabled : pThis->In.fEnabled)
858 {
859 /*
860 * Adjust the configuration (applying out settings) then call the backend driver.
861 */
862 rc = drvAudioStreamAdjustConfig(pThis, pCfg, pCfg->szName);
863 AssertLogRelRC(rc);
864 if (RT_SUCCESS(rc))
865 pThis->pHostDrvAudio->pfnStreamConfigHint(pThis->pHostDrvAudio, pCfg);
866 }
867 else
868 LogFunc(("Ignoring hint because direction is not currently enabled\n"));
869 }
870 else
871 LogFlowFunc(("Ignoring hint because backend has no pfnStreamConfigHint method.\n"));
872
873 RTCritSectLeave(&pThis->CritSect);
874}
875
876
877/**
878 * Worker for drvAudioStreamInitInternal and drvAudioStreamReInitInternal that
879 * creates the backend (host driver) side of an audio stream.
880 *
881 * @returns VBox status code.
882 * @param pThis Pointer to driver instance.
883 * @param pStreamEx Audio stream to create the backend side for.
884 * @param pCfgReq Requested audio stream configuration to use for
885 * stream creation.
886 * @param pCfgAcq Acquired audio stream configuration returned by
887 * the backend.
888 *
889 * @note Configuration precedence for requested audio stream configuration (first has highest priority, if set):
890 * - per global extra-data
891 * - per-VM extra-data
892 * - requested configuration (by pCfgReq)
893 * - default value
894 */
895static int drvAudioStreamCreateInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx,
896 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
897{
898 AssertMsg((pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_INITIALIZED) == 0,
899 ("Stream '%s' already initialized in backend\n", pStreamEx->Core.szName));
900
901 /*
902 * Adjust the requested stream config, applying our settings.
903 */
904 int rc = drvAudioStreamAdjustConfig(pThis, pCfgReq, pStreamEx->Core.szName);
905 if (RT_FAILURE(rc))
906 return rc;
907
908 /*
909 * Make the acquired host configuration the requested host configuration initially,
910 * in case the backend does not report back an acquired configuration.
911 */
912 /** @todo r=bird: This is conveniently not documented in the interface... */
913 rc = PDMAudioStrmCfgCopy(pCfgAcq, pCfgReq);
914 if (RT_FAILURE(rc))
915 {
916 LogRel(("Audio: Creating stream '%s' with an invalid backend configuration not possible, skipping\n",
917 pStreamEx->Core.szName));
918 return rc;
919 }
920
921 /*
922 * Call the host driver to create the stream.
923 */
924 AssertPtr(pThis->pHostDrvAudio);
925 if (pThis->pHostDrvAudio)
926 rc = pThis->pHostDrvAudio->pfnStreamCreate(pThis->pHostDrvAudio, pStreamEx->pBackend, pCfgReq, pCfgAcq);
927 else
928 rc = VERR_PDM_NO_ATTACHED_DRIVER;
929 if (RT_FAILURE(rc))
930 {
931 if (rc == VERR_NOT_SUPPORTED)
932 LogRel2(("Audio: Creating stream '%s' in backend not supported\n", pStreamEx->Core.szName));
933 else if (rc == VERR_AUDIO_STREAM_COULD_NOT_CREATE)
934 LogRel2(("Audio: Stream '%s' could not be created in backend because of missing hardware / drivers\n", pStreamEx->Core.szName));
935 else
936 LogRel(("Audio: Creating stream '%s' in backend failed with %Rrc\n", pStreamEx->Core.szName, rc));
937 return rc;
938 }
939 AssertLogRelReturn(pStreamEx->pBackend->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC, VERR_INTERNAL_ERROR_3);
940 AssertLogRelReturn(pStreamEx->pBackend->pStream == &pStreamEx->Core, VERR_INTERNAL_ERROR_3);
941
942 /* Validate acquired configuration. */
943 char szTmp[PDMAUDIOPROPSTOSTRING_MAX];
944 AssertLogRelMsgReturn(AudioHlpStreamCfgIsValid(pCfgAcq),
945 ("Audio: Creating stream '%s' returned an invalid backend configuration (%s), skipping\n",
946 pStreamEx->Core.szName, PDMAudioPropsToString(&pCfgAcq->Props, szTmp, sizeof(szTmp))),
947 VERR_INVALID_PARAMETER);
948
949 /* Let the user know that the backend changed one of the values requested above. */
950 if (pCfgAcq->Backend.cFramesBufferSize != pCfgReq->Backend.cFramesBufferSize)
951 LogRel2(("Audio: Buffer size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n",
952 pStreamEx->Core.szName, PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesBufferSize), pCfgAcq->Backend.cFramesBufferSize));
953
954 if (pCfgAcq->Backend.cFramesPeriod != pCfgReq->Backend.cFramesPeriod)
955 LogRel2(("Audio: Period size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n",
956 pStreamEx->Core.szName, PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPeriod), pCfgAcq->Backend.cFramesPeriod));
957
958 /* Was pre-buffering requested, but the acquired configuration from the backend told us something else? */
959 if (pCfgReq->Backend.cFramesPreBuffering)
960 {
961 if (pCfgAcq->Backend.cFramesPreBuffering != pCfgReq->Backend.cFramesPreBuffering)
962 LogRel2(("Audio: Pre-buffering size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n",
963 pStreamEx->Core.szName, PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPreBuffering), pCfgAcq->Backend.cFramesPreBuffering));
964
965 if (pCfgAcq->Backend.cFramesPreBuffering > pCfgAcq->Backend.cFramesBufferSize)
966 {
967 pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesBufferSize;
968 LogRel2(("Audio: Pre-buffering size bigger than buffer size for stream '%s', adjusting to %RU64ms (%RU32 frames)\n",
969 pStreamEx->Core.szName, PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPreBuffering), pCfgAcq->Backend.cFramesPreBuffering));
970 }
971 }
972 else if (pCfgReq->Backend.cFramesPreBuffering == 0) /* Was the pre-buffering requested as being disabeld? Tell the users. */
973 {
974 LogRel2(("Audio: Pre-buffering is disabled for stream '%s'\n", pStreamEx->Core.szName));
975 pCfgAcq->Backend.cFramesPreBuffering = 0;
976 }
977
978 /* Sanity for detecting buggy backends. */
979 AssertMsgReturn(pCfgAcq->Backend.cFramesPeriod < pCfgAcq->Backend.cFramesBufferSize,
980 ("Acquired period size must be smaller than buffer size\n"),
981 VERR_INVALID_PARAMETER);
982 AssertMsgReturn(pCfgAcq->Backend.cFramesPreBuffering <= pCfgAcq->Backend.cFramesBufferSize,
983 ("Acquired pre-buffering size must be smaller or as big as the buffer size\n"),
984 VERR_INVALID_PARAMETER);
985
986 pStreamEx->fLastBackendStatus = drvAudioStreamGetBackendStatus(pThis, pStreamEx);
987 pStreamEx->Core.fStatus |= PDMAUDIOSTREAM_STS_INITIALIZED;
988 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->Core.fStatus);
989
990 return VINF_SUCCESS;
991}
992
993
994/**
995 * Worker for drvAudioStreamCreate that initializes the audio stream.
996 *
997 * @returns VBox status code.
998 * @param pThis Pointer to driver instance.
999 * @param pStreamEx Stream to initialize.
1000 * @param fFlags PDMAUDIOSTREAM_CREATE_F_XXX.
1001 * @param pCfgHost Stream configuration to use for the host side (backend).
1002 * @param pCfgGuest Stream configuration to use for the guest side.
1003 */
1004static int drvAudioStreamInitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, uint32_t fFlags,
1005 PPDMAUDIOSTREAMCFG pCfgHost, PPDMAUDIOSTREAMCFG pCfgGuest)
1006{
1007 /*
1008 * Init host stream.
1009 */
1010 pStreamEx->Core.uMagic = PDMAUDIOSTREAM_MAGIC;
1011
1012 /* Set the host's default audio data layout. */
1013/** @todo r=bird: Why, oh why? OTOH, the layout stuff is non-sense anyway. */
1014 pCfgHost->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;
1015
1016#ifdef LOG_ENABLED
1017 LogFunc(("[%s] Requested host format:\n", pStreamEx->Core.szName));
1018 PDMAudioStrmCfgLog(pCfgHost);
1019#endif
1020
1021 LogRel2(("Audio: Creating stream '%s'\n", pStreamEx->Core.szName));
1022 LogRel2(("Audio: Guest %s format for '%s': %RU32Hz, %u%s, %RU8 channel%s\n",
1023 pCfgGuest->enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStreamEx->Core.szName,
1024 pCfgGuest->Props.uHz, PDMAudioPropsSampleBits(&pCfgGuest->Props), pCfgGuest->Props.fSigned ? "S" : "U",
1025 PDMAudioPropsChannels(&pCfgGuest->Props), PDMAudioPropsChannels(&pCfgGuest->Props) == 1 ? "" : "s"));
1026 LogRel2(("Audio: Requested host %s format for '%s': %RU32Hz, %u%s, %RU8 channel%s\n",
1027 pCfgHost->enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStreamEx->Core.szName,
1028 pCfgHost->Props.uHz, PDMAudioPropsSampleBits(&pCfgHost->Props), pCfgHost->Props.fSigned ? "S" : "U",
1029 PDMAudioPropsChannels(&pCfgHost->Props), PDMAudioPropsChannels(&pCfgHost->Props) == 1 ? "" : "s"));
1030
1031 PDMAUDIOSTREAMCFG CfgHostAcq;
1032 int rc = drvAudioStreamCreateInternalBackend(pThis, pStreamEx, pCfgHost, &CfgHostAcq);
1033 if (RT_FAILURE(rc))
1034 return rc;
1035
1036 LogFunc(("[%s] Acquired host format:\n", pStreamEx->Core.szName));
1037 PDMAudioStrmCfgLog(&CfgHostAcq);
1038 LogRel2(("Audio: Acquired host %s format for '%s': %RU32Hz, %u%s, %RU8 channel%s\n",
1039 CfgHostAcq.enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStreamEx->Core.szName,
1040 CfgHostAcq.Props.uHz, PDMAudioPropsSampleBits(&CfgHostAcq.Props), CfgHostAcq.Props.fSigned ? "S" : "U",
1041 PDMAudioPropsChannels(&CfgHostAcq.Props), PDMAudioPropsChannels(&CfgHostAcq.Props) == 1 ? "" : "s"));
1042 Assert(PDMAudioPropsAreValid(&CfgHostAcq.Props));
1043
1044 /* Set the stream properties (currently guest side, when DevSB16 is
1045 converted to mixer and PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF becomes
1046 default, this will just be the stream properties). */
1047 if (fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF)
1048 pStreamEx->Core.Props = CfgHostAcq.Props;
1049 else
1050 pStreamEx->Core.Props = pCfgGuest->Props;
1051
1052 /* Let the user know if the backend changed some of the tweakable values. */
1053 if (CfgHostAcq.Backend.cFramesBufferSize != pCfgHost->Backend.cFramesBufferSize)
1054 LogRel2(("Audio: Backend changed buffer size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",
1055 PDMAudioPropsFramesToMilli(&pCfgHost->Props, pCfgHost->Backend.cFramesBufferSize), pCfgHost->Backend.cFramesBufferSize,
1056 PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesBufferSize), CfgHostAcq.Backend.cFramesBufferSize));
1057
1058 if (CfgHostAcq.Backend.cFramesPeriod != pCfgHost->Backend.cFramesPeriod)
1059 LogRel2(("Audio: Backend changed period size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",
1060 PDMAudioPropsFramesToMilli(&pCfgHost->Props, pCfgHost->Backend.cFramesPeriod), pCfgHost->Backend.cFramesPeriod,
1061 PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesPeriod), CfgHostAcq.Backend.cFramesPeriod));
1062
1063 if (CfgHostAcq.Backend.cFramesPreBuffering != pCfgHost->Backend.cFramesPreBuffering)
1064 LogRel2(("Audio: Backend changed pre-buffering size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",
1065 PDMAudioPropsFramesToMilli(&pCfgHost->Props, pCfgHost->Backend.cFramesPreBuffering), pCfgHost->Backend.cFramesPreBuffering,
1066 PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesPreBuffering), CfgHostAcq.Backend.cFramesPreBuffering));
1067
1068 /*
1069 * Check if the backend did return sane values and correct if necessary.
1070 * Should never happen with our own backends, but you never know ...
1071 */
1072 uint32_t const cFramesPreBufferingMax = CfgHostAcq.Backend.cFramesBufferSize - RT_MIN(16, CfgHostAcq.Backend.cFramesBufferSize);
1073 if (CfgHostAcq.Backend.cFramesPreBuffering > cFramesPreBufferingMax)
1074 {
1075 LogRel2(("Audio: Warning: Pre-buffering size of %RU32 frames for stream '%s' is too close to or larger than the %RU32 frames buffer size, reducing it to %RU32 frames!\n",
1076 CfgHostAcq.Backend.cFramesPreBuffering, pStreamEx->Core.szName, CfgHostAcq.Backend.cFramesBufferSize, cFramesPreBufferingMax));
1077 AssertFailed();
1078 CfgHostAcq.Backend.cFramesPreBuffering = cFramesPreBufferingMax;
1079 }
1080
1081 if (CfgHostAcq.Backend.cFramesPeriod > CfgHostAcq.Backend.cFramesBufferSize)
1082 {
1083 LogRel2(("Audio: Warning: Period size of %RU32 frames for stream '%s' is larger than the %RU32 frames buffer size, reducing it to %RU32 frames!\n",
1084 CfgHostAcq.Backend.cFramesPeriod, pStreamEx->Core.szName, CfgHostAcq.Backend.cFramesBufferSize, CfgHostAcq.Backend.cFramesBufferSize / 2));
1085 AssertFailed();
1086 CfgHostAcq.Backend.cFramesPeriod = CfgHostAcq.Backend.cFramesBufferSize / 2;
1087 }
1088
1089 LogRel2(("Audio: Buffer size of stream '%s' is %RU64 ms / %RU32 frames\n", pStreamEx->Core.szName,
1090 PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesBufferSize), CfgHostAcq.Backend.cFramesBufferSize));
1091 LogRel2(("Audio: Pre-buffering size of stream '%s' is %RU64 ms / %RU32 frames\n", pStreamEx->Core.szName,
1092 PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesPreBuffering), CfgHostAcq.Backend.cFramesPreBuffering));
1093
1094 /* Make sure the configured buffer size by the backend at least can hold the configured latency. */
1095 const uint32_t msPeriod = PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesPeriod);
1096 LogRel2(("Audio: Period size of stream '%s' is %RU64 ms / %RU32 frames\n",
1097 pStreamEx->Core.szName, msPeriod, CfgHostAcq.Backend.cFramesPeriod));
1098
1099 if ( pCfgGuest->Device.cMsSchedulingHint /* Any scheduling hint set? */
1100 && pCfgGuest->Device.cMsSchedulingHint > msPeriod) /* This might lead to buffer underflows. */
1101 LogRel(("Audio: Warning: Scheduling hint of stream '%s' is bigger (%RU64ms) than used period size (%RU64ms)\n",
1102 pStreamEx->Core.szName, pCfgGuest->Device.cMsSchedulingHint, msPeriod));
1103
1104 /*
1105 * Make a copy of the acquired host stream configuration and the guest side one.
1106 */
1107 rc = PDMAudioStrmCfgCopy(&pStreamEx->Host.Cfg, &CfgHostAcq);
1108 AssertRC(rc);
1109
1110 /* Set the guests's default audio data layout. */
1111 pCfgGuest->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; /** @todo r=bird: WTF DO WE DO THIS? It's input and probably should've been const... */
1112 rc = PDMAudioStrmCfgCopy(&pStreamEx->Guest.Cfg, pCfgGuest);
1113 AssertRC(rc);
1114
1115 /*
1116 * Configure host buffers.
1117 */
1118
1119 /* Destroy any former mixing buffer. */
1120 AudioMixBufDestroy(&pStreamEx->Host.MixBuf);
1121
1122 if (!(fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF))
1123 {
1124 Assert(pCfgHost->enmDir == PDMAUDIODIR_IN);
1125 rc = AudioMixBufInit(&pStreamEx->Host.MixBuf, pStreamEx->Core.szName, &CfgHostAcq.Props, CfgHostAcq.Backend.cFramesBufferSize);
1126 AssertRCReturn(rc, rc);
1127 }
1128 /* Allocate space for pre-buffering of output stream w/o mixing buffers. */
1129 else if (pCfgHost->enmDir == PDMAUDIODIR_OUT)
1130 {
1131 Assert(pStreamEx->Out.cbPreBufAlloc == 0);
1132 Assert(pStreamEx->Out.cbPreBufThreshold == 0);
1133 Assert(pStreamEx->Out.cbPreBuffered == 0);
1134 Assert(pStreamEx->Out.offPreBuf == 0);
1135 if (CfgHostAcq.Backend.cFramesPreBuffering != 0)
1136 {
1137 pStreamEx->Out.cbPreBufThreshold = PDMAudioPropsFramesToBytes(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesPreBuffering);
1138 pStreamEx->Out.cbPreBufAlloc = PDMAudioPropsFramesToBytes(&CfgHostAcq.Props,
1139 CfgHostAcq.Backend.cFramesBufferSize - 2);
1140 pStreamEx->Out.cbPreBufAlloc = RT_MIN(RT_ALIGN_32(pStreamEx->Out.cbPreBufThreshold + _8K, _4K),
1141 pStreamEx->Out.cbPreBufAlloc);
1142 pStreamEx->Out.pbPreBuf = (uint8_t *)RTMemAllocZ(pStreamEx->Out.cbPreBufAlloc);
1143 AssertReturn(pStreamEx->Out.pbPreBuf, VERR_NO_MEMORY);
1144 }
1145 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY; /* Changed upon enable. */
1146 }
1147
1148 /*
1149 * Init guest stream.
1150 */
1151 if (pCfgGuest->Device.cMsSchedulingHint)
1152 LogRel2(("Audio: Stream '%s' got a scheduling hint of %RU32ms (%RU32 bytes)\n",
1153 pStreamEx->Core.szName, pCfgGuest->Device.cMsSchedulingHint,
1154 PDMAudioPropsMilliToBytes(&pCfgGuest->Props, pCfgGuest->Device.cMsSchedulingHint)));
1155
1156 /* Destroy any former mixing buffer. */
1157 AudioMixBufDestroy(&pStreamEx->Guest.MixBuf);
1158
1159 if (!(fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF))
1160 {
1161 Assert(pCfgHost->enmDir == PDMAUDIODIR_IN);
1162 rc = AudioMixBufInit(&pStreamEx->Guest.MixBuf, pStreamEx->Core.szName, &pCfgGuest->Props, CfgHostAcq.Backend.cFramesBufferSize);
1163 AssertRCReturn(rc, rc);
1164 }
1165
1166 if (RT_FAILURE(rc))
1167 LogRel(("Audio: Creating stream '%s' failed with %Rrc\n", pStreamEx->Core.szName, rc));
1168
1169 if (!(fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF))
1170 {
1171 Assert(pCfgHost->enmDir == PDMAUDIODIR_IN);
1172 /* Host (Parent) -> Guest (Child). */
1173 rc = AudioMixBufLinkTo(&pStreamEx->Host.MixBuf, &pStreamEx->Guest.MixBuf);
1174 AssertRC(rc);
1175 }
1176
1177 /*
1178 * Register statistics.
1179 */
1180 PPDMDRVINS const pDrvIns = pThis->pDrvIns;
1181 /** @todo expose config and more. */
1182 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Host.Cfg.Backend.cFramesBufferSize, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
1183 "Host side: The size of the backend buffer (in frames)", "%s/0-HostBackendBufSize", pStreamEx->Core.szName);
1184 if (!(fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF))
1185 {
1186 Assert(pCfgHost->enmDir == PDMAUDIODIR_IN);
1187 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Host.MixBuf.cFrames, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
1188 "Host side: The size of the mixer buffer (in frames)", "%s/1-HostMixBufSize", pStreamEx->Core.szName);
1189 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Guest.MixBuf.cFrames, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
1190 "Guest side: The size of the mixer buffer (in frames)", "%s/2-GuestMixBufSize", pStreamEx->Core.szName);
1191 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Host.MixBuf.cMixed, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
1192 "Host side: Number of frames in the mixer buffer", "%s/1-HostMixBufUsed", pStreamEx->Core.szName);
1193 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Guest.MixBuf.cUsed, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
1194 "Guest side: Number of frames in the mixer buffer", "%s/2-GuestMixBufUsed", pStreamEx->Core.szName);
1195 }
1196 if (pCfgGuest->enmDir == PDMAUDIODIR_IN)
1197 {
1198 /** @todo later? */
1199 }
1200 else
1201 {
1202 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.cbBackendWritableBefore, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
1203 "Host side: Free space in backend buffer before play", "%s/0-HostBackendBufFreeBefore", pStreamEx->Core.szName);
1204 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.cbBackendWritableAfter, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
1205 "Host side: Free space in backend buffer after play", "%s/0-HostBackendBufFreeAfter", pStreamEx->Core.szName);
1206 }
1207
1208#ifdef VBOX_WITH_STATISTICS
1209 if (pCfgGuest->enmDir == PDMAUDIODIR_IN)
1210 {
1211 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.TotalFramesCaptured, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_NONE,
1212 "Total frames played.", "%s/TotalFramesCaptured", pStreamEx->Core.szName);
1213 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.TotalTimesCaptured, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_NONE,
1214 "Total number of playbacks.", "%s/TotalTimesCaptured", pStreamEx->Core.szName);
1215 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.TotalTimesRead, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_NONE,
1216 "Total number of reads.", "%s/TotalTimesRead", pStreamEx->Core.szName);
1217 }
1218 else
1219 {
1220 Assert(pCfgGuest->enmDir == PDMAUDIODIR_OUT);
1221 }
1222#endif /* VBOX_WITH_STATISTICS */
1223
1224 LogFlowFunc(("[%s] Returning %Rrc\n", pStreamEx->Core.szName, rc));
1225 return rc;
1226}
1227
1228
1229/**
1230 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCreate}
1231 */
1232static DECLCALLBACK(int) drvAudioStreamCreate(PPDMIAUDIOCONNECTOR pInterface, uint32_t fFlags, PPDMAUDIOSTREAMCFG pCfgHost,
1233 PPDMAUDIOSTREAMCFG pCfgGuest, PPDMAUDIOSTREAM *ppStream)
1234{
1235 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
1236 AssertPtr(pThis);
1237
1238 /*
1239 * Assert sanity.
1240 */
1241 AssertReturn(!(fFlags & ~PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF), VERR_INVALID_FLAGS);
1242 AssertPtrReturn(pCfgHost, VERR_INVALID_POINTER);
1243 AssertPtrReturn(pCfgGuest, VERR_INVALID_POINTER);
1244 AssertPtrReturn(ppStream, VERR_INVALID_POINTER);
1245 LogFlowFunc(("Host=%s, Guest=%s\n", pCfgHost->szName, pCfgGuest->szName));
1246#ifdef LOG_ENABLED
1247 PDMAudioStrmCfgLog(pCfgHost);
1248 PDMAudioStrmCfgLog(pCfgGuest);
1249#endif
1250 AssertReturn(AudioHlpStreamCfgIsValid(pCfgHost), VERR_INVALID_PARAMETER);
1251 AssertReturn(AudioHlpStreamCfgIsValid(pCfgGuest), VERR_INVALID_PARAMETER);
1252 AssertReturn(pCfgHost->enmDir == pCfgGuest->enmDir, VERR_MISMATCH);
1253 AssertReturn(pCfgHost->enmDir == PDMAUDIODIR_IN || pCfgHost->enmDir == PDMAUDIODIR_OUT, VERR_NOT_SUPPORTED);
1254 /* Require PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF for output streams: */
1255 AssertReturn((fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF) || pCfgHost->enmDir == PDMAUDIODIR_IN, VERR_INVALID_FLAGS);
1256
1257 /*
1258 * Lock the whole driver instance.
1259 */
1260 int rc = RTCritSectEnter(&pThis->CritSect);
1261 AssertRCReturn(rc, rc);
1262
1263 /*
1264 * Check that we have free streams in the backend and get the
1265 * size of the backend specific stream data.
1266 */
1267 uint32_t *pcFreeStreams;
1268 if (pCfgHost->enmDir == PDMAUDIODIR_IN)
1269 {
1270 if (!pThis->In.cStreamsFree)
1271 {
1272 LogFlowFunc(("Maximum number of host input streams reached\n"));
1273 rc = VERR_AUDIO_NO_FREE_INPUT_STREAMS;
1274 }
1275 pcFreeStreams = &pThis->In.cStreamsFree;
1276 }
1277 else /* Out */
1278 {
1279 if (!pThis->Out.cStreamsFree)
1280 {
1281 LogFlowFunc(("Maximum number of host output streams reached\n"));
1282 rc = VERR_AUDIO_NO_FREE_OUTPUT_STREAMS;
1283 }
1284 pcFreeStreams = &pThis->Out.cStreamsFree;
1285 }
1286 size_t const cbHstStrm = pThis->BackendCfg.cbStream;
1287 AssertStmt(cbHstStrm >= sizeof(PDMAUDIOBACKENDSTREAM), rc = VERR_OUT_OF_RANGE);
1288 AssertStmt(cbHstStrm < _16M, rc = VERR_OUT_OF_RANGE);
1289 if (RT_SUCCESS(rc))
1290 {
1291 /*
1292 * Allocate and initialize common state.
1293 */
1294 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)RTMemAllocZ(sizeof(DRVAUDIOSTREAM) + RT_ALIGN_Z(cbHstStrm, 64));
1295 if (pStreamEx)
1296 {
1297 /* Retrieve host driver name for easier identification. */
1298 AssertPtr(pThis->pHostDrvAudio);
1299 RTStrPrintf(pStreamEx->Core.szName, RT_ELEMENTS(pStreamEx->Core.szName), "[%s] %s",
1300 pThis->BackendCfg.szName, pCfgHost->szName[0] != '\0' ? pCfgHost->szName : "<Untitled>");
1301
1302 PPDMAUDIOBACKENDSTREAM pBackend = (PPDMAUDIOBACKENDSTREAM)(pStreamEx + 1);
1303 pBackend->uMagic = PDMAUDIOBACKENDSTREAM_MAGIC;
1304 pBackend->pStream = &pStreamEx->Core;
1305 pStreamEx->pBackend = pBackend;
1306 pStreamEx->Core.enmDir = pCfgHost->enmDir;
1307 pStreamEx->Core.cbBackend = (uint32_t)cbHstStrm;
1308 pStreamEx->fNoMixBufs = RT_BOOL(fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF);
1309 pStreamEx->uMagic = DRVAUDIOSTREAM_MAGIC;
1310
1311 /*
1312 * Try to init the rest.
1313 */
1314 rc = drvAudioStreamInitInternal(pThis, pStreamEx, fFlags, pCfgHost, pCfgGuest);
1315 if (RT_SUCCESS(rc))
1316 {
1317 /* Set initial reference counts. */
1318 pStreamEx->Core.cRefs = 1;
1319
1320 /* Decrement the free stream counter. */
1321 Assert(*pcFreeStreams > 0);
1322 *pcFreeStreams -= 1;
1323
1324 /*
1325 * We're good.
1326 */
1327 RTListAppend(&pThis->lstStreams, &pStreamEx->ListEntry);
1328 STAM_COUNTER_INC(&pThis->Stats.TotalStreamsCreated);
1329 *ppStream = &pStreamEx->Core;
1330
1331 /*
1332 * Init debug stuff if enabled (ignore failures).
1333 */
1334 if (pCfgHost->enmDir == PDMAUDIODIR_IN)
1335 {
1336 if (pThis->In.Cfg.Dbg.fEnabled)
1337 {
1338 AudioHlpFileCreateAndOpen(&pStreamEx->In.Dbg.pFileCaptureNonInterleaved, pThis->In.Cfg.Dbg.szPathOut,
1339 "DrvAudioCapNonInt", pThis->pDrvIns->iInstance, &pStreamEx->Host.Cfg.Props);
1340 AudioHlpFileCreateAndOpen(&pStreamEx->In.Dbg.pFileStreamRead, pThis->In.Cfg.Dbg.szPathOut,
1341 "DrvAudioRead", pThis->pDrvIns->iInstance, &pStreamEx->Host.Cfg.Props);
1342 }
1343 }
1344 else /* Out */
1345 {
1346 if (pThis->Out.Cfg.Dbg.fEnabled)
1347 {
1348 AudioHlpFileCreateAndOpen(&pStreamEx->Out.Dbg.pFilePlayNonInterleaved, pThis->Out.Cfg.Dbg.szPathOut,
1349 "DrvAudioPlayNonInt", pThis->pDrvIns->iInstance, &pStreamEx->Host.Cfg.Props);
1350 AudioHlpFileCreateAndOpen(&pStreamEx->Out.Dbg.pFileStreamWrite, pThis->Out.Cfg.Dbg.szPathOut,
1351 "DrvAudioWrite", pThis->pDrvIns->iInstance, &pStreamEx->Host.Cfg.Props);
1352 }
1353 }
1354 }
1355 else
1356 {
1357 LogFunc(("drvAudioStreamInitInternal failed: %Rrc\n", rc));
1358 int rc2 = drvAudioStreamUninitInternal(pThis, pStreamEx);
1359 AssertRC(rc2);
1360 drvAudioStreamFree(pStreamEx);
1361 }
1362 }
1363 else
1364 rc = VERR_NO_MEMORY;
1365 }
1366
1367 RTCritSectLeave(&pThis->CritSect);
1368 LogFlowFuncLeaveRC(rc);
1369 return rc;
1370}
1371
1372
1373/**
1374 * Calls the backend to give it the chance to destroy its part of the audio stream.
1375 *
1376 * Called from drvAudioPowerOff, drvAudioStreamUninitInternal and
1377 * drvAudioStreamReInitInternal.
1378 *
1379 * @returns VBox status code.
1380 * @param pThis Pointer to driver instance.
1381 * @param pStreamEx Audio stream destruct backend for.
1382 */
1383static int drvAudioStreamDestroyInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
1384{
1385 AssertPtr(pThis);
1386 AssertPtr(pStreamEx);
1387
1388 int rc = VINF_SUCCESS;
1389
1390#ifdef LOG_ENABLED
1391 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
1392#endif
1393 LogFunc(("[%s] fStatus=%s\n", pStreamEx->Core.szName, dbgAudioStreamStatusToStr(szStreamSts, pStreamEx->Core.fStatus)));
1394
1395 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_INITIALIZED)
1396 {
1397 AssertPtr(pStreamEx->pBackend);
1398
1399 /* Check if the pointer to the host audio driver is still valid.
1400 * It can be NULL if we were called in drvAudioDestruct, for example. */
1401 if (pThis->pHostDrvAudio)
1402 rc = pThis->pHostDrvAudio->pfnStreamDestroy(pThis->pHostDrvAudio, pStreamEx->pBackend);
1403
1404 pStreamEx->Core.fStatus &= ~PDMAUDIOSTREAM_STS_INITIALIZED;
1405 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->Core.fStatus);
1406 }
1407
1408 LogFlowFunc(("[%s] Returning %Rrc\n", pStreamEx->Core.szName, rc));
1409 return rc;
1410}
1411
1412
1413/**
1414 * Uninitializes an audio stream - worker for drvAudioStreamDestroy,
1415 * drvAudioDestruct and drvAudioStreamCreate.
1416 *
1417 * @returns VBox status code.
1418 * @param pThis Pointer to driver instance.
1419 * @param pStreamEx Pointer to audio stream to uninitialize.
1420 *
1421 * @note Caller owns the critical section.
1422 */
1423static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
1424{
1425 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1426 AssertMsgReturn(pStreamEx->Core.cRefs <= 1,
1427 ("Stream '%s' still has %RU32 references held when uninitializing\n", pStreamEx->Core.szName, pStreamEx->Core.cRefs),
1428 VERR_WRONG_ORDER);
1429 LogFlowFunc(("[%s] cRefs=%RU32\n", pStreamEx->Core.szName, pStreamEx->Core.cRefs));
1430
1431 /*
1432 * ...
1433 */
1434 int rc = drvAudioStreamControlInternal(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
1435 if (RT_SUCCESS(rc))
1436 rc = drvAudioStreamDestroyInternalBackend(pThis, pStreamEx);
1437
1438 /* Destroy mixing buffers. */
1439 AudioMixBufDestroy(&pStreamEx->Guest.MixBuf);
1440 AudioMixBufDestroy(&pStreamEx->Host.MixBuf);
1441
1442 /* Free pre-buffer space. */
1443 if ( pStreamEx->Core.enmDir == PDMAUDIODIR_OUT
1444 && pStreamEx->Out.pbPreBuf)
1445 {
1446 RTMemFree(pStreamEx->Out.pbPreBuf);
1447 pStreamEx->Out.pbPreBuf = NULL;
1448 pStreamEx->Out.cbPreBufAlloc = 0;
1449 pStreamEx->Out.cbPreBuffered = 0;
1450 pStreamEx->Out.offPreBuf = 0;
1451 }
1452
1453 if (RT_SUCCESS(rc))
1454 {
1455#ifdef LOG_ENABLED
1456 if (pStreamEx->Core.fStatus != PDMAUDIOSTREAM_STS_NONE)
1457 {
1458 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
1459 LogFunc(("[%s] Warning: Still has %s set when uninitializing\n",
1460 pStreamEx->Core.szName, dbgAudioStreamStatusToStr(szStreamSts, pStreamEx->Core.fStatus)));
1461 }
1462#endif
1463 pStreamEx->Core.fStatus = PDMAUDIOSTREAM_STS_NONE;
1464 }
1465
1466 PPDMDRVINS const pDrvIns = pThis->pDrvIns;
1467 PDMDrvHlpSTAMDeregisterByPrefix(pDrvIns, pStreamEx->Core.szName);
1468
1469 if (pStreamEx->Core.enmDir == PDMAUDIODIR_IN)
1470 {
1471 if (pThis->In.Cfg.Dbg.fEnabled)
1472 {
1473 AudioHlpFileDestroy(pStreamEx->In.Dbg.pFileCaptureNonInterleaved);
1474 pStreamEx->In.Dbg.pFileCaptureNonInterleaved = NULL;
1475
1476 AudioHlpFileDestroy(pStreamEx->In.Dbg.pFileStreamRead);
1477 pStreamEx->In.Dbg.pFileStreamRead = NULL;
1478 }
1479 }
1480 else
1481 {
1482 Assert(pStreamEx->Core.enmDir == PDMAUDIODIR_OUT);
1483 if (pThis->Out.Cfg.Dbg.fEnabled)
1484 {
1485 AudioHlpFileDestroy(pStreamEx->Out.Dbg.pFilePlayNonInterleaved);
1486 pStreamEx->Out.Dbg.pFilePlayNonInterleaved = NULL;
1487
1488 AudioHlpFileDestroy(pStreamEx->Out.Dbg.pFileStreamWrite);
1489 pStreamEx->Out.Dbg.pFileStreamWrite = NULL;
1490 }
1491 }
1492 LogFlowFunc(("Returning %Rrc\n", rc));
1493 return rc;
1494}
1495
1496
1497/**
1498 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamDestroy}
1499 */
1500static DECLCALLBACK(int) drvAudioStreamDestroy(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
1501{
1502 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
1503 AssertPtr(pThis);
1504
1505 if (!pStream)
1506 return VINF_SUCCESS;
1507 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1508 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream; /* Note! Do not touch pStream after this! */
1509 Assert(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC);
1510 Assert(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC);
1511 Assert(pStreamEx->pBackend && pStreamEx->pBackend->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC);
1512
1513 int rc = RTCritSectEnter(&pThis->CritSect);
1514 AssertRCReturn(rc, rc);
1515
1516 LogRel2(("Audio: Destroying stream '%s'\n", pStreamEx->Core.szName));
1517
1518 LogFlowFunc(("[%s] cRefs=%RU32\n", pStreamEx->Core.szName, pStreamEx->Core.cRefs));
1519 AssertMsg(pStreamEx->Core.cRefs <= 1, ("%u %s\n", pStreamEx->Core.cRefs, pStreamEx->Core.szName));
1520 if (pStreamEx->Core.cRefs <= 1)
1521 {
1522 rc = drvAudioStreamUninitInternal(pThis, pStreamEx);
1523 if (RT_SUCCESS(rc))
1524 {
1525 if (pStreamEx->Core.enmDir == PDMAUDIODIR_IN)
1526 pThis->In.cStreamsFree++;
1527 else /* Out */
1528 pThis->Out.cStreamsFree++;
1529
1530 RTListNodeRemove(&pStreamEx->ListEntry);
1531
1532 drvAudioStreamFree(pStreamEx);
1533 pStreamEx = NULL;
1534 pStream = NULL;
1535 }
1536 else
1537 LogRel(("Audio: Uninitializing stream '%s' failed with %Rrc\n", pStreamEx->Core.szName, rc));
1538 }
1539 else
1540 rc = VERR_WRONG_ORDER;
1541
1542 RTCritSectLeave(&pThis->CritSect);
1543 LogFlowFuncLeaveRC(rc);
1544 return rc;
1545}
1546
1547
1548/**
1549 * Drops all audio data (and associated state) of a stream.
1550 *
1551 * Used by drvAudioStreamIterateInternal(), drvAudioStreamResetInternal(), and
1552 * drvAudioStreamReInitInternal().
1553 *
1554 * @param pStreamEx Stream to drop data for.
1555 */
1556static void drvAudioStreamDropInternal(PDRVAUDIOSTREAM pStreamEx)
1557{
1558 LogFunc(("[%s]\n", pStreamEx->Core.szName));
1559
1560 if (pStreamEx->fNoMixBufs)
1561 {
1562 AudioMixBufReset(&pStreamEx->Guest.MixBuf);
1563 AudioMixBufReset(&pStreamEx->Host.MixBuf);
1564 }
1565
1566 pStreamEx->nsLastIterated = 0;
1567 pStreamEx->nsLastPlayedCaptured = 0;
1568 pStreamEx->nsLastReadWritten = 0;
1569 if (pStreamEx->Host.Cfg.enmDir == PDMAUDIODIR_OUT)
1570 {
1571 pStreamEx->Out.cbPreBuffered = 0;
1572 pStreamEx->Out.offPreBuf = 0;
1573 pStreamEx->Out.enmPlayState = pStreamEx->Out.cbPreBufThreshold > 0
1574 ? DRVAUDIOPLAYSTATE_PREBUF : DRVAUDIOPLAYSTATE_PLAY;
1575 }
1576}
1577
1578
1579/**
1580 * Re-initializes an audio stream with its existing host and guest stream
1581 * configuration.
1582 *
1583 * This might be the case if the backend told us we need to re-initialize
1584 * because something on the host side has changed.
1585 *
1586 * @note Does not touch the stream's status flags.
1587 *
1588 * @returns VBox status code.
1589 * @param pThis Pointer to driver instance.
1590 * @param pStreamEx Stream to re-initialize.
1591 */
1592static int drvAudioStreamReInitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
1593{
1594 AssertPtr(pThis);
1595 AssertPtr(pStreamEx);
1596
1597 LogFlowFunc(("[%s]\n", pStreamEx->Core.szName));
1598
1599 /*
1600 * Gather current stream status.
1601 */
1602 const bool fIsEnabled = RT_BOOL(pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_ENABLED); /* Stream is enabled? */
1603
1604/** @todo r=bird: this is retried a bit too indiscriminately for my taste ... */
1605 /*
1606 * Destroy and re-create stream on backend side.
1607 */
1608 int rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
1609 if (RT_SUCCESS(rc))
1610 {
1611 rc = drvAudioStreamDestroyInternalBackend(pThis, pStreamEx);
1612 if (RT_SUCCESS(rc))
1613 {
1614 PDMAUDIOSTREAMCFG CfgHostAcq;
1615 rc = drvAudioStreamCreateInternalBackend(pThis, pStreamEx, &pStreamEx->Host.Cfg, &CfgHostAcq);
1616 /** @todo Validate (re-)acquired configuration with pStreamEx->Core.Host.Cfg? */
1617 if (RT_SUCCESS(rc))
1618 {
1619#ifdef LOG_ENABLED
1620 LogFunc(("[%s] Acquired host format:\n", pStreamEx->Core.szName));
1621 PDMAudioStrmCfgLog(&CfgHostAcq);
1622#endif
1623 }
1624 }
1625 }
1626
1627 /* Drop all old data. */
1628 drvAudioStreamDropInternal(pStreamEx);
1629
1630 /*
1631 * Restore previous stream state.
1632 */
1633 if (fIsEnabled)
1634 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_ENABLE);
1635
1636 if (RT_FAILURE(rc))
1637 LogRel(("Audio: Re-initializing stream '%s' failed with %Rrc\n", pStreamEx->Core.szName, rc));
1638
1639 LogFunc(("[%s] Returning %Rrc\n", pStreamEx->Core.szName, rc));
1640 return rc;
1641}
1642
1643
1644/**
1645 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamReInit}
1646 */
1647static DECLCALLBACK(int) drvAudioStreamReInit(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
1648{
1649 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
1650 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
1651 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
1652 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
1653 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
1654 AssertReturn(pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_NEED_REINIT, VERR_INVALID_STATE);
1655 LogFlowFunc(("\n"));
1656
1657 int rc = RTCritSectEnter(&pThis->CritSect);
1658 AssertRCReturn(rc, rc);
1659
1660 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_NEED_REINIT)
1661 {
1662 const unsigned cMaxTries = 3; /** @todo Make this configurable? */
1663 const uint64_t tsNowNs = RTTimeNanoTS();
1664
1665 /* Throttle re-initializing streams on failure. */
1666 if ( pStreamEx->cTriesReInit < cMaxTries
1667 && ( pStreamEx->nsLastReInit == 0
1668 || tsNowNs - pStreamEx->nsLastReInit >= RT_NS_1SEC * pStreamEx->cTriesReInit)) /** @todo Ditto. */
1669 {
1670#ifdef VBOX_WITH_AUDIO_ENUM
1671/** @todo do this elsewhere. */
1672 if (pThis->fEnumerateDevices)
1673 {
1674 /* Make sure to leave the driver's critical section before enumerating host stuff. */
1675 int rc2 = RTCritSectLeave(&pThis->CritSect);
1676 AssertRC(rc2);
1677
1678 /* Re-enumerate all host devices. */
1679 drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */);
1680
1681 /* Re-enter the critical section again. */
1682 rc2 = RTCritSectEnter(&pThis->CritSect);
1683 AssertRC(rc2);
1684
1685 pThis->fEnumerateDevices = false;
1686 }
1687#endif /* VBOX_WITH_AUDIO_ENUM */
1688
1689 rc = drvAudioStreamReInitInternal(pThis, pStreamEx);
1690 if (RT_SUCCESS(rc))
1691 {
1692 /* Remove the pending re-init flag on success. */
1693 pStreamEx->Core.fStatus &= ~PDMAUDIOSTREAM_STS_NEED_REINIT;
1694 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->Core.fStatus);
1695 }
1696 else
1697 {
1698 pStreamEx->cTriesReInit++;
1699 pStreamEx->nsLastReInit = tsNowNs;
1700 }
1701 }
1702 else
1703 {
1704 /* Did we exceed our tries re-initializing the stream?
1705 * Then this one is dead-in-the-water, so disable it for further use. */
1706/** @todo r=bird: This should be done above when drvAudioStreamReInitInternal fails! Duh^2! */
1707 if (pStreamEx->cTriesReInit == cMaxTries)
1708 {
1709 LogRel(("Audio: Re-initializing stream '%s' exceeded maximum retries (%u), leaving as disabled\n",
1710 pStreamEx->Core.szName, cMaxTries));
1711
1712 /* Don't try to re-initialize anymore and mark as disabled. */
1713 /** @todo should mark it as not-initialized too, shouldn't we? */
1714 pStreamEx->Core.fStatus &= ~(PDMAUDIOSTREAM_STS_NEED_REINIT | PDMAUDIOSTREAM_STS_ENABLED);
1715 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->Core.fStatus);
1716
1717 /* Note: Further writes to this stream go to / will be read from the bit bucket (/dev/null) from now on. */
1718 }
1719 }
1720
1721#ifdef LOG_ENABLED
1722 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
1723#endif
1724 Log3Func(("[%s] fStatus=%s\n", pStreamEx->Core.szName, dbgAudioStreamStatusToStr(szStreamSts, pStreamEx->Core.fStatus)));
1725 }
1726 else
1727 {
1728 AssertFailed();
1729 rc = VERR_INVALID_STATE;
1730 }
1731
1732 RTCritSectLeave(&pThis->CritSect);
1733 LogFlowFuncLeaveRC(rc);
1734 return rc;
1735}
1736
1737
1738/**
1739 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRetain}
1740 */
1741static DECLCALLBACK(uint32_t) drvAudioStreamRetain(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
1742{
1743 AssertPtrReturn(pInterface, UINT32_MAX);
1744 AssertPtrReturn(pStream, UINT32_MAX);
1745 AssertReturn(pStream->uMagic == PDMAUDIOSTREAM_MAGIC, UINT32_MAX);
1746 AssertReturn(((PDRVAUDIOSTREAM)pStream)->uMagic == DRVAUDIOSTREAM_MAGIC, UINT32_MAX);
1747 RT_NOREF(pInterface);
1748
1749 uint32_t const cRefs = ASMAtomicIncU32(&pStream->cRefs);
1750 Assert(cRefs > 1);
1751 Assert(cRefs < _1K);
1752
1753 return cRefs;
1754}
1755
1756
1757/**
1758 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRelease}
1759 */
1760static DECLCALLBACK(uint32_t) drvAudioStreamRelease(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
1761{
1762 AssertPtrReturn(pInterface, UINT32_MAX);
1763 AssertPtrReturn(pStream, UINT32_MAX);
1764 AssertReturn(pStream->uMagic == PDMAUDIOSTREAM_MAGIC, UINT32_MAX);
1765 AssertReturn(((PDRVAUDIOSTREAM)pStream)->uMagic == DRVAUDIOSTREAM_MAGIC, UINT32_MAX);
1766 RT_NOREF(pInterface);
1767
1768 uint32_t cRefs = ASMAtomicDecU32(&pStream->cRefs);
1769 AssertStmt(cRefs >= 1, cRefs = ASMAtomicIncU32(&pStream->cRefs));
1770 Assert(cRefs < _1K);
1771
1772 return cRefs;
1773}
1774
1775
1776/**
1777 * Controls a stream's backend.
1778 *
1779 * If the stream has no backend available, VERR_NOT_FOUND is returned
1780 * (bird: actually the code returns VINF_SUCCESS).
1781 *
1782 * @returns VBox status code.
1783 * @param pThis Pointer to driver instance.
1784 * @param pStreamEx Stream to control.
1785 * @param enmStreamCmd Control command.
1786 */
1787static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd)
1788{
1789 AssertPtr(pThis);
1790 AssertPtr(pStreamEx);
1791
1792#ifdef LOG_ENABLED
1793 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
1794#endif
1795 LogFlowFunc(("[%s] enmStreamCmd=%s, fStatus=%s\n", pStreamEx->Core.szName, PDMAudioStrmCmdGetName(enmStreamCmd),
1796 dbgAudioStreamStatusToStr(szStreamSts, pStreamEx->Core.fStatus)));
1797
1798 if (!pThis->pHostDrvAudio) /* If not lower driver is configured, bail out. */
1799 return VINF_SUCCESS;
1800
1801
1802 /*
1803 * Whether to propagate commands down to the backend.
1804 *
1805 * This is needed for critical operations like recording audio if audio input is disabled on a per-driver level.
1806 *
1807 * Note that not all commands will be covered by this, such as operations like stopping, draining and droppping,
1808 * which are considered uncritical and sometimes even are required for certain backends (like DirectSound on Windows).
1809 *
1810 * The actual stream state will be untouched to not make the state machine handling more complicated than
1811 * it already is.
1812 *
1813 * See @bugref{9882}.
1814 */
1815 const bool fEnabled = ( pStreamEx->Core.enmDir == PDMAUDIODIR_IN
1816 && pThis->In.fEnabled)
1817 || ( pStreamEx->Core.enmDir == PDMAUDIODIR_OUT
1818 && pThis->Out.fEnabled);
1819
1820 LogRel2(("Audio: %s stream '%s' in backend (%s is %s)\n", PDMAudioStrmCmdGetName(enmStreamCmd), pStreamEx->Core.szName,
1821 PDMAudioDirGetName(pStreamEx->Core.enmDir),
1822 fEnabled ? "enabled" : "disabled"));
1823 int rc = VINF_SUCCESS;
1824 switch (enmStreamCmd)
1825 {
1826 case PDMAUDIOSTREAMCMD_ENABLE:
1827 {
1828 if (fEnabled)
1829 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStreamEx->pBackend, PDMAUDIOSTREAMCMD_ENABLE);
1830 break;
1831 }
1832
1833 case PDMAUDIOSTREAMCMD_DISABLE:
1834 {
1835 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStreamEx->pBackend, PDMAUDIOSTREAMCMD_DISABLE);
1836 break;
1837 }
1838
1839 case PDMAUDIOSTREAMCMD_PAUSE:
1840 {
1841 if (fEnabled) /* Needed, as resume below also is being checked for. */
1842 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStreamEx->pBackend, PDMAUDIOSTREAMCMD_PAUSE);
1843 break;
1844 }
1845
1846 case PDMAUDIOSTREAMCMD_RESUME:
1847 {
1848 if (fEnabled)
1849 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStreamEx->pBackend, PDMAUDIOSTREAMCMD_RESUME);
1850 break;
1851 }
1852
1853 case PDMAUDIOSTREAMCMD_DRAIN:
1854 {
1855 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStreamEx->pBackend, PDMAUDIOSTREAMCMD_DRAIN);
1856 break;
1857 }
1858
1859 default:
1860 AssertMsgFailedReturn(("Command %RU32 not implemented\n", enmStreamCmd), VERR_INTERNAL_ERROR_2);
1861 }
1862
1863 if (RT_FAILURE(rc))
1864 {
1865 if ( rc != VERR_NOT_IMPLEMENTED
1866 && rc != VERR_NOT_SUPPORTED
1867 && rc != VERR_AUDIO_STREAM_NOT_READY)
1868 {
1869 LogRel(("Audio: %s stream '%s' failed with %Rrc\n", PDMAudioStrmCmdGetName(enmStreamCmd), pStreamEx->Core.szName, rc));
1870 }
1871
1872 LogFunc(("[%s] %s failed with %Rrc\n", pStreamEx->Core.szName, PDMAudioStrmCmdGetName(enmStreamCmd), rc));
1873 }
1874
1875 return rc;
1876}
1877
1878
1879/**
1880 * Resets the given audio stream.
1881 *
1882 * @param pStreamEx Stream to reset.
1883 */
1884static void drvAudioStreamResetInternal(PDRVAUDIOSTREAM pStreamEx)
1885{
1886 drvAudioStreamDropInternal(pStreamEx);
1887
1888 LogFunc(("[%s]\n", pStreamEx->Core.szName));
1889
1890 pStreamEx->Core.fStatus = PDMAUDIOSTREAM_STS_INITIALIZED;
1891 pStreamEx->Core.fWarningsShown = PDMAUDIOSTREAM_WARN_FLAGS_NONE;
1892
1893#ifdef VBOX_WITH_STATISTICS
1894 /*
1895 * Reset statistics.
1896 */
1897 if (pStreamEx->Core.enmDir == PDMAUDIODIR_IN)
1898 {
1899 STAM_COUNTER_RESET(&pStreamEx->In.Stats.TotalFramesCaptured);
1900 STAM_COUNTER_RESET(&pStreamEx->In.Stats.TotalTimesCaptured);
1901 STAM_COUNTER_RESET(&pStreamEx->In.Stats.TotalTimesRead);
1902 }
1903 else if (pStreamEx->Core.enmDir == PDMAUDIODIR_OUT)
1904 {
1905 }
1906 else
1907 AssertFailed();
1908#endif
1909}
1910
1911
1912/**
1913 * @callback_method_impl{FNTMTIMERDRV}
1914 */
1915static DECLCALLBACK(void) drvAudioEmergencyIterateTimer(PPDMDRVINS pDrvIns, TMTIMERHANDLE hTimer, void *pvUser)
1916{
1917 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
1918 RT_NOREF(hTimer, pvUser);
1919 RTCritSectEnter(&pThis->CritSect);
1920
1921 /*
1922 * Iterate any stream with the pending-disable flag set.
1923 */
1924 uint32_t cMilliesToNext = 0;
1925 PDRVAUDIOSTREAM pStreamEx, pStreamExNext;
1926 RTListForEachSafe(&pThis->lstStreams, pStreamEx, pStreamExNext, DRVAUDIOSTREAM, ListEntry)
1927 {
1928 if ( pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC
1929 && pStreamEx->Core.cRefs >= 1)
1930 {
1931 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)
1932 {
1933 drvAudioStreamIterateInternal(pThis, pStreamEx);
1934
1935 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)
1936 cMilliesToNext = 10;
1937 }
1938 }
1939 }
1940
1941 /*
1942 * Re-arm the timer if we still got streams in the pending state.
1943 */
1944 if (cMilliesToNext)
1945 {
1946 pThis->fTimerArmed = true;
1947 PDMDrvHlpTimerSetMillies(pDrvIns, pThis->hTimer, cMilliesToNext);
1948 }
1949 else
1950 pThis->fTimerArmed = false;
1951
1952 RTCritSectLeave(&pThis->CritSect);
1953}
1954
1955
1956/**
1957 * Controls an audio stream.
1958 *
1959 * @returns VBox status code.
1960 * @param pThis Pointer to driver instance.
1961 * @param pStreamEx Stream to control.
1962 * @param enmStreamCmd Control command.
1963 */
1964static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd)
1965{
1966 AssertPtr(pThis);
1967 AssertPtr(pStreamEx);
1968
1969#ifdef LOG_ENABLED
1970 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
1971#endif
1972 LogFunc(("[%s] enmStreamCmd=%s fStatus=%s\n", pStreamEx->Core.szName, PDMAudioStrmCmdGetName(enmStreamCmd),
1973 dbgAudioStreamStatusToStr(szStreamSts, pStreamEx->Core.fStatus)));
1974
1975 int rc = VINF_SUCCESS;
1976
1977 switch (enmStreamCmd)
1978 {
1979 case PDMAUDIOSTREAMCMD_ENABLE:
1980 {
1981 if (!(pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_ENABLED))
1982 {
1983 /* Is a pending disable outstanding? Then disable first. */
1984 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)
1985 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
1986 if (RT_SUCCESS(rc))
1987 {
1988 /* Reset the play state before we try to start. */
1989 pStreamEx->fLastBackendStatus = drvAudioStreamGetBackendStatus(pThis, pStreamEx);
1990 if (pStreamEx->Core.enmDir == PDMAUDIODIR_OUT)
1991 {
1992 pStreamEx->Out.cbPreBuffered = 0;
1993 pStreamEx->Out.offPreBuf = 0;
1994 pStreamEx->Out.enmPlayState = pStreamEx->Out.cbPreBufThreshold > 0
1995 ? DRVAUDIOPLAYSTATE_PREBUF
1996 : pStreamEx->fLastBackendStatus & PDMAUDIOSTREAM_STS_INITIALIZED
1997 ? DRVAUDIOPLAYSTATE_PLAY
1998 : DRVAUDIOPLAYSTATE_NOPLAY;
1999 LogFunc(("ENABLE: fLastBackendStatus=%#x enmPlayState=%s\n",
2000 pStreamEx->fLastBackendStatus, drvAudioPlayStateName(pStreamEx->Out.enmPlayState)));
2001 }
2002 else
2003 LogFunc(("ENABLE: fLastBackendStatus=%#x\n", pStreamEx->fLastBackendStatus));
2004
2005 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_ENABLE);
2006 if (RT_SUCCESS(rc))
2007 {
2008 pStreamEx->Core.fStatus |= PDMAUDIOSTREAM_STS_ENABLED;
2009 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->Core.fStatus);
2010 }
2011 }
2012 }
2013 break;
2014 }
2015
2016 case PDMAUDIOSTREAMCMD_DISABLE:
2017 {
2018 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_ENABLED)
2019 {
2020 /*
2021 * For playback (output) streams first mark the host stream as pending disable,
2022 * so that the rest of the remaining audio data will be played first before
2023 * closing the stream.
2024 */
2025 if (pStreamEx->Core.enmDir == PDMAUDIODIR_OUT)
2026 {
2027 LogFunc(("[%s] Pending disable/pause\n", pStreamEx->Core.szName));
2028 pStreamEx->Core.fStatus |= PDMAUDIOSTREAM_STS_PENDING_DISABLE;
2029 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->Core.fStatus);
2030
2031 /* Schedule a follow up timer to the pending-disable state. We cannot rely
2032 on the device to provide further callouts to finish the state transition.
2033 10ms is taking out of thin air and may be too course grained, we should
2034 really consider the amount of unplayed buffer in the backend and what not... */
2035 if (!pThis->fTimerArmed)
2036 {
2037 LogFlowFunc(("Arming emergency pending-disable hack...\n"));
2038 int rc2 = PDMDrvHlpTimerSetMillies(pThis->pDrvIns, pThis->hTimer, 10 /*ms*/);
2039 AssertRC(rc2);
2040 pThis->fTimerArmed = true;
2041 }
2042 }
2043
2044 /* Can we close the host stream as well (not in pending disable mode)? */
2045 if (!(pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE))
2046 {
2047 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
2048 if (RT_SUCCESS(rc))
2049 drvAudioStreamResetInternal(pStreamEx);
2050 }
2051 }
2052 break;
2053 }
2054
2055 case PDMAUDIOSTREAMCMD_PAUSE:
2056 {
2057 if (!(pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_PAUSED))
2058 {
2059 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_PAUSE);
2060 if (RT_SUCCESS(rc))
2061 {
2062 pStreamEx->Core.fStatus |= PDMAUDIOSTREAM_STS_PAUSED;
2063 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->Core.fStatus);
2064 }
2065 }
2066 break;
2067 }
2068
2069 case PDMAUDIOSTREAMCMD_RESUME:
2070 {
2071 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_PAUSED)
2072 {
2073 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_RESUME);
2074 if (RT_SUCCESS(rc))
2075 {
2076 pStreamEx->Core.fStatus &= ~PDMAUDIOSTREAM_STS_PAUSED;
2077 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->Core.fStatus);
2078 }
2079 }
2080 break;
2081 }
2082
2083 default:
2084 rc = VERR_NOT_IMPLEMENTED;
2085 break;
2086 }
2087
2088 if (RT_FAILURE(rc))
2089 LogFunc(("[%s] Failed with %Rrc\n", pStreamEx->Core.szName, rc));
2090
2091 return rc;
2092}
2093
2094
2095/**
2096 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamControl}
2097 */
2098static DECLCALLBACK(int) drvAudioStreamControl(PPDMIAUDIOCONNECTOR pInterface,
2099 PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
2100{
2101 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
2102 AssertPtr(pThis);
2103
2104 /** @todo r=bird: why? It's not documented to ignore NULL streams. */
2105 if (!pStream)
2106 return VINF_SUCCESS;
2107 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
2108 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
2109 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
2110 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
2111
2112 int rc = RTCritSectEnter(&pThis->CritSect);
2113 AssertRCReturn(rc, rc);
2114
2115 LogFlowFunc(("[%s] enmStreamCmd=%s\n", pStream->szName, PDMAudioStrmCmdGetName(enmStreamCmd)));
2116
2117 rc = drvAudioStreamControlInternal(pThis, pStreamEx, enmStreamCmd);
2118
2119 RTCritSectLeave(&pThis->CritSect);
2120 return rc;
2121}
2122
2123
2124/**
2125 * Copy data to the pre-buffer, ring-buffer style.
2126 *
2127 * This is used in two slightly different situations:
2128 *
2129 * -# When the stream is started (enabled) and we only want to prebuffer up
2130 * to the threshold before pushing the data to the backend. We
2131 * typically use the max buffer size for this situation.
2132 *
2133 * -# When the backend sets the PDMAUDIOSTREAM_STS_PREPARING_SWITCH
2134 * status bit and we're preparing for a smooth switch over to a
2135 * different audio device. Most of the pre-buffered data should not be
2136 * played on the old device prior to the switch, due to the prebuffering
2137 * at the start of the stream. We only use the threshold size for this
2138 * case.
2139 */
2140static int drvAudioStreamPreBuffer(PDRVAUDIOSTREAM pStreamEx, const uint8_t *pbBuf, uint32_t cbBuf, uint32_t cbMax)
2141{
2142 uint32_t const cbAlloc = pStreamEx->Out.cbPreBufAlloc;
2143 AssertReturn(cbAlloc >= cbMax, VERR_INTERNAL_ERROR_3);
2144 AssertReturn(cbAlloc >= 8, VERR_INTERNAL_ERROR_4);
2145 AssertReturn(cbMax >= 8, VERR_INTERNAL_ERROR_5);
2146
2147 uint32_t offRead = pStreamEx->Out.offPreBuf;
2148 uint32_t cbCur = pStreamEx->Out.cbPreBuffered;
2149 AssertStmt(offRead < cbAlloc, offRead %= cbAlloc);
2150 AssertStmt(cbCur <= cbMax, offRead = (offRead + cbCur - cbMax) % cbAlloc; cbCur = cbMax);
2151
2152 /*
2153 * First chunk.
2154 */
2155 uint32_t offWrite = (offRead + cbCur) % cbAlloc;
2156 uint32_t cbToCopy = RT_MIN(cbAlloc - offWrite, cbBuf);
2157 memcpy(&pStreamEx->Out.pbPreBuf[offWrite], pbBuf, cbToCopy);
2158
2159 /* Advance. */
2160 offWrite = (offWrite + cbToCopy) % cbAlloc;
2161 for (;;)
2162 {
2163 pbBuf += cbToCopy;
2164 cbCur += cbToCopy;
2165 if (cbCur > cbMax)
2166 offRead = (offRead + cbCur - cbMax) % cbAlloc;
2167 cbBuf -= cbToCopy;
2168 if (!cbBuf)
2169 break;
2170
2171 /*
2172 * Second+ chunk, from the start of the buffer.
2173 *
2174 * Note! It is assumed very unlikely that we will ever see a cbBuf larger than
2175 * cbMax, so we don't waste space on clipping cbBuf here (can happen with
2176 * custom pre-buffer sizes).
2177 */
2178 Assert(offWrite == 0);
2179 cbToCopy = RT_MIN(cbAlloc, cbBuf);
2180 memcpy(pStreamEx->Out.pbPreBuf, pbBuf, cbToCopy);
2181 }
2182
2183 /*
2184 * Update the pre-buffering size and position.
2185 */
2186 pStreamEx->Out.cbPreBuffered = cbCur;
2187 pStreamEx->Out.offPreBuf = offRead;
2188 return VINF_SUCCESS;
2189}
2190
2191
2192#if 0
2193/**
2194 * Worker for drvAudioStreamPlay() and drvAudioStreamIterateInternal().
2195 *
2196 * The buffer is NULL and has a zero length when called from the interate
2197 * function. This only occures when there is pre-buffered audio data that need
2198 * to be pushed to the backend due to a pending disabling of the stream.
2199 *
2200 * Caller owns the lock.
2201 */
2202static int drvAudioStreamPlayLocked(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, uint32_t fBackStatus,
2203 const uint8_t *pbBuf, uint32_t cbBuf, uint32_t *pcbWritten)
2204{
2205 Log3Func(("%s: @%#RX64: cbBuf=%#x fBackStatus=%#x\n", pStreamEx->Core.szName, pStreamEx->offInternal, cbBuf, fBackStatus));
2206 RT_NOREF(fBackStatus);
2207
2208 /*
2209 * Are we pre-buffering?
2210 *
2211 * Note! We do not restart pre-buffering in this version, as we'd
2212 * need some kind of cooperation with the backend buffer
2213 * managment to correctly detect an underrun.
2214 */
2215 bool fJustStarted = false;
2216 uint32_t cbWritten = 0;
2217 int rc;
2218 if ( pStreamEx->fThresholdReached
2219 && pStreamEx->Out.cbPreBuffered == 0)
2220 {
2221 /* not-prebuffering, likely after a while at least */
2222 rc = VINF_SUCCESS;
2223 }
2224 else
2225 {
2226 /*
2227 * Copy as much as we can to the pre-buffer.
2228 */
2229 uint32_t cbFree = pStreamEx->Out.cbPreBufAlloc - pStreamEx->Out.cbPreBuffered;
2230 AssertReturn((int32_t)cbFree >= 0, VERR_INTERNAL_ERROR_2);
2231 if (cbFree > 0 && cbBuf > 0)
2232 {
2233 cbWritten = RT_MIN(cbFree, cbBuf);
2234 cbWritten = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Props, cbWritten);
2235 if (pStreamEx->Out.offPreBuf == 0)
2236 memcpy(&pStreamEx->Out.pbPreBuf[pStreamEx->Out.cbPreBuffered], pbBuf, cbWritten);
2237 else
2238 {
2239
2240 }
2241
2242 pStreamEx->Out.cbPreBuffered += cbWritten;
2243 cbBuf -= cbWritten;
2244 pbBuf += cbWritten;
2245 pStreamEx->offInternal += cbWritten;
2246 }
2247
2248 /*
2249 * Get the special case of buggy backend drivers out of the way.
2250 * We get here if we couldn't write out all the pre-buffered data when
2251 * we hit the threshold.
2252 */
2253 if (pStreamEx->fThresholdReached)
2254 LogRel2(("Audio: @%#RX64: Stream '%s' pre-buffering commit problem: cbBuf=%#x cbPreBuffered=%#x\n",
2255 pStreamEx->offInternal, pStreamEx->Core.szName, cbBuf, pStreamEx->Out.cbPreBuffered));
2256 /*
2257 * Did we reach the backend's playback (pre-buffering) threshold?
2258 * Can be 0 if no pre-buffering desired.
2259 */
2260 else if (pStreamEx->Out.cbPreBuffered + cbBuf >= pStreamEx->Out.cbPreBufThreshold)
2261 {
2262 LogRel2(("Audio: @%#RX64: Stream '%s' buffering complete! (%#x + %#x bytes)\n",
2263 pStreamEx->offInternal, pStreamEx->Core.szName, pStreamEx->Out.cbPreBuffered, cbBuf));
2264 pStreamEx->fThresholdReached = fJustStarted = true;
2265 }
2266 /*
2267 * Some audio files are shorter than the pre-buffering level (e.g. the
2268 * "click" Explorer sounds on some Windows guests), so make sure that we
2269 * also play those by checking if the stream already is pending disable
2270 * mode, even if we didn't hit the pre-buffering watermark yet.
2271 *
2272 * Try play "Windows Navigation Start.wav" on Windows 7 (2824 samples).
2273 */
2274 else if ( (pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)
2275 && pStreamEx->Out.cbPreBuffered > 0)
2276 {
2277 LogRel2(("Audio: @%#RX64: Stream '%s' buffering complete - short sound! (%#x + %#x bytes)\n",
2278 pStreamEx->offInternal, pStreamEx->Core.szName, pStreamEx->Out.cbPreBuffered, cbBuf));
2279 pStreamEx->fThresholdReached = fJustStarted = true;
2280 }
2281 /*
2282 * Not yet, so still buffering audio data.
2283 */
2284 else
2285 {
2286 LogRel2(("Audio: @%#RX64: Stream '%s' is buffering (%RU8%% complete)...\n", pStreamEx->offInternal,
2287 pStreamEx->Core.szName, (100 * pStreamEx->Out.cbPreBuffered) / pStreamEx->Out.cbPreBufThreshold));
2288 Assert(cbBuf == 0);
2289 *pcbWritten = cbWritten;
2290 return VINF_SUCCESS;
2291 }
2292
2293 /*
2294 * Write the pre-buffered chunk.
2295 */
2296 uint32_t off = 0;
2297 uint32_t cbPreBufWritten;
2298 do
2299 {
2300 cbPreBufWritten = 0;
2301 rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStreamEx->pBackend, &pStreamEx->Out.pbPreBuf[off],
2302 pStreamEx->Out.cbPreBuffered - off, &cbPreBufWritten);
2303 AssertRCBreak(rc);
2304 off += cbPreBufWritten;
2305 } while (off < pStreamEx->Out.cbPreBuffered && cbPreBufWritten != 0);
2306
2307 if (off >= pStreamEx->Out.cbPreBuffered)
2308 {
2309 Assert(off == pStreamEx->Out.cbPreBuffered);
2310 LogFunc(("@%#RX64: Wrote all %#x bytes of pre-buffered audio data.\n", pStreamEx->offInternal, off));
2311 pStreamEx->Out.cbPreBuffered = 0;
2312 }
2313 else
2314 {
2315 LogRel2(("Audio: @%#RX64: Stream '%s' pre-buffering commit problem: wrote %#x out of %#x + %#x%s - rc=%Rrc *pcbWritten=%#x\n",
2316 pStreamEx->offInternal, pStreamEx->Core.szName, off, pStreamEx->Out.cbPreBuffered, cbBuf,
2317 fJustStarted ? " (just started)" : "", rc, cbWritten));
2318 AssertMsg(!fJustStarted || RT_FAILURE(rc),
2319 ("Buggy host driver buffer reporting: off=%#x cbPreBuffered=%#x\n", off, pStreamEx->Out.cbPreBuffered));
2320 if (off > 0)
2321 {
2322 memmove(pStreamEx->Out.pbPreBuf, &pStreamEx->Out.pbPreBuf[off], pStreamEx->Out.cbPreBuffered - off);
2323 pStreamEx->Out.cbPreBuffered -= off;
2324 }
2325 pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS();
2326 *pcbWritten = cbWritten;
2327 return cbWritten ? VINF_SUCCESS : rc;
2328 }
2329
2330 if (RT_FAILURE(rc))
2331 {
2332 *pcbWritten = cbWritten;
2333 return rc;
2334 }
2335 }
2336
2337 /*
2338 * Do the writing.
2339 */
2340 uint32_t cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
2341 pStreamEx->Out.Stats.cbBackendWritableBefore = cbWritable;
2342
2343 uint8_t const cbFrame = PDMAudioPropsFrameSize(&pStreamEx->Core.Props);
2344 while (cbBuf >= cbFrame && cbWritable >= cbFrame)
2345 {
2346 uint32_t const cbToWrite = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Props, RT_MIN(cbBuf, cbWritable));
2347 uint32_t cbWrittenNow = 0;
2348 rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStreamEx->pBackend, pbBuf, cbToWrite, &cbWrittenNow);
2349 if (RT_SUCCESS(rc))
2350 {
2351 if (cbWrittenNow != cbToWrite)
2352 Log3Func(("%s: @%#RX64: Wrote less bytes than requested: %#x, requested %#x\n",
2353 pStreamEx->Core.szName, pStreamEx->offInternal, cbWrittenNow, cbToWrite));
2354#ifdef DEBUG_bird
2355 Assert(cbWrittenNow == cbToWrite);
2356#endif
2357 AssertStmt(cbWrittenNow <= cbToWrite, cbWrittenNow = cbToWrite);
2358 cbWritten += cbWrittenNow;
2359 cbBuf -= cbWrittenNow;
2360 pbBuf += cbWrittenNow;
2361 pStreamEx->offInternal += cbWrittenNow;
2362 }
2363 else
2364 {
2365 *pcbWritten = cbWritten;
2366 LogFunc(("%s: @%#RX64: pfnStreamPlay failed writing %#x bytes (%#x previous written, %#x writable): %Rrc\n",
2367 pStreamEx->Core.szName, pStreamEx->offInternal, cbToWrite, cbWritten, cbWritable, rc));
2368 return cbWritten ? VINF_SUCCESS : rc;
2369 }
2370 cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
2371 }
2372
2373 *pcbWritten = cbWritten;
2374 pStreamEx->Out.Stats.cbBackendWritableAfter = cbWritable;
2375 if (cbWritten)
2376 pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS();
2377
2378 Log3Func(("%s: @%#RX64: Wrote %#x bytes (%#x bytes left)\n", pStreamEx->Core.szName, pStreamEx->offInternal, cbWritten, cbBuf));
2379 return rc;
2380}
2381#endif
2382
2383
2384/**
2385 * Worker for drvAudioStreamPlay() and drvAudioStreamIterateInternal().
2386 *
2387 * Caller owns the lock.
2388 */
2389static int drvAudioStreamPlayLocked(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx,
2390 const uint8_t *pbBuf, uint32_t cbBuf, uint32_t *pcbWritten)
2391{
2392 Log3Func(("%s: @%#RX64: cbBuf=%#x\n", pStreamEx->Core.szName, pStreamEx->offInternal, cbBuf));
2393
2394 uint32_t cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
2395 pStreamEx->Out.Stats.cbBackendWritableBefore = cbWritable;
2396
2397 uint32_t cbWritten = 0;
2398 int rc = VINF_SUCCESS;
2399 uint8_t const cbFrame = PDMAudioPropsFrameSize(&pStreamEx->Core.Props);
2400 while (cbBuf >= cbFrame && cbWritable >= cbFrame)
2401 {
2402 uint32_t const cbToWrite = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Props, RT_MIN(cbBuf, cbWritable));
2403 uint32_t cbWrittenNow = 0;
2404 rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStreamEx->pBackend, pbBuf, cbToWrite, &cbWrittenNow);
2405 if (RT_SUCCESS(rc))
2406 {
2407 if (cbWrittenNow != cbToWrite)
2408 Log3Func(("%s: @%#RX64: Wrote less bytes than requested: %#x, requested %#x\n",
2409 pStreamEx->Core.szName, pStreamEx->offInternal, cbWrittenNow, cbToWrite));
2410#ifdef DEBUG_bird
2411 Assert(cbWrittenNow == cbToWrite);
2412#endif
2413 AssertStmt(cbWrittenNow <= cbToWrite, cbWrittenNow = cbToWrite);
2414 cbWritten += cbWrittenNow;
2415 cbBuf -= cbWrittenNow;
2416 pbBuf += cbWrittenNow;
2417 pStreamEx->offInternal += cbWrittenNow;
2418 }
2419 else
2420 {
2421 *pcbWritten = cbWritten;
2422 LogFunc(("%s: @%#RX64: pfnStreamPlay failed writing %#x bytes (%#x previous written, %#x writable): %Rrc\n",
2423 pStreamEx->Core.szName, pStreamEx->offInternal, cbToWrite, cbWritten, cbWritable, rc));
2424 return cbWritten ? VINF_SUCCESS : rc;
2425 }
2426
2427 cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
2428 }
2429
2430 *pcbWritten = cbWritten;
2431 pStreamEx->Out.Stats.cbBackendWritableAfter = cbWritable;
2432 if (cbWritten)
2433 pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS();
2434
2435 Log3Func(("%s: @%#RX64: Wrote %#x bytes (%#x bytes left)\n", pStreamEx->Core.szName, pStreamEx->offInternal, cbWritten, cbBuf));
2436 return rc;
2437}
2438
2439
2440static int drvAudioStreamPlayToPreBuffer(PDRVAUDIOSTREAM pStreamEx, const void *pvBuf, uint32_t cbBuf, uint32_t cbMax,
2441 uint32_t *pcbWritten)
2442{
2443 int rc = drvAudioStreamPreBuffer(pStreamEx, (uint8_t const *)pvBuf, cbBuf, cbMax);
2444 if (RT_SUCCESS(rc))
2445 {
2446 *pcbWritten = cbBuf;
2447 pStreamEx->offInternal += cbBuf;
2448 Log3Func(("[%s] Pre-buffering (%s): wrote %#x bytes => %#x bytes / %u%%\n",
2449 pStreamEx->Core.szName, drvAudioPlayStateName(pStreamEx->Out.enmPlayState), cbBuf, pStreamEx->Out.cbPreBuffered,
2450 pStreamEx->Out.cbPreBuffered * 100 / RT_MAX(pStreamEx->Out.cbPreBufThreshold, 1)));
2451
2452 }
2453 else
2454 *pcbWritten = 0;
2455 return rc;
2456}
2457
2458
2459/**
2460 * Used when we're committing (transfering) the pre-buffered bytes to the
2461 * device.
2462 *
2463 * This is called both from drvAudioStreamPlay() and
2464 * drvAudioStreamIterateInternal().
2465 *
2466 * @returns VBox status code.
2467 * @param pThis Pointer to the DrvAudio instance data.
2468 * @param pStreamEx The stream to commit the pre-buffering for.
2469 * @param pbBuf Buffer with new bytes to write. Can be NULL when called
2470 * in the PENDING_DISABLE state from
2471 * drvAudioStreamIterateInternal().
2472 * @param cbBuf Number of new bytes. Can be zero.
2473 * @param pcbWritten Where to return the number of bytes written.
2474 */
2475static int drvAudioStreamPreBufComitting(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx,
2476 const uint8_t *pbBuf, uint32_t cbBuf, uint32_t *pcbWritten)
2477{
2478 /*
2479 * First, top up the buffer with new data from pbBuf.
2480 */
2481 *pcbWritten = 0;
2482 if (cbBuf > 0)
2483 {
2484 uint32_t const cbToCopy = RT_MIN(pStreamEx->Out.cbPreBufAlloc - pStreamEx->Out.cbPreBuffered, cbBuf);
2485 if (cbToCopy > 0)
2486 {
2487 int rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pbBuf, cbBuf, pStreamEx->Out.cbPreBufAlloc, pcbWritten);
2488 AssertRCReturn(rc, rc);
2489 pbBuf += cbToCopy;
2490 cbBuf -= cbToCopy;
2491 }
2492 }
2493
2494 /*
2495 * Write the pre-buffered chunk.
2496 */
2497 int rc = VINF_SUCCESS;
2498 uint32_t const cbAlloc = pStreamEx->Out.cbPreBufAlloc;
2499 AssertReturn(cbAlloc > 0, VERR_INTERNAL_ERROR_2);
2500 uint32_t off = pStreamEx->Out.offPreBuf;
2501 AssertStmt(off < pStreamEx->Out.cbPreBufAlloc, off %= cbAlloc);
2502 uint32_t cbLeft = pStreamEx->Out.cbPreBuffered;
2503 while (cbLeft > 0)
2504 {
2505 uint32_t const cbToWrite = RT_MIN(cbAlloc - off, cbLeft);
2506 Assert(cbToWrite > 0);
2507
2508 uint32_t cbPreBufWritten = 0;
2509 rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStreamEx->pBackend, &pStreamEx->Out.pbPreBuf[off],
2510 cbToWrite, &cbPreBufWritten);
2511 AssertRCBreak(rc);
2512 if (!cbPreBufWritten)
2513 break;
2514 AssertStmt(cbPreBufWritten <= cbToWrite, cbPreBufWritten = cbToWrite);
2515 off = (off + cbPreBufWritten) % cbAlloc;
2516 cbLeft -= cbPreBufWritten;
2517 }
2518
2519 if (cbLeft == 0)
2520 {
2521 LogFunc(("@%#RX64: Wrote all %#x bytes of pre-buffered audio data. %s -> PLAY\n", pStreamEx->offInternal,
2522 pStreamEx->Out.cbPreBuffered, drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
2523 pStreamEx->Out.cbPreBuffered = 0;
2524 pStreamEx->Out.offPreBuf = 0;
2525 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PLAY;
2526
2527 if (cbBuf > 0)
2528 {
2529 uint32_t cbWritten2 = 0;
2530 rc = drvAudioStreamPlayLocked(pThis, pStreamEx, pbBuf, cbBuf, &cbWritten2);
2531 if (RT_SUCCESS(rc))
2532 *pcbWritten += cbWritten2;
2533 }
2534 else
2535 pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS();
2536 }
2537 else
2538 {
2539 if (cbLeft != pStreamEx->Out.cbPreBuffered)
2540 pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS();
2541
2542 LogRel2(("Audio: @%#RX64: Stream '%s' pre-buffering commit problem: wrote %#x out of %#x + %#x - rc=%Rrc *pcbWritten=%#x %s -> PREBUF_COMMITTING\n",
2543 pStreamEx->offInternal, pStreamEx->Core.szName, pStreamEx->Out.cbPreBuffered - cbLeft,
2544 pStreamEx->Out.cbPreBuffered, cbBuf, rc, *pcbWritten, drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
2545 AssertMsg(pStreamEx->Out.enmPlayState == DRVAUDIOPLAYSTATE_PREBUF_COMMITTING || RT_FAILURE(rc),
2546 ("Buggy host driver buffer reporting? cbLeft=%#x cbPreBuffered=%#x\n", cbLeft, pStreamEx->Out.cbPreBuffered));
2547
2548 pStreamEx->Out.cbPreBuffered = cbLeft;
2549 pStreamEx->Out.offPreBuf = off;
2550 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_COMMITTING;
2551 }
2552
2553 return *pcbWritten ? VINF_SUCCESS : rc;
2554}
2555
2556
2557/**
2558 * Does one iteration of an audio stream.
2559 *
2560 * This function gives the backend the chance of iterating / altering data and
2561 * does the actual mixing between the guest <-> host mixing buffers.
2562 *
2563 * @returns VBox status code.
2564 * @param pThis Pointer to driver instance.
2565 * @param pStreamEx Stream to iterate.
2566 */
2567static int drvAudioStreamIterateInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
2568{
2569 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
2570
2571 if (!pThis->pHostDrvAudio)
2572 return VINF_SUCCESS;
2573
2574#ifdef LOG_ENABLED
2575 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
2576#endif
2577 Log3Func(("[%s] fStatus=%s\n", pStreamEx->Core.szName, dbgAudioStreamStatusToStr(szStreamSts, pStreamEx->Core.fStatus)));
2578
2579 /* Not enabled or paused? Skip iteration. */
2580 if ( !(pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_ENABLED)
2581 || (pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_PAUSED))
2582 {
2583 return VINF_SUCCESS;
2584 }
2585
2586 /*
2587 * Pending disable is really what we're here for. This only happens to output streams.
2588 */
2589 int rc = VINF_SUCCESS;
2590 if (!(pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE))
2591 { /* likely until we get to the end of the stream at least. */ }
2592 else
2593 {
2594 AssertReturn(pStreamEx->Core.enmDir == PDMAUDIODIR_OUT, VINF_SUCCESS);
2595 /** @todo Add a timeout to these proceedings. A few times that of the reported
2596 * buffer size or something like that. */
2597
2598 /*
2599 * Check if we have any data we need to write to the backend, try
2600 * move it now.
2601 */
2602 /** @todo r=bird: It is possible the device has data buffered (e.g.
2603 * internal DMA buffer (HDA) or mixing buffer (HDA + AC'97). We're
2604 * not taking that into account here. I also suspect that neither is
2605 * the device/mixer code, and that if the guest is too quick disabling
2606 * the stream, it will just remain there till the next time something
2607 * is played. That means that this code and associated timer hack
2608 * should probably not be here at all. */
2609 uint32_t cFramesLive = 0;
2610 switch (pStreamEx->Out.enmPlayState)
2611 {
2612 case DRVAUDIOPLAYSTATE_PLAY: /* Nothing prebuffered. */
2613 case DRVAUDIOPLAYSTATE_PLAY_PREBUF: /* Not pre-buffering for current device. */
2614 case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE: /* Output device isn't ready, drop it. */ /** @todo check state? */
2615 case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING: /* No output device yet. */
2616 case DRVAUDIOPLAYSTATE_NOPLAY:
2617 case DRVAUDIOPLAYSTATE_INVALID:
2618 case DRVAUDIOPLAYSTATE_END:
2619 /* no default, want warnings. */
2620 break;
2621 case DRVAUDIOPLAYSTATE_PREBUF:
2622 case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
2623 if (pStreamEx->Out.cbPreBuffered > 0)
2624 {
2625 uint32_t cbIgnored = 0;
2626 drvAudioStreamPreBufComitting(pThis, pStreamEx, NULL, 0, &cbIgnored);
2627 cFramesLive = PDMAudioPropsBytesToFrames(&pStreamEx->Core.Props, pStreamEx->Out.cbPreBuffered);
2628 }
2629 break;
2630 }
2631 Log3Func(("[%s] cFramesLive=%RU32\n", pStreamEx->Core.szName, cFramesLive));
2632 if (cFramesLive == 0)
2633 {
2634 /*
2635 * Tell the backend to start draining the stream, that is,
2636 * play the remaining buffered data and stop.
2637 */
2638 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DRAIN);
2639 if (rc == VERR_NOT_SUPPORTED) /* Not all backends support draining yet. */
2640 rc = VINF_SUCCESS;
2641 if (RT_SUCCESS(rc))
2642 {
2643 /*
2644 * Before we disable the stream, check if the backend has
2645 * finished playing the buffered data.
2646 */
2647 uint32_t cbPending;
2648 if (!pThis->pHostDrvAudio->pfnStreamGetPending) /* Optional. */
2649 cbPending = 0;
2650 else
2651 {
2652 cbPending = pThis->pHostDrvAudio->pfnStreamGetPending(pThis->pHostDrvAudio, pStreamEx->pBackend);
2653 Log3Func(("[%s] cbPending=%RU32 (%#RX32)\n", pStreamEx->Core.szName, cbPending, cbPending));
2654 }
2655 if (cbPending == 0)
2656 {
2657 /*
2658 * Okay, disable it.
2659 */
2660 LogFunc(("[%s] Disabling pending stream\n", pStreamEx->Core.szName));
2661 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
2662 if (RT_SUCCESS(rc))
2663 {
2664 pStreamEx->Core.fStatus &= ~(PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PENDING_DISABLE);
2665 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->Core.fStatus);
2666 drvAudioStreamDropInternal(pStreamEx); /* Not a DROP command, just a stream reset. */
2667 }
2668 else
2669 LogFunc(("[%s] Backend vetoed against closing pending input stream, rc=%Rrc\n", pStreamEx->Core.szName, rc));
2670 }
2671 }
2672 }
2673
2674 }
2675
2676 /* Update timestamps. */
2677 pStreamEx->nsLastIterated = RTTimeNanoTS();
2678
2679 if (RT_FAILURE(rc))
2680 LogFunc(("[%s] Failed with %Rrc\n", pStreamEx->Core.szName, rc));
2681
2682 return rc;
2683}
2684
2685
2686/**
2687 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamIterate}
2688 */
2689static DECLCALLBACK(int) drvAudioStreamIterate(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
2690{
2691 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
2692 AssertPtr(pThis);
2693 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
2694 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
2695 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
2696 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
2697
2698 int rc = RTCritSectEnter(&pThis->CritSect);
2699 AssertRCReturn(rc, rc);
2700
2701 rc = drvAudioStreamIterateInternal(pThis, pStreamEx);
2702
2703 RTCritSectLeave(&pThis->CritSect);
2704
2705 if (RT_FAILURE(rc))
2706 LogFlowFuncLeaveRC(rc);
2707 return rc;
2708}
2709
2710
2711/**
2712 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetReadable}
2713 */
2714static DECLCALLBACK(uint32_t) drvAudioStreamGetReadable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
2715{
2716 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
2717 AssertPtr(pThis);
2718 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
2719 AssertPtrReturn(pStreamEx, 0);
2720 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, 0);
2721 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, 0);
2722 AssertMsg(pStreamEx->Core.enmDir == PDMAUDIODIR_IN, ("Can't read from a non-input stream\n"));
2723 int rc = RTCritSectEnter(&pThis->CritSect);
2724 AssertRCReturn(rc, 0);
2725
2726 /*
2727 * ...
2728 */
2729 uint32_t cbReadable = 0;
2730
2731 /* All input streams for this driver disabled? See @bugref{9882}. */
2732 const bool fDisabled = !pThis->In.fEnabled;
2733
2734 if ( pThis->pHostDrvAudio
2735 && ( PDMAudioStrmStatusCanRead(pStreamEx->Core.fStatus)
2736 || fDisabled)
2737 )
2738 {
2739 if (pStreamEx->fNoMixBufs)
2740 cbReadable = pThis->pHostDrvAudio
2741 ? pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend) : 0;
2742 else
2743 {
2744 const uint32_t cfReadable = AudioMixBufLive(&pStreamEx->Guest.MixBuf);
2745 cbReadable = AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadable);
2746 }
2747
2748 if (!cbReadable)
2749 {
2750 /*
2751 * If nothing is readable, check if the stream on the backend side is ready to be read from.
2752 * If it isn't, return the number of bytes readable since the last read from this stream.
2753 *
2754 * This is needed for backends (e.g. VRDE) which do not provide any input data in certain
2755 * situations, but the device emulation needs input data to keep the DMA transfers moving.
2756 * Reading the actual data from a stream then will return silence then.
2757 */
2758 uint32_t fStatus = drvAudioStreamGetBackendStatus(pThis, pStreamEx);
2759 if ( !PDMAudioStrmStatusBackendCanRead(fStatus)
2760 || fDisabled)
2761 {
2762 cbReadable = PDMAudioPropsNanoToBytes(&pStreamEx->Host.Cfg.Props,
2763 RTTimeNanoTS() - pStreamEx->nsLastReadWritten);
2764 if (!(pStreamEx->Core.fWarningsShown & PDMAUDIOSTREAM_WARN_FLAGS_DISABLED))
2765 {
2766 if (fDisabled)
2767 LogRel(("Audio: Input for driver '%s' has been disabled, returning silence\n", pThis->szName));
2768 else
2769 LogRel(("Audio: Warning: Input for stream '%s' of driver '%s' not ready (current input status is %#x), returning silence\n",
2770 pStreamEx->Core.szName, pThis->szName, fStatus));
2771
2772 pStreamEx->Core.fWarningsShown |= PDMAUDIOSTREAM_WARN_FLAGS_DISABLED;
2773 }
2774 }
2775 }
2776
2777 /* Make sure to align the readable size to the guest's frame size. */
2778 if (cbReadable)
2779 cbReadable = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Guest.Cfg.Props, cbReadable);
2780 }
2781
2782 RTCritSectLeave(&pThis->CritSect);
2783 Log3Func(("[%s] cbReadable=%RU32 (%RU64ms)\n",
2784 pStreamEx->Core.szName, cbReadable, PDMAudioPropsBytesToMilli(&pStreamEx->Host.Cfg.Props, cbReadable)));
2785 return cbReadable;
2786}
2787
2788
2789/**
2790 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetWritable}
2791 */
2792static DECLCALLBACK(uint32_t) drvAudioStreamGetWritable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
2793{
2794 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
2795 AssertPtr(pThis);
2796 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
2797 AssertPtrReturn(pStreamEx, 0);
2798 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, 0);
2799 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, 0);
2800 AssertMsgReturn(pStreamEx->Core.enmDir == PDMAUDIODIR_OUT, ("Can't write to a non-output stream\n"), 0);
2801 int rc = RTCritSectEnter(&pThis->CritSect);
2802 AssertRCReturn(rc, 0);
2803
2804 /*
2805 * ...
2806 *
2807 * Note: We don't propagate the backend stream's status to the outside -- it's the job of this
2808 * audio connector to make sense of it.
2809 */
2810 uint32_t cbWritable = 0;
2811 if ( PDMAudioStrmStatusCanWrite(pStreamEx->Core.fStatus)
2812 && pThis->pHostDrvAudio != NULL)
2813 {
2814 Assert(pThis->pHostDrvAudio);
2815 switch (pStreamEx->Out.enmPlayState)
2816 {
2817 /*
2818 * Whatever the backend can hold.
2819 */
2820 case DRVAUDIOPLAYSTATE_PLAY:
2821 case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
2822 cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
2823 break;
2824
2825 /*
2826 * Whatever we've got of available space in the pre-buffer.
2827 * Note! For the last round when we pass the pre-buffering threshold, we may
2828 * report fewer bytes than what a DMA timer period for the guest device
2829 * typically produces, however that should be transfered in the following
2830 * round that goes directly to the backend buffer.
2831 */
2832 case DRVAUDIOPLAYSTATE_PREBUF:
2833 cbWritable = pStreamEx->Out.cbPreBufAlloc - pStreamEx->Out.cbPreBuffered;
2834 if (!cbWritable)
2835 cbWritable = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Props, 2);
2836 break;
2837
2838 /*
2839 * These are slightly more problematic and can go wrong if the pre-buffer is
2840 * manually configured to be smaller than the output of a typeical DMA timer
2841 * period for the guest device. So, to overcompensate, we just report back
2842 * the backend buffer size (the pre-buffer is circular, so no overflow issue).
2843 */
2844 case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
2845 case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
2846 cbWritable = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Props,
2847 RT_MAX(pStreamEx->Host.Cfg.Backend.cFramesBufferSize,
2848 pStreamEx->Host.Cfg.Backend.cFramesPreBuffering));
2849 break;
2850
2851 case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
2852 {
2853 /* Buggy backend: We weren't able to copy all the pre-buffered data to it
2854 when reaching the threshold. Try escape this situation, or at least
2855 keep the extra buffering to a minimum. We must try write something
2856 as long as there is space for it, as we need the pfnStreamWrite call
2857 to move the data. */
2858 uint32_t const cbMin = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Props, 8);
2859 cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
2860 if (cbWritable >= pStreamEx->Out.cbPreBuffered + cbMin)
2861 cbWritable -= pStreamEx->Out.cbPreBuffered + cbMin / 2;
2862 else
2863 cbWritable = RT_MIN(cbMin, pStreamEx->Out.cbPreBufAlloc - pStreamEx->Out.cbPreBuffered);
2864 AssertLogRel(cbWritable);
2865 break;
2866 }
2867
2868 case DRVAUDIOPLAYSTATE_NOPLAY:
2869 break;
2870 case DRVAUDIOPLAYSTATE_INVALID:
2871 case DRVAUDIOPLAYSTATE_END:
2872 AssertFailed();
2873 break;
2874 }
2875
2876 /* Make sure to align the writable size to the host's frame size. */
2877 cbWritable = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Host.Cfg.Props, cbWritable);
2878 }
2879
2880 RTCritSectLeave(&pThis->CritSect);
2881 Log3Func(("[%s] cbWritable=%RU32 (%RU64ms)\n",
2882 pStreamEx->Core.szName, cbWritable, PDMAudioPropsBytesToMilli(&pStreamEx->Host.Cfg.Props, cbWritable)));
2883 return cbWritable;
2884}
2885
2886
2887/**
2888 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetStatus}
2889 */
2890static DECLCALLBACK(uint32_t) drvAudioStreamGetStatus(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
2891{
2892 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
2893 AssertPtr(pThis);
2894
2895 /** @todo r=bird: It is not documented that we ignore NULL streams... Why is
2896 * this necessary? */
2897 if (!pStream)
2898 return PDMAUDIOSTREAM_STS_NONE;
2899 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
2900 AssertPtrReturn(pStreamEx, PDMAUDIOSTREAM_STS_NONE);
2901 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, PDMAUDIOSTREAM_STS_NONE);
2902 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, PDMAUDIOSTREAM_STS_NONE);
2903
2904 int rc = RTCritSectEnter(&pThis->CritSect);
2905 AssertRCReturn(rc, PDMAUDIOSTREAM_STS_NONE);
2906
2907 uint32_t fStrmStatus = pStreamEx->Core.fStatus;
2908
2909 RTCritSectLeave(&pThis->CritSect);
2910#ifdef LOG_ENABLED
2911 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
2912#endif
2913 Log3Func(("[%s] %s\n", pStreamEx->Core.szName, dbgAudioStreamStatusToStr(szStreamSts, fStrmStatus)));
2914 return fStrmStatus;
2915}
2916
2917
2918/**
2919 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamSetVolume}
2920 */
2921static DECLCALLBACK(int) drvAudioStreamSetVolume(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, PPDMAUDIOVOLUME pVol)
2922{
2923 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2924 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
2925 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
2926 AssertPtrReturn(pVol, VERR_INVALID_POINTER);
2927 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
2928 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
2929 AssertReturn(!pStreamEx->fNoMixBufs, VWRN_INVALID_STATE);
2930
2931 LogFlowFunc(("[%s] volL=%RU32, volR=%RU32, fMute=%RTbool\n", pStreamEx->Core.szName, pVol->uLeft, pVol->uRight, pVol->fMuted));
2932
2933 AudioMixBufSetVolume(&pStreamEx->Guest.MixBuf, pVol);
2934 AudioMixBufSetVolume(&pStreamEx->Host.MixBuf, pVol);
2935
2936 return VINF_SUCCESS;
2937}
2938
2939
2940static void drvAudioStreamPlayProcessBackendStateChange(PDRVAUDIOSTREAM pStreamEx, uint32_t fNewState, uint32_t fOldState)
2941{
2942 DRVAUDIOPLAYSTATE const enmPlayState = pStreamEx->Out.enmPlayState;
2943
2944 /*
2945 * Did PDMAUDIOSTREAM_STS_INITIALIZED change?
2946 */
2947 if ((fOldState ^ fNewState) & PDMAUDIOSTREAM_STS_INITIALIZED)
2948 {
2949 if (fOldState & PDMAUDIOSTREAM_STS_INITIALIZED)
2950 {
2951 switch (enmPlayState)
2952 {
2953 case DRVAUDIOPLAYSTATE_PLAY:
2954 case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
2955 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY;
2956 /** @todo We could enter PREBUF here and hope for the device to re-appear in
2957 * initialized state... */
2958 break;
2959 case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
2960 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_SWITCHING;
2961 break;
2962 case DRVAUDIOPLAYSTATE_PREBUF:
2963 break;
2964 case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
2965 case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
2966 AssertFailedBreak();
2967 /* no default */
2968 case DRVAUDIOPLAYSTATE_NOPLAY:
2969 case DRVAUDIOPLAYSTATE_END:
2970 case DRVAUDIOPLAYSTATE_INVALID:
2971 break;
2972 }
2973 LogFunc(("PDMAUDIOSTREAM_STS_INITIALIZED was cleared: %s -> %s\n",
2974 drvAudioPlayStateName(enmPlayState), drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
2975 }
2976 else
2977 {
2978 switch (enmPlayState)
2979 {
2980 case DRVAUDIOPLAYSTATE_PREBUF:
2981 case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
2982 break;
2983 case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
2984 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_COMMITTING;
2985 break;
2986 case DRVAUDIOPLAYSTATE_NOPLAY:
2987 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF;
2988 break;
2989 case DRVAUDIOPLAYSTATE_PLAY:
2990 case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
2991 case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
2992 AssertFailedBreak();
2993 /* no default */
2994 case DRVAUDIOPLAYSTATE_END:
2995 case DRVAUDIOPLAYSTATE_INVALID:
2996 break;
2997 }
2998 LogFunc(("PDMAUDIOSTREAM_STS_INITIALIZED was set: %s -> %s\n",
2999 drvAudioPlayStateName(enmPlayState), drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
3000 }
3001 }
3002
3003 /*
3004 * Deal with PDMAUDIOSTREAM_STS_PREPARING_SWITCH being set.
3005 *
3006 * Note! We don't care if it's cleared as the backend will call
3007 * PDMIAUDIONOTIFYFROMHOST::pfnStreamNotifyDeviceChanged when that takes place.
3008 */
3009 if ( !(fOldState & PDMAUDIOSTREAM_STS_PREPARING_SWITCH)
3010 && (fNewState & PDMAUDIOSTREAM_STS_PREPARING_SWITCH))
3011 {
3012 if (pStreamEx->Out.cbPreBufThreshold > 0)
3013 {
3014 switch (enmPlayState)
3015 {
3016 case DRVAUDIOPLAYSTATE_PREBUF:
3017 case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
3018 case DRVAUDIOPLAYSTATE_NOPLAY:
3019 case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING: /* simpler */
3020 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_SWITCHING;
3021 break;
3022 case DRVAUDIOPLAYSTATE_PLAY:
3023 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PLAY_PREBUF;
3024 break;
3025 case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
3026 case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
3027 break;
3028 /* no default */
3029 case DRVAUDIOPLAYSTATE_END:
3030 case DRVAUDIOPLAYSTATE_INVALID:
3031 break;
3032 }
3033 LogFunc(("PDMAUDIOSTREAM_STS_INITIALIZED was set: %s -> %s\n",
3034 drvAudioPlayStateName(enmPlayState), drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
3035 }
3036 else
3037 LogFunc(("PDMAUDIOSTREAM_STS_PREPARING_SWITCH was set, but no pre-buffering configured.\n"));
3038 }
3039}
3040
3041
3042/**
3043 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamPlay}
3044 */
3045static DECLCALLBACK(int) drvAudioStreamPlay(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream,
3046 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
3047{
3048 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
3049 AssertPtr(pThis);
3050
3051 /*
3052 * Check input and sanity.
3053 */
3054 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
3055 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
3056 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
3057 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
3058 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
3059 uint32_t uTmp;
3060 if (!pcbWritten)
3061 pcbWritten = &uTmp;
3062 AssertPtrReturn(pcbWritten, VERR_INVALID_PARAMETER);
3063
3064 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3065 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3066 AssertMsgReturn(pStreamEx->Core.enmDir == PDMAUDIODIR_OUT,
3067 ("Stream '%s' is not an output stream and therefore cannot be written to (direction is '%s')\n",
3068 pStreamEx->Core.szName, PDMAudioDirGetName(pStreamEx->Core.enmDir)), VERR_ACCESS_DENIED);
3069 Assert(pStreamEx->fNoMixBufs);
3070
3071 AssertMsg(PDMAudioPropsIsSizeAligned(&pStreamEx->Guest.Cfg.Props, cbBuf),
3072 ("Stream '%s' got a non-frame-aligned write (%RU32 bytes)\n", pStreamEx->Core.szName, cbBuf));
3073
3074 int rc = RTCritSectEnter(&pThis->CritSect);
3075 AssertRCReturn(rc, rc);
3076
3077 /*
3078 * First check that we can write to the stream, and if not,
3079 * whether to just drop the input into the bit bucket.
3080 */
3081 if (PDMAudioStrmStatusIsReady(pStreamEx->Core.fStatus))
3082 {
3083 if ( pThis->Out.fEnabled /* (see @bugref{9882}) */
3084 && pThis->pHostDrvAudio != NULL)
3085 {
3086#ifdef LOG_ENABLED
3087 char szState[DRVAUDIO_STATUS_STR_MAX];
3088#endif
3089 /*
3090 * Get the backend state and check if we've changed to "initialized" or
3091 * to switch prep mode.
3092 *
3093 * We don't really need to watch ENABLE or PAUSE here as these should be
3094 * in sync between our state and the backend (there is a tiny tiny chance
3095 * that we are resumed before the backend driver, but AIO threads really
3096 * shouldn't be getting here that fast I hope).
3097 */
3098 /** @todo
3099 * The PENDING_DISABLE (== draining) is not reported by most backend and it's an
3100 * open question whether we should still allow writes even when the backend
3101 * is draining anyway. We currently don't. Maybe we just re-define it as
3102 * a non-backend status flag.
3103 */
3104 uint32_t const fBackendStatus = drvAudioStreamGetBackendStatus(pThis, pStreamEx);
3105 Assert( (fBackendStatus & (PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PAUSED))
3106 == (pStreamEx->Core.fStatus & (PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PAUSED)) );
3107
3108 if (!( (pStreamEx->fLastBackendStatus ^ fBackendStatus)
3109 & (PDMAUDIOSTREAM_STS_INITIALIZED | PDMAUDIOSTREAM_STS_PREPARING_SWITCH)))
3110 { /* no relevant change - likely */ }
3111 else
3112 {
3113 drvAudioStreamPlayProcessBackendStateChange(pStreamEx, fBackendStatus, pStreamEx->fLastBackendStatus);
3114 pStreamEx->fLastBackendStatus = fBackendStatus;
3115 }
3116
3117 /*
3118 * Do the transfering.
3119 */
3120 switch (pStreamEx->Out.enmPlayState)
3121 {
3122 case DRVAUDIOPLAYSTATE_PLAY:
3123 rc = drvAudioStreamPlayLocked(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten);
3124 break;
3125
3126 case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
3127 rc = drvAudioStreamPlayLocked(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten);
3128 drvAudioStreamPreBuffer(pStreamEx, (uint8_t const *)pvBuf, *pcbWritten, pStreamEx->Out.cbPreBufThreshold);
3129 break;
3130
3131 case DRVAUDIOPLAYSTATE_PREBUF:
3132 if (cbBuf + pStreamEx->Out.cbPreBuffered < pStreamEx->Out.cbPreBufThreshold)
3133 rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pvBuf, cbBuf, pStreamEx->Out.cbPreBufThreshold, pcbWritten);
3134 else if (fBackendStatus & PDMAUDIOSTREAM_STS_INITIALIZED)
3135 {
3136 Log3Func(("[%s] Pre-buffering completing: cbBuf=%#x cbPreBuffered=%#x => %#x vs cbPreBufThreshold=%#x\n",
3137 pStreamEx->Core.szName, cbBuf, pStreamEx->Out.cbPreBuffered,
3138 cbBuf + pStreamEx->Out.cbPreBuffered, pStreamEx->Out.cbPreBufThreshold));
3139 rc = drvAudioStreamPreBufComitting(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten);
3140 }
3141 else
3142 {
3143 Log3Func(("[%s] Pre-buffering completing but device not ready: cbBuf=%#x cbPreBuffered=%#x => %#x vs cbPreBufThreshold=%#x; PREBUF -> PREBUF_OVERDUE\n",
3144 pStreamEx->Core.szName, cbBuf, pStreamEx->Out.cbPreBuffered,
3145 cbBuf + pStreamEx->Out.cbPreBuffered, pStreamEx->Out.cbPreBufThreshold));
3146 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_OVERDUE;
3147 rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pvBuf, cbBuf, pStreamEx->Out.cbPreBufThreshold, pcbWritten);
3148 }
3149 break;
3150
3151 case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
3152 Assert(!(fBackendStatus & PDMAUDIOSTREAM_STS_INITIALIZED));
3153 RT_FALL_THRU();
3154 case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
3155 rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pvBuf, cbBuf, pStreamEx->Out.cbPreBufThreshold, pcbWritten);
3156 break;
3157
3158 case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
3159 rc = drvAudioStreamPreBufComitting(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten);
3160 break;
3161
3162 case DRVAUDIOPLAYSTATE_NOPLAY:
3163 *pcbWritten = cbBuf;
3164 pStreamEx->offInternal += cbBuf;
3165 Log3Func(("[%s] Discarding the data, backend state: %s\n", pStreamEx->Core.szName,
3166 dbgAudioStreamStatusToStr(szState, fBackendStatus) ));
3167 break;
3168
3169 default:
3170 *pcbWritten = cbBuf;
3171 AssertMsgFailedBreak(("%d; cbBuf=%#x\n", pStreamEx->Out.enmPlayState, cbBuf));
3172 }
3173
3174 if (!pThis->Out.Cfg.Dbg.fEnabled || RT_FAILURE(rc))
3175 { /* likely */ }
3176 else
3177 AudioHlpFileWrite(pStreamEx->Out.Dbg.pFilePlayNonInterleaved, pvBuf, *pcbWritten, 0 /* fFlags */);
3178 }
3179 else
3180 {
3181 *pcbWritten = cbBuf;
3182 pStreamEx->offInternal += cbBuf;
3183 Log3Func(("[%s] Backend stream %s, discarding the data\n", pStreamEx->Core.szName,
3184 !pThis->Out.fEnabled ? "disabled" : !pThis->pHostDrvAudio ? "not attached" : "not ready yet"));
3185 }
3186 }
3187 else
3188 rc = VERR_AUDIO_STREAM_NOT_READY;
3189
3190 RTCritSectLeave(&pThis->CritSect);
3191 return rc;
3192}
3193
3194
3195/**
3196 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRead}
3197 */
3198static DECLCALLBACK(int) drvAudioStreamRead(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream,
3199 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
3200{
3201 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
3202 AssertPtr(pThis);
3203 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
3204 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
3205 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
3206 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
3207 AssertPtrNullReturn(pcbRead, VERR_INVALID_POINTER);
3208 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3209 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3210 AssertMsg(pStreamEx->Core.enmDir == PDMAUDIODIR_IN,
3211 ("Stream '%s' is not an input stream and therefore cannot be read from (direction is 0x%x)\n",
3212 pStreamEx->Core.szName, pStreamEx->Core.enmDir));
3213
3214 int rc = RTCritSectEnter(&pThis->CritSect);
3215 AssertRCReturn(rc, rc);
3216
3217 /*
3218 * ...
3219 */
3220 uint32_t cbReadTotal = 0;
3221
3222 do
3223 {
3224 uint32_t cfReadTotal = 0;
3225
3226 const uint32_t cfBuf = AUDIOMIXBUF_B2F(&pStreamEx->Guest.MixBuf, cbBuf);
3227
3228 if (pThis->In.fEnabled) /* Input for this audio driver enabled? See #9822. */
3229 {
3230 if (!PDMAudioStrmStatusCanRead(pStream->fStatus))
3231 {
3232 rc = VERR_AUDIO_STREAM_NOT_READY;
3233 break;
3234 }
3235
3236 /*
3237 * Read from the parent buffer (that is, the guest buffer) which
3238 * should have the audio data in the format the guest needs.
3239 */
3240 uint32_t cfToRead = RT_MIN(cfBuf, AudioMixBufLive(&pStreamEx->Guest.MixBuf));
3241 while (cfToRead)
3242 {
3243 uint32_t cfRead;
3244 rc = AudioMixBufAcquireReadBlock(&pStreamEx->Guest.MixBuf,
3245 (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal),
3246 AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfToRead), &cfRead);
3247 if (RT_FAILURE(rc))
3248 break;
3249
3250#ifdef VBOX_WITH_STATISTICS
3251 const uint32_t cbRead = AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfRead);
3252 STAM_COUNTER_ADD(&pThis->Stats.TotalBytesRead, cbRead);
3253 STAM_COUNTER_ADD(&pStreamEx->In.Stats.TotalFramesRead, cfRead);
3254 STAM_COUNTER_INC(&pStreamEx->In.Stats.TotalTimesRead);
3255#endif
3256 Assert(cfToRead >= cfRead);
3257 cfToRead -= cfRead;
3258
3259 cfReadTotal += cfRead;
3260
3261 AudioMixBufReleaseReadBlock(&pStreamEx->Guest.MixBuf, cfRead);
3262 }
3263
3264 if (cfReadTotal)
3265 {
3266 if (pThis->In.Cfg.Dbg.fEnabled)
3267 AudioHlpFileWrite(pStreamEx->In.Dbg.pFileStreamRead,
3268 pvBuf, AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal), 0 /* fFlags */);
3269
3270 AudioMixBufFinish(&pStreamEx->Guest.MixBuf, cfReadTotal);
3271 }
3272 }
3273
3274 /* If we were not able to read as much data as requested, fill up the returned
3275 * data with silence.
3276 *
3277 * This is needed to keep the device emulation DMA transfers up and running at a constant rate. */
3278 if (cfReadTotal < cfBuf)
3279 {
3280 Log3Func(("[%s] Filling in silence (%RU64ms / %RU64ms)\n", pStream->szName,
3281 PDMAudioPropsFramesToMilli(&pStreamEx->Guest.Cfg.Props, cfBuf - cfReadTotal),
3282 PDMAudioPropsFramesToMilli(&pStreamEx->Guest.Cfg.Props, cfBuf)));
3283
3284 PDMAudioPropsClearBuffer(&pStreamEx->Guest.Cfg.Props,
3285 (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal),
3286 AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfBuf - cfReadTotal),
3287 cfBuf - cfReadTotal);
3288
3289 cfReadTotal = cfBuf;
3290 }
3291
3292 cbReadTotal = AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal);
3293
3294 pStreamEx->nsLastReadWritten = RTTimeNanoTS();
3295
3296 Log3Func(("[%s] fEnabled=%RTbool, cbReadTotal=%RU32, rc=%Rrc\n", pStream->szName, pThis->In.fEnabled, cbReadTotal, rc));
3297
3298 } while (0);
3299
3300 RTCritSectLeave(&pThis->CritSect);
3301
3302 if (RT_SUCCESS(rc) && pcbRead)
3303 *pcbRead = cbReadTotal;
3304 return rc;
3305}
3306
3307
3308/**
3309 * Captures non-interleaved input from a host stream.
3310 *
3311 * @returns VBox status code.
3312 * @param pThis Driver instance.
3313 * @param pStreamEx Stream to capture from.
3314 * @param pcfCaptured Number of (host) audio frames captured.
3315 */
3316static int drvAudioStreamCaptureNonInterleaved(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, uint32_t *pcfCaptured)
3317{
3318 Assert(pStreamEx->Core.enmDir == PDMAUDIODIR_IN);
3319 Assert(pStreamEx->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED);
3320
3321 /*
3322 * ...
3323 */
3324 AssertPtr(pThis->pHostDrvAudio->pfnStreamGetReadable);
3325 uint32_t cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend);
3326 if (!cbReadable)
3327 Log2Func(("[%s] No readable data available\n", pStreamEx->Core.szName));
3328
3329 uint32_t cbFree = AudioMixBufFreeBytes(&pStreamEx->Guest.MixBuf); /* Parent */
3330 if (!cbFree)
3331 Log2Func(("[%s] Buffer full\n", pStreamEx->Core.szName));
3332
3333 if (cbReadable > cbFree) /* More data readable than we can store at the moment? Limit. */
3334 cbReadable = cbFree;
3335
3336 /*
3337 * ...
3338 */
3339 int rc = VINF_SUCCESS;
3340 uint32_t cfCapturedTotal = 0;
3341 while (cbReadable)
3342 {
3343 uint8_t abChunk[_4K];
3344 uint32_t cbCaptured;
3345 rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStreamEx->pBackend,
3346 abChunk, RT_MIN(cbReadable, (uint32_t)sizeof(abChunk)), &cbCaptured);
3347 if (RT_FAILURE(rc))
3348 {
3349 int rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
3350 AssertRC(rc2);
3351 break;
3352 }
3353
3354 Assert(cbCaptured <= sizeof(abChunk));
3355 if (cbCaptured > sizeof(abChunk)) /* Paranoia. */
3356 cbCaptured = (uint32_t)sizeof(abChunk);
3357
3358 if (!cbCaptured) /* Nothing captured? Take a shortcut. */
3359 break;
3360
3361 /* We use the host side mixing buffer as an intermediate buffer to do some
3362 * (first) processing (if needed), so always write the incoming data at offset 0. */
3363 uint32_t cfHstWritten = 0;
3364 rc = AudioMixBufWriteAt(&pStreamEx->Host.MixBuf, 0 /* offFrames */, abChunk, cbCaptured, &cfHstWritten);
3365 if ( RT_FAILURE(rc)
3366 || !cfHstWritten)
3367 {
3368 AssertMsgFailed(("[%s] Write failed: cbCaptured=%RU32, cfHstWritten=%RU32, rc=%Rrc\n",
3369 pStreamEx->Core.szName, cbCaptured, cfHstWritten, rc));
3370 break;
3371 }
3372
3373 if (pThis->In.Cfg.Dbg.fEnabled)
3374 AudioHlpFileWrite(pStreamEx->In.Dbg.pFileCaptureNonInterleaved, abChunk, cbCaptured, 0 /* fFlags */);
3375
3376 uint32_t cfHstMixed = 0;
3377 if (cfHstWritten)
3378 {
3379 int rc2 = AudioMixBufMixToParentEx(&pStreamEx->Host.MixBuf, 0 /* cSrcOffset */, cfHstWritten /* cSrcFrames */,
3380 &cfHstMixed /* pcSrcMixed */);
3381 Log3Func(("[%s] cbCaptured=%RU32, cfWritten=%RU32, cfMixed=%RU32, rc=%Rrc\n",
3382 pStreamEx->Core.szName, cbCaptured, cfHstWritten, cfHstMixed, rc2));
3383 AssertRC(rc2);
3384 }
3385
3386 Assert(cbReadable >= cbCaptured);
3387 cbReadable -= cbCaptured;
3388 cfCapturedTotal += cfHstMixed;
3389 }
3390
3391 if (RT_SUCCESS(rc))
3392 {
3393 if (cfCapturedTotal)
3394 Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStreamEx->Core.szName, cfCapturedTotal, rc));
3395 }
3396 else
3397 LogFunc(("[%s] Capturing failed with rc=%Rrc\n", pStreamEx->Core.szName, rc));
3398
3399 if (pcfCaptured)
3400 *pcfCaptured = cfCapturedTotal;
3401
3402 return rc;
3403}
3404
3405
3406/**
3407 * Captures raw input from a host stream.
3408 *
3409 * Raw input means that the backend directly operates on PDMAUDIOFRAME structs without
3410 * no data layout processing done in between.
3411 *
3412 * Needed for e.g. the VRDP audio backend (in Main).
3413 *
3414 * @returns VBox status code.
3415 * @param pThis Driver instance.
3416 * @param pStreamEx Stream to capture from.
3417 * @param pcfCaptured Number of (host) audio frames captured.
3418 */
3419static int drvAudioStreamCaptureRaw(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, uint32_t *pcfCaptured)
3420{
3421 Assert(pStreamEx->Core.enmDir == PDMAUDIODIR_IN);
3422 Assert(pStreamEx->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW);
3423 AssertPtr(pThis->pHostDrvAudio->pfnStreamGetReadable);
3424
3425 /*
3426 * ...
3427 */
3428 /* Note: Raw means *audio frames*, not bytes! */
3429 uint32_t cfReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend);
3430 if (!cfReadable)
3431 Log2Func(("[%s] No readable data available\n", pStreamEx->Core.szName));
3432
3433 const uint32_t cfFree = AudioMixBufFree(&pStreamEx->Guest.MixBuf); /* Parent */
3434 if (!cfFree)
3435 Log2Func(("[%s] Buffer full\n", pStreamEx->Core.szName));
3436
3437 if (cfReadable > cfFree) /* More data readable than we can store at the moment? Limit. */
3438 cfReadable = cfFree;
3439
3440 /*
3441 * ...
3442 */
3443 int rc = VINF_SUCCESS;
3444 uint32_t cfCapturedTotal = 0;
3445 while (cfReadable)
3446 {
3447 PPDMAUDIOFRAME paFrames;
3448 uint32_t cfWritable;
3449 rc = AudioMixBufPeekMutable(&pStreamEx->Host.MixBuf, cfReadable, &paFrames, &cfWritable);
3450 if ( RT_FAILURE(rc)
3451 || !cfWritable)
3452 break;
3453
3454 uint32_t cfCaptured;
3455 rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStreamEx->pBackend,
3456 paFrames, cfWritable, &cfCaptured);
3457 if (RT_FAILURE(rc))
3458 {
3459 int rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
3460 AssertRC(rc2);
3461 break;
3462 }
3463
3464 Assert(cfCaptured <= cfWritable);
3465 if (cfCaptured > cfWritable) /* Paranoia. */
3466 cfCaptured = cfWritable;
3467
3468 Assert(cfReadable >= cfCaptured);
3469 cfReadable -= cfCaptured;
3470 cfCapturedTotal += cfCaptured;
3471 }
3472
3473 if (pcfCaptured)
3474 *pcfCaptured = cfCapturedTotal;
3475 Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStreamEx->Core.szName, cfCapturedTotal, rc));
3476 return rc;
3477}
3478
3479
3480/**
3481 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCapture}
3482 */
3483static DECLCALLBACK(int) drvAudioStreamCapture(PPDMIAUDIOCONNECTOR pInterface,
3484 PPDMAUDIOSTREAM pStream, uint32_t *pcFramesCaptured)
3485{
3486 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
3487 AssertPtr(pThis);
3488 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
3489 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
3490 AssertPtrNull(pcFramesCaptured);
3491 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3492 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3493 AssertMsg(pStreamEx->Core.enmDir == PDMAUDIODIR_IN,
3494 ("Stream '%s' is not an input stream and therefore cannot be captured (direction is 0x%x)\n",
3495 pStreamEx->Core.szName, pStreamEx->Core.enmDir));
3496 int rc = RTCritSectEnter(&pThis->CritSect);
3497 AssertRCReturn(rc, rc);
3498
3499#ifdef LOG_ENABLED
3500 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
3501#endif
3502 Log3Func(("[%s] fStatus=%s\n", pStreamEx->Core.szName, dbgAudioStreamStatusToStr(szStreamSts, pStreamEx->Core.fStatus)));
3503
3504 /*
3505 * ...
3506 */
3507 uint32_t cfCaptured = 0;
3508 do
3509 {
3510 if (!pThis->pHostDrvAudio)
3511 {
3512 rc = VERR_PDM_NO_ATTACHED_DRIVER;
3513 break;
3514 }
3515
3516 if ( !pThis->In.fEnabled
3517 || !PDMAudioStrmStatusCanRead(pStreamEx->Core.fStatus))
3518 {
3519 rc = VERR_AUDIO_STREAM_NOT_READY;
3520 break;
3521 }
3522
3523 /*
3524 * Do the actual capturing.
3525 */
3526 if (RT_LIKELY(pStreamEx->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED))
3527 rc = drvAudioStreamCaptureNonInterleaved(pThis, pStreamEx, &cfCaptured);
3528 else if (pStreamEx->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW)
3529 rc = drvAudioStreamCaptureRaw(pThis, pStreamEx, &cfCaptured);
3530 else
3531 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
3532
3533 if (RT_SUCCESS(rc))
3534 {
3535 Log3Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStreamEx->Core.szName, cfCaptured, rc));
3536
3537 STAM_COUNTER_ADD(&pThis->Stats.TotalFramesIn, cfCaptured);
3538 STAM_COUNTER_ADD(&pStreamEx->In.Stats.TotalFramesCaptured, cfCaptured);
3539 }
3540 else if (RT_UNLIKELY(RT_FAILURE(rc)))
3541 LogRel(("Audio: Capturing stream '%s' failed with %Rrc\n", pStreamEx->Core.szName, rc));
3542 } while (0);
3543
3544 RTCritSectLeave(&pThis->CritSect);
3545
3546 if (pcFramesCaptured)
3547 *pcFramesCaptured = cfCaptured;
3548
3549 if (RT_FAILURE(rc))
3550 LogFlowFuncLeaveRC(rc);
3551 return rc;
3552}
3553
3554
3555/*********************************************************************************************************************************
3556* PDMIAUDIONOTIFYFROMHOST interface implementation. *
3557*********************************************************************************************************************************/
3558
3559/**
3560 * Marks a stream for re-init.
3561 */
3562static void drvAudioStreamMarkNeedReInit(PDRVAUDIOSTREAM pStreamEx, const char *pszCaller)
3563{
3564 LogFlow((LOG_FN_FMT ": Flagging %s for re-init.\n", pStreamEx->Core.szName)); RT_NOREF(pszCaller);
3565 pStreamEx->Core.fStatus |= PDMAUDIOSTREAM_STS_NEED_REINIT;
3566 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->Core.fStatus);
3567 pStreamEx->cTriesReInit = 0;
3568 pStreamEx->nsLastReInit = 0;
3569}
3570
3571
3572/**
3573 * @interface_method_impl{PDMIAUDIONOTIFYFROMHOST,pfnNotifyDeviceChanged}
3574 */
3575static DECLCALLBACK(void) drvAudioNotifyFromHost_NotifyDeviceChanged(PPDMIAUDIONOTIFYFROMHOST pInterface,
3576 PDMAUDIODIR enmDir, void *pvUser)
3577{
3578 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioNotifyFromHost);
3579 AssertReturnVoid(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT);
3580 LogRel(("Audio: The %s device for %s is changing.\n", enmDir == PDMAUDIODIR_IN ? "input" : "output", pThis->szName));
3581
3582 RTCritSectEnter(&pThis->CritSect);
3583 PDRVAUDIOSTREAM pStreamEx;
3584 RTListForEach(&pThis->lstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
3585 {
3586 if (pStreamEx->Core.enmDir == enmDir)
3587 {
3588 if (pThis->pHostDrvAudio->pfnStreamNotifyDeviceChanged)
3589 {
3590 LogFlowFunc(("Calling pfnStreamNotifyDeviceChanged on %s, old backend status: %#x...\n", pStreamEx->Core.szName,
3591 drvAudioStreamGetBackendStatus(pThis, pStreamEx) ));
3592 pThis->pHostDrvAudio->pfnStreamNotifyDeviceChanged(pThis->pHostDrvAudio, pStreamEx->pBackend, pvUser);
3593 LogFlowFunc(("New stream backend status: %#x.\n", drvAudioStreamGetBackendStatus(pThis, pStreamEx) ));
3594 }
3595 else
3596 drvAudioStreamMarkNeedReInit(pStreamEx, __PRETTY_FUNCTION__);
3597 }
3598 }
3599 RTCritSectLeave(&pThis->CritSect);
3600}
3601
3602
3603/**
3604 * @interface_method_impl{PDMIAUDIONOTIFYFROMHOST,pfnStreamNotifyDeviceChanged}
3605 */
3606static DECLCALLBACK(void) drvAudioNotifyFromHost_StreamNotifyDeviceChanged(PPDMIAUDIONOTIFYFROMHOST pInterface,
3607 PPDMAUDIOBACKENDSTREAM pStream, bool fReInit)
3608{
3609 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioNotifyFromHost);
3610
3611 /*
3612 * Backend stream to validated DrvAudio stream:
3613 */
3614 AssertPtrReturnVoid(pStream);
3615 AssertReturnVoid(pStream->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC);
3616 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream->pStream;
3617 AssertPtrReturnVoid(pStreamEx);
3618 AssertReturnVoid(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC);
3619 AssertReturnVoid(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC);
3620
3621 /*
3622 * Grab the lock and do the requested work.
3623 */
3624 RTCritSectEnter(&pThis->CritSect);
3625 AssertReturnVoidStmt(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, RTCritSectLeave(&pThis->CritSect)); /* paranoia */
3626
3627 if (fReInit)
3628 drvAudioStreamMarkNeedReInit(pStreamEx, __PRETTY_FUNCTION__);
3629 else
3630 {
3631 /*
3632 * Adjust the stream state now that the device has (perhaps finally) been switched.
3633 *
3634 * For enabled output streams, we must update the play state. We could try commit
3635 * pre-buffered data here, but it's really not worth the hazzle and risk (don't
3636 * know which thread we're on, do we now).
3637 */
3638 AssertStmt(!(pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_NEED_REINIT),
3639 pStreamEx->Core.fStatus &= ~PDMAUDIOSTREAM_STS_NEED_REINIT);
3640
3641 if ( pStreamEx->Core.enmDir == PDMAUDIODIR_OUT
3642 && (pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_ENABLED))
3643 {
3644 DRVAUDIOPLAYSTATE const enmPlayState = pStreamEx->Out.enmPlayState;
3645 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF;
3646 LogFunc(("%s: %s -> %s\n", pStreamEx->Core.szName, drvAudioPlayStateName(enmPlayState),
3647 drvAudioPlayStateName(pStreamEx->Out.enmPlayState) )); RT_NOREF(enmPlayState);
3648 }
3649 }
3650
3651 RTCritSectLeave(&pThis->CritSect);
3652}
3653
3654
3655/**
3656 * @interface_method_impl{PDMIAUDIONOTIFYFROMHOST,pfnNotifyDevicesChanged}
3657 */
3658static DECLCALLBACK(void) drvAudioNotifyFromHost_NotifyDevicesChanged(PPDMIAUDIONOTIFYFROMHOST pInterface)
3659{
3660 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioNotifyFromHost);
3661 LogRel(("Audio: Device configuration of driver '%s' has changed\n", pThis->szName));
3662
3663 /** @todo Remove legacy behaviour: */
3664 if (!pThis->pHostDrvAudio->pfnStreamNotifyDeviceChanged)
3665 {
3666/** @todo r=bird: Locking? */
3667 /* Mark all host streams to re-initialize. */
3668 PDRVAUDIOSTREAM pStreamEx;
3669 RTListForEach(&pThis->lstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
3670 {
3671 drvAudioStreamMarkNeedReInit(pStreamEx, __PRETTY_FUNCTION__);
3672 }
3673 }
3674
3675# ifdef VBOX_WITH_AUDIO_ENUM
3676 /* Re-enumerate all host devices as soon as possible. */
3677 pThis->fEnumerateDevices = true;
3678# endif
3679}
3680
3681
3682/*********************************************************************************************************************************
3683* PDMIBASE interface implementation. *
3684*********************************************************************************************************************************/
3685
3686/**
3687 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
3688 */
3689static DECLCALLBACK(void *) drvAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
3690{
3691 LogFlowFunc(("pInterface=%p, pszIID=%s\n", pInterface, pszIID));
3692
3693 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
3694 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3695
3696 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
3697 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIAUDIOCONNECTOR, &pThis->IAudioConnector);
3698 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIAUDIONOTIFYFROMHOST, &pThis->IAudioNotifyFromHost);
3699
3700 return NULL;
3701}
3702
3703
3704/*********************************************************************************************************************************
3705* PDMDRVREG interface implementation. *
3706*********************************************************************************************************************************/
3707
3708/**
3709 * Power Off notification.
3710 *
3711 * @param pDrvIns The driver instance data.
3712 */
3713static DECLCALLBACK(void) drvAudioPowerOff(PPDMDRVINS pDrvIns)
3714{
3715 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3716
3717 LogFlowFuncEnter();
3718
3719 /** @todo locking? */
3720 if (pThis->pHostDrvAudio) /* If not lower driver is configured, bail out. */
3721 {
3722 /*
3723 * Just destroy the host stream on the backend side.
3724 * The rest will either be destructed by the device emulation or
3725 * in drvAudioDestruct().
3726 */
3727 PDRVAUDIOSTREAM pStreamEx;
3728 RTListForEach(&pThis->lstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
3729 {
3730 drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
3731 drvAudioStreamDestroyInternalBackend(pThis, pStreamEx);
3732 }
3733
3734 pThis->pHostDrvAudio = NULL;
3735 }
3736
3737 LogFlowFuncLeave();
3738}
3739
3740
3741/**
3742 * Detach notification.
3743 *
3744 * @param pDrvIns The driver instance data.
3745 * @param fFlags Detach flags.
3746 */
3747static DECLCALLBACK(void) drvAudioDetach(PPDMDRVINS pDrvIns, uint32_t fFlags)
3748{
3749 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
3750 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3751 RT_NOREF(fFlags);
3752
3753 int rc = RTCritSectEnter(&pThis->CritSect);
3754 AssertRC(rc);
3755
3756 LogFunc(("%s (detached %p)\n", pThis->szName, pThis->pHostDrvAudio));
3757 pThis->pHostDrvAudio = NULL;
3758
3759 RTCritSectLeave(&pThis->CritSect);
3760}
3761
3762
3763/**
3764 * Initializes the host backend and queries its initial configuration.
3765 *
3766 * @returns VBox status code.
3767 * @param pThis Driver instance to be called.
3768 */
3769static int drvAudioHostInit(PDRVAUDIO pThis)
3770{
3771 LogFlowFuncEnter();
3772
3773 /*
3774 * Check the function pointers, make sure the ones we define as
3775 * mandatory are present.
3776 */
3777 PPDMIHOSTAUDIO pHostDrvAudio = pThis->pHostDrvAudio;
3778 AssertPtrReturn(pHostDrvAudio, VERR_INVALID_POINTER);
3779 AssertPtrReturn(pHostDrvAudio->pfnGetConfig, VERR_INVALID_POINTER);
3780 AssertPtrNullReturn(pHostDrvAudio->pfnGetDevices, VERR_INVALID_POINTER);
3781 AssertPtrNullReturn(pHostDrvAudio->pfnGetStatus, VERR_INVALID_POINTER);
3782 AssertPtrNullReturn(pHostDrvAudio->pfnStreamConfigHint, VERR_INVALID_POINTER);
3783 AssertPtrReturn(pHostDrvAudio->pfnStreamCreate, VERR_INVALID_POINTER);
3784 AssertPtrReturn(pHostDrvAudio->pfnStreamDestroy, VERR_INVALID_POINTER);
3785 AssertPtrNullReturn(pHostDrvAudio->pfnStreamNotifyDeviceChanged, VERR_INVALID_POINTER);
3786 AssertPtrReturn(pHostDrvAudio->pfnStreamControl, VERR_INVALID_POINTER);
3787 AssertPtrReturn(pHostDrvAudio->pfnStreamGetReadable, VERR_INVALID_POINTER);
3788 AssertPtrReturn(pHostDrvAudio->pfnStreamGetWritable, VERR_INVALID_POINTER);
3789 AssertPtrNullReturn(pHostDrvAudio->pfnStreamGetPending, VERR_INVALID_POINTER);
3790 AssertPtrReturn(pHostDrvAudio->pfnStreamGetStatus, VERR_INVALID_POINTER);
3791 AssertPtrReturn(pHostDrvAudio->pfnStreamPlay, VERR_INVALID_POINTER);
3792 AssertPtrReturn(pHostDrvAudio->pfnStreamCapture, VERR_INVALID_POINTER);
3793
3794 /*
3795 * Get the backend configuration.
3796 */
3797 int rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, &pThis->BackendCfg);
3798 if (RT_FAILURE(rc))
3799 {
3800 LogRel(("Audio: Getting configuration for driver '%s' failed with %Rrc\n", pThis->szName, rc));
3801 return VERR_AUDIO_BACKEND_INIT_FAILED;
3802 }
3803
3804 pThis->In.cStreamsFree = pThis->BackendCfg.cMaxStreamsIn;
3805 pThis->Out.cStreamsFree = pThis->BackendCfg.cMaxStreamsOut;
3806
3807 LogFlowFunc(("cStreamsFreeIn=%RU8, cStreamsFreeOut=%RU8\n", pThis->In.cStreamsFree, pThis->Out.cStreamsFree));
3808
3809 LogRel2(("Audio: Host driver '%s' supports %RU32 input streams and %RU32 output streams at once.\n",
3810 pThis->szName, pThis->In.cStreamsFree, pThis->Out.cStreamsFree));
3811
3812#ifdef VBOX_WITH_AUDIO_ENUM
3813 int rc2 = drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */);
3814 if (rc2 != VERR_NOT_SUPPORTED) /* Some backends don't implement device enumeration. */
3815 AssertRC(rc2);
3816
3817 RT_NOREF(rc2);
3818 /* Ignore rc. */
3819#endif
3820
3821 LogFlowFuncLeave();
3822 return VINF_SUCCESS;
3823}
3824
3825
3826/**
3827 * Does the actual backend driver attaching and queries the backend's interface.
3828 *
3829 * This is a worker for both drvAudioAttach and drvAudioConstruct.
3830 *
3831 * @returns VBox status code.
3832 * @param pDrvIns The driver instance.
3833 * @param pThis Pointer to driver instance.
3834 * @param fFlags Attach flags; see PDMDrvHlpAttach().
3835 */
3836static int drvAudioDoAttachInternal(PPDMDRVINS pDrvIns, PDRVAUDIO pThis, uint32_t fFlags)
3837{
3838 Assert(pThis->pHostDrvAudio == NULL); /* No nested attaching. */
3839
3840 /*
3841 * Attach driver below and query its connector interface.
3842 */
3843 PPDMIBASE pDownBase;
3844 int rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pDownBase);
3845 if (RT_SUCCESS(rc))
3846 {
3847 pThis->pHostDrvAudio = PDMIBASE_QUERY_INTERFACE(pDownBase, PDMIHOSTAUDIO);
3848 if (pThis->pHostDrvAudio)
3849 {
3850 /*
3851 * If everything went well, initialize the lower driver.
3852 */
3853 rc = drvAudioHostInit(pThis);
3854 if (RT_FAILURE(rc))
3855 pThis->pHostDrvAudio = NULL;
3856 }
3857 else
3858 {
3859 LogRel(("Audio: Failed to query interface for underlying host driver '%s'\n", pThis->szName));
3860 rc = PDMDRV_SET_ERROR(pThis->pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW,
3861 N_("The host audio driver does not implement PDMIHOSTAUDIO!"));
3862 }
3863 }
3864 /*
3865 * If the host driver below us failed to construct for some beningn reason,
3866 * we'll report it as a runtime error and replace it with the Null driver.
3867 *
3868 * Note! We do NOT change anything in PDM (or CFGM), so pDrvIns->pDownBase
3869 * will remain NULL in this case.
3870 */
3871 else if ( rc == VERR_AUDIO_BACKEND_INIT_FAILED
3872 || rc == VERR_MODULE_NOT_FOUND
3873 || rc == VERR_SYMBOL_NOT_FOUND
3874 || rc == VERR_FILE_NOT_FOUND
3875 || rc == VERR_PATH_NOT_FOUND)
3876 {
3877 /* Complain: */
3878 LogRel(("DrvAudio: Host audio driver '%s' init failed with %Rrc. Switching to the NULL driver for now.\n",
3879 pThis->szName, rc));
3880 PDMDrvHlpVMSetRuntimeError(pDrvIns, 0 /*fFlags*/, "HostAudioNotResponding",
3881 N_("Host audio backend (%s) initialization has failed. Selecting the NULL audio backend with the consequence that no sound is audible"),
3882 pThis->szName);
3883
3884 /* Replace with null audio: */
3885 pThis->pHostDrvAudio = (PPDMIHOSTAUDIO)&g_DrvHostAudioNull;
3886 RTStrCopy(pThis->szName, sizeof(pThis->szName), "NULL");
3887 rc = drvAudioHostInit(pThis);
3888 AssertRC(rc);
3889 }
3890
3891 LogFunc(("[%s] rc=%Rrc\n", pThis->szName, rc));
3892 return rc;
3893}
3894
3895
3896/**
3897 * Attach notification.
3898 *
3899 * @param pDrvIns The driver instance data.
3900 * @param fFlags Attach flags.
3901 */
3902static DECLCALLBACK(int) drvAudioAttach(PPDMDRVINS pDrvIns, uint32_t fFlags)
3903{
3904 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
3905 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3906 LogFunc(("%s\n", pThis->szName));
3907
3908 int rc = RTCritSectEnter(&pThis->CritSect);
3909 AssertRCReturn(rc, rc);
3910
3911 rc = drvAudioDoAttachInternal(pDrvIns, pThis, fFlags);
3912
3913 RTCritSectLeave(&pThis->CritSect);
3914 return rc;
3915}
3916
3917
3918/**
3919 * Handles state changes for all audio streams.
3920 *
3921 * @param pDrvIns Pointer to driver instance.
3922 * @param enmCmd Stream command to set for all streams.
3923 */
3924static void drvAudioStateHandler(PPDMDRVINS pDrvIns, PDMAUDIOSTREAMCMD enmCmd)
3925{
3926 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
3927 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3928 LogFlowFunc(("enmCmd=%s\n", PDMAudioStrmCmdGetName(enmCmd)));
3929
3930 int rc2 = RTCritSectEnter(&pThis->CritSect);
3931 AssertRCReturnVoid(rc2);
3932
3933 if (pThis->pHostDrvAudio)
3934 {
3935 PDRVAUDIOSTREAM pStreamEx;
3936 RTListForEach(&pThis->lstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
3937 {
3938 drvAudioStreamControlInternal(pThis, pStreamEx, enmCmd);
3939 }
3940 }
3941
3942 rc2 = RTCritSectLeave(&pThis->CritSect);
3943 AssertRC(rc2);
3944}
3945
3946
3947/**
3948 * Resume notification.
3949 *
3950 * @param pDrvIns The driver instance data.
3951 */
3952static DECLCALLBACK(void) drvAudioResume(PPDMDRVINS pDrvIns)
3953{
3954 drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_RESUME);
3955}
3956
3957
3958/**
3959 * Suspend notification.
3960 *
3961 * @param pDrvIns The driver instance data.
3962 */
3963static DECLCALLBACK(void) drvAudioSuspend(PPDMDRVINS pDrvIns)
3964{
3965 drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_PAUSE);
3966}
3967
3968
3969/**
3970 * Destructs an audio driver instance.
3971 *
3972 * @copydoc FNPDMDRVDESTRUCT
3973 */
3974static DECLCALLBACK(void) drvAudioDestruct(PPDMDRVINS pDrvIns)
3975{
3976 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
3977 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3978
3979 LogFlowFuncEnter();
3980
3981 if (RTCritSectIsInitialized(&pThis->CritSect))
3982 {
3983 int rc = RTCritSectEnter(&pThis->CritSect);
3984 AssertRC(rc);
3985 }
3986
3987 /*
3988 * Note: No calls here to the driver below us anymore,
3989 * as PDM already has destroyed it.
3990 * If you need to call something from the host driver,
3991 * do this in drvAudioPowerOff() instead.
3992 */
3993
3994 /* Thus, NULL the pointer to the host audio driver first,
3995 * so that routines like drvAudioStreamDestroyInternal() don't call the driver(s) below us anymore. */
3996 pThis->pHostDrvAudio = NULL;
3997
3998 PDRVAUDIOSTREAM pStreamEx, pStreamExNext;
3999 RTListForEachSafe(&pThis->lstStreams, pStreamEx, pStreamExNext, DRVAUDIOSTREAM, ListEntry)
4000 {
4001 int rc = drvAudioStreamUninitInternal(pThis, pStreamEx);
4002 if (RT_SUCCESS(rc))
4003 {
4004 RTListNodeRemove(&pStreamEx->ListEntry);
4005 drvAudioStreamFree(pStreamEx);
4006 }
4007 }
4008
4009 /* Sanity. */
4010 Assert(RTListIsEmpty(&pThis->lstStreams));
4011
4012 if (RTCritSectIsInitialized(&pThis->CritSect))
4013 {
4014 int rc = RTCritSectLeave(&pThis->CritSect);
4015 AssertRC(rc);
4016
4017 rc = RTCritSectDelete(&pThis->CritSect);
4018 AssertRC(rc);
4019 }
4020
4021 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Out.StatsReBuffering);
4022#ifdef VBOX_WITH_STATISTICS
4023 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalStreamsActive);
4024 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalStreamsCreated);
4025 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalFramesRead);
4026 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalFramesIn);
4027 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalBytesRead);
4028#endif
4029
4030 LogFlowFuncLeave();
4031}
4032
4033
4034/**
4035 * Constructs an audio driver instance.
4036 *
4037 * @copydoc FNPDMDRVCONSTRUCT
4038 */
4039static DECLCALLBACK(int) drvAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
4040{
4041 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
4042 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
4043 LogFlowFunc(("pDrvIns=%#p, pCfgHandle=%#p, fFlags=%x\n", pDrvIns, pCfg, fFlags));
4044
4045 /*
4046 * Basic instance init.
4047 */
4048 RTListInit(&pThis->lstStreams);
4049
4050 /*
4051 * Read configuration.
4052 */
4053 PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns,
4054 "DriverName|"
4055 "InputEnabled|"
4056 "OutputEnabled|"
4057 "DebugEnabled|"
4058 "DebugPathOut|"
4059 /* Deprecated: */
4060 "PCMSampleBitIn|"
4061 "PCMSampleBitOut|"
4062 "PCMSampleHzIn|"
4063 "PCMSampleHzOut|"
4064 "PCMSampleSignedIn|"
4065 "PCMSampleSignedOut|"
4066 "PCMSampleSwapEndianIn|"
4067 "PCMSampleSwapEndianOut|"
4068 "PCMSampleChannelsIn|"
4069 "PCMSampleChannelsOut|"
4070 "PeriodSizeMsIn|"
4071 "PeriodSizeMsOut|"
4072 "BufferSizeMsIn|"
4073 "BufferSizeMsOut|"
4074 "PreBufferSizeMsIn|"
4075 "PreBufferSizeMsOut",
4076 "In|Out");
4077
4078 int rc = CFGMR3QueryStringDef(pCfg, "DriverName", pThis->szName, sizeof(pThis->szName), "Untitled");
4079 AssertLogRelRCReturn(rc, rc);
4080
4081 /* Neither input nor output by default for security reasons. */
4082 rc = CFGMR3QueryBoolDef(pCfg, "InputEnabled", &pThis->In.fEnabled, false);
4083 AssertLogRelRCReturn(rc, rc);
4084
4085 rc = CFGMR3QueryBoolDef(pCfg, "OutputEnabled", &pThis->Out.fEnabled, false);
4086 AssertLogRelRCReturn(rc, rc);
4087
4088 /* Debug stuff (same for both directions). */
4089 rc = CFGMR3QueryBoolDef(pCfg, "DebugEnabled", &pThis->In.Cfg.Dbg.fEnabled, false);
4090 AssertLogRelRCReturn(rc, rc);
4091
4092 rc = CFGMR3QueryStringDef(pCfg, "DebugPathOut", pThis->In.Cfg.Dbg.szPathOut, sizeof(pThis->In.Cfg.Dbg.szPathOut), "");
4093 AssertLogRelRCReturn(rc, rc);
4094 if (pThis->In.Cfg.Dbg.szPathOut[0] == '\0')
4095 {
4096 rc = RTPathTemp(pThis->In.Cfg.Dbg.szPathOut, sizeof(pThis->In.Cfg.Dbg.szPathOut));
4097 if (RT_FAILURE(rc))
4098 {
4099 LogRel(("Audio: Warning! Failed to retrieve temporary directory: %Rrc - disabling debugging.\n", rc));
4100 pThis->In.Cfg.Dbg.szPathOut[0] = '\0';
4101 pThis->In.Cfg.Dbg.fEnabled = false;
4102 }
4103 }
4104 if (pThis->In.Cfg.Dbg.fEnabled)
4105 LogRel(("Audio: Debugging for driver '%s' enabled (audio data written to '%s')\n", pThis->szName, pThis->In.Cfg.Dbg.szPathOut));
4106
4107 /* Copy debug setup to the output direction. */
4108 pThis->Out.Cfg.Dbg = pThis->In.Cfg.Dbg;
4109
4110 LogRel2(("Audio: Verbose logging for driver '%s' is probably enabled too.\n", pThis->szName));
4111 /* This ^^^^^^^ is the *WRONG* place for that kind of statement. Verbose logging might only be enabled for DrvAudio. */
4112 LogRel2(("Audio: Initial status for driver '%s' is: input is %s, output is %s\n",
4113 pThis->szName, pThis->In.fEnabled ? "enabled" : "disabled", pThis->Out.fEnabled ? "enabled" : "disabled"));
4114
4115 /*
4116 * Per direction configuration. A bit complicated as
4117 * these wasn't originally in sub-nodes.
4118 */
4119 for (unsigned iDir = 0; iDir < 2; iDir++)
4120 {
4121 char szNm[48];
4122 PDRVAUDIOCFG pAudioCfg = iDir == 0 ? &pThis->In.Cfg : &pThis->Out.Cfg;
4123 const char *pszDir = iDir == 0 ? "In" : "Out";
4124
4125#define QUERY_VAL_RET(a_Width, a_szName, a_pValue, a_uDefault, a_ExprValid, a_szValidRange) \
4126 do { \
4127 rc = RT_CONCAT(CFGMR3QueryU,a_Width)(pDirNode, strcpy(szNm, a_szName), a_pValue); \
4128 if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) \
4129 { \
4130 rc = RT_CONCAT(CFGMR3QueryU,a_Width)(pCfg, strcat(szNm, pszDir), a_pValue); \
4131 if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) \
4132 { \
4133 *(a_pValue) = a_uDefault; \
4134 rc = VINF_SUCCESS; \
4135 } \
4136 else \
4137 LogRel(("DrvAudio: Warning! Please use '%s/" a_szName "' instead of '%s' for your VBoxInternal hacks\n", pszDir, szNm)); \
4138 } \
4139 AssertRCReturn(rc, PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, \
4140 N_("Configuration error: Failed to read %s config value '%s'"), pszDir, szNm)); \
4141 if (!(a_ExprValid)) \
4142 return PDMDrvHlpVMSetError(pDrvIns, VERR_OUT_OF_RANGE, RT_SRC_POS, \
4143 N_("Configuration error: Unsupported %s value %u. " a_szValidRange), szNm, *(a_pValue)); \
4144 } while (0)
4145
4146 PCFGMNODE const pDirNode = CFGMR3GetChild(pCfg, pszDir);
4147 rc = CFGMR3ValidateConfig(pDirNode, iDir == 0 ? "In/" : "Out/",
4148 "PCMSampleBit|"
4149 "PCMSampleHz|"
4150 "PCMSampleSigned|"
4151 "PCMSampleSwapEndian|"
4152 "PCMSampleChannels|"
4153 "PeriodSizeMs|"
4154 "BufferSizeMs|"
4155 "PreBufferSizeMs",
4156 "", pDrvIns->pReg->szName, pDrvIns->iInstance);
4157 AssertRCReturn(rc, rc);
4158
4159 uint8_t cSampleBits = 0;
4160 QUERY_VAL_RET(8, "PCMSampleBit", &cSampleBits, 0,
4161 cSampleBits == 0
4162 || cSampleBits == 8
4163 || cSampleBits == 16
4164 || cSampleBits == 32
4165 || cSampleBits == 64,
4166 "Must be either 0, 8, 16, 32 or 64");
4167 if (cSampleBits)
4168 PDMAudioPropsSetSampleSize(&pAudioCfg->Props, cSampleBits / 8);
4169
4170 uint8_t cChannels;
4171 QUERY_VAL_RET(8, "PCMSampleChannels", &cChannels, 0, cChannels <= 16, "Max 16");
4172 if (cChannels)
4173 PDMAudioPropsSetChannels(&pAudioCfg->Props, cChannels);
4174
4175 QUERY_VAL_RET(32, "PCMSampleHz", &pAudioCfg->Props.uHz, 0,
4176 pAudioCfg->Props.uHz == 0 || (pAudioCfg->Props.uHz >= 6000 && pAudioCfg->Props.uHz <= 768000),
4177 "In the range 6000 thru 768000, or 0");
4178
4179 QUERY_VAL_RET(8, "PCMSampleSigned", &pAudioCfg->uSigned, UINT8_MAX,
4180 pAudioCfg->uSigned == 0 || pAudioCfg->uSigned == 1 || pAudioCfg->uSigned == UINT8_MAX,
4181 "Must be either 0, 1, or 255");
4182
4183 QUERY_VAL_RET(8, "PCMSampleSwapEndian", &pAudioCfg->uSwapEndian, UINT8_MAX,
4184 pAudioCfg->uSwapEndian == 0 || pAudioCfg->uSwapEndian == 1 || pAudioCfg->uSwapEndian == UINT8_MAX,
4185 "Must be either 0, 1, or 255");
4186
4187 QUERY_VAL_RET(32, "PeriodSizeMs", &pAudioCfg->uPeriodSizeMs, 0,
4188 pAudioCfg->uPeriodSizeMs <= RT_MS_1SEC, "Max 1000");
4189
4190 QUERY_VAL_RET(32, "BufferSizeMs", &pAudioCfg->uBufferSizeMs, 0,
4191 pAudioCfg->uBufferSizeMs <= RT_MS_5SEC, "Max 5000");
4192
4193 QUERY_VAL_RET(32, "PreBufferSizeMs", &pAudioCfg->uPreBufSizeMs, UINT32_MAX,
4194 pAudioCfg->uPreBufSizeMs <= RT_MS_1SEC || pAudioCfg->uPreBufSizeMs == UINT32_MAX,
4195 "Max 1000, or 0xffffffff");
4196#undef QUERY_VAL_RET
4197 }
4198
4199 /*
4200 * Init the rest of the driver instance data.
4201 */
4202 rc = RTCritSectInit(&pThis->CritSect);
4203 AssertRCReturn(rc, rc);
4204
4205 pThis->fTerminate = false;
4206 pThis->pDrvIns = pDrvIns;
4207 /* IBase. */
4208 pDrvIns->IBase.pfnQueryInterface = drvAudioQueryInterface;
4209 /* IAudioConnector. */
4210 pThis->IAudioConnector.pfnEnable = drvAudioEnable;
4211 pThis->IAudioConnector.pfnIsEnabled = drvAudioIsEnabled;
4212 pThis->IAudioConnector.pfnGetConfig = drvAudioGetConfig;
4213 pThis->IAudioConnector.pfnGetStatus = drvAudioGetStatus;
4214 pThis->IAudioConnector.pfnStreamConfigHint = drvAudioStreamConfigHint;
4215 pThis->IAudioConnector.pfnStreamCreate = drvAudioStreamCreate;
4216 pThis->IAudioConnector.pfnStreamDestroy = drvAudioStreamDestroy;
4217 pThis->IAudioConnector.pfnStreamReInit = drvAudioStreamReInit;
4218 pThis->IAudioConnector.pfnStreamRetain = drvAudioStreamRetain;
4219 pThis->IAudioConnector.pfnStreamRelease = drvAudioStreamRelease;
4220 pThis->IAudioConnector.pfnStreamControl = drvAudioStreamControl;
4221 pThis->IAudioConnector.pfnStreamIterate = drvAudioStreamIterate;
4222 pThis->IAudioConnector.pfnStreamGetReadable = drvAudioStreamGetReadable;
4223 pThis->IAudioConnector.pfnStreamGetWritable = drvAudioStreamGetWritable;
4224 pThis->IAudioConnector.pfnStreamGetStatus = drvAudioStreamGetStatus;
4225 pThis->IAudioConnector.pfnStreamSetVolume = drvAudioStreamSetVolume;
4226 pThis->IAudioConnector.pfnStreamPlay = drvAudioStreamPlay;
4227 pThis->IAudioConnector.pfnStreamRead = drvAudioStreamRead;
4228 pThis->IAudioConnector.pfnStreamCapture = drvAudioStreamCapture;
4229 /* IAudioNotifyFromHost */
4230 pThis->IAudioNotifyFromHost.pfnNotifyDeviceChanged = drvAudioNotifyFromHost_NotifyDeviceChanged;
4231 pThis->IAudioNotifyFromHost.pfnStreamNotifyDeviceChanged = drvAudioNotifyFromHost_StreamNotifyDeviceChanged;
4232 pThis->IAudioNotifyFromHost.pfnNotifyDevicesChanged = drvAudioNotifyFromHost_NotifyDevicesChanged;
4233
4234 /*
4235 * Statistics.
4236 */
4237 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Out.StatsReBuffering, "OutputReBuffering",
4238 STAMUNIT_COUNT, "Number of times the output stream was re-buffered after starting.");
4239
4240#ifdef VBOX_WITH_STATISTICS
4241 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalStreamsActive, "TotalStreamsActive",
4242 STAMUNIT_COUNT, "Total active audio streams.");
4243 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalStreamsCreated, "TotalStreamsCreated",
4244 STAMUNIT_COUNT, "Total created audio streams.");
4245 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesRead, "TotalFramesRead",
4246 STAMUNIT_COUNT, "Total frames read by device emulation.");
4247 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesIn, "TotalFramesIn",
4248 STAMUNIT_COUNT, "Total frames captured by backend.");
4249 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalBytesRead, "TotalBytesRead",
4250 STAMUNIT_BYTES, "Total bytes read.");
4251#endif
4252
4253 /*
4254 * Create a timer to do finish closing output streams in PENDING_DISABLE state.
4255 *
4256 * The device won't call us again after it has disabled a the stream and this is
4257 * a real problem for truely cyclic buffer backends like DSound which will just
4258 * continue to loop and loop if not stopped.
4259 */
4260 RTStrPrintf(pThis->szTimerName, sizeof(pThis->szTimerName), "AudioIterate-%u", pDrvIns->iInstance);
4261 rc = PDMDrvHlpTMTimerCreate(pDrvIns, TMCLOCK_VIRTUAL, drvAudioEmergencyIterateTimer, NULL /*pvUser*/,
4262 0 /*fFlags*/, pThis->szTimerName, &pThis->hTimer);
4263 AssertRCReturn(rc, rc);
4264
4265 /*
4266 * Attach the host driver, if present.
4267 */
4268 rc = drvAudioDoAttachInternal(pDrvIns, pThis, fFlags);
4269 if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
4270 rc = VINF_SUCCESS;
4271
4272 LogFlowFuncLeaveRC(rc);
4273 return rc;
4274}
4275
4276/**
4277 * Audio driver registration record.
4278 */
4279const PDMDRVREG g_DrvAUDIO =
4280{
4281 /* u32Version */
4282 PDM_DRVREG_VERSION,
4283 /* szName */
4284 "AUDIO",
4285 /* szRCMod */
4286 "",
4287 /* szR0Mod */
4288 "",
4289 /* pszDescription */
4290 "Audio connector driver",
4291 /* fFlags */
4292 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
4293 /* fClass */
4294 PDM_DRVREG_CLASS_AUDIO,
4295 /* cMaxInstances */
4296 UINT32_MAX,
4297 /* cbInstance */
4298 sizeof(DRVAUDIO),
4299 /* pfnConstruct */
4300 drvAudioConstruct,
4301 /* pfnDestruct */
4302 drvAudioDestruct,
4303 /* pfnRelocate */
4304 NULL,
4305 /* pfnIOCtl */
4306 NULL,
4307 /* pfnPowerOn */
4308 NULL,
4309 /* pfnReset */
4310 NULL,
4311 /* pfnSuspend */
4312 drvAudioSuspend,
4313 /* pfnResume */
4314 drvAudioResume,
4315 /* pfnAttach */
4316 drvAudioAttach,
4317 /* pfnDetach */
4318 drvAudioDetach,
4319 /* pfnPowerOff */
4320 drvAudioPowerOff,
4321 /* pfnSoftReset */
4322 NULL,
4323 /* u32EndVersion */
4324 PDM_DRVREG_VERSION
4325};
4326
Note: See TracBrowser for help on using the repository browser.

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