VirtualBox

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

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

Audio: Changed PDMIHOSTAUDIO::pfnStreamGetStatus into pfnStreamGetState and defined a simpler state enum (PDMHOSTAUDIOSTREAMSTATE) that fits what DrvAudio needs and the backends actually want to tell us. Fixes one VRDE issue. bugref:9890

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