Limit convolution processing to the output ambisonic order
[openal-soft.git] / alc / backends / oss.cpp
blob0f00115e3a56e0fc93fdf39c08f181d9f0031422
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 "backends/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 <cstdio>
35 #include <cstring>
36 #include <exception>
37 #include <functional>
38 #include <memory>
39 #include <new>
40 #include <string>
41 #include <thread>
42 #include <utility>
44 #include "AL/al.h"
46 #include "alcmain.h"
47 #include "alconfig.h"
48 #include "alexcpt.h"
49 #include "almalloc.h"
50 #include "alnumeric.h"
51 #include "aloptional.h"
52 #include "alu.h"
53 #include "logging.h"
54 #include "ringbuffer.h"
55 #include "threads.h"
56 #include "vector.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
66 #endif
67 #ifndef SOUND_MIXER_WRITE
68 #define SOUND_MIXER_WRITE MIXER_WRITE
69 #endif
71 #if defined(SOUND_VERSION) && (SOUND_VERSION < 0x040000)
72 #define ALC_OSS_COMPAT
73 #endif
74 #ifndef SNDCTL_AUDIOINFO
75 #define ALC_OSS_COMPAT
76 #endif
79 * FreeBSD strongly discourages the use of specific devices,
80 * such as those returned in oss_audioinfo.devnode
82 #ifdef __FreeBSD__
83 #define ALC_OSS_DEVNODE_TRUC
84 #endif
86 namespace {
88 constexpr char DefaultName[] = "OSS Default";
89 std::string DefaultPlayback{"/dev/dsp"};
90 std::string DefaultCapture{"/dev/dsp"};
92 struct DevMap {
93 std::string name;
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});
110 #else
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);
125 #endif
126 if(handle.empty())
127 handle = path;
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())
135 return;
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();
142 int count{1};
143 std::string newname{basename};
144 while(checkName(newname))
146 newname = basename;
147 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)};
160 if(fd < 0)
162 TRACE("Could not open /dev/mixer: %s\n", strerror(errno));
163 goto done;
166 oss_sysinfo si;
167 if(ioctl(fd, SNDCTL_SYSINFO, &si) == -1)
169 TRACE("SNDCTL_SYSINFO failed: %s\n", strerror(errno));
170 goto done;
173 for(int i{0};i < si.numaudios;i++)
175 oss_audioinfo ai;
176 ai.dev = i;
177 if(ioctl(fd, SNDCTL_AUDIOINFO, &ai) == -1)
179 ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i, strerror(errno));
180 continue;
182 if(!(ai.caps&type_flag) || ai.devnode[0] == '\0')
183 continue;
185 al::span<const char> handle;
186 if(ai.handle[0] != '\0')
187 handle = {ai.handle, strnlen(ai.handle, sizeof(ai.handle))};
188 else
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);
195 done:
196 if(fd >= 0)
197 close(fd);
198 fd = -1;
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});
207 else
209 DevMap entry{std::move(*iter)};
210 devlist.erase(iter);
211 devlist.insert(devlist.begin(), std::move(entry));
213 devlist.shrink_to_fit();
216 #endif
218 ALCuint log2i(ALCuint x)
220 ALCuint y{0};
221 while(x > 1)
223 x >>= 1;
224 y++;
226 return y;
230 struct OSSPlayback final : public BackendBase {
231 OSSPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
232 ~OSSPlayback() override;
234 int mixerProc();
236 void open(const ALCchar *name) override;
237 bool reset() override;
238 void start() override;
239 void stop() override;
241 int mFd{-1};
243 al::vector<ALubyte> mMixData;
245 std::atomic<bool> mKillNow{true};
246 std::thread mThread;
248 DEF_NEWDEL(OSSPlayback)
251 OSSPlayback::~OSSPlayback()
253 if(mFd != -1)
254 close(mFd);
255 mFd = -1;
259 int OSSPlayback::mixerProc()
261 SetRTPriority();
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))
270 pollfd pollitem{};
271 pollitem.fd = mFd;
272 pollitem.events = POLLOUT;
274 int pret{poll(&pollitem, 1, 1000)};
275 if(pret < 0)
277 if(errno == EINTR || errno == EAGAIN)
278 continue;
279 ERR("poll failed: %s\n", strerror(errno));
280 mDevice->handleDisconnect("Failed waiting for playback buffer: %s", strerror(errno));
281 break;
283 else if(pret == 0)
285 WARN("poll timeout\n");
286 continue;
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)};
295 if(wrote < 0)
297 if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
298 continue;
299 ERR("write failed: %s\n", strerror(errno));
300 mDevice->handleDisconnect("Failed writing playback samples: %s", strerror(errno));
301 break;
304 to_write -= static_cast<size_t>(wrote);
305 write_ptr += wrote;
309 return 0;
313 void OSSPlayback::open(const ALCchar *name)
315 const char *devname{DefaultPlayback.c_str()};
316 if(!name)
317 name = DefaultName;
318 else
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);
333 if(mFd == -1)
334 throw al::backend_exception{ALC_INVALID_VALUE, "Could not open %s: %s", devname,
335 strerror(errno)};
337 mDevice->DeviceName = name;
340 bool OSSPlayback::reset()
342 int ossFormat{};
343 switch(mDevice->FmtType)
345 case DevFmtByte:
346 ossFormat = AFMT_S8;
347 break;
348 case DevFmtUByte:
349 ossFormat = AFMT_U8;
350 break;
351 case DevFmtUShort:
352 case DevFmtInt:
353 case DevFmtUInt:
354 case DevFmtFloat:
355 mDevice->FmtType = DevFmtShort;
356 /* fall-through */
357 case DevFmtShort:
358 ossFormat = AFMT_S16_NE;
359 break;
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{};
371 const char *err;
372 #define CHECKERR(func) if((func) < 0) { \
373 err = #func; \
374 goto err; \
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));
383 if(0)
385 err:
386 ERR("%s failed: %s\n", err, strerror(errno));
387 return false;
389 #undef CHECKERR
391 if(mDevice->channelsFromFmt() != numChannels)
393 ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice->FmtChans),
394 numChannels);
395 return false;
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),
403 ossFormat);
404 return false;
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());
415 return true;
418 void OSSPlayback::start()
420 try {
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",
426 e.what()};
430 void OSSPlayback::stop()
432 if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
433 return;
434 mThread.join();
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;
445 int recordProc();
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;
453 int mFd{-1};
455 RingBufferPtr mRing{nullptr};
457 std::atomic<bool> mKillNow{true};
458 std::thread mThread;
460 DEF_NEWDEL(OSScapture)
463 OSScapture::~OSScapture()
465 if(mFd != -1)
466 close(mFd);
467 mFd = -1;
471 int OSScapture::recordProc()
473 SetRTPriority();
474 althrd_setname(RECORD_THREAD_NAME);
476 const ALuint frame_size{mDevice->frameSizeFromFmt()};
477 while(!mKillNow.load(std::memory_order_acquire))
479 pollfd pollitem{};
480 pollitem.fd = mFd;
481 pollitem.events = POLLIN;
483 int sret{poll(&pollitem, 1, 1000)};
484 if(sret < 0)
486 if(errno == EINTR || errno == EAGAIN)
487 continue;
488 ERR("poll failed: %s\n", strerror(errno));
489 mDevice->handleDisconnect("Failed to check capture samples: %s", strerror(errno));
490 break;
492 else if(sret == 0)
494 WARN("poll timeout\n");
495 continue;
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)};
502 if(amt < 0)
504 ERR("read failed: %s\n", strerror(errno));
505 mDevice->handleDisconnect("Failed reading capture samples: %s", strerror(errno));
506 break;
508 mRing->writeAdvance(static_cast<ALuint>(amt)/frame_size);
512 return 0;
516 void OSScapture::open(const ALCchar *name)
518 const char *devname{DefaultCapture.c_str()};
519 if(!name)
520 name = DefaultName;
521 else
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);
536 if(mFd == -1)
537 throw al::backend_exception{ALC_INVALID_VALUE, "Could not open %s: %s", devname,
538 strerror(errno)};
540 int ossFormat{};
541 switch(mDevice->FmtType)
543 case DevFmtByte:
544 ossFormat = AFMT_S8;
545 break;
546 case DevFmtUByte:
547 ossFormat = AFMT_U8;
548 break;
549 case DevFmtShort:
550 ossFormat = AFMT_S16_NE;
551 break;
552 case DevFmtUShort:
553 case DevFmtInt:
554 case DevFmtUInt:
555 case DevFmtFloat:
556 throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported",
557 DevFmtTypeString(mDevice->FmtType)};
560 ALuint periods{4};
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));
577 #undef CHECKERR
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),
582 numChannels};
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),
589 ossFormat};
591 mRing = RingBuffer::Create(mDevice->BufferSize, frameSize, false);
593 mDevice->DeviceName = name;
596 void OSScapture::start()
598 try {
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",
604 e.what()};
608 void OSScapture::stop()
610 if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
611 return;
612 mThread.join();
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);
621 return ALC_NO_ERROR;
624 ALCuint OSScapture::availableSamples()
625 { return static_cast<ALCuint>(mRing->readSpace()); }
627 } // namespace
630 BackendFactory &OSSBackendFactory::getFactory()
632 static OSSBackendFactory factory{};
633 return 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);
643 return true;
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
655 struct stat buf;
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);
663 switch(type)
665 case BackendType::Playback:
666 PlaybackDevices.clear();
667 ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT);
668 std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
669 break;
671 case BackendType::Capture:
672 CaptureDevices.clear();
673 ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT);
674 std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
675 break;
678 return outnames;
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}};
687 return nullptr;