VirtualBox

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

Last change on this file since 88799 was 88776, checked in by vboxsync, 4 years ago

drvHostAudioWasApi: doxygen fixes. bugref:9890

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

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