VirtualBox

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

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

Audio: Reworking PDMIHOSTAUDIOPORT::pfnNotifyDevicesChanged a little, from now on it will only trigger re-enumeration (darwin still needs some adjusting). Use a timer to sligtly delay the enumeration and move it out of the re-init stream code, so that we don't redo it too frequently and that we do it even when there are active streams around. Made DrvHostAudioWasApi call pfnNotifyDevicesChanged. Reduced the DSound notification client code a little while adding pfnNotifyDeviceChanged calls to it. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 109.0 KB
Line 
1/* $Id: DrvHostAudioDSound.cpp 88853 2021-05-04 08:47:13Z 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(uint32_t) drvHostDSoundHA_StreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2407{
2408 RT_NOREF(pInterface);
2409 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2410 AssertPtrReturn(pStreamDS, PDMAUDIOSTREAM_STS_NONE);
2411
2412 uint32_t fStrmStatus = PDMAUDIOSTREAM_STS_INITIALIZED;
2413 if (pStreamDS->fEnabled)
2414 fStrmStatus |= PDMAUDIOSTREAM_STS_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.pfnDoOnWorkerThread = NULL;
2764 pThis->IHostAudio.pfnStreamConfigHint = NULL;
2765 pThis->IHostAudio.pfnStreamCreate = drvHostDSoundHA_StreamCreate;
2766 pThis->IHostAudio.pfnStreamInitAsync = NULL;
2767 pThis->IHostAudio.pfnStreamDestroy = drvHostDSoundHA_StreamDestroy;
2768 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
2769 pThis->IHostAudio.pfnStreamControl = drvHostDSoundHA_StreamControl;
2770 pThis->IHostAudio.pfnStreamGetReadable = drvHostDSoundHA_StreamGetReadable;
2771 pThis->IHostAudio.pfnStreamGetWritable = drvHostDSoundHA_StreamGetWritable;
2772 pThis->IHostAudio.pfnStreamGetPending = NULL;
2773 pThis->IHostAudio.pfnStreamGetStatus = drvHostDSoundHA_StreamGetStatus;
2774 pThis->IHostAudio.pfnStreamPlay = drvHostDSoundHA_StreamPlay;
2775 pThis->IHostAudio.pfnStreamCapture = drvHostDSoundHA_StreamCapture;
2776
2777 /*
2778 * Init the static parts.
2779 */
2780 PDMAudioHostEnumInit(&pThis->DeviceEnum);
2781
2782 pThis->fEnabledIn = false;
2783 pThis->fEnabledOut = false;
2784
2785 /*
2786 * Verify that IDirectSound is available.
2787 */
2788 LPDIRECTSOUND pDirectSound = NULL;
2789 HRESULT hrc = CoCreateInstance(CLSID_DirectSound, NULL, CLSCTX_ALL, IID_IDirectSound, (void **)&pDirectSound);
2790 if (SUCCEEDED(hrc))
2791 IDirectSound_Release(pDirectSound);
2792 else
2793 {
2794 LogRel(("DSound: DirectSound not available: %Rhrc\n", hrc));
2795 return VERR_AUDIO_BACKEND_INIT_FAILED;
2796 }
2797
2798#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
2799 /*
2800 * Set up WASAPI device change notifications (Vista+).
2801 */
2802 if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0))
2803 {
2804 /* Get the notification interface (from DrvAudio). */
2805# ifdef VBOX_WITH_AUDIO_CALLBACKS
2806 PPDMIHOSTAUDIOPORT pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT);
2807 Assert(pIHostAudioPort);
2808# else
2809 PPDMIHOSTAUDIOPORT pIHostAudioPort = NULL;
2810# endif
2811 try
2812 {
2813 pThis->m_pNotificationClient = new DrvHostAudioDSoundMMNotifClient(pIHostAudioPort,
2814 pThis->Cfg.pGuidCapture == NULL,
2815 pThis->Cfg.pGuidPlay == NULL);
2816 }
2817 catch (std::bad_alloc &)
2818 {
2819 return VERR_NO_MEMORY;
2820 }
2821 hrc = pThis->m_pNotificationClient->Initialize();
2822 if (SUCCEEDED(hrc))
2823 {
2824 hrc = pThis->m_pNotificationClient->Register();
2825 if (SUCCEEDED(hrc))
2826 LogRel2(("DSound: Notification client is enabled (ver %#RX64)\n", RTSystemGetNtVersion()));
2827 else
2828 {
2829 LogRel(("DSound: Notification client registration failed: %Rhrc\n", hrc));
2830 return VERR_AUDIO_BACKEND_INIT_FAILED;
2831 }
2832 }
2833 else
2834 {
2835 LogRel(("DSound: Notification client initialization failed: %Rhrc\n", hrc));
2836 return VERR_AUDIO_BACKEND_INIT_FAILED;
2837 }
2838 }
2839 else
2840 LogRel2(("DSound: Notification client is disabled (ver %#RX64)\n", RTSystemGetNtVersion()));
2841#endif
2842
2843 /*
2844 * Create a timer that helps making sure draining streams actually stop playing.
2845 * (Seem dsound doesn't stop by itself in many cases.)
2846 */
2847 int rc = PDMDrvHlpTMTimerCreate(pDrvIns, TMCLOCK_REAL, drvHostDSoundDrainStopHackTimer, NULL /*pvUser*/, 0 /*fFlags*/,
2848 "DSound drain", &pThis->hDrainTimer);
2849 AssertRCReturn(rc, rc);
2850
2851 /*
2852 * Initialize configuration values and critical section.
2853 */
2854 dsoundConfigInit(pThis, pCfg);
2855 return RTCritSectInit(&pThis->CritSect);
2856}
2857
2858
2859/**
2860 * PDM driver registration.
2861 */
2862const PDMDRVREG g_DrvHostDSound =
2863{
2864 /* u32Version */
2865 PDM_DRVREG_VERSION,
2866 /* szName */
2867 "DSoundAudio",
2868 /* szRCMod */
2869 "",
2870 /* szR0Mod */
2871 "",
2872 /* pszDescription */
2873 "DirectSound Audio host driver",
2874 /* fFlags */
2875 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
2876 /* fClass. */
2877 PDM_DRVREG_CLASS_AUDIO,
2878 /* cMaxInstances */
2879 ~0U,
2880 /* cbInstance */
2881 sizeof(DRVHOSTDSOUND),
2882 /* pfnConstruct */
2883 drvHostDSoundConstruct,
2884 /* pfnDestruct */
2885 drvHostDSoundDestruct,
2886 /* pfnRelocate */
2887 NULL,
2888 /* pfnIOCtl */
2889 NULL,
2890 /* pfnPowerOn */
2891 NULL,
2892 /* pfnReset */
2893 NULL,
2894 /* pfnSuspend */
2895 NULL,
2896 /* pfnResume */
2897 NULL,
2898 /* pfnAttach */
2899 NULL,
2900 /* pfnDetach */
2901 NULL,
2902 /* pfnPowerOff */
2903 NULL,
2904 /* pfnSoftReset */
2905 NULL,
2906 /* u32EndVersion */
2907 PDM_DRVREG_VERSION
2908};
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