VirtualBox

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

Last change on this file since 94368 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

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