diff options
author | Sven Gothel <[email protected]> | 2023-11-28 12:51:46 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2023-11-28 12:51:46 +0100 |
commit | 1aaf4f070011490bcece50394b9b32dfa593fd9e (patch) | |
tree | 17d68284e401a35eea3d3a574d986d446a60763a /alc/backends | |
parent | 6e7cee4fa9a8af03f28ca26cd89f8357390dfc90 (diff) | |
parent | 571b546f35eead77ce109f8d4dd6c3de3199d573 (diff) |
Merge remote-tracking branch 'upstream/master'
Diffstat (limited to 'alc/backends')
-rw-r--r-- | alc/backends/alsa.cpp | 48 | ||||
-rw-r--r-- | alc/backends/base.cpp | 3 | ||||
-rw-r--r-- | alc/backends/base.h | 11 | ||||
-rw-r--r-- | alc/backends/coreaudio.cpp | 239 | ||||
-rw-r--r-- | alc/backends/coreaudio.h | 2 | ||||
-rw-r--r-- | alc/backends/dsound.cpp | 81 | ||||
-rw-r--r-- | alc/backends/jack.cpp | 37 | ||||
-rw-r--r-- | alc/backends/loopback.cpp | 4 | ||||
-rw-r--r-- | alc/backends/null.cpp | 16 | ||||
-rw-r--r-- | alc/backends/oboe.cpp | 43 | ||||
-rw-r--r-- | alc/backends/opensl.cpp | 50 | ||||
-rw-r--r-- | alc/backends/oss.cpp | 42 | ||||
-rw-r--r-- | alc/backends/pipewire.cpp | 654 | ||||
-rw-r--r-- | alc/backends/pipewire.h | 2 | ||||
-rw-r--r-- | alc/backends/portaudio.cpp | 31 | ||||
-rw-r--r-- | alc/backends/pulseaudio.cpp | 252 | ||||
-rw-r--r-- | alc/backends/pulseaudio.h | 2 | ||||
-rw-r--r-- | alc/backends/sdl2.cpp | 32 | ||||
-rw-r--r-- | alc/backends/sndio.cpp | 46 | ||||
-rw-r--r-- | alc/backends/solaris.cpp | 23 | ||||
-rw-r--r-- | alc/backends/wasapi.cpp | 1813 | ||||
-rw-r--r-- | alc/backends/wasapi.h | 2 | ||||
-rw-r--r-- | alc/backends/wave.cpp | 25 | ||||
-rw-r--r-- | alc/backends/winmm.cpp | 37 |
24 files changed, 2270 insertions, 1225 deletions
diff --git a/alc/backends/alsa.cpp b/alc/backends/alsa.cpp index d620a83c..0d9ff30d 100644 --- a/alc/backends/alsa.cpp +++ b/alc/backends/alsa.cpp @@ -31,22 +31,22 @@ #include <exception> #include <functional> #include <memory> +#include <mutex> #include <string> #include <thread> #include <utility> +#include <vector> -#include "albyte.h" +#include "albit.h" #include "alc/alconfig.h" #include "almalloc.h" #include "alnumeric.h" -#include "aloptional.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" #include "ringbuffer.h" -#include "threads.h" -#include "vector.h" #include <alsa/asoundlib.h> @@ -248,8 +248,8 @@ struct DevMap { { } }; -al::vector<DevMap> PlaybackDevices; -al::vector<DevMap> CaptureDevices; +std::vector<DevMap> PlaybackDevices; +std::vector<DevMap> CaptureDevices; const char *prefix_name(snd_pcm_stream_t stream) @@ -258,9 +258,9 @@ const char *prefix_name(snd_pcm_stream_t stream) return (stream==SND_PCM_STREAM_PLAYBACK) ? "device-prefix" : "capture-prefix"; } -al::vector<DevMap> probe_devices(snd_pcm_stream_t stream) +std::vector<DevMap> probe_devices(snd_pcm_stream_t stream) { - al::vector<DevMap> devlist; + std::vector<DevMap> devlist; snd_ctl_card_info_t *info; snd_ctl_card_info_malloc(&info); @@ -427,7 +427,7 @@ struct AlsaPlayback final : public BackendBase { int mixerProc(); int mixerNoMMapProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -439,7 +439,7 @@ struct AlsaPlayback final : public BackendBase { std::mutex mMutex; uint mFrameStep{}; - al::vector<al::byte> mBuffer; + std::vector<std::byte> mBuffer; std::atomic<bool> mKillNow{true}; std::thread mThread; @@ -585,7 +585,7 @@ int AlsaPlayback::mixerNoMMapProc() continue; } - al::byte *WritePtr{mBuffer.data()}; + std::byte *WritePtr{mBuffer.data()}; avail = snd_pcm_bytes_to_frames(mPcmHandle, static_cast<ssize_t>(mBuffer.size())); std::lock_guard<std::mutex> _{mMutex}; mDevice->renderSamples(WritePtr, static_cast<uint>(avail), mFrameStep); @@ -626,10 +626,10 @@ int AlsaPlayback::mixerNoMMapProc() } -void AlsaPlayback::open(const char *name) +void AlsaPlayback::open(std::string_view name) { std::string driver{"default"}; - if(name) + if(!name.empty()) { if(PlaybackDevices.empty()) PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK); @@ -638,7 +638,7 @@ void AlsaPlayback::open(const char *name) [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", static_cast<int>(name.length()), name.data()}; driver = iter->device_name; } else @@ -871,16 +871,16 @@ struct AlsaCapture final : public BackendBase { AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~AlsaCapture() override; - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; ClockLatency getClockLatency() override; snd_pcm_t *mPcmHandle{nullptr}; - al::vector<al::byte> mBuffer; + std::vector<std::byte> mBuffer; bool mDoCapture{false}; RingBufferPtr mRing{nullptr}; @@ -898,10 +898,10 @@ AlsaCapture::~AlsaCapture() } -void AlsaCapture::open(const char *name) +void AlsaCapture::open(std::string_view name) { std::string driver{"default"}; - if(name) + if(!name.empty()) { if(CaptureDevices.empty()) CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE); @@ -910,7 +910,7 @@ void AlsaCapture::open(const char *name) [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", static_cast<int>(name.length()), name.data()}; driver = iter->device_name; } else @@ -1024,7 +1024,7 @@ void AlsaCapture::stop() /* The ring buffer implicitly captures when checking availability. * Direct access needs to explicitly capture it into temp storage. */ - auto temp = al::vector<al::byte>( + auto temp = std::vector<std::byte>( static_cast<size_t>(snd_pcm_frames_to_bytes(mPcmHandle, avail))); captureSamples(temp.data(), avail); mBuffer = std::move(temp); @@ -1035,7 +1035,7 @@ void AlsaCapture::stop() mDoCapture = false; } -void AlsaCapture::captureSamples(al::byte *buffer, uint samples) +void AlsaCapture::captureSamples(std::byte *buffer, uint samples) { if(mRing) { @@ -1093,7 +1093,7 @@ void AlsaCapture::captureSamples(al::byte *buffer, uint samples) } if(samples > 0) std::fill_n(buffer, snd_pcm_frames_to_bytes(mPcmHandle, samples), - al::byte((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0)); + std::byte((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0)); } uint AlsaCapture::availableSamples() @@ -1205,7 +1205,7 @@ bool AlsaBackendFactory::init() error = false; #define LOAD_FUNC(f) do { \ - p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(alsa_handle, #f)); \ + p##f = al::bit_cast<decltype(p##f)>(GetSymbol(alsa_handle, #f)); \ if(p##f == nullptr) { \ error = true; \ missing_funcs += "\n" #f; \ diff --git a/alc/backends/base.cpp b/alc/backends/base.cpp index e5ad8494..ab3ad028 100644 --- a/alc/backends/base.cpp +++ b/alc/backends/base.cpp @@ -14,7 +14,6 @@ #include "albit.h" #include "core/logging.h" -#include "aloptional.h" #endif #include "atomic.h" @@ -38,7 +37,7 @@ backend_exception::~backend_exception() = default; bool BackendBase::reset() { throw al::backend_exception{al::backend_error::DeviceError, "Invalid BackendBase call"}; } -void BackendBase::captureSamples(al::byte*, uint) +void BackendBase::captureSamples(std::byte*, uint) { } uint BackendBase::availableSamples() diff --git a/alc/backends/base.h b/alc/backends/base.h index b6b3d922..eea0d238 100644 --- a/alc/backends/base.h +++ b/alc/backends/base.h @@ -3,13 +3,15 @@ #include <chrono> #include <cstdarg> +#include <cstddef> #include <memory> #include <ratio> #include <string> +#include <string_view> -#include "albyte.h" #include "core/device.h" #include "core/except.h" +#include "alc/events.h" using uint = unsigned int; @@ -20,13 +22,13 @@ struct ClockLatency { }; struct BackendBase { - virtual void open(const char *name) = 0; + virtual void open(std::string_view name) = 0; virtual bool reset(); virtual void start() = 0; virtual void stop() = 0; - virtual void captureSamples(al::byte *buffer, uint samples); + virtual void captureSamples(std::byte *buffer, uint samples); virtual uint availableSamples(); virtual ClockLatency getClockLatency(); @@ -78,6 +80,9 @@ struct BackendFactory { virtual bool querySupport(BackendType type) = 0; + virtual alc::EventSupport queryEventSupport(alc::EventType, BackendType) + { return alc::EventSupport::NoSupport; } + virtual std::string probe(BackendType type) = 0; virtual BackendPtr createBackend(DeviceBase *device, BackendType type) = 0; diff --git a/alc/backends/coreaudio.cpp b/alc/backends/coreaudio.cpp index 8b0e75fd..16b0781e 100644 --- a/alc/backends/coreaudio.cpp +++ b/alc/backends/coreaudio.cpp @@ -22,16 +22,17 @@ #include "coreaudio.h" -#include <inttypes.h> +#include <cinttypes> +#include <cmath> +#include <memory> #include <stdint.h> #include <stdio.h> #include <stdlib.h> +#include <string> #include <string.h> #include <unistd.h> - -#include <cmath> -#include <memory> -#include <string> +#include <vector> +#include <optional> #include "alnumeric.h" #include "core/converter.h" @@ -42,18 +43,40 @@ #include <AudioUnit/AudioUnit.h> #include <AudioToolbox/AudioToolbox.h> - -namespace { - #if TARGET_OS_IOS || TARGET_OS_TV #define CAN_ENUMERATE 0 #else +#include <IOKit/audio/IOAudioTypes.h> #define CAN_ENUMERATE 1 #endif +namespace { + constexpr auto OutputElement = 0; constexpr auto InputElement = 1; +struct FourCCPrinter { + char mString[sizeof(UInt32) + 1]{}; + + constexpr FourCCPrinter(UInt32 code) noexcept + { + for(size_t i{0};i < sizeof(UInt32);++i) + { + const auto ch = static_cast<char>(code & 0xff); + /* If this breaks early it'll leave the first byte null, to get + * read as a 0-length string. + */ + if(ch <= 0x1f || ch >= 0x7f) + break; + mString[sizeof(UInt32)-1-i] = ch; + code >>= 8; + } + } + constexpr FourCCPrinter(int code) noexcept : FourCCPrinter{static_cast<UInt32>(code)} { } + + constexpr const char *c_str() const noexcept { return mString; } +}; + #if CAN_ENUMERATE struct DeviceEntry { AudioDeviceID mId; @@ -147,7 +170,8 @@ UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture) &propSize); if(err) { - ERR("kAudioDevicePropertyStreamConfiguration size query failed: %u\n", err); + ERR("kAudioDevicePropertyStreamConfiguration size query failed: '%s' (%u)\n", + FourCCPrinter{err}.c_str(), err); return 0; } @@ -158,7 +182,8 @@ UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture) buflist); if(err) { - ERR("kAudioDevicePropertyStreamConfiguration query failed: %u\n", err); + ERR("kAudioDevicePropertyStreamConfiguration query failed: '%s' (%u)\n", + FourCCPrinter{err}.c_str(), err); return 0; } @@ -182,7 +207,7 @@ void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture) auto devIds = std::vector<AudioDeviceID>(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown); if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data())) { - ERR("Failed to get device list: %u\n", err); + ERR("Failed to get device list: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); return; } @@ -247,6 +272,48 @@ void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture) newdevs.swap(list); } +struct DeviceHelper { + DeviceHelper() + { + AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain}; + OSStatus status = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil); + if (status != noErr) + ERR("AudioObjectAddPropertyListener fail: %d", status); + } + ~DeviceHelper() + { + AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain}; + OSStatus status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil); + if (status != noErr) + ERR("AudioObjectRemovePropertyListener fail: %d", status); + } + + static OSStatus DeviceListenerProc(AudioObjectID /*inObjectID*/, UInt32 inNumberAddresses, + const AudioObjectPropertyAddress *inAddresses, void* /*inClientData*/) + { + for(UInt32 i = 0; i < inNumberAddresses; ++i) + { + switch(inAddresses[i].mSelector) + { + case kAudioHardwarePropertyDefaultOutputDevice: + case kAudioHardwarePropertyDefaultSystemOutputDevice: + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, + "Default playback device changed: "+std::to_string(inAddresses[i].mSelector)); + break; + case kAudioHardwarePropertyDefaultInputDevice: + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, + "Default capture device changed: "+std::to_string(inAddresses[i].mSelector)); + break; + } + } + return noErr; + } +}; + +static std::optional<DeviceHelper> sDeviceHelper; + #else static constexpr char ca_device[] = "CoreAudio Default"; @@ -260,15 +327,8 @@ struct CoreAudioPlayback final : public BackendBase { OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept; - static OSStatus MixerProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, - AudioBufferList *ioData) noexcept - { - return static_cast<CoreAudioPlayback*>(inRefCon)->MixerProc(ioActionFlags, inTimeStamp, - inBusNumber, inNumberFrames, ioData); - } - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -301,11 +361,11 @@ OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTi } -void CoreAudioPlayback::open(const char *name) +void CoreAudioPlayback::open(std::string_view name) { #if CAN_ENUMERATE AudioDeviceID audioDevice{kAudioDeviceUnknown}; - if(!name) + if(name.empty()) GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice), &audioDevice); else @@ -318,16 +378,16 @@ void CoreAudioPlayback::open(const char *name) auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name); if(devmatch == PlaybackList.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", static_cast<int>(name.length()), name.data()}; audioDevice = devmatch->mId; } #else - if(!name) + if(name.empty()) name = ca_device; - else if(strcmp(name, ca_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + else if(name != ca_device) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + static_cast<int>(name.length()), name.data()}; #endif /* open the default output unit */ @@ -351,7 +411,7 @@ void CoreAudioPlayback::open(const char *name) OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::NoDevice, - "Could not create component instance: %u", err}; + "Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; #if CAN_ENUMERATE if(audioDevice != kAudioDeviceUnknown) @@ -362,7 +422,7 @@ void CoreAudioPlayback::open(const char *name) err = AudioUnitInitialize(audioUnit); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not initialize audio unit: %u", err}; + "Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; /* WARNING: I don't know if "valid" audio unit values are guaranteed to be * non-0. If not, this logic is broken. @@ -375,7 +435,7 @@ void CoreAudioPlayback::open(const char *name) mAudioUnit = audioUnit; #if CAN_ENUMERATE - if(name) + if(!name.empty()) mDevice->DeviceName = name; else { @@ -388,6 +448,21 @@ void CoreAudioPlayback::open(const char *name) if(!devname.empty()) mDevice->DeviceName = std::move(devname); else mDevice->DeviceName = "Unknown Device Name"; } + + if(audioDevice != kAudioDeviceUnknown) + { + UInt32 type{}; + err = GetDevProperty(audioDevice, kAudioDevicePropertyDataSource, false, + kAudioObjectPropertyElementMaster, sizeof(type), &type); + if(err != noErr) + ERR("Failed to get audio device type: %u\n", err); + else + { + TRACE("Got device type '%s'\n", FourCCPrinter{type}.c_str()); + mDevice->Flags.set(DirectEar, (type == kIOAudioOutputPortSubTypeHeadphones)); + } + } + #else mDevice->DeviceName = name; #endif @@ -397,7 +472,7 @@ bool CoreAudioPlayback::reset() { OSStatus err{AudioUnitUninitialize(mAudioUnit)}; if(err != noErr) - ERR("-- AudioUnitUninitialize failed.\n"); + ERR("AudioUnitUninitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); /* retrieve default output unit's properties (output side) */ AudioStreamBasicDescription streamFormat{}; @@ -406,7 +481,8 @@ bool CoreAudioPlayback::reset() OutputElement, &streamFormat, &size); if(err != noErr || size != sizeof(streamFormat)) { - ERR("AudioUnitGetProperty failed\n"); + ERR("AudioUnitGetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), + err); return false; } @@ -473,7 +549,8 @@ bool CoreAudioPlayback::reset() OutputElement, &streamFormat, sizeof(streamFormat)); if(err != noErr) { - ERR("AudioUnitSetProperty failed\n"); + ERR("AudioUnitSetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), + err); return false; } @@ -482,14 +559,16 @@ bool CoreAudioPlayback::reset() /* setup callback */ mFrameSize = mDevice->frameSizeFromFmt(); AURenderCallbackStruct input{}; - input.inputProc = CoreAudioPlayback::MixerProcC; + input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept + { return static_cast<CoreAudioPlayback*>(inRefCon)->MixerProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); }; input.inputProcRefCon = this; err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct)); if(err != noErr) { - ERR("AudioUnitSetProperty failed\n"); + ERR("AudioUnitSetProperty(SetRenderCallback) failed: '%s' (%u)\n", + FourCCPrinter{err}.c_str(), err); return false; } @@ -497,7 +576,7 @@ bool CoreAudioPlayback::reset() err = AudioUnitInitialize(mAudioUnit); if(err != noErr) { - ERR("AudioUnitInitialize failed\n"); + ERR("AudioUnitInitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); return false; } @@ -509,14 +588,14 @@ void CoreAudioPlayback::start() const OSStatus err{AudioOutputUnitStart(mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "AudioOutputUnitStart failed: %d", err}; + "AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; } void CoreAudioPlayback::stop() { OSStatus err{AudioOutputUnitStop(mAudioUnit)}; if(err != noErr) - ERR("AudioOutputUnitStop failed\n"); + ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); } @@ -527,18 +606,11 @@ struct CoreAudioCapture final : public BackendBase { OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept; - static OSStatus RecordProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, - AudioBufferList *ioData) noexcept - { - return static_cast<CoreAudioCapture*>(inRefCon)->RecordProc(ioActionFlags, inTimeStamp, - inBusNumber, inNumberFrames, ioData); - } - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; AudioUnit mAudioUnit{0}; @@ -548,7 +620,7 @@ struct CoreAudioCapture final : public BackendBase { SampleConverterPtr mConverter; - al::vector<char> mCaptureData; + std::vector<char> mCaptureData; RingBufferPtr mRing{nullptr}; @@ -568,7 +640,7 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags, AudioBufferList*) noexcept { union { - al::byte _[maxz(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))]; + std::byte _[maxz(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))]; AudioBufferList list; } audiobuf{}; @@ -581,7 +653,7 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags, inNumberFrames, &audiobuf.list)}; if(err != noErr) { - ERR("AudioUnitRender capture error: %d\n", err); + ERR("AudioUnitRender capture error: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); return err; } @@ -590,11 +662,11 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags, } -void CoreAudioCapture::open(const char *name) +void CoreAudioCapture::open(std::string_view name) { #if CAN_ENUMERATE AudioDeviceID audioDevice{kAudioDeviceUnknown}; - if(!name) + if(name.empty()) GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(audioDevice), &audioDevice); else @@ -607,16 +679,16 @@ void CoreAudioCapture::open(const char *name) auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name); if(devmatch == CaptureList.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", static_cast<int>(name.length()), name.data()}; audioDevice = devmatch->mId; } #else - if(!name) + if(name.empty()) name = ca_device; - else if(strcmp(name, ca_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + else if(name != ca_device) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + static_cast<int>(name.length()), name.data()}; #endif AudioComponentDescription desc{}; @@ -640,7 +712,7 @@ void CoreAudioCapture::open(const char *name) OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::NoDevice, - "Could not create component instance: %u", err}; + "Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; // Turn off AudioUnit output UInt32 enableIO{0}; @@ -648,7 +720,8 @@ void CoreAudioCapture::open(const char *name) kAudioUnitScope_Output, OutputElement, &enableIO, sizeof(enableIO)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not disable audio unit output property: %u", err}; + "Could not disable audio unit output property: '%s' (%u)", FourCCPrinter{err}.c_str(), + err}; // Turn on AudioUnit input enableIO = 1; @@ -656,7 +729,8 @@ void CoreAudioCapture::open(const char *name) kAudioUnitScope_Input, InputElement, &enableIO, sizeof(enableIO)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not enable audio unit input property: %u", err}; + "Could not enable audio unit input property: '%s' (%u)", FourCCPrinter{err}.c_str(), + err}; #if CAN_ENUMERATE if(audioDevice != kAudioDeviceUnknown) @@ -666,14 +740,15 @@ void CoreAudioCapture::open(const char *name) // set capture callback AURenderCallbackStruct input{}; - input.inputProc = CoreAudioCapture::RecordProcC; + input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept + { return static_cast<CoreAudioCapture*>(inRefCon)->RecordProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); }; input.inputProcRefCon = this; err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not set capture callback: %u", err}; + "Could not set capture callback: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; // Disable buffer allocation for capture UInt32 flag{0}; @@ -681,13 +756,14 @@ void CoreAudioCapture::open(const char *name) kAudioUnitScope_Output, InputElement, &flag, sizeof(flag)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not disable buffer allocation property: %u", err}; + "Could not disable buffer allocation property: '%s' (%u)", FourCCPrinter{err}.c_str(), + err}; // Initialize the device err = AudioUnitInitialize(mAudioUnit); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not initialize audio unit: %u", err}; + "Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; // Get the hardware format AudioStreamBasicDescription hardwareFormat{}; @@ -696,7 +772,7 @@ void CoreAudioCapture::open(const char *name) InputElement, &hardwareFormat, &propertySize); if(err != noErr || propertySize != sizeof(hardwareFormat)) throw al::backend_exception{al::backend_error::DeviceError, - "Could not get input format: %u", err}; + "Could not get input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; // Set up the requested format description AudioStreamBasicDescription requestedFormat{}; @@ -777,7 +853,7 @@ void CoreAudioCapture::open(const char *name) InputElement, &outputFormat, sizeof(outputFormat)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not set input format: %u", err}; + "Could not set input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; /* Calculate the minimum AudioUnit output format frame count for the pre- * conversion ring buffer. Ensure at least 100ms for the total buffer. @@ -796,7 +872,7 @@ void CoreAudioCapture::open(const char *name) kAudioUnitScope_Global, OutputElement, &outputFrameCount, &propertySize); if(err != noErr || propertySize != sizeof(outputFrameCount)) throw al::backend_exception{al::backend_error::DeviceError, - "Could not get input frame count: %u", err}; + "Could not get input frame count: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; mCaptureData.resize(outputFrameCount * mFrameSize); @@ -810,7 +886,7 @@ void CoreAudioCapture::open(const char *name) mDevice->Frequency, Resampler::FastBSinc24); #if CAN_ENUMERATE - if(name) + if(!name.empty()) mDevice->DeviceName = name; else { @@ -834,17 +910,17 @@ void CoreAudioCapture::start() OSStatus err{AudioOutputUnitStart(mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "AudioOutputUnitStart failed: %d", err}; + "AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; } void CoreAudioCapture::stop() { OSStatus err{AudioOutputUnitStop(mAudioUnit)}; if(err != noErr) - ERR("AudioOutputUnitStop failed\n"); + ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); } -void CoreAudioCapture::captureSamples(al::byte *buffer, uint samples) +void CoreAudioCapture::captureSamples(std::byte *buffer, uint samples) { if(!mConverter) { @@ -882,7 +958,13 @@ BackendFactory &CoreAudioBackendFactory::getFactory() return factory; } -bool CoreAudioBackendFactory::init() { return true; } +bool CoreAudioBackendFactory::init() +{ +#if CAN_ENUMERATE + sDeviceHelper.emplace(); +#endif + return true; +} bool CoreAudioBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } @@ -930,3 +1012,18 @@ BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendTyp return BackendPtr{new CoreAudioCapture{device}}; return nullptr; } + +alc::EventSupport CoreAudioBackendFactory::queryEventSupport(alc::EventType eventType, BackendType) +{ + switch(eventType) + { + case alc::EventType::DefaultDeviceChanged: + return alc::EventSupport::FullSupport; + + case alc::EventType::DeviceAdded: + case alc::EventType::DeviceRemoved: + case alc::EventType::Count: + break; + } + return alc::EventSupport::NoSupport; +} diff --git a/alc/backends/coreaudio.h b/alc/backends/coreaudio.h index 1252edde..6ea4307c 100644 --- a/alc/backends/coreaudio.h +++ b/alc/backends/coreaudio.h @@ -9,6 +9,8 @@ public: bool querySupport(BackendType type) override; + alc::EventSupport queryEventSupport(alc::EventType eventType, BackendType type) override; + std::string probe(BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override; diff --git a/alc/backends/dsound.cpp b/alc/backends/dsound.cpp index f549c0fe..58aa69b2 100644 --- a/alc/backends/dsound.cpp +++ b/alc/backends/dsound.cpp @@ -25,10 +25,6 @@ #define WIN32_LEAN_AND_MEAN #include <windows.h> -#include <stdlib.h> -#include <stdio.h> -#include <memory.h> - #include <cguid.h> #include <mmreg.h> #ifndef _WAVEFORMATEXTENSIBLE_ @@ -36,15 +32,22 @@ #include <ksmedia.h> #endif +#include <algorithm> #include <atomic> #include <cassert> -#include <thread> +#include <functional> +#include <memory.h> +#include <mutex> +#include <stdlib.h> +#include <stdio.h> #include <string> +#include <thread> #include <vector> -#include <algorithm> -#include <functional> +#include "albit.h" #include "alnumeric.h" +#include "alspan.h" +#include "althrd_setname.h" #include "comptr.h" #include "core/device.h" #include "core/helpers.h" @@ -52,7 +55,6 @@ #include "dynload.h" #include "ringbuffer.h" #include "strutils.h" -#include "threads.h" /* MinGW-w64 needs this for some unknown reason now. */ using LPCWAVEFORMATEX = const WAVEFORMATEX*; @@ -129,10 +131,10 @@ struct DevMap { { } }; -al::vector<DevMap> PlaybackDevices; -al::vector<DevMap> CaptureDevices; +std::vector<DevMap> PlaybackDevices; +std::vector<DevMap> CaptureDevices; -bool checkName(const al::vector<DevMap> &list, const std::string &name) +bool checkName(const al::span<DevMap> list, const std::string &name) { auto match_name = [&name](const DevMap &entry) -> bool { return entry.name == name; }; @@ -144,7 +146,7 @@ BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, voi if(!guid) return TRUE; - auto& devices = *static_cast<al::vector<DevMap>*>(data); + auto& devices = *static_cast<std::vector<DevMap>*>(data); const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)}; int count{1}; @@ -176,7 +178,7 @@ struct DSoundPlayback final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -299,24 +301,22 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() return 0; } -void DSoundPlayback::open(const char *name) +void DSoundPlayback::open(std::string_view name) { HRESULT hr; if(PlaybackDevices.empty()) { /* Initialize COM to prevent name truncation */ - HRESULT hrcom{CoInitialize(nullptr)}; + ComWrapper com{}; hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices); if(FAILED(hr)) ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr); - if(SUCCEEDED(hrcom)) - CoUninitialize(); } const GUID *guid{nullptr}; - if(!name && !PlaybackDevices.empty()) + if(name.empty() && !PlaybackDevices.empty()) { - name = PlaybackDevices[0].name.c_str(); + name = PlaybackDevices[0].name; guid = &PlaybackDevices[0].guid; } else @@ -332,7 +332,8 @@ void DSoundPlayback::open(const char *name) [&id](const DevMap &entry) -> bool { return entry.guid == id; }); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", static_cast<int>(name.length()), + name.data()}; } guid = &iter->guid; } @@ -347,7 +348,7 @@ void DSoundPlayback::open(const char *name) //DirectSound Init code ComPtr<IDirectSound> ds; if(SUCCEEDED(hr)) - hr = DirectSoundCreate(guid, ds.getPtr(), nullptr); + hr = DirectSoundCreate(guid, al::out_ptr(ds), nullptr); if(SUCCEEDED(hr)) hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY); if(FAILED(hr)) @@ -460,7 +461,7 @@ retry_open: DSBUFFERDESC DSBDescription{}; DSBDescription.dwSize = sizeof(DSBDescription); DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; - hr = mDS->CreateSoundBuffer(&DSBDescription, mPrimaryBuffer.getPtr(), nullptr); + hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mPrimaryBuffer), nullptr); } if(SUCCEEDED(hr)) hr = mPrimaryBuffer->SetFormat(&OutputType.Format); @@ -480,7 +481,7 @@ retry_open: DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign; DSBDescription.lpwfxFormat = &OutputType.Format; - hr = mDS->CreateSoundBuffer(&DSBDescription, mBuffer.getPtr(), nullptr); + hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mBuffer), nullptr); if(FAILED(hr) && mDevice->FmtType == DevFmtFloat) { mDevice->FmtType = DevFmtShort; @@ -490,12 +491,9 @@ retry_open: if(SUCCEEDED(hr)) { - void *ptr; - hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr); + hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, al::out_ptr(mNotifies)); if(SUCCEEDED(hr)) { - mNotifies = ComPtr<IDirectSoundNotify>{static_cast<IDirectSoundNotify*>(ptr)}; - uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; assert(num_updates <= MAX_UPDATES); @@ -550,10 +548,10 @@ struct DSoundCapture final : public BackendBase { DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~DSoundCapture() override; - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; ComPtr<IDirectSoundCapture> mDSC; @@ -577,24 +575,22 @@ DSoundCapture::~DSoundCapture() } -void DSoundCapture::open(const char *name) +void DSoundCapture::open(std::string_view name) { HRESULT hr; if(CaptureDevices.empty()) { /* Initialize COM to prevent name truncation */ - HRESULT hrcom{CoInitialize(nullptr)}; + ComWrapper com{}; hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices); if(FAILED(hr)) ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr); - if(SUCCEEDED(hrcom)) - CoUninitialize(); } const GUID *guid{nullptr}; - if(!name && !CaptureDevices.empty()) + if(name.empty() && !CaptureDevices.empty()) { - name = CaptureDevices[0].name.c_str(); + name = CaptureDevices[0].name; guid = &CaptureDevices[0].guid; } else @@ -610,7 +606,8 @@ void DSoundCapture::open(const char *name) [&id](const DevMap &entry) -> bool { return entry.guid == id; }); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", static_cast<int>(name.length()), + name.data()}; } guid = &iter->guid; } @@ -679,9 +676,9 @@ void DSoundCapture::open(const char *name) DSCBDescription.lpwfxFormat = &InputType.Format; //DirectSoundCapture Init code - hr = DirectSoundCaptureCreate(guid, mDSC.getPtr(), nullptr); + hr = DirectSoundCaptureCreate(guid, al::out_ptr(mDSC), nullptr); if(SUCCEEDED(hr)) - mDSC->CreateCaptureBuffer(&DSCBDescription, mDSCbuffer.getPtr(), nullptr); + mDSC->CreateCaptureBuffer(&DSCBDescription, al::out_ptr(mDSCbuffer), nullptr); if(SUCCEEDED(hr)) mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false); @@ -719,7 +716,7 @@ void DSoundCapture::stop() } } -void DSoundCapture::captureSamples(al::byte *buffer, uint samples) +void DSoundCapture::captureSamples(std::byte *buffer, uint samples) { mRing->read(buffer, samples); } uint DSoundCapture::availableSamples() @@ -781,7 +778,7 @@ bool DSoundBackendFactory::init() } #define LOAD_FUNC(f) do { \ - p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(ds_handle, #f)); \ + p##f = al::bit_cast<decltype(p##f)>(GetSymbol(ds_handle, #f)); \ if(!p##f) \ { \ CloseLib(ds_handle); \ @@ -814,8 +811,8 @@ std::string DSoundBackendFactory::probe(BackendType type) }; /* Initialize COM to prevent name truncation */ + ComWrapper com{}; HRESULT hr; - HRESULT hrcom{CoInitialize(nullptr)}; switch(type) { case BackendType::Playback: @@ -834,8 +831,6 @@ std::string DSoundBackendFactory::probe(BackendType type) std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); break; } - if(SUCCEEDED(hrcom)) - CoUninitialize(); return outnames; } diff --git a/alc/backends/jack.cpp b/alc/backends/jack.cpp index 791002ca..a0a5c440 100644 --- a/alc/backends/jack.cpp +++ b/alc/backends/jack.cpp @@ -22,23 +22,26 @@ #include "jack.h" +#include <array> #include <cstdlib> #include <cstdio> #include <cstring> #include <memory.h> - -#include <array> +#include <mutex> #include <thread> #include <functional> +#include <vector> +#include "albit.h" #include "alc/alconfig.h" #include "alnumeric.h" +#include "alsem.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" #include "ringbuffer.h" -#include "threads.h" #include <jack/jack.h> #include <jack/ringbuffer.h> @@ -126,7 +129,7 @@ bool jack_load() error = false; #define LOAD_FUNC(f) do { \ - p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)); \ + p##f = al::bit_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)); \ if(p##f == nullptr) { \ error = true; \ missing_funcs += "\n" #f; \ @@ -135,7 +138,7 @@ bool jack_load() JACK_FUNCS(LOAD_FUNC); #undef LOAD_FUNC /* Optional symbols. These don't exist in all versions of JACK. */ -#define LOAD_SYM(f) p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)) +#define LOAD_SYM(f) p##f = al::bit_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)) LOAD_SYM(jack_error_callback); #undef LOAD_SYM @@ -167,10 +170,10 @@ struct DeviceEntry { { } }; -al::vector<DeviceEntry> PlaybackList; +std::vector<DeviceEntry> PlaybackList; -void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list) +void EnumerateDevices(jack_client_t *client, std::vector<DeviceEntry> &list) { std::remove_reference_t<decltype(list)>{}.swap(list); @@ -295,7 +298,7 @@ struct JackPlayback final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -457,7 +460,7 @@ int JackPlayback::mixerProc() } -void JackPlayback::open(const char *name) +void JackPlayback::open(std::string_view name) { if(!mClient) { @@ -481,9 +484,9 @@ void JackPlayback::open(const char *name) if(PlaybackList.empty()) EnumerateDevices(mClient, PlaybackList); - if(!name && !PlaybackList.empty()) + if(name.empty() && !PlaybackList.empty()) { - name = PlaybackList[0].mName.c_str(); + name = PlaybackList[0].mName; mPortPattern = PlaybackList[0].mPattern; } else @@ -493,14 +496,10 @@ void JackPlayback::open(const char *name) auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name); if(iter == PlaybackList.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name?name:""}; + "Device name \"%.*s\" not found", static_cast<int>(name.length()), name.data()}; mPortPattern = iter->mPattern; } - mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", true); - jack_set_process_callback(mClient, - mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this); - mDevice->DeviceName = name; } @@ -511,6 +510,10 @@ bool JackPlayback::reset() std::for_each(mPort.begin(), mPort.end(), unregister_port); mPort.fill(nullptr); + mRTMixing = GetConfigValueBool(mDevice->DeviceName.c_str(), "jack", "rt-mix", true); + jack_set_process_callback(mClient, + mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this); + /* Ignore the requested buffer metrics and just keep one JACK-sized buffer * ready for when requested. */ @@ -586,7 +589,7 @@ void JackPlayback::start() throw al::backend_exception{al::backend_error::DeviceError, "No playback ports found"}; } - for(size_t i{0};i < al::size(mPort) && mPort[i];++i) + for(size_t i{0};i < std::size(mPort) && mPort[i];++i) { if(!pnames[i]) { diff --git a/alc/backends/loopback.cpp b/alc/backends/loopback.cpp index bf4ab246..2972fc01 100644 --- a/alc/backends/loopback.cpp +++ b/alc/backends/loopback.cpp @@ -30,7 +30,7 @@ namespace { struct LoopbackBackend final : public BackendBase { LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { } - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -39,7 +39,7 @@ struct LoopbackBackend final : public BackendBase { }; -void LoopbackBackend::open(const char *name) +void LoopbackBackend::open(std::string_view name) { mDevice->DeviceName = name; } diff --git a/alc/backends/null.cpp b/alc/backends/null.cpp index 5a8fc255..3c68e4ce 100644 --- a/alc/backends/null.cpp +++ b/alc/backends/null.cpp @@ -30,10 +30,10 @@ #include <functional> #include <thread> -#include "core/device.h" +#include "althrd_setname.h" #include "almalloc.h" +#include "core/device.h" #include "core/helpers.h" -#include "threads.h" namespace { @@ -50,7 +50,7 @@ struct NullBackend final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -105,13 +105,13 @@ int NullBackend::mixerProc() } -void NullBackend::open(const char *name) +void NullBackend::open(std::string_view name) { - if(!name) + if(name.empty()) name = nullDevice; - else if(strcmp(name, nullDevice) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + else if(name != nullDevice) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + static_cast<int>(name.length()), name.data()}; mDevice->DeviceName = name; } diff --git a/alc/backends/oboe.cpp b/alc/backends/oboe.cpp index 461f5a6a..b7bab19a 100644 --- a/alc/backends/oboe.cpp +++ b/alc/backends/oboe.cpp @@ -28,7 +28,9 @@ struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override; - void open(const char *name) override; + void onErrorAfterClose(oboe::AudioStream* /* audioStream */, oboe::Result /* error */) override; + + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -46,14 +48,21 @@ oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStrea return oboe::DataCallbackResult::Continue; } +void OboePlayback::onErrorAfterClose(oboe::AudioStream* audioStream, oboe::Result error) +{ + if (error == oboe::Result::ErrorDisconnected) { + mDevice->handleDisconnect("Oboe AudioStream was disconnected: %s", oboe::convertToText(error)); + } + TRACE("Error was %s", oboe::convertToText(error)); +} -void OboePlayback::open(const char *name) +void OboePlayback::open(std::string_view name) { - if(!name) + if(name.empty()) 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}; + else if(name != device_name) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + static_cast<int>(name.length()), name.data()}; /* Open a basic output stream, just to ensure it can work. */ oboe::ManagedStream stream; @@ -197,8 +206,7 @@ void OboePlayback::stop() { oboe::Result result{mStream->stop()}; if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s", - oboe::convertToText(result)}; + ERR("Failed to stop stream: %s\n", oboe::convertToText(result)); } @@ -212,10 +220,10 @@ struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override; - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; }; @@ -227,13 +235,13 @@ oboe::DataCallbackResult OboeCapture::onAudioReady(oboe::AudioStream*, void *aud } -void OboeCapture::open(const char *name) +void OboeCapture::open(std::string_view name) { - if(!name) + if(name.empty()) 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}; + else if(name != device_name) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + static_cast<int>(name.length()), name.data()}; oboe::AudioStreamBuilder builder; builder.setDirection(oboe::Direction::Input) @@ -315,14 +323,13 @@ void OboeCapture::stop() { const oboe::Result result{mStream->stop()}; if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s", - oboe::convertToText(result)}; + ERR("Failed to stop stream: %s\n", oboe::convertToText(result)); } uint OboeCapture::availableSamples() { return static_cast<uint>(mRing->readSpace()); } -void OboeCapture::captureSamples(al::byte *buffer, uint samples) +void OboeCapture::captureSamples(std::byte *buffer, uint samples) { mRing->read(buffer, samples); } } // namespace diff --git a/alc/backends/opensl.cpp b/alc/backends/opensl.cpp index f5b98fb8..61e3c9a7 100644 --- a/alc/backends/opensl.cpp +++ b/alc/backends/opensl.cpp @@ -26,20 +26,22 @@ #include <stdlib.h> #include <jni.h> -#include <new> #include <array> #include <cstring> +#include <mutex> +#include <new> #include <thread> #include <functional> #include "albit.h" #include "alnumeric.h" +#include "alsem.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "opthelpers.h" #include "ringbuffer.h" -#include "threads.h" #include <SLES/OpenSLES.h> #include <SLES/OpenSLES_Android.h> @@ -159,12 +161,10 @@ struct OpenSLPlayback final : public BackendBase { ~OpenSLPlayback() override; void process(SLAndroidSimpleBufferQueueItf bq) noexcept; - static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept - { static_cast<OpenSLPlayback*>(context)->process(bq); } int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -312,13 +312,13 @@ int OpenSLPlayback::mixerProc() } -void OpenSLPlayback::open(const char *name) +void OpenSLPlayback::open(std::string_view name) { - if(!name) + if(name.empty()) name = opensl_device; - else if(strcmp(name, opensl_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + else if(name != opensl_device) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + static_cast<int>(name.length()), name.data()}; /* There's only one device, so if it's already open, there's nothing to do. */ if(mEngineObj) return; @@ -564,7 +564,9 @@ void OpenSLPlayback::start() PrintErr(result, "bufferQueue->GetInterface"); if(SL_RESULT_SUCCESS == result) { - result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this); + result = VCALL(bufferQueue,RegisterCallback)( + [](SLAndroidSimpleBufferQueueItf bq, void *context) noexcept + { static_cast<OpenSLPlayback*>(context)->process(bq); }, this); PrintErr(result, "bufferQueue->RegisterCallback"); } if(SL_RESULT_SUCCESS != result) @@ -642,13 +644,11 @@ struct OpenSLCapture final : public BackendBase { ~OpenSLCapture() override; void process(SLAndroidSimpleBufferQueueItf bq) noexcept; - static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept - { static_cast<OpenSLCapture*>(context)->process(bq); } - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; /* engine interfaces */ @@ -686,13 +686,13 @@ void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf) noexcept } -void OpenSLCapture::open(const char* name) +void OpenSLCapture::open(std::string_view name) { - if(!name) + if(name.empty()) name = opensl_device; - else if(strcmp(name, opensl_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + else if(name != opensl_device) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + static_cast<int>(name.length()), name.data()}; SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)}; PrintErr(result, "slCreateEngine"); @@ -813,13 +813,15 @@ void OpenSLCapture::open(const char* name) } if(SL_RESULT_SUCCESS == result) { - result = VCALL(bufferQueue,RegisterCallback)(&OpenSLCapture::processC, this); + result = VCALL(bufferQueue,RegisterCallback)( + [](SLAndroidSimpleBufferQueueItf bq, void *context) noexcept + { static_cast<OpenSLCapture*>(context)->process(bq); }, this); PrintErr(result, "bufferQueue->RegisterCallback"); } if(SL_RESULT_SUCCESS == result) { const uint chunk_size{mDevice->UpdateSize * mFrameSize}; - const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::byte{0}; + const auto silence = (mDevice->FmtType == DevFmtUByte) ? std::byte{0x80} : std::byte{0}; auto data = mRing->getWriteVector(); std::fill_n(data.first.buf, data.first.len*chunk_size, silence); @@ -883,7 +885,7 @@ void OpenSLCapture::stop() } } -void OpenSLCapture::captureSamples(al::byte *buffer, uint samples) +void OpenSLCapture::captureSamples(std::byte *buffer, uint samples) { const uint update_size{mDevice->UpdateSize}; const uint chunk_size{update_size * mFrameSize}; @@ -932,7 +934,7 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples) return; /* For each buffer chunk that was fully read, queue another writable buffer - * chunk to keep the OpenSL queue full. This is rather convulated, as a + * chunk to keep the OpenSL queue full. This is rather convoluted, as a * result of the ring buffer holding more elements than are writable at a * given time. The end of the write vector increments when the read pointer * advances, which will "expose" a previously unwritable element. So for diff --git a/alc/backends/oss.cpp b/alc/backends/oss.cpp index 6d4fa261..87d3ba35 100644 --- a/alc/backends/oss.cpp +++ b/alc/backends/oss.cpp @@ -31,27 +31,23 @@ #include <algorithm> #include <atomic> #include <cerrno> -#include <cstdio> #include <cstring> #include <exception> #include <functional> #include <memory> -#include <new> #include <string> #include <thread> #include <utility> +#include <vector> -#include "albyte.h" #include "alc/alconfig.h" #include "almalloc.h" #include "alnumeric.h" -#include "aloptional.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "ringbuffer.h" -#include "threads.h" -#include "vector.h" #include <sys/soundcard.h> @@ -92,22 +88,22 @@ struct DevMap { std::string device_name; }; -al::vector<DevMap> PlaybackDevices; -al::vector<DevMap> CaptureDevices; +std::vector<DevMap> PlaybackDevices; +std::vector<DevMap> CaptureDevices; #ifdef ALC_OSS_COMPAT #define DSP_CAP_OUTPUT 0x00020000 #define DSP_CAP_INPUT 0x00010000 -void ALCossListPopulate(al::vector<DevMap> &devlist, int type) +void ALCossListPopulate(std::vector<DevMap> &devlist, int type) { devlist.emplace_back(DevMap{DefaultName, (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback}); } #else -void ALCossListAppend(al::vector<DevMap> &list, al::span<const char> handle, al::span<const char> path) +void ALCossListAppend(std::vector<DevMap> &list, al::span<const char> handle, al::span<const char> path) { #ifdef ALC_OSS_DEVNODE_TRUC for(size_t i{0};i < path.size();++i) @@ -152,7 +148,7 @@ void ALCossListAppend(al::vector<DevMap> &list, al::span<const char> handle, al: TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); } -void ALCossListPopulate(al::vector<DevMap> &devlist, int type_flag) +void ALCossListPopulate(std::vector<DevMap> &devlist, int type_flag) { int fd{open("/dev/mixer", O_RDONLY)}; if(fd < 0) @@ -231,14 +227,14 @@ struct OSSPlayback final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; int mFd{-1}; - al::vector<al::byte> mMixData; + std::vector<std::byte> mMixData; std::atomic<bool> mKillNow{true}; std::thread mThread; @@ -284,7 +280,7 @@ int OSSPlayback::mixerProc() continue; } - al::byte *write_ptr{mMixData.data()}; + std::byte *write_ptr{mMixData.data()}; size_t to_write{mMixData.size()}; mDevice->renderSamples(write_ptr, static_cast<uint>(to_write/frame_size), frame_step); while(to_write > 0 && !mKillNow.load(std::memory_order_acquire)) @@ -308,10 +304,10 @@ int OSSPlayback::mixerProc() } -void OSSPlayback::open(const char *name) +void OSSPlayback::open(std::string_view name) { const char *devname{DefaultPlayback.c_str()}; - if(!name) + if(name.empty()) name = DefaultName; else { @@ -324,7 +320,7 @@ void OSSPlayback::open(const char *name) ); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", static_cast<int>(name.length()), name.data()}; devname = iter->device_name.c_str(); } @@ -447,10 +443,10 @@ struct OSScapture final : public BackendBase { int recordProc(); - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; int mFd{-1}; @@ -516,10 +512,10 @@ int OSScapture::recordProc() } -void OSScapture::open(const char *name) +void OSScapture::open(std::string_view name) { const char *devname{DefaultCapture.c_str()}; - if(!name) + if(name.empty()) name = DefaultName; else { @@ -532,7 +528,7 @@ void OSScapture::open(const char *name) ); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", static_cast<int>(name.length()), name.data()}; devname = iter->device_name.c_str(); } @@ -620,7 +616,7 @@ void OSScapture::stop() ERR("Error resetting device: %s\n", strerror(errno)); } -void OSScapture::captureSamples(al::byte *buffer, uint samples) +void OSScapture::captureSamples(std::byte *buffer, uint samples) { mRing->read(buffer, samples); } uint OSScapture::availableSamples() diff --git a/alc/backends/pipewire.cpp b/alc/backends/pipewire.cpp index c6569a74..6a001d7a 100644 --- a/alc/backends/pipewire.cpp +++ b/alc/backends/pipewire.cpp @@ -24,6 +24,7 @@ #include <algorithm> #include <atomic> +#include <cstddef> #include <cstring> #include <cerrno> #include <chrono> @@ -31,16 +32,16 @@ #include <list> #include <memory> #include <mutex> +#include <optional> #include <stdint.h> #include <thread> #include <type_traits> #include <utility> -#include "albyte.h" +#include "albit.h" #include "alc/alconfig.h" #include "almalloc.h" #include "alnumeric.h" -#include "aloptional.h" #include "alspan.h" #include "alstring.h" #include "core/devformat.h" @@ -107,12 +108,12 @@ 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 make_pod_builder(void *data, uint32_t size) noexcept +{ return SPA_POD_BUILDER_INIT(data, size); } + constexpr auto PwIdAny = PW_ID_ANY; } // namespace @@ -120,6 +121,44 @@ _Pragma("GCC diagnostic pop") namespace { +struct PodDynamicBuilder { +private: + std::vector<std::byte> mStorage; + spa_pod_builder mPod{}; + + int overflow(uint32_t size) noexcept + { + try { + mStorage.resize(size); + } + catch(...) { + ERR("Failed to resize POD storage\n"); + return -ENOMEM; + } + mPod.data = mStorage.data(); + mPod.size = size; + return 0; + } + +public: + PodDynamicBuilder(uint32_t initSize=0) : mStorage(initSize) + , mPod{make_pod_builder(mStorage.data(), initSize)} + { + static constexpr auto callbacks{[] + { + spa_pod_builder_callbacks cb{}; + cb.version = SPA_VERSION_POD_BUILDER_CALLBACKS; + cb.overflow = [](void *data, uint32_t size) noexcept + { return static_cast<PodDynamicBuilder*>(data)->overflow(size); }; + return cb; + }()}; + + spa_pod_builder_set_callbacks(&mPod, &callbacks, this); + } + + spa_pod_builder *get() noexcept { return &mPod; } +}; + /* 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" @@ -210,7 +249,7 @@ bool pwire_load() } #define LOAD_FUNC(f) do { \ - p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pwire_handle, #f)); \ + p##f = al::bit_cast<decltype(p##f)>(GetSymbol(pwire_handle, #f)); \ if(p##f == nullptr) missing_funcs += "\n" #f; \ } while(0); PWIRE_FUNCS(LOAD_FUNC) @@ -304,12 +343,12 @@ al::span<const Pod_t<T>> get_array_span(const spa_pod *pod) } template<uint32_t T> -al::optional<Pod_t<T>> get_value(const spa_pod *value) +std::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; + return std::nullopt; } /* Internally, PipeWire types "inherit" from each other, but this is hidden @@ -328,11 +367,11 @@ To as(From) noexcept = delete; * - pw_metadata */ template<> -pw_proxy* as(pw_registry *reg) noexcept { return reinterpret_cast<pw_proxy*>(reg); } +pw_proxy* as(pw_registry *reg) noexcept { return al::bit_cast<pw_proxy*>(reg); } template<> -pw_proxy* as(pw_node *node) noexcept { return reinterpret_cast<pw_proxy*>(node); } +pw_proxy* as(pw_node *node) noexcept { return al::bit_cast<pw_proxy*>(node); } template<> -pw_proxy* as(pw_metadata *mdata) noexcept { return reinterpret_cast<pw_proxy*>(mdata); } +pw_proxy* as(pw_metadata *mdata) noexcept { return al::bit_cast<pw_proxy*>(mdata); } struct PwContextDeleter { @@ -433,8 +472,75 @@ using MainloopLockGuard = std::lock_guard<ThreadMainloop>; * devices provided by the server. */ -struct NodeProxy; -struct MetadataProxy; +/* 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 = [](void *object, const pw_node_info *info) noexcept + { static_cast<NodeProxy*>(object)->infoCallback(info); }; + ret.param = [](void *object, int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param) noexcept + { static_cast<NodeProxy*>(object)->paramCallback(seq, id, index, next, param); }; + 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 and current formats (indicates the + * default and active format, which is what we're interested in). + */ + uint32_t fmtids[]{SPA_PARAM_EnumFormat, SPA_PARAM_Format}; + ppw_node_subscribe_params(mNode.get(), std::data(fmtids), std::size(fmtids)); + } + ~NodeProxy() + { spa_hook_remove(&mListener); } + + + void infoCallback(const pw_node_info *info) noexcept; + + void paramCallback(int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param) noexcept; +}; + +/* 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 = [](void *object, uint32_t id, const char *key, const char *type, const char *value) noexcept + { return static_cast<MetadataProxy*>(object)->propertyCallback(id, key, type, value); }; + 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) noexcept; +}; + /* The global thread watching for global events. This particular class responds * to objects being added to or removed from the registry. @@ -450,8 +556,8 @@ struct EventManager { /* A list of proxy objects watching for events about changes to objects in * the registry. */ - std::vector<NodeProxy*> mNodeList; - MetadataProxy *mDefaultMetadata{nullptr}; + std::vector<std::unique_ptr<NodeProxy>> mNodeList; + std::optional<MetadataProxy> mDefaultMetadata; /* Initialization handling. When init() is called, mInitSeq is set to a * SequenceID that marks the end of populating the registry. As objects of @@ -463,24 +569,28 @@ struct EventManager { std::atomic<bool> mHasAudio{false}; int mInitSeq{}; + ~EventManager() { if(mLoop) mLoop.stop(); } + bool init(); - ~EventManager(); void kill(); auto lock() const { return mLoop.lock(); } auto unlock() const { return mLoop.unlock(); } + inline bool initIsDone(std::memory_order m=std::memory_order_seq_cst) noexcept + { return mInitDone.load(m); } + /** * 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 + if(!initIsDone(std::memory_order_acquire)) UNLIKELY { MainloopUniqueLock plock{mLoop}; - plock.wait([this](){ return mInitDone.load(std::memory_order_acquire); }); + plock.wait([this](){ return initIsDone(std::memory_order_acquire); }); } } @@ -496,7 +606,7 @@ struct EventManager { 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 || initIsDone(std::memory_order_acquire); }); return has_audio; } @@ -506,38 +616,34 @@ struct EventManager { /* 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)) + if(!initIsDone(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); } + const spa_dict *props) noexcept; - void removeCallback(uint32_t id); - static void removeCallbackC(void *object, uint32_t id) - { static_cast<EventManager*>(object)->removeCallback(id); } + void removeCallback(uint32_t id) noexcept; 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; + ret.global = [](void *object, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const spa_dict *props) noexcept + { static_cast<EventManager*>(object)->addCallback(id, permissions, type, version, props); }; + ret.global_remove = [](void *object, uint32_t id) noexcept + { static_cast<EventManager*>(object)->removeCallback(id); }; 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); } + void coreCallback(uint32_t id, int seq) noexcept; static constexpr pw_core_events CreateCoreEvents() { pw_core_events ret{}; ret.version = PW_VERSION_CORE_EVENTS; - ret.done = &EventManager::coreCallbackC; + ret.done = [](void *object, uint32_t id, int seq) noexcept + { static_cast<EventManager*>(object)->coreCallback(id, seq); }; return ret; } }; @@ -570,12 +676,23 @@ struct DeviceNode { static std::vector<DeviceNode> sList; static DeviceNode &Add(uint32_t id); static DeviceNode *Find(uint32_t id); + static DeviceNode *FindByDevName(std::string_view devname); static void Remove(uint32_t id); - static std::vector<DeviceNode> &GetList() noexcept { return sList; } + static auto GetList() noexcept { return al::span{sList}; } + + void parseSampleRate(const spa_pod *value, bool force_update) noexcept; + void parsePositions(const spa_pod *value, bool force_update) noexcept; + void parseChannelCount(const spa_pod *value, bool force_update) noexcept; - void parseSampleRate(const spa_pod *value) noexcept; - void parsePositions(const spa_pod *value) noexcept; - void parseChannelCount(const spa_pod *value) noexcept; + void callEvent(alc::EventType type, std::string_view message) + { + /* Source nodes aren't recognized for playback, only Sink and Duplex + * nodes are. All node types are recognized for capture. + */ + if(mType != NodeType::Source) + alc::Event(type, alc::DeviceType::Playback, message); + alc::Event(type, alc::DeviceType::Capture, message); + } }; std::vector<DeviceNode> DeviceNode::sList; std::string DefaultSinkDevice; @@ -601,8 +718,7 @@ DeviceNode &DeviceNode::Add(uint32_t id) auto match = std::find_if(sList.begin(), sList.end(), match_id); if(match != sList.end()) return *match; - sList.emplace_back(); - auto &n = sList.back(); + auto &n = sList.emplace_back(); n.mId = id; return n; } @@ -618,6 +734,17 @@ DeviceNode *DeviceNode::Find(uint32_t id) return nullptr; } +DeviceNode *DeviceNode::FindByDevName(std::string_view devname) +{ + auto match_id = [devname](DeviceNode &n) noexcept -> bool + { return n.mDevName == devname; }; + + 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 @@ -625,6 +752,11 @@ void DeviceNode::Remove(uint32_t id) if(n.mId != id) return false; TRACE("Removing device \"%s\"\n", n.mDevName.c_str()); + if(gEventHandler.initIsDone(std::memory_order_relaxed)) + { + const std::string msg{"Device removed: "+n.mName}; + n.callEvent(alc::EventType::DeviceRemoved, msg); + } return true; }; @@ -674,7 +806,7 @@ bool MatchChannelMap(const al::span<const uint32_t> map0, const spa_audio_channe return true; } -void DeviceNode::parseSampleRate(const spa_pod *value) noexcept +void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexcept { /* TODO: Can this be anything else? Long, Float, Double? */ uint32_t nvals{}, choiceType{}; @@ -683,7 +815,7 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept const uint podType{get_pod_type(value)}; if(podType != SPA_TYPE_Int) { - WARN("Unhandled sample rate POD type: %u\n", podType); + WARN(" Unhandled sample rate POD type: %u\n", podType); return; } @@ -691,15 +823,15 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept { if(nvals != 3) { - WARN("Unexpected SPA_CHOICE_Range count: %u\n", nvals); + 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)); + TRACE(" sample rate: %d (range: %d -> %d)\n", srates[0], srates[1], srates[2]); + if(!mSampleRate || force_update) + mSampleRate = static_cast<uint>(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE)); return; } @@ -707,7 +839,7 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept { if(nvals == 0) { - WARN("Unexpected SPA_CHOICE_Enum count: %u\n", nvals); + WARN(" Unexpected SPA_CHOICE_Enum count: %u\n", nvals); return; } auto srates = get_pod_body<int32_t>(value, nvals); @@ -719,7 +851,7 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept others += ", "; others += std::to_string(srates[i]); } - TRACE("Device ID %" PRIu64 " sample rate: %d (%s)\n", mSerial, srates[0], others.c_str()); + TRACE(" sample rate: %d (%s)\n", srates[0], others.c_str()); /* Pick the first rate listed that's within the allowed range (default * rate if possible). */ @@ -727,7 +859,8 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept { if(rate >= MIN_OUTPUT_RATE && rate <= MAX_OUTPUT_RATE) { - mSampleRate = static_cast<uint>(rate); + if(!mSampleRate || force_update) + mSampleRate = static_cast<uint>(rate); break; } } @@ -738,119 +871,100 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept { if(nvals != 1) { - WARN("Unexpected SPA_CHOICE_None count: %u\n", nvals); + 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)); + TRACE(" sample rate: %d\n", srates[0]); + if(!mSampleRate || force_update) + mSampleRate = static_cast<uint>(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE)); return; } - WARN("Unhandled sample rate choice type: %u\n", choiceType); + WARN(" Unhandled sample rate choice type: %u\n", choiceType); } -void DeviceNode::parsePositions(const spa_pod *value) noexcept +void DeviceNode::parsePositions(const spa_pod *value, bool force_update) noexcept { + uint32_t choiceCount{}, choiceType{}; + value = spa_pod_get_values(value, &choiceCount, &choiceType); + + if(choiceType != SPA_CHOICE_None || choiceCount != 1) + { + ERR(" Unexpected positions choice: type=%u, count=%u\n", choiceType, choiceCount); + return; + } + 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)":""); + if(mChannels == InvalidChannelConfig || force_update) + { + 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(" %zu position%s for %s%s\n", chanmap.size(), (chanmap.size()==1)?"":"s", + DevFmtChannelsString(mChannels), mIs51Rear?"(rear)":""); } -void DeviceNode::parseChannelCount(const spa_pod *value) noexcept +void DeviceNode::parseChannelCount(const spa_pod *value, bool force_update) noexcept { /* As a fallback with just a channel count, just assume mono or stereo. */ + uint32_t choiceCount{}, choiceType{}; + value = spa_pod_get_values(value, &choiceCount, &choiceType); + + if(choiceType != SPA_CHOICE_None || choiceCount != 1) + { + ERR(" Unexpected positions choice: type=%u, count=%u\n", choiceType, choiceCount); + return; + } + const auto chancount = get_value<SPA_TYPE_Int>(value); if(!chancount) return; - mIs51Rear = false; + if(mChannels == InvalidChannelConfig || force_update) + { + 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)); + if(*chancount >= 2) + mChannels = DevFmtStereo; + else if(*chancount >= 1) + mChannels = DevFmtMono; + } + TRACE(" %d channel%s for %s\n", *chancount, (*chancount==1)?"":"s", + DevFmtChannelsString(mChannels)); } constexpr char MonitorPrefix[]{"Monitor of "}; -constexpr auto MonitorPrefixLen = al::size(MonitorPrefix) - 1; +constexpr auto MonitorPrefixLen = std::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) +void NodeProxy::infoCallback(const pw_node_info *info) noexcept { /* We only care about property changes here (media class, name/desc). * Format changes will automatically invoke the param callback. @@ -888,6 +1002,7 @@ void NodeProxy::infoCallback(const pw_node_info *info) #ifdef PW_KEY_OBJECT_SERIAL if(const char *serial_str{spa_dict_lookup(info->props, PW_KEY_OBJECT_SERIAL)}) { + errno = 0; char *serial_end{}; serial_id = std::strtoull(serial_str, &serial_end, 0); if(*serial_end != '\0' || errno == ERANGE) @@ -898,15 +1013,40 @@ void NodeProxy::infoCallback(const pw_node_info *info) } #endif + std::string name; + if(nodeName && *nodeName) name = nodeName; + else name = "PipeWire node #"+std::to_string(info->id); + const char *form_factor{spa_dict_lookup(info->props, PW_KEY_DEVICE_FORM_FACTOR)}; TRACE("Got %s device \"%s\"%s%s%s\n", AsString(ntype), devName ? devName : "(nil)", form_factor?" (":"", form_factor?form_factor:"", form_factor?")":""); - TRACE(" \"%s\" = ID %" PRIu64 "\n", nodeName ? nodeName : "(nil)", serial_id); + TRACE(" \"%s\" = ID %" PRIu64 "\n", name.c_str(), 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); + /* This method is called both to notify about a new sink/source node, + * and update properties for the node. It's unclear what properties can + * change for an existing node without being removed first, so err on + * the side of caution: send a DeviceAdded event when the name differs, + * and send a DeviceRemoved event if it had a name that's being + * replaced. + * + * This is overkill if the name or devname can't change. + */ + if(node.mName != name) + { + if(gEventHandler.initIsDone(std::memory_order_relaxed)) + { + if(!node.mName.empty()) + { + const std::string msg{"Device removed: "+node.mName}; + node.callEvent(alc::EventType::DeviceRemoved, msg); + } + const std::string msg{"Device added: "+name}; + node.callEvent(alc::EventType::DeviceAdded, msg); + } + node.mName = std::move(name); + } node.mDevName = devName ? devName : ""; node.mType = ntype; node.mIsHeadphones = form_factor && (al::strcasecmp(form_factor, "headphones") == 0 @@ -914,57 +1054,30 @@ void NodeProxy::infoCallback(const pw_node_info *info) } } -void NodeProxy::paramCallback(int, uint32_t id, uint32_t, uint32_t, const spa_pod *param) +void NodeProxy::paramCallback(int, uint32_t id, uint32_t, uint32_t, const spa_pod *param) noexcept { - if(id == SPA_PARAM_EnumFormat) + if(id == SPA_PARAM_EnumFormat || id == SPA_PARAM_Format) { DeviceNode *node{DeviceNode::Find(mId)}; if(!node) UNLIKELY return; + TRACE("Device ID %" PRIu64 " %s format:\n", node->mSerial, + (id == SPA_PARAM_EnumFormat) ? "enumerable" : "current"); + + const bool force_update{id == SPA_PARAM_Format}; if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_rate)}) - node->parseSampleRate(&prop->value); + node->parseSampleRate(&prop->value, force_update); if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_position)}) - node->parsePositions(&prop->value); + node->parsePositions(&prop->value, force_update); else if((prop=spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_channels)) != nullptr) - node->parseChannelCount(&prop->value); + node->parseChannelCount(&prop->value, force_update); } } -/* 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) + const char *value) noexcept { if(id != PW_ID_CORE) return 0; @@ -997,7 +1110,7 @@ int MetadataProxy::propertyCallback(uint32_t id, const char *key, const char *ty auto get_json_string = [](spa_json *iter) { - al::optional<std::string> str; + std::optional<std::string> str; const char *val{}; int len{spa_json_next(iter, &val)}; @@ -1020,9 +1133,35 @@ int MetadataProxy::propertyCallback(uint32_t id, const char *key, const char *ty TRACE("Got default %s device \"%s\"\n", isCapture ? "capture" : "playback", propValue->c_str()); if(!isCapture) - DefaultSinkDevice = std::move(*propValue); + { + if(DefaultSinkDevice != *propValue) + { + if(gEventHandler.mInitDone.load(std::memory_order_relaxed)) + { + auto entry = DeviceNode::FindByDevName(*propValue); + const std::string msg{"Default playback device changed: "+ + (entry ? entry->mName : std::string{})}; + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, + msg); + } + DefaultSinkDevice = std::move(*propValue); + } + } else - DefaultSourceDevice = std::move(*propValue); + { + if(DefaultSourceDevice != *propValue) + { + if(gEventHandler.mInitDone.load(std::memory_order_relaxed)) + { + auto entry = DeviceNode::FindByDevName(*propValue); + const std::string msg{"Default capture device changed: "+ + (entry ? entry->mName : std::string{})}; + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, + msg); + } + DefaultSourceDevice = std::move(*propValue); + } + } } else { @@ -1044,7 +1183,7 @@ bool EventManager::init() return false; } - mContext = mLoop.newContext(pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr)); + mContext = mLoop.newContext(); if(!mContext) { ERR("Failed to create PipeWire event context (errno: %d)\n", errno); @@ -1085,26 +1224,12 @@ bool EventManager::init() 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); + mDefaultMetadata.reset(); mNodeList.clear(); - if(mDefaultMetadata) - al::destroy_at(mDefaultMetadata); - mDefaultMetadata = nullptr; mRegistry = nullptr; mCore = nullptr; @@ -1113,7 +1238,7 @@ void EventManager::kill() } void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t version, - const spa_dict *props) + const spa_dict *props) noexcept { /* We're only interested in interface nodes. */ if(std::strcmp(type, PW_TYPE_INTERFACE_Node) == 0) @@ -1136,7 +1261,7 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t /* Create the proxy object. */ auto node = PwNodePtr{static_cast<pw_node*>(pw_registry_bind(mRegistry.get(), id, type, - version, sizeof(NodeProxy)))}; + version, 0))}; if(!node) { ERR("Failed to create node proxy object (errno: %d)\n", errno); @@ -1146,8 +1271,7 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t /* 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))); + mNodeList.emplace_back(std::make_unique<NodeProxy>(id, std::move(node))); syncInit(); /* Signal any waiters that we have found a source or sink for audio @@ -1174,42 +1298,32 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t } auto mdata = PwMetadataPtr{static_cast<pw_metadata*>(pw_registry_bind(mRegistry.get(), id, - type, version, sizeof(MetadataProxy)))}; + type, version, 0))}; 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)); + mDefaultMetadata.emplace(id, std::move(mdata)); syncInit(); } } -void EventManager::removeCallback(uint32_t id) +void EventManager::removeCallback(uint32_t id) noexcept { DeviceNode::Remove(id); - auto clear_node = [id](NodeProxy *node) noexcept - { - if(node->mId != id) - return false; - al::destroy_at(node); - return true; - }; + auto clear_node = [id](std::unique_ptr<NodeProxy> &node) noexcept + { return node->mId == id; }; 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; - } + mDefaultMetadata.reset(); } -void EventManager::coreCallback(uint32_t id, int seq) +void EventManager::coreCallback(uint32_t id, int seq) noexcept { if(id == PW_ID_CORE && seq == mInitSeq) { @@ -1275,20 +1389,11 @@ spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e u } 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 stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error) noexcept; + void ioChangedCallback(uint32_t id, void *area, uint32_t size) noexcept; + void outputCallback() noexcept; - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -1309,9 +1414,12 @@ class PipeWirePlayback final : public BackendBase { { pw_stream_events ret{}; ret.version = PW_VERSION_STREAM_EVENTS; - ret.state_changed = &PipeWirePlayback::stateChangedCallbackC; - ret.io_changed = &PipeWirePlayback::ioChangedCallbackC; - ret.process = &PipeWirePlayback::outputCallbackC; + ret.state_changed = [](void *data, pw_stream_state old, pw_stream_state state, const char *error) noexcept + { static_cast<PipeWirePlayback*>(data)->stateChangedCallback(old, state, error); }; + ret.io_changed = [](void *data, uint32_t id, void *area, uint32_t size) noexcept + { static_cast<PipeWirePlayback*>(data)->ioChangedCallback(id, area, size); }; + ret.process = [](void *data) noexcept + { static_cast<PipeWirePlayback*>(data)->outputCallback(); }; return ret; } @@ -1327,10 +1435,10 @@ public: }; -void PipeWirePlayback::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) +void PipeWirePlayback::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) noexcept { mLoop.signal(false); } -void PipeWirePlayback::ioChangedCallback(uint32_t id, void *area, uint32_t size) +void PipeWirePlayback::ioChangedCallback(uint32_t id, void *area, uint32_t size) noexcept { switch(id) { @@ -1341,7 +1449,7 @@ void PipeWirePlayback::ioChangedCallback(uint32_t id, void *area, uint32_t size) } } -void PipeWirePlayback::outputCallback() +void PipeWirePlayback::outputCallback() noexcept { pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())}; if(!pw_buf) UNLIKELY return; @@ -1373,29 +1481,27 @@ void PipeWirePlayback::outputCallback() 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); } + + mDevice->renderSamples({mChannelPtrs.get(), chanptr_end}, length); + pw_buf->size = length; pw_stream_queue_buffer(mStream.get(), pw_buf); } -void PipeWirePlayback::open(const char *name) +void PipeWirePlayback::open(std::string_view name) { static std::atomic<uint> OpenCount{0}; uint64_t targetid{PwIdAny}; std::string devname{}; gEventHandler.waitForInit(); - if(!name) + if(name.empty()) { EventWatcherLockGuard _{gEventHandler}; auto&& devlist = DeviceNode::GetList(); @@ -1430,7 +1536,7 @@ void PipeWirePlayback::open(const char *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}; + "Device name \"%.*s\" not found", static_cast<int>(name.length()), name.data()}; targetid = match->mSerial; devname = match->mName; @@ -1523,14 +1629,10 @@ bool PipeWirePlayback::reset() */ 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)}; + static constexpr uint32_t pod_buffer_size{1024}; + PodDynamicBuilder b(pod_buffer_size); - const spa_pod *params{spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info)}; + const spa_pod *params{spa_format_audio_raw_build(b.get(), SPA_PARAM_EnumFormat, &info)}; if(!params) throw al::backend_exception{al::backend_error::DeviceError, "Failed to set PipeWire audio format parameters"}; @@ -1674,7 +1776,10 @@ void PipeWirePlayback::start() } #endif if(!--wait_count) + { + ERR("Timeout getting PipeWire stream buffering info\n"); break; + } plock.unlock(); std::this_thread::sleep_for(milliseconds{20}); @@ -1769,7 +1874,7 @@ ClockLatency PipeWirePlayback::getClockLatency() 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. + * in case timer drift got that severe. */ ClockLatency ret{}; ret.ClockTime = mixtime; @@ -1780,19 +1885,13 @@ ClockLatency PipeWirePlayback::getClockLatency() 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 stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error) noexcept; + void inputCallback() noexcept; - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; uint64_t mTargetId{PwIdAny}; @@ -1808,8 +1907,10 @@ class PipeWireCapture final : public BackendBase { { pw_stream_events ret{}; ret.version = PW_VERSION_STREAM_EVENTS; - ret.state_changed = &PipeWireCapture::stateChangedCallbackC; - ret.process = &PipeWireCapture::inputCallbackC; + ret.state_changed = [](void *data, pw_stream_state old, pw_stream_state state, const char *error) noexcept + { static_cast<PipeWireCapture*>(data)->stateChangedCallback(old, state, error); }; + ret.process = [](void *data) noexcept + { static_cast<PipeWireCapture*>(data)->inputCallback(); }; return ret; } @@ -1821,10 +1922,10 @@ public: }; -void PipeWireCapture::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) +void PipeWireCapture::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) noexcept { mLoop.signal(false); } -void PipeWireCapture::inputCallback() +void PipeWireCapture::inputCallback() noexcept { pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())}; if(!pw_buf) UNLIKELY return; @@ -1839,14 +1940,14 @@ void PipeWireCapture::inputCallback() } -void PipeWireCapture::open(const char *name) +void PipeWireCapture::open(std::string_view name) { static std::atomic<uint> OpenCount{0}; uint64_t targetid{PwIdAny}; std::string devname{}; gEventHandler.waitForInit(); - if(!name) + if(name.empty()) { EventWatcherLockGuard _{gEventHandler}; auto&& devlist = DeviceNode::GetList(); @@ -1884,16 +1985,17 @@ void PipeWireCapture::open(const char *name) 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) + if(match == devlist.cend() && name.length() >= MonitorPrefixLen + && std::strncmp(name.data(), MonitorPrefix, MonitorPrefixLen) == 0) { - const char *sinkname{name + MonitorPrefixLen}; + const std::string_view sinkname{name.substr(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}; + "Device name \"%.*s\" not found", static_cast<int>(name.length()), name.data()}; targetid = match->mSerial; devname = name; @@ -1952,11 +2054,10 @@ void PipeWireCapture::open(const char *name) } 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)}; + static constexpr uint32_t pod_buffer_size{1024}; + PodDynamicBuilder b(pod_buffer_size); - const spa_pod *params[]{spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info)}; + const spa_pod *params[]{spa_format_audio_raw_build(b.get(), SPA_PARAM_EnumFormat, &info)}; if(!params[0]) throw al::backend_exception{al::backend_error::DeviceError, "Failed to set PipeWire audio format parameters"}; @@ -2054,7 +2155,7 @@ void PipeWireCapture::stop() uint PipeWireCapture::availableSamples() { return static_cast<uint>(mRing->readSpace()); } -void PipeWireCapture::captureSamples(al::byte *buffer, uint samples) +void PipeWireCapture::captureSamples(std::byte *buffer, uint samples) { mRing->read(buffer, samples); } } // namespace @@ -2164,3 +2265,18 @@ BackendFactory &PipeWireBackendFactory::getFactory() static PipeWireBackendFactory factory{}; return factory; } + +alc::EventSupport PipeWireBackendFactory::queryEventSupport(alc::EventType eventType, BackendType) +{ + switch(eventType) + { + case alc::EventType::DefaultDeviceChanged: + case alc::EventType::DeviceAdded: + case alc::EventType::DeviceRemoved: + return alc::EventSupport::FullSupport; + + case alc::EventType::Count: + break; + } + return alc::EventSupport::NoSupport; +} diff --git a/alc/backends/pipewire.h b/alc/backends/pipewire.h index 5f930239..5493684f 100644 --- a/alc/backends/pipewire.h +++ b/alc/backends/pipewire.h @@ -13,6 +13,8 @@ public: bool querySupport(BackendType type) override; + alc::EventSupport queryEventSupport(alc::EventType eventType, BackendType type) override; + std::string probe(BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override; diff --git a/alc/backends/portaudio.cpp b/alc/backends/portaudio.cpp index 9c94587d..979a54d6 100644 --- a/alc/backends/portaudio.cpp +++ b/alc/backends/portaudio.cpp @@ -26,6 +26,7 @@ #include <cstdlib> #include <cstring> +#include "albit.h" #include "alc/alconfig.h" #include "alnumeric.h" #include "core/device.h" @@ -85,7 +86,7 @@ struct PortPlayback final : public BackendBase { framesPerBuffer, timeInfo, statusFlags); } - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -115,13 +116,13 @@ int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long f } -void PortPlayback::open(const char *name) +void PortPlayback::open(std::string_view name) { - if(!name) + if(name.empty()) name = pa_device; - else if(strcmp(name, pa_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + else if(name != pa_device) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + static_cast<int>(name.length()), name.data()}; PaStreamParameters params{}; auto devidopt = ConfigValueInt(nullptr, "port", "device"); @@ -244,10 +245,10 @@ struct PortCapture final : public BackendBase { framesPerBuffer, timeInfo, statusFlags); } - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; PaStream *mStream{nullptr}; @@ -275,13 +276,13 @@ int PortCapture::readCallback(const void *inputBuffer, void*, unsigned long fram } -void PortCapture::open(const char *name) +void PortCapture::open(std::string_view name) { - if(!name) + if(name.empty()) name = pa_device; - else if(strcmp(name, pa_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + else if(name != pa_device) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + static_cast<int>(name.length()), name.data()}; uint samples{mDevice->BufferSize}; samples = maxu(samples, 100 * mDevice->Frequency / 1000); @@ -348,7 +349,7 @@ void PortCapture::stop() uint PortCapture::availableSamples() { return static_cast<uint>(mRing->readSpace()); } -void PortCapture::captureSamples(al::byte *buffer, uint samples) +void PortCapture::captureSamples(std::byte *buffer, uint samples) { mRing->read(buffer, samples); } } // namespace @@ -376,7 +377,7 @@ bool PortBackendFactory::init() return false; #define LOAD_FUNC(f) do { \ - p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pa_handle, #f)); \ + p##f = al::bit_cast<decltype(p##f)>(GetSymbol(pa_handle, #f)); \ if(p##f == nullptr) \ { \ CloseLib(pa_handle); \ diff --git a/alc/backends/pulseaudio.cpp b/alc/backends/pulseaudio.cpp index 4b0e316f..bebc182d 100644 --- a/alc/backends/pulseaudio.cpp +++ b/alc/backends/pulseaudio.cpp @@ -31,17 +31,18 @@ #include <cstring> #include <limits> #include <mutex> +#include <optional> #include <stdint.h> #include <stdlib.h> #include <string> #include <sys/types.h> #include <utility> +#include <vector> -#include "albyte.h" +#include "albit.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" @@ -49,7 +50,6 @@ #include "dynload.h" #include "opthelpers.h" #include "strutils.h" -#include "vector.h" #include <pulse/pulseaudio.h> @@ -65,6 +65,8 @@ using uint = unsigned int; MAGIC(pa_context_get_state); \ MAGIC(pa_context_disconnect); \ MAGIC(pa_context_set_state_callback); \ + MAGIC(pa_context_set_subscribe_callback); \ + MAGIC(pa_context_subscribe); \ MAGIC(pa_context_errno); \ MAGIC(pa_context_connect); \ MAGIC(pa_context_get_server_info); \ @@ -136,6 +138,8 @@ PULSE_FUNCS(MAKE_FUNC) #define pa_context_get_state ppa_context_get_state #define pa_context_disconnect ppa_context_disconnect #define pa_context_set_state_callback ppa_context_set_state_callback +#define pa_context_set_subscribe_callback ppa_context_set_subscribe_callback +#define pa_context_subscribe ppa_context_subscribe #define pa_context_errno ppa_context_errno #define pa_context_connect ppa_context_connect #define pa_context_get_server_info ppa_context_get_server_info @@ -270,6 +274,9 @@ constexpr pa_context_flags_t& operator|=(pa_context_flags_t &lhs, pa_context_fla return lhs; } +constexpr pa_subscription_mask_t operator|(pa_subscription_mask_t lhs, pa_subscription_mask_t rhs) +{ return pa_subscription_mask_t(lhs | al::to_underlying(rhs)); } + struct DevMap { std::string name; @@ -282,8 +289,8 @@ bool checkName(const al::span<const DevMap> list, const std::string &name) return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); } -al::vector<DevMap> PlaybackDevices; -al::vector<DevMap> CaptureDevices; +std::vector<DevMap> PlaybackDevices; +std::vector<DevMap> CaptureDevices; /* Global flags and properties */ @@ -291,13 +298,14 @@ pa_context_flags_t pulse_ctx_flags; class PulseMainloop { pa_threaded_mainloop *mLoop{}; + pa_context *mContext{}; public: 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(); PulseMainloop& operator=(const PulseMainloop&) = delete; PulseMainloop& operator=(PulseMainloop&& rhs) noexcept @@ -316,6 +324,7 @@ public: auto stop() const { return pa_threaded_mainloop_stop(mLoop); } auto getApi() const { return pa_threaded_mainloop_get_api(mLoop); } + auto getContext() const noexcept { return mContext; } auto lock() const { return pa_threaded_mainloop_lock(mLoop); } auto unlock() const { return pa_threaded_mainloop_unlock(mLoop); } @@ -329,7 +338,7 @@ public: 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 close(pa_stream *stream=nullptr); void deviceSinkCallback(pa_context*, const pa_sink_info *info, int eol) noexcept @@ -420,6 +429,41 @@ struct MainloopUniqueLock : public std::unique_lock<PulseMainloop> { } + void setEventHandler() + { + pa_operation *op{pa_context_subscribe(mutex()->mContext, + PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, + [](pa_context*, int, void *pdata) noexcept + { static_cast<PulseMainloop*>(pdata)->signal(); }, + mutex())}; + waitForOperation(op); + + /* Watch for device added/removed events. + * + * TODO: Also track the "default" device, in as much as PulseAudio has + * the concept of a default device (whatever device is opened when not + * specifying a specific sink or source name). There doesn't seem to be + * an event for this. + */ + auto handler = [](pa_context*, pa_subscription_event_type_t t, uint32_t, void*) noexcept + { + const auto eventFacility = (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK); + if(eventFacility == PA_SUBSCRIPTION_EVENT_SINK + || eventFacility == PA_SUBSCRIPTION_EVENT_SOURCE) + { + const auto deviceType = (eventFacility == PA_SUBSCRIPTION_EVENT_SINK) + ? alc::DeviceType::Playback : alc::DeviceType::Capture; + const auto eventType = (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK); + if(eventType == PA_SUBSCRIPTION_EVENT_NEW) + alc::Event(alc::EventType::DeviceAdded, deviceType, "Device added"); + else if(eventType == PA_SUBSCRIPTION_EVENT_REMOVE) + alc::Event(alc::EventType::DeviceRemoved, deviceType, "Device removed"); + } + }; + pa_context_set_subscribe_callback(mutex()->mContext, handler, nullptr); + } + + void contextStateCallback(pa_context *context) noexcept { pa_context_state_t state{pa_context_get_state(context)}; @@ -434,31 +478,46 @@ struct MainloopUniqueLock : public std::unique_lock<PulseMainloop> { mutex()->signal(); } - pa_context *connectContext(); - pa_stream *connectStream(const char *device_name, pa_context *context, pa_stream_flags_t flags, + void connectContext(); + pa_stream *connectStream(const char *device_name, pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type); }; using MainloopLockGuard = std::lock_guard<PulseMainloop>; +PulseMainloop::~PulseMainloop() +{ + if(mContext) + { + MainloopUniqueLock _{*this}; + pa_context_disconnect(mContext); + pa_context_unref(mContext); + } + if(mLoop) + pa_threaded_mainloop_free(mLoop); +} + -pa_context *MainloopUniqueLock::connectContext() +void MainloopUniqueLock::connectContext() { - pa_context *context{pa_context_new(mutex()->getApi(), nullptr)}; - if(!context) throw al::backend_exception{al::backend_error::OutOfMemory, + if(mutex()->mContext) + return; + + mutex()->mContext = pa_context_new(mutex()->getApi(), nullptr); + if(!mutex()->mContext) throw al::backend_exception{al::backend_error::OutOfMemory, "pa_context_new() failed"}; - pa_context_set_state_callback(context, [](pa_context *ctx, void *pdata) noexcept + pa_context_set_state_callback(mutex()->mContext, [](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) + if((err=pa_context_connect(mutex()->mContext, nullptr, pulse_ctx_flags, nullptr)) >= 0) { pa_context_state_t state; - while((state=pa_context_get_state(context)) != PA_CONTEXT_READY) + while((state=pa_context_get_state(mutex()->mContext)) != PA_CONTEXT_READY) { if(!PA_CONTEXT_IS_GOOD(state)) { - err = pa_context_errno(context); + err = pa_context_errno(mutex()->mContext); if(err > 0) err = -err; break; } @@ -466,27 +525,25 @@ pa_context *MainloopUniqueLock::connectContext() wait(); } } - pa_context_set_state_callback(context, nullptr, nullptr); + pa_context_set_state_callback(mutex()->mContext, nullptr, nullptr); if(err < 0) { - pa_context_unref(context); + pa_context_unref(mutex()->mContext); + mutex()->mContext = nullptr; throw al::backend_exception{al::backend_error::DeviceError, "Context did not connect (%s)", pa_strerror(err)}; } - - return context; } -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) +pa_stream *MainloopUniqueLock::connectStream(const char *device_name, 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)}; + pa_stream *stream{pa_stream_new(mutex()->mContext, stream_id, spec, chanmap)}; if(!stream) throw al::backend_exception{al::backend_error::OutOfMemory, "pa_stream_new() failed (%s)", - pa_strerror(pa_context_errno(context))}; + pa_strerror(pa_context_errno(mutex()->mContext))}; pa_stream_set_state_callback(stream, [](pa_stream *strm, void *pdata) noexcept { return static_cast<MainloopUniqueLock*>(pdata)->streamStateCallback(strm); }, this); @@ -506,7 +563,7 @@ pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_context { if(!PA_STREAM_IS_GOOD(state)) { - err = pa_context_errno(context); + err = pa_context_errno(mutex()->mContext); pa_stream_unref(stream); throw al::backend_exception{al::backend_error::DeviceError, "%s did not get ready (%s)", stream_id, pa_strerror(err)}; @@ -519,75 +576,57 @@ pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_context return stream; } -void PulseMainloop::close(pa_context *context, pa_stream *stream) +void PulseMainloop::close(pa_stream *stream) { - MainloopUniqueLock _{*this}; - if(stream) - { - pa_stream_set_state_callback(stream, nullptr, nullptr); - pa_stream_set_moved_callback(stream, nullptr, nullptr); - pa_stream_set_write_callback(stream, nullptr, nullptr); - pa_stream_set_buffer_attr_callback(stream, nullptr, nullptr); - pa_stream_disconnect(stream); - pa_stream_unref(stream); - } + if(!stream) + return; - pa_context_disconnect(context); - pa_context_unref(context); + MainloopUniqueLock _{*this}; + pa_stream_set_state_callback(stream, nullptr, nullptr); + pa_stream_set_moved_callback(stream, nullptr, nullptr); + pa_stream_set_write_callback(stream, nullptr, nullptr); + pa_stream_set_buffer_attr_callback(stream, nullptr, nullptr); + pa_stream_disconnect(stream); + pa_stream_unref(stream); } void PulseMainloop::probePlaybackDevices() { - pa_context *context{}; - PlaybackDevices.clear(); try { MainloopUniqueLock plock{*this}; auto sink_callback = [](pa_context *ctx, const pa_sink_info *info, int eol, void *pdata) noexcept { return static_cast<PulseMainloop*>(pdata)->deviceSinkCallback(ctx, info, eol); }; - context = plock.connectContext(); - pa_operation *op{pa_context_get_sink_info_by_name(context, nullptr, sink_callback, this)}; + pa_operation *op{pa_context_get_sink_info_by_name(mContext, nullptr, sink_callback, this)}; plock.waitForOperation(op); - op = pa_context_get_sink_info_list(context, sink_callback, this); + op = pa_context_get_sink_info_list(mContext, sink_callback, this); plock.waitForOperation(op); - - pa_context_disconnect(context); - pa_context_unref(context); - context = nullptr; } catch(std::exception &e) { ERR("Error enumerating devices: %s\n", e.what()); - if(context) close(context); } } void PulseMainloop::probeCaptureDevices() { - pa_context *context{}; - CaptureDevices.clear(); try { MainloopUniqueLock plock{*this}; auto src_callback = [](pa_context *ctx, const pa_source_info *info, int eol, void *pdata) noexcept { return static_cast<PulseMainloop*>(pdata)->deviceSourceCallback(ctx, info, eol); }; - context = plock.connectContext(); - pa_operation *op{pa_context_get_source_info_by_name(context, nullptr, src_callback, this)}; + pa_operation *op{pa_context_get_source_info_by_name(mContext, nullptr, src_callback, + this)}; plock.waitForOperation(op); - op = pa_context_get_source_info_list(context, src_callback, this); + op = pa_context_get_source_info_list(mContext, src_callback, this); plock.waitForOperation(op); - - pa_context_disconnect(context); - pa_context_unref(context); - context = nullptr; } catch(std::exception &e) { ERR("Error enumerating devices: %s\n", e.what()); - if(context) close(context); } } @@ -607,7 +646,7 @@ struct PulsePlayback final : public BackendBase { void sinkNameCallback(pa_context *context, const pa_sink_info *info, int eol) noexcept; void streamMovedCallback(pa_stream *stream) noexcept; - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -615,14 +654,13 @@ struct PulsePlayback final : public BackendBase { PulseMainloop mMainloop; - al::optional<std::string> mDeviceName{al::nullopt}; + std::optional<std::string> mDeviceName{std::nullopt}; bool mIs51Rear{false}; pa_buffer_attr mAttr; pa_sample_spec mSpec; pa_stream *mStream{nullptr}; - pa_context *mContext{nullptr}; uint mFrameSize{0u}; @@ -630,14 +668,7 @@ struct PulsePlayback final : public BackendBase { }; PulsePlayback::~PulsePlayback() -{ - if(!mContext) - return; - - mMainloop.close(mContext, mStream); - mContext = nullptr; - mStream = nullptr; -} +{ if(mStream) mMainloop.close(mStream); } void PulsePlayback::bufferAttrCallback(pa_stream *stream) noexcept @@ -750,14 +781,14 @@ void PulsePlayback::streamMovedCallback(pa_stream *stream) noexcept } -void PulsePlayback::open(const char *name) +void PulsePlayback::open(std::string_view name) { mMainloop = PulseMainloop::Create(); mMainloop.start(); const char *pulse_name{nullptr}; const char *dev_name{nullptr}; - if(name) + if(!name.empty()) { if(PlaybackDevices.empty()) mMainloop.probePlaybackDevices(); @@ -766,13 +797,13 @@ void PulsePlayback::open(const char *name) [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", static_cast<int>(name.length()), name.data()}; pulse_name = iter->device_name.c_str(); dev_name = iter->name.c_str(); } MainloopUniqueLock plock{mMainloop}; - mContext = plock.connectContext(); + plock.connectContext(); pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE | PA_STREAM_FIX_CHANNELS}; @@ -790,7 +821,7 @@ void PulsePlayback::open(const char *name) if(defname) pulse_name = defname->c_str(); } TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); - mStream = plock.connectStream(pulse_name, mContext, flags, nullptr, &spec, nullptr, + mStream = plock.connectStream(pulse_name, flags, nullptr, &spec, nullptr, BackendType::Playback); pa_stream_set_moved_callback(mStream, [](pa_stream *stream, void *pdata) noexcept @@ -803,7 +834,7 @@ void PulsePlayback::open(const char *name) { 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_operation *op{pa_context_get_sink_info_by_name(mMainloop.getContext(), pa_stream_get_device_name(mStream), name_callback, this)}; plock.waitForOperation(op); } @@ -829,7 +860,8 @@ bool PulsePlayback::reset() 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)}; + pa_operation *op{pa_context_get_sink_info_by_name(mMainloop.getContext(), deviceName, + info_callback, this)}; plock.waitForOperation(op); pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | @@ -916,7 +948,7 @@ bool PulsePlayback::reset() mAttr.minreq = mDevice->UpdateSize * frame_size; mAttr.fragsize = ~0u; - mStream = plock.connectStream(deviceName, mContext, flags, &mAttr, &mSpec, &chanmap, + mStream = plock.connectStream(deviceName, flags, &mAttr, &mSpec, &chanmap, BackendType::Playback); pa_stream_set_state_callback(mStream, [](pa_stream *stream, void *pdata) noexcept @@ -1033,42 +1065,34 @@ struct PulseCapture final : public BackendBase { void sourceNameCallback(pa_context *context, const pa_source_info *info, int eol) noexcept; void streamMovedCallback(pa_stream *stream) noexcept; - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; ClockLatency getClockLatency() override; PulseMainloop mMainloop; - al::optional<std::string> mDeviceName{al::nullopt}; + std::optional<std::string> mDeviceName{std::nullopt}; - al::span<const al::byte> mCapBuffer; + al::span<const std::byte> mCapBuffer; size_t mHoleLength{0}; size_t mPacketLength{0}; uint mLastReadable{0u}; - al::byte mSilentVal{}; + std::byte mSilentVal{}; pa_buffer_attr mAttr{}; pa_sample_spec mSpec{}; pa_stream *mStream{nullptr}; - pa_context *mContext{nullptr}; DEF_NEWDEL(PulseCapture) }; PulseCapture::~PulseCapture() -{ - if(!mContext) - return; - - mMainloop.close(mContext, mStream); - mContext = nullptr; - mStream = nullptr; -} +{ if(mStream) mMainloop.close(mStream); } void PulseCapture::streamStateCallback(pa_stream *stream) noexcept @@ -1098,7 +1122,7 @@ void PulseCapture::streamMovedCallback(pa_stream *stream) noexcept } -void PulseCapture::open(const char *name) +void PulseCapture::open(std::string_view name) { if(!mMainloop) { @@ -1107,7 +1131,7 @@ void PulseCapture::open(const char *name) } const char *pulse_name{nullptr}; - if(name) + if(!name.empty()) { if(CaptureDevices.empty()) mMainloop.probeCaptureDevices(); @@ -1116,13 +1140,13 @@ void PulseCapture::open(const char *name) [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", static_cast<int>(name.length()), name.data()}; pulse_name = iter->device_name.c_str(); mDevice->DeviceName = iter->name; } MainloopUniqueLock plock{mMainloop}; - mContext = plock.connectContext(); + plock.connectContext(); pa_channel_map chanmap{}; switch(mDevice->FmtChans) @@ -1158,7 +1182,7 @@ void PulseCapture::open(const char *name) switch(mDevice->FmtType) { case DevFmtUByte: - mSilentVal = al::byte(0x80); + mSilentVal = std::byte(0x80); mSpec.format = PA_SAMPLE_U8; break; case DevFmtShort: @@ -1194,7 +1218,7 @@ void PulseCapture::open(const char *name) flags |= PA_STREAM_DONT_MOVE; TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); - mStream = plock.connectStream(pulse_name, mContext, flags, &mAttr, &mSpec, &chanmap, + mStream = plock.connectStream(pulse_name, flags, &mAttr, &mSpec, &chanmap, BackendType::Capture); pa_stream_set_moved_callback(mStream, [](pa_stream *stream, void *pdata) noexcept @@ -1208,7 +1232,7 @@ void PulseCapture::open(const char *name) { 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_operation *op{pa_context_get_source_info_by_name(mMainloop.getContext(), pa_stream_get_device_name(mStream), name_callback, this)}; plock.waitForOperation(op); } @@ -1230,9 +1254,9 @@ void PulseCapture::stop() plock.waitForOperation(op); } -void PulseCapture::captureSamples(al::byte *buffer, uint samples) +void PulseCapture::captureSamples(std::byte *buffer, uint samples) { - al::span<al::byte> dstbuf{buffer, samples * pa_frame_size(&mSpec)}; + al::span<std::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. @@ -1281,7 +1305,7 @@ void PulseCapture::captureSamples(al::byte *buffer, uint samples) if(pa_stream_peek(mStream, &capbuf, &caplen) < 0) UNLIKELY { mDevice->handleDisconnect("Failed retrieving capture samples: %s", - pa_strerror(pa_context_errno(mContext))); + pa_strerror(pa_context_errno(mMainloop.getContext()))); break; } plock.unlock(); @@ -1290,7 +1314,7 @@ void PulseCapture::captureSamples(al::byte *buffer, uint samples) if(!capbuf) UNLIKELY mHoleLength = caplen; else - mCapBuffer = {static_cast<const al::byte*>(capbuf), caplen}; + mCapBuffer = {static_cast<const std::byte*>(capbuf), caplen}; mPacketLength = caplen; } if(!dstbuf.empty()) @@ -1381,7 +1405,7 @@ bool PulseBackendFactory::init() } #define LOAD_FUNC(x) do { \ - p##x = reinterpret_cast<decltype(p##x)>(GetSymbol(pulse_handle, #x)); \ + p##x = al::bit_cast<decltype(p##x)>(GetSymbol(pulse_handle, #x)); \ if(!(p##x)) { \ ret = false; \ missing_funcs += "\n" #x; \ @@ -1412,9 +1436,8 @@ bool PulseBackendFactory::init() } MainloopUniqueLock plock{gGlobalMainloop}; - pa_context *context{plock.connectContext()}; - pa_context_disconnect(context); - pa_context_unref(context); + plock.connectContext(); + plock.setEventHandler(); return true; } catch(...) { @@ -1467,3 +1490,18 @@ BackendFactory &PulseBackendFactory::getFactory() static PulseBackendFactory factory{}; return factory; } + +alc::EventSupport PulseBackendFactory::queryEventSupport(alc::EventType eventType, BackendType) +{ + switch(eventType) + { + case alc::EventType::DeviceAdded: + case alc::EventType::DeviceRemoved: + return alc::EventSupport::FullSupport; + + case alc::EventType::DefaultDeviceChanged: + case alc::EventType::Count: + break; + } + return alc::EventSupport::NoSupport; +} diff --git a/alc/backends/pulseaudio.h b/alc/backends/pulseaudio.h index 6690fe8a..4752a891 100644 --- a/alc/backends/pulseaudio.h +++ b/alc/backends/pulseaudio.h @@ -9,6 +9,8 @@ public: bool querySupport(BackendType type) override; + alc::EventSupport queryEventSupport(alc::EventType eventType, BackendType type) override; + std::string probe(BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override; diff --git a/alc/backends/sdl2.cpp b/alc/backends/sdl2.cpp index a4a5a9ac..f5ed4316 100644 --- a/alc/backends/sdl2.cpp +++ b/alc/backends/sdl2.cpp @@ -53,10 +53,8 @@ struct Sdl2Backend final : public BackendBase { ~Sdl2Backend() override; void audioCallback(Uint8 *stream, int len) noexcept; - static void audioCallbackC(void *ptr, Uint8 *stream, int len) noexcept - { static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); } - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -86,7 +84,7 @@ void Sdl2Backend::audioCallback(Uint8 *stream, int len) noexcept mDevice->renderSamples(stream, ulen / mFrameSize, mDevice->channelsFromFmt()); } -void Sdl2Backend::open(const char *name) +void Sdl2Backend::open(std::string_view name) { SDL_AudioSpec want{}, have{}; @@ -103,23 +101,37 @@ void Sdl2Backend::open(const char *name) } want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2; want.samples = static_cast<Uint16>(minu(mDevice->UpdateSize, 8192)); - want.callback = &Sdl2Backend::audioCallbackC; + want.callback = [](void *ptr, Uint8 *stream, int len) noexcept + { return static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); }; want.userdata = this; /* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't * necessarily the first in the list. */ SDL_AudioDeviceID devid; - if(!name || strcmp(name, defaultDeviceName) == 0) + if(name.empty() || name == defaultDeviceName) + { + name = defaultDeviceName; devid = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); + } else { const size_t prefix_len = strlen(DEVNAME_PREFIX); - if(strncmp(name, DEVNAME_PREFIX, prefix_len) == 0) - devid = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have, + if(name.length() >= prefix_len && strncmp(name.data(), DEVNAME_PREFIX, prefix_len) == 0) + { + /* Copy the string_view to a string to ensure it's null terminated + * for this call. + */ + const std::string devname{name.substr(prefix_len)}; + devid = SDL_OpenAudioDevice(devname.c_str(), SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); + } else - devid = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); + { + const std::string devname{name}; + devid = SDL_OpenAudioDevice(devname.c_str(), SDL_FALSE, &want, &have, + SDL_AUDIO_ALLOW_ANY_CHANGE); + } } if(!devid) throw al::backend_exception{al::backend_error::NoDevice, "%s", SDL_GetError()}; @@ -161,7 +173,7 @@ void Sdl2Backend::open(const char *name) mFmtType = devtype; mUpdateSize = have.samples; - mDevice->DeviceName = name ? name : defaultDeviceName; + mDevice->DeviceName = name; } bool Sdl2Backend::reset() diff --git a/alc/backends/sndio.cpp b/alc/backends/sndio.cpp index 077e77f2..d54c337b 100644 --- a/alc/backends/sndio.cpp +++ b/alc/backends/sndio.cpp @@ -22,21 +22,21 @@ #include "sndio.h" +#include <cinttypes> #include <functional> -#include <inttypes.h> #include <poll.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <thread> +#include <vector> #include "alnumeric.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "ringbuffer.h" -#include "threads.h" -#include "vector.h" #include <sndio.h> @@ -57,7 +57,7 @@ struct SndioPlayback final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -65,7 +65,7 @@ struct SndioPlayback final : public BackendBase { sio_hdl *mSndHandle{nullptr}; uint mFrameStep{}; - al::vector<al::byte> mBuffer; + std::vector<std::byte> mBuffer; std::atomic<bool> mKillNow{true}; std::thread mThread; @@ -91,7 +91,7 @@ int SndioPlayback::mixerProc() while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { - al::span<al::byte> buffer{mBuffer}; + al::span<std::byte> buffer{mBuffer}; mDevice->renderSamples(buffer.data(), static_cast<uint>(buffer.size() / frameSize), frameStep); @@ -112,13 +112,13 @@ int SndioPlayback::mixerProc() } -void SndioPlayback::open(const char *name) +void SndioPlayback::open(std::string_view name) { - if(!name) + if(name.empty()) name = sndio_device; - else if(strcmp(name, sndio_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + else if(name != sndio_device) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + static_cast<int>(name.length()), name.data()}; sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)}; if(!sndHandle) @@ -231,9 +231,9 @@ retry_params: mBuffer.resize(mDevice->UpdateSize * par.pchan*par.bps); if(par.sig == 1) - std::fill(mBuffer.begin(), mBuffer.end(), al::byte{}); + std::fill(mBuffer.begin(), mBuffer.end(), std::byte{}); else if(par.bits == 8) - std::fill_n(mBuffer.data(), mBuffer.size(), al::byte(0x80)); + std::fill_n(mBuffer.data(), mBuffer.size(), std::byte(0x80)); else if(par.bits == 16) std::fill_n(reinterpret_cast<uint16_t*>(mBuffer.data()), mBuffer.size()/2, 0x8000); else if(par.bits == 32) @@ -280,10 +280,10 @@ struct SndioCapture final : public BackendBase { int recordProc(); - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; sio_hdl *mSndHandle{nullptr}; @@ -349,7 +349,7 @@ int SndioCapture::recordProc() continue; auto data = mRing->getWriteVector(); - al::span<al::byte> buffer{data.first.buf, data.first.len*frameSize}; + al::span<std::byte> buffer{data.first.buf, data.first.len*frameSize}; while(!buffer.empty()) { size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())}; @@ -382,13 +382,13 @@ int SndioCapture::recordProc() } -void SndioCapture::open(const char *name) +void SndioCapture::open(std::string_view name) { - if(!name) + if(name.empty()) name = sndio_device; - else if(strcmp(name, sndio_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + else if(name != sndio_device) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + static_cast<int>(name.length()), name.data()}; mSndHandle = sio_open(nullptr, SIO_REC, true); if(mSndHandle == nullptr) @@ -436,7 +436,7 @@ void SndioCapture::open(const char *name) if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par)) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to set device praameters"}; + "Failed to set device parameters"}; if(par.bps > 1 && par.le != SIO_LE_NATIVE) throw al::backend_exception{al::backend_error::DeviceError, @@ -496,7 +496,7 @@ void SndioCapture::stop() ERR("Error stopping device\n"); } -void SndioCapture::captureSamples(al::byte *buffer, uint samples) +void SndioCapture::captureSamples(std::byte *buffer, uint samples) { mRing->read(buffer, samples); } uint SndioCapture::availableSamples() diff --git a/alc/backends/solaris.cpp b/alc/backends/solaris.cpp index 791609ce..38f9db19 100644 --- a/alc/backends/solaris.cpp +++ b/alc/backends/solaris.cpp @@ -35,17 +35,16 @@ #include <poll.h> #include <math.h> #include <string.h> +#include <vector> #include <thread> #include <functional> -#include "albyte.h" #include "alc/alconfig.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" -#include "threads.h" -#include "vector.h" #include <sys/audioio.h> @@ -63,7 +62,7 @@ struct SolarisBackend final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -71,7 +70,7 @@ struct SolarisBackend final : public BackendBase { int mFd{-1}; uint mFrameStep{}; - al::vector<al::byte> mBuffer; + std::vector<std::byte> mBuffer; std::atomic<bool> mKillNow{true}; std::thread mThread; @@ -116,7 +115,7 @@ int SolarisBackend::mixerProc() continue; } - al::byte *write_ptr{mBuffer.data()}; + std::byte *write_ptr{mBuffer.data()}; size_t to_write{mBuffer.size()}; mDevice->renderSamples(write_ptr, static_cast<uint>(to_write/frame_size), frame_step); while(to_write > 0 && !mKillNow.load(std::memory_order_acquire)) @@ -140,13 +139,13 @@ int SolarisBackend::mixerProc() } -void SolarisBackend::open(const char *name) +void SolarisBackend::open(std::string_view name) { - if(!name) + if(name.empty()) name = solaris_device; - else if(strcmp(name, solaris_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + else if(name != solaris_device) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + static_cast<int>(name.length()), name.data()}; int fd{::open(solaris_driver.c_str(), O_WRONLY)}; if(fd == -1) @@ -231,7 +230,7 @@ bool SolarisBackend::reset() setDefaultChannelOrder(); mBuffer.resize(mDevice->UpdateSize * size_t{frame_size}); - std::fill(mBuffer.begin(), mBuffer.end(), al::byte{}); + std::fill(mBuffer.begin(), mBuffer.end(), std::byte{}); return true; } diff --git a/alc/backends/wasapi.cpp b/alc/backends/wasapi.cpp index e834eef4..3ee98457 100644 --- a/alc/backends/wasapi.cpp +++ b/alc/backends/wasapi.cpp @@ -31,7 +31,9 @@ #include <wtypes.h> #include <mmdeviceapi.h> +#include <audiosessiontypes.h> #include <audioclient.h> +#include <spatialaudioclient.h> #include <cguid.h> #include <devpropdef.h> #include <mmreg.h> @@ -59,6 +61,8 @@ #include "albit.h" #include "alc/alconfig.h" #include "alnumeric.h" +#include "alspan.h" +#include "althrd_setname.h" #include "comptr.h" #include "core/converter.h" #include "core/device.h" @@ -66,8 +70,22 @@ #include "core/logging.h" #include "ringbuffer.h" #include "strutils.h" -#include "threads.h" +#if defined(ALSOFT_UWP) + +#include <winrt/Windows.Media.Core.h> // !!This is important!! +#include <winrt/Windows.Foundation.Collections.h> +#include <winrt/Windows.Devices.h> +#include <winrt/Windows.Foundation.h> +#include <winrt/Windows.Devices.Enumeration.h> +#include <winrt/Windows.Media.Devices.h> + +using namespace winrt; +using namespace Windows::Foundation; +using namespace Windows::Media::Devices; +using namespace Windows::Devices::Enumeration; +using namespace Windows::Media::Devices; +#endif /* Some headers seem to define these as macros for __uuidof, which is annoying * since some headers don't declare them at all. Hopefully the ifdef is enough @@ -79,11 +97,11 @@ DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); #endif - +#if !defined(ALSOFT_UWP) DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14); DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0); DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23,0xe0, 0xc0,0xff,0xee,0x7f,0x0e, 4 ); - +#endif namespace { @@ -91,9 +109,9 @@ using std::chrono::nanoseconds; using std::chrono::milliseconds; using std::chrono::seconds; -using ReferenceTime = std::chrono::duration<REFERENCE_TIME,std::ratio<1,10000000>>; +using ReferenceTime = std::chrono::duration<REFERENCE_TIME,std::ratio<1,10'000'000>>; -inline constexpr ReferenceTime operator "" _reftime(unsigned long long int n) noexcept +constexpr ReferenceTime operator "" _reftime(unsigned long long int n) noexcept { return ReferenceTime{static_cast<REFERENCE_TIME>(n)}; } @@ -124,15 +142,61 @@ constexpr DWORD X61Mask{MaskFromTopBits(X6DOT1)}; constexpr DWORD X71Mask{MaskFromTopBits(X7DOT1)}; constexpr DWORD X714Mask{MaskFromTopBits(X7DOT1DOT4)}; + +#ifndef _MSC_VER +constexpr AudioObjectType operator|(AudioObjectType lhs, AudioObjectType rhs) noexcept +{ return static_cast<AudioObjectType>(lhs | al::to_underlying(rhs)); } +#endif + +constexpr AudioObjectType ChannelMask_Mono{AudioObjectType_FrontCenter}; +constexpr AudioObjectType ChannelMask_Stereo{AudioObjectType_FrontLeft + | AudioObjectType_FrontRight}; +constexpr AudioObjectType ChannelMask_Quad{AudioObjectType_FrontLeft | AudioObjectType_FrontRight + | AudioObjectType_BackLeft | AudioObjectType_BackRight}; +constexpr AudioObjectType ChannelMask_X51{AudioObjectType_FrontLeft | AudioObjectType_FrontRight + | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft + | AudioObjectType_SideRight}; +constexpr AudioObjectType ChannelMask_X51Rear{AudioObjectType_FrontLeft + | AudioObjectType_FrontRight | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency + | AudioObjectType_BackLeft | AudioObjectType_BackRight}; +constexpr AudioObjectType ChannelMask_X61{AudioObjectType_FrontLeft | AudioObjectType_FrontRight + | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft + | AudioObjectType_SideRight | AudioObjectType_BackCenter}; +constexpr AudioObjectType ChannelMask_X71{AudioObjectType_FrontLeft | AudioObjectType_FrontRight + | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft + | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight}; +constexpr AudioObjectType ChannelMask_X714{AudioObjectType_FrontLeft | AudioObjectType_FrontRight + | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft + | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight + | AudioObjectType_TopFrontLeft | AudioObjectType_TopFrontRight | AudioObjectType_TopBackLeft + | AudioObjectType_TopBackRight}; + + constexpr char DevNameHead[] = "OpenAL Soft on "; -constexpr size_t DevNameHeadLen{al::size(DevNameHead) - 1}; +constexpr size_t DevNameHeadLen{std::size(DevNameHead) - 1}; + + +template<typename... Ts> +struct overloaded : Ts... { using Ts::operator()...; }; + +template<typename... Ts> +overloaded(Ts...) -> overloaded<Ts...>; + + +template<typename T> +constexpr auto as_unsigned(T value) noexcept +{ + using UT = std::make_unsigned_t<T>; + return static_cast<UT>(value); +} /* Scales the given reftime value, rounding the result. */ -inline uint RefTime2Samples(const ReferenceTime &val, uint srate) +template<typename T> +constexpr uint RefTime2Samples(const ReferenceTime &val, T srate) noexcept { const auto retval = (val*srate + ReferenceTime{seconds{1}}/2) / seconds{1}; - return static_cast<uint>(mini64(retval, std::numeric_limits<uint>::max())); + return static_cast<uint>(std::min<decltype(retval)>(retval, std::numeric_limits<uint>::max())); } @@ -142,7 +206,7 @@ class GuidPrinter { public: GuidPrinter(const GUID &guid) { - std::snprintf(mMsg, al::size(mMsg), "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", + std::snprintf(mMsg, std::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]); } @@ -178,28 +242,73 @@ struct DevMap { , endpoint_guid{std::forward<T1>(guid_)} , devid{std::forward<T2>(devid_)} { } + /* To prevent GCC from complaining it doesn't want to inline this. */ + ~DevMap(); }; +DevMap::~DevMap() = default; -bool checkName(const al::vector<DevMap> &list, const std::string &name) +bool checkName(const al::span<DevMap> list, const std::string_view name) { - auto match_name = [&name](const DevMap &entry) -> bool - { return entry.name == name; }; + auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name; }; return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); } -al::vector<DevMap> PlaybackDevices; -al::vector<DevMap> CaptureDevices; + +struct DeviceList { + auto lock() noexcept(noexcept(mMutex.lock())) { return mMutex.lock(); } + auto unlock() noexcept(noexcept(mMutex.unlock())) { return mMutex.unlock(); } + +private: + std::mutex mMutex; + std::vector<DevMap> mPlayback; + std::vector<DevMap> mCapture; + std::wstring mPlaybackDefaultId; + std::wstring mCaptureDefaultId; + + friend struct DeviceListLock; +}; +struct DeviceListLock : public std::unique_lock<DeviceList> { + using std::unique_lock<DeviceList>::unique_lock; + + auto& getPlaybackList() const noexcept { return mutex()->mPlayback; } + auto& getCaptureList() const noexcept { return mutex()->mCapture; } + + void setPlaybackDefaultId(std::wstring_view devid) const { mutex()->mPlaybackDefaultId = devid; } + std::wstring_view getPlaybackDefaultId() const noexcept { return mutex()->mPlaybackDefaultId; } + void setCaptureDefaultId(std::wstring_view devid) const { mutex()->mCaptureDefaultId = devid; } + std::wstring_view getCaptureDefaultId() const noexcept { return mutex()->mCaptureDefaultId; } +}; + +DeviceList gDeviceList; + + +#if defined(ALSOFT_UWP) +enum EDataFlow { + eRender = 0, + eCapture = (eRender + 1), + eAll = (eCapture + 1), + EDataFlow_enum_count = (eAll + 1) +}; +#endif + +#if defined(ALSOFT_UWP) +using DeviceHandle = Windows::Devices::Enumeration::DeviceInformation; +using EventRegistrationToken = winrt::event_token; +#else +using DeviceHandle = ComPtr<IMMDevice>; +#endif using NameGUIDPair = std::pair<std::string,std::string>; -NameGUIDPair get_device_name_and_guid(IMMDevice *device) +static NameGUIDPair GetDeviceNameAndGuid(const DeviceHandle &device) { static constexpr char UnknownName[]{"Unknown Device Name"}; static constexpr char UnknownGuid[]{"Unknown Device GUID"}; +#if !defined(ALSOFT_UWP) std::string name, guid; ComPtr<IPropertyStore> ps; - HRESULT hr = device->OpenPropertyStore(STGM_READ, ps.getPtr()); + HRESULT hr{device->OpenPropertyStore(STGM_READ, al::out_ptr(ps))}; if(FAILED(hr)) { WARN("OpenPropertyStore failed: 0x%08lx\n", hr); @@ -207,42 +316,48 @@ NameGUIDPair get_device_name_and_guid(IMMDevice *device) } PropVariant pvprop; - hr = ps->GetValue(reinterpret_cast<const PROPERTYKEY&>(DEVPKEY_Device_FriendlyName), pvprop.get()); + hr = ps->GetValue(al::bit_cast<PROPERTYKEY>(DEVPKEY_Device_FriendlyName), pvprop.get()); if(FAILED(hr)) - { WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr); - name += UnknownName; - } else if(pvprop->vt == VT_LPWSTR) - name += wstr_to_utf8(pvprop->pwszVal); + name = wstr_to_utf8(pvprop->pwszVal); else - { - WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt); - name += UnknownName; - } + WARN("Unexpected Device_FriendlyName PROPVARIANT type: 0x%04x\n", pvprop->vt); pvprop.clear(); - hr = ps->GetValue(reinterpret_cast<const PROPERTYKEY&>(PKEY_AudioEndpoint_GUID), pvprop.get()); + hr = ps->GetValue(al::bit_cast<PROPERTYKEY>(PKEY_AudioEndpoint_GUID), pvprop.get()); if(FAILED(hr)) - { WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr); - 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 = UnknownGuid; + WARN("Unexpected AudioEndpoint_GUID PROPVARIANT type: 0x%04x\n", pvprop->vt); +#else + std::string name{wstr_to_utf8(device.Name())}; + std::string guid; + // device->Id is DeviceInterfacePath: \\?\SWD#MMDEVAPI#{0.0.0.00000000}.{a21c17a0-fc1d-405e-ab5a-b513422b57d1}#{e6327cad-dcec-4949-ae8a-991e976a79d2} + auto devIfPath = device.Id(); + if(auto devIdStart = wcsstr(devIfPath.data(), L"}.")) + { + devIdStart += 2; // L"}." + if(auto devIdStartEnd = wcschr(devIdStart, L'#')) + { + std::wstring wDevId{devIdStart, static_cast<size_t>(devIdStartEnd - devIdStart)}; + guid = wstr_to_utf8(wDevId.c_str()); + std::transform(guid.begin(), guid.end(), guid.begin(), + [](char ch) { return static_cast<char>(std::toupper(ch)); }); + } } - +#endif + if(name.empty()) name = UnknownName; + if(guid.empty()) guid = UnknownGuid; return std::make_pair(std::move(name), std::move(guid)); } - -EndpointFormFactor get_device_formfactor(IMMDevice *device) +#if !defined(ALSOFT_UWP) +EndpointFormFactor GetDeviceFormfactor(IMMDevice *device) { ComPtr<IPropertyStore> ps; - HRESULT hr{device->OpenPropertyStore(STGM_READ, ps.getPtr())}; + HRESULT hr{device->OpenPropertyStore(STGM_READ, al::out_ptr(ps))}; if(FAILED(hr)) { WARN("OpenPropertyStore failed: 0x%08lx\n", hr); @@ -260,90 +375,422 @@ EndpointFormFactor get_device_formfactor(IMMDevice *device) WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform->vt); return formfactor; } +#endif -void add_device(IMMDevice *device, const WCHAR *devid, al::vector<DevMap> &list) +#if defined(ALSOFT_UWP) +struct DeviceHelper final : public IActivateAudioInterfaceCompletionHandler +#else +struct DeviceHelper final : private IMMNotificationClient +#endif { - for(auto &entry : list) + DeviceHelper() { - if(entry.devid == devid) - return; +#if defined(ALSOFT_UWP) + /* TODO: UWP also needs to watch for device added/removed events and + * dynamically add/remove devices from the lists. + */ + mActiveClientEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + + mRenderDeviceChangedToken = MediaDevice::DefaultAudioRenderDeviceChanged([this](const IInspectable& /*sender*/, const DefaultAudioRenderDeviceChangedEventArgs& args) { + if (args.Role() == AudioDeviceRole::Default) + { + const std::string msg{ "Default playback device changed: " + + wstr_to_utf8(args.Id())}; + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, + msg); + } + }); + + mCaptureDeviceChangedToken = MediaDevice::DefaultAudioCaptureDeviceChanged([this](const IInspectable& /*sender*/, const DefaultAudioCaptureDeviceChangedEventArgs& args) { + if (args.Role() == AudioDeviceRole::Default) + { + const std::string msg{ "Default capture device changed: " + + wstr_to_utf8(args.Id()) }; + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, + msg); + } + }); +#endif + } + ~DeviceHelper() + { +#if defined(ALSOFT_UWP) + MediaDevice::DefaultAudioRenderDeviceChanged(mRenderDeviceChangedToken); + MediaDevice::DefaultAudioCaptureDeviceChanged(mCaptureDeviceChangedToken); + + if(mActiveClientEvent != nullptr) + CloseHandle(mActiveClientEvent); + mActiveClientEvent = nullptr; +#else + if(mEnumerator) + mEnumerator->UnregisterEndpointNotificationCallback(this); + mEnumerator = nullptr; +#endif } - auto name_guid = get_device_name_and_guid(device); + /** -------------------------- IUnknown ----------------------------- */ + std::atomic<ULONG> mRefCount{1}; + STDMETHODIMP_(ULONG) AddRef() noexcept override { return mRefCount.fetch_add(1u) + 1u; } + STDMETHODIMP_(ULONG) Release() noexcept override { return mRefCount.fetch_sub(1u) - 1u; } + + STDMETHODIMP QueryInterface(const IID& IId, void **UnknownPtrPtr) noexcept override + { + // Three rules of QueryInterface: + // https://docs.microsoft.com/en-us/windows/win32/com/rules-for-implementing-queryinterface + // 1. Objects must have identity. + // 2. The set of interfaces on an object instance must be static. + // 3. It must be possible to query successfully for any interface on an object from any other interface. + + // If ppvObject(the address) is nullptr, then this method returns E_POINTER. + if(!UnknownPtrPtr) + return E_POINTER; + + // https://docs.microsoft.com/en-us/windows/win32/com/implementing-reference-counting + // Whenever a client calls a method(or API function), such as QueryInterface, that returns a new interface + // pointer, the method being called is responsible for incrementing the reference count through the returned + // pointer. For example, when a client first creates an object, it receives an interface pointer to an object + // that, from the client's point of view, has a reference count of one. If the client then calls AddRef on the + // interface pointer, the reference count becomes two. The client must call Release twice on the interface + // pointer to drop all of its references to the object. +#if defined(ALSOFT_UWP) + if(IId == __uuidof(IActivateAudioInterfaceCompletionHandler)) + { + *UnknownPtrPtr = static_cast<IActivateAudioInterfaceCompletionHandler*>(this); + AddRef(); + return S_OK; + } +#else + if(IId == __uuidof(IMMNotificationClient)) + { + *UnknownPtrPtr = static_cast<IMMNotificationClient*>(this); + AddRef(); + return S_OK; + } +#endif + else if(IId == __uuidof(IAgileObject) || IId == __uuidof(IUnknown)) + { + *UnknownPtrPtr = static_cast<IUnknown*>(this); + AddRef(); + return S_OK; + } + + // This method returns S_OK if the interface is supported, and E_NOINTERFACE otherwise. + *UnknownPtrPtr = nullptr; + return E_NOINTERFACE; + } - int count{1}; - std::string newname{name_guid.first}; - while(checkName(list, newname)) +#if defined(ALSOFT_UWP) + /** ----------------------- IActivateAudioInterfaceCompletionHandler ------------ */ + HRESULT ActivateCompleted(IActivateAudioInterfaceAsyncOperation*) override { - newname = name_guid.first; - newname += " #"; - newname += std::to_string(++count); + SetEvent(mActiveClientEvent); + + // Need to return S_OK + return S_OK; } - list.emplace_back(std::move(newname), std::move(name_guid.second), devid); - const DevMap &newentry = list.back(); +#else + /** ----------------------- IMMNotificationClient ------------ */ + STDMETHODIMP OnDeviceStateChanged(LPCWSTR /*pwstrDeviceId*/, DWORD /*dwNewState*/) noexcept override { return S_OK; } - TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", newentry.name.c_str(), - newentry.endpoint_guid.c_str(), newentry.devid.c_str()); -} + STDMETHODIMP OnDeviceAdded(LPCWSTR pwstrDeviceId) noexcept override + { + ComPtr<IMMDevice> device; + HRESULT hr{mEnumerator->GetDevice(pwstrDeviceId, al::out_ptr(device))}; + if(FAILED(hr)) + { + ERR("Failed to get device: 0x%08lx\n", hr); + return S_OK; + } -WCHAR *get_device_id(IMMDevice *device) -{ - WCHAR *devid; + ComPtr<IMMEndpoint> endpoint; + hr = device->QueryInterface(__uuidof(IMMEndpoint), al::out_ptr(endpoint)); + if(FAILED(hr)) + { + ERR("Failed to get device endpoint: 0x%08lx\n", hr); + return S_OK; + } - const HRESULT hr{device->GetId(&devid)}; - if(FAILED(hr)) + EDataFlow flowdir{}; + hr = endpoint->GetDataFlow(&flowdir); + if(FAILED(hr)) + { + ERR("Failed to get endpoint data flow: 0x%08lx\n", hr); + return S_OK; + } + + auto devlock = DeviceListLock{gDeviceList}; + auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList(); + + if(AddDevice(device, pwstrDeviceId, list)) + { + const auto devtype = (flowdir==eRender) ? alc::DeviceType::Playback + : alc::DeviceType::Capture; + const std::string msg{"Device added: "+list.back().name}; + alc::Event(alc::EventType::DeviceAdded, devtype, msg); + } + + return S_OK; + } + + STDMETHODIMP OnDeviceRemoved(LPCWSTR pwstrDeviceId) noexcept override { - ERR("Failed to get device id: %lx\n", hr); - return nullptr; + auto devlock = DeviceListLock{gDeviceList}; + for(auto flowdir : std::array{eRender, eCapture}) + { + auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList(); + auto devtype = (flowdir==eRender)?alc::DeviceType::Playback : alc::DeviceType::Capture; + + /* Find the ID in the list to remove. */ + auto iter = std::find_if(list.begin(), list.end(), + [pwstrDeviceId](const DevMap &entry) noexcept + { return pwstrDeviceId == entry.devid; }); + if(iter == list.end()) continue; + + TRACE("Removing device \"%s\", \"%s\", \"%ls\"\n", iter->name.c_str(), + iter->endpoint_guid.c_str(), iter->devid.c_str()); + + std::string msg{"Device removed: "+std::move(iter->name)}; + list.erase(iter); + + alc::Event(alc::EventType::DeviceRemoved, devtype, msg); + } + return S_OK; } - return devid; -} + STDMETHODIMP OnPropertyValueChanged(LPCWSTR /*pwstrDeviceId*/, const PROPERTYKEY /*key*/) noexcept override { return S_OK; } -void probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, al::vector<DevMap> &list) -{ - al::vector<DevMap>{}.swap(list); + STDMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId) noexcept override + { + if(role != eMultimedia) + return S_OK; - ComPtr<IMMDeviceCollection> coll; - HRESULT hr{devenum->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE, coll.getPtr())}; - if(FAILED(hr)) + const std::wstring_view devid{pwstrDefaultDeviceId ? pwstrDefaultDeviceId + : std::wstring_view{}}; + if(flow == eRender) + { + DeviceListLock{gDeviceList}.setPlaybackDefaultId(devid); + const std::string msg{"Default playback device changed: " + wstr_to_utf8(devid)}; + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, msg); + } + else if(flow == eCapture) + { + DeviceListLock{gDeviceList}.setCaptureDefaultId(devid); + const std::string msg{"Default capture device changed: " + wstr_to_utf8(devid)}; + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, msg); + } + return S_OK; + } +#endif + + /** -------------------------- DeviceHelper ----------------------------- */ + HRESULT init() { - ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr); - return; +#if !defined(ALSOFT_UWP) + HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, + __uuidof(IMMDeviceEnumerator), al::out_ptr(mEnumerator))}; + if(SUCCEEDED(hr)) + mEnumerator->RegisterEndpointNotificationCallback(this); + else + WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr); + return hr; +#else + return S_OK; +#endif } - UINT count{0}; - hr = coll->GetCount(&count); - if(SUCCEEDED(hr) && count > 0) - list.reserve(count); + HRESULT openDevice(std::wstring_view devid, EDataFlow flow, DeviceHandle& device) + { +#if !defined(ALSOFT_UWP) + HRESULT hr{E_FAIL}; + if(mEnumerator) + { + if(devid.empty()) + hr = mEnumerator->GetDefaultAudioEndpoint(flow, eMultimedia, al::out_ptr(device)); + else + hr = mEnumerator->GetDevice(devid.data(), al::out_ptr(device)); + } + return hr; +#else + const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default; + auto devIfPath = + devid.empty() ? (flow == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole) : MediaDevice::GetDefaultAudioCaptureId(deviceRole)) + : winrt::hstring(devid.data()); + if (devIfPath.empty()) + return E_POINTER; + + auto&& deviceInfo = DeviceInformation::CreateFromIdAsync(devIfPath, nullptr, DeviceInformationKind::DeviceInterface).get(); + if (!deviceInfo) + return E_NOINTERFACE; + device = deviceInfo; + return S_OK; +#endif + } - ComPtr<IMMDevice> device; - hr = devenum->GetDefaultAudioEndpoint(flowdir, eMultimedia, device.getPtr()); - if(SUCCEEDED(hr)) +#if !defined(ALSOFT_UWP) + static HRESULT activateAudioClient(_In_ DeviceHandle &device, REFIID iid, void **ppv) + { return device->Activate(iid, CLSCTX_INPROC_SERVER, nullptr, ppv); } +#else + HRESULT activateAudioClient(_In_ DeviceHandle &device, _In_ REFIID iid, void **ppv) { - if(WCHAR *devid{get_device_id(device.get())}) + ComPtr<IActivateAudioInterfaceAsyncOperation> asyncOp; + HRESULT hr{ActivateAudioInterfaceAsync(device.Id().data(), iid, nullptr, this, + al::out_ptr(asyncOp))}; + if(FAILED(hr)) + return hr; + + /* I don't like waiting for INFINITE time, but the activate operation + * can take an indefinite amount of time since it can require user + * input. + */ + DWORD res{WaitForSingleObjectEx(mActiveClientEvent, INFINITE, FALSE)}; + if(res != WAIT_OBJECT_0) { - add_device(device.get(), devid, list); - CoTaskMemFree(devid); + ERR("WaitForSingleObjectEx error: 0x%lx\n", res); + return E_FAIL; } - device = nullptr; + + HRESULT hrActivateRes{E_FAIL}; + ComPtr<IUnknown> punkAudioIface; + hr = asyncOp->GetActivateResult(&hrActivateRes, al::out_ptr(punkAudioIface)); + if(SUCCEEDED(hr)) hr = hrActivateRes; + if(FAILED(hr)) return hr; + + return punkAudioIface->QueryInterface(iid, ppv); } +#endif - for(UINT i{0};i < count;++i) + std::wstring probeDevices(EDataFlow flowdir, std::vector<DevMap> &list) { - hr = coll->Item(i, device.getPtr()); - if(FAILED(hr)) continue; + std::wstring defaultId; + std::vector<DevMap>{}.swap(list); + +#if !defined(ALSOFT_UWP) + ComPtr<IMMDeviceCollection> coll; + HRESULT hr{mEnumerator->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE, + al::out_ptr(coll))}; + if(FAILED(hr)) + { + ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr); + return defaultId; + } + + UINT count{0}; + hr = coll->GetCount(&count); + if(SUCCEEDED(hr) && count > 0) + list.reserve(count); + + ComPtr<IMMDevice> device; + hr = mEnumerator->GetDefaultAudioEndpoint(flowdir, eMultimedia, al::out_ptr(device)); + if(SUCCEEDED(hr)) + { + if(WCHAR *devid{GetDeviceId(device.get())}) + { + defaultId = devid; + CoTaskMemFree(devid); + } + device = nullptr; + } + + for(UINT i{0};i < count;++i) + { + hr = coll->Item(i, al::out_ptr(device)); + if(FAILED(hr)) + continue; - if(WCHAR *devid{get_device_id(device.get())}) + if(WCHAR *devid{GetDeviceId(device.get())}) + { + std::ignore = AddDevice(device, devid, list); + CoTaskMemFree(devid); + } + device = nullptr; + } +#else + const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default; + auto DefaultAudioId = flowdir == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole) + : MediaDevice::GetDefaultAudioCaptureId(deviceRole); + if(!DefaultAudioId.empty()) { - add_device(device.get(), devid, list); - CoTaskMemFree(devid); + auto deviceInfo = DeviceInformation::CreateFromIdAsync(DefaultAudioId, nullptr, + DeviceInformationKind::DeviceInterface).get(); + if(deviceInfo) + defaultId = deviceInfo.Id().data(); } - device = nullptr; + + // Get the string identifier of the audio renderer + auto AudioSelector = flowdir == eRender ? MediaDevice::GetAudioRenderSelector() : MediaDevice::GetAudioCaptureSelector(); + + // Setup the asynchronous callback + auto&& DeviceInfoCollection = DeviceInformation::FindAllAsync(AudioSelector, /*PropertyList*/nullptr, DeviceInformationKind::DeviceInterface).get(); + if(DeviceInfoCollection) + { + try { + auto deviceCount = DeviceInfoCollection.Size(); + for(unsigned int i{0};i < deviceCount;++i) + { + auto deviceInfo = DeviceInfoCollection.GetAt(i); + if(deviceInfo) + std::ignore = AddDevice(deviceInfo, deviceInfo.Id().data(), list); + } + } + catch (const winrt::hresult_error& /*ex*/) { + } + } +#endif + + return defaultId; } -} +private: + static bool AddDevice(const DeviceHandle &device, const WCHAR *devid, std::vector<DevMap> &list) + { + for(auto &entry : list) + { + if(entry.devid == devid) + return false; + } + + auto name_guid = GetDeviceNameAndGuid(device); + int count{1}; + std::string newname{name_guid.first}; + while(checkName(list, newname)) + { + newname = name_guid.first; + newname += " #"; + newname += std::to_string(++count); + } + 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(), + newentry.endpoint_guid.c_str(), newentry.devid.c_str()); + return true; + } + +#if !defined(ALSOFT_UWP) + static WCHAR *GetDeviceId(IMMDevice *device) + { + WCHAR *devid; + + const HRESULT hr{device->GetId(&devid)}; + if(FAILED(hr)) + { + ERR("Failed to get device id: %lx\n", hr); + return nullptr; + } + + return devid; + } + ComPtr<IMMDeviceEnumerator> mEnumerator{nullptr}; + +#else + + HANDLE mActiveClientEvent{nullptr}; + + EventRegistrationToken mRenderDeviceChangedToken; + EventRegistrationToken mCaptureDeviceChangedToken; +#endif +}; bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in) { @@ -430,51 +877,51 @@ enum class MsgType { StartDevice, StopDevice, CloseDevice, - EnumeratePlayback, - EnumerateCapture, - Count, - QuitThread = Count + QuitThread }; -constexpr char MessageStr[static_cast<size_t>(MsgType::Count)][20]{ - "Open Device", - "Reset Device", - "Start Device", - "Stop Device", - "Close Device", - "Enumerate Playback", - "Enumerate Capture" -}; +constexpr const char *GetMessageTypeName(MsgType type) noexcept +{ + switch(type) + { + case MsgType::OpenDevice: return "Open Device"; + case MsgType::ResetDevice: return "Reset Device"; + case MsgType::StartDevice: return "Start Device"; + case MsgType::StopDevice: return "Stop Device"; + case MsgType::CloseDevice: return "Close Device"; + case MsgType::QuitThread: break; + } + return ""; +} /* Proxy interface used by the message handler. */ struct WasapiProxy { virtual ~WasapiProxy() = default; - virtual HRESULT openProxy(const char *name) = 0; + virtual HRESULT openProxy(std::string_view name) = 0; virtual void closeProxy() = 0; virtual HRESULT resetProxy() = 0; virtual HRESULT startProxy() = 0; - virtual void stopProxy() = 0; + virtual void stopProxy() = 0; struct Msg { MsgType mType; WasapiProxy *mProxy; - const char *mParam; + std::string_view 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; + static inline std::deque<Msg> mMsgQueue; + static inline std::mutex mMsgQueueLock; + static inline std::condition_variable mMsgQueueCond; + + static inline std::optional<DeviceHelper> sDeviceHelper; - std::future<HRESULT> pushMessage(MsgType type, const char *param=nullptr) + std::future<HRESULT> pushMessage(MsgType type, std::string_view param={}) { std::promise<HRESULT> promise; std::future<HRESULT> future{promise.get_future()}; @@ -492,7 +939,7 @@ struct WasapiProxy { std::future<HRESULT> future{promise.get_future()}; { std::lock_guard<std::mutex> _{mMsgQueueLock}; - mMsgQueue.emplace_back(Msg{type, nullptr, nullptr, std::move(promise)}); + mMsgQueue.emplace_back(Msg{type, nullptr, {}, std::move(promise)}); } mMsgQueueCond.notify_one(); return future; @@ -508,65 +955,41 @@ struct WasapiProxy { } 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 hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; - if(FAILED(hr)) + ComWrapper com{COINIT_MULTITHREADED}; + if(!com) { - WARN("Failed to initialize COM: 0x%08lx\n", hr); - promise->set_value(hr); + WARN("Failed to initialize COM: 0x%08lx\n", com.status()); + promise->set_value(com.status()); return 0; } - promise->set_value(S_OK); + + HRESULT hr{sDeviceHelper.emplace().init()}; + promise->set_value(hr); promise = nullptr; + if(FAILED(hr)) + goto skip_loop; + + { + auto devlock = DeviceListLock{gDeviceList}; + auto defaultId = sDeviceHelper->probeDevices(eRender, devlock.getPlaybackList()); + if(!defaultId.empty()) devlock.setPlaybackDefaultId(defaultId); + defaultId = sDeviceHelper->probeDevices(eCapture, devlock.getCaptureList()); + if(!defaultId.empty()) devlock.setCaptureDefaultId(defaultId); + } TRACE("Starting message loop\n"); while(Msg msg{popMessage()}) { - 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)); + TRACE("Got message \"%s\" (0x%04x, this=%p, param=\"%.*s\")\n", + GetMessageTypeName(msg.mType), static_cast<uint>(msg.mType), + static_cast<void*>(msg.mProxy), static_cast<int>(msg.mParam.length()), + msg.mParam.data()); switch(msg.mType) { @@ -595,27 +1018,6 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise) msg.mPromise.set_value(S_OK); continue; - case MsgType::EnumeratePlayback: - case MsgType::EnumerateCapture: - { - 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)}; - - 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; - } - case MsgType::QuitThread: break; } @@ -623,20 +1025,22 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise) msg.mPromise.set_value(E_FAIL); } TRACE("Message loop finished\n"); - CoUninitialize(); + +skip_loop: + sDeviceHelper.reset(); return 0; } - struct WasapiPlayback final : public BackendBase, WasapiProxy { WasapiPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~WasapiPlayback() override; int mixerProc(); + int mixerSpatialProc(); - void open(const char *name) override; - HRESULT openProxy(const char *name) override; + void open(std::string_view name) override; + HRESULT openProxy(std::string_view name) override; void closeProxy() override; bool reset() override; @@ -648,10 +1052,22 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { ClockLatency getClockLatency() override; + void prepareFormat(WAVEFORMATEXTENSIBLE &OutputType); + void finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType); + HRESULT mOpenStatus{E_FAIL}; - ComPtr<IMMDevice> mMMDev{nullptr}; - ComPtr<IAudioClient> mClient{nullptr}; - ComPtr<IAudioRenderClient> mRender{nullptr}; + DeviceHandle mMMDev{nullptr}; + + struct PlainDevice { + ComPtr<IAudioClient> mClient{nullptr}; + ComPtr<IAudioRenderClient> mRender{nullptr}; + }; + struct SpatialDevice { + ComPtr<ISpatialAudioClient> mClient{nullptr}; + ComPtr<ISpatialAudioObjectRenderStream> mRender{nullptr}; + AudioObjectType mStaticMask{}; + }; + std::variant<std::monostate,PlainDevice,SpatialDevice> mAudio; HANDLE mNotifyEvent{nullptr}; UINT32 mOrigBufferSize{}, mOrigUpdateSize{}; @@ -673,10 +1089,7 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { WasapiPlayback::~WasapiPlayback() { if(SUCCEEDED(mOpenStatus)) - { pushMessage(MsgType::CloseDevice).wait(); - DeinitThread(); - } mOpenStatus = E_FAIL; if(mNotifyEvent != nullptr) @@ -687,24 +1100,29 @@ WasapiPlayback::~WasapiPlayback() FORCE_ALIGN int WasapiPlayback::mixerProc() { - HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; - if(FAILED(hr)) + ComWrapper com{COINIT_MULTITHREADED}; + if(!com) { - ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr); - mDevice->handleDisconnect("COM init failed: 0x%08lx", hr); + ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status()); + mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status()); return 1; } + auto &audio = std::get<PlainDevice>(mAudio); + SetRTPriority(); althrd_setname(MIXER_THREAD_NAME); const uint frame_size{mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8u}; const uint update_size{mOrigUpdateSize}; const UINT32 buffer_len{mOrigBufferSize}; + const void *resbufferptr{}; + + mBufferFilled = 0; while(!mKillNow.load(std::memory_order_relaxed)) { UINT32 written; - hr = mClient->GetCurrentPadding(&written); + HRESULT hr{audio.mClient->GetCurrentPadding(&written)}; if(FAILED(hr)) { ERR("Failed to get padding: 0x%08lx\n", hr); @@ -723,7 +1141,7 @@ FORCE_ALIGN int WasapiPlayback::mixerProc() } BYTE *buffer; - hr = mRender->GetBuffer(len, &buffer); + hr = audio.mRender->GetBuffer(len, &buffer); if(SUCCEEDED(hr)) { if(mResampler) @@ -735,22 +1153,15 @@ FORCE_ALIGN int WasapiPlayback::mixerProc() { mDevice->renderSamples(mResampleBuffer.get(), mDevice->UpdateSize, mFormat.Format.nChannels); + resbufferptr = mResampleBuffer.get(); mBufferFilled = mDevice->UpdateSize; } - const void *src{mResampleBuffer.get()}; - uint srclen{mBufferFilled}; - uint got{mResampler->convert(&src, &srclen, buffer, len-done)}; + uint got{mResampler->convert(&resbufferptr, &mBufferFilled, 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 @@ -759,7 +1170,7 @@ FORCE_ALIGN int WasapiPlayback::mixerProc() mDevice->renderSamples(buffer, len, mFormat.Format.nChannels); mPadding.store(written + len, std::memory_order_relaxed); } - hr = mRender->ReleaseBuffer(len, 0); + hr = audio.mRender->ReleaseBuffer(len, 0); } if(FAILED(hr)) { @@ -770,12 +1181,126 @@ FORCE_ALIGN int WasapiPlayback::mixerProc() } mPadding.store(0u, std::memory_order_release); - CoUninitialize(); + return 0; +} + +FORCE_ALIGN int WasapiPlayback::mixerSpatialProc() +{ + ComWrapper com{COINIT_MULTITHREADED}; + if(!com) + { + ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status()); + mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status()); + return 1; + } + + auto &audio = std::get<SpatialDevice>(mAudio); + + SetRTPriority(); + althrd_setname(MIXER_THREAD_NAME); + + std::vector<ComPtr<ISpatialAudioObject>> channels; + std::vector<float*> buffers; + std::vector<float*> resbuffers; + std::vector<const void*> tmpbuffers; + + /* TODO: Set mPadding appropriately. There doesn't seem to be a way to + * update it dynamically based on the stream, so a fixed size may be the + * best we can do. + */ + mPadding.store(mOrigBufferSize-mOrigUpdateSize, std::memory_order_release); + + mBufferFilled = 0; + while(!mKillNow.load(std::memory_order_relaxed)) + { + if(DWORD res{WaitForSingleObjectEx(mNotifyEvent, 1000, FALSE)}; res != WAIT_OBJECT_0) + { + ERR("WaitForSingleObjectEx error: 0x%lx\n", res); + + HRESULT hr{audio.mRender->Reset()}; + if(FAILED(hr)) + { + ERR("ISpatialAudioObjectRenderStream::Reset failed: 0x%08lx\n", hr); + mDevice->handleDisconnect("Device lost: 0x%08lx", hr); + break; + } + } + + UINT32 dynamicCount{}, framesToDo{}; + HRESULT hr{audio.mRender->BeginUpdatingAudioObjects(&dynamicCount, &framesToDo)}; + if(SUCCEEDED(hr)) + { + if(channels.empty()) UNLIKELY + { + auto flags = as_unsigned(audio.mStaticMask); + channels.reserve(as_unsigned(al::popcount(flags))); + while(flags) + { + auto id = decltype(flags){1} << al::countr_zero(flags); + flags &= ~id; + + channels.emplace_back(); + audio.mRender->ActivateSpatialAudioObject(static_cast<AudioObjectType>(id), + al::out_ptr(channels.back())); + } + buffers.resize(channels.size()); + if(mResampler) + { + tmpbuffers.resize(buffers.size()); + resbuffers.resize(buffers.size()); + for(size_t i{0};i < tmpbuffers.size();++i) + resbuffers[i] = reinterpret_cast<float*>(mResampleBuffer.get()) + + mDevice->UpdateSize*i; + } + } + + /* We have to call to get each channel's buffer individually every + * update, unfortunately. + */ + std::transform(channels.cbegin(), channels.cend(), buffers.begin(), + [](const ComPtr<ISpatialAudioObject> &obj) -> float* + { + BYTE *buffer{}; + UINT32 size{}; + obj->GetBuffer(&buffer, &size); + return reinterpret_cast<float*>(buffer); + }); + + if(!mResampler) + mDevice->renderSamples(buffers, framesToDo); + else + { + std::lock_guard<std::mutex> _{mMutex}; + for(UINT32 pos{0};pos < framesToDo;) + { + if(mBufferFilled == 0) + { + mDevice->renderSamples(resbuffers, mDevice->UpdateSize); + std::copy(resbuffers.cbegin(), resbuffers.cend(), tmpbuffers.begin()); + mBufferFilled = mDevice->UpdateSize; + } + + const uint got{mResampler->convertPlanar(tmpbuffers.data(), &mBufferFilled, + reinterpret_cast<void*const*>(buffers.data()), framesToDo-pos)}; + for(auto &buf : buffers) + buf += got; + pos += got; + } + } + + hr = audio.mRender->EndUpdatingAudioObjects(); + } + + if(FAILED(hr)) + ERR("Failed to update playback objects: 0x%08lx\n", hr); + } + mPadding.store(0u, std::memory_order_release); + return 0; } -void WasapiPlayback::open(const char *name) +void WasapiPlayback::open(std::string_view name) { if(SUCCEEDED(mOpenStatus)) throw al::backend_exception{al::backend_error::DeviceError, @@ -789,132 +1314,69 @@ void WasapiPlayback::open(const char *name) "Failed to create notify events"}; } - HRESULT hr{InitThread()}; - if(FAILED(hr)) - { - throw al::backend_exception{al::backend_error::DeviceError, - "Failed to init COM thread: 0x%08lx", hr}; - } - - if(name) + if(name.length() >= DevNameHeadLen + && std::strncmp(name.data(), DevNameHead, DevNameHeadLen) == 0) { - if(PlaybackDevices.empty()) - pushMessage(MsgType::EnumeratePlayback); - if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0) - { - name += DevNameHeadLen; - if(*name == '\0') - name = nullptr; - } + name = name.substr(DevNameHeadLen); } mOpenStatus = pushMessage(MsgType::OpenDevice, name).get(); if(FAILED(mOpenStatus)) - { - DeinitThread(); throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", mOpenStatus}; - } } -HRESULT WasapiPlayback::openProxy(const char *name) +HRESULT WasapiPlayback::openProxy(std::string_view name) { - const wchar_t *devid{nullptr}; - if(name) + std::string devname; + std::wstring devid; + if(!name.empty()) { - auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), + auto devlock = DeviceListLock{gDeviceList}; + auto list = al::span{devlock.getPlaybackList()}; + auto iter = std::find_if(list.cbegin(), list.cend(), [name](const DevMap &entry) -> bool { return entry.name == name || entry.endpoint_guid == name; }); - if(iter == PlaybackDevices.cend()) + if(iter == list.cend()) { const std::wstring wname{utf8_to_wstr(name)}; - iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), + iter = std::find_if(list.cbegin(), list.cend(), [&wname](const DevMap &entry) -> bool { return entry.devid == wname; }); } - if(iter == PlaybackDevices.cend()) + if(iter == list.cend()) { - WARN("Failed to find device name matching \"%s\"\n", name); + WARN("Failed to find device name matching \"%.*s\"\n", static_cast<int>(name.length()), + name.data()); return E_FAIL; } - name = iter->name.c_str(); - devid = iter->devid.c_str(); + devname = iter->name; + devid = iter->devid; } - void *ptr; - ComPtr<IMMDevice> mmdev; - HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, - IID_IMMDeviceEnumerator, &ptr)}; - if(SUCCEEDED(hr)) - { - ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)}; - if(!devid) - hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, mmdev.getPtr()); - else - hr = enumerator->GetDevice(devid, mmdev.getPtr()); - } + HRESULT hr{sDeviceHelper->openDevice(devid, eRender, mMMDev)}; if(FAILED(hr)) { - WARN("Failed to open device \"%s\"\n", name?name:"(default)"); + WARN("Failed to open device \"%s\"\n", devname.empty() ? "(default)" : devname.c_str()); return hr; } + if(!devname.empty()) + mDevice->DeviceName = DevNameHead + std::move(devname); + else + mDevice->DeviceName = DevNameHead + GetDeviceNameAndGuid(mMMDev).first; - 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; + return S_OK; } void WasapiPlayback::closeProxy() { - mClient = nullptr; + mAudio.emplace<std::monostate>(); mMMDev = nullptr; } -bool WasapiPlayback::reset() -{ - HRESULT hr{pushMessage(MsgType::ResetDevice).get()}; - if(FAILED(hr)) - throw al::backend_exception{al::backend_error::DeviceError, "0x%08lx", hr}; - return true; -} - -HRESULT WasapiPlayback::resetProxy() +void WasapiPlayback::prepareFormat(WAVEFORMATEXTENSIBLE &OutputType) { - mClient = nullptr; - - void *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 = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)}; - - WAVEFORMATEX *wfx; - hr = mClient->GetMixFormat(&wfx); - if(FAILED(hr)) - { - ERR("Failed to get mix format: 0x%08lx\n", hr); - return hr; - } - TraceFormat("Device mix format", wfx); - - WAVEFORMATEXTENSIBLE OutputType; - if(!MakeExtensible(&OutputType, wfx)) - { - CoTaskMemFree(wfx); - return E_FAIL; - } - CoTaskMemFree(wfx); - wfx = nullptr; - - 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.test(FrequencyRequest)) @@ -928,7 +1390,7 @@ HRESULT WasapiPlayback::resetProxy() const uint32_t chancount{OutputType.Format.nChannels}; const DWORD chanmask{OutputType.dwChannelMask}; if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4) - mDevice->FmtChans = DevFmtX71; + mDevice->FmtChans = DevFmtX714; else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) mDevice->FmtChans = DevFmtX71; else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) @@ -1030,141 +1492,426 @@ HRESULT WasapiPlayback::resetProxy() OutputType.Format.wBitsPerSample / 8); OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * OutputType.Format.nBlockAlign; +} - TraceFormat("Requesting playback format", &OutputType.Format); - hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); - if(FAILED(hr)) +void WasapiPlayback::finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType) +{ + 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)) + { + /* 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) { - WARN("Failed to check format support: 0x%08lx\n", hr); - hr = mClient->GetMixFormat(&wfx); + 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(FAILED(hr)) + + if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM)) { - ERR("Failed to find a supported format: 0x%08lx\n", hr); - return hr; + if(OutputType.Format.wBitsPerSample == 8) + mDevice->FmtType = DevFmtUByte; + else if(OutputType.Format.wBitsPerSample == 16) + mDevice->FmtType = DevFmtShort; + else if(OutputType.Format.wBitsPerSample == 32) + mDevice->FmtType = DevFmtInt; + else + { + mDevice->FmtType = DevFmtShort; + OutputType.Format.wBitsPerSample = 16; + } + } + else if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) + { + mDevice->FmtType = DevFmtFloat; + OutputType.Format.wBitsPerSample = 32; + } + else + { + 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; + OutputType.Format.wBitsPerSample = 16; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; } + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; +} - if(wfx != nullptr) + +bool WasapiPlayback::reset() +{ + HRESULT hr{pushMessage(MsgType::ResetDevice).get()}; + if(FAILED(hr)) + throw al::backend_exception{al::backend_error::DeviceError, "0x%08lx", hr}; + return true; +} + +HRESULT WasapiPlayback::resetProxy() +{ + if(GetConfigValueBool(mDevice->DeviceName.c_str(), "wasapi", "spatial-api", false)) { - TraceFormat("Got playback format", wfx); - if(!MakeExtensible(&OutputType, wfx)) + auto &audio = mAudio.emplace<SpatialDevice>(); + HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(ISpatialAudioClient), + al::out_ptr(audio.mClient))}; + if(FAILED(hr)) { - CoTaskMemFree(wfx); - return E_FAIL; + ERR("Failed to activate spatial audio client: 0x%08lx\n", hr); + goto no_spatial; } - CoTaskMemFree(wfx); - wfx = nullptr; - if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "wasapi", "allow-resampler", true)) - mDevice->Frequency = OutputType.Format.nSamplesPerSec; - else - mDevice->Frequency = minu(mDevice->Frequency, OutputType.Format.nSamplesPerSec); + ComPtr<IAudioFormatEnumerator> fmtenum; + hr = audio.mClient->GetSupportedAudioObjectFormatEnumerator(al::out_ptr(fmtenum)); + if(FAILED(hr)) + { + ERR("Failed to get format enumerator: 0x%08lx\n", hr); + goto no_spatial; + } - 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)) + UINT32 fmtcount{}; + hr = fmtenum->GetCount(&fmtcount); + if(FAILED(hr) || fmtcount == 0) { - /* 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) + ERR("Failed to get format count: 0x%08lx\n", hr); + goto no_spatial; + } + + WAVEFORMATEX *preferredFormat{}; + hr = fmtenum->GetFormat(0, &preferredFormat); + if(FAILED(hr)) + { + ERR("Failed to get preferred format: 0x%08lx\n", hr); + goto no_spatial; + } + TraceFormat("Preferred mix format", preferredFormat); + + UINT32 maxFrames{}; + hr = audio.mClient->GetMaxFrameCount(preferredFormat, &maxFrames); + if(FAILED(hr)) + ERR("Failed to get max frames: 0x%08lx\n", hr); + else + TRACE("Max sample frames: %u\n", maxFrames); + for(UINT32 i{1};i < fmtcount;++i) + { + WAVEFORMATEX *otherFormat{}; + hr = fmtenum->GetFormat(i, &otherFormat); + if(FAILED(hr)) + ERR("Failed to format %u: 0x%08lx\n", i+1, hr); + else { - 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; + TraceFormat("Other mix format", otherFormat); + UINT32 otherMaxFrames{}; + hr = audio.mClient->GetMaxFrameCount(otherFormat, &otherMaxFrames); + if(FAILED(hr)) + ERR("Failed to get max frames: 0x%08lx\n", hr); + else + TRACE("Max sample frames: %u\n", otherMaxFrames); } } - if(!chansok) + + WAVEFORMATEXTENSIBLE OutputType; + if(!MakeExtensible(&OutputType, preferredFormat)) + goto no_spatial; + + /* Force 32-bit float. This is currently required for planar output. */ + if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE + && OutputType.Format.wFormatTag != WAVE_FORMAT_IEEE_FLOAT) + { + OutputType.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + OutputType.Format.cbSize = 0; + } + if(OutputType.Format.wBitsPerSample != 32) { + OutputType.Format.nAvgBytesPerSec = OutputType.Format.nAvgBytesPerSec * 32u + / OutputType.Format.wBitsPerSample; + OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nBlockAlign * 32 + / OutputType.Format.wBitsPerSample); + OutputType.Format.wBitsPerSample = 32; + } + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + + /* Match the output rate if not requesting anything specific. */ + if(!mDevice->Flags.test(FrequencyRequest)) + mDevice->Frequency = OutputType.Format.nSamplesPerSec; + + bool isRear51{false}; + if(!mDevice->Flags.test(ChannelsRequest)) + { + const uint32_t chancount{OutputType.Format.nChannels}; + const DWORD chanmask{OutputType.dwChannelMask}; 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)) + 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 + /* HACK: Don't autoselect mono. Wine returns this and makes the + * audio terrible. + */ + else if(!(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask))) + 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); + } + + auto getTypeMask = [isRear51](DevFmtChannels chans) noexcept + { + switch(chans) { - ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels, - OutputType.dwChannelMask); - mDevice->FmtChans = DevFmtStereo; - OutputType.Format.nChannels = 2; - OutputType.dwChannelMask = STEREO; + case DevFmtMono: return ChannelMask_Mono; + case DevFmtStereo: return ChannelMask_Stereo; + case DevFmtQuad: return ChannelMask_Quad; + case DevFmtX51: return isRear51 ? ChannelMask_X51Rear : ChannelMask_X51; + case DevFmtX61: return ChannelMask_X61; + case DevFmtX3D71: + case DevFmtX71: return ChannelMask_X71; + case DevFmtX714: return ChannelMask_X714; + case DevFmtAmbi3D: + break; } + return ChannelMask_Stereo; + }; + + SpatialAudioObjectRenderStreamActivationParams streamParams{}; + streamParams.ObjectFormat = &OutputType.Format; + streamParams.StaticObjectTypeMask = getTypeMask(mDevice->FmtChans); + streamParams.Category = AudioCategory_Media; + streamParams.EventHandle = mNotifyEvent; + + PropVariant paramProp{}; + paramProp->vt = VT_BLOB; + paramProp->blob.cbSize = sizeof(streamParams); + paramProp->blob.pBlobData = reinterpret_cast<BYTE*>(&streamParams); + + hr = audio.mClient->ActivateSpatialAudioStream(paramProp.get(), + __uuidof(ISpatialAudioObjectRenderStream), al::out_ptr(audio.mRender)); + if(FAILED(hr)) + { + ERR("Failed to activate spatial audio stream: 0x%08lx\n", hr); + goto no_spatial; } - if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM)) + audio.mStaticMask = streamParams.StaticObjectTypeMask; + mFormat = OutputType; + + mDevice->FmtType = DevFmtFloat; + mDevice->Flags.reset(DirectEar).set(Virtualization); + if(streamParams.StaticObjectTypeMask == ChannelMask_Stereo) + mDevice->FmtChans = DevFmtStereo; + if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "wasapi", "allow-resampler", true)) + mDevice->Frequency = OutputType.Format.nSamplesPerSec; + else + mDevice->Frequency = minu(mDevice->Frequency, OutputType.Format.nSamplesPerSec); + + setDefaultWFXChannelOrder(); + + /* FIXME: Get the real update and buffer size. Presumably the actual + * device is configured once ActivateSpatialAudioStream succeeds, and + * an IAudioClient from the same IMMDevice accesses the same device + * configuration. This isn't obviously correct, but for now assume + * IAudioClient::GetDevicePeriod returns the current device period time + * that ISpatialAudioObjectRenderStream will try to wake up at. + * + * Unfortunately this won't get the buffer size of the + * ISpatialAudioObjectRenderStream, so we only assume there's two + * periods. + */ + mOrigUpdateSize = mDevice->UpdateSize; + mOrigBufferSize = mOrigUpdateSize*2; + ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency}; + + ComPtr<IAudioClient> tmpClient; + hr = sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient), + al::out_ptr(tmpClient)); + if(FAILED(hr)) + ERR("Failed to activate audio client: 0x%08lx\n", hr); + else { - if(OutputType.Format.wBitsPerSample == 8) - mDevice->FmtType = DevFmtUByte; - else if(OutputType.Format.wBitsPerSample == 16) - mDevice->FmtType = DevFmtShort; - else if(OutputType.Format.wBitsPerSample == 32) - mDevice->FmtType = DevFmtInt; + hr = tmpClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(per_time), nullptr); + if(FAILED(hr)) + ERR("Failed to get device period: 0x%08lx\n", hr); else { - mDevice->FmtType = DevFmtShort; - OutputType.Format.wBitsPerSample = 16; + mOrigUpdateSize = RefTime2Samples(per_time, mFormat.Format.nSamplesPerSec); + mOrigBufferSize = mOrigUpdateSize*2; } } - else if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) + tmpClient = nullptr; + + mDevice->UpdateSize = RefTime2Samples(per_time, mDevice->Frequency); + mDevice->BufferSize = mDevice->UpdateSize*2; + + mResampler = nullptr; + mResampleBuffer = nullptr; + mBufferFilled = 0; + if(mDevice->Frequency != mFormat.Format.nSamplesPerSec) { - mDevice->FmtType = DevFmtFloat; - OutputType.Format.wBitsPerSample = 32; + const auto flags = as_unsigned(streamParams.StaticObjectTypeMask); + const auto channelCount = as_unsigned(al::popcount(flags)); + mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, + channelCount, mDevice->Frequency, mFormat.Format.nSamplesPerSec, + Resampler::FastBSinc24); + mResampleBuffer = std::make_unique<char[]>(size_t{mDevice->UpdateSize} * channelCount * + 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); } - else + + return S_OK; + } + +no_spatial: + mDevice->Flags.reset(Virtualization); + + auto &audio = mAudio.emplace<PlainDevice>(); + HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient), + al::out_ptr(audio.mClient))}; + if(FAILED(hr)) + { + ERR("Failed to reactivate audio client: 0x%08lx\n", hr); + return hr; + } + + WAVEFORMATEX *wfx; + hr = audio.mClient->GetMixFormat(&wfx); + if(FAILED(hr)) + { + ERR("Failed to get mix format: 0x%08lx\n", hr); + return hr; + } + TraceFormat("Device mix format", wfx); + + WAVEFORMATEXTENSIBLE OutputType; + if(!MakeExtensible(&OutputType, wfx)) + { + CoTaskMemFree(wfx); + return E_FAIL; + } + CoTaskMemFree(wfx); + wfx = nullptr; + + const ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency}; + const ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency}; + + prepareFormat(OutputType); + + TraceFormat("Requesting playback format", &OutputType.Format); + hr = audio.mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); + if(FAILED(hr)) + { + WARN("Failed to check format support: 0x%08lx\n", hr); + hr = audio.mClient->GetMixFormat(&wfx); + } + if(FAILED(hr)) + { + ERR("Failed to find a supported format: 0x%08lx\n", hr); + return hr; + } + + if(wfx != nullptr) + { + TraceFormat("Got playback format", wfx); + if(!MakeExtensible(&OutputType, wfx)) { - 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; - OutputType.Format.wBitsPerSample = 16; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + CoTaskMemFree(wfx); + return E_FAIL; } - OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; + CoTaskMemFree(wfx); + wfx = nullptr; + + finalizeFormat(OutputType); } mFormat = OutputType; - const EndpointFormFactor formfactor{get_device_formfactor(mMMDev.get())}; +#if !defined(ALSOFT_UWP) + const EndpointFormFactor formfactor{GetDeviceFormfactor(mMMDev.get())}; mDevice->Flags.set(DirectEar, (formfactor == Headphones || formfactor == Headset)); - +#else + mDevice->Flags.set(DirectEar, false); +#endif setDefaultWFXChannelOrder(); - hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + hr = audio.mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buf_time.count(), 0, &OutputType.Format, nullptr); if(FAILED(hr)) { @@ -1174,15 +1921,22 @@ HRESULT WasapiPlayback::resetProxy() UINT32 buffer_len{}; ReferenceTime min_per{}; - hr = mClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(min_per), nullptr); + hr = audio.mClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(min_per), nullptr); if(SUCCEEDED(hr)) - hr = mClient->GetBufferSize(&buffer_len); + hr = audio.mClient->GetBufferSize(&buffer_len); if(FAILED(hr)) { ERR("Failed to get audio buffer info: 0x%08lx\n", hr); return hr; } + hr = audio.mClient->SetEventHandle(mNotifyEvent); + if(FAILED(hr)) + { + ERR("Failed to set event handle: 0x%08lx\n", hr); + return hr; + } + /* 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); @@ -1212,13 +1966,6 @@ HRESULT WasapiPlayback::resetProxy() mDevice->UpdateSize); } - hr = mClient->SetEventHandle(mNotifyEvent); - if(FAILED(hr)) - { - ERR("Failed to set event handle: 0x%08lx\n", hr); - return hr; - } - return hr; } @@ -1235,33 +1982,61 @@ HRESULT WasapiPlayback::startProxy() { ResetEvent(mNotifyEvent); - HRESULT hr{mClient->Start()}; - if(FAILED(hr)) + auto mstate_fallback = [](std::monostate) -> HRESULT + { return E_FAIL; }; + auto start_plain = [&](PlainDevice &audio) -> HRESULT { - ERR("Failed to start audio client: 0x%08lx\n", hr); - return hr; - } + HRESULT hr{audio.mClient->Start()}; + if(FAILED(hr)) + { + ERR("Failed to start audio client: 0x%08lx\n", hr); + return hr; + } - void *ptr; - hr = mClient->GetService(IID_IAudioRenderClient, &ptr); - if(SUCCEEDED(hr)) + hr = audio.mClient->GetService(__uuidof(IAudioRenderClient), al::out_ptr(audio.mRender)); + if(SUCCEEDED(hr)) + { + try { + mKillNow.store(false, std::memory_order_release); + mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerProc), this}; + } + catch(...) { + audio.mRender = nullptr; + ERR("Failed to start thread\n"); + hr = E_FAIL; + } + } + if(FAILED(hr)) + audio.mClient->Stop(); + return hr; + }; + auto start_spatial = [&](SpatialDevice &audio) -> HRESULT { - mRender = ComPtr<IAudioRenderClient>{static_cast<IAudioRenderClient*>(ptr)}; + HRESULT hr{audio.mRender->Start()}; + if(FAILED(hr)) + { + ERR("Failed to start spatial audio stream: 0x%08lx\n", hr); + return hr; + } + try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerProc), this}; + mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerSpatialProc), this}; } catch(...) { - mRender = nullptr; ERR("Failed to start thread\n"); hr = E_FAIL; } - } - if(FAILED(hr)) - mClient->Stop(); + if(FAILED(hr)) + { + audio.mRender->Stop(); + audio.mRender->Reset(); + } + return hr; + }; - return hr; + return std::visit(overloaded{mstate_fallback, start_plain, start_spatial}, mAudio); } @@ -1270,14 +2045,25 @@ void WasapiPlayback::stop() void WasapiPlayback::stopProxy() { - if(!mRender || !mThread.joinable()) + if(!mThread.joinable()) return; mKillNow.store(true, std::memory_order_release); mThread.join(); - mRender = nullptr; - mClient->Stop(); + auto mstate_fallback = [](std::monostate) -> void + { }; + auto stop_plain = [](PlainDevice &audio) -> void + { + audio.mRender = nullptr; + audio.mClient->Stop(); + }; + auto stop_spatial = [](SpatialDevice &audio) -> void + { + audio.mRender->Stop(); + audio.mRender->Reset(); + }; + std::visit(overloaded{mstate_fallback, stop_plain, stop_spatial}, mAudio); } @@ -1306,8 +2092,8 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { int recordProc(); - void open(const char *name) override; - HRESULT openProxy(const char *name) override; + void open(std::string_view name) override; + HRESULT openProxy(std::string_view name) override; void closeProxy() override; HRESULT resetProxy() override; @@ -1316,11 +2102,11 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { void stop() override; void stopProxy() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; HRESULT mOpenStatus{E_FAIL}; - ComPtr<IMMDevice> mMMDev{nullptr}; + DeviceHandle mMMDev{nullptr}; ComPtr<IAudioClient> mClient{nullptr}; ComPtr<IAudioCaptureClient> mCapture{nullptr}; HANDLE mNotifyEvent{nullptr}; @@ -1338,10 +2124,7 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { WasapiCapture::~WasapiCapture() { if(SUCCEEDED(mOpenStatus)) - { pushMessage(MsgType::CloseDevice).wait(); - DeinitThread(); - } mOpenStatus = E_FAIL; if(mNotifyEvent != nullptr) @@ -1352,21 +2135,21 @@ WasapiCapture::~WasapiCapture() FORCE_ALIGN int WasapiCapture::recordProc() { - HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; - if(FAILED(hr)) + ComWrapper com{COINIT_MULTITHREADED}; + if(!com) { - ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr); - mDevice->handleDisconnect("COM init failed: 0x%08lx", hr); + ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status()); + mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status()); return 1; } althrd_setname(RECORD_THREAD_NAME); - al::vector<float> samples; + std::vector<float> samples; while(!mKillNow.load(std::memory_order_relaxed)) { UINT32 avail; - hr = mCapture->GetNextPacketSize(&avail); + HRESULT hr{mCapture->GetNextPacketSize(&avail)}; if(FAILED(hr)) ERR("Failed to get next packet size: 0x%08lx\n", hr); else if(avail > 0) @@ -1437,12 +2220,11 @@ FORCE_ALIGN int WasapiCapture::recordProc() ERR("WaitForSingleObjectEx error: 0x%lx\n", res); } - CoUninitialize(); return 0; } -void WasapiCapture::open(const char *name) +void WasapiCapture::open(std::string_view name) { if(SUCCEEDED(mOpenStatus)) throw al::backend_exception{al::backend_error::DeviceError, @@ -1456,34 +2238,18 @@ void WasapiCapture::open(const char *name) "Failed to create notify events"}; } - HRESULT hr{InitThread()}; - if(FAILED(hr)) - { - throw al::backend_exception{al::backend_error::DeviceError, - "Failed to init COM thread: 0x%08lx", hr}; - } - - if(name) + if(name.length() >= DevNameHeadLen + && std::strncmp(name.data(), DevNameHead, DevNameHeadLen) == 0) { - if(CaptureDevices.empty()) - pushMessage(MsgType::EnumerateCapture); - if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0) - { - name += DevNameHeadLen; - if(*name == '\0') - name = nullptr; - } + name = name.substr(DevNameHeadLen); } mOpenStatus = pushMessage(MsgType::OpenDevice, name).get(); if(FAILED(mOpenStatus)) - { - DeinitThread(); throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", mOpenStatus}; - } - hr = pushMessage(MsgType::ResetDevice).get(); + HRESULT hr{pushMessage(MsgType::ResetDevice).get()}; if(FAILED(hr)) { if(hr == E_OUTOFMEMORY) @@ -1492,52 +2258,47 @@ void WasapiCapture::open(const char *name) } } -HRESULT WasapiCapture::openProxy(const char *name) +HRESULT WasapiCapture::openProxy(std::string_view name) { - const wchar_t *devid{nullptr}; - if(name) + std::string devname; + std::wstring devid; + if(!name.empty()) { - auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), + auto devlock = DeviceListLock{gDeviceList}; + auto devlist = al::span{devlock.getCaptureList()}; + auto iter = std::find_if(devlist.cbegin(), devlist.cend(), [name](const DevMap &entry) -> bool { return entry.name == name || entry.endpoint_guid == name; }); - if(iter == CaptureDevices.cend()) + if(iter == devlist.cend()) { const std::wstring wname{utf8_to_wstr(name)}; - iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), + iter = std::find_if(devlist.cbegin(), devlist.cend(), [&wname](const DevMap &entry) -> bool { return entry.devid == wname; }); } - if(iter == CaptureDevices.cend()) + if(iter == devlist.cend()) { - WARN("Failed to find device name matching \"%s\"\n", name); + WARN("Failed to find device name matching \"%.*s\"\n", static_cast<int>(name.length()), + name.data()); return E_FAIL; } - name = iter->name.c_str(); - devid = iter->devid.c_str(); + devname = iter->name; + devid = iter->devid; } - void *ptr; - HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, - IID_IMMDeviceEnumerator, &ptr)}; - if(SUCCEEDED(hr)) - { - ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)}; - if(!devid) - hr = enumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, mMMDev.getPtr()); - else - hr = enumerator->GetDevice(devid, mMMDev.getPtr()); - } + HRESULT hr{sDeviceHelper->openDevice(devid, eCapture, mMMDev)}; if(FAILED(hr)) { - WARN("Failed to open device \"%s\"\n", name?name:"(default)"); + WARN("Failed to open device \"%s\"\n", devname.empty() ? "(default)" : devname.c_str()); return hr; } - mClient = nullptr; - if(name) mDevice->DeviceName = std::string{DevNameHead} + name; - else mDevice->DeviceName = DevNameHead + get_device_name_and_guid(mMMDev.get()).first; + if(!devname.empty()) + mDevice->DeviceName = DevNameHead + std::move(devname); + else + mDevice->DeviceName = DevNameHead + GetDeviceNameAndGuid(mMMDev).first; - return hr; + return S_OK; } void WasapiCapture::closeProxy() @@ -1550,14 +2311,13 @@ HRESULT WasapiCapture::resetProxy() { mClient = nullptr; - void *ptr; - HRESULT hr{mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr)}; + HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient), + al::out_ptr(mClient))}; if(FAILED(hr)) { ERR("Failed to reactivate audio client: 0x%08lx\n", hr); return hr; } - mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)}; WAVEFORMATEX *wfx; hr = mClient->GetMixFormat(&wfx); @@ -1849,11 +2609,9 @@ HRESULT WasapiCapture::startProxy() return hr; } - void *ptr; - hr = mClient->GetService(IID_IAudioCaptureClient, &ptr); + hr = mClient->GetService(__uuidof(IAudioCaptureClient), al::out_ptr(mCapture)); if(SUCCEEDED(hr)) { - mCapture = ComPtr<IAudioCaptureClient>{static_cast<IAudioCaptureClient*>(ptr)}; try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&WasapiCapture::recordProc), this}; @@ -1892,7 +2650,7 @@ void WasapiCapture::stopProxy() } -void WasapiCapture::captureSamples(al::byte *buffer, uint samples) +void WasapiCapture::captureSamples(std::byte *buffer, uint samples) { mRing->read(buffer, samples); } uint WasapiCapture::availableSamples() @@ -1904,34 +2662,13 @@ uint WasapiCapture::availableSamples() bool WasapiBackendFactory::init() { static HRESULT InitResult{E_FAIL}; - if(FAILED(InitResult)) try { - 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::promise<HRESULT> promise; + auto future = promise.get_future(); - InitResult = res.get(); + std::thread{&WasapiProxy::messageHandler, &promise}.detach(); + InitResult = future.get(); } catch(...) { } @@ -1944,34 +2681,45 @@ bool WasapiBackendFactory::querySupport(BackendType type) std::string WasapiBackendFactory::probe(BackendType type) { - struct ProxyControl { - HRESULT mResult{}; - ProxyControl() { mResult = WasapiProxy::InitThread(); } - ~ProxyControl() { if(SUCCEEDED(mResult)) WasapiProxy::DeinitThread(); } - }; - ProxyControl proxy; - std::string outnames; - if(FAILED(proxy.mResult)) - return outnames; + auto devlock = DeviceListLock{gDeviceList}; switch(type) { 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); + auto defaultId = devlock.getPlaybackDefaultId(); + for(const DevMap &entry : devlock.getPlaybackList()) + { + if(entry.devid != defaultId) + { + /* +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); + continue; + } + /* Default device goes first. */ + std::string name{DevNameHead + entry.name}; + outnames.insert(0, name.c_str(), name.length()+1); + } } break; 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); + { + auto defaultId = devlock.getCaptureDefaultId(); + for(const DevMap &entry : devlock.getCaptureList()) + { + if(entry.devid != defaultId) + { + outnames.append(DevNameHead).append(entry.name.c_str(), entry.name.length()+1); + continue; + } + std::string name{DevNameHead + entry.name}; + outnames.insert(0, name.c_str(), name.length()+1); + } + } break; } @@ -1992,3 +2740,22 @@ BackendFactory &WasapiBackendFactory::getFactory() static WasapiBackendFactory factory{}; return factory; } + +alc::EventSupport WasapiBackendFactory::queryEventSupport(alc::EventType eventType, BackendType) +{ + switch(eventType) + { + case alc::EventType::DefaultDeviceChanged: + return alc::EventSupport::FullSupport; + + case alc::EventType::DeviceAdded: + case alc::EventType::DeviceRemoved: +#if !defined(ALSOFT_UWP) + return alc::EventSupport::FullSupport; +#endif + + case alc::EventType::Count: + break; + } + return alc::EventSupport::NoSupport; +} diff --git a/alc/backends/wasapi.h b/alc/backends/wasapi.h index bb2671ee..12fd95ef 100644 --- a/alc/backends/wasapi.h +++ b/alc/backends/wasapi.h @@ -9,6 +9,8 @@ public: bool querySupport(BackendType type) override; + alc::EventSupport queryEventSupport(alc::EventType eventType, BackendType type) override; + std::string probe(BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override; diff --git a/alc/backends/wave.cpp b/alc/backends/wave.cpp index 1b40640c..794d5cb8 100644 --- a/alc/backends/wave.cpp +++ b/alc/backends/wave.cpp @@ -32,19 +32,18 @@ #include <exception> #include <functional> #include <thread> +#include <vector> #include "albit.h" -#include "albyte.h" #include "alc/alconfig.h" #include "almalloc.h" #include "alnumeric.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "opthelpers.h" #include "strutils.h" -#include "threads.h" -#include "vector.h" namespace { @@ -97,7 +96,7 @@ struct WaveBackend final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -105,7 +104,7 @@ struct WaveBackend final : public BackendBase { FILE *mFile{nullptr}; long mDataStart{-1}; - al::vector<al::byte> mBuffer; + std::vector<std::byte> mBuffer; std::atomic<bool> mKillNow{true}; std::thread mThread; @@ -155,13 +154,13 @@ int WaveBackend::mixerProc() if(bytesize == 2) { - const size_t len{mBuffer.size() & ~size_t{1}}; + const size_t len{mBuffer.size() & ~1_uz}; for(size_t i{0};i < len;i+=2) std::swap(mBuffer[i], mBuffer[i+1]); } else if(bytesize == 4) { - const size_t len{mBuffer.size() & ~size_t{3}}; + const size_t len{mBuffer.size() & ~3_uz}; for(size_t i{0};i < len;i+=4) { std::swap(mBuffer[i ], mBuffer[i+3]); @@ -195,24 +194,24 @@ int WaveBackend::mixerProc() return 0; } -void WaveBackend::open(const char *name) +void WaveBackend::open(std::string_view name) { auto fname = ConfigValueStr(nullptr, "wave", "file"); if(!fname) throw al::backend_exception{al::backend_error::NoDevice, "No wave output filename"}; - if(!name) + if(name.empty()) name = waveDevice; - else if(strcmp(name, waveDevice) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + else if(name != waveDevice) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + static_cast<int>(name.length()), name.data()}; /* There's only one "device", so if it's already open, we're done. */ if(mFile) return; #ifdef _WIN32 { - std::wstring wname{utf8_to_wstr(fname->c_str())}; + std::wstring wname{utf8_to_wstr(fname.value())}; mFile = _wfopen(wname.c_str(), L"wb"); } #else diff --git a/alc/backends/winmm.cpp b/alc/backends/winmm.cpp index 38e1193f..f0fb0a1c 100644 --- a/alc/backends/winmm.cpp +++ b/alc/backends/winmm.cpp @@ -39,12 +39,13 @@ #include <functional> #include "alnumeric.h" +#include "alsem.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "ringbuffer.h" #include "strutils.h" -#include "threads.h" #ifndef WAVE_FORMAT_IEEE_FLOAT #define WAVE_FORMAT_IEEE_FLOAT 0x0003 @@ -55,10 +56,10 @@ namespace { #define DEVNAME_HEAD "OpenAL Soft on " -al::vector<std::string> PlaybackDevices; -al::vector<std::string> CaptureDevices; +std::vector<std::string> PlaybackDevices; +std::vector<std::string> CaptureDevices; -bool checkName(const al::vector<std::string> &list, const std::string &name) +bool checkName(const std::vector<std::string> &list, const std::string &name) { return std::find(list.cbegin(), list.cend(), name) != list.cend(); } void ProbePlaybackDevices(void) @@ -134,7 +135,7 @@ struct WinMMPlayback final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -166,7 +167,7 @@ WinMMPlayback::~WinMMPlayback() /* WinMMPlayback::waveOutProc * - * Posts a message to 'WinMMPlayback::mixerProc' everytime a WaveOut Buffer is + * Posts a message to 'WinMMPlayback::mixerProc' every time a WaveOut Buffer is * completed and returns to the application (for more data) */ void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PTR) noexcept @@ -207,18 +208,18 @@ FORCE_ALIGN int WinMMPlayback::mixerProc() } -void WinMMPlayback::open(const char *name) +void WinMMPlayback::open(std::string_view name) { if(PlaybackDevices.empty()) ProbePlaybackDevices(); // Find the Device ID matching the deviceName if valid - auto iter = name ? + auto iter = !name.empty() ? std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) : PlaybackDevices.cbegin(); if(iter == PlaybackDevices.cend()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + static_cast<int>(name.length()), name.data()}; auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter)); DevFmtType fmttype{mDevice->FmtType}; @@ -369,10 +370,10 @@ struct WinMMCapture final : public BackendBase { int captureProc(); - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; std::atomic<uint> mReadable{0u}; @@ -405,7 +406,7 @@ WinMMCapture::~WinMMCapture() /* WinMMCapture::waveInProc * - * Posts a message to 'WinMMCapture::captureProc' everytime a WaveIn Buffer is + * Posts a message to 'WinMMCapture::captureProc' every time a WaveIn Buffer is * completed and returns to the application (with more data). */ void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) noexcept @@ -445,18 +446,18 @@ int WinMMCapture::captureProc() } -void WinMMCapture::open(const char *name) +void WinMMCapture::open(std::string_view name) { if(CaptureDevices.empty()) ProbeCaptureDevices(); // Find the Device ID matching the deviceName if valid - auto iter = name ? + auto iter = !name.empty() ? std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) : CaptureDevices.cbegin(); if(iter == CaptureDevices.cend()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + static_cast<int>(name.length()), name.data()}; auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter)); switch(mDevice->FmtChans) @@ -571,7 +572,7 @@ void WinMMCapture::stop() mIdx = 0; } -void WinMMCapture::captureSamples(al::byte *buffer, uint samples) +void WinMMCapture::captureSamples(std::byte *buffer, uint samples) { mRing->read(buffer, samples); } uint WinMMCapture::availableSamples() |