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
34 #ifndef _WAVEFORMATEXTENSIBLE_
47 #include "alnumeric.h"
49 #include "core/device.h"
50 #include "core/helpers.h"
51 #include "core/logging.h"
53 #include "ringbuffer.h"
57 /* MinGW-w64 needs this for some unknown reason now. */
58 using LPCWAVEFORMATEX = const WAVEFORMATEX*;
62 #ifndef DSSPEAKER_5POINT1
63 # define DSSPEAKER_5POINT1 0x00000006
65 #ifndef DSSPEAKER_5POINT1_BACK
66 # define DSSPEAKER_5POINT1_BACK 0x00000006
68 #ifndef DSSPEAKER_7POINT1
69 # define DSSPEAKER_7POINT1 0x00000007
71 #ifndef DSSPEAKER_7POINT1_SURROUND
72 # define DSSPEAKER_7POINT1_SURROUND 0x00000008
74 #ifndef DSSPEAKER_5POINT1_SURROUND
75 # define DSSPEAKER_5POINT1_SURROUND 0x00000009
79 /* Some headers seem to define these as macros for __uuidof, which is annoying
80 * since some headers don't declare them at all. Hopefully the ifdef is enough
81 * to tell if they need to be declared.
83 #ifndef KSDATAFORMAT_SUBTYPE_PCM
84 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
86 #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
87 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
92 #define DEVNAME_HEAD "OpenAL Soft on "
97 HRESULT (WINAPI *pDirectSoundCreate)(const GUID *pcGuidDevice, IDirectSound **ppDS, IUnknown *pUnkOuter);
98 HRESULT (WINAPI *pDirectSoundEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext);
99 HRESULT (WINAPI *pDirectSoundCaptureCreate)(const GUID *pcGuidDevice, IDirectSoundCapture **ppDSC, IUnknown *pUnkOuter);
100 HRESULT (WINAPI *pDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext);
102 #ifndef IN_IDE_PARSER
103 #define DirectSoundCreate pDirectSoundCreate
104 #define DirectSoundEnumerateW pDirectSoundEnumerateW
105 #define DirectSoundCaptureCreate pDirectSoundCaptureCreate
106 #define DirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW
111 #define MONO SPEAKER_FRONT_CENTER
112 #define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)
113 #define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
114 #define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
115 #define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
116 #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
117 #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)
118 #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)
120 #define MAX_UPDATES 128
126 template<typename T0, typename T1>
127 DevMap(T0&& name_, T1&& guid_)
128 : name{std::forward<T0>(name_)}, guid{std::forward<T1>(guid_)}
132 al::vector<DevMap> PlaybackDevices;
133 al::vector<DevMap> CaptureDevices;
135 bool checkName(const al::vector<DevMap> &list, const std::string &name)
137 auto match_name = [&name](const DevMap &entry) -> bool
138 { return entry.name == name; };
139 return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
142 BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, void *data) noexcept
147 auto& devices = *static_cast<al::vector<DevMap>*>(data);
148 const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)};
151 std::string newname{basename};
152 while(checkName(devices, newname))
156 newname += std::to_string(++count);
158 devices.emplace_back(std::move(newname), *guid);
159 const DevMap &newentry = devices.back();
161 OLECHAR *guidstr{nullptr};
162 HRESULT hr{StringFromCLSID(*guid, &guidstr)};
165 TRACE("Got device \"%s\", GUID \"%ls\"\n", newentry.name.c_str(), guidstr);
166 CoTaskMemFree(guidstr);
173 struct DSoundPlayback final : public BackendBase {
174 DSoundPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
175 ~DSoundPlayback() override;
179 void open(const char *name) override;
180 bool reset() override;
181 void start() override;
182 void stop() override;
184 ComPtr<IDirectSound> mDS;
185 ComPtr<IDirectSoundBuffer> mPrimaryBuffer;
186 ComPtr<IDirectSoundBuffer> mBuffer;
187 ComPtr<IDirectSoundNotify> mNotifies;
188 HANDLE mNotifyEvent{nullptr};
190 std::atomic<bool> mKillNow{true};
193 DEF_NEWDEL(DSoundPlayback)
196 DSoundPlayback::~DSoundPlayback()
200 mPrimaryBuffer = nullptr;
204 CloseHandle(mNotifyEvent);
205 mNotifyEvent = nullptr;
209 FORCE_ALIGN int DSoundPlayback::mixerProc()
212 althrd_setname(MIXER_THREAD_NAME);
215 DSBCaps.dwSize = sizeof(DSBCaps);
216 HRESULT err{mBuffer->GetCaps(&DSBCaps)};
219 ERR("Failed to get buffer caps: 0x%lx\n", err);
220 mDevice->handleDisconnect("Failure retrieving playback buffer info: 0x%lx", err);
224 const size_t FrameStep{mDevice->channelsFromFmt()};
225 uint FrameSize{mDevice->frameSizeFromFmt()};
226 DWORD FragSize{mDevice->UpdateSize * FrameSize};
229 DWORD LastCursor{0u};
230 mBuffer->GetCurrentPosition(&LastCursor, nullptr);
231 while(!mKillNow.load(std::memory_order_acquire)
232 && mDevice->Connected.load(std::memory_order_acquire))
234 // Get current play cursor
236 mBuffer->GetCurrentPosition(&PlayCursor, nullptr);
237 DWORD avail = (PlayCursor-LastCursor+DSBCaps.dwBufferBytes) % DSBCaps.dwBufferBytes;
243 err = mBuffer->Play(0, 0, DSBPLAY_LOOPING);
246 ERR("Failed to play buffer: 0x%lx\n", err);
247 mDevice->handleDisconnect("Failure starting playback: 0x%lx", err);
253 avail = WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE);
254 if(avail != WAIT_OBJECT_0)
255 ERR("WaitForSingleObjectEx error: 0x%lx\n", avail);
258 avail -= avail%FragSize;
260 // Lock output buffer
261 void *WritePtr1, *WritePtr2;
262 DWORD WriteCnt1{0u}, WriteCnt2{0u};
263 err = mBuffer->Lock(LastCursor, avail, &WritePtr1, &WriteCnt1, &WritePtr2, &WriteCnt2, 0);
265 // If the buffer is lost, restore it and lock
266 if(err == DSERR_BUFFERLOST)
268 WARN("Buffer lost, restoring...\n");
269 err = mBuffer->Restore();
274 err = mBuffer->Lock(0, DSBCaps.dwBufferBytes, &WritePtr1, &WriteCnt1,
275 &WritePtr2, &WriteCnt2, 0);
281 mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep);
283 mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep);
285 mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2);
289 ERR("Buffer lock error: %#lx\n", err);
290 mDevice->handleDisconnect("Failed to lock output buffer: 0x%lx", err);
294 // Update old write cursor location
295 LastCursor += WriteCnt1+WriteCnt2;
296 LastCursor %= DSBCaps.dwBufferBytes;
302 void DSoundPlayback::open(const char *name)
305 if(PlaybackDevices.empty())
307 /* Initialize COM to prevent name truncation */
308 HRESULT hrcom{CoInitialize(nullptr)};
309 hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
311 ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
316 const GUID *guid{nullptr};
317 if(!name && !PlaybackDevices.empty())
319 name = PlaybackDevices[0].name.c_str();
320 guid = &PlaybackDevices[0].guid;
324 auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
325 [name](const DevMap &entry) -> bool { return entry.name == name; });
326 if(iter == PlaybackDevices.cend())
329 hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id);
331 iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
332 [&id](const DevMap &entry) -> bool { return entry.guid == id; });
333 if(iter == PlaybackDevices.cend())
334 throw al::backend_exception{al::backend_error::NoDevice,
335 "Device name \"%s\" not found", name};
343 mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
344 if(!mNotifyEvent) hr = E_FAIL;
347 //DirectSound Init code
348 ComPtr<IDirectSound> ds;
350 hr = DirectSoundCreate(guid, ds.getPtr(), nullptr);
352 hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
354 throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
359 mPrimaryBuffer = nullptr;
362 mDevice->DeviceName = name;
365 bool DSoundPlayback::reset()
369 mPrimaryBuffer = nullptr;
371 switch(mDevice->FmtType)
374 mDevice->FmtType = DevFmtUByte;
377 if(mDevice->Flags.test(SampleTypeRequest))
381 mDevice->FmtType = DevFmtShort;
384 mDevice->FmtType = DevFmtInt;
392 WAVEFORMATEXTENSIBLE OutputType{};
394 HRESULT hr{mDS->GetSpeakerConfig(&speakers)};
396 throw al::backend_exception{al::backend_error::DeviceError,
397 "Failed to get speaker config: 0x%08lx", hr};
399 speakers = DSSPEAKER_CONFIG(speakers);
400 if(!mDevice->Flags.test(ChannelsRequest))
402 if(speakers == DSSPEAKER_MONO)
403 mDevice->FmtChans = DevFmtMono;
404 else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE)
405 mDevice->FmtChans = DevFmtStereo;
406 else if(speakers == DSSPEAKER_QUAD)
407 mDevice->FmtChans = DevFmtQuad;
408 else if(speakers == DSSPEAKER_5POINT1_SURROUND || speakers == DSSPEAKER_5POINT1_BACK)
409 mDevice->FmtChans = DevFmtX51;
410 else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
411 mDevice->FmtChans = DevFmtX71;
413 ERR("Unknown system speaker config: 0x%lx\n", speakers);
415 mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE));
416 const bool isRear51{speakers == DSSPEAKER_5POINT1_BACK};
418 switch(mDevice->FmtChans)
420 case DevFmtMono: OutputType.dwChannelMask = MONO; break;
421 case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo;
423 case DevFmtStereo: OutputType.dwChannelMask = STEREO; break;
424 case DevFmtQuad: OutputType.dwChannelMask = QUAD; break;
425 case DevFmtX51: OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break;
426 case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
427 case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break;
428 case DevFmtX714: OutputType.dwChannelMask = X7DOT1DOT4; break;
429 case DevFmtX3D71: OutputType.dwChannelMask = X7DOT1; break;
434 OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
435 OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
436 OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
437 OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
438 OutputType.Format.wBitsPerSample / 8);
439 OutputType.Format.nSamplesPerSec = mDevice->Frequency;
440 OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
441 OutputType.Format.nBlockAlign;
442 OutputType.Format.cbSize = 0;
444 if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
446 OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
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, mPrimaryBuffer.getPtr(), nullptr);
466 hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
471 uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
472 if(num_updates > MAX_UPDATES)
473 num_updates = MAX_UPDATES;
474 mDevice->BufferSize = mDevice->UpdateSize * num_updates;
476 DSBUFFERDESC DSBDescription{};
477 DSBDescription.dwSize = sizeof(DSBDescription);
478 DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2
479 | DSBCAPS_GLOBALFOCUS;
480 DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign;
481 DSBDescription.lpwfxFormat = &OutputType.Format;
483 hr = mDS->CreateSoundBuffer(&DSBDescription, mBuffer.getPtr(), nullptr);
484 if(FAILED(hr) && mDevice->FmtType == DevFmtFloat)
486 mDevice->FmtType = DevFmtShort;
494 hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr);
497 mNotifies = ComPtr<IDirectSoundNotify>{static_cast<IDirectSoundNotify*>(ptr)};
499 uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
500 assert(num_updates <= MAX_UPDATES);
502 std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots;
503 for(uint i{0};i < num_updates;++i)
505 nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign;
506 nots[i].hEventNotify = mNotifyEvent;
508 if(mNotifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK)
517 mPrimaryBuffer = nullptr;
521 ResetEvent(mNotifyEvent);
522 setDefaultWFXChannelOrder();
527 void DSoundPlayback::start()
530 mKillNow.store(false, std::memory_order_release);
531 mThread = std::thread{std::mem_fn(&DSoundPlayback::mixerProc), this};
533 catch(std::exception& e) {
534 throw al::backend_exception{al::backend_error::DeviceError,
535 "Failed to start mixing thread: %s", e.what()};
539 void DSoundPlayback::stop()
541 if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
549 struct DSoundCapture final : public BackendBase {
550 DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { }
551 ~DSoundCapture() override;
553 void open(const char *name) override;
554 void start() override;
555 void stop() override;
556 void captureSamples(al::byte *buffer, uint samples) override;
557 uint availableSamples() override;
559 ComPtr<IDirectSoundCapture> mDSC;
560 ComPtr<IDirectSoundCaptureBuffer> mDSCbuffer;
561 DWORD mBufferBytes{0u};
566 DEF_NEWDEL(DSoundCapture)
569 DSoundCapture::~DSoundCapture()
574 mDSCbuffer = nullptr;
580 void DSoundCapture::open(const char *name)
583 if(CaptureDevices.empty())
585 /* Initialize COM to prevent name truncation */
586 HRESULT hrcom{CoInitialize(nullptr)};
587 hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
589 ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
594 const GUID *guid{nullptr};
595 if(!name && !CaptureDevices.empty())
597 name = CaptureDevices[0].name.c_str();
598 guid = &CaptureDevices[0].guid;
602 auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
603 [name](const DevMap &entry) -> bool { return entry.name == name; });
604 if(iter == CaptureDevices.cend())
607 hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id);
609 iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
610 [&id](const DevMap &entry) -> bool { return entry.guid == id; });
611 if(iter == CaptureDevices.cend())
612 throw al::backend_exception{al::backend_error::NoDevice,
613 "Device name \"%s\" not found", name};
618 switch(mDevice->FmtType)
623 WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType));
624 throw al::backend_exception{al::backend_error::DeviceError,
625 "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
634 WAVEFORMATEXTENSIBLE InputType{};
635 switch(mDevice->FmtChans)
637 case DevFmtMono: InputType.dwChannelMask = MONO; break;
638 case DevFmtStereo: InputType.dwChannelMask = STEREO; break;
639 case DevFmtQuad: InputType.dwChannelMask = QUAD; break;
640 case DevFmtX51: InputType.dwChannelMask = X5DOT1; break;
641 case DevFmtX61: InputType.dwChannelMask = X6DOT1; break;
642 case DevFmtX71: InputType.dwChannelMask = X7DOT1; break;
643 case DevFmtX714: InputType.dwChannelMask = X7DOT1DOT4; break;
646 WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
647 throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
648 DevFmtChannelsString(mDevice->FmtChans)};
651 InputType.Format.wFormatTag = WAVE_FORMAT_PCM;
652 InputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
653 InputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
654 InputType.Format.nBlockAlign = static_cast<WORD>(InputType.Format.nChannels *
655 InputType.Format.wBitsPerSample / 8);
656 InputType.Format.nSamplesPerSec = mDevice->Frequency;
657 InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec *
658 InputType.Format.nBlockAlign;
659 InputType.Format.cbSize = 0;
660 InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample;
661 if(mDevice->FmtType == DevFmtFloat)
662 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
664 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
666 if(InputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
668 InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
669 InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
672 uint samples{mDevice->BufferSize};
673 samples = maxu(samples, 100 * mDevice->Frequency / 1000);
675 DSCBUFFERDESC DSCBDescription{};
676 DSCBDescription.dwSize = sizeof(DSCBDescription);
677 DSCBDescription.dwFlags = 0;
678 DSCBDescription.dwBufferBytes = samples * InputType.Format.nBlockAlign;
679 DSCBDescription.lpwfxFormat = &InputType.Format;
681 //DirectSoundCapture Init code
682 hr = DirectSoundCaptureCreate(guid, mDSC.getPtr(), nullptr);
684 mDSC->CreateCaptureBuffer(&DSCBDescription, mDSCbuffer.getPtr(), nullptr);
686 mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
691 mDSCbuffer = nullptr;
694 throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
698 mBufferBytes = DSCBDescription.dwBufferBytes;
699 setDefaultWFXChannelOrder();
701 mDevice->DeviceName = name;
704 void DSoundCapture::start()
706 const HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)};
708 throw al::backend_exception{al::backend_error::DeviceError,
709 "Failure starting capture: 0x%lx", hr};
712 void DSoundCapture::stop()
714 HRESULT hr{mDSCbuffer->Stop()};
717 ERR("stop failed: 0x%08lx\n", hr);
718 mDevice->handleDisconnect("Failure stopping capture: 0x%lx", hr);
722 void DSoundCapture::captureSamples(al::byte *buffer, uint samples)
723 { mRing->read(buffer, samples); }
725 uint DSoundCapture::availableSamples()
727 if(!mDevice->Connected.load(std::memory_order_acquire))
728 return static_cast<uint>(mRing->readSpace());
730 const uint FrameSize{mDevice->frameSizeFromFmt()};
731 const DWORD BufferBytes{mBufferBytes};
732 const DWORD LastCursor{mCursor};
735 void *ReadPtr1{}, *ReadPtr2{};
736 DWORD ReadCnt1{}, ReadCnt2{};
737 HRESULT hr{mDSCbuffer->GetCurrentPosition(nullptr, &ReadCursor)};
740 const DWORD NumBytes{(BufferBytes+ReadCursor-LastCursor) % BufferBytes};
741 if(!NumBytes) return static_cast<uint>(mRing->readSpace());
742 hr = mDSCbuffer->Lock(LastCursor, NumBytes, &ReadPtr1, &ReadCnt1, &ReadPtr2, &ReadCnt2, 0);
746 mRing->write(ReadPtr1, ReadCnt1/FrameSize);
747 if(ReadPtr2 != nullptr && ReadCnt2 > 0)
748 mRing->write(ReadPtr2, ReadCnt2/FrameSize);
749 hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2);
750 mCursor = ReadCursor;
755 ERR("update failed: 0x%08lx\n", hr);
756 mDevice->handleDisconnect("Failure retrieving capture data: 0x%lx", hr);
759 return static_cast<uint>(mRing->readSpace());
765 BackendFactory &DSoundBackendFactory::getFactory()
767 static DSoundBackendFactory factory{};
771 bool DSoundBackendFactory::init()
776 ds_handle = LoadLib("dsound.dll");
779 ERR("Failed to load dsound.dll\n");
783 #define LOAD_FUNC(f) do { \
784 p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(ds_handle, #f)); \
787 CloseLib(ds_handle); \
788 ds_handle = nullptr; \
792 LOAD_FUNC(DirectSoundCreate);
793 LOAD_FUNC(DirectSoundEnumerateW);
794 LOAD_FUNC(DirectSoundCaptureCreate);
795 LOAD_FUNC(DirectSoundCaptureEnumerateW);
802 bool DSoundBackendFactory::querySupport(BackendType type)
803 { return (type == BackendType::Playback || type == BackendType::Capture); }
805 std::string DSoundBackendFactory::probe(BackendType type)
807 std::string outnames;
808 auto add_device = [&outnames](const DevMap &entry) -> void
810 /* +1 to also append the null char (to ensure a null-separated list and
811 * double-null terminated list).
813 outnames.append(entry.name.c_str(), entry.name.length()+1);
816 /* Initialize COM to prevent name truncation */
818 HRESULT hrcom{CoInitialize(nullptr)};
821 case BackendType::Playback:
822 PlaybackDevices.clear();
823 hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
825 ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr);
826 std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
829 case BackendType::Capture:
830 CaptureDevices.clear();
831 hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
833 ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr);
834 std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
843 BackendPtr DSoundBackendFactory::createBackend(DeviceBase *device, BackendType type)
845 if(type == BackendType::Playback)
846 return BackendPtr{new DSoundPlayback{device}};
847 if(type == BackendType::Capture)
848 return BackendPtr{new DSoundCapture{device}};