update OpenAL-Soft to 1.24.3.

This commit is contained in:
Sasha Szpakowski
2025-05-03 12:51:37 -03:00
parent 375c6f88cd
commit 5e4f3241ac
322 changed files with 54386 additions and 12885 deletions
File diff suppressed because it is too large Load Diff
+93 -61
View File
@@ -34,7 +34,6 @@
#include <array>
#include <cctype>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <istream>
#include <limits>
@@ -47,9 +46,10 @@
#include "alstring.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "filesystem.h"
#include "strutils.h"
#if defined(ALSOFT_UWP)
#if ALSOFT_UWP
#include <winrt/Windows.Media.Core.h> // !!This is important!!
#include <winrt/Windows.Storage.h>
#include <winrt/Windows.Foundation.h>
@@ -61,7 +61,7 @@ namespace {
using namespace std::string_view_literals;
#if defined(_WIN32) && !defined(_GAMING_XBOX) && !defined(ALSOFT_UWP)
#if defined(_WIN32) && !defined(_GAMING_XBOX) && !ALSOFT_UWP
struct CoTaskMemDeleter {
void operator()(void *mem) const { CoTaskMemFree(mem); }
};
@@ -153,7 +153,7 @@ void LoadConfigFromFile(std::istream &f)
auto endpos = buffer.find(']', 1);
if(endpos == 1 || endpos == std::string::npos)
{
ERR(" config parse error: bad line \"%s\"\n", buffer.c_str());
ERR(" config parse error: bad line \"{}\"", buffer);
continue;
}
if(buffer[endpos+1] != '\0')
@@ -164,7 +164,7 @@ void LoadConfigFromFile(std::istream &f)
if(last < buffer.size() && buffer[last] != '#')
{
ERR(" config parse error: bad line \"%s\"\n", buffer.c_str());
ERR(" config parse error: bad line \"{}\"", buffer);
continue;
}
}
@@ -234,7 +234,7 @@ void LoadConfigFromFile(std::istream &f)
auto sep = buffer.find('=');
if(sep == std::string::npos)
{
ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
ERR(" config parse error: malformed option line: \"{}\"", buffer);
continue;
}
auto keypart = std::string_view{buffer}.substr(0, sep++);
@@ -242,7 +242,7 @@ void LoadConfigFromFile(std::istream &f)
keypart.remove_suffix(1);
if(keypart.empty())
{
ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
ERR(" config parse error: malformed option line: \"{}\"", buffer);
continue;
}
auto valpart = std::string_view{buffer}.substr(sep);
@@ -257,9 +257,9 @@ void LoadConfigFromFile(std::istream &f)
}
fullKey += keypart;
if(valpart.size() > std::numeric_limits<int>::max())
if(valpart.size() > size_t{std::numeric_limits<int>::max()})
{
ERR(" config parse error: value too long in line \"%s\"\n", buffer.c_str());
ERR(" config parse error: value too long in line \"{}\"", buffer);
continue;
}
if(valpart.size() > 1)
@@ -272,7 +272,7 @@ void LoadConfigFromFile(std::istream &f)
}
}
TRACE(" setting '%s' = '%.*s'\n", fullKey.c_str(), al::sizei(valpart), valpart.data());
TRACE(" setting '{}' = '{}'", fullKey, valpart);
/* Check if we already have this option set */
auto find_key = [&fullKey](const ConfigEntry &entry) -> bool
@@ -291,11 +291,12 @@ void LoadConfigFromFile(std::istream &f)
ConfOpts.shrink_to_fit();
}
const char *GetConfigValue(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName)
auto GetConfigValue(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName) -> const std::string&
{
static const auto emptyString = std::string{};
if(keyName.empty())
return nullptr;
return emptyString;
std::string key;
if(!blockName.empty() && al::case_compare(blockName, "general"sv) != 0)
@@ -314,14 +315,14 @@ const char *GetConfigValue(const std::string_view devName, const std::string_vie
[&key](const ConfigEntry &entry) -> bool { return entry.key == key; });
if(iter != ConfOpts.cend())
{
TRACE("Found option %s = \"%s\"\n", key.c_str(), iter->value.c_str());
TRACE("Found option {} = \"{}\"", key, iter->value);
if(!iter->value.empty())
return iter->value.c_str();
return nullptr;
return iter->value;
return emptyString;
}
if(devName.empty())
return nullptr;
return emptyString;
return GetConfigValue({}, blockName, keyName);
}
@@ -331,12 +332,11 @@ const char *GetConfigValue(const std::string_view devName, const std::string_vie
#ifdef _WIN32
void ReadALConfig()
{
namespace fs = std::filesystem;
fs::path path;
#if !defined(_GAMING_XBOX)
{
#if !defined(ALSOFT_UWP)
#if !ALSOFT_UWP
std::unique_ptr<WCHAR,CoTaskMemDeleter> bufstore;
const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND,
nullptr, al::out_ptr(bufstore))};
@@ -352,8 +352,8 @@ void ReadALConfig()
path = fs::path{buffer};
path /= L"alsoft.ini";
TRACE("Loading config %s...\n", path.u8string().c_str());
if(std::ifstream f{path}; f.is_open())
TRACE("Loading config {}...", al::u8_as_char(path.u8string()));
if(fs::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
}
}
@@ -362,17 +362,17 @@ void ReadALConfig()
path = fs::u8path(GetProcBinary().path);
if(!path.empty())
{
path /= "alsoft.ini";
TRACE("Loading config %s...\n", path.u8string().c_str());
if(std::ifstream f{path}; f.is_open())
path /= L"alsoft.ini";
TRACE("Loading config {}...", al::u8_as_char(path.u8string()));
if(fs::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
}
if(auto confpath = al::getenv(L"ALSOFT_CONF"))
{
path = *confpath;
TRACE("Loading config %s...\n", path.u8string().c_str());
if(std::ifstream f{path}; f.is_open())
TRACE("Loading config {}...", al::u8_as_char(path.u8string()));
if(fs::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
}
}
@@ -381,11 +381,10 @@ void ReadALConfig()
void ReadALConfig()
{
namespace fs = std::filesystem;
fs::path path{"/etc/openal/alsoft.conf"};
TRACE("Loading config %s...\n", path.u8string().c_str());
if(std::ifstream f{path}; f.is_open())
TRACE("Loading config {}...", al::u8_as_char(path.u8string()));
if(fs::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")};
@@ -409,13 +408,13 @@ void ReadALConfig()
}
if(!path.is_absolute())
WARN("Ignoring XDG config dir: %s\n", path.u8string().c_str());
WARN("Ignoring XDG config dir: {}", al::u8_as_char(path.u8string()));
else
{
path /= "alsoft.conf";
TRACE("Loading config %s...\n", path.u8string().c_str());
if(std::ifstream f{path}; f.is_open())
TRACE("Loading config {}...", al::u8_as_char(path.u8string()));
if(fs::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
}
}
@@ -441,7 +440,7 @@ void ReadALConfig()
path = *homedir;
path /= ".alsoftrc";
TRACE("Loading config %s...\n", path.u8string().c_str());
TRACE("Loading config {}...", al::u8_as_char(path.u8string()));
if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
}
@@ -462,7 +461,7 @@ void ReadALConfig()
}
if(!path.empty())
{
TRACE("Loading config %s...\n", path.u8string().c_str());
TRACE("Loading config {}...", al::u8_as_char(path.u8string()));
if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
}
@@ -472,66 +471,99 @@ void ReadALConfig()
{
path /= "alsoft.conf";
TRACE("Loading config %s...\n", path.u8string().c_str());
TRACE("Loading config {}...", al::u8_as_char(path.u8string()));
if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
}
if(auto confname = al::getenv("ALSOFT_CONF"))
{
TRACE("Loading config %s...\n", confname->c_str());
TRACE("Loading config {}...", *confname);
if(std::ifstream f{*confname}; f.is_open())
LoadConfigFromFile(f);
}
}
#endif
std::optional<std::string> ConfigValueStr(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName)
auto ConfigValueStr(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName) -> std::optional<std::string>
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty())
return val;
return std::nullopt;
}
std::optional<int> ConfigValueInt(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName)
auto ConfigValueInt(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName) -> std::optional<int>
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return static_cast<int>(std::strtol(val, nullptr, 0));
if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try {
return static_cast<int>(std::stol(val, nullptr, 0));
}
catch(std::exception&) {
WARN("Option is not an int: {} = {}", keyName, val);
}
return std::nullopt;
}
std::optional<unsigned int> ConfigValueUInt(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName)
auto ConfigValueUInt(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName) -> std::optional<unsigned int>
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return static_cast<unsigned int>(std::strtoul(val, nullptr, 0));
if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try {
return static_cast<unsigned int>(std::stoul(val, nullptr, 0));
}
catch(std::exception&) {
WARN("Option is not an unsigned int: {} = {}", keyName, val);
}
return std::nullopt;
}
std::optional<float> ConfigValueFloat(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName)
auto ConfigValueFloat(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName) -> std::optional<float>
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return std::strtof(val, nullptr);
if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try {
return std::stof(val);
}
catch(std::exception&) {
WARN("Option is not a float: {} = {}", keyName, val);
}
return std::nullopt;
}
std::optional<bool> ConfigValueBool(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName)
auto ConfigValueBool(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName) -> std::optional<bool>
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
|| al::strcasecmp(val, "true") == 0 || atoi(val) != 0;
if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try {
return al::case_compare(val, "on"sv) == 0 || al::case_compare(val, "yes"sv) == 0
|| al::case_compare(val, "true"sv) == 0 || std::stoll(val) != 0;
}
catch(std::out_of_range&) {
/* If out of range, the value is some non-0 (true) value and it doesn't
* matter that it's too big or small.
*/
return true;
}
catch(std::exception&) {
/* If stoll fails to convert for any other reason, it's some other word
* that's treated as false.
*/
return false;
}
return std::nullopt;
}
bool GetConfigValueBool(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName, bool def)
auto GetConfigValueBool(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName, bool def) -> bool
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
|| al::strcasecmp(val, "true") == 0 || atoi(val) != 0;
if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try {
return al::case_compare(val, "on"sv) == 0 || al::case_compare(val, "yes"sv) == 0
|| al::case_compare(val, "true"sv) == 0 || std::stoll(val) != 0;
}
catch(std::out_of_range&) {
return true;
}
catch(std::exception&) {
return false;
}
return def;
}
+272 -248
View File
@@ -19,6 +19,7 @@
*/
#include "config.h"
#include "config_simd.h"
#include "alu.h"
@@ -26,23 +27,25 @@
#include <array>
#include <atomic>
#include <cassert>
#include <chrono>
#include <climits>
#include <cmath>
#include <cstdarg>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <functional>
#include <iterator>
#include <limits>
#include <memory>
#include <new>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alsem.h"
#include "alspan.h"
#include "alstring.h"
#include "atomic.h"
@@ -70,6 +73,7 @@
#include "core/mixer/defs.h"
#include "core/mixer/hrtfdefs.h"
#include "core/resampler_limits.h"
#include "core/storage_formats.h"
#include "core/uhjfilter.h"
#include "core/voice.h"
#include "core/voice_change.h"
@@ -78,19 +82,18 @@
#include "ringbuffer.h"
#include "strutils.h"
#include "vecmat.h"
#include "vector.h"
struct CTag;
#ifdef HAVE_SSE
#if HAVE_SSE
struct SSETag;
#endif
#ifdef HAVE_SSE2
#if HAVE_SSE2
struct SSE2Tag;
#endif
#ifdef HAVE_SSE4_1
#if HAVE_SSE4_1
struct SSE4Tag;
#endif
#ifdef HAVE_NEON
#if HAVE_NEON
struct NEONTag;
#endif
struct PointTag;
@@ -135,19 +138,19 @@ float NfcScale{1.0f};
using HrtfDirectMixerFunc = void(*)(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut,
const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples,
const al::span<float,BufferLineSize> TempBuf, HrtfChannelState *ChanState, const size_t IrSize,
const size_t BufferSize);
const al::span<const FloatBufferLine> InSamples, const al::span<float2> AccumSamples,
const al::span<float,BufferLineSize> TempBuf, const al::span<HrtfChannelState> ChanState,
const size_t IrSize, const size_t SamplesToDo);
HrtfDirectMixerFunc MixDirectHrtf{MixDirectHrtf_<CTag>};
inline HrtfDirectMixerFunc SelectHrtfMixer()
{
#ifdef HAVE_NEON
#if HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return MixDirectHrtf_<NEONTag>;
#endif
#ifdef HAVE_SSE
#if HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
return MixDirectHrtf_<SSETag>;
#endif
@@ -176,7 +179,7 @@ inline void BsincPrepare(const uint increment, BsincState *state, const BSincTab
state->sf = sf;
state->m = table->m[si];
state->l = (state->m/2) - 1;
state->filter = table->Tab + table->filterOffset[si];
state->filter = table->Tab.subspan(table->filterOffset[si]);
}
inline ResamplerFunc SelectResampler(Resampler resampler, uint increment)
@@ -186,59 +189,62 @@ inline ResamplerFunc SelectResampler(Resampler resampler, uint increment)
case Resampler::Point:
return Resample_<PointTag,CTag>;
case Resampler::Linear:
#ifdef HAVE_NEON
#if HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return Resample_<LerpTag,NEONTag>;
#endif
#ifdef HAVE_SSE4_1
#if HAVE_SSE4_1
if((CPUCapFlags&CPU_CAP_SSE4_1))
return Resample_<LerpTag,SSE4Tag>;
#endif
#ifdef HAVE_SSE2
#if HAVE_SSE2
if((CPUCapFlags&CPU_CAP_SSE2))
return Resample_<LerpTag,SSE2Tag>;
#endif
return Resample_<LerpTag,CTag>;
case Resampler::Cubic:
#ifdef HAVE_NEON
case Resampler::Spline:
case Resampler::Gaussian:
#if HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return Resample_<CubicTag,NEONTag>;
#endif
#ifdef HAVE_SSE4_1
#if HAVE_SSE4_1
if((CPUCapFlags&CPU_CAP_SSE4_1))
return Resample_<CubicTag,SSE4Tag>;
#endif
#ifdef HAVE_SSE2
#if HAVE_SSE2
if((CPUCapFlags&CPU_CAP_SSE2))
return Resample_<CubicTag,SSE2Tag>;
#endif
#ifdef HAVE_SSE
#if HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
return Resample_<CubicTag,SSETag>;
#endif
return Resample_<CubicTag,CTag>;
case Resampler::BSinc12:
case Resampler::BSinc24:
case Resampler::BSinc48:
if(increment > MixerFracOne)
{
#ifdef HAVE_NEON
#if HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return Resample_<BSincTag,NEONTag>;
#endif
#ifdef HAVE_SSE
#if HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
return Resample_<BSincTag,SSETag>;
#endif
return Resample_<BSincTag,CTag>;
}
/* fall-through */
[[fallthrough]];
case Resampler::FastBSinc12:
case Resampler::FastBSinc24:
#ifdef HAVE_NEON
case Resampler::FastBSinc48:
#if HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return Resample_<FastBSincTag,NEONTag>;
#endif
#ifdef HAVE_SSE
#if HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
return Resample_<FastBSincTag,SSETag>;
#endif
@@ -268,7 +274,10 @@ ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState
case Resampler::Point:
case Resampler::Linear:
break;
case Resampler::Cubic:
case Resampler::Spline:
state->emplace<CubicState>(al::span{gSplineFilter.mTable});
break;
case Resampler::Gaussian:
state->emplace<CubicState>(al::span{gGaussianFilter.mTable});
break;
case Resampler::FastBSinc12:
@@ -279,6 +288,10 @@ ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState
case Resampler::BSinc24:
BsincPrepare(increment, &state->emplace<BsincState>(), &gBSinc24);
break;
case Resampler::FastBSinc48:
case Resampler::BSinc48:
BsincPrepare(increment, &state->emplace<BsincState>(), &gBSinc48);
break;
}
return SelectResampler(resampler, increment);
}
@@ -290,8 +303,8 @@ void DeviceBase::ProcessHrtf(const size_t SamplesToDo)
const size_t lidx{RealOut.ChannelIndex[FrontLeft]};
const size_t ridx{RealOut.ChannelIndex[FrontRight]};
MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData.data(),
mHrtfState->mTemp, mHrtfState->mChannels.data(), mHrtfState->mIrSize, SamplesToDo);
MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData,
mHrtfState->mTemp, mHrtfState->mChannels, mHrtfState->mIrSize, SamplesToDo);
}
void DeviceBase::ProcessAmbiDec(const size_t SamplesToDo)
@@ -330,7 +343,8 @@ void DeviceBase::ProcessBs2b(const size_t SamplesToDo)
const size_t ridx{RealOut.ChannelIndex[FrontRight]};
/* Now apply the BS2B binaural/crossfeed filter. */
Bs2b->cross_feed(RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(), SamplesToDo);
Bs2b->cross_feed(al::span{RealOut.Buffer[lidx]}.first(SamplesToDo),
al::span{RealOut.Buffer[ridx]}.first(SamplesToDo));
}
@@ -430,11 +444,19 @@ bool CalcContextParams(ContextBase *ctx)
ctx->mParams.Velocity = rot * vel;
ctx->mParams.Gain = props->Gain * ctx->mGainBoost;
ctx->mParams.MetersPerUnit = props->MetersPerUnit;
ctx->mParams.MetersPerUnit = props->MetersPerUnit
#if ALSOFT_EAX
* props->DistanceFactor
#endif
;
ctx->mParams.AirAbsorptionGainHF = props->AirAbsorptionGainHF;
ctx->mParams.DopplerFactor = props->DopplerFactor;
ctx->mParams.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity;
ctx->mParams.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity
#if ALSOFT_EAX
/ props->DistanceFactor
#endif
;
ctx->mParams.SourceDistanceModel = props->SourceDistanceModel;
ctx->mParams.mDistanceModel = props->mDistanceModel;
@@ -458,23 +480,27 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa
slot->Target = props->Target;
slot->EffectType = props->Type;
slot->mEffectProps = props->Props;
slot->RoomRolloff = 0.0f;
slot->DecayTime = 0.0f;
slot->DecayLFRatio = 0.0f;
slot->DecayHFRatio = 0.0f;
slot->DecayHFLimit = false;
slot->AirAbsorptionGainHF = 1.0f;
if(auto *reverbprops = std::get_if<ReverbProps>(&props->Props))
{
slot->RoomRolloff = reverbprops->RoomRolloffFactor;
slot->DecayTime = reverbprops->DecayTime;
slot->DecayLFRatio = reverbprops->DecayLFRatio;
slot->DecayHFRatio = reverbprops->DecayHFRatio;
slot->DecayHFLimit = reverbprops->DecayHFLimit;
slot->AirAbsorptionGainHF = reverbprops->AirAbsorptionGainHF;
}
else
{
slot->RoomRolloff = 0.0f;
slot->DecayTime = 0.0f;
slot->DecayLFRatio = 0.0f;
slot->DecayHFRatio = 0.0f;
slot->DecayHFLimit = false;
slot->AirAbsorptionGainHF = 1.0f;
/* If this effect slot's Auxiliary Send Auto is off, don't apply the
* automatic send adjustments based on source distance.
*/
if(slot->AuxSendAuto)
{
slot->DecayTime = reverbprops->DecayTime;
slot->DecayLFRatio = reverbprops->DecayLFRatio;
slot->DecayHFRatio = reverbprops->DecayHFRatio;
slot->DecayHFLimit = reverbprops->DecayHFLimit;
}
}
EffectState *state{props->State.release()};
@@ -489,9 +515,9 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa
/* Otherwise, if it would be deleted send it off with a release event. */
RingBuffer *ring{context->mAsyncEvents.get()};
auto evt_vec = ring->getWriteVector();
if(evt_vec.first.len > 0) LIKELY
if(evt_vec[0].len > 0) LIKELY
{
auto &evt = InitAsyncEvent<AsyncEffectReleaseEvent>(evt_vec.first.buf);
auto &evt = InitAsyncEvent<AsyncEffectReleaseEvent>(evt_vec[0].buf);
evt.mEffectState = oldstate;
ring->writeAdvance(1);
}
@@ -508,14 +534,13 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa
AtomicReplaceHead(context->mFreeEffectSlotProps, props);
EffectTarget output;
if(EffectSlot *target{slot->Target})
output = EffectTarget{&target->Wet, nullptr};
else
const auto output = [slot,context]() -> EffectTarget
{
if(EffectSlot *target{slot->Target})
return EffectTarget{&target->Wet, nullptr};
DeviceBase *device{context->mDevice};
output = EffectTarget{&device->Dry, &device->RealOut};
}
return EffectTarget{&device->Dry, &device->RealOut};
}();
state->update(context, slot, &slot->mEffectProps, output);
return true;
}
@@ -683,8 +708,8 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order)
/* Don't do anything for < 2nd order. */
if(order < 2) return;
auto P = [](const int i, const int l, const int a, const int n, const size_t last_band,
const AmbiRotateMatrix &R)
static constexpr auto P = [](const int i, const int l, const int a, const int n,
const size_t last_band, const AmbiRotateMatrix &R)
{
const float ri1{ R[ 1+2][static_cast<size_t>(i+2_z)]};
const float rim1{R[-1+2][static_cast<size_t>(i+2_z)]};
@@ -698,12 +723,12 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order)
return ri0*R[last_band + static_cast<size_t>(l-1_z+n)][y];
};
auto U = [P](const int l, const int m, const int n, const size_t last_band,
static constexpr auto U = [](const int l, const int m, const int n, const size_t last_band,
const AmbiRotateMatrix &R)
{
return P(0, l, m, n, last_band, R);
};
auto V = [P](const int l, const int m, const int n, const size_t last_band,
static constexpr auto V = [](const int l, const int m, const int n, const size_t last_band,
const AmbiRotateMatrix &R)
{
using namespace al::numbers;
@@ -719,7 +744,7 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order)
const float p1{P(-1, l, -m-1, n, last_band, R)};
return d ? p1*sqrt2_v<float> : (p0 + p1);
};
auto W = [P](const int l, const int m, const int n, const size_t last_band,
static constexpr auto W = [](const int l, const int m, const int n, const size_t last_band,
const AmbiRotateMatrix &R)
{
assert(m != 0);
@@ -833,7 +858,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con
ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}},
};
const auto Frequency = static_cast<float>(Device->Frequency);
const auto Frequency = static_cast<float>(Device->mSampleRate);
const uint NumSends{Device->NumAuxSends};
const size_t num_channels{voice->mChans.size()};
@@ -915,6 +940,10 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con
case TopBackLeft: return lgain;
case TopBackCenter: break;
case TopBackRight: return rgain;
case BottomFrontLeft: return lgain;
case BottomFrontRight: return rgain;
case BottomBackLeft: return lgain;
case BottomBackRight: return rgain;
case Aux0: case Aux1: case Aux2: case Aux3: case Aux4: case Aux5: case Aux6: case Aux7:
case Aux8: case Aux9: case Aux10: case Aux11: case Aux12: case Aux13: case Aux14:
case Aux15: case MaxChannels: break;
@@ -1176,8 +1205,6 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con
}
else for(size_t c{0};c < num_channels;c++)
{
using namespace al::numbers;
/* Skip LFE */
if(chans[c].channel == LFE) continue;
const float pangain{SelectChannelGain(chans[c].channel)};
@@ -1187,7 +1214,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con
* the source position, at full spread (pi*2), each channel is
* left unchanged.
*/
const float a{1.0f - (inv_pi_v<float>/2.0f)*Spread};
const float a{1.0f - (al::numbers::inv_pi_v<float>/2.0f)*Spread};
std::array pos{
lerpf(chans[c].pos[0], xpos, a),
lerpf(chans[c].pos[1], ypos, a),
@@ -1303,57 +1330,51 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con
voice->mChans[0].mWetParams[i].Gains.Target);
}
}
else
else for(size_t c{0};c < num_channels;c++)
{
using namespace al::numbers;
const auto pangain = SelectChannelGain(chans[c].channel);
for(size_t c{0};c < num_channels;c++)
/* Special-case LFE */
if(chans[c].channel == LFE)
{
const float pangain{SelectChannelGain(chans[c].channel)};
/* Special-case LFE */
if(chans[c].channel == LFE)
if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data())
{
if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data())
{
const uint idx{Device->channelIdxByName(chans[c].channel)};
if(idx != InvalidChannelIndex)
voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base
* pangain;
}
continue;
const auto idx = uint{Device->channelIdxByName(chans[c].channel)};
if(idx != InvalidChannelIndex)
voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * pangain;
}
continue;
}
/* Warp the channel position toward the source position as
* the spread decreases. With no spread, all channels are
* at the source position, at full spread (pi*2), each
* channel position is left unchanged.
*/
const float a{1.0f - (inv_pi_v<float>/2.0f)*Spread};
std::array pos{
lerpf(chans[c].pos[0], xpos, a),
lerpf(chans[c].pos[1], ypos, a),
lerpf(chans[c].pos[2], zpos, a)};
const float len{std::sqrt(pos[0]*pos[0] + pos[1]*pos[1] + pos[2]*pos[2])};
if(len < 1.0f)
{
pos[0] /= len;
pos[1] /= len;
pos[2] /= len;
}
/* Warp the channel position toward the source position as the
* spread decreases. With no spread, all channels are at the
* source position, at full spread (pi*2), each channel
* position is left unchanged.
*/
const auto a = 1.0f - (al::numbers::inv_pi_v<float>/2.0f)*Spread;
auto pos = std::array{
lerpf(chans[c].pos[0], xpos, a),
lerpf(chans[c].pos[1], ypos, a),
lerpf(chans[c].pos[2], zpos, a)};
const auto len = std::sqrt(pos[0]*pos[0] + pos[1]*pos[1] + pos[2]*pos[2]);
if(len < 1.0f)
{
pos[0] /= len;
pos[1] /= len;
pos[2] /= len;
}
if(Device->mRenderMode == RenderMode::Pairwise)
pos = ScaleAzimuthFront3(pos);
const auto coeffs = CalcDirectionCoeffs(pos, 0.0f);
if(Device->mRenderMode == RenderMode::Pairwise)
pos = ScaleAzimuthFront3(pos);
const auto coeffs = CalcDirectionCoeffs(pos, 0.0f);
ComputePanGains(&Device->Dry, coeffs, DryGain.Base * pangain,
voice->mChans[c].mDryParams.Gains.Target);
for(uint i{0};i < NumSends;i++)
{
if(const EffectSlot *Slot{SendSlots[i]})
ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain,
voice->mChans[c].mWetParams[i].Gains.Target);
}
ComputePanGains(&Device->Dry, coeffs, DryGain.Base * pangain,
voice->mChans[c].mDryParams.Gains.Target);
for(uint i{0};i < NumSends;i++)
{
if(const EffectSlot *Slot{SendSlots[i]})
ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain,
voice->mChans[c].mWetParams[i].Gains.Target);
}
}
}
@@ -1449,7 +1470,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con
void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBase *context)
{
DeviceBase *Device{context->mDevice};
std::array<EffectSlot*,MaxSendCount> SendSlots;
std::array<EffectSlot*,MaxSendCount> SendSlots{};
voice->mDirect.Buffer = Device->Dry.Buffer;
for(uint i{0};i < Device->NumAuxSends;i++)
@@ -1466,7 +1487,7 @@ void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const Contex
/* Calculate the stepping value */
const auto Pitch = static_cast<float>(voice->mFrequency) /
static_cast<float>(Device->Frequency) * props->Pitch;
static_cast<float>(Device->mSampleRate) * props->Pitch;
if(Pitch > float{MaxPitch})
voice->mStep = MaxPitch<<MixerFracBits;
else
@@ -1474,13 +1495,13 @@ void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const Contex
voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState);
/* Calculate gains */
GainTriplet DryGain;
DryGain.Base = std::min(std::clamp(props->Gain, props->MinGain, props->MaxGain) *
GainTriplet DryGain{};
DryGain.Base = std::min(std::clamp(props->Gain, props->MinGain, props->MaxGain) *
props->Direct.Gain * context->mParams.Gain, GainMixMax);
DryGain.HF = props->Direct.GainHF;
DryGain.LF = props->Direct.GainLF;
std::array<GainTriplet,MaxSendCount> WetGain;
std::array<GainTriplet,MaxSendCount> WetGain{};
for(uint i{0};i < Device->NumAuxSends;i++)
{
WetGain[i].Base = std::min(std::clamp(props->Gain, props->MinGain, props->MaxGain) *
@@ -1502,33 +1523,24 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa
voice->mDirect.Buffer = Device->Dry.Buffer;
std::array<EffectSlot*,MaxSendCount> SendSlots{};
std::array<float,MaxSendCount> RoomRolloff{};
std::bitset<MaxSendCount> UseDryAttnForRoom{0};
for(uint i{0};i < NumSends;i++)
{
SendSlots[i] = props->Send[i].Slot;
if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None)
{
SendSlots[i] = nullptr;
else if(SendSlots[i]->AuxSendAuto)
voice->mSend[i].Buffer = {};
}
else
{
/* NOTE: Contrary to the EFX docs, the effect's room rolloff factor
* applies to the selected distance model along with the source's
* room rolloff factor, not necessarily the inverse distance model.
*
* Generic Software also applies these rolloff factors regardless
* of any setting. It doesn't seem to use the effect slot's send
* auto for anything, though as far as I understand, it's supposed
* to control whether the send gets the same gain/gainhf as the
* direct path (excluding the filter).
*/
RoomRolloff[i] = props->RoomRolloffFactor + SendSlots[i]->RoomRolloff;
}
else
UseDryAttnForRoom.set(i);
if(!SendSlots[i])
voice->mSend[i].Buffer = {};
else
voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer;
}
}
/* Transform source to listener space (convert to head relative) */
@@ -1663,28 +1675,34 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa
std::array<GainTriplet,MaxSendCount> WetGain{};
for(uint i{0};i < NumSends;i++)
{
WetGainBase[i] = std::clamp(WetGainBase[i]*WetCone, props->MinGain, props->MaxGain) *
const auto gain = std::clamp(WetGainBase[i]*WetCone, props->MinGain, props->MaxGain) *
context->mParams.Gain;
/* If this effect slot's Auxiliary Send Auto is off, then use the dry
* path distance and cone attenuation, otherwise use the wet (room)
* path distance and cone attenuation. The send filter is used instead
* of the direct filter, regardless.
*/
const bool use_room{!UseDryAttnForRoom.test(i)};
const float gain{use_room ? WetGainBase[i] : DryGainBase};
WetGain[i].Base = std::min(gain * props->Send[i].Gain, GainMixMax);
WetGain[i].HF = (use_room ? WetConeHF : ConeHF) * props->Send[i].GainHF;
WetGain[i].HF = WetConeHF * props->Send[i].GainHF;
WetGain[i].LF = props->Send[i].GainLF;
}
/* Distance-based air absorption and initial send decay. */
if(Distance > props->RefDistance) LIKELY
{
const float distance_base{(Distance-props->RefDistance) * props->RolloffFactor};
const float distance_meters{distance_base * context->mParams.MetersPerUnit};
const float dryabsorb{distance_meters * props->AirAbsorptionFactor};
if(dryabsorb > std::numeric_limits<float>::epsilon())
DryGain.HF *= std::pow(context->mParams.AirAbsorptionGainHF, dryabsorb);
/* FIXME: In keeping with EAX, the base air absorption gain should be
* taken from the reverb property in the "primary fx slot" when it has
* a reverb effect and the environment flag set, and be applied to the
* direct path and all environment sends, rather than each path using
* the air absorption gain associated with the given slot's effect. At
* this point in the mixer, and even in EFX itself, there's no concept
* of a "primary fx slot" so it's unclear which effect slot should be
* checked.
*
* The HF reference is also intended to be handled the same way, but
* again, there's no concept of a "primary fx slot" here and no way to
* know which effect slot to look at for the reference frequency.
*/
const auto distance_units = float{(Distance-props->RefDistance) * props->RolloffFactor};
const auto distance_meters = float{distance_units * context->mParams.MetersPerUnit};
const auto absorb = float{distance_meters * props->AirAbsorptionFactor};
if(absorb > std::numeric_limits<float>::epsilon())
DryGain.HF *= std::pow(context->mParams.AirAbsorptionGainHF, absorb);
/* If the source's Auxiliary Send Filter Gain Auto is off, no extra
* adjustment is applied to the send gains.
@@ -1694,18 +1712,9 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa
if(!SendSlots[i] || !(SendSlots[i]->DecayTime > 0.0f))
continue;
if(distance_meters > std::numeric_limits<float>::epsilon())
WetGain[i].HF *= std::pow(SendSlots[i]->AirAbsorptionGainHF, distance_meters);
/* If this effect slot's Auxiliary Send Auto is off, don't apply
* the automatic initial reverb decay.
*
* NOTE: Generic Software applies the initial decay regardless of
* this setting. It doesn't seem to use it for anything, only the
* source's send filter gain auto flag affects this.
*/
if(!SendSlots[i]->AuxSendAuto)
continue;
if(SendSlots[i]->AirAbsorptionGainHF < 1.0f
&& absorb > std::numeric_limits<float>::epsilon())
WetGain[i].HF *= std::pow(SendSlots[i]->AirAbsorptionGainHF, absorb);
const float DecayDistance{SendSlots[i]->DecayTime * SpeedOfSoundMetersPerSec};
@@ -1719,7 +1728,7 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa
* with the reverb and source rolloff parameters.
*/
const float baseAttn{DryAttnBase};
const float fact{distance_base / DecayDistance};
const float fact{distance_meters / DecayDistance};
const float gain{std::pow(ReverbDecayGain, fact)*(1.0f-baseAttn) + baseAttn};
WetGain[i].Base *= gain;
}
@@ -1764,7 +1773,7 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa
/* Adjust pitch based on the buffer and output frequencies, and calculate
* fixed-point stepping value.
*/
Pitch *= static_cast<float>(voice->mFrequency) / static_cast<float>(Device->Frequency);
Pitch *= static_cast<float>(voice->mFrequency) / static_cast<float>(Device->mSampleRate);
if(Pitch > float{MaxPitch})
voice->mStep = MaxPitch<<MixerFracBits;
else
@@ -1807,9 +1816,9 @@ void SendSourceStateEvent(ContextBase *context, uint id, VChangeState state)
{
RingBuffer *ring{context->mAsyncEvents.get()};
auto evt_vec = ring->getWriteVector();
if(evt_vec.first.len < 1) return;
if(evt_vec[0].len < 1) return;
auto &evt = InitAsyncEvent<AsyncSourceStateEvent>(evt_vec.first.buf);
auto &evt = InitAsyncEvent<AsyncSourceStateEvent>(evt_vec[0].buf);
evt.mId = id;
switch(state)
{
@@ -1929,7 +1938,7 @@ void ProcessVoiceChanges(ContextBase *ctx)
}
void ProcessParamUpdates(ContextBase *ctx, const al::span<EffectSlot*> slots,
const al::span<Voice*> voices)
const al::span<EffectSlot*> sorted_slots, const al::span<Voice*> voices)
{
ProcessVoiceChanges(ctx);
@@ -1937,9 +1946,9 @@ void ProcessParamUpdates(ContextBase *ctx, const al::span<EffectSlot*> slots,
if(!ctx->mHoldUpdates.load(std::memory_order_acquire)) LIKELY
{
bool force{CalcContextParams(ctx)};
auto sorted_slots = al::to_address(slots.end());
auto sorted_slot_base = al::to_address(sorted_slots.begin());
for(EffectSlot *slot : slots)
force |= CalcEffectSlotParams(slot, sorted_slots, ctx);
force |= CalcEffectSlotParams(slot, sorted_slot_base, ctx);
for(Voice *voice : voices)
{
@@ -1955,97 +1964,93 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo)
{
ASSUME(SamplesToDo > 0);
const nanoseconds curtime{device->mClockBase.load(std::memory_order_relaxed) +
nanoseconds{seconds{device->mSamplesDone.load(std::memory_order_relaxed)}}/
device->Frequency};
const auto curtime = device->getClockTime();
for(ContextBase *ctx : *device->mContexts.load(std::memory_order_acquire))
auto proc_context = [SamplesToDo,curtime](ContextBase *ctx)
{
auto auxslots = al::span{*ctx->mActiveAuxSlots.load(std::memory_order_acquire)};
const al::span<Voice*> voices{ctx->getVoicesSpanAcquired()};
const auto auxslotspan = al::span{*ctx->mActiveAuxSlots.load(std::memory_order_acquire)};
const auto auxslots = auxslotspan.first(auxslotspan.size()>>1);
const auto sorted_slots = auxslotspan.last(auxslotspan.size()>>1);
const auto voices = ctx->getVoicesSpanAcquired();
/* Process pending property updates for objects on the context. */
ProcessParamUpdates(ctx, auxslots, voices);
ProcessParamUpdates(ctx, auxslots, sorted_slots, voices);
/* Clear auxiliary effect slot mixing buffers. */
for(EffectSlot *slot : auxslots)
auto clear_wetbuffers = [](EffectSlot *slot)
{
for(auto &buffer : slot->Wet.Buffer)
buffer.fill(0.0f);
}
auto clear_buffer = [](const FloatBufferSpan buffer)
{ std::fill(buffer.begin(), buffer.end(), 0.0f); };
std::for_each(slot->Wet.Buffer.begin(), slot->Wet.Buffer.end(), clear_buffer);
};
std::for_each(auxslots.begin(), auxslots.end(), clear_wetbuffers);
/* Process voices that have a playing source. */
for(Voice *voice : voices)
auto proc_voice = [ctx,curtime,SamplesToDo](Voice *voice)
{
const Voice::State vstate{voice->mPlayState.load(std::memory_order_acquire)};
if(vstate != Voice::Stopped && vstate != Voice::Pending)
voice->mix(vstate, ctx, curtime, SamplesToDo);
}
};
std::for_each(voices.begin(), voices.end(), proc_voice);
/* Process effects. */
if(!auxslots.empty())
{
/* Sort the slots into extra storage, so that effect slots come
* before their effect slot target (or their targets' target).
* before their effect slot target (or their targets' target). Skip
* sorting if it has already been done.
*/
const al::span sorted_slots{al::to_address(auxslots.end()), auxslots.size()};
/* Skip sorting if it has already been done. */
if(!sorted_slots[0])
{
/* First, copy the slots to the sorted list, then partition the
* sorted list so that all slots without a target slot go to
* the end.
/* First, copy the slots to the sorted list and partition them,
* so that all slots without a target slot go to the end.
*/
std::copy(auxslots.begin(), auxslots.end(), sorted_slots.begin());
auto split_point = std::partition(sorted_slots.begin(), sorted_slots.end(),
[](const EffectSlot *slot) noexcept -> bool
{ return slot->Target != nullptr; });
auto has_target = [](const EffectSlot *slot) noexcept -> bool
{ return slot->Target != nullptr; };
auto split_point = std::partition_copy(auxslots.rbegin(), auxslots.rend(),
sorted_slots.begin(), sorted_slots.rbegin(), has_target).first;
/* There must be at least one slot without a slot target. */
assert(split_point != sorted_slots.end());
/* Simple case: no more than 1 slot has a target slot. Either
* all slots go right to the output, or the remaining one must
* target an already-partitioned slot.
/* Starting from the back of the sorted list, continue
* partitioning the front of the list given each target until
* all targets are accounted for. This ensures all slots
* without a target go last, all slots directly targeting those
* last slots go second-to-last, all slots directly targeting
* those second-last slots go third-to-last, etc.
*/
if(split_point - sorted_slots.begin() > 1)
auto next_target = sorted_slots.end();
while(std::distance(sorted_slots.begin(), split_point) > 1)
{
/* At least two slots target other slots. Starting from the
* back of the sorted list, continue partitioning the front
* of the list given each target until all targets are
* accounted for. This ensures all slots without a target
* go last, all slots directly targeting those last slots
* go second-to-last, all slots directly targeting those
* second-last slots go third-to-last, etc.
/* This shouldn't happen, but if there's unsorted slots
* left that don't target any sorted slots, they can't
* contribute to the output, so leave them.
*/
auto next_target = sorted_slots.end();
do {
/* This shouldn't happen, but if there's unsorted slots
* left that don't target any sorted slots, they can't
* contribute to the output, so leave them.
*/
if(next_target == split_point) UNLIKELY
break;
if(next_target == split_point) UNLIKELY
break;
--next_target;
split_point = std::partition(sorted_slots.begin(), split_point,
[next_target](const EffectSlot *slot) noexcept -> bool
{ return slot->Target != *next_target; });
} while(split_point - sorted_slots.begin() > 1);
--next_target;
auto not_next = [next_target](const EffectSlot *slot) noexcept -> bool
{ return slot->Target != *next_target; };
split_point = std::partition(sorted_slots.begin(), split_point, not_next);
}
}
for(const EffectSlot *slot : sorted_slots)
auto proc_slot = [SamplesToDo](const EffectSlot *slot)
{
EffectState *state{slot->mEffectState.get()};
state->process(SamplesToDo, slot->Wet.Buffer, state->mOutTarget);
}
};
std::for_each(sorted_slots.begin(), sorted_slots.end(), proc_slot);
}
/* Signal the event handler if there are any events to read. */
RingBuffer *ring{ctx->mAsyncEvents.get()};
if(ring->readSpace() > 0)
if(RingBuffer *ring{ctx->mAsyncEvents.get()}; ring->readSpace() > 0)
ctx->mEventSem.post();
}
};
const auto contexts = al::span{*device->mContexts.load(std::memory_order_acquire)};
std::for_each(contexts.begin(), contexts.end(), proc_context);
}
@@ -2144,25 +2149,47 @@ void Write(const al::span<const FloatBufferLine> InBuffer, void *OutBuffer, cons
ASSUME(FrameStep > 0);
ASSUME(SamplesToDo > 0);
const auto output = al::span{static_cast<T*>(OutBuffer), (Offset+SamplesToDo)*FrameStep}
.subspan(Offset*FrameStep);
size_t c{0};
for(const FloatBufferLine &inbuf : InBuffer)
/* Some Clang versions don't like calling subspan on an rvalue here. */
const auto output_ = al::span{static_cast<T*>(OutBuffer), (Offset+SamplesToDo)*FrameStep};
const auto output = output_.subspan(Offset*FrameStep);
/* If there's extra channels in the interleaved output buffer to skip,
* clear the whole output buffer. This is simpler to ensure the extra
* channels are silent than trying to clear just the extra channels.
*/
if(FrameStep > InBuffer.size())
std::fill(output.begin(), output.end(), SampleConv<T>(0.0f));
auto outbase = output.begin();
for(const auto &srcbuf : InBuffer)
{
auto out = output.begin();
auto conv_sample = [FrameStep,c,&out](const float s) noexcept
const auto src = al::span{srcbuf}.first(SamplesToDo);
auto out = outbase++;
*out = SampleConv<T>(src.front());
std::for_each(src.begin()+1, src.end(), [FrameStep,&out](const float s) noexcept
{
out[c] = SampleConv<T>(s);
out += ptrdiff_t(FrameStep);
};
std::for_each_n(inbuf.cbegin(), SamplesToDo, conv_sample);
++c;
*out = SampleConv<T>(s);
});
}
if(const size_t extra{FrameStep - c})
}
template<typename T>
void Write(const al::span<const FloatBufferLine> InBuffer, al::span<void*> OutBuffers,
const size_t Offset, const size_t SamplesToDo)
{
ASSUME(SamplesToDo > 0);
auto srcbuf = InBuffer.cbegin();
for(auto *dstbuf : OutBuffers)
{
const auto silence = SampleConv<T>(0.0f);
for(size_t i{0};i < SamplesToDo;++i)
std::fill_n(&output[i*FrameStep + c], extra, silence);
const auto src = al::span{*srcbuf}.first(SamplesToDo);
/* Some Clang versions don't like calling subspan on an rvalue here. */
const auto dst_ = al::span{static_cast<T*>(dstbuf), Offset+SamplesToDo};
const auto dst = dst_.subspan(Offset);
std::transform(src.begin(), src.end(), dst.begin(), SampleConv<T>);
++srcbuf;
}
}
@@ -2187,10 +2214,10 @@ uint DeviceBase::renderSamples(const uint numSamples)
* also guarantees a stable conversion.
*/
auto samplesDone = mSamplesDone.load(std::memory_order_relaxed) + samplesToDo;
auto clockBase = mClockBase.load(std::memory_order_relaxed) +
std::chrono::seconds{samplesDone/Frequency};
mSamplesDone.store(samplesDone%Frequency, std::memory_order_relaxed);
mClockBase.store(clockBase, std::memory_order_relaxed);
auto clockBaseSec = mClockBaseSec.load(std::memory_order_relaxed) +
seconds32{samplesDone/mSampleRate};
mSamplesDone.store(samplesDone%mSampleRate, std::memory_order_relaxed);
mClockBaseSec.store(clockBaseSec, std::memory_order_relaxed);
}
/* Apply any needed post-process for finalizing the Dry mix to the RealOut
@@ -2199,7 +2226,7 @@ uint DeviceBase::renderSamples(const uint numSamples)
postProcess(samplesToDo);
/* Apply compression, limiting sample amplitude if needed or desired. */
if(Limiter) Limiter->process(samplesToDo, RealOut.Buffer.data());
if(Limiter) Limiter->process(samplesToDo, RealOut.Buffer);
/* Apply delays and attenuation for mismatched speaker distances. */
if(ChannelDelays)
@@ -2214,7 +2241,7 @@ uint DeviceBase::renderSamples(const uint numSamples)
return samplesToDo;
}
void DeviceBase::renderSamples(const al::span<float*> outBuffers, const uint numSamples)
void DeviceBase::renderSamples(const al::span<void*> outBuffers, const uint numSamples)
{
FPUCtl mixer_mode{};
uint total{0};
@@ -2222,13 +2249,19 @@ void DeviceBase::renderSamples(const al::span<float*> outBuffers, const uint num
{
const uint samplesToDo{renderSamples(todo)};
auto srcbuf = RealOut.Buffer.cbegin();
for(auto *dstbuf : outBuffers)
switch(FmtType)
{
const auto dst = al::span{dstbuf, numSamples}.subspan(total);
std::copy_n(srcbuf->cbegin(), samplesToDo, dst.begin());
++srcbuf;
#define HANDLE_WRITE(T) case T: \
Write<DevFmtType_t<T>>(RealOut.Buffer, outBuffers, total, samplesToDo); break;
HANDLE_WRITE(DevFmtByte)
HANDLE_WRITE(DevFmtUByte)
HANDLE_WRITE(DevFmtShort)
HANDLE_WRITE(DevFmtUShort)
HANDLE_WRITE(DevFmtInt)
HANDLE_WRITE(DevFmtUInt)
HANDLE_WRITE(DevFmtFloat)
}
#undef HANDLE_WRITE
total += samplesToDo;
}
@@ -2266,7 +2299,7 @@ void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const siz
}
}
void DeviceBase::handleDisconnect(const char *msg, ...)
void DeviceBase::doDisconnect(std::string msg)
{
const auto mixLock = getWriteMixLock();
@@ -2274,21 +2307,12 @@ void DeviceBase::handleDisconnect(const char *msg, ...)
{
AsyncEvent evt{std::in_place_type<AsyncDisconnectEvent>};
auto &disconnect = std::get<AsyncDisconnectEvent>(evt);
/* NOLINTBEGIN(*-array-to-pointer-decay) */
va_list args;
va_start(args, msg);
int msglen{vsnprintf(disconnect.msg.data(), disconnect.msg.size(), msg, args)};
va_end(args);
/* NOLINTEND(*-array-to-pointer-decay) */
if(msglen < 0 || static_cast<size_t>(msglen) >= disconnect.msg.size())
disconnect.msg[sizeof(disconnect.msg)-1] = 0;
disconnect.msg = std::move(msg);
for(ContextBase *ctx : *mContexts.load())
{
RingBuffer *ring{ctx->mAsyncEvents.get()};
auto evt_data = ring->getWriteVector().first;
auto evt_data = ring->getWriteVector()[0];
if(evt_data.len > 0)
{
al::construct_at(reinterpret_cast<AsyncEvent*>(evt_data.buf), evt);
+7 -4
View File
@@ -2,20 +2,23 @@
#define ALU_H
#include <bitset>
#include <cstdint>
#include <optional>
#include <stdint.h>
struct ALCcontext;
struct ALCdevice;
struct EffectSlot;
enum class StereoEncoding : uint8_t;
enum class StereoEncoding : std::uint8_t;
namespace al {
struct Device;
} // namespace al
constexpr float GainMixMax{1000.0f}; /* +60dB */
enum CompatFlags : uint8_t {
enum CompatFlags : std::uint8_t {
ReverseX,
ReverseY,
ReverseZ,
@@ -31,7 +34,7 @@ void aluInit(CompatFlagBitset flags, const float nfcscale);
* Set up the appropriate panning method and mixing method given the device
* properties.
*/
void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncoding> stereomode);
void aluInitRenderer(al::Device *device, int hrtf_id, std::optional<StereoEncoding> stereomode);
void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context);
+116 -126
View File
@@ -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;
}
@@ -452,6 +436,17 @@ int verify_state(snd_pcm_t *handle)
case SND_PCM_STATE_DISCONNECTED:
return -ENODEV;
/* ALSA headers have made this enum public, leaving us in a bind: use
* the enum despite being private and internal to the libasound, or
* ignore when an enum value isn't handled. We can't rely on it being
* declared either, since older headers don't have it and it could be
* removed in the future. We can't even really rely on its value, since
* being private/internal means it's subject to change, but this is the
* best we can do.
*/
case 1024 /*SND_PCM_STATE_PRIVATE1*/:
assert(state != 1024);
}
return state;
@@ -459,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();
@@ -496,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;
}
@@ -531,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;
@@ -552,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;
}
@@ -563,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;
}
@@ -580,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;
}
@@ -613,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;
}
@@ -675,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
@@ -684,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;
@@ -698,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()
@@ -729,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()));
@@ -787,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()));
@@ -824,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();
@@ -839,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()));
@@ -850,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;
}
@@ -863,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()};
}
}
@@ -880,31 +875,30 @@ 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()
{
ClockLatency ret;
std::lock_guard<std::mutex> dlock{mMutex};
ClockLatency ret{};
ret.ClockTime = mDevice->getClockTime();
snd_pcm_sframes_t delay{};
int err{snd_pcm_delay(mPcmHandle, &delay)};
if(err < 0)
{
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;
@@ -944,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
@@ -954,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();
@@ -988,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()));
@@ -1008,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));
}
@@ -1026,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;
@@ -1063,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;
}
@@ -1099,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;
@@ -1113,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
@@ -1139,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)
@@ -1152,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;
@@ -1189,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;
@@ -1206,18 +1200,17 @@ uint AlsaCapture::availableSamples()
ClockLatency AlsaCapture::getClockLatency()
{
ClockLatency ret;
ClockLatency ret{};
ret.ClockTime = mDevice->getClockTime();
snd_pcm_sframes_t delay{};
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;
}
@@ -1227,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;
}
@@ -1247,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;
@@ -1261,26 +1254,23 @@ bool AlsaBackendFactory::init()
bool AlsaBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string AlsaBackendFactory::probe(BackendType type)
auto AlsaBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
auto add_device = [&outnames](const DevMap &entry) -> void
{
/* +1 to also append the null char (to ensure a null-separated list and
* double-null terminated list).
*/
outnames.append(entry.name.c_str(), entry.name.length()+1);
};
{ outnames.emplace_back(entry.name); };
switch(type)
{
case BackendType::Playback:
PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK);
outnames.reserve(PlaybackDevices.size());
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case BackendType::Capture:
CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE);
outnames.reserve(CaptureDevices.size());
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}
+5 -5
View File
@@ -5,15 +5,15 @@
struct AlsaBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_ALSA_H */
+41 -23
View File
@@ -3,36 +3,18 @@
#include "base.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <utility>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmreg.h>
#include "albit.h"
#include "core/logging.h"
#endif
#include "atomic.h"
#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
@@ -60,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;
}
@@ -126,6 +108,24 @@ void BackendBase::setDefaultWFXChannelOrder() const
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
break;
case DevFmtX7144:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[FrontCenter] = 2;
mDevice->RealOut.ChannelIndex[LFE] = 3;
mDevice->RealOut.ChannelIndex[BackLeft] = 4;
mDevice->RealOut.ChannelIndex[BackRight] = 5;
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8;
mDevice->RealOut.ChannelIndex[TopFrontRight] = 9;
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
mDevice->RealOut.ChannelIndex[BottomFrontLeft] = 12;
mDevice->RealOut.ChannelIndex[BottomFrontRight] = 13;
mDevice->RealOut.ChannelIndex[BottomBackLeft] = 14;
mDevice->RealOut.ChannelIndex[BottomBackRight] = 15;
break;
case DevFmtX3D71:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
@@ -179,6 +179,24 @@ void BackendBase::setDefaultChannelOrder() const
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
break;
case DevFmtX7144:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[BackLeft] = 2;
mDevice->RealOut.ChannelIndex[BackRight] = 3;
mDevice->RealOut.ChannelIndex[FrontCenter] = 4;
mDevice->RealOut.ChannelIndex[LFE] = 5;
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8;
mDevice->RealOut.ChannelIndex[TopFrontRight] = 9;
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
mDevice->RealOut.ChannelIndex[BottomFrontLeft] = 12;
mDevice->RealOut.ChannelIndex[BottomFrontRight] = 13;
mDevice->RealOut.ChannelIndex[BottomBackLeft] = 14;
mDevice->RealOut.ChannelIndex[BottomBackRight] = 15;
break;
case DevFmtX3D71:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
+33 -14
View File
@@ -5,13 +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;
@@ -34,10 +35,17 @@ struct BackendBase {
virtual ClockLatency getClockLatency();
DeviceBase *const mDevice;
std::string mDeviceName;
BackendBase(DeviceBase *device) noexcept : mDevice{device} { }
BackendBase() = delete;
BackendBase(const BackendBase&) = delete;
BackendBase(BackendBase&&) = delete;
explicit BackendBase(DeviceBase *device) noexcept : mDevice{device} { }
virtual ~BackendBase() = default;
void operator=(const BackendBase&) = delete;
void operator=(BackendBase&&) = delete;
protected:
/** Sets the default channel order used by most non-WaveFormatEx-based APIs. */
void setDefaultChannelOrder() const;
@@ -64,18 +72,24 @@ inline ClockLatency GetClockLatency(DeviceBase *device, BackendBase *backend)
struct BackendFactory {
BackendFactory() = default;
BackendFactory(const BackendFactory&) = delete;
BackendFactory(BackendFactory&&) = delete;
virtual ~BackendFactory() = default;
virtual bool init() = 0;
void operator=(const BackendFactory&) = delete;
void operator=(BackendFactory&&) = delete;
virtual bool querySupport(BackendType type) = 0;
virtual auto init() -> bool = 0;
virtual alc::EventSupport queryEventSupport(alc::EventType, BackendType)
virtual auto querySupport(BackendType type) -> bool = 0;
virtual auto queryEventSupport(alc::EventType, BackendType) -> alc::EventSupport
{ return alc::EventSupport::NoSupport; }
virtual std::string probe(BackendType type) = 0;
virtual auto enumerate(BackendType type) -> std::vector<std::string> = 0;
virtual BackendPtr createBackend(DeviceBase *device, BackendType type) = 0;
virtual auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr = 0;
};
namespace al {
@@ -89,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; }
};
+171 -100
View File
@@ -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{};
@@ -822,15 +893,16 @@ void CoreAudioCapture::open(std::string_view name)
case DevFmtX61:
case DevFmtX71:
case DevFmtX714:
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;
@@ -850,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);
@@ -869,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);
@@ -877,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)};
@@ -893,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
}
@@ -907,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)
@@ -926,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);
@@ -966,23 +1038,23 @@ bool CoreAudioBackendFactory::init()
bool CoreAudioBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
std::string CoreAudioBackendFactory::probe(BackendType type)
auto CoreAudioBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
#if CAN_ENUMERATE
auto append_name = [&outnames](const DeviceEntry &entry) -> void
{
/* Includes null char. */
outnames.append(entry.mName.c_str(), entry.mName.length()+1);
};
{ outnames.emplace_back(entry.mName); };
switch(type)
{
case BackendType::Playback:
EnumerateDevices(PlaybackList, false);
outnames.reserve(PlaybackList.size());
std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
break;
case BackendType::Capture:
EnumerateDevices(CaptureList, true);
outnames.reserve(CaptureList.size());
std::for_each(CaptureList.cbegin(), CaptureList.cend(), append_name);
break;
}
@@ -993,8 +1065,7 @@ std::string CoreAudioBackendFactory::probe(BackendType type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
outnames.append(ca_device, sizeof(ca_device));
outnames.emplace_back(ca_device);
break;
}
#endif
+6 -6
View File
@@ -5,17 +5,17 @@
struct CoreAudioBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
alc::EventSupport queryEventSupport(alc::EventType eventType, BackendType type) override;
auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_COREAUDIO_H */
+117 -131
View File
@@ -37,23 +37,20 @@
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <functional>
#include <memory.h>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#include "albit.h"
#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"
@@ -92,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);
@@ -148,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);
}
@@ -174,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();
@@ -217,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};
@@ -244,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;
@@ -253,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;
@@ -266,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))
{
@@ -276,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;
@@ -309,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};
@@ -331,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;
}
@@ -350,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()
@@ -393,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))
@@ -409,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};
@@ -424,81 +412,83 @@ bool DSoundPlayback::reset()
case DevFmtX51: OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break;
case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break;
case DevFmtX7144: mDevice->FmtChans = DevFmtX714;
/* fall-through */
case DevFmtX714: OutputType.dwChannelMask = X7DOT1DOT4; break;
case DevFmtX3D71: OutputType.dwChannelMask = X7DOT1; break;
}
retry_open:
hr = S_OK;
OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
OutputType.Format.wBitsPerSample / 8);
OutputType.Format.nSamplesPerSec = mDevice->Frequency;
OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
OutputType.Format.nBlockAlign;
OutputType.Format.cbSize = 0;
do {
hr = S_OK;
OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
OutputType.Format.wBitsPerSample / 8);
OutputType.Format.nSamplesPerSec = mDevice->mSampleRate;
OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
OutputType.Format.nBlockAlign;
OutputType.Format.cbSize = 0;
if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
{
OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
if(mDevice->FmtType == DevFmtFloat)
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
else
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
mPrimaryBuffer = nullptr;
}
else
{
if(SUCCEEDED(hr) && !mPrimaryBuffer)
if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
{
DSBUFFERDESC DSBDescription{};
DSBDescription.dwSize = sizeof(DSBDescription);
DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mPrimaryBuffer), nullptr);
}
if(SUCCEEDED(hr))
hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
}
OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
/* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
if(mDevice->FmtType == DevFmtFloat)
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
else
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
if(SUCCEEDED(hr))
{
uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
mPrimaryBuffer = nullptr;
}
else
{
if(SUCCEEDED(hr) && !mPrimaryBuffer)
{
DSBUFFERDESC DSBDescription{};
DSBDescription.dwSize = sizeof(DSBDescription);
DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mPrimaryBuffer), nullptr);
}
if(SUCCEEDED(hr))
hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
}
if(FAILED(hr))
break;
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);
if(FAILED(hr) && mDevice->FmtType == DevFmtFloat)
{
mDevice->FmtType = DevFmtShort;
goto retry_open;
}
}
if(SUCCEEDED(hr) || mDevice->FmtType != DevFmtFloat)
break;
mDevice->FmtType = DevFmtShort;
} while(FAILED(hr));
if(SUCCEEDED(hr))
{
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;
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:
@@ -633,10 +623,11 @@ void DSoundCapture::open(std::string_view name)
case DevFmtX61: InputType.dwChannelMask = X6DOT1; break;
case DevFmtX71: InputType.dwChannelMask = X7DOT1; break;
case DevFmtX714: InputType.dwChannelMask = X7DOT1DOT4; break;
case DevFmtX7144:
case DevFmtX3D71:
case DevFmtAmbi3D:
WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
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)};
}
@@ -645,10 +636,11 @@ 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;
/* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample;
if(mDevice->FmtType == DevFmtFloat)
InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
@@ -661,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);
@@ -674,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))
{
@@ -682,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()
@@ -697,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()
@@ -705,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));
}
}
@@ -743,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());
@@ -761,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;
}
@@ -793,35 +785,29 @@ bool DSoundBackendFactory::init()
bool DSoundBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string DSoundBackendFactory::probe(BackendType type)
auto DSoundBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
auto add_device = [&outnames](const DevMap &entry) -> void
{
/* +1 to also append the null char (to ensure a null-separated list and
* double-null terminated list).
*/
outnames.append(entry.name.c_str(), entry.name.length()+1);
};
{ outnames.emplace_back(entry.name); };
/* Initialize COM to prevent name truncation */
ComWrapper com{};
HRESULT hr;
switch(type)
{
case BackendType::Playback:
PlaybackDevices.clear();
hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
if(FAILED(hr))
ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr);
if(HRESULT hr{DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices)}; FAILED(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;
case BackendType::Capture:
CaptureDevices.clear();
hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
if(FAILED(hr))
ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr);
if(HRESULT hr{DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices)};FAILED(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;
}
+5 -5
View File
@@ -5,15 +5,15 @@
struct DSoundBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_DSOUND_H */
+135 -110
View File
@@ -29,19 +29,17 @@
#include <memory.h>
#include <mutex>
#include <thread>
#include <functional>
#include <vector>
#include "albit.h"
#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>
@@ -52,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); \
@@ -109,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"
@@ -120,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;
}
@@ -138,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;
@@ -159,11 +159,19 @@ struct DeviceEntry {
std::string mName;
std::string mPattern;
DeviceEntry() = default;
DeviceEntry(const DeviceEntry&) = default;
DeviceEntry(DeviceEntry&&) = default;
template<typename T, typename U>
DeviceEntry(T&& name, U&& pattern)
: mName{std::forward<T>(name)}, mPattern{std::forward<U>(pattern)}
{ }
~DeviceEntry();
DeviceEntry& operator=(const DeviceEntry&) = default;
DeviceEntry& operator=(DeviceEntry&&) = default;
};
DeviceEntry::~DeviceEntry() = default;
std::vector<DeviceEntry> PlaybackList;
@@ -181,21 +189,21 @@ void EnumerateDevices(jack_client_t *client, std::vector<DeviceEntry> &list)
if(seppos == 0 || seppos >= portname.size())
continue;
const std::string_view portdev{ports[i], seppos};
const auto portdev = portname.substr(0, seppos);
auto check_name = [portdev](const DeviceEntry &entry) -> bool
{ return entry.mName == portdev; };
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);
}
}
@@ -208,8 +216,8 @@ void EnumerateDevices(jack_client_t *client, std::vector<DeviceEntry> &list)
size_t seppos{listopt->find('=', strpos)};
if(seppos >= nextpos || seppos == strpos)
{
const std::string entry{listopt->substr(strpos, nextpos-strpos)};
ERR("Invalid device entry: \"%s\"\n", entry.c_str());
const auto entry = std::string_view{*listopt}.substr(strpos, nextpos-strpos);
ERR("Invalid device entry: \"{}\"", entry);
if(nextpos != std::string::npos) ++nextpos;
strpos = nextpos;
continue;
@@ -227,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;
@@ -270,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;
@@ -322,22 +329,22 @@ JackPlayback::~JackPlayback()
int JackPlayback::processRt(jack_nframes_t numframes) noexcept
{
std::array<jack_default_audio_sample_t*,MaxOutputChannels> out;
size_t numchans{0};
auto outptrs = std::array<void*,MaxOutputChannels>{};
auto numchans = size_t{0};
for(auto port : mPort)
{
if(!port || numchans == mDevice->RealOut.Buffer.size())
break;
out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
outptrs[numchans++] = jack_port_get_buffer(port, numframes);
}
const auto dst = al::span{outptrs}.first(numchans);
if(mPlaying.load(std::memory_order_acquire)) LIKELY
mDevice->renderSamples({out.data(), numchans}, static_cast<uint>(numframes));
mDevice->renderSamples(dst, static_cast<uint>(numframes));
else
{
auto clear_buf = [numframes](float *outbuf) -> void
{ std::fill_n(outbuf, numframes, 0.0f); };
std::for_each(out.begin(), out.begin()+numchans, clear_buf);
std::for_each(dst.begin(), dst.end(), [numframes](void *outbuf) -> void
{ std::fill_n(static_cast<float*>(outbuf), numframes, 0.0f); });
}
return 0;
@@ -354,43 +361,38 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept
out[numchans++] = {static_cast<float*>(jack_port_get_buffer(port, numframes)), numframes};
}
jack_nframes_t total{0};
size_t total{0};
if(mPlaying.load(std::memory_order_acquire)) LIKELY
{
auto data = mRing->getReadVector();
jack_nframes_t todo{std::min(numframes, static_cast<jack_nframes_t>(data.first.len))};
auto firstin = al::span{reinterpret_cast<const float*>(data.first.buf), todo};
for(size_t c{0};c < numchans;++c)
{
auto in = firstin.cbegin();
auto deinterlace_input = [&in,c,numchans]() noexcept -> float
{
const float ret{in[c]};
in += ptrdiff_t(numchans);
return ret;
};
std::generate_n(out[c].begin(), todo, deinterlace_input);
out[c] = out[c].subspan(todo);
}
total += todo;
const auto update_size = size_t{mDevice->mUpdateSize};
todo = std::min(numframes-total, static_cast<jack_nframes_t>(data.second.len));
if(todo > 0)
const auto outlen = size_t{numframes / update_size};
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[0].buf), update_size*len1*numchans};
for(size_t i{0};i < len1;++i)
{
auto secondin = al::span{reinterpret_cast<const float*>(data.second.buf), todo};
for(size_t c{0};c < numchans;++c)
{
auto in = secondin.cbegin();
auto deinterlace_input = [&in,c,numchans]() noexcept -> float
{
float ret{in[c]};
in += ptrdiff_t(numchans);
return ret;
};
std::generate_n(out[c].begin(), todo, deinterlace_input);
out[c] = out[c].subspan(todo);
const auto iter = std::copy_n(src.begin(), update_size, out[c].begin());
out[c] = {iter, out[c].end()};
src = src.subspan(update_size);
}
total += todo;
total += update_size;
}
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)
{
const auto iter = std::copy_n(src.begin(), update_size, out[c].begin());
out[c] = {iter, out[c].end()};
src = src.subspan(update_size);
}
total += update_size;
}
mRing->readAdvance(total);
@@ -412,29 +414,52 @@ int JackPlayback::mixerProc()
SetRTPriority();
althrd_setname(GetMixerThreadName());
const size_t frame_step{mDevice->channelsFromFmt()};
const auto update_size = uint{mDevice->mUpdateSize};
const auto num_channels = size_t{mDevice->channelsFromFmt()};
auto outptrs = std::vector<void*>(num_channels);
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
if(mRing->writeSpace() < mDevice->UpdateSize)
if(mRing->writeSpace() < update_size)
{
mSem.wait();
continue;
}
auto data = mRing->getWriteVector();
size_t todo{data.first.len + data.second.len};
todo -= todo%mDevice->UpdateSize;
const auto len1 = static_cast<uint>(std::min(data.first.len, todo));
const auto len2 = static_cast<uint>(std::min(data.second.len, todo-len1));
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};
mDevice->renderSamples(data.first.buf, len1, frame_step);
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)
{
std::generate_n(outptrs.begin(), outptrs.size(), [&bufiter,update_size]
{
auto ret = al::to_address(bufiter);
bufiter += ptrdiff_t(update_size);
return ret;
});
mDevice->renderSamples(outptrs, update_size);
}
if(len2 > 0)
mDevice->renderSamples(data.second.buf, len2, frame_step);
mRing->writeAdvance(todo);
{
buffer = al::span{reinterpret_cast<float*>(data[1].buf), data[1].len*num_channels};
bufiter = buffer.begin();
for(size_t i{0};i < len2;++i)
{
std::generate_n(outptrs.begin(), outptrs.size(), [&bufiter,update_size]
{
auto ret = al::to_address(bufiter);
bufiter += ptrdiff_t(update_size);
return ret;
});
mDevice->renderSamples(outptrs, update_size);
}
}
mRing->writeAdvance((len1+len2) * update_size);
}
return 0;
@@ -452,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);
}
}
@@ -477,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()
@@ -491,37 +517,38 @@ 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. */
mDevice->FmtType = DevFmtFloat;
int port_num{0};
auto ports_end = mPort.begin() + mDevice->channelsFromFmt();
auto bad_port = mPort.begin();
while(bad_port != ports_end)
auto ports = al::span{mPort}.first(mDevice->channelsFromFmt());
auto bad_port = ports.begin();
while(bad_port != ports.end())
{
std::string name{"channel_" + std::to_string(++port_num)};
*bad_port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE,
@@ -529,17 +556,17 @@ bool JackPlayback::reset()
if(!*bad_port) break;
++bad_port;
}
if(bad_port != ports_end)
if(bad_port != ports.end())
{
ERR("Failed to register enough JACK ports for %s output\n",
ERR("Failed to register enough JACK ports for {} output",
DevFmtChannelsString(mDevice->FmtChans));
if(bad_port == mPort.begin()) return false;
if(bad_port == ports.begin()) return false;
if(bad_port == mPort.begin()+1)
if(bad_port == ports.begin()+1)
mDevice->FmtChans = DevFmtMono;
else
{
ports_end = mPort.begin()+2;
const auto ports_end = ports.begin()+2;
while(bad_port != ports_end)
{
jack_port_unregister(mClient, *(--bad_port));
@@ -559,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,
@@ -574,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]);
}
}
@@ -587,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()};
}
}
}
@@ -635,12 +663,11 @@ void JackPlayback::stop()
ClockLatency JackPlayback::getClockLatency()
{
ClockLatency ret;
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;
}
@@ -648,7 +675,7 @@ ClockLatency JackPlayback::getClockLatency()
void jack_msg_handler(const char *message)
{
WARN("%s\n", message);
WARN("{}", message);
}
} // namespace
@@ -671,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;
}
@@ -684,14 +711,11 @@ bool JackBackendFactory::init()
bool JackBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback); }
std::string JackBackendFactory::probe(BackendType type)
auto JackBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
auto append_name = [&outnames](const DeviceEntry &entry) -> void
{
/* Includes null char. */
outnames.append(entry.mName.c_str(), entry.mName.length()+1);
};
{ outnames.emplace_back(entry.mName); };
const PathNamePair &binname = GetProcBinary();
const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
@@ -705,7 +729,8 @@ std::string JackBackendFactory::probe(BackendType type)
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;
case BackendType::Capture:
+5 -5
View File
@@ -5,15 +5,15 @@
struct JackBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_JACK_H */
+4 -4
View File
@@ -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()
@@ -63,8 +63,8 @@ bool LoopbackBackendFactory::init()
bool LoopbackBackendFactory::querySupport(BackendType)
{ return true; }
std::string LoopbackBackendFactory::probe(BackendType)
{ return std::string{}; }
auto LoopbackBackendFactory::enumerate(BackendType) -> std::vector<std::string>
{ return {}; }
BackendPtr LoopbackBackendFactory::createBackend(DeviceBase *device, BackendType)
{ return BackendPtr{new LoopbackBackend{device}}; }
+5 -5
View File
@@ -5,15 +5,15 @@
struct LoopbackBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_LOOPBACK_H */
+19 -21
View File
@@ -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()};
}
}
@@ -150,17 +148,17 @@ bool NullBackendFactory::init()
bool NullBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback); }
std::string NullBackendFactory::probe(BackendType type)
auto NullBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
switch(type)
{
case BackendType::Playback:
/* Include null char. */
return std::string{GetDeviceName()} + '\0';
return std::vector{std::string{GetDeviceName()}};
case BackendType::Capture:
break;
}
return std::string{};
return {};
}
BackendPtr NullBackendFactory::createBackend(DeviceBase *device, BackendType type)
+5 -5
View File
@@ -5,15 +5,15 @@
struct NullBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_NULL_H */
+35 -34
View File
@@ -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.
@@ -273,9 +274,10 @@ void OboeCapture::open(std::string_view name)
case DevFmtX61:
case DevFmtX71:
case DevFmtX714:
case DevFmtX7144:
case DevFmtX3D71:
case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported",
DevFmtChannelsString(mDevice->FmtChans)};
}
@@ -300,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)};
}
@@ -329,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()
@@ -345,16 +347,15 @@ bool OboeBackendFactory::init() { return true; }
bool OboeBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
std::string OboeBackendFactory::probe(BackendType type)
auto OboeBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
switch(type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Include null char. */
return std::string{GetDeviceName()} + '\0';
return std::vector{std::string{GetDeviceName()}};
}
return std::string{};
return {};
}
BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type)
+5 -5
View File
@@ -5,15 +5,15 @@
struct OboeBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_OBOE_H */
+131 -72
View File
@@ -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
@@ -85,6 +112,7 @@ constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept
SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT |
SL_SPEAKER_TOP_FRONT_LEFT | SL_SPEAKER_TOP_FRONT_RIGHT | SL_SPEAKER_TOP_BACK_LEFT |
SL_SPEAKER_TOP_BACK_RIGHT;
case DevFmtX7144:
case DevFmtAmbi3D:
break;
}
@@ -155,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;
@@ -246,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))
@@ -264,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;
}
@@ -277,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;
}
}
@@ -318,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;
@@ -360,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()
@@ -396,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);
@@ -434,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);
@@ -471,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)
@@ -504,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()};
}
}
@@ -565,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;
@@ -622,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");
@@ -641,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)
{
@@ -669,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);
@@ -699,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);
@@ -751,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");
}
}
@@ -781,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()
@@ -800,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()
@@ -818,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
@@ -829,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;
@@ -839,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;
@@ -857,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;
}
}
@@ -876,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);
@@ -899,25 +927,56 @@ 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); }
std::string OSLBackendFactory::probe(BackendType type)
auto OSLBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
switch(type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Include null char. */
return std::string{GetDeviceName()} + '\0';
return std::vector{std::string{GetDeviceName()}};
}
return std::string{};
return {};
}
BackendPtr OSLBackendFactory::createBackend(DeviceBase *device, BackendType type)
+5 -5
View File
@@ -5,15 +5,15 @@
struct OSLBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_OSL_H */
+72 -79
View File
@@ -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)
@@ -668,18 +664,13 @@ bool OSSBackendFactory::init()
bool OSSBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string OSSBackendFactory::probe(BackendType type)
auto OSSBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
auto add_device = [&outnames](const DevMap &entry) -> void
{
struct stat buf;
if(stat(entry.device_name.c_str(), &buf) == 0)
{
/* Includes null char. */
outnames.append(entry.name.c_str(), entry.name.length()+1);
}
if(struct stat buf{}; stat(entry.device_name.c_str(), &buf) == 0)
outnames.emplace_back(entry.name);
};
switch(type)
@@ -687,12 +678,14 @@ std::string OSSBackendFactory::probe(BackendType type)
case BackendType::Playback:
PlaybackDevices.clear();
ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT);
outnames.reserve(PlaybackDevices.size());
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case BackendType::Capture:
CaptureDevices.clear();
ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT);
outnames.reserve(CaptureDevices.size());
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}
+5 -5
View File
@@ -5,15 +5,15 @@
struct OSSBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_OSS_H */
+700
View 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
View 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 */
+164 -155
View File
@@ -23,25 +23,31 @@
#include "pipewire.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <bitset>
#include <cinttypes>
#include <cmath>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cerrno>
#include <chrono>
#include <cstdint>
#include <ctime>
#include <list>
#include <functional>
#include <iterator>
#include <memory>
#include <mutex>
#include <optional>
#include <string_view>
#include <thread>
#include <type_traits>
#include <tuple>
#include <utility>
#include "albit.h"
#include "alc/alconfig.h"
#include "alc/backends/base.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alspan.h"
#include "alstring.h"
#include "core/devformat.h"
@@ -49,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"
@@ -72,6 +80,7 @@ _Pragma("GCC diagnostic ignored \"-Weverything\"")
#include "spa/buffer/buffer.h"
#include "spa/param/audio/format-utils.h"
#include "spa/param/audio/raw.h"
#include "spa/param/format.h"
#include "spa/param/param.h"
#include "spa/pod/builder.h"
#include "spa/utils/json.h"
@@ -126,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;
@@ -137,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();
@@ -146,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{[]
@@ -183,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) \
@@ -245,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;
}
@@ -259,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;
@@ -405,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; }
@@ -491,7 +505,7 @@ struct NodeProxy {
uint32_t mId{};
PwNodePtr mNode{};
PwNodePtr mNode;
spa_hook mListener{};
NodeProxy(uint32_t id, PwNodePtr node)
@@ -526,7 +540,7 @@ struct MetadataProxy {
uint32_t mId{};
PwMetadataPtr mMetadata{};
PwMetadataPtr mMetadata;
spa_hook mListener{};
MetadataProxy(uint32_t id, PwMetadataPtr mdata)
@@ -547,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{};
@@ -579,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); }
/**
@@ -753,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};
@@ -822,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;
}
@@ -830,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));
@@ -847,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).
*/
@@ -879,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
@@ -901,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;
}
@@ -932,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)":"");
}
@@ -944,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;
}
@@ -960,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));
}
@@ -999,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;
}
@@ -1018,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;
@@ -1081,8 +1092,9 @@ 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:\n", node->mSerial,
(id == SPA_PARAM_EnumFormat) ? "enumerable" : "current");
TRACE("Device ID {} {} format{}:", node->mSerial,
(id == SPA_PARAM_EnumFormat) ? "available" : "current",
(id == SPA_PARAM_EnumFormat) ? "s" : "");
const bool force_update{id == SPA_PARAM_Format};
if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_rate)})
@@ -1115,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;
}
@@ -1153,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))
@@ -1196,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;
}
@@ -1234,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;
}
@@ -1272,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;
}
@@ -1281,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;
}
@@ -1304,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;
}
@@ -1318,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;
}
@@ -1375,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)
@@ -1391,6 +1403,7 @@ spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e u
case DevFmtX71: map = X71Map; break;
case DevFmtX714: map = X714Map; break;
case DevFmtX3D71: map = X71Map; break;
case DevFmtX7144:
case DevFmtAmbi3D:
info.flags |= SPA_AUDIO_FLAG_UNPOSITIONED;
info.channels = device->channelsFromFmt();
@@ -1424,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()
{
@@ -1440,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. */
@@ -1484,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
@@ -1495,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;
@@ -1552,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;
@@ -1560,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)
@@ -1577,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();
@@ -1592,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()
@@ -1626,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;
@@ -1644,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"};
@@ -1658,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",
@@ -1668,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
@@ -1684,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, &params, 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()]()
@@ -1703,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.
@@ -1727,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).
@@ -1738,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;
});
@@ -1753,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;
}
@@ -1768,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;
}
@@ -1783,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;
}
@@ -1807,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()]()
@@ -1828,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
@@ -1856,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
{
@@ -1916,7 +1927,7 @@ class PipeWireCapture final : public BackendBase {
PwStreamPtr mStream;
spa_hook mStreamListener{};
RingBufferPtr mRing{};
RingBufferPtr mRing;
static constexpr pw_stream_events CreateEvents()
{
@@ -1930,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(); }
};
@@ -2017,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;
@@ -2026,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)
@@ -2043,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();
@@ -2058,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};
@@ -2075,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,
@@ -2098,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
@@ -2117,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, &params, 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()]()
@@ -2134,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();
@@ -2142,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);
}
@@ -2152,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()]()
{
@@ -2160,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;
});
}
@@ -2169,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; });
@@ -2192,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())
@@ -2209,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;
@@ -2218,9 +2226,9 @@ bool PipeWireBackendFactory::init()
bool PipeWireBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
std::string PipeWireBackendFactory::probe(BackendType type)
auto PipeWireBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
gEventHandler.waitForInit();
EventWatcherLockGuard evtlock{gEventHandler};
@@ -2241,31 +2249,31 @@ std::string PipeWireBackendFactory::probe(BackendType type)
case BackendType::Playback:
defmatch = std::find_if(defmatch, devlist.cend(), match_defsink);
if(defmatch != devlist.cend())
{
/* Includes null char. */
outnames.append(defmatch->mName.c_str(), defmatch->mName.length()+1);
}
outnames.emplace_back(defmatch->mName);
for(auto iter = devlist.cbegin();iter != devlist.cend();++iter)
{
if(iter != defmatch && iter->mType != NodeType::Source)
outnames.append(iter->mName.c_str(), iter->mName.length()+1);
outnames.emplace_back(iter->mName);
}
break;
case BackendType::Capture:
outnames.reserve(devlist.size());
defmatch = std::find_if(defmatch, devlist.cend(), match_defsource);
if(defmatch != devlist.cend())
{
if(defmatch->mType == NodeType::Sink)
outnames.append(GetMonitorPrefix());
outnames.append(defmatch->mName.c_str(), defmatch->mName.length()+1);
outnames.emplace_back(std::string{GetMonitorPrefix()}+defmatch->mName);
else
outnames.emplace_back(defmatch->mName);
}
for(auto iter = devlist.cbegin();iter != devlist.cend();++iter)
{
if(iter != defmatch)
{
if(iter->mType == NodeType::Sink)
outnames.append(GetMonitorPrefix());
outnames.append(iter->mName.c_str(), iter->mName.length()+1);
outnames.emplace_back(std::string{GetMonitorPrefix()}+iter->mName);
else
outnames.emplace_back(iter->mName);
}
}
break;
@@ -2274,6 +2282,7 @@ std::string PipeWireBackendFactory::probe(BackendType type)
return outnames;
}
BackendPtr PipeWireBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
+8 -6
View File
@@ -2,24 +2,26 @@
#define BACKENDS_PIPEWIRE_H
#include <string>
#include <vector>
#include "alc/events.h"
#include "base.h"
struct DeviceBase;
struct PipeWireBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
alc::EventSupport queryEventSupport(alc::EventType eventType, BackendType type) override;
auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_PIPEWIRE_H */
+230 -117
View File
@@ -20,32 +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 {
using namespace std::string_view_literals;
[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "PortAudio Default"sv; }
#ifdef HAVE_DYNLOAD
#if HAVE_DYNLOAD
void *pa_handle;
#define MAKE_FUNC(x) decltype(x) * p##x
MAKE_FUNC(Pa_Initialize);
@@ -55,6 +48,8 @@ MAKE_FUNC(Pa_StartStream);
MAKE_FUNC(Pa_StopStream);
MAKE_FUNC(Pa_OpenStream);
MAKE_FUNC(Pa_CloseStream);
MAKE_FUNC(Pa_GetDeviceCount);
MAKE_FUNC(Pa_GetDeviceInfo);
MAKE_FUNC(Pa_GetDefaultOutputDevice);
MAKE_FUNC(Pa_GetDefaultInputDevice);
MAKE_FUNC(Pa_GetStreamInfo);
@@ -68,35 +63,72 @@ MAKE_FUNC(Pa_GetStreamInfo);
#define Pa_StopStream pPa_StopStream
#define Pa_OpenStream pPa_OpenStream
#define Pa_CloseStream pPa_CloseStream
#define Pa_GetDeviceCount pPa_GetDeviceCount
#define Pa_GetDeviceInfo pPa_GetDeviceInfo
#define Pa_GetDefaultOutputDevice pPa_GetDefaultOutputDevice
#define Pa_GetDefaultInputDevice pPa_GetDefaultInputDevice
#define Pa_GetStreamInfo pPa_GetStreamInfo
#endif
#endif
struct DeviceEntry {
std::string mName;
uint mPlaybackChannels{};
uint mCaptureChannels{};
};
std::vector<DeviceEntry> DeviceNames;
void EnumerateDevices()
{
const auto devcount = Pa_GetDeviceCount();
if(devcount < 0)
{
ERR("Error getting device count: {}", Pa_GetErrorText(devcount));
return;
}
std::vector<DeviceEntry>(static_cast<uint>(devcount)).swap(DeviceNames);
PaDeviceIndex idx{0};
for(auto &entry : DeviceNames)
{
if(auto info = Pa_GetDeviceInfo(idx); info && info->name)
{
entry.mName = info->name;
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{};
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;
}
@@ -110,45 +142,29 @@ int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long f
}
void PortPlayback::open(std::string_view name)
void PortPlayback::createStream(PaDeviceIndex deviceid)
{
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()};
auto &devinfo = DeviceNames.at(static_cast<uint>(deviceid));
PaStreamParameters params{};
auto devidopt = ConfigValueInt({}, "port", "device");
if(devidopt && *devidopt >= 0) params.device = *devidopt;
else params.device = Pa_GetDefaultOutputDevice();
params.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency);
auto params = StreamParamsExt{};
params.device = deviceid;
params.suggestedLatency = mDevice->mBufferSize / static_cast<double>(mDevice->mSampleRate);
params.hostApiSpecificStreamInfo = nullptr;
params.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
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:
/* fall-through */
case DevFmtShort:
params.sampleFormat = paInt16;
break;
case DevFmtUInt:
/* fall-through */
case DevFmtInt:
params.sampleFormat = paInt32;
break;
case DevFmtFloat:
params.sampleFormat = paFloat32;
break;
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,
@@ -157,55 +173,103 @@ void PortPlayback::open(std::string_view name)
return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer,
framesPerBuffer, timeInfo, statusFlags);
};
PaStream *stream{};
while(PaError err{Pa_OpenStream(&stream, nullptr, &params, mDevice->Frequency,
mDevice->UpdateSize, paNoFlag, writeCallback, this)})
while(PaError err{Pa_OpenStream(&mStream, nullptr, &params, srate, params.updateSize, paNoFlag,
writeCallback, this)})
{
if(params.sampleFormat != paFloat32)
throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
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)};
params.sampleFormat = paInt16;
}
Pa_CloseStream(mStream);
mStream = stream;
mParams = params;
mUpdateSize = mDevice->UpdateSize;
}
mDevice->DeviceName = name;
void PortPlayback::open(std::string_view name)
{
if(DeviceNames.empty())
EnumerateDevices();
PaDeviceIndex deviceid{-1};
if(name.empty())
{
if(auto devidopt = ConfigValueInt({}, "port", "device"))
deviceid = *devidopt;
if(deviceid < 0 || static_cast<uint>(deviceid) >= DeviceNames.size())
deviceid = Pa_GetDefaultOutputDevice();
name = DeviceNames.at(static_cast<uint>(deviceid)).mName;
}
else
{
auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(),
[name](const DeviceEntry &entry)
{ return entry.mPlaybackChannels > 0 && name == entry.mName; });
if(iter == DeviceNames.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"{}\" not found", name};
deviceid = static_cast<int>(std::distance(DeviceNames.cbegin(), iter));
}
createStream(deviceid);
mDeviceIdx = deviceid;
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->UpdateSize = mUpdateSize;
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
mDevice->mSampleRate = static_cast<uint>(std::lround(streamInfo->sampleRate));
mDevice->mUpdateSize = mParams.updateSize;
mDevice->mBufferSize = mDevice->mUpdateSize * 2;
if(streamInfo->outputLatency > 0.0f)
{
ERR("Unexpected sample format: 0x%lx\n", mParams.sampleFormat);
return false;
const double sampleLatency{streamInfo->outputLatency * streamInfo->sampleRate};
TRACE("Reported stream latency: {:f} sec ({:f} samples)", streamInfo->outputLatency,
sampleLatency);
mDevice->mBufferSize = static_cast<uint>(std::clamp(sampleLatency,
double(mDevice->mBufferSize), double{std::numeric_limits<int>::max()}));
}
if(mParams.channelCount >= 2)
mDevice->FmtChans = DevFmtStereo;
else if(mParams.channelCount == 1)
mDevice->FmtChans = DevFmtMono;
else
{
ERR("Unexpected channel count: %u\n", mParams.channelCount);
return false;
}
setDefaultChannelOrder();
return true;
@@ -213,22 +277,20 @@ bool PortPlayback::reset()
void PortPlayback::start()
{
const PaError err{Pa_StartStream(mStream)};
if(err != paNoError)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: %s",
if(const PaError err{Pa_StartStream(mStream)}; err != paNoError)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: {}",
Pa_GetErrorText(err)};
}
void PortPlayback::stop()
{
PaError err{Pa_StopStream(mStream)};
if(err != paNoError)
ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
if(PaError err{Pa_StopStream(mStream)}; err != paNoError)
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,
@@ -250,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;
}
@@ -265,20 +327,35 @@ int PortCapture::readCallback(const void *inputBuffer, void*, unsigned long fram
void PortCapture::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()};
if(DeviceNames.empty())
EnumerateDevices();
const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)};
int deviceid{};
if(name.empty())
{
if(auto devidopt = ConfigValueInt({}, "port", "capture"))
deviceid = *devidopt;
if(deviceid < 0 || static_cast<uint>(deviceid) >= DeviceNames.size())
deviceid = Pa_GetDefaultInputDevice();
name = DeviceNames.at(static_cast<uint>(deviceid)).mName;
}
else
{
auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(),
[name](const DeviceEntry &entry)
{ return entry.mCaptureChannels > 0 && name == entry.mName; });
if(iter == DeviceNames.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"{}\" not found", name};
deviceid = static_cast<int>(std::distance(DeviceNames.cbegin(), iter));
}
const uint samples{std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u)};
const uint frame_size{mDevice->frameSizeFromFmt()};
mRing = RingBuffer::Create(samples, frame_size, false);
auto devidopt = ConfigValueInt({}, "port", "capture");
if(devidopt && *devidopt >= 0) mParams.device = *devidopt;
else mParams.device = Pa_GetDefaultOutputDevice();
mParams.device = deviceid;
mParams.suggestedLatency = 0.0f;
mParams.hostApiSpecificStreamInfo = nullptr;
@@ -301,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());
@@ -313,29 +390,27 @@ 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;
}
void PortCapture::start()
{
const PaError err{Pa_StartStream(mStream)};
if(err != paNoError)
if(const PaError err{Pa_StartStream(mStream)}; err != paNoError)
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start recording: %s", Pa_GetErrorText(err)};
"Failed to start recording: {}", Pa_GetErrorText(err)};
}
void PortCapture::stop()
{
PaError err{Pa_StopStream(mStream)};
if(err != paNoError)
ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
if(PaError err{Pa_StopStream(mStream)}; err != paNoError)
ERR("Error stopping stream: {}", Pa_GetErrorText(err));
}
@@ -350,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
@@ -383,6 +458,8 @@ bool PortBackendFactory::init()
LOAD_FUNC(Pa_StopStream);
LOAD_FUNC(Pa_OpenStream);
LOAD_FUNC(Pa_CloseStream);
LOAD_FUNC(Pa_GetDeviceCount);
LOAD_FUNC(Pa_GetDeviceInfo);
LOAD_FUNC(Pa_GetDefaultOutputDevice);
LOAD_FUNC(Pa_GetDefaultInputDevice);
LOAD_FUNC(Pa_GetStreamInfo);
@@ -391,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;
@@ -401,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
@@ -411,16 +488,52 @@ bool PortBackendFactory::init()
bool PortBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string PortBackendFactory::probe(BackendType type)
auto PortBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::vector<std::string> devices;
EnumerateDevices();
int defaultid{-1};
switch(type)
{
case BackendType::Playback:
defaultid = Pa_GetDefaultOutputDevice();
if(auto devidopt = ConfigValueInt({}, "port", "device"); devidopt && *devidopt >= 0
&& static_cast<uint>(*devidopt) < DeviceNames.size())
defaultid = *devidopt;
for(size_t i{0};i < DeviceNames.size();++i)
{
if(DeviceNames[i].mPlaybackChannels > 0)
{
if(defaultid >= 0 && static_cast<uint>(defaultid) == i)
devices.emplace(devices.cbegin(), DeviceNames[i].mName);
else
devices.emplace_back(DeviceNames[i].mName);
}
}
break;
case BackendType::Capture:
/* Include null char. */
return std::string{GetDefaultName()} + '\0';
defaultid = Pa_GetDefaultInputDevice();
if(auto devidopt = ConfigValueInt({}, "port", "capture"); devidopt && *devidopt >= 0
&& static_cast<uint>(*devidopt) < DeviceNames.size())
defaultid = *devidopt;
for(size_t i{0};i < DeviceNames.size();++i)
{
if(DeviceNames[i].mCaptureChannels > 0)
{
if(defaultid >= 0 && static_cast<uint>(defaultid) == i)
devices.emplace(devices.cbegin(), DeviceNames[i].mName);
else
devices.emplace_back(DeviceNames[i].mName);
}
}
break;
}
return std::string{};
return devices;
}
BackendPtr PortBackendFactory::createBackend(DeviceBase *device, BackendType type)
-19
View File
@@ -1,19 +0,0 @@
#ifndef BACKENDS_PORTAUDIO_H
#define BACKENDS_PORTAUDIO_H
#include "base.h"
struct PortBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_PORTAUDIO_H */
@@ -0,0 +1,19 @@
#ifndef BACKENDS_PORTAUDIO_HPP
#define BACKENDS_PORTAUDIO_HPP
#include "base.h"
struct PortBackendFactory 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_PORTAUDIO_HPP */
+293 -225
View File
@@ -28,27 +28,28 @@
#include <atomic>
#include <bitset>
#include <chrono>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <mutex>
#include <optional>
#include <string>
#include <string_view>
#include <sys/types.h>
#include <utility>
#include <vector>
#include "albit.h"
#include "alc/alconfig.h"
#include "almalloc.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"
@@ -57,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); \
@@ -71,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); \
@@ -144,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
@@ -251,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)
@@ -277,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)
@@ -293,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;
@@ -343,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)
@@ -360,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
@@ -391,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;
};
@@ -433,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);
}
@@ -483,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>;
@@ -532,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)};
}
}
@@ -543,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
@@ -555,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)};
}
@@ -567,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;
});
@@ -592,52 +693,12 @@ void PulseMainloop::close(pa_stream *stream)
}
void PulseMainloop::probePlaybackDevices()
{
PlaybackDevices.clear();
try {
MainloopUniqueLock plock{*this};
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};
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;
@@ -655,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{};
@@ -678,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();
@@ -711,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);
}
@@ -754,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));
}
@@ -770,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);
}
@@ -787,21 +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()};
pulse_name = iter->device_name.c_str();
display_name = iter->name;
"Device name \"{}\" not found", name};
pulse_name = iter->device_name;
mDeviceName = iter->name;
}
MainloopUniqueLock plock{mMainloop};
@@ -817,37 +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);
pa_stream_set_moved_callback(mStream, [](pa_stream *stream, void *pdata) noexcept
{ return static_cast<PulsePlayback*>(pdata)->streamMovedCallback(stream); }, this);
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)
{
@@ -860,17 +921,17 @@ bool PulsePlayback::reset()
mStream = nullptr;
}
auto info_callback = [](pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept
auto info_cb = [](pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept
{ return static_cast<PulsePlayback*>(pdata)->sinkInfoCallback(context, info, eol); };
pa_operation *op{pa_context_get_sink_info_by_name(mMainloop.getContext(), deviceName,
info_callback, this)};
pa_operation *op{pa_context_get_sink_info_by_name(mMainloop.getContext(), deviceName, info_cb,
this)};
plock.waitForOperation(op);
pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING |
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,
@@ -879,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;
@@ -908,6 +969,9 @@ bool PulsePlayback::reset()
case DevFmtX3D71:
chanmap = X71ChanMap;
break;
case DevFmtX7144:
mDevice->FmtChans = DevFmtX714;
/*fall-through*/
case DevFmtX714:
chanmap = X714ChanMap;
break;
@@ -938,38 +1002,41 @@ 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,
BackendType::Playback);
pa_stream_set_state_callback(mStream, [](pa_stream *stream, void *pdata) noexcept
{ return static_cast<PulsePlayback*>(pdata)->streamStateCallback(stream); }, this);
pa_stream_set_moved_callback(mStream, [](pa_stream *stream, void *pdata) noexcept
{ return static_cast<PulsePlayback*>(pdata)->streamMovedCallback(stream); }, this);
constexpr auto state_callback = [](pa_stream *stream, void *pdata) noexcept
{ return static_cast<PulsePlayback*>(pdata)->streamStateCallback(stream); };
pa_stream_set_state_callback(mStream, state_callback, this);
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);
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;
@@ -981,16 +1048,16 @@ bool PulsePlayback::reset()
&mMainloop);
plock.waitForOperation(op);
mDevice->Frequency = mSpec.rate;
mDevice->mSampleRate = mSpec.rate;
}
auto attr_callback = [](pa_stream *stream, void *pdata) noexcept
constexpr auto attr_callback = [](pa_stream *stream, void *pdata) noexcept
{ return static_cast<PulsePlayback*>(pdata)->bufferAttrCallback(stream); };
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;
}
@@ -1009,8 +1076,9 @@ void PulsePlayback::start()
pa_stream_write(mStream, buf, todo, pa_xfree, 0, PA_SEEK_RELATIVE);
}
pa_stream_set_write_callback(mStream, [](pa_stream *stream, size_t nbytes, void *pdata)noexcept
{ return static_cast<PulsePlayback*>(pdata)->streamWriteCallback(stream, nbytes); }, this);
constexpr auto stream_write = [](pa_stream *stream, size_t nbytes, void *pdata) noexcept
{ return static_cast<PulsePlayback*>(pdata)->streamWriteCallback(stream, nbytes); };
pa_stream_set_write_callback(mStream, stream_write, this);
pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC,
&mMainloop)};
@@ -1030,7 +1098,7 @@ void PulsePlayback::stop()
ClockLatency PulsePlayback::getClockLatency()
{
ClockLatency ret;
ClockLatency ret{};
pa_usec_t latency{};
int neg{}, err{};
@@ -1047,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
@@ -1060,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;
@@ -1076,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};
@@ -1099,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();
@@ -1112,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);
}
@@ -1132,20 +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()};
pulse_name = iter->device_name.c_str();
mDevice->DeviceName = iter->name;
"Device name \"{}\" not found", name};
pulse_name = iter->device_name;
mDeviceName = iter->name;
}
MainloopUniqueLock plock{mMainloop};
@@ -1154,30 +1222,17 @@ void PulseCapture::open(std::string_view name)
pa_channel_map chanmap{};
switch(mDevice->FmtChans)
{
case DevFmtMono:
chanmap = MonoChanMap;
break;
case DevFmtStereo:
chanmap = StereoChanMap;
break;
case DevFmtQuad:
chanmap = QuadChanMap;
break;
case DevFmtX51:
chanmap = X51ChanMap;
break;
case DevFmtX61:
chanmap = X61ChanMap;
break;
case DevFmtX71:
chanmap = X71ChanMap;
break;
case DevFmtX714:
chanmap = X714ChanMap;
break;
case DevFmtMono: chanmap = MonoChanMap; break;
case DevFmtStereo: chanmap = StereoChanMap; break;
case DevFmtQuad: chanmap = QuadChanMap; break;
case DevFmtX51: chanmap = X51ChanMap; break;
case DevFmtX61: chanmap = X61ChanMap; break;
case DevFmtX71: chanmap = X71ChanMap; break;
case DevFmtX714: chanmap = X714ChanMap; break;
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();
@@ -1201,39 +1256,44 @@ 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);
pa_stream_set_moved_callback(mStream, [](pa_stream *stream, void *pdata) noexcept
{ return static_cast<PulseCapture*>(pdata)->streamMovedCallback(stream); }, this);
pa_stream_set_state_callback(mStream, [](pa_stream *stream, void *pdata) noexcept
{ return static_cast<PulseCapture*>(pdata)->streamStateCallback(stream); }, this);
constexpr auto move_callback = [](pa_stream *stream, void *pdata) noexcept
{ return static_cast<PulseCapture*>(pdata)->streamMovedCallback(stream); };
pa_stream_set_moved_callback(mStream, move_callback, this);
if(pulse_name) mDeviceName.emplace(pulse_name);
else mDeviceName.reset();
if(mDevice->DeviceName.empty())
constexpr auto state_callback = [](pa_stream *stream, void *pdata) noexcept
{ return static_cast<PulseCapture*>(pdata)->streamStateCallback(stream); };
pa_stream_set_state_callback(mStream, state_callback, this);
if(!pulse_name.empty())
mDeviceId.emplace(std::move(pulse_name));
if(mDeviceName.empty())
{
auto name_callback = [](pa_context *context, const pa_source_info *info, int eol, void *pdata) noexcept
constexpr auto name_callback = [](pa_context *context, const pa_source_info *info, int eol,
void *pdata) noexcept
{ return static_cast<PulseCapture*>(pdata)->sourceNameCallback(context, info, eol); };
pa_operation *op{pa_context_get_source_info_by_name(mMainloop.getContext(),
pa_stream_get_device_name(mStream), name_callback, this)};
@@ -1299,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;
}
@@ -1307,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;
}
@@ -1335,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
{
@@ -1359,7 +1419,7 @@ uint PulseCapture::availableSamples()
ClockLatency PulseCapture::getClockLatency()
{
ClockLatency ret;
ClockLatency ret{};
pa_usec_t latency{};
int neg{}, err{};
@@ -1371,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;
}
@@ -1387,7 +1447,7 @@ ClockLatency PulseCapture::getClockLatency()
bool PulseBackendFactory::init()
{
#ifdef HAVE_DYNLOAD
#if HAVE_DYNLOAD
if(!pulse_handle)
{
#ifdef _WIN32
@@ -1400,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;
}
@@ -1414,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))
@@ -1450,28 +1510,36 @@ bool PulseBackendFactory::init()
bool PulseBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
std::string PulseBackendFactory::probe(BackendType type)
auto PulseBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
auto add_device = [&outnames](const DevMap &entry) -> void
auto add_playback_device = [&outnames](const DevMap &entry) -> void
{
/* +1 to also append the null char (to ensure a null-separated list and
* double-null terminated list).
*/
outnames.append(entry.name.c_str(), entry.name.length()+1);
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();
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
outnames.reserve(PlaybackDevices.size());
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_playback_device);
break;
case BackendType::Capture:
gGlobalMainloop.probeCaptureDevices();
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
outnames.reserve(CaptureDevices.size());
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_capture_device);
break;
}
@@ -1499,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;
}
+12 -6
View File
@@ -1,21 +1,27 @@
#ifndef BACKENDS_PULSEAUDIO_H
#define BACKENDS_PULSEAUDIO_H
#include <string>
#include <vector>
#include "alc/events.h"
#include "base.h"
struct DeviceBase;
class PulseBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
alc::EventSupport queryEventSupport(alc::EventType eventType, BackendType type) override;
auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_PULSEAUDIO_H */
+110 -77
View File
@@ -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;
}
@@ -209,26 +243,25 @@ bool SDL2BackendFactory::init()
bool SDL2BackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
std::string SDL2BackendFactory::probe(BackendType type)
auto SDL2BackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
if(type != BackendType::Playback)
return outnames;
int num_devices{SDL_GetNumAudioDevices(SDL_FALSE)};
if(num_devices <= 0)
return outnames;
/* Includes null char. */
outnames += getDefaultDeviceName();
outnames += '\0';
outnames.reserve(static_cast<unsigned int>(num_devices)+1_uz);
outnames.emplace_back(getDefaultDeviceName());
for(int i{0};i < num_devices;++i)
{
outnames += getDevicePrefix();
if(const char *name = SDL_GetAudioDeviceName(i, SDL_FALSE))
outnames += name;
outnames.emplace_back(name);
else
outnames += "Unknown Device Name #"+std::to_string(i);
outnames += '\0';
outnames.emplace_back("Unknown Device Name #"+std::to_string(i));
}
return outnames;
}
+5 -5
View File
@@ -5,15 +5,15 @@
struct SDL2BackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_SDL2_H */
+393
View 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
View 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 */
+51 -57
View File
@@ -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)
@@ -520,16 +515,15 @@ bool SndIOBackendFactory::init()
bool SndIOBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string SndIOBackendFactory::probe(BackendType type)
auto SndIOBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
switch(type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Include null char. */
return std::string{GetDefaultName()} + '\0';
return std::vector{std::string{GetDefaultName()}};
}
return std::string{};
return {};
}
BackendPtr SndIOBackendFactory::createBackend(DeviceBase *device, BackendType type)
-19
View File
@@ -1,19 +0,0 @@
#ifndef BACKENDS_SNDIO_H
#define BACKENDS_SNDIO_H
#include "base.h"
struct SndIOBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_SNDIO_H */
+19
View File
@@ -0,0 +1,19 @@
#ifndef BACKENDS_SNDIO_HPP
#define BACKENDS_SNDIO_HPP
#include "base.h"
struct SndIOBackendFactory 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_SNDIO_HPP */
+26 -26
View File
@@ -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
@@ -275,19 +275,19 @@ bool SolarisBackendFactory::init()
bool SolarisBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
std::string SolarisBackendFactory::probe(BackendType type)
auto SolarisBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
switch(type)
{
case BackendType::Playback:
if(struct stat buf{}; stat(solaris_driver.c_str(), &buf) == 0)
return std::string{GetDefaultName()} + '\0';
return std::vector{std::string{GetDefaultName()}};
break;
case BackendType::Capture:
break;
}
return std::string{};
return {};
}
BackendPtr SolarisBackendFactory::createBackend(DeviceBase *device, BackendType type)
+5 -5
View File
@@ -5,15 +5,15 @@
struct SolarisBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_SOLARIS_H */
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -5,17 +5,17 @@
struct WasapiBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
alc::EventSupport queryEventSupport(alc::EventType eventType, BackendType type) override;
auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_WASAPI_H */
+50 -45
View File
@@ -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;
@@ -264,6 +256,9 @@ bool WaveBackend::reset()
case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break;
case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break;
case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break;
case DevFmtX7144:
mDevice->FmtChans = DevFmtX714;
[[fallthrough]];
case DevFmtX714:
chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400 | 0x1000 | 0x4000
| 0x8000 | 0x20000;
@@ -279,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());
@@ -297,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
@@ -316,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;
@@ -336,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()};
}
}
@@ -376,17 +382,16 @@ bool WaveBackendFactory::init()
bool WaveBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
std::string WaveBackendFactory::probe(BackendType type)
auto WaveBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
switch(type)
{
case BackendType::Playback:
/* Include null char. */
return std::string{GetDeviceName()} + '\0';
return std::vector{std::string{GetDeviceName()}};
case BackendType::Capture:
break;
}
return std::string{};
return {};
}
BackendPtr WaveBackendFactory::createBackend(DeviceBase *device, BackendType type)
+5 -5
View File
@@ -5,15 +5,15 @@
struct WaveBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_WAVE_H */
+75 -93
View File
@@ -36,15 +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"
@@ -55,9 +54,6 @@
namespace {
#define DEVNAME_HEAD "OpenAL Soft on "
std::vector<std::string> PlaybackDevices;
std::vector<std::string> CaptureDevices;
@@ -77,19 +73,15 @@ void ProbePlaybackDevices()
WAVEOUTCAPSW WaveCaps{};
if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
{
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(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));
}
@@ -108,19 +100,15 @@ void ProbeCaptureDevices()
WAVEINCAPSW WaveCaps{};
if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
{
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(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));
}
@@ -128,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;
@@ -195,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);
@@ -216,61 +204,57 @@ 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};
retry_open:
WAVEFORMATEX format{};
if(fmttype == DevFmtFloat)
{
format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
format.wBitsPerSample = 32;
}
else
{
format.wFormatTag = WAVE_FORMAT_PCM;
if(fmttype == DevFmtUByte || fmttype == DevFmtByte)
format.wBitsPerSample = 8;
else
format.wBitsPerSample = 16;
}
format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
format.nBlockAlign = static_cast<WORD>(format.wBitsPerSample * format.nChannels / 8);
format.nSamplesPerSec = mDevice->Frequency;
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
format.cbSize = 0;
HWAVEOUT outHandle{};
MMRESULT res{waveOutOpen(&outHandle, DeviceID, &format,
reinterpret_cast<DWORD_PTR>(&WinMMPlayback::waveOutProcC),
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
if(res != MMSYSERR_NOERROR)
{
do {
format = WAVEFORMATEX{};
if(fmttype == DevFmtFloat)
{
fmttype = DevFmtShort;
goto retry_open;
format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
format.wBitsPerSample = 32;
}
throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", res};
}
else
{
format.wFormatTag = WAVE_FORMAT_PCM;
if(fmttype == DevFmtUByte || fmttype == DevFmtByte)
format.wBitsPerSample = 8;
else
format.wBitsPerSample = 16;
}
format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
format.nBlockAlign = static_cast<WORD>(format.wBitsPerSample * format.nChannels / 8);
format.nSamplesPerSec = mDevice->mSampleRate;
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
format.cbSize = 0;
MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &format,
reinterpret_cast<DWORD_PTR>(&WinMMPlayback::waveOutProcC),
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
if(res == MMSYSERR_NOERROR) break;
if(fmttype != DevFmtFloat)
throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: {}",
res};
fmttype = DevFmtShort;
} while(true);
if(mOutHdl)
waveOutClose(mOutHdl);
mOutHdl = outHandle;
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)
{
@@ -278,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;
}
}
@@ -290,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;
}
@@ -306,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{};
@@ -336,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()};
}
}
@@ -359,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;
@@ -451,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)
@@ -466,9 +450,10 @@ void WinMMCapture::open(std::string_view name)
case DevFmtX61:
case DevFmtX71:
case DevFmtX714:
case DevFmtX7144:
case DevFmtX3D71:
case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported",
DevFmtChannelsString(mDevice->FmtChans)};
}
@@ -483,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)};
}
@@ -493,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;
@@ -501,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};
@@ -509,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);
@@ -525,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()
@@ -538,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()};
}
}
@@ -582,26 +567,23 @@ bool WinMMBackendFactory::init()
bool WinMMBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
std::string WinMMBackendFactory::probe(BackendType type)
auto WinMMBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
auto add_device = [&outnames](const std::string &dname) -> void
{
/* +1 to also append the null char (to ensure a null-separated list and
* double-null terminated list).
*/
if(!dname.empty())
outnames.append(dname.c_str(), dname.length()+1);
};
{ if(!dname.empty()) outnames.emplace_back(dname); };
switch(type)
{
case BackendType::Playback:
ProbePlaybackDevices();
outnames.reserve(PlaybackDevices.size());
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case BackendType::Capture:
ProbeCaptureDevices();
outnames.reserve(CaptureDevices.size());
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}
+5 -5
View File
@@ -5,15 +5,15 @@
struct WinMMBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_WINMM_H */
+123 -105
View File
@@ -6,12 +6,12 @@
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstring>
#include <functional>
#include <limits>
#include <iterator>
#include <numeric>
#include <stdexcept>
#include <optional>
#include <string_view>
#include <tuple>
#include <utility>
#include "AL/efx.h"
@@ -25,20 +25,22 @@
#include "albit.h"
#include "alc/alu.h"
#include "alc/backends/base.h"
#include "alnumeric.h"
#include "alspan.h"
#include "atomic.h"
#include "core/async_event.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effectslot.h"
#include "core/logging.h"
#include "core/voice.h"
#include "core/voice_change.h"
#include "device.h"
#include "flexarray.h"
#include "ringbuffer.h"
#include "vecmat.h"
#ifdef ALSOFT_EAX
#include "alstring.h"
#if ALSOFT_EAX
#include "al/eax/call.h"
#include "al/eax/globals.h"
#endif // ALSOFT_EAX
@@ -71,7 +73,7 @@ std::vector<std::string_view> getContextExtensions() noexcept
"AL_EXT_STEREO_ANGLES"sv,
"AL_LOKI_quadriphonic"sv,
"AL_SOFT_bformat_ex"sv,
"AL_SOFTX_bformat_hoa"sv,
"AL_SOFT_bformat_hoa"sv,
"AL_SOFT_block_alignment"sv,
"AL_SOFT_buffer_length_query"sv,
"AL_SOFT_callback_buffer"sv,
@@ -108,7 +110,7 @@ ALCcontext::ThreadCtx::~ThreadCtx()
if(ALCcontext *ctx{std::exchange(ALCcontext::sLocalContext, nullptr)})
{
const bool result{ctx->releaseIfNoDelete()};
ERR("Context %p current for thread being destroyed%s!\n", voidp{ctx},
ERR("Context {} current for thread being destroyed{}!", voidp{ctx},
result ? "" : ", leak detected");
}
}
@@ -117,26 +119,30 @@ thread_local ALCcontext::ThreadCtx ALCcontext::sThreadContext;
ALeffect ALCcontext::sDefaultEffect;
ALCcontext::ALCcontext(al::intrusive_ptr<ALCdevice> device, ContextFlagBitset flags)
ALCcontext::ALCcontext(al::intrusive_ptr<al::Device> device, ContextFlagBitset flags)
: ContextBase{device.get()}, mALDevice{std::move(device)}, mContextFlags{flags}
{
mDebugGroups.emplace_back(DebugSource::Other, 0, std::string{});
mDebugEnabled.store(mContextFlags.test(ContextFlags::DebugBit), std::memory_order_relaxed);
/* Low-severity debug messages are disabled by default. */
alDebugMessageControlDirectEXT(this, AL_DONT_CARE_EXT, AL_DONT_CARE_EXT,
AL_DEBUG_SEVERITY_LOW_EXT, 0, nullptr, AL_FALSE);
}
ALCcontext::~ALCcontext()
{
TRACE("Freeing context %p\n", voidp{this});
TRACE("Freeing context {}", voidp{this});
size_t count{std::accumulate(mSourceList.cbegin(), mSourceList.cend(), 0_uz,
[](size_t cur, const SourceSubList &sublist) noexcept -> size_t
{ return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); })};
if(count > 0)
WARN("%zu Source%s not deleted\n", count, (count==1)?"":"s");
WARN("{} Source{} not deleted", count, (count==1)?"":"s");
mSourceList.clear();
mNumSources = 0;
#ifdef ALSOFT_EAX
#if ALSOFT_EAX
eaxUninitialize();
#endif // ALSOFT_EAX
@@ -145,7 +151,7 @@ ALCcontext::~ALCcontext()
[](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t
{ return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); });
if(count > 0)
WARN("%zu AuxiliaryEffectSlot%s not deleted\n", count, (count==1)?"":"s");
WARN("{} AuxiliaryEffectSlot{} not deleted", count, (count==1)?"":"s");
mEffectSlotList.clear();
mNumEffectSlots = 0;
}
@@ -163,9 +169,9 @@ void ALCcontext::init()
auxslots = EffectSlot::CreatePtrArray(0);
else
{
auxslots = EffectSlot::CreatePtrArray(1);
auxslots = EffectSlot::CreatePtrArray(2);
(*auxslots)[0] = mDefaultSlot->mSlot;
std::uninitialized_fill_n(al::to_address(auxslots->end()), 1_uz, nullptr);
(*auxslots)[1] = mDefaultSlot->mSlot;
mDefaultSlot->mState = SlotState::Playing;
}
mActiveAuxSlots.store(std::move(auxslots), std::memory_order_relaxed);
@@ -182,15 +188,17 @@ void ALCcontext::init()
if(sBufferSubDataCompat)
{
auto iter = std::find(mExtensions.begin(), mExtensions.end(), "AL_EXT_SOURCE_RADIUS");
auto iter = std::find(mExtensions.begin(), mExtensions.end(), "AL_EXT_SOURCE_RADIUS"sv);
if(iter != mExtensions.end()) mExtensions.erase(iter);
/* TODO: Would be nice to sort this alphabetically. Needs case-
* insensitive searching.
/* Insert the AL_SOFT_buffer_sub_data extension string between
* AL_SOFT_buffer_length_query and AL_SOFT_callback_buffer.
*/
mExtensions.emplace_back("AL_SOFT_buffer_sub_data");
iter = std::find(mExtensions.begin(), mExtensions.end(), "AL_SOFT_callback_buffer"sv);
mExtensions.emplace(iter, "AL_SOFT_buffer_sub_data"sv);
}
#ifdef ALSOFT_EAX
#if ALSOFT_EAX
eax_initialize_extensions();
#endif // ALSOFT_EAX
@@ -213,19 +221,31 @@ void ALCcontext::init()
mExtensionsString = std::move(extensions);
}
#if ALSOFT_EAX
eax_set_defaults();
#endif
mParams.Position = alu::Vector{0.0f, 0.0f, 0.0f, 1.0f};
mParams.Matrix = alu::Matrix::Identity();
mParams.Velocity = alu::Vector{};
mParams.Gain = mListener.Gain;
mParams.MetersPerUnit = mListener.mMetersPerUnit;
mParams.MetersPerUnit = mListener.mMetersPerUnit
#if ALSOFT_EAX
* eaxGetDistanceFactor()
#endif
;
mParams.AirAbsorptionGainHF = mAirAbsorptionGainHF;
mParams.DopplerFactor = mDopplerFactor;
mParams.SpeedOfSound = mSpeedOfSound * mDopplerVelocity;
mParams.SpeedOfSound = mSpeedOfSound * mDopplerVelocity
#if ALSOFT_EAX
/ eaxGetDistanceFactor()
#endif
;
mParams.SourceDistanceModel = mSourceDistanceModel;
mParams.mDistanceModel = mDistanceModel;
mAsyncEvents = RingBuffer::Create(511, sizeof(AsyncEvent), false);
mAsyncEvents = RingBuffer::Create(1024, sizeof(AsyncEvent), false);
StartEventThrd(this);
@@ -237,35 +257,34 @@ void ALCcontext::deinit()
{
if(sLocalContext == this)
{
WARN("%p released while current on thread\n", voidp{this});
WARN("{} released while current on thread", voidp{this});
auto _ = ContextRef{sLocalContext};
sThreadContext.set(nullptr);
dec_ref();
}
ALCcontext *origctx{this};
if(sGlobalContext.compare_exchange_strong(origctx, nullptr))
if(ALCcontext *origctx{this}; sGlobalContext.compare_exchange_strong(origctx, nullptr))
{
auto _ = ContextRef{origctx};
while(sGlobalContextLock.load()) {
/* Wait to make sure another thread didn't get the context and is
* trying to increment its refcount.
*/
}
dec_ref();
}
bool stopPlayback{};
/* First make sure this context exists in the device's list. */
auto *oldarray = mDevice->mContexts.load(std::memory_order_acquire);
if(auto toremove = static_cast<size_t>(std::count(oldarray->begin(), oldarray->end(), this)))
auto oldarray = al::span{*mDevice->mContexts.load(std::memory_order_acquire)};
if(auto toremove = static_cast<size_t>(std::count(oldarray.begin(), oldarray.end(), this)))
{
using ContextArray = al::FlexArray<ContextBase*>;
const size_t newsize{oldarray->size() - toremove};
const auto newsize = size_t{oldarray.size() - toremove};
auto newarray = ContextArray::Create(newsize);
/* Copy the current/old context handles to the new array, excluding the
* given context.
*/
std::copy_if(oldarray->begin(), oldarray->end(), newarray->begin(),
std::copy_if(oldarray.begin(), oldarray.end(), newarray->begin(),
[this](ContextBase *ctx) { return ctx != this; });
/* Store the new context array in the device. Wait for any current mix
@@ -277,7 +296,7 @@ void ALCcontext::deinit()
stopPlayback = (newsize == 0);
}
else
stopPlayback = oldarray->empty();
stopPlayback = oldarray.empty();
StopEventThrd(this);
@@ -298,7 +317,7 @@ void ALCcontext::applyAllUpdates()
/* busy-wait */
}
#ifdef ALSOFT_EAX
#if ALSOFT_EAX
if(mEaxNeedsCommit)
eaxCommit();
#endif
@@ -315,7 +334,7 @@ void ALCcontext::applyAllUpdates()
}
#ifdef ALSOFT_EAX
#if ALSOFT_EAX
namespace {
template<typename F>
@@ -493,7 +512,7 @@ void ALCcontext::eax_initialize()
eax_ensure_compatibility();
eax_set_defaults();
eax_context_commit_air_absorbtion_hf();
eax_context_commit_air_absorption_hf();
eax_update_speaker_configuration();
eax_initialize_fx_slots();
@@ -547,10 +566,11 @@ unsigned long ALCcontext::eax_detect_speaker_configuration() const
case DevFmtX51: return SPEAKERS_5;
case DevFmtX61: return SPEAKERS_6;
case DevFmtX71: return SPEAKERS_7;
/* 7.1.4 is compatible with 7.1. This could instead be HEADPHONES to
/* 7.1.4(.4) is compatible with 7.1. This could instead be HEADPHONES to
* suggest with-height surround sound (like HRTF).
*/
case DevFmtX714: return SPEAKERS_7;
case DevFmtX7144: return SPEAKERS_7;
/* 3D7.1 is only compatible with 5.1. This could instead be HEADPHONES to
* suggest full-sphere surround sound (like HRTF).
*/
@@ -561,7 +581,8 @@ unsigned long ALCcontext::eax_detect_speaker_configuration() const
*/
case DevFmtAmbi3D: return SPEAKERS_7;
}
ERR(EAX_PREFIX "Unexpected device channel format 0x%x.\n", mDevice->FmtChans);
ERR(EAX_PREFIX "Unexpected device channel format {:#x}.",
uint{al::to_underlying(mDevice->FmtChans)});
return HEADPHONES;
#undef EAX_PREFIX
@@ -619,7 +640,7 @@ void ALCcontext::eax_context_set_defaults()
eax5_context_set_defaults(mEax5);
mEax = mEax5.i;
mEaxVersion = 5;
mEaxDf = EaxDirtyFlags{};
mEaxDf.reset();
}
void ALCcontext::eax_set_defaults()
@@ -746,14 +767,11 @@ void ALCcontext::eax_context_commit_primary_fx_slot_id()
void ALCcontext::eax_context_commit_distance_factor()
{
if(mListener.mMetersPerUnit == mEax.flDistanceFactor)
return;
mListener.mMetersPerUnit = mEax.flDistanceFactor;
/* mEax.flDistanceFactor was changed, so the context props are dirty. */
mPropsDirty = true;
}
void ALCcontext::eax_context_commit_air_absorbtion_hf()
void ALCcontext::eax_context_commit_air_absorption_hf()
{
const auto new_value = level_mb_to_gain(mEax.flAirAbsorptionHF);
@@ -814,16 +832,16 @@ void ALCcontext::eax4_defer_all(const EaxCall& call, Eax4State& state)
dst_d = src;
if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID)
mEaxDf |= eax_primary_fx_slot_id_dirty_bit;
mEaxDf.set(eax_primary_fx_slot_id_dirty_bit);
if(dst_i.flDistanceFactor != dst_d.flDistanceFactor)
mEaxDf |= eax_distance_factor_dirty_bit;
mEaxDf.set(eax_distance_factor_dirty_bit);
if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF)
mEaxDf |= eax_air_absorption_hf_dirty_bit;
mEaxDf.set(eax_air_absorption_hf_dirty_bit);
if(dst_i.flHFReference != dst_d.flHFReference)
mEaxDf |= eax_hf_reference_dirty_bit;
mEaxDf.set(eax_hf_reference_dirty_bit);
}
void ALCcontext::eax4_defer(const EaxCall& call, Eax4State& state)
@@ -834,20 +852,20 @@ void ALCcontext::eax4_defer(const EaxCall& call, Eax4State& state)
eax4_defer_all(call, state);
break;
case EAXCONTEXT_PRIMARYFXSLOTID:
eax_defer<Eax4PrimaryFxSlotIdValidator, eax_primary_fx_slot_id_dirty_bit>(
call, state, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID);
eax_defer<Eax4PrimaryFxSlotIdValidator, eax_primary_fx_slot_id_dirty_bit>(call, state,
&EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID);
break;
case EAXCONTEXT_DISTANCEFACTOR:
eax_defer<Eax4DistanceFactorValidator, eax_distance_factor_dirty_bit>(
call, state, &EAX40CONTEXTPROPERTIES::flDistanceFactor);
eax_defer<Eax4DistanceFactorValidator, eax_distance_factor_dirty_bit>(call, state,
&EAX40CONTEXTPROPERTIES::flDistanceFactor);
break;
case EAXCONTEXT_AIRABSORPTIONHF:
eax_defer<Eax4AirAbsorptionHfValidator, eax_air_absorption_hf_dirty_bit>(
call, state, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF);
eax_defer<Eax4AirAbsorptionHfValidator, eax_air_absorption_hf_dirty_bit>(call, state,
&EAX40CONTEXTPROPERTIES::flAirAbsorptionHF);
break;
case EAXCONTEXT_HFREFERENCE:
eax_defer<Eax4HfReferenceValidator, eax_hf_reference_dirty_bit>(
call, state, &EAX40CONTEXTPROPERTIES::flHFReference);
eax_defer<Eax4HfReferenceValidator, eax_hf_reference_dirty_bit>(call, state,
&EAX40CONTEXTPROPERTIES::flHFReference);
break;
default:
eax_set_misc(call);
@@ -864,19 +882,19 @@ void ALCcontext::eax5_defer_all(const EaxCall& call, Eax5State& state)
dst_d = src;
if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID)
mEaxDf |= eax_primary_fx_slot_id_dirty_bit;
mEaxDf.set(eax_primary_fx_slot_id_dirty_bit);
if(dst_i.flDistanceFactor != dst_d.flDistanceFactor)
mEaxDf |= eax_distance_factor_dirty_bit;
mEaxDf.set(eax_distance_factor_dirty_bit);
if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF)
mEaxDf |= eax_air_absorption_hf_dirty_bit;
mEaxDf.set(eax_air_absorption_hf_dirty_bit);
if(dst_i.flHFReference != dst_d.flHFReference)
mEaxDf |= eax_hf_reference_dirty_bit;
mEaxDf.set(eax_hf_reference_dirty_bit);
if(dst_i.flMacroFXFactor != dst_d.flMacroFXFactor)
mEaxDf |= eax_macro_fx_factor_dirty_bit;
mEaxDf.set(eax_macro_fx_factor_dirty_bit);
}
void ALCcontext::eax5_defer(const EaxCall& call, Eax5State& state)
@@ -887,24 +905,24 @@ void ALCcontext::eax5_defer(const EaxCall& call, Eax5State& state)
eax5_defer_all(call, state);
break;
case EAXCONTEXT_PRIMARYFXSLOTID:
eax_defer<Eax5PrimaryFxSlotIdValidator, eax_primary_fx_slot_id_dirty_bit>(
call, state, &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID);
eax_defer<Eax5PrimaryFxSlotIdValidator, eax_primary_fx_slot_id_dirty_bit>(call, state,
&EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID);
break;
case EAXCONTEXT_DISTANCEFACTOR:
eax_defer<Eax4DistanceFactorValidator, eax_distance_factor_dirty_bit>(
call, state, &EAX50CONTEXTPROPERTIES::flDistanceFactor);
eax_defer<Eax4DistanceFactorValidator, eax_distance_factor_dirty_bit>(call, state,
&EAX50CONTEXTPROPERTIES::flDistanceFactor);
break;
case EAXCONTEXT_AIRABSORPTIONHF:
eax_defer<Eax4AirAbsorptionHfValidator, eax_air_absorption_hf_dirty_bit>(
call, state, &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF);
eax_defer<Eax4AirAbsorptionHfValidator, eax_air_absorption_hf_dirty_bit>(call, state,
&EAX50CONTEXTPROPERTIES::flAirAbsorptionHF);
break;
case EAXCONTEXT_HFREFERENCE:
eax_defer<Eax4HfReferenceValidator, eax_hf_reference_dirty_bit>(
call, state, &EAX50CONTEXTPROPERTIES::flHFReference);
eax_defer<Eax4HfReferenceValidator, eax_hf_reference_dirty_bit>(call, state,
&EAX50CONTEXTPROPERTIES::flHFReference);
break;
case EAXCONTEXT_MACROFXFACTOR:
eax_defer<Eax5MacroFxFactorValidator, eax_macro_fx_factor_dirty_bit>(
call, state, &EAX50CONTEXTPROPERTIES::flMacroFXFactor);
eax_defer<Eax5MacroFxFactorValidator, eax_macro_fx_factor_dirty_bit>(call, state,
&EAX50CONTEXTPROPERTIES::flMacroFXFactor);
break;
default:
eax_set_misc(call);
@@ -922,49 +940,49 @@ void ALCcontext::eax_set(const EaxCall& call)
default: eax_fail_unknown_version();
}
if(version != mEaxVersion)
mEaxDf = ~EaxDirtyFlags();
mEaxDf.set();
mEaxVersion = version;
}
void ALCcontext::eax4_context_commit(Eax4State& state, EaxDirtyFlags& dst_df)
void ALCcontext::eax4_context_commit(Eax4State& state, std::bitset<eax_dirty_bit_count>& dst_df)
{
if(mEaxDf == EaxDirtyFlags{})
if(mEaxDf.none())
return;
eax_context_commit_property<eax_primary_fx_slot_id_dirty_bit>(
state, dst_df, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID);
eax_context_commit_property<eax_distance_factor_dirty_bit>(
state, dst_df, &EAX40CONTEXTPROPERTIES::flDistanceFactor);
eax_context_commit_property<eax_air_absorption_hf_dirty_bit>(
state, dst_df, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF);
eax_context_commit_property<eax_hf_reference_dirty_bit>(
state, dst_df, &EAX40CONTEXTPROPERTIES::flHFReference);
eax_context_commit_property<eax_primary_fx_slot_id_dirty_bit>(state, dst_df,
&EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID);
eax_context_commit_property<eax_distance_factor_dirty_bit>(state, dst_df,
&EAX40CONTEXTPROPERTIES::flDistanceFactor);
eax_context_commit_property<eax_air_absorption_hf_dirty_bit>(state, dst_df,
&EAX40CONTEXTPROPERTIES::flAirAbsorptionHF);
eax_context_commit_property<eax_hf_reference_dirty_bit>(state, dst_df,
&EAX40CONTEXTPROPERTIES::flHFReference);
mEaxDf = EaxDirtyFlags{};
mEaxDf.reset();
}
void ALCcontext::eax5_context_commit(Eax5State& state, EaxDirtyFlags& dst_df)
void ALCcontext::eax5_context_commit(Eax5State& state, std::bitset<eax_dirty_bit_count>& dst_df)
{
if(mEaxDf == EaxDirtyFlags{})
if(mEaxDf.none())
return;
eax_context_commit_property<eax_primary_fx_slot_id_dirty_bit>(
state, dst_df, &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID);
eax_context_commit_property<eax_distance_factor_dirty_bit>(
state, dst_df, &EAX50CONTEXTPROPERTIES::flDistanceFactor);
eax_context_commit_property<eax_air_absorption_hf_dirty_bit>(
state, dst_df, &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF);
eax_context_commit_property<eax_hf_reference_dirty_bit>(
state, dst_df, &EAX50CONTEXTPROPERTIES::flHFReference);
eax_context_commit_property<eax_macro_fx_factor_dirty_bit>(
state, dst_df, &EAX50CONTEXTPROPERTIES::flMacroFXFactor);
eax_context_commit_property<eax_primary_fx_slot_id_dirty_bit>(state, dst_df,
&EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID);
eax_context_commit_property<eax_distance_factor_dirty_bit>(state, dst_df,
&EAX50CONTEXTPROPERTIES::flDistanceFactor);
eax_context_commit_property<eax_air_absorption_hf_dirty_bit>(state, dst_df,
&EAX50CONTEXTPROPERTIES::flAirAbsorptionHF);
eax_context_commit_property<eax_hf_reference_dirty_bit>(state, dst_df,
&EAX50CONTEXTPROPERTIES::flHFReference);
eax_context_commit_property<eax_macro_fx_factor_dirty_bit>(state, dst_df,
&EAX50CONTEXTPROPERTIES::flMacroFXFactor);
mEaxDf = EaxDirtyFlags{};
mEaxDf.reset();
}
void ALCcontext::eax_context_commit()
{
auto dst_df = EaxDirtyFlags{};
auto dst_df = std::bitset<eax_dirty_bit_count>{};
switch(mEaxVersion)
{
@@ -981,25 +999,25 @@ void ALCcontext::eax_context_commit()
break;
}
if(dst_df == EaxDirtyFlags{})
if(dst_df.none())
return;
if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{})
if(dst_df.test(eax_primary_fx_slot_id_dirty_bit))
eax_context_commit_primary_fx_slot_id();
if((dst_df & eax_distance_factor_dirty_bit) != EaxDirtyFlags{})
if(dst_df.test(eax_distance_factor_dirty_bit))
eax_context_commit_distance_factor();
if((dst_df & eax_air_absorption_hf_dirty_bit) != EaxDirtyFlags{})
eax_context_commit_air_absorbtion_hf();
if(dst_df.test(eax_air_absorption_hf_dirty_bit))
eax_context_commit_air_absorption_hf();
if((dst_df & eax_hf_reference_dirty_bit) != EaxDirtyFlags{})
if(dst_df.test(eax_hf_reference_dirty_bit))
eax_context_commit_hf_reference();
if((dst_df & eax_macro_fx_factor_dirty_bit) != EaxDirtyFlags{})
if(dst_df.test(eax_macro_fx_factor_dirty_bit))
eax_context_commit_macro_fx_factor();
if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{})
if(dst_df.test(eax_primary_fx_slot_id_dirty_bit))
eax_update_sources();
}
+65 -45
View File
@@ -1,12 +1,14 @@
#ifndef ALC_CONTEXT_H
#define ALC_CONTEXT_H
#include <array>
#include "config.h"
#include <atomic>
#include <bitset>
#include <cstdint>
#include <deque>
#include <memory>
#include <mutex>
#include <stdint.h>
#include <string>
#include <string_view>
#include <unordered_map>
@@ -18,32 +20,31 @@
#include "AL/alext.h"
#include "al/listener.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "althreads.h"
#include "atomic.h"
#include "core/context.h"
#include "inprogext.h"
#include "fmt/core.h"
#include "intrusive_ptr.h"
#include "opthelpers.h"
#ifdef ALSOFT_EAX
#include "al/eax/call.h"
#if ALSOFT_EAX
#include "al/eax/api.h"
#include "al/eax/exception.h"
#include "al/eax/fx_slot_index.h"
#include "al/eax/fx_slots.h"
#include "al/eax/utils.h"
class EaxCall;
#endif // ALSOFT_EAX
struct ALeffect;
struct ALeffectslot;
struct ALsource;
struct DebugGroup;
struct EffectSlotSubList;
struct SourceSubList;
enum class DebugSource : uint8_t;
enum class DebugType : uint8_t;
enum class DebugSeverity : uint8_t;
enum class DebugSource : std::uint8_t;
enum class DebugType : std::uint8_t;
enum class DebugSeverity : std::uint8_t;
using uint = unsigned int;
@@ -72,9 +73,12 @@ struct DebugLogEntry {
};
struct ALCcontext : public al::intrusive_ref<ALCcontext>, ContextBase {
const al::intrusive_ptr<ALCdevice> mALDevice;
namespace al {
struct Device;
} // namespace al
struct ALCcontext final : public al::intrusive_ref<ALCcontext>, ContextBase {
const al::intrusive_ptr<al::Device> mALDevice;
bool mPropsDirty{true};
bool mDeferUpdates{false};
@@ -118,15 +122,15 @@ struct ALCcontext : public al::intrusive_ref<ALCcontext>, ContextBase {
std::unique_ptr<ALeffectslot> mDefaultSlot;
std::vector<std::string_view> mExtensions;
std::string mExtensionsString{};
std::string mExtensionsString;
std::unordered_map<ALuint,std::string> mSourceNames;
std::unordered_map<ALuint,std::string> mEffectSlotNames;
ALCcontext(al::intrusive_ptr<ALCdevice> device, ContextFlagBitset flags);
ALCcontext(al::intrusive_ptr<al::Device> device, ContextFlagBitset flags);
ALCcontext(const ALCcontext&) = delete;
ALCcontext& operator=(const ALCcontext&) = delete;
~ALCcontext();
~ALCcontext() final;
void init();
/**
@@ -159,12 +163,18 @@ struct ALCcontext : public al::intrusive_ref<ALCcontext>, ContextBase {
*/
void applyAllUpdates();
#ifdef __MINGW32__
[[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]]
#else
[[gnu::format(printf, 3, 4)]]
#endif
void setError(ALenum errorCode, const char *msg, ...);
void setErrorImpl(ALenum errorCode, const fmt::string_view fmt, fmt::format_args args);
template<typename ...Args>
void setError(ALenum errorCode, fmt::format_string<Args...> msg, Args&& ...args)
{ setErrorImpl(errorCode, msg, fmt::make_format_args(args...)); }
[[noreturn]]
void throw_error_impl(ALenum errorCode, const fmt::string_view fmt, fmt::format_args args);
template<typename ...Args> [[noreturn]]
void throw_error(ALenum errorCode, fmt::format_string<Args...> fmt, Args&&... args)
{ throw_error_impl(errorCode, fmt, fmt::make_format_args(args...)); }
void sendDebugMessage(std::unique_lock<std::mutex> &debuglock, DebugSource source,
DebugType type, ALuint id, DebugSeverity severity, std::string_view message);
@@ -191,6 +201,10 @@ private:
*/
class ThreadCtx {
public:
ThreadCtx() = default;
ThreadCtx(const ThreadCtx&) = delete;
auto operator=(const ThreadCtx&) -> ThreadCtx& = delete;
~ThreadCtx();
/* NOLINTBEGIN(readability-convert-member-functions-to-static)
* This should be non-static to invoke construction of the thread-local
@@ -210,7 +224,7 @@ public:
/* Default effect that applies to sources that don't have an effect on send 0. */
static ALeffect sDefaultEffect;
#ifdef ALSOFT_EAX
#if ALSOFT_EAX
bool hasEax() const noexcept { return mEaxIsInitialized; }
bool eaxIsCapable() const noexcept;
@@ -232,7 +246,11 @@ public:
void eaxSetLastError() noexcept;
EaxFxSlotIndex eaxGetPrimaryFxSlotIndex() const noexcept
[[nodiscard]]
auto eaxGetDistanceFactor() const noexcept -> float { return mEax.flDistanceFactor; }
[[nodiscard]]
auto eaxGetPrimaryFxSlotIndex() const noexcept -> EaxFxSlotIndex
{ return mEaxPrimaryFxSlotIndex; }
const ALeffectslot& eaxGetFxSlot(EaxFxSlotIndexValue fx_slot_index) const
@@ -247,11 +265,14 @@ public:
{ mEaxFxSlots.commit(); }
private:
static constexpr auto eax_primary_fx_slot_id_dirty_bit = EaxDirtyFlags{1} << 0;
static constexpr auto eax_distance_factor_dirty_bit = EaxDirtyFlags{1} << 1;
static constexpr auto eax_air_absorption_hf_dirty_bit = EaxDirtyFlags{1} << 2;
static constexpr auto eax_hf_reference_dirty_bit = EaxDirtyFlags{1} << 3;
static constexpr auto eax_macro_fx_factor_dirty_bit = EaxDirtyFlags{1} << 4;
enum {
eax_primary_fx_slot_id_dirty_bit,
eax_distance_factor_dirty_bit,
eax_air_absorption_hf_dirty_bit,
eax_hf_reference_dirty_bit,
eax_macro_fx_factor_dirty_bit,
eax_dirty_bit_count
};
using Eax4Props = EAX40CONTEXTPROPERTIES;
@@ -267,12 +288,11 @@ private:
Eax5Props d; // Deferred.
};
class ContextException : public EaxException
{
class ContextException final : public EaxException {
public:
explicit ContextException(const char* message)
explicit ContextException(const char *message)
: EaxException{"EAX_CONTEXT", message}
{}
{ }
};
struct Eax4PrimaryFxSlotIdValidator {
@@ -420,7 +440,7 @@ private:
int mEaxVersion{}; // Current EAX version.
bool mEaxNeedsCommit{};
EaxDirtyFlags mEaxDf{}; // Dirty flags for the current EAX version.
std::bitset<eax_dirty_bit_count> mEaxDf; // Dirty flags for the current EAX version.
Eax5State mEax123{}; // EAX1/EAX2/EAX3 state.
Eax4State mEax4{}; // EAX4 state.
Eax5State mEax5{}; // EAX5 state.
@@ -450,7 +470,7 @@ private:
// updates a dirty flag.
template<
typename TValidator,
EaxDirtyFlags TDirtyBit,
size_t DirtyBit,
typename TMemberResult,
typename TProps,
typename TState>
@@ -463,20 +483,20 @@ private:
dst_d = src;
if(dst_i != dst_d)
mEaxDf |= TDirtyBit;
mEaxDf.set(DirtyBit);
}
template<
EaxDirtyFlags TDirtyBit,
size_t DirtyBit,
typename TMemberResult,
typename TProps,
typename TState>
void eax_context_commit_property(TState& state, EaxDirtyFlags& dst_df,
void eax_context_commit_property(TState& state, std::bitset<eax_dirty_bit_count>& dst_df,
TMemberResult TProps::*member) noexcept
{
if((mEaxDf & TDirtyBit) != EaxDirtyFlags{})
if(mEaxDf.test(DirtyBit))
{
dst_df |= TDirtyBit;
dst_df.set(DirtyBit);
const auto& src_d = state.d.*member;
state.i.*member = src_d;
mEax.*member = src_d;
@@ -514,7 +534,7 @@ private:
void eax_context_commit_primary_fx_slot_id();
void eax_context_commit_distance_factor();
void eax_context_commit_air_absorbtion_hf();
void eax_context_commit_air_absorption_hf();
void eax_context_commit_hf_reference();
void eax_context_commit_macro_fx_factor();
@@ -529,8 +549,8 @@ private:
void eax5_defer(const EaxCall& call, Eax5State& state);
void eax_set(const EaxCall& call);
void eax4_context_commit(Eax4State& state, EaxDirtyFlags& dst_df);
void eax5_context_commit(Eax5State& state, EaxDirtyFlags& dst_df);
void eax4_context_commit(Eax4State& state, std::bitset<eax_dirty_bit_count>& dst_df);
void eax5_context_commit(Eax5State& state, std::bitset<eax_dirty_bit_count>& dst_df);
void eax_context_commit();
#endif // ALSOFT_EAX
};
@@ -545,7 +565,7 @@ void UpdateContextProps(ALCcontext *context);
inline bool TrapALError{false};
#ifdef ALSOFT_EAX
#if ALSOFT_EAX
auto AL_APIENTRY EAXSet(const GUID *property_set_id, ALuint property_id,
ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum;
+18 -14
View File
@@ -3,6 +3,7 @@
#include "device.h"
#include <algorithm>
#include <cstddef>
#include <numeric>
@@ -10,15 +11,14 @@
#include "al/effect.h"
#include "al/filter.h"
#include "albit.h"
#include "alconfig.h"
#include "alnumeric.h"
#include "atomic.h"
#include "backends/base.h"
#include "core/bformatdec.h"
#include "core/bs2b.h"
#include "core/front_stablizer.h"
#include "core/devformat.h"
#include "core/hrtf.h"
#include "core/logging.h"
#include "core/mastering.h"
#include "core/uhjfilter.h"
#include "flexarray.h"
namespace {
@@ -27,13 +27,14 @@ using voidp = void*;
} // namespace
namespace al {
ALCdevice::ALCdevice(DeviceType type) : DeviceBase{type}
Device::Device(DeviceType type) : DeviceBase{type}
{ }
ALCdevice::~ALCdevice()
Device::~Device()
{
TRACE("Freeing device %p\n", voidp{this});
TRACE("Freeing device {}", voidp{this});
Backend = nullptr;
@@ -41,35 +42,35 @@ ALCdevice::~ALCdevice()
[](size_t cur, const BufferSubList &sublist) noexcept -> size_t
{ return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); })};
if(count > 0)
WARN("%zu Buffer%s not deleted\n", count, (count==1)?"":"s");
WARN("{} Buffer{} not deleted", count, (count==1)?"":"s");
count = std::accumulate(EffectList.cbegin(), EffectList.cend(), 0_uz,
[](size_t cur, const EffectSubList &sublist) noexcept -> size_t
{ return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); });
if(count > 0)
WARN("%zu Effect%s not deleted\n", count, (count==1)?"":"s");
WARN("{} Effect{} not deleted", count, (count==1)?"":"s");
count = std::accumulate(FilterList.cbegin(), FilterList.cend(), 0_uz,
[](size_t cur, const FilterSubList &sublist) noexcept -> size_t
{ return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); });
if(count > 0)
WARN("%zu Filter%s not deleted\n", count, (count==1)?"":"s");
WARN("{} Filter{} not deleted", count, (count==1)?"":"s");
}
void ALCdevice::enumerateHrtfs()
void Device::enumerateHrtfs()
{
mHrtfList = EnumerateHrtf(configValue<std::string>({}, "hrtf-paths"));
if(auto defhrtfopt = configValue<std::string>({}, "default-hrtf"))
{
auto iter = std::find(mHrtfList.begin(), mHrtfList.end(), *defhrtfopt);
if(iter == mHrtfList.end())
WARN("Failed to find default HRTF \"%s\"\n", defhrtfopt->c_str());
WARN("Failed to find default HRTF \"{}\"", *defhrtfopt);
else if(iter != mHrtfList.begin())
std::rotate(mHrtfList.begin(), iter, iter+1);
}
}
auto ALCdevice::getOutputMode1() const noexcept -> OutputMode1
auto Device::getOutputMode1() const noexcept -> OutputMode1
{
if(mContexts.load(std::memory_order_relaxed)->empty())
return OutputMode1::Any;
@@ -88,9 +89,12 @@ auto ALCdevice::getOutputMode1() const noexcept -> OutputMode1
case DevFmtX61: return OutputMode1::X61;
case DevFmtX71: return OutputMode1::X71;
case DevFmtX714:
case DevFmtX7144:
case DevFmtX3D71:
case DevFmtAmbi3D:
break;
}
return OutputMode1::Any;
}
} // namespace al
+37 -32
View File
@@ -1,34 +1,29 @@
#ifndef ALC_DEVICE_H
#define ALC_DEVICE_H
#include <array>
#include "config.h"
#include <atomic>
#include <memory>
#include <mutex>
#include <optional>
#include <stdint.h>
#include <string>
#include <unordered_map>
#include <utility>
#include <string_view>
#include <vector>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "alconfig.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "core/device.h"
#include "inprogext.h"
#include "intrusive_ptr.h"
#ifdef ALSOFT_EAX
#if ALSOFT_EAX
#include "al/eax/x_ram.h"
#endif // ALSOFT_EAX
struct ALbuffer;
struct ALeffect;
struct ALfilter;
struct BackendBase;
struct BufferSubList;
struct EffectSubList;
@@ -37,7 +32,11 @@ struct FilterSubList;
using uint = unsigned int;
struct ALCdevice : public al::intrusive_ref<ALCdevice>, DeviceBase {
struct ALCdevice { virtual ~ALCdevice() = default; };
namespace al {
struct Device final : public ALCdevice, al::intrusive_ref<al::Device>, DeviceBase {
/* This lock protects the device state (format, update size, etc) from
* being from being changed in multiple threads, or being accessed while
* being changed. It's also used to serialize calls to the backend.
@@ -87,7 +86,7 @@ struct ALCdevice : public al::intrusive_ref<ALCdevice>, DeviceBase {
std::mutex FilterLock;
std::vector<FilterSubList> FilterList;
#ifdef ALSOFT_EAX
#if ALSOFT_EAX
ALuint eax_x_ram_free_size{eax_x_ram_max_size};
#endif // ALSOFT_EAX
@@ -96,35 +95,41 @@ struct ALCdevice : public al::intrusive_ref<ALCdevice>, DeviceBase {
std::unordered_map<ALuint,std::string> mEffectNames;
std::unordered_map<ALuint,std::string> mFilterNames;
ALCdevice(DeviceType type);
~ALCdevice();
std::string mVendorOverride;
std::string mVersionOverride;
std::string mRendererOverride;
explicit Device(DeviceType type);
~Device() final;
void enumerateHrtfs();
bool getConfigValueBool(const std::string_view block, const std::string_view key, bool def)
{ return GetConfigValueBool(DeviceName, block, key, def); }
{ return GetConfigValueBool(mDeviceName, block, key, def); }
template<typename T>
inline std::optional<T> configValue(const std::string_view block, const std::string_view key) = delete;
auto configValue(const std::string_view block, const std::string_view key) -> std::optional<T> = delete;
};
template<>
inline std::optional<std::string> ALCdevice::configValue(const std::string_view block, const std::string_view key)
{ return ConfigValueStr(DeviceName, block, key); }
template<>
inline std::optional<int> ALCdevice::configValue(const std::string_view block, const std::string_view key)
{ return ConfigValueInt(DeviceName, block, key); }
template<>
inline std::optional<uint> ALCdevice::configValue(const std::string_view block, const std::string_view key)
{ return ConfigValueUInt(DeviceName, block, key); }
template<>
inline std::optional<float> ALCdevice::configValue(const std::string_view block, const std::string_view key)
{ return ConfigValueFloat(DeviceName, block, key); }
template<>
inline std::optional<bool> ALCdevice::configValue(const std::string_view block, const std::string_view key)
{ return ConfigValueBool(DeviceName, block, key); }
template<> inline
auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional<std::string>
{ return ConfigValueStr(mDeviceName, block, key); }
template<> inline
auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional<int>
{ return ConfigValueInt(mDeviceName, block, key); }
template<> inline
auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional<uint>
{ return ConfigValueUInt(mDeviceName, block, key); }
template<> inline
auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional<float>
{ return ConfigValueFloat(mDeviceName, block, key); }
template<> inline
auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional<bool>
{ return ConfigValueBool(mDeviceName, block, key); }
} // namespace al
/** Stores the latest ALC device error. */
void alcSetError(ALCdevice *device, ALCenum errorCode);
void alcSetError(al::Device *device, ALCenum errorCode);
#endif
+2 -2
View File
@@ -122,7 +122,7 @@ void AutowahState::update(const ContextBase *context, const EffectSlot *slot,
{
auto &props = std::get<AutowahProps>(*props_);
const DeviceBase *device{context->mDevice};
const auto frequency = static_cast<float>(device->Frequency);
const auto frequency = static_cast<float>(device->mSampleRate);
const float ReleaseTime{std::clamp(props.ReleaseTime, 0.001f, 1.0f)};
@@ -214,7 +214,7 @@ void AutowahState::process(const size_t samplesToDo,
chandata->mFilter.z2 = z2;
/* Now, mix the processed sound data to the output. */
MixSamples({mBufferOut.data(), samplesToDo}, samplesOut[outidx].data(),
MixSamples(al::span{mBufferOut}.first(samplesToDo), samplesOut[outidx],
chandata->mCurrentGain, chandata->mTargetGain, samplesToDo);
++chandata;
}
+2 -5
View File
@@ -12,21 +12,18 @@ inline float ReverbBoost{1.0f};
EffectStateFactory *NullStateFactory_getFactory();
EffectStateFactory *ReverbStateFactory_getFactory();
EffectStateFactory *StdReverbStateFactory_getFactory();
EffectStateFactory *AutowahStateFactory_getFactory();
EffectStateFactory *ChorusStateFactory_getFactory();
EffectStateFactory *AutowahStateFactory_getFactory();
EffectStateFactory *CompressorStateFactory_getFactory();
EffectStateFactory *DistortionStateFactory_getFactory();
EffectStateFactory *EchoStateFactory_getFactory();
EffectStateFactory *EqualizerStateFactory_getFactory();
EffectStateFactory *FlangerStateFactory_getFactory();
EffectStateFactory *FshifterStateFactory_getFactory();
EffectStateFactory *ModulatorStateFactory_getFactory();
EffectStateFactory *PshifterStateFactory_getFactory();
EffectStateFactory* VmorpherStateFactory_getFactory();
EffectStateFactory *DedicatedDialogStateFactory_getFactory();
EffectStateFactory *DedicatedLfeStateFactory_getFactory();
EffectStateFactory *DedicatedStateFactory_getFactory();
EffectStateFactory *ConvolutionStateFactory_getFactory();
+52 -84
View File
@@ -58,7 +58,7 @@ constexpr auto lcoeffs_nrml = CalcDirectionCoeffs(std::array{-inv_sqrt2, 0.0f, i
constexpr auto rcoeffs_nrml = CalcDirectionCoeffs(std::array{ inv_sqrt2, 0.0f, inv_sqrt2});
struct ChorusState : public EffectState {
struct ChorusState final : public EffectState {
std::vector<float> mDelayBuffer;
uint mOffset{0};
@@ -94,35 +94,18 @@ struct ChorusState : public EffectState {
const float delay, const float depth, const float feedback, const float rate,
int phase, const EffectTarget target);
void deviceUpdate(const DeviceBase *device, const BufferStorage*) override
{ deviceUpdate(device, ChorusMaxDelay); }
void deviceUpdate(const DeviceBase *device, const BufferStorage*) final;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_,
const EffectTarget target) override
{
auto &props = std::get<ChorusProps>(*props_);
update(context, slot, props.Waveform, props.Delay, props.Depth, props.Feedback, props.Rate,
props.Phase, target);
}
const EffectTarget target) final;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) final;
};
struct FlangerState final : public ChorusState {
void deviceUpdate(const DeviceBase *device, const BufferStorage*) final
{ ChorusState::deviceUpdate(device, FlangerMaxDelay); }
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_,
const EffectTarget target) final
{
auto &props = std::get<FlangerProps>(*props_);
ChorusState::update(context, slot, props.Waveform, props.Delay, props.Depth,
props.Feedback, props.Rate, props.Phase, target);
}
};
void ChorusState::deviceUpdate(const DeviceBase *Device, const float MaxDelay)
void ChorusState::deviceUpdate(const DeviceBase *Device, const BufferStorage*)
{
const auto frequency = static_cast<float>(Device->Frequency);
constexpr auto MaxDelay = std::max(ChorusMaxDelay, FlangerMaxDelay);
const auto frequency = static_cast<float>(Device->mSampleRate);
const size_t maxlen{NextPowerOf2(float2uint(MaxDelay*2.0f*frequency) + 1u)};
if(maxlen != mDelayBuffer.size())
decltype(mDelayBuffer)(maxlen).swap(mDelayBuffer);
@@ -136,34 +119,40 @@ void ChorusState::deviceUpdate(const DeviceBase *Device, const float MaxDelay)
}
void ChorusState::update(const ContextBase *context, const EffectSlot *slot,
const ChorusWaveform waveform, const float delay, const float depth, const float feedback,
const float rate, int phase, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
static constexpr int mindelay{MaxResamplerEdge << gCubicTable.sTableBits};
auto &props = std::get<ChorusProps>(*props_);
/* The LFO depth is scaled to be relative to the sample delay. Clamp the
* delay and depth to allow enough padding for resampling.
*/
const DeviceBase *device{context->mDevice};
const auto frequency = static_cast<float>(device->Frequency);
const auto frequency = static_cast<float>(device->mSampleRate);
mWaveform = waveform;
mWaveform = props.Waveform;
mDelay = std::max(float2int(std::round(delay*frequency*gCubicTable.sTableSteps)), mindelay);
mDepth = std::min(static_cast<float>(mDelay)*depth, static_cast<float>(mDelay-mindelay));
const auto stepscale = float{frequency * gCubicTable.sTableSteps};
mDelay = std::max(float2int(std::round(props.Delay * stepscale)), mindelay);
mDepth = std::min(static_cast<float>(mDelay) * props.Depth,
static_cast<float>(mDelay - mindelay));
mFeedback = feedback;
mFeedback = props.Feedback;
/* Gains for left and right sides */
const bool ispairwise{device->mRenderMode == RenderMode::Pairwise};
const auto lcoeffs = (!ispairwise) ? al::span{lcoeffs_nrml} : al::span{lcoeffs_pw};
const auto rcoeffs = (!ispairwise) ? al::span{rcoeffs_nrml} : al::span{rcoeffs_pw};
/* Attenuate the outputs by -3dB, since we duplicate a single mono input to
* separate left/right outputs.
*/
const auto gain = slot->Gain * (1.0f/al::numbers::sqrt2_v<float>);
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, lcoeffs, slot->Gain, mGains[0].Target);
ComputePanGains(target.Main, rcoeffs, slot->Gain, mGains[1].Target);
ComputePanGains(target.Main, lcoeffs, gain, mGains[0].Target);
ComputePanGains(target.Main, rcoeffs, gain, mGains[1].Target);
if(!(rate > 0.0f))
if(!(props.Rate > 0.0f))
{
mLfoOffset = 0;
mLfoRange = 1;
@@ -176,7 +165,8 @@ void ChorusState::update(const ContextBase *context, const EffectSlot *slot,
* max range to avoid overflow when calculating the displacement.
*/
static constexpr int range_limit{std::numeric_limits<int>::max()/360 - 180};
const uint lfo_range{float2uint(std::min(std::round(frequency/rate), float{range_limit}))};
const auto range = std::round(frequency / props.Rate);
const uint lfo_range{float2uint(std::min(range, float{range_limit}))};
mLfoOffset = mLfoOffset * lfo_range / mLfoRange;
mLfoRange = lfo_range;
@@ -191,7 +181,8 @@ void ChorusState::update(const ContextBase *context, const EffectSlot *slot,
}
/* Calculate lfo phase displacement */
if(phase < 0) phase = 360 + phase;
auto phase = props.Phase;
if(phase < 0) phase += 360;
mLfoDisp = (mLfoRange*static_cast<uint>(phase) + 180) / 360;
}
}
@@ -204,9 +195,6 @@ void ChorusState::calcTriangleDelays(const size_t todo)
const float depth{mDepth};
const int delay{mDelay};
ASSUME(lfo_range > 0);
ASSUME(todo > 0);
auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint
{
const float offset_norm{static_cast<float>(offset) * lfo_scale};
@@ -214,25 +202,24 @@ void ChorusState::calcTriangleDelays(const size_t todo)
};
uint offset{mLfoOffset};
ASSUME(lfo_range > offset);
auto ldelays = mModDelays[0].begin();
for(size_t i{0};i < todo;)
{
size_t rem{std::min(todo-i, size_t{lfo_range-offset})};
do {
mModDelays[0][i++] = gen_lfo(offset++);
} while(--rem);
if(offset == lfo_range)
offset = 0;
const size_t rem{std::min(todo-i, size_t{lfo_range-offset})};
ldelays = std::generate_n(ldelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); });
if(offset == lfo_range) offset = 0;
i += rem;
}
offset = (mLfoOffset+mLfoDisp) % lfo_range;
auto rdelays = mModDelays[1].begin();
for(size_t i{0};i < todo;)
{
size_t rem{std::min(todo-i, size_t{lfo_range-offset})};
do {
mModDelays[1][i++] = gen_lfo(offset++);
} while(--rem);
if(offset == lfo_range)
offset = 0;
const size_t rem{std::min(todo-i, size_t{lfo_range-offset})};
rdelays = std::generate_n(rdelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); });
if(offset == lfo_range) offset = 0;
i += rem;
}
mLfoOffset = static_cast<uint>(mLfoOffset+todo) % lfo_range;
@@ -245,9 +232,6 @@ void ChorusState::calcSinusoidDelays(const size_t todo)
const float depth{mDepth};
const int delay{mDelay};
ASSUME(lfo_range > 0);
ASSUME(todo > 0);
auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint
{
const float offset_norm{static_cast<float>(offset) * lfo_scale};
@@ -255,25 +239,24 @@ void ChorusState::calcSinusoidDelays(const size_t todo)
};
uint offset{mLfoOffset};
ASSUME(lfo_range > offset);
auto ldelays = mModDelays[0].begin();
for(size_t i{0};i < todo;)
{
size_t rem{std::min(todo-i, size_t{lfo_range-offset})};
do {
mModDelays[0][i++] = gen_lfo(offset++);
} while(--rem);
if(offset == lfo_range)
offset = 0;
const size_t rem{std::min(todo-i, size_t{lfo_range-offset})};
ldelays = std::generate_n(ldelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); });
if(offset == lfo_range) offset = 0;
i += rem;
}
offset = (mLfoOffset+mLfoDisp) % lfo_range;
auto rdelays = mModDelays[1].begin();
for(size_t i{0};i < todo;)
{
size_t rem{std::min(todo-i, size_t{lfo_range-offset})};
do {
mModDelays[1][i++] = gen_lfo(offset++);
} while(--rem);
if(offset == lfo_range)
offset = 0;
const size_t rem{std::min(todo-i, size_t{lfo_range-offset})};
rdelays = std::generate_n(rdelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); });
if(offset == lfo_range) offset = 0;
i += rem;
}
mLfoOffset = static_cast<uint>(mLfoOffset+todo) % lfo_range;
@@ -322,10 +305,10 @@ void ChorusState::process(const size_t samplesToDo, const al::span<const FloatBu
++offset;
}
MixSamples(lbuffer.first(samplesToDo), samplesOut, mGains[0].Current.data(),
mGains[0].Target.data(), samplesToDo, 0);
MixSamples(rbuffer.first(samplesToDo), samplesOut, mGains[1].Current.data(),
mGains[1].Target.data(), samplesToDo, 0);
MixSamples(lbuffer.first(samplesToDo), samplesOut, mGains[0].Current, mGains[0].Target,
samplesToDo, 0);
MixSamples(rbuffer.first(samplesToDo), samplesOut, mGains[1].Current, mGains[1].Target,
samplesToDo, 0);
mOffset = offset;
}
@@ -336,15 +319,6 @@ struct ChorusStateFactory final : public EffectStateFactory {
{ return al::intrusive_ptr<EffectState>{new ChorusState{}}; }
};
/* Flanger is basically a chorus with a really short delay. They can both use
* the same processing functions, so piggyback flanger on the chorus functions.
*/
struct FlangerStateFactory final : public EffectStateFactory {
al::intrusive_ptr<EffectState> create() override
{ return al::intrusive_ptr<EffectState>{new FlangerState{}}; }
};
} // namespace
EffectStateFactory *ChorusStateFactory_getFactory()
@@ -352,9 +326,3 @@ EffectStateFactory *ChorusStateFactory_getFactory()
static ChorusStateFactory ChorusFactory{};
return &ChorusFactory;
}
EffectStateFactory *FlangerStateFactory_getFactory()
{
static FlangerStateFactory FlangerFactory{};
return &FlangerFactory;
}
+52 -61
View File
@@ -74,6 +74,7 @@ struct CompressorState final : public EffectState {
float mAttackMult{1.0f};
float mReleaseMult{1.0f};
float mEnvFollower{1.0f};
alignas(16) FloatBufferLine mGains{};
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
@@ -88,8 +89,8 @@ void CompressorState::deviceUpdate(const DeviceBase *device, const BufferStorage
/* Number of samples to do a full attack and release (non-integer sample
* counts are okay).
*/
const float attackCount{static_cast<float>(device->Frequency) * AttackTime};
const float releaseCount{static_cast<float>(device->Frequency) * ReleaseTime};
const float attackCount{static_cast<float>(device->mSampleRate) * AttackTime};
const float releaseCount{static_cast<float>(device->mSampleRate) * ReleaseTime};
/* Calculate per-sample multipliers to attack and release at the desired
* rates.
@@ -115,72 +116,62 @@ void CompressorState::update(const ContextBase*, const EffectSlot *slot,
void CompressorState::process(const size_t samplesToDo,
const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
for(size_t base{0u};base < samplesToDo;)
/* Generate the per-sample gains from the signal envelope. */
float env{mEnvFollower};
if(mEnabled)
{
std::array<float,256> gains;
const size_t td{std::min(gains.size(), samplesToDo-base)};
/* Generate the per-sample gains from the signal envelope. */
float env{mEnvFollower};
if(mEnabled)
for(size_t i{0u};i < samplesToDo;++i)
{
for(size_t i{0u};i < td;++i)
{
/* Clamp the absolute amplitude to the defined envelope limits,
* then attack or release the envelope to reach it.
*/
const float amplitude{std::clamp(std::fabs(samplesIn[0][base+i]), AmpEnvelopeMin,
AmpEnvelopeMax)};
if(amplitude > env)
env = std::min(env*mAttackMult, amplitude);
else if(amplitude < env)
env = std::max(env*mReleaseMult, amplitude);
/* Apply the reciprocal of the envelope to normalize the volume
* (compress the dynamic range).
*/
gains[i] = 1.0f / env;
}
}
else
{
/* Same as above, except the amplitude is forced to 1. This helps
* ensure smooth gain changes when the compressor is turned on and
* off.
/* Clamp the absolute amplitude to the defined envelope limits,
* then attack or release the envelope to reach it.
*/
for(size_t i{0u};i < td;++i)
{
const float amplitude{1.0f};
if(amplitude > env)
env = std::min(env*mAttackMult, amplitude);
else if(amplitude < env)
env = std::max(env*mReleaseMult, amplitude);
const float amplitude{std::clamp(std::fabs(samplesIn[0][i]), AmpEnvelopeMin,
AmpEnvelopeMax)};
if(amplitude > env)
env = std::min(env*mAttackMult, amplitude);
else if(amplitude < env)
env = std::max(env*mReleaseMult, amplitude);
gains[i] = 1.0f / env;
}
/* Apply the reciprocal of the envelope to normalize the volume
* (compress the dynamic range).
*/
mGains[i] = 1.0f / env;
}
mEnvFollower = env;
/* Now compress the signal amplitude to output. */
auto chan = mChans.cbegin();
for(const auto &input : samplesIn)
}
else
{
/* Same as above, except the amplitude is forced to 1. This helps
* ensure smooth gain changes when the compressor is turned on and off.
*/
for(size_t i{0u};i < samplesToDo;++i)
{
const size_t outidx{chan->mTarget};
if(outidx != InvalidChannelIndex)
{
const auto src = al::span{input}.subspan(base);
float *RESTRICT dst{samplesOut[outidx].data() + base};
const float gain{chan->mGain};
if(!(std::fabs(gain) > GainSilenceThreshold))
{
for(size_t i{0u};i < td;i++)
dst[i] += src[i] * gains[i] * gain;
}
}
++chan;
}
const float amplitude{1.0f};
if(amplitude > env)
env = std::min(env*mAttackMult, amplitude);
else if(amplitude < env)
env = std::max(env*mReleaseMult, amplitude);
base += td;
mGains[i] = 1.0f / env;
}
}
mEnvFollower = env;
/* Now compress the signal amplitude to output. */
auto chan = mChans.cbegin();
for(const auto &input : samplesIn)
{
const size_t outidx{chan->mTarget};
if(outidx != InvalidChannelIndex)
{
const auto dst = al::span{samplesOut[outidx]};
const float gain{chan->mGain};
if(!(std::fabs(gain) > GainSilenceThreshold))
{
for(size_t i{0u};i < samplesToDo;++i)
dst[i] += input[i] * mGains[i] * gain;
}
}
++chan;
}
}
+15 -15
View File
@@ -1,5 +1,6 @@
#include "config.h"
#include "config_simd.h"
#include <algorithm>
#include <array>
@@ -11,11 +12,10 @@
#include <functional>
#include <memory>
#include <vector>
#include <variant>
#ifdef HAVE_SSE_INTRINSICS
#if HAVE_SSE_INTRINSICS
#include <xmmintrin.h>
#elif defined(HAVE_NEON)
#elif HAVE_NEON
#include <arm_neon.h>
#endif
@@ -171,7 +171,7 @@ constexpr size_t ConvolveUpdateSamples{ConvolveUpdateSize / 2};
void apply_fir(al::span<float> dst, const al::span<const float> input, const al::span<const float,ConvolveUpdateSamples> filter)
{
auto src = input.begin();
#ifdef HAVE_SSE_INTRINSICS
#if HAVE_SSE_INTRINSICS
std::generate(dst.begin(), dst.end(), [&src,filter]
{
__m128 r4{_mm_setzero_ps()};
@@ -189,7 +189,7 @@ void apply_fir(al::span<float> dst, const al::span<const float> input, const al:
return _mm_cvtss_f32(r4);
});
#elif defined(HAVE_NEON)
#elif HAVE_NEON
std::generate(dst.begin(), dst.end(), [&src,filter]
{
@@ -227,7 +227,7 @@ struct ConvolutionState final : public EffectState {
al::vector<std::array<float,ConvolveUpdateSamples>,16> mFilter;
al::vector<std::array<float,ConvolveUpdateSamples*2>,16> mOutput;
PFFFTSetup mFft{};
PFFFTSetup mFft;
alignas(16) std::array<float,ConvolveUpdateSize> mFftBuffer{};
alignas(16) std::array<float,ConvolveUpdateSize> mFftWorkBuffer{};
@@ -237,7 +237,7 @@ struct ConvolutionState final : public EffectState {
struct ChannelData {
alignas(16) FloatBufferLine mBuffer{};
float mHfScale{}, mLfScale{};
BandSplitter mFilter{};
BandSplitter mFilter;
std::array<float,MaxOutputChannels> Current{};
std::array<float,MaxOutputChannels> Target{};
};
@@ -264,8 +264,8 @@ void ConvolutionState::NormalMix(const al::span<FloatBufferLine> samplesOut,
const size_t samplesToDo)
{
for(auto &chan : mChans)
MixSamples({chan.mBuffer.data(), samplesToDo}, samplesOut, chan.Current.data(),
chan.Target.data(), samplesToDo, 0);
MixSamples(al::span{chan.mBuffer}.first(samplesToDo), samplesOut, chan.Current,
chan.Target, samplesToDo, 0);
}
void ConvolutionState::UpsampleMix(const al::span<FloatBufferLine> samplesOut,
@@ -273,9 +273,9 @@ void ConvolutionState::UpsampleMix(const al::span<FloatBufferLine> samplesOut,
{
for(auto &chan : mChans)
{
const al::span<float> src{chan.mBuffer.data(), samplesToDo};
const auto src = al::span{chan.mBuffer}.first(samplesToDo);
chan.mFilter.processScale(src, chan.mHfScale, chan.mLfScale);
MixSamples(src, samplesOut, chan.Current.data(), chan.Target.data(), samplesToDo, 0);
MixSamples(src, samplesOut, chan.Current, chan.Target, samplesToDo, 0);
}
}
@@ -322,13 +322,13 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorag
* called very infrequently, go ahead and use the polyphase resampler.
*/
PPhaseResampler resampler;
if(device->Frequency != buffer->mSampleRate)
resampler.init(buffer->mSampleRate, device->Frequency);
if(device->mSampleRate != buffer->mSampleRate)
resampler.init(buffer->mSampleRate, device->mSampleRate);
const auto resampledCount = static_cast<uint>(
(uint64_t{buffer->mSampleLen}*device->Frequency+(buffer->mSampleRate-1)) /
(uint64_t{buffer->mSampleLen}*device->mSampleRate+(buffer->mSampleRate-1)) /
buffer->mSampleRate);
const BandSplitter splitter{device->mXOverFreq / static_cast<float>(device->Frequency)};
const BandSplitter splitter{device->mXOverFreq / static_cast<float>(device->mSampleRate)};
for(auto &e : mChans)
e.mFilter = splitter;
+36 -54
View File
@@ -43,7 +43,7 @@ namespace {
using uint = unsigned int;
struct DedicatedState : public EffectState {
struct DedicatedState final : public EffectState {
/* The "dedicated" effect can output to the real output, so should have
* gains for all possible output channels and not just the main ambisonic
* buffer.
@@ -53,90 +53,72 @@ struct DedicatedState : public EffectState {
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) final;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_,
const EffectTarget target) final;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) final;
};
struct DedicatedLfeState final : public DedicatedState {
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) final;
};
void DedicatedState::deviceUpdate(const DeviceBase*, const BufferStorage*)
{
std::fill(mCurrentGains.begin(), mCurrentGains.end(), 0.0f);
}
void DedicatedState::update(const ContextBase*, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
std::fill(mTargetGains.begin(), mTargetGains.end(), 0.0f);
const float Gain{slot->Gain * std::get<DedicatedDialogProps>(*props).Gain};
auto &props = std::get<DedicatedProps>(*props_);
const float Gain{slot->Gain * props.Gain};
/* Dialog goes to the front-center speaker if it exists, otherwise it plays
* from the front-center location.
*/
const size_t idx{target.RealOut ? target.RealOut->ChannelIndex[FrontCenter]
: InvalidChannelIndex};
if(idx != InvalidChannelIndex)
if(props.Target == DedicatedProps::Dialog)
{
mOutTarget = target.RealOut->Buffer;
mTargetGains[idx] = Gain;
/* Dialog goes to the front-center speaker if it exists, otherwise it
* plays from the front-center location.
*/
const size_t idx{target.RealOut ? target.RealOut->ChannelIndex[FrontCenter]
: InvalidChannelIndex};
if(idx != InvalidChannelIndex)
{
mOutTarget = target.RealOut->Buffer;
mTargetGains[idx] = Gain;
}
else
{
static constexpr auto coeffs = CalcDirectionCoeffs(std::array{0.0f, 0.0f, -1.0f});
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs, Gain, mTargetGains);
}
}
else
else if(props.Target == DedicatedProps::Lfe)
{
static constexpr auto coeffs = CalcDirectionCoeffs(std::array{0.0f, 0.0f, -1.0f});
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs, Gain, mTargetGains);
}
}
void DedicatedLfeState::update(const ContextBase*, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
{
std::fill(mTargetGains.begin(), mTargetGains.end(), 0.0f);
const float Gain{slot->Gain * std::get<DedicatedLfeProps>(*props).Gain};
const size_t idx{target.RealOut ? target.RealOut->ChannelIndex[LFE] : InvalidChannelIndex};
if(idx != InvalidChannelIndex)
{
mOutTarget = target.RealOut->Buffer;
mTargetGains[idx] = Gain;
const size_t idx{target.RealOut ? target.RealOut->ChannelIndex[LFE] : InvalidChannelIndex};
if(idx != InvalidChannelIndex)
{
mOutTarget = target.RealOut->Buffer;
mTargetGains[idx] = Gain;
}
}
}
void DedicatedState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
MixSamples({samplesIn[0].data(), samplesToDo}, samplesOut, mCurrentGains.data(),
mTargetGains.data(), samplesToDo, 0);
MixSamples(al::span{samplesIn[0]}.first(samplesToDo), samplesOut, mCurrentGains, mTargetGains,
samplesToDo, 0);
}
struct DedicatedDialogStateFactory final : public EffectStateFactory {
struct DedicatedStateFactory final : public EffectStateFactory {
al::intrusive_ptr<EffectState> create() override
{ return al::intrusive_ptr<EffectState>{new DedicatedState{}}; }
};
struct DedicatedLfeStateFactory final : public EffectStateFactory {
al::intrusive_ptr<EffectState> create() override
{ return al::intrusive_ptr<EffectState>{new DedicatedLfeState{}}; }
};
} // namespace
EffectStateFactory *DedicatedDialogStateFactory_getFactory()
EffectStateFactory *DedicatedStateFactory_getFactory()
{
static DedicatedDialogStateFactory DedicatedFactory{};
return &DedicatedFactory;
}
EffectStateFactory *DedicatedLfeStateFactory_getFactory()
{
static DedicatedLfeStateFactory DedicatedFactory{};
static DedicatedStateFactory DedicatedFactory{};
return &DedicatedFactory;
}
+16 -8
View File
@@ -87,7 +87,7 @@ void DistortionState::update(const ContextBase *context, const EffectSlot *slot,
/* Divide normalized frequency by the amount of oversampling done during
* processing.
*/
auto frequency = static_cast<float>(device->Frequency);
auto frequency = static_cast<float>(device->mSampleRate);
mLowpass.setParamsFromBandwidth(BiquadType::LowPass, cutoff/frequency/4.0f, 1.0f, bandwidth);
cutoff = props.EQCenter;
@@ -124,7 +124,7 @@ void DistortionState::process(const size_t samplesToDo, const al::span<const Flo
* (which is fortunately first step of distortion). So combine three
* operations into the one.
*/
mLowpass.process({mBuffer[0].data(), todo}, mBuffer[1]);
mLowpass.process(al::span{mBuffer[0]}.first(todo), mBuffer[1]);
/* Second step, do distortion using waveshaper function to emulate
* signal processing during tube overdriving. Three steps of
@@ -142,22 +142,30 @@ void DistortionState::process(const size_t samplesToDo, const al::span<const Flo
proc_sample);
/* Third step, do bandpass filtering of distorted signal. */
mBandpass.process({mBuffer[0].data(), todo}, mBuffer[1]);
mBandpass.process(al::span{mBuffer[0]}.first(todo), mBuffer[1]);
todo >>= 2;
auto outgains = mGain.cbegin();
for(FloatBufferLine &RESTRICT output : samplesOut)
auto proc_bufline = [this,base,todo,&outgains](FloatBufferSpan output)
{
/* Fourth step, final, do attenuation and perform decimation,
* storing only one sample out of four.
*/
const float gain{*(outgains++)};
if(!(std::fabs(gain) > GainSilenceThreshold))
continue;
return;
for(size_t i{0u};i < todo;i++)
output[base+i] += gain * mBuffer[1][i*4];
}
auto src = mBuffer[1].cbegin();
const auto dst = al::span{output}.subspan(base, todo);
auto dec_sample = [gain,&src](float sample) noexcept -> float
{
sample += *src * gain;
src += 4;
return sample;
};
std::transform(dst.begin(), dst.end(), dst.begin(), dec_sample);
};
std::for_each(samplesOut.begin(), samplesOut.end(), proc_bufline);
base += todo;
}
+4 -4
View File
@@ -78,7 +78,7 @@ struct EchoState final : public EffectState {
void EchoState::deviceUpdate(const DeviceBase *Device, const BufferStorage*)
{
const auto frequency = static_cast<float>(Device->Frequency);
const auto frequency = static_cast<float>(Device->mSampleRate);
// Use the next power of 2 for the buffer length, so the tap offsets can be
// wrapped using a mask instead of a modulo
@@ -100,7 +100,7 @@ void EchoState::update(const ContextBase *context, const EffectSlot *slot,
{
auto &props = std::get<EchoProps>(*props_);
const DeviceBase *device{context->mDevice};
const auto frequency = static_cast<float>(device->Frequency);
const auto frequency = static_cast<float>(device->mSampleRate);
mDelayTap[0] = std::max(float2uint(std::round(props.Delay*frequency)), 1u);
mDelayTap[1] = float2uint(std::round(props.LRDelay*frequency)) + mDelayTap[0];
@@ -160,8 +160,8 @@ void EchoState::process(const size_t samplesToDo, const al::span<const FloatBuff
mOffset = offset;
for(size_t c{0};c < 2;c++)
MixSamples({mTempBuffer[c].data(), samplesToDo}, samplesOut, mGains[c].Current.data(),
mGains[c].Target.data(), samplesToDo, 0);
MixSamples(al::span{mTempBuffer[c]}.first(samplesToDo), samplesOut, mGains[c].Current,
mGains[c].Target, samplesToDo, 0);
}
+5 -6
View File
@@ -123,7 +123,7 @@ void EqualizerState::update(const ContextBase *context, const EffectSlot *slot,
{
auto &props = std::get<EqualizerProps>(*props_);
const DeviceBase *device{context->mDevice};
auto frequency = static_cast<float>(device->Frequency);
auto frequency = static_cast<float>(device->mSampleRate);
/* Calculate coefficients for the each type of filter. Note that the shelf
* and peaking filters' gain is for the centerpoint of the transition band,
@@ -169,18 +169,17 @@ void EqualizerState::update(const ContextBase *context, const EffectSlot *slot,
void EqualizerState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
const al::span<float> buffer{mSampleBuffer.data(), samplesToDo};
const auto buffer = al::span{mSampleBuffer}.first(samplesToDo);
auto chan = mChans.begin();
for(const auto &input : samplesIn)
{
const size_t outidx{chan->mTargetChannel};
if(outidx != InvalidChannelIndex)
if(const size_t outidx{chan->mTargetChannel}; outidx != InvalidChannelIndex)
{
const al::span<const float> inbuf{input.data(), samplesToDo};
const auto inbuf = al::span{input}.first(samplesToDo);
DualBiquad{chan->mFilter[0], chan->mFilter[1]}.process(inbuf, buffer);
DualBiquad{chan->mFilter[2], chan->mFilter[3]}.process(buffer, buffer);
MixSamples(buffer, samplesOut[outidx].data(), chan->mCurrentGain, chan->mTargetGain,
MixSamples(buffer, samplesOut[outidx], chan->mCurrentGain, chan->mTargetGain,
samplesToDo);
}
++chan;
+3 -3
View File
@@ -134,7 +134,7 @@ void FshifterState::update(const ContextBase *context, const EffectSlot *slot,
auto &props = std::get<FshifterProps>(*props_);
const DeviceBase *device{context->mDevice};
const float step{props.Frequency / static_cast<float>(device->Frequency)};
const float step{props.Frequency / static_cast<float>(device->mSampleRate)};
mPhaseStep[0] = mPhaseStep[1] = fastf2u(std::min(step, 1.0f) * MixerFracOne);
switch(props.LeftDirection)
@@ -239,8 +239,8 @@ void FshifterState::process(const size_t samplesToDo, const al::span<const Float
mPhase[c] = phase_idx;
/* Now, mix the processed sound data to the output. */
MixSamples({mBufferOut.data(), samplesToDo}, samplesOut, mGains[c].Current.data(),
mGains[c].Target.data(), std::max(samplesToDo, 512_uz), 0);
MixSamples(al::span{mBufferOut}.first(samplesToDo), samplesOut, mGains[c].Current,
mGains[c].Target, std::max(samplesToDo, 512_uz), 0);
}
}
+6 -7
View File
@@ -122,10 +122,10 @@ void ModulatorState::update(const ContextBase *context, const EffectSlot *slot,
* many iterations per sample.
*/
const float samplesPerCycle{props.Frequency > 0.0f
? static_cast<float>(device->Frequency)/props.Frequency + 0.5f
? static_cast<float>(device->mSampleRate)/props.Frequency + 0.5f
: 1.0f};
const uint range{static_cast<uint>(std::clamp(samplesPerCycle, 1.0f,
static_cast<float>(device->Frequency)))};
static_cast<float>(device->mSampleRate)))};
mIndex = static_cast<uint>(uint64_t{mIndex} * range / mRange);
mRange = range;
@@ -155,7 +155,7 @@ void ModulatorState::update(const ContextBase *context, const EffectSlot *slot,
mSampleGen.emplace<SquareFunc>();
}
float f0norm{props.HighPassCutoff / static_cast<float>(device->Frequency)};
float f0norm{props.HighPassCutoff / static_cast<float>(device->mSampleRate)};
f0norm = std::clamp(f0norm, 1.0f/512.0f, 0.49f);
/* Bandwidth value is constant in octaves. */
mChans[0].mFilter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f);
@@ -198,14 +198,13 @@ void ModulatorState::process(const size_t samplesToDo, const al::span<const Floa
auto chandata = mChans.begin();
for(const auto &input : samplesIn)
{
const size_t outidx{chandata->mTargetChannel};
if(outidx != InvalidChannelIndex)
if(const size_t outidx{chandata->mTargetChannel}; outidx != InvalidChannelIndex)
{
chandata->mFilter.process({input.data(), samplesToDo}, mBuffer);
chandata->mFilter.process(al::span{input}.first(samplesToDo), mBuffer);
std::transform(mBuffer.cbegin(), mBuffer.cbegin()+samplesToDo, mModSamples.cbegin(),
mBuffer.begin(), std::multiplies<>{});
MixSamples({mBuffer.data(), samplesToDo}, samplesOut[outidx].data(),
MixSamples(al::span{mBuffer}.first(samplesToDo), samplesOut[outidx],
chandata->mCurrentGain, chandata->mTargetGain, std::min(samplesToDo, 64_uz));
}
++chandata;
+2 -2
View File
@@ -306,8 +306,8 @@ void PshifterState::process(const size_t samplesToDo,
}
/* Now, mix the processed sound data to the output. */
MixSamples({mBufferOut.data(), samplesToDo}, samplesOut, mCurrentGains.data(),
mTargetGains.data(), std::max(samplesToDo, 512_uz), 0);
MixSamples(al::span{mBufferOut}.first(samplesToDo), samplesOut, mCurrentGains, mTargetGains,
std::max(samplesToDo, 512_uz), 0);
}
File diff suppressed because it is too large Load Diff
+4 -3
View File
@@ -249,7 +249,7 @@ void VmorpherState::update(const ContextBase *context, const EffectSlot *slot,
{
auto &props = std::get<VmorpherProps>(*props_);
const DeviceBase *device{context->mDevice};
const float frequency{static_cast<float>(device->Frequency)};
const float frequency{static_cast<float>(device->mSampleRate)};
const float step{props.Rate / frequency};
mStep = fastf2u(std::clamp(step*WaveformFracOne, 0.0f, WaveformFracOne-1.0f));
@@ -286,6 +286,8 @@ void VmorpherState::update(const ContextBase *context, const EffectSlot *slot,
void VmorpherState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
alignas(16) std::array<float,MaxUpdateSamples> blended{};
/* Following the EFX specification for a conformant implementation which describes
* the effect as a pair of 4-band formant filters blended together using an LFO.
*/
@@ -324,12 +326,11 @@ void VmorpherState::process(const size_t samplesToDo, const al::span<const Float
vowelB[2].process(&input[base], mSampleBufferB.data(), td);
vowelB[3].process(&input[base], mSampleBufferB.data(), td);
alignas(16) std::array<float,MaxUpdateSamples> blended;
for(size_t i{0u};i < td;i++)
blended[i] = lerpf(mSampleBufferA[i], mSampleBufferB[i], mLfo[i]);
/* Now, mix the processed sound data to the output. */
MixSamples({blended.data(), td}, samplesOut[outidx].data()+base,
MixSamples(al::span{blended}.first(td), al::span{samplesOut[outidx]}.subspan(base),
chandata->mCurrentGain, chandata->mTargetGain, samplesToDo-base);
++chandata;
}
+4 -2
View File
@@ -3,9 +3,11 @@
#include "events.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/logging.h"
#include "device.h"
#include "fmt/core.h"
namespace {
@@ -19,7 +21,7 @@ ALCenum EnumFromEventType(const alc::EventType type)
case alc::EventType::DeviceRemoved: return ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT;
case alc::EventType::Count: break;
}
throw std::runtime_error{"Invalid EventType: "+std::to_string(al::to_underlying(type))};
throw std::runtime_error{fmt::format("Invalid EventType: {}", int{al::to_underlying(type)})};
}
} // namespace
@@ -74,7 +76,7 @@ FORCE_ALIGN ALCboolean ALC_APIENTRY alcEventControlSOFT(ALCsizei count, const AL
auto etype = alc::GetEventType(type);
if(!etype)
{
WARN("Invalid event type: 0x%04x\n", type);
WARN("Invalid event type: {:#04x}", as_unsigned(type));
alcSetError(nullptr, ALC_INVALID_ENUM);
return ALC_FALSE;
}
+14 -11
View File
@@ -1,12 +1,14 @@
#ifndef ALC_EXPORT_LIST_H
#define ALC_EXPORT_LIST_H
#include "config.h"
#include "AL/alc.h"
#include "AL/al.h"
#include "AL/alext.h"
#include "inprogext.h"
#ifdef ALSOFT_EAX
#if ALSOFT_EAX
#include "context.h"
#include "al/eax/x_ram.h"
#endif
@@ -202,11 +204,6 @@ inline const FuncExport alcFunctions[]{
DECL(alGetBuffer3PtrSOFT),
DECL(alGetBufferPtrvSOFT),
DECL(alAuxiliaryEffectSlotPlaySOFT),
DECL(alAuxiliaryEffectSlotPlayvSOFT),
DECL(alAuxiliaryEffectSlotStopSOFT),
DECL(alAuxiliaryEffectSlotStopvSOFT),
DECL(alSourcePlayAtTimeSOFT),
DECL(alSourcePlayAtTimevSOFT),
@@ -220,6 +217,10 @@ inline const FuncExport alcFunctions[]{
DECL(alPushDebugGroupEXT),
DECL(alPopDebugGroupEXT),
DECL(alGetDebugMessageLogEXT),
DECL(alObjectLabelEXT),
DECL(alGetObjectLabelEXT),
DECL(alGetPointerEXT),
DECL(alGetPointervEXT),
/* Direct Context functions */
DECL(alcGetProcAddress2),
@@ -370,15 +371,15 @@ inline const FuncExport alcFunctions[]{
DECL(alPushDebugGroupDirectEXT),
DECL(alPopDebugGroupDirectEXT),
DECL(alGetDebugMessageLogDirectEXT),
DECL(alObjectLabelEXT),
DECL(alObjectLabelDirectEXT),
DECL(alGetObjectLabelEXT),
DECL(alGetObjectLabelDirectEXT),
DECL(alGetPointerDirectEXT),
DECL(alGetPointervDirectEXT),
/* Extra functions */
DECL(alsoft_set_log_callback),
};
#ifdef ALSOFT_EAX
#if ALSOFT_EAX
inline const std::array eaxFunctions{
DECL(EAXGet),
DECL(EAXSet),
@@ -632,6 +633,8 @@ inline const EnumExport alcEnumerations[]{
DECL(AL_FORMAT_51CHN_I32),
DECL(AL_FORMAT_61CHN_I32),
DECL(AL_FORMAT_71CHN_I32),
DECL(AL_FORMAT_BFORMAT2D_I32),
DECL(AL_FORMAT_BFORMAT3D_I32),
DECL(AL_FORMAT_UHJ2CHN_I32_SOFT),
DECL(AL_FORMAT_UHJ3CHN_I32_SOFT),
DECL(AL_FORMAT_UHJ4CHN_I32_SOFT),
@@ -908,7 +911,7 @@ inline const EnumExport alcEnumerations[]{
DECL(AL_STOP_SOURCES_ON_DISCONNECT_SOFT),
};
#ifdef ALSOFT_EAX
#if ALSOFT_EAX
inline const std::array eaxEnumerations{
DECL(AL_EAX_RAM_SIZE),
DECL(AL_EAX_RAM_FREE),
@@ -916,7 +919,7 @@ inline const std::array eaxEnumerations{
DECL(AL_STORAGE_HARDWARE),
DECL(AL_STORAGE_ACCESSIBLE),
};
#endif // ALSOFT_EAX
#endif
#undef DECL
#endif /* ALC_EXPORT_LIST_H */
+13 -20
View File
@@ -37,26 +37,11 @@ void AL_APIENTRY alFlushMappedBufferDirectSOFT(ALCcontext *context, ALuint buffe
#endif
#endif
#ifndef AL_SOFT_bformat_hoa
#define AL_SOFT_bformat_hoa
#define AL_UNPACK_AMBISONIC_ORDER_SOFT 0x199D
#endif
#ifndef AL_SOFT_convolution_effect
#define AL_SOFT_convolution_effect
#define AL_EFFECT_CONVOLUTION_SOFT 0xA000
#define AL_CONVOLUTION_ORIENTATION_SOFT 0x100F /* same as AL_ORIENTATION */
#define AL_EFFECTSLOT_STATE_SOFT 0x199E
typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTPLAYSOFT)(ALuint slotid) AL_API_NOEXCEPT17;
typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTPLAYVSOFT)(ALsizei n, const ALuint *slotids) AL_API_NOEXCEPT17;
typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTSTOPSOFT)(ALuint slotid) AL_API_NOEXCEPT17;
typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTSTOPVSOFT)(ALsizei n, const ALuint *slotids) AL_API_NOEXCEPT17;
#ifdef AL_ALEXT_PROTOTYPES
AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint slotid) AL_API_NOEXCEPT;
AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei n, const ALuint *slotids) AL_API_NOEXCEPT;
AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint slotid) AL_API_NOEXCEPT;
AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *slotids) AL_API_NOEXCEPT;
#endif
#endif
#ifndef AL_SOFT_hold_on_disconnect
@@ -80,15 +65,18 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *
#define AL_FORMAT_71CHN_I32 0x19E5
#define AL_FORMAT_71CHN_FLOAT32 0x19E6
#define AL_FORMAT_UHJ2CHN_I32_SOFT 0x19E7
#define AL_FORMAT_UHJ3CHN_I32_SOFT 0x19E8
#define AL_FORMAT_UHJ4CHN_I32_SOFT 0x19E9
#define AL_FORMAT_BFORMAT2D_I32 0x19E7
#define AL_FORMAT_BFORMAT3D_I32 0x19E8
#define AL_FORMAT_UHJ2CHN_I32_SOFT 0x19E9
#define AL_FORMAT_UHJ3CHN_I32_SOFT 0x19EA
#define AL_FORMAT_UHJ4CHN_I32_SOFT 0x19EB
#endif
#ifndef AL_SOFT_source_panning
#define AL_SOFT_source_panning
#define AL_PANNING_ENABLED_SOFT 0x19EA
#define AL_PAN_SOFT 0x19EB
#define AL_PANNING_ENABLED_SOFT 0x19EC
#define AL_PAN_SOFT 0x19ED
#endif
/* Non-standard exports. Not part of any extension. */
@@ -101,6 +89,11 @@ void ALC_APIENTRY alsoft_set_log_callback(LPALSOFTLOGCALLBACK callback, void *us
AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint src, ALsizei nb,
const ALuint *buffers) noexcept;
AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint slotid) noexcept;
AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei n, const ALuint *slotids) noexcept;
AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint slotid) noexcept;
AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *slotids) noexcept;
AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname) AL_API_NOEXCEPT;
AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values) AL_API_NOEXCEPT;
ALint64SOFT AL_APIENTRY alGetInteger64DirectSOFT(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT;
+199 -100
View File
@@ -26,10 +26,10 @@
#include <cassert>
#include <chrono>
#include <cmath>
#include <cstddef>
#include <cstdio>
#include <cstdint>
#include <functional>
#include <iterator>
#include <memory>
#include <numeric>
#include <optional>
@@ -42,7 +42,6 @@
#include "AL/alext.h"
#include "alc/context.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
@@ -77,48 +76,77 @@ using namespace std::string_view_literals;
using std::chrono::seconds;
using std::chrono::nanoseconds;
inline const char *GetLabelFromChannel(Channel channel)
[[nodiscard]]
auto GetLabelFromChannel(Channel channel) -> std::string_view
{
switch(channel)
{
case FrontLeft: return "front-left";
case FrontRight: return "front-right";
case FrontCenter: return "front-center";
case LFE: return "lfe";
case BackLeft: return "back-left";
case BackRight: return "back-right";
case BackCenter: return "back-center";
case SideLeft: return "side-left";
case SideRight: return "side-right";
case FrontLeft: return "front-left"sv;
case FrontRight: return "front-right"sv;
case FrontCenter: return "front-center"sv;
case LFE: return "lfe"sv;
case BackLeft: return "back-left"sv;
case BackRight: return "back-right"sv;
case BackCenter: return "back-center"sv;
case SideLeft: return "side-left"sv;
case SideRight: return "side-right"sv;
case TopFrontLeft: return "top-front-left";
case TopFrontCenter: return "top-front-center";
case TopFrontRight: return "top-front-right";
case TopCenter: return "top-center";
case TopBackLeft: return "top-back-left";
case TopBackCenter: return "top-back-center";
case TopBackRight: return "top-back-right";
case TopFrontLeft: return "top-front-left"sv;
case TopFrontCenter: return "top-front-center"sv;
case TopFrontRight: return "top-front-right"sv;
case TopCenter: return "top-center"sv;
case TopBackLeft: return "top-back-left"sv;
case TopBackCenter: return "top-back-center"sv;
case TopBackRight: return "top-back-right"sv;
case Aux0: return "Aux0";
case Aux1: return "Aux1";
case Aux2: return "Aux2";
case Aux3: return "Aux3";
case Aux4: return "Aux4";
case Aux5: return "Aux5";
case Aux6: return "Aux6";
case Aux7: return "Aux7";
case Aux8: return "Aux8";
case Aux9: return "Aux9";
case Aux10: return "Aux10";
case Aux11: return "Aux11";
case Aux12: return "Aux12";
case Aux13: return "Aux13";
case Aux14: return "Aux14";
case Aux15: return "Aux15";
case BottomFrontLeft: return "bottom-front-left"sv;
case BottomFrontRight: return "bottom-front-right"sv;
case BottomBackLeft: return "bottom-back-left"sv;
case BottomBackRight: return "bottom-back-right"sv;
case MaxChannels: break;
case Aux0: return "Aux0"sv;
case Aux1: return "Aux1"sv;
case Aux2: return "Aux2"sv;
case Aux3: return "Aux3"sv;
case Aux4: return "Aux4"sv;
case Aux5: return "Aux5"sv;
case Aux6: return "Aux6"sv;
case Aux7: return "Aux7"sv;
case Aux8: return "Aux8"sv;
case Aux9: return "Aux9"sv;
case Aux10: return "Aux10"sv;
case Aux11: return "Aux11"sv;
case Aux12: return "Aux12"sv;
case Aux13: return "Aux13"sv;
case Aux14: return "Aux14"sv;
case Aux15: return "Aux15"sv;
case MaxChannels: break;
}
return "(unknown)";
return "(unknown)"sv;
}
[[nodiscard]]
auto GetLayoutName(DevAmbiLayout layout) noexcept -> std::string_view
{
switch(layout)
{
case DevAmbiLayout::FuMa: return "FuMa"sv;
case DevAmbiLayout::ACN: return "ACN"sv;
}
return "<unknown layout enum>"sv;
}
[[nodiscard]]
auto GetScalingName(DevAmbiScaling scaling) noexcept -> std::string_view
{
switch(scaling)
{
case DevAmbiScaling::FuMa: return "FuMa"sv;
case DevAmbiScaling::SN3D: return "SN3D"sv;
case DevAmbiScaling::N3D: return "N3D"sv;
}
return "<unknown scaling enum>"sv;
}
@@ -136,14 +164,14 @@ std::unique_ptr<FrontStablizer> CreateStablizer(const size_t outchans, const uin
return stablizer;
}
void AllocChannels(ALCdevice *device, const size_t main_chans, const size_t real_chans)
void AllocChannels(al::Device *device, const size_t main_chans, const size_t real_chans)
{
TRACE("Channel config, Main: %zu, Real: %zu\n", main_chans, real_chans);
TRACE("Channel config, Main: {}, Real: {}", main_chans, real_chans);
/* Allocate extra channels for any post-filter output. */
const size_t num_chans{main_chans + real_chans};
TRACE("Allocating %zu channels, %zu bytes\n", num_chans,
TRACE("Allocating {} channels, {} bytes", num_chans,
num_chans*sizeof(device->MixBuffer[0]));
device->MixBuffer.resize(num_chans);
al::span<FloatBufferLine> buffer{device->MixBuffer};
@@ -235,7 +263,8 @@ struct DecoderConfig<DualBand, 0> {
using DecoderView = DecoderConfig<DualBand, 0>;
void InitNearFieldCtrl(ALCdevice *device, const float ctrl_dist, const uint order, const bool is3d)
void InitNearFieldCtrl(al::Device *device, const float ctrl_dist, const uint order,
const bool is3d)
{
static const std::array<uint,MaxAmbiOrder+1> chans_per_order2d{{1, 2, 2, 2}};
static const std::array<uint,MaxAmbiOrder+1> chans_per_order3d{{1, 3, 5, 7}};
@@ -245,10 +274,10 @@ void InitNearFieldCtrl(ALCdevice *device, const float ctrl_dist, const uint orde
return;
device->AvgSpeakerDist = std::clamp(ctrl_dist, 0.1f, 10.0f);
TRACE("Using near-field reference distance: %.2f meters\n", device->AvgSpeakerDist);
TRACE("Using near-field reference distance: {:.2f} meters", device->AvgSpeakerDist);
const float w1{SpeedOfSoundMetersPerSec /
(device->AvgSpeakerDist * static_cast<float>(device->Frequency))};
(device->AvgSpeakerDist * static_cast<float>(device->mSampleRate))};
device->mNFCtrlFilter.init(w1);
auto iter = std::copy_n(is3d ? chans_per_order3d.begin() : chans_per_order2d.begin(), order+1u,
@@ -256,7 +285,7 @@ void InitNearFieldCtrl(ALCdevice *device, const float ctrl_dist, const uint orde
std::fill(iter, device->NumChannelsPerOrder.end(), 0u);
}
void InitDistanceComp(ALCdevice *device, const al::span<const Channel> channels,
void InitDistanceComp(al::Device *device, const al::span<const Channel> channels,
const al::span<const float,MaxOutputChannels> dists)
{
const float maxdist{std::accumulate(dists.begin(), dists.end(), 0.0f,
@@ -265,7 +294,7 @@ void InitDistanceComp(ALCdevice *device, const al::span<const Channel> channels,
if(!device->getConfigValueBool("decoder", "distance-comp", true) || !(maxdist > 0.0f))
return;
const auto distSampleScale = static_cast<float>(device->Frequency) / SpeedOfSoundMetersPerSec;
const auto distSampleScale = static_cast<float>(device->mSampleRate)/SpeedOfSoundMetersPerSec;
struct DistCoeffs { uint Length{}; float Gain{}; };
std::vector<DistCoeffs> ChanDelay;
@@ -290,7 +319,7 @@ void InitDistanceComp(ALCdevice *device, const al::span<const Channel> channels,
float delay{std::floor((maxdist - distance)*distSampleScale + 0.5f)};
if(delay > float{DistanceComp::MaxDelay-1})
{
ERR("Delay for channel %zu (%s) exceeds buffer length (%f > %d)\n", idx,
ERR("Delay for channel {} ({}) exceeds buffer length ({:f} > {})", idx,
GetLabelFromChannel(ch), delay, DistanceComp::MaxDelay-1);
delay = float{DistanceComp::MaxDelay-1};
}
@@ -298,7 +327,7 @@ void InitDistanceComp(ALCdevice *device, const al::span<const Channel> channels,
ChanDelay.resize(std::max(ChanDelay.size(), idx+1_uz));
ChanDelay[idx].Length = static_cast<uint>(delay);
ChanDelay[idx].Gain = distance / maxdist;
TRACE("Channel %s distance comp: %u samples, %f gain\n", GetLabelFromChannel(ch),
TRACE("Channel {} distance comp: {} samples, {:f} gain", GetLabelFromChannel(ch),
ChanDelay[idx].Length, ChanDelay[idx].Gain);
/* Round up to the next 4th sample, so each channel buffer starts
@@ -341,8 +370,8 @@ constexpr auto GetAmbiLayout(DevAmbiLayout layouttype) noexcept
}
DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf,
DecoderConfig<DualBand,MaxOutputChannels> &decoder)
auto MakeDecoderView(al::Device *device, const AmbDecConf *conf,
DecoderConfig<DualBand,MaxOutputChannels> &decoder) -> DecoderView
{
DecoderView ret{};
@@ -372,7 +401,7 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf,
const auto lfmatrix = conf->LFMatrix;
uint chan_count{0};
for(auto &speaker : al::span<const AmbDecConf::SpeakerConf>{conf->Speakers})
for(auto &speaker : al::span{std::as_const(conf->Speakers)})
{
/* NOTE: AmbDec does not define any standard speaker names, however
* for this to work we have to by able to find the output channel
@@ -391,44 +420,57 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf,
* RFT = Top front right
* LBT = Top back left
* RBT = Top back right
* LFB = Bottom front left
* RFB = Bottom front right
* LBB = Bottom back left
* RBB = Bottom back right
*
* Additionally, surround51 will acknowledge back speakers for side
* channels, to avoid issues with an ambdec expecting 5.1 to use the
* back channels.
*/
Channel ch{};
if(speaker.Name == "LF")
if(speaker.Name == "LF"sv)
ch = FrontLeft;
else if(speaker.Name == "RF")
else if(speaker.Name == "RF"sv)
ch = FrontRight;
else if(speaker.Name == "CE")
else if(speaker.Name == "CE"sv)
ch = FrontCenter;
else if(speaker.Name == "LS")
else if(speaker.Name == "LS"sv)
ch = SideLeft;
else if(speaker.Name == "RS")
else if(speaker.Name == "RS"sv)
ch = SideRight;
else if(speaker.Name == "LB")
else if(speaker.Name == "LB"sv)
ch = (device->FmtChans == DevFmtX51) ? SideLeft : BackLeft;
else if(speaker.Name == "RB")
else if(speaker.Name == "RB"sv)
ch = (device->FmtChans == DevFmtX51) ? SideRight : BackRight;
else if(speaker.Name == "CB")
else if(speaker.Name == "CB"sv)
ch = BackCenter;
else if(speaker.Name == "LFT")
else if(speaker.Name == "LFT"sv)
ch = TopFrontLeft;
else if(speaker.Name == "RFT")
else if(speaker.Name == "RFT"sv)
ch = TopFrontRight;
else if(speaker.Name == "LBT")
else if(speaker.Name == "LBT"sv)
ch = TopBackLeft;
else if(speaker.Name == "RBT")
else if(speaker.Name == "RBT"sv)
ch = TopBackRight;
else if(speaker.Name == "LFB"sv)
ch = BottomFrontLeft;
else if(speaker.Name == "RFB"sv)
ch = BottomFrontRight;
else if(speaker.Name == "LBB"sv)
ch = BottomBackLeft;
else if(speaker.Name == "RBB"sv)
ch = BottomBackRight;
else
{
int idx{};
char c{};
/* NOLINTNEXTLINE(cert-err34-c,cppcoreguidelines-pro-type-vararg) */
if(sscanf(speaker.Name.c_str(), "AUX%d%c", &idx, &c) != 1 || idx < 0
|| idx >= MaxChannels-Aux0)
{
ERR("AmbDec speaker label \"%s\" not recognized\n", speaker.Name.c_str());
ERR("AmbDec speaker label \"{}\" not recognized", speaker.Name);
continue;
}
ch = static_cast<Channel>(Aux0+idx);
@@ -594,8 +636,46 @@ constexpr DecoderConfig<SingleBand, 10> X714Config{
{{8.80892603e-02f, -7.48948724e-02f, 9.08779842e-02f, -6.22480443e-02f}},
}}
};
constexpr DecoderConfig<DualBand, 14> X7144Config{
1, true, {{BackLeft, SideLeft, FrontLeft, FrontRight, SideRight, BackRight, TopBackLeft, TopFrontLeft, TopFrontRight, TopBackRight, BottomBackLeft, BottomFrontLeft, BottomFrontRight, BottomBackRight}},
DevAmbiScaling::N3D,
/*HF*/{{2.64575131e+0f, 1.52752523e+0f}},
{{
{{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}},
{{7.14285714e-02f, 1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}},
{{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}},
{{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}},
{{7.14285714e-02f, -1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}},
{{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}},
{{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}},
{{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}},
{{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}},
{{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}},
{{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}},
{{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}},
{{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}},
{{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}},
}},
/*LF*/{{1.00000000e+0f, 1.00000000e+0f}},
{{
{{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}},
{{7.14285714e-02f, 1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}},
{{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}},
{{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}},
{{7.14285714e-02f, -1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}},
{{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}},
{{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}},
{{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}},
{{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}},
{{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}},
{{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}},
{{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}},
{{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}},
{{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}},
}}
};
void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=false,
void InitPanning(al::Device *device, const bool hqdec=false, const bool stablize=false,
DecoderView decoder={})
{
if(!decoder)
@@ -609,6 +689,7 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=
case DevFmtX61: decoder = X61Config; break;
case DevFmtX71: decoder = X71Config; break;
case DevFmtX714: decoder = X714Config; break;
case DevFmtX7144: decoder = X7144Config; break;
case DevFmtX3D71: decoder = X3D71Config; break;
case DevFmtAmbi3D:
/* For DevFmtAmbi3D, the ambisonic order is already set. */
@@ -627,10 +708,13 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=
avg_dist = *distopt;
else if(auto delayopt = device->configValue<float>("decoder", "nfc-ref-delay"))
{
WARN("nfc-ref-delay is deprecated, use speaker-dist instead\n");
WARN("nfc-ref-delay is deprecated, use speaker-dist instead");
avg_dist = *delayopt * SpeedOfSoundMetersPerSec;
}
TRACE("{}{} order ambisonic output ({} layout, {} scaling)", device->mAmbiOrder,
GetCounterSuffix(device->mAmbiOrder), GetLayoutName(device->mAmbiLayout),
GetScalingName(device->mAmbiScale));
InitNearFieldCtrl(device, avg_dist, device->mAmbiOrder, true);
return;
}
@@ -645,7 +729,7 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=
const size_t idx{device->channelIdxByName(decoder.mChannels[i])};
if(idx == InvalidChannelIndex)
{
ERR("Failed to find %s channel in device\n",
ERR("Failed to find {} channel in device",
GetLabelFromChannel(decoder.mChannels[i]));
continue;
}
@@ -673,8 +757,8 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=
device->mAmbiOrder = decoder.mOrder;
device->m2DMixing = !decoder.mIs3D;
const al::span<const uint8_t> acnmap{decoder.mIs3D ? AmbiIndex::FromACN.data() :
AmbiIndex::FromACN2D.data(), ambicount};
const auto acnmap = decoder.mIs3D ? al::span{AmbiIndex::FromACN}.first(ambicount)
: al::span{AmbiIndex::FromACN2D}.first(ambicount);
const auto coeffscale = GetAmbiScales(decoder.mScaling);
std::transform(acnmap.begin(), acnmap.end(), device->Dry.AmbiMap.begin(),
[coeffscale](const uint8_t &acn) noexcept
@@ -701,22 +785,21 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=
}
if(!hasfc)
{
stablizer = CreateStablizer(device->channelsFromFmt(), device->Frequency);
TRACE("Front stablizer enabled\n");
stablizer = CreateStablizer(device->channelsFromFmt(), device->mSampleRate);
TRACE("Front stablizer enabled");
}
}
TRACE("Enabling %s-band %s-order%s ambisonic decoder\n",
!dual_band ? "single" : "dual",
TRACE("Enabling {}-band {}-order{} ambisonic decoder", !dual_band ? "single" : "dual",
(decoder.mOrder > 3) ? "fourth" :
(decoder.mOrder > 2) ? "third" :
(decoder.mOrder > 1) ? "second" : "first",
decoder.mIs3D ? " periphonic" : "");
device->AmbiDecoder = BFormatDec::Create(ambicount, chancoeffs, chancoeffslf,
device->mXOverFreq/static_cast<float>(device->Frequency), std::move(stablizer));
device->mXOverFreq/static_cast<float>(device->mSampleRate), std::move(stablizer));
}
void InitHrtfPanning(ALCdevice *device)
void InitHrtfPanning(al::Device *device)
{
static constexpr float Deg180{al::numbers::pi_v<float>};
static constexpr float Deg_90{Deg180 / 2.0f /* 90 degrees*/};
@@ -874,7 +957,7 @@ void InitHrtfPanning(ALCdevice *device)
std::string_view mode{*modeopt};
if(al::case_compare(mode, "basic"sv) == 0)
{
ERR("HRTF mode \"%s\" deprecated, substituting \"%s\"\n", modeopt->c_str(), "ambi2");
ERR("HRTF mode \"{}\" deprecated, substituting \"{}\"", *modeopt, "ambi2");
mode = "ambi2";
}
@@ -882,16 +965,16 @@ void InitHrtfPanning(ALCdevice *device)
{ return al::case_compare(mode, entry.name) == 0; };
auto iter = std::find_if(hrtf_modes.begin(), hrtf_modes.end(), match_entry);
if(iter == hrtf_modes.end())
ERR("Unexpected hrtf-mode: %s\n", modeopt->c_str());
ERR("Unexpected hrtf-mode: {}", *modeopt);
else
{
device->mRenderMode = iter->mode;
ambi_order = iter->order;
}
}
TRACE("%u%s order %sHRTF rendering enabled, using \"%s\"\n", ambi_order,
TRACE("{}{} order {}HRTF rendering enabled, using \"{}\"", ambi_order,
GetCounterSuffix(ambi_order), (device->mRenderMode == RenderMode::Hrtf) ? "+ Full " : "",
device->mHrtfName.c_str());
device->mHrtfName);
bool perHrirMin{false};
auto AmbiPoints = al::span{AmbiPoints1O}.subspan(0);
@@ -928,7 +1011,7 @@ void InitHrtfPanning(ALCdevice *device)
InitNearFieldCtrl(device, Hrtf->mFields[0].distance, ambi_order, true);
}
void InitUhjPanning(ALCdevice *device)
void InitUhjPanning(al::Device *device)
{
/* UHJ is always 2D first-order. */
static constexpr size_t count{Ambi2DChannelsFromOrder(1)};
@@ -941,11 +1024,21 @@ void InitUhjPanning(ALCdevice *device)
[](const uint8_t &acn) noexcept -> BFChannelConfig
{ return BFChannelConfig{1.0f/AmbiScale::FromUHJ[acn], acn}; });
AllocChannels(device, count, device->channelsFromFmt());
/* TODO: Should this default to something else? This is simply a regular
* (first-order) B-Format mixing which just happens to be UHJ-encoded. As I
* understand it, a proper first-order B-Format signal essentially has an
* infinite control distance, which we can't really do. However, from what
* I've read, 2 meters or so should be sufficient as the near-field
* reference becomes inconsequential beyond that.
*/
const auto spkr_dist = ConfigValueFloat({}, "uhj"sv, "distance-ref"sv).value_or(2.0f);
InitNearFieldCtrl(device, spkr_dist, device->mAmbiOrder, !device->m2DMixing);
}
} // namespace
void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncoding> stereomode)
void aluInitRenderer(al::Device *device, int hrtf_id, std::optional<StereoEncoding> stereomode)
{
/* Hold the HRTF the device last used, in case it's used again. */
HrtfStorePtr old_hrtf{std::move(device->mHrtf)};
@@ -972,6 +1065,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncodin
case DevFmtX61: layout = "surround61"; break;
case DevFmtX71: layout = "surround71"; break;
case DevFmtX714: layout = "surround714"; break;
case DevFmtX7144: layout = "surround7144"; break;
case DevFmtX3D71: layout = "surround3d71"; break;
/* Mono, Stereo, and Ambisonics output don't use custom decoders. */
case DevFmtMono:
@@ -988,31 +1082,32 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncodin
AmbDecConf conf{};
if(auto err = conf.load(config))
{
ERR("Failed to load layout file %s\n", config);
ERR(" %s\n", err->c_str());
ERR("Failed to load layout file {}", config);
ERR(" {}", *err);
return false;
}
if(conf.Speakers.size() > MaxOutputChannels)
{
ERR("Unsupported decoder speaker count %zu (max %zu)\n", conf.Speakers.size(),
ERR("Unsupported decoder speaker count {} (max {})", conf.Speakers.size(),
MaxOutputChannels);
return false;
}
if(conf.ChanMask > Ambi3OrderMask)
{
ERR("Unsupported decoder channel mask 0x%04x (max 0x%x)\n", conf.ChanMask,
ERR("Unsupported decoder channel mask {:#x} (max {:#x})", conf.ChanMask,
Ambi3OrderMask);
return false;
}
TRACE("Using %s decoder: \"%s\"\n", DevFmtChannelsString(device->FmtChans),
conf.Description.c_str());
TRACE("Using {} decoder: \"{}\"", DevFmtChannelsString(device->FmtChans),
conf.Description);
device->mXOverFreq = std::clamp(conf.XOverFreq, 100.0f, 1000.0f);
decoder_store = std::make_unique<DecoderConfig<DualBand,MaxOutputChannels>>();
decoder = MakeDecoderView(device, &conf, *decoder_store);
const auto confspeakers = al::span{conf.Speakers.cbegin(), decoder.mChannels.size()};
const auto confspeakers = al::span{std::as_const(conf.Speakers)}
.first(decoder.mChannels.size());
std::transform(confspeakers.cbegin(), confspeakers.cend(), speakerdists.begin(),
std::mem_fn(&AmbDecConf::SpeakerConf::Distance));
return true;
@@ -1024,7 +1119,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncodin
usingCustom = load_config(decopt->c_str());
}
if(!usingCustom && device->FmtChans != DevFmtAmbi3D)
TRACE("Using built-in %s decoder\n", DevFmtChannelsString(device->FmtChans));
TRACE("Using built-in {} decoder", DevFmtChannelsString(device->FmtChans));
/* Enable the stablizer only for formats that have front-left, front-
* right, and front-center outputs.
@@ -1056,8 +1151,8 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncodin
}
if(auto *ambidec{device->AmbiDecoder.get()})
{
device->PostProcess = ambidec->hasStablizer() ? &ALCdevice::ProcessAmbiDecStablized
: &ALCdevice::ProcessAmbiDec;
device->PostProcess = ambidec->hasStablizer() ? &al::Device::ProcessAmbiDecStablized
: &al::Device::ProcessAmbiDec;
}
return;
}
@@ -1075,7 +1170,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncodin
if(hrtf_id >= 0 && static_cast<uint>(hrtf_id) < device->mHrtfList.size())
{
const std::string_view hrtfname{device->mHrtfList[static_cast<uint>(hrtf_id)]};
if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)})
if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->mSampleRate)})
{
device->mHrtf = std::move(hrtf);
device->mHrtfName = hrtfname;
@@ -1086,7 +1181,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncodin
{
for(const std::string_view hrtfname : device->mHrtfList)
{
if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)})
if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->mSampleRate)})
{
device->mHrtf = std::move(hrtf);
device->mHrtfName = hrtfname;
@@ -1108,7 +1203,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncodin
}
InitHrtfPanning(device);
device->PostProcess = &ALCdevice::ProcessHrtf;
device->PostProcess = &al::Device::ProcessHrtf;
device->mHrtfStatus = ALC_HRTF_ENABLED_SOFT;
return;
}
@@ -1117,23 +1212,27 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncodin
if(stereomode.value_or(StereoEncoding::Default) == StereoEncoding::Uhj)
{
auto ftype = std::string_view{};
switch(UhjEncodeQuality)
{
case UhjQualityType::IIR:
device->mUhjEncoder = std::make_unique<UhjEncoderIIR>();
ftype = "IIR"sv;
break;
case UhjQualityType::FIR256:
device->mUhjEncoder = std::make_unique<UhjEncoder<UhjLength256>>();
ftype = "FIR-256"sv;
break;
case UhjQualityType::FIR512:
device->mUhjEncoder = std::make_unique<UhjEncoder<UhjLength512>>();
ftype = "FIR-512"sv;
break;
}
assert(device->mUhjEncoder != nullptr);
TRACE("UHJ enabled\n");
TRACE("UHJ enabled ({} encoder)", ftype);
InitUhjPanning(device);
device->PostProcess = &ALCdevice::ProcessUhj;
device->PostProcess = &al::Device::ProcessUhj;
return;
}
@@ -1145,19 +1244,19 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncodin
if(*cflevopt > 0 && *cflevopt <= 6)
{
auto bs2b = std::make_unique<Bs2b::bs2b>();
bs2b->set_params(*cflevopt, static_cast<int>(device->Frequency));
bs2b->set_params(*cflevopt, static_cast<int>(device->mSampleRate));
device->Bs2b = std::move(bs2b);
TRACE("BS2B enabled\n");
TRACE("BS2B enabled");
InitPanning(device);
device->PostProcess = &ALCdevice::ProcessBs2b;
device->PostProcess = &al::Device::ProcessBs2b;
return;
}
}
}
TRACE("Stereo rendering\n");
TRACE("Stereo rendering");
InitPanning(device);
device->PostProcess = &ALCdevice::ProcessAmbiDec;
device->PostProcess = &al::Device::ProcessAmbiDec;
}