🛠️🐜 Antkeeper superbuild with dependencies included https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

847 lines
27 KiB

  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 <stdlib.h>
  25. #include <stdio.h>
  26. #include <memory.h>
  27. #include <cguid.h>
  28. #include <mmreg.h>
  29. #ifndef _WAVEFORMATEXTENSIBLE_
  30. #include <ks.h>
  31. #include <ksmedia.h>
  32. #endif
  33. #include <atomic>
  34. #include <cassert>
  35. #include <thread>
  36. #include <string>
  37. #include <vector>
  38. #include <algorithm>
  39. #include <functional>
  40. #include "alnumeric.h"
  41. #include "comptr.h"
  42. #include "core/device.h"
  43. #include "core/helpers.h"
  44. #include "core/logging.h"
  45. #include "dynload.h"
  46. #include "ringbuffer.h"
  47. #include "strutils.h"
  48. #include "threads.h"
  49. /* MinGW-w64 needs this for some unknown reason now. */
  50. using LPCWAVEFORMATEX = const WAVEFORMATEX*;
  51. #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
  67. /* Some headers seem to define these as macros for __uuidof, which is annoying
  68. * since some headers don't declare them at all. Hopefully the ifdef is enough
  69. * to tell if they need to be declared.
  70. */
  71. #ifndef KSDATAFORMAT_SUBTYPE_PCM
  72. DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
  73. #endif
  74. #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
  75. DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
  76. #endif
  77. namespace {
  78. #define DEVNAME_HEAD "OpenAL Soft on "
  79. #ifdef HAVE_DYNLOAD
  80. void *ds_handle;
  81. HRESULT (WINAPI *pDirectSoundCreate)(const GUID *pcGuidDevice, IDirectSound **ppDS, IUnknown *pUnkOuter);
  82. HRESULT (WINAPI *pDirectSoundEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext);
  83. HRESULT (WINAPI *pDirectSoundCaptureCreate)(const GUID *pcGuidDevice, IDirectSoundCapture **ppDSC, IUnknown *pUnkOuter);
  84. HRESULT (WINAPI *pDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext);
  85. #ifndef IN_IDE_PARSER
  86. #define DirectSoundCreate pDirectSoundCreate
  87. #define DirectSoundEnumerateW pDirectSoundEnumerateW
  88. #define DirectSoundCaptureCreate pDirectSoundCaptureCreate
  89. #define DirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW
  90. #endif
  91. #endif
  92. #define MONO SPEAKER_FRONT_CENTER
  93. #define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)
  94. #define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
  95. #define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
  96. #define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
  97. #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
  98. #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)
  99. #define MAX_UPDATES 128
  100. struct DevMap {
  101. std::string name;
  102. GUID guid;
  103. template<typename T0, typename T1>
  104. DevMap(T0&& name_, T1&& guid_)
  105. : name{std::forward<T0>(name_)}, guid{std::forward<T1>(guid_)}
  106. { }
  107. };
  108. al::vector<DevMap> PlaybackDevices;
  109. al::vector<DevMap> CaptureDevices;
  110. bool checkName(const al::vector<DevMap> &list, const std::string &name)
  111. {
  112. auto match_name = [&name](const DevMap &entry) -> bool
  113. { return entry.name == name; };
  114. return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
  115. }
  116. BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, void *data) noexcept
  117. {
  118. if(!guid)
  119. return TRUE;
  120. auto& devices = *static_cast<al::vector<DevMap>*>(data);
  121. const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)};
  122. int count{1};
  123. std::string newname{basename};
  124. while(checkName(devices, newname))
  125. {
  126. newname = basename;
  127. newname += " #";
  128. newname += std::to_string(++count);
  129. }
  130. devices.emplace_back(std::move(newname), *guid);
  131. const DevMap &newentry = devices.back();
  132. OLECHAR *guidstr{nullptr};
  133. HRESULT hr{StringFromCLSID(*guid, &guidstr)};
  134. if(SUCCEEDED(hr))
  135. {
  136. TRACE("Got device \"%s\", GUID \"%ls\"\n", newentry.name.c_str(), guidstr);
  137. CoTaskMemFree(guidstr);
  138. }
  139. return TRUE;
  140. }
  141. struct DSoundPlayback final : public BackendBase {
  142. DSoundPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
  143. ~DSoundPlayback() override;
  144. int mixerProc();
  145. void open(const char *name) override;
  146. bool reset() override;
  147. void start() override;
  148. void stop() override;
  149. ComPtr<IDirectSound> mDS;
  150. ComPtr<IDirectSoundBuffer> mPrimaryBuffer;
  151. ComPtr<IDirectSoundBuffer> mBuffer;
  152. ComPtr<IDirectSoundNotify> mNotifies;
  153. HANDLE mNotifyEvent{nullptr};
  154. std::atomic<bool> mKillNow{true};
  155. std::thread mThread;
  156. DEF_NEWDEL(DSoundPlayback)
  157. };
  158. DSoundPlayback::~DSoundPlayback()
  159. {
  160. mNotifies = nullptr;
  161. mBuffer = nullptr;
  162. mPrimaryBuffer = nullptr;
  163. mDS = nullptr;
  164. if(mNotifyEvent)
  165. CloseHandle(mNotifyEvent);
  166. mNotifyEvent = nullptr;
  167. }
  168. FORCE_ALIGN int DSoundPlayback::mixerProc()
  169. {
  170. SetRTPriority();
  171. althrd_setname(MIXER_THREAD_NAME);
  172. DSBCAPS DSBCaps{};
  173. DSBCaps.dwSize = sizeof(DSBCaps);
  174. HRESULT err{mBuffer->GetCaps(&DSBCaps)};
  175. if(FAILED(err))
  176. {
  177. ERR("Failed to get buffer caps: 0x%lx\n", err);
  178. mDevice->handleDisconnect("Failure retrieving playback buffer info: 0x%lx", err);
  179. return 1;
  180. }
  181. const size_t FrameStep{mDevice->channelsFromFmt()};
  182. uint FrameSize{mDevice->frameSizeFromFmt()};
  183. DWORD FragSize{mDevice->UpdateSize * FrameSize};
  184. bool Playing{false};
  185. DWORD LastCursor{0u};
  186. mBuffer->GetCurrentPosition(&LastCursor, nullptr);
  187. while(!mKillNow.load(std::memory_order_acquire)
  188. && mDevice->Connected.load(std::memory_order_acquire))
  189. {
  190. // Get current play cursor
  191. DWORD PlayCursor;
  192. mBuffer->GetCurrentPosition(&PlayCursor, nullptr);
  193. DWORD avail = (PlayCursor-LastCursor+DSBCaps.dwBufferBytes) % DSBCaps.dwBufferBytes;
  194. if(avail < FragSize)
  195. {
  196. if(!Playing)
  197. {
  198. err = mBuffer->Play(0, 0, DSBPLAY_LOOPING);
  199. if(FAILED(err))
  200. {
  201. ERR("Failed to play buffer: 0x%lx\n", err);
  202. mDevice->handleDisconnect("Failure starting playback: 0x%lx", err);
  203. return 1;
  204. }
  205. Playing = true;
  206. }
  207. avail = WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE);
  208. if(avail != WAIT_OBJECT_0)
  209. ERR("WaitForSingleObjectEx error: 0x%lx\n", avail);
  210. continue;
  211. }
  212. avail -= avail%FragSize;
  213. // Lock output buffer
  214. void *WritePtr1, *WritePtr2;
  215. DWORD WriteCnt1{0u}, WriteCnt2{0u};
  216. err = mBuffer->Lock(LastCursor, avail, &WritePtr1, &WriteCnt1, &WritePtr2, &WriteCnt2, 0);
  217. // If the buffer is lost, restore it and lock
  218. if(err == DSERR_BUFFERLOST)
  219. {
  220. WARN("Buffer lost, restoring...\n");
  221. err = mBuffer->Restore();
  222. if(SUCCEEDED(err))
  223. {
  224. Playing = false;
  225. LastCursor = 0;
  226. err = mBuffer->Lock(0, DSBCaps.dwBufferBytes, &WritePtr1, &WriteCnt1,
  227. &WritePtr2, &WriteCnt2, 0);
  228. }
  229. }
  230. if(SUCCEEDED(err))
  231. {
  232. mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep);
  233. if(WriteCnt2 > 0)
  234. mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep);
  235. mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2);
  236. }
  237. else
  238. {
  239. ERR("Buffer lock error: %#lx\n", err);
  240. mDevice->handleDisconnect("Failed to lock output buffer: 0x%lx", err);
  241. return 1;
  242. }
  243. // Update old write cursor location
  244. LastCursor += WriteCnt1+WriteCnt2;
  245. LastCursor %= DSBCaps.dwBufferBytes;
  246. }
  247. return 0;
  248. }
  249. void DSoundPlayback::open(const char *name)
  250. {
  251. HRESULT hr;
  252. if(PlaybackDevices.empty())
  253. {
  254. /* Initialize COM to prevent name truncation */
  255. HRESULT hrcom{CoInitialize(nullptr)};
  256. hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
  257. if(FAILED(hr))
  258. ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
  259. if(SUCCEEDED(hrcom))
  260. CoUninitialize();
  261. }
  262. const GUID *guid{nullptr};
  263. if(!name && !PlaybackDevices.empty())
  264. {
  265. name = PlaybackDevices[0].name.c_str();
  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", name};
  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, ds.getPtr(), 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 DevFmtX3D71: OutputType.dwChannelMask = X7DOT1; break;
  366. }
  367. retry_open:
  368. hr = S_OK;
  369. OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
  370. OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
  371. OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
  372. OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
  373. OutputType.Format.wBitsPerSample / 8);
  374. OutputType.Format.nSamplesPerSec = mDevice->Frequency;
  375. OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
  376. OutputType.Format.nBlockAlign;
  377. OutputType.Format.cbSize = 0;
  378. if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
  379. {
  380. OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
  381. OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
  382. OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
  383. if(mDevice->FmtType == DevFmtFloat)
  384. OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
  385. else
  386. OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
  387. mPrimaryBuffer = nullptr;
  388. }
  389. else
  390. {
  391. if(SUCCEEDED(hr) && !mPrimaryBuffer)
  392. {
  393. DSBUFFERDESC DSBDescription{};
  394. DSBDescription.dwSize = sizeof(DSBDescription);
  395. DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
  396. hr = mDS->CreateSoundBuffer(&DSBDescription, mPrimaryBuffer.getPtr(), nullptr);
  397. }
  398. if(SUCCEEDED(hr))
  399. hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
  400. }
  401. if(SUCCEEDED(hr))
  402. {
  403. uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
  404. if(num_updates > MAX_UPDATES)
  405. num_updates = MAX_UPDATES;
  406. mDevice->BufferSize = mDevice->UpdateSize * num_updates;
  407. DSBUFFERDESC DSBDescription{};
  408. DSBDescription.dwSize = sizeof(DSBDescription);
  409. DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2
  410. | DSBCAPS_GLOBALFOCUS;
  411. DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign;
  412. DSBDescription.lpwfxFormat = &OutputType.Format;
  413. hr = mDS->CreateSoundBuffer(&DSBDescription, mBuffer.getPtr(), nullptr);
  414. if(FAILED(hr) && mDevice->FmtType == DevFmtFloat)
  415. {
  416. mDevice->FmtType = DevFmtShort;
  417. goto retry_open;
  418. }
  419. }
  420. if(SUCCEEDED(hr))
  421. {
  422. void *ptr;
  423. hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr);
  424. if(SUCCEEDED(hr))
  425. {
  426. mNotifies = ComPtr<IDirectSoundNotify>{static_cast<IDirectSoundNotify*>(ptr)};
  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(const char *name) override;
  472. void start() override;
  473. void stop() override;
  474. void captureSamples(al::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. DEF_NEWDEL(DSoundCapture)
  482. };
  483. DSoundCapture::~DSoundCapture()
  484. {
  485. if(mDSCbuffer)
  486. {
  487. mDSCbuffer->Stop();
  488. mDSCbuffer = nullptr;
  489. }
  490. mDSC = nullptr;
  491. }
  492. void DSoundCapture::open(const char *name)
  493. {
  494. HRESULT hr;
  495. if(CaptureDevices.empty())
  496. {
  497. /* Initialize COM to prevent name truncation */
  498. HRESULT hrcom{CoInitialize(nullptr)};
  499. hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
  500. if(FAILED(hr))
  501. ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
  502. if(SUCCEEDED(hrcom))
  503. CoUninitialize();
  504. }
  505. const GUID *guid{nullptr};
  506. if(!name && !CaptureDevices.empty())
  507. {
  508. name = CaptureDevices[0].name.c_str();
  509. guid = &CaptureDevices[0].guid;
  510. }
  511. else
  512. {
  513. auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
  514. [name](const DevMap &entry) -> bool { return entry.name == name; });
  515. if(iter == CaptureDevices.cend())
  516. {
  517. GUID id{};
  518. hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id);
  519. if(SUCCEEDED(hr))
  520. iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
  521. [&id](const DevMap &entry) -> bool { return entry.guid == id; });
  522. if(iter == CaptureDevices.cend())
  523. throw al::backend_exception{al::backend_error::NoDevice,
  524. "Device name \"%s\" not found", name};
  525. }
  526. guid = &iter->guid;
  527. }
  528. switch(mDevice->FmtType)
  529. {
  530. case DevFmtByte:
  531. case DevFmtUShort:
  532. case DevFmtUInt:
  533. WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType));
  534. throw al::backend_exception{al::backend_error::DeviceError,
  535. "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
  536. case DevFmtUByte:
  537. case DevFmtShort:
  538. case DevFmtInt:
  539. case DevFmtFloat:
  540. break;
  541. }
  542. WAVEFORMATEXTENSIBLE InputType{};
  543. switch(mDevice->FmtChans)
  544. {
  545. case DevFmtMono: InputType.dwChannelMask = MONO; break;
  546. case DevFmtStereo: InputType.dwChannelMask = STEREO; break;
  547. case DevFmtQuad: InputType.dwChannelMask = QUAD; break;
  548. case DevFmtX51: InputType.dwChannelMask = X5DOT1; break;
  549. case DevFmtX61: InputType.dwChannelMask = X6DOT1; break;
  550. case DevFmtX71: InputType.dwChannelMask = X7DOT1; break;
  551. case DevFmtX3D71:
  552. case DevFmtAmbi3D:
  553. WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
  554. throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
  555. DevFmtChannelsString(mDevice->FmtChans)};
  556. }
  557. InputType.Format.wFormatTag = WAVE_FORMAT_PCM;
  558. InputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
  559. InputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
  560. InputType.Format.nBlockAlign = static_cast<WORD>(InputType.Format.nChannels *
  561. InputType.Format.wBitsPerSample / 8);
  562. InputType.Format.nSamplesPerSec = mDevice->Frequency;
  563. InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec *
  564. InputType.Format.nBlockAlign;
  565. InputType.Format.cbSize = 0;
  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. uint samples{mDevice->BufferSize};
  577. samples = maxu(samples, 100 * mDevice->Frequency / 1000);
  578. DSCBUFFERDESC DSCBDescription{};
  579. DSCBDescription.dwSize = sizeof(DSCBDescription);
  580. DSCBDescription.dwFlags = 0;
  581. DSCBDescription.dwBufferBytes = samples * InputType.Format.nBlockAlign;
  582. DSCBDescription.lpwfxFormat = &InputType.Format;
  583. //DirectSoundCapture Init code
  584. hr = DirectSoundCaptureCreate(guid, mDSC.getPtr(), nullptr);
  585. if(SUCCEEDED(hr))
  586. mDSC->CreateCaptureBuffer(&DSCBDescription, mDSCbuffer.getPtr(), nullptr);
  587. if(SUCCEEDED(hr))
  588. mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
  589. if(FAILED(hr))
  590. {
  591. mRing = nullptr;
  592. mDSCbuffer = nullptr;
  593. mDSC = nullptr;
  594. throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
  595. hr};
  596. }
  597. mBufferBytes = DSCBDescription.dwBufferBytes;
  598. setDefaultWFXChannelOrder();
  599. mDevice->DeviceName = name;
  600. }
  601. void DSoundCapture::start()
  602. {
  603. const HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)};
  604. if(FAILED(hr))
  605. throw al::backend_exception{al::backend_error::DeviceError,
  606. "Failure starting capture: 0x%lx", hr};
  607. }
  608. void DSoundCapture::stop()
  609. {
  610. HRESULT hr{mDSCbuffer->Stop()};
  611. if(FAILED(hr))
  612. {
  613. ERR("stop failed: 0x%08lx\n", hr);
  614. mDevice->handleDisconnect("Failure stopping capture: 0x%lx", hr);
  615. }
  616. }
  617. void DSoundCapture::captureSamples(al::byte *buffer, uint samples)
  618. { mRing->read(buffer, samples); }
  619. uint DSoundCapture::availableSamples()
  620. {
  621. if(!mDevice->Connected.load(std::memory_order_acquire))
  622. return static_cast<uint>(mRing->readSpace());
  623. const uint FrameSize{mDevice->frameSizeFromFmt()};
  624. const DWORD BufferBytes{mBufferBytes};
  625. const DWORD LastCursor{mCursor};
  626. DWORD ReadCursor{};
  627. void *ReadPtr1{}, *ReadPtr2{};
  628. DWORD ReadCnt1{}, ReadCnt2{};
  629. HRESULT hr{mDSCbuffer->GetCurrentPosition(nullptr, &ReadCursor)};
  630. if(SUCCEEDED(hr))
  631. {
  632. const DWORD NumBytes{(BufferBytes+ReadCursor-LastCursor) % BufferBytes};
  633. if(!NumBytes) return static_cast<uint>(mRing->readSpace());
  634. hr = mDSCbuffer->Lock(LastCursor, NumBytes, &ReadPtr1, &ReadCnt1, &ReadPtr2, &ReadCnt2, 0);
  635. }
  636. if(SUCCEEDED(hr))
  637. {
  638. mRing->write(ReadPtr1, ReadCnt1/FrameSize);
  639. if(ReadPtr2 != nullptr && ReadCnt2 > 0)
  640. mRing->write(ReadPtr2, ReadCnt2/FrameSize);
  641. hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2);
  642. mCursor = ReadCursor;
  643. }
  644. if(FAILED(hr))
  645. {
  646. ERR("update failed: 0x%08lx\n", hr);
  647. mDevice->handleDisconnect("Failure retrieving capture data: 0x%lx", hr);
  648. }
  649. return static_cast<uint>(mRing->readSpace());
  650. }
  651. } // namespace
  652. BackendFactory &DSoundBackendFactory::getFactory()
  653. {
  654. static DSoundBackendFactory factory{};
  655. return factory;
  656. }
  657. bool DSoundBackendFactory::init()
  658. {
  659. #ifdef HAVE_DYNLOAD
  660. if(!ds_handle)
  661. {
  662. ds_handle = LoadLib("dsound.dll");
  663. if(!ds_handle)
  664. {
  665. ERR("Failed to load dsound.dll\n");
  666. return false;
  667. }
  668. #define LOAD_FUNC(f) do { \
  669. p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(ds_handle, #f)); \
  670. if(!p##f) \
  671. { \
  672. CloseLib(ds_handle); \
  673. ds_handle = nullptr; \
  674. return false; \
  675. } \
  676. } while(0)
  677. LOAD_FUNC(DirectSoundCreate);
  678. LOAD_FUNC(DirectSoundEnumerateW);
  679. LOAD_FUNC(DirectSoundCaptureCreate);
  680. LOAD_FUNC(DirectSoundCaptureEnumerateW);
  681. #undef LOAD_FUNC
  682. }
  683. #endif
  684. return true;
  685. }
  686. bool DSoundBackendFactory::querySupport(BackendType type)
  687. { return (type == BackendType::Playback || type == BackendType::Capture); }
  688. std::string DSoundBackendFactory::probe(BackendType type)
  689. {
  690. std::string outnames;
  691. auto add_device = [&outnames](const DevMap &entry) -> void
  692. {
  693. /* +1 to also append the null char (to ensure a null-separated list and
  694. * double-null terminated list).
  695. */
  696. outnames.append(entry.name.c_str(), entry.name.length()+1);
  697. };
  698. /* Initialize COM to prevent name truncation */
  699. HRESULT hr;
  700. HRESULT hrcom{CoInitialize(nullptr)};
  701. switch(type)
  702. {
  703. case BackendType::Playback:
  704. PlaybackDevices.clear();
  705. hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
  706. if(FAILED(hr))
  707. ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr);
  708. std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
  709. break;
  710. case BackendType::Capture:
  711. CaptureDevices.clear();
  712. hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
  713. if(FAILED(hr))
  714. ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr);
  715. std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
  716. break;
  717. }
  718. if(SUCCEEDED(hrcom))
  719. CoUninitialize();
  720. return outnames;
  721. }
  722. BackendPtr DSoundBackendFactory::createBackend(DeviceBase *device, BackendType type)
  723. {
  724. if(type == BackendType::Playback)
  725. return BackendPtr{new DSoundPlayback{device}};
  726. if(type == BackendType::Capture)
  727. return BackendPtr{new DSoundCapture{device}};
  728. return nullptr;
  729. }