Recogmize jack64 for finding the JACK library name
[openal-soft.git] / alc / backends / dsound.cpp
blob5e5221162e73ea2f054e9e22eec56f0dbd3203e3
1 /**
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
21 #include "config.h"
23 #include "dsound.h"
25 #define WIN32_LEAN_AND_MEAN
26 #include <windows.h>
28 #include <cguid.h>
29 #include <mmreg.h>
30 #ifndef _WAVEFORMATEXTENSIBLE_
31 #include <ks.h>
32 #include <ksmedia.h>
33 #endif
35 #include <algorithm>
36 #include <atomic>
37 #include <cassert>
38 #include <cstdio>
39 #include <cstdlib>
40 #include <functional>
41 #include <memory.h>
42 #include <mutex>
43 #include <string>
44 #include <thread>
45 #include <vector>
47 #include "alspan.h"
48 #include "alstring.h"
49 #include "althrd_setname.h"
50 #include "comptr.h"
51 #include "core/device.h"
52 #include "core/helpers.h"
53 #include "core/logging.h"
54 #include "dynload.h"
55 #include "ringbuffer.h"
56 #include "strutils.h"
58 /* MinGW-w64 needs this for some unknown reason now. */
59 using LPCWAVEFORMATEX = const WAVEFORMATEX*;
60 #include <dsound.h>
63 #ifndef DSSPEAKER_5POINT1
64 # define DSSPEAKER_5POINT1 0x00000006
65 #endif
66 #ifndef DSSPEAKER_5POINT1_BACK
67 # define DSSPEAKER_5POINT1_BACK 0x00000006
68 #endif
69 #ifndef DSSPEAKER_7POINT1
70 # define DSSPEAKER_7POINT1 0x00000007
71 #endif
72 #ifndef DSSPEAKER_7POINT1_SURROUND
73 # define DSSPEAKER_7POINT1_SURROUND 0x00000008
74 #endif
75 #ifndef DSSPEAKER_5POINT1_SURROUND
76 # define DSSPEAKER_5POINT1_SURROUND 0x00000009
77 #endif
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);
86 #endif
87 #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
88 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
89 #endif
91 namespace {
93 #ifdef HAVE_DYNLOAD
94 void *ds_handle;
95 HRESULT (WINAPI *pDirectSoundCreate)(const GUID *pcGuidDevice, IDirectSound **ppDS, IUnknown *pUnkOuter);
96 HRESULT (WINAPI *pDirectSoundEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext);
97 HRESULT (WINAPI *pDirectSoundCaptureCreate)(const GUID *pcGuidDevice, IDirectSoundCapture **ppDSC, IUnknown *pUnkOuter);
98 HRESULT (WINAPI *pDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext);
100 #ifndef IN_IDE_PARSER
101 #define DirectSoundCreate pDirectSoundCreate
102 #define DirectSoundEnumerateW pDirectSoundEnumerateW
103 #define DirectSoundCaptureCreate pDirectSoundCaptureCreate
104 #define DirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW
105 #endif
106 #endif
109 #define MONO SPEAKER_FRONT_CENTER
110 #define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)
111 #define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
112 #define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
113 #define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
114 #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
115 #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)
116 #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)
118 #define MAX_UPDATES 128
120 struct DevMap {
121 std::string name;
122 GUID guid;
124 template<typename T0, typename T1>
125 DevMap(T0&& name_, T1&& guid_)
126 : name{std::forward<T0>(name_)}, guid{std::forward<T1>(guid_)}
130 std::vector<DevMap> PlaybackDevices;
131 std::vector<DevMap> CaptureDevices;
133 bool checkName(const al::span<DevMap> list, const std::string &name)
135 auto match_name = [&name](const DevMap &entry) -> bool
136 { return entry.name == name; };
137 return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
140 BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, void *data) noexcept
142 if(!guid)
143 return TRUE;
145 auto& devices = *static_cast<std::vector<DevMap>*>(data);
146 const auto basename = wstr_to_utf8(desc);
148 int count{1};
149 std::string newname{basename};
150 while(checkName(devices, newname))
152 newname = basename;
153 newname += " #";
154 newname += std::to_string(++count);
156 devices.emplace_back(std::move(newname), *guid);
157 const DevMap &newentry = devices.back();
159 OLECHAR *guidstr{nullptr};
160 HRESULT hr{StringFromCLSID(*guid, &guidstr)};
161 if(SUCCEEDED(hr))
163 TRACE("Got device \"%s\", GUID \"%ls\"\n", newentry.name.c_str(), guidstr);
164 CoTaskMemFree(guidstr);
167 return TRUE;
171 struct DSoundPlayback final : public BackendBase {
172 DSoundPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
173 ~DSoundPlayback() override;
175 int mixerProc();
177 void open(std::string_view name) override;
178 bool reset() override;
179 void start() override;
180 void stop() override;
182 ComPtr<IDirectSound> mDS;
183 ComPtr<IDirectSoundBuffer> mPrimaryBuffer;
184 ComPtr<IDirectSoundBuffer> mBuffer;
185 ComPtr<IDirectSoundNotify> mNotifies;
186 HANDLE mNotifyEvent{nullptr};
188 std::atomic<bool> mKillNow{true};
189 std::thread mThread;
192 DSoundPlayback::~DSoundPlayback()
194 mNotifies = nullptr;
195 mBuffer = nullptr;
196 mPrimaryBuffer = nullptr;
197 mDS = nullptr;
199 if(mNotifyEvent)
200 CloseHandle(mNotifyEvent);
201 mNotifyEvent = nullptr;
205 FORCE_ALIGN int DSoundPlayback::mixerProc()
207 SetRTPriority();
208 althrd_setname(GetMixerThreadName());
210 DSBCAPS DSBCaps{};
211 DSBCaps.dwSize = sizeof(DSBCaps);
212 HRESULT err{mBuffer->GetCaps(&DSBCaps)};
213 if(FAILED(err))
215 ERR("Failed to get buffer caps: 0x%lx\n", err);
216 mDevice->handleDisconnect("Failure retrieving playback buffer info: 0x%lx", err);
217 return 1;
220 const size_t FrameStep{mDevice->channelsFromFmt()};
221 uint FrameSize{mDevice->frameSizeFromFmt()};
222 DWORD FragSize{mDevice->UpdateSize * FrameSize};
224 bool Playing{false};
225 DWORD LastCursor{0u};
226 mBuffer->GetCurrentPosition(&LastCursor, nullptr);
227 while(!mKillNow.load(std::memory_order_acquire)
228 && mDevice->Connected.load(std::memory_order_acquire))
230 // Get current play cursor
231 DWORD PlayCursor;
232 mBuffer->GetCurrentPosition(&PlayCursor, nullptr);
233 DWORD avail = (PlayCursor-LastCursor+DSBCaps.dwBufferBytes) % DSBCaps.dwBufferBytes;
235 if(avail < FragSize)
237 if(!Playing)
239 err = mBuffer->Play(0, 0, DSBPLAY_LOOPING);
240 if(FAILED(err))
242 ERR("Failed to play buffer: 0x%lx\n", err);
243 mDevice->handleDisconnect("Failure starting playback: 0x%lx", err);
244 return 1;
246 Playing = true;
249 avail = WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE);
250 if(avail != WAIT_OBJECT_0)
251 ERR("WaitForSingleObjectEx error: 0x%lx\n", avail);
252 continue;
254 avail -= avail%FragSize;
256 // Lock output buffer
257 void *WritePtr1, *WritePtr2;
258 DWORD WriteCnt1{0u}, WriteCnt2{0u};
259 err = mBuffer->Lock(LastCursor, avail, &WritePtr1, &WriteCnt1, &WritePtr2, &WriteCnt2, 0);
261 // If the buffer is lost, restore it and lock
262 if(err == DSERR_BUFFERLOST)
264 WARN("Buffer lost, restoring...\n");
265 err = mBuffer->Restore();
266 if(SUCCEEDED(err))
268 Playing = false;
269 LastCursor = 0;
270 err = mBuffer->Lock(0, DSBCaps.dwBufferBytes, &WritePtr1, &WriteCnt1,
271 &WritePtr2, &WriteCnt2, 0);
275 if(SUCCEEDED(err))
277 mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep);
278 if(WriteCnt2 > 0)
279 mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep);
281 mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2);
283 else
285 ERR("Buffer lock error: %#lx\n", err);
286 mDevice->handleDisconnect("Failed to lock output buffer: 0x%lx", err);
287 return 1;
290 // Update old write cursor location
291 LastCursor += WriteCnt1+WriteCnt2;
292 LastCursor %= DSBCaps.dwBufferBytes;
295 return 0;
298 void DSoundPlayback::open(std::string_view name)
300 HRESULT hr;
301 if(PlaybackDevices.empty())
303 /* Initialize COM to prevent name truncation */
304 ComWrapper com{};
305 hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
306 if(FAILED(hr))
307 ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
310 const GUID *guid{nullptr};
311 if(name.empty() && !PlaybackDevices.empty())
313 name = PlaybackDevices[0].name;
314 guid = &PlaybackDevices[0].guid;
316 else
318 auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
319 [name](const DevMap &entry) -> bool { return entry.name == name; });
320 if(iter == PlaybackDevices.cend())
322 GUID id{};
323 hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id);
324 if(SUCCEEDED(hr))
325 iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
326 [&id](const DevMap &entry) -> bool { return entry.guid == id; });
327 if(iter == PlaybackDevices.cend())
328 throw al::backend_exception{al::backend_error::NoDevice,
329 "Device name \"%.*s\" not found", al::sizei(name), name.data()};
331 guid = &iter->guid;
334 hr = DS_OK;
335 if(!mNotifyEvent)
337 mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
338 if(!mNotifyEvent) hr = E_FAIL;
341 //DirectSound Init code
342 ComPtr<IDirectSound> ds;
343 if(SUCCEEDED(hr))
344 hr = DirectSoundCreate(guid, al::out_ptr(ds), nullptr);
345 if(SUCCEEDED(hr))
346 hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
347 if(FAILED(hr))
348 throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
349 hr};
351 mNotifies = nullptr;
352 mBuffer = nullptr;
353 mPrimaryBuffer = nullptr;
354 mDS = std::move(ds);
356 mDeviceName = name;
359 bool DSoundPlayback::reset()
361 mNotifies = nullptr;
362 mBuffer = nullptr;
363 mPrimaryBuffer = nullptr;
365 switch(mDevice->FmtType)
367 case DevFmtByte:
368 mDevice->FmtType = DevFmtUByte;
369 break;
370 case DevFmtFloat:
371 if(mDevice->Flags.test(SampleTypeRequest))
372 break;
373 /* fall-through */
374 case DevFmtUShort:
375 mDevice->FmtType = DevFmtShort;
376 break;
377 case DevFmtUInt:
378 mDevice->FmtType = DevFmtInt;
379 break;
380 case DevFmtUByte:
381 case DevFmtShort:
382 case DevFmtInt:
383 break;
386 WAVEFORMATEXTENSIBLE OutputType{};
387 DWORD speakers{};
388 HRESULT hr{mDS->GetSpeakerConfig(&speakers)};
389 if(FAILED(hr))
390 throw al::backend_exception{al::backend_error::DeviceError,
391 "Failed to get speaker config: 0x%08lx", hr};
393 speakers = DSSPEAKER_CONFIG(speakers);
394 if(!mDevice->Flags.test(ChannelsRequest))
396 if(speakers == DSSPEAKER_MONO)
397 mDevice->FmtChans = DevFmtMono;
398 else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE)
399 mDevice->FmtChans = DevFmtStereo;
400 else if(speakers == DSSPEAKER_QUAD)
401 mDevice->FmtChans = DevFmtQuad;
402 else if(speakers == DSSPEAKER_5POINT1_SURROUND || speakers == DSSPEAKER_5POINT1_BACK)
403 mDevice->FmtChans = DevFmtX51;
404 else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
405 mDevice->FmtChans = DevFmtX71;
406 else
407 ERR("Unknown system speaker config: 0x%lx\n", speakers);
409 mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE));
410 const bool isRear51{speakers == DSSPEAKER_5POINT1_BACK};
412 switch(mDevice->FmtChans)
414 case DevFmtMono: OutputType.dwChannelMask = MONO; break;
415 case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo;
416 /* fall-through */
417 case DevFmtStereo: OutputType.dwChannelMask = STEREO; break;
418 case DevFmtQuad: OutputType.dwChannelMask = QUAD; break;
419 case DevFmtX51: OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break;
420 case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
421 case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break;
422 case DevFmtX7144: mDevice->FmtChans = DevFmtX714;
423 /* fall-through */
424 case DevFmtX714: OutputType.dwChannelMask = X7DOT1DOT4; break;
425 case DevFmtX3D71: OutputType.dwChannelMask = X7DOT1; break;
428 do {
429 hr = S_OK;
430 OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
431 OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
432 OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
433 OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
434 OutputType.Format.wBitsPerSample / 8);
435 OutputType.Format.nSamplesPerSec = mDevice->Frequency;
436 OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
437 OutputType.Format.nBlockAlign;
438 OutputType.Format.cbSize = 0;
440 if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
442 OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
443 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
444 OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
445 OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
446 if(mDevice->FmtType == DevFmtFloat)
447 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
448 else
449 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
451 mPrimaryBuffer = nullptr;
453 else
455 if(SUCCEEDED(hr) && !mPrimaryBuffer)
457 DSBUFFERDESC DSBDescription{};
458 DSBDescription.dwSize = sizeof(DSBDescription);
459 DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
460 hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mPrimaryBuffer), nullptr);
462 if(SUCCEEDED(hr))
463 hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
466 if(FAILED(hr))
467 break;
469 uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
470 if(num_updates > MAX_UPDATES)
471 num_updates = MAX_UPDATES;
472 mDevice->BufferSize = mDevice->UpdateSize * num_updates;
474 DSBUFFERDESC DSBDescription{};
475 DSBDescription.dwSize = sizeof(DSBDescription);
476 DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2
477 | DSBCAPS_GLOBALFOCUS;
478 DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign;
479 DSBDescription.lpwfxFormat = &OutputType.Format;
481 hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mBuffer), nullptr);
482 if(SUCCEEDED(hr) || mDevice->FmtType != DevFmtFloat)
483 break;
484 mDevice->FmtType = DevFmtShort;
485 } while(FAILED(hr));
487 if(SUCCEEDED(hr))
489 hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, al::out_ptr(mNotifies));
490 if(SUCCEEDED(hr))
492 uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
493 assert(num_updates <= MAX_UPDATES);
495 std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots{};
496 for(uint i{0};i < num_updates;++i)
498 nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign;
499 nots[i].hEventNotify = mNotifyEvent;
501 if(mNotifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK)
502 hr = E_FAIL;
506 if(FAILED(hr))
508 mNotifies = nullptr;
509 mBuffer = nullptr;
510 mPrimaryBuffer = nullptr;
511 return false;
514 ResetEvent(mNotifyEvent);
515 setDefaultWFXChannelOrder();
517 return true;
520 void DSoundPlayback::start()
522 try {
523 mKillNow.store(false, std::memory_order_release);
524 mThread = std::thread{std::mem_fn(&DSoundPlayback::mixerProc), this};
526 catch(std::exception& e) {
527 throw al::backend_exception{al::backend_error::DeviceError,
528 "Failed to start mixing thread: %s", e.what()};
532 void DSoundPlayback::stop()
534 if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
535 return;
536 mThread.join();
538 mBuffer->Stop();
542 struct DSoundCapture final : public BackendBase {
543 DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { }
544 ~DSoundCapture() override;
546 void open(std::string_view name) override;
547 void start() override;
548 void stop() override;
549 void captureSamples(std::byte *buffer, uint samples) override;
550 uint availableSamples() override;
552 ComPtr<IDirectSoundCapture> mDSC;
553 ComPtr<IDirectSoundCaptureBuffer> mDSCbuffer;
554 DWORD mBufferBytes{0u};
555 DWORD mCursor{0u};
557 RingBufferPtr mRing;
560 DSoundCapture::~DSoundCapture()
562 if(mDSCbuffer)
564 mDSCbuffer->Stop();
565 mDSCbuffer = nullptr;
567 mDSC = nullptr;
571 void DSoundCapture::open(std::string_view name)
573 HRESULT hr;
574 if(CaptureDevices.empty())
576 /* Initialize COM to prevent name truncation */
577 ComWrapper com{};
578 hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
579 if(FAILED(hr))
580 ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
583 const GUID *guid{nullptr};
584 if(name.empty() && !CaptureDevices.empty())
586 name = CaptureDevices[0].name;
587 guid = &CaptureDevices[0].guid;
589 else
591 auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
592 [name](const DevMap &entry) -> bool { return entry.name == name; });
593 if(iter == CaptureDevices.cend())
595 GUID id{};
596 hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id);
597 if(SUCCEEDED(hr))
598 iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
599 [&id](const DevMap &entry) -> bool { return entry.guid == id; });
600 if(iter == CaptureDevices.cend())
601 throw al::backend_exception{al::backend_error::NoDevice,
602 "Device name \"%.*s\" not found", al::sizei(name), name.data()};
604 guid = &iter->guid;
607 switch(mDevice->FmtType)
609 case DevFmtByte:
610 case DevFmtUShort:
611 case DevFmtUInt:
612 WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType));
613 throw al::backend_exception{al::backend_error::DeviceError,
614 "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
616 case DevFmtUByte:
617 case DevFmtShort:
618 case DevFmtInt:
619 case DevFmtFloat:
620 break;
623 WAVEFORMATEXTENSIBLE InputType{};
624 switch(mDevice->FmtChans)
626 case DevFmtMono: InputType.dwChannelMask = MONO; break;
627 case DevFmtStereo: InputType.dwChannelMask = STEREO; break;
628 case DevFmtQuad: InputType.dwChannelMask = QUAD; break;
629 case DevFmtX51: InputType.dwChannelMask = X5DOT1; break;
630 case DevFmtX61: InputType.dwChannelMask = X6DOT1; break;
631 case DevFmtX71: InputType.dwChannelMask = X7DOT1; break;
632 case DevFmtX714: InputType.dwChannelMask = X7DOT1DOT4; break;
633 case DevFmtX7144:
634 case DevFmtX3D71:
635 case DevFmtAmbi3D:
636 WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
637 throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
638 DevFmtChannelsString(mDevice->FmtChans)};
641 InputType.Format.wFormatTag = WAVE_FORMAT_PCM;
642 InputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
643 InputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
644 InputType.Format.nBlockAlign = static_cast<WORD>(InputType.Format.nChannels *
645 InputType.Format.wBitsPerSample / 8);
646 InputType.Format.nSamplesPerSec = mDevice->Frequency;
647 InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec *
648 InputType.Format.nBlockAlign;
649 InputType.Format.cbSize = 0;
650 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
651 InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample;
652 if(mDevice->FmtType == DevFmtFloat)
653 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
654 else
655 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
657 if(InputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
659 InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
660 InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
663 const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)};
665 DSCBUFFERDESC DSCBDescription{};
666 DSCBDescription.dwSize = sizeof(DSCBDescription);
667 DSCBDescription.dwFlags = 0;
668 DSCBDescription.dwBufferBytes = samples * InputType.Format.nBlockAlign;
669 DSCBDescription.lpwfxFormat = &InputType.Format;
671 //DirectSoundCapture Init code
672 hr = DirectSoundCaptureCreate(guid, al::out_ptr(mDSC), nullptr);
673 if(SUCCEEDED(hr))
674 mDSC->CreateCaptureBuffer(&DSCBDescription, al::out_ptr(mDSCbuffer), nullptr);
675 if(SUCCEEDED(hr))
676 mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
678 if(FAILED(hr))
680 mRing = nullptr;
681 mDSCbuffer = nullptr;
682 mDSC = nullptr;
684 throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
685 hr};
688 mBufferBytes = DSCBDescription.dwBufferBytes;
689 setDefaultWFXChannelOrder();
691 mDeviceName = name;
694 void DSoundCapture::start()
696 const HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)};
697 if(FAILED(hr))
698 throw al::backend_exception{al::backend_error::DeviceError,
699 "Failure starting capture: 0x%lx", hr};
702 void DSoundCapture::stop()
704 HRESULT hr{mDSCbuffer->Stop()};
705 if(FAILED(hr))
707 ERR("stop failed: 0x%08lx\n", hr);
708 mDevice->handleDisconnect("Failure stopping capture: 0x%lx", hr);
712 void DSoundCapture::captureSamples(std::byte *buffer, uint samples)
713 { std::ignore = mRing->read(buffer, samples); }
715 uint DSoundCapture::availableSamples()
717 if(!mDevice->Connected.load(std::memory_order_acquire))
718 return static_cast<uint>(mRing->readSpace());
720 const uint FrameSize{mDevice->frameSizeFromFmt()};
721 const DWORD BufferBytes{mBufferBytes};
722 const DWORD LastCursor{mCursor};
724 DWORD ReadCursor{};
725 void *ReadPtr1{}, *ReadPtr2{};
726 DWORD ReadCnt1{}, ReadCnt2{};
727 HRESULT hr{mDSCbuffer->GetCurrentPosition(nullptr, &ReadCursor)};
728 if(SUCCEEDED(hr))
730 const DWORD NumBytes{(BufferBytes+ReadCursor-LastCursor) % BufferBytes};
731 if(!NumBytes) return static_cast<uint>(mRing->readSpace());
732 hr = mDSCbuffer->Lock(LastCursor, NumBytes, &ReadPtr1, &ReadCnt1, &ReadPtr2, &ReadCnt2, 0);
734 if(SUCCEEDED(hr))
736 std::ignore = mRing->write(ReadPtr1, ReadCnt1/FrameSize);
737 if(ReadPtr2 != nullptr && ReadCnt2 > 0)
738 std::ignore = mRing->write(ReadPtr2, ReadCnt2/FrameSize);
739 hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2);
740 mCursor = ReadCursor;
743 if(FAILED(hr))
745 ERR("update failed: 0x%08lx\n", hr);
746 mDevice->handleDisconnect("Failure retrieving capture data: 0x%lx", hr);
749 return static_cast<uint>(mRing->readSpace());
752 } // namespace
755 BackendFactory &DSoundBackendFactory::getFactory()
757 static DSoundBackendFactory factory{};
758 return factory;
761 bool DSoundBackendFactory::init()
763 #ifdef HAVE_DYNLOAD
764 if(!ds_handle)
766 ds_handle = LoadLib("dsound.dll");
767 if(!ds_handle)
769 ERR("Failed to load dsound.dll\n");
770 return false;
773 #define LOAD_FUNC(f) do { \
774 p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(ds_handle, #f)); \
775 if(!p##f) \
777 CloseLib(ds_handle); \
778 ds_handle = nullptr; \
779 return false; \
781 } while(0)
782 LOAD_FUNC(DirectSoundCreate);
783 LOAD_FUNC(DirectSoundEnumerateW);
784 LOAD_FUNC(DirectSoundCaptureCreate);
785 LOAD_FUNC(DirectSoundCaptureEnumerateW);
786 #undef LOAD_FUNC
788 #endif
789 return true;
792 bool DSoundBackendFactory::querySupport(BackendType type)
793 { return (type == BackendType::Playback || type == BackendType::Capture); }
795 auto DSoundBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
797 std::vector<std::string> outnames;
798 auto add_device = [&outnames](const DevMap &entry) -> void
799 { outnames.emplace_back(entry.name); };
801 /* Initialize COM to prevent name truncation */
802 ComWrapper com{};
803 switch(type)
805 case BackendType::Playback:
806 PlaybackDevices.clear();
807 if(HRESULT hr{DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices)}; FAILED(hr))
808 ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr);
809 outnames.reserve(PlaybackDevices.size());
810 std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
811 break;
813 case BackendType::Capture:
814 CaptureDevices.clear();
815 if(HRESULT hr{DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices)};FAILED(hr))
816 ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr);
817 outnames.reserve(CaptureDevices.size());
818 std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
819 break;
822 return outnames;
825 BackendPtr DSoundBackendFactory::createBackend(DeviceBase *device, BackendType type)
827 if(type == BackendType::Playback)
828 return BackendPtr{new DSoundPlayback{device}};
829 if(type == BackendType::Capture)
830 return BackendPtr{new DSoundCapture{device}};
831 return nullptr;