VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/DrvHostAudioWasApi.cpp@ 108397

Last change on this file since 108397 was 108361, checked in by vboxsync, 2 months ago

Audio/DrvHostAudioWasApi: Two bugfixes for bugref:10844

  • Check for the device status when looking for a cache hit and invalidate the entry if the device is not in active state (anymore)
  • If the default device has been changed, also take the device role into account.

See comments for details.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 134.7 KB
Line 
1/* $Id: DrvHostAudioWasApi.cpp 108361 2025-02-24 18:09:03Z vboxsync $ */
2/** @file
3 * Host audio driver - Windows Audio Session API.
4 */
5
6/*
7 * Copyright (C) 2021-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
33/*#define INITGUID - defined in VBoxhostAudioDSound.cpp already */
34#include <VBox/log.h>
35#include <iprt/win/windows.h>
36#include <Mmdeviceapi.h>
37#include <iprt/win/audioclient.h>
38#include <functiondiscoverykeys_devpkey.h>
39#include <AudioSessionTypes.h>
40#ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
41# define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY UINT32_C(0x08000000)
42#endif
43#ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
44# define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM UINT32_C(0x80000000)
45#endif
46
47#include <VBox/vmm/pdmaudioinline.h>
48#include <VBox/vmm/pdmaudiohostenuminline.h>
49
50#include <iprt/rand.h>
51#include <iprt/semaphore.h>
52#include <iprt/utf16.h>
53#include <iprt/uuid.h>
54
55#include <new> /* std::bad_alloc */
56
57#include "VBoxDD.h"
58
59
60/*********************************************************************************************************************************
61* Defined Constants And Macros *
62*********************************************************************************************************************************/
63/** Max GetCurrentPadding value we accept (to make sure it's safe to convert to bytes). */
64#define VBOX_WASAPI_MAX_PADDING UINT32_C(0x007fffff)
65
66/** Maximum number of cached device configs in each direction.
67 * The number 4 was picked at random. */
68#define VBOX_WASAPI_MAX_TOTAL_CONFIG_ENTRIES 4
69
70#if 0
71/** @name WM_DRVHOSTAUDIOWAS_XXX - Worker thread messages.
72 * @{ */
73#define WM_DRVHOSTAUDIOWAS_PURGE_CACHE (WM_APP + 3)
74/** @} */
75#endif
76
77
78/** @name DRVHOSTAUDIOWAS_DO_XXX - Worker thread operations.
79 * @{ */
80#define DRVHOSTAUDIOWAS_DO_PURGE_CACHE ((uintptr_t)0x49f37300 + 1)
81#define DRVHOSTAUDIOWAS_DO_PRUNE_CACHE ((uintptr_t)0x49f37300 + 2)
82#define DRVHOSTAUDIOWAS_DO_STREAM_DEV_SWITCH ((uintptr_t)0x49f37300 + 3)
83/** @} */
84
85
86/*********************************************************************************************************************************
87* Structures and Typedefs *
88*********************************************************************************************************************************/
89class DrvHostAudioWasMmNotifyClient;
90
91/** Pointer to the cache entry for a host audio device (+dir). */
92typedef struct DRVHOSTAUDIOWASCACHEDEV *PDRVHOSTAUDIOWASCACHEDEV;
93
94/**
95 * Cached pre-initialized audio client for a device.
96 *
97 * The activation and initialization of an IAudioClient has been observed to be
98 * very very slow (> 100 ms) and not suitable to be done on an EMT. So, we'll
99 * pre-initialize the device clients at construction time and when the default
100 * device changes to try avoid this problem.
101 *
102 * A client is returned to the cache after we're done with it, provided it still
103 * works fine.
104 */
105typedef struct DRVHOSTAUDIOWASCACHEDEVCFG
106{
107 /** Entry in DRVHOSTAUDIOWASCACHEDEV::ConfigList. */
108 RTLISTNODE ListEntry;
109 /** The device. */
110 PDRVHOSTAUDIOWASCACHEDEV pDevEntry;
111 /** The cached audio client. */
112 IAudioClient *pIAudioClient;
113 /** Output streams: The render client interface. */
114 IAudioRenderClient *pIAudioRenderClient;
115 /** Input streams: The capture client interface. */
116 IAudioCaptureClient *pIAudioCaptureClient;
117 /** The configuration. */
118 PDMAUDIOPCMPROPS Props;
119 /** The buffer size in frames. */
120 uint32_t cFramesBufferSize;
121 /** The device/whatever period in frames. */
122 uint32_t cFramesPeriod;
123 /** The setup status code.
124 * This is set to VERR_AUDIO_STREAM_INIT_IN_PROGRESS while the asynchronous
125 * initialization is still running. */
126 int volatile rcSetup;
127 /** Creation timestamp (just for reference). */
128 uint64_t nsCreated;
129 /** Init complete timestamp (just for reference). */
130 uint64_t nsInited;
131 /** When it was last used. */
132 uint64_t nsLastUsed;
133 /** The stringified properties. */
134 char szProps[32];
135} DRVHOSTAUDIOWASCACHEDEVCFG;
136/** Pointer to a pre-initialized audio client. */
137typedef DRVHOSTAUDIOWASCACHEDEVCFG *PDRVHOSTAUDIOWASCACHEDEVCFG;
138
139/**
140 * Per audio device (+ direction) cache entry.
141 */
142typedef struct DRVHOSTAUDIOWASCACHEDEV
143{
144 /** Entry in DRVHOSTAUDIOWAS::CacheHead. */
145 RTLISTNODE ListEntry;
146 /** The MM device associated with the stream. */
147 IMMDevice *pIDevice;
148 /** The direction of the device. */
149 PDMAUDIODIR enmDir;
150#if 0 /* According to https://social.msdn.microsoft.com/Forums/en-US/1d974d90-6636-4121-bba3-a8861d9ab92a,
151 these were always support just missing from the SDK. */
152 /** Support for AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM: -1=unknown, 0=no, 1=yes. */
153 int8_t fSupportsAutoConvertPcm;
154 /** Support for AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY: -1=unknown, 0=no, 1=yes. */
155 int8_t fSupportsSrcDefaultQuality;
156#endif
157 /** List of cached configurations (DRVHOSTAUDIOWASCACHEDEVCFG). */
158 RTLISTANCHOR ConfigList;
159 /** The device ID length in RTUTF16 units. */
160 size_t cwcDevId;
161 /** The device ID. */
162 RTUTF16 wszDevId[RT_FLEXIBLE_ARRAY];
163} DRVHOSTAUDIOWASCACHEDEV;
164
165
166/**
167 * Data for a WASABI stream.
168 */
169typedef struct DRVHOSTAUDIOWASSTREAM
170{
171 /** Common part. */
172 PDMAUDIOBACKENDSTREAM Core;
173
174 /** Entry in DRVHOSTAUDIOWAS::StreamHead. */
175 RTLISTNODE ListEntry;
176 /** The stream's acquired configuration. */
177 PDMAUDIOSTREAMCFG Cfg;
178 /** Cache entry to be relased when destroying the stream. */
179 PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg;
180
181 /** Set if the stream is enabled. */
182 bool fEnabled;
183 /** Set if the stream is started (playing/capturing). */
184 bool fStarted;
185 /** Set if the stream is draining (output only). */
186 bool fDraining;
187 /** Set if we should restart the stream on resume (saved pause state). */
188 bool fRestartOnResume;
189 /** Set if we're switching to a new output/input device. */
190 bool fSwitchingDevice;
191
192 /** The RTTimeMilliTS() deadline for the draining of this stream (output). */
193 uint64_t msDrainDeadline;
194 /** Internal stream offset (bytes). */
195 uint64_t offInternal;
196 /** The RTTimeMilliTS() at the end of the last transfer. */
197 uint64_t msLastTransfer;
198
199 /** Input: Current capture buffer (advanced as we read). */
200 uint8_t *pbCapture;
201 /** Input: The number of bytes left in the current capture buffer. */
202 uint32_t cbCapture;
203 /** Input: The full size of what pbCapture is part of (for ReleaseBuffer). */
204 uint32_t cFramesCaptureToRelease;
205
206 /** Critical section protecting: . */
207 RTCRITSECT CritSect;
208 /** Buffer that drvHostWasStreamStatusString uses. */
209 char szStatus[128];
210} DRVHOSTAUDIOWASSTREAM;
211/** Pointer to a WASABI stream. */
212typedef DRVHOSTAUDIOWASSTREAM *PDRVHOSTAUDIOWASSTREAM;
213
214
215/**
216 * WASAPI-specific device entry.
217 */
218typedef struct DRVHOSTAUDIOWASDEV
219{
220 /** The core structure. */
221 PDMAUDIOHOSTDEV Core;
222 /** The device ID (flexible length). */
223 RTUTF16 wszDevId[RT_FLEXIBLE_ARRAY];
224} DRVHOSTAUDIOWASDEV;
225/** Pointer to a DirectSound device entry. */
226typedef DRVHOSTAUDIOWASDEV *PDRVHOSTAUDIOWASDEV;
227
228
229/**
230 * Data for a WASAPI host audio instance.
231 */
232typedef struct DRVHOSTAUDIOWAS
233{
234 /** The audio host audio interface we export. */
235 PDMIHOSTAUDIO IHostAudio;
236 /** Pointer to the PDM driver instance. */
237 PPDMDRVINS pDrvIns;
238 /** Audio device enumerator instance that we use for getting the default
239 * devices (or specific ones if overriden by config). Also used for
240 * implementing enumeration. */
241 IMMDeviceEnumerator *pIEnumerator;
242 /** The upwards interface. */
243 PPDMIHOSTAUDIOPORT pIHostAudioPort;
244 /** The output device ID, NULL for default.
245 * Protected by DrvHostAudioWasMmNotifyClient::m_CritSect. */
246 PRTUTF16 pwszOutputDevId;
247 /** The input device ID, NULL for default.
248 * Protected by DrvHostAudioWasMmNotifyClient::m_CritSect. */
249 PRTUTF16 pwszInputDevId;
250
251 /** Pointer to the MM notification client instance. */
252 DrvHostAudioWasMmNotifyClient *pNotifyClient;
253 /** The input device to use. This can be NULL if there wasn't a suitable one
254 * around when we last looked or if it got removed/disabled/whatever.
255 * All access must be done inside the pNotifyClient critsect. */
256 IMMDevice *pIDeviceInput;
257 /** The output device to use. This can be NULL if there wasn't a suitable one
258 * around when we last looked or if it got removed/disabled/whatever.
259 * All access must be done inside the pNotifyClient critsect. */
260 IMMDevice *pIDeviceOutput;
261
262 /** List of streams (DRVHOSTAUDIOWASSTREAM).
263 * Requires CritSect ownership. */
264 RTLISTANCHOR StreamHead;
265 /** Serializing access to StreamHead. */
266 RTCRITSECTRW CritSectStreamList;
267
268 /** Whether the device cache (\a CacheHead and friends) is being used or not.
269 * Enabled by default, can be disabled via CFGM. */
270 bool fCacheEnabled;
271 /** List of cached devices (DRVHOSTAUDIOWASCACHEDEV).
272 * Protected by CritSectCache */
273 RTLISTANCHOR CacheHead;
274 /** Serializing access to CacheHead. */
275 RTCRITSECT CritSectCache;
276 /** Semaphore for signalling that cache purge is done and that the destructor
277 * can do cleanups. */
278 RTSEMEVENTMULTI hEvtCachePurge;
279 /** Total number of device config entire for capturing.
280 * This includes in-use ones. */
281 uint32_t volatile cCacheEntriesIn;
282 /** Total number of device config entire for playback.
283 * This includes in-use ones. */
284 uint32_t volatile cCacheEntriesOut;
285
286#if 0
287 /** The worker thread. */
288 RTTHREAD hWorkerThread;
289 /** The TID of the worker thread (for posting messages to it). */
290 DWORD idWorkerThread;
291 /** The fixed wParam value for the worker thread. */
292 WPARAM uWorkerThreadFixedParam;
293#endif
294} DRVHOSTAUDIOWAS;
295/** Pointer to the data for a WASAPI host audio driver instance. */
296typedef DRVHOSTAUDIOWAS *PDRVHOSTAUDIOWAS;
297
298
299
300
301/**
302 * Gets the stream status.
303 *
304 * @returns Pointer to stream status string.
305 * @param pStreamWas The stream to get the status for.
306 */
307static const char *drvHostWasStreamStatusString(PDRVHOSTAUDIOWASSTREAM pStreamWas)
308{
309 static RTSTRTUPLE const s_aEnable[2] =
310 {
311 { RT_STR_TUPLE("DISABLED") },
312 { RT_STR_TUPLE("ENABLED ") },
313 };
314 PCRTSTRTUPLE pTuple = &s_aEnable[pStreamWas->fEnabled];
315 memcpy(pStreamWas->szStatus, pTuple->psz, pTuple->cch);
316 size_t off = pTuple->cch;
317
318 static RTSTRTUPLE const s_aStarted[2] =
319 {
320 { RT_STR_TUPLE(" STOPPED") },
321 { RT_STR_TUPLE(" STARTED") },
322 };
323 pTuple = &s_aStarted[pStreamWas->fStarted];
324 memcpy(&pStreamWas->szStatus[off], pTuple->psz, pTuple->cch);
325 off += pTuple->cch;
326
327 static RTSTRTUPLE const s_aDraining[2] =
328 {
329 { RT_STR_TUPLE("") },
330 { RT_STR_TUPLE(" DRAINING") },
331 };
332 pTuple = &s_aDraining[pStreamWas->fDraining];
333 memcpy(&pStreamWas->szStatus[off], pTuple->psz, pTuple->cch);
334 off += pTuple->cch;
335
336 Assert(off < sizeof(pStreamWas->szStatus));
337 pStreamWas->szStatus[off] = '\0';
338 return pStreamWas->szStatus;
339}
340
341
342/**
343 * Returns the MMDeviceState as a string.
344 *
345 * @returns Pointer to MMDeviceState string.
346 * @param dwState MMDeviceState to return as a string.
347 */
348DECLINLINE(const char *) drvHostAudioWasMMDeviceStateToString(DWORD dwState)
349{
350 switch (dwState)
351 {
352 case DEVICE_STATE_ACTIVE: return "ACTIVE";
353 case DEVICE_STATE_DISABLED: return "DISABLED";
354 case DEVICE_STATE_NOTPRESENT: return "NOTPRESENT";
355 case DEVICE_STATE_UNPLUGGED: return "UNPLUGGED";
356 default: break;
357 }
358
359 AssertFailed();
360 return "???";
361}
362
363
364/*********************************************************************************************************************************
365* IMMNotificationClient implementation
366*********************************************************************************************************************************/
367/**
368 * Multimedia notification client.
369 *
370 * We want to know when the default device changes so we can switch running
371 * streams to use the new one and so we can pre-activate it in preparation
372 * for new streams.
373 */
374class DrvHostAudioWasMmNotifyClient : public IMMNotificationClient
375{
376private:
377 /** Reference counter. */
378 uint32_t volatile m_cRefs;
379 /** The WASAPI host audio driver instance data.
380 * @note This can be NULL. Only access after entering critical section. */
381 PDRVHOSTAUDIOWAS m_pDrvWas;
382 /** Critical section serializing access to m_pDrvWas. */
383 RTCRITSECT m_CritSect;
384
385public:
386 /**
387 * @throws int on critical section init failure.
388 */
389 DrvHostAudioWasMmNotifyClient(PDRVHOSTAUDIOWAS a_pDrvWas)
390 : m_cRefs(1)
391 , m_pDrvWas(a_pDrvWas)
392 {
393 RT_ZERO(m_CritSect);
394 }
395
396 virtual ~DrvHostAudioWasMmNotifyClient() RT_NOEXCEPT
397 {
398 if (RTCritSectIsInitialized(&m_CritSect))
399 RTCritSectDelete(&m_CritSect);
400 }
401
402 /**
403 * Initializes the critical section.
404 * @note Must be buildable w/o exceptions enabled, so cannot do this from the
405 * constructor. */
406 int init(void) RT_NOEXCEPT
407 {
408 return RTCritSectInit(&m_CritSect);
409 }
410
411 /**
412 * Called by drvHostAudioWasDestruct to set m_pDrvWas to NULL.
413 */
414 void notifyDriverDestroyed() RT_NOEXCEPT
415 {
416 RTCritSectEnter(&m_CritSect);
417 m_pDrvWas = NULL;
418 RTCritSectLeave(&m_CritSect);
419 }
420
421 /**
422 * Enters the notification critsect for getting at the IMMDevice members in
423 * PDMHOSTAUDIOWAS.
424 */
425 void lockEnter() RT_NOEXCEPT
426 {
427 RTCritSectEnter(&m_CritSect);
428 }
429
430 /**
431 * Leaves the notification critsect.
432 */
433 void lockLeave() RT_NOEXCEPT
434 {
435 RTCritSectLeave(&m_CritSect);
436 }
437
438 /** @name IUnknown interface
439 * @{ */
440 IFACEMETHODIMP_(ULONG) AddRef()
441 {
442 uint32_t cRefs = ASMAtomicIncU32(&m_cRefs);
443 AssertMsg(cRefs < 64, ("%#x\n", cRefs));
444 Log6Func(("returns %u\n", cRefs));
445 return cRefs;
446 }
447
448 IFACEMETHODIMP_(ULONG) Release()
449 {
450 uint32_t cRefs = ASMAtomicDecU32(&m_cRefs);
451 AssertMsg(cRefs < 64, ("%#x\n", cRefs));
452 if (cRefs == 0)
453 delete this;
454 Log6Func(("returns %u\n", cRefs));
455 return cRefs;
456 }
457
458 IFACEMETHODIMP QueryInterface(const IID &rIID, void **ppvInterface)
459 {
460 if (IsEqualIID(rIID, IID_IUnknown))
461 *ppvInterface = static_cast<IUnknown *>(this);
462 else if (IsEqualIID(rIID, __uuidof(IMMNotificationClient)))
463 *ppvInterface = static_cast<IMMNotificationClient *>(this);
464 else
465 {
466 LogFunc(("Unknown rIID={%RTuuid}\n", &rIID));
467 *ppvInterface = NULL;
468 return E_NOINTERFACE;
469 }
470 Log6Func(("returns S_OK + %p\n", *ppvInterface));
471 return S_OK;
472 }
473 /** @} */
474
475 /** @name IMMNotificationClient interface
476 * @{ */
477 IFACEMETHODIMP OnDeviceStateChanged(LPCWSTR pwszDeviceId, DWORD dwNewState)
478 {
479 LogRelMax(64, ("WasAPI: Device state for '%ls' changed to %s (%#x)\n",
480 pwszDeviceId, drvHostAudioWasMMDeviceStateToString(dwNewState), dwNewState));
481 /*
482 * Just trigger device re-enumeration.
483 */
484 notifyDeviceChanges();
485
486 /** @todo do we need to check for our devices here too? Not when using a
487 * default device. But when using a specific device, we could perhaps
488 * re-init the stream when dwNewState indicates precense. We might
489 * also take action when a devices ceases to be operating, but again
490 * only for non-default devices, probably... */
491
492 return S_OK;
493 }
494
495 IFACEMETHODIMP OnDeviceAdded(LPCWSTR pwszDeviceId)
496 {
497 LogRelMax(64, ("WasAPI: Device '%ls' added\n", pwszDeviceId));
498
499 /*
500 * Is this a device we're interested in? Grab the enumerator if it is.
501 */
502 bool fOutput = false;
503 IMMDeviceEnumerator *pIEnumerator = NULL;
504 RTCritSectEnter(&m_CritSect);
505 if ( m_pDrvWas != NULL
506 && ( (fOutput = RTUtf16ICmp(m_pDrvWas->pwszOutputDevId, pwszDeviceId) == 0)
507 || RTUtf16ICmp(m_pDrvWas->pwszInputDevId, pwszDeviceId) == 0))
508 {
509 pIEnumerator = m_pDrvWas->pIEnumerator;
510 if (pIEnumerator /* paranoia */)
511 pIEnumerator->AddRef();
512 }
513 RTCritSectLeave(&m_CritSect);
514 if (pIEnumerator)
515 {
516 /*
517 * Get the device and update it.
518 */
519 IMMDevice *pIDevice = NULL;
520 HRESULT hrc = pIEnumerator->GetDevice(pwszDeviceId, &pIDevice);
521 if (SUCCEEDED(hrc))
522 setDevice(fOutput, pIDevice, pwszDeviceId, __PRETTY_FUNCTION__);
523 else
524 LogRelMax(64, ("WasAPI: Failed to get %s device '%ls' (OnDeviceAdded): %Rhrc\n",
525 fOutput ? "output" : "input", pwszDeviceId, hrc));
526 pIEnumerator->Release();
527
528 /*
529 * Trigger device re-enumeration.
530 */
531 notifyDeviceChanges();
532 }
533 return S_OK;
534 }
535
536 IFACEMETHODIMP OnDeviceRemoved(LPCWSTR pwszDeviceId)
537 {
538 LogRelMax(64, ("WasAPI: Device '%ls' removed\n", pwszDeviceId));
539
540 /*
541 * Is this a device we're interested in? Then set it to NULL.
542 */
543 bool fOutput = false;
544 RTCritSectEnter(&m_CritSect);
545 if ( m_pDrvWas != NULL
546 && ( (fOutput = RTUtf16ICmp(m_pDrvWas->pwszOutputDevId, pwszDeviceId) == 0)
547 || RTUtf16ICmp(m_pDrvWas->pwszInputDevId, pwszDeviceId) == 0))
548 {
549 RTCritSectLeave(&m_CritSect);
550 setDevice(fOutput, NULL, pwszDeviceId, __PRETTY_FUNCTION__);
551 }
552 else
553 RTCritSectLeave(&m_CritSect);
554
555 /*
556 * Trigger device re-enumeration.
557 */
558 notifyDeviceChanges();
559 return S_OK;
560 }
561
562 IFACEMETHODIMP OnDefaultDeviceChanged(EDataFlow enmFlow, ERole enmRole, LPCWSTR pwszDefaultDeviceId)
563 {
564 /*
565 * Are we interested in this device? If so grab the enumerator.
566 */
567 IMMDeviceEnumerator *pIEnumerator = NULL;
568 RTCritSectEnter(&m_CritSect);
569 if ( m_pDrvWas != NULL
570 && ( (enmFlow == eRender && !m_pDrvWas->pwszOutputDevId)
571 || (enmFlow == eCapture && !m_pDrvWas->pwszInputDevId))
572 /* We only care for the multimedia role of a device here.
573 * Devices can have multiple roles they expose interfaces for.
574 *
575 * Not having this check will result in several OnDefaultDeviceChanged calls
576 * with the same device ID but with a different role. However, in our audio stack there is no concept
577 * of a device role, so we try avoiding that by only caring about eMultimedia.
578 *
579 * See @bugref{10844} */
580 && (enmRole == eMultimedia)
581 )
582 {
583 pIEnumerator = m_pDrvWas->pIEnumerator;
584 if (pIEnumerator /* paranoia */)
585 pIEnumerator->AddRef();
586 }
587
588 LogRelMax2(64, ("WasAPI: Default %s device changed (role=%#x, id='%ls')\n",
589 enmFlow == eRender ? "output" : "input", enmRole, pwszDefaultDeviceId ? pwszDefaultDeviceId : L"<None>"));
590
591 RTCritSectLeave(&m_CritSect);
592 if (pIEnumerator)
593 {
594 /*
595 * Get the device and update it.
596 */
597 IMMDevice *pIDevice = NULL;
598 HRESULT hrc = pIEnumerator->GetDefaultAudioEndpoint(enmFlow, enmRole, &pIDevice);
599 if (SUCCEEDED(hrc))
600 setDevice(enmFlow == eRender, pIDevice, pwszDefaultDeviceId, __PRETTY_FUNCTION__);
601 else
602 LogRelMax(64, ("WasAPI: Failed to get default %s device (OnDefaultDeviceChange): %Rhrc\n",
603 enmFlow == eRender ? "output" : "input", hrc));
604 pIEnumerator->Release();
605
606 /*
607 * Trigger device re-enumeration.
608 */
609 notifyDeviceChanges();
610 }
611
612 Log7Func(("enmFlow=%d enmRole=%d pwszDefaultDeviceId=%ls\n", enmFlow, enmRole, pwszDefaultDeviceId));
613 return S_OK;
614 }
615
616 IFACEMETHODIMP OnPropertyValueChanged(LPCWSTR pwszDeviceId, const PROPERTYKEY Key)
617 {
618 RT_NOREF(pwszDeviceId, Key);
619 Log7Func(("pwszDeviceId=%ls Key={%RTuuid, %u (%#x)}\n", pwszDeviceId, &Key.fmtid, Key.pid, Key.pid));
620 return S_OK;
621 }
622 /** @} */
623
624private:
625 /**
626 * Sets DRVHOSTAUDIOWAS::pIDeviceOutput or DRVHOSTAUDIOWAS::pIDeviceInput to @a pIDevice.
627 */
628 void setDevice(bool fOutput, IMMDevice *pIDevice, LPCWSTR pwszDeviceId, const char *pszCaller)
629 {
630 RT_NOREF(pszCaller, pwszDeviceId);
631
632 RTCritSectEnter(&m_CritSect);
633
634 /*
635 * Update our internal device reference.
636 */
637 if (m_pDrvWas)
638 {
639 if (fOutput)
640 {
641 Log7((LOG_FN_FMT ": Changing output device from %p to %p (%ls)\n",
642 pszCaller, m_pDrvWas->pIDeviceOutput, pIDevice, pwszDeviceId));
643 if (m_pDrvWas->pIDeviceOutput)
644 m_pDrvWas->pIDeviceOutput->Release();
645 m_pDrvWas->pIDeviceOutput = pIDevice;
646 }
647 else
648 {
649 Log7((LOG_FN_FMT ": Changing input device from %p to %p (%ls)\n",
650 pszCaller, m_pDrvWas->pIDeviceInput, pIDevice, pwszDeviceId));
651 if (m_pDrvWas->pIDeviceInput)
652 m_pDrvWas->pIDeviceInput->Release();
653 m_pDrvWas->pIDeviceInput = pIDevice;
654 }
655 }
656 else if (pIDevice)
657 pIDevice->Release();
658
659 /*
660 * Tell DrvAudio that the device has changed for one of the directions.
661 *
662 * We have to exit the critsect when doing so, or we'll create a locking
663 * order violation. So, try make sure the VM won't be destroyed while
664 * till DrvAudio have entered its critical section...
665 */
666 if (m_pDrvWas)
667 {
668 PPDMIHOSTAUDIOPORT const pIHostAudioPort = m_pDrvWas->pIHostAudioPort;
669 if (pIHostAudioPort)
670 {
671 VMSTATE const enmVmState = PDMDrvHlpVMState(m_pDrvWas->pDrvIns);
672 if (enmVmState < VMSTATE_POWERING_OFF)
673 {
674 RTCritSectLeave(&m_CritSect);
675 pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, fOutput ? PDMAUDIODIR_OUT : PDMAUDIODIR_IN, NULL);
676 return;
677 }
678 LogFlowFunc(("Ignoring change: enmVmState=%d\n", enmVmState));
679 }
680 }
681
682 RTCritSectLeave(&m_CritSect);
683 }
684
685 /**
686 * Tell DrvAudio to re-enumerate devices when it get a chance.
687 *
688 * We exit the critsect here too before calling DrvAudio just to be on the safe
689 * side (see setDevice()), even though the current DrvAudio code doesn't take
690 * any critsects.
691 */
692 void notifyDeviceChanges(void)
693 {
694 RTCritSectEnter(&m_CritSect);
695 if (m_pDrvWas)
696 {
697 PPDMIHOSTAUDIOPORT const pIHostAudioPort = m_pDrvWas->pIHostAudioPort;
698 if (pIHostAudioPort)
699 {
700 VMSTATE const enmVmState = PDMDrvHlpVMState(m_pDrvWas->pDrvIns);
701 if (enmVmState < VMSTATE_POWERING_OFF)
702 {
703 RTCritSectLeave(&m_CritSect);
704 pIHostAudioPort->pfnNotifyDevicesChanged(pIHostAudioPort);
705 return;
706 }
707 LogFlowFunc(("Ignoring change: enmVmState=%d\n", enmVmState));
708 }
709 }
710 RTCritSectLeave(&m_CritSect);
711 }
712};
713
714
715/*********************************************************************************************************************************
716* Pre-configured audio client cache. *
717*********************************************************************************************************************************/
718#define WAS_CACHE_MAX_ENTRIES_SAME_DEVICE 2
719
720/**
721 * Converts from PDM stream config to windows WAVEFORMATEXTENSIBLE struct.
722 *
723 * @param pProps The PDM audio PCM properties to convert from.
724 * @param pFmt The windows structure to initialize.
725 */
726static void drvHostAudioWasWaveFmtExtFromProps(PCPDMAUDIOPCMPROPS pProps, PWAVEFORMATEXTENSIBLE pFmt)
727{
728 RT_ZERO(*pFmt);
729 pFmt->Format.wFormatTag = WAVE_FORMAT_PCM;
730 pFmt->Format.nChannels = PDMAudioPropsChannels(pProps);
731 pFmt->Format.wBitsPerSample = PDMAudioPropsSampleBits(pProps);
732 pFmt->Format.nSamplesPerSec = PDMAudioPropsHz(pProps);
733 pFmt->Format.nBlockAlign = PDMAudioPropsFrameSize(pProps);
734 pFmt->Format.nAvgBytesPerSec = PDMAudioPropsFramesToBytes(pProps, PDMAudioPropsHz(pProps));
735 pFmt->Format.cbSize = 0; /* No extra data specified. */
736
737 /*
738 * We need to use the extensible structure if there are more than two channels
739 * or if the channels have non-standard assignments.
740 */
741 if ( pFmt->Format.nChannels > 2
742 || ( pFmt->Format.nChannels == 1
743 ? pProps->aidChannels[0] != PDMAUDIOCHANNELID_MONO
744 : pProps->aidChannels[0] != PDMAUDIOCHANNELID_FRONT_LEFT
745 || pProps->aidChannels[1] != PDMAUDIOCHANNELID_FRONT_RIGHT))
746 {
747 pFmt->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
748 pFmt->Format.cbSize = sizeof(*pFmt) - sizeof(pFmt->Format);
749 pFmt->Samples.wValidBitsPerSample = PDMAudioPropsSampleBits(pProps);
750 pFmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
751 pFmt->dwChannelMask = 0;
752 unsigned const cSrcChannels = pFmt->Format.nChannels;
753 for (unsigned i = 0; i < cSrcChannels; i++)
754 if ( pProps->aidChannels[i] >= PDMAUDIOCHANNELID_FIRST_STANDARD
755 && pProps->aidChannels[i] < PDMAUDIOCHANNELID_END_STANDARD)
756 pFmt->dwChannelMask |= RT_BIT_32(pProps->aidChannels[i] - PDMAUDIOCHANNELID_FIRST_STANDARD);
757 else
758 pFmt->Format.nChannels -= 1;
759 }
760}
761
762
763#if 0 /* unused */
764/**
765 * Converts from windows WAVEFORMATEX and stream props to PDM audio properties.
766 *
767 * @returns VINF_SUCCESS on success, VERR_AUDIO_STREAM_COULD_NOT_CREATE if not
768 * supported.
769 * @param pProps The output properties structure.
770 * @param pFmt The windows wave format structure.
771 * @param pszStream The stream name for error logging.
772 * @param pwszDevId The device ID for error logging.
773 */
774static int drvHostAudioWasCacheWaveFmtExToProps(PPDMAUDIOPCMPROPS pProps, WAVEFORMATEX const *pFmt,
775 const char *pszStream, PCRTUTF16 pwszDevId)
776{
777 if (pFmt->wFormatTag == WAVE_FORMAT_PCM)
778 {
779 if ( pFmt->wBitsPerSample == 8
780 || pFmt->wBitsPerSample == 16
781 || pFmt->wBitsPerSample == 32)
782 {
783 if (pFmt->nChannels > 0 && pFmt->nChannels < 16)
784 {
785 if (pFmt->nSamplesPerSec >= 4096 && pFmt->nSamplesPerSec <= 768000)
786 {
787 PDMAudioPropsInit(pProps, pFmt->wBitsPerSample / 8, true /*fSigned*/, pFmt->nChannels, pFmt->nSamplesPerSec);
788 if (PDMAudioPropsFrameSize(pProps) == pFmt->nBlockAlign)
789 return VINF_SUCCESS;
790 }
791 }
792 }
793 }
794 LogRelMax(64, ("WasAPI: Error! Unsupported stream format for '%s' suggested by '%ls':\n"
795 "WasAPI: wFormatTag = %RU16 (expected %d)\n"
796 "WasAPI: nChannels = %RU16 (expected 1..15)\n"
797 "WasAPI: nSamplesPerSec = %RU32 (expected 4096..768000)\n"
798 "WasAPI: nAvgBytesPerSec = %RU32\n"
799 "WasAPI: nBlockAlign = %RU16\n"
800 "WasAPI: wBitsPerSample = %RU16 (expected 8, 16, or 32)\n"
801 "WasAPI: cbSize = %RU16\n",
802 pszStream, pwszDevId, pFmt->wFormatTag, WAVE_FORMAT_PCM, pFmt->nChannels, pFmt->nSamplesPerSec, pFmt->nAvgBytesPerSec,
803 pFmt->nBlockAlign, pFmt->wBitsPerSample, pFmt->cbSize));
804 return VERR_AUDIO_STREAM_COULD_NOT_CREATE;
805}
806#endif
807
808
809/**
810 * Destroys a devie config cache entry.
811 *
812 * @param pThis The WASAPI host audio driver instance data.
813 * @param pDevCfg Device config entry. Must not be in the list.
814 */
815static void drvHostAudioWasCacheDestroyDevConfig(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg)
816{
817 if (pDevCfg->pDevEntry->enmDir == PDMAUDIODIR_IN)
818 ASMAtomicDecU32(&pThis->cCacheEntriesIn);
819 else
820 ASMAtomicDecU32(&pThis->cCacheEntriesOut);
821
822 uint32_t cTypeClientRefs = 0;
823 if (pDevCfg->pIAudioCaptureClient)
824 {
825 cTypeClientRefs = pDevCfg->pIAudioCaptureClient->Release();
826 pDevCfg->pIAudioCaptureClient = NULL;
827 }
828
829 if (pDevCfg->pIAudioRenderClient)
830 {
831 cTypeClientRefs = pDevCfg->pIAudioRenderClient->Release();
832 pDevCfg->pIAudioRenderClient = NULL;
833 }
834
835 uint32_t cClientRefs = 0;
836 if (pDevCfg->pIAudioClient /* paranoia */)
837 {
838 cClientRefs = pDevCfg->pIAudioClient->Release();
839 pDevCfg->pIAudioClient = NULL;
840 }
841
842 Log8Func(("Destroying cache config entry: '%ls: %s' - cClientRefs=%u cTypeClientRefs=%u\n",
843 pDevCfg->pDevEntry->wszDevId, pDevCfg->szProps, cClientRefs, cTypeClientRefs));
844 RT_NOREF(cClientRefs, cTypeClientRefs);
845
846 pDevCfg->pDevEntry = NULL;
847 RTMemFree(pDevCfg);
848}
849
850
851/**
852 * Destroys a device cache entry.
853 *
854 * @param pThis The WASAPI host audio driver instance data.
855 * @param pDevEntry The device entry. Must not be in the cache!
856 */
857static void drvHostAudioWasCacheDestroyDevEntry(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASCACHEDEV pDevEntry)
858{
859 Log8Func(("Destroying cache entry: %p - '%ls'\n", pDevEntry, pDevEntry->wszDevId));
860
861 PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg, pDevCfgNext;
862 RTListForEachSafe(&pDevEntry->ConfigList, pDevCfg, pDevCfgNext, DRVHOSTAUDIOWASCACHEDEVCFG, ListEntry)
863 drvHostAudioWasCacheDestroyDevConfig(pThis, pDevCfg);
864
865 uint32_t cDevRefs = 0;
866 if (pDevEntry->pIDevice /* paranoia */)
867 {
868 cDevRefs = pDevEntry->pIDevice->Release();
869 pDevEntry->pIDevice = NULL;
870 }
871
872 pDevEntry->cwcDevId = 0;
873 pDevEntry->wszDevId[0] = '\0';
874 RTMemFree(pDevEntry);
875 Log8Func(("Destroyed cache entry: %p cDevRefs=%u\n", pDevEntry, cDevRefs));
876}
877
878
879/**
880 * Prunes the cache.
881 */
882static void drvHostAudioWasCachePrune(PDRVHOSTAUDIOWAS pThis)
883{
884 AssertReturnVoid(pThis->fCacheEnabled);
885
886 /*
887 * Prune each direction separately.
888 */
889 struct
890 {
891 PDMAUDIODIR enmDir;
892 uint32_t volatile *pcEntries;
893 } aWork[] = { { PDMAUDIODIR_IN, &pThis->cCacheEntriesIn }, { PDMAUDIODIR_OUT, &pThis->cCacheEntriesOut }, };
894 for (uint32_t iWork = 0; iWork < RT_ELEMENTS(aWork); iWork++)
895 {
896 /*
897 * Remove the least recently used entry till we're below the threshold
898 * or there are no more inactive entries.
899 */
900 LogFlowFunc(("iWork=%u cEntries=%u\n", iWork, *aWork[iWork].pcEntries));
901 while (*aWork[iWork].pcEntries > VBOX_WASAPI_MAX_TOTAL_CONFIG_ENTRIES)
902 {
903 RTCritSectEnter(&pThis->CritSectCache);
904 PDRVHOSTAUDIOWASCACHEDEVCFG pLeastRecentlyUsed = NULL;
905 PDRVHOSTAUDIOWASCACHEDEV pDevEntry;
906 RTListForEach(&pThis->CacheHead, pDevEntry, DRVHOSTAUDIOWASCACHEDEV, ListEntry)
907 {
908 if (pDevEntry->enmDir == aWork[iWork].enmDir)
909 {
910 PDRVHOSTAUDIOWASCACHEDEVCFG pHeadCfg = RTListGetFirst(&pDevEntry->ConfigList,
911 DRVHOSTAUDIOWASCACHEDEVCFG, ListEntry);
912 if ( pHeadCfg
913 && (!pLeastRecentlyUsed || pHeadCfg->nsLastUsed < pLeastRecentlyUsed->nsLastUsed))
914 pLeastRecentlyUsed = pHeadCfg;
915 }
916 }
917 if (pLeastRecentlyUsed)
918 RTListNodeRemove(&pLeastRecentlyUsed->ListEntry);
919 RTCritSectLeave(&pThis->CritSectCache);
920
921 if (!pLeastRecentlyUsed)
922 break;
923 drvHostAudioWasCacheDestroyDevConfig(pThis, pLeastRecentlyUsed);
924 }
925 }
926}
927
928
929/**
930 * Purges all the entries in the cache.
931 */
932static void drvHostAudioWasCachePurge(PDRVHOSTAUDIOWAS pThis, bool fOnWorker)
933{
934 AssertReturnVoid(pThis->fCacheEnabled);
935
936 for (;;)
937 {
938 RTCritSectEnter(&pThis->CritSectCache);
939 PDRVHOSTAUDIOWASCACHEDEV pDevEntry = RTListRemoveFirst(&pThis->CacheHead, DRVHOSTAUDIOWASCACHEDEV, ListEntry);
940 RTCritSectLeave(&pThis->CritSectCache);
941 if (!pDevEntry)
942 break;
943 drvHostAudioWasCacheDestroyDevEntry(pThis, pDevEntry);
944 }
945
946 if (fOnWorker)
947 {
948 int rc = RTSemEventMultiSignal(pThis->hEvtCachePurge);
949 AssertRC(rc);
950 }
951}
952
953
954/**
955 * Looks up a specific configuration.
956 *
957 * @returns Pointer to the device config (removed from cache) on success. NULL
958 * if no matching config found.
959 * @param pDevEntry Where to perform the lookup.
960 * @param pProps The config properties to match.
961 */
962static PDRVHOSTAUDIOWASCACHEDEVCFG
963drvHostAudioWasCacheLookupLocked(PDRVHOSTAUDIOWASCACHEDEV pDevEntry, PCPDMAUDIOPCMPROPS pProps)
964{
965 PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg;
966 RTListForEach(&pDevEntry->ConfigList, pDevCfg, DRVHOSTAUDIOWASCACHEDEVCFG, ListEntry)
967 {
968 if (PDMAudioPropsAreEqual(&pDevCfg->Props, pProps))
969 {
970 RTListNodeRemove(&pDevCfg->ListEntry);
971 pDevCfg->nsLastUsed = RTTimeNanoTS();
972 return pDevCfg;
973 }
974 }
975 return NULL;
976}
977
978
979/**
980 * Initializes a device config entry.
981 *
982 * This is usually done on the worker thread.
983 *
984 * @returns VBox status code.
985 * @param pDevCfg The device configuration entry to initialize.
986 */
987static int drvHostAudioWasCacheInitConfig(PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg)
988{
989 /*
990 * Assert some sanity given that we migth be called on the worker thread
991 * and pDevCfg being a message parameter.
992 */
993 AssertPtrReturn(pDevCfg, VERR_INTERNAL_ERROR_2);
994 AssertReturn(pDevCfg->rcSetup == VERR_AUDIO_STREAM_INIT_IN_PROGRESS, VERR_INTERNAL_ERROR_2);
995 AssertReturn(pDevCfg->pIAudioClient == NULL, VERR_INTERNAL_ERROR_2);
996 AssertReturn(pDevCfg->pIAudioCaptureClient == NULL, VERR_INTERNAL_ERROR_2);
997 AssertReturn(pDevCfg->pIAudioRenderClient == NULL, VERR_INTERNAL_ERROR_2);
998 AssertReturn(PDMAudioPropsAreValid(&pDevCfg->Props), VERR_INTERNAL_ERROR_2);
999
1000 PDRVHOSTAUDIOWASCACHEDEV pDevEntry = pDevCfg->pDevEntry;
1001 AssertPtrReturn(pDevEntry, VERR_INTERNAL_ERROR_2);
1002 AssertPtrReturn(pDevEntry->pIDevice, VERR_INTERNAL_ERROR_2);
1003 AssertReturn(pDevEntry->enmDir == PDMAUDIODIR_IN || pDevEntry->enmDir == PDMAUDIODIR_OUT, VERR_INTERNAL_ERROR_2);
1004
1005 /*
1006 * First we need an IAudioClient interface for calling IsFormatSupported
1007 * on so we can get guidance as to what to do next.
1008 *
1009 * Initially, I thought the AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM was not
1010 * supported all the way back to Vista and that we'd had to try different
1011 * things here to get the most optimal format. However, according to
1012 * https://social.msdn.microsoft.com/Forums/en-US/1d974d90-6636-4121-bba3-a8861d9ab92a
1013 * it is supported, just maybe missing from the SDK or something...
1014 *
1015 * I'll leave the IsFormatSupported call here as it gives us a clue as to
1016 * what exactly the WAS needs to convert our audio stream into/from.
1017 */
1018 Log8Func(("Activating an IAudioClient for '%ls' ...\n", pDevEntry->wszDevId));
1019 IAudioClient *pIAudioClient = NULL;
1020 HRESULT hrc = pDevEntry->pIDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL,
1021 NULL /*pActivationParams*/, (void **)&pIAudioClient);
1022 Log8Func(("Activate('%ls', IAudioClient) -> %Rhrc\n", pDevEntry->wszDevId, hrc));
1023 if (FAILED(hrc))
1024 {
1025 LogRelMax(64, ("WasAPI: Activate(%ls, IAudioClient) failed: %Rhrc\n", pDevEntry->wszDevId, hrc));
1026 pDevCfg->nsInited = RTTimeNanoTS();
1027 pDevCfg->nsLastUsed = pDevCfg->nsInited;
1028 return pDevCfg->rcSetup = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1029 }
1030
1031 WAVEFORMATEXTENSIBLE WaveFmtExt;
1032 drvHostAudioWasWaveFmtExtFromProps(&pDevCfg->Props, &WaveFmtExt);
1033
1034 PWAVEFORMATEX pClosestMatch = NULL;
1035 hrc = pIAudioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &WaveFmtExt.Format, &pClosestMatch);
1036
1037 /*
1038 * If the format is supported, go ahead and initialize the client instance.
1039 *
1040 * The docs talks about AUDCLNT_E_UNSUPPORTED_FORMAT being success too, but
1041 * that doesn't seem to be the case (at least not for mixing up the
1042 * WAVEFORMATEX::wFormatTag values). Seems that is the standard return code
1043 * if there is anything it doesn't grok.
1044 */
1045 if (SUCCEEDED(hrc))
1046 {
1047 if (hrc == S_OK)
1048 Log8Func(("IsFormatSupported(,%s,) -> S_OK + %p: requested format is supported\n", pDevCfg->szProps, pClosestMatch));
1049 else
1050 Log8Func(("IsFormatSupported(,%s,) -> %Rhrc + %p: %uch S%u %uHz\n", pDevCfg->szProps, hrc, pClosestMatch,
1051 pClosestMatch ? pClosestMatch->nChannels : 0, pClosestMatch ? pClosestMatch->wBitsPerSample : 0,
1052 pClosestMatch ? pClosestMatch->nSamplesPerSec : 0));
1053
1054 REFERENCE_TIME const cBufferSizeInNtTicks = PDMAudioPropsFramesToNtTicks(&pDevCfg->Props, pDevCfg->cFramesBufferSize);
1055 uint32_t fInitFlags = AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
1056 | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY;
1057 hrc = pIAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, fInitFlags, cBufferSizeInNtTicks,
1058 0 /*cPeriodicityInNtTicks*/, &WaveFmtExt.Format, NULL /*pAudioSessionGuid*/);
1059 Log8Func(("Initialize(,%x, %RI64, %s,) -> %Rhrc\n", fInitFlags, cBufferSizeInNtTicks, pDevCfg->szProps, hrc));
1060 if (SUCCEEDED(hrc))
1061 {
1062 /*
1063 * The direction specific client interface.
1064 */
1065 if (pDevEntry->enmDir == PDMAUDIODIR_IN)
1066 hrc = pIAudioClient->GetService(__uuidof(IAudioCaptureClient), (void **)&pDevCfg->pIAudioCaptureClient);
1067 else
1068 hrc = pIAudioClient->GetService(__uuidof(IAudioRenderClient), (void **)&pDevCfg->pIAudioRenderClient);
1069 Log8Func(("GetService -> %Rhrc + %p\n", hrc, pDevEntry->enmDir == PDMAUDIODIR_IN
1070 ? (void *)pDevCfg->pIAudioCaptureClient : (void *)pDevCfg->pIAudioRenderClient));
1071 if (SUCCEEDED(hrc))
1072 {
1073 /*
1074 * Obtain the actual stream format and buffer config.
1075 */
1076 UINT32 cFramesBufferSize = 0;
1077 REFERENCE_TIME cDefaultPeriodInNtTicks = 0;
1078 REFERENCE_TIME cMinimumPeriodInNtTicks = 0;
1079 REFERENCE_TIME cLatencyinNtTicks = 0;
1080 hrc = pIAudioClient->GetBufferSize(&cFramesBufferSize);
1081 if (SUCCEEDED(hrc))
1082 {
1083 hrc = pIAudioClient->GetDevicePeriod(&cDefaultPeriodInNtTicks, &cMinimumPeriodInNtTicks);
1084 if (SUCCEEDED(hrc))
1085 {
1086 hrc = pIAudioClient->GetStreamLatency(&cLatencyinNtTicks);
1087 if (SUCCEEDED(hrc))
1088 {
1089 LogRel2(("WasAPI: Aquired buffer parameters for %s:\n"
1090 "WasAPI: cFramesBufferSize = %RU32\n"
1091 "WasAPI: cDefaultPeriodInNtTicks = %RI64\n"
1092 "WasAPI: cMinimumPeriodInNtTicks = %RI64\n"
1093 "WasAPI: cLatencyinNtTicks = %RI64\n",
1094 pDevCfg->szProps, cFramesBufferSize, cDefaultPeriodInNtTicks,
1095 cMinimumPeriodInNtTicks, cLatencyinNtTicks));
1096
1097 pDevCfg->pIAudioClient = pIAudioClient;
1098 pDevCfg->cFramesBufferSize = cFramesBufferSize;
1099 pDevCfg->cFramesPeriod = PDMAudioPropsNanoToFrames(&pDevCfg->Props,
1100 cDefaultPeriodInNtTicks * 100);
1101 pDevCfg->nsInited = RTTimeNanoTS();
1102 pDevCfg->nsLastUsed = pDevCfg->nsInited;
1103 pDevCfg->rcSetup = VINF_SUCCESS;
1104
1105 if (pClosestMatch)
1106 CoTaskMemFree(pClosestMatch);
1107 Log8Func(("returns VINF_SUCCESS (%p (%s) inited in %'RU64 ns)\n",
1108 pDevCfg, pDevCfg->szProps, pDevCfg->nsInited - pDevCfg->nsCreated));
1109 return VINF_SUCCESS;
1110 }
1111 LogRelMax(64, ("WasAPI: GetStreamLatency failed: %Rhrc\n", hrc));
1112 }
1113 else
1114 LogRelMax(64, ("WasAPI: GetDevicePeriod failed: %Rhrc\n", hrc));
1115 }
1116 else
1117 LogRelMax(64, ("WasAPI: GetBufferSize failed: %Rhrc\n", hrc));
1118
1119 if (pDevCfg->pIAudioCaptureClient)
1120 {
1121 pDevCfg->pIAudioCaptureClient->Release();
1122 pDevCfg->pIAudioCaptureClient = NULL;
1123 }
1124
1125 if (pDevCfg->pIAudioRenderClient)
1126 {
1127 pDevCfg->pIAudioRenderClient->Release();
1128 pDevCfg->pIAudioRenderClient = NULL;
1129 }
1130 }
1131 else
1132 LogRelMax(64, ("WasAPI: IAudioClient::GetService(%s) failed: %Rhrc\n", pDevCfg->szProps, hrc));
1133 }
1134 else
1135 LogRelMax(64, ("WasAPI: IAudioClient::Initialize(%s) failed: %Rhrc\n", pDevCfg->szProps, hrc));
1136 }
1137 else
1138 LogRelMax(64,("WasAPI: IAudioClient::IsFormatSupported(,%s,) failed: %Rhrc\n", pDevCfg->szProps, hrc));
1139
1140 pIAudioClient->Release();
1141 if (pClosestMatch)
1142 CoTaskMemFree(pClosestMatch);
1143 pDevCfg->nsInited = RTTimeNanoTS();
1144 pDevCfg->nsLastUsed = 0;
1145 Log8Func(("returns VERR_AUDIO_STREAM_COULD_NOT_CREATE (inited in %'RU64 ns)\n", pDevCfg->nsInited - pDevCfg->nsCreated));
1146 return pDevCfg->rcSetup = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1147}
1148
1149
1150/**
1151 * Worker for drvHostAudioWasCacheLookupOrCreate.
1152 *
1153 * If lookup fails, a new entry will be created.
1154 *
1155 * @note Called holding the cache's lock, returning without holding it!
1156 */
1157static int drvHostAudioWasCacheLookupOrCreateConfig(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASCACHEDEV pDevEntry,
1158 PCPDMAUDIOSTREAMCFG pCfgReq, bool fOnWorker,
1159 PDRVHOSTAUDIOWASCACHEDEVCFG *ppDevCfg)
1160{
1161 PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg;
1162
1163 /*
1164 * Check if we've got a matching config.
1165 */
1166 if (pThis->fCacheEnabled)
1167 {
1168 pDevCfg = drvHostAudioWasCacheLookupLocked(pDevEntry, &pCfgReq->Props);
1169 if (pDevCfg)
1170 {
1171 *ppDevCfg = pDevCfg;
1172 RTCritSectLeave(&pThis->CritSectCache);
1173 Log8Func(("Config cache hit '%s' on '%ls': %p\n", pDevCfg->szProps, pDevEntry->wszDevId, pDevCfg));
1174 return VINF_SUCCESS;
1175 }
1176
1177 RTCritSectLeave(&pThis->CritSectCache);
1178 }
1179
1180 /*
1181 * Allocate an device config entry and hand the creation task over to the
1182 * worker thread, unless we're already on it.
1183 */
1184 pDevCfg = (PDRVHOSTAUDIOWASCACHEDEVCFG)RTMemAllocZ(sizeof(*pDevCfg));
1185 AssertPtrReturn(pDevCfg, VERR_NO_MEMORY);
1186 RTListInit(&pDevCfg->ListEntry);
1187 pDevCfg->pDevEntry = pDevEntry;
1188 pDevCfg->rcSetup = VERR_AUDIO_STREAM_INIT_IN_PROGRESS;
1189 pDevCfg->Props = pCfgReq->Props;
1190 pDevCfg->cFramesBufferSize = pCfgReq->Backend.cFramesBufferSize;
1191 PDMAudioPropsToString(&pDevCfg->Props, pDevCfg->szProps, sizeof(pDevCfg->szProps));
1192 pDevCfg->nsCreated = RTTimeNanoTS();
1193 pDevCfg->nsLastUsed = pDevCfg->nsCreated;
1194
1195 if (pThis->fCacheEnabled)
1196 {
1197 uint32_t cCacheEntries;
1198 if (pDevCfg->pDevEntry->enmDir == PDMAUDIODIR_IN)
1199 cCacheEntries = ASMAtomicIncU32(&pThis->cCacheEntriesIn);
1200 else
1201 cCacheEntries = ASMAtomicIncU32(&pThis->cCacheEntriesOut);
1202 if (cCacheEntries > VBOX_WASAPI_MAX_TOTAL_CONFIG_ENTRIES)
1203 {
1204 LogFlowFunc(("Trigger cache pruning.\n"));
1205 int rc2 = pThis->pIHostAudioPort->pfnDoOnWorkerThread(pThis->pIHostAudioPort, NULL /*pStream*/,
1206 DRVHOSTAUDIOWAS_DO_PRUNE_CACHE, NULL /*pvUser*/);
1207 AssertRCStmt(rc2, drvHostAudioWasCachePrune(pThis));
1208 }
1209 }
1210
1211 if (!fOnWorker)
1212 {
1213 *ppDevCfg = pDevCfg;
1214 LogFlowFunc(("Doing the rest of the work on %p via pfnStreamInitAsync...\n", pDevCfg));
1215 return VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED;
1216 }
1217
1218 /*
1219 * Initialize the entry on the calling thread.
1220 */
1221 int rc = drvHostAudioWasCacheInitConfig(pDevCfg);
1222 AssertRC(pDevCfg->rcSetup == rc);
1223 if (RT_SUCCESS(rc))
1224 rc = pDevCfg->rcSetup; /* paranoia */
1225 if (RT_SUCCESS(rc))
1226 {
1227 *ppDevCfg = pDevCfg;
1228 LogFlowFunc(("Returning %p\n", pDevCfg));
1229 return VINF_SUCCESS;
1230 }
1231 RTMemFree(pDevCfg);
1232 *ppDevCfg = NULL;
1233 return rc;
1234}
1235
1236
1237/**
1238 * Looks up the given device + config combo in the cache, creating a new entry
1239 * if missing.
1240 *
1241 * @returns VBox status code.
1242 * @retval VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED if @a fOnWorker is @c false and
1243 * we created a new entry that needs initalization by calling
1244 * drvHostAudioWasCacheInitConfig() on it.
1245 * @param pThis The WASAPI host audio driver instance data.
1246 * @param pIDevice The device to look up.
1247 * @param pCfgReq The configuration to look up.
1248 * @param fOnWorker Set if we're on a worker thread, otherwise false. When
1249 * set to @c true, VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED will
1250 * not be returned and a new entry will be fully
1251 * initialized before returning.
1252 * @param ppDevCfg Where to return the requested device config.
1253 */
1254static int drvHostAudioWasCacheLookupOrCreate(PDRVHOSTAUDIOWAS pThis, IMMDevice *pIDevice, PCPDMAUDIOSTREAMCFG pCfgReq,
1255 bool fOnWorker, PDRVHOSTAUDIOWASCACHEDEVCFG *ppDevCfg)
1256{
1257 *ppDevCfg = NULL;
1258
1259 /*
1260 * Get the device ID so we can perform the lookup.
1261 */
1262 int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1263 LPWSTR pwszDevId = NULL;
1264 HRESULT hrc = pIDevice->GetId(&pwszDevId);
1265 if (SUCCEEDED(hrc))
1266 {
1267 PDRVHOSTAUDIOWASCACHEDEV pDevEntry;
1268 size_t cwcDevId = RTUtf16Len(pwszDevId);
1269
1270 if (pThis->fCacheEnabled)
1271 {
1272 LogRel2(("WasAPI: Checking for cached device '%ls' ...\n", pwszDevId));
1273
1274#define LOG_STALE_DEVICE(a_LogRel2What) \
1275 LogRel2(a_LogRel2What); \
1276 LogRel(("WasAPI: Stale audio interface '%ls' detected!\n", pDevEntry->wszDevId));
1277
1278#define LOG_STALE_DEVICE_BREAK(a_LogRel2What) \
1279 { \
1280 LOG_STALE_DEVICE(a_LogRel2What); \
1281 break; \
1282 }
1283
1284 /*
1285 * The cache has two levels, so first the device entry.
1286 */
1287 PDRVHOSTAUDIOWASCACHEDEV pDevEntryNext;
1288 RTCritSectEnter(&pThis->CritSectCache);
1289 RTListForEachSafe(&pThis->CacheHead, pDevEntry, pDevEntryNext, DRVHOSTAUDIOWASCACHEDEV, ListEntry)
1290 {
1291 if ( pDevEntry->cwcDevId == cwcDevId
1292 && pDevEntry->enmDir == pCfgReq->enmDir
1293 && RTUtf16Cmp(pDevEntry->wszDevId, pwszDevId) == 0)
1294 {
1295 /* First, try retrieving the current device state here as a yet another messure for
1296 * AUDCLNT_E_DEVICE_INVALIDATED errors. See @bugref{10844} */
1297 DWORD dwState;
1298 hrc = pIDevice->GetState(&dwState);
1299 if (SUCCEEDED(hrc))
1300 {
1301 if (dwState != DEVICE_STATE_ACTIVE)
1302 LOG_STALE_DEVICE_BREAK(("WasAPI: Cache hit for device '%ls': Non-active state (state %#x)\n",
1303 pDevEntry->wszDevId, drvHostAudioWasMMDeviceStateToString(dwState)));
1304 }
1305 else
1306 LOG_STALE_DEVICE_BREAK(("WasAPI: Cache hit for device '%ls': Unable to retrieve state (hr=%#x)\n",
1307 pDevEntry->wszDevId, hrc));
1308
1309 /*
1310 * Cache hit -- here we now need to also check if the device interface we want to look up
1311 * actually matches the one we have in the cache entry.
1312 *
1313 * If it doesn't, bail out and add a new device entry to the cache with the new interface below then.
1314 *
1315 * This is needed when switching audio interfaces and the device interface becomes invalid via
1316 * AUDCLNT_E_DEVICE_INVALIDATED. See @bugref{10503}
1317 */
1318 if (pDevEntry->pIDevice != pIDevice)
1319 LOG_STALE_DEVICE_BREAK(("WasAPI: Cache hit for device '%ls': Stale interface (new: %p, old: %p)\n",
1320 pDevEntry->wszDevId, pIDevice, pDevEntry->pIDevice));
1321
1322 LogRel2(("WasAPI: Cache hit for device '%ls' (iface %p)\n", pwszDevId, pIDevice));
1323
1324 CoTaskMemFree(pwszDevId);
1325 pwszDevId = NULL;
1326
1327 return drvHostAudioWasCacheLookupOrCreateConfig(pThis, pDevEntry, pCfgReq, fOnWorker, ppDevCfg);
1328 }
1329 }
1330 RTCritSectLeave(&pThis->CritSectCache);
1331
1332 LogRel2(("WasAPI: Cache miss for device '%ls' (iface %p)\n", pwszDevId, pIDevice));
1333
1334#undef LOG_STALE_DEVICE_BREAK
1335#undef LOG_STALE_DEVICE
1336
1337 }
1338 else
1339 LogRel2(("WasAPI: Cache disabled for device '%ls' (iface %p)\n", pwszDevId, pIDevice));
1340
1341 /*
1342 * Device not in the cache, add it.
1343 */
1344 pDevEntry = (PDRVHOSTAUDIOWASCACHEDEV)RTMemAllocZVar(RT_UOFFSETOF_DYN(DRVHOSTAUDIOWASCACHEDEV, wszDevId[cwcDevId + 1]));
1345 if (pDevEntry)
1346 {
1347 if (pThis->fCacheEnabled)
1348 pIDevice->AddRef();
1349 pDevEntry->pIDevice = pIDevice;
1350 pDevEntry->enmDir = pCfgReq->enmDir;
1351 pDevEntry->cwcDevId = cwcDevId;
1352#if 0
1353 pDevEntry->fSupportsAutoConvertPcm = -1;
1354 pDevEntry->fSupportsSrcDefaultQuality = -1;
1355#endif
1356 RTListInit(&pDevEntry->ConfigList);
1357 memcpy(pDevEntry->wszDevId, pwszDevId, cwcDevId * sizeof(RTUTF16));
1358 pDevEntry->wszDevId[cwcDevId] = '\0';
1359
1360 CoTaskMemFree(pwszDevId);
1361 pwszDevId = NULL;
1362
1363 if (pThis->fCacheEnabled)
1364 {
1365 /*
1366 * Before adding the device to the cache, check that someone didn't race us adding it.
1367 */
1368 RTCritSectEnter(&pThis->CritSectCache);
1369 PDRVHOSTAUDIOWASCACHEDEV pDevEntry2;
1370 RTListForEach(&pThis->CacheHead, pDevEntry2, DRVHOSTAUDIOWASCACHEDEV, ListEntry)
1371 {
1372 if ( pDevEntry2->cwcDevId == cwcDevId
1373 /* Note: We have to compare the device interface here as well, as a cached device entry might
1374 * have a stale audio interface for the same device. In such a case a new device entry will be created below. */
1375 && pDevEntry2->pIDevice == pIDevice
1376 && pDevEntry2->enmDir == pCfgReq->enmDir
1377 && RTUtf16Cmp(pDevEntry2->wszDevId, pDevEntry->wszDevId) == 0)
1378 {
1379 pIDevice->Release();
1380 RTMemFree(pDevEntry);
1381 pDevEntry = NULL;
1382
1383 LogRel2(("WasAPI: Lost race adding device '%ls' (node %p)\n", pDevEntry2->wszDevId, pDevEntry2));
1384 return drvHostAudioWasCacheLookupOrCreateConfig(pThis, pDevEntry2, pCfgReq, fOnWorker, ppDevCfg);
1385 }
1386 }
1387 RTListPrepend(&pThis->CacheHead, &pDevEntry->ListEntry);
1388
1389 LogRel2(("WasAPI: Added device '%ls' to cache (node %p)\n", pDevEntry->wszDevId, pDevEntry));
1390 }
1391
1392 return drvHostAudioWasCacheLookupOrCreateConfig(pThis, pDevEntry, pCfgReq, fOnWorker, ppDevCfg);
1393 }
1394 CoTaskMemFree(pwszDevId);
1395 }
1396 else
1397 LogRelMax(64, ("WasAPI: GetId failed (lookup): %Rhrc\n", hrc));
1398 return rc;
1399}
1400
1401
1402/**
1403 * Return the given config to the cache.
1404 *
1405 * @param pThis The WASAPI host audio driver instance data.
1406 * @param pDevCfg The device config to put back.
1407 */
1408static void drvHostAudioWasCachePutBack(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg)
1409{
1410 /*
1411 * Reset the audio client to see that it works and to make sure it's in a sensible state.
1412 */
1413 HRESULT hrc = pDevCfg->pIAudioClient ? pDevCfg->pIAudioClient->Reset()
1414 : pDevCfg->rcSetup == VERR_AUDIO_STREAM_INIT_IN_PROGRESS ? S_OK : E_FAIL;
1415 if (SUCCEEDED(hrc))
1416 {
1417 if (pThis->fCacheEnabled)
1418 {
1419 Log8Func(("Putting %p/'%s' back\n", pDevCfg, pDevCfg->szProps));
1420 RTCritSectEnter(&pThis->CritSectCache);
1421 RTListAppend(&pDevCfg->pDevEntry->ConfigList, &pDevCfg->ListEntry);
1422 uint32_t const cEntries = pDevCfg->pDevEntry->enmDir == PDMAUDIODIR_IN ? pThis->cCacheEntriesIn : pThis->cCacheEntriesOut;
1423 RTCritSectLeave(&pThis->CritSectCache);
1424
1425 /* Trigger pruning if we're over the threshold. */
1426 if (cEntries > VBOX_WASAPI_MAX_TOTAL_CONFIG_ENTRIES)
1427 {
1428 LogFlowFunc(("Trigger cache pruning.\n"));
1429 int rc2 = pThis->pIHostAudioPort->pfnDoOnWorkerThread(pThis->pIHostAudioPort, NULL /*pStream*/,
1430 DRVHOSTAUDIOWAS_DO_PRUNE_CACHE, NULL /*pvUser*/);
1431 AssertRCStmt(rc2, drvHostAudioWasCachePrune(pThis));
1432 }
1433 }
1434 }
1435 else
1436 {
1437 Log8Func(("IAudioClient::Reset failed (%Rhrc) on %p/'%s', destroying it.\n", hrc, pDevCfg, pDevCfg->szProps));
1438 drvHostAudioWasCacheDestroyDevConfig(pThis, pDevCfg);
1439 }
1440}
1441
1442
1443static void drvHostWasCacheConfigHinting(PDRVHOSTAUDIOWAS pThis, PPDMAUDIOSTREAMCFG pCfgReq, bool fOnWorker)
1444{
1445 /*
1446 * Get the device.
1447 */
1448 pThis->pNotifyClient->lockEnter();
1449 IMMDevice *pIDevice = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->pIDeviceInput : pThis->pIDeviceOutput;
1450 if (pIDevice)
1451 pIDevice->AddRef();
1452 pThis->pNotifyClient->lockLeave();
1453 if (pIDevice)
1454 {
1455 /*
1456 * Look up the config and put it back.
1457 */
1458 PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg = NULL;
1459 int rc = drvHostAudioWasCacheLookupOrCreate(pThis, pIDevice, pCfgReq, fOnWorker, &pDevCfg);
1460 LogFlowFunc(("pDevCfg=%p rc=%Rrc\n", pDevCfg, rc));
1461 if (pDevCfg && RT_SUCCESS(rc))
1462 drvHostAudioWasCachePutBack(pThis, pDevCfg);
1463 pIDevice->Release();
1464 }
1465}
1466
1467
1468/**
1469 * Prefills the cache.
1470 *
1471 * @param pThis The WASAPI host audio driver instance data.
1472 */
1473static void drvHostAudioWasCacheFill(PDRVHOSTAUDIOWAS pThis)
1474{
1475#if 0 /* we don't have the buffer config nor do we really know which frequences to expect */
1476 Log8Func(("enter\n"));
1477 struct
1478 {
1479 PCRTUTF16 pwszDevId;
1480 PDMAUDIODIR enmDir;
1481 } aToCache[] =
1482 {
1483 { pThis->pwszInputDevId, PDMAUDIODIR_IN },
1484 { pThis->pwszOutputDevId, PDMAUDIODIR_OUT }
1485 };
1486 for (unsigned i = 0; i < RT_ELEMENTS(aToCache); i++)
1487 {
1488 PCRTUTF16 pwszDevId = aToCache[i].pwszDevId;
1489 IMMDevice *pIDevice = NULL;
1490 HRESULT hrc;
1491 if (pwszDevId)
1492 hrc = pThis->pIEnumerator->GetDevice(pwszDevId, &pIDevice);
1493 else
1494 {
1495 hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(aToCache[i].enmDir == PDMAUDIODIR_IN ? eCapture : eRender,
1496 eMultimedia, &pIDevice);
1497 pwszDevId = aToCache[i].enmDir == PDMAUDIODIR_IN ? L"{Default-In}" : L"{Default-Out}";
1498 }
1499 if (SUCCEEDED(hrc))
1500 {
1501 PDMAUDIOSTREAMCFG Cfg = { aToCache[i].enmDir, { PDMAUDIOPLAYBACKDST_INVALID },
1502 PDMAUDIOPCMPROPS_INITIALIZER(2, true, 2, 44100, false) };
1503 Cfg.Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&Cfg.Props, 300);
1504 PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg = drvHostAudioWasCacheLookupOrCreate(pThis, pIDevice, &Cfg);
1505 if (pDevCfg)
1506 drvHostAudioWasCachePutBack(pThis, pDevCfg);
1507
1508 pIDevice->Release();
1509 }
1510 else
1511 LogRelMax(64, ("WasAPI: Failed to open audio device '%ls' (pre-caching): %Rhrc\n", pwszDevId, hrc));
1512 }
1513 Log8Func(("leave\n"));
1514#else
1515 RT_NOREF(pThis);
1516#endif
1517}
1518
1519
1520/*********************************************************************************************************************************
1521* Worker thread *
1522*********************************************************************************************************************************/
1523#if 0
1524
1525/**
1526 * @callback_method_impl{FNRTTHREAD,
1527 * Asynchronous thread for setting up audio client configs.}
1528 */
1529static DECLCALLBACK(int) drvHostWasWorkerThread(RTTHREAD hThreadSelf, void *pvUser)
1530{
1531 PDRVHOSTAUDIOWAS pThis = (PDRVHOSTAUDIOWAS)pvUser;
1532
1533 /*
1534 * We need to set the thread ID so others can post us thread messages.
1535 * And before we signal that we're ready, make sure we've got a message queue.
1536 */
1537 pThis->idWorkerThread = GetCurrentThreadId();
1538 LogFunc(("idWorkerThread=%#x (%u)\n", pThis->idWorkerThread, pThis->idWorkerThread));
1539
1540 MSG Msg;
1541 PeekMessageW(&Msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
1542
1543 int rc = RTThreadUserSignal(hThreadSelf);
1544 AssertRC(rc);
1545
1546 /*
1547 * Message loop.
1548 */
1549 BOOL fRet;
1550 while ((fRet = GetMessageW(&Msg, NULL, 0, 0)) != FALSE)
1551 {
1552 if (fRet != -1)
1553 {
1554 TranslateMessage(&Msg);
1555 Log9Func(("Msg: time=%u: msg=%#x l=%p w=%p for hwnd=%p\n", Msg.time, Msg.message, Msg.lParam, Msg.wParam, Msg.hwnd));
1556 switch (Msg.message)
1557 {
1558 case WM_DRVHOSTAUDIOWAS_PURGE_CACHE:
1559 {
1560 AssertMsgBreak(Msg.wParam == pThis->uWorkerThreadFixedParam, ("%p\n", Msg.wParam));
1561 AssertBreak(Msg.hwnd == NULL);
1562 AssertBreak(Msg.lParam == 0);
1563
1564 drvHostAudioWasCachePurge(pThis, false /*fOnWorker*/);
1565 break;
1566 }
1567
1568 default:
1569 break;
1570 }
1571 DispatchMessageW(&Msg);
1572 }
1573 else
1574 AssertMsgFailed(("GetLastError()=%u\n", GetLastError()));
1575 }
1576
1577 LogFlowFunc(("Pre-quit cache purge...\n"));
1578 drvHostAudioWasCachePurge(pThis, false /*fOnWorker*/);
1579
1580 LogFunc(("Quits\n"));
1581 return VINF_SUCCESS;
1582}
1583#endif
1584
1585
1586/*********************************************************************************************************************************
1587* PDMIHOSTAUDIO *
1588*********************************************************************************************************************************/
1589
1590/**
1591 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
1592 */
1593static DECLCALLBACK(int) drvHostAudioWasHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
1594{
1595 RT_NOREF(pInterface);
1596 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1597 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
1598
1599
1600 /*
1601 * Fill in the config structure.
1602 */
1603 RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "WasAPI");
1604 pBackendCfg->cbStream = sizeof(DRVHOSTAUDIOWASSTREAM);
1605 pBackendCfg->fFlags = PDMAUDIOBACKEND_F_ASYNC_HINT;
1606 pBackendCfg->cMaxStreamsIn = UINT32_MAX;
1607 pBackendCfg->cMaxStreamsOut = UINT32_MAX;
1608
1609 return VINF_SUCCESS;
1610}
1611
1612
1613/**
1614 * Queries information for @a pDevice and adds an entry to the enumeration.
1615 *
1616 * @returns VBox status code.
1617 * @param pDevEnm The enumeration to add the device to.
1618 * @param pIDevice The device.
1619 * @param enmType The type of device.
1620 * @param fDefault Whether it's the default device.
1621 */
1622static int drvHostWasEnumAddDev(PPDMAUDIOHOSTENUM pDevEnm, IMMDevice *pIDevice, EDataFlow enmType, bool fDefault)
1623{
1624 int rc = VINF_SUCCESS; /* ignore most errors */
1625 RT_NOREF(fDefault); /** @todo default device marking/skipping. */
1626
1627 /*
1628 * Gather the necessary properties.
1629 */
1630 IPropertyStore *pProperties = NULL;
1631 HRESULT hrc = pIDevice->OpenPropertyStore(STGM_READ, &pProperties);
1632 if (SUCCEEDED(hrc))
1633 {
1634 /* Get the friendly name (string). */
1635 PROPVARIANT VarName;
1636 PropVariantInit(&VarName);
1637 hrc = pProperties->GetValue(PKEY_Device_FriendlyName, &VarName);
1638 if (SUCCEEDED(hrc))
1639 {
1640 /* Get the device ID (string). */
1641 LPWSTR pwszDevId = NULL;
1642 hrc = pIDevice->GetId(&pwszDevId);
1643 if (SUCCEEDED(hrc))
1644 {
1645 size_t const cwcDevId = RTUtf16Len(pwszDevId);
1646
1647 /* Get the device format (blob). */
1648 PROPVARIANT VarFormat;
1649 PropVariantInit(&VarFormat);
1650 hrc = pProperties->GetValue(PKEY_AudioEngine_DeviceFormat, &VarFormat);
1651 if (SUCCEEDED(hrc))
1652 {
1653 WAVEFORMATEX const * const pFormat = (WAVEFORMATEX const *)VarFormat.blob.pBlobData;
1654 AssertPtr(pFormat); /* Observed sometimes being NULL on windows 7 sp1. */
1655
1656 /*
1657 * Create a enumeration entry for it.
1658 */
1659 size_t const cbId = RTUtf16CalcUtf8Len(pwszDevId) + 1;
1660 size_t const cbName = RTUtf16CalcUtf8Len(VarName.pwszVal) + 1;
1661 size_t const cbDev = RT_ALIGN_Z( RT_OFFSETOF(DRVHOSTAUDIOWASDEV, wszDevId)
1662 + (cwcDevId + 1) * sizeof(RTUTF16),
1663 64);
1664 PDRVHOSTAUDIOWASDEV pDev = (PDRVHOSTAUDIOWASDEV)PDMAudioHostDevAlloc(cbDev, cbName, cbId);
1665 if (pDev)
1666 {
1667 pDev->Core.enmType = PDMAUDIODEVICETYPE_BUILTIN;
1668 pDev->Core.enmUsage = enmType == eRender ? PDMAUDIODIR_OUT : PDMAUDIODIR_IN;
1669 if (fDefault)
1670 pDev->Core.fFlags = enmType == eRender ? PDMAUDIOHOSTDEV_F_DEFAULT_OUT : PDMAUDIOHOSTDEV_F_DEFAULT_IN;
1671 if (enmType == eRender)
1672 pDev->Core.cMaxOutputChannels = RT_VALID_PTR(pFormat) ? pFormat->nChannels : 2;
1673 else
1674 pDev->Core.cMaxInputChannels = RT_VALID_PTR(pFormat) ? pFormat->nChannels : 1;
1675
1676 memcpy(pDev->wszDevId, pwszDevId, cwcDevId * sizeof(RTUTF16));
1677 pDev->wszDevId[cwcDevId] = '\0';
1678
1679 Assert(pDev->Core.pszName);
1680 rc = RTUtf16ToUtf8Ex(VarName.pwszVal, RTSTR_MAX, &pDev->Core.pszName, cbName, NULL);
1681 if (RT_SUCCESS(rc))
1682 {
1683 Assert(pDev->Core.pszId);
1684 rc = RTUtf16ToUtf8Ex(pDev->wszDevId, RTSTR_MAX, &pDev->Core.pszId, cbId, NULL);
1685 if (RT_SUCCESS(rc))
1686 PDMAudioHostEnumAppend(pDevEnm, &pDev->Core);
1687 else
1688 PDMAudioHostDevFree(&pDev->Core);
1689 }
1690 else
1691 PDMAudioHostDevFree(&pDev->Core);
1692 }
1693 else
1694 rc = VERR_NO_MEMORY;
1695 PropVariantClear(&VarFormat);
1696 }
1697 else
1698 LogFunc(("Failed to get PKEY_AudioEngine_DeviceFormat: %Rhrc\n", hrc));
1699 CoTaskMemFree(pwszDevId);
1700 }
1701 else
1702 LogFunc(("Failed to get the device ID: %Rhrc\n", hrc));
1703 PropVariantClear(&VarName);
1704 }
1705 else
1706 LogFunc(("Failed to get PKEY_Device_FriendlyName: %Rhrc\n", hrc));
1707 pProperties->Release();
1708 }
1709 else
1710 LogFunc(("OpenPropertyStore failed: %Rhrc\n", hrc));
1711
1712 if (hrc == E_OUTOFMEMORY && RT_SUCCESS_NP(rc))
1713 rc = VERR_NO_MEMORY;
1714 return rc;
1715}
1716
1717
1718/**
1719 * Does a (Re-)enumeration of the host's playback + capturing devices.
1720 *
1721 * @return VBox status code.
1722 * @param pThis The WASAPI host audio driver instance data.
1723 * @param pDevEnm Where to store the enumerated devices.
1724 */
1725static int drvHostWasEnumerateDevices(PDRVHOSTAUDIOWAS pThis, PPDMAUDIOHOSTENUM pDevEnm)
1726{
1727 LogRel2(("WasAPI: Enumerating devices ...\n"));
1728
1729 int rc = VINF_SUCCESS;
1730 for (unsigned idxPass = 0; idxPass < 2 && RT_SUCCESS(rc); idxPass++)
1731 {
1732 EDataFlow const enmType = idxPass == 0 ? EDataFlow::eRender : EDataFlow::eCapture;
1733
1734 /* Get the default device first. */
1735 IMMDevice *pIDefaultDevice = NULL;
1736 HRESULT hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(enmType, eMultimedia, &pIDefaultDevice);
1737 if (SUCCEEDED(hrc))
1738 rc = drvHostWasEnumAddDev(pDevEnm, pIDefaultDevice, enmType, true);
1739 else
1740 pIDefaultDevice = NULL;
1741
1742 /* Enumerate the devices. */
1743 IMMDeviceCollection *pCollection = NULL;
1744 hrc = pThis->pIEnumerator->EnumAudioEndpoints(enmType, DEVICE_STATE_ACTIVE /*| DEVICE_STATE_UNPLUGGED?*/, &pCollection);
1745 if (SUCCEEDED(hrc) && pCollection != NULL)
1746 {
1747 UINT cDevices = 0;
1748 hrc = pCollection->GetCount(&cDevices);
1749 if (SUCCEEDED(hrc))
1750 {
1751 for (UINT idxDevice = 0; idxDevice < cDevices && RT_SUCCESS(rc); idxDevice++)
1752 {
1753 IMMDevice *pIDevice = NULL;
1754 hrc = pCollection->Item(idxDevice, &pIDevice);
1755 if (SUCCEEDED(hrc) && pIDevice)
1756 {
1757 if (pIDevice != pIDefaultDevice)
1758 rc = drvHostWasEnumAddDev(pDevEnm, pIDevice, enmType, false);
1759 pIDevice->Release();
1760 }
1761 }
1762 }
1763 pCollection->Release();
1764 }
1765 else
1766 LogRelMax(10, ("EnumAudioEndpoints(%s) failed: %Rhrc\n", idxPass == 0 ? "output" : "input", hrc));
1767
1768 if (pIDefaultDevice)
1769 pIDefaultDevice->Release();
1770 }
1771
1772 LogRel2(("WasAPI: Enumerating devices done - %u device (%Rrc)\n", pDevEnm->cDevices, rc));
1773 return rc;
1774}
1775
1776
1777/**
1778 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices}
1779 */
1780static DECLCALLBACK(int) drvHostAudioWasHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum)
1781{
1782 PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
1783 AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER);
1784
1785 PDMAudioHostEnumInit(pDeviceEnum);
1786 int rc = drvHostWasEnumerateDevices(pThis, pDeviceEnum);
1787 if (RT_FAILURE(rc))
1788 PDMAudioHostEnumDelete(pDeviceEnum);
1789
1790 LogFlowFunc(("Returning %Rrc\n", rc));
1791 return rc;
1792}
1793
1794
1795/**
1796 * Worker for drvHostAudioWasHA_SetDevice.
1797 */
1798static int drvHostAudioWasSetDeviceWorker(PDRVHOSTAUDIOWAS pThis, const char *pszId, PRTUTF16 *ppwszDevId, IMMDevice **ppIDevice,
1799 EDataFlow enmFlow, PDMAUDIODIR enmDir, const char *pszWhat)
1800{
1801 pThis->pNotifyClient->lockEnter();
1802
1803 /*
1804 * Did anything actually change?
1805 */
1806 if ( (pszId == NULL) != (*ppwszDevId == NULL)
1807 || ( pszId
1808 && RTUtf16ICmpUtf8(*ppwszDevId, pszId) != 0))
1809 {
1810 /*
1811 * Duplicate the ID.
1812 */
1813 PRTUTF16 pwszDevId = NULL;
1814 if (pszId)
1815 {
1816 int rc = RTStrToUtf16(pszId, &pwszDevId);
1817 AssertRCReturnStmt(rc, pThis->pNotifyClient->lockLeave(), rc);
1818 }
1819
1820 /*
1821 * Try get the device.
1822 */
1823 IMMDevice *pIDevice = NULL;
1824 HRESULT hrc;
1825 if (pwszDevId)
1826 hrc = pThis->pIEnumerator->GetDevice(pwszDevId, &pIDevice);
1827 else
1828 hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(enmFlow, eMultimedia, &pIDevice);
1829 LogFlowFunc(("Got device %p (%Rhrc)\n", pIDevice, hrc));
1830 if (FAILED(hrc))
1831 {
1832 LogRel(("WasAPI: Failed to get IMMDevice for %s audio device '%s' (SetDevice): %Rhrc\n",
1833 pszWhat, pszId ? pszId : "{default}", hrc));
1834 pIDevice = NULL;
1835 }
1836
1837 /*
1838 * Make the switch.
1839 */
1840 LogRel(("PulseAudio: Changing %s device: '%ls' -> '%s'\n",
1841 pszWhat, *ppwszDevId ? *ppwszDevId : L"{Default}", pszId ? pszId : "{Default}"));
1842
1843 if (*ppIDevice)
1844 (*ppIDevice)->Release();
1845 *ppIDevice = pIDevice;
1846
1847 RTUtf16Free(*ppwszDevId);
1848 *ppwszDevId = pwszDevId;
1849
1850 /*
1851 * Only notify the driver above us.
1852 */
1853 PPDMIHOSTAUDIOPORT const pIHostAudioPort = pThis->pIHostAudioPort;
1854 pThis->pNotifyClient->lockLeave();
1855
1856 if (pIHostAudioPort)
1857 {
1858 LogFlowFunc(("Notifying parent driver about %s device change...\n", pszWhat));
1859 pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, enmDir, NULL);
1860 }
1861 }
1862 else
1863 {
1864 pThis->pNotifyClient->lockLeave();
1865 LogFunc(("No %s device change\n", pszWhat));
1866 }
1867
1868 return VINF_SUCCESS;
1869}
1870
1871
1872/**
1873 * @interface_method_impl{PDMIHOSTAUDIO,pfnSetDevice}
1874 */
1875static DECLCALLBACK(int) drvHostAudioWasHA_SetDevice(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir, const char *pszId)
1876{
1877 PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
1878
1879 /*
1880 * Validate and normalize input.
1881 */
1882 AssertReturn(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX, VERR_INVALID_PARAMETER);
1883 AssertPtrNullReturn(pszId, VERR_INVALID_POINTER);
1884 if (!pszId || !*pszId)
1885 pszId = NULL;
1886 else
1887 AssertReturn(strlen(pszId) < 1024, VERR_INVALID_NAME);
1888 LogFunc(("enmDir=%d pszId=%s\n", enmDir, pszId));
1889
1890 /*
1891 * Do the updating.
1892 */
1893 if (enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_DUPLEX)
1894 {
1895 int rc = drvHostAudioWasSetDeviceWorker(pThis, pszId, &pThis->pwszInputDevId, &pThis->pIDeviceInput,
1896 eCapture, PDMAUDIODIR_IN, "input");
1897 AssertRCReturn(rc, rc);
1898 }
1899
1900 if (enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX)
1901 {
1902 int rc = drvHostAudioWasSetDeviceWorker(pThis, pszId, &pThis->pwszOutputDevId, &pThis->pIDeviceOutput,
1903 eRender, PDMAUDIODIR_OUT, "output");
1904 AssertRCReturn(rc, rc);
1905 }
1906
1907 return VINF_SUCCESS;
1908}
1909
1910
1911/**
1912 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
1913 */
1914static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostAudioWasHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
1915{
1916 RT_NOREF(pInterface, enmDir);
1917 return PDMAUDIOBACKENDSTS_RUNNING;
1918}
1919
1920
1921/**
1922 * Performs the actual switching of device config.
1923 *
1924 * Worker for drvHostAudioWasDoStreamDevSwitch() and
1925 * drvHostAudioWasHA_StreamNotifyDeviceChanged().
1926 */
1927static void drvHostAudioWasCompleteStreamDevSwitch(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASSTREAM pStreamWas,
1928 PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg)
1929{
1930 RTCritSectEnter(&pStreamWas->CritSect);
1931
1932 /* Do the switch. */
1933 PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfgOld = pStreamWas->pDevCfg;
1934 pStreamWas->pDevCfg = pDevCfg;
1935
1936 /* The new stream is neither started nor draining. */
1937 pStreamWas->fStarted = false;
1938 pStreamWas->fDraining = false;
1939
1940 /* Device switching is done now. */
1941 pStreamWas->fSwitchingDevice = false;
1942
1943 /* Stop the old stream or Reset() will fail when putting it back into the cache. */
1944 if (pStreamWas->fEnabled && pDevCfgOld->pIAudioClient)
1945 pDevCfgOld->pIAudioClient->Stop();
1946
1947 RTCritSectLeave(&pStreamWas->CritSect);
1948
1949 /* Notify DrvAudio. */
1950 pThis->pIHostAudioPort->pfnStreamNotifyDeviceChanged(pThis->pIHostAudioPort, &pStreamWas->Core, false /*fReInit*/);
1951
1952 /* Put the old config back into the cache. */
1953 drvHostAudioWasCachePutBack(pThis, pDevCfgOld);
1954
1955 LogFlowFunc(("returns with '%s' state: %s\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) ));
1956}
1957
1958
1959/**
1960 * Called on a worker thread to initialize a new device config and switch the
1961 * given stream to using it.
1962 *
1963 * @sa drvHostAudioWasHA_StreamNotifyDeviceChanged
1964 */
1965static void drvHostAudioWasDoStreamDevSwitch(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASSTREAM pStreamWas,
1966 PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg)
1967{
1968 /*
1969 * Do the initializing.
1970 */
1971 int rc = drvHostAudioWasCacheInitConfig(pDevCfg);
1972 if (RT_SUCCESS(rc))
1973 drvHostAudioWasCompleteStreamDevSwitch(pThis, pStreamWas, pDevCfg);
1974 else
1975 {
1976 LogRelMax(64, ("WasAPI: Failed to set up new device config '%ls:%s' for stream '%s': %Rrc\n",
1977 pDevCfg->pDevEntry->wszDevId, pDevCfg->szProps, pStreamWas->Cfg.szName, rc));
1978 drvHostAudioWasCacheDestroyDevConfig(pThis, pDevCfg);
1979 pThis->pIHostAudioPort->pfnStreamNotifyDeviceChanged(pThis->pIHostAudioPort, &pStreamWas->Core, true /*fReInit*/);
1980 }
1981}
1982
1983
1984/**
1985 * @interface_method_impl{PDMIHOSTAUDIO,pfnDoOnWorkerThread}
1986 */
1987static DECLCALLBACK(void) drvHostAudioWasHA_DoOnWorkerThread(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1988 uintptr_t uUser, void *pvUser)
1989{
1990 PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
1991 LogFlowFunc(("uUser=%#zx pStream=%p pvUser=%p\n", uUser, pStream, pvUser));
1992
1993 switch (uUser)
1994 {
1995 case DRVHOSTAUDIOWAS_DO_PURGE_CACHE:
1996 Assert(pStream == NULL);
1997 Assert(pvUser == NULL);
1998 Assert(pThis->fCacheEnabled);
1999 drvHostAudioWasCachePurge(pThis, true /*fOnWorker*/);
2000 break;
2001
2002 case DRVHOSTAUDIOWAS_DO_PRUNE_CACHE:
2003 Assert(pStream == NULL);
2004 Assert(pvUser == NULL);
2005 Assert(pThis->fCacheEnabled);
2006 drvHostAudioWasCachePrune(pThis);
2007 break;
2008
2009 case DRVHOSTAUDIOWAS_DO_STREAM_DEV_SWITCH:
2010 AssertPtr(pStream);
2011 AssertPtr(pvUser);
2012 drvHostAudioWasDoStreamDevSwitch(pThis, (PDRVHOSTAUDIOWASSTREAM)pStream, (PDRVHOSTAUDIOWASCACHEDEVCFG)pvUser);
2013 break;
2014
2015 default:
2016 AssertMsgFailedBreak(("%#zx\n", uUser));
2017 }
2018}
2019
2020
2021/**
2022 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamConfigHint}
2023 *
2024 * @note This is called on a DrvAudio worker thread.
2025 */
2026static DECLCALLBACK(void) drvHostAudioWasHA_StreamConfigHint(PPDMIHOSTAUDIO pInterface, PPDMAUDIOSTREAMCFG pCfg)
2027{
2028#if 0 /* disable to test async stream creation. */
2029 PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
2030 LogFlowFunc(("pCfg=%p\n", pCfg));
2031
2032 drvHostWasCacheConfigHinting(pThis, pCfg);
2033#else
2034 RT_NOREF(pInterface, pCfg);
2035#endif
2036}
2037
2038
2039/**
2040 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
2041 */
2042static DECLCALLBACK(int) drvHostAudioWasHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2043 PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
2044{
2045 PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
2046 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2047 AssertPtrReturn(pStreamWas, VERR_INVALID_POINTER);
2048 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
2049 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
2050 AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
2051 Assert(PDMAudioStrmCfgEquals(pCfgReq, pCfgAcq));
2052
2053 const char * const pszStreamType = pCfgReq->enmDir == PDMAUDIODIR_IN ? "capture" : "playback"; RT_NOREF(pszStreamType);
2054 LogFlowFunc(("enmPath=%s '%s'\n", PDMAudioPathGetName(pCfgReq->enmPath), pCfgReq->szName));
2055#if defined(RTLOG_REL_ENABLED) || defined(LOG_ENABLED)
2056 char szTmp[64];
2057#endif
2058 LogRel2(("WasAPI: Opening %s stream '%s' (%s)\n", pCfgReq->szName, pszStreamType,
2059 PDMAudioPropsToString(&pCfgReq->Props, szTmp, sizeof(szTmp))));
2060
2061 RTListInit(&pStreamWas->ListEntry);
2062
2063 /*
2064 * Do configuration conversion.
2065 */
2066 WAVEFORMATEXTENSIBLE WaveFmtExt;
2067 drvHostAudioWasWaveFmtExtFromProps(&pCfgReq->Props, &WaveFmtExt);
2068 LogRel2(("WasAPI: Requested %s format for '%s':\n"
2069 "WasAPI: wFormatTag = %#RX16\n"
2070 "WasAPI: nChannels = %RU16\n"
2071 "WasAPI: nSamplesPerSec = %RU32\n"
2072 "WasAPI: nAvgBytesPerSec = %RU32\n"
2073 "WasAPI: nBlockAlign = %RU16\n"
2074 "WasAPI: wBitsPerSample = %RU16\n"
2075 "WasAPI: cbSize = %RU16\n"
2076 "WasAPI: cBufferSizeInNtTicks = %RU64\n",
2077 pszStreamType, pCfgReq->szName, WaveFmtExt.Format.wFormatTag, WaveFmtExt.Format.nChannels,
2078 WaveFmtExt.Format.nSamplesPerSec, WaveFmtExt.Format.nAvgBytesPerSec, WaveFmtExt.Format.nBlockAlign,
2079 WaveFmtExt.Format.wBitsPerSample, WaveFmtExt.Format.cbSize,
2080 PDMAudioPropsFramesToNtTicks(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize) ));
2081 if (WaveFmtExt.Format.cbSize != 0)
2082 LogRel2(("WasAPI: dwChannelMask = %#RX32\n"
2083 "WasAPI: wValidBitsPerSample = %RU16\n",
2084 WaveFmtExt.dwChannelMask, WaveFmtExt.Samples.wValidBitsPerSample));
2085
2086 /* Set up the acquired format here as channel count + layout may have
2087 changed and need to be communicated to caller and used in cache lookup. */
2088 *pCfgAcq = *pCfgReq;
2089 if (WaveFmtExt.Format.cbSize != 0)
2090 {
2091 PDMAudioPropsSetChannels(&pCfgAcq->Props, WaveFmtExt.Format.nChannels);
2092 uint8_t idCh = 0;
2093 for (unsigned iBit = 0; iBit < 32 && idCh < WaveFmtExt.Format.nChannels; iBit++)
2094 if (WaveFmtExt.dwChannelMask & RT_BIT_32(iBit))
2095 {
2096 pCfgAcq->Props.aidChannels[idCh] = (unsigned)PDMAUDIOCHANNELID_FIRST_STANDARD + iBit;
2097 idCh++;
2098 }
2099 Assert(idCh == WaveFmtExt.Format.nChannels);
2100 }
2101
2102 /*
2103 * Get the device we're supposed to use.
2104 * (We cache this as it takes ~2ms to get the default device on a random W10 19042 system.)
2105 */
2106 pThis->pNotifyClient->lockEnter();
2107 IMMDevice *pIDevice = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->pIDeviceInput : pThis->pIDeviceOutput;
2108 if (pIDevice)
2109 pIDevice->AddRef();
2110 pThis->pNotifyClient->lockLeave();
2111
2112 PCRTUTF16 pwszDevId = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->pwszInputDevId : pThis->pwszOutputDevId;
2113 PCRTUTF16 const pwszDevIdDesc = pwszDevId ? pwszDevId : pCfgReq->enmDir == PDMAUDIODIR_IN ? L"{Default-In}" : L"{Default-Out}";
2114 if (!pIDevice)
2115 {
2116 /* This might not strictly be necessary anymore, however it shouldn't
2117 hurt and may be useful when using specific devices. */
2118 HRESULT hrc;
2119 if (pwszDevId)
2120 hrc = pThis->pIEnumerator->GetDevice(pwszDevId, &pIDevice);
2121 else
2122 hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(pCfgReq->enmDir == PDMAUDIODIR_IN ? eCapture : eRender,
2123 eMultimedia, &pIDevice);
2124 LogFlowFunc(("Got device %p (%Rhrc)\n", pIDevice, hrc));
2125 if (FAILED(hrc))
2126 {
2127 LogRelMax(64, ("WasAPI: Failed to open audio %s device '%ls': %Rhrc\n", pszStreamType, pwszDevIdDesc, hrc));
2128 return VERR_AUDIO_STREAM_COULD_NOT_CREATE;
2129 }
2130 }
2131
2132 /*
2133 * Ask the cache to retrieve or instantiate the requested configuration.
2134 */
2135 /** @todo make it return a status code too and retry if the default device
2136 * was invalidated/changed while we where working on it here. */
2137 PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg = NULL;
2138 int rc = drvHostAudioWasCacheLookupOrCreate(pThis, pIDevice, pCfgAcq, false /*fOnWorker*/, &pDevCfg);
2139
2140 pIDevice->Release();
2141 pIDevice = NULL;
2142
2143 if (pDevCfg && RT_SUCCESS(rc))
2144 {
2145 pStreamWas->pDevCfg = pDevCfg;
2146
2147 pCfgAcq->Props = pDevCfg->Props;
2148 pCfgAcq->Backend.cFramesBufferSize = pDevCfg->cFramesBufferSize;
2149 pCfgAcq->Backend.cFramesPeriod = pDevCfg->cFramesPeriod;
2150 pCfgAcq->Backend.cFramesPreBuffering = pCfgReq->Backend.cFramesPreBuffering * pDevCfg->cFramesBufferSize
2151 / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
2152
2153 PDMAudioStrmCfgCopy(&pStreamWas->Cfg, pCfgAcq);
2154
2155 /* Finally, the critical section. */
2156 int rc2 = RTCritSectInit(&pStreamWas->CritSect);
2157 if (RT_SUCCESS(rc2))
2158 {
2159 RTCritSectRwEnterExcl(&pThis->CritSectStreamList);
2160 RTListAppend(&pThis->StreamHead, &pStreamWas->ListEntry);
2161 RTCritSectRwLeaveExcl(&pThis->CritSectStreamList);
2162
2163 if (pStreamWas->pDevCfg->pIAudioClient != NULL)
2164 {
2165 LogFlowFunc(("returns VINF_SUCCESS\n", rc));
2166 return VINF_SUCCESS;
2167 }
2168 LogFlowFunc(("returns VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED\n", rc));
2169 return VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED;
2170 }
2171
2172 LogRelMax(64, ("WasAPI: Failed to create critical section for stream.\n"));
2173 drvHostAudioWasCachePutBack(pThis, pDevCfg);
2174 pStreamWas->pDevCfg = NULL;
2175 }
2176 else
2177 LogRelMax(64, ("WasAPI: Failed to setup %s on audio device '%ls' (%Rrc).\n", pszStreamType, pwszDevIdDesc, rc));
2178
2179 LogFlowFunc(("returns %Rrc\n", rc));
2180 return rc;
2181}
2182
2183
2184/**
2185 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamInitAsync}
2186 */
2187static DECLCALLBACK(int) drvHostAudioWasHA_StreamInitAsync(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2188 bool fDestroyed)
2189{
2190 RT_NOREF(pInterface);
2191 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2192 AssertPtrReturn(pStreamWas, VERR_INVALID_POINTER);
2193 LogFlowFunc(("Stream '%s'%s\n", pStreamWas->Cfg.szName, fDestroyed ? " - destroyed!" : ""));
2194
2195 /*
2196 * Assert sane preconditions for this call.
2197 */
2198 AssertPtrReturn(pStreamWas->Core.pStream, VERR_INTERNAL_ERROR);
2199 AssertPtrReturn(pStreamWas->pDevCfg, VERR_INTERNAL_ERROR_2);
2200 AssertPtrReturn(pStreamWas->pDevCfg->pDevEntry, VERR_INTERNAL_ERROR_3);
2201 AssertPtrReturn(pStreamWas->pDevCfg->pDevEntry->pIDevice, VERR_INTERNAL_ERROR_4);
2202 AssertReturn(pStreamWas->pDevCfg->pDevEntry->enmDir == pStreamWas->Core.pStream->Cfg.enmDir, VERR_INTERNAL_ERROR_4);
2203 AssertReturn(pStreamWas->pDevCfg->pIAudioClient == NULL, VERR_INTERNAL_ERROR_5);
2204 AssertReturn(pStreamWas->pDevCfg->pIAudioRenderClient == NULL, VERR_INTERNAL_ERROR_5);
2205 AssertReturn(pStreamWas->pDevCfg->pIAudioCaptureClient == NULL, VERR_INTERNAL_ERROR_5);
2206
2207 /*
2208 * Do the job.
2209 */
2210 int rc;
2211 if (!fDestroyed)
2212 rc = drvHostAudioWasCacheInitConfig(pStreamWas->pDevCfg);
2213 else
2214 {
2215 AssertReturn(pStreamWas->pDevCfg->rcSetup == VERR_AUDIO_STREAM_INIT_IN_PROGRESS, VERR_INTERNAL_ERROR_2);
2216 pStreamWas->pDevCfg->rcSetup = VERR_WRONG_ORDER;
2217 rc = VINF_SUCCESS;
2218 }
2219
2220 LogFlowFunc(("returns %Rrc (%s)\n", rc, pStreamWas->Cfg.szName));
2221 return rc;
2222}
2223
2224
2225/**
2226 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
2227 */
2228static DECLCALLBACK(int) drvHostAudioWasHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2229 bool fImmediate)
2230{
2231 PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
2232 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2233 AssertPtrReturn(pStreamWas, VERR_INVALID_POINTER);
2234 LogFlowFunc(("Stream '%s'\n", pStreamWas->Cfg.szName));
2235 RT_NOREF(fImmediate);
2236 HRESULT hrc;
2237
2238 if (RTCritSectIsInitialized(&pStreamWas->CritSect))
2239 {
2240 RTCritSectRwEnterExcl(&pThis->CritSectStreamList);
2241 RTListNodeRemove(&pStreamWas->ListEntry);
2242 RTCritSectRwLeaveExcl(&pThis->CritSectStreamList);
2243
2244 RTCritSectDelete(&pStreamWas->CritSect);
2245 }
2246
2247 if (pStreamWas->fStarted && pStreamWas->pDevCfg && pStreamWas->pDevCfg->pIAudioClient)
2248 {
2249 hrc = pStreamWas->pDevCfg->pIAudioClient->Stop();
2250 LogFunc(("Stop('%s') -> %Rhrc\n", pStreamWas->Cfg.szName, hrc));
2251 pStreamWas->fStarted = false;
2252 }
2253
2254 if (pStreamWas->cFramesCaptureToRelease)
2255 {
2256 hrc = pStreamWas->pDevCfg->pIAudioCaptureClient->ReleaseBuffer(0);
2257 Log4Func(("Releasing capture buffer (%#x frames): %Rhrc\n", pStreamWas->cFramesCaptureToRelease, hrc));
2258 pStreamWas->cFramesCaptureToRelease = 0;
2259 pStreamWas->pbCapture = NULL;
2260 pStreamWas->cbCapture = 0;
2261 }
2262
2263 if (pStreamWas->pDevCfg)
2264 {
2265 drvHostAudioWasCachePutBack(pThis, pStreamWas->pDevCfg);
2266 pStreamWas->pDevCfg = NULL;
2267 }
2268
2269 LogFlowFunc(("returns\n"));
2270 return VINF_SUCCESS;
2271}
2272
2273
2274/**
2275 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamNotifyDeviceChanged}
2276 */
2277static DECLCALLBACK(void) drvHostAudioWasHA_StreamNotifyDeviceChanged(PPDMIHOSTAUDIO pInterface,
2278 PPDMAUDIOBACKENDSTREAM pStream, void *pvUser)
2279{
2280 PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
2281 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2282 LogFlowFunc(("pStreamWas=%p (%s)\n", pStreamWas, pStreamWas->Cfg.szName));
2283 RT_NOREF(pvUser);
2284
2285 /*
2286 * See if we've got a cached config for the new device around.
2287 * We ignore this entirely, for now at least, if the device was
2288 * disconnected and there is no replacement.
2289 */
2290 pThis->pNotifyClient->lockEnter();
2291 IMMDevice *pIDevice = pStreamWas->Cfg.enmDir == PDMAUDIODIR_IN ? pThis->pIDeviceInput : pThis->pIDeviceOutput;
2292 if (pIDevice)
2293 pIDevice->AddRef();
2294 pThis->pNotifyClient->lockLeave();
2295 if (pIDevice)
2296 {
2297 PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg = NULL;
2298 int rc = drvHostAudioWasCacheLookupOrCreate(pThis, pIDevice, &pStreamWas->Cfg, false /*fOnWorker*/, &pDevCfg);
2299
2300 pIDevice->Release();
2301 pIDevice = NULL;
2302
2303 /*
2304 * If we have a working audio client, just do the switch.
2305 */
2306 if (RT_SUCCESS(rc) && pDevCfg->pIAudioClient)
2307 {
2308 LogFlowFunc(("New device config is ready already!\n"));
2309 Assert(rc == VINF_SUCCESS);
2310 drvHostAudioWasCompleteStreamDevSwitch(pThis, pStreamWas, pDevCfg);
2311 }
2312 /*
2313 * Otherwise create one asynchronously on a worker thread.
2314 */
2315 else if (RT_SUCCESS(rc))
2316 {
2317 LogFlowFunc(("New device config needs async init ...\n"));
2318 Assert(rc == VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED);
2319
2320 RTCritSectEnter(&pStreamWas->CritSect);
2321 pStreamWas->fSwitchingDevice = true;
2322 RTCritSectLeave(&pStreamWas->CritSect);
2323
2324 pThis->pIHostAudioPort->pfnStreamNotifyPreparingDeviceSwitch(pThis->pIHostAudioPort, &pStreamWas->Core);
2325
2326 rc = pThis->pIHostAudioPort->pfnDoOnWorkerThread(pThis->pIHostAudioPort, &pStreamWas->Core,
2327 DRVHOSTAUDIOWAS_DO_STREAM_DEV_SWITCH, pDevCfg);
2328 AssertRCStmt(rc, drvHostAudioWasDoStreamDevSwitch(pThis, pStreamWas, pDevCfg));
2329 }
2330 else
2331 {
2332 LogRelMax(64, ("WasAPI: Failed to create new device config '%ls:%s' for stream '%s': %Rrc\n",
2333 pDevCfg->pDevEntry->wszDevId, pDevCfg->szProps, pStreamWas->Cfg.szName, rc));
2334
2335 pThis->pIHostAudioPort->pfnStreamNotifyDeviceChanged(pThis->pIHostAudioPort, &pStreamWas->Core, true /*fReInit*/);
2336 }
2337 }
2338 else
2339 LogFlowFunc(("no new device, leaving it as-is\n"));
2340}
2341
2342
2343/**
2344 * Wrapper for starting a stream.
2345 *
2346 * @returns VBox status code.
2347 * @param pThis The WASAPI host audio driver instance data.
2348 * @param pStreamWas The stream.
2349 * @param pszOperation The operation we're doing.
2350 */
2351static int drvHostAudioWasStreamStartWorker(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASSTREAM pStreamWas, const char *pszOperation)
2352{
2353 HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->Start();
2354 LogFlow(("%s: Start(%s) returns %Rhrc\n", pszOperation, pStreamWas->Cfg.szName, hrc));
2355 AssertStmt(hrc != AUDCLNT_E_NOT_STOPPED, hrc = S_OK);
2356 if (SUCCEEDED(hrc))
2357 {
2358 pStreamWas->fStarted = true;
2359 return VINF_SUCCESS;
2360 }
2361
2362 /** @todo try re-setup the stuff on AUDCLNT_E_DEVICEINVALIDATED.
2363 * Need some way of telling the caller (e.g. playback, capture) so they can
2364 * retry what they're doing */
2365 RT_NOREF(pThis);
2366
2367 pStreamWas->fStarted = false;
2368 LogRelMax(64, ("WasAPI: Starting '%s' failed (%s): %Rhrc\n", pStreamWas->Cfg.szName, pszOperation, hrc));
2369 return VERR_AUDIO_STREAM_NOT_READY;
2370}
2371
2372
2373/**
2374 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
2375 */
2376static DECLCALLBACK(int) drvHostAudioWasHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2377{
2378 PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
2379 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2380 LogFlowFunc(("Stream '%s' {%s}\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas)));
2381 HRESULT hrc;
2382 RTCritSectEnter(&pStreamWas->CritSect);
2383
2384 Assert(!pStreamWas->fEnabled);
2385 Assert(!pStreamWas->fStarted);
2386
2387 /*
2388 * We always reset the buffer before enabling the stream (normally never necessary).
2389 */
2390 if (pStreamWas->cFramesCaptureToRelease)
2391 {
2392 hrc = pStreamWas->pDevCfg->pIAudioCaptureClient->ReleaseBuffer(pStreamWas->cFramesCaptureToRelease);
2393 Log4Func(("Releasing capture buffer (%#x frames): %Rhrc\n", pStreamWas->cFramesCaptureToRelease, hrc));
2394 pStreamWas->cFramesCaptureToRelease = 0;
2395 pStreamWas->pbCapture = NULL;
2396 pStreamWas->cbCapture = 0;
2397 }
2398
2399 hrc = pStreamWas->pDevCfg->pIAudioClient->Reset();
2400 if (FAILED(hrc))
2401 LogRelMax(64, ("WasAPI: Stream reset failed when enabling '%s': %Rhrc\n", pStreamWas->Cfg.szName, hrc));
2402 pStreamWas->offInternal = 0;
2403 pStreamWas->fDraining = false;
2404 pStreamWas->fEnabled = true;
2405 pStreamWas->fRestartOnResume = false;
2406
2407 /*
2408 * Input streams will start capturing, while output streams will only start
2409 * playing once we get some audio data to play.
2410 */
2411 int rc = VINF_SUCCESS;
2412 if (pStreamWas->Cfg.enmDir == PDMAUDIODIR_IN)
2413 rc = drvHostAudioWasStreamStartWorker(pThis, pStreamWas, "enable");
2414 else
2415 Assert(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT);
2416
2417 RTCritSectLeave(&pStreamWas->CritSect);
2418 LogFlowFunc(("returns %Rrc\n", rc));
2419 return rc;
2420}
2421
2422
2423/**
2424 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
2425 */
2426static DECLCALLBACK(int) drvHostAudioWasHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2427{
2428 RT_NOREF(pInterface);
2429 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2430 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
2431 pStreamWas->msLastTransfer ? RTTimeMilliTS() - pStreamWas->msLastTransfer : -1,
2432 pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) ));
2433 RTCritSectEnter(&pStreamWas->CritSect);
2434
2435 /*
2436 * Always try stop it (draining or no).
2437 */
2438 pStreamWas->fEnabled = false;
2439 pStreamWas->fRestartOnResume = false;
2440 Assert(!pStreamWas->fDraining || pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT);
2441
2442 int rc = VINF_SUCCESS;
2443 if (pStreamWas->fStarted)
2444 {
2445 HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->Stop();
2446 LogFlowFunc(("Stop(%s) returns %Rhrc\n", pStreamWas->Cfg.szName, hrc));
2447 if (FAILED(hrc))
2448 {
2449 LogRelMax(64, ("WasAPI: Stopping '%s' failed (disable): %Rhrc\n", pStreamWas->Cfg.szName, hrc));
2450 rc = VERR_GENERAL_FAILURE;
2451 }
2452 pStreamWas->fStarted = false;
2453 pStreamWas->fDraining = false;
2454 }
2455
2456 RTCritSectLeave(&pStreamWas->CritSect);
2457 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostWasStreamStatusString(pStreamWas)));
2458 return rc;
2459}
2460
2461
2462/**
2463 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
2464 *
2465 * @note Basically the same as drvHostAudioWasHA_StreamDisable, just w/o the
2466 * buffer resetting and fEnabled change.
2467 */
2468static DECLCALLBACK(int) drvHostAudioWasHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2469{
2470 RT_NOREF(pInterface);
2471 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2472 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
2473 pStreamWas->msLastTransfer ? RTTimeMilliTS() - pStreamWas->msLastTransfer : -1,
2474 pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) ));
2475 RTCritSectEnter(&pStreamWas->CritSect);
2476
2477 /*
2478 * Unless we're draining the stream, stop it if it's started.
2479 */
2480 int rc = VINF_SUCCESS;
2481 if (pStreamWas->fStarted && !pStreamWas->fDraining)
2482 {
2483 pStreamWas->fRestartOnResume = true;
2484
2485 HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->Stop();
2486 LogFlowFunc(("Stop(%s) returns %Rhrc\n", pStreamWas->Cfg.szName, hrc));
2487 if (FAILED(hrc))
2488 {
2489 LogRelMax(64, ("WasAPI: Stopping '%s' failed (pause): %Rhrc\n", pStreamWas->Cfg.szName, hrc));
2490 rc = VERR_GENERAL_FAILURE;
2491 }
2492 pStreamWas->fStarted = false;
2493 }
2494 else
2495 {
2496 pStreamWas->fRestartOnResume = false;
2497 if (pStreamWas->fDraining)
2498 {
2499 LogFunc(("Stream '%s' is draining\n", pStreamWas->Cfg.szName));
2500 Assert(pStreamWas->fStarted);
2501 }
2502 }
2503
2504 RTCritSectLeave(&pStreamWas->CritSect);
2505 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostWasStreamStatusString(pStreamWas)));
2506 return rc;
2507}
2508
2509
2510/**
2511 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
2512 */
2513static DECLCALLBACK(int) drvHostAudioWasHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2514{
2515 PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
2516 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2517 LogFlowFunc(("Stream '%s' {%s}\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas)));
2518 RTCritSectEnter(&pStreamWas->CritSect);
2519
2520 /*
2521 * Resume according to state saved by drvHostAudioWasHA_StreamPause.
2522 */
2523 int rc;
2524 if (pStreamWas->fRestartOnResume)
2525 rc = drvHostAudioWasStreamStartWorker(pThis, pStreamWas, "resume");
2526 else
2527 rc = VINF_SUCCESS;
2528 pStreamWas->fRestartOnResume = false;
2529
2530 RTCritSectLeave(&pStreamWas->CritSect);
2531 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostWasStreamStatusString(pStreamWas)));
2532 return rc;
2533}
2534
2535
2536/**
2537 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
2538 */
2539static DECLCALLBACK(int) drvHostAudioWasHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2540{
2541 RT_NOREF(pInterface);
2542 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2543 AssertReturn(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
2544 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
2545 pStreamWas->msLastTransfer ? RTTimeMilliTS() - pStreamWas->msLastTransfer : -1,
2546 pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) ));
2547
2548 /*
2549 * If the stram was started, calculate when the buffered data has finished
2550 * playing and switch to drain mode. DrvAudio will keep on calling
2551 * pfnStreamPlay with an empty buffer while we're draining, so we'll use
2552 * that for checking the deadline and finally stopping the stream.
2553 */
2554 RTCritSectEnter(&pStreamWas->CritSect);
2555 int rc = VINF_SUCCESS;
2556 if (pStreamWas->fStarted)
2557 {
2558 if (!pStreamWas->fDraining)
2559 {
2560 uint64_t const msNow = RTTimeMilliTS();
2561 uint64_t msDrainDeadline = 0;
2562 UINT32 cFramesPending = 0;
2563 HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->GetCurrentPadding(&cFramesPending);
2564 if (SUCCEEDED(hrc))
2565 msDrainDeadline = msNow
2566 + PDMAudioPropsFramesToMilli(&pStreamWas->Cfg.Props,
2567 RT_MIN(cFramesPending,
2568 pStreamWas->Cfg.Backend.cFramesBufferSize * 2))
2569 + 1 /*fudge*/;
2570 else
2571 {
2572 msDrainDeadline = msNow;
2573 LogRelMax(64, ("WasAPI: GetCurrentPadding fail on '%s' when starting draining: %Rhrc\n",
2574 pStreamWas->Cfg.szName, hrc));
2575 }
2576 pStreamWas->msDrainDeadline = msDrainDeadline;
2577 pStreamWas->fDraining = true;
2578 }
2579 else
2580 LogFlowFunc(("Already draining '%s' ...\n", pStreamWas->Cfg.szName));
2581 }
2582 else
2583 {
2584 LogFlowFunc(("Drain requested for '%s', but not started playback...\n", pStreamWas->Cfg.szName));
2585 AssertStmt(!pStreamWas->fDraining, pStreamWas->fDraining = false);
2586 }
2587 RTCritSectLeave(&pStreamWas->CritSect);
2588
2589 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostWasStreamStatusString(pStreamWas)));
2590 return rc;
2591}
2592
2593
2594/**
2595 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
2596 */
2597static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHostAudioWasHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
2598 PPDMAUDIOBACKENDSTREAM pStream)
2599{
2600 RT_NOREF(pInterface);
2601 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2602 AssertPtrReturn(pStreamWas, PDMHOSTAUDIOSTREAMSTATE_INVALID);
2603
2604 PDMHOSTAUDIOSTREAMSTATE enmState;
2605 AssertPtr(pStreamWas->pDevCfg);
2606 if (pStreamWas->pDevCfg /*paranoia*/)
2607 {
2608 if (RT_SUCCESS(pStreamWas->pDevCfg->rcSetup))
2609 {
2610 if (!pStreamWas->fDraining)
2611 enmState = PDMHOSTAUDIOSTREAMSTATE_OKAY;
2612 else
2613 {
2614 Assert(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT);
2615 enmState = PDMHOSTAUDIOSTREAMSTATE_DRAINING;
2616 }
2617 }
2618 else if ( pStreamWas->pDevCfg->rcSetup == VERR_AUDIO_STREAM_INIT_IN_PROGRESS
2619 || pStreamWas->fSwitchingDevice )
2620 enmState = PDMHOSTAUDIOSTREAMSTATE_INITIALIZING;
2621 else
2622 enmState = PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING;
2623 }
2624 else if (pStreamWas->fSwitchingDevice)
2625 enmState = PDMHOSTAUDIOSTREAMSTATE_INITIALIZING;
2626 else
2627 enmState = PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING;
2628
2629 LogFlowFunc(("returns %d for '%s' {%s}\n", enmState, pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas)));
2630 return enmState;
2631}
2632
2633
2634/**
2635 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending}
2636 */
2637static DECLCALLBACK(uint32_t) drvHostAudioWasHA_StreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2638{
2639 RT_NOREF(pInterface);
2640 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2641 AssertPtrReturn(pStreamWas, 0);
2642 LogFlowFunc(("Stream '%s' {%s}\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas)));
2643 AssertReturn(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT, 0);
2644
2645 uint32_t cbPending = 0;
2646 RTCritSectEnter(&pStreamWas->CritSect);
2647
2648 if ( pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT
2649 && pStreamWas->pDevCfg->pIAudioClient /* paranoia */)
2650 {
2651 if (pStreamWas->fStarted)
2652 {
2653 UINT32 cFramesPending = 0;
2654 HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->GetCurrentPadding(&cFramesPending);
2655 if (SUCCEEDED(hrc))
2656 {
2657 AssertMsg(cFramesPending <= pStreamWas->Cfg.Backend.cFramesBufferSize,
2658 ("cFramesPending=%#x cFramesBufferSize=%#x\n",
2659 cFramesPending, pStreamWas->Cfg.Backend.cFramesBufferSize));
2660 cbPending = PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props, RT_MIN(cFramesPending, VBOX_WASAPI_MAX_PADDING));
2661 }
2662 else
2663 LogRelMax(64, ("WasAPI: GetCurrentPadding failed on '%s': %Rhrc\n", pStreamWas->Cfg.szName, hrc));
2664 }
2665 }
2666
2667 RTCritSectLeave(&pStreamWas->CritSect);
2668
2669 LogFlowFunc(("returns %#x (%u) {%s}\n", cbPending, cbPending, drvHostWasStreamStatusString(pStreamWas)));
2670 return cbPending;
2671}
2672
2673
2674/**
2675 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
2676 */
2677static DECLCALLBACK(uint32_t) drvHostAudioWasHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2678{
2679 RT_NOREF(pInterface);
2680 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2681 AssertPtrReturn(pStreamWas, 0);
2682 LogFlowFunc(("Stream '%s' {%s}\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas)));
2683 Assert(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT);
2684
2685 uint32_t cbWritable = 0;
2686 RTCritSectEnter(&pStreamWas->CritSect);
2687
2688 if ( pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT
2689 && pStreamWas->pDevCfg->pIAudioClient /* paranoia */)
2690 {
2691 UINT32 cFramesPending = 0;
2692 HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->GetCurrentPadding(&cFramesPending);
2693 if (SUCCEEDED(hrc))
2694 {
2695 if (cFramesPending < pStreamWas->Cfg.Backend.cFramesBufferSize)
2696 cbWritable = PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props,
2697 pStreamWas->Cfg.Backend.cFramesBufferSize - cFramesPending);
2698 else if (cFramesPending > pStreamWas->Cfg.Backend.cFramesBufferSize)
2699 {
2700 LogRelMax(64, ("WasAPI: Warning! GetCurrentPadding('%s') return too high: cFramesPending=%#x > cFramesBufferSize=%#x\n",
2701 pStreamWas->Cfg.szName, cFramesPending, pStreamWas->Cfg.Backend.cFramesBufferSize));
2702 AssertMsgFailed(("cFramesPending=%#x > cFramesBufferSize=%#x\n",
2703 cFramesPending, pStreamWas->Cfg.Backend.cFramesBufferSize));
2704 }
2705 }
2706 else
2707 LogRelMax(64, ("WasAPI: GetCurrentPadding failed on '%s': %Rhrc\n", pStreamWas->Cfg.szName, hrc));
2708 }
2709
2710 RTCritSectLeave(&pStreamWas->CritSect);
2711
2712 LogFlowFunc(("returns %#x (%u) {%s}\n", cbWritable, cbWritable, drvHostWasStreamStatusString(pStreamWas)));
2713 return cbWritable;
2714}
2715
2716
2717/**
2718 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
2719 */
2720static DECLCALLBACK(int) drvHostAudioWasHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2721 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
2722{
2723 PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
2724 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2725 AssertPtrReturn(pStreamWas, VERR_INVALID_POINTER);
2726 AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
2727 if (cbBuf)
2728 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2729 Assert(PDMAudioPropsIsSizeAligned(&pStreamWas->Cfg.Props, cbBuf));
2730
2731 RTCritSectEnter(&pStreamWas->CritSect);
2732 if (pStreamWas->fEnabled)
2733 { /* likely */ }
2734 else
2735 {
2736 RTCritSectLeave(&pStreamWas->CritSect);
2737 *pcbWritten = 0;
2738 LogFunc(("Skipping %#x byte write to disabled stream {%s}\n", cbBuf, drvHostWasStreamStatusString(pStreamWas)));
2739 return VINF_SUCCESS;
2740 }
2741 Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) ));
2742
2743 /*
2744 * Transfer loop.
2745 */
2746 int rc = VINF_SUCCESS;
2747 uint32_t cReInits = 0;
2748 uint32_t cbWritten = 0;
2749 while (cbBuf > 0)
2750 {
2751 AssertBreakStmt(pStreamWas->pDevCfg && pStreamWas->pDevCfg->pIAudioRenderClient && pStreamWas->pDevCfg->pIAudioClient,
2752 rc = VERR_AUDIO_STREAM_NOT_READY);
2753
2754 /*
2755 * Figure out how much we can possibly write.
2756 */
2757 UINT32 cFramesPending = 0;
2758 uint32_t cbWritable = 0;
2759 HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->GetCurrentPadding(&cFramesPending);
2760 if (SUCCEEDED(hrc))
2761 cbWritable = PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props,
2762 pStreamWas->Cfg.Backend.cFramesBufferSize
2763 - RT_MIN(cFramesPending, pStreamWas->Cfg.Backend.cFramesBufferSize));
2764 else
2765 {
2766 LogRelMax(64, ("WasAPI: GetCurrentPadding(%s) failed during playback: %Rhrc (@%#RX64)\n",
2767 pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal));
2768 /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */
2769 rc = VERR_AUDIO_STREAM_NOT_READY;
2770 break;
2771 }
2772 if (cbWritable <= PDMAudioPropsFrameSize(&pStreamWas->Cfg.Props))
2773 break;
2774
2775 uint32_t const cbToWrite = PDMAudioPropsFloorBytesToFrame(&pStreamWas->Cfg.Props, RT_MIN(cbWritable, cbBuf));
2776 uint32_t const cFramesToWrite = PDMAudioPropsBytesToFrames(&pStreamWas->Cfg.Props, cbToWrite);
2777 Assert(PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props, cFramesToWrite) == cbToWrite);
2778 Log3Func(("@%#RX64: cFramesPending=%#x -> cbWritable=%#x cbToWrite=%#x cFramesToWrite=%#x {%s}\n",
2779 pStreamWas->offInternal, cFramesPending, cbWritable, cbToWrite, cFramesToWrite,
2780 drvHostWasStreamStatusString(pStreamWas) ));
2781
2782 /*
2783 * Get the buffer, copy the data into it, and relase it back to the WAS machinery.
2784 */
2785 BYTE *pbData = NULL;
2786 hrc = pStreamWas->pDevCfg->pIAudioRenderClient->GetBuffer(cFramesToWrite, &pbData);
2787 if (SUCCEEDED(hrc))
2788 {
2789 memcpy(pbData, pvBuf, cbToWrite);
2790 hrc = pStreamWas->pDevCfg->pIAudioRenderClient->ReleaseBuffer(cFramesToWrite, 0 /*fFlags*/);
2791 if (SUCCEEDED(hrc))
2792 {
2793 /*
2794 * Before we advance the buffer position (so we can resubmit it
2795 * after re-init), make sure we've successfully started stream.
2796 */
2797 if (pStreamWas->fStarted)
2798 { }
2799 else
2800 {
2801 rc = drvHostAudioWasStreamStartWorker(pThis, pStreamWas, "play");
2802 if (rc == VINF_SUCCESS)
2803 { /* likely */ }
2804 else if (RT_SUCCESS(rc) && ++cReInits < 5)
2805 continue; /* re-submit buffer after re-init */
2806 else
2807 break;
2808 }
2809
2810 /* advance. */
2811 pvBuf = (uint8_t *)pvBuf + cbToWrite;
2812 cbBuf -= cbToWrite;
2813 cbWritten += cbToWrite;
2814 pStreamWas->offInternal += cbToWrite;
2815 }
2816 else
2817 {
2818 LogRelMax(64, ("WasAPI: ReleaseBuffer(%#x) failed on '%s' during playback: %Rhrc (@%#RX64)\n",
2819 cFramesToWrite, pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal));
2820 /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */
2821 rc = VERR_AUDIO_STREAM_NOT_READY;
2822 break;
2823 }
2824 }
2825 else
2826 {
2827 LogRelMax(64, ("WasAPI: GetBuffer(%#x) failed on '%s' during playback: %Rhrc (@%#RX64)\n",
2828 cFramesToWrite, pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal));
2829 /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */
2830 rc = VERR_AUDIO_STREAM_NOT_READY;
2831 break;
2832 }
2833 }
2834
2835 /*
2836 * Do draining deadline processing.
2837 */
2838 uint64_t const msNow = RTTimeMilliTS();
2839 if ( !pStreamWas->fDraining
2840 || msNow < pStreamWas->msDrainDeadline)
2841 { /* likely */ }
2842 else
2843 {
2844 LogRel2(("WasAPI: Stopping draining of '%s' {%s} ...\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas)));
2845 HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->Stop();
2846 if (FAILED(hrc))
2847 LogRelMax(64, ("WasAPI: Failed to stop draining stream '%s': %Rhrc\n", pStreamWas->Cfg.szName, hrc));
2848 pStreamWas->fDraining = false;
2849 pStreamWas->fStarted = false;
2850 pStreamWas->fEnabled = false;
2851 }
2852
2853 /*
2854 * Done.
2855 */
2856 uint64_t const msPrev = pStreamWas->msLastTransfer; RT_NOREF(msPrev);
2857 if (cbWritten)
2858 pStreamWas->msLastTransfer = msNow;
2859
2860 RTCritSectLeave(&pStreamWas->CritSect);
2861
2862 *pcbWritten = cbWritten;
2863 if (RT_SUCCESS(rc) || !cbWritten)
2864 { }
2865 else
2866 {
2867 LogFlowFunc(("Suppressing %Rrc to report %#x bytes written\n", rc, cbWritten));
2868 rc = VINF_SUCCESS;
2869 }
2870 LogFlowFunc(("@%#RX64: rc=%Rrc cbWritten=%RU32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamWas->offInternal, rc, cbWritten,
2871 msPrev ? msNow - msPrev : 0, msPrev, pStreamWas->msLastTransfer, drvHostWasStreamStatusString(pStreamWas) ));
2872 return rc;
2873}
2874
2875
2876/**
2877 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
2878 */
2879static DECLCALLBACK(uint32_t) drvHostAudioWasHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2880{
2881 RT_NOREF(pInterface);
2882 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2883 AssertPtrReturn(pStreamWas, 0);
2884 Assert(pStreamWas->Cfg.enmDir == PDMAUDIODIR_IN);
2885
2886 uint32_t cbReadable = 0;
2887 RTCritSectEnter(&pStreamWas->CritSect);
2888
2889 if (pStreamWas->pDevCfg->pIAudioCaptureClient /* paranoia */)
2890 {
2891 UINT32 cFramesPending = 0;
2892 HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->GetCurrentPadding(&cFramesPending);
2893 if (SUCCEEDED(hrc))
2894 {
2895 /* An unreleased buffer is included in the pending frame count, so subtract
2896 whatever we've got hanging around since the previous pfnStreamCapture call. */
2897 AssertMsgStmt(cFramesPending >= pStreamWas->cFramesCaptureToRelease,
2898 ("%#x vs %#x\n", cFramesPending, pStreamWas->cFramesCaptureToRelease),
2899 cFramesPending = pStreamWas->cFramesCaptureToRelease);
2900 cFramesPending -= pStreamWas->cFramesCaptureToRelease;
2901
2902 /* Add what we've got left in said buffer. */
2903 uint32_t cFramesCurPacket = PDMAudioPropsBytesToFrames(&pStreamWas->Cfg.Props, pStreamWas->cbCapture);
2904 cFramesPending += cFramesCurPacket;
2905
2906 /* Paranoia: Make sure we don't exceed the buffer size. */
2907 AssertMsgStmt(cFramesPending <= pStreamWas->Cfg.Backend.cFramesBufferSize,
2908 ("cFramesPending=%#x cFramesCaptureToRelease=%#x cFramesCurPacket=%#x cFramesBufferSize=%#x\n",
2909 cFramesPending, pStreamWas->cFramesCaptureToRelease, cFramesCurPacket,
2910 pStreamWas->Cfg.Backend.cFramesBufferSize),
2911 cFramesPending = pStreamWas->Cfg.Backend.cFramesBufferSize);
2912
2913 cbReadable = PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props, cFramesPending);
2914 }
2915 else
2916 LogRelMax(64, ("WasAPI: GetCurrentPadding failed on '%s': %Rhrc\n", pStreamWas->Cfg.szName, hrc));
2917 }
2918
2919 RTCritSectLeave(&pStreamWas->CritSect);
2920
2921 LogFlowFunc(("returns %#x (%u) {%s}\n", cbReadable, cbReadable, drvHostWasStreamStatusString(pStreamWas)));
2922 return cbReadable;
2923}
2924
2925
2926/**
2927 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
2928 */
2929static DECLCALLBACK(int) drvHostAudioWasHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2930 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
2931{
2932 RT_NOREF(pInterface); //PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
2933 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2934 AssertPtrReturn(pStreamWas, 0);
2935 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2936 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
2937 AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
2938 Assert(PDMAudioPropsIsSizeAligned(&pStreamWas->Cfg.Props, cbBuf));
2939
2940 RTCritSectEnter(&pStreamWas->CritSect);
2941 if (pStreamWas->fEnabled)
2942 { /* likely */ }
2943 else
2944 {
2945 RTCritSectLeave(&pStreamWas->CritSect);
2946 *pcbRead = 0;
2947 LogFunc(("Skipping %#x byte read from disabled stream {%s}\n", cbBuf, drvHostWasStreamStatusString(pStreamWas)));
2948 return VINF_SUCCESS;
2949 }
2950 Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) ));
2951
2952
2953 /*
2954 * Transfer loop.
2955 */
2956 int rc = VINF_SUCCESS;
2957 uint32_t cbRead = 0;
2958 uint32_t const cbFrame = PDMAudioPropsFrameSize(&pStreamWas->Cfg.Props);
2959 while (cbBuf >= cbFrame)
2960 {
2961 AssertBreakStmt(pStreamWas->pDevCfg->pIAudioCaptureClient && pStreamWas->pDevCfg->pIAudioClient, rc = VERR_AUDIO_STREAM_NOT_READY);
2962
2963 /*
2964 * Anything pending from last call?
2965 * (This is rather similar to the Pulse interface.)
2966 */
2967 if (pStreamWas->cFramesCaptureToRelease)
2968 {
2969 uint32_t const cbToCopy = RT_MIN(pStreamWas->cbCapture, cbBuf);
2970 memcpy(pvBuf, pStreamWas->pbCapture, cbToCopy);
2971 pvBuf = (uint8_t *)pvBuf + cbToCopy;
2972 cbBuf -= cbToCopy;
2973 cbRead += cbToCopy;
2974 pStreamWas->offInternal += cbToCopy;
2975 pStreamWas->pbCapture += cbToCopy;
2976 pStreamWas->cbCapture -= cbToCopy;
2977 if (!pStreamWas->cbCapture)
2978 {
2979 HRESULT hrc = pStreamWas->pDevCfg->pIAudioCaptureClient->ReleaseBuffer(pStreamWas->cFramesCaptureToRelease);
2980 Log4Func(("@%#RX64: Releasing capture buffer (%#x frames): %Rhrc\n",
2981 pStreamWas->offInternal, pStreamWas->cFramesCaptureToRelease, hrc));
2982 if (SUCCEEDED(hrc))
2983 {
2984 pStreamWas->cFramesCaptureToRelease = 0;
2985 pStreamWas->pbCapture = NULL;
2986 }
2987 else
2988 {
2989 LogRelMax(64, ("WasAPI: ReleaseBuffer(%s) failed during capture: %Rhrc (@%#RX64)\n",
2990 pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal));
2991 /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */
2992 rc = VERR_AUDIO_STREAM_NOT_READY;
2993 break;
2994 }
2995 }
2996 if (cbBuf < cbFrame)
2997 break;
2998 }
2999
3000 /*
3001 * Figure out if there is any data available to be read now. (Docs hint that we can not
3002 * skip this and go straight for GetBuffer or we risk getting unwritten buffer space back).
3003 */
3004 UINT32 cFramesCaptured = 0;
3005 HRESULT hrc = pStreamWas->pDevCfg->pIAudioCaptureClient->GetNextPacketSize(&cFramesCaptured);
3006 if (SUCCEEDED(hrc))
3007 {
3008 if (!cFramesCaptured)
3009 break;
3010 }
3011 else
3012 {
3013 LogRelMax(64, ("WasAPI: GetNextPacketSize(%s) failed during capture: %Rhrc (@%#RX64)\n",
3014 pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal));
3015 /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */
3016 rc = VERR_AUDIO_STREAM_NOT_READY;
3017 break;
3018 }
3019
3020 /*
3021 * Get the buffer.
3022 */
3023 cFramesCaptured = 0;
3024 UINT64 uQpsNtTicks = 0;
3025 UINT64 offDevice = 0;
3026 DWORD fBufFlags = 0;
3027 BYTE *pbData = NULL;
3028 hrc = pStreamWas->pDevCfg->pIAudioCaptureClient->GetBuffer(&pbData, &cFramesCaptured, &fBufFlags, &offDevice, &uQpsNtTicks);
3029 Log4Func(("@%#RX64: GetBuffer -> %Rhrc pbData=%p cFramesCaptured=%#x fBufFlags=%#x offDevice=%#RX64 uQpcNtTicks=%#RX64\n",
3030 pStreamWas->offInternal, hrc, pbData, cFramesCaptured, fBufFlags, offDevice, uQpsNtTicks));
3031 if (SUCCEEDED(hrc))
3032 {
3033 Assert(cFramesCaptured < VBOX_WASAPI_MAX_PADDING);
3034 pStreamWas->pbCapture = pbData;
3035 pStreamWas->cFramesCaptureToRelease = cFramesCaptured;
3036 pStreamWas->cbCapture = PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props, cFramesCaptured);
3037 /* Just loop and re-use the copying code above. Can optimize later. */
3038 }
3039 else
3040 {
3041 LogRelMax(64, ("WasAPI: GetBuffer() failed on '%s' during capture: %Rhrc (@%#RX64)\n",
3042 pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal));
3043 /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */
3044 rc = VERR_AUDIO_STREAM_NOT_READY;
3045 break;
3046 }
3047 }
3048
3049 /*
3050 * Done.
3051 */
3052 uint64_t const msPrev = pStreamWas->msLastTransfer; RT_NOREF(msPrev);
3053 uint64_t const msNow = RTTimeMilliTS();
3054 if (cbRead)
3055 pStreamWas->msLastTransfer = msNow;
3056
3057 RTCritSectLeave(&pStreamWas->CritSect);
3058
3059 *pcbRead = cbRead;
3060 if (RT_SUCCESS(rc) || !cbRead)
3061 { }
3062 else
3063 {
3064 LogFlowFunc(("Suppressing %Rrc to report %#x bytes read\n", rc, cbRead));
3065 rc = VINF_SUCCESS;
3066 }
3067 LogFlowFunc(("@%#RX64: rc=%Rrc cbRead=%#RX32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamWas->offInternal, rc, cbRead,
3068 msPrev ? msNow - msPrev : 0, msPrev, pStreamWas->msLastTransfer, drvHostWasStreamStatusString(pStreamWas) ));
3069 return rc;
3070}
3071
3072
3073/*********************************************************************************************************************************
3074* PDMDRVINS::IBase Interface *
3075*********************************************************************************************************************************/
3076
3077/**
3078 * @callback_method_impl{PDMIBASE,pfnQueryInterface}
3079 */
3080static DECLCALLBACK(void *) drvHostAudioWasQueryInterface(PPDMIBASE pInterface, const char *pszIID)
3081{
3082 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
3083 PDRVHOSTAUDIOWAS pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTAUDIOWAS);
3084
3085 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
3086 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
3087 return NULL;
3088}
3089
3090
3091/*********************************************************************************************************************************
3092* PDMDRVREG Interface *
3093*********************************************************************************************************************************/
3094
3095/**
3096 * @callback_method_impl{FNPDMDRVDESTRUCT, pfnDestruct}
3097 */
3098static DECLCALLBACK(void) drvHostAudioWasPowerOff(PPDMDRVINS pDrvIns)
3099{
3100 PDRVHOSTAUDIOWAS pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTAUDIOWAS);
3101
3102 /*
3103 * Start purging the cache asynchronously before we get to destruct.
3104 * This might speed up VM shutdown a tiny fraction and also stress
3105 * the shutting down of the thread pool a little.
3106 */
3107#if 0
3108 if (pThis->hWorkerThread != NIL_RTTHREAD)
3109 {
3110 BOOL fRc = PostThreadMessageW(pThis->idWorkerThread, WM_DRVHOSTAUDIOWAS_PURGE_CACHE, pThis->uWorkerThreadFixedParam, 0);
3111 LogFlowFunc(("Posted WM_DRVHOSTAUDIOWAS_PURGE_CACHE: %d\n", fRc));
3112 Assert(fRc); RT_NOREF(fRc);
3113 }
3114#else
3115 if ( pThis->fCacheEnabled
3116 && !RTListIsEmpty(&pThis->CacheHead) && pThis->pIHostAudioPort)
3117 {
3118 int rc = RTSemEventMultiCreate(&pThis->hEvtCachePurge);
3119 if (RT_SUCCESS(rc))
3120 {
3121 rc = pThis->pIHostAudioPort->pfnDoOnWorkerThread(pThis->pIHostAudioPort, NULL/*pStream*/,
3122 DRVHOSTAUDIOWAS_DO_PURGE_CACHE, NULL /*pvUser*/);
3123 if (RT_FAILURE(rc))
3124 {
3125 LogFunc(("pfnDoOnWorkerThread/DRVHOSTAUDIOWAS_DO_PURGE_CACHE failed: %Rrc\n", rc));
3126 RTSemEventMultiDestroy(pThis->hEvtCachePurge);
3127 pThis->hEvtCachePurge = NIL_RTSEMEVENTMULTI;
3128 }
3129 }
3130 }
3131#endif
3132
3133 /*
3134 * Deregister the notification client to reduce the risk of notifications
3135 * comming in while we're being detatched or the VM is being destroyed.
3136 */
3137 if (pThis->pNotifyClient)
3138 {
3139 pThis->pNotifyClient->notifyDriverDestroyed();
3140 pThis->pIEnumerator->UnregisterEndpointNotificationCallback(pThis->pNotifyClient);
3141 pThis->pNotifyClient->Release();
3142 pThis->pNotifyClient = NULL;
3143 }
3144}
3145
3146
3147/**
3148 * @callback_method_impl{FNPDMDRVDESTRUCT, pfnDestruct}
3149 */
3150static DECLCALLBACK(void) drvHostAudioWasDestruct(PPDMDRVINS pDrvIns)
3151{
3152 PDRVHOSTAUDIOWAS pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTAUDIOWAS);
3153 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
3154 LogFlowFuncEnter();
3155
3156 /*
3157 * Release the notification client first.
3158 */
3159 if (pThis->pNotifyClient)
3160 {
3161 pThis->pNotifyClient->notifyDriverDestroyed();
3162 pThis->pIEnumerator->UnregisterEndpointNotificationCallback(pThis->pNotifyClient);
3163 pThis->pNotifyClient->Release();
3164 pThis->pNotifyClient = NULL;
3165 }
3166
3167#if 0
3168 if (pThis->hWorkerThread != NIL_RTTHREAD)
3169 {
3170 BOOL fRc = PostThreadMessageW(pThis->idWorkerThread, WM_QUIT, 0, 0);
3171 Assert(fRc); RT_NOREF(fRc);
3172
3173 int rc = RTThreadWait(pThis->hWorkerThread, RT_MS_15SEC, NULL);
3174 AssertRC(rc);
3175 }
3176#endif
3177
3178 if (pThis->fCacheEnabled)
3179 {
3180 if (RTCritSectIsInitialized(&pThis->CritSectCache))
3181 {
3182 drvHostAudioWasCachePurge(pThis, false /*fOnWorker*/);
3183 if (pThis->hEvtCachePurge != NIL_RTSEMEVENTMULTI)
3184 RTSemEventMultiWait(pThis->hEvtCachePurge, RT_MS_30SEC);
3185 RTCritSectDelete(&pThis->CritSectCache);
3186 }
3187
3188 if (pThis->hEvtCachePurge != NIL_RTSEMEVENTMULTI)
3189 {
3190 RTSemEventMultiDestroy(pThis->hEvtCachePurge);
3191 pThis->hEvtCachePurge = NIL_RTSEMEVENTMULTI;
3192 }
3193 }
3194 else
3195 {
3196 Assert(pThis->cCacheEntriesIn == 0);
3197 Assert(pThis->cCacheEntriesOut == 0);
3198 }
3199
3200 if (pThis->pIEnumerator)
3201 {
3202 uint32_t cRefs = pThis->pIEnumerator->Release(); RT_NOREF(cRefs);
3203 LogFlowFunc(("cRefs=%d\n", cRefs));
3204 }
3205
3206 if (pThis->pIDeviceOutput)
3207 {
3208 pThis->pIDeviceOutput->Release();
3209 pThis->pIDeviceOutput = NULL;
3210 }
3211
3212 if (pThis->pIDeviceInput)
3213 {
3214 pThis->pIDeviceInput->Release();
3215 pThis->pIDeviceInput = NULL;
3216 }
3217
3218
3219 if (RTCritSectRwIsInitialized(&pThis->CritSectStreamList))
3220 RTCritSectRwDelete(&pThis->CritSectStreamList);
3221
3222 LogFlowFuncLeave();
3223}
3224
3225
3226/**
3227 * @callback_method_impl{FNPDMDRVCONSTRUCT, pfnConstruct}
3228 */
3229static DECLCALLBACK(int) drvHostAudioWasConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
3230{
3231 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
3232 PDRVHOSTAUDIOWAS pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTAUDIOWAS);
3233 PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
3234 RT_NOREF(fFlags, pCfg);
3235
3236 /*
3237 * Init basic data members and interfaces.
3238 */
3239 pThis->pDrvIns = pDrvIns;
3240 pThis->hEvtCachePurge = NIL_RTSEMEVENTMULTI;
3241#if 0
3242 pThis->hWorkerThread = NIL_RTTHREAD;
3243 pThis->idWorkerThread = 0;
3244#endif
3245 RTListInit(&pThis->StreamHead);
3246 RTListInit(&pThis->CacheHead);
3247 /* IBase */
3248 pDrvIns->IBase.pfnQueryInterface = drvHostAudioWasQueryInterface;
3249 /* IHostAudio */
3250 pThis->IHostAudio.pfnGetConfig = drvHostAudioWasHA_GetConfig;
3251 pThis->IHostAudio.pfnGetDevices = drvHostAudioWasHA_GetDevices;
3252 pThis->IHostAudio.pfnSetDevice = drvHostAudioWasHA_SetDevice;
3253 pThis->IHostAudio.pfnGetStatus = drvHostAudioWasHA_GetStatus;
3254 pThis->IHostAudio.pfnDoOnWorkerThread = drvHostAudioWasHA_DoOnWorkerThread;
3255 pThis->IHostAudio.pfnStreamConfigHint = drvHostAudioWasHA_StreamConfigHint;
3256 pThis->IHostAudio.pfnStreamCreate = drvHostAudioWasHA_StreamCreate;
3257 pThis->IHostAudio.pfnStreamInitAsync = drvHostAudioWasHA_StreamInitAsync;
3258 pThis->IHostAudio.pfnStreamDestroy = drvHostAudioWasHA_StreamDestroy;
3259 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = drvHostAudioWasHA_StreamNotifyDeviceChanged;
3260 pThis->IHostAudio.pfnStreamEnable = drvHostAudioWasHA_StreamEnable;
3261 pThis->IHostAudio.pfnStreamDisable = drvHostAudioWasHA_StreamDisable;
3262 pThis->IHostAudio.pfnStreamPause = drvHostAudioWasHA_StreamPause;
3263 pThis->IHostAudio.pfnStreamResume = drvHostAudioWasHA_StreamResume;
3264 pThis->IHostAudio.pfnStreamDrain = drvHostAudioWasHA_StreamDrain;
3265 pThis->IHostAudio.pfnStreamGetState = drvHostAudioWasHA_StreamGetState;
3266 pThis->IHostAudio.pfnStreamGetPending = drvHostAudioWasHA_StreamGetPending;
3267 pThis->IHostAudio.pfnStreamGetWritable = drvHostAudioWasHA_StreamGetWritable;
3268 pThis->IHostAudio.pfnStreamPlay = drvHostAudioWasHA_StreamPlay;
3269 pThis->IHostAudio.pfnStreamGetReadable = drvHostAudioWasHA_StreamGetReadable;
3270 pThis->IHostAudio.pfnStreamCapture = drvHostAudioWasHA_StreamCapture;
3271
3272 /*
3273 * Validate and read the configuration.
3274 */
3275 PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "VmName|VmUuid|InputDeviceID|OutputDeviceID|CacheEnabled", "");
3276
3277 char szTmp[1024];
3278 int rc = pHlp->pfnCFGMQueryStringDef(pCfg, "InputDeviceID", szTmp, sizeof(szTmp), "");
3279 AssertMsgRCReturn(rc, ("Confguration error: Failed to read \"InputDeviceID\" as string: rc=%Rrc\n", rc), rc);
3280 if (szTmp[0])
3281 {
3282 rc = RTStrToUtf16(szTmp, &pThis->pwszInputDevId);
3283 AssertRCReturn(rc, rc);
3284 }
3285
3286 rc = pHlp->pfnCFGMQueryStringDef(pCfg, "OutputDeviceID", szTmp, sizeof(szTmp), "");
3287 AssertMsgRCReturn(rc, ("Confguration error: Failed to read \"OutputDeviceID\" as string: rc=%Rrc\n", rc), rc);
3288 if (szTmp[0])
3289 {
3290 rc = RTStrToUtf16(szTmp, &pThis->pwszOutputDevId);
3291 AssertRCReturn(rc, rc);
3292 }
3293
3294 /* Caching is enabled by default. */
3295 rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "CacheEnabled", &pThis->fCacheEnabled, true);
3296 AssertMsgRCReturn(rc, ("Confguration error: Failed to read \"CacheEnabled\" as boolean: rc=%Rrc\n", rc), rc);
3297
3298 AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
3299 ("Configuration error: Not possible to attach anything to this driver!\n"),
3300 VERR_PDM_DRVINS_NO_ATTACH);
3301
3302 LogRel2(("WasAPI: Caching device configuration entries is %s\n", pThis->fCacheEnabled ? "enabled" : "disabled"));
3303
3304 /*
3305 * Initialize the critical sections early.
3306 */
3307 rc = RTCritSectRwInit(&pThis->CritSectStreamList);
3308 AssertRCReturn(rc, rc);
3309
3310 if (pThis->fCacheEnabled)
3311 {
3312 rc = RTCritSectInit(&pThis->CritSectCache);
3313 AssertRCReturn(rc, rc);
3314 }
3315
3316 /*
3317 * Create an enumerator instance that we can get the default devices from
3318 * as well as do enumeration thru.
3319 */
3320 HRESULT hrc = CoCreateInstance(__uuidof(MMDeviceEnumerator), 0, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator),
3321 (void **)&pThis->pIEnumerator);
3322 if (FAILED(hrc))
3323 {
3324 pThis->pIEnumerator = NULL;
3325 LogRel(("WasAPI: Failed to create an MMDeviceEnumerator object: %Rhrc\n", hrc));
3326 return VERR_AUDIO_BACKEND_INIT_FAILED;
3327 }
3328 AssertPtr(pThis->pIEnumerator);
3329
3330 /*
3331 * Resolve the interface to the driver above us.
3332 */
3333 pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT);
3334 AssertPtrReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE);
3335
3336 /*
3337 * Instantiate and register the notification client with the enumerator.
3338 *
3339 * Failure here isn't considered fatal at this time as we'll just miss
3340 * default device changes.
3341 */
3342#ifdef RT_EXCEPTIONS_ENABLED
3343 try { pThis->pNotifyClient = new DrvHostAudioWasMmNotifyClient(pThis); }
3344 catch (std::bad_alloc &) { return VERR_NO_MEMORY; }
3345#else
3346 pThis->pNotifyClient = new DrvHostAudioWasMmNotifyClient(pThis);
3347 AssertReturn(pThis->pNotifyClient, VERR_NO_MEMORY);
3348#endif
3349 rc = pThis->pNotifyClient->init();
3350 AssertRCReturn(rc, rc);
3351
3352 hrc = pThis->pIEnumerator->RegisterEndpointNotificationCallback(pThis->pNotifyClient);
3353 AssertMsg(SUCCEEDED(hrc), ("%Rhrc\n", hrc));
3354 if (FAILED(hrc))
3355 {
3356 LogRel(("WasAPI: RegisterEndpointNotificationCallback failed: %Rhrc (ignored)\n"
3357 "WasAPI: Warning! Will not be able to detect default device changes!\n"));
3358 pThis->pNotifyClient->notifyDriverDestroyed();
3359 pThis->pNotifyClient->Release();
3360 pThis->pNotifyClient = NULL;
3361 }
3362
3363 /*
3364 * Retrieve the input and output device.
3365 */
3366 IMMDevice *pIDeviceInput = NULL;
3367 if (pThis->pwszInputDevId)
3368 hrc = pThis->pIEnumerator->GetDevice(pThis->pwszInputDevId, &pIDeviceInput);
3369 else
3370 hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, &pIDeviceInput);
3371 if (SUCCEEDED(hrc))
3372 LogRel(("WasAPI: Input device is: %ls (iface %p)\n",
3373 pThis->pwszInputDevId ? pThis->pwszInputDevId : L"{Default}", pIDeviceInput));
3374 else
3375 {
3376 LogRel(("WasAPI: Failed to get audio input device '%ls': %Rhrc\n",
3377 pThis->pwszInputDevId ? pThis->pwszInputDevId : L"{Default}", hrc));
3378 pIDeviceInput = NULL;
3379 }
3380
3381 IMMDevice *pIDeviceOutput = NULL;
3382 if (pThis->pwszOutputDevId)
3383 hrc = pThis->pIEnumerator->GetDevice(pThis->pwszOutputDevId, &pIDeviceOutput);
3384 else
3385 hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &pIDeviceOutput);
3386 if (SUCCEEDED(hrc))
3387 LogRel(("WasAPI: Output device is: %ls (iface %p)\n",
3388 pThis->pwszOutputDevId ? pThis->pwszOutputDevId : L"{Default}", pIDeviceOutput));
3389 else
3390 {
3391 LogRel(("WasAPI: Failed to get audio output device '%ls': %Rhrc\n",
3392 pThis->pwszOutputDevId ? pThis->pwszOutputDevId : L"{Default}", hrc));
3393 pIDeviceOutput = NULL;
3394 }
3395
3396 /* Carefully place them in the instance data: */
3397 pThis->pNotifyClient->lockEnter();
3398
3399 if (pThis->pIDeviceInput)
3400 pThis->pIDeviceInput->Release();
3401 pThis->pIDeviceInput = pIDeviceInput;
3402
3403 if (pThis->pIDeviceOutput)
3404 pThis->pIDeviceOutput->Release();
3405 pThis->pIDeviceOutput = pIDeviceOutput;
3406
3407 pThis->pNotifyClient->lockLeave();
3408
3409#if 0
3410 /*
3411 * Create the worker thread. This thread has a message loop and will be
3412 * signalled by DrvHostAudioWasMmNotifyClient while the VM is paused/whatever,
3413 * so better make it a regular thread rather than PDM thread.
3414 */
3415 pThis->uWorkerThreadFixedParam = (WPARAM)RTRandU64();
3416 rc = RTThreadCreateF(&pThis->hWorkerThread, drvHostWasWorkerThread, pThis, 0 /*cbStack*/, RTTHREADTYPE_DEFAULT,
3417 RTTHREADFLAGS_WAITABLE | RTTHREADFLAGS_COM_MTA, "WasWork%u", pDrvIns->iInstance);
3418 AssertRCReturn(rc, rc);
3419
3420 rc = RTThreadUserWait(pThis->hWorkerThread, RT_MS_10SEC);
3421 AssertRC(rc);
3422#endif
3423
3424 /*
3425 * Prime the cache.
3426 */
3427 if (pThis->fCacheEnabled)
3428 drvHostAudioWasCacheFill(pThis);
3429
3430 return VINF_SUCCESS;
3431}
3432
3433
3434/**
3435 * PDM driver registration for WasAPI.
3436 */
3437const PDMDRVREG g_DrvHostAudioWas =
3438{
3439 /* u32Version */
3440 PDM_DRVREG_VERSION,
3441 /* szName */
3442 "HostAudioWas",
3443 /* szRCMod */
3444 "",
3445 /* szR0Mod */
3446 "",
3447 /* pszDescription */
3448 "Windows Audio Session API (WASAPI) host audio driver",
3449 /* fFlags */
3450 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
3451 /* fClass. */
3452 PDM_DRVREG_CLASS_AUDIO,
3453 /* cMaxInstances */
3454 ~0U,
3455 /* cbInstance */
3456 sizeof(DRVHOSTAUDIOWAS),
3457 /* pfnConstruct */
3458 drvHostAudioWasConstruct,
3459 /* pfnDestruct */
3460 drvHostAudioWasDestruct,
3461 /* pfnRelocate */
3462 NULL,
3463 /* pfnIOCtl */
3464 NULL,
3465 /* pfnPowerOn */
3466 NULL,
3467 /* pfnReset */
3468 NULL,
3469 /* pfnSuspend */
3470 NULL,
3471 /* pfnResume */
3472 NULL,
3473 /* pfnAttach */
3474 NULL,
3475 /* pfnDetach */
3476 NULL,
3477 /* pfnPowerOff */
3478 drvHostAudioWasPowerOff,
3479 /* pfnSoftReset */
3480 NULL,
3481 /* u32EndVersion */
3482 PDM_DRVREG_VERSION
3483};
3484
Note: See TracBrowser for help on using the repository browser.

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