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
25 #include <sys/ioctl.h>
26 #include <sys/types.h>
43 #include "alc/alconfig.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>
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
;
68 void open(std::string_view name
) override
;
69 bool reset() override
;
70 void start() override
;
76 std::vector
<std::byte
> mBuffer
;
78 std::atomic
<bool> mKillNow
{true};
82 SolarisBackend::~SolarisBackend()
89 int SolarisBackend::mixerProc()
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
))
102 pollitem
.events
= POLLOUT
;
104 int pret
{poll(&pollitem
, 1, 1000)};
107 if(errno
== EINTR
|| errno
== EAGAIN
)
109 ERR("poll failed: %s\n", strerror(errno
));
110 mDevice
->handleDisconnect("Failed to wait for playback buffer: %s", strerror(errno
));
115 WARN("poll timeout\n");
119 al::span
<std::byte
> buffer
{mBuffer
};
120 mDevice
->renderSamples(buffer
.data(), static_cast<uint
>(buffer
.size()/frame_size
),
122 while(!buffer
.empty() && !mKillNow
.load(std::memory_order_acquire
))
124 ssize_t wrote
{write(mFd
, buffer
.data(), buffer
.size())};
127 if(errno
== EAGAIN
|| errno
== EWOULDBLOCK
|| errno
== EINTR
)
129 ERR("write failed: %s\n", strerror(errno
));
130 mDevice
->handleDisconnect("Failed to write playback samples: %s", strerror(errno
));
134 buffer
= buffer
.subspan(static_cast<size_t>(wrote
));
142 void SolarisBackend::open(std::string_view name
)
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
)};
152 throw al::backend_exception
{al::backend_error::NoDevice
, "Could not open %s: %s",
153 solaris_driver
.c_str(), strerror(errno
)};
159 mDevice
->DeviceName
= name
;
162 bool SolarisBackend::reset()
165 AUDIO_INITINFO(&info
);
167 info
.play
.sample_rate
= mDevice
->Frequency
;
168 info
.play
.channels
= mDevice
->channelsFromFmt();
169 switch(mDevice
->FmtType
)
172 info
.play
.precision
= 8;
173 info
.play
.encoding
= AUDIO_ENCODING_LINEAR
;
176 info
.play
.precision
= 8;
177 info
.play
.encoding
= AUDIO_ENCODING_LINEAR8
;
183 mDevice
->FmtType
= DevFmtShort
;
186 info
.play
.precision
= 16;
187 info
.play
.encoding
= AUDIO_ENCODING_LINEAR
;
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
));
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
;
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
;
219 ERR("Got unhandled sample type: %d (0x%x)\n", info
.play
.precision
, info
.play
.encoding
);
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
{});
238 void SolarisBackend::start()
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())
256 if(ioctl(mFd
, AUDIO_DRAIN
) < 0)
257 ERR("Error draining device: %s\n", strerror(errno
));
262 BackendFactory
&SolarisBackendFactory::getFactory()
264 static SolarisBackendFactory factory
{};
268 bool SolarisBackendFactory::init()
270 if(auto devopt
= ConfigValueStr({}, "solaris", "device"))
271 solaris_driver
= std::move(*devopt
);
275 bool SolarisBackendFactory::querySupport(BackendType type
)
276 { return type
== BackendType::Playback
; }
278 auto SolarisBackendFactory::enumerate(BackendType type
) -> std::vector
<std::string
>
282 case BackendType::Playback
:
283 if(struct stat buf
{}; stat(solaris_driver
.c_str(), &buf
) == 0)
284 return std::vector
{std::string
{GetDefaultName()}};
287 case BackendType::Capture
:
293 BackendPtr
SolarisBackendFactory::createBackend(DeviceBase
*device
, BackendType type
)
295 if(type
== BackendType::Playback
)
296 return BackendPtr
{new SolarisBackend
{device
}};