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
23 #include "backends/oss.h"
27 #include <sys/ioctl.h>
49 #include "alnumeric.h"
50 #include "aloptional.h"
53 #include "ringbuffer.h"
57 #include <sys/soundcard.h>
60 * The OSS documentation talks about SOUND_MIXER_READ, but the header
61 * only contains MIXER_READ. Play safe. Same for WRITE.
63 #ifndef SOUND_MIXER_READ
64 #define SOUND_MIXER_READ MIXER_READ
66 #ifndef SOUND_MIXER_WRITE
67 #define SOUND_MIXER_WRITE MIXER_WRITE
70 #if defined(SOUND_VERSION) && (SOUND_VERSION < 0x040000)
71 #define ALC_OSS_COMPAT
73 #ifndef SNDCTL_AUDIOINFO
74 #define ALC_OSS_COMPAT
78 * FreeBSD strongly discourages the use of specific devices,
79 * such as those returned in oss_audioinfo.devnode
82 #define ALC_OSS_DEVNODE_TRUC
87 constexpr char DefaultName
[] = "OSS Default";
88 std::string DefaultPlayback
{"/dev/dsp"};
89 std::string DefaultCapture
{"/dev/dsp"};
93 std::string device_name
;
96 bool checkName(const al::vector
<DevMap
> &list
, const std::string
&name
)
98 return std::find_if(list
.cbegin(), list
.cend(),
99 [&name
](const DevMap
&entry
) -> bool
100 { return entry
.name
== name
; }
104 al::vector
<DevMap
> PlaybackDevices
;
105 al::vector
<DevMap
> CaptureDevices
;
108 #ifdef ALC_OSS_COMPAT
110 #define DSP_CAP_OUTPUT 0x00020000
111 #define DSP_CAP_INPUT 0x00010000
112 void ALCossListPopulate(al::vector
<DevMap
> *devlist
, int type
)
114 devlist
->emplace_back(DevMap
{DefaultName
, (type
==DSP_CAP_INPUT
) ? DefaultCapture
: DefaultPlayback
});
119 void ALCossListAppend(al::vector
<DevMap
> *list
, const char *handle
, size_t hlen
, const char *path
, size_t plen
)
121 #ifdef ALC_OSS_DEVNODE_TRUC
122 for(size_t i
{0};i
< plen
;i
++)
126 if(strncmp(path
+ i
, handle
+ hlen
+ i
- plen
, plen
- i
) == 0)
127 hlen
= hlen
+ i
- plen
;
132 if(handle
[0] == '\0')
138 std::string basename
{handle
, hlen
};
139 basename
.erase(std::find(basename
.begin(), basename
.end(), '\0'), basename
.end());
140 std::string devname
{path
, plen
};
141 devname
.erase(std::find(devname
.begin(), devname
.end(), '\0'), devname
.end());
143 auto iter
= std::find_if(list
->cbegin(), list
->cend(),
144 [&devname
](const DevMap
&entry
) -> bool
145 { return entry
.device_name
== devname
; }
147 if(iter
!= list
->cend())
151 std::string newname
{basename
};
152 while(checkName(PlaybackDevices
, newname
))
156 newname
+= std::to_string(++count
);
159 list
->emplace_back(DevMap
{std::move(newname
), std::move(devname
)});
160 const DevMap
&entry
= list
->back();
162 TRACE("Got device \"%s\", \"%s\"\n", entry
.name
.c_str(), entry
.device_name
.c_str());
165 void ALCossListPopulate(al::vector
<DevMap
> *devlist
, int type_flag
)
167 int fd
{open("/dev/mixer", O_RDONLY
)};
170 TRACE("Could not open /dev/mixer: %s\n", strerror(errno
));
175 if(ioctl(fd
, SNDCTL_SYSINFO
, &si
) == -1)
177 TRACE("SNDCTL_SYSINFO failed: %s\n", strerror(errno
));
181 for(int i
{0};i
< si
.numaudios
;i
++)
185 if(ioctl(fd
, SNDCTL_AUDIOINFO
, &ai
) == -1)
187 ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i
, strerror(errno
));
190 if(!(ai
.caps
&type_flag
) || ai
.devnode
[0] == '\0')
195 if(ai
.handle
[0] != '\0')
197 len
= strnlen(ai
.handle
, sizeof(ai
.handle
));
202 len
= strnlen(ai
.name
, sizeof(ai
.name
));
206 ALCossListAppend(devlist
, handle
, len
, ai
.devnode
,
207 strnlen(ai
.devnode
, sizeof(ai
.devnode
)));
215 const char *defdev
{((type_flag
==DSP_CAP_INPUT
) ? DefaultCapture
: DefaultPlayback
).c_str()};
216 auto iter
= std::find_if(devlist
->cbegin(), devlist
->cend(),
217 [defdev
](const DevMap
&entry
) -> bool
218 { return entry
.device_name
== defdev
; }
220 if(iter
== devlist
->cend())
221 devlist
->insert(devlist
->begin(), DevMap
{DefaultName
, defdev
});
224 DevMap entry
{std::move(*iter
)};
225 devlist
->erase(iter
);
226 devlist
->insert(devlist
->begin(), std::move(entry
));
228 devlist
->shrink_to_fit();
233 ALCuint
log2i(ALCuint x
)
245 struct OSSPlayback final
: public BackendBase
{
246 OSSPlayback(ALCdevice
*device
) noexcept
: BackendBase
{device
} { }
247 ~OSSPlayback() override
;
251 ALCenum
open(const ALCchar
*name
) override
;
252 bool reset() override
;
253 bool start() override
;
254 void stop() override
;
258 al::vector
<ALubyte
> mMixData
;
260 std::atomic
<bool> mKillNow
{true};
263 DEF_NEWDEL(OSSPlayback
)
266 OSSPlayback::~OSSPlayback()
274 int OSSPlayback::mixerProc()
277 althrd_setname(MIXER_THREAD_NAME
);
279 const ALuint frame_size
{mDevice
->frameSizeFromFmt()};
282 while(!mKillNow
.load(std::memory_order_acquire
) &&
283 mDevice
->Connected
.load(std::memory_order_acquire
))
287 pollitem
.events
= POLLOUT
;
290 int pret
{poll(&pollitem
, 1, 1000)};
294 if(errno
== EINTR
|| errno
== EAGAIN
)
296 ERR("poll failed: %s\n", strerror(errno
));
297 aluHandleDisconnect(mDevice
, "Failed waiting for playback buffer: %s", strerror(errno
));
302 WARN("poll timeout\n");
306 ALubyte
*write_ptr
{mMixData
.data()};
307 size_t to_write
{mMixData
.size()};
308 aluMixData(mDevice
, write_ptr
, static_cast<ALuint
>(to_write
/frame_size
));
309 while(to_write
> 0 && !mKillNow
.load(std::memory_order_acquire
))
311 ssize_t wrote
{write(mFd
, write_ptr
, to_write
)};
314 if(errno
== EAGAIN
|| errno
== EWOULDBLOCK
|| errno
== EINTR
)
316 ERR("write failed: %s\n", strerror(errno
));
317 aluHandleDisconnect(mDevice
, "Failed writing playback samples: %s",
322 to_write
-= static_cast<size_t>(wrote
);
332 ALCenum
OSSPlayback::open(const ALCchar
*name
)
334 const char *devname
{DefaultPlayback
.c_str()};
339 if(PlaybackDevices
.empty())
340 ALCossListPopulate(&PlaybackDevices
, DSP_CAP_OUTPUT
);
342 auto iter
= std::find_if(PlaybackDevices
.cbegin(), PlaybackDevices
.cend(),
343 [&name
](const DevMap
&entry
) -> bool
344 { return entry
.name
== name
; }
346 if(iter
== PlaybackDevices
.cend())
347 return ALC_INVALID_VALUE
;
348 devname
= iter
->device_name
.c_str();
351 mFd
= ::open(devname
, O_WRONLY
);
354 ERR("Could not open %s: %s\n", devname
, strerror(errno
));
355 return ALC_INVALID_VALUE
;
358 mDevice
->DeviceName
= name
;
362 bool OSSPlayback::reset()
365 switch(mDevice
->FmtType
)
377 mDevice
->FmtType
= DevFmtShort
;
380 ossFormat
= AFMT_S16_NE
;
384 ALuint periods
{mDevice
->BufferSize
/ mDevice
->UpdateSize
};
385 ALuint numChannels
{mDevice
->channelsFromFmt()};
386 ALuint ossSpeed
{mDevice
->Frequency
};
387 ALuint frameSize
{numChannels
* mDevice
->bytesFromFmt()};
388 /* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */
389 ALuint log2FragmentSize
{maxu(log2i(mDevice
->UpdateSize
*frameSize
), 4)};
390 ALuint numFragmentsLogSize
{(periods
<< 16) | log2FragmentSize
};
392 audio_buf_info info
{};
394 #define CHECKERR(func) if((func) < 0) { \
398 /* Don't fail if SETFRAGMENT fails. We can handle just about anything
399 * that's reported back via GETOSPACE */
400 ioctl(mFd
, SNDCTL_DSP_SETFRAGMENT
, &numFragmentsLogSize
);
401 CHECKERR(ioctl(mFd
, SNDCTL_DSP_SETFMT
, &ossFormat
));
402 CHECKERR(ioctl(mFd
, SNDCTL_DSP_CHANNELS
, &numChannels
));
403 CHECKERR(ioctl(mFd
, SNDCTL_DSP_SPEED
, &ossSpeed
));
404 CHECKERR(ioctl(mFd
, SNDCTL_DSP_GETOSPACE
, &info
));
408 ERR("%s failed: %s\n", err
, strerror(errno
));
413 if(mDevice
->channelsFromFmt() != numChannels
)
415 ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice
->FmtChans
),
420 if(!((ossFormat
== AFMT_S8
&& mDevice
->FmtType
== DevFmtByte
) ||
421 (ossFormat
== AFMT_U8
&& mDevice
->FmtType
== DevFmtUByte
) ||
422 (ossFormat
== AFMT_S16_NE
&& mDevice
->FmtType
== DevFmtShort
)))
424 ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(mDevice
->FmtType
),
429 mDevice
->Frequency
= ossSpeed
;
430 mDevice
->UpdateSize
= static_cast<ALuint
>(info
.fragsize
) / frameSize
;
431 mDevice
->BufferSize
= static_cast<ALuint
>(info
.fragments
) * mDevice
->UpdateSize
;
433 SetDefaultChannelOrder(mDevice
);
435 mMixData
.resize(mDevice
->UpdateSize
* mDevice
->frameSizeFromFmt());
440 bool OSSPlayback::start()
443 mKillNow
.store(false, std::memory_order_release
);
444 mThread
= std::thread
{std::mem_fn(&OSSPlayback::mixerProc
), this};
447 catch(std::exception
& e
) {
448 ERR("Could not create playback thread: %s\n", e
.what());
455 void OSSPlayback::stop()
457 if(mKillNow
.exchange(true, std::memory_order_acq_rel
) || !mThread
.joinable())
461 if(ioctl(mFd
, SNDCTL_DSP_RESET
) != 0)
462 ERR("Error resetting device: %s\n", strerror(errno
));
466 struct OSScapture final
: public BackendBase
{
467 OSScapture(ALCdevice
*device
) noexcept
: BackendBase
{device
} { }
468 ~OSScapture() override
;
472 ALCenum
open(const ALCchar
*name
) override
;
473 bool start() override
;
474 void stop() override
;
475 ALCenum
captureSamples(al::byte
*buffer
, ALCuint samples
) override
;
476 ALCuint
availableSamples() override
;
480 RingBufferPtr mRing
{nullptr};
482 std::atomic
<bool> mKillNow
{true};
485 DEF_NEWDEL(OSScapture
)
488 OSScapture::~OSScapture()
496 int OSScapture::recordProc()
499 althrd_setname(RECORD_THREAD_NAME
);
501 const ALuint frame_size
{mDevice
->frameSizeFromFmt()};
502 while(!mKillNow
.load(std::memory_order_acquire
))
506 pollitem
.events
= POLLIN
;
508 int sret
{poll(&pollitem
, 1, 1000)};
511 if(errno
== EINTR
|| errno
== EAGAIN
)
513 ERR("poll failed: %s\n", strerror(errno
));
514 aluHandleDisconnect(mDevice
, "Failed to check capture samples: %s", strerror(errno
));
519 WARN("poll timeout\n");
523 auto vec
= mRing
->getWriteVector();
524 if(vec
.first
.len
> 0)
526 ssize_t amt
{read(mFd
, vec
.first
.buf
, vec
.first
.len
*frame_size
)};
529 ERR("read failed: %s\n", strerror(errno
));
530 aluHandleDisconnect(mDevice
, "Failed reading capture samples: %s",
534 mRing
->writeAdvance(static_cast<ALuint
>(amt
)/frame_size
);
542 ALCenum
OSScapture::open(const ALCchar
*name
)
544 const char *devname
{DefaultCapture
.c_str()};
549 if(CaptureDevices
.empty())
550 ALCossListPopulate(&CaptureDevices
, DSP_CAP_INPUT
);
552 auto iter
= std::find_if(CaptureDevices
.cbegin(), CaptureDevices
.cend(),
553 [&name
](const DevMap
&entry
) -> bool
554 { return entry
.name
== name
; }
556 if(iter
== CaptureDevices
.cend())
557 return ALC_INVALID_VALUE
;
558 devname
= iter
->device_name
.c_str();
561 mFd
= ::open(devname
, O_RDONLY
);
564 ERR("Could not open %s: %s\n", devname
, strerror(errno
));
565 return ALC_INVALID_VALUE
;
569 switch(mDevice
->FmtType
)
578 ossFormat
= AFMT_S16_NE
;
584 ERR("%s capture samples not supported\n", DevFmtTypeString(mDevice
->FmtType
));
585 return ALC_INVALID_VALUE
;
589 ALuint numChannels
{mDevice
->channelsFromFmt()};
590 ALuint frameSize
{numChannels
* mDevice
->bytesFromFmt()};
591 ALuint ossSpeed
{mDevice
->Frequency
};
592 /* according to the OSS spec, 16 bytes are the minimum */
593 ALuint log2FragmentSize
{maxu(log2i(mDevice
->BufferSize
* frameSize
/ periods
), 4)};
594 ALuint numFragmentsLogSize
{(periods
<< 16) | log2FragmentSize
};
596 audio_buf_info info
{};
598 #define CHECKERR(func) if((func) < 0) { \
602 CHECKERR(ioctl(mFd
, SNDCTL_DSP_SETFRAGMENT
, &numFragmentsLogSize
));
603 CHECKERR(ioctl(mFd
, SNDCTL_DSP_SETFMT
, &ossFormat
));
604 CHECKERR(ioctl(mFd
, SNDCTL_DSP_CHANNELS
, &numChannels
));
605 CHECKERR(ioctl(mFd
, SNDCTL_DSP_SPEED
, &ossSpeed
));
606 CHECKERR(ioctl(mFd
, SNDCTL_DSP_GETISPACE
, &info
));
610 ERR("%s failed: %s\n", err
, strerror(errno
));
613 return ALC_INVALID_VALUE
;
617 if(mDevice
->channelsFromFmt() != numChannels
)
619 ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice
->FmtChans
),
623 return ALC_INVALID_VALUE
;
626 if(!((ossFormat
== AFMT_S8
&& mDevice
->FmtType
== DevFmtByte
) ||
627 (ossFormat
== AFMT_U8
&& mDevice
->FmtType
== DevFmtUByte
) ||
628 (ossFormat
== AFMT_S16_NE
&& mDevice
->FmtType
== DevFmtShort
)))
630 ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(mDevice
->FmtType
), ossFormat
);
633 return ALC_INVALID_VALUE
;
636 mRing
= CreateRingBuffer(mDevice
->BufferSize
, frameSize
, false);
639 ERR("Ring buffer create failed\n");
642 return ALC_OUT_OF_MEMORY
;
645 mDevice
->DeviceName
= name
;
649 bool OSScapture::start()
652 mKillNow
.store(false, std::memory_order_release
);
653 mThread
= std::thread
{std::mem_fn(&OSScapture::recordProc
), this};
656 catch(std::exception
& e
) {
657 ERR("Could not create record thread: %s\n", e
.what());
664 void OSScapture::stop()
666 if(mKillNow
.exchange(true, std::memory_order_acq_rel
) || !mThread
.joinable())
670 if(ioctl(mFd
, SNDCTL_DSP_RESET
) != 0)
671 ERR("Error resetting device: %s\n", strerror(errno
));
674 ALCenum
OSScapture::captureSamples(al::byte
*buffer
, ALCuint samples
)
676 mRing
->read(buffer
, samples
);
680 ALCuint
OSScapture::availableSamples()
681 { return static_cast<ALCuint
>(mRing
->readSpace()); }
686 BackendFactory
&OSSBackendFactory::getFactory()
688 static OSSBackendFactory factory
{};
692 bool OSSBackendFactory::init()
694 if(auto devopt
= ConfigValueStr(nullptr, "oss", "device"))
695 DefaultPlayback
= std::move(*devopt
);
696 if(auto capopt
= ConfigValueStr(nullptr, "oss", "capture"))
697 DefaultCapture
= std::move(*capopt
);
702 bool OSSBackendFactory::querySupport(BackendType type
)
703 { return (type
== BackendType::Playback
|| type
== BackendType::Capture
); }
705 void OSSBackendFactory::probe(DevProbe type
, std::string
*outnames
)
707 auto add_device
= [outnames
](const DevMap
&entry
) -> void
711 if(stat(entry
.device_name
.c_str(), &buf
) == 0)
714 /* Includes null char. */
715 outnames
->append(entry
.name
.c_str(), entry
.name
.length()+1);
721 case DevProbe::Playback
:
722 PlaybackDevices
.clear();
723 ALCossListPopulate(&PlaybackDevices
, DSP_CAP_OUTPUT
);
724 std::for_each(PlaybackDevices
.cbegin(), PlaybackDevices
.cend(), add_device
);
727 case DevProbe::Capture
:
728 CaptureDevices
.clear();
729 ALCossListPopulate(&CaptureDevices
, DSP_CAP_INPUT
);
730 std::for_each(CaptureDevices
.cbegin(), CaptureDevices
.cend(), add_device
);
735 BackendPtr
OSSBackendFactory::createBackend(ALCdevice
*device
, BackendType type
)
737 if(type
== BackendType::Playback
)
738 return BackendPtr
{new OSSPlayback
{device
}};
739 if(type
== BackendType::Capture
)
740 return BackendPtr
{new OSScapture
{device
}};