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
27 #include <sys/ioctl.h>
39 #include <string_view>
40 #include <system_error>
45 #include "alc/alconfig.h"
47 #include "alnumeric.h"
49 #include "althrd_setname.h"
50 #include "core/device.h"
51 #include "core/helpers.h"
52 #include "core/logging.h"
53 #include "ringbuffer.h"
55 #include <sys/soundcard.h>
58 * The OSS documentation talks about SOUND_MIXER_READ, but the header
59 * only contains MIXER_READ. Play safe. Same for WRITE.
61 #ifndef SOUND_MIXER_READ
62 #define SOUND_MIXER_READ MIXER_READ
64 #ifndef SOUND_MIXER_WRITE
65 #define SOUND_MIXER_WRITE MIXER_WRITE
68 #if defined(SOUND_VERSION) && (SOUND_VERSION < 0x040000)
69 #define ALC_OSS_COMPAT
71 #ifndef SNDCTL_AUDIOINFO
72 #define ALC_OSS_COMPAT
76 * FreeBSD strongly discourages the use of specific devices,
77 * such as those returned in oss_audioinfo.devnode
80 #define ALC_OSS_DEVNODE_TRUC
85 using namespace std::string_literals
;
86 using namespace std::string_view_literals
;
88 [[nodiscard
]] constexpr auto GetDefaultName() noexcept
{ return "OSS Default"sv
; }
90 std::string DefaultPlayback
{"/dev/dsp"s
};
91 std::string DefaultCapture
{"/dev/dsp"s
};
95 std::string device_name
;
97 template<typename T
, typename U
>
98 DevMap(T
&& name_
, U
&& devname_
)
99 : name
{std::forward
<T
>(name_
)}, device_name
{std::forward
<U
>(devname_
)}
103 std::vector
<DevMap
> PlaybackDevices
;
104 std::vector
<DevMap
> CaptureDevices
;
107 #ifdef ALC_OSS_COMPAT
109 #define DSP_CAP_OUTPUT 0x00020000
110 #define DSP_CAP_INPUT 0x00010000
111 void ALCossListPopulate(std::vector
<DevMap
> &devlist
, int type
)
113 devlist
.emplace_back(GetDefaultName(), (type
==DSP_CAP_INPUT
) ? DefaultCapture
: DefaultPlayback
);
122 FileHandle() = default;
123 FileHandle(const FileHandle
&) = delete;
124 FileHandle
& operator=(const FileHandle
&) = delete;
125 ~FileHandle() { if(mFd
!= -1) ::close(mFd
); }
127 template<typename
...Args
>
128 [[nodiscard
]] auto open(const char *fname
, Args
&& ...args
) -> bool
131 mFd
= ::open(fname
, std::forward
<Args
>(args
)...);
142 auto get() const noexcept
-> int { return mFd
; }
145 void ALCossListAppend(std::vector
<DevMap
> &list
, std::string_view handle
, std::string_view path
)
147 #ifdef ALC_OSS_DEVNODE_TRUC
148 for(size_t i
{0};i
< path
.size();++i
)
150 if(path
[i
] == '.' && handle
.size() >= path
.size() - i
)
152 const size_t hoffset
{handle
.size() + i
- path
.size()};
153 if(strncmp(path
.data() + i
, handle
.data() + hoffset
, path
.size() - i
) == 0)
154 handle
= handle
.substr(0, hoffset
);
155 path
= path
.substr(0, i
);
162 auto match_devname
= [path
](const DevMap
&entry
) -> bool
163 { return entry
.device_name
== path
; };
164 if(std::find_if(list
.cbegin(), list
.cend(), match_devname
) != list
.cend())
167 auto checkName
= [&list
](const std::string_view name
) -> bool
169 auto match_name
= [name
](const DevMap
&entry
) -> bool { return entry
.name
== name
; };
170 return std::find_if(list
.cbegin(), list
.cend(), match_name
) != list
.cend();
173 std::string newname
{handle
};
174 while(checkName(newname
))
178 newname
+= std::to_string(++count
);
181 const DevMap
&entry
= list
.emplace_back(std::move(newname
), path
);
183 TRACE("Got device \"%s\", \"%s\"\n", entry
.name
.c_str(), entry
.device_name
.c_str());
186 void ALCossListPopulate(std::vector
<DevMap
> &devlist
, int type_flag
)
190 if(!file
.open("/dev/mixer", O_RDONLY
))
192 TRACE("Could not open /dev/mixer: %s\n", std::generic_category().message(errno
).c_str());
196 if(ioctl(file
.get(), SNDCTL_SYSINFO
, &si
) == -1)
198 TRACE("SNDCTL_SYSINFO failed: %s\n", std::generic_category().message(errno
).c_str());
202 for(int i
{0};i
< si
.numaudios
;i
++)
206 if(ioctl(file
.get(), SNDCTL_AUDIOINFO
, &ai
) == -1)
208 ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i
,
209 std::generic_category().message(errno
).c_str());
212 if(!(ai
.caps
&type_flag
) || ai
.devnode
[0] == '\0')
215 std::string_view handle
;
216 if(ai
.handle
[0] != '\0')
217 handle
= {ai
.handle
, strnlen(ai
.handle
, sizeof(ai
.handle
))};
219 handle
= {ai
.name
, strnlen(ai
.name
, sizeof(ai
.name
))};
220 const std::string_view devnode
{ai
.devnode
, strnlen(ai
.devnode
, sizeof(ai
.devnode
))};
222 ALCossListAppend(devlist
, handle
, devnode
);
228 const char *defdev
{((type_flag
==DSP_CAP_INPUT
) ? DefaultCapture
: DefaultPlayback
).c_str()};
229 auto iter
= std::find_if(devlist
.cbegin(), devlist
.cend(),
230 [defdev
](const DevMap
&entry
) -> bool
231 { return entry
.device_name
== defdev
; }
233 if(iter
== devlist
.cend())
234 devlist
.insert(devlist
.begin(), DevMap
{GetDefaultName(), defdev
});
237 DevMap entry
{std::move(*iter
)};
239 devlist
.insert(devlist
.begin(), std::move(entry
));
241 devlist
.shrink_to_fit();
258 struct OSSPlayback final
: public BackendBase
{
259 OSSPlayback(DeviceBase
*device
) noexcept
: BackendBase
{device
} { }
260 ~OSSPlayback() override
;
264 void open(std::string_view name
) override
;
265 bool reset() override
;
266 void start() override
;
267 void stop() override
;
271 std::vector
<std::byte
> mMixData
;
273 std::atomic
<bool> mKillNow
{true};
277 OSSPlayback::~OSSPlayback()
285 int OSSPlayback::mixerProc()
288 althrd_setname(GetMixerThreadName());
290 const size_t frame_step
{mDevice
->channelsFromFmt()};
291 const size_t frame_size
{mDevice
->frameSizeFromFmt()};
293 while(!mKillNow
.load(std::memory_order_acquire
)
294 && mDevice
->Connected
.load(std::memory_order_acquire
))
298 pollitem
.events
= POLLOUT
;
300 if(int pret
{poll(&pollitem
, 1, 1000)}; pret
< 0)
302 if(errno
== EINTR
|| errno
== EAGAIN
)
304 const auto errstr
= std::generic_category().message(errno
);
305 ERR("poll failed: %s\n", errstr
.c_str());
306 mDevice
->handleDisconnect("Failed waiting for playback buffer: %s", errstr
.c_str());
309 else if(pret
== 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */
311 WARN("poll timeout\n");
315 al::span write_buf
{mMixData
};
316 mDevice
->renderSamples(write_buf
.data(), static_cast<uint
>(write_buf
.size()/frame_size
),
318 while(!write_buf
.empty() && !mKillNow
.load(std::memory_order_acquire
))
320 ssize_t wrote
{write(mFd
, write_buf
.data(), write_buf
.size())};
323 if(errno
== EAGAIN
|| errno
== EWOULDBLOCK
|| errno
== EINTR
)
325 const auto errstr
= std::generic_category().message(errno
);
326 ERR("write failed: %s\n", errstr
.c_str());
327 mDevice
->handleDisconnect("Failed writing playback samples: %s", errstr
.c_str());
331 write_buf
= write_buf
.subspan(static_cast<size_t>(wrote
));
339 void OSSPlayback::open(std::string_view name
)
341 const char *devname
{DefaultPlayback
.c_str()};
343 name
= GetDefaultName();
346 if(PlaybackDevices
.empty())
347 ALCossListPopulate(PlaybackDevices
, DSP_CAP_OUTPUT
);
349 auto iter
= std::find_if(PlaybackDevices
.cbegin(), PlaybackDevices
.cend(),
350 [&name
](const DevMap
&entry
) -> bool
351 { return entry
.name
== name
; }
353 if(iter
== PlaybackDevices
.cend())
354 throw al::backend_exception
{al::backend_error::NoDevice
,
355 "Device name \"%.*s\" not found", al::sizei(name
), name
.data()};
356 devname
= iter
->device_name
.c_str();
359 int fd
{::open(devname
, O_WRONLY
)};
361 throw al::backend_exception
{al::backend_error::NoDevice
, "Could not open %s: %s", devname
,
362 std::generic_category().message(errno
).c_str()};
371 bool OSSPlayback::reset()
374 switch(mDevice
->FmtType
)
386 mDevice
->FmtType
= DevFmtShort
;
389 ossFormat
= AFMT_S16_NE
;
393 uint periods
{mDevice
->BufferSize
/ mDevice
->UpdateSize
};
394 uint numChannels
{mDevice
->channelsFromFmt()};
395 uint ossSpeed
{mDevice
->Frequency
};
396 uint frameSize
{numChannels
* mDevice
->bytesFromFmt()};
397 /* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */
398 uint log2FragmentSize
{std::max(log2i(mDevice
->UpdateSize
*frameSize
), 4u)};
399 uint numFragmentsLogSize
{(periods
<< 16) | log2FragmentSize
};
401 audio_buf_info info
{};
402 #define CHECKERR(func) if((func) < 0) \
403 throw al::backend_exception{al::backend_error::DeviceError, "%s failed: %s\n", #func, \
404 std::generic_category().message(errno).c_str()};
406 /* Don't fail if SETFRAGMENT fails. We can handle just about anything
407 * that's reported back via GETOSPACE */
408 ioctl(mFd
, SNDCTL_DSP_SETFRAGMENT
, &numFragmentsLogSize
);
409 CHECKERR(ioctl(mFd
, SNDCTL_DSP_SETFMT
, &ossFormat
));
410 CHECKERR(ioctl(mFd
, SNDCTL_DSP_CHANNELS
, &numChannels
));
411 CHECKERR(ioctl(mFd
, SNDCTL_DSP_SPEED
, &ossSpeed
));
412 CHECKERR(ioctl(mFd
, SNDCTL_DSP_GETOSPACE
, &info
));
415 if(mDevice
->channelsFromFmt() != numChannels
)
417 ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice
->FmtChans
),
422 if(!((ossFormat
== AFMT_S8
&& mDevice
->FmtType
== DevFmtByte
) ||
423 (ossFormat
== AFMT_U8
&& mDevice
->FmtType
== DevFmtUByte
) ||
424 (ossFormat
== AFMT_S16_NE
&& mDevice
->FmtType
== DevFmtShort
)))
426 ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(mDevice
->FmtType
),
431 mDevice
->Frequency
= ossSpeed
;
432 mDevice
->UpdateSize
= static_cast<uint
>(info
.fragsize
) / frameSize
;
433 mDevice
->BufferSize
= static_cast<uint
>(info
.fragments
) * mDevice
->UpdateSize
;
435 setDefaultChannelOrder();
437 mMixData
.resize(size_t{mDevice
->UpdateSize
} * mDevice
->frameSizeFromFmt());
442 void OSSPlayback::start()
445 mKillNow
.store(false, std::memory_order_release
);
446 mThread
= std::thread
{std::mem_fn(&OSSPlayback::mixerProc
), this};
448 catch(std::exception
& e
) {
449 throw al::backend_exception
{al::backend_error::DeviceError
,
450 "Failed to start mixing thread: %s", e
.what()};
454 void OSSPlayback::stop()
456 if(mKillNow
.exchange(true, std::memory_order_acq_rel
) || !mThread
.joinable())
460 if(ioctl(mFd
, SNDCTL_DSP_RESET
) != 0)
461 ERR("Error resetting device: %s\n", std::generic_category().message(errno
).c_str());
465 struct OSScapture final
: public BackendBase
{
466 OSScapture(DeviceBase
*device
) noexcept
: BackendBase
{device
} { }
467 ~OSScapture() override
;
471 void open(std::string_view name
) override
;
472 void start() override
;
473 void stop() override
;
474 void captureSamples(std::byte
*buffer
, uint samples
) override
;
475 uint
availableSamples() override
;
479 RingBufferPtr mRing
{nullptr};
481 std::atomic
<bool> mKillNow
{true};
485 OSScapture::~OSScapture()
493 int OSScapture::recordProc()
496 althrd_setname(GetRecordThreadName());
498 const size_t frame_size
{mDevice
->frameSizeFromFmt()};
499 while(!mKillNow
.load(std::memory_order_acquire
))
503 pollitem
.events
= POLLIN
;
505 if(int pret
{poll(&pollitem
, 1, 1000)}; pret
< 0)
507 if(errno
== EINTR
|| errno
== EAGAIN
)
509 const auto errstr
= std::generic_category().message(errno
);
510 ERR("poll failed: %s\n", errstr
.c_str());
511 mDevice
->handleDisconnect("Failed to check capture samples: %s", errstr
.c_str());
514 else if(pret
== 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */
516 WARN("poll timeout\n");
520 auto vec
= mRing
->getWriteVector();
521 if(vec
.first
.len
> 0)
523 ssize_t amt
{read(mFd
, vec
.first
.buf
, vec
.first
.len
*frame_size
)};
526 const auto errstr
= std::generic_category().message(errno
);
527 ERR("read failed: %s\n", errstr
.c_str());
528 mDevice
->handleDisconnect("Failed reading capture samples: %s", errstr
.c_str());
531 mRing
->writeAdvance(static_cast<size_t>(amt
)/frame_size
);
539 void OSScapture::open(std::string_view name
)
541 const char *devname
{DefaultCapture
.c_str()};
543 name
= GetDefaultName();
546 if(CaptureDevices
.empty())
547 ALCossListPopulate(CaptureDevices
, DSP_CAP_INPUT
);
549 auto iter
= std::find_if(CaptureDevices
.cbegin(), CaptureDevices
.cend(),
550 [&name
](const DevMap
&entry
) -> bool
551 { return entry
.name
== name
; }
553 if(iter
== CaptureDevices
.cend())
554 throw al::backend_exception
{al::backend_error::NoDevice
,
555 "Device name \"%.*s\" not found", al::sizei(name
), name
.data()};
556 devname
= iter
->device_name
.c_str();
559 mFd
= ::open(devname
, O_RDONLY
);
561 throw al::backend_exception
{al::backend_error::NoDevice
, "Could not open %s: %s", devname
,
562 std::generic_category().message(errno
).c_str()};
565 switch(mDevice
->FmtType
)
574 ossFormat
= AFMT_S16_NE
;
580 throw al::backend_exception
{al::backend_error::DeviceError
,
581 "%s capture samples not supported", DevFmtTypeString(mDevice
->FmtType
)};
585 uint numChannels
{mDevice
->channelsFromFmt()};
586 uint frameSize
{numChannels
* mDevice
->bytesFromFmt()};
587 uint ossSpeed
{mDevice
->Frequency
};
588 /* according to the OSS spec, 16 bytes are the minimum */
589 uint log2FragmentSize
{std::max(log2i(mDevice
->BufferSize
* frameSize
/ periods
), 4u)};
590 uint numFragmentsLogSize
{(periods
<< 16) | log2FragmentSize
};
592 audio_buf_info info
{};
593 #define CHECKERR(func) if((func) < 0) { \
594 throw al::backend_exception{al::backend_error::DeviceError, #func " failed: %s", \
595 std::generic_category().message(errno).c_str()}; \
597 CHECKERR(ioctl(mFd
, SNDCTL_DSP_SETFRAGMENT
, &numFragmentsLogSize
));
598 CHECKERR(ioctl(mFd
, SNDCTL_DSP_SETFMT
, &ossFormat
));
599 CHECKERR(ioctl(mFd
, SNDCTL_DSP_CHANNELS
, &numChannels
));
600 CHECKERR(ioctl(mFd
, SNDCTL_DSP_SPEED
, &ossSpeed
));
601 CHECKERR(ioctl(mFd
, SNDCTL_DSP_GETISPACE
, &info
));
604 if(mDevice
->channelsFromFmt() != numChannels
)
605 throw al::backend_exception
{al::backend_error::DeviceError
,
606 "Failed to set %s, got %d channels instead", DevFmtChannelsString(mDevice
->FmtChans
),
609 if(!((ossFormat
== AFMT_S8
&& mDevice
->FmtType
== DevFmtByte
)
610 || (ossFormat
== AFMT_U8
&& mDevice
->FmtType
== DevFmtUByte
)
611 || (ossFormat
== AFMT_S16_NE
&& mDevice
->FmtType
== DevFmtShort
)))
612 throw al::backend_exception
{al::backend_error::DeviceError
,
613 "Failed to set %s samples, got OSS format %#x", DevFmtTypeString(mDevice
->FmtType
),
616 mRing
= RingBuffer::Create(mDevice
->BufferSize
, frameSize
, false);
621 void OSScapture::start()
624 mKillNow
.store(false, std::memory_order_release
);
625 mThread
= std::thread
{std::mem_fn(&OSScapture::recordProc
), this};
627 catch(std::exception
& e
) {
628 throw al::backend_exception
{al::backend_error::DeviceError
,
629 "Failed to start recording thread: %s", e
.what()};
633 void OSScapture::stop()
635 if(mKillNow
.exchange(true, std::memory_order_acq_rel
) || !mThread
.joinable())
639 if(ioctl(mFd
, SNDCTL_DSP_RESET
) != 0)
640 ERR("Error resetting device: %s\n", std::generic_category().message(errno
).c_str());
643 void OSScapture::captureSamples(std::byte
*buffer
, uint samples
)
644 { std::ignore
= mRing
->read(buffer
, samples
); }
646 uint
OSScapture::availableSamples()
647 { return static_cast<uint
>(mRing
->readSpace()); }
652 BackendFactory
&OSSBackendFactory::getFactory()
654 static OSSBackendFactory factory
{};
658 bool OSSBackendFactory::init()
660 if(auto devopt
= ConfigValueStr({}, "oss", "device"))
661 DefaultPlayback
= std::move(*devopt
);
662 if(auto capopt
= ConfigValueStr({}, "oss", "capture"))
663 DefaultCapture
= std::move(*capopt
);
668 bool OSSBackendFactory::querySupport(BackendType type
)
669 { return (type
== BackendType::Playback
|| type
== BackendType::Capture
); }
671 auto OSSBackendFactory::enumerate(BackendType type
) -> std::vector
<std::string
>
673 std::vector
<std::string
> outnames
;
674 auto add_device
= [&outnames
](const DevMap
&entry
) -> void
676 if(struct stat buf
{}; stat(entry
.device_name
.c_str(), &buf
) == 0)
677 outnames
.emplace_back(entry
.name
);
682 case BackendType::Playback
:
683 PlaybackDevices
.clear();
684 ALCossListPopulate(PlaybackDevices
, DSP_CAP_OUTPUT
);
685 outnames
.reserve(PlaybackDevices
.size());
686 std::for_each(PlaybackDevices
.cbegin(), PlaybackDevices
.cend(), add_device
);
689 case BackendType::Capture
:
690 CaptureDevices
.clear();
691 ALCossListPopulate(CaptureDevices
, DSP_CAP_INPUT
);
692 outnames
.reserve(CaptureDevices
.size());
693 std::for_each(CaptureDevices
.cbegin(), CaptureDevices
.cend(), add_device
);
700 BackendPtr
OSSBackendFactory::createBackend(DeviceBase
*device
, BackendType type
)
702 if(type
== BackendType::Playback
)
703 return BackendPtr
{new OSSPlayback
{device
}};
704 if(type
== BackendType::Capture
)
705 return BackendPtr
{new OSScapture
{device
}};