update openal

This commit is contained in:
AzaezelX 2024-06-30 14:35:57 -05:00
parent 62f3b93ff9
commit 6721a6b021
287 changed files with 33851 additions and 27325 deletions

View file

@ -31,29 +31,33 @@
#include <exception>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <thread>
#include <utility>
#include <vector>
#include "albyte.h"
#include "albit.h"
#include "alc/alconfig.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "aloptional.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "dynload.h"
#include "ringbuffer.h"
#include "threads.h"
#include "vector.h"
#include <alsa/asoundlib.h>
namespace {
constexpr char alsaDevice[] = "ALSA Default";
using namespace std::string_view_literals;
[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "ALSA Default"sv; }
#ifdef HAVE_DYNLOAD
@ -248,59 +252,97 @@ struct DevMap {
{ }
};
al::vector<DevMap> PlaybackDevices;
al::vector<DevMap> CaptureDevices;
std::vector<DevMap> PlaybackDevices;
std::vector<DevMap> CaptureDevices;
const char *prefix_name(snd_pcm_stream_t stream)
std::string_view prefix_name(snd_pcm_stream_t stream) noexcept
{
assert(stream == SND_PCM_STREAM_PLAYBACK || stream == SND_PCM_STREAM_CAPTURE);
return (stream==SND_PCM_STREAM_PLAYBACK) ? "device-prefix" : "capture-prefix";
if(stream == SND_PCM_STREAM_PLAYBACK)
return "device-prefix"sv;
return "capture-prefix"sv;
}
al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
struct SndCtlCardInfo {
snd_ctl_card_info_t *mInfo{};
SndCtlCardInfo() { snd_ctl_card_info_malloc(&mInfo); }
~SndCtlCardInfo() { if(mInfo) snd_ctl_card_info_free(mInfo); }
SndCtlCardInfo(const SndCtlCardInfo&) = delete;
SndCtlCardInfo& operator=(const SndCtlCardInfo&) = delete;
[[nodiscard]]
operator snd_ctl_card_info_t*() const noexcept { return mInfo; }
};
struct SndPcmInfo {
snd_pcm_info_t *mInfo{};
SndPcmInfo() { snd_pcm_info_malloc(&mInfo); }
~SndPcmInfo() { if(mInfo) snd_pcm_info_free(mInfo); }
SndPcmInfo(const SndPcmInfo&) = delete;
SndPcmInfo& operator=(const SndPcmInfo&) = delete;
[[nodiscard]]
operator snd_pcm_info_t*() const noexcept { return mInfo; }
};
struct SndCtl {
snd_ctl_t *mHandle{};
SndCtl() = default;
~SndCtl() { if(mHandle) snd_ctl_close(mHandle); }
SndCtl(const SndCtl&) = delete;
SndCtl& operator=(const SndCtl&) = delete;
[[nodiscard]]
auto open(const char *name, int mode) { return snd_ctl_open(&mHandle, name, mode); }
[[nodiscard]]
operator snd_ctl_t*() const noexcept { return mHandle; }
};
std::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
{
al::vector<DevMap> devlist;
std::vector<DevMap> devlist;
snd_ctl_card_info_t *info;
snd_ctl_card_info_malloc(&info);
snd_pcm_info_t *pcminfo;
snd_pcm_info_malloc(&pcminfo);
SndCtlCardInfo info;
SndPcmInfo pcminfo;
auto defname = ConfigValueStr(nullptr, "alsa",
(stream == SND_PCM_STREAM_PLAYBACK) ? "device" : "capture");
devlist.emplace_back(alsaDevice, defname ? defname->c_str() : "default");
auto defname = ConfigValueStr({}, "alsa"sv,
(stream == SND_PCM_STREAM_PLAYBACK) ? "device"sv : "capture"sv);
devlist.emplace_back(GetDefaultName(), defname ? std::string_view{*defname} : "default"sv);
if(auto customdevs = ConfigValueStr(nullptr, "alsa",
(stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures"))
if(auto customdevs = ConfigValueStr({}, "alsa"sv,
(stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices"sv : "custom-captures"sv))
{
size_t nextpos{customdevs->find_first_not_of(';')};
size_t curpos;
while((curpos=nextpos) < customdevs->length())
size_t curpos{customdevs->find_first_not_of(';')};
while(curpos < customdevs->length())
{
nextpos = customdevs->find_first_of(';', curpos+1);
size_t seppos{customdevs->find_first_of('=', curpos)};
size_t nextpos{customdevs->find(';', curpos+1)};
const size_t seppos{customdevs->find('=', curpos)};
if(seppos == curpos || seppos >= nextpos)
{
std::string spec{customdevs->substr(curpos, nextpos-curpos)};
const std::string spec{customdevs->substr(curpos, nextpos-curpos)};
ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str());
}
else
{
devlist.emplace_back(customdevs->substr(curpos, seppos-curpos),
customdevs->substr(seppos+1, nextpos-seppos-1));
const auto &entry = devlist.back();
const std::string_view strview{*customdevs};
const auto &entry = devlist.emplace_back(strview.substr(curpos, seppos-curpos),
strview.substr(seppos+1, nextpos-seppos-1));
TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
}
if(nextpos < customdevs->length())
nextpos = customdevs->find_first_not_of(';', nextpos+1);
curpos = nextpos;
}
}
const std::string main_prefix{
ConfigValueStr(nullptr, "alsa", prefix_name(stream)).value_or("plughw:")};
const std::string main_prefix{ConfigValueStr({}, "alsa"sv, prefix_name(stream))
.value_or("plughw:")};
int card{-1};
int err{snd_card_next(&card)};
@ -308,16 +350,17 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
{
std::string name{"hw:" + std::to_string(card)};
snd_ctl_t *handle;
if((err=snd_ctl_open(&handle, name.c_str(), 0)) < 0)
SndCtl handle;
err = handle.open(name.c_str(), 0);
if(err < 0)
{
ERR("control open (hw:%d): %s\n", card, snd_strerror(err));
continue;
}
if((err=snd_ctl_card_info(handle, info)) < 0)
err = snd_ctl_card_info(handle, info);
if(err < 0)
{
ERR("control hardware info (hw:%d): %s\n", card, snd_strerror(err));
snd_ctl_close(handle);
continue;
}
@ -326,8 +369,7 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
name = prefix_name(stream);
name += '-';
name += cardid;
const std::string card_prefix{
ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(main_prefix)};
const std::string card_prefix{ConfigValueStr({}, "alsa"sv, name).value_or(main_prefix)};
int dev{-1};
while(true)
@ -339,7 +381,8 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
snd_pcm_info_set_device(pcminfo, static_cast<uint>(dev));
snd_pcm_info_set_subdevice(pcminfo, 0);
snd_pcm_info_set_stream(pcminfo, stream);
if((err=snd_ctl_pcm_info(handle, pcminfo)) < 0)
err = snd_ctl_pcm_info(handle, pcminfo);
if(err < 0)
{
if(err != -ENOENT)
ERR("control digital audio info (hw:%d): %s\n", card, snd_strerror(err));
@ -352,8 +395,8 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
name += cardid;
name += '-';
name += std::to_string(dev);
const std::string device_prefix{
ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(card_prefix)};
const std::string device_prefix{ConfigValueStr({}, "alsa"sv, name)
.value_or(card_prefix)};
/* "CardName, PcmName (CARD=cardid,DEV=dev)" */
name = cardname;
@ -372,18 +415,13 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
device += ",DEV=";
device += std::to_string(dev);
devlist.emplace_back(std::move(name), std::move(device));
const auto &entry = devlist.back();
const auto &entry = devlist.emplace_back(std::move(name), std::move(device));
TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
}
snd_ctl_close(handle);
}
if(err < 0)
ERR("snd_card_next failed: %s\n", snd_strerror(err));
snd_pcm_info_free(pcminfo);
snd_ctl_card_info_free(info);
return devlist;
}
@ -392,7 +430,6 @@ int verify_state(snd_pcm_t *handle)
{
snd_pcm_state_t state{snd_pcm_state(handle)};
int err;
switch(state)
{
case SND_PCM_STATE_OPEN:
@ -405,15 +442,27 @@ int verify_state(snd_pcm_t *handle)
break;
case SND_PCM_STATE_XRUN:
if((err=snd_pcm_recover(handle, -EPIPE, 1)) < 0)
if(int err{snd_pcm_recover(handle, -EPIPE, 1)}; err < 0)
return err;
break;
case SND_PCM_STATE_SUSPENDED:
if((err=snd_pcm_recover(handle, -ESTRPIPE, 1)) < 0)
if(int err{snd_pcm_recover(handle, -ESTRPIPE, 1)}; err < 0)
return err;
break;
case SND_PCM_STATE_DISCONNECTED:
return -ENODEV;
/* ALSA headers have made this enum public, leaving us in a bind: use
* the enum despite being private and internal to the libasound, or
* ignore when an enum value isn't handled. We can't rely on it being
* declared either, since older headers don't have it and it could be
* removed in the future. We can't even really rely on its value, since
* being private/internal means it's subject to change, but this is the
* best we can do.
*/
case 1024 /*SND_PCM_STATE_PRIVATE1*/:
assert(state != 1024);
}
return state;
@ -427,7 +476,7 @@ struct AlsaPlayback final : public BackendBase {
int mixerProc();
int mixerNoMMapProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -439,12 +488,10 @@ struct AlsaPlayback final : public BackendBase {
std::mutex mMutex;
uint mFrameStep{};
al::vector<al::byte> mBuffer;
std::vector<std::byte> mBuffer;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(AlsaPlayback)
};
AlsaPlayback::~AlsaPlayback()
@ -458,7 +505,7 @@ AlsaPlayback::~AlsaPlayback()
int AlsaPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
const snd_pcm_uframes_t update_size{mDevice->UpdateSize};
const snd_pcm_uframes_t buffer_size{mDevice->BufferSize};
@ -506,7 +553,7 @@ int AlsaPlayback::mixerProc()
avail -= avail%update_size;
// it is possible that contiguous areas are smaller, thus we use a loop
std::lock_guard<std::mutex> _{mMutex};
std::lock_guard<std::mutex> dlock{mMutex};
while(avail > 0)
{
snd_pcm_uframes_t frames{avail};
@ -520,6 +567,7 @@ int AlsaPlayback::mixerProc()
break;
}
/* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
char *WritePtr{static_cast<char*>(areas->addr) + (offset * areas->step / 8)};
mDevice->renderSamples(WritePtr, static_cast<uint>(frames), mFrameStep);
@ -541,7 +589,7 @@ int AlsaPlayback::mixerProc()
int AlsaPlayback::mixerNoMMapProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
const snd_pcm_uframes_t update_size{mDevice->UpdateSize};
const snd_pcm_uframes_t buffer_size{mDevice->BufferSize};
@ -585,13 +633,13 @@ int AlsaPlayback::mixerNoMMapProc()
continue;
}
al::byte *WritePtr{mBuffer.data()};
auto WritePtr = mBuffer.begin();
avail = snd_pcm_bytes_to_frames(mPcmHandle, static_cast<ssize_t>(mBuffer.size()));
std::lock_guard<std::mutex> _{mMutex};
mDevice->renderSamples(WritePtr, static_cast<uint>(avail), mFrameStep);
std::lock_guard<std::mutex> dlock{mMutex};
mDevice->renderSamples(al::to_address(WritePtr), static_cast<uint>(avail), mFrameStep);
while(avail > 0)
{
snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, WritePtr,
snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, al::to_address(WritePtr),
static_cast<snd_pcm_uframes_t>(avail))};
switch(ret)
{
@ -626,10 +674,10 @@ int AlsaPlayback::mixerNoMMapProc()
}
void AlsaPlayback::open(const char *name)
void AlsaPlayback::open(std::string_view name)
{
std::string driver{"default"};
if(name)
if(!name.empty())
{
if(PlaybackDevices.empty())
PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK);
@ -638,13 +686,13 @@ void AlsaPlayback::open(const char *name)
[name](const DevMap &entry) -> bool { return entry.name == name; });
if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
driver = iter->device_name;
}
else
{
name = alsaDevice;
if(auto driveropt = ConfigValueStr(nullptr, "alsa", "device"))
name = GetDefaultName();
if(auto driveropt = ConfigValueStr({}, "alsa"sv, "device"sv))
driver = std::move(driveropt).value();
}
TRACE("Opening device \"%s\"\n", driver.c_str());
@ -692,15 +740,14 @@ bool AlsaPlayback::reset()
break;
}
bool allowmmap{!!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "mmap", true)};
bool allowmmap{GetConfigValueBool(mDevice->DeviceName, "alsa"sv, "mmap"sv, true)};
uint periodLen{static_cast<uint>(mDevice->UpdateSize * 1000000_u64 / mDevice->Frequency)};
uint bufferLen{static_cast<uint>(mDevice->BufferSize * 1000000_u64 / mDevice->Frequency)};
uint rate{mDevice->Frequency};
int err{};
HwParamsPtr hp{CreateHwParams()};
#define CHECK(x) do { \
if((err=(x)) < 0) \
if(int err{x}; err < 0) \
throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \
snd_strerror(err)}; \
} while(0)
@ -715,17 +762,18 @@ bool AlsaPlayback::reset()
/* test and set format (implicitly sets sample bits) */
if(snd_pcm_hw_params_test_format(mPcmHandle, hp.get(), format) < 0)
{
static const struct {
struct FormatMap {
snd_pcm_format_t format;
DevFmtType fmttype;
} formatlist[] = {
{ SND_PCM_FORMAT_FLOAT, DevFmtFloat },
{ SND_PCM_FORMAT_S32, DevFmtInt },
{ SND_PCM_FORMAT_U32, DevFmtUInt },
{ SND_PCM_FORMAT_S16, DevFmtShort },
{ SND_PCM_FORMAT_U16, DevFmtUShort },
{ SND_PCM_FORMAT_S8, DevFmtByte },
{ SND_PCM_FORMAT_U8, DevFmtUByte },
};
static constexpr std::array formatlist{
FormatMap{SND_PCM_FORMAT_FLOAT, DevFmtFloat },
FormatMap{SND_PCM_FORMAT_S32, DevFmtInt },
FormatMap{SND_PCM_FORMAT_U32, DevFmtUInt },
FormatMap{SND_PCM_FORMAT_S16, DevFmtShort },
FormatMap{SND_PCM_FORMAT_U16, DevFmtUShort},
FormatMap{SND_PCM_FORMAT_S8, DevFmtByte },
FormatMap{SND_PCM_FORMAT_U8, DevFmtUByte },
};
for(const auto &fmt : formatlist)
@ -750,7 +798,7 @@ bool AlsaPlayback::reset()
else mDevice->FmtChans = DevFmtStereo;
}
/* set rate (implicitly constrains period/buffer parameters) */
if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", false)
if(!GetConfigValueBool(mDevice->DeviceName, "alsa", "allow-resampler", false)
|| !mDevice->Flags.test(FrequencyRequest))
{
if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0)
@ -760,10 +808,10 @@ bool AlsaPlayback::reset()
WARN("Failed to enable ALSA resampler\n");
CHECK(snd_pcm_hw_params_set_rate_near(mPcmHandle, hp.get(), &rate, nullptr));
/* set period time (implicitly constrains period/buffer parameters) */
if((err=snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)) < 0)
if(int err{snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)}; err < 0)
ERR("snd_pcm_hw_params_set_period_time_near failed: %s\n", snd_strerror(err));
/* set buffer time (implicitly sets buffer size/bytes/time and period size/bytes) */
if((err=snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp.get(), &bufferLen, nullptr)) < 0)
if(int err{snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp.get(), &bufferLen, nullptr)}; err < 0)
ERR("snd_pcm_hw_params_set_buffer_time_near failed: %s\n", snd_strerror(err));
/* install and prepare hardware configuration */
CHECK(snd_pcm_hw_params(mPcmHandle, hp.get()));
@ -798,11 +846,10 @@ bool AlsaPlayback::reset()
void AlsaPlayback::start()
{
int err{};
snd_pcm_access_t access{};
HwParamsPtr hp{CreateHwParams()};
#define CHECK(x) do { \
if((err=(x)) < 0) \
if(int err{x}; err < 0) \
throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \
snd_strerror(err)}; \
} while(0)
@ -849,10 +896,9 @@ void AlsaPlayback::stop()
ClockLatency AlsaPlayback::getClockLatency()
{
ClockLatency ret;
std::lock_guard<std::mutex> _{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice);
std::lock_guard<std::mutex> dlock{mMutex};
ClockLatency ret{};
ret.ClockTime = mDevice->getClockTime();
snd_pcm_sframes_t delay{};
int err{snd_pcm_delay(mPcmHandle, &delay)};
if(err < 0)
@ -871,23 +917,21 @@ struct AlsaCapture final : public BackendBase {
AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~AlsaCapture() override;
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
ClockLatency getClockLatency() override;
snd_pcm_t *mPcmHandle{nullptr};
al::vector<al::byte> mBuffer;
std::vector<std::byte> mBuffer;
bool mDoCapture{false};
RingBufferPtr mRing{nullptr};
snd_pcm_sframes_t mLastAvail{0};
DEF_NEWDEL(AlsaCapture)
};
AlsaCapture::~AlsaCapture()
@ -898,10 +942,10 @@ AlsaCapture::~AlsaCapture()
}
void AlsaCapture::open(const char *name)
void AlsaCapture::open(std::string_view name)
{
std::string driver{"default"};
if(name)
if(!name.empty())
{
if(CaptureDevices.empty())
CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE);
@ -910,19 +954,18 @@ void AlsaCapture::open(const char *name)
[name](const DevMap &entry) -> bool { return entry.name == name; });
if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
driver = iter->device_name;
}
else
{
name = alsaDevice;
if(auto driveropt = ConfigValueStr(nullptr, "alsa", "capture"))
name = GetDefaultName();
if(auto driveropt = ConfigValueStr({}, "alsa"sv, "capture"sv))
driver = std::move(driveropt).value();
}
TRACE("Opening device \"%s\"\n", driver.c_str());
int err{snd_pcm_open(&mPcmHandle, driver.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)};
if(err < 0)
if(int err{snd_pcm_open(&mPcmHandle, driver.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)}; err < 0)
throw al::backend_exception{al::backend_error::NoDevice,
"Could not open ALSA device \"%s\"", driver.c_str()};
@ -955,13 +998,15 @@ void AlsaCapture::open(const char *name)
break;
}
snd_pcm_uframes_t bufferSizeInFrames{maxu(mDevice->BufferSize, 100*mDevice->Frequency/1000)};
snd_pcm_uframes_t periodSizeInFrames{minu(mDevice->BufferSize, 25*mDevice->Frequency/1000)};
snd_pcm_uframes_t bufferSizeInFrames{std::max(mDevice->BufferSize,
100u*mDevice->Frequency/1000u)};
snd_pcm_uframes_t periodSizeInFrames{std::min(mDevice->BufferSize,
25u*mDevice->Frequency/1000u)};
bool needring{false};
HwParamsPtr hp{CreateHwParams()};
#define CHECK(x) do { \
if((err=(x)) < 0) \
if(int err{x}; err < 0) \
throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \
snd_strerror(err)}; \
} while(0)
@ -999,13 +1044,11 @@ void AlsaCapture::open(const char *name)
void AlsaCapture::start()
{
int err{snd_pcm_prepare(mPcmHandle)};
if(err < 0)
if(int err{snd_pcm_prepare(mPcmHandle)}; err < 0)
throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_prepare failed: %s",
snd_strerror(err)};
err = snd_pcm_start(mPcmHandle);
if(err < 0)
if(int err{snd_pcm_start(mPcmHandle)}; err < 0)
throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_start failed: %s",
snd_strerror(err)};
@ -1024,25 +1067,27 @@ void AlsaCapture::stop()
/* The ring buffer implicitly captures when checking availability.
* Direct access needs to explicitly capture it into temp storage.
*/
auto temp = al::vector<al::byte>(
auto temp = std::vector<std::byte>(
static_cast<size_t>(snd_pcm_frames_to_bytes(mPcmHandle, avail)));
captureSamples(temp.data(), avail);
mBuffer = std::move(temp);
}
int err{snd_pcm_drop(mPcmHandle)};
if(err < 0)
if(int err{snd_pcm_drop(mPcmHandle)}; err < 0)
ERR("drop failed: %s\n", snd_strerror(err));
mDoCapture = false;
}
void AlsaCapture::captureSamples(al::byte *buffer, uint samples)
void AlsaCapture::captureSamples(std::byte *buffer, uint samples)
{
if(mRing)
{
mRing->read(buffer, samples);
std::ignore = mRing->read(buffer, samples);
return;
}
const auto outspan = al::span{buffer,
static_cast<size_t>(snd_pcm_frames_to_bytes(mPcmHandle, samples))};
auto outiter = outspan.begin();
mLastAvail -= samples;
while(mDevice->Connected.load(std::memory_order_acquire) && samples > 0)
{
@ -1055,20 +1100,21 @@ void AlsaCapture::captureSamples(al::byte *buffer, uint samples)
if(static_cast<snd_pcm_uframes_t>(amt) > samples) amt = samples;
amt = snd_pcm_frames_to_bytes(mPcmHandle, amt);
std::copy_n(mBuffer.begin(), amt, buffer);
std::copy_n(mBuffer.begin(), amt, outiter);
mBuffer.erase(mBuffer.begin(), mBuffer.begin()+amt);
amt = snd_pcm_bytes_to_frames(mPcmHandle, amt);
}
else if(mDoCapture)
amt = snd_pcm_readi(mPcmHandle, buffer, samples);
amt = snd_pcm_readi(mPcmHandle, al::to_address(outiter), samples);
if(amt < 0)
{
ERR("read error: %s\n", snd_strerror(static_cast<int>(amt)));
if(amt == -EAGAIN)
continue;
if((amt=snd_pcm_recover(mPcmHandle, static_cast<int>(amt), 1)) >= 0)
amt = snd_pcm_recover(mPcmHandle, static_cast<int>(amt), 1);
if(amt >= 0)
{
amt = snd_pcm_start(mPcmHandle);
if(amt >= 0)
@ -1088,12 +1134,12 @@ void AlsaCapture::captureSamples(al::byte *buffer, uint samples)
continue;
}
buffer = buffer + amt;
outiter += amt;
samples -= static_cast<uint>(amt);
}
if(samples > 0)
std::fill_n(buffer, snd_pcm_frames_to_bytes(mPcmHandle, samples),
al::byte((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0));
std::fill_n(outiter, snd_pcm_frames_to_bytes(mPcmHandle, samples),
std::byte((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0));
}
uint AlsaCapture::availableSamples()
@ -1105,7 +1151,8 @@ uint AlsaCapture::availableSamples()
{
ERR("avail update failed: %s\n", snd_strerror(static_cast<int>(avail)));
if((avail=snd_pcm_recover(mPcmHandle, static_cast<int>(avail), 1)) >= 0)
avail = snd_pcm_recover(mPcmHandle, static_cast<int>(avail), 1);
if(avail >= 0)
{
if(mDoCapture)
avail = snd_pcm_start(mPcmHandle);
@ -1141,7 +1188,8 @@ uint AlsaCapture::availableSamples()
if(amt == -EAGAIN)
continue;
if((amt=snd_pcm_recover(mPcmHandle, static_cast<int>(amt), 1)) >= 0)
amt = snd_pcm_recover(mPcmHandle, static_cast<int>(amt), 1);
if(amt >= 0)
{
if(mDoCapture)
amt = snd_pcm_start(mPcmHandle);
@ -1168,9 +1216,8 @@ uint AlsaCapture::availableSamples()
ClockLatency AlsaCapture::getClockLatency()
{
ClockLatency ret;
ret.ClockTime = GetDeviceClockTime(mDevice);
ClockLatency ret{};
ret.ClockTime = mDevice->getClockTime();
snd_pcm_sframes_t delay{};
int err{snd_pcm_delay(mPcmHandle, &delay)};
if(err < 0)
@ -1189,13 +1236,9 @@ ClockLatency AlsaCapture::getClockLatency()
bool AlsaBackendFactory::init()
{
bool error{false};
#ifdef HAVE_DYNLOAD
if(!alsa_handle)
{
std::string missing_funcs;
alsa_handle = LoadLib("libasound.so.2");
if(!alsa_handle)
{
@ -1203,52 +1246,47 @@ bool AlsaBackendFactory::init()
return false;
}
error = false;
std::string missing_funcs;
#define LOAD_FUNC(f) do { \
p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(alsa_handle, #f)); \
if(p##f == nullptr) { \
error = true; \
missing_funcs += "\n" #f; \
} \
if(p##f == nullptr) missing_funcs += "\n" #f; \
} while(0)
ALSA_FUNCS(LOAD_FUNC);
#undef LOAD_FUNC
if(error)
if(!missing_funcs.empty())
{
WARN("Missing expected functions:%s\n", missing_funcs.c_str());
CloseLib(alsa_handle);
alsa_handle = nullptr;
return false;
}
}
#endif
return !error;
return true;
}
bool AlsaBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string AlsaBackendFactory::probe(BackendType type)
auto AlsaBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
auto add_device = [&outnames](const DevMap &entry) -> void
{
/* +1 to also append the null char (to ensure a null-separated list and
* double-null terminated list).
*/
outnames.append(entry.name.c_str(), entry.name.length()+1);
};
{ outnames.emplace_back(entry.name); };
switch(type)
{
case BackendType::Playback:
PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK);
outnames.reserve(PlaybackDevices.size());
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case BackendType::Capture:
CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE);
outnames.reserve(CaptureDevices.size());
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}

View file

@ -5,15 +5,15 @@
struct AlsaBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_ALSA_H */

View file

@ -7,17 +7,6 @@
#include <array>
#include <atomic>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmreg.h>
#include "albit.h"
#include "core/logging.h"
#include "aloptional.h"
#endif
#include "atomic.h"
#include "core/devformat.h"
@ -25,10 +14,12 @@ namespace al {
backend_exception::backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code}
{
/* NOLINTBEGIN(*-array-to-pointer-decay) */
std::va_list args;
va_start(args, msg);
setMessage(msg, args);
va_end(args);
/* NOLINTEND(*-array-to-pointer-decay) */
}
backend_exception::~backend_exception() = default;
@ -38,7 +29,7 @@ backend_exception::~backend_exception() = default;
bool BackendBase::reset()
{ throw al::backend_exception{al::backend_error::DeviceError, "Invalid BackendBase call"}; }
void BackendBase::captureSamples(al::byte*, uint)
void BackendBase::captureSamples(std::byte*, uint)
{ }
uint BackendBase::availableSamples()
@ -46,27 +37,26 @@ uint BackendBase::availableSamples()
ClockLatency BackendBase::getClockLatency()
{
ClockLatency ret;
ClockLatency ret{};
uint refcount;
do {
refcount = mDevice->waitForMix();
ret.ClockTime = GetDeviceClockTime(mDevice);
ret.ClockTime = mDevice->getClockTime();
std::atomic_thread_fence(std::memory_order_acquire);
} while(refcount != ReadRef(mDevice->MixCount));
} while(refcount != mDevice->mMixCount.load(std::memory_order_relaxed));
/* NOTE: The device will generally have about all but one periods filled at
* any given time during playback. Without a more accurate measurement from
* the output, this is an okay approximation.
*/
ret.Latency = std::max(std::chrono::seconds{mDevice->BufferSize-mDevice->UpdateSize},
std::chrono::seconds::zero());
ret.Latency = std::chrono::seconds{mDevice->BufferSize - mDevice->UpdateSize};
ret.Latency /= mDevice->Frequency;
return ret;
}
void BackendBase::setDefaultWFXChannelOrder()
void BackendBase::setDefaultWFXChannelOrder() const
{
mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex);
@ -126,6 +116,24 @@ void BackendBase::setDefaultWFXChannelOrder()
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
break;
case DevFmtX7144:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[FrontCenter] = 2;
mDevice->RealOut.ChannelIndex[LFE] = 3;
mDevice->RealOut.ChannelIndex[BackLeft] = 4;
mDevice->RealOut.ChannelIndex[BackRight] = 5;
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8;
mDevice->RealOut.ChannelIndex[TopFrontRight] = 9;
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
mDevice->RealOut.ChannelIndex[BottomFrontLeft] = 12;
mDevice->RealOut.ChannelIndex[BottomFrontRight] = 13;
mDevice->RealOut.ChannelIndex[BottomBackLeft] = 14;
mDevice->RealOut.ChannelIndex[BottomBackRight] = 15;
break;
case DevFmtX3D71:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
@ -141,7 +149,7 @@ void BackendBase::setDefaultWFXChannelOrder()
}
}
void BackendBase::setDefaultChannelOrder()
void BackendBase::setDefaultChannelOrder() const
{
mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex);
@ -179,6 +187,24 @@ void BackendBase::setDefaultChannelOrder()
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
break;
case DevFmtX7144:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[BackLeft] = 2;
mDevice->RealOut.ChannelIndex[BackRight] = 3;
mDevice->RealOut.ChannelIndex[FrontCenter] = 4;
mDevice->RealOut.ChannelIndex[LFE] = 5;
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8;
mDevice->RealOut.ChannelIndex[TopFrontRight] = 9;
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
mDevice->RealOut.ChannelIndex[BottomFrontLeft] = 12;
mDevice->RealOut.ChannelIndex[BottomFrontRight] = 13;
mDevice->RealOut.ChannelIndex[BottomBackLeft] = 14;
mDevice->RealOut.ChannelIndex[BottomBackRight] = 15;
break;
case DevFmtX3D71:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;

View file

@ -3,13 +3,16 @@
#include <chrono>
#include <cstdarg>
#include <cstddef>
#include <memory>
#include <ratio>
#include <string>
#include <string_view>
#include <vector>
#include "albyte.h"
#include "core/device.h"
#include "core/except.h"
#include "alc/events.h"
using uint = unsigned int;
@ -20,27 +23,33 @@ struct ClockLatency {
};
struct BackendBase {
virtual void open(const char *name) = 0;
virtual void open(std::string_view name) = 0;
virtual bool reset();
virtual void start() = 0;
virtual void stop() = 0;
virtual void captureSamples(al::byte *buffer, uint samples);
virtual void captureSamples(std::byte *buffer, uint samples);
virtual uint availableSamples();
virtual ClockLatency getClockLatency();
DeviceBase *const mDevice;
BackendBase() = delete;
BackendBase(const BackendBase&) = delete;
BackendBase(BackendBase&&) = delete;
BackendBase(DeviceBase *device) noexcept : mDevice{device} { }
virtual ~BackendBase() = default;
void operator=(const BackendBase&) = delete;
void operator=(BackendBase&&) = delete;
protected:
/** Sets the default channel order used by most non-WaveFormatEx-based APIs. */
void setDefaultChannelOrder();
void setDefaultChannelOrder() const;
/** Sets the default channel order used by WaveFormatEx. */
void setDefaultWFXChannelOrder();
void setDefaultWFXChannelOrder() const;
};
using BackendPtr = std::unique_ptr<BackendBase>;
@ -50,18 +59,6 @@ enum class BackendType {
};
/* Helper to get the current clock time from the device's ClockBase, and
* SamplesDone converted from the sample rate.
*/
inline std::chrono::nanoseconds GetDeviceClockTime(DeviceBase *device)
{
using std::chrono::seconds;
using std::chrono::nanoseconds;
auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency;
return device->ClockBase + ns;
}
/* Helper to get the device latency from the backend, including any fixed
* latency from post-processing.
*/
@ -74,16 +71,24 @@ inline ClockLatency GetClockLatency(DeviceBase *device, BackendBase *backend)
struct BackendFactory {
virtual bool init() = 0;
virtual bool querySupport(BackendType type) = 0;
virtual std::string probe(BackendType type) = 0;
virtual BackendPtr createBackend(DeviceBase *device, BackendType type) = 0;
protected:
BackendFactory() = default;
BackendFactory(const BackendFactory&) = delete;
BackendFactory(BackendFactory&&) = delete;
virtual ~BackendFactory() = default;
void operator=(const BackendFactory&) = delete;
void operator=(BackendFactory&&) = delete;
virtual auto init() -> bool = 0;
virtual auto querySupport(BackendType type) -> bool = 0;
virtual auto queryEventSupport(alc::EventType, BackendType) -> alc::EventSupport
{ return alc::EventSupport::NoSupport; }
virtual auto enumerate(BackendType type) -> std::vector<std::string> = 0;
virtual auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr = 0;
};
namespace al {
@ -98,15 +103,15 @@ class backend_exception final : public base_exception {
backend_error mErrorCode;
public:
#ifdef __USE_MINGW_ANSI_STDIO
[[gnu::format(gnu_printf, 3, 4)]]
#ifdef __MINGW32__
[[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]]
#else
[[gnu::format(printf, 3, 4)]]
#endif
backend_exception(backend_error code, const char *msg, ...);
~backend_exception() override;
backend_error errorCode() const noexcept { return mErrorCode; }
[[nodiscard]] auto errorCode() const noexcept -> backend_error { return mErrorCode; }
};
} // namespace al

View file

@ -22,18 +22,20 @@
#include "coreaudio.h"
#include <inttypes.h>
#include <cinttypes>
#include <cmath>
#include <memory>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <string.h>
#include <unistd.h>
#include <cmath>
#include <memory>
#include <string>
#include <vector>
#include <optional>
#include "alnumeric.h"
#include "alstring.h"
#include "core/converter.h"
#include "core/device.h"
#include "core/logging.h"
@ -42,18 +44,40 @@
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
namespace {
#if TARGET_OS_IOS || TARGET_OS_TV
#define CAN_ENUMERATE 0
#else
#include <IOKit/audio/IOAudioTypes.h>
#define CAN_ENUMERATE 1
#endif
namespace {
constexpr auto OutputElement = 0;
constexpr auto InputElement = 1;
struct FourCCPrinter {
char mString[sizeof(UInt32) + 1]{};
constexpr FourCCPrinter(UInt32 code) noexcept
{
for(size_t i{0};i < sizeof(UInt32);++i)
{
const auto ch = static_cast<char>(code & 0xff);
/* If this breaks early it'll leave the first byte null, to get
* read as a 0-length string.
*/
if(ch <= 0x1f || ch >= 0x7f)
break;
mString[sizeof(UInt32)-1-i] = ch;
code >>= 8;
}
}
constexpr FourCCPrinter(int code) noexcept : FourCCPrinter{static_cast<UInt32>(code)} { }
constexpr const char *c_str() const noexcept { return mString; }
};
#if CAN_ENUMERATE
struct DeviceEntry {
AudioDeviceID mId;
@ -147,7 +171,8 @@ UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture)
&propSize);
if(err)
{
ERR("kAudioDevicePropertyStreamConfiguration size query failed: %u\n", err);
ERR("kAudioDevicePropertyStreamConfiguration size query failed: '%s' (%u)\n",
FourCCPrinter{err}.c_str(), err);
return 0;
}
@ -158,7 +183,8 @@ UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture)
buflist);
if(err)
{
ERR("kAudioDevicePropertyStreamConfiguration query failed: %u\n", err);
ERR("kAudioDevicePropertyStreamConfiguration query failed: '%s' (%u)\n",
FourCCPrinter{err}.c_str(), err);
return 0;
}
@ -182,7 +208,7 @@ void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
auto devIds = std::vector<AudioDeviceID>(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown);
if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data()))
{
ERR("Failed to get device list: %u\n", err);
ERR("Failed to get device list: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
return;
}
@ -247,6 +273,48 @@ void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
newdevs.swap(list);
}
struct DeviceHelper {
DeviceHelper()
{
AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain};
OSStatus status = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil);
if (status != noErr)
ERR("AudioObjectAddPropertyListener fail: %d", status);
}
~DeviceHelper()
{
AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain};
OSStatus status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil);
if (status != noErr)
ERR("AudioObjectRemovePropertyListener fail: %d", status);
}
static OSStatus DeviceListenerProc(AudioObjectID /*inObjectID*/, UInt32 inNumberAddresses,
const AudioObjectPropertyAddress *inAddresses, void* /*inClientData*/)
{
for(UInt32 i = 0; i < inNumberAddresses; ++i)
{
switch(inAddresses[i].mSelector)
{
case kAudioHardwarePropertyDefaultOutputDevice:
case kAudioHardwarePropertyDefaultSystemOutputDevice:
alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback,
"Default playback device changed: "+std::to_string(inAddresses[i].mSelector));
break;
case kAudioHardwarePropertyDefaultInputDevice:
alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture,
"Default capture device changed: "+std::to_string(inAddresses[i].mSelector));
break;
}
}
return noErr;
}
};
static std::optional<DeviceHelper> sDeviceHelper;
#else
static constexpr char ca_device[] = "CoreAudio Default";
@ -260,15 +328,8 @@ struct CoreAudioPlayback final : public BackendBase {
OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList *ioData) noexcept;
static OSStatus MixerProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList *ioData) noexcept
{
return static_cast<CoreAudioPlayback*>(inRefCon)->MixerProc(ioActionFlags, inTimeStamp,
inBusNumber, inNumberFrames, ioData);
}
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -277,8 +338,6 @@ struct CoreAudioPlayback final : public BackendBase {
uint mFrameSize{0u};
AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
DEF_NEWDEL(CoreAudioPlayback)
};
CoreAudioPlayback::~CoreAudioPlayback()
@ -301,11 +360,11 @@ OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTi
}
void CoreAudioPlayback::open(const char *name)
void CoreAudioPlayback::open(std::string_view name)
{
#if CAN_ENUMERATE
AudioDeviceID audioDevice{kAudioDeviceUnknown};
if(!name)
if(name.empty())
GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice),
&audioDevice);
else
@ -318,16 +377,16 @@ void CoreAudioPlayback::open(const char *name)
auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name);
if(devmatch == PlaybackList.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
audioDevice = devmatch->mId;
}
#else
if(!name)
if(name.empty())
name = ca_device;
else if(strcmp(name, ca_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
else if(name != ca_device)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
#endif
/* open the default output unit */
@ -351,7 +410,7 @@ void CoreAudioPlayback::open(const char *name)
OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)};
if(err != noErr)
throw al::backend_exception{al::backend_error::NoDevice,
"Could not create component instance: %u", err};
"Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
#if CAN_ENUMERATE
if(audioDevice != kAudioDeviceUnknown)
@ -362,7 +421,7 @@ void CoreAudioPlayback::open(const char *name)
err = AudioUnitInitialize(audioUnit);
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not initialize audio unit: %u", err};
"Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
/* WARNING: I don't know if "valid" audio unit values are guaranteed to be
* non-0. If not, this logic is broken.
@ -375,7 +434,7 @@ void CoreAudioPlayback::open(const char *name)
mAudioUnit = audioUnit;
#if CAN_ENUMERATE
if(name)
if(!name.empty())
mDevice->DeviceName = name;
else
{
@ -388,6 +447,21 @@ void CoreAudioPlayback::open(const char *name)
if(!devname.empty()) mDevice->DeviceName = std::move(devname);
else mDevice->DeviceName = "Unknown Device Name";
}
if(audioDevice != kAudioDeviceUnknown)
{
UInt32 type{};
err = GetDevProperty(audioDevice, kAudioDevicePropertyDataSource, false,
kAudioObjectPropertyElementMaster, sizeof(type), &type);
if(err != noErr)
ERR("Failed to get audio device type: %u\n", err);
else
{
TRACE("Got device type '%s'\n", FourCCPrinter{type}.c_str());
mDevice->Flags.set(DirectEar, (type == kIOAudioOutputPortSubTypeHeadphones));
}
}
#else
mDevice->DeviceName = name;
#endif
@ -397,7 +471,7 @@ bool CoreAudioPlayback::reset()
{
OSStatus err{AudioUnitUninitialize(mAudioUnit)};
if(err != noErr)
ERR("-- AudioUnitUninitialize failed.\n");
ERR("AudioUnitUninitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
/* retrieve default output unit's properties (output side) */
AudioStreamBasicDescription streamFormat{};
@ -406,7 +480,8 @@ bool CoreAudioPlayback::reset()
OutputElement, &streamFormat, &size);
if(err != noErr || size != sizeof(streamFormat))
{
ERR("AudioUnitGetProperty failed\n");
ERR("AudioUnitGetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(),
err);
return false;
}
@ -473,7 +548,8 @@ bool CoreAudioPlayback::reset()
OutputElement, &streamFormat, sizeof(streamFormat));
if(err != noErr)
{
ERR("AudioUnitSetProperty failed\n");
ERR("AudioUnitSetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(),
err);
return false;
}
@ -482,14 +558,16 @@ bool CoreAudioPlayback::reset()
/* setup callback */
mFrameSize = mDevice->frameSizeFromFmt();
AURenderCallbackStruct input{};
input.inputProc = CoreAudioPlayback::MixerProcC;
input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept
{ return static_cast<CoreAudioPlayback*>(inRefCon)->MixerProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); };
input.inputProcRefCon = this;
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct));
if(err != noErr)
{
ERR("AudioUnitSetProperty failed\n");
ERR("AudioUnitSetProperty(SetRenderCallback) failed: '%s' (%u)\n",
FourCCPrinter{err}.c_str(), err);
return false;
}
@ -497,7 +575,7 @@ bool CoreAudioPlayback::reset()
err = AudioUnitInitialize(mAudioUnit);
if(err != noErr)
{
ERR("AudioUnitInitialize failed\n");
ERR("AudioUnitInitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
return false;
}
@ -509,14 +587,14 @@ void CoreAudioPlayback::start()
const OSStatus err{AudioOutputUnitStart(mAudioUnit)};
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"AudioOutputUnitStart failed: %d", err};
"AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
}
void CoreAudioPlayback::stop()
{
OSStatus err{AudioOutputUnitStop(mAudioUnit)};
if(err != noErr)
ERR("AudioOutputUnitStop failed\n");
ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
}
@ -527,18 +605,11 @@ struct CoreAudioCapture final : public BackendBase {
OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
UInt32 inNumberFrames, AudioBufferList *ioData) noexcept;
static OSStatus RecordProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList *ioData) noexcept
{
return static_cast<CoreAudioCapture*>(inRefCon)->RecordProc(ioActionFlags, inTimeStamp,
inBusNumber, inNumberFrames, ioData);
}
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
AudioUnit mAudioUnit{0};
@ -548,11 +619,9 @@ struct CoreAudioCapture final : public BackendBase {
SampleConverterPtr mConverter;
al::vector<char> mCaptureData;
std::vector<char> mCaptureData;
RingBufferPtr mRing{nullptr};
DEF_NEWDEL(CoreAudioCapture)
};
CoreAudioCapture::~CoreAudioCapture()
@ -568,7 +637,7 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
AudioBufferList*) noexcept
{
union {
al::byte _[maxz(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))];
std::byte buf[std::max(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))];
AudioBufferList list;
} audiobuf{};
@ -581,20 +650,20 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
inNumberFrames, &audiobuf.list)};
if(err != noErr)
{
ERR("AudioUnitRender capture error: %d\n", err);
ERR("AudioUnitRender capture error: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
return err;
}
mRing->write(mCaptureData.data(), inNumberFrames);
std::ignore = mRing->write(mCaptureData.data(), inNumberFrames);
return noErr;
}
void CoreAudioCapture::open(const char *name)
void CoreAudioCapture::open(std::string_view name)
{
#if CAN_ENUMERATE
AudioDeviceID audioDevice{kAudioDeviceUnknown};
if(!name)
if(name.empty())
GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(audioDevice),
&audioDevice);
else
@ -607,16 +676,16 @@ void CoreAudioCapture::open(const char *name)
auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name);
if(devmatch == CaptureList.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
audioDevice = devmatch->mId;
}
#else
if(!name)
if(name.empty())
name = ca_device;
else if(strcmp(name, ca_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
else if(name != ca_device)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
#endif
AudioComponentDescription desc{};
@ -640,7 +709,7 @@ void CoreAudioCapture::open(const char *name)
OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
if(err != noErr)
throw al::backend_exception{al::backend_error::NoDevice,
"Could not create component instance: %u", err};
"Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
// Turn off AudioUnit output
UInt32 enableIO{0};
@ -648,7 +717,8 @@ void CoreAudioCapture::open(const char *name)
kAudioUnitScope_Output, OutputElement, &enableIO, sizeof(enableIO));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not disable audio unit output property: %u", err};
"Could not disable audio unit output property: '%s' (%u)", FourCCPrinter{err}.c_str(),
err};
// Turn on AudioUnit input
enableIO = 1;
@ -656,7 +726,8 @@ void CoreAudioCapture::open(const char *name)
kAudioUnitScope_Input, InputElement, &enableIO, sizeof(enableIO));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not enable audio unit input property: %u", err};
"Could not enable audio unit input property: '%s' (%u)", FourCCPrinter{err}.c_str(),
err};
#if CAN_ENUMERATE
if(audioDevice != kAudioDeviceUnknown)
@ -666,14 +737,15 @@ void CoreAudioCapture::open(const char *name)
// set capture callback
AURenderCallbackStruct input{};
input.inputProc = CoreAudioCapture::RecordProcC;
input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept
{ return static_cast<CoreAudioCapture*>(inRefCon)->RecordProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); };
input.inputProcRefCon = this;
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not set capture callback: %u", err};
"Could not set capture callback: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
// Disable buffer allocation for capture
UInt32 flag{0};
@ -681,13 +753,14 @@ void CoreAudioCapture::open(const char *name)
kAudioUnitScope_Output, InputElement, &flag, sizeof(flag));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not disable buffer allocation property: %u", err};
"Could not disable buffer allocation property: '%s' (%u)", FourCCPrinter{err}.c_str(),
err};
// Initialize the device
err = AudioUnitInitialize(mAudioUnit);
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not initialize audio unit: %u", err};
"Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
// Get the hardware format
AudioStreamBasicDescription hardwareFormat{};
@ -696,7 +769,7 @@ void CoreAudioCapture::open(const char *name)
InputElement, &hardwareFormat, &propertySize);
if(err != noErr || propertySize != sizeof(hardwareFormat))
throw al::backend_exception{al::backend_error::DeviceError,
"Could not get input format: %u", err};
"Could not get input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
// Set up the requested format description
AudioStreamBasicDescription requestedFormat{};
@ -749,6 +822,7 @@ void CoreAudioCapture::open(const char *name)
case DevFmtX61:
case DevFmtX71:
case DevFmtX714:
case DevFmtX7144:
case DevFmtX3D71:
case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s not supported",
@ -777,14 +851,14 @@ void CoreAudioCapture::open(const char *name)
InputElement, &outputFormat, sizeof(outputFormat));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not set input format: %u", err};
"Could not set input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
/* Calculate the minimum AudioUnit output format frame count for the pre-
* conversion ring buffer. Ensure at least 100ms for the total buffer.
*/
double srateScale{outputFormat.mSampleRate / mDevice->Frequency};
auto FrameCount64 = maxu64(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)),
static_cast<UInt32>(outputFormat.mSampleRate)/10);
auto FrameCount64 = std::max(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)),
static_cast<UInt32>(outputFormat.mSampleRate)/10_u64);
FrameCount64 += MaxResamplerPadding;
if(FrameCount64 > std::numeric_limits<int32_t>::max())
throw al::backend_exception{al::backend_error::DeviceError,
@ -796,11 +870,11 @@ void CoreAudioCapture::open(const char *name)
kAudioUnitScope_Global, OutputElement, &outputFrameCount, &propertySize);
if(err != noErr || propertySize != sizeof(outputFrameCount))
throw al::backend_exception{al::backend_error::DeviceError,
"Could not get input frame count: %u", err};
"Could not get input frame count: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
mCaptureData.resize(outputFrameCount * mFrameSize);
outputFrameCount = static_cast<UInt32>(maxu64(outputFrameCount, FrameCount64));
outputFrameCount = static_cast<UInt32>(std::max(uint64_t{outputFrameCount}, FrameCount64));
mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false);
/* Set up sample converter if needed */
@ -810,7 +884,7 @@ void CoreAudioCapture::open(const char *name)
mDevice->Frequency, Resampler::FastBSinc24);
#if CAN_ENUMERATE
if(name)
if(!name.empty())
mDevice->DeviceName = name;
else
{
@ -834,21 +908,21 @@ void CoreAudioCapture::start()
OSStatus err{AudioOutputUnitStart(mAudioUnit)};
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"AudioOutputUnitStart failed: %d", err};
"AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
}
void CoreAudioCapture::stop()
{
OSStatus err{AudioOutputUnitStop(mAudioUnit)};
if(err != noErr)
ERR("AudioOutputUnitStop failed\n");
ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
}
void CoreAudioCapture::captureSamples(al::byte *buffer, uint samples)
void CoreAudioCapture::captureSamples(std::byte *buffer, uint samples)
{
if(!mConverter)
{
mRing->read(buffer, samples);
std::ignore = mRing->read(buffer, samples);
return;
}
@ -882,28 +956,34 @@ BackendFactory &CoreAudioBackendFactory::getFactory()
return factory;
}
bool CoreAudioBackendFactory::init() { return true; }
bool CoreAudioBackendFactory::init()
{
#if CAN_ENUMERATE
sDeviceHelper.emplace();
#endif
return true;
}
bool CoreAudioBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
std::string CoreAudioBackendFactory::probe(BackendType type)
auto CoreAudioBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
#if CAN_ENUMERATE
auto append_name = [&outnames](const DeviceEntry &entry) -> void
{
/* Includes null char. */
outnames.append(entry.mName.c_str(), entry.mName.length()+1);
};
{ outnames.emplace_back(entry.mName); };
switch(type)
{
case BackendType::Playback:
EnumerateDevices(PlaybackList, false);
outnames.reserve(PlaybackList.size());
std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
break;
case BackendType::Capture:
EnumerateDevices(CaptureList, true);
outnames.reserve(CaptureList.size());
std::for_each(CaptureList.cbegin(), CaptureList.cend(), append_name);
break;
}
@ -914,8 +994,7 @@ std::string CoreAudioBackendFactory::probe(BackendType type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
outnames.append(ca_device, sizeof(ca_device));
outnames.emplace_back(ca_device);
break;
}
#endif
@ -930,3 +1009,18 @@ BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendTyp
return BackendPtr{new CoreAudioCapture{device}};
return nullptr;
}
alc::EventSupport CoreAudioBackendFactory::queryEventSupport(alc::EventType eventType, BackendType)
{
switch(eventType)
{
case alc::EventType::DefaultDeviceChanged:
return alc::EventSupport::FullSupport;
case alc::EventType::DeviceAdded:
case alc::EventType::DeviceRemoved:
case alc::EventType::Count:
break;
}
return alc::EventSupport::NoSupport;
}

