VirtualBox

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

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

DrvHostAudioDSound: Multi channel support. bugref:9890

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