VirtualBox

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

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

DrvHostAudioDSound: Build fix. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 108.8 KB
Line 
1/* $Id: DrvHostAudioDSound.cpp 88573 2021-04-16 19:31:14Z 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#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
40# include <new> /* For bad_alloc. */
41# include "DrvHostAudioDSoundMMNotifClient.h"
42#endif
43
44
45/*********************************************************************************************************************************
46* Defined Constants And Macros *
47*********************************************************************************************************************************/
48/*
49 * Optional release logging, which a user can turn on with the
50 * 'VBoxManage debugvm' command.
51 * Debug logging still uses the common Log* macros from VBox.
52 * Messages which always should go to the release log use LogRel.
53 *
54 * @deprecated Use LogRelMax, LogRel2 and LogRel3 directly.
55 */
56/** General code behavior. */
57#define DSLOG(a) do { LogRel2(a); } while(0)
58/** Something which produce a lot of logging during playback/recording. */
59#define DSLOGF(a) do { LogRel3(a); } while(0)
60/** Important messages like errors. Limited in the default release log to avoid log flood. */
61#define DSLOGREL(a) \
62 do { \
63 static int8_t s_cLogged = 0; \
64 if (s_cLogged < 8) { \
65 ++s_cLogged; \
66 LogRel(a); \
67 } else DSLOG(a); \
68 } while (0)
69
70/** Maximum number of attempts to restore the sound buffer before giving up. */
71#define DRV_DSOUND_RESTORE_ATTEMPTS_MAX 3
72#if 0 /** @todo r=bird: What are these for? Nobody is using them... */
73/** Default input latency (in ms). */
74#define DRV_DSOUND_DEFAULT_LATENCY_MS_IN 50
75/** Default output latency (in ms). */
76#define DRV_DSOUND_DEFAULT_LATENCY_MS_OUT 50
77#endif
78
79
80/*********************************************************************************************************************************
81* Structures and Typedefs *
82*********************************************************************************************************************************/
83/* Dynamically load dsound.dll. */
84typedef HRESULT WINAPI FNDIRECTSOUNDENUMERATEW(LPDSENUMCALLBACKW pDSEnumCallback, PVOID pContext);
85typedef FNDIRECTSOUNDENUMERATEW *PFNDIRECTSOUNDENUMERATEW;
86typedef HRESULT WINAPI FNDIRECTSOUNDCAPTUREENUMERATEW(LPDSENUMCALLBACKW pDSEnumCallback, PVOID pContext);
87typedef FNDIRECTSOUNDCAPTUREENUMERATEW *PFNDIRECTSOUNDCAPTUREENUMERATEW;
88typedef HRESULT WINAPI FNDIRECTSOUNDCAPTURECREATE8(LPCGUID lpcGUID, LPDIRECTSOUNDCAPTURE8 *lplpDSC, LPUNKNOWN pUnkOuter);
89typedef FNDIRECTSOUNDCAPTURECREATE8 *PFNDIRECTSOUNDCAPTURECREATE8;
90
91#define VBOX_DSOUND_MAX_EVENTS 3
92
93typedef enum DSOUNDEVENT
94{
95 DSOUNDEVENT_NOTIFY = 0,
96 DSOUNDEVENT_INPUT,
97 DSOUNDEVENT_OUTPUT,
98} DSOUNDEVENT;
99
100typedef struct DSOUNDHOSTCFG
101{
102 RTUUID uuidPlay;
103 LPCGUID pGuidPlay;
104 RTUUID uuidCapture;
105 LPCGUID pGuidCapture;
106} DSOUNDHOSTCFG, *PDSOUNDHOSTCFG;
107
108typedef struct DSOUNDSTREAM
109{
110 /** Entry in DRVHOSTDSOUND::HeadStreams. */
111 RTLISTNODE ListEntry;
112 /** The stream's acquired configuration. */
113 PDMAUDIOSTREAMCFG Cfg;
114 /** Buffer alignment. */
115 uint8_t uAlign;
116 /** Whether this stream is in an enable state on the DirectSound side. */
117 bool fEnabled;
118 bool afPadding[2];
119 /** Size (in bytes) of the DirectSound buffer. */
120 DWORD cbBufSize;
121 union
122 {
123 struct
124 {
125 /** The actual DirectSound Buffer (DSB) used for the capturing.
126 * This is a secondary buffer and is used as a streaming buffer. */
127 LPDIRECTSOUNDCAPTUREBUFFER8 pDSCB;
128 /** Current read offset (in bytes) within the DSB. */
129 DWORD offReadPos;
130 /** Number of buffer overruns happened. Used for logging. */
131 uint8_t cOverruns;
132 } In;
133 struct
134 {
135 /** The actual DirectSound Buffer (DSB) used for playback.
136 * This is a secondary buffer and is used as a streaming buffer. */
137 LPDIRECTSOUNDBUFFER8 pDSB;
138 /** Current write offset (in bytes) within the DSB.
139 * @note This is needed as the current write position as kept by direct sound
140 * will move ahead if we're too late. */
141 DWORD offWritePos;
142 /** Offset of last play cursor within the DSB when checked for pending. */
143 DWORD offPlayCursorLastPending;
144 /** Offset of last play cursor within the DSB when last played. */
145 DWORD offPlayCursorLastPlayed;
146 /** Total amount (in bytes) written to our internal ring buffer. */
147 uint64_t cbWritten;
148 /** Total amount (in bytes) played (to the DirectSound buffer). */
149 uint64_t cbTransferred;
150 /** Flag indicating whether playback was just (re)started. */
151 bool fFirstTransfer;
152 /** Flag indicating whether this stream is in draining mode, e.g. no new
153 * data is being written to it but DirectSound still needs to be able to
154 * play its remaining (buffered) data. */
155 bool fDrain;
156 /** How much (in bytes) the last transfer from the internal buffer
157 * to the DirectSound buffer was. */
158 uint32_t cbLastTransferred;
159 /** The RTTimeMilliTS() deadline for the draining of this stream. */
160 uint64_t msDrainDeadline;
161 } Out;
162 };
163 /** Timestamp (in ms) of the last transfer from the internal buffer to/from the
164 * DirectSound buffer. */
165 uint64_t msLastTransfer;
166 /** The stream's critical section for synchronizing access. */
167 RTCRITSECT CritSect;
168 /** Used for formatting the current DSound status. */
169 char szStatus[127];
170 /** Fixed zero terminator. */
171 char const chStateZero;
172} DSOUNDSTREAM, *PDSOUNDSTREAM;
173
174/**
175 * DirectSound-specific device entry.
176 */
177typedef struct DSOUNDDEV
178{
179 PDMAUDIOHOSTDEV Core;
180 /** The GUID if handy. */
181 GUID Guid;
182} DSOUNDDEV;
183/** Pointer to a DirectSound device entry. */
184typedef DSOUNDDEV *PDSOUNDDEV;
185
186/**
187 * Structure for holding a device enumeration context.
188 */
189typedef struct DSOUNDENUMCBCTX
190{
191 /** Enumeration flags. */
192 uint32_t fFlags;
193 /** Pointer to device list to populate. */
194 PPDMAUDIOHOSTENUM pDevEnm;
195} DSOUNDENUMCBCTX, *PDSOUNDENUMCBCTX;
196
197typedef struct DRVHOSTDSOUND
198{
199 /** Pointer to the driver instance structure. */
200 PPDMDRVINS pDrvIns;
201 /** Our audio host audio interface. */
202 PDMIHOSTAUDIO IHostAudio;
203 /** Critical section to serialize access. */
204 RTCRITSECT CritSect;
205 /** DirectSound configuration options. */
206 DSOUNDHOSTCFG Cfg;
207 /** List of devices of last enumeration. */
208 PDMAUDIOHOSTENUM DeviceEnum;
209 /** Whether this backend supports any audio input.
210 * @todo r=bird: This is not actually used for anything. */
211 bool fEnabledIn;
212 /** Whether this backend supports any audio output.
213 * @todo r=bird: This is not actually used for anything. */
214 bool fEnabledOut;
215 /** The Direct Sound playback interface. */
216 LPDIRECTSOUND8 pDS;
217 /** The Direct Sound capturing interface. */
218 LPDIRECTSOUNDCAPTURE8 pDSC;
219 /** A drain stop timer that makes sure a draining stream will be
220 * properly stopped (seem the non-loop mode is a bit buggy). */
221 TMTIMERHANDLE hDrainTimer;
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 /* Don't stop draining buffers. They'll stop by themselves. */
1959 if (pStreamDS->Out.fDrain)
1960 LogFunc(("Stream '%s' is draining\n", pStreamDS->Cfg.szName));
1961 else
1962 {
1963 rc = drvHostDSoundStreamStopPlayback(pThis, pStreamDS, true /*fReset*/);
1964 if (RT_SUCCESS(rc))
1965 LogRel3(("DSound: Stopped playback on '%s'.\n", pStreamDS->Cfg.szName));
1966 }
1967 }
1968 }
1969
1970 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostDSoundStreamStatusString(pStreamDS)));
1971 return rc;
1972}
1973
1974
1975/**
1976 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
1977 *
1978 * @note Basically the same as drvHostDSoundHA_StreamDisable, just w/o the
1979 * buffer resetting and fEnabled change.
1980 */
1981static DECLCALLBACK(int) drvHostDSoundHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1982{
1983 PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
1984 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
1985 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
1986 pStreamDS->msLastTransfer ? RTTimeMilliTS() - pStreamDS->msLastTransfer : -1,
1987 pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) ));
1988
1989 /*
1990 * Stop the stream and maybe reset the buffer.
1991 */
1992 int rc = VINF_SUCCESS;
1993 if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
1994 {
1995 if (pStreamDS->In.pDSCB)
1996 {
1997 HRESULT hrc = IDirectSoundCaptureBuffer_Stop(pStreamDS->In.pDSCB);
1998 if (SUCCEEDED(hrc))
1999 LogRel3(("DSound: Stopped capture on '%s'.\n", pStreamDS->Cfg.szName));
2000 else
2001 {
2002 LogRelMax(64, ("DSound: Stopping capture on '%s' failed: %Rhrc\n", pStreamDS->Cfg.szName, hrc));
2003 /* Don't report errors up to the caller, as it might just be a capture device change. */
2004 }
2005 }
2006 }
2007 else
2008 {
2009 Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT);
2010 if (pStreamDS->Out.pDSB)
2011 {
2012 /* Don't stop draining buffers, we won't be resuming them right.
2013 They'll stop by themselves anyway. */
2014 if (pStreamDS->Out.fDrain)
2015 LogFunc(("Stream '%s' is draining\n", pStreamDS->Cfg.szName));
2016 else
2017 {
2018 rc = drvHostDSoundStreamStopPlayback(pThis, pStreamDS, false /*fReset*/);
2019 if (RT_SUCCESS(rc))
2020 LogRel3(("DSound: Stopped playback on '%s'.\n", pStreamDS->Cfg.szName));
2021 }
2022 }
2023 }
2024
2025 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostDSoundStreamStatusString(pStreamDS)));
2026 return rc;
2027}
2028
2029
2030/**
2031 * Worker for drvHostDSoundHA_StreamResume and drvHostDSoundHA_StreamPlay that
2032 * starts playing the DirectSound Buffer.
2033 *
2034 * @returns VBox status code.
2035 * @param pThis Host audio driver instance.
2036 * @param pStreamDS Stream to start playing.
2037 */
2038static int directSoundPlayStart(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
2039{
2040 if (!pStreamDS->Out.pDSB)
2041 return VERR_AUDIO_STREAM_NOT_READY;
2042
2043 LogRel2(("DSound: Starting playback of '%s' ...\n", pStreamDS->Cfg.szName));
2044 HRESULT hrc = IDirectSoundBuffer8_Play(pStreamDS->Out.pDSB, 0, 0, DSCBSTART_LOOPING);
2045 if (SUCCEEDED(hrc))
2046 return VINF_SUCCESS;
2047
2048 for (unsigned i = 0; hrc == DSERR_BUFFERLOST && i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++)
2049 {
2050 LogFunc(("Restarting playback failed due to lost buffer, restoring ...\n"));
2051 directSoundPlayRestore(pThis, pStreamDS->Out.pDSB);
2052
2053 hrc = IDirectSoundBuffer8_Play(pStreamDS->Out.pDSB, 0, 0, DSCBSTART_LOOPING);
2054 if (SUCCEEDED(hrc))
2055 return VINF_SUCCESS;
2056 }
2057
2058 LogRelMax(64, ("DSound: Failed to start playback of '%s': %Rhrc\n", pStreamDS->Cfg.szName, hrc));
2059 return VERR_AUDIO_STREAM_NOT_READY;
2060}
2061
2062
2063/**
2064 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
2065 */
2066static DECLCALLBACK(int) drvHostDSoundHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2067{
2068 PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
2069 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2070 LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
2071
2072 /*
2073 * Input streams will start capturing, while output streams will only start
2074 * playing if we're past the pre-buffering state.
2075 */
2076 int rc = VINF_SUCCESS;
2077 if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
2078 rc = drvHostDSoundStreamCaptureStart(pThis, pStreamDS);
2079 else
2080 {
2081 Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT);
2082 if (!pStreamDS->Out.fFirstTransfer)
2083 rc = directSoundPlayStart(pThis, pStreamDS);
2084 }
2085
2086 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostDSoundStreamStatusString(pStreamDS)));
2087 return rc;
2088}
2089
2090
2091/**
2092 * This is used by the timer function as well as when arming the timer.
2093 *
2094 * @param pThis The DSound host audio driver instance data.
2095 * @param msNow A current RTTimeMilliTS() value.
2096 */
2097static void drvHostDSoundDrainTimerWorker(PDRVHOSTDSOUND pThis, uint64_t msNow)
2098{
2099 /*
2100 * Go thru the stream list and look at draining streams.
2101 */
2102 uint64_t msNext = UINT64_MAX;
2103 RTCritSectEnter(&pThis->CritSect);
2104 PDSOUNDSTREAM pCur;
2105 RTListForEach(&pThis->HeadStreams, pCur, DSOUNDSTREAM, ListEntry)
2106 {
2107 if ( pCur->Cfg.enmDir == PDMAUDIODIR_OUT
2108 && pCur->Out.fDrain)
2109 {
2110 uint64_t const msCurDeadline = pCur->Out.msDrainDeadline;
2111 if (msCurDeadline > 0 && msCurDeadline < msNext)
2112 {
2113 if (msCurDeadline > msNow)
2114 msNext = pCur->Out.msDrainDeadline;
2115 else
2116 {
2117 LogRel2(("DSound: Stopping draining of '%s' {%s} ...\n",
2118 pCur->Cfg.szName, drvHostDSoundStreamStatusString(pCur)));
2119 if (pCur->Out.pDSB)
2120 {
2121 HRESULT hrc = IDirectSoundBuffer8_Stop(pCur->Out.pDSB);
2122 if (FAILED(hrc))
2123 LogRelMax(64, ("DSound: Failed to stop draining stream '%s': %Rhrc\n", pCur->Cfg.szName, hrc));
2124 }
2125 pCur->Out.fDrain = false;
2126 }
2127 }
2128 }
2129 }
2130
2131 /*
2132 * Re-arm the timer if necessary.
2133 */
2134 if (msNext != UINT64_MAX)
2135 PDMDrvHlpTimerSetMillies(pThis->pDrvIns, pThis->hDrainTimer, msNext - msNow);
2136 RTCritSectLeave(&pThis->CritSect);
2137}
2138
2139
2140/**
2141 * @callback_method_impl{FNTMTIMERDRV,
2142 * This is a hack to ensure that draining streams stop playing.}
2143 */
2144static DECLCALLBACK(void) drvHostDSoundDrainStopHackTimer(PPDMDRVINS pDrvIns, TMTIMERHANDLE hTimer, void *pvUser)
2145{
2146 PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND);
2147 RT_NOREF(hTimer, pvUser);
2148 drvHostDSoundDrainTimerWorker(pThis, RTTimeMilliTS());
2149}
2150
2151
2152/**
2153 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
2154 */
2155static DECLCALLBACK(int) drvHostDSoundHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2156{
2157 PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
2158 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2159 AssertReturn(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
2160 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
2161 pStreamDS->msLastTransfer ? RTTimeMilliTS() - pStreamDS->msLastTransfer : -1,
2162 pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) ));
2163
2164 /*
2165 * We've started the buffer in looping mode, try switch to non-looping...
2166 */
2167 int rc = VINF_SUCCESS;
2168 if (pStreamDS->Out.pDSB && !pStreamDS->Out.fDrain)
2169 {
2170 LogRel2(("DSound: Switching playback stream '%s' to drain mode...\n", pStreamDS->Cfg.szName));
2171 HRESULT hrc = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB);
2172 if (SUCCEEDED(hrc))
2173 {
2174 hrc = IDirectSoundBuffer8_Play(pStreamDS->Out.pDSB, 0, 0, 0);
2175 if (SUCCEEDED(hrc))
2176 {
2177 uint64_t const msNow = RTTimeMilliTS();
2178 pStreamDS->Out.msDrainDeadline = PDMAudioPropsBytesToMilli(&pStreamDS->Cfg.Props, pStreamDS->cbBufSize) + msNow;
2179 pStreamDS->Out.fDrain = true;
2180 drvHostDSoundDrainTimerWorker(pThis, msNow);
2181 }
2182 else
2183 LogRelMax(64, ("DSound: Failed to restart '%s' in drain mode: %Rhrc\n", pStreamDS->Cfg.szName, hrc));
2184 }
2185 else
2186 {
2187 Log2Func(("drain: IDirectSoundBuffer8_Stop failed: %Rhrc\n", hrc));
2188 directSoundPlayRestore(pThis, pStreamDS->Out.pDSB);
2189
2190 HRESULT hrc2 = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB);
2191 if (SUCCEEDED(hrc2))
2192 LogFunc(("Successfully stopped the stream after restoring it. (hrc=%Rhrc)\n", hrc));
2193 else
2194 {
2195 LogRelMax(64, ("DSound: Failed to stop playback stream '%s' for putting into drain mode: %Rhrc (initial), %Rhrc (after restore)\n",
2196 pStreamDS->Cfg.szName, hrc, hrc2));
2197 rc = VERR_AUDIO_STREAM_NOT_READY;
2198 }
2199 }
2200 }
2201 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostDSoundStreamStatusString(pStreamDS)));
2202 return rc;
2203}
2204
2205
2206/**
2207 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
2208 */
2209static DECLCALLBACK(int) drvHostDSoundHA_StreamControl(PPDMIHOSTAUDIO pInterface,
2210 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
2211{
2212 /** @todo r=bird: I'd like to get rid of this pfnStreamControl method,
2213 * replacing it with individual StreamXxxx methods. That would save us
2214 * potentally huge switches and more easily see which drivers implement
2215 * which operations (grep for pfnStreamXxxx). */
2216 switch (enmStreamCmd)
2217 {
2218 case PDMAUDIOSTREAMCMD_ENABLE:
2219 return drvHostDSoundHA_StreamEnable(pInterface, pStream);
2220 case PDMAUDIOSTREAMCMD_DISABLE:
2221 return drvHostDSoundHA_StreamDisable(pInterface, pStream);
2222 case PDMAUDIOSTREAMCMD_PAUSE:
2223 return drvHostDSoundHA_StreamPause(pInterface, pStream);
2224 case PDMAUDIOSTREAMCMD_RESUME:
2225 return drvHostDSoundHA_StreamResume(pInterface, pStream);
2226 case PDMAUDIOSTREAMCMD_DRAIN:
2227 return drvHostDSoundHA_StreamDrain(pInterface, pStream);
2228
2229 case PDMAUDIOSTREAMCMD_END:
2230 case PDMAUDIOSTREAMCMD_32BIT_HACK:
2231 case PDMAUDIOSTREAMCMD_INVALID:
2232 /* no default*/
2233 break;
2234 }
2235 return VERR_NOT_SUPPORTED;
2236}
2237
2238
2239/**
2240 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
2241 */
2242static DECLCALLBACK(uint32_t) drvHostDSoundHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2243{
2244 /*PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); */ RT_NOREF(pInterface);
2245 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2246 AssertPtrReturn(pStreamDS, 0);
2247 Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN);
2248
2249 if (pStreamDS->fEnabled)
2250 {
2251 /* This is the same calculation as for StreamGetPending. */
2252 AssertPtr(pStreamDS->In.pDSCB);
2253 DWORD offCaptureCursor = 0;
2254 DWORD offReadCursor = 0;
2255 HRESULT hrc = IDirectSoundCaptureBuffer_GetCurrentPosition(pStreamDS->In.pDSCB, &offCaptureCursor, &offReadCursor);
2256 if (SUCCEEDED(hrc))
2257 {
2258 uint32_t cbPending = dsoundRingDistance(offCaptureCursor, offReadCursor, pStreamDS->cbBufSize);
2259 Log3Func(("cbPending=%RU32\n", cbPending));
2260 return cbPending;
2261 }
2262 AssertMsgFailed(("hrc=%Rhrc\n", hrc));
2263 }
2264
2265 return 0;
2266}
2267
2268
2269/**
2270 * Retrieves the number of free bytes available for writing to a DirectSound output stream.
2271 *
2272 * @return VBox status code. VERR_NOT_AVAILABLE if unable to determine or the
2273 * buffer was not recoverable.
2274 * @param pThis Host audio driver instance.
2275 * @param pStreamDS DirectSound output stream to retrieve number for.
2276 * @param pdwFree Where to return the free amount on success.
2277 * @param poffPlayCursor Where to return the play cursor offset.
2278 */
2279static int dsoundGetFreeOut(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, DWORD *pdwFree, DWORD *poffPlayCursor)
2280{
2281 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
2282 AssertPtrReturn(pStreamDS, VERR_INVALID_POINTER);
2283 AssertPtrReturn(pdwFree, VERR_INVALID_POINTER);
2284 AssertPtr(poffPlayCursor);
2285
2286 Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT); /* Paranoia. */
2287
2288 LPDIRECTSOUNDBUFFER8 pDSB = pStreamDS->Out.pDSB;
2289 AssertPtrReturn(pDSB, VERR_INVALID_POINTER);
2290
2291 HRESULT hr = S_OK;
2292
2293 /* Get the current play position which is used for calculating the free space in the buffer. */
2294 for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++)
2295 {
2296 DWORD offPlayCursor = 0;
2297 DWORD offWriteCursor = 0;
2298 hr = IDirectSoundBuffer8_GetCurrentPosition(pDSB, &offPlayCursor, &offWriteCursor);
2299 if (SUCCEEDED(hr))
2300 {
2301 int32_t cbDiff = offWriteCursor - offPlayCursor;
2302 if (cbDiff < 0)
2303 cbDiff += pStreamDS->cbBufSize;
2304
2305 int32_t cbFree = offPlayCursor - pStreamDS->Out.offWritePos;
2306 if (cbFree < 0)
2307 cbFree += pStreamDS->cbBufSize;
2308
2309 if (cbFree > (int32_t)pStreamDS->cbBufSize - cbDiff)
2310 {
2311 /** @todo count/log these. */
2312 pStreamDS->Out.offWritePos = offWriteCursor;
2313 cbFree = pStreamDS->cbBufSize - cbDiff;
2314 }
2315
2316 /* When starting to use a DirectSound buffer, offPlayCursor and offWriteCursor
2317 * both point at position 0, so we won't be able to detect how many bytes
2318 * are writable that way.
2319 *
2320 * So use our per-stream written indicator to see if we just started a stream. */
2321 if (pStreamDS->Out.cbWritten == 0)
2322 cbFree = pStreamDS->cbBufSize;
2323
2324 DSLOGREL(("DSound: offPlayCursor=%RU32, offWriteCursor=%RU32, offWritePos=%RU32 -> cbFree=%RI32\n",
2325 offPlayCursor, offWriteCursor, pStreamDS->Out.offWritePos, cbFree));
2326
2327 *pdwFree = cbFree;
2328 *poffPlayCursor = offPlayCursor;
2329 return VINF_SUCCESS;
2330 }
2331
2332 if (hr != DSERR_BUFFERLOST) /** @todo MSDN doesn't state this error for GetCurrentPosition(). */
2333 break;
2334
2335 LogFunc(("Getting playing position failed due to lost buffer, restoring ...\n"));
2336
2337 directSoundPlayRestore(pThis, pDSB);
2338 }
2339
2340 if (hr != DSERR_BUFFERLOST) /* Avoid log flooding if the error is still there. */
2341 DSLOGREL(("DSound: Getting current playback position failed with %Rhrc\n", hr));
2342
2343 LogFunc(("Failed with %Rhrc\n", hr));
2344
2345 *poffPlayCursor = pStreamDS->cbBufSize;
2346 return VERR_NOT_AVAILABLE;
2347}
2348
2349
2350/**
2351 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
2352 */
2353static DECLCALLBACK(uint32_t) drvHostDSoundHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2354{
2355 PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
2356 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2357 AssertPtrReturn(pStreamDS, 0);
2358 LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
2359
2360 DWORD cbFree = 0;
2361 DWORD offIgn = 0;
2362 int rc = dsoundGetFreeOut(pThis, pStreamDS, &cbFree, &offIgn);
2363 AssertRCReturn(rc, 0);
2364
2365 return cbFree;
2366}
2367
2368#if 0 /* This isn't working as the write cursor is more a function of time than what we do.
2369 Previously we only reported the pre-buffering status anyway, so no harm. */
2370/**
2371 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending}
2372 */
2373static DECLCALLBACK(uint32_t) drvHostDSoundHA_StreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2374{
2375 /*PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); */ RT_NOREF(pInterface);
2376 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2377 AssertPtrReturn(pStreamDS, 0);
2378 LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
2379
2380 if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT)
2381 {
2382 /* This is a similar calculation as for StreamGetReadable, only for an output buffer. */
2383 AssertPtr(pStreamDS->In.pDSCB);
2384 DWORD offPlayCursor = 0;
2385 DWORD offWriteCursor = 0;
2386 HRESULT hrc = IDirectSoundBuffer8_GetCurrentPosition(pStreamDS->Out.pDSB, &offPlayCursor, &offWriteCursor);
2387 if (SUCCEEDED(hrc))
2388 {
2389 uint32_t cbPending = dsoundRingDistance(offWriteCursor, offPlayCursor, pStreamDS->cbBufSize);
2390 Log3Func(("cbPending=%RU32\n", cbPending));
2391 return cbPending;
2392 }
2393 AssertMsgFailed(("hrc=%Rhrc\n", hrc));
2394 }
2395 /* else: For input streams we never have any pending data. */
2396
2397 return 0;
2398}
2399#endif
2400
2401/**
2402 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
2403 */
2404static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostDSoundHA_StreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2405{
2406 RT_NOREF(pInterface);
2407 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2408 AssertPtrReturn(pStreamDS, PDMAUDIOSTREAMSTS_FLAGS_NONE);
2409
2410 PDMAUDIOSTREAMSTS fStrmStatus = PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED;
2411 if (pStreamDS->fEnabled)
2412 fStrmStatus |= PDMAUDIOSTREAMSTS_FLAGS_ENABLED;
2413
2414 LogFlowFunc(("returns %#x for '%s' {%s}\n", fStrmStatus, pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
2415 return fStrmStatus;
2416}
2417
2418
2419/**
2420 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
2421 */
2422static DECLCALLBACK(int) drvHostDSoundHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2423 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
2424{
2425 PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
2426 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2427 AssertPtrReturn(pStreamDS, 0);
2428 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2429 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
2430 AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
2431
2432 if (pStreamDS->fEnabled)
2433 AssertReturn(pStreamDS->cbBufSize, VERR_INTERNAL_ERROR_2);
2434 else
2435 {
2436 Log2Func(("Skipping disabled stream {%s}\n", drvHostDSoundStreamStatusString(pStreamDS)));
2437 return VINF_SUCCESS;
2438 }
2439 Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) ));
2440
2441/** @todo Any condition under which we should call dsoundUpdateStatusInternal(pThis) here?
2442 * The old code thought it did so in case of failure, only it couldn't ever fails, so it never did. */
2443
2444 /*
2445 * Transfer loop.
2446 */
2447 uint32_t cbWritten = 0;
2448 while (cbBuf > 0)
2449 {
2450 /*
2451 * Figure out how much we can possibly write.
2452 */
2453 DWORD offPlayCursor = 0;
2454 DWORD cbWritable = 0;
2455 int rc = dsoundGetFreeOut(pThis, pStreamDS, &cbWritable, &offPlayCursor);
2456 AssertRCReturn(rc, rc);
2457 if (cbWritable < pStreamDS->Cfg.Props.cbFrame)
2458 break;
2459
2460 uint32_t const cbToWrite = RT_MIN(cbWritable, cbBuf);
2461 Log3Func(("offPlay=%#x offWritePos=%#x -> cbWritable=%#x cbToWrite=%#x {%s}\n", offPlayCursor, pStreamDS->Out.offWritePos,
2462 cbWritable, cbToWrite, drvHostDSoundStreamStatusString(pStreamDS) ));
2463
2464 /*
2465 * Lock that amount of buffer.
2466 */
2467 PVOID pv1 = NULL;
2468 DWORD cb1 = 0;
2469 PVOID pv2 = NULL;
2470 DWORD cb2 = 0;
2471 HRESULT hrc = directSoundPlayLock(pThis, pStreamDS, pStreamDS->Out.offWritePos, cbToWrite,
2472 &pv1, &pv2, &cb1, &cb2, 0 /*dwFlags*/);
2473 AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), VERR_ACCESS_DENIED); /** @todo translate these status codes already! */
2474 //AssertMsg(cb1 + cb2 == cbToWrite, ("%#x + %#x vs %#x\n", cb1, cb2, cbToWrite));
2475
2476 /*
2477 * Copy over the data.
2478 */
2479 memcpy(pv1, pvBuf, cb1);
2480 pvBuf = (uint8_t *)pvBuf + cb1;
2481 cbBuf -= cb1;
2482 cbWritten += cb1;
2483
2484 if (pv2)
2485 {
2486 memcpy(pv2, pvBuf, cb2);
2487 pvBuf = (uint8_t *)pvBuf + cb2;
2488 cbBuf -= cb2;
2489 cbWritten += cb2;
2490 }
2491
2492 /*
2493 * Unlock and update the write position.
2494 */
2495 directSoundPlayUnlock(pThis, pStreamDS->Out.pDSB, pv1, pv2, cb1, cb2); /** @todo r=bird: pThis + pDSB parameters here for Unlock, but only pThis for Lock. Why? */
2496 pStreamDS->Out.offWritePos = (pStreamDS->Out.offWritePos + cb1 + cb2) % pStreamDS->cbBufSize;
2497
2498 /*
2499 * If this was the first chunk, kick off playing.
2500 */
2501 if (!pStreamDS->Out.fFirstTransfer)
2502 { /* likely */ }
2503 else
2504 {
2505 *pcbWritten = cbWritten;
2506 rc = directSoundPlayStart(pThis, pStreamDS);
2507 AssertRCReturn(rc, rc);
2508 pStreamDS->Out.fFirstTransfer = false;
2509 }
2510 }
2511
2512 /*
2513 * Done.
2514 */
2515 *pcbWritten = cbWritten;
2516
2517 pStreamDS->Out.cbTransferred += cbWritten;
2518 if (cbWritten)
2519 {
2520 uint64_t const msPrev = pStreamDS->msLastTransfer;
2521 pStreamDS->Out.cbLastTransferred = cbWritten;
2522 pStreamDS->msLastTransfer = RTTimeMilliTS();
2523 LogFlowFunc(("cbLastTransferred=%RU32, msLastTransfer=%RU64 msNow=%RU64 cMsDelta=%RU64 {%s}\n",
2524 cbWritten, msPrev, pStreamDS->msLastTransfer, msPrev ? pStreamDS->msLastTransfer - msPrev : 0,
2525 drvHostDSoundStreamStatusString(pStreamDS) ));
2526 }
2527 return VINF_SUCCESS;
2528}
2529
2530
2531/**
2532 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
2533 */
2534static DECLCALLBACK(int) drvHostDSoundHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2535 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
2536{
2537 /*PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);*/ RT_NOREF(pInterface);
2538 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2539 AssertPtrReturn(pStreamDS, 0);
2540 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2541 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
2542 AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
2543
2544#if 0 /** @todo r=bird: shouldn't we do the same check as for output streams? */
2545 if (pStreamDS->fEnabled)
2546 AssertReturn(pStreamDS->cbBufSize, VERR_INTERNAL_ERROR_2);
2547 else
2548 {
2549 Log2Func(("Stream disabled, skipping\n"));
2550 return VINF_SUCCESS;
2551 }
2552#endif
2553 Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) ));
2554
2555 /*
2556 * Read loop.
2557 */
2558 uint32_t cbRead = 0;
2559 while (cbBuf > 0)
2560 {
2561 /*
2562 * Figure out how much we can read.
2563 */
2564 DWORD offCaptureCursor = 0;
2565 DWORD offReadCursor = 0;
2566 HRESULT hrc = IDirectSoundCaptureBuffer_GetCurrentPosition(pStreamDS->In.pDSCB, &offCaptureCursor, &offReadCursor);
2567 AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), VERR_ACCESS_DENIED); /** @todo translate these status codes already! */
2568 //AssertMsg(offReadCursor == pStreamDS->In.offReadPos, ("%#x %#x\n", offReadCursor, pStreamDS->In.offReadPos));
2569
2570 uint32_t const cbReadable = dsoundRingDistance(offCaptureCursor, pStreamDS->In.offReadPos, pStreamDS->cbBufSize);
2571
2572 if (cbReadable >= pStreamDS->Cfg.Props.cbFrame)
2573 { /* likely */ }
2574 else
2575 {
2576 if (cbRead > 0)
2577 { /* likely */ }
2578 else if (pStreamDS->In.cOverruns < 32)
2579 {
2580 pStreamDS->In.cOverruns++;
2581 DSLOG(("DSound: Warning: Buffer full (size is %zu bytes), skipping to record data (overflow #%RU32)\n",
2582 pStreamDS->cbBufSize, pStreamDS->In.cOverruns));
2583 }
2584 break;
2585 }
2586
2587 uint32_t const cbToRead = RT_MIN(cbReadable, cbBuf);
2588 Log3Func(("offCapture=%#x offRead=%#x/%#x -> cbWritable=%#x cbToWrite=%#x {%s}\n", offCaptureCursor, offReadCursor,
2589 pStreamDS->In.offReadPos, cbReadable, cbToRead, drvHostDSoundStreamStatusString(pStreamDS)));
2590
2591 /*
2592 * Lock that amount of buffer.
2593 */
2594 PVOID pv1 = NULL;
2595 DWORD cb1 = 0;
2596 PVOID pv2 = NULL;
2597 DWORD cb2 = 0;
2598 hrc = directSoundCaptureLock(pStreamDS, pStreamDS->In.offReadPos, cbToRead, &pv1, &pv2, &cb1, &cb2, 0 /*dwFlags*/);
2599 AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), VERR_ACCESS_DENIED); /** @todo translate these status codes already! */
2600 AssertMsg(cb1 + cb2 == cbToRead, ("%#x + %#x vs %#x\n", cb1, cb2, cbToRead));
2601
2602 /*
2603 * Copy over the data.
2604 */
2605 memcpy(pvBuf, pv1, cb1);
2606 pvBuf = (uint8_t *)pvBuf + cb1;
2607 cbBuf -= cb1;
2608 cbRead += cb1;
2609
2610 if (pv2)
2611 {
2612 memcpy(pvBuf, pv2, cb2);
2613 pvBuf = (uint8_t *)pvBuf + cb2;
2614 cbBuf -= cb2;
2615 cbRead += cb2;
2616 }
2617
2618 /*
2619 * Unlock and update the write position.
2620 */
2621 directSoundCaptureUnlock(pStreamDS->In.pDSCB, pv1, pv2, cb1, cb2); /** @todo r=bird: pDSB parameter here for Unlock, but pStreamDS for Lock. Why? */
2622 pStreamDS->In.offReadPos = (pStreamDS->In.offReadPos + cb1 + cb2) % pStreamDS->cbBufSize;
2623 }
2624
2625 /*
2626 * Done.
2627 */
2628 *pcbRead = cbRead;
2629 if (cbRead)
2630 {
2631 uint64_t const msPrev = pStreamDS->msLastTransfer;
2632 pStreamDS->msLastTransfer = RTTimeMilliTS();
2633 LogFlowFunc(("cbRead=%RU32, msLastTransfer=%RU64 msNow=%RU64 cMsDelta=%RU64 {%s}\n",
2634 cbRead, msPrev, pStreamDS->msLastTransfer, msPrev ? pStreamDS->msLastTransfer - msPrev : 0,
2635 drvHostDSoundStreamStatusString(pStreamDS) ));
2636 }
2637
2638#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
2639 if (cbRead)
2640 {
2641 RTFILE hFile;
2642 int rc2 = RTFileOpen(&hFile, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "dsoundCapture.pcm",
2643 RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
2644 if (RT_SUCCESS(rc2))
2645 {
2646 RTFileWrite(hFile, (uint8_t *)pvBuf - cbRead, cbRead, NULL);
2647 RTFileClose(hFile);
2648 }
2649 }
2650#endif
2651 return VINF_SUCCESS;
2652}
2653
2654
2655/*********************************************************************************************************************************
2656* PDMDRVINS::IBase Interface *
2657*********************************************************************************************************************************/
2658
2659/**
2660 * @callback_method_impl{PDMIBASE,pfnQueryInterface}
2661 */
2662static DECLCALLBACK(void *) drvHostDSoundQueryInterface(PPDMIBASE pInterface, const char *pszIID)
2663{
2664 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
2665 PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND);
2666
2667 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
2668 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
2669 return NULL;
2670}
2671
2672
2673/*********************************************************************************************************************************
2674* PDMDRVREG Interface *
2675*********************************************************************************************************************************/
2676
2677/**
2678 * @callback_method_impl{FNPDMDRVDESTRUCT, pfnDestruct}
2679 */
2680static DECLCALLBACK(void) drvHostDSoundDestruct(PPDMDRVINS pDrvIns)
2681{
2682 PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND);
2683 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
2684
2685 LogFlowFuncEnter();
2686
2687#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
2688 if (pThis->m_pNotificationClient)
2689 {
2690 pThis->m_pNotificationClient->Unregister();
2691 pThis->m_pNotificationClient->Release();
2692
2693 pThis->m_pNotificationClient = NULL;
2694 }
2695#endif
2696
2697 PDMAudioHostEnumDelete(&pThis->DeviceEnum);
2698
2699 if (pThis->pDrvIns)
2700 CoUninitialize();
2701
2702 int rc2 = RTCritSectDelete(&pThis->CritSect);
2703 AssertRC(rc2);
2704
2705 LogFlowFuncLeave();
2706}
2707
2708
2709static LPCGUID dsoundConfigQueryGUID(PCFGMNODE pCfg, const char *pszName, RTUUID *pUuid)
2710{
2711 LPCGUID pGuid = NULL;
2712
2713 char *pszGuid = NULL;
2714 int rc = CFGMR3QueryStringAlloc(pCfg, pszName, &pszGuid);
2715 if (RT_SUCCESS(rc))
2716 {
2717 rc = RTUuidFromStr(pUuid, pszGuid);
2718 if (RT_SUCCESS(rc))
2719 pGuid = (LPCGUID)&pUuid;
2720 else
2721 DSLOGREL(("DSound: Error parsing device GUID for device '%s': %Rrc\n", pszName, rc));
2722
2723 RTStrFree(pszGuid);
2724 }
2725
2726 return pGuid;
2727}
2728
2729
2730static void dsoundConfigInit(PDRVHOSTDSOUND pThis, PCFGMNODE pCfg)
2731{
2732 pThis->Cfg.pGuidPlay = dsoundConfigQueryGUID(pCfg, "DeviceGuidOut", &pThis->Cfg.uuidPlay);
2733 pThis->Cfg.pGuidCapture = dsoundConfigQueryGUID(pCfg, "DeviceGuidIn", &pThis->Cfg.uuidCapture);
2734
2735 DSLOG(("DSound: Configuration: DeviceGuidOut {%RTuuid}, DeviceGuidIn {%RTuuid}\n",
2736 &pThis->Cfg.uuidPlay,
2737 &pThis->Cfg.uuidCapture));
2738}
2739
2740
2741/**
2742 * @callback_method_impl{FNPDMDRVCONSTRUCT,
2743 * Construct a DirectSound Audio driver instance.}
2744 */
2745static DECLCALLBACK(int) drvHostDSoundConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
2746{
2747 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
2748 PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND);
2749 RT_NOREF(fFlags);
2750 LogRel(("Audio: Initializing DirectSound audio driver\n"));
2751
2752 /*
2753 * Init basic data members and interfaces.
2754 */
2755 RTListInit(&pThis->HeadStreams);
2756 pThis->pDrvIns = pDrvIns;
2757 pThis->hDrainTimer = NIL_TMTIMERHANDLE;
2758 /* IBase */
2759 pDrvIns->IBase.pfnQueryInterface = drvHostDSoundQueryInterface;
2760 /* IHostAudio */
2761 pThis->IHostAudio.pfnGetConfig = drvHostDSoundHA_GetConfig;
2762 pThis->IHostAudio.pfnGetDevices = drvHostDSoundHA_GetDevices;
2763 pThis->IHostAudio.pfnGetStatus = drvHostDSoundHA_GetStatus;
2764 pThis->IHostAudio.pfnStreamCreate = drvHostDSoundHA_StreamCreate;
2765 pThis->IHostAudio.pfnStreamDestroy = drvHostDSoundHA_StreamDestroy;
2766 pThis->IHostAudio.pfnStreamControl = drvHostDSoundHA_StreamControl;
2767 pThis->IHostAudio.pfnStreamGetReadable = drvHostDSoundHA_StreamGetReadable;
2768 pThis->IHostAudio.pfnStreamGetWritable = drvHostDSoundHA_StreamGetWritable;
2769 pThis->IHostAudio.pfnStreamGetPending = NULL;
2770 pThis->IHostAudio.pfnStreamGetStatus = drvHostDSoundHA_StreamGetStatus;
2771 pThis->IHostAudio.pfnStreamPlay = drvHostDSoundHA_StreamPlay;
2772 pThis->IHostAudio.pfnStreamCapture = drvHostDSoundHA_StreamCapture;
2773
2774 /*
2775 * Init the static parts.
2776 */
2777 PDMAudioHostEnumInit(&pThis->DeviceEnum);
2778
2779 pThis->fEnabledIn = false;
2780 pThis->fEnabledOut = false;
2781
2782 /*
2783 * Verify that IDirectSound is available.
2784 */
2785 HRESULT hrc = CoInitializeEx(NULL, COINIT_MULTITHREADED);
2786 if (SUCCEEDED(hrc))
2787 {
2788 LPDIRECTSOUND pDirectSound = NULL;
2789 hrc = CoCreateInstance(CLSID_DirectSound, NULL, CLSCTX_ALL, IID_IDirectSound, (void **)&pDirectSound);
2790 if (SUCCEEDED(hrc))
2791 IDirectSound_Release(pDirectSound);
2792 else
2793 {
2794 LogRel(("DSound: DirectSound not available: %Rhrc\n", hrc));
2795 return VERR_AUDIO_BACKEND_INIT_FAILED;
2796 }
2797 }
2798 else
2799 {
2800 LogRel(("DSound: CoInitializeEx(,COINIT_MULTITHREADED) failed: %Rhrc\n", hrc));
2801 return VERR_AUDIO_BACKEND_INIT_FAILED;
2802 }
2803
2804#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
2805 /*
2806 * Set up WASAPI device change notifications (Vista+).
2807 */
2808 if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0))
2809 {
2810 /* Get the notification interface (from DrvAudio). */
2811# ifdef VBOX_WITH_AUDIO_CALLBACKS
2812 PPDMIAUDIONOTIFYFROMHOST pIAudioNotifyFromHost = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIAUDIONOTIFYFROMHOST);
2813 Assert(pIAudioNotifyFromHost);
2814# else
2815 PPDMIAUDIONOTIFYFROMHOST pIAudioNotifyFromHost = NULL;
2816# endif
2817 try
2818 {
2819 pThis->m_pNotificationClient = new DrvHostAudioDSoundMMNotifClient(pIAudioNotifyFromHost);
2820 }
2821 catch (std::bad_alloc &)
2822 {
2823 return VERR_NO_MEMORY;
2824 }
2825 hrc = pThis->m_pNotificationClient->Initialize();
2826 if (SUCCEEDED(hrc))
2827 {
2828 hrc = pThis->m_pNotificationClient->Register();
2829 if (SUCCEEDED(hrc))
2830 LogRel2(("DSound: Notification client is enabled (ver %#RX64)\n", RTSystemGetNtVersion()));
2831 else
2832 {
2833 LogRel(("DSound: Notification client registration failed: %Rhrc\n", hrc));
2834 return VERR_AUDIO_BACKEND_INIT_FAILED;
2835 }
2836 }
2837 else
2838 {
2839 LogRel(("DSound: Notification client initialization failed: %Rhrc\n", hrc));
2840 return VERR_AUDIO_BACKEND_INIT_FAILED;
2841 }
2842 }
2843 else
2844 LogRel2(("DSound: Notification client is disabled (ver %#RX64)\n", RTSystemGetNtVersion()));
2845#endif
2846
2847 /*
2848 * Create a timer that helps making sure draining streams actually stop playing.
2849 * (Seem dsound doesn't stop by itself in many cases.)
2850 */
2851 int rc = PDMDrvHlpTMTimerCreate(pDrvIns, TMCLOCK_REAL, drvHostDSoundDrainStopHackTimer, NULL /*pvUser*/, 0 /*fFlags*/,
2852 "DSound drain", &pThis->hDrainTimer);
2853 AssertRCReturn(rc, rc);
2854
2855 /*
2856 * Initialize configuration values and critical section.
2857 */
2858 dsoundConfigInit(pThis, pCfg);
2859 return RTCritSectInit(&pThis->CritSect);
2860}
2861
2862
2863/**
2864 * PDM driver registration.
2865 */
2866const PDMDRVREG g_DrvHostDSound =
2867{
2868 /* u32Version */
2869 PDM_DRVREG_VERSION,
2870 /* szName */
2871 "DSoundAudio",
2872 /* szRCMod */
2873 "",
2874 /* szR0Mod */
2875 "",
2876 /* pszDescription */
2877 "DirectSound Audio host driver",
2878 /* fFlags */
2879 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
2880 /* fClass. */
2881 PDM_DRVREG_CLASS_AUDIO,
2882 /* cMaxInstances */
2883 ~0U,
2884 /* cbInstance */
2885 sizeof(DRVHOSTDSOUND),
2886 /* pfnConstruct */
2887 drvHostDSoundConstruct,
2888 /* pfnDestruct */
2889 drvHostDSoundDestruct,
2890 /* pfnRelocate */
2891 NULL,
2892 /* pfnIOCtl */
2893 NULL,
2894 /* pfnPowerOn */
2895 NULL,
2896 /* pfnReset */
2897 NULL,
2898 /* pfnSuspend */
2899 NULL,
2900 /* pfnResume */
2901 NULL,
2902 /* pfnAttach */
2903 NULL,
2904 /* pfnDetach */
2905 NULL,
2906 /* pfnPowerOff */
2907 NULL,
2908 /* pfnSoftReset */
2909 NULL,
2910 /* u32EndVersion */
2911 PDM_DRVREG_VERSION
2912};
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