Enable proper full C++ exception handling on MSVC
[openal-soft.git] / alc / backends / solaris.cpp
blob047736c6ad46753ac0c621fb82bdbfa15c54d05b
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.
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.
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
21 #include "config.h"
23 #include "solaris.h"
25 #include <sys/ioctl.h>
26 #include <sys/types.h>
27 #include <sys/time.h>
28 #include <sys/stat.h>
29 #include <fcntl.h>
30 #include <stdlib.h>
31 #include <stdio.h>
32 #include <memory.h>
33 #include <unistd.h>
34 #include <errno.h>
35 #include <poll.h>
36 #include <math.h>
37 #include <string.h>
38 #include <vector>
40 #include <thread>
41 #include <functional>
43 #include "alc/alconfig.h"
44 #include "alstring.h"
45 #include "althrd_setname.h"
46 #include "core/device.h"
47 #include "core/helpers.h"
48 #include "core/logging.h"
50 #include <sys/audioio.h>
53 namespace {
55 using namespace std::string_view_literals;
57 [[nodiscard]] constexpr auto GetDefaultName() noexcept { return "Solaris Default"sv; }
59 std::string solaris_driver{"/dev/audio"};
62 struct SolarisBackend final : public BackendBase {
63 SolarisBackend(DeviceBase *device) noexcept : BackendBase{device} { }
64 ~SolarisBackend() override;
66 int mixerProc();
68 void open(std::string_view name) override;
69 bool reset() override;
70 void start() override;
71 void stop() override;
73 int mFd{-1};
75 uint mFrameStep{};
76 std::vector<std::byte> mBuffer;
78 std::atomic<bool> mKillNow{true};
79 std::thread mThread;
82 SolarisBackend::~SolarisBackend()
84 if(mFd != -1)
85 close(mFd);
86 mFd = -1;
89 int SolarisBackend::mixerProc()
91 SetRTPriority();
92 althrd_setname(GetMixerThreadName());
94 const size_t frame_step{mDevice->channelsFromFmt()};
95 const size_t frame_size{mDevice->frameSizeFromFmt()};
97 while(!mKillNow.load(std::memory_order_acquire)
98 && mDevice->Connected.load(std::memory_order_acquire))
100 pollfd pollitem{};
101 pollitem.fd = mFd;
102 pollitem.events = POLLOUT;
104 int pret{poll(&pollitem, 1, 1000)};
105 if(pret < 0)
107 if(errno == EINTR || errno == EAGAIN)
108 continue;
109 ERR("poll failed: %s\n", strerror(errno));
110 mDevice->handleDisconnect("Failed to wait for playback buffer: %s", strerror(errno));
111 break;
113 else if(pret == 0)
115 WARN("poll timeout\n");
116 continue;
119 al::span<std::byte> buffer{mBuffer};
120 mDevice->renderSamples(buffer.data(), static_cast<uint>(buffer.size()/frame_size),
121 frame_step);
122 while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire))
124 ssize_t wrote{write(mFd, buffer.data(), buffer.size())};
125 if(wrote < 0)
127 if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
128 continue;
129 ERR("write failed: %s\n", strerror(errno));
130 mDevice->handleDisconnect("Failed to write playback samples: %s", strerror(errno));
131 break;
134 buffer = buffer.subspan(static_cast<size_t>(wrote));
138 return 0;
142 void SolarisBackend::open(std::string_view name)
144 if(name.empty())
145 name = GetDefaultName();
146 else if(name != GetDefaultName())
147 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
148 al::sizei(name), name.data()};
150 int fd{::open(solaris_driver.c_str(), O_WRONLY)};
151 if(fd == -1)
152 throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s",
153 solaris_driver.c_str(), strerror(errno)};
155 if(mFd != -1)
156 ::close(mFd);
157 mFd = fd;
159 mDevice->DeviceName = name;
162 bool SolarisBackend::reset()
164 audio_info_t info;
165 AUDIO_INITINFO(&info);
167 info.play.sample_rate = mDevice->Frequency;
168 info.play.channels = mDevice->channelsFromFmt();
169 switch(mDevice->FmtType)
171 case DevFmtByte:
172 info.play.precision = 8;
173 info.play.encoding = AUDIO_ENCODING_LINEAR;
174 break;
175 case DevFmtUByte:
176 info.play.precision = 8;
177 info.play.encoding = AUDIO_ENCODING_LINEAR8;
178 break;
179 case DevFmtUShort:
180 case DevFmtInt:
181 case DevFmtUInt:
182 case DevFmtFloat:
183 mDevice->FmtType = DevFmtShort;
184 /* fall-through */
185 case DevFmtShort:
186 info.play.precision = 16;
187 info.play.encoding = AUDIO_ENCODING_LINEAR;
188 break;
190 info.play.buffer_size = mDevice->BufferSize * mDevice->frameSizeFromFmt();
192 if(ioctl(mFd, AUDIO_SETINFO, &info) < 0)
194 ERR("ioctl failed: %s\n", strerror(errno));
195 return false;
198 if(mDevice->channelsFromFmt() != info.play.channels)
200 if(info.play.channels >= 2)
201 mDevice->FmtChans = DevFmtStereo;
202 else if(info.play.channels == 1)
203 mDevice->FmtChans = DevFmtMono;
204 else
205 throw al::backend_exception{al::backend_error::DeviceError,
206 "Got %u device channels", info.play.channels};
209 if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8)
210 mDevice->FmtType = DevFmtUByte;
211 else if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR)
212 mDevice->FmtType = DevFmtByte;
213 else if(info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR)
214 mDevice->FmtType = DevFmtShort;
215 else if(info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR)
216 mDevice->FmtType = DevFmtInt;
217 else
219 ERR("Got unhandled sample type: %d (0x%x)\n", info.play.precision, info.play.encoding);
220 return false;
223 uint frame_size{mDevice->bytesFromFmt() * info.play.channels};
224 mFrameStep = info.play.channels;
225 mDevice->Frequency = info.play.sample_rate;
226 mDevice->BufferSize = info.play.buffer_size / frame_size;
227 /* How to get the actual period size/count? */
228 mDevice->UpdateSize = mDevice->BufferSize / 2;
230 setDefaultChannelOrder();
232 mBuffer.resize(mDevice->UpdateSize * size_t{frame_size});
233 std::fill(mBuffer.begin(), mBuffer.end(), std::byte{});
235 return true;
238 void SolarisBackend::start()
240 try {
241 mKillNow.store(false, std::memory_order_release);
242 mThread = std::thread{std::mem_fn(&SolarisBackend::mixerProc), this};
244 catch(std::exception& e) {
245 throw al::backend_exception{al::backend_error::DeviceError,
246 "Failed to start mixing thread: %s", e.what()};
250 void SolarisBackend::stop()
252 if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
253 return;
254 mThread.join();
256 if(ioctl(mFd, AUDIO_DRAIN) < 0)
257 ERR("Error draining device: %s\n", strerror(errno));
260 } // namespace
262 BackendFactory &SolarisBackendFactory::getFactory()
264 static SolarisBackendFactory factory{};
265 return factory;
268 bool SolarisBackendFactory::init()
270 if(auto devopt = ConfigValueStr({}, "solaris", "device"))
271 solaris_driver = std::move(*devopt);
272 return true;
275 bool SolarisBackendFactory::querySupport(BackendType type)
276 { return type == BackendType::Playback; }
278 auto SolarisBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
280 switch(type)
282 case BackendType::Playback:
283 if(struct stat buf{}; stat(solaris_driver.c_str(), &buf) == 0)
284 return std::vector{std::string{GetDefaultName()}};
285 break;
287 case BackendType::Capture:
288 break;
290 return {};
293 BackendPtr SolarisBackendFactory::createBackend(DeviceBase *device, BackendType type)
295 if(type == BackendType::Playback)
296 return BackendPtr{new SolarisBackend{device}};
297 return nullptr;