otherio.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  1. /**
  2. * OpenAL cross platform audio library
  3. * Copyright (C) 2024 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 "otherio.h"
  22. #define WIN32_LEAN_AND_MEAN
  23. #include <windows.h>
  24. #include <winreg.h>
  25. #include <cstdio>
  26. #include <cstdlib>
  27. #include <memory.h>
  28. #include <wtypes.h>
  29. #include <cguid.h>
  30. #include <devpropdef.h>
  31. #include <mmreg.h>
  32. #include <propsys.h>
  33. #include <propkey.h>
  34. #include <devpkey.h>
  35. #ifndef _WAVEFORMATEXTENSIBLE_
  36. #include <ks.h>
  37. #include <ksmedia.h>
  38. #endif
  39. #include <algorithm>
  40. #include <atomic>
  41. #include <chrono>
  42. #include <condition_variable>
  43. #include <cstring>
  44. #include <deque>
  45. #include <future>
  46. #include <mutex>
  47. #include <string>
  48. #include <string_view>
  49. #include <thread>
  50. #include <vector>
  51. #include "albit.h"
  52. #include "alnumeric.h"
  53. #include "althrd_setname.h"
  54. #include "comptr.h"
  55. #include "core/converter.h"
  56. #include "core/device.h"
  57. #include "core/helpers.h"
  58. #include "core/logging.h"
  59. #include "strutils.h"
  60. /* A custom C++ interface that should be capable of interoperating with ASIO
  61. * drivers.
  62. */
  63. enum class ORIOError : LONG {
  64. Okay = 0,
  65. Success = 0x3f4847a0,
  66. NotPresent = -1000,
  67. HWMalfunction,
  68. InvalidParameter,
  69. InvalidMode,
  70. SPNotAdvancing,
  71. NoClock,
  72. NoMemory,
  73. };
  74. /* A 64-bit integer or double, which has the most significant 32-bit word first. */
  75. struct ORIO64Bit {
  76. uint32_t hi;
  77. uint32_t lo;
  78. template<typename T>
  79. auto as() const -> T = delete;
  80. };
  81. template<> [[nodiscard]]
  82. auto ORIO64Bit::as() const -> uint64_t { return (uint64_t{hi}<<32) | lo; }
  83. template<> [[nodiscard]]
  84. auto ORIO64Bit::as() const -> int64_t { return static_cast<int64_t>(as<uint64_t>()); }
  85. template<> [[nodiscard]]
  86. auto ORIO64Bit::as() const -> double { return al::bit_cast<double>(as<uint64_t>()); }
  87. enum class ORIOSampleType : LONG {
  88. Int16BE = 0,
  89. Int24BE = 1,
  90. Int32BE = 2,
  91. Float32BE = 3,
  92. Float64BE = 4,
  93. Int32BE16 = 8,
  94. Int32BE18 = 9,
  95. Int32BE20 = 10,
  96. Int32BE24 = 11,
  97. Int16LE = 16,
  98. Int24LE = 17,
  99. Int32LE = 18,
  100. Float32LE = 19,
  101. Float64LE = 20,
  102. Int32LE16 = 24,
  103. Int32LE18 = 25,
  104. Int32LE20 = 26,
  105. Int32LE24 = 27,
  106. DSDInt8LSB1 = 32,
  107. DSDInt8MSB1 = 33,
  108. DSDInt8 = 40,
  109. };
  110. struct ORIOClockSource {
  111. LONG mIndex;
  112. LONG mAssocChannel;
  113. LONG mAssocGroup;
  114. LONG mIsCurrent;
  115. std::array<char,32> mName;
  116. };
  117. struct ORIOChannelInfo {
  118. LONG mChannel;
  119. LONG mIsInput;
  120. LONG mIsActive;
  121. LONG mGroup;
  122. ORIOSampleType mSampleType;
  123. std::array<char,32> mName;
  124. };
  125. struct ORIOBufferInfo {
  126. LONG mIsInput;
  127. LONG mChannelNum;
  128. std::array<void*,2> mBuffers;
  129. };
  130. struct ORIOTime {
  131. struct TimeInfo {
  132. double mSpeed;
  133. ORIO64Bit mSystemTime;
  134. ORIO64Bit mSamplePosition;
  135. double mSampleRate;
  136. ULONG mFlags;
  137. std::array<char,12> mReserved;
  138. };
  139. struct TimeCode {
  140. double mSpeed;
  141. ORIO64Bit mTimeCodeSamples;
  142. ULONG mFlags;
  143. std::array<char,64> mFuture;
  144. };
  145. std::array<LONG,4> mReserved;
  146. TimeInfo mTimeInfo;
  147. TimeCode mTimeCode;
  148. };
  149. #ifdef _WIN64
  150. #define ORIO_CALLBACK CALLBACK
  151. #else
  152. #define ORIO_CALLBACK
  153. #endif
  154. struct ORIOCallbacks {
  155. void (ORIO_CALLBACK*BufferSwitch)(LONG bufferIndex, LONG directProcess) noexcept;
  156. void (ORIO_CALLBACK*SampleRateDidChange)(double srate) noexcept;
  157. auto (ORIO_CALLBACK*Message)(LONG selector, LONG value, void *message, double *opt) noexcept -> LONG;
  158. auto (ORIO_CALLBACK*BufferSwitchTimeInfo)(ORIOTime *timeInfo, LONG bufferIndex, LONG directProcess) noexcept -> ORIOTime*;
  159. };
  160. /* COM interfaces don't include a virtual destructor in their pure-virtual
  161. * classes, and we can't add one without breaking ABI.
  162. */
  163. #ifdef __GNUC__
  164. _Pragma("GCC diagnostic push")
  165. _Pragma("GCC diagnostic ignored \"-Wnon-virtual-dtor\"")
  166. #endif
  167. /* NOLINTNEXTLINE(cppcoreguidelines-virtual-class-destructor) */
  168. struct ORIOiface : public IUnknown {
  169. STDMETHOD_(LONG, Init)(void *sysHandle) = 0;
  170. /* A fixed-length span should be passed exactly the same as one pointer.
  171. * This ensures an appropriately-sized buffer for the driver.
  172. */
  173. STDMETHOD_(void, GetDriverName)(al::span<char,32> name) = 0;
  174. STDMETHOD_(LONG, GetDriverVersion)() = 0;
  175. STDMETHOD_(void, GetErrorMessage)(al::span<char,124> message) = 0;
  176. STDMETHOD_(ORIOError, Start)() = 0;
  177. STDMETHOD_(ORIOError, Stop)() = 0;
  178. STDMETHOD_(ORIOError, GetChannels)(LONG *numInput, LONG *numOutput) = 0;
  179. STDMETHOD_(ORIOError, GetLatencies)(LONG *inputLatency, LONG *outputLatency) = 0;
  180. STDMETHOD_(ORIOError, GetBufferSize)(LONG *minSize, LONG *maxSize, LONG *preferredSize, LONG *granularity) = 0;
  181. STDMETHOD_(ORIOError, CanSampleRate)(double srate) = 0;
  182. STDMETHOD_(ORIOError, GetSampleRate)(double *srate) = 0;
  183. STDMETHOD_(ORIOError, SetSampleRate)(double srate) = 0;
  184. STDMETHOD_(ORIOError, GetClockSources)(ORIOClockSource *clocks, LONG *numSources) = 0;
  185. STDMETHOD_(ORIOError, SetClockSource)(LONG index) = 0;
  186. STDMETHOD_(ORIOError, GetSamplePosition)(ORIO64Bit *splPos, ORIO64Bit *tstampNS) = 0;
  187. STDMETHOD_(ORIOError, GetChannelInfo)(ORIOChannelInfo *info) = 0;
  188. STDMETHOD_(ORIOError, CreateBuffers)(ORIOBufferInfo *infos, LONG numInfos, LONG bufferSize, ORIOCallbacks *callbacks) = 0;
  189. STDMETHOD_(ORIOError, DisposeBuffers)() = 0;
  190. STDMETHOD_(ORIOError, ControlPanel)() = 0;
  191. STDMETHOD_(ORIOError, Future)(LONG selector, void *opt) = 0;
  192. STDMETHOD_(ORIOError, OutputReady)() = 0;
  193. ORIOiface() = default;
  194. ORIOiface(const ORIOiface&) = delete;
  195. auto operator=(const ORIOiface&) -> ORIOiface& = delete;
  196. ~ORIOiface() = delete;
  197. };
  198. #ifdef __GNUC__
  199. _Pragma("GCC diagnostic pop")
  200. #endif
  201. namespace {
  202. using namespace std::string_view_literals;
  203. using std::chrono::nanoseconds;
  204. using std::chrono::milliseconds;
  205. using std::chrono::seconds;
  206. struct DeviceEntry {
  207. std::string mDrvName;
  208. CLSID mDrvGuid{};
  209. };
  210. std::vector<DeviceEntry> gDeviceList;
  211. struct KeyCloser {
  212. void operator()(HKEY key) { RegCloseKey(key); }
  213. };
  214. using KeyPtr = std::unique_ptr<std::remove_pointer_t<HKEY>,KeyCloser>;
  215. [[nodiscard]]
  216. auto PopulateDeviceList() -> HRESULT
  217. {
  218. auto regbase = KeyPtr{};
  219. auto res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\ASIO", 0, KEY_READ,
  220. al::out_ptr(regbase));
  221. if(res != ERROR_SUCCESS)
  222. {
  223. ERR("Error opening HKLM\\Software\\ASIO: {}", res);
  224. return E_NOINTERFACE;
  225. }
  226. auto numkeys = DWORD{};
  227. auto maxkeylen = DWORD{};
  228. res = RegQueryInfoKeyW(regbase.get(), nullptr, nullptr, nullptr, &numkeys, &maxkeylen, nullptr,
  229. nullptr, nullptr, nullptr, nullptr, nullptr);
  230. if(res != ERROR_SUCCESS)
  231. {
  232. ERR("Error querying HKLM\\Software\\ASIO info: {}", res);
  233. return E_FAIL;
  234. }
  235. /* maxkeylen is the max number of unicode characters a subkey is. A unicode
  236. * character can occupy two WCHARs, so ensure there's enough space for them
  237. * and the null char.
  238. */
  239. auto keyname = std::vector<WCHAR>(maxkeylen*2 + 1);
  240. for(DWORD i{0};i < numkeys;++i)
  241. {
  242. auto namelen = static_cast<DWORD>(keyname.size());
  243. res = RegEnumKeyExW(regbase.get(), i, keyname.data(), &namelen, nullptr, nullptr, nullptr,
  244. nullptr);
  245. if(res != ERROR_SUCCESS)
  246. {
  247. ERR("Error querying HKLM\\Software\\ASIO subkey {}: {}", i, res);
  248. continue;
  249. }
  250. if(namelen == 0)
  251. {
  252. ERR("HKLM\\Software\\ASIO subkey {} is blank?", i);
  253. continue;
  254. }
  255. auto subkeyname = wstr_to_utf8({keyname.data(), namelen});
  256. auto subkey = KeyPtr{};
  257. res = RegOpenKeyExW(regbase.get(), keyname.data(), 0, KEY_READ, al::out_ptr(subkey));
  258. if(res != ERROR_SUCCESS)
  259. {
  260. ERR("Error opening HKLM\\Software\\ASIO\\{}: {}", subkeyname, res);
  261. continue;
  262. }
  263. auto idstr = std::array<WCHAR,48>{};
  264. auto readsize = DWORD{idstr.size()*sizeof(WCHAR)};
  265. res = RegGetValueW(subkey.get(), L"", L"CLSID", RRF_RT_REG_SZ, nullptr, idstr.data(),
  266. &readsize);
  267. if(res != ERROR_SUCCESS)
  268. {
  269. ERR("Failed to read HKLM\\Software\\ASIO\\{}\\CLSID: {}", subkeyname, res);
  270. continue;
  271. }
  272. idstr.back() = 0;
  273. auto guid = CLSID{};
  274. if(auto hr = CLSIDFromString(idstr.data(), &guid); FAILED(hr))
  275. {
  276. ERR("Failed to parse CLSID \"{}\": {:#x}", wstr_to_utf8(idstr.data()),
  277. as_unsigned(hr));
  278. continue;
  279. }
  280. /* The CLSID is also used for the IID. */
  281. auto iface = ComPtr<ORIOiface>{};
  282. auto hr = CoCreateInstance(guid, nullptr, CLSCTX_INPROC_SERVER, guid, al::out_ptr(iface));
  283. if(SUCCEEDED(hr))
  284. {
  285. #if !ALSOFT_UWP
  286. if(!iface->Init(GetForegroundWindow()))
  287. #else
  288. if(!iface->Init(nullptr))
  289. #endif
  290. {
  291. ERR("Failed to initialize {}", subkeyname);
  292. continue;
  293. }
  294. auto drvname = std::array<char,32>{};
  295. iface->GetDriverName(drvname);
  296. auto drvver = iface->GetDriverVersion();
  297. auto &entry = gDeviceList.emplace_back();
  298. entry.mDrvName = drvname.data();
  299. entry.mDrvGuid = guid;
  300. TRACE("Got {} v{}, CLSID {{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}",
  301. entry.mDrvName, drvver, guid.Data1, guid.Data2, guid.Data3, guid.Data4[0],
  302. guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5],
  303. guid.Data4[6], guid.Data4[7]);
  304. }
  305. else
  306. ERR("Failed to create {} instance for CLSID {{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}: {:#x}",
  307. subkeyname.c_str(), guid.Data1, guid.Data2, guid.Data3, guid.Data4[0],
  308. guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5],
  309. guid.Data4[6], guid.Data4[7], as_unsigned(hr));
  310. }
  311. return S_OK;
  312. }
  313. enum class MsgType {
  314. OpenDevice,
  315. ResetDevice,
  316. StartDevice,
  317. StopDevice,
  318. CloseDevice,
  319. QuitThread
  320. };
  321. constexpr const char *GetMessageTypeName(MsgType type) noexcept
  322. {
  323. switch(type)
  324. {
  325. case MsgType::OpenDevice: return "Open Device";
  326. case MsgType::ResetDevice: return "Reset Device";
  327. case MsgType::StartDevice: return "Start Device";
  328. case MsgType::StopDevice: return "Stop Device";
  329. case MsgType::CloseDevice: return "Close Device";
  330. case MsgType::QuitThread: break;
  331. }
  332. return "";
  333. }
  334. /* Proxy interface used by the message handler, to ensure COM objects are used
  335. * on a thread where COM is initialized.
  336. */
  337. struct OtherIOProxy {
  338. OtherIOProxy() = default;
  339. OtherIOProxy(const OtherIOProxy&) = delete;
  340. OtherIOProxy(OtherIOProxy&&) = delete;
  341. virtual ~OtherIOProxy() = default;
  342. void operator=(const OtherIOProxy&) = delete;
  343. void operator=(OtherIOProxy&&) = delete;
  344. virtual HRESULT openProxy(std::string_view name) = 0;
  345. virtual void closeProxy() = 0;
  346. virtual HRESULT resetProxy() = 0;
  347. virtual HRESULT startProxy() = 0;
  348. virtual void stopProxy() = 0;
  349. struct Msg {
  350. MsgType mType;
  351. OtherIOProxy *mProxy;
  352. std::string_view mParam;
  353. std::promise<HRESULT> mPromise;
  354. explicit operator bool() const noexcept { return mType != MsgType::QuitThread; }
  355. };
  356. static inline std::deque<Msg> mMsgQueue;
  357. static inline std::mutex mMsgQueueLock;
  358. static inline std::condition_variable mMsgQueueCond;
  359. auto pushMessage(MsgType type, std::string_view param={}) -> std::future<HRESULT>
  360. {
  361. auto promise = std::promise<HRESULT>{};
  362. auto future = std::future<HRESULT>{promise.get_future()};
  363. {
  364. auto msglock = std::lock_guard{mMsgQueueLock};
  365. mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)});
  366. }
  367. mMsgQueueCond.notify_one();
  368. return future;
  369. }
  370. static auto popMessage() -> Msg
  371. {
  372. auto lock = std::unique_lock{mMsgQueueLock};
  373. mMsgQueueCond.wait(lock, []{return !mMsgQueue.empty();});
  374. auto msg = Msg{std::move(mMsgQueue.front())};
  375. mMsgQueue.pop_front();
  376. return msg;
  377. }
  378. static void messageHandler(std::promise<HRESULT> *promise);
  379. };
  380. void OtherIOProxy::messageHandler(std::promise<HRESULT> *promise)
  381. {
  382. TRACE("Starting COM message thread");
  383. auto com = ComWrapper{COINIT_APARTMENTTHREADED};
  384. if(!com)
  385. {
  386. WARN("Failed to initialize COM: {:#x}", as_unsigned(com.status()));
  387. promise->set_value(com.status());
  388. return;
  389. }
  390. auto hr = PopulateDeviceList();
  391. if(FAILED(hr))
  392. {
  393. promise->set_value(hr);
  394. return;
  395. }
  396. promise->set_value(S_OK);
  397. promise = nullptr;
  398. TRACE("Starting message loop");
  399. while(Msg msg{popMessage()})
  400. {
  401. TRACE("Got message \"{}\" ({:#04x}, this={}, param=\"{}\")",
  402. GetMessageTypeName(msg.mType), static_cast<uint>(msg.mType),
  403. static_cast<void*>(msg.mProxy), msg.mParam);
  404. switch(msg.mType)
  405. {
  406. case MsgType::OpenDevice:
  407. hr = msg.mProxy->openProxy(msg.mParam);
  408. msg.mPromise.set_value(hr);
  409. continue;
  410. case MsgType::ResetDevice:
  411. hr = msg.mProxy->resetProxy();
  412. msg.mPromise.set_value(hr);
  413. continue;
  414. case MsgType::StartDevice:
  415. hr = msg.mProxy->startProxy();
  416. msg.mPromise.set_value(hr);
  417. continue;
  418. case MsgType::StopDevice:
  419. msg.mProxy->stopProxy();
  420. msg.mPromise.set_value(S_OK);
  421. continue;
  422. case MsgType::CloseDevice:
  423. msg.mProxy->closeProxy();
  424. msg.mPromise.set_value(S_OK);
  425. continue;
  426. case MsgType::QuitThread:
  427. break;
  428. }
  429. ERR("Unexpected message: {}", int{al::to_underlying(msg.mType)});
  430. msg.mPromise.set_value(E_FAIL);
  431. }
  432. TRACE("Message loop finished");
  433. }
  434. struct OtherIOPlayback final : public BackendBase, OtherIOProxy {
  435. explicit OtherIOPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
  436. ~OtherIOPlayback() final;
  437. void mixerProc();
  438. void open(std::string_view name) final;
  439. auto openProxy(std::string_view name) -> HRESULT final;
  440. void closeProxy() final;
  441. auto reset() -> bool final;
  442. auto resetProxy() -> HRESULT final;
  443. void start() final;
  444. auto startProxy() -> HRESULT final;
  445. void stop() final;
  446. void stopProxy() final;
  447. HRESULT mOpenStatus{E_FAIL};
  448. std::atomic<bool> mKillNow{true};
  449. std::thread mThread;
  450. };
  451. OtherIOPlayback::~OtherIOPlayback()
  452. {
  453. if(SUCCEEDED(mOpenStatus))
  454. pushMessage(MsgType::CloseDevice).wait();
  455. }
  456. void OtherIOPlayback::mixerProc()
  457. {
  458. const auto restTime = milliseconds{mDevice->mUpdateSize*1000/mDevice->mSampleRate / 2};
  459. SetRTPriority();
  460. althrd_setname(GetMixerThreadName());
  461. auto done = int64_t{0};
  462. auto start = std::chrono::steady_clock::now();
  463. while(!mKillNow.load(std::memory_order_acquire)
  464. && mDevice->Connected.load(std::memory_order_acquire))
  465. {
  466. auto now = std::chrono::steady_clock::now();
  467. /* This converts from nanoseconds to nanosamples, then to samples. */
  468. const auto avail = int64_t{std::chrono::duration_cast<seconds>((now-start)
  469. * mDevice->mSampleRate).count()};
  470. if(avail-done < mDevice->mUpdateSize)
  471. {
  472. std::this_thread::sleep_for(restTime);
  473. continue;
  474. }
  475. while(avail-done >= mDevice->mUpdateSize)
  476. {
  477. mDevice->renderSamples(nullptr, mDevice->mUpdateSize, 0u);
  478. done += mDevice->mUpdateSize;
  479. }
  480. if(done >= mDevice->mSampleRate)
  481. {
  482. auto s = seconds{done/mDevice->mSampleRate};
  483. start += s;
  484. done -= mDevice->mSampleRate*s.count();
  485. }
  486. }
  487. }
  488. void OtherIOPlayback::open(std::string_view name)
  489. {
  490. if(name.empty() && !gDeviceList.empty())
  491. name = gDeviceList[0].mDrvName;
  492. else
  493. {
  494. auto iter = std::find_if(gDeviceList.cbegin(), gDeviceList.cend(),
  495. [name](const DeviceEntry &entry) { return entry.mDrvName == name; });
  496. if(iter == gDeviceList.cend())
  497. throw al::backend_exception{al::backend_error::NoDevice,
  498. "Device name \"{}\" not found", name};
  499. }
  500. mOpenStatus = pushMessage(MsgType::OpenDevice, name).get();
  501. if(FAILED(mOpenStatus))
  502. throw al::backend_exception{al::backend_error::DeviceError, "Failed to open \"{}\"", name};
  503. mDeviceName = name;
  504. }
  505. auto OtherIOPlayback::openProxy(std::string_view name [[maybe_unused]]) -> HRESULT
  506. {
  507. return S_OK;
  508. }
  509. void OtherIOPlayback::closeProxy()
  510. {
  511. }
  512. auto OtherIOPlayback::reset() -> bool
  513. {
  514. return SUCCEEDED(pushMessage(MsgType::ResetDevice).get());
  515. }
  516. auto OtherIOPlayback::resetProxy() -> HRESULT
  517. {
  518. setDefaultWFXChannelOrder();
  519. return S_OK;
  520. }
  521. void OtherIOPlayback::start()
  522. {
  523. auto hr = pushMessage(MsgType::StartDevice).get();
  524. if(FAILED(hr))
  525. throw al::backend_exception{al::backend_error::DeviceError,
  526. "Failed to start playback: {:#x}", as_unsigned(hr)};
  527. }
  528. auto OtherIOPlayback::startProxy() -> HRESULT
  529. {
  530. try {
  531. mKillNow.store(false, std::memory_order_release);
  532. mThread = std::thread{&OtherIOPlayback::mixerProc, this};
  533. return S_OK;
  534. }
  535. catch(std::exception& e) {
  536. ERR("Failed to start mixing thread: {}", e.what());
  537. }
  538. return E_FAIL;
  539. }
  540. void OtherIOPlayback::stop()
  541. {
  542. pushMessage(MsgType::StopDevice).wait();
  543. }
  544. void OtherIOPlayback::stopProxy()
  545. {
  546. if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
  547. return;
  548. mThread.join();
  549. }
  550. } // namespace
  551. auto OtherIOBackendFactory::init() -> bool
  552. {
  553. static HRESULT InitResult{E_FAIL};
  554. if(FAILED(InitResult)) try
  555. {
  556. auto promise = std::promise<HRESULT>{};
  557. auto future = promise.get_future();
  558. std::thread{&OtherIOProxy::messageHandler, &promise}.detach();
  559. InitResult = future.get();
  560. }
  561. catch(...) {
  562. }
  563. return SUCCEEDED(InitResult);
  564. }
  565. auto OtherIOBackendFactory::querySupport(BackendType type) -> bool
  566. { return type == BackendType::Playback; }
  567. auto OtherIOBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
  568. {
  569. std::vector<std::string> outnames;
  570. switch(type)
  571. {
  572. case BackendType::Playback:
  573. std::for_each(gDeviceList.cbegin(), gDeviceList.cend(),
  574. [&outnames](const DeviceEntry &entry) { outnames.emplace_back(entry.mDrvName); });
  575. break;
  576. case BackendType::Capture:
  577. break;
  578. }
  579. return outnames;
  580. }
  581. auto OtherIOBackendFactory::createBackend(DeviceBase *device, BackendType type) -> BackendPtr
  582. {
  583. if(type == BackendType::Playback)
  584. return BackendPtr{new OtherIOPlayback{device}};
  585. return nullptr;
  586. }
  587. auto OtherIOBackendFactory::getFactory() -> BackendFactory&
  588. {
  589. static auto factory = OtherIOBackendFactory{};
  590. return factory;
  591. }
  592. auto OtherIOBackendFactory::queryEventSupport(alc::EventType, BackendType) -> alc::EventSupport
  593. {
  594. return alc::EventSupport::NoSupport;
  595. }