View file

@ -5,15 +5,17 @@
struct CoreAudioBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
static BackendFactory &getFactory();
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_COREAUDIO_H */

View file

@ -25,10 +25,6 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include <cguid.h>
#include <mmreg.h>
#ifndef _WAVEFORMATEXTENSIBLE_
@ -36,15 +32,21 @@
#include <ksmedia.h>
#endif
#include <algorithm>
#include <atomic>
#include <cassert>
#include <thread>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <functional>
#include <memory.h>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#include "alnumeric.h"
#include "alspan.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "comptr.h"
#include "core/device.h"
#include "core/helpers.h"
@ -52,7 +54,6 @@
#include "dynload.h"
#include "ringbuffer.h"
#include "strutils.h"
#include "threads.h"
/* MinGW-w64 needs this for some unknown reason now. */
using LPCWAVEFORMATEX = const WAVEFORMATEX*;
@ -129,10 +130,10 @@ struct DevMap {
{ }
};
al::vector<DevMap> PlaybackDevices;
al::vector<DevMap> CaptureDevices;
std::vector<DevMap> PlaybackDevices;
std::vector<DevMap> CaptureDevices;
bool checkName(const al::vector<DevMap> &list, const std::string &name)
bool checkName(const al::span<DevMap> list, const std::string &name)
{
auto match_name = [&name](const DevMap &entry) -> bool
{ return entry.name == name; };
@ -144,7 +145,7 @@ BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, voi
if(!guid)
return TRUE;
auto& devices = *static_cast<al::vector<DevMap>*>(data);
auto& devices = *static_cast<std::vector<DevMap>*>(data);
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)};
int count{1};
@ -176,7 +177,7 @@ struct DSoundPlayback final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -189,8 +190,6 @@ struct DSoundPlayback final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(DSoundPlayback)
};
DSoundPlayback::~DSoundPlayback()
@ -209,7 +208,7 @@ DSoundPlayback::~DSoundPlayback()
FORCE_ALIGN int DSoundPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
DSBCAPS DSBCaps{};
DSBCaps.dwSize = sizeof(DSBCaps);
@ -299,24 +298,22 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
return 0;
}
void DSoundPlayback::open(const char *name)
void DSoundPlayback::open(std::string_view name)
{
HRESULT hr;
if(PlaybackDevices.empty())
{
/* Initialize COM to prevent name truncation */
HRESULT hrcom{CoInitialize(nullptr)};
ComWrapper com{};
hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
if(FAILED(hr))
ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
if(SUCCEEDED(hrcom))
CoUninitialize();
}
const GUID *guid{nullptr};
if(!name && !PlaybackDevices.empty())
if(name.empty() && !PlaybackDevices.empty())
{
name = PlaybackDevices[0].name.c_str();
name = PlaybackDevices[0].name;
guid = &PlaybackDevices[0].guid;
}
else
@ -332,7 +329,7 @@ void DSoundPlayback::open(const char *name)
[&id](const DevMap &entry) -> bool { return entry.guid == id; });
if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
}
guid = &iter->guid;
}
@ -347,7 +344,7 @@ void DSoundPlayback::open(const char *name)
//DirectSound Init code
ComPtr<IDirectSound> ds;
if(SUCCEEDED(hr))
hr = DirectSoundCreate(guid, ds.getPtr(), nullptr);
hr = DirectSoundCreate(guid, al::out_ptr(ds), nullptr);
if(SUCCEEDED(hr))
hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
if(FAILED(hr))
@ -425,49 +422,53 @@ bool DSoundPlayback::reset()
case DevFmtX51: OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break;
case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break;
case DevFmtX7144: mDevice->FmtChans = DevFmtX714;
/* fall-through */
case DevFmtX714: OutputType.dwChannelMask = X7DOT1DOT4; break;
case DevFmtX3D71: OutputType.dwChannelMask = X7DOT1; break;
}
retry_open:
hr = S_OK;
OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
OutputType.Format.wBitsPerSample / 8);
OutputType.Format.nSamplesPerSec = mDevice->Frequency;
OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
OutputType.Format.nBlockAlign;
OutputType.Format.cbSize = 0;
do {
hr = S_OK;
OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
OutputType.Format.wBitsPerSample / 8);
OutputType.Format.nSamplesPerSec = mDevice->Frequency;
OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
OutputType.Format.nBlockAlign;
OutputType.Format.cbSize = 0;
if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
{
OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
if(mDevice->FmtType == DevFmtFloat)
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
else
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
mPrimaryBuffer = nullptr;
}
else
{
if(SUCCEEDED(hr) && !mPrimaryBuffer)
if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
{
DSBUFFERDESC DSBDescription{};
DSBDescription.dwSize = sizeof(DSBDescription);
DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
hr = mDS->CreateSoundBuffer(&DSBDescription, mPrimaryBuffer.getPtr(), nullptr);
}
if(SUCCEEDED(hr))
hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
}
OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
/* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
if(mDevice->FmtType == DevFmtFloat)
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
else
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
mPrimaryBuffer = nullptr;
}
else
{
if(SUCCEEDED(hr) && !mPrimaryBuffer)
{
DSBUFFERDESC DSBDescription{};
DSBDescription.dwSize = sizeof(DSBDescription);
DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mPrimaryBuffer), nullptr);
}
if(SUCCEEDED(hr))
hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
}
if(FAILED(hr))
break;
if(SUCCEEDED(hr))
{
uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
if(num_updates > MAX_UPDATES)
num_updates = MAX_UPDATES;
@ -480,26 +481,21 @@ retry_open:
DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign;
DSBDescription.lpwfxFormat = &OutputType.Format;
hr = mDS->CreateSoundBuffer(&DSBDescription, mBuffer.getPtr(), nullptr);
if(FAILED(hr) && mDevice->FmtType == DevFmtFloat)
{
mDevice->FmtType = DevFmtShort;
goto retry_open;
}
}
hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mBuffer), nullptr);
if(SUCCEEDED(hr) || mDevice->FmtType != DevFmtFloat)
break;
mDevice->FmtType = DevFmtShort;
} while(FAILED(hr));
if(SUCCEEDED(hr))
{
void *ptr;
hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr);
hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, al::out_ptr(mNotifies));
if(SUCCEEDED(hr))
{
mNotifies = ComPtr<IDirectSoundNotify>{static_cast<IDirectSoundNotify*>(ptr)};
uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
assert(num_updates <= MAX_UPDATES);
std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots;
std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots{};
for(uint i{0};i < num_updates;++i)
{
nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign;
@ -550,10 +546,10 @@ struct DSoundCapture final : public BackendBase {
DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~DSoundCapture() override;
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
ComPtr<IDirectSoundCapture> mDSC;
@ -562,8 +558,6 @@ struct DSoundCapture final : public BackendBase {
DWORD mCursor{0u};
RingBufferPtr mRing;
DEF_NEWDEL(DSoundCapture)
};
DSoundCapture::~DSoundCapture()
@ -577,24 +571,22 @@ DSoundCapture::~DSoundCapture()
}
void DSoundCapture::open(const char *name)
void DSoundCapture::open(std::string_view name)
{
HRESULT hr;
if(CaptureDevices.empty())
{
/* Initialize COM to prevent name truncation */
HRESULT hrcom{CoInitialize(nullptr)};
ComWrapper com{};
hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
if(FAILED(hr))
ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
if(SUCCEEDED(hrcom))
CoUninitialize();
}
const GUID *guid{nullptr};
if(!name && !CaptureDevices.empty())
if(name.empty() && !CaptureDevices.empty())
{
name = CaptureDevices[0].name.c_str();
name = CaptureDevices[0].name;
guid = &CaptureDevices[0].guid;
}
else
@ -610,7 +602,7 @@ void DSoundCapture::open(const char *name)
[&id](const DevMap &entry) -> bool { return entry.guid == id; });
if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
}
guid = &iter->guid;
}
@ -641,6 +633,7 @@ void DSoundCapture::open(const char *name)
case DevFmtX61: InputType.dwChannelMask = X6DOT1; break;
case DevFmtX71: InputType.dwChannelMask = X7DOT1; break;
case DevFmtX714: InputType.dwChannelMask = X7DOT1DOT4; break;
case DevFmtX7144:
case DevFmtX3D71:
case DevFmtAmbi3D:
WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
@ -657,6 +650,7 @@ void DSoundCapture::open(const char *name)
InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec *
InputType.Format.nBlockAlign;
InputType.Format.cbSize = 0;
/* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample;
if(mDevice->FmtType == DevFmtFloat)
InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
@ -669,8 +663,7 @@ void DSoundCapture::open(const char *name)
InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
}
uint samples{mDevice->BufferSize};
samples = maxu(samples, 100 * mDevice->Frequency / 1000);
const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)};
DSCBUFFERDESC DSCBDescription{};
DSCBDescription.dwSize = sizeof(DSCBDescription);
@ -679,9 +672,9 @@ void DSoundCapture::open(const char *name)
DSCBDescription.lpwfxFormat = &InputType.Format;
//DirectSoundCapture Init code
hr = DirectSoundCaptureCreate(guid, mDSC.getPtr(), nullptr);
hr = DirectSoundCaptureCreate(guid, al::out_ptr(mDSC), nullptr);
if(SUCCEEDED(hr))
mDSC->CreateCaptureBuffer(&DSCBDescription, mDSCbuffer.getPtr(), nullptr);
mDSC->CreateCaptureBuffer(&DSCBDescription, al::out_ptr(mDSCbuffer), nullptr);
if(SUCCEEDED(hr))
mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
@ -719,8 +712,8 @@ void DSoundCapture::stop()
}
}
void DSoundCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
void DSoundCapture::captureSamples(std::byte *buffer, uint samples)
{ std::ignore = mRing->read(buffer, samples); }
uint DSoundCapture::availableSamples()
{
@ -743,9 +736,9 @@ uint DSoundCapture::availableSamples()
}
if(SUCCEEDED(hr))
{
mRing->write(ReadPtr1, ReadCnt1/FrameSize);
std::ignore = mRing->write(ReadPtr1, ReadCnt1/FrameSize);
if(ReadPtr2 != nullptr && ReadCnt2 > 0)
mRing->write(ReadPtr2, ReadCnt2/FrameSize);
std::ignore = mRing->write(ReadPtr2, ReadCnt2/FrameSize);
hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2);
mCursor = ReadCursor;
}
@ -802,40 +795,32 @@ bool DSoundBackendFactory::init()
bool DSoundBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string DSoundBackendFactory::probe(BackendType type)
auto DSoundBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
auto add_device = [&outnames](const DevMap &entry) -> void
{
/* +1 to also append the null char (to ensure a null-separated list and
* double-null terminated list).
*/
outnames.append(entry.name.c_str(), entry.name.length()+1);
};
{ outnames.emplace_back(entry.name); };
/* Initialize COM to prevent name truncation */
HRESULT hr;
HRESULT hrcom{CoInitialize(nullptr)};
ComWrapper com{};
switch(type)
{
case BackendType::Playback:
PlaybackDevices.clear();
hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
if(FAILED(hr))
if(HRESULT hr{DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices)}; FAILED(hr))
ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr);
outnames.reserve(PlaybackDevices.size());
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case BackendType::Capture:
CaptureDevices.clear();
hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
if(FAILED(hr))
if(HRESULT hr{DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices)};FAILED(hr))
ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr);
outnames.reserve(CaptureDevices.size());
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}
if(SUCCEEDED(hrcom))
CoUninitialize();
return outnames;
}

