oss.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  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 "oss.h"
  22. #include <fcntl.h>
  23. #include <poll.h>
  24. #include <sys/ioctl.h>
  25. #include <sys/stat.h>
  26. #include <unistd.h>
  27. #include <algorithm>
  28. #include <atomic>
  29. #include <cerrno>
  30. #include <cstring>
  31. #include <exception>
  32. #include <memory>
  33. #include <string>
  34. #include <string_view>
  35. #include <system_error>
  36. #include <thread>
  37. #include <utility>
  38. #include <vector>
  39. #include "alc/alconfig.h"
  40. #include "alnumeric.h"
  41. #include "althrd_setname.h"
  42. #include "core/device.h"
  43. #include "core/helpers.h"
  44. #include "core/logging.h"
  45. #include "fmt/core.h"
  46. #include "ringbuffer.h"
  47. #include <sys/soundcard.h>
  48. /*
  49. * The OSS documentation talks about SOUND_MIXER_READ, but the header
  50. * only contains MIXER_READ. Play safe. Same for WRITE.
  51. */
  52. #ifndef SOUND_MIXER_READ
  53. #define SOUND_MIXER_READ MIXER_READ
  54. #endif
  55. #ifndef SOUND_MIXER_WRITE
  56. #define SOUND_MIXER_WRITE MIXER_WRITE
  57. #endif
  58. #if defined(SOUND_VERSION) && (SOUND_VERSION < 0x040000)
  59. #define ALC_OSS_COMPAT
  60. #endif
  61. #ifndef SNDCTL_AUDIOINFO
  62. #define ALC_OSS_COMPAT
  63. #endif
  64. /*
  65. * FreeBSD strongly discourages the use of specific devices,
  66. * such as those returned in oss_audioinfo.devnode
  67. */
  68. #ifdef __FreeBSD__
  69. #define ALC_OSS_DEVNODE_TRUC
  70. #endif
  71. namespace {
  72. using namespace std::string_literals;
  73. using namespace std::string_view_literals;
  74. [[nodiscard]] constexpr auto GetDefaultName() noexcept { return "OSS Default"sv; }
  75. std::string DefaultPlayback{"/dev/dsp"s};
  76. std::string DefaultCapture{"/dev/dsp"s};
  77. struct DevMap {
  78. std::string name;
  79. std::string device_name;
  80. template<typename T, typename U>
  81. DevMap(T&& name_, U&& devname_)
  82. : name{std::forward<T>(name_)}, device_name{std::forward<U>(devname_)}
  83. { }
  84. };
  85. std::vector<DevMap> PlaybackDevices;
  86. std::vector<DevMap> CaptureDevices;
  87. #ifdef ALC_OSS_COMPAT
  88. #define DSP_CAP_OUTPUT 0x00020000
  89. #define DSP_CAP_INPUT 0x00010000
  90. void ALCossListPopulate(std::vector<DevMap> &devlist, int type)
  91. {
  92. devlist.emplace_back(GetDefaultName(), (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback);
  93. }
  94. #else
  95. class FileHandle {
  96. int mFd{-1};
  97. public:
  98. FileHandle() = default;
  99. FileHandle(const FileHandle&) = delete;
  100. FileHandle& operator=(const FileHandle&) = delete;
  101. ~FileHandle() { if(mFd != -1) ::close(mFd); }
  102. template<typename ...Args>
  103. [[nodiscard]] auto open(const char *fname, Args&& ...args) -> bool
  104. {
  105. close();
  106. mFd = ::open(fname, std::forward<Args>(args)...);
  107. return mFd != -1;
  108. }
  109. void close()
  110. {
  111. if(mFd != -1)
  112. ::close(mFd);
  113. mFd = -1;
  114. }
  115. [[nodiscard]]
  116. auto get() const noexcept -> int { return mFd; }
  117. };
  118. void ALCossListAppend(std::vector<DevMap> &list, std::string_view handle, std::string_view path)
  119. {
  120. #ifdef ALC_OSS_DEVNODE_TRUC
  121. for(size_t i{0};i < path.size();++i)
  122. {
  123. if(path[i] == '.' && handle.size() >= path.size() - i)
  124. {
  125. const size_t hoffset{handle.size() + i - path.size()};
  126. if(strncmp(path.data() + i, handle.data() + hoffset, path.size() - i) == 0)
  127. handle = handle.substr(0, hoffset);
  128. path = path.substr(0, i);
  129. }
  130. }
  131. #endif
  132. if(handle.empty())
  133. handle = path;
  134. auto match_devname = [path](const DevMap &entry) -> bool
  135. { return entry.device_name == path; };
  136. if(std::find_if(list.cbegin(), list.cend(), match_devname) != list.cend())
  137. return;
  138. auto checkName = [&list](const std::string_view name) -> bool
  139. {
  140. auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name; };
  141. return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
  142. };
  143. auto count = 1;
  144. auto newname = std::string{handle};
  145. while(checkName(newname))
  146. newname = fmt::format("{} #{}", handle, ++count);
  147. const auto &entry = list.emplace_back(std::move(newname), path);
  148. TRACE("Got device \"{}\", \"{}\"", entry.name, entry.device_name);
  149. }
  150. void ALCossListPopulate(std::vector<DevMap> &devlist, int type_flag)
  151. {
  152. oss_sysinfo si{};
  153. FileHandle file;
  154. if(!file.open("/dev/mixer", O_RDONLY))
  155. {
  156. TRACE("Could not open /dev/mixer: {}", std::generic_category().message(errno));
  157. goto done;
  158. }
  159. if(ioctl(file.get(), SNDCTL_SYSINFO, &si) == -1)
  160. {
  161. TRACE("SNDCTL_SYSINFO failed: {}", std::generic_category().message(errno));
  162. goto done;
  163. }
  164. for(int i{0};i < si.numaudios;i++)
  165. {
  166. oss_audioinfo ai{};
  167. ai.dev = i;
  168. if(ioctl(file.get(), SNDCTL_AUDIOINFO, &ai) == -1)
  169. {
  170. ERR("SNDCTL_AUDIOINFO ({}) failed: {}", i, std::generic_category().message(errno));
  171. continue;
  172. }
  173. if(!(ai.caps&type_flag) || ai.devnode[0] == '\0')
  174. continue;
  175. std::string_view handle;
  176. if(ai.handle[0] != '\0')
  177. handle = {ai.handle, strnlen(ai.handle, sizeof(ai.handle))};
  178. else
  179. handle = {ai.name, strnlen(ai.name, sizeof(ai.name))};
  180. const std::string_view devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))};
  181. ALCossListAppend(devlist, handle, devnode);
  182. }
  183. done:
  184. file.close();
  185. const char *defdev{((type_flag==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback).c_str()};
  186. auto iter = std::find_if(devlist.cbegin(), devlist.cend(),
  187. [defdev](const DevMap &entry) -> bool
  188. { return entry.device_name == defdev; }
  189. );
  190. if(iter == devlist.cend())
  191. devlist.insert(devlist.begin(), DevMap{GetDefaultName(), defdev});
  192. else
  193. {
  194. DevMap entry{std::move(*iter)};
  195. devlist.erase(iter);
  196. devlist.insert(devlist.begin(), std::move(entry));
  197. }
  198. devlist.shrink_to_fit();
  199. }
  200. #endif
  201. uint log2i(uint x)
  202. {
  203. uint y{0};
  204. while(x > 1)
  205. {
  206. x >>= 1;
  207. y++;
  208. }
  209. return y;
  210. }
  211. struct OSSPlayback final : public BackendBase {
  212. explicit OSSPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
  213. ~OSSPlayback() override;
  214. int mixerProc();
  215. void open(std::string_view name) override;
  216. bool reset() override;
  217. void start() override;
  218. void stop() override;
  219. int mFd{-1};
  220. std::vector<std::byte> mMixData;
  221. std::atomic<bool> mKillNow{true};
  222. std::thread mThread;
  223. };
  224. OSSPlayback::~OSSPlayback()
  225. {
  226. if(mFd != -1)
  227. ::close(mFd);
  228. mFd = -1;
  229. }
  230. int OSSPlayback::mixerProc()
  231. {
  232. SetRTPriority();
  233. althrd_setname(GetMixerThreadName());
  234. const size_t frame_step{mDevice->channelsFromFmt()};
  235. const size_t frame_size{mDevice->frameSizeFromFmt()};
  236. while(!mKillNow.load(std::memory_order_acquire)
  237. && mDevice->Connected.load(std::memory_order_acquire))
  238. {
  239. pollfd pollitem{};
  240. pollitem.fd = mFd;
  241. pollitem.events = POLLOUT;
  242. if(int pret{poll(&pollitem, 1, 1000)}; pret < 0)
  243. {
  244. if(errno == EINTR || errno == EAGAIN)
  245. continue;
  246. const auto errstr = std::generic_category().message(errno);
  247. ERR("poll failed: {}", errstr);
  248. mDevice->handleDisconnect("Failed waiting for playback buffer: {}", errstr);
  249. break;
  250. }
  251. else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */
  252. {
  253. WARN("poll timeout");
  254. continue;
  255. }
  256. al::span write_buf{mMixData};
  257. mDevice->renderSamples(write_buf.data(), static_cast<uint>(write_buf.size()/frame_size),
  258. frame_step);
  259. while(!write_buf.empty() && !mKillNow.load(std::memory_order_acquire))
  260. {
  261. ssize_t wrote{write(mFd, write_buf.data(), write_buf.size())};
  262. if(wrote < 0)
  263. {
  264. if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
  265. continue;
  266. const auto errstr = std::generic_category().message(errno);
  267. ERR("write failed: {}", errstr);
  268. mDevice->handleDisconnect("Failed writing playback samples: {}", errstr);
  269. break;
  270. }
  271. write_buf = write_buf.subspan(static_cast<size_t>(wrote));
  272. }
  273. }
  274. return 0;
  275. }
  276. void OSSPlayback::open(std::string_view name)
  277. {
  278. const char *devname{DefaultPlayback.c_str()};
  279. if(name.empty())
  280. name = GetDefaultName();
  281. else
  282. {
  283. if(PlaybackDevices.empty())
  284. ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT);
  285. auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
  286. [&name](const DevMap &entry) -> bool
  287. { return entry.name == name; }
  288. );
  289. if(iter == PlaybackDevices.cend())
  290. throw al::backend_exception{al::backend_error::NoDevice,
  291. "Device name \"{}\" not found", name};
  292. devname = iter->device_name.c_str();
  293. }
  294. const auto fd = ::open(devname, O_WRONLY); /* NOLINT(cppcoreguidelines-pro-type-vararg) */
  295. if(fd == -1)
  296. throw al::backend_exception{al::backend_error::NoDevice, "Could not open {}: {}", devname,
  297. std::generic_category().message(errno)};
  298. if(mFd != -1)
  299. ::close(mFd);
  300. mFd = fd;
  301. mDeviceName = name;
  302. }
  303. bool OSSPlayback::reset()
  304. {
  305. int ossFormat{};
  306. switch(mDevice->FmtType)
  307. {
  308. case DevFmtByte:
  309. ossFormat = AFMT_S8;
  310. break;
  311. case DevFmtUByte:
  312. ossFormat = AFMT_U8;
  313. break;
  314. case DevFmtUShort:
  315. case DevFmtInt:
  316. case DevFmtUInt:
  317. case DevFmtFloat:
  318. mDevice->FmtType = DevFmtShort;
  319. /* fall-through */
  320. case DevFmtShort:
  321. ossFormat = AFMT_S16_NE;
  322. break;
  323. }
  324. uint periods{mDevice->mBufferSize / mDevice->mUpdateSize};
  325. uint numChannels{mDevice->channelsFromFmt()};
  326. uint ossSpeed{mDevice->mSampleRate};
  327. uint frameSize{numChannels * mDevice->bytesFromFmt()};
  328. /* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */
  329. uint log2FragmentSize{std::max(log2i(mDevice->mUpdateSize*frameSize), 4u)};
  330. uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
  331. audio_buf_info info{};
  332. #define CHECKERR(func) if((func) < 0) \
  333. throw al::backend_exception{al::backend_error::DeviceError, #func " failed: {}", \
  334. std::generic_category().message(errno)};
  335. /* Don't fail if SETFRAGMENT fails. We can handle just about anything
  336. * that's reported back via GETOSPACE */
  337. /* NOLINTBEGIN(cppcoreguidelines-pro-type-vararg) */
  338. ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize);
  339. CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
  340. CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels));
  341. CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed));
  342. CHECKERR(ioctl(mFd, SNDCTL_DSP_GETOSPACE, &info));
  343. /* NOLINTEND(cppcoreguidelines-pro-type-vararg) */
  344. #undef CHECKERR
  345. if(mDevice->channelsFromFmt() != numChannels)
  346. {
  347. ERR("Failed to set {}, got {} channels instead", DevFmtChannelsString(mDevice->FmtChans),
  348. numChannels);
  349. return false;
  350. }
  351. if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte) ||
  352. (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) ||
  353. (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort)))
  354. {
  355. ERR("Failed to set {} samples, got OSS format {:#x}", DevFmtTypeString(mDevice->FmtType),
  356. as_unsigned(ossFormat));
  357. return false;
  358. }
  359. mDevice->mSampleRate = ossSpeed;
  360. mDevice->mUpdateSize = static_cast<uint>(info.fragsize) / frameSize;
  361. mDevice->mBufferSize = static_cast<uint>(info.fragments) * mDevice->mUpdateSize;
  362. setDefaultChannelOrder();
  363. mMixData.resize(size_t{mDevice->mUpdateSize} * mDevice->frameSizeFromFmt());
  364. return true;
  365. }
  366. void OSSPlayback::start()
  367. {
  368. try {
  369. mKillNow.store(false, std::memory_order_release);
  370. mThread = std::thread{&OSSPlayback::mixerProc, this};
  371. }
  372. catch(std::exception& e) {
  373. throw al::backend_exception{al::backend_error::DeviceError,
  374. "Failed to start mixing thread: {}", e.what()};
  375. }
  376. }
  377. void OSSPlayback::stop()
  378. {
  379. if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
  380. return;
  381. mThread.join();
  382. if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) /* NOLINT(cppcoreguidelines-pro-type-vararg) */
  383. ERR("Error resetting device: {}", std::generic_category().message(errno));
  384. }
  385. struct OSScapture final : public BackendBase {
  386. explicit OSScapture(DeviceBase *device) noexcept : BackendBase{device} { }
  387. ~OSScapture() override;
  388. int recordProc();
  389. void open(std::string_view name) override;
  390. void start() override;
  391. void stop() override;
  392. void captureSamples(std::byte *buffer, uint samples) override;
  393. uint availableSamples() override;
  394. int mFd{-1};
  395. RingBufferPtr mRing{nullptr};
  396. std::atomic<bool> mKillNow{true};
  397. std::thread mThread;
  398. };
  399. OSScapture::~OSScapture()
  400. {
  401. if(mFd != -1)
  402. close(mFd);
  403. mFd = -1;
  404. }
  405. int OSScapture::recordProc()
  406. {
  407. SetRTPriority();
  408. althrd_setname(GetRecordThreadName());
  409. const size_t frame_size{mDevice->frameSizeFromFmt()};
  410. while(!mKillNow.load(std::memory_order_acquire))
  411. {
  412. pollfd pollitem{};
  413. pollitem.fd = mFd;
  414. pollitem.events = POLLIN;
  415. if(int pret{poll(&pollitem, 1, 1000)}; pret < 0)
  416. {
  417. if(errno == EINTR || errno == EAGAIN)
  418. continue;
  419. const auto errstr = std::generic_category().message(errno);
  420. ERR("poll failed: {}", errstr);
  421. mDevice->handleDisconnect("Failed to check capture samples: {}", errstr);
  422. break;
  423. }
  424. else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */
  425. {
  426. WARN("poll timeout");
  427. continue;
  428. }
  429. auto vec = mRing->getWriteVector();
  430. if(vec[0].len > 0)
  431. {
  432. ssize_t amt{read(mFd, vec[0].buf, vec[0].len*frame_size)};
  433. if(amt < 0)
  434. {
  435. const auto errstr = std::generic_category().message(errno);
  436. ERR("read failed: {}", errstr);
  437. mDevice->handleDisconnect("Failed reading capture samples: {}", errstr);
  438. break;
  439. }
  440. mRing->writeAdvance(static_cast<size_t>(amt)/frame_size);
  441. }
  442. }
  443. return 0;
  444. }
  445. void OSScapture::open(std::string_view name)
  446. {
  447. const char *devname{DefaultCapture.c_str()};
  448. if(name.empty())
  449. name = GetDefaultName();
  450. else
  451. {
  452. if(CaptureDevices.empty())
  453. ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT);
  454. auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
  455. [&name](const DevMap &entry) -> bool
  456. { return entry.name == name; }
  457. );
  458. if(iter == CaptureDevices.cend())
  459. throw al::backend_exception{al::backend_error::NoDevice,
  460. "Device name \"{}\" not found", name};
  461. devname = iter->device_name.c_str();
  462. }
  463. mFd = ::open(devname, O_RDONLY); /* NOLINT(cppcoreguidelines-pro-type-vararg) */
  464. if(mFd == -1)
  465. throw al::backend_exception{al::backend_error::NoDevice, "Could not open {}: {}", devname,
  466. std::generic_category().message(errno)};
  467. int ossFormat{};
  468. switch(mDevice->FmtType)
  469. {
  470. case DevFmtByte:
  471. ossFormat = AFMT_S8;
  472. break;
  473. case DevFmtUByte:
  474. ossFormat = AFMT_U8;
  475. break;
  476. case DevFmtShort:
  477. ossFormat = AFMT_S16_NE;
  478. break;
  479. case DevFmtUShort:
  480. case DevFmtInt:
  481. case DevFmtUInt:
  482. case DevFmtFloat:
  483. throw al::backend_exception{al::backend_error::DeviceError,
  484. "{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
  485. }
  486. uint periods{4};
  487. uint numChannels{mDevice->channelsFromFmt()};
  488. uint frameSize{numChannels * mDevice->bytesFromFmt()};
  489. uint ossSpeed{mDevice->mSampleRate};
  490. /* according to the OSS spec, 16 bytes are the minimum */
  491. uint log2FragmentSize{std::max(log2i(mDevice->mBufferSize * frameSize / periods), 4u)};
  492. uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
  493. audio_buf_info info{};
  494. #define CHECKERR(func) if((func) < 0) { \
  495. throw al::backend_exception{al::backend_error::DeviceError, #func " failed: {}", \
  496. std::generic_category().message(errno)}; \
  497. }
  498. /* NOLINTBEGIN(cppcoreguidelines-pro-type-vararg) */
  499. CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize));
  500. CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
  501. CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels));
  502. CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed));
  503. CHECKERR(ioctl(mFd, SNDCTL_DSP_GETISPACE, &info));
  504. /* NOLINTEND(cppcoreguidelines-pro-type-vararg) */
  505. #undef CHECKERR
  506. if(mDevice->channelsFromFmt() != numChannels)
  507. throw al::backend_exception{al::backend_error::DeviceError,
  508. "Failed to set {}, got {} channels instead", DevFmtChannelsString(mDevice->FmtChans),
  509. numChannels};
  510. if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte)
  511. || (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte)
  512. || (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort)))
  513. throw al::backend_exception{al::backend_error::DeviceError,
  514. "Failed to set {} samples, got OSS format {:#x}", DevFmtTypeString(mDevice->FmtType),
  515. as_unsigned(ossFormat)};
  516. mRing = RingBuffer::Create(mDevice->mBufferSize, frameSize, false);
  517. mDeviceName = name;
  518. }
  519. void OSScapture::start()
  520. {
  521. try {
  522. mKillNow.store(false, std::memory_order_release);
  523. mThread = std::thread{&OSScapture::recordProc, this};
  524. }
  525. catch(std::exception& e) {
  526. throw al::backend_exception{al::backend_error::DeviceError,
  527. "Failed to start recording thread: {}", e.what()};
  528. }
  529. }
  530. void OSScapture::stop()
  531. {
  532. if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
  533. return;
  534. mThread.join();
  535. if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) /* NOLINT(cppcoreguidelines-pro-type-vararg) */
  536. ERR("Error resetting device: {}", std::generic_category().message(errno));
  537. }
  538. void OSScapture::captureSamples(std::byte *buffer, uint samples)
  539. { std::ignore = mRing->read(buffer, samples); }
  540. uint OSScapture::availableSamples()
  541. { return static_cast<uint>(mRing->readSpace()); }
  542. } // namespace
  543. BackendFactory &OSSBackendFactory::getFactory()
  544. {
  545. static OSSBackendFactory factory{};
  546. return factory;
  547. }
  548. bool OSSBackendFactory::init()
  549. {
  550. if(auto devopt = ConfigValueStr({}, "oss", "device"))
  551. DefaultPlayback = std::move(*devopt);
  552. if(auto capopt = ConfigValueStr({}, "oss", "capture"))
  553. DefaultCapture = std::move(*capopt);
  554. return true;
  555. }
  556. bool OSSBackendFactory::querySupport(BackendType type)
  557. { return (type == BackendType::Playback || type == BackendType::Capture); }
  558. auto OSSBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
  559. {
  560. std::vector<std::string> outnames;
  561. auto add_device = [&outnames](const DevMap &entry) -> void
  562. {
  563. if(struct stat buf{}; stat(entry.device_name.c_str(), &buf) == 0)
  564. outnames.emplace_back(entry.name);
  565. };
  566. switch(type)
  567. {
  568. case BackendType::Playback:
  569. PlaybackDevices.clear();
  570. ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT);
  571. outnames.reserve(PlaybackDevices.size());
  572. std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
  573. break;
  574. case BackendType::Capture:
  575. CaptureDevices.clear();
  576. ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT);
  577. outnames.reserve(CaptureDevices.size());
  578. std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
  579. break;
  580. }
  581. return outnames;
  582. }
  583. BackendPtr OSSBackendFactory::createBackend(DeviceBase *device, BackendType type)
  584. {
  585. if(type == BackendType::Playback)
  586. return BackendPtr{new OSSPlayback{device}};
  587. if(type == BackendType::Capture)
  588. return BackendPtr{new OSScapture{device}};
  589. return nullptr;
  590. }