Make C callbacks noexcept
[openal-soft.git] / alc / backends / dsound.cpp
blobc04ba9e4a90cbc4f88960f1f5e463abdbbff6a18
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 "backends/dsound.h"
25 #define WIN32_LEAN_AND_MEAN
26 #include <windows.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <memory.h>
32 #include <cguid.h>
33 #include <mmreg.h>
34 #ifndef _WAVEFORMATEXTENSIBLE_
35 #include <ks.h>
36 #include <ksmedia.h>
37 #endif
39 #include <atomic>
40 #include <cassert>
41 #include <thread>
42 #include <string>
43 #include <vector>
44 #include <algorithm>
45 #include <functional>
47 #include "alcmain.h"
48 #include "alexcpt.h"
49 #include "alu.h"
50 #include "ringbuffer.h"
51 #include "compat.h"
52 #include "dynload.h"
53 #include "strutils.h"
54 #include "threads.h"
56 /* MinGW-w64 needs this for some unknown reason now. */
57 using LPCWAVEFORMATEX = const WAVEFORMATEX*;
58 #include <dsound.h>
61 #ifndef DSSPEAKER_5POINT1
62 # define DSSPEAKER_5POINT1 0x00000006
63 #endif
64 #ifndef DSSPEAKER_5POINT1_BACK
65 # define DSSPEAKER_5POINT1_BACK 0x00000006
66 #endif
67 #ifndef DSSPEAKER_7POINT1
68 # define DSSPEAKER_7POINT1 0x00000007
69 #endif
70 #ifndef DSSPEAKER_7POINT1_SURROUND
71 # define DSSPEAKER_7POINT1_SURROUND 0x00000008
72 #endif
73 #ifndef DSSPEAKER_5POINT1_SURROUND
74 # define DSSPEAKER_5POINT1_SURROUND 0x00000009
75 #endif
78 /* Some headers seem to define these as macros for __uuidof, which is annoying
79 * since some headers don't declare them at all. Hopefully the ifdef is enough
80 * to tell if they need to be declared.
82 #ifndef KSDATAFORMAT_SUBTYPE_PCM
83 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
84 #endif
85 #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
86 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
87 #endif
89 namespace {
91 #define DEVNAME_HEAD "OpenAL Soft on "
94 #ifdef HAVE_DYNLOAD
95 void *ds_handle;
96 HRESULT (WINAPI *pDirectSoundCreate)(const GUID *pcGuidDevice, IDirectSound **ppDS, IUnknown *pUnkOuter);
97 HRESULT (WINAPI *pDirectSoundEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext);
98 HRESULT (WINAPI *pDirectSoundCaptureCreate)(const GUID *pcGuidDevice, IDirectSoundCapture **ppDSC, IUnknown *pUnkOuter);
99 HRESULT (WINAPI *pDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext);
101 #ifndef IN_IDE_PARSER
102 #define DirectSoundCreate pDirectSoundCreate
103 #define DirectSoundEnumerateW pDirectSoundEnumerateW
104 #define DirectSoundCaptureCreate pDirectSoundCaptureCreate
105 #define DirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW
106 #endif
107 #endif
110 #define MAX_UPDATES 128
112 struct DevMap {
113 std::string name;
114 GUID guid;
116 template<typename T0, typename T1>
117 DevMap(T0&& name_, T1&& guid_)
118 : name{std::forward<T0>(name_)}, guid{std::forward<T1>(guid_)}
122 al::vector<DevMap> PlaybackDevices;
123 al::vector<DevMap> CaptureDevices;
125 bool checkName(const al::vector<DevMap> &list, const std::string &name)
127 auto match_name = [&name](const DevMap &entry) -> bool
128 { return entry.name == name; };
129 return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
132 BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, void *data) noexcept
134 if(!guid)
135 return TRUE;
137 auto& devices = *static_cast<al::vector<DevMap>*>(data);
138 const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)};
140 int count{1};
141 std::string newname{basename};
142 while(checkName(devices, newname))
144 newname = basename;
145 newname += " #";
146 newname += std::to_string(++count);
148 devices.emplace_back(std::move(newname), *guid);
149 const DevMap &newentry = devices.back();
151 OLECHAR *guidstr{nullptr};
152 HRESULT hr{StringFromCLSID(*guid, &guidstr)};
153 if(SUCCEEDED(hr))
155 TRACE("Got device \"%s\", GUID \"%ls\"\n", newentry.name.c_str(), guidstr);
156 CoTaskMemFree(guidstr);
159 return TRUE;
163 struct DSoundPlayback final : public BackendBase {
164 DSoundPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
165 ~DSoundPlayback() override;
167 int mixerProc();
169 void open(const ALCchar *name) override;
170 bool reset() override;
171 bool start() override;
172 void stop() override;
174 IDirectSound *mDS{nullptr};
175 IDirectSoundBuffer *mPrimaryBuffer{nullptr};
176 IDirectSoundBuffer *mBuffer{nullptr};
177 IDirectSoundNotify *mNotifies{nullptr};
178 HANDLE mNotifyEvent{nullptr};
180 std::atomic<bool> mKillNow{true};
181 std::thread mThread;
183 DEF_NEWDEL(DSoundPlayback)
186 DSoundPlayback::~DSoundPlayback()
188 if(mNotifies)
189 mNotifies->Release();
190 mNotifies = nullptr;
191 if(mBuffer)
192 mBuffer->Release();
193 mBuffer = nullptr;
194 if(mPrimaryBuffer)
195 mPrimaryBuffer->Release();
196 mPrimaryBuffer = nullptr;
198 if(mDS)
199 mDS->Release();
200 mDS = nullptr;
201 if(mNotifyEvent)
202 CloseHandle(mNotifyEvent);
203 mNotifyEvent = nullptr;
207 FORCE_ALIGN int DSoundPlayback::mixerProc()
209 SetRTPriority();
210 althrd_setname(MIXER_THREAD_NAME);
212 DSBCAPS DSBCaps{};
213 DSBCaps.dwSize = sizeof(DSBCaps);
214 HRESULT err{mBuffer->GetCaps(&DSBCaps)};
215 if(FAILED(err))
217 ERR("Failed to get buffer caps: 0x%lx\n", err);
218 aluHandleDisconnect(mDevice, "Failure retrieving playback buffer info: 0x%lx", err);
219 return 1;
222 ALuint FrameSize{mDevice->frameSizeFromFmt()};
223 DWORD FragSize{mDevice->UpdateSize * FrameSize};
225 bool Playing{false};
226 DWORD LastCursor{0u};
227 mBuffer->GetCurrentPosition(&LastCursor, nullptr);
228 while(!mKillNow.load(std::memory_order_acquire) &&
229 mDevice->Connected.load(std::memory_order_acquire))
231 // Get current play cursor
232 DWORD PlayCursor;
233 mBuffer->GetCurrentPosition(&PlayCursor, nullptr);
234 DWORD avail = (PlayCursor-LastCursor+DSBCaps.dwBufferBytes) % DSBCaps.dwBufferBytes;
236 if(avail < FragSize)
238 if(!Playing)
240 err = mBuffer->Play(0, 0, DSBPLAY_LOOPING);
241 if(FAILED(err))
243 ERR("Failed to play buffer: 0x%lx\n", err);
244 aluHandleDisconnect(mDevice, "Failure starting playback: 0x%lx", err);
245 return 1;
247 Playing = true;
250 avail = WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE);
251 if(avail != WAIT_OBJECT_0)
252 ERR("WaitForSingleObjectEx error: 0x%lx\n", avail);
253 continue;
255 avail -= avail%FragSize;
257 // Lock output buffer
258 void *WritePtr1, *WritePtr2;
259 DWORD WriteCnt1{0u}, WriteCnt2{0u};
260 err = mBuffer->Lock(LastCursor, avail, &WritePtr1, &WriteCnt1, &WritePtr2, &WriteCnt2, 0);
262 // If the buffer is lost, restore it and lock
263 if(err == DSERR_BUFFERLOST)
265 WARN("Buffer lost, restoring...\n");
266 err = mBuffer->Restore();
267 if(SUCCEEDED(err))
269 Playing = false;
270 LastCursor = 0;
271 err = mBuffer->Lock(0, DSBCaps.dwBufferBytes, &WritePtr1, &WriteCnt1,
272 &WritePtr2, &WriteCnt2, 0);
276 if(SUCCEEDED(err))
278 std::unique_lock<DSoundPlayback> dlock{*this};
279 aluMixData(mDevice, WritePtr1, WriteCnt1/FrameSize);
280 if(WriteCnt2 > 0)
281 aluMixData(mDevice, WritePtr2, WriteCnt2/FrameSize);
282 dlock.unlock();
284 mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2);
286 else
288 ERR("Buffer lock error: %#lx\n", err);
289 std::lock_guard<DSoundPlayback> _{*this};
290 aluHandleDisconnect(mDevice, "Failed to lock output buffer: 0x%lx", err);
291 return 1;
294 // Update old write cursor location
295 LastCursor += WriteCnt1+WriteCnt2;
296 LastCursor %= DSBCaps.dwBufferBytes;
299 return 0;
302 void DSoundPlayback::open(const ALCchar *name)
304 HRESULT hr;
305 if(PlaybackDevices.empty())
307 /* Initialize COM to prevent name truncation */
308 HRESULT hrcom{CoInitialize(nullptr)};
309 hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
310 if(FAILED(hr))
311 ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
312 if(SUCCEEDED(hrcom))
313 CoUninitialize();
316 const GUID *guid{nullptr};
317 if(!name && !PlaybackDevices.empty())
319 name = PlaybackDevices[0].name.c_str();
320 guid = &PlaybackDevices[0].guid;
322 else
324 auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
325 [name](const DevMap &entry) -> bool
326 { return entry.name == name; }
328 if(iter == PlaybackDevices.cend())
329 throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
330 guid = &iter->guid;
333 hr = DS_OK;
334 mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
335 if(!mNotifyEvent) hr = E_FAIL;
337 //DirectSound Init code
338 if(SUCCEEDED(hr))
339 hr = DirectSoundCreate(guid, &mDS, nullptr);
340 if(SUCCEEDED(hr))
341 hr = mDS->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
342 if(FAILED(hr))
343 throw al::backend_exception{ALC_INVALID_VALUE, "Device init failed: 0x%08lx", hr};
345 mDevice->DeviceName = name;
348 bool DSoundPlayback::reset()
350 if(mNotifies)
351 mNotifies->Release();
352 mNotifies = nullptr;
353 if(mBuffer)
354 mBuffer->Release();
355 mBuffer = nullptr;
356 if(mPrimaryBuffer)
357 mPrimaryBuffer->Release();
358 mPrimaryBuffer = nullptr;
360 switch(mDevice->FmtType)
362 case DevFmtByte:
363 mDevice->FmtType = DevFmtUByte;
364 break;
365 case DevFmtFloat:
366 if(mDevice->Flags.get<SampleTypeRequest>())
367 break;
368 /* fall-through */
369 case DevFmtUShort:
370 mDevice->FmtType = DevFmtShort;
371 break;
372 case DevFmtUInt:
373 mDevice->FmtType = DevFmtInt;
374 break;
375 case DevFmtUByte:
376 case DevFmtShort:
377 case DevFmtInt:
378 break;
381 WAVEFORMATEXTENSIBLE OutputType{};
382 DWORD speakers;
383 HRESULT hr{mDS->GetSpeakerConfig(&speakers)};
384 if(SUCCEEDED(hr))
386 speakers = DSSPEAKER_CONFIG(speakers);
387 if(!mDevice->Flags.get<ChannelsRequest>())
389 if(speakers == DSSPEAKER_MONO)
390 mDevice->FmtChans = DevFmtMono;
391 else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE)
392 mDevice->FmtChans = DevFmtStereo;
393 else if(speakers == DSSPEAKER_QUAD)
394 mDevice->FmtChans = DevFmtQuad;
395 else if(speakers == DSSPEAKER_5POINT1_SURROUND)
396 mDevice->FmtChans = DevFmtX51;
397 else if(speakers == DSSPEAKER_5POINT1_BACK)
398 mDevice->FmtChans = DevFmtX51Rear;
399 else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
400 mDevice->FmtChans = DevFmtX71;
401 else
402 ERR("Unknown system speaker config: 0x%lx\n", speakers);
404 mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo &&
405 speakers == DSSPEAKER_HEADPHONE);
407 switch(mDevice->FmtChans)
409 case DevFmtMono:
410 OutputType.dwChannelMask = SPEAKER_FRONT_CENTER;
411 break;
412 case DevFmtAmbi3D:
413 mDevice->FmtChans = DevFmtStereo;
414 /*fall-through*/
415 case DevFmtStereo:
416 OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
417 SPEAKER_FRONT_RIGHT;
418 break;
419 case DevFmtQuad:
420 OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
421 SPEAKER_FRONT_RIGHT |
422 SPEAKER_BACK_LEFT |
423 SPEAKER_BACK_RIGHT;
424 break;
425 case DevFmtX51:
426 OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
427 SPEAKER_FRONT_RIGHT |
428 SPEAKER_FRONT_CENTER |
429 SPEAKER_LOW_FREQUENCY |
430 SPEAKER_SIDE_LEFT |
431 SPEAKER_SIDE_RIGHT;
432 break;
433 case DevFmtX51Rear:
434 OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
435 SPEAKER_FRONT_RIGHT |
436 SPEAKER_FRONT_CENTER |
437 SPEAKER_LOW_FREQUENCY |
438 SPEAKER_BACK_LEFT |
439 SPEAKER_BACK_RIGHT;
440 break;
441 case DevFmtX61:
442 OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
443 SPEAKER_FRONT_RIGHT |
444 SPEAKER_FRONT_CENTER |
445 SPEAKER_LOW_FREQUENCY |
446 SPEAKER_BACK_CENTER |
447 SPEAKER_SIDE_LEFT |
448 SPEAKER_SIDE_RIGHT;
449 break;
450 case DevFmtX71:
451 OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
452 SPEAKER_FRONT_RIGHT |
453 SPEAKER_FRONT_CENTER |
454 SPEAKER_LOW_FREQUENCY |
455 SPEAKER_BACK_LEFT |
456 SPEAKER_BACK_RIGHT |
457 SPEAKER_SIDE_LEFT |
458 SPEAKER_SIDE_RIGHT;
459 break;
462 retry_open:
463 hr = S_OK;
464 OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
465 OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
466 OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
467 OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
468 OutputType.Format.wBitsPerSample / 8);
469 OutputType.Format.nSamplesPerSec = mDevice->Frequency;
470 OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
471 OutputType.Format.nBlockAlign;
472 OutputType.Format.cbSize = 0;
475 if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
477 OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
478 OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
479 OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
480 if(mDevice->FmtType == DevFmtFloat)
481 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
482 else
483 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
485 if(mPrimaryBuffer)
486 mPrimaryBuffer->Release();
487 mPrimaryBuffer = nullptr;
489 else
491 if(SUCCEEDED(hr) && !mPrimaryBuffer)
493 DSBUFFERDESC DSBDescription{};
494 DSBDescription.dwSize = sizeof(DSBDescription);
495 DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
496 hr = mDS->CreateSoundBuffer(&DSBDescription, &mPrimaryBuffer, nullptr);
498 if(SUCCEEDED(hr))
499 hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
502 if(SUCCEEDED(hr))
504 ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
505 if(num_updates > MAX_UPDATES)
506 num_updates = MAX_UPDATES;
507 mDevice->BufferSize = mDevice->UpdateSize * num_updates;
509 DSBUFFERDESC DSBDescription{};
510 DSBDescription.dwSize = sizeof(DSBDescription);
511 DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 |
512 DSBCAPS_GLOBALFOCUS;
513 DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign;
514 DSBDescription.lpwfxFormat = &OutputType.Format;
516 hr = mDS->CreateSoundBuffer(&DSBDescription, &mBuffer, nullptr);
517 if(FAILED(hr) && mDevice->FmtType == DevFmtFloat)
519 mDevice->FmtType = DevFmtShort;
520 goto retry_open;
524 if(SUCCEEDED(hr))
526 void *ptr;
527 hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr);
528 if(SUCCEEDED(hr))
530 auto Notifies = static_cast<IDirectSoundNotify*>(ptr);
531 mNotifies = Notifies;
533 ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
534 assert(num_updates <= MAX_UPDATES);
536 std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots;
537 for(ALuint i{0};i < num_updates;++i)
539 nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign;
540 nots[i].hEventNotify = mNotifyEvent;
542 if(Notifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK)
543 hr = E_FAIL;
547 if(FAILED(hr))
549 if(mNotifies)
550 mNotifies->Release();
551 mNotifies = nullptr;
552 if(mBuffer)
553 mBuffer->Release();
554 mBuffer = nullptr;
555 if(mPrimaryBuffer)
556 mPrimaryBuffer->Release();
557 mPrimaryBuffer = nullptr;
558 return false;
561 ResetEvent(mNotifyEvent);
562 SetDefaultWFXChannelOrder(mDevice);
564 return true;
567 bool DSoundPlayback::start()
569 try {
570 mKillNow.store(false, std::memory_order_release);
571 mThread = std::thread{std::mem_fn(&DSoundPlayback::mixerProc), this};
572 return true;
574 catch(std::exception& e) {
575 ERR("Failed to start mixing thread: %s\n", e.what());
577 catch(...) {
579 return false;
582 void DSoundPlayback::stop()
584 if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
585 return;
586 mThread.join();
588 mBuffer->Stop();
592 struct DSoundCapture final : public BackendBase {
593 DSoundCapture(ALCdevice *device) noexcept : BackendBase{device} { }
594 ~DSoundCapture() override;
596 void open(const ALCchar *name) override;
597 bool start() override;
598 void stop() override;
599 ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
600 ALCuint availableSamples() override;
602 IDirectSoundCapture *mDSC{nullptr};
603 IDirectSoundCaptureBuffer *mDSCbuffer{nullptr};
604 DWORD mBufferBytes{0u};
605 DWORD mCursor{0u};
607 RingBufferPtr mRing;
609 DEF_NEWDEL(DSoundCapture)
612 DSoundCapture::~DSoundCapture()
614 if(mDSCbuffer)
616 mDSCbuffer->Stop();
617 mDSCbuffer->Release();
618 mDSCbuffer = nullptr;
621 if(mDSC)
622 mDSC->Release();
623 mDSC = nullptr;
627 void DSoundCapture::open(const ALCchar *name)
629 HRESULT hr;
630 if(CaptureDevices.empty())
632 /* Initialize COM to prevent name truncation */
633 HRESULT hrcom{CoInitialize(nullptr)};
634 hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
635 if(FAILED(hr))
636 ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
637 if(SUCCEEDED(hrcom))
638 CoUninitialize();
641 const GUID *guid{nullptr};
642 if(!name && !CaptureDevices.empty())
644 name = CaptureDevices[0].name.c_str();
645 guid = &CaptureDevices[0].guid;
647 else
649 auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
650 [name](const DevMap &entry) -> bool
651 { return entry.name == name; }
653 if(iter == CaptureDevices.cend())
654 throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
655 guid = &iter->guid;
658 switch(mDevice->FmtType)
660 case DevFmtByte:
661 case DevFmtUShort:
662 case DevFmtUInt:
663 WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType));
664 throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported",
665 DevFmtTypeString(mDevice->FmtType)};
667 case DevFmtUByte:
668 case DevFmtShort:
669 case DevFmtInt:
670 case DevFmtFloat:
671 break;
674 WAVEFORMATEXTENSIBLE InputType{};
675 switch(mDevice->FmtChans)
677 case DevFmtMono:
678 InputType.dwChannelMask = SPEAKER_FRONT_CENTER;
679 break;
680 case DevFmtStereo:
681 InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
682 break;
683 case DevFmtQuad:
684 InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT |
685 SPEAKER_BACK_RIGHT;
686 break;
687 case DevFmtX51:
688 InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
689 SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT;
690 break;
691 case DevFmtX51Rear:
692 InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
693 SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
694 break;
695 case DevFmtX61:
696 InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
697 SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_CENTER | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT;
698 break;
699 case DevFmtX71:
700 InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
701 SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT |
702 SPEAKER_SIDE_RIGHT;
703 break;
704 case DevFmtAmbi3D:
705 WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
706 throw al::backend_exception{ALC_INVALID_VALUE, "%s capture not supported",
707 DevFmtChannelsString(mDevice->FmtChans)};
710 InputType.Format.wFormatTag = WAVE_FORMAT_PCM;
711 InputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
712 InputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
713 InputType.Format.nBlockAlign = static_cast<WORD>(InputType.Format.nChannels *
714 InputType.Format.wBitsPerSample / 8);
715 InputType.Format.nSamplesPerSec = mDevice->Frequency;
716 InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec *
717 InputType.Format.nBlockAlign;
718 InputType.Format.cbSize = 0;
719 InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample;
720 if(mDevice->FmtType == DevFmtFloat)
721 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
722 else
723 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
725 if(InputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
727 InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
728 InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
731 ALuint samples{mDevice->BufferSize};
732 samples = maxu(samples, 100 * mDevice->Frequency / 1000);
734 DSCBUFFERDESC DSCBDescription{};
735 DSCBDescription.dwSize = sizeof(DSCBDescription);
736 DSCBDescription.dwFlags = 0;
737 DSCBDescription.dwBufferBytes = samples * InputType.Format.nBlockAlign;
738 DSCBDescription.lpwfxFormat = &InputType.Format;
740 //DirectSoundCapture Init code
741 hr = DirectSoundCaptureCreate(guid, &mDSC, nullptr);
742 if(SUCCEEDED(hr))
743 mDSC->CreateCaptureBuffer(&DSCBDescription, &mDSCbuffer, nullptr);
744 if(SUCCEEDED(hr))
745 mRing = CreateRingBuffer(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
747 if(FAILED(hr))
749 mRing = nullptr;
750 if(mDSCbuffer)
751 mDSCbuffer->Release();
752 mDSCbuffer = nullptr;
753 if(mDSC)
754 mDSC->Release();
755 mDSC = nullptr;
757 throw al::backend_exception{ALC_INVALID_VALUE, "Device init failed: 0x%08lx", hr};
760 mBufferBytes = DSCBDescription.dwBufferBytes;
761 SetDefaultWFXChannelOrder(mDevice);
763 mDevice->DeviceName = name;
766 bool DSoundCapture::start()
768 HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)};
769 if(FAILED(hr))
771 ERR("start failed: 0x%08lx\n", hr);
772 aluHandleDisconnect(mDevice, "Failure starting capture: 0x%lx", hr);
773 return false;
775 return true;
778 void DSoundCapture::stop()
780 HRESULT hr{mDSCbuffer->Stop()};
781 if(FAILED(hr))
783 ERR("stop failed: 0x%08lx\n", hr);
784 aluHandleDisconnect(mDevice, "Failure stopping capture: 0x%lx", hr);
788 ALCenum DSoundCapture::captureSamples(al::byte *buffer, ALCuint samples)
790 mRing->read(buffer, samples);
791 return ALC_NO_ERROR;
794 ALCuint DSoundCapture::availableSamples()
796 if(!mDevice->Connected.load(std::memory_order_acquire))
797 return static_cast<ALCuint>(mRing->readSpace());
799 ALuint FrameSize{mDevice->frameSizeFromFmt()};
800 DWORD BufferBytes{mBufferBytes};
801 DWORD LastCursor{mCursor};
803 DWORD ReadCursor{};
804 void *ReadPtr1{}, *ReadPtr2{};
805 DWORD ReadCnt1{}, ReadCnt2{};
806 HRESULT hr{mDSCbuffer->GetCurrentPosition(nullptr, &ReadCursor)};
807 if(SUCCEEDED(hr))
809 DWORD NumBytes{(ReadCursor-LastCursor + BufferBytes) % BufferBytes};
810 if(!NumBytes) return static_cast<ALCubyte>(mRing->readSpace());
811 hr = mDSCbuffer->Lock(LastCursor, NumBytes, &ReadPtr1, &ReadCnt1, &ReadPtr2, &ReadCnt2, 0);
813 if(SUCCEEDED(hr))
815 mRing->write(ReadPtr1, ReadCnt1/FrameSize);
816 if(ReadPtr2 != nullptr && ReadCnt2 > 0)
817 mRing->write(ReadPtr2, ReadCnt2/FrameSize);
818 hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2);
819 mCursor = (LastCursor+ReadCnt1+ReadCnt2) % BufferBytes;
822 if(FAILED(hr))
824 ERR("update failed: 0x%08lx\n", hr);
825 aluHandleDisconnect(mDevice, "Failure retrieving capture data: 0x%lx", hr);
828 return static_cast<ALCuint>(mRing->readSpace());
831 } // namespace
834 BackendFactory &DSoundBackendFactory::getFactory()
836 static DSoundBackendFactory factory{};
837 return factory;
840 bool DSoundBackendFactory::init()
842 #ifdef HAVE_DYNLOAD
843 if(!ds_handle)
845 ds_handle = LoadLib("dsound.dll");
846 if(!ds_handle)
848 ERR("Failed to load dsound.dll\n");
849 return false;
852 #define LOAD_FUNC(f) do { \
853 p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(ds_handle, #f)); \
854 if(!p##f) \
856 CloseLib(ds_handle); \
857 ds_handle = nullptr; \
858 return false; \
860 } while(0)
861 LOAD_FUNC(DirectSoundCreate);
862 LOAD_FUNC(DirectSoundEnumerateW);
863 LOAD_FUNC(DirectSoundCaptureCreate);
864 LOAD_FUNC(DirectSoundCaptureEnumerateW);
865 #undef LOAD_FUNC
867 #endif
868 return true;
871 bool DSoundBackendFactory::querySupport(BackendType type)
872 { return (type == BackendType::Playback || type == BackendType::Capture); }
874 void DSoundBackendFactory::probe(DevProbe type, std::string *outnames)
876 auto add_device = [outnames](const DevMap &entry) -> void
878 /* +1 to also append the null char (to ensure a null-separated list and
879 * double-null terminated list).
881 outnames->append(entry.name.c_str(), entry.name.length()+1);
884 /* Initialize COM to prevent name truncation */
885 HRESULT hr;
886 HRESULT hrcom{CoInitialize(nullptr)};
887 switch(type)
889 case DevProbe::Playback:
890 PlaybackDevices.clear();
891 hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
892 if(FAILED(hr))
893 ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr);
894 std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
895 break;
897 case DevProbe::Capture:
898 CaptureDevices.clear();
899 hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
900 if(FAILED(hr))
901 ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr);
902 std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
903 break;
905 if(SUCCEEDED(hrcom))
906 CoUninitialize();
909 BackendPtr DSoundBackendFactory::createBackend(ALCdevice *device, BackendType type)
911 if(type == BackendType::Playback)
912 return BackendPtr{new DSoundPlayback{device}};
913 if(type == BackendType::Capture)
914 return BackendPtr{new DSoundCapture{device}};
915 return nullptr;