VirtualBox

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

Last change on this file was 100142, checked in by vboxsync, 11 months ago

Audio/DrvHostAudioDSound: Return the correct pointer address in dsoundConfigQueryGUID(). Issue found by Parfait [committed too much].

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

© 2023 Oracle
ContactPrivacy policyTerms of Use