VirtualBox

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

Last change on this file since 89032 was 88991, checked in by vboxsync, 4 years ago

Audio: Worked over draining, starting with the internal DMA buffer (instead of just the pre-buffer and backend buffer) and using the async I/O thread to keep calling PDMIAUDIOCONNECTOR::pfnStreamIterate and PDMIHOSTAUDIO::pfnStreamPlay (NULL buffer) every so often till the draining is done. Also put a rough deadline on the draining. The PDMAUDIOSTREAMCMD_DISABLE is now defined to stop playback/capturing immediately, even when already draining (if possible). This gets rid of the timers in DrvAudio and windows backends. DrvAudio no longer issue an DISABLE command at the end of the drain, it assumes the backend does that internally itself. After issuing PDMAUDIOSTREAMCMD_DRAIN the client (be it mixer or drvaudio) will not provide any more data for the buffers via pfnStreamPlay. Only tested windows, needs to re-test all platforms. bugref:9890

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

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