diff options
Diffstat (limited to 'alc/backends')
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, ®istryEvents, 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, ¶ms, 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, ¶ms, 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, ¶m); - param.sched_priority=param.sched_curpriority+1; - SchedSet(0, 0, SCHED_NOCHANGE, ¶m); - - 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(); }; |