VirtualBox

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

Last change on this file was 108549, checked in by vboxsync, 4 weeks ago

Audio/DrvHostAudioWasApi: Follow-up fix for r167721 -- check the passed-in device interface before trying to look it up in the cache. Remove stale / non-functioning device interfaces from the cache and try re-creating it. Logging tweaks to hopefully better find the cause and recovery gaps [Doxygen fix]. bugref:10844

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 137.4 KB
Line 
1/* $Id: DrvHostAudioWasApi.cpp 108549 2025-03-13 14:08: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 (if \a fUseCache is \c true), returning without holding it!
1156 */
1157static int drvHostAudioWasCacheLookupOrCreateConfig(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASCACHEDEV pDevEntry,
1158 PCPDMAUDIOSTREAMCFG pCfgReq, bool fUseCache, bool fOnWorker,
1159 PDRVHOSTAUDIOWASCACHEDEVCFG *ppDevCfg)
1160{
1161 PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg;
1162
1163 /*
1164 * Check if we've got a matching config.
1165 */
1166 if (fUseCache)
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 (fUseCache)
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 LogRel2(("WasAPI: Looking up or creating cache entry (caching is set to %s, iface %p)\n",
1260 pThis->fCacheEnabled ? "enabled" : "disabled", pIDevice));
1261
1262 /*
1263 * Get the device ID so we can perform the lookup.
1264 */
1265 int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1266 LPWSTR pwszDevId = NULL;
1267 HRESULT hrc = pIDevice->GetId(&pwszDevId);
1268 if (SUCCEEDED(hrc))
1269 {
1270 bool fLookupOrCreateInCache = pThis->fCacheEnabled; /* Whether to lookup and/or create the device in our cache. */
1271
1272 /* First, try retrieving the current device state of the passed device here as a yet another meassure for
1273 * AUDCLNT_E_DEVICE_INVALIDATED errors. We try not to cache malfunctioning devices here. See @bugref{10844} */
1274 DWORD dwState;
1275 hrc = pIDevice->GetState(&dwState);
1276 if (SUCCEEDED(hrc))
1277 {
1278 if (dwState != DEVICE_STATE_ACTIVE)
1279 {
1280 LogRel2(("WasAPI: Device '%ls': Is in non-active state (state is %#x, iface %p)\n",
1281 pwszDevId, drvHostAudioWasMMDeviceStateToString(dwState), pIDevice));
1282 fLookupOrCreateInCache = false;
1283 }
1284 }
1285 else
1286 {
1287 LogRel2(("WasAPI: Device '%ls': Unable to retrieve state (hr=%#x, iface %p)\n", pwszDevId, hrc, pIDevice));
1288 fLookupOrCreateInCache = false;
1289 }
1290
1291 PDRVHOSTAUDIOWASCACHEDEV pDevEntry = NULL;
1292 size_t const cwcDevId = RTUtf16Len(pwszDevId);
1293
1294 if (fLookupOrCreateInCache)
1295 {
1296 LogRel2(("WasAPI: Checking for cached device '%ls' ...\n", pwszDevId));
1297
1298#define DEVICE_STALE_OR_INVALID_BREAK(a_LogRel2What) \
1299 { \
1300 LogRel2(a_LogRel2What); \
1301 LogRel(("WasAPI: Stale or invalid audio interface '%ls' detected!\n", pDevEntry->wszDevId)); \
1302 rc = VERR_AUDIO_STREAM_NOT_READY; \
1303 break; \
1304 }
1305 /*
1306 * The cache has two levels, so first the device entry.
1307 */
1308 PDRVHOSTAUDIOWASCACHEDEV pDevEntryNext;
1309 RTCritSectEnter(&pThis->CritSectCache);
1310 RTListForEachSafe(&pThis->CacheHead, pDevEntry, pDevEntryNext, DRVHOSTAUDIOWASCACHEDEV, ListEntry)
1311 {
1312 if ( pDevEntry->cwcDevId == cwcDevId
1313 && pDevEntry->enmDir == pCfgReq->enmDir
1314 && RTUtf16Cmp(pDevEntry->wszDevId, pwszDevId) == 0)
1315 {
1316 /*
1317 * Cache hit.
1318 * First we need to check if the cached device interface is in a working (active) shape.
1319 */
1320 AssertPtrBreakStmt(pDevEntry->pIDevice, rc = VERR_AUDIO_STREAM_NOT_READY); /* Paranoia. */
1321 hrc = pDevEntry->pIDevice->GetState(&dwState);
1322 if (SUCCEEDED(hrc))
1323 {
1324 if (dwState != DEVICE_STATE_ACTIVE)
1325 DEVICE_STALE_OR_INVALID_BREAK(("WasAPI: Cache hit for device '%ls': Is in non-active state (state is %s)\n",
1326 pDevEntry->wszDevId, drvHostAudioWasMMDeviceStateToString(dwState)));
1327 }
1328 else
1329 DEVICE_STALE_OR_INVALID_BREAK(("WasAPI: Cache hit for device '%ls': Unable to retrieve state (hr=%#x)\n",
1330 pDevEntry->wszDevId, hrc));
1331 /*
1332 * Next we now need to also check if the device interface we want to look up
1333 * actually matches the one we have in the cache entry.
1334 *
1335 * If it doesn't, bail out and add a new device entry to the cache with the new interface below then.
1336 *
1337 * This is needed when switching audio interfaces and the device interface becomes invalid via
1338 * AUDCLNT_E_DEVICE_INVALIDATED. See @bugref{10503}
1339 */
1340 if (pDevEntry->pIDevice != pIDevice)
1341 DEVICE_STALE_OR_INVALID_BREAK(("WasAPI: Cache hit for device '%ls': Stale interface detected (new: %p, old: %p)\n",
1342 pDevEntry->wszDevId, pIDevice, pDevEntry->pIDevice));
1343
1344 LogRel2(("WasAPI: Cache hit for device '%ls' (iface %p)\n", pwszDevId, pIDevice));
1345
1346 CoTaskMemFree(pwszDevId);
1347 pwszDevId = NULL;
1348
1349 return drvHostAudioWasCacheLookupOrCreateConfig(pThis, pDevEntry, pCfgReq,
1350 true /* fUseCache */, fOnWorker, ppDevCfg);
1351 }
1352 }
1353 RTCritSectLeave(&pThis->CritSectCache);
1354
1355 if (!pDevEntry)
1356 LogRel2(("WasAPI: Cache miss for device '%ls' (iface %p)\n", pwszDevId, pIDevice));
1357
1358#undef DEVICE_STALE_OR_INVALID_BREAK
1359 }
1360
1361 /*
1362 * If we got a stale or somehow other invalid cache entry, remove it first.
1363 */
1364 if ( pThis->fCacheEnabled
1365 && pDevEntry /* Cache hit? */
1366 && rc == VERR_AUDIO_STREAM_NOT_READY)
1367 {
1368 LogRel2(("WasAPI: Removing stale device '%ls' from cache (iface %p)\n", pDevEntry->wszDevId, pDevEntry->pIDevice));
1369
1370 RTCritSectEnter(&pThis->CritSectCache);
1371 RTListNodeRemove(&pDevEntry->ListEntry);
1372 RTCritSectLeave(&pThis->CritSectCache);
1373
1374 drvHostAudioWasCacheDestroyDevEntry(pThis, pDevEntry);
1375 pDevEntry = NULL;
1376
1377 rc = VINF_SUCCESS;
1378 }
1379
1380 /*
1381 * Device not in the cache (anymore), (re-)add it.
1382 */
1383 pDevEntry = (PDRVHOSTAUDIOWASCACHEDEV)RTMemAllocZVar(RT_UOFFSETOF_DYN(DRVHOSTAUDIOWASCACHEDEV, wszDevId[cwcDevId + 1]));
1384 if (pDevEntry)
1385 {
1386 if (pThis->fCacheEnabled)
1387 pIDevice->AddRef();
1388 pDevEntry->pIDevice = pIDevice;
1389 pDevEntry->enmDir = pCfgReq->enmDir;
1390 pDevEntry->cwcDevId = cwcDevId;
1391#if 0
1392 pDevEntry->fSupportsAutoConvertPcm = -1;
1393 pDevEntry->fSupportsSrcDefaultQuality = -1;
1394#endif
1395 RTListInit(&pDevEntry->ConfigList);
1396 memcpy(pDevEntry->wszDevId, pwszDevId, cwcDevId * sizeof(RTUTF16));
1397 pDevEntry->wszDevId[cwcDevId] = '\0';
1398
1399 CoTaskMemFree(pwszDevId);
1400 pwszDevId = NULL;
1401
1402 if (fLookupOrCreateInCache)
1403 {
1404 /* Make sure to enter the cache's critsect again for the following calls. */
1405 RTCritSectEnter(&pThis->CritSectCache);
1406
1407 /*
1408 * Before adding the device to the cache, check that someone didn't race us adding it.
1409 */
1410 PDRVHOSTAUDIOWASCACHEDEV pDevEntry2;
1411 RTListForEach(&pThis->CacheHead, pDevEntry2, DRVHOSTAUDIOWASCACHEDEV, ListEntry)
1412 {
1413 if ( pDevEntry2->cwcDevId == cwcDevId
1414 /* Note: We have to compare the device interface here as well, as a cached device entry might
1415 * have a stale audio interface for the same device. In such a case a new device entry will be created below. */
1416 && pDevEntry2->pIDevice == pIDevice
1417 && pDevEntry2->enmDir == pCfgReq->enmDir
1418 && RTUtf16Cmp(pDevEntry2->wszDevId, pDevEntry->wszDevId) == 0)
1419 {
1420 pIDevice->Release();
1421 RTMemFree(pDevEntry);
1422 pDevEntry = NULL;
1423
1424 LogRel2(("WasAPI: Lost race adding device '%ls' (node %p)\n", pDevEntry2->wszDevId, pDevEntry2));
1425 return drvHostAudioWasCacheLookupOrCreateConfig(pThis, pDevEntry2, pCfgReq,
1426 true /* fUseCache */, fOnWorker, ppDevCfg);
1427 }
1428 }
1429 RTListPrepend(&pThis->CacheHead, &pDevEntry->ListEntry);
1430
1431 LogRel2(("WasAPI: Added device '%ls' to cache (node %p)\n", pDevEntry->wszDevId, pDevEntry));
1432 }
1433 else
1434 LogRel2(("WasAPI: Not caching device '%ls' (iface %p)\n", pDevEntry->wszDevId, pIDevice));
1435
1436 return drvHostAudioWasCacheLookupOrCreateConfig(pThis, pDevEntry, pCfgReq,
1437 fLookupOrCreateInCache /* fUseCache */, fOnWorker, ppDevCfg);
1438 }
1439 CoTaskMemFree(pwszDevId);
1440 }
1441 else
1442 LogRelMax(64, ("WasAPI: GetId failed (lookup): %Rhrc\n", hrc));
1443 return rc;
1444}
1445
1446
1447/**
1448 * Return the given config to the cache.
1449 *
1450 * @param pThis The WASAPI host audio driver instance data.
1451 * @param pDevCfg The device config to put back.
1452 */
1453static void drvHostAudioWasCachePutBack(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg)
1454{
1455 /*
1456 * Reset the audio client to see that it works and to make sure it's in a sensible state.
1457 */
1458 HRESULT hrc = pDevCfg->pIAudioClient ? pDevCfg->pIAudioClient->Reset()
1459 : pDevCfg->rcSetup == VERR_AUDIO_STREAM_INIT_IN_PROGRESS ? S_OK : E_FAIL;
1460 if (SUCCEEDED(hrc))
1461 {
1462 if (pThis->fCacheEnabled)
1463 {
1464 Log8Func(("Putting %p/'%s' back\n", pDevCfg, pDevCfg->szProps));
1465 RTCritSectEnter(&pThis->CritSectCache);
1466 RTListAppend(&pDevCfg->pDevEntry->ConfigList, &pDevCfg->ListEntry);
1467 uint32_t const cEntries = pDevCfg->pDevEntry->enmDir == PDMAUDIODIR_IN ? pThis->cCacheEntriesIn : pThis->cCacheEntriesOut;
1468 RTCritSectLeave(&pThis->CritSectCache);
1469
1470 /* Trigger pruning if we're over the threshold. */
1471 if (cEntries > VBOX_WASAPI_MAX_TOTAL_CONFIG_ENTRIES)
1472 {
1473 LogFlowFunc(("Trigger cache pruning.\n"));
1474 int rc2 = pThis->pIHostAudioPort->pfnDoOnWorkerThread(pThis->pIHostAudioPort, NULL /*pStream*/,
1475 DRVHOSTAUDIOWAS_DO_PRUNE_CACHE, NULL /*pvUser*/);
1476 AssertRCStmt(rc2, drvHostAudioWasCachePrune(pThis));
1477 }
1478 }
1479 }
1480 else
1481 {
1482 Log8Func(("IAudioClient::Reset failed (%Rhrc) on %p/'%s', destroying it.\n", hrc, pDevCfg, pDevCfg->szProps));
1483 drvHostAudioWasCacheDestroyDevConfig(pThis, pDevCfg);
1484 }
1485}
1486
1487
1488static void drvHostWasCacheConfigHinting(PDRVHOSTAUDIOWAS pThis, PPDMAUDIOSTREAMCFG pCfgReq, bool fOnWorker)
1489{
1490 /*
1491 * Get the device.
1492 */
1493 pThis->pNotifyClient->lockEnter();
1494 IMMDevice *pIDevice = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->pIDeviceInput : pThis->pIDeviceOutput;
1495 if (pIDevice)
1496 pIDevice->AddRef();
1497 pThis->pNotifyClient->lockLeave();
1498 if (pIDevice)
1499 {
1500 /*
1501 * Look up the config and put it back.
1502 */
1503 PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg = NULL;
1504 int rc = drvHostAudioWasCacheLookupOrCreate(pThis, pIDevice, pCfgReq, fOnWorker, &pDevCfg);
1505 LogFlowFunc(("pDevCfg=%p rc=%Rrc\n", pDevCfg, rc));
1506 if (pDevCfg && RT_SUCCESS(rc))
1507 drvHostAudioWasCachePutBack(pThis, pDevCfg);
1508 pIDevice->Release();
1509 }
1510}
1511
1512
1513/**
1514 * Prefills the cache.
1515 *
1516 * @param pThis The WASAPI host audio driver instance data.
1517 */
1518static void drvHostAudioWasCacheFill(PDRVHOSTAUDIOWAS pThis)
1519{
1520#if 0 /* we don't have the buffer config nor do we really know which frequences to expect */
1521 Log8Func(("enter\n"));
1522 struct
1523 {
1524 PCRTUTF16 pwszDevId;
1525 PDMAUDIODIR enmDir;
1526 } aToCache[] =
1527 {
1528 { pThis->pwszInputDevId, PDMAUDIODIR_IN },
1529 { pThis->pwszOutputDevId, PDMAUDIODIR_OUT }
1530 };
1531 for (unsigned i = 0; i < RT_ELEMENTS(aToCache); i++)
1532 {
1533 PCRTUTF16 pwszDevId = aToCache[i].pwszDevId;
1534 IMMDevice *pIDevice = NULL;
1535 HRESULT hrc;
1536 if (pwszDevId)
1537 hrc = pThis->pIEnumerator->GetDevice(pwszDevId, &pIDevice);
1538 else
1539 {
1540 hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(aToCache[i].enmDir == PDMAUDIODIR_IN ? eCapture : eRender,
1541 eMultimedia, &pIDevice);
1542 pwszDevId = aToCache[i].enmDir == PDMAUDIODIR_IN ? L"{Default-In}" : L"{Default-Out}";
1543 }
1544 if (SUCCEEDED(hrc))
1545 {
1546 PDMAUDIOSTREAMCFG Cfg = { aToCache[i].enmDir, { PDMAUDIOPLAYBACKDST_INVALID },
1547 PDMAUDIOPCMPROPS_INITIALIZER(2, true, 2, 44100, false) };
1548 Cfg.Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&Cfg.Props, 300);
1549 PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg = drvHostAudioWasCacheLookupOrCreate(pThis, pIDevice, &Cfg);
1550 if (pDevCfg)
1551 drvHostAudioWasCachePutBack(pThis, pDevCfg);
1552
1553 pIDevice->Release();
1554 }
1555 else
1556 LogRelMax(64, ("WasAPI: Failed to open audio device '%ls' (pre-caching): %Rhrc\n", pwszDevId, hrc));
1557 }
1558 Log8Func(("leave\n"));
1559#else
1560 RT_NOREF(pThis);
1561#endif
1562}
1563
1564
1565/*********************************************************************************************************************************
1566* Worker thread *
1567*********************************************************************************************************************************/
1568#if 0
1569
1570/**
1571 * @callback_method_impl{FNRTTHREAD,
1572 * Asynchronous thread for setting up audio client configs.}
1573 */
1574static DECLCALLBACK(int) drvHostWasWorkerThread(RTTHREAD hThreadSelf, void *pvUser)
1575{
1576 PDRVHOSTAUDIOWAS pThis = (PDRVHOSTAUDIOWAS)pvUser;
1577
1578 /*
1579 * We need to set the thread ID so others can post us thread messages.
1580 * And before we signal that we're ready, make sure we've got a message queue.
1581 */
1582 pThis->idWorkerThread = GetCurrentThreadId();
1583 LogFunc(("idWorkerThread=%#x (%u)\n", pThis->idWorkerThread, pThis->idWorkerThread));
1584
1585 MSG Msg;
1586 PeekMessageW(&Msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
1587
1588 int rc = RTThreadUserSignal(hThreadSelf);
1589 AssertRC(rc);
1590
1591 /*
1592 * Message loop.
1593 */
1594 BOOL fRet;
1595 while ((fRet = GetMessageW(&Msg, NULL, 0, 0)) != FALSE)
1596 {
1597 if (fRet != -1)
1598 {
1599 TranslateMessage(&Msg);
1600 Log9Func(("Msg: time=%u: msg=%#x l=%p w=%p for hwnd=%p\n", Msg.time, Msg.message, Msg.lParam, Msg.wParam, Msg.hwnd));
1601 switch (Msg.message)
1602 {
1603 case WM_DRVHOSTAUDIOWAS_PURGE_CACHE:
1604 {
1605 AssertMsgBreak(Msg.wParam == pThis->uWorkerThreadFixedParam, ("%p\n", Msg.wParam));
1606 AssertBreak(Msg.hwnd == NULL);
1607 AssertBreak(Msg.lParam == 0);
1608
1609 drvHostAudioWasCachePurge(pThis, false /*fOnWorker*/);
1610 break;
1611 }
1612
1613 default:
1614 break;
1615 }
1616 DispatchMessageW(&Msg);
1617 }
1618 else
1619 AssertMsgFailed(("GetLastError()=%u\n", GetLastError()));
1620 }
1621
1622 LogFlowFunc(("Pre-quit cache purge...\n"));
1623 drvHostAudioWasCachePurge(pThis, false /*fOnWorker*/);
1624
1625 LogFunc(("Quits\n"));
1626 return VINF_SUCCESS;
1627}
1628#endif
1629
1630
1631/*********************************************************************************************************************************
1632* PDMIHOSTAUDIO *
1633*********************************************************************************************************************************/
1634
1635/**
1636 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
1637 */
1638static DECLCALLBACK(int) drvHostAudioWasHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
1639{
1640 RT_NOREF(pInterface);
1641 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1642 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
1643
1644
1645 /*
1646 * Fill in the config structure.
1647 */
1648 RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "WasAPI");
1649 pBackendCfg->cbStream = sizeof(DRVHOSTAUDIOWASSTREAM);
1650 pBackendCfg->fFlags = PDMAUDIOBACKEND_F_ASYNC_HINT;
1651 pBackendCfg->cMaxStreamsIn = UINT32_MAX;
1652 pBackendCfg->cMaxStreamsOut = UINT32_MAX;
1653
1654 return VINF_SUCCESS;
1655}
1656
1657
1658/**
1659 * Queries information for @a pDevice and adds an entry to the enumeration.
1660 *
1661 * @returns VBox status code.
1662 * @param pDevEnm The enumeration to add the device to.
1663 * @param pIDevice The device.
1664 * @param enmType The type of device.
1665 * @param fDefault Whether it's the default device.
1666 */
1667static int drvHostWasEnumAddDev(PPDMAUDIOHOSTENUM pDevEnm, IMMDevice *pIDevice, EDataFlow enmType, bool fDefault)
1668{
1669 int rc = VINF_SUCCESS; /* ignore most errors */
1670 RT_NOREF(fDefault); /** @todo default device marking/skipping. */
1671
1672 /*
1673 * Gather the necessary properties.
1674 */
1675 IPropertyStore *pProperties = NULL;
1676 HRESULT hrc = pIDevice->OpenPropertyStore(STGM_READ, &pProperties);
1677 if (SUCCEEDED(hrc))
1678 {
1679 /* Get the friendly name (string). */
1680 PROPVARIANT VarName;
1681 PropVariantInit(&VarName);
1682 hrc = pProperties->GetValue(PKEY_Device_FriendlyName, &VarName);
1683 if (SUCCEEDED(hrc))
1684 {
1685 /* Get the device ID (string). */
1686 LPWSTR pwszDevId = NULL;
1687 hrc = pIDevice->GetId(&pwszDevId);
1688 if (SUCCEEDED(hrc))
1689 {
1690 size_t const cwcDevId = RTUtf16Len(pwszDevId);
1691
1692 /* Get the device format (blob). */
1693 PROPVARIANT VarFormat;
1694 PropVariantInit(&VarFormat);
1695 hrc = pProperties->GetValue(PKEY_AudioEngine_DeviceFormat, &VarFormat);
1696 if (SUCCEEDED(hrc))
1697 {
1698 WAVEFORMATEX const * const pFormat = (WAVEFORMATEX const *)VarFormat.blob.pBlobData;
1699 AssertPtr(pFormat); /* Observed sometimes being NULL on windows 7 sp1. */
1700
1701 /*
1702 * Create a enumeration entry for it.
1703 */
1704 size_t const cbId = RTUtf16CalcUtf8Len(pwszDevId) + 1;
1705 size_t const cbName = RTUtf16CalcUtf8Len(VarName.pwszVal) + 1;
1706 size_t const cbDev = RT_ALIGN_Z( RT_OFFSETOF(DRVHOSTAUDIOWASDEV, wszDevId)
1707 + (cwcDevId + 1) * sizeof(RTUTF16),
1708 64);
1709 PDRVHOSTAUDIOWASDEV pDev = (PDRVHOSTAUDIOWASDEV)PDMAudioHostDevAlloc(cbDev, cbName, cbId);
1710 if (pDev)
1711 {
1712 pDev->Core.enmType = PDMAUDIODEVICETYPE_BUILTIN;
1713 pDev->Core.enmUsage = enmType == eRender ? PDMAUDIODIR_OUT : PDMAUDIODIR_IN;
1714 if (fDefault)
1715 pDev->Core.fFlags = enmType == eRender ? PDMAUDIOHOSTDEV_F_DEFAULT_OUT : PDMAUDIOHOSTDEV_F_DEFAULT_IN;
1716 if (enmType == eRender)
1717 pDev->Core.cMaxOutputChannels = RT_VALID_PTR(pFormat) ? pFormat->nChannels : 2;
1718 else
1719 pDev->Core.cMaxInputChannels = RT_VALID_PTR(pFormat) ? pFormat->nChannels : 1;
1720
1721 memcpy(pDev->wszDevId, pwszDevId, cwcDevId * sizeof(RTUTF16));
1722 pDev->wszDevId[cwcDevId] = '\0';
1723
1724 Assert(pDev->Core.pszName);
1725 rc = RTUtf16ToUtf8Ex(VarName.pwszVal, RTSTR_MAX, &pDev->Core.pszName, cbName, NULL);
1726 if (RT_SUCCESS(rc))
1727 {
1728 Assert(pDev->Core.pszId);
1729 rc = RTUtf16ToUtf8Ex(pDev->wszDevId, RTSTR_MAX, &pDev->Core.pszId, cbId, NULL);
1730 if (RT_SUCCESS(rc))
1731 {
1732 PDMAudioHostEnumAppend(pDevEnm, &pDev->Core);
1733 LogRel2(("WasAPI: Device '%ls': %p\n", pDev->wszDevId, pIDevice));
1734 }
1735 else
1736 PDMAudioHostDevFree(&pDev->Core);
1737 }
1738 else
1739 PDMAudioHostDevFree(&pDev->Core);
1740 }
1741 else
1742 rc = VERR_NO_MEMORY;
1743 PropVariantClear(&VarFormat);
1744 }
1745 else
1746 LogFunc(("Failed to get PKEY_AudioEngine_DeviceFormat: %Rhrc\n", hrc));
1747 CoTaskMemFree(pwszDevId);
1748 }
1749 else
1750 LogFunc(("Failed to get the device ID: %Rhrc\n", hrc));
1751 PropVariantClear(&VarName);
1752 }
1753 else
1754 LogFunc(("Failed to get PKEY_Device_FriendlyName: %Rhrc\n", hrc));
1755 pProperties->Release();
1756 }
1757 else
1758 LogFunc(("OpenPropertyStore failed: %Rhrc\n", hrc));
1759
1760 if (hrc == E_OUTOFMEMORY && RT_SUCCESS_NP(rc))
1761 rc = VERR_NO_MEMORY;
1762 return rc;
1763}
1764
1765
1766/**
1767 * Does a (Re-)enumeration of the host's playback + capturing devices.
1768 *
1769 * @return VBox status code.
1770 * @param pThis The WASAPI host audio driver instance data.
1771 * @param pDevEnm Where to store the enumerated devices.
1772 */
1773static int drvHostWasEnumerateDevices(PDRVHOSTAUDIOWAS pThis, PPDMAUDIOHOSTENUM pDevEnm)
1774{
1775 LogRel2(("WasAPI: Enumerating devices ...\n"));
1776
1777 int rc = VINF_SUCCESS;
1778 for (unsigned idxPass = 0; idxPass < 2 && RT_SUCCESS(rc); idxPass++)
1779 {
1780 EDataFlow const enmType = idxPass == 0 ? EDataFlow::eRender : EDataFlow::eCapture;
1781
1782 /* Get the default device first. */
1783 IMMDevice *pIDefaultDevice = NULL;
1784 HRESULT hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(enmType, eMultimedia, &pIDefaultDevice);
1785 if (SUCCEEDED(hrc))
1786 rc = drvHostWasEnumAddDev(pDevEnm, pIDefaultDevice, enmType, true);
1787 else
1788 pIDefaultDevice = NULL;
1789
1790 /* Enumerate the devices. */
1791 IMMDeviceCollection *pCollection = NULL;
1792 hrc = pThis->pIEnumerator->EnumAudioEndpoints(enmType, DEVICE_STATE_ACTIVE /*| DEVICE_STATE_UNPLUGGED?*/, &pCollection);
1793 if (SUCCEEDED(hrc) && pCollection != NULL)
1794 {
1795 UINT cDevices = 0;
1796 hrc = pCollection->GetCount(&cDevices);
1797 if (SUCCEEDED(hrc))
1798 {
1799 for (UINT idxDevice = 0; idxDevice < cDevices && RT_SUCCESS(rc); idxDevice++)
1800 {
1801 IMMDevice *pIDevice = NULL;
1802 hrc = pCollection->Item(idxDevice, &pIDevice);
1803 if (SUCCEEDED(hrc) && pIDevice)
1804 {
1805 if (pIDevice != pIDefaultDevice)
1806 rc = drvHostWasEnumAddDev(pDevEnm, pIDevice, enmType, false);
1807 pIDevice->Release();
1808 }
1809 }
1810 }
1811 pCollection->Release();
1812 }
1813 else
1814 LogRelMax(10, ("EnumAudioEndpoints(%s) failed: %Rhrc\n", idxPass == 0 ? "output" : "input", hrc));
1815
1816 if (pIDefaultDevice)
1817 pIDefaultDevice->Release();
1818 }
1819
1820 LogRel2(("WasAPI: Enumerating devices done - %u devices (%Rrc)\n", pDevEnm->cDevices, rc));
1821 return rc;
1822}
1823
1824
1825/**
1826 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices}
1827 */
1828static DECLCALLBACK(int) drvHostAudioWasHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum)
1829{
1830 PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
1831 AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER);
1832
1833 PDMAudioHostEnumInit(pDeviceEnum);
1834 int rc = drvHostWasEnumerateDevices(pThis, pDeviceEnum);
1835 if (RT_FAILURE(rc))
1836 PDMAudioHostEnumDelete(pDeviceEnum);
1837
1838 LogFlowFunc(("Returning %Rrc\n", rc));
1839 return rc;
1840}
1841
1842
1843/**
1844 * Worker for drvHostAudioWasHA_SetDevice.
1845 */
1846static int drvHostAudioWasSetDeviceWorker(PDRVHOSTAUDIOWAS pThis, const char *pszId, PRTUTF16 *ppwszDevId, IMMDevice **ppIDevice,
1847 EDataFlow enmFlow, PDMAUDIODIR enmDir, const char *pszWhat)
1848{
1849 pThis->pNotifyClient->lockEnter();
1850
1851 /*
1852 * Did anything actually change?
1853 */
1854 if ( (pszId == NULL) != (*ppwszDevId == NULL)
1855 || ( pszId
1856 && RTUtf16ICmpUtf8(*ppwszDevId, pszId) != 0))
1857 {
1858 /*
1859 * Duplicate the ID.
1860 */
1861 PRTUTF16 pwszDevId = NULL;
1862 if (pszId)
1863 {
1864 int rc = RTStrToUtf16(pszId, &pwszDevId);
1865 AssertRCReturnStmt(rc, pThis->pNotifyClient->lockLeave(), rc);
1866 }
1867
1868 /*
1869 * Try get the device.
1870 */
1871 IMMDevice *pIDevice = NULL;
1872 HRESULT hrc;
1873 if (pwszDevId)
1874 hrc = pThis->pIEnumerator->GetDevice(pwszDevId, &pIDevice);
1875 else
1876 hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(enmFlow, eMultimedia, &pIDevice);
1877 LogFlowFunc(("Got device %p (%Rhrc)\n", pIDevice, hrc));
1878 if (FAILED(hrc))
1879 {
1880 LogRel(("WasAPI: Failed to get IMMDevice for %s audio device '%s' (SetDevice): %Rhrc\n",
1881 pszWhat, pszId ? pszId : "{default}", hrc));
1882 pIDevice = NULL;
1883 }
1884
1885 /*
1886 * Make the switch.
1887 */
1888 LogRel(("PulseAudio: Changing %s device: '%ls' -> '%s'\n",
1889 pszWhat, *ppwszDevId ? *ppwszDevId : L"{Default}", pszId ? pszId : "{Default}"));
1890
1891 if (*ppIDevice)
1892 (*ppIDevice)->Release();
1893 *ppIDevice = pIDevice;
1894
1895 RTUtf16Free(*ppwszDevId);
1896 *ppwszDevId = pwszDevId;
1897
1898 /*
1899 * Only notify the driver above us.
1900 */
1901 PPDMIHOSTAUDIOPORT const pIHostAudioPort = pThis->pIHostAudioPort;
1902 pThis->pNotifyClient->lockLeave();
1903
1904 if (pIHostAudioPort)
1905 {
1906 LogFlowFunc(("Notifying parent driver about %s device change...\n", pszWhat));
1907 pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, enmDir, NULL);
1908 }
1909 }
1910 else
1911 {
1912 pThis->pNotifyClient->lockLeave();
1913 LogFunc(("No %s device change\n", pszWhat));
1914 }
1915
1916 return VINF_SUCCESS;
1917}
1918
1919
1920/**
1921 * @interface_method_impl{PDMIHOSTAUDIO,pfnSetDevice}
1922 */
1923static DECLCALLBACK(int) drvHostAudioWasHA_SetDevice(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir, const char *pszId)
1924{
1925 PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
1926
1927 /*
1928 * Validate and normalize input.
1929 */
1930 AssertReturn(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX, VERR_INVALID_PARAMETER);
1931 AssertPtrNullReturn(pszId, VERR_INVALID_POINTER);
1932 if (!pszId || !*pszId)
1933 pszId = NULL;
1934 else
1935 AssertReturn(strlen(pszId) < 1024, VERR_INVALID_NAME);
1936 LogFunc(("enmDir=%d pszId=%s\n", enmDir, pszId));
1937
1938 /*
1939 * Do the updating.
1940 */
1941 if (enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_DUPLEX)
1942 {
1943 int rc = drvHostAudioWasSetDeviceWorker(pThis, pszId, &pThis->pwszInputDevId, &pThis->pIDeviceInput,
1944 eCapture, PDMAUDIODIR_IN, "input");
1945 AssertRCReturn(rc, rc);
1946 }
1947
1948 if (enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX)
1949 {
1950 int rc = drvHostAudioWasSetDeviceWorker(pThis, pszId, &pThis->pwszOutputDevId, &pThis->pIDeviceOutput,
1951 eRender, PDMAUDIODIR_OUT, "output");
1952 AssertRCReturn(rc, rc);
1953 }
1954
1955 return VINF_SUCCESS;
1956}
1957
1958
1959/**
1960 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
1961 */
1962static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostAudioWasHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
1963{
1964 RT_NOREF(pInterface, enmDir);
1965 return PDMAUDIOBACKENDSTS_RUNNING;
1966}
1967
1968
1969/**
1970 * Performs the actual switching of device config.
1971 *
1972 * Worker for drvHostAudioWasDoStreamDevSwitch() and
1973 * drvHostAudioWasHA_StreamNotifyDeviceChanged().
1974 */
1975static void drvHostAudioWasCompleteStreamDevSwitch(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASSTREAM pStreamWas,
1976 PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg)
1977{
1978 RTCritSectEnter(&pStreamWas->CritSect);
1979
1980 /* Do the switch. */
1981 PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfgOld = pStreamWas->pDevCfg;
1982 pStreamWas->pDevCfg = pDevCfg;
1983
1984 /* The new stream is neither started nor draining. */
1985 pStreamWas->fStarted = false;
1986 pStreamWas->fDraining = false;
1987
1988 /* Device switching is done now. */
1989 pStreamWas->fSwitchingDevice = false;
1990
1991 /* Stop the old stream or Reset() will fail when putting it back into the cache. */
1992 if (pStreamWas->fEnabled && pDevCfgOld->pIAudioClient)
1993 pDevCfgOld->pIAudioClient->Stop();
1994
1995 RTCritSectLeave(&pStreamWas->CritSect);
1996
1997 /* Notify DrvAudio. */
1998 pThis->pIHostAudioPort->pfnStreamNotifyDeviceChanged(pThis->pIHostAudioPort, &pStreamWas->Core, false /*fReInit*/);
1999
2000 /* Put the old config back into the cache. */
2001 drvHostAudioWasCachePutBack(pThis, pDevCfgOld);
2002
2003 LogFlowFunc(("returns with '%s' state: %s\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) ));
2004}
2005
2006
2007/**
2008 * Called on a worker thread to initialize a new device config and switch the
2009 * given stream to using it.
2010 *
2011 * @sa drvHostAudioWasHA_StreamNotifyDeviceChanged
2012 */
2013static void drvHostAudioWasDoStreamDevSwitch(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASSTREAM pStreamWas,
2014 PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg)
2015{
2016 /*
2017 * Do the initializing.
2018 */
2019 int rc = drvHostAudioWasCacheInitConfig(pDevCfg);
2020 if (RT_SUCCESS(rc))
2021 drvHostAudioWasCompleteStreamDevSwitch(pThis, pStreamWas, pDevCfg);
2022 else
2023 {
2024 LogRelMax(64, ("WasAPI: Failed to set up new device config '%ls:%s' for stream '%s': %Rrc\n",
2025 pDevCfg->pDevEntry->wszDevId, pDevCfg->szProps, pStreamWas->Cfg.szName, rc));
2026 drvHostAudioWasCacheDestroyDevConfig(pThis, pDevCfg);
2027 pThis->pIHostAudioPort->pfnStreamNotifyDeviceChanged(pThis->pIHostAudioPort, &pStreamWas->Core, true /*fReInit*/);
2028 }
2029}
2030
2031
2032/**
2033 * @interface_method_impl{PDMIHOSTAUDIO,pfnDoOnWorkerThread}
2034 */
2035static DECLCALLBACK(void) drvHostAudioWasHA_DoOnWorkerThread(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2036 uintptr_t uUser, void *pvUser)
2037{
2038 PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
2039 LogFlowFunc(("uUser=%#zx pStream=%p pvUser=%p\n", uUser, pStream, pvUser));
2040
2041 switch (uUser)
2042 {
2043 case DRVHOSTAUDIOWAS_DO_PURGE_CACHE:
2044 Assert(pStream == NULL);
2045 Assert(pvUser == NULL);
2046 Assert(pThis->fCacheEnabled);
2047 drvHostAudioWasCachePurge(pThis, true /*fOnWorker*/);
2048 break;
2049
2050 case DRVHOSTAUDIOWAS_DO_PRUNE_CACHE:
2051 Assert(pStream == NULL);
2052 Assert(pvUser == NULL);
2053 Assert(pThis->fCacheEnabled);
2054 drvHostAudioWasCachePrune(pThis);
2055 break;
2056
2057 case DRVHOSTAUDIOWAS_DO_STREAM_DEV_SWITCH:
2058 AssertPtr(pStream);
2059 AssertPtr(pvUser);
2060 drvHostAudioWasDoStreamDevSwitch(pThis, (PDRVHOSTAUDIOWASSTREAM)pStream, (PDRVHOSTAUDIOWASCACHEDEVCFG)pvUser);
2061 break;
2062
2063 default:
2064 AssertMsgFailedBreak(("%#zx\n", uUser));
2065 }
2066}
2067
2068
2069/**
2070 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamConfigHint}
2071 *
2072 * @note This is called on a DrvAudio worker thread.
2073 */
2074static DECLCALLBACK(void) drvHostAudioWasHA_StreamConfigHint(PPDMIHOSTAUDIO pInterface, PPDMAUDIOSTREAMCFG pCfg)
2075{
2076#if 0 /* disable to test async stream creation. */
2077 PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
2078 LogFlowFunc(("pCfg=%p\n", pCfg));
2079
2080 drvHostWasCacheConfigHinting(pThis, pCfg);
2081#else
2082 RT_NOREF(pInterface, pCfg);
2083#endif
2084}
2085
2086
2087/**
2088 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
2089 */
2090static DECLCALLBACK(int) drvHostAudioWasHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2091 PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
2092{
2093 PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
2094 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2095 AssertPtrReturn(pStreamWas, VERR_INVALID_POINTER);
2096 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
2097 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
2098 AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
2099 Assert(PDMAudioStrmCfgEquals(pCfgReq, pCfgAcq));
2100
2101 const char * const pszStreamType = pCfgReq->enmDir == PDMAUDIODIR_IN ? "capture" : "playback"; RT_NOREF(pszStreamType);
2102 LogFlowFunc(("enmPath=%s '%s'\n", PDMAudioPathGetName(pCfgReq->enmPath), pCfgReq->szName));
2103#if defined(RTLOG_REL_ENABLED) || defined(LOG_ENABLED)
2104 char szTmp[64];
2105#endif
2106 LogRel2(("WasAPI: Opening %s stream '%s' (%s)\n", pCfgReq->szName, pszStreamType,
2107 PDMAudioPropsToString(&pCfgReq->Props, szTmp, sizeof(szTmp))));
2108
2109 RTListInit(&pStreamWas->ListEntry);
2110
2111 /*
2112 * Do configuration conversion.
2113 */
2114 WAVEFORMATEXTENSIBLE WaveFmtExt;
2115 drvHostAudioWasWaveFmtExtFromProps(&pCfgReq->Props, &WaveFmtExt);
2116 LogRel2(("WasAPI: Requested %s format for '%s':\n"
2117 "WasAPI: wFormatTag = %#RX16\n"
2118 "WasAPI: nChannels = %RU16\n"
2119 "WasAPI: nSamplesPerSec = %RU32\n"
2120 "WasAPI: nAvgBytesPerSec = %RU32\n"
2121 "WasAPI: nBlockAlign = %RU16\n"
2122 "WasAPI: wBitsPerSample = %RU16\n"
2123 "WasAPI: cbSize = %RU16\n"
2124 "WasAPI: cBufferSizeInNtTicks = %RU64\n",
2125 pszStreamType, pCfgReq->szName, WaveFmtExt.Format.wFormatTag, WaveFmtExt.Format.nChannels,
2126 WaveFmtExt.Format.nSamplesPerSec, WaveFmtExt.Format.nAvgBytesPerSec, WaveFmtExt.Format.nBlockAlign,
2127 WaveFmtExt.Format.wBitsPerSample, WaveFmtExt.Format.cbSize,
2128 PDMAudioPropsFramesToNtTicks(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize) ));
2129 if (WaveFmtExt.Format.cbSize != 0)
2130 LogRel2(("WasAPI: dwChannelMask = %#RX32\n"
2131 "WasAPI: wValidBitsPerSample = %RU16\n",
2132 WaveFmtExt.dwChannelMask, WaveFmtExt.Samples.wValidBitsPerSample));
2133
2134 /* Set up the acquired format here as channel count + layout may have
2135 changed and need to be communicated to caller and used in cache lookup. */
2136 *pCfgAcq = *pCfgReq;
2137 if (WaveFmtExt.Format.cbSize != 0)
2138 {
2139 PDMAudioPropsSetChannels(&pCfgAcq->Props, WaveFmtExt.Format.nChannels);
2140 uint8_t idCh = 0;
2141 for (unsigned iBit = 0; iBit < 32 && idCh < WaveFmtExt.Format.nChannels; iBit++)
2142 if (WaveFmtExt.dwChannelMask & RT_BIT_32(iBit))
2143 {
2144 pCfgAcq->Props.aidChannels[idCh] = (unsigned)PDMAUDIOCHANNELID_FIRST_STANDARD + iBit;
2145 idCh++;
2146 }
2147 Assert(idCh == WaveFmtExt.Format.nChannels);
2148 }
2149
2150 /*
2151 * Get the device we're supposed to use.
2152 * (We cache this as it takes ~2ms to get the default device on a random W10 19042 system.)
2153 */
2154 pThis->pNotifyClient->lockEnter();
2155 IMMDevice *pIDevice = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->pIDeviceInput : pThis->pIDeviceOutput;
2156 if (pIDevice)
2157 pIDevice->AddRef();
2158 pThis->pNotifyClient->lockLeave();
2159
2160 PCRTUTF16 pwszDevId = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->pwszInputDevId : pThis->pwszOutputDevId;
2161 PCRTUTF16 const pwszDevIdDesc = pwszDevId ? pwszDevId : pCfgReq->enmDir == PDMAUDIODIR_IN ? L"{Default-In}" : L"{Default-Out}";
2162 if (!pIDevice)
2163 {
2164 /* This might not strictly be necessary anymore, however it shouldn't
2165 hurt and may be useful when using specific devices. */
2166 HRESULT hrc;
2167 if (pwszDevId)
2168 hrc = pThis->pIEnumerator->GetDevice(pwszDevId, &pIDevice);
2169 else
2170 hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(pCfgReq->enmDir == PDMAUDIODIR_IN ? eCapture : eRender,
2171 eMultimedia, &pIDevice);
2172 LogFlowFunc(("Got device %p (%Rhrc)\n", pIDevice, hrc));
2173 if (FAILED(hrc))
2174 {
2175 LogRelMax(64, ("WasAPI: Failed to open audio %s device '%ls': %Rhrc\n", pszStreamType, pwszDevIdDesc, hrc));
2176 return VERR_AUDIO_STREAM_COULD_NOT_CREATE;
2177 }
2178 }
2179
2180 /*
2181 * Ask the cache to retrieve or instantiate the requested configuration.
2182 */
2183 /** @todo make it return a status code too and retry if the default device
2184 * was invalidated/changed while we where working on it here. */
2185 PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg = NULL;
2186 int rc = drvHostAudioWasCacheLookupOrCreate(pThis, pIDevice, pCfgAcq, false /*fOnWorker*/, &pDevCfg);
2187
2188 pIDevice->Release();
2189 pIDevice = NULL;
2190
2191 if (pDevCfg && RT_SUCCESS(rc))
2192 {
2193 pStreamWas->pDevCfg = pDevCfg;
2194
2195 pCfgAcq->Props = pDevCfg->Props;
2196 pCfgAcq->Backend.cFramesBufferSize = pDevCfg->cFramesBufferSize;
2197 pCfgAcq->Backend.cFramesPeriod = pDevCfg->cFramesPeriod;
2198 pCfgAcq->Backend.cFramesPreBuffering = pCfgReq->Backend.cFramesPreBuffering * pDevCfg->cFramesBufferSize
2199 / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
2200
2201 PDMAudioStrmCfgCopy(&pStreamWas->Cfg, pCfgAcq);
2202
2203 /* Finally, the critical section. */
2204 int rc2 = RTCritSectInit(&pStreamWas->CritSect);
2205 if (RT_SUCCESS(rc2))
2206 {
2207 RTCritSectRwEnterExcl(&pThis->CritSectStreamList);
2208 RTListAppend(&pThis->StreamHead, &pStreamWas->ListEntry);
2209 RTCritSectRwLeaveExcl(&pThis->CritSectStreamList);
2210
2211 if (pStreamWas->pDevCfg->pIAudioClient != NULL)
2212 {
2213 LogFlowFunc(("returns VINF_SUCCESS\n", rc));
2214 return VINF_SUCCESS;
2215 }
2216 LogFlowFunc(("returns VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED\n", rc));
2217 return VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED;
2218 }
2219
2220 LogRelMax(64, ("WasAPI: Failed to create critical section for stream.\n"));
2221 drvHostAudioWasCachePutBack(pThis, pDevCfg);
2222 pStreamWas->pDevCfg = NULL;
2223 }
2224 else
2225 LogRelMax(64, ("WasAPI: Failed to setup %s on audio device '%ls' (%Rrc).\n", pszStreamType, pwszDevIdDesc, rc));
2226
2227 LogFlowFunc(("returns %Rrc\n", rc));
2228 return rc;
2229}
2230
2231
2232/**
2233 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamInitAsync}
2234 */
2235static DECLCALLBACK(int) drvHostAudioWasHA_StreamInitAsync(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2236 bool fDestroyed)
2237{
2238 RT_NOREF(pInterface);
2239 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2240 AssertPtrReturn(pStreamWas, VERR_INVALID_POINTER);
2241 LogFlowFunc(("Stream '%s'%s\n", pStreamWas->Cfg.szName, fDestroyed ? " - destroyed!" : ""));
2242
2243 /*
2244 * Assert sane preconditions for this call.
2245 */
2246 AssertPtrReturn(pStreamWas->Core.pStream, VERR_INTERNAL_ERROR);
2247 AssertPtrReturn(pStreamWas->pDevCfg, VERR_INTERNAL_ERROR_2);
2248 AssertPtrReturn(pStreamWas->pDevCfg->pDevEntry, VERR_INTERNAL_ERROR_3);
2249 AssertPtrReturn(pStreamWas->pDevCfg->pDevEntry->pIDevice, VERR_INTERNAL_ERROR_4);
2250 AssertReturn(pStreamWas->pDevCfg->pDevEntry->enmDir == pStreamWas->Core.pStream->Cfg.enmDir, VERR_INTERNAL_ERROR_4);
2251 AssertReturn(pStreamWas->pDevCfg->pIAudioClient == NULL, VERR_INTERNAL_ERROR_5);
2252 AssertReturn(pStreamWas->pDevCfg->pIAudioRenderClient == NULL, VERR_INTERNAL_ERROR_5);
2253 AssertReturn(pStreamWas->pDevCfg->pIAudioCaptureClient == NULL, VERR_INTERNAL_ERROR_5);
2254
2255 /*
2256 * Do the job.
2257 */
2258 int rc;
2259 if (!fDestroyed)
2260 rc = drvHostAudioWasCacheInitConfig(pStreamWas->pDevCfg);
2261 else
2262 {
2263 AssertReturn(pStreamWas->pDevCfg->rcSetup == VERR_AUDIO_STREAM_INIT_IN_PROGRESS, VERR_INTERNAL_ERROR_2);
2264 pStreamWas->pDevCfg->rcSetup = VERR_WRONG_ORDER;
2265 rc = VINF_SUCCESS;
2266 }
2267
2268 LogFlowFunc(("returns %Rrc (%s)\n", rc, pStreamWas->Cfg.szName));
2269 return rc;
2270}
2271
2272
2273/**
2274 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
2275 */
2276static DECLCALLBACK(int) drvHostAudioWasHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2277 bool fImmediate)
2278{
2279 PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
2280 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2281 AssertPtrReturn(pStreamWas, VERR_INVALID_POINTER);
2282 LogFlowFunc(("Stream '%s'\n", pStreamWas->Cfg.szName));
2283 RT_NOREF(fImmediate);
2284 HRESULT hrc;
2285
2286 if (RTCritSectIsInitialized(&pStreamWas->CritSect))
2287 {
2288 RTCritSectRwEnterExcl(&pThis->CritSectStreamList);
2289 RTListNodeRemove(&pStreamWas->ListEntry);
2290 RTCritSectRwLeaveExcl(&pThis->CritSectStreamList);
2291
2292 RTCritSectDelete(&pStreamWas->CritSect);
2293 }
2294
2295 if (pStreamWas->fStarted && pStreamWas->pDevCfg && pStreamWas->pDevCfg->pIAudioClient)
2296 {
2297 hrc = pStreamWas->pDevCfg->pIAudioClient->Stop();
2298 LogFunc(("Stop('%s') -> %Rhrc\n", pStreamWas->Cfg.szName, hrc));
2299 pStreamWas->fStarted = false;
2300 }
2301
2302 if (pStreamWas->cFramesCaptureToRelease)
2303 {
2304 hrc = pStreamWas->pDevCfg->pIAudioCaptureClient->ReleaseBuffer(0);
2305 Log4Func(("Releasing capture buffer (%#x frames): %Rhrc\n", pStreamWas->cFramesCaptureToRelease, hrc));
2306 pStreamWas->cFramesCaptureToRelease = 0;
2307 pStreamWas->pbCapture = NULL;
2308 pStreamWas->cbCapture = 0;
2309 }
2310
2311 if (pStreamWas->pDevCfg)
2312 {
2313 drvHostAudioWasCachePutBack(pThis, pStreamWas->pDevCfg);
2314 pStreamWas->pDevCfg = NULL;
2315 }
2316
2317 LogFlowFunc(("returns\n"));
2318 return VINF_SUCCESS;
2319}
2320
2321
2322/**
2323 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamNotifyDeviceChanged}
2324 */
2325static DECLCALLBACK(void) drvHostAudioWasHA_StreamNotifyDeviceChanged(PPDMIHOSTAUDIO pInterface,
2326 PPDMAUDIOBACKENDSTREAM pStream, void *pvUser)
2327{
2328 PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
2329 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2330 LogFlowFunc(("pStreamWas=%p (%s)\n", pStreamWas, pStreamWas->Cfg.szName));
2331 RT_NOREF(pvUser);
2332
2333 /*
2334 * See if we've got a cached config for the new device around.
2335 * We ignore this entirely, for now at least, if the device was
2336 * disconnected and there is no replacement.
2337 */
2338 pThis->pNotifyClient->lockEnter();
2339 IMMDevice *pIDevice = pStreamWas->Cfg.enmDir == PDMAUDIODIR_IN ? pThis->pIDeviceInput : pThis->pIDeviceOutput;
2340 if (pIDevice)
2341 pIDevice->AddRef();
2342 pThis->pNotifyClient->lockLeave();
2343 if (pIDevice)
2344 {
2345 PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg = NULL;
2346 int rc = drvHostAudioWasCacheLookupOrCreate(pThis, pIDevice, &pStreamWas->Cfg, false /*fOnWorker*/, &pDevCfg);
2347
2348 pIDevice->Release();
2349 pIDevice = NULL;
2350
2351 /*
2352 * If we have a working audio client, just do the switch.
2353 */
2354 if (RT_SUCCESS(rc) && pDevCfg->pIAudioClient)
2355 {
2356 LogFlowFunc(("New device config is ready already!\n"));
2357 Assert(rc == VINF_SUCCESS);
2358 drvHostAudioWasCompleteStreamDevSwitch(pThis, pStreamWas, pDevCfg);
2359 }
2360 /*
2361 * Otherwise create one asynchronously on a worker thread.
2362 */
2363 else if (RT_SUCCESS(rc))
2364 {
2365 LogFlowFunc(("New device config needs async init ...\n"));
2366 Assert(rc == VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED);
2367
2368 RTCritSectEnter(&pStreamWas->CritSect);
2369 pStreamWas->fSwitchingDevice = true;
2370 RTCritSectLeave(&pStreamWas->CritSect);
2371
2372 pThis->pIHostAudioPort->pfnStreamNotifyPreparingDeviceSwitch(pThis->pIHostAudioPort, &pStreamWas->Core);
2373
2374 rc = pThis->pIHostAudioPort->pfnDoOnWorkerThread(pThis->pIHostAudioPort, &pStreamWas->Core,
2375 DRVHOSTAUDIOWAS_DO_STREAM_DEV_SWITCH, pDevCfg);
2376 AssertRCStmt(rc, drvHostAudioWasDoStreamDevSwitch(pThis, pStreamWas, pDevCfg));
2377 }
2378 else
2379 {
2380 LogRelMax(64, ("WasAPI: Failed to create new device config '%ls:%s' for stream '%s': %Rrc\n",
2381 pDevCfg->pDevEntry->wszDevId, pDevCfg->szProps, pStreamWas->Cfg.szName, rc));
2382
2383 pThis->pIHostAudioPort->pfnStreamNotifyDeviceChanged(pThis->pIHostAudioPort, &pStreamWas->Core, true /*fReInit*/);
2384 }
2385 }
2386 else
2387 LogFlowFunc(("no new device, leaving it as-is\n"));
2388}
2389
2390
2391/**
2392 * Wrapper for starting a stream.
2393 *
2394 * @returns VBox status code.
2395 * @param pThis The WASAPI host audio driver instance data.
2396 * @param pStreamWas The stream.
2397 * @param pszOperation The operation we're doing.
2398 */
2399static int drvHostAudioWasStreamStartWorker(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASSTREAM pStreamWas, const char *pszOperation)
2400{
2401 HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->Start();
2402 LogFlow(("%s: Start(%s) returns %Rhrc\n", pszOperation, pStreamWas->Cfg.szName, hrc));
2403 AssertStmt(hrc != AUDCLNT_E_NOT_STOPPED, hrc = S_OK);
2404 if (SUCCEEDED(hrc))
2405 {
2406 pStreamWas->fStarted = true;
2407 return VINF_SUCCESS;
2408 }
2409
2410 /** @todo try re-setup the stuff on AUDCLNT_E_DEVICEINVALIDATED.
2411 * Need some way of telling the caller (e.g. playback, capture) so they can
2412 * retry what they're doing */
2413 RT_NOREF(pThis);
2414
2415 pStreamWas->fStarted = false;
2416 LogRelMax(64, ("WasAPI: Starting '%s' failed (%s): %Rhrc\n", pStreamWas->Cfg.szName, pszOperation, hrc));
2417 return VERR_AUDIO_STREAM_NOT_READY;
2418}
2419
2420
2421/**
2422 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
2423 */
2424static DECLCALLBACK(int) drvHostAudioWasHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2425{
2426 PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
2427 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2428 LogFlowFunc(("Stream '%s' {%s}\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas)));
2429 HRESULT hrc;
2430 RTCritSectEnter(&pStreamWas->CritSect);
2431
2432 Assert(!pStreamWas->fEnabled);
2433 Assert(!pStreamWas->fStarted);
2434
2435 /*
2436 * We always reset the buffer before enabling the stream (normally never necessary).
2437 */
2438 if (pStreamWas->cFramesCaptureToRelease)
2439 {
2440 hrc = pStreamWas->pDevCfg->pIAudioCaptureClient->ReleaseBuffer(pStreamWas->cFramesCaptureToRelease);
2441 Log4Func(("Releasing capture buffer (%#x frames): %Rhrc\n", pStreamWas->cFramesCaptureToRelease, hrc));
2442 pStreamWas->cFramesCaptureToRelease = 0;
2443 pStreamWas->pbCapture = NULL;
2444 pStreamWas->cbCapture = 0;
2445 }
2446
2447 hrc = pStreamWas->pDevCfg->pIAudioClient->Reset();
2448 if (FAILED(hrc))
2449 LogRelMax(64, ("WasAPI: Stream reset failed when enabling '%s': %Rhrc\n", pStreamWas->Cfg.szName, hrc));
2450 pStreamWas->offInternal = 0;
2451 pStreamWas->fDraining = false;
2452 pStreamWas->fEnabled = true;
2453 pStreamWas->fRestartOnResume = false;
2454
2455 /*
2456 * Input streams will start capturing, while output streams will only start
2457 * playing once we get some audio data to play.
2458 */
2459 int rc = VINF_SUCCESS;
2460 if (pStreamWas->Cfg.enmDir == PDMAUDIODIR_IN)
2461 rc = drvHostAudioWasStreamStartWorker(pThis, pStreamWas, "enable");
2462 else
2463 Assert(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT);
2464
2465 RTCritSectLeave(&pStreamWas->CritSect);
2466 LogFlowFunc(("returns %Rrc\n", rc));
2467 return rc;
2468}
2469
2470
2471/**
2472 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
2473 */
2474static DECLCALLBACK(int) drvHostAudioWasHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2475{
2476 RT_NOREF(pInterface);
2477 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2478 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
2479 pStreamWas->msLastTransfer ? RTTimeMilliTS() - pStreamWas->msLastTransfer : -1,
2480 pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) ));
2481 RTCritSectEnter(&pStreamWas->CritSect);
2482
2483 /*
2484 * Always try stop it (draining or no).
2485 */
2486 pStreamWas->fEnabled = false;
2487 pStreamWas->fRestartOnResume = false;
2488 Assert(!pStreamWas->fDraining || pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT);
2489
2490 int rc = VINF_SUCCESS;
2491 if (pStreamWas->fStarted)
2492 {
2493 HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->Stop();
2494 LogFlowFunc(("Stop(%s) returns %Rhrc\n", pStreamWas->Cfg.szName, hrc));
2495 if (FAILED(hrc))
2496 {
2497 LogRelMax(64, ("WasAPI: Stopping '%s' failed (disable): %Rhrc\n", pStreamWas->Cfg.szName, hrc));
2498 rc = VERR_GENERAL_FAILURE;
2499 }
2500 pStreamWas->fStarted = false;
2501 pStreamWas->fDraining = false;
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,pfnStreamPause}
2512 *
2513 * @note Basically the same as drvHostAudioWasHA_StreamDisable, just w/o the
2514 * buffer resetting and fEnabled change.
2515 */
2516static DECLCALLBACK(int) drvHostAudioWasHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2517{
2518 RT_NOREF(pInterface);
2519 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2520 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
2521 pStreamWas->msLastTransfer ? RTTimeMilliTS() - pStreamWas->msLastTransfer : -1,
2522 pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) ));
2523 RTCritSectEnter(&pStreamWas->CritSect);
2524
2525 /*
2526 * Unless we're draining the stream, stop it if it's started.
2527 */
2528 int rc = VINF_SUCCESS;
2529 if (pStreamWas->fStarted && !pStreamWas->fDraining)
2530 {
2531 pStreamWas->fRestartOnResume = true;
2532
2533 HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->Stop();
2534 LogFlowFunc(("Stop(%s) returns %Rhrc\n", pStreamWas->Cfg.szName, hrc));
2535 if (FAILED(hrc))
2536 {
2537 LogRelMax(64, ("WasAPI: Stopping '%s' failed (pause): %Rhrc\n", pStreamWas->Cfg.szName, hrc));
2538 rc = VERR_GENERAL_FAILURE;
2539 }
2540 pStreamWas->fStarted = false;
2541 }
2542 else
2543 {
2544 pStreamWas->fRestartOnResume = false;
2545 if (pStreamWas->fDraining)
2546 {
2547 LogFunc(("Stream '%s' is draining\n", pStreamWas->Cfg.szName));
2548 Assert(pStreamWas->fStarted);
2549 }
2550 }
2551
2552 RTCritSectLeave(&pStreamWas->CritSect);
2553 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostWasStreamStatusString(pStreamWas)));
2554 return rc;
2555}
2556
2557
2558/**
2559 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
2560 */
2561static DECLCALLBACK(int) drvHostAudioWasHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2562{
2563 PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
2564 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2565 LogFlowFunc(("Stream '%s' {%s}\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas)));
2566 RTCritSectEnter(&pStreamWas->CritSect);
2567
2568 /*
2569 * Resume according to state saved by drvHostAudioWasHA_StreamPause.
2570 */
2571 int rc;
2572 if (pStreamWas->fRestartOnResume)
2573 rc = drvHostAudioWasStreamStartWorker(pThis, pStreamWas, "resume");
2574 else
2575 rc = VINF_SUCCESS;
2576 pStreamWas->fRestartOnResume = false;
2577
2578 RTCritSectLeave(&pStreamWas->CritSect);
2579 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostWasStreamStatusString(pStreamWas)));
2580 return rc;
2581}
2582
2583
2584/**
2585 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
2586 */
2587static DECLCALLBACK(int) drvHostAudioWasHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2588{
2589 RT_NOREF(pInterface);
2590 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2591 AssertReturn(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
2592 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
2593 pStreamWas->msLastTransfer ? RTTimeMilliTS() - pStreamWas->msLastTransfer : -1,
2594 pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) ));
2595
2596 /*
2597 * If the stram was started, calculate when the buffered data has finished
2598 * playing and switch to drain mode. DrvAudio will keep on calling
2599 * pfnStreamPlay with an empty buffer while we're draining, so we'll use
2600 * that for checking the deadline and finally stopping the stream.
2601 */
2602 RTCritSectEnter(&pStreamWas->CritSect);
2603 int rc = VINF_SUCCESS;
2604 if (pStreamWas->fStarted)
2605 {
2606 if (!pStreamWas->fDraining)
2607 {
2608 uint64_t const msNow = RTTimeMilliTS();
2609 uint64_t msDrainDeadline = 0;
2610 UINT32 cFramesPending = 0;
2611 HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->GetCurrentPadding(&cFramesPending);
2612 if (SUCCEEDED(hrc))
2613 msDrainDeadline = msNow
2614 + PDMAudioPropsFramesToMilli(&pStreamWas->Cfg.Props,
2615 RT_MIN(cFramesPending,
2616 pStreamWas->Cfg.Backend.cFramesBufferSize * 2))
2617 + 1 /*fudge*/;
2618 else
2619 {
2620 msDrainDeadline = msNow;
2621 LogRelMax(64, ("WasAPI: GetCurrentPadding fail on '%s' when starting draining: %Rhrc\n",
2622 pStreamWas->Cfg.szName, hrc));
2623 }
2624 pStreamWas->msDrainDeadline = msDrainDeadline;
2625 pStreamWas->fDraining = true;
2626 }
2627 else
2628 LogFlowFunc(("Already draining '%s' ...\n", pStreamWas->Cfg.szName));
2629 }
2630 else
2631 {
2632 LogFlowFunc(("Drain requested for '%s', but not started playback...\n", pStreamWas->Cfg.szName));
2633 AssertStmt(!pStreamWas->fDraining, pStreamWas->fDraining = false);
2634 }
2635 RTCritSectLeave(&pStreamWas->CritSect);
2636
2637 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostWasStreamStatusString(pStreamWas)));
2638 return rc;
2639}
2640
2641
2642/**
2643 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
2644 */
2645static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHostAudioWasHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
2646 PPDMAUDIOBACKENDSTREAM pStream)
2647{
2648 RT_NOREF(pInterface);
2649 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2650 AssertPtrReturn(pStreamWas, PDMHOSTAUDIOSTREAMSTATE_INVALID);
2651
2652 PDMHOSTAUDIOSTREAMSTATE enmState;
2653 AssertPtr(pStreamWas->pDevCfg);
2654 if (pStreamWas->pDevCfg /*paranoia*/)
2655 {
2656 if (RT_SUCCESS(pStreamWas->pDevCfg->rcSetup))
2657 {
2658 if (!pStreamWas->fDraining)
2659 enmState = PDMHOSTAUDIOSTREAMSTATE_OKAY;
2660 else
2661 {
2662 Assert(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT);
2663 enmState = PDMHOSTAUDIOSTREAMSTATE_DRAINING;
2664 }
2665 }
2666 else if ( pStreamWas->pDevCfg->rcSetup == VERR_AUDIO_STREAM_INIT_IN_PROGRESS
2667 || pStreamWas->fSwitchingDevice )
2668 enmState = PDMHOSTAUDIOSTREAMSTATE_INITIALIZING;
2669 else
2670 enmState = PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING;
2671 }
2672 else if (pStreamWas->fSwitchingDevice)
2673 enmState = PDMHOSTAUDIOSTREAMSTATE_INITIALIZING;
2674 else
2675 enmState = PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING;
2676
2677 LogFlowFunc(("returns %d for '%s' {%s}\n", enmState, pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas)));
2678 return enmState;
2679}
2680
2681
2682/**
2683 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending}
2684 */
2685static DECLCALLBACK(uint32_t) drvHostAudioWasHA_StreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2686{
2687 RT_NOREF(pInterface);
2688 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2689 AssertPtrReturn(pStreamWas, 0);
2690 LogFlowFunc(("Stream '%s' {%s}\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas)));
2691 AssertReturn(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT, 0);
2692
2693 uint32_t cbPending = 0;
2694 RTCritSectEnter(&pStreamWas->CritSect);
2695
2696 if ( pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT
2697 && pStreamWas->pDevCfg->pIAudioClient /* paranoia */)
2698 {
2699 if (pStreamWas->fStarted)
2700 {
2701 UINT32 cFramesPending = 0;
2702 HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->GetCurrentPadding(&cFramesPending);
2703 if (SUCCEEDED(hrc))
2704 {
2705 AssertMsg(cFramesPending <= pStreamWas->Cfg.Backend.cFramesBufferSize,
2706 ("cFramesPending=%#x cFramesBufferSize=%#x\n",
2707 cFramesPending, pStreamWas->Cfg.Backend.cFramesBufferSize));
2708 cbPending = PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props, RT_MIN(cFramesPending, VBOX_WASAPI_MAX_PADDING));
2709 }
2710 else /* Don't use LogRelMax here to get a more accurate tracking about when this works again wrt recovery. */
2711 LogRel2(("WasAPI: GetCurrentPadding[R] failed on '%s': %Rhrc\n", pStreamWas->Cfg.szName, hrc));
2712 }
2713 }
2714
2715 RTCritSectLeave(&pStreamWas->CritSect);
2716
2717 LogFlowFunc(("returns %#x (%u) {%s}\n", cbPending, cbPending, drvHostWasStreamStatusString(pStreamWas)));
2718 return cbPending;
2719}
2720
2721
2722/**
2723 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
2724 */
2725static DECLCALLBACK(uint32_t) drvHostAudioWasHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2726{
2727 RT_NOREF(pInterface);
2728 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2729 AssertPtrReturn(pStreamWas, 0);
2730 LogFlowFunc(("Stream '%s' {%s}\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas)));
2731 Assert(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT);
2732
2733 uint32_t cbWritable = 0;
2734 RTCritSectEnter(&pStreamWas->CritSect);
2735
2736 if ( pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT
2737 && pStreamWas->pDevCfg->pIAudioClient /* paranoia */)
2738 {
2739 UINT32 cFramesPending = 0;
2740 HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->GetCurrentPadding(&cFramesPending);
2741 if (SUCCEEDED(hrc))
2742 {
2743 if (cFramesPending < pStreamWas->Cfg.Backend.cFramesBufferSize)
2744 cbWritable = PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props,
2745 pStreamWas->Cfg.Backend.cFramesBufferSize - cFramesPending);
2746 else if (cFramesPending > pStreamWas->Cfg.Backend.cFramesBufferSize)
2747 {
2748 LogRelMax(64, ("WasAPI: Warning! GetCurrentPadding('%s') return too high: cFramesPending=%#x > cFramesBufferSize=%#x\n",
2749 pStreamWas->Cfg.szName, cFramesPending, pStreamWas->Cfg.Backend.cFramesBufferSize));
2750 AssertMsgFailed(("cFramesPending=%#x > cFramesBufferSize=%#x\n",
2751 cFramesPending, pStreamWas->Cfg.Backend.cFramesBufferSize));
2752 }
2753 }
2754 else /* Don't use LogRelMax here to get a more accurate tracking about when this works again wrt recovery. */
2755 LogRel2(("WasAPI: GetCurrentPadding[W] failed on '%s': %Rhrc\n", pStreamWas->Cfg.szName, hrc));
2756 }
2757
2758 RTCritSectLeave(&pStreamWas->CritSect);
2759
2760 LogFlowFunc(("returns %#x (%u) {%s}\n", cbWritable, cbWritable, drvHostWasStreamStatusString(pStreamWas)));
2761 return cbWritable;
2762}
2763
2764
2765/**
2766 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
2767 */
2768static DECLCALLBACK(int) drvHostAudioWasHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2769 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
2770{
2771 PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
2772 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2773 AssertPtrReturn(pStreamWas, VERR_INVALID_POINTER);
2774 AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
2775 if (cbBuf)
2776 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2777 Assert(PDMAudioPropsIsSizeAligned(&pStreamWas->Cfg.Props, cbBuf));
2778
2779 RTCritSectEnter(&pStreamWas->CritSect);
2780 if (pStreamWas->fEnabled)
2781 { /* likely */ }
2782 else
2783 {
2784 RTCritSectLeave(&pStreamWas->CritSect);
2785 *pcbWritten = 0;
2786 LogFunc(("Skipping %#x byte write to disabled stream {%s}\n", cbBuf, drvHostWasStreamStatusString(pStreamWas)));
2787 return VINF_SUCCESS;
2788 }
2789 Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) ));
2790
2791 /*
2792 * Transfer loop.
2793 */
2794 int rc = VINF_SUCCESS;
2795 uint32_t cReInits = 0;
2796 uint32_t cbWritten = 0;
2797 while (cbBuf > 0)
2798 {
2799 AssertBreakStmt(pStreamWas->pDevCfg && pStreamWas->pDevCfg->pIAudioRenderClient && pStreamWas->pDevCfg->pIAudioClient,
2800 rc = VERR_AUDIO_STREAM_NOT_READY);
2801
2802 /*
2803 * Figure out how much we can possibly write.
2804 */
2805 UINT32 cFramesPending = 0;
2806 uint32_t cbWritable = 0;
2807 HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->GetCurrentPadding(&cFramesPending);
2808 if (SUCCEEDED(hrc))
2809 cbWritable = PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props,
2810 pStreamWas->Cfg.Backend.cFramesBufferSize
2811 - RT_MIN(cFramesPending, pStreamWas->Cfg.Backend.cFramesBufferSize));
2812 else
2813 {
2814 LogRelMax(64, ("WasAPI: GetCurrentPadding(%s) failed during playback: %Rhrc (@%#RX64)\n",
2815 pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal));
2816 /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */
2817 rc = VERR_AUDIO_STREAM_NOT_READY;
2818 break;
2819 }
2820 if (cbWritable <= PDMAudioPropsFrameSize(&pStreamWas->Cfg.Props))
2821 break;
2822
2823 uint32_t const cbToWrite = PDMAudioPropsFloorBytesToFrame(&pStreamWas->Cfg.Props, RT_MIN(cbWritable, cbBuf));
2824 uint32_t const cFramesToWrite = PDMAudioPropsBytesToFrames(&pStreamWas->Cfg.Props, cbToWrite);
2825 Assert(PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props, cFramesToWrite) == cbToWrite);
2826 Log3Func(("@%#RX64: cFramesPending=%#x -> cbWritable=%#x cbToWrite=%#x cFramesToWrite=%#x {%s}\n",
2827 pStreamWas->offInternal, cFramesPending, cbWritable, cbToWrite, cFramesToWrite,
2828 drvHostWasStreamStatusString(pStreamWas) ));
2829
2830 /*
2831 * Get the buffer, copy the data into it, and relase it back to the WAS machinery.
2832 */
2833 BYTE *pbData = NULL;
2834 hrc = pStreamWas->pDevCfg->pIAudioRenderClient->GetBuffer(cFramesToWrite, &pbData);
2835 if (SUCCEEDED(hrc))
2836 {
2837 memcpy(pbData, pvBuf, cbToWrite);
2838 hrc = pStreamWas->pDevCfg->pIAudioRenderClient->ReleaseBuffer(cFramesToWrite, 0 /*fFlags*/);
2839 if (SUCCEEDED(hrc))
2840 {
2841 /*
2842 * Before we advance the buffer position (so we can resubmit it
2843 * after re-init), make sure we've successfully started stream.
2844 */
2845 if (pStreamWas->fStarted)
2846 { }
2847 else
2848 {
2849 rc = drvHostAudioWasStreamStartWorker(pThis, pStreamWas, "play");
2850 if (rc == VINF_SUCCESS)
2851 { /* likely */ }
2852 else if (RT_SUCCESS(rc) && ++cReInits < 5)
2853 continue; /* re-submit buffer after re-init */
2854 else
2855 break;
2856 }
2857
2858 /* advance. */
2859 pvBuf = (uint8_t *)pvBuf + cbToWrite;
2860 cbBuf -= cbToWrite;
2861 cbWritten += cbToWrite;
2862 pStreamWas->offInternal += cbToWrite;
2863 }
2864 else
2865 {
2866 LogRelMax(64, ("WasAPI: ReleaseBuffer(%#x) failed on '%s' during playback: %Rhrc (@%#RX64)\n",
2867 cFramesToWrite, pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal));
2868 /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */
2869 rc = VERR_AUDIO_STREAM_NOT_READY;
2870 break;
2871 }
2872 }
2873 else
2874 {
2875 LogRelMax(64, ("WasAPI: GetBuffer(%#x) failed on '%s' during playback: %Rhrc (@%#RX64)\n",
2876 cFramesToWrite, pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal));
2877 /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */
2878 rc = VERR_AUDIO_STREAM_NOT_READY;
2879 break;
2880 }
2881 }
2882
2883 /*
2884 * Do draining deadline processing.
2885 */
2886 uint64_t const msNow = RTTimeMilliTS();
2887 if ( !pStreamWas->fDraining
2888 || msNow < pStreamWas->msDrainDeadline)
2889 { /* likely */ }
2890 else
2891 {
2892 LogRel2(("WasAPI: Stopping draining of '%s' {%s} ...\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas)));
2893 HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->Stop();
2894 if (FAILED(hrc))
2895 LogRelMax(64, ("WasAPI: Failed to stop draining stream '%s': %Rhrc\n", pStreamWas->Cfg.szName, hrc));
2896 pStreamWas->fDraining = false;
2897 pStreamWas->fStarted = false;
2898 pStreamWas->fEnabled = false;
2899 }
2900
2901 /*
2902 * Done.
2903 */
2904 uint64_t const msPrev = pStreamWas->msLastTransfer; RT_NOREF(msPrev);
2905 if (cbWritten)
2906 pStreamWas->msLastTransfer = msNow;
2907
2908 RTCritSectLeave(&pStreamWas->CritSect);
2909
2910 *pcbWritten = cbWritten;
2911 if (RT_SUCCESS(rc) || !cbWritten)
2912 { }
2913 else
2914 {
2915 LogFlowFunc(("Suppressing %Rrc to report %#x bytes written\n", rc, cbWritten));
2916 rc = VINF_SUCCESS;
2917 }
2918 LogFlowFunc(("@%#RX64: rc=%Rrc cbWritten=%RU32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamWas->offInternal, rc, cbWritten,
2919 msPrev ? msNow - msPrev : 0, msPrev, pStreamWas->msLastTransfer, drvHostWasStreamStatusString(pStreamWas) ));
2920 return rc;
2921}
2922
2923
2924/**
2925 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
2926 */
2927static DECLCALLBACK(uint32_t) drvHostAudioWasHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2928{
2929 RT_NOREF(pInterface);
2930 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2931 AssertPtrReturn(pStreamWas, 0);
2932 Assert(pStreamWas->Cfg.enmDir == PDMAUDIODIR_IN);
2933
2934 uint32_t cbReadable = 0;
2935 RTCritSectEnter(&pStreamWas->CritSect);
2936
2937 if (pStreamWas->pDevCfg->pIAudioCaptureClient /* paranoia */)
2938 {
2939 UINT32 cFramesPending = 0;
2940 HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->GetCurrentPadding(&cFramesPending);
2941 if (SUCCEEDED(hrc))
2942 {
2943 /* An unreleased buffer is included in the pending frame count, so subtract
2944 whatever we've got hanging around since the previous pfnStreamCapture call. */
2945 AssertMsgStmt(cFramesPending >= pStreamWas->cFramesCaptureToRelease,
2946 ("%#x vs %#x\n", cFramesPending, pStreamWas->cFramesCaptureToRelease),
2947 cFramesPending = pStreamWas->cFramesCaptureToRelease);
2948 cFramesPending -= pStreamWas->cFramesCaptureToRelease;
2949
2950 /* Add what we've got left in said buffer. */
2951 uint32_t cFramesCurPacket = PDMAudioPropsBytesToFrames(&pStreamWas->Cfg.Props, pStreamWas->cbCapture);
2952 cFramesPending += cFramesCurPacket;
2953
2954 /* Paranoia: Make sure we don't exceed the buffer size. */
2955 AssertMsgStmt(cFramesPending <= pStreamWas->Cfg.Backend.cFramesBufferSize,
2956 ("cFramesPending=%#x cFramesCaptureToRelease=%#x cFramesCurPacket=%#x cFramesBufferSize=%#x\n",
2957 cFramesPending, pStreamWas->cFramesCaptureToRelease, cFramesCurPacket,
2958 pStreamWas->Cfg.Backend.cFramesBufferSize),
2959 cFramesPending = pStreamWas->Cfg.Backend.cFramesBufferSize);
2960
2961 cbReadable = PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props, cFramesPending);
2962 }
2963 else
2964 LogRelMax(64, ("WasAPI: GetCurrentPadding failed on '%s': %Rhrc\n", pStreamWas->Cfg.szName, hrc));
2965 }
2966
2967 RTCritSectLeave(&pStreamWas->CritSect);
2968
2969 LogFlowFunc(("returns %#x (%u) {%s}\n", cbReadable, cbReadable, drvHostWasStreamStatusString(pStreamWas)));
2970 return cbReadable;
2971}
2972
2973
2974/**
2975 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
2976 */
2977static DECLCALLBACK(int) drvHostAudioWasHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2978 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
2979{
2980 RT_NOREF(pInterface); //PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
2981 PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
2982 AssertPtrReturn(pStreamWas, 0);
2983 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2984 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
2985 AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
2986 Assert(PDMAudioPropsIsSizeAligned(&pStreamWas->Cfg.Props, cbBuf));
2987
2988 RTCritSectEnter(&pStreamWas->CritSect);
2989 if (pStreamWas->fEnabled)
2990 { /* likely */ }
2991 else
2992 {
2993 RTCritSectLeave(&pStreamWas->CritSect);
2994 *pcbRead = 0;
2995 LogFunc(("Skipping %#x byte read from disabled stream {%s}\n", cbBuf, drvHostWasStreamStatusString(pStreamWas)));
2996 return VINF_SUCCESS;
2997 }
2998 Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) ));
2999
3000
3001 /*
3002 * Transfer loop.
3003 */
3004 int rc = VINF_SUCCESS;
3005 uint32_t cbRead = 0;
3006 uint32_t const cbFrame = PDMAudioPropsFrameSize(&pStreamWas->Cfg.Props);
3007 while (cbBuf >= cbFrame)
3008 {
3009 AssertBreakStmt(pStreamWas->pDevCfg->pIAudioCaptureClient && pStreamWas->pDevCfg->pIAudioClient, rc = VERR_AUDIO_STREAM_NOT_READY);
3010
3011 /*
3012 * Anything pending from last call?
3013 * (This is rather similar to the Pulse interface.)
3014 */
3015 if (pStreamWas->cFramesCaptureToRelease)
3016 {
3017 uint32_t const cbToCopy = RT_MIN(pStreamWas->cbCapture, cbBuf);
3018 memcpy(pvBuf, pStreamWas->pbCapture, cbToCopy);
3019 pvBuf = (uint8_t *)pvBuf + cbToCopy;
3020 cbBuf -= cbToCopy;
3021 cbRead += cbToCopy;
3022 pStreamWas->offInternal += cbToCopy;
3023 pStreamWas->pbCapture += cbToCopy;
3024 pStreamWas->cbCapture -= cbToCopy;
3025 if (!pStreamWas->cbCapture)
3026 {
3027 HRESULT hrc = pStreamWas->pDevCfg->pIAudioCaptureClient->ReleaseBuffer(pStreamWas->cFramesCaptureToRelease);
3028 Log4Func(("@%#RX64: Releasing capture buffer (%#x frames): %Rhrc\n",
3029 pStreamWas->offInternal, pStreamWas->cFramesCaptureToRelease, hrc));
3030 if (SUCCEEDED(hrc))
3031 {
3032 pStreamWas->cFramesCaptureToRelease = 0;
3033 pStreamWas->pbCapture = NULL;
3034 }
3035 else
3036 {
3037 LogRelMax(64, ("WasAPI: ReleaseBuffer(%s) failed during capture: %Rhrc (@%#RX64)\n",
3038 pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal));
3039 /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */
3040 rc = VERR_AUDIO_STREAM_NOT_READY;
3041 break;
3042 }
3043 }
3044 if (cbBuf < cbFrame)
3045 break;
3046 }
3047
3048 /*
3049 * Figure out if there is any data available to be read now. (Docs hint that we can not
3050 * skip this and go straight for GetBuffer or we risk getting unwritten buffer space back).
3051 */
3052 UINT32 cFramesCaptured = 0;
3053 HRESULT hrc = pStreamWas->pDevCfg->pIAudioCaptureClient->GetNextPacketSize(&cFramesCaptured);
3054 if (SUCCEEDED(hrc))
3055 {
3056 if (!cFramesCaptured)
3057 break;
3058 }
3059 else
3060 {
3061 LogRelMax(64, ("WasAPI: GetNextPacketSize(%s) failed during capture: %Rhrc (@%#RX64)\n",
3062 pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal));
3063 /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */
3064 rc = VERR_AUDIO_STREAM_NOT_READY;
3065 break;
3066 }
3067
3068 /*
3069 * Get the buffer.
3070 */
3071 cFramesCaptured = 0;
3072 UINT64 uQpsNtTicks = 0;
3073 UINT64 offDevice = 0;
3074 DWORD fBufFlags = 0;
3075 BYTE *pbData = NULL;
3076 hrc = pStreamWas->pDevCfg->pIAudioCaptureClient->GetBuffer(&pbData, &cFramesCaptured, &fBufFlags, &offDevice, &uQpsNtTicks);
3077 Log4Func(("@%#RX64: GetBuffer -> %Rhrc pbData=%p cFramesCaptured=%#x fBufFlags=%#x offDevice=%#RX64 uQpcNtTicks=%#RX64\n",
3078 pStreamWas->offInternal, hrc, pbData, cFramesCaptured, fBufFlags, offDevice, uQpsNtTicks));
3079 if (SUCCEEDED(hrc))
3080 {
3081 Assert(cFramesCaptured < VBOX_WASAPI_MAX_PADDING);
3082 pStreamWas->pbCapture = pbData;
3083 pStreamWas->cFramesCaptureToRelease = cFramesCaptured;
3084 pStreamWas->cbCapture = PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props, cFramesCaptured);
3085 /* Just loop and re-use the copying code above. Can optimize later. */
3086 }
3087 else
3088 {
3089 LogRelMax(64, ("WasAPI: GetBuffer() failed on '%s' during capture: %Rhrc (@%#RX64)\n",
3090 pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal));
3091 /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */
3092 rc = VERR_AUDIO_STREAM_NOT_READY;
3093 break;
3094 }
3095 }
3096
3097 /*
3098 * Done.
3099 */
3100 uint64_t const msPrev = pStreamWas->msLastTransfer; RT_NOREF(msPrev);
3101 uint64_t const msNow = RTTimeMilliTS();
3102 if (cbRead)
3103 pStreamWas->msLastTransfer = msNow;
3104
3105 RTCritSectLeave(&pStreamWas->CritSect);
3106
3107 *pcbRead = cbRead;
3108 if (RT_SUCCESS(rc) || !cbRead)
3109 { }
3110 else
3111 {
3112 LogFlowFunc(("Suppressing %Rrc to report %#x bytes read\n", rc, cbRead));
3113 rc = VINF_SUCCESS;
3114 }
3115 LogFlowFunc(("@%#RX64: rc=%Rrc cbRead=%#RX32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamWas->offInternal, rc, cbRead,
3116 msPrev ? msNow - msPrev : 0, msPrev, pStreamWas->msLastTransfer, drvHostWasStreamStatusString(pStreamWas) ));
3117 return rc;
3118}
3119
3120
3121/*********************************************************************************************************************************
3122* PDMDRVINS::IBase Interface *
3123*********************************************************************************************************************************/
3124
3125/**
3126 * @callback_method_impl{PDMIBASE,pfnQueryInterface}
3127 */
3128static DECLCALLBACK(void *) drvHostAudioWasQueryInterface(PPDMIBASE pInterface, const char *pszIID)
3129{
3130 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
3131 PDRVHOSTAUDIOWAS pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTAUDIOWAS);
3132
3133 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
3134 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
3135 return NULL;
3136}
3137
3138
3139/*********************************************************************************************************************************
3140* PDMDRVREG Interface *
3141*********************************************************************************************************************************/
3142
3143/**
3144 * @callback_method_impl{FNPDMDRVDESTRUCT, pfnDestruct}
3145 */
3146static DECLCALLBACK(void) drvHostAudioWasPowerOff(PPDMDRVINS pDrvIns)
3147{
3148 PDRVHOSTAUDIOWAS pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTAUDIOWAS);
3149
3150 /*
3151 * Start purging the cache asynchronously before we get to destruct.
3152 * This might speed up VM shutdown a tiny fraction and also stress
3153 * the shutting down of the thread pool a little.
3154 */
3155#if 0
3156 if (pThis->hWorkerThread != NIL_RTTHREAD)
3157 {
3158 BOOL fRc = PostThreadMessageW(pThis->idWorkerThread, WM_DRVHOSTAUDIOWAS_PURGE_CACHE, pThis->uWorkerThreadFixedParam, 0);
3159 LogFlowFunc(("Posted WM_DRVHOSTAUDIOWAS_PURGE_CACHE: %d\n", fRc));
3160 Assert(fRc); RT_NOREF(fRc);
3161 }
3162#else
3163 if ( pThis->fCacheEnabled
3164 && !RTListIsEmpty(&pThis->CacheHead) && pThis->pIHostAudioPort)
3165 {
3166 int rc = RTSemEventMultiCreate(&pThis->hEvtCachePurge);
3167 if (RT_SUCCESS(rc))
3168 {
3169 rc = pThis->pIHostAudioPort->pfnDoOnWorkerThread(pThis->pIHostAudioPort, NULL/*pStream*/,
3170 DRVHOSTAUDIOWAS_DO_PURGE_CACHE, NULL /*pvUser*/);
3171 if (RT_FAILURE(rc))
3172 {
3173 LogFunc(("pfnDoOnWorkerThread/DRVHOSTAUDIOWAS_DO_PURGE_CACHE failed: %Rrc\n", rc));
3174 RTSemEventMultiDestroy(pThis->hEvtCachePurge);
3175 pThis->hEvtCachePurge = NIL_RTSEMEVENTMULTI;
3176 }
3177 }
3178 }
3179#endif
3180
3181 /*
3182 * Deregister the notification client to reduce the risk of notifications
3183 * comming in while we're being detatched or the VM is being destroyed.
3184 */
3185 if (pThis->pNotifyClient)
3186 {
3187 pThis->pNotifyClient->notifyDriverDestroyed();
3188 pThis->pIEnumerator->UnregisterEndpointNotificationCallback(pThis->pNotifyClient);
3189 pThis->pNotifyClient->Release();
3190 pThis->pNotifyClient = NULL;
3191 }
3192}
3193
3194
3195/**
3196 * @callback_method_impl{FNPDMDRVDESTRUCT, pfnDestruct}
3197 */
3198static DECLCALLBACK(void) drvHostAudioWasDestruct(PPDMDRVINS pDrvIns)
3199{
3200 PDRVHOSTAUDIOWAS pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTAUDIOWAS);
3201 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
3202 LogFlowFuncEnter();
3203
3204 /*
3205 * Release the notification client first.
3206 */
3207 if (pThis->pNotifyClient)
3208 {
3209 pThis->pNotifyClient->notifyDriverDestroyed();
3210 pThis->pIEnumerator->UnregisterEndpointNotificationCallback(pThis->pNotifyClient);
3211 pThis->pNotifyClient->Release();
3212 pThis->pNotifyClient = NULL;
3213 }
3214
3215#if 0
3216 if (pThis->hWorkerThread != NIL_RTTHREAD)
3217 {
3218 BOOL fRc = PostThreadMessageW(pThis->idWorkerThread, WM_QUIT, 0, 0);
3219 Assert(fRc); RT_NOREF(fRc);
3220
3221 int rc = RTThreadWait(pThis->hWorkerThread, RT_MS_15SEC, NULL);
3222 AssertRC(rc);
3223 }
3224#endif
3225
3226 if (pThis->fCacheEnabled)
3227 {
3228 if (RTCritSectIsInitialized(&pThis->CritSectCache))
3229 {
3230 drvHostAudioWasCachePurge(pThis, false /*fOnWorker*/);
3231 if (pThis->hEvtCachePurge != NIL_RTSEMEVENTMULTI)
3232 RTSemEventMultiWait(pThis->hEvtCachePurge, RT_MS_30SEC);
3233 RTCritSectDelete(&pThis->CritSectCache);
3234 }
3235
3236 if (pThis->hEvtCachePurge != NIL_RTSEMEVENTMULTI)
3237 {
3238 RTSemEventMultiDestroy(pThis->hEvtCachePurge);
3239 pThis->hEvtCachePurge = NIL_RTSEMEVENTMULTI;
3240 }
3241 }
3242 else
3243 {
3244 Assert(pThis->cCacheEntriesIn == 0);
3245 Assert(pThis->cCacheEntriesOut == 0);
3246 }
3247
3248 if (pThis->pIEnumerator)
3249 {
3250 uint32_t cRefs = pThis->pIEnumerator->Release(); RT_NOREF(cRefs);
3251 LogFlowFunc(("cRefs=%d\n", cRefs));
3252 }
3253
3254 if (pThis->pIDeviceOutput)
3255 {
3256 pThis->pIDeviceOutput->Release();
3257 pThis->pIDeviceOutput = NULL;
3258 }
3259
3260 if (pThis->pIDeviceInput)
3261 {
3262 pThis->pIDeviceInput->Release();
3263 pThis->pIDeviceInput = NULL;
3264 }
3265
3266
3267 if (RTCritSectRwIsInitialized(&pThis->CritSectStreamList))
3268 RTCritSectRwDelete(&pThis->CritSectStreamList);
3269
3270 LogFlowFuncLeave();
3271}
3272
3273
3274/**
3275 * @callback_method_impl{FNPDMDRVCONSTRUCT, pfnConstruct}
3276 */
3277static DECLCALLBACK(int) drvHostAudioWasConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
3278{
3279 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
3280 PDRVHOSTAUDIOWAS pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTAUDIOWAS);
3281 PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
3282 RT_NOREF(fFlags, pCfg);
3283
3284 /*
3285 * Init basic data members and interfaces.
3286 */
3287 pThis->pDrvIns = pDrvIns;
3288 pThis->hEvtCachePurge = NIL_RTSEMEVENTMULTI;
3289#if 0
3290 pThis->hWorkerThread = NIL_RTTHREAD;
3291 pThis->idWorkerThread = 0;
3292#endif
3293 RTListInit(&pThis->StreamHead);
3294 RTListInit(&pThis->CacheHead);
3295 /* IBase */
3296 pDrvIns->IBase.pfnQueryInterface = drvHostAudioWasQueryInterface;
3297 /* IHostAudio */
3298 pThis->IHostAudio.pfnGetConfig = drvHostAudioWasHA_GetConfig;
3299 pThis->IHostAudio.pfnGetDevices = drvHostAudioWasHA_GetDevices;
3300 pThis->IHostAudio.pfnSetDevice = drvHostAudioWasHA_SetDevice;
3301 pThis->IHostAudio.pfnGetStatus = drvHostAudioWasHA_GetStatus;
3302 pThis->IHostAudio.pfnDoOnWorkerThread = drvHostAudioWasHA_DoOnWorkerThread;
3303 pThis->IHostAudio.pfnStreamConfigHint = drvHostAudioWasHA_StreamConfigHint;
3304 pThis->IHostAudio.pfnStreamCreate = drvHostAudioWasHA_StreamCreate;
3305 pThis->IHostAudio.pfnStreamInitAsync = drvHostAudioWasHA_StreamInitAsync;
3306 pThis->IHostAudio.pfnStreamDestroy = drvHostAudioWasHA_StreamDestroy;
3307 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = drvHostAudioWasHA_StreamNotifyDeviceChanged;
3308 pThis->IHostAudio.pfnStreamEnable = drvHostAudioWasHA_StreamEnable;
3309 pThis->IHostAudio.pfnStreamDisable = drvHostAudioWasHA_StreamDisable;
3310 pThis->IHostAudio.pfnStreamPause = drvHostAudioWasHA_StreamPause;
3311 pThis->IHostAudio.pfnStreamResume = drvHostAudioWasHA_StreamResume;
3312 pThis->IHostAudio.pfnStreamDrain = drvHostAudioWasHA_StreamDrain;
3313 pThis->IHostAudio.pfnStreamGetState = drvHostAudioWasHA_StreamGetState;
3314 pThis->IHostAudio.pfnStreamGetPending = drvHostAudioWasHA_StreamGetPending;
3315 pThis->IHostAudio.pfnStreamGetWritable = drvHostAudioWasHA_StreamGetWritable;
3316 pThis->IHostAudio.pfnStreamPlay = drvHostAudioWasHA_StreamPlay;
3317 pThis->IHostAudio.pfnStreamGetReadable = drvHostAudioWasHA_StreamGetReadable;
3318 pThis->IHostAudio.pfnStreamCapture = drvHostAudioWasHA_StreamCapture;
3319
3320 /*
3321 * Validate and read the configuration.
3322 */
3323 PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "VmName|VmUuid|InputDeviceID|OutputDeviceID|CacheEnabled", "");
3324
3325 char szTmp[1024];
3326 int rc = pHlp->pfnCFGMQueryStringDef(pCfg, "InputDeviceID", szTmp, sizeof(szTmp), "");
3327 AssertMsgRCReturn(rc, ("Confguration error: Failed to read \"InputDeviceID\" as string: rc=%Rrc\n", rc), rc);
3328 if (szTmp[0])
3329 {
3330 rc = RTStrToUtf16(szTmp, &pThis->pwszInputDevId);
3331 AssertRCReturn(rc, rc);
3332 }
3333
3334 rc = pHlp->pfnCFGMQueryStringDef(pCfg, "OutputDeviceID", szTmp, sizeof(szTmp), "");
3335 AssertMsgRCReturn(rc, ("Confguration error: Failed to read \"OutputDeviceID\" as string: rc=%Rrc\n", rc), rc);
3336 if (szTmp[0])
3337 {
3338 rc = RTStrToUtf16(szTmp, &pThis->pwszOutputDevId);
3339 AssertRCReturn(rc, rc);
3340 }
3341
3342 /* Caching is enabled by default. */
3343 rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "CacheEnabled", &pThis->fCacheEnabled, true);
3344 AssertMsgRCReturn(rc, ("Confguration error: Failed to read \"CacheEnabled\" as boolean: rc=%Rrc\n", rc), rc);
3345
3346 AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
3347 ("Configuration error: Not possible to attach anything to this driver!\n"),
3348 VERR_PDM_DRVINS_NO_ATTACH);
3349
3350 LogRel2(("WasAPI: Caching device configuration entries is %s\n", pThis->fCacheEnabled ? "enabled" : "disabled"));
3351
3352 /*
3353 * Initialize the critical sections early.
3354 */
3355 rc = RTCritSectRwInit(&pThis->CritSectStreamList);
3356 AssertRCReturn(rc, rc);
3357
3358 if (pThis->fCacheEnabled)
3359 {
3360 rc = RTCritSectInit(&pThis->CritSectCache);
3361 AssertRCReturn(rc, rc);
3362 }
3363
3364 /*
3365 * Create an enumerator instance that we can get the default devices from
3366 * as well as do enumeration thru.
3367 */
3368 HRESULT hrc = CoCreateInstance(__uuidof(MMDeviceEnumerator), 0, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator),
3369 (void **)&pThis->pIEnumerator);
3370 if (FAILED(hrc))
3371 {
3372 pThis->pIEnumerator = NULL;
3373 LogRel(("WasAPI: Failed to create an MMDeviceEnumerator object: %Rhrc\n", hrc));
3374 return VERR_AUDIO_BACKEND_INIT_FAILED;
3375 }
3376 AssertPtr(pThis->pIEnumerator);
3377
3378 /*
3379 * Resolve the interface to the driver above us.
3380 */
3381 pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT);
3382 AssertPtrReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE);
3383
3384 /*
3385 * Instantiate and register the notification client with the enumerator.
3386 *
3387 * Failure here isn't considered fatal at this time as we'll just miss
3388 * default device changes.
3389 */
3390#ifdef RT_EXCEPTIONS_ENABLED
3391 try { pThis->pNotifyClient = new DrvHostAudioWasMmNotifyClient(pThis); }
3392 catch (std::bad_alloc &) { return VERR_NO_MEMORY; }
3393#else
3394 pThis->pNotifyClient = new DrvHostAudioWasMmNotifyClient(pThis);
3395 AssertReturn(pThis->pNotifyClient, VERR_NO_MEMORY);
3396#endif
3397 rc = pThis->pNotifyClient->init();
3398 AssertRCReturn(rc, rc);
3399
3400 hrc = pThis->pIEnumerator->RegisterEndpointNotificationCallback(pThis->pNotifyClient);
3401 AssertMsg(SUCCEEDED(hrc), ("%Rhrc\n", hrc));
3402 if (FAILED(hrc))
3403 {
3404 LogRel(("WasAPI: RegisterEndpointNotificationCallback failed: %Rhrc (ignored)\n"
3405 "WasAPI: Warning! Will not be able to detect default device changes!\n"));
3406 pThis->pNotifyClient->notifyDriverDestroyed();
3407 pThis->pNotifyClient->Release();
3408 pThis->pNotifyClient = NULL;
3409 }
3410
3411 /*
3412 * Retrieve the input and output device.
3413 */
3414 IMMDevice *pIDeviceInput = NULL;
3415 if (pThis->pwszInputDevId)
3416 hrc = pThis->pIEnumerator->GetDevice(pThis->pwszInputDevId, &pIDeviceInput);
3417 else
3418 hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, &pIDeviceInput);
3419 if (SUCCEEDED(hrc))
3420 LogRel(("WasAPI: Input device is: %ls (iface %p)\n",
3421 pThis->pwszInputDevId ? pThis->pwszInputDevId : L"{Default}", pIDeviceInput));
3422 else
3423 {
3424 LogRel(("WasAPI: Failed to get audio input device '%ls': %Rhrc\n",
3425 pThis->pwszInputDevId ? pThis->pwszInputDevId : L"{Default}", hrc));
3426 pIDeviceInput = NULL;
3427 }
3428
3429 IMMDevice *pIDeviceOutput = NULL;
3430 if (pThis->pwszOutputDevId)
3431 hrc = pThis->pIEnumerator->GetDevice(pThis->pwszOutputDevId, &pIDeviceOutput);
3432 else
3433 hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &pIDeviceOutput);
3434 if (SUCCEEDED(hrc))
3435 LogRel(("WasAPI: Output device is: %ls (iface %p)\n",
3436 pThis->pwszOutputDevId ? pThis->pwszOutputDevId : L"{Default}", pIDeviceOutput));
3437 else
3438 {
3439 LogRel(("WasAPI: Failed to get audio output device '%ls': %Rhrc\n",
3440 pThis->pwszOutputDevId ? pThis->pwszOutputDevId : L"{Default}", hrc));
3441 pIDeviceOutput = NULL;
3442 }
3443
3444 /* Carefully place them in the instance data: */
3445 pThis->pNotifyClient->lockEnter();
3446
3447 if (pThis->pIDeviceInput)
3448 pThis->pIDeviceInput->Release();
3449 pThis->pIDeviceInput = pIDeviceInput;
3450
3451 if (pThis->pIDeviceOutput)
3452 pThis->pIDeviceOutput->Release();
3453 pThis->pIDeviceOutput = pIDeviceOutput;
3454
3455 pThis->pNotifyClient->lockLeave();
3456
3457#if 0
3458 /*
3459 * Create the worker thread. This thread has a message loop and will be
3460 * signalled by DrvHostAudioWasMmNotifyClient while the VM is paused/whatever,
3461 * so better make it a regular thread rather than PDM thread.
3462 */
3463 pThis->uWorkerThreadFixedParam = (WPARAM)RTRandU64();
3464 rc = RTThreadCreateF(&pThis->hWorkerThread, drvHostWasWorkerThread, pThis, 0 /*cbStack*/, RTTHREADTYPE_DEFAULT,
3465 RTTHREADFLAGS_WAITABLE | RTTHREADFLAGS_COM_MTA, "WasWork%u", pDrvIns->iInstance);
3466 AssertRCReturn(rc, rc);
3467
3468 rc = RTThreadUserWait(pThis->hWorkerThread, RT_MS_10SEC);
3469 AssertRC(rc);
3470#endif
3471
3472 /*
3473 * Prime the cache.
3474 */
3475 if (pThis->fCacheEnabled)
3476 drvHostAudioWasCacheFill(pThis);
3477
3478 return VINF_SUCCESS;
3479}
3480
3481
3482/**
3483 * PDM driver registration for WasAPI.
3484 */
3485const PDMDRVREG g_DrvHostAudioWas =
3486{
3487 /* u32Version */
3488 PDM_DRVREG_VERSION,
3489 /* szName */
3490 "HostAudioWas",
3491 /* szRCMod */
3492 "",
3493 /* szR0Mod */
3494 "",
3495 /* pszDescription */
3496 "Windows Audio Session API (WASAPI) host audio driver",
3497 /* fFlags */
3498 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
3499 /* fClass. */
3500 PDM_DRVREG_CLASS_AUDIO,
3501 /* cMaxInstances */
3502 ~0U,
3503 /* cbInstance */
3504 sizeof(DRVHOSTAUDIOWAS),
3505 /* pfnConstruct */
3506 drvHostAudioWasConstruct,
3507 /* pfnDestruct */
3508 drvHostAudioWasDestruct,
3509 /* pfnRelocate */
3510 NULL,
3511 /* pfnIOCtl */
3512 NULL,
3513 /* pfnPowerOn */
3514 NULL,
3515 /* pfnReset */
3516 NULL,
3517 /* pfnSuspend */
3518 NULL,
3519 /* pfnResume */
3520 NULL,
3521 /* pfnAttach */
3522 NULL,
3523 /* pfnDetach */
3524 NULL,
3525 /* pfnPowerOff */
3526 drvHostAudioWasPowerOff,
3527 /* pfnSoftReset */
3528 NULL,
3529 /* u32EndVersion */
3530 PDM_DRVREG_VERSION
3531};
3532
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