]> git.tdb.fi Git - ext/openal.git/blob - alc/backends/solaris.cpp
Import OpenAL Soft 1.23.1 sources
[ext/openal.git] / alc / backends / solaris.cpp
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
21 #include "config.h"
22
23 #include "solaris.h"
24
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
39 #include <thread>
40 #include <functional>
41
42 #include "albyte.h"
43 #include "alc/alconfig.h"
44 #include "core/device.h"
45 #include "core/helpers.h"
46 #include "core/logging.h"
47 #include "threads.h"
48 #include "vector.h"
49
50 #include <sys/audioio.h>
51
52
53 namespace {
54
55 constexpr char solaris_device[] = "Solaris Default";
56
57 std::string solaris_driver{"/dev/audio"};
58
59
60 struct SolarisBackend final : public BackendBase {
61     SolarisBackend(DeviceBase *device) noexcept : BackendBase{device} { }
62     ~SolarisBackend() override;
63
64     int mixerProc();
65
66     void open(const char *name) override;
67     bool reset() override;
68     void start() override;
69     void stop() override;
70
71     int mFd{-1};
72
73     uint mFrameStep{};
74     al::vector<al::byte> mBuffer;
75
76     std::atomic<bool> mKillNow{true};
77     std::thread mThread;
78
79     DEF_NEWDEL(SolarisBackend)
80 };
81
82 SolarisBackend::~SolarisBackend()
83 {
84     if(mFd != -1)
85         close(mFd);
86     mFd = -1;
87 }
88
89 int SolarisBackend::mixerProc()
90 {
91     SetRTPriority();
92     althrd_setname(MIXER_THREAD_NAME);
93
94     const size_t frame_step{mDevice->channelsFromFmt()};
95     const uint frame_size{mDevice->frameSizeFromFmt()};
96
97     while(!mKillNow.load(std::memory_order_acquire)
98         && mDevice->Connected.load(std::memory_order_acquire))
99     {
100         pollfd pollitem{};
101         pollitem.fd = mFd;
102         pollitem.events = POLLOUT;
103
104         int pret{poll(&pollitem, 1, 1000)};
105         if(pret < 0)
106         {
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;
112         }
113         else if(pret == 0)
114         {
115             WARN("poll timeout\n");
116             continue;
117         }
118
119         al::byte *write_ptr{mBuffer.data()};
120         size_t to_write{mBuffer.size()};
121         mDevice->renderSamples(write_ptr, static_cast<uint>(to_write/frame_size), frame_step);
122         while(to_write > 0 && !mKillNow.load(std::memory_order_acquire))
123         {
124             ssize_t wrote{write(mFd, write_ptr, to_write)};
125             if(wrote < 0)
126             {
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;
132             }
133
134             to_write -= static_cast<size_t>(wrote);
135             write_ptr += wrote;
136         }
137     }
138
139     return 0;
140 }
141
142
143 void SolarisBackend::open(const char *name)
144 {
145     if(!name)
146         name = solaris_device;
147     else if(strcmp(name, solaris_device) != 0)
148         throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
149             name};
150
151     int fd{::open(solaris_driver.c_str(), O_WRONLY)};
152     if(fd == -1)
153         throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s",
154             solaris_driver.c_str(), strerror(errno)};
155
156     if(mFd != -1)
157         ::close(mFd);
158     mFd = fd;
159
160     mDevice->DeviceName = name;
161 }
162
163 bool SolarisBackend::reset()
164 {
165     audio_info_t info;
166     AUDIO_INITINFO(&info);
167
168     info.play.sample_rate = mDevice->Frequency;
169     info.play.channels = mDevice->channelsFromFmt();
170     switch(mDevice->FmtType)
171     {
172     case DevFmtByte:
173         info.play.precision = 8;
174         info.play.encoding = AUDIO_ENCODING_LINEAR;
175         break;
176     case DevFmtUByte:
177         info.play.precision = 8;
178         info.play.encoding = AUDIO_ENCODING_LINEAR8;
179         break;
180     case DevFmtUShort:
181     case DevFmtInt:
182     case DevFmtUInt:
183     case DevFmtFloat:
184         mDevice->FmtType = DevFmtShort;
185         /* fall-through */
186     case DevFmtShort:
187         info.play.precision = 16;
188         info.play.encoding = AUDIO_ENCODING_LINEAR;
189         break;
190     }
191     info.play.buffer_size = mDevice->BufferSize * mDevice->frameSizeFromFmt();
192
193     if(ioctl(mFd, AUDIO_SETINFO, &info) < 0)
194     {
195         ERR("ioctl failed: %s\n", strerror(errno));
196         return false;
197     }
198
199     if(mDevice->channelsFromFmt() != info.play.channels)
200     {
201         if(info.play.channels >= 2)
202             mDevice->FmtChans = DevFmtStereo;
203         else if(info.play.channels == 1)
204             mDevice->FmtChans = DevFmtMono;
205         else
206             throw al::backend_exception{al::backend_error::DeviceError,
207                 "Got %u device channels", info.play.channels};
208     }
209
210     if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8)
211         mDevice->FmtType = DevFmtUByte;
212     else if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR)
213         mDevice->FmtType = DevFmtByte;
214     else if(info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR)
215         mDevice->FmtType = DevFmtShort;
216     else if(info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR)
217         mDevice->FmtType = DevFmtInt;
218     else
219     {
220         ERR("Got unhandled sample type: %d (0x%x)\n", info.play.precision, info.play.encoding);
221         return false;
222     }
223
224     uint frame_size{mDevice->bytesFromFmt() * info.play.channels};
225     mFrameStep = info.play.channels;
226     mDevice->Frequency = info.play.sample_rate;
227     mDevice->BufferSize = info.play.buffer_size / frame_size;
228     /* How to get the actual period size/count? */
229     mDevice->UpdateSize = mDevice->BufferSize / 2;
230
231     setDefaultChannelOrder();
232
233     mBuffer.resize(mDevice->UpdateSize * size_t{frame_size});
234     std::fill(mBuffer.begin(), mBuffer.end(), al::byte{});
235
236     return true;
237 }
238
239 void SolarisBackend::start()
240 {
241     try {
242         mKillNow.store(false, std::memory_order_release);
243         mThread = std::thread{std::mem_fn(&SolarisBackend::mixerProc), this};
244     }
245     catch(std::exception& e) {
246         throw al::backend_exception{al::backend_error::DeviceError,
247             "Failed to start mixing thread: %s", e.what()};
248     }
249 }
250
251 void SolarisBackend::stop()
252 {
253     if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
254         return;
255     mThread.join();
256
257     if(ioctl(mFd, AUDIO_DRAIN) < 0)
258         ERR("Error draining device: %s\n", strerror(errno));
259 }
260
261 } // namespace
262
263 BackendFactory &SolarisBackendFactory::getFactory()
264 {
265     static SolarisBackendFactory factory{};
266     return factory;
267 }
268
269 bool SolarisBackendFactory::init()
270 {
271     if(auto devopt = ConfigValueStr(nullptr, "solaris", "device"))
272         solaris_driver = std::move(*devopt);
273     return true;
274 }
275
276 bool SolarisBackendFactory::querySupport(BackendType type)
277 { return type == BackendType::Playback; }
278
279 std::string SolarisBackendFactory::probe(BackendType type)
280 {
281     std::string outnames;
282     switch(type)
283     {
284     case BackendType::Playback:
285     {
286         struct stat buf;
287         if(stat(solaris_driver.c_str(), &buf) == 0)
288             outnames.append(solaris_device, sizeof(solaris_device));
289     }
290     break;
291
292     case BackendType::Capture:
293         break;
294     }
295     return outnames;
296 }
297
298 BackendPtr SolarisBackendFactory::createBackend(DeviceBase *device, BackendType type)
299 {
300     if(type == BackendType::Playback)
301         return BackendPtr{new SolarisBackend{device}};
302     return nullptr;
303 }