VirtualBox

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

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

Audio/DrvHostAudioWasApi: Follow-up fix for r167721 to exclude caching for non-active devices. bugref:10844

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