VirtualBox

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

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

DrvAudio: Shuffled a few more functions around. No functional change. bugref:9890

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