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.
8 *
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.
13 *
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
19 */
20
21 #include "config.h"
22
23 #include "backends/oss.h"
24
25 #include <fcntl.h>
26 #include <poll.h>
27 #include <sys/ioctl.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30
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>
43
44 #include "alcmain.h"
45 #include "alconfig.h"
casecompare(char c1,char c2)46 #include "albyte.h"
47 #include "almalloc.h"
48 #include "alnumeric.h"
49 #include "aloptional.h"
50 #include "alu.h"
51 #include "core/logging.h"
52 #include "ringbuffer.h"
53 #include "threads.h"
54 #include "vector.h"
55
56 #include <sys/soundcard.h>
57
58 /*
59 * The OSS documentation talks about SOUND_MIXER_READ, but the header
60 * only contains MIXER_READ. Play safe. Same for WRITE.
61 */
62 #ifndef SOUND_MIXER_READ
63 #define SOUND_MIXER_READ MIXER_READ
64 #endif
65 #ifndef SOUND_MIXER_WRITE
66 #define SOUND_MIXER_WRITE MIXER_WRITE
67 #endif
68
69 #if defined(SOUND_VERSION) && (SOUND_VERSION < 0x040000)
70 #define ALC_OSS_COMPAT
71 #endif
72 #ifndef SNDCTL_AUDIOINFO
73 #define ALC_OSS_COMPAT
74 #endif
75
76 /*
77 * FreeBSD strongly discourages the use of specific devices,
78 * such as those returned in oss_audioinfo.devnode
79 */
80 #ifdef __FreeBSD__
81 #define ALC_OSS_DEVNODE_TRUC
82 #endif
83
84 namespace {
85
86 constexpr char DefaultName[] = "OSS Default";
87 std::string DefaultPlayback{"/dev/dsp"};
88 std::string DefaultCapture{"/dev/dsp"};
89
90 struct DevMap {
91 std::string name;
92 std::string device_name;
93 };
94
95 al::vector<DevMap> PlaybackDevices;
96 al::vector<DevMap> CaptureDevices;
97
98
99 #ifdef ALC_OSS_COMPAT
100
101 #define DSP_CAP_OUTPUT 0x00020000
102 #define DSP_CAP_INPUT 0x00010000
103 void ALCossListPopulate(al::vector<DevMap> &devlist, int type)
104 {
105 devlist.emplace_back(DevMap{DefaultName, (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback});
106 }
107
108 #else
109
110 void ALCossListAppend(al::vector<DevMap> &list, al::span<const char> handle, al::span<const char> path)
111 {
112 #ifdef ALC_OSS_DEVNODE_TRUC
113 for(size_t i{0};i < path.size();++i)
114 {
115 if(path[i] == '.' && handle.size() + i >= path.size())
116 {
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);
121 }
122 }
123 #endif
124 if(handle.empty())
125 handle = path;
126
127 std::string basename{handle.data(), handle.size()};
128 std::string devname{path.data(), path.size()};
129
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())
133 return;
134
135 auto checkName = [&list](const std::string &name) -> bool
136 {
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();
139 };
140 int count{1};
141 std::string newname{basename};
142 while(checkName(newname))
143 {
144 newname = basename;
145 newname += " #";
146 newname += std::to_string(++count);
147 }
148
149 list.emplace_back(DevMap{std::move(newname), std::move(devname)});
150 const DevMap &entry = list.back();
151
152 TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
153 }
154
155 void ALCossListPopulate(al::vector<DevMap> &devlist, int type_flag)
156 {
157 int fd{open("/dev/mixer", O_RDONLY)};
158 if(fd < 0)
159 {
160 TRACE("Could not open /dev/mixer: %s\n", strerror(errno));
161 goto done;
162 }
163
164 oss_sysinfo si;
165 if(ioctl(fd, SNDCTL_SYSINFO, &si) == -1)
166 {
167 TRACE("SNDCTL_SYSINFO failed: %s\n", strerror(errno));
168 goto done;
169 }
170
171 for(int i{0};i < si.numaudios;i++)
172 {
173 oss_audioinfo ai;
174 ai.dev = i;
175 if(ioctl(fd, SNDCTL_AUDIOINFO, &ai) == -1)
176 {
177 ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i, strerror(errno));
178 continue;
179 }
180 if(!(ai.caps&type_flag) || ai.devnode[0] == '\0')
181 continue;
182
183 al::span<const char> handle;
184 if(ai.handle[0] != '\0')
185 handle = {ai.handle, strnlen(ai.handle, sizeof(ai.handle))};
186 else
187 handle = {ai.name, strnlen(ai.name, sizeof(ai.name))};
188 al::span<const char> devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))};
189
190 ALCossListAppend(devlist, handle, devnode);
191 }
192
193 done:
194 if(fd >= 0)
195 close(fd);
196 fd = -1;
197
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; }
202 );
203 if(iter == devlist.cend())
204 devlist.insert(devlist.begin(), DevMap{DefaultName, defdev});
205 else
206 {
207 DevMap entry{std::move(*iter)};
208 devlist.erase(iter);
209 devlist.insert(devlist.begin(), std::move(entry));
210 }
211 devlist.shrink_to_fit();
212 }
213
214 #endif
215
216 uint log2i(uint x)
217 {
218 uint y{0};
219 while(x > 1)
220 {
221 x >>= 1;
222 y++;
223 }
224 return y;
225 }
226
227
228 struct OSSPlayback final : public BackendBase {
229 OSSPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
230 ~OSSPlayback() override;
231
232 int mixerProc();
233
234 void open(const char *name) override;
235 bool reset() override;
236 void start() override;
237 void stop() override;
238
239 int mFd{-1};
240
241 al::vector<al::byte> mMixData;
242
243 std::atomic<bool> mKillNow{true};
244 std::thread mThread;
245
246 DEF_NEWDEL(OSSPlayback)
247 };
248
249 OSSPlayback::~OSSPlayback()
250 {
251 if(mFd != -1)
252 close(mFd);
253 mFd = -1;
254 }
255
256
257 int OSSPlayback::mixerProc()
258 {
259 SetRTPriority();
260 althrd_setname(MIXER_THREAD_NAME);
261
262 const size_t frame_step{mDevice->channelsFromFmt()};
263 const size_t frame_size{mDevice->frameSizeFromFmt()};
264
265 while(!mKillNow.load(std::memory_order_acquire)
266 && mDevice->Connected.load(std::memory_order_acquire))
267 {
268 pollfd pollitem{};
269 pollitem.fd = mFd;
270 pollitem.events = POLLOUT;
271
272 int pret{poll(&pollitem, 1, 1000)};
273 if(pret < 0)
274 {
275 if(errno == EINTR || errno == EAGAIN)
276 continue;
277 ERR("poll failed: %s\n", strerror(errno));
278 mDevice->handleDisconnect("Failed waiting for playback buffer: %s", strerror(errno));
279 break;
280 }
281 else if(pret == 0)
282 {
283 WARN("poll timeout\n");
284 continue;
285 }
286
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))
291 {
292 ssize_t wrote{write(mFd, write_ptr, to_write)};
293 if(wrote < 0)
294 {
295 if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
296 continue;
297 ERR("write failed: %s\n", strerror(errno));
298 mDevice->handleDisconnect("Failed writing playback samples: %s", strerror(errno));
299 break;
300 }
301
302 to_write -= static_cast<size_t>(wrote);
303 write_ptr += wrote;
304 }
305 }
306
307 return 0;
308 }
309
310
311 void OSSPlayback::open(const char *name)
312 {
313 const char *devname{DefaultPlayback.c_str()};
314 if(!name)
315 name = DefaultName;
316 else
317 {
318 if(PlaybackDevices.empty())
319 ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT);
320
321 auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
322 [&name](const DevMap &entry) -> bool
323 { return entry.name == name; }
324 );
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();
329 }
330
331 mFd = ::open(devname, O_WRONLY);
332 if(mFd == -1)
333 throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
334 strerror(errno)};
335
336 mDevice->DeviceName = name;
337 }
338
339 bool OSSPlayback::reset()
340 {
341 int ossFormat{};
342 switch(mDevice->FmtType)
343 {
344 case DevFmtByte:
345 ossFormat = AFMT_S8;
346 break;
347 case DevFmtUByte:
348 ossFormat = AFMT_U8;
349 break;
350 case DevFmtUShort:
351 case DevFmtInt:
352 case DevFmtUInt:
353 case DevFmtFloat:
354 mDevice->FmtType = DevFmtShort;
355 /* fall-through */
356 case DevFmtShort:
357 ossFormat = AFMT_S16_NE;
358 break;
359 }
360
361 uint periods{mDevice->BufferSize / mDevice->UpdateSize};
362 uint numChannels{mDevice->channelsFromFmt()};
363 uint ossSpeed{mDevice->Frequency};
364 uint frameSize{numChannels * mDevice->bytesFromFmt()};
365 /* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */
366 uint log2FragmentSize{maxu(log2i(mDevice->UpdateSize*frameSize), 4)};
367 uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
368
369 audio_buf_info info{};
370 const char *err;
371 #define CHECKERR(func) if((func) < 0) { \
372 err = #func; \
373 goto err; \
374 }
375 /* Don't fail if SETFRAGMENT fails. We can handle just about anything
376 * that's reported back via GETOSPACE */
377 ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize);
378 CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
379 CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels));
380 CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed));
381 CHECKERR(ioctl(mFd, SNDCTL_DSP_GETOSPACE, &info));
382 if(0)
383 {
384 err:
385 ERR("%s failed: %s\n", err, strerror(errno));
386 return false;
387 }
388 #undef CHECKERR
389
390 if(mDevice->channelsFromFmt() != numChannels)
391 {
392 ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice->FmtChans),
393 numChannels);
394 return false;
395 }
396
397 if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte) ||
398 (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) ||
399 (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort)))
400 {
401 ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(mDevice->FmtType),
402 ossFormat);
403 return false;
404 }
405
406 mDevice->Frequency = ossSpeed;
407 mDevice->UpdateSize = static_cast<uint>(info.fragsize) / frameSize;
408 mDevice->BufferSize = static_cast<uint>(info.fragments) * mDevice->UpdateSize;
409
410 setDefaultChannelOrder();
411
412 mMixData.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
413
414 return true;
415 }
416
417 void OSSPlayback::start()
418 {
419 try {
420 mKillNow.store(false, std::memory_order_release);
421 mThread = std::thread{std::mem_fn(&OSSPlayback::mixerProc), this};
422 }
423 catch(std::exception& e) {
424 throw al::backend_exception{al::backend_error::DeviceError,
425 "Failed to start mixing thread: %s", e.what()};
426 }
427 }
428
429 void OSSPlayback::stop()
430 {
431 if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
432 return;
433 mThread.join();
434
435 if(ioctl(mFd, SNDCTL_DSP_RESET) != 0)
436 ERR("Error resetting device: %s\n", strerror(errno));
437 }
438
439
440 struct OSScapture final : public BackendBase {
441 OSScapture(ALCdevice *device) noexcept : BackendBase{device} { }
442 ~OSScapture() override;
443
444 int recordProc();
445
446 void open(const char *name) override;
447 void start() override;
448 void stop() override;
449 void captureSamples(al::byte *buffer, uint samples) override;
450 uint availableSamples() override;
451
452 int mFd{-1};
453
454 RingBufferPtr mRing{nullptr};
455
456 std::atomic<bool> mKillNow{true};
457 std::thread mThread;
458
459 DEF_NEWDEL(OSScapture)
460 };
461
462 OSScapture::~OSScapture()
463 {
464 if(mFd != -1)
465 close(mFd);
466 mFd = -1;
467 }
468
469
470 int OSScapture::recordProc()
471 {
472 SetRTPriority();
473 althrd_setname(RECORD_THREAD_NAME);
474
475 const size_t frame_size{mDevice->frameSizeFromFmt()};
476 while(!mKillNow.load(std::memory_order_acquire))
477 {
478 pollfd pollitem{};
479 pollitem.fd = mFd;
480 pollitem.events = POLLIN;
481
482 int sret{poll(&pollitem, 1, 1000)};
483 if(sret < 0)
484 {
485 if(errno == EINTR || errno == EAGAIN)
486 continue;
487 ERR("poll failed: %s\n", strerror(errno));
488 mDevice->handleDisconnect("Failed to check capture samples: %s", strerror(errno));
489 break;
490 }
491 else if(sret == 0)
492 {
493 WARN("poll timeout\n");
494 continue;
495 }
496
497 auto vec = mRing->getWriteVector();
498 if(vec.first.len > 0)
499 {
500 ssize_t amt{read(mFd, vec.first.buf, vec.first.len*frame_size)};
501 if(amt < 0)
502 {
503 ERR("read failed: %s\n", strerror(errno));
504 mDevice->handleDisconnect("Failed reading capture samples: %s", strerror(errno));
505 break;
506 }
507 mRing->writeAdvance(static_cast<size_t>(amt)/frame_size);
508 }
509 }
510
511 return 0;
512 }
513
514
515 void OSScapture::open(const char *name)
516 {
517 const char *devname{DefaultCapture.c_str()};
518 if(!name)
519 name = DefaultName;
520 else
521 {
522 if(CaptureDevices.empty())
523 ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT);
524
525 auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
526 [&name](const DevMap &entry) -> bool
527 { return entry.name == name; }
528 );
529 if(iter == CaptureDevices.cend())
530 throw al::backend_exception{al::backend_error::NoDevice,
531 "Device name \"%s\" not found", name};
532 devname = iter->device_name.c_str();
533 }
534
535 mFd = ::open(devname, O_RDONLY);
536 if(mFd == -1)
537 throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
538 strerror(errno)};
539
540 int ossFormat{};
541 switch(mDevice->FmtType)
542 {
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{al::backend_error::DeviceError,
557 "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
558 }
559
560 uint periods{4};
561 uint numChannels{mDevice->channelsFromFmt()};
562 uint frameSize{numChannels * mDevice->bytesFromFmt()};
563 uint ossSpeed{mDevice->Frequency};
564 /* according to the OSS spec, 16 bytes are the minimum */
565 uint log2FragmentSize{maxu(log2i(mDevice->BufferSize * frameSize / periods), 4)};
566 uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
567
568 audio_buf_info info{};
569 #define CHECKERR(func) if((func) < 0) { \
570 throw al::backend_exception{al::backend_error::DeviceError, #func " failed: %s", \
571 strerror(errno)}; \
572 }
573 CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize));
574 CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
575 CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels));
576 CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed));
577 CHECKERR(ioctl(mFd, SNDCTL_DSP_GETISPACE, &info));
578 #undef CHECKERR
579
580 if(mDevice->channelsFromFmt() != numChannels)
581 throw al::backend_exception{al::backend_error::DeviceError,
582 "Failed to set %s, got %d channels instead", DevFmtChannelsString(mDevice->FmtChans),
583 numChannels};
584
585 if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte)
586 || (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte)
587 || (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort)))
588 throw al::backend_exception{al::backend_error::DeviceError,
589 "Failed to set %s samples, got OSS format %#x", DevFmtTypeString(mDevice->FmtType),
590 ossFormat};
591
592 mRing = RingBuffer::Create(mDevice->BufferSize, frameSize, false);
593
594 mDevice->DeviceName = name;
595 }
596
597 void OSScapture::start()
598 {
599 try {
600 mKillNow.store(false, std::memory_order_release);
601 mThread = std::thread{std::mem_fn(&OSScapture::recordProc), this};
602 }
603 catch(std::exception& e) {
604 throw al::backend_exception{al::backend_error::DeviceError,
605 "Failed to start recording thread: %s", e.what()};
606 }
607 }
608
609 void OSScapture::stop()
610 {
611 if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
612 return;
613 mThread.join();
614
615 if(ioctl(mFd, SNDCTL_DSP_RESET) != 0)
616 ERR("Error resetting device: %s\n", strerror(errno));
617 }
618
619 void OSScapture::captureSamples(al::byte *buffer, uint samples)
620 { mRing->read(buffer, samples); }
621
622 uint OSScapture::availableSamples()
623 { return static_cast<uint>(mRing->readSpace()); }
624
625 } // namespace
626
627
628 BackendFactory &OSSBackendFactory::getFactory()
629 {
630 static OSSBackendFactory factory{};
631 return factory;
632 }
633
634 bool OSSBackendFactory::init()
635 {
636 if(auto devopt = ConfigValueStr(nullptr, "oss", "device"))
637 DefaultPlayback = std::move(*devopt);
638 if(auto capopt = ConfigValueStr(nullptr, "oss", "capture"))
639 DefaultCapture = std::move(*capopt);
640
641 return true;
642 }
643
644 bool OSSBackendFactory::querySupport(BackendType type)
645 { return (type == BackendType::Playback || type == BackendType::Capture); }
646
647 std::string OSSBackendFactory::probe(BackendType type)
648 {
649 std::string outnames;
650
651 auto add_device = [&outnames](const DevMap &entry) -> void
652 {
653 struct stat buf;
654 if(stat(entry.device_name.c_str(), &buf) == 0)
655 {
656 /* Includes null char. */
657 outnames.append(entry.name.c_str(), entry.name.length()+1);
658 }
659 };
660
661 switch(type)
662 {
663 case BackendType::Playback:
664 PlaybackDevices.clear();
665 ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT);
666 std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
667 break;
668
669 case BackendType::Capture:
670 CaptureDevices.clear();
671 ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT);
672 std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
673 break;
674 }
675
676 return outnames;
677 }
678
679 BackendPtr OSSBackendFactory::createBackend(ALCdevice *device, BackendType type)
680 {
681 if(type == BackendType::Playback)
682 return BackendPtr{new OSSPlayback{device}};
683 if(type == BackendType::Capture)
684 return BackendPtr{new OSScapture{device}};
685 return nullptr;
686 }
687