solaris.cpp 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  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 "backends/solaris.h"
  22. #include <sys/ioctl.h>
  23. #include <sys/types.h>
  24. #include <sys/time.h>
  25. #include <sys/stat.h>
  26. #include <fcntl.h>
  27. #include <stdlib.h>
  28. #include <stdio.h>
  29. #include <memory.h>
  30. #include <unistd.h>
  31. #include <errno.h>
  32. #include <poll.h>
  33. #include <math.h>
  34. #include <thread>
  35. #include <functional>
  36. #include "alcmain.h"
  37. #include "albyte.h"
  38. #include "alu.h"
  39. #include "alconfig.h"
  40. #include "compat.h"
  41. #include "core/logging.h"
  42. #include "threads.h"
  43. #include "vector.h"
  44. #include <sys/audioio.h>
  45. namespace {
  46. constexpr char solaris_device[] = "Solaris Default";
  47. std::string solaris_driver{"/dev/audio"};
  48. struct SolarisBackend final : public BackendBase {
  49. SolarisBackend(ALCdevice *device) noexcept : BackendBase{device} { }
  50. ~SolarisBackend() override;
  51. int mixerProc();
  52. void open(const char *name) override;
  53. bool reset() override;
  54. void start() override;
  55. void stop() override;
  56. int mFd{-1};
  57. al::vector<al::byte> mBuffer;
  58. std::atomic<bool> mKillNow{true};
  59. std::thread mThread;
  60. DEF_NEWDEL(SolarisBackend)
  61. };
  62. SolarisBackend::~SolarisBackend()
  63. {
  64. if(mFd != -1)
  65. close(mFd);
  66. mFd = -1;
  67. }
  68. int SolarisBackend::mixerProc()
  69. {
  70. SetRTPriority();
  71. althrd_setname(MIXER_THREAD_NAME);
  72. const size_t frame_step{mDevice->channelsFromFmt()};
  73. const uint frame_size{mDevice->frameSizeFromFmt()};
  74. while(!mKillNow.load(std::memory_order_acquire)
  75. && mDevice->Connected.load(std::memory_order_acquire))
  76. {
  77. pollfd pollitem{};
  78. pollitem.fd = mFd;
  79. pollitem.events = POLLOUT;
  80. int pret{poll(&pollitem, 1, 1000)};
  81. if(pret < 0)
  82. {
  83. if(errno == EINTR || errno == EAGAIN)
  84. continue;
  85. ERR("poll failed: %s\n", strerror(errno));
  86. mDevice->handleDisconnect("Failed to wait for playback buffer: %s", strerror(errno));
  87. break;
  88. }
  89. else if(pret == 0)
  90. {
  91. WARN("poll timeout\n");
  92. continue;
  93. }
  94. al::byte *write_ptr{mBuffer.data()};
  95. size_t to_write{mBuffer.size()};
  96. mDevice->renderSamples(write_ptr, static_cast<uint>(to_write/frame_size), frame_step);
  97. while(to_write > 0 && !mKillNow.load(std::memory_order_acquire))
  98. {
  99. ssize_t wrote{write(mFd, write_ptr, to_write)};
  100. if(wrote < 0)
  101. {
  102. if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
  103. continue;
  104. ERR("write failed: %s\n", strerror(errno));
  105. mDevice->handleDisconnect("Failed to write playback samples: %s", strerror(errno));
  106. break;
  107. }
  108. to_write -= static_cast<size_t>(wrote);
  109. write_ptr += wrote;
  110. }
  111. }
  112. return 0;
  113. }
  114. void SolarisBackend::open(const char *name)
  115. {
  116. if(!name)
  117. name = solaris_device;
  118. else if(strcmp(name, solaris_device) != 0)
  119. throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
  120. name};
  121. mFd = ::open(solaris_driver.c_str(), O_WRONLY);
  122. if(mFd == -1)
  123. throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s",
  124. solaris_driver.c_str(), strerror(errno)};
  125. mDevice->DeviceName = name;
  126. }
  127. bool SolarisBackend::reset()
  128. {
  129. audio_info_t info;
  130. AUDIO_INITINFO(&info);
  131. info.play.sample_rate = mDevice->Frequency;
  132. if(mDevice->FmtChans != DevFmtMono)
  133. mDevice->FmtChans = DevFmtStereo;
  134. uint numChannels{mDevice->channelsFromFmt()};
  135. info.play.channels = numChannels;
  136. switch(mDevice->FmtType)
  137. {
  138. case DevFmtByte:
  139. info.play.precision = 8;
  140. info.play.encoding = AUDIO_ENCODING_LINEAR;
  141. break;
  142. case DevFmtUByte:
  143. info.play.precision = 8;
  144. info.play.encoding = AUDIO_ENCODING_LINEAR8;
  145. break;
  146. case DevFmtUShort:
  147. case DevFmtInt:
  148. case DevFmtUInt:
  149. case DevFmtFloat:
  150. mDevice->FmtType = DevFmtShort;
  151. /* fall-through */
  152. case DevFmtShort:
  153. info.play.precision = 16;
  154. info.play.encoding = AUDIO_ENCODING_LINEAR;
  155. break;
  156. }
  157. uint frameSize{numChannels * mDevice->bytesFromFmt()};
  158. info.play.buffer_size = mDevice->BufferSize * frameSize;
  159. if(ioctl(mFd, AUDIO_SETINFO, &info) < 0)
  160. {
  161. ERR("ioctl failed: %s\n", strerror(errno));
  162. return false;
  163. }
  164. if(mDevice->channelsFromFmt() != info.play.channels)
  165. {
  166. ERR("Failed to set %s, got %u channels instead\n", DevFmtChannelsString(mDevice->FmtChans),
  167. info.play.channels);
  168. return false;
  169. }
  170. if(!((info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8 && mDevice->FmtType == DevFmtUByte) ||
  171. (info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtByte) ||
  172. (info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtShort) ||
  173. (info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtInt)))
  174. {
  175. ERR("Could not set %s samples, got %d (0x%x)\n", DevFmtTypeString(mDevice->FmtType),
  176. info.play.precision, info.play.encoding);
  177. return false;
  178. }
  179. mDevice->Frequency = info.play.sample_rate;
  180. mDevice->BufferSize = info.play.buffer_size / frameSize;
  181. mDevice->UpdateSize = mDevice->BufferSize / 2;
  182. setDefaultChannelOrder();
  183. mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
  184. std::fill(mBuffer.begin(), mBuffer.end(), al::byte{});
  185. return true;
  186. }
  187. void SolarisBackend::start()
  188. {
  189. try {
  190. mKillNow.store(false, std::memory_order_release);
  191. mThread = std::thread{std::mem_fn(&SolarisBackend::mixerProc), this};
  192. }
  193. catch(std::exception& e) {
  194. throw al::backend_exception{al::backend_error::DeviceError,
  195. "Failed to start mixing thread: %s", e.what()};
  196. }
  197. }
  198. void SolarisBackend::stop()
  199. {
  200. if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
  201. return;
  202. mThread.join();
  203. if(ioctl(mFd, AUDIO_DRAIN) < 0)
  204. ERR("Error draining device: %s\n", strerror(errno));
  205. }
  206. } // namespace
  207. BackendFactory &SolarisBackendFactory::getFactory()
  208. {
  209. static SolarisBackendFactory factory{};
  210. return factory;
  211. }
  212. bool SolarisBackendFactory::init()
  213. {
  214. if(auto devopt = ConfigValueStr(nullptr, "solaris", "device"))
  215. solaris_driver = std::move(*devopt);
  216. return true;
  217. }
  218. bool SolarisBackendFactory::querySupport(BackendType type)
  219. { return type == BackendType::Playback; }
  220. std::string SolarisBackendFactory::probe(BackendType type)
  221. {
  222. std::string outnames;
  223. switch(type)
  224. {
  225. case BackendType::Playback:
  226. {
  227. struct stat buf;
  228. if(stat(solaris_driver.c_str(), &buf) == 0)
  229. outnames.append(solaris_device, sizeof(solaris_device));
  230. }
  231. break;
  232. case BackendType::Capture:
  233. break;
  234. }
  235. return outnames;
  236. }
  237. BackendPtr SolarisBackendFactory::createBackend(ALCdevice *device, BackendType type)
  238. {
  239. if(type == BackendType::Playback)
  240. return BackendPtr{new SolarisBackend{device}};
  241. return nullptr;
  242. }