VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/DrvHostAudioDSound.cpp@ 89057

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

ValKit/Audio: Eliminated VBoxDDVKAT.h. Made some progress getting the PulseAudio driver to work (integer overflow now due to invalid config). [build fix] bugref:10008

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 106.8 KB
Line 
1/* $Id: DrvHostAudioDSound.cpp 89057 2021-05-15 17:00:40Z vboxsync $ */
2/** @file
3 * Host audio driver - DirectSound (Windows).
4 */
5
6/*
7 * Copyright (C) 2006-2020 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
24#include <VBox/log.h>
25#include <iprt/win/windows.h>
26#include <dsound.h>
27#include <Mmdeviceapi.h>
28#include <functiondiscoverykeys_devpkey.h>
29
30#include <iprt/alloc.h>
31#include <iprt/system.h>
32#include <iprt/uuid.h>
33#include <iprt/utf16.h>
34
35#include <VBox/vmm/pdmaudioinline.h>
36#include <VBox/vmm/pdmaudiohostenuminline.h>
37
38#include "VBoxDD.h"
39
40#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
41# include <new> /* For bad_alloc. */
42# include "DrvHostAudioDSoundMMNotifClient.h"
43#endif
44
45
46/*********************************************************************************************************************************
47* Defined Constants And Macros *
48*********************************************************************************************************************************/
49/*
50 * Optional release logging, which a user can turn on with the
51 * 'VBoxManage debugvm' command.
52 * Debug logging still uses the common Log* macros from VBox.
53 * Messages which always should go to the release log use LogRel.
54 *
55 * @deprecated Use LogRelMax, LogRel2 and LogRel3 directly.
56 */
57/** General code behavior. */
58#define DSLOG(a) do { LogRel2(a); } while(0)
59/** Something which produce a lot of logging during playback/recording. */
60#define DSLOGF(a) do { LogRel3(a); } while(0)
61/** Important messages like errors. Limited in the default release log to avoid log flood. */
62#define DSLOGREL(a) \
63 do { \
64 static int8_t s_cLogged = 0; \
65 if (s_cLogged < 8) { \
66 ++s_cLogged; \
67 LogRel(a); \
68 } else DSLOG(a); \
69 } while (0)
70
71/** Maximum number of attempts to restore the sound buffer before giving up. */
72#define DRV_DSOUND_RESTORE_ATTEMPTS_MAX 3
73#if 0 /** @todo r=bird: What are these for? Nobody is using them... */
74/** Default input latency (in ms). */
75#define DRV_DSOUND_DEFAULT_LATENCY_MS_IN 50
76/** Default output latency (in ms). */
77#define DRV_DSOUND_DEFAULT_LATENCY_MS_OUT 50
78#endif
79
80
81/*********************************************************************************************************************************
82* Structures and Typedefs *
83*********************************************************************************************************************************/
84/* Dynamically load dsound.dll. */
85typedef HRESULT WINAPI FNDIRECTSOUNDENUMERATEW(LPDSENUMCALLBACKW pDSEnumCallback, PVOID pContext);
86typedef FNDIRECTSOUNDENUMERATEW *PFNDIRECTSOUNDENUMERATEW;
87typedef HRESULT WINAPI FNDIRECTSOUNDCAPTUREENUMERATEW(LPDSENUMCALLBACKW pDSEnumCallback, PVOID pContext);
88typedef FNDIRECTSOUNDCAPTUREENUMERATEW *PFNDIRECTSOUNDCAPTUREENUMERATEW;
89typedef HRESULT WINAPI FNDIRECTSOUNDCAPTURECREATE8(LPCGUID lpcGUID, LPDIRECTSOUNDCAPTURE8 *lplpDSC, LPUNKNOWN pUnkOuter);
90typedef FNDIRECTSOUNDCAPTURECREATE8 *PFNDIRECTSOUNDCAPTURECREATE8;
91
92#define VBOX_DSOUND_MAX_EVENTS 3
93
94typedef enum DSOUNDEVENT
95{
96 DSOUNDEVENT_NOTIFY = 0,
97 DSOUNDEVENT_INPUT,
98 DSOUNDEVENT_OUTPUT,
99} DSOUNDEVENT;
100
101typedef struct DSOUNDHOSTCFG
102{
103 RTUUID uuidPlay;
104 LPCGUID pGuidPlay;
105 RTUUID uuidCapture;
106 LPCGUID pGuidCapture;
107} DSOUNDHOSTCFG, *PDSOUNDHOSTCFG;
108
109typedef struct DSOUNDSTREAM
110{
111 /** Common part. */
112 PDMAUDIOBACKENDSTREAM Core;
113 /** Entry in DRVHOSTDSOUND::HeadStreams. */
114 RTLISTNODE ListEntry;
115 /** The stream's acquired configuration. */
116 PDMAUDIOSTREAMCFG Cfg;
117 /** Buffer alignment. */
118 uint8_t uAlign;
119 /** Whether this stream is in an enable state on the DirectSound side. */
120 bool fEnabled;
121 bool afPadding[2];
122 /** Size (in bytes) of the DirectSound buffer. */
123 DWORD cbBufSize;
124 union
125 {
126 struct
127 {
128 /** The actual DirectSound Buffer (DSB) used for the capturing.
129 * This is a secondary buffer and is used as a streaming buffer. */
130 LPDIRECTSOUNDCAPTUREBUFFER8 pDSCB;
131 /** Current read offset (in bytes) within the DSB. */
132 DWORD offReadPos;
133 /** Number of buffer overruns happened. Used for logging. */
134 uint8_t cOverruns;
135 } In;
136 struct
137 {
138 /** The actual DirectSound Buffer (DSB) used for playback.
139 * This is a secondary buffer and is used as a streaming buffer. */
140 LPDIRECTSOUNDBUFFER8 pDSB;
141 /** Current write offset (in bytes) within the DSB.
142 * @note This is needed as the current write position as kept by direct sound
143 * will move ahead if we're too late. */
144 DWORD offWritePos;
145 /** Offset of last play cursor within the DSB when checked for pending. */
146 DWORD offPlayCursorLastPending;
147 /** Offset of last play cursor within the DSB when last played. */
148 DWORD offPlayCursorLastPlayed;
149 /** Total amount (in bytes) written to our internal ring buffer. */
150 uint64_t cbWritten;
151 /** Total amount (in bytes) played (to the DirectSound buffer). */
152 uint64_t cbTransferred;
153 /** Flag indicating whether playback was just (re)started. */
154 bool fFirstTransfer;
155 /** Flag indicating whether this stream is in draining mode, e.g. no new
156 * data is being written to it but DirectSound still needs to be able to
157 * play its remaining (buffered) data. */
158 bool fDrain;
159 /** How much (in bytes) the last transfer from the internal buffer
160 * to the DirectSound buffer was. */
161 uint32_t cbLastTransferred;
162 /** The RTTimeMilliTS() deadline for the draining of this stream. */
163 uint64_t msDrainDeadline;
164 } Out;
165 };
166 /** Timestamp (in ms) of the last transfer from the internal buffer to/from the
167 * DirectSound buffer. */
168 uint64_t msLastTransfer;
169 /** The stream's critical section for synchronizing access. */
170 RTCRITSECT CritSect;
171 /** Used for formatting the current DSound status. */
172 char szStatus[127];
173 /** Fixed zero terminator. */
174 char const chStateZero;
175} DSOUNDSTREAM, *PDSOUNDSTREAM;
176
177/**
178 * DirectSound-specific device entry.
179 */
180typedef struct DSOUNDDEV
181{
182 PDMAUDIOHOSTDEV Core;
183 /** The GUID if handy. */
184 GUID Guid;
185} DSOUNDDEV;
186/** Pointer to a DirectSound device entry. */
187typedef DSOUNDDEV *PDSOUNDDEV;
188
189/**
190 * Structure for holding a device enumeration context.
191 */
192typedef struct DSOUNDENUMCBCTX
193{
194 /** Enumeration flags. */
195 uint32_t fFlags;
196 /** Pointer to device list to populate. */
197 PPDMAUDIOHOSTENUM pDevEnm;
198} DSOUNDENUMCBCTX, *PDSOUNDENUMCBCTX;
199
200typedef struct DRVHOSTDSOUND
201{
202 /** Pointer to the driver instance structure. */
203 PPDMDRVINS pDrvIns;
204 /** Our audio host audio interface. */
205 PDMIHOSTAUDIO IHostAudio;
206 /** Critical section to serialize access. */
207 RTCRITSECT CritSect;
208 /** DirectSound configuration options. */
209 DSOUNDHOSTCFG Cfg;
210 /** List of devices of last enumeration. */
211 PDMAUDIOHOSTENUM DeviceEnum;
212 /** Whether this backend supports any audio input.
213 * @todo r=bird: This is not actually used for anything. */
214 bool fEnabledIn;
215 /** Whether this backend supports any audio output.
216 * @todo r=bird: This is not actually used for anything. */
217 bool fEnabledOut;
218 /** The Direct Sound playback interface. */
219 LPDIRECTSOUND8 pDS;
220 /** The Direct Sound capturing interface. */
221 LPDIRECTSOUNDCAPTURE8 pDSC;
222 /** List of streams (DSOUNDSTREAM).
223 * Requires CritSect ownership. */
224 RTLISTANCHOR HeadStreams;
225
226#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
227 DrvHostAudioDSoundMMNotifClient *m_pNotificationClient;
228#endif
229} DRVHOSTDSOUND, *PDRVHOSTDSOUND;
230
231
232/*********************************************************************************************************************************
233* Internal Functions *
234*********************************************************************************************************************************/
235static HRESULT directSoundPlayRestore(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB);
236static int drvHostDSoundStreamStopPlayback(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fReset);
237
238static int dsoundDevicesEnumerate(PDRVHOSTDSOUND pThis, PDMAUDIOHOSTENUM pDevEnm, uint32_t fEnum);
239
240static void dsoundUpdateStatusInternal(PDRVHOSTDSOUND pThis);
241
242
243#if defined(LOG_ENABLED) || defined(RTLOG_REL_ENABLED)
244/**
245 * Gets the stream status as a string for logging purposes.
246 *
247 * @returns Status string (pStreamDS->szStatus).
248 * @param pStreamDS The stream to get the status for.
249 */
250static const char *drvHostDSoundStreamStatusString(PDSOUNDSTREAM pStreamDS)
251{
252 /*
253 * Out internal stream status first.
254 */
255 size_t off;
256 if (pStreamDS->fEnabled)
257 {
258 memcpy(pStreamDS->szStatus, RT_STR_TUPLE("ENABLED "));
259 off = sizeof("ENABLED ") - 1;
260 }
261 else
262 {
263 memcpy(pStreamDS->szStatus, RT_STR_TUPLE("DISABLED"));
264 off = sizeof("DISABLED") - 1;
265 }
266
267 /*
268 * Direction specific stuff, returning with a status DWORD and string mappings for it.
269 */
270 typedef struct DRVHOSTDSOUNDSFLAGS2STR
271 {
272 const char *pszMnemonic;
273 uint32_t cchMnemonic;
274 uint32_t fFlag;
275 } DRVHOSTDSOUNDSFLAGS2STR;
276 static const DRVHOSTDSOUNDSFLAGS2STR s_aCaptureFlags[] =
277 {
278 { RT_STR_TUPLE(" CAPTURING"), DSCBSTATUS_CAPTURING },
279 { RT_STR_TUPLE(" LOOPING"), DSCBSTATUS_LOOPING },
280 };
281 static const DRVHOSTDSOUNDSFLAGS2STR s_aPlaybackFlags[] =
282 {
283 { RT_STR_TUPLE(" PLAYING"), DSBSTATUS_PLAYING },
284 { RT_STR_TUPLE(" BUFFERLOST"), DSBSTATUS_BUFFERLOST },
285 { RT_STR_TUPLE(" LOOPING"), DSBSTATUS_LOOPING },
286 { RT_STR_TUPLE(" LOCHARDWARE"), DSBSTATUS_LOCHARDWARE },
287 { RT_STR_TUPLE(" LOCSOFTWARE"), DSBSTATUS_LOCSOFTWARE },
288 { RT_STR_TUPLE(" TERMINATED"), DSBSTATUS_TERMINATED },
289 };
290 DRVHOSTDSOUNDSFLAGS2STR const *paMappings = NULL;
291 size_t cMappings = 0;
292 DWORD fStatus = 0;
293 if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
294 {
295 if (pStreamDS->In.pDSCB)
296 {
297 HRESULT hrc = pStreamDS->In.pDSCB->GetStatus(&fStatus);
298 if (SUCCEEDED(hrc))
299 {
300 paMappings = s_aCaptureFlags;
301 cMappings = RT_ELEMENTS(s_aCaptureFlags);
302 }
303 else
304 RTStrPrintf(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, "GetStatus->%Rhrc", hrc);
305 }
306 else
307 RTStrCopy(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, "NO-DSCB");
308 }
309 else if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT)
310 {
311 if (pStreamDS->Out.fDrain)
312 {
313 memcpy(&pStreamDS->szStatus[off], RT_STR_TUPLE(" DRAINING"));
314 off += sizeof(" DRAINING") - 1;
315 }
316
317 if (pStreamDS->Out.fFirstTransfer)
318 {
319 memcpy(&pStreamDS->szStatus[off], RT_STR_TUPLE(" NOXFER"));
320 off += sizeof(" NOXFER") - 1;
321 }
322
323 if (pStreamDS->Out.pDSB)
324 {
325 HRESULT hrc = pStreamDS->Out.pDSB->GetStatus(&fStatus);
326 if (SUCCEEDED(hrc))
327 {
328 paMappings = s_aPlaybackFlags;
329 cMappings = RT_ELEMENTS(s_aPlaybackFlags);
330 }
331 else
332 RTStrPrintf(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, "GetStatus->%Rhrc", hrc);
333 }
334 else
335 RTStrCopy(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, "NO-DSB");
336 }
337 else
338 RTStrCopy(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, "BAD-DIR");
339
340 /* Format flags. */
341 if (paMappings)
342 {
343 if (fStatus == 0)
344 RTStrCopy(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, " 0");
345 else
346 {
347 for (size_t i = 0; i < cMappings; i++)
348 if (fStatus & paMappings[i].fFlag)
349 {
350 memcpy(&pStreamDS->szStatus[off], paMappings[i].pszMnemonic, paMappings[i].cchMnemonic);
351 off += paMappings[i].cchMnemonic;
352
353 fStatus &= ~paMappings[i].fFlag;
354 if (!fStatus)
355 break;
356 }
357 if (fStatus != 0)
358 off += RTStrPrintf(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, " %#x", fStatus);
359 }
360 }
361
362 /*
363 * Finally, terminate the string. By postponing it this long, it won't be
364 * a big deal if two threads go thru here at the same time as long as the
365 * status is the same.
366 */
367 Assert(off < sizeof(pStreamDS->szStatus));
368 pStreamDS->szStatus[off] = '\0';
369
370 return pStreamDS->szStatus;
371}
372#endif /* LOG_ENABLED || RTLOG_REL_ENABLED */
373
374
375static DWORD dsoundRingDistance(DWORD offEnd, DWORD offBegin, DWORD cSize)
376{
377 AssertReturn(offEnd <= cSize, 0);
378 AssertReturn(offBegin <= cSize, 0);
379
380 return offEnd >= offBegin ? offEnd - offBegin : cSize - offBegin + offEnd;
381}
382
383
384static char *dsoundGUIDToUtf8StrA(LPCGUID pGUID)
385{
386 if (pGUID)
387 {
388 LPOLESTR lpOLEStr;
389 HRESULT hr = StringFromCLSID(*pGUID, &lpOLEStr);
390 if (SUCCEEDED(hr))
391 {
392 char *pszGUID;
393 int rc = RTUtf16ToUtf8(lpOLEStr, &pszGUID);
394 CoTaskMemFree(lpOLEStr);
395
396 return RT_SUCCESS(rc) ? pszGUID : NULL;
397 }
398 }
399
400 return RTStrDup("{Default device}");
401}
402
403
404static HRESULT directSoundPlayRestore(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB)
405{
406 RT_NOREF(pThis);
407 HRESULT hr = IDirectSoundBuffer8_Restore(pDSB);
408 if (FAILED(hr))
409 DSLOG(("DSound: Restoring playback buffer\n"));
410 else
411 DSLOGREL(("DSound: Restoring playback buffer failed with %Rhrc\n", hr));
412
413 return hr;
414}
415
416
417static HRESULT directSoundPlayUnlock(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB,
418 PVOID pv1, PVOID pv2,
419 DWORD cb1, DWORD cb2)
420{
421 RT_NOREF(pThis);
422 HRESULT hr = IDirectSoundBuffer8_Unlock(pDSB, pv1, cb1, pv2, cb2);
423 if (FAILED(hr))
424 DSLOGREL(("DSound: Unlocking playback buffer failed with %Rhrc\n", hr));
425 return hr;
426}
427
428
429static HRESULT directSoundPlayLock(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS,
430 DWORD dwOffset, DWORD dwBytes,
431 PVOID *ppv1, PVOID *ppv2,
432 DWORD *pcb1, DWORD *pcb2,
433 DWORD dwFlags)
434{
435 AssertReturn(dwBytes, VERR_INVALID_PARAMETER);
436
437 HRESULT hr = E_FAIL;
438 AssertCompile(DRV_DSOUND_RESTORE_ATTEMPTS_MAX > 0);
439 for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++)
440 {
441 PVOID pv1, pv2;
442 DWORD cb1, cb2;
443 hr = IDirectSoundBuffer8_Lock(pStreamDS->Out.pDSB, dwOffset, dwBytes, &pv1, &cb1, &pv2, &cb2, dwFlags);
444 if (SUCCEEDED(hr))
445 {
446 if ( (!pv1 || !(cb1 & pStreamDS->uAlign))
447 && (!pv2 || !(cb2 & pStreamDS->uAlign)))
448 {
449 if (ppv1)
450 *ppv1 = pv1;
451 if (ppv2)
452 *ppv2 = pv2;
453 if (pcb1)
454 *pcb1 = cb1;
455 if (pcb2)
456 *pcb2 = cb2;
457 return S_OK;
458 }
459 DSLOGREL(("DSound: Locking playback buffer returned misaligned buffer: cb1=%#RX32, cb2=%#RX32 (alignment: %#RX32)\n",
460 *pcb1, *pcb2, pStreamDS->uAlign));
461 directSoundPlayUnlock(pThis, pStreamDS->Out.pDSB, pv1, pv2, cb1, cb2);
462 return E_FAIL;
463 }
464
465 if (hr != DSERR_BUFFERLOST)
466 break;
467
468 LogFlowFunc(("Locking failed due to lost buffer, restoring ...\n"));
469 directSoundPlayRestore(pThis, pStreamDS->Out.pDSB);
470 }
471
472 DSLOGREL(("DSound: Locking playback buffer failed with %Rhrc (dwOff=%ld, dwBytes=%ld)\n", hr, dwOffset, dwBytes));
473 return hr;
474}
475
476
477static HRESULT directSoundCaptureUnlock(LPDIRECTSOUNDCAPTUREBUFFER8 pDSCB,
478 PVOID pv1, PVOID pv2,
479 DWORD cb1, DWORD cb2)
480{
481 HRESULT hr = IDirectSoundCaptureBuffer8_Unlock(pDSCB, pv1, cb1, pv2, cb2);
482 if (FAILED(hr))
483 DSLOGREL(("DSound: Unlocking capture buffer failed with %Rhrc\n", hr));
484 return hr;
485}
486
487
488static HRESULT directSoundCaptureLock(PDSOUNDSTREAM pStreamDS,
489 DWORD dwOffset, DWORD dwBytes,
490 PVOID *ppv1, PVOID *ppv2,
491 DWORD *pcb1, DWORD *pcb2,
492 DWORD dwFlags)
493{
494 PVOID pv1 = NULL;
495 PVOID pv2 = NULL;
496 DWORD cb1 = 0;
497 DWORD cb2 = 0;
498
499 HRESULT hr = IDirectSoundCaptureBuffer8_Lock(pStreamDS->In.pDSCB, dwOffset, dwBytes,
500 &pv1, &cb1, &pv2, &cb2, dwFlags);
501 if (FAILED(hr))
502 {
503 DSLOGREL(("DSound: Locking capture buffer failed with %Rhrc\n", hr));
504 return hr;
505 }
506
507 if ( (pv1 && (cb1 & pStreamDS->uAlign))
508 || (pv2 && (cb2 & pStreamDS->uAlign)))
509 {
510 DSLOGREL(("DSound: Locking capture buffer returned misaligned buffer: cb1=%RI32, cb2=%RI32 (alignment: %RU32)\n",
511 cb1, cb2, pStreamDS->uAlign));
512 directSoundCaptureUnlock(pStreamDS->In.pDSCB, pv1, pv2, cb1, cb2);
513 return E_FAIL;
514 }
515
516 *ppv1 = pv1;
517 *ppv2 = pv2;
518 *pcb1 = cb1;
519 *pcb2 = cb2;
520
521 return S_OK;
522}
523
524
525/*
526 * DirectSound playback
527 */
528
529/**
530 * Creates a DirectSound playback instance.
531 *
532 * @return HRESULT
533 * @param pGUID GUID of device to create the playback interface for. NULL
534 * for the default device.
535 * @param ppDS Where to return the interface to the created instance.
536 */
537static HRESULT drvHostDSoundCreateDSPlaybackInstance(LPCGUID pGUID, LPDIRECTSOUND8 *ppDS)
538{
539 LogFlowFuncEnter();
540
541 LPDIRECTSOUND8 pDS = NULL;
542 HRESULT hrc = CoCreateInstance(CLSID_DirectSound8, NULL, CLSCTX_ALL, IID_IDirectSound8, (void **)&pDS);
543 if (SUCCEEDED(hrc))
544 {
545 hrc = IDirectSound8_Initialize(pDS, pGUID);
546 if (SUCCEEDED(hrc))
547 {
548 HWND hWnd = GetDesktopWindow();
549 hrc = IDirectSound8_SetCooperativeLevel(pDS, hWnd, DSSCL_PRIORITY);
550 if (SUCCEEDED(hrc))
551 {
552 *ppDS = pDS;
553 LogFlowFunc(("LEAVE S_OK\n"));
554 return S_OK;
555 }
556 LogRelMax(64, ("DSound: Setting cooperative level for (hWnd=%p) failed: %Rhrc\n", hWnd, hrc));
557 }
558 else if (hrc == DSERR_NODRIVER) /* Usually means that no playback devices are attached. */
559 LogRelMax(64, ("DSound: DirectSound playback is currently unavailable\n"));
560 else
561 LogRelMax(64, ("DSound: DirectSound playback initialization failed: %Rhrc\n", hrc));
562
563 IDirectSound8_Release(pDS);
564 }
565 else
566 LogRelMax(64, ("DSound: Creating playback instance failed: %Rhrc\n", hrc));
567
568 LogFlowFunc(("LEAVE %Rhrc\n", hrc));
569 return hrc;
570}
571
572
573#if 0 /* not used */
574static HRESULT directSoundPlayGetStatus(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB, DWORD *pdwStatus)
575{
576 AssertPtrReturn(pThis, E_POINTER);
577 AssertPtrReturn(pDSB, E_POINTER);
578
579 AssertPtrNull(pdwStatus);
580
581 DWORD dwStatus = 0;
582 HRESULT hr = E_FAIL;
583 for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++)
584 {
585 hr = IDirectSoundBuffer8_GetStatus(pDSB, &dwStatus);
586 if ( hr == DSERR_BUFFERLOST
587 || ( SUCCEEDED(hr)
588 && (dwStatus & DSBSTATUS_BUFFERLOST)))
589 {
590 LogFlowFunc(("Getting status failed due to lost buffer, restoring ...\n"));
591 directSoundPlayRestore(pThis, pDSB);
592 }
593 else
594 break;
595 }
596
597 if (SUCCEEDED(hr))
598 {
599 if (pdwStatus)
600 *pdwStatus = dwStatus;
601 }
602 else
603 DSLOGREL(("DSound: Retrieving playback status failed with %Rhrc\n", hr));
604
605 return hr;
606}
607#endif
608
609
610/*
611 * DirectSoundCapture
612 */
613
614#if 0 /* unused */
615static LPCGUID dsoundCaptureSelectDevice(PDRVHOSTDSOUND pThis, PPDMAUDIOSTREAMCFG pCfg)
616{
617 AssertPtrReturn(pThis, NULL);
618 AssertPtrReturn(pCfg, NULL);
619
620 int rc = VINF_SUCCESS;
621
622 LPCGUID pGUID = pThis->Cfg.pGuidCapture;
623 if (!pGUID)
624 {
625 PDSOUNDDEV pDev = NULL;
626 switch (pCfg->u.enmSrc)
627 {
628 case PDMAUDIORECSRC_LINE:
629 /*
630 * At the moment we're only supporting line-in in the HDA emulation,
631 * and line-in + mic-in in the AC'97 emulation both are expected
632 * to use the host's mic-in as well.
633 *
634 * So the fall through here is intentional for now.
635 */
636 case PDMAUDIORECSRC_MIC:
637 pDev = (PDSOUNDDEV)DrvAudioHlpDeviceEnumGetDefaultDevice(&pThis->DeviceEnum, PDMAUDIODIR_IN);
638 break;
639
640 default:
641 AssertFailedStmt(rc = VERR_NOT_SUPPORTED);
642 break;
643 }
644
645 if ( RT_SUCCESS(rc)
646 && pDev)
647 {
648 DSLOG(("DSound: Guest source '%s' is using host recording device '%s'\n",
649 PDMAudioRecSrcGetName(pCfg->u.enmSrc), pDev->Core.szName));
650 pGUID = &pDev->Guid;
651 }
652 if (RT_FAILURE(rc))
653 {
654 LogRel(("DSound: Selecting recording device failed with %Rrc\n", rc));
655 return NULL;
656 }
657 }
658
659 /* This always has to be in the release log. */
660 char *pszGUID = dsoundGUIDToUtf8StrA(pGUID);
661 LogRel(("DSound: Guest source '%s' is using host recording device with GUID '%s'\n",
662 PDMAudioRecSrcGetName(pCfg->u.enmSrc), pszGUID ? pszGUID: "{?}"));
663 RTStrFree(pszGUID);
664
665 return pGUID;
666}
667#endif
668
669
670/**
671 * Creates a DirectSound capture instance.
672 *
673 * @returns HRESULT
674 * @param pGUID GUID of device to create the capture interface for. NULL
675 * for default.
676 * @param ppDSC Where to return the interface to the created instance.
677 */
678static HRESULT drvHostDSoundCreateDSCaptureInstance(LPCGUID pGUID, LPDIRECTSOUNDCAPTURE8 *ppDSC)
679{
680 LogFlowFuncEnter();
681
682 LPDIRECTSOUNDCAPTURE8 pDSC = NULL;
683 HRESULT hrc = CoCreateInstance(CLSID_DirectSoundCapture8, NULL, CLSCTX_ALL, IID_IDirectSoundCapture8, (void **)&pDSC);
684 if (SUCCEEDED(hrc))
685 {
686 hrc = IDirectSoundCapture_Initialize(pDSC, pGUID);
687 if (SUCCEEDED(hrc))
688 {
689 *ppDSC = pDSC;
690 LogFlowFunc(("LEAVE S_OK\n"));
691 return S_OK;
692 }
693 if (hrc == DSERR_NODRIVER) /* Usually means that no capture devices are attached. */
694 LogRelMax(64, ("DSound: Capture device currently is unavailable\n"));
695 else
696 LogRelMax(64, ("DSound: Initializing capturing device failed: %Rhrc\n", hrc));
697 IDirectSoundCapture_Release(pDSC);
698 }
699 else
700 LogRelMax(64, ("DSound: Creating capture instance failed: %Rhrc\n", hrc));
701
702 LogFlowFunc(("LEAVE %Rhrc\n", hrc));
703 return hrc;
704}
705
706
707/**
708 * Updates this host driver's internal status, according to the global, overall input/output
709 * state and all connected (native) audio streams.
710 *
711 * @todo r=bird: This is a 'ing waste of 'ing time! We're doing this everytime
712 * an 'ing stream is created and we doesn't 'ing use the information here
713 * for any darn thing! Given the reported slowness of enumeration and
714 * issues with the 'ing code the only appropriate response is:
715 * AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARG!!!!!!!
716 *
717 * @param pThis Host audio driver instance.
718 */
719static void dsoundUpdateStatusInternal(PDRVHOSTDSOUND pThis)
720{
721#if 0 /** @todo r=bird: This isn't doing *ANYTHING* useful. So, I've just disabled it. */
722 AssertPtrReturnVoid(pThis);
723 LogFlowFuncEnter();
724
725 PDMAudioHostEnumDelete(&pThis->DeviceEnum);
726 int rc = dsoundDevicesEnumerate(pThis, &pThis->DeviceEnum);
727 if (RT_SUCCESS(rc))
728 {
729#if 0
730 if ( pThis->fEnabledOut != RT_BOOL(cbCtx.cDevOut)
731 || pThis->fEnabledIn != RT_BOOL(cbCtx.cDevIn))
732 {
733 /** @todo Use a registered callback to the audio connector (e.g "OnConfigurationChanged") to
734 * let the connector know that something has changed within the host backend. */
735 }
736#endif
737 pThis->fEnabledIn = PDMAudioHostEnumCountMatching(&pThis->DeviceEnum, PDMAUDIODIR_IN) != 0;
738 pThis->fEnabledOut = PDMAudioHostEnumCountMatching(&pThis->DeviceEnum, PDMAUDIODIR_OUT) != 0;
739 }
740
741 LogFlowFuncLeaveRC(rc);
742#else
743 RT_NOREF(pThis);
744#endif
745}
746
747
748/*********************************************************************************************************************************
749* PDMIHOSTAUDIO *
750*********************************************************************************************************************************/
751
752/**
753 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
754 */
755static DECLCALLBACK(int) drvHostDSoundHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
756{
757 RT_NOREF(pInterface);
758 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
759 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
760
761
762 /*
763 * Fill in the config structure.
764 */
765 RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "DirectSound");
766 pBackendCfg->cbStream = sizeof(DSOUNDSTREAM);
767 pBackendCfg->fFlags = 0;
768 pBackendCfg->cMaxStreamsIn = UINT32_MAX;
769 pBackendCfg->cMaxStreamsOut = UINT32_MAX;
770
771 return VINF_SUCCESS;
772}
773
774
775/**
776 * Callback for the playback device enumeration.
777 *
778 * @return TRUE if continuing enumeration, FALSE if not.
779 * @param pGUID Pointer to GUID of enumerated device. Can be NULL.
780 * @param pwszDescription Pointer to (friendly) description of enumerated device.
781 * @param pwszModule Pointer to module name of enumerated device.
782 * @param lpContext Pointer to PDSOUNDENUMCBCTX context for storing the enumerated information.
783 *
784 * @note Carbon copy of drvHostDSoundEnumOldStyleCaptureCallback with OUT direction.
785 */
786static BOOL CALLBACK drvHostDSoundEnumOldStylePlaybackCallback(LPGUID pGUID, LPCWSTR pwszDescription,
787 LPCWSTR pwszModule, PVOID lpContext)
788{
789 PDSOUNDENUMCBCTX pEnumCtx = (PDSOUNDENUMCBCTX)lpContext;
790 AssertPtrReturn(pEnumCtx, FALSE);
791
792 PPDMAUDIOHOSTENUM pDevEnm = pEnumCtx->pDevEnm;
793 AssertPtrReturn(pDevEnm, FALSE);
794
795 AssertPtrNullReturn(pGUID, FALSE); /* pGUID can be NULL for default device(s). */
796 AssertPtrReturn(pwszDescription, FALSE);
797 RT_NOREF(pwszModule); /* Do not care about pwszModule. */
798
799 int rc;
800 PDSOUNDDEV pDev = (PDSOUNDDEV)PDMAudioHostDevAlloc(sizeof(DSOUNDDEV));
801 if (pDev)
802 {
803 pDev->Core.enmUsage = PDMAUDIODIR_OUT;
804 pDev->Core.enmType = PDMAUDIODEVICETYPE_BUILTIN;
805
806 if (pGUID == NULL)
807 pDev->Core.fFlags = PDMAUDIOHOSTDEV_F_DEFAULT;
808
809 char *pszName;
810 rc = RTUtf16ToUtf8(pwszDescription, &pszName);
811 if (RT_SUCCESS(rc))
812 {
813 RTStrCopy(pDev->Core.szName, sizeof(pDev->Core.szName), pszName);
814 RTStrFree(pszName);
815
816 if (pGUID) /* pGUID == NULL means default device. */
817 memcpy(&pDev->Guid, pGUID, sizeof(pDev->Guid));
818
819 PDMAudioHostEnumAppend(pDevEnm, &pDev->Core);
820
821 /* Note: Querying the actual device information will be done at some
822 * later point in time outside this enumeration callback to prevent
823 * DSound hangs. */
824 return TRUE;
825 }
826 PDMAudioHostDevFree(&pDev->Core);
827 }
828 else
829 rc = VERR_NO_MEMORY;
830
831 LogRel(("DSound: Error enumeration playback device '%ls': rc=%Rrc\n", pwszDescription, rc));
832 return FALSE; /* Abort enumeration. */
833}
834
835
836/**
837 * Callback for the capture device enumeration.
838 *
839 * @return TRUE if continuing enumeration, FALSE if not.
840 * @param pGUID Pointer to GUID of enumerated device. Can be NULL.
841 * @param pwszDescription Pointer to (friendly) description of enumerated device.
842 * @param pwszModule Pointer to module name of enumerated device.
843 * @param lpContext Pointer to PDSOUNDENUMCBCTX context for storing the enumerated information.
844 *
845 * @note Carbon copy of drvHostDSoundEnumOldStylePlaybackCallback with IN direction.
846 */
847static BOOL CALLBACK drvHostDSoundEnumOldStyleCaptureCallback(LPGUID pGUID, LPCWSTR pwszDescription,
848 LPCWSTR pwszModule, PVOID lpContext)
849{
850 PDSOUNDENUMCBCTX pEnumCtx = (PDSOUNDENUMCBCTX )lpContext;
851 AssertPtrReturn(pEnumCtx, FALSE);
852
853 PPDMAUDIOHOSTENUM pDevEnm = pEnumCtx->pDevEnm;
854 AssertPtrReturn(pDevEnm, FALSE);
855
856 AssertPtrNullReturn(pGUID, FALSE); /* pGUID can be NULL for default device(s). */
857 AssertPtrReturn(pwszDescription, FALSE);
858 RT_NOREF(pwszModule); /* Do not care about pwszModule. */
859
860 int rc;
861 PDSOUNDDEV pDev = (PDSOUNDDEV)PDMAudioHostDevAlloc(sizeof(DSOUNDDEV));
862 if (pDev)
863 {
864 pDev->Core.enmUsage = PDMAUDIODIR_IN;
865 pDev->Core.enmType = PDMAUDIODEVICETYPE_BUILTIN;
866
867 char *pszName;
868 rc = RTUtf16ToUtf8(pwszDescription, &pszName);
869 if (RT_SUCCESS(rc))
870 {
871 RTStrCopy(pDev->Core.szName, sizeof(pDev->Core.szName), pszName);
872 RTStrFree(pszName);
873
874 if (pGUID) /* pGUID == NULL means default capture device. */
875 memcpy(&pDev->Guid, pGUID, sizeof(pDev->Guid));
876
877 PDMAudioHostEnumAppend(pDevEnm, &pDev->Core);
878
879 /* Note: Querying the actual device information will be done at some
880 * later point in time outside this enumeration callback to prevent
881 * DSound hangs. */
882 return TRUE;
883 }
884 PDMAudioHostDevFree(&pDev->Core);
885 }
886 else
887 rc = VERR_NO_MEMORY;
888
889 LogRel(("DSound: Error enumeration capture device '%ls', rc=%Rrc\n", pwszDescription, rc));
890 return FALSE; /* Abort enumeration. */
891}
892
893
894/**
895 * Queries information for a given (DirectSound) device.
896 *
897 * @returns VBox status code.
898 * @param pDev Audio device to query information for.
899 */
900static int drvHostDSoundEnumOldStyleQueryDeviceInfo(PDSOUNDDEV pDev)
901{
902 AssertPtr(pDev);
903 int rc;
904
905 if (pDev->Core.enmUsage == PDMAUDIODIR_OUT)
906 {
907 LPDIRECTSOUND8 pDS;
908 HRESULT hr = drvHostDSoundCreateDSPlaybackInstance(&pDev->Guid, &pDS);
909 if (SUCCEEDED(hr))
910 {
911 DSCAPS DSCaps;
912 RT_ZERO(DSCaps);
913 DSCaps.dwSize = sizeof(DSCAPS);
914 hr = IDirectSound_GetCaps(pDS, &DSCaps);
915 if (SUCCEEDED(hr))
916 {
917 pDev->Core.cMaxOutputChannels = DSCaps.dwFlags & DSCAPS_PRIMARYSTEREO ? 2 : 1;
918
919 DWORD dwSpeakerCfg;
920 hr = IDirectSound_GetSpeakerConfig(pDS, &dwSpeakerCfg);
921 if (SUCCEEDED(hr))
922 {
923 unsigned uSpeakerCount = 0;
924 switch (DSSPEAKER_CONFIG(dwSpeakerCfg))
925 {
926 case DSSPEAKER_MONO: uSpeakerCount = 1; break;
927 case DSSPEAKER_HEADPHONE: uSpeakerCount = 2; break;
928 case DSSPEAKER_STEREO: uSpeakerCount = 2; break;
929 case DSSPEAKER_QUAD: uSpeakerCount = 4; break;
930 case DSSPEAKER_SURROUND: uSpeakerCount = 4; break;
931 case DSSPEAKER_5POINT1: uSpeakerCount = 6; break;
932 case DSSPEAKER_5POINT1_SURROUND: uSpeakerCount = 6; break;
933 case DSSPEAKER_7POINT1: uSpeakerCount = 8; break;
934 case DSSPEAKER_7POINT1_SURROUND: uSpeakerCount = 8; break;
935 default: break;
936 }
937
938 if (uSpeakerCount) /* Do we need to update the channel count? */
939 pDev->Core.cMaxOutputChannels = uSpeakerCount;
940
941 rc = VINF_SUCCESS;
942 }
943 else
944 {
945 LogRel(("DSound: Error retrieving playback device speaker config, hr=%Rhrc\n", hr));
946 rc = VERR_ACCESS_DENIED; /** @todo Fudge! */
947 }
948 }
949 else
950 {
951 LogRel(("DSound: Error retrieving playback device capabilities, hr=%Rhrc\n", hr));
952 rc = VERR_ACCESS_DENIED; /** @todo Fudge! */
953 }
954
955 IDirectSound8_Release(pDS);
956 }
957 else
958 rc = VERR_GENERAL_FAILURE;
959 }
960 else if (pDev->Core.enmUsage == PDMAUDIODIR_IN)
961 {
962 LPDIRECTSOUNDCAPTURE8 pDSC;
963 HRESULT hr = drvHostDSoundCreateDSCaptureInstance(&pDev->Guid, &pDSC);
964 if (SUCCEEDED(hr))
965 {
966 DSCCAPS DSCCaps;
967 RT_ZERO(DSCCaps);
968 DSCCaps.dwSize = sizeof(DSCCAPS);
969 hr = IDirectSoundCapture_GetCaps(pDSC, &DSCCaps);
970 if (SUCCEEDED(hr))
971 {
972 pDev->Core.cMaxInputChannels = DSCCaps.dwChannels;
973 rc = VINF_SUCCESS;
974 }
975 else
976 {
977 LogRel(("DSound: Error retrieving capture device capabilities, hr=%Rhrc\n", hr));
978 rc = VERR_ACCESS_DENIED; /** @todo Fudge! */
979 }
980
981 IDirectSoundCapture_Release(pDSC);
982 }
983 else
984 rc = VERR_GENERAL_FAILURE;
985 }
986 else
987 AssertFailedStmt(rc = VERR_NOT_SUPPORTED);
988
989 return rc;
990}
991
992
993/**
994 * Queries information for @a pDevice and adds an entry to the enumeration.
995 *
996 * @returns VBox status code.
997 * @param pDevEnm The enumeration to add the device to.
998 * @param pDevice The device.
999 * @param enmType The type of device.
1000 * @param fDefault Whether it's the default device.
1001 */
1002static int drvHostDSoundEnumNewStyleAdd(PPDMAUDIOHOSTENUM pDevEnm, IMMDevice *pDevice, EDataFlow enmType, bool fDefault)
1003{
1004 int rc = VINF_SUCCESS; /* ignore most errors */
1005
1006 /*
1007 * Gather the necessary properties.
1008 */
1009 IPropertyStore *pProperties = NULL;
1010 HRESULT hrc = pDevice->OpenPropertyStore(STGM_READ, &pProperties);
1011 if (SUCCEEDED(hrc))
1012 {
1013 /* Get the friendly name. */
1014 PROPVARIANT VarName;
1015 PropVariantInit(&VarName);
1016 hrc = pProperties->GetValue(PKEY_Device_FriendlyName, &VarName);
1017 if (SUCCEEDED(hrc))
1018 {
1019 /* Get the DirectSound GUID. */
1020 PROPVARIANT VarGUID;
1021 PropVariantInit(&VarGUID);
1022 hrc = pProperties->GetValue(PKEY_AudioEndpoint_GUID, &VarGUID);
1023 if (SUCCEEDED(hrc))
1024 {
1025 /* Get the device format. */
1026 PROPVARIANT VarFormat;
1027 PropVariantInit(&VarFormat);
1028 hrc = pProperties->GetValue(PKEY_AudioEngine_DeviceFormat, &VarFormat);
1029 if (SUCCEEDED(hrc))
1030 {
1031 WAVEFORMATEX const * const pFormat = (WAVEFORMATEX const *)VarFormat.blob.pBlobData;
1032 AssertPtr(pFormat);
1033
1034 /*
1035 * Create a enumeration entry for it.
1036 */
1037 PDSOUNDDEV pDev = (PDSOUNDDEV)PDMAudioHostDevAlloc(sizeof(DSOUNDDEV));
1038 if (pDev)
1039 {
1040 pDev->Core.enmUsage = enmType == eRender ? PDMAUDIODIR_OUT : PDMAUDIODIR_IN;
1041 pDev->Core.enmType = PDMAUDIODEVICETYPE_BUILTIN;
1042 if (enmType == eRender)
1043 pDev->Core.cMaxOutputChannels = pFormat->nChannels;
1044 else
1045 pDev->Core.cMaxInputChannels = pFormat->nChannels;
1046
1047 RT_NOREF(fDefault);
1048 //if (fDefault)
1049 hrc = UuidFromStringW(VarGUID.pwszVal, &pDev->Guid);
1050 if (SUCCEEDED(hrc))
1051 {
1052 char *pszName;
1053 rc = RTUtf16ToUtf8(VarName.pwszVal, &pszName);
1054 if (RT_SUCCESS(rc))
1055 {
1056 RTStrCopy(pDev->Core.szName, sizeof(pDev->Core.szName), pszName);
1057 RTStrFree(pszName);
1058
1059 PDMAudioHostEnumAppend(pDevEnm, &pDev->Core);
1060 }
1061 else
1062 PDMAudioHostDevFree(&pDev->Core);
1063 }
1064 else
1065 {
1066 LogFunc(("UuidFromStringW(%ls): %Rhrc\n", VarGUID.pwszVal, hrc));
1067 PDMAudioHostDevFree(&pDev->Core);
1068 }
1069 }
1070 else
1071 rc = VERR_NO_MEMORY;
1072 PropVariantClear(&VarFormat);
1073 }
1074 else
1075 LogFunc(("Failed to get PKEY_AudioEngine_DeviceFormat: %Rhrc\n", hrc));
1076 PropVariantClear(&VarGUID);
1077 }
1078 else
1079 LogFunc(("Failed to get PKEY_AudioEndpoint_GUID: %Rhrc\n", hrc));
1080 PropVariantClear(&VarName);
1081 }
1082 else
1083 LogFunc(("Failed to get PKEY_Device_FriendlyName: %Rhrc\n", hrc));
1084 pProperties->Release();
1085 }
1086 else
1087 LogFunc(("OpenPropertyStore failed: %Rhrc\n", hrc));
1088
1089 if (hrc == E_OUTOFMEMORY && RT_SUCCESS_NP(rc))
1090 rc = VERR_NO_MEMORY;
1091 return rc;
1092}
1093
1094
1095/**
1096 * Does a (Re-)enumeration of the host's playback + capturing devices.
1097 *
1098 * @return VBox status code.
1099 * @param pDevEnm Where to store the enumerated devices.
1100 */
1101static int drvHostDSoundEnumerateDevices(PPDMAUDIOHOSTENUM pDevEnm)
1102{
1103 DSLOG(("DSound: Enumerating devices ...\n"));
1104
1105 /*
1106 * Use the Vista+ API.
1107 */
1108 IMMDeviceEnumerator *pEnumerator;
1109 HRESULT hrc = CoCreateInstance(__uuidof(MMDeviceEnumerator), 0, CLSCTX_ALL,
1110 __uuidof(IMMDeviceEnumerator), (void **)&pEnumerator);
1111 if (SUCCEEDED(hrc))
1112 {
1113 int rc = VINF_SUCCESS;
1114 for (unsigned idxPass = 0; idxPass < 2 && RT_SUCCESS(rc); idxPass++)
1115 {
1116 EDataFlow const enmType = idxPass == 0 ? EDataFlow::eRender : EDataFlow::eCapture;
1117
1118 /* Get the default device first. */
1119 IMMDevice *pDefaultDevice = NULL;
1120 hrc = pEnumerator->GetDefaultAudioEndpoint(enmType, eMultimedia, &pDefaultDevice);
1121 if (SUCCEEDED(hrc))
1122 rc = drvHostDSoundEnumNewStyleAdd(pDevEnm, pDefaultDevice, enmType, true);
1123 else
1124 pDefaultDevice = NULL;
1125
1126 /* Enumerate the devices. */
1127 IMMDeviceCollection *pCollection = NULL;
1128 hrc = pEnumerator->EnumAudioEndpoints(enmType, DEVICE_STATE_ACTIVE /*| DEVICE_STATE_UNPLUGGED?*/, &pCollection);
1129 if (SUCCEEDED(hrc) && pCollection != NULL)
1130 {
1131 UINT cDevices = 0;
1132 hrc = pCollection->GetCount(&cDevices);
1133 if (SUCCEEDED(hrc))
1134 {
1135 for (UINT idxDevice = 0; idxDevice < cDevices && RT_SUCCESS(rc); idxDevice++)
1136 {
1137 IMMDevice *pDevice = NULL;
1138 hrc = pCollection->Item(idxDevice, &pDevice);
1139 if (SUCCEEDED(hrc) && pDevice)
1140 {
1141 if (pDevice != pDefaultDevice)
1142 rc = drvHostDSoundEnumNewStyleAdd(pDevEnm, pDevice, enmType, false);
1143 pDevice->Release();
1144 }
1145 }
1146 }
1147 pCollection->Release();
1148 }
1149 else
1150 LogRelMax(10, ("EnumAudioEndpoints(%s) failed: %Rhrc\n", idxPass == 0 ? "output" : "input", hrc));
1151
1152 if (pDefaultDevice)
1153 pDefaultDevice->Release();
1154 }
1155 pEnumerator->Release();
1156 if (pDevEnm->cDevices > 0 || RT_FAILURE(rc))
1157 {
1158 DSLOG(("DSound: Enumerating devices done - %u device (%Rrc)\n", pDevEnm->cDevices, rc));
1159 return rc;
1160 }
1161 }
1162
1163 /*
1164 * Fall back to dsound.
1165 */
1166 /* Resolve symbols once. */
1167 static PFNDIRECTSOUNDENUMERATEW volatile s_pfnDirectSoundEnumerateW = NULL;
1168 static PFNDIRECTSOUNDCAPTUREENUMERATEW volatile s_pfnDirectSoundCaptureEnumerateW = NULL;
1169
1170 PFNDIRECTSOUNDENUMERATEW pfnDirectSoundEnumerateW = s_pfnDirectSoundEnumerateW;
1171 PFNDIRECTSOUNDCAPTUREENUMERATEW pfnDirectSoundCaptureEnumerateW = s_pfnDirectSoundCaptureEnumerateW;
1172 if (!pfnDirectSoundEnumerateW || !pfnDirectSoundCaptureEnumerateW)
1173 {
1174 RTLDRMOD hModDSound = NIL_RTLDRMOD;
1175 int rc = RTLdrLoadSystem("dsound.dll", true /*fNoUnload*/, &hModDSound);
1176 if (RT_SUCCESS(rc))
1177 {
1178 rc = RTLdrGetSymbol(hModDSound, "DirectSoundEnumerateW", (void **)&pfnDirectSoundEnumerateW);
1179 if (RT_SUCCESS(rc))
1180 s_pfnDirectSoundEnumerateW = pfnDirectSoundEnumerateW;
1181 else
1182 LogRel(("DSound: Failed to get dsound.dll export DirectSoundEnumerateW: %Rrc\n", rc));
1183
1184 rc = RTLdrGetSymbol(hModDSound, "DirectSoundCaptureEnumerateW", (void **)&pfnDirectSoundCaptureEnumerateW);
1185 if (RT_SUCCESS(rc))
1186 s_pfnDirectSoundCaptureEnumerateW = pfnDirectSoundCaptureEnumerateW;
1187 else
1188 LogRel(("DSound: Failed to get dsound.dll export DirectSoundCaptureEnumerateW: %Rrc\n", rc));
1189 RTLdrClose(hModDSound);
1190 }
1191 else
1192 LogRel(("DSound: Unable to load dsound.dll for enumerating devices: %Rrc\n", rc));
1193 if (!pfnDirectSoundEnumerateW && !pfnDirectSoundCaptureEnumerateW)
1194 return rc;
1195 }
1196
1197 /* Common callback context for both playback and capture enumerations: */
1198 DSOUNDENUMCBCTX EnumCtx;
1199 EnumCtx.fFlags = 0;
1200 EnumCtx.pDevEnm = pDevEnm;
1201
1202 /* Enumerate playback devices. */
1203 if (pfnDirectSoundEnumerateW)
1204 {
1205 DSLOG(("DSound: Enumerating playback devices ...\n"));
1206 HRESULT hr = pfnDirectSoundEnumerateW(&drvHostDSoundEnumOldStylePlaybackCallback, &EnumCtx);
1207 if (FAILED(hr))
1208 LogRel(("DSound: Error enumerating host playback devices: %Rhrc\n", hr));
1209 }
1210
1211 /* Enumerate capture devices. */
1212 if (pfnDirectSoundCaptureEnumerateW)
1213 {
1214 DSLOG(("DSound: Enumerating capture devices ...\n"));
1215 HRESULT hr = pfnDirectSoundCaptureEnumerateW(&drvHostDSoundEnumOldStyleCaptureCallback, &EnumCtx);
1216 if (FAILED(hr))
1217 LogRel(("DSound: Error enumerating host capture devices: %Rhrc\n", hr));
1218 }
1219
1220 /*
1221 * Query Information for all enumerated devices.
1222 * Note! This is problematic to do from the enumeration callbacks.
1223 */
1224 PDSOUNDDEV pDev;
1225 RTListForEach(&pDevEnm->LstDevices, pDev, DSOUNDDEV, Core.ListEntry)
1226 {
1227 drvHostDSoundEnumOldStyleQueryDeviceInfo(pDev); /* ignore rc */
1228 }
1229
1230 DSLOG(("DSound: Enumerating devices done\n"));
1231
1232 return VINF_SUCCESS;
1233}
1234
1235
1236/**
1237 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices}
1238 */
1239static DECLCALLBACK(int) drvHostDSoundHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum)
1240{
1241 RT_NOREF(pInterface);
1242 AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER);
1243
1244 PDMAudioHostEnumInit(pDeviceEnum);
1245 int rc = drvHostDSoundEnumerateDevices(pDeviceEnum);
1246 if (RT_FAILURE(rc))
1247 PDMAudioHostEnumDelete(pDeviceEnum);
1248
1249 LogFlowFunc(("Returning %Rrc\n", rc));
1250 return rc;
1251}
1252
1253
1254/**
1255 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
1256 */
1257static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostDSoundHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
1258{
1259 RT_NOREF(pInterface, enmDir);
1260 return PDMAUDIOBACKENDSTS_RUNNING;
1261}
1262
1263
1264/**
1265 * Converts from PDM stream config to windows WAVEFORMATEX struct.
1266 *
1267 * @param pCfg The PDM audio stream config to convert from.
1268 * @param pFmt The windows structure to initialize.
1269 */
1270static void dsoundWaveFmtFromCfg(PCPDMAUDIOSTREAMCFG pCfg, PWAVEFORMATEX pFmt)
1271{
1272 RT_ZERO(*pFmt);
1273 pFmt->wFormatTag = WAVE_FORMAT_PCM;
1274 pFmt->nChannels = PDMAudioPropsChannels(&pCfg->Props);
1275 pFmt->wBitsPerSample = PDMAudioPropsSampleBits(&pCfg->Props);
1276 pFmt->nSamplesPerSec = PDMAudioPropsHz(&pCfg->Props);
1277 pFmt->nBlockAlign = PDMAudioPropsFrameSize(&pCfg->Props);
1278 pFmt->nAvgBytesPerSec = PDMAudioPropsFramesToBytes(&pCfg->Props, PDMAudioPropsHz(&pCfg->Props));
1279 pFmt->cbSize = 0; /* No extra data specified. */
1280}
1281
1282
1283/**
1284 * Resets the state of a DirectSound stream, clearing the buffer content.
1285 *
1286 * @param pThis Host audio driver instance.
1287 * @param pStreamDS Stream to reset state for.
1288 */
1289static void drvHostDSoundStreamReset(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
1290{
1291 RT_NOREF(pThis);
1292 LogFunc(("Resetting %s\n", pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN ? "capture" : "playback"));
1293
1294 if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
1295 {
1296 /*
1297 * Input streams.
1298 */
1299 LogFunc(("Resetting capture stream '%s'\n", pStreamDS->Cfg.szName));
1300
1301 /* Reset the state: */
1302 pStreamDS->msLastTransfer = 0;
1303/** @todo r=bird: We set the read position to zero here, but shouldn't we query it
1304 * from the buffer instead given that there isn't any interface for repositioning
1305 * to the start of the buffer as with playback buffers? */
1306 pStreamDS->In.offReadPos = 0;
1307 pStreamDS->In.cOverruns = 0;
1308
1309 /* Clear the buffer content: */
1310 AssertPtr(pStreamDS->In.pDSCB);
1311 if (pStreamDS->In.pDSCB)
1312 {
1313 PVOID pv1 = NULL;
1314 DWORD cb1 = 0;
1315 PVOID pv2 = NULL;
1316 DWORD cb2 = 0;
1317 HRESULT hrc = IDirectSoundCaptureBuffer8_Lock(pStreamDS->In.pDSCB, 0, pStreamDS->cbBufSize,
1318 &pv1, &cb1, &pv2, &cb2, 0 /*fFlags*/);
1319 if (SUCCEEDED(hrc))
1320 {
1321 PDMAudioPropsClearBuffer(&pStreamDS->Cfg.Props, pv1, cb1, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb1));
1322 if (pv2 && cb2)
1323 PDMAudioPropsClearBuffer(&pStreamDS->Cfg.Props, pv2, cb2, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb2));
1324 hrc = IDirectSoundCaptureBuffer8_Unlock(pStreamDS->In.pDSCB, pv1, cb1, pv2, cb2);
1325 if (FAILED(hrc))
1326 LogRelMaxFunc(64, ("DSound: Unlocking capture buffer '%s' after reset failed: %Rhrc\n",
1327 pStreamDS->Cfg.szName, hrc));
1328 }
1329 else
1330 LogRelMaxFunc(64, ("DSound: Locking capture buffer '%s' for reset failed: %Rhrc\n",
1331 pStreamDS->Cfg.szName, hrc));
1332 }
1333 }
1334 else
1335 {
1336 /*
1337 * Output streams.
1338 */
1339 Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT);
1340 LogFunc(("Resetting playback stream '%s'\n", pStreamDS->Cfg.szName));
1341
1342 /* If draining was enagaged, make sure dsound has stopped playing: */
1343 if (pStreamDS->Out.fDrain && pStreamDS->Out.pDSB)
1344 pStreamDS->Out.pDSB->Stop();
1345
1346 /* Reset the internal state: */
1347 pStreamDS->msLastTransfer = 0;
1348 pStreamDS->Out.fFirstTransfer = true;
1349 pStreamDS->Out.fDrain = false;
1350 pStreamDS->Out.cbLastTransferred = 0;
1351 pStreamDS->Out.cbTransferred = 0;
1352 pStreamDS->Out.cbWritten = 0;
1353 pStreamDS->Out.offWritePos = 0;
1354 pStreamDS->Out.offPlayCursorLastPending = 0;
1355 pStreamDS->Out.offPlayCursorLastPlayed = 0;
1356
1357 /* Reset the buffer content and repositioning the buffer to the start of the buffer. */
1358 AssertPtr(pStreamDS->Out.pDSB);
1359 if (pStreamDS->Out.pDSB)
1360 {
1361 HRESULT hrc = IDirectSoundBuffer8_SetCurrentPosition(pStreamDS->Out.pDSB, 0);
1362 if (FAILED(hrc))
1363 LogRelMaxFunc(64, ("DSound: Failed to set buffer position for '%s': %Rhrc\n", pStreamDS->Cfg.szName, hrc));
1364
1365 PVOID pv1 = NULL;
1366 DWORD cb1 = 0;
1367 PVOID pv2 = NULL;
1368 DWORD cb2 = 0;
1369 hrc = IDirectSoundBuffer8_Lock(pStreamDS->Out.pDSB, 0, pStreamDS->cbBufSize, &pv1, &cb1, &pv2, &cb2, 0 /*fFlags*/);
1370 if (hrc == DSERR_BUFFERLOST)
1371 {
1372 directSoundPlayRestore(pThis, pStreamDS->Out.pDSB);
1373 hrc = IDirectSoundBuffer8_Lock(pStreamDS->Out.pDSB, 0, pStreamDS->cbBufSize, &pv1, &cb1, &pv2, &cb2, 0 /*fFlags*/);
1374 }
1375 if (SUCCEEDED(hrc))
1376 {
1377 PDMAudioPropsClearBuffer(&pStreamDS->Cfg.Props, pv1, cb1, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb1));
1378 if (pv2 && cb2)
1379 PDMAudioPropsClearBuffer(&pStreamDS->Cfg.Props, pv2, cb2, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb2));
1380
1381 hrc = IDirectSoundBuffer8_Unlock(pStreamDS->Out.pDSB, pv1, cb1, pv2, cb2);
1382 if (FAILED(hrc))
1383 LogRelMaxFunc(64, ("DSound: Unlocking playback buffer '%s' after reset failed: %Rhrc\n",
1384 pStreamDS->Cfg.szName, hrc));
1385 }
1386 else
1387 LogRelMaxFunc(64, ("DSound: Locking playback buffer '%s' for reset failed: %Rhrc\n", pStreamDS->Cfg.szName, hrc));
1388 }
1389 }
1390}
1391
1392
1393/**
1394 * Worker for drvHostDSoundHA_StreamCreate that creates caputre stream.
1395 *
1396 * @returns Windows COM status code.
1397 * @param pThis The DSound instance data.
1398 * @param pStreamDS The stream instance data.
1399 * @param pCfgReq The requested stream config (input).
1400 * @param pCfgAcq Where to return the actual stream config. This is a
1401 * copy of @a *pCfgReq when called.
1402 * @param pWaveFmtX On input the requested stream format.
1403 * Updated to the actual stream format on successful
1404 * return.
1405 */
1406static HRESULT drvHostDSoundStreamCreateCapture(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS,
1407 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq, WAVEFORMATEX *pWaveFmtX)
1408{
1409 Assert(pStreamDS->In.pDSCB == NULL);
1410 HRESULT hrc;
1411
1412 /*
1413 * Create, initialize and set up a IDirectSoundCapture instance the first time
1414 * we go thru here.
1415 */
1416 /** @todo bird: Or should we rather just throw this away after we've gotten the
1417 * capture buffer? Old code would just leak it... */
1418 if (pThis->pDSC == NULL)
1419 {
1420 hrc = drvHostDSoundCreateDSCaptureInstance(pThis->Cfg.pGuidCapture, &pThis->pDSC);
1421 if (FAILED(hrc))
1422 return hrc; /* The worker has complained to the release log already. */
1423 }
1424
1425 /*
1426 * Create the capture buffer.
1427 */
1428 DSCBUFFERDESC BufferDesc =
1429 {
1430 /*.dwSize = */ sizeof(BufferDesc),
1431 /*.dwFlags = */ 0,
1432 /*.dwBufferBytes =*/ PDMAudioPropsFramesToBytes(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize),
1433 /*.dwReserved = */ 0,
1434 /*.lpwfxFormat = */ pWaveFmtX,
1435 /*.dwFXCount = */ 0,
1436 /*.lpDSCFXDesc = */ NULL
1437 };
1438
1439 LogRel2(("DSound: Requested capture buffer is %#x B / %u B / %RU64 ms\n", BufferDesc.dwBufferBytes, BufferDesc.dwBufferBytes,
1440 PDMAudioPropsBytesToMilli(&pCfgReq->Props, BufferDesc.dwBufferBytes)));
1441
1442 LPDIRECTSOUNDCAPTUREBUFFER pLegacyDSCB = NULL;
1443 hrc = IDirectSoundCapture_CreateCaptureBuffer(pThis->pDSC, &BufferDesc, &pLegacyDSCB, NULL);
1444 if (FAILED(hrc))
1445 {
1446 LogRelMax(64, ("DSound: Creating capture buffer for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc));
1447 return hrc;
1448 }
1449
1450 /* Get the IDirectSoundCaptureBuffer8 version of the interface. */
1451 hrc = IDirectSoundCaptureBuffer_QueryInterface(pLegacyDSCB, IID_IDirectSoundCaptureBuffer8, (void **)&pStreamDS->In.pDSCB);
1452 IDirectSoundCaptureBuffer_Release(pLegacyDSCB);
1453 if (FAILED(hrc))
1454 {
1455 LogRelMax(64, ("DSound: Querying IID_IDirectSoundCaptureBuffer8 for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc));
1456 return hrc;
1457 }
1458
1459 /*
1460 * Query the actual stream configuration.
1461 */
1462#if 0 /** @todo r=bird: WTF was this for? */
1463 DWORD offByteReadPos = 0;
1464 hrc = IDirectSoundCaptureBuffer8_GetCurrentPosition(pStreamDS->In.pDSCB, NULL, &offByteReadPos);
1465 if (FAILED(hrc))
1466 {
1467 offByteReadPos = 0;
1468 DSLOGREL(("DSound: Getting capture position failed with %Rhrc\n", hr));
1469 }
1470#endif
1471 RT_ZERO(*pWaveFmtX);
1472 hrc = IDirectSoundCaptureBuffer8_GetFormat(pStreamDS->In.pDSCB, pWaveFmtX, sizeof(*pWaveFmtX), NULL);
1473 if (SUCCEEDED(hrc))
1474 {
1475 /** @todo r=bird: We aren't converting/checking the pWaveFmtX content... */
1476
1477 DSCBCAPS BufferCaps = { /*.dwSize = */ sizeof(BufferCaps), 0, 0, 0 };
1478 hrc = IDirectSoundCaptureBuffer8_GetCaps(pStreamDS->In.pDSCB, &BufferCaps);
1479 if (SUCCEEDED(hrc))
1480 {
1481 LogRel2(("DSound: Acquired capture buffer capabilities for '%s':\n"
1482 "DSound: dwFlags = %#RX32\n"
1483 "DSound: dwBufferBytes = %#RX32 B / %RU32 B / %RU64 ms\n"
1484 "DSound: dwReserved = %#RX32\n",
1485 pCfgReq->szName, BufferCaps.dwFlags, BufferCaps.dwBufferBytes, BufferCaps.dwBufferBytes,
1486 PDMAudioPropsBytesToMilli(&pCfgReq->Props, BufferCaps.dwBufferBytes), BufferCaps.dwReserved ));
1487
1488 /* Update buffer related stuff: */
1489 pStreamDS->In.offReadPos = 0; /** @todo shouldn't we use offBytReadPos here to "read at the initial capture position"? */
1490 pStreamDS->cbBufSize = BufferCaps.dwBufferBytes;
1491 pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, BufferCaps.dwBufferBytes);
1492
1493#if 0 /** @todo r=bird: uAlign isn't set anywhere, so this hasn't been checking anything for a while... */
1494 if (bc.dwBufferBytes & pStreamDS->uAlign)
1495 DSLOGREL(("DSound: Capture GetCaps returned misaligned buffer: size %RU32, alignment %RU32\n",
1496 bc.dwBufferBytes, pStreamDS->uAlign + 1));
1497#endif
1498 LogFlow(("returns S_OK\n"));
1499 return S_OK;
1500 }
1501 LogRelMax(64, ("DSound: Getting capture buffer capabilities for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc));
1502 }
1503 else
1504 LogRelMax(64, ("DSound: Getting capture format for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc));
1505
1506 IDirectSoundCaptureBuffer8_Release(pStreamDS->In.pDSCB);
1507 pStreamDS->In.pDSCB = NULL;
1508 LogFlowFunc(("returns %Rhrc\n", hrc));
1509 return hrc;
1510}
1511
1512
1513/**
1514 * Worker for drvHostDSoundHA_StreamCreate that creates playback stream.
1515 *
1516 * @returns Windows COM status code.
1517 * @param pThis The DSound instance data.
1518 * @param pStreamDS The stream instance data.
1519 * @param pCfgReq The requested stream config (input).
1520 * @param pCfgAcq Where to return the actual stream config. This is a
1521 * copy of @a *pCfgReq when called.
1522 * @param pWaveFmtX On input the requested stream format.
1523 * Updated to the actual stream format on successful
1524 * return.
1525 */
1526static HRESULT drvHostDSoundStreamCreatePlayback(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS,
1527 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq, WAVEFORMATEX *pWaveFmtX)
1528{
1529 Assert(pStreamDS->Out.pDSB == NULL);
1530 HRESULT hrc;
1531
1532 /*
1533 * Create, initialize and set up a DirectSound8 instance the first time
1534 * we go thru here.
1535 */
1536 /** @todo bird: Or should we rather just throw this away after we've gotten the
1537 * sound buffer? Old code would just leak it... */
1538 if (pThis->pDS == NULL)
1539 {
1540 hrc = drvHostDSoundCreateDSPlaybackInstance(pThis->Cfg.pGuidPlay, &pThis->pDS);
1541 if (FAILED(hrc))
1542 return hrc; /* The worker has complained to the release log already. */
1543 }
1544
1545 /*
1546 * As we reuse our (secondary) buffer for playing out data as it comes in,
1547 * we're using this buffer as a so-called streaming buffer.
1548 *
1549 * See https://msdn.microsoft.com/en-us/library/windows/desktop/ee419014(v=vs.85).aspx
1550 *
1551 * However, as we do not want to use memory on the sound device directly
1552 * (as most modern audio hardware on the host doesn't have this anyway),
1553 * we're *not* going to use DSBCAPS_STATIC for that.
1554 *
1555 * Instead we're specifying DSBCAPS_LOCSOFTWARE, as this fits the bill
1556 * of copying own buffer data to our secondary's Direct Sound buffer.
1557 */
1558 DSBUFFERDESC BufferDesc =
1559 {
1560 /*.dwSize = */ sizeof(BufferDesc),
1561 /*.dwFlags = */ DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCSOFTWARE,
1562 /*.dwBufferBytes = */ PDMAudioPropsFramesToBytes(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize),
1563 /*.dwReserved = */ 0,
1564 /*.lpwfxFormat = */ pWaveFmtX
1565 /*.guid3DAlgorithm = {0, 0, 0, {0,0,0,0, 0,0,0,0}} */
1566 };
1567 LogRel2(("DSound: Requested playback buffer is %#x B / %u B / %RU64 ms\n", BufferDesc.dwBufferBytes, BufferDesc.dwBufferBytes,
1568 PDMAudioPropsBytesToMilli(&pCfgReq->Props, BufferDesc.dwBufferBytes)));
1569
1570 LPDIRECTSOUNDBUFFER pLegacyDSB = NULL;
1571 hrc = IDirectSound8_CreateSoundBuffer(pThis->pDS, &BufferDesc, &pLegacyDSB, NULL);
1572 if (FAILED(hrc))
1573 {
1574 LogRelMax(64, ("DSound: Creating playback sound buffer for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc));
1575 return hrc;
1576 }
1577
1578 /* Get the IDirectSoundBuffer8 version of the interface. */
1579 hrc = IDirectSoundBuffer_QueryInterface(pLegacyDSB, IID_IDirectSoundBuffer8, (PVOID *)&pStreamDS->Out.pDSB);
1580 IDirectSoundBuffer_Release(pLegacyDSB);
1581 if (FAILED(hrc))
1582 {
1583 LogRelMax(64, ("DSound: Querying IID_IDirectSoundBuffer8 for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc));
1584 return hrc;
1585 }
1586
1587 /*
1588 * Query the actual stream parameters, they may differ from what we requested.
1589 */
1590 RT_ZERO(*pWaveFmtX);
1591 hrc = IDirectSoundBuffer8_GetFormat(pStreamDS->Out.pDSB, pWaveFmtX, sizeof(*pWaveFmtX), NULL);
1592 if (SUCCEEDED(hrc))
1593 {
1594 /** @todo r=bird: We aren't converting/checking the pWaveFmtX content... */
1595
1596 DSBCAPS BufferCaps = { /*.dwSize = */ sizeof(BufferCaps), 0, 0, 0, 0 };
1597 hrc = IDirectSoundBuffer8_GetCaps(pStreamDS->Out.pDSB, &BufferCaps);
1598 if (SUCCEEDED(hrc))
1599 {
1600 LogRel2(("DSound: Acquired playback buffer capabilities for '%s':\n"
1601 "DSound: dwFlags = %#RX32\n"
1602 "DSound: dwBufferBytes = %#RX32 B / %RU32 B / %RU64 ms\n"
1603 "DSound: dwUnlockTransferRate = %RU32 KB/s\n"
1604 "DSound: dwPlayCpuOverhead = %RU32%%\n",
1605 pCfgReq->szName, BufferCaps.dwFlags, BufferCaps.dwBufferBytes, BufferCaps.dwBufferBytes,
1606 PDMAudioPropsBytesToMilli(&pCfgReq->Props, BufferCaps.dwBufferBytes),
1607 BufferCaps.dwUnlockTransferRate, BufferCaps.dwPlayCpuOverhead));
1608
1609 /* Update buffer related stuff: */
1610 pStreamDS->cbBufSize = BufferCaps.dwBufferBytes;
1611 pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, BufferCaps.dwBufferBytes);
1612 pCfgAcq->Backend.cFramesPeriod = pCfgAcq->Backend.cFramesBufferSize / 4; /* total fiction */
1613 pCfgAcq->Backend.cFramesPreBuffering = pCfgReq->Backend.cFramesPreBuffering * pCfgAcq->Backend.cFramesBufferSize
1614 / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
1615
1616#if 0 /** @todo r=bird: uAlign isn't set anywhere, so this hasn't been checking anything for a while... */
1617 if (bc.dwBufferBytes & pStreamDS->uAlign)
1618 DSLOGREL(("DSound: Playback capabilities returned misaligned buffer: size %RU32, alignment %RU32\n",
1619 bc.dwBufferBytes, pStreamDS->uAlign + 1));
1620#endif
1621 LogFlow(("returns S_OK\n"));
1622 return S_OK;
1623 }
1624 LogRelMax(64, ("DSound: Getting playback buffer capabilities for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc));
1625 }
1626 else
1627 LogRelMax(64, ("DSound: Getting playback format for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc));
1628
1629 IDirectSoundBuffer8_Release(pStreamDS->Out.pDSB);
1630 pStreamDS->Out.pDSB = NULL;
1631 LogFlowFunc(("returns %Rhrc\n", hrc));
1632 return hrc;
1633}
1634
1635
1636/**
1637 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
1638 */
1639static DECLCALLBACK(int) drvHostDSoundHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1640 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
1641{
1642 PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
1643 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
1644 AssertPtrReturn(pStreamDS, VERR_INVALID_POINTER);
1645 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
1646 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
1647 AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
1648 Assert(PDMAudioStrmCfgEquals(pCfgReq, pCfgAcq));
1649
1650 const char * const pszStreamType = pCfgReq->enmDir == PDMAUDIODIR_IN ? "capture" : "playback"; RT_NOREF(pszStreamType);
1651 LogFlowFunc(("enmSrc/Dst=%s '%s'\n",
1652 pCfgReq->enmDir == PDMAUDIODIR_IN ? PDMAudioRecSrcGetName(pCfgReq->u.enmSrc)
1653 : PDMAudioPlaybackDstGetName(pCfgReq->u.enmDst), pCfgReq->szName));
1654 RTListInit(&pStreamDS->ListEntry); /* paranoia */
1655
1656 /* For whatever reason: */
1657 dsoundUpdateStatusInternal(pThis);
1658
1659 /*
1660 * DSound has different COM interfaces for working with input and output
1661 * streams, so we'll quickly part ways here after some common format
1662 * specification setup and logging.
1663 */
1664#if defined(RTLOG_REL_ENABLED) || defined(LOG_ENABLED)
1665 char szTmp[64];
1666#endif
1667 LogRel2(("DSound: Opening %s stream '%s' (%s)\n", pCfgReq->szName, pszStreamType,
1668 PDMAudioPropsToString(&pCfgReq->Props, szTmp, sizeof(szTmp))));
1669
1670 WAVEFORMATEX WaveFmtX;
1671 dsoundWaveFmtFromCfg(pCfgReq, &WaveFmtX);
1672 LogRel2(("DSound: Requested %s format for '%s':\n"
1673 "DSound: wFormatTag = %RU16\n"
1674 "DSound: nChannels = %RU16\n"
1675 "DSound: nSamplesPerSec = %RU32\n"
1676 "DSound: nAvgBytesPerSec = %RU32\n"
1677 "DSound: nBlockAlign = %RU16\n"
1678 "DSound: wBitsPerSample = %RU16\n"
1679 "DSound: cbSize = %RU16\n",
1680 pszStreamType, pCfgReq->szName, WaveFmtX.wFormatTag, WaveFmtX.nChannels, WaveFmtX.nSamplesPerSec,
1681 WaveFmtX.nAvgBytesPerSec, WaveFmtX.nBlockAlign, WaveFmtX.wBitsPerSample, WaveFmtX.cbSize));
1682
1683 HRESULT hrc;
1684 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
1685 hrc = drvHostDSoundStreamCreateCapture(pThis, pStreamDS, pCfgReq, pCfgAcq, &WaveFmtX);
1686 else
1687 hrc = drvHostDSoundStreamCreatePlayback(pThis, pStreamDS, pCfgReq, pCfgAcq, &WaveFmtX);
1688 int rc;
1689 if (SUCCEEDED(hrc))
1690 {
1691 LogRel2(("DSound: Acquired %s format for '%s':\n"
1692 "DSound: wFormatTag = %RU16\n"
1693 "DSound: nChannels = %RU16\n"
1694 "DSound: nSamplesPerSec = %RU32\n"
1695 "DSound: nAvgBytesPerSec = %RU32\n"
1696 "DSound: nBlockAlign = %RU16\n"
1697 "DSound: wBitsPerSample = %RU16\n"
1698 "DSound: cbSize = %RU16\n",
1699 pszStreamType, pCfgReq->szName, WaveFmtX.wFormatTag, WaveFmtX.nChannels, WaveFmtX.nSamplesPerSec,
1700 WaveFmtX.nAvgBytesPerSec, WaveFmtX.nBlockAlign, WaveFmtX.wBitsPerSample, WaveFmtX.cbSize));
1701
1702 /*
1703 * Copy the acquired config and reset the stream (clears the buffer).
1704 */
1705 PDMAudioStrmCfgCopy(&pStreamDS->Cfg, pCfgAcq);
1706 drvHostDSoundStreamReset(pThis, pStreamDS);
1707
1708 RTCritSectEnter(&pThis->CritSect);
1709 RTListAppend(&pThis->HeadStreams, &pStreamDS->ListEntry);
1710 RTCritSectLeave(&pThis->CritSect);
1711
1712 rc = VINF_SUCCESS;
1713 }
1714 else
1715 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1716
1717 LogFlowFunc(("returns %Rrc\n", rc));
1718 return rc;
1719}
1720
1721
1722/**
1723 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
1724 */
1725static DECLCALLBACK(int) drvHostDSoundHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1726{
1727 PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
1728 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
1729 AssertPtrReturn(pStreamDS, VERR_INVALID_POINTER);
1730 LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
1731
1732 RTCritSectEnter(&pThis->CritSect);
1733 RTListNodeRemove(&pStreamDS->ListEntry);
1734 RTCritSectLeave(&pThis->CritSect);
1735
1736 if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
1737 {
1738 /*
1739 * Input.
1740 */
1741 if (pStreamDS->In.pDSCB)
1742 {
1743 HRESULT hrc = IDirectSoundCaptureBuffer_Stop(pStreamDS->In.pDSCB);
1744 if (FAILED(hrc))
1745 LogFunc(("IDirectSoundCaptureBuffer_Stop failed: %Rhrc\n", hrc));
1746
1747 drvHostDSoundStreamReset(pThis, pStreamDS);
1748
1749 IDirectSoundCaptureBuffer8_Release(pStreamDS->In.pDSCB);
1750 pStreamDS->In.pDSCB = NULL;
1751 }
1752 }
1753 else
1754 {
1755 /*
1756 * Output.
1757 */
1758 if (pStreamDS->Out.pDSB)
1759 {
1760 drvHostDSoundStreamStopPlayback(pThis, pStreamDS, true /*fReset*/);
1761
1762 IDirectSoundBuffer8_Release(pStreamDS->Out.pDSB);
1763 pStreamDS->Out.pDSB = NULL;
1764 }
1765 }
1766
1767 if (RTCritSectIsInitialized(&pStreamDS->CritSect))
1768 RTCritSectDelete(&pStreamDS->CritSect);
1769
1770 return VINF_SUCCESS;
1771}
1772
1773
1774/**
1775 * Worker for drvHostDSoundHA_StreamEnable and drvHostDSoundHA_StreamResume.
1776 *
1777 * This will try re-open the capture device if we're having trouble starting it.
1778 *
1779 * @returns VBox status code.
1780 * @param pThis The DSound host audio driver instance data.
1781 * @param pStreamDS The stream instance data.
1782 */
1783static int drvHostDSoundStreamCaptureStart(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
1784{
1785 /*
1786 * Check the stream status first.
1787 */
1788 int rc = VERR_AUDIO_STREAM_NOT_READY;
1789 if (pStreamDS->In.pDSCB)
1790 {
1791 DWORD fStatus = 0;
1792 HRESULT hrc = IDirectSoundCaptureBuffer8_GetStatus(pStreamDS->In.pDSCB, &fStatus);
1793 if (SUCCEEDED(hrc))
1794 {
1795 /*
1796 * Try start capturing if it's not already doing so.
1797 */
1798 if (!(fStatus & DSCBSTATUS_CAPTURING))
1799 {
1800 LogRel2(("DSound: Starting capture on '%s' ... \n", pStreamDS->Cfg.szName));
1801 hrc = IDirectSoundCaptureBuffer8_Start(pStreamDS->In.pDSCB, DSCBSTART_LOOPING);
1802 if (SUCCEEDED(hrc))
1803 rc = VINF_SUCCESS;
1804 else
1805 {
1806 /*
1807 * Failed to start, try re-create the capture buffer.
1808 */
1809 LogRelMax(64, ("DSound: Starting to capture on '%s' failed: %Rhrc - will try re-open it ...\n",
1810 pStreamDS->Cfg.szName, hrc));
1811
1812 IDirectSoundCaptureBuffer8_Release(pStreamDS->In.pDSCB);
1813 pStreamDS->In.pDSCB = NULL;
1814
1815 PDMAUDIOSTREAMCFG CfgReq = pStreamDS->Cfg;
1816 PDMAUDIOSTREAMCFG CfgAcq = pStreamDS->Cfg;
1817 WAVEFORMATEX WaveFmtX;
1818 dsoundWaveFmtFromCfg(&pStreamDS->Cfg, &WaveFmtX);
1819 hrc = drvHostDSoundStreamCreateCapture(pThis, pStreamDS, &CfgReq, &CfgAcq, &WaveFmtX);
1820 if (SUCCEEDED(hrc))
1821 {
1822 PDMAudioStrmCfgCopy(&pStreamDS->Cfg, &CfgAcq);
1823
1824 /*
1825 * Try starting capture again.
1826 */
1827 LogRel2(("DSound: Starting capture on re-opened '%s' ... \n", pStreamDS->Cfg.szName));
1828 hrc = IDirectSoundCaptureBuffer8_Start(pStreamDS->In.pDSCB, DSCBSTART_LOOPING);
1829 if (SUCCEEDED(hrc))
1830 rc = VINF_SUCCESS;
1831 else
1832 LogRelMax(64, ("DSound: Starting to capture on re-opened '%s' failed: %Rhrc\n",
1833 pStreamDS->Cfg.szName, hrc));
1834 }
1835 else
1836 LogRelMax(64, ("DSound: Re-opening '%s' failed: %Rhrc\n", pStreamDS->Cfg.szName, hrc));
1837 }
1838 }
1839 else
1840 {
1841 LogRel2(("DSound: Already capturing (%#x)\n", fStatus));
1842 AssertFailed();
1843 }
1844 }
1845 else
1846 LogRelMax(64, ("DSound: Retrieving capture status for '%s' failed: %Rhrc\n", pStreamDS->Cfg.szName, hrc));
1847 }
1848 LogFlowFunc(("returns %Rrc\n", rc));
1849 return rc;
1850}
1851
1852
1853/**
1854 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
1855 */
1856static DECLCALLBACK(int) drvHostDSoundHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1857{
1858 PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
1859 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
1860 LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
1861
1862 /*
1863 * We always reset the buffer before enabling the stream (normally never necessary).
1864 */
1865 drvHostDSoundStreamReset(pThis, pStreamDS);
1866 pStreamDS->fEnabled = true;
1867
1868 /*
1869 * Input streams will start capturing, while output streams will only start
1870 * playing once we get some audio data to play.
1871 */
1872 int rc = VINF_SUCCESS;
1873 if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
1874 rc = drvHostDSoundStreamCaptureStart(pThis, pStreamDS);
1875 else
1876 Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT);
1877
1878 LogFlowFunc(("returns %Rrc\n", rc));
1879 return rc;
1880}
1881
1882
1883/**
1884 * Worker for drvHostDSoundHA_StreamDestroy, drvHostDSoundHA_StreamDisable and
1885 * drvHostDSoundHA_StreamPause.
1886 *
1887 * @returns VBox status code.
1888 * @param pThis The DSound host audio driver instance data.
1889 * @param pStreamDS The stream instance data.
1890 * @param fReset Whether to reset the buffer and state.
1891 */
1892static int drvHostDSoundStreamStopPlayback(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fReset)
1893{
1894 if (!pStreamDS->Out.pDSB)
1895 return VINF_SUCCESS;
1896
1897 LogRel2(("DSound: Stopping playback of '%s'...\n", pStreamDS->Cfg.szName));
1898 HRESULT hrc = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB);
1899 if (FAILED(hrc))
1900 {
1901 LogFunc(("IDirectSoundBuffer8_Stop -> %Rhrc; will attempt restoring the stream...\n", hrc));
1902 directSoundPlayRestore(pThis, pStreamDS->Out.pDSB);
1903 hrc = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB);
1904 if (FAILED(hrc))
1905 LogRelMax(64, ("DSound: %s playback of '%s' failed: %Rhrc\n", fReset ? "Stopping" : "Pausing",
1906 pStreamDS->Cfg.szName, hrc));
1907 }
1908 LogRel2(("DSound: Stopped playback of '%s': %Rhrc\n", pStreamDS->Cfg.szName, hrc));
1909
1910 if (fReset)
1911 drvHostDSoundStreamReset(pThis, pStreamDS);
1912 return SUCCEEDED(hrc) ? VINF_SUCCESS : VERR_AUDIO_STREAM_NOT_READY;
1913}
1914
1915
1916/**
1917 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
1918 */
1919static DECLCALLBACK(int) drvHostDSoundHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1920{
1921 PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
1922 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
1923 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
1924 pStreamDS->msLastTransfer ? RTTimeMilliTS() - pStreamDS->msLastTransfer : -1,
1925 pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) ));
1926
1927 /*
1928 * Change the state.
1929 */
1930 pStreamDS->fEnabled = false;
1931
1932 /*
1933 * Stop the stream and maybe reset the buffer.
1934 */
1935 int rc = VINF_SUCCESS;
1936 if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
1937 {
1938 if (pStreamDS->In.pDSCB)
1939 {
1940 HRESULT hrc = IDirectSoundCaptureBuffer_Stop(pStreamDS->In.pDSCB);
1941 if (SUCCEEDED(hrc))
1942 LogRel3(("DSound: Stopped capture on '%s'.\n", pStreamDS->Cfg.szName));
1943 else
1944 {
1945 LogRelMax(64, ("DSound: Stopping capture on '%s' failed: %Rhrc\n", pStreamDS->Cfg.szName, hrc));
1946 /* Don't report errors up to the caller, as it might just be a capture device change. */
1947 }
1948
1949 /* This isn't strictly speaking necessary since StreamEnable does it too... */
1950 drvHostDSoundStreamReset(pThis, pStreamDS);
1951 }
1952 }
1953 else
1954 {
1955 Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT);
1956 if (pStreamDS->Out.pDSB)
1957 {
1958 rc = drvHostDSoundStreamStopPlayback(pThis, pStreamDS, true /*fReset*/);
1959 if (RT_SUCCESS(rc))
1960 LogRel3(("DSound: Stopped playback on '%s'.\n", pStreamDS->Cfg.szName));
1961 }
1962 }
1963
1964 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostDSoundStreamStatusString(pStreamDS)));
1965 return rc;
1966}
1967
1968
1969/**
1970 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
1971 *
1972 * @note Basically the same as drvHostDSoundHA_StreamDisable, just w/o the
1973 * buffer resetting and fEnabled change.
1974 */
1975static DECLCALLBACK(int) drvHostDSoundHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1976{
1977 PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
1978 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
1979 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
1980 pStreamDS->msLastTransfer ? RTTimeMilliTS() - pStreamDS->msLastTransfer : -1,
1981 pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) ));
1982
1983 /*
1984 * Stop the stream and maybe reset the buffer.
1985 */
1986 int rc = VINF_SUCCESS;
1987 if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
1988 {
1989 if (pStreamDS->In.pDSCB)
1990 {
1991 HRESULT hrc = IDirectSoundCaptureBuffer_Stop(pStreamDS->In.pDSCB);
1992 if (SUCCEEDED(hrc))
1993 LogRel3(("DSound: Stopped capture on '%s'.\n", pStreamDS->Cfg.szName));
1994 else
1995 {
1996 LogRelMax(64, ("DSound: Stopping capture on '%s' failed: %Rhrc\n", pStreamDS->Cfg.szName, hrc));
1997 /* Don't report errors up to the caller, as it might just be a capture device change. */
1998 }
1999 }
2000 }
2001 else
2002 {
2003 Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT);
2004 if (pStreamDS->Out.pDSB)
2005 {
2006 /* Don't stop draining buffers, we won't be resuming them right.
2007 They'll stop by themselves anyway. */
2008 if (pStreamDS->Out.fDrain)
2009 LogFunc(("Stream '%s' is draining\n", pStreamDS->Cfg.szName));
2010 else
2011 {
2012 rc = drvHostDSoundStreamStopPlayback(pThis, pStreamDS, false /*fReset*/);
2013 if (RT_SUCCESS(rc))
2014 LogRel3(("DSound: Stopped playback on '%s'.\n", pStreamDS->Cfg.szName));
2015 }
2016 }
2017 }
2018
2019 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostDSoundStreamStatusString(pStreamDS)));
2020 return rc;
2021}
2022
2023
2024/**
2025 * Worker for drvHostDSoundHA_StreamResume and drvHostDSoundHA_StreamPlay that
2026 * starts playing the DirectSound Buffer.
2027 *
2028 * @returns VBox status code.
2029 * @param pThis Host audio driver instance.
2030 * @param pStreamDS Stream to start playing.
2031 */
2032static int directSoundPlayStart(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
2033{
2034 if (!pStreamDS->Out.pDSB)
2035 return VERR_AUDIO_STREAM_NOT_READY;
2036
2037 LogRel2(("DSound: Starting playback of '%s' ...\n", pStreamDS->Cfg.szName));
2038 HRESULT hrc = IDirectSoundBuffer8_Play(pStreamDS->Out.pDSB, 0, 0, DSCBSTART_LOOPING);
2039 if (SUCCEEDED(hrc))
2040 return VINF_SUCCESS;
2041
2042 for (unsigned i = 0; hrc == DSERR_BUFFERLOST && i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++)
2043 {
2044 LogFunc(("Restarting playback failed due to lost buffer, restoring ...\n"));
2045 directSoundPlayRestore(pThis, pStreamDS->Out.pDSB);
2046
2047 hrc = IDirectSoundBuffer8_Play(pStreamDS->Out.pDSB, 0, 0, DSCBSTART_LOOPING);
2048 if (SUCCEEDED(hrc))
2049 return VINF_SUCCESS;
2050 }
2051
2052 LogRelMax(64, ("DSound: Failed to start playback of '%s': %Rhrc\n", pStreamDS->Cfg.szName, hrc));
2053 return VERR_AUDIO_STREAM_NOT_READY;
2054}
2055
2056
2057/**
2058 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
2059 */
2060static DECLCALLBACK(int) drvHostDSoundHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2061{
2062 PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
2063 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2064 LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
2065
2066 /*
2067 * Input streams will start capturing, while output streams will only start
2068 * playing if we're past the pre-buffering state.
2069 */
2070 int rc = VINF_SUCCESS;
2071 if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
2072 rc = drvHostDSoundStreamCaptureStart(pThis, pStreamDS);
2073 else
2074 {
2075 Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT);
2076 if (!pStreamDS->Out.fFirstTransfer)
2077 rc = directSoundPlayStart(pThis, pStreamDS);
2078 }
2079
2080 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostDSoundStreamStatusString(pStreamDS)));
2081 return rc;
2082}
2083
2084
2085/**
2086 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
2087 */
2088static DECLCALLBACK(int) drvHostDSoundHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2089{
2090 PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
2091 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2092 AssertReturn(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
2093 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
2094 pStreamDS->msLastTransfer ? RTTimeMilliTS() - pStreamDS->msLastTransfer : -1,
2095 pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) ));
2096
2097 /*
2098 * We've started the buffer in looping mode, try switch to non-looping...
2099 */
2100 int rc = VINF_SUCCESS;
2101 if (pStreamDS->Out.pDSB && !pStreamDS->Out.fDrain)
2102 {
2103 LogRel2(("DSound: Switching playback stream '%s' to drain mode...\n", pStreamDS->Cfg.szName));
2104 HRESULT hrc = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB);
2105 if (SUCCEEDED(hrc))
2106 {
2107 hrc = IDirectSoundBuffer8_Play(pStreamDS->Out.pDSB, 0, 0, 0);
2108 if (SUCCEEDED(hrc))
2109 {
2110 uint64_t const msNow = RTTimeMilliTS();
2111 pStreamDS->Out.msDrainDeadline = PDMAudioPropsBytesToMilli(&pStreamDS->Cfg.Props, pStreamDS->cbBufSize) + msNow;
2112 pStreamDS->Out.fDrain = true;
2113 }
2114 else
2115 LogRelMax(64, ("DSound: Failed to restart '%s' in drain mode: %Rhrc\n", pStreamDS->Cfg.szName, hrc));
2116 }
2117 else
2118 {
2119 Log2Func(("drain: IDirectSoundBuffer8_Stop failed: %Rhrc\n", hrc));
2120 directSoundPlayRestore(pThis, pStreamDS->Out.pDSB);
2121
2122 HRESULT hrc2 = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB);
2123 if (SUCCEEDED(hrc2))
2124 LogFunc(("Successfully stopped the stream after restoring it. (hrc=%Rhrc)\n", hrc));
2125 else
2126 {
2127 LogRelMax(64, ("DSound: Failed to stop playback stream '%s' for putting into drain mode: %Rhrc (initial), %Rhrc (after restore)\n",
2128 pStreamDS->Cfg.szName, hrc, hrc2));
2129 rc = VERR_AUDIO_STREAM_NOT_READY;
2130 }
2131 }
2132 }
2133 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostDSoundStreamStatusString(pStreamDS)));
2134 return rc;
2135}
2136
2137
2138/**
2139 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
2140 */
2141static DECLCALLBACK(int) drvHostDSoundHA_StreamControl(PPDMIHOSTAUDIO pInterface,
2142 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
2143{
2144 /** @todo r=bird: I'd like to get rid of this pfnStreamControl method,
2145 * replacing it with individual StreamXxxx methods. That would save us
2146 * potentally huge switches and more easily see which drivers implement
2147 * which operations (grep for pfnStreamXxxx). */
2148 switch (enmStreamCmd)
2149 {
2150 case PDMAUDIOSTREAMCMD_ENABLE:
2151 return drvHostDSoundHA_StreamEnable(pInterface, pStream);
2152 case PDMAUDIOSTREAMCMD_DISABLE:
2153 return drvHostDSoundHA_StreamDisable(pInterface, pStream);
2154 case PDMAUDIOSTREAMCMD_PAUSE:
2155 return drvHostDSoundHA_StreamPause(pInterface, pStream);
2156 case PDMAUDIOSTREAMCMD_RESUME:
2157 return drvHostDSoundHA_StreamResume(pInterface, pStream);
2158 case PDMAUDIOSTREAMCMD_DRAIN:
2159 return drvHostDSoundHA_StreamDrain(pInterface, pStream);
2160
2161 case PDMAUDIOSTREAMCMD_END:
2162 case PDMAUDIOSTREAMCMD_32BIT_HACK:
2163 case PDMAUDIOSTREAMCMD_INVALID:
2164 /* no default*/
2165 break;
2166 }
2167 return VERR_NOT_SUPPORTED;
2168}
2169
2170
2171/**
2172 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
2173 */
2174static DECLCALLBACK(uint32_t) drvHostDSoundHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2175{
2176 /*PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); */ RT_NOREF(pInterface);
2177 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2178 AssertPtrReturn(pStreamDS, 0);
2179 Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN);
2180
2181 if (pStreamDS->fEnabled)
2182 {
2183 /* This is the same calculation as for StreamGetPending. */
2184 AssertPtr(pStreamDS->In.pDSCB);
2185 DWORD offCaptureCursor = 0;
2186 DWORD offReadCursor = 0;
2187 HRESULT hrc = IDirectSoundCaptureBuffer_GetCurrentPosition(pStreamDS->In.pDSCB, &offCaptureCursor, &offReadCursor);
2188 if (SUCCEEDED(hrc))
2189 {
2190 uint32_t cbPending = dsoundRingDistance(offCaptureCursor, offReadCursor, pStreamDS->cbBufSize);
2191 Log3Func(("cbPending=%RU32\n", cbPending));
2192 return cbPending;
2193 }
2194 AssertMsgFailed(("hrc=%Rhrc\n", hrc));
2195 }
2196
2197 return 0;
2198}
2199
2200
2201/**
2202 * Retrieves the number of free bytes available for writing to a DirectSound output stream.
2203 *
2204 * @return VBox status code. VERR_NOT_AVAILABLE if unable to determine or the
2205 * buffer was not recoverable.
2206 * @param pThis Host audio driver instance.
2207 * @param pStreamDS DirectSound output stream to retrieve number for.
2208 * @param pdwFree Where to return the free amount on success.
2209 * @param poffPlayCursor Where to return the play cursor offset.
2210 */
2211static int dsoundGetFreeOut(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, DWORD *pdwFree, DWORD *poffPlayCursor)
2212{
2213 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
2214 AssertPtrReturn(pStreamDS, VERR_INVALID_POINTER);
2215 AssertPtrReturn(pdwFree, VERR_INVALID_POINTER);
2216 AssertPtr(poffPlayCursor);
2217
2218 Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT); /* Paranoia. */
2219
2220 LPDIRECTSOUNDBUFFER8 pDSB = pStreamDS->Out.pDSB;
2221 AssertPtrReturn(pDSB, VERR_INVALID_POINTER);
2222
2223 HRESULT hr = S_OK;
2224
2225 /* Get the current play position which is used for calculating the free space in the buffer. */
2226 for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++)
2227 {
2228 DWORD offPlayCursor = 0;
2229 DWORD offWriteCursor = 0;
2230 hr = IDirectSoundBuffer8_GetCurrentPosition(pDSB, &offPlayCursor, &offWriteCursor);
2231 if (SUCCEEDED(hr))
2232 {
2233 int32_t cbDiff = offWriteCursor - offPlayCursor;
2234 if (cbDiff < 0)
2235 cbDiff += pStreamDS->cbBufSize;
2236
2237 int32_t cbFree = offPlayCursor - pStreamDS->Out.offWritePos;
2238 if (cbFree < 0)
2239 cbFree += pStreamDS->cbBufSize;
2240
2241 if (cbFree > (int32_t)pStreamDS->cbBufSize - cbDiff)
2242 {
2243 /** @todo count/log these. */
2244 pStreamDS->Out.offWritePos = offWriteCursor;
2245 cbFree = pStreamDS->cbBufSize - cbDiff;
2246 }
2247
2248 /* When starting to use a DirectSound buffer, offPlayCursor and offWriteCursor
2249 * both point at position 0, so we won't be able to detect how many bytes
2250 * are writable that way.
2251 *
2252 * So use our per-stream written indicator to see if we just started a stream. */
2253 if (pStreamDS->Out.cbWritten == 0)
2254 cbFree = pStreamDS->cbBufSize;
2255
2256 DSLOGREL(("DSound: offPlayCursor=%RU32, offWriteCursor=%RU32, offWritePos=%RU32 -> cbFree=%RI32\n",
2257 offPlayCursor, offWriteCursor, pStreamDS->Out.offWritePos, cbFree));
2258
2259 *pdwFree = cbFree;
2260 *poffPlayCursor = offPlayCursor;
2261 return VINF_SUCCESS;
2262 }
2263
2264 if (hr != DSERR_BUFFERLOST) /** @todo MSDN doesn't state this error for GetCurrentPosition(). */
2265 break;
2266
2267 LogFunc(("Getting playing position failed due to lost buffer, restoring ...\n"));
2268
2269 directSoundPlayRestore(pThis, pDSB);
2270 }
2271
2272 if (hr != DSERR_BUFFERLOST) /* Avoid log flooding if the error is still there. */
2273 DSLOGREL(("DSound: Getting current playback position failed with %Rhrc\n", hr));
2274
2275 LogFunc(("Failed with %Rhrc\n", hr));
2276
2277 *poffPlayCursor = pStreamDS->cbBufSize;
2278 return VERR_NOT_AVAILABLE;
2279}
2280
2281
2282/**
2283 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
2284 */
2285static DECLCALLBACK(uint32_t) drvHostDSoundHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2286{
2287 PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
2288 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2289 AssertPtrReturn(pStreamDS, 0);
2290 LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
2291
2292 DWORD cbFree = 0;
2293 DWORD offIgn = 0;
2294 int rc = dsoundGetFreeOut(pThis, pStreamDS, &cbFree, &offIgn);
2295 AssertRCReturn(rc, 0);
2296
2297 return cbFree;
2298}
2299
2300#if 0 /* This isn't working as the write cursor is more a function of time than what we do.
2301 Previously we only reported the pre-buffering status anyway, so no harm. */
2302/**
2303 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending}
2304 */
2305static DECLCALLBACK(uint32_t) drvHostDSoundHA_StreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2306{
2307 /*PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); */ RT_NOREF(pInterface);
2308 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2309 AssertPtrReturn(pStreamDS, 0);
2310 LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
2311
2312 if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT)
2313 {
2314 /* This is a similar calculation as for StreamGetReadable, only for an output buffer. */
2315 AssertPtr(pStreamDS->In.pDSCB);
2316 DWORD offPlayCursor = 0;
2317 DWORD offWriteCursor = 0;
2318 HRESULT hrc = IDirectSoundBuffer8_GetCurrentPosition(pStreamDS->Out.pDSB, &offPlayCursor, &offWriteCursor);
2319 if (SUCCEEDED(hrc))
2320 {
2321 uint32_t cbPending = dsoundRingDistance(offWriteCursor, offPlayCursor, pStreamDS->cbBufSize);
2322 Log3Func(("cbPending=%RU32\n", cbPending));
2323 return cbPending;
2324 }
2325 AssertMsgFailed(("hrc=%Rhrc\n", hrc));
2326 }
2327 /* else: For input streams we never have any pending data. */
2328
2329 return 0;
2330}
2331#endif
2332
2333/**
2334 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
2335 */
2336static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHostDSoundHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
2337 PPDMAUDIOBACKENDSTREAM pStream)
2338{
2339 RT_NOREF(pInterface);
2340 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2341 AssertPtrReturn(pStreamDS, PDMHOSTAUDIOSTREAMSTATE_INVALID);
2342
2343 if ( pStreamDS->Cfg.enmDir != PDMAUDIODIR_OUT
2344 || !pStreamDS->Out.fDrain)
2345 {
2346 LogFlowFunc(("returns OKAY for '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
2347 return PDMHOSTAUDIOSTREAMSTATE_OKAY;
2348 }
2349 LogFlowFunc(("returns DRAINING for '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
2350 return PDMHOSTAUDIOSTREAMSTATE_DRAINING;
2351}
2352
2353
2354/**
2355 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
2356 */
2357static DECLCALLBACK(int) drvHostDSoundHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2358 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
2359{
2360 PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
2361 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2362 AssertPtrReturn(pStreamDS, 0);
2363 if (cbBuf)
2364 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2365 AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
2366
2367 if (pStreamDS->fEnabled)
2368 AssertReturn(pStreamDS->cbBufSize, VERR_INTERNAL_ERROR_2);
2369 else
2370 {
2371 Log2Func(("Skipping disabled stream {%s}\n", drvHostDSoundStreamStatusString(pStreamDS)));
2372 return VINF_SUCCESS;
2373 }
2374 Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) ));
2375
2376/** @todo Any condition under which we should call dsoundUpdateStatusInternal(pThis) here?
2377 * The old code thought it did so in case of failure, only it couldn't ever fails, so it never did. */
2378
2379 /*
2380 * Transfer loop.
2381 */
2382 uint32_t cbWritten = 0;
2383 while (cbBuf > 0)
2384 {
2385 /*
2386 * Figure out how much we can possibly write.
2387 */
2388 DWORD offPlayCursor = 0;
2389 DWORD cbWritable = 0;
2390 int rc = dsoundGetFreeOut(pThis, pStreamDS, &cbWritable, &offPlayCursor);
2391 AssertRCReturn(rc, rc);
2392 if (cbWritable < pStreamDS->Cfg.Props.cbFrame)
2393 break;
2394
2395 uint32_t const cbToWrite = RT_MIN(cbWritable, cbBuf);
2396 Log3Func(("offPlay=%#x offWritePos=%#x -> cbWritable=%#x cbToWrite=%#x {%s}\n", offPlayCursor, pStreamDS->Out.offWritePos,
2397 cbWritable, cbToWrite, drvHostDSoundStreamStatusString(pStreamDS) ));
2398
2399 /*
2400 * Lock that amount of buffer.
2401 */
2402 PVOID pv1 = NULL;
2403 DWORD cb1 = 0;
2404 PVOID pv2 = NULL;
2405 DWORD cb2 = 0;
2406 HRESULT hrc = directSoundPlayLock(pThis, pStreamDS, pStreamDS->Out.offWritePos, cbToWrite,
2407 &pv1, &pv2, &cb1, &cb2, 0 /*dwFlags*/);
2408 AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), VERR_ACCESS_DENIED); /** @todo translate these status codes already! */
2409 //AssertMsg(cb1 + cb2 == cbToWrite, ("%#x + %#x vs %#x\n", cb1, cb2, cbToWrite));
2410
2411 /*
2412 * Copy over the data.
2413 */
2414 memcpy(pv1, pvBuf, cb1);
2415 pvBuf = (uint8_t *)pvBuf + cb1;
2416 cbBuf -= cb1;
2417 cbWritten += cb1;
2418
2419 if (pv2)
2420 {
2421 memcpy(pv2, pvBuf, cb2);
2422 pvBuf = (uint8_t *)pvBuf + cb2;
2423 cbBuf -= cb2;
2424 cbWritten += cb2;
2425 }
2426
2427 /*
2428 * Unlock and update the write position.
2429 */
2430 directSoundPlayUnlock(pThis, pStreamDS->Out.pDSB, pv1, pv2, cb1, cb2); /** @todo r=bird: pThis + pDSB parameters here for Unlock, but only pThis for Lock. Why? */
2431 pStreamDS->Out.offWritePos = (pStreamDS->Out.offWritePos + cb1 + cb2) % pStreamDS->cbBufSize;
2432
2433 /*
2434 * If this was the first chunk, kick off playing.
2435 */
2436 if (!pStreamDS->Out.fFirstTransfer)
2437 { /* likely */ }
2438 else
2439 {
2440 *pcbWritten = cbWritten;
2441 rc = directSoundPlayStart(pThis, pStreamDS);
2442 AssertRCReturn(rc, rc);
2443 pStreamDS->Out.fFirstTransfer = false;
2444 }
2445 }
2446
2447 /*
2448 * Done.
2449 */
2450 *pcbWritten = cbWritten;
2451
2452 pStreamDS->Out.cbTransferred += cbWritten;
2453 if (cbWritten)
2454 {
2455 uint64_t const msPrev = pStreamDS->msLastTransfer;
2456 pStreamDS->Out.cbLastTransferred = cbWritten;
2457 pStreamDS->msLastTransfer = RTTimeMilliTS();
2458 LogFlowFunc(("cbLastTransferred=%RU32, msLastTransfer=%RU64 msNow=%RU64 cMsDelta=%RU64 {%s}\n",
2459 cbWritten, msPrev, pStreamDS->msLastTransfer, msPrev ? pStreamDS->msLastTransfer - msPrev : 0,
2460 drvHostDSoundStreamStatusString(pStreamDS) ));
2461 }
2462 else if ( pStreamDS->Out.fDrain
2463 && RTTimeMilliTS() >= pStreamDS->Out.msDrainDeadline)
2464 {
2465 LogRel2(("DSound: Stopping draining of '%s' {%s} ...\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
2466 if (pStreamDS->Out.pDSB)
2467 {
2468 HRESULT hrc = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB);
2469 if (FAILED(hrc))
2470 LogRelMax(64, ("DSound: Failed to stop draining stream '%s': %Rhrc\n", pStreamDS->Cfg.szName, hrc));
2471 }
2472 pStreamDS->Out.fDrain = false;
2473 pStreamDS->fEnabled = false;
2474 }
2475
2476 return VINF_SUCCESS;
2477}
2478
2479
2480/**
2481 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
2482 */
2483static DECLCALLBACK(int) drvHostDSoundHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2484 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
2485{
2486 /*PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);*/ RT_NOREF(pInterface);
2487 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2488 AssertPtrReturn(pStreamDS, 0);
2489 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2490 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
2491 AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
2492
2493#if 0 /** @todo r=bird: shouldn't we do the same check as for output streams? */
2494 if (pStreamDS->fEnabled)
2495 AssertReturn(pStreamDS->cbBufSize, VERR_INTERNAL_ERROR_2);
2496 else
2497 {
2498 Log2Func(("Stream disabled, skipping\n"));
2499 return VINF_SUCCESS;
2500 }
2501#endif
2502 Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) ));
2503
2504 /*
2505 * Read loop.
2506 */
2507 uint32_t cbRead = 0;
2508 while (cbBuf > 0)
2509 {
2510 /*
2511 * Figure out how much we can read.
2512 */
2513 DWORD offCaptureCursor = 0;
2514 DWORD offReadCursor = 0;
2515 HRESULT hrc = IDirectSoundCaptureBuffer_GetCurrentPosition(pStreamDS->In.pDSCB, &offCaptureCursor, &offReadCursor);
2516 AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), VERR_ACCESS_DENIED); /** @todo translate these status codes already! */
2517 //AssertMsg(offReadCursor == pStreamDS->In.offReadPos, ("%#x %#x\n", offReadCursor, pStreamDS->In.offReadPos));
2518
2519 uint32_t const cbReadable = dsoundRingDistance(offCaptureCursor, pStreamDS->In.offReadPos, pStreamDS->cbBufSize);
2520
2521 if (cbReadable >= pStreamDS->Cfg.Props.cbFrame)
2522 { /* likely */ }
2523 else
2524 {
2525 if (cbRead > 0)
2526 { /* likely */ }
2527 else if (pStreamDS->In.cOverruns < 32)
2528 {
2529 pStreamDS->In.cOverruns++;
2530 DSLOG(("DSound: Warning: Buffer full (size is %zu bytes), skipping to record data (overflow #%RU32)\n",
2531 pStreamDS->cbBufSize, pStreamDS->In.cOverruns));
2532 }
2533 break;
2534 }
2535
2536 uint32_t const cbToRead = RT_MIN(cbReadable, cbBuf);
2537 Log3Func(("offCapture=%#x offRead=%#x/%#x -> cbWritable=%#x cbToWrite=%#x {%s}\n", offCaptureCursor, offReadCursor,
2538 pStreamDS->In.offReadPos, cbReadable, cbToRead, drvHostDSoundStreamStatusString(pStreamDS)));
2539
2540 /*
2541 * Lock that amount of buffer.
2542 */
2543 PVOID pv1 = NULL;
2544 DWORD cb1 = 0;
2545 PVOID pv2 = NULL;
2546 DWORD cb2 = 0;
2547 hrc = directSoundCaptureLock(pStreamDS, pStreamDS->In.offReadPos, cbToRead, &pv1, &pv2, &cb1, &cb2, 0 /*dwFlags*/);
2548 AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), VERR_ACCESS_DENIED); /** @todo translate these status codes already! */
2549 AssertMsg(cb1 + cb2 == cbToRead, ("%#x + %#x vs %#x\n", cb1, cb2, cbToRead));
2550
2551 /*
2552 * Copy over the data.
2553 */
2554 memcpy(pvBuf, pv1, cb1);
2555 pvBuf = (uint8_t *)pvBuf + cb1;
2556 cbBuf -= cb1;
2557 cbRead += cb1;
2558
2559 if (pv2)
2560 {
2561 memcpy(pvBuf, pv2, cb2);
2562 pvBuf = (uint8_t *)pvBuf + cb2;
2563 cbBuf -= cb2;
2564 cbRead += cb2;
2565 }
2566
2567 /*
2568 * Unlock and update the write position.
2569 */
2570 directSoundCaptureUnlock(pStreamDS->In.pDSCB, pv1, pv2, cb1, cb2); /** @todo r=bird: pDSB parameter here for Unlock, but pStreamDS for Lock. Why? */
2571 pStreamDS->In.offReadPos = (pStreamDS->In.offReadPos + cb1 + cb2) % pStreamDS->cbBufSize;
2572 }
2573
2574 /*
2575 * Done.
2576 */
2577 *pcbRead = cbRead;
2578 if (cbRead)
2579 {
2580 uint64_t const msPrev = pStreamDS->msLastTransfer;
2581 pStreamDS->msLastTransfer = RTTimeMilliTS();
2582 LogFlowFunc(("cbRead=%RU32, msLastTransfer=%RU64 msNow=%RU64 cMsDelta=%RU64 {%s}\n",
2583 cbRead, msPrev, pStreamDS->msLastTransfer, msPrev ? pStreamDS->msLastTransfer - msPrev : 0,
2584 drvHostDSoundStreamStatusString(pStreamDS) ));
2585 }
2586
2587#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
2588 if (cbRead)
2589 {
2590 RTFILE hFile;
2591 int rc2 = RTFileOpen(&hFile, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "dsoundCapture.pcm",
2592 RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
2593 if (RT_SUCCESS(rc2))
2594 {
2595 RTFileWrite(hFile, (uint8_t *)pvBuf - cbRead, cbRead, NULL);
2596 RTFileClose(hFile);
2597 }
2598 }
2599#endif
2600 return VINF_SUCCESS;
2601}
2602
2603
2604/*********************************************************************************************************************************
2605* PDMDRVINS::IBase Interface *
2606*********************************************************************************************************************************/
2607
2608/**
2609 * @callback_method_impl{PDMIBASE,pfnQueryInterface}
2610 */
2611static DECLCALLBACK(void *) drvHostDSoundQueryInterface(PPDMIBASE pInterface, const char *pszIID)
2612{
2613 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
2614 PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND);
2615
2616 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
2617 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
2618 return NULL;
2619}
2620
2621
2622/*********************************************************************************************************************************
2623* PDMDRVREG Interface *
2624*********************************************************************************************************************************/
2625
2626/**
2627 * @callback_method_impl{FNPDMDRVDESTRUCT, pfnDestruct}
2628 */
2629static DECLCALLBACK(void) drvHostDSoundDestruct(PPDMDRVINS pDrvIns)
2630{
2631 PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND);
2632 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
2633
2634 LogFlowFuncEnter();
2635
2636#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
2637 if (pThis->m_pNotificationClient)
2638 {
2639 pThis->m_pNotificationClient->Unregister();
2640 pThis->m_pNotificationClient->Release();
2641
2642 pThis->m_pNotificationClient = NULL;
2643 }
2644#endif
2645
2646 PDMAudioHostEnumDelete(&pThis->DeviceEnum);
2647
2648 int rc2 = RTCritSectDelete(&pThis->CritSect);
2649 AssertRC(rc2);
2650
2651 LogFlowFuncLeave();
2652}
2653
2654
2655static LPCGUID dsoundConfigQueryGUID(PCFGMNODE pCfg, const char *pszName, RTUUID *pUuid)
2656{
2657 LPCGUID pGuid = NULL;
2658
2659 char *pszGuid = NULL;
2660 int rc = CFGMR3QueryStringAlloc(pCfg, pszName, &pszGuid);
2661 if (RT_SUCCESS(rc))
2662 {
2663 rc = RTUuidFromStr(pUuid, pszGuid);
2664 if (RT_SUCCESS(rc))
2665 pGuid = (LPCGUID)&pUuid;
2666 else
2667 DSLOGREL(("DSound: Error parsing device GUID for device '%s': %Rrc\n", pszName, rc));
2668
2669 RTStrFree(pszGuid);
2670 }
2671
2672 return pGuid;
2673}
2674
2675
2676static void dsoundConfigInit(PDRVHOSTDSOUND pThis, PCFGMNODE pCfg)
2677{
2678 pThis->Cfg.pGuidPlay = dsoundConfigQueryGUID(pCfg, "DeviceGuidOut", &pThis->Cfg.uuidPlay);
2679 pThis->Cfg.pGuidCapture = dsoundConfigQueryGUID(pCfg, "DeviceGuidIn", &pThis->Cfg.uuidCapture);
2680
2681 DSLOG(("DSound: Configuration: DeviceGuidOut {%RTuuid}, DeviceGuidIn {%RTuuid}\n",
2682 &pThis->Cfg.uuidPlay,
2683 &pThis->Cfg.uuidCapture));
2684}
2685
2686
2687/**
2688 * @callback_method_impl{FNPDMDRVCONSTRUCT,
2689 * Construct a DirectSound Audio driver instance.}
2690 */
2691static DECLCALLBACK(int) drvHostDSoundConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
2692{
2693 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
2694 PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND);
2695 RT_NOREF(fFlags);
2696 LogRel(("Audio: Initializing DirectSound audio driver\n"));
2697
2698 /*
2699 * Init basic data members and interfaces.
2700 */
2701 RTListInit(&pThis->HeadStreams);
2702 pThis->pDrvIns = pDrvIns;
2703 /* IBase */
2704 pDrvIns->IBase.pfnQueryInterface = drvHostDSoundQueryInterface;
2705 /* IHostAudio */
2706 pThis->IHostAudio.pfnGetConfig = drvHostDSoundHA_GetConfig;
2707 pThis->IHostAudio.pfnGetDevices = drvHostDSoundHA_GetDevices;
2708 pThis->IHostAudio.pfnGetStatus = drvHostDSoundHA_GetStatus;
2709 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
2710 pThis->IHostAudio.pfnStreamConfigHint = NULL;
2711 pThis->IHostAudio.pfnStreamCreate = drvHostDSoundHA_StreamCreate;
2712 pThis->IHostAudio.pfnStreamInitAsync = NULL;
2713 pThis->IHostAudio.pfnStreamDestroy = drvHostDSoundHA_StreamDestroy;
2714 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
2715 pThis->IHostAudio.pfnStreamControl = drvHostDSoundHA_StreamControl;
2716 pThis->IHostAudio.pfnStreamGetReadable = drvHostDSoundHA_StreamGetReadable;
2717 pThis->IHostAudio.pfnStreamGetWritable = drvHostDSoundHA_StreamGetWritable;
2718 pThis->IHostAudio.pfnStreamGetPending = NULL;
2719 pThis->IHostAudio.pfnStreamGetState = drvHostDSoundHA_StreamGetState;
2720 pThis->IHostAudio.pfnStreamPlay = drvHostDSoundHA_StreamPlay;
2721 pThis->IHostAudio.pfnStreamCapture = drvHostDSoundHA_StreamCapture;
2722
2723 /*
2724 * Init the static parts.
2725 */
2726 PDMAudioHostEnumInit(&pThis->DeviceEnum);
2727
2728 pThis->fEnabledIn = false;
2729 pThis->fEnabledOut = false;
2730
2731 /*
2732 * Verify that IDirectSound is available.
2733 */
2734 LPDIRECTSOUND pDirectSound = NULL;
2735 HRESULT hrc = CoCreateInstance(CLSID_DirectSound, NULL, CLSCTX_ALL, IID_IDirectSound, (void **)&pDirectSound);
2736 if (SUCCEEDED(hrc))
2737 IDirectSound_Release(pDirectSound);
2738 else
2739 {
2740 LogRel(("DSound: DirectSound not available: %Rhrc\n", hrc));
2741 return VERR_AUDIO_BACKEND_INIT_FAILED;
2742 }
2743
2744#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
2745 /*
2746 * Set up WASAPI device change notifications (Vista+).
2747 */
2748 if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0))
2749 {
2750 /* Get the notification interface (from DrvAudio). */
2751# ifdef VBOX_WITH_AUDIO_CALLBACKS
2752 PPDMIHOSTAUDIOPORT pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT);
2753 Assert(pIHostAudioPort);
2754# else
2755 PPDMIHOSTAUDIOPORT pIHostAudioPort = NULL;
2756# endif
2757 try
2758 {
2759 pThis->m_pNotificationClient = new DrvHostAudioDSoundMMNotifClient(pIHostAudioPort,
2760 pThis->Cfg.pGuidCapture == NULL,
2761 pThis->Cfg.pGuidPlay == NULL);
2762 }
2763 catch (std::bad_alloc &)
2764 {
2765 return VERR_NO_MEMORY;
2766 }
2767 hrc = pThis->m_pNotificationClient->Initialize();
2768 if (SUCCEEDED(hrc))
2769 {
2770 hrc = pThis->m_pNotificationClient->Register();
2771 if (SUCCEEDED(hrc))
2772 LogRel2(("DSound: Notification client is enabled (ver %#RX64)\n", RTSystemGetNtVersion()));
2773 else
2774 {
2775 LogRel(("DSound: Notification client registration failed: %Rhrc\n", hrc));
2776 return VERR_AUDIO_BACKEND_INIT_FAILED;
2777 }
2778 }
2779 else
2780 {
2781 LogRel(("DSound: Notification client initialization failed: %Rhrc\n", hrc));
2782 return VERR_AUDIO_BACKEND_INIT_FAILED;
2783 }
2784 }
2785 else
2786 LogRel2(("DSound: Notification client is disabled (ver %#RX64)\n", RTSystemGetNtVersion()));
2787#endif
2788
2789 /*
2790 * Initialize configuration values and critical section.
2791 */
2792 dsoundConfigInit(pThis, pCfg);
2793 return RTCritSectInit(&pThis->CritSect);
2794}
2795
2796
2797/**
2798 * PDM driver registration.
2799 */
2800const PDMDRVREG g_DrvHostDSound =
2801{
2802 /* u32Version */
2803 PDM_DRVREG_VERSION,
2804 /* szName */
2805 "DSoundAudio",
2806 /* szRCMod */
2807 "",
2808 /* szR0Mod */
2809 "",
2810 /* pszDescription */
2811 "DirectSound Audio host driver",
2812 /* fFlags */
2813 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
2814 /* fClass. */
2815 PDM_DRVREG_CLASS_AUDIO,
2816 /* cMaxInstances */
2817 ~0U,
2818 /* cbInstance */
2819 sizeof(DRVHOSTDSOUND),
2820 /* pfnConstruct */
2821 drvHostDSoundConstruct,
2822 /* pfnDestruct */
2823 drvHostDSoundDestruct,
2824 /* pfnRelocate */
2825 NULL,
2826 /* pfnIOCtl */
2827 NULL,
2828 /* pfnPowerOn */
2829 NULL,
2830 /* pfnReset */
2831 NULL,
2832 /* pfnSuspend */
2833 NULL,
2834 /* pfnResume */
2835 NULL,
2836 /* pfnAttach */
2837 NULL,
2838 /* pfnDetach */
2839 NULL,
2840 /* pfnPowerOff */
2841 NULL,
2842 /* pfnSoftReset */
2843 NULL,
2844 /* u32EndVersion */
2845 PDM_DRVREG_VERSION
2846};
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