2 * OpenAL cross platform audio library
3 * Copyright (C) 1999-2007 by authors.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 * Or go to http://www.gnu.org/copyleft/lgpl.html
25 #define WIN32_LEAN_AND_MEAN
30 #ifndef _WAVEFORMATEXTENSIBLE_
49 #include "althrd_setname.h"
51 #include "core/device.h"
52 #include "core/helpers.h"
53 #include "core/logging.h"
55 #include "ringbuffer.h"
58 /* MinGW-w64 needs this for some unknown reason now. */
59 using LPCWAVEFORMATEX
= const WAVEFORMATEX
*;
63 #ifndef DSSPEAKER_5POINT1
64 # define DSSPEAKER_5POINT1 0x00000006
66 #ifndef DSSPEAKER_5POINT1_BACK
67 # define DSSPEAKER_5POINT1_BACK 0x00000006
69 #ifndef DSSPEAKER_7POINT1
70 # define DSSPEAKER_7POINT1 0x00000007
72 #ifndef DSSPEAKER_7POINT1_SURROUND
73 # define DSSPEAKER_7POINT1_SURROUND 0x00000008
75 #ifndef DSSPEAKER_5POINT1_SURROUND
76 # define DSSPEAKER_5POINT1_SURROUND 0x00000009
80 /* Some headers seem to define these as macros for __uuidof, which is annoying
81 * since some headers don't declare them at all. Hopefully the ifdef is enough
82 * to tell if they need to be declared.
84 #ifndef KSDATAFORMAT_SUBTYPE_PCM
85 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM
, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
87 #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
88 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
93 #define DEVNAME_HEAD "OpenAL Soft on "
98 HRESULT (WINAPI
*pDirectSoundCreate
)(const GUID
*pcGuidDevice
, IDirectSound
**ppDS
, IUnknown
*pUnkOuter
);
99 HRESULT (WINAPI
*pDirectSoundEnumerateW
)(LPDSENUMCALLBACKW pDSEnumCallback
, void *pContext
);
100 HRESULT (WINAPI
*pDirectSoundCaptureCreate
)(const GUID
*pcGuidDevice
, IDirectSoundCapture
**ppDSC
, IUnknown
*pUnkOuter
);
101 HRESULT (WINAPI
*pDirectSoundCaptureEnumerateW
)(LPDSENUMCALLBACKW pDSEnumCallback
, void *pContext
);
103 #ifndef IN_IDE_PARSER
104 #define DirectSoundCreate pDirectSoundCreate
105 #define DirectSoundEnumerateW pDirectSoundEnumerateW
106 #define DirectSoundCaptureCreate pDirectSoundCaptureCreate
107 #define DirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW
112 #define MONO SPEAKER_FRONT_CENTER
113 #define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)
114 #define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
115 #define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
116 #define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
117 #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
118 #define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
119 #define X7DOT1DOT4 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT|SPEAKER_TOP_FRONT_LEFT|SPEAKER_TOP_FRONT_RIGHT|SPEAKER_TOP_BACK_LEFT|SPEAKER_TOP_BACK_RIGHT)
121 #define MAX_UPDATES 128
127 template<typename T0
, typename T1
>
128 DevMap(T0
&& name_
, T1
&& guid_
)
129 : name
{std::forward
<T0
>(name_
)}, guid
{std::forward
<T1
>(guid_
)}
133 std::vector
<DevMap
> PlaybackDevices
;
134 std::vector
<DevMap
> CaptureDevices
;
136 bool checkName(const al::span
<DevMap
> list
, const std::string
&name
)
138 auto match_name
= [&name
](const DevMap
&entry
) -> bool
139 { return entry
.name
== name
; };
140 return std::find_if(list
.cbegin(), list
.cend(), match_name
) != list
.cend();
143 BOOL CALLBACK
DSoundEnumDevices(GUID
*guid
, const WCHAR
*desc
, const WCHAR
*, void *data
) noexcept
148 auto& devices
= *static_cast<std::vector
<DevMap
>*>(data
);
149 const std::string basename
{DEVNAME_HEAD
+ wstr_to_utf8(desc
)};
152 std::string newname
{basename
};
153 while(checkName(devices
, newname
))
157 newname
+= std::to_string(++count
);
159 devices
.emplace_back(std::move(newname
), *guid
);
160 const DevMap
&newentry
= devices
.back();
162 OLECHAR
*guidstr
{nullptr};
163 HRESULT hr
{StringFromCLSID(*guid
, &guidstr
)};
166 TRACE("Got device \"%s\", GUID \"%ls\"\n", newentry
.name
.c_str(), guidstr
);
167 CoTaskMemFree(guidstr
);
174 struct DSoundPlayback final
: public BackendBase
{
175 DSoundPlayback(DeviceBase
*device
) noexcept
: BackendBase
{device
} { }
176 ~DSoundPlayback() override
;
180 void open(std::string_view name
) override
;
181 bool reset() override
;
182 void start() override
;
183 void stop() override
;
185 ComPtr
<IDirectSound
> mDS
;
186 ComPtr
<IDirectSoundBuffer
> mPrimaryBuffer
;
187 ComPtr
<IDirectSoundBuffer
> mBuffer
;
188 ComPtr
<IDirectSoundNotify
> mNotifies
;
189 HANDLE mNotifyEvent
{nullptr};
191 std::atomic
<bool> mKillNow
{true};
195 DSoundPlayback::~DSoundPlayback()
199 mPrimaryBuffer
= nullptr;
203 CloseHandle(mNotifyEvent
);
204 mNotifyEvent
= nullptr;
208 FORCE_ALIGN
int DSoundPlayback::mixerProc()
211 althrd_setname(GetMixerThreadName());
214 DSBCaps
.dwSize
= sizeof(DSBCaps
);
215 HRESULT err
{mBuffer
->GetCaps(&DSBCaps
)};
218 ERR("Failed to get buffer caps: 0x%lx\n", err
);
219 mDevice
->handleDisconnect("Failure retrieving playback buffer info: 0x%lx", err
);
223 const size_t FrameStep
{mDevice
->channelsFromFmt()};
224 uint FrameSize
{mDevice
->frameSizeFromFmt()};
225 DWORD FragSize
{mDevice
->UpdateSize
* FrameSize
};
228 DWORD LastCursor
{0u};
229 mBuffer
->GetCurrentPosition(&LastCursor
, nullptr);
230 while(!mKillNow
.load(std::memory_order_acquire
)
231 && mDevice
->Connected
.load(std::memory_order_acquire
))
233 // Get current play cursor
235 mBuffer
->GetCurrentPosition(&PlayCursor
, nullptr);
236 DWORD avail
= (PlayCursor
-LastCursor
+DSBCaps
.dwBufferBytes
) % DSBCaps
.dwBufferBytes
;
242 err
= mBuffer
->Play(0, 0, DSBPLAY_LOOPING
);
245 ERR("Failed to play buffer: 0x%lx\n", err
);
246 mDevice
->handleDisconnect("Failure starting playback: 0x%lx", err
);
252 avail
= WaitForSingleObjectEx(mNotifyEvent
, 2000, FALSE
);
253 if(avail
!= WAIT_OBJECT_0
)
254 ERR("WaitForSingleObjectEx error: 0x%lx\n", avail
);
257 avail
-= avail
%FragSize
;
259 // Lock output buffer
260 void *WritePtr1
, *WritePtr2
;
261 DWORD WriteCnt1
{0u}, WriteCnt2
{0u};
262 err
= mBuffer
->Lock(LastCursor
, avail
, &WritePtr1
, &WriteCnt1
, &WritePtr2
, &WriteCnt2
, 0);
264 // If the buffer is lost, restore it and lock
265 if(err
== DSERR_BUFFERLOST
)
267 WARN("Buffer lost, restoring...\n");
268 err
= mBuffer
->Restore();
273 err
= mBuffer
->Lock(0, DSBCaps
.dwBufferBytes
, &WritePtr1
, &WriteCnt1
,
274 &WritePtr2
, &WriteCnt2
, 0);
280 mDevice
->renderSamples(WritePtr1
, WriteCnt1
/FrameSize
, FrameStep
);
282 mDevice
->renderSamples(WritePtr2
, WriteCnt2
/FrameSize
, FrameStep
);
284 mBuffer
->Unlock(WritePtr1
, WriteCnt1
, WritePtr2
, WriteCnt2
);
288 ERR("Buffer lock error: %#lx\n", err
);
289 mDevice
->handleDisconnect("Failed to lock output buffer: 0x%lx", err
);
293 // Update old write cursor location
294 LastCursor
+= WriteCnt1
+WriteCnt2
;
295 LastCursor
%= DSBCaps
.dwBufferBytes
;
301 void DSoundPlayback::open(std::string_view name
)
304 if(PlaybackDevices
.empty())
306 /* Initialize COM to prevent name truncation */
308 hr
= DirectSoundEnumerateW(DSoundEnumDevices
, &PlaybackDevices
);
310 ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr
);
313 const GUID
*guid
{nullptr};
314 if(name
.empty() && !PlaybackDevices
.empty())
316 name
= PlaybackDevices
[0].name
;
317 guid
= &PlaybackDevices
[0].guid
;
321 auto iter
= std::find_if(PlaybackDevices
.cbegin(), PlaybackDevices
.cend(),
322 [name
](const DevMap
&entry
) -> bool { return entry
.name
== name
; });
323 if(iter
== PlaybackDevices
.cend())
326 hr
= CLSIDFromString(utf8_to_wstr(name
).c_str(), &id
);
328 iter
= std::find_if(PlaybackDevices
.cbegin(), PlaybackDevices
.cend(),
329 [&id
](const DevMap
&entry
) -> bool { return entry
.guid
== id
; });
330 if(iter
== PlaybackDevices
.cend())
331 throw al::backend_exception
{al::backend_error::NoDevice
,
332 "Device name \"%.*s\" not found", al::sizei(name
), name
.data()};
340 mNotifyEvent
= CreateEventW(nullptr, FALSE
, FALSE
, nullptr);
341 if(!mNotifyEvent
) hr
= E_FAIL
;
344 //DirectSound Init code
345 ComPtr
<IDirectSound
> ds
;
347 hr
= DirectSoundCreate(guid
, al::out_ptr(ds
), nullptr);
349 hr
= ds
->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY
);
351 throw al::backend_exception
{al::backend_error::DeviceError
, "Device init failed: 0x%08lx",
356 mPrimaryBuffer
= nullptr;
359 mDevice
->DeviceName
= name
;
362 bool DSoundPlayback::reset()
366 mPrimaryBuffer
= nullptr;
368 switch(mDevice
->FmtType
)
371 mDevice
->FmtType
= DevFmtUByte
;
374 if(mDevice
->Flags
.test(SampleTypeRequest
))
378 mDevice
->FmtType
= DevFmtShort
;
381 mDevice
->FmtType
= DevFmtInt
;
389 WAVEFORMATEXTENSIBLE OutputType
{};
391 HRESULT hr
{mDS
->GetSpeakerConfig(&speakers
)};
393 throw al::backend_exception
{al::backend_error::DeviceError
,
394 "Failed to get speaker config: 0x%08lx", hr
};
396 speakers
= DSSPEAKER_CONFIG(speakers
);
397 if(!mDevice
->Flags
.test(ChannelsRequest
))
399 if(speakers
== DSSPEAKER_MONO
)
400 mDevice
->FmtChans
= DevFmtMono
;
401 else if(speakers
== DSSPEAKER_STEREO
|| speakers
== DSSPEAKER_HEADPHONE
)
402 mDevice
->FmtChans
= DevFmtStereo
;
403 else if(speakers
== DSSPEAKER_QUAD
)
404 mDevice
->FmtChans
= DevFmtQuad
;
405 else if(speakers
== DSSPEAKER_5POINT1_SURROUND
|| speakers
== DSSPEAKER_5POINT1_BACK
)
406 mDevice
->FmtChans
= DevFmtX51
;
407 else if(speakers
== DSSPEAKER_7POINT1
|| speakers
== DSSPEAKER_7POINT1_SURROUND
)
408 mDevice
->FmtChans
= DevFmtX71
;
410 ERR("Unknown system speaker config: 0x%lx\n", speakers
);
412 mDevice
->Flags
.set(DirectEar
, (speakers
== DSSPEAKER_HEADPHONE
));
413 const bool isRear51
{speakers
== DSSPEAKER_5POINT1_BACK
};
415 switch(mDevice
->FmtChans
)
417 case DevFmtMono
: OutputType
.dwChannelMask
= MONO
; break;
418 case DevFmtAmbi3D
: mDevice
->FmtChans
= DevFmtStereo
;
420 case DevFmtStereo
: OutputType
.dwChannelMask
= STEREO
; break;
421 case DevFmtQuad
: OutputType
.dwChannelMask
= QUAD
; break;
422 case DevFmtX51
: OutputType
.dwChannelMask
= isRear51
? X5DOT1REAR
: X5DOT1
; break;
423 case DevFmtX61
: OutputType
.dwChannelMask
= X6DOT1
; break;
424 case DevFmtX71
: OutputType
.dwChannelMask
= X7DOT1
; break;
425 case DevFmtX7144
: mDevice
->FmtChans
= DevFmtX714
;
427 case DevFmtX714
: OutputType
.dwChannelMask
= X7DOT1DOT4
; break;
428 case DevFmtX3D71
: OutputType
.dwChannelMask
= X7DOT1
; break;
433 OutputType
.Format
.wFormatTag
= WAVE_FORMAT_PCM
;
434 OutputType
.Format
.nChannels
= static_cast<WORD
>(mDevice
->channelsFromFmt());
435 OutputType
.Format
.wBitsPerSample
= static_cast<WORD
>(mDevice
->bytesFromFmt() * 8);
436 OutputType
.Format
.nBlockAlign
= static_cast<WORD
>(OutputType
.Format
.nChannels
*
437 OutputType
.Format
.wBitsPerSample
/ 8);
438 OutputType
.Format
.nSamplesPerSec
= mDevice
->Frequency
;
439 OutputType
.Format
.nAvgBytesPerSec
= OutputType
.Format
.nSamplesPerSec
*
440 OutputType
.Format
.nBlockAlign
;
441 OutputType
.Format
.cbSize
= 0;
443 if(OutputType
.Format
.nChannels
> 2 || mDevice
->FmtType
== DevFmtFloat
)
445 OutputType
.Format
.wFormatTag
= WAVE_FORMAT_EXTENSIBLE
;
446 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
447 OutputType
.Samples
.wValidBitsPerSample
= OutputType
.Format
.wBitsPerSample
;
448 OutputType
.Format
.cbSize
= sizeof(WAVEFORMATEXTENSIBLE
) - sizeof(WAVEFORMATEX
);
449 if(mDevice
->FmtType
== DevFmtFloat
)
450 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
452 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
454 mPrimaryBuffer
= nullptr;
458 if(SUCCEEDED(hr
) && !mPrimaryBuffer
)
460 DSBUFFERDESC DSBDescription
{};
461 DSBDescription
.dwSize
= sizeof(DSBDescription
);
462 DSBDescription
.dwFlags
= DSBCAPS_PRIMARYBUFFER
;
463 hr
= mDS
->CreateSoundBuffer(&DSBDescription
, al::out_ptr(mPrimaryBuffer
), nullptr);
466 hr
= mPrimaryBuffer
->SetFormat(&OutputType
.Format
);
472 uint num_updates
{mDevice
->BufferSize
/ mDevice
->UpdateSize
};
473 if(num_updates
> MAX_UPDATES
)
474 num_updates
= MAX_UPDATES
;
475 mDevice
->BufferSize
= mDevice
->UpdateSize
* num_updates
;
477 DSBUFFERDESC DSBDescription
{};
478 DSBDescription
.dwSize
= sizeof(DSBDescription
);
479 DSBDescription
.dwFlags
= DSBCAPS_CTRLPOSITIONNOTIFY
| DSBCAPS_GETCURRENTPOSITION2
480 | DSBCAPS_GLOBALFOCUS
;
481 DSBDescription
.dwBufferBytes
= mDevice
->BufferSize
* OutputType
.Format
.nBlockAlign
;
482 DSBDescription
.lpwfxFormat
= &OutputType
.Format
;
484 hr
= mDS
->CreateSoundBuffer(&DSBDescription
, al::out_ptr(mBuffer
), nullptr);
485 if(SUCCEEDED(hr
) || mDevice
->FmtType
!= DevFmtFloat
)
487 mDevice
->FmtType
= DevFmtShort
;
492 hr
= mBuffer
->QueryInterface(IID_IDirectSoundNotify
, al::out_ptr(mNotifies
));
495 uint num_updates
{mDevice
->BufferSize
/ mDevice
->UpdateSize
};
496 assert(num_updates
<= MAX_UPDATES
);
498 std::array
<DSBPOSITIONNOTIFY
,MAX_UPDATES
> nots
{};
499 for(uint i
{0};i
< num_updates
;++i
)
501 nots
[i
].dwOffset
= i
* mDevice
->UpdateSize
* OutputType
.Format
.nBlockAlign
;
502 nots
[i
].hEventNotify
= mNotifyEvent
;
504 if(mNotifies
->SetNotificationPositions(num_updates
, nots
.data()) != DS_OK
)
513 mPrimaryBuffer
= nullptr;
517 ResetEvent(mNotifyEvent
);
518 setDefaultWFXChannelOrder();
523 void DSoundPlayback::start()
526 mKillNow
.store(false, std::memory_order_release
);
527 mThread
= std::thread
{std::mem_fn(&DSoundPlayback::mixerProc
), this};
529 catch(std::exception
& e
) {
530 throw al::backend_exception
{al::backend_error::DeviceError
,
531 "Failed to start mixing thread: %s", e
.what()};
535 void DSoundPlayback::stop()
537 if(mKillNow
.exchange(true, std::memory_order_acq_rel
) || !mThread
.joinable())
545 struct DSoundCapture final
: public BackendBase
{
546 DSoundCapture(DeviceBase
*device
) noexcept
: BackendBase
{device
} { }
547 ~DSoundCapture() override
;
549 void open(std::string_view name
) override
;
550 void start() override
;
551 void stop() override
;
552 void captureSamples(std::byte
*buffer
, uint samples
) override
;
553 uint
availableSamples() override
;
555 ComPtr
<IDirectSoundCapture
> mDSC
;
556 ComPtr
<IDirectSoundCaptureBuffer
> mDSCbuffer
;
557 DWORD mBufferBytes
{0u};
563 DSoundCapture::~DSoundCapture()
568 mDSCbuffer
= nullptr;
574 void DSoundCapture::open(std::string_view name
)
577 if(CaptureDevices
.empty())
579 /* Initialize COM to prevent name truncation */
581 hr
= DirectSoundCaptureEnumerateW(DSoundEnumDevices
, &CaptureDevices
);
583 ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr
);
586 const GUID
*guid
{nullptr};
587 if(name
.empty() && !CaptureDevices
.empty())
589 name
= CaptureDevices
[0].name
;
590 guid
= &CaptureDevices
[0].guid
;
594 auto iter
= std::find_if(CaptureDevices
.cbegin(), CaptureDevices
.cend(),
595 [name
](const DevMap
&entry
) -> bool { return entry
.name
== name
; });
596 if(iter
== CaptureDevices
.cend())
599 hr
= CLSIDFromString(utf8_to_wstr(name
).c_str(), &id
);
601 iter
= std::find_if(CaptureDevices
.cbegin(), CaptureDevices
.cend(),
602 [&id
](const DevMap
&entry
) -> bool { return entry
.guid
== id
; });
603 if(iter
== CaptureDevices
.cend())
604 throw al::backend_exception
{al::backend_error::NoDevice
,
605 "Device name \"%.*s\" not found", al::sizei(name
), name
.data()};
610 switch(mDevice
->FmtType
)
615 WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice
->FmtType
));
616 throw al::backend_exception
{al::backend_error::DeviceError
,
617 "%s capture samples not supported", DevFmtTypeString(mDevice
->FmtType
)};
626 WAVEFORMATEXTENSIBLE InputType
{};
627 switch(mDevice
->FmtChans
)
629 case DevFmtMono
: InputType
.dwChannelMask
= MONO
; break;
630 case DevFmtStereo
: InputType
.dwChannelMask
= STEREO
; break;
631 case DevFmtQuad
: InputType
.dwChannelMask
= QUAD
; break;
632 case DevFmtX51
: InputType
.dwChannelMask
= X5DOT1
; break;
633 case DevFmtX61
: InputType
.dwChannelMask
= X6DOT1
; break;
634 case DevFmtX71
: InputType
.dwChannelMask
= X7DOT1
; break;
635 case DevFmtX714
: InputType
.dwChannelMask
= X7DOT1DOT4
; break;
639 WARN("%s capture not supported\n", DevFmtChannelsString(mDevice
->FmtChans
));
640 throw al::backend_exception
{al::backend_error::DeviceError
, "%s capture not supported",
641 DevFmtChannelsString(mDevice
->FmtChans
)};
644 InputType
.Format
.wFormatTag
= WAVE_FORMAT_PCM
;
645 InputType
.Format
.nChannels
= static_cast<WORD
>(mDevice
->channelsFromFmt());
646 InputType
.Format
.wBitsPerSample
= static_cast<WORD
>(mDevice
->bytesFromFmt() * 8);
647 InputType
.Format
.nBlockAlign
= static_cast<WORD
>(InputType
.Format
.nChannels
*
648 InputType
.Format
.wBitsPerSample
/ 8);
649 InputType
.Format
.nSamplesPerSec
= mDevice
->Frequency
;
650 InputType
.Format
.nAvgBytesPerSec
= InputType
.Format
.nSamplesPerSec
*
651 InputType
.Format
.nBlockAlign
;
652 InputType
.Format
.cbSize
= 0;
653 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
654 InputType
.Samples
.wValidBitsPerSample
= InputType
.Format
.wBitsPerSample
;
655 if(mDevice
->FmtType
== DevFmtFloat
)
656 InputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
658 InputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
660 if(InputType
.Format
.nChannels
> 2 || mDevice
->FmtType
== DevFmtFloat
)
662 InputType
.Format
.wFormatTag
= WAVE_FORMAT_EXTENSIBLE
;
663 InputType
.Format
.cbSize
= sizeof(WAVEFORMATEXTENSIBLE
) - sizeof(WAVEFORMATEX
);
666 const uint samples
{std::max(mDevice
->BufferSize
, mDevice
->Frequency
/10u)};
668 DSCBUFFERDESC DSCBDescription
{};
669 DSCBDescription
.dwSize
= sizeof(DSCBDescription
);
670 DSCBDescription
.dwFlags
= 0;
671 DSCBDescription
.dwBufferBytes
= samples
* InputType
.Format
.nBlockAlign
;
672 DSCBDescription
.lpwfxFormat
= &InputType
.Format
;
674 //DirectSoundCapture Init code
675 hr
= DirectSoundCaptureCreate(guid
, al::out_ptr(mDSC
), nullptr);
677 mDSC
->CreateCaptureBuffer(&DSCBDescription
, al::out_ptr(mDSCbuffer
), nullptr);
679 mRing
= RingBuffer::Create(mDevice
->BufferSize
, InputType
.Format
.nBlockAlign
, false);
684 mDSCbuffer
= nullptr;
687 throw al::backend_exception
{al::backend_error::DeviceError
, "Device init failed: 0x%08lx",
691 mBufferBytes
= DSCBDescription
.dwBufferBytes
;
692 setDefaultWFXChannelOrder();
694 mDevice
->DeviceName
= name
;
697 void DSoundCapture::start()
699 const HRESULT hr
{mDSCbuffer
->Start(DSCBSTART_LOOPING
)};
701 throw al::backend_exception
{al::backend_error::DeviceError
,
702 "Failure starting capture: 0x%lx", hr
};
705 void DSoundCapture::stop()
707 HRESULT hr
{mDSCbuffer
->Stop()};
710 ERR("stop failed: 0x%08lx\n", hr
);
711 mDevice
->handleDisconnect("Failure stopping capture: 0x%lx", hr
);
715 void DSoundCapture::captureSamples(std::byte
*buffer
, uint samples
)
716 { std::ignore
= mRing
->read(buffer
, samples
); }
718 uint
DSoundCapture::availableSamples()
720 if(!mDevice
->Connected
.load(std::memory_order_acquire
))
721 return static_cast<uint
>(mRing
->readSpace());
723 const uint FrameSize
{mDevice
->frameSizeFromFmt()};
724 const DWORD BufferBytes
{mBufferBytes
};
725 const DWORD LastCursor
{mCursor
};
728 void *ReadPtr1
{}, *ReadPtr2
{};
729 DWORD ReadCnt1
{}, ReadCnt2
{};
730 HRESULT hr
{mDSCbuffer
->GetCurrentPosition(nullptr, &ReadCursor
)};
733 const DWORD NumBytes
{(BufferBytes
+ReadCursor
-LastCursor
) % BufferBytes
};
734 if(!NumBytes
) return static_cast<uint
>(mRing
->readSpace());
735 hr
= mDSCbuffer
->Lock(LastCursor
, NumBytes
, &ReadPtr1
, &ReadCnt1
, &ReadPtr2
, &ReadCnt2
, 0);
739 std::ignore
= mRing
->write(ReadPtr1
, ReadCnt1
/FrameSize
);
740 if(ReadPtr2
!= nullptr && ReadCnt2
> 0)
741 std::ignore
= mRing
->write(ReadPtr2
, ReadCnt2
/FrameSize
);
742 hr
= mDSCbuffer
->Unlock(ReadPtr1
, ReadCnt1
, ReadPtr2
, ReadCnt2
);
743 mCursor
= ReadCursor
;
748 ERR("update failed: 0x%08lx\n", hr
);
749 mDevice
->handleDisconnect("Failure retrieving capture data: 0x%lx", hr
);
752 return static_cast<uint
>(mRing
->readSpace());
758 BackendFactory
&DSoundBackendFactory::getFactory()
760 static DSoundBackendFactory factory
{};
764 bool DSoundBackendFactory::init()
769 ds_handle
= LoadLib("dsound.dll");
772 ERR("Failed to load dsound.dll\n");
776 #define LOAD_FUNC(f) do { \
777 p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(ds_handle, #f)); \
780 CloseLib(ds_handle); \
781 ds_handle = nullptr; \
785 LOAD_FUNC(DirectSoundCreate
);
786 LOAD_FUNC(DirectSoundEnumerateW
);
787 LOAD_FUNC(DirectSoundCaptureCreate
);
788 LOAD_FUNC(DirectSoundCaptureEnumerateW
);
795 bool DSoundBackendFactory::querySupport(BackendType type
)
796 { return (type
== BackendType::Playback
|| type
== BackendType::Capture
); }
798 auto DSoundBackendFactory::enumerate(BackendType type
) -> std::vector
<std::string
>
800 std::vector
<std::string
> outnames
;
801 auto add_device
= [&outnames
](const DevMap
&entry
) -> void
802 { outnames
.emplace_back(entry
.name
); };
804 /* Initialize COM to prevent name truncation */
808 case BackendType::Playback
:
809 PlaybackDevices
.clear();
810 if(HRESULT hr
{DirectSoundEnumerateW(DSoundEnumDevices
, &PlaybackDevices
)}; FAILED(hr
))
811 ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr
);
812 outnames
.reserve(PlaybackDevices
.size());
813 std::for_each(PlaybackDevices
.cbegin(), PlaybackDevices
.cend(), add_device
);
816 case BackendType::Capture
:
817 CaptureDevices
.clear();
818 if(HRESULT hr
{DirectSoundCaptureEnumerateW(DSoundEnumDevices
, &CaptureDevices
)};FAILED(hr
))
819 ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr
);
820 outnames
.reserve(CaptureDevices
.size());
821 std::for_each(CaptureDevices
.cbegin(), CaptureDevices
.cend(), add_device
);
828 BackendPtr
DSoundBackendFactory::createBackend(DeviceBase
*device
, BackendType type
)
830 if(type
== BackendType::Playback
)
831 return BackendPtr
{new DSoundPlayback
{device
}};
832 if(type
== BackendType::Capture
)
833 return BackendPtr
{new DSoundCapture
{device
}};