VirtualBox

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

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

Audio: Worked over draining, starting with the internal DMA buffer (instead of just the pre-buffer and backend buffer) and using the async I/O thread to keep calling PDMIAUDIOCONNECTOR::pfnStreamIterate and PDMIHOSTAUDIO::pfnStreamPlay (NULL buffer) every so often till the draining is done. Also put a rough deadline on the draining. The PDMAUDIOSTREAMCMD_DISABLE is now defined to stop playback/capturing immediately, even when already draining (if possible). This gets rid of the timers in DrvAudio and windows backends. DrvAudio no longer issue an DISABLE command at the end of the drain, it assumes the backend does that internally itself. After issuing PDMAUDIOSTREAMCMD_DRAIN the client (be it mixer or drvaudio) will not provide any more data for the buffers via pfnStreamPlay. Only tested windows, needs to re-test all platforms. bugref:9890

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

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