VirtualBox

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

Last change on this file since 98103 was 98103, checked in by vboxsync, 23 months ago

Copyright year updates by scm.

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