View file

@ -5,15 +5,15 @@
struct DSoundBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_DSOUND_H */

View file

@ -22,23 +22,26 @@
#include "jack.h"
#include <array>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <memory.h>
#include <array>
#include <mutex>
#include <thread>
#include <functional>
#include <vector>
#include "alc/alconfig.h"
#include "alnumeric.h"
#include "alsem.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "dynload.h"
#include "ringbuffer.h"
#include "threads.h"
#include <jack/jack.h>
#include <jack/ringbuffer.h>
@ -46,6 +49,8 @@
namespace {
using namespace std::string_view_literals;
#ifdef HAVE_DYNLOAD
#define JACK_FUNCS(MAGIC) \
MAGIC(jack_client_open); \
@ -99,19 +104,13 @@ decltype(jack_error_callback) * pjack_error_callback;
#endif
constexpr char JackDefaultAudioType[] = JACK_DEFAULT_AUDIO_TYPE;
jack_options_t ClientOptions = JackNullOption;
bool jack_load()
{
bool error{false};
#ifdef HAVE_DYNLOAD
if(!jack_handle)
{
std::string missing_funcs;
#ifdef _WIN32
#define JACKLIB "libjack.dll"
#else
@ -124,13 +123,10 @@ bool jack_load()
return false;
}
error = false;
std::string missing_funcs;
#define LOAD_FUNC(f) do { \
p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)); \
if(p##f == nullptr) { \
error = true; \
missing_funcs += "\n" #f; \
} \
if(p##f == nullptr) missing_funcs += "\n" #f; \
} while(0)
JACK_FUNCS(LOAD_FUNC);
#undef LOAD_FUNC
@ -139,61 +135,66 @@ bool jack_load()
LOAD_SYM(jack_error_callback);
#undef LOAD_SYM
if(error)
if(!missing_funcs.empty())
{
WARN("Missing expected functions:%s\n", missing_funcs.c_str());
CloseLib(jack_handle);
jack_handle = nullptr;
return false;
}
}
#endif
return !error;
return true;
}
struct JackDeleter {
void operator()(void *ptr) { jack_free(ptr); }
};
using JackPortsPtr = std::unique_ptr<const char*[],JackDeleter>;
using JackPortsPtr = std::unique_ptr<const char*[],JackDeleter>; /* NOLINT(*-avoid-c-arrays) */
struct DeviceEntry {
std::string mName;
std::string mPattern;
DeviceEntry() = default;
DeviceEntry(const DeviceEntry&) = default;
DeviceEntry(DeviceEntry&&) = default;
template<typename T, typename U>
DeviceEntry(T&& name, U&& pattern)
: mName{std::forward<T>(name)}, mPattern{std::forward<U>(pattern)}
{ }
~DeviceEntry();
DeviceEntry& operator=(const DeviceEntry&) = default;
DeviceEntry& operator=(DeviceEntry&&) = default;
};
DeviceEntry::~DeviceEntry() = default;
al::vector<DeviceEntry> PlaybackList;
std::vector<DeviceEntry> PlaybackList;
void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
void EnumerateDevices(jack_client_t *client, std::vector<DeviceEntry> &list)
{
std::remove_reference_t<decltype(list)>{}.swap(list);
if(JackPortsPtr ports{jack_get_ports(client, nullptr, JackDefaultAudioType, JackPortIsInput)})
if(JackPortsPtr ports{jack_get_ports(client, nullptr, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput)})
{
for(size_t i{0};ports[i];++i)
{
const char *sep{std::strchr(ports[i], ':')};
if(!sep || ports[i] == sep) continue;
const std::string_view portname{ports[i]};
const size_t seppos{portname.find(':')};
if(seppos == 0 || seppos >= portname.size())
continue;
const al::span<const char> portdev{ports[i], sep};
const auto portdev = portname.substr(0, seppos);
auto check_name = [portdev](const DeviceEntry &entry) -> bool
{
const size_t len{portdev.size()};
return entry.mName.length() == len
&& entry.mName.compare(0, len, portdev.data(), len) == 0;
};
{ return entry.mName == portdev; };
if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend())
continue;
std::string name{portdev.data(), portdev.size()};
list.emplace_back(name, name+":");
const auto &entry = list.back();
const auto &entry = list.emplace_back(portdev, std::string{portdev}+":");
TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
}
/* There are ports but couldn't get device names from them. Add a
@ -202,11 +203,11 @@ void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
if(ports[0] && list.empty())
{
WARN("No device names found in available ports, adding a generic name.\n");
list.emplace_back("JACK", "");
list.emplace_back("JACK"sv, ""sv);
}
}
if(auto listopt = ConfigValueStr(nullptr, "jack", "custom-devices"))
if(auto listopt = ConfigValueStr({}, "jack", "custom-devices"))
{
for(size_t strpos{0};strpos < listopt->size();)
{
@ -214,38 +215,32 @@ void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
size_t seppos{listopt->find('=', strpos)};
if(seppos >= nextpos || seppos == strpos)
{
const std::string entry{listopt->substr(strpos, nextpos-strpos)};
ERR("Invalid device entry: \"%s\"\n", entry.c_str());
const auto entry = std::string_view{*listopt}.substr(strpos, nextpos-strpos);
ERR("Invalid device entry: \"%.*s\"\n", al::sizei(entry), entry.data());
if(nextpos != std::string::npos) ++nextpos;
strpos = nextpos;
continue;
}
const al::span<const char> name{listopt->data()+strpos, seppos-strpos};
const al::span<const char> pattern{listopt->data()+(seppos+1),
std::min(nextpos, listopt->size())-(seppos+1)};
const auto name = std::string_view{*listopt}.substr(strpos, seppos-strpos);
const auto pattern = std::string_view{*listopt}.substr(seppos+1,
std::min(nextpos, listopt->size())-(seppos+1));
/* Check if this custom pattern already exists in the list. */
auto check_pattern = [pattern](const DeviceEntry &entry) -> bool
{
const size_t len{pattern.size()};
return entry.mPattern.length() == len
&& entry.mPattern.compare(0, len, pattern.data(), len) == 0;
};
{ return entry.mPattern == pattern; };
auto itemmatch = std::find_if(list.begin(), list.end(), check_pattern);
if(itemmatch != list.end())
{
/* If so, replace the name with this custom one. */
itemmatch->mName.assign(name.data(), name.size());
itemmatch->mName = name;
TRACE("Customized device name: %s = %s\n", itemmatch->mName.c_str(),
itemmatch->mPattern.c_str());
}
else
{
/* Otherwise, add a new device entry. */
list.emplace_back(std::string{name.data(), name.size()},
std::string{pattern.data(), pattern.size()});
const auto &entry = list.back();
const auto &entry = list.emplace_back(name, pattern);
TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
}
@ -295,7 +290,7 @@ struct JackPlayback final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -304,7 +299,7 @@ struct JackPlayback final : public BackendBase {
std::string mPortPattern;
jack_client_t *mClient{nullptr};
std::array<jack_port_t*,MAX_OUTPUT_CHANNELS> mPort{};
std::array<jack_port_t*,MaxOutputChannels> mPort{};
std::mutex mMutex;
@ -315,8 +310,6 @@ struct JackPlayback final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(JackPlayback)
};
JackPlayback::~JackPlayback()
@ -336,22 +329,22 @@ JackPlayback::~JackPlayback()
int JackPlayback::processRt(jack_nframes_t numframes) noexcept
{
std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
size_t numchans{0};
auto outptrs = std::array<jack_default_audio_sample_t*,MaxOutputChannels>{};
auto numchans = size_t{0};
for(auto port : mPort)
{
if(!port || numchans == mDevice->RealOut.Buffer.size())
break;
out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
outptrs[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
}
const auto dst = al::span{outptrs}.first(numchans);
if(mPlaying.load(std::memory_order_acquire)) LIKELY
mDevice->renderSamples({out.data(), numchans}, static_cast<uint>(numframes));
mDevice->renderSamples(dst, static_cast<uint>(numframes));
else
{
auto clear_buf = [numframes](float *outbuf) -> void
{ std::fill_n(outbuf, numframes, 0.0f); };
std::for_each(out.begin(), out.begin()+numchans, clear_buf);
std::for_each(dst.begin(), dst.end(), [numframes](float *outbuf) -> void
{ std::fill_n(outbuf, numframes, 0.0f); });
}
return 0;
@ -360,53 +353,46 @@ int JackPlayback::processRt(jack_nframes_t numframes) noexcept
int JackPlayback::process(jack_nframes_t numframes) noexcept
{
std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
std::array<al::span<float>,MaxOutputChannels> out;
size_t numchans{0};
for(auto port : mPort)
{
if(!port) break;
out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
out[numchans++] = {static_cast<float*>(jack_port_get_buffer(port, numframes)), numframes};
}
jack_nframes_t total{0};
size_t total{0};
if(mPlaying.load(std::memory_order_acquire)) LIKELY
{
auto data = mRing->getReadVector();
jack_nframes_t todo{minu(numframes, static_cast<uint>(data.first.len))};
auto write_first = [&data,numchans,todo](float *outbuf) -> float*
{
const float *RESTRICT in = reinterpret_cast<float*>(data.first.buf);
auto deinterlace_input = [&in,numchans]() noexcept -> float
{
float ret{*in};
in += numchans;
return ret;
};
std::generate_n(outbuf, todo, deinterlace_input);
data.first.buf += sizeof(float);
return outbuf + todo;
};
std::transform(out.begin(), out.begin()+numchans, out.begin(), write_first);
total += todo;
const auto update_size = size_t{mDevice->UpdateSize};
todo = minu(numframes-total, static_cast<uint>(data.second.len));
if(todo > 0)
const auto outlen = size_t{numframes / update_size};
const auto len1 = size_t{std::min(data.first.len/update_size, outlen)};
const auto len2 = size_t{std::min(data.second.len/update_size, outlen-len1)};
auto src = al::span{reinterpret_cast<float*>(data.first.buf), update_size*len1*numchans};
for(size_t i{0};i < len1;++i)
{
auto write_second = [&data,numchans,todo](float *outbuf) -> float*
for(size_t c{0};c < numchans;++c)
{
const float *RESTRICT in = reinterpret_cast<float*>(data.second.buf);
auto deinterlace_input = [&in,numchans]() noexcept -> float
{
float ret{*in};
in += numchans;
return ret;
};
std::generate_n(outbuf, todo, deinterlace_input);
data.second.buf += sizeof(float);
return outbuf + todo;
};
std::transform(out.begin(), out.begin()+numchans, out.begin(), write_second);
total += todo;
const auto iter = std::copy_n(src.begin(), update_size, out[c].begin());
out[c] = {iter, out[c].end()};
src = src.subspan(update_size);
}
total += update_size;
}
src = al::span{reinterpret_cast<float*>(data.second.buf), update_size*len2*numchans};
for(size_t i{0};i < len2;++i)
{
for(size_t c{0};c < numchans;++c)
{
const auto iter = std::copy_n(src.begin(), update_size, out[c].begin());
out[c] = {iter, out[c].end()};
src = src.subspan(update_size);
}
total += update_size;
}
mRing->readAdvance(total);
@ -415,8 +401,8 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept
if(numframes > total)
{
const jack_nframes_t todo{numframes - total};
auto clear_buf = [todo](float *outbuf) -> void { std::fill_n(outbuf, todo, 0.0f); };
auto clear_buf = [](const al::span<float> outbuf) -> void
{ std::fill(outbuf.begin(), outbuf.end(), 0.0f); };
std::for_each(out.begin(), out.begin()+numchans, clear_buf);
}
@ -426,45 +412,70 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept
int JackPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
const size_t frame_step{mDevice->channelsFromFmt()};
const auto update_size = uint{mDevice->UpdateSize};
const auto num_channels = size_t{mDevice->channelsFromFmt()};
auto outptrs = std::vector<float*>(num_channels);
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
if(mRing->writeSpace() < mDevice->UpdateSize)
if(mRing->writeSpace() < update_size)
{
mSem.wait();
continue;
}
auto data = mRing->getWriteVector();
size_t todo{data.first.len + data.second.len};
todo -= todo%mDevice->UpdateSize;
const auto len1 = size_t{data.first.len / update_size};
const auto len2 = size_t{data.second.len / update_size};
const auto len1 = static_cast<uint>(minz(data.first.len, todo));
const auto len2 = static_cast<uint>(minz(data.second.len, todo-len1));
std::lock_guard<std::mutex> _{mMutex};
mDevice->renderSamples(data.first.buf, len1, frame_step);
std::lock_guard<std::mutex> dlock{mMutex};
auto buffer = al::span{reinterpret_cast<float*>(data.first.buf),
data.first.len*num_channels};
auto bufiter = buffer.begin();
for(size_t i{0};i < len1;++i)
{
std::generate_n(outptrs.begin(), outptrs.size(), [&bufiter,update_size]
{
auto ret = al::to_address(bufiter);
bufiter += ptrdiff_t(update_size);
return ret;
});
mDevice->renderSamples(outptrs, update_size);
}
if(len2 > 0)
mDevice->renderSamples(data.second.buf, len2, frame_step);
mRing->writeAdvance(todo);
{
buffer = al::span{reinterpret_cast<float*>(data.second.buf),
data.second.len*num_channels};
bufiter = buffer.begin();
for(size_t i{0};i < len2;++i)
{
std::generate_n(outptrs.begin(), outptrs.size(), [&bufiter,update_size]
{
auto ret = al::to_address(bufiter);
bufiter += ptrdiff_t(update_size);
return ret;
});
mDevice->renderSamples(outptrs, update_size);
}
}
mRing->writeAdvance((len1+len2) * update_size);
}
return 0;
}
void JackPlayback::open(const char *name)
void JackPlayback::open(std::string_view name)
{
if(!mClient)
{
const PathNamePair &binname = GetProcBinary();
const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
jack_status_t status;
jack_status_t status{};
mClient = jack_client_open(client_name, ClientOptions, &status, nullptr);
if(mClient == nullptr)
throw al::backend_exception{al::backend_error::DeviceError,
@ -481,9 +492,9 @@ void JackPlayback::open(const char *name)
if(PlaybackList.empty())
EnumerateDevices(mClient, PlaybackList);
if(!name && !PlaybackList.empty())
if(name.empty() && !PlaybackList.empty())
{
name = PlaybackList[0].mName.c_str();
name = PlaybackList[0].mName;
mPortPattern = PlaybackList[0].mPattern;
}
else
@ -493,14 +504,10 @@ void JackPlayback::open(const char *name)
auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name);
if(iter == PlaybackList.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name?name:""};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
mPortPattern = iter->mPattern;
}
mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", true);
jack_set_process_callback(mClient,
mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this);
mDevice->DeviceName = name;
}
@ -511,6 +518,10 @@ bool JackPlayback::reset()
std::for_each(mPort.begin(), mPort.end(), unregister_port);
mPort.fill(nullptr);
mRTMixing = GetConfigValueBool(mDevice->DeviceName, "jack", "rt-mix", true);
jack_set_process_callback(mClient,
mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this);
/* Ignore the requested buffer metrics and just keep one JACK-sized buffer
* ready for when requested.
*/
@ -525,9 +536,9 @@ bool JackPlayback::reset()
}
else
{
const char *devname{mDevice->DeviceName.c_str()};
const std::string_view devname{mDevice->DeviceName};
uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize);
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
}
@ -535,27 +546,27 @@ bool JackPlayback::reset()
mDevice->FmtType = DevFmtFloat;
int port_num{0};
auto ports_end = mPort.begin() + mDevice->channelsFromFmt();
auto bad_port = mPort.begin();
while(bad_port != ports_end)
auto ports = al::span{mPort}.first(mDevice->channelsFromFmt());
auto bad_port = ports.begin();
while(bad_port != ports.end())
{
std::string name{"channel_" + std::to_string(++port_num)};
*bad_port = jack_port_register(mClient, name.c_str(), JackDefaultAudioType,
*bad_port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput | JackPortIsTerminal, 0);
if(!*bad_port) break;
++bad_port;
}
if(bad_port != ports_end)
if(bad_port != ports.end())
{
ERR("Failed to register enough JACK ports for %s output\n",
DevFmtChannelsString(mDevice->FmtChans));
if(bad_port == mPort.begin()) return false;
if(bad_port == ports.begin()) return false;
if(bad_port == mPort.begin()+1)
if(bad_port == ports.begin()+1)
mDevice->FmtChans = DevFmtMono;
else
{
ports_end = mPort.begin()+2;
const auto ports_end = ports.begin()+2;
while(bad_port != ports_end)
{
jack_port_unregister(mClient, *(--bad_port));
@ -575,10 +586,10 @@ void JackPlayback::start()
if(jack_activate(mClient))
throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"};
const char *devname{mDevice->DeviceName.c_str()};
const std::string_view devname{mDevice->DeviceName};
if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true))
{
JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JackDefaultAudioType,
JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JACK_DEFAULT_AUDIO_TYPE,
JackPortIsInput)};
if(!pnames)
{
@ -586,7 +597,7 @@ void JackPlayback::start()
throw al::backend_exception{al::backend_error::DeviceError, "No playback ports found"};
}
for(size_t i{0};i < al::size(mPort) && mPort[i];++i)
for(size_t i{0};i < std::size(mPort) && mPort[i];++i)
{
if(!pnames[i])
{
@ -613,7 +624,7 @@ void JackPlayback::start()
else
{
uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize);
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true);
@ -651,10 +662,9 @@ void JackPlayback::stop()
ClockLatency JackPlayback::getClockLatency()
{
ClockLatency ret;
std::lock_guard<std::mutex> _{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice);
std::lock_guard<std::mutex> dlock{mMutex};
ClockLatency ret{};
ret.ClockTime = mDevice->getClockTime();
ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->UpdateSize};
ret.Latency /= mDevice->Frequency;
@ -674,7 +684,7 @@ bool JackBackendFactory::init()
if(!jack_load())
return false;
if(!GetConfigValueBool(nullptr, "jack", "spawn-server", false))
if(!GetConfigValueBool({}, "jack", "spawn-server", false))
ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer);
const PathNamePair &binname = GetProcBinary();
@ -682,7 +692,7 @@ bool JackBackendFactory::init()
void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr};
jack_set_error_function(jack_msg_handler);
jack_status_t status;
jack_status_t status{};
jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)};
jack_set_error_function(old_error_cb);
if(!client)
@ -700,18 +710,15 @@ bool JackBackendFactory::init()
bool JackBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback); }
std::string JackBackendFactory::probe(BackendType type)
auto JackBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
auto append_name = [&outnames](const DeviceEntry &entry) -> void
{
/* Includes null char. */
outnames.append(entry.mName.c_str(), entry.mName.length()+1);
};
{ outnames.emplace_back(entry.mName); };
const PathNamePair &binname = GetProcBinary();
const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
jack_status_t status;
jack_status_t status{};
switch(type)
{
case BackendType::Playback:
@ -722,6 +729,7 @@ std::string JackBackendFactory::probe(BackendType type)
}
else
WARN("jack_client_open() failed, 0x%02x\n", status);
outnames.reserve(PlaybackList.size());
std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
break;
case BackendType::Capture:

