Limit convolution processing to the output ambisonic order
[openal-soft.git] / alc / backends / dsound.cpp
blobc19214302ae77b290d41bebad90d1947a1662ff5
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 "compat.h"
51 #include "dynload.h"
52 #include "logging.h"
53 #include "ringbuffer.h"
54 #include "strutils.h"
55 #include "threads.h"
57 /* MinGW-w64 needs this for some unknown reason now. */
58 using LPCWAVEFORMATEX = const WAVEFORMATEX*;
59 #include <dsound.h>
62 #ifndef DSSPEAKER_5POINT1
63 # define DSSPEAKER_5POINT1 0x00000006
64 #endif
65 #ifndef DSSPEAKER_5POINT1_BACK
66 # define DSSPEAKER_5POINT1_BACK 0x00000006
67 #endif
68 #ifndef DSSPEAKER_7POINT1
69 # define DSSPEAKER_7POINT1 0x00000007
70 #endif
71 #ifndef DSSPEAKER_7POINT1_SURROUND
72 # define DSSPEAKER_7POINT1_SURROUND 0x00000008
73 #endif
74 #ifndef DSSPEAKER_5POINT1_SURROUND
75 # define DSSPEAKER_5POINT1_SURROUND 0x00000009
76 #endif
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);
85 #endif
86 #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
87 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
88 #endif
90 namespace {
92 #define DEVNAME_HEAD "OpenAL Soft on "
95 #ifdef HAVE_DYNLOAD
96 void *ds_handle;
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
107 #endif
108 #endif
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)
119 #define MAX_UPDATES 128
121 struct DevMap {
122 std::string name;
123 GUID guid;
125 template<typename T0, typename T1>
126 DevMap(T0&& name_, T1&& guid_)
127 : name{std::forward<T0>(name_)}, guid{std::forward<T1>(guid_)}
131 al::vector<DevMap> PlaybackDevices;
132 al::vector<DevMap> CaptureDevices;
134 bool checkName(const al::vector<DevMap> &list, const std::string &name)
136 auto match_name = [&name](const DevMap &entry) -> bool
137 { return entry.name == name; };
138 return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
141 BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, void *data) noexcept
143 if(!guid)
144 return TRUE;
146 auto& devices = *static_cast<al::vector<DevMap>*>(data);
147 const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)};
149 int count{1};
150 std::string newname{basename};
151 while(checkName(devices, newname))
153 newname = basename;
154 newname += " #";
155 newname += std::to_string(++count);
157 devices.emplace_back(std::move(newname), *guid);
158 const DevMap &newentry = devices.back();
160 OLECHAR *guidstr{nullptr};
161 HRESULT hr{StringFromCLSID(*guid, &guidstr)};
162 if(SUCCEEDED(hr))
164 TRACE("Got device \"%s\", GUID \"%ls\"\n", newentry.name.c_str(), guidstr);
165 CoTaskMemFree(guidstr);
168 return TRUE;
172 struct DSoundPlayback final : public BackendBase {
173 DSoundPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
174 ~DSoundPlayback() override;
176 int mixerProc();
178 void open(const ALCchar *name) override;
179 bool reset() override;
180 void start() override;
181 void stop() override;
183 IDirectSound *mDS{nullptr};
184 IDirectSoundBuffer *mPrimaryBuffer{nullptr};
185 IDirectSoundBuffer *mBuffer{nullptr};
186 IDirectSoundNotify *mNotifies{nullptr};
187 HANDLE mNotifyEvent{nullptr};
189 std::atomic<bool> mKillNow{true};
190 std::thread mThread;
192 DEF_NEWDEL(DSoundPlayback)
195 DSoundPlayback::~DSoundPlayback()
197 if(mNotifies)
198 mNotifies->Release();
199 mNotifies = nullptr;
200 if(mBuffer)
201 mBuffer->Release();
202 mBuffer = nullptr;
203 if(mPrimaryBuffer)
204 mPrimaryBuffer->Release();
205 mPrimaryBuffer = nullptr;
207 if(mDS)
208 mDS->Release();
209 mDS = nullptr;
210 if(mNotifyEvent)
211 CloseHandle(mNotifyEvent);
212 mNotifyEvent = nullptr;
216 FORCE_ALIGN int DSoundPlayback::mixerProc()
218 SetRTPriority();
219 althrd_setname(MIXER_THREAD_NAME);
221 DSBCAPS DSBCaps{};
222 DSBCaps.dwSize = sizeof(DSBCaps);
223 HRESULT err{mBuffer->GetCaps(&DSBCaps)};
224 if(FAILED(err))
226 ERR("Failed to get buffer caps: 0x%lx\n", err);
227 mDevice->handleDisconnect("Failure retrieving playback buffer info: 0x%lx", err);
228 return 1;
231 const size_t FrameStep{mDevice->channelsFromFmt()};
232 ALuint FrameSize{mDevice->frameSizeFromFmt()};
233 DWORD FragSize{mDevice->UpdateSize * FrameSize};
235 bool Playing{false};
236 DWORD LastCursor{0u};
237 mBuffer->GetCurrentPosition(&LastCursor, nullptr);
238 while(!mKillNow.load(std::memory_order_acquire) &&
239 mDevice->Connected.load(std::memory_order_acquire))
241 // Get current play cursor
242 DWORD PlayCursor;
243 mBuffer->GetCurrentPosition(&PlayCursor, nullptr);
244 DWORD avail = (PlayCursor-LastCursor+DSBCaps.dwBufferBytes) % DSBCaps.dwBufferBytes;
246 if(avail < FragSize)
248 if(!Playing)
250 err = mBuffer->Play(0, 0, DSBPLAY_LOOPING);
251 if(FAILED(err))
253 ERR("Failed to play buffer: 0x%lx\n", err);
254 mDevice->handleDisconnect("Failure starting playback: 0x%lx", err);
255 return 1;
257 Playing = true;
260 avail = WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE);
261 if(avail != WAIT_OBJECT_0)
262 ERR("WaitForSingleObjectEx error: 0x%lx\n", avail);
263 continue;
265 avail -= avail%FragSize;
267 // Lock output buffer
268 void *WritePtr1, *WritePtr2;
269 DWORD WriteCnt1{0u}, WriteCnt2{0u};
270 err = mBuffer->Lock(LastCursor, avail, &WritePtr1, &WriteCnt1, &WritePtr2, &WriteCnt2, 0);
272 // If the buffer is lost, restore it and lock
273 if(err == DSERR_BUFFERLOST)
275 WARN("Buffer lost, restoring...\n");
276 err = mBuffer->Restore();
277 if(SUCCEEDED(err))
279 Playing = false;
280 LastCursor = 0;
281 err = mBuffer->Lock(0, DSBCaps.dwBufferBytes, &WritePtr1, &WriteCnt1,
282 &WritePtr2, &WriteCnt2, 0);
286 if(SUCCEEDED(err))
288 mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep);
289 if(WriteCnt2 > 0)
290 mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep);
292 mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2);
294 else
296 ERR("Buffer lock error: %#lx\n", err);
297 mDevice->handleDisconnect("Failed to lock output buffer: 0x%lx", err);
298 return 1;
301 // Update old write cursor location
302 LastCursor += WriteCnt1+WriteCnt2;
303 LastCursor %= DSBCaps.dwBufferBytes;
306 return 0;
309 void DSoundPlayback::open(const ALCchar *name)
311 HRESULT hr;
312 if(PlaybackDevices.empty())
314 /* Initialize COM to prevent name truncation */
315 HRESULT hrcom{CoInitialize(nullptr)};
316 hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
317 if(FAILED(hr))
318 ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
319 if(SUCCEEDED(hrcom))
320 CoUninitialize();
323 const GUID *guid{nullptr};
324 if(!name && !PlaybackDevices.empty())
326 name = PlaybackDevices[0].name.c_str();
327 guid = &PlaybackDevices[0].guid;
329 else
331 auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
332 [name](const DevMap &entry) -> bool
333 { return entry.name == name; }
335 if(iter == PlaybackDevices.cend())
336 throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
337 guid = &iter->guid;
340 hr = DS_OK;
341 mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
342 if(!mNotifyEvent) hr = E_FAIL;
344 //DirectSound Init code
345 if(SUCCEEDED(hr))
346 hr = DirectSoundCreate(guid, &mDS, nullptr);
347 if(SUCCEEDED(hr))
348 hr = mDS->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
349 if(FAILED(hr))
350 throw al::backend_exception{ALC_INVALID_VALUE, "Device init failed: 0x%08lx", hr};
352 mDevice->DeviceName = name;
355 bool DSoundPlayback::reset()
357 if(mNotifies)
358 mNotifies->Release();
359 mNotifies = nullptr;
360 if(mBuffer)
361 mBuffer->Release();
362 mBuffer = nullptr;
363 if(mPrimaryBuffer)
364 mPrimaryBuffer->Release();
365 mPrimaryBuffer = nullptr;
367 switch(mDevice->FmtType)
369 case DevFmtByte:
370 mDevice->FmtType = DevFmtUByte;
371 break;
372 case DevFmtFloat:
373 if(mDevice->Flags.get<SampleTypeRequest>())
374 break;
375 /* fall-through */
376 case DevFmtUShort:
377 mDevice->FmtType = DevFmtShort;
378 break;
379 case DevFmtUInt:
380 mDevice->FmtType = DevFmtInt;
381 break;
382 case DevFmtUByte:
383 case DevFmtShort:
384 case DevFmtInt:
385 break;
388 WAVEFORMATEXTENSIBLE OutputType{};
389 DWORD speakers;
390 HRESULT hr{mDS->GetSpeakerConfig(&speakers)};
391 if(SUCCEEDED(hr))
393 speakers = DSSPEAKER_CONFIG(speakers);
394 if(!mDevice->Flags.get<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)
403 mDevice->FmtChans = DevFmtX51;
404 else if(speakers == DSSPEAKER_5POINT1_BACK)
405 mDevice->FmtChans = DevFmtX51Rear;
406 else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
407 mDevice->FmtChans = DevFmtX71;
408 else
409 ERR("Unknown system speaker config: 0x%lx\n", speakers);
411 mDevice->IsHeadphones = mDevice->FmtChans == DevFmtStereo
412 && speakers == DSSPEAKER_HEADPHONE;
414 switch(mDevice->FmtChans)
416 case DevFmtMono: OutputType.dwChannelMask = MONO; break;
417 case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo;
418 /*fall-through*/
419 case DevFmtStereo: OutputType.dwChannelMask = STEREO; break;
420 case DevFmtQuad: OutputType.dwChannelMask = QUAD; break;
421 case DevFmtX51: OutputType.dwChannelMask = X5DOT1; break;
422 case DevFmtX51Rear: OutputType.dwChannelMask = X5DOT1REAR; break;
423 case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
424 case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break;
427 retry_open:
428 hr = S_OK;
429 OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
430 OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
431 OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
432 OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
433 OutputType.Format.wBitsPerSample / 8);
434 OutputType.Format.nSamplesPerSec = mDevice->Frequency;
435 OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
436 OutputType.Format.nBlockAlign;
437 OutputType.Format.cbSize = 0;
440 if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
442 OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
443 OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
444 OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
445 if(mDevice->FmtType == DevFmtFloat)
446 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
447 else
448 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
450 if(mPrimaryBuffer)
451 mPrimaryBuffer->Release();
452 mPrimaryBuffer = nullptr;
454 else
456 if(SUCCEEDED(hr) && !mPrimaryBuffer)
458 DSBUFFERDESC DSBDescription{};
459 DSBDescription.dwSize = sizeof(DSBDescription);
460 DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
461 hr = mDS->CreateSoundBuffer(&DSBDescription, &mPrimaryBuffer, nullptr);
463 if(SUCCEEDED(hr))
464 hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
467 if(SUCCEEDED(hr))
469 ALuint 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, &mBuffer, nullptr);
482 if(FAILED(hr) && mDevice->FmtType == DevFmtFloat)
484 mDevice->FmtType = DevFmtShort;
485 goto retry_open;
489 if(SUCCEEDED(hr))
491 void *ptr;
492 hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr);
493 if(SUCCEEDED(hr))
495 mNotifies = static_cast<IDirectSoundNotify*>(ptr);
497 ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
498 assert(num_updates <= MAX_UPDATES);
500 std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots;
501 for(ALuint i{0};i < num_updates;++i)
503 nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign;
504 nots[i].hEventNotify = mNotifyEvent;
506 if(mNotifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK)
507 hr = E_FAIL;
511 if(FAILED(hr))
513 if(mNotifies)
514 mNotifies->Release();
515 mNotifies = nullptr;
516 if(mBuffer)
517 mBuffer->Release();
518 mBuffer = nullptr;
519 if(mPrimaryBuffer)
520 mPrimaryBuffer->Release();
521 mPrimaryBuffer = nullptr;
522 return false;
525 ResetEvent(mNotifyEvent);
526 setChannelOrderFromWFXMask(OutputType.dwChannelMask);
528 return true;
531 void DSoundPlayback::start()
533 try {
534 mKillNow.store(false, std::memory_order_release);
535 mThread = std::thread{std::mem_fn(&DSoundPlayback::mixerProc), this};
537 catch(std::exception& e) {
538 throw al::backend_exception{ALC_INVALID_DEVICE, "Failed to start mixing thread: %s",
539 e.what()};
543 void DSoundPlayback::stop()
545 if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
546 return;
547 mThread.join();
549 mBuffer->Stop();
553 struct DSoundCapture final : public BackendBase {
554 DSoundCapture(ALCdevice *device) noexcept : BackendBase{device} { }
555 ~DSoundCapture() override;
557 void open(const ALCchar *name) override;
558 void start() override;
559 void stop() override;
560 ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
561 ALCuint availableSamples() override;
563 IDirectSoundCapture *mDSC{nullptr};
564 IDirectSoundCaptureBuffer *mDSCbuffer{nullptr};
565 DWORD mBufferBytes{0u};
566 DWORD mCursor{0u};
568 RingBufferPtr mRing;
570 DEF_NEWDEL(DSoundCapture)
573 DSoundCapture::~DSoundCapture()
575 if(mDSCbuffer)
577 mDSCbuffer->Stop();
578 mDSCbuffer->Release();
579 mDSCbuffer = nullptr;
582 if(mDSC)
583 mDSC->Release();
584 mDSC = nullptr;
588 void DSoundCapture::open(const ALCchar *name)
590 HRESULT hr;
591 if(CaptureDevices.empty())
593 /* Initialize COM to prevent name truncation */
594 HRESULT hrcom{CoInitialize(nullptr)};
595 hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
596 if(FAILED(hr))
597 ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
598 if(SUCCEEDED(hrcom))
599 CoUninitialize();
602 const GUID *guid{nullptr};
603 if(!name && !CaptureDevices.empty())
605 name = CaptureDevices[0].name.c_str();
606 guid = &CaptureDevices[0].guid;
608 else
610 auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
611 [name](const DevMap &entry) -> bool
612 { return entry.name == name; }
614 if(iter == CaptureDevices.cend())
615 throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
616 guid = &iter->guid;
619 switch(mDevice->FmtType)
621 case DevFmtByte:
622 case DevFmtUShort:
623 case DevFmtUInt:
624 WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType));
625 throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported",
626 DevFmtTypeString(mDevice->FmtType)};
628 case DevFmtUByte:
629 case DevFmtShort:
630 case DevFmtInt:
631 case DevFmtFloat:
632 break;
635 WAVEFORMATEXTENSIBLE InputType{};
636 switch(mDevice->FmtChans)
638 case DevFmtMono: InputType.dwChannelMask = MONO; break;
639 case DevFmtStereo: InputType.dwChannelMask = STEREO; break;
640 case DevFmtQuad: InputType.dwChannelMask = QUAD; break;
641 case DevFmtX51: InputType.dwChannelMask = X5DOT1; break;
642 case DevFmtX51Rear: InputType.dwChannelMask = X5DOT1REAR; break;
643 case DevFmtX61: InputType.dwChannelMask = X6DOT1; break;
644 case DevFmtX71: InputType.dwChannelMask = X7DOT1; break;
645 case DevFmtAmbi3D:
646 WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
647 throw al::backend_exception{ALC_INVALID_VALUE, "%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;
663 else
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 ALuint 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, nullptr);
683 if(SUCCEEDED(hr))
684 mDSC->CreateCaptureBuffer(&DSCBDescription, &mDSCbuffer, nullptr);
685 if(SUCCEEDED(hr))
686 mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
688 if(FAILED(hr))
690 mRing = nullptr;
691 if(mDSCbuffer)
692 mDSCbuffer->Release();
693 mDSCbuffer = nullptr;
694 if(mDSC)
695 mDSC->Release();
696 mDSC = nullptr;
698 throw al::backend_exception{ALC_INVALID_VALUE, "Device init failed: 0x%08lx", hr};
701 mBufferBytes = DSCBDescription.dwBufferBytes;
702 setChannelOrderFromWFXMask(InputType.dwChannelMask);
704 mDevice->DeviceName = name;
707 void DSoundCapture::start()
709 const HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)};
710 if(FAILED(hr))
711 throw al::backend_exception{ALC_INVALID_DEVICE, "Failure starting capture: 0x%lx", hr};
714 void DSoundCapture::stop()
716 HRESULT hr{mDSCbuffer->Stop()};
717 if(FAILED(hr))
719 ERR("stop failed: 0x%08lx\n", hr);
720 mDevice->handleDisconnect("Failure stopping capture: 0x%lx", hr);
724 ALCenum DSoundCapture::captureSamples(al::byte *buffer, ALCuint samples)
726 mRing->read(buffer, samples);
727 return ALC_NO_ERROR;
730 ALCuint DSoundCapture::availableSamples()
732 if(!mDevice->Connected.load(std::memory_order_acquire))
733 return static_cast<ALCuint>(mRing->readSpace());
735 ALuint FrameSize{mDevice->frameSizeFromFmt()};
736 DWORD BufferBytes{mBufferBytes};
737 DWORD LastCursor{mCursor};
739 DWORD ReadCursor{};
740 void *ReadPtr1{}, *ReadPtr2{};
741 DWORD ReadCnt1{}, ReadCnt2{};
742 HRESULT hr{mDSCbuffer->GetCurrentPosition(nullptr, &ReadCursor)};
743 if(SUCCEEDED(hr))
745 DWORD NumBytes{(ReadCursor-LastCursor + BufferBytes) % BufferBytes};
746 if(!NumBytes) return static_cast<ALCubyte>(mRing->readSpace());
747 hr = mDSCbuffer->Lock(LastCursor, NumBytes, &ReadPtr1, &ReadCnt1, &ReadPtr2, &ReadCnt2, 0);
749 if(SUCCEEDED(hr))
751 mRing->write(ReadPtr1, ReadCnt1/FrameSize);
752 if(ReadPtr2 != nullptr && ReadCnt2 > 0)
753 mRing->write(ReadPtr2, ReadCnt2/FrameSize);
754 hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2);
755 mCursor = (LastCursor+ReadCnt1+ReadCnt2) % BufferBytes;
758 if(FAILED(hr))
760 ERR("update failed: 0x%08lx\n", hr);
761 mDevice->handleDisconnect("Failure retrieving capture data: 0x%lx", hr);
764 return static_cast<ALCuint>(mRing->readSpace());
767 } // namespace
770 BackendFactory &DSoundBackendFactory::getFactory()
772 static DSoundBackendFactory factory{};
773 return factory;
776 bool DSoundBackendFactory::init()
778 #ifdef HAVE_DYNLOAD
779 if(!ds_handle)
781 ds_handle = LoadLib("dsound.dll");
782 if(!ds_handle)
784 ERR("Failed to load dsound.dll\n");
785 return false;
788 #define LOAD_FUNC(f) do { \
789 p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(ds_handle, #f)); \
790 if(!p##f) \
792 CloseLib(ds_handle); \
793 ds_handle = nullptr; \
794 return false; \
796 } while(0)
797 LOAD_FUNC(DirectSoundCreate);
798 LOAD_FUNC(DirectSoundEnumerateW);
799 LOAD_FUNC(DirectSoundCaptureCreate);
800 LOAD_FUNC(DirectSoundCaptureEnumerateW);
801 #undef LOAD_FUNC
803 #endif
804 return true;
807 bool DSoundBackendFactory::querySupport(BackendType type)
808 { return (type == BackendType::Playback || type == BackendType::Capture); }
810 std::string DSoundBackendFactory::probe(BackendType type)
812 std::string outnames;
813 auto add_device = [&outnames](const DevMap &entry) -> void
815 /* +1 to also append the null char (to ensure a null-separated list and
816 * double-null terminated list).
818 outnames.append(entry.name.c_str(), entry.name.length()+1);
821 /* Initialize COM to prevent name truncation */
822 HRESULT hr;
823 HRESULT hrcom{CoInitialize(nullptr)};
824 switch(type)
826 case BackendType::Playback:
827 PlaybackDevices.clear();
828 hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
829 if(FAILED(hr))
830 ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr);
831 std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
832 break;
834 case BackendType::Capture:
835 CaptureDevices.clear();
836 hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
837 if(FAILED(hr))
838 ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr);
839 std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
840 break;
842 if(SUCCEEDED(hrcom))
843 CoUninitialize();
845 return outnames;
848 BackendPtr DSoundBackendFactory::createBackend(ALCdevice *device, BackendType type)
850 if(type == BackendType::Playback)
851 return BackendPtr{new DSoundPlayback{device}};
852 if(type == BackendType::Capture)
853 return BackendPtr{new DSoundCapture{device}};
854 return nullptr;