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>
45 #include "alc/alconfig.h"
47 #include "alnumeric.h"
48 #include "aloptional.h"
49 #include "core/device.h"
50 #include "core/helpers.h"
51 #include "core/logging.h"
52 #include "ringbuffer.h"
56 #include <sys/soundcard.h>
59 * The OSS documentation talks about SOUND_MIXER_READ, but the header
60 * only contains MIXER_READ. Play safe. Same for WRITE.
62 #ifndef SOUND_MIXER_READ
63 #define SOUND_MIXER_READ MIXER_READ
65 #ifndef SOUND_MIXER_WRITE
66 #define SOUND_MIXER_WRITE MIXER_WRITE
69 #if defined(SOUND_VERSION) && (SOUND_VERSION < 0x040000)
70 #define ALC_OSS_COMPAT
72 #ifndef SNDCTL_AUDIOINFO
73 #define ALC_OSS_COMPAT
77 * FreeBSD strongly discourages the use of specific devices,
78 * such as those returned in oss_audioinfo.devnode
81 #define ALC_OSS_DEVNODE_TRUC
86 constexpr char DefaultName[] = "OSS Default";
87 std::string DefaultPlayback{"/dev/dsp"};
88 std::string DefaultCapture{"/dev/dsp"};
92 std::string device_name;
95 al::vector<DevMap> PlaybackDevices;
96 al::vector<DevMap> CaptureDevices;
101 #define DSP_CAP_OUTPUT 0x00020000
102 #define DSP_CAP_INPUT 0x00010000
103 void ALCossListPopulate(al::vector<DevMap> &devlist, int type)
105 devlist.emplace_back(DevMap{DefaultName, (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback});
110 void ALCossListAppend(al::vector<DevMap> &list, al::span<const char> handle, al::span<const char> path)
112 #ifdef ALC_OSS_DEVNODE_TRUC
113 for(size_t i{0};i < path.size();++i)
115 if(path[i] == '.' && handle.size() + i >= path.size())
117 const size_t hoffset{handle.size() + i - path.size()};
118 if(strncmp(path.data() + i, handle.data() + hoffset, path.size() - i) == 0)
119 handle = handle.first(hoffset);
120 path = path.first(i);
127 std::string basename{handle.data(), handle.size()};
128 std::string devname{path.data(), path.size()};
130 auto match_devname = [&devname](const DevMap &entry) -> bool
131 { return entry.device_name == devname; };
132 if(std::find_if(list.cbegin(), list.cend(), match_devname) != list.cend())
135 auto checkName = [&list](const std::string &name) -> bool
137 auto match_name = [&name](const DevMap &entry) -> bool { return entry.name == name; };
138 return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
141 std::string newname{basename};
142 while(checkName(newname))
146 newname += std::to_string(++count);
149 list.emplace_back(DevMap{std::move(newname), std::move(devname)});
150 const DevMap &entry = list.back();
152 TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
155 void ALCossListPopulate(al::vector<DevMap> &devlist, int type_flag)
157 int fd{open("/dev/mixer", O_RDONLY)};
160 TRACE("Could not open /dev/mixer: %s\n", strerror(errno));
165 if(ioctl(fd, SNDCTL_SYSINFO, &si) == -1)
167 TRACE("SNDCTL_SYSINFO failed: %s\n", strerror(errno));
171 for(int i{0};i < si.numaudios;i++)
175 if(ioctl(fd, SNDCTL_AUDIOINFO, &ai) == -1)
177 ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i, strerror(errno));
180 if(!(ai.caps&type_flag) || ai.devnode[0] == '\0')
183 al::span<const char> handle;
184 if(ai.handle[0] != '\0')
185 handle = {ai.handle, strnlen(ai.handle, sizeof(ai.handle))};
187 handle = {ai.name, strnlen(ai.name, sizeof(ai.name))};
188 al::span<const char> devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))};
190 ALCossListAppend(devlist, handle, devnode);
198 const char *defdev{((type_flag==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback).c_str()};
199 auto iter = std::find_if(devlist.cbegin(), devlist.cend(),
200 [defdev](const DevMap &entry) -> bool
201 { return entry.device_name == defdev; }
203 if(iter == devlist.cend())
204 devlist.insert(devlist.begin(), DevMap{DefaultName, defdev});
207 DevMap entry{std::move(*iter)};
209 devlist.insert(devlist.begin(), std::move(entry));
211 devlist.shrink_to_fit();
228 struct OSSPlayback final : public BackendBase {
229 OSSPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
230 ~OSSPlayback() override;
234 void open(const char *name) override;
235 bool reset() override;
236 void start() override;
237 void stop() override;
241 al::vector<al::byte> mMixData;
243 std::atomic<bool> mKillNow{true};
246 DEF_NEWDEL(OSSPlayback)
249 OSSPlayback::~OSSPlayback()
257 int OSSPlayback::mixerProc()
260 althrd_setname(MIXER_THREAD_NAME);
262 const size_t frame_step{mDevice->channelsFromFmt()};
263 const size_t frame_size{mDevice->frameSizeFromFmt()};
265 while(!mKillNow.load(std::memory_order_acquire)
266 && mDevice->Connected.load(std::memory_order_acquire))
270 pollitem.events = POLLOUT;
272 int pret{poll(&pollitem, 1, 1000)};
275 if(errno == EINTR || errno == EAGAIN)
277 ERR("poll failed: %s\n", strerror(errno));
278 mDevice->handleDisconnect("Failed waiting for playback buffer: %s", strerror(errno));
283 WARN("poll timeout\n");
287 al::byte *write_ptr{mMixData.data()};
288 size_t to_write{mMixData.size()};
289 mDevice->renderSamples(write_ptr, static_cast<uint>(to_write/frame_size), frame_step);
290 while(to_write > 0 && !mKillNow.load(std::memory_order_acquire))
292 ssize_t wrote{write(mFd, write_ptr, to_write)};
295 if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
297 ERR("write failed: %s\n", strerror(errno));
298 mDevice->handleDisconnect("Failed writing playback samples: %s", strerror(errno));
302 to_write -= static_cast<size_t>(wrote);
311 void OSSPlayback::open(const char *name)
313 const char *devname{DefaultPlayback.c_str()};
318 if(PlaybackDevices.empty())
319 ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT);
321 auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
322 [&name](const DevMap &entry) -> bool
323 { return entry.name == name; }
325 if(iter == PlaybackDevices.cend())
326 throw al::backend_exception{al::backend_error::NoDevice,
327 "Device name \"%s\" not found", name};
328 devname = iter->device_name.c_str();
331 int fd{::open(devname, O_WRONLY)};
333 throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
340 mDevice->DeviceName = name;
343 bool OSSPlayback::reset()
346 switch(mDevice->FmtType)
358 mDevice->FmtType = DevFmtShort;
361 ossFormat = AFMT_S16_NE;
365 uint periods{mDevice->BufferSize / mDevice->UpdateSize};
366 uint numChannels{mDevice->channelsFromFmt()};
367 uint ossSpeed{mDevice->Frequency};
368 uint frameSize{numChannels * mDevice->bytesFromFmt()};
369 /* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */
370 uint log2FragmentSize{maxu(log2i(mDevice->UpdateSize*frameSize), 4)};
371 uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
373 audio_buf_info info{};
375 #define CHECKERR(func) if((func) < 0) { \
379 /* Don't fail if SETFRAGMENT fails. We can handle just about anything
380 * that's reported back via GETOSPACE */
381 ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize);
382 CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
383 CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels));
384 CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed));
385 CHECKERR(ioctl(mFd, SNDCTL_DSP_GETOSPACE, &info));
389 ERR("%s failed: %s\n", err, strerror(errno));
394 if(mDevice->channelsFromFmt() != numChannels)
396 ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice->FmtChans),
401 if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte) ||
402 (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) ||
403 (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort)))
405 ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(mDevice->FmtType),
410 mDevice->Frequency = ossSpeed;
411 mDevice->UpdateSize = static_cast<uint>(info.fragsize) / frameSize;
412 mDevice->BufferSize = static_cast<uint>(info.fragments) * mDevice->UpdateSize;
414 setDefaultChannelOrder();
416 mMixData.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
421 void OSSPlayback::start()
424 mKillNow.store(false, std::memory_order_release);
425 mThread = std::thread{std::mem_fn(&OSSPlayback::mixerProc), this};
427 catch(std::exception& e) {
428 throw al::backend_exception{al::backend_error::DeviceError,
429 "Failed to start mixing thread: %s", e.what()};
433 void OSSPlayback::stop()
435 if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
439 if(ioctl(mFd, SNDCTL_DSP_RESET) != 0)
440 ERR("Error resetting device: %s\n", strerror(errno));
444 struct OSScapture final : public BackendBase {
445 OSScapture(DeviceBase *device) noexcept : BackendBase{device} { }
446 ~OSScapture() override;
450 void open(const char *name) override;
451 void start() override;
452 void stop() override;
453 void captureSamples(al::byte *buffer, uint samples) override;
454 uint availableSamples() override;
458 RingBufferPtr mRing{nullptr};
460 std::atomic<bool> mKillNow{true};
463 DEF_NEWDEL(OSScapture)
466 OSScapture::~OSScapture()
474 int OSScapture::recordProc()
477 althrd_setname(RECORD_THREAD_NAME);
479 const size_t frame_size{mDevice->frameSizeFromFmt()};
480 while(!mKillNow.load(std::memory_order_acquire))
484 pollitem.events = POLLIN;
486 int sret{poll(&pollitem, 1, 1000)};
489 if(errno == EINTR || errno == EAGAIN)
491 ERR("poll failed: %s\n", strerror(errno));
492 mDevice->handleDisconnect("Failed to check capture samples: %s", strerror(errno));
497 WARN("poll timeout\n");
501 auto vec = mRing->getWriteVector();
502 if(vec.first.len > 0)
504 ssize_t amt{read(mFd, vec.first.buf, vec.first.len*frame_size)};
507 ERR("read failed: %s\n", strerror(errno));
508 mDevice->handleDisconnect("Failed reading capture samples: %s", strerror(errno));
511 mRing->writeAdvance(static_cast<size_t>(amt)/frame_size);
519 void OSScapture::open(const char *name)
521 const char *devname{DefaultCapture.c_str()};
526 if(CaptureDevices.empty())
527 ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT);
529 auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
530 [&name](const DevMap &entry) -> bool
531 { return entry.name == name; }
533 if(iter == CaptureDevices.cend())
534 throw al::backend_exception{al::backend_error::NoDevice,
535 "Device name \"%s\" not found", name};
536 devname = iter->device_name.c_str();
539 mFd = ::open(devname, O_RDONLY);
541 throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
545 switch(mDevice->FmtType)
554 ossFormat = AFMT_S16_NE;
560 throw al::backend_exception{al::backend_error::DeviceError,
561 "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
565 uint numChannels{mDevice->channelsFromFmt()};
566 uint frameSize{numChannels * mDevice->bytesFromFmt()};
567 uint ossSpeed{mDevice->Frequency};
568 /* according to the OSS spec, 16 bytes are the minimum */
569 uint log2FragmentSize{maxu(log2i(mDevice->BufferSize * frameSize / periods), 4)};
570 uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
572 audio_buf_info info{};
573 #define CHECKERR(func) if((func) < 0) { \
574 throw al::backend_exception{al::backend_error::DeviceError, #func " failed: %s", \
577 CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize));
578 CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
579 CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels));
580 CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed));
581 CHECKERR(ioctl(mFd, SNDCTL_DSP_GETISPACE, &info));
584 if(mDevice->channelsFromFmt() != numChannels)
585 throw al::backend_exception{al::backend_error::DeviceError,
586 "Failed to set %s, got %d channels instead", DevFmtChannelsString(mDevice->FmtChans),
589 if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte)
590 || (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte)
591 || (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort)))
592 throw al::backend_exception{al::backend_error::DeviceError,
593 "Failed to set %s samples, got OSS format %#x", DevFmtTypeString(mDevice->FmtType),
596 mRing = RingBuffer::Create(mDevice->BufferSize, frameSize, false);
598 mDevice->DeviceName = name;
601 void OSScapture::start()
604 mKillNow.store(false, std::memory_order_release);
605 mThread = std::thread{std::mem_fn(&OSScapture::recordProc), this};
607 catch(std::exception& e) {
608 throw al::backend_exception{al::backend_error::DeviceError,
609 "Failed to start recording thread: %s", e.what()};
613 void OSScapture::stop()
615 if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
619 if(ioctl(mFd, SNDCTL_DSP_RESET) != 0)
620 ERR("Error resetting device: %s\n", strerror(errno));
623 void OSScapture::captureSamples(al::byte *buffer, uint samples)
624 { mRing->read(buffer, samples); }
626 uint OSScapture::availableSamples()
627 { return static_cast<uint>(mRing->readSpace()); }
632 BackendFactory &OSSBackendFactory::getFactory()
634 static OSSBackendFactory factory{};
638 bool OSSBackendFactory::init()
640 if(auto devopt = ConfigValueStr(nullptr, "oss", "device"))
641 DefaultPlayback = std::move(*devopt);
642 if(auto capopt = ConfigValueStr(nullptr, "oss", "capture"))
643 DefaultCapture = std::move(*capopt);
648 bool OSSBackendFactory::querySupport(BackendType type)
649 { return (type == BackendType::Playback || type == BackendType::Capture); }
651 std::string OSSBackendFactory::probe(BackendType type)
653 std::string outnames;
655 auto add_device = [&outnames](const DevMap &entry) -> void
658 if(stat(entry.device_name.c_str(), &buf) == 0)
660 /* Includes null char. */
661 outnames.append(entry.name.c_str(), entry.name.length()+1);
667 case BackendType::Playback:
668 PlaybackDevices.clear();
669 ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT);
670 std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
673 case BackendType::Capture:
674 CaptureDevices.clear();
675 ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT);
676 std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
683 BackendPtr OSSBackendFactory::createBackend(DeviceBase *device, BackendType type)
685 if(type == BackendType::Playback)
686 return BackendPtr{new OSSPlayback{device}};
687 if(type == BackendType::Capture)
688 return BackendPtr{new OSScapture{device}};