VirtualBox

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

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

Audio: Changed PPDMAUDIOBACKENDSTREAM from opaque to a common base-structure which the backends can extend with their own data. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 108.6 KB
Line 
1/* $Id: DrvHostAudioDSound.cpp 88718 2021-04-26 21:21:24Z 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,pfnStreamGetStatus}
2405 */
2406static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostDSoundHA_StreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2407{
2408 RT_NOREF(pInterface);
2409 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2410 AssertPtrReturn(pStreamDS, PDMAUDIOSTREAMSTS_FLAGS_NONE);
2411
2412 PDMAUDIOSTREAMSTS fStrmStatus = PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED;
2413 if (pStreamDS->fEnabled)
2414 fStrmStatus |= PDMAUDIOSTREAMSTS_FLAGS_ENABLED;
2415
2416 LogFlowFunc(("returns %#x for '%s' {%s}\n", fStrmStatus, pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
2417 return fStrmStatus;
2418}
2419
2420
2421/**
2422 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
2423 */
2424static DECLCALLBACK(int) drvHostDSoundHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2425 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
2426{
2427 PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
2428 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2429 AssertPtrReturn(pStreamDS, 0);
2430 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2431 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
2432 AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
2433
2434 if (pStreamDS->fEnabled)
2435 AssertReturn(pStreamDS->cbBufSize, VERR_INTERNAL_ERROR_2);
2436 else
2437 {
2438 Log2Func(("Skipping disabled stream {%s}\n", drvHostDSoundStreamStatusString(pStreamDS)));
2439 return VINF_SUCCESS;
2440 }
2441 Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) ));
2442
2443/** @todo Any condition under which we should call dsoundUpdateStatusInternal(pThis) here?
2444 * The old code thought it did so in case of failure, only it couldn't ever fails, so it never did. */
2445
2446 /*
2447 * Transfer loop.
2448 */
2449 uint32_t cbWritten = 0;
2450 while (cbBuf > 0)
2451 {
2452 /*
2453 * Figure out how much we can possibly write.
2454 */
2455 DWORD offPlayCursor = 0;
2456 DWORD cbWritable = 0;
2457 int rc = dsoundGetFreeOut(pThis, pStreamDS, &cbWritable, &offPlayCursor);
2458 AssertRCReturn(rc, rc);
2459 if (cbWritable < pStreamDS->Cfg.Props.cbFrame)
2460 break;
2461
2462 uint32_t const cbToWrite = RT_MIN(cbWritable, cbBuf);
2463 Log3Func(("offPlay=%#x offWritePos=%#x -> cbWritable=%#x cbToWrite=%#x {%s}\n", offPlayCursor, pStreamDS->Out.offWritePos,
2464 cbWritable, cbToWrite, drvHostDSoundStreamStatusString(pStreamDS) ));
2465
2466 /*
2467 * Lock that amount of buffer.
2468 */
2469 PVOID pv1 = NULL;
2470 DWORD cb1 = 0;
2471 PVOID pv2 = NULL;
2472 DWORD cb2 = 0;
2473 HRESULT hrc = directSoundPlayLock(pThis, pStreamDS, pStreamDS->Out.offWritePos, cbToWrite,
2474 &pv1, &pv2, &cb1, &cb2, 0 /*dwFlags*/);
2475 AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), VERR_ACCESS_DENIED); /** @todo translate these status codes already! */
2476 //AssertMsg(cb1 + cb2 == cbToWrite, ("%#x + %#x vs %#x\n", cb1, cb2, cbToWrite));
2477
2478 /*
2479 * Copy over the data.
2480 */
2481 memcpy(pv1, pvBuf, cb1);
2482 pvBuf = (uint8_t *)pvBuf + cb1;
2483 cbBuf -= cb1;
2484 cbWritten += cb1;
2485
2486 if (pv2)
2487 {
2488 memcpy(pv2, pvBuf, cb2);
2489 pvBuf = (uint8_t *)pvBuf + cb2;
2490 cbBuf -= cb2;
2491 cbWritten += cb2;
2492 }
2493
2494 /*
2495 * Unlock and update the write position.
2496 */
2497 directSoundPlayUnlock(pThis, pStreamDS->Out.pDSB, pv1, pv2, cb1, cb2); /** @todo r=bird: pThis + pDSB parameters here for Unlock, but only pThis for Lock. Why? */
2498 pStreamDS->Out.offWritePos = (pStreamDS->Out.offWritePos + cb1 + cb2) % pStreamDS->cbBufSize;
2499
2500 /*
2501 * If this was the first chunk, kick off playing.
2502 */
2503 if (!pStreamDS->Out.fFirstTransfer)
2504 { /* likely */ }
2505 else
2506 {
2507 *pcbWritten = cbWritten;
2508 rc = directSoundPlayStart(pThis, pStreamDS);
2509 AssertRCReturn(rc, rc);
2510 pStreamDS->Out.fFirstTransfer = false;
2511 }
2512 }
2513
2514 /*
2515 * Done.
2516 */
2517 *pcbWritten = cbWritten;
2518
2519 pStreamDS->Out.cbTransferred += cbWritten;
2520 if (cbWritten)
2521 {
2522 uint64_t const msPrev = pStreamDS->msLastTransfer;
2523 pStreamDS->Out.cbLastTransferred = cbWritten;
2524 pStreamDS->msLastTransfer = RTTimeMilliTS();
2525 LogFlowFunc(("cbLastTransferred=%RU32, msLastTransfer=%RU64 msNow=%RU64 cMsDelta=%RU64 {%s}\n",
2526 cbWritten, msPrev, pStreamDS->msLastTransfer, msPrev ? pStreamDS->msLastTransfer - msPrev : 0,
2527 drvHostDSoundStreamStatusString(pStreamDS) ));
2528 }
2529 return VINF_SUCCESS;
2530}
2531
2532
2533/**
2534 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
2535 */
2536static DECLCALLBACK(int) drvHostDSoundHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2537 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
2538{
2539 /*PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);*/ RT_NOREF(pInterface);
2540 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2541 AssertPtrReturn(pStreamDS, 0);
2542 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2543 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
2544 AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
2545
2546#if 0 /** @todo r=bird: shouldn't we do the same check as for output streams? */
2547 if (pStreamDS->fEnabled)
2548 AssertReturn(pStreamDS->cbBufSize, VERR_INTERNAL_ERROR_2);
2549 else
2550 {
2551 Log2Func(("Stream disabled, skipping\n"));
2552 return VINF_SUCCESS;
2553 }
2554#endif
2555 Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) ));
2556
2557 /*
2558 * Read loop.
2559 */
2560 uint32_t cbRead = 0;
2561 while (cbBuf > 0)
2562 {
2563 /*
2564 * Figure out how much we can read.
2565 */
2566 DWORD offCaptureCursor = 0;
2567 DWORD offReadCursor = 0;
2568 HRESULT hrc = IDirectSoundCaptureBuffer_GetCurrentPosition(pStreamDS->In.pDSCB, &offCaptureCursor, &offReadCursor);
2569 AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), VERR_ACCESS_DENIED); /** @todo translate these status codes already! */
2570 //AssertMsg(offReadCursor == pStreamDS->In.offReadPos, ("%#x %#x\n", offReadCursor, pStreamDS->In.offReadPos));
2571
2572 uint32_t const cbReadable = dsoundRingDistance(offCaptureCursor, pStreamDS->In.offReadPos, pStreamDS->cbBufSize);
2573
2574 if (cbReadable >= pStreamDS->Cfg.Props.cbFrame)
2575 { /* likely */ }
2576 else
2577 {
2578 if (cbRead > 0)
2579 { /* likely */ }
2580 else if (pStreamDS->In.cOverruns < 32)
2581 {
2582 pStreamDS->In.cOverruns++;
2583 DSLOG(("DSound: Warning: Buffer full (size is %zu bytes), skipping to record data (overflow #%RU32)\n",
2584 pStreamDS->cbBufSize, pStreamDS->In.cOverruns));
2585 }
2586 break;
2587 }
2588
2589 uint32_t const cbToRead = RT_MIN(cbReadable, cbBuf);
2590 Log3Func(("offCapture=%#x offRead=%#x/%#x -> cbWritable=%#x cbToWrite=%#x {%s}\n", offCaptureCursor, offReadCursor,
2591 pStreamDS->In.offReadPos, cbReadable, cbToRead, drvHostDSoundStreamStatusString(pStreamDS)));
2592
2593 /*
2594 * Lock that amount of buffer.
2595 */
2596 PVOID pv1 = NULL;
2597 DWORD cb1 = 0;
2598 PVOID pv2 = NULL;
2599 DWORD cb2 = 0;
2600 hrc = directSoundCaptureLock(pStreamDS, pStreamDS->In.offReadPos, cbToRead, &pv1, &pv2, &cb1, &cb2, 0 /*dwFlags*/);
2601 AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), VERR_ACCESS_DENIED); /** @todo translate these status codes already! */
2602 AssertMsg(cb1 + cb2 == cbToRead, ("%#x + %#x vs %#x\n", cb1, cb2, cbToRead));
2603
2604 /*
2605 * Copy over the data.
2606 */
2607 memcpy(pvBuf, pv1, cb1);
2608 pvBuf = (uint8_t *)pvBuf + cb1;
2609 cbBuf -= cb1;
2610 cbRead += cb1;
2611
2612 if (pv2)
2613 {
2614 memcpy(pvBuf, pv2, cb2);
2615 pvBuf = (uint8_t *)pvBuf + cb2;
2616 cbBuf -= cb2;
2617 cbRead += cb2;
2618 }
2619
2620 /*
2621 * Unlock and update the write position.
2622 */
2623 directSoundCaptureUnlock(pStreamDS->In.pDSCB, pv1, pv2, cb1, cb2); /** @todo r=bird: pDSB parameter here for Unlock, but pStreamDS for Lock. Why? */
2624 pStreamDS->In.offReadPos = (pStreamDS->In.offReadPos + cb1 + cb2) % pStreamDS->cbBufSize;
2625 }
2626
2627 /*
2628 * Done.
2629 */
2630 *pcbRead = cbRead;
2631 if (cbRead)
2632 {
2633 uint64_t const msPrev = pStreamDS->msLastTransfer;
2634 pStreamDS->msLastTransfer = RTTimeMilliTS();
2635 LogFlowFunc(("cbRead=%RU32, msLastTransfer=%RU64 msNow=%RU64 cMsDelta=%RU64 {%s}\n",
2636 cbRead, msPrev, pStreamDS->msLastTransfer, msPrev ? pStreamDS->msLastTransfer - msPrev : 0,
2637 drvHostDSoundStreamStatusString(pStreamDS) ));
2638 }
2639
2640#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
2641 if (cbRead)
2642 {
2643 RTFILE hFile;
2644 int rc2 = RTFileOpen(&hFile, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "dsoundCapture.pcm",
2645 RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
2646 if (RT_SUCCESS(rc2))
2647 {
2648 RTFileWrite(hFile, (uint8_t *)pvBuf - cbRead, cbRead, NULL);
2649 RTFileClose(hFile);
2650 }
2651 }
2652#endif
2653 return VINF_SUCCESS;
2654}
2655
2656
2657/*********************************************************************************************************************************
2658* PDMDRVINS::IBase Interface *
2659*********************************************************************************************************************************/
2660
2661/**
2662 * @callback_method_impl{PDMIBASE,pfnQueryInterface}
2663 */
2664static DECLCALLBACK(void *) drvHostDSoundQueryInterface(PPDMIBASE pInterface, const char *pszIID)
2665{
2666 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
2667 PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND);
2668
2669 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
2670 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
2671 return NULL;
2672}
2673
2674
2675/*********************************************************************************************************************************
2676* PDMDRVREG Interface *
2677*********************************************************************************************************************************/
2678
2679/**
2680 * @callback_method_impl{FNPDMDRVDESTRUCT, pfnDestruct}
2681 */
2682static DECLCALLBACK(void) drvHostDSoundDestruct(PPDMDRVINS pDrvIns)
2683{
2684 PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND);
2685 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
2686
2687 LogFlowFuncEnter();
2688
2689#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
2690 if (pThis->m_pNotificationClient)
2691 {
2692 pThis->m_pNotificationClient->Unregister();
2693 pThis->m_pNotificationClient->Release();
2694
2695 pThis->m_pNotificationClient = NULL;
2696 }
2697#endif
2698
2699 PDMAudioHostEnumDelete(&pThis->DeviceEnum);
2700
2701 int rc2 = RTCritSectDelete(&pThis->CritSect);
2702 AssertRC(rc2);
2703
2704 LogFlowFuncLeave();
2705}
2706
2707
2708static LPCGUID dsoundConfigQueryGUID(PCFGMNODE pCfg, const char *pszName, RTUUID *pUuid)
2709{
2710 LPCGUID pGuid = NULL;
2711
2712 char *pszGuid = NULL;
2713 int rc = CFGMR3QueryStringAlloc(pCfg, pszName, &pszGuid);
2714 if (RT_SUCCESS(rc))
2715 {
2716 rc = RTUuidFromStr(pUuid, pszGuid);
2717 if (RT_SUCCESS(rc))
2718 pGuid = (LPCGUID)&pUuid;
2719 else
2720 DSLOGREL(("DSound: Error parsing device GUID for device '%s': %Rrc\n", pszName, rc));
2721
2722 RTStrFree(pszGuid);
2723 }
2724
2725 return pGuid;
2726}
2727
2728
2729static void dsoundConfigInit(PDRVHOSTDSOUND pThis, PCFGMNODE pCfg)
2730{
2731 pThis->Cfg.pGuidPlay = dsoundConfigQueryGUID(pCfg, "DeviceGuidOut", &pThis->Cfg.uuidPlay);
2732 pThis->Cfg.pGuidCapture = dsoundConfigQueryGUID(pCfg, "DeviceGuidIn", &pThis->Cfg.uuidCapture);
2733
2734 DSLOG(("DSound: Configuration: DeviceGuidOut {%RTuuid}, DeviceGuidIn {%RTuuid}\n",
2735 &pThis->Cfg.uuidPlay,
2736 &pThis->Cfg.uuidCapture));
2737}
2738
2739
2740/**
2741 * @callback_method_impl{FNPDMDRVCONSTRUCT,
2742 * Construct a DirectSound Audio driver instance.}
2743 */
2744static DECLCALLBACK(int) drvHostDSoundConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
2745{
2746 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
2747 PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND);
2748 RT_NOREF(fFlags);
2749 LogRel(("Audio: Initializing DirectSound audio driver\n"));
2750
2751 /*
2752 * Init basic data members and interfaces.
2753 */
2754 RTListInit(&pThis->HeadStreams);
2755 pThis->pDrvIns = pDrvIns;
2756 pThis->hDrainTimer = NIL_TMTIMERHANDLE;
2757 /* IBase */
2758 pDrvIns->IBase.pfnQueryInterface = drvHostDSoundQueryInterface;
2759 /* IHostAudio */
2760 pThis->IHostAudio.pfnGetConfig = drvHostDSoundHA_GetConfig;
2761 pThis->IHostAudio.pfnGetDevices = drvHostDSoundHA_GetDevices;
2762 pThis->IHostAudio.pfnGetStatus = drvHostDSoundHA_GetStatus;
2763 pThis->IHostAudio.pfnStreamCreate = drvHostDSoundHA_StreamCreate;
2764 pThis->IHostAudio.pfnStreamDestroy = drvHostDSoundHA_StreamDestroy;
2765 pThis->IHostAudio.pfnStreamControl = drvHostDSoundHA_StreamControl;
2766 pThis->IHostAudio.pfnStreamGetReadable = drvHostDSoundHA_StreamGetReadable;
2767 pThis->IHostAudio.pfnStreamGetWritable = drvHostDSoundHA_StreamGetWritable;
2768 pThis->IHostAudio.pfnStreamGetPending = NULL;
2769 pThis->IHostAudio.pfnStreamGetStatus = drvHostDSoundHA_StreamGetStatus;
2770 pThis->IHostAudio.pfnStreamPlay = drvHostDSoundHA_StreamPlay;
2771 pThis->IHostAudio.pfnStreamCapture = drvHostDSoundHA_StreamCapture;
2772
2773 /*
2774 * Init the static parts.
2775 */
2776 PDMAudioHostEnumInit(&pThis->DeviceEnum);
2777
2778 pThis->fEnabledIn = false;
2779 pThis->fEnabledOut = false;
2780
2781 /*
2782 * Verify that IDirectSound is available.
2783 */
2784 LPDIRECTSOUND pDirectSound = NULL;
2785 HRESULT hrc = CoCreateInstance(CLSID_DirectSound, NULL, CLSCTX_ALL, IID_IDirectSound, (void **)&pDirectSound);
2786 if (SUCCEEDED(hrc))
2787 IDirectSound_Release(pDirectSound);
2788 else
2789 {
2790 LogRel(("DSound: DirectSound not available: %Rhrc\n", hrc));
2791 return VERR_AUDIO_BACKEND_INIT_FAILED;
2792 }
2793
2794#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
2795 /*
2796 * Set up WASAPI device change notifications (Vista+).
2797 */
2798 if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0))
2799 {
2800 /* Get the notification interface (from DrvAudio). */
2801# ifdef VBOX_WITH_AUDIO_CALLBACKS
2802 PPDMIAUDIONOTIFYFROMHOST pIAudioNotifyFromHost = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIAUDIONOTIFYFROMHOST);
2803 Assert(pIAudioNotifyFromHost);
2804# else
2805 PPDMIAUDIONOTIFYFROMHOST pIAudioNotifyFromHost = NULL;
2806# endif
2807 try
2808 {
2809 pThis->m_pNotificationClient = new DrvHostAudioDSoundMMNotifClient(pIAudioNotifyFromHost);
2810 }
2811 catch (std::bad_alloc &)
2812 {
2813 return VERR_NO_MEMORY;
2814 }
2815 hrc = pThis->m_pNotificationClient->Initialize();
2816 if (SUCCEEDED(hrc))
2817 {
2818 hrc = pThis->m_pNotificationClient->Register();
2819 if (SUCCEEDED(hrc))
2820 LogRel2(("DSound: Notification client is enabled (ver %#RX64)\n", RTSystemGetNtVersion()));
2821 else
2822 {
2823 LogRel(("DSound: Notification client registration failed: %Rhrc\n", hrc));
2824 return VERR_AUDIO_BACKEND_INIT_FAILED;
2825 }
2826 }
2827 else
2828 {
2829 LogRel(("DSound: Notification client initialization failed: %Rhrc\n", hrc));
2830 return VERR_AUDIO_BACKEND_INIT_FAILED;
2831 }
2832 }
2833 else
2834 LogRel2(("DSound: Notification client is disabled (ver %#RX64)\n", RTSystemGetNtVersion()));
2835#endif
2836
2837 /*
2838 * Create a timer that helps making sure draining streams actually stop playing.
2839 * (Seem dsound doesn't stop by itself in many cases.)
2840 */
2841 int rc = PDMDrvHlpTMTimerCreate(pDrvIns, TMCLOCK_REAL, drvHostDSoundDrainStopHackTimer, NULL /*pvUser*/, 0 /*fFlags*/,
2842 "DSound drain", &pThis->hDrainTimer);
2843 AssertRCReturn(rc, rc);
2844
2845 /*
2846 * Initialize configuration values and critical section.
2847 */
2848 dsoundConfigInit(pThis, pCfg);
2849 return RTCritSectInit(&pThis->CritSect);
2850}
2851
2852
2853/**
2854 * PDM driver registration.
2855 */
2856const PDMDRVREG g_DrvHostDSound =
2857{
2858 /* u32Version */
2859 PDM_DRVREG_VERSION,
2860 /* szName */
2861 "DSoundAudio",
2862 /* szRCMod */
2863 "",
2864 /* szR0Mod */
2865 "",
2866 /* pszDescription */
2867 "DirectSound Audio host driver",
2868 /* fFlags */
2869 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
2870 /* fClass. */
2871 PDM_DRVREG_CLASS_AUDIO,
2872 /* cMaxInstances */
2873 ~0U,
2874 /* cbInstance */
2875 sizeof(DRVHOSTDSOUND),
2876 /* pfnConstruct */
2877 drvHostDSoundConstruct,
2878 /* pfnDestruct */
2879 drvHostDSoundDestruct,
2880 /* pfnRelocate */
2881 NULL,
2882 /* pfnIOCtl */
2883 NULL,
2884 /* pfnPowerOn */
2885 NULL,
2886 /* pfnReset */
2887 NULL,
2888 /* pfnSuspend */
2889 NULL,
2890 /* pfnResume */
2891 NULL,
2892 /* pfnAttach */
2893 NULL,
2894 /* pfnDetach */
2895 NULL,
2896 /* pfnPowerOff */
2897 NULL,
2898 /* pfnSoftReset */
2899 NULL,
2900 /* u32EndVersion */
2901 PDM_DRVREG_VERSION
2902};
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