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/oss.h"
27 #include <sys/ioctl.h>
50 #include "alnumeric.h"
51 #include "aloptional.h"
54 #include "ringbuffer.h"
58 #include <sys/soundcard.h>
61 * The OSS documentation talks about SOUND_MIXER_READ, but the header
62 * only contains MIXER_READ. Play safe. Same for WRITE.
64 #ifndef SOUND_MIXER_READ
65 #define SOUND_MIXER_READ MIXER_READ
67 #ifndef SOUND_MIXER_WRITE
68 #define SOUND_MIXER_WRITE MIXER_WRITE
71 #if defined(SOUND_VERSION) && (SOUND_VERSION < 0x040000)
72 #define ALC_OSS_COMPAT
74 #ifndef SNDCTL_AUDIOINFO
75 #define ALC_OSS_COMPAT
79 * FreeBSD strongly discourages the use of specific devices,
80 * such as those returned in oss_audioinfo.devnode
83 #define ALC_OSS_DEVNODE_TRUC
88 constexpr char DefaultName
[] = "OSS Default";
89 std::string DefaultPlayback
{"/dev/dsp"};
90 std::string DefaultCapture
{"/dev/dsp"};
94 std::string device_name
;
97 al::vector
<DevMap
> PlaybackDevices
;
98 al::vector
<DevMap
> CaptureDevices
;
101 #ifdef ALC_OSS_COMPAT
103 #define DSP_CAP_OUTPUT 0x00020000
104 #define DSP_CAP_INPUT 0x00010000
105 void ALCossListPopulate(al::vector
<DevMap
> &devlist
, int type
)
107 devlist
.emplace_back(DevMap
{DefaultName
, (type
==DSP_CAP_INPUT
) ? DefaultCapture
: DefaultPlayback
});
112 void ALCossListAppend(al::vector
<DevMap
> &list
, al::span
<const char> handle
, al::span
<const char> path
)
114 #ifdef ALC_OSS_DEVNODE_TRUC
115 for(size_t i
{0};i
< path
.size();++i
)
117 if(path
[i
] == '.' && handle
.size() + i
>= path
.size())
119 const size_t hoffset
{handle
.size() + i
- path
.size()};
120 if(strncmp(path
.data() + i
, handle
.data() + hoffset
, path
.size() - i
) == 0)
121 handle
= handle
.first(hoffset
);
122 path
= path
.first(i
);
129 std::string basename
{handle
.data(), handle
.size()};
130 std::string devname
{path
.data(), path
.size()};
132 auto match_devname
= [&devname
](const DevMap
&entry
) -> bool
133 { return entry
.device_name
== devname
; };
134 if(std::find_if(list
.cbegin(), list
.cend(), match_devname
) != list
.cend())
137 auto checkName
= [&list
](const std::string
&name
) -> bool
139 auto match_name
= [&name
](const DevMap
&entry
) -> bool { return entry
.name
== name
; };
140 return std::find_if(list
.cbegin(), list
.cend(), match_name
) != list
.cend();
143 std::string newname
{basename
};
144 while(checkName(newname
))
148 newname
+= std::to_string(++count
);
151 list
.emplace_back(DevMap
{std::move(newname
), std::move(devname
)});
152 const DevMap
&entry
= list
.back();
154 TRACE("Got device \"%s\", \"%s\"\n", entry
.name
.c_str(), entry
.device_name
.c_str());
157 void ALCossListPopulate(al::vector
<DevMap
> &devlist
, int type_flag
)
159 int fd
{open("/dev/mixer", O_RDONLY
)};
162 TRACE("Could not open /dev/mixer: %s\n", strerror(errno
));
167 if(ioctl(fd
, SNDCTL_SYSINFO
, &si
) == -1)
169 TRACE("SNDCTL_SYSINFO failed: %s\n", strerror(errno
));
173 for(int i
{0};i
< si
.numaudios
;i
++)
177 if(ioctl(fd
, SNDCTL_AUDIOINFO
, &ai
) == -1)
179 ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i
, strerror(errno
));
182 if(!(ai
.caps
&type_flag
) || ai
.devnode
[0] == '\0')
185 al::span
<const char> handle
;
186 if(ai
.handle
[0] != '\0')
187 handle
= {ai
.handle
, strnlen(ai
.handle
, sizeof(ai
.handle
))};
189 handle
= {ai
.name
, strnlen(ai
.name
, sizeof(ai
.name
))};
190 al::span
<const char> devnode
{ai
.devnode
, strnlen(ai
.devnode
, sizeof(ai
.devnode
))};
192 ALCossListAppend(devlist
, handle
, devnode
);
200 const char *defdev
{((type_flag
==DSP_CAP_INPUT
) ? DefaultCapture
: DefaultPlayback
).c_str()};
201 auto iter
= std::find_if(devlist
.cbegin(), devlist
.cend(),
202 [defdev
](const DevMap
&entry
) -> bool
203 { return entry
.device_name
== defdev
; }
205 if(iter
== devlist
.cend())
206 devlist
.insert(devlist
.begin(), DevMap
{DefaultName
, defdev
});
209 DevMap entry
{std::move(*iter
)};
211 devlist
.insert(devlist
.begin(), std::move(entry
));
213 devlist
.shrink_to_fit();
218 ALCuint
log2i(ALCuint x
)
230 struct OSSPlayback final
: public BackendBase
{
231 OSSPlayback(ALCdevice
*device
) noexcept
: BackendBase
{device
} { }
232 ~OSSPlayback() override
;
236 void open(const ALCchar
*name
) override
;
237 bool reset() override
;
238 void start() override
;
239 void stop() override
;
243 al::vector
<ALubyte
> mMixData
;
245 std::atomic
<bool> mKillNow
{true};
248 DEF_NEWDEL(OSSPlayback
)
251 OSSPlayback::~OSSPlayback()
259 int OSSPlayback::mixerProc()
262 althrd_setname(MIXER_THREAD_NAME
);
264 const size_t frame_step
{mDevice
->channelsFromFmt()};
265 const ALuint frame_size
{mDevice
->frameSizeFromFmt()};
267 while(!mKillNow
.load(std::memory_order_acquire
) &&
268 mDevice
->Connected
.load(std::memory_order_acquire
))
272 pollitem
.events
= POLLOUT
;
274 int pret
{poll(&pollitem
, 1, 1000)};
277 if(errno
== EINTR
|| errno
== EAGAIN
)
279 ERR("poll failed: %s\n", strerror(errno
));
280 mDevice
->handleDisconnect("Failed waiting for playback buffer: %s", strerror(errno
));
285 WARN("poll timeout\n");
289 ALubyte
*write_ptr
{mMixData
.data()};
290 size_t to_write
{mMixData
.size()};
291 mDevice
->renderSamples(write_ptr
, static_cast<ALuint
>(to_write
/frame_size
), frame_step
);
292 while(to_write
> 0 && !mKillNow
.load(std::memory_order_acquire
))
294 ssize_t wrote
{write(mFd
, write_ptr
, to_write
)};
297 if(errno
== EAGAIN
|| errno
== EWOULDBLOCK
|| errno
== EINTR
)
299 ERR("write failed: %s\n", strerror(errno
));
300 mDevice
->handleDisconnect("Failed writing playback samples: %s", strerror(errno
));
304 to_write
-= static_cast<size_t>(wrote
);
313 void OSSPlayback::open(const ALCchar
*name
)
315 const char *devname
{DefaultPlayback
.c_str()};
320 if(PlaybackDevices
.empty())
321 ALCossListPopulate(PlaybackDevices
, DSP_CAP_OUTPUT
);
323 auto iter
= std::find_if(PlaybackDevices
.cbegin(), PlaybackDevices
.cend(),
324 [&name
](const DevMap
&entry
) -> bool
325 { return entry
.name
== name
; }
327 if(iter
== PlaybackDevices
.cend())
328 throw al::backend_exception
{ALC_INVALID_VALUE
, "Device name \"%s\" not found", name
};
329 devname
= iter
->device_name
.c_str();
332 mFd
= ::open(devname
, O_WRONLY
);
334 throw al::backend_exception
{ALC_INVALID_VALUE
, "Could not open %s: %s", devname
,
337 mDevice
->DeviceName
= name
;
340 bool OSSPlayback::reset()
343 switch(mDevice
->FmtType
)
355 mDevice
->FmtType
= DevFmtShort
;
358 ossFormat
= AFMT_S16_NE
;
362 ALuint periods
{mDevice
->BufferSize
/ mDevice
->UpdateSize
};
363 ALuint numChannels
{mDevice
->channelsFromFmt()};
364 ALuint ossSpeed
{mDevice
->Frequency
};
365 ALuint frameSize
{numChannels
* mDevice
->bytesFromFmt()};
366 /* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */
367 ALuint log2FragmentSize
{maxu(log2i(mDevice
->UpdateSize
*frameSize
), 4)};
368 ALuint numFragmentsLogSize
{(periods
<< 16) | log2FragmentSize
};
370 audio_buf_info info
{};
372 #define CHECKERR(func) if((func) < 0) { \
376 /* Don't fail if SETFRAGMENT fails. We can handle just about anything
377 * that's reported back via GETOSPACE */
378 ioctl(mFd
, SNDCTL_DSP_SETFRAGMENT
, &numFragmentsLogSize
);
379 CHECKERR(ioctl(mFd
, SNDCTL_DSP_SETFMT
, &ossFormat
));
380 CHECKERR(ioctl(mFd
, SNDCTL_DSP_CHANNELS
, &numChannels
));
381 CHECKERR(ioctl(mFd
, SNDCTL_DSP_SPEED
, &ossSpeed
));
382 CHECKERR(ioctl(mFd
, SNDCTL_DSP_GETOSPACE
, &info
));
386 ERR("%s failed: %s\n", err
, strerror(errno
));
391 if(mDevice
->channelsFromFmt() != numChannels
)
393 ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice
->FmtChans
),
398 if(!((ossFormat
== AFMT_S8
&& mDevice
->FmtType
== DevFmtByte
) ||
399 (ossFormat
== AFMT_U8
&& mDevice
->FmtType
== DevFmtUByte
) ||
400 (ossFormat
== AFMT_S16_NE
&& mDevice
->FmtType
== DevFmtShort
)))
402 ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(mDevice
->FmtType
),
407 mDevice
->Frequency
= ossSpeed
;
408 mDevice
->UpdateSize
= static_cast<ALuint
>(info
.fragsize
) / frameSize
;
409 mDevice
->BufferSize
= static_cast<ALuint
>(info
.fragments
) * mDevice
->UpdateSize
;
411 setDefaultChannelOrder();
413 mMixData
.resize(mDevice
->UpdateSize
* mDevice
->frameSizeFromFmt());
418 void OSSPlayback::start()
421 mKillNow
.store(false, std::memory_order_release
);
422 mThread
= std::thread
{std::mem_fn(&OSSPlayback::mixerProc
), this};
424 catch(std::exception
& e
) {
425 throw al::backend_exception
{ALC_INVALID_DEVICE
, "Failed to start mixing thread: %s",
430 void OSSPlayback::stop()
432 if(mKillNow
.exchange(true, std::memory_order_acq_rel
) || !mThread
.joinable())
436 if(ioctl(mFd
, SNDCTL_DSP_RESET
) != 0)
437 ERR("Error resetting device: %s\n", strerror(errno
));
441 struct OSScapture final
: public BackendBase
{
442 OSScapture(ALCdevice
*device
) noexcept
: BackendBase
{device
} { }
443 ~OSScapture() override
;
447 void open(const ALCchar
*name
) override
;
448 void start() override
;
449 void stop() override
;
450 ALCenum
captureSamples(al::byte
*buffer
, ALCuint samples
) override
;
451 ALCuint
availableSamples() override
;
455 RingBufferPtr mRing
{nullptr};
457 std::atomic
<bool> mKillNow
{true};
460 DEF_NEWDEL(OSScapture
)
463 OSScapture::~OSScapture()
471 int OSScapture::recordProc()
474 althrd_setname(RECORD_THREAD_NAME
);
476 const ALuint frame_size
{mDevice
->frameSizeFromFmt()};
477 while(!mKillNow
.load(std::memory_order_acquire
))
481 pollitem
.events
= POLLIN
;
483 int sret
{poll(&pollitem
, 1, 1000)};
486 if(errno
== EINTR
|| errno
== EAGAIN
)
488 ERR("poll failed: %s\n", strerror(errno
));
489 mDevice
->handleDisconnect("Failed to check capture samples: %s", strerror(errno
));
494 WARN("poll timeout\n");
498 auto vec
= mRing
->getWriteVector();
499 if(vec
.first
.len
> 0)
501 ssize_t amt
{read(mFd
, vec
.first
.buf
, vec
.first
.len
*frame_size
)};
504 ERR("read failed: %s\n", strerror(errno
));
505 mDevice
->handleDisconnect("Failed reading capture samples: %s", strerror(errno
));
508 mRing
->writeAdvance(static_cast<ALuint
>(amt
)/frame_size
);
516 void OSScapture::open(const ALCchar
*name
)
518 const char *devname
{DefaultCapture
.c_str()};
523 if(CaptureDevices
.empty())
524 ALCossListPopulate(CaptureDevices
, DSP_CAP_INPUT
);
526 auto iter
= std::find_if(CaptureDevices
.cbegin(), CaptureDevices
.cend(),
527 [&name
](const DevMap
&entry
) -> bool
528 { return entry
.name
== name
; }
530 if(iter
== CaptureDevices
.cend())
531 throw al::backend_exception
{ALC_INVALID_VALUE
, "Device name \"%s\" not found", name
};
532 devname
= iter
->device_name
.c_str();
535 mFd
= ::open(devname
, O_RDONLY
);
537 throw al::backend_exception
{ALC_INVALID_VALUE
, "Could not open %s: %s", devname
,
541 switch(mDevice
->FmtType
)
550 ossFormat
= AFMT_S16_NE
;
556 throw al::backend_exception
{ALC_INVALID_VALUE
, "%s capture samples not supported",
557 DevFmtTypeString(mDevice
->FmtType
)};
561 ALuint numChannels
{mDevice
->channelsFromFmt()};
562 ALuint frameSize
{numChannels
* mDevice
->bytesFromFmt()};
563 ALuint ossSpeed
{mDevice
->Frequency
};
564 /* according to the OSS spec, 16 bytes are the minimum */
565 ALuint log2FragmentSize
{maxu(log2i(mDevice
->BufferSize
* frameSize
/ periods
), 4)};
566 ALuint numFragmentsLogSize
{(periods
<< 16) | log2FragmentSize
};
568 audio_buf_info info
{};
569 #define CHECKERR(func) if((func) < 0) { \
570 throw al::backend_exception{ALC_INVALID_VALUE, #func " failed: %s", strerror(errno)}; \
572 CHECKERR(ioctl(mFd
, SNDCTL_DSP_SETFRAGMENT
, &numFragmentsLogSize
));
573 CHECKERR(ioctl(mFd
, SNDCTL_DSP_SETFMT
, &ossFormat
));
574 CHECKERR(ioctl(mFd
, SNDCTL_DSP_CHANNELS
, &numChannels
));
575 CHECKERR(ioctl(mFd
, SNDCTL_DSP_SPEED
, &ossSpeed
));
576 CHECKERR(ioctl(mFd
, SNDCTL_DSP_GETISPACE
, &info
));
579 if(mDevice
->channelsFromFmt() != numChannels
)
580 throw al::backend_exception
{ALC_INVALID_VALUE
,
581 "Failed to set %s, got %d channels instead", DevFmtChannelsString(mDevice
->FmtChans
),
584 if(!((ossFormat
== AFMT_S8
&& mDevice
->FmtType
== DevFmtByte
)
585 || (ossFormat
== AFMT_U8
&& mDevice
->FmtType
== DevFmtUByte
)
586 || (ossFormat
== AFMT_S16_NE
&& mDevice
->FmtType
== DevFmtShort
)))
587 throw al::backend_exception
{ALC_INVALID_VALUE
,
588 "Failed to set %s samples, got OSS format %#x", DevFmtTypeString(mDevice
->FmtType
),
591 mRing
= RingBuffer::Create(mDevice
->BufferSize
, frameSize
, false);
593 mDevice
->DeviceName
= name
;
596 void OSScapture::start()
599 mKillNow
.store(false, std::memory_order_release
);
600 mThread
= std::thread
{std::mem_fn(&OSScapture::recordProc
), this};
602 catch(std::exception
& e
) {
603 throw al::backend_exception
{ALC_INVALID_DEVICE
, "Failed to start recording thread: %s",
608 void OSScapture::stop()
610 if(mKillNow
.exchange(true, std::memory_order_acq_rel
) || !mThread
.joinable())
614 if(ioctl(mFd
, SNDCTL_DSP_RESET
) != 0)
615 ERR("Error resetting device: %s\n", strerror(errno
));
618 ALCenum
OSScapture::captureSamples(al::byte
*buffer
, ALCuint samples
)
620 mRing
->read(buffer
, samples
);
624 ALCuint
OSScapture::availableSamples()
625 { return static_cast<ALCuint
>(mRing
->readSpace()); }
630 BackendFactory
&OSSBackendFactory::getFactory()
632 static OSSBackendFactory factory
{};
636 bool OSSBackendFactory::init()
638 if(auto devopt
= ConfigValueStr(nullptr, "oss", "device"))
639 DefaultPlayback
= std::move(*devopt
);
640 if(auto capopt
= ConfigValueStr(nullptr, "oss", "capture"))
641 DefaultCapture
= std::move(*capopt
);
646 bool OSSBackendFactory::querySupport(BackendType type
)
647 { return (type
== BackendType::Playback
|| type
== BackendType::Capture
); }
649 std::string
OSSBackendFactory::probe(BackendType type
)
651 std::string outnames
;
653 auto add_device
= [&outnames
](const DevMap
&entry
) -> void
656 if(stat(entry
.device_name
.c_str(), &buf
) == 0)
658 /* Includes null char. */
659 outnames
.append(entry
.name
.c_str(), entry
.name
.length()+1);
665 case BackendType::Playback
:
666 PlaybackDevices
.clear();
667 ALCossListPopulate(PlaybackDevices
, DSP_CAP_OUTPUT
);
668 std::for_each(PlaybackDevices
.cbegin(), PlaybackDevices
.cend(), add_device
);
671 case BackendType::Capture
:
672 CaptureDevices
.clear();
673 ALCossListPopulate(CaptureDevices
, DSP_CAP_INPUT
);
674 std::for_each(CaptureDevices
.cbegin(), CaptureDevices
.cend(), add_device
);
681 BackendPtr
OSSBackendFactory::createBackend(ALCdevice
*device
, BackendType type
)
683 if(type
== BackendType::Playback
)
684 return BackendPtr
{new OSSPlayback
{device
}};
685 if(type
== BackendType::Capture
)
686 return BackendPtr
{new OSScapture
{device
}};