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
29 #include <system_error>
33 #include "althrd_setname.h"
34 #include "core/device.h"
35 #include "core/helpers.h"
36 #include "core/logging.h"
37 #include "ringbuffer.h"
44 using namespace std::string_view_literals
;
46 [[nodiscard
]] constexpr auto GetDefaultName() noexcept
{ return "SndIO Default"sv
; }
48 struct SioPar
: public sio_par
{
49 SioPar() : sio_par
{} { sio_initpar(this); }
51 void clear() { sio_initpar(this); }
54 struct SndioPlayback final
: public BackendBase
{
55 SndioPlayback(DeviceBase
*device
) noexcept
: BackendBase
{device
} { }
56 ~SndioPlayback() override
;
60 void open(std::string_view name
) override
;
61 bool reset() override
;
62 void start() override
;
65 sio_hdl
*mSndHandle
{nullptr};
68 std::vector
<std::byte
> mBuffer
;
70 std::atomic
<bool> mKillNow
{true};
74 SndioPlayback::~SndioPlayback()
77 sio_close(mSndHandle
);
81 int SndioPlayback::mixerProc()
83 const size_t frameStep
{mFrameStep
};
84 const size_t frameSize
{frameStep
* mDevice
->bytesFromFmt()};
87 althrd_setname(GetMixerThreadName());
89 while(!mKillNow
.load(std::memory_order_acquire
)
90 && mDevice
->Connected
.load(std::memory_order_acquire
))
92 al::span
<std::byte
> buffer
{mBuffer
};
94 mDevice
->renderSamples(buffer
.data(), static_cast<uint
>(buffer
.size() / frameSize
),
96 while(!buffer
.empty() && !mKillNow
.load(std::memory_order_acquire
))
98 size_t wrote
{sio_write(mSndHandle
, buffer
.data(), buffer
.size())};
99 if(wrote
> buffer
.size() || wrote
== 0)
101 ERR("sio_write failed: {:#x}", wrote
);
102 mDevice
->handleDisconnect("Failed to write playback samples");
105 buffer
= buffer
.subspan(wrote
);
113 void SndioPlayback::open(std::string_view name
)
116 name
= GetDefaultName();
117 else if(name
!= GetDefaultName())
118 throw al::backend_exception
{al::backend_error::NoDevice
, "Device name \"{}\" not found",
121 sio_hdl
*sndHandle
{sio_open(nullptr, SIO_PLAY
, 0)};
123 throw al::backend_exception
{al::backend_error::NoDevice
, "Could not open backend device"};
126 sio_close(mSndHandle
);
127 mSndHandle
= sndHandle
;
132 bool SndioPlayback::reset()
136 auto tryfmt
= mDevice
->FmtType
;
167 par
.bps
= SIO_BPS(par
.bits
);
168 par
.le
= SIO_LE_NATIVE
;
171 par
.rate
= mDevice
->Frequency
;
172 par
.pchan
= mDevice
->channelsFromFmt();
174 par
.round
= mDevice
->UpdateSize
;
175 par
.appbufsz
= mDevice
->BufferSize
- mDevice
->UpdateSize
;
176 if(!par
.appbufsz
) par
.appbufsz
= mDevice
->UpdateSize
;
179 if(!sio_setpar(mSndHandle
, &par
))
180 throw al::backend_exception
{al::backend_error::DeviceError
,
181 "Failed to set device parameters"};
184 if(!sio_getpar(mSndHandle
, &par
))
185 throw al::backend_exception
{al::backend_error::DeviceError
,
186 "Failed to get device parameters"};
188 if(par
.bps
> 1 && par
.le
!= SIO_LE_NATIVE
)
189 throw al::backend_exception
{al::backend_error::DeviceError
,
190 "{}-endian samples not supported", par
.le
? "Little" : "Big"};
191 if(par
.bits
< par
.bps
*8 && !par
.msb
)
192 throw al::backend_exception
{al::backend_error::DeviceError
,
193 "MSB-padded samples not supported ({} of {} bits)", par
.bits
, par
.bps
*8};
195 throw al::backend_exception
{al::backend_error::DeviceError
,
196 "No playback channels on device"};
200 catch(al::backend_exception
&e
) {
201 if(tryfmt
== DevFmtShort
)
204 tryfmt
= DevFmtShort
;
209 mDevice
->FmtType
= (par
.sig
==1) ? DevFmtByte
: DevFmtUByte
;
210 else if(par
.bps
== 2)
211 mDevice
->FmtType
= (par
.sig
==1) ? DevFmtShort
: DevFmtUShort
;
212 else if(par
.bps
== 4)
213 mDevice
->FmtType
= (par
.sig
==1) ? DevFmtInt
: DevFmtUInt
;
215 throw al::backend_exception
{al::backend_error::DeviceError
,
216 "Unhandled sample format: {} {}-bit", (par
.sig
?"signed":"unsigned"), par
.bps
*8};
218 mFrameStep
= par
.pchan
;
219 if(par
.pchan
!= mDevice
->channelsFromFmt())
221 WARN("Got {} channel{} for {}", par
.pchan
, (par
.pchan
==1)?"":"s",
222 DevFmtChannelsString(mDevice
->FmtChans
));
223 if(par
.pchan
< 2) mDevice
->FmtChans
= DevFmtMono
;
224 else mDevice
->FmtChans
= DevFmtStereo
;
226 mDevice
->Frequency
= par
.rate
;
228 setDefaultChannelOrder();
230 mDevice
->UpdateSize
= par
.round
;
231 mDevice
->BufferSize
= par
.bufsz
+ par
.round
;
233 mBuffer
.resize(size_t{mDevice
->UpdateSize
} * par
.pchan
*par
.bps
);
235 std::fill(mBuffer
.begin(), mBuffer
.end(), std::byte
{});
236 else if(par
.bits
== 8)
237 std::fill_n(mBuffer
.data(), mBuffer
.size(), std::byte(0x80));
238 else if(par
.bits
== 16)
239 std::fill_n(reinterpret_cast<uint16_t*>(mBuffer
.data()), mBuffer
.size()/2, 0x8000);
240 else if(par
.bits
== 32)
241 std::fill_n(reinterpret_cast<uint32_t*>(mBuffer
.data()), mBuffer
.size()/4, 0x80000000u
);
246 void SndioPlayback::start()
248 if(!sio_start(mSndHandle
))
249 throw al::backend_exception
{al::backend_error::DeviceError
, "Error starting playback"};
252 mKillNow
.store(false, std::memory_order_release
);
253 mThread
= std::thread
{&SndioPlayback::mixerProc
, this};
255 catch(std::exception
& e
) {
256 sio_stop(mSndHandle
);
257 throw al::backend_exception
{al::backend_error::DeviceError
,
258 "Failed to start mixing thread: {}", e
.what()};
262 void SndioPlayback::stop()
264 if(mKillNow
.exchange(true, std::memory_order_acq_rel
) || !mThread
.joinable())
268 if(!sio_stop(mSndHandle
))
269 ERR("Error stopping device");
273 /* TODO: This could be improved by avoiding the ring buffer and record thread,
274 * counting the available samples with the sio_onmove callback and reading
275 * directly from the device. However, this depends on reasonable support for
276 * capture buffer sizes apps may request.
278 struct SndioCapture final
: public BackendBase
{
279 SndioCapture(DeviceBase
*device
) noexcept
: BackendBase
{device
} { }
280 ~SndioCapture() override
;
284 void open(std::string_view name
) override
;
285 void start() override
;
286 void stop() override
;
287 void captureSamples(std::byte
*buffer
, uint samples
) override
;
288 uint
availableSamples() override
;
290 sio_hdl
*mSndHandle
{nullptr};
294 std::atomic
<bool> mKillNow
{true};
298 SndioCapture::~SndioCapture()
301 sio_close(mSndHandle
);
302 mSndHandle
= nullptr;
305 int SndioCapture::recordProc()
308 althrd_setname(GetRecordThreadName());
310 const uint frameSize
{mDevice
->frameSizeFromFmt()};
312 int nfds_pre
{sio_nfds(mSndHandle
)};
315 mDevice
->handleDisconnect("Incorrect return value from sio_nfds(): {}", nfds_pre
);
319 auto fds
= std::vector
<pollfd
>(static_cast<uint
>(nfds_pre
));
321 while(!mKillNow
.load(std::memory_order_acquire
)
322 && mDevice
->Connected
.load(std::memory_order_acquire
))
324 /* Wait until there's some samples to read. */
325 const int nfds
{sio_pollfd(mSndHandle
, fds
.data(), POLLIN
)};
328 mDevice
->handleDisconnect("Failed to get polling fds: {}", nfds
);
331 int pollres
{::poll(fds
.data(), fds
.size(), 2000)};
334 if(errno
== EINTR
) continue;
335 mDevice
->handleDisconnect("Poll error: {}", std::generic_category().message(errno
));
341 const int revents
{sio_revents(mSndHandle
, fds
.data())};
342 if((revents
&POLLHUP
))
344 mDevice
->handleDisconnect("Got POLLHUP from poll events");
347 if(!(revents
&POLLIN
))
350 auto data
= mRing
->getWriteVector();
351 al::span
<std::byte
> buffer
{data
[0].buf
, data
[0].len
*frameSize
};
352 while(!buffer
.empty())
354 size_t got
{sio_read(mSndHandle
, buffer
.data(), buffer
.size())};
357 if(got
> buffer
.size())
359 ERR("sio_read failed: {:#x}", got
);
360 mDevice
->handleDisconnect("sio_read failed: {:#x}", got
);
364 mRing
->writeAdvance(got
/ frameSize
);
365 buffer
= buffer
.subspan(got
);
368 data
= mRing
->getWriteVector();
369 buffer
= {data
[0].buf
, data
[0].len
*frameSize
};
374 /* Got samples to read, but no place to store it. Drop it. */
375 static std::array
<char,4096> junk
;
376 sio_read(mSndHandle
, junk
.data(), junk
.size() - (junk
.size()%frameSize
));
384 void SndioCapture::open(std::string_view name
)
387 name
= GetDefaultName();
388 else if(name
!= GetDefaultName())
389 throw al::backend_exception
{al::backend_error::NoDevice
, "Device name \"{}\" not found",
392 mSndHandle
= sio_open(nullptr, SIO_REC
, true);
393 if(mSndHandle
== nullptr)
394 throw al::backend_exception
{al::backend_error::NoDevice
, "Could not open backend device"};
397 switch(mDevice
->FmtType
)
424 throw al::backend_exception
{al::backend_error::DeviceError
,
425 "{} capture samples not supported", DevFmtTypeString(mDevice
->FmtType
)};
427 par
.bps
= SIO_BPS(par
.bits
);
428 par
.le
= SIO_LE_NATIVE
;
430 par
.rchan
= mDevice
->channelsFromFmt();
431 par
.rate
= mDevice
->Frequency
;
433 par
.appbufsz
= std::max(mDevice
->BufferSize
, mDevice
->Frequency
/10u);
434 par
.round
= std::min(par
.appbufsz
/2u, mDevice
->Frequency
/40u);
436 if(!sio_setpar(mSndHandle
, &par
) || !sio_getpar(mSndHandle
, &par
))
437 throw al::backend_exception
{al::backend_error::DeviceError
,
438 "Failed to set device parameters"};
440 if(par
.bps
> 1 && par
.le
!= SIO_LE_NATIVE
)
441 throw al::backend_exception
{al::backend_error::DeviceError
,
442 "{}-endian samples not supported", par
.le
? "Little" : "Big"};
443 if(par
.bits
< par
.bps
*8 && !par
.msb
)
444 throw al::backend_exception
{al::backend_error::DeviceError
,
445 "Padded samples not supported (got {} of {} bits)", par
.bits
, par
.bps
*8};
447 auto match_fmt
= [](DevFmtType fmttype
, const sio_par
&p
) -> bool
449 return (fmttype
== DevFmtByte
&& p
.bps
== 1 && p
.sig
!= 0)
450 || (fmttype
== DevFmtUByte
&& p
.bps
== 1 && p
.sig
== 0)
451 || (fmttype
== DevFmtShort
&& p
.bps
== 2 && p
.sig
!= 0)
452 || (fmttype
== DevFmtUShort
&& p
.bps
== 2 && p
.sig
== 0)
453 || (fmttype
== DevFmtInt
&& p
.bps
== 4 && p
.sig
!= 0)
454 || (fmttype
== DevFmtUInt
&& p
.bps
== 4 && p
.sig
== 0);
456 if(!match_fmt(mDevice
->FmtType
, par
) || mDevice
->channelsFromFmt() != par
.rchan
457 || mDevice
->Frequency
!= par
.rate
)
458 throw al::backend_exception
{al::backend_error::DeviceError
,
459 "Failed to set format {} {} {}hz, got {}{} {}-channel {}hz instead",
460 DevFmtTypeString(mDevice
->FmtType
), DevFmtChannelsString(mDevice
->FmtChans
),
461 mDevice
->Frequency
, par
.sig
?'s':'u', par
.bps
*8, par
.rchan
, par
.rate
};
463 mRing
= RingBuffer::Create(mDevice
->BufferSize
, size_t{par
.bps
}*par
.rchan
, false);
464 mDevice
->BufferSize
= static_cast<uint
>(mRing
->writeSpace());
465 mDevice
->UpdateSize
= par
.round
;
467 setDefaultChannelOrder();
472 void SndioCapture::start()
474 if(!sio_start(mSndHandle
))
475 throw al::backend_exception
{al::backend_error::DeviceError
, "Error starting capture"};
478 mKillNow
.store(false, std::memory_order_release
);
479 mThread
= std::thread
{&SndioCapture::recordProc
, this};
481 catch(std::exception
& e
) {
482 sio_stop(mSndHandle
);
483 throw al::backend_exception
{al::backend_error::DeviceError
,
484 "Failed to start capture thread: {}", e
.what()};
488 void SndioCapture::stop()
490 if(mKillNow
.exchange(true, std::memory_order_acq_rel
) || !mThread
.joinable())
494 if(!sio_stop(mSndHandle
))
495 ERR("Error stopping device");
498 void SndioCapture::captureSamples(std::byte
*buffer
, uint samples
)
499 { std::ignore
= mRing
->read(buffer
, samples
); }
501 uint
SndioCapture::availableSamples()
502 { return static_cast<uint
>(mRing
->readSpace()); }
506 BackendFactory
&SndIOBackendFactory::getFactory()
508 static SndIOBackendFactory factory
{};
512 bool SndIOBackendFactory::init()
515 bool SndIOBackendFactory::querySupport(BackendType type
)
516 { return (type
== BackendType::Playback
|| type
== BackendType::Capture
); }
518 auto SndIOBackendFactory::enumerate(BackendType type
) -> std::vector
<std::string
>
522 case BackendType::Playback
:
523 case BackendType::Capture
:
524 return std::vector
{std::string
{GetDefaultName()}};
529 BackendPtr
SndIOBackendFactory::createBackend(DeviceBase
*device
, BackendType type
)
531 if(type
== BackendType::Playback
)
532 return BackendPtr
{new SndioPlayback
{device
}};
533 if(type
== BackendType::Capture
)
534 return BackendPtr
{new SndioCapture
{device
}};