Clean up the debug example a little
[openal-soft.git] / alc / backends / oss.cpp
blob1370246cc8439209c954a5b813cb993768d942aa
1 /**
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
21 #include "config.h"
23 #include "oss.h"
25 #include <fcntl.h>
26 #include <poll.h>
27 #include <sys/ioctl.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
31 #include <algorithm>
32 #include <atomic>
33 #include <cerrno>
34 #include <cstring>
35 #include <exception>
36 #include <functional>
37 #include <memory>
38 #include <string>
39 #include <string_view>
40 #include <system_error>
41 #include <thread>
42 #include <utility>
43 #include <vector>
45 #include "alc/alconfig.h"
46 #include "almalloc.h"
47 #include "alnumeric.h"
48 #include "alstring.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
63 #endif
64 #ifndef SOUND_MIXER_WRITE
65 #define SOUND_MIXER_WRITE MIXER_WRITE
66 #endif
68 #if defined(SOUND_VERSION) && (SOUND_VERSION < 0x040000)
69 #define ALC_OSS_COMPAT
70 #endif
71 #ifndef SNDCTL_AUDIOINFO
72 #define ALC_OSS_COMPAT
73 #endif
76 * FreeBSD strongly discourages the use of specific devices,
77 * such as those returned in oss_audioinfo.devnode
79 #ifdef __FreeBSD__
80 #define ALC_OSS_DEVNODE_TRUC
81 #endif
83 namespace {
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};
93 struct DevMap {
94 std::string name;
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);
116 #else
118 class FileHandle {
119 int mFd{-1};
121 public:
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
130 close();
131 mFd = ::open(fname, std::forward<Args>(args)...);
132 return mFd != -1;
134 void close()
136 if(mFd != -1)
137 ::close(mFd);
138 mFd = -1;
141 [[nodiscard]]
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);
158 #endif
159 if(handle.empty())
160 handle = path;
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())
165 return;
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();
172 int count{1};
173 std::string newname{handle};
174 while(checkName(newname))
176 newname = handle;
177 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)
188 oss_sysinfo si{};
189 FileHandle file;
190 if(!file.open("/dev/mixer", O_RDONLY))
192 TRACE("Could not open /dev/mixer: %s\n", std::generic_category().message(errno).c_str());
193 goto done;
196 if(ioctl(file.get(), SNDCTL_SYSINFO, &si) == -1)
198 TRACE("SNDCTL_SYSINFO failed: %s\n", std::generic_category().message(errno).c_str());
199 goto done;
202 for(int i{0};i < si.numaudios;i++)
204 oss_audioinfo ai{};
205 ai.dev = 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());
210 continue;
212 if(!(ai.caps&type_flag) || ai.devnode[0] == '\0')
213 continue;
215 std::string_view handle;
216 if(ai.handle[0] != '\0')
217 handle = {ai.handle, strnlen(ai.handle, sizeof(ai.handle))};
218 else
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);
225 done:
226 file.close();
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});
235 else
237 DevMap entry{std::move(*iter)};
238 devlist.erase(iter);
239 devlist.insert(devlist.begin(), std::move(entry));
241 devlist.shrink_to_fit();
244 #endif
246 uint log2i(uint x)
248 uint y{0};
249 while(x > 1)
251 x >>= 1;
252 y++;
254 return y;
258 struct OSSPlayback final : public BackendBase {
259 OSSPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
260 ~OSSPlayback() override;
262 int mixerProc();
264 void open(std::string_view name) override;
265 bool reset() override;
266 void start() override;
267 void stop() override;
269 int mFd{-1};
271 std::vector<std::byte> mMixData;
273 std::atomic<bool> mKillNow{true};
274 std::thread mThread;
277 OSSPlayback::~OSSPlayback()
279 if(mFd != -1)
280 ::close(mFd);
281 mFd = -1;
285 int OSSPlayback::mixerProc()
287 SetRTPriority();
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))
296 pollfd pollitem{};
297 pollitem.fd = mFd;
298 pollitem.events = POLLOUT;
300 if(int pret{poll(&pollitem, 1, 1000)}; pret < 0)
302 if(errno == EINTR || errno == EAGAIN)
303 continue;
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());
307 break;
309 else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */
311 WARN("poll timeout\n");
312 continue;
315 al::span write_buf{mMixData};
316 mDevice->renderSamples(write_buf.data(), static_cast<uint>(write_buf.size()/frame_size),
317 frame_step);
318 while(!write_buf.empty() && !mKillNow.load(std::memory_order_acquire))
320 ssize_t wrote{write(mFd, write_buf.data(), write_buf.size())};
321 if(wrote < 0)
323 if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
324 continue;
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());
328 break;
331 write_buf = write_buf.subspan(static_cast<size_t>(wrote));
335 return 0;
339 void OSSPlayback::open(std::string_view name)
341 const char *devname{DefaultPlayback.c_str()};
342 if(name.empty())
343 name = GetDefaultName();
344 else
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)};
360 if(fd == -1)
361 throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
362 std::generic_category().message(errno).c_str()};
364 if(mFd != -1)
365 ::close(mFd);
366 mFd = fd;
368 mDeviceName = name;
371 bool OSSPlayback::reset()
373 int ossFormat{};
374 switch(mDevice->FmtType)
376 case DevFmtByte:
377 ossFormat = AFMT_S8;
378 break;
379 case DevFmtUByte:
380 ossFormat = AFMT_U8;
381 break;
382 case DevFmtUShort:
383 case DevFmtInt:
384 case DevFmtUInt:
385 case DevFmtFloat:
386 mDevice->FmtType = DevFmtShort;
387 /* fall-through */
388 case DevFmtShort:
389 ossFormat = AFMT_S16_NE;
390 break;
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));
413 #undef CHECKERR
415 if(mDevice->channelsFromFmt() != numChannels)
417 ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice->FmtChans),
418 numChannels);
419 return false;
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),
427 ossFormat);
428 return false;
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());
439 return true;
442 void OSSPlayback::start()
444 try {
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())
457 return;
458 mThread.join();
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;
469 int recordProc();
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;
477 int mFd{-1};
479 RingBufferPtr mRing{nullptr};
481 std::atomic<bool> mKillNow{true};
482 std::thread mThread;
485 OSScapture::~OSScapture()
487 if(mFd != -1)
488 close(mFd);
489 mFd = -1;
493 int OSScapture::recordProc()
495 SetRTPriority();
496 althrd_setname(GetRecordThreadName());
498 const size_t frame_size{mDevice->frameSizeFromFmt()};
499 while(!mKillNow.load(std::memory_order_acquire))
501 pollfd pollitem{};
502 pollitem.fd = mFd;
503 pollitem.events = POLLIN;
505 if(int pret{poll(&pollitem, 1, 1000)}; pret < 0)
507 if(errno == EINTR || errno == EAGAIN)
508 continue;
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());
512 break;
514 else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */
516 WARN("poll timeout\n");
517 continue;
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)};
524 if(amt < 0)
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());
529 break;
531 mRing->writeAdvance(static_cast<size_t>(amt)/frame_size);
535 return 0;
539 void OSScapture::open(std::string_view name)
541 const char *devname{DefaultCapture.c_str()};
542 if(name.empty())
543 name = GetDefaultName();
544 else
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);
560 if(mFd == -1)
561 throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
562 std::generic_category().message(errno).c_str()};
564 int ossFormat{};
565 switch(mDevice->FmtType)
567 case DevFmtByte:
568 ossFormat = AFMT_S8;
569 break;
570 case DevFmtUByte:
571 ossFormat = AFMT_U8;
572 break;
573 case DevFmtShort:
574 ossFormat = AFMT_S16_NE;
575 break;
576 case DevFmtUShort:
577 case DevFmtInt:
578 case DevFmtUInt:
579 case DevFmtFloat:
580 throw al::backend_exception{al::backend_error::DeviceError,
581 "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
584 uint periods{4};
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));
602 #undef CHECKERR
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),
607 numChannels};
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),
614 ossFormat};
616 mRing = RingBuffer::Create(mDevice->BufferSize, frameSize, false);
618 mDeviceName = name;
621 void OSScapture::start()
623 try {
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())
636 return;
637 mThread.join();
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()); }
649 } // namespace
652 BackendFactory &OSSBackendFactory::getFactory()
654 static OSSBackendFactory factory{};
655 return 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);
665 return true;
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);
680 switch(type)
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);
687 break;
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);
694 break;
697 return outnames;
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}};
706 return nullptr;