View file

@ -5,15 +5,15 @@
struct JackBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_JACK_H */

View file

@ -30,16 +30,14 @@ namespace {
struct LoopbackBackend final : public BackendBase {
LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { }
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
DEF_NEWDEL(LoopbackBackend)
};
void LoopbackBackend::open(const char *name)
void LoopbackBackend::open(std::string_view name)
{
mDevice->DeviceName = name;
}
@ -65,8 +63,8 @@ bool LoopbackBackendFactory::init()
bool LoopbackBackendFactory::querySupport(BackendType)
{ return true; }
std::string LoopbackBackendFactory::probe(BackendType)
{ return std::string{}; }
auto LoopbackBackendFactory::enumerate(BackendType) -> std::vector<std::string>
{ return {}; }
BackendPtr LoopbackBackendFactory::createBackend(DeviceBase *device, BackendType)
{ return BackendPtr{new LoopbackBackend{device}}; }

View file

@ -5,15 +5,15 @@
struct LoopbackBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_LOOPBACK_H */

View file

@ -30,10 +30,11 @@
#include <functional>
#include <thread>
#include "core/device.h"
#include "almalloc.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "threads.h"
namespace {
@ -41,8 +42,9 @@ namespace {
using std::chrono::seconds;
using std::chrono::milliseconds;
using std::chrono::nanoseconds;
using namespace std::string_view_literals;
constexpr char nullDevice[] = "No Output";
[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "No Output"sv; }
struct NullBackend final : public BackendBase {
@ -50,15 +52,13 @@ struct NullBackend final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(NullBackend)
};
int NullBackend::mixerProc()
@ -66,7 +66,7 @@ int NullBackend::mixerProc()
const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2};
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
int64_t done{0};
auto start = std::chrono::steady_clock::now();
@ -105,13 +105,13 @@ int NullBackend::mixerProc()
}
void NullBackend::open(const char *name)
void NullBackend::open(std::string_view name)
{
if(!name)
name = nullDevice;
else if(strcmp(name, nullDevice) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDeviceName();
else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
mDevice->DeviceName = name;
}
@ -150,19 +150,17 @@ bool NullBackendFactory::init()
bool NullBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback); }
std::string NullBackendFactory::probe(BackendType type)
auto NullBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
switch(type)
{
case BackendType::Playback:
/* Includes null char. */
outnames.append(nullDevice, sizeof(nullDevice));
break;
/* Include null char. */
return std::vector{std::string{GetDeviceName()}};
case BackendType::Capture:
break;
}
return outnames;
return {};
}
BackendPtr NullBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct NullBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_NULL_H */

View file

