VirtualBox

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

Last change on this file since 88705 was 88693, checked in by vboxsync, 4 years ago

Audio: Added optional pfnStreamConfigHint methods to PDMIAUDIOCONNECTOR and PDMIHOSTAUDIO so the WASAPI backend can get some useful cache hints to avoid potentially horried EMT blocking when the guest tries to play audio later. This is rather crude, but with typical guest config it helps a lot. bugref:9890

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

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