VirtualBox

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

Last change on this file was 108354, checked in by vboxsync, 3 months ago

Audio/DrvHostAudioDSound.cpp: Removed unused code.

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette