dsound.cpp 28 KB

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