VirtualBox

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

Last change on this file since 102638 was 102638, checked in by vboxsync, 5 months ago

Audio/WAS: Attempt to fix crashes when invalidating the cached audio interfaces of the device entry configurations. This is needed when the audio interface becomes invalid on a host device switch.

This patch modifies the original one so that we don't remove the current device entry from the cache, but just create a new cached entry with the new audio interface instead. The former approach apparently caused crashes when putting back a stale cache entry back to the cache when completing the device switch.

bugref:10503

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

© 2023 Oracle
ContactPrivacy policyTerms of Use