mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-07-16 00:54:54 +00:00
update openal-soft to 1.24.3
keeping the alt 87514151c4 (diff-73a8dc1ce58605f6c5ea53548454c3bae516ec5132a29c9d7ff7edf9730c75be)
This commit is contained in:
parent
12db0500e8
commit
ba32094b7b
276 changed files with 49304 additions and 8712 deletions
|
|
@ -29,7 +29,6 @@
|
|||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
|
@ -38,16 +37,15 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "albit.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 "dynload.h"
|
||||
#include "fmt/core.h"
|
||||
#include "ringbuffer.h"
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
|
@ -60,7 +58,7 @@ using namespace std::string_view_literals;
|
|||
[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "ALSA Default"sv; }
|
||||
|
||||
|
||||
#ifdef HAVE_DYNLOAD
|
||||
#if HAVE_DYNLOAD
|
||||
#define ALSA_FUNCS(MAGIC) \
|
||||
MAGIC(snd_strerror); \
|
||||
MAGIC(snd_pcm_open); \
|
||||
|
|
@ -272,7 +270,7 @@ struct SndCtlCardInfo {
|
|||
SndCtlCardInfo& operator=(const SndCtlCardInfo&) = delete;
|
||||
|
||||
[[nodiscard]]
|
||||
operator snd_ctl_card_info_t*() const noexcept { return mInfo; }
|
||||
operator snd_ctl_card_info_t*() const noexcept { return mInfo; } /* NOLINT(google-explicit-constructor) */
|
||||
};
|
||||
|
||||
struct SndPcmInfo {
|
||||
|
|
@ -284,7 +282,7 @@ struct SndPcmInfo {
|
|||
SndPcmInfo& operator=(const SndPcmInfo&) = delete;
|
||||
|
||||
[[nodiscard]]
|
||||
operator snd_pcm_info_t*() const noexcept { return mInfo; }
|
||||
operator snd_pcm_info_t*() const noexcept { return mInfo; } /* NOLINT(google-explicit-constructor) */
|
||||
};
|
||||
|
||||
struct SndCtl {
|
||||
|
|
@ -299,7 +297,7 @@ struct SndCtl {
|
|||
auto open(const char *name, int mode) { return snd_ctl_open(&mHandle, name, mode); }
|
||||
|
||||
[[nodiscard]]
|
||||
operator snd_ctl_t*() const noexcept { return mHandle; }
|
||||
operator snd_ctl_t*() const noexcept { return mHandle; } /* NOLINT(google-explicit-constructor) */
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -324,15 +322,15 @@ std::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
|
|||
const size_t seppos{customdevs->find('=', curpos)};
|
||||
if(seppos == curpos || seppos >= nextpos)
|
||||
{
|
||||
const std::string spec{customdevs->substr(curpos, nextpos-curpos)};
|
||||
ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str());
|
||||
const auto spec = std::string_view{*customdevs}.substr(curpos, nextpos-curpos);
|
||||
ERR("Invalid ALSA device specification \"{}\"", spec);
|
||||
}
|
||||
else
|
||||
{
|
||||
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());
|
||||
TRACE("Got device \"{}\", \"{}\"", entry.name, entry.device_name);
|
||||
}
|
||||
|
||||
if(nextpos < customdevs->length())
|
||||
|
|
@ -354,13 +352,13 @@ std::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
|
|||
err = handle.open(name.c_str(), 0);
|
||||
if(err < 0)
|
||||
{
|
||||
ERR("control open (hw:%d): %s\n", card, snd_strerror(err));
|
||||
ERR("control open (hw:{}): {}", card, snd_strerror(err));
|
||||
continue;
|
||||
}
|
||||
err = snd_ctl_card_info(handle, info);
|
||||
if(err < 0)
|
||||
{
|
||||
ERR("control hardware info (hw:%d): %s\n", card, snd_strerror(err));
|
||||
ERR("control hardware info (hw:{}): {}", card, snd_strerror(err));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -375,7 +373,7 @@ std::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
|
|||
while(true)
|
||||
{
|
||||
if(snd_ctl_pcm_next_device(handle, &dev) < 0)
|
||||
ERR("snd_ctl_pcm_next_device failed\n");
|
||||
ERR("snd_ctl_pcm_next_device failed");
|
||||
if(dev < 0) break;
|
||||
|
||||
snd_pcm_info_set_device(pcminfo, static_cast<uint>(dev));
|
||||
|
|
@ -385,42 +383,28 @@ std::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
|
|||
if(err < 0)
|
||||
{
|
||||
if(err != -ENOENT)
|
||||
ERR("control digital audio info (hw:%d): %s\n", card, snd_strerror(err));
|
||||
ERR("control digital audio info (hw:{}): {}", card, snd_strerror(err));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* "prefix-cardid-dev" */
|
||||
name = prefix_name(stream);
|
||||
name += '-';
|
||||
name += cardid;
|
||||
name += '-';
|
||||
name += std::to_string(dev);
|
||||
const std::string device_prefix{ConfigValueStr({}, "alsa"sv, name)
|
||||
name = fmt::format("{}-{}-{}", prefix_name(stream), cardid, dev);
|
||||
const auto device_prefix = std::string{ConfigValueStr({}, "alsa"sv, name)
|
||||
.value_or(card_prefix)};
|
||||
|
||||
/* "CardName, PcmName (CARD=cardid,DEV=dev)" */
|
||||
name = cardname;
|
||||
name += ", ";
|
||||
name += snd_pcm_info_get_name(pcminfo);
|
||||
name += " (CARD=";
|
||||
name += cardid;
|
||||
name += ",DEV=";
|
||||
name += std::to_string(dev);
|
||||
name += ')';
|
||||
name = fmt::format("{}, {} (CARD={},DEV={})", cardname, snd_pcm_info_get_name(pcminfo),
|
||||
cardid, dev);
|
||||
|
||||
/* "devprefixCARD=cardid,DEV=dev" */
|
||||
std::string device{device_prefix};
|
||||
device += "CARD=";
|
||||
device += cardid;
|
||||
device += ",DEV=";
|
||||
device += std::to_string(dev);
|
||||
auto device = fmt::format("{}CARD={},DEV={}", device_prefix, cardid, dev);
|
||||
|
||||
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());
|
||||
TRACE("Got device \"{}\", \"{}\"", entry.name, entry.device_name);
|
||||
}
|
||||
}
|
||||
if(err < 0)
|
||||
ERR("snd_card_next failed: %s\n", snd_strerror(err));
|
||||
ERR("snd_card_next failed: {}", snd_strerror(err));
|
||||
|
||||
return devlist;
|
||||
}
|
||||
|
|
@ -470,7 +454,7 @@ int verify_state(snd_pcm_t *handle)
|
|||
|
||||
|
||||
struct AlsaPlayback final : public BackendBase {
|
||||
AlsaPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit AlsaPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~AlsaPlayback() override;
|
||||
|
||||
int mixerProc();
|
||||
|
|
@ -507,29 +491,29 @@ int AlsaPlayback::mixerProc()
|
|||
SetRTPriority();
|
||||
althrd_setname(GetMixerThreadName());
|
||||
|
||||
const snd_pcm_uframes_t update_size{mDevice->UpdateSize};
|
||||
const snd_pcm_uframes_t buffer_size{mDevice->BufferSize};
|
||||
const snd_pcm_uframes_t update_size{mDevice->mUpdateSize};
|
||||
const snd_pcm_uframes_t buffer_size{mDevice->mBufferSize};
|
||||
while(!mKillNow.load(std::memory_order_acquire))
|
||||
{
|
||||
int state{verify_state(mPcmHandle)};
|
||||
if(state < 0)
|
||||
{
|
||||
ERR("Invalid state detected: %s\n", snd_strerror(state));
|
||||
mDevice->handleDisconnect("Bad state: %s", snd_strerror(state));
|
||||
ERR("Invalid state detected: {}", snd_strerror(state));
|
||||
mDevice->handleDisconnect("Bad state: {}", snd_strerror(state));
|
||||
break;
|
||||
}
|
||||
|
||||
snd_pcm_sframes_t avails{snd_pcm_avail_update(mPcmHandle)};
|
||||
if(avails < 0)
|
||||
{
|
||||
ERR("available update failed: %s\n", snd_strerror(static_cast<int>(avails)));
|
||||
ERR("available update failed: {}", snd_strerror(static_cast<int>(avails)));
|
||||
continue;
|
||||
}
|
||||
snd_pcm_uframes_t avail{static_cast<snd_pcm_uframes_t>(avails)};
|
||||
|
||||
if(avail > buffer_size)
|
||||
{
|
||||
WARN("available samples exceeds the buffer size\n");
|
||||
WARN("available samples exceeds the buffer size");
|
||||
snd_pcm_reset(mPcmHandle);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -542,12 +526,12 @@ int AlsaPlayback::mixerProc()
|
|||
int err{snd_pcm_start(mPcmHandle)};
|
||||
if(err < 0)
|
||||
{
|
||||
ERR("start failed: %s\n", snd_strerror(err));
|
||||
ERR("start failed: {}", snd_strerror(err));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if(snd_pcm_wait(mPcmHandle, 1000) == 0)
|
||||
ERR("Wait timeout... buffer size too low?\n");
|
||||
ERR("Wait timeout... buffer size too low?");
|
||||
continue;
|
||||
}
|
||||
avail -= avail%update_size;
|
||||
|
|
@ -563,7 +547,7 @@ int AlsaPlayback::mixerProc()
|
|||
int err{snd_pcm_mmap_begin(mPcmHandle, &areas, &offset, &frames)};
|
||||
if(err < 0)
|
||||
{
|
||||
ERR("mmap begin error: %s\n", snd_strerror(err));
|
||||
ERR("mmap begin error: {}", snd_strerror(err));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -574,7 +558,7 @@ int AlsaPlayback::mixerProc()
|
|||
snd_pcm_sframes_t commitres{snd_pcm_mmap_commit(mPcmHandle, offset, frames)};
|
||||
if(commitres < 0 || static_cast<snd_pcm_uframes_t>(commitres) != frames)
|
||||
{
|
||||
ERR("mmap commit error: %s\n",
|
||||
ERR("mmap commit error: {}",
|
||||
snd_strerror(commitres >= 0 ? -EPIPE : static_cast<int>(commitres)));
|
||||
break;
|
||||
}
|
||||
|
|
@ -591,28 +575,28 @@ int AlsaPlayback::mixerNoMMapProc()
|
|||
SetRTPriority();
|
||||
althrd_setname(GetMixerThreadName());
|
||||
|
||||
const snd_pcm_uframes_t update_size{mDevice->UpdateSize};
|
||||
const snd_pcm_uframes_t buffer_size{mDevice->BufferSize};
|
||||
const snd_pcm_uframes_t update_size{mDevice->mUpdateSize};
|
||||
const snd_pcm_uframes_t buffer_size{mDevice->mBufferSize};
|
||||
while(!mKillNow.load(std::memory_order_acquire))
|
||||
{
|
||||
int state{verify_state(mPcmHandle)};
|
||||
if(state < 0)
|
||||
{
|
||||
ERR("Invalid state detected: %s\n", snd_strerror(state));
|
||||
mDevice->handleDisconnect("Bad state: %s", snd_strerror(state));
|
||||
ERR("Invalid state detected: {}", snd_strerror(state));
|
||||
mDevice->handleDisconnect("Bad state: {}", snd_strerror(state));
|
||||
break;
|
||||
}
|
||||
|
||||
snd_pcm_sframes_t avail{snd_pcm_avail_update(mPcmHandle)};
|
||||
if(avail < 0)
|
||||
{
|
||||
ERR("available update failed: %s\n", snd_strerror(static_cast<int>(avail)));
|
||||
ERR("available update failed: {}", snd_strerror(static_cast<int>(avail)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if(static_cast<snd_pcm_uframes_t>(avail) > buffer_size)
|
||||
{
|
||||
WARN("available samples exceeds the buffer size\n");
|
||||
WARN("available samples exceeds the buffer size");
|
||||
snd_pcm_reset(mPcmHandle);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -624,12 +608,12 @@ int AlsaPlayback::mixerNoMMapProc()
|
|||
int err{snd_pcm_start(mPcmHandle)};
|
||||
if(err < 0)
|
||||
{
|
||||
ERR("start failed: %s\n", snd_strerror(err));
|
||||
ERR("start failed: {}", snd_strerror(err));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if(snd_pcm_wait(mPcmHandle, 1000) == 0)
|
||||
ERR("Wait timeout... buffer size too low?\n");
|
||||
ERR("Wait timeout... buffer size too low?");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -686,7 +670,7 @@ void AlsaPlayback::open(std::string_view 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", al::sizei(name), name.data()};
|
||||
"Device name \"{}\" not found", name};
|
||||
driver = iter->device_name;
|
||||
}
|
||||
else
|
||||
|
|
@ -695,13 +679,13 @@ void AlsaPlayback::open(std::string_view name)
|
|||
if(auto driveropt = ConfigValueStr({}, "alsa"sv, "device"sv))
|
||||
driver = std::move(driveropt).value();
|
||||
}
|
||||
TRACE("Opening device \"%s\"\n", driver.c_str());
|
||||
TRACE("Opening device \"{}\"", driver);
|
||||
|
||||
snd_pcm_t *pcmHandle{};
|
||||
int err{snd_pcm_open(&pcmHandle, driver.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)};
|
||||
if(err < 0)
|
||||
throw al::backend_exception{al::backend_error::NoDevice,
|
||||
"Could not open ALSA device \"%s\"", driver.c_str()};
|
||||
"Could not open ALSA device \"{}\"", driver};
|
||||
if(mPcmHandle)
|
||||
snd_pcm_close(mPcmHandle);
|
||||
mPcmHandle = pcmHandle;
|
||||
|
|
@ -709,7 +693,7 @@ void AlsaPlayback::open(std::string_view name)
|
|||
/* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */
|
||||
snd_config_update_free_global();
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
bool AlsaPlayback::reset()
|
||||
|
|
@ -740,15 +724,15 @@ bool AlsaPlayback::reset()
|
|||
break;
|
||||
}
|
||||
|
||||
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};
|
||||
bool allowmmap{GetConfigValueBool(mDevice->mDeviceName, "alsa"sv, "mmap"sv, true)};
|
||||
uint periodLen{static_cast<uint>(mDevice->mUpdateSize * 1000000_u64 / mDevice->mSampleRate)};
|
||||
uint bufferLen{static_cast<uint>(mDevice->mBufferSize * 1000000_u64 / mDevice->mSampleRate)};
|
||||
uint rate{mDevice->mSampleRate};
|
||||
|
||||
HwParamsPtr hp{CreateHwParams()};
|
||||
#define CHECK(x) do { \
|
||||
if(int err{x}; err < 0) \
|
||||
throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \
|
||||
throw al::backend_exception{al::backend_error::DeviceError, #x " failed: {}", \
|
||||
snd_strerror(err)}; \
|
||||
} while(0)
|
||||
CHECK(snd_pcm_hw_params_any(mPcmHandle, hp.get()));
|
||||
|
|
@ -798,21 +782,21 @@ bool AlsaPlayback::reset()
|
|||
else mDevice->FmtChans = DevFmtStereo;
|
||||
}
|
||||
/* set rate (implicitly constrains period/buffer parameters) */
|
||||
if(!GetConfigValueBool(mDevice->DeviceName, "alsa", "allow-resampler", false)
|
||||
if(!GetConfigValueBool(mDevice->mDeviceName, "alsa", "allow-resampler", false)
|
||||
|| !mDevice->Flags.test(FrequencyRequest))
|
||||
{
|
||||
if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0)
|
||||
WARN("Failed to disable ALSA resampler\n");
|
||||
WARN("Failed to disable ALSA resampler");
|
||||
}
|
||||
else if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 1) < 0)
|
||||
WARN("Failed to enable ALSA resampler\n");
|
||||
WARN("Failed to enable ALSA resampler");
|
||||
CHECK(snd_pcm_hw_params_set_rate_near(mPcmHandle, hp.get(), &rate, nullptr));
|
||||
/* set period time (implicitly constrains period/buffer parameters) */
|
||||
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));
|
||||
ERR("snd_pcm_hw_params_set_period_time_near failed: {}", snd_strerror(err));
|
||||
/* set buffer time (implicitly sets buffer size/bytes/time and period size/bytes) */
|
||||
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));
|
||||
ERR("snd_pcm_hw_params_set_buffer_time_near failed: {}", snd_strerror(err));
|
||||
/* install and prepare hardware configuration */
|
||||
CHECK(snd_pcm_hw_params(mPcmHandle, hp.get()));
|
||||
|
||||
|
|
@ -835,9 +819,9 @@ bool AlsaPlayback::reset()
|
|||
#undef CHECK
|
||||
sp = nullptr;
|
||||
|
||||
mDevice->BufferSize = static_cast<uint>(bufferSizeInFrames);
|
||||
mDevice->UpdateSize = static_cast<uint>(periodSizeInFrames);
|
||||
mDevice->Frequency = rate;
|
||||
mDevice->mBufferSize = static_cast<uint>(bufferSizeInFrames);
|
||||
mDevice->mUpdateSize = static_cast<uint>(periodSizeInFrames);
|
||||
mDevice->mSampleRate = rate;
|
||||
|
||||
setDefaultChannelOrder();
|
||||
|
||||
|
|
@ -850,7 +834,7 @@ void AlsaPlayback::start()
|
|||
HwParamsPtr hp{CreateHwParams()};
|
||||
#define CHECK(x) do { \
|
||||
if(int err{x}; err < 0) \
|
||||
throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \
|
||||
throw al::backend_exception{al::backend_error::DeviceError, #x " failed: {}", \
|
||||
snd_strerror(err)}; \
|
||||
} while(0)
|
||||
CHECK(snd_pcm_hw_params_current(mPcmHandle, hp.get()));
|
||||
|
|
@ -861,7 +845,7 @@ void AlsaPlayback::start()
|
|||
int (AlsaPlayback::*thread_func)(){};
|
||||
if(access == SND_PCM_ACCESS_RW_INTERLEAVED)
|
||||
{
|
||||
auto datalen = snd_pcm_frames_to_bytes(mPcmHandle, mDevice->UpdateSize);
|
||||
auto datalen = snd_pcm_frames_to_bytes(mPcmHandle, mDevice->mUpdateSize);
|
||||
mBuffer.resize(static_cast<size_t>(datalen));
|
||||
thread_func = &AlsaPlayback::mixerNoMMapProc;
|
||||
}
|
||||
|
|
@ -874,11 +858,11 @@ void AlsaPlayback::start()
|
|||
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(thread_func), this};
|
||||
mThread = std::thread{thread_func, this};
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start mixing thread: %s", e.what()};
|
||||
"Failed to start mixing thread: {}", e.what()};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -891,7 +875,7 @@ void AlsaPlayback::stop()
|
|||
mBuffer.clear();
|
||||
int err{snd_pcm_drop(mPcmHandle)};
|
||||
if(err < 0)
|
||||
ERR("snd_pcm_drop failed: %s\n", snd_strerror(err));
|
||||
ERR("snd_pcm_drop failed: {}", snd_strerror(err));
|
||||
}
|
||||
|
||||
ClockLatency AlsaPlayback::getClockLatency()
|
||||
|
|
@ -903,18 +887,18 @@ ClockLatency AlsaPlayback::getClockLatency()
|
|||
int err{snd_pcm_delay(mPcmHandle, &delay)};
|
||||
if(err < 0)
|
||||
{
|
||||
ERR("Failed to get pcm delay: %s\n", snd_strerror(err));
|
||||
ERR("Failed to get pcm delay: {}", snd_strerror(err));
|
||||
delay = 0;
|
||||
}
|
||||
ret.Latency = std::chrono::seconds{std::max<snd_pcm_sframes_t>(0, delay)};
|
||||
ret.Latency /= mDevice->Frequency;
|
||||
ret.Latency /= mDevice->mSampleRate;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
struct AlsaCapture final : public BackendBase {
|
||||
AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~AlsaCapture() override;
|
||||
|
||||
void open(std::string_view name) override;
|
||||
|
|
@ -954,7 +938,7 @@ void AlsaCapture::open(std::string_view 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", al::sizei(name), name.data()};
|
||||
"Device name \"{}\" not found", name};
|
||||
driver = iter->device_name;
|
||||
}
|
||||
else
|
||||
|
|
@ -964,10 +948,10 @@ void AlsaCapture::open(std::string_view name)
|
|||
driver = std::move(driveropt).value();
|
||||
}
|
||||
|
||||
TRACE("Opening device \"%s\"\n", driver.c_str());
|
||||
TRACE("Opening device \"{}\"", driver);
|
||||
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()};
|
||||
"Could not open ALSA device \"{}\"", driver};
|
||||
|
||||
/* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */
|
||||
snd_config_update_free_global();
|
||||
|
|
@ -998,16 +982,16 @@ void AlsaCapture::open(std::string_view name)
|
|||
break;
|
||||
}
|
||||
|
||||
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)};
|
||||
snd_pcm_uframes_t bufferSizeInFrames{std::max(mDevice->mBufferSize,
|
||||
100u*mDevice->mSampleRate/1000u)};
|
||||
snd_pcm_uframes_t periodSizeInFrames{std::min(mDevice->mBufferSize,
|
||||
25u*mDevice->mSampleRate/1000u)};
|
||||
|
||||
bool needring{false};
|
||||
HwParamsPtr hp{CreateHwParams()};
|
||||
#define CHECK(x) do { \
|
||||
if(int err{x}; err < 0) \
|
||||
throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \
|
||||
throw al::backend_exception{al::backend_error::DeviceError, #x " failed: {}", \
|
||||
snd_strerror(err)}; \
|
||||
} while(0)
|
||||
CHECK(snd_pcm_hw_params_any(mPcmHandle, hp.get()));
|
||||
|
|
@ -1018,11 +1002,11 @@ void AlsaCapture::open(std::string_view name)
|
|||
/* set channels (implicitly sets frame bits) */
|
||||
CHECK(snd_pcm_hw_params_set_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt()));
|
||||
/* set rate (implicitly constrains period/buffer parameters) */
|
||||
CHECK(snd_pcm_hw_params_set_rate(mPcmHandle, hp.get(), mDevice->Frequency, 0));
|
||||
CHECK(snd_pcm_hw_params_set_rate(mPcmHandle, hp.get(), mDevice->mSampleRate, 0));
|
||||
/* set buffer size in frame units (implicitly sets period size/bytes/time and buffer time/bytes) */
|
||||
if(snd_pcm_hw_params_set_buffer_size_min(mPcmHandle, hp.get(), &bufferSizeInFrames) < 0)
|
||||
{
|
||||
TRACE("Buffer too large, using intermediate ring buffer\n");
|
||||
TRACE("Buffer too large, using intermediate ring buffer");
|
||||
needring = true;
|
||||
CHECK(snd_pcm_hw_params_set_buffer_size_near(mPcmHandle, hp.get(), &bufferSizeInFrames));
|
||||
}
|
||||
|
|
@ -1036,20 +1020,20 @@ void AlsaCapture::open(std::string_view name)
|
|||
hp = nullptr;
|
||||
|
||||
if(needring)
|
||||
mRing = RingBuffer::Create(mDevice->BufferSize, mDevice->frameSizeFromFmt(), false);
|
||||
mRing = RingBuffer::Create(mDevice->mBufferSize, mDevice->frameSizeFromFmt(), false);
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
|
||||
void AlsaCapture::start()
|
||||
{
|
||||
if(int err{snd_pcm_prepare(mPcmHandle)}; err < 0)
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_prepare failed: %s",
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_prepare failed: {}",
|
||||
snd_strerror(err)};
|
||||
|
||||
if(int err{snd_pcm_start(mPcmHandle)}; err < 0)
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_start failed: %s",
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_start failed: {}",
|
||||
snd_strerror(err)};
|
||||
|
||||
mDoCapture = true;
|
||||
|
|
@ -1073,7 +1057,7 @@ void AlsaCapture::stop()
|
|||
mBuffer = std::move(temp);
|
||||
}
|
||||
if(int err{snd_pcm_drop(mPcmHandle)}; err < 0)
|
||||
ERR("drop failed: %s\n", snd_strerror(err));
|
||||
ERR("snd_pcm_drop failed: {}", snd_strerror(err));
|
||||
mDoCapture = false;
|
||||
}
|
||||
|
||||
|
|
@ -1109,7 +1093,7 @@ void AlsaCapture::captureSamples(std::byte *buffer, uint 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)));
|
||||
ERR("read error: {}", snd_strerror(static_cast<int>(amt)));
|
||||
|
||||
if(amt == -EAGAIN)
|
||||
continue;
|
||||
|
|
@ -1123,8 +1107,8 @@ void AlsaCapture::captureSamples(std::byte *buffer, uint samples)
|
|||
if(amt < 0)
|
||||
{
|
||||
const char *err{snd_strerror(static_cast<int>(amt))};
|
||||
ERR("restore error: %s\n", err);
|
||||
mDevice->handleDisconnect("Capture recovery failure: %s", err);
|
||||
ERR("restore error: {}", err);
|
||||
mDevice->handleDisconnect("Capture recovery failure: {}", err);
|
||||
break;
|
||||
}
|
||||
/* If the amount available is less than what's asked, we lost it
|
||||
|
|
@ -1149,7 +1133,7 @@ uint AlsaCapture::availableSamples()
|
|||
avail = snd_pcm_avail_update(mPcmHandle);
|
||||
if(avail < 0)
|
||||
{
|
||||
ERR("avail update failed: %s\n", snd_strerror(static_cast<int>(avail)));
|
||||
ERR("snd_pcm_avail_update failed: {}", snd_strerror(static_cast<int>(avail)));
|
||||
|
||||
avail = snd_pcm_recover(mPcmHandle, static_cast<int>(avail), 1);
|
||||
if(avail >= 0)
|
||||
|
|
@ -1162,29 +1146,29 @@ uint AlsaCapture::availableSamples()
|
|||
if(avail < 0)
|
||||
{
|
||||
const char *err{snd_strerror(static_cast<int>(avail))};
|
||||
ERR("restore error: %s\n", err);
|
||||
mDevice->handleDisconnect("Capture recovery failure: %s", err);
|
||||
ERR("restore error: {}", err);
|
||||
mDevice->handleDisconnect("Capture recovery failure: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
if(!mRing)
|
||||
{
|
||||
if(avail < 0) avail = 0;
|
||||
avail = std::max<snd_pcm_sframes_t>(avail, 0);
|
||||
avail += snd_pcm_bytes_to_frames(mPcmHandle, static_cast<ssize_t>(mBuffer.size()));
|
||||
if(avail > mLastAvail) mLastAvail = avail;
|
||||
mLastAvail = std::max(mLastAvail, avail);
|
||||
return static_cast<uint>(mLastAvail);
|
||||
}
|
||||
|
||||
while(avail > 0)
|
||||
{
|
||||
auto vec = mRing->getWriteVector();
|
||||
if(vec.first.len == 0) break;
|
||||
if(vec[0].len == 0) break;
|
||||
|
||||
snd_pcm_sframes_t amt{std::min(static_cast<snd_pcm_sframes_t>(vec.first.len), avail)};
|
||||
amt = snd_pcm_readi(mPcmHandle, vec.first.buf, static_cast<snd_pcm_uframes_t>(amt));
|
||||
snd_pcm_sframes_t amt{std::min(static_cast<snd_pcm_sframes_t>(vec[0].len), avail)};
|
||||
amt = snd_pcm_readi(mPcmHandle, vec[0].buf, static_cast<snd_pcm_uframes_t>(amt));
|
||||
if(amt < 0)
|
||||
{
|
||||
ERR("read error: %s\n", snd_strerror(static_cast<int>(amt)));
|
||||
ERR("read error: {}", snd_strerror(static_cast<int>(amt)));
|
||||
|
||||
if(amt == -EAGAIN)
|
||||
continue;
|
||||
|
|
@ -1199,8 +1183,8 @@ uint AlsaCapture::availableSamples()
|
|||
if(amt < 0)
|
||||
{
|
||||
const char *err{snd_strerror(static_cast<int>(amt))};
|
||||
ERR("restore error: %s\n", err);
|
||||
mDevice->handleDisconnect("Capture recovery failure: %s", err);
|
||||
ERR("restore error: {}", err);
|
||||
mDevice->handleDisconnect("Capture recovery failure: {}", err);
|
||||
break;
|
||||
}
|
||||
avail = amt;
|
||||
|
|
@ -1222,11 +1206,11 @@ ClockLatency AlsaCapture::getClockLatency()
|
|||
int err{snd_pcm_delay(mPcmHandle, &delay)};
|
||||
if(err < 0)
|
||||
{
|
||||
ERR("Failed to get pcm delay: %s\n", snd_strerror(err));
|
||||
ERR("Failed to get pcm delay: {}", snd_strerror(err));
|
||||
delay = 0;
|
||||
}
|
||||
ret.Latency = std::chrono::seconds{std::max<snd_pcm_sframes_t>(0, delay)};
|
||||
ret.Latency /= mDevice->Frequency;
|
||||
ret.Latency /= mDevice->mSampleRate;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -1236,13 +1220,13 @@ ClockLatency AlsaCapture::getClockLatency()
|
|||
|
||||
bool AlsaBackendFactory::init()
|
||||
{
|
||||
#ifdef HAVE_DYNLOAD
|
||||
#if HAVE_DYNLOAD
|
||||
if(!alsa_handle)
|
||||
{
|
||||
alsa_handle = LoadLib("libasound.so.2");
|
||||
if(!alsa_handle)
|
||||
{
|
||||
WARN("Failed to load %s\n", "libasound.so.2");
|
||||
WARN("Failed to load {}", "libasound.so.2");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1256,7 +1240,7 @@ bool AlsaBackendFactory::init()
|
|||
|
||||
if(!missing_funcs.empty())
|
||||
{
|
||||
WARN("Missing expected functions:%s\n", missing_funcs.c_str());
|
||||
WARN("Missing expected functions:{}", missing_funcs);
|
||||
CloseLib(alsa_handle);
|
||||
alsa_handle = nullptr;
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -3,26 +3,18 @@
|
|||
|
||||
#include "base.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <utility>
|
||||
|
||||
#include "core/devformat.h"
|
||||
|
||||
|
||||
namespace al {
|
||||
auto backend_exception::make_string(fmt::string_view fmt, fmt::format_args args) -> std::string
|
||||
{ return fmt::vformat(fmt, std::move(args)); }
|
||||
|
||||
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;
|
||||
|
||||
} // namespace al
|
||||
|
||||
|
||||
|
|
@ -50,8 +42,8 @@ ClockLatency BackendBase::getClockLatency()
|
|||
* any given time during playback. Without a more accurate measurement from
|
||||
* the output, this is an okay approximation.
|
||||
*/
|
||||
ret.Latency = std::chrono::seconds{mDevice->BufferSize - mDevice->UpdateSize};
|
||||
ret.Latency /= mDevice->Frequency;
|
||||
ret.Latency = std::chrono::seconds{mDevice->mBufferSize - mDevice->mUpdateSize};
|
||||
ret.Latency /= mDevice->mSampleRate;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@
|
|||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <ratio>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "alc/events.h"
|
||||
#include "core/device.h"
|
||||
#include "core/except.h"
|
||||
#include "alc/events.h"
|
||||
#include "fmt/core.h"
|
||||
|
||||
|
||||
using uint = unsigned int;
|
||||
|
|
@ -35,11 +35,12 @@ struct BackendBase {
|
|||
virtual ClockLatency getClockLatency();
|
||||
|
||||
DeviceBase *const mDevice;
|
||||
std::string mDeviceName;
|
||||
|
||||
BackendBase() = delete;
|
||||
BackendBase(const BackendBase&) = delete;
|
||||
BackendBase(BackendBase&&) = delete;
|
||||
BackendBase(DeviceBase *device) noexcept : mDevice{device} { }
|
||||
explicit BackendBase(DeviceBase *device) noexcept : mDevice{device} { }
|
||||
virtual ~BackendBase() = default;
|
||||
|
||||
void operator=(const BackendBase&) = delete;
|
||||
|
|
@ -102,15 +103,20 @@ enum class backend_error {
|
|||
class backend_exception final : public base_exception {
|
||||
backend_error mErrorCode;
|
||||
|
||||
static auto make_string(fmt::string_view fmt, fmt::format_args args) -> std::string;
|
||||
|
||||
public:
|
||||
#ifdef __MINGW32__
|
||||
[[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]]
|
||||
#else
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
#endif
|
||||
backend_exception(backend_error code, const char *msg, ...);
|
||||
template<typename ...Args>
|
||||
backend_exception(backend_error code, fmt::format_string<Args...> fmt, Args&& ...args)
|
||||
: base_exception{make_string(fmt, fmt::make_format_args(args...))}, mErrorCode{code}
|
||||
{ }
|
||||
backend_exception(const backend_exception&) = default;
|
||||
backend_exception(backend_exception&&) = default;
|
||||
~backend_exception() override;
|
||||
|
||||
backend_exception& operator=(const backend_exception&) = default;
|
||||
backend_exception& operator=(backend_exception&&) = default;
|
||||
|
||||
[[nodiscard]] auto errorCode() const noexcept -> backend_error { return mErrorCode; }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,9 @@
|
|||
|
||||
#include <cinttypes>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -32,13 +34,13 @@
|
|||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
#include "alnumeric.h"
|
||||
#include "alstring.h"
|
||||
#include "core/converter.h"
|
||||
#include "core/device.h"
|
||||
#include "core/logging.h"
|
||||
#include "fmt/core.h"
|
||||
#include "ringbuffer.h"
|
||||
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
|
|
@ -56,10 +58,40 @@ namespace {
|
|||
constexpr auto OutputElement = 0;
|
||||
constexpr auto InputElement = 1;
|
||||
|
||||
// These following arrays should always be defined in ascending AudioChannelLabel value order
|
||||
constexpr std::array<AudioChannelLabel, 1> MonoChanMap { kAudioChannelLabel_Mono };
|
||||
constexpr std::array<AudioChannelLabel, 2> StereoChanMap { kAudioChannelLabel_Left, kAudioChannelLabel_Right};
|
||||
constexpr std::array<AudioChannelLabel, 4> QuadChanMap {
|
||||
kAudioChannelLabel_Left, kAudioChannelLabel_Right,
|
||||
kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround
|
||||
};
|
||||
constexpr std::array<AudioChannelLabel, 6> X51ChanMap {
|
||||
kAudioChannelLabel_Left, kAudioChannelLabel_Right,
|
||||
kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen,
|
||||
kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround
|
||||
};
|
||||
constexpr std::array<AudioChannelLabel, 6> X51RearChanMap {
|
||||
kAudioChannelLabel_Left, kAudioChannelLabel_Right,
|
||||
kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen,
|
||||
kAudioChannelLabel_RearSurroundRight, kAudioChannelLabel_RearSurroundLeft
|
||||
};
|
||||
constexpr std::array<AudioChannelLabel, 7> X61ChanMap {
|
||||
kAudioChannelLabel_Left, kAudioChannelLabel_Right,
|
||||
kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen,
|
||||
kAudioChannelLabel_CenterSurround,
|
||||
kAudioChannelLabel_RearSurroundRight, kAudioChannelLabel_RearSurroundLeft
|
||||
};
|
||||
constexpr std::array<AudioChannelLabel, 8> X71ChanMap {
|
||||
kAudioChannelLabel_Left, kAudioChannelLabel_Right,
|
||||
kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen,
|
||||
kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround,
|
||||
kAudioChannelLabel_LeftCenter, kAudioChannelLabel_RightCenter
|
||||
};
|
||||
|
||||
struct FourCCPrinter {
|
||||
char mString[sizeof(UInt32) + 1]{};
|
||||
|
||||
constexpr FourCCPrinter(UInt32 code) noexcept
|
||||
explicit constexpr FourCCPrinter(UInt32 code) noexcept
|
||||
{
|
||||
for(size_t i{0};i < sizeof(UInt32);++i)
|
||||
{
|
||||
|
|
@ -73,7 +105,7 @@ struct FourCCPrinter {
|
|||
code >>= 8;
|
||||
}
|
||||
}
|
||||
constexpr FourCCPrinter(int code) noexcept : FourCCPrinter{static_cast<UInt32>(code)} { }
|
||||
explicit constexpr FourCCPrinter(OSStatus code) noexcept : FourCCPrinter{static_cast<UInt32>(code)} { }
|
||||
|
||||
constexpr const char *c_str() const noexcept { return mString; }
|
||||
};
|
||||
|
|
@ -159,19 +191,19 @@ std::string GetDeviceName(AudioDeviceID devId)
|
|||
/* Clear extraneous nul chars that may have been written with the name
|
||||
* string, and return it.
|
||||
*/
|
||||
while(!devname.back())
|
||||
while(!devname.empty() && !devname.back())
|
||||
devname.pop_back();
|
||||
return devname;
|
||||
}
|
||||
|
||||
UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture)
|
||||
auto GetDeviceChannelCount(AudioDeviceID devId, bool isCapture) -> UInt32
|
||||
{
|
||||
UInt32 propSize{};
|
||||
auto propSize = UInt32{};
|
||||
auto err = GetDevPropertySize(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0,
|
||||
&propSize);
|
||||
if(err)
|
||||
{
|
||||
ERR("kAudioDevicePropertyStreamConfiguration size query failed: '%s' (%u)\n",
|
||||
ERR("kAudioDevicePropertyStreamConfiguration size query failed: '{}' ({})",
|
||||
FourCCPrinter{err}.c_str(), err);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -183,15 +215,14 @@ UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture)
|
|||
buflist);
|
||||
if(err)
|
||||
{
|
||||
ERR("kAudioDevicePropertyStreamConfiguration query failed: '%s' (%u)\n",
|
||||
ERR("kAudioDevicePropertyStreamConfiguration query failed: '{}' ({})",
|
||||
FourCCPrinter{err}.c_str(), err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
UInt32 numChannels{0};
|
||||
auto numChannels = UInt32{0};
|
||||
for(size_t i{0};i < buflist->mNumberBuffers;++i)
|
||||
numChannels += buflist->mBuffers[i].mNumberChannels;
|
||||
|
||||
return numChannels;
|
||||
}
|
||||
|
||||
|
|
@ -201,14 +232,14 @@ void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
|
|||
UInt32 propSize{};
|
||||
if(auto err = GetHwPropertySize(kAudioHardwarePropertyDevices, &propSize))
|
||||
{
|
||||
ERR("Failed to get device list size: %u\n", err);
|
||||
ERR("Failed to get device list size: {}", err);
|
||||
return;
|
||||
}
|
||||
|
||||
auto devIds = std::vector<AudioDeviceID>(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown);
|
||||
if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data()))
|
||||
{
|
||||
ERR("Failed to get device list: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
|
||||
ERR("Failed to get device list: '{}' ({})", FourCCPrinter{err}.c_str(), err);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -223,7 +254,7 @@ void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
|
|||
{
|
||||
newdevs.emplace_back(DeviceEntry{defaultId, GetDeviceName(defaultId)});
|
||||
const auto &entry = newdevs.back();
|
||||
TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId);
|
||||
TRACE("Got device: {} = ID {}", entry.mName, entry.mId);
|
||||
}
|
||||
for(const AudioDeviceID devId : devIds)
|
||||
{
|
||||
|
|
@ -240,7 +271,7 @@ void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
|
|||
{
|
||||
newdevs.emplace_back(DeviceEntry{devId, GetDeviceName(devId)});
|
||||
const auto &entry = newdevs.back();
|
||||
TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId);
|
||||
TRACE("Got device: {} = ID {}", entry.mName, entry.mId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -255,14 +286,12 @@ void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
|
|||
{ return entry.mName == curitem->mName; };
|
||||
if(std::find_if(newdevs.begin(), curitem, check_match) != curitem)
|
||||
{
|
||||
std::string name{curitem->mName};
|
||||
size_t count{1};
|
||||
auto name = std::string{curitem->mName};
|
||||
auto count = 1_uz;
|
||||
auto check_name = [&name](const DeviceEntry &entry) -> bool
|
||||
{ return entry.mName == name; };
|
||||
do {
|
||||
name = curitem->mName;
|
||||
name += " #";
|
||||
name += std::to_string(++count);
|
||||
name = fmt::format("{} #{}", curitem->mName, ++count);
|
||||
} while(std::find_if(newdevs.begin(), curitem, check_name) != curitem);
|
||||
curitem->mName = std::move(name);
|
||||
}
|
||||
|
|
@ -277,18 +306,18 @@ struct DeviceHelper {
|
|||
DeviceHelper()
|
||||
{
|
||||
AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice,
|
||||
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain};
|
||||
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
|
||||
OSStatus status = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil);
|
||||
if (status != noErr)
|
||||
ERR("AudioObjectAddPropertyListener fail: %d", status);
|
||||
ERR("AudioObjectAddPropertyListener fail: {}", status);
|
||||
}
|
||||
~DeviceHelper()
|
||||
{
|
||||
AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice,
|
||||
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain};
|
||||
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
|
||||
OSStatus status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil);
|
||||
if (status != noErr)
|
||||
ERR("AudioObjectRemovePropertyListener fail: %d", status);
|
||||
ERR("AudioObjectRemovePropertyListener fail: {}", status);
|
||||
}
|
||||
|
||||
static OSStatus DeviceListenerProc(AudioObjectID /*inObjectID*/, UInt32 inNumberAddresses,
|
||||
|
|
@ -322,7 +351,7 @@ static constexpr char ca_device[] = "CoreAudio Default";
|
|||
|
||||
|
||||
struct CoreAudioPlayback final : public BackendBase {
|
||||
CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~CoreAudioPlayback() override;
|
||||
|
||||
OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags,
|
||||
|
|
@ -377,7 +406,7 @@ void CoreAudioPlayback::open(std::string_view 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", al::sizei(name), name.data()};
|
||||
"Device name \"{}\" not found", name};
|
||||
|
||||
audioDevice = devmatch->mId;
|
||||
}
|
||||
|
|
@ -385,8 +414,8 @@ void CoreAudioPlayback::open(std::string_view name)
|
|||
if(name.empty())
|
||||
name = ca_device;
|
||||
else if(name != ca_device)
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
|
||||
al::sizei(name), name.data()};
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
|
||||
name};
|
||||
#endif
|
||||
|
||||
/* open the default output unit */
|
||||
|
|
@ -410,7 +439,7 @@ void CoreAudioPlayback::open(std::string_view name)
|
|||
OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)};
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{al::backend_error::NoDevice,
|
||||
"Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
|
||||
"Could not create component instance: '{}' ({})", FourCCPrinter{err}.c_str(), err};
|
||||
|
||||
#if CAN_ENUMERATE
|
||||
if(audioDevice != kAudioDeviceUnknown)
|
||||
|
|
@ -421,7 +450,7 @@ void CoreAudioPlayback::open(std::string_view name)
|
|||
err = AudioUnitInitialize(audioUnit);
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
|
||||
"Could not initialize audio unit: '{}' ({})", 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.
|
||||
|
|
@ -435,7 +464,7 @@ void CoreAudioPlayback::open(std::string_view name)
|
|||
|
||||
#if CAN_ENUMERATE
|
||||
if(!name.empty())
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
else
|
||||
{
|
||||
UInt32 propSize{sizeof(audioDevice)};
|
||||
|
|
@ -444,8 +473,8 @@ void CoreAudioPlayback::open(std::string_view name)
|
|||
kAudioUnitScope_Global, OutputElement, &audioDevice, &propSize);
|
||||
|
||||
std::string devname{GetDeviceName(audioDevice)};
|
||||
if(!devname.empty()) mDevice->DeviceName = std::move(devname);
|
||||
else mDevice->DeviceName = "Unknown Device Name";
|
||||
if(!devname.empty()) mDeviceName = std::move(devname);
|
||||
else mDeviceName = "Unknown Device Name";
|
||||
}
|
||||
|
||||
if(audioDevice != kAudioDeviceUnknown)
|
||||
|
|
@ -454,16 +483,16 @@ void CoreAudioPlayback::open(std::string_view name)
|
|||
err = GetDevProperty(audioDevice, kAudioDevicePropertyDataSource, false,
|
||||
kAudioObjectPropertyElementMaster, sizeof(type), &type);
|
||||
if(err != noErr)
|
||||
ERR("Failed to get audio device type: %u\n", err);
|
||||
WARN("Failed to get audio device type: '{}' ({})", FourCCPrinter{err}.c_str(), err);
|
||||
else
|
||||
{
|
||||
TRACE("Got device type '%s'\n", FourCCPrinter{type}.c_str());
|
||||
TRACE("Got device type '{}'", FourCCPrinter{type}.c_str());
|
||||
mDevice->Flags.set(DirectEar, (type == kIOAudioOutputPortSubTypeHeadphones));
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
@ -471,7 +500,7 @@ bool CoreAudioPlayback::reset()
|
|||
{
|
||||
OSStatus err{AudioUnitUninitialize(mAudioUnit)};
|
||||
if(err != noErr)
|
||||
ERR("AudioUnitUninitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
|
||||
ERR("AudioUnitUninitialize failed: '{}' ({})", FourCCPrinter{err}.c_str(), err);
|
||||
|
||||
/* retrieve default output unit's properties (output side) */
|
||||
AudioStreamBasicDescription streamFormat{};
|
||||
|
|
@ -480,33 +509,75 @@ bool CoreAudioPlayback::reset()
|
|||
OutputElement, &streamFormat, &size);
|
||||
if(err != noErr || size != sizeof(streamFormat))
|
||||
{
|
||||
ERR("AudioUnitGetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(),
|
||||
ERR("AudioUnitGetProperty(StreamFormat) failed: '{}' ({})", FourCCPrinter{err}.c_str(),
|
||||
err);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
TRACE("Output streamFormat of default output unit -\n");
|
||||
TRACE(" streamFormat.mFramesPerPacket = %d\n", streamFormat.mFramesPerPacket);
|
||||
TRACE(" streamFormat.mChannelsPerFrame = %d\n", streamFormat.mChannelsPerFrame);
|
||||
TRACE(" streamFormat.mBitsPerChannel = %d\n", streamFormat.mBitsPerChannel);
|
||||
TRACE(" streamFormat.mBytesPerPacket = %d\n", streamFormat.mBytesPerPacket);
|
||||
TRACE(" streamFormat.mBytesPerFrame = %d\n", streamFormat.mBytesPerFrame);
|
||||
TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate);
|
||||
#endif
|
||||
|
||||
/* Use the sample rate from the output unit's current parameters, but reset
|
||||
* everything else.
|
||||
*/
|
||||
if(mDevice->Frequency != streamFormat.mSampleRate)
|
||||
if(mDevice->mSampleRate != streamFormat.mSampleRate)
|
||||
{
|
||||
mDevice->BufferSize = static_cast<uint>(mDevice->BufferSize*streamFormat.mSampleRate/
|
||||
mDevice->Frequency + 0.5);
|
||||
mDevice->Frequency = static_cast<uint>(streamFormat.mSampleRate);
|
||||
mDevice->mBufferSize = static_cast<uint>(mDevice->mBufferSize*streamFormat.mSampleRate/
|
||||
mDevice->mSampleRate + 0.5);
|
||||
mDevice->mSampleRate = static_cast<uint>(streamFormat.mSampleRate);
|
||||
}
|
||||
|
||||
/* FIXME: How to tell what channels are what in the output device, and how
|
||||
* to specify what we're giving? e.g. 6.0 vs 5.1
|
||||
struct ChannelMap {
|
||||
DevFmtChannels fmt;
|
||||
al::span<const AudioChannelLabel> map;
|
||||
bool is_51rear;
|
||||
};
|
||||
|
||||
static constexpr std::array<ChannelMap,7> chanmaps{{
|
||||
{ DevFmtX71, X71ChanMap, false },
|
||||
{ DevFmtX61, X61ChanMap, false },
|
||||
{ DevFmtX51, X51ChanMap, false },
|
||||
{ DevFmtX51, X51RearChanMap, true },
|
||||
{ DevFmtQuad, QuadChanMap, false },
|
||||
{ DevFmtStereo, StereoChanMap, false },
|
||||
{ DevFmtMono, MonoChanMap, false }
|
||||
}};
|
||||
|
||||
if(!mDevice->Flags.test(ChannelsRequest))
|
||||
{
|
||||
auto propSize = UInt32{};
|
||||
auto writable = Boolean{};
|
||||
|
||||
err = AudioUnitGetPropertyInfo(mAudioUnit, kAudioUnitProperty_AudioChannelLayout,
|
||||
kAudioUnitScope_Output, OutputElement, &propSize, &writable);
|
||||
if(err == noErr)
|
||||
{
|
||||
auto layout_data = std::make_unique<char[]>(propSize);
|
||||
auto *layout = reinterpret_cast<AudioChannelLayout*>(layout_data.get());
|
||||
|
||||
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_AudioChannelLayout,
|
||||
kAudioUnitScope_Output, OutputElement, layout, &propSize);
|
||||
if(err == noErr)
|
||||
{
|
||||
auto descs = al::span{std::data(layout->mChannelDescriptions),
|
||||
layout->mNumberChannelDescriptions};
|
||||
auto labels = std::vector<AudioChannelLayoutTag>(descs.size());
|
||||
|
||||
std::transform(descs.begin(), descs.end(), labels.begin(),
|
||||
std::mem_fn(&AudioChannelDescription::mChannelLabel));
|
||||
sort(labels.begin(), labels.end());
|
||||
|
||||
auto check_labels = [&labels](const ChannelMap &chanmap) -> bool
|
||||
{
|
||||
return std::includes(labels.begin(), labels.end(), chanmap.map.begin(),
|
||||
chanmap.map.end());
|
||||
};
|
||||
auto chaniter = std::find_if(chanmaps.cbegin(), chanmaps.cend(), check_labels);
|
||||
if(chaniter != chanmaps.cend())
|
||||
mDevice->FmtChans = chaniter->fmt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: Also set kAudioUnitProperty_AudioChannelLayout according to the AL
|
||||
* device's channel configuration.
|
||||
*/
|
||||
streamFormat.mChannelsPerFrame = mDevice->channelsFromFmt();
|
||||
|
||||
|
|
@ -548,7 +619,7 @@ bool CoreAudioPlayback::reset()
|
|||
OutputElement, &streamFormat, sizeof(streamFormat));
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitSetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(),
|
||||
ERR("AudioUnitSetProperty(StreamFormat) failed: '{}' ({})", FourCCPrinter{err}.c_str(),
|
||||
err);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -566,7 +637,7 @@ bool CoreAudioPlayback::reset()
|
|||
kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct));
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitSetProperty(SetRenderCallback) failed: '%s' (%u)\n",
|
||||
ERR("AudioUnitSetProperty(SetRenderCallback) failed: '{}' ({})",
|
||||
FourCCPrinter{err}.c_str(), err);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -575,7 +646,7 @@ bool CoreAudioPlayback::reset()
|
|||
err = AudioUnitInitialize(mAudioUnit);
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitInitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
|
||||
ERR("AudioUnitInitialize failed: '{}' ({})", FourCCPrinter{err}.c_str(), err);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -587,19 +658,19 @@ void CoreAudioPlayback::start()
|
|||
const OSStatus err{AudioOutputUnitStart(mAudioUnit)};
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
|
||||
"AudioOutputUnitStart failed: '{}' ({})", FourCCPrinter{err}.c_str(), err};
|
||||
}
|
||||
|
||||
void CoreAudioPlayback::stop()
|
||||
{
|
||||
OSStatus err{AudioOutputUnitStop(mAudioUnit)};
|
||||
if(err != noErr)
|
||||
ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
|
||||
ERR("AudioOutputUnitStop failed: '{}' ({})", FourCCPrinter{err}.c_str(), err);
|
||||
}
|
||||
|
||||
|
||||
struct CoreAudioCapture final : public BackendBase {
|
||||
CoreAudioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit CoreAudioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~CoreAudioCapture() override;
|
||||
|
||||
OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
|
||||
|
|
@ -650,7 +721,7 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
|
|||
inNumberFrames, &audiobuf.list)};
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitRender capture error: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
|
||||
ERR("AudioUnitRender capture error: '{}' ({})", FourCCPrinter{err}.c_str(), err);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
@ -676,7 +747,7 @@ void CoreAudioCapture::open(std::string_view 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", al::sizei(name), name.data()};
|
||||
"Device name \"{}\" not found", name};
|
||||
|
||||
audioDevice = devmatch->mId;
|
||||
}
|
||||
|
|
@ -684,8 +755,8 @@ void CoreAudioCapture::open(std::string_view name)
|
|||
if(name.empty())
|
||||
name = ca_device;
|
||||
else if(name != ca_device)
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
|
||||
al::sizei(name), name.data()};
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
|
||||
name};
|
||||
#endif
|
||||
|
||||
AudioComponentDescription desc{};
|
||||
|
|
@ -709,7 +780,7 @@ void CoreAudioCapture::open(std::string_view name)
|
|||
OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{al::backend_error::NoDevice,
|
||||
"Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
|
||||
"Could not create component instance: '{}' ({})", FourCCPrinter{err}.c_str(), err};
|
||||
|
||||
// Turn off AudioUnit output
|
||||
UInt32 enableIO{0};
|
||||
|
|
@ -717,7 +788,7 @@ void CoreAudioCapture::open(std::string_view 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: '%s' (%u)", FourCCPrinter{err}.c_str(),
|
||||
"Could not disable audio unit output property: '{}' ({})", FourCCPrinter{err}.c_str(),
|
||||
err};
|
||||
|
||||
// Turn on AudioUnit input
|
||||
|
|
@ -726,7 +797,7 @@ void CoreAudioCapture::open(std::string_view 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: '%s' (%u)", FourCCPrinter{err}.c_str(),
|
||||
"Could not enable audio unit input property: '{}' ({})", FourCCPrinter{err}.c_str(),
|
||||
err};
|
||||
|
||||
#if CAN_ENUMERATE
|
||||
|
|
@ -745,7 +816,7 @@ void CoreAudioCapture::open(std::string_view name)
|
|||
kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct));
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Could not set capture callback: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
|
||||
"Could not set capture callback: '{}' ({})", FourCCPrinter{err}.c_str(), err};
|
||||
|
||||
// Disable buffer allocation for capture
|
||||
UInt32 flag{0};
|
||||
|
|
@ -753,14 +824,14 @@ void CoreAudioCapture::open(std::string_view name)
|
|||
kAudioUnitScope_Output, InputElement, &flag, sizeof(flag));
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Could not disable buffer allocation property: '%s' (%u)", FourCCPrinter{err}.c_str(),
|
||||
"Could not disable buffer allocation property: '{}' ({})", 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: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
|
||||
"Could not initialize audio unit: '{}' ({})", FourCCPrinter{err}.c_str(), err};
|
||||
|
||||
// Get the hardware format
|
||||
AudioStreamBasicDescription hardwareFormat{};
|
||||
|
|
@ -769,7 +840,7 @@ void CoreAudioCapture::open(std::string_view name)
|
|||
InputElement, &hardwareFormat, &propertySize);
|
||||
if(err != noErr || propertySize != sizeof(hardwareFormat))
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Could not get input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
|
||||
"Could not get input format: '{}' ({})", FourCCPrinter{err}.c_str(), err};
|
||||
|
||||
// Set up the requested format description
|
||||
AudioStreamBasicDescription requestedFormat{};
|
||||
|
|
@ -825,13 +896,13 @@ void CoreAudioCapture::open(std::string_view name)
|
|||
case DevFmtX7144:
|
||||
case DevFmtX3D71:
|
||||
case DevFmtAmbi3D:
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "%s not supported",
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "{} not supported",
|
||||
DevFmtChannelsString(mDevice->FmtChans)};
|
||||
}
|
||||
|
||||
requestedFormat.mBytesPerFrame = requestedFormat.mChannelsPerFrame * requestedFormat.mBitsPerChannel / 8;
|
||||
requestedFormat.mBytesPerPacket = requestedFormat.mBytesPerFrame;
|
||||
requestedFormat.mSampleRate = mDevice->Frequency;
|
||||
requestedFormat.mSampleRate = mDevice->mSampleRate;
|
||||
requestedFormat.mFormatID = kAudioFormatLinearPCM;
|
||||
requestedFormat.mReserved = 0;
|
||||
requestedFormat.mFramesPerPacket = 1;
|
||||
|
|
@ -851,18 +922,18 @@ void CoreAudioCapture::open(std::string_view name)
|
|||
InputElement, &outputFormat, sizeof(outputFormat));
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Could not set input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
|
||||
"Could not set input format: '{}' ({})", 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 = std::max(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)),
|
||||
double srateScale{outputFormat.mSampleRate / mDevice->mSampleRate};
|
||||
auto FrameCount64 = std::max(static_cast<uint64_t>(std::ceil(mDevice->mBufferSize*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,
|
||||
"Calculated frame count is too large: %" PRIu64, FrameCount64};
|
||||
"Calculated frame count is too large: {}", FrameCount64};
|
||||
|
||||
UInt32 outputFrameCount{};
|
||||
propertySize = sizeof(outputFrameCount);
|
||||
|
|
@ -870,7 +941,7 @@ void CoreAudioCapture::open(std::string_view 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: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
|
||||
"Could not get input frame count: '{}' ({})", FourCCPrinter{err}.c_str(), err};
|
||||
|
||||
mCaptureData.resize(outputFrameCount * mFrameSize);
|
||||
|
||||
|
|
@ -878,14 +949,14 @@ void CoreAudioCapture::open(std::string_view name)
|
|||
mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false);
|
||||
|
||||
/* Set up sample converter if needed */
|
||||
if(outputFormat.mSampleRate != mDevice->Frequency)
|
||||
if(outputFormat.mSampleRate != mDevice->mSampleRate)
|
||||
mConverter = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType,
|
||||
mFormat.mChannelsPerFrame, static_cast<uint>(hardwareFormat.mSampleRate),
|
||||
mDevice->Frequency, Resampler::FastBSinc24);
|
||||
mDevice->mSampleRate, Resampler::FastBSinc24);
|
||||
|
||||
#if CAN_ENUMERATE
|
||||
if(!name.empty())
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
else
|
||||
{
|
||||
UInt32 propSize{sizeof(audioDevice)};
|
||||
|
|
@ -894,11 +965,11 @@ void CoreAudioCapture::open(std::string_view name)
|
|||
kAudioUnitScope_Global, InputElement, &audioDevice, &propSize);
|
||||
|
||||
std::string devname{GetDeviceName(audioDevice)};
|
||||
if(!devname.empty()) mDevice->DeviceName = std::move(devname);
|
||||
else mDevice->DeviceName = "Unknown Device Name";
|
||||
if(!devname.empty()) mDeviceName = std::move(devname);
|
||||
else mDeviceName = "Unknown Device Name";
|
||||
}
|
||||
#else
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
@ -908,14 +979,14 @@ void CoreAudioCapture::start()
|
|||
OSStatus err{AudioOutputUnitStart(mAudioUnit)};
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
|
||||
"AudioOutputUnitStart failed: '{}' ({})", FourCCPrinter{err}.c_str(), err};
|
||||
}
|
||||
|
||||
void CoreAudioCapture::stop()
|
||||
{
|
||||
OSStatus err{AudioOutputUnitStop(mAudioUnit)};
|
||||
if(err != noErr)
|
||||
ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
|
||||
ERR("AudioOutputUnitStop failed: '{}' ({})", FourCCPrinter{err}.c_str(), err);
|
||||
}
|
||||
|
||||
void CoreAudioCapture::captureSamples(std::byte *buffer, uint samples)
|
||||
|
|
@ -927,16 +998,16 @@ void CoreAudioCapture::captureSamples(std::byte *buffer, uint samples)
|
|||
}
|
||||
|
||||
auto rec_vec = mRing->getReadVector();
|
||||
const void *src0{rec_vec.first.buf};
|
||||
auto src0len = static_cast<uint>(rec_vec.first.len);
|
||||
const void *src0{rec_vec[0].buf};
|
||||
auto src0len = static_cast<uint>(rec_vec[0].len);
|
||||
uint got{mConverter->convert(&src0, &src0len, buffer, samples)};
|
||||
size_t total_read{rec_vec.first.len - src0len};
|
||||
if(got < samples && !src0len && rec_vec.second.len > 0)
|
||||
size_t total_read{rec_vec[0].len - src0len};
|
||||
if(got < samples && !src0len && rec_vec[1].len > 0)
|
||||
{
|
||||
const void *src1{rec_vec.second.buf};
|
||||
auto src1len = static_cast<uint>(rec_vec.second.len);
|
||||
const void *src1{rec_vec[1].buf};
|
||||
auto src1len = static_cast<uint>(rec_vec[1].len);
|
||||
got += mConverter->convert(&src1, &src1len, buffer + got*mFrameSize, samples-got);
|
||||
total_read += rec_vec.second.len - src1len;
|
||||
total_read += rec_vec[1].len - src1len;
|
||||
}
|
||||
|
||||
mRing->readAdvance(total_read);
|
||||
|
|
|
|||
|
|
@ -37,21 +37,20 @@
|
|||
#include <cassert>
|
||||
#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"
|
||||
#include "core/logging.h"
|
||||
#include "dynload.h"
|
||||
#include "fmt/core.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "strutils.h"
|
||||
|
||||
|
|
@ -90,10 +89,7 @@ DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0
|
|||
|
||||
namespace {
|
||||
|
||||
#define DEVNAME_HEAD "OpenAL Soft on "
|
||||
|
||||
|
||||
#ifdef HAVE_DYNLOAD
|
||||
#if HAVE_DYNLOAD
|
||||
void *ds_handle;
|
||||
HRESULT (WINAPI *pDirectSoundCreate)(const GUID *pcGuidDevice, IDirectSound **ppDS, IUnknown *pUnkOuter);
|
||||
HRESULT (WINAPI *pDirectSoundEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext);
|
||||
|
|
@ -146,24 +142,19 @@ BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, voi
|
|||
return TRUE;
|
||||
|
||||
auto& devices = *static_cast<std::vector<DevMap>*>(data);
|
||||
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)};
|
||||
const auto basename = wstr_to_utf8(desc);
|
||||
|
||||
int count{1};
|
||||
std::string newname{basename};
|
||||
auto count = 1;
|
||||
auto newname = basename;
|
||||
while(checkName(devices, newname))
|
||||
{
|
||||
newname = basename;
|
||||
newname += " #";
|
||||
newname += std::to_string(++count);
|
||||
}
|
||||
devices.emplace_back(std::move(newname), *guid);
|
||||
const DevMap &newentry = devices.back();
|
||||
newname = fmt::format("{} #{}", basename, ++count);
|
||||
const DevMap &newentry = devices.emplace_back(std::move(newname), *guid);
|
||||
|
||||
OLECHAR *guidstr{nullptr};
|
||||
HRESULT hr{StringFromCLSID(*guid, &guidstr)};
|
||||
if(SUCCEEDED(hr))
|
||||
{
|
||||
TRACE("Got device \"%s\", GUID \"%ls\"\n", newentry.name.c_str(), guidstr);
|
||||
TRACE("Got device \"{}\", GUID \"{}\"", newentry.name, wstr_to_utf8(guidstr));
|
||||
CoTaskMemFree(guidstr);
|
||||
}
|
||||
|
||||
|
|
@ -172,7 +163,7 @@ BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, voi
|
|||
|
||||
|
||||
struct DSoundPlayback final : public BackendBase {
|
||||
DSoundPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit DSoundPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~DSoundPlayback() override;
|
||||
|
||||
int mixerProc();
|
||||
|
|
@ -215,14 +206,15 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
|
|||
HRESULT err{mBuffer->GetCaps(&DSBCaps)};
|
||||
if(FAILED(err))
|
||||
{
|
||||
ERR("Failed to get buffer caps: 0x%lx\n", err);
|
||||
mDevice->handleDisconnect("Failure retrieving playback buffer info: 0x%lx", err);
|
||||
ERR("Failed to get buffer caps: {:#x}", as_unsigned(err));
|
||||
mDevice->handleDisconnect("Failure retrieving playback buffer info: {:#x}",
|
||||
as_unsigned(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
const size_t FrameStep{mDevice->channelsFromFmt()};
|
||||
uint FrameSize{mDevice->frameSizeFromFmt()};
|
||||
DWORD FragSize{mDevice->UpdateSize * FrameSize};
|
||||
DWORD FragSize{mDevice->mUpdateSize * FrameSize};
|
||||
|
||||
bool Playing{false};
|
||||
DWORD LastCursor{0u};
|
||||
|
|
@ -242,8 +234,9 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
|
|||
err = mBuffer->Play(0, 0, DSBPLAY_LOOPING);
|
||||
if(FAILED(err))
|
||||
{
|
||||
ERR("Failed to play buffer: 0x%lx\n", err);
|
||||
mDevice->handleDisconnect("Failure starting playback: 0x%lx", err);
|
||||
ERR("Failed to play buffer: {:#x}", as_unsigned(err));
|
||||
mDevice->handleDisconnect("Failure starting playback: {:#x}",
|
||||
as_unsigned(err));
|
||||
return 1;
|
||||
}
|
||||
Playing = true;
|
||||
|
|
@ -251,7 +244,7 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
|
|||
|
||||
avail = WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE);
|
||||
if(avail != WAIT_OBJECT_0)
|
||||
ERR("WaitForSingleObjectEx error: 0x%lx\n", avail);
|
||||
ERR("WaitForSingleObjectEx error: {:#x}", avail);
|
||||
continue;
|
||||
}
|
||||
avail -= avail%FragSize;
|
||||
|
|
@ -264,7 +257,7 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
|
|||
// If the buffer is lost, restore it and lock
|
||||
if(err == DSERR_BUFFERLOST)
|
||||
{
|
||||
WARN("Buffer lost, restoring...\n");
|
||||
WARN("Buffer lost, restoring...");
|
||||
err = mBuffer->Restore();
|
||||
if(SUCCEEDED(err))
|
||||
{
|
||||
|
|
@ -274,22 +267,19 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
|
|||
&WritePtr2, &WriteCnt2, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if(SUCCEEDED(err))
|
||||
if(FAILED(err))
|
||||
{
|
||||
mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep);
|
||||
if(WriteCnt2 > 0)
|
||||
mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep);
|
||||
|
||||
mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2);
|
||||
}
|
||||
else
|
||||
{
|
||||
ERR("Buffer lock error: %#lx\n", err);
|
||||
mDevice->handleDisconnect("Failed to lock output buffer: 0x%lx", err);
|
||||
ERR("Buffer lock error: {:#x}", as_unsigned(err));
|
||||
mDevice->handleDisconnect("Failed to lock output buffer: {:#x}", as_unsigned(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep);
|
||||
if(WriteCnt2 > 0)
|
||||
mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep);
|
||||
|
||||
mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2);
|
||||
|
||||
// Update old write cursor location
|
||||
LastCursor += WriteCnt1+WriteCnt2;
|
||||
LastCursor %= DSBCaps.dwBufferBytes;
|
||||
|
|
@ -307,7 +297,7 @@ void DSoundPlayback::open(std::string_view name)
|
|||
ComWrapper com{};
|
||||
hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
|
||||
if(FAILED(hr))
|
||||
ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
|
||||
ERR("Error enumerating DirectSound devices: {:#x}", as_unsigned(hr));
|
||||
}
|
||||
|
||||
const GUID *guid{nullptr};
|
||||
|
|
@ -329,7 +319,7 @@ void DSoundPlayback::open(std::string_view 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", al::sizei(name), name.data()};
|
||||
"Device name \"{}\" not found", name};
|
||||
}
|
||||
guid = &iter->guid;
|
||||
}
|
||||
|
|
@ -348,15 +338,15 @@ void DSoundPlayback::open(std::string_view name)
|
|||
if(SUCCEEDED(hr))
|
||||
hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
|
||||
if(FAILED(hr))
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
|
||||
hr};
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: {:#x}",
|
||||
as_unsigned(hr)};
|
||||
|
||||
mNotifies = nullptr;
|
||||
mBuffer = nullptr;
|
||||
mPrimaryBuffer = nullptr;
|
||||
mDS = std::move(ds);
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
bool DSoundPlayback::reset()
|
||||
|
|
@ -391,7 +381,7 @@ bool DSoundPlayback::reset()
|
|||
HRESULT hr{mDS->GetSpeakerConfig(&speakers)};
|
||||
if(FAILED(hr))
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to get speaker config: 0x%08lx", hr};
|
||||
"Failed to get speaker config: {:#x}", as_unsigned(hr)};
|
||||
|
||||
speakers = DSSPEAKER_CONFIG(speakers);
|
||||
if(!mDevice->Flags.test(ChannelsRequest))
|
||||
|
|
@ -407,7 +397,7 @@ bool DSoundPlayback::reset()
|
|||
else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
|
||||
mDevice->FmtChans = DevFmtX71;
|
||||
else
|
||||
ERR("Unknown system speaker config: 0x%lx\n", speakers);
|
||||
ERR("Unknown system speaker config: {:#x}", speakers);
|
||||
}
|
||||
mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE));
|
||||
const bool isRear51{speakers == DSSPEAKER_5POINT1_BACK};
|
||||
|
|
@ -435,7 +425,7 @@ bool DSoundPlayback::reset()
|
|||
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.nSamplesPerSec = mDevice->mSampleRate;
|
||||
OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
|
||||
OutputType.Format.nBlockAlign;
|
||||
OutputType.Format.cbSize = 0;
|
||||
|
|
@ -469,16 +459,16 @@ bool DSoundPlayback::reset()
|
|||
if(FAILED(hr))
|
||||
break;
|
||||
|
||||
uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
|
||||
uint num_updates{mDevice->mBufferSize / mDevice->mUpdateSize};
|
||||
if(num_updates > MAX_UPDATES)
|
||||
num_updates = MAX_UPDATES;
|
||||
mDevice->BufferSize = mDevice->UpdateSize * num_updates;
|
||||
mDevice->mBufferSize = mDevice->mUpdateSize * num_updates;
|
||||
|
||||
DSBUFFERDESC DSBDescription{};
|
||||
DSBDescription.dwSize = sizeof(DSBDescription);
|
||||
DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2
|
||||
| DSBCAPS_GLOBALFOCUS;
|
||||
DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign;
|
||||
DSBDescription.dwBufferBytes = mDevice->mBufferSize * OutputType.Format.nBlockAlign;
|
||||
DSBDescription.lpwfxFormat = &OutputType.Format;
|
||||
|
||||
hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mBuffer), nullptr);
|
||||
|
|
@ -492,13 +482,13 @@ bool DSoundPlayback::reset()
|
|||
hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, al::out_ptr(mNotifies));
|
||||
if(SUCCEEDED(hr))
|
||||
{
|
||||
uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
|
||||
uint num_updates{mDevice->mBufferSize / mDevice->mUpdateSize};
|
||||
assert(num_updates <= MAX_UPDATES);
|
||||
|
||||
std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots{};
|
||||
for(uint i{0};i < num_updates;++i)
|
||||
{
|
||||
nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign;
|
||||
nots[i].dwOffset = i * mDevice->mUpdateSize * OutputType.Format.nBlockAlign;
|
||||
nots[i].hEventNotify = mNotifyEvent;
|
||||
}
|
||||
if(mNotifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK)
|
||||
|
|
@ -524,11 +514,11 @@ void DSoundPlayback::start()
|
|||
{
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&DSoundPlayback::mixerProc), this};
|
||||
mThread = std::thread{&DSoundPlayback::mixerProc, this};
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start mixing thread: %s", e.what()};
|
||||
"Failed to start mixing thread: {}", e.what()};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -543,7 +533,7 @@ void DSoundPlayback::stop()
|
|||
|
||||
|
||||
struct DSoundCapture final : public BackendBase {
|
||||
DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~DSoundCapture() override;
|
||||
|
||||
void open(std::string_view name) override;
|
||||
|
|
@ -580,7 +570,7 @@ void DSoundCapture::open(std::string_view name)
|
|||
ComWrapper com{};
|
||||
hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
|
||||
if(FAILED(hr))
|
||||
ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
|
||||
ERR("Error enumerating DirectSound devices: {:#x}", as_unsigned(hr));
|
||||
}
|
||||
|
||||
const GUID *guid{nullptr};
|
||||
|
|
@ -602,7 +592,7 @@ void DSoundCapture::open(std::string_view 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", al::sizei(name), name.data()};
|
||||
"Device name \"{}\" not found", name};
|
||||
}
|
||||
guid = &iter->guid;
|
||||
}
|
||||
|
|
@ -612,9 +602,9 @@ void DSoundCapture::open(std::string_view name)
|
|||
case DevFmtByte:
|
||||
case DevFmtUShort:
|
||||
case DevFmtUInt:
|
||||
WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType));
|
||||
WARN("{} capture samples not supported", DevFmtTypeString(mDevice->FmtType));
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
|
||||
"{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
|
||||
|
||||
case DevFmtUByte:
|
||||
case DevFmtShort:
|
||||
|
|
@ -636,8 +626,8 @@ void DSoundCapture::open(std::string_view name)
|
|||
case DevFmtX7144:
|
||||
case DevFmtX3D71:
|
||||
case DevFmtAmbi3D:
|
||||
WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
|
||||
WARN("{} capture not supported", DevFmtChannelsString(mDevice->FmtChans));
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported",
|
||||
DevFmtChannelsString(mDevice->FmtChans)};
|
||||
}
|
||||
|
||||
|
|
@ -646,7 +636,7 @@ void DSoundCapture::open(std::string_view name)
|
|||
InputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
|
||||
InputType.Format.nBlockAlign = static_cast<WORD>(InputType.Format.nChannels *
|
||||
InputType.Format.wBitsPerSample / 8);
|
||||
InputType.Format.nSamplesPerSec = mDevice->Frequency;
|
||||
InputType.Format.nSamplesPerSec = mDevice->mSampleRate;
|
||||
InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec *
|
||||
InputType.Format.nBlockAlign;
|
||||
InputType.Format.cbSize = 0;
|
||||
|
|
@ -663,7 +653,7 @@ void DSoundCapture::open(std::string_view name)
|
|||
InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
|
||||
}
|
||||
|
||||
const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)};
|
||||
const uint samples{std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u)};
|
||||
|
||||
DSCBUFFERDESC DSCBDescription{};
|
||||
DSCBDescription.dwSize = sizeof(DSCBDescription);
|
||||
|
|
@ -676,7 +666,7 @@ void DSoundCapture::open(std::string_view name)
|
|||
if(SUCCEEDED(hr))
|
||||
mDSC->CreateCaptureBuffer(&DSCBDescription, al::out_ptr(mDSCbuffer), nullptr);
|
||||
if(SUCCEEDED(hr))
|
||||
mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
|
||||
mRing = RingBuffer::Create(mDevice->mBufferSize, InputType.Format.nBlockAlign, false);
|
||||
|
||||
if(FAILED(hr))
|
||||
{
|
||||
|
|
@ -684,14 +674,14 @@ void DSoundCapture::open(std::string_view name)
|
|||
mDSCbuffer = nullptr;
|
||||
mDSC = nullptr;
|
||||
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
|
||||
hr};
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: {:#x}",
|
||||
as_unsigned(hr)};
|
||||
}
|
||||
|
||||
mBufferBytes = DSCBDescription.dwBufferBytes;
|
||||
setDefaultWFXChannelOrder();
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
void DSoundCapture::start()
|
||||
|
|
@ -699,7 +689,7 @@ void DSoundCapture::start()
|
|||
const HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)};
|
||||
if(FAILED(hr))
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failure starting capture: 0x%lx", hr};
|
||||
"Failure starting capture: {:#x}", as_unsigned(hr)};
|
||||
}
|
||||
|
||||
void DSoundCapture::stop()
|
||||
|
|
@ -707,8 +697,8 @@ void DSoundCapture::stop()
|
|||
HRESULT hr{mDSCbuffer->Stop()};
|
||||
if(FAILED(hr))
|
||||
{
|
||||
ERR("stop failed: 0x%08lx\n", hr);
|
||||
mDevice->handleDisconnect("Failure stopping capture: 0x%lx", hr);
|
||||
ERR("stop failed: {:#x}", as_unsigned(hr));
|
||||
mDevice->handleDisconnect("Failure stopping capture: {:#x}", as_unsigned(hr));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -745,8 +735,8 @@ uint DSoundCapture::availableSamples()
|
|||
|
||||
if(FAILED(hr))
|
||||
{
|
||||
ERR("update failed: 0x%08lx\n", hr);
|
||||
mDevice->handleDisconnect("Failure retrieving capture data: 0x%lx", hr);
|
||||
ERR("update failed: {:#x}", as_unsigned(hr));
|
||||
mDevice->handleDisconnect("Failure retrieving capture data: {:#x}", as_unsigned(hr));
|
||||
}
|
||||
|
||||
return static_cast<uint>(mRing->readSpace());
|
||||
|
|
@ -763,13 +753,13 @@ BackendFactory &DSoundBackendFactory::getFactory()
|
|||
|
||||
bool DSoundBackendFactory::init()
|
||||
{
|
||||
#ifdef HAVE_DYNLOAD
|
||||
#if HAVE_DYNLOAD
|
||||
if(!ds_handle)
|
||||
{
|
||||
ds_handle = LoadLib("dsound.dll");
|
||||
if(!ds_handle)
|
||||
{
|
||||
ERR("Failed to load dsound.dll\n");
|
||||
ERR("Failed to load dsound.dll");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -808,7 +798,7 @@ auto DSoundBackendFactory::enumerate(BackendType type) -> std::vector<std::strin
|
|||
case BackendType::Playback:
|
||||
PlaybackDevices.clear();
|
||||
if(HRESULT hr{DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices)}; FAILED(hr))
|
||||
ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr);
|
||||
ERR("Error enumerating DirectSound playback devices: {:#x}", as_unsigned(hr));
|
||||
outnames.reserve(PlaybackDevices.size());
|
||||
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
|
||||
break;
|
||||
|
|
@ -816,7 +806,7 @@ auto DSoundBackendFactory::enumerate(BackendType type) -> std::vector<std::strin
|
|||
case BackendType::Capture:
|
||||
CaptureDevices.clear();
|
||||
if(HRESULT hr{DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices)};FAILED(hr))
|
||||
ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr);
|
||||
ERR("Error enumerating DirectSound capture devices: {:#x}", as_unsigned(hr));
|
||||
outnames.reserve(CaptureDevices.size());
|
||||
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -29,18 +29,17 @@
|
|||
#include <memory.h>
|
||||
#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 "fmt/format.h"
|
||||
#include "ringbuffer.h"
|
||||
|
||||
#include <jack/jack.h>
|
||||
|
|
@ -51,7 +50,7 @@ namespace {
|
|||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
#ifdef HAVE_DYNLOAD
|
||||
#if HAVE_DYNLOAD
|
||||
#define JACK_FUNCS(MAGIC) \
|
||||
MAGIC(jack_client_open); \
|
||||
MAGIC(jack_client_close); \
|
||||
|
|
@ -108,10 +107,12 @@ jack_options_t ClientOptions = JackNullOption;
|
|||
|
||||
bool jack_load()
|
||||
{
|
||||
#ifdef HAVE_DYNLOAD
|
||||
#if HAVE_DYNLOAD
|
||||
if(!jack_handle)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
#if defined(_WIN64)
|
||||
#define JACKLIB "libjack64.dll"
|
||||
#elif defined(_WIN32)
|
||||
#define JACKLIB "libjack.dll"
|
||||
#else
|
||||
#define JACKLIB "libjack.so.0"
|
||||
|
|
@ -119,7 +120,7 @@ bool jack_load()
|
|||
jack_handle = LoadLib(JACKLIB);
|
||||
if(!jack_handle)
|
||||
{
|
||||
WARN("Failed to load %s\n", JACKLIB);
|
||||
WARN("Failed to load {}", JACKLIB);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -137,7 +138,7 @@ bool jack_load()
|
|||
|
||||
if(!missing_funcs.empty())
|
||||
{
|
||||
WARN("Missing expected functions:%s\n", missing_funcs.c_str());
|
||||
WARN("Missing expected functions:{}", missing_funcs);
|
||||
CloseLib(jack_handle);
|
||||
jack_handle = nullptr;
|
||||
return false;
|
||||
|
|
@ -194,15 +195,15 @@ void EnumerateDevices(jack_client_t *client, std::vector<DeviceEntry> &list)
|
|||
if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend())
|
||||
continue;
|
||||
|
||||
const auto &entry = list.emplace_back(portdev, std::string{portdev}+":");
|
||||
TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
|
||||
const auto &entry = list.emplace_back(portdev, fmt::format("{}:", portdev));
|
||||
TRACE("Got device: {} = {}", entry.mName, entry.mPattern);
|
||||
}
|
||||
/* There are ports but couldn't get device names from them. Add a
|
||||
* generic entry.
|
||||
*/
|
||||
if(ports[0] && list.empty())
|
||||
{
|
||||
WARN("No device names found in available ports, adding a generic name.\n");
|
||||
WARN("No device names found in available ports, adding a generic name.");
|
||||
list.emplace_back("JACK"sv, ""sv);
|
||||
}
|
||||
}
|
||||
|
|
@ -216,7 +217,7 @@ void EnumerateDevices(jack_client_t *client, std::vector<DeviceEntry> &list)
|
|||
if(seppos >= nextpos || seppos == strpos)
|
||||
{
|
||||
const auto entry = std::string_view{*listopt}.substr(strpos, nextpos-strpos);
|
||||
ERR("Invalid device entry: \"%.*s\"\n", al::sizei(entry), entry.data());
|
||||
ERR("Invalid device entry: \"{}\"", entry);
|
||||
if(nextpos != std::string::npos) ++nextpos;
|
||||
strpos = nextpos;
|
||||
continue;
|
||||
|
|
@ -234,14 +235,13 @@ void EnumerateDevices(jack_client_t *client, std::vector<DeviceEntry> &list)
|
|||
{
|
||||
/* If so, replace the name with this custom one. */
|
||||
itemmatch->mName = name;
|
||||
TRACE("Customized device name: %s = %s\n", itemmatch->mName.c_str(),
|
||||
itemmatch->mPattern.c_str());
|
||||
TRACE("Customized device name: {} = {}", itemmatch->mName, itemmatch->mPattern);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Otherwise, add a new device entry. */
|
||||
const auto &entry = list.emplace_back(name, pattern);
|
||||
TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
|
||||
TRACE("Got custom device: {} = {}", entry.mName, entry.mPattern);
|
||||
}
|
||||
|
||||
if(nextpos != std::string::npos) ++nextpos;
|
||||
|
|
@ -277,7 +277,7 @@ void EnumerateDevices(jack_client_t *client, std::vector<DeviceEntry> &list)
|
|||
|
||||
|
||||
struct JackPlayback final : public BackendBase {
|
||||
JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~JackPlayback() override;
|
||||
|
||||
int processRt(jack_nframes_t numframes) noexcept;
|
||||
|
|
@ -329,13 +329,13 @@ JackPlayback::~JackPlayback()
|
|||
|
||||
int JackPlayback::processRt(jack_nframes_t numframes) noexcept
|
||||
{
|
||||
auto outptrs = std::array<jack_default_audio_sample_t*,MaxOutputChannels>{};
|
||||
auto outptrs = std::array<void*,MaxOutputChannels>{};
|
||||
auto numchans = size_t{0};
|
||||
for(auto port : mPort)
|
||||
{
|
||||
if(!port || numchans == mDevice->RealOut.Buffer.size())
|
||||
break;
|
||||
outptrs[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
|
||||
outptrs[numchans++] = jack_port_get_buffer(port, numframes);
|
||||
}
|
||||
|
||||
const auto dst = al::span{outptrs}.first(numchans);
|
||||
|
|
@ -343,8 +343,8 @@ int JackPlayback::processRt(jack_nframes_t numframes) noexcept
|
|||
mDevice->renderSamples(dst, static_cast<uint>(numframes));
|
||||
else
|
||||
{
|
||||
std::for_each(dst.begin(), dst.end(), [numframes](float *outbuf) -> void
|
||||
{ std::fill_n(outbuf, numframes, 0.0f); });
|
||||
std::for_each(dst.begin(), dst.end(), [numframes](void *outbuf) -> void
|
||||
{ std::fill_n(static_cast<float*>(outbuf), numframes, 0.0f); });
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -365,13 +365,13 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept
|
|||
if(mPlaying.load(std::memory_order_acquire)) LIKELY
|
||||
{
|
||||
auto data = mRing->getReadVector();
|
||||
const auto update_size = size_t{mDevice->UpdateSize};
|
||||
const auto update_size = size_t{mDevice->mUpdateSize};
|
||||
|
||||
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)};
|
||||
const auto len1 = size_t{std::min(data[0].len/update_size, outlen)};
|
||||
const auto len2 = size_t{std::min(data[1].len/update_size, outlen-len1)};
|
||||
|
||||
auto src = al::span{reinterpret_cast<float*>(data.first.buf), update_size*len1*numchans};
|
||||
auto src = al::span{reinterpret_cast<float*>(data[0].buf), update_size*len1*numchans};
|
||||
for(size_t i{0};i < len1;++i)
|
||||
{
|
||||
for(size_t c{0};c < numchans;++c)
|
||||
|
|
@ -383,7 +383,7 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept
|
|||
total += update_size;
|
||||
}
|
||||
|
||||
src = al::span{reinterpret_cast<float*>(data.second.buf), update_size*len2*numchans};
|
||||
src = al::span{reinterpret_cast<float*>(data[1].buf), update_size*len2*numchans};
|
||||
for(size_t i{0};i < len2;++i)
|
||||
{
|
||||
for(size_t c{0};c < numchans;++c)
|
||||
|
|
@ -414,9 +414,9 @@ int JackPlayback::mixerProc()
|
|||
SetRTPriority();
|
||||
althrd_setname(GetMixerThreadName());
|
||||
|
||||
const auto update_size = uint{mDevice->UpdateSize};
|
||||
const auto update_size = uint{mDevice->mUpdateSize};
|
||||
const auto num_channels = size_t{mDevice->channelsFromFmt()};
|
||||
auto outptrs = std::vector<float*>(num_channels);
|
||||
auto outptrs = std::vector<void*>(num_channels);
|
||||
|
||||
while(!mKillNow.load(std::memory_order_acquire)
|
||||
&& mDevice->Connected.load(std::memory_order_acquire))
|
||||
|
|
@ -428,12 +428,11 @@ int JackPlayback::mixerProc()
|
|||
}
|
||||
|
||||
auto data = mRing->getWriteVector();
|
||||
const auto len1 = size_t{data.first.len / update_size};
|
||||
const auto len2 = size_t{data.second.len / update_size};
|
||||
const auto len1 = size_t{data[0].len / update_size};
|
||||
const auto len2 = size_t{data[1].len / update_size};
|
||||
|
||||
std::lock_guard<std::mutex> dlock{mMutex};
|
||||
auto buffer = al::span{reinterpret_cast<float*>(data.first.buf),
|
||||
data.first.len*num_channels};
|
||||
auto buffer = al::span{reinterpret_cast<float*>(data[0].buf), data[0].len*num_channels};
|
||||
auto bufiter = buffer.begin();
|
||||
for(size_t i{0};i < len1;++i)
|
||||
{
|
||||
|
|
@ -447,8 +446,7 @@ int JackPlayback::mixerProc()
|
|||
}
|
||||
if(len2 > 0)
|
||||
{
|
||||
buffer = al::span{reinterpret_cast<float*>(data.second.buf),
|
||||
data.second.len*num_channels};
|
||||
buffer = al::span{reinterpret_cast<float*>(data[1].buf), data[1].len*num_channels};
|
||||
bufiter = buffer.begin();
|
||||
for(size_t i{0};i < len2;++i)
|
||||
{
|
||||
|
|
@ -479,13 +477,14 @@ void JackPlayback::open(std::string_view name)
|
|||
mClient = jack_client_open(client_name, ClientOptions, &status, nullptr);
|
||||
if(mClient == nullptr)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to open client connection: 0x%02x", status};
|
||||
"Failed to open client connection: {:#02x}",
|
||||
as_unsigned(al::to_underlying(status))};
|
||||
if((status&JackServerStarted))
|
||||
TRACE("JACK server started\n");
|
||||
TRACE("JACK server started");
|
||||
if((status&JackNameNotUnique))
|
||||
{
|
||||
client_name = jack_get_client_name(mClient);
|
||||
TRACE("Client name not unique, got '%s' instead\n", client_name);
|
||||
TRACE("Client name not unique, got '{}' instead", client_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -504,11 +503,11 @@ void JackPlayback::open(std::string_view 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", al::sizei(name), name.data()};
|
||||
"Device name \"{}\" not found", name};
|
||||
mPortPattern = iter->mPattern;
|
||||
}
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
bool JackPlayback::reset()
|
||||
|
|
@ -518,28 +517,29 @@ bool JackPlayback::reset()
|
|||
std::for_each(mPort.begin(), mPort.end(), unregister_port);
|
||||
mPort.fill(nullptr);
|
||||
|
||||
mRTMixing = GetConfigValueBool(mDevice->DeviceName, "jack", "rt-mix", true);
|
||||
mRTMixing = GetConfigValueBool(mDevice->mDeviceName, "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.
|
||||
*/
|
||||
mDevice->Frequency = jack_get_sample_rate(mClient);
|
||||
mDevice->UpdateSize = jack_get_buffer_size(mClient);
|
||||
mDevice->mSampleRate = jack_get_sample_rate(mClient);
|
||||
mDevice->mUpdateSize = jack_get_buffer_size(mClient);
|
||||
if(mRTMixing)
|
||||
{
|
||||
/* Assume only two periods when directly mixing. Should try to query
|
||||
* the total port latency when connected.
|
||||
*/
|
||||
mDevice->BufferSize = mDevice->UpdateSize * 2;
|
||||
mDevice->mBufferSize = mDevice->mUpdateSize * 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string_view devname{mDevice->DeviceName};
|
||||
uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
|
||||
bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize);
|
||||
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
|
||||
const auto devname = std::string_view{mDevice->mDeviceName};
|
||||
auto bufsize = ConfigValueUInt(devname, "jack", "buffer-size")
|
||||
.value_or(mDevice->mUpdateSize);
|
||||
bufsize = std::max(NextPowerOf2(bufsize), mDevice->mUpdateSize);
|
||||
mDevice->mBufferSize = bufsize + mDevice->mUpdateSize;
|
||||
}
|
||||
|
||||
/* Force 32-bit float output. */
|
||||
|
|
@ -558,7 +558,7 @@ bool JackPlayback::reset()
|
|||
}
|
||||
if(bad_port != ports.end())
|
||||
{
|
||||
ERR("Failed to register enough JACK ports for %s output\n",
|
||||
ERR("Failed to register enough JACK ports for {} output",
|
||||
DevFmtChannelsString(mDevice->FmtChans));
|
||||
if(bad_port == ports.begin()) return false;
|
||||
|
||||
|
|
@ -586,7 +586,7 @@ void JackPlayback::start()
|
|||
if(jack_activate(mClient))
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"};
|
||||
|
||||
const std::string_view devname{mDevice->DeviceName};
|
||||
const auto devname = std::string_view{mDevice->mDeviceName};
|
||||
if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true))
|
||||
{
|
||||
JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JACK_DEFAULT_AUDIO_TYPE,
|
||||
|
|
@ -601,11 +601,11 @@ void JackPlayback::start()
|
|||
{
|
||||
if(!pnames[i])
|
||||
{
|
||||
ERR("No physical playback port for \"%s\"\n", jack_port_name(mPort[i]));
|
||||
ERR("No physical playback port for \"{}\"", jack_port_name(mPort[i]));
|
||||
break;
|
||||
}
|
||||
if(jack_connect(mClient, jack_port_name(mPort[i]), pnames[i]))
|
||||
ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(mPort[i]),
|
||||
ERR("Failed to connect output port \"{}\" to \"{}\"", jack_port_name(mPort[i]),
|
||||
pnames[i]);
|
||||
}
|
||||
}
|
||||
|
|
@ -614,31 +614,32 @@ void JackPlayback::start()
|
|||
* (it won't change again after jack_activate), then allocate the ring
|
||||
* buffer with the appropriate size.
|
||||
*/
|
||||
mDevice->Frequency = jack_get_sample_rate(mClient);
|
||||
mDevice->UpdateSize = jack_get_buffer_size(mClient);
|
||||
mDevice->BufferSize = mDevice->UpdateSize * 2;
|
||||
mDevice->mSampleRate = jack_get_sample_rate(mClient);
|
||||
mDevice->mUpdateSize = jack_get_buffer_size(mClient);
|
||||
mDevice->mBufferSize = mDevice->mUpdateSize * 2;
|
||||
|
||||
mRing = nullptr;
|
||||
if(mRTMixing)
|
||||
mPlaying.store(true, std::memory_order_release);
|
||||
else
|
||||
{
|
||||
uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
|
||||
bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize);
|
||||
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
|
||||
uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size")
|
||||
.value_or(mDevice->mUpdateSize)};
|
||||
bufsize = std::max(NextPowerOf2(bufsize), mDevice->mUpdateSize);
|
||||
mDevice->mBufferSize = bufsize + mDevice->mUpdateSize;
|
||||
|
||||
mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true);
|
||||
|
||||
try {
|
||||
mPlaying.store(true, std::memory_order_release);
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this};
|
||||
mThread = std::thread{&JackPlayback::mixerProc, this};
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
jack_deactivate(mClient);
|
||||
mPlaying.store(false, std::memory_order_release);
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start mixing thread: %s", e.what()};
|
||||
"Failed to start mixing thread: {}", e.what()};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -665,8 +666,8 @@ ClockLatency JackPlayback::getClockLatency()
|
|||
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;
|
||||
ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->mUpdateSize};
|
||||
ret.Latency /= mDevice->mSampleRate;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -674,7 +675,7 @@ ClockLatency JackPlayback::getClockLatency()
|
|||
|
||||
void jack_msg_handler(const char *message)
|
||||
{
|
||||
WARN("%s\n", message);
|
||||
WARN("{}", message);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
@ -697,9 +698,9 @@ bool JackBackendFactory::init()
|
|||
jack_set_error_function(old_error_cb);
|
||||
if(!client)
|
||||
{
|
||||
WARN("jack_client_open() failed, 0x%02x\n", status);
|
||||
WARN("jack_client_open() failed, {:#02x}", as_unsigned(al::to_underlying(status)));
|
||||
if((status&JackServerFailed) && !(ClientOptions&JackNoStartServer))
|
||||
ERR("Unable to connect to JACK server\n");
|
||||
ERR("Unable to connect to JACK server");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -728,7 +729,7 @@ auto JackBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
|
|||
jack_client_close(client);
|
||||
}
|
||||
else
|
||||
WARN("jack_client_open() failed, 0x%02x\n", status);
|
||||
WARN("jack_client_open() failed, {:#02x}", as_unsigned(al::to_underlying(status)));
|
||||
outnames.reserve(PlaybackList.size());
|
||||
std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
namespace {
|
||||
|
||||
struct LoopbackBackend final : public BackendBase {
|
||||
LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
|
||||
void open(std::string_view name) override;
|
||||
bool reset() override;
|
||||
|
|
@ -39,7 +39,7 @@ struct LoopbackBackend final : public BackendBase {
|
|||
|
||||
void LoopbackBackend::open(std::string_view name)
|
||||
{
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
bool LoopbackBackend::reset()
|
||||
|
|
|
|||
|
|
@ -27,11 +27,8 @@
|
|||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alstring.h"
|
||||
#include "althrd_setname.h"
|
||||
#include "core/device.h"
|
||||
#include "core/helpers.h"
|
||||
|
|
@ -48,7 +45,7 @@ using namespace std::string_view_literals;
|
|||
|
||||
|
||||
struct NullBackend final : public BackendBase {
|
||||
NullBackend(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit NullBackend(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
|
||||
int mixerProc();
|
||||
|
||||
|
|
@ -63,7 +60,7 @@ struct NullBackend final : public BackendBase {
|
|||
|
||||
int NullBackend::mixerProc()
|
||||
{
|
||||
const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2};
|
||||
const milliseconds restTime{mDevice->mUpdateSize*1000/mDevice->mSampleRate / 2};
|
||||
|
||||
SetRTPriority();
|
||||
althrd_setname(GetMixerThreadName());
|
||||
|
|
@ -76,16 +73,17 @@ int NullBackend::mixerProc()
|
|||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
/* This converts from nanoseconds to nanosamples, then to samples. */
|
||||
int64_t avail{std::chrono::duration_cast<seconds>((now-start) * mDevice->Frequency).count()};
|
||||
if(avail-done < mDevice->UpdateSize)
|
||||
const auto avail = int64_t{std::chrono::duration_cast<seconds>((now-start)
|
||||
* mDevice->mSampleRate).count()};
|
||||
if(avail-done < mDevice->mUpdateSize)
|
||||
{
|
||||
std::this_thread::sleep_for(restTime);
|
||||
continue;
|
||||
}
|
||||
while(avail-done >= mDevice->UpdateSize)
|
||||
while(avail-done >= mDevice->mUpdateSize)
|
||||
{
|
||||
mDevice->renderSamples(nullptr, mDevice->UpdateSize, 0u);
|
||||
done += mDevice->UpdateSize;
|
||||
mDevice->renderSamples(nullptr, mDevice->mUpdateSize, 0u);
|
||||
done += mDevice->mUpdateSize;
|
||||
}
|
||||
|
||||
/* For every completed second, increment the start time and reduce the
|
||||
|
|
@ -93,11 +91,11 @@ int NullBackend::mixerProc()
|
|||
* and current time from growing too large, while maintaining the
|
||||
* correct number of samples to render.
|
||||
*/
|
||||
if(done >= mDevice->Frequency)
|
||||
if(done >= mDevice->mSampleRate)
|
||||
{
|
||||
seconds s{done/mDevice->Frequency};
|
||||
seconds s{done/mDevice->mSampleRate};
|
||||
start += s;
|
||||
done -= mDevice->Frequency*s.count();
|
||||
done -= mDevice->mSampleRate*s.count();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -110,10 +108,10 @@ void NullBackend::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()};
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
|
||||
name};
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
bool NullBackend::reset()
|
||||
|
|
@ -126,11 +124,11 @@ void NullBackend::start()
|
|||
{
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&NullBackend::mixerProc), this};
|
||||
mThread = std::thread{&NullBackend::mixerProc, this};
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start mixing thread: %s", e.what()};
|
||||
"Failed to start mixing thread: {}", e.what()};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ using namespace std::string_view_literals;
|
|||
|
||||
|
||||
struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback {
|
||||
OboePlayback(DeviceBase *device) : BackendBase{device} { }
|
||||
explicit OboePlayback(DeviceBase *device) : BackendBase{device} { }
|
||||
|
||||
oboe::ManagedStream mStream;
|
||||
|
||||
|
|
@ -54,8 +54,9 @@ oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStrea
|
|||
void OboePlayback::onErrorAfterClose(oboe::AudioStream*, oboe::Result error)
|
||||
{
|
||||
if(error == oboe::Result::ErrorDisconnected)
|
||||
mDevice->handleDisconnect("Oboe AudioStream was disconnected: %s", oboe::convertToText(error));
|
||||
TRACE("Error was %s", oboe::convertToText(error));
|
||||
mDevice->handleDisconnect("Oboe AudioStream was disconnected: {}",
|
||||
oboe::convertToText(error));
|
||||
TRACE("Error was {}", oboe::convertToText(error));
|
||||
}
|
||||
|
||||
void OboePlayback::open(std::string_view name)
|
||||
|
|
@ -63,8 +64,8 @@ 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()};
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
|
||||
name};
|
||||
|
||||
/* Open a basic output stream, just to ensure it can work. */
|
||||
oboe::ManagedStream stream;
|
||||
|
|
@ -72,10 +73,10 @@ void OboePlayback::open(std::string_view name)
|
|||
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
|
||||
->openManagedStream(stream)};
|
||||
if(result != oboe::Result::OK)
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: {}",
|
||||
oboe::convertToText(result)};
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
bool OboePlayback::reset()
|
||||
|
|
@ -95,7 +96,7 @@ bool OboePlayback::reset()
|
|||
if(mDevice->Flags.test(FrequencyRequest))
|
||||
{
|
||||
builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High);
|
||||
builder.setSampleRate(static_cast<int32_t>(mDevice->Frequency));
|
||||
builder.setSampleRate(static_cast<int32_t>(mDevice->mSampleRate));
|
||||
}
|
||||
if(mDevice->Flags.test(ChannelsRequest))
|
||||
{
|
||||
|
|
@ -145,11 +146,11 @@ bool OboePlayback::reset()
|
|||
result = builder.openManagedStream(mStream);
|
||||
}
|
||||
if(result != oboe::Result::OK)
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: {}",
|
||||
oboe::convertToText(result)};
|
||||
mStream->setBufferSizeInFrames(std::min(static_cast<int32_t>(mDevice->BufferSize),
|
||||
mStream->setBufferSizeInFrames(std::min(static_cast<int32_t>(mDevice->mBufferSize),
|
||||
mStream->getBufferCapacityInFrames()));
|
||||
TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
|
||||
TRACE("Got stream with properties:\n{}", oboe::convertToText(mStream.get()));
|
||||
|
||||
if(static_cast<uint>(mStream->getChannelCount()) != mDevice->channelsFromFmt())
|
||||
{
|
||||
|
|
@ -159,7 +160,7 @@ bool OboePlayback::reset()
|
|||
mDevice->FmtChans = DevFmtMono;
|
||||
else
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Got unhandled channel count: %d", mStream->getChannelCount()};
|
||||
"Got unhandled channel count: {}", mStream->getChannelCount()};
|
||||
}
|
||||
setDefaultWFXChannelOrder();
|
||||
|
||||
|
|
@ -183,18 +184,18 @@ bool OboePlayback::reset()
|
|||
case oboe::AudioFormat::Unspecified:
|
||||
case oboe::AudioFormat::Invalid:
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Got unhandled sample type: %s", oboe::convertToText(mStream->getFormat())};
|
||||
"Got unhandled sample type: {}", oboe::convertToText(mStream->getFormat())};
|
||||
}
|
||||
mDevice->Frequency = static_cast<uint32_t>(mStream->getSampleRate());
|
||||
mDevice->mSampleRate = static_cast<uint32_t>(mStream->getSampleRate());
|
||||
|
||||
/* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0
|
||||
* indicating variable updates, but OpenAL should have a reasonable minimum update size set.
|
||||
* FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum
|
||||
* update size.
|
||||
*/
|
||||
mDevice->UpdateSize = std::max(mDevice->Frequency/100u,
|
||||
mDevice->mUpdateSize = std::max(mDevice->mSampleRate/100u,
|
||||
static_cast<uint32_t>(mStream->getFramesPerBurst()));
|
||||
mDevice->BufferSize = std::max(mDevice->UpdateSize*2u,
|
||||
mDevice->mBufferSize = std::max(mDevice->mUpdateSize*2u,
|
||||
static_cast<uint32_t>(mStream->getBufferSizeInFrames()));
|
||||
|
||||
return true;
|
||||
|
|
@ -204,7 +205,7 @@ void OboePlayback::start()
|
|||
{
|
||||
const oboe::Result result{mStream->start()};
|
||||
if(result != oboe::Result::OK)
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s",
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: {}",
|
||||
oboe::convertToText(result)};
|
||||
}
|
||||
|
||||
|
|
@ -212,12 +213,12 @@ void OboePlayback::stop()
|
|||
{
|
||||
oboe::Result result{mStream->stop()};
|
||||
if(result != oboe::Result::OK)
|
||||
ERR("Failed to stop stream: %s\n", oboe::convertToText(result));
|
||||
ERR("Failed to stop stream: {}", oboe::convertToText(result));
|
||||
}
|
||||
|
||||
|
||||
struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback {
|
||||
OboeCapture(DeviceBase *device) : BackendBase{device} { }
|
||||
explicit OboeCapture(DeviceBase *device) : BackendBase{device} { }
|
||||
|
||||
oboe::ManagedStream mStream;
|
||||
|
||||
|
|
@ -246,8 +247,8 @@ void OboeCapture::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()};
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
|
||||
name};
|
||||
|
||||
oboe::AudioStreamBuilder builder;
|
||||
builder.setDirection(oboe::Direction::Input)
|
||||
|
|
@ -255,7 +256,7 @@ void OboeCapture::open(std::string_view name)
|
|||
->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High)
|
||||
->setChannelConversionAllowed(true)
|
||||
->setFormatConversionAllowed(true)
|
||||
->setSampleRate(static_cast<int32_t>(mDevice->Frequency))
|
||||
->setSampleRate(static_cast<int32_t>(mDevice->mSampleRate))
|
||||
->setCallback(this);
|
||||
/* Only use mono or stereo at user request. There's no telling what
|
||||
* other counts may be inferred as.
|
||||
|
|
@ -276,7 +277,7 @@ void OboeCapture::open(std::string_view name)
|
|||
case DevFmtX7144:
|
||||
case DevFmtX3D71:
|
||||
case DevFmtAmbi3D:
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported",
|
||||
DevFmtChannelsString(mDevice->FmtChans)};
|
||||
}
|
||||
|
||||
|
|
@ -301,28 +302,28 @@ void OboeCapture::open(std::string_view name)
|
|||
case DevFmtUShort:
|
||||
case DevFmtUInt:
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
|
||||
"{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
|
||||
}
|
||||
|
||||
oboe::Result result{builder.openManagedStream(mStream)};
|
||||
if(result != oboe::Result::OK)
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: {}",
|
||||
oboe::convertToText(result)};
|
||||
|
||||
TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
|
||||
TRACE("Got stream with properties:\n{}", oboe::convertToText(mStream.get()));
|
||||
|
||||
/* Ensure a minimum ringbuffer size of 100ms. */
|
||||
mRing = RingBuffer::Create(std::max(mDevice->BufferSize, mDevice->Frequency/10u),
|
||||
mRing = RingBuffer::Create(std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u),
|
||||
static_cast<uint32_t>(mStream->getBytesPerFrame()), false);
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
void OboeCapture::start()
|
||||
{
|
||||
const oboe::Result result{mStream->start()};
|
||||
if(result != oboe::Result::OK)
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s",
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: {}",
|
||||
oboe::convertToText(result)};
|
||||
}
|
||||
|
||||
|
|
@ -330,7 +331,7 @@ void OboeCapture::stop()
|
|||
{
|
||||
const oboe::Result result{mStream->stop()};
|
||||
if(result != oboe::Result::OK)
|
||||
ERR("Failed to stop stream: %s\n", oboe::convertToText(result));
|
||||
ERR("Failed to stop stream: {}", oboe::convertToText(result));
|
||||
}
|
||||
|
||||
uint OboeCapture::availableSamples()
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@
|
|||
#include "core/device.h"
|
||||
#include "core/helpers.h"
|
||||
#include "core/logging.h"
|
||||
#include "dynload.h"
|
||||
#include "opthelpers.h"
|
||||
#include "ringbuffer.h"
|
||||
|
||||
|
|
@ -53,6 +54,32 @@ namespace {
|
|||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
|
||||
#if HAVE_DYNLOAD
|
||||
#define SLES_SYMBOLS(MAGIC) \
|
||||
MAGIC(slCreateEngine); \
|
||||
MAGIC(SL_IID_ANDROIDCONFIGURATION); \
|
||||
MAGIC(SL_IID_ANDROIDSIMPLEBUFFERQUEUE); \
|
||||
MAGIC(SL_IID_ENGINE); \
|
||||
MAGIC(SL_IID_PLAY); \
|
||||
MAGIC(SL_IID_RECORD);
|
||||
|
||||
void *sles_handle;
|
||||
#define MAKE_SYMBOL(f) decltype(f) * p##f
|
||||
SLES_SYMBOLS(MAKE_SYMBOL)
|
||||
#undef MAKE_SYMBOL
|
||||
|
||||
#ifndef IN_IDE_PARSER
|
||||
#define slCreateEngine (*pslCreateEngine)
|
||||
#define SL_IID_ANDROIDCONFIGURATION (*pSL_IID_ANDROIDCONFIGURATION)
|
||||
#define SL_IID_ANDROIDSIMPLEBUFFERQUEUE (*pSL_IID_ANDROIDSIMPLEBUFFERQUEUE)
|
||||
#define SL_IID_ENGINE (*pSL_IID_ENGINE)
|
||||
#define SL_IID_PLAY (*pSL_IID_PLAY)
|
||||
#define SL_IID_RECORD (*pSL_IID_RECORD)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
/* Helper macros */
|
||||
#define EXTRACT_VCALL_ARGS(...) __VA_ARGS__))
|
||||
#define VCALL(obj, func) ((*(obj))->func((obj), EXTRACT_VCALL_ARGS
|
||||
|
|
@ -156,12 +183,12 @@ constexpr const char *res_str(SLresult result) noexcept
|
|||
inline void PrintErr(SLresult res, const char *str)
|
||||
{
|
||||
if(res != SL_RESULT_SUCCESS) UNLIKELY
|
||||
ERR("%s: %s\n", str, res_str(res));
|
||||
ERR("{}: {}", str, res_str(res));
|
||||
}
|
||||
|
||||
|
||||
struct OpenSLPlayback final : public BackendBase {
|
||||
OpenSLPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit OpenSLPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~OpenSLPlayback() override;
|
||||
|
||||
void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
|
||||
|
|
@ -247,7 +274,7 @@ int OpenSLPlayback::mixerProc()
|
|||
const size_t frame_step{mDevice->channelsFromFmt()};
|
||||
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
mDevice->handleDisconnect("Failed to get playback buffer: 0x%08x", result);
|
||||
mDevice->handleDisconnect("Failed to get playback buffer: {:#08x}", result);
|
||||
|
||||
while(SL_RESULT_SUCCESS == result && !mKillNow.load(std::memory_order_acquire)
|
||||
&& mDevice->Connected.load(std::memory_order_acquire))
|
||||
|
|
@ -265,7 +292,7 @@ int OpenSLPlayback::mixerProc()
|
|||
}
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
{
|
||||
mDevice->handleDisconnect("Failed to start playback: 0x%08x", result);
|
||||
mDevice->handleDisconnect("Failed to start playback: {:#08x}", result);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -278,35 +305,35 @@ int OpenSLPlayback::mixerProc()
|
|||
|
||||
std::unique_lock<std::mutex> dlock{mMutex};
|
||||
auto data = mRing->getWriteVector();
|
||||
mDevice->renderSamples(data.first.buf,
|
||||
static_cast<uint>(data.first.len)*mDevice->UpdateSize, frame_step);
|
||||
if(data.second.len > 0)
|
||||
mDevice->renderSamples(data.second.buf,
|
||||
static_cast<uint>(data.second.len)*mDevice->UpdateSize, frame_step);
|
||||
mDevice->renderSamples(data[0].buf,
|
||||
static_cast<uint>(data[0].len)*mDevice->mUpdateSize, frame_step);
|
||||
if(data[1].len > 0)
|
||||
mDevice->renderSamples(data[1].buf,
|
||||
static_cast<uint>(data[1].len)*mDevice->mUpdateSize, frame_step);
|
||||
|
||||
size_t todo{data.first.len + data.second.len};
|
||||
const auto todo = size_t{data[0].len + data[1].len};
|
||||
mRing->writeAdvance(todo);
|
||||
dlock.unlock();
|
||||
|
||||
for(size_t i{0};i < todo;i++)
|
||||
{
|
||||
if(!data.first.len)
|
||||
if(!data[0].len)
|
||||
{
|
||||
data.first = data.second;
|
||||
data.second.buf = nullptr;
|
||||
data.second.len = 0;
|
||||
data[0] = data[1];
|
||||
data[1].buf = nullptr;
|
||||
data[1].len = 0;
|
||||
}
|
||||
|
||||
result = VCALL(bufferQueue,Enqueue)(data.first.buf, mDevice->UpdateSize*mFrameSize);
|
||||
result = VCALL(bufferQueue,Enqueue)(data[0].buf, mDevice->mUpdateSize*mFrameSize);
|
||||
PrintErr(result, "bufferQueue->Enqueue");
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
{
|
||||
mDevice->handleDisconnect("Failed to queue audio: 0x%08x", result);
|
||||
mDevice->handleDisconnect("Failed to queue audio: {:#08x}", result);
|
||||
break;
|
||||
}
|
||||
|
||||
data.first.len--;
|
||||
data.first.buf += mDevice->UpdateSize*mFrameSize;
|
||||
data[0].len--;
|
||||
data[0].buf += mDevice->mUpdateSize*mFrameSize;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -319,8 +346,8 @@ void OpenSLPlayback::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()};
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
|
||||
name};
|
||||
|
||||
/* There's only one device, so if it's already open, there's nothing to do. */
|
||||
if(mEngineObj) return;
|
||||
|
|
@ -361,10 +388,10 @@ void OpenSLPlayback::open(std::string_view name)
|
|||
mEngine = nullptr;
|
||||
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to initialize OpenSL device: 0x%08x", result};
|
||||
"Failed to initialize OpenSL device: {:#08x}", result};
|
||||
}
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
bool OpenSLPlayback::reset()
|
||||
|
|
@ -397,14 +424,14 @@ bool OpenSLPlayback::reset()
|
|||
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bufq{};
|
||||
loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
|
||||
loc_bufq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize;
|
||||
loc_bufq.numBuffers = mDevice->mBufferSize / mDevice->mUpdateSize;
|
||||
|
||||
SLDataSource audioSrc{};
|
||||
#ifdef SL_ANDROID_DATAFORMAT_PCM_EX
|
||||
SLAndroidDataFormat_PCM_EX format_pcm_ex{};
|
||||
format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
|
||||
format_pcm_ex.numChannels = mDevice->channelsFromFmt();
|
||||
format_pcm_ex.sampleRate = mDevice->Frequency * 1000;
|
||||
format_pcm_ex.sampleRate = mDevice->mSampleRate * 1000;
|
||||
format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8;
|
||||
format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample;
|
||||
format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans);
|
||||
|
|
@ -435,7 +462,7 @@ bool OpenSLPlayback::reset()
|
|||
SLDataFormat_PCM format_pcm{};
|
||||
format_pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
format_pcm.numChannels = mDevice->channelsFromFmt();
|
||||
format_pcm.samplesPerSec = mDevice->Frequency * 1000;
|
||||
format_pcm.samplesPerSec = mDevice->mSampleRate * 1000;
|
||||
format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
|
||||
format_pcm.containerSize = format_pcm.bitsPerSample;
|
||||
format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
|
||||
|
|
@ -472,8 +499,8 @@ bool OpenSLPlayback::reset()
|
|||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
const uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
|
||||
mRing = RingBuffer::Create(num_updates, mFrameSize*mDevice->UpdateSize, true);
|
||||
const uint num_updates{mDevice->mBufferSize / mDevice->mUpdateSize};
|
||||
mRing = RingBuffer::Create(num_updates, mFrameSize*mDevice->mUpdateSize, true);
|
||||
}
|
||||
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
|
|
@ -505,15 +532,15 @@ void OpenSLPlayback::start()
|
|||
}
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to register callback: 0x%08x", result};
|
||||
"Failed to register callback: {:#08x}", result};
|
||||
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread(std::mem_fn(&OpenSLPlayback::mixerProc), this);
|
||||
mThread = std::thread(&OpenSLPlayback::mixerProc, this);
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start mixing thread: %s", e.what()};
|
||||
"Failed to start mixing thread: {}", e.what()};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -566,15 +593,15 @@ ClockLatency OpenSLPlayback::getClockLatency()
|
|||
|
||||
std::lock_guard<std::mutex> dlock{mMutex};
|
||||
ret.ClockTime = mDevice->getClockTime();
|
||||
ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize};
|
||||
ret.Latency /= mDevice->Frequency;
|
||||
ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->mUpdateSize};
|
||||
ret.Latency /= mDevice->mSampleRate;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
struct OpenSLCapture final : public BackendBase {
|
||||
OpenSLCapture(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit OpenSLCapture(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~OpenSLCapture() override;
|
||||
|
||||
void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
|
||||
|
|
@ -623,8 +650,8 @@ void OpenSLCapture::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()};
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
|
||||
name};
|
||||
|
||||
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
|
||||
PrintErr(result, "slCreateEngine");
|
||||
|
|
@ -642,16 +669,16 @@ void OpenSLCapture::open(std::string_view name)
|
|||
{
|
||||
mFrameSize = mDevice->frameSizeFromFmt();
|
||||
/* Ensure the total length is at least 100ms */
|
||||
uint length{std::max(mDevice->BufferSize, mDevice->Frequency/10u)};
|
||||
uint length{std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u)};
|
||||
/* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */
|
||||
uint update_len{std::clamp(mDevice->BufferSize/3u, mDevice->Frequency/100u,
|
||||
mDevice->Frequency/100u*5u)};
|
||||
uint update_len{std::clamp(mDevice->mBufferSize/3u, mDevice->mSampleRate/100u,
|
||||
mDevice->mSampleRate/100u*5u)};
|
||||
uint num_updates{(length+update_len-1) / update_len};
|
||||
|
||||
mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false);
|
||||
|
||||
mDevice->UpdateSize = update_len;
|
||||
mDevice->BufferSize = static_cast<uint>(mRing->writeSpace() * update_len);
|
||||
mDevice->mUpdateSize = update_len;
|
||||
mDevice->mBufferSize = static_cast<uint>(mRing->writeSpace() * update_len);
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
|
|
@ -670,14 +697,14 @@ void OpenSLCapture::open(std::string_view name)
|
|||
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bq{};
|
||||
loc_bq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
|
||||
loc_bq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize;
|
||||
loc_bq.numBuffers = mDevice->mBufferSize / mDevice->mUpdateSize;
|
||||
|
||||
SLDataSink audioSnk{};
|
||||
#ifdef SL_ANDROID_DATAFORMAT_PCM_EX
|
||||
SLAndroidDataFormat_PCM_EX format_pcm_ex{};
|
||||
format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
|
||||
format_pcm_ex.numChannels = mDevice->channelsFromFmt();
|
||||
format_pcm_ex.sampleRate = mDevice->Frequency * 1000;
|
||||
format_pcm_ex.sampleRate = mDevice->mSampleRate * 1000;
|
||||
format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8;
|
||||
format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample;
|
||||
format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans);
|
||||
|
|
@ -700,7 +727,7 @@ void OpenSLCapture::open(std::string_view name)
|
|||
SLDataFormat_PCM format_pcm{};
|
||||
format_pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
format_pcm.numChannels = mDevice->channelsFromFmt();
|
||||
format_pcm.samplesPerSec = mDevice->Frequency * 1000;
|
||||
format_pcm.samplesPerSec = mDevice->mSampleRate * 1000;
|
||||
format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
|
||||
format_pcm.containerSize = format_pcm.bitsPerSample;
|
||||
format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
|
||||
|
|
@ -752,20 +779,20 @@ void OpenSLCapture::open(std::string_view name)
|
|||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
const uint chunk_size{mDevice->UpdateSize * mFrameSize};
|
||||
const uint chunk_size{mDevice->mUpdateSize * mFrameSize};
|
||||
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);
|
||||
std::fill_n(data.second.buf, data.second.len*chunk_size, silence);
|
||||
for(size_t i{0u};i < data.first.len && SL_RESULT_SUCCESS == result;i++)
|
||||
std::fill_n(data[0].buf, data[0].len*chunk_size, silence);
|
||||
std::fill_n(data[1].buf, data[1].len*chunk_size, silence);
|
||||
for(size_t i{0u};i < data[0].len && SL_RESULT_SUCCESS == result;i++)
|
||||
{
|
||||
result = VCALL(bufferQueue,Enqueue)(data.first.buf + chunk_size*i, chunk_size);
|
||||
result = VCALL(bufferQueue,Enqueue)(data[0].buf + chunk_size*i, chunk_size);
|
||||
PrintErr(result, "bufferQueue->Enqueue");
|
||||
}
|
||||
for(size_t i{0u};i < data.second.len && SL_RESULT_SUCCESS == result;i++)
|
||||
for(size_t i{0u};i < data[1].len && SL_RESULT_SUCCESS == result;i++)
|
||||
{
|
||||
result = VCALL(bufferQueue,Enqueue)(data.second.buf + chunk_size*i, chunk_size);
|
||||
result = VCALL(bufferQueue,Enqueue)(data[1].buf + chunk_size*i, chunk_size);
|
||||
PrintErr(result, "bufferQueue->Enqueue");
|
||||
}
|
||||
}
|
||||
|
|
@ -782,10 +809,10 @@ void OpenSLCapture::open(std::string_view name)
|
|||
mEngine = nullptr;
|
||||
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to initialize OpenSL device: 0x%08x", result};
|
||||
"Failed to initialize OpenSL device: {:#08x}", result};
|
||||
}
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
void OpenSLCapture::start()
|
||||
|
|
@ -801,7 +828,7 @@ void OpenSLCapture::start()
|
|||
}
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start capture: 0x%08x", result};
|
||||
"Failed to start capture: {:#08x}", result};
|
||||
}
|
||||
|
||||
void OpenSLCapture::stop()
|
||||
|
|
@ -819,7 +846,7 @@ void OpenSLCapture::stop()
|
|||
|
||||
void OpenSLCapture::captureSamples(std::byte *buffer, uint samples)
|
||||
{
|
||||
const uint update_size{mDevice->UpdateSize};
|
||||
const uint update_size{mDevice->mUpdateSize};
|
||||
const uint chunk_size{update_size * mFrameSize};
|
||||
|
||||
/* Read the desired samples from the ring buffer then advance its read
|
||||
|
|
@ -830,7 +857,7 @@ void OpenSLCapture::captureSamples(std::byte *buffer, uint samples)
|
|||
for(uint i{0};i < samples;)
|
||||
{
|
||||
const uint rem{std::min(samples - i, update_size - mSplOffset)};
|
||||
std::copy_n(rdata.first.buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize},
|
||||
std::copy_n(rdata[0].buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize},
|
||||
buffer + i*size_t{mFrameSize});
|
||||
|
||||
mSplOffset += rem;
|
||||
|
|
@ -840,11 +867,11 @@ void OpenSLCapture::captureSamples(std::byte *buffer, uint samples)
|
|||
mSplOffset = 0;
|
||||
|
||||
++adv_count;
|
||||
rdata.first.len -= 1;
|
||||
if(!rdata.first.len)
|
||||
rdata.first = rdata.second;
|
||||
rdata[0].len -= 1;
|
||||
if(!rdata[0].len)
|
||||
rdata[0] = rdata[1];
|
||||
else
|
||||
rdata.first.buf += chunk_size;
|
||||
rdata[0].buf += chunk_size;
|
||||
}
|
||||
|
||||
i += rem;
|
||||
|
|
@ -858,7 +885,7 @@ void OpenSLCapture::captureSamples(std::byte *buffer, uint samples)
|
|||
PrintErr(result, "recordObj->GetInterface");
|
||||
if(SL_RESULT_SUCCESS != result) UNLIKELY
|
||||
{
|
||||
mDevice->handleDisconnect("Failed to get capture buffer queue: 0x%08x", result);
|
||||
mDevice->handleDisconnect("Failed to get capture buffer queue: {:#08x}", result);
|
||||
bufferQueue = nullptr;
|
||||
}
|
||||
}
|
||||
|
|
@ -877,20 +904,20 @@ void OpenSLCapture::captureSamples(std::byte *buffer, uint samples)
|
|||
|
||||
SLresult result{SL_RESULT_SUCCESS};
|
||||
auto wdata = mRing->getWriteVector();
|
||||
if(adv_count > wdata.second.len) LIKELY
|
||||
if(adv_count > wdata[1].len) LIKELY
|
||||
{
|
||||
auto len1 = std::min(wdata.first.len, adv_count-wdata.second.len);
|
||||
auto buf1 = wdata.first.buf + chunk_size*(wdata.first.len-len1);
|
||||
auto len1 = std::min(wdata[0].len, adv_count-wdata[1].len);
|
||||
auto buf1 = wdata[0].buf + chunk_size*(wdata[0].len-len1);
|
||||
for(size_t i{0u};i < len1 && SL_RESULT_SUCCESS == result;i++)
|
||||
{
|
||||
result = VCALL(bufferQueue,Enqueue)(buf1 + chunk_size*i, chunk_size);
|
||||
PrintErr(result, "bufferQueue->Enqueue");
|
||||
}
|
||||
}
|
||||
if(wdata.second.len > 0)
|
||||
if(wdata[1].len > 0)
|
||||
{
|
||||
auto len2 = std::min(wdata.second.len, adv_count);
|
||||
auto buf2 = wdata.second.buf + chunk_size*(wdata.second.len-len2);
|
||||
auto len2 = std::min(wdata[1].len, adv_count);
|
||||
auto buf2 = wdata[1].buf + chunk_size*(wdata[1].len-len2);
|
||||
for(size_t i{0u};i < len2 && SL_RESULT_SUCCESS == result;i++)
|
||||
{
|
||||
result = VCALL(bufferQueue,Enqueue)(buf2 + chunk_size*i, chunk_size);
|
||||
|
|
@ -900,11 +927,43 @@ void OpenSLCapture::captureSamples(std::byte *buffer, uint samples)
|
|||
}
|
||||
|
||||
uint OpenSLCapture::availableSamples()
|
||||
{ return static_cast<uint>(mRing->readSpace()*mDevice->UpdateSize - mSplOffset); }
|
||||
{ return static_cast<uint>(mRing->readSpace()*mDevice->mUpdateSize - mSplOffset); }
|
||||
|
||||
} // namespace
|
||||
|
||||
bool OSLBackendFactory::init() { return true; }
|
||||
bool OSLBackendFactory::init()
|
||||
{
|
||||
#if HAVE_DYNLOAD
|
||||
if(!sles_handle)
|
||||
{
|
||||
#define SLES_LIBNAME "libOpenSLES.so"
|
||||
sles_handle = LoadLib(SLES_LIBNAME);
|
||||
if(!sles_handle)
|
||||
{
|
||||
WARN("Failed to load {}", SLES_LIBNAME);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string missing_syms;
|
||||
#define LOAD_SYMBOL(f) do { \
|
||||
p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(sles_handle, #f)); \
|
||||
if(p##f == nullptr) missing_syms += "\n" #f; \
|
||||
} while(0)
|
||||
SLES_SYMBOLS(LOAD_SYMBOL);
|
||||
#undef LOAD_SYMBOL
|
||||
|
||||
if(!missing_syms.empty())
|
||||
{
|
||||
WARN("Missing expected symbols:{}", missing_syms);
|
||||
CloseLib(sles_handle);
|
||||
sles_handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OSLBackendFactory::querySupport(BackendType type)
|
||||
{ return (type == BackendType::Playback || type == BackendType::Capture); }
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@
|
|||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
|
@ -43,13 +42,12 @@
|
|||
#include <vector>
|
||||
|
||||
#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 "fmt/core.h"
|
||||
#include "ringbuffer.h"
|
||||
|
||||
#include <sys/soundcard.h>
|
||||
|
|
@ -169,18 +167,13 @@ void ALCossListAppend(std::vector<DevMap> &list, std::string_view handle, std::s
|
|||
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{handle};
|
||||
auto count = 1;
|
||||
auto newname = std::string{handle};
|
||||
while(checkName(newname))
|
||||
{
|
||||
newname = handle;
|
||||
newname += " #";
|
||||
newname += std::to_string(++count);
|
||||
}
|
||||
newname = fmt::format("{} #{}", handle, ++count);
|
||||
|
||||
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());
|
||||
const auto &entry = list.emplace_back(std::move(newname), path);
|
||||
TRACE("Got device \"{}\", \"{}\"", entry.name, entry.device_name);
|
||||
}
|
||||
|
||||
void ALCossListPopulate(std::vector<DevMap> &devlist, int type_flag)
|
||||
|
|
@ -189,13 +182,13 @@ void ALCossListPopulate(std::vector<DevMap> &devlist, int type_flag)
|
|||
FileHandle file;
|
||||
if(!file.open("/dev/mixer", O_RDONLY))
|
||||
{
|
||||
TRACE("Could not open /dev/mixer: %s\n", std::generic_category().message(errno).c_str());
|
||||
TRACE("Could not open /dev/mixer: {}", std::generic_category().message(errno));
|
||||
goto done;
|
||||
}
|
||||
|
||||
if(ioctl(file.get(), SNDCTL_SYSINFO, &si) == -1)
|
||||
{
|
||||
TRACE("SNDCTL_SYSINFO failed: %s\n", std::generic_category().message(errno).c_str());
|
||||
TRACE("SNDCTL_SYSINFO failed: {}", std::generic_category().message(errno));
|
||||
goto done;
|
||||
}
|
||||
|
||||
|
|
@ -205,8 +198,7 @@ void ALCossListPopulate(std::vector<DevMap> &devlist, int type_flag)
|
|||
ai.dev = i;
|
||||
if(ioctl(file.get(), SNDCTL_AUDIOINFO, &ai) == -1)
|
||||
{
|
||||
ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i,
|
||||
std::generic_category().message(errno).c_str());
|
||||
ERR("SNDCTL_AUDIOINFO ({}) failed: {}", i, std::generic_category().message(errno));
|
||||
continue;
|
||||
}
|
||||
if(!(ai.caps&type_flag) || ai.devnode[0] == '\0')
|
||||
|
|
@ -256,7 +248,7 @@ uint log2i(uint x)
|
|||
|
||||
|
||||
struct OSSPlayback final : public BackendBase {
|
||||
OSSPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit OSSPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~OSSPlayback() override;
|
||||
|
||||
int mixerProc();
|
||||
|
|
@ -302,13 +294,13 @@ int OSSPlayback::mixerProc()
|
|||
if(errno == EINTR || errno == EAGAIN)
|
||||
continue;
|
||||
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());
|
||||
ERR("poll failed: {}", errstr);
|
||||
mDevice->handleDisconnect("Failed waiting for playback buffer: {}", errstr);
|
||||
break;
|
||||
}
|
||||
else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */
|
||||
{
|
||||
WARN("poll timeout\n");
|
||||
WARN("poll timeout");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -323,8 +315,8 @@ int OSSPlayback::mixerProc()
|
|||
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
|
||||
continue;
|
||||
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());
|
||||
ERR("write failed: {}", errstr);
|
||||
mDevice->handleDisconnect("Failed writing playback samples: {}", errstr);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -352,20 +344,20 @@ void OSSPlayback::open(std::string_view name)
|
|||
);
|
||||
if(iter == PlaybackDevices.cend())
|
||||
throw al::backend_exception{al::backend_error::NoDevice,
|
||||
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
|
||||
"Device name \"{}\" not found", name};
|
||||
devname = iter->device_name.c_str();
|
||||
}
|
||||
|
||||
int fd{::open(devname, O_WRONLY)};
|
||||
const auto fd = ::open(devname, O_WRONLY); /* NOLINT(cppcoreguidelines-pro-type-vararg) */
|
||||
if(fd == -1)
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
|
||||
std::generic_category().message(errno).c_str()};
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Could not open {}: {}", devname,
|
||||
std::generic_category().message(errno)};
|
||||
|
||||
if(mFd != -1)
|
||||
::close(mFd);
|
||||
mFd = fd;
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
bool OSSPlayback::reset()
|
||||
|
|
@ -390,31 +382,33 @@ bool OSSPlayback::reset()
|
|||
break;
|
||||
}
|
||||
|
||||
uint periods{mDevice->BufferSize / mDevice->UpdateSize};
|
||||
uint periods{mDevice->mBufferSize / mDevice->mUpdateSize};
|
||||
uint numChannels{mDevice->channelsFromFmt()};
|
||||
uint ossSpeed{mDevice->Frequency};
|
||||
uint ossSpeed{mDevice->mSampleRate};
|
||||
uint frameSize{numChannels * mDevice->bytesFromFmt()};
|
||||
/* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */
|
||||
uint log2FragmentSize{std::max(log2i(mDevice->UpdateSize*frameSize), 4u)};
|
||||
uint log2FragmentSize{std::max(log2i(mDevice->mUpdateSize*frameSize), 4u)};
|
||||
uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
|
||||
|
||||
audio_buf_info info{};
|
||||
#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()};
|
||||
throw al::backend_exception{al::backend_error::DeviceError, #func " failed: {}", \
|
||||
std::generic_category().message(errno)};
|
||||
|
||||
/* Don't fail if SETFRAGMENT fails. We can handle just about anything
|
||||
* that's reported back via GETOSPACE */
|
||||
/* NOLINTBEGIN(cppcoreguidelines-pro-type-vararg) */
|
||||
ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize);
|
||||
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
|
||||
CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels));
|
||||
CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed));
|
||||
CHECKERR(ioctl(mFd, SNDCTL_DSP_GETOSPACE, &info));
|
||||
/* NOLINTEND(cppcoreguidelines-pro-type-vararg) */
|
||||
#undef CHECKERR
|
||||
|
||||
if(mDevice->channelsFromFmt() != numChannels)
|
||||
{
|
||||
ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice->FmtChans),
|
||||
ERR("Failed to set {}, got {} channels instead", DevFmtChannelsString(mDevice->FmtChans),
|
||||
numChannels);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -423,18 +417,18 @@ bool OSSPlayback::reset()
|
|||
(ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) ||
|
||||
(ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort)))
|
||||
{
|
||||
ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(mDevice->FmtType),
|
||||
ossFormat);
|
||||
ERR("Failed to set {} samples, got OSS format {:#x}", DevFmtTypeString(mDevice->FmtType),
|
||||
as_unsigned(ossFormat));
|
||||
return false;
|
||||
}
|
||||
|
||||
mDevice->Frequency = ossSpeed;
|
||||
mDevice->UpdateSize = static_cast<uint>(info.fragsize) / frameSize;
|
||||
mDevice->BufferSize = static_cast<uint>(info.fragments) * mDevice->UpdateSize;
|
||||
mDevice->mSampleRate = ossSpeed;
|
||||
mDevice->mUpdateSize = static_cast<uint>(info.fragsize) / frameSize;
|
||||
mDevice->mBufferSize = static_cast<uint>(info.fragments) * mDevice->mUpdateSize;
|
||||
|
||||
setDefaultChannelOrder();
|
||||
|
||||
mMixData.resize(size_t{mDevice->UpdateSize} * mDevice->frameSizeFromFmt());
|
||||
mMixData.resize(size_t{mDevice->mUpdateSize} * mDevice->frameSizeFromFmt());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -443,11 +437,11 @@ void OSSPlayback::start()
|
|||
{
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&OSSPlayback::mixerProc), this};
|
||||
mThread = std::thread{&OSSPlayback::mixerProc, this};
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start mixing thread: %s", e.what()};
|
||||
"Failed to start mixing thread: {}", e.what()};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -457,13 +451,13 @@ void OSSPlayback::stop()
|
|||
return;
|
||||
mThread.join();
|
||||
|
||||
if(ioctl(mFd, SNDCTL_DSP_RESET) != 0)
|
||||
ERR("Error resetting device: %s\n", std::generic_category().message(errno).c_str());
|
||||
if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) /* NOLINT(cppcoreguidelines-pro-type-vararg) */
|
||||
ERR("Error resetting device: {}", std::generic_category().message(errno));
|
||||
}
|
||||
|
||||
|
||||
struct OSScapture final : public BackendBase {
|
||||
OSScapture(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit OSScapture(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~OSScapture() override;
|
||||
|
||||
int recordProc();
|
||||
|
|
@ -507,25 +501,25 @@ int OSScapture::recordProc()
|
|||
if(errno == EINTR || errno == EAGAIN)
|
||||
continue;
|
||||
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());
|
||||
ERR("poll failed: {}", errstr);
|
||||
mDevice->handleDisconnect("Failed to check capture samples: {}", errstr);
|
||||
break;
|
||||
}
|
||||
else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */
|
||||
{
|
||||
WARN("poll timeout\n");
|
||||
WARN("poll timeout");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto vec = mRing->getWriteVector();
|
||||
if(vec.first.len > 0)
|
||||
if(vec[0].len > 0)
|
||||
{
|
||||
ssize_t amt{read(mFd, vec.first.buf, vec.first.len*frame_size)};
|
||||
ssize_t amt{read(mFd, vec[0].buf, vec[0].len*frame_size)};
|
||||
if(amt < 0)
|
||||
{
|
||||
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());
|
||||
ERR("read failed: {}", errstr);
|
||||
mDevice->handleDisconnect("Failed reading capture samples: {}", errstr);
|
||||
break;
|
||||
}
|
||||
mRing->writeAdvance(static_cast<size_t>(amt)/frame_size);
|
||||
|
|
@ -552,14 +546,14 @@ void OSScapture::open(std::string_view name)
|
|||
);
|
||||
if(iter == CaptureDevices.cend())
|
||||
throw al::backend_exception{al::backend_error::NoDevice,
|
||||
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
|
||||
"Device name \"{}\" not found", name};
|
||||
devname = iter->device_name.c_str();
|
||||
}
|
||||
|
||||
mFd = ::open(devname, O_RDONLY);
|
||||
mFd = ::open(devname, O_RDONLY); /* NOLINT(cppcoreguidelines-pro-type-vararg) */
|
||||
if(mFd == -1)
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
|
||||
std::generic_category().message(errno).c_str()};
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Could not open {}: {}", devname,
|
||||
std::generic_category().message(errno)};
|
||||
|
||||
int ossFormat{};
|
||||
switch(mDevice->FmtType)
|
||||
|
|
@ -578,55 +572,57 @@ void OSScapture::open(std::string_view name)
|
|||
case DevFmtUInt:
|
||||
case DevFmtFloat:
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
|
||||
"{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
|
||||
}
|
||||
|
||||
uint periods{4};
|
||||
uint numChannels{mDevice->channelsFromFmt()};
|
||||
uint frameSize{numChannels * mDevice->bytesFromFmt()};
|
||||
uint ossSpeed{mDevice->Frequency};
|
||||
uint ossSpeed{mDevice->mSampleRate};
|
||||
/* according to the OSS spec, 16 bytes are the minimum */
|
||||
uint log2FragmentSize{std::max(log2i(mDevice->BufferSize * frameSize / periods), 4u)};
|
||||
uint log2FragmentSize{std::max(log2i(mDevice->mBufferSize * 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", \
|
||||
std::generic_category().message(errno).c_str()}; \
|
||||
throw al::backend_exception{al::backend_error::DeviceError, #func " failed: {}", \
|
||||
std::generic_category().message(errno)}; \
|
||||
}
|
||||
/* NOLINTBEGIN(cppcoreguidelines-pro-type-vararg) */
|
||||
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize));
|
||||
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
|
||||
CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels));
|
||||
CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed));
|
||||
CHECKERR(ioctl(mFd, SNDCTL_DSP_GETISPACE, &info));
|
||||
/* NOLINTEND(cppcoreguidelines-pro-type-vararg) */
|
||||
#undef CHECKERR
|
||||
|
||||
if(mDevice->channelsFromFmt() != numChannels)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to set %s, got %d channels instead", DevFmtChannelsString(mDevice->FmtChans),
|
||||
"Failed to set {}, got {} channels instead", DevFmtChannelsString(mDevice->FmtChans),
|
||||
numChannels};
|
||||
|
||||
if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte)
|
||||
|| (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte)
|
||||
|| (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort)))
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to set %s samples, got OSS format %#x", DevFmtTypeString(mDevice->FmtType),
|
||||
ossFormat};
|
||||
"Failed to set {} samples, got OSS format {:#x}", DevFmtTypeString(mDevice->FmtType),
|
||||
as_unsigned(ossFormat)};
|
||||
|
||||
mRing = RingBuffer::Create(mDevice->BufferSize, frameSize, false);
|
||||
mRing = RingBuffer::Create(mDevice->mBufferSize, frameSize, false);
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
void OSScapture::start()
|
||||
{
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&OSScapture::recordProc), this};
|
||||
mThread = std::thread{&OSScapture::recordProc, this};
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start recording thread: %s", e.what()};
|
||||
"Failed to start recording thread: {}", e.what()};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -636,8 +632,8 @@ void OSScapture::stop()
|
|||
return;
|
||||
mThread.join();
|
||||
|
||||
if(ioctl(mFd, SNDCTL_DSP_RESET) != 0)
|
||||
ERR("Error resetting device: %s\n", std::generic_category().message(errno).c_str());
|
||||
if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) /* NOLINT(cppcoreguidelines-pro-type-vararg) */
|
||||
ERR("Error resetting device: {}", std::generic_category().message(errno));
|
||||
}
|
||||
|
||||
void OSScapture::captureSamples(std::byte *buffer, uint samples)
|
||||
|
|
|
|||
700
Engine/lib/openal-soft/alc/backends/otherio.cpp
Normal file
700
Engine/lib/openal-soft/alc/backends/otherio.cpp
Normal file
|
|
@ -0,0 +1,700 @@
|
|||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2024 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "otherio.h"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <winreg.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <memory.h>
|
||||
|
||||
#include <wtypes.h>
|
||||
#include <cguid.h>
|
||||
#include <devpropdef.h>
|
||||
#include <mmreg.h>
|
||||
#include <propsys.h>
|
||||
#include <propkey.h>
|
||||
#include <devpkey.h>
|
||||
#ifndef _WAVEFORMATEXTENSIBLE_
|
||||
#include <ks.h>
|
||||
#include <ksmedia.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "albit.h"
|
||||
#include "alnumeric.h"
|
||||
#include "althrd_setname.h"
|
||||
#include "comptr.h"
|
||||
#include "core/converter.h"
|
||||
#include "core/device.h"
|
||||
#include "core/helpers.h"
|
||||
#include "core/logging.h"
|
||||
#include "strutils.h"
|
||||
|
||||
|
||||
/* A custom C++ interface that should be capable of interoperating with ASIO
|
||||
* drivers.
|
||||
*/
|
||||
enum class ORIOError : LONG {
|
||||
Okay = 0,
|
||||
Success = 0x3f4847a0,
|
||||
NotPresent = -1000,
|
||||
HWMalfunction,
|
||||
InvalidParameter,
|
||||
InvalidMode,
|
||||
SPNotAdvancing,
|
||||
NoClock,
|
||||
NoMemory,
|
||||
};
|
||||
|
||||
/* A 64-bit integer or double, which has the most significant 32-bit word first. */
|
||||
struct ORIO64Bit {
|
||||
uint32_t hi;
|
||||
uint32_t lo;
|
||||
|
||||
template<typename T>
|
||||
auto as() const -> T = delete;
|
||||
};
|
||||
|
||||
template<> [[nodiscard]]
|
||||
auto ORIO64Bit::as() const -> uint64_t { return (uint64_t{hi}<<32) | lo; }
|
||||
template<> [[nodiscard]]
|
||||
auto ORIO64Bit::as() const -> int64_t { return static_cast<int64_t>(as<uint64_t>()); }
|
||||
template<> [[nodiscard]]
|
||||
auto ORIO64Bit::as() const -> double { return al::bit_cast<double>(as<uint64_t>()); }
|
||||
|
||||
|
||||
enum class ORIOSampleType : LONG {
|
||||
Int16BE = 0,
|
||||
Int24BE = 1,
|
||||
Int32BE = 2,
|
||||
Float32BE = 3,
|
||||
Float64BE = 4,
|
||||
Int32BE16 = 8,
|
||||
Int32BE18 = 9,
|
||||
Int32BE20 = 10,
|
||||
Int32BE24 = 11,
|
||||
|
||||
Int16LE = 16,
|
||||
Int24LE = 17,
|
||||
Int32LE = 18,
|
||||
Float32LE = 19,
|
||||
Float64LE = 20,
|
||||
Int32LE16 = 24,
|
||||
Int32LE18 = 25,
|
||||
Int32LE20 = 26,
|
||||
Int32LE24 = 27,
|
||||
|
||||
DSDInt8LSB1 = 32,
|
||||
DSDInt8MSB1 = 33,
|
||||
|
||||
DSDInt8 = 40,
|
||||
};
|
||||
|
||||
struct ORIOClockSource {
|
||||
LONG mIndex;
|
||||
LONG mAssocChannel;
|
||||
LONG mAssocGroup;
|
||||
LONG mIsCurrent;
|
||||
std::array<char,32> mName;
|
||||
};
|
||||
|
||||
struct ORIOChannelInfo {
|
||||
LONG mChannel;
|
||||
LONG mIsInput;
|
||||
LONG mIsActive;
|
||||
LONG mGroup;
|
||||
ORIOSampleType mSampleType;
|
||||
std::array<char,32> mName;
|
||||
};
|
||||
|
||||
struct ORIOBufferInfo {
|
||||
LONG mIsInput;
|
||||
LONG mChannelNum;
|
||||
std::array<void*,2> mBuffers;
|
||||
};
|
||||
|
||||
struct ORIOTime {
|
||||
struct TimeInfo {
|
||||
double mSpeed;
|
||||
ORIO64Bit mSystemTime;
|
||||
ORIO64Bit mSamplePosition;
|
||||
double mSampleRate;
|
||||
ULONG mFlags;
|
||||
std::array<char,12> mReserved;
|
||||
};
|
||||
struct TimeCode {
|
||||
double mSpeed;
|
||||
ORIO64Bit mTimeCodeSamples;
|
||||
ULONG mFlags;
|
||||
std::array<char,64> mFuture;
|
||||
};
|
||||
|
||||
std::array<LONG,4> mReserved;
|
||||
TimeInfo mTimeInfo;
|
||||
TimeCode mTimeCode;
|
||||
};
|
||||
|
||||
#ifdef _WIN64
|
||||
#define ORIO_CALLBACK CALLBACK
|
||||
#else
|
||||
#define ORIO_CALLBACK
|
||||
#endif
|
||||
|
||||
struct ORIOCallbacks {
|
||||
void (ORIO_CALLBACK*BufferSwitch)(LONG bufferIndex, LONG directProcess) noexcept;
|
||||
void (ORIO_CALLBACK*SampleRateDidChange)(double srate) noexcept;
|
||||
auto (ORIO_CALLBACK*Message)(LONG selector, LONG value, void *message, double *opt) noexcept -> LONG;
|
||||
auto (ORIO_CALLBACK*BufferSwitchTimeInfo)(ORIOTime *timeInfo, LONG bufferIndex, LONG directProcess) noexcept -> ORIOTime*;
|
||||
};
|
||||
|
||||
/* COM interfaces don't include a virtual destructor in their pure-virtual
|
||||
* classes, and we can't add one without breaking ABI.
|
||||
*/
|
||||
#ifdef __GNUC__
|
||||
_Pragma("GCC diagnostic push")
|
||||
_Pragma("GCC diagnostic ignored \"-Wnon-virtual-dtor\"")
|
||||
#endif
|
||||
/* NOLINTNEXTLINE(cppcoreguidelines-virtual-class-destructor) */
|
||||
struct ORIOiface : public IUnknown {
|
||||
STDMETHOD_(LONG, Init)(void *sysHandle) = 0;
|
||||
/* A fixed-length span should be passed exactly the same as one pointer.
|
||||
* This ensures an appropriately-sized buffer for the driver.
|
||||
*/
|
||||
STDMETHOD_(void, GetDriverName)(al::span<char,32> name) = 0;
|
||||
STDMETHOD_(LONG, GetDriverVersion)() = 0;
|
||||
STDMETHOD_(void, GetErrorMessage)(al::span<char,124> message) = 0;
|
||||
STDMETHOD_(ORIOError, Start)() = 0;
|
||||
STDMETHOD_(ORIOError, Stop)() = 0;
|
||||
STDMETHOD_(ORIOError, GetChannels)(LONG *numInput, LONG *numOutput) = 0;
|
||||
STDMETHOD_(ORIOError, GetLatencies)(LONG *inputLatency, LONG *outputLatency) = 0;
|
||||
STDMETHOD_(ORIOError, GetBufferSize)(LONG *minSize, LONG *maxSize, LONG *preferredSize, LONG *granularity) = 0;
|
||||
STDMETHOD_(ORIOError, CanSampleRate)(double srate) = 0;
|
||||
STDMETHOD_(ORIOError, GetSampleRate)(double *srate) = 0;
|
||||
STDMETHOD_(ORIOError, SetSampleRate)(double srate) = 0;
|
||||
STDMETHOD_(ORIOError, GetClockSources)(ORIOClockSource *clocks, LONG *numSources) = 0;
|
||||
STDMETHOD_(ORIOError, SetClockSource)(LONG index) = 0;
|
||||
STDMETHOD_(ORIOError, GetSamplePosition)(ORIO64Bit *splPos, ORIO64Bit *tstampNS) = 0;
|
||||
STDMETHOD_(ORIOError, GetChannelInfo)(ORIOChannelInfo *info) = 0;
|
||||
STDMETHOD_(ORIOError, CreateBuffers)(ORIOBufferInfo *infos, LONG numInfos, LONG bufferSize, ORIOCallbacks *callbacks) = 0;
|
||||
STDMETHOD_(ORIOError, DisposeBuffers)() = 0;
|
||||
STDMETHOD_(ORIOError, ControlPanel)() = 0;
|
||||
STDMETHOD_(ORIOError, Future)(LONG selector, void *opt) = 0;
|
||||
STDMETHOD_(ORIOError, OutputReady)() = 0;
|
||||
|
||||
ORIOiface() = default;
|
||||
ORIOiface(const ORIOiface&) = delete;
|
||||
auto operator=(const ORIOiface&) -> ORIOiface& = delete;
|
||||
~ORIOiface() = delete;
|
||||
};
|
||||
#ifdef __GNUC__
|
||||
_Pragma("GCC diagnostic pop")
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
using std::chrono::nanoseconds;
|
||||
using std::chrono::milliseconds;
|
||||
using std::chrono::seconds;
|
||||
|
||||
|
||||
struct DeviceEntry {
|
||||
std::string mDrvName;
|
||||
CLSID mDrvGuid{};
|
||||
};
|
||||
|
||||
std::vector<DeviceEntry> gDeviceList;
|
||||
|
||||
|
||||
struct KeyCloser {
|
||||
void operator()(HKEY key) { RegCloseKey(key); }
|
||||
};
|
||||
using KeyPtr = std::unique_ptr<std::remove_pointer_t<HKEY>,KeyCloser>;
|
||||
|
||||
[[nodiscard]]
|
||||
auto PopulateDeviceList() -> HRESULT
|
||||
{
|
||||
auto regbase = KeyPtr{};
|
||||
auto res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\ASIO", 0, KEY_READ,
|
||||
al::out_ptr(regbase));
|
||||
if(res != ERROR_SUCCESS)
|
||||
{
|
||||
ERR("Error opening HKLM\\Software\\ASIO: {}", res);
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
auto numkeys = DWORD{};
|
||||
auto maxkeylen = DWORD{};
|
||||
res = RegQueryInfoKeyW(regbase.get(), nullptr, nullptr, nullptr, &numkeys, &maxkeylen, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr);
|
||||
if(res != ERROR_SUCCESS)
|
||||
{
|
||||
ERR("Error querying HKLM\\Software\\ASIO info: {}", res);
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
/* maxkeylen is the max number of unicode characters a subkey is. A unicode
|
||||
* character can occupy two WCHARs, so ensure there's enough space for them
|
||||
* and the null char.
|
||||
*/
|
||||
auto keyname = std::vector<WCHAR>(maxkeylen*2 + 1);
|
||||
for(DWORD i{0};i < numkeys;++i)
|
||||
{
|
||||
auto namelen = static_cast<DWORD>(keyname.size());
|
||||
res = RegEnumKeyExW(regbase.get(), i, keyname.data(), &namelen, nullptr, nullptr, nullptr,
|
||||
nullptr);
|
||||
if(res != ERROR_SUCCESS)
|
||||
{
|
||||
ERR("Error querying HKLM\\Software\\ASIO subkey {}: {}", i, res);
|
||||
continue;
|
||||
}
|
||||
if(namelen == 0)
|
||||
{
|
||||
ERR("HKLM\\Software\\ASIO subkey {} is blank?", i);
|
||||
continue;
|
||||
}
|
||||
auto subkeyname = wstr_to_utf8({keyname.data(), namelen});
|
||||
|
||||
auto subkey = KeyPtr{};
|
||||
res = RegOpenKeyExW(regbase.get(), keyname.data(), 0, KEY_READ, al::out_ptr(subkey));
|
||||
if(res != ERROR_SUCCESS)
|
||||
{
|
||||
ERR("Error opening HKLM\\Software\\ASIO\\{}: {}", subkeyname, res);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto idstr = std::array<WCHAR,48>{};
|
||||
auto readsize = DWORD{idstr.size()*sizeof(WCHAR)};
|
||||
res = RegGetValueW(subkey.get(), L"", L"CLSID", RRF_RT_REG_SZ, nullptr, idstr.data(),
|
||||
&readsize);
|
||||
if(res != ERROR_SUCCESS)
|
||||
{
|
||||
ERR("Failed to read HKLM\\Software\\ASIO\\{}\\CLSID: {}", subkeyname, res);
|
||||
continue;
|
||||
}
|
||||
idstr.back() = 0;
|
||||
|
||||
auto guid = CLSID{};
|
||||
if(auto hr = CLSIDFromString(idstr.data(), &guid); FAILED(hr))
|
||||
{
|
||||
ERR("Failed to parse CLSID \"{}\": {:#x}", wstr_to_utf8(idstr.data()),
|
||||
as_unsigned(hr));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The CLSID is also used for the IID. */
|
||||
auto iface = ComPtr<ORIOiface>{};
|
||||
auto hr = CoCreateInstance(guid, nullptr, CLSCTX_INPROC_SERVER, guid, al::out_ptr(iface));
|
||||
if(SUCCEEDED(hr))
|
||||
{
|
||||
#if !ALSOFT_UWP
|
||||
if(!iface->Init(GetForegroundWindow()))
|
||||
#else
|
||||
if(!iface->Init(nullptr))
|
||||
#endif
|
||||
{
|
||||
ERR("Failed to initialize {}", subkeyname);
|
||||
continue;
|
||||
}
|
||||
auto drvname = std::array<char,32>{};
|
||||
iface->GetDriverName(drvname);
|
||||
auto drvver = iface->GetDriverVersion();
|
||||
|
||||
auto &entry = gDeviceList.emplace_back();
|
||||
entry.mDrvName = drvname.data();
|
||||
entry.mDrvGuid = guid;
|
||||
|
||||
TRACE("Got {} v{}, CLSID {{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}",
|
||||
entry.mDrvName, drvver, guid.Data1, guid.Data2, guid.Data3, guid.Data4[0],
|
||||
guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5],
|
||||
guid.Data4[6], guid.Data4[7]);
|
||||
}
|
||||
else
|
||||
ERR("Failed to create {} instance for CLSID {{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}: {:#x}",
|
||||
subkeyname.c_str(), guid.Data1, guid.Data2, guid.Data3, guid.Data4[0],
|
||||
guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5],
|
||||
guid.Data4[6], guid.Data4[7], as_unsigned(hr));
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
||||
enum class MsgType {
|
||||
OpenDevice,
|
||||
ResetDevice,
|
||||
StartDevice,
|
||||
StopDevice,
|
||||
CloseDevice,
|
||||
|
||||
QuitThread
|
||||
};
|
||||
|
||||
constexpr const char *GetMessageTypeName(MsgType type) noexcept
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case MsgType::OpenDevice: return "Open Device";
|
||||
case MsgType::ResetDevice: return "Reset Device";
|
||||
case MsgType::StartDevice: return "Start Device";
|
||||
case MsgType::StopDevice: return "Stop Device";
|
||||
case MsgType::CloseDevice: return "Close Device";
|
||||
case MsgType::QuitThread: break;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
/* Proxy interface used by the message handler, to ensure COM objects are used
|
||||
* on a thread where COM is initialized.
|
||||
*/
|
||||
struct OtherIOProxy {
|
||||
OtherIOProxy() = default;
|
||||
OtherIOProxy(const OtherIOProxy&) = delete;
|
||||
OtherIOProxy(OtherIOProxy&&) = delete;
|
||||
virtual ~OtherIOProxy() = default;
|
||||
|
||||
void operator=(const OtherIOProxy&) = delete;
|
||||
void operator=(OtherIOProxy&&) = delete;
|
||||
|
||||
virtual HRESULT openProxy(std::string_view name) = 0;
|
||||
virtual void closeProxy() = 0;
|
||||
|
||||
virtual HRESULT resetProxy() = 0;
|
||||
virtual HRESULT startProxy() = 0;
|
||||
virtual void stopProxy() = 0;
|
||||
|
||||
struct Msg {
|
||||
MsgType mType;
|
||||
OtherIOProxy *mProxy;
|
||||
std::string_view mParam;
|
||||
std::promise<HRESULT> mPromise;
|
||||
|
||||
explicit operator bool() const noexcept { return mType != MsgType::QuitThread; }
|
||||
};
|
||||
static inline std::deque<Msg> mMsgQueue;
|
||||
static inline std::mutex mMsgQueueLock;
|
||||
static inline std::condition_variable mMsgQueueCond;
|
||||
|
||||
auto pushMessage(MsgType type, std::string_view param={}) -> std::future<HRESULT>
|
||||
{
|
||||
auto promise = std::promise<HRESULT>{};
|
||||
auto future = std::future<HRESULT>{promise.get_future()};
|
||||
{
|
||||
auto msglock = std::lock_guard{mMsgQueueLock};
|
||||
mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)});
|
||||
}
|
||||
mMsgQueueCond.notify_one();
|
||||
return future;
|
||||
}
|
||||
|
||||
static auto popMessage() -> Msg
|
||||
{
|
||||
auto lock = std::unique_lock{mMsgQueueLock};
|
||||
mMsgQueueCond.wait(lock, []{return !mMsgQueue.empty();});
|
||||
auto msg = Msg{std::move(mMsgQueue.front())};
|
||||
mMsgQueue.pop_front();
|
||||
return msg;
|
||||
}
|
||||
|
||||
static void messageHandler(std::promise<HRESULT> *promise);
|
||||
};
|
||||
|
||||
void OtherIOProxy::messageHandler(std::promise<HRESULT> *promise)
|
||||
{
|
||||
TRACE("Starting COM message thread");
|
||||
|
||||
auto com = ComWrapper{COINIT_APARTMENTTHREADED};
|
||||
if(!com)
|
||||
{
|
||||
WARN("Failed to initialize COM: {:#x}", as_unsigned(com.status()));
|
||||
promise->set_value(com.status());
|
||||
return;
|
||||
}
|
||||
|
||||
auto hr = PopulateDeviceList();
|
||||
if(FAILED(hr))
|
||||
{
|
||||
promise->set_value(hr);
|
||||
return;
|
||||
}
|
||||
|
||||
promise->set_value(S_OK);
|
||||
promise = nullptr;
|
||||
|
||||
TRACE("Starting message loop");
|
||||
while(Msg msg{popMessage()})
|
||||
{
|
||||
TRACE("Got message \"{}\" ({:#04x}, this={}, param=\"{}\")",
|
||||
GetMessageTypeName(msg.mType), static_cast<uint>(msg.mType),
|
||||
static_cast<void*>(msg.mProxy), msg.mParam);
|
||||
|
||||
switch(msg.mType)
|
||||
{
|
||||
case MsgType::OpenDevice:
|
||||
hr = msg.mProxy->openProxy(msg.mParam);
|
||||
msg.mPromise.set_value(hr);
|
||||
continue;
|
||||
|
||||
case MsgType::ResetDevice:
|
||||
hr = msg.mProxy->resetProxy();
|
||||
msg.mPromise.set_value(hr);
|
||||
continue;
|
||||
|
||||
case MsgType::StartDevice:
|
||||
hr = msg.mProxy->startProxy();
|
||||
msg.mPromise.set_value(hr);
|
||||
continue;
|
||||
|
||||
case MsgType::StopDevice:
|
||||
msg.mProxy->stopProxy();
|
||||
msg.mPromise.set_value(S_OK);
|
||||
continue;
|
||||
|
||||
case MsgType::CloseDevice:
|
||||
msg.mProxy->closeProxy();
|
||||
msg.mPromise.set_value(S_OK);
|
||||
continue;
|
||||
|
||||
case MsgType::QuitThread:
|
||||
break;
|
||||
}
|
||||
ERR("Unexpected message: {}", int{al::to_underlying(msg.mType)});
|
||||
msg.mPromise.set_value(E_FAIL);
|
||||
}
|
||||
TRACE("Message loop finished");
|
||||
}
|
||||
|
||||
|
||||
struct OtherIOPlayback final : public BackendBase, OtherIOProxy {
|
||||
explicit OtherIOPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~OtherIOPlayback() final;
|
||||
|
||||
void mixerProc();
|
||||
|
||||
void open(std::string_view name) final;
|
||||
auto openProxy(std::string_view name) -> HRESULT final;
|
||||
void closeProxy() final;
|
||||
auto reset() -> bool final;
|
||||
auto resetProxy() -> HRESULT final;
|
||||
void start() final;
|
||||
auto startProxy() -> HRESULT final;
|
||||
void stop() final;
|
||||
void stopProxy() final;
|
||||
|
||||
HRESULT mOpenStatus{E_FAIL};
|
||||
|
||||
std::atomic<bool> mKillNow{true};
|
||||
std::thread mThread;
|
||||
};
|
||||
|
||||
OtherIOPlayback::~OtherIOPlayback()
|
||||
{
|
||||
if(SUCCEEDED(mOpenStatus))
|
||||
pushMessage(MsgType::CloseDevice).wait();
|
||||
}
|
||||
|
||||
void OtherIOPlayback::mixerProc()
|
||||
{
|
||||
const auto restTime = milliseconds{mDevice->mUpdateSize*1000/mDevice->mSampleRate / 2};
|
||||
|
||||
SetRTPriority();
|
||||
althrd_setname(GetMixerThreadName());
|
||||
|
||||
auto done = int64_t{0};
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
while(!mKillNow.load(std::memory_order_acquire)
|
||||
&& mDevice->Connected.load(std::memory_order_acquire))
|
||||
{
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
/* This converts from nanoseconds to nanosamples, then to samples. */
|
||||
const auto avail = int64_t{std::chrono::duration_cast<seconds>((now-start)
|
||||
* mDevice->mSampleRate).count()};
|
||||
if(avail-done < mDevice->mUpdateSize)
|
||||
{
|
||||
std::this_thread::sleep_for(restTime);
|
||||
continue;
|
||||
}
|
||||
while(avail-done >= mDevice->mUpdateSize)
|
||||
{
|
||||
mDevice->renderSamples(nullptr, mDevice->mUpdateSize, 0u);
|
||||
done += mDevice->mUpdateSize;
|
||||
}
|
||||
|
||||
if(done >= mDevice->mSampleRate)
|
||||
{
|
||||
auto s = seconds{done/mDevice->mSampleRate};
|
||||
start += s;
|
||||
done -= mDevice->mSampleRate*s.count();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OtherIOPlayback::open(std::string_view name)
|
||||
{
|
||||
if(name.empty() && !gDeviceList.empty())
|
||||
name = gDeviceList[0].mDrvName;
|
||||
else
|
||||
{
|
||||
auto iter = std::find_if(gDeviceList.cbegin(), gDeviceList.cend(),
|
||||
[name](const DeviceEntry &entry) { return entry.mDrvName == name; });
|
||||
if(iter == gDeviceList.cend())
|
||||
throw al::backend_exception{al::backend_error::NoDevice,
|
||||
"Device name \"{}\" not found", name};
|
||||
}
|
||||
|
||||
mOpenStatus = pushMessage(MsgType::OpenDevice, name).get();
|
||||
if(FAILED(mOpenStatus))
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Failed to open \"{}\"", name};
|
||||
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
auto OtherIOPlayback::openProxy(std::string_view name [[maybe_unused]]) -> HRESULT
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void OtherIOPlayback::closeProxy()
|
||||
{
|
||||
}
|
||||
|
||||
auto OtherIOPlayback::reset() -> bool
|
||||
{
|
||||
return SUCCEEDED(pushMessage(MsgType::ResetDevice).get());
|
||||
}
|
||||
|
||||
auto OtherIOPlayback::resetProxy() -> HRESULT
|
||||
{
|
||||
setDefaultWFXChannelOrder();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void OtherIOPlayback::start()
|
||||
{
|
||||
auto hr = pushMessage(MsgType::StartDevice).get();
|
||||
if(FAILED(hr))
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start playback: {:#x}", as_unsigned(hr)};
|
||||
}
|
||||
|
||||
auto OtherIOPlayback::startProxy() -> HRESULT
|
||||
{
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{&OtherIOPlayback::mixerProc, this};
|
||||
return S_OK;
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
ERR("Failed to start mixing thread: {}", e.what());
|
||||
}
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
void OtherIOPlayback::stop()
|
||||
{
|
||||
pushMessage(MsgType::StopDevice).wait();
|
||||
}
|
||||
|
||||
void OtherIOPlayback::stopProxy()
|
||||
{
|
||||
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
|
||||
return;
|
||||
mThread.join();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
auto OtherIOBackendFactory::init() -> bool
|
||||
{
|
||||
static HRESULT InitResult{E_FAIL};
|
||||
if(FAILED(InitResult)) try
|
||||
{
|
||||
auto promise = std::promise<HRESULT>{};
|
||||
auto future = promise.get_future();
|
||||
|
||||
std::thread{&OtherIOProxy::messageHandler, &promise}.detach();
|
||||
InitResult = future.get();
|
||||
}
|
||||
catch(...) {
|
||||
}
|
||||
|
||||
return SUCCEEDED(InitResult);
|
||||
}
|
||||
|
||||
auto OtherIOBackendFactory::querySupport(BackendType type) -> bool
|
||||
{ return type == BackendType::Playback; }
|
||||
|
||||
auto OtherIOBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
|
||||
{
|
||||
std::vector<std::string> outnames;
|
||||
|
||||
switch(type)
|
||||
{
|
||||
case BackendType::Playback:
|
||||
std::for_each(gDeviceList.cbegin(), gDeviceList.cend(),
|
||||
[&outnames](const DeviceEntry &entry) { outnames.emplace_back(entry.mDrvName); });
|
||||
break;
|
||||
|
||||
case BackendType::Capture:
|
||||
break;
|
||||
}
|
||||
|
||||
return outnames;
|
||||
}
|
||||
|
||||
auto OtherIOBackendFactory::createBackend(DeviceBase *device, BackendType type) -> BackendPtr
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new OtherIOPlayback{device}};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto OtherIOBackendFactory::getFactory() -> BackendFactory&
|
||||
{
|
||||
static auto factory = OtherIOBackendFactory{};
|
||||
return factory;
|
||||
}
|
||||
|
||||
auto OtherIOBackendFactory::queryEventSupport(alc::EventType, BackendType) -> alc::EventSupport
|
||||
{
|
||||
return alc::EventSupport::NoSupport;
|
||||
}
|
||||
21
Engine/lib/openal-soft/alc/backends/otherio.h
Normal file
21
Engine/lib/openal-soft/alc/backends/otherio.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef BACKENDS_OTHERIO_H
|
||||
#define BACKENDS_OTHERIO_H
|
||||
|
||||
#include "base.h"
|
||||
|
||||
struct OtherIOBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
auto init() -> bool final;
|
||||
|
||||
auto querySupport(BackendType type) -> bool final;
|
||||
|
||||
auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final;
|
||||
|
||||
auto enumerate(BackendType type) -> std::vector<std::string> final;
|
||||
|
||||
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
|
||||
|
||||
static auto getFactory() -> BackendFactory&;
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_OTHERIO_H */
|
||||
|
|
@ -35,6 +35,7 @@
|
|||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
|
@ -54,6 +55,8 @@
|
|||
#include "core/helpers.h"
|
||||
#include "core/logging.h"
|
||||
#include "dynload.h"
|
||||
#include "fmt/core.h"
|
||||
#include "fmt/ranges.h"
|
||||
#include "opthelpers.h"
|
||||
#include "ringbuffer.h"
|
||||
|
||||
|
|
@ -132,6 +135,9 @@ _Pragma("GCC diagnostic pop")
|
|||
|
||||
namespace {
|
||||
|
||||
template<typename T> [[nodiscard]] constexpr
|
||||
auto as_const_ptr(T *ptr) noexcept -> std::add_const_t<T>* { return ptr; }
|
||||
|
||||
struct PodDynamicBuilder {
|
||||
private:
|
||||
std::vector<std::byte> mStorage;
|
||||
|
|
@ -143,7 +149,7 @@ private:
|
|||
mStorage.resize(size);
|
||||
}
|
||||
catch(...) {
|
||||
ERR("Failed to resize POD storage\n");
|
||||
ERR("Failed to resize POD storage");
|
||||
return -ENOMEM;
|
||||
}
|
||||
mPod.data = mStorage.data();
|
||||
|
|
@ -152,7 +158,7 @@ private:
|
|||
}
|
||||
|
||||
public:
|
||||
PodDynamicBuilder(uint32_t initSize=0) : mStorage(initSize)
|
||||
explicit PodDynamicBuilder(uint32_t initSize=1024) : mStorage(initSize)
|
||||
, mPod{make_pod_builder(mStorage.data(), initSize)}
|
||||
{
|
||||
static constexpr auto callbacks{[]
|
||||
|
|
@ -189,12 +195,13 @@ bool check_version(const char *version)
|
|||
* future.
|
||||
*/
|
||||
int major{0}, minor{0}, revision{0};
|
||||
/* NOLINTNEXTLINE(cert-err34-c,cppcoreguidelines-pro-type-vararg) */
|
||||
int ret{sscanf(version, "%d.%d.%d", &major, &minor, &revision)};
|
||||
return ret == 3 && (major > PW_MAJOR || (major == PW_MAJOR && minor > PW_MINOR)
|
||||
|| (major == PW_MAJOR && minor == PW_MINOR && revision >= PW_MICRO));
|
||||
}
|
||||
|
||||
#ifdef HAVE_DYNLOAD
|
||||
#if HAVE_DYNLOAD
|
||||
#define PWIRE_FUNCS(MAGIC) \
|
||||
MAGIC(pw_context_connect) \
|
||||
MAGIC(pw_context_destroy) \
|
||||
|
|
@ -251,7 +258,7 @@ bool pwire_load()
|
|||
pwire_handle = LoadLib(pwire_library);
|
||||
if(!pwire_handle)
|
||||
{
|
||||
WARN("Failed to load %s\n", pwire_library);
|
||||
WARN("Failed to load {}", pwire_library);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -265,7 +272,7 @@ bool pwire_load()
|
|||
|
||||
if(!missing_funcs.empty())
|
||||
{
|
||||
WARN("Missing expected functions:%s\n", missing_funcs.c_str());
|
||||
WARN("Missing expected functions:{}", missing_funcs);
|
||||
CloseLib(pwire_handle);
|
||||
pwire_handle = nullptr;
|
||||
return false;
|
||||
|
|
@ -411,9 +418,10 @@ struct PwStreamDeleter {
|
|||
};
|
||||
using PwStreamPtr = std::unique_ptr<pw_stream,PwStreamDeleter>;
|
||||
|
||||
/* Enums for bitflags... again... *sigh* */
|
||||
/* NOLINTBEGIN(*EnumCastOutOfRange) Enums for bitflags... again... *sigh* */
|
||||
constexpr pw_stream_flags operator|(pw_stream_flags lhs, pw_stream_flags rhs) noexcept
|
||||
{ return static_cast<pw_stream_flags>(lhs | al::to_underlying(rhs)); }
|
||||
/* NOLINTEND(*EnumCastOutOfRange) */
|
||||
|
||||
constexpr pw_stream_flags& operator|=(pw_stream_flags &lhs, pw_stream_flags rhs) noexcept
|
||||
{ lhs = lhs | rhs; return lhs; }
|
||||
|
|
@ -497,7 +505,7 @@ struct NodeProxy {
|
|||
|
||||
uint32_t mId{};
|
||||
|
||||
PwNodePtr mNode{};
|
||||
PwNodePtr mNode;
|
||||
spa_hook mListener{};
|
||||
|
||||
NodeProxy(uint32_t id, PwNodePtr node)
|
||||
|
|
@ -532,7 +540,7 @@ struct MetadataProxy {
|
|||
|
||||
uint32_t mId{};
|
||||
|
||||
PwMetadataPtr mMetadata{};
|
||||
PwMetadataPtr mMetadata;
|
||||
spa_hook mListener{};
|
||||
|
||||
MetadataProxy(uint32_t id, PwMetadataPtr mdata)
|
||||
|
|
@ -553,10 +561,10 @@ struct MetadataProxy {
|
|||
* to objects being added to or removed from the registry.
|
||||
*/
|
||||
struct EventManager {
|
||||
ThreadMainloop mLoop{};
|
||||
PwContextPtr mContext{};
|
||||
PwCorePtr mCore{};
|
||||
PwRegistryPtr mRegistry{};
|
||||
ThreadMainloop mLoop;
|
||||
PwContextPtr mContext;
|
||||
PwCorePtr mCore;
|
||||
PwRegistryPtr mRegistry;
|
||||
spa_hook mRegistryListener{};
|
||||
spa_hook mCoreListener{};
|
||||
|
||||
|
|
@ -585,8 +593,8 @@ struct EventManager {
|
|||
auto lock() const { return mLoop.lock(); }
|
||||
auto unlock() const { return mLoop.unlock(); }
|
||||
|
||||
[[nodiscard]] inline
|
||||
bool initIsDone(std::memory_order m=std::memory_order_seq_cst) const noexcept
|
||||
[[nodiscard]]
|
||||
auto initIsDone(std::memory_order m=std::memory_order_seq_cst) const noexcept -> bool
|
||||
{ return mInitDone.load(m); }
|
||||
|
||||
/**
|
||||
|
|
@ -759,7 +767,7 @@ void DeviceNode::Remove(uint32_t id)
|
|||
{
|
||||
if(n.mId != id)
|
||||
return false;
|
||||
TRACE("Removing device \"%s\"\n", n.mDevName.c_str());
|
||||
TRACE("Removing device \"{}\"", n.mDevName);
|
||||
if(gEventHandler.initIsDone(std::memory_order_relaxed))
|
||||
{
|
||||
const std::string msg{"Device removed: "+n.mName};
|
||||
|
|
@ -828,7 +836,7 @@ void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexce
|
|||
const uint podType{get_pod_type(value)};
|
||||
if(podType != SPA_TYPE_Int)
|
||||
{
|
||||
WARN(" Unhandled sample rate POD type: %u\n", podType);
|
||||
WARN(" Unhandled sample rate POD type: {}", podType);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -836,13 +844,13 @@ void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexce
|
|||
{
|
||||
if(nvals != 3)
|
||||
{
|
||||
WARN(" Unexpected SPA_CHOICE_Range count: %u\n", nvals);
|
||||
WARN(" Unexpected SPA_CHOICE_Range count: {}", nvals);
|
||||
return;
|
||||
}
|
||||
auto srates = get_pod_body<int32_t,3>(value);
|
||||
|
||||
/* [0] is the default, [1] is the min, and [2] is the max. */
|
||||
TRACE(" sample rate: %d (range: %d -> %d)\n", srates[0], srates[1], srates[2]);
|
||||
TRACE(" sample rate: {}, range: {}", srates[0], srates.subspan<1>());
|
||||
if(!mSampleRate || force_update)
|
||||
mSampleRate = static_cast<uint>(std::clamp<int>(srates[0], MinOutputRate,
|
||||
MaxOutputRate));
|
||||
|
|
@ -853,19 +861,13 @@ void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexce
|
|||
{
|
||||
if(nvals == 0)
|
||||
{
|
||||
WARN(" Unexpected SPA_CHOICE_Enum count: %u\n", nvals);
|
||||
WARN(" Unexpected SPA_CHOICE_Enum count: {}", nvals);
|
||||
return;
|
||||
}
|
||||
auto srates = get_pod_body<int32_t>(value, nvals);
|
||||
|
||||
/* [0] is the default, [1...size()-1] are available selections. */
|
||||
std::string others{(srates.size() > 1) ? std::to_string(srates[1]) : std::string{}};
|
||||
for(size_t i{2};i < srates.size();++i)
|
||||
{
|
||||
others += ", ";
|
||||
others += std::to_string(srates[i]);
|
||||
}
|
||||
TRACE(" sample rate: %d (%s)\n", srates[0], others.c_str());
|
||||
TRACE(" sample rate: {}, list: {}", srates[0], srates.subspan(1));
|
||||
/* Pick the first rate listed that's within the allowed range (default
|
||||
* rate if possible).
|
||||
*/
|
||||
|
|
@ -885,19 +887,19 @@ void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexce
|
|||
{
|
||||
if(nvals != 1)
|
||||
{
|
||||
WARN(" Unexpected SPA_CHOICE_None count: %u\n", nvals);
|
||||
WARN(" Unexpected SPA_CHOICE_None count: {}", nvals);
|
||||
return;
|
||||
}
|
||||
auto srates = get_pod_body<int32_t,1>(value);
|
||||
|
||||
TRACE(" sample rate: %d\n", srates[0]);
|
||||
TRACE(" sample rate: {}", srates[0]);
|
||||
if(!mSampleRate || force_update)
|
||||
mSampleRate = static_cast<uint>(std::clamp<int>(srates[0], MinOutputRate,
|
||||
MaxOutputRate));
|
||||
return;
|
||||
}
|
||||
|
||||
WARN(" Unhandled sample rate choice type: %u\n", choiceType);
|
||||
WARN(" Unhandled sample rate choice type: {}", choiceType);
|
||||
}
|
||||
|
||||
void DeviceNode::parsePositions(const spa_pod *value, bool force_update) noexcept
|
||||
|
|
@ -907,7 +909,7 @@ void DeviceNode::parsePositions(const spa_pod *value, bool force_update) noexcep
|
|||
|
||||
if(choiceType != SPA_CHOICE_None || choiceCount != 1)
|
||||
{
|
||||
ERR(" Unexpected positions choice: type=%u, count=%u\n", choiceType, choiceCount);
|
||||
ERR(" Unexpected positions choice: type={}, count={}", choiceType, choiceCount);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -938,7 +940,7 @@ void DeviceNode::parsePositions(const spa_pod *value, bool force_update) noexcep
|
|||
else
|
||||
mChannels = DevFmtMono;
|
||||
}
|
||||
TRACE(" %zu position%s for %s%s\n", chanmap.size(), (chanmap.size()==1)?"":"s",
|
||||
TRACE(" {} position{} for {}{}", chanmap.size(), (chanmap.size()==1)?"":"s",
|
||||
DevFmtChannelsString(mChannels), mIs51Rear?"(rear)":"");
|
||||
}
|
||||
|
||||
|
|
@ -950,7 +952,7 @@ void DeviceNode::parseChannelCount(const spa_pod *value, bool force_update) noex
|
|||
|
||||
if(choiceType != SPA_CHOICE_None || choiceCount != 1)
|
||||
{
|
||||
ERR(" Unexpected positions choice: type=%u, count=%u\n", choiceType, choiceCount);
|
||||
ERR(" Unexpected positions choice: type={}, count={}", choiceType, choiceCount);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -966,7 +968,7 @@ void DeviceNode::parseChannelCount(const spa_pod *value, bool force_update) noex
|
|||
else if(*chancount >= 1)
|
||||
mChannels = DevFmtMono;
|
||||
}
|
||||
TRACE(" %d channel%s for %s\n", *chancount, (*chancount==1)?"":"s",
|
||||
TRACE(" {} channel{} for {}", *chancount, (*chancount==1)?"":"s",
|
||||
DevFmtChannelsString(mChannels));
|
||||
}
|
||||
|
||||
|
|
@ -1005,7 +1007,7 @@ void NodeProxy::infoCallback(void*, const pw_node_info *info) noexcept
|
|||
ntype = NodeType::Duplex;
|
||||
else
|
||||
{
|
||||
TRACE("Dropping device node %u which became type \"%s\"\n", info->id, media_class);
|
||||
TRACE("Dropping device node {} which became type \"{}\"", info->id, media_class);
|
||||
DeviceNode::Remove(info->id);
|
||||
return;
|
||||
}
|
||||
|
|
@ -1024,20 +1026,23 @@ void NodeProxy::infoCallback(void*, const pw_node_info *info) noexcept
|
|||
serial_id = std::strtoull(serial_str, &serial_end, 0);
|
||||
if(*serial_end != '\0' || errno == ERANGE)
|
||||
{
|
||||
ERR("Unexpected object serial: %s\n", serial_str);
|
||||
ERR("Unexpected object serial: {}", serial_str);
|
||||
serial_id = info->id;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string name;
|
||||
if(nodeName && *nodeName) name = nodeName;
|
||||
else name = "PipeWire node #"+std::to_string(info->id);
|
||||
auto name = std::invoke([nodeName,info]() -> std::string
|
||||
{
|
||||
if(nodeName && *nodeName)
|
||||
return std::string{nodeName};
|
||||
return fmt::format("PipeWire node #{}", info->id);
|
||||
});
|
||||
|
||||
const char *form_factor{spa_dict_lookup(info->props, PW_KEY_DEVICE_FORM_FACTOR)};
|
||||
TRACE("Got %s device \"%s\"%s%s%s\n", AsString(ntype), devName ? devName : "(nil)",
|
||||
TRACE("Got {} device \"{}\"{}{}{}", AsString(ntype), devName ? devName : "(nil)",
|
||||
form_factor?" (":"", form_factor?form_factor:"", form_factor?")":"");
|
||||
TRACE(" \"%s\" = ID %" PRIu64 "\n", name.c_str(), serial_id);
|
||||
TRACE(" \"{}\" = ID {}", name, serial_id);
|
||||
|
||||
DeviceNode &node = DeviceNode::Add(info->id);
|
||||
node.mSerial = serial_id;
|
||||
|
|
@ -1087,7 +1092,7 @@ void NodeProxy::paramCallback(int, uint32_t id, uint32_t, uint32_t, const spa_po
|
|||
DeviceNode *node{DeviceNode::Find(mId)};
|
||||
if(!node) UNLIKELY return;
|
||||
|
||||
TRACE("Device ID %" PRIu64 " %s format%s:\n", node->mSerial,
|
||||
TRACE("Device ID {} {} format{}:", node->mSerial,
|
||||
(id == SPA_PARAM_EnumFormat) ? "available" : "current",
|
||||
(id == SPA_PARAM_EnumFormat) ? "s" : "");
|
||||
|
||||
|
|
@ -1122,14 +1127,14 @@ auto MetadataProxy::propertyCallback(void*, uint32_t id, const char *key, const
|
|||
|
||||
if(!type)
|
||||
{
|
||||
TRACE("Default %s device cleared\n", isCapture ? "capture" : "playback");
|
||||
TRACE("Default {} device cleared", isCapture ? "capture" : "playback");
|
||||
if(!isCapture) DefaultSinkDevice.clear();
|
||||
else DefaultSourceDevice.clear();
|
||||
return 0;
|
||||
}
|
||||
if("Spa:String:JSON"sv != type)
|
||||
{
|
||||
ERR("Unexpected %s property type: %s\n", key, type);
|
||||
ERR("Unexpected {} property type: {}", key, type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -1160,8 +1165,8 @@ auto MetadataProxy::propertyCallback(void*, uint32_t id, const char *key, const
|
|||
auto propValue = get_json_string(&std::get<1>(it));
|
||||
if(!propValue) break;
|
||||
|
||||
TRACE("Got default %s device \"%s\"\n", isCapture ? "capture" : "playback",
|
||||
propValue->c_str());
|
||||
TRACE("Got default {} device \"{}\"", isCapture ? "capture" : "playback",
|
||||
*propValue);
|
||||
if(!isCapture && DefaultSinkDevice != *propValue)
|
||||
{
|
||||
if(gEventHandler.mInitDone.load(std::memory_order_relaxed))
|
||||
|
|
@ -1203,28 +1208,28 @@ bool EventManager::init()
|
|||
mLoop = ThreadMainloop::Create("PWEventThread");
|
||||
if(!mLoop)
|
||||
{
|
||||
ERR("Failed to create PipeWire event thread loop (errno: %d)\n", errno);
|
||||
ERR("Failed to create PipeWire event thread loop (errno: {})", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
mContext = mLoop.newContext();
|
||||
if(!mContext)
|
||||
{
|
||||
ERR("Failed to create PipeWire event context (errno: %d)\n", errno);
|
||||
ERR("Failed to create PipeWire event context (errno: {})", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)};
|
||||
if(!mCore)
|
||||
{
|
||||
ERR("Failed to connect PipeWire event context (errno: %d)\n", errno);
|
||||
ERR("Failed to connect PipeWire event context (errno: {})", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
mRegistry = PwRegistryPtr{pw_core_get_registry(mCore.get(), PW_VERSION_REGISTRY, 0)};
|
||||
if(!mRegistry)
|
||||
{
|
||||
ERR("Failed to get PipeWire event registry (errno: %d)\n", errno);
|
||||
ERR("Failed to get PipeWire event registry (errno: {})", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1241,7 +1246,7 @@ bool EventManager::init()
|
|||
|
||||
if(int res{mLoop.start()})
|
||||
{
|
||||
ERR("Failed to start PipeWire event thread loop (res: %d)\n", res);
|
||||
ERR("Failed to start PipeWire event thread loop (res: {})", res);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1279,7 +1284,7 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t
|
|||
if(!isGood)
|
||||
{
|
||||
if(!al::contains(className, "/Video"sv) && !al::starts_with(className, "Stream/"sv))
|
||||
TRACE("Ignoring node class %s\n", media_class);
|
||||
TRACE("Ignoring node class {}", media_class);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1288,7 +1293,7 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t
|
|||
version, 0))};
|
||||
if(!node)
|
||||
{
|
||||
ERR("Failed to create node proxy object (errno: %d)\n", errno);
|
||||
ERR("Failed to create node proxy object (errno: {})", errno);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1311,13 +1316,13 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t
|
|||
|
||||
if("default"sv != data_class)
|
||||
{
|
||||
TRACE("Ignoring metadata \"%s\"\n", data_class);
|
||||
TRACE("Ignoring metadata \"{}\"", data_class);
|
||||
return;
|
||||
}
|
||||
|
||||
if(mDefaultMetadata)
|
||||
{
|
||||
ERR("Duplicate default metadata\n");
|
||||
ERR("Duplicate default metadata");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1325,7 +1330,7 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t
|
|||
type, version, 0))};
|
||||
if(!mdata)
|
||||
{
|
||||
ERR("Failed to create metadata proxy object (errno: %d)\n", errno);
|
||||
ERR("Failed to create metadata proxy object (errno: {})", errno);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1382,7 +1387,7 @@ spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e u
|
|||
case DevFmtFloat: info.format = SPA_AUDIO_FORMAT_F32; break;
|
||||
}
|
||||
|
||||
info.rate = device->Frequency;
|
||||
info.rate = device->mSampleRate;
|
||||
|
||||
al::span<const spa_audio_channel> map{};
|
||||
switch(device->FmtChans)
|
||||
|
|
@ -1432,7 +1437,7 @@ class PipeWirePlayback final : public BackendBase {
|
|||
PwStreamPtr mStream;
|
||||
spa_hook mStreamListener{};
|
||||
spa_io_rate_match *mRateMatch{};
|
||||
std::vector<float*> mChannelPtrs;
|
||||
std::vector<void*> mChannelPtrs;
|
||||
|
||||
static constexpr pw_stream_events CreateEvents()
|
||||
{
|
||||
|
|
@ -1448,7 +1453,7 @@ class PipeWirePlayback final : public BackendBase {
|
|||
}
|
||||
|
||||
public:
|
||||
PipeWirePlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit PipeWirePlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~PipeWirePlayback() final
|
||||
{
|
||||
/* Stop the mainloop so the stream can be properly destroyed. */
|
||||
|
|
@ -1492,7 +1497,7 @@ void PipeWirePlayback::outputCallback() noexcept
|
|||
uint length{mRateMatch ? mRateMatch->size : 0u};
|
||||
#endif
|
||||
/* If no length is specified, use the device's update size as a fallback. */
|
||||
if(!length) UNLIKELY length = mDevice->UpdateSize;
|
||||
if(!length) UNLIKELY length = mDevice->mUpdateSize;
|
||||
|
||||
/* For planar formats, each datas[] seems to contain one channel, so store
|
||||
* the pointers in an array. Limit the render length in case the available
|
||||
|
|
@ -1503,7 +1508,7 @@ void PipeWirePlayback::outputCallback() noexcept
|
|||
for(const auto &data : datas)
|
||||
{
|
||||
length = std::min(length, data.maxsize/uint{sizeof(float)});
|
||||
*chanptr_end = static_cast<float*>(data.data);
|
||||
*chanptr_end = data.data;
|
||||
++chanptr_end;
|
||||
|
||||
data.chunk->offset = 0;
|
||||
|
|
@ -1560,7 +1565,7 @@ void PipeWirePlayback::open(std::string_view name)
|
|||
auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name);
|
||||
if(match == devlist.cend())
|
||||
throw al::backend_exception{al::backend_error::NoDevice,
|
||||
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
|
||||
"Device name \"{}\" not found", name};
|
||||
|
||||
targetid = match->mSerial;
|
||||
devname = match->mName;
|
||||
|
|
@ -1568,15 +1573,15 @@ void PipeWirePlayback::open(std::string_view name)
|
|||
|
||||
if(!mLoop)
|
||||
{
|
||||
const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)};
|
||||
const std::string thread_name{"ALSoftP" + std::to_string(count)};
|
||||
const auto count = OpenCount.fetch_add(1u, std::memory_order_relaxed);
|
||||
const auto thread_name = fmt::format("ALSoftP{}", count);
|
||||
mLoop = ThreadMainloop::Create(thread_name.c_str());
|
||||
if(!mLoop)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to create PipeWire mainloop (errno: %d)", errno};
|
||||
"Failed to create PipeWire mainloop (errno: {})", errno};
|
||||
if(int res{mLoop.start()})
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start PipeWire mainloop (res: %d)", res};
|
||||
"Failed to start PipeWire mainloop (res: {})", res};
|
||||
}
|
||||
MainloopUniqueLock mlock{mLoop};
|
||||
if(!mContext)
|
||||
|
|
@ -1585,14 +1590,14 @@ void PipeWirePlayback::open(std::string_view name)
|
|||
mContext = mLoop.newContext(cprops);
|
||||
if(!mContext)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to create PipeWire event context (errno: %d)\n", errno};
|
||||
"Failed to create PipeWire event context (errno: {})\n", errno};
|
||||
}
|
||||
if(!mCore)
|
||||
{
|
||||
mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)};
|
||||
if(!mCore)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to connect PipeWire event context (errno: %d)\n", errno};
|
||||
"Failed to connect PipeWire event context (errno: {})\n", errno};
|
||||
}
|
||||
mlock.unlock();
|
||||
|
||||
|
|
@ -1600,9 +1605,9 @@ void PipeWirePlayback::open(std::string_view name)
|
|||
|
||||
mTargetId = targetid;
|
||||
if(!devname.empty())
|
||||
mDevice->DeviceName = std::move(devname);
|
||||
mDeviceName = std::move(devname);
|
||||
else
|
||||
mDevice->DeviceName = "PipeWire Output"sv;
|
||||
mDeviceName = "PipeWire Output"sv;
|
||||
}
|
||||
|
||||
bool PipeWirePlayback::reset()
|
||||
|
|
@ -1634,13 +1639,13 @@ bool PipeWirePlayback::reset()
|
|||
if(!mDevice->Flags.test(FrequencyRequest) && match->mSampleRate > 0)
|
||||
{
|
||||
/* Scale the update size if the sample rate changes. */
|
||||
const double scale{static_cast<double>(match->mSampleRate) / mDevice->Frequency};
|
||||
const double updatesize{std::round(mDevice->UpdateSize * scale)};
|
||||
const double buffersize{std::round(mDevice->BufferSize * scale)};
|
||||
const double scale{static_cast<double>(match->mSampleRate) / mDevice->mSampleRate};
|
||||
const double updatesize{std::round(mDevice->mUpdateSize * scale)};
|
||||
const double buffersize{std::round(mDevice->mBufferSize * scale)};
|
||||
|
||||
mDevice->Frequency = match->mSampleRate;
|
||||
mDevice->UpdateSize = static_cast<uint>(std::clamp(updatesize, 64.0, 8192.0));
|
||||
mDevice->BufferSize = static_cast<uint>(std::max(buffersize, 128.0));
|
||||
mDevice->mSampleRate = match->mSampleRate;
|
||||
mDevice->mUpdateSize = static_cast<uint>(std::clamp(updatesize, 64.0, 8192.0));
|
||||
mDevice->mBufferSize = static_cast<uint>(std::max(buffersize, 128.0));
|
||||
}
|
||||
if(!mDevice->Flags.test(ChannelsRequest) && match->mChannels != InvalidChannelConfig)
|
||||
mDevice->FmtChans = match->mChannels;
|
||||
|
|
@ -1652,12 +1657,10 @@ bool PipeWirePlayback::reset()
|
|||
/* Force planar 32-bit float output for playback. This is what PipeWire
|
||||
* handles internally, and it's easier for us too.
|
||||
*/
|
||||
spa_audio_info_raw info{make_spa_info(mDevice, is51rear, ForceF32Planar)};
|
||||
auto info = spa_audio_info_raw{make_spa_info(mDevice, is51rear, ForceF32Planar)};
|
||||
|
||||
static constexpr uint32_t pod_buffer_size{1024};
|
||||
PodDynamicBuilder b(pod_buffer_size);
|
||||
|
||||
const spa_pod *params{spa_format_audio_raw_build(b.get(), SPA_PARAM_EnumFormat, &info)};
|
||||
auto b = PodDynamicBuilder{};
|
||||
auto params = as_const_ptr(spa_format_audio_raw_build(b.get(), SPA_PARAM_EnumFormat, &info));
|
||||
if(!params)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to set PipeWire audio format parameters"};
|
||||
|
|
@ -1666,7 +1669,7 @@ bool PipeWirePlayback::reset()
|
|||
* be useful?
|
||||
*/
|
||||
auto&& binary = GetProcBinary();
|
||||
const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"};
|
||||
const char *appname{!binary.fname.empty() ? binary.fname.c_str() : "OpenAL Soft"};
|
||||
pw_properties *props{pw_properties_new(PW_KEY_NODE_NAME, appname,
|
||||
PW_KEY_NODE_DESCRIPTION, appname,
|
||||
PW_KEY_MEDIA_TYPE, "Audio",
|
||||
|
|
@ -1676,11 +1679,11 @@ bool PipeWirePlayback::reset()
|
|||
nullptr)};
|
||||
if(!props)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to create PipeWire stream properties (errno: %d)", errno};
|
||||
"Failed to create PipeWire stream properties (errno: {})", errno};
|
||||
|
||||
pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", mDevice->UpdateSize,
|
||||
mDevice->Frequency);
|
||||
pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency);
|
||||
pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", mDevice->mUpdateSize,
|
||||
mDevice->mSampleRate);
|
||||
pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->mSampleRate);
|
||||
#ifdef PW_KEY_TARGET_OBJECT
|
||||
pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId);
|
||||
#else
|
||||
|
|
@ -1692,17 +1695,17 @@ bool PipeWirePlayback::reset()
|
|||
mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Playback Stream", props)};
|
||||
if(!mStream)
|
||||
throw al::backend_exception{al::backend_error::NoDevice,
|
||||
"Failed to create PipeWire stream (errno: %d)", errno};
|
||||
"Failed to create PipeWire stream (errno: {})", errno};
|
||||
static constexpr pw_stream_events streamEvents{CreateEvents()};
|
||||
pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this);
|
||||
|
||||
pw_stream_flags flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE
|
||||
| PW_STREAM_FLAG_MAP_BUFFERS};
|
||||
if(GetConfigValueBool(mDevice->DeviceName, "pipewire", "rt-mix", false))
|
||||
if(GetConfigValueBool(mDevice->mDeviceName, "pipewire", "rt-mix", false))
|
||||
flags |= PW_STREAM_FLAG_RT_PROCESS;
|
||||
if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_OUTPUT, PwIdAny, flags, ¶ms, 1)})
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Error connecting PipeWire stream (res: %d)", res};
|
||||
"Error connecting PipeWire stream (res: {})", res};
|
||||
|
||||
/* Wait for the stream to become paused (ready to start streaming). */
|
||||
plock.wait([stream=mStream.get()]()
|
||||
|
|
@ -1711,12 +1714,12 @@ bool PipeWirePlayback::reset()
|
|||
pw_stream_state state{pw_stream_get_state(stream, &error)};
|
||||
if(state == PW_STREAM_STATE_ERROR)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Error connecting PipeWire stream: \"%s\"", error};
|
||||
"Error connecting PipeWire stream: \"{}\"", error};
|
||||
return state == PW_STREAM_STATE_PAUSED;
|
||||
});
|
||||
|
||||
/* TODO: Update mDevice->UpdateSize with the stream's quantum, and
|
||||
* mDevice->BufferSize with the total known buffering delay from the head
|
||||
/* TODO: Update mDevice->mUpdateSize with the stream's quantum, and
|
||||
* mDevice->mBufferSize with the total known buffering delay from the head
|
||||
* of this playback stream to the tail of the device output.
|
||||
*
|
||||
* This info is apparently not available until after the stream starts.
|
||||
|
|
@ -1735,7 +1738,7 @@ void PipeWirePlayback::start()
|
|||
MainloopUniqueLock plock{mLoop};
|
||||
if(int res{pw_stream_set_active(mStream.get(), true)})
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start PipeWire stream (res: %d)", res};
|
||||
"Failed to start PipeWire stream (res: {})", res};
|
||||
|
||||
/* Wait for the stream to start playing (would be nice to not, but we need
|
||||
* the actual update size which is only available after starting).
|
||||
|
|
@ -1746,7 +1749,7 @@ void PipeWirePlayback::start()
|
|||
pw_stream_state state{pw_stream_get_state(stream, &error)};
|
||||
if(state == PW_STREAM_STATE_ERROR)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"PipeWire stream error: %s", error ? error : "(unknown)"};
|
||||
"PipeWire stream error: {}", error ? error : "(unknown)"};
|
||||
return state == PW_STREAM_STATE_STREAMING;
|
||||
});
|
||||
|
||||
|
|
@ -1761,7 +1764,7 @@ void PipeWirePlayback::start()
|
|||
pw_time ptime{};
|
||||
if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))})
|
||||
{
|
||||
ERR("Failed to get PipeWire stream time (res: %d)\n", res);
|
||||
ERR("Failed to get PipeWire stream time (res: {})", res);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1776,11 +1779,11 @@ void PipeWirePlayback::start()
|
|||
const uint totalbuffers{ptime.avail_buffers + ptime.queued_buffers};
|
||||
|
||||
/* Ensure the delay is in sample frames. */
|
||||
const uint64_t delay{static_cast<uint64_t>(ptime.delay) * mDevice->Frequency *
|
||||
const uint64_t delay{static_cast<uint64_t>(ptime.delay) * mDevice->mSampleRate *
|
||||
ptime.rate.num / ptime.rate.denom};
|
||||
|
||||
mDevice->UpdateSize = updatesize;
|
||||
mDevice->BufferSize = static_cast<uint>(ptime.buffered + delay +
|
||||
mDevice->mUpdateSize = updatesize;
|
||||
mDevice->mBufferSize = static_cast<uint>(ptime.buffered + delay +
|
||||
uint64_t{totalbuffers}*updatesize);
|
||||
break;
|
||||
}
|
||||
|
|
@ -1791,17 +1794,17 @@ void PipeWirePlayback::start()
|
|||
if(ptime.rate.denom > 0 && updatesize > 0)
|
||||
{
|
||||
/* Ensure the delay is in sample frames. */
|
||||
const uint64_t delay{static_cast<uint64_t>(ptime.delay) * mDevice->Frequency *
|
||||
const uint64_t delay{static_cast<uint64_t>(ptime.delay) * mDevice->mSampleRate *
|
||||
ptime.rate.num / ptime.rate.denom};
|
||||
|
||||
mDevice->UpdateSize = updatesize;
|
||||
mDevice->BufferSize = static_cast<uint>(delay + updatesize);
|
||||
mDevice->mUpdateSize = updatesize;
|
||||
mDevice->mBufferSize = static_cast<uint>(delay + updatesize);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
if(!--wait_count)
|
||||
{
|
||||
ERR("Timeout getting PipeWire stream buffering info\n");
|
||||
ERR("Timeout getting PipeWire stream buffering info");
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1815,7 +1818,7 @@ void PipeWirePlayback::stop()
|
|||
{
|
||||
MainloopUniqueLock plock{mLoop};
|
||||
if(int res{pw_stream_set_active(mStream.get(), false)})
|
||||
ERR("Failed to stop PipeWire stream (res: %d)\n", res);
|
||||
ERR("Failed to stop PipeWire stream (res: {})", res);
|
||||
|
||||
/* Wait for the stream to stop playing. */
|
||||
plock.wait([stream=mStream.get()]()
|
||||
|
|
@ -1836,7 +1839,7 @@ ClockLatency PipeWirePlayback::getClockLatency()
|
|||
{
|
||||
MainloopLockGuard looplock{mLoop};
|
||||
if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))})
|
||||
ERR("Failed to get PipeWire stream time (res: %d)\n", res);
|
||||
ERR("Failed to get PipeWire stream time (res: {})", res);
|
||||
}
|
||||
|
||||
/* Now get the mixer time and the CLOCK_MONOTONIC time atomically (i.e. the
|
||||
|
|
@ -1864,7 +1867,7 @@ ClockLatency PipeWirePlayback::getClockLatency()
|
|||
*/
|
||||
ptime.now = monoclock.count();
|
||||
curtic = mixtime;
|
||||
delay = nanoseconds{seconds{mDevice->BufferSize}} / mDevice->Frequency;
|
||||
delay = nanoseconds{seconds{mDevice->mBufferSize}} / mDevice->mSampleRate;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1924,7 +1927,7 @@ class PipeWireCapture final : public BackendBase {
|
|||
PwStreamPtr mStream;
|
||||
spa_hook mStreamListener{};
|
||||
|
||||
RingBufferPtr mRing{};
|
||||
RingBufferPtr mRing;
|
||||
|
||||
static constexpr pw_stream_events CreateEvents()
|
||||
{
|
||||
|
|
@ -1938,7 +1941,7 @@ class PipeWireCapture final : public BackendBase {
|
|||
}
|
||||
|
||||
public:
|
||||
PipeWireCapture(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit PipeWireCapture(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~PipeWireCapture() final { if(mLoop) mLoop.stop(); }
|
||||
};
|
||||
|
||||
|
|
@ -2025,7 +2028,7 @@ void PipeWireCapture::open(std::string_view name)
|
|||
}
|
||||
if(match == devlist.cend())
|
||||
throw al::backend_exception{al::backend_error::NoDevice,
|
||||
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
|
||||
"Device name \"{}\" not found", name};
|
||||
|
||||
targetid = match->mSerial;
|
||||
if(match->mType != NodeType::Sink) devname = match->mName;
|
||||
|
|
@ -2034,15 +2037,15 @@ void PipeWireCapture::open(std::string_view name)
|
|||
|
||||
if(!mLoop)
|
||||
{
|
||||
const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)};
|
||||
const std::string thread_name{"ALSoftC" + std::to_string(count)};
|
||||
const auto count = OpenCount.fetch_add(1u, std::memory_order_relaxed);
|
||||
const auto thread_name = fmt::format("ALSoftC{}", count);
|
||||
mLoop = ThreadMainloop::Create(thread_name.c_str());
|
||||
if(!mLoop)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to create PipeWire mainloop (errno: %d)", errno};
|
||||
"Failed to create PipeWire mainloop (errno: {})", errno};
|
||||
if(int res{mLoop.start()})
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start PipeWire mainloop (res: %d)", res};
|
||||
"Failed to start PipeWire mainloop (res: {})", res};
|
||||
}
|
||||
MainloopUniqueLock mlock{mLoop};
|
||||
if(!mContext)
|
||||
|
|
@ -2051,14 +2054,14 @@ void PipeWireCapture::open(std::string_view name)
|
|||
mContext = mLoop.newContext(cprops);
|
||||
if(!mContext)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to create PipeWire event context (errno: %d)\n", errno};
|
||||
"Failed to create PipeWire event context (errno: {})\n", errno};
|
||||
}
|
||||
if(!mCore)
|
||||
{
|
||||
mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)};
|
||||
if(!mCore)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to connect PipeWire event context (errno: %d)\n", errno};
|
||||
"Failed to connect PipeWire event context (errno: {})\n", errno};
|
||||
}
|
||||
mlock.unlock();
|
||||
|
||||
|
|
@ -2066,9 +2069,9 @@ void PipeWireCapture::open(std::string_view name)
|
|||
|
||||
mTargetId = targetid;
|
||||
if(!devname.empty())
|
||||
mDevice->DeviceName = std::move(devname);
|
||||
mDeviceName = std::move(devname);
|
||||
else
|
||||
mDevice->DeviceName = "PipeWire Input"sv;
|
||||
mDeviceName = "PipeWire Input"sv;
|
||||
|
||||
|
||||
bool is51rear{false};
|
||||
|
|
@ -2083,19 +2086,16 @@ void PipeWireCapture::open(std::string_view name)
|
|||
if(match != devlist.cend())
|
||||
is51rear = match->mIs51Rear;
|
||||
}
|
||||
spa_audio_info_raw info{make_spa_info(mDevice, is51rear, UseDevType)};
|
||||
auto info = spa_audio_info_raw{make_spa_info(mDevice, is51rear, UseDevType)};
|
||||
|
||||
static constexpr uint32_t pod_buffer_size{1024};
|
||||
PodDynamicBuilder b(pod_buffer_size);
|
||||
|
||||
std::array params{static_cast<const spa_pod*>(spa_format_audio_raw_build(b.get(),
|
||||
SPA_PARAM_EnumFormat, &info))};
|
||||
if(!params[0])
|
||||
auto b = PodDynamicBuilder{};
|
||||
auto params = as_const_ptr(spa_format_audio_raw_build(b.get(), SPA_PARAM_EnumFormat, &info));
|
||||
if(!params)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to set PipeWire audio format parameters"};
|
||||
|
||||
auto&& binary = GetProcBinary();
|
||||
const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"};
|
||||
const char *appname{!binary.fname.empty() ? binary.fname.c_str() : "OpenAL Soft"};
|
||||
pw_properties *props{pw_properties_new(
|
||||
PW_KEY_NODE_NAME, appname,
|
||||
PW_KEY_NODE_DESCRIPTION, appname,
|
||||
|
|
@ -2106,15 +2106,15 @@ void PipeWireCapture::open(std::string_view name)
|
|||
nullptr)};
|
||||
if(!props)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to create PipeWire stream properties (errno: %d)", errno};
|
||||
"Failed to create PipeWire stream properties (errno: {})", errno};
|
||||
|
||||
/* We don't actually care what the latency/update size is, as long as it's
|
||||
* reasonable. Unfortunately, when unspecified PipeWire seems to default to
|
||||
* around 40ms, which isn't great. So request 20ms instead.
|
||||
*/
|
||||
pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", (mDevice->Frequency+25) / 50,
|
||||
mDevice->Frequency);
|
||||
pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency);
|
||||
pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", (mDevice->mSampleRate+25) / 50,
|
||||
mDevice->mSampleRate);
|
||||
pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->mSampleRate);
|
||||
#ifdef PW_KEY_TARGET_OBJECT
|
||||
pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId);
|
||||
#else
|
||||
|
|
@ -2125,15 +2125,15 @@ void PipeWireCapture::open(std::string_view name)
|
|||
mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Capture Stream", props)};
|
||||
if(!mStream)
|
||||
throw al::backend_exception{al::backend_error::NoDevice,
|
||||
"Failed to create PipeWire stream (errno: %d)", errno};
|
||||
"Failed to create PipeWire stream (errno: {})", errno};
|
||||
static constexpr pw_stream_events streamEvents{CreateEvents()};
|
||||
pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this);
|
||||
|
||||
constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE
|
||||
| PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS};
|
||||
if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, PwIdAny, Flags, params.data(), 1)})
|
||||
if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, PwIdAny, Flags, ¶ms, 1)})
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Error connecting PipeWire stream (res: %d)", res};
|
||||
"Error connecting PipeWire stream (res: {})", res};
|
||||
|
||||
/* Wait for the stream to become paused (ready to start streaming). */
|
||||
plock.wait([stream=mStream.get()]()
|
||||
|
|
@ -2142,7 +2142,7 @@ void PipeWireCapture::open(std::string_view name)
|
|||
pw_stream_state state{pw_stream_get_state(stream, &error)};
|
||||
if(state == PW_STREAM_STATE_ERROR)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Error connecting PipeWire stream: \"%s\"", error};
|
||||
"Error connecting PipeWire stream: \"{}\"", error};
|
||||
return state == PW_STREAM_STATE_PAUSED;
|
||||
});
|
||||
plock.unlock();
|
||||
|
|
@ -2150,7 +2150,7 @@ void PipeWireCapture::open(std::string_view name)
|
|||
setDefaultWFXChannelOrder();
|
||||
|
||||
/* Ensure at least a 100ms capture buffer. */
|
||||
mRing = RingBuffer::Create(std::max(mDevice->Frequency/10u, mDevice->BufferSize),
|
||||
mRing = RingBuffer::Create(std::max(mDevice->mSampleRate/10u, mDevice->mBufferSize),
|
||||
mDevice->frameSizeFromFmt(), false);
|
||||
}
|
||||
|
||||
|
|
@ -2160,7 +2160,7 @@ void PipeWireCapture::start()
|
|||
MainloopUniqueLock plock{mLoop};
|
||||
if(int res{pw_stream_set_active(mStream.get(), true)})
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start PipeWire stream (res: %d)", res};
|
||||
"Failed to start PipeWire stream (res: {})", res};
|
||||
|
||||
plock.wait([stream=mStream.get()]()
|
||||
{
|
||||
|
|
@ -2168,7 +2168,7 @@ void PipeWireCapture::start()
|
|||
pw_stream_state state{pw_stream_get_state(stream, &error)};
|
||||
if(state == PW_STREAM_STATE_ERROR)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"PipeWire stream error: %s", error ? error : "(unknown)"};
|
||||
"PipeWire stream error: {}", error ? error : "(unknown)"};
|
||||
return state == PW_STREAM_STATE_STREAMING;
|
||||
});
|
||||
}
|
||||
|
|
@ -2177,7 +2177,7 @@ void PipeWireCapture::stop()
|
|||
{
|
||||
MainloopUniqueLock plock{mLoop};
|
||||
if(int res{pw_stream_set_active(mStream.get(), false)})
|
||||
ERR("Failed to stop PipeWire stream (res: %d)\n", res);
|
||||
ERR("Failed to stop PipeWire stream (res: {})", res);
|
||||
|
||||
plock.wait([stream=mStream.get()]()
|
||||
{ return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; });
|
||||
|
|
@ -2200,11 +2200,11 @@ bool PipeWireBackendFactory::init()
|
|||
const char *version{pw_get_library_version()};
|
||||
if(!check_version(version))
|
||||
{
|
||||
WARN("PipeWire version \"%s\" too old (%s or newer required)\n", version,
|
||||
WARN("PipeWire version \"{}\" too old ({} or newer required)", version,
|
||||
pw_get_headers_version());
|
||||
return false;
|
||||
}
|
||||
TRACE("Found PipeWire version \"%s\" (%s or newer)\n", version, pw_get_headers_version());
|
||||
TRACE("Found PipeWire version \"{}\" ({} or newer)", version, pw_get_headers_version());
|
||||
|
||||
pw_init(nullptr, nullptr);
|
||||
if(!gEventHandler.init())
|
||||
|
|
@ -2217,7 +2217,7 @@ bool PipeWireBackendFactory::init()
|
|||
/* TODO: Temporary warning, until PipeWire gets a proper way to report
|
||||
* audio support.
|
||||
*/
|
||||
WARN("No audio support detected in PipeWire. See the PipeWire options in alsoftrc.sample if this is wrong.\n");
|
||||
WARN("No audio support detected in PipeWire. See the PipeWire options in alsoftrc.sample if this is wrong.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -20,27 +20,25 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "portaudio.h"
|
||||
#include "portaudio.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#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> /* NOLINT(*-duplicate-include) Not the same header. */
|
||||
#include <portaudio.h>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
#ifdef HAVE_DYNLOAD
|
||||
#if HAVE_DYNLOAD
|
||||
void *pa_handle;
|
||||
#define MAKE_FUNC(x) decltype(x) * p##x
|
||||
MAKE_FUNC(Pa_Initialize);
|
||||
|
|
@ -75,8 +73,8 @@ MAKE_FUNC(Pa_GetStreamInfo);
|
|||
|
||||
struct DeviceEntry {
|
||||
std::string mName;
|
||||
bool mIsPlayback{};
|
||||
bool mIsCapture{};
|
||||
uint mPlaybackChannels{};
|
||||
uint mCaptureChannels{};
|
||||
};
|
||||
std::vector<DeviceEntry> DeviceNames;
|
||||
|
||||
|
|
@ -85,7 +83,7 @@ void EnumerateDevices()
|
|||
const auto devcount = Pa_GetDeviceCount();
|
||||
if(devcount < 0)
|
||||
{
|
||||
ERR("Error getting device count: %s\n", Pa_GetErrorText(devcount));
|
||||
ERR("Error getting device count: {}", Pa_GetErrorText(devcount));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -95,44 +93,42 @@ void EnumerateDevices()
|
|||
{
|
||||
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(),
|
||||
entry.mPlaybackChannels = static_cast<uint>(std::max(info->maxOutputChannels, 0));
|
||||
entry.mCaptureChannels = static_cast<uint>(std::max(info->maxInputChannels, 0));
|
||||
TRACE("Device {} \"{}\": {} playback, {} capture channels", idx, entry.mName,
|
||||
info->maxOutputChannels, info->maxInputChannels);
|
||||
}
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
|
||||
struct StreamParamsExt : public PaStreamParameters { uint updateSize; };
|
||||
|
||||
struct PortPlayback final : public BackendBase {
|
||||
PortPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit PortPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~PortPlayback() override;
|
||||
|
||||
int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
|
||||
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
|
||||
|
||||
void createStream(PaDeviceIndex deviceid);
|
||||
|
||||
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};
|
||||
StreamParamsExt mParams{};
|
||||
PaDeviceIndex mDeviceIdx{-1};
|
||||
};
|
||||
|
||||
PortPlayback::~PortPlayback()
|
||||
{
|
||||
PaError err{mStream ? Pa_CloseStream(mStream) : paNoError};
|
||||
if(err != paNoError)
|
||||
ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
|
||||
ERR("Error closing stream: {}", Pa_GetErrorText(err));
|
||||
mStream = nullptr;
|
||||
}
|
||||
|
||||
|
|
@ -146,12 +142,62 @@ int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long f
|
|||
}
|
||||
|
||||
|
||||
void PortPlayback::createStream(PaDeviceIndex deviceid)
|
||||
{
|
||||
auto &devinfo = DeviceNames.at(static_cast<uint>(deviceid));
|
||||
|
||||
auto params = StreamParamsExt{};
|
||||
params.device = deviceid;
|
||||
params.suggestedLatency = mDevice->mBufferSize / static_cast<double>(mDevice->mSampleRate);
|
||||
params.hostApiSpecificStreamInfo = nullptr;
|
||||
params.channelCount = static_cast<int>(std::min(devinfo.mPlaybackChannels,
|
||||
mDevice->channelsFromFmt()));
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte: params.sampleFormat = paInt8; break;
|
||||
case DevFmtUByte: params.sampleFormat = paUInt8; break;
|
||||
case DevFmtUShort: [[fallthrough]];
|
||||
case DevFmtShort: params.sampleFormat = paInt16; break;
|
||||
case DevFmtUInt: [[fallthrough]];
|
||||
case DevFmtInt: params.sampleFormat = paInt32; break;
|
||||
case DevFmtFloat: params.sampleFormat = paFloat32; break;
|
||||
}
|
||||
params.updateSize = mDevice->mUpdateSize;
|
||||
|
||||
auto srate = uint{mDevice->mSampleRate};
|
||||
|
||||
static constexpr auto writeCallback = [](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);
|
||||
};
|
||||
while(PaError err{Pa_OpenStream(&mStream, nullptr, ¶ms, srate, params.updateSize, paNoFlag,
|
||||
writeCallback, this)})
|
||||
{
|
||||
if(params.updateSize != DefaultUpdateSize)
|
||||
params.updateSize = DefaultUpdateSize;
|
||||
else if(srate != 48000u)
|
||||
srate = (srate != 44100u) ? 44100u : 48000u;
|
||||
else if(params.sampleFormat != paInt16)
|
||||
params.sampleFormat = paInt16;
|
||||
else if(params.channelCount != 2)
|
||||
params.channelCount = 2;
|
||||
else
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: {}",
|
||||
Pa_GetErrorText(err)};
|
||||
}
|
||||
|
||||
mParams = params;
|
||||
}
|
||||
|
||||
void PortPlayback::open(std::string_view name)
|
||||
{
|
||||
if(DeviceNames.empty())
|
||||
EnumerateDevices();
|
||||
|
||||
int deviceid{-1};
|
||||
PaDeviceIndex deviceid{-1};
|
||||
if(name.empty())
|
||||
{
|
||||
if(auto devidopt = ConfigValueInt({}, "port", "device"))
|
||||
|
|
@ -163,101 +209,65 @@ void PortPlayback::open(std::string_view name)
|
|||
else
|
||||
{
|
||||
auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(),
|
||||
[name](const DeviceEntry &entry) { return entry.mIsPlayback && name == entry.mName; });
|
||||
[name](const DeviceEntry &entry)
|
||||
{ return entry.mPlaybackChannels > 0 && 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()};
|
||||
"Device name \"{}\" not found", name};
|
||||
deviceid = static_cast<int>(std::distance(DeviceNames.cbegin(), iter));
|
||||
}
|
||||
|
||||
PaStreamParameters params{};
|
||||
params.device = deviceid;
|
||||
params.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency);
|
||||
params.hostApiSpecificStreamInfo = nullptr;
|
||||
createStream(deviceid);
|
||||
mDeviceIdx = deviceid;
|
||||
|
||||
mChannelConfig = mDevice->FmtChans;
|
||||
mAmbiOrder = mDevice->mAmbiOrder;
|
||||
params.channelCount = static_cast<int>(mDevice->channelsFromFmt());
|
||||
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
params.sampleFormat = paInt8;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
params.sampleFormat = paUInt8;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
/* fall-through */
|
||||
case DevFmtShort:
|
||||
params.sampleFormat = paInt16;
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
/* fall-through */
|
||||
case DevFmtInt:
|
||||
params.sampleFormat = paInt32;
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
params.sampleFormat = paFloat32;
|
||||
break;
|
||||
}
|
||||
|
||||
static constexpr auto writeCallback = [](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);
|
||||
};
|
||||
PaStream *stream{};
|
||||
while(PaError err{Pa_OpenStream(&stream, nullptr, ¶ms, 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);
|
||||
mStream = stream;
|
||||
mParams = params;
|
||||
mUpdateSize = mDevice->UpdateSize;
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
bool PortPlayback::reset()
|
||||
{
|
||||
if(mStream)
|
||||
{
|
||||
auto err = Pa_CloseStream(mStream);
|
||||
if(err != paNoError)
|
||||
ERR("Error closing stream: {}", Pa_GetErrorText(err));
|
||||
mStream = nullptr;
|
||||
}
|
||||
|
||||
createStream(mDeviceIdx);
|
||||
|
||||
switch(mParams.sampleFormat)
|
||||
{
|
||||
case paFloat32: mDevice->FmtType = DevFmtFloat; break;
|
||||
case paInt32: mDevice->FmtType = DevFmtInt; break;
|
||||
case paInt16: mDevice->FmtType = DevFmtShort; break;
|
||||
case paInt8: mDevice->FmtType = DevFmtByte; break;
|
||||
case paUInt8: mDevice->FmtType = DevFmtUByte; break;
|
||||
default:
|
||||
ERR("Unexpected PortAudio sample format: {}", mParams.sampleFormat);
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Invalid sample format: {}",
|
||||
mParams.sampleFormat};
|
||||
}
|
||||
|
||||
if(mParams.channelCount != static_cast<int>(mDevice->channelsFromFmt()))
|
||||
{
|
||||
if(mParams.channelCount >= 2)
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
else if(mParams.channelCount == 1)
|
||||
mDevice->FmtChans = DevFmtMono;
|
||||
mDevice->mAmbiOrder = 0;
|
||||
}
|
||||
|
||||
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;
|
||||
mDevice->mSampleRate = static_cast<uint>(std::lround(streamInfo->sampleRate));
|
||||
mDevice->mUpdateSize = mParams.updateSize;
|
||||
mDevice->mBufferSize = mDevice->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,
|
||||
TRACE("Reported stream latency: {:f} sec ({:f} samples)", 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;
|
||||
else if(mParams.sampleFormat == paUInt8)
|
||||
mDevice->FmtType = DevFmtUByte;
|
||||
else if(mParams.sampleFormat == paInt16)
|
||||
mDevice->FmtType = DevFmtShort;
|
||||
else if(mParams.sampleFormat == paInt32)
|
||||
mDevice->FmtType = DevFmtInt;
|
||||
else if(mParams.sampleFormat == paFloat32)
|
||||
mDevice->FmtType = DevFmtFloat;
|
||||
else
|
||||
{
|
||||
ERR("Unexpected sample format: 0x%lx\n", mParams.sampleFormat);
|
||||
return false;
|
||||
mDevice->mBufferSize = static_cast<uint>(std::clamp(sampleLatency,
|
||||
double(mDevice->mBufferSize), double{std::numeric_limits<int>::max()}));
|
||||
}
|
||||
|
||||
setDefaultChannelOrder();
|
||||
|
|
@ -268,19 +278,19 @@ bool PortPlayback::reset()
|
|||
void PortPlayback::start()
|
||||
{
|
||||
if(const PaError err{Pa_StartStream(mStream)}; err != paNoError)
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: %s",
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: {}",
|
||||
Pa_GetErrorText(err)};
|
||||
}
|
||||
|
||||
void PortPlayback::stop()
|
||||
{
|
||||
if(PaError err{Pa_StopStream(mStream)}; err != paNoError)
|
||||
ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
|
||||
ERR("Error stopping stream: {}", Pa_GetErrorText(err));
|
||||
}
|
||||
|
||||
|
||||
struct PortCapture final : public BackendBase {
|
||||
PortCapture(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit PortCapture(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~PortCapture() override;
|
||||
|
||||
int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
|
||||
|
|
@ -302,7 +312,7 @@ PortCapture::~PortCapture()
|
|||
{
|
||||
PaError err{mStream ? Pa_CloseStream(mStream) : paNoError};
|
||||
if(err != paNoError)
|
||||
ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
|
||||
ERR("Error closing stream: {}", Pa_GetErrorText(err));
|
||||
mStream = nullptr;
|
||||
}
|
||||
|
||||
|
|
@ -332,14 +342,15 @@ void PortCapture::open(std::string_view name)
|
|||
else
|
||||
{
|
||||
auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(),
|
||||
[name](const DeviceEntry &entry) { return entry.mIsCapture && name == entry.mName; });
|
||||
[name](const DeviceEntry &entry)
|
||||
{ return entry.mCaptureChannels > 0 && 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()};
|
||||
"Device name \"{}\" not found", name};
|
||||
deviceid = static_cast<int>(std::distance(DeviceNames.cbegin(), iter));
|
||||
}
|
||||
|
||||
const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)};
|
||||
const uint samples{std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u)};
|
||||
const uint frame_size{mDevice->frameSizeFromFmt()};
|
||||
|
||||
mRing = RingBuffer::Create(samples, frame_size, false);
|
||||
|
|
@ -367,7 +378,7 @@ void PortCapture::open(std::string_view name)
|
|||
break;
|
||||
case DevFmtUInt:
|
||||
case DevFmtUShort:
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported",
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "{} samples not supported",
|
||||
DevFmtTypeString(mDevice->FmtType)};
|
||||
}
|
||||
mParams.channelCount = static_cast<int>(mDevice->channelsFromFmt());
|
||||
|
|
@ -379,13 +390,13 @@ void PortCapture::open(std::string_view name)
|
|||
return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer,
|
||||
framesPerBuffer, timeInfo, statusFlags);
|
||||
};
|
||||
PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency,
|
||||
PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->mSampleRate,
|
||||
paFramesPerBufferUnspecified, paNoFlag, readCallback, this)};
|
||||
if(err != paNoError)
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: {}",
|
||||
Pa_GetErrorText(err)};
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -393,13 +404,13 @@ void PortCapture::start()
|
|||
{
|
||||
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)};
|
||||
"Failed to start recording: {}", Pa_GetErrorText(err)};
|
||||
}
|
||||
|
||||
void PortCapture::stop()
|
||||
{
|
||||
if(PaError err{Pa_StopStream(mStream)}; err != paNoError)
|
||||
ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
|
||||
ERR("Error stopping stream: {}", Pa_GetErrorText(err));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -414,7 +425,7 @@ void PortCapture::captureSamples(std::byte *buffer, uint samples)
|
|||
|
||||
bool PortBackendFactory::init()
|
||||
{
|
||||
#ifdef HAVE_DYNLOAD
|
||||
#if HAVE_DYNLOAD
|
||||
if(!pa_handle)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
|
|
@ -457,7 +468,7 @@ bool PortBackendFactory::init()
|
|||
const PaError err{Pa_Initialize()};
|
||||
if(err != paNoError)
|
||||
{
|
||||
ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
|
||||
ERR("Pa_Initialize() returned an error: {}", Pa_GetErrorText(err));
|
||||
CloseLib(pa_handle);
|
||||
pa_handle = nullptr;
|
||||
return false;
|
||||
|
|
@ -467,7 +478,7 @@ bool PortBackendFactory::init()
|
|||
const PaError err{Pa_Initialize()};
|
||||
if(err != paNoError)
|
||||
{
|
||||
ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
|
||||
ERR("Pa_Initialize() returned an error: {}", Pa_GetErrorText(err));
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
|
@ -493,7 +504,7 @@ auto PortBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
|
|||
|
||||
for(size_t i{0};i < DeviceNames.size();++i)
|
||||
{
|
||||
if(DeviceNames[i].mIsPlayback)
|
||||
if(DeviceNames[i].mPlaybackChannels > 0)
|
||||
{
|
||||
if(defaultid >= 0 && static_cast<uint>(defaultid) == i)
|
||||
devices.emplace(devices.cbegin(), DeviceNames[i].mName);
|
||||
|
|
@ -511,7 +522,7 @@ auto PortBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
|
|||
|
||||
for(size_t i{0};i < DeviceNames.size();++i)
|
||||
{
|
||||
if(DeviceNames[i].mIsCapture)
|
||||
if(DeviceNames[i].mCaptureChannels > 0)
|
||||
{
|
||||
if(defaultid >= 0 && static_cast<uint>(defaultid) == i)
|
||||
devices.emplace(devices.cbegin(), DeviceNames[i].mName);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef BACKENDS_PORTAUDIO_H
|
||||
#define BACKENDS_PORTAUDIO_H
|
||||
#ifndef BACKENDS_PORTAUDIO_HPP
|
||||
#define BACKENDS_PORTAUDIO_HPP
|
||||
|
||||
#include "base.h"
|
||||
|
||||
|
|
@ -16,4 +16,4 @@ public:
|
|||
static auto getFactory() -> BackendFactory&;
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_PORTAUDIO_H */
|
||||
#endif /* BACKENDS_PORTAUDIO_HPP */
|
||||
|
|
@ -32,7 +32,6 @@
|
|||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
|
|
@ -43,13 +42,14 @@
|
|||
#include <vector>
|
||||
|
||||
#include "alc/alconfig.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "alstring.h"
|
||||
#include "base.h"
|
||||
#include "core/devformat.h"
|
||||
#include "core/device.h"
|
||||
#include "core/logging.h"
|
||||
#include "dynload.h"
|
||||
#include "fmt/core.h"
|
||||
#include "opthelpers.h"
|
||||
#include "strutils.h"
|
||||
|
||||
|
|
@ -58,9 +58,10 @@
|
|||
|
||||
namespace {
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
using uint = unsigned int;
|
||||
|
||||
#ifdef HAVE_DYNLOAD
|
||||
#if HAVE_DYNLOAD
|
||||
#define PULSE_FUNCS(MAGIC) \
|
||||
MAGIC(pa_context_new); \
|
||||
MAGIC(pa_context_unref); \
|
||||
|
|
@ -72,8 +73,10 @@ using uint = unsigned int;
|
|||
MAGIC(pa_context_errno); \
|
||||
MAGIC(pa_context_connect); \
|
||||
MAGIC(pa_context_get_server_info); \
|
||||
MAGIC(pa_context_get_sink_info_by_index); \
|
||||
MAGIC(pa_context_get_sink_info_by_name); \
|
||||
MAGIC(pa_context_get_sink_info_list); \
|
||||
MAGIC(pa_context_get_source_info_by_index); \
|
||||
MAGIC(pa_context_get_source_info_by_name); \
|
||||
MAGIC(pa_context_get_source_info_list); \
|
||||
MAGIC(pa_stream_new); \
|
||||
|
|
@ -145,8 +148,10 @@ PULSE_FUNCS(MAKE_FUNC)
|
|||
#define pa_context_errno ppa_context_errno
|
||||
#define pa_context_connect ppa_context_connect
|
||||
#define pa_context_get_server_info ppa_context_get_server_info
|
||||
#define pa_context_get_sink_info_by_index ppa_context_get_sink_info_by_index
|
||||
#define pa_context_get_sink_info_by_name ppa_context_get_sink_info_by_name
|
||||
#define pa_context_get_sink_info_list ppa_context_get_sink_info_list
|
||||
#define pa_context_get_source_info_by_index ppa_context_get_source_info_by_index
|
||||
#define pa_context_get_source_info_by_name ppa_context_get_source_info_by_name
|
||||
#define pa_context_get_source_info_list ppa_context_get_source_info_list
|
||||
#define pa_stream_new ppa_stream_new
|
||||
|
|
@ -252,7 +257,7 @@ constexpr pa_channel_map MonoChanMap{
|
|||
};
|
||||
|
||||
|
||||
/* *grumble* Don't use enums for bitflags. */
|
||||
/* NOLINTBEGIN(*EnumCastOutOfRange) *grumble* Don't use enums for bitflags. */
|
||||
constexpr pa_stream_flags_t operator|(pa_stream_flags_t lhs, pa_stream_flags_t rhs)
|
||||
{ return pa_stream_flags_t(lhs | al::to_underlying(rhs)); }
|
||||
constexpr pa_stream_flags_t& operator|=(pa_stream_flags_t &lhs, pa_stream_flags_t rhs)
|
||||
|
|
@ -278,11 +283,13 @@ constexpr pa_context_flags_t& operator|=(pa_context_flags_t &lhs, pa_context_fla
|
|||
|
||||
constexpr pa_subscription_mask_t operator|(pa_subscription_mask_t lhs, pa_subscription_mask_t rhs)
|
||||
{ return pa_subscription_mask_t(lhs | al::to_underlying(rhs)); }
|
||||
/* NOLINTEND(*EnumCastOutOfRange) */
|
||||
|
||||
|
||||
struct DevMap {
|
||||
std::string name;
|
||||
std::string device_name;
|
||||
uint32_t index{};
|
||||
};
|
||||
|
||||
bool checkName(const al::span<const DevMap> list, const std::string &name)
|
||||
|
|
@ -294,6 +301,9 @@ bool checkName(const al::span<const DevMap> list, const std::string &name)
|
|||
std::vector<DevMap> PlaybackDevices;
|
||||
std::vector<DevMap> CaptureDevices;
|
||||
|
||||
std::string DefaultPlaybackDevName;
|
||||
std::string DefaultCaptureDevName;
|
||||
|
||||
|
||||
/* Global flags and properties */
|
||||
pa_context_flags_t pulse_ctx_flags;
|
||||
|
|
@ -344,6 +354,32 @@ public:
|
|||
void close(pa_stream *stream=nullptr);
|
||||
|
||||
|
||||
void updateDefaultDevice(pa_context*, const pa_server_info *info) const
|
||||
{
|
||||
auto default_sink = info->default_sink_name ? std::string_view{info->default_sink_name}
|
||||
: std::string_view{};
|
||||
auto default_src = info->default_source_name ? std::string_view{info->default_source_name}
|
||||
: std::string_view{};
|
||||
|
||||
if(default_sink != DefaultPlaybackDevName)
|
||||
{
|
||||
TRACE("Default playback device: {}", default_sink);
|
||||
DefaultPlaybackDevName = default_sink;
|
||||
|
||||
const auto msg = fmt::format("Default playback device changed: {}", default_sink);
|
||||
alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, msg);
|
||||
}
|
||||
if(default_src != DefaultCaptureDevName)
|
||||
{
|
||||
TRACE("Default capture device: {}", default_src);
|
||||
DefaultCaptureDevName = default_src;
|
||||
|
||||
const auto msg = fmt::format("Default capture device changed: {}", default_src);
|
||||
alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, msg);
|
||||
}
|
||||
signal();
|
||||
}
|
||||
|
||||
void deviceSinkCallback(pa_context*, const pa_sink_info *info, int eol) const noexcept
|
||||
{
|
||||
if(eol)
|
||||
|
|
@ -361,18 +397,18 @@ public:
|
|||
/* Make sure the display name (description) is unique. Append a number
|
||||
* counter as needed.
|
||||
*/
|
||||
int count{1};
|
||||
std::string newname{info->description};
|
||||
auto count = 1;
|
||||
auto newname = std::string{info->description};
|
||||
while(checkName(PlaybackDevices, newname))
|
||||
{
|
||||
newname = info->description;
|
||||
newname += " #";
|
||||
newname += std::to_string(++count);
|
||||
}
|
||||
PlaybackDevices.emplace_back(DevMap{std::move(newname), info->name});
|
||||
DevMap &newentry = PlaybackDevices.back();
|
||||
newname = fmt::format("{} #{}", info->description, ++count);
|
||||
|
||||
TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str());
|
||||
const auto &newentry = PlaybackDevices.emplace_back(DevMap{std::move(newname),
|
||||
info->name, info->index});
|
||||
TRACE("Got device \"{}\", \"{}\" ({})", newentry.name, newentry.device_name,
|
||||
newentry.index);
|
||||
|
||||
const auto msg = fmt::format("Device added: {}", newentry.device_name);
|
||||
alc::Event(alc::EventType::DeviceAdded, alc::DeviceType::Playback, msg);
|
||||
}
|
||||
|
||||
void deviceSourceCallback(pa_context*, const pa_source_info *info, int eol) const noexcept
|
||||
|
|
@ -392,22 +428,77 @@ public:
|
|||
/* Make sure the display name (description) is unique. Append a number
|
||||
* counter as needed.
|
||||
*/
|
||||
int count{1};
|
||||
std::string newname{info->description};
|
||||
auto count = 1;
|
||||
auto newname = std::string{info->description};
|
||||
while(checkName(CaptureDevices, newname))
|
||||
{
|
||||
newname = info->description;
|
||||
newname += " #";
|
||||
newname += std::to_string(++count);
|
||||
}
|
||||
CaptureDevices.emplace_back(DevMap{std::move(newname), info->name});
|
||||
DevMap &newentry = CaptureDevices.back();
|
||||
newname = fmt::format("{} #{}", info->description, ++count);
|
||||
|
||||
TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str());
|
||||
const auto &newentry = CaptureDevices.emplace_back(DevMap{std::move(newname), info->name,
|
||||
info->index});
|
||||
TRACE("Got device \"{}\", \"{}\" ({})", newentry.name, newentry.device_name,
|
||||
newentry.index);
|
||||
|
||||
const auto msg = fmt::format("Device added: {}", newentry.device_name);
|
||||
alc::Event(alc::EventType::DeviceAdded, alc::DeviceType::Capture, msg);
|
||||
}
|
||||
|
||||
void probePlaybackDevices();
|
||||
void probeCaptureDevices();
|
||||
void eventCallback(pa_context *context, pa_subscription_event_type_t t, uint32_t idx) noexcept
|
||||
{
|
||||
const auto eventFacility = (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK);
|
||||
const auto eventType = (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK);
|
||||
|
||||
if(eventFacility == PA_SUBSCRIPTION_EVENT_SERVER
|
||||
&& eventType == PA_SUBSCRIPTION_EVENT_CHANGE)
|
||||
{
|
||||
static constexpr auto server_cb = [](pa_context *ctx, const pa_server_info *info,
|
||||
void *pdata) noexcept
|
||||
{ return static_cast<PulseMainloop*>(pdata)->updateDefaultDevice(ctx, info); };
|
||||
auto *op = pa_context_get_server_info(context, server_cb, this);
|
||||
if(op) pa_operation_unref(op);
|
||||
}
|
||||
|
||||
if(eventFacility != PA_SUBSCRIPTION_EVENT_SINK
|
||||
&& eventFacility != PA_SUBSCRIPTION_EVENT_SOURCE)
|
||||
return;
|
||||
|
||||
const auto devtype = (eventFacility == PA_SUBSCRIPTION_EVENT_SINK)
|
||||
? alc::DeviceType::Playback : alc::DeviceType::Capture;
|
||||
|
||||
if(eventType == PA_SUBSCRIPTION_EVENT_NEW)
|
||||
{
|
||||
if(eventFacility == PA_SUBSCRIPTION_EVENT_SINK)
|
||||
{
|
||||
static constexpr auto devcallback = [](pa_context *ctx, const pa_sink_info *info,
|
||||
int eol, void *pdata) noexcept
|
||||
{ return static_cast<PulseMainloop*>(pdata)->deviceSinkCallback(ctx, info, eol); };
|
||||
auto *op = pa_context_get_sink_info_by_index(context, idx, devcallback, this);
|
||||
if(op) pa_operation_unref(op);
|
||||
}
|
||||
else
|
||||
{
|
||||
static constexpr auto devcallback = [](pa_context *ctx, const pa_source_info *info,
|
||||
int eol, void *pdata) noexcept
|
||||
{ return static_cast<PulseMainloop*>(pdata)->deviceSourceCallback(ctx,info,eol); };
|
||||
auto *op = pa_context_get_source_info_by_index(context, idx, devcallback, this);
|
||||
if(op) pa_operation_unref(op);
|
||||
}
|
||||
}
|
||||
else if(eventType == PA_SUBSCRIPTION_EVENT_REMOVE)
|
||||
{
|
||||
auto find_index = [idx](const DevMap &entry) noexcept { return entry.index == idx; };
|
||||
|
||||
auto &devlist = (eventFacility == PA_SUBSCRIPTION_EVENT_SINK)
|
||||
? PlaybackDevices : CaptureDevices;
|
||||
auto iter = std::find_if(devlist.cbegin(), devlist.cend(), find_index);
|
||||
if(iter != devlist.cend())
|
||||
{
|
||||
devlist.erase(iter);
|
||||
|
||||
const auto msg = fmt::format("Device removed: {}", idx);
|
||||
alc::Event(alc::EventType::DeviceRemoved, devtype, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
friend struct MainloopUniqueLock;
|
||||
};
|
||||
|
|
@ -434,36 +525,38 @@ struct MainloopUniqueLock : public std::unique_lock<PulseMainloop> {
|
|||
|
||||
void setEventHandler()
|
||||
{
|
||||
pa_operation *op{pa_context_subscribe(mutex()->mContext,
|
||||
PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE,
|
||||
[](pa_context*, int, void *pdata) noexcept
|
||||
{ static_cast<PulseMainloop*>(pdata)->signal(); },
|
||||
mutex())};
|
||||
auto *context = mutex()->mContext;
|
||||
|
||||
/* Watch for device added/removed and server changed events. */
|
||||
static constexpr auto submask = PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE
|
||||
| PA_SUBSCRIPTION_MASK_SERVER;
|
||||
static constexpr auto do_signal = [](pa_context*, int, void *pdata) noexcept
|
||||
{ static_cast<PulseMainloop*>(pdata)->signal(); };
|
||||
auto *op = pa_context_subscribe(context, submask, do_signal, mutex());
|
||||
waitForOperation(op);
|
||||
|
||||
/* Watch for device added/removed events.
|
||||
*
|
||||
* TODO: Also track the "default" device, in as much as PulseAudio has
|
||||
* the concept of a default device (whatever device is opened when not
|
||||
* specifying a specific sink or source name). There doesn't seem to be
|
||||
* an event for this.
|
||||
*/
|
||||
auto handler = [](pa_context*, pa_subscription_event_type_t t, uint32_t, void*) noexcept
|
||||
{
|
||||
const auto eventFacility = (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK);
|
||||
if(eventFacility == PA_SUBSCRIPTION_EVENT_SINK
|
||||
|| eventFacility == PA_SUBSCRIPTION_EVENT_SOURCE)
|
||||
{
|
||||
const auto deviceType = (eventFacility == PA_SUBSCRIPTION_EVENT_SINK)
|
||||
? alc::DeviceType::Playback : alc::DeviceType::Capture;
|
||||
const auto eventType = (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK);
|
||||
if(eventType == PA_SUBSCRIPTION_EVENT_NEW)
|
||||
alc::Event(alc::EventType::DeviceAdded, deviceType, "Device added");
|
||||
else if(eventType == PA_SUBSCRIPTION_EVENT_REMOVE)
|
||||
alc::Event(alc::EventType::DeviceRemoved, deviceType, "Device removed");
|
||||
}
|
||||
};
|
||||
pa_context_set_subscribe_callback(mutex()->mContext, handler, nullptr);
|
||||
static constexpr auto handler = [](pa_context *ctx, pa_subscription_event_type_t t,
|
||||
uint32_t index, void *pdata) noexcept
|
||||
{ return static_cast<PulseMainloop*>(pdata)->eventCallback(ctx, t, index); };
|
||||
pa_context_set_subscribe_callback(context, handler, mutex());
|
||||
|
||||
/* Fill in the initial device lists, and get the defaults. */
|
||||
auto sink_callback = [](pa_context *ctx, const pa_sink_info *info, int eol, void *pdata) noexcept
|
||||
{ return static_cast<PulseMainloop*>(pdata)->deviceSinkCallback(ctx, info, eol); };
|
||||
|
||||
auto src_callback = [](pa_context *ctx, const pa_source_info *info, int eol, void *pdata) noexcept
|
||||
{ return static_cast<PulseMainloop*>(pdata)->deviceSourceCallback(ctx, info, eol); };
|
||||
|
||||
auto server_callback = [](pa_context *ctx, const pa_server_info *info, void *pdata) noexcept
|
||||
{ return static_cast<PulseMainloop*>(pdata)->updateDefaultDevice(ctx, info); };
|
||||
|
||||
auto *sinkop = pa_context_get_sink_info_list(context, sink_callback, mutex());
|
||||
auto *srcop = pa_context_get_source_info_list(context, src_callback, mutex());
|
||||
auto *serverop = pa_context_get_server_info(context, server_callback, mutex());
|
||||
|
||||
waitForOperation(sinkop);
|
||||
waitForOperation(srcop);
|
||||
waitForOperation(serverop);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -484,6 +577,13 @@ struct MainloopUniqueLock : public std::unique_lock<PulseMainloop> {
|
|||
void connectContext();
|
||||
pa_stream *connectStream(const char *device_name, pa_stream_flags_t flags,
|
||||
pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type);
|
||||
|
||||
pa_stream *connectStream(const std::string &device_name, pa_stream_flags_t flags,
|
||||
pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type)
|
||||
{
|
||||
return connectStream(device_name.empty() ? nullptr : device_name.c_str(), flags, attr,
|
||||
spec, chanmap, type);
|
||||
}
|
||||
};
|
||||
using MainloopLockGuard = std::lock_guard<PulseMainloop>;
|
||||
|
||||
|
|
@ -533,7 +633,7 @@ void MainloopUniqueLock::connectContext()
|
|||
{
|
||||
pa_context_unref(mutex()->mContext);
|
||||
mutex()->mContext = nullptr;
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Context did not connect (%s)",
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Context did not connect ({})",
|
||||
pa_strerror(err)};
|
||||
}
|
||||
}
|
||||
|
|
@ -544,7 +644,7 @@ pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_stream_
|
|||
const char *stream_id{(type==BackendType::Playback) ? "Playback Stream" : "Capture Stream"};
|
||||
pa_stream *stream{pa_stream_new(mutex()->mContext, stream_id, spec, chanmap)};
|
||||
if(!stream)
|
||||
throw al::backend_exception{al::backend_error::OutOfMemory, "pa_stream_new() failed (%s)",
|
||||
throw al::backend_exception{al::backend_error::OutOfMemory, "pa_stream_new() failed ({})",
|
||||
pa_strerror(pa_context_errno(mutex()->mContext))};
|
||||
|
||||
pa_stream_set_state_callback(stream, [](pa_stream *strm, void *pdata) noexcept
|
||||
|
|
@ -556,7 +656,7 @@ pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_stream_
|
|||
if(err < 0)
|
||||
{
|
||||
pa_stream_unref(stream);
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "%s did not connect (%s)",
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "%s did not connect ({})",
|
||||
stream_id, pa_strerror(err)};
|
||||
}
|
||||
|
||||
|
|
@ -568,7 +668,7 @@ pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_stream_
|
|||
err = pa_context_errno(mutex()->mContext);
|
||||
pa_stream_unref(stream);
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"%s did not get ready (%s)", stream_id, pa_strerror(err)};
|
||||
"{} did not get ready ({})", stream_id, pa_strerror(err)};
|
||||
}
|
||||
return state == PA_STREAM_READY;
|
||||
});
|
||||
|
|
@ -593,56 +693,12 @@ void PulseMainloop::close(pa_stream *stream)
|
|||
}
|
||||
|
||||
|
||||
void PulseMainloop::probePlaybackDevices()
|
||||
{
|
||||
PlaybackDevices.clear();
|
||||
try {
|
||||
MainloopUniqueLock plock{*this};
|
||||
plock.connectContext();
|
||||
|
||||
auto sink_callback = [](pa_context *ctx, const pa_sink_info *info, int eol, void *pdata) noexcept
|
||||
{ return static_cast<PulseMainloop*>(pdata)->deviceSinkCallback(ctx, info, eol); };
|
||||
|
||||
pa_operation *op{pa_context_get_sink_info_by_name(mContext, nullptr, sink_callback, this)};
|
||||
plock.waitForOperation(op);
|
||||
|
||||
op = pa_context_get_sink_info_list(mContext, sink_callback, this);
|
||||
plock.waitForOperation(op);
|
||||
}
|
||||
catch(std::exception &e) {
|
||||
ERR("Error enumerating devices: %s\n", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void PulseMainloop::probeCaptureDevices()
|
||||
{
|
||||
CaptureDevices.clear();
|
||||
try {
|
||||
MainloopUniqueLock plock{*this};
|
||||
plock.connectContext();
|
||||
|
||||
auto src_callback = [](pa_context *ctx, const pa_source_info *info, int eol, void *pdata) noexcept
|
||||
{ return static_cast<PulseMainloop*>(pdata)->deviceSourceCallback(ctx, info, eol); };
|
||||
|
||||
pa_operation *op{pa_context_get_source_info_by_name(mContext, nullptr, src_callback,
|
||||
this)};
|
||||
plock.waitForOperation(op);
|
||||
|
||||
op = pa_context_get_source_info_list(mContext, src_callback, this);
|
||||
plock.waitForOperation(op);
|
||||
}
|
||||
catch(std::exception &e) {
|
||||
ERR("Error enumerating devices: %s\n", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Used for initial connection test and enumeration. */
|
||||
PulseMainloop gGlobalMainloop;
|
||||
|
||||
|
||||
struct PulsePlayback final : public BackendBase {
|
||||
PulsePlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit PulsePlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~PulsePlayback() override;
|
||||
|
||||
void bufferAttrCallback(pa_stream *stream) noexcept;
|
||||
|
|
@ -660,7 +716,7 @@ struct PulsePlayback final : public BackendBase {
|
|||
|
||||
PulseMainloop mMainloop;
|
||||
|
||||
std::optional<std::string> mDeviceName{std::nullopt};
|
||||
std::optional<std::string> mDeviceId{std::nullopt};
|
||||
|
||||
bool mIs51Rear{false};
|
||||
pa_buffer_attr mAttr{};
|
||||
|
|
@ -683,14 +739,14 @@ void PulsePlayback::bufferAttrCallback(pa_stream *stream) noexcept
|
|||
* leaving it alone means ALC_REFRESH will be off.
|
||||
*/
|
||||
mAttr = *(pa_stream_get_buffer_attr(stream));
|
||||
TRACE("minreq=%d, tlength=%d, prebuf=%d\n", mAttr.minreq, mAttr.tlength, mAttr.prebuf);
|
||||
TRACE("minreq={}, tlength={}, prebuf={}", mAttr.minreq, mAttr.tlength, mAttr.prebuf);
|
||||
}
|
||||
|
||||
void PulsePlayback::streamStateCallback(pa_stream *stream) noexcept
|
||||
{
|
||||
if(pa_stream_get_state(stream) == PA_STREAM_FAILED)
|
||||
{
|
||||
ERR("Received stream failure!\n");
|
||||
ERR("Received stream failure!");
|
||||
mDevice->handleDisconnect("Playback stream failure");
|
||||
}
|
||||
mMainloop.signal();
|
||||
|
|
@ -716,7 +772,7 @@ void PulsePlayback::streamWriteCallback(pa_stream *stream, size_t nbytes) noexce
|
|||
|
||||
int ret{pa_stream_write(stream, buf, buflen, free_func, 0, PA_SEEK_RELATIVE)};
|
||||
if(ret != PA_OK) UNLIKELY
|
||||
ERR("Failed to write to stream: %d, %s\n", ret, pa_strerror(ret));
|
||||
ERR("Failed to write to stream: {}, {}", ret, pa_strerror(ret));
|
||||
} while(nbytes > 0);
|
||||
}
|
||||
|
||||
|
|
@ -759,11 +815,11 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int
|
|||
mIs51Rear = false;
|
||||
std::array<char,PA_CHANNEL_MAP_SNPRINT_MAX> chanmap_str{};
|
||||
pa_channel_map_snprint(chanmap_str.data(), chanmap_str.size(), &info->channel_map);
|
||||
WARN("Failed to find format for channel map:\n %s\n", chanmap_str.data());
|
||||
WARN("Failed to find format for channel map:\n {}", chanmap_str.data());
|
||||
}
|
||||
|
||||
if(info->active_port)
|
||||
TRACE("Active port: %s (%s)\n", info->active_port->name, info->active_port->description);
|
||||
TRACE("Active port: {} ({})", info->active_port->name, info->active_port->description);
|
||||
mDevice->Flags.set(DirectEar, (info->active_port
|
||||
&& strcmp(info->active_port->name, "analog-output-headphones") == 0));
|
||||
}
|
||||
|
|
@ -775,13 +831,13 @@ void PulsePlayback::sinkNameCallback(pa_context*, const pa_sink_info *info, int
|
|||
mMainloop.signal();
|
||||
return;
|
||||
}
|
||||
mDevice->DeviceName = info->description;
|
||||
mDeviceName = info->description;
|
||||
}
|
||||
|
||||
void PulsePlayback::streamMovedCallback(pa_stream *stream) noexcept
|
||||
{
|
||||
mDeviceName = pa_stream_get_device_name(stream);
|
||||
TRACE("Stream moved to %s\n", mDeviceName->c_str());
|
||||
mDeviceId = pa_stream_get_device_name(stream);
|
||||
TRACE("Stream moved to {}", *mDeviceId);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -792,22 +848,20 @@ void PulsePlayback::open(std::string_view name)
|
|||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start device mainloop"};
|
||||
|
||||
const char *pulse_name{nullptr};
|
||||
std::string_view display_name;
|
||||
auto pulse_name = std::string{};
|
||||
if(!name.empty())
|
||||
{
|
||||
if(PlaybackDevices.empty())
|
||||
mMainloop.probePlaybackDevices();
|
||||
|
||||
auto match_name = [name](const DevMap &entry) -> bool
|
||||
{ return entry.name == name || entry.device_name == name; };
|
||||
|
||||
auto plock = MainloopUniqueLock{gGlobalMainloop};
|
||||
auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), match_name);
|
||||
if(iter == PlaybackDevices.cend())
|
||||
throw al::backend_exception{al::backend_error::NoDevice,
|
||||
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
|
||||
"Device name \"{}\" not found", name};
|
||||
|
||||
pulse_name = iter->device_name.c_str();
|
||||
display_name = iter->name;
|
||||
pulse_name = iter->device_name;
|
||||
mDeviceName = iter->name;
|
||||
}
|
||||
|
||||
MainloopUniqueLock plock{mMainloop};
|
||||
|
|
@ -823,38 +877,38 @@ void PulsePlayback::open(std::string_view name)
|
|||
spec.rate = 44100;
|
||||
spec.channels = 2;
|
||||
|
||||
if(!pulse_name)
|
||||
if(pulse_name.empty())
|
||||
{
|
||||
static const auto defname = al::getenv("ALSOFT_PULSE_DEFAULT");
|
||||
if(defname) pulse_name = defname->c_str();
|
||||
if(defname) pulse_name = *defname;
|
||||
}
|
||||
TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)");
|
||||
TRACE("Connecting to \"{}\"", pulse_name.empty() ? "(default)"sv:std::string_view{pulse_name});
|
||||
mStream = plock.connectStream(pulse_name, flags, nullptr, &spec, nullptr,
|
||||
BackendType::Playback);
|
||||
|
||||
constexpr auto move_callback = [](pa_stream *stream, void *pdata) noexcept
|
||||
static constexpr auto move_callback = [](pa_stream *stream, void *pdata) noexcept
|
||||
{ return static_cast<PulsePlayback*>(pdata)->streamMovedCallback(stream); };
|
||||
pa_stream_set_moved_callback(mStream, move_callback, this);
|
||||
mFrameSize = static_cast<uint>(pa_frame_size(pa_stream_get_sample_spec(mStream)));
|
||||
|
||||
if(pulse_name) mDeviceName.emplace(pulse_name);
|
||||
else mDeviceName.reset();
|
||||
if(display_name.empty())
|
||||
if(!pulse_name.empty())
|
||||
mDeviceId.emplace(std::move(pulse_name));
|
||||
|
||||
if(mDeviceName.empty())
|
||||
{
|
||||
auto name_callback = [](pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept
|
||||
static constexpr auto name_callback = [](pa_context *context, const pa_sink_info *info,
|
||||
int eol, void *pdata) noexcept
|
||||
{ return static_cast<PulsePlayback*>(pdata)->sinkNameCallback(context, info, eol); };
|
||||
pa_operation *op{pa_context_get_sink_info_by_name(mMainloop.getContext(),
|
||||
pa_stream_get_device_name(mStream), name_callback, this)};
|
||||
plock.waitForOperation(op);
|
||||
}
|
||||
else
|
||||
mDevice->DeviceName = display_name;
|
||||
}
|
||||
|
||||
bool PulsePlayback::reset()
|
||||
{
|
||||
MainloopUniqueLock plock{mMainloop};
|
||||
const auto deviceName = mDeviceName ? mDeviceName->c_str() : nullptr;
|
||||
const auto deviceName = mDeviceId ? mDeviceId->c_str() : nullptr;
|
||||
|
||||
if(mStream)
|
||||
{
|
||||
|
|
@ -877,7 +931,7 @@ bool PulsePlayback::reset()
|
|||
PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_EARLY_REQUESTS};
|
||||
if(!GetConfigValueBool({}, "pulse", "allow-moves", true))
|
||||
flags |= PA_STREAM_DONT_MOVE;
|
||||
if(GetConfigValueBool(mDevice->DeviceName, "pulse", "adjust-latency", false))
|
||||
if(GetConfigValueBool(mDevice->mDeviceName, "pulse", "adjust-latency", false))
|
||||
{
|
||||
/* ADJUST_LATENCY can't be specified with EARLY_REQUESTS, for some
|
||||
* reason. So if the user wants to adjust the overall device latency,
|
||||
|
|
@ -886,7 +940,7 @@ bool PulsePlayback::reset()
|
|||
flags &= ~PA_STREAM_EARLY_REQUESTS;
|
||||
flags |= PA_STREAM_ADJUST_LATENCY;
|
||||
}
|
||||
if(GetConfigValueBool(mDevice->DeviceName, "pulse", "fix-rate", false)
|
||||
if(GetConfigValueBool(mDevice->mDeviceName, "pulse", "fix-rate", false)
|
||||
|| !mDevice->Flags.test(FrequencyRequest))
|
||||
flags |= PA_STREAM_FIX_RATE;
|
||||
|
||||
|
|
@ -948,16 +1002,16 @@ bool PulsePlayback::reset()
|
|||
mSpec.format = PA_SAMPLE_FLOAT32NE;
|
||||
break;
|
||||
}
|
||||
mSpec.rate = mDevice->Frequency;
|
||||
mSpec.rate = mDevice->mSampleRate;
|
||||
mSpec.channels = static_cast<uint8_t>(mDevice->channelsFromFmt());
|
||||
if(pa_sample_spec_valid(&mSpec) == 0)
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample spec"};
|
||||
|
||||
const auto frame_size = static_cast<uint>(pa_frame_size(&mSpec));
|
||||
mAttr.maxlength = ~0u;
|
||||
mAttr.tlength = mDevice->BufferSize * frame_size;
|
||||
mAttr.tlength = mDevice->mBufferSize * frame_size;
|
||||
mAttr.prebuf = 0u;
|
||||
mAttr.minreq = mDevice->UpdateSize * frame_size;
|
||||
mAttr.minreq = mDevice->mUpdateSize * frame_size;
|
||||
mAttr.fragsize = ~0u;
|
||||
|
||||
mStream = plock.connectStream(deviceName, flags, &mAttr, &mSpec, &chanmap,
|
||||
|
|
@ -974,15 +1028,15 @@ bool PulsePlayback::reset()
|
|||
mSpec = *(pa_stream_get_sample_spec(mStream));
|
||||
mFrameSize = static_cast<uint>(pa_frame_size(&mSpec));
|
||||
|
||||
if(mDevice->Frequency != mSpec.rate)
|
||||
if(mDevice->mSampleRate != mSpec.rate)
|
||||
{
|
||||
/* Server updated our playback rate, so modify the buffer attribs
|
||||
* accordingly.
|
||||
*/
|
||||
const auto scale = static_cast<double>(mSpec.rate) / mDevice->Frequency;
|
||||
const auto perlen = std::clamp(std::round(scale*mDevice->UpdateSize), 64.0, 8192.0);
|
||||
const auto scale = static_cast<double>(mSpec.rate) / mDevice->mSampleRate;
|
||||
const auto perlen = std::clamp(std::round(scale*mDevice->mUpdateSize), 64.0, 8192.0);
|
||||
const auto bufmax = uint{std::numeric_limits<int>::max()} / mFrameSize;
|
||||
const auto buflen = std::clamp(std::round(scale*mDevice->BufferSize), perlen*2.0,
|
||||
const auto buflen = std::clamp(std::round(scale*mDevice->mBufferSize), perlen*2.0,
|
||||
static_cast<double>(bufmax));
|
||||
|
||||
mAttr.maxlength = ~0u;
|
||||
|
|
@ -994,7 +1048,7 @@ bool PulsePlayback::reset()
|
|||
&mMainloop);
|
||||
plock.waitForOperation(op);
|
||||
|
||||
mDevice->Frequency = mSpec.rate;
|
||||
mDevice->mSampleRate = mSpec.rate;
|
||||
}
|
||||
|
||||
constexpr auto attr_callback = [](pa_stream *stream, void *pdata) noexcept
|
||||
|
|
@ -1002,8 +1056,8 @@ bool PulsePlayback::reset()
|
|||
pa_stream_set_buffer_attr_callback(mStream, attr_callback, this);
|
||||
bufferAttrCallback(mStream);
|
||||
|
||||
mDevice->BufferSize = mAttr.tlength / mFrameSize;
|
||||
mDevice->UpdateSize = mAttr.minreq / mFrameSize;
|
||||
mDevice->mBufferSize = mAttr.tlength / mFrameSize;
|
||||
mDevice->mUpdateSize = mAttr.minreq / mFrameSize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1061,8 +1115,8 @@ ClockLatency PulsePlayback::getClockLatency()
|
|||
* server yet. Give a generic value since nothing better is available.
|
||||
*/
|
||||
if(err != -PA_ERR_NODATA)
|
||||
ERR("Failed to get stream latency: 0x%x\n", err);
|
||||
latency = mDevice->BufferSize - mDevice->UpdateSize;
|
||||
ERR("Failed to get stream latency: {:#x}", as_unsigned(err));
|
||||
latency = mDevice->mBufferSize - mDevice->mUpdateSize;
|
||||
neg = 0;
|
||||
}
|
||||
else if(neg) UNLIKELY
|
||||
|
|
@ -1074,7 +1128,7 @@ ClockLatency PulsePlayback::getClockLatency()
|
|||
|
||||
|
||||
struct PulseCapture final : public BackendBase {
|
||||
PulseCapture(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit PulseCapture(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~PulseCapture() override;
|
||||
|
||||
void streamStateCallback(pa_stream *stream) noexcept;
|
||||
|
|
@ -1090,7 +1144,7 @@ struct PulseCapture final : public BackendBase {
|
|||
|
||||
PulseMainloop mMainloop;
|
||||
|
||||
std::optional<std::string> mDeviceName{std::nullopt};
|
||||
std::optional<std::string> mDeviceId{std::nullopt};
|
||||
|
||||
al::span<const std::byte> mCapBuffer;
|
||||
size_t mHoleLength{0};
|
||||
|
|
@ -1113,7 +1167,7 @@ void PulseCapture::streamStateCallback(pa_stream *stream) noexcept
|
|||
{
|
||||
if(pa_stream_get_state(stream) == PA_STREAM_FAILED)
|
||||
{
|
||||
ERR("Received stream failure!\n");
|
||||
ERR("Received stream failure!");
|
||||
mDevice->handleDisconnect("Capture stream failure");
|
||||
}
|
||||
mMainloop.signal();
|
||||
|
|
@ -1126,13 +1180,13 @@ void PulseCapture::sourceNameCallback(pa_context*, const pa_source_info *info, i
|
|||
mMainloop.signal();
|
||||
return;
|
||||
}
|
||||
mDevice->DeviceName = info->description;
|
||||
mDeviceName = info->description;
|
||||
}
|
||||
|
||||
void PulseCapture::streamMovedCallback(pa_stream *stream) noexcept
|
||||
{
|
||||
mDeviceName = pa_stream_get_device_name(stream);
|
||||
TRACE("Stream moved to %s\n", mDeviceName->c_str());
|
||||
mDeviceId = pa_stream_get_device_name(stream);
|
||||
TRACE("Stream moved to {}", *mDeviceId);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1146,21 +1200,20 @@ void PulseCapture::open(std::string_view name)
|
|||
"Failed to start device mainloop"};
|
||||
}
|
||||
|
||||
const char *pulse_name{nullptr};
|
||||
auto pulse_name = std::string{};
|
||||
if(!name.empty())
|
||||
{
|
||||
if(CaptureDevices.empty())
|
||||
mMainloop.probeCaptureDevices();
|
||||
|
||||
auto match_name = [name](const DevMap &entry) -> bool
|
||||
{ return entry.name == name || entry.device_name == name; };
|
||||
|
||||
auto plock = MainloopUniqueLock{gGlobalMainloop};
|
||||
auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), match_name);
|
||||
if(iter == CaptureDevices.cend())
|
||||
throw al::backend_exception{al::backend_error::NoDevice,
|
||||
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
|
||||
"Device name \"{}\" not found", name};
|
||||
|
||||
pulse_name = iter->device_name.c_str();
|
||||
mDevice->DeviceName = iter->name;
|
||||
pulse_name = iter->device_name;
|
||||
mDeviceName = iter->name;
|
||||
}
|
||||
|
||||
MainloopUniqueLock plock{mMainloop};
|
||||
|
|
@ -1179,7 +1232,7 @@ void PulseCapture::open(std::string_view name)
|
|||
case DevFmtX7144:
|
||||
case DevFmtX3D71:
|
||||
case DevFmtAmbi3D:
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported",
|
||||
DevFmtChannelsString(mDevice->FmtChans)};
|
||||
}
|
||||
setDefaultWFXChannelOrder();
|
||||
|
|
@ -1203,26 +1256,26 @@ void PulseCapture::open(std::string_view name)
|
|||
case DevFmtUShort:
|
||||
case DevFmtUInt:
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
|
||||
"{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
|
||||
}
|
||||
mSpec.rate = mDevice->Frequency;
|
||||
mSpec.rate = mDevice->mSampleRate;
|
||||
mSpec.channels = static_cast<uint8_t>(mDevice->channelsFromFmt());
|
||||
if(pa_sample_spec_valid(&mSpec) == 0)
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample format"};
|
||||
|
||||
const auto frame_size = static_cast<uint>(pa_frame_size(&mSpec));
|
||||
const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency*100u/1000u)};
|
||||
const uint samples{std::max(mDevice->mBufferSize, mDevice->mSampleRate*100u/1000u)};
|
||||
mAttr.minreq = ~0u;
|
||||
mAttr.prebuf = ~0u;
|
||||
mAttr.maxlength = samples * frame_size;
|
||||
mAttr.tlength = ~0u;
|
||||
mAttr.fragsize = std::min(samples, mDevice->Frequency*50u/1000u) * frame_size;
|
||||
mAttr.fragsize = std::min(samples, mDevice->mSampleRate*50u/1000u) * frame_size;
|
||||
|
||||
pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY};
|
||||
if(!GetConfigValueBool({}, "pulse", "allow-moves", true))
|
||||
flags |= PA_STREAM_DONT_MOVE;
|
||||
|
||||
TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)");
|
||||
TRACE("Connecting to \"{}\"", pulse_name.empty() ? "(default)"sv:std::string_view{pulse_name});
|
||||
mStream = plock.connectStream(pulse_name, flags, &mAttr, &mSpec, &chanmap,
|
||||
BackendType::Capture);
|
||||
|
||||
|
|
@ -1234,9 +1287,10 @@ void PulseCapture::open(std::string_view name)
|
|||
{ return static_cast<PulseCapture*>(pdata)->streamStateCallback(stream); };
|
||||
pa_stream_set_state_callback(mStream, state_callback, this);
|
||||
|
||||
if(pulse_name) mDeviceName.emplace(pulse_name);
|
||||
else mDeviceName.reset();
|
||||
if(mDevice->DeviceName.empty())
|
||||
if(!pulse_name.empty())
|
||||
mDeviceId.emplace(std::move(pulse_name));
|
||||
|
||||
if(mDeviceName.empty())
|
||||
{
|
||||
constexpr auto name_callback = [](pa_context *context, const pa_source_info *info, int eol,
|
||||
void *pdata) noexcept
|
||||
|
|
@ -1305,7 +1359,7 @@ void PulseCapture::captureSamples(std::byte *buffer, uint samples)
|
|||
const pa_stream_state_t state{pa_stream_get_state(mStream)};
|
||||
if(!PA_STREAM_IS_GOOD(state)) UNLIKELY
|
||||
{
|
||||
mDevice->handleDisconnect("Bad capture state: %u", state);
|
||||
mDevice->handleDisconnect("Bad capture state: {}", al::to_underlying(state));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1313,7 +1367,7 @@ void PulseCapture::captureSamples(std::byte *buffer, uint samples)
|
|||
size_t caplen{};
|
||||
if(pa_stream_peek(mStream, &capbuf, &caplen) < 0) UNLIKELY
|
||||
{
|
||||
mDevice->handleDisconnect("Failed retrieving capture samples: %s",
|
||||
mDevice->handleDisconnect("Failed retrieving capture samples: {}",
|
||||
pa_strerror(pa_context_errno(mMainloop.getContext())));
|
||||
break;
|
||||
}
|
||||
|
|
@ -1341,8 +1395,8 @@ uint PulseCapture::availableSamples()
|
|||
if(static_cast<ssize_t>(got) < 0) UNLIKELY
|
||||
{
|
||||
const char *err{pa_strerror(static_cast<int>(got))};
|
||||
ERR("pa_stream_readable_size() failed: %s\n", err);
|
||||
mDevice->handleDisconnect("Failed getting readable size: %s", err);
|
||||
ERR("pa_stream_readable_size() failed: {}", err);
|
||||
mDevice->handleDisconnect("Failed getting readable size: {}", err);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1377,7 +1431,7 @@ ClockLatency PulseCapture::getClockLatency()
|
|||
|
||||
if(err != 0) UNLIKELY
|
||||
{
|
||||
ERR("Failed to get stream latency: 0x%x\n", err);
|
||||
ERR("Failed to get stream latency: {:#x}", as_unsigned(err));
|
||||
latency = 0;
|
||||
neg = 0;
|
||||
}
|
||||
|
|
@ -1393,7 +1447,7 @@ ClockLatency PulseCapture::getClockLatency()
|
|||
|
||||
bool PulseBackendFactory::init()
|
||||
{
|
||||
#ifdef HAVE_DYNLOAD
|
||||
#if HAVE_DYNLOAD
|
||||
if(!pulse_handle)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
|
|
@ -1406,7 +1460,7 @@ bool PulseBackendFactory::init()
|
|||
pulse_handle = LoadLib(PALIB);
|
||||
if(!pulse_handle)
|
||||
{
|
||||
WARN("Failed to load %s\n", PALIB);
|
||||
WARN("Failed to load {}", PALIB);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1420,13 +1474,13 @@ bool PulseBackendFactory::init()
|
|||
|
||||
if(!missing_funcs.empty())
|
||||
{
|
||||
WARN("Missing expected functions:%s\n", missing_funcs.c_str());
|
||||
WARN("Missing expected functions:{}", missing_funcs);
|
||||
CloseLib(pulse_handle);
|
||||
pulse_handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif /* HAVE_DYNLOAD */
|
||||
#endif
|
||||
|
||||
pulse_ctx_flags = PA_CONTEXT_NOFLAGS;
|
||||
if(!GetConfigValueBool({}, "pulse", "spawn-server", false))
|
||||
|
|
@ -1460,21 +1514,32 @@ auto PulseBackendFactory::enumerate(BackendType type) -> std::vector<std::string
|
|||
{
|
||||
std::vector<std::string> outnames;
|
||||
|
||||
auto add_device = [&outnames](const DevMap &entry) -> void
|
||||
{ outnames.push_back(entry.name); };
|
||||
auto add_playback_device = [&outnames](const DevMap &entry) -> void
|
||||
{
|
||||
if(entry.device_name == DefaultPlaybackDevName)
|
||||
outnames.emplace(outnames.cbegin(), entry.name);
|
||||
else
|
||||
outnames.push_back(entry.name);
|
||||
};
|
||||
auto add_capture_device = [&outnames](const DevMap &entry) -> void
|
||||
{
|
||||
if(entry.device_name == DefaultCaptureDevName)
|
||||
outnames.emplace(outnames.cbegin(), entry.name);
|
||||
else
|
||||
outnames.push_back(entry.name);
|
||||
};
|
||||
|
||||
auto plock = MainloopUniqueLock{gGlobalMainloop};
|
||||
switch(type)
|
||||
{
|
||||
case BackendType::Playback:
|
||||
gGlobalMainloop.probePlaybackDevices();
|
||||
outnames.reserve(PlaybackDevices.size());
|
||||
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
|
||||
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_playback_device);
|
||||
break;
|
||||
|
||||
case BackendType::Capture:
|
||||
gGlobalMainloop.probeCaptureDevices();
|
||||
outnames.reserve(CaptureDevices.size());
|
||||
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
|
||||
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_capture_device);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1502,9 +1567,9 @@ alc::EventSupport PulseBackendFactory::queryEventSupport(alc::EventType eventTyp
|
|||
{
|
||||
case alc::EventType::DeviceAdded:
|
||||
case alc::EventType::DeviceRemoved:
|
||||
case alc::EventType::DefaultDeviceChanged:
|
||||
return alc::EventSupport::FullSupport;
|
||||
|
||||
case alc::EventType::DefaultDeviceChanged:
|
||||
case alc::EventType::Count:
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,10 +28,8 @@
|
|||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "core/device.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
_Pragma("GCC diagnostic push")
|
||||
_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"")
|
||||
|
|
@ -41,18 +39,13 @@ _Pragma("GCC diagnostic pop")
|
|||
|
||||
namespace {
|
||||
|
||||
#ifdef _WIN32
|
||||
#define DEVNAME_PREFIX "OpenAL Soft on "
|
||||
#else
|
||||
#define DEVNAME_PREFIX ""
|
||||
#endif
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
constexpr auto getDevicePrefix() noexcept -> std::string_view { return DEVNAME_PREFIX; }
|
||||
constexpr auto getDefaultDeviceName() noexcept -> std::string_view
|
||||
{ return DEVNAME_PREFIX "Default Device"; }
|
||||
[[nodiscard]] constexpr auto getDefaultDeviceName() noexcept -> std::string_view
|
||||
{ return "Default Device"sv; }
|
||||
|
||||
struct Sdl2Backend final : public BackendBase {
|
||||
Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~Sdl2Backend() override;
|
||||
|
||||
void audioCallback(Uint8 *stream, int len) noexcept;
|
||||
|
|
@ -62,13 +55,9 @@ struct Sdl2Backend final : public BackendBase {
|
|||
void start() override;
|
||||
void stop() override;
|
||||
|
||||
std::string mSDLName;
|
||||
SDL_AudioDeviceID mDeviceID{0u};
|
||||
uint mFrameSize{0};
|
||||
|
||||
uint mFrequency{0u};
|
||||
DevFmtChannels mFmtChans{};
|
||||
DevFmtType mFmtType{};
|
||||
uint mUpdateSize{0u};
|
||||
};
|
||||
|
||||
Sdl2Backend::~Sdl2Backend()
|
||||
|
|
@ -89,7 +78,7 @@ void Sdl2Backend::open(std::string_view name)
|
|||
{
|
||||
SDL_AudioSpec want{}, have{};
|
||||
|
||||
want.freq = static_cast<int>(mDevice->Frequency);
|
||||
want.freq = static_cast<int>(mDevice->mSampleRate);
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtUByte: want.format = AUDIO_U8; break;
|
||||
|
|
@ -100,8 +89,9 @@ void Sdl2Backend::open(std::string_view name)
|
|||
case DevFmtInt: want.format = AUDIO_S32SYS; break;
|
||||
case DevFmtFloat: want.format = AUDIO_F32; break;
|
||||
}
|
||||
want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2;
|
||||
want.samples = static_cast<Uint16>(std::min(mDevice->UpdateSize, 8192u));
|
||||
want.channels = static_cast<Uint8>(std::min<uint>(mDevice->channelsFromFmt(),
|
||||
std::numeric_limits<Uint8>::max()));
|
||||
want.samples = static_cast<Uint16>(std::min(mDevice->mUpdateSize, 8192u));
|
||||
want.callback = [](void *ptr, Uint8 *stream, int len) noexcept
|
||||
{ return static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); };
|
||||
want.userdata = this;
|
||||
|
|
@ -110,45 +100,21 @@ void Sdl2Backend::open(std::string_view name)
|
|||
* necessarily the first in the list.
|
||||
*/
|
||||
const auto defaultDeviceName = getDefaultDeviceName();
|
||||
SDL_AudioDeviceID devid;
|
||||
if(name.empty() || name == defaultDeviceName)
|
||||
{
|
||||
name = defaultDeviceName;
|
||||
devid = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE);
|
||||
mSDLName.clear();
|
||||
mDeviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have,
|
||||
SDL_AUDIO_ALLOW_ANY_CHANGE);
|
||||
}
|
||||
else
|
||||
{
|
||||
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
|
||||
{
|
||||
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()};
|
||||
|
||||
DevFmtChannels devchans{};
|
||||
if(have.channels >= 2)
|
||||
devchans = DevFmtStereo;
|
||||
else if(have.channels == 1)
|
||||
devchans = DevFmtMono;
|
||||
else
|
||||
{
|
||||
SDL_CloseAudioDevice(devid);
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Unhandled SDL channel count: %d", int{have.channels}};
|
||||
mSDLName = name;
|
||||
mDeviceID = SDL_OpenAudioDevice(mSDLName.c_str(), SDL_FALSE, &want, &have,
|
||||
SDL_AUDIO_ALLOW_ANY_CHANGE);
|
||||
}
|
||||
if(!mDeviceID)
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "{}", SDL_GetError()};
|
||||
|
||||
DevFmtType devtype{};
|
||||
switch(have.format)
|
||||
|
|
@ -160,32 +126,100 @@ void Sdl2Backend::open(std::string_view name)
|
|||
case AUDIO_S32SYS: devtype = DevFmtInt; break;
|
||||
case AUDIO_F32SYS: devtype = DevFmtFloat; break;
|
||||
default:
|
||||
SDL_CloseAudioDevice(devid);
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL format: 0x%04x",
|
||||
have.format};
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Unhandled SDL format: {:#04x}", have.format};
|
||||
}
|
||||
|
||||
if(mDeviceID)
|
||||
SDL_CloseAudioDevice(mDeviceID);
|
||||
mDeviceID = devid;
|
||||
|
||||
mFrameSize = BytesFromDevFmt(devtype) * have.channels;
|
||||
mFrequency = static_cast<uint>(have.freq);
|
||||
mFmtChans = devchans;
|
||||
mFmtType = devtype;
|
||||
mUpdateSize = have.samples;
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
bool Sdl2Backend::reset()
|
||||
{
|
||||
mDevice->Frequency = mFrequency;
|
||||
mDevice->FmtChans = mFmtChans;
|
||||
mDevice->FmtType = mFmtType;
|
||||
mDevice->UpdateSize = mUpdateSize;
|
||||
mDevice->BufferSize = mUpdateSize * 2; /* SDL always (tries to) use two periods. */
|
||||
if(mDeviceID)
|
||||
SDL_CloseAudioDevice(mDeviceID);
|
||||
mDeviceID = 0;
|
||||
|
||||
auto want = SDL_AudioSpec{};
|
||||
want.freq = static_cast<int>(mDevice->mSampleRate);
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtUByte: want.format = AUDIO_U8; break;
|
||||
case DevFmtByte: want.format = AUDIO_S8; break;
|
||||
case DevFmtUShort: want.format = AUDIO_U16SYS; break;
|
||||
case DevFmtShort: want.format = AUDIO_S16SYS; break;
|
||||
case DevFmtUInt: [[fallthrough]];
|
||||
case DevFmtInt: want.format = AUDIO_S32SYS; break;
|
||||
case DevFmtFloat: want.format = AUDIO_F32; break;
|
||||
}
|
||||
want.channels = static_cast<Uint8>(std::min<uint>(mDevice->channelsFromFmt(),
|
||||
std::numeric_limits<Uint8>::max()));
|
||||
want.samples = static_cast<Uint16>(std::min(mDevice->mUpdateSize, 8192u));
|
||||
want.callback = [](void *ptr, Uint8 *stream, int len) noexcept
|
||||
{ return static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); };
|
||||
want.userdata = this;
|
||||
|
||||
auto have = SDL_AudioSpec{};
|
||||
if(mSDLName.empty())
|
||||
{
|
||||
mDeviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have,
|
||||
SDL_AUDIO_ALLOW_ANY_CHANGE);
|
||||
}
|
||||
else
|
||||
{
|
||||
mDeviceID = SDL_OpenAudioDevice(mSDLName.c_str(), SDL_FALSE, &want, &have,
|
||||
SDL_AUDIO_ALLOW_ANY_CHANGE);
|
||||
}
|
||||
if(!mDeviceID)
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "{}", SDL_GetError()};
|
||||
|
||||
if(have.channels != mDevice->channelsFromFmt())
|
||||
{
|
||||
/* SDL guarantees these layouts for the given channel count. */
|
||||
if(have.channels == 8)
|
||||
mDevice->FmtChans = DevFmtX71;
|
||||
else if(have.channels == 7)
|
||||
mDevice->FmtChans = DevFmtX61;
|
||||
else if(have.channels == 6)
|
||||
mDevice->FmtChans = DevFmtX51;
|
||||
else if(have.channels == 4)
|
||||
mDevice->FmtChans = DevFmtQuad;
|
||||
else if(have.channels >= 2)
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
else if(have.channels == 1)
|
||||
mDevice->FmtChans = DevFmtMono;
|
||||
else
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Unhandled SDL channel count: {}", int{have.channels}};
|
||||
mDevice->mAmbiOrder = 0;
|
||||
}
|
||||
|
||||
switch(have.format)
|
||||
{
|
||||
case AUDIO_U8: mDevice->FmtType = DevFmtUByte; break;
|
||||
case AUDIO_S8: mDevice->FmtType = DevFmtByte; break;
|
||||
case AUDIO_U16SYS: mDevice->FmtType = DevFmtUShort; break;
|
||||
case AUDIO_S16SYS: mDevice->FmtType = DevFmtShort; break;
|
||||
case AUDIO_S32SYS: mDevice->FmtType = DevFmtInt; break;
|
||||
case AUDIO_F32SYS: mDevice->FmtType = DevFmtFloat; break;
|
||||
default:
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Unhandled SDL format: {:#04x}", have.format};
|
||||
}
|
||||
|
||||
mFrameSize = BytesFromDevFmt(mDevice->FmtType) * have.channels;
|
||||
|
||||
if(have.freq < int{MinOutputRate})
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Unhandled SDL sample rate: {}", have.freq};
|
||||
|
||||
mDevice->mSampleRate = static_cast<uint>(have.freq);
|
||||
mDevice->mUpdateSize = have.samples;
|
||||
mDevice->mBufferSize = std::max(have.size/mFrameSize, mDevice->mUpdateSize*2u);
|
||||
|
||||
setDefaultWFXChannelOrder();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -220,16 +254,14 @@ auto SDL2BackendFactory::enumerate(BackendType type) -> std::vector<std::string>
|
|||
if(num_devices <= 0)
|
||||
return outnames;
|
||||
|
||||
outnames.reserve(static_cast<unsigned int>(num_devices));
|
||||
outnames.reserve(static_cast<unsigned int>(num_devices)+1_uz);
|
||||
outnames.emplace_back(getDefaultDeviceName());
|
||||
for(int i{0};i < num_devices;++i)
|
||||
{
|
||||
std::string outname{getDevicePrefix()};
|
||||
if(const char *name = SDL_GetAudioDeviceName(i, SDL_FALSE))
|
||||
outname += name;
|
||||
outnames.emplace_back(name);
|
||||
else
|
||||
outname += "Unknown Device Name #"+std::to_string(i);
|
||||
outnames.emplace_back(std::move(outname));
|
||||
outnames.emplace_back("Unknown Device Name #"+std::to_string(i));
|
||||
}
|
||||
return outnames;
|
||||
}
|
||||
|
|
|
|||
393
Engine/lib/openal-soft/alc/backends/sdl3.cpp
Normal file
393
Engine/lib/openal-soft/alc/backends/sdl3.cpp
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2024 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "sdl3.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "core/device.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
_Pragma("GCC diagnostic push")
|
||||
_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"")
|
||||
#include "SDL3/SDL_audio.h"
|
||||
#include "SDL3/SDL_init.h"
|
||||
#include "SDL3/SDL_stdinc.h"
|
||||
_Pragma("GCC diagnostic pop")
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
_Pragma("GCC diagnostic push")
|
||||
_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"")
|
||||
constexpr auto DefaultPlaybackDeviceID = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
|
||||
_Pragma("GCC diagnostic pop")
|
||||
|
||||
|
||||
template<typename T>
|
||||
struct SdlDeleter {
|
||||
/* NOLINTNEXTLINE(cppcoreguidelines-no-malloc) */
|
||||
void operator()(gsl::owner<T*> ptr) const { SDL_free(ptr); }
|
||||
};
|
||||
template<typename T>
|
||||
using unique_sdl_ptr = std::unique_ptr<T,SdlDeleter<T>>;
|
||||
|
||||
|
||||
struct DeviceEntry {
|
||||
std::string mName;
|
||||
SDL_AudioDeviceID mPhysDeviceID{};
|
||||
};
|
||||
|
||||
std::vector<DeviceEntry> gPlaybackDevices;
|
||||
|
||||
void EnumeratePlaybackDevices()
|
||||
{
|
||||
auto numdevs = int{};
|
||||
auto devicelist = unique_sdl_ptr<SDL_AudioDeviceID>{SDL_GetAudioPlaybackDevices(&numdevs)};
|
||||
if(!devicelist || numdevs < 0)
|
||||
{
|
||||
ERR("Failed to get playback devices: {}", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
auto devids = al::span{devicelist.get(), static_cast<uint>(numdevs)};
|
||||
auto newlist = std::vector<DeviceEntry>{};
|
||||
|
||||
newlist.reserve(devids.size());
|
||||
std::transform(devids.begin(), devids.end(), std::back_inserter(newlist),
|
||||
[](SDL_AudioDeviceID id)
|
||||
{
|
||||
auto *name = SDL_GetAudioDeviceName(id);
|
||||
if(!name) return DeviceEntry{};
|
||||
TRACE("Got device \"{}\", ID {}", name, id);
|
||||
return DeviceEntry{name, id};
|
||||
});
|
||||
|
||||
gPlaybackDevices.swap(newlist);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto getDefaultDeviceName() noexcept -> std::string_view
|
||||
{ return "Default Device"sv; }
|
||||
|
||||
|
||||
struct Sdl3Backend final : public BackendBase {
|
||||
explicit Sdl3Backend(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~Sdl3Backend() final;
|
||||
|
||||
void audioCallback(SDL_AudioStream *stream, int additional_amount, int total_amount) noexcept;
|
||||
|
||||
void open(std::string_view name) final;
|
||||
auto reset() -> bool final;
|
||||
void start() final;
|
||||
void stop() final;
|
||||
|
||||
SDL_AudioDeviceID mDeviceID{0};
|
||||
SDL_AudioStream *mStream{nullptr};
|
||||
uint mNumChannels{0};
|
||||
uint mFrameSize{0};
|
||||
std::vector<std::byte> mBuffer;
|
||||
};
|
||||
|
||||
Sdl3Backend::~Sdl3Backend()
|
||||
{
|
||||
if(mStream)
|
||||
SDL_DestroyAudioStream(mStream);
|
||||
mStream = nullptr;
|
||||
}
|
||||
|
||||
void Sdl3Backend::audioCallback(SDL_AudioStream *stream, int additional_amount, int total_amount)
|
||||
noexcept
|
||||
{
|
||||
if(additional_amount < 0)
|
||||
additional_amount = total_amount;
|
||||
if(additional_amount <= 0)
|
||||
return;
|
||||
|
||||
const auto ulen = static_cast<unsigned int>(additional_amount);
|
||||
assert((ulen % mFrameSize) == 0);
|
||||
|
||||
if(ulen > mBuffer.size())
|
||||
{
|
||||
mBuffer.resize(ulen);
|
||||
std::fill(mBuffer.begin(), mBuffer.end(), (mDevice->FmtType == DevFmtUByte)
|
||||
? std::byte{0x80} : std::byte{});
|
||||
}
|
||||
|
||||
mDevice->renderSamples(mBuffer.data(), ulen / mFrameSize, mNumChannels);
|
||||
SDL_PutAudioStreamData(stream, mBuffer.data(), additional_amount);
|
||||
}
|
||||
|
||||
void Sdl3Backend::open(std::string_view name)
|
||||
{
|
||||
const auto defaultDeviceName = getDefaultDeviceName();
|
||||
if(name.empty() || name == defaultDeviceName)
|
||||
{
|
||||
name = defaultDeviceName;
|
||||
mDeviceID = DefaultPlaybackDeviceID;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(gPlaybackDevices.empty())
|
||||
EnumeratePlaybackDevices();
|
||||
|
||||
const auto iter = std::find_if(gPlaybackDevices.cbegin(), gPlaybackDevices.cend(),
|
||||
[name](const DeviceEntry &entry) { return name == entry.mName; });
|
||||
if(iter == gPlaybackDevices.cend())
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "No device named {}", name};
|
||||
|
||||
mDeviceID = iter->mPhysDeviceID;
|
||||
}
|
||||
|
||||
mStream = SDL_OpenAudioDeviceStream(mDeviceID, nullptr, nullptr, nullptr);
|
||||
if(!mStream)
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "{}", SDL_GetError()};
|
||||
|
||||
auto have = SDL_AudioSpec{};
|
||||
auto update_size = int{};
|
||||
if(SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(mStream), &have, &update_size))
|
||||
{
|
||||
auto devtype = mDevice->FmtType;
|
||||
switch(have.format)
|
||||
{
|
||||
case SDL_AUDIO_U8: devtype = DevFmtUByte; break;
|
||||
case SDL_AUDIO_S8: devtype = DevFmtByte; break;
|
||||
case SDL_AUDIO_S16: devtype = DevFmtShort; break;
|
||||
case SDL_AUDIO_S32: devtype = DevFmtInt; break;
|
||||
case SDL_AUDIO_F32: devtype = DevFmtFloat; break;
|
||||
default: break;
|
||||
}
|
||||
mDevice->FmtType = devtype;
|
||||
|
||||
if(have.freq >= int{MinOutputRate} && have.freq <= int{MaxOutputRate})
|
||||
mDevice->mSampleRate = static_cast<uint>(have.freq);
|
||||
|
||||
/* SDL guarantees these layouts for the given channel count. */
|
||||
if(have.channels == 8)
|
||||
mDevice->FmtChans = DevFmtX71;
|
||||
else if(have.channels == 7)
|
||||
mDevice->FmtChans = DevFmtX61;
|
||||
else if(have.channels == 6)
|
||||
mDevice->FmtChans = DevFmtX51;
|
||||
else if(have.channels == 4)
|
||||
mDevice->FmtChans = DevFmtQuad;
|
||||
else if(have.channels >= 2)
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
else if(have.channels == 1)
|
||||
mDevice->FmtChans = DevFmtMono;
|
||||
mDevice->mAmbiOrder = 0;
|
||||
|
||||
mNumChannels = static_cast<uint>(have.channels);
|
||||
mFrameSize = mDevice->bytesFromFmt() * mNumChannels;
|
||||
|
||||
if(update_size >= 64)
|
||||
{
|
||||
/* We have to assume the total buffer size is just twice the update
|
||||
* size. SDL doesn't tell us the full end-to-end buffer latency.
|
||||
*/
|
||||
mDevice->mUpdateSize = static_cast<uint>(update_size);
|
||||
mDevice->mBufferSize = mDevice->mUpdateSize*2u;
|
||||
}
|
||||
else
|
||||
ERR("Invalid update size from SDL stream: {}", update_size);
|
||||
}
|
||||
else
|
||||
ERR("Failed to get format from SDL stream: {}", SDL_GetError());
|
||||
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
auto Sdl3Backend::reset() -> bool
|
||||
{
|
||||
static constexpr auto callback = [](void *ptr, SDL_AudioStream *stream, int additional_amount,
|
||||
int total_amount) noexcept
|
||||
{
|
||||
return static_cast<Sdl3Backend*>(ptr)->audioCallback(stream, additional_amount,
|
||||
total_amount);
|
||||
};
|
||||
|
||||
if(mStream)
|
||||
SDL_DestroyAudioStream(mStream);
|
||||
mStream = nullptr;
|
||||
|
||||
mBuffer.clear();
|
||||
mBuffer.shrink_to_fit();
|
||||
|
||||
auto want = SDL_AudioSpec{};
|
||||
if(!SDL_GetAudioDeviceFormat(mDeviceID, &want, nullptr))
|
||||
ERR("Failed to get device format: {}", SDL_GetError());
|
||||
|
||||
if(mDevice->Flags.test(FrequencyRequest) || want.freq < int{MinOutputRate})
|
||||
want.freq = static_cast<int>(mDevice->mSampleRate);
|
||||
if(mDevice->Flags.test(SampleTypeRequest)
|
||||
|| !(want.format == SDL_AUDIO_U8 || want.format == SDL_AUDIO_S8
|
||||
|| want.format == SDL_AUDIO_S16 || want.format == SDL_AUDIO_S32
|
||||
|| want.format == SDL_AUDIO_F32))
|
||||
{
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtUByte: want.format = SDL_AUDIO_U8; break;
|
||||
case DevFmtByte: want.format = SDL_AUDIO_S8; break;
|
||||
case DevFmtUShort: [[fallthrough]];
|
||||
case DevFmtShort: want.format = SDL_AUDIO_S16; break;
|
||||
case DevFmtUInt: [[fallthrough]];
|
||||
case DevFmtInt: want.format = SDL_AUDIO_S32; break;
|
||||
case DevFmtFloat: want.format = SDL_AUDIO_F32; break;
|
||||
}
|
||||
}
|
||||
if(mDevice->Flags.test(ChannelsRequest) || want.channels < 1)
|
||||
want.channels = static_cast<int>(std::min<uint>(mDevice->channelsFromFmt(),
|
||||
std::numeric_limits<int>::max()));
|
||||
|
||||
mStream = SDL_OpenAudioDeviceStream(mDeviceID, &want, callback, this);
|
||||
if(!mStream)
|
||||
{
|
||||
/* If creating the stream failed, try again without a specific format. */
|
||||
mStream = SDL_OpenAudioDeviceStream(mDeviceID, nullptr, callback, this);
|
||||
if(!mStream)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to recreate stream: {}", SDL_GetError()};
|
||||
}
|
||||
|
||||
auto update_size = int{};
|
||||
auto have = SDL_AudioSpec{};
|
||||
SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(mStream), &have, &update_size);
|
||||
|
||||
have = SDL_AudioSpec{};
|
||||
if(!SDL_GetAudioStreamFormat(mStream, &have, nullptr))
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to get stream format: {}", SDL_GetError()};
|
||||
|
||||
if(!mDevice->Flags.test(ChannelsRequest)
|
||||
|| (static_cast<uint>(have.channels) != mDevice->channelsFromFmt()
|
||||
&& !(mDevice->FmtChans == DevFmtStereo && have.channels >= 2)))
|
||||
{
|
||||
/* SDL guarantees these layouts for the given channel count. */
|
||||
if(have.channels == 8)
|
||||
mDevice->FmtChans = DevFmtX71;
|
||||
else if(have.channels == 7)
|
||||
mDevice->FmtChans = DevFmtX61;
|
||||
else if(have.channels == 6)
|
||||
mDevice->FmtChans = DevFmtX51;
|
||||
else if(have.channels == 4)
|
||||
mDevice->FmtChans = DevFmtQuad;
|
||||
else if(have.channels >= 2)
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
else if(have.channels == 1)
|
||||
mDevice->FmtChans = DevFmtMono;
|
||||
else
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Unhandled SDL channel count: {}", have.channels};
|
||||
mDevice->mAmbiOrder = 0;
|
||||
}
|
||||
mNumChannels = static_cast<uint>(have.channels);
|
||||
|
||||
switch(have.format)
|
||||
{
|
||||
case SDL_AUDIO_U8: mDevice->FmtType = DevFmtUByte; break;
|
||||
case SDL_AUDIO_S8: mDevice->FmtType = DevFmtByte; break;
|
||||
case SDL_AUDIO_S16: mDevice->FmtType = DevFmtShort; break;
|
||||
case SDL_AUDIO_S32: mDevice->FmtType = DevFmtInt; break;
|
||||
case SDL_AUDIO_F32: mDevice->FmtType = DevFmtFloat; break;
|
||||
default:
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Unhandled SDL format: {:#04x}", al::to_underlying(have.format)};
|
||||
}
|
||||
|
||||
mFrameSize = mDevice->bytesFromFmt() * mNumChannels;
|
||||
|
||||
if(have.freq < int{MinOutputRate})
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Unhandled SDL sample rate: {}", have.freq};
|
||||
mDevice->mSampleRate = static_cast<uint>(have.freq);
|
||||
|
||||
if(update_size >= 64)
|
||||
{
|
||||
mDevice->mUpdateSize = static_cast<uint>(update_size);
|
||||
mDevice->mBufferSize = mDevice->mUpdateSize*2u;
|
||||
|
||||
mBuffer.resize(size_t{mDevice->mUpdateSize} * mFrameSize);
|
||||
std::fill(mBuffer.begin(), mBuffer.end(), (mDevice->FmtType == DevFmtUByte)
|
||||
? std::byte{0x80} : std::byte{});
|
||||
}
|
||||
else
|
||||
ERR("Invalid update size from SDL stream: {}", update_size);
|
||||
|
||||
setDefaultWFXChannelOrder();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Sdl3Backend::start()
|
||||
{ SDL_ResumeAudioStreamDevice(mStream); }
|
||||
|
||||
void Sdl3Backend::stop()
|
||||
{ SDL_PauseAudioStreamDevice(mStream); }
|
||||
|
||||
} // namespace
|
||||
|
||||
auto SDL3BackendFactory::getFactory() -> BackendFactory&
|
||||
{
|
||||
static SDL3BackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
|
||||
auto SDL3BackendFactory::init() -> bool
|
||||
{
|
||||
if(!SDL_InitSubSystem(SDL_INIT_AUDIO))
|
||||
return false;
|
||||
TRACE("Current SDL3 audio driver: \"{}\"", SDL_GetCurrentAudioDriver());
|
||||
return true;
|
||||
}
|
||||
|
||||
auto SDL3BackendFactory::querySupport(BackendType type) -> bool
|
||||
{ return type == BackendType::Playback; }
|
||||
|
||||
auto SDL3BackendFactory::enumerate(BackendType type) -> std::vector<std::string>
|
||||
{
|
||||
auto outnames = std::vector<std::string>{};
|
||||
|
||||
if(type != BackendType::Playback)
|
||||
return outnames;
|
||||
|
||||
EnumeratePlaybackDevices();
|
||||
outnames.reserve(gPlaybackDevices.size()+1);
|
||||
outnames.emplace_back(getDefaultDeviceName());
|
||||
std::transform(gPlaybackDevices.begin(), gPlaybackDevices.end(), std::back_inserter(outnames),
|
||||
std::mem_fn(&DeviceEntry::mName));
|
||||
|
||||
return outnames;
|
||||
}
|
||||
|
||||
auto SDL3BackendFactory::createBackend(DeviceBase *device, BackendType type) -> BackendPtr
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new Sdl3Backend{device}};
|
||||
return nullptr;
|
||||
}
|
||||
19
Engine/lib/openal-soft/alc/backends/sdl3.h
Normal file
19
Engine/lib/openal-soft/alc/backends/sdl3.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef BACKENDS_SDL3_H
|
||||
#define BACKENDS_SDL3_H
|
||||
|
||||
#include "base.h"
|
||||
|
||||
struct SDL3BackendFactory final : public BackendFactory {
|
||||
public:
|
||||
auto init() -> bool final;
|
||||
|
||||
auto querySupport(BackendType type) -> bool final;
|
||||
|
||||
auto enumerate(BackendType type) -> std::vector<std::string> final;
|
||||
|
||||
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
|
||||
|
||||
static auto getFactory() -> BackendFactory&;
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_SDL3_H */
|
||||
|
|
@ -20,27 +20,23 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "sndio.h"
|
||||
#include "sndio.hpp"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <poll.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 <sndio.h> /* NOLINT(*-duplicate-include) Not the same header. */
|
||||
#include <sndio.h>
|
||||
|
||||
|
||||
namespace {
|
||||
|
|
@ -56,7 +52,7 @@ struct SioPar : public sio_par {
|
|||
};
|
||||
|
||||
struct SndioPlayback final : public BackendBase {
|
||||
SndioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit SndioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~SndioPlayback() override;
|
||||
|
||||
int mixerProc();
|
||||
|
|
@ -102,7 +98,7 @@ int SndioPlayback::mixerProc()
|
|||
size_t wrote{sio_write(mSndHandle, buffer.data(), buffer.size())};
|
||||
if(wrote > buffer.size() || wrote == 0)
|
||||
{
|
||||
ERR("sio_write failed: 0x%" PRIx64 "\n", wrote);
|
||||
ERR("sio_write failed: {:#x}", wrote);
|
||||
mDevice->handleDisconnect("Failed to write playback samples");
|
||||
break;
|
||||
}
|
||||
|
|
@ -119,8 +115,8 @@ void SndioPlayback::open(std::string_view 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()};
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
|
||||
name};
|
||||
|
||||
sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)};
|
||||
if(!sndHandle)
|
||||
|
|
@ -130,7 +126,7 @@ void SndioPlayback::open(std::string_view name)
|
|||
sio_close(mSndHandle);
|
||||
mSndHandle = sndHandle;
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
bool SndioPlayback::reset()
|
||||
|
|
@ -172,12 +168,12 @@ bool SndioPlayback::reset()
|
|||
par.le = SIO_LE_NATIVE;
|
||||
par.msb = 1;
|
||||
|
||||
par.rate = mDevice->Frequency;
|
||||
par.rate = mDevice->mSampleRate;
|
||||
par.pchan = mDevice->channelsFromFmt();
|
||||
|
||||
par.round = mDevice->UpdateSize;
|
||||
par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize;
|
||||
if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize;
|
||||
par.round = mDevice->mUpdateSize;
|
||||
par.appbufsz = mDevice->mBufferSize - mDevice->mUpdateSize;
|
||||
if(!par.appbufsz) par.appbufsz = mDevice->mUpdateSize;
|
||||
|
||||
try {
|
||||
if(!sio_setpar(mSndHandle, &par))
|
||||
|
|
@ -191,10 +187,10 @@ bool SndioPlayback::reset()
|
|||
|
||||
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"};
|
||||
"{}-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};
|
||||
"MSB-padded samples not supported ({} of {} bits)", par.bits, par.bps*8};
|
||||
if(par.pchan < 1)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"No playback channels on device"};
|
||||
|
|
@ -217,24 +213,24 @@ bool SndioPlayback::reset()
|
|||
mDevice->FmtType = (par.sig==1) ? DevFmtInt : DevFmtUInt;
|
||||
else
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Unhandled sample format: %s %u-bit", (par.sig?"signed":"unsigned"), par.bps*8};
|
||||
"Unhandled sample format: {} {}-bit", (par.sig?"signed":"unsigned"), par.bps*8};
|
||||
|
||||
mFrameStep = par.pchan;
|
||||
if(par.pchan != mDevice->channelsFromFmt())
|
||||
{
|
||||
WARN("Got %u channel%s for %s\n", par.pchan, (par.pchan==1)?"":"s",
|
||||
WARN("Got {} channel{} for {}", par.pchan, (par.pchan==1)?"":"s",
|
||||
DevFmtChannelsString(mDevice->FmtChans));
|
||||
if(par.pchan < 2) mDevice->FmtChans = DevFmtMono;
|
||||
else mDevice->FmtChans = DevFmtStereo;
|
||||
}
|
||||
mDevice->Frequency = par.rate;
|
||||
mDevice->mSampleRate = par.rate;
|
||||
|
||||
setDefaultChannelOrder();
|
||||
|
||||
mDevice->UpdateSize = par.round;
|
||||
mDevice->BufferSize = par.bufsz + par.round;
|
||||
mDevice->mUpdateSize = par.round;
|
||||
mDevice->mBufferSize = par.bufsz + par.round;
|
||||
|
||||
mBuffer.resize(size_t{mDevice->UpdateSize} * par.pchan*par.bps);
|
||||
mBuffer.resize(size_t{mDevice->mUpdateSize} * par.pchan*par.bps);
|
||||
if(par.sig == 1)
|
||||
std::fill(mBuffer.begin(), mBuffer.end(), std::byte{});
|
||||
else if(par.bits == 8)
|
||||
|
|
@ -254,12 +250,12 @@ void SndioPlayback::start()
|
|||
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&SndioPlayback::mixerProc), this};
|
||||
mThread = std::thread{&SndioPlayback::mixerProc, this};
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
sio_stop(mSndHandle);
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start mixing thread: %s", e.what()};
|
||||
"Failed to start mixing thread: {}", e.what()};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -270,7 +266,7 @@ void SndioPlayback::stop()
|
|||
mThread.join();
|
||||
|
||||
if(!sio_stop(mSndHandle))
|
||||
ERR("Error stopping device\n");
|
||||
ERR("Error stopping device");
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -280,7 +276,7 @@ void SndioPlayback::stop()
|
|||
* capture buffer sizes apps may request.
|
||||
*/
|
||||
struct SndioCapture final : public BackendBase {
|
||||
SndioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit SndioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~SndioCapture() override;
|
||||
|
||||
int recordProc();
|
||||
|
|
@ -316,7 +312,7 @@ int SndioCapture::recordProc()
|
|||
int nfds_pre{sio_nfds(mSndHandle)};
|
||||
if(nfds_pre <= 0)
|
||||
{
|
||||
mDevice->handleDisconnect("Incorrect return value from sio_nfds(): %d", nfds_pre);
|
||||
mDevice->handleDisconnect("Incorrect return value from sio_nfds(): {}", nfds_pre);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -329,15 +325,14 @@ int SndioCapture::recordProc()
|
|||
const int nfds{sio_pollfd(mSndHandle, fds.data(), POLLIN)};
|
||||
if(nfds <= 0)
|
||||
{
|
||||
mDevice->handleDisconnect("Failed to get polling fds: %d", nfds);
|
||||
mDevice->handleDisconnect("Failed to get polling fds: {}", nfds);
|
||||
break;
|
||||
}
|
||||
int pollres{::poll(fds.data(), fds.size(), 2000)};
|
||||
if(pollres < 0)
|
||||
{
|
||||
if(errno == EINTR) continue;
|
||||
mDevice->handleDisconnect("Poll error: %s",
|
||||
std::generic_category().message(errno).c_str());
|
||||
mDevice->handleDisconnect("Poll error: {}", std::generic_category().message(errno));
|
||||
break;
|
||||
}
|
||||
if(pollres == 0)
|
||||
|
|
@ -353,7 +348,7 @@ int SndioCapture::recordProc()
|
|||
continue;
|
||||
|
||||
auto data = mRing->getWriteVector();
|
||||
al::span<std::byte> buffer{data.first.buf, data.first.len*frameSize};
|
||||
al::span<std::byte> buffer{data[0].buf, data[0].len*frameSize};
|
||||
while(!buffer.empty())
|
||||
{
|
||||
size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())};
|
||||
|
|
@ -361,8 +356,8 @@ int SndioCapture::recordProc()
|
|||
break;
|
||||
if(got > buffer.size())
|
||||
{
|
||||
ERR("sio_read failed: 0x%" PRIx64 "\n", got);
|
||||
mDevice->handleDisconnect("sio_read failed: 0x%" PRIx64, got);
|
||||
ERR("sio_read failed: {:#x}", got);
|
||||
mDevice->handleDisconnect("sio_read failed: {:#x}", got);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -371,7 +366,7 @@ int SndioCapture::recordProc()
|
|||
if(buffer.empty())
|
||||
{
|
||||
data = mRing->getWriteVector();
|
||||
buffer = {data.first.buf, data.first.len*frameSize};
|
||||
buffer = {data[0].buf, data[0].len*frameSize};
|
||||
}
|
||||
}
|
||||
if(buffer.empty())
|
||||
|
|
@ -391,8 +386,8 @@ void SndioCapture::open(std::string_view 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()};
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
|
||||
name};
|
||||
|
||||
mSndHandle = sio_open(nullptr, SIO_REC, true);
|
||||
if(mSndHandle == nullptr)
|
||||
|
|
@ -427,16 +422,16 @@ void SndioCapture::open(std::string_view name)
|
|||
break;
|
||||
case DevFmtFloat:
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
|
||||
"{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
|
||||
}
|
||||
par.bps = SIO_BPS(par.bits);
|
||||
par.le = SIO_LE_NATIVE;
|
||||
par.msb = 1;
|
||||
par.rchan = mDevice->channelsFromFmt();
|
||||
par.rate = mDevice->Frequency;
|
||||
par.rate = mDevice->mSampleRate;
|
||||
|
||||
par.appbufsz = std::max(mDevice->BufferSize, mDevice->Frequency/10u);
|
||||
par.round = std::min(par.appbufsz/2u, mDevice->Frequency/40u);
|
||||
par.appbufsz = std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u);
|
||||
par.round = std::min(par.appbufsz/2u, mDevice->mSampleRate/40u);
|
||||
|
||||
if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
|
|
@ -444,10 +439,10 @@ void SndioCapture::open(std::string_view name)
|
|||
|
||||
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"};
|
||||
"{}-endian samples not supported", par.le ? "Little" : "Big"};
|
||||
if(par.bits < par.bps*8 && !par.msb)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Padded samples not supported (got %u of %u bits)", par.bits, par.bps*8};
|
||||
"Padded samples not supported (got {} of {} bits)", par.bits, par.bps*8};
|
||||
|
||||
auto match_fmt = [](DevFmtType fmttype, const sio_par &p) -> bool
|
||||
{
|
||||
|
|
@ -459,19 +454,19 @@ void SndioCapture::open(std::string_view name)
|
|||
|| (fmttype == DevFmtUInt && p.bps == 4 && p.sig == 0);
|
||||
};
|
||||
if(!match_fmt(mDevice->FmtType, par) || mDevice->channelsFromFmt() != par.rchan
|
||||
|| mDevice->Frequency != par.rate)
|
||||
|| mDevice->mSampleRate != par.rate)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead",
|
||||
"Failed to set format {} {} {}hz, got {}{} {}-channel {}hz instead",
|
||||
DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans),
|
||||
mDevice->Frequency, par.sig?'s':'u', par.bps*8, par.rchan, par.rate};
|
||||
mDevice->mSampleRate, par.sig?'s':'u', par.bps*8, par.rchan, par.rate};
|
||||
|
||||
mRing = RingBuffer::Create(mDevice->BufferSize, size_t{par.bps}*par.rchan, false);
|
||||
mDevice->BufferSize = static_cast<uint>(mRing->writeSpace());
|
||||
mDevice->UpdateSize = par.round;
|
||||
mRing = RingBuffer::Create(mDevice->mBufferSize, size_t{par.bps}*par.rchan, false);
|
||||
mDevice->mBufferSize = static_cast<uint>(mRing->writeSpace());
|
||||
mDevice->mUpdateSize = par.round;
|
||||
|
||||
setDefaultChannelOrder();
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
void SndioCapture::start()
|
||||
|
|
@ -481,12 +476,12 @@ void SndioCapture::start()
|
|||
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&SndioCapture::recordProc), this};
|
||||
mThread = std::thread{&SndioCapture::recordProc, this};
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
sio_stop(mSndHandle);
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start capture thread: %s", e.what()};
|
||||
"Failed to start capture thread: {}", e.what()};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -497,7 +492,7 @@ void SndioCapture::stop()
|
|||
mThread.join();
|
||||
|
||||
if(!sio_stop(mSndHandle))
|
||||
ERR("Error stopping device\n");
|
||||
ERR("Error stopping device");
|
||||
}
|
||||
|
||||
void SndioCapture::captureSamples(std::byte *buffer, uint samples)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef BACKENDS_SNDIO_H
|
||||
#define BACKENDS_SNDIO_H
|
||||
#ifndef BACKENDS_SNDIO_HPP
|
||||
#define BACKENDS_SNDIO_HPP
|
||||
|
||||
#include "base.h"
|
||||
|
||||
|
|
@ -16,4 +16,4 @@ public:
|
|||
static auto getFactory() -> BackendFactory&;
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_SNDIO_H */
|
||||
#endif /* BACKENDS_SNDIO_HPP */
|
||||
|
|
@ -60,7 +60,7 @@ std::string solaris_driver{"/dev/audio"};
|
|||
|
||||
|
||||
struct SolarisBackend final : public BackendBase {
|
||||
SolarisBackend(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit SolarisBackend(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~SolarisBackend() override;
|
||||
|
||||
int mixerProc();
|
||||
|
|
@ -106,13 +106,13 @@ int SolarisBackend::mixerProc()
|
|||
{
|
||||
if(errno == EINTR || errno == EAGAIN)
|
||||
continue;
|
||||
ERR("poll failed: %s\n", strerror(errno));
|
||||
mDevice->handleDisconnect("Failed to wait for playback buffer: %s", strerror(errno));
|
||||
ERR("poll failed: {}", strerror(errno));
|
||||
mDevice->handleDisconnect("Failed to wait for playback buffer: {}", strerror(errno));
|
||||
break;
|
||||
}
|
||||
else if(pret == 0)
|
||||
{
|
||||
WARN("poll timeout\n");
|
||||
WARN("poll timeout");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -126,8 +126,8 @@ int SolarisBackend::mixerProc()
|
|||
{
|
||||
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
|
||||
continue;
|
||||
ERR("write failed: %s\n", strerror(errno));
|
||||
mDevice->handleDisconnect("Failed to write playback samples: %s", strerror(errno));
|
||||
ERR("write failed: {}", strerror(errno));
|
||||
mDevice->handleDisconnect("Failed to write playback samples: {}", strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -144,19 +144,19 @@ void SolarisBackend::open(std::string_view 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()};
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
|
||||
name};
|
||||
|
||||
int fd{::open(solaris_driver.c_str(), O_WRONLY)};
|
||||
if(fd == -1)
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s",
|
||||
solaris_driver.c_str(), strerror(errno)};
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Could not open {}: {}",
|
||||
solaris_driver, strerror(errno)};
|
||||
|
||||
if(mFd != -1)
|
||||
::close(mFd);
|
||||
mFd = fd;
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
bool SolarisBackend::reset()
|
||||
|
|
@ -164,7 +164,7 @@ bool SolarisBackend::reset()
|
|||
audio_info_t info;
|
||||
AUDIO_INITINFO(&info);
|
||||
|
||||
info.play.sample_rate = mDevice->Frequency;
|
||||
info.play.sample_rate = mDevice->mSampleRate;
|
||||
info.play.channels = mDevice->channelsFromFmt();
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
|
|
@ -187,11 +187,11 @@ bool SolarisBackend::reset()
|
|||
info.play.encoding = AUDIO_ENCODING_LINEAR;
|
||||
break;
|
||||
}
|
||||
info.play.buffer_size = mDevice->BufferSize * mDevice->frameSizeFromFmt();
|
||||
info.play.buffer_size = mDevice->mBufferSize * mDevice->frameSizeFromFmt();
|
||||
|
||||
if(ioctl(mFd, AUDIO_SETINFO, &info) < 0)
|
||||
{
|
||||
ERR("ioctl failed: %s\n", strerror(errno));
|
||||
ERR("ioctl failed: {}", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -203,7 +203,7 @@ bool SolarisBackend::reset()
|
|||
mDevice->FmtChans = DevFmtMono;
|
||||
else
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Got %u device channels", info.play.channels};
|
||||
"Got {} device channels", info.play.channels};
|
||||
}
|
||||
|
||||
if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8)
|
||||
|
|
@ -216,20 +216,20 @@ bool SolarisBackend::reset()
|
|||
mDevice->FmtType = DevFmtInt;
|
||||
else
|
||||
{
|
||||
ERR("Got unhandled sample type: %d (0x%x)\n", info.play.precision, info.play.encoding);
|
||||
ERR("Got unhandled sample type: {} ({:#x})", info.play.precision, info.play.encoding);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint frame_size{mDevice->bytesFromFmt() * info.play.channels};
|
||||
mFrameStep = info.play.channels;
|
||||
mDevice->Frequency = info.play.sample_rate;
|
||||
mDevice->BufferSize = info.play.buffer_size / frame_size;
|
||||
mDevice->mSampleRate = info.play.sample_rate;
|
||||
mDevice->mBufferSize = info.play.buffer_size / frame_size;
|
||||
/* How to get the actual period size/count? */
|
||||
mDevice->UpdateSize = mDevice->BufferSize / 2;
|
||||
mDevice->mUpdateSize = mDevice->mBufferSize / 2;
|
||||
|
||||
setDefaultChannelOrder();
|
||||
|
||||
mBuffer.resize(mDevice->UpdateSize * size_t{frame_size});
|
||||
mBuffer.resize(mDevice->mUpdateSize * size_t{frame_size});
|
||||
std::fill(mBuffer.begin(), mBuffer.end(), std::byte{});
|
||||
|
||||
return true;
|
||||
|
|
@ -239,11 +239,11 @@ void SolarisBackend::start()
|
|||
{
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&SolarisBackend::mixerProc), this};
|
||||
mThread = std::thread{&SolarisBackend::mixerProc, this};
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start mixing thread: %s", e.what()};
|
||||
"Failed to start mixing thread: {}", e.what()};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -254,7 +254,7 @@ void SolarisBackend::stop()
|
|||
mThread.join();
|
||||
|
||||
if(ioctl(mFd, AUDIO_DRAIN) < 0)
|
||||
ERR("Error draining device: %s\n", strerror(errno));
|
||||
ERR("Error draining device: {}", strerror(errno));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -30,7 +30,6 @@
|
|||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <system_error>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
|
@ -39,12 +38,9 @@
|
|||
#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"
|
||||
|
||||
|
||||
|
|
@ -99,7 +95,7 @@ void fwrite32le(uint val, FILE *f)
|
|||
|
||||
|
||||
struct WaveBackend final : public BackendBase {
|
||||
WaveBackend(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit WaveBackend(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~WaveBackend() override;
|
||||
|
||||
int mixerProc();
|
||||
|
|
@ -122,7 +118,7 @@ WaveBackend::~WaveBackend() = default;
|
|||
|
||||
int WaveBackend::mixerProc()
|
||||
{
|
||||
const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2};
|
||||
const milliseconds restTime{mDevice->mUpdateSize*1000/mDevice->mSampleRate / 2};
|
||||
|
||||
althrd_setname(GetMixerThreadName());
|
||||
|
||||
|
|
@ -137,17 +133,17 @@ int WaveBackend::mixerProc()
|
|||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
/* This converts from nanoseconds to nanosamples, then to samples. */
|
||||
int64_t avail{std::chrono::duration_cast<seconds>((now-start) *
|
||||
mDevice->Frequency).count()};
|
||||
if(avail-done < mDevice->UpdateSize)
|
||||
const auto avail = int64_t{std::chrono::duration_cast<seconds>((now-start) *
|
||||
mDevice->mSampleRate).count()};
|
||||
if(avail-done < mDevice->mUpdateSize)
|
||||
{
|
||||
std::this_thread::sleep_for(restTime);
|
||||
continue;
|
||||
}
|
||||
while(avail-done >= mDevice->UpdateSize)
|
||||
while(avail-done >= mDevice->mUpdateSize)
|
||||
{
|
||||
mDevice->renderSamples(mBuffer.data(), mDevice->UpdateSize, frameStep);
|
||||
done += mDevice->UpdateSize;
|
||||
mDevice->renderSamples(mBuffer.data(), mDevice->mUpdateSize, frameStep);
|
||||
done += mDevice->mUpdateSize;
|
||||
|
||||
if(al::endian::native != al::endian::little)
|
||||
{
|
||||
|
|
@ -170,10 +166,10 @@ int WaveBackend::mixerProc()
|
|||
}
|
||||
}
|
||||
|
||||
const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile.get())};
|
||||
if(fs < mDevice->UpdateSize || ferror(mFile.get()))
|
||||
const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->mUpdateSize, mFile.get())};
|
||||
if(fs < mDevice->mUpdateSize || ferror(mFile.get()))
|
||||
{
|
||||
ERR("Error writing to file\n");
|
||||
ERR("Error writing to file");
|
||||
mDevice->handleDisconnect("Failed to write playback samples");
|
||||
break;
|
||||
}
|
||||
|
|
@ -184,10 +180,10 @@ int WaveBackend::mixerProc()
|
|||
* and current time from growing too large, while maintaining the
|
||||
* correct number of samples to render.
|
||||
*/
|
||||
if(done >= mDevice->Frequency)
|
||||
if(done >= mDevice->mSampleRate)
|
||||
{
|
||||
seconds s{done/mDevice->Frequency};
|
||||
done %= mDevice->Frequency;
|
||||
seconds s{done/mDevice->mSampleRate};
|
||||
done %= mDevice->mSampleRate;
|
||||
start += s;
|
||||
}
|
||||
}
|
||||
|
|
@ -204,8 +200,8 @@ void WaveBackend::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()};
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
|
||||
name};
|
||||
|
||||
/* There's only one "device", so if it's already open, we're done. */
|
||||
if(mFile) return;
|
||||
|
|
@ -219,20 +215,14 @@ void WaveBackend::open(std::string_view name)
|
|||
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(), std::generic_category().message(errno).c_str()};
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '{}': {}",
|
||||
*fname, std::generic_category().message(errno)};
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
mDeviceName = name;
|
||||
}
|
||||
|
||||
bool WaveBackend::reset()
|
||||
{
|
||||
uint channels{0}, bytes{0}, chanmask{0};
|
||||
bool isbformat{false};
|
||||
|
||||
fseek(mFile.get(), 0, SEEK_SET);
|
||||
clearerr(mFile.get());
|
||||
|
||||
if(GetConfigValueBool({}, "wave", "bformat", false))
|
||||
{
|
||||
mDevice->FmtChans = DevFmtAmbi3D;
|
||||
|
|
@ -256,6 +246,8 @@ bool WaveBackend::reset()
|
|||
case DevFmtFloat:
|
||||
break;
|
||||
}
|
||||
auto chanmask = 0u;
|
||||
auto isbformat = false;
|
||||
switch(mDevice->FmtChans)
|
||||
{
|
||||
case DevFmtMono: chanmask = 0x04; break;
|
||||
|
|
@ -282,13 +274,24 @@ bool WaveBackend::reset()
|
|||
chanmask = 0;
|
||||
break;
|
||||
}
|
||||
bytes = mDevice->bytesFromFmt();
|
||||
channels = mDevice->channelsFromFmt();
|
||||
const auto bytes = mDevice->bytesFromFmt();
|
||||
const auto channels = mDevice->channelsFromFmt();
|
||||
|
||||
rewind(mFile.get());
|
||||
if(fseek(mFile.get(), 0, SEEK_CUR) != 0)
|
||||
{
|
||||
/* ESPIPE means the underlying file isn't seekable, which is fine for
|
||||
* piped output.
|
||||
*/
|
||||
if(auto errcode = errno; errcode != ESPIPE)
|
||||
{
|
||||
ERR("Failed to reset file offset: {} ({})", std::generic_category().message(errcode),
|
||||
errcode);
|
||||
}
|
||||
}
|
||||
clearerr(mFile.get());
|
||||
|
||||
fputs("RIFF", mFile.get());
|
||||
fwrite32le(0xFFFFFFFF, mFile.get()); // 'RIFF' header len; filled in at close
|
||||
fwrite32le(0xFFFFFFFF, mFile.get()); // 'RIFF' header len; filled in at stop
|
||||
|
||||
fputs("WAVE", mFile.get());
|
||||
|
||||
|
|
@ -300,9 +303,9 @@ bool WaveBackend::reset()
|
|||
// 16-bit val, channel count
|
||||
fwrite16le(static_cast<ushort>(channels), mFile.get());
|
||||
// 32-bit val, frequency
|
||||
fwrite32le(mDevice->Frequency, mFile.get());
|
||||
fwrite32le(mDevice->mSampleRate, mFile.get());
|
||||
// 32-bit val, bytes per second
|
||||
fwrite32le(mDevice->Frequency * channels * bytes, mFile.get());
|
||||
fwrite32le(mDevice->mSampleRate * channels * bytes, mFile.get());
|
||||
// 16-bit val, frame size
|
||||
fwrite16le(static_cast<ushort>(channels * bytes), mFile.get());
|
||||
// 16-bit val, bits per sample
|
||||
|
|
@ -319,18 +322,18 @@ bool WaveBackend::reset()
|
|||
(isbformat ? SUBTYPE_BFORMAT_PCM.data() : SUBTYPE_PCM.data()), 1, 16, mFile.get());
|
||||
|
||||
fputs("data", mFile.get());
|
||||
fwrite32le(0xFFFFFFFF, mFile.get()); // 'data' header len; filled in at close
|
||||
fwrite32le(0xFFFFFFFF, mFile.get()); // 'data' header len; filled in at stop
|
||||
|
||||
if(ferror(mFile.get()))
|
||||
{
|
||||
ERR("Error writing header: %s\n", std::generic_category().message(errno).c_str());
|
||||
ERR("Error writing header: {}", std::generic_category().message(errno));
|
||||
return false;
|
||||
}
|
||||
mDataStart = ftell(mFile.get());
|
||||
|
||||
setDefaultWFXChannelOrder();
|
||||
|
||||
const uint bufsize{mDevice->frameSizeFromFmt() * mDevice->UpdateSize};
|
||||
const uint bufsize{mDevice->frameSizeFromFmt() * mDevice->mUpdateSize};
|
||||
mBuffer.resize(bufsize);
|
||||
|
||||
return true;
|
||||
|
|
@ -339,14 +342,14 @@ bool WaveBackend::reset()
|
|||
void WaveBackend::start()
|
||||
{
|
||||
if(mDataStart > 0 && fseek(mFile.get(), 0, SEEK_END) != 0)
|
||||
WARN("Failed to seek on output file\n");
|
||||
WARN("Failed to seek on output file");
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&WaveBackend::mixerProc), this};
|
||||
mThread = std::thread{&WaveBackend::mixerProc, this};
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start mixing thread: %s", e.what()};
|
||||
"Failed to start mixing thread: {}", e.what()};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,14 +36,14 @@
|
|||
#include <vector>
|
||||
#include <string>
|
||||
#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 "fmt/core.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "strutils.h"
|
||||
#include "vector.h"
|
||||
|
|
@ -54,9 +54,6 @@
|
|||
|
||||
namespace {
|
||||
|
||||
#define DEVNAME_HEAD "OpenAL Soft on "
|
||||
|
||||
|
||||
std::vector<std::string> PlaybackDevices;
|
||||
std::vector<std::string> CaptureDevices;
|
||||
|
||||
|
|
@ -76,19 +73,15 @@ void ProbePlaybackDevices()
|
|||
WAVEOUTCAPSW WaveCaps{};
|
||||
if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
|
||||
{
|
||||
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(std::data(WaveCaps.szPname))};
|
||||
const auto basename = wstr_to_utf8(std::data(WaveCaps.szPname));
|
||||
|
||||
int count{1};
|
||||
std::string newname{basename};
|
||||
auto count = 1;
|
||||
auto newname = basename;
|
||||
while(checkName(PlaybackDevices, newname))
|
||||
{
|
||||
newname = basename;
|
||||
newname += " #";
|
||||
newname += std::to_string(++count);
|
||||
}
|
||||
newname = fmt::format("{} #{}", basename, ++count);
|
||||
dname = std::move(newname);
|
||||
|
||||
TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i);
|
||||
TRACE("Got device \"{}\", ID {}", dname, i);
|
||||
}
|
||||
PlaybackDevices.emplace_back(std::move(dname));
|
||||
}
|
||||
|
|
@ -107,19 +100,15 @@ void ProbeCaptureDevices()
|
|||
WAVEINCAPSW WaveCaps{};
|
||||
if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
|
||||
{
|
||||
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(std::data(WaveCaps.szPname))};
|
||||
const auto basename = wstr_to_utf8(std::data(WaveCaps.szPname));
|
||||
|
||||
int count{1};
|
||||
std::string newname{basename};
|
||||
auto count = 1;
|
||||
auto newname = basename;
|
||||
while(checkName(CaptureDevices, newname))
|
||||
{
|
||||
newname = basename;
|
||||
newname += " #";
|
||||
newname += std::to_string(++count);
|
||||
}
|
||||
newname = fmt::format("{} #{}", basename, ++count);
|
||||
dname = std::move(newname);
|
||||
|
||||
TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i);
|
||||
TRACE("Got device \"{}\", ID {}", dname, i);
|
||||
}
|
||||
CaptureDevices.emplace_back(std::move(dname));
|
||||
}
|
||||
|
|
@ -127,7 +116,7 @@ void ProbeCaptureDevices()
|
|||
|
||||
|
||||
struct WinMMPlayback final : public BackendBase {
|
||||
WinMMPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit WinMMPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~WinMMPlayback() override;
|
||||
|
||||
void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
|
||||
|
|
@ -194,7 +183,7 @@ FORCE_ALIGN int WinMMPlayback::mixerProc()
|
|||
WAVEHDR &waveHdr = mWaveBuffer[widx];
|
||||
if(++widx == mWaveBuffer.size()) widx = 0;
|
||||
|
||||
mDevice->renderSamples(waveHdr.lpData, mDevice->UpdateSize, mFormat.nChannels);
|
||||
mDevice->renderSamples(waveHdr.lpData, mDevice->mUpdateSize, mFormat.nChannels);
|
||||
mWritable.fetch_sub(1, std::memory_order_acq_rel);
|
||||
waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR));
|
||||
} while(--todo);
|
||||
|
|
@ -215,8 +204,8 @@ void WinMMPlayback::open(std::string_view name)
|
|||
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",
|
||||
al::sizei(name), name.data()};
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
|
||||
name};
|
||||
auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter));
|
||||
|
||||
DevFmtType fmttype{mDevice->FmtType};
|
||||
|
|
@ -238,7 +227,7 @@ void WinMMPlayback::open(std::string_view name)
|
|||
}
|
||||
format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
|
||||
format.nBlockAlign = static_cast<WORD>(format.wBitsPerSample * format.nChannels / 8);
|
||||
format.nSamplesPerSec = mDevice->Frequency;
|
||||
format.nSamplesPerSec = mDevice->mSampleRate;
|
||||
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
|
||||
format.cbSize = 0;
|
||||
|
||||
|
|
@ -248,7 +237,7 @@ void WinMMPlayback::open(std::string_view name)
|
|||
if(res == MMSYSERR_NOERROR) break;
|
||||
|
||||
if(fmttype != DevFmtFloat)
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u",
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: {}",
|
||||
res};
|
||||
|
||||
fmttype = DevFmtShort;
|
||||
|
|
@ -256,16 +245,16 @@ void WinMMPlayback::open(std::string_view name)
|
|||
|
||||
mFormat = format;
|
||||
|
||||
mDevice->DeviceName = PlaybackDevices[DeviceID];
|
||||
mDeviceName = PlaybackDevices[DeviceID];
|
||||
}
|
||||
|
||||
bool WinMMPlayback::reset()
|
||||
{
|
||||
mDevice->BufferSize = static_cast<uint>(uint64_t{mDevice->BufferSize} *
|
||||
mFormat.nSamplesPerSec / mDevice->Frequency);
|
||||
mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3u;
|
||||
mDevice->UpdateSize = mDevice->BufferSize / 4;
|
||||
mDevice->Frequency = mFormat.nSamplesPerSec;
|
||||
mDevice->mBufferSize = static_cast<uint>(uint64_t{mDevice->mBufferSize} *
|
||||
mFormat.nSamplesPerSec / mDevice->mSampleRate);
|
||||
mDevice->mBufferSize = (mDevice->mBufferSize+3) & ~0x3u;
|
||||
mDevice->mUpdateSize = mDevice->mBufferSize / 4;
|
||||
mDevice->mSampleRate = mFormat.nSamplesPerSec;
|
||||
|
||||
if(mFormat.wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
|
||||
{
|
||||
|
|
@ -273,7 +262,7 @@ bool WinMMPlayback::reset()
|
|||
mDevice->FmtType = DevFmtFloat;
|
||||
else
|
||||
{
|
||||
ERR("Unhandled IEEE float sample depth: %d\n", mFormat.wBitsPerSample);
|
||||
ERR("Unhandled IEEE float sample depth: {}", mFormat.wBitsPerSample);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -285,13 +274,13 @@ bool WinMMPlayback::reset()
|
|||
mDevice->FmtType = DevFmtUByte;
|
||||
else
|
||||
{
|
||||
ERR("Unhandled PCM sample depth: %d\n", mFormat.wBitsPerSample);
|
||||
ERR("Unhandled PCM sample depth: {}", mFormat.wBitsPerSample);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ERR("Unhandled format tag: 0x%04x\n", mFormat.wFormatTag);
|
||||
ERR("Unhandled format tag: {:#04x}", as_unsigned(mFormat.wFormatTag));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -301,12 +290,12 @@ bool WinMMPlayback::reset()
|
|||
mDevice->FmtChans = DevFmtMono;
|
||||
else
|
||||
{
|
||||
ERR("Unhandled channel count: %d\n", mFormat.nChannels);
|
||||
ERR("Unhandled channel count: {}", mFormat.nChannels);
|
||||
return false;
|
||||
}
|
||||
setDefaultWFXChannelOrder();
|
||||
|
||||
const uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()};
|
||||
const uint BufferSize{mDevice->mUpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()};
|
||||
|
||||
decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer);
|
||||
mWaveBuffer[0] = WAVEHDR{};
|
||||
|
|
@ -331,11 +320,11 @@ void WinMMPlayback::start()
|
|||
mWritable.store(static_cast<uint>(mWaveBuffer.size()), std::memory_order_release);
|
||||
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&WinMMPlayback::mixerProc), this};
|
||||
mThread = std::thread{&WinMMPlayback::mixerProc, this};
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start mixing thread: %s", e.what()};
|
||||
"Failed to start mixing thread: {}", e.what()};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -354,7 +343,7 @@ void WinMMPlayback::stop()
|
|||
|
||||
|
||||
struct WinMMCapture final : public BackendBase {
|
||||
WinMMCapture(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
explicit WinMMCapture(DeviceBase *device) noexcept : BackendBase{device} { }
|
||||
~WinMMCapture() override;
|
||||
|
||||
void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
|
||||
|
|
@ -446,8 +435,8 @@ void WinMMCapture::open(std::string_view name)
|
|||
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",
|
||||
al::sizei(name), name.data()};
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
|
||||
name};
|
||||
auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter));
|
||||
|
||||
switch(mDevice->FmtChans)
|
||||
|
|
@ -464,7 +453,7 @@ void WinMMCapture::open(std::string_view name)
|
|||
case DevFmtX7144:
|
||||
case DevFmtX3D71:
|
||||
case DevFmtAmbi3D:
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported",
|
||||
DevFmtChannelsString(mDevice->FmtChans)};
|
||||
}
|
||||
|
||||
|
|
@ -479,7 +468,7 @@ void WinMMCapture::open(std::string_view name)
|
|||
case DevFmtByte:
|
||||
case DevFmtUShort:
|
||||
case DevFmtUInt:
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported",
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "{} samples not supported",
|
||||
DevFmtTypeString(mDevice->FmtType)};
|
||||
}
|
||||
|
||||
|
|
@ -489,7 +478,7 @@ void WinMMCapture::open(std::string_view name)
|
|||
mFormat.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
|
||||
mFormat.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
|
||||
mFormat.nBlockAlign = static_cast<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8);
|
||||
mFormat.nSamplesPerSec = mDevice->Frequency;
|
||||
mFormat.nSamplesPerSec = mDevice->mSampleRate;
|
||||
mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign;
|
||||
mFormat.cbSize = 0;
|
||||
|
||||
|
|
@ -497,7 +486,7 @@ void WinMMCapture::open(std::string_view name)
|
|||
reinterpret_cast<DWORD_PTR>(&WinMMCapture::waveInProcC),
|
||||
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
|
||||
if(res != MMSYSERR_NOERROR)
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "waveInOpen failed: %u", res};
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "waveInOpen failed: {}", res};
|
||||
|
||||
// Ensure each buffer is 50ms each
|
||||
DWORD BufferSize{mFormat.nAvgBytesPerSec / 20u};
|
||||
|
|
@ -505,7 +494,7 @@ void WinMMCapture::open(std::string_view name)
|
|||
|
||||
// Allocate circular memory buffer for the captured audio
|
||||
// Make sure circular buffer is at least 100ms in size
|
||||
const auto CapturedDataSize = std::max<size_t>(mDevice->BufferSize,
|
||||
const auto CapturedDataSize = std::max<size_t>(mDevice->mBufferSize,
|
||||
BufferSize*mWaveBuffer.size());
|
||||
|
||||
mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false);
|
||||
|
|
@ -521,7 +510,7 @@ void WinMMCapture::open(std::string_view name)
|
|||
mWaveBuffer[i].dwBufferLength = mWaveBuffer[i-1].dwBufferLength;
|
||||
}
|
||||
|
||||
mDevice->DeviceName = CaptureDevices[DeviceID];
|
||||
mDeviceName = CaptureDevices[DeviceID];
|
||||
}
|
||||
|
||||
void WinMMCapture::start()
|
||||
|
|
@ -534,13 +523,13 @@ void WinMMCapture::start()
|
|||
}
|
||||
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&WinMMCapture::captureProc), this};
|
||||
mThread = std::thread{&WinMMCapture::captureProc, this};
|
||||
|
||||
waveInStart(mInHdl);
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start recording thread: %s", e.what()};
|
||||
"Failed to start recording thread: {}", e.what()};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue