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
23 #include "backends/sndio.h"
36 #include "ringbuffer.h"
43 static const ALCchar sndio_device
[] = "SndIO Default";
46 struct SndioPlayback final
: public BackendBase
{
47 SndioPlayback(ALCdevice
*device
) noexcept
: BackendBase
{device
} { }
48 ~SndioPlayback() override
;
52 ALCenum
open(const ALCchar
*name
) override
;
53 bool reset() override
;
54 bool start() override
;
57 sio_hdl
*mSndHandle
{nullptr};
59 al::vector
<ALubyte
> mBuffer
;
61 std::atomic
<bool> mKillNow
{true};
64 DEF_NEWDEL(SndioPlayback
)
67 SndioPlayback::~SndioPlayback()
70 sio_close(mSndHandle
);
74 int SndioPlayback::mixerProc()
77 althrd_setname(MIXER_THREAD_NAME
);
79 const ALuint frameSize
{mDevice
->frameSizeFromFmt()};
81 while(!mKillNow
.load(std::memory_order_acquire
) &&
82 mDevice
->Connected
.load(std::memory_order_acquire
))
84 ALubyte
*WritePtr
{mBuffer
.data()};
85 size_t len
{mBuffer
.size()};
88 aluMixData(mDevice
, WritePtr
, static_cast<ALuint
>(len
/frameSize
));
90 while(len
> 0 && !mKillNow
.load(std::memory_order_acquire
))
92 size_t wrote
{sio_write(mSndHandle
, WritePtr
, len
)};
95 ERR("sio_write failed\n");
96 aluHandleDisconnect(mDevice
, "Failed to write playback samples");
109 ALCenum
SndioPlayback::open(const ALCchar
*name
)
113 else if(strcmp(name
, sndio_device
) != 0)
114 return ALC_INVALID_VALUE
;
116 mSndHandle
= sio_open(nullptr, SIO_PLAY
, 0);
117 if(mSndHandle
== nullptr)
119 ERR("Could not open device\n");
120 return ALC_INVALID_VALUE
;
123 mDevice
->DeviceName
= name
;
127 bool SndioPlayback::reset()
132 par
.rate
= mDevice
->Frequency
;
133 par
.pchan
= ((mDevice
->FmtChans
!= DevFmtMono
) ? 2 : 1);
135 switch(mDevice
->FmtType
)
163 par
.le
= SIO_LE_NATIVE
;
165 par
.round
= mDevice
->UpdateSize
;
166 par
.appbufsz
= mDevice
->BufferSize
- mDevice
->UpdateSize
;
167 if(!par
.appbufsz
) par
.appbufsz
= mDevice
->UpdateSize
;
169 if(!sio_setpar(mSndHandle
, &par
) || !sio_getpar(mSndHandle
, &par
))
171 ERR("Failed to set device parameters\n");
175 if(par
.bits
!= par
.bps
*8)
177 ERR("Padded samples not supported (%u of %u bits)\n", par
.bits
, par
.bps
*8);
181 mDevice
->Frequency
= par
.rate
;
182 mDevice
->FmtChans
= ((par
.pchan
==1) ? DevFmtMono
: DevFmtStereo
);
184 if(par
.bits
== 8 && par
.sig
== 1)
185 mDevice
->FmtType
= DevFmtByte
;
186 else if(par
.bits
== 8 && par
.sig
== 0)
187 mDevice
->FmtType
= DevFmtUByte
;
188 else if(par
.bits
== 16 && par
.sig
== 1)
189 mDevice
->FmtType
= DevFmtShort
;
190 else if(par
.bits
== 16 && par
.sig
== 0)
191 mDevice
->FmtType
= DevFmtUShort
;
192 else if(par
.bits
== 32 && par
.sig
== 1)
193 mDevice
->FmtType
= DevFmtInt
;
194 else if(par
.bits
== 32 && par
.sig
== 0)
195 mDevice
->FmtType
= DevFmtUInt
;
198 ERR("Unhandled sample format: %s %u-bit\n", (par
.sig
?"signed":"unsigned"), par
.bits
);
202 SetDefaultChannelOrder(mDevice
);
204 mDevice
->UpdateSize
= par
.round
;
205 mDevice
->BufferSize
= par
.bufsz
+ par
.round
;
207 mBuffer
.resize(mDevice
->UpdateSize
* mDevice
->frameSizeFromFmt());
208 std::fill(mBuffer
.begin(), mBuffer
.end(), 0);
213 bool SndioPlayback::start()
215 if(!sio_start(mSndHandle
))
217 ERR("Error starting playback\n");
222 mKillNow
.store(false, std::memory_order_release
);
223 mThread
= std::thread
{std::mem_fn(&SndioPlayback::mixerProc
), this};
226 catch(std::exception
& e
) {
227 ERR("Could not create playback thread: %s\n", e
.what());
231 sio_stop(mSndHandle
);
235 void SndioPlayback::stop()
237 if(mKillNow
.exchange(true, std::memory_order_acq_rel
) || !mThread
.joinable())
241 if(!sio_stop(mSndHandle
))
242 ERR("Error stopping device\n");
246 struct SndioCapture final
: public BackendBase
{
247 SndioCapture(ALCdevice
*device
) noexcept
: BackendBase
{device
} { }
248 ~SndioCapture() override
;
252 ALCenum
open(const ALCchar
*name
) override
;
253 bool start() override
;
254 void stop() override
;
255 ALCenum
captureSamples(al::byte
*buffer
, ALCuint samples
) override
;
256 ALCuint
availableSamples() override
;
258 sio_hdl
*mSndHandle
{nullptr};
262 std::atomic
<bool> mKillNow
{true};
265 DEF_NEWDEL(SndioCapture
)
268 SndioCapture::~SndioCapture()
271 sio_close(mSndHandle
);
272 mSndHandle
= nullptr;
275 int SndioCapture::recordProc()
278 althrd_setname(RECORD_THREAD_NAME
);
280 const ALuint frameSize
{mDevice
->frameSizeFromFmt()};
282 while(!mKillNow
.load(std::memory_order_acquire
) &&
283 mDevice
->Connected
.load(std::memory_order_acquire
))
285 auto data
= mRing
->getWriteVector();
286 size_t todo
{data
.first
.len
+ data
.second
.len
};
289 static char junk
[4096];
290 sio_read(mSndHandle
, junk
,
291 minz(sizeof(junk
)/frameSize
, mDevice
->UpdateSize
)*frameSize
);
296 data
.first
.len
*= frameSize
;
297 data
.second
.len
*= frameSize
;
298 todo
= minz(todo
, mDevice
->UpdateSize
) * frameSize
;
302 data
.first
= data
.second
;
304 size_t got
{sio_read(mSndHandle
, data
.first
.buf
, minz(todo
-total
, data
.first
.len
))};
307 aluHandleDisconnect(mDevice
, "Failed to read capture samples");
311 data
.first
.buf
+= got
;
312 data
.first
.len
-= got
;
315 mRing
->writeAdvance(total
/ frameSize
);
322 ALCenum
SndioCapture::open(const ALCchar
*name
)
326 else if(strcmp(name
, sndio_device
) != 0)
327 return ALC_INVALID_VALUE
;
329 mSndHandle
= sio_open(nullptr, SIO_REC
, 0);
330 if(mSndHandle
== nullptr)
332 ERR("Could not open device\n");
333 return ALC_INVALID_VALUE
;
339 switch(mDevice
->FmtType
)
366 ERR("%s capture samples not supported\n", DevFmtTypeString(mDevice
->FmtType
));
367 return ALC_INVALID_VALUE
;
369 par
.bits
= par
.bps
* 8;
370 par
.le
= SIO_LE_NATIVE
;
371 par
.msb
= SIO_LE_NATIVE
? 0 : 1;
372 par
.rchan
= mDevice
->channelsFromFmt();
373 par
.rate
= mDevice
->Frequency
;
375 par
.appbufsz
= maxu(mDevice
->BufferSize
, mDevice
->Frequency
/10);
376 par
.round
= minu(par
.appbufsz
, mDevice
->Frequency
/40);
378 mDevice
->UpdateSize
= par
.round
;
379 mDevice
->BufferSize
= par
.appbufsz
;
381 if(!sio_setpar(mSndHandle
, &par
) || !sio_getpar(mSndHandle
, &par
))
383 ERR("Failed to set device parameters\n");
384 return ALC_INVALID_VALUE
;
387 if(par
.bits
!= par
.bps
*8)
389 ERR("Padded samples not supported (%u of %u bits)\n", par
.bits
, par
.bps
*8);
390 return ALC_INVALID_VALUE
;
393 if(!((mDevice
->FmtType
== DevFmtByte
&& par
.bits
== 8 && par
.sig
!= 0) ||
394 (mDevice
->FmtType
== DevFmtUByte
&& par
.bits
== 8 && par
.sig
== 0) ||
395 (mDevice
->FmtType
== DevFmtShort
&& par
.bits
== 16 && par
.sig
!= 0) ||
396 (mDevice
->FmtType
== DevFmtUShort
&& par
.bits
== 16 && par
.sig
== 0) ||
397 (mDevice
->FmtType
== DevFmtInt
&& par
.bits
== 32 && par
.sig
!= 0) ||
398 (mDevice
->FmtType
== DevFmtUInt
&& par
.bits
== 32 && par
.sig
== 0)) ||
399 mDevice
->channelsFromFmt() != par
.rchan
|| mDevice
->Frequency
!= par
.rate
)
401 ERR("Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead\n",
402 DevFmtTypeString(mDevice
->FmtType
), DevFmtChannelsString(mDevice
->FmtChans
),
403 mDevice
->Frequency
, par
.sig
?'s':'u', par
.bits
, par
.rchan
, par
.rate
);
404 return ALC_INVALID_VALUE
;
407 mRing
= CreateRingBuffer(mDevice
->BufferSize
, par
.bps
*par
.rchan
, false);
410 ERR("Failed to allocate %u-byte ringbuffer\n", mDevice
->BufferSize
*par
.bps
*par
.rchan
);
411 return ALC_OUT_OF_MEMORY
;
414 SetDefaultChannelOrder(mDevice
);
416 mDevice
->DeviceName
= name
;
420 bool SndioCapture::start()
422 if(!sio_start(mSndHandle
))
424 ERR("Error starting playback\n");
429 mKillNow
.store(false, std::memory_order_release
);
430 mThread
= std::thread
{std::mem_fn(&SndioCapture::recordProc
), this};
433 catch(std::exception
& e
) {
434 ERR("Could not create record thread: %s\n", e
.what());
438 sio_stop(mSndHandle
);
442 void SndioCapture::stop()
444 if(mKillNow
.exchange(true, std::memory_order_acq_rel
) || !mThread
.joinable())
448 if(!sio_stop(mSndHandle
))
449 ERR("Error stopping device\n");
452 ALCenum
SndioCapture::captureSamples(al::byte
*buffer
, ALCuint samples
)
454 mRing
->read(buffer
, samples
);
458 ALCuint
SndioCapture::availableSamples()
459 { return static_cast<ALCuint
>(mRing
->readSpace()); }
463 BackendFactory
&SndIOBackendFactory::getFactory()
465 static SndIOBackendFactory factory
{};
469 bool SndIOBackendFactory::init()
472 bool SndIOBackendFactory::querySupport(BackendType type
)
473 { return (type
== BackendType::Playback
|| type
== BackendType::Capture
); }
475 void SndIOBackendFactory::probe(DevProbe type
, std::string
*outnames
)
479 case DevProbe::Playback
:
480 case DevProbe::Capture
:
481 /* Includes null char. */
482 outnames
->append(sndio_device
, sizeof(sndio_device
));
487 BackendPtr
SndIOBackendFactory::createBackend(ALCdevice
*device
, BackendType type
)
489 if(type
== BackendType::Playback
)
490 return BackendPtr
{new SndioPlayback
{device
}};
491 if(type
== BackendType::Capture
)
492 return BackendPtr
{new SndioCapture
{device
}};