aboutsummaryrefslogtreecommitdiffstats
path: root/alc/backends
diff options
context:
space:
mode:
Diffstat (limited to 'alc/backends')
-rw-r--r--alc/backends/alsa.cpp351
-rw-r--r--alc/backends/alsa.h6
-rw-r--r--alc/backends/base.cpp184
-rw-r--r--alc/backends/base.h104
-rw-r--r--alc/backends/coreaudio.cpp753
-rw-r--r--alc/backends/coreaudio.h6
-rw-r--r--alc/backends/dsound.cpp486
-rw-r--r--alc/backends/dsound.h6
-rw-r--r--alc/backends/jack.cpp596
-rw-r--r--alc/backends/jack.h6
-rw-r--r--alc/backends/loopback.cpp25
-rw-r--r--alc/backends/loopback.h6
-rw-r--r--alc/backends/null.cpp57
-rw-r--r--alc/backends/null.h6
-rw-r--r--alc/backends/oboe.cpp360
-rw-r--r--alc/backends/oboe.h19
-rw-r--r--alc/backends/opensl.cpp373
-rw-r--r--alc/backends/opensl.h6
-rw-r--r--alc/backends/oss.cpp284
-rw-r--r--alc/backends/oss.h6
-rw-r--r--alc/backends/pipewire.cpp2166
-rw-r--r--alc/backends/pipewire.h23
-rw-r--r--alc/backends/portaudio.cpp148
-rw-r--r--alc/backends/portaudio.h6
-rw-r--r--alc/backends/pulseaudio.cpp1028
-rw-r--r--alc/backends/pulseaudio.h6
-rw-r--r--alc/backends/qsa.cpp963
-rw-r--r--alc/backends/qsa.h19
-rw-r--r--alc/backends/sdl2.cpp135
-rw-r--r--alc/backends/sdl2.h6
-rw-r--r--alc/backends/sndio.cpp456
-rw-r--r--alc/backends/sndio.h6
-rw-r--r--alc/backends/solaris.cpp180
-rw-r--r--alc/backends/solaris.h6
-rw-r--r--alc/backends/wasapi.cpp1476
-rw-r--r--alc/backends/wasapi.h6
-rw-r--r--alc/backends/wave.cpp239
-rw-r--r--alc/backends/wave.h6
-rw-r--r--alc/backends/winmm.cpp216
-rw-r--r--alc/backends/winmm.h6
40 files changed, 6596 insertions, 4141 deletions
diff --git a/alc/backends/alsa.cpp b/alc/backends/alsa.cpp
index 7dc3c3c4..d620a83c 100644
--- a/alc/backends/alsa.cpp
+++ b/alc/backends/alsa.cpp
@@ -20,7 +20,7 @@
#include "config.h"
-#include "backends/alsa.h"
+#include "alsa.h"
#include <algorithm>
#include <atomic>
@@ -35,18 +35,15 @@
#include <thread>
#include <utility>
-#include "AL/al.h"
-
#include "albyte.h"
-#include "alcmain.h"
-#include "alconfig.h"
-#include "alexcpt.h"
+#include "alc/alconfig.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "aloptional.h"
-#include "alu.h"
+#include "core/device.h"
+#include "core/helpers.h"
+#include "core/logging.h"
#include "dynload.h"
-#include "logging.h"
#include "ringbuffer.h"
#include "threads.h"
#include "vector.h"
@@ -56,7 +53,7 @@
namespace {
-constexpr ALCchar alsaDevice[] = "ALSA Default";
+constexpr char alsaDevice[] = "ALSA Default";
#ifdef HAVE_DYNLOAD
@@ -71,35 +68,37 @@ constexpr ALCchar alsaDevice[] = "ALSA Default";
MAGIC(snd_pcm_hw_params_free); \
MAGIC(snd_pcm_hw_params_any); \
MAGIC(snd_pcm_hw_params_current); \
+ MAGIC(snd_pcm_hw_params_get_access); \
+ MAGIC(snd_pcm_hw_params_get_buffer_size); \
+ MAGIC(snd_pcm_hw_params_get_buffer_time_min); \
+ MAGIC(snd_pcm_hw_params_get_buffer_time_max); \
+ MAGIC(snd_pcm_hw_params_get_channels); \
+ MAGIC(snd_pcm_hw_params_get_period_size); \
+ MAGIC(snd_pcm_hw_params_get_period_time_max); \
+ MAGIC(snd_pcm_hw_params_get_period_time_min); \
+ MAGIC(snd_pcm_hw_params_get_periods); \
MAGIC(snd_pcm_hw_params_set_access); \
- MAGIC(snd_pcm_hw_params_set_format); \
+ MAGIC(snd_pcm_hw_params_set_buffer_size_min); \
+ MAGIC(snd_pcm_hw_params_set_buffer_size_near); \
+ MAGIC(snd_pcm_hw_params_set_buffer_time_near); \
MAGIC(snd_pcm_hw_params_set_channels); \
+ MAGIC(snd_pcm_hw_params_set_channels_near); \
+ MAGIC(snd_pcm_hw_params_set_format); \
+ MAGIC(snd_pcm_hw_params_set_period_time_near); \
+ MAGIC(snd_pcm_hw_params_set_period_size_near); \
MAGIC(snd_pcm_hw_params_set_periods_near); \
MAGIC(snd_pcm_hw_params_set_rate_near); \
MAGIC(snd_pcm_hw_params_set_rate); \
MAGIC(snd_pcm_hw_params_set_rate_resample); \
- MAGIC(snd_pcm_hw_params_set_buffer_time_near); \
- MAGIC(snd_pcm_hw_params_set_period_time_near); \
- MAGIC(snd_pcm_hw_params_set_buffer_size_near); \
- MAGIC(snd_pcm_hw_params_set_period_size_near); \
- MAGIC(snd_pcm_hw_params_set_buffer_size_min); \
- MAGIC(snd_pcm_hw_params_get_buffer_time_min); \
- MAGIC(snd_pcm_hw_params_get_buffer_time_max); \
- MAGIC(snd_pcm_hw_params_get_period_time_min); \
- MAGIC(snd_pcm_hw_params_get_period_time_max); \
- MAGIC(snd_pcm_hw_params_get_buffer_size); \
- MAGIC(snd_pcm_hw_params_get_period_size); \
- MAGIC(snd_pcm_hw_params_get_access); \
- MAGIC(snd_pcm_hw_params_get_periods); \
MAGIC(snd_pcm_hw_params_test_format); \
MAGIC(snd_pcm_hw_params_test_channels); \
MAGIC(snd_pcm_hw_params); \
- MAGIC(snd_pcm_sw_params_malloc); \
+ MAGIC(snd_pcm_sw_params); \
MAGIC(snd_pcm_sw_params_current); \
+ MAGIC(snd_pcm_sw_params_free); \
+ MAGIC(snd_pcm_sw_params_malloc); \
MAGIC(snd_pcm_sw_params_set_avail_min); \
MAGIC(snd_pcm_sw_params_set_stop_threshold); \
- MAGIC(snd_pcm_sw_params); \
- MAGIC(snd_pcm_sw_params_free); \
MAGIC(snd_pcm_prepare); \
MAGIC(snd_pcm_start); \
MAGIC(snd_pcm_resume); \
@@ -108,7 +107,6 @@ constexpr ALCchar alsaDevice[] = "ALSA Default";
MAGIC(snd_pcm_delay); \
MAGIC(snd_pcm_state); \
MAGIC(snd_pcm_avail_update); \
- MAGIC(snd_pcm_areas_silence); \
MAGIC(snd_pcm_mmap_begin); \
MAGIC(snd_pcm_mmap_commit); \
MAGIC(snd_pcm_readi); \
@@ -134,7 +132,7 @@ constexpr ALCchar alsaDevice[] = "ALSA Default";
MAGIC(snd_card_next); \
MAGIC(snd_config_update_free_global)
-static void *alsa_handle;
+void *alsa_handle;
#define MAKE_FUNC(f) decltype(f) * p##f
ALSA_FUNCS(MAKE_FUNC);
#undef MAKE_FUNC
@@ -153,6 +151,7 @@ ALSA_FUNCS(MAKE_FUNC);
#define snd_pcm_hw_params_set_access psnd_pcm_hw_params_set_access
#define snd_pcm_hw_params_set_format psnd_pcm_hw_params_set_format
#define snd_pcm_hw_params_set_channels psnd_pcm_hw_params_set_channels
+#define snd_pcm_hw_params_set_channels_near psnd_pcm_hw_params_set_channels_near
#define snd_pcm_hw_params_set_periods_near psnd_pcm_hw_params_set_periods_near
#define snd_pcm_hw_params_set_rate_near psnd_pcm_hw_params_set_rate_near
#define snd_pcm_hw_params_set_rate psnd_pcm_hw_params_set_rate
@@ -170,6 +169,7 @@ ALSA_FUNCS(MAKE_FUNC);
#define snd_pcm_hw_params_get_period_size psnd_pcm_hw_params_get_period_size
#define snd_pcm_hw_params_get_access psnd_pcm_hw_params_get_access
#define snd_pcm_hw_params_get_periods psnd_pcm_hw_params_get_periods
+#define snd_pcm_hw_params_get_channels psnd_pcm_hw_params_get_channels
#define snd_pcm_hw_params_test_format psnd_pcm_hw_params_test_format
#define snd_pcm_hw_params_test_channels psnd_pcm_hw_params_test_channels
#define snd_pcm_hw_params psnd_pcm_hw_params
@@ -187,7 +187,6 @@ ALSA_FUNCS(MAKE_FUNC);
#define snd_pcm_delay psnd_pcm_delay
#define snd_pcm_state psnd_pcm_state
#define snd_pcm_avail_update psnd_pcm_avail_update
-#define snd_pcm_areas_silence psnd_pcm_areas_silence
#define snd_pcm_mmap_begin psnd_pcm_mmap_begin
#define snd_pcm_mmap_commit psnd_pcm_mmap_commit
#define snd_pcm_readi psnd_pcm_readi
@@ -242,6 +241,11 @@ SwParamsPtr CreateSwParams()
struct DevMap {
std::string name;
std::string device_name;
+
+ template<typename T, typename U>
+ DevMap(T&& name_, U&& devname)
+ : name{std::forward<T>(name_)}, device_name{std::forward<U>(devname)}
+ { }
};
al::vector<DevMap> PlaybackDevices;
@@ -263,29 +267,36 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
snd_pcm_info_t *pcminfo;
snd_pcm_info_malloc(&pcminfo);
- devlist.emplace_back(DevMap{alsaDevice,
- GetConfigValue(nullptr, "alsa", (stream==SND_PCM_STREAM_PLAYBACK) ? "device" : "capture",
- "default")});
+ auto defname = ConfigValueStr(nullptr, "alsa",
+ (stream == SND_PCM_STREAM_PLAYBACK) ? "device" : "capture");
+ devlist.emplace_back(alsaDevice, defname ? defname->c_str() : "default");
- const char *customdevs{GetConfigValue(nullptr, "alsa",
- (stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures", "")};
- while(const char *curdev{customdevs})
+ if(auto customdevs = ConfigValueStr(nullptr, "alsa",
+ (stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures"))
{
- if(!curdev[0]) break;
- customdevs = strchr(curdev, ';');
- const char *sep{strchr(curdev, '=')};
- if(!sep)
+ size_t nextpos{customdevs->find_first_not_of(';')};
+ size_t curpos;
+ while((curpos=nextpos) < customdevs->length())
{
- std::string spec{customdevs ? std::string(curdev, customdevs++) : std::string(curdev)};
- ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str());
- continue;
- }
+ nextpos = customdevs->find_first_of(';', curpos+1);
- const char *oldsep{sep++};
- devlist.emplace_back(DevMap{std::string(curdev, oldsep),
- customdevs ? std::string(sep, customdevs++) : std::string(sep)});
- const auto &entry = devlist.back();
- TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
+ size_t seppos{customdevs->find_first_of('=', curpos)};
+ if(seppos == curpos || seppos >= nextpos)
+ {
+ std::string spec{customdevs->substr(curpos, nextpos-curpos)};
+ ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str());
+ }
+ else
+ {
+ devlist.emplace_back(customdevs->substr(curpos, seppos-curpos),
+ customdevs->substr(seppos+1, nextpos-seppos-1));
+ const auto &entry = devlist.back();
+ TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
+ }
+
+ if(nextpos < customdevs->length())
+ nextpos = customdevs->find_first_not_of(';', nextpos+1);
+ }
}
const std::string main_prefix{
@@ -319,13 +330,13 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(main_prefix)};
int dev{-1};
- while(1)
+ while(true)
{
if(snd_ctl_pcm_next_device(handle, &dev) < 0)
ERR("snd_ctl_pcm_next_device failed\n");
if(dev < 0) break;
- snd_pcm_info_set_device(pcminfo, static_cast<ALuint>(dev));
+ snd_pcm_info_set_device(pcminfo, static_cast<uint>(dev));
snd_pcm_info_set_subdevice(pcminfo, 0);
snd_pcm_info_set_stream(pcminfo, stream);
if((err=snd_ctl_pcm_info(handle, pcminfo)) < 0)
@@ -361,7 +372,7 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
device += ",DEV=";
device += std::to_string(dev);
- devlist.emplace_back(DevMap{std::move(name), std::move(device)});
+ devlist.emplace_back(std::move(name), std::move(device));
const auto &entry = devlist.back();
TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
}
@@ -410,21 +421,24 @@ int verify_state(snd_pcm_t *handle)
struct AlsaPlayback final : public BackendBase {
- AlsaPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+ AlsaPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~AlsaPlayback() override;
int mixerProc();
int mixerNoMMapProc();
- void open(const ALCchar *name) override;
+ void open(const char *name) override;
bool reset() override;
- bool start() override;
+ void start() override;
void stop() override;
ClockLatency getClockLatency() override;
snd_pcm_t *mPcmHandle{nullptr};
+ std::mutex mMutex;
+
+ uint mFrameStep{};
al::vector<al::byte> mBuffer;
std::atomic<bool> mKillNow{true};
@@ -454,7 +468,7 @@ int AlsaPlayback::mixerProc()
if(state < 0)
{
ERR("Invalid state detected: %s\n", snd_strerror(state));
- aluHandleDisconnect(mDevice, "Bad state: %s", snd_strerror(state));
+ mDevice->handleDisconnect("Bad state: %s", snd_strerror(state));
break;
}
@@ -492,7 +506,7 @@ int AlsaPlayback::mixerProc()
avail -= avail%update_size;
// it is possible that contiguous areas are smaller, thus we use a loop
- std::lock_guard<AlsaPlayback> _{*this};
+ std::lock_guard<std::mutex> _{mMutex};
while(avail > 0)
{
snd_pcm_uframes_t frames{avail};
@@ -507,10 +521,10 @@ int AlsaPlayback::mixerProc()
}
char *WritePtr{static_cast<char*>(areas->addr) + (offset * areas->step / 8)};
- aluMixData(mDevice, WritePtr, static_cast<ALuint>(frames));
+ mDevice->renderSamples(WritePtr, static_cast<uint>(frames), mFrameStep);
snd_pcm_sframes_t commitres{snd_pcm_mmap_commit(mPcmHandle, offset, frames)};
- if(commitres < 0 || (static_cast<snd_pcm_uframes_t>(commitres)-frames) != 0)
+ if(commitres < 0 || static_cast<snd_pcm_uframes_t>(commitres) != frames)
{
ERR("mmap commit error: %s\n",
snd_strerror(commitres >= 0 ? -EPIPE : static_cast<int>(commitres)));
@@ -537,7 +551,7 @@ int AlsaPlayback::mixerNoMMapProc()
if(state < 0)
{
ERR("Invalid state detected: %s\n", snd_strerror(state));
- aluHandleDisconnect(mDevice, "Bad state: %s", snd_strerror(state));
+ mDevice->handleDisconnect("Bad state: %s", snd_strerror(state));
break;
}
@@ -571,10 +585,10 @@ int AlsaPlayback::mixerNoMMapProc()
continue;
}
- std::lock_guard<AlsaPlayback> _{*this};
al::byte *WritePtr{mBuffer.data()};
avail = snd_pcm_bytes_to_frames(mPcmHandle, static_cast<ssize_t>(mBuffer.size()));
- aluMixData(mDevice, WritePtr, static_cast<ALuint>(avail));
+ std::lock_guard<std::mutex> _{mMutex};
+ mDevice->renderSamples(WritePtr, static_cast<uint>(avail), mFrameStep);
while(avail > 0)
{
snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, WritePtr,
@@ -612,33 +626,37 @@ int AlsaPlayback::mixerNoMMapProc()
}
-void AlsaPlayback::open(const ALCchar *name)
+void AlsaPlayback::open(const char *name)
{
- const char *driver{};
+ std::string driver{"default"};
if(name)
{
if(PlaybackDevices.empty())
PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK);
auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
- [name](const DevMap &entry) -> bool
- { return entry.name == name; }
- );
+ [name](const DevMap &entry) -> bool { return entry.name == name; });
if(iter == PlaybackDevices.cend())
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
- driver = iter->device_name.c_str();
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Device name \"%s\" not found", name};
+ driver = iter->device_name;
}
else
{
name = alsaDevice;
- driver = GetConfigValue(nullptr, "alsa", "device", "default");
+ if(auto driveropt = ConfigValueStr(nullptr, "alsa", "device"))
+ driver = std::move(driveropt).value();
}
+ TRACE("Opening device \"%s\"\n", driver.c_str());
- TRACE("Opening device \"%s\"\n", driver);
- int err{snd_pcm_open(&mPcmHandle, driver, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)};
+ 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{ALC_OUT_OF_MEMORY, "Could not open ALSA device \"%s\"",
- driver};
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Could not open ALSA device \"%s\"", driver.c_str()};
+ if(mPcmHandle)
+ snd_pcm_close(mPcmHandle);
+ mPcmHandle = pcmHandle;
/* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */
snd_config_update_free_global();
@@ -674,16 +692,17 @@ bool AlsaPlayback::reset()
break;
}
- bool allowmmap{!!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "mmap", 1)};
- ALuint periodLen{static_cast<ALuint>(mDevice->UpdateSize * 1000000_u64 / mDevice->Frequency)};
- ALuint bufferLen{static_cast<ALuint>(mDevice->BufferSize * 1000000_u64 / mDevice->Frequency)};
- ALuint rate{mDevice->Frequency};
+ bool allowmmap{!!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "mmap", true)};
+ uint periodLen{static_cast<uint>(mDevice->UpdateSize * 1000000_u64 / mDevice->Frequency)};
+ uint bufferLen{static_cast<uint>(mDevice->BufferSize * 1000000_u64 / mDevice->Frequency)};
+ uint rate{mDevice->Frequency};
int err{};
HwParamsPtr hp{CreateHwParams()};
#define CHECK(x) do { \
if((err=(x)) < 0) \
- throw al::backend_exception{ALC_INVALID_VALUE, #x " failed: %s", snd_strerror(err)}; \
+ throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \
+ snd_strerror(err)}; \
} while(0)
CHECK(snd_pcm_hw_params_any(mPcmHandle, hp.get()));
/* set interleaved access */
@@ -720,37 +739,25 @@ bool AlsaPlayback::reset()
}
}
CHECK(snd_pcm_hw_params_set_format(mPcmHandle, hp.get(), format));
- /* test and set channels (implicitly sets frame bits) */
- if(snd_pcm_hw_params_test_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt()) < 0)
+ /* set channels (implicitly sets frame bits) */
+ if(snd_pcm_hw_params_set_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt()) < 0)
{
- static const DevFmtChannels channellist[] = {
- DevFmtStereo,
- DevFmtQuad,
- DevFmtX51,
- DevFmtX71,
- DevFmtMono,
- };
-
- for(const auto &chan : channellist)
- {
- if(snd_pcm_hw_params_test_channels(mPcmHandle, hp.get(), ChannelsFromDevFmt(chan, 0)) >= 0)
- {
- mDevice->FmtChans = chan;
- mDevice->mAmbiOrder = 0;
- break;
- }
- }
+ uint numchans{2u};
+ CHECK(snd_pcm_hw_params_set_channels_near(mPcmHandle, hp.get(), &numchans));
+ if(numchans < 1)
+ throw al::backend_exception{al::backend_error::DeviceError, "Got 0 device channels"};
+ if(numchans == 1) mDevice->FmtChans = DevFmtMono;
+ else mDevice->FmtChans = DevFmtStereo;
}
- CHECK(snd_pcm_hw_params_set_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt()));
/* set rate (implicitly constrains period/buffer parameters) */
- if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", 0) ||
- !mDevice->Flags.get<FrequencyRequest>())
+ if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", false)
+ || !mDevice->Flags.test(FrequencyRequest))
{
if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0)
- ERR("Failed to disable ALSA resampler\n");
+ WARN("Failed to disable ALSA resampler\n");
}
else if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 1) < 0)
- ERR("Failed to enable ALSA resampler\n");
+ WARN("Failed to enable ALSA resampler\n");
CHECK(snd_pcm_hw_params_set_rate_near(mPcmHandle, hp.get(), &rate, nullptr));
/* set period time (implicitly constrains period/buffer parameters) */
if((err=snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)) < 0)
@@ -769,6 +776,7 @@ bool AlsaPlayback::reset()
CHECK(snd_pcm_hw_params_get_access(hp.get(), &access));
CHECK(snd_pcm_hw_params_get_period_size(hp.get(), &periodSizeInFrames, nullptr));
CHECK(snd_pcm_hw_params_get_buffer_size(hp.get(), &bufferSizeInFrames));
+ CHECK(snd_pcm_hw_params_get_channels(hp.get(), &mFrameStep));
hp = nullptr;
SwParamsPtr sp{CreateSwParams()};
@@ -779,58 +787,52 @@ bool AlsaPlayback::reset()
#undef CHECK
sp = nullptr;
- mDevice->BufferSize = static_cast<ALuint>(bufferSizeInFrames);
- mDevice->UpdateSize = static_cast<ALuint>(periodSizeInFrames);
+ mDevice->BufferSize = static_cast<uint>(bufferSizeInFrames);
+ mDevice->UpdateSize = static_cast<uint>(periodSizeInFrames);
mDevice->Frequency = rate;
- SetDefaultChannelOrder(mDevice);
+ setDefaultChannelOrder();
return true;
}
-bool AlsaPlayback::start()
+void AlsaPlayback::start()
{
int err{};
snd_pcm_access_t access{};
HwParamsPtr hp{CreateHwParams()};
#define CHECK(x) do { \
if((err=(x)) < 0) \
- throw al::backend_exception{ALC_INVALID_VALUE, #x " failed: %s", snd_strerror(err)}; \
+ throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \
+ snd_strerror(err)}; \
} while(0)
CHECK(snd_pcm_hw_params_current(mPcmHandle, hp.get()));
/* retrieve configuration info */
CHECK(snd_pcm_hw_params_get_access(hp.get(), &access));
-#undef CHECK
hp = nullptr;
int (AlsaPlayback::*thread_func)(){};
if(access == SND_PCM_ACCESS_RW_INTERLEAVED)
{
- mBuffer.resize(
- static_cast<size_t>(snd_pcm_frames_to_bytes(mPcmHandle, mDevice->UpdateSize)));
+ auto datalen = snd_pcm_frames_to_bytes(mPcmHandle, mDevice->UpdateSize);
+ mBuffer.resize(static_cast<size_t>(datalen));
thread_func = &AlsaPlayback::mixerNoMMapProc;
}
else
{
- err = snd_pcm_prepare(mPcmHandle);
- if(err < 0)
- {
- ERR("snd_pcm_prepare(data->mPcmHandle) failed: %s\n", snd_strerror(err));
- return false;
- }
+ CHECK(snd_pcm_prepare(mPcmHandle));
thread_func = &AlsaPlayback::mixerProc;
}
+#undef CHECK
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(thread_func), this};
- return true;
}
catch(std::exception& e) {
- ERR("Could not create playback thread: %s\n", e.what());
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to start mixing thread: %s", e.what()};
}
- mBuffer.clear();
- return false;
}
void AlsaPlayback::stop()
@@ -840,13 +842,16 @@ void AlsaPlayback::stop()
mThread.join();
mBuffer.clear();
+ int err{snd_pcm_drop(mPcmHandle)};
+ if(err < 0)
+ ERR("snd_pcm_drop failed: %s\n", snd_strerror(err));
}
ClockLatency AlsaPlayback::getClockLatency()
{
ClockLatency ret;
- std::lock_guard<AlsaPlayback> _{*this};
+ std::lock_guard<std::mutex> _{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice);
snd_pcm_sframes_t delay{};
int err{snd_pcm_delay(mPcmHandle, &delay)};
@@ -863,14 +868,14 @@ ClockLatency AlsaPlayback::getClockLatency()
struct AlsaCapture final : public BackendBase {
- AlsaCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+ AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~AlsaCapture() override;
- void open(const ALCchar *name) override;
- bool start() override;
+ void open(const char *name) override;
+ void start() override;
void stop() override;
- ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
- ALCuint availableSamples() override;
+ void captureSamples(al::byte *buffer, uint samples) override;
+ uint availableSamples() override;
ClockLatency getClockLatency() override;
snd_pcm_t *mPcmHandle{nullptr};
@@ -893,33 +898,33 @@ AlsaCapture::~AlsaCapture()
}
-void AlsaCapture::open(const ALCchar *name)
+void AlsaCapture::open(const char *name)
{
- const char *driver{};
+ std::string driver{"default"};
if(name)
{
if(CaptureDevices.empty())
CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE);
auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
- [name](const DevMap &entry) -> bool
- { return entry.name == name; }
- );
+ [name](const DevMap &entry) -> bool { return entry.name == name; });
if(iter == CaptureDevices.cend())
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
- driver = iter->device_name.c_str();
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Device name \"%s\" not found", name};
+ driver = iter->device_name;
}
else
{
name = alsaDevice;
- driver = GetConfigValue(nullptr, "alsa", "capture", "default");
+ if(auto driveropt = ConfigValueStr(nullptr, "alsa", "capture"))
+ driver = std::move(driveropt).value();
}
- TRACE("Opening device \"%s\"\n", driver);
- int err{snd_pcm_open(&mPcmHandle, driver, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)};
+ TRACE("Opening device \"%s\"\n", driver.c_str());
+ int err{snd_pcm_open(&mPcmHandle, driver.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)};
if(err < 0)
- throw al::backend_exception{ALC_OUT_OF_MEMORY, "Could not open ALSA device \"%s\"",
- driver};
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Could not open ALSA device \"%s\"", driver.c_str()};
/* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */
snd_config_update_free_global();
@@ -957,7 +962,8 @@ void AlsaCapture::open(const ALCchar *name)
HwParamsPtr hp{CreateHwParams()};
#define CHECK(x) do { \
if((err=(x)) < 0) \
- throw al::backend_exception{ALC_INVALID_VALUE, #x " failed: %s", snd_strerror(err)}; \
+ throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \
+ snd_strerror(err)}; \
} while(0)
CHECK(snd_pcm_hw_params_any(mPcmHandle, hp.get()));
/* set interleaved access */
@@ -985,26 +991,25 @@ void AlsaCapture::open(const ALCchar *name)
hp = nullptr;
if(needring)
- mRing = CreateRingBuffer(mDevice->BufferSize, mDevice->frameSizeFromFmt(), false);
+ mRing = RingBuffer::Create(mDevice->BufferSize, mDevice->frameSizeFromFmt(), false);
mDevice->DeviceName = name;
}
-bool AlsaCapture::start()
+void AlsaCapture::start()
{
int err{snd_pcm_prepare(mPcmHandle)};
if(err < 0)
- throw al::backend_exception{ALC_INVALID_VALUE, "snd_pcm_prepare failed: %s",
+ throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_prepare failed: %s",
snd_strerror(err)};
err = snd_pcm_start(mPcmHandle);
if(err < 0)
- throw al::backend_exception{ALC_INVALID_VALUE, "snd_pcm_start failed: %s",
+ throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_start failed: %s",
snd_strerror(err)};
mDoCapture = true;
- return true;
}
void AlsaCapture::stop()
@@ -1013,11 +1018,12 @@ void AlsaCapture::stop()
* snd_pcm_drain is unreliable and snd_pcm_drop drops it. Capture what's
* available now so it'll be available later after the drop.
*/
- ALCuint avail{availableSamples()};
+ uint avail{availableSamples()};
if(!mRing && avail > 0)
{
/* The ring buffer implicitly captures when checking availability.
- * Direct access needs to explicitly capture it into temp storage. */
+ * Direct access needs to explicitly capture it into temp storage.
+ */
auto temp = al::vector<al::byte>(
static_cast<size_t>(snd_pcm_frames_to_bytes(mPcmHandle, avail)));
captureSamples(temp.data(), avail);
@@ -1029,12 +1035,12 @@ void AlsaCapture::stop()
mDoCapture = false;
}
-ALCenum AlsaCapture::captureSamples(al::byte *buffer, ALCuint samples)
+void AlsaCapture::captureSamples(al::byte *buffer, uint samples)
{
if(mRing)
{
mRing->read(buffer, samples);
- return ALC_NO_ERROR;
+ return;
}
mLastAvail -= samples;
@@ -1072,7 +1078,7 @@ ALCenum AlsaCapture::captureSamples(al::byte *buffer, ALCuint samples)
{
const char *err{snd_strerror(static_cast<int>(amt))};
ERR("restore error: %s\n", err);
- aluHandleDisconnect(mDevice, "Capture recovery failure: %s", err);
+ mDevice->handleDisconnect("Capture recovery failure: %s", err);
break;
}
/* If the amount available is less than what's asked, we lost it
@@ -1083,16 +1089,14 @@ ALCenum AlsaCapture::captureSamples(al::byte *buffer, ALCuint samples)
}
buffer = buffer + amt;
- samples -= static_cast<ALCuint>(amt);
+ samples -= static_cast<uint>(amt);
}
if(samples > 0)
std::fill_n(buffer, snd_pcm_frames_to_bytes(mPcmHandle, samples),
al::byte((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0));
-
- return ALC_NO_ERROR;
}
-ALCuint AlsaCapture::availableSamples()
+uint AlsaCapture::availableSamples()
{
snd_pcm_sframes_t avail{0};
if(mDevice->Connected.load(std::memory_order_acquire) && mDoCapture)
@@ -1112,7 +1116,7 @@ ALCuint AlsaCapture::availableSamples()
{
const char *err{snd_strerror(static_cast<int>(avail))};
ERR("restore error: %s\n", err);
- aluHandleDisconnect(mDevice, "Capture recovery failure: %s", err);
+ mDevice->handleDisconnect("Capture recovery failure: %s", err);
}
}
@@ -1121,7 +1125,7 @@ ALCuint AlsaCapture::availableSamples()
if(avail < 0) avail = 0;
avail += snd_pcm_bytes_to_frames(mPcmHandle, static_cast<ssize_t>(mBuffer.size()));
if(avail > mLastAvail) mLastAvail = avail;
- return static_cast<ALCuint>(mLastAvail);
+ return static_cast<uint>(mLastAvail);
}
while(avail > 0)
@@ -1148,7 +1152,7 @@ ALCuint AlsaCapture::availableSamples()
{
const char *err{snd_strerror(static_cast<int>(amt))};
ERR("restore error: %s\n", err);
- aluHandleDisconnect(mDevice, "Capture recovery failure: %s", err);
+ mDevice->handleDisconnect("Capture recovery failure: %s", err);
break;
}
avail = amt;
@@ -1159,14 +1163,13 @@ ALCuint AlsaCapture::availableSamples()
avail -= amt;
}
- return static_cast<ALCuint>(mRing->readSpace());
+ return static_cast<uint>(mRing->readSpace());
}
ClockLatency AlsaCapture::getClockLatency()
{
ClockLatency ret;
- std::lock_guard<AlsaCapture> _{*this};
ret.ClockTime = GetDeviceClockTime(mDevice);
snd_pcm_sframes_t delay{};
int err{snd_pcm_delay(mPcmHandle, &delay)};
@@ -1197,10 +1200,10 @@ bool AlsaBackendFactory::init()
if(!alsa_handle)
{
WARN("Failed to load %s\n", "libasound.so.2");
- return ALC_FALSE;
+ return false;
}
- error = ALC_FALSE;
+ error = false;
#define LOAD_FUNC(f) do { \
p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(alsa_handle, #f)); \
if(p##f == nullptr) { \
@@ -1226,30 +1229,34 @@ bool AlsaBackendFactory::init()
bool AlsaBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
-void AlsaBackendFactory::probe(DevProbe type, std::string *outnames)
+std::string AlsaBackendFactory::probe(BackendType type)
{
- auto add_device = [outnames](const DevMap &entry) -> void
+ 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.append(entry.name.c_str(), entry.name.length()+1);
};
switch(type)
{
- case DevProbe::Playback:
- PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK);
- std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
- break;
+ case BackendType::Playback:
+ PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK);
+ std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
+ break;
- case DevProbe::Capture:
- CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE);
- std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
- break;
+ case BackendType::Capture:
+ CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE);
+ std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
+ break;
}
+
+ return outnames;
}
-BackendPtr AlsaBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr AlsaBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new AlsaPlayback{device}};
diff --git a/alc/backends/alsa.h b/alc/backends/alsa.h
index fb9de006..b256dcf5 100644
--- a/alc/backends/alsa.h
+++ b/alc/backends/alsa.h
@@ -1,7 +1,7 @@
#ifndef BACKENDS_ALSA_H
#define BACKENDS_ALSA_H
-#include "backends/base.h"
+#include "base.h"
struct AlsaBackendFactory final : public BackendFactory {
public:
@@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
- void probe(DevProbe type, std::string *outnames) override;
+ std::string probe(BackendType type) override;
- BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+ BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
diff --git a/alc/backends/base.cpp b/alc/backends/base.cpp
index 25531cf5..e5ad8494 100644
--- a/alc/backends/base.cpp
+++ b/alc/backends/base.cpp
@@ -3,49 +3,54 @@
#include "base.h"
+#include <algorithm>
+#include <array>
#include <atomic>
-#include <thread>
-#include "AL/al.h"
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <mmreg.h>
+
+#include "albit.h"
+#include "core/logging.h"
+#include "aloptional.h"
+#endif
-#include "alcmain.h"
-#include "alexcpt.h"
-#include "alnumeric.h"
#include "atomic.h"
+#include "core/devformat.h"
+
+namespace al {
-ClockLatency GetClockLatency(ALCdevice *device)
+backend_exception::backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code}
{
- BackendBase *backend{device->Backend.get()};
- ClockLatency ret{backend->getClockLatency()};
- ret.Latency += device->FixedLatency;
- return ret;
+ std::va_list args;
+ va_start(args, msg);
+ setMessage(msg, args);
+ va_end(args);
}
+backend_exception::~backend_exception() = default;
+} // namespace al
-/* BackendBase method implementations. */
-BackendBase::BackendBase(ALCdevice *device) noexcept : mDevice{device}
-{ }
-
-BackendBase::~BackendBase() = default;
bool BackendBase::reset()
-{ throw al::backend_exception{ALC_INVALID_DEVICE, "Invalid BackendBase call"}; }
+{ throw al::backend_exception{al::backend_error::DeviceError, "Invalid BackendBase call"}; }
-ALCenum BackendBase::captureSamples(al::byte*, ALCuint)
-{ return ALC_INVALID_DEVICE; }
+void BackendBase::captureSamples(al::byte*, uint)
+{ }
-ALCuint BackendBase::availableSamples()
+uint BackendBase::availableSamples()
{ return 0; }
ClockLatency BackendBase::getClockLatency()
{
ClockLatency ret;
- ALuint refcount;
+ uint refcount;
do {
- while(((refcount=ReadRef(mDevice->MixCount))&1) != 0)
- std::this_thread::yield();
+ refcount = mDevice->waitForMix();
ret.ClockTime = GetDeviceClockTime(mDevice);
std::atomic_thread_fence(std::memory_order_acquire);
} while(refcount != ReadRef(mDevice->MixCount));
@@ -60,3 +65,138 @@ ClockLatency BackendBase::getClockLatency()
return ret;
}
+
+void BackendBase::setDefaultWFXChannelOrder()
+{
+ mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex);
+
+ switch(mDevice->FmtChans)
+ {
+ case DevFmtMono:
+ mDevice->RealOut.ChannelIndex[FrontCenter] = 0;
+ break;
+ case DevFmtStereo:
+ mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
+ mDevice->RealOut.ChannelIndex[FrontRight] = 1;
+ break;
+ case DevFmtQuad:
+ mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
+ mDevice->RealOut.ChannelIndex[FrontRight] = 1;
+ mDevice->RealOut.ChannelIndex[BackLeft] = 2;
+ mDevice->RealOut.ChannelIndex[BackRight] = 3;
+ break;
+ case DevFmtX51:
+ mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
+ mDevice->RealOut.ChannelIndex[FrontRight] = 1;
+ mDevice->RealOut.ChannelIndex[FrontCenter] = 2;
+ mDevice->RealOut.ChannelIndex[LFE] = 3;
+ mDevice->RealOut.ChannelIndex[SideLeft] = 4;
+ mDevice->RealOut.ChannelIndex[SideRight] = 5;
+ break;
+ case DevFmtX61:
+ mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
+ mDevice->RealOut.ChannelIndex[FrontRight] = 1;
+ mDevice->RealOut.ChannelIndex[FrontCenter] = 2;
+ mDevice->RealOut.ChannelIndex[LFE] = 3;
+ mDevice->RealOut.ChannelIndex[BackCenter] = 4;
+ mDevice->RealOut.ChannelIndex[SideLeft] = 5;
+ mDevice->RealOut.ChannelIndex[SideRight] = 6;
+ break;
+ case DevFmtX71:
+ 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;
+ break;
+ case DevFmtX714:
+ 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;
+ break;
+ case DevFmtX3D71:
+ mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
+ mDevice->RealOut.ChannelIndex[FrontRight] = 1;
+ mDevice->RealOut.ChannelIndex[FrontCenter] = 2;
+ mDevice->RealOut.ChannelIndex[LFE] = 3;
+ mDevice->RealOut.ChannelIndex[Aux0] = 4;
+ mDevice->RealOut.ChannelIndex[Aux1] = 5;
+ mDevice->RealOut.ChannelIndex[SideLeft] = 6;
+ mDevice->RealOut.ChannelIndex[SideRight] = 7;
+ break;
+ case DevFmtAmbi3D:
+ break;
+ }
+}
+
+void BackendBase::setDefaultChannelOrder()
+{
+ mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex);
+
+ switch(mDevice->FmtChans)
+ {
+ case DevFmtX51:
+ mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
+ mDevice->RealOut.ChannelIndex[FrontRight] = 1;
+ mDevice->RealOut.ChannelIndex[SideLeft] = 2;
+ mDevice->RealOut.ChannelIndex[SideRight] = 3;
+ mDevice->RealOut.ChannelIndex[FrontCenter] = 4;
+ mDevice->RealOut.ChannelIndex[LFE] = 5;
+ return;
+ case DevFmtX71:
+ 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;
+ return;
+ case DevFmtX714:
+ 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;
+ break;
+ case DevFmtX3D71:
+ mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
+ mDevice->RealOut.ChannelIndex[FrontRight] = 1;
+ mDevice->RealOut.ChannelIndex[Aux0] = 2;
+ mDevice->RealOut.ChannelIndex[Aux1] = 3;
+ mDevice->RealOut.ChannelIndex[FrontCenter] = 4;
+ mDevice->RealOut.ChannelIndex[LFE] = 5;
+ mDevice->RealOut.ChannelIndex[SideLeft] = 6;
+ mDevice->RealOut.ChannelIndex[SideRight] = 7;
+ return;
+
+ /* Same as WFX order */
+ case DevFmtMono:
+ case DevFmtStereo:
+ case DevFmtQuad:
+ case DevFmtX61:
+ case DevFmtAmbi3D:
+ setDefaultWFXChannelOrder();
+ break;
+ }
+}
diff --git a/alc/backends/base.h b/alc/backends/base.h
index d4856818..b6b3d922 100644
--- a/alc/backends/base.h
+++ b/alc/backends/base.h
@@ -2,70 +2,75 @@
#define ALC_BACKENDS_BASE_H
#include <chrono>
+#include <cstdarg>
#include <memory>
-#include <mutex>
+#include <ratio>
#include <string>
-#include "AL/alc.h"
-
-#include "alcmain.h"
#include "albyte.h"
+#include "core/device.h"
+#include "core/except.h"
+
+using uint = unsigned int;
struct ClockLatency {
std::chrono::nanoseconds ClockTime;
std::chrono::nanoseconds Latency;
};
-/* Helper to get the current clock time from the device's ClockBase, and
- * SamplesDone converted from the sample rate.
- */
-inline std::chrono::nanoseconds GetDeviceClockTime(ALCdevice *device)
-{
- using std::chrono::seconds;
- using std::chrono::nanoseconds;
-
- auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency;
- return device->ClockBase + ns;
-}
-
-ClockLatency GetClockLatency(ALCdevice *device);
-
struct BackendBase {
- virtual void open(const ALCchar *name) = 0;
+ virtual void open(const char *name) = 0;
virtual bool reset();
- virtual bool start() = 0;
+ virtual void start() = 0;
virtual void stop() = 0;
- virtual ALCenum captureSamples(al::byte *buffer, ALCuint samples);
- virtual ALCuint availableSamples();
+ virtual void captureSamples(al::byte *buffer, uint samples);
+ virtual uint availableSamples();
virtual ClockLatency getClockLatency();
- virtual void lock() { mMutex.lock(); }
- virtual void unlock() { mMutex.unlock(); }
+ DeviceBase *const mDevice;
- ALCdevice *mDevice;
+ BackendBase(DeviceBase *device) noexcept : mDevice{device} { }
+ virtual ~BackendBase() = default;
- std::recursive_mutex mMutex;
-
- BackendBase(ALCdevice *device) noexcept;
- virtual ~BackendBase();
+protected:
+ /** Sets the default channel order used by most non-WaveFormatEx-based APIs. */
+ void setDefaultChannelOrder();
+ /** Sets the default channel order used by WaveFormatEx. */
+ void setDefaultWFXChannelOrder();
};
using BackendPtr = std::unique_ptr<BackendBase>;
-using BackendUniqueLock = std::unique_lock<BackendBase>;
-using BackendLockGuard = std::lock_guard<BackendBase>;
enum class BackendType {
Playback,
Capture
};
-enum class DevProbe {
- Playback,
- Capture
-};
+
+/* Helper to get the current clock time from the device's ClockBase, and
+ * SamplesDone converted from the sample rate.
+ */
+inline std::chrono::nanoseconds GetDeviceClockTime(DeviceBase *device)
+{
+ using std::chrono::seconds;
+ using std::chrono::nanoseconds;
+
+ auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency;
+ return device->ClockBase + ns;
+}
+
+/* Helper to get the device latency from the backend, including any fixed
+ * latency from post-processing.
+ */
+inline ClockLatency GetClockLatency(DeviceBase *device, BackendBase *backend)
+{
+ ClockLatency ret{backend->getClockLatency()};
+ ret.Latency += device->FixedLatency;
+ return ret;
+}
struct BackendFactory {
@@ -73,12 +78,37 @@ struct BackendFactory {
virtual bool querySupport(BackendType type) = 0;
- virtual void probe(DevProbe type, std::string *outnames) = 0;
+ virtual std::string probe(BackendType type) = 0;
- virtual BackendPtr createBackend(ALCdevice *device, BackendType type) = 0;
+ virtual BackendPtr createBackend(DeviceBase *device, BackendType type) = 0;
protected:
virtual ~BackendFactory() = default;
};
+namespace al {
+
+enum class backend_error {
+ NoDevice,
+ DeviceError,
+ OutOfMemory
+};
+
+class backend_exception final : public base_exception {
+ backend_error mErrorCode;
+
+public:
+#ifdef __USE_MINGW_ANSI_STDIO
+ [[gnu::format(gnu_printf, 3, 4)]]
+#else
+ [[gnu::format(printf, 3, 4)]]
+#endif
+ backend_exception(backend_error code, const char *msg, ...);
+ ~backend_exception() override;
+
+ backend_error errorCode() const noexcept { return mErrorCode; }
+};
+
+} // namespace al
+
#endif /* ALC_BACKENDS_BASE_H */
diff --git a/alc/backends/coreaudio.cpp b/alc/backends/coreaudio.cpp
index 7c18287b..8b0e75fd 100644
--- a/alc/backends/coreaudio.cpp
+++ b/alc/backends/coreaudio.cpp
@@ -20,31 +20,241 @@
#include "config.h"
-#include "backends/coreaudio.h"
+#include "coreaudio.h"
+#include <inttypes.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <unistd.h>
+
+#include <cmath>
+#include <memory>
+#include <string>
-#include "alcmain.h"
-#include "alexcpt.h"
-#include "alu.h"
+#include "alnumeric.h"
+#include "core/converter.h"
+#include "core/device.h"
+#include "core/logging.h"
#include "ringbuffer.h"
-#include "converter.h"
-#include "backends/base.h"
-#include <unistd.h>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
namespace {
-static const ALCchar ca_device[] = "CoreAudio Default";
+#if TARGET_OS_IOS || TARGET_OS_TV
+#define CAN_ENUMERATE 0
+#else
+#define CAN_ENUMERATE 1
+#endif
+
+constexpr auto OutputElement = 0;
+constexpr auto InputElement = 1;
+
+#if CAN_ENUMERATE
+struct DeviceEntry {
+ AudioDeviceID mId;
+ std::string mName;
+};
+
+std::vector<DeviceEntry> PlaybackList;
+std::vector<DeviceEntry> CaptureList;
+
+
+OSStatus GetHwProperty(AudioHardwarePropertyID propId, UInt32 dataSize, void *propData)
+{
+ const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ return AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0, nullptr, &dataSize,
+ propData);
+}
+
+OSStatus GetHwPropertySize(AudioHardwarePropertyID propId, UInt32 *outSize)
+{
+ const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ return AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr, 0, nullptr, outSize);
+}
+
+OSStatus GetDevProperty(AudioDeviceID devId, AudioDevicePropertyID propId, bool isCapture,
+ UInt32 elem, UInt32 dataSize, void *propData)
+{
+ static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput,
+ kAudioDevicePropertyScopeInput};
+ const AudioObjectPropertyAddress addr{propId, scopes[isCapture], elem};
+ return AudioObjectGetPropertyData(devId, &addr, 0, nullptr, &dataSize, propData);
+}
+
+OSStatus GetDevPropertySize(AudioDeviceID devId, AudioDevicePropertyID inPropertyID,
+ bool isCapture, UInt32 elem, UInt32 *outSize)
+{
+ static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput,
+ kAudioDevicePropertyScopeInput};
+ const AudioObjectPropertyAddress addr{inPropertyID, scopes[isCapture], elem};
+ return AudioObjectGetPropertyDataSize(devId, &addr, 0, nullptr, outSize);
+}
+
+
+std::string GetDeviceName(AudioDeviceID devId)
+{
+ std::string devname;
+ CFStringRef nameRef;
+
+ /* Try to get the device name as a CFString, for Unicode name support. */
+ OSStatus err{GetDevProperty(devId, kAudioDevicePropertyDeviceNameCFString, false, 0,
+ sizeof(nameRef), &nameRef)};
+ if(err == noErr)
+ {
+ const CFIndex propSize{CFStringGetMaximumSizeForEncoding(CFStringGetLength(nameRef),
+ kCFStringEncodingUTF8)};
+ devname.resize(static_cast<size_t>(propSize)+1, '\0');
+
+ CFStringGetCString(nameRef, &devname[0], propSize+1, kCFStringEncodingUTF8);
+ CFRelease(nameRef);
+ }
+ else
+ {
+ /* If that failed, just get the C string. Hopefully there's nothing bad
+ * with this.
+ */
+ UInt32 propSize{};
+ if(GetDevPropertySize(devId, kAudioDevicePropertyDeviceName, false, 0, &propSize))
+ return devname;
+
+ devname.resize(propSize+1, '\0');
+ if(GetDevProperty(devId, kAudioDevicePropertyDeviceName, false, 0, propSize, &devname[0]))
+ {
+ devname.clear();
+ return devname;
+ }
+ }
+
+ /* Clear extraneous nul chars that may have been written with the name
+ * string, and return it.
+ */
+ while(!devname.back())
+ devname.pop_back();
+ return devname;
+}
+
+UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture)
+{
+ UInt32 propSize{};
+ auto err = GetDevPropertySize(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0,
+ &propSize);
+ if(err)
+ {
+ ERR("kAudioDevicePropertyStreamConfiguration size query failed: %u\n", err);
+ return 0;
+ }
+
+ auto buflist_data = std::make_unique<char[]>(propSize);
+ auto *buflist = reinterpret_cast<AudioBufferList*>(buflist_data.get());
+
+ err = GetDevProperty(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0, propSize,
+ buflist);
+ if(err)
+ {
+ ERR("kAudioDevicePropertyStreamConfiguration query failed: %u\n", err);
+ return 0;
+ }
+
+ UInt32 numChannels{0};
+ for(size_t i{0};i < buflist->mNumberBuffers;++i)
+ numChannels += buflist->mBuffers[i].mNumberChannels;
+
+ return numChannels;
+}
+
+
+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);
+ return;
+ }
+
+ auto devIds = std::vector<AudioDeviceID>(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown);
+ if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data()))
+ {
+ ERR("Failed to get device list: %u\n", err);
+ return;
+ }
+
+ std::vector<DeviceEntry> newdevs;
+ newdevs.reserve(devIds.size());
+
+ AudioDeviceID defaultId{kAudioDeviceUnknown};
+ GetHwProperty(isCapture ? kAudioHardwarePropertyDefaultInputDevice :
+ kAudioHardwarePropertyDefaultOutputDevice, sizeof(defaultId), &defaultId);
+
+ if(defaultId != kAudioDeviceUnknown)
+ {
+ 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);
+ }
+ for(const AudioDeviceID devId : devIds)
+ {
+ if(devId == kAudioDeviceUnknown)
+ continue;
+
+ auto match_devid = [devId](const DeviceEntry &entry) noexcept -> bool
+ { return entry.mId == devId; };
+ auto match = std::find_if(newdevs.cbegin(), newdevs.cend(), match_devid);
+ if(match != newdevs.cend()) continue;
+
+ auto numChannels = GetDeviceChannelCount(devId, isCapture);
+ if(numChannels > 0)
+ {
+ 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);
+ }
+ }
+
+ if(newdevs.size() > 1)
+ {
+ /* Rename entries that have matching names, by appending '#2', '#3',
+ * etc, as needed.
+ */
+ for(auto curitem = newdevs.begin()+1;curitem != newdevs.end();++curitem)
+ {
+ auto check_match = [curitem](const DeviceEntry &entry) -> bool
+ { 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 check_name = [&name](const DeviceEntry &entry) -> bool
+ { return entry.mName == name; };
+ do {
+ name = curitem->mName;
+ name += " #";
+ name += std::to_string(++count);
+ } while(std::find_if(newdevs.begin(), curitem, check_name) != curitem);
+ curitem->mName = std::move(name);
+ }
+ }
+ }
+
+ newdevs.shrink_to_fit();
+ newdevs.swap(list);
+}
+
+#else
+
+static constexpr char ca_device[] = "CoreAudio Default";
+#endif
struct CoreAudioPlayback final : public BackendBase {
- CoreAudioPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+ CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~CoreAudioPlayback() override;
OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags,
@@ -58,14 +268,14 @@ struct CoreAudioPlayback final : public BackendBase {
inBusNumber, inNumberFrames, ioData);
}
- void open(const ALCchar *name) override;
+ void open(const char *name) override;
bool reset() override;
- bool start() override;
+ void start() override;
void stop() override;
AudioUnit mAudioUnit{};
- ALuint mFrameSize{0u};
+ uint mFrameSize{0u};
AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
DEF_NEWDEL(CoreAudioPlayback)
@@ -81,26 +291,53 @@ CoreAudioPlayback::~CoreAudioPlayback()
OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32,
UInt32, AudioBufferList *ioData) noexcept
{
- std::lock_guard<CoreAudioPlayback> _{*this};
- aluMixData(mDevice, ioData->mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize/mFrameSize);
+ for(size_t i{0};i < ioData->mNumberBuffers;++i)
+ {
+ auto &buffer = ioData->mBuffers[i];
+ mDevice->renderSamples(buffer.mData, buffer.mDataByteSize/mFrameSize,
+ buffer.mNumberChannels);
+ }
return noErr;
}
-void CoreAudioPlayback::open(const ALCchar *name)
+void CoreAudioPlayback::open(const char *name)
{
+#if CAN_ENUMERATE
+ AudioDeviceID audioDevice{kAudioDeviceUnknown};
+ if(!name)
+ GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice),
+ &audioDevice);
+ else
+ {
+ if(PlaybackList.empty())
+ EnumerateDevices(PlaybackList, false);
+
+ auto find_name = [name](const DeviceEntry &entry) -> bool
+ { return entry.mName == name; };
+ auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name);
+ if(devmatch == PlaybackList.cend())
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Device name \"%s\" not found", name};
+
+ audioDevice = devmatch->mId;
+ }
+#else
if(!name)
name = ca_device;
else if(strcmp(name, ca_device) != 0)
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+ name};
+#endif
/* open the default output unit */
AudioComponentDescription desc{};
desc.componentType = kAudioUnitType_Output;
-#if TARGET_OS_IOS
- desc.componentSubType = kAudioUnitSubType_RemoteIO;
+#if CAN_ENUMERATE
+ desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
+ kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
#else
- desc.componentSubType = kAudioUnitSubType_DefaultOutput;
+ desc.componentSubType = kAudioUnitSubType_RemoteIO;
#endif
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
@@ -108,19 +345,52 @@ void CoreAudioPlayback::open(const ALCchar *name)
AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
if(comp == nullptr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not find audio component"};
+ throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
- OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
+ AudioUnit audioUnit{};
+ OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)};
if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not create component instance: %u",
- err};
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Could not create component instance: %u", err};
- /* init and start the default audio unit... */
- err = AudioUnitInitialize(mAudioUnit);
+#if CAN_ENUMERATE
+ if(audioDevice != kAudioDeviceUnknown)
+ AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global, OutputElement, &audioDevice, sizeof(AudioDeviceID));
+#endif
+
+ err = AudioUnitInitialize(audioUnit);
if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not initialize audio unit: %u", err};
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Could not initialize audio unit: %u", err};
+ /* WARNING: I don't know if "valid" audio unit values are guaranteed to be
+ * non-0. If not, this logic is broken.
+ */
+ if(mAudioUnit)
+ {
+ AudioUnitUninitialize(mAudioUnit);
+ AudioComponentInstanceDispose(mAudioUnit);
+ }
+ mAudioUnit = audioUnit;
+
+#if CAN_ENUMERATE
+ if(name)
+ mDevice->DeviceName = name;
+ else
+ {
+ UInt32 propSize{sizeof(audioDevice)};
+ audioDevice = kAudioDeviceUnknown;
+ AudioUnitGetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global, OutputElement, &audioDevice, &propSize);
+
+ std::string devname{GetDeviceName(audioDevice)};
+ if(!devname.empty()) mDevice->DeviceName = std::move(devname);
+ else mDevice->DeviceName = "Unknown Device Name";
+ }
+#else
mDevice->DeviceName = name;
+#endif
}
bool CoreAudioPlayback::reset()
@@ -131,10 +401,10 @@ bool CoreAudioPlayback::reset()
/* retrieve default output unit's properties (output side) */
AudioStreamBasicDescription streamFormat{};
- auto size = static_cast<UInt32>(sizeof(AudioStreamBasicDescription));
+ UInt32 size{sizeof(streamFormat)};
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
- 0, &streamFormat, &size);
- if(err != noErr || size != sizeof(AudioStreamBasicDescription))
+ OutputElement, &streamFormat, &size);
+ if(err != noErr || size != sizeof(streamFormat))
{
ERR("AudioUnitGetProperty failed\n");
return false;
@@ -150,99 +420,65 @@ bool CoreAudioPlayback::reset()
TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate);
#endif
- /* set default output unit's input side to match output side */
- err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
- 0, &streamFormat, size);
- if(err != noErr)
- {
- ERR("AudioUnitSetProperty failed\n");
- return false;
- }
-
+ /* Use the sample rate from the output unit's current parameters, but reset
+ * everything else.
+ */
if(mDevice->Frequency != streamFormat.mSampleRate)
{
- mDevice->BufferSize = static_cast<ALuint>(uint64_t{mDevice->BufferSize} *
- streamFormat.mSampleRate / mDevice->Frequency);
- mDevice->Frequency = static_cast<ALuint>(streamFormat.mSampleRate);
+ mDevice->BufferSize = static_cast<uint>(mDevice->BufferSize*streamFormat.mSampleRate/
+ mDevice->Frequency + 0.5);
+ mDevice->Frequency = 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? eg, 6.0 vs 5.1 */
- switch(streamFormat.mChannelsPerFrame)
- {
- case 1:
- mDevice->FmtChans = DevFmtMono;
- break;
- case 2:
- mDevice->FmtChans = DevFmtStereo;
- break;
- case 4:
- mDevice->FmtChans = DevFmtQuad;
- break;
- case 6:
- mDevice->FmtChans = DevFmtX51;
- break;
- case 7:
- mDevice->FmtChans = DevFmtX61;
- break;
- case 8:
- mDevice->FmtChans = DevFmtX71;
- break;
- default:
- ERR("Unhandled channel count (%d), using Stereo\n", streamFormat.mChannelsPerFrame);
- mDevice->FmtChans = DevFmtStereo;
- streamFormat.mChannelsPerFrame = 2;
- break;
- }
- SetDefaultWFXChannelOrder(mDevice);
+ * to specify what we're giving? e.g. 6.0 vs 5.1
+ */
+ streamFormat.mChannelsPerFrame = mDevice->channelsFromFmt();
- /* use channel count and sample rate from the default output unit's current
- * parameters, but reset everything else */
streamFormat.mFramesPerPacket = 1;
- streamFormat.mFormatFlags = 0;
+ streamFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kLinearPCMFormatFlagIsPacked;
+ streamFormat.mFormatID = kAudioFormatLinearPCM;
switch(mDevice->FmtType)
{
case DevFmtUByte:
mDevice->FmtType = DevFmtByte;
/* fall-through */
case DevFmtByte:
- streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
+ streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
streamFormat.mBitsPerChannel = 8;
break;
case DevFmtUShort:
mDevice->FmtType = DevFmtShort;
/* fall-through */
case DevFmtShort:
- streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
+ streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
streamFormat.mBitsPerChannel = 16;
break;
case DevFmtUInt:
mDevice->FmtType = DevFmtInt;
/* fall-through */
case DevFmtInt:
- streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
+ streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
streamFormat.mBitsPerChannel = 32;
break;
case DevFmtFloat:
- streamFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat;
+ streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
streamFormat.mBitsPerChannel = 32;
break;
}
- streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame *
- streamFormat.mBitsPerChannel / 8;
- streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame;
- streamFormat.mFormatID = kAudioFormatLinearPCM;
- streamFormat.mFormatFlags |= kAudioFormatFlagsNativeEndian |
- kLinearPCMFormatFlagIsPacked;
+ streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame*streamFormat.mBitsPerChannel/8;
+ streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame*streamFormat.mFramesPerPacket;
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
- 0, &streamFormat, sizeof(AudioStreamBasicDescription));
+ OutputElement, &streamFormat, sizeof(streamFormat));
if(err != noErr)
{
ERR("AudioUnitSetProperty failed\n");
return false;
}
+ setDefaultWFXChannelOrder();
+
/* setup callback */
mFrameSize = mDevice->frameSizeFromFmt();
AURenderCallbackStruct input{};
@@ -250,7 +486,7 @@ bool CoreAudioPlayback::reset()
input.inputProcRefCon = this;
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback,
- kAudioUnitScope_Input, 0, &input, sizeof(AURenderCallbackStruct));
+ kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct));
if(err != noErr)
{
ERR("AudioUnitSetProperty failed\n");
@@ -268,15 +504,12 @@ bool CoreAudioPlayback::reset()
return true;
}
-bool CoreAudioPlayback::start()
+void CoreAudioPlayback::start()
{
- OSStatus err{AudioOutputUnitStart(mAudioUnit)};
+ const OSStatus err{AudioOutputUnitStart(mAudioUnit)};
if(err != noErr)
- {
- ERR("AudioOutputUnitStart failed\n");
- return false;
- }
- return true;
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "AudioOutputUnitStart failed: %d", err};
}
void CoreAudioPlayback::stop()
@@ -288,7 +521,7 @@ void CoreAudioPlayback::stop()
struct CoreAudioCapture final : public BackendBase {
- CoreAudioCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+ CoreAudioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~CoreAudioCapture() override;
OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
@@ -302,19 +535,21 @@ struct CoreAudioCapture final : public BackendBase {
inBusNumber, inNumberFrames, ioData);
}
- void open(const ALCchar *name) override;
- bool start() override;
+ void open(const char *name) override;
+ void start() override;
void stop() override;
- ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
- ALCuint availableSamples() override;
+ void captureSamples(al::byte *buffer, uint samples) override;
+ uint availableSamples() override;
AudioUnit mAudioUnit{0};
- ALuint mFrameSize{0u};
+ uint mFrameSize{0u};
AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
SampleConverterPtr mConverter;
+ al::vector<char> mCaptureData;
+
RingBufferPtr mRing{nullptr};
DEF_NEWDEL(CoreAudioCapture)
@@ -328,181 +563,176 @@ CoreAudioCapture::~CoreAudioCapture()
}
-OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags*,
- const AudioTimeStamp *inTimeStamp, UInt32, UInt32 inNumberFrames,
+OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
+ const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList*) noexcept
{
- AudioUnitRenderActionFlags flags = 0;
union {
- al::byte _[sizeof(AudioBufferList) + sizeof(AudioBuffer)*2];
+ al::byte _[maxz(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))];
AudioBufferList list;
} audiobuf{};
- auto rec_vec = mRing->getWriteVector();
- inNumberFrames = static_cast<UInt32>(minz(inNumberFrames,
- rec_vec.first.len+rec_vec.second.len));
+ audiobuf.list.mNumberBuffers = 1;
+ audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
+ audiobuf.list.mBuffers[0].mData = mCaptureData.data();
+ audiobuf.list.mBuffers[0].mDataByteSize = static_cast<UInt32>(mCaptureData.size());
- // Fill the ringbuffer's two segments with data from the input device
- if(rec_vec.first.len >= inNumberFrames)
- {
- audiobuf.list.mNumberBuffers = 1;
- audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
- audiobuf.list.mBuffers[0].mData = rec_vec.first.buf;
- audiobuf.list.mBuffers[0].mDataByteSize = inNumberFrames * mFormat.mBytesPerFrame;
- }
- else
- {
- const auto remaining = static_cast<ALuint>(inNumberFrames - rec_vec.first.len);
- audiobuf.list.mNumberBuffers = 2;
- audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
- audiobuf.list.mBuffers[0].mData = rec_vec.first.buf;
- audiobuf.list.mBuffers[0].mDataByteSize = static_cast<UInt32>(rec_vec.first.len) *
- mFormat.mBytesPerFrame;
- audiobuf.list.mBuffers[1].mNumberChannels = mFormat.mChannelsPerFrame;
- audiobuf.list.mBuffers[1].mData = rec_vec.second.buf;
- audiobuf.list.mBuffers[1].mDataByteSize = remaining * mFormat.mBytesPerFrame;
- }
- OSStatus err{AudioUnitRender(mAudioUnit, &flags, inTimeStamp, audiobuf.list.mNumberBuffers,
+ OSStatus err{AudioUnitRender(mAudioUnit, ioActionFlags, inTimeStamp, inBusNumber,
inNumberFrames, &audiobuf.list)};
if(err != noErr)
{
- ERR("AudioUnitRender error: %d\n", err);
+ ERR("AudioUnitRender capture error: %d\n", err);
return err;
}
- mRing->writeAdvance(inNumberFrames);
+ mRing->write(mCaptureData.data(), inNumberFrames);
return noErr;
}
-void CoreAudioCapture::open(const ALCchar *name)
+void CoreAudioCapture::open(const char *name)
{
- AudioStreamBasicDescription requestedFormat; // The application requested format
- AudioStreamBasicDescription hardwareFormat; // The hardware format
- AudioStreamBasicDescription outputFormat; // The AudioUnit output format
- AURenderCallbackStruct input;
- AudioComponentDescription desc;
- UInt32 outputFrameCount;
- UInt32 propertySize;
-#if !TARGET_OS_IOS
- AudioObjectPropertyAddress propertyAddress;
-#endif
- UInt32 enableIO;
- AudioComponent comp;
- OSStatus err;
+#if CAN_ENUMERATE
+ AudioDeviceID audioDevice{kAudioDeviceUnknown};
+ if(!name)
+ GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(audioDevice),
+ &audioDevice);
+ else
+ {
+ if(CaptureList.empty())
+ EnumerateDevices(CaptureList, true);
+
+ auto find_name = [name](const DeviceEntry &entry) -> bool
+ { return entry.mName == name; };
+ auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name);
+ if(devmatch == CaptureList.cend())
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Device name \"%s\" not found", name};
+ audioDevice = devmatch->mId;
+ }
+#else
if(!name)
name = ca_device;
else if(strcmp(name, ca_device) != 0)
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+ name};
+#endif
+ AudioComponentDescription desc{};
desc.componentType = kAudioUnitType_Output;
-#if TARGET_OS_IOS
- desc.componentSubType = kAudioUnitSubType_RemoteIO;
+#if CAN_ENUMERATE
+ desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
+ kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
#else
- desc.componentSubType = kAudioUnitSubType_HALOutput;
+ desc.componentSubType = kAudioUnitSubType_RemoteIO;
#endif
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
// Search for component with given description
- comp = AudioComponentFindNext(NULL, &desc);
+ AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
if(comp == NULL)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not find audio component"};
+ throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
// Open the component
- err = AudioComponentInstanceNew(comp, &mAudioUnit);
+ OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not create component instance: %u",
- err};
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Could not create component instance: %u", err};
// Turn off AudioUnit output
- enableIO = 0;
+ UInt32 enableIO{0};
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
- kAudioUnitScope_Output, 0, &enableIO, sizeof(ALuint));
+ kAudioUnitScope_Output, OutputElement, &enableIO, sizeof(enableIO));
if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE,
+ throw al::backend_exception{al::backend_error::DeviceError,
"Could not disable audio unit output property: %u", err};
// Turn on AudioUnit input
enableIO = 1;
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
- kAudioUnitScope_Input, 1, &enableIO, sizeof(ALuint));
+ kAudioUnitScope_Input, InputElement, &enableIO, sizeof(enableIO));
if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE,
+ throw al::backend_exception{al::backend_error::DeviceError,
"Could not enable audio unit input property: %u", err};
-#if !TARGET_OS_IOS
- {
- // Get the default input device
- AudioDeviceID inputDevice = kAudioDeviceUnknown;
-
- propertySize = sizeof(AudioDeviceID);
- propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
- propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
- propertyAddress.mElement = kAudioObjectPropertyElementMaster;
-
- err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, nullptr,
- &propertySize, &inputDevice);
- if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not get input device: %u", err};
- if(inputDevice == kAudioDeviceUnknown)
- throw al::backend_exception{ALC_INVALID_VALUE, "Unknown input device"};
-
- // Track the input device
- err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
- kAudioUnitScope_Global, 0, &inputDevice, sizeof(AudioDeviceID));
- if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not set input device: %u", err};
- }
+#if CAN_ENUMERATE
+ if(audioDevice != kAudioDeviceUnknown)
+ AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global, InputElement, &audioDevice, sizeof(AudioDeviceID));
#endif
// set capture callback
+ AURenderCallbackStruct input{};
input.inputProc = CoreAudioCapture::RecordProcC;
input.inputProcRefCon = this;
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback,
- kAudioUnitScope_Global, 0, &input, sizeof(AURenderCallbackStruct));
+ kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct));
+ if(err != noErr)
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Could not set capture callback: %u", err};
+
+ // Disable buffer allocation for capture
+ UInt32 flag{0};
+ err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_ShouldAllocateBuffer,
+ kAudioUnitScope_Output, InputElement, &flag, sizeof(flag));
if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not set capture callback: %u", err};
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Could not disable buffer allocation property: %u", err};
// Initialize the device
err = AudioUnitInitialize(mAudioUnit);
if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not initialize audio unit: %u", err};
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Could not initialize audio unit: %u", err};
// Get the hardware format
- propertySize = sizeof(AudioStreamBasicDescription);
+ AudioStreamBasicDescription hardwareFormat{};
+ UInt32 propertySize{sizeof(hardwareFormat)};
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
- 1, &hardwareFormat, &propertySize);
- if(err != noErr || propertySize != sizeof(AudioStreamBasicDescription))
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not get input format: %u", err};
+ InputElement, &hardwareFormat, &propertySize);
+ if(err != noErr || propertySize != sizeof(hardwareFormat))
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Could not get input format: %u", err};
// Set up the requested format description
+ AudioStreamBasicDescription requestedFormat{};
switch(mDevice->FmtType)
{
+ case DevFmtByte:
+ requestedFormat.mBitsPerChannel = 8;
+ requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
+ break;
case DevFmtUByte:
requestedFormat.mBitsPerChannel = 8;
requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked;
break;
case DevFmtShort:
requestedFormat.mBitsPerChannel = 16;
- requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
+ requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger
+ | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
+ break;
+ case DevFmtUShort:
+ requestedFormat.mBitsPerChannel = 16;
+ requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
break;
case DevFmtInt:
requestedFormat.mBitsPerChannel = 32;
- requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
+ requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger
+ | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
+ break;
+ case DevFmtUInt:
+ requestedFormat.mBitsPerChannel = 32;
+ requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
break;
case DevFmtFloat:
requestedFormat.mBitsPerChannel = 32;
- requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked;
+ requestedFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian
+ | kAudioFormatFlagIsPacked;
break;
- case DevFmtByte:
- case DevFmtUShort:
- case DevFmtUInt:
- throw al::backend_exception{ALC_INVALID_VALUE, "%s samples not suppoted",
- DevFmtTypeString(mDevice->FmtType)};
}
switch(mDevice->FmtChans)
@@ -516,11 +746,12 @@ void CoreAudioCapture::open(const ALCchar *name)
case DevFmtQuad:
case DevFmtX51:
- case DevFmtX51Rear:
case DevFmtX61:
case DevFmtX71:
+ case DevFmtX714:
+ case DevFmtX3D71:
case DevFmtAmbi3D:
- throw al::backend_exception{ALC_INVALID_VALUE, "%s not supported",
+ throw al::backend_exception{al::backend_error::DeviceError, "%s not supported",
DevFmtChannelsString(mDevice->FmtChans)};
}
@@ -537,53 +768,73 @@ void CoreAudioCapture::open(const ALCchar *name)
// Use intermediate format for sample rate conversion (outputFormat)
// Set sample rate to the same as hardware for resampling later
- outputFormat = requestedFormat;
+ AudioStreamBasicDescription outputFormat{requestedFormat};
outputFormat.mSampleRate = hardwareFormat.mSampleRate;
// The output format should be the requested format, but using the hardware sample rate
// This is because the AudioUnit will automatically scale other properties, except for sample rate
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
- 1, &outputFormat, sizeof(outputFormat));
+ InputElement, &outputFormat, sizeof(outputFormat));
if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not set input format: %u", err};
-
- // Set the AudioUnit output format frame count
- uint64_t FrameCount64{mDevice->UpdateSize};
- FrameCount64 = static_cast<uint64_t>(FrameCount64*outputFormat.mSampleRate + mDevice->Frequency-1) /
- mDevice->Frequency;
- FrameCount64 += MAX_RESAMPLER_PADDING;
- if(FrameCount64 > std::numeric_limits<uint32_t>::max()/2)
- throw al::backend_exception{ALC_INVALID_VALUE,
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Could not set input format: %u", err};
+
+ /* Calculate the minimum AudioUnit output format frame count for the pre-
+ * conversion ring buffer. Ensure at least 100ms for the total buffer.
+ */
+ double srateScale{outputFormat.mSampleRate / mDevice->Frequency};
+ auto FrameCount64 = maxu64(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)),
+ static_cast<UInt32>(outputFormat.mSampleRate)/10);
+ 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};
- outputFrameCount = static_cast<uint32_t>(FrameCount64);
- err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
- kAudioUnitScope_Output, 0, &outputFrameCount, sizeof(outputFrameCount));
- if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Failed to set capture frame count: %u",
- err};
+ UInt32 outputFrameCount{};
+ propertySize = sizeof(outputFrameCount);
+ err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
+ kAudioUnitScope_Global, OutputElement, &outputFrameCount, &propertySize);
+ if(err != noErr || propertySize != sizeof(outputFrameCount))
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Could not get input frame count: %u", err};
+
+ mCaptureData.resize(outputFrameCount * mFrameSize);
+
+ outputFrameCount = static_cast<UInt32>(maxu64(outputFrameCount, FrameCount64));
+ mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false);
- // Set up sample converter if needed
+ /* Set up sample converter if needed */
if(outputFormat.mSampleRate != mDevice->Frequency)
- mConverter = CreateSampleConverter(mDevice->FmtType, mDevice->FmtType,
- mFormat.mChannelsPerFrame, static_cast<ALuint>(hardwareFormat.mSampleRate),
+ mConverter = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType,
+ mFormat.mChannelsPerFrame, static_cast<uint>(hardwareFormat.mSampleRate),
mDevice->Frequency, Resampler::FastBSinc24);
- mRing = CreateRingBuffer(outputFrameCount, mFrameSize, false);
-
+#if CAN_ENUMERATE
+ if(name)
+ mDevice->DeviceName = name;
+ else
+ {
+ UInt32 propSize{sizeof(audioDevice)};
+ audioDevice = kAudioDeviceUnknown;
+ AudioUnitGetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global, InputElement, &audioDevice, &propSize);
+
+ std::string devname{GetDeviceName(audioDevice)};
+ if(!devname.empty()) mDevice->DeviceName = std::move(devname);
+ else mDevice->DeviceName = "Unknown Device Name";
+ }
+#else
mDevice->DeviceName = name;
+#endif
}
-bool CoreAudioCapture::start()
+void CoreAudioCapture::start()
{
OSStatus err{AudioOutputUnitStart(mAudioUnit)};
if(err != noErr)
- {
- ERR("AudioOutputUnitStart failed\n");
- return false;
- }
- return true;
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "AudioOutputUnitStart failed: %d", err};
}
void CoreAudioCapture::stop()
@@ -593,35 +844,34 @@ void CoreAudioCapture::stop()
ERR("AudioOutputUnitStop failed\n");
}
-ALCenum CoreAudioCapture::captureSamples(al::byte *buffer, ALCuint samples)
+void CoreAudioCapture::captureSamples(al::byte *buffer, uint samples)
{
if(!mConverter)
{
mRing->read(buffer, samples);
- return ALC_NO_ERROR;
+ return;
}
auto rec_vec = mRing->getReadVector();
const void *src0{rec_vec.first.buf};
- auto src0len = static_cast<ALuint>(rec_vec.first.len);
- ALuint got{mConverter->convert(&src0, &src0len, buffer, samples)};
+ auto src0len = static_cast<uint>(rec_vec.first.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)
{
const void *src1{rec_vec.second.buf};
- auto src1len = static_cast<ALuint>(rec_vec.second.len);
- got += mConverter->convert(&src1, &src1len, buffer+got, samples-got);
+ auto src1len = static_cast<uint>(rec_vec.second.len);
+ got += mConverter->convert(&src1, &src1len, buffer + got*mFrameSize, samples-got);
total_read += rec_vec.second.len - src1len;
}
mRing->readAdvance(total_read);
- return ALC_NO_ERROR;
}
-ALCuint CoreAudioCapture::availableSamples()
+uint CoreAudioCapture::availableSamples()
{
- if(!mConverter) return static_cast<ALCuint>(mRing->readSpace());
- return mConverter->availableOut(static_cast<ALCuint>(mRing->readSpace()));
+ if(!mConverter) return static_cast<uint>(mRing->readSpace());
+ return mConverter->availableOut(static_cast<uint>(mRing->readSpace()));
}
} // namespace
@@ -637,19 +887,42 @@ bool CoreAudioBackendFactory::init() { return true; }
bool CoreAudioBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
-void CoreAudioBackendFactory::probe(DevProbe type, std::string *outnames)
+std::string CoreAudioBackendFactory::probe(BackendType type)
{
+ 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);
+ };
switch(type)
{
- case DevProbe::Playback:
- case DevProbe::Capture:
- /* Includes null char. */
- outnames->append(ca_device, sizeof(ca_device));
- break;
+ case BackendType::Playback:
+ EnumerateDevices(PlaybackList, false);
+ std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
+ break;
+ case BackendType::Capture:
+ EnumerateDevices(CaptureList, true);
+ std::for_each(CaptureList.cbegin(), CaptureList.cend(), append_name);
+ break;
}
+
+#else
+
+ switch(type)
+ {
+ case BackendType::Playback:
+ case BackendType::Capture:
+ /* Includes null char. */
+ outnames.append(ca_device, sizeof(ca_device));
+ break;
+ }
+#endif
+ return outnames;
}
-BackendPtr CoreAudioBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new CoreAudioPlayback{device}};
diff --git a/alc/backends/coreaudio.h b/alc/backends/coreaudio.h
index 37b9ebe5..1252edde 100644
--- a/alc/backends/coreaudio.h
+++ b/alc/backends/coreaudio.h
@@ -1,7 +1,7 @@
#ifndef BACKENDS_COREAUDIO_H
#define BACKENDS_COREAUDIO_H
-#include "backends/base.h"
+#include "base.h"
struct CoreAudioBackendFactory final : public BackendFactory {
public:
@@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
- void probe(DevProbe type, std::string *outnames) override;
+ std::string probe(BackendType type) override;
- BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+ BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
diff --git a/alc/backends/dsound.cpp b/alc/backends/dsound.cpp
index c04ba9e4..f549c0fe 100644
--- a/alc/backends/dsound.cpp
+++ b/alc/backends/dsound.cpp
@@ -20,7 +20,7 @@
#include "config.h"
-#include "backends/dsound.h"
+#include "dsound.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
@@ -44,12 +44,13 @@
#include <algorithm>
#include <functional>
-#include "alcmain.h"
-#include "alexcpt.h"
-#include "alu.h"
-#include "ringbuffer.h"
-#include "compat.h"
+#include "alnumeric.h"
+#include "comptr.h"
+#include "core/device.h"
+#include "core/helpers.h"
+#include "core/logging.h"
#include "dynload.h"
+#include "ringbuffer.h"
#include "strutils.h"
#include "threads.h"
@@ -107,6 +108,15 @@ HRESULT (WINAPI *pDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallbac
#endif
+#define MONO SPEAKER_FRONT_CENTER
+#define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)
+#define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
+#define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
+#define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
+#define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
+#define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
+#define X7DOT1DOT4 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT|SPEAKER_TOP_FRONT_LEFT|SPEAKER_TOP_FRONT_RIGHT|SPEAKER_TOP_BACK_LEFT|SPEAKER_TOP_BACK_RIGHT)
+
#define MAX_UPDATES 128
struct DevMap {
@@ -161,21 +171,21 @@ BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, voi
struct DSoundPlayback final : public BackendBase {
- DSoundPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+ DSoundPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~DSoundPlayback() override;
int mixerProc();
- void open(const ALCchar *name) override;
+ void open(const char *name) override;
bool reset() override;
- bool start() override;
+ void start() override;
void stop() override;
- IDirectSound *mDS{nullptr};
- IDirectSoundBuffer *mPrimaryBuffer{nullptr};
- IDirectSoundBuffer *mBuffer{nullptr};
- IDirectSoundNotify *mNotifies{nullptr};
- HANDLE mNotifyEvent{nullptr};
+ ComPtr<IDirectSound> mDS;
+ ComPtr<IDirectSoundBuffer> mPrimaryBuffer;
+ ComPtr<IDirectSoundBuffer> mBuffer;
+ ComPtr<IDirectSoundNotify> mNotifies;
+ HANDLE mNotifyEvent{nullptr};
std::atomic<bool> mKillNow{true};
std::thread mThread;
@@ -185,19 +195,11 @@ struct DSoundPlayback final : public BackendBase {
DSoundPlayback::~DSoundPlayback()
{
- if(mNotifies)
- mNotifies->Release();
mNotifies = nullptr;
- if(mBuffer)
- mBuffer->Release();
mBuffer = nullptr;
- if(mPrimaryBuffer)
- mPrimaryBuffer->Release();
mPrimaryBuffer = nullptr;
-
- if(mDS)
- mDS->Release();
mDS = nullptr;
+
if(mNotifyEvent)
CloseHandle(mNotifyEvent);
mNotifyEvent = nullptr;
@@ -215,18 +217,19 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
if(FAILED(err))
{
ERR("Failed to get buffer caps: 0x%lx\n", err);
- aluHandleDisconnect(mDevice, "Failure retrieving playback buffer info: 0x%lx", err);
+ mDevice->handleDisconnect("Failure retrieving playback buffer info: 0x%lx", err);
return 1;
}
- ALuint FrameSize{mDevice->frameSizeFromFmt()};
+ const size_t FrameStep{mDevice->channelsFromFmt()};
+ uint FrameSize{mDevice->frameSizeFromFmt()};
DWORD FragSize{mDevice->UpdateSize * FrameSize};
bool Playing{false};
DWORD LastCursor{0u};
mBuffer->GetCurrentPosition(&LastCursor, nullptr);
- while(!mKillNow.load(std::memory_order_acquire) &&
- mDevice->Connected.load(std::memory_order_acquire))
+ while(!mKillNow.load(std::memory_order_acquire)
+ && mDevice->Connected.load(std::memory_order_acquire))
{
// Get current play cursor
DWORD PlayCursor;
@@ -241,7 +244,7 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
if(FAILED(err))
{
ERR("Failed to play buffer: 0x%lx\n", err);
- aluHandleDisconnect(mDevice, "Failure starting playback: 0x%lx", err);
+ mDevice->handleDisconnect("Failure starting playback: 0x%lx", err);
return 1;
}
Playing = true;
@@ -275,19 +278,16 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
if(SUCCEEDED(err))
{
- std::unique_lock<DSoundPlayback> dlock{*this};
- aluMixData(mDevice, WritePtr1, WriteCnt1/FrameSize);
+ mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep);
if(WriteCnt2 > 0)
- aluMixData(mDevice, WritePtr2, WriteCnt2/FrameSize);
- dlock.unlock();
+ mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep);
mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2);
}
else
{
ERR("Buffer lock error: %#lx\n", err);
- std::lock_guard<DSoundPlayback> _{*this};
- aluHandleDisconnect(mDevice, "Failed to lock output buffer: 0x%lx", err);
+ mDevice->handleDisconnect("Failed to lock output buffer: 0x%lx", err);
return 1;
}
@@ -299,7 +299,7 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
return 0;
}
-void DSoundPlayback::open(const ALCchar *name)
+void DSoundPlayback::open(const char *name)
{
HRESULT hr;
if(PlaybackDevices.empty())
@@ -322,155 +322,124 @@ void DSoundPlayback::open(const ALCchar *name)
else
{
auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
- [name](const DevMap &entry) -> bool
- { return entry.name == name; }
- );
+ [name](const DevMap &entry) -> bool { return entry.name == name; });
if(iter == PlaybackDevices.cend())
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ {
+ GUID id{};
+ hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id);
+ if(SUCCEEDED(hr))
+ iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
+ [&id](const DevMap &entry) -> bool { return entry.guid == id; });
+ if(iter == PlaybackDevices.cend())
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Device name \"%s\" not found", name};
+ }
guid = &iter->guid;
}
hr = DS_OK;
- mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
- if(!mNotifyEvent) hr = E_FAIL;
+ if(!mNotifyEvent)
+ {
+ mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
+ if(!mNotifyEvent) hr = E_FAIL;
+ }
//DirectSound Init code
+ ComPtr<IDirectSound> ds;
if(SUCCEEDED(hr))
- hr = DirectSoundCreate(guid, &mDS, nullptr);
+ hr = DirectSoundCreate(guid, ds.getPtr(), nullptr);
if(SUCCEEDED(hr))
- hr = mDS->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
+ hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
if(FAILED(hr))
- throw al::backend_exception{ALC_INVALID_VALUE, "Device init failed: 0x%08lx", hr};
+ throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
+ hr};
+
+ mNotifies = nullptr;
+ mBuffer = nullptr;
+ mPrimaryBuffer = nullptr;
+ mDS = std::move(ds);
mDevice->DeviceName = name;
}
bool DSoundPlayback::reset()
{
- if(mNotifies)
- mNotifies->Release();
mNotifies = nullptr;
- if(mBuffer)
- mBuffer->Release();
mBuffer = nullptr;
- if(mPrimaryBuffer)
- mPrimaryBuffer->Release();
mPrimaryBuffer = nullptr;
switch(mDevice->FmtType)
{
- case DevFmtByte:
- mDevice->FmtType = DevFmtUByte;
- break;
- case DevFmtFloat:
- if(mDevice->Flags.get<SampleTypeRequest>())
- break;
- /* fall-through */
- case DevFmtUShort:
- mDevice->FmtType = DevFmtShort;
- break;
- case DevFmtUInt:
- mDevice->FmtType = DevFmtInt;
- break;
- case DevFmtUByte:
- case DevFmtShort:
- case DevFmtInt:
+ case DevFmtByte:
+ mDevice->FmtType = DevFmtUByte;
+ break;
+ case DevFmtFloat:
+ if(mDevice->Flags.test(SampleTypeRequest))
break;
+ /* fall-through */
+ case DevFmtUShort:
+ mDevice->FmtType = DevFmtShort;
+ break;
+ case DevFmtUInt:
+ mDevice->FmtType = DevFmtInt;
+ break;
+ case DevFmtUByte:
+ case DevFmtShort:
+ case DevFmtInt:
+ break;
}
WAVEFORMATEXTENSIBLE OutputType{};
- DWORD speakers;
+ DWORD speakers{};
HRESULT hr{mDS->GetSpeakerConfig(&speakers)};
- if(SUCCEEDED(hr))
+ if(FAILED(hr))
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to get speaker config: 0x%08lx", hr};
+
+ speakers = DSSPEAKER_CONFIG(speakers);
+ if(!mDevice->Flags.test(ChannelsRequest))
{
- speakers = DSSPEAKER_CONFIG(speakers);
- if(!mDevice->Flags.get<ChannelsRequest>())
- {
- if(speakers == DSSPEAKER_MONO)
- mDevice->FmtChans = DevFmtMono;
- else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE)
- mDevice->FmtChans = DevFmtStereo;
- else if(speakers == DSSPEAKER_QUAD)
- mDevice->FmtChans = DevFmtQuad;
- else if(speakers == DSSPEAKER_5POINT1_SURROUND)
- mDevice->FmtChans = DevFmtX51;
- else if(speakers == DSSPEAKER_5POINT1_BACK)
- mDevice->FmtChans = DevFmtX51Rear;
- else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
- mDevice->FmtChans = DevFmtX71;
- else
- ERR("Unknown system speaker config: 0x%lx\n", speakers);
- }
- mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo &&
- speakers == DSSPEAKER_HEADPHONE);
+ if(speakers == DSSPEAKER_MONO)
+ mDevice->FmtChans = DevFmtMono;
+ else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE)
+ mDevice->FmtChans = DevFmtStereo;
+ else if(speakers == DSSPEAKER_QUAD)
+ mDevice->FmtChans = DevFmtQuad;
+ else if(speakers == DSSPEAKER_5POINT1_SURROUND || speakers == DSSPEAKER_5POINT1_BACK)
+ mDevice->FmtChans = DevFmtX51;
+ else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
+ mDevice->FmtChans = DevFmtX71;
+ else
+ ERR("Unknown system speaker config: 0x%lx\n", speakers);
+ }
+ mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE));
+ const bool isRear51{speakers == DSSPEAKER_5POINT1_BACK};
- switch(mDevice->FmtChans)
- {
- case DevFmtMono:
- OutputType.dwChannelMask = SPEAKER_FRONT_CENTER;
- break;
- case DevFmtAmbi3D:
- mDevice->FmtChans = DevFmtStereo;
- /*fall-through*/
- case DevFmtStereo:
- OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
- SPEAKER_FRONT_RIGHT;
- break;
- case DevFmtQuad:
- OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
- SPEAKER_FRONT_RIGHT |
- SPEAKER_BACK_LEFT |
- SPEAKER_BACK_RIGHT;
- break;
- case DevFmtX51:
- OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
- SPEAKER_FRONT_RIGHT |
- SPEAKER_FRONT_CENTER |
- SPEAKER_LOW_FREQUENCY |
- SPEAKER_SIDE_LEFT |
- SPEAKER_SIDE_RIGHT;
- break;
- case DevFmtX51Rear:
- OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
- SPEAKER_FRONT_RIGHT |
- SPEAKER_FRONT_CENTER |
- SPEAKER_LOW_FREQUENCY |
- SPEAKER_BACK_LEFT |
- SPEAKER_BACK_RIGHT;
- break;
- case DevFmtX61:
- OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
- SPEAKER_FRONT_RIGHT |
- SPEAKER_FRONT_CENTER |
- SPEAKER_LOW_FREQUENCY |
- SPEAKER_BACK_CENTER |
- SPEAKER_SIDE_LEFT |
- SPEAKER_SIDE_RIGHT;
- break;
- case DevFmtX71:
- OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
- SPEAKER_FRONT_RIGHT |
- SPEAKER_FRONT_CENTER |
- SPEAKER_LOW_FREQUENCY |
- SPEAKER_BACK_LEFT |
- SPEAKER_BACK_RIGHT |
- SPEAKER_SIDE_LEFT |
- SPEAKER_SIDE_RIGHT;
- break;
- }
+ switch(mDevice->FmtChans)
+ {
+ case DevFmtMono: OutputType.dwChannelMask = MONO; break;
+ case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo;
+ /* fall-through */
+ case DevFmtStereo: OutputType.dwChannelMask = STEREO; break;
+ case DevFmtQuad: OutputType.dwChannelMask = QUAD; break;
+ case DevFmtX51: OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break;
+ case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
+ case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break;
+ 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;
- }
+ hr = S_OK;
+ OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
+ OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
+ OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
+ OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
+ OutputType.Format.wBitsPerSample / 8);
+ OutputType.Format.nSamplesPerSec = mDevice->Frequency;
+ OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
+ OutputType.Format.nBlockAlign;
+ OutputType.Format.cbSize = 0;
if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
{
@@ -482,8 +451,6 @@ retry_open:
else
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
- if(mPrimaryBuffer)
- mPrimaryBuffer->Release();
mPrimaryBuffer = nullptr;
}
else
@@ -493,7 +460,7 @@ retry_open:
DSBUFFERDESC DSBDescription{};
DSBDescription.dwSize = sizeof(DSBDescription);
DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
- hr = mDS->CreateSoundBuffer(&DSBDescription, &mPrimaryBuffer, nullptr);
+ hr = mDS->CreateSoundBuffer(&DSBDescription, mPrimaryBuffer.getPtr(), nullptr);
}
if(SUCCEEDED(hr))
hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
@@ -501,19 +468,19 @@ retry_open:
if(SUCCEEDED(hr))
{
- ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
+ uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
if(num_updates > MAX_UPDATES)
num_updates = MAX_UPDATES;
mDevice->BufferSize = mDevice->UpdateSize * num_updates;
DSBUFFERDESC DSBDescription{};
DSBDescription.dwSize = sizeof(DSBDescription);
- DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 |
- DSBCAPS_GLOBALFOCUS;
+ DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2
+ | DSBCAPS_GLOBALFOCUS;
DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign;
DSBDescription.lpwfxFormat = &OutputType.Format;
- hr = mDS->CreateSoundBuffer(&DSBDescription, &mBuffer, nullptr);
+ hr = mDS->CreateSoundBuffer(&DSBDescription, mBuffer.getPtr(), nullptr);
if(FAILED(hr) && mDevice->FmtType == DevFmtFloat)
{
mDevice->FmtType = DevFmtShort;
@@ -527,56 +494,46 @@ retry_open:
hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr);
if(SUCCEEDED(hr))
{
- auto Notifies = static_cast<IDirectSoundNotify*>(ptr);
- mNotifies = Notifies;
+ mNotifies = ComPtr<IDirectSoundNotify>{static_cast<IDirectSoundNotify*>(ptr)};
- ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
+ uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
assert(num_updates <= MAX_UPDATES);
std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots;
- for(ALuint i{0};i < num_updates;++i)
+ for(uint i{0};i < num_updates;++i)
{
nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign;
nots[i].hEventNotify = mNotifyEvent;
}
- if(Notifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK)
+ if(mNotifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK)
hr = E_FAIL;
}
}
if(FAILED(hr))
{
- if(mNotifies)
- mNotifies->Release();
mNotifies = nullptr;
- if(mBuffer)
- mBuffer->Release();
mBuffer = nullptr;
- if(mPrimaryBuffer)
- mPrimaryBuffer->Release();
mPrimaryBuffer = nullptr;
return false;
}
ResetEvent(mNotifyEvent);
- SetDefaultWFXChannelOrder(mDevice);
+ setDefaultWFXChannelOrder();
return true;
}
-bool DSoundPlayback::start()
+void DSoundPlayback::start()
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&DSoundPlayback::mixerProc), this};
- return true;
}
catch(std::exception& e) {
- ERR("Failed to start mixing thread: %s\n", e.what());
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to start mixing thread: %s", e.what()};
}
- catch(...) {
- }
- return false;
}
void DSoundPlayback::stop()
@@ -590,17 +547,17 @@ void DSoundPlayback::stop()
struct DSoundCapture final : public BackendBase {
- DSoundCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+ DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~DSoundCapture() override;
- void open(const ALCchar *name) override;
- bool start() override;
+ void open(const char *name) override;
+ void start() override;
void stop() override;
- ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
- ALCuint availableSamples() override;
+ void captureSamples(al::byte *buffer, uint samples) override;
+ uint availableSamples() override;
- IDirectSoundCapture *mDSC{nullptr};
- IDirectSoundCaptureBuffer *mDSCbuffer{nullptr};
+ ComPtr<IDirectSoundCapture> mDSC;
+ ComPtr<IDirectSoundCaptureBuffer> mDSCbuffer;
DWORD mBufferBytes{0u};
DWORD mCursor{0u};
@@ -614,17 +571,13 @@ DSoundCapture::~DSoundCapture()
if(mDSCbuffer)
{
mDSCbuffer->Stop();
- mDSCbuffer->Release();
mDSCbuffer = nullptr;
}
-
- if(mDSC)
- mDSC->Release();
mDSC = nullptr;
}
-void DSoundCapture::open(const ALCchar *name)
+void DSoundCapture::open(const char *name)
{
HRESULT hr;
if(CaptureDevices.empty())
@@ -647,11 +600,18 @@ void DSoundCapture::open(const ALCchar *name)
else
{
auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
- [name](const DevMap &entry) -> bool
- { return entry.name == name; }
- );
+ [name](const DevMap &entry) -> bool { return entry.name == name; });
if(iter == CaptureDevices.cend())
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ {
+ GUID id{};
+ hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id);
+ if(SUCCEEDED(hr))
+ iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
+ [&id](const DevMap &entry) -> bool { return entry.guid == id; });
+ if(iter == CaptureDevices.cend())
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Device name \"%s\" not found", name};
+ }
guid = &iter->guid;
}
@@ -661,8 +621,8 @@ void DSoundCapture::open(const ALCchar *name)
case DevFmtUShort:
case DevFmtUInt:
WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType));
- throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported",
- DevFmtTypeString(mDevice->FmtType)};
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
case DevFmtUByte:
case DevFmtShort:
@@ -674,36 +634,17 @@ void DSoundCapture::open(const ALCchar *name)
WAVEFORMATEXTENSIBLE InputType{};
switch(mDevice->FmtChans)
{
- case DevFmtMono:
- InputType.dwChannelMask = SPEAKER_FRONT_CENTER;
- break;
- case DevFmtStereo:
- InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
- break;
- case DevFmtQuad:
- InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT |
- SPEAKER_BACK_RIGHT;
- break;
- case DevFmtX51:
- InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
- SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT;
- break;
- case DevFmtX51Rear:
- InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
- SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
- break;
- case DevFmtX61:
- InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
- SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_CENTER | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT;
- break;
- case DevFmtX71:
- InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
- SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT |
- SPEAKER_SIDE_RIGHT;
- break;
+ case DevFmtMono: InputType.dwChannelMask = MONO; break;
+ case DevFmtStereo: InputType.dwChannelMask = STEREO; break;
+ case DevFmtQuad: InputType.dwChannelMask = QUAD; break;
+ case DevFmtX51: InputType.dwChannelMask = X5DOT1; break;
+ case DevFmtX61: InputType.dwChannelMask = X6DOT1; break;
+ case DevFmtX71: InputType.dwChannelMask = X7DOT1; break;
+ case DevFmtX714: InputType.dwChannelMask = X7DOT1DOT4; break;
+ case DevFmtX3D71:
case DevFmtAmbi3D:
WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
- throw al::backend_exception{ALC_INVALID_VALUE, "%s capture not supported",
+ throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
DevFmtChannelsString(mDevice->FmtChans)};
}
@@ -728,7 +669,7 @@ void DSoundCapture::open(const ALCchar *name)
InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
}
- ALuint samples{mDevice->BufferSize};
+ uint samples{mDevice->BufferSize};
samples = maxu(samples, 100 * mDevice->Frequency / 1000);
DSCBUFFERDESC DSCBDescription{};
@@ -738,41 +679,34 @@ void DSoundCapture::open(const ALCchar *name)
DSCBDescription.lpwfxFormat = &InputType.Format;
//DirectSoundCapture Init code
- hr = DirectSoundCaptureCreate(guid, &mDSC, nullptr);
+ hr = DirectSoundCaptureCreate(guid, mDSC.getPtr(), nullptr);
if(SUCCEEDED(hr))
- mDSC->CreateCaptureBuffer(&DSCBDescription, &mDSCbuffer, nullptr);
+ mDSC->CreateCaptureBuffer(&DSCBDescription, mDSCbuffer.getPtr(), nullptr);
if(SUCCEEDED(hr))
- mRing = CreateRingBuffer(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
+ mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
if(FAILED(hr))
{
mRing = nullptr;
- if(mDSCbuffer)
- mDSCbuffer->Release();
mDSCbuffer = nullptr;
- if(mDSC)
- mDSC->Release();
mDSC = nullptr;
- throw al::backend_exception{ALC_INVALID_VALUE, "Device init failed: 0x%08lx", hr};
+ throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
+ hr};
}
mBufferBytes = DSCBDescription.dwBufferBytes;
- SetDefaultWFXChannelOrder(mDevice);
+ setDefaultWFXChannelOrder();
mDevice->DeviceName = name;
}
-bool DSoundCapture::start()
+void DSoundCapture::start()
{
- HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)};
+ const HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)};
if(FAILED(hr))
- {
- ERR("start failed: 0x%08lx\n", hr);
- aluHandleDisconnect(mDevice, "Failure starting capture: 0x%lx", hr);
- return false;
- }
- return true;
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failure starting capture: 0x%lx", hr};
}
void DSoundCapture::stop()
@@ -781,24 +715,21 @@ void DSoundCapture::stop()
if(FAILED(hr))
{
ERR("stop failed: 0x%08lx\n", hr);
- aluHandleDisconnect(mDevice, "Failure stopping capture: 0x%lx", hr);
+ mDevice->handleDisconnect("Failure stopping capture: 0x%lx", hr);
}
}
-ALCenum DSoundCapture::captureSamples(al::byte *buffer, ALCuint samples)
-{
- mRing->read(buffer, samples);
- return ALC_NO_ERROR;
-}
+void DSoundCapture::captureSamples(al::byte *buffer, uint samples)
+{ mRing->read(buffer, samples); }
-ALCuint DSoundCapture::availableSamples()
+uint DSoundCapture::availableSamples()
{
if(!mDevice->Connected.load(std::memory_order_acquire))
- return static_cast<ALCuint>(mRing->readSpace());
+ return static_cast<uint>(mRing->readSpace());
- ALuint FrameSize{mDevice->frameSizeFromFmt()};
- DWORD BufferBytes{mBufferBytes};
- DWORD LastCursor{mCursor};
+ const uint FrameSize{mDevice->frameSizeFromFmt()};
+ const DWORD BufferBytes{mBufferBytes};
+ const DWORD LastCursor{mCursor};
DWORD ReadCursor{};
void *ReadPtr1{}, *ReadPtr2{};
@@ -806,8 +737,8 @@ ALCuint DSoundCapture::availableSamples()
HRESULT hr{mDSCbuffer->GetCurrentPosition(nullptr, &ReadCursor)};
if(SUCCEEDED(hr))
{
- DWORD NumBytes{(ReadCursor-LastCursor + BufferBytes) % BufferBytes};
- if(!NumBytes) return static_cast<ALCubyte>(mRing->readSpace());
+ const DWORD NumBytes{(BufferBytes+ReadCursor-LastCursor) % BufferBytes};
+ if(!NumBytes) return static_cast<uint>(mRing->readSpace());
hr = mDSCbuffer->Lock(LastCursor, NumBytes, &ReadPtr1, &ReadCnt1, &ReadPtr2, &ReadCnt2, 0);
}
if(SUCCEEDED(hr))
@@ -816,16 +747,16 @@ ALCuint DSoundCapture::availableSamples()
if(ReadPtr2 != nullptr && ReadCnt2 > 0)
mRing->write(ReadPtr2, ReadCnt2/FrameSize);
hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2);
- mCursor = (LastCursor+ReadCnt1+ReadCnt2) % BufferBytes;
+ mCursor = ReadCursor;
}
if(FAILED(hr))
{
ERR("update failed: 0x%08lx\n", hr);
- aluHandleDisconnect(mDevice, "Failure retrieving capture data: 0x%lx", hr);
+ mDevice->handleDisconnect("Failure retrieving capture data: 0x%lx", hr);
}
- return static_cast<ALCuint>(mRing->readSpace());
+ return static_cast<uint>(mRing->readSpace());
}
} // namespace
@@ -871,14 +802,15 @@ bool DSoundBackendFactory::init()
bool DSoundBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
-void DSoundBackendFactory::probe(DevProbe type, std::string *outnames)
+std::string DSoundBackendFactory::probe(BackendType type)
{
- auto add_device = [outnames](const DevMap &entry) -> void
+ 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.append(entry.name.c_str(), entry.name.length()+1);
};
/* Initialize COM to prevent name truncation */
@@ -886,27 +818,29 @@ void DSoundBackendFactory::probe(DevProbe type, std::string *outnames)
HRESULT hrcom{CoInitialize(nullptr)};
switch(type)
{
- case DevProbe::Playback:
- PlaybackDevices.clear();
- hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
- if(FAILED(hr))
- ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr);
- std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
- break;
+ case BackendType::Playback:
+ PlaybackDevices.clear();
+ hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
+ if(FAILED(hr))
+ ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr);
+ std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
+ break;
- case DevProbe::Capture:
- CaptureDevices.clear();
- hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
- if(FAILED(hr))
- ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr);
- std::for_each(CaptureDevices.cbegin(), CaptureDevices.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);
+ std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
+ break;
}
if(SUCCEEDED(hrcom))
CoUninitialize();
+
+ return outnames;
}
-BackendPtr DSoundBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr DSoundBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new DSoundPlayback{device}};
diff --git a/alc/backends/dsound.h b/alc/backends/dsound.h
index 6bef0bfc..787f227a 100644
--- a/alc/backends/dsound.h
+++ b/alc/backends/dsound.h
@@ -1,7 +1,7 @@
#ifndef BACKENDS_DSOUND_H
#define BACKENDS_DSOUND_H
-#include "backends/base.h"
+#include "base.h"
struct DSoundBackendFactory final : public BackendFactory {
public:
@@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
- void probe(DevProbe type, std::string *outnames) override;
+ std::string probe(BackendType type) override;
- BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+ BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
diff --git a/alc/backends/jack.cpp b/alc/backends/jack.cpp
index c7bf8469..791002ca 100644
--- a/alc/backends/jack.cpp
+++ b/alc/backends/jack.cpp
@@ -20,19 +20,22 @@
#include "config.h"
-#include "backends/jack.h"
+#include "jack.h"
#include <cstdlib>
#include <cstdio>
+#include <cstring>
#include <memory.h>
+#include <array>
#include <thread>
#include <functional>
-#include "alcmain.h"
-#include "alu.h"
-#include "alconfig.h"
-#include "alexcpt.h"
+#include "alc/alconfig.h"
+#include "alnumeric.h"
+#include "core/device.h"
+#include "core/helpers.h"
+#include "core/logging.h"
#include "dynload.h"
#include "ringbuffer.h"
#include "threads.h"
@@ -43,9 +46,6 @@
namespace {
-constexpr ALCchar jackDevice[] = "JACK Default";
-
-
#ifdef HAVE_DYNLOAD
#define JACK_FUNCS(MAGIC) \
MAGIC(jack_client_open); \
@@ -70,7 +70,7 @@ constexpr ALCchar jackDevice[] = "JACK Default";
void *jack_handle;
#define MAKE_FUNC(f) decltype(f) * p##f
-JACK_FUNCS(MAKE_FUNC);
+JACK_FUNCS(MAKE_FUNC)
decltype(jack_error_callback) * pjack_error_callback;
#undef MAKE_FUNC
@@ -99,11 +99,13 @@ decltype(jack_error_callback) * pjack_error_callback;
#endif
+constexpr char JackDefaultAudioType[] = JACK_DEFAULT_AUDIO_TYPE;
+
jack_options_t ClientOptions = JackNullOption;
-ALCboolean jack_load()
+bool jack_load()
{
- ALCboolean error = ALC_FALSE;
+ bool error{false};
#ifdef HAVE_DYNLOAD
if(!jack_handle)
@@ -119,14 +121,14 @@ ALCboolean jack_load()
if(!jack_handle)
{
WARN("Failed to load %s\n", JACKLIB);
- return ALC_FALSE;
+ return false;
}
- error = ALC_FALSE;
+ error = false;
#define LOAD_FUNC(f) do { \
p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)); \
if(p##f == nullptr) { \
- error = ALC_TRUE; \
+ error = true; \
missing_funcs += "\n" #f; \
} \
} while(0)
@@ -150,13 +152,142 @@ ALCboolean jack_load()
}
+struct JackDeleter {
+ void operator()(void *ptr) { jack_free(ptr); }
+};
+using JackPortsPtr = std::unique_ptr<const char*[],JackDeleter>;
+
+struct DeviceEntry {
+ std::string mName;
+ std::string mPattern;
+
+ template<typename T, typename U>
+ DeviceEntry(T&& name, U&& pattern)
+ : mName{std::forward<T>(name)}, mPattern{std::forward<U>(pattern)}
+ { }
+};
+
+al::vector<DeviceEntry> PlaybackList;
+
+
+void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
+{
+ std::remove_reference_t<decltype(list)>{}.swap(list);
+
+ if(JackPortsPtr ports{jack_get_ports(client, nullptr, JackDefaultAudioType, JackPortIsInput)})
+ {
+ for(size_t i{0};ports[i];++i)
+ {
+ const char *sep{std::strchr(ports[i], ':')};
+ if(!sep || ports[i] == sep) continue;
+
+ const al::span<const char> portdev{ports[i], sep};
+ auto check_name = [portdev](const DeviceEntry &entry) -> bool
+ {
+ const size_t len{portdev.size()};
+ return entry.mName.length() == len
+ && entry.mName.compare(0, len, portdev.data(), len) == 0;
+ };
+ if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend())
+ continue;
+
+ std::string name{portdev.data(), portdev.size()};
+ list.emplace_back(name, name+":");
+ const auto &entry = list.back();
+ TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
+ }
+ /* There are ports but couldn't get device names from them. Add a
+ * generic entry.
+ */
+ if(ports[0] && list.empty())
+ {
+ WARN("No device names found in available ports, adding a generic name.\n");
+ list.emplace_back("JACK", "");
+ }
+ }
+
+ if(auto listopt = ConfigValueStr(nullptr, "jack", "custom-devices"))
+ {
+ for(size_t strpos{0};strpos < listopt->size();)
+ {
+ size_t nextpos{listopt->find(';', strpos)};
+ 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());
+ if(nextpos != std::string::npos) ++nextpos;
+ strpos = nextpos;
+ continue;
+ }
+
+ const al::span<const char> name{listopt->data()+strpos, seppos-strpos};
+ const al::span<const char> pattern{listopt->data()+(seppos+1),
+ std::min(nextpos, listopt->size())-(seppos+1)};
+
+ /* Check if this custom pattern already exists in the list. */
+ auto check_pattern = [pattern](const DeviceEntry &entry) -> bool
+ {
+ const size_t len{pattern.size()};
+ return entry.mPattern.length() == len
+ && entry.mPattern.compare(0, len, pattern.data(), len) == 0;
+ };
+ auto itemmatch = std::find_if(list.begin(), list.end(), check_pattern);
+ if(itemmatch != list.end())
+ {
+ /* If so, replace the name with this custom one. */
+ itemmatch->mName.assign(name.data(), name.size());
+ TRACE("Customized device name: %s = %s\n", itemmatch->mName.c_str(),
+ itemmatch->mPattern.c_str());
+ }
+ else
+ {
+ /* Otherwise, add a new device entry. */
+ list.emplace_back(std::string{name.data(), name.size()},
+ std::string{pattern.data(), pattern.size()});
+ const auto &entry = list.back();
+ TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
+ }
+
+ if(nextpos != std::string::npos) ++nextpos;
+ strpos = nextpos;
+ }
+ }
+
+ if(list.size() > 1)
+ {
+ /* Rename entries that have matching names, by appending '#2', '#3',
+ * etc, as needed.
+ */
+ for(auto curitem = list.begin()+1;curitem != list.end();++curitem)
+ {
+ auto check_match = [curitem](const DeviceEntry &entry) -> bool
+ { return entry.mName == curitem->mName; };
+ if(std::find_if(list.begin(), curitem, check_match) != curitem)
+ {
+ std::string name{curitem->mName};
+ size_t count{1};
+ auto check_name = [&name](const DeviceEntry &entry) -> bool
+ { return entry.mName == name; };
+ do {
+ name = curitem->mName;
+ name += " #";
+ name += std::to_string(++count);
+ } while(std::find_if(list.begin(), curitem, check_name) != curitem);
+ curitem->mName = std::move(name);
+ }
+ }
+ }
+}
+
+
struct JackPlayback final : public BackendBase {
- JackPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+ JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~JackPlayback() override;
- int bufferSizeNotify(jack_nframes_t numframes) noexcept;
- static int bufferSizeNotifyC(jack_nframes_t numframes, void *arg) noexcept
- { return static_cast<JackPlayback*>(arg)->bufferSizeNotify(numframes); }
+ int processRt(jack_nframes_t numframes) noexcept;
+ static int processRtC(jack_nframes_t numframes, void *arg) noexcept
+ { return static_cast<JackPlayback*>(arg)->processRt(numframes); }
int process(jack_nframes_t numframes) noexcept;
static int processC(jack_nframes_t numframes, void *arg) noexcept
@@ -164,15 +295,21 @@ struct JackPlayback final : public BackendBase {
int mixerProc();
- void open(const ALCchar *name) override;
+ void open(const char *name) override;
bool reset() override;
- bool start() override;
+ void start() override;
void stop() override;
ClockLatency getClockLatency() override;
+ std::string mPortPattern;
+
jack_client_t *mClient{nullptr};
- jack_port_t *mPort[MAX_OUTPUT_CHANNELS]{};
+ std::array<jack_port_t*,MAX_OUTPUT_CHANNELS> mPort{};
+
+ std::mutex mMutex;
+ std::atomic<bool> mPlaying{false};
+ bool mRTMixing{false};
RingBufferPtr mRing;
al::semaphore mSem;
@@ -187,31 +324,35 @@ JackPlayback::~JackPlayback()
if(!mClient)
return;
- std::for_each(std::begin(mPort), std::end(mPort),
- [this](jack_port_t *port) -> void
- { if(port) jack_port_unregister(mClient, port); }
- );
- std::fill(std::begin(mPort), std::end(mPort), nullptr);
+ auto unregister_port = [this](jack_port_t *port) -> void
+ { if(port) jack_port_unregister(mClient, port); };
+ std::for_each(mPort.begin(), mPort.end(), unregister_port);
+ mPort.fill(nullptr);
+
jack_client_close(mClient);
mClient = nullptr;
}
-int JackPlayback::bufferSizeNotify(jack_nframes_t numframes) noexcept
+int JackPlayback::processRt(jack_nframes_t numframes) noexcept
{
- std::lock_guard<std::mutex> _{mDevice->StateLock};
- mDevice->UpdateSize = numframes;
- mDevice->BufferSize = numframes*2;
-
- const char *devname{mDevice->DeviceName.c_str()};
- ALuint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
- bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
- mDevice->BufferSize = bufsize + mDevice->UpdateSize;
-
- TRACE("%u / %u buffer\n", mDevice->UpdateSize, mDevice->BufferSize);
+ std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
+ size_t numchans{0};
+ for(auto port : mPort)
+ {
+ if(!port || numchans == mDevice->RealOut.Buffer.size())
+ break;
+ out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
+ }
- mRing = nullptr;
- mRing = CreateRingBuffer(bufsize, mDevice->frameSizeFromFmt(), true);
+ if(mPlaying.load(std::memory_order_acquire)) LIKELY
+ mDevice->renderSamples({out.data(), numchans}, 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);
+ }
return 0;
}
@@ -219,69 +360,64 @@ int JackPlayback::bufferSizeNotify(jack_nframes_t numframes) noexcept
int JackPlayback::process(jack_nframes_t numframes) noexcept
{
- jack_default_audio_sample_t *out[MAX_OUTPUT_CHANNELS];
- ALsizei numchans{0};
+ std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
+ size_t numchans{0};
for(auto port : mPort)
{
if(!port) break;
out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
}
- auto data = mRing->getReadVector();
- jack_nframes_t todo{minu(numframes, static_cast<ALuint>(data.first.len))};
- std::transform(out, out+numchans, out,
- [&data,numchans,todo](ALfloat *outbuf) -> ALfloat*
+ jack_nframes_t total{0};
+ if(mPlaying.load(std::memory_order_acquire)) LIKELY
+ {
+ auto data = mRing->getReadVector();
+ jack_nframes_t todo{minu(numframes, static_cast<uint>(data.first.len))};
+ auto write_first = [&data,numchans,todo](float *outbuf) -> float*
{
- const ALfloat *RESTRICT in = reinterpret_cast<ALfloat*>(data.first.buf);
- std::generate_n(outbuf, todo,
- [&in,numchans]() noexcept -> ALfloat
+ const float *RESTRICT in = reinterpret_cast<float*>(data.first.buf);
+ auto deinterlace_input = [&in,numchans]() noexcept -> float
+ {
+ float ret{*in};
+ in += numchans;
+ return ret;
+ };
+ std::generate_n(outbuf, todo, deinterlace_input);
+ data.first.buf += sizeof(float);
+ return outbuf + todo;
+ };
+ std::transform(out.begin(), out.begin()+numchans, out.begin(), write_first);
+ total += todo;
+
+ todo = minu(numframes-total, static_cast<uint>(data.second.len));
+ if(todo > 0)
+ {
+ auto write_second = [&data,numchans,todo](float *outbuf) -> float*
+ {
+ const float *RESTRICT in = reinterpret_cast<float*>(data.second.buf);
+ auto deinterlace_input = [&in,numchans]() noexcept -> float
{
- ALfloat ret{*in};
+ float ret{*in};
in += numchans;
return ret;
- }
- );
- data.first.buf += sizeof(ALfloat);
- return outbuf + todo;
+ };
+ std::generate_n(outbuf, todo, deinterlace_input);
+ data.second.buf += sizeof(float);
+ return outbuf + todo;
+ };
+ std::transform(out.begin(), out.begin()+numchans, out.begin(), write_second);
+ total += todo;
}
- );
- jack_nframes_t total{todo};
- todo = minu(numframes-total, static_cast<ALuint>(data.second.len));
- if(todo > 0)
- {
- std::transform(out, out+numchans, out,
- [&data,numchans,todo](ALfloat *outbuf) -> ALfloat*
- {
- const ALfloat *RESTRICT in = reinterpret_cast<ALfloat*>(data.second.buf);
- std::generate_n(outbuf, todo,
- [&in,numchans]() noexcept -> ALfloat
- {
- ALfloat ret{*in};
- in += numchans;
- return ret;
- }
- );
- data.second.buf += sizeof(ALfloat);
- return outbuf + todo;
- }
- );
- total += todo;
+ mRing->readAdvance(total);
+ mSem.post();
}
- mRing->readAdvance(total);
- mSem.post();
-
if(numframes > total)
{
- todo = numframes-total;
- std::transform(out, out+numchans, out,
- [todo](ALfloat *outbuf) -> ALfloat*
- {
- std::fill_n(outbuf, todo, 0.0f);
- return outbuf + todo;
- }
- );
+ const jack_nframes_t todo{numframes - total};
+ auto clear_buf = [todo](float *outbuf) -> void { std::fill_n(outbuf, todo, 0.0f); };
+ std::for_each(out.begin(), out.begin()+numchans, clear_buf);
}
return 0;
@@ -292,28 +428,28 @@ int JackPlayback::mixerProc()
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
- std::unique_lock<JackPlayback> dlock{*this};
- while(!mKillNow.load(std::memory_order_acquire) &&
- mDevice->Connected.load(std::memory_order_acquire))
+ const size_t frame_step{mDevice->channelsFromFmt()};
+
+ while(!mKillNow.load(std::memory_order_acquire)
+ && mDevice->Connected.load(std::memory_order_acquire))
{
if(mRing->writeSpace() < mDevice->UpdateSize)
{
- dlock.unlock();
mSem.wait();
- dlock.lock();
continue;
}
auto data = mRing->getWriteVector();
- auto todo = static_cast<ALuint>(data.first.len + data.second.len);
+ size_t todo{data.first.len + data.second.len};
todo -= todo%mDevice->UpdateSize;
- ALuint len1{minu(static_cast<ALuint>(data.first.len), todo)};
- ALuint len2{minu(static_cast<ALuint>(data.second.len), todo-len1)};
+ const auto len1 = static_cast<uint>(minz(data.first.len, todo));
+ const auto len2 = static_cast<uint>(minz(data.second.len, todo-len1));
- aluMixData(mDevice, data.first.buf, len1);
+ std::lock_guard<std::mutex> _{mMutex};
+ mDevice->renderSamples(data.first.buf, len1, frame_step);
if(len2 > 0)
- aluMixData(mDevice, data.second.buf, len2);
+ mDevice->renderSamples(data.second.buf, len2, frame_step);
mRing->writeAdvance(todo);
}
@@ -321,77 +457,105 @@ int JackPlayback::mixerProc()
}
-void JackPlayback::open(const ALCchar *name)
+void JackPlayback::open(const char *name)
{
- if(!name)
- name = jackDevice;
- else if(strcmp(name, jackDevice) != 0)
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ if(!mClient)
+ {
+ const PathNamePair &binname = GetProcBinary();
+ const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
+
+ jack_status_t status;
+ 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};
+ if((status&JackServerStarted))
+ TRACE("JACK server started\n");
+ if((status&JackNameNotUnique))
+ {
+ client_name = jack_get_client_name(mClient);
+ TRACE("Client name not unique, got '%s' instead\n", client_name);
+ }
+ }
- const char *client_name{"alsoft"};
- jack_status_t status;
- mClient = jack_client_open(client_name, ClientOptions, &status, nullptr);
- if(mClient == nullptr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Failed to open client connection: 0x%02x",
- status};
-
- if((status&JackServerStarted))
- TRACE("JACK server started\n");
- if((status&JackNameNotUnique))
+ if(PlaybackList.empty())
+ EnumerateDevices(mClient, PlaybackList);
+
+ if(!name && !PlaybackList.empty())
{
- client_name = jack_get_client_name(mClient);
- TRACE("Client name not unique, got `%s' instead\n", client_name);
+ name = PlaybackList[0].mName.c_str();
+ mPortPattern = PlaybackList[0].mPattern;
+ }
+ else
+ {
+ auto check_name = [name](const DeviceEntry &entry) -> bool
+ { return entry.mName == name; };
+ auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name);
+ if(iter == PlaybackList.cend())
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Device name \"%s\" not found", name?name:""};
+ mPortPattern = iter->mPattern;
}
- jack_set_process_callback(mClient, &JackPlayback::processC, this);
- jack_set_buffer_size_callback(mClient, &JackPlayback::bufferSizeNotifyC, this);
+ mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", true);
+ jack_set_process_callback(mClient,
+ mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this);
mDevice->DeviceName = name;
}
bool JackPlayback::reset()
{
- std::for_each(std::begin(mPort), std::end(mPort),
- [this](jack_port_t *port) -> void
- { if(port) jack_port_unregister(mClient, port); }
- );
- std::fill(std::begin(mPort), std::end(mPort), nullptr);
+ auto unregister_port = [this](jack_port_t *port) -> void
+ { if(port) jack_port_unregister(mClient, port); };
+ std::for_each(mPort.begin(), mPort.end(), unregister_port);
+ mPort.fill(nullptr);
/* 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->BufferSize = mDevice->UpdateSize * 2;
-
- const char *devname{mDevice->DeviceName.c_str()};
- ALuint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
- bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
- mDevice->BufferSize = bufsize + mDevice->UpdateSize;
+ if(mRTMixing)
+ {
+ /* Assume only two periods when directly mixing. Should try to query
+ * the total port latency when connected.
+ */
+ mDevice->BufferSize = mDevice->UpdateSize * 2;
+ }
+ else
+ {
+ const char *devname{mDevice->DeviceName.c_str()};
+ uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
+ bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
+ mDevice->BufferSize = bufsize + mDevice->UpdateSize;
+ }
/* Force 32-bit float output. */
mDevice->FmtType = DevFmtFloat;
- auto ports_end = std::begin(mPort) + mDevice->channelsFromFmt();
- auto bad_port = std::find_if_not(std::begin(mPort), ports_end,
- [this](jack_port_t *&port) -> bool
- {
- std::string name{"channel_" + std::to_string(&port - mPort + 1)};
- port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE,
- JackPortIsOutput, 0);
- return port != nullptr;
- }
- );
+ int port_num{0};
+ auto ports_end = mPort.begin() + mDevice->channelsFromFmt();
+ auto bad_port = mPort.begin();
+ while(bad_port != ports_end)
+ {
+ std::string name{"channel_" + std::to_string(++port_num)};
+ *bad_port = jack_port_register(mClient, name.c_str(), JackDefaultAudioType,
+ JackPortIsOutput | JackPortIsTerminal, 0);
+ if(!*bad_port) break;
+ ++bad_port;
+ }
if(bad_port != ports_end)
{
- ERR("Not enough JACK ports available for %s output\n", DevFmtChannelsString(mDevice->FmtChans));
- if(bad_port == std::begin(mPort)) return false;
+ ERR("Failed to register enough JACK ports for %s output\n",
+ DevFmtChannelsString(mDevice->FmtChans));
+ if(bad_port == mPort.begin()) return false;
- if(bad_port == std::begin(mPort)+1)
+ if(bad_port == mPort.begin()+1)
mDevice->FmtChans = DevFmtMono;
else
{
- ports_end = mPort+2;
+ ports_end = mPort.begin()+2;
while(bad_port != ports_end)
{
jack_port_unregister(mClient, *(--bad_port));
@@ -401,70 +565,87 @@ bool JackPlayback::reset()
}
}
- mRing = nullptr;
- mRing = CreateRingBuffer(bufsize, mDevice->frameSizeFromFmt(), true);
-
- SetDefaultChannelOrder(mDevice);
+ setDefaultChannelOrder();
return true;
}
-bool JackPlayback::start()
+void JackPlayback::start()
{
if(jack_activate(mClient))
- {
- ERR("Failed to activate client\n");
- return false;
- }
+ throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"};
- const char **ports{jack_get_ports(mClient, nullptr, nullptr,
- JackPortIsPhysical|JackPortIsInput)};
- if(ports == nullptr)
+ const char *devname{mDevice->DeviceName.c_str()};
+ if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true))
{
- ERR("No physical playback ports found\n");
- jack_deactivate(mClient);
- return false;
- }
- std::mismatch(std::begin(mPort), std::end(mPort), ports,
- [this](const jack_port_t *port, const char *pname) -> bool
+ JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JackDefaultAudioType,
+ JackPortIsInput)};
+ if(!pnames)
{
- if(!port) return false;
- if(!pname)
+ jack_deactivate(mClient);
+ throw al::backend_exception{al::backend_error::DeviceError, "No playback ports found"};
+ }
+
+ for(size_t i{0};i < al::size(mPort) && mPort[i];++i)
+ {
+ if(!pnames[i])
{
- ERR("No physical playback port for \"%s\"\n", jack_port_name(port));
- return false;
+ ERR("No physical playback port for \"%s\"\n", jack_port_name(mPort[i]));
+ break;
}
- if(jack_connect(mClient, jack_port_name(port), pname))
- ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(port),
- pname);
- return true;
+ 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]),
+ pnames[i]);
}
- );
- jack_free(ports);
-
- try {
- mKillNow.store(false, std::memory_order_release);
- mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this};
- return true;
}
- catch(std::exception& e) {
- ERR("Could not create playback thread: %s\n", e.what());
- }
- catch(...) {
+
+ /* Reconfigure buffer metrics in case the server changed it since the reset
+ * (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;
+
+ mRing = nullptr;
+ if(mRTMixing)
+ mPlaying.store(true, std::memory_order_release);
+ else
+ {
+ uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
+ bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
+ mDevice->BufferSize = bufsize + mDevice->UpdateSize;
+
+ 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};
+ }
+ 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()};
+ }
}
- jack_deactivate(mClient);
- return false;
}
void JackPlayback::stop()
{
- if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
- return;
-
- mSem.post();
- mThread.join();
+ if(mPlaying.load(std::memory_order_acquire))
+ {
+ mKillNow.store(true, std::memory_order_release);
+ if(mThread.joinable())
+ {
+ mSem.post();
+ mThread.join();
+ }
- jack_deactivate(mClient);
+ jack_deactivate(mClient);
+ mPlaying.store(false, std::memory_order_release);
+ }
}
@@ -472,9 +653,9 @@ ClockLatency JackPlayback::getClockLatency()
{
ClockLatency ret;
- std::lock_guard<JackPlayback> _{*this};
+ std::lock_guard<std::mutex> _{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice);
- ret.Latency = std::chrono::seconds{mRing->readSpace()};
+ ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->UpdateSize};
ret.Latency /= mDevice->Frequency;
return ret;
@@ -493,13 +674,16 @@ bool JackBackendFactory::init()
if(!jack_load())
return false;
- if(!GetConfigValueBool(nullptr, "jack", "spawn-server", 0))
+ if(!GetConfigValueBool(nullptr, "jack", "spawn-server", false))
ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer);
+ const PathNamePair &binname = GetProcBinary();
+ const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
+
void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr};
jack_set_error_function(jack_msg_handler);
jack_status_t status;
- jack_client_t *client{jack_client_open("alsoft", ClientOptions, &status, nullptr)};
+ jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)};
jack_set_error_function(old_error_cb);
if(!client)
{
@@ -516,21 +700,37 @@ bool JackBackendFactory::init()
bool JackBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback); }
-void JackBackendFactory::probe(DevProbe type, std::string *outnames)
+std::string JackBackendFactory::probe(BackendType type)
{
- switch(type)
+ std::string outnames;
+ auto append_name = [&outnames](const DeviceEntry &entry) -> void
{
- case DevProbe::Playback:
- /* Includes null char. */
- outnames->append(jackDevice, sizeof(jackDevice));
- break;
+ /* Includes null char. */
+ outnames.append(entry.mName.c_str(), entry.mName.length()+1);
+ };
- case DevProbe::Capture:
- break;
+ const PathNamePair &binname = GetProcBinary();
+ const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
+ jack_status_t status;
+ switch(type)
+ {
+ case BackendType::Playback:
+ if(jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)})
+ {
+ EnumerateDevices(client, PlaybackList);
+ jack_client_close(client);
+ }
+ else
+ WARN("jack_client_open() failed, 0x%02x\n", status);
+ std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
+ break;
+ case BackendType::Capture:
+ break;
}
+ return outnames;
}
-BackendPtr JackBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr JackBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new JackPlayback{device}};
diff --git a/alc/backends/jack.h b/alc/backends/jack.h
index 10beebfb..b83f24dd 100644
--- a/alc/backends/jack.h
+++ b/alc/backends/jack.h
@@ -1,7 +1,7 @@
#ifndef BACKENDS_JACK_H
#define BACKENDS_JACK_H
-#include "backends/base.h"
+#include "base.h"
struct JackBackendFactory final : public BackendFactory {
public:
@@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
- void probe(DevProbe type, std::string *outnames) override;
+ std::string probe(BackendType type) override;
- BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+ BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
diff --git a/alc/backends/loopback.cpp b/alc/backends/loopback.cpp
index 511061f3..bf4ab246 100644
--- a/alc/backends/loopback.cpp
+++ b/alc/backends/loopback.cpp
@@ -20,39 +20,38 @@
#include "config.h"
-#include "backends/loopback.h"
+#include "loopback.h"
-#include "alcmain.h"
-#include "alu.h"
+#include "core/device.h"
namespace {
struct LoopbackBackend final : public BackendBase {
- LoopbackBackend(ALCdevice *device) noexcept : BackendBase{device} { }
+ LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { }
- void open(const ALCchar *name) override;
+ void open(const char *name) override;
bool reset() override;
- bool start() override;
+ void start() override;
void stop() override;
DEF_NEWDEL(LoopbackBackend)
};
-void LoopbackBackend::open(const ALCchar *name)
+void LoopbackBackend::open(const char *name)
{
mDevice->DeviceName = name;
}
bool LoopbackBackend::reset()
{
- SetDefaultWFXChannelOrder(mDevice);
+ setDefaultWFXChannelOrder();
return true;
}
-bool LoopbackBackend::start()
-{ return true; }
+void LoopbackBackend::start()
+{ }
void LoopbackBackend::stop()
{ }
@@ -66,10 +65,10 @@ bool LoopbackBackendFactory::init()
bool LoopbackBackendFactory::querySupport(BackendType)
{ return true; }
-void LoopbackBackendFactory::probe(DevProbe, std::string*)
-{ }
+std::string LoopbackBackendFactory::probe(BackendType)
+{ return std::string{}; }
-BackendPtr LoopbackBackendFactory::createBackend(ALCdevice *device, BackendType)
+BackendPtr LoopbackBackendFactory::createBackend(DeviceBase *device, BackendType)
{ return BackendPtr{new LoopbackBackend{device}}; }
BackendFactory &LoopbackBackendFactory::getFactory()
diff --git a/alc/backends/loopback.h b/alc/backends/loopback.h
index 09c085b8..cb42b3c8 100644
--- a/alc/backends/loopback.h
+++ b/alc/backends/loopback.h
@@ -1,7 +1,7 @@
#ifndef BACKENDS_LOOPBACK_H
#define BACKENDS_LOOPBACK_H
-#include "backends/base.h"
+#include "base.h"
struct LoopbackBackendFactory final : public BackendFactory {
public:
@@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
- void probe(DevProbe type, std::string *outnames) override;
+ std::string probe(BackendType type) override;
- BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+ BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
diff --git a/alc/backends/null.cpp b/alc/backends/null.cpp
index bc2a0c9c..5a8fc255 100644
--- a/alc/backends/null.cpp
+++ b/alc/backends/null.cpp
@@ -20,7 +20,7 @@
#include "config.h"
-#include "backends/null.h"
+#include "null.h"
#include <exception>
#include <atomic>
@@ -30,11 +30,9 @@
#include <functional>
#include <thread>
-#include "alcmain.h"
-#include "alexcpt.h"
+#include "core/device.h"
#include "almalloc.h"
-#include "alu.h"
-#include "logging.h"
+#include "core/helpers.h"
#include "threads.h"
@@ -44,17 +42,17 @@ using std::chrono::seconds;
using std::chrono::milliseconds;
using std::chrono::nanoseconds;
-constexpr ALCchar nullDevice[] = "No Output";
+constexpr char nullDevice[] = "No Output";
struct NullBackend final : public BackendBase {
- NullBackend(ALCdevice *device) noexcept : BackendBase{device} { }
+ NullBackend(DeviceBase *device) noexcept : BackendBase{device} { }
int mixerProc();
- void open(const ALCchar *name) override;
+ void open(const char *name) override;
bool reset() override;
- bool start() override;
+ void start() override;
void stop() override;
std::atomic<bool> mKillNow{true};
@@ -72,8 +70,8 @@ int NullBackend::mixerProc()
int64_t done{0};
auto start = std::chrono::steady_clock::now();
- while(!mKillNow.load(std::memory_order_acquire) &&
- mDevice->Connected.load(std::memory_order_acquire))
+ while(!mKillNow.load(std::memory_order_acquire)
+ && mDevice->Connected.load(std::memory_order_acquire))
{
auto now = std::chrono::steady_clock::now();
@@ -86,8 +84,7 @@ int NullBackend::mixerProc()
}
while(avail-done >= mDevice->UpdateSize)
{
- std::lock_guard<NullBackend> _{*this};
- aluMixData(mDevice, nullptr, mDevice->UpdateSize);
+ mDevice->renderSamples(nullptr, mDevice->UpdateSize, 0u);
done += mDevice->UpdateSize;
}
@@ -108,35 +105,33 @@ int NullBackend::mixerProc()
}
-void NullBackend::open(const ALCchar *name)
+void NullBackend::open(const char *name)
{
if(!name)
name = nullDevice;
else if(strcmp(name, nullDevice) != 0)
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+ name};
mDevice->DeviceName = name;
}
bool NullBackend::reset()
{
- SetDefaultWFXChannelOrder(mDevice);
+ setDefaultWFXChannelOrder();
return true;
}
-bool NullBackend::start()
+void NullBackend::start()
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&NullBackend::mixerProc), this};
- return true;
}
catch(std::exception& e) {
- ERR("Failed to start mixing thread: %s\n", e.what());
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to start mixing thread: %s", e.what()};
}
- catch(...) {
- }
- return false;
}
void NullBackend::stop()
@@ -155,20 +150,22 @@ bool NullBackendFactory::init()
bool NullBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback); }
-void NullBackendFactory::probe(DevProbe type, std::string *outnames)
+std::string NullBackendFactory::probe(BackendType type)
{
+ std::string outnames;
switch(type)
{
- case DevProbe::Playback:
- /* Includes null char. */
- outnames->append(nullDevice, sizeof(nullDevice));
- break;
- case DevProbe::Capture:
- break;
+ case BackendType::Playback:
+ /* Includes null char. */
+ outnames.append(nullDevice, sizeof(nullDevice));
+ break;
+ case BackendType::Capture:
+ break;
}
+ return outnames;
}
-BackendPtr NullBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr NullBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new NullBackend{device}};
diff --git a/alc/backends/null.h b/alc/backends/null.h
index f19d5b4d..7048cad6 100644
--- a/alc/backends/null.h
+++ b/alc/backends/null.h
@@ -1,7 +1,7 @@
#ifndef BACKENDS_NULL_H
#define BACKENDS_NULL_H
-#include "backends/base.h"
+#include "base.h"
struct NullBackendFactory final : public BackendFactory {
public:
@@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
- void probe(DevProbe type, std::string *outnames) override;
+ std::string probe(BackendType type) override;
- BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+ BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
diff --git a/alc/backends/oboe.cpp b/alc/backends/oboe.cpp
new file mode 100644
index 00000000..461f5a6a
--- /dev/null
+++ b/alc/backends/oboe.cpp
@@ -0,0 +1,360 @@
+
+#include "config.h"
+
+#include "oboe.h"
+
+#include <cassert>
+#include <cstring>
+#include <stdint.h>
+
+#include "alnumeric.h"
+#include "core/device.h"
+#include "core/logging.h"
+#include "ringbuffer.h"
+
+#include "oboe/Oboe.h"
+
+
+namespace {
+
+constexpr char device_name[] = "Oboe Default";
+
+
+struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback {
+ OboePlayback(DeviceBase *device) : BackendBase{device} { }
+
+ oboe::ManagedStream mStream;
+
+ oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
+ int32_t numFrames) override;
+
+ void open(const char *name) override;
+ bool reset() override;
+ void start() override;
+ void stop() override;
+};
+
+
+oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
+ int32_t numFrames)
+{
+ assert(numFrames > 0);
+ const int32_t numChannels{oboeStream->getChannelCount()};
+
+ mDevice->renderSamples(audioData, static_cast<uint32_t>(numFrames),
+ static_cast<uint32_t>(numChannels));
+ return oboe::DataCallbackResult::Continue;
+}
+
+
+void OboePlayback::open(const char *name)
+{
+ if(!name)
+ name = device_name;
+ else if(std::strcmp(name, device_name) != 0)
+ throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+ name};
+
+ /* Open a basic output stream, just to ensure it can work. */
+ oboe::ManagedStream stream;
+ oboe::Result result{oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output)
+ ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
+ ->openManagedStream(stream)};
+ if(result != oboe::Result::OK)
+ throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
+ oboe::convertToText(result)};
+
+ mDevice->DeviceName = name;
+}
+
+bool OboePlayback::reset()
+{
+ oboe::AudioStreamBuilder builder;
+ builder.setDirection(oboe::Direction::Output);
+ builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
+ /* Don't let Oboe convert. We should be able to handle anything it gives
+ * back.
+ */
+ builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::None);
+ builder.setChannelConversionAllowed(false);
+ builder.setFormatConversionAllowed(false);
+ builder.setCallback(this);
+
+ if(mDevice->Flags.test(FrequencyRequest))
+ {
+ builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High);
+ builder.setSampleRate(static_cast<int32_t>(mDevice->Frequency));
+ }
+ if(mDevice->Flags.test(ChannelsRequest))
+ {
+ /* Only use mono or stereo at user request. There's no telling what
+ * other counts may be inferred as.
+ */
+ builder.setChannelCount((mDevice->FmtChans==DevFmtMono) ? oboe::ChannelCount::Mono
+ : (mDevice->FmtChans==DevFmtStereo) ? oboe::ChannelCount::Stereo
+ : oboe::ChannelCount::Unspecified);
+ }
+ if(mDevice->Flags.test(SampleTypeRequest))
+ {
+ oboe::AudioFormat format{oboe::AudioFormat::Unspecified};
+ switch(mDevice->FmtType)
+ {
+ case DevFmtByte:
+ case DevFmtUByte:
+ case DevFmtShort:
+ case DevFmtUShort:
+ format = oboe::AudioFormat::I16;
+ break;
+ case DevFmtInt:
+ case DevFmtUInt:
+#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
+ format = oboe::AudioFormat::I32;
+ break;
+#endif
+ case DevFmtFloat:
+ format = oboe::AudioFormat::Float;
+ break;
+ }
+ builder.setFormat(format);
+ }
+
+ oboe::Result result{builder.openManagedStream(mStream)};
+ /* If the format failed, try asking for the defaults. */
+ while(result == oboe::Result::ErrorInvalidFormat)
+ {
+ if(builder.getFormat() != oboe::AudioFormat::Unspecified)
+ builder.setFormat(oboe::AudioFormat::Unspecified);
+ else if(builder.getSampleRate() != oboe::kUnspecified)
+ builder.setSampleRate(oboe::kUnspecified);
+ else if(builder.getChannelCount() != oboe::ChannelCount::Unspecified)
+ builder.setChannelCount(oboe::ChannelCount::Unspecified);
+ else
+ break;
+ result = builder.openManagedStream(mStream);
+ }
+ if(result != oboe::Result::OK)
+ throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
+ oboe::convertToText(result)};
+ mStream->setBufferSizeInFrames(mini(static_cast<int32_t>(mDevice->BufferSize),
+ mStream->getBufferCapacityInFrames()));
+ TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
+
+ if(static_cast<uint>(mStream->getChannelCount()) != mDevice->channelsFromFmt())
+ {
+ if(mStream->getChannelCount() >= 2)
+ mDevice->FmtChans = DevFmtStereo;
+ else if(mStream->getChannelCount() == 1)
+ mDevice->FmtChans = DevFmtMono;
+ else
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Got unhandled channel count: %d", mStream->getChannelCount()};
+ }
+ setDefaultWFXChannelOrder();
+
+ switch(mStream->getFormat())
+ {
+ case oboe::AudioFormat::I16:
+ mDevice->FmtType = DevFmtShort;
+ break;
+ case oboe::AudioFormat::Float:
+ mDevice->FmtType = DevFmtFloat;
+ break;
+#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
+ case oboe::AudioFormat::I32:
+ mDevice->FmtType = DevFmtInt;
+ break;
+ case oboe::AudioFormat::I24:
+#endif
+ 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())};
+ }
+ mDevice->Frequency = 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 = maxu(mDevice->Frequency / 100,
+ static_cast<uint32_t>(mStream->getFramesPerBurst()));
+ mDevice->BufferSize = maxu(mDevice->UpdateSize * 2,
+ static_cast<uint32_t>(mStream->getBufferSizeInFrames()));
+
+ return true;
+}
+
+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",
+ oboe::convertToText(result)};
+}
+
+void OboePlayback::stop()
+{
+ oboe::Result result{mStream->stop()};
+ if(result != oboe::Result::OK)
+ throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
+ oboe::convertToText(result)};
+}
+
+
+struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback {
+ OboeCapture(DeviceBase *device) : BackendBase{device} { }
+
+ oboe::ManagedStream mStream;
+
+ RingBufferPtr mRing{nullptr};
+
+ oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
+ int32_t numFrames) override;
+
+ void open(const char *name) override;
+ void start() override;
+ void stop() override;
+ void captureSamples(al::byte *buffer, uint samples) override;
+ uint availableSamples() override;
+};
+
+oboe::DataCallbackResult OboeCapture::onAudioReady(oboe::AudioStream*, void *audioData,
+ int32_t numFrames)
+{
+ mRing->write(audioData, static_cast<uint32_t>(numFrames));
+ return oboe::DataCallbackResult::Continue;
+}
+
+
+void OboeCapture::open(const char *name)
+{
+ if(!name)
+ name = device_name;
+ else if(std::strcmp(name, device_name) != 0)
+ throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+ name};
+
+ oboe::AudioStreamBuilder builder;
+ builder.setDirection(oboe::Direction::Input)
+ ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
+ ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High)
+ ->setChannelConversionAllowed(true)
+ ->setFormatConversionAllowed(true)
+ ->setSampleRate(static_cast<int32_t>(mDevice->Frequency))
+ ->setCallback(this);
+ /* Only use mono or stereo at user request. There's no telling what
+ * other counts may be inferred as.
+ */
+ switch(mDevice->FmtChans)
+ {
+ case DevFmtMono:
+ builder.setChannelCount(oboe::ChannelCount::Mono);
+ break;
+ case DevFmtStereo:
+ builder.setChannelCount(oboe::ChannelCount::Stereo);
+ break;
+ case DevFmtQuad:
+ case DevFmtX51:
+ case DevFmtX61:
+ case DevFmtX71:
+ case DevFmtX714:
+ case DevFmtX3D71:
+ case DevFmtAmbi3D:
+ throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
+ DevFmtChannelsString(mDevice->FmtChans)};
+ }
+
+ /* FIXME: This really should support UByte, but Oboe doesn't. We'll need to
+ * convert.
+ */
+ switch(mDevice->FmtType)
+ {
+ case DevFmtShort:
+ builder.setFormat(oboe::AudioFormat::I16);
+ break;
+ case DevFmtFloat:
+ builder.setFormat(oboe::AudioFormat::Float);
+ break;
+ case DevFmtInt:
+#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
+ builder.setFormat(oboe::AudioFormat::I32);
+ break;
+#endif
+ case DevFmtByte:
+ case DevFmtUByte:
+ case DevFmtUShort:
+ case DevFmtUInt:
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "%s 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",
+ oboe::convertToText(result)};
+
+ TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
+
+ /* Ensure a minimum ringbuffer size of 100ms. */
+ mRing = RingBuffer::Create(maxu(mDevice->BufferSize, mDevice->Frequency/10),
+ static_cast<uint32_t>(mStream->getBytesPerFrame()), false);
+
+ mDevice->DeviceName = 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",
+ oboe::convertToText(result)};
+}
+
+void OboeCapture::stop()
+{
+ const oboe::Result result{mStream->stop()};
+ if(result != oboe::Result::OK)
+ throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
+ oboe::convertToText(result)};
+}
+
+uint OboeCapture::availableSamples()
+{ return static_cast<uint>(mRing->readSpace()); }
+
+void OboeCapture::captureSamples(al::byte *buffer, uint samples)
+{ mRing->read(buffer, samples); }
+
+} // namespace
+
+bool OboeBackendFactory::init() { return true; }
+
+bool OboeBackendFactory::querySupport(BackendType type)
+{ return type == BackendType::Playback || type == BackendType::Capture; }
+
+std::string OboeBackendFactory::probe(BackendType type)
+{
+ switch(type)
+ {
+ case BackendType::Playback:
+ case BackendType::Capture:
+ /* Includes null char. */
+ return std::string{device_name, sizeof(device_name)};
+ }
+ return std::string{};
+}
+
+BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type)
+{
+ if(type == BackendType::Playback)
+ return BackendPtr{new OboePlayback{device}};
+ if(type == BackendType::Capture)
+ return BackendPtr{new OboeCapture{device}};
+ return BackendPtr{};
+}
+
+BackendFactory &OboeBackendFactory::getFactory()
+{
+ static OboeBackendFactory factory{};
+ return factory;
+}
diff --git a/alc/backends/oboe.h b/alc/backends/oboe.h
new file mode 100644
index 00000000..a39c2445
--- /dev/null
+++ b/alc/backends/oboe.h
@@ -0,0 +1,19 @@
+#ifndef BACKENDS_OBOE_H
+#define BACKENDS_OBOE_H
+
+#include "base.h"
+
+struct OboeBackendFactory 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_OBOE_H */
diff --git a/alc/backends/opensl.cpp b/alc/backends/opensl.cpp
index a1fdccc7..f5b98fb8 100644
--- a/alc/backends/opensl.cpp
+++ b/alc/backends/opensl.cpp
@@ -21,7 +21,7 @@
#include "config.h"
-#include "backends/opensl.h"
+#include "opensl.h"
#include <stdlib.h>
#include <jni.h>
@@ -32,11 +32,12 @@
#include <thread>
#include <functional>
-#include "alcmain.h"
-#include "alexcpt.h"
-#include "alu.h"
-#include "compat.h"
-#include "endiantest.h"
+#include "albit.h"
+#include "alnumeric.h"
+#include "core/device.h"
+#include "core/helpers.h"
+#include "core/logging.h"
+#include "opthelpers.h"
#include "ringbuffer.h"
#include "threads.h"
@@ -53,10 +54,10 @@ namespace {
#define VCALL0(obj, func) ((*(obj))->func((obj) EXTRACT_VCALL_ARGS
-constexpr ALCchar opensl_device[] = "OpenSL";
+constexpr char opensl_device[] = "OpenSL";
-SLuint32 GetChannelMask(DevFmtChannels chans)
+constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept
{
switch(chans)
{
@@ -67,15 +68,18 @@ SLuint32 GetChannelMask(DevFmtChannels chans)
case DevFmtX51: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_SIDE_LEFT |
SL_SPEAKER_SIDE_RIGHT;
- case DevFmtX51Rear: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
- SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT |
- SL_SPEAKER_BACK_RIGHT;
case DevFmtX61: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_CENTER |
SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT;
- case DevFmtX71: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
+ case DevFmtX71:
+ case DevFmtX3D71: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT |
SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT;
+ case DevFmtX714: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
+ SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT |
+ 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 DevFmtAmbi3D:
break;
}
@@ -83,7 +87,7 @@ SLuint32 GetChannelMask(DevFmtChannels chans)
}
#ifdef SL_ANDROID_DATAFORMAT_PCM_EX
-SLuint32 GetTypeRepresentation(DevFmtType type)
+constexpr SLuint32 GetTypeRepresentation(DevFmtType type) noexcept
{
switch(type)
{
@@ -102,7 +106,14 @@ SLuint32 GetTypeRepresentation(DevFmtType type)
}
#endif
-const char *res_str(SLresult result)
+constexpr SLuint32 GetByteOrderEndianness() noexcept
+{
+ if(al::endian::native == al::endian::little)
+ return SL_BYTEORDER_LITTLEENDIAN;
+ return SL_BYTEORDER_BIGENDIAN;
+}
+
+constexpr const char *res_str(SLresult result) noexcept
{
switch(result)
{
@@ -136,14 +147,15 @@ const char *res_str(SLresult result)
return "Unknown error code";
}
-#define PRINTERR(x, s) do { \
- if UNLIKELY((x) != SL_RESULT_SUCCESS) \
- ERR("%s: %s\n", (s), res_str((x))); \
-} while(0)
+inline void PrintErr(SLresult res, const char *str)
+{
+ if(res != SL_RESULT_SUCCESS) UNLIKELY
+ ERR("%s: %s\n", str, res_str(res));
+}
struct OpenSLPlayback final : public BackendBase {
- OpenSLPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+ OpenSLPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~OpenSLPlayback() override;
void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
@@ -152,9 +164,9 @@ struct OpenSLPlayback final : public BackendBase {
int mixerProc();
- void open(const ALCchar *name) override;
+ void open(const char *name) override;
bool reset() override;
- bool start() override;
+ void start() override;
void stop() override;
ClockLatency getClockLatency() override;
@@ -171,7 +183,9 @@ struct OpenSLPlayback final : public BackendBase {
RingBufferPtr mRing{nullptr};
al::semaphore mSem;
- ALuint mFrameSize{0};
+ std::mutex mMutex;
+
+ uint mFrameSize{0};
std::atomic<bool> mKillNow{true};
std::thread mThread;
@@ -221,55 +235,56 @@ int OpenSLPlayback::mixerProc()
SLAndroidSimpleBufferQueueItf bufferQueue;
SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&bufferQueue)};
- PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
+ PrintErr(result, "bufferQueue->GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player);
- PRINTERR(result, "bufferQueue->GetInterface SL_IID_PLAY");
+ PrintErr(result, "bufferQueue->GetInterface SL_IID_PLAY");
}
- std::unique_lock<OpenSLPlayback> dlock{*this};
+ const size_t frame_step{mDevice->channelsFromFmt()};
+
if(SL_RESULT_SUCCESS != result)
- aluHandleDisconnect(mDevice, "Failed to get playback buffer: 0x%08x", result);
+ mDevice->handleDisconnect("Failed to get playback buffer: 0x%08x", result);
- while(SL_RESULT_SUCCESS == result && !mKillNow.load(std::memory_order_acquire) &&
- mDevice->Connected.load(std::memory_order_acquire))
+ while(SL_RESULT_SUCCESS == result && !mKillNow.load(std::memory_order_acquire)
+ && mDevice->Connected.load(std::memory_order_acquire))
{
if(mRing->writeSpace() == 0)
{
SLuint32 state{0};
result = VCALL(player,GetPlayState)(&state);
- PRINTERR(result, "player->GetPlayState");
+ PrintErr(result, "player->GetPlayState");
if(SL_RESULT_SUCCESS == result && state != SL_PLAYSTATE_PLAYING)
{
result = VCALL(player,SetPlayState)(SL_PLAYSTATE_PLAYING);
- PRINTERR(result, "player->SetPlayState");
+ PrintErr(result, "player->SetPlayState");
}
if(SL_RESULT_SUCCESS != result)
{
- aluHandleDisconnect(mDevice, "Failed to start platback: 0x%08x", result);
+ mDevice->handleDisconnect("Failed to start playback: 0x%08x", result);
break;
}
if(mRing->writeSpace() == 0)
{
- dlock.unlock();
mSem.wait();
- dlock.lock();
continue;
}
}
+ std::unique_lock<std::mutex> dlock{mMutex};
auto data = mRing->getWriteVector();
- aluMixData(mDevice, data.first.buf,
- static_cast<ALuint>(data.first.len*mDevice->UpdateSize));
+ mDevice->renderSamples(data.first.buf,
+ static_cast<uint>(data.first.len)*mDevice->UpdateSize, frame_step);
if(data.second.len > 0)
- aluMixData(mDevice, data.second.buf,
- static_cast<ALuint>(data.second.len*mDevice->UpdateSize));
+ mDevice->renderSamples(data.second.buf,
+ static_cast<uint>(data.second.len)*mDevice->UpdateSize, frame_step);
size_t todo{data.first.len + data.second.len};
mRing->writeAdvance(todo);
+ dlock.unlock();
for(size_t i{0};i < todo;i++)
{
@@ -281,10 +296,10 @@ int OpenSLPlayback::mixerProc()
}
result = VCALL(bufferQueue,Enqueue)(data.first.buf, mDevice->UpdateSize*mFrameSize);
- PRINTERR(result, "bufferQueue->Enqueue");
+ PrintErr(result, "bufferQueue->Enqueue");
if(SL_RESULT_SUCCESS != result)
{
- aluHandleDisconnect(mDevice, "Failed to queue audio: 0x%08x", result);
+ mDevice->handleDisconnect("Failed to queue audio: 0x%08x", result);
break;
}
@@ -297,35 +312,39 @@ int OpenSLPlayback::mixerProc()
}
-void OpenSLPlayback::open(const ALCchar *name)
+void OpenSLPlayback::open(const char *name)
{
if(!name)
name = opensl_device;
else if(strcmp(name, opensl_device) != 0)
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+ name};
+
+ /* There's only one device, so if it's already open, there's nothing to do. */
+ if(mEngineObj) return;
// create engine
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
- PRINTERR(result, "slCreateEngine");
+ PrintErr(result, "slCreateEngine");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE);
- PRINTERR(result, "engine->Realize");
+ PrintErr(result, "engine->Realize");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine);
- PRINTERR(result, "engine->GetInterface");
+ PrintErr(result, "engine->GetInterface");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mEngine,CreateOutputMix)(&mOutputMix, 0, nullptr, nullptr);
- PRINTERR(result, "engine->CreateOutputMix");
+ PrintErr(result, "engine->CreateOutputMix");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mOutputMix,Realize)(SL_BOOLEAN_FALSE);
- PRINTERR(result, "outputMix->Realize");
+ PrintErr(result, "outputMix->Realize");
}
if(SL_RESULT_SUCCESS != result)
@@ -339,7 +358,7 @@ void OpenSLPlayback::open(const ALCchar *name)
mEngineObj = nullptr;
mEngine = nullptr;
- throw al::backend_exception{ALC_INVALID_VALUE,
+ throw al::backend_exception{al::backend_error::DeviceError,
"Failed to initialize OpenSL device: 0x%08x", result};
}
@@ -427,7 +446,7 @@ bool OpenSLPlayback::reset()
mDevice->FmtChans = DevFmtStereo;
mDevice->FmtType = DevFmtShort;
- SetDefaultWFXChannelOrder(mDevice);
+ setDefaultWFXChannelOrder();
mFrameSize = mDevice->frameSizeFromFmt();
@@ -455,7 +474,7 @@ bool OpenSLPlayback::reset()
format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8;
format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample;
format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans);
- format_pcm_ex.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN;
+ format_pcm_ex.endianness = GetByteOrderEndianness();
format_pcm_ex.representation = GetTypeRepresentation(mDevice->FmtType);
audioSrc.pLocator = &loc_bufq;
@@ -486,28 +505,27 @@ bool OpenSLPlayback::reset()
format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
format_pcm.containerSize = format_pcm.bitsPerSample;
format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
- format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN :
- SL_BYTEORDER_BIGENDIAN;
+ format_pcm.endianness = GetByteOrderEndianness();
audioSrc.pLocator = &loc_bufq;
audioSrc.pFormat = &format_pcm;
result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(),
ids.data(), reqs.data());
- PRINTERR(result, "engine->CreateAudioPlayer");
+ PrintErr(result, "engine->CreateAudioPlayer");
}
if(SL_RESULT_SUCCESS == result)
{
/* Set the stream type to "media" (games, music, etc), if possible. */
SLAndroidConfigurationItf config;
result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config);
- PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDCONFIGURATION");
+ PrintErr(result, "bufferQueue->GetInterface SL_IID_ANDROIDCONFIGURATION");
if(SL_RESULT_SUCCESS == result)
{
SLint32 streamType = SL_ANDROID_STREAM_MEDIA;
result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_STREAM_TYPE, &streamType,
sizeof(streamType));
- PRINTERR(result, "config->SetConfiguration");
+ PrintErr(result, "config->SetConfiguration");
}
/* Clear any error since this was optional. */
@@ -516,12 +534,12 @@ bool OpenSLPlayback::reset()
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mBufferQueueObj,Realize)(SL_BOOLEAN_FALSE);
- PRINTERR(result, "bufferQueue->Realize");
+ PrintErr(result, "bufferQueue->Realize");
}
if(SL_RESULT_SUCCESS == result)
{
- const ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
- mRing = CreateRingBuffer(num_updates, mFrameSize*mDevice->UpdateSize, true);
+ const uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
+ mRing = RingBuffer::Create(num_updates, mFrameSize*mDevice->UpdateSize, true);
}
if(SL_RESULT_SUCCESS != result)
@@ -536,32 +554,31 @@ bool OpenSLPlayback::reset()
return true;
}
-bool OpenSLPlayback::start()
+void OpenSLPlayback::start()
{
mRing->reset();
SLAndroidSimpleBufferQueueItf bufferQueue;
SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&bufferQueue)};
- PRINTERR(result, "bufferQueue->GetInterface");
+ PrintErr(result, "bufferQueue->GetInterface");
+ if(SL_RESULT_SUCCESS == result)
+ {
+ result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this);
+ PrintErr(result, "bufferQueue->RegisterCallback");
+ }
if(SL_RESULT_SUCCESS != result)
- return false;
-
- result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this);
- PRINTERR(result, "bufferQueue->RegisterCallback");
- if(SL_RESULT_SUCCESS != result) return false;
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to register callback: 0x%08x", result};
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread(std::mem_fn(&OpenSLPlayback::mixerProc), this);
- return true;
}
catch(std::exception& e) {
- ERR("Could not create playback thread: %s\n", e.what());
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to start mixing thread: %s", e.what()};
}
- catch(...) {
- }
- return false;
}
void OpenSLPlayback::stop()
@@ -574,25 +591,25 @@ void OpenSLPlayback::stop()
SLPlayItf player;
SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player)};
- PRINTERR(result, "bufferQueue->GetInterface");
+ PrintErr(result, "bufferQueue->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(player,SetPlayState)(SL_PLAYSTATE_STOPPED);
- PRINTERR(result, "player->SetPlayState");
+ PrintErr(result, "player->SetPlayState");
}
SLAndroidSimpleBufferQueueItf bufferQueue;
result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue);
- PRINTERR(result, "bufferQueue->GetInterface");
+ PrintErr(result, "bufferQueue->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL0(bufferQueue,Clear)();
- PRINTERR(result, "bufferQueue->Clear");
+ PrintErr(result, "bufferQueue->Clear");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(bufferQueue,RegisterCallback)(nullptr, nullptr);
- PRINTERR(result, "bufferQueue->RegisterCallback");
+ PrintErr(result, "bufferQueue->RegisterCallback");
}
if(SL_RESULT_SUCCESS == result)
{
@@ -601,7 +618,9 @@ void OpenSLPlayback::stop()
std::this_thread::yield();
result = VCALL(bufferQueue,GetState)(&state);
} while(SL_RESULT_SUCCESS == result && state.count > 0);
- PRINTERR(result, "bufferQueue->GetState");
+ PrintErr(result, "bufferQueue->GetState");
+
+ mRing->reset();
}
}
@@ -609,7 +628,7 @@ ClockLatency OpenSLPlayback::getClockLatency()
{
ClockLatency ret;
- std::lock_guard<OpenSLPlayback> _{*this};
+ std::lock_guard<std::mutex> _{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice);
ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize};
ret.Latency /= mDevice->Frequency;
@@ -619,18 +638,18 @@ ClockLatency OpenSLPlayback::getClockLatency()
struct OpenSLCapture final : public BackendBase {
- OpenSLCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+ OpenSLCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~OpenSLCapture() override;
void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
{ static_cast<OpenSLCapture*>(context)->process(bq); }
- void open(const ALCchar *name) override;
- bool start() override;
+ void open(const char *name) override;
+ void start() override;
void stop() override;
- ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
- ALCuint availableSamples() override;
+ void captureSamples(al::byte *buffer, uint samples) override;
+ uint availableSamples() override;
/* engine interfaces */
SLObjectItf mEngineObj{nullptr};
@@ -640,9 +659,9 @@ struct OpenSLCapture final : public BackendBase {
SLObjectItf mRecordObj{nullptr};
RingBufferPtr mRing{nullptr};
- ALCuint mSplOffset{0u};
+ uint mSplOffset{0u};
- ALuint mFrameSize{0};
+ uint mFrameSize{0};
DEF_NEWDEL(OpenSLCapture)
};
@@ -667,39 +686,40 @@ void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf) noexcept
}
-void OpenSLCapture::open(const ALCchar* name)
+void OpenSLCapture::open(const char* name)
{
if(!name)
name = opensl_device;
else if(strcmp(name, opensl_device) != 0)
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+ name};
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
- PRINTERR(result, "slCreateEngine");
+ PrintErr(result, "slCreateEngine");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE);
- PRINTERR(result, "engine->Realize");
+ PrintErr(result, "engine->Realize");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine);
- PRINTERR(result, "engine->GetInterface");
+ PrintErr(result, "engine->GetInterface");
}
if(SL_RESULT_SUCCESS == result)
{
mFrameSize = mDevice->frameSizeFromFmt();
/* Ensure the total length is at least 100ms */
- ALuint length{maxu(mDevice->BufferSize, mDevice->Frequency/10)};
+ uint length{maxu(mDevice->BufferSize, mDevice->Frequency/10)};
/* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */
- ALuint update_len{clampu(mDevice->BufferSize/3, mDevice->Frequency/100,
+ uint update_len{clampu(mDevice->BufferSize/3, mDevice->Frequency/100,
mDevice->Frequency/100*5)};
- ALuint num_updates{(length+update_len-1) / update_len};
+ uint num_updates{(length+update_len-1) / update_len};
- mRing = CreateRingBuffer(num_updates, update_len*mFrameSize, false);
+ mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false);
mDevice->UpdateSize = update_len;
- mDevice->BufferSize = static_cast<ALuint>(mRing->writeSpace() * update_len);
+ mDevice->BufferSize = static_cast<uint>(mRing->writeSpace() * update_len);
}
if(SL_RESULT_SUCCESS == result)
{
@@ -729,8 +749,7 @@ void OpenSLCapture::open(const ALCchar* name)
format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8;
format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample;
format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans);
- format_pcm_ex.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN :
- SL_BYTEORDER_BIGENDIAN;
+ format_pcm_ex.endianness = GetByteOrderEndianness();
format_pcm_ex.representation = GetTypeRepresentation(mDevice->FmtType);
audioSnk.pLocator = &loc_bq;
@@ -753,15 +772,14 @@ void OpenSLCapture::open(const ALCchar* name)
format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
format_pcm.containerSize = format_pcm.bitsPerSample;
format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
- format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN :
- SL_BYTEORDER_BIGENDIAN;
+ format_pcm.endianness = GetByteOrderEndianness();
audioSnk.pLocator = &loc_bq;
audioSnk.pFormat = &format_pcm;
result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk,
ids.size(), ids.data(), reqs.data());
}
- PRINTERR(result, "engine->CreateAudioRecorder");
+ PrintErr(result, "engine->CreateAudioRecorder");
}
}
if(SL_RESULT_SUCCESS == result)
@@ -769,13 +787,13 @@ void OpenSLCapture::open(const ALCchar* name)
/* Set the record preset to "generic", if possible. */
SLAndroidConfigurationItf config;
result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config);
- PRINTERR(result, "recordObj->GetInterface SL_IID_ANDROIDCONFIGURATION");
+ PrintErr(result, "recordObj->GetInterface SL_IID_ANDROIDCONFIGURATION");
if(SL_RESULT_SUCCESS == result)
{
SLuint32 preset = SL_ANDROID_RECORDING_PRESET_GENERIC;
result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_RECORDING_PRESET, &preset,
sizeof(preset));
- PRINTERR(result, "config->SetConfiguration");
+ PrintErr(result, "config->SetConfiguration");
}
/* Clear any error since this was optional. */
@@ -784,34 +802,37 @@ void OpenSLCapture::open(const ALCchar* name)
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mRecordObj,Realize)(SL_BOOLEAN_FALSE);
- PRINTERR(result, "recordObj->Realize");
+ PrintErr(result, "recordObj->Realize");
}
SLAndroidSimpleBufferQueueItf bufferQueue;
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue);
- PRINTERR(result, "recordObj->GetInterface");
+ PrintErr(result, "recordObj->GetInterface");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(bufferQueue,RegisterCallback)(&OpenSLCapture::processC, this);
- PRINTERR(result, "bufferQueue->RegisterCallback");
+ PrintErr(result, "bufferQueue->RegisterCallback");
}
if(SL_RESULT_SUCCESS == result)
{
- const ALuint chunk_size{mDevice->UpdateSize * mFrameSize};
+ const uint chunk_size{mDevice->UpdateSize * mFrameSize};
+ const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::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++)
{
result = VCALL(bufferQueue,Enqueue)(data.first.buf + chunk_size*i, chunk_size);
- PRINTERR(result, "bufferQueue->Enqueue");
+ PrintErr(result, "bufferQueue->Enqueue");
}
for(size_t i{0u};i < data.second.len && SL_RESULT_SUCCESS == result;i++)
{
result = VCALL(bufferQueue,Enqueue)(data.second.buf + chunk_size*i, chunk_size);
- PRINTERR(result, "bufferQueue->Enqueue");
+ PrintErr(result, "bufferQueue->Enqueue");
}
}
@@ -826,108 +847,126 @@ void OpenSLCapture::open(const ALCchar* name)
mEngineObj = nullptr;
mEngine = nullptr;
- throw al::backend_exception{ALC_INVALID_VALUE,
+ throw al::backend_exception{al::backend_error::DeviceError,
"Failed to initialize OpenSL device: 0x%08x", result};
}
mDevice->DeviceName = name;
}
-bool OpenSLCapture::start()
+void OpenSLCapture::start()
{
SLRecordItf record;
SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)};
- PRINTERR(result, "recordObj->GetInterface");
+ PrintErr(result, "recordObj->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(record,SetRecordState)(SL_RECORDSTATE_RECORDING);
- PRINTERR(result, "record->SetRecordState");
+ PrintErr(result, "record->SetRecordState");
}
-
if(SL_RESULT_SUCCESS != result)
- {
- aluHandleDisconnect(mDevice, "Failed to start capture: 0x%08x", result);
- return false;
- }
-
- return true;
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to start capture: 0x%08x", result};
}
void OpenSLCapture::stop()
{
SLRecordItf record;
SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)};
- PRINTERR(result, "recordObj->GetInterface");
+ PrintErr(result, "recordObj->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(record,SetRecordState)(SL_RECORDSTATE_PAUSED);
- PRINTERR(result, "record->SetRecordState");
+ PrintErr(result, "record->SetRecordState");
}
}
-ALCenum OpenSLCapture::captureSamples(al::byte *buffer, ALCuint samples)
+void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
{
- SLAndroidSimpleBufferQueueItf bufferQueue{};
- if LIKELY(mDevice->Connected.load(std::memory_order_acquire))
- {
- const SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
- &bufferQueue)};
- PRINTERR(result, "recordObj->GetInterface");
- if UNLIKELY(SL_RESULT_SUCCESS != result)
- {
- aluHandleDisconnect(mDevice, "Failed to get capture buffer queue: 0x%08x", result);
- bufferQueue = nullptr;
- }
- }
-
- const ALuint update_size{mDevice->UpdateSize};
- const ALuint chunk_size{update_size * mFrameSize};
+ const uint update_size{mDevice->UpdateSize};
+ const uint chunk_size{update_size * mFrameSize};
/* Read the desired samples from the ring buffer then advance its read
* pointer.
*/
- auto data = mRing->getReadVector();
- for(ALCuint i{0};i < samples;)
+ size_t adv_count{0};
+ auto rdata = mRing->getReadVector();
+ for(uint i{0};i < samples;)
{
- const ALCuint rem{minu(samples - i, update_size - mSplOffset)};
- std::copy_n(data.first.buf + mSplOffset*mFrameSize, rem*mFrameSize, buffer + i*mFrameSize);
+ const uint rem{minu(samples - i, update_size - mSplOffset)};
+ std::copy_n(rdata.first.buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize},
+ buffer + i*size_t{mFrameSize});
mSplOffset += rem;
if(mSplOffset == update_size)
{
/* Finished a chunk, reset the offset and advance the read pointer. */
mSplOffset = 0;
- mRing->readAdvance(1);
-
- if LIKELY(bufferQueue)
- {
- const SLresult result{VCALL(bufferQueue,Enqueue)(data.first.buf, chunk_size)};
- PRINTERR(result, "bufferQueue->Enqueue");
- if UNLIKELY(SL_RESULT_SUCCESS != result)
- {
- aluHandleDisconnect(mDevice, "Failed to update capture buffer: 0x%08x",
- result);
- bufferQueue = nullptr;
- }
- }
- data.first.len--;
- if(!data.first.len)
- data.first = data.second;
+ ++adv_count;
+ rdata.first.len -= 1;
+ if(!rdata.first.len)
+ rdata.first = rdata.second;
else
- data.first.buf += chunk_size;
+ rdata.first.buf += chunk_size;
}
i += rem;
}
- return ALC_NO_ERROR;
+ SLAndroidSimpleBufferQueueItf bufferQueue{};
+ if(mDevice->Connected.load(std::memory_order_acquire)) LIKELY
+ {
+ const SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ &bufferQueue)};
+ PrintErr(result, "recordObj->GetInterface");
+ if(SL_RESULT_SUCCESS != result) UNLIKELY
+ {
+ mDevice->handleDisconnect("Failed to get capture buffer queue: 0x%08x", result);
+ bufferQueue = nullptr;
+ }
+ }
+ if(!bufferQueue || adv_count == 0)
+ return;
+
+ /* For each buffer chunk that was fully read, queue another writable buffer
+ * chunk to keep the OpenSL queue full. This is rather convulated, as a
+ * result of the ring buffer holding more elements than are writable at a
+ * given time. The end of the write vector increments when the read pointer
+ * advances, which will "expose" a previously unwritable element. So for
+ * every element that we've finished reading, we queue that many elements
+ * from the end of the write vector.
+ */
+ mRing->readAdvance(adv_count);
+
+ SLresult result{SL_RESULT_SUCCESS};
+ auto wdata = mRing->getWriteVector();
+ if(adv_count > wdata.second.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);
+ 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)
+ {
+ auto len2 = std::min(wdata.second.len, adv_count);
+ auto buf2 = wdata.second.buf + chunk_size*(wdata.second.len-len2);
+ for(size_t i{0u};i < len2 && SL_RESULT_SUCCESS == result;i++)
+ {
+ result = VCALL(bufferQueue,Enqueue)(buf2 + chunk_size*i, chunk_size);
+ PrintErr(result, "bufferQueue->Enqueue");
+ }
+ }
}
-ALCuint OpenSLCapture::availableSamples()
-{ return static_cast<ALuint>(mRing->readSpace()*mDevice->UpdateSize - mSplOffset); }
+uint OpenSLCapture::availableSamples()
+{ return static_cast<uint>(mRing->readSpace()*mDevice->UpdateSize - mSplOffset); }
} // namespace
@@ -936,19 +975,21 @@ bool OSLBackendFactory::init() { return true; }
bool OSLBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
-void OSLBackendFactory::probe(DevProbe type, std::string *outnames)
+std::string OSLBackendFactory::probe(BackendType type)
{
+ std::string outnames;
switch(type)
{
- case DevProbe::Playback:
- case DevProbe::Capture:
- /* Includes null char. */
- outnames->append(opensl_device, sizeof(opensl_device));
- break;
+ case BackendType::Playback:
+ case BackendType::Capture:
+ /* Includes null char. */
+ outnames.append(opensl_device, sizeof(opensl_device));
+ break;
}
+ return outnames;
}
-BackendPtr OSLBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr OSLBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new OpenSLPlayback{device}};
diff --git a/alc/backends/opensl.h b/alc/backends/opensl.h
index 809aa339..b8162447 100644
--- a/alc/backends/opensl.h
+++ b/alc/backends/opensl.h
@@ -1,7 +1,7 @@
#ifndef BACKENDS_OSL_H
#define BACKENDS_OSL_H
-#include "backends/base.h"
+#include "base.h"
struct OSLBackendFactory final : public BackendFactory {
public:
@@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
- void probe(DevProbe type, std::string *outnames) override;
+ std::string probe(BackendType type) override;
- BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+ BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
diff --git a/alc/backends/oss.cpp b/alc/backends/oss.cpp
index 59cc44e4..6d4fa261 100644
--- a/alc/backends/oss.cpp
+++ b/alc/backends/oss.cpp
@@ -20,7 +20,7 @@
#include "config.h"
-#include "backends/oss.h"
+#include "oss.h"
#include <fcntl.h>
#include <poll.h>
@@ -41,16 +41,14 @@
#include <thread>
#include <utility>
-#include "AL/al.h"
-
-#include "alcmain.h"
-#include "alconfig.h"
-#include "alexcpt.h"
+#include "albyte.h"
+#include "alc/alconfig.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "aloptional.h"
-#include "alu.h"
-#include "logging.h"
+#include "core/device.h"
+#include "core/helpers.h"
+#include "core/logging.h"
#include "ringbuffer.h"
#include "threads.h"
#include "vector.h"
@@ -94,14 +92,6 @@ struct DevMap {
std::string device_name;
};
-bool checkName(const al::vector<DevMap> &list, const std::string &name)
-{
- return std::find_if(list.cbegin(), list.cend(),
- [&name](const DevMap &entry) -> bool
- { return entry.name == name; }
- ) != list.cend();
-}
-
al::vector<DevMap> PlaybackDevices;
al::vector<DevMap> CaptureDevices;
@@ -110,60 +100,59 @@ al::vector<DevMap> CaptureDevices;
#define DSP_CAP_OUTPUT 0x00020000
#define DSP_CAP_INPUT 0x00010000
-void ALCossListPopulate(al::vector<DevMap> *devlist, int type)
+void ALCossListPopulate(al::vector<DevMap> &devlist, int type)
{
- devlist->emplace_back(DevMap{DefaultName, (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback});
+ devlist.emplace_back(DevMap{DefaultName, (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback});
}
#else
-void ALCossListAppend(al::vector<DevMap> *list, const char *handle, size_t hlen, const char *path, size_t plen)
+void ALCossListAppend(al::vector<DevMap> &list, al::span<const char> handle, al::span<const char> path)
{
#ifdef ALC_OSS_DEVNODE_TRUC
- for(size_t i{0};i < plen;i++)
+ for(size_t i{0};i < path.size();++i)
{
- if(path[i] == '.')
+ if(path[i] == '.' && handle.size() + i >= path.size())
{
- if(strncmp(path + i, handle + hlen + i - plen, plen - i) == 0)
- hlen = hlen + i - plen;
- plen = i;
+ const size_t hoffset{handle.size() + i - path.size()};
+ if(strncmp(path.data() + i, handle.data() + hoffset, path.size() - i) == 0)
+ handle = handle.first(hoffset);
+ path = path.first(i);
}
}
#endif
- if(handle[0] == '\0')
- {
+ if(handle.empty())
handle = path;
- hlen = plen;
- }
- std::string basename{handle, hlen};
- basename.erase(std::find(basename.begin(), basename.end(), '\0'), basename.end());
- std::string devname{path, plen};
- devname.erase(std::find(devname.begin(), devname.end(), '\0'), devname.end());
+ std::string basename{handle.data(), handle.size()};
+ std::string devname{path.data(), path.size()};
- auto iter = std::find_if(list->cbegin(), list->cend(),
- [&devname](const DevMap &entry) -> bool
- { return entry.device_name == devname; }
- );
- if(iter != list->cend())
+ auto match_devname = [&devname](const DevMap &entry) -> bool
+ { return entry.device_name == devname; };
+ if(std::find_if(list.cbegin(), list.cend(), match_devname) != list.cend())
return;
+ auto checkName = [&list](const std::string &name) -> bool
+ {
+ auto match_name = [&name](const DevMap &entry) -> bool { return entry.name == name; };
+ return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
+ };
int count{1};
std::string newname{basename};
- while(checkName(PlaybackDevices, newname))
+ while(checkName(newname))
{
newname = basename;
newname += " #";
newname += std::to_string(++count);
}
- list->emplace_back(DevMap{std::move(newname), std::move(devname)});
- const DevMap &entry = list->back();
+ list.emplace_back(DevMap{std::move(newname), std::move(devname)});
+ const DevMap &entry = list.back();
TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
}
-void ALCossListPopulate(al::vector<DevMap> *devlist, int type_flag)
+void ALCossListPopulate(al::vector<DevMap> &devlist, int type_flag)
{
int fd{open("/dev/mixer", O_RDONLY)};
if(fd < 0)
@@ -191,21 +180,14 @@ void ALCossListPopulate(al::vector<DevMap> *devlist, int type_flag)
if(!(ai.caps&type_flag) || ai.devnode[0] == '\0')
continue;
- const char *handle;
- size_t len;
+ al::span<const char> handle;
if(ai.handle[0] != '\0')
- {
- len = strnlen(ai.handle, sizeof(ai.handle));
- handle = ai.handle;
- }
+ handle = {ai.handle, strnlen(ai.handle, sizeof(ai.handle))};
else
- {
- len = strnlen(ai.name, sizeof(ai.name));
- handle = ai.name;
- }
+ handle = {ai.name, strnlen(ai.name, sizeof(ai.name))};
+ al::span<const char> devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))};
- ALCossListAppend(devlist, handle, len, ai.devnode,
- strnlen(ai.devnode, sizeof(ai.devnode)));
+ ALCossListAppend(devlist, handle, devnode);
}
done:
@@ -214,26 +196,26 @@ done:
fd = -1;
const char *defdev{((type_flag==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback).c_str()};
- auto iter = std::find_if(devlist->cbegin(), devlist->cend(),
+ auto iter = std::find_if(devlist.cbegin(), devlist.cend(),
[defdev](const DevMap &entry) -> bool
{ return entry.device_name == defdev; }
);
- if(iter == devlist->cend())
- devlist->insert(devlist->begin(), DevMap{DefaultName, defdev});
+ if(iter == devlist.cend())
+ devlist.insert(devlist.begin(), DevMap{DefaultName, defdev});
else
{
DevMap entry{std::move(*iter)};
- devlist->erase(iter);
- devlist->insert(devlist->begin(), std::move(entry));
+ devlist.erase(iter);
+ devlist.insert(devlist.begin(), std::move(entry));
}
- devlist->shrink_to_fit();
+ devlist.shrink_to_fit();
}
#endif
-ALCuint log2i(ALCuint x)
+uint log2i(uint x)
{
- ALCuint y{0};
+ uint y{0};
while(x > 1)
{
x >>= 1;
@@ -244,19 +226,19 @@ ALCuint log2i(ALCuint x)
struct OSSPlayback final : public BackendBase {
- OSSPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+ OSSPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~OSSPlayback() override;
int mixerProc();
- void open(const ALCchar *name) override;
+ void open(const char *name) override;
bool reset() override;
- bool start() override;
+ void start() override;
void stop() override;
int mFd{-1};
- al::vector<ALubyte> mMixData;
+ al::vector<al::byte> mMixData;
std::atomic<bool> mKillNow{true};
std::thread mThread;
@@ -267,7 +249,7 @@ struct OSSPlayback final : public BackendBase {
OSSPlayback::~OSSPlayback()
{
if(mFd != -1)
- close(mFd);
+ ::close(mFd);
mFd = -1;
}
@@ -277,25 +259,23 @@ int OSSPlayback::mixerProc()
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
- const ALuint frame_size{mDevice->frameSizeFromFmt()};
+ const size_t frame_step{mDevice->channelsFromFmt()};
+ const size_t frame_size{mDevice->frameSizeFromFmt()};
- std::unique_lock<OSSPlayback> dlock{*this};
- while(!mKillNow.load(std::memory_order_acquire) &&
- mDevice->Connected.load(std::memory_order_acquire))
+ while(!mKillNow.load(std::memory_order_acquire)
+ && mDevice->Connected.load(std::memory_order_acquire))
{
pollfd pollitem{};
pollitem.fd = mFd;
pollitem.events = POLLOUT;
- dlock.unlock();
int pret{poll(&pollitem, 1, 1000)};
- dlock.lock();
if(pret < 0)
{
if(errno == EINTR || errno == EAGAIN)
continue;
ERR("poll failed: %s\n", strerror(errno));
- aluHandleDisconnect(mDevice, "Failed waiting for playback buffer: %s", strerror(errno));
+ mDevice->handleDisconnect("Failed waiting for playback buffer: %s", strerror(errno));
break;
}
else if(pret == 0)
@@ -304,9 +284,9 @@ int OSSPlayback::mixerProc()
continue;
}
- ALubyte *write_ptr{mMixData.data()};
+ al::byte *write_ptr{mMixData.data()};
size_t to_write{mMixData.size()};
- aluMixData(mDevice, write_ptr, static_cast<ALuint>(to_write/frame_size));
+ mDevice->renderSamples(write_ptr, static_cast<uint>(to_write/frame_size), frame_step);
while(to_write > 0 && !mKillNow.load(std::memory_order_acquire))
{
ssize_t wrote{write(mFd, write_ptr, to_write)};
@@ -315,8 +295,7 @@ int OSSPlayback::mixerProc()
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
continue;
ERR("write failed: %s\n", strerror(errno));
- aluHandleDisconnect(mDevice, "Failed writing playback samples: %s",
- strerror(errno));
+ mDevice->handleDisconnect("Failed writing playback samples: %s", strerror(errno));
break;
}
@@ -329,7 +308,7 @@ int OSSPlayback::mixerProc()
}
-void OSSPlayback::open(const ALCchar *name)
+void OSSPlayback::open(const char *name)
{
const char *devname{DefaultPlayback.c_str()};
if(!name)
@@ -337,22 +316,27 @@ void OSSPlayback::open(const ALCchar *name)
else
{
if(PlaybackDevices.empty())
- ALCossListPopulate(&PlaybackDevices, DSP_CAP_OUTPUT);
+ ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT);
auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
[&name](const DevMap &entry) -> bool
{ return entry.name == name; }
);
if(iter == PlaybackDevices.cend())
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Device name \"%s\" not found", name};
devname = iter->device_name.c_str();
}
- mFd = ::open(devname, O_WRONLY);
- if(mFd == -1)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not open %s: %s", devname,
+ int fd{::open(devname, O_WRONLY)};
+ if(fd == -1)
+ throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
strerror(errno)};
+ if(mFd != -1)
+ ::close(mFd);
+ mFd = fd;
+
mDevice->DeviceName = name;
}
@@ -378,13 +362,13 @@ bool OSSPlayback::reset()
break;
}
- ALuint periods{mDevice->BufferSize / mDevice->UpdateSize};
- ALuint numChannels{mDevice->channelsFromFmt()};
- ALuint ossSpeed{mDevice->Frequency};
- ALuint frameSize{numChannels * mDevice->bytesFromFmt()};
+ uint periods{mDevice->BufferSize / mDevice->UpdateSize};
+ uint numChannels{mDevice->channelsFromFmt()};
+ uint ossSpeed{mDevice->Frequency};
+ uint frameSize{numChannels * mDevice->bytesFromFmt()};
/* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */
- ALuint log2FragmentSize{maxu(log2i(mDevice->UpdateSize*frameSize), 4)};
- ALuint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
+ uint log2FragmentSize{maxu(log2i(mDevice->UpdateSize*frameSize), 4)};
+ uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
audio_buf_info info{};
const char *err;
@@ -424,29 +408,26 @@ bool OSSPlayback::reset()
}
mDevice->Frequency = ossSpeed;
- mDevice->UpdateSize = static_cast<ALuint>(info.fragsize) / frameSize;
- mDevice->BufferSize = static_cast<ALuint>(info.fragments) * mDevice->UpdateSize;
+ mDevice->UpdateSize = static_cast<uint>(info.fragsize) / frameSize;
+ mDevice->BufferSize = static_cast<uint>(info.fragments) * mDevice->UpdateSize;
- SetDefaultChannelOrder(mDevice);
+ setDefaultChannelOrder();
mMixData.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
return true;
}
-bool OSSPlayback::start()
+void OSSPlayback::start()
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&OSSPlayback::mixerProc), this};
- return true;
}
catch(std::exception& e) {
- ERR("Could not create playback thread: %s\n", e.what());
- }
- catch(...) {
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to start mixing thread: %s", e.what()};
}
- return false;
}
void OSSPlayback::stop()
@@ -461,16 +442,16 @@ void OSSPlayback::stop()
struct OSScapture final : public BackendBase {
- OSScapture(ALCdevice *device) noexcept : BackendBase{device} { }
+ OSScapture(DeviceBase *device) noexcept : BackendBase{device} { }
~OSScapture() override;
int recordProc();
- void open(const ALCchar *name) override;
- bool start() override;
+ void open(const char *name) override;
+ void start() override;
void stop() override;
- ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
- ALCuint availableSamples() override;
+ void captureSamples(al::byte *buffer, uint samples) override;
+ uint availableSamples() override;
int mFd{-1};
@@ -495,7 +476,7 @@ int OSScapture::recordProc()
SetRTPriority();
althrd_setname(RECORD_THREAD_NAME);
- const ALuint frame_size{mDevice->frameSizeFromFmt()};
+ const size_t frame_size{mDevice->frameSizeFromFmt()};
while(!mKillNow.load(std::memory_order_acquire))
{
pollfd pollitem{};
@@ -508,7 +489,7 @@ int OSScapture::recordProc()
if(errno == EINTR || errno == EAGAIN)
continue;
ERR("poll failed: %s\n", strerror(errno));
- aluHandleDisconnect(mDevice, "Failed to check capture samples: %s", strerror(errno));
+ mDevice->handleDisconnect("Failed to check capture samples: %s", strerror(errno));
break;
}
else if(sret == 0)
@@ -524,11 +505,10 @@ int OSScapture::recordProc()
if(amt < 0)
{
ERR("read failed: %s\n", strerror(errno));
- aluHandleDisconnect(mDevice, "Failed reading capture samples: %s",
- strerror(errno));
+ mDevice->handleDisconnect("Failed reading capture samples: %s", strerror(errno));
break;
}
- mRing->writeAdvance(static_cast<ALuint>(amt)/frame_size);
+ mRing->writeAdvance(static_cast<size_t>(amt)/frame_size);
}
}
@@ -536,7 +516,7 @@ int OSScapture::recordProc()
}
-void OSScapture::open(const ALCchar *name)
+void OSScapture::open(const char *name)
{
const char *devname{DefaultCapture.c_str()};
if(!name)
@@ -544,20 +524,21 @@ void OSScapture::open(const ALCchar *name)
else
{
if(CaptureDevices.empty())
- ALCossListPopulate(&CaptureDevices, DSP_CAP_INPUT);
+ ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT);
auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
[&name](const DevMap &entry) -> bool
{ return entry.name == name; }
);
if(iter == CaptureDevices.cend())
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Device name \"%s\" not found", name};
devname = iter->device_name.c_str();
}
mFd = ::open(devname, O_RDONLY);
if(mFd == -1)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not open %s: %s", devname,
+ throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
strerror(errno)};
int ossFormat{};
@@ -576,21 +557,22 @@ void OSScapture::open(const ALCchar *name)
case DevFmtInt:
case DevFmtUInt:
case DevFmtFloat:
- throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported",
- DevFmtTypeString(mDevice->FmtType)};
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
}
- ALuint periods{4};
- ALuint numChannels{mDevice->channelsFromFmt()};
- ALuint frameSize{numChannels * mDevice->bytesFromFmt()};
- ALuint ossSpeed{mDevice->Frequency};
+ uint periods{4};
+ uint numChannels{mDevice->channelsFromFmt()};
+ uint frameSize{numChannels * mDevice->bytesFromFmt()};
+ uint ossSpeed{mDevice->Frequency};
/* according to the OSS spec, 16 bytes are the minimum */
- ALuint log2FragmentSize{maxu(log2i(mDevice->BufferSize * frameSize / periods), 4)};
- ALuint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
+ uint log2FragmentSize{maxu(log2i(mDevice->BufferSize * frameSize / periods), 4)};
+ uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
audio_buf_info info{};
#define CHECKERR(func) if((func) < 0) { \
- throw al::backend_exception{ALC_INVALID_VALUE, #func " failed: %s", strerror(errno)}; \
+ throw al::backend_exception{al::backend_error::DeviceError, #func " failed: %s", \
+ strerror(errno)}; \
}
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize));
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
@@ -600,35 +582,32 @@ void OSScapture::open(const ALCchar *name)
#undef CHECKERR
if(mDevice->channelsFromFmt() != numChannels)
- throw al::backend_exception{ALC_INVALID_VALUE,
+ throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set %s, got %d 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{ALC_INVALID_VALUE,
+ throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set %s samples, got OSS format %#x", DevFmtTypeString(mDevice->FmtType),
ossFormat};
- mRing = CreateRingBuffer(mDevice->BufferSize, frameSize, false);
+ mRing = RingBuffer::Create(mDevice->BufferSize, frameSize, false);
mDevice->DeviceName = name;
}
-bool OSScapture::start()
+void OSScapture::start()
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&OSScapture::recordProc), this};
- return true;
}
catch(std::exception& e) {
- ERR("Could not create record thread: %s\n", e.what());
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to start recording thread: %s", e.what()};
}
- catch(...) {
- }
- return false;
}
void OSScapture::stop()
@@ -641,14 +620,11 @@ void OSScapture::stop()
ERR("Error resetting device: %s\n", strerror(errno));
}
-ALCenum OSScapture::captureSamples(al::byte *buffer, ALCuint samples)
-{
- mRing->read(buffer, samples);
- return ALC_NO_ERROR;
-}
+void OSScapture::captureSamples(al::byte *buffer, uint samples)
+{ mRing->read(buffer, samples); }
-ALCuint OSScapture::availableSamples()
-{ return static_cast<ALCuint>(mRing->readSpace()); }
+uint OSScapture::availableSamples()
+{ return static_cast<uint>(mRing->readSpace()); }
} // namespace
@@ -672,37 +648,39 @@ bool OSSBackendFactory::init()
bool OSSBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
-void OSSBackendFactory::probe(DevProbe type, std::string *outnames)
+std::string OSSBackendFactory::probe(BackendType type)
{
- auto add_device = [outnames](const DevMap &entry) -> void
+ std::string outnames;
+
+ auto add_device = [&outnames](const DevMap &entry) -> void
{
-#ifdef HAVE_STAT
struct stat buf;
if(stat(entry.device_name.c_str(), &buf) == 0)
-#endif
{
/* Includes null char. */
- outnames->append(entry.name.c_str(), entry.name.length()+1);
+ outnames.append(entry.name.c_str(), entry.name.length()+1);
}
};
switch(type)
{
- case DevProbe::Playback:
- PlaybackDevices.clear();
- ALCossListPopulate(&PlaybackDevices, DSP_CAP_OUTPUT);
- std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
- break;
+ case BackendType::Playback:
+ PlaybackDevices.clear();
+ ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT);
+ std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
+ break;
- case DevProbe::Capture:
- CaptureDevices.clear();
- ALCossListPopulate(&CaptureDevices, DSP_CAP_INPUT);
- std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
- break;
+ case BackendType::Capture:
+ CaptureDevices.clear();
+ ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT);
+ std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
+ break;
}
+
+ return outnames;
}
-BackendPtr OSSBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr OSSBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new OSSPlayback{device}};
diff --git a/alc/backends/oss.h b/alc/backends/oss.h
index 9e63d7b6..4f2c00b9 100644
--- a/alc/backends/oss.h
+++ b/alc/backends/oss.h
@@ -1,7 +1,7 @@
#ifndef BACKENDS_OSS_H
#define BACKENDS_OSS_H
-#include "backends/base.h"
+#include "base.h"
struct OSSBackendFactory final : public BackendFactory {
public:
@@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
- void probe(DevProbe type, std::string *outnames) override;
+ std::string probe(BackendType type) override;
- BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+ BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
diff --git a/alc/backends/pipewire.cpp b/alc/backends/pipewire.cpp
new file mode 100644
index 00000000..c6569a74
--- /dev/null
+++ b/alc/backends/pipewire.cpp
@@ -0,0 +1,2166 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2010 by Chris Robinson
+ * 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 "pipewire.h"
+
+#include <algorithm>
+#include <atomic>
+#include <cstring>
+#include <cerrno>
+#include <chrono>
+#include <ctime>
+#include <list>
+#include <memory>
+#include <mutex>
+#include <stdint.h>
+#include <thread>
+#include <type_traits>
+#include <utility>
+
+#include "albyte.h"
+#include "alc/alconfig.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "aloptional.h"
+#include "alspan.h"
+#include "alstring.h"
+#include "core/devformat.h"
+#include "core/device.h"
+#include "core/helpers.h"
+#include "core/logging.h"
+#include "dynload.h"
+#include "opthelpers.h"
+#include "ringbuffer.h"
+
+/* Ignore warnings caused by PipeWire headers (lots in standard C++ mode). GCC
+ * doesn't support ignoring -Weverything, so we have the list the individual
+ * warnings to ignore (and ignoring -Winline doesn't seem to work).
+ */
+_Pragma("GCC diagnostic push")
+_Pragma("GCC diagnostic ignored \"-Wpedantic\"")
+_Pragma("GCC diagnostic ignored \"-Wconversion\"")
+_Pragma("GCC diagnostic ignored \"-Wfloat-conversion\"")
+_Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"")
+_Pragma("GCC diagnostic ignored \"-Wunused-parameter\"")
+_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"")
+_Pragma("GCC diagnostic ignored \"-Wsign-compare\"")
+_Pragma("GCC diagnostic ignored \"-Winline\"")
+_Pragma("GCC diagnostic ignored \"-Wpragmas\"")
+_Pragma("GCC diagnostic ignored \"-Weverything\"")
+#include "pipewire/pipewire.h"
+#include "pipewire/extensions/metadata.h"
+#include "spa/buffer/buffer.h"
+#include "spa/param/audio/format-utils.h"
+#include "spa/param/audio/raw.h"
+#include "spa/param/param.h"
+#include "spa/pod/builder.h"
+#include "spa/utils/json.h"
+
+namespace {
+/* Wrap some nasty macros here too... */
+template<typename ...Args>
+auto ppw_core_add_listener(pw_core *core, Args&& ...args)
+{ return pw_core_add_listener(core, std::forward<Args>(args)...); }
+template<typename ...Args>
+auto ppw_core_sync(pw_core *core, Args&& ...args)
+{ return pw_core_sync(core, std::forward<Args>(args)...); }
+template<typename ...Args>
+auto ppw_registry_add_listener(pw_registry *reg, Args&& ...args)
+{ return pw_registry_add_listener(reg, std::forward<Args>(args)...); }
+template<typename ...Args>
+auto ppw_node_add_listener(pw_node *node, Args&& ...args)
+{ return pw_node_add_listener(node, std::forward<Args>(args)...); }
+template<typename ...Args>
+auto ppw_node_subscribe_params(pw_node *node, Args&& ...args)
+{ return pw_node_subscribe_params(node, std::forward<Args>(args)...); }
+template<typename ...Args>
+auto ppw_metadata_add_listener(pw_metadata *mdata, Args&& ...args)
+{ return pw_metadata_add_listener(mdata, std::forward<Args>(args)...); }
+
+
+constexpr auto get_pod_type(const spa_pod *pod) noexcept
+{ return SPA_POD_TYPE(pod); }
+
+template<typename T>
+constexpr auto get_pod_body(const spa_pod *pod, size_t count) noexcept
+{ return al::span<T>{static_cast<T*>(SPA_POD_BODY(pod)), count}; }
+template<typename T, size_t N>
+constexpr auto get_pod_body(const spa_pod *pod) noexcept
+{ return al::span<T,N>{static_cast<T*>(SPA_POD_BODY(pod)), N}; }
+
+constexpr auto make_pod_builder(void *data, uint32_t size) noexcept
+{ return SPA_POD_BUILDER_INIT(data, size); }
+
+constexpr auto get_array_value_type(const spa_pod *pod) noexcept
+{ return SPA_POD_ARRAY_VALUE_TYPE(pod); }
+
+constexpr auto PwIdAny = PW_ID_ANY;
+
+} // namespace
+_Pragma("GCC diagnostic pop")
+
+namespace {
+
+/* Added in 0.3.33, but we currently only require 0.3.23. */
+#ifndef PW_KEY_NODE_RATE
+#define PW_KEY_NODE_RATE "node.rate"
+#endif
+
+using std::chrono::seconds;
+using std::chrono::milliseconds;
+using std::chrono::nanoseconds;
+using uint = unsigned int;
+
+constexpr char pwireDevice[] = "PipeWire Output";
+constexpr char pwireInput[] = "PipeWire Input";
+
+
+bool check_version(const char *version)
+{
+ /* There doesn't seem to be a function to get the version as an integer, so
+ * instead we have to parse the string, which hopefully won't break in the
+ * future.
+ */
+ int major{0}, minor{0}, revision{0};
+ int ret{sscanf(version, "%d.%d.%d", &major, &minor, &revision)};
+ if(ret == 3 && (major > PW_MAJOR || (major == PW_MAJOR && minor > PW_MINOR)
+ || (major == PW_MAJOR && minor == PW_MINOR && revision >= PW_MICRO)))
+ return true;
+ return false;
+}
+
+#ifdef HAVE_DYNLOAD
+#define PWIRE_FUNCS(MAGIC) \
+ MAGIC(pw_context_connect) \
+ MAGIC(pw_context_destroy) \
+ MAGIC(pw_context_new) \
+ MAGIC(pw_core_disconnect) \
+ MAGIC(pw_get_library_version) \
+ MAGIC(pw_init) \
+ MAGIC(pw_properties_free) \
+ MAGIC(pw_properties_new) \
+ MAGIC(pw_properties_set) \
+ MAGIC(pw_properties_setf) \
+ MAGIC(pw_proxy_add_object_listener) \
+ MAGIC(pw_proxy_destroy) \
+ MAGIC(pw_proxy_get_user_data) \
+ MAGIC(pw_stream_add_listener) \
+ MAGIC(pw_stream_connect) \
+ MAGIC(pw_stream_dequeue_buffer) \
+ MAGIC(pw_stream_destroy) \
+ MAGIC(pw_stream_get_state) \
+ MAGIC(pw_stream_new) \
+ MAGIC(pw_stream_queue_buffer) \
+ MAGIC(pw_stream_set_active) \
+ MAGIC(pw_thread_loop_new) \
+ MAGIC(pw_thread_loop_destroy) \
+ MAGIC(pw_thread_loop_get_loop) \
+ MAGIC(pw_thread_loop_start) \
+ MAGIC(pw_thread_loop_stop) \
+ MAGIC(pw_thread_loop_lock) \
+ MAGIC(pw_thread_loop_wait) \
+ MAGIC(pw_thread_loop_signal) \
+ MAGIC(pw_thread_loop_unlock)
+#if PW_CHECK_VERSION(0,3,50)
+#define PWIRE_FUNCS2(MAGIC) \
+ MAGIC(pw_stream_get_time_n)
+#else
+#define PWIRE_FUNCS2(MAGIC) \
+ MAGIC(pw_stream_get_time)
+#endif
+
+void *pwire_handle;
+#define MAKE_FUNC(f) decltype(f) * p##f;
+PWIRE_FUNCS(MAKE_FUNC)
+PWIRE_FUNCS2(MAKE_FUNC)
+#undef MAKE_FUNC
+
+bool pwire_load()
+{
+ if(pwire_handle)
+ return true;
+
+ static constexpr char pwire_library[] = "libpipewire-0.3.so.0";
+ std::string missing_funcs;
+
+ pwire_handle = LoadLib(pwire_library);
+ if(!pwire_handle)
+ {
+ WARN("Failed to load %s\n", pwire_library);
+ return false;
+ }
+
+#define LOAD_FUNC(f) do { \
+ p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pwire_handle, #f)); \
+ if(p##f == nullptr) missing_funcs += "\n" #f; \
+} while(0);
+ PWIRE_FUNCS(LOAD_FUNC)
+ PWIRE_FUNCS2(LOAD_FUNC)
+#undef LOAD_FUNC
+
+ if(!missing_funcs.empty())
+ {
+ WARN("Missing expected functions:%s\n", missing_funcs.c_str());
+ CloseLib(pwire_handle);
+ pwire_handle = nullptr;
+ return false;
+ }
+
+ return true;
+}
+
+#ifndef IN_IDE_PARSER
+#define pw_context_connect ppw_context_connect
+#define pw_context_destroy ppw_context_destroy
+#define pw_context_new ppw_context_new
+#define pw_core_disconnect ppw_core_disconnect
+#define pw_get_library_version ppw_get_library_version
+#define pw_init ppw_init
+#define pw_properties_free ppw_properties_free
+#define pw_properties_new ppw_properties_new
+#define pw_properties_set ppw_properties_set
+#define pw_properties_setf ppw_properties_setf
+#define pw_proxy_add_object_listener ppw_proxy_add_object_listener
+#define pw_proxy_destroy ppw_proxy_destroy
+#define pw_proxy_get_user_data ppw_proxy_get_user_data
+#define pw_stream_add_listener ppw_stream_add_listener
+#define pw_stream_connect ppw_stream_connect
+#define pw_stream_dequeue_buffer ppw_stream_dequeue_buffer
+#define pw_stream_destroy ppw_stream_destroy
+#define pw_stream_get_state ppw_stream_get_state
+#define pw_stream_new ppw_stream_new
+#define pw_stream_queue_buffer ppw_stream_queue_buffer
+#define pw_stream_set_active ppw_stream_set_active
+#define pw_thread_loop_destroy ppw_thread_loop_destroy
+#define pw_thread_loop_get_loop ppw_thread_loop_get_loop
+#define pw_thread_loop_lock ppw_thread_loop_lock
+#define pw_thread_loop_new ppw_thread_loop_new
+#define pw_thread_loop_signal ppw_thread_loop_signal
+#define pw_thread_loop_start ppw_thread_loop_start
+#define pw_thread_loop_stop ppw_thread_loop_stop
+#define pw_thread_loop_unlock ppw_thread_loop_unlock
+#define pw_thread_loop_wait ppw_thread_loop_wait
+#if PW_CHECK_VERSION(0,3,50)
+#define pw_stream_get_time_n ppw_stream_get_time_n
+#else
+inline auto pw_stream_get_time_n(pw_stream *stream, pw_time *ptime, size_t /*size*/)
+{ return ppw_stream_get_time(stream, ptime); }
+#endif
+#endif
+
+#else
+
+constexpr bool pwire_load() { return true; }
+#endif
+
+/* Helpers for retrieving values from params */
+template<uint32_t T> struct PodInfo { };
+
+template<>
+struct PodInfo<SPA_TYPE_Int> {
+ using Type = int32_t;
+ static auto get_value(const spa_pod *pod, int32_t *val)
+ { return spa_pod_get_int(pod, val); }
+};
+template<>
+struct PodInfo<SPA_TYPE_Id> {
+ using Type = uint32_t;
+ static auto get_value(const spa_pod *pod, uint32_t *val)
+ { return spa_pod_get_id(pod, val); }
+};
+
+template<uint32_t T>
+using Pod_t = typename PodInfo<T>::Type;
+
+template<uint32_t T>
+al::span<const Pod_t<T>> get_array_span(const spa_pod *pod)
+{
+ uint32_t nvals;
+ if(void *v{spa_pod_get_array(pod, &nvals)})
+ {
+ if(get_array_value_type(pod) == T)
+ return {static_cast<const Pod_t<T>*>(v), nvals};
+ }
+ return {};
+}
+
+template<uint32_t T>
+al::optional<Pod_t<T>> get_value(const spa_pod *value)
+{
+ Pod_t<T> val{};
+ if(PodInfo<T>::get_value(value, &val) == 0)
+ return val;
+ return al::nullopt;
+}
+
+/* Internally, PipeWire types "inherit" from each other, but this is hidden
+ * from the API and the caller is expected to C-style cast to inherited types
+ * as needed. It's also not made very clear what types a given type can be
+ * casted to. To make it a bit safer, this as() method allows casting pw_*
+ * types to known inherited types, generating a compile-time error for
+ * unexpected/invalid casts.
+ */
+template<typename To, typename From>
+To as(From) noexcept = delete;
+
+/* pw_proxy
+ * - pw_registry
+ * - pw_node
+ * - pw_metadata
+ */
+template<>
+pw_proxy* as(pw_registry *reg) noexcept { return reinterpret_cast<pw_proxy*>(reg); }
+template<>
+pw_proxy* as(pw_node *node) noexcept { return reinterpret_cast<pw_proxy*>(node); }
+template<>
+pw_proxy* as(pw_metadata *mdata) noexcept { return reinterpret_cast<pw_proxy*>(mdata); }
+
+
+struct PwContextDeleter {
+ void operator()(pw_context *context) const { pw_context_destroy(context); }
+};
+using PwContextPtr = std::unique_ptr<pw_context,PwContextDeleter>;
+
+struct PwCoreDeleter {
+ void operator()(pw_core *core) const { pw_core_disconnect(core); }
+};
+using PwCorePtr = std::unique_ptr<pw_core,PwCoreDeleter>;
+
+struct PwRegistryDeleter {
+ void operator()(pw_registry *reg) const { pw_proxy_destroy(as<pw_proxy*>(reg)); }
+};
+using PwRegistryPtr = std::unique_ptr<pw_registry,PwRegistryDeleter>;
+
+struct PwNodeDeleter {
+ void operator()(pw_node *node) const { pw_proxy_destroy(as<pw_proxy*>(node)); }
+};
+using PwNodePtr = std::unique_ptr<pw_node,PwNodeDeleter>;
+
+struct PwMetadataDeleter {
+ void operator()(pw_metadata *mdata) const { pw_proxy_destroy(as<pw_proxy*>(mdata)); }
+};
+using PwMetadataPtr = std::unique_ptr<pw_metadata,PwMetadataDeleter>;
+
+struct PwStreamDeleter {
+ void operator()(pw_stream *stream) const { pw_stream_destroy(stream); }
+};
+using PwStreamPtr = std::unique_ptr<pw_stream,PwStreamDeleter>;
+
+/* 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)); }
+
+constexpr pw_stream_flags& operator|=(pw_stream_flags &lhs, pw_stream_flags rhs) noexcept
+{ lhs = lhs | rhs; return lhs; }
+
+class ThreadMainloop {
+ pw_thread_loop *mLoop{};
+
+public:
+ ThreadMainloop() = default;
+ ThreadMainloop(const ThreadMainloop&) = delete;
+ ThreadMainloop(ThreadMainloop&& rhs) noexcept : mLoop{rhs.mLoop} { rhs.mLoop = nullptr; }
+ explicit ThreadMainloop(pw_thread_loop *loop) noexcept : mLoop{loop} { }
+ ~ThreadMainloop() { if(mLoop) pw_thread_loop_destroy(mLoop); }
+
+ ThreadMainloop& operator=(const ThreadMainloop&) = delete;
+ ThreadMainloop& operator=(ThreadMainloop&& rhs) noexcept
+ { std::swap(mLoop, rhs.mLoop); return *this; }
+ ThreadMainloop& operator=(std::nullptr_t) noexcept
+ {
+ if(mLoop)
+ pw_thread_loop_destroy(mLoop);
+ mLoop = nullptr;
+ return *this;
+ }
+
+ explicit operator bool() const noexcept { return mLoop != nullptr; }
+
+ auto start() const { return pw_thread_loop_start(mLoop); }
+ auto stop() const { return pw_thread_loop_stop(mLoop); }
+
+ auto getLoop() const { return pw_thread_loop_get_loop(mLoop); }
+
+ auto lock() const { return pw_thread_loop_lock(mLoop); }
+ auto unlock() const { return pw_thread_loop_unlock(mLoop); }
+
+ auto signal(bool wait) const { return pw_thread_loop_signal(mLoop, wait); }
+
+ auto newContext(pw_properties *props=nullptr, size_t user_data_size=0)
+ { return PwContextPtr{pw_context_new(getLoop(), props, user_data_size)}; }
+
+ static auto Create(const char *name, spa_dict *props=nullptr)
+ { return ThreadMainloop{pw_thread_loop_new(name, props)}; }
+
+ friend struct MainloopUniqueLock;
+};
+struct MainloopUniqueLock : public std::unique_lock<ThreadMainloop> {
+ using std::unique_lock<ThreadMainloop>::unique_lock;
+ MainloopUniqueLock& operator=(MainloopUniqueLock&&) = default;
+
+ auto wait() const -> void
+ { pw_thread_loop_wait(mutex()->mLoop); }
+
+ template<typename Predicate>
+ auto wait(Predicate done_waiting) const -> void
+ { while(!done_waiting()) wait(); }
+};
+using MainloopLockGuard = std::lock_guard<ThreadMainloop>;
+
+
+/* There's quite a mess here, but the purpose is to track active devices and
+ * their default formats, so playback devices can be configured to match. The
+ * device list is updated asynchronously, so it will have the latest list of
+ * devices provided by the server.
+ */
+
+struct NodeProxy;
+struct MetadataProxy;
+
+/* The global thread watching for global events. This particular class responds
+ * to objects being added to or removed from the registry.
+ */
+struct EventManager {
+ ThreadMainloop mLoop{};
+ PwContextPtr mContext{};
+ PwCorePtr mCore{};
+ PwRegistryPtr mRegistry{};
+ spa_hook mRegistryListener{};
+ spa_hook mCoreListener{};
+
+ /* A list of proxy objects watching for events about changes to objects in
+ * the registry.
+ */
+ std::vector<NodeProxy*> mNodeList;
+ MetadataProxy *mDefaultMetadata{nullptr};
+
+ /* Initialization handling. When init() is called, mInitSeq is set to a
+ * SequenceID that marks the end of populating the registry. As objects of
+ * interest are found, events to parse them are generated and mInitSeq is
+ * updated with a newer ID. When mInitSeq stops being updated and the event
+ * corresponding to it is reached, mInitDone will be set to true.
+ */
+ std::atomic<bool> mInitDone{false};
+ std::atomic<bool> mHasAudio{false};
+ int mInitSeq{};
+
+ bool init();
+ ~EventManager();
+
+ void kill();
+
+ auto lock() const { return mLoop.lock(); }
+ auto unlock() const { return mLoop.unlock(); }
+
+ /**
+ * Waits for initialization to finish. The event manager must *NOT* be
+ * locked when calling this.
+ */
+ void waitForInit()
+ {
+ if(!mInitDone.load(std::memory_order_acquire)) UNLIKELY
+ {
+ MainloopUniqueLock plock{mLoop};
+ plock.wait([this](){ return mInitDone.load(std::memory_order_acquire); });
+ }
+ }
+
+ /**
+ * Waits for audio support to be detected, or initialization to finish,
+ * whichever is first. Returns true if audio support was detected. The
+ * event manager must *NOT* be locked when calling this.
+ */
+ bool waitForAudio()
+ {
+ MainloopUniqueLock plock{mLoop};
+ bool has_audio{};
+ plock.wait([this,&has_audio]()
+ {
+ has_audio = mHasAudio.load(std::memory_order_acquire);
+ return has_audio || mInitDone.load(std::memory_order_acquire);
+ });
+ return has_audio;
+ }
+
+ void syncInit()
+ {
+ /* If initialization isn't done, update the sequence ID so it won't
+ * complete until after currently scheduled events.
+ */
+ if(!mInitDone.load(std::memory_order_relaxed))
+ mInitSeq = ppw_core_sync(mCore.get(), PW_ID_CORE, mInitSeq);
+ }
+
+ void addCallback(uint32_t id, uint32_t permissions, const char *type, uint32_t version,
+ const spa_dict *props);
+ static void addCallbackC(void *object, uint32_t id, uint32_t permissions, const char *type,
+ uint32_t version, const spa_dict *props)
+ { static_cast<EventManager*>(object)->addCallback(id, permissions, type, version, props); }
+
+ void removeCallback(uint32_t id);
+ static void removeCallbackC(void *object, uint32_t id)
+ { static_cast<EventManager*>(object)->removeCallback(id); }
+
+ static constexpr pw_registry_events CreateRegistryEvents()
+ {
+ pw_registry_events ret{};
+ ret.version = PW_VERSION_REGISTRY_EVENTS;
+ ret.global = &EventManager::addCallbackC;
+ ret.global_remove = &EventManager::removeCallbackC;
+ return ret;
+ }
+
+ void coreCallback(uint32_t id, int seq);
+ static void coreCallbackC(void *object, uint32_t id, int seq)
+ { static_cast<EventManager*>(object)->coreCallback(id, seq); }
+
+ static constexpr pw_core_events CreateCoreEvents()
+ {
+ pw_core_events ret{};
+ ret.version = PW_VERSION_CORE_EVENTS;
+ ret.done = &EventManager::coreCallbackC;
+ return ret;
+ }
+};
+using EventWatcherUniqueLock = std::unique_lock<EventManager>;
+using EventWatcherLockGuard = std::lock_guard<EventManager>;
+
+EventManager gEventHandler;
+
+/* Enumerated devices. This is updated asynchronously as the app runs, and the
+ * gEventHandler thread loop must be locked when accessing the list.
+ */
+enum class NodeType : unsigned char {
+ Sink, Source, Duplex
+};
+constexpr auto InvalidChannelConfig = DevFmtChannels(255);
+struct DeviceNode {
+ uint32_t mId{};
+
+ uint64_t mSerial{};
+ std::string mName;
+ std::string mDevName;
+
+ NodeType mType{};
+ bool mIsHeadphones{};
+ bool mIs51Rear{};
+
+ uint mSampleRate{};
+ DevFmtChannels mChannels{InvalidChannelConfig};
+
+ static std::vector<DeviceNode> sList;
+ static DeviceNode &Add(uint32_t id);
+ static DeviceNode *Find(uint32_t id);
+ static void Remove(uint32_t id);
+ static std::vector<DeviceNode> &GetList() noexcept { return sList; }
+
+ void parseSampleRate(const spa_pod *value) noexcept;
+ void parsePositions(const spa_pod *value) noexcept;
+ void parseChannelCount(const spa_pod *value) noexcept;
+};
+std::vector<DeviceNode> DeviceNode::sList;
+std::string DefaultSinkDevice;
+std::string DefaultSourceDevice;
+
+const char *AsString(NodeType type) noexcept
+{
+ switch(type)
+ {
+ case NodeType::Sink: return "sink";
+ case NodeType::Source: return "source";
+ case NodeType::Duplex: return "duplex";
+ }
+ return "<unknown>";
+}
+
+DeviceNode &DeviceNode::Add(uint32_t id)
+{
+ auto match_id = [id](DeviceNode &n) noexcept -> bool
+ { return n.mId == id; };
+
+ /* If the node is already in the list, return the existing entry. */
+ auto match = std::find_if(sList.begin(), sList.end(), match_id);
+ if(match != sList.end()) return *match;
+
+ sList.emplace_back();
+ auto &n = sList.back();
+ n.mId = id;
+ return n;
+}
+
+DeviceNode *DeviceNode::Find(uint32_t id)
+{
+ auto match_id = [id](DeviceNode &n) noexcept -> bool
+ { return n.mId == id; };
+
+ auto match = std::find_if(sList.begin(), sList.end(), match_id);
+ if(match != sList.end()) return al::to_address(match);
+
+ return nullptr;
+}
+
+void DeviceNode::Remove(uint32_t id)
+{
+ auto match_id = [id](DeviceNode &n) noexcept -> bool
+ {
+ if(n.mId != id)
+ return false;
+ TRACE("Removing device \"%s\"\n", n.mDevName.c_str());
+ return true;
+ };
+
+ auto end = std::remove_if(sList.begin(), sList.end(), match_id);
+ sList.erase(end, sList.end());
+}
+
+
+const spa_audio_channel MonoMap[]{
+ SPA_AUDIO_CHANNEL_MONO
+}, StereoMap[] {
+ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR
+}, QuadMap[]{
+ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR
+}, X51Map[]{
+ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
+ SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR
+}, X51RearMap[]{
+ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
+ SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR
+}, X61Map[]{
+ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
+ SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR
+}, X71Map[]{
+ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
+ SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR
+}, X714Map[]{
+ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
+ SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR,
+ SPA_AUDIO_CHANNEL_TFL, SPA_AUDIO_CHANNEL_TFR, SPA_AUDIO_CHANNEL_TRL, SPA_AUDIO_CHANNEL_TRR
+};
+
+/**
+ * Checks if every channel in 'map1' exists in 'map0' (that is, map0 is equal
+ * to or a superset of map1).
+ */
+template<size_t N>
+bool MatchChannelMap(const al::span<const uint32_t> map0, const spa_audio_channel (&map1)[N])
+{
+ if(map0.size() < N)
+ return false;
+ for(const spa_audio_channel chid : map1)
+ {
+ if(std::find(map0.begin(), map0.end(), chid) == map0.end())
+ return false;
+ }
+ return true;
+}
+
+void DeviceNode::parseSampleRate(const spa_pod *value) noexcept
+{
+ /* TODO: Can this be anything else? Long, Float, Double? */
+ uint32_t nvals{}, choiceType{};
+ value = spa_pod_get_values(value, &nvals, &choiceType);
+
+ const uint podType{get_pod_type(value)};
+ if(podType != SPA_TYPE_Int)
+ {
+ WARN("Unhandled sample rate POD type: %u\n", podType);
+ return;
+ }
+
+ if(choiceType == SPA_CHOICE_Range)
+ {
+ if(nvals != 3)
+ {
+ WARN("Unexpected SPA_CHOICE_Range count: %u\n", 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("Device ID %" PRIu64 " sample rate: %d (range: %d -> %d)\n", mSerial, srates[0],
+ srates[1], srates[2]);
+ mSampleRate = static_cast<uint>(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE));
+ return;
+ }
+
+ if(choiceType == SPA_CHOICE_Enum)
+ {
+ if(nvals == 0)
+ {
+ WARN("Unexpected SPA_CHOICE_Enum count: %u\n", 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("Device ID %" PRIu64 " sample rate: %d (%s)\n", mSerial, srates[0], others.c_str());
+ /* Pick the first rate listed that's within the allowed range (default
+ * rate if possible).
+ */
+ for(const auto &rate : srates)
+ {
+ if(rate >= MIN_OUTPUT_RATE && rate <= MAX_OUTPUT_RATE)
+ {
+ mSampleRate = static_cast<uint>(rate);
+ break;
+ }
+ }
+ return;
+ }
+
+ if(choiceType == SPA_CHOICE_None)
+ {
+ if(nvals != 1)
+ {
+ WARN("Unexpected SPA_CHOICE_None count: %u\n", nvals);
+ return;
+ }
+ auto srates = get_pod_body<int32_t,1>(value);
+
+ TRACE("Device ID %" PRIu64 " sample rate: %d\n", mSerial, srates[0]);
+ mSampleRate = static_cast<uint>(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE));
+ return;
+ }
+
+ WARN("Unhandled sample rate choice type: %u\n", choiceType);
+}
+
+void DeviceNode::parsePositions(const spa_pod *value) noexcept
+{
+ const auto chanmap = get_array_span<SPA_TYPE_Id>(value);
+ if(chanmap.empty()) return;
+
+ mIs51Rear = false;
+
+ if(MatchChannelMap(chanmap, X714Map))
+ mChannels = DevFmtX714;
+ else if(MatchChannelMap(chanmap, X71Map))
+ mChannels = DevFmtX71;
+ else if(MatchChannelMap(chanmap, X61Map))
+ mChannels = DevFmtX61;
+ else if(MatchChannelMap(chanmap, X51Map))
+ mChannels = DevFmtX51;
+ else if(MatchChannelMap(chanmap, X51RearMap))
+ {
+ mChannels = DevFmtX51;
+ mIs51Rear = true;
+ }
+ else if(MatchChannelMap(chanmap, QuadMap))
+ mChannels = DevFmtQuad;
+ else if(MatchChannelMap(chanmap, StereoMap))
+ mChannels = DevFmtStereo;
+ else
+ mChannels = DevFmtMono;
+ TRACE("Device ID %" PRIu64 " got %zu position%s for %s%s\n", mSerial, chanmap.size(),
+ (chanmap.size()==1)?"":"s", DevFmtChannelsString(mChannels), mIs51Rear?"(rear)":"");
+}
+
+void DeviceNode::parseChannelCount(const spa_pod *value) noexcept
+{
+ /* As a fallback with just a channel count, just assume mono or stereo. */
+ const auto chancount = get_value<SPA_TYPE_Int>(value);
+ if(!chancount) return;
+
+ mIs51Rear = false;
+
+ if(*chancount >= 2)
+ mChannels = DevFmtStereo;
+ else if(*chancount >= 1)
+ mChannels = DevFmtMono;
+ TRACE("Device ID %" PRIu64 " got %d channel%s for %s\n", mSerial, *chancount,
+ (*chancount==1)?"":"s", DevFmtChannelsString(mChannels));
+}
+
+
+constexpr char MonitorPrefix[]{"Monitor of "};
+constexpr auto MonitorPrefixLen = al::size(MonitorPrefix) - 1;
+constexpr char AudioSinkClass[]{"Audio/Sink"};
+constexpr char AudioSourceClass[]{"Audio/Source"};
+constexpr char AudioSourceVirtualClass[]{"Audio/Source/Virtual"};
+constexpr char AudioDuplexClass[]{"Audio/Duplex"};
+constexpr char StreamClass[]{"Stream/"};
+
+/* A generic PipeWire node proxy object used to track changes to sink and
+ * source nodes.
+ */
+struct NodeProxy {
+ static constexpr pw_node_events CreateNodeEvents()
+ {
+ pw_node_events ret{};
+ ret.version = PW_VERSION_NODE_EVENTS;
+ ret.info = &NodeProxy::infoCallbackC;
+ ret.param = &NodeProxy::paramCallbackC;
+ return ret;
+ }
+
+ uint32_t mId{};
+
+ PwNodePtr mNode{};
+ spa_hook mListener{};
+
+ NodeProxy(uint32_t id, PwNodePtr node)
+ : mId{id}, mNode{std::move(node)}
+ {
+ static constexpr pw_node_events nodeEvents{CreateNodeEvents()};
+ ppw_node_add_listener(mNode.get(), &mListener, &nodeEvents, this);
+
+ /* Track changes to the enumerable formats (indicates the default
+ * format, which is what we're interested in).
+ */
+ uint32_t fmtids[]{SPA_PARAM_EnumFormat};
+ ppw_node_subscribe_params(mNode.get(), al::data(fmtids), al::size(fmtids));
+ }
+ ~NodeProxy()
+ { spa_hook_remove(&mListener); }
+
+
+ void infoCallback(const pw_node_info *info);
+ static void infoCallbackC(void *object, const pw_node_info *info)
+ { static_cast<NodeProxy*>(object)->infoCallback(info); }
+
+ void paramCallback(int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param);
+ static void paramCallbackC(void *object, int seq, uint32_t id, uint32_t index, uint32_t next,
+ const spa_pod *param)
+ { static_cast<NodeProxy*>(object)->paramCallback(seq, id, index, next, param); }
+};
+
+void NodeProxy::infoCallback(const pw_node_info *info)
+{
+ /* We only care about property changes here (media class, name/desc).
+ * Format changes will automatically invoke the param callback.
+ *
+ * TODO: Can the media class or name/desc change without being removed and
+ * readded?
+ */
+ if((info->change_mask&PW_NODE_CHANGE_MASK_PROPS))
+ {
+ /* Can this actually change? */
+ const char *media_class{spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS)};
+ if(!media_class) UNLIKELY return;
+
+ NodeType ntype{};
+ if(al::strcasecmp(media_class, AudioSinkClass) == 0)
+ ntype = NodeType::Sink;
+ else if(al::strcasecmp(media_class, AudioSourceClass) == 0
+ || al::strcasecmp(media_class, AudioSourceVirtualClass) == 0)
+ ntype = NodeType::Source;
+ else if(al::strcasecmp(media_class, AudioDuplexClass) == 0)
+ ntype = NodeType::Duplex;
+ else
+ {
+ TRACE("Dropping device node %u which became type \"%s\"\n", info->id, media_class);
+ DeviceNode::Remove(info->id);
+ return;
+ }
+
+ const char *devName{spa_dict_lookup(info->props, PW_KEY_NODE_NAME)};
+ const char *nodeName{spa_dict_lookup(info->props, PW_KEY_NODE_DESCRIPTION)};
+ if(!nodeName || !*nodeName) nodeName = spa_dict_lookup(info->props, PW_KEY_NODE_NICK);
+ if(!nodeName || !*nodeName) nodeName = devName;
+
+ uint64_t serial_id{info->id};
+#ifdef PW_KEY_OBJECT_SERIAL
+ if(const char *serial_str{spa_dict_lookup(info->props, PW_KEY_OBJECT_SERIAL)})
+ {
+ char *serial_end{};
+ serial_id = std::strtoull(serial_str, &serial_end, 0);
+ if(*serial_end != '\0' || errno == ERANGE)
+ {
+ ERR("Unexpected object serial: %s\n", serial_str);
+ serial_id = info->id;
+ }
+ }
+#endif
+
+ 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)",
+ form_factor?" (":"", form_factor?form_factor:"", form_factor?")":"");
+ TRACE(" \"%s\" = ID %" PRIu64 "\n", nodeName ? nodeName : "(nil)", serial_id);
+
+ DeviceNode &node = DeviceNode::Add(info->id);
+ node.mSerial = serial_id;
+ if(nodeName && *nodeName) node.mName = nodeName;
+ else node.mName = "PipeWire node #"+std::to_string(info->id);
+ node.mDevName = devName ? devName : "";
+ node.mType = ntype;
+ node.mIsHeadphones = form_factor && (al::strcasecmp(form_factor, "headphones") == 0
+ || al::strcasecmp(form_factor, "headset") == 0);
+ }
+}
+
+void NodeProxy::paramCallback(int, uint32_t id, uint32_t, uint32_t, const spa_pod *param)
+{
+ if(id == SPA_PARAM_EnumFormat)
+ {
+ DeviceNode *node{DeviceNode::Find(mId)};
+ if(!node) UNLIKELY return;
+
+ if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_rate)})
+ node->parseSampleRate(&prop->value);
+
+ if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_position)})
+ node->parsePositions(&prop->value);
+ else if((prop=spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_channels)) != nullptr)
+ node->parseChannelCount(&prop->value);
+ }
+}
+
+
+/* A metadata proxy object used to query the default sink and source. */
+struct MetadataProxy {
+ static constexpr pw_metadata_events CreateMetadataEvents()
+ {
+ pw_metadata_events ret{};
+ ret.version = PW_VERSION_METADATA_EVENTS;
+ ret.property = &MetadataProxy::propertyCallbackC;
+ return ret;
+ }
+
+ uint32_t mId{};
+
+ PwMetadataPtr mMetadata{};
+ spa_hook mListener{};
+
+ MetadataProxy(uint32_t id, PwMetadataPtr mdata)
+ : mId{id}, mMetadata{std::move(mdata)}
+ {
+ static constexpr pw_metadata_events metadataEvents{CreateMetadataEvents()};
+ ppw_metadata_add_listener(mMetadata.get(), &mListener, &metadataEvents, this);
+ }
+ ~MetadataProxy()
+ { spa_hook_remove(&mListener); }
+
+
+ int propertyCallback(uint32_t id, const char *key, const char *type, const char *value);
+ static int propertyCallbackC(void *object, uint32_t id, const char *key, const char *type,
+ const char *value)
+ { return static_cast<MetadataProxy*>(object)->propertyCallback(id, key, type, value); }
+};
+
+int MetadataProxy::propertyCallback(uint32_t id, const char *key, const char *type,
+ const char *value)
+{
+ if(id != PW_ID_CORE)
+ return 0;
+
+ bool isCapture{};
+ if(std::strcmp(key, "default.audio.sink") == 0)
+ isCapture = false;
+ else if(std::strcmp(key, "default.audio.source") == 0)
+ isCapture = true;
+ else
+ return 0;
+
+ if(!type)
+ {
+ TRACE("Default %s device cleared\n", isCapture ? "capture" : "playback");
+ if(!isCapture) DefaultSinkDevice.clear();
+ else DefaultSourceDevice.clear();
+ return 0;
+ }
+ if(std::strcmp(type, "Spa:String:JSON") != 0)
+ {
+ ERR("Unexpected %s property type: %s\n", key, type);
+ return 0;
+ }
+
+ spa_json it[2]{};
+ spa_json_init(&it[0], value, strlen(value));
+ if(spa_json_enter_object(&it[0], &it[1]) <= 0)
+ return 0;
+
+ auto get_json_string = [](spa_json *iter)
+ {
+ al::optional<std::string> str;
+
+ const char *val{};
+ int len{spa_json_next(iter, &val)};
+ if(len <= 0) return str;
+
+ str.emplace().resize(static_cast<uint>(len), '\0');
+ if(spa_json_parse_string(val, len, &str->front()) <= 0)
+ str.reset();
+ else while(!str->empty() && str->back() == '\0')
+ str->pop_back();
+ return str;
+ };
+ while(auto propKey = get_json_string(&it[1]))
+ {
+ if(*propKey == "name")
+ {
+ auto propValue = get_json_string(&it[1]);
+ if(!propValue) break;
+
+ TRACE("Got default %s device \"%s\"\n", isCapture ? "capture" : "playback",
+ propValue->c_str());
+ if(!isCapture)
+ DefaultSinkDevice = std::move(*propValue);
+ else
+ DefaultSourceDevice = std::move(*propValue);
+ }
+ else
+ {
+ const char *v{};
+ if(spa_json_next(&it[1], &v) <= 0)
+ break;
+ }
+ }
+ return 0;
+}
+
+
+bool EventManager::init()
+{
+ mLoop = ThreadMainloop::Create("PWEventThread");
+ if(!mLoop)
+ {
+ ERR("Failed to create PipeWire event thread loop (errno: %d)\n", errno);
+ return false;
+ }
+
+ mContext = mLoop.newContext(pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr));
+ if(!mContext)
+ {
+ ERR("Failed to create PipeWire event context (errno: %d)\n", 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);
+ 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);
+ return false;
+ }
+
+ static constexpr pw_core_events coreEvents{CreateCoreEvents()};
+ static constexpr pw_registry_events registryEvents{CreateRegistryEvents()};
+
+ ppw_core_add_listener(mCore.get(), &mCoreListener, &coreEvents, this);
+ ppw_registry_add_listener(mRegistry.get(), &mRegistryListener, &registryEvents, this);
+
+ /* Set an initial sequence ID for initialization, to trigger after the
+ * registry is first populated.
+ */
+ mInitSeq = ppw_core_sync(mCore.get(), PW_ID_CORE, 0);
+
+ if(int res{mLoop.start()})
+ {
+ ERR("Failed to start PipeWire event thread loop (res: %d)\n", res);
+ return false;
+ }
+
+ return true;
+}
+
+EventManager::~EventManager()
+{
+ if(mLoop) mLoop.stop();
+
+ for(NodeProxy *node : mNodeList)
+ al::destroy_at(node);
+ if(mDefaultMetadata)
+ al::destroy_at(mDefaultMetadata);
+}
+
+void EventManager::kill()
+{
+ if(mLoop) mLoop.stop();
+
+ for(NodeProxy *node : mNodeList)
+ al::destroy_at(node);
+ mNodeList.clear();
+ if(mDefaultMetadata)
+ al::destroy_at(mDefaultMetadata);
+ mDefaultMetadata = nullptr;
+
+ mRegistry = nullptr;
+ mCore = nullptr;
+ mContext = nullptr;
+ mLoop = nullptr;
+}
+
+void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t version,
+ const spa_dict *props)
+{
+ /* We're only interested in interface nodes. */
+ if(std::strcmp(type, PW_TYPE_INTERFACE_Node) == 0)
+ {
+ const char *media_class{spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)};
+ if(!media_class) return;
+
+ /* Specifically, audio sinks and sources (and duplexes). */
+ const bool isGood{al::strcasecmp(media_class, AudioSinkClass) == 0
+ || al::strcasecmp(media_class, AudioSourceClass) == 0
+ || al::strcasecmp(media_class, AudioSourceVirtualClass) == 0
+ || al::strcasecmp(media_class, AudioDuplexClass) == 0};
+ if(!isGood)
+ {
+ if(std::strstr(media_class, "/Video") == nullptr
+ && std::strncmp(media_class, StreamClass, sizeof(StreamClass)-1) != 0)
+ TRACE("Ignoring node class %s\n", media_class);
+ return;
+ }
+
+ /* Create the proxy object. */
+ auto node = PwNodePtr{static_cast<pw_node*>(pw_registry_bind(mRegistry.get(), id, type,
+ version, sizeof(NodeProxy)))};
+ if(!node)
+ {
+ ERR("Failed to create node proxy object (errno: %d)\n", errno);
+ return;
+ }
+
+ /* Initialize the NodeProxy to hold the node object, add it to the
+ * active node list, and update the sync point.
+ */
+ auto *proxy = static_cast<NodeProxy*>(pw_proxy_get_user_data(as<pw_proxy*>(node.get())));
+ mNodeList.emplace_back(al::construct_at(proxy, id, std::move(node)));
+ syncInit();
+
+ /* Signal any waiters that we have found a source or sink for audio
+ * support.
+ */
+ if(!mHasAudio.exchange(true, std::memory_order_acq_rel))
+ mLoop.signal(false);
+ }
+ else if(std::strcmp(type, PW_TYPE_INTERFACE_Metadata) == 0)
+ {
+ const char *data_class{spa_dict_lookup(props, PW_KEY_METADATA_NAME)};
+ if(!data_class) return;
+
+ if(std::strcmp(data_class, "default") != 0)
+ {
+ TRACE("Ignoring metadata \"%s\"\n", data_class);
+ return;
+ }
+
+ if(mDefaultMetadata)
+ {
+ ERR("Duplicate default metadata\n");
+ return;
+ }
+
+ auto mdata = PwMetadataPtr{static_cast<pw_metadata*>(pw_registry_bind(mRegistry.get(), id,
+ type, version, sizeof(MetadataProxy)))};
+ if(!mdata)
+ {
+ ERR("Failed to create metadata proxy object (errno: %d)\n", errno);
+ return;
+ }
+
+ auto *proxy = static_cast<MetadataProxy*>(
+ pw_proxy_get_user_data(as<pw_proxy*>(mdata.get())));
+ mDefaultMetadata = al::construct_at(proxy, id, std::move(mdata));
+ syncInit();
+ }
+}
+
+void EventManager::removeCallback(uint32_t id)
+{
+ DeviceNode::Remove(id);
+
+ auto clear_node = [id](NodeProxy *node) noexcept
+ {
+ if(node->mId != id)
+ return false;
+ al::destroy_at(node);
+ return true;
+ };
+ auto node_end = std::remove_if(mNodeList.begin(), mNodeList.end(), clear_node);
+ mNodeList.erase(node_end, mNodeList.end());
+
+ if(mDefaultMetadata && mDefaultMetadata->mId == id)
+ {
+ al::destroy_at(mDefaultMetadata);
+ mDefaultMetadata = nullptr;
+ }
+}
+
+void EventManager::coreCallback(uint32_t id, int seq)
+{
+ if(id == PW_ID_CORE && seq == mInitSeq)
+ {
+ /* Initialization done. Remove this callback and signal anyone that may
+ * be waiting.
+ */
+ spa_hook_remove(&mCoreListener);
+
+ mInitDone.store(true);
+ mLoop.signal(false);
+ }
+}
+
+
+enum use_f32p_e : bool { UseDevType=false, ForceF32Planar=true };
+spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e use_f32p)
+{
+ spa_audio_info_raw info{};
+ if(use_f32p)
+ {
+ device->FmtType = DevFmtFloat;
+ info.format = SPA_AUDIO_FORMAT_F32P;
+ }
+ else switch(device->FmtType)
+ {
+ case DevFmtByte: info.format = SPA_AUDIO_FORMAT_S8; break;
+ case DevFmtUByte: info.format = SPA_AUDIO_FORMAT_U8; break;
+ case DevFmtShort: info.format = SPA_AUDIO_FORMAT_S16; break;
+ case DevFmtUShort: info.format = SPA_AUDIO_FORMAT_U16; break;
+ case DevFmtInt: info.format = SPA_AUDIO_FORMAT_S32; break;
+ case DevFmtUInt: info.format = SPA_AUDIO_FORMAT_U32; break;
+ case DevFmtFloat: info.format = SPA_AUDIO_FORMAT_F32; break;
+ }
+
+ info.rate = device->Frequency;
+
+ al::span<const spa_audio_channel> map{};
+ switch(device->FmtChans)
+ {
+ case DevFmtMono: map = MonoMap; break;
+ case DevFmtStereo: map = StereoMap; break;
+ case DevFmtQuad: map = QuadMap; break;
+ case DevFmtX51:
+ if(is51rear) map = X51RearMap;
+ else map = X51Map;
+ break;
+ case DevFmtX61: map = X61Map; break;
+ case DevFmtX71: map = X71Map; break;
+ case DevFmtX714: map = X714Map; break;
+ case DevFmtX3D71: map = X71Map; break;
+ case DevFmtAmbi3D:
+ info.flags |= SPA_AUDIO_FLAG_UNPOSITIONED;
+ info.channels = device->channelsFromFmt();
+ break;
+ }
+ if(!map.empty())
+ {
+ info.channels = static_cast<uint32_t>(map.size());
+ std::copy(map.begin(), map.end(), info.position);
+ }
+
+ return info;
+}
+
+class PipeWirePlayback final : public BackendBase {
+ void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error);
+ static void stateChangedCallbackC(void *data, pw_stream_state old, pw_stream_state state,
+ const char *error)
+ { static_cast<PipeWirePlayback*>(data)->stateChangedCallback(old, state, error); }
+
+ void ioChangedCallback(uint32_t id, void *area, uint32_t size);
+ static void ioChangedCallbackC(void *data, uint32_t id, void *area, uint32_t size)
+ { static_cast<PipeWirePlayback*>(data)->ioChangedCallback(id, area, size); }
+
+ void outputCallback();
+ static void outputCallbackC(void *data)
+ { static_cast<PipeWirePlayback*>(data)->outputCallback(); }
+
+ void open(const char *name) override;
+ bool reset() override;
+ void start() override;
+ void stop() override;
+ ClockLatency getClockLatency() override;
+
+ uint64_t mTargetId{PwIdAny};
+ nanoseconds mTimeBase{0};
+ ThreadMainloop mLoop;
+ PwContextPtr mContext;
+ PwCorePtr mCore;
+ PwStreamPtr mStream;
+ spa_hook mStreamListener{};
+ spa_io_rate_match *mRateMatch{};
+ std::unique_ptr<float*[]> mChannelPtrs;
+ uint mNumChannels{};
+
+ static constexpr pw_stream_events CreateEvents()
+ {
+ pw_stream_events ret{};
+ ret.version = PW_VERSION_STREAM_EVENTS;
+ ret.state_changed = &PipeWirePlayback::stateChangedCallbackC;
+ ret.io_changed = &PipeWirePlayback::ioChangedCallbackC;
+ ret.process = &PipeWirePlayback::outputCallbackC;
+ return ret;
+ }
+
+public:
+ PipeWirePlayback(DeviceBase *device) noexcept : BackendBase{device} { }
+ ~PipeWirePlayback()
+ {
+ /* Stop the mainloop so the stream can be properly destroyed. */
+ if(mLoop) mLoop.stop();
+ }
+
+ DEF_NEWDEL(PipeWirePlayback)
+};
+
+
+void PipeWirePlayback::stateChangedCallback(pw_stream_state, pw_stream_state, const char*)
+{ mLoop.signal(false); }
+
+void PipeWirePlayback::ioChangedCallback(uint32_t id, void *area, uint32_t size)
+{
+ switch(id)
+ {
+ case SPA_IO_RateMatch:
+ if(size >= sizeof(spa_io_rate_match))
+ mRateMatch = static_cast<spa_io_rate_match*>(area);
+ break;
+ }
+}
+
+void PipeWirePlayback::outputCallback()
+{
+ pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())};
+ if(!pw_buf) UNLIKELY return;
+
+ const al::span<spa_data> datas{pw_buf->buffer->datas,
+ minu(mNumChannels, pw_buf->buffer->n_datas)};
+#if PW_CHECK_VERSION(0,3,49)
+ /* In 0.3.49, pw_buffer::requested specifies the number of samples needed
+ * by the resampler/graph for this audio update.
+ */
+ uint length{static_cast<uint>(pw_buf->requested)};
+#else
+ /* In 0.3.48 and earlier, spa_io_rate_match::size apparently has the number
+ * of samples per update.
+ */
+ 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;
+
+ /* 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
+ * buffer length in any one channel is smaller than we wanted (shouldn't
+ * be, but just in case).
+ */
+ float **chanptr_end{mChannelPtrs.get()};
+ for(const auto &data : datas)
+ {
+ length = minu(length, data.maxsize/sizeof(float));
+ *chanptr_end = static_cast<float*>(data.data);
+ ++chanptr_end;
+ }
+
+ mDevice->renderSamples({mChannelPtrs.get(), chanptr_end}, length);
+
+ for(const auto &data : datas)
+ {
+ data.chunk->offset = 0;
+ data.chunk->stride = sizeof(float);
+ data.chunk->size = length * sizeof(float);
+ }
+ pw_buf->size = length;
+ pw_stream_queue_buffer(mStream.get(), pw_buf);
+}
+
+
+void PipeWirePlayback::open(const char *name)
+{
+ static std::atomic<uint> OpenCount{0};
+
+ uint64_t targetid{PwIdAny};
+ std::string devname{};
+ gEventHandler.waitForInit();
+ if(!name)
+ {
+ EventWatcherLockGuard _{gEventHandler};
+ auto&& devlist = DeviceNode::GetList();
+
+ auto match = devlist.cend();
+ if(!DefaultSinkDevice.empty())
+ {
+ auto match_default = [](const DeviceNode &n) -> bool
+ { return n.mDevName == DefaultSinkDevice; };
+ match = std::find_if(devlist.cbegin(), devlist.cend(), match_default);
+ }
+ if(match == devlist.cend())
+ {
+ auto match_playback = [](const DeviceNode &n) -> bool
+ { return n.mType != NodeType::Source; };
+ match = std::find_if(devlist.cbegin(), devlist.cend(), match_playback);
+ if(match == devlist.cend())
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "No PipeWire playback device found"};
+ }
+
+ targetid = match->mSerial;
+ devname = match->mName;
+ }
+ else
+ {
+ EventWatcherLockGuard _{gEventHandler};
+ auto&& devlist = DeviceNode::GetList();
+
+ auto match_name = [name](const DeviceNode &n) -> bool
+ { return n.mType != NodeType::Source && n.mName == 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", name};
+
+ targetid = match->mSerial;
+ devname = match->mName;
+ }
+
+ if(!mLoop)
+ {
+ const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)};
+ const std::string thread_name{"ALSoftP" + std::to_string(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};
+ if(int res{mLoop.start()})
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to start PipeWire mainloop (res: %d)", res};
+ }
+ MainloopUniqueLock mlock{mLoop};
+ if(!mContext)
+ {
+ pw_properties *cprops{pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr)};
+ mContext = mLoop.newContext(cprops);
+ if(!mContext)
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to create PipeWire event context (errno: %d)\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};
+ }
+ mlock.unlock();
+
+ /* TODO: Ensure the target ID is still valid/usable and accepts streams. */
+
+ mTargetId = targetid;
+ if(!devname.empty())
+ mDevice->DeviceName = std::move(devname);
+ else
+ mDevice->DeviceName = pwireDevice;
+}
+
+bool PipeWirePlayback::reset()
+{
+ if(mStream)
+ {
+ MainloopLockGuard _{mLoop};
+ mStream = nullptr;
+ }
+ mStreamListener = {};
+ mRateMatch = nullptr;
+ mTimeBase = GetDeviceClockTime(mDevice);
+
+ /* If connecting to a specific device, update various device parameters to
+ * match its format.
+ */
+ bool is51rear{false};
+ mDevice->Flags.reset(DirectEar);
+ if(mTargetId != PwIdAny)
+ {
+ EventWatcherLockGuard _{gEventHandler};
+ auto&& devlist = DeviceNode::GetList();
+
+ auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool
+ { return targetid == n.mSerial; };
+ auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id);
+ if(match != devlist.cend())
+ {
+ 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 numbufs{static_cast<double>(mDevice->BufferSize)/mDevice->UpdateSize};
+ mDevice->Frequency = match->mSampleRate;
+ mDevice->UpdateSize = static_cast<uint>(clampd(mDevice->UpdateSize*scale + 0.5,
+ 64.0, 8192.0));
+ mDevice->BufferSize = static_cast<uint>(numbufs*mDevice->UpdateSize + 0.5);
+ }
+ if(!mDevice->Flags.test(ChannelsRequest) && match->mChannels != InvalidChannelConfig)
+ mDevice->FmtChans = match->mChannels;
+ if(match->mChannels == DevFmtStereo && match->mIsHeadphones)
+ mDevice->Flags.set(DirectEar);
+ is51rear = match->mIs51Rear;
+ }
+ }
+ /* 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)};
+
+ /* TODO: How to tell what an appropriate size is? Examples just use this
+ * magic value.
+ */
+ constexpr uint32_t pod_buffer_size{1024};
+ auto pod_buffer = std::make_unique<al::byte[]>(pod_buffer_size);
+ spa_pod_builder b{make_pod_builder(pod_buffer.get(), pod_buffer_size)};
+
+ const spa_pod *params{spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info)};
+ if(!params)
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to set PipeWire audio format parameters"};
+
+ /* TODO: Which properties are actually needed here? Any others that could
+ * be useful?
+ */
+ auto&& binary = GetProcBinary();
+ const char *appname{binary.fname.length() ? 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",
+ PW_KEY_MEDIA_CATEGORY, "Playback",
+ PW_KEY_MEDIA_ROLE, "Game",
+ PW_KEY_NODE_ALWAYS_PROCESS, "true",
+ nullptr)};
+ if(!props)
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to create PipeWire stream properties (errno: %d)", 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);
+#ifdef PW_KEY_TARGET_OBJECT
+ pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId);
+#else
+ pw_properties_setf(props, PW_KEY_NODE_TARGET, "%" PRIu64, mTargetId);
+#endif
+
+ MainloopUniqueLock plock{mLoop};
+ /* The stream takes overship of 'props', even in the case of failure. */
+ 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};
+ 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.c_str(), "pipewire", "rt-mix", true))
+ 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};
+
+ /* Wait for the stream to become paused (ready to start streaming). */
+ plock.wait([stream=mStream.get()]()
+ {
+ const char *error{};
+ 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};
+ 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
+ * of this playback stream to the tail of the device output.
+ *
+ * This info is apparently not available until after the stream starts.
+ */
+ plock.unlock();
+
+ mNumChannels = mDevice->channelsFromFmt();
+ mChannelPtrs = std::make_unique<float*[]>(mNumChannels);
+
+ setDefaultWFXChannelOrder();
+
+ return true;
+}
+
+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};
+
+ /* 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).
+ */
+ plock.wait([stream=mStream.get()]()
+ {
+ const char *error{};
+ 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)"};
+ return state == PW_STREAM_STATE_STREAMING;
+ });
+
+ /* HACK: Try to work out the update size and total buffering size. There's
+ * no actual query for this, so we have to work it out from the stream time
+ * info, and assume it stays accurate with future updates. The stream time
+ * info may also not be available right away, so we have to wait until it
+ * is (up to about 2 seconds).
+ */
+ int wait_count{100};
+ do {
+ 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);
+ break;
+ }
+
+ /* The rate match size is the update size for each buffer. */
+ const uint updatesize{mRateMatch ? mRateMatch->size : 0u};
+#if PW_CHECK_VERSION(0,3,50)
+ /* Assume ptime.avail_buffers+ptime.queued_buffers is the target buffer
+ * queue size.
+ */
+ if(ptime.rate.denom > 0 && (ptime.avail_buffers || ptime.queued_buffers) && updatesize > 0)
+ {
+ 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 *
+ ptime.rate.num / ptime.rate.denom};
+
+ mDevice->UpdateSize = updatesize;
+ mDevice->BufferSize = static_cast<uint>(ptime.buffered + delay +
+ totalbuffers*updatesize);
+ break;
+ }
+#else
+ /* Prior to 0.3.50, we can only measure the delay with the update size,
+ * assuming one buffer and no resample buffering.
+ */
+ 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 *
+ ptime.rate.num / ptime.rate.denom};
+
+ mDevice->UpdateSize = updatesize;
+ mDevice->BufferSize = static_cast<uint>(delay + updatesize);
+ break;
+ }
+#endif
+ if(!--wait_count)
+ break;
+
+ plock.unlock();
+ std::this_thread::sleep_for(milliseconds{20});
+ plock.lock();
+ } while(pw_stream_get_state(mStream.get(), nullptr) == PW_STREAM_STATE_STREAMING);
+}
+
+void PipeWirePlayback::stop()
+{
+ MainloopUniqueLock plock{mLoop};
+ if(int res{pw_stream_set_active(mStream.get(), false)})
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to stop PipeWire stream (res: %d)", res};
+
+ /* Wait for the stream to stop playing. */
+ plock.wait([stream=mStream.get()]()
+ { return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; });
+}
+
+ClockLatency PipeWirePlayback::getClockLatency()
+{
+ /* Given a real-time low-latency output, this is rather complicated to get
+ * accurate timing. So, here we go.
+ */
+
+ /* First, get the stream time info (tick delay, ticks played, and the
+ * CLOCK_MONOTONIC time closest to when that last tick was played).
+ */
+ pw_time ptime{};
+ if(mStream)
+ {
+ MainloopLockGuard _{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);
+ }
+
+ /* Now get the mixer time and the CLOCK_MONOTONIC time atomically (i.e. the
+ * monotonic clock closest to 'now', and the last mixer time at 'now').
+ */
+ nanoseconds mixtime{};
+ timespec tspec{};
+ uint refcount;
+ do {
+ refcount = mDevice->waitForMix();
+ mixtime = GetDeviceClockTime(mDevice);
+ clock_gettime(CLOCK_MONOTONIC, &tspec);
+ std::atomic_thread_fence(std::memory_order_acquire);
+ } while(refcount != ReadRef(mDevice->MixCount));
+
+ /* Convert the monotonic clock, stream ticks, and stream delay to
+ * nanoseconds.
+ */
+ nanoseconds monoclock{seconds{tspec.tv_sec} + nanoseconds{tspec.tv_nsec}};
+ nanoseconds curtic{}, delay{};
+ if(ptime.rate.denom < 1) UNLIKELY
+ {
+ /* If there's no stream rate, the stream hasn't had a chance to get
+ * going and return time info yet. Just use dummy values.
+ */
+ ptime.now = monoclock.count();
+ curtic = mixtime;
+ delay = nanoseconds{seconds{mDevice->BufferSize}} / mDevice->Frequency;
+ }
+ else
+ {
+ /* The stream gets recreated with each reset, so include the time that
+ * had already passed with previous streams.
+ */
+ curtic = mTimeBase;
+ /* More safely scale the ticks to avoid overflowing the pre-division
+ * temporary as it gets larger.
+ */
+ curtic += seconds{ptime.ticks / ptime.rate.denom} * ptime.rate.num;
+ curtic += nanoseconds{seconds{ptime.ticks%ptime.rate.denom} * ptime.rate.num} /
+ ptime.rate.denom;
+
+ /* The delay should be small enough to not worry about overflow. */
+ delay = nanoseconds{seconds{ptime.delay} * ptime.rate.num} / ptime.rate.denom;
+ }
+
+ /* If the mixer time is ahead of the stream time, there's that much more
+ * delay relative to the stream delay.
+ */
+ if(mixtime > curtic)
+ delay += mixtime - curtic;
+ /* Reduce the delay according to how much time has passed since the known
+ * stream time. This isn't 100% accurate since the system monotonic clock
+ * doesn't tick at the exact same rate as the audio device, but it should
+ * be good enough with ptime.now being constantly updated every few
+ * milliseconds with ptime.ticks.
+ */
+ delay -= monoclock - nanoseconds{ptime.now};
+
+ /* Return the mixer time and delay. Clamp the delay to no less than 0,
+ * incase timer drift got that severe.
+ */
+ ClockLatency ret{};
+ ret.ClockTime = mixtime;
+ ret.Latency = std::max(delay, nanoseconds{});
+
+ return ret;
+}
+
+
+class PipeWireCapture final : public BackendBase {
+ void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error);
+ static void stateChangedCallbackC(void *data, pw_stream_state old, pw_stream_state state,
+ const char *error)
+ { static_cast<PipeWireCapture*>(data)->stateChangedCallback(old, state, error); }
+
+ void inputCallback();
+ static void inputCallbackC(void *data)
+ { static_cast<PipeWireCapture*>(data)->inputCallback(); }
+
+ void open(const char *name) override;
+ void start() override;
+ void stop() override;
+ void captureSamples(al::byte *buffer, uint samples) override;
+ uint availableSamples() override;
+
+ uint64_t mTargetId{PwIdAny};
+ ThreadMainloop mLoop;
+ PwContextPtr mContext;
+ PwCorePtr mCore;
+ PwStreamPtr mStream;
+ spa_hook mStreamListener{};
+
+ RingBufferPtr mRing{};
+
+ static constexpr pw_stream_events CreateEvents()
+ {
+ pw_stream_events ret{};
+ ret.version = PW_VERSION_STREAM_EVENTS;
+ ret.state_changed = &PipeWireCapture::stateChangedCallbackC;
+ ret.process = &PipeWireCapture::inputCallbackC;
+ return ret;
+ }
+
+public:
+ PipeWireCapture(DeviceBase *device) noexcept : BackendBase{device} { }
+ ~PipeWireCapture() { if(mLoop) mLoop.stop(); }
+
+ DEF_NEWDEL(PipeWireCapture)
+};
+
+
+void PipeWireCapture::stateChangedCallback(pw_stream_state, pw_stream_state, const char*)
+{ mLoop.signal(false); }
+
+void PipeWireCapture::inputCallback()
+{
+ pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())};
+ if(!pw_buf) UNLIKELY return;
+
+ spa_data *bufdata{pw_buf->buffer->datas};
+ const uint offset{minu(bufdata->chunk->offset, bufdata->maxsize)};
+ const uint size{minu(bufdata->chunk->size, bufdata->maxsize - offset)};
+
+ mRing->write(static_cast<char*>(bufdata->data) + offset, size / mRing->getElemSize());
+
+ pw_stream_queue_buffer(mStream.get(), pw_buf);
+}
+
+
+void PipeWireCapture::open(const char *name)
+{
+ static std::atomic<uint> OpenCount{0};
+
+ uint64_t targetid{PwIdAny};
+ std::string devname{};
+ gEventHandler.waitForInit();
+ if(!name)
+ {
+ EventWatcherLockGuard _{gEventHandler};
+ auto&& devlist = DeviceNode::GetList();
+
+ auto match = devlist.cend();
+ if(!DefaultSourceDevice.empty())
+ {
+ auto match_default = [](const DeviceNode &n) -> bool
+ { return n.mDevName == DefaultSourceDevice; };
+ match = std::find_if(devlist.cbegin(), devlist.cend(), match_default);
+ }
+ if(match == devlist.cend())
+ {
+ auto match_capture = [](const DeviceNode &n) -> bool
+ { return n.mType != NodeType::Sink; };
+ match = std::find_if(devlist.cbegin(), devlist.cend(), match_capture);
+ }
+ if(match == devlist.cend())
+ {
+ match = devlist.cbegin();
+ if(match == devlist.cend())
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "No PipeWire capture device found"};
+ }
+
+ targetid = match->mSerial;
+ if(match->mType != NodeType::Sink) devname = match->mName;
+ else devname = MonitorPrefix+match->mName;
+ }
+ else
+ {
+ EventWatcherLockGuard _{gEventHandler};
+ auto&& devlist = DeviceNode::GetList();
+
+ auto match_name = [name](const DeviceNode &n) -> bool
+ { return n.mType != NodeType::Sink && n.mName == name; };
+ auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name);
+ if(match == devlist.cend() && std::strncmp(name, MonitorPrefix, MonitorPrefixLen) == 0)
+ {
+ const char *sinkname{name + MonitorPrefixLen};
+ auto match_sinkname = [sinkname](const DeviceNode &n) -> bool
+ { return n.mType == NodeType::Sink && n.mName == sinkname; };
+ match = std::find_if(devlist.cbegin(), devlist.cend(), match_sinkname);
+ }
+ if(match == devlist.cend())
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Device name \"%s\" not found", name};
+
+ targetid = match->mSerial;
+ devname = name;
+ }
+
+ if(!mLoop)
+ {
+ const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)};
+ const std::string thread_name{"ALSoftC" + std::to_string(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};
+ if(int res{mLoop.start()})
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to start PipeWire mainloop (res: %d)", res};
+ }
+ MainloopUniqueLock mlock{mLoop};
+ if(!mContext)
+ {
+ pw_properties *cprops{pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr)};
+ mContext = mLoop.newContext(cprops);
+ if(!mContext)
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to create PipeWire event context (errno: %d)\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};
+ }
+ mlock.unlock();
+
+ /* TODO: Ensure the target ID is still valid/usable and accepts streams. */
+
+ mTargetId = targetid;
+ if(!devname.empty())
+ mDevice->DeviceName = std::move(devname);
+ else
+ mDevice->DeviceName = pwireInput;
+
+
+ bool is51rear{false};
+ if(mTargetId != PwIdAny)
+ {
+ EventWatcherLockGuard _{gEventHandler};
+ auto&& devlist = DeviceNode::GetList();
+
+ auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool
+ { return targetid == n.mSerial; };
+ auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id);
+ if(match != devlist.cend())
+ is51rear = match->mIs51Rear;
+ }
+ spa_audio_info_raw info{make_spa_info(mDevice, is51rear, UseDevType)};
+
+ constexpr uint32_t pod_buffer_size{1024};
+ auto pod_buffer = std::make_unique<al::byte[]>(pod_buffer_size);
+ spa_pod_builder b{make_pod_builder(pod_buffer.get(), pod_buffer_size)};
+
+ const spa_pod *params[]{spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info)};
+ if(!params[0])
+ 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"};
+ pw_properties *props{pw_properties_new(
+ PW_KEY_NODE_NAME, appname,
+ PW_KEY_NODE_DESCRIPTION, appname,
+ PW_KEY_MEDIA_TYPE, "Audio",
+ PW_KEY_MEDIA_CATEGORY, "Capture",
+ PW_KEY_MEDIA_ROLE, "Game",
+ PW_KEY_NODE_ALWAYS_PROCESS, "true",
+ nullptr)};
+ if(!props)
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to create PipeWire stream properties (errno: %d)", 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);
+#ifdef PW_KEY_TARGET_OBJECT
+ pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId);
+#else
+ pw_properties_setf(props, PW_KEY_NODE_TARGET, "%" PRIu64, mTargetId);
+#endif
+
+ MainloopUniqueLock plock{mLoop};
+ 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};
+ 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, 1)})
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Error connecting PipeWire stream (res: %d)", res};
+
+ /* Wait for the stream to become paused (ready to start streaming). */
+ plock.wait([stream=mStream.get()]()
+ {
+ const char *error{};
+ 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};
+ return state == PW_STREAM_STATE_PAUSED;
+ });
+ plock.unlock();
+
+ setDefaultWFXChannelOrder();
+
+ /* Ensure at least a 100ms capture buffer. */
+ mRing = RingBuffer::Create(maxu(mDevice->Frequency/10, mDevice->BufferSize),
+ mDevice->frameSizeFromFmt(), false);
+}
+
+
+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};
+
+ plock.wait([stream=mStream.get()]()
+ {
+ const char *error{};
+ 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)"};
+ return state == PW_STREAM_STATE_STREAMING;
+ });
+}
+
+void PipeWireCapture::stop()
+{
+ MainloopUniqueLock plock{mLoop};
+ if(int res{pw_stream_set_active(mStream.get(), false)})
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to stop PipeWire stream (res: %d)", res};
+
+ plock.wait([stream=mStream.get()]()
+ { return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; });
+}
+
+uint PipeWireCapture::availableSamples()
+{ return static_cast<uint>(mRing->readSpace()); }
+
+void PipeWireCapture::captureSamples(al::byte *buffer, uint samples)
+{ mRing->read(buffer, samples); }
+
+} // namespace
+
+
+bool PipeWireBackendFactory::init()
+{
+ if(!pwire_load())
+ return false;
+
+ const char *version{pw_get_library_version()};
+ if(!check_version(version))
+ {
+ WARN("PipeWire version \"%s\" too old (%s or newer required)\n", version,
+ pw_get_headers_version());
+ return false;
+ }
+ TRACE("Found PipeWire version \"%s\" (%s or newer)\n", version, pw_get_headers_version());
+
+ pw_init(0, nullptr);
+ if(!gEventHandler.init())
+ return false;
+
+ if(!GetConfigValueBool(nullptr, "pipewire", "assume-audio", false)
+ && !gEventHandler.waitForAudio())
+ {
+ gEventHandler.kill();
+ /* 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");
+ return false;
+ }
+ return true;
+}
+
+bool PipeWireBackendFactory::querySupport(BackendType type)
+{ return type == BackendType::Playback || type == BackendType::Capture; }
+
+std::string PipeWireBackendFactory::probe(BackendType type)
+{
+ std::string outnames;
+
+ gEventHandler.waitForInit();
+ EventWatcherLockGuard _{gEventHandler};
+ auto&& devlist = DeviceNode::GetList();
+
+ auto match_defsink = [](const DeviceNode &n) -> bool
+ { return n.mDevName == DefaultSinkDevice; };
+ auto match_defsource = [](const DeviceNode &n) -> bool
+ { return n.mDevName == DefaultSourceDevice; };
+
+ auto sort_devnode = [](DeviceNode &lhs, DeviceNode &rhs) noexcept -> bool
+ { return lhs.mId < rhs.mId; };
+ std::sort(devlist.begin(), devlist.end(), sort_devnode);
+
+ auto defmatch = devlist.cbegin();
+ switch(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);
+ }
+ 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);
+ }
+ break;
+ case BackendType::Capture:
+ defmatch = std::find_if(defmatch, devlist.cend(), match_defsource);
+ if(defmatch != devlist.cend())
+ {
+ if(defmatch->mType == NodeType::Sink)
+ outnames.append(MonitorPrefix);
+ outnames.append(defmatch->mName.c_str(), defmatch->mName.length()+1);
+ }
+ for(auto iter = devlist.cbegin();iter != devlist.cend();++iter)
+ {
+ if(iter != defmatch)
+ {
+ if(iter->mType == NodeType::Sink)
+ outnames.append(MonitorPrefix);
+ outnames.append(iter->mName.c_str(), iter->mName.length()+1);
+ }
+ }
+ break;
+ }
+
+ return outnames;
+}
+
+BackendPtr PipeWireBackendFactory::createBackend(DeviceBase *device, BackendType type)
+{
+ if(type == BackendType::Playback)
+ return BackendPtr{new PipeWirePlayback{device}};
+ if(type == BackendType::Capture)
+ return BackendPtr{new PipeWireCapture{device}};
+ return nullptr;
+}
+
+BackendFactory &PipeWireBackendFactory::getFactory()
+{
+ static PipeWireBackendFactory factory{};
+ return factory;
+}
diff --git a/alc/backends/pipewire.h b/alc/backends/pipewire.h
new file mode 100644
index 00000000..5f930239
--- /dev/null
+++ b/alc/backends/pipewire.h
@@ -0,0 +1,23 @@
+#ifndef BACKENDS_PIPEWIRE_H
+#define BACKENDS_PIPEWIRE_H
+
+#include <string>
+
+#include "base.h"
+
+struct DeviceBase;
+
+struct PipeWireBackendFactory 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_PIPEWIRE_H */
diff --git a/alc/backends/portaudio.cpp b/alc/backends/portaudio.cpp
index 1e3d0ce8..9c94587d 100644
--- a/alc/backends/portaudio.cpp
+++ b/alc/backends/portaudio.cpp
@@ -20,16 +20,16 @@
#include "config.h"
-#include "backends/portaudio.h"
+#include "portaudio.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
-#include "alcmain.h"
-#include "alexcpt.h"
-#include "alu.h"
-#include "alconfig.h"
+#include "alc/alconfig.h"
+#include "alnumeric.h"
+#include "core/device.h"
+#include "core/logging.h"
#include "dynload.h"
#include "ringbuffer.h"
@@ -38,7 +38,7 @@
namespace {
-constexpr ALCchar pa_device[] = "PortAudio Default";
+constexpr char pa_device[] = "PortAudio Default";
#ifdef HAVE_DYNLOAD
@@ -72,7 +72,7 @@ MAKE_FUNC(Pa_GetStreamInfo);
struct PortPlayback final : public BackendBase {
- PortPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+ PortPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~PortPlayback() override;
int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
@@ -85,14 +85,14 @@ struct PortPlayback final : public BackendBase {
framesPerBuffer, timeInfo, statusFlags);
}
- void open(const ALCchar *name) override;
+ void open(const char *name) override;
bool reset() override;
- bool start() override;
+ void start() override;
void stop() override;
PaStream *mStream{nullptr};
PaStreamParameters mParams{};
- ALuint mUpdateSize{0u};
+ uint mUpdateSize{0u};
DEF_NEWDEL(PortPlayback)
};
@@ -109,73 +109,79 @@ PortPlayback::~PortPlayback()
int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept
{
- std::lock_guard<PortPlayback> _{*this};
- aluMixData(mDevice, outputBuffer, static_cast<ALuint>(framesPerBuffer));
+ mDevice->renderSamples(outputBuffer, static_cast<uint>(framesPerBuffer),
+ static_cast<uint>(mParams.channelCount));
return 0;
}
-void PortPlayback::open(const ALCchar *name)
+void PortPlayback::open(const char *name)
{
if(!name)
name = pa_device;
else if(strcmp(name, pa_device) != 0)
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
-
- mUpdateSize = mDevice->UpdateSize;
+ throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+ name};
+ PaStreamParameters params{};
auto devidopt = ConfigValueInt(nullptr, "port", "device");
- if(devidopt && *devidopt >= 0) mParams.device = *devidopt;
- else mParams.device = Pa_GetDefaultOutputDevice();
- mParams.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency);
- mParams.hostApiSpecificStreamInfo = nullptr;
+ if(devidopt && *devidopt >= 0) params.device = *devidopt;
+ else params.device = Pa_GetDefaultOutputDevice();
+ params.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency);
+ params.hostApiSpecificStreamInfo = nullptr;
- mParams.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
+ params.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
switch(mDevice->FmtType)
{
case DevFmtByte:
- mParams.sampleFormat = paInt8;
+ params.sampleFormat = paInt8;
break;
case DevFmtUByte:
- mParams.sampleFormat = paUInt8;
+ params.sampleFormat = paUInt8;
break;
case DevFmtUShort:
/* fall-through */
case DevFmtShort:
- mParams.sampleFormat = paInt16;
+ params.sampleFormat = paInt16;
break;
case DevFmtUInt:
/* fall-through */
case DevFmtInt:
- mParams.sampleFormat = paInt32;
+ params.sampleFormat = paInt32;
break;
case DevFmtFloat:
- mParams.sampleFormat = paFloat32;
+ params.sampleFormat = paFloat32;
break;
}
retry_open:
- PaError err{Pa_OpenStream(&mStream, nullptr, &mParams, mDevice->Frequency, mDevice->UpdateSize,
+ PaStream *stream{};
+ PaError err{Pa_OpenStream(&stream, nullptr, &params, mDevice->Frequency, mDevice->UpdateSize,
paNoFlag, &PortPlayback::writeCallbackC, this)};
if(err != paNoError)
{
- if(mParams.sampleFormat == paFloat32)
+ if(params.sampleFormat == paFloat32)
{
- mParams.sampleFormat = paInt16;
+ params.sampleFormat = paInt16;
goto retry_open;
}
- throw al::backend_exception{ALC_INVALID_VALUE, "Failed to open stream: %s",
+ throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
Pa_GetErrorText(err)};
}
+ Pa_CloseStream(mStream);
+ mStream = stream;
+ mParams = params;
+ mUpdateSize = mDevice->UpdateSize;
+
mDevice->DeviceName = name;
}
bool PortPlayback::reset()
{
const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)};
- mDevice->Frequency = static_cast<ALuint>(streamInfo->sampleRate);
+ mDevice->Frequency = static_cast<uint>(streamInfo->sampleRate);
mDevice->UpdateSize = mUpdateSize;
if(mParams.sampleFormat == paInt8)
@@ -194,7 +200,7 @@ bool PortPlayback::reset()
return false;
}
- if(mParams.channelCount == 2)
+ if(mParams.channelCount >= 2)
mDevice->FmtChans = DevFmtStereo;
else if(mParams.channelCount == 1)
mDevice->FmtChans = DevFmtMono;
@@ -203,20 +209,17 @@ bool PortPlayback::reset()
ERR("Unexpected channel count: %u\n", mParams.channelCount);
return false;
}
- SetDefaultChannelOrder(mDevice);
+ setDefaultChannelOrder();
return true;
}
-bool PortPlayback::start()
+void PortPlayback::start()
{
- PaError err{Pa_StartStream(mStream)};
- if(err != paNoError)
- {
- ERR("Pa_StartStream() returned an error: %s\n", Pa_GetErrorText(err));
- return false;
- }
- return true;
+ const PaError err{Pa_StartStream(mStream)};
+ if(err == paNoError)
+ throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: %s",
+ Pa_GetErrorText(err)};
}
void PortPlayback::stop()
@@ -228,7 +231,7 @@ void PortPlayback::stop()
struct PortCapture final : public BackendBase {
- PortCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+ PortCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~PortCapture() override;
int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
@@ -241,11 +244,11 @@ struct PortCapture final : public BackendBase {
framesPerBuffer, timeInfo, statusFlags);
}
- void open(const ALCchar *name) override;
- bool start() override;
+ void open(const char *name) override;
+ void start() override;
void stop() override;
- ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
- ALCuint availableSamples() override;
+ void captureSamples(al::byte *buffer, uint samples) override;
+ uint availableSamples() override;
PaStream *mStream{nullptr};
PaStreamParameters mParams;
@@ -272,18 +275,19 @@ int PortCapture::readCallback(const void *inputBuffer, void*, unsigned long fram
}
-void PortCapture::open(const ALCchar *name)
+void PortCapture::open(const char *name)
{
if(!name)
name = pa_device;
else if(strcmp(name, pa_device) != 0)
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+ name};
- ALuint samples{mDevice->BufferSize};
+ uint samples{mDevice->BufferSize};
samples = maxu(samples, 100 * mDevice->Frequency / 1000);
- ALuint frame_size{mDevice->frameSizeFromFmt()};
+ uint frame_size{mDevice->frameSizeFromFmt()};
- mRing = CreateRingBuffer(samples, frame_size, false);
+ mRing = RingBuffer::Create(samples, frame_size, false);
auto devidopt = ConfigValueInt(nullptr, "port", "capture");
if(devidopt && *devidopt >= 0) mParams.device = *devidopt;
@@ -310,7 +314,7 @@ void PortCapture::open(const ALCchar *name)
break;
case DevFmtUInt:
case DevFmtUShort:
- throw al::backend_exception{ALC_INVALID_VALUE, "%s samples not supported",
+ throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported",
DevFmtTypeString(mDevice->FmtType)};
}
mParams.channelCount = static_cast<int>(mDevice->channelsFromFmt());
@@ -318,22 +322,19 @@ void PortCapture::open(const ALCchar *name)
PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency,
paFramesPerBufferUnspecified, paNoFlag, &PortCapture::readCallbackC, this)};
if(err != paNoError)
- throw al::backend_exception{ALC_INVALID_VALUE, "Failed to open stream: %s",
+ throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
Pa_GetErrorText(err)};
mDevice->DeviceName = name;
}
-bool PortCapture::start()
+void PortCapture::start()
{
- PaError err{Pa_StartStream(mStream)};
+ const PaError err{Pa_StartStream(mStream)};
if(err != paNoError)
- {
- ERR("Error starting stream: %s\n", Pa_GetErrorText(err));
- return false;
- }
- return true;
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to start recording: %s", Pa_GetErrorText(err)};
}
void PortCapture::stop()
@@ -344,14 +345,11 @@ void PortCapture::stop()
}
-ALCuint PortCapture::availableSamples()
-{ return static_cast<ALCuint>(mRing->readSpace()); }
+uint PortCapture::availableSamples()
+{ return static_cast<uint>(mRing->readSpace()); }
-ALCenum PortCapture::captureSamples(al::byte *buffer, ALCuint samples)
-{
- mRing->read(buffer, samples);
- return ALC_NO_ERROR;
-}
+void PortCapture::captureSamples(al::byte *buffer, uint samples)
+{ mRing->read(buffer, samples); }
} // namespace
@@ -419,19 +417,21 @@ bool PortBackendFactory::init()
bool PortBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
-void PortBackendFactory::probe(DevProbe type, std::string *outnames)
+std::string PortBackendFactory::probe(BackendType type)
{
+ std::string outnames;
switch(type)
{
- case DevProbe::Playback:
- case DevProbe::Capture:
- /* Includes null char. */
- outnames->append(pa_device, sizeof(pa_device));
- break;
+ case BackendType::Playback:
+ case BackendType::Capture:
+ /* Includes null char. */
+ outnames.append(pa_device, sizeof(pa_device));
+ break;
}
+ return outnames;
}
-BackendPtr PortBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr PortBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new PortPlayback{device}};
diff --git a/alc/backends/portaudio.h b/alc/backends/portaudio.h
index 082e9020..c35ccff2 100644
--- a/alc/backends/portaudio.h
+++ b/alc/backends/portaudio.h
@@ -1,7 +1,7 @@
#ifndef BACKENDS_PORTAUDIO_H
#define BACKENDS_PORTAUDIO_H
-#include "backends/base.h"
+#include "base.h"
struct PortBackendFactory final : public BackendFactory {
public:
@@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
- void probe(DevProbe type, std::string *outnames) override;
+ std::string probe(BackendType type) override;
- BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+ BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
diff --git a/alc/backends/pulseaudio.cpp b/alc/backends/pulseaudio.cpp
index 4e46460b..4b0e316f 100644
--- a/alc/backends/pulseaudio.cpp
+++ b/alc/backends/pulseaudio.cpp
@@ -21,41 +21,45 @@
#include "config.h"
-#include "backends/pulseaudio.h"
-
-#include <poll.h>
-#include <cstring>
+#include "pulseaudio.h"
+#include <algorithm>
#include <array>
-#include <string>
-#include <vector>
#include <atomic>
-#include <thread>
-#include <algorithm>
-#include <functional>
-#include <condition_variable>
-
-#include "alcmain.h"
-#include "alu.h"
-#include "alconfig.h"
-#include "alexcpt.h"
-#include "compat.h"
+#include <bitset>
+#include <chrono>
+#include <cstring>
+#include <limits>
+#include <mutex>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string>
+#include <sys/types.h>
+#include <utility>
+
+#include "albyte.h"
+#include "alc/alconfig.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "aloptional.h"
+#include "alspan.h"
+#include "core/devformat.h"
+#include "core/device.h"
+#include "core/logging.h"
#include "dynload.h"
+#include "opthelpers.h"
#include "strutils.h"
+#include "vector.h"
#include <pulse/pulseaudio.h>
namespace {
+using uint = unsigned int;
+
#ifdef HAVE_DYNLOAD
#define PULSE_FUNCS(MAGIC) \
- MAGIC(pa_mainloop_new); \
- MAGIC(pa_mainloop_free); \
- MAGIC(pa_mainloop_set_poll_func); \
- MAGIC(pa_mainloop_run); \
- MAGIC(pa_mainloop_quit); \
- MAGIC(pa_mainloop_get_api); \
MAGIC(pa_context_new); \
MAGIC(pa_context_unref); \
MAGIC(pa_context_get_state); \
@@ -96,11 +100,21 @@ namespace {
MAGIC(pa_stream_disconnect); \
MAGIC(pa_stream_set_buffer_attr_callback); \
MAGIC(pa_stream_begin_write); \
+ MAGIC(pa_threaded_mainloop_free); \
+ MAGIC(pa_threaded_mainloop_get_api); \
+ MAGIC(pa_threaded_mainloop_lock); \
+ MAGIC(pa_threaded_mainloop_new); \
+ MAGIC(pa_threaded_mainloop_signal); \
+ MAGIC(pa_threaded_mainloop_start); \
+ MAGIC(pa_threaded_mainloop_stop); \
+ MAGIC(pa_threaded_mainloop_unlock); \
+ MAGIC(pa_threaded_mainloop_wait); \
MAGIC(pa_channel_map_init_auto); \
MAGIC(pa_channel_map_parse); \
MAGIC(pa_channel_map_snprint); \
MAGIC(pa_channel_map_equal); \
MAGIC(pa_channel_map_superset); \
+ MAGIC(pa_channel_position_to_string); \
MAGIC(pa_operation_get_state); \
MAGIC(pa_operation_unref); \
MAGIC(pa_sample_spec_valid); \
@@ -117,12 +131,6 @@ PULSE_FUNCS(MAKE_FUNC)
#undef MAKE_FUNC
#ifndef IN_IDE_PARSER
-#define pa_mainloop_new ppa_mainloop_new
-#define pa_mainloop_free ppa_mainloop_free
-#define pa_mainloop_set_poll_func ppa_mainloop_set_poll_func
-#define pa_mainloop_run ppa_mainloop_run
-#define pa_mainloop_quit ppa_mainloop_quit
-#define pa_mainloop_get_api ppa_mainloop_get_api
#define pa_context_new ppa_context_new
#define pa_context_unref ppa_context_unref
#define pa_context_get_state ppa_context_get_state
@@ -158,12 +166,22 @@ PULSE_FUNCS(MAKE_FUNC)
#define pa_stream_get_device_name ppa_stream_get_device_name
#define pa_stream_get_latency ppa_stream_get_latency
#define pa_stream_set_buffer_attr_callback ppa_stream_set_buffer_attr_callback
-#define pa_stream_begin_write ppa_stream_begin_write*/
+#define pa_stream_begin_write ppa_stream_begin_write
+#define pa_threaded_mainloop_free ppa_threaded_mainloop_free
+#define pa_threaded_mainloop_get_api ppa_threaded_mainloop_get_api
+#define pa_threaded_mainloop_lock ppa_threaded_mainloop_lock
+#define pa_threaded_mainloop_new ppa_threaded_mainloop_new
+#define pa_threaded_mainloop_signal ppa_threaded_mainloop_signal
+#define pa_threaded_mainloop_start ppa_threaded_mainloop_start
+#define pa_threaded_mainloop_stop ppa_threaded_mainloop_stop
+#define pa_threaded_mainloop_unlock ppa_threaded_mainloop_unlock
+#define pa_threaded_mainloop_wait ppa_threaded_mainloop_wait
#define pa_channel_map_init_auto ppa_channel_map_init_auto
#define pa_channel_map_parse ppa_channel_map_parse
#define pa_channel_map_snprint ppa_channel_map_snprint
#define pa_channel_map_equal ppa_channel_map_equal
#define pa_channel_map_superset ppa_channel_map_superset
+#define pa_channel_position_to_string ppa_channel_position_to_string
#define pa_operation_get_state ppa_operation_get_state
#define pa_operation_unref ppa_operation_unref
#define pa_sample_spec_valid ppa_sample_spec_valid
@@ -216,211 +234,221 @@ constexpr pa_channel_map MonoChanMap{
PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT,
PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT
}
-};
-
-size_t ChannelFromPulse(pa_channel_position_t chan)
-{
- switch(chan)
- {
- case PA_CHANNEL_POSITION_INVALID: break;
- case PA_CHANNEL_POSITION_MONO: return FrontCenter;
- case PA_CHANNEL_POSITION_FRONT_LEFT: return FrontLeft;
- case PA_CHANNEL_POSITION_FRONT_RIGHT: return FrontRight;
- case PA_CHANNEL_POSITION_FRONT_CENTER: return FrontCenter;
- case PA_CHANNEL_POSITION_REAR_CENTER: return BackCenter;
- case PA_CHANNEL_POSITION_REAR_LEFT: return BackLeft;
- case PA_CHANNEL_POSITION_REAR_RIGHT: return BackRight;
- case PA_CHANNEL_POSITION_LFE: return LFE;
- case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: break;
- case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: break;
- case PA_CHANNEL_POSITION_SIDE_LEFT: return SideLeft;
- case PA_CHANNEL_POSITION_SIDE_RIGHT: return SideRight;
- case PA_CHANNEL_POSITION_AUX0: return Aux0;
- case PA_CHANNEL_POSITION_AUX1: return Aux1;
- case PA_CHANNEL_POSITION_AUX2: return Aux2;
- case PA_CHANNEL_POSITION_AUX3: return Aux3;
- case PA_CHANNEL_POSITION_AUX4: return Aux4;
- case PA_CHANNEL_POSITION_AUX5: return Aux5;
- case PA_CHANNEL_POSITION_AUX6: return Aux6;
- case PA_CHANNEL_POSITION_AUX7: return Aux7;
- case PA_CHANNEL_POSITION_AUX8: return Aux8;
- case PA_CHANNEL_POSITION_AUX9: return Aux9;
- case PA_CHANNEL_POSITION_AUX10: return Aux10;
- case PA_CHANNEL_POSITION_AUX11: return Aux11;
- case PA_CHANNEL_POSITION_AUX12: return Aux12;
- case PA_CHANNEL_POSITION_AUX13: return Aux13;
- case PA_CHANNEL_POSITION_AUX14: return Aux14;
- case PA_CHANNEL_POSITION_AUX15: return Aux15;
- case PA_CHANNEL_POSITION_AUX16: break;
- case PA_CHANNEL_POSITION_AUX17: break;
- case PA_CHANNEL_POSITION_AUX18: break;
- case PA_CHANNEL_POSITION_AUX19: break;
- case PA_CHANNEL_POSITION_AUX20: break;
- case PA_CHANNEL_POSITION_AUX21: break;
- case PA_CHANNEL_POSITION_AUX22: break;
- case PA_CHANNEL_POSITION_AUX23: break;
- case PA_CHANNEL_POSITION_AUX24: break;
- case PA_CHANNEL_POSITION_AUX25: break;
- case PA_CHANNEL_POSITION_AUX26: break;
- case PA_CHANNEL_POSITION_AUX27: break;
- case PA_CHANNEL_POSITION_AUX28: break;
- case PA_CHANNEL_POSITION_AUX29: break;
- case PA_CHANNEL_POSITION_AUX30: break;
- case PA_CHANNEL_POSITION_AUX31: break;
- case PA_CHANNEL_POSITION_TOP_CENTER: break;
- case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return UpperFrontLeft;
- case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return UpperFrontRight;
- case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: break;
- case PA_CHANNEL_POSITION_TOP_REAR_LEFT: return UpperBackLeft;
- case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return UpperBackRight;
- case PA_CHANNEL_POSITION_TOP_REAR_CENTER: break;
- case PA_CHANNEL_POSITION_MAX: break;
+}, X714ChanMap{
+ 12, {
+ PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
+ PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE,
+ PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT,
+ PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT,
+ PA_CHANNEL_POSITION_TOP_FRONT_LEFT, PA_CHANNEL_POSITION_TOP_FRONT_RIGHT,
+ PA_CHANNEL_POSITION_TOP_REAR_LEFT, PA_CHANNEL_POSITION_TOP_REAR_RIGHT
}
- throw al::backend_exception{ALC_INVALID_VALUE, "Unexpected channel enum %d", chan};
-}
-
-void SetChannelOrderFromMap(ALCdevice *device, const pa_channel_map &chanmap)
-{
- device->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX);
- for(ALuint i{0};i < chanmap.channels;++i)
- device->RealOut.ChannelIndex[ChannelFromPulse(chanmap.map[i])] = i;
-}
+};
/* *grumble* Don't use enums for bitflags. */
-constexpr inline pa_stream_flags_t operator|(pa_stream_flags_t lhs, pa_stream_flags_t rhs)
-{ return pa_stream_flags_t(int(lhs) | int(rhs)); }
-inline pa_stream_flags_t& operator|=(pa_stream_flags_t &lhs, pa_stream_flags_t rhs)
+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)
{
lhs = lhs | rhs;
return lhs;
}
-inline pa_stream_flags_t& operator&=(pa_stream_flags_t &lhs, int rhs)
+constexpr pa_stream_flags_t operator~(pa_stream_flags_t flag)
+{ return pa_stream_flags_t(~al::to_underlying(flag)); }
+constexpr pa_stream_flags_t& operator&=(pa_stream_flags_t &lhs, pa_stream_flags_t rhs)
{
- lhs = pa_stream_flags_t(int(lhs) & rhs);
+ lhs = pa_stream_flags_t(al::to_underlying(lhs) & rhs);
return lhs;
}
-inline pa_context_flags_t& operator|=(pa_context_flags_t &lhs, pa_context_flags_t rhs)
+constexpr pa_context_flags_t operator|(pa_context_flags_t lhs, pa_context_flags_t rhs)
+{ return pa_context_flags_t(lhs | al::to_underlying(rhs)); }
+constexpr pa_context_flags_t& operator|=(pa_context_flags_t &lhs, pa_context_flags_t rhs)
{
- lhs = pa_context_flags_t(int(lhs) | int(rhs));
+ lhs = lhs | rhs;
return lhs;
}
-/* Global flags and properties */
-pa_context_flags_t pulse_ctx_flags;
+struct DevMap {
+ std::string name;
+ std::string device_name;
+};
-int pulse_poll_func(struct pollfd *ufds, unsigned long nfds, int timeout, void *userdata) noexcept
+bool checkName(const al::span<const DevMap> list, const std::string &name)
{
- auto plock = static_cast<std::unique_lock<std::mutex>*>(userdata);
- plock->unlock();
- int r{poll(ufds, nfds, timeout)};
- plock->lock();
- return r;
+ auto match_name = [&name](const DevMap &entry) -> bool { return entry.name == name; };
+ return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
}
+al::vector<DevMap> PlaybackDevices;
+al::vector<DevMap> CaptureDevices;
+
+
+/* Global flags and properties */
+pa_context_flags_t pulse_ctx_flags;
+
class PulseMainloop {
- std::thread mThread;
- std::mutex mMutex;
- std::condition_variable mCondVar;
- pa_mainloop *mMainloop{nullptr};
+ pa_threaded_mainloop *mLoop{};
public:
- ~PulseMainloop()
+ PulseMainloop() = default;
+ PulseMainloop(const PulseMainloop&) = delete;
+ PulseMainloop(PulseMainloop&& rhs) noexcept : mLoop{rhs.mLoop} { rhs.mLoop = nullptr; }
+ explicit PulseMainloop(pa_threaded_mainloop *loop) noexcept : mLoop{loop} { }
+ ~PulseMainloop() { if(mLoop) pa_threaded_mainloop_free(mLoop); }
+
+ PulseMainloop& operator=(const PulseMainloop&) = delete;
+ PulseMainloop& operator=(PulseMainloop&& rhs) noexcept
+ { std::swap(mLoop, rhs.mLoop); return *this; }
+ PulseMainloop& operator=(std::nullptr_t) noexcept
{
- if(mThread.joinable())
- {
- pa_mainloop_quit(mMainloop, 0);
- mThread.join();
- }
+ if(mLoop)
+ pa_threaded_mainloop_free(mLoop);
+ mLoop = nullptr;
+ return *this;
}
- int mainloop_thread()
- {
- SetRTPriority();
+ explicit operator bool() const noexcept { return mLoop != nullptr; }
- std::unique_lock<std::mutex> plock{mMutex};
- mMainloop = pa_mainloop_new();
+ auto start() const { return pa_threaded_mainloop_start(mLoop); }
+ auto stop() const { return pa_threaded_mainloop_stop(mLoop); }
- pa_mainloop_set_poll_func(mMainloop, pulse_poll_func, &plock);
- mCondVar.notify_all();
+ auto getApi() const { return pa_threaded_mainloop_get_api(mLoop); }
- int ret{};
- pa_mainloop_run(mMainloop, &ret);
+ auto lock() const { return pa_threaded_mainloop_lock(mLoop); }
+ auto unlock() const { return pa_threaded_mainloop_unlock(mLoop); }
- pa_mainloop_free(mMainloop);
- mMainloop = nullptr;
+ auto signal(bool wait=false) const { return pa_threaded_mainloop_signal(mLoop, wait); }
- return ret;
- }
+ static auto Create() { return PulseMainloop{pa_threaded_mainloop_new()}; }
- void doLock() { mMutex.lock(); }
- void doUnlock() { mMutex.unlock(); }
- std::unique_lock<std::mutex> getLock() { return std::unique_lock<std::mutex>{mMutex}; }
- std::condition_variable &getCondVar() noexcept { return mCondVar; }
- void contextStateCallback(pa_context *context) noexcept
+ void streamSuccessCallback(pa_stream*, int) noexcept { signal(); }
+ static void streamSuccessCallbackC(pa_stream *stream, int success, void *pdata) noexcept
+ { static_cast<PulseMainloop*>(pdata)->streamSuccessCallback(stream, success); }
+
+ void close(pa_context *context, pa_stream *stream=nullptr);
+
+
+ void deviceSinkCallback(pa_context*, const pa_sink_info *info, int eol) noexcept
{
- pa_context_state_t state{pa_context_get_state(context)};
- if(state == PA_CONTEXT_READY || !PA_CONTEXT_IS_GOOD(state))
- mCondVar.notify_all();
+ if(eol)
+ {
+ signal();
+ return;
+ }
+
+ /* Skip this device is if it's already in the list. */
+ auto match_devname = [info](const DevMap &entry) -> bool
+ { return entry.device_name == info->name; };
+ if(std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), match_devname) != PlaybackDevices.cend())
+ return;
+
+ /* Make sure the display name (description) is unique. Append a number
+ * counter as needed.
+ */
+ int count{1};
+ std::string newname{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();
+
+ TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str());
}
- static void contextStateCallbackC(pa_context *context, void *pdata) noexcept
- { static_cast<PulseMainloop*>(pdata)->contextStateCallback(context); }
- void streamStateCallback(pa_stream *stream) noexcept
+ void deviceSourceCallback(pa_context*, const pa_source_info *info, int eol) noexcept
{
- pa_stream_state_t state{pa_stream_get_state(stream)};
- if(state == PA_STREAM_READY || !PA_STREAM_IS_GOOD(state))
- mCondVar.notify_all();
+ if(eol)
+ {
+ signal();
+ return;
+ }
+
+ /* Skip this device is if it's already in the list. */
+ auto match_devname = [info](const DevMap &entry) -> bool
+ { return entry.device_name == info->name; };
+ if(std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), match_devname) != CaptureDevices.cend())
+ return;
+
+ /* Make sure the display name (description) is unique. Append a number
+ * counter as needed.
+ */
+ int count{1};
+ std::string newname{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();
+
+ TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str());
}
- static void streamStateCallbackC(pa_stream *stream, void *pdata) noexcept
- { static_cast<PulseMainloop*>(pdata)->streamStateCallback(stream); }
- void streamSuccessCallback(pa_stream*, int) noexcept
- { mCondVar.notify_all(); }
- static void streamSuccessCallbackC(pa_stream *stream, int success, void *pdata) noexcept
- { static_cast<PulseMainloop*>(pdata)->streamSuccessCallback(stream, success); }
+ void probePlaybackDevices();
+ void probeCaptureDevices();
+
+ friend struct MainloopUniqueLock;
+};
+struct MainloopUniqueLock : public std::unique_lock<PulseMainloop> {
+ using std::unique_lock<PulseMainloop>::unique_lock;
+ MainloopUniqueLock& operator=(MainloopUniqueLock&&) = default;
+
+ auto wait() const -> void
+ { pa_threaded_mainloop_wait(mutex()->mLoop); }
- void waitForOperation(pa_operation *op, std::unique_lock<std::mutex> &plock)
+ template<typename Predicate>
+ auto wait(Predicate done_waiting) const -> void
+ { while(!done_waiting()) wait(); }
+
+ void waitForOperation(pa_operation *op)
{
if(op)
{
- while(pa_operation_get_state(op) == PA_OPERATION_RUNNING)
- mCondVar.wait(plock);
+ wait([op]{ return pa_operation_get_state(op) != PA_OPERATION_RUNNING; });
pa_operation_unref(op);
}
}
- pa_context *connectContext(std::unique_lock<std::mutex> &plock);
- pa_stream *connectStream(const char *device_name, std::unique_lock<std::mutex> &plock,
- pa_context *context, pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec,
- pa_channel_map *chanmap, BackendType type);
+ void contextStateCallback(pa_context *context) noexcept
+ {
+ pa_context_state_t state{pa_context_get_state(context)};
+ if(state == PA_CONTEXT_READY || !PA_CONTEXT_IS_GOOD(state))
+ mutex()->signal();
+ }
- void close(pa_context *context, pa_stream *stream);
+ void streamStateCallback(pa_stream *stream) noexcept
+ {
+ pa_stream_state_t state{pa_stream_get_state(stream)};
+ if(state == PA_STREAM_READY || !PA_STREAM_IS_GOOD(state))
+ mutex()->signal();
+ }
+
+ pa_context *connectContext();
+ pa_stream *connectStream(const char *device_name, pa_context *context, pa_stream_flags_t flags,
+ pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type);
};
+using MainloopLockGuard = std::lock_guard<PulseMainloop>;
-pa_context *PulseMainloop::connectContext(std::unique_lock<std::mutex> &plock)
+pa_context *MainloopUniqueLock::connectContext()
{
- const char *name{"OpenAL Soft"};
+ pa_context *context{pa_context_new(mutex()->getApi(), nullptr)};
+ if(!context) throw al::backend_exception{al::backend_error::OutOfMemory,
+ "pa_context_new() failed"};
- const PathNamePair &binname = GetProcBinary();
- if(!binname.fname.empty())
- name = binname.fname.c_str();
-
- if(!mMainloop)
- {
- mThread = std::thread{std::mem_fn(&PulseMainloop::mainloop_thread), this};
- while(!mMainloop) mCondVar.wait(plock);
- }
-
- pa_context *context{pa_context_new(pa_mainloop_get_api(mMainloop), name)};
- if(!context) throw al::backend_exception{ALC_OUT_OF_MEMORY, "pa_context_new() failed"};
-
- pa_context_set_state_callback(context, &contextStateCallbackC, this);
+ pa_context_set_state_callback(context, [](pa_context *ctx, void *pdata) noexcept
+ { return static_cast<MainloopUniqueLock*>(pdata)->contextStateCallback(ctx); }, this);
int err;
if((err=pa_context_connect(context, nullptr, pulse_ctx_flags, nullptr)) >= 0)
@@ -435,7 +463,7 @@ pa_context *PulseMainloop::connectContext(std::unique_lock<std::mutex> &plock)
break;
}
- mCondVar.wait(plock);
+ wait();
}
}
pa_context_set_state_callback(context, nullptr, nullptr);
@@ -443,24 +471,25 @@ pa_context *PulseMainloop::connectContext(std::unique_lock<std::mutex> &plock)
if(err < 0)
{
pa_context_unref(context);
- throw al::backend_exception{ALC_INVALID_VALUE, "Context did not connect (%s)",
+ throw al::backend_exception{al::backend_error::DeviceError, "Context did not connect (%s)",
pa_strerror(err)};
}
return context;
}
-pa_stream *PulseMainloop::connectStream(const char *device_name,
- std::unique_lock<std::mutex> &plock, pa_context *context, pa_stream_flags_t flags,
- pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type)
+pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_context *context,
+ pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap,
+ BackendType type)
{
const char *stream_id{(type==BackendType::Playback) ? "Playback Stream" : "Capture Stream"};
pa_stream *stream{pa_stream_new(context, stream_id, spec, chanmap)};
if(!stream)
- throw al::backend_exception{ALC_OUT_OF_MEMORY, "pa_stream_new() failed (%s)",
+ throw al::backend_exception{al::backend_error::OutOfMemory, "pa_stream_new() failed (%s)",
pa_strerror(pa_context_errno(context))};
- pa_stream_set_state_callback(stream, &streamStateCallbackC, this);
+ pa_stream_set_state_callback(stream, [](pa_stream *strm, void *pdata) noexcept
+ { return static_cast<MainloopUniqueLock*>(pdata)->streamStateCallback(strm); }, this);
int err{(type==BackendType::Playback) ?
pa_stream_connect_playback(stream, device_name, attr, flags, nullptr, nullptr) :
@@ -468,8 +497,8 @@ pa_stream *PulseMainloop::connectStream(const char *device_name,
if(err < 0)
{
pa_stream_unref(stream);
- throw al::backend_exception{ALC_INVALID_VALUE, "%s did not connect (%s)", stream_id,
- pa_strerror(err)};
+ throw al::backend_exception{al::backend_error::DeviceError, "%s did not connect (%s)",
+ stream_id, pa_strerror(err)};
}
pa_stream_state_t state;
@@ -479,11 +508,11 @@ pa_stream *PulseMainloop::connectStream(const char *device_name,
{
err = pa_context_errno(context);
pa_stream_unref(stream);
- throw al::backend_exception{ALC_INVALID_VALUE, "%s did not get ready (%s)", stream_id,
- pa_strerror(err)};
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "%s did not get ready (%s)", stream_id, pa_strerror(err)};
}
- mCondVar.wait(plock);
+ wait();
}
pa_stream_set_state_callback(stream, nullptr, nullptr);
@@ -492,7 +521,7 @@ pa_stream *PulseMainloop::connectStream(const char *device_name,
void PulseMainloop::close(pa_context *context, pa_stream *stream)
{
- std::lock_guard<std::mutex> _{mMutex};
+ MainloopUniqueLock _{*this};
if(stream)
{
pa_stream_set_state_callback(stream, nullptr, nullptr);
@@ -508,88 +537,22 @@ void PulseMainloop::close(pa_context *context, pa_stream *stream)
}
-/* Used for initial connection test and enumeration. */
-PulseMainloop gGlobalMainloop;
-
-
-struct DevMap {
- std::string name;
- std::string device_name;
-};
-
-bool checkName(const al::vector<DevMap> &list, const std::string &name)
-{
- auto match_name = [&name](const DevMap &entry) -> bool { return entry.name == name; };
- return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
-}
-
-al::vector<DevMap> PlaybackDevices;
-al::vector<DevMap> CaptureDevices;
-
-
-void device_sink_callback(pa_context*, const pa_sink_info *info, int eol, void *pdata) noexcept
-{
- if(eol)
- {
- static_cast<PulseMainloop*>(pdata)->getCondVar().notify_all();
- return;
- }
-
- /* Skip this device is if it's already in the list. */
- if(std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
- [info](const DevMap &entry) -> bool
- { return entry.device_name == info->name; }
- ) != PlaybackDevices.cend())
- return;
-
- /* Make sure the display name (description) is unique. Append a number
- * counter as needed.
- */
- int count{1};
- std::string newname{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();
-
- TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str());
-}
-
-void probePlaybackDevices(PulseMainloop &mainloop)
+void PulseMainloop::probePlaybackDevices()
{
pa_context *context{};
- pa_stream *stream{};
PlaybackDevices.clear();
try {
- auto plock = mainloop.getLock();
+ 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); };
- context = mainloop.connectContext(plock);
-
- constexpr pa_stream_flags_t flags{PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE |
- PA_STREAM_FIX_CHANNELS | PA_STREAM_DONT_MOVE | PA_STREAM_START_CORKED};
-
- pa_sample_spec spec{};
- spec.format = PA_SAMPLE_S16NE;
- spec.rate = 44100;
- spec.channels = 2;
-
- stream = mainloop.connectStream(nullptr, plock, context, flags, nullptr, &spec, nullptr,
- BackendType::Playback);
- pa_operation *op{pa_context_get_sink_info_by_name(context,
- pa_stream_get_device_name(stream), device_sink_callback, &mainloop)};
- mainloop.waitForOperation(op, plock);
-
- pa_stream_disconnect(stream);
- pa_stream_unref(stream);
- stream = nullptr;
+ context = plock.connectContext();
+ pa_operation *op{pa_context_get_sink_info_by_name(context, nullptr, sink_callback, this)};
+ plock.waitForOperation(op);
- op = pa_context_get_sink_info_list(context, device_sink_callback, &mainloop);
- mainloop.waitForOperation(op, plock);
+ op = pa_context_get_sink_info_list(context, sink_callback, this);
+ plock.waitForOperation(op);
pa_context_disconnect(context);
pa_context_unref(context);
@@ -597,74 +560,26 @@ void probePlaybackDevices(PulseMainloop &mainloop)
}
catch(std::exception &e) {
ERR("Error enumerating devices: %s\n", e.what());
- if(context) mainloop.close(context, stream);
+ if(context) close(context);
}
}
-
-void device_source_callback(pa_context*, const pa_source_info *info, int eol, void *pdata) noexcept
-{
- if(eol)
- {
- static_cast<PulseMainloop*>(pdata)->getCondVar().notify_all();
- return;
- }
-
- /* Skip this device is if it's already in the list. */
- if(std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
- [info](const DevMap &entry) -> bool
- { return entry.device_name == info->name; }
- ) != CaptureDevices.cend())
- return;
-
- /* Make sure the display name (description) is unique. Append a number
- * counter as needed.
- */
- int count{1};
- std::string newname{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();
-
- TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str());
-}
-
-void probeCaptureDevices(PulseMainloop &mainloop)
+void PulseMainloop::probeCaptureDevices()
{
pa_context *context{};
- pa_stream *stream{};
CaptureDevices.clear();
try {
- auto plock = mainloop.getLock();
-
- context = mainloop.connectContext(plock);
-
- constexpr pa_stream_flags_t flags{PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE |
- PA_STREAM_FIX_CHANNELS | PA_STREAM_DONT_MOVE | PA_STREAM_START_CORKED};
-
- pa_sample_spec spec{};
- spec.format = PA_SAMPLE_S16NE;
- spec.rate = 44100;
- spec.channels = 1;
-
- stream = mainloop.connectStream(nullptr, plock, context, flags, nullptr, &spec, nullptr,
- BackendType::Capture);
- pa_operation *op{pa_context_get_source_info_by_name(context,
- pa_stream_get_device_name(stream), device_source_callback, &mainloop)};
- mainloop.waitForOperation(op, plock);
+ 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_stream_disconnect(stream);
- pa_stream_unref(stream);
- stream = nullptr;
+ context = plock.connectContext();
+ pa_operation *op{pa_context_get_source_info_by_name(context, nullptr, src_callback, this)};
+ plock.waitForOperation(op);
- op = pa_context_get_source_info_list(context, device_source_callback, &mainloop);
- mainloop.waitForOperation(op, plock);
+ op = pa_context_get_source_info_list(context, src_callback, this);
+ plock.waitForOperation(op);
pa_context_disconnect(context);
pa_context_unref(context);
@@ -672,58 +587,44 @@ void probeCaptureDevices(PulseMainloop &mainloop)
}
catch(std::exception &e) {
ERR("Error enumerating devices: %s\n", e.what());
- if(context) mainloop.close(context, stream);
+ if(context) close(context);
}
}
+/* Used for initial connection test and enumeration. */
+PulseMainloop gGlobalMainloop;
+
+
struct PulsePlayback final : public BackendBase {
- PulsePlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+ PulsePlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~PulsePlayback() override;
void bufferAttrCallback(pa_stream *stream) noexcept;
- static void bufferAttrCallbackC(pa_stream *stream, void *pdata) noexcept
- { static_cast<PulsePlayback*>(pdata)->bufferAttrCallback(stream); }
-
void streamStateCallback(pa_stream *stream) noexcept;
- static void streamStateCallbackC(pa_stream *stream, void *pdata) noexcept
- { static_cast<PulsePlayback*>(pdata)->streamStateCallback(stream); }
-
void streamWriteCallback(pa_stream *stream, size_t nbytes) noexcept;
- static void streamWriteCallbackC(pa_stream *stream, size_t nbytes, void *pdata) noexcept
- { static_cast<PulsePlayback*>(pdata)->streamWriteCallback(stream, nbytes); }
-
void sinkInfoCallback(pa_context *context, const pa_sink_info *info, int eol) noexcept;
- static void sinkInfoCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept
- { static_cast<PulsePlayback*>(pdata)->sinkInfoCallback(context, info, eol); }
-
void sinkNameCallback(pa_context *context, const pa_sink_info *info, int eol) noexcept;
- static void sinkNameCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept
- { static_cast<PulsePlayback*>(pdata)->sinkNameCallback(context, info, eol); }
-
void streamMovedCallback(pa_stream *stream) noexcept;
- static void streamMovedCallbackC(pa_stream *stream, void *pdata) noexcept
- { static_cast<PulsePlayback*>(pdata)->streamMovedCallback(stream); }
- void open(const ALCchar *name) override;
+ void open(const char *name) override;
bool reset() override;
- bool start() override;
+ void start() override;
void stop() override;
ClockLatency getClockLatency() override;
- void lock() override { mMainloop.doLock(); }
- void unlock() override { mMainloop.doUnlock(); }
PulseMainloop mMainloop;
- std::string mDeviceName;
+ al::optional<std::string> mDeviceName{al::nullopt};
+ bool mIs51Rear{false};
pa_buffer_attr mAttr;
pa_sample_spec mSpec;
pa_stream *mStream{nullptr};
pa_context *mContext{nullptr};
- ALuint mFrameSize{0u};
+ uint mFrameSize{0u};
DEF_NEWDEL(PulsePlayback)
};
@@ -755,19 +656,33 @@ void PulsePlayback::streamStateCallback(pa_stream *stream) noexcept
if(pa_stream_get_state(stream) == PA_STREAM_FAILED)
{
ERR("Received stream failure!\n");
- aluHandleDisconnect(mDevice, "Playback stream failure");
+ mDevice->handleDisconnect("Playback stream failure");
}
- mMainloop.getCondVar().notify_all();
+ mMainloop.signal();
}
void PulsePlayback::streamWriteCallback(pa_stream *stream, size_t nbytes) noexcept
{
- void *buf{pa_xmalloc(nbytes)};
- aluMixData(mDevice, buf, static_cast<ALuint>(nbytes/mFrameSize));
+ do {
+ pa_free_cb_t free_func{nullptr};
+ auto buflen = static_cast<size_t>(-1);
+ void *buf{};
+ if(pa_stream_begin_write(stream, &buf, &buflen) || !buf) UNLIKELY
+ {
+ buflen = nbytes;
+ buf = pa_xmalloc(buflen);
+ free_func = pa_xfree;
+ }
+ else
+ buflen = minz(buflen, nbytes);
+ nbytes -= buflen;
+
+ mDevice->renderSamples(buf, static_cast<uint>(buflen/mFrameSize), mSpec.channels);
- int ret{pa_stream_write(stream, buf, nbytes, pa_xfree, 0, PA_SEEK_RELATIVE)};
- if UNLIKELY(ret != PA_OK)
- ERR("Failed to write to stream: %d, %s\n", ret, pa_strerror(ret));
+ 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));
+ } while(nbytes > 0);
}
void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int eol) noexcept
@@ -775,20 +690,22 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int
struct ChannelMap {
DevFmtChannels fmt;
pa_channel_map map;
+ bool is_51rear;
};
- static constexpr std::array<ChannelMap,7> chanmaps{{
- { DevFmtX71, X71ChanMap },
- { DevFmtX61, X61ChanMap },
- { DevFmtX51, X51ChanMap },
- { DevFmtX51Rear, X51RearChanMap },
- { DevFmtQuad, QuadChanMap },
- { DevFmtStereo, StereoChanMap },
- { DevFmtMono, MonoChanMap }
+ static constexpr std::array<ChannelMap,8> chanmaps{{
+ { DevFmtX714, X714ChanMap, false },
+ { DevFmtX71, X71ChanMap, false },
+ { DevFmtX61, X61ChanMap, false },
+ { DevFmtX51, X51ChanMap, false },
+ { DevFmtX51, X51RearChanMap, true },
+ { DevFmtQuad, QuadChanMap, false },
+ { DevFmtStereo, StereoChanMap, false },
+ { DevFmtMono, MonoChanMap, false }
}};
if(eol)
{
- mMainloop.getCondVar().notify_all();
+ mMainloop.signal();
return;
}
@@ -798,11 +715,13 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int
);
if(chaniter != chanmaps.cend())
{
- if(!mDevice->Flags.get<ChannelsRequest>())
+ if(!mDevice->Flags.test(ChannelsRequest))
mDevice->FmtChans = chaniter->fmt;
+ mIs51Rear = chaniter->is_51rear;
}
else
{
+ mIs51Rear = false;
char chanmap_str[PA_CHANNEL_MAP_SNPRINT_MAX]{};
pa_channel_map_snprint(chanmap_str, sizeof(chanmap_str), &info->channel_map);
WARN("Failed to find format for channel map:\n %s\n", chanmap_str);
@@ -810,15 +729,15 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int
if(info->active_port)
TRACE("Active port: %s (%s)\n", info->active_port->name, info->active_port->description);
- mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo &&
- info->active_port && strcmp(info->active_port->name, "analog-output-headphones") == 0);
+ mDevice->Flags.set(DirectEar, (info->active_port
+ && strcmp(info->active_port->name, "analog-output-headphones") == 0));
}
void PulsePlayback::sinkNameCallback(pa_context*, const pa_sink_info *info, int eol) noexcept
{
if(eol)
{
- mMainloop.getCondVar().notify_all();
+ mMainloop.signal();
return;
}
mDevice->DeviceName = info->description;
@@ -827,37 +746,37 @@ void PulsePlayback::sinkNameCallback(pa_context*, const pa_sink_info *info, int
void PulsePlayback::streamMovedCallback(pa_stream *stream) noexcept
{
mDeviceName = pa_stream_get_device_name(stream);
- TRACE("Stream moved to %s\n", mDeviceName.c_str());
+ TRACE("Stream moved to %s\n", mDeviceName->c_str());
}
-void PulsePlayback::open(const ALCchar *name)
+void PulsePlayback::open(const char *name)
{
+ mMainloop = PulseMainloop::Create();
+ mMainloop.start();
+
const char *pulse_name{nullptr};
const char *dev_name{nullptr};
-
if(name)
{
if(PlaybackDevices.empty())
- probePlaybackDevices(mMainloop);
+ mMainloop.probePlaybackDevices();
auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
- [name](const DevMap &entry) -> bool
- { return entry.name == name; }
- );
+ [name](const DevMap &entry) -> bool { return entry.name == name; });
if(iter == PlaybackDevices.cend())
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Device name \"%s\" not found", name};
pulse_name = iter->device_name.c_str();
dev_name = iter->name.c_str();
}
- auto plock = mMainloop.getLock();
-
- mContext = mMainloop.connectContext(plock);
+ MainloopUniqueLock plock{mMainloop};
+ mContext = plock.connectContext();
pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE |
PA_STREAM_FIX_CHANNELS};
- if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", 1))
+ if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", true))
flags |= PA_STREAM_DONT_MOVE;
pa_sample_spec spec{};
@@ -871,18 +790,22 @@ void PulsePlayback::open(const ALCchar *name)
if(defname) pulse_name = defname->c_str();
}
TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)");
- mStream = mMainloop.connectStream(pulse_name, plock, mContext, flags, nullptr, &spec, nullptr,
+ mStream = plock.connectStream(pulse_name, mContext, flags, nullptr, &spec, nullptr,
BackendType::Playback);
- pa_stream_set_moved_callback(mStream, &PulsePlayback::streamMovedCallbackC, this);
- mFrameSize = static_cast<ALuint>(pa_frame_size(pa_stream_get_sample_spec(mStream)));
+ pa_stream_set_moved_callback(mStream, [](pa_stream *stream, void *pdata) noexcept
+ { return static_cast<PulsePlayback*>(pdata)->streamMovedCallback(stream); }, this);
+ mFrameSize = static_cast<uint>(pa_frame_size(pa_stream_get_sample_spec(mStream)));
- mDeviceName = pa_stream_get_device_name(mStream);
+ if(pulse_name) mDeviceName.emplace(pulse_name);
+ else mDeviceName.reset();
if(!dev_name)
{
- pa_operation *op{pa_context_get_sink_info_by_name(mContext, mDeviceName.c_str(),
- &PulsePlayback::sinkNameCallbackC, this)};
- mMainloop.waitForOperation(op, plock);
+ 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(mContext,
+ pa_stream_get_device_name(mStream), name_callback, this)};
+ plock.waitForOperation(op);
}
else
mDevice->DeviceName = dev_name;
@@ -890,7 +813,8 @@ void PulsePlayback::open(const ALCchar *name)
bool PulsePlayback::reset()
{
- auto plock = mMainloop.getLock();
+ MainloopUniqueLock plock{mMainloop};
+ const auto deviceName = mDeviceName ? mDeviceName->c_str() : nullptr;
if(mStream)
{
@@ -903,15 +827,16 @@ bool PulsePlayback::reset()
mStream = nullptr;
}
- pa_operation *op{pa_context_get_sink_info_by_name(mContext, mDeviceName.c_str(),
- &PulsePlayback::sinkInfoCallbackC, this)};
- mMainloop.waitForOperation(op, plock);
+ auto info_callback = [](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(mContext, deviceName, info_callback, 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(nullptr, "pulse", "allow-moves", 1))
+ if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", true))
flags |= PA_STREAM_DONT_MOVE;
- if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "adjust-latency", 0))
+ if(GetConfigValueBool(mDevice->DeviceName.c_str(), "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,
@@ -920,8 +845,8 @@ bool PulsePlayback::reset()
flags &= ~PA_STREAM_EARLY_REQUESTS;
flags |= PA_STREAM_ADJUST_LATENCY;
}
- if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "fix-rate", 0) ||
- !mDevice->Flags.get<FrequencyRequest>())
+ if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "fix-rate", false)
+ || !mDevice->Flags.test(FrequencyRequest))
flags |= PA_STREAM_FIX_RATE;
pa_channel_map chanmap{};
@@ -940,19 +865,20 @@ bool PulsePlayback::reset()
chanmap = QuadChanMap;
break;
case DevFmtX51:
- chanmap = X51ChanMap;
- break;
- case DevFmtX51Rear:
- chanmap = X51RearChanMap;
+ chanmap = (mIs51Rear ? X51RearChanMap : X51ChanMap);
break;
case DevFmtX61:
chanmap = X61ChanMap;
break;
case DevFmtX71:
+ case DevFmtX3D71:
chanmap = X71ChanMap;
break;
+ case DevFmtX714:
+ chanmap = X714ChanMap;
+ break;
}
- SetChannelOrderFromMap(mDevice, chanmap);
+ setDefaultWFXChannelOrder();
switch(mDevice->FmtType)
{
@@ -981,23 +907,25 @@ bool PulsePlayback::reset()
mSpec.rate = mDevice->Frequency;
mSpec.channels = static_cast<uint8_t>(mDevice->channelsFromFmt());
if(pa_sample_spec_valid(&mSpec) == 0)
- throw al::backend_exception{ALC_INVALID_VALUE, "Invalid sample spec"};
+ throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample spec"};
- const ALuint frame_size{static_cast<ALuint>(pa_frame_size(&mSpec))};
+ const auto frame_size = static_cast<uint>(pa_frame_size(&mSpec));
mAttr.maxlength = ~0u;
mAttr.tlength = mDevice->BufferSize * frame_size;
mAttr.prebuf = 0u;
mAttr.minreq = mDevice->UpdateSize * frame_size;
mAttr.fragsize = ~0u;
- mStream = mMainloop.connectStream(mDeviceName.c_str(), plock, mContext, flags, &mAttr, &mSpec,
- &chanmap, BackendType::Playback);
+ mStream = plock.connectStream(deviceName, mContext, flags, &mAttr, &mSpec, &chanmap,
+ BackendType::Playback);
- pa_stream_set_state_callback(mStream, &PulsePlayback::streamStateCallbackC, this);
- pa_stream_set_moved_callback(mStream, &PulsePlayback::streamMovedCallbackC, this);
+ 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);
mSpec = *(pa_stream_get_sample_spec(mStream));
- mFrameSize = static_cast<ALuint>(pa_frame_size(&mSpec));
+ mFrameSize = static_cast<uint>(pa_frame_size(&mSpec));
if(mDevice->Frequency != mSpec.rate)
{
@@ -1005,10 +933,10 @@ bool PulsePlayback::reset()
* accordingly.
*/
const auto scale = static_cast<double>(mSpec.rate) / mDevice->Frequency;
- const ALuint perlen{static_cast<ALuint>(clampd(scale*mDevice->UpdateSize + 0.5, 64.0,
- 8192.0))};
- const ALuint buflen{static_cast<ALuint>(clampd(scale*mDevice->BufferSize + 0.5, perlen*2,
- std::numeric_limits<int>::max()/mFrameSize))};
+ const auto perlen = static_cast<uint>(clampd(scale*mDevice->UpdateSize + 0.5, 64.0,
+ 8192.0));
+ const auto buflen = static_cast<uint>(clampd(scale*mDevice->BufferSize + 0.5, perlen*2,
+ std::numeric_limits<int>::max()/mFrameSize));
mAttr.maxlength = ~0u;
mAttr.tlength = buflen * mFrameSize;
@@ -1017,12 +945,14 @@ bool PulsePlayback::reset()
op = pa_stream_set_buffer_attr(mStream, &mAttr, &PulseMainloop::streamSuccessCallbackC,
&mMainloop);
- mMainloop.waitForOperation(op, plock);
+ plock.waitForOperation(op);
mDevice->Frequency = mSpec.rate;
}
- pa_stream_set_buffer_attr_callback(mStream, &PulsePlayback::bufferAttrCallbackC, this);
+ 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;
@@ -1031,25 +961,35 @@ bool PulsePlayback::reset()
return true;
}
-bool PulsePlayback::start()
+void PulsePlayback::start()
{
- auto plock = mMainloop.getLock();
+ MainloopUniqueLock plock{mMainloop};
+
+ /* Write some samples to fill the buffer before we start feeding it newly
+ * mixed samples.
+ */
+ if(size_t todo{pa_stream_writable_size(mStream)})
+ {
+ void *buf{pa_xmalloc(todo)};
+ mDevice->renderSamples(buf, static_cast<uint>(todo/mFrameSize), mSpec.channels);
+ pa_stream_write(mStream, buf, todo, pa_xfree, 0, PA_SEEK_RELATIVE);
+ }
- pa_stream_set_write_callback(mStream, &PulsePlayback::streamWriteCallbackC, this);
+ pa_stream_set_write_callback(mStream, [](pa_stream *stream, size_t nbytes, void *pdata)noexcept
+ { return static_cast<PulsePlayback*>(pdata)->streamWriteCallback(stream, nbytes); }, this);
pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC,
&mMainloop)};
- mMainloop.waitForOperation(op, plock);
- return true;
+ plock.waitForOperation(op);
}
void PulsePlayback::stop()
{
- auto plock = mMainloop.getLock();
+ MainloopUniqueLock plock{mMainloop};
pa_operation *op{pa_stream_cork(mStream, 1, &PulseMainloop::streamSuccessCallbackC,
&mMainloop)};
- mMainloop.waitForOperation(op, plock);
+ plock.waitForOperation(op);
pa_stream_set_write_callback(mStream, nullptr, nullptr);
}
@@ -1061,23 +1001,23 @@ ClockLatency PulsePlayback::getClockLatency()
int neg, err;
{
- auto _ = mMainloop.getLock();
+ MainloopUniqueLock plock{mMainloop};
ret.ClockTime = GetDeviceClockTime(mDevice);
err = pa_stream_get_latency(mStream, &latency, &neg);
}
- if UNLIKELY(err != 0)
+ if(err != 0) UNLIKELY
{
- /* FIXME: if err = -PA_ERR_NODATA, it means we were called too soon
- * after starting the stream and no timing info has been received from
- * the server yet. Should we wait, possibly stalling the app, or give a
- * dummy value? Either way, it shouldn't be 0. */
+ /* If err = -PA_ERR_NODATA, it means we were called too soon after
+ * starting the stream and no timing info has been received from the
+ * 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 = 0;
+ latency = mDevice->BufferSize - mDevice->UpdateSize;
neg = 0;
}
- else if UNLIKELY(neg)
+ else if(neg) UNLIKELY
latency = 0;
ret.Latency = std::chrono::microseconds{latency};
@@ -1086,39 +1026,30 @@ ClockLatency PulsePlayback::getClockLatency()
struct PulseCapture final : public BackendBase {
- PulseCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+ PulseCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~PulseCapture() override;
void streamStateCallback(pa_stream *stream) noexcept;
- static void streamStateCallbackC(pa_stream *stream, void *pdata) noexcept
- { static_cast<PulseCapture*>(pdata)->streamStateCallback(stream); }
-
void sourceNameCallback(pa_context *context, const pa_source_info *info, int eol) noexcept;
- static void sourceNameCallbackC(pa_context *context, const pa_source_info *info, int eol, void *pdata) noexcept
- { static_cast<PulseCapture*>(pdata)->sourceNameCallback(context, info, eol); }
-
void streamMovedCallback(pa_stream *stream) noexcept;
- static void streamMovedCallbackC(pa_stream *stream, void *pdata) noexcept
- { static_cast<PulseCapture*>(pdata)->streamMovedCallback(stream); }
- void open(const ALCchar *name) override;
- bool start() override;
+ void open(const char *name) override;
+ void start() override;
void stop() override;
- ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
- ALCuint availableSamples() override;
+ void captureSamples(al::byte *buffer, uint samples) override;
+ uint availableSamples() override;
ClockLatency getClockLatency() override;
- void lock() override { mMainloop.doLock(); }
- void unlock() override { mMainloop.doUnlock(); }
PulseMainloop mMainloop;
- std::string mDeviceName;
-
- ALCuint mLastReadable{0u};
- al::byte mSilentVal{};
+ al::optional<std::string> mDeviceName{al::nullopt};
al::span<const al::byte> mCapBuffer;
- ssize_t mCapLen{0};
+ size_t mHoleLength{0};
+ size_t mPacketLength{0};
+
+ uint mLastReadable{0u};
+ al::byte mSilentVal{};
pa_buffer_attr mAttr{};
pa_sample_spec mSpec{};
@@ -1145,16 +1076,16 @@ void PulseCapture::streamStateCallback(pa_stream *stream) noexcept
if(pa_stream_get_state(stream) == PA_STREAM_FAILED)
{
ERR("Received stream failure!\n");
- aluHandleDisconnect(mDevice, "Capture stream failure");
+ mDevice->handleDisconnect("Capture stream failure");
}
- mMainloop.getCondVar().notify_all();
+ mMainloop.signal();
}
void PulseCapture::sourceNameCallback(pa_context*, const pa_source_info *info, int eol) noexcept
{
if(eol)
{
- mMainloop.getCondVar().notify_all();
+ mMainloop.signal();
return;
}
mDevice->DeviceName = info->description;
@@ -1163,31 +1094,35 @@ void PulseCapture::sourceNameCallback(pa_context*, const pa_source_info *info, i
void PulseCapture::streamMovedCallback(pa_stream *stream) noexcept
{
mDeviceName = pa_stream_get_device_name(stream);
- TRACE("Stream moved to %s\n", mDeviceName.c_str());
+ TRACE("Stream moved to %s\n", mDeviceName->c_str());
}
-void PulseCapture::open(const ALCchar *name)
+void PulseCapture::open(const char *name)
{
+ if(!mMainloop)
+ {
+ mMainloop = PulseMainloop::Create();
+ mMainloop.start();
+ }
+
const char *pulse_name{nullptr};
if(name)
{
if(CaptureDevices.empty())
- probeCaptureDevices(mMainloop);
+ mMainloop.probeCaptureDevices();
auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
- [name](const DevMap &entry) -> bool
- { return entry.name == name; }
- );
+ [name](const DevMap &entry) -> bool { return entry.name == name; });
if(iter == CaptureDevices.cend())
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Device name \"%s\" not found", name};
pulse_name = iter->device_name.c_str();
mDevice->DeviceName = iter->name;
}
- auto plock = mMainloop.getLock();
-
- mContext = mMainloop.connectContext(plock);
+ MainloopUniqueLock plock{mMainloop};
+ mContext = plock.connectContext();
pa_channel_map chanmap{};
switch(mDevice->FmtChans)
@@ -1204,20 +1139,21 @@ void PulseCapture::open(const ALCchar *name)
case DevFmtX51:
chanmap = X51ChanMap;
break;
- case DevFmtX51Rear:
- chanmap = X51RearChanMap;
- break;
case DevFmtX61:
chanmap = X61ChanMap;
break;
case DevFmtX71:
chanmap = X71ChanMap;
break;
+ case DevFmtX714:
+ chanmap = X714ChanMap;
+ break;
+ case DevFmtX3D71:
case DevFmtAmbi3D:
- throw al::backend_exception{ALC_INVALID_VALUE, "%s capture not supported",
+ throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
DevFmtChannelsString(mDevice->FmtChans)};
}
- SetChannelOrderFromMap(mDevice, chanmap);
+ setDefaultWFXChannelOrder();
switch(mDevice->FmtType)
{
@@ -1237,16 +1173,16 @@ void PulseCapture::open(const ALCchar *name)
case DevFmtByte:
case DevFmtUShort:
case DevFmtUInt:
- throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported",
- DevFmtTypeString(mDevice->FmtType)};
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
}
mSpec.rate = mDevice->Frequency;
mSpec.channels = static_cast<uint8_t>(mDevice->channelsFromFmt());
if(pa_sample_spec_valid(&mSpec) == 0)
- throw al::backend_exception{ALC_INVALID_VALUE, "Invalid sample format"};
+ throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample format"};
- const ALuint frame_size{static_cast<ALuint>(pa_frame_size(&mSpec))};
- const ALuint samples{maxu(mDevice->BufferSize, 100 * mDevice->Frequency / 1000)};
+ const auto frame_size = static_cast<uint>(pa_frame_size(&mSpec));
+ const uint samples{maxu(mDevice->BufferSize, 100 * mDevice->Frequency / 1000)};
mAttr.minreq = ~0u;
mAttr.prebuf = ~0u;
mAttr.maxlength = samples * frame_size;
@@ -1254,127 +1190,143 @@ void PulseCapture::open(const ALCchar *name)
mAttr.fragsize = minu(samples, 50*mDevice->Frequency/1000) * frame_size;
pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY};
- if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", 1))
+ if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", true))
flags |= PA_STREAM_DONT_MOVE;
TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)");
- mStream = mMainloop.connectStream(pulse_name, plock, mContext, flags, &mAttr, &mSpec, &chanmap,
+ mStream = plock.connectStream(pulse_name, mContext, flags, &mAttr, &mSpec, &chanmap,
BackendType::Capture);
- pa_stream_set_moved_callback(mStream, &PulseCapture::streamMovedCallbackC, this);
- pa_stream_set_state_callback(mStream, &PulseCapture::streamStateCallbackC, this);
+ 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);
- mDeviceName = pa_stream_get_device_name(mStream);
+ if(pulse_name) mDeviceName.emplace(pulse_name);
+ else mDeviceName.reset();
if(mDevice->DeviceName.empty())
{
- pa_operation *op{pa_context_get_source_info_by_name(mContext, mDeviceName.c_str(),
- &PulseCapture::sourceNameCallbackC, this)};
- mMainloop.waitForOperation(op, plock);
+ 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(mContext,
+ pa_stream_get_device_name(mStream), name_callback, this)};
+ plock.waitForOperation(op);
}
}
-bool PulseCapture::start()
+void PulseCapture::start()
{
- auto plock = mMainloop.getLock();
+ MainloopUniqueLock plock{mMainloop};
pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC,
&mMainloop)};
- mMainloop.waitForOperation(op, plock);
- return true;
+ plock.waitForOperation(op);
}
void PulseCapture::stop()
{
- auto plock = mMainloop.getLock();
+ MainloopUniqueLock plock{mMainloop};
pa_operation *op{pa_stream_cork(mStream, 1, &PulseMainloop::streamSuccessCallbackC,
&mMainloop)};
- mMainloop.waitForOperation(op, plock);
+ plock.waitForOperation(op);
}
-ALCenum PulseCapture::captureSamples(al::byte *buffer, ALCuint samples)
+void PulseCapture::captureSamples(al::byte *buffer, uint samples)
{
al::span<al::byte> dstbuf{buffer, samples * pa_frame_size(&mSpec)};
/* Capture is done in fragment-sized chunks, so we loop until we get all
- * that's available */
- mLastReadable -= static_cast<ALCuint>(dstbuf.size());
+ * that's available.
+ */
+ mLastReadable -= static_cast<uint>(dstbuf.size());
while(!dstbuf.empty())
{
+ if(mHoleLength > 0) UNLIKELY
+ {
+ const size_t rem{minz(dstbuf.size(), mHoleLength)};
+ std::fill_n(dstbuf.begin(), rem, mSilentVal);
+ dstbuf = dstbuf.subspan(rem);
+ mHoleLength -= rem;
+
+ continue;
+ }
if(!mCapBuffer.empty())
{
const size_t rem{minz(dstbuf.size(), mCapBuffer.size())};
- if UNLIKELY(mCapLen < 0)
- std::fill_n(dstbuf.begin(), rem, mSilentVal);
- else
- std::copy_n(mCapBuffer.begin(), rem, dstbuf.begin());
+ std::copy_n(mCapBuffer.begin(), rem, dstbuf.begin());
dstbuf = dstbuf.subspan(rem);
mCapBuffer = mCapBuffer.subspan(rem);
continue;
}
- if UNLIKELY(!mDevice->Connected.load(std::memory_order_acquire))
+ if(!mDevice->Connected.load(std::memory_order_acquire)) UNLIKELY
break;
- auto plock = mMainloop.getLock();
- if(mCapLen != 0)
+ MainloopUniqueLock plock{mMainloop};
+ if(mPacketLength > 0)
{
pa_stream_drop(mStream);
- mCapBuffer = {};
- mCapLen = 0;
+ mPacketLength = 0;
}
+
const pa_stream_state_t state{pa_stream_get_state(mStream)};
- if UNLIKELY(!PA_STREAM_IS_GOOD(state))
+ if(!PA_STREAM_IS_GOOD(state)) UNLIKELY
{
- aluHandleDisconnect(mDevice, "Bad capture state: %u", state);
+ mDevice->handleDisconnect("Bad capture state: %u", state);
break;
}
+
const void *capbuf;
size_t caplen;
- if UNLIKELY(pa_stream_peek(mStream, &capbuf, &caplen) < 0)
+ if(pa_stream_peek(mStream, &capbuf, &caplen) < 0) UNLIKELY
{
- aluHandleDisconnect(mDevice, "Failed retrieving capture samples: %s",
+ mDevice->handleDisconnect("Failed retrieving capture samples: %s",
pa_strerror(pa_context_errno(mContext)));
break;
}
plock.unlock();
if(caplen == 0) break;
- if UNLIKELY(!capbuf)
- mCapLen = -static_cast<ssize_t>(caplen);
+ if(!capbuf) UNLIKELY
+ mHoleLength = caplen;
else
- mCapLen = static_cast<ssize_t>(caplen);
- mCapBuffer = {static_cast<const al::byte*>(capbuf), caplen};
+ mCapBuffer = {static_cast<const al::byte*>(capbuf), caplen};
+ mPacketLength = caplen;
}
if(!dstbuf.empty())
std::fill(dstbuf.begin(), dstbuf.end(), mSilentVal);
-
- return ALC_NO_ERROR;
}
-ALCuint PulseCapture::availableSamples()
+uint PulseCapture::availableSamples()
{
- size_t readable{mCapBuffer.size()};
+ size_t readable{maxz(mCapBuffer.size(), mHoleLength)};
if(mDevice->Connected.load(std::memory_order_acquire))
{
- auto _ = mMainloop.getLock();
+ MainloopUniqueLock plock{mMainloop};
size_t got{pa_stream_readable_size(mStream)};
- if UNLIKELY(static_cast<ssize_t>(got) < 0)
+ 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);
- aluHandleDisconnect(mDevice, "Failed getting readable size: %s", err);
+ mDevice->handleDisconnect("Failed getting readable size: %s", err);
}
else
{
- const auto caplen = static_cast<size_t>(std::abs(mCapLen));
- if(got > caplen) readable += got - caplen;
+ /* "readable" is the number of bytes from the last packet that have
+ * not yet been read by the caller. So add the stream's readable
+ * size excluding the last packet (the stream size includes the
+ * last packet until it's dropped).
+ */
+ if(got > mPacketLength)
+ readable += got - mPacketLength;
}
}
- readable = std::min<size_t>(readable, std::numeric_limits<ALCuint>::max());
- mLastReadable = std::max(mLastReadable, static_cast<ALCuint>(readable));
- return mLastReadable / static_cast<ALCuint>(pa_frame_size(&mSpec));
+ /* Avoid uint overflow, and avoid decreasing the readable count. */
+ readable = std::min<size_t>(readable, std::numeric_limits<uint>::max());
+ mLastReadable = std::max(mLastReadable, static_cast<uint>(readable));
+ return mLastReadable / static_cast<uint>(pa_frame_size(&mSpec));
}
@@ -1385,18 +1337,18 @@ ClockLatency PulseCapture::getClockLatency()
int neg, err;
{
- auto _ = mMainloop.getLock();
+ MainloopUniqueLock plock{mMainloop};
ret.ClockTime = GetDeviceClockTime(mDevice);
err = pa_stream_get_latency(mStream, &latency, &neg);
}
- if UNLIKELY(err != 0)
+ if(err != 0) UNLIKELY
{
ERR("Failed to get stream latency: 0x%x\n", err);
latency = 0;
neg = 0;
}
- else if UNLIKELY(neg)
+ else if(neg) UNLIKELY
latency = 0;
ret.Latency = std::chrono::microseconds{latency};
@@ -1449,12 +1401,18 @@ bool PulseBackendFactory::init()
#endif /* HAVE_DYNLOAD */
pulse_ctx_flags = PA_CONTEXT_NOFLAGS;
- if(!GetConfigValueBool(nullptr, "pulse", "spawn-server", 1))
+ if(!GetConfigValueBool(nullptr, "pulse", "spawn-server", false))
pulse_ctx_flags |= PA_CONTEXT_NOAUTOSPAWN;
try {
- auto plock = gGlobalMainloop.getLock();
- pa_context *context{gGlobalMainloop.connectContext(plock)};
+ if(!gGlobalMainloop)
+ {
+ gGlobalMainloop = PulseMainloop::Create();
+ gGlobalMainloop.start();
+ }
+
+ MainloopUniqueLock plock{gGlobalMainloop};
+ pa_context *context{plock.connectContext()};
pa_context_disconnect(context);
pa_context_unref(context);
return true;
@@ -1467,31 +1425,35 @@ bool PulseBackendFactory::init()
bool PulseBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
-void PulseBackendFactory::probe(DevProbe type, std::string *outnames)
+std::string PulseBackendFactory::probe(BackendType type)
{
- auto add_device = [outnames](const DevMap &entry) -> void
+ 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.append(entry.name.c_str(), entry.name.length()+1);
};
switch(type)
{
- case DevProbe::Playback:
- probePlaybackDevices(gGlobalMainloop);
+ case BackendType::Playback:
+ gGlobalMainloop.probePlaybackDevices();
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
- case DevProbe::Capture:
- probeCaptureDevices(gGlobalMainloop);
+ case BackendType::Capture:
+ gGlobalMainloop.probeCaptureDevices();
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}
+
+ return outnames;
}
-BackendPtr PulseBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr PulseBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new PulsePlayback{device}};
diff --git a/alc/backends/pulseaudio.h b/alc/backends/pulseaudio.h
index 40f3e305..6690fe8a 100644
--- a/alc/backends/pulseaudio.h
+++ b/alc/backends/pulseaudio.h
@@ -1,7 +1,7 @@
#ifndef BACKENDS_PULSEAUDIO_H
#define BACKENDS_PULSEAUDIO_H
-#include "backends/base.h"
+#include "base.h"
class PulseBackendFactory final : public BackendFactory {
public:
@@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
- void probe(DevProbe type, std::string *outnames) override;
+ std::string probe(BackendType type) override;
- BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+ BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
diff --git a/alc/backends/qsa.cpp b/alc/backends/qsa.cpp
deleted file mode 100644
index 5ed65798..00000000
--- a/alc/backends/qsa.cpp
+++ /dev/null
@@ -1,963 +0,0 @@
-/**
- * OpenAL cross platform audio library
- * Copyright (C) 2011-2013 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 "backends/qsa.h"
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <sched.h>
-#include <errno.h>
-#include <memory.h>
-#include <poll.h>
-
-#include <thread>
-#include <memory>
-#include <algorithm>
-
-#include "alcmain.h"
-#include "alexcpt.h"
-#include "alu.h"
-#include "threads.h"
-
-#include <sys/asoundlib.h>
-#include <sys/neutrino.h>
-
-
-namespace {
-
-struct qsa_data {
- snd_pcm_t* pcmHandle{nullptr};
- int audio_fd{-1};
-
- snd_pcm_channel_setup_t csetup{};
- snd_pcm_channel_params_t cparams{};
-
- ALvoid* buffer{nullptr};
- ALsizei size{0};
-
- std::atomic<ALenum> mKillNow{AL_TRUE};
- std::thread mThread;
-};
-
-struct DevMap {
- ALCchar* name;
- int card;
- int dev;
-};
-
-al::vector<DevMap> DeviceNameMap;
-al::vector<DevMap> CaptureNameMap;
-
-constexpr ALCchar qsaDevice[] = "QSA Default";
-
-constexpr struct {
- int32_t format;
-} formatlist[] = {
- {SND_PCM_SFMT_FLOAT_LE},
- {SND_PCM_SFMT_S32_LE},
- {SND_PCM_SFMT_U32_LE},
- {SND_PCM_SFMT_S16_LE},
- {SND_PCM_SFMT_U16_LE},
- {SND_PCM_SFMT_S8},
- {SND_PCM_SFMT_U8},
- {0},
-};
-
-constexpr struct {
- int32_t rate;
-} ratelist[] = {
- {192000},
- {176400},
- {96000},
- {88200},
- {48000},
- {44100},
- {32000},
- {24000},
- {22050},
- {16000},
- {12000},
- {11025},
- {8000},
- {0},
-};
-
-constexpr struct {
- int32_t channels;
-} channellist[] = {
- {8},
- {7},
- {6},
- {4},
- {2},
- {1},
- {0},
-};
-
-void deviceList(int type, al::vector<DevMap> *devmap)
-{
- snd_ctl_t* handle;
- snd_pcm_info_t pcminfo;
- int max_cards, card, err, dev;
- DevMap entry;
- char name[1024];
- snd_ctl_hw_info info;
-
- max_cards = snd_cards();
- if(max_cards < 0)
- return;
-
- std::for_each(devmap->begin(), devmap->end(),
- [](const DevMap &entry) -> void
- { free(entry.name); }
- );
- devmap->clear();
-
- entry.name = strdup(qsaDevice);
- entry.card = 0;
- entry.dev = 0;
- devmap->push_back(entry);
-
- for(card = 0;card < max_cards;card++)
- {
- if((err=snd_ctl_open(&handle, card)) < 0)
- continue;
-
- if((err=snd_ctl_hw_info(handle, &info)) < 0)
- {
- snd_ctl_close(handle);
- continue;
- }
-
- for(dev = 0;dev < (int)info.pcmdevs;dev++)
- {
- if((err=snd_ctl_pcm_info(handle, dev, &pcminfo)) < 0)
- continue;
-
- if((type==SND_PCM_CHANNEL_PLAYBACK && (pcminfo.flags&SND_PCM_INFO_PLAYBACK)) ||
- (type==SND_PCM_CHANNEL_CAPTURE && (pcminfo.flags&SND_PCM_INFO_CAPTURE)))
- {
- snprintf(name, sizeof(name), "%s [%s] (hw:%d,%d)", info.name, pcminfo.name, card, dev);
- entry.name = strdup(name);
- entry.card = card;
- entry.dev = dev;
-
- devmap->push_back(entry);
- TRACE("Got device \"%s\", card %d, dev %d\n", name, card, dev);
- }
- }
- snd_ctl_close(handle);
- }
-}
-
-
-/* Wrappers to use an old-style backend with the new interface. */
-struct PlaybackWrapper final : public BackendBase {
- PlaybackWrapper(ALCdevice *device) noexcept : BackendBase{device} { }
- ~PlaybackWrapper() override;
-
- void open(const ALCchar *name) override;
- bool reset() override;
- bool start() override;
- void stop() override;
-
- std::unique_ptr<qsa_data> mExtraData;
-
- DEF_NEWDEL(PlaybackWrapper)
-};
-
-
-FORCE_ALIGN static int qsa_proc_playback(void *ptr)
-{
- PlaybackWrapper *self = static_cast<PlaybackWrapper*>(ptr);
- ALCdevice *device = self->mDevice;
- qsa_data *data = self->mExtraData.get();
- snd_pcm_channel_status_t status;
- sched_param param;
- char* write_ptr;
- ALint len;
- int sret;
-
- SetRTPriority();
- althrd_setname(MIXER_THREAD_NAME);
-
- /* Increase default 10 priority to 11 to avoid jerky sound */
- SchedGet(0, 0, &param);
- param.sched_priority=param.sched_curpriority+1;
- SchedSet(0, 0, SCHED_NOCHANGE, &param);
-
- const ALint frame_size = device->frameSizeFromFmt();
-
- std::unique_lock<PlaybackWrapper> dlock{*self};
- while(!data->mKillNow.load(std::memory_order_acquire))
- {
- pollfd pollitem{};
- pollitem.fd = data->audio_fd;
- pollitem.events = POLLOUT;
-
- /* Select also works like time slice to OS */
- dlock.unlock();
- sret = poll(&pollitem, 1, 2000);
- dlock.lock();
- if(sret == -1)
- {
- if(errno == EINTR || errno == EAGAIN)
- continue;
- ERR("poll error: %s\n", strerror(errno));
- aluHandleDisconnect(device, "Failed waiting for playback buffer: %s", strerror(errno));
- break;
- }
- if(sret == 0)
- {
- ERR("poll timeout\n");
- continue;
- }
-
- len = data->size;
- write_ptr = static_cast<char*>(data->buffer);
- aluMixData(device, write_ptr, len/frame_size);
- while(len>0 && !data->mKillNow.load(std::memory_order_acquire))
- {
- int wrote = snd_pcm_plugin_write(data->pcmHandle, write_ptr, len);
- if(wrote <= 0)
- {
- if(errno==EAGAIN || errno==EWOULDBLOCK)
- continue;
-
- memset(&status, 0, sizeof(status));
- status.channel = SND_PCM_CHANNEL_PLAYBACK;
-
- snd_pcm_plugin_status(data->pcmHandle, &status);
-
- /* we need to reinitialize the sound channel if we've underrun the buffer */
- if(status.status == SND_PCM_STATUS_UNDERRUN ||
- status.status == SND_PCM_STATUS_READY)
- {
- if(snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK) < 0)
- {
- aluHandleDisconnect(device, "Playback recovery failed");
- break;
- }
- }
- }
- else
- {
- write_ptr += wrote;
- len -= wrote;
- }
- }
- }
-
- return 0;
-}
-
-/************/
-/* Playback */
-/************/
-
-static ALCenum qsa_open_playback(PlaybackWrapper *self, const ALCchar* deviceName)
-{
- ALCdevice *device = self->mDevice;
- int card, dev;
- int status;
-
- std::unique_ptr<qsa_data> data{new qsa_data{}};
- data->mKillNow.store(AL_TRUE, std::memory_order_relaxed);
-
- if(!deviceName)
- deviceName = qsaDevice;
-
- if(strcmp(deviceName, qsaDevice) == 0)
- status = snd_pcm_open_preferred(&data->pcmHandle, &card, &dev, SND_PCM_OPEN_PLAYBACK);
- else
- {
- if(DeviceNameMap.empty())
- deviceList(SND_PCM_CHANNEL_PLAYBACK, &DeviceNameMap);
-
- auto iter = std::find_if(DeviceNameMap.begin(), DeviceNameMap.end(),
- [deviceName](const DevMap &entry) -> bool
- { return entry.name && strcmp(deviceName, entry.name) == 0; }
- );
- if(iter == DeviceNameMap.cend())
- return ALC_INVALID_DEVICE;
-
- status = snd_pcm_open(&data->pcmHandle, iter->card, iter->dev, SND_PCM_OPEN_PLAYBACK);
- }
-
- if(status < 0)
- return ALC_INVALID_DEVICE;
-
- data->audio_fd = snd_pcm_file_descriptor(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK);
- if(data->audio_fd < 0)
- {
- snd_pcm_close(data->pcmHandle);
- return ALC_INVALID_DEVICE;
- }
-
- device->DeviceName = deviceName;
- self->mExtraData = std::move(data);
-
- return ALC_NO_ERROR;
-}
-
-static void qsa_close_playback(PlaybackWrapper *self)
-{
- qsa_data *data = self->mExtraData.get();
-
- if (data->buffer!=NULL)
- {
- free(data->buffer);
- data->buffer=NULL;
- }
-
- snd_pcm_close(data->pcmHandle);
-
- self->mExtraData = nullptr;
-}
-
-static ALCboolean qsa_reset_playback(PlaybackWrapper *self)
-{
- ALCdevice *device = self->mDevice;
- qsa_data *data = self->mExtraData.get();
- int32_t format=-1;
-
- switch(device->FmtType)
- {
- case DevFmtByte:
- format=SND_PCM_SFMT_S8;
- break;
- case DevFmtUByte:
- format=SND_PCM_SFMT_U8;
- break;
- case DevFmtShort:
- format=SND_PCM_SFMT_S16_LE;
- break;
- case DevFmtUShort:
- format=SND_PCM_SFMT_U16_LE;
- break;
- case DevFmtInt:
- format=SND_PCM_SFMT_S32_LE;
- break;
- case DevFmtUInt:
- format=SND_PCM_SFMT_U32_LE;
- break;
- case DevFmtFloat:
- format=SND_PCM_SFMT_FLOAT_LE;
- break;
- }
-
- /* we actually don't want to block on writes */
- snd_pcm_nonblock_mode(data->pcmHandle, 1);
- /* Disable mmap to control data transfer to the audio device */
- snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_MMAP);
- snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_BUFFER_PARTIAL_BLOCKS);
-
- // configure a sound channel
- memset(&data->cparams, 0, sizeof(data->cparams));
- data->cparams.channel=SND_PCM_CHANNEL_PLAYBACK;
- data->cparams.mode=SND_PCM_MODE_BLOCK;
- data->cparams.start_mode=SND_PCM_START_FULL;
- data->cparams.stop_mode=SND_PCM_STOP_STOP;
-
- data->cparams.buf.block.frag_size=device->UpdateSize * device->frameSizeFromFmt();
- data->cparams.buf.block.frags_max=device->BufferSize / device->UpdateSize;
- data->cparams.buf.block.frags_min=data->cparams.buf.block.frags_max;
-
- data->cparams.format.interleave=1;
- data->cparams.format.rate=device->Frequency;
- data->cparams.format.voices=device->channelsFromFmt();
- data->cparams.format.format=format;
-
- if ((snd_pcm_plugin_params(data->pcmHandle, &data->cparams))<0)
- {
- int original_rate=data->cparams.format.rate;
- int original_voices=data->cparams.format.voices;
- int original_format=data->cparams.format.format;
- int it;
- int jt;
-
- for (it=0; it<1; it++)
- {
- /* Check for second pass */
- if (it==1)
- {
- original_rate=ratelist[0].rate;
- original_voices=channellist[0].channels;
- original_format=formatlist[0].format;
- }
-
- do {
- /* At first downgrade sample format */
- jt=0;
- do {
- if (formatlist[jt].format==data->cparams.format.format)
- {
- data->cparams.format.format=formatlist[jt+1].format;
- break;
- }
- if (formatlist[jt].format==0)
- {
- data->cparams.format.format=0;
- break;
- }
- jt++;
- } while(1);
-
- if (data->cparams.format.format==0)
- {
- data->cparams.format.format=original_format;
-
- /* At secod downgrade sample rate */
- jt=0;
- do {
- if (ratelist[jt].rate==data->cparams.format.rate)
- {
- data->cparams.format.rate=ratelist[jt+1].rate;
- break;
- }
- if (ratelist[jt].rate==0)
- {
- data->cparams.format.rate=0;
- break;
- }
- jt++;
- } while(1);
-
- if (data->cparams.format.rate==0)
- {
- data->cparams.format.rate=original_rate;
- data->cparams.format.format=original_format;
-
- /* At third downgrade channels number */
- jt=0;
- do {
- if(channellist[jt].channels==data->cparams.format.voices)
- {
- data->cparams.format.voices=channellist[jt+1].channels;
- break;
- }
- if (channellist[jt].channels==0)
- {
- data->cparams.format.voices=0;
- break;
- }
- jt++;
- } while(1);
- }
-
- if (data->cparams.format.voices==0)
- {
- break;
- }
- }
-
- data->cparams.buf.block.frag_size=device->UpdateSize*
- data->cparams.format.voices*
- snd_pcm_format_width(data->cparams.format.format)/8;
- data->cparams.buf.block.frags_max=device->NumUpdates;
- data->cparams.buf.block.frags_min=device->NumUpdates;
- if ((snd_pcm_plugin_params(data->pcmHandle, &data->cparams))<0)
- {
- continue;
- }
- else
- {
- break;
- }
- } while(1);
-
- if (data->cparams.format.voices!=0)
- {
- break;
- }
- }
-
- if (data->cparams.format.voices==0)
- {
- return ALC_FALSE;
- }
- }
-
- if ((snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK))<0)
- {
- return ALC_FALSE;
- }
-
- memset(&data->csetup, 0, sizeof(data->csetup));
- data->csetup.channel=SND_PCM_CHANNEL_PLAYBACK;
- if (snd_pcm_plugin_setup(data->pcmHandle, &data->csetup)<0)
- {
- return ALC_FALSE;
- }
-
- /* now fill back to the our AL device */
- device->Frequency=data->cparams.format.rate;
-
- switch (data->cparams.format.voices)
- {
- case 1:
- device->FmtChans=DevFmtMono;
- break;
- case 2:
- device->FmtChans=DevFmtStereo;
- break;
- case 4:
- device->FmtChans=DevFmtQuad;
- break;
- case 6:
- device->FmtChans=DevFmtX51;
- break;
- case 7:
- device->FmtChans=DevFmtX61;
- break;
- case 8:
- device->FmtChans=DevFmtX71;
- break;
- default:
- device->FmtChans=DevFmtMono;
- break;
- }
-
- switch (data->cparams.format.format)
- {
- case SND_PCM_SFMT_S8:
- device->FmtType=DevFmtByte;
- break;
- case SND_PCM_SFMT_U8:
- device->FmtType=DevFmtUByte;
- break;
- case SND_PCM_SFMT_S16_LE:
- device->FmtType=DevFmtShort;
- break;
- case SND_PCM_SFMT_U16_LE:
- device->FmtType=DevFmtUShort;
- break;
- case SND_PCM_SFMT_S32_LE:
- device->FmtType=DevFmtInt;
- break;
- case SND_PCM_SFMT_U32_LE:
- device->FmtType=DevFmtUInt;
- break;
- case SND_PCM_SFMT_FLOAT_LE:
- device->FmtType=DevFmtFloat;
- break;
- default:
- device->FmtType=DevFmtShort;
- break;
- }
-
- SetDefaultChannelOrder(device);
-
- device->UpdateSize=data->csetup.buf.block.frag_size / device->frameSizeFromFmt();
- device->NumUpdates=data->csetup.buf.block.frags;
-
- data->size=data->csetup.buf.block.frag_size;
- data->buffer=malloc(data->size);
- if (!data->buffer)
- {
- return ALC_FALSE;
- }
-
- return ALC_TRUE;
-}
-
-static ALCboolean qsa_start_playback(PlaybackWrapper *self)
-{
- qsa_data *data = self->mExtraData.get();
-
- try {
- data->mKillNow.store(AL_FALSE, std::memory_order_release);
- data->mThread = std::thread(qsa_proc_playback, self);
- return ALC_TRUE;
- }
- catch(std::exception& e) {
- ERR("Could not create playback thread: %s\n", e.what());
- }
- catch(...) {
- }
- return ALC_FALSE;
-}
-
-static void qsa_stop_playback(PlaybackWrapper *self)
-{
- qsa_data *data = self->mExtraData.get();
-
- if(data->mKillNow.exchange(AL_TRUE, std::memory_order_acq_rel) || !data->mThread.joinable())
- return;
- data->mThread.join();
-}
-
-
-PlaybackWrapper::~PlaybackWrapper()
-{
- if(mExtraData)
- qsa_close_playback(this);
-}
-
-void PlaybackWrapper::open(const ALCchar *name)
-{
- if(auto err = qsa_open_playback(this, name))
- throw al::backend_exception{ALC_INVALID_VALUE, "%d", err};
-}
-
-bool PlaybackWrapper::reset()
-{
- if(!qsa_reset_playback(this))
- throw al::backend_exception{ALC_INVALID_VALUE, ""};
- return true;
-}
-
-bool PlaybackWrapper::start()
-{ return qsa_start_playback(this); }
-
-void PlaybackWrapper::stop()
-{ qsa_stop_playback(this); }
-
-
-/***********/
-/* Capture */
-/***********/
-
-struct CaptureWrapper final : public BackendBase {
- CaptureWrapper(ALCdevice *device) noexcept : BackendBase{device} { }
- ~CaptureWrapper() override;
-
- void open(const ALCchar *name) override;
- bool start() override;
- void stop() override;
- ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
- ALCuint availableSamples() override;
-
- std::unique_ptr<qsa_data> mExtraData;
-
- DEF_NEWDEL(CaptureWrapper)
-};
-
-static ALCenum qsa_open_capture(CaptureWrapper *self, const ALCchar *deviceName)
-{
- ALCdevice *device = self->mDevice;
- int card, dev;
- int format=-1;
- int status;
-
- std::unique_ptr<qsa_data> data{new qsa_data{}};
-
- if(!deviceName)
- deviceName = qsaDevice;
-
- if(strcmp(deviceName, qsaDevice) == 0)
- status = snd_pcm_open_preferred(&data->pcmHandle, &card, &dev, SND_PCM_OPEN_CAPTURE);
- else
- {
- if(CaptureNameMap.empty())
- deviceList(SND_PCM_CHANNEL_CAPTURE, &CaptureNameMap);
-
- auto iter = std::find_if(CaptureNameMap.cbegin(), CaptureNameMap.cend(),
- [deviceName](const DevMap &entry) -> bool
- { return entry.name && strcmp(deviceName, entry.name) == 0; }
- );
- if(iter == CaptureNameMap.cend())
- return ALC_INVALID_DEVICE;
-
- status = snd_pcm_open(&data->pcmHandle, iter->card, iter->dev, SND_PCM_OPEN_CAPTURE);
- }
-
- if(status < 0)
- return ALC_INVALID_DEVICE;
-
- data->audio_fd = snd_pcm_file_descriptor(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE);
- if(data->audio_fd < 0)
- {
- snd_pcm_close(data->pcmHandle);
- return ALC_INVALID_DEVICE;
- }
-
- device->DeviceName = deviceName;
-
- switch (device->FmtType)
- {
- case DevFmtByte:
- format=SND_PCM_SFMT_S8;
- break;
- case DevFmtUByte:
- format=SND_PCM_SFMT_U8;
- break;
- case DevFmtShort:
- format=SND_PCM_SFMT_S16_LE;
- break;
- case DevFmtUShort:
- format=SND_PCM_SFMT_U16_LE;
- break;
- case DevFmtInt:
- format=SND_PCM_SFMT_S32_LE;
- break;
- case DevFmtUInt:
- format=SND_PCM_SFMT_U32_LE;
- break;
- case DevFmtFloat:
- format=SND_PCM_SFMT_FLOAT_LE;
- break;
- }
-
- /* we actually don't want to block on reads */
- snd_pcm_nonblock_mode(data->pcmHandle, 1);
- /* Disable mmap to control data transfer to the audio device */
- snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_MMAP);
-
- /* configure a sound channel */
- memset(&data->cparams, 0, sizeof(data->cparams));
- data->cparams.mode=SND_PCM_MODE_BLOCK;
- data->cparams.channel=SND_PCM_CHANNEL_CAPTURE;
- data->cparams.start_mode=SND_PCM_START_GO;
- data->cparams.stop_mode=SND_PCM_STOP_STOP;
-
- data->cparams.buf.block.frag_size=device->UpdateSize * device->frameSizeFromFmt();
- data->cparams.buf.block.frags_max=device->NumUpdates;
- data->cparams.buf.block.frags_min=device->NumUpdates;
-
- data->cparams.format.interleave=1;
- data->cparams.format.rate=device->Frequency;
- data->cparams.format.voices=device->channelsFromFmt();
- data->cparams.format.format=format;
-
- if(snd_pcm_plugin_params(data->pcmHandle, &data->cparams) < 0)
- {
- snd_pcm_close(data->pcmHandle);
- return ALC_INVALID_VALUE;
- }
-
- self->mExtraData = std::move(data);
-
- return ALC_NO_ERROR;
-}
-
-static void qsa_close_capture(CaptureWrapper *self)
-{
- qsa_data *data = self->mExtraData.get();
-
- if (data->pcmHandle!=nullptr)
- snd_pcm_close(data->pcmHandle);
- data->pcmHandle = nullptr;
-
- self->mExtraData = nullptr;
-}
-
-static void qsa_start_capture(CaptureWrapper *self)
-{
- qsa_data *data = self->mExtraData.get();
- int rstatus;
-
- if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0)
- {
- ERR("capture prepare failed: %s\n", snd_strerror(rstatus));
- return;
- }
-
- memset(&data->csetup, 0, sizeof(data->csetup));
- data->csetup.channel=SND_PCM_CHANNEL_CAPTURE;
- if ((rstatus=snd_pcm_plugin_setup(data->pcmHandle, &data->csetup))<0)
- {
- ERR("capture setup failed: %s\n", snd_strerror(rstatus));
- return;
- }
-
- snd_pcm_capture_go(data->pcmHandle);
-}
-
-static void qsa_stop_capture(CaptureWrapper *self)
-{
- qsa_data *data = self->mExtraData.get();
- snd_pcm_capture_flush(data->pcmHandle);
-}
-
-static ALCuint qsa_available_samples(CaptureWrapper *self)
-{
- ALCdevice *device = self->mDevice;
- qsa_data *data = self->mExtraData.get();
- snd_pcm_channel_status_t status;
- ALint frame_size = device->frameSizeFromFmt();
- ALint free_size;
- int rstatus;
-
- memset(&status, 0, sizeof (status));
- status.channel=SND_PCM_CHANNEL_CAPTURE;
- snd_pcm_plugin_status(data->pcmHandle, &status);
- if ((status.status==SND_PCM_STATUS_OVERRUN) ||
- (status.status==SND_PCM_STATUS_READY))
- {
- if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0)
- {
- ERR("capture prepare failed: %s\n", snd_strerror(rstatus));
- aluHandleDisconnect(device, "Failed capture recovery: %s", snd_strerror(rstatus));
- return 0;
- }
-
- snd_pcm_capture_go(data->pcmHandle);
- return 0;
- }
-
- free_size=data->csetup.buf.block.frag_size*data->csetup.buf.block.frags;
- free_size-=status.free;
-
- return free_size/frame_size;
-}
-
-static ALCenum qsa_capture_samples(CaptureWrapper *self, ALCvoid *buffer, ALCuint samples)
-{
- ALCdevice *device = self->mDevice;
- qsa_data *data = self->mExtraData.get();
- char* read_ptr;
- snd_pcm_channel_status_t status;
- int selectret;
- int bytes_read;
- ALint frame_size=device->frameSizeFromFmt();
- ALint len=samples*frame_size;
- int rstatus;
-
- read_ptr = static_cast<char*>(buffer);
-
- while (len>0)
- {
- pollfd pollitem{};
- pollitem.fd = data->audio_fd;
- pollitem.events = POLLOUT;
-
- /* Select also works like time slice to OS */
- bytes_read=0;
- selectret = poll(&pollitem, 1, 2000);
- switch (selectret)
- {
- case -1:
- aluHandleDisconnect(device, "Failed to check capture samples");
- return ALC_INVALID_DEVICE;
- case 0:
- break;
- default:
- bytes_read=snd_pcm_plugin_read(data->pcmHandle, read_ptr, len);
- break;
- }
-
- if (bytes_read<=0)
- {
- if ((errno==EAGAIN) || (errno==EWOULDBLOCK))
- {
- continue;
- }
-
- memset(&status, 0, sizeof (status));
- status.channel=SND_PCM_CHANNEL_CAPTURE;
- snd_pcm_plugin_status(data->pcmHandle, &status);
-
- /* we need to reinitialize the sound channel if we've overrun the buffer */
- if ((status.status==SND_PCM_STATUS_OVERRUN) ||
- (status.status==SND_PCM_STATUS_READY))
- {
- if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0)
- {
- ERR("capture prepare failed: %s\n", snd_strerror(rstatus));
- aluHandleDisconnect(device, "Failed capture recovery: %s",
- snd_strerror(rstatus));
- return ALC_INVALID_DEVICE;
- }
- snd_pcm_capture_go(data->pcmHandle);
- }
- }
- else
- {
- read_ptr+=bytes_read;
- len-=bytes_read;
- }
- }
-
- return ALC_NO_ERROR;
-}
-
-
-CaptureWrapper::~CaptureWrapper()
-{
- if(mExtraData)
- qsa_close_capture(this);
-}
-
-void CaptureWrapper::open(const ALCchar *name)
-{
- if(auto err = qsa_open_capture(this, name))
- throw al::backend_exception{ALC_INVALID_VALUE, "%d", err};
-}
-
-bool CaptureWrapper::start()
-{ qsa_start_capture(this); return true; }
-
-void CaptureWrapper::stop()
-{ qsa_stop_capture(this); }
-
-ALCenum CaptureWrapper::captureSamples(al::byte *buffer, ALCuint samples)
-{ return qsa_capture_samples(this, buffer, samples); }
-
-ALCuint CaptureWrapper::availableSamples()
-{ return qsa_available_samples(this); }
-
-} // namespace
-
-
-bool QSABackendFactory::init()
-{ return true; }
-
-bool QSABackendFactory::querySupport(BackendType type)
-{ return (type == BackendType::Playback || type == BackendType::Capture); }
-
-void QSABackendFactory::probe(DevProbe type, std::string *outnames)
-{
- auto add_device = [outnames](const DevMap &entry) -> void
- {
- const char *n = entry.name;
- if(n && n[0])
- outnames->append(n, strlen(n)+1);
- };
-
- switch (type)
- {
- case DevProbe::Playback:
- deviceList(SND_PCM_CHANNEL_PLAYBACK, &DeviceNameMap);
- std::for_each(DeviceNameMap.cbegin(), DeviceNameMap.cend(), add_device);
- break;
- case DevProbe::Capture:
- deviceList(SND_PCM_CHANNEL_CAPTURE, &CaptureNameMap);
- std::for_each(CaptureNameMap.cbegin(), CaptureNameMap.cend(), add_device);
- break;
- }
-}
-
-BackendPtr QSABackendFactory::createBackend(ALCdevice *device, BackendType type)
-{
- if(type == BackendType::Playback)
- return BackendPtr{new PlaybackWrapper{device}};
- if(type == BackendType::Capture)
- return BackendPtr{new CaptureWrapper{device}};
- return nullptr;
-}
-
-BackendFactory &QSABackendFactory::getFactory()
-{
- static QSABackendFactory factory{};
- return factory;
-}
diff --git a/alc/backends/qsa.h b/alc/backends/qsa.h
deleted file mode 100644
index da548bba..00000000
--- a/alc/backends/qsa.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#ifndef BACKENDS_QSA_H
-#define BACKENDS_QSA_H
-
-#include "backends/base.h"
-
-struct QSABackendFactory final : public BackendFactory {
-public:
- bool init() override;
-
- bool querySupport(BackendType type) override;
-
- void probe(DevProbe type, std::string *outnames) override;
-
- BackendPtr createBackend(ALCdevice *device, BackendType type) override;
-
- static BackendFactory &getFactory();
-};
-
-#endif /* BACKENDS_QSA_H */
diff --git a/alc/backends/sdl2.cpp b/alc/backends/sdl2.cpp
index 25b5d4d9..a4a5a9ac 100644
--- a/alc/backends/sdl2.cpp
+++ b/alc/backends/sdl2.cpp
@@ -20,22 +20,22 @@
#include "config.h"
-#include "backends/sdl2.h"
+#include "sdl2.h"
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <string>
-#include "AL/al.h"
-
-#include "alcmain.h"
-#include "alexcpt.h"
#include "almalloc.h"
-#include "alu.h"
-#include "logging.h"
+#include "alnumeric.h"
+#include "core/device.h"
+#include "core/logging.h"
-#include <SDL2/SDL.h>
+_Pragma("GCC diagnostic push")
+_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"")
+#include "SDL.h"
+_Pragma("GCC diagnostic pop")
namespace {
@@ -46,30 +46,28 @@ namespace {
#define DEVNAME_PREFIX ""
#endif
-constexpr ALCchar defaultDeviceName[] = DEVNAME_PREFIX "Default Device";
+constexpr char defaultDeviceName[] = DEVNAME_PREFIX "Default Device";
struct Sdl2Backend final : public BackendBase {
- Sdl2Backend(ALCdevice *device) noexcept : BackendBase{device} { }
+ Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { }
~Sdl2Backend() override;
void audioCallback(Uint8 *stream, int len) noexcept;
static void audioCallbackC(void *ptr, Uint8 *stream, int len) noexcept
{ static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); }
- void open(const ALCchar *name) override;
+ void open(const char *name) override;
bool reset() override;
- bool start() override;
+ void start() override;
void stop() override;
- void lock() override;
- void unlock() override;
SDL_AudioDeviceID mDeviceID{0u};
- ALuint mFrameSize{0};
+ uint mFrameSize{0};
- ALuint mFrequency{0u};
+ uint mFrequency{0u};
DevFmtChannels mFmtChans{};
DevFmtType mFmtType{};
- ALuint mUpdateSize{0u};
+ uint mUpdateSize{0u};
DEF_NEWDEL(Sdl2Backend)
};
@@ -85,10 +83,10 @@ void Sdl2Backend::audioCallback(Uint8 *stream, int len) noexcept
{
const auto ulen = static_cast<unsigned int>(len);
assert((ulen % mFrameSize) == 0);
- aluMixData(mDevice, stream, ulen / mFrameSize);
+ mDevice->renderSamples(stream, ulen / mFrameSize, mDevice->channelsFromFmt());
}
-void Sdl2Backend::open(const ALCchar *name)
+void Sdl2Backend::open(const char *name)
{
SDL_AudioSpec want{}, have{};
@@ -104,59 +102,64 @@ void Sdl2Backend::open(const ALCchar *name)
case DevFmtFloat: want.format = AUDIO_F32; break;
}
want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2;
- want.samples = static_cast<Uint16>(mDevice->UpdateSize);
+ want.samples = static_cast<Uint16>(minu(mDevice->UpdateSize, 8192));
want.callback = &Sdl2Backend::audioCallbackC;
want.userdata = this;
/* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't
* necessarily the first in the list.
*/
+ SDL_AudioDeviceID devid;
if(!name || strcmp(name, defaultDeviceName) == 0)
- mDeviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have,
- SDL_AUDIO_ALLOW_ANY_CHANGE);
+ devid = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE);
else
{
const size_t prefix_len = strlen(DEVNAME_PREFIX);
if(strncmp(name, DEVNAME_PREFIX, prefix_len) == 0)
- mDeviceID = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have,
+ devid = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have,
SDL_AUDIO_ALLOW_ANY_CHANGE);
else
- mDeviceID = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have,
- SDL_AUDIO_ALLOW_ANY_CHANGE);
+ devid = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE);
}
- if(mDeviceID == 0)
- throw al::backend_exception{ALC_INVALID_VALUE, "%s", SDL_GetError()};
-
- mDevice->Frequency = static_cast<ALuint>(have.freq);
-
- if(have.channels == 1)
- mDevice->FmtChans = DevFmtMono;
- else if(have.channels == 2)
- mDevice->FmtChans = DevFmtStereo;
+ 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
- throw al::backend_exception{ALC_INVALID_VALUE, "Unhandled SDL channel count: %d",
- int{have.channels}};
+ {
+ SDL_CloseAudioDevice(devid);
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Unhandled SDL channel count: %d", int{have.channels}};
+ }
+ DevFmtType devtype{};
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;
+ case AUDIO_U8: devtype = DevFmtUByte; break;
+ case AUDIO_S8: devtype = DevFmtByte; break;
+ case AUDIO_U16SYS: devtype = DevFmtUShort; break;
+ case AUDIO_S16SYS: devtype = DevFmtShort; break;
+ case AUDIO_S32SYS: devtype = DevFmtInt; break;
+ case AUDIO_F32SYS: devtype = DevFmtFloat; break;
default:
- throw al::backend_exception{ALC_INVALID_VALUE, "Unhandled SDL format: 0x%04x",
+ SDL_CloseAudioDevice(devid);
+ throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL format: 0x%04x",
have.format};
}
- mDevice->UpdateSize = have.samples;
- mDevice->BufferSize = have.samples * 2; /* SDL always (tries to) use two periods. */
- mFrameSize = mDevice->frameSizeFromFmt();
- mFrequency = mDevice->Frequency;
- mFmtChans = mDevice->FmtChans;
- mFmtType = mDevice->FmtType;
- mUpdateSize = mDevice->UpdateSize;
+ 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 ? name : defaultDeviceName;
}
@@ -167,26 +170,17 @@ bool Sdl2Backend::reset()
mDevice->FmtChans = mFmtChans;
mDevice->FmtType = mFmtType;
mDevice->UpdateSize = mUpdateSize;
- mDevice->BufferSize = mUpdateSize * 2;
- SetDefaultWFXChannelOrder(mDevice);
+ mDevice->BufferSize = mUpdateSize * 2; /* SDL always (tries to) use two periods. */
+ setDefaultWFXChannelOrder();
return true;
}
-bool Sdl2Backend::start()
-{
- SDL_PauseAudioDevice(mDeviceID, 0);
- return true;
-}
+void Sdl2Backend::start()
+{ SDL_PauseAudioDevice(mDeviceID, 0); }
void Sdl2Backend::stop()
{ SDL_PauseAudioDevice(mDeviceID, 1); }
-void Sdl2Backend::lock()
-{ SDL_LockAudioDevice(mDeviceID); }
-
-void Sdl2Backend::unlock()
-{ SDL_UnlockAudioDevice(mDeviceID); }
-
} // namespace
BackendFactory &SDL2BackendFactory::getFactory()
@@ -201,25 +195,28 @@ bool SDL2BackendFactory::init()
bool SDL2BackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
-void SDL2BackendFactory::probe(DevProbe type, std::string *outnames)
+std::string SDL2BackendFactory::probe(BackendType type)
{
- if(type != DevProbe::Playback)
- return;
+ std::string outnames;
+
+ if(type != BackendType::Playback)
+ return outnames;
int num_devices{SDL_GetNumAudioDevices(SDL_FALSE)};
/* Includes null char. */
- outnames->append(defaultDeviceName, sizeof(defaultDeviceName));
+ outnames.append(defaultDeviceName, sizeof(defaultDeviceName));
for(int i{0};i < num_devices;++i)
{
std::string name{DEVNAME_PREFIX};
name += SDL_GetAudioDeviceName(i, SDL_FALSE);
if(!name.empty())
- outnames->append(name.c_str(), name.length()+1);
+ outnames.append(name.c_str(), name.length()+1);
}
+ return outnames;
}
-BackendPtr SDL2BackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr SDL2BackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new Sdl2Backend{device}};
diff --git a/alc/backends/sdl2.h b/alc/backends/sdl2.h
index 041d47ee..3bd8df86 100644
--- a/alc/backends/sdl2.h
+++ b/alc/backends/sdl2.h
@@ -1,7 +1,7 @@
#ifndef BACKENDS_SDL2_H
#define BACKENDS_SDL2_H
-#include "backends/base.h"
+#include "base.h"
struct SDL2BackendFactory final : public BackendFactory {
public:
@@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
- void probe(DevProbe type, std::string *outnames) override;
+ std::string probe(BackendType type) override;
- BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+ BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
diff --git a/alc/backends/sndio.cpp b/alc/backends/sndio.cpp
index 7799316f..077e77f2 100644
--- a/alc/backends/sndio.cpp
+++ b/alc/backends/sndio.cpp
@@ -20,44 +20,52 @@
#include "config.h"
-#include "backends/sndio.h"
+#include "sndio.h"
+#include <functional>
+#include <inttypes.h>
+#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-
#include <thread>
-#include <functional>
-#include "alcmain.h"
-#include "alexcpt.h"
-#include "alu.h"
+#include "alnumeric.h"
+#include "core/device.h"
+#include "core/helpers.h"
+#include "core/logging.h"
+#include "ringbuffer.h"
#include "threads.h"
#include "vector.h"
-#include "ringbuffer.h"
#include <sndio.h>
namespace {
-static const ALCchar sndio_device[] = "SndIO Default";
+static const char sndio_device[] = "SndIO Default";
+
+struct SioPar : public sio_par {
+ SioPar() { sio_initpar(this); }
+ void clear() { sio_initpar(this); }
+};
struct SndioPlayback final : public BackendBase {
- SndioPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+ SndioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~SndioPlayback() override;
int mixerProc();
- void open(const ALCchar *name) override;
+ void open(const char *name) override;
bool reset() override;
- bool start() override;
+ void start() override;
void stop() override;
sio_hdl *mSndHandle{nullptr};
+ uint mFrameStep{};
- al::vector<ALubyte> mBuffer;
+ al::vector<al::byte> mBuffer;
std::atomic<bool> mKillNow{true};
std::thread mThread;
@@ -74,33 +82,29 @@ SndioPlayback::~SndioPlayback()
int SndioPlayback::mixerProc()
{
+ const size_t frameStep{mFrameStep};
+ const size_t frameSize{frameStep * mDevice->bytesFromFmt()};
+
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
- const ALuint frameSize{mDevice->frameSizeFromFmt()};
-
- while(!mKillNow.load(std::memory_order_acquire) &&
- mDevice->Connected.load(std::memory_order_acquire))
+ while(!mKillNow.load(std::memory_order_acquire)
+ && mDevice->Connected.load(std::memory_order_acquire))
{
- ALubyte *WritePtr{mBuffer.data()};
- size_t len{mBuffer.size()};
+ al::span<al::byte> buffer{mBuffer};
+ mDevice->renderSamples(buffer.data(), static_cast<uint>(buffer.size() / frameSize),
+ frameStep);
+ while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire))
{
- std::lock_guard<SndioPlayback> _{*this};
- aluMixData(mDevice, WritePtr, static_cast<ALuint>(len/frameSize));
- }
- while(len > 0 && !mKillNow.load(std::memory_order_acquire))
- {
- size_t wrote{sio_write(mSndHandle, WritePtr, len)};
- if(wrote == 0)
+ size_t wrote{sio_write(mSndHandle, buffer.data(), buffer.size())};
+ if(wrote > buffer.size() || wrote == 0)
{
- ERR("sio_write failed\n");
- aluHandleDisconnect(mDevice, "Failed to write playback samples");
+ ERR("sio_write failed: 0x%" PRIx64 "\n", wrote);
+ mDevice->handleDisconnect("Failed to write playback samples");
break;
}
-
- len -= wrote;
- WritePtr += wrote;
+ buffer = buffer.subspan(wrote);
}
}
@@ -108,126 +112,150 @@ int SndioPlayback::mixerProc()
}
-void SndioPlayback::open(const ALCchar *name)
+void SndioPlayback::open(const char *name)
{
if(!name)
name = sndio_device;
else if(strcmp(name, sndio_device) != 0)
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+ name};
- mSndHandle = sio_open(nullptr, SIO_PLAY, 0);
- if(mSndHandle == nullptr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not open backend device"};
+ sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)};
+ if(!sndHandle)
+ throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"};
+
+ if(mSndHandle)
+ sio_close(mSndHandle);
+ mSndHandle = sndHandle;
mDevice->DeviceName = name;
}
bool SndioPlayback::reset()
{
- sio_par par;
- sio_initpar(&par);
-
- par.rate = mDevice->Frequency;
- par.pchan = ((mDevice->FmtChans != DevFmtMono) ? 2 : 1);
+ SioPar par;
- switch(mDevice->FmtType)
+ auto tryfmt = mDevice->FmtType;
+retry_params:
+ switch(tryfmt)
{
- case DevFmtByte:
- par.bits = 8;
- par.sig = 1;
- break;
- case DevFmtUByte:
- par.bits = 8;
- par.sig = 0;
- break;
- case DevFmtFloat:
- case DevFmtShort:
- par.bits = 16;
- par.sig = 1;
- break;
- case DevFmtUShort:
- par.bits = 16;
- par.sig = 0;
- break;
- case DevFmtInt:
- par.bits = 32;
- par.sig = 1;
- break;
- case DevFmtUInt:
- par.bits = 32;
- par.sig = 0;
- break;
+ case DevFmtByte:
+ par.bits = 8;
+ par.sig = 1;
+ break;
+ case DevFmtUByte:
+ par.bits = 8;
+ par.sig = 0;
+ break;
+ case DevFmtShort:
+ par.bits = 16;
+ par.sig = 1;
+ break;
+ case DevFmtUShort:
+ par.bits = 16;
+ par.sig = 0;
+ break;
+ case DevFmtFloat:
+ case DevFmtInt:
+ par.bits = 32;
+ par.sig = 1;
+ break;
+ case DevFmtUInt:
+ par.bits = 32;
+ par.sig = 0;
+ break;
}
+ par.bps = SIO_BPS(par.bits);
par.le = SIO_LE_NATIVE;
+ par.msb = 1;
+
+ par.rate = mDevice->Frequency;
+ par.pchan = mDevice->channelsFromFmt();
par.round = mDevice->UpdateSize;
par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize;
if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize;
- if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
- {
- ERR("Failed to set device parameters\n");
- return false;
+ try {
+ if(!sio_setpar(mSndHandle, &par))
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to set device parameters"};
+
+ par.clear();
+ if(!sio_getpar(mSndHandle, &par))
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to get device parameters"};
+
+ if(par.bps > 1 && par.le != SIO_LE_NATIVE)
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "%s-endian samples not supported", par.le ? "Little" : "Big"};
+ if(par.bits < par.bps*8 && !par.msb)
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "MSB-padded samples not supported (%u of %u bits)", par.bits, par.bps*8};
+ if(par.pchan < 1)
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "No playback channels on device"};
}
-
- if(par.bits != par.bps*8)
- {
- ERR("Padded samples not supported (%u of %u bits)\n", par.bits, par.bps*8);
- return true;
+ catch(al::backend_exception &e) {
+ if(tryfmt == DevFmtShort)
+ throw;
+ par.clear();
+ tryfmt = DevFmtShort;
+ goto retry_params;
}
- mDevice->Frequency = par.rate;
- mDevice->FmtChans = ((par.pchan==1) ? DevFmtMono : DevFmtStereo);
-
- if(par.bits == 8 && par.sig == 1)
- mDevice->FmtType = DevFmtByte;
- else if(par.bits == 8 && par.sig == 0)
- mDevice->FmtType = DevFmtUByte;
- else if(par.bits == 16 && par.sig == 1)
- mDevice->FmtType = DevFmtShort;
- else if(par.bits == 16 && par.sig == 0)
- mDevice->FmtType = DevFmtUShort;
- else if(par.bits == 32 && par.sig == 1)
- mDevice->FmtType = DevFmtInt;
- else if(par.bits == 32 && par.sig == 0)
- mDevice->FmtType = DevFmtUInt;
+ if(par.bps == 1)
+ mDevice->FmtType = (par.sig==1) ? DevFmtByte : DevFmtUByte;
+ else if(par.bps == 2)
+ mDevice->FmtType = (par.sig==1) ? DevFmtShort : DevFmtUShort;
+ else if(par.bps == 4)
+ 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};
+
+ mFrameStep = par.pchan;
+ if(par.pchan != mDevice->channelsFromFmt())
{
- ERR("Unhandled sample format: %s %u-bit\n", (par.sig?"signed":"unsigned"), par.bits);
- return false;
+ WARN("Got %u channel%s for %s\n", par.pchan, (par.pchan==1)?"":"s",
+ DevFmtChannelsString(mDevice->FmtChans));
+ if(par.pchan < 2) mDevice->FmtChans = DevFmtMono;
+ else mDevice->FmtChans = DevFmtStereo;
}
+ mDevice->Frequency = par.rate;
- SetDefaultChannelOrder(mDevice);
+ setDefaultChannelOrder();
mDevice->UpdateSize = par.round;
mDevice->BufferSize = par.bufsz + par.round;
- mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
- std::fill(mBuffer.begin(), mBuffer.end(), 0);
+ mBuffer.resize(mDevice->UpdateSize * par.pchan*par.bps);
+ if(par.sig == 1)
+ std::fill(mBuffer.begin(), mBuffer.end(), al::byte{});
+ else if(par.bits == 8)
+ std::fill_n(mBuffer.data(), mBuffer.size(), al::byte(0x80));
+ else if(par.bits == 16)
+ std::fill_n(reinterpret_cast<uint16_t*>(mBuffer.data()), mBuffer.size()/2, 0x8000);
+ else if(par.bits == 32)
+ std::fill_n(reinterpret_cast<uint32_t*>(mBuffer.data()), mBuffer.size()/4, 0x80000000u);
return true;
}
-bool SndioPlayback::start()
+void SndioPlayback::start()
{
if(!sio_start(mSndHandle))
- {
- ERR("Error starting playback\n");
- return false;
- }
+ throw al::backend_exception{al::backend_error::DeviceError, "Error starting playback"};
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&SndioPlayback::mixerProc), this};
- return true;
}
catch(std::exception& e) {
- ERR("Could not create playback thread: %s\n", e.what());
+ sio_stop(mSndHandle);
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to start mixing thread: %s", e.what()};
}
- catch(...) {
- }
- sio_stop(mSndHandle);
- return false;
}
void SndioPlayback::stop()
@@ -241,17 +269,22 @@ void SndioPlayback::stop()
}
+/* TODO: This could be improved by avoiding the ring buffer and record thread,
+ * counting the available samples with the sio_onmove callback and reading
+ * directly from the device. However, this depends on reasonable support for
+ * capture buffer sizes apps may request.
+ */
struct SndioCapture final : public BackendBase {
- SndioCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+ SndioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~SndioCapture() override;
int recordProc();
- void open(const ALCchar *name) override;
- bool start() override;
+ void open(const char *name) override;
+ void start() override;
void stop() override;
- ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
- ALCuint availableSamples() override;
+ void captureSamples(al::byte *buffer, uint samples) override;
+ uint availableSamples() override;
sio_hdl *mSndHandle{nullptr};
@@ -275,150 +308,182 @@ int SndioCapture::recordProc()
SetRTPriority();
althrd_setname(RECORD_THREAD_NAME);
- const ALuint frameSize{mDevice->frameSizeFromFmt()};
+ const uint frameSize{mDevice->frameSizeFromFmt()};
- while(!mKillNow.load(std::memory_order_acquire) &&
- mDevice->Connected.load(std::memory_order_acquire))
+ int nfds_pre{sio_nfds(mSndHandle)};
+ if(nfds_pre <= 0)
{
- auto data = mRing->getWriteVector();
- size_t todo{data.first.len + data.second.len};
- if(todo == 0)
+ mDevice->handleDisconnect("Incorrect return value from sio_nfds(): %d", nfds_pre);
+ return 1;
+ }
+
+ auto fds = std::make_unique<pollfd[]>(static_cast<uint>(nfds_pre));
+
+ while(!mKillNow.load(std::memory_order_acquire)
+ && mDevice->Connected.load(std::memory_order_acquire))
+ {
+ /* Wait until there's some samples to read. */
+ const int nfds{sio_pollfd(mSndHandle, fds.get(), POLLIN)};
+ if(nfds <= 0)
{
- static char junk[4096];
- sio_read(mSndHandle, junk,
- minz(sizeof(junk)/frameSize, mDevice->UpdateSize)*frameSize);
- continue;
+ mDevice->handleDisconnect("Failed to get polling fds: %d", nfds);
+ break;
+ }
+ int pollres{::poll(fds.get(), static_cast<uint>(nfds), 2000)};
+ if(pollres < 0)
+ {
+ if(errno == EINTR) continue;
+ mDevice->handleDisconnect("Poll error: %s", strerror(errno));
+ break;
}
+ if(pollres == 0)
+ continue;
- size_t total{0u};
- data.first.len *= frameSize;
- data.second.len *= frameSize;
- todo = minz(todo, mDevice->UpdateSize) * frameSize;
- while(total < todo)
+ const int revents{sio_revents(mSndHandle, fds.get())};
+ if((revents&POLLHUP))
{
- if(!data.first.len)
- data.first = data.second;
+ mDevice->handleDisconnect("Got POLLHUP from poll events");
+ break;
+ }
+ if(!(revents&POLLIN))
+ continue;
- size_t got{sio_read(mSndHandle, data.first.buf, minz(todo-total, data.first.len))};
- if(!got)
+ auto data = mRing->getWriteVector();
+ al::span<al::byte> buffer{data.first.buf, data.first.len*frameSize};
+ while(!buffer.empty())
+ {
+ size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())};
+ if(got == 0)
+ break;
+ if(got > buffer.size())
{
- aluHandleDisconnect(mDevice, "Failed to read capture samples");
+ ERR("sio_read failed: 0x%" PRIx64 "\n", got);
+ mDevice->handleDisconnect("sio_read failed: 0x%" PRIx64, got);
break;
}
- data.first.buf += got;
- data.first.len -= got;
- total += got;
+ mRing->writeAdvance(got / frameSize);
+ buffer = buffer.subspan(got);
+ if(buffer.empty())
+ {
+ data = mRing->getWriteVector();
+ buffer = {data.first.buf, data.first.len*frameSize};
+ }
+ }
+ if(buffer.empty())
+ {
+ /* Got samples to read, but no place to store it. Drop it. */
+ static char junk[4096];
+ sio_read(mSndHandle, junk, sizeof(junk) - (sizeof(junk)%frameSize));
}
- mRing->writeAdvance(total / frameSize);
}
return 0;
}
-void SndioCapture::open(const ALCchar *name)
+void SndioCapture::open(const char *name)
{
if(!name)
name = sndio_device;
else if(strcmp(name, sndio_device) != 0)
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+ name};
- mSndHandle = sio_open(nullptr, SIO_REC, 0);
+ mSndHandle = sio_open(nullptr, SIO_REC, true);
if(mSndHandle == nullptr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not open backend device"};
-
- sio_par par;
- sio_initpar(&par);
+ throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"};
+ SioPar par;
switch(mDevice->FmtType)
{
case DevFmtByte:
- par.bps = 1;
+ par.bits = 8;
par.sig = 1;
break;
case DevFmtUByte:
- par.bps = 1;
+ par.bits = 8;
par.sig = 0;
break;
case DevFmtShort:
- par.bps = 2;
+ par.bits = 16;
par.sig = 1;
break;
case DevFmtUShort:
- par.bps = 2;
+ par.bits = 16;
par.sig = 0;
break;
case DevFmtInt:
- par.bps = 4;
+ par.bits = 32;
par.sig = 1;
break;
case DevFmtUInt:
- par.bps = 4;
+ par.bits = 32;
par.sig = 0;
break;
case DevFmtFloat:
- throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported",
- DevFmtTypeString(mDevice->FmtType)};
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
}
- par.bits = par.bps * 8;
+ par.bps = SIO_BPS(par.bits);
par.le = SIO_LE_NATIVE;
- par.msb = SIO_LE_NATIVE ? 0 : 1;
+ par.msb = 1;
par.rchan = mDevice->channelsFromFmt();
par.rate = mDevice->Frequency;
par.appbufsz = maxu(mDevice->BufferSize, mDevice->Frequency/10);
- par.round = minu(par.appbufsz, mDevice->Frequency/40);
-
- mDevice->UpdateSize = par.round;
- mDevice->BufferSize = par.appbufsz;
+ par.round = minu(par.appbufsz/2, mDevice->Frequency/40);
if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
- throw al::backend_exception{ALC_INVALID_VALUE, "Failed to set device praameters"};
-
- if(par.bits != par.bps*8)
- throw al::backend_exception{ALC_INVALID_VALUE,
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to set device praameters"};
+
+ if(par.bps > 1 && par.le != SIO_LE_NATIVE)
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "%s-endian samples not supported", par.le ? "Little" : "Big"};
+ if(par.bits < par.bps*8 && !par.msb)
+ throw al::backend_exception{al::backend_error::DeviceError,
"Padded samples not supported (got %u of %u bits)", par.bits, par.bps*8};
- if(!((mDevice->FmtType == DevFmtByte && par.bits == 8 && par.sig != 0)
- || (mDevice->FmtType == DevFmtUByte && par.bits == 8 && par.sig == 0)
- || (mDevice->FmtType == DevFmtShort && par.bits == 16 && par.sig != 0)
- || (mDevice->FmtType == DevFmtUShort && par.bits == 16 && par.sig == 0)
- || (mDevice->FmtType == DevFmtInt && par.bits == 32 && par.sig != 0)
- || (mDevice->FmtType == DevFmtUInt && par.bits == 32 && par.sig == 0))
- || mDevice->channelsFromFmt() != par.rchan || mDevice->Frequency != par.rate)
- throw al::backend_exception{ALC_INVALID_VALUE,
+ auto match_fmt = [](DevFmtType fmttype, const sio_par &p) -> bool
+ {
+ return (fmttype == DevFmtByte && p.bps == 1 && p.sig != 0)
+ || (fmttype == DevFmtUByte && p.bps == 1 && p.sig == 0)
+ || (fmttype == DevFmtShort && p.bps == 2 && p.sig != 0)
+ || (fmttype == DevFmtUShort && p.bps == 2 && p.sig == 0)
+ || (fmttype == DevFmtInt && p.bps == 4 && p.sig != 0)
+ || (fmttype == DevFmtUInt && p.bps == 4 && p.sig == 0);
+ };
+ if(!match_fmt(mDevice->FmtType, par) || mDevice->channelsFromFmt() != par.rchan
+ || mDevice->Frequency != par.rate)
+ throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead",
DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans),
- mDevice->Frequency, par.sig?'s':'u', par.bits, par.rchan, par.rate};
+ mDevice->Frequency, par.sig?'s':'u', par.bps*8, par.rchan, par.rate};
- mRing = CreateRingBuffer(mDevice->BufferSize, par.bps*par.rchan, false);
+ mRing = RingBuffer::Create(mDevice->BufferSize, par.bps*par.rchan, false);
+ mDevice->BufferSize = static_cast<uint>(mRing->writeSpace());
+ mDevice->UpdateSize = par.round;
- SetDefaultChannelOrder(mDevice);
+ setDefaultChannelOrder();
mDevice->DeviceName = name;
}
-bool SndioCapture::start()
+void SndioCapture::start()
{
if(!sio_start(mSndHandle))
- {
- ERR("Error starting playback\n");
- return false;
- }
+ throw al::backend_exception{al::backend_error::DeviceError, "Error starting capture"};
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&SndioCapture::recordProc), this};
- return true;
}
catch(std::exception& e) {
- ERR("Could not create record thread: %s\n", e.what());
+ sio_stop(mSndHandle);
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to start capture thread: %s", e.what()};
}
- catch(...) {
- }
- sio_stop(mSndHandle);
- return false;
}
void SndioCapture::stop()
@@ -431,14 +496,11 @@ void SndioCapture::stop()
ERR("Error stopping device\n");
}
-ALCenum SndioCapture::captureSamples(al::byte *buffer, ALCuint samples)
-{
- mRing->read(buffer, samples);
- return ALC_NO_ERROR;
-}
+void SndioCapture::captureSamples(al::byte *buffer, uint samples)
+{ mRing->read(buffer, samples); }
-ALCuint SndioCapture::availableSamples()
-{ return static_cast<ALCuint>(mRing->readSpace()); }
+uint SndioCapture::availableSamples()
+{ return static_cast<uint>(mRing->readSpace()); }
} // namespace
@@ -454,19 +516,21 @@ bool SndIOBackendFactory::init()
bool SndIOBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
-void SndIOBackendFactory::probe(DevProbe type, std::string *outnames)
+std::string SndIOBackendFactory::probe(BackendType type)
{
+ std::string outnames;
switch(type)
{
- case DevProbe::Playback:
- case DevProbe::Capture:
- /* Includes null char. */
- outnames->append(sndio_device, sizeof(sndio_device));
- break;
+ case BackendType::Playback:
+ case BackendType::Capture:
+ /* Includes null char. */
+ outnames.append(sndio_device, sizeof(sndio_device));
+ break;
}
+ return outnames;
}
-BackendPtr SndIOBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr SndIOBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new SndioPlayback{device}};
diff --git a/alc/backends/sndio.h b/alc/backends/sndio.h
index 1ed63d5e..d9433191 100644
--- a/alc/backends/sndio.h
+++ b/alc/backends/sndio.h
@@ -1,7 +1,7 @@
#ifndef BACKENDS_SNDIO_H
#define BACKENDS_SNDIO_H
-#include "backends/base.h"
+#include "base.h"
struct SndIOBackendFactory final : public BackendFactory {
public:
@@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
- void probe(DevProbe type, std::string *outnames) override;
+ std::string probe(BackendType type) override;
- BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+ BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
diff --git a/alc/backends/solaris.cpp b/alc/backends/solaris.cpp
index 7cc2606e..791609ce 100644
--- a/alc/backends/solaris.cpp
+++ b/alc/backends/solaris.cpp
@@ -20,7 +20,7 @@
#include "config.h"
-#include "backends/solaris.h"
+#include "solaris.h"
#include <sys/ioctl.h>
#include <sys/types.h>
@@ -34,42 +34,44 @@
#include <errno.h>
#include <poll.h>
#include <math.h>
+#include <string.h>
#include <thread>
#include <functional>
-#include "alcmain.h"
-#include "alexcpt.h"
-#include "alu.h"
-#include "alconfig.h"
+#include "albyte.h"
+#include "alc/alconfig.h"
+#include "core/device.h"
+#include "core/helpers.h"
+#include "core/logging.h"
#include "threads.h"
#include "vector.h"
-#include "compat.h"
#include <sys/audioio.h>
namespace {
-constexpr ALCchar solaris_device[] = "Solaris Default";
+constexpr char solaris_device[] = "Solaris Default";
std::string solaris_driver{"/dev/audio"};
struct SolarisBackend final : public BackendBase {
- SolarisBackend(ALCdevice *device) noexcept : BackendBase{device} { }
+ SolarisBackend(DeviceBase *device) noexcept : BackendBase{device} { }
~SolarisBackend() override;
int mixerProc();
- void open(const ALCchar *name) override;
+ void open(const char *name) override;
bool reset() override;
- bool start() override;
+ void start() override;
void stop() override;
int mFd{-1};
- al::vector<ALubyte> mBuffer;
+ uint mFrameStep{};
+ al::vector<al::byte> mBuffer;
std::atomic<bool> mKillNow{true};
std::thread mThread;
@@ -89,26 +91,23 @@ int SolarisBackend::mixerProc()
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
- const ALuint frame_size{mDevice->frameSizeFromFmt()};
+ const size_t frame_step{mDevice->channelsFromFmt()};
+ const uint frame_size{mDevice->frameSizeFromFmt()};
- std::unique_lock<SolarisBackend> dlock{*this};
- while(!mKillNow.load(std::memory_order_acquire) &&
- mDevice->Connected.load(std::memory_order_acquire))
+ while(!mKillNow.load(std::memory_order_acquire)
+ && mDevice->Connected.load(std::memory_order_acquire))
{
pollfd pollitem{};
pollitem.fd = mFd;
pollitem.events = POLLOUT;
- dlock.unlock();
int pret{poll(&pollitem, 1, 1000)};
- dlock.lock();
if(pret < 0)
{
if(errno == EINTR || errno == EAGAIN)
continue;
ERR("poll failed: %s\n", strerror(errno));
- aluHandleDisconnect(mDevice, "Failed to wait for playback buffer: %s",
- strerror(errno));
+ mDevice->handleDisconnect("Failed to wait for playback buffer: %s", strerror(errno));
break;
}
else if(pret == 0)
@@ -117,9 +116,9 @@ int SolarisBackend::mixerProc()
continue;
}
- ALubyte *write_ptr{mBuffer.data()};
+ al::byte *write_ptr{mBuffer.data()};
size_t to_write{mBuffer.size()};
- aluMixData(mDevice, write_ptr, to_write/frame_size);
+ mDevice->renderSamples(write_ptr, static_cast<uint>(to_write/frame_size), frame_step);
while(to_write > 0 && !mKillNow.load(std::memory_order_acquire))
{
ssize_t wrote{write(mFd, write_ptr, to_write)};
@@ -128,12 +127,11 @@ int SolarisBackend::mixerProc()
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
continue;
ERR("write failed: %s\n", strerror(errno));
- aluHandleDisconnect(mDevice, "Failed to write playback samples: %s",
- strerror(errno));
+ mDevice->handleDisconnect("Failed to write playback samples: %s", strerror(errno));
break;
}
- to_write -= wrote;
+ to_write -= static_cast<size_t>(wrote);
write_ptr += wrote;
}
}
@@ -142,18 +140,23 @@ int SolarisBackend::mixerProc()
}
-void SolarisBackend::open(const ALCchar *name)
+void SolarisBackend::open(const char *name)
{
if(!name)
name = solaris_device;
else if(strcmp(name, solaris_device) != 0)
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+ name};
- mFd = ::open(solaris_driver.c_str(), O_WRONLY);
- if(mFd == -1)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not open %s: %s",
+ 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)};
+ if(mFd != -1)
+ ::close(mFd);
+ mFd = fd;
+
mDevice->DeviceName = name;
}
@@ -163,36 +166,29 @@ bool SolarisBackend::reset()
AUDIO_INITINFO(&info);
info.play.sample_rate = mDevice->Frequency;
-
- if(mDevice->FmtChans != DevFmtMono)
- mDevice->FmtChans = DevFmtStereo;
- ALuint numChannels{mDevice->channelsFromFmt()};
- info.play.channels = numChannels;
-
+ info.play.channels = mDevice->channelsFromFmt();
switch(mDevice->FmtType)
{
- case DevFmtByte:
- info.play.precision = 8;
- info.play.encoding = AUDIO_ENCODING_LINEAR;
- break;
- case DevFmtUByte:
- info.play.precision = 8;
- info.play.encoding = AUDIO_ENCODING_LINEAR8;
- break;
- case DevFmtUShort:
- case DevFmtInt:
- case DevFmtUInt:
- case DevFmtFloat:
- mDevice->FmtType = DevFmtShort;
- /* fall-through */
- case DevFmtShort:
- info.play.precision = 16;
- info.play.encoding = AUDIO_ENCODING_LINEAR;
- break;
+ case DevFmtByte:
+ info.play.precision = 8;
+ info.play.encoding = AUDIO_ENCODING_LINEAR;
+ break;
+ case DevFmtUByte:
+ info.play.precision = 8;
+ info.play.encoding = AUDIO_ENCODING_LINEAR8;
+ break;
+ case DevFmtUShort:
+ case DevFmtInt:
+ case DevFmtUInt:
+ case DevFmtFloat:
+ mDevice->FmtType = DevFmtShort;
+ /* fall-through */
+ case DevFmtShort:
+ info.play.precision = 16;
+ info.play.encoding = AUDIO_ENCODING_LINEAR;
+ break;
}
-
- ALuint frameSize{numChannels * mDevice->bytesFromFmt()};
- info.play.buffer_size = mDevice->BufferSize * frameSize;
+ info.play.buffer_size = mDevice->BufferSize * mDevice->frameSizeFromFmt();
if(ioctl(mFd, AUDIO_SETINFO, &info) < 0)
{
@@ -202,46 +198,54 @@ bool SolarisBackend::reset()
if(mDevice->channelsFromFmt() != info.play.channels)
{
- ERR("Failed to set %s, got %u channels instead\n", DevFmtChannelsString(mDevice->FmtChans),
- info.play.channels);
- return false;
+ if(info.play.channels >= 2)
+ mDevice->FmtChans = DevFmtStereo;
+ else if(info.play.channels == 1)
+ mDevice->FmtChans = DevFmtMono;
+ else
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Got %u device channels", info.play.channels};
}
- if(!((info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8 && mDevice->FmtType == DevFmtUByte) ||
- (info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtByte) ||
- (info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtShort) ||
- (info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtInt)))
+ if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8)
+ mDevice->FmtType = DevFmtUByte;
+ else if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR)
+ mDevice->FmtType = DevFmtByte;
+ else if(info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR)
+ mDevice->FmtType = DevFmtShort;
+ else if(info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR)
+ mDevice->FmtType = DevFmtInt;
+ else
{
- ERR("Could not set %s samples, got %d (0x%x)\n", DevFmtTypeString(mDevice->FmtType),
- info.play.precision, info.play.encoding);
+ ERR("Got unhandled sample type: %d (0x%x)\n", 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 / frameSize;
+ mDevice->BufferSize = info.play.buffer_size / frame_size;
+ /* How to get the actual period size/count? */
mDevice->UpdateSize = mDevice->BufferSize / 2;
- SetDefaultChannelOrder(mDevice);
+ setDefaultChannelOrder();
- mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
- std::fill(mBuffer.begin(), mBuffer.end(), 0);
+ mBuffer.resize(mDevice->UpdateSize * size_t{frame_size});
+ std::fill(mBuffer.begin(), mBuffer.end(), al::byte{});
return true;
}
-bool SolarisBackend::start()
+void SolarisBackend::start()
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&SolarisBackend::mixerProc), this};
- return true;
}
catch(std::exception& e) {
- ERR("Could not create playback thread: %s\n", e.what());
- }
- catch(...) {
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to start mixing thread: %s", e.what()};
}
- return false;
}
void SolarisBackend::stop()
@@ -272,26 +276,26 @@ bool SolarisBackendFactory::init()
bool SolarisBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
-void SolarisBackendFactory::probe(DevProbe type, std::string *outnames)
+std::string SolarisBackendFactory::probe(BackendType type)
{
+ std::string outnames;
switch(type)
{
- case DevProbe::Playback:
- {
-#ifdef HAVE_STAT
- struct stat buf;
- if(stat(solaris_driver.c_str(), &buf) == 0)
-#endif
- outnames->append(solaris_device, sizeof(solaris_device));
- }
- break;
+ case BackendType::Playback:
+ {
+ struct stat buf;
+ if(stat(solaris_driver.c_str(), &buf) == 0)
+ outnames.append(solaris_device, sizeof(solaris_device));
+ }
+ break;
- case DevProbe::Capture:
- break;
+ case BackendType::Capture:
+ break;
}
+ return outnames;
}
-BackendPtr SolarisBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr SolarisBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new SolarisBackend{device}};
diff --git a/alc/backends/solaris.h b/alc/backends/solaris.h
index 98b10593..5da6ad3a 100644
--- a/alc/backends/solaris.h
+++ b/alc/backends/solaris.h
@@ -1,7 +1,7 @@
#ifndef BACKENDS_SOLARIS_H
#define BACKENDS_SOLARIS_H
-#include "backends/base.h"
+#include "base.h"
struct SolarisBackendFactory final : public BackendFactory {
public:
@@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
- void probe(DevProbe type, std::string *outnames) override;
+ std::string probe(BackendType type) override;
- BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+ BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
diff --git a/alc/backends/wasapi.cpp b/alc/backends/wasapi.cpp
index 37a547af..e834eef4 100644
--- a/alc/backends/wasapi.cpp
+++ b/alc/backends/wasapi.cpp
@@ -20,7 +20,7 @@
#include "config.h"
-#include "backends/wasapi.h"
+#include "wasapi.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
@@ -43,23 +43,28 @@
#include <ksmedia.h>
#endif
+#include <algorithm>
+#include <atomic>
+#include <chrono>
+#include <condition_variable>
+#include <cstring>
#include <deque>
+#include <functional>
+#include <future>
#include <mutex>
-#include <atomic>
+#include <string>
#include <thread>
#include <vector>
-#include <string>
-#include <future>
-#include <algorithm>
-#include <functional>
-#include <condition_variable>
-#include "alcmain.h"
-#include "alexcpt.h"
-#include "alu.h"
+#include "albit.h"
+#include "alc/alconfig.h"
+#include "alnumeric.h"
+#include "comptr.h"
+#include "core/converter.h"
+#include "core/device.h"
+#include "core/helpers.h"
+#include "core/logging.h"
#include "ringbuffer.h"
-#include "compat.h"
-#include "converter.h"
#include "strutils.h"
#include "threads.h"
@@ -82,8 +87,15 @@ DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x
namespace {
-inline constexpr REFERENCE_TIME operator "" _reftime(unsigned long long int n) noexcept
-{ return static_cast<REFERENCE_TIME>(n); }
+using std::chrono::nanoseconds;
+using std::chrono::milliseconds;
+using std::chrono::seconds;
+
+using ReferenceTime = std::chrono::duration<REFERENCE_TIME,std::ratio<1,10000000>>;
+
+inline constexpr ReferenceTime operator "" _reftime(unsigned long long int n) noexcept
+{ return ReferenceTime{static_cast<REFERENCE_TIME>(n)}; }
+
#define MONO SPEAKER_FRONT_CENTER
#define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)
@@ -92,20 +104,51 @@ inline constexpr REFERENCE_TIME operator "" _reftime(unsigned long long int n) n
#define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
#define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
#define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
-#define X7DOT1_WIDE (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_FRONT_LEFT_OF_CENTER|SPEAKER_FRONT_RIGHT_OF_CENTER)
+#define X7DOT1DOT4 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT|SPEAKER_TOP_FRONT_LEFT|SPEAKER_TOP_FRONT_RIGHT|SPEAKER_TOP_BACK_LEFT|SPEAKER_TOP_BACK_RIGHT)
-#define REFTIME_PER_SEC 10000000_reftime
+constexpr inline DWORD MaskFromTopBits(DWORD b) noexcept
+{
+ b |= b>>1;
+ b |= b>>2;
+ b |= b>>4;
+ b |= b>>8;
+ b |= b>>16;
+ return b;
+}
+constexpr DWORD MonoMask{MaskFromTopBits(MONO)};
+constexpr DWORD StereoMask{MaskFromTopBits(STEREO)};
+constexpr DWORD QuadMask{MaskFromTopBits(QUAD)};
+constexpr DWORD X51Mask{MaskFromTopBits(X5DOT1)};
+constexpr DWORD X51RearMask{MaskFromTopBits(X5DOT1REAR)};
+constexpr DWORD X61Mask{MaskFromTopBits(X6DOT1)};
+constexpr DWORD X71Mask{MaskFromTopBits(X7DOT1)};
+constexpr DWORD X714Mask{MaskFromTopBits(X7DOT1DOT4)};
-#define DEVNAME_HEAD "OpenAL Soft on "
+constexpr char DevNameHead[] = "OpenAL Soft on ";
+constexpr size_t DevNameHeadLen{al::size(DevNameHead) - 1};
-/* Scales the given value using 64-bit integer math, ceiling the result. */
-inline int64_t ScaleCeil(int64_t val, int64_t new_scale, int64_t old_scale)
+/* Scales the given reftime value, rounding the result. */
+inline uint RefTime2Samples(const ReferenceTime &val, uint srate)
{
- return (val*new_scale + old_scale-1) / old_scale;
+ const auto retval = (val*srate + ReferenceTime{seconds{1}}/2) / seconds{1};
+ return static_cast<uint>(mini64(retval, std::numeric_limits<uint>::max()));
}
+class GuidPrinter {
+ char mMsg[64];
+
+public:
+ GuidPrinter(const GUID &guid)
+ {
+ std::snprintf(mMsg, al::size(mMsg), "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
+ DWORD{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]);
+ }
+ const char *c_str() const { return mMsg; }
+};
+
struct PropVariant {
PROPVARIANT mProp;
@@ -139,10 +182,9 @@ struct DevMap {
bool checkName(const al::vector<DevMap> &list, const std::string &name)
{
- return std::find_if(list.cbegin(), list.cend(),
- [&name](const DevMap &entry) -> bool
- { return entry.name == name; }
- ) != list.cend();
+ auto match_name = [&name](const DevMap &entry) -> bool
+ { return entry.name == name; };
+ return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
}
al::vector<DevMap> PlaybackDevices;
@@ -152,15 +194,16 @@ al::vector<DevMap> CaptureDevices;
using NameGUIDPair = std::pair<std::string,std::string>;
NameGUIDPair get_device_name_and_guid(IMMDevice *device)
{
- std::string name{DEVNAME_HEAD};
- std::string guid;
+ static constexpr char UnknownName[]{"Unknown Device Name"};
+ static constexpr char UnknownGuid[]{"Unknown Device GUID"};
+ std::string name, guid;
- IPropertyStore *ps;
- HRESULT hr = device->OpenPropertyStore(STGM_READ, &ps);
+ ComPtr<IPropertyStore> ps;
+ HRESULT hr = device->OpenPropertyStore(STGM_READ, ps.getPtr());
if(FAILED(hr))
{
WARN("OpenPropertyStore failed: 0x%08lx\n", hr);
- return { name+"Unknown Device Name", "Unknown Device GUID" };
+ return std::make_pair(UnknownName, UnknownGuid);
}
PropVariant pvprop;
@@ -168,14 +211,14 @@ NameGUIDPair get_device_name_and_guid(IMMDevice *device)
if(FAILED(hr))
{
WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr);
- name += "Unknown Device Name";
+ name += UnknownName;
}
else if(pvprop->vt == VT_LPWSTR)
name += wstr_to_utf8(pvprop->pwszVal);
else
{
WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt);
- name += "Unknown Device Name";
+ name += UnknownName;
}
pvprop.clear();
@@ -183,60 +226,61 @@ NameGUIDPair get_device_name_and_guid(IMMDevice *device)
if(FAILED(hr))
{
WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr);
- guid = "Unknown Device GUID";
+ guid = UnknownGuid;
}
else if(pvprop->vt == VT_LPWSTR)
guid = wstr_to_utf8(pvprop->pwszVal);
else
{
WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt);
- guid = "Unknown Device GUID";
+ guid = UnknownGuid;
}
- ps->Release();
-
- return {name, guid};
+ return std::make_pair(std::move(name), std::move(guid));
}
-void get_device_formfactor(IMMDevice *device, EndpointFormFactor *formfactor)
+EndpointFormFactor get_device_formfactor(IMMDevice *device)
{
- IPropertyStore *ps;
- HRESULT hr = device->OpenPropertyStore(STGM_READ, &ps);
+ ComPtr<IPropertyStore> ps;
+ HRESULT hr{device->OpenPropertyStore(STGM_READ, ps.getPtr())};
if(FAILED(hr))
{
WARN("OpenPropertyStore failed: 0x%08lx\n", hr);
- return;
+ return UnknownFormFactor;
}
+ EndpointFormFactor formfactor{UnknownFormFactor};
PropVariant pvform;
- hr = ps->GetValue(reinterpret_cast<const PROPERTYKEY&>(PKEY_AudioEndpoint_FormFactor), pvform.get());
+ hr = ps->GetValue(PKEY_AudioEndpoint_FormFactor, pvform.get());
if(FAILED(hr))
WARN("GetValue AudioEndpoint_FormFactor failed: 0x%08lx\n", hr);
else if(pvform->vt == VT_UI4)
- *formfactor = static_cast<EndpointFormFactor>(pvform->ulVal);
- else if(pvform->vt == VT_EMPTY)
- *formfactor = UnknownFormFactor;
- else
+ formfactor = static_cast<EndpointFormFactor>(pvform->ulVal);
+ else if(pvform->vt != VT_EMPTY)
WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform->vt);
-
- ps->Release();
+ return formfactor;
}
void add_device(IMMDevice *device, const WCHAR *devid, al::vector<DevMap> &list)
{
- std::string basename, guidstr;
- std::tie(basename, guidstr) = get_device_name_and_guid(device);
+ for(auto &entry : list)
+ {
+ if(entry.devid == devid)
+ return;
+ }
+
+ auto name_guid = get_device_name_and_guid(device);
int count{1};
- std::string newname{basename};
+ std::string newname{name_guid.first};
while(checkName(list, newname))
{
- newname = basename;
+ newname = name_guid.first;
newname += " #";
newname += std::to_string(++count);
}
- list.emplace_back(std::move(newname), std::move(guidstr), devid);
+ list.emplace_back(std::move(newname), std::move(name_guid.second), devid);
const DevMap &newentry = list.back();
TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", newentry.name.c_str(),
@@ -247,7 +291,7 @@ WCHAR *get_device_id(IMMDevice *device)
{
WCHAR *devid;
- HRESULT hr = device->GetId(&devid);
+ const HRESULT hr{device->GetId(&devid)};
if(FAILED(hr))
{
ERR("Failed to get device id: %lx\n", hr);
@@ -257,55 +301,47 @@ WCHAR *get_device_id(IMMDevice *device)
return devid;
}
-HRESULT probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, al::vector<DevMap> &list)
+void probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, al::vector<DevMap> &list)
{
- IMMDeviceCollection *coll;
- HRESULT hr{devenum->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE, &coll)};
+ al::vector<DevMap>{}.swap(list);
+
+ ComPtr<IMMDeviceCollection> coll;
+ HRESULT hr{devenum->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE, coll.getPtr())};
if(FAILED(hr))
{
ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr);
- return hr;
+ return;
}
- IMMDevice *defdev{nullptr};
- WCHAR *defdevid{nullptr};
UINT count{0};
hr = coll->GetCount(&count);
if(SUCCEEDED(hr) && count > 0)
- {
- list.clear();
list.reserve(count);
- hr = devenum->GetDefaultAudioEndpoint(flowdir, eMultimedia, &defdev);
- }
- if(SUCCEEDED(hr) && defdev != nullptr)
+ ComPtr<IMMDevice> device;
+ hr = devenum->GetDefaultAudioEndpoint(flowdir, eMultimedia, device.getPtr());
+ if(SUCCEEDED(hr))
{
- defdevid = get_device_id(defdev);
- if(defdevid)
- add_device(defdev, defdevid, list);
+ if(WCHAR *devid{get_device_id(device.get())})
+ {
+ add_device(device.get(), devid, list);
+ CoTaskMemFree(devid);
+ }
+ device = nullptr;
}
for(UINT i{0};i < count;++i)
{
- IMMDevice *device;
- hr = coll->Item(i, &device);
+ hr = coll->Item(i, device.getPtr());
if(FAILED(hr)) continue;
- WCHAR *devid{get_device_id(device)};
- if(devid)
+ if(WCHAR *devid{get_device_id(device.get())})
{
- if(!defdevid || wcscmp(devid, defdevid) != 0)
- add_device(device, devid, list);
+ add_device(device.get(), devid, list);
CoTaskMemFree(devid);
}
- device->Release();
+ device = nullptr;
}
-
- if(defdev) defdev->Release();
- if(defdevid) CoTaskMemFree(defdevid);
- coll->Release();
-
- return S_OK;
}
@@ -356,21 +392,6 @@ void TraceFormat(const char *msg, const WAVEFORMATEX *format)
constexpr size_t fmtex_extra_size{sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX)};
if(format->wFormatTag == WAVE_FORMAT_EXTENSIBLE && format->cbSize >= fmtex_extra_size)
{
- class GuidPrinter {
- char mMsg[64];
-
- public:
- GuidPrinter(const GUID &guid)
- {
- std::snprintf(mMsg, al::size(mMsg),
- "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
- DWORD{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]);
- }
- const char *c_str() const { return mMsg; }
- };
-
const WAVEFORMATEXTENSIBLE *fmtex{
CONTAINING_RECORD(format, const WAVEFORMATEXTENSIBLE, Format)};
TRACE("%s:\n"
@@ -411,9 +432,9 @@ enum class MsgType {
CloseDevice,
EnumeratePlayback,
EnumerateCapture,
- QuitThread,
- Count
+ Count,
+ QuitThread = Count
};
constexpr char MessageStr[static_cast<size_t>(MsgType::Count)][20]{
@@ -423,8 +444,7 @@ constexpr char MessageStr[static_cast<size_t>(MsgType::Count)][20]{
"Stop Device",
"Close Device",
"Enumerate Playback",
- "Enumerate Capture",
- "Quit"
+ "Enumerate Capture"
};
@@ -432,7 +452,7 @@ constexpr char MessageStr[static_cast<size_t>(MsgType::Count)][20]{
struct WasapiProxy {
virtual ~WasapiProxy() = default;
- virtual HRESULT openProxy() = 0;
+ virtual HRESULT openProxy(const char *name) = 0;
virtual void closeProxy() = 0;
virtual HRESULT resetProxy() = 0;
@@ -442,18 +462,25 @@ struct WasapiProxy {
struct Msg {
MsgType mType;
WasapiProxy *mProxy;
+ const char *mParam;
std::promise<HRESULT> mPromise;
+
+ explicit operator bool() const noexcept { return mType != MsgType::QuitThread; }
};
+ static std::thread sThread;
static std::deque<Msg> mMsgQueue;
static std::mutex mMsgQueueLock;
static std::condition_variable mMsgQueueCond;
+ static std::mutex sThreadLock;
+ static size_t sInitCount;
- std::future<HRESULT> pushMessage(MsgType type)
+ std::future<HRESULT> pushMessage(MsgType type, const char *param=nullptr)
{
std::promise<HRESULT> promise;
std::future<HRESULT> future{promise.get_future()};
- { std::lock_guard<std::mutex> _{mMsgQueueLock};
- mMsgQueue.emplace_back(Msg{type, this, std::move(promise)});
+ {
+ std::lock_guard<std::mutex> _{mMsgQueueLock};
+ mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)});
}
mMsgQueueCond.notify_one();
return future;
@@ -463,84 +490,89 @@ struct WasapiProxy {
{
std::promise<HRESULT> promise;
std::future<HRESULT> future{promise.get_future()};
- { std::lock_guard<std::mutex> _{mMsgQueueLock};
- mMsgQueue.emplace_back(Msg{type, nullptr, std::move(promise)});
+ {
+ std::lock_guard<std::mutex> _{mMsgQueueLock};
+ mMsgQueue.emplace_back(Msg{type, nullptr, nullptr, std::move(promise)});
}
mMsgQueueCond.notify_one();
return future;
}
- static bool popMessage(Msg &msg)
+ static Msg popMessage()
{
std::unique_lock<std::mutex> lock{mMsgQueueLock};
- while(mMsgQueue.empty())
- mMsgQueueCond.wait(lock);
- msg = std::move(mMsgQueue.front());
+ mMsgQueueCond.wait(lock, []{return !mMsgQueue.empty();});
+ Msg msg{std::move(mMsgQueue.front())};
mMsgQueue.pop_front();
- return msg.mType != MsgType::QuitThread;
+ return msg;
}
static int messageHandler(std::promise<HRESULT> *promise);
+
+ static HRESULT InitThread()
+ {
+ std::lock_guard<std::mutex> _{sThreadLock};
+ HRESULT res{S_OK};
+ if(!sThread.joinable())
+ {
+ std::promise<HRESULT> promise;
+ auto future = promise.get_future();
+
+ sThread = std::thread{&WasapiProxy::messageHandler, &promise};
+ res = future.get();
+ if(FAILED(res))
+ {
+ sThread.join();
+ return res;
+ }
+ }
+ ++sInitCount;
+ return res;
+ }
+
+ static void DeinitThread()
+ {
+ std::lock_guard<std::mutex> _{sThreadLock};
+ if(!--sInitCount && sThread.joinable())
+ {
+ pushMessageStatic(MsgType::QuitThread);
+ sThread.join();
+ }
+ }
};
+std::thread WasapiProxy::sThread;
std::deque<WasapiProxy::Msg> WasapiProxy::mMsgQueue;
std::mutex WasapiProxy::mMsgQueueLock;
std::condition_variable WasapiProxy::mMsgQueueCond;
+std::mutex WasapiProxy::sThreadLock;
+size_t WasapiProxy::sInitCount{0};
int WasapiProxy::messageHandler(std::promise<HRESULT> *promise)
{
TRACE("Starting message thread\n");
- HRESULT cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
- if(FAILED(cohr))
- {
- WARN("Failed to initialize COM: 0x%08lx\n", cohr);
- promise->set_value(cohr);
- return 0;
- }
-
- void *ptr{};
- HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
- IID_IMMDeviceEnumerator, &ptr)};
+ HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)};
if(FAILED(hr))
{
- WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr);
+ WARN("Failed to initialize COM: 0x%08lx\n", hr);
promise->set_value(hr);
- CoUninitialize();
return 0;
}
- auto Enumerator = static_cast<IMMDeviceEnumerator*>(ptr);
- Enumerator->Release();
- Enumerator = nullptr;
- CoUninitialize();
-
- TRACE("Message thread initialization complete\n");
promise->set_value(S_OK);
promise = nullptr;
TRACE("Starting message loop\n");
- ALuint deviceCount{0};
- Msg msg;
- while(popMessage(msg))
+ while(Msg msg{popMessage()})
{
- TRACE("Got message \"%s\" (0x%04x, this=%p)\n",
- MessageStr[static_cast<size_t>(msg.mType)], static_cast<int>(msg.mType),
- decltype(std::declval<void*>()){msg.mProxy});
+ TRACE("Got message \"%s\" (0x%04x, this=%p, param=%p)\n",
+ MessageStr[static_cast<size_t>(msg.mType)], static_cast<uint>(msg.mType),
+ static_cast<void*>(msg.mProxy), static_cast<const void*>(msg.mParam));
switch(msg.mType)
{
case MsgType::OpenDevice:
- hr = cohr = S_OK;
- if(++deviceCount == 1)
- hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
- if(SUCCEEDED(hr))
- hr = msg.mProxy->openProxy();
+ hr = msg.mProxy->openProxy(msg.mParam);
msg.mPromise.set_value(hr);
-
- if(FAILED(hr))
- {
- if(--deviceCount == 0 && SUCCEEDED(cohr))
- CoUninitialize();
- }
continue;
case MsgType::ResetDevice:
@@ -561,78 +593,77 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise)
case MsgType::CloseDevice:
msg.mProxy->closeProxy();
msg.mPromise.set_value(S_OK);
-
- if(--deviceCount == 0)
- CoUninitialize();
continue;
case MsgType::EnumeratePlayback:
case MsgType::EnumerateCapture:
- hr = cohr = S_OK;
- if(++deviceCount == 1)
- hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
- if(SUCCEEDED(hr))
- hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, &ptr);
- if(FAILED(hr))
- msg.mPromise.set_value(hr);
- else
{
- Enumerator = static_cast<IMMDeviceEnumerator*>(ptr);
-
- if(msg.mType == MsgType::EnumeratePlayback)
- hr = probe_devices(Enumerator, eRender, PlaybackDevices);
- else if(msg.mType == MsgType::EnumerateCapture)
- hr = probe_devices(Enumerator, eCapture, CaptureDevices);
- msg.mPromise.set_value(hr);
+ void *ptr{};
+ hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IMMDeviceEnumerator, &ptr);
+ if(FAILED(hr))
+ msg.mPromise.set_value(hr);
+ else
+ {
+ ComPtr<IMMDeviceEnumerator> devenum{static_cast<IMMDeviceEnumerator*>(ptr)};
- Enumerator->Release();
- Enumerator = nullptr;
+ if(msg.mType == MsgType::EnumeratePlayback)
+ probe_devices(devenum.get(), eRender, PlaybackDevices);
+ else if(msg.mType == MsgType::EnumerateCapture)
+ probe_devices(devenum.get(), eCapture, CaptureDevices);
+ msg.mPromise.set_value(S_OK);
+ }
+ continue;
}
- if(--deviceCount == 0 && SUCCEEDED(cohr))
- CoUninitialize();
- continue;
-
- default:
- ERR("Unexpected message: %u\n", static_cast<unsigned int>(msg.mType));
- msg.mPromise.set_value(E_FAIL);
- continue;
+ case MsgType::QuitThread:
+ break;
}
+ ERR("Unexpected message: %u\n", static_cast<uint>(msg.mType));
+ msg.mPromise.set_value(E_FAIL);
}
TRACE("Message loop finished\n");
+ CoUninitialize();
return 0;
}
struct WasapiPlayback final : public BackendBase, WasapiProxy {
- WasapiPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+ WasapiPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~WasapiPlayback() override;
int mixerProc();
- void open(const ALCchar *name) override;
- HRESULT openProxy() override;
+ void open(const char *name) override;
+ HRESULT openProxy(const char *name) override;
void closeProxy() override;
bool reset() override;
HRESULT resetProxy() override;
- bool start() override;
+ void start() override;
HRESULT startProxy() override;
void stop() override;
void stopProxy() override;
ClockLatency getClockLatency() override;
- std::wstring mDevId;
-
- IMMDevice *mMMDev{nullptr};
- IAudioClient *mClient{nullptr};
- IAudioRenderClient *mRender{nullptr};
+ HRESULT mOpenStatus{E_FAIL};
+ ComPtr<IMMDevice> mMMDev{nullptr};
+ ComPtr<IAudioClient> mClient{nullptr};
+ ComPtr<IAudioRenderClient> mRender{nullptr};
HANDLE mNotifyEvent{nullptr};
+ UINT32 mOrigBufferSize{}, mOrigUpdateSize{};
+ std::unique_ptr<char[]> mResampleBuffer{};
+ uint mBufferFilled{0};
+ SampleConverterPtr mResampler;
+
+ WAVEFORMATEXTENSIBLE mFormat{};
std::atomic<UINT32> mPadding{0u};
+ std::mutex mMutex;
+
std::atomic<bool> mKillNow{true};
std::thread mThread;
@@ -641,7 +672,12 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy {
WasapiPlayback::~WasapiPlayback()
{
- pushMessage(MsgType::CloseDevice).wait();
+ if(SUCCEEDED(mOpenStatus))
+ {
+ pushMessage(MsgType::CloseDevice).wait();
+ DeinitThread();
+ }
+ mOpenStatus = E_FAIL;
if(mNotifyEvent != nullptr)
CloseHandle(mNotifyEvent);
@@ -651,19 +687,20 @@ WasapiPlayback::~WasapiPlayback()
FORCE_ALIGN int WasapiPlayback::mixerProc()
{
- HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+ HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)};
if(FAILED(hr))
{
ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr);
- aluHandleDisconnect(mDevice, "COM init failed: 0x%08lx", hr);
+ mDevice->handleDisconnect("COM init failed: 0x%08lx", hr);
return 1;
}
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
- const ALuint update_size{mDevice->UpdateSize};
- const UINT32 buffer_len{mDevice->BufferSize};
+ const uint frame_size{mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8u};
+ const uint update_size{mOrigUpdateSize};
+ const UINT32 buffer_len{mOrigBufferSize};
while(!mKillNow.load(std::memory_order_relaxed))
{
UINT32 written;
@@ -671,12 +708,12 @@ FORCE_ALIGN int WasapiPlayback::mixerProc()
if(FAILED(hr))
{
ERR("Failed to get padding: 0x%08lx\n", hr);
- aluHandleDisconnect(mDevice, "Failed to retrieve buffer padding: 0x%08lx", hr);
+ mDevice->handleDisconnect("Failed to retrieve buffer padding: 0x%08lx", hr);
break;
}
mPadding.store(written, std::memory_order_relaxed);
- ALuint len{buffer_len - written};
+ uint len{buffer_len - written};
if(len < update_size)
{
DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)};
@@ -689,16 +726,45 @@ FORCE_ALIGN int WasapiPlayback::mixerProc()
hr = mRender->GetBuffer(len, &buffer);
if(SUCCEEDED(hr))
{
- std::unique_lock<WasapiPlayback> dlock{*this};
- aluMixData(mDevice, buffer, len);
- mPadding.store(written + len, std::memory_order_relaxed);
- dlock.unlock();
+ if(mResampler)
+ {
+ std::lock_guard<std::mutex> _{mMutex};
+ for(UINT32 done{0};done < len;)
+ {
+ if(mBufferFilled == 0)
+ {
+ mDevice->renderSamples(mResampleBuffer.get(), mDevice->UpdateSize,
+ mFormat.Format.nChannels);
+ mBufferFilled = mDevice->UpdateSize;
+ }
+
+ const void *src{mResampleBuffer.get()};
+ uint srclen{mBufferFilled};
+ uint got{mResampler->convert(&src, &srclen, buffer, len-done)};
+ buffer += got*frame_size;
+ done += got;
+
+ mPadding.store(written + done, std::memory_order_relaxed);
+ if(srclen)
+ {
+ const char *bsrc{static_cast<const char*>(src)};
+ std::copy(bsrc, bsrc + srclen*frame_size, mResampleBuffer.get());
+ }
+ mBufferFilled = srclen;
+ }
+ }
+ else
+ {
+ std::lock_guard<std::mutex> _{mMutex};
+ mDevice->renderSamples(buffer, len, mFormat.Format.nChannels);
+ mPadding.store(written + len, std::memory_order_relaxed);
+ }
hr = mRender->ReleaseBuffer(len, 0);
}
if(FAILED(hr))
{
ERR("Failed to buffer data: 0x%08lx\n", hr);
- aluHandleDisconnect(mDevice, "Failed to send playback samples: 0x%08lx", hr);
+ mDevice->handleDisconnect("Failed to send playback samples: 0x%08lx", hr);
break;
}
}
@@ -709,103 +775,101 @@ FORCE_ALIGN int WasapiPlayback::mixerProc()
}
-void WasapiPlayback::open(const ALCchar *name)
+void WasapiPlayback::open(const char *name)
{
- HRESULT hr{S_OK};
+ if(SUCCEEDED(mOpenStatus))
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Unexpected duplicate open call"};
mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if(mNotifyEvent == nullptr)
{
ERR("Failed to create notify events: %lu\n", GetLastError());
- hr = E_FAIL;
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to create notify events"};
}
- if(SUCCEEDED(hr))
+ HRESULT hr{InitThread()};
+ if(FAILED(hr))
{
- if(name)
- {
- if(PlaybackDevices.empty())
- pushMessage(MsgType::EnumeratePlayback).wait();
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to init COM thread: 0x%08lx", hr};
+ }
- hr = E_FAIL;
- auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
- [name](const DevMap &entry) -> bool
- { return entry.name == name || entry.endpoint_guid == name; }
- );
- if(iter == PlaybackDevices.cend())
- {
- std::wstring wname{utf8_to_wstr(name)};
- iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
- [&wname](const DevMap &entry) -> bool
- { return entry.devid == wname; }
- );
- }
- if(iter == PlaybackDevices.cend())
- WARN("Failed to find device name matching \"%s\"\n", name);
- else
- {
- mDevId = iter->devid;
- mDevice->DeviceName = iter->name;
- hr = S_OK;
- }
+ if(name)
+ {
+ if(PlaybackDevices.empty())
+ pushMessage(MsgType::EnumeratePlayback);
+ if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0)
+ {
+ name += DevNameHeadLen;
+ if(*name == '\0')
+ name = nullptr;
}
}
- if(SUCCEEDED(hr))
- hr = pushMessage(MsgType::OpenDevice).get();
-
- if(FAILED(hr))
+ mOpenStatus = pushMessage(MsgType::OpenDevice, name).get();
+ if(FAILED(mOpenStatus))
{
- if(mNotifyEvent != nullptr)
- CloseHandle(mNotifyEvent);
- mNotifyEvent = nullptr;
-
- mDevId.clear();
-
- throw al::backend_exception{ALC_INVALID_VALUE, "Device init failed: 0x%08lx", hr};
+ DeinitThread();
+ throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
+ mOpenStatus};
}
}
-HRESULT WasapiPlayback::openProxy()
+HRESULT WasapiPlayback::openProxy(const char *name)
{
- void *ptr;
- HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, &ptr)};
- if(SUCCEEDED(hr))
+ const wchar_t *devid{nullptr};
+ if(name)
{
- auto Enumerator = static_cast<IMMDeviceEnumerator*>(ptr);
- if(mDevId.empty())
- hr = Enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &mMMDev);
- else
- hr = Enumerator->GetDevice(mDevId.c_str(), &mMMDev);
- Enumerator->Release();
+ auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
+ [name](const DevMap &entry) -> bool
+ { return entry.name == name || entry.endpoint_guid == name; });
+ if(iter == PlaybackDevices.cend())
+ {
+ const std::wstring wname{utf8_to_wstr(name)};
+ iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
+ [&wname](const DevMap &entry) -> bool
+ { return entry.devid == wname; });
+ }
+ if(iter == PlaybackDevices.cend())
+ {
+ WARN("Failed to find device name matching \"%s\"\n", name);
+ return E_FAIL;
+ }
+ name = iter->name.c_str();
+ devid = iter->devid.c_str();
}
- if(SUCCEEDED(hr))
- hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr);
+
+ void *ptr;
+ ComPtr<IMMDevice> mmdev;
+ HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IMMDeviceEnumerator, &ptr)};
if(SUCCEEDED(hr))
{
- mClient = static_cast<IAudioClient*>(ptr);
- if(mDevice->DeviceName.empty())
- mDevice->DeviceName = get_device_name_and_guid(mMMDev).first;
+ ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)};
+ if(!devid)
+ hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, mmdev.getPtr());
+ else
+ hr = enumerator->GetDevice(devid, mmdev.getPtr());
}
-
if(FAILED(hr))
{
- if(mMMDev)
- mMMDev->Release();
- mMMDev = nullptr;
+ WARN("Failed to open device \"%s\"\n", name?name:"(default)");
+ return hr;
}
+ mClient = nullptr;
+ mMMDev = std::move(mmdev);
+ if(name) mDevice->DeviceName = std::string{DevNameHead} + name;
+ else mDevice->DeviceName = DevNameHead + get_device_name_and_guid(mMMDev.get()).first;
+
return hr;
}
void WasapiPlayback::closeProxy()
{
- if(mClient)
- mClient->Release();
mClient = nullptr;
-
- if(mMMDev)
- mMMDev->Release();
mMMDev = nullptr;
}
@@ -814,24 +878,22 @@ bool WasapiPlayback::reset()
{
HRESULT hr{pushMessage(MsgType::ResetDevice).get()};
if(FAILED(hr))
- throw al::backend_exception{ALC_INVALID_VALUE, "0x%08lx", hr};
+ throw al::backend_exception{al::backend_error::DeviceError, "0x%08lx", hr};
return true;
}
HRESULT WasapiPlayback::resetProxy()
{
- if(mClient)
- mClient->Release();
mClient = nullptr;
void *ptr;
- HRESULT hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr);
+ HRESULT hr{mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr)};
if(FAILED(hr))
{
ERR("Failed to reactivate audio client: 0x%08lx\n", hr);
return hr;
}
- mClient = static_cast<IAudioClient*>(ptr);
+ mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)};
WAVEFORMATEX *wfx;
hr = mClient->GetMixFormat(&wfx);
@@ -840,6 +902,7 @@ HRESULT WasapiPlayback::resetProxy()
ERR("Failed to get mix format: 0x%08lx\n", hr);
return hr;
}
+ TraceFormat("Device mix format", wfx);
WAVEFORMATEXTENSIBLE OutputType;
if(!MakeExtensible(&OutputType, wfx))
@@ -850,97 +913,116 @@ HRESULT WasapiPlayback::resetProxy()
CoTaskMemFree(wfx);
wfx = nullptr;
- const REFERENCE_TIME per_time{mDevice->UpdateSize * REFTIME_PER_SEC / mDevice->Frequency};
- const REFERENCE_TIME buf_time{mDevice->BufferSize * REFTIME_PER_SEC / mDevice->Frequency};
+ const ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency};
+ const ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency};
+ bool isRear51{false};
- if(!mDevice->Flags.get<FrequencyRequest>())
+ if(!mDevice->Flags.test(FrequencyRequest))
mDevice->Frequency = OutputType.Format.nSamplesPerSec;
- if(!mDevice->Flags.get<ChannelsRequest>())
+ if(!mDevice->Flags.test(ChannelsRequest))
{
- if(OutputType.Format.nChannels == 1 && OutputType.dwChannelMask == MONO)
- mDevice->FmtChans = DevFmtMono;
- else if(OutputType.Format.nChannels == 2 && OutputType.dwChannelMask == STEREO)
- mDevice->FmtChans = DevFmtStereo;
- else if(OutputType.Format.nChannels == 4 && OutputType.dwChannelMask == QUAD)
- mDevice->FmtChans = DevFmtQuad;
- else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1)
- mDevice->FmtChans = DevFmtX51;
- else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1REAR)
- mDevice->FmtChans = DevFmtX51Rear;
- else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1)
- mDevice->FmtChans = DevFmtX61;
- else if(OutputType.Format.nChannels == 8 && (OutputType.dwChannelMask == X7DOT1 || OutputType.dwChannelMask == X7DOT1_WIDE))
+ /* If not requesting a channel configuration, auto-select given what
+ * fits the mask's lsb (to ensure no gaps in the output channels). If
+ * there's no mask, we can only assume mono or stereo.
+ */
+ const uint32_t chancount{OutputType.Format.nChannels};
+ const DWORD chanmask{OutputType.dwChannelMask};
+ if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4)
mDevice->FmtChans = DevFmtX71;
+ else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
+ mDevice->FmtChans = DevFmtX71;
+ else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
+ mDevice->FmtChans = DevFmtX61;
+ else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1)
+ mDevice->FmtChans = DevFmtX51;
+ else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR)
+ {
+ mDevice->FmtChans = DevFmtX51;
+ isRear51 = true;
+ }
+ else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
+ mDevice->FmtChans = DevFmtQuad;
+ else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask))
+ mDevice->FmtChans = DevFmtStereo;
+ else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask))
+ mDevice->FmtChans = DevFmtMono;
else
- ERR("Unhandled channel config: %d -- 0x%08lx\n", OutputType.Format.nChannels, OutputType.dwChannelMask);
+ ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount, chanmask);
+ }
+ else
+ {
+ const uint32_t chancount{OutputType.Format.nChannels};
+ const DWORD chanmask{OutputType.dwChannelMask};
+ isRear51 = (chancount == 6 && (chanmask&X51RearMask) == X5DOT1REAR);
}
OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
switch(mDevice->FmtChans)
{
- case DevFmtMono:
- OutputType.Format.nChannels = 1;
- OutputType.dwChannelMask = MONO;
- break;
- case DevFmtAmbi3D:
- mDevice->FmtChans = DevFmtStereo;
- /*fall-through*/
- case DevFmtStereo:
- OutputType.Format.nChannels = 2;
- OutputType.dwChannelMask = STEREO;
- break;
- case DevFmtQuad:
- OutputType.Format.nChannels = 4;
- OutputType.dwChannelMask = QUAD;
- break;
- case DevFmtX51:
- OutputType.Format.nChannels = 6;
- OutputType.dwChannelMask = X5DOT1;
- break;
- case DevFmtX51Rear:
- OutputType.Format.nChannels = 6;
- OutputType.dwChannelMask = X5DOT1REAR;
- break;
- case DevFmtX61:
- OutputType.Format.nChannels = 7;
- OutputType.dwChannelMask = X6DOT1;
- break;
- case DevFmtX71:
- OutputType.Format.nChannels = 8;
- OutputType.dwChannelMask = X7DOT1;
- break;
+ case DevFmtMono:
+ OutputType.Format.nChannels = 1;
+ OutputType.dwChannelMask = MONO;
+ break;
+ case DevFmtAmbi3D:
+ mDevice->FmtChans = DevFmtStereo;
+ /*fall-through*/
+ case DevFmtStereo:
+ OutputType.Format.nChannels = 2;
+ OutputType.dwChannelMask = STEREO;
+ break;
+ case DevFmtQuad:
+ OutputType.Format.nChannels = 4;
+ OutputType.dwChannelMask = QUAD;
+ break;
+ case DevFmtX51:
+ OutputType.Format.nChannels = 6;
+ OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1;
+ break;
+ case DevFmtX61:
+ OutputType.Format.nChannels = 7;
+ OutputType.dwChannelMask = X6DOT1;
+ break;
+ case DevFmtX71:
+ case DevFmtX3D71:
+ OutputType.Format.nChannels = 8;
+ OutputType.dwChannelMask = X7DOT1;
+ break;
+ case DevFmtX714:
+ OutputType.Format.nChannels = 12;
+ OutputType.dwChannelMask = X7DOT1DOT4;
+ break;
}
switch(mDevice->FmtType)
{
- case DevFmtByte:
- mDevice->FmtType = DevFmtUByte;
- /* fall-through */
- case DevFmtUByte:
- OutputType.Format.wBitsPerSample = 8;
- OutputType.Samples.wValidBitsPerSample = 8;
- OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
- break;
- case DevFmtUShort:
- mDevice->FmtType = DevFmtShort;
- /* fall-through */
- case DevFmtShort:
- OutputType.Format.wBitsPerSample = 16;
- OutputType.Samples.wValidBitsPerSample = 16;
- OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
- break;
- case DevFmtUInt:
- mDevice->FmtType = DevFmtInt;
- /* fall-through */
- case DevFmtInt:
- OutputType.Format.wBitsPerSample = 32;
- OutputType.Samples.wValidBitsPerSample = 32;
- OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
- break;
- case DevFmtFloat:
- OutputType.Format.wBitsPerSample = 32;
- OutputType.Samples.wValidBitsPerSample = 32;
- OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
- break;
+ case DevFmtByte:
+ mDevice->FmtType = DevFmtUByte;
+ /* fall-through */
+ case DevFmtUByte:
+ OutputType.Format.wBitsPerSample = 8;
+ OutputType.Samples.wValidBitsPerSample = 8;
+ OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+ break;
+ case DevFmtUShort:
+ mDevice->FmtType = DevFmtShort;
+ /* fall-through */
+ case DevFmtShort:
+ OutputType.Format.wBitsPerSample = 16;
+ OutputType.Samples.wValidBitsPerSample = 16;
+ OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+ break;
+ case DevFmtUInt:
+ mDevice->FmtType = DevFmtInt;
+ /* fall-through */
+ case DevFmtInt:
+ OutputType.Format.wBitsPerSample = 32;
+ OutputType.Samples.wValidBitsPerSample = 32;
+ OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+ break;
+ case DevFmtFloat:
+ OutputType.Format.wBitsPerSample = 32;
+ OutputType.Samples.wValidBitsPerSample = 32;
+ OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+ break;
}
OutputType.Format.nSamplesPerSec = mDevice->Frequency;
@@ -953,7 +1035,7 @@ HRESULT WasapiPlayback::resetProxy()
hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx);
if(FAILED(hr))
{
- ERR("Failed to check format support: 0x%08lx\n", hr);
+ WARN("Failed to check format support: 0x%08lx\n", hr);
hr = mClient->GetMixFormat(&wfx);
}
if(FAILED(hr))
@@ -973,27 +1055,76 @@ HRESULT WasapiPlayback::resetProxy()
CoTaskMemFree(wfx);
wfx = nullptr;
- mDevice->Frequency = OutputType.Format.nSamplesPerSec;
- if(OutputType.Format.nChannels == 1 && OutputType.dwChannelMask == MONO)
- mDevice->FmtChans = DevFmtMono;
- else if(OutputType.Format.nChannels == 2 && OutputType.dwChannelMask == STEREO)
- mDevice->FmtChans = DevFmtStereo;
- else if(OutputType.Format.nChannels == 4 && OutputType.dwChannelMask == QUAD)
- mDevice->FmtChans = DevFmtQuad;
- else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1)
- mDevice->FmtChans = DevFmtX51;
- else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1REAR)
- mDevice->FmtChans = DevFmtX51Rear;
- else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1)
- mDevice->FmtChans = DevFmtX61;
- else if(OutputType.Format.nChannels == 8 && (OutputType.dwChannelMask == X7DOT1 || OutputType.dwChannelMask == X7DOT1_WIDE))
- mDevice->FmtChans = DevFmtX71;
+ if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "wasapi", "allow-resampler", true))
+ mDevice->Frequency = OutputType.Format.nSamplesPerSec;
else
+ mDevice->Frequency = minu(mDevice->Frequency, OutputType.Format.nSamplesPerSec);
+
+ const uint32_t chancount{OutputType.Format.nChannels};
+ const DWORD chanmask{OutputType.dwChannelMask};
+ /* Don't update the channel format if the requested format fits what's
+ * supported.
+ */
+ bool chansok{false};
+ if(mDevice->Flags.test(ChannelsRequest))
{
- ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels, OutputType.dwChannelMask);
- mDevice->FmtChans = DevFmtStereo;
- OutputType.Format.nChannels = 2;
- OutputType.dwChannelMask = STEREO;
+ /* When requesting a channel configuration, make sure it fits the
+ * mask's lsb (to ensure no gaps in the output channels). If
+ * there's no mask, assume the request fits with enough channels.
+ */
+ switch(mDevice->FmtChans)
+ {
+ case DevFmtMono:
+ chansok = (chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask));
+ break;
+ case DevFmtStereo:
+ chansok = (chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask));
+ break;
+ case DevFmtQuad:
+ chansok = (chancount >= 4 && ((chanmask&QuadMask) == QUAD || !chanmask));
+ break;
+ case DevFmtX51:
+ chansok = (chancount >= 6 && ((chanmask&X51Mask) == X5DOT1
+ || (chanmask&X51RearMask) == X5DOT1REAR || !chanmask));
+ break;
+ case DevFmtX61:
+ chansok = (chancount >= 7 && ((chanmask&X61Mask) == X6DOT1 || !chanmask));
+ break;
+ case DevFmtX71:
+ case DevFmtX3D71:
+ chansok = (chancount >= 8 && ((chanmask&X71Mask) == X7DOT1 || !chanmask));
+ break;
+ case DevFmtX714:
+ chansok = (chancount >= 12 && ((chanmask&X714Mask) == X7DOT1DOT4 || !chanmask));
+ case DevFmtAmbi3D:
+ break;
+ }
+ }
+ if(!chansok)
+ {
+ if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4)
+ mDevice->FmtChans = DevFmtX714;
+ else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
+ mDevice->FmtChans = DevFmtX71;
+ else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
+ mDevice->FmtChans = DevFmtX61;
+ else if(chancount >= 6 && ((chanmask&X51Mask) == X5DOT1
+ || (chanmask&X51RearMask) == X5DOT1REAR))
+ mDevice->FmtChans = DevFmtX51;
+ else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
+ mDevice->FmtChans = DevFmtQuad;
+ else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask))
+ mDevice->FmtChans = DevFmtStereo;
+ else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask))
+ mDevice->FmtChans = DevFmtMono;
+ else
+ {
+ ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels,
+ OutputType.dwChannelMask);
+ mDevice->FmtChans = DevFmtStereo;
+ OutputType.Format.nChannels = 2;
+ OutputType.dwChannelMask = STEREO;
+ }
}
if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM))
@@ -1017,7 +1148,7 @@ HRESULT WasapiPlayback::resetProxy()
}
else
{
- ERR("Unhandled format sub-type\n");
+ ERR("Unhandled format sub-type: %s\n", GuidPrinter{OutputType.SubFormat}.c_str());
mDevice->FmtType = DevFmtShort;
if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE)
OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
@@ -1026,25 +1157,24 @@ HRESULT WasapiPlayback::resetProxy()
}
OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
}
+ mFormat = OutputType;
- EndpointFormFactor formfactor = UnknownFormFactor;
- get_device_formfactor(mMMDev, &formfactor);
- mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo &&
- (formfactor == Headphones || formfactor == Headset));
+ const EndpointFormFactor formfactor{get_device_formfactor(mMMDev.get())};
+ mDevice->Flags.set(DirectEar, (formfactor == Headphones || formfactor == Headset));
- SetDefaultWFXChannelOrder(mDevice);
+ setDefaultWFXChannelOrder();
- hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buf_time,
- 0, &OutputType.Format, nullptr);
+ hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
+ buf_time.count(), 0, &OutputType.Format, nullptr);
if(FAILED(hr))
{
ERR("Failed to initialize audio client: 0x%08lx\n", hr);
return hr;
}
- UINT32 buffer_len{}, min_len{};
- REFERENCE_TIME min_per{};
- hr = mClient->GetDevicePeriod(&min_per, nullptr);
+ UINT32 buffer_len{};
+ ReferenceTime min_per{};
+ hr = mClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(min_per), nullptr);
if(SUCCEEDED(hr))
hr = mClient->GetBufferSize(&buffer_len);
if(FAILED(hr))
@@ -1056,11 +1186,31 @@ HRESULT WasapiPlayback::resetProxy()
/* Find the nearest multiple of the period size to the update size */
if(min_per < per_time)
min_per *= maxi64((per_time + min_per/2) / min_per, 1);
- min_len = static_cast<UINT32>(ScaleCeil(min_per, mDevice->Frequency, REFTIME_PER_SEC));
- min_len = minu(min_len, buffer_len/2);
- mDevice->UpdateSize = min_len;
- mDevice->BufferSize = buffer_len;
+ mOrigBufferSize = buffer_len;
+ mOrigUpdateSize = minu(RefTime2Samples(min_per, mFormat.Format.nSamplesPerSec), buffer_len/2);
+
+ mDevice->BufferSize = static_cast<uint>(uint64_t{buffer_len} * mDevice->Frequency /
+ mFormat.Format.nSamplesPerSec);
+ mDevice->UpdateSize = minu(RefTime2Samples(min_per, mDevice->Frequency),
+ mDevice->BufferSize/2);
+
+ mResampler = nullptr;
+ mResampleBuffer = nullptr;
+ mBufferFilled = 0;
+ if(mDevice->Frequency != mFormat.Format.nSamplesPerSec)
+ {
+ mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType,
+ mFormat.Format.nChannels, mDevice->Frequency, mFormat.Format.nSamplesPerSec,
+ Resampler::FastBSinc24);
+ mResampleBuffer = std::make_unique<char[]>(size_t{mDevice->UpdateSize} *
+ mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8);
+
+ TRACE("Created converter for %s/%s format, dst: %luhz (%u), src: %uhz (%u)\n",
+ DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
+ mFormat.Format.nSamplesPerSec, mOrigUpdateSize, mDevice->Frequency,
+ mDevice->UpdateSize);
+ }
hr = mClient->SetEventHandle(mNotifyEvent);
if(FAILED(hr))
@@ -1073,17 +1223,19 @@ HRESULT WasapiPlayback::resetProxy()
}
-bool WasapiPlayback::start()
+void WasapiPlayback::start()
{
- HRESULT hr{pushMessage(MsgType::StartDevice).get()};
- return SUCCEEDED(hr) ? true : false;
+ const HRESULT hr{pushMessage(MsgType::StartDevice).get()};
+ if(FAILED(hr))
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to start playback: 0x%lx", hr};
}
HRESULT WasapiPlayback::startProxy()
{
ResetEvent(mNotifyEvent);
- HRESULT hr = mClient->Start();
+ HRESULT hr{mClient->Start()};
if(FAILED(hr))
{
ERR("Failed to start audio client: 0x%08lx\n", hr);
@@ -1094,13 +1246,12 @@ HRESULT WasapiPlayback::startProxy()
hr = mClient->GetService(IID_IAudioRenderClient, &ptr);
if(SUCCEEDED(hr))
{
- mRender = static_cast<IAudioRenderClient*>(ptr);
+ mRender = ComPtr<IAudioRenderClient>{static_cast<IAudioRenderClient*>(ptr)};
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerProc), this};
}
catch(...) {
- mRender->Release();
mRender = nullptr;
ERR("Failed to start thread\n");
hr = E_FAIL;
@@ -1125,7 +1276,6 @@ void WasapiPlayback::stopProxy()
mKillNow.store(true, std::memory_order_release);
mThread.join();
- mRender->Release();
mRender = nullptr;
mClient->Stop();
}
@@ -1135,39 +1285,44 @@ ClockLatency WasapiPlayback::getClockLatency()
{
ClockLatency ret;
- std::lock_guard<WasapiPlayback> _{*this};
+ std::lock_guard<std::mutex> _{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice);
- ret.Latency = std::chrono::seconds{mPadding.load(std::memory_order_relaxed)};
- ret.Latency /= mDevice->Frequency;
+ ret.Latency = seconds{mPadding.load(std::memory_order_relaxed)};
+ ret.Latency /= mFormat.Format.nSamplesPerSec;
+ if(mResampler)
+ {
+ auto extra = mResampler->currentInputDelay();
+ ret.Latency += std::chrono::duration_cast<nanoseconds>(extra) / mDevice->Frequency;
+ ret.Latency += nanoseconds{seconds{mBufferFilled}} / mDevice->Frequency;
+ }
return ret;
}
struct WasapiCapture final : public BackendBase, WasapiProxy {
- WasapiCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+ WasapiCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~WasapiCapture() override;
int recordProc();
- void open(const ALCchar *name) override;
- HRESULT openProxy() override;
+ void open(const char *name) override;
+ HRESULT openProxy(const char *name) override;
void closeProxy() override;
HRESULT resetProxy() override;
- bool start() override;
+ void start() override;
HRESULT startProxy() override;
void stop() override;
void stopProxy() override;
- ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
- ALCuint availableSamples() override;
-
- std::wstring mDevId;
+ void captureSamples(al::byte *buffer, uint samples) override;
+ uint availableSamples() override;
- IMMDevice *mMMDev{nullptr};
- IAudioClient *mClient{nullptr};
- IAudioCaptureClient *mCapture{nullptr};
+ HRESULT mOpenStatus{E_FAIL};
+ ComPtr<IMMDevice> mMMDev{nullptr};
+ ComPtr<IAudioClient> mClient{nullptr};
+ ComPtr<IAudioCaptureClient> mCapture{nullptr};
HANDLE mNotifyEvent{nullptr};
ChannelConverter mChannelConv{};
@@ -1182,7 +1337,12 @@ struct WasapiCapture final : public BackendBase, WasapiProxy {
WasapiCapture::~WasapiCapture()
{
- pushMessage(MsgType::CloseDevice).wait();
+ if(SUCCEEDED(mOpenStatus))
+ {
+ pushMessage(MsgType::CloseDevice).wait();
+ DeinitThread();
+ }
+ mOpenStatus = E_FAIL;
if(mNotifyEvent != nullptr)
CloseHandle(mNotifyEvent);
@@ -1192,11 +1352,11 @@ WasapiCapture::~WasapiCapture()
FORCE_ALIGN int WasapiCapture::recordProc()
{
- HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+ HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)};
if(FAILED(hr))
{
ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr);
- aluHandleDisconnect(mDevice, "COM init failed: 0x%08lx", hr);
+ mDevice->handleDisconnect("COM init failed: 0x%08lx", hr);
return 1;
}
@@ -1232,11 +1392,11 @@ FORCE_ALIGN int WasapiCapture::recordProc()
size_t dstframes;
if(mSampleConv)
{
- const ALvoid *srcdata{rdata};
- ALuint srcframes{numsamples};
+ const void *srcdata{rdata};
+ uint srcframes{numsamples};
dstframes = mSampleConv->convert(&srcdata, &srcframes, data.first.buf,
- static_cast<ALuint>(minz(data.first.len, INT_MAX)));
+ static_cast<uint>(minz(data.first.len, INT_MAX)));
if(srcframes > 0 && dstframes == data.first.len && data.second.len > 0)
{
/* If some source samples remain, all of the first dest
@@ -1244,12 +1404,12 @@ FORCE_ALIGN int WasapiCapture::recordProc()
* dest block, do another run for the second block.
*/
dstframes += mSampleConv->convert(&srcdata, &srcframes, data.second.buf,
- static_cast<ALuint>(minz(data.second.len, INT_MAX)));
+ static_cast<uint>(minz(data.second.len, INT_MAX)));
}
}
else
{
- const auto framesize = static_cast<ALuint>(mDevice->frameSizeFromFmt());
+ const uint framesize{mDevice->frameSizeFromFmt()};
size_t len1{minz(data.first.len, numsamples)};
size_t len2{minz(data.second.len, numsamples-len1)};
@@ -1268,7 +1428,7 @@ FORCE_ALIGN int WasapiCapture::recordProc()
if(FAILED(hr))
{
- aluHandleDisconnect(mDevice, "Failed to capture samples: 0x%08lx", hr);
+ mDevice->handleDisconnect("Failed to capture samples: 0x%08lx", hr);
break;
}
@@ -1282,119 +1442,112 @@ FORCE_ALIGN int WasapiCapture::recordProc()
}
-void WasapiCapture::open(const ALCchar *name)
+void WasapiCapture::open(const char *name)
{
- HRESULT hr{S_OK};
+ if(SUCCEEDED(mOpenStatus))
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Unexpected duplicate open call"};
mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if(mNotifyEvent == nullptr)
{
- ERR("Failed to create notify event: %lu\n", GetLastError());
- hr = E_FAIL;
+ ERR("Failed to create notify events: %lu\n", GetLastError());
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to create notify events"};
}
- if(SUCCEEDED(hr))
+ HRESULT hr{InitThread()};
+ if(FAILED(hr))
{
- if(name)
- {
- if(CaptureDevices.empty())
- pushMessage(MsgType::EnumerateCapture).wait();
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to init COM thread: 0x%08lx", hr};
+ }
- hr = E_FAIL;
- auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
- [name](const DevMap &entry) -> bool
- { return entry.name == name || entry.endpoint_guid == name; }
- );
- if(iter == CaptureDevices.cend())
- {
- std::wstring wname{utf8_to_wstr(name)};
- iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
- [&wname](const DevMap &entry) -> bool
- { return entry.devid == wname; }
- );
- }
- if(iter == CaptureDevices.cend())
- WARN("Failed to find device name matching \"%s\"\n", name);
- else
- {
- mDevId = iter->devid;
- mDevice->DeviceName = iter->name;
- hr = S_OK;
- }
+ if(name)
+ {
+ if(CaptureDevices.empty())
+ pushMessage(MsgType::EnumerateCapture);
+ if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0)
+ {
+ name += DevNameHeadLen;
+ if(*name == '\0')
+ name = nullptr;
}
}
- if(SUCCEEDED(hr))
- hr = pushMessage(MsgType::OpenDevice).get();
-
- if(FAILED(hr))
+ mOpenStatus = pushMessage(MsgType::OpenDevice, name).get();
+ if(FAILED(mOpenStatus))
{
- if(mNotifyEvent != nullptr)
- CloseHandle(mNotifyEvent);
- mNotifyEvent = nullptr;
-
- mDevId.clear();
-
- throw al::backend_exception{ALC_INVALID_VALUE, "Device init failed: 0x%08lx", hr};
+ DeinitThread();
+ throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
+ mOpenStatus};
}
hr = pushMessage(MsgType::ResetDevice).get();
if(FAILED(hr))
{
if(hr == E_OUTOFMEMORY)
- throw al::backend_exception{ALC_OUT_OF_MEMORY, "Out of memory"};
- throw al::backend_exception{ALC_INVALID_VALUE, "Device reset failed"};
+ throw al::backend_exception{al::backend_error::OutOfMemory, "Out of memory"};
+ throw al::backend_exception{al::backend_error::DeviceError, "Device reset failed"};
}
}
-HRESULT WasapiCapture::openProxy()
+HRESULT WasapiCapture::openProxy(const char *name)
{
+ const wchar_t *devid{nullptr};
+ if(name)
+ {
+ auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
+ [name](const DevMap &entry) -> bool
+ { return entry.name == name || entry.endpoint_guid == name; });
+ if(iter == CaptureDevices.cend())
+ {
+ const std::wstring wname{utf8_to_wstr(name)};
+ iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
+ [&wname](const DevMap &entry) -> bool
+ { return entry.devid == wname; });
+ }
+ if(iter == CaptureDevices.cend())
+ {
+ WARN("Failed to find device name matching \"%s\"\n", name);
+ return E_FAIL;
+ }
+ name = iter->name.c_str();
+ devid = iter->devid.c_str();
+ }
+
void *ptr;
HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
IID_IMMDeviceEnumerator, &ptr)};
if(SUCCEEDED(hr))
{
- auto Enumerator = static_cast<IMMDeviceEnumerator*>(ptr);
- if(mDevId.empty())
- hr = Enumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, &mMMDev);
+ ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)};
+ if(!devid)
+ hr = enumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, mMMDev.getPtr());
else
- hr = Enumerator->GetDevice(mDevId.c_str(), &mMMDev);
- Enumerator->Release();
+ hr = enumerator->GetDevice(devid, mMMDev.getPtr());
}
- if(SUCCEEDED(hr))
- hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr);
- if(SUCCEEDED(hr))
- {
- mClient = static_cast<IAudioClient*>(ptr);
- if(mDevice->DeviceName.empty())
- mDevice->DeviceName = get_device_name_and_guid(mMMDev).first;
- }
-
if(FAILED(hr))
{
- if(mMMDev)
- mMMDev->Release();
- mMMDev = nullptr;
+ WARN("Failed to open device \"%s\"\n", name?name:"(default)");
+ return hr;
}
+ mClient = nullptr;
+ if(name) mDevice->DeviceName = std::string{DevNameHead} + name;
+ else mDevice->DeviceName = DevNameHead + get_device_name_and_guid(mMMDev.get()).first;
+
return hr;
}
void WasapiCapture::closeProxy()
{
- if(mClient)
- mClient->Release();
mClient = nullptr;
-
- if(mMMDev)
- mMMDev->Release();
mMMDev = nullptr;
}
HRESULT WasapiCapture::resetProxy()
{
- if(mClient)
- mClient->Release();
mClient = nullptr;
void *ptr;
@@ -1404,86 +1557,112 @@ HRESULT WasapiCapture::resetProxy()
ERR("Failed to reactivate audio client: 0x%08lx\n", hr);
return hr;
}
- mClient = static_cast<IAudioClient*>(ptr);
+ mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)};
+
+ WAVEFORMATEX *wfx;
+ hr = mClient->GetMixFormat(&wfx);
+ if(FAILED(hr))
+ {
+ ERR("Failed to get capture format: 0x%08lx\n", hr);
+ return hr;
+ }
+ TraceFormat("Device capture format", wfx);
+
+ WAVEFORMATEXTENSIBLE InputType{};
+ if(!MakeExtensible(&InputType, wfx))
+ {
+ CoTaskMemFree(wfx);
+ return E_FAIL;
+ }
+ CoTaskMemFree(wfx);
+ wfx = nullptr;
+
+ const bool isRear51{InputType.Format.nChannels == 6
+ && (InputType.dwChannelMask&X51RearMask) == X5DOT1REAR};
// Make sure buffer is at least 100ms in size
- REFERENCE_TIME buf_time{mDevice->BufferSize * REFTIME_PER_SEC / mDevice->Frequency};
- buf_time = maxu64(buf_time, REFTIME_PER_SEC/10);
+ ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency};
+ buf_time = std::max(buf_time, ReferenceTime{milliseconds{100}});
- WAVEFORMATEXTENSIBLE OutputType{};
- OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ InputType = {};
+ InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
switch(mDevice->FmtChans)
{
- case DevFmtMono:
- OutputType.Format.nChannels = 1;
- OutputType.dwChannelMask = MONO;
- break;
- case DevFmtStereo:
- OutputType.Format.nChannels = 2;
- OutputType.dwChannelMask = STEREO;
- break;
- case DevFmtQuad:
- OutputType.Format.nChannels = 4;
- OutputType.dwChannelMask = QUAD;
- break;
- case DevFmtX51:
- OutputType.Format.nChannels = 6;
- OutputType.dwChannelMask = X5DOT1;
- break;
- case DevFmtX51Rear:
- OutputType.Format.nChannels = 6;
- OutputType.dwChannelMask = X5DOT1REAR;
- break;
- case DevFmtX61:
- OutputType.Format.nChannels = 7;
- OutputType.dwChannelMask = X6DOT1;
- break;
- case DevFmtX71:
- OutputType.Format.nChannels = 8;
- OutputType.dwChannelMask = X7DOT1;
- break;
+ case DevFmtMono:
+ InputType.Format.nChannels = 1;
+ InputType.dwChannelMask = MONO;
+ break;
+ case DevFmtStereo:
+ InputType.Format.nChannels = 2;
+ InputType.dwChannelMask = STEREO;
+ break;
+ case DevFmtQuad:
+ InputType.Format.nChannels = 4;
+ InputType.dwChannelMask = QUAD;
+ break;
+ case DevFmtX51:
+ InputType.Format.nChannels = 6;
+ InputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1;
+ break;
+ case DevFmtX61:
+ InputType.Format.nChannels = 7;
+ InputType.dwChannelMask = X6DOT1;
+ break;
+ case DevFmtX71:
+ InputType.Format.nChannels = 8;
+ InputType.dwChannelMask = X7DOT1;
+ break;
+ case DevFmtX714:
+ InputType.Format.nChannels = 12;
+ InputType.dwChannelMask = X7DOT1DOT4;
+ break;
- case DevFmtAmbi3D:
- return E_FAIL;
+ case DevFmtX3D71:
+ case DevFmtAmbi3D:
+ return E_FAIL;
}
switch(mDevice->FmtType)
{
- /* NOTE: Signedness doesn't matter, the converter will handle it. */
- case DevFmtByte:
- case DevFmtUByte:
- OutputType.Format.wBitsPerSample = 8;
- OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
- break;
- case DevFmtShort:
- case DevFmtUShort:
- OutputType.Format.wBitsPerSample = 16;
- OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
- break;
- case DevFmtInt:
- case DevFmtUInt:
- OutputType.Format.wBitsPerSample = 32;
- OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
- break;
- case DevFmtFloat:
- OutputType.Format.wBitsPerSample = 32;
- OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
- break;
+ /* NOTE: Signedness doesn't matter, the converter will handle it. */
+ case DevFmtByte:
+ case DevFmtUByte:
+ InputType.Format.wBitsPerSample = 8;
+ InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+ break;
+ case DevFmtShort:
+ case DevFmtUShort:
+ InputType.Format.wBitsPerSample = 16;
+ InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+ break;
+ case DevFmtInt:
+ case DevFmtUInt:
+ InputType.Format.wBitsPerSample = 32;
+ InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+ break;
+ case DevFmtFloat:
+ InputType.Format.wBitsPerSample = 32;
+ InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+ break;
}
- OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
- OutputType.Format.nSamplesPerSec = mDevice->Frequency;
+ InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample;
+ InputType.Format.nSamplesPerSec = mDevice->Frequency;
- OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
- OutputType.Format.wBitsPerSample / 8);
- OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
- OutputType.Format.nBlockAlign;
- OutputType.Format.cbSize = sizeof(OutputType) - sizeof(OutputType.Format);
+ InputType.Format.nBlockAlign = static_cast<WORD>(InputType.Format.nChannels *
+ InputType.Format.wBitsPerSample / 8);
+ InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec *
+ InputType.Format.nBlockAlign;
+ InputType.Format.cbSize = sizeof(InputType) - sizeof(InputType.Format);
- TraceFormat("Requesting capture format", &OutputType.Format);
- WAVEFORMATEX *wfx;
- hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx);
+ TraceFormat("Requesting capture format", &InputType.Format);
+ hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &InputType.Format, &wfx);
+ if(FAILED(hr))
+ {
+ WARN("Failed to check capture format support: 0x%08lx\n", hr);
+ hr = mClient->GetMixFormat(&wfx);
+ }
if(FAILED(hr))
{
- ERR("Failed to check format support: 0x%08lx\n", hr);
+ ERR("Failed to find a supported capture format: 0x%08lx\n", hr);
return hr;
}
@@ -1493,92 +1672,132 @@ HRESULT WasapiCapture::resetProxy()
if(wfx != nullptr)
{
TraceFormat("Got capture format", wfx);
- if(!(wfx->nChannels == OutputType.Format.nChannels ||
- (wfx->nChannels == 1 && OutputType.Format.nChannels == 2) ||
- (wfx->nChannels == 2 && OutputType.Format.nChannels == 1)))
+ if(!MakeExtensible(&InputType, wfx))
{
- ERR("Failed to get matching format, wanted: %s %s %uhz, got: %d channel%s %d-bit %luhz\n",
- DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
- mDevice->Frequency, wfx->nChannels, (wfx->nChannels==1)?"":"s", wfx->wBitsPerSample,
- wfx->nSamplesPerSec);
CoTaskMemFree(wfx);
return E_FAIL;
}
+ CoTaskMemFree(wfx);
+ wfx = nullptr;
- if(!MakeExtensible(&OutputType, wfx))
+ auto validate_fmt = [](DeviceBase *device, uint32_t chancount, DWORD chanmask) noexcept
+ -> bool
{
- CoTaskMemFree(wfx);
+ switch(device->FmtChans)
+ {
+ /* If the device wants mono, we can handle any input. */
+ case DevFmtMono:
+ return true;
+ /* If the device wants stereo, we can handle mono or stereo input. */
+ case DevFmtStereo:
+ return (chancount == 2 && (chanmask == 0 || (chanmask&StereoMask) == STEREO))
+ || (chancount == 1 && (chanmask&MonoMask) == MONO);
+ /* Otherwise, the device must match the input type. */
+ case DevFmtQuad:
+ return (chancount == 4 && (chanmask == 0 || (chanmask&QuadMask) == QUAD));
+ /* 5.1 (Side) and 5.1 (Rear) are interchangeable here. */
+ case DevFmtX51:
+ return (chancount == 6 && (chanmask == 0 || (chanmask&X51Mask) == X5DOT1
+ || (chanmask&X51RearMask) == X5DOT1REAR));
+ case DevFmtX61:
+ return (chancount == 7 && (chanmask == 0 || (chanmask&X61Mask) == X6DOT1));
+ case DevFmtX71:
+ case DevFmtX3D71:
+ return (chancount == 8 && (chanmask == 0 || (chanmask&X71Mask) == X7DOT1));
+ case DevFmtX714:
+ return (chancount == 12 && (chanmask == 0 || (chanmask&X714Mask) == X7DOT1DOT4));
+ case DevFmtAmbi3D:
+ return (chanmask == 0 && chancount == device->channelsFromFmt());
+ }
+ return false;
+ };
+ if(!validate_fmt(mDevice, InputType.Format.nChannels, InputType.dwChannelMask))
+ {
+ ERR("Failed to match format, wanted: %s %s %uhz, got: 0x%08lx mask %d channel%s %d-bit %luhz\n",
+ DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
+ mDevice->Frequency, InputType.dwChannelMask, InputType.Format.nChannels,
+ (InputType.Format.nChannels==1)?"":"s", InputType.Format.wBitsPerSample,
+ InputType.Format.nSamplesPerSec);
return E_FAIL;
}
- CoTaskMemFree(wfx);
- wfx = nullptr;
}
- DevFmtType srcType;
- if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM))
+ DevFmtType srcType{};
+ if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM))
{
- if(OutputType.Format.wBitsPerSample == 8)
+ if(InputType.Format.wBitsPerSample == 8)
srcType = DevFmtUByte;
- else if(OutputType.Format.wBitsPerSample == 16)
+ else if(InputType.Format.wBitsPerSample == 16)
srcType = DevFmtShort;
- else if(OutputType.Format.wBitsPerSample == 32)
+ else if(InputType.Format.wBitsPerSample == 32)
srcType = DevFmtInt;
else
{
- ERR("Unhandled integer bit depth: %d\n", OutputType.Format.wBitsPerSample);
+ ERR("Unhandled integer bit depth: %d\n", InputType.Format.wBitsPerSample);
return E_FAIL;
}
}
- else if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
+ else if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
{
- if(OutputType.Format.wBitsPerSample == 32)
+ if(InputType.Format.wBitsPerSample == 32)
srcType = DevFmtFloat;
else
{
- ERR("Unhandled float bit depth: %d\n", OutputType.Format.wBitsPerSample);
+ ERR("Unhandled float bit depth: %d\n", InputType.Format.wBitsPerSample);
return E_FAIL;
}
}
else
{
- ERR("Unhandled format sub-type\n");
+ ERR("Unhandled format sub-type: %s\n", GuidPrinter{InputType.SubFormat}.c_str());
return E_FAIL;
}
- if(mDevice->FmtChans == DevFmtMono && OutputType.Format.nChannels == 2)
+ if(mDevice->FmtChans == DevFmtMono && InputType.Format.nChannels != 1)
{
- mChannelConv = ChannelConverter{srcType, DevFmtStereo, mDevice->FmtChans};
- TRACE("Created %s stereo-to-mono converter\n", DevFmtTypeString(srcType));
+ uint chanmask{(1u<<InputType.Format.nChannels) - 1u};
+ /* Exclude LFE from the downmix. */
+ if((InputType.dwChannelMask&SPEAKER_LOW_FREQUENCY))
+ {
+ constexpr auto lfemask = MaskFromTopBits(SPEAKER_LOW_FREQUENCY);
+ const int lfeidx{al::popcount(InputType.dwChannelMask&lfemask) - 1};
+ chanmask &= ~(1u << lfeidx);
+ }
+
+ mChannelConv = ChannelConverter{srcType, InputType.Format.nChannels, chanmask,
+ mDevice->FmtChans};
+ TRACE("Created %s multichannel-to-mono converter\n", DevFmtTypeString(srcType));
/* The channel converter always outputs float, so change the input type
* for the resampler/type-converter.
*/
srcType = DevFmtFloat;
}
- else if(mDevice->FmtChans == DevFmtStereo && OutputType.Format.nChannels == 1)
+ else if(mDevice->FmtChans == DevFmtStereo && InputType.Format.nChannels == 1)
{
- mChannelConv = ChannelConverter{srcType, DevFmtMono, mDevice->FmtChans};
+ mChannelConv = ChannelConverter{srcType, 1, 0x1, mDevice->FmtChans};
TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType));
srcType = DevFmtFloat;
}
- if(mDevice->Frequency != OutputType.Format.nSamplesPerSec || mDevice->FmtType != srcType)
+ if(mDevice->Frequency != InputType.Format.nSamplesPerSec || mDevice->FmtType != srcType)
{
- mSampleConv = CreateSampleConverter(srcType, mDevice->FmtType, mDevice->channelsFromFmt(),
- OutputType.Format.nSamplesPerSec, mDevice->Frequency, Resampler::FastBSinc24);
+ mSampleConv = SampleConverter::Create(srcType, mDevice->FmtType,
+ mDevice->channelsFromFmt(), InputType.Format.nSamplesPerSec, mDevice->Frequency,
+ Resampler::FastBSinc24);
if(!mSampleConv)
{
ERR("Failed to create converter for %s format, dst: %s %uhz, src: %s %luhz\n",
DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
- mDevice->Frequency, DevFmtTypeString(srcType), OutputType.Format.nSamplesPerSec);
+ mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec);
return E_FAIL;
}
TRACE("Created converter for %s format, dst: %s %uhz, src: %s %luhz\n",
- DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
- mDevice->Frequency, DevFmtTypeString(srcType), OutputType.Format.nSamplesPerSec);
+ DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
+ mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec);
}
- hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buf_time,
- 0, &OutputType.Format, nullptr);
+ hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
+ buf_time.count(), 0, &InputType.Format, nullptr);
if(FAILED(hr))
{
ERR("Failed to initialize audio client: 0x%08lx\n", hr);
@@ -1586,8 +1805,8 @@ HRESULT WasapiCapture::resetProxy()
}
UINT32 buffer_len{};
- REFERENCE_TIME min_per{};
- hr = mClient->GetDevicePeriod(&min_per, nullptr);
+ ReferenceTime min_per{};
+ hr = mClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(min_per), nullptr);
if(SUCCEEDED(hr))
hr = mClient->GetBufferSize(&buffer_len);
if(FAILED(hr))
@@ -1595,12 +1814,10 @@ HRESULT WasapiCapture::resetProxy()
ERR("Failed to get buffer size: 0x%08lx\n", hr);
return hr;
}
- mDevice->UpdateSize = static_cast<ALuint>(ScaleCeil(min_per, mDevice->Frequency,
- REFTIME_PER_SEC));
+ mDevice->UpdateSize = RefTime2Samples(min_per, mDevice->Frequency);
mDevice->BufferSize = buffer_len;
- buffer_len = maxu(mDevice->BufferSize, buffer_len);
- mRing = CreateRingBuffer(buffer_len, mDevice->frameSizeFromFmt(), false);
+ mRing = RingBuffer::Create(buffer_len, mDevice->frameSizeFromFmt(), false);
hr = mClient->SetEventHandle(mNotifyEvent);
if(FAILED(hr))
@@ -1613,10 +1830,12 @@ HRESULT WasapiCapture::resetProxy()
}
-bool WasapiCapture::start()
+void WasapiCapture::start()
{
- HRESULT hr{pushMessage(MsgType::StartDevice).get()};
- return SUCCEEDED(hr) ? true : false;
+ const HRESULT hr{pushMessage(MsgType::StartDevice).get()};
+ if(FAILED(hr))
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to start recording: 0x%lx", hr};
}
HRESULT WasapiCapture::startProxy()
@@ -1634,13 +1853,12 @@ HRESULT WasapiCapture::startProxy()
hr = mClient->GetService(IID_IAudioCaptureClient, &ptr);
if(SUCCEEDED(hr))
{
- mCapture = static_cast<IAudioCaptureClient*>(ptr);
+ mCapture = ComPtr<IAudioCaptureClient>{static_cast<IAudioCaptureClient*>(ptr)};
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&WasapiCapture::recordProc), this};
}
catch(...) {
- mCapture->Release();
mCapture = nullptr;
ERR("Failed to start thread\n");
hr = E_FAIL;
@@ -1668,21 +1886,17 @@ void WasapiCapture::stopProxy()
mKillNow.store(true, std::memory_order_release);
mThread.join();
- mCapture->Release();
mCapture = nullptr;
mClient->Stop();
mClient->Reset();
}
-ALCuint WasapiCapture::availableSamples()
-{ return static_cast<ALCuint>(mRing->readSpace()); }
+void WasapiCapture::captureSamples(al::byte *buffer, uint samples)
+{ mRing->read(buffer, samples); }
-ALCenum WasapiCapture::captureSamples(al::byte *buffer, ALCuint samples)
-{
- mRing->read(buffer, samples);
- return ALC_NO_ERROR;
-}
+uint WasapiCapture::availableSamples()
+{ return static_cast<uint>(mRing->readSpace()); }
} // namespace
@@ -1693,48 +1907,78 @@ bool WasapiBackendFactory::init()
if(FAILED(InitResult)) try
{
- std::promise<HRESULT> promise;
- auto future = promise.get_future();
+ auto res = std::async(std::launch::async, []() -> HRESULT
+ {
+ HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)};
+ if(FAILED(hr))
+ {
+ WARN("Failed to initialize COM: 0x%08lx\n", hr);
+ return hr;
+ }
+
+ void *ptr{};
+ hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IMMDeviceEnumerator, &ptr);
+ if(FAILED(hr))
+ {
+ WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr);
+ CoUninitialize();
+ return hr;
+ }
+ static_cast<IMMDeviceEnumerator*>(ptr)->Release();
+ CoUninitialize();
+
+ return S_OK;
+ });
- std::thread{&WasapiProxy::messageHandler, &promise}.detach();
- InitResult = future.get();
+ InitResult = res.get();
}
catch(...) {
}
- return SUCCEEDED(InitResult) ? ALC_TRUE : ALC_FALSE;
+ return SUCCEEDED(InitResult);
}
bool WasapiBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
-void WasapiBackendFactory::probe(DevProbe type, std::string *outnames)
+std::string WasapiBackendFactory::probe(BackendType type)
{
- 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);
+ struct ProxyControl {
+ HRESULT mResult{};
+ ProxyControl() { mResult = WasapiProxy::InitThread(); }
+ ~ProxyControl() { if(SUCCEEDED(mResult)) WasapiProxy::DeinitThread(); }
};
- HRESULT hr{};
+ ProxyControl proxy;
+
+ std::string outnames;
+ if(FAILED(proxy.mResult))
+ return outnames;
+
switch(type)
{
- case DevProbe::Playback:
- hr = WasapiProxy::pushMessageStatic(MsgType::EnumeratePlayback).get();
- if(SUCCEEDED(hr))
- std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
+ case BackendType::Playback:
+ WasapiProxy::pushMessageStatic(MsgType::EnumeratePlayback).wait();
+ for(const DevMap &entry : PlaybackDevices)
+ {
+ /* +1 to also append the null char (to ensure a null-separated list
+ * and double-null terminated list).
+ */
+ outnames.append(DevNameHead).append(entry.name.c_str(), entry.name.length()+1);
+ }
break;
- case DevProbe::Capture:
- hr = WasapiProxy::pushMessageStatic(MsgType::EnumerateCapture).get();
- if(SUCCEEDED(hr))
- std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
+ case BackendType::Capture:
+ WasapiProxy::pushMessageStatic(MsgType::EnumerateCapture).wait();
+ for(const DevMap &entry : CaptureDevices)
+ outnames.append(DevNameHead).append(entry.name.c_str(), entry.name.length()+1);
break;
}
+
+ return outnames;
}
-BackendPtr WasapiBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr WasapiBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new WasapiPlayback{device}};
diff --git a/alc/backends/wasapi.h b/alc/backends/wasapi.h
index 067dd259..bb2671ee 100644
--- a/alc/backends/wasapi.h
+++ b/alc/backends/wasapi.h
@@ -1,7 +1,7 @@
#ifndef BACKENDS_WASAPI_H
#define BACKENDS_WASAPI_H
-#include "backends/base.h"
+#include "base.h"
struct WasapiBackendFactory final : public BackendFactory {
public:
@@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
- void probe(DevProbe type, std::string *outnames) override;
+ std::string probe(BackendType type) override;
- BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+ BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
diff --git a/alc/backends/wave.cpp b/alc/backends/wave.cpp
index 7bcc3436..1b40640c 100644
--- a/alc/backends/wave.cpp
+++ b/alc/backends/wave.cpp
@@ -20,7 +20,7 @@
#include "config.h"
-#include "backends/wave.h"
+#include "wave.h"
#include <algorithm>
#include <atomic>
@@ -33,18 +33,15 @@
#include <functional>
#include <thread>
-#include "AL/al.h"
-
+#include "albit.h"
#include "albyte.h"
-#include "alcmain.h"
-#include "alconfig.h"
-#include "alexcpt.h"
+#include "alc/alconfig.h"
#include "almalloc.h"
#include "alnumeric.h"
-#include "alu.h"
-#include "compat.h"
-#include "endiantest.h"
-#include "logging.h"
+#include "core/device.h"
+#include "core/helpers.h"
+#include "core/logging.h"
+#include "opthelpers.h"
#include "strutils.h"
#include "threads.h"
#include "vector.h"
@@ -56,50 +53,53 @@ using std::chrono::seconds;
using std::chrono::milliseconds;
using std::chrono::nanoseconds;
-constexpr ALCchar waveDevice[] = "Wave File Writer";
+using ubyte = unsigned char;
+using ushort = unsigned short;
+
+constexpr char waveDevice[] = "Wave File Writer";
-constexpr ALubyte SUBTYPE_PCM[]{
+constexpr ubyte SUBTYPE_PCM[]{
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
0x00, 0x38, 0x9b, 0x71
};
-constexpr ALubyte SUBTYPE_FLOAT[]{
+constexpr ubyte SUBTYPE_FLOAT[]{
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
0x00, 0x38, 0x9b, 0x71
};
-constexpr ALubyte SUBTYPE_BFORMAT_PCM[]{
+constexpr ubyte SUBTYPE_BFORMAT_PCM[]{
0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
0xca, 0x00, 0x00, 0x00
};
-constexpr ALubyte SUBTYPE_BFORMAT_FLOAT[]{
+constexpr ubyte SUBTYPE_BFORMAT_FLOAT[]{
0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
0xca, 0x00, 0x00, 0x00
};
-void fwrite16le(ALushort val, FILE *f)
+void fwrite16le(ushort val, FILE *f)
{
- ALubyte data[2]{ static_cast<ALubyte>(val&0xff), static_cast<ALubyte>((val>>8)&0xff) };
+ ubyte data[2]{ static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff) };
fwrite(data, 1, 2, f);
}
-void fwrite32le(ALuint val, FILE *f)
+void fwrite32le(uint val, FILE *f)
{
- ALubyte data[4]{ static_cast<ALubyte>(val&0xff), static_cast<ALubyte>((val>>8)&0xff),
- static_cast<ALubyte>((val>>16)&0xff), static_cast<ALubyte>((val>>24)&0xff) };
+ ubyte data[4]{ static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff),
+ static_cast<ubyte>((val>>16)&0xff), static_cast<ubyte>((val>>24)&0xff) };
fwrite(data, 1, 4, f);
}
struct WaveBackend final : public BackendBase {
- WaveBackend(ALCdevice *device) noexcept : BackendBase{device} { }
+ WaveBackend(DeviceBase *device) noexcept : BackendBase{device} { }
~WaveBackend() override;
int mixerProc();
- void open(const ALCchar *name) override;
+ void open(const char *name) override;
bool reset() override;
- bool start() override;
+ void start() override;
void stop() override;
FILE *mFile{nullptr};
@@ -126,12 +126,13 @@ int WaveBackend::mixerProc()
althrd_setname(MIXER_THREAD_NAME);
- const ALuint frameSize{mDevice->frameSizeFromFmt()};
+ const size_t frameStep{mDevice->channelsFromFmt()};
+ const size_t frameSize{mDevice->frameSizeFromFmt()};
int64_t done{0};
auto start = std::chrono::steady_clock::now();
- while(!mKillNow.load(std::memory_order_acquire) &&
- mDevice->Connected.load(std::memory_order_acquire))
+ while(!mKillNow.load(std::memory_order_acquire)
+ && mDevice->Connected.load(std::memory_order_acquire))
{
auto now = std::chrono::steady_clock::now();
@@ -145,45 +146,35 @@ int WaveBackend::mixerProc()
}
while(avail-done >= mDevice->UpdateSize)
{
- {
- std::lock_guard<WaveBackend> _{*this};
- aluMixData(mDevice, mBuffer.data(), mDevice->UpdateSize);
- }
+ mDevice->renderSamples(mBuffer.data(), mDevice->UpdateSize, frameStep);
done += mDevice->UpdateSize;
- if(!IS_LITTLE_ENDIAN)
+ if(al::endian::native != al::endian::little)
{
- const ALuint bytesize{mDevice->bytesFromFmt()};
+ const uint bytesize{mDevice->bytesFromFmt()};
if(bytesize == 2)
{
- ALushort *samples = reinterpret_cast<ALushort*>(mBuffer.data());
- const size_t len{mBuffer.size() / 2};
- for(size_t i{0};i < len;i++)
- {
- const ALushort samp{samples[i]};
- samples[i] = static_cast<ALushort>((samp>>8) | (samp<<8));
- }
+ const size_t len{mBuffer.size() & ~size_t{1}};
+ for(size_t i{0};i < len;i+=2)
+ std::swap(mBuffer[i], mBuffer[i+1]);
}
else if(bytesize == 4)
{
- ALuint *samples = reinterpret_cast<ALuint*>(mBuffer.data());
- const size_t len{mBuffer.size() / 4};
- for(size_t i{0};i < len;i++)
+ const size_t len{mBuffer.size() & ~size_t{3}};
+ for(size_t i{0};i < len;i+=4)
{
- const ALuint samp{samples[i]};
- samples[i] = (samp>>24) | ((samp>>8)&0x0000ff00) |
- ((samp<<8)&0x00ff0000) | (samp<<24);
+ std::swap(mBuffer[i ], mBuffer[i+3]);
+ std::swap(mBuffer[i+1], mBuffer[i+2]);
}
}
}
- size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)};
- (void)fs;
- if(ferror(mFile))
+ const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)};
+ if(fs < mDevice->UpdateSize || ferror(mFile))
{
ERR("Error writing to file\n");
- aluHandleDisconnect(mDevice, "Failed to write playback samples");
+ mDevice->handleDisconnect("Failed to write playback samples");
break;
}
}
@@ -196,49 +187,54 @@ int WaveBackend::mixerProc()
if(done >= mDevice->Frequency)
{
seconds s{done/mDevice->Frequency};
+ done %= mDevice->Frequency;
start += s;
- done -= mDevice->Frequency*s.count();
}
}
return 0;
}
-void WaveBackend::open(const ALCchar *name)
+void WaveBackend::open(const char *name)
{
- const char *fname{GetConfigValue(nullptr, "wave", "file", "")};
- if(!fname[0]) throw al::backend_exception{ALC_INVALID_VALUE, "No wave output filename"};
+ auto fname = ConfigValueStr(nullptr, "wave", "file");
+ if(!fname) throw al::backend_exception{al::backend_error::NoDevice,
+ "No wave output filename"};
if(!name)
name = waveDevice;
else if(strcmp(name, waveDevice) != 0)
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+ name};
+
+ /* There's only one "device", so if it's already open, we're done. */
+ if(mFile) return;
#ifdef _WIN32
{
- std::wstring wname = utf8_to_wstr(fname);
+ std::wstring wname{utf8_to_wstr(fname->c_str())};
mFile = _wfopen(wname.c_str(), L"wb");
}
#else
- mFile = fopen(fname, "wb");
+ mFile = fopen(fname->c_str(), "wb");
#endif
if(!mFile)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not open file '%s': %s", fname,
- strerror(errno)};
+ throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '%s': %s",
+ fname->c_str(), strerror(errno)};
mDevice->DeviceName = name;
}
bool WaveBackend::reset()
{
- ALuint channels=0, bytes=0, chanmask=0;
- int isbformat = 0;
+ uint channels{0}, bytes{0}, chanmask{0};
+ bool isbformat{false};
size_t val;
fseek(mFile, 0, SEEK_SET);
clearerr(mFile);
- if(GetConfigValueBool(nullptr, "wave", "bformat", 0))
+ if(GetConfigValueBool(nullptr, "wave", "bformat", false))
{
mDevice->FmtChans = DevFmtAmbi3D;
mDevice->mAmbiOrder = 1;
@@ -246,38 +242,43 @@ bool WaveBackend::reset()
switch(mDevice->FmtType)
{
- case DevFmtByte:
- mDevice->FmtType = DevFmtUByte;
- break;
- case DevFmtUShort:
- mDevice->FmtType = DevFmtShort;
- break;
- case DevFmtUInt:
- mDevice->FmtType = DevFmtInt;
- break;
- case DevFmtUByte:
- case DevFmtShort:
- case DevFmtInt:
- case DevFmtFloat:
- break;
+ case DevFmtByte:
+ mDevice->FmtType = DevFmtUByte;
+ break;
+ case DevFmtUShort:
+ mDevice->FmtType = DevFmtShort;
+ break;
+ case DevFmtUInt:
+ mDevice->FmtType = DevFmtInt;
+ break;
+ case DevFmtUByte:
+ case DevFmtShort:
+ case DevFmtInt:
+ case DevFmtFloat:
+ break;
}
switch(mDevice->FmtChans)
{
- case DevFmtMono: chanmask = 0x04; break;
- case DevFmtStereo: chanmask = 0x01 | 0x02; break;
- case DevFmtQuad: chanmask = 0x01 | 0x02 | 0x10 | 0x20; break;
- case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break;
- case DevFmtX51Rear: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020; 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 DevFmtAmbi3D:
- /* .amb output requires FuMa */
- mDevice->mAmbiOrder = minu(mDevice->mAmbiOrder, 3);
- mDevice->mAmbiLayout = AmbiLayout::FuMa;
- mDevice->mAmbiScale = AmbiNorm::FuMa;
- isbformat = 1;
- chanmask = 0;
- break;
+ case DevFmtMono: chanmask = 0x04; break;
+ case DevFmtStereo: chanmask = 0x01 | 0x02; break;
+ case DevFmtQuad: chanmask = 0x01 | 0x02 | 0x10 | 0x20; break;
+ 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 DevFmtX714:
+ chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400 | 0x1000 | 0x4000
+ | 0x8000 | 0x20000;
+ break;
+ /* NOTE: Same as 7.1. */
+ case DevFmtX3D71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break;
+ case DevFmtAmbi3D:
+ /* .amb output requires FuMa */
+ mDevice->mAmbiOrder = minu(mDevice->mAmbiOrder, 3);
+ mDevice->mAmbiLayout = DevAmbiLayout::FuMa;
+ mDevice->mAmbiScale = DevAmbiScaling::FuMa;
+ isbformat = true;
+ chanmask = 0;
+ break;
}
bytes = mDevice->bytesFromFmt();
channels = mDevice->channelsFromFmt();
@@ -295,19 +296,19 @@ bool WaveBackend::reset()
// 16-bit val, format type id (extensible: 0xFFFE)
fwrite16le(0xFFFE, mFile);
// 16-bit val, channel count
- fwrite16le(static_cast<ALushort>(channels), mFile);
+ fwrite16le(static_cast<ushort>(channels), mFile);
// 32-bit val, frequency
fwrite32le(mDevice->Frequency, mFile);
// 32-bit val, bytes per second
fwrite32le(mDevice->Frequency * channels * bytes, mFile);
// 16-bit val, frame size
- fwrite16le(static_cast<ALushort>(channels * bytes), mFile);
+ fwrite16le(static_cast<ushort>(channels * bytes), mFile);
// 16-bit val, bits per sample
- fwrite16le(static_cast<ALushort>(bytes * 8), mFile);
+ fwrite16le(static_cast<ushort>(bytes * 8), mFile);
// 16-bit val, extra byte count
fwrite16le(22, mFile);
// 16-bit val, valid bits per sample
- fwrite16le(static_cast<ALushort>(bytes * 8), mFile);
+ fwrite16le(static_cast<ushort>(bytes * 8), mFile);
// 32-bit val, channel mask
fwrite32le(chanmask, mFile);
// 16 byte GUID, sub-type format
@@ -326,27 +327,26 @@ bool WaveBackend::reset()
}
mDataStart = ftell(mFile);
- SetDefaultWFXChannelOrder(mDevice);
+ setDefaultWFXChannelOrder();
- const ALuint bufsize{mDevice->frameSizeFromFmt() * mDevice->UpdateSize};
+ const uint bufsize{mDevice->frameSizeFromFmt() * mDevice->UpdateSize};
mBuffer.resize(bufsize);
return true;
}
-bool WaveBackend::start()
+void WaveBackend::start()
{
+ if(mDataStart > 0 && fseek(mFile, 0, SEEK_END) != 0)
+ WARN("Failed to seek on output file\n");
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&WaveBackend::mixerProc), this};
- return true;
}
catch(std::exception& e) {
- ERR("Failed to start mixing thread: %s\n", e.what());
- }
- catch(...) {
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to start mixing thread: %s", e.what()};
}
- return false;
}
void WaveBackend::stop()
@@ -355,14 +355,17 @@ void WaveBackend::stop()
return;
mThread.join();
- long size{ftell(mFile)};
- if(size > 0)
+ if(mDataStart > 0)
{
- long dataLen{size - mDataStart};
- if(fseek(mFile, mDataStart-4, SEEK_SET) == 0)
- fwrite32le(static_cast<ALuint>(dataLen), mFile); // 'data' header len
- if(fseek(mFile, 4, SEEK_SET) == 0)
- fwrite32le(static_cast<ALuint>(size-8), mFile); // 'WAVE' header len
+ long size{ftell(mFile)};
+ if(size > 0)
+ {
+ long dataLen{size - mDataStart};
+ if(fseek(mFile, 4, SEEK_SET) == 0)
+ fwrite32le(static_cast<uint>(size-8), mFile); // 'WAVE' header len
+ if(fseek(mFile, mDataStart-4, SEEK_SET) == 0)
+ fwrite32le(static_cast<uint>(dataLen), mFile); // 'data' header len
+ }
}
}
@@ -375,20 +378,22 @@ bool WaveBackendFactory::init()
bool WaveBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
-void WaveBackendFactory::probe(DevProbe type, std::string *outnames)
+std::string WaveBackendFactory::probe(BackendType type)
{
+ std::string outnames;
switch(type)
{
- case DevProbe::Playback:
- /* Includes null char. */
- outnames->append(waveDevice, sizeof(waveDevice));
- break;
- case DevProbe::Capture:
- break;
+ case BackendType::Playback:
+ /* Includes null char. */
+ outnames.append(waveDevice, sizeof(waveDevice));
+ break;
+ case BackendType::Capture:
+ break;
}
+ return outnames;
}
-BackendPtr WaveBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr WaveBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new WaveBackend{device}};
diff --git a/alc/backends/wave.h b/alc/backends/wave.h
index b9b62d7f..e768d336 100644
--- a/alc/backends/wave.h
+++ b/alc/backends/wave.h
@@ -1,7 +1,7 @@
#ifndef BACKENDS_WAVE_H
#define BACKENDS_WAVE_H
-#include "backends/base.h"
+#include "base.h"
struct WaveBackendFactory final : public BackendFactory {
public:
@@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
- void probe(DevProbe type, std::string *outnames) override;
+ std::string probe(BackendType type) override;
- BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+ BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
diff --git a/alc/backends/winmm.cpp b/alc/backends/winmm.cpp
index 649bb345..38e1193f 100644
--- a/alc/backends/winmm.cpp
+++ b/alc/backends/winmm.cpp
@@ -20,7 +20,7 @@
#include "config.h"
-#include "backends/winmm.h"
+#include "winmm.h"
#include <stdlib.h>
#include <stdio.h>
@@ -28,6 +28,7 @@
#include <windows.h>
#include <mmsystem.h>
+#include <mmreg.h>
#include <array>
#include <atomic>
@@ -37,13 +38,13 @@
#include <algorithm>
#include <functional>
-#include "alcmain.h"
-#include "alexcpt.h"
-#include "alu.h"
+#include "alnumeric.h"
+#include "core/device.h"
+#include "core/helpers.h"
+#include "core/logging.h"
#include "ringbuffer.h"
#include "strutils.h"
#include "threads.h"
-#include "compat.h"
#ifndef WAVE_FORMAT_IEEE_FLOAT
#define WAVE_FORMAT_IEEE_FLOAT 0x0003
@@ -64,9 +65,9 @@ void ProbePlaybackDevices(void)
{
PlaybackDevices.clear();
- ALuint numdevs{waveOutGetNumDevs()};
+ UINT numdevs{waveOutGetNumDevs()};
PlaybackDevices.reserve(numdevs);
- for(ALuint i{0};i < numdevs;i++)
+ for(UINT i{0};i < numdevs;++i)
{
std::string dname;
@@ -95,9 +96,9 @@ void ProbeCaptureDevices(void)
{
CaptureDevices.clear();
- ALuint numdevs{waveInGetNumDevs()};
+ UINT numdevs{waveInGetNumDevs()};
CaptureDevices.reserve(numdevs);
- for(ALuint i{0};i < numdevs;i++)
+ for(UINT i{0};i < numdevs;++i)
{
std::string dname;
@@ -124,7 +125,7 @@ void ProbeCaptureDevices(void)
struct WinMMPlayback final : public BackendBase {
- WinMMPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+ WinMMPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~WinMMPlayback() override;
void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
@@ -133,14 +134,14 @@ struct WinMMPlayback final : public BackendBase {
int mixerProc();
- void open(const ALCchar *name) override;
+ void open(const char *name) override;
bool reset() override;
- bool start() override;
+ void start() override;
void stop() override;
- std::atomic<ALuint> mWritable{0u};
+ std::atomic<uint> mWritable{0u};
al::semaphore mSem;
- ALuint mIdx{0u};
+ uint mIdx{0u};
std::array<WAVEHDR,4> mWaveBuffer{};
HWAVEOUT mOutHdl{nullptr};
@@ -180,36 +181,33 @@ FORCE_ALIGN int WinMMPlayback::mixerProc()
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
- std::unique_lock<WinMMPlayback> dlock{*this};
- while(!mKillNow.load(std::memory_order_acquire) &&
- mDevice->Connected.load(std::memory_order_acquire))
+ while(!mKillNow.load(std::memory_order_acquire)
+ && mDevice->Connected.load(std::memory_order_acquire))
{
- ALsizei todo = mWritable.load(std::memory_order_acquire);
+ uint todo{mWritable.load(std::memory_order_acquire)};
if(todo < 1)
{
- dlock.unlock();
mSem.wait();
- dlock.lock();
continue;
}
size_t widx{mIdx};
do {
WAVEHDR &waveHdr = mWaveBuffer[widx];
- widx = (widx+1) % mWaveBuffer.size();
+ if(++widx == mWaveBuffer.size()) widx = 0;
- aluMixData(mDevice, waveHdr.lpData, mDevice->UpdateSize);
+ mDevice->renderSamples(waveHdr.lpData, mDevice->UpdateSize, mFormat.nChannels);
mWritable.fetch_sub(1, std::memory_order_acq_rel);
waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR));
} while(--todo);
- mIdx = static_cast<ALuint>(widx);
+ mIdx = static_cast<uint>(widx);
}
return 0;
}
-void WinMMPlayback::open(const ALCchar *name)
+void WinMMPlayback::open(const char *name)
{
if(PlaybackDevices.empty())
ProbePlaybackDevices();
@@ -219,51 +217,59 @@ void WinMMPlayback::open(const ALCchar *name)
std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) :
PlaybackDevices.cbegin();
if(iter == PlaybackDevices.cend())
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+ name};
auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter));
+ DevFmtType fmttype{mDevice->FmtType};
retry_open:
- mFormat = WAVEFORMATEX{};
- if(mDevice->FmtType == DevFmtFloat)
+ WAVEFORMATEX format{};
+ if(fmttype == DevFmtFloat)
{
- mFormat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
- mFormat.wBitsPerSample = 32;
+ format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
+ format.wBitsPerSample = 32;
}
else
{
- mFormat.wFormatTag = WAVE_FORMAT_PCM;
- if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtByte)
- mFormat.wBitsPerSample = 8;
+ format.wFormatTag = WAVE_FORMAT_PCM;
+ if(fmttype == DevFmtUByte || fmttype == DevFmtByte)
+ format.wBitsPerSample = 8;
else
- mFormat.wBitsPerSample = 16;
+ format.wBitsPerSample = 16;
}
- mFormat.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
- mFormat.nBlockAlign = static_cast<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8);
- mFormat.nSamplesPerSec = mDevice->Frequency;
- mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign;
- mFormat.cbSize = 0;
-
- MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &mFormat,
+ 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)
{
- if(mDevice->FmtType == DevFmtFloat)
+ if(fmttype == DevFmtFloat)
{
- mDevice->FmtType = DevFmtShort;
+ fmttype = DevFmtShort;
goto retry_open;
}
- throw al::backend_exception{ALC_INVALID_VALUE, "waveOutOpen failed: %u", res};
+ throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", res};
}
+ if(mOutHdl)
+ waveOutClose(mOutHdl);
+ mOutHdl = outHandle;
+ mFormat = format;
+
mDevice->DeviceName = PlaybackDevices[DeviceID];
}
bool WinMMPlayback::reset()
{
- mDevice->BufferSize = static_cast<ALuint>(uint64_t{mDevice->BufferSize} *
+ mDevice->BufferSize = static_cast<uint>(uint64_t{mDevice->BufferSize} *
mFormat.nSamplesPerSec / mDevice->Frequency);
- mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3;
+ mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3u;
mDevice->UpdateSize = mDevice->BufferSize / 4;
mDevice->Frequency = mFormat.nSamplesPerSec;
@@ -295,7 +301,7 @@ bool WinMMPlayback::reset()
return false;
}
- if(mFormat.nChannels == 2)
+ if(mFormat.nChannels >= 2)
mDevice->FmtChans = DevFmtStereo;
else if(mFormat.nChannels == 1)
mDevice->FmtChans = DevFmtMono;
@@ -304,9 +310,9 @@ bool WinMMPlayback::reset()
ERR("Unhandled channel count: %d\n", mFormat.nChannels);
return false;
}
- SetDefaultWFXChannelOrder(mDevice);
+ setDefaultWFXChannelOrder();
- ALuint BufferSize{mDevice->UpdateSize * mDevice->frameSizeFromFmt()};
+ uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()};
al_free(mWaveBuffer[0].lpData);
mWaveBuffer[0] = WAVEHDR{};
@@ -323,25 +329,20 @@ bool WinMMPlayback::reset()
return true;
}
-bool WinMMPlayback::start()
+void WinMMPlayback::start()
{
try {
- std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(),
- [this](WAVEHDR &waveHdr) -> void
- { waveOutPrepareHeader(mOutHdl, &waveHdr, static_cast<UINT>(sizeof(WAVEHDR))); }
- );
- mWritable.store(static_cast<ALuint>(mWaveBuffer.size()), std::memory_order_release);
+ for(auto &waveHdr : mWaveBuffer)
+ waveOutPrepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR));
+ 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};
- return true;
}
catch(std::exception& e) {
- ERR("Failed to start mixing thread: %s\n", e.what());
- }
- catch(...) {
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to start mixing thread: %s", e.what()};
}
- return false;
}
void WinMMPlayback::stop()
@@ -352,16 +353,14 @@ void WinMMPlayback::stop()
while(mWritable.load(std::memory_order_acquire) < mWaveBuffer.size())
mSem.wait();
- std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(),
- [this](WAVEHDR &waveHdr) -> void
- { waveOutUnprepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); }
- );
+ for(auto &waveHdr : mWaveBuffer)
+ waveOutUnprepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR));
mWritable.store(0, std::memory_order_release);
}
struct WinMMCapture final : public BackendBase {
- WinMMCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+ WinMMCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~WinMMCapture() override;
void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
@@ -370,15 +369,15 @@ struct WinMMCapture final : public BackendBase {
int captureProc();
- void open(const ALCchar *name) override;
- bool start() override;
+ void open(const char *name) override;
+ void start() override;
void stop() override;
- ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
- ALCuint availableSamples() override;
+ void captureSamples(al::byte *buffer, uint samples) override;
+ uint availableSamples() override;
- std::atomic<ALuint> mReadable{0u};
+ std::atomic<uint> mReadable{0u};
al::semaphore mSem;
- ALuint mIdx{0};
+ uint mIdx{0};
std::array<WAVEHDR,4> mWaveBuffer{};
HWAVEIN mInHdl{nullptr};
@@ -420,16 +419,13 @@ int WinMMCapture::captureProc()
{
althrd_setname(RECORD_THREAD_NAME);
- std::unique_lock<WinMMCapture> dlock{*this};
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
{
- ALuint todo{mReadable.load(std::memory_order_acquire)};
+ uint todo{mReadable.load(std::memory_order_acquire)};
if(todo < 1)
{
- dlock.unlock();
mSem.wait();
- dlock.lock();
continue;
}
@@ -442,14 +438,14 @@ int WinMMCapture::captureProc()
mReadable.fetch_sub(1, std::memory_order_acq_rel);
waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR));
} while(--todo);
- mIdx = static_cast<ALuint>(widx);
+ mIdx = static_cast<uint>(widx);
}
return 0;
}
-void WinMMCapture::open(const ALCchar *name)
+void WinMMCapture::open(const char *name)
{
if(CaptureDevices.empty())
ProbeCaptureDevices();
@@ -459,7 +455,8 @@ void WinMMCapture::open(const ALCchar *name)
std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) :
CaptureDevices.cbegin();
if(iter == CaptureDevices.cend())
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+ name};
auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter));
switch(mDevice->FmtChans)
@@ -470,11 +467,12 @@ void WinMMCapture::open(const ALCchar *name)
case DevFmtQuad:
case DevFmtX51:
- case DevFmtX51Rear:
case DevFmtX61:
case DevFmtX71:
+ case DevFmtX714:
+ case DevFmtX3D71:
case DevFmtAmbi3D:
- throw al::backend_exception{ALC_INVALID_VALUE, "%s capture not supported",
+ throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
DevFmtChannelsString(mDevice->FmtChans)};
}
@@ -489,7 +487,7 @@ void WinMMCapture::open(const ALCchar *name)
case DevFmtByte:
case DevFmtUShort:
case DevFmtUInt:
- throw al::backend_exception{ALC_INVALID_VALUE, "%s samples not supported",
+ throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported",
DevFmtTypeString(mDevice->FmtType)};
}
@@ -507,7 +505,7 @@ void WinMMCapture::open(const ALCchar *name)
reinterpret_cast<DWORD_PTR>(&WinMMCapture::waveInProcC),
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
if(res != MMSYSERR_NOERROR)
- throw al::backend_exception{ALC_INVALID_VALUE, "waveInOpen failed: %u", res};
+ throw al::backend_exception{al::backend_error::DeviceError, "waveInOpen failed: %u", res};
// Ensure each buffer is 50ms each
DWORD BufferSize{mFormat.nAvgBytesPerSec / 20u};
@@ -515,14 +513,14 @@ void WinMMCapture::open(const ALCchar *name)
// Allocate circular memory buffer for the captured audio
// Make sure circular buffer is at least 100ms in size
- ALuint CapturedDataSize{mDevice->BufferSize};
- CapturedDataSize = static_cast<ALuint>(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size()));
+ uint CapturedDataSize{mDevice->BufferSize};
+ CapturedDataSize = static_cast<uint>(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size()));
- mRing = CreateRingBuffer(CapturedDataSize, mFormat.nBlockAlign, false);
+ mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false);
al_free(mWaveBuffer[0].lpData);
mWaveBuffer[0] = WAVEHDR{};
- mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize*4));
+ mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
mWaveBuffer[0].dwBufferLength = BufferSize;
for(size_t i{1};i < mWaveBuffer.size();++i)
{
@@ -534,7 +532,7 @@ void WinMMCapture::open(const ALCchar *name)
mDevice->DeviceName = CaptureDevices[DeviceID];
}
-bool WinMMCapture::start()
+void WinMMCapture::start()
{
try {
for(size_t i{0};i < mWaveBuffer.size();++i)
@@ -547,14 +545,11 @@ bool WinMMCapture::start()
mThread = std::thread{std::mem_fn(&WinMMCapture::captureProc), this};
waveInStart(mInHdl);
- return true;
}
catch(std::exception& e) {
- ERR("Failed to start mixing thread: %s\n", e.what());
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to start recording thread: %s", e.what()};
}
- catch(...) {
- }
- return false;
}
void WinMMCapture::stop()
@@ -576,14 +571,11 @@ void WinMMCapture::stop()
mIdx = 0;
}
-ALCenum WinMMCapture::captureSamples(al::byte *buffer, ALCuint samples)
-{
- mRing->read(buffer, samples);
- return ALC_NO_ERROR;
-}
+void WinMMCapture::captureSamples(al::byte *buffer, uint samples)
+{ mRing->read(buffer, samples); }
-ALCuint WinMMCapture::availableSamples()
-{ return static_cast<ALCuint>(mRing->readSpace()); }
+uint WinMMCapture::availableSamples()
+{ return static_cast<uint>(mRing->readSpace()); }
} // namespace
@@ -594,31 +586,33 @@ bool WinMMBackendFactory::init()
bool WinMMBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
-void WinMMBackendFactory::probe(DevProbe type, std::string *outnames)
+std::string WinMMBackendFactory::probe(BackendType type)
{
- auto add_device = [outnames](const std::string &dname) -> void
+ 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);
+ outnames.append(dname.c_str(), dname.length()+1);
};
switch(type)
{
- case DevProbe::Playback:
- ProbePlaybackDevices();
- std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
- break;
-
- case DevProbe::Capture:
- ProbeCaptureDevices();
- std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
- break;
+ case BackendType::Playback:
+ ProbePlaybackDevices();
+ std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
+ break;
+
+ case BackendType::Capture:
+ ProbeCaptureDevices();
+ std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
+ break;
}
+ return outnames;
}
-BackendPtr WinMMBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr WinMMBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new WinMMPlayback{device}};
diff --git a/alc/backends/winmm.h b/alc/backends/winmm.h
index e357ec19..45a706aa 100644
--- a/alc/backends/winmm.h
+++ b/alc/backends/winmm.h
@@ -1,7 +1,7 @@
#ifndef BACKENDS_WINMM_H
#define BACKENDS_WINMM_H
-#include "backends/base.h"
+#include "base.h"
struct WinMMBackendFactory final : public BackendFactory {
public:
@@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
- void probe(DevProbe type, std::string *outnames) override;
+ std::string probe(BackendType type) override;
- BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+ BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};