@ -4,10 +4,11 @@
#include "oboe.h"
#include <cassert>
#include <cstdint>
#include <cstring>
#include <stdint.h>
#include "alnumeric.h"
#include "alstring.h"
#include "core/device.h"
#include "core/logging.h"
#include "ringbuffer.h"
@ -17,7 +18,9 @@
namespace {
constexpr char device_name[] = "Oboe Default";
using namespace std::string_view_literals;
[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "Oboe Default"sv; }
struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback {
@ -28,7 +31,9 @@ struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
int32_t numFrames) override;
void open(const char *name) override;
void onErrorAfterClose(oboe::AudioStream* /* audioStream */, oboe::Result /* error */) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -46,14 +51,20 @@ oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStrea
return oboe::DataCallbackResult::Continue;
}
void OboePlayback::open(const char *name)
void OboePlayback::onErrorAfterClose(oboe::AudioStream*, oboe::Result error)
{
if(!name)
name = device_name;
else if(std::strcmp(name, device_name) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(error == oboe::Result::ErrorDisconnected)
mDevice->handleDisconnect("Oboe AudioStream was disconnected: %s", oboe::convertToText(error));
TRACE("Error was %s", oboe::convertToText(error));
}
void OboePlayback::open(std::string_view name)
{
if(name.empty())
name = GetDeviceName();
else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
/* Open a basic output stream, just to ensure it can work. */
oboe::ManagedStream stream;
@ -72,6 +83,7 @@ bool OboePlayback::reset()
oboe::AudioStreamBuilder builder;
builder.setDirection(oboe::Direction::Output);
builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
builder.setUsage(oboe::Usage::Game);
/* Don't let Oboe convert. We should be able to handle anything it gives
* back.
*/
@ -135,7 +147,7 @@ bool OboePlayback::reset()
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
oboe::convertToText(result)};
mStream->setBufferSizeInFrames(mini(static_cast<int32_t>(mDevice->BufferSize),
mStream->setBufferSizeInFrames(std::min(static_cast<int32_t>(mDevice->BufferSize),
mStream->getBufferCapacityInFrames()));
TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
@ -164,6 +176,9 @@ bool OboePlayback::reset()
mDevice->FmtType = DevFmtInt;
break;
case oboe::AudioFormat::I24:
#endif
#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 8)
case oboe::AudioFormat::IEC61937:
#endif
case oboe::AudioFormat::Unspecified:
case oboe::AudioFormat::Invalid:
@ -177,9 +192,9 @@ bool OboePlayback::reset()
* FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum
* update size.
*/
mDevice->UpdateSize = maxu(mDevice->Frequency / 100,
mDevice->UpdateSize = std::max(mDevice->Frequency/100u,
static_cast<uint32_t>(mStream->getFramesPerBurst()));
mDevice->BufferSize = maxu(mDevice->UpdateSize * 2,
mDevice->BufferSize = std::max(mDevice->UpdateSize*2u,
static_cast<uint32_t>(mStream->getBufferSizeInFrames()));
return true;
@ -197,8 +212,7 @@ void OboePlayback::stop()
{
oboe::Result result{mStream->stop()};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
oboe::convertToText(result)};
ERR("Failed to stop stream: %s\n", oboe::convertToText(result));
}
@ -212,28 +226,28 @@ struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
int32_t numFrames) override;
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
};
oboe::DataCallbackResult OboeCapture::onAudioReady(oboe::AudioStream*, void *audioData,
int32_t numFrames)
{
mRing->write(audioData, static_cast<uint32_t>(numFrames));
std::ignore = mRing->write(audioData, static_cast<uint32_t>(numFrames));
return oboe::DataCallbackResult::Continue;
}
void OboeCapture::open(const char *name)
void OboeCapture::open(std::string_view name)
{
if(!name)
name = device_name;
else if(std::strcmp(name, device_name) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDeviceName();
else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
oboe::AudioStreamBuilder builder;
builder.setDirection(oboe::Direction::Input)
@ -259,6 +273,7 @@ void OboeCapture::open(const char *name)
case DevFmtX61:
case DevFmtX71:
case DevFmtX714:
case DevFmtX7144:
case DevFmtX3D71:
case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
@ -297,7 +312,7 @@ void OboeCapture::open(const char *name)
TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
/* Ensure a minimum ringbuffer size of 100ms. */
mRing = RingBuffer::Create(maxu(mDevice->BufferSize, mDevice->Frequency/10),
mRing = RingBuffer::Create(std::max(mDevice->BufferSize, mDevice->Frequency/10u),
static_cast<uint32_t>(mStream->getBytesPerFrame()), false);
mDevice->DeviceName = name;
@ -315,15 +330,14 @@ void OboeCapture::stop()
{
const oboe::Result result{mStream->stop()};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
oboe::convertToText(result)};
ERR("Failed to stop stream: %s\n", oboe::convertToText(result));
}
uint OboeCapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()); }
void OboeCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
void OboeCapture::captureSamples(std::byte *buffer, uint samples)
{ std::ignore = mRing->read(buffer, samples); }
} // namespace
@ -332,16 +346,15 @@ bool OboeBackendFactory::init() { return true; }
bool OboeBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
std::string OboeBackendFactory::probe(BackendType type)
auto OboeBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
switch(type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
return std::string{device_name, sizeof(device_name)};
return std::vector{std::string{GetDeviceName()}};
}
return std::string{};
return {};
}
BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct OboeBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_OBOE_H */

View file

@ -23,23 +23,26 @@
#include "opensl.h"
#include <stdlib.h>
#include <jni.h>
#include <new>
#include <array>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <new>
#include <thread>
#include <functional>
#include "albit.h"
#include "alnumeric.h"
#include "alsem.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "opthelpers.h"
#include "ringbuffer.h"
#include "threads.h"
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
@ -48,15 +51,17 @@
namespace {
using namespace std::string_view_literals;
/* Helper macros */
#define EXTRACT_VCALL_ARGS(...) __VA_ARGS__))
#define VCALL(obj, func) ((*(obj))->func((obj), EXTRACT_VCALL_ARGS
#define VCALL0(obj, func) ((*(obj))->func((obj) EXTRACT_VCALL_ARGS
constexpr char opensl_device[] = "OpenSL";
[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "OpenSL"sv; }
[[nodiscard]]
constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept
{
switch(chans)
@ -80,6 +85,7 @@ constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept
SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT |
SL_SPEAKER_TOP_FRONT_LEFT | SL_SPEAKER_TOP_FRONT_RIGHT | SL_SPEAKER_TOP_BACK_LEFT |
SL_SPEAKER_TOP_BACK_RIGHT;
case DevFmtX7144:
case DevFmtAmbi3D:
break;
}
@ -159,12 +165,10 @@ struct OpenSLPlayback final : public BackendBase {
~OpenSLPlayback() override;
void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
{ static_cast<OpenSLPlayback*>(context)->process(bq); }
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -189,8 +193,6 @@ struct OpenSLPlayback final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(OpenSLPlayback)
};
OpenSLPlayback::~OpenSLPlayback()
@ -229,7 +231,7 @@ void OpenSLPlayback::process(SLAndroidSimpleBufferQueueItf) noexcept
int OpenSLPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
SLPlayItf player;
SLAndroidSimpleBufferQueueItf bufferQueue;
@ -312,13 +314,13 @@ int OpenSLPlayback::mixerProc()
}
void OpenSLPlayback::open(const char *name)
void OpenSLPlayback::open(std::string_view name)
{
if(!name)
name = opensl_device;
else if(strcmp(name, opensl_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDeviceName();
else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
/* There's only one device, so if it's already open, there's nothing to do. */
if(mEngineObj) return;
@ -375,74 +377,6 @@ bool OpenSLPlayback::reset()
mRing = nullptr;
#if 0
if(!mDevice->Flags.get<FrequencyRequest>())
{
/* FIXME: Disabled until I figure out how to get the Context needed for
* the getSystemService call.
*/
JNIEnv *env = Android_GetJNIEnv();
jobject jctx = Android_GetContext();
/* Get necessary stuff for using java.lang.Integer,
* android.content.Context, and android.media.AudioManager.
*/
jclass int_cls = JCALL(env,FindClass)("java/lang/Integer");
jmethodID int_parseint = JCALL(env,GetStaticMethodID)(int_cls,
"parseInt", "(Ljava/lang/String;)I"
);
TRACE("Integer: %p, parseInt: %p\n", int_cls, int_parseint);
jclass ctx_cls = JCALL(env,FindClass)("android/content/Context");
jfieldID ctx_audsvc = JCALL(env,GetStaticFieldID)(ctx_cls,
"AUDIO_SERVICE", "Ljava/lang/String;"
);
jmethodID ctx_getSysSvc = JCALL(env,GetMethodID)(ctx_cls,
"getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"
);
TRACE("Context: %p, AUDIO_SERVICE: %p, getSystemService: %p\n",
ctx_cls, ctx_audsvc, ctx_getSysSvc);
jclass audmgr_cls = JCALL(env,FindClass)("android/media/AudioManager");
jfieldID audmgr_prop_out_srate = JCALL(env,GetStaticFieldID)(audmgr_cls,
"PROPERTY_OUTPUT_SAMPLE_RATE", "Ljava/lang/String;"
);
jmethodID audmgr_getproperty = JCALL(env,GetMethodID)(audmgr_cls,
"getProperty", "(Ljava/lang/String;)Ljava/lang/String;"
);
TRACE("AudioManager: %p, PROPERTY_OUTPUT_SAMPLE_RATE: %p, getProperty: %p\n",
audmgr_cls, audmgr_prop_out_srate, audmgr_getproperty);
const char *strchars;
jstring strobj;
/* Now make the calls. */
//AudioManager audMgr = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
strobj = JCALL(env,GetStaticObjectField)(ctx_cls, ctx_audsvc);
jobject audMgr = JCALL(env,CallObjectMethod)(jctx, ctx_getSysSvc, strobj);
strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr);
TRACE("Context.getSystemService(%s) = %p\n", strchars, audMgr);
JCALL(env,ReleaseStringUTFChars)(strobj, strchars);
//String srateStr = audMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
strobj = JCALL(env,GetStaticObjectField)(audmgr_cls, audmgr_prop_out_srate);
jstring srateStr = JCALL(env,CallObjectMethod)(audMgr, audmgr_getproperty, strobj);
strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr);
TRACE("audMgr.getProperty(%s) = %p\n", strchars, srateStr);
JCALL(env,ReleaseStringUTFChars)(strobj, strchars);
//int sampleRate = Integer.parseInt(srateStr);
sampleRate = JCALL(env,CallStaticIntMethod)(int_cls, int_parseint, srateStr);
strchars = JCALL(env,GetStringUTFChars)(srateStr, nullptr);
TRACE("Got system sample rate %uhz (%s)\n", sampleRate, strchars);
JCALL(env,ReleaseStringUTFChars)(srateStr, strchars);
if(!sampleRate) sampleRate = device->Frequency;
else sampleRate = maxu(sampleRate, MIN_OUTPUT_RATE);
}
#endif
mDevice->FmtChans = DevFmtStereo;
mDevice->FmtType = DevFmtShort;
@ -564,7 +498,9 @@ void OpenSLPlayback::start()
PrintErr(result, "bufferQueue->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this);
result = VCALL(bufferQueue,RegisterCallback)(
[](SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
{ static_cast<OpenSLPlayback*>(context)->process(bq); }, this);
PrintErr(result, "bufferQueue->RegisterCallback");
}
if(SL_RESULT_SUCCESS != result)
@ -628,8 +564,8 @@ ClockLatency OpenSLPlayback::getClockLatency()
{
ClockLatency ret;
std::lock_guard<std::mutex> _{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice);
std::lock_guard<std::mutex> dlock{mMutex};
ret.ClockTime = mDevice->getClockTime();
ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize};
ret.Latency /= mDevice->Frequency;
@ -642,13 +578,11 @@ struct OpenSLCapture final : public BackendBase {
~OpenSLCapture() override;
void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
{ static_cast<OpenSLCapture*>(context)->process(bq); }
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
/* engine interfaces */
@ -662,8 +596,6 @@ struct OpenSLCapture final : public BackendBase {
uint mSplOffset{0u};
uint mFrameSize{0};
DEF_NEWDEL(OpenSLCapture)
};
OpenSLCapture::~OpenSLCapture()
@ -686,13 +618,13 @@ void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf) noexcept
}
void OpenSLCapture::open(const char* name)
void OpenSLCapture::open(std::string_view name)
{
if(!name)
name = opensl_device;
else if(strcmp(name, opensl_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDeviceName();
else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
PrintErr(result, "slCreateEngine");
@ -710,10 +642,10 @@ void OpenSLCapture::open(const char* name)
{
mFrameSize = mDevice->frameSizeFromFmt();
/* Ensure the total length is at least 100ms */
uint length{maxu(mDevice->BufferSize, mDevice->Frequency/10)};
uint length{std::max(mDevice->BufferSize, mDevice->Frequency/10u)};
/* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */
uint update_len{clampu(mDevice->BufferSize/3, mDevice->Frequency/100,
mDevice->Frequency/100*5)};
uint update_len{std::clamp(mDevice->BufferSize/3u, mDevice->Frequency/100u,
mDevice->Frequency/100u*5u)};
uint num_updates{(length+update_len-1) / update_len};
mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false);
@ -813,13 +745,15 @@ void OpenSLCapture::open(const char* name)
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(bufferQueue,RegisterCallback)(&OpenSLCapture::processC, this);
result = VCALL(bufferQueue,RegisterCallback)(
[](SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
{ static_cast<OpenSLCapture*>(context)->process(bq); }, this);
PrintErr(result, "bufferQueue->RegisterCallback");
}
if(SL_RESULT_SUCCESS == result)
{
const uint chunk_size{mDevice->UpdateSize * mFrameSize};
const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::byte{0};
const auto silence = (mDevice->FmtType == DevFmtUByte) ? std::byte{0x80} : std::byte{0};
auto data = mRing->getWriteVector();
std::fill_n(data.first.buf, data.first.len*chunk_size, silence);
@ -883,7 +817,7 @@ void OpenSLCapture::stop()
}
}
void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
void OpenSLCapture::captureSamples(std::byte *buffer, uint samples)
{
const uint update_size{mDevice->UpdateSize};
const uint chunk_size{update_size * mFrameSize};
@ -895,7 +829,7 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
auto rdata = mRing->getReadVector();
for(uint i{0};i < samples;)
{
const uint rem{minu(samples - i, update_size - mSplOffset)};
const uint rem{std::min(samples - i, update_size - mSplOffset)};
std::copy_n(rdata.first.buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize},
buffer + i*size_t{mFrameSize});
@ -932,7 +866,7 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
return;
/* For each buffer chunk that was fully read, queue another writable buffer
* chunk to keep the OpenSL queue full. This is rather convulated, as a
* chunk to keep the OpenSL queue full. This is rather convoluted, as a
* result of the ring buffer holding more elements than are writable at a
* given time. The end of the write vector increments when the read pointer
* advances, which will "expose" a previously unwritable element. So for
@ -975,18 +909,15 @@ bool OSLBackendFactory::init() { return true; }
bool OSLBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string OSLBackendFactory::probe(BackendType type)
auto OSLBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
switch(type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
outnames.append(opensl_device, sizeof(opensl_device));
break;
return std::vector{std::string{GetDeviceName()}};
}
return outnames;
return {};
}
BackendPtr OSLBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct OSLBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_OSL_H */

View file

@ -31,27 +31,26 @@
#include <algorithm>
#include <atomic>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <exception>
#include <functional>
#include <memory>
#include <new>
#include <string>
#include <string_view>
#include <system_error>
#include <thread>
#include <utility>
#include <vector>
#include "albyte.h"
#include "alc/alconfig.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "aloptional.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "ringbuffer.h"
#include "threads.h"
#include "vector.h"
#include <sys/soundcard.h>
@ -83,117 +82,148 @@
namespace {
constexpr char DefaultName[] = "OSS Default";
std::string DefaultPlayback{"/dev/dsp"};
std::string DefaultCapture{"/dev/dsp"};
using namespace std::string_literals;
using namespace std::string_view_literals;
[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "OSS Default"sv; }
std::string DefaultPlayback{"/dev/dsp"s};
std::string DefaultCapture{"/dev/dsp"s};
struct DevMap {
std::string name;
std::string device_name;
template<typename T, typename U>
DevMap(T&& name_, U&& devname_)
: name{std::forward<T>(name_)}, device_name{std::forward<U>(devname_)}
{ }
};
al::vector<DevMap> PlaybackDevices;
al::vector<DevMap> CaptureDevices;
std::vector<DevMap> PlaybackDevices;
std::vector<DevMap> CaptureDevices;
#ifdef ALC_OSS_COMPAT
#define DSP_CAP_OUTPUT 0x00020000
#define DSP_CAP_INPUT 0x00010000
void ALCossListPopulate(al::vector<DevMap> &devlist, int type)
void ALCossListPopulate(std::vector<DevMap> &devlist, int type)
{
devlist.emplace_back(DevMap{DefaultName, (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback});
devlist.emplace_back(GetDefaultName(), (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback);
}
#else
void ALCossListAppend(al::vector<DevMap> &list, al::span<const char> handle, al::span<const char> path)
class FileHandle {
int mFd{-1};
public:
FileHandle() = default;
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
~FileHandle() { if(mFd != -1) ::close(mFd); }
template<typename ...Args>
[[nodiscard]] auto open(const char *fname, Args&& ...args) -> bool
{
close();
mFd = ::open(fname, std::forward<Args>(args)...);
return mFd != -1;
}
void close()
{
if(mFd != -1)
::close(mFd);
mFd = -1;
}
[[nodiscard]]
auto get() const noexcept -> int { return mFd; }
};
void ALCossListAppend(std::vector<DevMap> &list, std::string_view handle, std::string_view path)
{
#ifdef ALC_OSS_DEVNODE_TRUC
for(size_t i{0};i < path.size();++i)
{
if(path[i] == '.' && handle.size() + i >= path.size())
if(path[i] == '.' && handle.size() >= path.size() - i)
{
const size_t hoffset{handle.size() + i - path.size()};
if(strncmp(path.data() + i, handle.data() + hoffset, path.size() - i) == 0)
handle = handle.first(hoffset);
path = path.first(i);
handle = handle.substr(0, hoffset);
path = path.substr(0, i);
}
}
#endif
if(handle.empty())
handle = path;
std::string basename{handle.data(), handle.size()};
std::string devname{path.data(), path.size()};
auto match_devname = [&devname](const DevMap &entry) -> bool
{ return entry.device_name == devname; };
auto match_devname = [path](const DevMap &entry) -> bool
{ return entry.device_name == path; };
if(std::find_if(list.cbegin(), list.cend(), match_devname) != list.cend())
return;
auto checkName = [&list](const std::string &name) -> bool
auto checkName = [&list](const std::string_view name) -> bool
{
auto match_name = [&name](const DevMap &entry) -> bool { return entry.name == name; };
auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name; };
return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
};
int count{1};
std::string newname{basename};
std::string newname{handle};
while(checkName(newname))
{
newname = basename;
newname = handle;
newname += " #";
newname += std::to_string(++count);
}
list.emplace_back(DevMap{std::move(newname), std::move(devname)});
const DevMap &entry = list.back();
const DevMap &entry = list.emplace_back(std::move(newname), path);
TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
}
void ALCossListPopulate(al::vector<DevMap> &devlist, int type_flag)
void ALCossListPopulate(std::vector<DevMap> &devlist, int type_flag)
{
int fd{open("/dev/mixer", O_RDONLY)};
if(fd < 0)
oss_sysinfo si{};
FileHandle file;
if(!file.open("/dev/mixer", O_RDONLY))
{
TRACE("Could not open /dev/mixer: %s\n", strerror(errno));
TRACE("Could not open /dev/mixer: %s\n", std::generic_category().message(errno).c_str());
goto done;
}
oss_sysinfo si;
if(ioctl(fd, SNDCTL_SYSINFO, &si) == -1)
if(ioctl(file.get(), SNDCTL_SYSINFO, &si) == -1)
{
TRACE("SNDCTL_SYSINFO failed: %s\n", strerror(errno));
TRACE("SNDCTL_SYSINFO failed: %s\n", std::generic_category().message(errno).c_str());
goto done;
}
for(int i{0};i < si.numaudios;i++)
{
oss_audioinfo ai;
oss_audioinfo ai{};
ai.dev = i;
if(ioctl(fd, SNDCTL_AUDIOINFO, &ai) == -1)
if(ioctl(file.get(), SNDCTL_AUDIOINFO, &ai) == -1)
{
ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i, strerror(errno));
ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i,
std::generic_category().message(errno).c_str());
continue;
}
if(!(ai.caps&type_flag) || ai.devnode[0] == '\0')
continue;
al::span<const char> handle;
std::string_view handle;
if(ai.handle[0] != '\0')
handle = {ai.handle, strnlen(ai.handle, sizeof(ai.handle))};
else
handle = {ai.name, strnlen(ai.name, sizeof(ai.name))};
al::span<const char> devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))};
const std::string_view devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))};
ALCossListAppend(devlist, handle, devnode);
}
done:
if(fd >= 0)
close(fd);
fd = -1;
file.close();
const char *defdev{((type_flag==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback).c_str()};
auto iter = std::find_if(devlist.cbegin(), devlist.cend(),
@ -201,7 +231,7 @@ done:
{ return entry.device_name == defdev; }
);
if(iter == devlist.cend())
devlist.insert(devlist.begin(), DevMap{DefaultName, defdev});
devlist.insert(devlist.begin(), DevMap{GetDefaultName(), defdev});
else
{
DevMap entry{std::move(*iter)};
@ -231,19 +261,17 @@ struct OSSPlayback final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
int mFd{-1};
al::vector<al::byte> mMixData;
std::vector<std::byte> mMixData;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(OSSPlayback)
};
OSSPlayback::~OSSPlayback()
@ -257,7 +285,7 @@ OSSPlayback::~OSSPlayback()
int OSSPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
const size_t frame_step{mDevice->channelsFromFmt()};
const size_t frame_size{mDevice->frameSizeFromFmt()};
@ -269,38 +297,38 @@ int OSSPlayback::mixerProc()
pollitem.fd = mFd;
pollitem.events = POLLOUT;
int pret{poll(&pollitem, 1, 1000)};
if(pret < 0)
if(int pret{poll(&pollitem, 1, 1000)}; pret < 0)
{
if(errno == EINTR || errno == EAGAIN)
continue;
ERR("poll failed: %s\n", strerror(errno));
mDevice->handleDisconnect("Failed waiting for playback buffer: %s", strerror(errno));
const auto errstr = std::generic_category().message(errno);
ERR("poll failed: %s\n", errstr.c_str());
mDevice->handleDisconnect("Failed waiting for playback buffer: %s", errstr.c_str());
break;
}
else if(pret == 0)
else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */
{
WARN("poll timeout\n");
continue;
}
al::byte *write_ptr{mMixData.data()};
size_t to_write{mMixData.size()};
mDevice->renderSamples(write_ptr, static_cast<uint>(to_write/frame_size), frame_step);
while(to_write > 0 && !mKillNow.load(std::memory_order_acquire))
al::span write_buf{mMixData};
mDevice->renderSamples(write_buf.data(), static_cast<uint>(write_buf.size()/frame_size),
frame_step);
while(!write_buf.empty() && !mKillNow.load(std::memory_order_acquire))
{
ssize_t wrote{write(mFd, write_ptr, to_write)};
ssize_t wrote{write(mFd, write_buf.data(), write_buf.size())};
if(wrote < 0)
{
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
continue;
ERR("write failed: %s\n", strerror(errno));
mDevice->handleDisconnect("Failed writing playback samples: %s", strerror(errno));
const auto errstr = std::generic_category().message(errno);
ERR("write failed: %s\n", errstr.c_str());
mDevice->handleDisconnect("Failed writing playback samples: %s", errstr.c_str());
break;
}
to_write -= static_cast<size_t>(wrote);
write_ptr += wrote;
write_buf = write_buf.subspan(static_cast<size_t>(wrote));
}
}
@ -308,11 +336,11 @@ int OSSPlayback::mixerProc()
}
void OSSPlayback::open(const char *name)
void OSSPlayback::open(std::string_view name)
{
const char *devname{DefaultPlayback.c_str()};
if(!name)
name = DefaultName;
if(name.empty())
name = GetDefaultName();
else
{
if(PlaybackDevices.empty())
@ -324,14 +352,14 @@ void OSSPlayback::open(const char *name)
);
if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
devname = iter->device_name.c_str();
}
int fd{::open(devname, O_WRONLY)};
if(fd == -1)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
strerror(errno)};
std::generic_category().message(errno).c_str()};
if(mFd != -1)
::close(mFd);
@ -367,15 +395,14 @@ bool OSSPlayback::reset()
uint ossSpeed{mDevice->Frequency};
uint frameSize{numChannels * mDevice->bytesFromFmt()};
/* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */
uint log2FragmentSize{maxu(log2i(mDevice->UpdateSize*frameSize), 4)};
uint log2FragmentSize{std::max(log2i(mDevice->UpdateSize*frameSize), 4u)};
uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
audio_buf_info info{};
const char *err;
#define CHECKERR(func) if((func) < 0) { \
err = #func; \
goto err; \
}
#define CHECKERR(func) if((func) < 0) \
throw al::backend_exception{al::backend_error::DeviceError, "%s failed: %s\n", #func, \
std::generic_category().message(errno).c_str()};
/* Don't fail if SETFRAGMENT fails. We can handle just about anything
* that's reported back via GETOSPACE */
ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize);
@ -383,12 +410,6 @@ bool OSSPlayback::reset()
CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels));
CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed));
CHECKERR(ioctl(mFd, SNDCTL_DSP_GETOSPACE, &info));
if(0)
{
err:
ERR("%s failed: %s\n", err, strerror(errno));
return false;
}
#undef CHECKERR
if(mDevice->channelsFromFmt() != numChannels)
@ -413,7 +434,7 @@ bool OSSPlayback::reset()
setDefaultChannelOrder();
mMixData.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
mMixData.resize(size_t{mDevice->UpdateSize} * mDevice->frameSizeFromFmt());
return true;
}
@ -437,7 +458,7 @@ void OSSPlayback::stop()
mThread.join();
if(ioctl(mFd, SNDCTL_DSP_RESET) != 0)
ERR("Error resetting device: %s\n", strerror(errno));
ERR("Error resetting device: %s\n", std::generic_category().message(errno).c_str());
}
@ -447,10 +468,10 @@ struct OSScapture final : public BackendBase {
int recordProc();
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
int mFd{-1};
@ -459,8 +480,6 @@ struct OSScapture final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(OSScapture)
};
OSScapture::~OSScapture()
@ -474,7 +493,7 @@ OSScapture::~OSScapture()
int OSScapture::recordProc()
{
SetRTPriority();
althrd_setname(RECORD_THREAD_NAME);
althrd_setname(GetRecordThreadName());
const size_t frame_size{mDevice->frameSizeFromFmt()};
while(!mKillNow.load(std::memory_order_acquire))
@ -483,16 +502,16 @@ int OSScapture::recordProc()
pollitem.fd = mFd;
pollitem.events = POLLIN;
int sret{poll(&pollitem, 1, 1000)};
if(sret < 0)
if(int pret{poll(&pollitem, 1, 1000)}; pret < 0)
{
if(errno == EINTR || errno == EAGAIN)
continue;
ERR("poll failed: %s\n", strerror(errno));
mDevice->handleDisconnect("Failed to check capture samples: %s", strerror(errno));
const auto errstr = std::generic_category().message(errno);
ERR("poll failed: %s\n", errstr.c_str());
mDevice->handleDisconnect("Failed to check capture samples: %s", errstr.c_str());
break;
}
else if(sret == 0)
else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */
{
WARN("poll timeout\n");
continue;
@ -504,8 +523,9 @@ int OSScapture::recordProc()
ssize_t amt{read(mFd, vec.first.buf, vec.first.len*frame_size)};
if(amt < 0)
{
ERR("read failed: %s\n", strerror(errno));
mDevice->handleDisconnect("Failed reading capture samples: %s", strerror(errno));
const auto errstr = std::generic_category().message(errno);
ERR("read failed: %s\n", errstr.c_str());
mDevice->handleDisconnect("Failed reading capture samples: %s", errstr.c_str());
break;
}
mRing->writeAdvance(static_cast<size_t>(amt)/frame_size);
@ -516,11 +536,11 @@ int OSScapture::recordProc()
}
void OSScapture::open(const char *name)
void OSScapture::open(std::string_view name)
{
const char *devname{DefaultCapture.c_str()};
if(!name)
name = DefaultName;
if(name.empty())
name = GetDefaultName();
else
{
if(CaptureDevices.empty())
@ -532,14 +552,14 @@ void OSScapture::open(const char *name)
);
if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
devname = iter->device_name.c_str();
}
mFd = ::open(devname, O_RDONLY);
if(mFd == -1)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
strerror(errno)};
std::generic_category().message(errno).c_str()};
int ossFormat{};
switch(mDevice->FmtType)
@ -566,13 +586,13 @@ void OSScapture::open(const char *name)
uint frameSize{numChannels * mDevice->bytesFromFmt()};
uint ossSpeed{mDevice->Frequency};
/* according to the OSS spec, 16 bytes are the minimum */
uint log2FragmentSize{maxu(log2i(mDevice->BufferSize * frameSize / periods), 4)};
uint log2FragmentSize{std::max(log2i(mDevice->BufferSize * frameSize / periods), 4u)};
uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
audio_buf_info info{};
#define CHECKERR(func) if((func) < 0) { \
throw al::backend_exception{al::backend_error::DeviceError, #func " failed: %s", \
strerror(errno)}; \
std::generic_category().message(errno).c_str()}; \
}
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize));
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
@ -617,11 +637,11 @@ void OSScapture::stop()
mThread.join();
if(ioctl(mFd, SNDCTL_DSP_RESET) != 0)
ERR("Error resetting device: %s\n", strerror(errno));
ERR("Error resetting device: %s\n", std::generic_category().message(errno).c_str());
}
void OSScapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
void OSScapture::captureSamples(std::byte *buffer, uint samples)
{ std::ignore = mRing->read(buffer, samples); }
uint OSScapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()); }
@ -637,9 +657,9 @@ BackendFactory &OSSBackendFactory::getFactory()
bool OSSBackendFactory::init()
{
if(auto devopt = ConfigValueStr(nullptr, "oss", "device"))
if(auto devopt = ConfigValueStr({}, "oss", "device"))
DefaultPlayback = std::move(*devopt);
if(auto capopt = ConfigValueStr(nullptr, "oss", "capture"))
if(auto capopt = ConfigValueStr({}, "oss", "capture"))
DefaultCapture = std::move(*capopt);
return true;
@ -648,18 +668,13 @@ bool OSSBackendFactory::init()
bool OSSBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string OSSBackendFactory::probe(BackendType type)
auto OSSBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
auto add_device = [&outnames](const DevMap &entry) -> void
{
struct stat buf;
if(stat(entry.device_name.c_str(), &buf) == 0)
{
/* Includes null char. */
outnames.append(entry.name.c_str(), entry.name.length()+1);
}
if(struct stat buf{}; stat(entry.device_name.c_str(), &buf) == 0)
outnames.emplace_back(entry.name);
};
switch(type)
@ -667,12 +682,14 @@ std::string OSSBackendFactory::probe(BackendType type)
case BackendType::Playback:
PlaybackDevices.clear();
ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT);
outnames.reserve(PlaybackDevices.size());
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case BackendType::Capture:
CaptureDevices.clear();
ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT);
outnames.reserve(CaptureDevices.size());
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}

