VirtualBox

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

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

DrvAudio: Logging build fix. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 172.8 KB
Line 
1/* $Id: DrvAudio.cpp 88763 2021-04-29 01:23:22Z 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 if (!(pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_ENABLED))
1981 {
1982 /* Is a pending disable outstanding? Then disable first. */
1983 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)
1984 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
1985 if (RT_SUCCESS(rc))
1986 {
1987 /* Reset the play state before we try to start. */
1988 pStreamEx->fLastBackendStatus = drvAudioStreamGetBackendStatus(pThis, pStreamEx);
1989 if (pStreamEx->Core.enmDir == PDMAUDIODIR_OUT)
1990 {
1991 pStreamEx->Out.cbPreBuffered = 0;
1992 pStreamEx->Out.offPreBuf = 0;
1993 pStreamEx->Out.enmPlayState = pStreamEx->Out.cbPreBufThreshold > 0
1994 ? DRVAUDIOPLAYSTATE_PREBUF
1995 : pStreamEx->fLastBackendStatus & PDMAUDIOSTREAM_STS_INITIALIZED
1996 ? DRVAUDIOPLAYSTATE_PLAY
1997 : DRVAUDIOPLAYSTATE_NOPLAY;
1998 LogFunc(("ENABLE: fLastBackendStatus=%#x enmPlayState=%s\n",
1999 pStreamEx->fLastBackendStatus, drvAudioPlayStateName(pStreamEx->Out.enmPlayState)));
2000 }
2001 else
2002 LogFunc(("ENABLE: fLastBackendStatus=%#x\n", pStreamEx->fLastBackendStatus));
2003
2004 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_ENABLE);
2005 if (RT_SUCCESS(rc))
2006 {
2007 pStreamEx->Core.fStatus |= PDMAUDIOSTREAM_STS_ENABLED;
2008 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->Core.fStatus);
2009 }
2010 }
2011 }
2012 break;
2013
2014 case PDMAUDIOSTREAMCMD_DISABLE:
2015 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_ENABLED)
2016 {
2017 /*
2018 * For playback (output) streams first mark the host stream as pending disable,
2019 * so that the rest of the remaining audio data will be played first before
2020 * closing the stream.
2021 */
2022 if (pStreamEx->Core.enmDir == PDMAUDIODIR_OUT)
2023 {
2024 LogFunc(("[%s] Pending disable/pause\n", pStreamEx->Core.szName));
2025 pStreamEx->Core.fStatus |= PDMAUDIOSTREAM_STS_PENDING_DISABLE;
2026 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->Core.fStatus);
2027
2028 /* Schedule a follow up timer to the pending-disable state. We cannot rely
2029 on the device to provide further callouts to finish the state transition.
2030 10ms is taking out of thin air and may be too course grained, we should
2031 really consider the amount of unplayed buffer in the backend and what not... */
2032 if (!pThis->fTimerArmed)
2033 {
2034 LogFlowFunc(("Arming emergency pending-disable hack...\n"));
2035 int rc2 = PDMDrvHlpTimerSetMillies(pThis->pDrvIns, pThis->hTimer, 10 /*ms*/);
2036 AssertRC(rc2);
2037 pThis->fTimerArmed = true;
2038 }
2039 }
2040
2041 /* Can we close the host stream as well (not in pending disable mode)? */
2042 if (!(pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE))
2043 {
2044 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
2045 if (RT_SUCCESS(rc))
2046 drvAudioStreamResetInternal(pStreamEx);
2047 }
2048 }
2049 break;
2050
2051 case PDMAUDIOSTREAMCMD_PAUSE:
2052 if ( (pStreamEx->Core.fStatus & (PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PAUSED))
2053 == PDMAUDIOSTREAM_STS_ENABLED)
2054 {
2055 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_PAUSE);
2056 if (RT_SUCCESS(rc))
2057 {
2058 pStreamEx->Core.fStatus |= PDMAUDIOSTREAM_STS_PAUSED;
2059 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->Core.fStatus);
2060 }
2061 }
2062 break;
2063
2064 case PDMAUDIOSTREAMCMD_RESUME:
2065 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_PAUSED)
2066 {
2067 Assert(pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_ENABLED);
2068 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_RESUME);
2069 if (RT_SUCCESS(rc))
2070 {
2071 pStreamEx->Core.fStatus &= ~PDMAUDIOSTREAM_STS_PAUSED;
2072 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->Core.fStatus);
2073 }
2074 }
2075 break;
2076
2077 default:
2078 rc = VERR_NOT_IMPLEMENTED;
2079 break;
2080 }
2081
2082 if (RT_FAILURE(rc))
2083 LogFunc(("[%s] Failed with %Rrc\n", pStreamEx->Core.szName, rc));
2084
2085 return rc;
2086}
2087
2088
2089/**
2090 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamControl}
2091 */
2092static DECLCALLBACK(int) drvAudioStreamControl(PPDMIAUDIOCONNECTOR pInterface,
2093 PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
2094{
2095 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
2096 AssertPtr(pThis);
2097
2098 /** @todo r=bird: why? It's not documented to ignore NULL streams. */
2099 if (!pStream)
2100 return VINF_SUCCESS;
2101 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
2102 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
2103 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
2104 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
2105
2106 int rc = RTCritSectEnter(&pThis->CritSect);
2107 AssertRCReturn(rc, rc);
2108
2109 LogFlowFunc(("[%s] enmStreamCmd=%s\n", pStream->szName, PDMAudioStrmCmdGetName(enmStreamCmd)));
2110
2111 rc = drvAudioStreamControlInternal(pThis, pStreamEx, enmStreamCmd);
2112
2113 RTCritSectLeave(&pThis->CritSect);
2114 return rc;
2115}
2116
2117
2118/**
2119 * Copy data to the pre-buffer, ring-buffer style.
2120 *
2121 * This is used in two slightly different situations:
2122 *
2123 * -# When the stream is started (enabled) and we only want to prebuffer up
2124 * to the threshold before pushing the data to the backend. We
2125 * typically use the max buffer size for this situation.
2126 *
2127 * -# When the backend sets the PDMAUDIOSTREAM_STS_PREPARING_SWITCH
2128 * status bit and we're preparing for a smooth switch over to a
2129 * different audio device. Most of the pre-buffered data should not be
2130 * played on the old device prior to the switch, due to the prebuffering
2131 * at the start of the stream. We only use the threshold size for this
2132 * case.
2133 */
2134static int drvAudioStreamPreBuffer(PDRVAUDIOSTREAM pStreamEx, const uint8_t *pbBuf, uint32_t cbBuf, uint32_t cbMax)
2135{
2136 uint32_t const cbAlloc = pStreamEx->Out.cbPreBufAlloc;
2137 AssertReturn(cbAlloc >= cbMax, VERR_INTERNAL_ERROR_3);
2138 AssertReturn(cbAlloc >= 8, VERR_INTERNAL_ERROR_4);
2139 AssertReturn(cbMax >= 8, VERR_INTERNAL_ERROR_5);
2140
2141 uint32_t offRead = pStreamEx->Out.offPreBuf;
2142 uint32_t cbCur = pStreamEx->Out.cbPreBuffered;
2143 AssertStmt(offRead < cbAlloc, offRead %= cbAlloc);
2144 AssertStmt(cbCur <= cbMax, offRead = (offRead + cbCur - cbMax) % cbAlloc; cbCur = cbMax);
2145
2146 /*
2147 * First chunk.
2148 */
2149 uint32_t offWrite = (offRead + cbCur) % cbAlloc;
2150 uint32_t cbToCopy = RT_MIN(cbAlloc - offWrite, cbBuf);
2151 memcpy(&pStreamEx->Out.pbPreBuf[offWrite], pbBuf, cbToCopy);
2152
2153 /* Advance. */
2154 offWrite = (offWrite + cbToCopy) % cbAlloc;
2155 for (;;)
2156 {
2157 pbBuf += cbToCopy;
2158 cbCur += cbToCopy;
2159 if (cbCur > cbMax)
2160 offRead = (offRead + cbCur - cbMax) % cbAlloc;
2161 cbBuf -= cbToCopy;
2162 if (!cbBuf)
2163 break;
2164
2165 /*
2166 * Second+ chunk, from the start of the buffer.
2167 *
2168 * Note! It is assumed very unlikely that we will ever see a cbBuf larger than
2169 * cbMax, so we don't waste space on clipping cbBuf here (can happen with
2170 * custom pre-buffer sizes).
2171 */
2172 Assert(offWrite == 0);
2173 cbToCopy = RT_MIN(cbAlloc, cbBuf);
2174 memcpy(pStreamEx->Out.pbPreBuf, pbBuf, cbToCopy);
2175 }
2176
2177 /*
2178 * Update the pre-buffering size and position.
2179 */
2180 pStreamEx->Out.cbPreBuffered = cbCur;
2181 pStreamEx->Out.offPreBuf = offRead;
2182 return VINF_SUCCESS;
2183}
2184
2185
2186#if 0
2187/**
2188 * Worker for drvAudioStreamPlay() and drvAudioStreamIterateInternal().
2189 *
2190 * The buffer is NULL and has a zero length when called from the interate
2191 * function. This only occures when there is pre-buffered audio data that need
2192 * to be pushed to the backend due to a pending disabling of the stream.
2193 *
2194 * Caller owns the lock.
2195 */
2196static int drvAudioStreamPlayLocked(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, uint32_t fBackStatus,
2197 const uint8_t *pbBuf, uint32_t cbBuf, uint32_t *pcbWritten)
2198{
2199 Log3Func(("%s: @%#RX64: cbBuf=%#x fBackStatus=%#x\n", pStreamEx->Core.szName, pStreamEx->offInternal, cbBuf, fBackStatus));
2200 RT_NOREF(fBackStatus);
2201
2202 /*
2203 * Are we pre-buffering?
2204 *
2205 * Note! We do not restart pre-buffering in this version, as we'd
2206 * need some kind of cooperation with the backend buffer
2207 * managment to correctly detect an underrun.
2208 */
2209 bool fJustStarted = false;
2210 uint32_t cbWritten = 0;
2211 int rc;
2212 if ( pStreamEx->fThresholdReached
2213 && pStreamEx->Out.cbPreBuffered == 0)
2214 {
2215 /* not-prebuffering, likely after a while at least */
2216 rc = VINF_SUCCESS;
2217 }
2218 else
2219 {
2220 /*
2221 * Copy as much as we can to the pre-buffer.
2222 */
2223 uint32_t cbFree = pStreamEx->Out.cbPreBufAlloc - pStreamEx->Out.cbPreBuffered;
2224 AssertReturn((int32_t)cbFree >= 0, VERR_INTERNAL_ERROR_2);
2225 if (cbFree > 0 && cbBuf > 0)
2226 {
2227 cbWritten = RT_MIN(cbFree, cbBuf);
2228 cbWritten = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Props, cbWritten);
2229 if (pStreamEx->Out.offPreBuf == 0)
2230 memcpy(&pStreamEx->Out.pbPreBuf[pStreamEx->Out.cbPreBuffered], pbBuf, cbWritten);
2231 else
2232 {
2233
2234 }
2235
2236 pStreamEx->Out.cbPreBuffered += cbWritten;
2237 cbBuf -= cbWritten;
2238 pbBuf += cbWritten;
2239 pStreamEx->offInternal += cbWritten;
2240 }
2241
2242 /*
2243 * Get the special case of buggy backend drivers out of the way.
2244 * We get here if we couldn't write out all the pre-buffered data when
2245 * we hit the threshold.
2246 */
2247 if (pStreamEx->fThresholdReached)
2248 LogRel2(("Audio: @%#RX64: Stream '%s' pre-buffering commit problem: cbBuf=%#x cbPreBuffered=%#x\n",
2249 pStreamEx->offInternal, pStreamEx->Core.szName, cbBuf, pStreamEx->Out.cbPreBuffered));
2250 /*
2251 * Did we reach the backend's playback (pre-buffering) threshold?
2252 * Can be 0 if no pre-buffering desired.
2253 */
2254 else if (pStreamEx->Out.cbPreBuffered + cbBuf >= pStreamEx->Out.cbPreBufThreshold)
2255 {
2256 LogRel2(("Audio: @%#RX64: Stream '%s' buffering complete! (%#x + %#x bytes)\n",
2257 pStreamEx->offInternal, pStreamEx->Core.szName, pStreamEx->Out.cbPreBuffered, cbBuf));
2258 pStreamEx->fThresholdReached = fJustStarted = true;
2259 }
2260 /*
2261 * Some audio files are shorter than the pre-buffering level (e.g. the
2262 * "click" Explorer sounds on some Windows guests), so make sure that we
2263 * also play those by checking if the stream already is pending disable
2264 * mode, even if we didn't hit the pre-buffering watermark yet.
2265 *
2266 * Try play "Windows Navigation Start.wav" on Windows 7 (2824 samples).
2267 */
2268 else if ( (pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)
2269 && pStreamEx->Out.cbPreBuffered > 0)
2270 {
2271 LogRel2(("Audio: @%#RX64: Stream '%s' buffering complete - short sound! (%#x + %#x bytes)\n",
2272 pStreamEx->offInternal, pStreamEx->Core.szName, pStreamEx->Out.cbPreBuffered, cbBuf));
2273 pStreamEx->fThresholdReached = fJustStarted = true;
2274 }
2275 /*
2276 * Not yet, so still buffering audio data.
2277 */
2278 else
2279 {
2280 LogRel2(("Audio: @%#RX64: Stream '%s' is buffering (%RU8%% complete)...\n", pStreamEx->offInternal,
2281 pStreamEx->Core.szName, (100 * pStreamEx->Out.cbPreBuffered) / pStreamEx->Out.cbPreBufThreshold));
2282 Assert(cbBuf == 0);
2283 *pcbWritten = cbWritten;
2284 return VINF_SUCCESS;
2285 }
2286
2287 /*
2288 * Write the pre-buffered chunk.
2289 */
2290 uint32_t off = 0;
2291 uint32_t cbPreBufWritten;
2292 do
2293 {
2294 cbPreBufWritten = 0;
2295 rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStreamEx->pBackend, &pStreamEx->Out.pbPreBuf[off],
2296 pStreamEx->Out.cbPreBuffered - off, &cbPreBufWritten);
2297 AssertRCBreak(rc);
2298 off += cbPreBufWritten;
2299 } while (off < pStreamEx->Out.cbPreBuffered && cbPreBufWritten != 0);
2300
2301 if (off >= pStreamEx->Out.cbPreBuffered)
2302 {
2303 Assert(off == pStreamEx->Out.cbPreBuffered);
2304 LogFunc(("@%#RX64: Wrote all %#x bytes of pre-buffered audio data.\n", pStreamEx->offInternal, off));
2305 pStreamEx->Out.cbPreBuffered = 0;
2306 }
2307 else
2308 {
2309 LogRel2(("Audio: @%#RX64: Stream '%s' pre-buffering commit problem: wrote %#x out of %#x + %#x%s - rc=%Rrc *pcbWritten=%#x\n",
2310 pStreamEx->offInternal, pStreamEx->Core.szName, off, pStreamEx->Out.cbPreBuffered, cbBuf,
2311 fJustStarted ? " (just started)" : "", rc, cbWritten));
2312 AssertMsg(!fJustStarted || RT_FAILURE(rc),
2313 ("Buggy host driver buffer reporting: off=%#x cbPreBuffered=%#x\n", off, pStreamEx->Out.cbPreBuffered));
2314 if (off > 0)
2315 {
2316 memmove(pStreamEx->Out.pbPreBuf, &pStreamEx->Out.pbPreBuf[off], pStreamEx->Out.cbPreBuffered - off);
2317 pStreamEx->Out.cbPreBuffered -= off;
2318 }
2319 pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS();
2320 *pcbWritten = cbWritten;
2321 return cbWritten ? VINF_SUCCESS : rc;
2322 }
2323
2324 if (RT_FAILURE(rc))
2325 {
2326 *pcbWritten = cbWritten;
2327 return rc;
2328 }
2329 }
2330
2331 /*
2332 * Do the writing.
2333 */
2334 uint32_t cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
2335 pStreamEx->Out.Stats.cbBackendWritableBefore = cbWritable;
2336
2337 uint8_t const cbFrame = PDMAudioPropsFrameSize(&pStreamEx->Core.Props);
2338 while (cbBuf >= cbFrame && cbWritable >= cbFrame)
2339 {
2340 uint32_t const cbToWrite = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Props, RT_MIN(cbBuf, cbWritable));
2341 uint32_t cbWrittenNow = 0;
2342 rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStreamEx->pBackend, pbBuf, cbToWrite, &cbWrittenNow);
2343 if (RT_SUCCESS(rc))
2344 {
2345 if (cbWrittenNow != cbToWrite)
2346 Log3Func(("%s: @%#RX64: Wrote less bytes than requested: %#x, requested %#x\n",
2347 pStreamEx->Core.szName, pStreamEx->offInternal, cbWrittenNow, cbToWrite));
2348#ifdef DEBUG_bird
2349 Assert(cbWrittenNow == cbToWrite);
2350#endif
2351 AssertStmt(cbWrittenNow <= cbToWrite, cbWrittenNow = cbToWrite);
2352 cbWritten += cbWrittenNow;
2353 cbBuf -= cbWrittenNow;
2354 pbBuf += cbWrittenNow;
2355 pStreamEx->offInternal += cbWrittenNow;
2356 }
2357 else
2358 {
2359 *pcbWritten = cbWritten;
2360 LogFunc(("%s: @%#RX64: pfnStreamPlay failed writing %#x bytes (%#x previous written, %#x writable): %Rrc\n",
2361 pStreamEx->Core.szName, pStreamEx->offInternal, cbToWrite, cbWritten, cbWritable, rc));
2362 return cbWritten ? VINF_SUCCESS : rc;
2363 }
2364 cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
2365 }
2366
2367 *pcbWritten = cbWritten;
2368 pStreamEx->Out.Stats.cbBackendWritableAfter = cbWritable;
2369 if (cbWritten)
2370 pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS();
2371
2372 Log3Func(("%s: @%#RX64: Wrote %#x bytes (%#x bytes left)\n", pStreamEx->Core.szName, pStreamEx->offInternal, cbWritten, cbBuf));
2373 return rc;
2374}
2375#endif
2376
2377
2378/**
2379 * Worker for drvAudioStreamPlay() and drvAudioStreamIterateInternal().
2380 *
2381 * Caller owns the lock.
2382 */
2383static int drvAudioStreamPlayLocked(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx,
2384 const uint8_t *pbBuf, uint32_t cbBuf, uint32_t *pcbWritten)
2385{
2386 Log3Func(("%s: @%#RX64: cbBuf=%#x\n", pStreamEx->Core.szName, pStreamEx->offInternal, cbBuf));
2387
2388 uint32_t cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
2389 pStreamEx->Out.Stats.cbBackendWritableBefore = cbWritable;
2390
2391 uint32_t cbWritten = 0;
2392 int rc = VINF_SUCCESS;
2393 uint8_t const cbFrame = PDMAudioPropsFrameSize(&pStreamEx->Core.Props);
2394 while (cbBuf >= cbFrame && cbWritable >= cbFrame)
2395 {
2396 uint32_t const cbToWrite = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Props, RT_MIN(cbBuf, cbWritable));
2397 uint32_t cbWrittenNow = 0;
2398 rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStreamEx->pBackend, pbBuf, cbToWrite, &cbWrittenNow);
2399 if (RT_SUCCESS(rc))
2400 {
2401 if (cbWrittenNow != cbToWrite)
2402 Log3Func(("%s: @%#RX64: Wrote less bytes than requested: %#x, requested %#x\n",
2403 pStreamEx->Core.szName, pStreamEx->offInternal, cbWrittenNow, cbToWrite));
2404#ifdef DEBUG_bird
2405 Assert(cbWrittenNow == cbToWrite);
2406#endif
2407 AssertStmt(cbWrittenNow <= cbToWrite, cbWrittenNow = cbToWrite);
2408 cbWritten += cbWrittenNow;
2409 cbBuf -= cbWrittenNow;
2410 pbBuf += cbWrittenNow;
2411 pStreamEx->offInternal += cbWrittenNow;
2412 }
2413 else
2414 {
2415 *pcbWritten = cbWritten;
2416 LogFunc(("%s: @%#RX64: pfnStreamPlay failed writing %#x bytes (%#x previous written, %#x writable): %Rrc\n",
2417 pStreamEx->Core.szName, pStreamEx->offInternal, cbToWrite, cbWritten, cbWritable, rc));
2418 return cbWritten ? VINF_SUCCESS : rc;
2419 }
2420
2421 cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
2422 }
2423
2424 *pcbWritten = cbWritten;
2425 pStreamEx->Out.Stats.cbBackendWritableAfter = cbWritable;
2426 if (cbWritten)
2427 pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS();
2428
2429 Log3Func(("%s: @%#RX64: Wrote %#x bytes (%#x bytes left)\n", pStreamEx->Core.szName, pStreamEx->offInternal, cbWritten, cbBuf));
2430 return rc;
2431}
2432
2433
2434static int drvAudioStreamPlayToPreBuffer(PDRVAUDIOSTREAM pStreamEx, const void *pvBuf, uint32_t cbBuf, uint32_t cbMax,
2435 uint32_t *pcbWritten)
2436{
2437 int rc = drvAudioStreamPreBuffer(pStreamEx, (uint8_t const *)pvBuf, cbBuf, cbMax);
2438 if (RT_SUCCESS(rc))
2439 {
2440 *pcbWritten = cbBuf;
2441 pStreamEx->offInternal += cbBuf;
2442 Log3Func(("[%s] Pre-buffering (%s): wrote %#x bytes => %#x bytes / %u%%\n",
2443 pStreamEx->Core.szName, drvAudioPlayStateName(pStreamEx->Out.enmPlayState), cbBuf, pStreamEx->Out.cbPreBuffered,
2444 pStreamEx->Out.cbPreBuffered * 100 / RT_MAX(pStreamEx->Out.cbPreBufThreshold, 1)));
2445
2446 }
2447 else
2448 *pcbWritten = 0;
2449 return rc;
2450}
2451
2452
2453/**
2454 * Used when we're committing (transfering) the pre-buffered bytes to the
2455 * device.
2456 *
2457 * This is called both from drvAudioStreamPlay() and
2458 * drvAudioStreamIterateInternal().
2459 *
2460 * @returns VBox status code.
2461 * @param pThis Pointer to the DrvAudio instance data.
2462 * @param pStreamEx The stream to commit the pre-buffering for.
2463 * @param pbBuf Buffer with new bytes to write. Can be NULL when called
2464 * in the PENDING_DISABLE state from
2465 * drvAudioStreamIterateInternal().
2466 * @param cbBuf Number of new bytes. Can be zero.
2467 * @param pcbWritten Where to return the number of bytes written.
2468 */
2469static int drvAudioStreamPreBufComitting(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx,
2470 const uint8_t *pbBuf, uint32_t cbBuf, uint32_t *pcbWritten)
2471{
2472 /*
2473 * First, top up the buffer with new data from pbBuf.
2474 */
2475 *pcbWritten = 0;
2476 if (cbBuf > 0)
2477 {
2478 uint32_t const cbToCopy = RT_MIN(pStreamEx->Out.cbPreBufAlloc - pStreamEx->Out.cbPreBuffered, cbBuf);
2479 if (cbToCopy > 0)
2480 {
2481 int rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pbBuf, cbBuf, pStreamEx->Out.cbPreBufAlloc, pcbWritten);
2482 AssertRCReturn(rc, rc);
2483 pbBuf += cbToCopy;
2484 cbBuf -= cbToCopy;
2485 }
2486 }
2487
2488 /*
2489 * Write the pre-buffered chunk.
2490 */
2491 int rc = VINF_SUCCESS;
2492 uint32_t const cbAlloc = pStreamEx->Out.cbPreBufAlloc;
2493 AssertReturn(cbAlloc > 0, VERR_INTERNAL_ERROR_2);
2494 uint32_t off = pStreamEx->Out.offPreBuf;
2495 AssertStmt(off < pStreamEx->Out.cbPreBufAlloc, off %= cbAlloc);
2496 uint32_t cbLeft = pStreamEx->Out.cbPreBuffered;
2497 while (cbLeft > 0)
2498 {
2499 uint32_t const cbToWrite = RT_MIN(cbAlloc - off, cbLeft);
2500 Assert(cbToWrite > 0);
2501
2502 uint32_t cbPreBufWritten = 0;
2503 rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStreamEx->pBackend, &pStreamEx->Out.pbPreBuf[off],
2504 cbToWrite, &cbPreBufWritten);
2505 AssertRCBreak(rc);
2506 if (!cbPreBufWritten)
2507 break;
2508 AssertStmt(cbPreBufWritten <= cbToWrite, cbPreBufWritten = cbToWrite);
2509 off = (off + cbPreBufWritten) % cbAlloc;
2510 cbLeft -= cbPreBufWritten;
2511 }
2512
2513 if (cbLeft == 0)
2514 {
2515 LogFunc(("@%#RX64: Wrote all %#x bytes of pre-buffered audio data. %s -> PLAY\n", pStreamEx->offInternal,
2516 pStreamEx->Out.cbPreBuffered, drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
2517 pStreamEx->Out.cbPreBuffered = 0;
2518 pStreamEx->Out.offPreBuf = 0;
2519 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PLAY;
2520
2521 if (cbBuf > 0)
2522 {
2523 uint32_t cbWritten2 = 0;
2524 rc = drvAudioStreamPlayLocked(pThis, pStreamEx, pbBuf, cbBuf, &cbWritten2);
2525 if (RT_SUCCESS(rc))
2526 *pcbWritten += cbWritten2;
2527 }
2528 else
2529 pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS();
2530 }
2531 else
2532 {
2533 if (cbLeft != pStreamEx->Out.cbPreBuffered)
2534 pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS();
2535
2536 LogRel2(("Audio: @%#RX64: Stream '%s' pre-buffering commit problem: wrote %#x out of %#x + %#x - rc=%Rrc *pcbWritten=%#x %s -> PREBUF_COMMITTING\n",
2537 pStreamEx->offInternal, pStreamEx->Core.szName, pStreamEx->Out.cbPreBuffered - cbLeft,
2538 pStreamEx->Out.cbPreBuffered, cbBuf, rc, *pcbWritten, drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
2539 AssertMsg(pStreamEx->Out.enmPlayState == DRVAUDIOPLAYSTATE_PREBUF_COMMITTING || RT_FAILURE(rc),
2540 ("Buggy host driver buffer reporting? cbLeft=%#x cbPreBuffered=%#x\n", cbLeft, pStreamEx->Out.cbPreBuffered));
2541
2542 pStreamEx->Out.cbPreBuffered = cbLeft;
2543 pStreamEx->Out.offPreBuf = off;
2544 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_COMMITTING;
2545 }
2546
2547 return *pcbWritten ? VINF_SUCCESS : rc;
2548}
2549
2550
2551/**
2552 * Does one iteration of an audio stream.
2553 *
2554 * This function gives the backend the chance of iterating / altering data and
2555 * does the actual mixing between the guest <-> host mixing buffers.
2556 *
2557 * @returns VBox status code.
2558 * @param pThis Pointer to driver instance.
2559 * @param pStreamEx Stream to iterate.
2560 */
2561static int drvAudioStreamIterateInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
2562{
2563 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
2564
2565 if (!pThis->pHostDrvAudio)
2566 return VINF_SUCCESS;
2567
2568#ifdef LOG_ENABLED
2569 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
2570#endif
2571 Log3Func(("[%s] fStatus=%s\n", pStreamEx->Core.szName, dbgAudioStreamStatusToStr(szStreamSts, pStreamEx->Core.fStatus)));
2572
2573 /* Not enabled or paused? Skip iteration. */
2574 if ( !(pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_ENABLED)
2575 || (pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_PAUSED))
2576 {
2577 return VINF_SUCCESS;
2578 }
2579
2580 /*
2581 * Pending disable is really what we're here for. This only happens to output streams.
2582 */
2583 int rc = VINF_SUCCESS;
2584 if (!(pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE))
2585 { /* likely until we get to the end of the stream at least. */ }
2586 else
2587 {
2588 AssertReturn(pStreamEx->Core.enmDir == PDMAUDIODIR_OUT, VINF_SUCCESS);
2589 /** @todo Add a timeout to these proceedings. A few times that of the reported
2590 * buffer size or something like that. */
2591
2592 /*
2593 * Check if we have any data we need to write to the backend, try
2594 * move it now.
2595 */
2596 /** @todo r=bird: It is possible the device has data buffered (e.g.
2597 * internal DMA buffer (HDA) or mixing buffer (HDA + AC'97). We're
2598 * not taking that into account here. I also suspect that neither is
2599 * the device/mixer code, and that if the guest is too quick disabling
2600 * the stream, it will just remain there till the next time something
2601 * is played. That means that this code and associated timer hack
2602 * should probably not be here at all. */
2603 uint32_t cFramesLive = 0;
2604 switch (pStreamEx->Out.enmPlayState)
2605 {
2606 case DRVAUDIOPLAYSTATE_PLAY: /* Nothing prebuffered. */
2607 case DRVAUDIOPLAYSTATE_PLAY_PREBUF: /* Not pre-buffering for current device. */
2608 case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE: /* Output device isn't ready, drop it. */ /** @todo check state? */
2609 case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING: /* No output device yet. */
2610 case DRVAUDIOPLAYSTATE_NOPLAY:
2611 case DRVAUDIOPLAYSTATE_INVALID:
2612 case DRVAUDIOPLAYSTATE_END:
2613 /* no default, want warnings. */
2614 break;
2615 case DRVAUDIOPLAYSTATE_PREBUF:
2616 case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
2617 if (pStreamEx->Out.cbPreBuffered > 0)
2618 {
2619 uint32_t cbIgnored = 0;
2620 drvAudioStreamPreBufComitting(pThis, pStreamEx, NULL, 0, &cbIgnored);
2621 cFramesLive = PDMAudioPropsBytesToFrames(&pStreamEx->Core.Props, pStreamEx->Out.cbPreBuffered);
2622 }
2623 break;
2624 }
2625 Log3Func(("[%s] cFramesLive=%RU32\n", pStreamEx->Core.szName, cFramesLive));
2626 if (cFramesLive == 0)
2627 {
2628 /*
2629 * Tell the backend to start draining the stream, that is,
2630 * play the remaining buffered data and stop.
2631 */
2632 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DRAIN);
2633 if (rc == VERR_NOT_SUPPORTED) /* Not all backends support draining yet. */
2634 rc = VINF_SUCCESS;
2635 if (RT_SUCCESS(rc))
2636 {
2637 /*
2638 * Before we disable the stream, check if the backend has
2639 * finished playing the buffered data.
2640 */
2641 uint32_t cbPending;
2642 if (!pThis->pHostDrvAudio->pfnStreamGetPending) /* Optional. */
2643 cbPending = 0;
2644 else
2645 {
2646 cbPending = pThis->pHostDrvAudio->pfnStreamGetPending(pThis->pHostDrvAudio, pStreamEx->pBackend);
2647 Log3Func(("[%s] cbPending=%RU32 (%#RX32)\n", pStreamEx->Core.szName, cbPending, cbPending));
2648 }
2649 if (cbPending == 0)
2650 {
2651 /*
2652 * Okay, disable it.
2653 */
2654 LogFunc(("[%s] Disabling pending stream\n", pStreamEx->Core.szName));
2655 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
2656 if (RT_SUCCESS(rc))
2657 {
2658 pStreamEx->Core.fStatus &= ~(PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PENDING_DISABLE);
2659 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->Core.fStatus);
2660 drvAudioStreamDropInternal(pStreamEx); /* Not a DROP command, just a stream reset. */
2661 }
2662 else
2663 LogFunc(("[%s] Backend vetoed against closing pending input stream, rc=%Rrc\n", pStreamEx->Core.szName, rc));
2664 }
2665 }
2666 }
2667
2668 }
2669
2670 /* Update timestamps. */
2671 pStreamEx->nsLastIterated = RTTimeNanoTS();
2672
2673 if (RT_FAILURE(rc))
2674 LogFunc(("[%s] Failed with %Rrc\n", pStreamEx->Core.szName, rc));
2675
2676 return rc;
2677}
2678
2679
2680/**
2681 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamIterate}
2682 */
2683static DECLCALLBACK(int) drvAudioStreamIterate(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
2684{
2685 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
2686 AssertPtr(pThis);
2687 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
2688 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
2689 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
2690 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
2691
2692 int rc = RTCritSectEnter(&pThis->CritSect);
2693 AssertRCReturn(rc, rc);
2694
2695 rc = drvAudioStreamIterateInternal(pThis, pStreamEx);
2696
2697 RTCritSectLeave(&pThis->CritSect);
2698
2699 if (RT_FAILURE(rc))
2700 LogFlowFuncLeaveRC(rc);
2701 return rc;
2702}
2703
2704
2705/**
2706 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetReadable}
2707 */
2708static DECLCALLBACK(uint32_t) drvAudioStreamGetReadable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
2709{
2710 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
2711 AssertPtr(pThis);
2712 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
2713 AssertPtrReturn(pStreamEx, 0);
2714 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, 0);
2715 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, 0);
2716 AssertMsg(pStreamEx->Core.enmDir == PDMAUDIODIR_IN, ("Can't read from a non-input stream\n"));
2717 int rc = RTCritSectEnter(&pThis->CritSect);
2718 AssertRCReturn(rc, 0);
2719
2720 /*
2721 * ...
2722 */
2723 uint32_t cbReadable = 0;
2724
2725 /* All input streams for this driver disabled? See @bugref{9882}. */
2726 const bool fDisabled = !pThis->In.fEnabled;
2727
2728 if ( pThis->pHostDrvAudio
2729 && ( PDMAudioStrmStatusCanRead(pStreamEx->Core.fStatus)
2730 || fDisabled)
2731 )
2732 {
2733 if (pStreamEx->fNoMixBufs)
2734 cbReadable = pThis->pHostDrvAudio
2735 ? pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend) : 0;
2736 else
2737 {
2738 const uint32_t cfReadable = AudioMixBufLive(&pStreamEx->Guest.MixBuf);
2739 cbReadable = AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadable);
2740 }
2741
2742 if (!cbReadable)
2743 {
2744 /*
2745 * If nothing is readable, check if the stream on the backend side is ready to be read from.
2746 * If it isn't, return the number of bytes readable since the last read from this stream.
2747 *
2748 * This is needed for backends (e.g. VRDE) which do not provide any input data in certain
2749 * situations, but the device emulation needs input data to keep the DMA transfers moving.
2750 * Reading the actual data from a stream then will return silence then.
2751 */
2752 uint32_t fStatus = drvAudioStreamGetBackendStatus(pThis, pStreamEx);
2753 if ( !PDMAudioStrmStatusBackendCanRead(fStatus)
2754 || fDisabled)
2755 {
2756 cbReadable = PDMAudioPropsNanoToBytes(&pStreamEx->Host.Cfg.Props,
2757 RTTimeNanoTS() - pStreamEx->nsLastReadWritten);
2758 if (!(pStreamEx->Core.fWarningsShown & PDMAUDIOSTREAM_WARN_FLAGS_DISABLED))
2759 {
2760 if (fDisabled)
2761 LogRel(("Audio: Input for driver '%s' has been disabled, returning silence\n", pThis->szName));
2762 else
2763 LogRel(("Audio: Warning: Input for stream '%s' of driver '%s' not ready (current input status is %#x), returning silence\n",
2764 pStreamEx->Core.szName, pThis->szName, fStatus));
2765
2766 pStreamEx->Core.fWarningsShown |= PDMAUDIOSTREAM_WARN_FLAGS_DISABLED;
2767 }
2768 }
2769 }
2770
2771 /* Make sure to align the readable size to the guest's frame size. */
2772 if (cbReadable)
2773 cbReadable = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Guest.Cfg.Props, cbReadable);
2774 }
2775
2776 RTCritSectLeave(&pThis->CritSect);
2777 Log3Func(("[%s] cbReadable=%RU32 (%RU64ms)\n",
2778 pStreamEx->Core.szName, cbReadable, PDMAudioPropsBytesToMilli(&pStreamEx->Host.Cfg.Props, cbReadable)));
2779 return cbReadable;
2780}
2781
2782
2783/**
2784 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetWritable}
2785 */
2786static DECLCALLBACK(uint32_t) drvAudioStreamGetWritable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
2787{
2788 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
2789 AssertPtr(pThis);
2790 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
2791 AssertPtrReturn(pStreamEx, 0);
2792 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, 0);
2793 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, 0);
2794 AssertMsgReturn(pStreamEx->Core.enmDir == PDMAUDIODIR_OUT, ("Can't write to a non-output stream\n"), 0);
2795 int rc = RTCritSectEnter(&pThis->CritSect);
2796 AssertRCReturn(rc, 0);
2797
2798 /*
2799 * ...
2800 *
2801 * Note: We don't propagate the backend stream's status to the outside -- it's the job of this
2802 * audio connector to make sense of it.
2803 */
2804 uint32_t cbWritable = 0;
2805 if ( PDMAudioStrmStatusCanWrite(pStreamEx->Core.fStatus)
2806 && pThis->pHostDrvAudio != NULL)
2807 {
2808 Assert(pThis->pHostDrvAudio);
2809 switch (pStreamEx->Out.enmPlayState)
2810 {
2811 /*
2812 * Whatever the backend can hold.
2813 */
2814 case DRVAUDIOPLAYSTATE_PLAY:
2815 case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
2816 cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
2817 break;
2818
2819 /*
2820 * Whatever we've got of available space in the pre-buffer.
2821 * Note! For the last round when we pass the pre-buffering threshold, we may
2822 * report fewer bytes than what a DMA timer period for the guest device
2823 * typically produces, however that should be transfered in the following
2824 * round that goes directly to the backend buffer.
2825 */
2826 case DRVAUDIOPLAYSTATE_PREBUF:
2827 cbWritable = pStreamEx->Out.cbPreBufAlloc - pStreamEx->Out.cbPreBuffered;
2828 if (!cbWritable)
2829 cbWritable = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Props, 2);
2830 break;
2831
2832 /*
2833 * These are slightly more problematic and can go wrong if the pre-buffer is
2834 * manually configured to be smaller than the output of a typeical DMA timer
2835 * period for the guest device. So, to overcompensate, we just report back
2836 * the backend buffer size (the pre-buffer is circular, so no overflow issue).
2837 */
2838 case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
2839 case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
2840 cbWritable = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Props,
2841 RT_MAX(pStreamEx->Host.Cfg.Backend.cFramesBufferSize,
2842 pStreamEx->Host.Cfg.Backend.cFramesPreBuffering));
2843 break;
2844
2845 case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
2846 {
2847 /* Buggy backend: We weren't able to copy all the pre-buffered data to it
2848 when reaching the threshold. Try escape this situation, or at least
2849 keep the extra buffering to a minimum. We must try write something
2850 as long as there is space for it, as we need the pfnStreamWrite call
2851 to move the data. */
2852 uint32_t const cbMin = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Props, 8);
2853 cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
2854 if (cbWritable >= pStreamEx->Out.cbPreBuffered + cbMin)
2855 cbWritable -= pStreamEx->Out.cbPreBuffered + cbMin / 2;
2856 else
2857 cbWritable = RT_MIN(cbMin, pStreamEx->Out.cbPreBufAlloc - pStreamEx->Out.cbPreBuffered);
2858 AssertLogRel(cbWritable);
2859 break;
2860 }
2861
2862 case DRVAUDIOPLAYSTATE_NOPLAY:
2863 break;
2864 case DRVAUDIOPLAYSTATE_INVALID:
2865 case DRVAUDIOPLAYSTATE_END:
2866 AssertFailed();
2867 break;
2868 }
2869
2870 /* Make sure to align the writable size to the host's frame size. */
2871 cbWritable = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Host.Cfg.Props, cbWritable);
2872 }
2873
2874 RTCritSectLeave(&pThis->CritSect);
2875 Log3Func(("[%s] cbWritable=%RU32 (%RU64ms)\n",
2876 pStreamEx->Core.szName, cbWritable, PDMAudioPropsBytesToMilli(&pStreamEx->Host.Cfg.Props, cbWritable)));
2877 return cbWritable;
2878}
2879
2880
2881/**
2882 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetStatus}
2883 */
2884static DECLCALLBACK(uint32_t) drvAudioStreamGetStatus(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
2885{
2886 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
2887 AssertPtr(pThis);
2888
2889 /** @todo r=bird: It is not documented that we ignore NULL streams... Why is
2890 * this necessary? */
2891 if (!pStream)
2892 return PDMAUDIOSTREAM_STS_NONE;
2893 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
2894 AssertPtrReturn(pStreamEx, PDMAUDIOSTREAM_STS_NONE);
2895 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, PDMAUDIOSTREAM_STS_NONE);
2896 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, PDMAUDIOSTREAM_STS_NONE);
2897
2898 int rc = RTCritSectEnter(&pThis->CritSect);
2899 AssertRCReturn(rc, PDMAUDIOSTREAM_STS_NONE);
2900
2901 uint32_t fStrmStatus = pStreamEx->Core.fStatus;
2902
2903 RTCritSectLeave(&pThis->CritSect);
2904#ifdef LOG_ENABLED
2905 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
2906#endif
2907 Log3Func(("[%s] %s\n", pStreamEx->Core.szName, dbgAudioStreamStatusToStr(szStreamSts, fStrmStatus)));
2908 return fStrmStatus;
2909}
2910
2911
2912/**
2913 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamSetVolume}
2914 */
2915static DECLCALLBACK(int) drvAudioStreamSetVolume(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, PPDMAUDIOVOLUME pVol)
2916{
2917 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2918 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
2919 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
2920 AssertPtrReturn(pVol, VERR_INVALID_POINTER);
2921 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
2922 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
2923 AssertReturn(!pStreamEx->fNoMixBufs, VWRN_INVALID_STATE);
2924
2925 LogFlowFunc(("[%s] volL=%RU32, volR=%RU32, fMute=%RTbool\n", pStreamEx->Core.szName, pVol->uLeft, pVol->uRight, pVol->fMuted));
2926
2927 AudioMixBufSetVolume(&pStreamEx->Guest.MixBuf, pVol);
2928 AudioMixBufSetVolume(&pStreamEx->Host.MixBuf, pVol);
2929
2930 return VINF_SUCCESS;
2931}
2932
2933
2934static void drvAudioStreamPlayProcessBackendStateChange(PDRVAUDIOSTREAM pStreamEx, uint32_t fNewState, uint32_t fOldState)
2935{
2936 DRVAUDIOPLAYSTATE const enmPlayState = pStreamEx->Out.enmPlayState;
2937
2938 /*
2939 * Did PDMAUDIOSTREAM_STS_INITIALIZED change?
2940 */
2941 if ((fOldState ^ fNewState) & PDMAUDIOSTREAM_STS_INITIALIZED)
2942 {
2943 if (fOldState & PDMAUDIOSTREAM_STS_INITIALIZED)
2944 {
2945 switch (enmPlayState)
2946 {
2947 case DRVAUDIOPLAYSTATE_PLAY:
2948 case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
2949 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY;
2950 /** @todo We could enter PREBUF here and hope for the device to re-appear in
2951 * initialized state... */
2952 break;
2953 case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
2954 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_SWITCHING;
2955 break;
2956 case DRVAUDIOPLAYSTATE_PREBUF:
2957 break;
2958 case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
2959 case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
2960 AssertFailedBreak();
2961 /* no default */
2962 case DRVAUDIOPLAYSTATE_NOPLAY:
2963 case DRVAUDIOPLAYSTATE_END:
2964 case DRVAUDIOPLAYSTATE_INVALID:
2965 break;
2966 }
2967 LogFunc(("PDMAUDIOSTREAM_STS_INITIALIZED was cleared: %s -> %s\n",
2968 drvAudioPlayStateName(enmPlayState), drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
2969 }
2970 else
2971 {
2972 switch (enmPlayState)
2973 {
2974 case DRVAUDIOPLAYSTATE_PREBUF:
2975 case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
2976 break;
2977 case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
2978 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_COMMITTING;
2979 break;
2980 case DRVAUDIOPLAYSTATE_NOPLAY:
2981 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF;
2982 break;
2983 case DRVAUDIOPLAYSTATE_PLAY:
2984 case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
2985 case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
2986 AssertFailedBreak();
2987 /* no default */
2988 case DRVAUDIOPLAYSTATE_END:
2989 case DRVAUDIOPLAYSTATE_INVALID:
2990 break;
2991 }
2992 LogFunc(("PDMAUDIOSTREAM_STS_INITIALIZED was set: %s -> %s\n",
2993 drvAudioPlayStateName(enmPlayState), drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
2994 }
2995 }
2996
2997 /*
2998 * Deal with PDMAUDIOSTREAM_STS_PREPARING_SWITCH being set.
2999 *
3000 * Note! We don't care if it's cleared as the backend will call
3001 * PDMIAUDIONOTIFYFROMHOST::pfnStreamNotifyDeviceChanged when that takes place.
3002 */
3003 if ( !(fOldState & PDMAUDIOSTREAM_STS_PREPARING_SWITCH)
3004 && (fNewState & PDMAUDIOSTREAM_STS_PREPARING_SWITCH))
3005 {
3006 if (pStreamEx->Out.cbPreBufThreshold > 0)
3007 {
3008 switch (enmPlayState)
3009 {
3010 case DRVAUDIOPLAYSTATE_PREBUF:
3011 case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
3012 case DRVAUDIOPLAYSTATE_NOPLAY:
3013 case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING: /* simpler */
3014 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_SWITCHING;
3015 break;
3016 case DRVAUDIOPLAYSTATE_PLAY:
3017 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PLAY_PREBUF;
3018 break;
3019 case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
3020 case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
3021 break;
3022 /* no default */
3023 case DRVAUDIOPLAYSTATE_END:
3024 case DRVAUDIOPLAYSTATE_INVALID:
3025 break;
3026 }
3027 LogFunc(("PDMAUDIOSTREAM_STS_INITIALIZED was set: %s -> %s\n",
3028 drvAudioPlayStateName(enmPlayState), drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
3029 }
3030 else
3031 LogFunc(("PDMAUDIOSTREAM_STS_PREPARING_SWITCH was set, but no pre-buffering configured.\n"));
3032 }
3033}
3034
3035
3036/**
3037 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamPlay}
3038 */
3039static DECLCALLBACK(int) drvAudioStreamPlay(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream,
3040 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
3041{
3042 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
3043 AssertPtr(pThis);
3044
3045 /*
3046 * Check input and sanity.
3047 */
3048 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
3049 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
3050 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
3051 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
3052 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
3053 uint32_t uTmp;
3054 if (!pcbWritten)
3055 pcbWritten = &uTmp;
3056 AssertPtrReturn(pcbWritten, VERR_INVALID_PARAMETER);
3057
3058 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3059 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3060 AssertMsgReturn(pStreamEx->Core.enmDir == PDMAUDIODIR_OUT,
3061 ("Stream '%s' is not an output stream and therefore cannot be written to (direction is '%s')\n",
3062 pStreamEx->Core.szName, PDMAudioDirGetName(pStreamEx->Core.enmDir)), VERR_ACCESS_DENIED);
3063 Assert(pStreamEx->fNoMixBufs);
3064
3065 AssertMsg(PDMAudioPropsIsSizeAligned(&pStreamEx->Guest.Cfg.Props, cbBuf),
3066 ("Stream '%s' got a non-frame-aligned write (%RU32 bytes)\n", pStreamEx->Core.szName, cbBuf));
3067
3068 int rc = RTCritSectEnter(&pThis->CritSect);
3069 AssertRCReturn(rc, rc);
3070
3071 /*
3072 * First check that we can write to the stream, and if not,
3073 * whether to just drop the input into the bit bucket.
3074 */
3075 if (PDMAudioStrmStatusIsReady(pStreamEx->Core.fStatus))
3076 {
3077 if ( pThis->Out.fEnabled /* (see @bugref{9882}) */
3078 && pThis->pHostDrvAudio != NULL)
3079 {
3080#ifdef LOG_ENABLED
3081 char szState[DRVAUDIO_STATUS_STR_MAX];
3082#endif
3083 /*
3084 * Get the backend state and check if we've changed to "initialized" or
3085 * to switch prep mode.
3086 *
3087 * We don't really need to watch ENABLE or PAUSE here as these should be
3088 * in sync between our state and the backend (there is a tiny tiny chance
3089 * that we are resumed before the backend driver, but AIO threads really
3090 * shouldn't be getting here that fast I hope).
3091 */
3092 /** @todo
3093 * The PENDING_DISABLE (== draining) is not reported by most backend and it's an
3094 * open question whether we should still allow writes even when the backend
3095 * is draining anyway. We currently don't. Maybe we just re-define it as
3096 * a non-backend status flag.
3097 */
3098 uint32_t const fBackendStatus = drvAudioStreamGetBackendStatus(pThis, pStreamEx);
3099 Assert( (fBackendStatus & (PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PAUSED))
3100 == (pStreamEx->Core.fStatus & (PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PAUSED)) );
3101
3102 if (!( (pStreamEx->fLastBackendStatus ^ fBackendStatus)
3103 & (PDMAUDIOSTREAM_STS_INITIALIZED | PDMAUDIOSTREAM_STS_PREPARING_SWITCH)))
3104 { /* no relevant change - likely */ }
3105 else
3106 {
3107 drvAudioStreamPlayProcessBackendStateChange(pStreamEx, fBackendStatus, pStreamEx->fLastBackendStatus);
3108 pStreamEx->fLastBackendStatus = fBackendStatus;
3109 }
3110
3111 /*
3112 * Do the transfering.
3113 */
3114 switch (pStreamEx->Out.enmPlayState)
3115 {
3116 case DRVAUDIOPLAYSTATE_PLAY:
3117 rc = drvAudioStreamPlayLocked(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten);
3118 break;
3119
3120 case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
3121 rc = drvAudioStreamPlayLocked(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten);
3122 drvAudioStreamPreBuffer(pStreamEx, (uint8_t const *)pvBuf, *pcbWritten, pStreamEx->Out.cbPreBufThreshold);
3123 break;
3124
3125 case DRVAUDIOPLAYSTATE_PREBUF:
3126 if (cbBuf + pStreamEx->Out.cbPreBuffered < pStreamEx->Out.cbPreBufThreshold)
3127 rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pvBuf, cbBuf, pStreamEx->Out.cbPreBufThreshold, pcbWritten);
3128 else if (fBackendStatus & PDMAUDIOSTREAM_STS_INITIALIZED)
3129 {
3130 Log3Func(("[%s] Pre-buffering completing: cbBuf=%#x cbPreBuffered=%#x => %#x vs cbPreBufThreshold=%#x\n",
3131 pStreamEx->Core.szName, cbBuf, pStreamEx->Out.cbPreBuffered,
3132 cbBuf + pStreamEx->Out.cbPreBuffered, pStreamEx->Out.cbPreBufThreshold));
3133 rc = drvAudioStreamPreBufComitting(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten);
3134 }
3135 else
3136 {
3137 Log3Func(("[%s] Pre-buffering completing but device not ready: cbBuf=%#x cbPreBuffered=%#x => %#x vs cbPreBufThreshold=%#x; PREBUF -> PREBUF_OVERDUE\n",
3138 pStreamEx->Core.szName, cbBuf, pStreamEx->Out.cbPreBuffered,
3139 cbBuf + pStreamEx->Out.cbPreBuffered, pStreamEx->Out.cbPreBufThreshold));
3140 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_OVERDUE;
3141 rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pvBuf, cbBuf, pStreamEx->Out.cbPreBufThreshold, pcbWritten);
3142 }
3143 break;
3144
3145 case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
3146 Assert(!(fBackendStatus & PDMAUDIOSTREAM_STS_INITIALIZED));
3147 RT_FALL_THRU();
3148 case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
3149 rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pvBuf, cbBuf, pStreamEx->Out.cbPreBufThreshold, pcbWritten);
3150 break;
3151
3152 case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
3153 rc = drvAudioStreamPreBufComitting(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten);
3154 break;
3155
3156 case DRVAUDIOPLAYSTATE_NOPLAY:
3157 *pcbWritten = cbBuf;
3158 pStreamEx->offInternal += cbBuf;
3159 Log3Func(("[%s] Discarding the data, backend state: %s\n", pStreamEx->Core.szName,
3160 dbgAudioStreamStatusToStr(szState, fBackendStatus) ));
3161 break;
3162
3163 default:
3164 *pcbWritten = cbBuf;
3165 AssertMsgFailedBreak(("%d; cbBuf=%#x\n", pStreamEx->Out.enmPlayState, cbBuf));
3166 }
3167
3168 if (!pThis->Out.Cfg.Dbg.fEnabled || RT_FAILURE(rc))
3169 { /* likely */ }
3170 else
3171 AudioHlpFileWrite(pStreamEx->Out.Dbg.pFilePlayNonInterleaved, pvBuf, *pcbWritten, 0 /* fFlags */);
3172 }
3173 else
3174 {
3175 *pcbWritten = cbBuf;
3176 pStreamEx->offInternal += cbBuf;
3177 Log3Func(("[%s] Backend stream %s, discarding the data\n", pStreamEx->Core.szName,
3178 !pThis->Out.fEnabled ? "disabled" : !pThis->pHostDrvAudio ? "not attached" : "not ready yet"));
3179 }
3180 }
3181 else
3182 rc = VERR_AUDIO_STREAM_NOT_READY;
3183
3184 RTCritSectLeave(&pThis->CritSect);
3185 return rc;
3186}
3187
3188
3189/**
3190 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRead}
3191 */
3192static DECLCALLBACK(int) drvAudioStreamRead(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream,
3193 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
3194{
3195 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
3196 AssertPtr(pThis);
3197 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
3198 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
3199 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
3200 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
3201 AssertPtrNullReturn(pcbRead, VERR_INVALID_POINTER);
3202 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3203 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3204 AssertMsg(pStreamEx->Core.enmDir == PDMAUDIODIR_IN,
3205 ("Stream '%s' is not an input stream and therefore cannot be read from (direction is 0x%x)\n",
3206 pStreamEx->Core.szName, pStreamEx->Core.enmDir));
3207
3208 int rc = RTCritSectEnter(&pThis->CritSect);
3209 AssertRCReturn(rc, rc);
3210
3211 /*
3212 * ...
3213 */
3214 uint32_t cbReadTotal = 0;
3215
3216 do
3217 {
3218 uint32_t cfReadTotal = 0;
3219
3220 const uint32_t cfBuf = AUDIOMIXBUF_B2F(&pStreamEx->Guest.MixBuf, cbBuf);
3221
3222 if (pThis->In.fEnabled) /* Input for this audio driver enabled? See #9822. */
3223 {
3224 if (!PDMAudioStrmStatusCanRead(pStream->fStatus))
3225 {
3226 rc = VERR_AUDIO_STREAM_NOT_READY;
3227 break;
3228 }
3229
3230 /*
3231 * Read from the parent buffer (that is, the guest buffer) which
3232 * should have the audio data in the format the guest needs.
3233 */
3234 uint32_t cfToRead = RT_MIN(cfBuf, AudioMixBufLive(&pStreamEx->Guest.MixBuf));
3235 while (cfToRead)
3236 {
3237 uint32_t cfRead;
3238 rc = AudioMixBufAcquireReadBlock(&pStreamEx->Guest.MixBuf,
3239 (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal),
3240 AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfToRead), &cfRead);
3241 if (RT_FAILURE(rc))
3242 break;
3243
3244#ifdef VBOX_WITH_STATISTICS
3245 const uint32_t cbRead = AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfRead);
3246 STAM_COUNTER_ADD(&pThis->Stats.TotalBytesRead, cbRead);
3247 STAM_COUNTER_ADD(&pStreamEx->In.Stats.TotalFramesRead, cfRead);
3248 STAM_COUNTER_INC(&pStreamEx->In.Stats.TotalTimesRead);
3249#endif
3250 Assert(cfToRead >= cfRead);
3251 cfToRead -= cfRead;
3252
3253 cfReadTotal += cfRead;
3254
3255 AudioMixBufReleaseReadBlock(&pStreamEx->Guest.MixBuf, cfRead);
3256 }
3257
3258 if (cfReadTotal)
3259 {
3260 if (pThis->In.Cfg.Dbg.fEnabled)
3261 AudioHlpFileWrite(pStreamEx->In.Dbg.pFileStreamRead,
3262 pvBuf, AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal), 0 /* fFlags */);
3263
3264 AudioMixBufFinish(&pStreamEx->Guest.MixBuf, cfReadTotal);
3265 }
3266 }
3267
3268 /* If we were not able to read as much data as requested, fill up the returned
3269 * data with silence.
3270 *
3271 * This is needed to keep the device emulation DMA transfers up and running at a constant rate. */
3272 if (cfReadTotal < cfBuf)
3273 {
3274 Log3Func(("[%s] Filling in silence (%RU64ms / %RU64ms)\n", pStream->szName,
3275 PDMAudioPropsFramesToMilli(&pStreamEx->Guest.Cfg.Props, cfBuf - cfReadTotal),
3276 PDMAudioPropsFramesToMilli(&pStreamEx->Guest.Cfg.Props, cfBuf)));
3277
3278 PDMAudioPropsClearBuffer(&pStreamEx->Guest.Cfg.Props,
3279 (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal),
3280 AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfBuf - cfReadTotal),
3281 cfBuf - cfReadTotal);
3282
3283 cfReadTotal = cfBuf;
3284 }
3285
3286 cbReadTotal = AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal);
3287
3288 pStreamEx->nsLastReadWritten = RTTimeNanoTS();
3289
3290 Log3Func(("[%s] fEnabled=%RTbool, cbReadTotal=%RU32, rc=%Rrc\n", pStream->szName, pThis->In.fEnabled, cbReadTotal, rc));
3291
3292 } while (0);
3293
3294 RTCritSectLeave(&pThis->CritSect);
3295
3296 if (RT_SUCCESS(rc) && pcbRead)
3297 *pcbRead = cbReadTotal;
3298 return rc;
3299}
3300
3301
3302/**
3303 * Captures non-interleaved input from a host stream.
3304 *
3305 * @returns VBox status code.
3306 * @param pThis Driver instance.
3307 * @param pStreamEx Stream to capture from.
3308 * @param pcfCaptured Number of (host) audio frames captured.
3309 */
3310static int drvAudioStreamCaptureNonInterleaved(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, uint32_t *pcfCaptured)
3311{
3312 Assert(pStreamEx->Core.enmDir == PDMAUDIODIR_IN);
3313 Assert(pStreamEx->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED);
3314
3315 /*
3316 * ...
3317 */
3318 AssertPtr(pThis->pHostDrvAudio->pfnStreamGetReadable);
3319 uint32_t cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend);
3320 if (!cbReadable)
3321 Log2Func(("[%s] No readable data available\n", pStreamEx->Core.szName));
3322
3323 uint32_t cbFree = AudioMixBufFreeBytes(&pStreamEx->Guest.MixBuf); /* Parent */
3324 if (!cbFree)
3325 Log2Func(("[%s] Buffer full\n", pStreamEx->Core.szName));
3326
3327 if (cbReadable > cbFree) /* More data readable than we can store at the moment? Limit. */
3328 cbReadable = cbFree;
3329
3330 /*
3331 * ...
3332 */
3333 int rc = VINF_SUCCESS;
3334 uint32_t cfCapturedTotal = 0;
3335 while (cbReadable)
3336 {
3337 uint8_t abChunk[_4K];
3338 uint32_t cbCaptured;
3339 rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStreamEx->pBackend,
3340 abChunk, RT_MIN(cbReadable, (uint32_t)sizeof(abChunk)), &cbCaptured);
3341 if (RT_FAILURE(rc))
3342 {
3343 int rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
3344 AssertRC(rc2);
3345 break;
3346 }
3347
3348 Assert(cbCaptured <= sizeof(abChunk));
3349 if (cbCaptured > sizeof(abChunk)) /* Paranoia. */
3350 cbCaptured = (uint32_t)sizeof(abChunk);
3351
3352 if (!cbCaptured) /* Nothing captured? Take a shortcut. */
3353 break;
3354
3355 /* We use the host side mixing buffer as an intermediate buffer to do some
3356 * (first) processing (if needed), so always write the incoming data at offset 0. */
3357 uint32_t cfHstWritten = 0;
3358 rc = AudioMixBufWriteAt(&pStreamEx->Host.MixBuf, 0 /* offFrames */, abChunk, cbCaptured, &cfHstWritten);
3359 if ( RT_FAILURE(rc)
3360 || !cfHstWritten)
3361 {
3362 AssertMsgFailed(("[%s] Write failed: cbCaptured=%RU32, cfHstWritten=%RU32, rc=%Rrc\n",
3363 pStreamEx->Core.szName, cbCaptured, cfHstWritten, rc));
3364 break;
3365 }
3366
3367 if (pThis->In.Cfg.Dbg.fEnabled)
3368 AudioHlpFileWrite(pStreamEx->In.Dbg.pFileCaptureNonInterleaved, abChunk, cbCaptured, 0 /* fFlags */);
3369
3370 uint32_t cfHstMixed = 0;
3371 if (cfHstWritten)
3372 {
3373 int rc2 = AudioMixBufMixToParentEx(&pStreamEx->Host.MixBuf, 0 /* cSrcOffset */, cfHstWritten /* cSrcFrames */,
3374 &cfHstMixed /* pcSrcMixed */);
3375 Log3Func(("[%s] cbCaptured=%RU32, cfWritten=%RU32, cfMixed=%RU32, rc=%Rrc\n",
3376 pStreamEx->Core.szName, cbCaptured, cfHstWritten, cfHstMixed, rc2));
3377 AssertRC(rc2);
3378 }
3379
3380 Assert(cbReadable >= cbCaptured);
3381 cbReadable -= cbCaptured;
3382 cfCapturedTotal += cfHstMixed;
3383 }
3384
3385 if (RT_SUCCESS(rc))
3386 {
3387 if (cfCapturedTotal)
3388 Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStreamEx->Core.szName, cfCapturedTotal, rc));
3389 }
3390 else
3391 LogFunc(("[%s] Capturing failed with rc=%Rrc\n", pStreamEx->Core.szName, rc));
3392
3393 if (pcfCaptured)
3394 *pcfCaptured = cfCapturedTotal;
3395
3396 return rc;
3397}
3398
3399
3400/**
3401 * Captures raw input from a host stream.
3402 *
3403 * Raw input means that the backend directly operates on PDMAUDIOFRAME structs without
3404 * no data layout processing done in between.
3405 *
3406 * Needed for e.g. the VRDP audio backend (in Main).
3407 *
3408 * @returns VBox status code.
3409 * @param pThis Driver instance.
3410 * @param pStreamEx Stream to capture from.
3411 * @param pcfCaptured Number of (host) audio frames captured.
3412 */
3413static int drvAudioStreamCaptureRaw(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, uint32_t *pcfCaptured)
3414{
3415 Assert(pStreamEx->Core.enmDir == PDMAUDIODIR_IN);
3416 Assert(pStreamEx->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW);
3417 AssertPtr(pThis->pHostDrvAudio->pfnStreamGetReadable);
3418
3419 /*
3420 * ...
3421 */
3422 /* Note: Raw means *audio frames*, not bytes! */
3423 uint32_t cfReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend);
3424 if (!cfReadable)
3425 Log2Func(("[%s] No readable data available\n", pStreamEx->Core.szName));
3426
3427 const uint32_t cfFree = AudioMixBufFree(&pStreamEx->Guest.MixBuf); /* Parent */
3428 if (!cfFree)
3429 Log2Func(("[%s] Buffer full\n", pStreamEx->Core.szName));
3430
3431 if (cfReadable > cfFree) /* More data readable than we can store at the moment? Limit. */
3432 cfReadable = cfFree;
3433
3434 /*
3435 * ...
3436 */
3437 int rc = VINF_SUCCESS;
3438 uint32_t cfCapturedTotal = 0;
3439 while (cfReadable)
3440 {
3441 PPDMAUDIOFRAME paFrames;
3442 uint32_t cfWritable;
3443 rc = AudioMixBufPeekMutable(&pStreamEx->Host.MixBuf, cfReadable, &paFrames, &cfWritable);
3444 if ( RT_FAILURE(rc)
3445 || !cfWritable)
3446 break;
3447
3448 uint32_t cfCaptured;
3449 rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStreamEx->pBackend,
3450 paFrames, cfWritable, &cfCaptured);
3451 if (RT_FAILURE(rc))
3452 {
3453 int rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
3454 AssertRC(rc2);
3455 break;
3456 }
3457
3458 Assert(cfCaptured <= cfWritable);
3459 if (cfCaptured > cfWritable) /* Paranoia. */
3460 cfCaptured = cfWritable;
3461
3462 Assert(cfReadable >= cfCaptured);
3463 cfReadable -= cfCaptured;
3464 cfCapturedTotal += cfCaptured;
3465 }
3466
3467 if (pcfCaptured)
3468 *pcfCaptured = cfCapturedTotal;
3469 Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStreamEx->Core.szName, cfCapturedTotal, rc));
3470 return rc;
3471}
3472
3473
3474/**
3475 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCapture}
3476 */
3477static DECLCALLBACK(int) drvAudioStreamCapture(PPDMIAUDIOCONNECTOR pInterface,
3478 PPDMAUDIOSTREAM pStream, uint32_t *pcFramesCaptured)
3479{
3480 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
3481 AssertPtr(pThis);
3482 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
3483 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
3484 AssertPtrNull(pcFramesCaptured);
3485 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3486 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3487 AssertMsg(pStreamEx->Core.enmDir == PDMAUDIODIR_IN,
3488 ("Stream '%s' is not an input stream and therefore cannot be captured (direction is 0x%x)\n",
3489 pStreamEx->Core.szName, pStreamEx->Core.enmDir));
3490 int rc = RTCritSectEnter(&pThis->CritSect);
3491 AssertRCReturn(rc, rc);
3492
3493#ifdef LOG_ENABLED
3494 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
3495#endif
3496 Log3Func(("[%s] fStatus=%s\n", pStreamEx->Core.szName, dbgAudioStreamStatusToStr(szStreamSts, pStreamEx->Core.fStatus)));
3497
3498 /*
3499 * ...
3500 */
3501 uint32_t cfCaptured = 0;
3502 do
3503 {
3504 if (!pThis->pHostDrvAudio)
3505 {
3506 rc = VERR_PDM_NO_ATTACHED_DRIVER;
3507 break;
3508 }
3509
3510 if ( !pThis->In.fEnabled
3511 || !PDMAudioStrmStatusCanRead(pStreamEx->Core.fStatus))
3512 {
3513 rc = VERR_AUDIO_STREAM_NOT_READY;
3514 break;
3515 }
3516
3517 /*
3518 * Do the actual capturing.
3519 */
3520 if (RT_LIKELY(pStreamEx->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED))
3521 rc = drvAudioStreamCaptureNonInterleaved(pThis, pStreamEx, &cfCaptured);
3522 else if (pStreamEx->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW)
3523 rc = drvAudioStreamCaptureRaw(pThis, pStreamEx, &cfCaptured);
3524 else
3525 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
3526
3527 if (RT_SUCCESS(rc))
3528 {
3529 Log3Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStreamEx->Core.szName, cfCaptured, rc));
3530
3531 STAM_COUNTER_ADD(&pThis->Stats.TotalFramesIn, cfCaptured);
3532 STAM_COUNTER_ADD(&pStreamEx->In.Stats.TotalFramesCaptured, cfCaptured);
3533 }
3534 else if (RT_UNLIKELY(RT_FAILURE(rc)))
3535 LogRel(("Audio: Capturing stream '%s' failed with %Rrc\n", pStreamEx->Core.szName, rc));
3536 } while (0);
3537
3538 RTCritSectLeave(&pThis->CritSect);
3539
3540 if (pcFramesCaptured)
3541 *pcFramesCaptured = cfCaptured;
3542
3543 if (RT_FAILURE(rc))
3544 LogFlowFuncLeaveRC(rc);
3545 return rc;
3546}
3547
3548
3549/*********************************************************************************************************************************
3550* PDMIAUDIONOTIFYFROMHOST interface implementation. *
3551*********************************************************************************************************************************/
3552
3553/**
3554 * Marks a stream for re-init.
3555 */
3556static void drvAudioStreamMarkNeedReInit(PDRVAUDIOSTREAM pStreamEx, const char *pszCaller)
3557{
3558 LogFlow((LOG_FN_FMT ": Flagging %s for re-init.\n", pszCaller, pStreamEx->Core.szName)); RT_NOREF(pszCaller);
3559 pStreamEx->Core.fStatus |= PDMAUDIOSTREAM_STS_NEED_REINIT;
3560 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->Core.fStatus);
3561 pStreamEx->cTriesReInit = 0;
3562 pStreamEx->nsLastReInit = 0;
3563}
3564
3565
3566/**
3567 * @interface_method_impl{PDMIAUDIONOTIFYFROMHOST,pfnNotifyDeviceChanged}
3568 */
3569static DECLCALLBACK(void) drvAudioNotifyFromHost_NotifyDeviceChanged(PPDMIAUDIONOTIFYFROMHOST pInterface,
3570 PDMAUDIODIR enmDir, void *pvUser)
3571{
3572 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioNotifyFromHost);
3573 AssertReturnVoid(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT);
3574 LogRel(("Audio: The %s device for %s is changing.\n", enmDir == PDMAUDIODIR_IN ? "input" : "output", pThis->szName));
3575
3576 RTCritSectEnter(&pThis->CritSect);
3577 PDRVAUDIOSTREAM pStreamEx;
3578 RTListForEach(&pThis->lstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
3579 {
3580 if (pStreamEx->Core.enmDir == enmDir)
3581 {
3582 if (pThis->pHostDrvAudio->pfnStreamNotifyDeviceChanged)
3583 {
3584 LogFlowFunc(("Calling pfnStreamNotifyDeviceChanged on %s, old backend status: %#x...\n", pStreamEx->Core.szName,
3585 drvAudioStreamGetBackendStatus(pThis, pStreamEx) ));
3586 pThis->pHostDrvAudio->pfnStreamNotifyDeviceChanged(pThis->pHostDrvAudio, pStreamEx->pBackend, pvUser);
3587 LogFlowFunc(("New stream backend status: %#x.\n", drvAudioStreamGetBackendStatus(pThis, pStreamEx) ));
3588 }
3589 else
3590 drvAudioStreamMarkNeedReInit(pStreamEx, __PRETTY_FUNCTION__);
3591 }
3592 }
3593 RTCritSectLeave(&pThis->CritSect);
3594}
3595
3596
3597/**
3598 * @interface_method_impl{PDMIAUDIONOTIFYFROMHOST,pfnStreamNotifyDeviceChanged}
3599 */
3600static DECLCALLBACK(void) drvAudioNotifyFromHost_StreamNotifyDeviceChanged(PPDMIAUDIONOTIFYFROMHOST pInterface,
3601 PPDMAUDIOBACKENDSTREAM pStream, bool fReInit)
3602{
3603 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioNotifyFromHost);
3604
3605 /*
3606 * Backend stream to validated DrvAudio stream:
3607 */
3608 AssertPtrReturnVoid(pStream);
3609 AssertReturnVoid(pStream->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC);
3610 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream->pStream;
3611 AssertPtrReturnVoid(pStreamEx);
3612 AssertReturnVoid(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC);
3613 AssertReturnVoid(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC);
3614
3615 /*
3616 * Grab the lock and do the requested work.
3617 */
3618 RTCritSectEnter(&pThis->CritSect);
3619 AssertReturnVoidStmt(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, RTCritSectLeave(&pThis->CritSect)); /* paranoia */
3620
3621 if (fReInit)
3622 drvAudioStreamMarkNeedReInit(pStreamEx, __PRETTY_FUNCTION__);
3623 else
3624 {
3625 /*
3626 * Adjust the stream state now that the device has (perhaps finally) been switched.
3627 *
3628 * For enabled output streams, we must update the play state. We could try commit
3629 * pre-buffered data here, but it's really not worth the hazzle and risk (don't
3630 * know which thread we're on, do we now).
3631 */
3632 AssertStmt(!(pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_NEED_REINIT),
3633 pStreamEx->Core.fStatus &= ~PDMAUDIOSTREAM_STS_NEED_REINIT);
3634
3635 if ( pStreamEx->Core.enmDir == PDMAUDIODIR_OUT
3636 && (pStreamEx->Core.fStatus & PDMAUDIOSTREAM_STS_ENABLED))
3637 {
3638 DRVAUDIOPLAYSTATE const enmPlayState = pStreamEx->Out.enmPlayState;
3639 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF;
3640 LogFunc(("%s: %s -> %s\n", pStreamEx->Core.szName, drvAudioPlayStateName(enmPlayState),
3641 drvAudioPlayStateName(pStreamEx->Out.enmPlayState) )); RT_NOREF(enmPlayState);
3642 }
3643 }
3644
3645 RTCritSectLeave(&pThis->CritSect);
3646}
3647
3648
3649/**
3650 * @interface_method_impl{PDMIAUDIONOTIFYFROMHOST,pfnNotifyDevicesChanged}
3651 */
3652static DECLCALLBACK(void) drvAudioNotifyFromHost_NotifyDevicesChanged(PPDMIAUDIONOTIFYFROMHOST pInterface)
3653{
3654 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioNotifyFromHost);
3655 LogRel(("Audio: Device configuration of driver '%s' has changed\n", pThis->szName));
3656
3657 /** @todo Remove legacy behaviour: */
3658 if (!pThis->pHostDrvAudio->pfnStreamNotifyDeviceChanged)
3659 {
3660/** @todo r=bird: Locking? */
3661 /* Mark all host streams to re-initialize. */
3662 PDRVAUDIOSTREAM pStreamEx;
3663 RTListForEach(&pThis->lstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
3664 {
3665 drvAudioStreamMarkNeedReInit(pStreamEx, __PRETTY_FUNCTION__);
3666 }
3667 }
3668
3669# ifdef VBOX_WITH_AUDIO_ENUM
3670 /* Re-enumerate all host devices as soon as possible. */
3671 pThis->fEnumerateDevices = true;
3672# endif
3673}
3674
3675
3676/*********************************************************************************************************************************
3677* PDMIBASE interface implementation. *
3678*********************************************************************************************************************************/
3679
3680/**
3681 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
3682 */
3683static DECLCALLBACK(void *) drvAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
3684{
3685 LogFlowFunc(("pInterface=%p, pszIID=%s\n", pInterface, pszIID));
3686
3687 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
3688 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3689
3690 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
3691 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIAUDIOCONNECTOR, &pThis->IAudioConnector);
3692 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIAUDIONOTIFYFROMHOST, &pThis->IAudioNotifyFromHost);
3693
3694 return NULL;
3695}
3696
3697
3698/*********************************************************************************************************************************
3699* PDMDRVREG interface implementation. *
3700*********************************************************************************************************************************/
3701
3702/**
3703 * Power Off notification.
3704 *
3705 * @param pDrvIns The driver instance data.
3706 */
3707static DECLCALLBACK(void) drvAudioPowerOff(PPDMDRVINS pDrvIns)
3708{
3709 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3710
3711 LogFlowFuncEnter();
3712
3713 /** @todo locking? */
3714 if (pThis->pHostDrvAudio) /* If not lower driver is configured, bail out. */
3715 {
3716 /*
3717 * Just destroy the host stream on the backend side.
3718 * The rest will either be destructed by the device emulation or
3719 * in drvAudioDestruct().
3720 */
3721 PDRVAUDIOSTREAM pStreamEx;
3722 RTListForEach(&pThis->lstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
3723 {
3724 drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
3725 drvAudioStreamDestroyInternalBackend(pThis, pStreamEx);
3726 }
3727
3728 pThis->pHostDrvAudio = NULL;
3729 }
3730
3731 LogFlowFuncLeave();
3732}
3733
3734
3735/**
3736 * Detach notification.
3737 *
3738 * @param pDrvIns The driver instance data.
3739 * @param fFlags Detach flags.
3740 */
3741static DECLCALLBACK(void) drvAudioDetach(PPDMDRVINS pDrvIns, uint32_t fFlags)
3742{
3743 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
3744 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3745 RT_NOREF(fFlags);
3746
3747 int rc = RTCritSectEnter(&pThis->CritSect);
3748 AssertRC(rc);
3749
3750 LogFunc(("%s (detached %p)\n", pThis->szName, pThis->pHostDrvAudio));
3751 pThis->pHostDrvAudio = NULL;
3752
3753 RTCritSectLeave(&pThis->CritSect);
3754}
3755
3756
3757/**
3758 * Initializes the host backend and queries its initial configuration.
3759 *
3760 * @returns VBox status code.
3761 * @param pThis Driver instance to be called.
3762 */
3763static int drvAudioHostInit(PDRVAUDIO pThis)
3764{
3765 LogFlowFuncEnter();
3766
3767 /*
3768 * Check the function pointers, make sure the ones we define as
3769 * mandatory are present.
3770 */
3771 PPDMIHOSTAUDIO pHostDrvAudio = pThis->pHostDrvAudio;
3772 AssertPtrReturn(pHostDrvAudio, VERR_INVALID_POINTER);
3773 AssertPtrReturn(pHostDrvAudio->pfnGetConfig, VERR_INVALID_POINTER);
3774 AssertPtrNullReturn(pHostDrvAudio->pfnGetDevices, VERR_INVALID_POINTER);
3775 AssertPtrNullReturn(pHostDrvAudio->pfnGetStatus, VERR_INVALID_POINTER);
3776 AssertPtrNullReturn(pHostDrvAudio->pfnStreamConfigHint, VERR_INVALID_POINTER);
3777 AssertPtrReturn(pHostDrvAudio->pfnStreamCreate, VERR_INVALID_POINTER);
3778 AssertPtrReturn(pHostDrvAudio->pfnStreamDestroy, VERR_INVALID_POINTER);
3779 AssertPtrNullReturn(pHostDrvAudio->pfnStreamNotifyDeviceChanged, VERR_INVALID_POINTER);
3780 AssertPtrReturn(pHostDrvAudio->pfnStreamControl, VERR_INVALID_POINTER);
3781 AssertPtrReturn(pHostDrvAudio->pfnStreamGetReadable, VERR_INVALID_POINTER);
3782 AssertPtrReturn(pHostDrvAudio->pfnStreamGetWritable, VERR_INVALID_POINTER);
3783 AssertPtrNullReturn(pHostDrvAudio->pfnStreamGetPending, VERR_INVALID_POINTER);
3784 AssertPtrReturn(pHostDrvAudio->pfnStreamGetStatus, VERR_INVALID_POINTER);
3785 AssertPtrReturn(pHostDrvAudio->pfnStreamPlay, VERR_INVALID_POINTER);
3786 AssertPtrReturn(pHostDrvAudio->pfnStreamCapture, VERR_INVALID_POINTER);
3787
3788 /*
3789 * Get the backend configuration.
3790 */
3791 int rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, &pThis->BackendCfg);
3792 if (RT_FAILURE(rc))
3793 {
3794 LogRel(("Audio: Getting configuration for driver '%s' failed with %Rrc\n", pThis->szName, rc));
3795 return VERR_AUDIO_BACKEND_INIT_FAILED;
3796 }
3797
3798 pThis->In.cStreamsFree = pThis->BackendCfg.cMaxStreamsIn;
3799 pThis->Out.cStreamsFree = pThis->BackendCfg.cMaxStreamsOut;
3800
3801 LogFlowFunc(("cStreamsFreeIn=%RU8, cStreamsFreeOut=%RU8\n", pThis->In.cStreamsFree, pThis->Out.cStreamsFree));
3802
3803 LogRel2(("Audio: Host driver '%s' supports %RU32 input streams and %RU32 output streams at once.\n",
3804 pThis->szName, pThis->In.cStreamsFree, pThis->Out.cStreamsFree));
3805
3806#ifdef VBOX_WITH_AUDIO_ENUM
3807 int rc2 = drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */);
3808 if (rc2 != VERR_NOT_SUPPORTED) /* Some backends don't implement device enumeration. */
3809 AssertRC(rc2);
3810
3811 RT_NOREF(rc2);
3812 /* Ignore rc. */
3813#endif
3814
3815 LogFlowFuncLeave();
3816 return VINF_SUCCESS;
3817}
3818
3819
3820/**
3821 * Does the actual backend driver attaching and queries the backend's interface.
3822 *
3823 * This is a worker for both drvAudioAttach and drvAudioConstruct.
3824 *
3825 * @returns VBox status code.
3826 * @param pDrvIns The driver instance.
3827 * @param pThis Pointer to driver instance.
3828 * @param fFlags Attach flags; see PDMDrvHlpAttach().
3829 */
3830static int drvAudioDoAttachInternal(PPDMDRVINS pDrvIns, PDRVAUDIO pThis, uint32_t fFlags)
3831{
3832 Assert(pThis->pHostDrvAudio == NULL); /* No nested attaching. */
3833
3834 /*
3835 * Attach driver below and query its connector interface.
3836 */
3837 PPDMIBASE pDownBase;
3838 int rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pDownBase);
3839 if (RT_SUCCESS(rc))
3840 {
3841 pThis->pHostDrvAudio = PDMIBASE_QUERY_INTERFACE(pDownBase, PDMIHOSTAUDIO);
3842 if (pThis->pHostDrvAudio)
3843 {
3844 /*
3845 * If everything went well, initialize the lower driver.
3846 */
3847 rc = drvAudioHostInit(pThis);
3848 if (RT_FAILURE(rc))
3849 pThis->pHostDrvAudio = NULL;
3850 }
3851 else
3852 {
3853 LogRel(("Audio: Failed to query interface for underlying host driver '%s'\n", pThis->szName));
3854 rc = PDMDRV_SET_ERROR(pThis->pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW,
3855 N_("The host audio driver does not implement PDMIHOSTAUDIO!"));
3856 }
3857 }
3858 /*
3859 * If the host driver below us failed to construct for some beningn reason,
3860 * we'll report it as a runtime error and replace it with the Null driver.
3861 *
3862 * Note! We do NOT change anything in PDM (or CFGM), so pDrvIns->pDownBase
3863 * will remain NULL in this case.
3864 */
3865 else if ( rc == VERR_AUDIO_BACKEND_INIT_FAILED
3866 || rc == VERR_MODULE_NOT_FOUND
3867 || rc == VERR_SYMBOL_NOT_FOUND
3868 || rc == VERR_FILE_NOT_FOUND
3869 || rc == VERR_PATH_NOT_FOUND)
3870 {
3871 /* Complain: */
3872 LogRel(("DrvAudio: Host audio driver '%s' init failed with %Rrc. Switching to the NULL driver for now.\n",
3873 pThis->szName, rc));
3874 PDMDrvHlpVMSetRuntimeError(pDrvIns, 0 /*fFlags*/, "HostAudioNotResponding",
3875 N_("Host audio backend (%s) initialization has failed. Selecting the NULL audio backend with the consequence that no sound is audible"),
3876 pThis->szName);
3877
3878 /* Replace with null audio: */
3879 pThis->pHostDrvAudio = (PPDMIHOSTAUDIO)&g_DrvHostAudioNull;
3880 RTStrCopy(pThis->szName, sizeof(pThis->szName), "NULL");
3881 rc = drvAudioHostInit(pThis);
3882 AssertRC(rc);
3883 }
3884
3885 LogFunc(("[%s] rc=%Rrc\n", pThis->szName, rc));
3886 return rc;
3887}
3888
3889
3890/**
3891 * Attach notification.
3892 *
3893 * @param pDrvIns The driver instance data.
3894 * @param fFlags Attach flags.
3895 */
3896static DECLCALLBACK(int) drvAudioAttach(PPDMDRVINS pDrvIns, uint32_t fFlags)
3897{
3898 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
3899 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3900 LogFunc(("%s\n", pThis->szName));
3901
3902 int rc = RTCritSectEnter(&pThis->CritSect);
3903 AssertRCReturn(rc, rc);
3904
3905 rc = drvAudioDoAttachInternal(pDrvIns, pThis, fFlags);
3906
3907 RTCritSectLeave(&pThis->CritSect);
3908 return rc;
3909}
3910
3911
3912/**
3913 * Handles state changes for all audio streams.
3914 *
3915 * @param pDrvIns Pointer to driver instance.
3916 * @param enmCmd Stream command to set for all streams.
3917 */
3918static void drvAudioStateHandler(PPDMDRVINS pDrvIns, PDMAUDIOSTREAMCMD enmCmd)
3919{
3920 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
3921 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3922 LogFlowFunc(("enmCmd=%s\n", PDMAudioStrmCmdGetName(enmCmd)));
3923
3924 int rc2 = RTCritSectEnter(&pThis->CritSect);
3925 AssertRCReturnVoid(rc2);
3926
3927 if (pThis->pHostDrvAudio)
3928 {
3929 PDRVAUDIOSTREAM pStreamEx;
3930 RTListForEach(&pThis->lstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
3931 {
3932 drvAudioStreamControlInternal(pThis, pStreamEx, enmCmd);
3933 }
3934 }
3935
3936 rc2 = RTCritSectLeave(&pThis->CritSect);
3937 AssertRC(rc2);
3938}
3939
3940
3941/**
3942 * Resume notification.
3943 *
3944 * @param pDrvIns The driver instance data.
3945 */
3946static DECLCALLBACK(void) drvAudioResume(PPDMDRVINS pDrvIns)
3947{
3948 drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_RESUME);
3949}
3950
3951
3952/**
3953 * Suspend notification.
3954 *
3955 * @param pDrvIns The driver instance data.
3956 */
3957static DECLCALLBACK(void) drvAudioSuspend(PPDMDRVINS pDrvIns)
3958{
3959 drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_PAUSE);
3960}
3961
3962
3963/**
3964 * Destructs an audio driver instance.
3965 *
3966 * @copydoc FNPDMDRVDESTRUCT
3967 */
3968static DECLCALLBACK(void) drvAudioDestruct(PPDMDRVINS pDrvIns)
3969{
3970 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
3971 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3972
3973 LogFlowFuncEnter();
3974
3975 if (RTCritSectIsInitialized(&pThis->CritSect))
3976 {
3977 int rc = RTCritSectEnter(&pThis->CritSect);
3978 AssertRC(rc);
3979 }
3980
3981 /*
3982 * Note: No calls here to the driver below us anymore,
3983 * as PDM already has destroyed it.
3984 * If you need to call something from the host driver,
3985 * do this in drvAudioPowerOff() instead.
3986 */
3987
3988 /* Thus, NULL the pointer to the host audio driver first,
3989 * so that routines like drvAudioStreamDestroyInternal() don't call the driver(s) below us anymore. */
3990 pThis->pHostDrvAudio = NULL;
3991
3992 PDRVAUDIOSTREAM pStreamEx, pStreamExNext;
3993 RTListForEachSafe(&pThis->lstStreams, pStreamEx, pStreamExNext, DRVAUDIOSTREAM, ListEntry)
3994 {
3995 int rc = drvAudioStreamUninitInternal(pThis, pStreamEx);
3996 if (RT_SUCCESS(rc))
3997 {
3998 RTListNodeRemove(&pStreamEx->ListEntry);
3999 drvAudioStreamFree(pStreamEx);
4000 }
4001 }
4002
4003 /* Sanity. */
4004 Assert(RTListIsEmpty(&pThis->lstStreams));
4005
4006 if (RTCritSectIsInitialized(&pThis->CritSect))
4007 {
4008 int rc = RTCritSectLeave(&pThis->CritSect);
4009 AssertRC(rc);
4010
4011 rc = RTCritSectDelete(&pThis->CritSect);
4012 AssertRC(rc);
4013 }
4014
4015 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Out.StatsReBuffering);
4016#ifdef VBOX_WITH_STATISTICS
4017 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalStreamsActive);
4018 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalStreamsCreated);
4019 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalFramesRead);
4020 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalFramesIn);
4021 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalBytesRead);
4022#endif
4023
4024 LogFlowFuncLeave();
4025}
4026
4027
4028/**
4029 * Constructs an audio driver instance.
4030 *
4031 * @copydoc FNPDMDRVCONSTRUCT
4032 */
4033static DECLCALLBACK(int) drvAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
4034{
4035 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
4036 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
4037 LogFlowFunc(("pDrvIns=%#p, pCfgHandle=%#p, fFlags=%x\n", pDrvIns, pCfg, fFlags));
4038
4039 /*
4040 * Basic instance init.
4041 */
4042 RTListInit(&pThis->lstStreams);
4043
4044 /*
4045 * Read configuration.
4046 */
4047 PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns,
4048 "DriverName|"
4049 "InputEnabled|"
4050 "OutputEnabled|"
4051 "DebugEnabled|"
4052 "DebugPathOut|"
4053 /* Deprecated: */
4054 "PCMSampleBitIn|"
4055 "PCMSampleBitOut|"
4056 "PCMSampleHzIn|"
4057 "PCMSampleHzOut|"
4058 "PCMSampleSignedIn|"
4059 "PCMSampleSignedOut|"
4060 "PCMSampleSwapEndianIn|"
4061 "PCMSampleSwapEndianOut|"
4062 "PCMSampleChannelsIn|"
4063 "PCMSampleChannelsOut|"
4064 "PeriodSizeMsIn|"
4065 "PeriodSizeMsOut|"
4066 "BufferSizeMsIn|"
4067 "BufferSizeMsOut|"
4068 "PreBufferSizeMsIn|"
4069 "PreBufferSizeMsOut",
4070 "In|Out");
4071
4072 int rc = CFGMR3QueryStringDef(pCfg, "DriverName", pThis->szName, sizeof(pThis->szName), "Untitled");
4073 AssertLogRelRCReturn(rc, rc);
4074
4075 /* Neither input nor output by default for security reasons. */
4076 rc = CFGMR3QueryBoolDef(pCfg, "InputEnabled", &pThis->In.fEnabled, false);
4077 AssertLogRelRCReturn(rc, rc);
4078
4079 rc = CFGMR3QueryBoolDef(pCfg, "OutputEnabled", &pThis->Out.fEnabled, false);
4080 AssertLogRelRCReturn(rc, rc);
4081
4082 /* Debug stuff (same for both directions). */
4083 rc = CFGMR3QueryBoolDef(pCfg, "DebugEnabled", &pThis->In.Cfg.Dbg.fEnabled, false);
4084 AssertLogRelRCReturn(rc, rc);
4085
4086 rc = CFGMR3QueryStringDef(pCfg, "DebugPathOut", pThis->In.Cfg.Dbg.szPathOut, sizeof(pThis->In.Cfg.Dbg.szPathOut), "");
4087 AssertLogRelRCReturn(rc, rc);
4088 if (pThis->In.Cfg.Dbg.szPathOut[0] == '\0')
4089 {
4090 rc = RTPathTemp(pThis->In.Cfg.Dbg.szPathOut, sizeof(pThis->In.Cfg.Dbg.szPathOut));
4091 if (RT_FAILURE(rc))
4092 {
4093 LogRel(("Audio: Warning! Failed to retrieve temporary directory: %Rrc - disabling debugging.\n", rc));
4094 pThis->In.Cfg.Dbg.szPathOut[0] = '\0';
4095 pThis->In.Cfg.Dbg.fEnabled = false;
4096 }
4097 }
4098 if (pThis->In.Cfg.Dbg.fEnabled)
4099 LogRel(("Audio: Debugging for driver '%s' enabled (audio data written to '%s')\n", pThis->szName, pThis->In.Cfg.Dbg.szPathOut));
4100
4101 /* Copy debug setup to the output direction. */
4102 pThis->Out.Cfg.Dbg = pThis->In.Cfg.Dbg;
4103
4104 LogRel2(("Audio: Verbose logging for driver '%s' is probably enabled too.\n", pThis->szName));
4105 /* This ^^^^^^^ is the *WRONG* place for that kind of statement. Verbose logging might only be enabled for DrvAudio. */
4106 LogRel2(("Audio: Initial status for driver '%s' is: input is %s, output is %s\n",
4107 pThis->szName, pThis->In.fEnabled ? "enabled" : "disabled", pThis->Out.fEnabled ? "enabled" : "disabled"));
4108
4109 /*
4110 * Per direction configuration. A bit complicated as
4111 * these wasn't originally in sub-nodes.
4112 */
4113 for (unsigned iDir = 0; iDir < 2; iDir++)
4114 {
4115 char szNm[48];
4116 PDRVAUDIOCFG pAudioCfg = iDir == 0 ? &pThis->In.Cfg : &pThis->Out.Cfg;
4117 const char *pszDir = iDir == 0 ? "In" : "Out";
4118
4119#define QUERY_VAL_RET(a_Width, a_szName, a_pValue, a_uDefault, a_ExprValid, a_szValidRange) \
4120 do { \
4121 rc = RT_CONCAT(CFGMR3QueryU,a_Width)(pDirNode, strcpy(szNm, a_szName), a_pValue); \
4122 if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) \
4123 { \
4124 rc = RT_CONCAT(CFGMR3QueryU,a_Width)(pCfg, strcat(szNm, pszDir), a_pValue); \
4125 if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) \
4126 { \
4127 *(a_pValue) = a_uDefault; \
4128 rc = VINF_SUCCESS; \
4129 } \
4130 else \
4131 LogRel(("DrvAudio: Warning! Please use '%s/" a_szName "' instead of '%s' for your VBoxInternal hacks\n", pszDir, szNm)); \
4132 } \
4133 AssertRCReturn(rc, PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, \
4134 N_("Configuration error: Failed to read %s config value '%s'"), pszDir, szNm)); \
4135 if (!(a_ExprValid)) \
4136 return PDMDrvHlpVMSetError(pDrvIns, VERR_OUT_OF_RANGE, RT_SRC_POS, \
4137 N_("Configuration error: Unsupported %s value %u. " a_szValidRange), szNm, *(a_pValue)); \
4138 } while (0)
4139
4140 PCFGMNODE const pDirNode = CFGMR3GetChild(pCfg, pszDir);
4141 rc = CFGMR3ValidateConfig(pDirNode, iDir == 0 ? "In/" : "Out/",
4142 "PCMSampleBit|"
4143 "PCMSampleHz|"
4144 "PCMSampleSigned|"
4145 "PCMSampleSwapEndian|"
4146 "PCMSampleChannels|"
4147 "PeriodSizeMs|"
4148 "BufferSizeMs|"
4149 "PreBufferSizeMs",
4150 "", pDrvIns->pReg->szName, pDrvIns->iInstance);
4151 AssertRCReturn(rc, rc);
4152
4153 uint8_t cSampleBits = 0;
4154 QUERY_VAL_RET(8, "PCMSampleBit", &cSampleBits, 0,
4155 cSampleBits == 0
4156 || cSampleBits == 8
4157 || cSampleBits == 16
4158 || cSampleBits == 32
4159 || cSampleBits == 64,
4160 "Must be either 0, 8, 16, 32 or 64");
4161 if (cSampleBits)
4162 PDMAudioPropsSetSampleSize(&pAudioCfg->Props, cSampleBits / 8);
4163
4164 uint8_t cChannels;
4165 QUERY_VAL_RET(8, "PCMSampleChannels", &cChannels, 0, cChannels <= 16, "Max 16");
4166 if (cChannels)
4167 PDMAudioPropsSetChannels(&pAudioCfg->Props, cChannels);
4168
4169 QUERY_VAL_RET(32, "PCMSampleHz", &pAudioCfg->Props.uHz, 0,
4170 pAudioCfg->Props.uHz == 0 || (pAudioCfg->Props.uHz >= 6000 && pAudioCfg->Props.uHz <= 768000),
4171 "In the range 6000 thru 768000, or 0");
4172
4173 QUERY_VAL_RET(8, "PCMSampleSigned", &pAudioCfg->uSigned, UINT8_MAX,
4174 pAudioCfg->uSigned == 0 || pAudioCfg->uSigned == 1 || pAudioCfg->uSigned == UINT8_MAX,
4175 "Must be either 0, 1, or 255");
4176
4177 QUERY_VAL_RET(8, "PCMSampleSwapEndian", &pAudioCfg->uSwapEndian, UINT8_MAX,
4178 pAudioCfg->uSwapEndian == 0 || pAudioCfg->uSwapEndian == 1 || pAudioCfg->uSwapEndian == UINT8_MAX,
4179 "Must be either 0, 1, or 255");
4180
4181 QUERY_VAL_RET(32, "PeriodSizeMs", &pAudioCfg->uPeriodSizeMs, 0,
4182 pAudioCfg->uPeriodSizeMs <= RT_MS_1SEC, "Max 1000");
4183
4184 QUERY_VAL_RET(32, "BufferSizeMs", &pAudioCfg->uBufferSizeMs, 0,
4185 pAudioCfg->uBufferSizeMs <= RT_MS_5SEC, "Max 5000");
4186
4187 QUERY_VAL_RET(32, "PreBufferSizeMs", &pAudioCfg->uPreBufSizeMs, UINT32_MAX,
4188 pAudioCfg->uPreBufSizeMs <= RT_MS_1SEC || pAudioCfg->uPreBufSizeMs == UINT32_MAX,
4189 "Max 1000, or 0xffffffff");
4190#undef QUERY_VAL_RET
4191 }
4192
4193 /*
4194 * Init the rest of the driver instance data.
4195 */
4196 rc = RTCritSectInit(&pThis->CritSect);
4197 AssertRCReturn(rc, rc);
4198
4199 pThis->fTerminate = false;
4200 pThis->pDrvIns = pDrvIns;
4201 /* IBase. */
4202 pDrvIns->IBase.pfnQueryInterface = drvAudioQueryInterface;
4203 /* IAudioConnector. */
4204 pThis->IAudioConnector.pfnEnable = drvAudioEnable;
4205 pThis->IAudioConnector.pfnIsEnabled = drvAudioIsEnabled;
4206 pThis->IAudioConnector.pfnGetConfig = drvAudioGetConfig;
4207 pThis->IAudioConnector.pfnGetStatus = drvAudioGetStatus;
4208 pThis->IAudioConnector.pfnStreamConfigHint = drvAudioStreamConfigHint;
4209 pThis->IAudioConnector.pfnStreamCreate = drvAudioStreamCreate;
4210 pThis->IAudioConnector.pfnStreamDestroy = drvAudioStreamDestroy;
4211 pThis->IAudioConnector.pfnStreamReInit = drvAudioStreamReInit;
4212 pThis->IAudioConnector.pfnStreamRetain = drvAudioStreamRetain;
4213 pThis->IAudioConnector.pfnStreamRelease = drvAudioStreamRelease;
4214 pThis->IAudioConnector.pfnStreamControl = drvAudioStreamControl;
4215 pThis->IAudioConnector.pfnStreamIterate = drvAudioStreamIterate;
4216 pThis->IAudioConnector.pfnStreamGetReadable = drvAudioStreamGetReadable;
4217 pThis->IAudioConnector.pfnStreamGetWritable = drvAudioStreamGetWritable;
4218 pThis->IAudioConnector.pfnStreamGetStatus = drvAudioStreamGetStatus;
4219 pThis->IAudioConnector.pfnStreamSetVolume = drvAudioStreamSetVolume;
4220 pThis->IAudioConnector.pfnStreamPlay = drvAudioStreamPlay;
4221 pThis->IAudioConnector.pfnStreamRead = drvAudioStreamRead;
4222 pThis->IAudioConnector.pfnStreamCapture = drvAudioStreamCapture;
4223 /* IAudioNotifyFromHost */
4224 pThis->IAudioNotifyFromHost.pfnNotifyDeviceChanged = drvAudioNotifyFromHost_NotifyDeviceChanged;
4225 pThis->IAudioNotifyFromHost.pfnStreamNotifyDeviceChanged = drvAudioNotifyFromHost_StreamNotifyDeviceChanged;
4226 pThis->IAudioNotifyFromHost.pfnNotifyDevicesChanged = drvAudioNotifyFromHost_NotifyDevicesChanged;
4227
4228 /*
4229 * Statistics.
4230 */
4231 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Out.StatsReBuffering, "OutputReBuffering",
4232 STAMUNIT_COUNT, "Number of times the output stream was re-buffered after starting.");
4233
4234#ifdef VBOX_WITH_STATISTICS
4235 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalStreamsActive, "TotalStreamsActive",
4236 STAMUNIT_COUNT, "Total active audio streams.");
4237 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalStreamsCreated, "TotalStreamsCreated",
4238 STAMUNIT_COUNT, "Total created audio streams.");
4239 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesRead, "TotalFramesRead",
4240 STAMUNIT_COUNT, "Total frames read by device emulation.");
4241 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesIn, "TotalFramesIn",
4242 STAMUNIT_COUNT, "Total frames captured by backend.");
4243 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalBytesRead, "TotalBytesRead",
4244 STAMUNIT_BYTES, "Total bytes read.");
4245#endif
4246
4247 /*
4248 * Create a timer to do finish closing output streams in PENDING_DISABLE state.
4249 *
4250 * The device won't call us again after it has disabled a the stream and this is
4251 * a real problem for truely cyclic buffer backends like DSound which will just
4252 * continue to loop and loop if not stopped.
4253 */
4254 RTStrPrintf(pThis->szTimerName, sizeof(pThis->szTimerName), "AudioIterate-%u", pDrvIns->iInstance);
4255 rc = PDMDrvHlpTMTimerCreate(pDrvIns, TMCLOCK_VIRTUAL, drvAudioEmergencyIterateTimer, NULL /*pvUser*/,
4256 0 /*fFlags*/, pThis->szTimerName, &pThis->hTimer);
4257 AssertRCReturn(rc, rc);
4258
4259 /*
4260 * Attach the host driver, if present.
4261 */
4262 rc = drvAudioDoAttachInternal(pDrvIns, pThis, fFlags);
4263 if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
4264 rc = VINF_SUCCESS;
4265
4266 LogFlowFuncLeaveRC(rc);
4267 return rc;
4268}
4269
4270/**
4271 * Audio driver registration record.
4272 */
4273const PDMDRVREG g_DrvAUDIO =
4274{
4275 /* u32Version */
4276 PDM_DRVREG_VERSION,
4277 /* szName */
4278 "AUDIO",
4279 /* szRCMod */
4280 "",
4281 /* szR0Mod */
4282 "",
4283 /* pszDescription */
4284 "Audio connector driver",
4285 /* fFlags */
4286 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
4287 /* fClass */
4288 PDM_DRVREG_CLASS_AUDIO,
4289 /* cMaxInstances */
4290 UINT32_MAX,
4291 /* cbInstance */
4292 sizeof(DRVAUDIO),
4293 /* pfnConstruct */
4294 drvAudioConstruct,
4295 /* pfnDestruct */
4296 drvAudioDestruct,
4297 /* pfnRelocate */
4298 NULL,
4299 /* pfnIOCtl */
4300 NULL,
4301 /* pfnPowerOn */
4302 NULL,
4303 /* pfnReset */
4304 NULL,
4305 /* pfnSuspend */
4306 drvAudioSuspend,
4307 /* pfnResume */
4308 drvAudioResume,
4309 /* pfnAttach */
4310 drvAudioAttach,
4311 /* pfnDetach */
4312 drvAudioDetach,
4313 /* pfnPowerOff */
4314 drvAudioPowerOff,
4315 /* pfnSoftReset */
4316 NULL,
4317 /* u32EndVersion */
4318 PDM_DRVREG_VERSION
4319};
4320
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