VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/DrvHostDSound.cpp@ 76768

Last change on this file since 76768 was 76553, checked in by vboxsync, 6 years ago

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 82.6 KB
Line 
1/* $Id: DrvHostDSound.cpp 76553 2019-01-01 01:45:53Z vboxsync $ */
2/** @file
3 * Windows host backend driver using DirectSound.
4 */
5
6/*
7 * Copyright (C) 2006-2019 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#include <VBox/log.h>
24#include <iprt/win/windows.h>
25#include <dsound.h>
26
27#include <iprt/alloc.h>
28#include <iprt/system.h>
29#include <iprt/uuid.h>
30#include <iprt/utf16.h>
31
32#include "DrvAudio.h"
33#include "VBoxDD.h"
34#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
35# include <new> /* For bad_alloc. */
36# include "VBoxMMNotificationClient.h"
37#endif
38
39
40/*********************************************************************************************************************************
41* Defined Constants And Macros *
42*********************************************************************************************************************************/
43/*
44 * IDirectSound* interface uses HRESULT status codes and the driver callbacks use
45 * the IPRT status codes. To minimize HRESULT->IPRT conversion most internal functions
46 * in the driver return HRESULT and conversion is done in the driver callbacks.
47 *
48 * Naming convention:
49 * 'dsound*' functions return IPRT status code;
50 * 'directSound*' - return HRESULT.
51 */
52
53/*
54 * Optional release logging, which a user can turn on with the
55 * 'VBoxManage debugvm' command.
56 * Debug logging still uses the common Log* macros from IPRT.
57 * Messages which always should go to the release log use LogRel.
58 */
59/* General code behavior. */
60#define DSLOG(a) do { LogRel2(a); } while(0)
61/* Something which produce a lot of logging during playback/recording. */
62#define DSLOGF(a) do { LogRel3(a); } while(0)
63/* Important messages like errors. Limited in the default release log to avoid log flood. */
64#define DSLOGREL(a) \
65 do { \
66 static int8_t s_cLogged = 0; \
67 if (s_cLogged < 8) { \
68 ++s_cLogged; \
69 LogRel(a); \
70 } else DSLOG(a); \
71 } while (0)
72
73/** Maximum number of attempts to restore the sound buffer before giving up. */
74#define DRV_DSOUND_RESTORE_ATTEMPTS_MAX 3
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
80/** Makes DRVHOSTDSOUND out of PDMIHOSTAUDIO. */
81#define PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface) \
82 ( (PDRVHOSTDSOUND)((uintptr_t)pInterface - RT_UOFFSETOF(DRVHOSTDSOUND, IHostAudio)) )
83
84
85/*********************************************************************************************************************************
86* Structures and Typedefs *
87*********************************************************************************************************************************/
88/* Dynamically load dsound.dll. */
89typedef HRESULT WINAPI FNDIRECTSOUNDENUMERATEW(LPDSENUMCALLBACKW pDSEnumCallback, PVOID pContext);
90typedef FNDIRECTSOUNDENUMERATEW *PFNDIRECTSOUNDENUMERATEW;
91typedef HRESULT WINAPI FNDIRECTSOUNDCAPTUREENUMERATEW(LPDSENUMCALLBACKW pDSEnumCallback, PVOID pContext);
92typedef FNDIRECTSOUNDCAPTUREENUMERATEW *PFNDIRECTSOUNDCAPTUREENUMERATEW;
93typedef HRESULT WINAPI FNDIRECTSOUNDCAPTURECREATE8(LPCGUID lpcGUID, LPDIRECTSOUNDCAPTURE8 *lplpDSC, LPUNKNOWN pUnkOuter);
94typedef FNDIRECTSOUNDCAPTURECREATE8 *PFNDIRECTSOUNDCAPTURECREATE8;
95
96#define VBOX_DSOUND_MAX_EVENTS 3
97
98typedef enum DSOUNDEVENT
99{
100 DSOUNDEVENT_NOTIFY = 0,
101 DSOUNDEVENT_INPUT,
102 DSOUNDEVENT_OUTPUT,
103} DSOUNDEVENT;
104
105typedef struct DSOUNDHOSTCFG
106{
107 RTUUID uuidPlay;
108 LPCGUID pGuidPlay;
109 RTUUID uuidCapture;
110 LPCGUID pGuidCapture;
111} DSOUNDHOSTCFG, *PDSOUNDHOSTCFG;
112
113typedef struct DSOUNDSTREAM
114{
115 /** The stream's acquired configuration. */
116 PDMAUDIOSTREAMCFG Cfg;
117 /** Buffer alignment. */
118 uint8_t uAlign;
119 /** Whether this stream is in an enable state on the DirectSound side. */
120 bool fEnabled;
121 /** The stream's critical section for synchronizing access. */
122 RTCRITSECT CritSect;
123 /** The internal playback / capturing buffer. */
124 PRTCIRCBUF pCircBuf;
125 /** Size (in bytes) of the DirectSound buffer.
126 * Note: This in *not* the size of the circular buffer above! */
127 DWORD cbBufSize;
128 union
129 {
130 struct
131 {
132 /** The actual DirectSound Buffer (DSB) used for the capturing.
133 * This is a secondary buffer and is used as a streaming buffer. */
134 LPDIRECTSOUNDCAPTUREBUFFER8 pDSCB;
135 /** Current read offset (in bytes) within the DSB. */
136 DWORD offReadPos;
137 /** Number of buffer overruns happened. Used for logging. */
138 uint8_t cOverruns;
139 } In;
140 struct
141 {
142 /** The actual DirectSound Buffer (DSB) used for playback.
143 * This is a secondary buffer and is used as a streaming buffer. */
144 LPDIRECTSOUNDBUFFER8 pDSB;
145 /** Current write offset (in bytes) within the DSB. */
146 DWORD offWritePos;
147 /** Offset of last play cursor within the DSB when checked for pending. */
148 DWORD offPlayCursorLastPending;
149 /** Offset of last play cursor within the DSB when last played. */
150 DWORD offPlayCursorLastPlayed;
151 /** Total amount (in bytes) written to our internal ring buffer. */
152 uint64_t cbWritten;
153 /** Total amount (in bytes) played (to the DirectSound buffer). */
154 uint64_t cbTransferred;
155 /** Flag indicating whether playback was just (re)started. */
156 bool fFirstTransfer;
157 /** Flag indicating whether this stream is in draining mode, e.g. no new
158 * data is being written to it but DirectSound still needs to be able to
159 * play its remaining (buffered) data. */
160 bool fDrain;
161 /** How much (in bytes) the last transfer from the internal buffer
162 * to the DirectSound buffer was. */
163 uint32_t cbLastTransferred;
164 /** Timestamp (in ms) of the last transfer from the internal buffer
165 * to the DirectSound buffer. */
166 uint64_t tsLastTransferredMs;
167 /** Number of buffer underruns happened. Used for logging. */
168 uint8_t cUnderruns;
169 } Out;
170 };
171#ifdef LOG_ENABLED
172 struct
173 {
174 uint64_t tsLastTransferredMs;
175 } Dbg;
176#endif
177} DSOUNDSTREAM, *PDSOUNDSTREAM;
178
179typedef struct DRVHOSTDSOUND
180{
181 /** Pointer to the driver instance structure. */
182 PPDMDRVINS pDrvIns;
183 /** Our audio host audio interface. */
184 PDMIHOSTAUDIO IHostAudio;
185 /** Critical section to serialize access. */
186 RTCRITSECT CritSect;
187 /** List of found host input devices. */
188 RTLISTANCHOR lstDevInput;
189 /** List of found host output devices. */
190 RTLISTANCHOR lstDevOutput;
191 /** DirectSound configuration options. */
192 DSOUNDHOSTCFG Cfg;
193 /** Whether this backend supports any audio input. */
194 bool fEnabledIn;
195 /** Whether this backend supports any audio output. */
196 bool fEnabledOut;
197 /** The Direct Sound playback interface. */
198 LPDIRECTSOUND8 pDS;
199 /** The Direct Sound capturing interface. */
200 LPDIRECTSOUNDCAPTURE8 pDSC;
201#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
202 VBoxMMNotificationClient *m_pNotificationClient;
203#endif
204#ifdef VBOX_WITH_AUDIO_CALLBACKS
205 /** Callback function to the upper driver.
206 * Can be NULL if not being used / registered. */
207 PFNPDMHOSTAUDIOCALLBACK pfnCallback;
208#endif
209 /** Pointer to the input stream. */
210 PDSOUNDSTREAM pDSStrmIn;
211 /** Pointer to the output stream. */
212 PDSOUNDSTREAM pDSStrmOut;
213} DRVHOSTDSOUND, *PDRVHOSTDSOUND;
214
215/** No flags specified. */
216#define DSOUNDENUMCBFLAGS_NONE 0
217/** (Release) log found devices. */
218#define DSOUNDENUMCBFLAGS_LOG RT_BIT(0)
219
220/**
221 * Callback context for enumeration callbacks
222 */
223typedef struct DSOUNDENUMCBCTX
224{
225 /** Pointer to host backend driver. */
226 PDRVHOSTDSOUND pDrv;
227 /** Enumeration flags. */
228 uint32_t fFlags;
229 /** Number of found input devices. */
230 uint8_t cDevIn;
231 /** Number of found output devices. */
232 uint8_t cDevOut;
233} DSOUNDENUMCBCTX, *PDSOUNDENUMCBCTX;
234
235typedef struct DSOUNDDEV
236{
237 RTLISTNODE Node;
238 char *pszName;
239 GUID Guid;
240} DSOUNDDEV, *PDSOUNDDEV;
241
242
243/*********************************************************************************************************************************
244* Internal Functions *
245*********************************************************************************************************************************/
246static HRESULT directSoundPlayRestore(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB);
247static HRESULT directSoundPlayStart(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS);
248static HRESULT directSoundPlayStop(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fFlush);
249static HRESULT directSoundCaptureStop(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fFlush);
250
251static void dsoundDeviceRemove(PDSOUNDDEV pDev);
252static int dsoundDevicesEnumerate(PDRVHOSTDSOUND pThis, PPDMAUDIOBACKENDCFG pCfg);
253
254static int dsoundStreamEnable(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fEnable);
255static void dsoundStreamReset(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS);
256static void dsoundUpdateStatusInternal(PDRVHOSTDSOUND pThis);
257static void dsoundUpdateStatusInternalEx(PDRVHOSTDSOUND pThis, PPDMAUDIOBACKENDCFG pCfg, uint32_t fEnum);
258
259
260static DWORD dsoundRingDistance(DWORD offEnd, DWORD offBegin, DWORD cSize)
261{
262 AssertReturn(offEnd <= cSize, 0);
263 AssertReturn(offBegin <= cSize, 0);
264
265 return offEnd >= offBegin ? offEnd - offBegin : cSize - offBegin + offEnd;
266}
267
268static int dsoundWaveFmtFromCfg(PPDMAUDIOSTREAMCFG pCfg, PWAVEFORMATEX pFmt)
269{
270 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
271 AssertPtrReturn(pFmt, VERR_INVALID_POINTER);
272
273 RT_BZERO(pFmt, sizeof(WAVEFORMATEX));
274
275 pFmt->wFormatTag = WAVE_FORMAT_PCM;
276 pFmt->nChannels = pCfg->Props.cChannels;
277 pFmt->wBitsPerSample = pCfg->Props.cBytes * 8;
278 pFmt->nSamplesPerSec = pCfg->Props.uHz;
279 pFmt->nBlockAlign = pFmt->nChannels * pFmt->wBitsPerSample / 8;
280 pFmt->nAvgBytesPerSec = pFmt->nSamplesPerSec * pFmt->nBlockAlign;
281 pFmt->cbSize = 0; /* No extra data specified. */
282
283 return VINF_SUCCESS;
284}
285
286/**
287 * Retrieves the number of free bytes available for writing to a DirectSound output stream.
288 *
289 * @return IPRT status code. VERR_NOT_AVAILABLE if unable to determine or the buffer was not recoverable.
290 * @param pThis Host audio driver instance.
291 * @param pStreamDS DirectSound output stream to retrieve number for.
292 * @param pdwFree Where to return the free amount on success.
293 */
294static int dsoundGetFreeOut(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, DWORD *pdwFree)
295{
296 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
297 AssertPtrReturn(pStreamDS, VERR_INVALID_POINTER);
298 AssertPtrReturn(pdwFree, VERR_INVALID_POINTER);
299
300 Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT); /* Paranoia. */
301
302 LPDIRECTSOUNDBUFFER8 pDSB = pStreamDS->Out.pDSB;
303 if (!pDSB)
304 {
305 AssertPtr(pDSB);
306 return VERR_INVALID_POINTER;
307 }
308
309 HRESULT hr = S_OK;
310
311 /* Get the current play position which is used for calculating the free space in the buffer. */
312 for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++)
313 {
314 DWORD cbPlayCursor, cbWriteCursor;
315 hr = IDirectSoundBuffer8_GetCurrentPosition(pDSB, &cbPlayCursor, &cbWriteCursor);
316 if (SUCCEEDED(hr))
317 {
318 int32_t cbDiff = cbWriteCursor - cbPlayCursor;
319 if (cbDiff < 0)
320 cbDiff += pStreamDS->cbBufSize;
321
322 int32_t cbFree = cbPlayCursor - pStreamDS->Out.offWritePos;
323 if (cbFree < 0)
324 cbFree += pStreamDS->cbBufSize;
325
326 if (cbFree > (int32_t)pStreamDS->cbBufSize - cbDiff)
327 {
328 pStreamDS->Out.offWritePos = cbWriteCursor;
329 cbFree = pStreamDS->cbBufSize - cbDiff;
330 }
331
332 /* When starting to use a DirectSound buffer, cbPlayCursor and cbWriteCursor
333 * both point at position 0, so we won't be able to detect how many bytes
334 * are writable that way.
335 *
336 * So use our per-stream written indicator to see if we just started a stream. */
337 if (pStreamDS->Out.cbWritten == 0)
338 cbFree = pStreamDS->cbBufSize;
339
340 DSLOGREL(("DSound: cbPlayCursor=%RU32, cbWriteCursor=%RU32, offWritePos=%RU32 -> cbFree=%RI32\n",
341 cbPlayCursor, cbWriteCursor, pStreamDS->Out.offWritePos, cbFree));
342
343 *pdwFree = cbFree;
344
345 return VINF_SUCCESS;
346 }
347
348 if (hr != DSERR_BUFFERLOST) /** @todo MSDN doesn't state this error for GetCurrentPosition(). */
349 break;
350
351 LogFunc(("Getting playing position failed due to lost buffer, restoring ...\n"));
352
353 directSoundPlayRestore(pThis, pDSB);
354 }
355
356 if (hr != DSERR_BUFFERLOST) /* Avoid log flooding if the error is still there. */
357 DSLOGREL(("DSound: Getting current playback position failed with %Rhrc\n", hr));
358
359 LogFunc(("Failed with %Rhrc\n", hr));
360
361 return VERR_NOT_AVAILABLE;
362}
363
364static char *dsoundGUIDToUtf8StrA(LPCGUID pGUID)
365{
366 if (pGUID)
367 {
368 LPOLESTR lpOLEStr;
369 HRESULT hr = StringFromCLSID(*pGUID, &lpOLEStr);
370 if (SUCCEEDED(hr))
371 {
372 char *pszGUID;
373 int rc = RTUtf16ToUtf8(lpOLEStr, &pszGUID);
374 CoTaskMemFree(lpOLEStr);
375
376 return RT_SUCCESS(rc) ? pszGUID : NULL;
377 }
378 }
379
380 return RTStrDup("{Default device}");
381}
382
383/**
384 * Clears the list of the host's playback + capturing devices.
385 *
386 * @param pThis Host audio driver instance.
387 */
388static void dsoundDevicesClear(PDRVHOSTDSOUND pThis)
389{
390 AssertPtrReturnVoid(pThis);
391
392 PDSOUNDDEV pDev, pDevNext;
393 RTListForEachSafe(&pThis->lstDevInput, pDev, pDevNext, DSOUNDDEV, Node)
394 dsoundDeviceRemove(pDev);
395
396 Assert(RTListIsEmpty(&pThis->lstDevInput));
397
398 RTListForEachSafe(&pThis->lstDevOutput, pDev, pDevNext, DSOUNDDEV, Node)
399 dsoundDeviceRemove(pDev);
400
401 Assert(RTListIsEmpty(&pThis->lstDevOutput));
402}
403
404static HRESULT directSoundPlayRestore(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB)
405{
406 RT_NOREF(pThis);
407 HRESULT hr = IDirectSoundBuffer8_Restore(pDSB);
408 if (FAILED(hr))
409 DSLOG(("DSound: Restoring playback buffer\n"));
410 else
411 DSLOGREL(("DSound: Restoring playback buffer failed with %Rhrc\n", hr));
412
413 return hr;
414}
415
416static HRESULT directSoundPlayUnlock(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB,
417 PVOID pv1, PVOID pv2,
418 DWORD cb1, DWORD cb2)
419{
420 RT_NOREF(pThis);
421 HRESULT hr = IDirectSoundBuffer8_Unlock(pDSB, pv1, cb1, pv2, cb2);
422 if (FAILED(hr))
423 DSLOGREL(("DSound: Unlocking playback buffer failed with %Rhrc\n", hr));
424 return hr;
425}
426
427static HRESULT directSoundCaptureUnlock(LPDIRECTSOUNDCAPTUREBUFFER8 pDSCB,
428 PVOID pv1, PVOID pv2,
429 DWORD cb1, DWORD cb2)
430{
431 HRESULT hr = IDirectSoundCaptureBuffer8_Unlock(pDSCB, pv1, cb1, pv2, cb2);
432 if (FAILED(hr))
433 DSLOGREL(("DSound: Unlocking capture buffer failed with %Rhrc\n", hr));
434 return hr;
435}
436
437static HRESULT directSoundPlayLock(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS,
438 DWORD dwOffset, DWORD dwBytes,
439 PVOID *ppv1, PVOID *ppv2,
440 DWORD *pcb1, DWORD *pcb2,
441 DWORD dwFlags)
442{
443 AssertReturn(dwBytes, VERR_INVALID_PARAMETER);
444
445 HRESULT hr = E_FAIL;
446 AssertCompile(DRV_DSOUND_RESTORE_ATTEMPTS_MAX > 0);
447 for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++)
448 {
449 PVOID pv1, pv2;
450 DWORD cb1, cb2;
451 hr = IDirectSoundBuffer8_Lock(pStreamDS->Out.pDSB, dwOffset, dwBytes, &pv1, &cb1, &pv2, &cb2, dwFlags);
452 if (SUCCEEDED(hr))
453 {
454 if ( (!pv1 || !(cb1 & pStreamDS->uAlign))
455 && (!pv2 || !(cb2 & pStreamDS->uAlign)))
456 {
457 if (ppv1)
458 *ppv1 = pv1;
459 if (ppv2)
460 *ppv2 = pv2;
461 if (pcb1)
462 *pcb1 = cb1;
463 if (pcb2)
464 *pcb2 = cb2;
465 return S_OK;
466 }
467 DSLOGREL(("DSound: Locking playback buffer returned misaligned buffer: cb1=%#RX32, cb2=%#RX32 (alignment: %#RX32)\n",
468 *pcb1, *pcb2, pStreamDS->uAlign));
469 directSoundPlayUnlock(pThis, pStreamDS->Out.pDSB, pv1, pv2, cb1, cb2);
470 return E_FAIL;
471 }
472
473 if (hr != DSERR_BUFFERLOST)
474 break;
475
476 LogFlowFunc(("Locking failed due to lost buffer, restoring ...\n"));
477 directSoundPlayRestore(pThis, pStreamDS->Out.pDSB);
478 }
479
480 DSLOGREL(("DSound: Locking playback buffer failed with %Rhrc (dwOff=%ld, dwBytes=%ld)\n", hr, dwOffset, dwBytes));
481 return hr;
482}
483
484static HRESULT directSoundCaptureLock(PDSOUNDSTREAM pStreamDS,
485 DWORD dwOffset, DWORD dwBytes,
486 PVOID *ppv1, PVOID *ppv2,
487 DWORD *pcb1, DWORD *pcb2,
488 DWORD dwFlags)
489{
490 PVOID pv1 = NULL;
491 PVOID pv2 = NULL;
492 DWORD cb1 = 0;
493 DWORD cb2 = 0;
494
495 HRESULT hr = IDirectSoundCaptureBuffer8_Lock(pStreamDS->In.pDSCB, dwOffset, dwBytes,
496 &pv1, &cb1, &pv2, &cb2, dwFlags);
497 if (FAILED(hr))
498 {
499 DSLOGREL(("DSound: Locking capture buffer failed with %Rhrc\n", hr));
500 return hr;
501 }
502
503 if ( (pv1 && (cb1 & pStreamDS->uAlign))
504 || (pv2 && (cb2 & pStreamDS->uAlign)))
505 {
506 DSLOGREL(("DSound: Locking capture buffer returned misaligned buffer: cb1=%RI32, cb2=%RI32 (alignment: %RU32)\n",
507 cb1, cb2, pStreamDS->uAlign));
508 directSoundCaptureUnlock(pStreamDS->In.pDSCB, pv1, pv2, cb1, cb2);
509 return E_FAIL;
510 }
511
512 *ppv1 = pv1;
513 *ppv2 = pv2;
514 *pcb1 = cb1;
515 *pcb2 = cb2;
516
517 return S_OK;
518}
519
520/*
521 * DirectSound playback
522 */
523
524static void directSoundPlayInterfaceDestroy(PDRVHOSTDSOUND pThis)
525{
526 if (pThis->pDS)
527 {
528 LogFlowFuncEnter();
529
530 IDirectSound8_Release(pThis->pDS);
531 pThis->pDS = NULL;
532 }
533}
534
535static HRESULT directSoundPlayInterfaceCreate(PDRVHOSTDSOUND pThis)
536{
537 if (pThis->pDS != NULL)
538 return S_OK;
539
540 LogFlowFuncEnter();
541
542 HRESULT hr = CoCreateInstance(CLSID_DirectSound8, NULL, CLSCTX_ALL,
543 IID_IDirectSound8, (void **)&pThis->pDS);
544 if (FAILED(hr))
545 {
546 DSLOGREL(("DSound: Creating playback instance failed with %Rhrc\n", hr));
547 }
548 else
549 {
550 hr = IDirectSound8_Initialize(pThis->pDS, pThis->Cfg.pGuidPlay);
551 if (SUCCEEDED(hr))
552 {
553 HWND hWnd = GetDesktopWindow();
554 hr = IDirectSound8_SetCooperativeLevel(pThis->pDS, hWnd, DSSCL_PRIORITY);
555 if (FAILED(hr))
556 DSLOGREL(("DSound: Setting cooperative level for window %p failed with %Rhrc\n", hWnd, hr));
557 }
558
559 if (FAILED(hr))
560 {
561 if (hr == DSERR_NODRIVER) /* Usually means that no playback devices are attached. */
562 DSLOGREL(("DSound: DirectSound playback is currently unavailable\n"));
563 else
564 DSLOGREL(("DSound: DirectSound playback initialization failed with %Rhrc\n", hr));
565
566 directSoundPlayInterfaceDestroy(pThis);
567 }
568 }
569
570 LogFlowFunc(("Returning %Rhrc\n", hr));
571 return hr;
572}
573
574static HRESULT directSoundPlayClose(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
575{
576 AssertPtrReturn(pThis, E_POINTER);
577 AssertPtrReturn(pStreamDS, E_POINTER);
578
579 LogFlowFuncEnter();
580
581 HRESULT hr = directSoundPlayStop(pThis, pStreamDS, true /* fFlush */);
582 if (FAILED(hr))
583 return hr;
584
585 DSLOG(("DSound: Closing playback stream\n"));
586
587 if (pStreamDS->pCircBuf)
588 Assert(RTCircBufUsed(pStreamDS->pCircBuf) == 0);
589
590 if (SUCCEEDED(hr))
591 {
592 RTCritSectEnter(&pThis->CritSect);
593
594 if (pStreamDS->pCircBuf)
595 {
596 RTCircBufDestroy(pStreamDS->pCircBuf);
597 pStreamDS->pCircBuf = NULL;
598 }
599
600 if (pStreamDS->Out.pDSB)
601 {
602 IDirectSoundBuffer8_Release(pStreamDS->Out.pDSB);
603 pStreamDS->Out.pDSB = NULL;
604 }
605
606 pThis->pDSStrmOut = NULL;
607
608 RTCritSectLeave(&pThis->CritSect);
609 }
610
611 if (FAILED(hr))
612 DSLOGREL(("DSound: Stopping playback stream %p failed with %Rhrc\n", pStreamDS, hr));
613
614 return hr;
615}
616
617static HRESULT directSoundPlayOpen(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS,
618 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
619{
620 AssertPtrReturn(pThis, E_POINTER);
621 AssertPtrReturn(pStreamDS, E_POINTER);
622 AssertPtrReturn(pCfgReq, E_POINTER);
623 AssertPtrReturn(pCfgAcq, E_POINTER);
624
625 LogFlowFuncEnter();
626
627 Assert(pStreamDS->Out.pDSB == NULL);
628
629 DSLOG(("DSound: Opening playback stream (uHz=%RU32, cChannels=%RU8, cBits=%RU8, fSigned=%RTbool)\n",
630 pCfgReq->Props.uHz,
631 pCfgReq->Props.cChannels,
632 pCfgReq->Props.cBytes * 8,
633 pCfgReq->Props.fSigned));
634
635 WAVEFORMATEX wfx;
636 int rc = dsoundWaveFmtFromCfg(pCfgReq, &wfx);
637 if (RT_FAILURE(rc))
638 return E_INVALIDARG;
639
640 DSLOG(("DSound: Requested playback format:\n"
641 " wFormatTag = %RI16\n"
642 " nChannels = %RI16\n"
643 " nSamplesPerSec = %RU32\n"
644 " nAvgBytesPerSec = %RU32\n"
645 " nBlockAlign = %RI16\n"
646 " wBitsPerSample = %RI16\n"
647 " cbSize = %RI16\n",
648 wfx.wFormatTag,
649 wfx.nChannels,
650 wfx.nSamplesPerSec,
651 wfx.nAvgBytesPerSec,
652 wfx.nBlockAlign,
653 wfx.wBitsPerSample,
654 wfx.cbSize));
655
656 dsoundUpdateStatusInternal(pThis);
657
658 HRESULT hr = directSoundPlayInterfaceCreate(pThis);
659 if (FAILED(hr))
660 return hr;
661
662 do /* To use breaks. */
663 {
664 LPDIRECTSOUNDBUFFER pDSB = NULL;
665
666 DSBUFFERDESC bd;
667 RT_ZERO(bd);
668 bd.dwSize = sizeof(bd);
669 bd.lpwfxFormat = &wfx;
670
671 /*
672 * As we reuse our (secondary) buffer for playing out data as it comes in,
673 * we're using this buffer as a so-called streaming buffer.
674 *
675 * See https://msdn.microsoft.com/en-us/library/windows/desktop/ee419014(v=vs.85).aspx
676 *
677 * However, as we do not want to use memory on the sound device directly
678 * (as most modern audio hardware on the host doesn't have this anyway),
679 * we're *not* going to use DSBCAPS_STATIC for that.
680 *
681 * Instead we're specifying DSBCAPS_LOCSOFTWARE, as this fits the bill
682 * of copying own buffer data to our secondary's Direct Sound buffer.
683 */
684 bd.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCSOFTWARE;
685 bd.dwBufferBytes = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props);
686
687 DSLOG(("DSound: Requested playback buffer is %RU64ms (%ld bytes)\n",
688 DrvAudioHlpBytesToMilli(bd.dwBufferBytes, &pCfgReq->Props), bd.dwBufferBytes));
689
690 hr = IDirectSound8_CreateSoundBuffer(pThis->pDS, &bd, &pDSB, NULL);
691 if (FAILED(hr))
692 {
693 DSLOGREL(("DSound: Creating playback sound buffer failed with %Rhrc\n", hr));
694 break;
695 }
696
697 /* "Upgrade" to IDirectSoundBuffer8 interface. */
698 hr = IDirectSoundBuffer_QueryInterface(pDSB, IID_IDirectSoundBuffer8, (PVOID *)&pStreamDS->Out.pDSB);
699 IDirectSoundBuffer_Release(pDSB);
700 if (FAILED(hr))
701 {
702 DSLOGREL(("DSound: Querying playback sound buffer interface failed with %Rhrc\n", hr));
703 break;
704 }
705
706 /*
707 * Query the actual parameters set for this stream.
708 * Those might be different than the initially requested parameters.
709 */
710 RT_ZERO(wfx);
711 hr = IDirectSoundBuffer8_GetFormat(pStreamDS->Out.pDSB, &wfx, sizeof(wfx), NULL);
712 if (FAILED(hr))
713 {
714 DSLOGREL(("DSound: Getting playback format failed with %Rhrc\n", hr));
715 break;
716 }
717
718 DSBCAPS bc;
719 RT_ZERO(bc);
720 bc.dwSize = sizeof(bc);
721
722 hr = IDirectSoundBuffer8_GetCaps(pStreamDS->Out.pDSB, &bc);
723 if (FAILED(hr))
724 {
725 DSLOGREL(("DSound: Getting playback capabilities failed with %Rhrc\n", hr));
726 break;
727 }
728
729 DSLOG(("DSound: Acquired playback buffer is %RU64ms (%ld bytes)\n",
730 DrvAudioHlpBytesToMilli(bc.dwBufferBytes, &pCfgReq->Props), bc.dwBufferBytes));
731
732 DSLOG(("DSound: Acquired playback format:\n"
733 " dwBufferBytes = %RI32\n"
734 " dwFlags = 0x%x\n"
735 " wFormatTag = %RI16\n"
736 " nChannels = %RI16\n"
737 " nSamplesPerSec = %RU32\n"
738 " nAvgBytesPerSec = %RU32\n"
739 " nBlockAlign = %RI16\n"
740 " wBitsPerSample = %RI16\n"
741 " cbSize = %RI16\n",
742 bc.dwBufferBytes,
743 bc.dwFlags,
744 wfx.wFormatTag,
745 wfx.nChannels,
746 wfx.nSamplesPerSec,
747 wfx.nAvgBytesPerSec,
748 wfx.nBlockAlign,
749 wfx.wBitsPerSample,
750 wfx.cbSize));
751
752 if (bc.dwBufferBytes & pStreamDS->uAlign)
753 DSLOGREL(("DSound: Playback capabilities returned misaligned buffer: size %RU32, alignment %RU32\n",
754 bc.dwBufferBytes, pStreamDS->uAlign + 1));
755
756 /*
757 * Initial state.
758 * dsoundPlayStart initializes part of it to make sure that Stop/Start continues with a correct
759 * playback buffer position.
760 */
761 pStreamDS->cbBufSize = bc.dwBufferBytes;
762
763 rc = RTCircBufCreate(&pStreamDS->pCircBuf, pStreamDS->cbBufSize) * 2; /* Use "double buffering" */
764 AssertRC(rc);
765
766 pThis->pDSStrmOut = pStreamDS;
767
768 const uint32_t cfBufSize = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamDS->cbBufSize);
769 if (cfBufSize != pCfgAcq->Backend.cfBufferSize)
770 {
771 pCfgAcq->Backend.cfBufferSize = cfBufSize;
772 pCfgAcq->Backend.cfPeriod = cfBufSize / 4;
773 pCfgAcq->Backend.cfPreBuf = pCfgAcq->Backend.cfPeriod * 2;
774 }
775
776 } while (0);
777
778 if (FAILED(hr))
779 directSoundPlayClose(pThis, pStreamDS);
780
781 return hr;
782}
783
784static void dsoundPlayClearBuffer(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
785{
786 AssertPtrReturnVoid(pStreamDS);
787
788 PPDMAUDIOPCMPROPS pProps = &pStreamDS->Cfg.Props;
789
790 HRESULT hr = IDirectSoundBuffer_SetCurrentPosition(pStreamDS->Out.pDSB, 0 /* Position */);
791 if (FAILED(hr))
792 DSLOGREL(("DSound: Setting current position to 0 when clearing buffer failed with %Rhrc\n", hr));
793
794 PVOID pv1;
795 hr = directSoundPlayLock(pThis, pStreamDS,
796 0 /* dwOffset */, pStreamDS->cbBufSize,
797 &pv1, NULL, 0, 0, DSBLOCK_ENTIREBUFFER);
798 if (SUCCEEDED(hr))
799 {
800 DrvAudioHlpClearBuf(pProps, pv1, pStreamDS->cbBufSize, PDMAUDIOPCMPROPS_B2F(pProps, pStreamDS->cbBufSize));
801
802 directSoundPlayUnlock(pThis, pStreamDS->Out.pDSB, pv1, NULL, 0, 0);
803
804 /* Make sure to get the last playback position and current write position from DirectSound again.
805 * Those positions in theory could have changed, re-fetch them to be sure. */
806 hr = IDirectSoundBuffer_GetCurrentPosition(pStreamDS->Out.pDSB,
807 &pStreamDS->Out.offPlayCursorLastPlayed, &pStreamDS->Out.offWritePos);
808 if (FAILED(hr))
809 DSLOGREL(("DSound: Re-fetching current position when clearing buffer failed with %Rhrc\n", hr));
810 }
811}
812
813/**
814 * Transfers audio data from the internal buffer to the DirectSound playback instance.
815 * Due to internal accounting and querying DirectSound, this function knows how much it can transfer at once.
816 *
817 * @return IPRT status code.
818 * @param pThis Host audio driver instance.
819 * @param pStreamDS Stream to transfer playback data for.
820 */
821static int dsoundPlayTransfer(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
822{
823 if (!pStreamDS->fEnabled)
824 {
825 Log2Func(("Stream disabled, skipping\n"));
826 return VINF_SUCCESS;
827 }
828
829 uint32_t cbTransferred = 0;
830
831 PRTCIRCBUF pCircBuf = pStreamDS->pCircBuf;
832 AssertPtr(pCircBuf);
833
834 LPDIRECTSOUNDBUFFER8 pDSB = pStreamDS->Out.pDSB;
835 AssertPtr(pDSB);
836
837 int rc = VINF_SUCCESS;
838
839 DWORD offPlayCursor, offWriteCursor;
840 HRESULT hr = IDirectSoundBuffer8_GetCurrentPosition(pDSB, &offPlayCursor, &offWriteCursor);
841 if (FAILED(hr))
842 {
843 rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
844 return rc;
845 }
846
847 DWORD cbFree, cbRemaining;
848 if (pStreamDS->Out.fFirstTransfer)
849 {
850 cbRemaining = 0;
851 cbFree = pStreamDS->cbBufSize;
852 }
853 else
854 {
855 cbFree = dsoundRingDistance(offPlayCursor, pStreamDS->Out.offWritePos, pStreamDS->cbBufSize);
856 cbRemaining = dsoundRingDistance(pStreamDS->Out.offWritePos, offPlayCursor, pStreamDS->cbBufSize);
857 }
858
859 uint32_t cbAvail = (uint32_t)RTCircBufUsed(pCircBuf);
860 uint32_t cbToTransfer = RT_MIN(cbFree, cbAvail);
861
862#ifdef LOG_ENABLED
863 if (!pStreamDS->Dbg.tsLastTransferredMs)
864 pStreamDS->Dbg.tsLastTransferredMs = RTTimeMilliTS();
865 Log3Func(("offPlay=%RU32, offWrite=%RU32, tsLastTransferredMs=%RU64ms, cbAvail=%RU32, cbFree=%RU32 -> cbToTransfer=%RU32 "
866 "(fFirst=%RTbool, fDrain=%RTbool)\n",
867 offPlayCursor, offWriteCursor, RTTimeMilliTS() - pStreamDS->Dbg.tsLastTransferredMs, cbAvail, cbFree, cbToTransfer,
868 pStreamDS->Out.fFirstTransfer, pStreamDS->Out.fDrain));
869 pStreamDS->Dbg.tsLastTransferredMs = RTTimeMilliTS();
870#endif
871
872 while (cbToTransfer)
873 {
874 DWORD cb1 = 0;
875 DWORD cb2 = 0;
876
877 void *pvBuf;
878 size_t cbBuf;
879 RTCircBufAcquireReadBlock(pCircBuf, cbToTransfer, &pvBuf, &cbBuf);
880
881 if (cbBuf)
882 {
883 PVOID pv1, pv2;
884 hr = directSoundPlayLock(pThis, pStreamDS, pStreamDS->Out.offWritePos, (DWORD)cbBuf,
885 &pv1, &pv2, &cb1, &cb2, 0 /* dwFlags */);
886 if (FAILED(hr))
887 {
888 rc = VERR_ACCESS_DENIED;
889 break;
890 }
891
892 AssertPtr(pv1);
893 Assert(cb1);
894
895 memcpy(pv1, pvBuf, cb1);
896
897 if (pv2 && cb2) /* Buffer wrap-around? Write second part. */
898 memcpy(pv2, (uint8_t *)pvBuf + cb1, cb2);
899
900 directSoundPlayUnlock(pThis, pDSB, pv1, pv2, cb1, cb2);
901
902 pStreamDS->Out.offWritePos = (pStreamDS->Out.offWritePos + cb1 + cb2) % pStreamDS->cbBufSize;
903
904 Assert(cbToTransfer >= cbBuf);
905 cbToTransfer -= (uint32_t)cbBuf;
906
907 cbTransferred += cb1 + cb2;
908 }
909
910 RTCircBufReleaseReadBlock(pCircBuf, cb1 + cb2);
911 }
912
913 pStreamDS->Out.cbTransferred += cbTransferred;
914
915 if ( pStreamDS->Out.fFirstTransfer
916 && pStreamDS->Out.cbTransferred >= DrvAudioHlpFramesToBytes(pStreamDS->Cfg.Backend.cfPreBuf, &pStreamDS->Cfg.Props))
917 {
918 hr = directSoundPlayStart(pThis, pStreamDS);
919 if (SUCCEEDED(hr))
920 {
921 pStreamDS->Out.fFirstTransfer = false;
922 }
923 else
924 rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
925 }
926
927 cbAvail = (uint32_t)RTCircBufUsed(pCircBuf);
928 if ( !cbAvail
929 && cbTransferred)
930 {
931 pStreamDS->Out.cbLastTransferred = cbTransferred;
932 pStreamDS->Out.tsLastTransferredMs = RTTimeMilliTS();
933
934 LogFlowFunc(("cbLastTransferred=%RU32, tsLastTransferredMs=%RU64\n",
935 pStreamDS->Out.cbLastTransferred, pStreamDS->Out.tsLastTransferredMs));
936 }
937
938 LogFlowFunc(("cbTransferred=%RU32, cbAvail=%RU32, rc=%Rrc\n", cbTransferred, cbAvail, rc));
939 return rc;
940}
941
942static HRESULT directSoundPlayGetStatus(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB, DWORD *pdwStatus)
943{
944 AssertPtrReturn(pThis, E_POINTER);
945 AssertPtrReturn(pDSB, E_POINTER);
946
947 AssertPtrNull(pdwStatus);
948
949 DWORD dwStatus = 0;
950 HRESULT hr = E_FAIL;
951 for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++)
952 {
953 hr = IDirectSoundBuffer8_GetStatus(pDSB, &dwStatus);
954 if ( hr == DSERR_BUFFERLOST
955 || ( SUCCEEDED(hr)
956 && (dwStatus & DSBSTATUS_BUFFERLOST)))
957 {
958 LogFlowFunc(("Getting status failed due to lost buffer, restoring ...\n"));
959 directSoundPlayRestore(pThis, pDSB);
960 }
961 else
962 break;
963 }
964
965 if (SUCCEEDED(hr))
966 {
967 if (pdwStatus)
968 *pdwStatus = dwStatus;
969 }
970 else
971 DSLOGREL(("DSound: Retrieving playback status failed with %Rhrc\n", hr));
972
973 return hr;
974}
975
976static HRESULT directSoundPlayStop(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fFlush)
977{
978 AssertPtrReturn(pThis, E_POINTER);
979 AssertPtrReturn(pStreamDS, E_POINTER);
980
981 HRESULT hr = S_OK;
982
983 if (pStreamDS->Out.pDSB)
984 {
985 DSLOG(("DSound: Stopping playback\n"));
986 hr = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB);
987 if (FAILED(hr))
988 {
989 hr = directSoundPlayRestore(pThis, pStreamDS->Out.pDSB);
990 if (FAILED(hr))
991 hr = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB);
992 }
993 }
994
995 if (SUCCEEDED(hr))
996 {
997 if (fFlush)
998 dsoundStreamReset(pThis, pStreamDS);
999 }
1000
1001 if (FAILED(hr))
1002 DSLOGREL(("DSound: %s playback failed with %Rhrc\n", fFlush ? "Stopping" : "Pausing", hr));
1003
1004 return hr;
1005}
1006
1007/**
1008 * Enables or disables a stream.
1009 *
1010 * @return IPRT status code.
1011 * @param pThis Host audio driver instance.
1012 * @param pStreamDS Stream to enable / disable.
1013 * @param fEnable Whether to enable or disable the stream.
1014 */
1015static int dsoundStreamEnable(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fEnable)
1016{
1017 RT_NOREF(pThis);
1018
1019 LogFunc(("%s %s\n",
1020 fEnable ? "Enabling" : "Disabling",
1021 pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN ? "capture" : "playback"));
1022
1023 pStreamDS->fEnabled = fEnable;
1024
1025 return VINF_SUCCESS;
1026}
1027
1028
1029/**
1030 * Resets the state of a DirectSound stream.
1031 *
1032 * @param pThis Host audio driver instance.
1033 * @param pStreamDS Stream to reset state for.
1034 */
1035static void dsoundStreamReset(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
1036{
1037 RT_NOREF(pThis);
1038
1039 LogFunc(("Resetting %s\n",
1040 pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN ? "capture" : "playback"));
1041
1042 if (pStreamDS->pCircBuf)
1043 RTCircBufReset(pStreamDS->pCircBuf);
1044
1045 if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
1046 {
1047 pStreamDS->In.offReadPos = 0;
1048 pStreamDS->In.cOverruns = 0;
1049 }
1050 else if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT)
1051 {
1052 pStreamDS->Out.fFirstTransfer = true;
1053 pStreamDS->Out.fDrain = false;
1054 pStreamDS->Out.cUnderruns = 0;
1055
1056 pStreamDS->Out.cbLastTransferred = 0;
1057 pStreamDS->Out.tsLastTransferredMs = 0;
1058
1059 pStreamDS->Out.cbTransferred = 0;
1060 pStreamDS->Out.cbWritten = 0;
1061
1062 pStreamDS->Out.offWritePos = 0;
1063 pStreamDS->Out.offPlayCursorLastPending = 0;
1064 pStreamDS->Out.offPlayCursorLastPlayed = 0;
1065 }
1066
1067#ifdef LOG_ENABLED
1068 pStreamDS->Dbg.tsLastTransferredMs = 0;
1069#endif
1070}
1071
1072
1073/**
1074 * Starts playing a DirectSound stream.
1075 *
1076 * @return HRESULT
1077 * @param pThis Host audio driver instance.
1078 * @param pStreamDS Stream to start playing.
1079 */
1080static HRESULT directSoundPlayStart(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
1081{
1082 HRESULT hr = S_OK;
1083
1084 DWORD fFlags = DSCBSTART_LOOPING;
1085
1086 for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++)
1087 {
1088 DSLOG(("DSound: Starting playback\n"));
1089 hr = IDirectSoundBuffer8_Play(pStreamDS->Out.pDSB, 0, 0, fFlags);
1090 if ( SUCCEEDED(hr)
1091 || hr != DSERR_BUFFERLOST)
1092 break;
1093 else
1094 {
1095 LogFunc(("Restarting playback failed due to lost buffer, restoring ...\n"));
1096 directSoundPlayRestore(pThis, pStreamDS->Out.pDSB);
1097 }
1098 }
1099
1100 return hr;
1101}
1102
1103
1104/*
1105 * DirectSoundCapture
1106 */
1107
1108static LPCGUID dsoundCaptureSelectDevice(PDRVHOSTDSOUND pThis, PPDMAUDIOSTREAMCFG pCfg)
1109{
1110 AssertPtrReturn(pThis, NULL);
1111 AssertPtrReturn(pCfg, NULL);
1112
1113 int rc = VINF_SUCCESS;
1114
1115 LPCGUID pGUID = pThis->Cfg.pGuidCapture;
1116 if (!pGUID)
1117 {
1118 PDSOUNDDEV pDev = NULL;
1119
1120 switch (pCfg->DestSource.Source)
1121 {
1122 case PDMAUDIORECSOURCE_LINE:
1123 /*
1124 * At the moment we're only supporting line-in in the HDA emulation,
1125 * and line-in + mic-in in the AC'97 emulation both are expected
1126 * to use the host's mic-in as well.
1127 *
1128 * So the fall through here is intentional for now.
1129 */
1130 case PDMAUDIORECSOURCE_MIC:
1131 {
1132 RTListForEach(&pThis->lstDevInput, pDev, DSOUNDDEV, Node)
1133 {
1134 if (RTStrIStr(pDev->pszName, "Mic")) /** @todo What is with non en_us windows versions? */
1135 break;
1136 }
1137
1138 if (RTListNodeIsDummy(&pThis->lstDevInput, pDev, DSOUNDDEV, Node))
1139 pDev = NULL; /* Found nothing. */
1140
1141 break;
1142 }
1143
1144 default:
1145 AssertFailedStmt(rc = VERR_NOT_SUPPORTED);
1146 break;
1147 }
1148
1149 if ( RT_SUCCESS(rc)
1150 && pDev)
1151 {
1152 DSLOG(("DSound: Guest source '%s' is using host recording device '%s'\n",
1153 DrvAudioHlpRecSrcToStr(pCfg->DestSource.Source), pDev->pszName));
1154
1155 pGUID = &pDev->Guid;
1156 }
1157 }
1158
1159 if (RT_FAILURE(rc))
1160 {
1161 LogRel(("DSound: Selecting recording device failed with %Rrc\n", rc));
1162 return NULL;
1163 }
1164
1165 char *pszGUID = dsoundGUIDToUtf8StrA(pGUID);
1166
1167 /* This always has to be in the release log. */
1168 LogRel(("DSound: Guest source '%s' is using host recording device with GUID '%s'\n",
1169 DrvAudioHlpRecSrcToStr(pCfg->DestSource.Source), pszGUID ? pszGUID: "{?}"));
1170
1171 if (pszGUID)
1172 {
1173 RTStrFree(pszGUID);
1174 pszGUID = NULL;
1175 }
1176
1177 return pGUID;
1178}
1179
1180/**
1181 * Transfers audio data from the DirectSound capture instance to the internal buffer.
1182 * Due to internal accounting and querying DirectSound, this function knows how much it can transfer at once.
1183 *
1184 * @return IPRT status code.
1185 * @param pThis Host audio driver instance.
1186 * @param pStreamDS Stream to capture audio data for.
1187 */
1188static int dsoundCaptureTransfer(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
1189{
1190 RT_NOREF(pThis);
1191
1192 LPDIRECTSOUNDCAPTUREBUFFER8 pDSCB = pStreamDS->In.pDSCB;
1193 AssertPtr(pDSCB);
1194
1195 DWORD offCaptureCursor;
1196 HRESULT hr = IDirectSoundCaptureBuffer_GetCurrentPosition(pDSCB, NULL, &offCaptureCursor);
1197 if (FAILED(hr))
1198 {
1199 AssertFailed();
1200 return VERR_ACCESS_DENIED; /** @todo Find a better rc. */
1201 }
1202
1203 DWORD cbUsed = dsoundRingDistance(offCaptureCursor, pStreamDS->In.offReadPos, pStreamDS->cbBufSize);
1204
1205 PRTCIRCBUF pCircBuf = pStreamDS->pCircBuf;
1206 AssertPtr(pCircBuf);
1207
1208 uint32_t cbFree = (uint32_t)RTCircBufFree(pCircBuf);
1209 if ( !cbFree
1210 || pStreamDS->In.cOverruns < 32) /** @todo Make this configurable. */
1211 {
1212 DSLOG(("DSound: Warning: Capture buffer full, skipping to record data (%RU32 bytes)\n", cbUsed));
1213 pStreamDS->In.cOverruns++;
1214 }
1215
1216 DWORD cbToCapture = RT_MIN(cbUsed, cbFree);
1217
1218 Log3Func(("cbUsed=%ld, cbToCapture=%ld\n", cbUsed, cbToCapture));
1219
1220 while (cbToCapture)
1221 {
1222 void *pvBuf;
1223 size_t cbBuf;
1224 RTCircBufAcquireWriteBlock(pCircBuf, cbToCapture, &pvBuf, &cbBuf);
1225
1226 if (cbBuf)
1227 {
1228 PVOID pv1, pv2;
1229 DWORD cb1, cb2;
1230 hr = directSoundCaptureLock(pStreamDS, pStreamDS->In.offReadPos, (DWORD)cbBuf,
1231 &pv1, &pv2, &cb1, &cb2, 0 /* dwFlags */);
1232 if (FAILED(hr))
1233 break;
1234
1235 AssertPtr(pv1);
1236 Assert(cb1);
1237
1238 memcpy(pvBuf, pv1, cb1);
1239
1240 if (pv2 && cb2) /* Buffer wrap-around? Write second part. */
1241 memcpy((uint8_t *)pvBuf + cb1, pv2, cb2);
1242
1243 directSoundCaptureUnlock(pDSCB, pv1, pv2, cb1, cb2);
1244
1245 pStreamDS->In.offReadPos = (pStreamDS->In.offReadPos + cb1 + cb2) % pStreamDS->cbBufSize;
1246
1247 Assert(cbToCapture >= cbBuf);
1248 cbToCapture -= (uint32_t)cbBuf;
1249 }
1250
1251#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
1252 if (cbBuf)
1253 {
1254 RTFILE fh;
1255 int rc2 = RTFileOpen(&fh, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "dsoundCapture.pcm",
1256 RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
1257 if (RT_SUCCESS(rc2))
1258 {
1259 RTFileWrite(fh, pvBuf, cbBuf, NULL);
1260 RTFileClose(fh);
1261 }
1262 }
1263#endif
1264 RTCircBufReleaseWriteBlock(pCircBuf, cbBuf);
1265 }
1266
1267 return VINF_SUCCESS;
1268}
1269
1270/**
1271 * Destroys the DirectSound capturing interface.
1272 *
1273 * @return IPRT status code.
1274 * @param pThis Driver instance to destroy capturing interface for.
1275 */
1276static void directSoundCaptureInterfaceDestroy(PDRVHOSTDSOUND pThis)
1277{
1278 if (pThis->pDSC)
1279 {
1280 LogFlowFuncEnter();
1281
1282 IDirectSoundCapture_Release(pThis->pDSC);
1283 pThis->pDSC = NULL;
1284 }
1285}
1286
1287/**
1288 * Creates the DirectSound capturing interface.
1289 *
1290 * @return IPRT status code.
1291 * @param pThis Driver instance to create the capturing interface for.
1292 * @param pCfg Audio stream to use for creating the capturing interface.
1293 */
1294static HRESULT directSoundCaptureInterfaceCreate(PDRVHOSTDSOUND pThis, PPDMAUDIOSTREAMCFG pCfg)
1295{
1296 AssertPtrReturn(pThis, E_POINTER);
1297 AssertPtrReturn(pCfg, E_POINTER);
1298
1299 if (pThis->pDSC)
1300 return S_OK;
1301
1302 LogFlowFuncEnter();
1303
1304 HRESULT hr = CoCreateInstance(CLSID_DirectSoundCapture8, NULL, CLSCTX_ALL,
1305 IID_IDirectSoundCapture8, (void **)&pThis->pDSC);
1306 if (FAILED(hr))
1307 {
1308 DSLOGREL(("DSound: Creating capture instance failed with %Rhrc\n", hr));
1309 }
1310 else
1311 {
1312 LPCGUID pGUID = dsoundCaptureSelectDevice(pThis, pCfg);
1313 /* pGUID can be NULL when using the default device. */
1314
1315 hr = IDirectSoundCapture_Initialize(pThis->pDSC, pGUID);
1316 if (FAILED(hr))
1317 {
1318 if (hr == DSERR_NODRIVER) /* Usually means that no capture devices are attached. */
1319 DSLOGREL(("DSound: Capture device currently is unavailable\n"));
1320 else
1321 DSLOGREL(("DSound: Initializing capturing device failed with %Rhrc\n", hr));
1322
1323 directSoundCaptureInterfaceDestroy(pThis);
1324 }
1325 }
1326
1327 LogFlowFunc(("Returning %Rhrc\n", hr));
1328 return hr;
1329}
1330
1331static HRESULT directSoundCaptureClose(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
1332{
1333 AssertPtrReturn(pThis, E_POINTER);
1334 AssertPtrReturn(pStreamDS, E_POINTER);
1335
1336 LogFlowFuncEnter();
1337
1338 HRESULT hr = directSoundCaptureStop(pThis, pStreamDS, true /* fFlush */);
1339 if (FAILED(hr))
1340 return hr;
1341
1342 if ( pStreamDS
1343 && pStreamDS->In.pDSCB)
1344 {
1345 DSLOG(("DSound: Closing capturing stream\n"));
1346
1347 IDirectSoundCaptureBuffer8_Release(pStreamDS->In.pDSCB);
1348 pStreamDS->In.pDSCB = NULL;
1349 }
1350
1351 LogFlowFunc(("Returning %Rhrc\n", hr));
1352 return hr;
1353}
1354
1355static HRESULT directSoundCaptureOpen(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS,
1356 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
1357{
1358 AssertPtrReturn(pThis, E_POINTER);
1359 AssertPtrReturn(pStreamDS, E_POINTER);
1360 AssertPtrReturn(pCfgReq, E_POINTER);
1361 AssertPtrReturn(pCfgAcq, E_POINTER);
1362
1363 LogFlowFuncEnter();
1364
1365 Assert(pStreamDS->In.pDSCB == NULL);
1366
1367 DSLOG(("DSound: Opening capturing stream (uHz=%RU32, cChannels=%RU8, cBits=%RU8, fSigned=%RTbool)\n",
1368 pCfgReq->Props.uHz,
1369 pCfgReq->Props.cChannels,
1370 pCfgReq->Props.cBytes * 8,
1371 pCfgReq->Props.fSigned));
1372
1373 WAVEFORMATEX wfx;
1374 int rc = dsoundWaveFmtFromCfg(pCfgReq, &wfx);
1375 if (RT_FAILURE(rc))
1376 return E_INVALIDARG;
1377
1378 dsoundUpdateStatusInternalEx(pThis, NULL /* Cfg */, DSOUNDENUMCBFLAGS_LOG /* fEnum */);
1379
1380 HRESULT hr = directSoundCaptureInterfaceCreate(pThis, pCfgReq);
1381 if (FAILED(hr))
1382 return hr;
1383
1384 do /* To use breaks. */
1385 {
1386 DSCBUFFERDESC bd;
1387 RT_ZERO(bd);
1388
1389 bd.dwSize = sizeof(bd);
1390 bd.lpwfxFormat = &wfx;
1391 bd.dwBufferBytes = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props);
1392
1393 DSLOG(("DSound: Requested capture buffer is %RU64ms (%ld bytes)\n",
1394 DrvAudioHlpBytesToMilli(bd.dwBufferBytes, &pCfgReq->Props), bd.dwBufferBytes));
1395
1396 LPDIRECTSOUNDCAPTUREBUFFER pDSCB;
1397 hr = IDirectSoundCapture_CreateCaptureBuffer(pThis->pDSC, &bd, &pDSCB, NULL);
1398 if (FAILED(hr))
1399 {
1400 if (hr == E_ACCESSDENIED)
1401 {
1402 DSLOGREL(("DSound: Capturing input from host not possible, access denied\n"));
1403 }
1404 else
1405 DSLOGREL(("DSound: Creating capture buffer failed with %Rhrc\n", hr));
1406 break;
1407 }
1408
1409 hr = IDirectSoundCaptureBuffer_QueryInterface(pDSCB, IID_IDirectSoundCaptureBuffer8, (void **)&pStreamDS->In.pDSCB);
1410 IDirectSoundCaptureBuffer_Release(pDSCB);
1411 if (FAILED(hr))
1412 {
1413 DSLOGREL(("DSound: Querying interface for capture buffer failed with %Rhrc\n", hr));
1414 break;
1415 }
1416
1417 /*
1418 * Query the actual parameters.
1419 */
1420 DWORD offByteReadPos = 0;
1421 hr = IDirectSoundCaptureBuffer8_GetCurrentPosition(pStreamDS->In.pDSCB, NULL, &offByteReadPos);
1422 if (FAILED(hr))
1423 {
1424 offByteReadPos = 0;
1425 DSLOGREL(("DSound: Getting capture position failed with %Rhrc\n", hr));
1426 }
1427
1428 RT_ZERO(wfx);
1429 hr = IDirectSoundCaptureBuffer8_GetFormat(pStreamDS->In.pDSCB, &wfx, sizeof(wfx), NULL);
1430 if (FAILED(hr))
1431 {
1432 DSLOGREL(("DSound: Getting capture format failed with %Rhrc\n", hr));
1433 break;
1434 }
1435
1436 DSCBCAPS bc;
1437 RT_ZERO(bc);
1438 bc.dwSize = sizeof(bc);
1439 hr = IDirectSoundCaptureBuffer8_GetCaps(pStreamDS->In.pDSCB, &bc);
1440 if (FAILED(hr))
1441 {
1442 DSLOGREL(("DSound: Getting capture capabilities failed with %Rhrc\n", hr));
1443 break;
1444 }
1445
1446 DSLOG(("DSound: Acquired capture buffer is %RU64ms (%ld bytes)\n",
1447 DrvAudioHlpBytesToMilli(bc.dwBufferBytes, &pCfgReq->Props), bc.dwBufferBytes));
1448
1449 DSLOG(("DSound: Capture format:\n"
1450 " dwBufferBytes = %RI32\n"
1451 " dwFlags = 0x%x\n"
1452 " wFormatTag = %RI16\n"
1453 " nChannels = %RI16\n"
1454 " nSamplesPerSec = %RU32\n"
1455 " nAvgBytesPerSec = %RU32\n"
1456 " nBlockAlign = %RI16\n"
1457 " wBitsPerSample = %RI16\n"
1458 " cbSize = %RI16\n",
1459 bc.dwBufferBytes,
1460 bc.dwFlags,
1461 wfx.wFormatTag,
1462 wfx.nChannels,
1463 wfx.nSamplesPerSec,
1464 wfx.nAvgBytesPerSec,
1465 wfx.nBlockAlign,
1466 wfx.wBitsPerSample,
1467 wfx.cbSize));
1468
1469 if (bc.dwBufferBytes & pStreamDS->uAlign)
1470 DSLOGREL(("DSound: Capture GetCaps returned misaligned buffer: size %RU32, alignment %RU32\n",
1471 bc.dwBufferBytes, pStreamDS->uAlign + 1));
1472
1473 /* Initial state: reading at the initial capture position, no error. */
1474 pStreamDS->In.offReadPos = 0;
1475 pStreamDS->cbBufSize = bc.dwBufferBytes;
1476
1477 rc = RTCircBufCreate(&pStreamDS->pCircBuf, pStreamDS->cbBufSize) * 2; /* Use "double buffering". */
1478 AssertRC(rc);
1479
1480 pThis->pDSStrmIn = pStreamDS;
1481
1482 pCfgAcq->Backend.cfBufferSize = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamDS->cbBufSize);
1483
1484 } while (0);
1485
1486 if (FAILED(hr))
1487 directSoundCaptureClose(pThis, pStreamDS);
1488
1489 LogFlowFunc(("Returning %Rhrc\n", hr));
1490 return hr;
1491}
1492
1493static HRESULT directSoundCaptureStop(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fFlush)
1494{
1495 AssertPtrReturn(pThis, E_POINTER);
1496 AssertPtrReturn(pStreamDS, E_POINTER);
1497
1498 RT_NOREF(pThis);
1499
1500 HRESULT hr = S_OK;
1501
1502 if (pStreamDS->In.pDSCB)
1503 {
1504 DSLOG(("DSound: Stopping capture\n"));
1505 hr = IDirectSoundCaptureBuffer_Stop(pStreamDS->In.pDSCB);
1506 }
1507
1508 if (SUCCEEDED(hr))
1509 {
1510 if (fFlush)
1511 dsoundStreamReset(pThis, pStreamDS);
1512 }
1513
1514 if (FAILED(hr))
1515 DSLOGREL(("DSound: Stopping capture buffer failed with %Rhrc\n", hr));
1516
1517 return hr;
1518}
1519
1520static HRESULT directSoundCaptureStart(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
1521{
1522 AssertPtrReturn(pThis, E_POINTER);
1523 AssertPtrReturn(pStreamDS, E_POINTER);
1524
1525 HRESULT hr;
1526 if (pStreamDS->In.pDSCB)
1527 {
1528 DWORD dwStatus;
1529 hr = IDirectSoundCaptureBuffer8_GetStatus(pStreamDS->In.pDSCB, &dwStatus);
1530 if (FAILED(hr))
1531 {
1532 DSLOGREL(("DSound: Retrieving capture status failed with %Rhrc\n", hr));
1533 }
1534 else
1535 {
1536 if (dwStatus & DSCBSTATUS_CAPTURING)
1537 {
1538 DSLOG(("DSound: Already capturing\n"));
1539 }
1540 else
1541 {
1542 const DWORD fFlags = DSCBSTART_LOOPING;
1543
1544 DSLOG(("DSound: Starting to capture\n"));
1545 hr = IDirectSoundCaptureBuffer8_Start(pStreamDS->In.pDSCB, fFlags);
1546 if (FAILED(hr))
1547 DSLOGREL(("DSound: Starting to capture failed with %Rhrc\n", hr));
1548 }
1549 }
1550 }
1551 else
1552 hr = E_UNEXPECTED;
1553
1554 LogFlowFunc(("Returning %Rhrc\n", hr));
1555 return hr;
1556}
1557
1558static int dsoundDevAdd(PRTLISTANCHOR pList, LPGUID pGUID, LPCWSTR pwszDescription, PDSOUNDDEV *ppDev)
1559{
1560 AssertPtrReturn(pList, VERR_INVALID_POINTER);
1561 AssertPtrReturn(pGUID, VERR_INVALID_POINTER);
1562 AssertPtrReturn(pwszDescription, VERR_INVALID_POINTER);
1563
1564 PDSOUNDDEV pDev = (PDSOUNDDEV)RTMemAlloc(sizeof(DSOUNDDEV));
1565 if (!pDev)
1566 return VERR_NO_MEMORY;
1567
1568 int rc = RTUtf16ToUtf8(pwszDescription, &pDev->pszName);
1569 if ( RT_SUCCESS(rc)
1570 && pGUID)
1571 {
1572 memcpy(&pDev->Guid, pGUID, sizeof(GUID));
1573 }
1574
1575 if (RT_SUCCESS(rc))
1576 RTListAppend(pList, &pDev->Node);
1577
1578 if (ppDev)
1579 *ppDev = pDev;
1580
1581 return rc;
1582}
1583
1584static void dsoundDeviceRemove(PDSOUNDDEV pDev)
1585{
1586 if (pDev)
1587 {
1588 if (pDev->pszName)
1589 {
1590 RTStrFree(pDev->pszName);
1591 pDev->pszName = NULL;
1592 }
1593
1594 RTListNodeRemove(&pDev->Node);
1595
1596 RTMemFree(pDev);
1597 pDev = NULL;
1598 }
1599}
1600
1601static void dsoundLogDevice(const char *pszType, LPGUID pGUID, LPCWSTR pwszDescription, LPCWSTR pwszModule)
1602{
1603 char *pszGUID = dsoundGUIDToUtf8StrA(pGUID);
1604 /* This always has to be in the release log.
1605 * Only print this when we're running in verbose (audio debug) mode, as this can generate a lot of content. */
1606 LogRel2(("DSound: %s: GUID: %s [%ls] (Module: %ls)\n", pszType, pszGUID ? pszGUID : "{?}", pwszDescription, pwszModule));
1607 RTStrFree(pszGUID);
1608}
1609
1610static BOOL CALLBACK dsoundDevicesEnumCbPlayback(LPGUID pGUID, LPCWSTR pwszDescription, LPCWSTR pwszModule, PVOID lpContext)
1611{
1612 PDSOUNDENUMCBCTX pCtx = (PDSOUNDENUMCBCTX)lpContext;
1613 AssertPtrReturn(pCtx, FALSE);
1614 AssertPtrReturn(pCtx->pDrv, FALSE);
1615
1616 if (!pGUID)
1617 return TRUE;
1618
1619 AssertPtrReturn(pwszDescription, FALSE);
1620 /* Do not care about pwszModule. */
1621
1622 if (pCtx->fFlags & DSOUNDENUMCBFLAGS_LOG)
1623 dsoundLogDevice("Output", pGUID, pwszDescription, pwszModule);
1624
1625 int rc = dsoundDevAdd(&pCtx->pDrv->lstDevOutput,
1626 pGUID, pwszDescription, NULL /* ppDev */);
1627 if (RT_FAILURE(rc))
1628 return FALSE; /* Abort enumeration. */
1629
1630 pCtx->cDevOut++;
1631
1632 return TRUE;
1633}
1634
1635static BOOL CALLBACK dsoundDevicesEnumCbCapture(LPGUID pGUID, LPCWSTR pwszDescription, LPCWSTR pwszModule, PVOID lpContext)
1636{
1637 PDSOUNDENUMCBCTX pCtx = (PDSOUNDENUMCBCTX)lpContext;
1638 AssertPtrReturn(pCtx, FALSE);
1639 AssertPtrReturn(pCtx->pDrv, FALSE);
1640
1641 if (!pGUID)
1642 return TRUE;
1643
1644 if (pCtx->fFlags & DSOUNDENUMCBFLAGS_LOG)
1645 dsoundLogDevice("Input", pGUID, pwszDescription, pwszModule);
1646
1647 int rc = dsoundDevAdd(&pCtx->pDrv->lstDevInput,
1648 pGUID, pwszDescription, NULL /* ppDev */);
1649 if (RT_FAILURE(rc))
1650 return FALSE; /* Abort enumeration. */
1651
1652 pCtx->cDevIn++;
1653
1654 return TRUE;
1655}
1656
1657/**
1658 * Does a (Re-)enumeration of the host's playback + capturing devices.
1659 *
1660 * @return IPRT status code.
1661 * @param pThis Host audio driver instance.
1662 * @param pEnmCtx Enumeration context to use.
1663 * @param fEnum Enumeration flags.
1664 */
1665static int dsoundDevicesEnumerate(PDRVHOSTDSOUND pThis, PDSOUNDENUMCBCTX pEnmCtx, uint32_t fEnum)
1666{
1667 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1668 AssertPtrReturn(pEnmCtx, VERR_INVALID_POINTER);
1669
1670 dsoundDevicesClear(pThis);
1671
1672 RTLDRMOD hDSound = NULL;
1673 int rc = RTLdrLoadSystem("dsound.dll", true /*fNoUnload*/, &hDSound);
1674 if (RT_SUCCESS(rc))
1675 {
1676 PFNDIRECTSOUNDENUMERATEW pfnDirectSoundEnumerateW = NULL;
1677 PFNDIRECTSOUNDCAPTUREENUMERATEW pfnDirectSoundCaptureEnumerateW = NULL;
1678
1679 rc = RTLdrGetSymbol(hDSound, "DirectSoundEnumerateW", (void**)&pfnDirectSoundEnumerateW);
1680 if (RT_SUCCESS(rc))
1681 rc = RTLdrGetSymbol(hDSound, "DirectSoundCaptureEnumerateW", (void**)&pfnDirectSoundCaptureEnumerateW);
1682
1683 if (RT_SUCCESS(rc))
1684 {
1685 HRESULT hr = pfnDirectSoundEnumerateW(&dsoundDevicesEnumCbPlayback, pEnmCtx);
1686 if (FAILED(hr))
1687 LogRel2(("DSound: Error enumerating host playback devices: %Rhrc\n", hr));
1688
1689 hr = pfnDirectSoundCaptureEnumerateW(&dsoundDevicesEnumCbCapture, pEnmCtx);
1690 if (FAILED(hr))
1691 LogRel2(("DSound: Error enumerating host capturing devices: %Rhrc\n", hr));
1692
1693 if (fEnum & DSOUNDENUMCBFLAGS_LOG)
1694 {
1695 LogRel2(("DSound: Found %RU8 host playback devices\n", pEnmCtx->cDevOut));
1696 LogRel2(("DSound: Found %RU8 host capturing devices\n", pEnmCtx->cDevIn));
1697 }
1698 }
1699
1700 RTLdrClose(hDSound);
1701 }
1702 else
1703 {
1704 /* No dsound.dll on this system. */
1705 LogRel2(("DSound: Could not load dsound.dll: %Rrc\n", rc));
1706 }
1707
1708 return rc;
1709}
1710
1711/**
1712 * Updates this host driver's internal status, according to the global, overall input/output
1713 * state and all connected (native) audio streams.
1714 *
1715 * @param pThis Host audio driver instance.
1716 * @param pCfg Where to store the backend configuration. Optional.
1717 * @param fEnum Enumeration flags.
1718 */
1719static void dsoundUpdateStatusInternalEx(PDRVHOSTDSOUND pThis, PPDMAUDIOBACKENDCFG pCfg, uint32_t fEnum)
1720{
1721 AssertPtrReturnVoid(pThis);
1722 /* pCfg is optional. */
1723
1724 PDMAUDIOBACKENDCFG Cfg;
1725 RT_ZERO(Cfg);
1726
1727 Cfg.cbStreamOut = sizeof(DSOUNDSTREAM);
1728 Cfg.cbStreamIn = sizeof(DSOUNDSTREAM);
1729
1730 DSOUNDENUMCBCTX cbCtx = { pThis, fEnum, 0, 0 };
1731
1732 int rc = dsoundDevicesEnumerate(pThis, &cbCtx, fEnum);
1733 if (RT_SUCCESS(rc))
1734 {
1735#if 0
1736 if ( pThis->fEnabledOut != RT_BOOL(cbCtx.cDevOut)
1737 || pThis->fEnabledIn != RT_BOOL(cbCtx.cDevIn))
1738 {
1739 /** @todo Use a registered callback to the audio connector (e.g "OnConfigurationChanged") to
1740 * let the connector know that something has changed within the host backend. */
1741 }
1742#endif
1743 pThis->fEnabledOut = RT_BOOL(cbCtx.cDevOut);
1744 pThis->fEnabledIn = RT_BOOL(cbCtx.cDevIn);
1745
1746 RTStrPrintf2(Cfg.szName, sizeof(Cfg.szName), "DirectSound audio driver");
1747
1748 Cfg.cMaxStreamsIn = UINT32_MAX;
1749 Cfg.cMaxStreamsOut = UINT32_MAX;
1750
1751 if (pCfg)
1752 memcpy(pCfg, &Cfg, sizeof(PDMAUDIOBACKENDCFG));
1753 }
1754
1755 LogFlowFuncLeaveRC(rc);
1756}
1757
1758static void dsoundUpdateStatusInternal(PDRVHOSTDSOUND pThis)
1759{
1760 dsoundUpdateStatusInternalEx(pThis, NULL /* pCfg */, 0 /* fEnum */);
1761}
1762
1763static int dsoundCreateStreamOut(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS,
1764 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
1765{
1766 LogFlowFunc(("pStreamDS=%p, pCfgReq=%p\n", pStreamDS, pCfgReq));
1767
1768 int rc = VINF_SUCCESS;
1769
1770 /* Try to open playback in case the device is already there. */
1771 HRESULT hr = directSoundPlayOpen(pThis, pStreamDS, pCfgReq, pCfgAcq);
1772 if (SUCCEEDED(hr))
1773 {
1774 rc = DrvAudioHlpStreamCfgCopy(&pStreamDS->Cfg, pCfgAcq);
1775 if (RT_SUCCESS(rc))
1776 dsoundStreamReset(pThis, pStreamDS);
1777 }
1778 else
1779 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1780
1781 LogFlowFuncLeaveRC(rc);
1782 return rc;
1783}
1784
1785static int dsoundControlStreamOut(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, PDMAUDIOSTREAMCMD enmStreamCmd)
1786{
1787 LogFlowFunc(("pStreamDS=%p, cmd=%d\n", pStreamDS, enmStreamCmd));
1788
1789 int rc = VINF_SUCCESS;
1790
1791 HRESULT hr;
1792 switch (enmStreamCmd)
1793 {
1794 case PDMAUDIOSTREAMCMD_ENABLE:
1795 {
1796 dsoundStreamEnable(pThis, pStreamDS, true /* fEnable */);
1797 break;
1798 }
1799
1800 case PDMAUDIOSTREAMCMD_RESUME:
1801 {
1802 hr = directSoundPlayStart(pThis, pStreamDS);
1803 if (FAILED(hr))
1804 rc = VERR_NOT_SUPPORTED; /** @todo Fix this. */
1805 break;
1806 }
1807
1808 case PDMAUDIOSTREAMCMD_DISABLE:
1809 {
1810 dsoundStreamEnable(pThis, pStreamDS, false /* fEnable */);
1811 hr = directSoundPlayStop(pThis, pStreamDS, true /* fFlush */);
1812 if (FAILED(hr))
1813 rc = VERR_NOT_SUPPORTED;
1814 break;
1815 }
1816
1817 case PDMAUDIOSTREAMCMD_PAUSE:
1818 {
1819 hr = directSoundPlayStop(pThis, pStreamDS, false /* fFlush */);
1820 if (FAILED(hr))
1821 rc = VERR_NOT_SUPPORTED;
1822 break;
1823 }
1824
1825 case PDMAUDIOSTREAMCMD_DRAIN:
1826 {
1827 /* Make sure we transferred everything. */
1828 pStreamDS->fEnabled = true;
1829 pStreamDS->Out.fDrain = true;
1830 rc = dsoundPlayTransfer(pThis, pStreamDS);
1831 if ( RT_SUCCESS(rc)
1832 && pStreamDS->Out.fFirstTransfer)
1833 {
1834 /* If this was the first transfer ever for this stream, make sure to also play the (short) audio data. */
1835 DSLOG(("DSound: Started playing output (short sound)\n"));
1836
1837 pStreamDS->Out.fFirstTransfer = false;
1838 pStreamDS->Out.cbLastTransferred = pStreamDS->Out.cbTransferred; /* All transferred audio data must be played. */
1839 pStreamDS->Out.tsLastTransferredMs = RTTimeMilliTS();
1840
1841 hr = directSoundPlayStart(pThis, pStreamDS);
1842 if (FAILED(hr))
1843 rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
1844 }
1845 break;
1846 }
1847
1848 case PDMAUDIOSTREAMCMD_DROP:
1849 {
1850 pStreamDS->Out.cbLastTransferred = 0;
1851 pStreamDS->Out.tsLastTransferredMs = 0;
1852 RTCircBufReset(pStreamDS->pCircBuf);
1853 break;
1854 }
1855
1856 default:
1857 rc = VERR_NOT_SUPPORTED;
1858 break;
1859 }
1860
1861 LogFlowFuncLeaveRC(rc);
1862 return rc;
1863}
1864
1865/**
1866 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
1867 */
1868int drvHostDSoundStreamPlay(PPDMIHOSTAUDIO pInterface,
1869 PPDMAUDIOBACKENDSTREAM pStream, const void *pvBuf, uint32_t cxBuf, uint32_t *pcxWritten)
1870{
1871 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1872 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1873 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
1874 AssertReturn(cxBuf, VERR_INVALID_PARAMETER);
1875 /* pcxWritten is optional. */
1876
1877 PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface);
1878 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
1879
1880 int rc = VINF_SUCCESS;
1881
1882 uint32_t cbWrittenTotal = 0;
1883
1884 uint8_t *pbBuf = (uint8_t *)pvBuf;
1885 PRTCIRCBUF pCircBuf = pStreamDS->pCircBuf;
1886
1887 uint32_t cbToPlay = RT_MIN(cxBuf, (uint32_t)RTCircBufFree(pCircBuf));
1888 while (cbToPlay)
1889 {
1890 void *pvChunk;
1891 size_t cbChunk;
1892 RTCircBufAcquireWriteBlock(pCircBuf, cbToPlay, &pvChunk, &cbChunk);
1893
1894 if (cbChunk)
1895 {
1896 memcpy(pvChunk, pbBuf, cbChunk);
1897
1898 pbBuf += cbChunk;
1899 Assert(cbToPlay >= cbChunk);
1900 cbToPlay -= (uint32_t)cbChunk;
1901
1902 cbWrittenTotal += (uint32_t)cbChunk;
1903 }
1904
1905 RTCircBufReleaseWriteBlock(pCircBuf, cbChunk);
1906 }
1907
1908 Assert(cbWrittenTotal <= cxBuf);
1909 Assert(cbWrittenTotal == cxBuf);
1910
1911 pStreamDS->Out.cbWritten += cbWrittenTotal;
1912
1913 if (RT_SUCCESS(rc))
1914 {
1915 if (pcxWritten)
1916 *pcxWritten = cbWrittenTotal;
1917 }
1918 else
1919 dsoundUpdateStatusInternal(pThis);
1920
1921 return rc;
1922}
1923
1924static int dsoundDestroyStreamOut(PDRVHOSTDSOUND pThis, PPDMAUDIOBACKENDSTREAM pStream)
1925{
1926 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
1927
1928 LogFlowFuncEnter();
1929
1930 HRESULT hr = directSoundPlayStop(pThis, pStreamDS, true /* fFlush */);
1931 if (SUCCEEDED(hr))
1932 {
1933 hr = directSoundPlayClose(pThis, pStreamDS);
1934 if (FAILED(hr))
1935 return VERR_GENERAL_FAILURE; /** @todo Fix. */
1936 }
1937
1938 return VINF_SUCCESS;
1939}
1940
1941static int dsoundCreateStreamIn(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS,
1942 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
1943{
1944 LogFunc(("pStreamDS=%p, pCfgReq=%p, enmRecSource=%s\n",
1945 pStreamDS, pCfgReq, DrvAudioHlpRecSrcToStr(pCfgReq->DestSource.Source)));
1946
1947 int rc = VINF_SUCCESS;
1948
1949 /* Try to open capture in case the device is already there. */
1950 HRESULT hr = directSoundCaptureOpen(pThis, pStreamDS, pCfgReq, pCfgAcq);
1951 if (SUCCEEDED(hr))
1952 {
1953 rc = DrvAudioHlpStreamCfgCopy(&pStreamDS->Cfg, pCfgAcq);
1954 if (RT_SUCCESS(rc))
1955 dsoundStreamReset(pThis, pStreamDS);
1956 }
1957 else
1958 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1959
1960 return rc;
1961}
1962
1963static int dsoundControlStreamIn(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, PDMAUDIOSTREAMCMD enmStreamCmd)
1964{
1965 LogFlowFunc(("pStreamDS=%p, enmStreamCmd=%ld\n", pStreamDS, enmStreamCmd));
1966
1967 int rc = VINF_SUCCESS;
1968
1969 HRESULT hr;
1970 switch (enmStreamCmd)
1971 {
1972 case PDMAUDIOSTREAMCMD_ENABLE:
1973 dsoundStreamEnable(pThis, pStreamDS, true /* fEnable */);
1974 RT_FALL_THROUGH();
1975 case PDMAUDIOSTREAMCMD_RESUME:
1976 {
1977 /* Try to start capture. If it fails, then reopen and try again. */
1978 hr = directSoundCaptureStart(pThis, pStreamDS);
1979 if (FAILED(hr))
1980 {
1981 hr = directSoundCaptureClose(pThis, pStreamDS);
1982 if (SUCCEEDED(hr))
1983 {
1984 PDMAUDIOSTREAMCFG CfgAcq;
1985 hr = directSoundCaptureOpen(pThis, pStreamDS, &pStreamDS->Cfg /* pCfgReq */, &CfgAcq);
1986 if (SUCCEEDED(hr))
1987 {
1988 rc = DrvAudioHlpStreamCfgCopy(&pStreamDS->Cfg, &CfgAcq);
1989 if (RT_FAILURE(rc))
1990 break;
1991
1992 /** @todo What to do if the format has changed? */
1993
1994 hr = directSoundCaptureStart(pThis, pStreamDS);
1995 }
1996 }
1997 }
1998
1999 if (FAILED(hr))
2000 rc = VERR_NOT_SUPPORTED;
2001 break;
2002 }
2003
2004 case PDMAUDIOSTREAMCMD_DISABLE:
2005 dsoundStreamEnable(pThis, pStreamDS, false /* fEnable */);
2006 RT_FALL_THROUGH();
2007 case PDMAUDIOSTREAMCMD_PAUSE:
2008 {
2009 directSoundCaptureStop(pThis, pStreamDS,
2010 enmStreamCmd == PDMAUDIOSTREAMCMD_DISABLE /* fFlush */);
2011
2012 /* Return success in any case, as stopping the capture can fail if
2013 * the capture buffer is not around anymore.
2014 *
2015 * This can happen if the host's capturing device has been changed suddenly. */
2016 rc = VINF_SUCCESS;
2017 break;
2018 }
2019
2020 default:
2021 rc = VERR_NOT_SUPPORTED;
2022 break;
2023 }
2024
2025 return rc;
2026}
2027
2028/**
2029 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
2030 */
2031int drvHostDSoundStreamCapture(PPDMIHOSTAUDIO pInterface,
2032 PPDMAUDIOBACKENDSTREAM pStream, void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead)
2033{
2034
2035 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2036 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2037 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2038 AssertReturn(cxBuf, VERR_INVALID_PARAMETER);
2039
2040 PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface);
2041 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2042
2043 int rc = VINF_SUCCESS;
2044
2045 uint32_t cbReadTotal = 0;
2046
2047 uint32_t cbToRead = RT_MIN((uint32_t)RTCircBufUsed(pStreamDS->pCircBuf), cxBuf);
2048 while (cbToRead)
2049 {
2050 void *pvChunk;
2051 size_t cbChunk;
2052 RTCircBufAcquireReadBlock(pStreamDS->pCircBuf, cbToRead, &pvChunk, &cbChunk);
2053
2054 if (cbChunk)
2055 {
2056 memcpy((uint8_t *)pvBuf + cbReadTotal, pvChunk, cbChunk);
2057 cbReadTotal += (uint32_t)cbChunk;
2058 Assert(cbToRead >= cbChunk);
2059 cbToRead -= (uint32_t)cbChunk;
2060 }
2061
2062 RTCircBufReleaseReadBlock(pStreamDS->pCircBuf, cbChunk);
2063 }
2064
2065 if (RT_SUCCESS(rc))
2066 {
2067 if (pcxRead)
2068 *pcxRead = cbReadTotal;
2069 }
2070 else
2071 dsoundUpdateStatusInternal(pThis);
2072
2073 return rc;
2074}
2075
2076static int dsoundDestroyStreamIn(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
2077{
2078 LogFlowFuncEnter();
2079
2080 directSoundCaptureClose(pThis, pStreamDS);
2081
2082 return VINF_SUCCESS;
2083}
2084
2085/**
2086 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
2087 */
2088int drvHostDSoundGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
2089{
2090 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2091 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
2092
2093 PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface);
2094
2095 dsoundUpdateStatusInternalEx(pThis, pBackendCfg, 0 /* fEnum */);
2096
2097 return VINF_SUCCESS;
2098}
2099
2100/**
2101 * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown}
2102 */
2103void drvHostDSoundShutdown(PPDMIHOSTAUDIO pInterface)
2104{
2105 PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface);
2106
2107 LogFlowFuncEnter();
2108
2109 RT_NOREF(pThis);
2110
2111 LogFlowFuncLeave();
2112}
2113
2114/**
2115 * @interface_method_impl{PDMIHOSTAUDIO,pfnInit}
2116 */
2117static DECLCALLBACK(int) drvHostDSoundInit(PPDMIHOSTAUDIO pInterface)
2118{
2119 PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface);
2120 LogFlowFuncEnter();
2121
2122 int rc;
2123
2124 /* Verify that IDirectSound is available. */
2125 LPDIRECTSOUND pDirectSound = NULL;
2126 HRESULT hr = CoCreateInstance(CLSID_DirectSound, NULL, CLSCTX_ALL, IID_IDirectSound, (void **)&pDirectSound);
2127 if (SUCCEEDED(hr))
2128 {
2129 IDirectSound_Release(pDirectSound);
2130
2131 rc = VINF_SUCCESS;
2132
2133 dsoundUpdateStatusInternalEx(pThis, NULL /* pCfg */, DSOUNDENUMCBFLAGS_LOG /* fEnum */);
2134 }
2135 else
2136 {
2137 DSLOGREL(("DSound: DirectSound not available: %Rhrc\n", hr));
2138 rc = VERR_NOT_SUPPORTED;
2139 }
2140
2141 LogFlowFuncLeaveRC(rc);
2142 return rc;
2143}
2144
2145static LPCGUID dsoundConfigQueryGUID(PCFGMNODE pCfg, const char *pszName, RTUUID *pUuid)
2146{
2147 LPCGUID pGuid = NULL;
2148
2149 char *pszGuid = NULL;
2150 int rc = CFGMR3QueryStringAlloc(pCfg, pszName, &pszGuid);
2151 if (RT_SUCCESS(rc))
2152 {
2153 rc = RTUuidFromStr(pUuid, pszGuid);
2154 if (RT_SUCCESS(rc))
2155 pGuid = (LPCGUID)&pUuid;
2156 else
2157 DSLOGREL(("DSound: Error parsing device GUID for device '%s': %Rrc\n", pszName, rc));
2158
2159 RTStrFree(pszGuid);
2160 }
2161
2162 return pGuid;
2163}
2164
2165static int dsoundConfigInit(PDRVHOSTDSOUND pThis, PCFGMNODE pCfg)
2166{
2167 pThis->Cfg.pGuidPlay = dsoundConfigQueryGUID(pCfg, "DeviceGuidOut", &pThis->Cfg.uuidPlay);
2168 pThis->Cfg.pGuidCapture = dsoundConfigQueryGUID(pCfg, "DeviceGuidIn", &pThis->Cfg.uuidCapture);
2169
2170 DSLOG(("DSound: Configuration: DeviceGuidOut {%RTuuid}, DeviceGuidIn {%RTuuid}\n",
2171 &pThis->Cfg.uuidPlay,
2172 &pThis->Cfg.uuidCapture));
2173
2174 return VINF_SUCCESS;
2175}
2176
2177/**
2178 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
2179 */
2180static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostDSoundGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
2181{
2182 RT_NOREF(enmDir);
2183 AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
2184
2185 return PDMAUDIOBACKENDSTS_RUNNING;
2186}
2187
2188/**
2189 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
2190 */
2191static DECLCALLBACK(int) drvHostDSoundStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2192 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
2193{
2194 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2195 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2196 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
2197 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
2198
2199 PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface);
2200 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2201
2202 int rc;
2203 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
2204 rc = dsoundCreateStreamIn(pThis, pStreamDS, pCfgReq, pCfgAcq);
2205 else
2206 rc = dsoundCreateStreamOut(pThis, pStreamDS, pCfgReq, pCfgAcq);
2207
2208 if (RT_SUCCESS(rc))
2209 {
2210 rc = DrvAudioHlpStreamCfgCopy(&pStreamDS->Cfg, pCfgAcq);
2211 if (RT_SUCCESS(rc))
2212 rc = RTCritSectInit(&pStreamDS->CritSect);
2213 }
2214
2215 return rc;
2216}
2217
2218/**
2219 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
2220 */
2221static DECLCALLBACK(int) drvHostDSoundStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2222{
2223 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2224 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2225
2226 PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface);
2227 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2228
2229 int rc;
2230 if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
2231 rc = dsoundDestroyStreamIn(pThis, pStreamDS);
2232 else
2233 rc = dsoundDestroyStreamOut(pThis, pStreamDS);
2234
2235 if (RT_SUCCESS(rc))
2236 {
2237 if (RTCritSectIsInitialized(&pStreamDS->CritSect))
2238 rc = RTCritSectDelete(&pStreamDS->CritSect);
2239 }
2240
2241 return rc;
2242}
2243
2244/**
2245 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
2246 */
2247static DECLCALLBACK(int) drvHostDSoundStreamControl(PPDMIHOSTAUDIO pInterface,
2248 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
2249{
2250 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2251 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2252
2253 PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface);
2254 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2255
2256 int rc;
2257 if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
2258 rc = dsoundControlStreamIn(pThis, pStreamDS, enmStreamCmd);
2259 else
2260 rc = dsoundControlStreamOut(pThis, pStreamDS, enmStreamCmd);
2261
2262 return rc;
2263}
2264
2265/**
2266 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
2267 */
2268static DECLCALLBACK(uint32_t) drvHostDSoundStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2269{
2270 RT_NOREF(pInterface);
2271 AssertPtrReturn(pStream, PDMAUDIOSTREAMSTS_FLAG_NONE);
2272
2273 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2274
2275 if ( pStreamDS->fEnabled
2276 && pStreamDS->pCircBuf)
2277 {
2278 return (uint32_t)RTCircBufUsed(pStreamDS->pCircBuf);
2279 }
2280
2281 return 0;
2282}
2283
2284/**
2285 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
2286 */
2287static DECLCALLBACK(uint32_t) drvHostDSoundStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2288{
2289 AssertPtrReturn(pInterface, PDMAUDIOSTREAMSTS_FLAG_NONE);
2290 AssertPtrReturn(pStream, PDMAUDIOSTREAMSTS_FLAG_NONE);
2291
2292 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2293
2294 if (pStreamDS->fEnabled)
2295 return (uint32_t)RTCircBufFree(pStreamDS->pCircBuf);
2296
2297 return 0;
2298}
2299
2300/**
2301 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending}
2302 */
2303static DECLCALLBACK(uint32_t) drvHostDSoundStreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2304{
2305 RT_NOREF(pInterface);
2306 AssertPtrReturn(pStream, 0);
2307
2308 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2309
2310 if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT)
2311 {
2312 uint32_t cbPending = 0;
2313
2314 /* Any uncommitted data left? */
2315 if (pStreamDS->pCircBuf)
2316 cbPending = (uint32_t)RTCircBufUsed(pStreamDS->pCircBuf);
2317
2318 /* Check if we have committed data which still needs to be played by
2319 * by DirectSound's streaming buffer. */
2320 if (!cbPending)
2321 {
2322 const uint64_t diffLastTransferredMs = RTTimeMilliTS() - pStreamDS->Out.tsLastTransferredMs;
2323 const uint64_t uLastTranserredChunkMs = DrvAudioHlpBytesToMilli(pStreamDS->Out.cbLastTransferred, &pStreamDS->Cfg.Props);
2324 if ( uLastTranserredChunkMs
2325 && diffLastTransferredMs < uLastTranserredChunkMs)
2326 cbPending = 1;
2327
2328 Log3Func(("diffLastTransferredMs=%RU64ms, uLastTranserredChunkMs=%RU64ms (%RU32 bytes) -> cbPending=%RU32\n",
2329 diffLastTransferredMs, uLastTranserredChunkMs, pStreamDS->Out.cbLastTransferred, cbPending));
2330 }
2331 else
2332 Log3Func(("cbPending=%RU32\n", cbPending));
2333
2334 return cbPending;
2335 }
2336 /* Note: For input streams we never have pending data left. */
2337
2338 return 0;
2339}
2340
2341/**
2342 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
2343 */
2344static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostDSoundStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2345{
2346 RT_NOREF(pInterface);
2347 AssertPtrReturn(pStream, PDMAUDIOSTREAMSTS_FLAG_NONE);
2348
2349 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2350
2351 PDMAUDIOSTREAMSTS strmSts = PDMAUDIOSTREAMSTS_FLAG_INITIALIZED;
2352
2353 if (pStreamDS->fEnabled)
2354 strmSts |= PDMAUDIOSTREAMSTS_FLAG_ENABLED;
2355
2356 return strmSts;
2357}
2358
2359/**
2360 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate}
2361 */
2362static DECLCALLBACK(int) drvHostDSoundStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2363{
2364 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2365 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2366
2367 PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface);
2368 PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
2369
2370 if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
2371 {
2372 return dsoundCaptureTransfer(pThis, pStreamDS);
2373 }
2374 else if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT)
2375 {
2376 return dsoundPlayTransfer(pThis, pStreamDS);
2377 }
2378
2379 return VINF_SUCCESS;
2380}
2381
2382#ifdef VBOX_WITH_AUDIO_CALLBACKS
2383/**
2384 * @interface_method_impl{PDMIHOSTAUDIO,pfnSetCallback}
2385 */
2386static DECLCALLBACK(int) drvHostDSoundSetCallback(PPDMIHOSTAUDIO pInterface, PFNPDMHOSTAUDIOCALLBACK pfnCallback)
2387{
2388 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2389 /* pfnCallback will be handled below. */
2390
2391 PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface);
2392
2393 int rc = RTCritSectEnter(&pThis->CritSect);
2394 if (RT_SUCCESS(rc))
2395 {
2396 LogFunc(("pfnCallback=%p\n", pfnCallback));
2397
2398 if (pfnCallback) /* Register. */
2399 {
2400 Assert(pThis->pfnCallback == NULL);
2401 pThis->pfnCallback = pfnCallback;
2402
2403#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
2404 if (pThis->m_pNotificationClient)
2405 pThis->m_pNotificationClient->RegisterCallback(pThis->pDrvIns, pfnCallback);
2406#endif
2407 }
2408 else /* Unregister. */
2409 {
2410 if (pThis->pfnCallback)
2411 pThis->pfnCallback = NULL;
2412
2413#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
2414 if (pThis->m_pNotificationClient)
2415 pThis->m_pNotificationClient->UnregisterCallback();
2416#endif
2417 }
2418
2419 int rc2 = RTCritSectLeave(&pThis->CritSect);
2420 AssertRC(rc2);
2421 }
2422
2423 return rc;
2424}
2425#endif
2426
2427
2428/*********************************************************************************************************************************
2429* PDMDRVINS::IBase Interface *
2430*********************************************************************************************************************************/
2431
2432/**
2433 * @callback_method_impl{PDMIBASE,pfnQueryInterface}
2434 */
2435static DECLCALLBACK(void *) drvHostDSoundQueryInterface(PPDMIBASE pInterface, const char *pszIID)
2436{
2437 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
2438 PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND);
2439
2440 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
2441 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
2442 return NULL;
2443}
2444
2445
2446/*********************************************************************************************************************************
2447* PDMDRVREG Interface *
2448*********************************************************************************************************************************/
2449
2450/**
2451 * @callback_method_impl{FNPDMDRVDESTRUCT, pfnDestruct}
2452 */
2453static DECLCALLBACK(void) drvHostDSoundDestruct(PPDMDRVINS pDrvIns)
2454{
2455 PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND);
2456 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
2457
2458 LogFlowFuncEnter();
2459
2460#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
2461 if (pThis->m_pNotificationClient)
2462 {
2463 pThis->m_pNotificationClient->Dispose();
2464 pThis->m_pNotificationClient->Release();
2465
2466 pThis->m_pNotificationClient = NULL;
2467 }
2468#endif
2469
2470 if (pThis->pDrvIns)
2471 CoUninitialize();
2472
2473 int rc2 = RTCritSectDelete(&pThis->CritSect);
2474 AssertRC(rc2);
2475
2476 LogFlowFuncLeave();
2477}
2478
2479/**
2480 * @callback_method_impl{FNPDMDRVCONSTRUCT,
2481 * Construct a DirectSound Audio driver instance.}
2482 */
2483static DECLCALLBACK(int) drvHostDSoundConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
2484{
2485 RT_NOREF(fFlags);
2486 PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND);
2487
2488 LogRel(("Audio: Initializing DirectSound audio driver\n"));
2489
2490 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
2491
2492 HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
2493 if (FAILED(hr))
2494 {
2495 DSLOGREL(("DSound: CoInitializeEx failed with %Rhrc\n", hr));
2496 return VERR_NOT_SUPPORTED;
2497 }
2498
2499 /*
2500 * Init basic data members and interfaces.
2501 */
2502 pThis->pDrvIns = pDrvIns;
2503 /* IBase */
2504 pDrvIns->IBase.pfnQueryInterface = drvHostDSoundQueryInterface;
2505 /* IHostAudio */
2506 PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostDSound);
2507 pThis->IHostAudio.pfnStreamGetPending = drvHostDSoundStreamGetPending;
2508
2509#ifdef VBOX_WITH_AUDIO_CALLBACKS
2510 /* This backend supports host audio callbacks. */
2511 pThis->IHostAudio.pfnSetCallback = drvHostDSoundSetCallback;
2512 pThis->pfnCallback = NULL;
2513#endif
2514
2515 /*
2516 * Init the static parts.
2517 */
2518 RTListInit(&pThis->lstDevInput);
2519 RTListInit(&pThis->lstDevOutput);
2520
2521 pThis->fEnabledIn = false;
2522 pThis->fEnabledOut = false;
2523
2524 int rc = VINF_SUCCESS;
2525
2526#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
2527 bool fUseNotificationClient = false;
2528
2529 char szOSVersion[32];
2530 rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szOSVersion, sizeof(szOSVersion));
2531 if (RT_SUCCESS(rc))
2532 {
2533 /* IMMNotificationClient is available starting at Windows Vista. */
2534 if (RTStrVersionCompare(szOSVersion, "6.0") >= 0)
2535 fUseNotificationClient = true;
2536 }
2537
2538 if (fUseNotificationClient)
2539 {
2540 try
2541 {
2542 pThis->m_pNotificationClient = new VBoxMMNotificationClient();
2543
2544 HRESULT hr = pThis->m_pNotificationClient->Initialize();
2545 if (FAILED(hr))
2546 rc = VERR_AUDIO_BACKEND_INIT_FAILED;
2547 }
2548 catch (std::bad_alloc &ex)
2549 {
2550 NOREF(ex);
2551 rc = VERR_NO_MEMORY;
2552 }
2553 }
2554
2555 LogRel2(("DSound: Notification client is %s\n", fUseNotificationClient ? "enabled" : "disabled"));
2556#endif
2557
2558 if (RT_SUCCESS(rc))
2559 {
2560 /*
2561 * Initialize configuration values.
2562 */
2563 rc = dsoundConfigInit(pThis, pCfg);
2564 if (RT_SUCCESS(rc))
2565 rc = RTCritSectInit(&pThis->CritSect);
2566 }
2567
2568 return rc;
2569}
2570
2571
2572/**
2573 * PDM driver registration.
2574 */
2575const PDMDRVREG g_DrvHostDSound =
2576{
2577 /* u32Version */
2578 PDM_DRVREG_VERSION,
2579 /* szName */
2580 "DSoundAudio",
2581 /* szRCMod */
2582 "",
2583 /* szR0Mod */
2584 "",
2585 /* pszDescription */
2586 "DirectSound Audio host driver",
2587 /* fFlags */
2588 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
2589 /* fClass. */
2590 PDM_DRVREG_CLASS_AUDIO,
2591 /* cMaxInstances */
2592 ~0U,
2593 /* cbInstance */
2594 sizeof(DRVHOSTDSOUND),
2595 /* pfnConstruct */
2596 drvHostDSoundConstruct,
2597 /* pfnDestruct */
2598 drvHostDSoundDestruct,
2599 /* pfnRelocate */
2600 NULL,
2601 /* pfnIOCtl */
2602 NULL,
2603 /* pfnPowerOn */
2604 NULL,
2605 /* pfnReset */
2606 NULL,
2607 /* pfnSuspend */
2608 NULL,
2609 /* pfnResume */
2610 NULL,
2611 /* pfnAttach */
2612 NULL,
2613 /* pfnDetach */
2614 NULL,
2615 /* pfnPowerOff */
2616 NULL,
2617 /* pfnSoftReset */
2618 NULL,
2619 /* u32EndVersion */
2620 PDM_DRVREG_VERSION
2621};
2622
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