View file

@ -5,15 +5,15 @@
struct OSSBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_OSS_H */

File diff suppressed because it is too large Load diff

View file

@ -2,22 +2,26 @@
#define BACKENDS_PIPEWIRE_H
#include <string>
#include <vector>
#include "alc/events.h"
#include "base.h"
struct DeviceBase;
struct PipeWireBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
static BackendFactory &getFactory();
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_PIPEWIRE_H */

View file

@ -26,21 +26,20 @@
#include <cstdlib>
#include <cstring>
#include "albit.h"
#include "alc/alconfig.h"
#include "alnumeric.h"
#include "alstring.h"
#include "core/device.h"
#include "core/logging.h"
#include "dynload.h"
#include "ringbuffer.h"
#include <portaudio.h>
#include <portaudio.h> /* NOLINT(*-duplicate-include) Not the same header. */
namespace {
constexpr char pa_device[] = "PortAudio Default";
#ifdef HAVE_DYNLOAD
void *pa_handle;
#define MAKE_FUNC(x) decltype(x) * p##x
@ -51,6 +50,8 @@ MAKE_FUNC(Pa_StartStream);
MAKE_FUNC(Pa_StopStream);
MAKE_FUNC(Pa_OpenStream);
MAKE_FUNC(Pa_CloseStream);
MAKE_FUNC(Pa_GetDeviceCount);
MAKE_FUNC(Pa_GetDeviceInfo);
MAKE_FUNC(Pa_GetDefaultOutputDevice);
MAKE_FUNC(Pa_GetDefaultInputDevice);
MAKE_FUNC(Pa_GetStreamInfo);
@ -64,12 +65,49 @@ MAKE_FUNC(Pa_GetStreamInfo);
#define Pa_StopStream pPa_StopStream
#define Pa_OpenStream pPa_OpenStream
#define Pa_CloseStream pPa_CloseStream
#define Pa_GetDeviceCount pPa_GetDeviceCount
#define Pa_GetDeviceInfo pPa_GetDeviceInfo
#define Pa_GetDefaultOutputDevice pPa_GetDefaultOutputDevice
#define Pa_GetDefaultInputDevice pPa_GetDefaultInputDevice
#define Pa_GetStreamInfo pPa_GetStreamInfo
#endif
#endif
struct DeviceEntry {
std::string mName;
bool mIsPlayback{};
bool mIsCapture{};
};
std::vector<DeviceEntry> DeviceNames;
void EnumerateDevices()
{
const auto devcount = Pa_GetDeviceCount();
if(devcount < 0)
{
ERR("Error getting device count: %s\n", Pa_GetErrorText(devcount));
return;
}
std::vector<DeviceEntry>(static_cast<uint>(devcount)).swap(DeviceNames);
PaDeviceIndex idx{0};
for(auto &entry : DeviceNames)
{
if(auto info = Pa_GetDeviceInfo(idx); info && info->name)
{
#ifdef _WIN32
entry.mName = "OpenAL Soft on "+std::string{info->name};
#else
entry.mName = info->name;
#endif
entry.mIsPlayback = (info->maxOutputChannels > 0);
entry.mIsCapture = (info->maxInputChannels > 0);
TRACE("Device %d \"%s\": %d playback, %d capture channels\n", idx, entry.mName.c_str(),
info->maxOutputChannels, info->maxInputChannels);
}
++idx;
}
}
struct PortPlayback final : public BackendBase {
PortPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
@ -77,24 +115,17 @@ struct PortPlayback final : public BackendBase {
int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
static int writeCallbackC(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
const PaStreamCallbackFlags statusFlags, void *userData) noexcept
{
return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer,
framesPerBuffer, timeInfo, statusFlags);
}
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
PaStream *mStream{nullptr};
PaStreamParameters mParams{};
DevFmtChannels mChannelConfig{};
uint mAmbiOrder{};
uint mUpdateSize{0u};
DEF_NEWDEL(PortPlayback)
};
PortPlayback::~PortPlayback()
@ -115,22 +146,38 @@ int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long f
}
void PortPlayback::open(const char *name)
void PortPlayback::open(std::string_view name)
{
if(!name)
name = pa_device;
else if(strcmp(name, pa_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(DeviceNames.empty())
EnumerateDevices();
int deviceid{-1};
if(name.empty())
{
if(auto devidopt = ConfigValueInt({}, "port", "device"))
deviceid = *devidopt;
if(deviceid < 0 || static_cast<uint>(deviceid) >= DeviceNames.size())
deviceid = Pa_GetDefaultOutputDevice();
name = DeviceNames.at(static_cast<uint>(deviceid)).mName;
}
else
{
auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(),
[name](const DeviceEntry &entry) { return entry.mIsPlayback && name == entry.mName; });
if(iter == DeviceNames.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
deviceid = static_cast<int>(std::distance(DeviceNames.cbegin(), iter));
}
PaStreamParameters params{};
auto devidopt = ConfigValueInt(nullptr, "port", "device");
if(devidopt && *devidopt >= 0) params.device = *devidopt;
else params.device = Pa_GetDefaultOutputDevice();
params.device = deviceid;
params.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency);
params.hostApiSpecificStreamInfo = nullptr;
params.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
mChannelConfig = mDevice->FmtChans;
mAmbiOrder = mDevice->mAmbiOrder;
params.channelCount = static_cast<int>(mDevice->channelsFromFmt());
switch(mDevice->FmtType)
{
@ -155,19 +202,21 @@ void PortPlayback::open(const char *name)
break;
}
retry_open:
PaStream *stream{};
PaError err{Pa_OpenStream(&stream, nullptr, &params, mDevice->Frequency, mDevice->UpdateSize,
paNoFlag, &PortPlayback::writeCallbackC, this)};
if(err != paNoError)
static constexpr auto writeCallback = [](const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
const PaStreamCallbackFlags statusFlags, void *userData) noexcept
{
if(params.sampleFormat == paFloat32)
{
params.sampleFormat = paInt16;
goto retry_open;
}
throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
Pa_GetErrorText(err)};
return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer,
framesPerBuffer, timeInfo, statusFlags);
};
PaStream *stream{};
while(PaError err{Pa_OpenStream(&stream, nullptr, &params, mDevice->Frequency,
mDevice->UpdateSize, paNoFlag, writeCallback, this)})
{
if(params.sampleFormat != paFloat32)
throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
Pa_GetErrorText(err)};
params.sampleFormat = paInt16;
}
Pa_CloseStream(mStream);
@ -182,7 +231,18 @@ bool PortPlayback::reset()
{
const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)};
mDevice->Frequency = static_cast<uint>(streamInfo->sampleRate);
mDevice->FmtChans = mChannelConfig;
mDevice->mAmbiOrder = mAmbiOrder;
mDevice->UpdateSize = mUpdateSize;
mDevice->BufferSize = mUpdateSize * 2;
if(streamInfo->outputLatency > 0.0f)
{
const double sampleLatency{streamInfo->outputLatency * streamInfo->sampleRate};
TRACE("Reported stream latency: %f sec (%f samples)\n", streamInfo->outputLatency,
sampleLatency);
mDevice->BufferSize = static_cast<uint>(std::clamp(sampleLatency,
double(mDevice->BufferSize), double{std::numeric_limits<int>::max()}));
}
if(mParams.sampleFormat == paInt8)
mDevice->FmtType = DevFmtByte;
@ -200,15 +260,6 @@ bool PortPlayback::reset()
return false;
}
if(mParams.channelCount >= 2)
mDevice->FmtChans = DevFmtStereo;
else if(mParams.channelCount == 1)
mDevice->FmtChans = DevFmtMono;
else
{
ERR("Unexpected channel count: %u\n", mParams.channelCount);
return false;
}
setDefaultChannelOrder();
return true;
@ -216,16 +267,14 @@ bool PortPlayback::reset()
void PortPlayback::start()
{
const PaError err{Pa_StartStream(mStream)};
if(err == paNoError)
if(const PaError err{Pa_StartStream(mStream)}; err != paNoError)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: %s",
Pa_GetErrorText(err)};
}
void PortPlayback::stop()
{
PaError err{Pa_StopStream(mStream)};
if(err != paNoError)
if(PaError err{Pa_StopStream(mStream)}; err != paNoError)
ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
}
@ -235,27 +284,18 @@ struct PortCapture final : public BackendBase {
~PortCapture() override;
int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
static int readCallbackC(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
const PaStreamCallbackFlags statusFlags, void *userData) noexcept
{
return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer,
framesPerBuffer, timeInfo, statusFlags);
}
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) const noexcept;
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
PaStream *mStream{nullptr};
PaStreamParameters mParams;
PaStreamParameters mParams{};
RingBufferPtr mRing{nullptr};
DEF_NEWDEL(PortCapture)
};
PortCapture::~PortCapture()
@ -268,30 +308,43 @@ PortCapture::~PortCapture()
int PortCapture::readCallback(const void *inputBuffer, void*, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept
const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) const noexcept
{
mRing->write(inputBuffer, framesPerBuffer);
std::ignore = mRing->write(inputBuffer, framesPerBuffer);
return 0;
}
void PortCapture::open(const char *name)
void PortCapture::open(std::string_view name)
{
if(!name)
name = pa_device;
else if(strcmp(name, pa_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(DeviceNames.empty())
EnumerateDevices();
uint samples{mDevice->BufferSize};
samples = maxu(samples, 100 * mDevice->Frequency / 1000);
uint frame_size{mDevice->frameSizeFromFmt()};
int deviceid{};
if(name.empty())
{
if(auto devidopt = ConfigValueInt({}, "port", "capture"))
deviceid = *devidopt;
if(deviceid < 0 || static_cast<uint>(deviceid) >= DeviceNames.size())
deviceid = Pa_GetDefaultInputDevice();
name = DeviceNames.at(static_cast<uint>(deviceid)).mName;
}
else
{
auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(),
[name](const DeviceEntry &entry) { return entry.mIsCapture && name == entry.mName; });
if(iter == DeviceNames.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
deviceid = static_cast<int>(std::distance(DeviceNames.cbegin(), iter));
}
const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)};
const uint frame_size{mDevice->frameSizeFromFmt()};
mRing = RingBuffer::Create(samples, frame_size, false);
auto devidopt = ConfigValueInt(nullptr, "port", "capture");
if(devidopt && *devidopt >= 0) mParams.device = *devidopt;
else mParams.device = Pa_GetDefaultOutputDevice();
mParams.device = deviceid;
mParams.suggestedLatency = 0.0f;
mParams.hostApiSpecificStreamInfo = nullptr;
@ -319,8 +372,15 @@ void PortCapture::open(const char *name)
}
mParams.channelCount = static_cast<int>(mDevice->channelsFromFmt());
static constexpr auto readCallback = [](const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
const PaStreamCallbackFlags statusFlags, void *userData) noexcept
{
return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer,
framesPerBuffer, timeInfo, statusFlags);
};
PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency,
paFramesPerBufferUnspecified, paNoFlag, &PortCapture::readCallbackC, this)};
paFramesPerBufferUnspecified, paNoFlag, readCallback, this)};
if(err != paNoError)
throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
Pa_GetErrorText(err)};
@ -331,16 +391,14 @@ void PortCapture::open(const char *name)
void PortCapture::start()
{
const PaError err{Pa_StartStream(mStream)};
if(err != paNoError)
if(const PaError err{Pa_StartStream(mStream)}; err != paNoError)
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start recording: %s", Pa_GetErrorText(err)};
}
void PortCapture::stop()
{
PaError err{Pa_StopStream(mStream)};
if(err != paNoError)
if(PaError err{Pa_StopStream(mStream)}; err != paNoError)
ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
}
@ -348,16 +406,14 @@ void PortCapture::stop()
uint PortCapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()); }
void PortCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
void PortCapture::captureSamples(std::byte *buffer, uint samples)
{ std::ignore = mRing->read(buffer, samples); }
} // namespace
bool PortBackendFactory::init()
{
PaError err;
#ifdef HAVE_DYNLOAD
if(!pa_handle)
{
@ -391,12 +447,15 @@ bool PortBackendFactory::init()
LOAD_FUNC(Pa_StopStream);
LOAD_FUNC(Pa_OpenStream);
LOAD_FUNC(Pa_CloseStream);
LOAD_FUNC(Pa_GetDeviceCount);
LOAD_FUNC(Pa_GetDeviceInfo);
LOAD_FUNC(Pa_GetDefaultOutputDevice);
LOAD_FUNC(Pa_GetDefaultInputDevice);
LOAD_FUNC(Pa_GetStreamInfo);
#undef LOAD_FUNC
if((err=Pa_Initialize()) != paNoError)
const PaError err{Pa_Initialize()};
if(err != paNoError)
{
ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
CloseLib(pa_handle);
@ -405,7 +464,8 @@ bool PortBackendFactory::init()
}
}
#else
if((err=Pa_Initialize()) != paNoError)
const PaError err{Pa_Initialize()};
if(err != paNoError)
{
ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
return false;
@ -417,18 +477,52 @@ bool PortBackendFactory::init()
bool PortBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string PortBackendFactory::probe(BackendType type)
auto PortBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> devices;
EnumerateDevices();
int defaultid{-1};
switch(type)
{
case BackendType::Playback:
defaultid = Pa_GetDefaultOutputDevice();
if(auto devidopt = ConfigValueInt({}, "port", "device"); devidopt && *devidopt >= 0
&& static_cast<uint>(*devidopt) < DeviceNames.size())
defaultid = *devidopt;
for(size_t i{0};i < DeviceNames.size();++i)
{
if(DeviceNames[i].mIsPlayback)
{
if(defaultid >= 0 && static_cast<uint>(defaultid) == i)
devices.emplace(devices.cbegin(), DeviceNames[i].mName);
else
devices.emplace_back(DeviceNames[i].mName);
}
}
break;
case BackendType::Capture:
/* Includes null char. */
outnames.append(pa_device, sizeof(pa_device));
defaultid = Pa_GetDefaultInputDevice();
if(auto devidopt = ConfigValueInt({}, "port", "capture"); devidopt && *devidopt >= 0
&& static_cast<uint>(*devidopt) < DeviceNames.size())
defaultid = *devidopt;
for(size_t i{0};i < DeviceNames.size();++i)
{
if(DeviceNames[i].mIsCapture)
{
if(defaultid >= 0 && static_cast<uint>(defaultid) == i)
devices.emplace(devices.cbegin(), DeviceNames[i].mName);
else
devices.emplace_back(DeviceNames[i].mName);
}
}
break;
}
return outnames;
return devices;
}
BackendPtr PortBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct PortBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_PORTAUDIO_H */

File diff suppressed because it is too large Load diff

View file

@ -1,19 +1,27 @@
#ifndef BACKENDS_PULSEAUDIO_H
#define BACKENDS_PULSEAUDIO_H
#include <string>
#include <vector>
#include "alc/events.h"
#include "base.h"
struct DeviceBase;
class PulseBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
static BackendFactory &getFactory();
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_PULSEAUDIO_H */

View file

@ -26,6 +26,7 @@
#include <cstdlib>
#include <cstring>
#include <string>
#include <string_view>
#include "almalloc.h"
#include "alnumeric.h"
@ -46,17 +47,17 @@ namespace {
#define DEVNAME_PREFIX ""
#endif
constexpr char defaultDeviceName[] = DEVNAME_PREFIX "Default Device";
constexpr auto getDevicePrefix() noexcept -> std::string_view { return DEVNAME_PREFIX; }
constexpr auto getDefaultDeviceName() noexcept -> std::string_view
{ return DEVNAME_PREFIX "Default Device"; }
struct Sdl2Backend final : public BackendBase {
Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { }
~Sdl2Backend() override;
void audioCallback(Uint8 *stream, int len) noexcept;
static void audioCallbackC(void *ptr, Uint8 *stream, int len) noexcept
{ static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); }
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -68,8 +69,6 @@ struct Sdl2Backend final : public BackendBase {
DevFmtChannels mFmtChans{};
DevFmtType mFmtType{};
uint mUpdateSize{0u};
DEF_NEWDEL(Sdl2Backend)
};
Sdl2Backend::~Sdl2Backend()
@ -86,7 +85,7 @@ void Sdl2Backend::audioCallback(Uint8 *stream, int len) noexcept
mDevice->renderSamples(stream, ulen / mFrameSize, mDevice->channelsFromFmt());
}
void Sdl2Backend::open(const char *name)
void Sdl2Backend::open(std::string_view name)
{
SDL_AudioSpec want{}, have{};
@ -102,24 +101,39 @@ void Sdl2Backend::open(const char *name)
case DevFmtFloat: want.format = AUDIO_F32; break;
}
want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2;
want.samples = static_cast<Uint16>(minu(mDevice->UpdateSize, 8192));
want.callback = &Sdl2Backend::audioCallbackC;
want.samples = static_cast<Uint16>(std::min(mDevice->UpdateSize, 8192u));
want.callback = [](void *ptr, Uint8 *stream, int len) noexcept
{ return static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); };
want.userdata = this;
/* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't
* necessarily the first in the list.
*/
const auto defaultDeviceName = getDefaultDeviceName();
SDL_AudioDeviceID devid;
if(!name || strcmp(name, defaultDeviceName) == 0)
if(name.empty() || name == defaultDeviceName)
{
name = defaultDeviceName;
devid = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE);
}
else
{
const size_t prefix_len = strlen(DEVNAME_PREFIX);
if(strncmp(name, DEVNAME_PREFIX, prefix_len) == 0)
devid = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have,
const auto namePrefix = getDevicePrefix();
if(name.size() >= namePrefix.size() && name.substr(0, namePrefix.size()) == namePrefix)
{
/* Copy the string_view to a string to ensure it's null terminated
* for this call.
*/
const std::string devname{name.substr(namePrefix.size())};
devid = SDL_OpenAudioDevice(devname.c_str(), SDL_FALSE, &want, &have,
SDL_AUDIO_ALLOW_ANY_CHANGE);
}
else
devid = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE);
{
const std::string devname{name};
devid = SDL_OpenAudioDevice(devname.c_str(), SDL_FALSE, &want, &have,
SDL_AUDIO_ALLOW_ANY_CHANGE);
}
}
if(!devid)
throw al::backend_exception{al::backend_error::NoDevice, "%s", SDL_GetError()};
@ -161,7 +175,7 @@ void Sdl2Backend::open(const char *name)
mFmtType = devtype;
mUpdateSize = have.samples;
mDevice->DeviceName = name ? name : defaultDeviceName;
mDevice->DeviceName = name;
}
bool Sdl2Backend::reset()
@ -195,23 +209,27 @@ bool SDL2BackendFactory::init()
bool SDL2BackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
std::string SDL2BackendFactory::probe(BackendType type)
auto SDL2BackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
if(type != BackendType::Playback)
return outnames;
int num_devices{SDL_GetNumAudioDevices(SDL_FALSE)};
if(num_devices <= 0)
return outnames;
/* Includes null char. */
outnames.append(defaultDeviceName, sizeof(defaultDeviceName));
outnames.reserve(static_cast<unsigned int>(num_devices));
outnames.emplace_back(getDefaultDeviceName());
for(int i{0};i < num_devices;++i)
{
std::string name{DEVNAME_PREFIX};
name += SDL_GetAudioDeviceName(i, SDL_FALSE);
if(!name.empty())
outnames.append(name.c_str(), name.length()+1);
std::string outname{getDevicePrefix()};
if(const char *name = SDL_GetAudioDeviceName(i, SDL_FALSE))
outname += name;
else
outname += "Unknown Device Name #"+std::to_string(i);
outnames.emplace_back(std::move(outname));
}
return outnames;
}

