VirtualBox

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

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

Audio: Added an fImmediate indicator to the pfnStreamDestroy methods so the backend knows whether it's okay to continue draining the stream or if it must be destroyed without delay. The latter is typically only for shutdown and driver plumbing. This helps quite a bit for HDA/CoreAudio/knoppix. bugref:9890

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