VirtualBox

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

Last change on this file was 102678, checked in by vboxsync, 4 months ago

Audio/WAS: Also check the audio interface of an audio device when checking the cache for a race, before adding it to the cache. bugref:10503

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

© 2023 Oracle
ContactPrivacy policyTerms of Use