View file

@ -5,15 +5,15 @@
struct SDL2BackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_SDL2_H */

View file

@ -22,31 +22,35 @@
#include "sndio.h"
#include <cinttypes>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <inttypes.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <system_error>
#include <thread>
#include <vector>
#include "alnumeric.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "ringbuffer.h"
#include "threads.h"
#include "vector.h"
#include <sndio.h>
#include <sndio.h> /* NOLINT(*-duplicate-include) Not the same header. */
namespace {
static const char sndio_device[] = "SndIO Default";
using namespace std::string_view_literals;
[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "SndIO Default"sv; }
struct SioPar : public sio_par {
SioPar() { sio_initpar(this); }
SioPar() : sio_par{} { sio_initpar(this); }
void clear() { sio_initpar(this); }
};
@ -57,7 +61,7 @@ struct SndioPlayback final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -65,12 +69,10 @@ struct SndioPlayback final : public BackendBase {
sio_hdl *mSndHandle{nullptr};
uint mFrameStep{};
al::vector<al::byte> mBuffer;
std::vector<std::byte> mBuffer;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(SndioPlayback)
};
SndioPlayback::~SndioPlayback()
@ -86,12 +88,12 @@ int SndioPlayback::mixerProc()
const size_t frameSize{frameStep * mDevice->bytesFromFmt()};
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
al::span<al::byte> buffer{mBuffer};
al::span<std::byte> buffer{mBuffer};
mDevice->renderSamples(buffer.data(), static_cast<uint>(buffer.size() / frameSize),
frameStep);
@ -112,13 +114,13 @@ int SndioPlayback::mixerProc()
}
void SndioPlayback::open(const char *name)
void SndioPlayback::open(std::string_view name)
{
if(!name)
name = sndio_device;
else if(strcmp(name, sndio_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDefaultName();
else if(name != GetDefaultName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)};
if(!sndHandle)
@ -136,72 +138,75 @@ bool SndioPlayback::reset()
SioPar par;
auto tryfmt = mDevice->FmtType;
retry_params:
switch(tryfmt)
while(true)
{
case DevFmtByte:
par.bits = 8;
par.sig = 1;
break;
case DevFmtUByte:
par.bits = 8;
par.sig = 0;
break;
case DevFmtShort:
par.bits = 16;
par.sig = 1;
break;
case DevFmtUShort:
par.bits = 16;
par.sig = 0;
break;
case DevFmtFloat:
case DevFmtInt:
par.bits = 32;
par.sig = 1;
break;
case DevFmtUInt:
par.bits = 32;
par.sig = 0;
break;
}
par.bps = SIO_BPS(par.bits);
par.le = SIO_LE_NATIVE;
par.msb = 1;
switch(tryfmt)
{
case DevFmtByte:
par.bits = 8;
par.sig = 1;
break;
case DevFmtUByte:
par.bits = 8;
par.sig = 0;
break;
case DevFmtShort:
par.bits = 16;
par.sig = 1;
break;
case DevFmtUShort:
par.bits = 16;
par.sig = 0;
break;
case DevFmtFloat:
case DevFmtInt:
par.bits = 32;
par.sig = 1;
break;
case DevFmtUInt:
par.bits = 32;
par.sig = 0;
break;
}
par.bps = SIO_BPS(par.bits);
par.le = SIO_LE_NATIVE;
par.msb = 1;
par.rate = mDevice->Frequency;
par.pchan = mDevice->channelsFromFmt();
par.rate = mDevice->Frequency;
par.pchan = mDevice->channelsFromFmt();
par.round = mDevice->UpdateSize;
par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize;
if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize;
par.round = mDevice->UpdateSize;
par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize;
if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize;
try {
if(!sio_setpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set device parameters"};
try {
if(!sio_setpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set device parameters"};
par.clear();
if(!sio_getpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to get device parameters"};
par.clear();
if(!sio_getpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to get device parameters"};
if(par.bps > 1 && par.le != SIO_LE_NATIVE)
throw al::backend_exception{al::backend_error::DeviceError,
"%s-endian samples not supported", par.le ? "Little" : "Big"};
if(par.bits < par.bps*8 && !par.msb)
throw al::backend_exception{al::backend_error::DeviceError,
"MSB-padded samples not supported (%u of %u bits)", par.bits, par.bps*8};
if(par.pchan < 1)
throw al::backend_exception{al::backend_error::DeviceError,
"No playback channels on device"};
}
catch(al::backend_exception &e) {
if(tryfmt == DevFmtShort)
throw;
par.clear();
tryfmt = DevFmtShort;
goto retry_params;
if(par.bps > 1 && par.le != SIO_LE_NATIVE)
throw al::backend_exception{al::backend_error::DeviceError,
"%s-endian samples not supported", par.le ? "Little" : "Big"};
if(par.bits < par.bps*8 && !par.msb)
throw al::backend_exception{al::backend_error::DeviceError,
"MSB-padded samples not supported (%u of %u bits)", par.bits, par.bps*8};
if(par.pchan < 1)
throw al::backend_exception{al::backend_error::DeviceError,
"No playback channels on device"};
break;
}
catch(al::backend_exception &e) {
if(tryfmt == DevFmtShort)
throw;
par.clear();
tryfmt = DevFmtShort;
}
}
if(par.bps == 1)
@ -229,11 +234,11 @@ retry_params:
mDevice->UpdateSize = par.round;
mDevice->BufferSize = par.bufsz + par.round;
mBuffer.resize(mDevice->UpdateSize * par.pchan*par.bps);
mBuffer.resize(size_t{mDevice->UpdateSize} * par.pchan*par.bps);
if(par.sig == 1)
std::fill(mBuffer.begin(), mBuffer.end(), al::byte{});
std::fill(mBuffer.begin(), mBuffer.end(), std::byte{});
else if(par.bits == 8)
std::fill_n(mBuffer.data(), mBuffer.size(), al::byte(0x80));
std::fill_n(mBuffer.data(), mBuffer.size(), std::byte(0x80));
else if(par.bits == 16)
std::fill_n(reinterpret_cast<uint16_t*>(mBuffer.data()), mBuffer.size()/2, 0x8000);
else if(par.bits == 32)
@ -280,10 +285,10 @@ struct SndioCapture final : public BackendBase {
int recordProc();
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
sio_hdl *mSndHandle{nullptr};
@ -292,8 +297,6 @@ struct SndioCapture final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(SndioCapture)
};
SndioCapture::~SndioCapture()
@ -306,7 +309,7 @@ SndioCapture::~SndioCapture()
int SndioCapture::recordProc()
{
SetRTPriority();
althrd_setname(RECORD_THREAD_NAME);
althrd_setname(GetRecordThreadName());
const uint frameSize{mDevice->frameSizeFromFmt()};
@ -317,29 +320,30 @@ int SndioCapture::recordProc()
return 1;
}
auto fds = std::make_unique<pollfd[]>(static_cast<uint>(nfds_pre));
auto fds = std::vector<pollfd>(static_cast<uint>(nfds_pre));
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
/* Wait until there's some samples to read. */
const int nfds{sio_pollfd(mSndHandle, fds.get(), POLLIN)};
const int nfds{sio_pollfd(mSndHandle, fds.data(), POLLIN)};
if(nfds <= 0)
{
mDevice->handleDisconnect("Failed to get polling fds: %d", nfds);
break;
}
int pollres{::poll(fds.get(), static_cast<uint>(nfds), 2000)};
int pollres{::poll(fds.data(), fds.size(), 2000)};
if(pollres < 0)
{
if(errno == EINTR) continue;
mDevice->handleDisconnect("Poll error: %s", strerror(errno));
mDevice->handleDisconnect("Poll error: %s",
std::generic_category().message(errno).c_str());
break;
}
if(pollres == 0)
continue;
const int revents{sio_revents(mSndHandle, fds.get())};
const int revents{sio_revents(mSndHandle, fds.data())};
if((revents&POLLHUP))
{
mDevice->handleDisconnect("Got POLLHUP from poll events");
@ -349,7 +353,7 @@ int SndioCapture::recordProc()
continue;
auto data = mRing->getWriteVector();
al::span<al::byte> buffer{data.first.buf, data.first.len*frameSize};
al::span<std::byte> buffer{data.first.buf, data.first.len*frameSize};
while(!buffer.empty())
{
size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())};
@ -373,8 +377,8 @@ int SndioCapture::recordProc()
if(buffer.empty())
{
/* Got samples to read, but no place to store it. Drop it. */
static char junk[4096];
sio_read(mSndHandle, junk, sizeof(junk) - (sizeof(junk)%frameSize));
static std::array<char,4096> junk;
sio_read(mSndHandle, junk.data(), junk.size() - (junk.size()%frameSize));
}
}
@ -382,13 +386,13 @@ int SndioCapture::recordProc()
}
void SndioCapture::open(const char *name)
void SndioCapture::open(std::string_view name)
{
if(!name)
name = sndio_device;
else if(strcmp(name, sndio_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDefaultName();
else if(name != GetDefaultName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
mSndHandle = sio_open(nullptr, SIO_REC, true);
if(mSndHandle == nullptr)
@ -431,12 +435,12 @@ void SndioCapture::open(const char *name)
par.rchan = mDevice->channelsFromFmt();
par.rate = mDevice->Frequency;
par.appbufsz = maxu(mDevice->BufferSize, mDevice->Frequency/10);
par.round = minu(par.appbufsz/2, mDevice->Frequency/40);
par.appbufsz = std::max(mDevice->BufferSize, mDevice->Frequency/10u);
par.round = std::min(par.appbufsz/2u, mDevice->Frequency/40u);
if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set device praameters"};
"Failed to set device parameters"};
if(par.bps > 1 && par.le != SIO_LE_NATIVE)
throw al::backend_exception{al::backend_error::DeviceError,
@ -461,7 +465,7 @@ void SndioCapture::open(const char *name)
DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans),
mDevice->Frequency, par.sig?'s':'u', par.bps*8, par.rchan, par.rate};
mRing = RingBuffer::Create(mDevice->BufferSize, par.bps*par.rchan, false);
mRing = RingBuffer::Create(mDevice->BufferSize, size_t{par.bps}*par.rchan, false);
mDevice->BufferSize = static_cast<uint>(mRing->writeSpace());
mDevice->UpdateSize = par.round;
@ -496,8 +500,8 @@ void SndioCapture::stop()
ERR("Error stopping device\n");
}
void SndioCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
void SndioCapture::captureSamples(std::byte *buffer, uint samples)
{ std::ignore = mRing->read(buffer, samples); }
uint SndioCapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()); }
@ -516,18 +520,15 @@ bool SndIOBackendFactory::init()
bool SndIOBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string SndIOBackendFactory::probe(BackendType type)
auto SndIOBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
switch(type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
outnames.append(sndio_device, sizeof(sndio_device));
break;
return std::vector{std::string{GetDefaultName()}};
}
return outnames;
return {};
}
BackendPtr SndIOBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct SndIOBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_SNDIO_H */

View file

@ -35,24 +35,26 @@
#include <poll.h>
#include <math.h>
#include <string.h>
#include <vector>
#include <thread>
#include <functional>
#include "albyte.h"
#include "alc/alconfig.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "threads.h"
#include "vector.h"
#include <sys/audioio.h>
namespace {
constexpr char solaris_device[] = "Solaris Default";
using namespace std::string_view_literals;
[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "Solaris Default"sv; }
std::string solaris_driver{"/dev/audio"};
@ -63,7 +65,7 @@ struct SolarisBackend final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -71,12 +73,10 @@ struct SolarisBackend final : public BackendBase {
int mFd{-1};
uint mFrameStep{};
al::vector<al::byte> mBuffer;
std::vector<std::byte> mBuffer;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(SolarisBackend)
};
SolarisBackend::~SolarisBackend()
@ -89,10 +89,10 @@ SolarisBackend::~SolarisBackend()
int SolarisBackend::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
const size_t frame_step{mDevice->channelsFromFmt()};
const uint frame_size{mDevice->frameSizeFromFmt()};
const size_t frame_size{mDevice->frameSizeFromFmt()};
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
@ -116,12 +116,12 @@ int SolarisBackend::mixerProc()
continue;
}
al::byte *write_ptr{mBuffer.data()};
size_t to_write{mBuffer.size()};
mDevice->renderSamples(write_ptr, static_cast<uint>(to_write/frame_size), frame_step);
while(to_write > 0 && !mKillNow.load(std::memory_order_acquire))
al::span<std::byte> buffer{mBuffer};
mDevice->renderSamples(buffer.data(), static_cast<uint>(buffer.size()/frame_size),
frame_step);
while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire))
{
ssize_t wrote{write(mFd, write_ptr, to_write)};
ssize_t wrote{write(mFd, buffer.data(), buffer.size())};
if(wrote < 0)
{
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
@ -131,8 +131,7 @@ int SolarisBackend::mixerProc()
break;
}
to_write -= static_cast<size_t>(wrote);
write_ptr += wrote;
buffer = buffer.subspan(static_cast<size_t>(wrote));
}
}
@ -140,13 +139,13 @@ int SolarisBackend::mixerProc()
}
void SolarisBackend::open(const char *name)
void SolarisBackend::open(std::string_view name)
{
if(!name)
name = solaris_device;
else if(strcmp(name, solaris_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDefaultName();
else if(name != GetDefaultName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
int fd{::open(solaris_driver.c_str(), O_WRONLY)};
if(fd == -1)
@ -231,7 +230,7 @@ bool SolarisBackend::reset()
setDefaultChannelOrder();
mBuffer.resize(mDevice->UpdateSize * size_t{frame_size});
std::fill(mBuffer.begin(), mBuffer.end(), al::byte{});
std::fill(mBuffer.begin(), mBuffer.end(), std::byte{});
return true;
}
@ -268,7 +267,7 @@ BackendFactory &SolarisBackendFactory::getFactory()
bool SolarisBackendFactory::init()
{
if(auto devopt = ConfigValueStr(nullptr, "solaris", "device"))
if(auto devopt = ConfigValueStr({}, "solaris", "device"))
solaris_driver = std::move(*devopt);
return true;
}
@ -276,23 +275,19 @@ bool SolarisBackendFactory::init()
bool SolarisBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
std::string SolarisBackendFactory::probe(BackendType type)
auto SolarisBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
switch(type)
{
case BackendType::Playback:
{
struct stat buf;
if(stat(solaris_driver.c_str(), &buf) == 0)
outnames.append(solaris_device, sizeof(solaris_device));
}
break;
if(struct stat buf{}; stat(solaris_driver.c_str(), &buf) == 0)
return std::vector{std::string{GetDefaultName()}};
break;
case BackendType::Capture:
break;
}
return outnames;
return {};
}
BackendPtr SolarisBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct SolarisBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_SOLARIS_H */

File diff suppressed because it is too large Load diff

View file

@ -5,15 +5,17 @@
struct WasapiBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
static BackendFactory &getFactory();
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_WASAPI_H */

View file

@ -31,24 +31,26 @@
#include <cstring>
#include <exception>
#include <functional>
#include <system_error>
#include <thread>
#include <vector>
#include "albit.h"
#include "albyte.h"
#include "alc/alconfig.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "opthelpers.h"
#include "strutils.h"
#include "threads.h"
#include "vector.h"
namespace {
using namespace std::string_view_literals;
using std::chrono::seconds;
using std::chrono::milliseconds;
using std::chrono::nanoseconds;
@ -56,38 +58,43 @@ using std::chrono::nanoseconds;
using ubyte = unsigned char;
using ushort = unsigned short;
constexpr char waveDevice[] = "Wave File Writer";
struct FileDeleter {
void operator()(gsl::owner<FILE*> f) { fclose(f); }
};
using FilePtr = std::unique_ptr<FILE,FileDeleter>;
constexpr ubyte SUBTYPE_PCM[]{
[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "Wave File Writer"sv; }
constexpr std::array<ubyte,16> SUBTYPE_PCM{{
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
0x00, 0x38, 0x9b, 0x71
};
constexpr ubyte SUBTYPE_FLOAT[]{
}};
constexpr std::array<ubyte,16> SUBTYPE_FLOAT{{
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
0x00, 0x38, 0x9b, 0x71
};
}};
constexpr ubyte SUBTYPE_BFORMAT_PCM[]{
constexpr std::array<ubyte,16> SUBTYPE_BFORMAT_PCM{{
0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
0xca, 0x00, 0x00, 0x00
};
}};
constexpr ubyte SUBTYPE_BFORMAT_FLOAT[]{
constexpr std::array<ubyte,16> SUBTYPE_BFORMAT_FLOAT{{
0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
0xca, 0x00, 0x00, 0x00
};
}};
void fwrite16le(ushort val, FILE *f)
{
ubyte data[2]{ static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff) };
fwrite(data, 1, 2, f);
std::array data{static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff)};
fwrite(data.data(), 1, data.size(), f);
}
void fwrite32le(uint val, FILE *f)
{
ubyte data[4]{ static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff),
static_cast<ubyte>((val>>16)&0xff), static_cast<ubyte>((val>>24)&0xff) };
fwrite(data, 1, 4, f);
std::array data{static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff),
static_cast<ubyte>((val>>16)&0xff), static_cast<ubyte>((val>>24)&0xff)};
fwrite(data.data(), 1, data.size(), f);
}
@ -97,34 +104,27 @@ struct WaveBackend final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
FILE *mFile{nullptr};
FilePtr mFile{nullptr};
long mDataStart{-1};
al::vector<al::byte> mBuffer;
std::vector<std::byte> mBuffer;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(WaveBackend)
};
WaveBackend::~WaveBackend()
{
if(mFile)
fclose(mFile);
mFile = nullptr;
}
WaveBackend::~WaveBackend() = default;
int WaveBackend::mixerProc()
{
const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2};
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
const size_t frameStep{mDevice->channelsFromFmt()};
const size_t frameSize{mDevice->frameSizeFromFmt()};
@ -155,13 +155,13 @@ int WaveBackend::mixerProc()
if(bytesize == 2)
{
const size_t len{mBuffer.size() & ~size_t{1}};
const size_t len{mBuffer.size() & ~1_uz};
for(size_t i{0};i < len;i+=2)
std::swap(mBuffer[i], mBuffer[i+1]);
}
else if(bytesize == 4)
{
const size_t len{mBuffer.size() & ~size_t{3}};
const size_t len{mBuffer.size() & ~3_uz};
for(size_t i{0};i < len;i+=4)
{
std::swap(mBuffer[i ], mBuffer[i+3]);
@ -170,8 +170,8 @@ int WaveBackend::mixerProc()
}
}
const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)};
if(fs < mDevice->UpdateSize || ferror(mFile))
const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile.get())};
if(fs < mDevice->UpdateSize || ferror(mFile.get()))
{
ERR("Error writing to file\n");
mDevice->handleDisconnect("Failed to write playback samples");
@ -195,32 +195,32 @@ int WaveBackend::mixerProc()
return 0;
}
void WaveBackend::open(const char *name)
void WaveBackend::open(std::string_view name)
{
auto fname = ConfigValueStr(nullptr, "wave", "file");
auto fname = ConfigValueStr({}, "wave", "file");
if(!fname) throw al::backend_exception{al::backend_error::NoDevice,
"No wave output filename"};
if(!name)
name = waveDevice;
else if(strcmp(name, waveDevice) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDeviceName();
else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
/* There's only one "device", so if it's already open, we're done. */
if(mFile) return;
#ifdef _WIN32
{
std::wstring wname{utf8_to_wstr(fname->c_str())};
mFile = _wfopen(wname.c_str(), L"wb");
std::wstring wname{utf8_to_wstr(fname.value())};
mFile = FilePtr{_wfopen(wname.c_str(), L"wb")};
}
#else
mFile = fopen(fname->c_str(), "wb");
mFile = FilePtr{fopen(fname->c_str(), "wb")};
#endif
if(!mFile)
throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '%s': %s",
fname->c_str(), strerror(errno)};
fname->c_str(), std::generic_category().message(errno).c_str()};
mDevice->DeviceName = name;
}
@ -229,12 +229,11 @@ bool WaveBackend::reset()
{
uint channels{0}, bytes{0}, chanmask{0};
bool isbformat{false};
size_t val;
fseek(mFile, 0, SEEK_SET);
clearerr(mFile);
fseek(mFile.get(), 0, SEEK_SET);
clearerr(mFile.get());
if(GetConfigValueBool(nullptr, "wave", "bformat", false))
if(GetConfigValueBool({}, "wave", "bformat", false))
{
mDevice->FmtChans = DevFmtAmbi3D;
mDevice->mAmbiOrder = 1;
@ -265,6 +264,9 @@ bool WaveBackend::reset()
case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break;
case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break;
case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break;
case DevFmtX7144:
mDevice->FmtChans = DevFmtX714;
[[fallthrough]];
case DevFmtX714:
chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400 | 0x1000 | 0x4000
| 0x8000 | 0x20000;
@ -273,7 +275,7 @@ bool WaveBackend::reset()
case DevFmtX3D71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break;
case DevFmtAmbi3D:
/* .amb output requires FuMa */
mDevice->mAmbiOrder = minu(mDevice->mAmbiOrder, 3);
mDevice->mAmbiOrder = std::min(mDevice->mAmbiOrder, 3u);
mDevice->mAmbiLayout = DevAmbiLayout::FuMa;
mDevice->mAmbiScale = DevAmbiScaling::FuMa;
isbformat = true;
@ -283,49 +285,48 @@ bool WaveBackend::reset()
bytes = mDevice->bytesFromFmt();
channels = mDevice->channelsFromFmt();
rewind(mFile);
rewind(mFile.get());
fputs("RIFF", mFile);
fwrite32le(0xFFFFFFFF, mFile); // 'RIFF' header len; filled in at close
fputs("RIFF", mFile.get());
fwrite32le(0xFFFFFFFF, mFile.get()); // 'RIFF' header len; filled in at close
fputs("WAVE", mFile);
fputs("WAVE", mFile.get());
fputs("fmt ", mFile);
fwrite32le(40, mFile); // 'fmt ' header len; 40 bytes for EXTENSIBLE
fputs("fmt ", mFile.get());
fwrite32le(40, mFile.get()); // 'fmt ' header len; 40 bytes for EXTENSIBLE
// 16-bit val, format type id (extensible: 0xFFFE)
fwrite16le(0xFFFE, mFile);
fwrite16le(0xFFFE, mFile.get());
// 16-bit val, channel count
fwrite16le(static_cast<ushort>(channels), mFile);
fwrite16le(static_cast<ushort>(channels), mFile.get());
// 32-bit val, frequency
fwrite32le(mDevice->Frequency, mFile);
fwrite32le(mDevice->Frequency, mFile.get());
// 32-bit val, bytes per second
fwrite32le(mDevice->Frequency * channels * bytes, mFile);
fwrite32le(mDevice->Frequency * channels * bytes, mFile.get());
// 16-bit val, frame size
fwrite16le(static_cast<ushort>(channels * bytes), mFile);
fwrite16le(static_cast<ushort>(channels * bytes), mFile.get());
// 16-bit val, bits per sample
fwrite16le(static_cast<ushort>(bytes * 8), mFile);
fwrite16le(static_cast<ushort>(bytes * 8), mFile.get());
// 16-bit val, extra byte count
fwrite16le(22, mFile);
fwrite16le(22, mFile.get());
// 16-bit val, valid bits per sample
fwrite16le(static_cast<ushort>(bytes * 8), mFile);
fwrite16le(static_cast<ushort>(bytes * 8), mFile.get());
// 32-bit val, channel mask
fwrite32le(chanmask, mFile);
fwrite32le(chanmask, mFile.get());
// 16 byte GUID, sub-type format
val = fwrite((mDevice->FmtType == DevFmtFloat) ?
(isbformat ? SUBTYPE_BFORMAT_FLOAT : SUBTYPE_FLOAT) :
(isbformat ? SUBTYPE_BFORMAT_PCM : SUBTYPE_PCM), 1, 16, mFile);
(void)val;
std::ignore = fwrite((mDevice->FmtType == DevFmtFloat) ?
(isbformat ? SUBTYPE_BFORMAT_FLOAT.data() : SUBTYPE_FLOAT.data()) :
(isbformat ? SUBTYPE_BFORMAT_PCM.data() : SUBTYPE_PCM.data()), 1, 16, mFile.get());
fputs("data", mFile);
fwrite32le(0xFFFFFFFF, mFile); // 'data' header len; filled in at close
fputs("data", mFile.get());
fwrite32le(0xFFFFFFFF, mFile.get()); // 'data' header len; filled in at close
if(ferror(mFile))
if(ferror(mFile.get()))
{
ERR("Error writing header: %s\n", strerror(errno));
ERR("Error writing header: %s\n", std::generic_category().message(errno).c_str());
return false;
}
mDataStart = ftell(mFile);
mDataStart = ftell(mFile.get());
setDefaultWFXChannelOrder();
@ -337,7 +338,7 @@ bool WaveBackend::reset()
void WaveBackend::start()
{
if(mDataStart > 0 && fseek(mFile, 0, SEEK_END) != 0)
if(mDataStart > 0 && fseek(mFile.get(), 0, SEEK_END) != 0)
WARN("Failed to seek on output file\n");
try {
mKillNow.store(false, std::memory_order_release);
@ -357,14 +358,14 @@ void WaveBackend::stop()
if(mDataStart > 0)
{
long size{ftell(mFile)};
long size{ftell(mFile.get())};
if(size > 0)
{
long dataLen{size - mDataStart};
if(fseek(mFile, 4, SEEK_SET) == 0)
fwrite32le(static_cast<uint>(size-8), mFile); // 'WAVE' header len
if(fseek(mFile, mDataStart-4, SEEK_SET) == 0)
fwrite32le(static_cast<uint>(dataLen), mFile); // 'data' header len
if(fseek(mFile.get(), 4, SEEK_SET) == 0)
fwrite32le(static_cast<uint>(size-8), mFile.get()); // 'WAVE' header len
if(fseek(mFile.get(), mDataStart-4, SEEK_SET) == 0)
fwrite32le(static_cast<uint>(dataLen), mFile.get()); // 'data' header len
}
}
}
@ -378,19 +379,16 @@ bool WaveBackendFactory::init()
bool WaveBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
std::string WaveBackendFactory::probe(BackendType type)
auto WaveBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
switch(type)
{
case BackendType::Playback:
/* Includes null char. */
outnames.append(waveDevice, sizeof(waveDevice));
break;
return std::vector{std::string{GetDeviceName()}};
case BackendType::Capture:
break;
}
return outnames;
return {};
}
BackendPtr WaveBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct WaveBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_WAVE_H */

View file

@ -22,8 +22,8 @@
#include "winmm.h"
#include <stdlib.h>
#include <stdio.h>
#include <cstdlib>
#include <cstdio>
#include <memory.h>
#include <windows.h>
@ -38,13 +38,15 @@
#include <algorithm>
#include <functional>
#include "alnumeric.h"
#include "alsem.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "ringbuffer.h"
#include "strutils.h"
#include "threads.h"
#include "vector.h"
#ifndef WAVE_FORMAT_IEEE_FLOAT
#define WAVE_FORMAT_IEEE_FLOAT 0x0003
@ -55,13 +57,13 @@ namespace {
#define DEVNAME_HEAD "OpenAL Soft on "
al::vector<std::string> PlaybackDevices;
al::vector<std::string> CaptureDevices;
std::vector<std::string> PlaybackDevices;
std::vector<std::string> CaptureDevices;
bool checkName(const al::vector<std::string> &list, const std::string &name)
bool checkName(const std::vector<std::string> &list, const std::string &name)
{ return std::find(list.cbegin(), list.cend(), name) != list.cend(); }
void ProbePlaybackDevices(void)
void ProbePlaybackDevices()
{
PlaybackDevices.clear();
@ -74,7 +76,7 @@ void ProbePlaybackDevices(void)
WAVEOUTCAPSW WaveCaps{};
if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
{
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(std::data(WaveCaps.szPname))};
int count{1};
std::string newname{basename};
@ -92,7 +94,7 @@ void ProbePlaybackDevices(void)
}
}
void ProbeCaptureDevices(void)
void ProbeCaptureDevices()
{
CaptureDevices.clear();
@ -105,7 +107,7 @@ void ProbeCaptureDevices(void)
WAVEINCAPSW WaveCaps{};
if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
{
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(std::data(WaveCaps.szPname))};
int count{1};
std::string newname{basename};
@ -134,7 +136,7 @@ struct WinMMPlayback final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -143,6 +145,7 @@ struct WinMMPlayback final : public BackendBase {
al::semaphore mSem;
uint mIdx{0u};
std::array<WAVEHDR,4> mWaveBuffer{};
al::vector<char,16> mBuffer;
HWAVEOUT mOutHdl{nullptr};
@ -150,8 +153,6 @@ struct WinMMPlayback final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(WinMMPlayback)
};
WinMMPlayback::~WinMMPlayback()
@ -159,14 +160,11 @@ WinMMPlayback::~WinMMPlayback()
if(mOutHdl)
waveOutClose(mOutHdl);
mOutHdl = nullptr;
al_free(mWaveBuffer[0].lpData);
std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
}
/* WinMMPlayback::waveOutProc
*
* Posts a message to 'WinMMPlayback::mixerProc' everytime a WaveOut Buffer is
* Posts a message to 'WinMMPlayback::mixerProc' every time a WaveOut Buffer is
* completed and returns to the application (for more data)
*/
void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
@ -179,7 +177,7 @@ void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PT
FORCE_ALIGN int WinMMPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
@ -207,59 +205,55 @@ FORCE_ALIGN int WinMMPlayback::mixerProc()
}
void WinMMPlayback::open(const char *name)
void WinMMPlayback::open(std::string_view name)
{
if(PlaybackDevices.empty())
ProbePlaybackDevices();
// Find the Device ID matching the deviceName if valid
auto iter = name ?
auto iter = !name.empty() ?
std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) :
PlaybackDevices.cbegin();
if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter));
DevFmtType fmttype{mDevice->FmtType};
retry_open:
WAVEFORMATEX format{};
if(fmttype == DevFmtFloat)
{
format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
format.wBitsPerSample = 32;
}
else
{
format.wFormatTag = WAVE_FORMAT_PCM;
if(fmttype == DevFmtUByte || fmttype == DevFmtByte)
format.wBitsPerSample = 8;
else
format.wBitsPerSample = 16;
}
format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
format.nBlockAlign = static_cast<WORD>(format.wBitsPerSample * format.nChannels / 8);
format.nSamplesPerSec = mDevice->Frequency;
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
format.cbSize = 0;
HWAVEOUT outHandle{};
MMRESULT res{waveOutOpen(&outHandle, DeviceID, &format,
reinterpret_cast<DWORD_PTR>(&WinMMPlayback::waveOutProcC),
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
if(res != MMSYSERR_NOERROR)
{
do {
format = WAVEFORMATEX{};
if(fmttype == DevFmtFloat)
{
fmttype = DevFmtShort;
goto retry_open;
format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
format.wBitsPerSample = 32;
}
throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", res};
}
else
{
format.wFormatTag = WAVE_FORMAT_PCM;
if(fmttype == DevFmtUByte || fmttype == DevFmtByte)
format.wBitsPerSample = 8;
else
format.wBitsPerSample = 16;
}
format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
format.nBlockAlign = static_cast<WORD>(format.wBitsPerSample * format.nChannels / 8);
format.nSamplesPerSec = mDevice->Frequency;
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
format.cbSize = 0;
MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &format,
reinterpret_cast<DWORD_PTR>(&WinMMPlayback::waveOutProcC),
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
if(res == MMSYSERR_NOERROR) break;
if(fmttype != DevFmtFloat)
throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u",
res};
fmttype = DevFmtShort;
} while(true);
if(mOutHdl)
waveOutClose(mOutHdl);
mOutHdl = outHandle;
mFormat = format;
mDevice->DeviceName = PlaybackDevices[DeviceID];
@ -312,11 +306,11 @@ bool WinMMPlayback::reset()
}
setDefaultWFXChannelOrder();
uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()};
const uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()};
al_free(mWaveBuffer[0].lpData);
decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer);
mWaveBuffer[0] = WAVEHDR{};
mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
mWaveBuffer[0].lpData = mBuffer.data();
mWaveBuffer[0].dwBufferLength = BufferSize;
for(size_t i{1};i < mWaveBuffer.size();i++)
{
@ -369,16 +363,17 @@ struct WinMMCapture final : public BackendBase {
int captureProc();
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
std::atomic<uint> mReadable{0u};
al::semaphore mSem;
uint mIdx{0};
std::array<WAVEHDR,4> mWaveBuffer{};
al::vector<char,16> mBuffer;
HWAVEIN mInHdl{nullptr};
@ -388,8 +383,6 @@ struct WinMMCapture final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(WinMMCapture)
};
WinMMCapture::~WinMMCapture()
@ -398,14 +391,11 @@ WinMMCapture::~WinMMCapture()
if(mInHdl)
waveInClose(mInHdl);
mInHdl = nullptr;
al_free(mWaveBuffer[0].lpData);
std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
}
/* WinMMCapture::waveInProc
*
* Posts a message to 'WinMMCapture::captureProc' everytime a WaveIn Buffer is
* Posts a message to 'WinMMCapture::captureProc' every time a WaveIn Buffer is
* completed and returns to the application (with more data).
*/
void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
@ -417,7 +407,7 @@ void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR)
int WinMMCapture::captureProc()
{
althrd_setname(RECORD_THREAD_NAME);
althrd_setname(GetRecordThreadName());
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
@ -434,7 +424,8 @@ int WinMMCapture::captureProc()
WAVEHDR &waveHdr = mWaveBuffer[widx];
widx = (widx+1) % mWaveBuffer.size();
mRing->write(waveHdr.lpData, waveHdr.dwBytesRecorded / mFormat.nBlockAlign);
std::ignore = mRing->write(waveHdr.lpData,
waveHdr.dwBytesRecorded / mFormat.nBlockAlign);
mReadable.fetch_sub(1, std::memory_order_acq_rel);
waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR));
} while(--todo);
@ -445,18 +436,18 @@ int WinMMCapture::captureProc()
}
void WinMMCapture::open(const char *name)
void WinMMCapture::open(std::string_view name)
{
if(CaptureDevices.empty())
ProbeCaptureDevices();
// Find the Device ID matching the deviceName if valid
auto iter = name ?
auto iter = !name.empty() ?
std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) :
CaptureDevices.cbegin();
if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter));
switch(mDevice->FmtChans)
@ -470,6 +461,7 @@ void WinMMCapture::open(const char *name)
case DevFmtX61:
case DevFmtX71:
case DevFmtX714:
case DevFmtX7144:
case DevFmtX3D71:
case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
@ -513,14 +505,14 @@ void WinMMCapture::open(const char *name)
// Allocate circular memory buffer for the captured audio
// Make sure circular buffer is at least 100ms in size
uint CapturedDataSize{mDevice->BufferSize};
CapturedDataSize = static_cast<uint>(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size()));
const auto CapturedDataSize = std::max<size_t>(mDevice->BufferSize,
BufferSize*mWaveBuffer.size());
mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false);
al_free(mWaveBuffer[0].lpData);
decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer);
mWaveBuffer[0] = WAVEHDR{};
mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
mWaveBuffer[0].lpData = mBuffer.data();
mWaveBuffer[0].dwBufferLength = BufferSize;
for(size_t i{1};i < mWaveBuffer.size();++i)
{
@ -571,8 +563,8 @@ void WinMMCapture::stop()
mIdx = 0;
}
void WinMMCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
void WinMMCapture::captureSamples(std::byte *buffer, uint samples)
{ std::ignore = mRing->read(buffer, samples); }
uint WinMMCapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()); }
@ -586,26 +578,23 @@ bool WinMMBackendFactory::init()
bool WinMMBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
std::string WinMMBackendFactory::probe(BackendType type)
auto WinMMBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
auto add_device = [&outnames](const std::string &dname) -> void
{
/* +1 to also append the null char (to ensure a null-separated list and
* double-null terminated list).
*/
if(!dname.empty())
outnames.append(dname.c_str(), dname.length()+1);
};
{ if(!dname.empty()) outnames.emplace_back(dname); };
switch(type)
{
case BackendType::Playback:
ProbePlaybackDevices();
outnames.reserve(PlaybackDevices.size());
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case BackendType::Capture:
ProbeCaptureDevices();
outnames.reserve(CaptureDevices.size());
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}

View file

@ -5,15 +5,15 @@
struct WinMMBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_WINMM_H */