Rename some struct members
[openal-soft.git] / Alc / backends / dsound.cpp
blob3bce5cad1bd73186e32785922038d45852bb03cf
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 #include <stdlib.h>
26 #include <stdio.h>
27 #include <memory.h>
29 #include <cguid.h>
30 #include <mmreg.h>
31 #ifndef _WAVEFORMATEXTENSIBLE_
32 #include <ks.h>
33 #include <ksmedia.h>
34 #endif
36 #include <atomic>
37 #include <thread>
38 #include <string>
39 #include <vector>
40 #include <algorithm>
42 #include "alMain.h"
43 #include "alu.h"
44 #include "ringbuffer.h"
45 #include "compat.h"
47 /* MinGW-w64 needs this for some unknown reason now. */
48 typedef const WAVEFORMATEX *LPCWAVEFORMATEX;
49 #include <dsound.h>
52 #ifndef DSSPEAKER_5POINT1
53 # define DSSPEAKER_5POINT1 0x00000006
54 #endif
55 #ifndef DSSPEAKER_5POINT1_BACK
56 # define DSSPEAKER_5POINT1_BACK 0x00000006
57 #endif
58 #ifndef DSSPEAKER_7POINT1
59 # define DSSPEAKER_7POINT1 0x00000007
60 #endif
61 #ifndef DSSPEAKER_7POINT1_SURROUND
62 # define DSSPEAKER_7POINT1_SURROUND 0x00000008
63 #endif
64 #ifndef DSSPEAKER_5POINT1_SURROUND
65 # define DSSPEAKER_5POINT1_SURROUND 0x00000009
66 #endif
69 /* Some headers seem to define these as macros for __uuidof, which is annoying
70 * since some headers don't declare them at all. Hopefully the ifdef is enough
71 * to tell if they need to be declared.
73 #ifndef KSDATAFORMAT_SUBTYPE_PCM
74 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
75 #endif
76 #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
77 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
78 #endif
80 namespace {
82 #define DEVNAME_HEAD "OpenAL Soft on "
85 #ifdef HAVE_DYNLOAD
86 void *ds_handle;
87 HRESULT (WINAPI *pDirectSoundCreate)(const GUID *pcGuidDevice, IDirectSound **ppDS, IUnknown *pUnkOuter);
88 HRESULT (WINAPI *pDirectSoundEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext);
89 HRESULT (WINAPI *pDirectSoundCaptureCreate)(const GUID *pcGuidDevice, IDirectSoundCapture **ppDSC, IUnknown *pUnkOuter);
90 HRESULT (WINAPI *pDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext);
92 #ifndef IN_IDE_PARSER
93 #define DirectSoundCreate pDirectSoundCreate
94 #define DirectSoundEnumerateW pDirectSoundEnumerateW
95 #define DirectSoundCaptureCreate pDirectSoundCaptureCreate
96 #define DirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW
97 #endif
98 #endif
101 bool DSoundLoad(void)
103 #ifdef HAVE_DYNLOAD
104 if(!ds_handle)
106 ds_handle = LoadLib("dsound.dll");
107 if(!ds_handle)
109 ERR("Failed to load dsound.dll\n");
110 return false;
113 #define LOAD_FUNC(f) do { \
114 p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(ds_handle, #f)); \
115 if(!p##f) \
117 CloseLib(ds_handle); \
118 ds_handle = nullptr; \
119 return false; \
121 } while(0)
122 LOAD_FUNC(DirectSoundCreate);
123 LOAD_FUNC(DirectSoundEnumerateW);
124 LOAD_FUNC(DirectSoundCaptureCreate);
125 LOAD_FUNC(DirectSoundCaptureEnumerateW);
126 #undef LOAD_FUNC
128 #endif
129 return true;
133 #define MAX_UPDATES 128
135 struct DevMap {
136 std::string name;
137 GUID guid;
139 template<typename T0, typename T1>
140 DevMap(T0&& name_, T1&& guid_)
141 : name{std::forward<T0>(name_)}, guid{std::forward<T1>(guid_)}
145 al::vector<DevMap> PlaybackDevices;
146 al::vector<DevMap> CaptureDevices;
148 bool checkName(const al::vector<DevMap> &list, const std::string &name)
150 return std::find_if(list.cbegin(), list.cend(),
151 [&name](const DevMap &entry) -> bool
152 { return entry.name == name; }
153 ) != list.cend();
156 BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR* UNUSED(drvname), void *data)
158 if(!guid)
159 return TRUE;
161 auto& devices = *reinterpret_cast<al::vector<DevMap>*>(data);
162 const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)};
164 int count{1};
165 std::string newname{basename};
166 while(checkName(devices, newname))
168 newname = basename;
169 newname += " #";
170 newname += std::to_string(++count);
172 devices.emplace_back(std::move(newname), *guid);
173 const DevMap &newentry = devices.back();
175 OLECHAR *guidstr{nullptr};
176 HRESULT hr{StringFromCLSID(*guid, &guidstr)};
177 if(SUCCEEDED(hr))
179 TRACE("Got device \"%s\", GUID \"%ls\"\n", newentry.name.c_str(), guidstr);
180 CoTaskMemFree(guidstr);
183 return TRUE;
187 struct ALCdsoundPlayback final : public ALCbackend {
188 IDirectSound *DS{nullptr};
189 IDirectSoundBuffer *PrimaryBuffer{nullptr};
190 IDirectSoundBuffer *Buffer{nullptr};
191 IDirectSoundNotify *Notifies{nullptr};
192 HANDLE NotifyEvent{nullptr};
194 std::atomic<ALenum> mKillNow{AL_TRUE};
195 std::thread mThread;
198 int ALCdsoundPlayback_mixerProc(ALCdsoundPlayback *self);
200 void ALCdsoundPlayback_Construct(ALCdsoundPlayback *self, ALCdevice *device);
201 void ALCdsoundPlayback_Destruct(ALCdsoundPlayback *self);
202 ALCenum ALCdsoundPlayback_open(ALCdsoundPlayback *self, const ALCchar *name);
203 ALCboolean ALCdsoundPlayback_reset(ALCdsoundPlayback *self);
204 ALCboolean ALCdsoundPlayback_start(ALCdsoundPlayback *self);
205 void ALCdsoundPlayback_stop(ALCdsoundPlayback *self);
206 DECLARE_FORWARD2(ALCdsoundPlayback, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
207 DECLARE_FORWARD(ALCdsoundPlayback, ALCbackend, ALCuint, availableSamples)
208 DECLARE_FORWARD(ALCdsoundPlayback, ALCbackend, ClockLatency, getClockLatency)
209 DECLARE_FORWARD(ALCdsoundPlayback, ALCbackend, void, lock)
210 DECLARE_FORWARD(ALCdsoundPlayback, ALCbackend, void, unlock)
211 DECLARE_DEFAULT_ALLOCATORS(ALCdsoundPlayback)
213 DEFINE_ALCBACKEND_VTABLE(ALCdsoundPlayback);
216 void ALCdsoundPlayback_Construct(ALCdsoundPlayback *self, ALCdevice *device)
218 new (self) ALCdsoundPlayback{};
219 ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
220 SET_VTABLE2(ALCdsoundPlayback, ALCbackend, self);
223 void ALCdsoundPlayback_Destruct(ALCdsoundPlayback *self)
225 if(self->Notifies)
226 self->Notifies->Release();
227 self->Notifies = nullptr;
228 if(self->Buffer)
229 self->Buffer->Release();
230 self->Buffer = nullptr;
231 if(self->PrimaryBuffer)
232 self->PrimaryBuffer->Release();
233 self->PrimaryBuffer = nullptr;
235 if(self->DS)
236 self->DS->Release();
237 self->DS = nullptr;
238 if(self->NotifyEvent)
239 CloseHandle(self->NotifyEvent);
240 self->NotifyEvent = nullptr;
242 ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
243 self->~ALCdsoundPlayback();
247 FORCE_ALIGN int ALCdsoundPlayback_mixerProc(ALCdsoundPlayback *self)
249 ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
251 SetRTPriority();
252 althrd_setname(MIXER_THREAD_NAME);
254 IDirectSoundBuffer *const Buffer{self->Buffer};
256 DSBCAPS DSBCaps{};
257 DSBCaps.dwSize = sizeof(DSBCaps);
258 HRESULT err{Buffer->GetCaps(&DSBCaps)};
259 if(FAILED(err))
261 ERR("Failed to get buffer caps: 0x%lx\n", err);
262 ALCdsoundPlayback_lock(self);
263 aluHandleDisconnect(device, "Failure retrieving playback buffer info: 0x%lx", err);
264 ALCdsoundPlayback_unlock(self);
265 return 1;
268 ALsizei FrameSize{FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->mAmbiOrder)};
269 DWORD FragSize{device->UpdateSize * FrameSize};
271 bool Playing{false};
272 DWORD LastCursor{0u};
273 Buffer->GetCurrentPosition(&LastCursor, nullptr);
274 while(!self->mKillNow.load(std::memory_order_acquire) &&
275 ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
277 // Get current play cursor
278 DWORD PlayCursor;
279 Buffer->GetCurrentPosition(&PlayCursor, nullptr);
280 DWORD avail = (PlayCursor-LastCursor+DSBCaps.dwBufferBytes) % DSBCaps.dwBufferBytes;
282 if(avail < FragSize)
284 if(!Playing)
286 err = Buffer->Play(0, 0, DSBPLAY_LOOPING);
287 if(FAILED(err))
289 ERR("Failed to play buffer: 0x%lx\n", err);
290 ALCdsoundPlayback_lock(self);
291 aluHandleDisconnect(device, "Failure starting playback: 0x%lx", err);
292 ALCdsoundPlayback_unlock(self);
293 return 1;
295 Playing = true;
298 avail = WaitForSingleObjectEx(self->NotifyEvent, 2000, FALSE);
299 if(avail != WAIT_OBJECT_0)
300 ERR("WaitForSingleObjectEx error: 0x%lx\n", avail);
301 continue;
303 avail -= avail%FragSize;
305 // Lock output buffer
306 void *WritePtr1, *WritePtr2;
307 DWORD WriteCnt1{0u}, WriteCnt2{0u};
308 err = Buffer->Lock(LastCursor, avail, &WritePtr1, &WriteCnt1, &WritePtr2, &WriteCnt2, 0);
310 // If the buffer is lost, restore it and lock
311 if(err == DSERR_BUFFERLOST)
313 WARN("Buffer lost, restoring...\n");
314 err = Buffer->Restore();
315 if(SUCCEEDED(err))
317 Playing = false;
318 LastCursor = 0;
319 err = Buffer->Lock(0, DSBCaps.dwBufferBytes, &WritePtr1, &WriteCnt1,
320 &WritePtr2, &WriteCnt2, 0);
324 // Successfully locked the output buffer
325 if(SUCCEEDED(err))
327 // If we have an active context, mix data directly into output buffer otherwise fill with silence
328 ALCdsoundPlayback_lock(self);
329 aluMixData(device, WritePtr1, WriteCnt1/FrameSize);
330 aluMixData(device, WritePtr2, WriteCnt2/FrameSize);
331 ALCdsoundPlayback_unlock(self);
333 // Unlock output buffer only when successfully locked
334 Buffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2);
336 else
338 ERR("Buffer lock error: %#lx\n", err);
339 ALCdsoundPlayback_lock(self);
340 aluHandleDisconnect(device, "Failed to lock output buffer: 0x%lx", err);
341 ALCdsoundPlayback_unlock(self);
342 return 1;
345 // Update old write cursor location
346 LastCursor += WriteCnt1+WriteCnt2;
347 LastCursor %= DSBCaps.dwBufferBytes;
350 return 0;
353 ALCenum ALCdsoundPlayback_open(ALCdsoundPlayback *self, const ALCchar *deviceName)
355 ALCdevice *device{STATIC_CAST(ALCbackend, self)->mDevice};
357 HRESULT hr;
358 if(PlaybackDevices.empty())
360 /* Initialize COM to prevent name truncation */
361 HRESULT hrcom{CoInitialize(nullptr)};
362 hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
363 if(FAILED(hr))
364 ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
365 if(SUCCEEDED(hrcom))
366 CoUninitialize();
369 const GUID *guid{nullptr};
370 if(!deviceName && !PlaybackDevices.empty())
372 deviceName = PlaybackDevices[0].name.c_str();
373 guid = &PlaybackDevices[0].guid;
375 else
377 auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
378 [deviceName](const DevMap &entry) -> bool
379 { return entry.name == deviceName; }
381 if(iter == PlaybackDevices.cend())
382 return ALC_INVALID_VALUE;
383 guid = &iter->guid;
386 hr = DS_OK;
387 self->NotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
388 if(!self->NotifyEvent) hr = E_FAIL;
390 //DirectSound Init code
391 if(SUCCEEDED(hr))
392 hr = DirectSoundCreate(guid, &self->DS, nullptr);
393 if(SUCCEEDED(hr))
394 hr = self->DS->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
395 if(FAILED(hr))
397 ERR("Device init failed: 0x%08lx\n", hr);
398 return ALC_INVALID_VALUE;
401 device->DeviceName = deviceName;
402 return ALC_NO_ERROR;
405 ALCboolean ALCdsoundPlayback_reset(ALCdsoundPlayback *self)
407 ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
409 if(self->Notifies)
410 self->Notifies->Release();
411 self->Notifies = nullptr;
412 if(self->Buffer)
413 self->Buffer->Release();
414 self->Buffer = nullptr;
415 if(self->PrimaryBuffer)
416 self->PrimaryBuffer->Release();
417 self->PrimaryBuffer = nullptr;
419 switch(device->FmtType)
421 case DevFmtByte:
422 device->FmtType = DevFmtUByte;
423 break;
424 case DevFmtFloat:
425 if((device->Flags&DEVICE_SAMPLE_TYPE_REQUEST))
426 break;
427 /* fall-through */
428 case DevFmtUShort:
429 device->FmtType = DevFmtShort;
430 break;
431 case DevFmtUInt:
432 device->FmtType = DevFmtInt;
433 break;
434 case DevFmtUByte:
435 case DevFmtShort:
436 case DevFmtInt:
437 break;
440 WAVEFORMATEXTENSIBLE OutputType{};
441 DWORD speakers;
442 HRESULT hr{self->DS->GetSpeakerConfig(&speakers)};
443 if(SUCCEEDED(hr))
445 speakers = DSSPEAKER_CONFIG(speakers);
446 if(!(device->Flags&DEVICE_CHANNELS_REQUEST))
448 if(speakers == DSSPEAKER_MONO)
449 device->FmtChans = DevFmtMono;
450 else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE)
451 device->FmtChans = DevFmtStereo;
452 else if(speakers == DSSPEAKER_QUAD)
453 device->FmtChans = DevFmtQuad;
454 else if(speakers == DSSPEAKER_5POINT1_SURROUND)
455 device->FmtChans = DevFmtX51;
456 else if(speakers == DSSPEAKER_5POINT1_BACK)
457 device->FmtChans = DevFmtX51Rear;
458 else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
459 device->FmtChans = DevFmtX71;
460 else
461 ERR("Unknown system speaker config: 0x%lx\n", speakers);
463 device->IsHeadphones = (device->FmtChans == DevFmtStereo &&
464 speakers == DSSPEAKER_HEADPHONE);
466 switch(device->FmtChans)
468 case DevFmtMono:
469 OutputType.dwChannelMask = SPEAKER_FRONT_CENTER;
470 break;
471 case DevFmtAmbi3D:
472 device->FmtChans = DevFmtStereo;
473 /*fall-through*/
474 case DevFmtStereo:
475 OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
476 SPEAKER_FRONT_RIGHT;
477 break;
478 case DevFmtQuad:
479 OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
480 SPEAKER_FRONT_RIGHT |
481 SPEAKER_BACK_LEFT |
482 SPEAKER_BACK_RIGHT;
483 break;
484 case DevFmtX51:
485 OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
486 SPEAKER_FRONT_RIGHT |
487 SPEAKER_FRONT_CENTER |
488 SPEAKER_LOW_FREQUENCY |
489 SPEAKER_SIDE_LEFT |
490 SPEAKER_SIDE_RIGHT;
491 break;
492 case DevFmtX51Rear:
493 OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
494 SPEAKER_FRONT_RIGHT |
495 SPEAKER_FRONT_CENTER |
496 SPEAKER_LOW_FREQUENCY |
497 SPEAKER_BACK_LEFT |
498 SPEAKER_BACK_RIGHT;
499 break;
500 case DevFmtX61:
501 OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
502 SPEAKER_FRONT_RIGHT |
503 SPEAKER_FRONT_CENTER |
504 SPEAKER_LOW_FREQUENCY |
505 SPEAKER_BACK_CENTER |
506 SPEAKER_SIDE_LEFT |
507 SPEAKER_SIDE_RIGHT;
508 break;
509 case DevFmtX71:
510 OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
511 SPEAKER_FRONT_RIGHT |
512 SPEAKER_FRONT_CENTER |
513 SPEAKER_LOW_FREQUENCY |
514 SPEAKER_BACK_LEFT |
515 SPEAKER_BACK_RIGHT |
516 SPEAKER_SIDE_LEFT |
517 SPEAKER_SIDE_RIGHT;
518 break;
521 retry_open:
522 hr = S_OK;
523 OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
524 OutputType.Format.nChannels = ChannelsFromDevFmt(device->FmtChans, device->mAmbiOrder);
525 OutputType.Format.wBitsPerSample = BytesFromDevFmt(device->FmtType) * 8;
526 OutputType.Format.nBlockAlign = OutputType.Format.nChannels*OutputType.Format.wBitsPerSample/8;
527 OutputType.Format.nSamplesPerSec = device->Frequency;
528 OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec*OutputType.Format.nBlockAlign;
529 OutputType.Format.cbSize = 0;
532 if(OutputType.Format.nChannels > 2 || device->FmtType == DevFmtFloat)
534 OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
535 OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
536 OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
537 if(device->FmtType == DevFmtFloat)
538 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
539 else
540 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
542 if(self->PrimaryBuffer)
543 self->PrimaryBuffer->Release();
544 self->PrimaryBuffer = nullptr;
546 else
548 if(SUCCEEDED(hr) && !self->PrimaryBuffer)
550 DSBUFFERDESC DSBDescription{};
551 DSBDescription.dwSize = sizeof(DSBDescription);
552 DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
553 hr = self->DS->CreateSoundBuffer(&DSBDescription, &self->PrimaryBuffer, nullptr);
555 if(SUCCEEDED(hr))
556 hr = self->PrimaryBuffer->SetFormat(&OutputType.Format);
559 if(SUCCEEDED(hr))
561 if(device->NumUpdates > MAX_UPDATES)
563 device->UpdateSize = (device->UpdateSize*device->NumUpdates +
564 MAX_UPDATES-1) / MAX_UPDATES;
565 device->NumUpdates = MAX_UPDATES;
568 DSBUFFERDESC DSBDescription{};
569 DSBDescription.dwSize = sizeof(DSBDescription);
570 DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 |
571 DSBCAPS_GLOBALFOCUS;
572 DSBDescription.dwBufferBytes = device->UpdateSize * device->NumUpdates *
573 OutputType.Format.nBlockAlign;
574 DSBDescription.lpwfxFormat = &OutputType.Format;
576 hr = self->DS->CreateSoundBuffer(&DSBDescription, &self->Buffer, nullptr);
577 if(FAILED(hr) && device->FmtType == DevFmtFloat)
579 device->FmtType = DevFmtShort;
580 goto retry_open;
584 if(SUCCEEDED(hr))
586 void *ptr;
587 hr = self->Buffer->QueryInterface(IID_IDirectSoundNotify, &ptr);
588 if(SUCCEEDED(hr))
590 auto Notifies = reinterpret_cast<IDirectSoundNotify*>(ptr);
591 self->Notifies = Notifies;
593 device->NumUpdates = minu(device->NumUpdates, MAX_UPDATES);
595 std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots;
596 for(ALuint i{0};i < device->NumUpdates;++i)
598 nots[i].dwOffset = i * device->UpdateSize * OutputType.Format.nBlockAlign;
599 nots[i].hEventNotify = self->NotifyEvent;
601 if(Notifies->SetNotificationPositions(device->NumUpdates, nots.data()) != DS_OK)
602 hr = E_FAIL;
606 if(FAILED(hr))
608 if(self->Notifies)
609 self->Notifies->Release();
610 self->Notifies = nullptr;
611 if(self->Buffer)
612 self->Buffer->Release();
613 self->Buffer = nullptr;
614 if(self->PrimaryBuffer)
615 self->PrimaryBuffer->Release();
616 self->PrimaryBuffer = nullptr;
617 return ALC_FALSE;
620 ResetEvent(self->NotifyEvent);
621 SetDefaultWFXChannelOrder(device);
623 return ALC_TRUE;
626 ALCboolean ALCdsoundPlayback_start(ALCdsoundPlayback *self)
628 try {
629 self->mKillNow.store(AL_FALSE, std::memory_order_release);
630 self->mThread = std::thread(ALCdsoundPlayback_mixerProc, self);
631 return ALC_TRUE;
633 catch(std::exception& e) {
634 ERR("Failed to start mixing thread: %s\n", e.what());
636 catch(...) {
638 return ALC_FALSE;
641 void ALCdsoundPlayback_stop(ALCdsoundPlayback *self)
643 if(self->mKillNow.exchange(AL_TRUE, std::memory_order_acq_rel) || !self->mThread.joinable())
644 return;
646 self->mThread.join();
648 self->Buffer->Stop();
652 struct ALCdsoundCapture final : public ALCbackend {
653 IDirectSoundCapture *DSC{nullptr};
654 IDirectSoundCaptureBuffer *DSCbuffer{nullptr};
655 DWORD BufferBytes{0u};
656 DWORD Cursor{0u};
658 ll_ringbuffer_t *Ring{nullptr};
661 void ALCdsoundCapture_Construct(ALCdsoundCapture *self, ALCdevice *device);
662 void ALCdsoundCapture_Destruct(ALCdsoundCapture *self);
663 ALCenum ALCdsoundCapture_open(ALCdsoundCapture *self, const ALCchar *name);
664 DECLARE_FORWARD(ALCdsoundCapture, ALCbackend, ALCboolean, reset)
665 ALCboolean ALCdsoundCapture_start(ALCdsoundCapture *self);
666 void ALCdsoundCapture_stop(ALCdsoundCapture *self);
667 ALCenum ALCdsoundCapture_captureSamples(ALCdsoundCapture *self, ALCvoid *buffer, ALCuint samples);
668 ALCuint ALCdsoundCapture_availableSamples(ALCdsoundCapture *self);
669 DECLARE_FORWARD(ALCdsoundCapture, ALCbackend, ClockLatency, getClockLatency)
670 DECLARE_FORWARD(ALCdsoundCapture, ALCbackend, void, lock)
671 DECLARE_FORWARD(ALCdsoundCapture, ALCbackend, void, unlock)
672 DECLARE_DEFAULT_ALLOCATORS(ALCdsoundCapture)
673 DEFINE_ALCBACKEND_VTABLE(ALCdsoundCapture);
675 void ALCdsoundCapture_Construct(ALCdsoundCapture *self, ALCdevice *device)
677 new (self) ALCdsoundCapture{};
678 ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
679 SET_VTABLE2(ALCdsoundCapture, ALCbackend, self);
682 void ALCdsoundCapture_Destruct(ALCdsoundCapture *self)
684 ll_ringbuffer_free(self->Ring);
685 self->Ring = nullptr;
687 if(self->DSCbuffer)
689 self->DSCbuffer->Stop();
690 self->DSCbuffer->Release();
691 self->DSCbuffer = nullptr;
694 if(self->DSC)
695 self->DSC->Release();
696 self->DSC = nullptr;
698 ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
699 self->~ALCdsoundCapture();
703 ALCenum ALCdsoundCapture_open(ALCdsoundCapture *self, const ALCchar *deviceName)
705 ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
707 HRESULT hr;
708 if(CaptureDevices.empty())
710 /* Initialize COM to prevent name truncation */
711 HRESULT hrcom{CoInitialize(nullptr)};
712 hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
713 if(FAILED(hr))
714 ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
715 if(SUCCEEDED(hrcom))
716 CoUninitialize();
719 const GUID *guid{nullptr};
720 if(!deviceName && !CaptureDevices.empty())
722 deviceName = CaptureDevices[0].name.c_str();
723 guid = &CaptureDevices[0].guid;
725 else
727 auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
728 [deviceName](const DevMap &entry) -> bool
729 { return entry.name == deviceName; }
731 if(iter == CaptureDevices.cend())
732 return ALC_INVALID_VALUE;
733 guid = &iter->guid;
736 switch(device->FmtType)
738 case DevFmtByte:
739 case DevFmtUShort:
740 case DevFmtUInt:
741 WARN("%s capture samples not supported\n", DevFmtTypeString(device->FmtType));
742 return ALC_INVALID_ENUM;
744 case DevFmtUByte:
745 case DevFmtShort:
746 case DevFmtInt:
747 case DevFmtFloat:
748 break;
751 WAVEFORMATEXTENSIBLE InputType{};
752 switch(device->FmtChans)
754 case DevFmtMono:
755 InputType.dwChannelMask = SPEAKER_FRONT_CENTER;
756 break;
757 case DevFmtStereo:
758 InputType.dwChannelMask = SPEAKER_FRONT_LEFT |
759 SPEAKER_FRONT_RIGHT;
760 break;
761 case DevFmtQuad:
762 InputType.dwChannelMask = SPEAKER_FRONT_LEFT |
763 SPEAKER_FRONT_RIGHT |
764 SPEAKER_BACK_LEFT |
765 SPEAKER_BACK_RIGHT;
766 break;
767 case DevFmtX51:
768 InputType.dwChannelMask = SPEAKER_FRONT_LEFT |
769 SPEAKER_FRONT_RIGHT |
770 SPEAKER_FRONT_CENTER |
771 SPEAKER_LOW_FREQUENCY |
772 SPEAKER_SIDE_LEFT |
773 SPEAKER_SIDE_RIGHT;
774 break;
775 case DevFmtX51Rear:
776 InputType.dwChannelMask = SPEAKER_FRONT_LEFT |
777 SPEAKER_FRONT_RIGHT |
778 SPEAKER_FRONT_CENTER |
779 SPEAKER_LOW_FREQUENCY |
780 SPEAKER_BACK_LEFT |
781 SPEAKER_BACK_RIGHT;
782 break;
783 case DevFmtX61:
784 InputType.dwChannelMask = SPEAKER_FRONT_LEFT |
785 SPEAKER_FRONT_RIGHT |
786 SPEAKER_FRONT_CENTER |
787 SPEAKER_LOW_FREQUENCY |
788 SPEAKER_BACK_CENTER |
789 SPEAKER_SIDE_LEFT |
790 SPEAKER_SIDE_RIGHT;
791 break;
792 case DevFmtX71:
793 InputType.dwChannelMask = SPEAKER_FRONT_LEFT |
794 SPEAKER_FRONT_RIGHT |
795 SPEAKER_FRONT_CENTER |
796 SPEAKER_LOW_FREQUENCY |
797 SPEAKER_BACK_LEFT |
798 SPEAKER_BACK_RIGHT |
799 SPEAKER_SIDE_LEFT |
800 SPEAKER_SIDE_RIGHT;
801 break;
802 case DevFmtAmbi3D:
803 WARN("%s capture not supported\n", DevFmtChannelsString(device->FmtChans));
804 return ALC_INVALID_ENUM;
807 InputType.Format.wFormatTag = WAVE_FORMAT_PCM;
808 InputType.Format.nChannels = ChannelsFromDevFmt(device->FmtChans, device->mAmbiOrder);
809 InputType.Format.wBitsPerSample = BytesFromDevFmt(device->FmtType) * 8;
810 InputType.Format.nBlockAlign = InputType.Format.nChannels*InputType.Format.wBitsPerSample/8;
811 InputType.Format.nSamplesPerSec = device->Frequency;
812 InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec*InputType.Format.nBlockAlign;
813 InputType.Format.cbSize = 0;
814 InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample;
815 if(device->FmtType == DevFmtFloat)
816 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
817 else
818 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
820 if(InputType.Format.nChannels > 2 || device->FmtType == DevFmtFloat)
822 InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
823 InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
826 ALuint samples{device->UpdateSize * device->NumUpdates};
827 samples = maxu(samples, 100 * device->Frequency / 1000);
829 DSCBUFFERDESC DSCBDescription{};
830 DSCBDescription.dwSize = sizeof(DSCBDescription);
831 DSCBDescription.dwFlags = 0;
832 DSCBDescription.dwBufferBytes = samples * InputType.Format.nBlockAlign;
833 DSCBDescription.lpwfxFormat = &InputType.Format;
835 //DirectSoundCapture Init code
836 hr = DirectSoundCaptureCreate(guid, &self->DSC, nullptr);
837 if(SUCCEEDED(hr))
838 self->DSC->CreateCaptureBuffer(&DSCBDescription, &self->DSCbuffer, nullptr);
839 if(SUCCEEDED(hr))
841 self->Ring = ll_ringbuffer_create(device->UpdateSize*device->NumUpdates,
842 InputType.Format.nBlockAlign, false);
843 if(!self->Ring) hr = DSERR_OUTOFMEMORY;
846 if(FAILED(hr))
848 ERR("Device init failed: 0x%08lx\n", hr);
850 ll_ringbuffer_free(self->Ring);
851 self->Ring = nullptr;
852 if(self->DSCbuffer)
853 self->DSCbuffer->Release();
854 self->DSCbuffer = nullptr;
855 if(self->DSC)
856 self->DSC->Release();
857 self->DSC = nullptr;
859 return ALC_INVALID_VALUE;
862 self->BufferBytes = DSCBDescription.dwBufferBytes;
863 SetDefaultWFXChannelOrder(device);
865 device->DeviceName = deviceName;
866 return ALC_NO_ERROR;
869 ALCboolean ALCdsoundCapture_start(ALCdsoundCapture *self)
871 HRESULT hr{self->DSCbuffer->Start(DSCBSTART_LOOPING)};
872 if(FAILED(hr))
874 ERR("start failed: 0x%08lx\n", hr);
875 aluHandleDisconnect(STATIC_CAST(ALCbackend, self)->mDevice,
876 "Failure starting capture: 0x%lx", hr);
877 return ALC_FALSE;
880 return ALC_TRUE;
883 void ALCdsoundCapture_stop(ALCdsoundCapture *self)
885 HRESULT hr{self->DSCbuffer->Stop()};
886 if(FAILED(hr))
888 ERR("stop failed: 0x%08lx\n", hr);
889 aluHandleDisconnect(STATIC_CAST(ALCbackend, self)->mDevice,
890 "Failure stopping capture: 0x%lx", hr);
894 ALCenum ALCdsoundCapture_captureSamples(ALCdsoundCapture *self, ALCvoid *buffer, ALCuint samples)
896 ll_ringbuffer_read(self->Ring, buffer, samples);
897 return ALC_NO_ERROR;
900 ALCuint ALCdsoundCapture_availableSamples(ALCdsoundCapture *self)
902 ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
904 if(!ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
905 return ll_ringbuffer_read_space(self->Ring);
907 ALsizei FrameSize{FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->mAmbiOrder)};
908 DWORD BufferBytes{self->BufferBytes};
909 DWORD LastCursor{self->Cursor};
911 DWORD ReadCursor;
912 void *ReadPtr1, *ReadPtr2;
913 DWORD ReadCnt1, ReadCnt2;
914 HRESULT hr{self->DSCbuffer->GetCurrentPosition(nullptr, &ReadCursor)};
915 if(SUCCEEDED(hr))
917 DWORD NumBytes{(ReadCursor-LastCursor + BufferBytes) % BufferBytes};
918 if(!NumBytes) return ll_ringbuffer_read_space(self->Ring);
919 hr = self->DSCbuffer->Lock(LastCursor, NumBytes, &ReadPtr1, &ReadCnt1,
920 &ReadPtr2, &ReadCnt2, 0);
922 if(SUCCEEDED(hr))
924 ll_ringbuffer_write(self->Ring, ReadPtr1, ReadCnt1/FrameSize);
925 if(ReadPtr2 != nullptr)
926 ll_ringbuffer_write(self->Ring, ReadPtr2, ReadCnt2/FrameSize);
927 hr = self->DSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2);
928 self->Cursor = (LastCursor+ReadCnt1+ReadCnt2) % BufferBytes;
931 if(FAILED(hr))
933 ERR("update failed: 0x%08lx\n", hr);
934 aluHandleDisconnect(device, "Failure retrieving capture data: 0x%lx", hr);
937 return ll_ringbuffer_read_space(self->Ring);
940 } // namespace
943 BackendFactory &DSoundBackendFactory::getFactory()
945 static DSoundBackendFactory factory{};
946 return factory;
949 bool DSoundBackendFactory::init()
950 { return DSoundLoad(); }
952 void DSoundBackendFactory::deinit()
954 PlaybackDevices.clear();
955 CaptureDevices.clear();
957 #ifdef HAVE_DYNLOAD
958 if(ds_handle)
959 CloseLib(ds_handle);
960 ds_handle = nullptr;
961 #endif
964 bool DSoundBackendFactory::querySupport(ALCbackend_Type type)
965 { return (type == ALCbackend_Playback || type == ALCbackend_Capture); }
967 void DSoundBackendFactory::probe(enum DevProbe type, std::string *outnames)
969 auto add_device = [outnames](const DevMap &entry) -> void
971 /* +1 to also append the null char (to ensure a null-separated list and
972 * double-null terminated list).
974 outnames->append(entry.name.c_str(), entry.name.length()+1);
977 /* Initialize COM to prevent name truncation */
978 HRESULT hr;
979 HRESULT hrcom{CoInitialize(nullptr)};
980 switch(type)
982 case ALL_DEVICE_PROBE:
983 PlaybackDevices.clear();
984 hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
985 if(FAILED(hr))
986 ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr);
987 std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
988 break;
990 case CAPTURE_DEVICE_PROBE:
991 CaptureDevices.clear();
992 hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
993 if(FAILED(hr))
994 ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr);
995 std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
996 break;
998 if(SUCCEEDED(hrcom))
999 CoUninitialize();
1002 ALCbackend *DSoundBackendFactory::createBackend(ALCdevice *device, ALCbackend_Type type)
1004 if(type == ALCbackend_Playback)
1006 ALCdsoundPlayback *backend;
1007 NEW_OBJ(backend, ALCdsoundPlayback)(device);
1008 if(!backend) return nullptr;
1009 return STATIC_CAST(ALCbackend, backend);
1012 if(type == ALCbackend_Capture)
1014 ALCdsoundCapture *backend;
1015 NEW_OBJ(backend, ALCdsoundCapture)(device);
1016 if(!backend) return nullptr;
1017 return STATIC_CAST(ALCbackend, backend);
1020 return nullptr;