]> git.tdb.fi Git - ext/openal.git/blob - alc/backends/dsound.cpp
Import OpenAL Soft 1.23.1 sources
[ext/openal.git] / alc / backends / dsound.cpp
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.
8  *
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.
13  *
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
19  */
20
21 #include "config.h"
22
23 #include "dsound.h"
24
25 #define WIN32_LEAN_AND_MEAN
26 #include <windows.h>
27
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <memory.h>
31
32 #include <cguid.h>
33 #include <mmreg.h>
34 #ifndef _WAVEFORMATEXTENSIBLE_
35 #include <ks.h>
36 #include <ksmedia.h>
37 #endif
38
39 #include <atomic>
40 #include <cassert>
41 #include <thread>
42 #include <string>
43 #include <vector>
44 #include <algorithm>
45 #include <functional>
46
47 #include "alnumeric.h"
48 #include "comptr.h"
49 #include "core/device.h"
50 #include "core/helpers.h"
51 #include "core/logging.h"
52 #include "dynload.h"
53 #include "ringbuffer.h"
54 #include "strutils.h"
55 #include "threads.h"
56
57 /* MinGW-w64 needs this for some unknown reason now. */
58 using LPCWAVEFORMATEX = const WAVEFORMATEX*;
59 #include <dsound.h>
60
61
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
77
78
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.
82  */
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
89
90 namespace {
91
92 #define DEVNAME_HEAD "OpenAL Soft on "
93
94
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);
101
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
109
110
111 #define MONO SPEAKER_FRONT_CENTER
112 #define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)
113 #define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
114 #define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
115 #define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
116 #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
117 #define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
118 #define X7DOT1DOT4 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT|SPEAKER_TOP_FRONT_LEFT|SPEAKER_TOP_FRONT_RIGHT|SPEAKER_TOP_BACK_LEFT|SPEAKER_TOP_BACK_RIGHT)
119
120 #define MAX_UPDATES 128
121
122 struct DevMap {
123     std::string name;
124     GUID guid;
125
126     template<typename T0, typename T1>
127     DevMap(T0&& name_, T1&& guid_)
128       : name{std::forward<T0>(name_)}, guid{std::forward<T1>(guid_)}
129     { }
130 };
131
132 al::vector<DevMap> PlaybackDevices;
133 al::vector<DevMap> CaptureDevices;
134
135 bool checkName(const al::vector<DevMap> &list, const std::string &name)
136 {
137     auto match_name = [&name](const DevMap &entry) -> bool
138     { return entry.name == name; };
139     return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
140 }
141
142 BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, void *data) noexcept
143 {
144     if(!guid)
145         return TRUE;
146
147     auto& devices = *static_cast<al::vector<DevMap>*>(data);
148     const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)};
149
150     int count{1};
151     std::string newname{basename};
152     while(checkName(devices, newname))
153     {
154         newname = basename;
155         newname += " #";
156         newname += std::to_string(++count);
157     }
158     devices.emplace_back(std::move(newname), *guid);
159     const DevMap &newentry = devices.back();
160
161     OLECHAR *guidstr{nullptr};
162     HRESULT hr{StringFromCLSID(*guid, &guidstr)};
163     if(SUCCEEDED(hr))
164     {
165         TRACE("Got device \"%s\", GUID \"%ls\"\n", newentry.name.c_str(), guidstr);
166         CoTaskMemFree(guidstr);
167     }
168
169     return TRUE;
170 }
171
172
173 struct DSoundPlayback final : public BackendBase {
174     DSoundPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
175     ~DSoundPlayback() override;
176
177     int mixerProc();
178
179     void open(const char *name) override;
180     bool reset() override;
181     void start() override;
182     void stop() override;
183
184     ComPtr<IDirectSound>       mDS;
185     ComPtr<IDirectSoundBuffer> mPrimaryBuffer;
186     ComPtr<IDirectSoundBuffer> mBuffer;
187     ComPtr<IDirectSoundNotify> mNotifies;
188     HANDLE mNotifyEvent{nullptr};
189
190     std::atomic<bool> mKillNow{true};
191     std::thread mThread;
192
193     DEF_NEWDEL(DSoundPlayback)
194 };
195
196 DSoundPlayback::~DSoundPlayback()
197 {
198     mNotifies = nullptr;
199     mBuffer = nullptr;
200     mPrimaryBuffer = nullptr;
201     mDS = nullptr;
202
203     if(mNotifyEvent)
204         CloseHandle(mNotifyEvent);
205     mNotifyEvent = nullptr;
206 }
207
208
209 FORCE_ALIGN int DSoundPlayback::mixerProc()
210 {
211     SetRTPriority();
212     althrd_setname(MIXER_THREAD_NAME);
213
214     DSBCAPS DSBCaps{};
215     DSBCaps.dwSize = sizeof(DSBCaps);
216     HRESULT err{mBuffer->GetCaps(&DSBCaps)};
217     if(FAILED(err))
218     {
219         ERR("Failed to get buffer caps: 0x%lx\n", err);
220         mDevice->handleDisconnect("Failure retrieving playback buffer info: 0x%lx", err);
221         return 1;
222     }
223
224     const size_t FrameStep{mDevice->channelsFromFmt()};
225     uint FrameSize{mDevice->frameSizeFromFmt()};
226     DWORD FragSize{mDevice->UpdateSize * FrameSize};
227
228     bool Playing{false};
229     DWORD LastCursor{0u};
230     mBuffer->GetCurrentPosition(&LastCursor, nullptr);
231     while(!mKillNow.load(std::memory_order_acquire)
232         && mDevice->Connected.load(std::memory_order_acquire))
233     {
234         // Get current play cursor
235         DWORD PlayCursor;
236         mBuffer->GetCurrentPosition(&PlayCursor, nullptr);
237         DWORD avail = (PlayCursor-LastCursor+DSBCaps.dwBufferBytes) % DSBCaps.dwBufferBytes;
238
239         if(avail < FragSize)
240         {
241             if(!Playing)
242             {
243                 err = mBuffer->Play(0, 0, DSBPLAY_LOOPING);
244                 if(FAILED(err))
245                 {
246                     ERR("Failed to play buffer: 0x%lx\n", err);
247                     mDevice->handleDisconnect("Failure starting playback: 0x%lx", err);
248                     return 1;
249                 }
250                 Playing = true;
251             }
252
253             avail = WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE);
254             if(avail != WAIT_OBJECT_0)
255                 ERR("WaitForSingleObjectEx error: 0x%lx\n", avail);
256             continue;
257         }
258         avail -= avail%FragSize;
259
260         // Lock output buffer
261         void *WritePtr1, *WritePtr2;
262         DWORD WriteCnt1{0u},  WriteCnt2{0u};
263         err = mBuffer->Lock(LastCursor, avail, &WritePtr1, &WriteCnt1, &WritePtr2, &WriteCnt2, 0);
264
265         // If the buffer is lost, restore it and lock
266         if(err == DSERR_BUFFERLOST)
267         {
268             WARN("Buffer lost, restoring...\n");
269             err = mBuffer->Restore();
270             if(SUCCEEDED(err))
271             {
272                 Playing = false;
273                 LastCursor = 0;
274                 err = mBuffer->Lock(0, DSBCaps.dwBufferBytes, &WritePtr1, &WriteCnt1,
275                                     &WritePtr2, &WriteCnt2, 0);
276             }
277         }
278
279         if(SUCCEEDED(err))
280         {
281             mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep);
282             if(WriteCnt2 > 0)
283                 mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep);
284
285             mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2);
286         }
287         else
288         {
289             ERR("Buffer lock error: %#lx\n", err);
290             mDevice->handleDisconnect("Failed to lock output buffer: 0x%lx", err);
291             return 1;
292         }
293
294         // Update old write cursor location
295         LastCursor += WriteCnt1+WriteCnt2;
296         LastCursor %= DSBCaps.dwBufferBytes;
297     }
298
299     return 0;
300 }
301
302 void DSoundPlayback::open(const char *name)
303 {
304     HRESULT hr;
305     if(PlaybackDevices.empty())
306     {
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();
314     }
315
316     const GUID *guid{nullptr};
317     if(!name && !PlaybackDevices.empty())
318     {
319         name = PlaybackDevices[0].name.c_str();
320         guid = &PlaybackDevices[0].guid;
321     }
322     else
323     {
324         auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
325             [name](const DevMap &entry) -> bool { return entry.name == name; });
326         if(iter == PlaybackDevices.cend())
327         {
328             GUID id{};
329             hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id);
330             if(SUCCEEDED(hr))
331                 iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
332                     [&id](const DevMap &entry) -> bool { return entry.guid == id; });
333             if(iter == PlaybackDevices.cend())
334                 throw al::backend_exception{al::backend_error::NoDevice,
335                     "Device name \"%s\" not found", name};
336         }
337         guid = &iter->guid;
338     }
339
340     hr = DS_OK;
341     if(!mNotifyEvent)
342     {
343         mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
344         if(!mNotifyEvent) hr = E_FAIL;
345     }
346
347     //DirectSound Init code
348     ComPtr<IDirectSound> ds;
349     if(SUCCEEDED(hr))
350         hr = DirectSoundCreate(guid, ds.getPtr(), nullptr);
351     if(SUCCEEDED(hr))
352         hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
353     if(FAILED(hr))
354         throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
355             hr};
356
357     mNotifies = nullptr;
358     mBuffer = nullptr;
359     mPrimaryBuffer = nullptr;
360     mDS = std::move(ds);
361
362     mDevice->DeviceName = name;
363 }
364
365 bool DSoundPlayback::reset()
366 {
367     mNotifies = nullptr;
368     mBuffer = nullptr;
369     mPrimaryBuffer = nullptr;
370
371     switch(mDevice->FmtType)
372     {
373     case DevFmtByte:
374         mDevice->FmtType = DevFmtUByte;
375         break;
376     case DevFmtFloat:
377         if(mDevice->Flags.test(SampleTypeRequest))
378             break;
379         /* fall-through */
380     case DevFmtUShort:
381         mDevice->FmtType = DevFmtShort;
382         break;
383     case DevFmtUInt:
384         mDevice->FmtType = DevFmtInt;
385         break;
386     case DevFmtUByte:
387     case DevFmtShort:
388     case DevFmtInt:
389         break;
390     }
391
392     WAVEFORMATEXTENSIBLE OutputType{};
393     DWORD speakers{};
394     HRESULT hr{mDS->GetSpeakerConfig(&speakers)};
395     if(FAILED(hr))
396         throw al::backend_exception{al::backend_error::DeviceError,
397             "Failed to get speaker config: 0x%08lx", hr};
398
399     speakers = DSSPEAKER_CONFIG(speakers);
400     if(!mDevice->Flags.test(ChannelsRequest))
401     {
402         if(speakers == DSSPEAKER_MONO)
403             mDevice->FmtChans = DevFmtMono;
404         else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE)
405             mDevice->FmtChans = DevFmtStereo;
406         else if(speakers == DSSPEAKER_QUAD)
407             mDevice->FmtChans = DevFmtQuad;
408         else if(speakers == DSSPEAKER_5POINT1_SURROUND || speakers == DSSPEAKER_5POINT1_BACK)
409             mDevice->FmtChans = DevFmtX51;
410         else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
411             mDevice->FmtChans = DevFmtX71;
412         else
413             ERR("Unknown system speaker config: 0x%lx\n", speakers);
414     }
415     mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE));
416     const bool isRear51{speakers == DSSPEAKER_5POINT1_BACK};
417
418     switch(mDevice->FmtChans)
419     {
420     case DevFmtMono: OutputType.dwChannelMask = MONO; break;
421     case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo;
422         /* fall-through */
423     case DevFmtStereo: OutputType.dwChannelMask = STEREO; break;
424     case DevFmtQuad: OutputType.dwChannelMask = QUAD; break;
425     case DevFmtX51: OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break;
426     case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
427     case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break;
428     case DevFmtX714: OutputType.dwChannelMask = X7DOT1DOT4; break;
429     case DevFmtX3D71: OutputType.dwChannelMask = X7DOT1; break;
430     }
431
432 retry_open:
433     hr = S_OK;
434     OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
435     OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
436     OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
437     OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
438         OutputType.Format.wBitsPerSample / 8);
439     OutputType.Format.nSamplesPerSec = mDevice->Frequency;
440     OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
441         OutputType.Format.nBlockAlign;
442     OutputType.Format.cbSize = 0;
443
444     if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
445     {
446         OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
447         OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
448         OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
449         if(mDevice->FmtType == DevFmtFloat)
450             OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
451         else
452             OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
453
454         mPrimaryBuffer = nullptr;
455     }
456     else
457     {
458         if(SUCCEEDED(hr) && !mPrimaryBuffer)
459         {
460             DSBUFFERDESC DSBDescription{};
461             DSBDescription.dwSize = sizeof(DSBDescription);
462             DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
463             hr = mDS->CreateSoundBuffer(&DSBDescription, mPrimaryBuffer.getPtr(), nullptr);
464         }
465         if(SUCCEEDED(hr))
466             hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
467     }
468
469     if(SUCCEEDED(hr))
470     {
471         uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
472         if(num_updates > MAX_UPDATES)
473             num_updates = MAX_UPDATES;
474         mDevice->BufferSize = mDevice->UpdateSize * num_updates;
475
476         DSBUFFERDESC DSBDescription{};
477         DSBDescription.dwSize = sizeof(DSBDescription);
478         DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2
479             | DSBCAPS_GLOBALFOCUS;
480         DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign;
481         DSBDescription.lpwfxFormat = &OutputType.Format;
482
483         hr = mDS->CreateSoundBuffer(&DSBDescription, mBuffer.getPtr(), nullptr);
484         if(FAILED(hr) && mDevice->FmtType == DevFmtFloat)
485         {
486             mDevice->FmtType = DevFmtShort;
487             goto retry_open;
488         }
489     }
490
491     if(SUCCEEDED(hr))
492     {
493         void *ptr;
494         hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr);
495         if(SUCCEEDED(hr))
496         {
497             mNotifies = ComPtr<IDirectSoundNotify>{static_cast<IDirectSoundNotify*>(ptr)};
498
499             uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
500             assert(num_updates <= MAX_UPDATES);
501
502             std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots;
503             for(uint i{0};i < num_updates;++i)
504             {
505                 nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign;
506                 nots[i].hEventNotify = mNotifyEvent;
507             }
508             if(mNotifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK)
509                 hr = E_FAIL;
510         }
511     }
512
513     if(FAILED(hr))
514     {
515         mNotifies = nullptr;
516         mBuffer = nullptr;
517         mPrimaryBuffer = nullptr;
518         return false;
519     }
520
521     ResetEvent(mNotifyEvent);
522     setDefaultWFXChannelOrder();
523
524     return true;
525 }
526
527 void DSoundPlayback::start()
528 {
529     try {
530         mKillNow.store(false, std::memory_order_release);
531         mThread = std::thread{std::mem_fn(&DSoundPlayback::mixerProc), this};
532     }
533     catch(std::exception& e) {
534         throw al::backend_exception{al::backend_error::DeviceError,
535             "Failed to start mixing thread: %s", e.what()};
536     }
537 }
538
539 void DSoundPlayback::stop()
540 {
541     if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
542         return;
543     mThread.join();
544
545     mBuffer->Stop();
546 }
547
548
549 struct DSoundCapture final : public BackendBase {
550     DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { }
551     ~DSoundCapture() override;
552
553     void open(const char *name) override;
554     void start() override;
555     void stop() override;
556     void captureSamples(al::byte *buffer, uint samples) override;
557     uint availableSamples() override;
558
559     ComPtr<IDirectSoundCapture> mDSC;
560     ComPtr<IDirectSoundCaptureBuffer> mDSCbuffer;
561     DWORD mBufferBytes{0u};
562     DWORD mCursor{0u};
563
564     RingBufferPtr mRing;
565
566     DEF_NEWDEL(DSoundCapture)
567 };
568
569 DSoundCapture::~DSoundCapture()
570 {
571     if(mDSCbuffer)
572     {
573         mDSCbuffer->Stop();
574         mDSCbuffer = nullptr;
575     }
576     mDSC = nullptr;
577 }
578
579
580 void DSoundCapture::open(const char *name)
581 {
582     HRESULT hr;
583     if(CaptureDevices.empty())
584     {
585         /* Initialize COM to prevent name truncation */
586         HRESULT hrcom{CoInitialize(nullptr)};
587         hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
588         if(FAILED(hr))
589             ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
590         if(SUCCEEDED(hrcom))
591             CoUninitialize();
592     }
593
594     const GUID *guid{nullptr};
595     if(!name && !CaptureDevices.empty())
596     {
597         name = CaptureDevices[0].name.c_str();
598         guid = &CaptureDevices[0].guid;
599     }
600     else
601     {
602         auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
603             [name](const DevMap &entry) -> bool { return entry.name == name; });
604         if(iter == CaptureDevices.cend())
605         {
606             GUID id{};
607             hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id);
608             if(SUCCEEDED(hr))
609                 iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
610                     [&id](const DevMap &entry) -> bool { return entry.guid == id; });
611             if(iter == CaptureDevices.cend())
612                 throw al::backend_exception{al::backend_error::NoDevice,
613                     "Device name \"%s\" not found", name};
614         }
615         guid = &iter->guid;
616     }
617
618     switch(mDevice->FmtType)
619     {
620     case DevFmtByte:
621     case DevFmtUShort:
622     case DevFmtUInt:
623         WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType));
624         throw al::backend_exception{al::backend_error::DeviceError,
625             "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
626
627     case DevFmtUByte:
628     case DevFmtShort:
629     case DevFmtInt:
630     case DevFmtFloat:
631         break;
632     }
633
634     WAVEFORMATEXTENSIBLE InputType{};
635     switch(mDevice->FmtChans)
636     {
637     case DevFmtMono: InputType.dwChannelMask = MONO; break;
638     case DevFmtStereo: InputType.dwChannelMask = STEREO; break;
639     case DevFmtQuad: InputType.dwChannelMask = QUAD; break;
640     case DevFmtX51: InputType.dwChannelMask = X5DOT1; break;
641     case DevFmtX61: InputType.dwChannelMask = X6DOT1; break;
642     case DevFmtX71: InputType.dwChannelMask = X7DOT1; break;
643     case DevFmtX714: InputType.dwChannelMask = X7DOT1DOT4; break;
644     case DevFmtX3D71:
645     case DevFmtAmbi3D:
646         WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
647         throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
648             DevFmtChannelsString(mDevice->FmtChans)};
649     }
650
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;
665
666     if(InputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
667     {
668         InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
669         InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
670     }
671
672     uint samples{mDevice->BufferSize};
673     samples = maxu(samples, 100 * mDevice->Frequency / 1000);
674
675     DSCBUFFERDESC DSCBDescription{};
676     DSCBDescription.dwSize = sizeof(DSCBDescription);
677     DSCBDescription.dwFlags = 0;
678     DSCBDescription.dwBufferBytes = samples * InputType.Format.nBlockAlign;
679     DSCBDescription.lpwfxFormat = &InputType.Format;
680
681     //DirectSoundCapture Init code
682     hr = DirectSoundCaptureCreate(guid, mDSC.getPtr(), nullptr);
683     if(SUCCEEDED(hr))
684         mDSC->CreateCaptureBuffer(&DSCBDescription, mDSCbuffer.getPtr(), nullptr);
685     if(SUCCEEDED(hr))
686          mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
687
688     if(FAILED(hr))
689     {
690         mRing = nullptr;
691         mDSCbuffer = nullptr;
692         mDSC = nullptr;
693
694         throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
695             hr};
696     }
697
698     mBufferBytes = DSCBDescription.dwBufferBytes;
699     setDefaultWFXChannelOrder();
700
701     mDevice->DeviceName = name;
702 }
703
704 void DSoundCapture::start()
705 {
706     const HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)};
707     if(FAILED(hr))
708         throw al::backend_exception{al::backend_error::DeviceError,
709             "Failure starting capture: 0x%lx", hr};
710 }
711
712 void DSoundCapture::stop()
713 {
714     HRESULT hr{mDSCbuffer->Stop()};
715     if(FAILED(hr))
716     {
717         ERR("stop failed: 0x%08lx\n", hr);
718         mDevice->handleDisconnect("Failure stopping capture: 0x%lx", hr);
719     }
720 }
721
722 void DSoundCapture::captureSamples(al::byte *buffer, uint samples)
723 { mRing->read(buffer, samples); }
724
725 uint DSoundCapture::availableSamples()
726 {
727     if(!mDevice->Connected.load(std::memory_order_acquire))
728         return static_cast<uint>(mRing->readSpace());
729
730     const uint FrameSize{mDevice->frameSizeFromFmt()};
731     const DWORD BufferBytes{mBufferBytes};
732     const DWORD LastCursor{mCursor};
733
734     DWORD ReadCursor{};
735     void *ReadPtr1{}, *ReadPtr2{};
736     DWORD ReadCnt1{},  ReadCnt2{};
737     HRESULT hr{mDSCbuffer->GetCurrentPosition(nullptr, &ReadCursor)};
738     if(SUCCEEDED(hr))
739     {
740         const DWORD NumBytes{(BufferBytes+ReadCursor-LastCursor) % BufferBytes};
741         if(!NumBytes) return static_cast<uint>(mRing->readSpace());
742         hr = mDSCbuffer->Lock(LastCursor, NumBytes, &ReadPtr1, &ReadCnt1, &ReadPtr2, &ReadCnt2, 0);
743     }
744     if(SUCCEEDED(hr))
745     {
746         mRing->write(ReadPtr1, ReadCnt1/FrameSize);
747         if(ReadPtr2 != nullptr && ReadCnt2 > 0)
748             mRing->write(ReadPtr2, ReadCnt2/FrameSize);
749         hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2);
750         mCursor = ReadCursor;
751     }
752
753     if(FAILED(hr))
754     {
755         ERR("update failed: 0x%08lx\n", hr);
756         mDevice->handleDisconnect("Failure retrieving capture data: 0x%lx", hr);
757     }
758
759     return static_cast<uint>(mRing->readSpace());
760 }
761
762 } // namespace
763
764
765 BackendFactory &DSoundBackendFactory::getFactory()
766 {
767     static DSoundBackendFactory factory{};
768     return factory;
769 }
770
771 bool DSoundBackendFactory::init()
772 {
773 #ifdef HAVE_DYNLOAD
774     if(!ds_handle)
775     {
776         ds_handle = LoadLib("dsound.dll");
777         if(!ds_handle)
778         {
779             ERR("Failed to load dsound.dll\n");
780             return false;
781         }
782
783 #define LOAD_FUNC(f) do {                                                     \
784     p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(ds_handle, #f));        \
785     if(!p##f)                                                                 \
786     {                                                                         \
787         CloseLib(ds_handle);                                                  \
788         ds_handle = nullptr;                                                  \
789         return false;                                                         \
790     }                                                                         \
791 } while(0)
792         LOAD_FUNC(DirectSoundCreate);
793         LOAD_FUNC(DirectSoundEnumerateW);
794         LOAD_FUNC(DirectSoundCaptureCreate);
795         LOAD_FUNC(DirectSoundCaptureEnumerateW);
796 #undef LOAD_FUNC
797     }
798 #endif
799     return true;
800 }
801
802 bool DSoundBackendFactory::querySupport(BackendType type)
803 { return (type == BackendType::Playback || type == BackendType::Capture); }
804
805 std::string DSoundBackendFactory::probe(BackendType type)
806 {
807     std::string outnames;
808     auto add_device = [&outnames](const DevMap &entry) -> void
809     {
810         /* +1 to also append the null char (to ensure a null-separated list and
811          * double-null terminated list).
812          */
813         outnames.append(entry.name.c_str(), entry.name.length()+1);
814     };
815
816     /* Initialize COM to prevent name truncation */
817     HRESULT hr;
818     HRESULT hrcom{CoInitialize(nullptr)};
819     switch(type)
820     {
821     case BackendType::Playback:
822         PlaybackDevices.clear();
823         hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
824         if(FAILED(hr))
825             ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr);
826         std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
827         break;
828
829     case BackendType::Capture:
830         CaptureDevices.clear();
831         hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
832         if(FAILED(hr))
833             ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr);
834         std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
835         break;
836     }
837     if(SUCCEEDED(hrcom))
838         CoUninitialize();
839
840     return outnames;
841 }
842
843 BackendPtr DSoundBackendFactory::createBackend(DeviceBase *device, BackendType type)
844 {
845     if(type == BackendType::Playback)
846         return BackendPtr{new DSoundPlayback{device}};
847     if(type == BackendType::Capture)
848         return BackendPtr{new DSoundCapture{device}};
849     return nullptr;
850 }