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
33 #include "alnumeric.h"
34 #include "core/device.h"
35 #include "core/helpers.h"
36 #include "core/logging.h"
37 #include "ringbuffer.h"
46 static const char sndio_device
[] = "SndIO Default";
48 struct SioPar
: public sio_par
{
49 SioPar() { 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(const char *name
) override
;
61 bool reset() override
;
62 void start() override
;
65 sio_hdl
*mSndHandle
{nullptr};
68 al::vector
<al::byte
> mBuffer
;
70 std::atomic
<bool> mKillNow
{true};
73 DEF_NEWDEL(SndioPlayback
)
76 SndioPlayback::~SndioPlayback()
79 sio_close(mSndHandle
);
83 int SndioPlayback::mixerProc()
85 const size_t frameStep
{mFrameStep
};
86 const size_t frameSize
{frameStep
* mDevice
->bytesFromFmt()};
89 althrd_setname(MIXER_THREAD_NAME
);
91 while(!mKillNow
.load(std::memory_order_acquire
)
92 && mDevice
->Connected
.load(std::memory_order_acquire
))
94 al::span
<al::byte
> buffer
{mBuffer
};
96 mDevice
->renderSamples(buffer
.data(), static_cast<uint
>(buffer
.size() / frameSize
),
98 while(!buffer
.empty() && !mKillNow
.load(std::memory_order_acquire
))
100 size_t wrote
{sio_write(mSndHandle
, buffer
.data(), buffer
.size())};
103 ERR("sio_write failed\n");
104 mDevice
->handleDisconnect("Failed to write playback samples");
107 buffer
= buffer
.subspan(wrote
);
115 void SndioPlayback::open(const char *name
)
119 else if(strcmp(name
, sndio_device
) != 0)
120 throw al::backend_exception
{al::backend_error::NoDevice
, "Device name \"%s\" not found",
123 sio_hdl
*sndHandle
{sio_open(nullptr, SIO_PLAY
, 0)};
125 throw al::backend_exception
{al::backend_error::NoDevice
, "Could not open backend device"};
128 sio_close(mSndHandle
);
129 mSndHandle
= sndHandle
;
131 mDevice
->DeviceName
= name
;
134 bool SndioPlayback::reset()
138 auto tryfmt
= mDevice
->FmtType
;
168 par
.bps
= SIO_BPS(par
.bits
);
169 par
.le
= SIO_LE_NATIVE
;
172 par
.rate
= mDevice
->Frequency
;
173 par
.pchan
= mDevice
->channelsFromFmt();
175 par
.round
= mDevice
->UpdateSize
;
176 par
.appbufsz
= mDevice
->BufferSize
- mDevice
->UpdateSize
;
177 if(!par
.appbufsz
) par
.appbufsz
= mDevice
->UpdateSize
;
180 if(!sio_setpar(mSndHandle
, &par
))
181 throw al::backend_exception
{al::backend_error::DeviceError
,
182 "Failed to set device parameters"};
185 if(!sio_getpar(mSndHandle
, &par
))
186 throw al::backend_exception
{al::backend_error::DeviceError
,
187 "Failed to get device parameters"};
189 if(par
.bps
> 1 && par
.le
!= SIO_LE_NATIVE
)
190 throw al::backend_exception
{al::backend_error::DeviceError
,
191 "%s-endian samples not supported", par
.le
? "Little" : "Big"};
192 if(par
.bits
< par
.bps
*8 && !par
.msb
)
193 throw al::backend_exception
{al::backend_error::DeviceError
,
194 "MSB-padded samples not supported (%u of %u bits)", par
.bits
, par
.bps
*8};
196 throw al::backend_exception
{al::backend_error::DeviceError
,
197 "No playback channels on device"};
199 catch(al::backend_exception
&e
) {
200 if(tryfmt
== DevFmtShort
)
203 tryfmt
= DevFmtShort
;
208 mDevice
->FmtType
= (par
.sig
==1) ? DevFmtByte
: DevFmtUByte
;
209 else if(par
.bps
== 2)
210 mDevice
->FmtType
= (par
.sig
==1) ? DevFmtShort
: DevFmtUShort
;
211 else if(par
.bps
== 4)
212 mDevice
->FmtType
= (par
.sig
==1) ? DevFmtInt
: DevFmtUInt
;
214 throw al::backend_exception
{al::backend_error::DeviceError
,
215 "Unhandled sample format: %s %u-bit", (par
.sig
?"signed":"unsigned"), par
.bps
*8};
217 mFrameStep
= par
.pchan
;
218 if(par
.pchan
!= mDevice
->channelsFromFmt())
220 WARN("Got %u channel%s for %s\n", par
.pchan
, (par
.pchan
==1)?"":"s",
221 DevFmtChannelsString(mDevice
->FmtChans
));
222 if(par
.pchan
< 2) mDevice
->FmtChans
= DevFmtMono
;
223 else mDevice
->FmtChans
= DevFmtStereo
;
225 mDevice
->Frequency
= par
.rate
;
227 setDefaultChannelOrder();
229 mDevice
->UpdateSize
= par
.round
;
230 mDevice
->BufferSize
= par
.bufsz
+ par
.round
;
232 mBuffer
.resize(mDevice
->UpdateSize
* par
.pchan
*par
.bps
);
234 std::fill(mBuffer
.begin(), mBuffer
.end(), al::byte
{});
235 else if(par
.bits
== 8)
236 std::fill_n(mBuffer
.data(), mBuffer
.size(), al::byte(0x80));
237 else if(par
.bits
== 16)
238 std::fill_n(reinterpret_cast<uint16_t*>(mBuffer
.data()), mBuffer
.size()/2, 0x8000);
239 else if(par
.bits
== 32)
240 std::fill_n(reinterpret_cast<uint32_t*>(mBuffer
.data()), mBuffer
.size()/4, 0x80000000u
);
245 void SndioPlayback::start()
247 if(!sio_start(mSndHandle
))
248 throw al::backend_exception
{al::backend_error::DeviceError
, "Error starting playback"};
251 mKillNow
.store(false, std::memory_order_release
);
252 mThread
= std::thread
{std::mem_fn(&SndioPlayback::mixerProc
), this};
254 catch(std::exception
& e
) {
255 sio_stop(mSndHandle
);
256 throw al::backend_exception
{al::backend_error::DeviceError
,
257 "Failed to start mixing thread: %s", e
.what()};
261 void SndioPlayback::stop()
263 if(mKillNow
.exchange(true, std::memory_order_acq_rel
) || !mThread
.joinable())
267 if(!sio_stop(mSndHandle
))
268 ERR("Error stopping device\n");
272 /* TODO: This could be improved by avoiding the ring buffer and record thread,
273 * counting the available samples with the sio_onmove callback and reading
274 * directly from the device. However, this depends on reasonable support for
275 * capture buffer sizes apps may request.
277 struct SndioCapture final
: public BackendBase
{
278 SndioCapture(DeviceBase
*device
) noexcept
: BackendBase
{device
} { }
279 ~SndioCapture() override
;
283 void open(const char *name
) override
;
284 void start() override
;
285 void stop() override
;
286 void captureSamples(al::byte
*buffer
, uint samples
) override
;
287 uint
availableSamples() override
;
289 sio_hdl
*mSndHandle
{nullptr};
293 std::atomic
<bool> mKillNow
{true};
296 DEF_NEWDEL(SndioCapture
)
299 SndioCapture::~SndioCapture()
302 sio_close(mSndHandle
);
303 mSndHandle
= nullptr;
306 int SndioCapture::recordProc()
309 althrd_setname(RECORD_THREAD_NAME
);
311 const uint frameSize
{mDevice
->frameSizeFromFmt()};
313 int nfds_pre
{sio_nfds(mSndHandle
)};
316 mDevice
->handleDisconnect("Incorrect return value from sio_nfds(): %d", nfds_pre
);
320 auto fds
= std::make_unique
<pollfd
[]>(static_cast<uint
>(nfds_pre
));
322 while(!mKillNow
.load(std::memory_order_acquire
)
323 && mDevice
->Connected
.load(std::memory_order_acquire
))
325 /* Wait until there's some samples to read. */
326 const int nfds
{sio_pollfd(mSndHandle
, fds
.get(), POLLIN
)};
329 mDevice
->handleDisconnect("Failed to get polling fds: %d", nfds
);
332 int pollres
{::poll(fds
.get(), static_cast<uint
>(nfds
), 2000)};
335 if(errno
== EINTR
) continue;
336 mDevice
->handleDisconnect("Poll error: %s", strerror(errno
));
342 const int revents
{sio_revents(mSndHandle
, fds
.get())};
343 if((revents
&POLLHUP
))
345 mDevice
->handleDisconnect("Got POLLHUP from poll events");
348 if(!(revents
&POLLIN
))
351 auto data
= mRing
->getWriteVector();
352 al::span
<al::byte
> buffer
{data
.first
.buf
, data
.first
.len
*frameSize
};
353 while(!buffer
.empty())
355 size_t got
{sio_read(mSndHandle
, buffer
.data(), buffer
.size())};
358 mRing
->writeAdvance(got
/ frameSize
);
359 buffer
= buffer
.subspan(got
);
362 data
= mRing
->getWriteVector();
363 buffer
= {data
.first
.buf
, data
.first
.len
*frameSize
};
368 /* Got samples to read, but no place to store it. Drop it. */
369 static char junk
[4096];
370 sio_read(mSndHandle
, junk
, sizeof(junk
) - (sizeof(junk
)%frameSize
));
378 void SndioCapture::open(const char *name
)
382 else if(strcmp(name
, sndio_device
) != 0)
383 throw al::backend_exception
{al::backend_error::NoDevice
, "Device name \"%s\" not found",
386 mSndHandle
= sio_open(nullptr, SIO_REC
, true);
387 if(mSndHandle
== nullptr)
388 throw al::backend_exception
{al::backend_error::NoDevice
, "Could not open backend device"};
391 switch(mDevice
->FmtType
)
418 throw al::backend_exception
{al::backend_error::DeviceError
,
419 "%s capture samples not supported", DevFmtTypeString(mDevice
->FmtType
)};
421 par
.bps
= SIO_BPS(par
.bits
);
422 par
.le
= SIO_LE_NATIVE
;
424 par
.rchan
= mDevice
->channelsFromFmt();
425 par
.rate
= mDevice
->Frequency
;
427 par
.appbufsz
= maxu(mDevice
->BufferSize
, mDevice
->Frequency
/10);
428 par
.round
= minu(par
.appbufsz
/2, mDevice
->Frequency
/40);
430 if(!sio_setpar(mSndHandle
, &par
) || !sio_getpar(mSndHandle
, &par
))
431 throw al::backend_exception
{al::backend_error::DeviceError
,
432 "Failed to set device praameters"};
434 if(par
.bps
> 1 && par
.le
!= SIO_LE_NATIVE
)
435 throw al::backend_exception
{al::backend_error::DeviceError
,
436 "%s-endian samples not supported", par
.le
? "Little" : "Big"};
437 if(par
.bits
< par
.bps
*8 && !par
.msb
)
438 throw al::backend_exception
{al::backend_error::DeviceError
,
439 "Padded samples not supported (got %u of %u bits)", par
.bits
, par
.bps
*8};
441 auto match_fmt
= [](DevFmtType fmttype
, const sio_par
&par
) -> bool
443 return (fmttype
== DevFmtByte
&& par
.bps
== 1 && par
.sig
!= 0)
444 || (fmttype
== DevFmtUByte
&& par
.bps
== 1 && par
.sig
== 0)
445 || (fmttype
== DevFmtShort
&& par
.bps
== 2 && par
.sig
!= 0)
446 || (fmttype
== DevFmtUShort
&& par
.bps
== 2 && par
.sig
== 0)
447 || (fmttype
== DevFmtInt
&& par
.bps
== 4 && par
.sig
!= 0)
448 || (fmttype
== DevFmtUInt
&& par
.bps
== 4 && par
.sig
== 0);
450 if(!match_fmt(mDevice
->FmtType
, par
) || mDevice
->channelsFromFmt() != par
.rchan
451 || mDevice
->Frequency
!= par
.rate
)
452 throw al::backend_exception
{al::backend_error::DeviceError
,
453 "Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead",
454 DevFmtTypeString(mDevice
->FmtType
), DevFmtChannelsString(mDevice
->FmtChans
),
455 mDevice
->Frequency
, par
.sig
?'s':'u', par
.bps
*8, par
.rchan
, par
.rate
};
457 mRing
= RingBuffer::Create(mDevice
->BufferSize
, par
.bps
*par
.rchan
, false);
458 mDevice
->BufferSize
= static_cast<uint
>(mRing
->writeSpace());
459 mDevice
->UpdateSize
= par
.round
;
461 setDefaultChannelOrder();
463 mDevice
->DeviceName
= name
;
466 void SndioCapture::start()
468 if(!sio_start(mSndHandle
))
469 throw al::backend_exception
{al::backend_error::DeviceError
, "Error starting capture"};
472 mKillNow
.store(false, std::memory_order_release
);
473 mThread
= std::thread
{std::mem_fn(&SndioCapture::recordProc
), this};
475 catch(std::exception
& e
) {
476 sio_stop(mSndHandle
);
477 throw al::backend_exception
{al::backend_error::DeviceError
,
478 "Failed to start capture thread: %s", e
.what()};
482 void SndioCapture::stop()
484 if(mKillNow
.exchange(true, std::memory_order_acq_rel
) || !mThread
.joinable())
488 if(!sio_stop(mSndHandle
))
489 ERR("Error stopping device\n");
492 void SndioCapture::captureSamples(al::byte
*buffer
, uint samples
)
493 { mRing
->read(buffer
, samples
); }
495 uint
SndioCapture::availableSamples()
496 { return static_cast<uint
>(mRing
->readSpace()); }
500 BackendFactory
&SndIOBackendFactory::getFactory()
502 static SndIOBackendFactory factory
{};
506 bool SndIOBackendFactory::init()
509 bool SndIOBackendFactory::querySupport(BackendType type
)
510 { return (type
== BackendType::Playback
|| type
== BackendType::Capture
); }
512 std::string
SndIOBackendFactory::probe(BackendType type
)
514 std::string outnames
;
517 case BackendType::Playback
:
518 case BackendType::Capture
:
519 /* Includes null char. */
520 outnames
.append(sndio_device
, sizeof(sndio_device
));
526 BackendPtr
SndIOBackendFactory::createBackend(DeviceBase
*device
, BackendType type
)
528 if(type
== BackendType::Playback
)
529 return BackendPtr
{new SndioPlayback
{device
}};
530 if(type
== BackendType::Capture
)
531 return BackendPtr
{new SndioCapture
{device
}};