Initial commit

added libraries:
opus
flac
libsndfile

updated:
libvorbis
libogg
openal

- Everything works as expected for now. Bare in mind libsndfile needed the check for whether or not it could find the xiph libraries removed in order for this to work.
This commit is contained in:
marauder2k7 2024-03-21 17:33:47 +00:00
parent 05a083ca6f
commit a745fc3757
1954 changed files with 431332 additions and 21037 deletions

View file

@ -132,7 +132,7 @@ constexpr char alsaDevice[] = "ALSA Default";
MAGIC(snd_card_next); \
MAGIC(snd_config_update_free_global)
static void *alsa_handle;
void *alsa_handle;
#define MAKE_FUNC(f) decltype(f) * p##f
ALSA_FUNCS(MAKE_FUNC);
#undef MAKE_FUNC
@ -241,6 +241,11 @@ SwParamsPtr CreateSwParams()
struct DevMap {
std::string name;
std::string device_name;
template<typename T, typename U>
DevMap(T&& name_, U&& devname)
: name{std::forward<T>(name_)}, device_name{std::forward<U>(devname)}
{ }
};
al::vector<DevMap> PlaybackDevices;
@ -264,7 +269,7 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
auto defname = ConfigValueStr(nullptr, "alsa",
(stream == SND_PCM_STREAM_PLAYBACK) ? "device" : "capture");
devlist.emplace_back(DevMap{alsaDevice, defname ? defname->c_str() : "default"});
devlist.emplace_back(alsaDevice, defname ? defname->c_str() : "default");
if(auto customdevs = ConfigValueStr(nullptr, "alsa",
(stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures"))
@ -283,8 +288,8 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
}
else
{
devlist.emplace_back(DevMap{customdevs->substr(curpos, seppos-curpos),
customdevs->substr(seppos+1, nextpos-seppos-1)});
devlist.emplace_back(customdevs->substr(curpos, seppos-curpos),
customdevs->substr(seppos+1, nextpos-seppos-1));
const auto &entry = devlist.back();
TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
}
@ -325,7 +330,7 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(main_prefix)};
int dev{-1};
while(1)
while(true)
{
if(snd_ctl_pcm_next_device(handle, &dev) < 0)
ERR("snd_ctl_pcm_next_device failed\n");
@ -367,7 +372,7 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
device += ",DEV=";
device += std::to_string(dev);
devlist.emplace_back(DevMap{std::move(name), std::move(device)});
devlist.emplace_back(std::move(name), std::move(device));
const auto &entry = devlist.back();
TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
}
@ -623,8 +628,7 @@ int AlsaPlayback::mixerNoMMapProc()
void AlsaPlayback::open(const char *name)
{
al::optional<std::string> driveropt;
const char *driver{"default"};
std::string driver{"default"};
if(name)
{
if(PlaybackDevices.empty())
@ -635,21 +639,21 @@ void AlsaPlayback::open(const char *name)
if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
driver = iter->device_name.c_str();
driver = iter->device_name;
}
else
{
name = alsaDevice;
if(bool{driveropt = ConfigValueStr(nullptr, "alsa", "device")})
driver = driveropt->c_str();
if(auto driveropt = ConfigValueStr(nullptr, "alsa", "device"))
driver = std::move(driveropt).value();
}
TRACE("Opening device \"%s\"\n", driver);
TRACE("Opening device \"%s\"\n", driver.c_str());
snd_pcm_t *pcmHandle{};
int err{snd_pcm_open(&pcmHandle, driver, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)};
int err{snd_pcm_open(&pcmHandle, driver.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)};
if(err < 0)
throw al::backend_exception{al::backend_error::NoDevice,
"Could not open ALSA device \"%s\"", driver};
"Could not open ALSA device \"%s\"", driver.c_str()};
if(mPcmHandle)
snd_pcm_close(mPcmHandle);
mPcmHandle = pcmHandle;
@ -688,7 +692,7 @@ bool AlsaPlayback::reset()
break;
}
bool allowmmap{!!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "mmap", 1)};
bool allowmmap{!!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "mmap", true)};
uint periodLen{static_cast<uint>(mDevice->UpdateSize * 1000000_u64 / mDevice->Frequency)};
uint bufferLen{static_cast<uint>(mDevice->BufferSize * 1000000_u64 / mDevice->Frequency)};
uint rate{mDevice->Frequency};
@ -746,7 +750,7 @@ bool AlsaPlayback::reset()
else mDevice->FmtChans = DevFmtStereo;
}
/* set rate (implicitly constrains period/buffer parameters) */
if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", 0)
if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", false)
|| !mDevice->Flags.test(FrequencyRequest))
{
if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0)
@ -896,8 +900,7 @@ AlsaCapture::~AlsaCapture()
void AlsaCapture::open(const char *name)
{
al::optional<std::string> driveropt;
const char *driver{"default"};
std::string driver{"default"};
if(name)
{
if(CaptureDevices.empty())
@ -908,20 +911,20 @@ void AlsaCapture::open(const char *name)
if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
driver = iter->device_name.c_str();
driver = iter->device_name;
}
else
{
name = alsaDevice;
if(bool{driveropt = ConfigValueStr(nullptr, "alsa", "capture")})
driver = driveropt->c_str();
if(auto driveropt = ConfigValueStr(nullptr, "alsa", "capture"))
driver = std::move(driveropt).value();
}
TRACE("Opening device \"%s\"\n", driver);
int err{snd_pcm_open(&mPcmHandle, driver, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)};
TRACE("Opening device \"%s\"\n", driver.c_str());
int err{snd_pcm_open(&mPcmHandle, driver.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)};
if(err < 0)
throw al::backend_exception{al::backend_error::NoDevice,
"Could not open ALSA device \"%s\"", driver};
"Could not open ALSA device \"%s\"", driver.c_str()};
/* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */
snd_config_update_free_global();

View file

@ -21,6 +21,20 @@
#include "core/devformat.h"
namespace al {
backend_exception::backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code}
{
std::va_list args;
va_start(args, msg);
setMessage(msg, args);
va_end(args);
}
backend_exception::~backend_exception() = default;
} // namespace al
bool BackendBase::reset()
{ throw al::backend_exception{al::backend_error::DeviceError, "Invalid BackendBase call"}; }
@ -54,7 +68,7 @@ ClockLatency BackendBase::getClockLatency()
void BackendBase::setDefaultWFXChannelOrder()
{
mDevice->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX);
mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex);
switch(mDevice->FmtChans)
{
@ -98,6 +112,30 @@ void BackendBase::setDefaultWFXChannelOrder()
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
break;
case DevFmtX714:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[FrontCenter] = 2;
mDevice->RealOut.ChannelIndex[LFE] = 3;
mDevice->RealOut.ChannelIndex[BackLeft] = 4;
mDevice->RealOut.ChannelIndex[BackRight] = 5;
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8;
mDevice->RealOut.ChannelIndex[TopFrontRight] = 9;
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
break;
case DevFmtX3D71:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[FrontCenter] = 2;
mDevice->RealOut.ChannelIndex[LFE] = 3;
mDevice->RealOut.ChannelIndex[Aux0] = 4;
mDevice->RealOut.ChannelIndex[Aux1] = 5;
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
break;
case DevFmtAmbi3D:
break;
}
@ -105,7 +143,7 @@ void BackendBase::setDefaultWFXChannelOrder()
void BackendBase::setDefaultChannelOrder()
{
mDevice->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX);
mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex);
switch(mDevice->FmtChans)
{
@ -127,6 +165,30 @@ void BackendBase::setDefaultChannelOrder()
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
return;
case DevFmtX714:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[BackLeft] = 2;
mDevice->RealOut.ChannelIndex[BackRight] = 3;
mDevice->RealOut.ChannelIndex[FrontCenter] = 4;
mDevice->RealOut.ChannelIndex[LFE] = 5;
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8;
mDevice->RealOut.ChannelIndex[TopFrontRight] = 9;
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
break;
case DevFmtX3D71:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[Aux0] = 2;
mDevice->RealOut.ChannelIndex[Aux1] = 3;
mDevice->RealOut.ChannelIndex[FrontCenter] = 4;
mDevice->RealOut.ChannelIndex[LFE] = 5;
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
return;
/* Same as WFX order */
case DevFmtMono:
@ -138,57 +200,3 @@ void BackendBase::setDefaultChannelOrder()
break;
}
}
#ifdef _WIN32
void BackendBase::setChannelOrderFromWFXMask(uint chanmask)
{
static constexpr uint x51{SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER
| SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT};
static constexpr uint x51rear{SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER
| SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT};
/* Swap a 5.1 mask using the back channels for one with the sides. */
if(chanmask == x51rear) chanmask = x51;
auto get_channel = [](const DWORD chanbit) noexcept -> al::optional<Channel>
{
switch(chanbit)
{
case SPEAKER_FRONT_LEFT: return al::make_optional(FrontLeft);
case SPEAKER_FRONT_RIGHT: return al::make_optional(FrontRight);
case SPEAKER_FRONT_CENTER: return al::make_optional(FrontCenter);
case SPEAKER_LOW_FREQUENCY: return al::make_optional(LFE);
case SPEAKER_BACK_LEFT: return al::make_optional(BackLeft);
case SPEAKER_BACK_RIGHT: return al::make_optional(BackRight);
case SPEAKER_FRONT_LEFT_OF_CENTER: break;
case SPEAKER_FRONT_RIGHT_OF_CENTER: break;
case SPEAKER_BACK_CENTER: return al::make_optional(BackCenter);
case SPEAKER_SIDE_LEFT: return al::make_optional(SideLeft);
case SPEAKER_SIDE_RIGHT: return al::make_optional(SideRight);
case SPEAKER_TOP_CENTER: return al::make_optional(TopCenter);
case SPEAKER_TOP_FRONT_LEFT: return al::make_optional(TopFrontLeft);
case SPEAKER_TOP_FRONT_CENTER: return al::make_optional(TopFrontCenter);
case SPEAKER_TOP_FRONT_RIGHT: return al::make_optional(TopFrontRight);
case SPEAKER_TOP_BACK_LEFT: return al::make_optional(TopBackLeft);
case SPEAKER_TOP_BACK_CENTER: return al::make_optional(TopBackCenter);
case SPEAKER_TOP_BACK_RIGHT: return al::make_optional(TopBackRight);
}
WARN("Unhandled WFX channel bit 0x%lx\n", chanbit);
return al::nullopt;
};
const uint numchans{mDevice->channelsFromFmt()};
uint idx{0};
while(chanmask)
{
const int bit{al::countr_zero(chanmask)};
const uint mask{1u << bit};
chanmask &= ~mask;
if(auto label = get_channel(mask))
{
mDevice->RealOut.ChannelIndex[*label] = idx;
if(++idx == numchans) break;
}
}
}
#endif

View file

@ -41,11 +41,6 @@ protected:
void setDefaultChannelOrder();
/** Sets the default channel order used by WaveFormatEx. */
void setDefaultWFXChannelOrder();
#ifdef _WIN32
/** Sets the channel order given the WaveFormatEx mask. */
void setChannelOrderFromWFXMask(uint chanmask);
#endif
};
using BackendPtr = std::unique_ptr<BackendBase>;
@ -108,13 +103,9 @@ public:
#else
[[gnu::format(printf, 3, 4)]]
#endif
backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code}
{
std::va_list args;
va_start(args, msg);
setMessage(msg, args);
va_end(args);
}
backend_exception(backend_error code, const char *msg, ...);
~backend_exception() override;
backend_error errorCode() const noexcept { return mErrorCode; }
};

View file

@ -51,6 +51,8 @@ namespace {
#define CAN_ENUMERATE 1
#endif
constexpr auto OutputElement = 0;
constexpr auto InputElement = 1;
#if CAN_ENUMERATE
struct DeviceEntry {
@ -354,7 +356,7 @@ void CoreAudioPlayback::open(const char *name)
#if CAN_ENUMERATE
if(audioDevice != kAudioDeviceUnknown)
AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, 0, &audioDevice, sizeof(AudioDeviceID));
kAudioUnitScope_Global, OutputElement, &audioDevice, sizeof(AudioDeviceID));
#endif
err = AudioUnitInitialize(audioUnit);
@ -380,7 +382,7 @@ void CoreAudioPlayback::open(const char *name)
UInt32 propSize{sizeof(audioDevice)};
audioDevice = kAudioDeviceUnknown;
AudioUnitGetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, 0, &audioDevice, &propSize);
kAudioUnitScope_Global, OutputElement, &audioDevice, &propSize);
std::string devname{GetDeviceName(audioDevice)};
if(!devname.empty()) mDevice->DeviceName = std::move(devname);
@ -401,7 +403,7 @@ bool CoreAudioPlayback::reset()
AudioStreamBasicDescription streamFormat{};
UInt32 size{sizeof(streamFormat)};
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
0, &streamFormat, &size);
OutputElement, &streamFormat, &size);
if(err != noErr || size != sizeof(streamFormat))
{
ERR("AudioUnitGetProperty failed\n");
@ -423,8 +425,8 @@ bool CoreAudioPlayback::reset()
*/
if(mDevice->Frequency != streamFormat.mSampleRate)
{
mDevice->BufferSize = static_cast<uint>(uint64_t{mDevice->BufferSize} *
streamFormat.mSampleRate / mDevice->Frequency);
mDevice->BufferSize = static_cast<uint>(mDevice->BufferSize*streamFormat.mSampleRate/
mDevice->Frequency + 0.5);
mDevice->Frequency = static_cast<uint>(streamFormat.mSampleRate);
}
@ -468,7 +470,7 @@ bool CoreAudioPlayback::reset()
streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame*streamFormat.mFramesPerPacket;
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
0, &streamFormat, sizeof(streamFormat));
OutputElement, &streamFormat, sizeof(streamFormat));
if(err != noErr)
{
ERR("AudioUnitSetProperty failed\n");
@ -484,7 +486,7 @@ bool CoreAudioPlayback::reset()
input.inputProcRefCon = this;
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, 0, &input, sizeof(AURenderCallbackStruct));
kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct));
if(err != noErr)
{
ERR("AudioUnitSetProperty failed\n");
@ -546,6 +548,8 @@ struct CoreAudioCapture final : public BackendBase {
SampleConverterPtr mConverter;
al::vector<char> mCaptureData;
RingBufferPtr mRing{nullptr};
DEF_NEWDEL(CoreAudioCapture)
@ -559,49 +563,29 @@ CoreAudioCapture::~CoreAudioCapture()
}
OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags*,
const AudioTimeStamp *inTimeStamp, UInt32, UInt32 inNumberFrames,
OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList*) noexcept
{
AudioUnitRenderActionFlags flags = 0;
union {
al::byte _[sizeof(AudioBufferList) + sizeof(AudioBuffer)*2];
al::byte _[maxz(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))];
AudioBufferList list;
} audiobuf{};
auto rec_vec = mRing->getWriteVector();
inNumberFrames = static_cast<UInt32>(minz(inNumberFrames,
rec_vec.first.len+rec_vec.second.len));
audiobuf.list.mNumberBuffers = 1;
audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
audiobuf.list.mBuffers[0].mData = mCaptureData.data();
audiobuf.list.mBuffers[0].mDataByteSize = static_cast<UInt32>(mCaptureData.size());
// Fill the ringbuffer's two segments with data from the input device
if(rec_vec.first.len >= inNumberFrames)
{
audiobuf.list.mNumberBuffers = 1;
audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
audiobuf.list.mBuffers[0].mData = rec_vec.first.buf;
audiobuf.list.mBuffers[0].mDataByteSize = inNumberFrames * mFormat.mBytesPerFrame;
}
else
{
const auto remaining = static_cast<uint>(inNumberFrames - rec_vec.first.len);
audiobuf.list.mNumberBuffers = 2;
audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
audiobuf.list.mBuffers[0].mData = rec_vec.first.buf;
audiobuf.list.mBuffers[0].mDataByteSize = static_cast<UInt32>(rec_vec.first.len) *
mFormat.mBytesPerFrame;
audiobuf.list.mBuffers[1].mNumberChannels = mFormat.mChannelsPerFrame;
audiobuf.list.mBuffers[1].mData = rec_vec.second.buf;
audiobuf.list.mBuffers[1].mDataByteSize = remaining * mFormat.mBytesPerFrame;
}
OSStatus err{AudioUnitRender(mAudioUnit, &flags, inTimeStamp, audiobuf.list.mNumberBuffers,
OSStatus err{AudioUnitRender(mAudioUnit, ioActionFlags, inTimeStamp, inBusNumber,
inNumberFrames, &audiobuf.list)};
if(err != noErr)
{
ERR("AudioUnitRender error: %d\n", err);
ERR("AudioUnitRender capture error: %d\n", err);
return err;
}
mRing->writeAdvance(inNumberFrames);
mRing->write(mCaptureData.data(), inNumberFrames);
return noErr;
}
@ -658,16 +642,10 @@ void CoreAudioCapture::open(const char *name)
throw al::backend_exception{al::backend_error::NoDevice,
"Could not create component instance: %u", err};
#if CAN_ENUMERATE
if(audioDevice != kAudioDeviceUnknown)
AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, 0, &audioDevice, sizeof(AudioDeviceID));
#endif
// Turn off AudioUnit output
UInt32 enableIO{0};
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output, 0, &enableIO, sizeof(enableIO));
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};
@ -675,18 +653,24 @@ void CoreAudioCapture::open(const char *name)
// Turn on AudioUnit input
enableIO = 1;
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input, 1, &enableIO, sizeof(enableIO));
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};
#if CAN_ENUMERATE
if(audioDevice != kAudioDeviceUnknown)
AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, InputElement, &audioDevice, sizeof(AudioDeviceID));
#endif
// set capture callback
AURenderCallbackStruct input{};
input.inputProc = CoreAudioCapture::RecordProcC;
input.inputProcRefCon = this;
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global, 0, &input, sizeof(AURenderCallbackStruct));
kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not set capture callback: %u", err};
@ -694,7 +678,7 @@ void CoreAudioCapture::open(const char *name)
// Disable buffer allocation for capture
UInt32 flag{0};
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_ShouldAllocateBuffer,
kAudioUnitScope_Output, 1, &flag, sizeof(flag));
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};
@ -709,7 +693,7 @@ void CoreAudioCapture::open(const char *name)
AudioStreamBasicDescription hardwareFormat{};
UInt32 propertySize{sizeof(hardwareFormat)};
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
1, &hardwareFormat, &propertySize);
InputElement, &hardwareFormat, &propertySize);
if(err != noErr || propertySize != sizeof(hardwareFormat))
throw al::backend_exception{al::backend_error::DeviceError,
"Could not get input format: %u", err};
@ -764,6 +748,8 @@ void CoreAudioCapture::open(const char *name)
case DevFmtX51:
case DevFmtX61:
case DevFmtX71:
case DevFmtX714:
case DevFmtX3D71:
case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s not supported",
DevFmtChannelsString(mDevice->FmtChans)};
@ -788,7 +774,7 @@ void CoreAudioCapture::open(const char *name)
// The output format should be the requested format, but using the hardware sample rate
// This is because the AudioUnit will automatically scale other properties, except for sample rate
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
1, &outputFormat, sizeof(outputFormat));
InputElement, &outputFormat, sizeof(outputFormat));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not set input format: %u", err};
@ -796,7 +782,7 @@ void CoreAudioCapture::open(const char *name)
/* Calculate the minimum AudioUnit output format frame count for the pre-
* conversion ring buffer. Ensure at least 100ms for the total buffer.
*/
double srateScale{double{outputFormat.mSampleRate} / mDevice->Frequency};
double srateScale{outputFormat.mSampleRate / mDevice->Frequency};
auto FrameCount64 = maxu64(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)),
static_cast<UInt32>(outputFormat.mSampleRate)/10);
FrameCount64 += MaxResamplerPadding;
@ -807,17 +793,19 @@ void CoreAudioCapture::open(const char *name)
UInt32 outputFrameCount{};
propertySize = sizeof(outputFrameCount);
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
kAudioUnitScope_Global, 0, &outputFrameCount, &propertySize);
kAudioUnitScope_Global, OutputElement, &outputFrameCount, &propertySize);
if(err != noErr || propertySize != sizeof(outputFrameCount))
throw al::backend_exception{al::backend_error::DeviceError,
"Could not get input frame count: %u", err};
mCaptureData.resize(outputFrameCount * mFrameSize);
outputFrameCount = static_cast<UInt32>(maxu64(outputFrameCount, FrameCount64));
mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false);
/* Set up sample converter if needed */
if(outputFormat.mSampleRate != mDevice->Frequency)
mConverter = CreateSampleConverter(mDevice->FmtType, mDevice->FmtType,
mConverter = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType,
mFormat.mChannelsPerFrame, static_cast<uint>(hardwareFormat.mSampleRate),
mDevice->Frequency, Resampler::FastBSinc24);
@ -829,7 +817,7 @@ void CoreAudioCapture::open(const char *name)
UInt32 propSize{sizeof(audioDevice)};
audioDevice = kAudioDeviceUnknown;
AudioUnitGetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, 0, &audioDevice, &propSize);
kAudioUnitScope_Global, InputElement, &audioDevice, &propSize);
std::string devname{GetDeviceName(audioDevice)};
if(!devname.empty()) mDevice->DeviceName = std::move(devname);

View file

@ -115,6 +115,7 @@ HRESULT (WINAPI *pDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallbac
#define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
#define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
#define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
#define X7DOT1DOT4 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT|SPEAKER_TOP_FRONT_LEFT|SPEAKER_TOP_FRONT_RIGHT|SPEAKER_TOP_BACK_LEFT|SPEAKER_TOP_BACK_RIGHT)
#define MAX_UPDATES 128
@ -389,52 +390,56 @@ bool DSoundPlayback::reset()
}
WAVEFORMATEXTENSIBLE OutputType{};
DWORD speakers;
DWORD speakers{};
HRESULT hr{mDS->GetSpeakerConfig(&speakers)};
if(SUCCEEDED(hr))
{
speakers = DSSPEAKER_CONFIG(speakers);
if(!mDevice->Flags.test(ChannelsRequest))
{
if(speakers == DSSPEAKER_MONO)
mDevice->FmtChans = DevFmtMono;
else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE)
mDevice->FmtChans = DevFmtStereo;
else if(speakers == DSSPEAKER_QUAD)
mDevice->FmtChans = DevFmtQuad;
else if(speakers == DSSPEAKER_5POINT1_SURROUND || speakers == DSSPEAKER_5POINT1_BACK)
mDevice->FmtChans = DevFmtX51;
else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
mDevice->FmtChans = DevFmtX71;
else
ERR("Unknown system speaker config: 0x%lx\n", speakers);
}
mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE));
if(FAILED(hr))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to get speaker config: 0x%08lx", hr};
switch(mDevice->FmtChans)
{
case DevFmtMono: OutputType.dwChannelMask = MONO; break;
case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo;
/*fall-through*/
case DevFmtStereo: OutputType.dwChannelMask = STEREO; break;
case DevFmtQuad: OutputType.dwChannelMask = QUAD; break;
case DevFmtX51: OutputType.dwChannelMask = X5DOT1; break;
case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break;
}
speakers = DSSPEAKER_CONFIG(speakers);
if(!mDevice->Flags.test(ChannelsRequest))
{
if(speakers == DSSPEAKER_MONO)
mDevice->FmtChans = DevFmtMono;
else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE)
mDevice->FmtChans = DevFmtStereo;
else if(speakers == DSSPEAKER_QUAD)
mDevice->FmtChans = DevFmtQuad;
else if(speakers == DSSPEAKER_5POINT1_SURROUND || speakers == DSSPEAKER_5POINT1_BACK)
mDevice->FmtChans = DevFmtX51;
else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
mDevice->FmtChans = DevFmtX71;
else
ERR("Unknown system speaker config: 0x%lx\n", speakers);
}
mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE));
const bool isRear51{speakers == DSSPEAKER_5POINT1_BACK};
switch(mDevice->FmtChans)
{
case DevFmtMono: OutputType.dwChannelMask = MONO; break;
case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo;
/* fall-through */
case DevFmtStereo: OutputType.dwChannelMask = STEREO; break;
case DevFmtQuad: OutputType.dwChannelMask = QUAD; break;
case DevFmtX51: OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break;
case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break;
case DevFmtX714: OutputType.dwChannelMask = X7DOT1DOT4; break;
case DevFmtX3D71: OutputType.dwChannelMask = X7DOT1; break;
}
retry_open:
hr = S_OK;
OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
OutputType.Format.wBitsPerSample / 8);
OutputType.Format.nSamplesPerSec = mDevice->Frequency;
OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
OutputType.Format.nBlockAlign;
OutputType.Format.cbSize = 0;
}
hr = S_OK;
OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
OutputType.Format.wBitsPerSample / 8);
OutputType.Format.nSamplesPerSec = mDevice->Frequency;
OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
OutputType.Format.nBlockAlign;
OutputType.Format.cbSize = 0;
if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
{
@ -514,7 +519,7 @@ retry_open:
}
ResetEvent(mNotifyEvent);
setChannelOrderFromWFXMask(OutputType.dwChannelMask);
setDefaultWFXChannelOrder();
return true;
}
@ -635,6 +640,8 @@ void DSoundCapture::open(const char *name)
case DevFmtX51: InputType.dwChannelMask = X5DOT1; break;
case DevFmtX61: InputType.dwChannelMask = X6DOT1; break;
case DevFmtX71: InputType.dwChannelMask = X7DOT1; break;
case DevFmtX714: InputType.dwChannelMask = X7DOT1DOT4; break;
case DevFmtX3D71:
case DevFmtAmbi3D:
WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
@ -689,7 +696,7 @@ void DSoundCapture::open(const char *name)
}
mBufferBytes = DSCBDescription.dwBufferBytes;
setChannelOrderFromWFXMask(InputType.dwChannelMask);
setDefaultWFXChannelOrder();
mDevice->DeviceName = name;
}

View file

@ -160,6 +160,11 @@ using JackPortsPtr = std::unique_ptr<const char*[],JackDeleter>;
struct DeviceEntry {
std::string mName;
std::string mPattern;
template<typename T, typename U>
DeviceEntry(T&& name, U&& pattern)
: mName{std::forward<T>(name)}, mPattern{std::forward<U>(pattern)}
{ }
};
al::vector<DeviceEntry> PlaybackList;
@ -187,7 +192,7 @@ void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
continue;
std::string name{portdev.data(), portdev.size()};
list.emplace_back(DeviceEntry{name, name+":"});
list.emplace_back(name, name+":");
const auto &entry = list.back();
TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
}
@ -197,7 +202,7 @@ void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
if(ports[0] && list.empty())
{
WARN("No device names found in available ports, adding a generic name.\n");
list.emplace_back(DeviceEntry{"JACK", ""});
list.emplace_back("JACK", "");
}
}
@ -238,8 +243,8 @@ void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
else
{
/* Otherwise, add a new device entry. */
list.emplace_back(DeviceEntry{std::string{name.data(), name.size()},
std::string{pattern.data(), pattern.size()}});
list.emplace_back(std::string{name.data(), name.size()},
std::string{pattern.data(), pattern.size()});
const auto &entry = list.back();
TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
}
@ -340,7 +345,7 @@ int JackPlayback::processRt(jack_nframes_t numframes) noexcept
out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
}
if LIKELY(mPlaying.load(std::memory_order_acquire))
if(mPlaying.load(std::memory_order_acquire)) LIKELY
mDevice->renderSamples({out.data(), numchans}, static_cast<uint>(numframes));
else
{
@ -364,7 +369,7 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept
}
jack_nframes_t total{0};
if LIKELY(mPlaying.load(std::memory_order_acquire))
if(mPlaying.load(std::memory_order_acquire)) LIKELY
{
auto data = mRing->getReadVector();
jack_nframes_t todo{minu(numframes, static_cast<uint>(data.first.len))};
@ -492,7 +497,7 @@ void JackPlayback::open(const char *name)
mPortPattern = iter->mPattern;
}
mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", 1);
mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", true);
jack_set_process_callback(mClient,
mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this);
@ -669,7 +674,7 @@ bool JackBackendFactory::init()
if(!jack_load())
return false;
if(!GetConfigValueBool(nullptr, "jack", "spawn-server", 0))
if(!GetConfigValueBool(nullptr, "jack", "spawn-server", false))
ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer);
const PathNamePair &binname = GetProcBinary();

View file

@ -10,6 +10,7 @@
#include "alnumeric.h"
#include "core/device.h"
#include "core/logging.h"
#include "ringbuffer.h"
#include "oboe/Oboe.h"
@ -80,7 +81,10 @@ bool OboePlayback::reset()
builder.setCallback(this);
if(mDevice->Flags.test(FrequencyRequest))
{
builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High);
builder.setSampleRate(static_cast<int32_t>(mDevice->Frequency));
}
if(mDevice->Flags.test(ChannelsRequest))
{
/* Only use mono or stereo at user request. There's no telling what
@ -103,6 +107,10 @@ bool OboePlayback::reset()
break;
case DevFmtInt:
case DevFmtUInt:
#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
format = oboe::AudioFormat::I32;
break;
#endif
case DevFmtFloat:
format = oboe::AudioFormat::Float;
break;
@ -151,6 +159,12 @@ bool OboePlayback::reset()
case oboe::AudioFormat::Float:
mDevice->FmtType = DevFmtFloat;
break;
#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
case oboe::AudioFormat::I32:
mDevice->FmtType = DevFmtInt;
break;
case oboe::AudioFormat::I24:
#endif
case oboe::AudioFormat::Unspecified:
case oboe::AudioFormat::Invalid:
throw al::backend_exception{al::backend_error::DeviceError,
@ -188,13 +202,15 @@ void OboePlayback::stop()
}
struct OboeCapture final : public BackendBase {
struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback {
OboeCapture(DeviceBase *device) : BackendBase{device} { }
oboe::ManagedStream mStream;
std::vector<al::byte> mSamples;
uint mLastAvail{0u};
RingBufferPtr mRing{nullptr};
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
int32_t numFrames) override;
void open(const char *name) override;
void start() override;
@ -203,6 +219,14 @@ struct OboeCapture final : public BackendBase {
uint availableSamples() override;
};
oboe::DataCallbackResult OboeCapture::onAudioReady(oboe::AudioStream*, void *audioData,
int32_t numFrames)
{
mRing->write(audioData, static_cast<uint32_t>(numFrames));
return oboe::DataCallbackResult::Continue;
}
void OboeCapture::open(const char *name)
{
if(!name)
@ -217,8 +241,8 @@ void OboeCapture::open(const char *name)
->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High)
->setChannelConversionAllowed(true)
->setFormatConversionAllowed(true)
->setBufferCapacityInFrames(static_cast<int32_t>(mDevice->BufferSize))
->setSampleRate(static_cast<int32_t>(mDevice->Frequency));
->setSampleRate(static_cast<int32_t>(mDevice->Frequency))
->setCallback(this);
/* Only use mono or stereo at user request. There's no telling what
* other counts may be inferred as.
*/
@ -234,13 +258,15 @@ void OboeCapture::open(const char *name)
case DevFmtX51:
case DevFmtX61:
case DevFmtX71:
case DevFmtX714:
case DevFmtX3D71:
case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
DevFmtChannelsString(mDevice->FmtChans)};
}
/* FIXME: This really should support UByte, but Oboe doesn't. We'll need to
* use a temp buffer and convert.
* convert.
*/
switch(mDevice->FmtType)
{
@ -250,10 +276,14 @@ void OboeCapture::open(const char *name)
case DevFmtFloat:
builder.setFormat(oboe::AudioFormat::Float);
break;
case DevFmtInt:
#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
builder.setFormat(oboe::AudioFormat::I32);
break;
#endif
case DevFmtByte:
case DevFmtUByte:
case DevFmtUShort:
case DevFmtInt:
case DevFmtUInt:
throw al::backend_exception{al::backend_error::DeviceError,
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
@ -263,22 +293,13 @@ void OboeCapture::open(const char *name)
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
oboe::convertToText(result)};
if(static_cast<int32_t>(mDevice->BufferSize) > mStream->getBufferCapacityInFrames())
throw al::backend_exception{al::backend_error::DeviceError,
"Buffer size too large (%u > %d)", mDevice->BufferSize,
mStream->getBufferCapacityInFrames()};
auto buffer_result = mStream->setBufferSizeInFrames(static_cast<int32_t>(mDevice->BufferSize));
if(!buffer_result)
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set buffer size: %s", oboe::convertToText(buffer_result.error())};
else if(buffer_result.value() < static_cast<int32_t>(mDevice->BufferSize))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set large enough buffer size (%u > %d)", mDevice->BufferSize,
buffer_result.value()};
mDevice->BufferSize = static_cast<uint>(buffer_result.value());
TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
/* Ensure a minimum ringbuffer size of 100ms. */
mRing = RingBuffer::Create(maxu(mDevice->BufferSize, mDevice->Frequency/10),
static_cast<uint32_t>(mStream->getBytesPerFrame()), false);
mDevice->DeviceName = name;
}
@ -292,23 +313,6 @@ void OboeCapture::start()
void OboeCapture::stop()
{
/* Capture any unread samples before stopping. Oboe drops whatever's left
* in the stream.
*/
if(auto availres = mStream->getAvailableFrames())
{
const auto avail = std::max(static_cast<uint>(availres.value()), mLastAvail);
const size_t frame_size{static_cast<uint32_t>(mStream->getBytesPerFrame())};
const size_t pos{mSamples.size()};
mSamples.resize(pos + avail*frame_size);
auto result = mStream->read(&mSamples[pos], availres.value(), 0);
uint got{bool{result} ? static_cast<uint>(result.value()) : 0u};
if(got < avail)
std::fill_n(&mSamples[pos + got*frame_size], (avail-got)*frame_size, al::byte{});
mLastAvail = 0;
}
const oboe::Result result{mStream->stop()};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
@ -316,38 +320,10 @@ void OboeCapture::stop()
}
uint OboeCapture::availableSamples()
{
/* Keep track of the max available frame count, to ensure it doesn't go
* backwards.
*/
if(auto result = mStream->getAvailableFrames())
mLastAvail = std::max(static_cast<uint>(result.value()), mLastAvail);
const auto frame_size = static_cast<uint32_t>(mStream->getBytesPerFrame());
return static_cast<uint>(mSamples.size()/frame_size) + mLastAvail;
}
{ return static_cast<uint>(mRing->readSpace()); }
void OboeCapture::captureSamples(al::byte *buffer, uint samples)
{
const auto frame_size = static_cast<uint>(mStream->getBytesPerFrame());
if(const size_t storelen{mSamples.size()})
{
const auto instore = static_cast<uint>(storelen / frame_size);
const uint tocopy{std::min(samples, instore) * frame_size};
std::copy_n(mSamples.begin(), tocopy, buffer);
mSamples.erase(mSamples.begin(), mSamples.begin() + tocopy);
buffer += tocopy;
samples -= tocopy/frame_size;
if(!samples) return;
}
auto result = mStream->read(buffer, static_cast<int32_t>(samples), 0);
uint got{bool{result} ? static_cast<uint>(result.value()) : 0u};
if(got < samples)
std::fill_n(buffer + got*frame_size, (samples-got)*frame_size, al::byte{});
mLastAvail = std::max(mLastAvail, samples) - samples;
}
{ mRing->read(buffer, samples); }
} // namespace

View file

@ -71,9 +71,15 @@ constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept
case DevFmtX61: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_CENTER |
SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT;
case DevFmtX71: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
case DevFmtX71:
case DevFmtX3D71: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT |
SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT;
case DevFmtX714: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT |
SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT |
SL_SPEAKER_TOP_FRONT_LEFT | SL_SPEAKER_TOP_FRONT_RIGHT | SL_SPEAKER_TOP_BACK_LEFT |
SL_SPEAKER_TOP_BACK_RIGHT;
case DevFmtAmbi3D:
break;
}
@ -107,7 +113,7 @@ constexpr SLuint32 GetByteOrderEndianness() noexcept
return SL_BYTEORDER_BIGENDIAN;
}
const char *res_str(SLresult result) noexcept
constexpr const char *res_str(SLresult result) noexcept
{
switch(result)
{
@ -141,10 +147,11 @@ const char *res_str(SLresult result) noexcept
return "Unknown error code";
}
#define PRINTERR(x, s) do { \
if UNLIKELY((x) != SL_RESULT_SUCCESS) \
ERR("%s: %s\n", (s), res_str((x))); \
} while(0)
inline void PrintErr(SLresult res, const char *str)
{
if(res != SL_RESULT_SUCCESS) UNLIKELY
ERR("%s: %s\n", str, res_str(res));
}
struct OpenSLPlayback final : public BackendBase {
@ -228,11 +235,11 @@ int OpenSLPlayback::mixerProc()
SLAndroidSimpleBufferQueueItf bufferQueue;
SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&bufferQueue)};
PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
PrintErr(result, "bufferQueue->GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player);
PRINTERR(result, "bufferQueue->GetInterface SL_IID_PLAY");
PrintErr(result, "bufferQueue->GetInterface SL_IID_PLAY");
}
const size_t frame_step{mDevice->channelsFromFmt()};
@ -248,11 +255,11 @@ int OpenSLPlayback::mixerProc()
SLuint32 state{0};
result = VCALL(player,GetPlayState)(&state);
PRINTERR(result, "player->GetPlayState");
PrintErr(result, "player->GetPlayState");
if(SL_RESULT_SUCCESS == result && state != SL_PLAYSTATE_PLAYING)
{
result = VCALL(player,SetPlayState)(SL_PLAYSTATE_PLAYING);
PRINTERR(result, "player->SetPlayState");
PrintErr(result, "player->SetPlayState");
}
if(SL_RESULT_SUCCESS != result)
{
@ -289,7 +296,7 @@ int OpenSLPlayback::mixerProc()
}
result = VCALL(bufferQueue,Enqueue)(data.first.buf, mDevice->UpdateSize*mFrameSize);
PRINTERR(result, "bufferQueue->Enqueue");
PrintErr(result, "bufferQueue->Enqueue");
if(SL_RESULT_SUCCESS != result)
{
mDevice->handleDisconnect("Failed to queue audio: 0x%08x", result);
@ -318,26 +325,26 @@ void OpenSLPlayback::open(const char *name)
// create engine
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
PRINTERR(result, "slCreateEngine");
PrintErr(result, "slCreateEngine");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE);
PRINTERR(result, "engine->Realize");
PrintErr(result, "engine->Realize");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine);
PRINTERR(result, "engine->GetInterface");
PrintErr(result, "engine->GetInterface");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mEngine,CreateOutputMix)(&mOutputMix, 0, nullptr, nullptr);
PRINTERR(result, "engine->CreateOutputMix");
PrintErr(result, "engine->CreateOutputMix");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mOutputMix,Realize)(SL_BOOLEAN_FALSE);
PRINTERR(result, "outputMix->Realize");
PrintErr(result, "outputMix->Realize");
}
if(SL_RESULT_SUCCESS != result)
@ -505,20 +512,20 @@ bool OpenSLPlayback::reset()
result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(),
ids.data(), reqs.data());
PRINTERR(result, "engine->CreateAudioPlayer");
PrintErr(result, "engine->CreateAudioPlayer");
}
if(SL_RESULT_SUCCESS == result)
{
/* Set the stream type to "media" (games, music, etc), if possible. */
SLAndroidConfigurationItf config;
result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config);
PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDCONFIGURATION");
PrintErr(result, "bufferQueue->GetInterface SL_IID_ANDROIDCONFIGURATION");
if(SL_RESULT_SUCCESS == result)
{
SLint32 streamType = SL_ANDROID_STREAM_MEDIA;
result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_STREAM_TYPE, &streamType,
sizeof(streamType));
PRINTERR(result, "config->SetConfiguration");
PrintErr(result, "config->SetConfiguration");
}
/* Clear any error since this was optional. */
@ -527,7 +534,7 @@ bool OpenSLPlayback::reset()
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mBufferQueueObj,Realize)(SL_BOOLEAN_FALSE);
PRINTERR(result, "bufferQueue->Realize");
PrintErr(result, "bufferQueue->Realize");
}
if(SL_RESULT_SUCCESS == result)
{
@ -554,11 +561,11 @@ void OpenSLPlayback::start()
SLAndroidSimpleBufferQueueItf bufferQueue;
SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&bufferQueue)};
PRINTERR(result, "bufferQueue->GetInterface");
PrintErr(result, "bufferQueue->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this);
PRINTERR(result, "bufferQueue->RegisterCallback");
PrintErr(result, "bufferQueue->RegisterCallback");
}
if(SL_RESULT_SUCCESS != result)
throw al::backend_exception{al::backend_error::DeviceError,
@ -584,25 +591,25 @@ void OpenSLPlayback::stop()
SLPlayItf player;
SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player)};
PRINTERR(result, "bufferQueue->GetInterface");
PrintErr(result, "bufferQueue->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(player,SetPlayState)(SL_PLAYSTATE_STOPPED);
PRINTERR(result, "player->SetPlayState");
PrintErr(result, "player->SetPlayState");
}
SLAndroidSimpleBufferQueueItf bufferQueue;
result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue);
PRINTERR(result, "bufferQueue->GetInterface");
PrintErr(result, "bufferQueue->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL0(bufferQueue,Clear)();
PRINTERR(result, "bufferQueue->Clear");
PrintErr(result, "bufferQueue->Clear");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(bufferQueue,RegisterCallback)(nullptr, nullptr);
PRINTERR(result, "bufferQueue->RegisterCallback");
PrintErr(result, "bufferQueue->RegisterCallback");
}
if(SL_RESULT_SUCCESS == result)
{
@ -611,7 +618,9 @@ void OpenSLPlayback::stop()
std::this_thread::yield();
result = VCALL(bufferQueue,GetState)(&state);
} while(SL_RESULT_SUCCESS == result && state.count > 0);
PRINTERR(result, "bufferQueue->GetState");
PrintErr(result, "bufferQueue->GetState");
mRing->reset();
}
}
@ -686,16 +695,16 @@ void OpenSLCapture::open(const char* name)
name};
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
PRINTERR(result, "slCreateEngine");
PrintErr(result, "slCreateEngine");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE);
PRINTERR(result, "engine->Realize");
PrintErr(result, "engine->Realize");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine);
PRINTERR(result, "engine->GetInterface");
PrintErr(result, "engine->GetInterface");
}
if(SL_RESULT_SUCCESS == result)
{
@ -770,7 +779,7 @@ void OpenSLCapture::open(const char* name)
result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk,
ids.size(), ids.data(), reqs.data());
}
PRINTERR(result, "engine->CreateAudioRecorder");
PrintErr(result, "engine->CreateAudioRecorder");
}
}
if(SL_RESULT_SUCCESS == result)
@ -778,13 +787,13 @@ void OpenSLCapture::open(const char* name)
/* Set the record preset to "generic", if possible. */
SLAndroidConfigurationItf config;
result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config);
PRINTERR(result, "recordObj->GetInterface SL_IID_ANDROIDCONFIGURATION");
PrintErr(result, "recordObj->GetInterface SL_IID_ANDROIDCONFIGURATION");
if(SL_RESULT_SUCCESS == result)
{
SLuint32 preset = SL_ANDROID_RECORDING_PRESET_GENERIC;
result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_RECORDING_PRESET, &preset,
sizeof(preset));
PRINTERR(result, "config->SetConfiguration");
PrintErr(result, "config->SetConfiguration");
}
/* Clear any error since this was optional. */
@ -793,19 +802,19 @@ void OpenSLCapture::open(const char* name)
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mRecordObj,Realize)(SL_BOOLEAN_FALSE);
PRINTERR(result, "recordObj->Realize");
PrintErr(result, "recordObj->Realize");
}
SLAndroidSimpleBufferQueueItf bufferQueue;
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue);
PRINTERR(result, "recordObj->GetInterface");
PrintErr(result, "recordObj->GetInterface");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(bufferQueue,RegisterCallback)(&OpenSLCapture::processC, this);
PRINTERR(result, "bufferQueue->RegisterCallback");
PrintErr(result, "bufferQueue->RegisterCallback");
}
if(SL_RESULT_SUCCESS == result)
{
@ -818,12 +827,12 @@ void OpenSLCapture::open(const char* name)
for(size_t i{0u};i < data.first.len && SL_RESULT_SUCCESS == result;i++)
{
result = VCALL(bufferQueue,Enqueue)(data.first.buf + chunk_size*i, chunk_size);
PRINTERR(result, "bufferQueue->Enqueue");
PrintErr(result, "bufferQueue->Enqueue");
}
for(size_t i{0u};i < data.second.len && SL_RESULT_SUCCESS == result;i++)
{
result = VCALL(bufferQueue,Enqueue)(data.second.buf + chunk_size*i, chunk_size);
PRINTERR(result, "bufferQueue->Enqueue");
PrintErr(result, "bufferQueue->Enqueue");
}
}
@ -849,12 +858,12 @@ void OpenSLCapture::start()
{
SLRecordItf record;
SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)};
PRINTERR(result, "recordObj->GetInterface");
PrintErr(result, "recordObj->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(record,SetRecordState)(SL_RECORDSTATE_RECORDING);
PRINTERR(result, "record->SetRecordState");
PrintErr(result, "record->SetRecordState");
}
if(SL_RESULT_SUCCESS != result)
throw al::backend_exception{al::backend_error::DeviceError,
@ -865,12 +874,12 @@ void OpenSLCapture::stop()
{
SLRecordItf record;
SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)};
PRINTERR(result, "recordObj->GetInterface");
PrintErr(result, "recordObj->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(record,SetRecordState)(SL_RECORDSTATE_PAUSED);
PRINTERR(result, "record->SetRecordState");
PrintErr(result, "record->SetRecordState");
}
}
@ -878,7 +887,6 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
{
const uint update_size{mDevice->UpdateSize};
const uint chunk_size{update_size * mFrameSize};
const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::byte{0};
/* Read the desired samples from the ring buffer then advance its read
* pointer.
@ -907,39 +915,52 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
i += rem;
}
mRing->readAdvance(adv_count);
SLAndroidSimpleBufferQueueItf bufferQueue{};
if LIKELY(mDevice->Connected.load(std::memory_order_acquire))
if(mDevice->Connected.load(std::memory_order_acquire)) LIKELY
{
const SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&bufferQueue)};
PRINTERR(result, "recordObj->GetInterface");
if UNLIKELY(SL_RESULT_SUCCESS != result)
PrintErr(result, "recordObj->GetInterface");
if(SL_RESULT_SUCCESS != result) UNLIKELY
{
mDevice->handleDisconnect("Failed to get capture buffer queue: 0x%08x", result);
bufferQueue = nullptr;
}
}
if(!bufferQueue || adv_count == 0)
return;
if LIKELY(bufferQueue)
/* For each buffer chunk that was fully read, queue another writable buffer
* chunk to keep the OpenSL queue full. This is rather convulated, as a
* result of the ring buffer holding more elements than are writable at a
* given time. The end of the write vector increments when the read pointer
* advances, which will "expose" a previously unwritable element. So for
* every element that we've finished reading, we queue that many elements
* from the end of the write vector.
*/
mRing->readAdvance(adv_count);
SLresult result{SL_RESULT_SUCCESS};
auto wdata = mRing->getWriteVector();
if(adv_count > wdata.second.len) LIKELY
{
SLresult result{SL_RESULT_SUCCESS};
auto wdata = mRing->getWriteVector();
std::fill_n(wdata.first.buf, wdata.first.len*chunk_size, silence);
for(size_t i{0u};i < wdata.first.len && SL_RESULT_SUCCESS == result;i++)
auto len1 = std::min(wdata.first.len, adv_count-wdata.second.len);
auto buf1 = wdata.first.buf + chunk_size*(wdata.first.len-len1);
for(size_t i{0u};i < len1 && SL_RESULT_SUCCESS == result;i++)
{
result = VCALL(bufferQueue,Enqueue)(wdata.first.buf + chunk_size*i, chunk_size);
PRINTERR(result, "bufferQueue->Enqueue");
result = VCALL(bufferQueue,Enqueue)(buf1 + chunk_size*i, chunk_size);
PrintErr(result, "bufferQueue->Enqueue");
}
if(wdata.second.len > 0)
}
if(wdata.second.len > 0)
{
auto len2 = std::min(wdata.second.len, adv_count);
auto buf2 = wdata.second.buf + chunk_size*(wdata.second.len-len2);
for(size_t i{0u};i < len2 && SL_RESULT_SUCCESS == result;i++)
{
std::fill_n(wdata.second.buf, wdata.second.len*chunk_size, silence);
for(size_t i{0u};i < wdata.second.len && SL_RESULT_SUCCESS == result;i++)
{
result = VCALL(bufferQueue,Enqueue)(wdata.second.buf + chunk_size*i, chunk_size);
PRINTERR(result, "bufferQueue->Enqueue");
}
result = VCALL(bufferQueue,Enqueue)(buf2 + chunk_size*i, chunk_size);
PrintErr(result, "bufferQueue->Enqueue");
}
}
}

View file

@ -32,6 +32,7 @@
#include <memory>
#include <mutex>
#include <stdint.h>
#include <thread>
#include <type_traits>
#include <utility>
@ -50,8 +51,20 @@
#include "opthelpers.h"
#include "ringbuffer.h"
/* Ignore warnings caused by PipeWire headers (lots in standard C++ mode). */
/* Ignore warnings caused by PipeWire headers (lots in standard C++ mode). GCC
* doesn't support ignoring -Weverything, so we have the list the individual
* warnings to ignore (and ignoring -Winline doesn't seem to work).
*/
_Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Wpedantic\"")
_Pragma("GCC diagnostic ignored \"-Wconversion\"")
_Pragma("GCC diagnostic ignored \"-Wfloat-conversion\"")
_Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"")
_Pragma("GCC diagnostic ignored \"-Wunused-parameter\"")
_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"")
_Pragma("GCC diagnostic ignored \"-Wsign-compare\"")
_Pragma("GCC diagnostic ignored \"-Winline\"")
_Pragma("GCC diagnostic ignored \"-Wpragmas\"")
_Pragma("GCC diagnostic ignored \"-Weverything\"")
#include "pipewire/pipewire.h"
#include "pipewire/extensions/metadata.h"
@ -107,7 +120,13 @@ _Pragma("GCC diagnostic pop")
namespace {
/* Added in 0.3.33, but we currently only require 0.3.23. */
#ifndef PW_KEY_NODE_RATE
#define PW_KEY_NODE_RATE "node.rate"
#endif
using std::chrono::seconds;
using std::chrono::milliseconds;
using std::chrono::nanoseconds;
using uint = unsigned int;
@ -115,12 +134,27 @@ constexpr char pwireDevice[] = "PipeWire Output";
constexpr char pwireInput[] = "PipeWire Input";
bool check_version(const char *version)
{
/* There doesn't seem to be a function to get the version as an integer, so
* instead we have to parse the string, which hopefully won't break in the
* future.
*/
int major{0}, minor{0}, revision{0};
int ret{sscanf(version, "%d.%d.%d", &major, &minor, &revision)};
if(ret == 3 && (major > PW_MAJOR || (major == PW_MAJOR && minor > PW_MINOR)
|| (major == PW_MAJOR && minor == PW_MINOR && revision >= PW_MICRO)))
return true;
return false;
}
#ifdef HAVE_DYNLOAD
#define PWIRE_FUNCS(MAGIC) \
MAGIC(pw_context_connect) \
MAGIC(pw_context_destroy) \
MAGIC(pw_context_new) \
MAGIC(pw_core_disconnect) \
MAGIC(pw_get_library_version) \
MAGIC(pw_init) \
MAGIC(pw_properties_free) \
MAGIC(pw_properties_new) \
@ -134,7 +168,6 @@ constexpr char pwireInput[] = "PipeWire Input";
MAGIC(pw_stream_dequeue_buffer) \
MAGIC(pw_stream_destroy) \
MAGIC(pw_stream_get_state) \
MAGIC(pw_stream_get_time) \
MAGIC(pw_stream_new) \
MAGIC(pw_stream_queue_buffer) \
MAGIC(pw_stream_set_active) \
@ -146,11 +179,19 @@ constexpr char pwireInput[] = "PipeWire Input";
MAGIC(pw_thread_loop_lock) \
MAGIC(pw_thread_loop_wait) \
MAGIC(pw_thread_loop_signal) \
MAGIC(pw_thread_loop_unlock) \
MAGIC(pw_thread_loop_unlock)
#if PW_CHECK_VERSION(0,3,50)
#define PWIRE_FUNCS2(MAGIC) \
MAGIC(pw_stream_get_time_n)
#else
#define PWIRE_FUNCS2(MAGIC) \
MAGIC(pw_stream_get_time)
#endif
void *pwire_handle;
#define MAKE_FUNC(f) decltype(f) * p##f;
PWIRE_FUNCS(MAKE_FUNC)
PWIRE_FUNCS2(MAKE_FUNC)
#undef MAKE_FUNC
bool pwire_load()
@ -173,6 +214,7 @@ bool pwire_load()
if(p##f == nullptr) missing_funcs += "\n" #f; \
} while(0);
PWIRE_FUNCS(LOAD_FUNC)
PWIRE_FUNCS2(LOAD_FUNC)
#undef LOAD_FUNC
if(!missing_funcs.empty())
@ -191,6 +233,7 @@ bool pwire_load()
#define pw_context_destroy ppw_context_destroy
#define pw_context_new ppw_context_new
#define pw_core_disconnect ppw_core_disconnect
#define pw_get_library_version ppw_get_library_version
#define pw_init ppw_init
#define pw_properties_free ppw_properties_free
#define pw_properties_new ppw_properties_new
@ -204,7 +247,6 @@ bool pwire_load()
#define pw_stream_dequeue_buffer ppw_stream_dequeue_buffer
#define pw_stream_destroy ppw_stream_destroy
#define pw_stream_get_state ppw_stream_get_state
#define pw_stream_get_time ppw_stream_get_time
#define pw_stream_new ppw_stream_new
#define pw_stream_queue_buffer ppw_stream_queue_buffer
#define pw_stream_set_active ppw_stream_set_active
@ -217,6 +259,12 @@ bool pwire_load()
#define pw_thread_loop_stop ppw_thread_loop_stop
#define pw_thread_loop_unlock ppw_thread_loop_unlock
#define pw_thread_loop_wait ppw_thread_loop_wait
#if PW_CHECK_VERSION(0,3,50)
#define pw_stream_get_time_n ppw_stream_get_time_n
#else
inline auto pw_stream_get_time_n(pw_stream *stream, pw_time *ptime, size_t /*size*/)
{ return ppw_stream_get_time(stream, ptime); }
#endif
#endif
#else
@ -319,7 +367,10 @@ using PwStreamPtr = std::unique_ptr<pw_stream,PwStreamDeleter>;
/* Enums for bitflags... again... *sigh* */
constexpr pw_stream_flags operator|(pw_stream_flags lhs, pw_stream_flags rhs) noexcept
{ return static_cast<pw_stream_flags>(lhs | std::underlying_type_t<pw_stream_flags>{rhs}); }
{ return static_cast<pw_stream_flags>(lhs | al::to_underlying(rhs)); }
constexpr pw_stream_flags& operator|=(pw_stream_flags &lhs, pw_stream_flags rhs) noexcept
{ lhs = lhs | rhs; return lhs; }
class ThreadMainloop {
pw_thread_loop *mLoop{};
@ -426,7 +477,7 @@ struct EventManager {
*/
void waitForInit()
{
if(unlikely(!mInitDone.load(std::memory_order_acquire)))
if(!mInitDone.load(std::memory_order_acquire)) UNLIKELY
{
MainloopUniqueLock plock{mLoop};
plock.wait([this](){ return mInitDone.load(std::memory_order_acquire); });
@ -503,10 +554,12 @@ enum class NodeType : unsigned char {
};
constexpr auto InvalidChannelConfig = DevFmtChannels(255);
struct DeviceNode {
uint32_t mId{};
uint64_t mSerial{};
std::string mName;
std::string mDevName;
uint32_t mId{};
NodeType mType{};
bool mIsHeadphones{};
bool mIs51Rear{};
@ -560,7 +613,7 @@ DeviceNode *DeviceNode::Find(uint32_t id)
{ return n.mId == id; };
auto match = std::find_if(sList.begin(), sList.end(), match_id);
if(match != sList.end()) return std::addressof(*match);
if(match != sList.end()) return al::to_address(match);
return nullptr;
}
@ -598,6 +651,10 @@ const spa_audio_channel MonoMap[]{
}, X71Map[]{
SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR
}, X714Map[]{
SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR,
SPA_AUDIO_CHANNEL_TFL, SPA_AUDIO_CHANNEL_TFR, SPA_AUDIO_CHANNEL_TRL, SPA_AUDIO_CHANNEL_TRR
};
/**
@ -640,8 +697,8 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept
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 %u sample rate: %d (range: %d -> %d)\n", mId, srates[0], srates[1],
srates[2]);
TRACE("Device ID %" PRIu64 " sample rate: %d (range: %d -> %d)\n", mSerial, srates[0],
srates[1], srates[2]);
mSampleRate = static_cast<uint>(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE));
return;
}
@ -662,7 +719,7 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept
others += ", ";
others += std::to_string(srates[i]);
}
TRACE("Device ID %u sample rate: %d (%s)\n", mId, srates[0], others.c_str());
TRACE("Device ID %" PRIu64 " sample rate: %d (%s)\n", mSerial, srates[0], others.c_str());
/* Pick the first rate listed that's within the allowed range (default
* rate if possible).
*/
@ -686,7 +743,7 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept
}
auto srates = get_pod_body<int32_t,1>(value);
TRACE("Device ID %u sample rate: %d\n", mId, srates[0]);
TRACE("Device ID %" PRIu64 " sample rate: %d\n", mSerial, srates[0]);
mSampleRate = static_cast<uint>(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE));
return;
}
@ -701,7 +758,9 @@ void DeviceNode::parsePositions(const spa_pod *value) noexcept
mIs51Rear = false;
if(MatchChannelMap(chanmap, X71Map))
if(MatchChannelMap(chanmap, X714Map))
mChannels = DevFmtX714;
else if(MatchChannelMap(chanmap, X71Map))
mChannels = DevFmtX71;
else if(MatchChannelMap(chanmap, X61Map))
mChannels = DevFmtX61;
@ -718,7 +777,7 @@ void DeviceNode::parsePositions(const spa_pod *value) noexcept
mChannels = DevFmtStereo;
else
mChannels = DevFmtMono;
TRACE("Device ID %u got %zu position%s for %s%s\n", mId, chanmap.size(),
TRACE("Device ID %" PRIu64 " got %zu position%s for %s%s\n", mSerial, chanmap.size(),
(chanmap.size()==1)?"":"s", DevFmtChannelsString(mChannels), mIs51Rear?"(rear)":"");
}
@ -734,8 +793,8 @@ void DeviceNode::parseChannelCount(const spa_pod *value) noexcept
mChannels = DevFmtStereo;
else if(*chancount >= 1)
mChannels = DevFmtMono;
TRACE("Device ID %u got %d channel%s for %s\n", mId, *chancount, (*chancount==1)?"":"s",
DevFmtChannelsString(mChannels));
TRACE("Device ID %" PRIu64 " got %d channel%s for %s\n", mSerial, *chancount,
(*chancount==1)?"":"s", DevFmtChannelsString(mChannels));
}
@ -743,6 +802,7 @@ constexpr char MonitorPrefix[]{"Monitor of "};
constexpr auto MonitorPrefixLen = al::size(MonitorPrefix) - 1;
constexpr char AudioSinkClass[]{"Audio/Sink"};
constexpr char AudioSourceClass[]{"Audio/Source"};
constexpr char AudioSourceVirtualClass[]{"Audio/Source/Virtual"};
constexpr char AudioDuplexClass[]{"Audio/Duplex"};
constexpr char StreamClass[]{"Stream/"};
@ -802,12 +862,13 @@ void NodeProxy::infoCallback(const pw_node_info *info)
{
/* Can this actually change? */
const char *media_class{spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS)};
if(unlikely(!media_class)) return;
if(!media_class) UNLIKELY return;
NodeType ntype{};
if(al::strcasecmp(media_class, AudioSinkClass) == 0)
ntype = NodeType::Sink;
else if(al::strcasecmp(media_class, AudioSourceClass) == 0)
else if(al::strcasecmp(media_class, AudioSourceClass) == 0
|| al::strcasecmp(media_class, AudioSourceVirtualClass) == 0)
ntype = NodeType::Source;
else if(al::strcasecmp(media_class, AudioDuplexClass) == 0)
ntype = NodeType::Duplex;
@ -823,12 +884,27 @@ void NodeProxy::infoCallback(const pw_node_info *info)
if(!nodeName || !*nodeName) nodeName = spa_dict_lookup(info->props, PW_KEY_NODE_NICK);
if(!nodeName || !*nodeName) nodeName = devName;
uint64_t serial_id{info->id};
#ifdef PW_KEY_OBJECT_SERIAL
if(const char *serial_str{spa_dict_lookup(info->props, PW_KEY_OBJECT_SERIAL)})
{
char *serial_end{};
serial_id = std::strtoull(serial_str, &serial_end, 0);
if(*serial_end != '\0' || errno == ERANGE)
{
ERR("Unexpected object serial: %s\n", serial_str);
serial_id = info->id;
}
}
#endif
const char *form_factor{spa_dict_lookup(info->props, PW_KEY_DEVICE_FORM_FACTOR)};
TRACE("Got %s device \"%s\"%s%s%s\n", AsString(ntype), devName ? devName : "(nil)",
form_factor?" (":"", form_factor?form_factor:"", form_factor?")":"");
TRACE(" \"%s\" = ID %u\n", nodeName ? nodeName : "(nil)", info->id);
TRACE(" \"%s\" = ID %" PRIu64 "\n", nodeName ? nodeName : "(nil)", serial_id);
DeviceNode &node = DeviceNode::Add(info->id);
node.mSerial = serial_id;
if(nodeName && *nodeName) node.mName = nodeName;
else node.mName = "PipeWire node #"+std::to_string(info->id);
node.mDevName = devName ? devName : "";
@ -843,7 +919,7 @@ void NodeProxy::paramCallback(int, uint32_t id, uint32_t, uint32_t, const spa_po
if(id == SPA_PARAM_EnumFormat)
{
DeviceNode *node{DeviceNode::Find(mId)};
if(unlikely(!node)) return;
if(!node) UNLIKELY return;
if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_rate)})
node->parseSampleRate(&prop->value);
@ -1048,6 +1124,7 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t
/* Specifically, audio sinks and sources (and duplexes). */
const bool isGood{al::strcasecmp(media_class, AudioSinkClass) == 0
|| al::strcasecmp(media_class, AudioSourceClass) == 0
|| al::strcasecmp(media_class, AudioSourceVirtualClass) == 0
|| al::strcasecmp(media_class, AudioDuplexClass) == 0};
if(!isGood)
{
@ -1181,6 +1258,8 @@ spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e u
break;
case DevFmtX61: map = X61Map; break;
case DevFmtX71: map = X71Map; break;
case DevFmtX714: map = X714Map; break;
case DevFmtX3D71: map = X71Map; break;
case DevFmtAmbi3D:
info.flags |= SPA_AUDIO_FLAG_UNPOSITIONED;
info.channels = device->channelsFromFmt();
@ -1215,7 +1294,7 @@ class PipeWirePlayback final : public BackendBase {
void stop() override;
ClockLatency getClockLatency() override;
uint32_t mTargetId{PwIdAny};
uint64_t mTargetId{PwIdAny};
nanoseconds mTimeBase{0};
ThreadMainloop mLoop;
PwContextPtr mContext;
@ -1265,34 +1344,44 @@ void PipeWirePlayback::ioChangedCallback(uint32_t id, void *area, uint32_t size)
void PipeWirePlayback::outputCallback()
{
pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())};
if(unlikely(!pw_buf)) return;
if(!pw_buf) UNLIKELY return;
const al::span<spa_data> datas{pw_buf->buffer->datas,
minu(mNumChannels, pw_buf->buffer->n_datas)};
#if PW_CHECK_VERSION(0,3,49)
/* In 0.3.49, pw_buffer::requested specifies the number of samples needed
* by the resampler/graph for this audio update.
*/
uint length{static_cast<uint>(pw_buf->requested)};
#else
/* In 0.3.48 and earlier, spa_io_rate_match::size apparently has the number
* of samples per update.
*/
uint length{mRateMatch ? mRateMatch->size : 0u};
#endif
/* If no length is specified, use the device's update size as a fallback. */
if(!length) UNLIKELY length = mDevice->UpdateSize;
/* For planar formats, each datas[] seems to contain one channel, so store
* the pointers in an array. Limit the render length in case the available
* buffer length in any one channel is smaller than we wanted (shouldn't
* be, but just in case).
*/
spa_data *datas{pw_buf->buffer->datas};
const size_t chancount{minu(mNumChannels, pw_buf->buffer->n_datas)};
/* TODO: How many samples should actually be written? 'maxsize' can be 16k
* samples, which is excessive (~341ms @ 48khz). SPA_IO_RateMatch contains
* a 'size' field that apparently indicates how many samples should be
* written per update, but it's not obviously right.
*/
uint length{mRateMatch ? mRateMatch->size : mDevice->UpdateSize};
for(size_t i{0};i < chancount;++i)
float **chanptr_end{mChannelPtrs.get()};
for(const auto &data : datas)
{
length = minu(length, datas[i].maxsize/sizeof(float));
mChannelPtrs[i] = static_cast<float*>(datas[i].data);
length = minu(length, data.maxsize/sizeof(float));
*chanptr_end = static_cast<float*>(data.data);
++chanptr_end;
}
mDevice->renderSamples({mChannelPtrs.get(), chancount}, length);
mDevice->renderSamples({mChannelPtrs.get(), chanptr_end}, length);
for(size_t i{0};i < chancount;++i)
for(const auto &data : datas)
{
datas[i].chunk->offset = 0;
datas[i].chunk->stride = sizeof(float);
datas[i].chunk->size = length * sizeof(float);
data.chunk->offset = 0;
data.chunk->stride = sizeof(float);
data.chunk->size = length * sizeof(float);
}
pw_buf->size = length;
pw_stream_queue_buffer(mStream.get(), pw_buf);
@ -1303,7 +1392,7 @@ void PipeWirePlayback::open(const char *name)
{
static std::atomic<uint> OpenCount{0};
uint32_t targetid{PwIdAny};
uint64_t targetid{PwIdAny};
std::string devname{};
gEventHandler.waitForInit();
if(!name)
@ -1328,7 +1417,7 @@ void PipeWirePlayback::open(const char *name)
"No PipeWire playback device found"};
}
targetid = match->mId;
targetid = match->mSerial;
devname = match->mName;
}
else
@ -1343,7 +1432,7 @@ void PipeWirePlayback::open(const char *name)
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
targetid = match->mId;
targetid = match->mSerial;
devname = match->mName;
}
@ -1408,7 +1497,7 @@ bool PipeWirePlayback::reset()
auto&& devlist = DeviceNode::GetList();
auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool
{ return targetid == n.mId; };
{ return targetid == n.mSerial; };
auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id);
if(match != devlist.cend())
{
@ -1416,10 +1505,11 @@ bool PipeWirePlayback::reset()
{
/* Scale the update size if the sample rate changes. */
const double scale{static_cast<double>(match->mSampleRate) / mDevice->Frequency};
const double numbufs{static_cast<double>(mDevice->BufferSize)/mDevice->UpdateSize};
mDevice->Frequency = match->mSampleRate;
mDevice->UpdateSize = static_cast<uint>(clampd(mDevice->UpdateSize*scale + 0.5,
64.0, 8192.0));
mDevice->BufferSize = mDevice->UpdateSize * 2;
mDevice->BufferSize = static_cast<uint>(numbufs*mDevice->UpdateSize + 0.5);
}
if(!mDevice->Flags.test(ChannelsRequest) && match->mChannels != InvalidChannelConfig)
mDevice->FmtChans = match->mChannels;
@ -1445,7 +1535,13 @@ bool PipeWirePlayback::reset()
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set PipeWire audio format parameters"};
pw_properties *props{pw_properties_new(
/* TODO: Which properties are actually needed here? Any others that could
* be useful?
*/
auto&& binary = GetProcBinary();
const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"};
pw_properties *props{pw_properties_new(PW_KEY_NODE_NAME, appname,
PW_KEY_NODE_DESCRIPTION, appname,
PW_KEY_MEDIA_TYPE, "Audio",
PW_KEY_MEDIA_CATEGORY, "Playback",
PW_KEY_MEDIA_ROLE, "Game",
@ -1455,16 +1551,14 @@ bool PipeWirePlayback::reset()
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to create PipeWire stream properties (errno: %d)", errno};
auto&& binary = GetProcBinary();
const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"};
/* TODO: Which properties are actually needed here? Any others that could
* be useful?
*/
pw_properties_set(props, PW_KEY_NODE_NAME, appname);
pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, appname);
pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", mDevice->UpdateSize,
mDevice->Frequency);
pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency);
#ifdef PW_KEY_TARGET_OBJECT
pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId);
#else
pw_properties_setf(props, PW_KEY_NODE_TARGET, "%" PRIu64, mTargetId);
#endif
MainloopUniqueLock plock{mLoop};
/* The stream takes overship of 'props', even in the case of failure. */
@ -1475,28 +1569,31 @@ bool PipeWirePlayback::reset()
static constexpr pw_stream_events streamEvents{CreateEvents()};
pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this);
constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE
| PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS};
if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_OUTPUT, mTargetId, Flags, &params, 1)})
pw_stream_flags flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE
| PW_STREAM_FLAG_MAP_BUFFERS};
if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pipewire", "rt-mix", true))
flags |= PW_STREAM_FLAG_RT_PROCESS;
if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_OUTPUT, PwIdAny, flags, &params, 1)})
throw al::backend_exception{al::backend_error::DeviceError,
"Error connecting PipeWire stream (res: %d)", res};
/* Wait for the stream to become paused (ready to start streaming). */
pw_stream_state state{};
const char *error{};
plock.wait([stream=mStream.get(),&state,&error]()
plock.wait([stream=mStream.get()]()
{
state = pw_stream_get_state(stream, &error);
const char *error{};
pw_stream_state state{pw_stream_get_state(stream, &error)};
if(state == PW_STREAM_STATE_ERROR)
throw al::backend_exception{al::backend_error::DeviceError,
"Error connecting PipeWire stream: \"%s\"", error};
return state == PW_STREAM_STATE_PAUSED;
});
/* TODO: Update mDevice->BufferSize with the total known buffering delay
* from the head of this playback stream to the tail of the device output.
/* TODO: Update mDevice->UpdateSize with the stream's quantum, and
* mDevice->BufferSize with the total known buffering delay from the head
* of this playback stream to the tail of the device output.
*
* This info is apparently not available until after the stream starts.
*/
mDevice->BufferSize = mDevice->UpdateSize * 2;
plock.unlock();
mNumChannels = mDevice->channelsFromFmt();
@ -1517,22 +1614,72 @@ void PipeWirePlayback::start()
/* Wait for the stream to start playing (would be nice to not, but we need
* the actual update size which is only available after starting).
*/
pw_stream_state state{};
const char *error{};
plock.wait([stream=mStream.get(),&state,&error]()
plock.wait([stream=mStream.get()]()
{
state = pw_stream_get_state(stream, &error);
return state != PW_STREAM_STATE_PAUSED;
const char *error{};
pw_stream_state state{pw_stream_get_state(stream, &error)};
if(state == PW_STREAM_STATE_ERROR)
throw al::backend_exception{al::backend_error::DeviceError,
"PipeWire stream error: %s", error ? error : "(unknown)"};
return state == PW_STREAM_STATE_STREAMING;
});
if(state == PW_STREAM_STATE_ERROR)
throw al::backend_exception{al::backend_error::DeviceError,
"PipeWire stream error: %s", error ? error : "(unknown)"};
if(state == PW_STREAM_STATE_STREAMING && mRateMatch && mRateMatch->size)
{
mDevice->UpdateSize = mRateMatch->size;
mDevice->BufferSize = mDevice->UpdateSize * 2;
}
/* HACK: Try to work out the update size and total buffering size. There's
* no actual query for this, so we have to work it out from the stream time
* info, and assume it stays accurate with future updates. The stream time
* info may also not be available right away, so we have to wait until it
* is (up to about 2 seconds).
*/
int wait_count{100};
do {
pw_time ptime{};
if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))})
{
ERR("Failed to get PipeWire stream time (res: %d)\n", res);
break;
}
/* The rate match size is the update size for each buffer. */
const uint updatesize{mRateMatch ? mRateMatch->size : 0u};
#if PW_CHECK_VERSION(0,3,50)
/* Assume ptime.avail_buffers+ptime.queued_buffers is the target buffer
* queue size.
*/
if(ptime.rate.denom > 0 && (ptime.avail_buffers || ptime.queued_buffers) && updatesize > 0)
{
const uint totalbuffers{ptime.avail_buffers + ptime.queued_buffers};
/* Ensure the delay is in sample frames. */
const uint64_t delay{static_cast<uint64_t>(ptime.delay) * mDevice->Frequency *
ptime.rate.num / ptime.rate.denom};
mDevice->UpdateSize = updatesize;
mDevice->BufferSize = static_cast<uint>(ptime.buffered + delay +
totalbuffers*updatesize);
break;
}
#else
/* Prior to 0.3.50, we can only measure the delay with the update size,
* assuming one buffer and no resample buffering.
*/
if(ptime.rate.denom > 0 && updatesize > 0)
{
/* Ensure the delay is in sample frames. */
const uint64_t delay{static_cast<uint64_t>(ptime.delay) * mDevice->Frequency *
ptime.rate.num / ptime.rate.denom};
mDevice->UpdateSize = updatesize;
mDevice->BufferSize = static_cast<uint>(delay + updatesize);
break;
}
#endif
if(!--wait_count)
break;
plock.unlock();
std::this_thread::sleep_for(milliseconds{20});
plock.lock();
} while(pw_stream_get_state(mStream.get(), nullptr) == PW_STREAM_STATE_STREAMING);
}
void PipeWirePlayback::stop()
@ -1560,7 +1707,7 @@ ClockLatency PipeWirePlayback::getClockLatency()
if(mStream)
{
MainloopLockGuard _{mLoop};
if(int res{pw_stream_get_time(mStream.get(), &ptime)})
if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))})
ERR("Failed to get PipeWire stream time (res: %d)\n", res);
}
@ -1582,7 +1729,7 @@ ClockLatency PipeWirePlayback::getClockLatency()
*/
nanoseconds monoclock{seconds{tspec.tv_sec} + nanoseconds{tspec.tv_nsec}};
nanoseconds curtic{}, delay{};
if(unlikely(ptime.rate.denom < 1))
if(ptime.rate.denom < 1) UNLIKELY
{
/* If there's no stream rate, the stream hasn't had a chance to get
* going and return time info yet. Just use dummy values.
@ -1648,7 +1795,7 @@ class PipeWireCapture final : public BackendBase {
void captureSamples(al::byte *buffer, uint samples) override;
uint availableSamples() override;
uint32_t mTargetId{PwIdAny};
uint64_t mTargetId{PwIdAny};
ThreadMainloop mLoop;
PwContextPtr mContext;
PwCorePtr mCore;
@ -1680,7 +1827,7 @@ void PipeWireCapture::stateChangedCallback(pw_stream_state, pw_stream_state, con
void PipeWireCapture::inputCallback()
{
pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())};
if(unlikely(!pw_buf)) return;
if(!pw_buf) UNLIKELY return;
spa_data *bufdata{pw_buf->buffer->datas};
const uint offset{minu(bufdata->chunk->offset, bufdata->maxsize)};
@ -1696,7 +1843,7 @@ void PipeWireCapture::open(const char *name)
{
static std::atomic<uint> OpenCount{0};
uint32_t targetid{PwIdAny};
uint64_t targetid{PwIdAny};
std::string devname{};
gEventHandler.waitForInit();
if(!name)
@ -1725,7 +1872,7 @@ void PipeWireCapture::open(const char *name)
"No PipeWire capture device found"};
}
targetid = match->mId;
targetid = match->mSerial;
if(match->mType != NodeType::Sink) devname = match->mName;
else devname = MonitorPrefix+match->mName;
}
@ -1748,7 +1895,7 @@ void PipeWireCapture::open(const char *name)
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
targetid = match->mId;
targetid = match->mSerial;
devname = name;
}
@ -1798,7 +1945,7 @@ void PipeWireCapture::open(const char *name)
auto&& devlist = DeviceNode::GetList();
auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool
{ return targetid == n.mId; };
{ return targetid == n.mSerial; };
auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id);
if(match != devlist.cend())
is51rear = match->mIs51Rear;
@ -1814,7 +1961,11 @@ void PipeWireCapture::open(const char *name)
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set PipeWire audio format parameters"};
auto&& binary = GetProcBinary();
const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"};
pw_properties *props{pw_properties_new(
PW_KEY_NODE_NAME, appname,
PW_KEY_NODE_DESCRIPTION, appname,
PW_KEY_MEDIA_TYPE, "Audio",
PW_KEY_MEDIA_CATEGORY, "Capture",
PW_KEY_MEDIA_ROLE, "Game",
@ -1824,10 +1975,6 @@ void PipeWireCapture::open(const char *name)
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to create PipeWire stream properties (errno: %d)", errno};
auto&& binary = GetProcBinary();
const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"};
pw_properties_set(props, PW_KEY_NODE_NAME, appname);
pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, appname);
/* We don't actually care what the latency/update size is, as long as it's
* reasonable. Unfortunately, when unspecified PipeWire seems to default to
* around 40ms, which isn't great. So request 20ms instead.
@ -1835,6 +1982,11 @@ void PipeWireCapture::open(const char *name)
pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", (mDevice->Frequency+25) / 50,
mDevice->Frequency);
pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency);
#ifdef PW_KEY_TARGET_OBJECT
pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId);
#else
pw_properties_setf(props, PW_KEY_NODE_TARGET, "%" PRIu64, mTargetId);
#endif
MainloopUniqueLock plock{mLoop};
mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Capture Stream", props)};
@ -1846,16 +1998,15 @@ void PipeWireCapture::open(const char *name)
constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE
| PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS};
if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, mTargetId, Flags, params, 1)})
if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, PwIdAny, Flags, params, 1)})
throw al::backend_exception{al::backend_error::DeviceError,
"Error connecting PipeWire stream (res: %d)", res};
/* Wait for the stream to become paused (ready to start streaming). */
pw_stream_state state{};
const char *error{};
plock.wait([stream=mStream.get(),&state,&error]()
plock.wait([stream=mStream.get()]()
{
state = pw_stream_get_state(stream, &error);
const char *error{};
pw_stream_state state{pw_stream_get_state(stream, &error)};
if(state == PW_STREAM_STATE_ERROR)
throw al::backend_exception{al::backend_error::DeviceError,
"Error connecting PipeWire stream: \"%s\"", error};
@ -1878,17 +2029,15 @@ void PipeWireCapture::start()
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start PipeWire stream (res: %d)", res};
pw_stream_state state{};
const char *error{};
plock.wait([stream=mStream.get(),&state,&error]()
plock.wait([stream=mStream.get()]()
{
state = pw_stream_get_state(stream, &error);
return state != PW_STREAM_STATE_PAUSED;
const char *error{};
pw_stream_state state{pw_stream_get_state(stream, &error)};
if(state == PW_STREAM_STATE_ERROR)
throw al::backend_exception{al::backend_error::DeviceError,
"PipeWire stream error: %s", error ? error : "(unknown)"};
return state == PW_STREAM_STATE_STREAMING;
});
if(state == PW_STREAM_STATE_ERROR)
throw al::backend_exception{al::backend_error::DeviceError,
"PipeWire stream error: %s", error ? error : "(unknown)"};
}
void PipeWireCapture::stop()
@ -1916,6 +2065,15 @@ bool PipeWireBackendFactory::init()
if(!pwire_load())
return false;
const char *version{pw_get_library_version()};
if(!check_version(version))
{
WARN("PipeWire version \"%s\" too old (%s or newer required)\n", version,
pw_get_headers_version());
return false;
}
TRACE("Found PipeWire version \"%s\" (%s or newer)\n", version, pw_get_headers_version());
pw_init(0, nullptr);
if(!gEventHandler.init())
return false;

File diff suppressed because it is too large Load diff

View file

@ -32,7 +32,10 @@
#include "core/device.h"
#include "core/logging.h"
#include <SDL2/SDL.h>
_Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"")
#include "SDL.h"
_Pragma("GCC diagnostic pop")
namespace {

View file

@ -22,13 +22,13 @@
#include "sndio.h"
#include <functional>
#include <inttypes.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <thread>
#include <functional>
#include "alnumeric.h"
#include "core/device.h"
@ -98,9 +98,9 @@ int SndioPlayback::mixerProc()
while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire))
{
size_t wrote{sio_write(mSndHandle, buffer.data(), buffer.size())};
if(wrote == 0)
if(wrote > buffer.size() || wrote == 0)
{
ERR("sio_write failed\n");
ERR("sio_write failed: 0x%" PRIx64 "\n", wrote);
mDevice->handleDisconnect("Failed to write playback samples");
break;
}
@ -353,7 +353,14 @@ int SndioCapture::recordProc()
while(!buffer.empty())
{
size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())};
if(got == 0) break;
if(got == 0)
break;
if(got > buffer.size())
{
ERR("sio_read failed: 0x%" PRIx64 "\n", got);
mDevice->handleDisconnect("sio_read failed: 0x%" PRIx64, got);
break;
}
mRing->writeAdvance(got / frameSize);
buffer = buffer.subspan(got);

View file

@ -57,6 +57,7 @@
#include <vector>
#include "albit.h"
#include "alc/alconfig.h"
#include "alnumeric.h"
#include "comptr.h"
#include "core/converter.h"
@ -86,6 +87,7 @@ DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x
namespace {
using std::chrono::nanoseconds;
using std::chrono::milliseconds;
using std::chrono::seconds;
@ -102,6 +104,7 @@ inline constexpr ReferenceTime operator "" _reftime(unsigned long long int n) no
#define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
#define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
#define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
#define X7DOT1DOT4 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT|SPEAKER_TOP_FRONT_LEFT|SPEAKER_TOP_FRONT_RIGHT|SPEAKER_TOP_BACK_LEFT|SPEAKER_TOP_BACK_RIGHT)
constexpr inline DWORD MaskFromTopBits(DWORD b) noexcept
{
@ -119,6 +122,7 @@ constexpr DWORD X51Mask{MaskFromTopBits(X5DOT1)};
constexpr DWORD X51RearMask{MaskFromTopBits(X5DOT1REAR)};
constexpr DWORD X61Mask{MaskFromTopBits(X6DOT1)};
constexpr DWORD X71Mask{MaskFromTopBits(X7DOT1)};
constexpr DWORD X714Mask{MaskFromTopBits(X7DOT1DOT4)};
constexpr char DevNameHead[] = "OpenAL Soft on ";
constexpr size_t DevNameHeadLen{al::size(DevNameHead) - 1};
@ -422,7 +426,6 @@ void TraceFormat(const char *msg, const WAVEFORMATEX *format)
enum class MsgType {
OpenDevice,
ReopenDevice,
ResetDevice,
StartDevice,
StopDevice,
@ -436,7 +439,6 @@ enum class MsgType {
constexpr char MessageStr[static_cast<size_t>(MsgType::Count)][20]{
"Open Device",
"Reopen Device",
"Reset Device",
"Start Device",
"Stop Device",
@ -465,9 +467,12 @@ struct WasapiProxy {
explicit operator bool() const noexcept { return mType != MsgType::QuitThread; }
};
static std::thread sThread;
static std::deque<Msg> mMsgQueue;
static std::mutex mMsgQueueLock;
static std::condition_variable mMsgQueueCond;
static std::mutex sThreadLock;
static size_t sInitCount;
std::future<HRESULT> pushMessage(MsgType type, const char *param=nullptr)
{
@ -503,42 +508,60 @@ 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 cohr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)};
if(FAILED(cohr))
{
WARN("Failed to initialize COM: 0x%08lx\n", cohr);
promise->set_value(cohr);
return 0;
}
void *ptr{};
HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
IID_IMMDeviceEnumerator, &ptr)};
HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)};
if(FAILED(hr))
{
WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr);
WARN("Failed to initialize COM: 0x%08lx\n", hr);
promise->set_value(hr);
CoUninitialize();
return 0;
}
static_cast<IMMDeviceEnumerator*>(ptr)->Release();
CoUninitialize();
TRACE("Message thread initialization complete\n");
promise->set_value(S_OK);
promise = nullptr;
TRACE("Starting message loop\n");
uint deviceCount{0};
while(Msg msg{popMessage()})
{
TRACE("Got message \"%s\" (0x%04x, this=%p, param=%p)\n",
@ -548,21 +571,6 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise)
switch(msg.mType)
{
case MsgType::OpenDevice:
hr = cohr = S_OK;
if(++deviceCount == 1)
hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if(SUCCEEDED(hr))
hr = msg.mProxy->openProxy(msg.mParam);
msg.mPromise.set_value(hr);
if(FAILED(hr))
{
if(--deviceCount == 0 && SUCCEEDED(cohr))
CoUninitialize();
}
continue;
case MsgType::ReopenDevice:
hr = msg.mProxy->openProxy(msg.mParam);
msg.mPromise.set_value(hr);
continue;
@ -585,36 +593,29 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise)
case MsgType::CloseDevice:
msg.mProxy->closeProxy();
msg.mPromise.set_value(S_OK);
if(--deviceCount == 0)
CoUninitialize();
continue;
case MsgType::EnumeratePlayback:
case MsgType::EnumerateCapture:
hr = cohr = S_OK;
if(++deviceCount == 1)
hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if(SUCCEEDED(hr))
{
void *ptr{};
hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
IID_IMMDeviceEnumerator, &ptr);
if(FAILED(hr))
msg.mPromise.set_value(hr);
else
{
ComPtr<IMMDeviceEnumerator> enumerator{static_cast<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(enumerator.get(), eRender, PlaybackDevices);
else if(msg.mType == MsgType::EnumerateCapture)
probe_devices(enumerator.get(), eCapture, CaptureDevices);
msg.mPromise.set_value(S_OK);
if(msg.mType == MsgType::EnumeratePlayback)
probe_devices(devenum.get(), eRender, PlaybackDevices);
else if(msg.mType == MsgType::EnumerateCapture)
probe_devices(devenum.get(), eCapture, CaptureDevices);
msg.mPromise.set_value(S_OK);
}
continue;
}
if(--deviceCount == 0 && SUCCEEDED(cohr))
CoUninitialize();
continue;
case MsgType::QuitThread:
break;
}
@ -622,6 +623,7 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise)
msg.mPromise.set_value(E_FAIL);
}
TRACE("Message loop finished\n");
CoUninitialize();
return 0;
}
@ -652,7 +654,12 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy {
ComPtr<IAudioRenderClient> mRender{nullptr};
HANDLE mNotifyEvent{nullptr};
UINT32 mFrameStep{0u};
UINT32 mOrigBufferSize{}, mOrigUpdateSize{};
std::unique_ptr<char[]> mResampleBuffer{};
uint mBufferFilled{0};
SampleConverterPtr mResampler;
WAVEFORMATEXTENSIBLE mFormat{};
std::atomic<UINT32> mPadding{0u};
std::mutex mMutex;
@ -666,7 +673,10 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy {
WasapiPlayback::~WasapiPlayback()
{
if(SUCCEEDED(mOpenStatus))
{
pushMessage(MsgType::CloseDevice).wait();
DeinitThread();
}
mOpenStatus = E_FAIL;
if(mNotifyEvent != nullptr)
@ -688,8 +698,9 @@ FORCE_ALIGN int WasapiPlayback::mixerProc()
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
const uint update_size{mDevice->UpdateSize};
const UINT32 buffer_len{mDevice->BufferSize};
const uint frame_size{mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8u};
const uint update_size{mOrigUpdateSize};
const UINT32 buffer_len{mOrigBufferSize};
while(!mKillNow.load(std::memory_order_relaxed))
{
UINT32 written;
@ -715,9 +726,37 @@ FORCE_ALIGN int WasapiPlayback::mixerProc()
hr = mRender->GetBuffer(len, &buffer);
if(SUCCEEDED(hr))
{
if(mResampler)
{
std::lock_guard<std::mutex> _{mMutex};
mDevice->renderSamples(buffer, len, mFrameStep);
for(UINT32 done{0};done < len;)
{
if(mBufferFilled == 0)
{
mDevice->renderSamples(mResampleBuffer.get(), mDevice->UpdateSize,
mFormat.Format.nChannels);
mBufferFilled = mDevice->UpdateSize;
}
const void *src{mResampleBuffer.get()};
uint srclen{mBufferFilled};
uint got{mResampler->convert(&src, &srclen, buffer, len-done)};
buffer += got*frame_size;
done += got;
mPadding.store(written + done, std::memory_order_relaxed);
if(srclen)
{
const char *bsrc{static_cast<const char*>(src)};
std::copy(bsrc, bsrc + srclen*frame_size, mResampleBuffer.get());
}
mBufferFilled = srclen;
}
}
else
{
std::lock_guard<std::mutex> _{mMutex};
mDevice->renderSamples(buffer, len, mFormat.Format.nChannels);
mPadding.store(written + len, std::memory_order_relaxed);
}
hr = mRender->ReleaseBuffer(len, 0);
@ -738,44 +777,44 @@ FORCE_ALIGN int WasapiPlayback::mixerProc()
void WasapiPlayback::open(const char *name)
{
HRESULT hr{S_OK};
if(SUCCEEDED(mOpenStatus))
throw al::backend_exception{al::backend_error::DeviceError,
"Unexpected duplicate open call"};
if(!mNotifyEvent)
mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if(mNotifyEvent == nullptr)
{
mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if(mNotifyEvent == nullptr)
{
ERR("Failed to create notify events: %lu\n", GetLastError());
hr = E_FAIL;
}
}
if(SUCCEEDED(hr))
{
if(name)
{
if(PlaybackDevices.empty())
pushMessage(MsgType::EnumeratePlayback);
if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0)
{
name += DevNameHeadLen;
if(*name == '\0')
name = nullptr;
}
}
if(SUCCEEDED(mOpenStatus))
hr = pushMessage(MsgType::ReopenDevice, name).get();
else
{
hr = pushMessage(MsgType::OpenDevice, name).get();
mOpenStatus = hr;
}
ERR("Failed to create notify events: %lu\n", GetLastError());
throw al::backend_exception{al::backend_error::DeviceError,
"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(PlaybackDevices.empty())
pushMessage(MsgType::EnumeratePlayback);
if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0)
{
name += DevNameHeadLen;
if(*name == '\0')
name = nullptr;
}
}
mOpenStatus = pushMessage(MsgType::OpenDevice, name).get();
if(FAILED(mOpenStatus))
{
DeinitThread();
throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
hr};
mOpenStatus};
}
}
HRESULT WasapiPlayback::openProxy(const char *name)
@ -863,6 +902,7 @@ HRESULT WasapiPlayback::resetProxy()
ERR("Failed to get mix format: 0x%08lx\n", hr);
return hr;
}
TraceFormat("Device mix format", wfx);
WAVEFORMATEXTENSIBLE OutputType;
if(!MakeExtensible(&OutputType, wfx))
@ -875,29 +915,46 @@ HRESULT WasapiPlayback::resetProxy()
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))
mDevice->Frequency = OutputType.Format.nSamplesPerSec;
if(!mDevice->Flags.test(ChannelsRequest))
{
/* If not requesting a channel configuration, auto-select given what
* fits the mask's lsb (to ensure no gaps in the output channels). If
* there's no mask, we can only assume mono or stereo.
*/
const uint32_t chancount{OutputType.Format.nChannels};
const DWORD chanmask{OutputType.dwChannelMask};
if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4)
mDevice->FmtChans = DevFmtX71;
else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
mDevice->FmtChans = DevFmtX71;
else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
mDevice->FmtChans = DevFmtX61;
else if(chancount >= 6 && ((chanmask&X51Mask) == X5DOT1
|| (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)
else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask))
mDevice->FmtChans = DevFmtStereo;
else if(chancount >= 1 && (chanmask&MonoMask) == MONO)
else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask))
mDevice->FmtChans = DevFmtMono;
else
ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount, chanmask);
}
else
{
const uint32_t chancount{OutputType.Format.nChannels};
const DWORD chanmask{OutputType.dwChannelMask};
isRear51 = (chancount == 6 && (chanmask&X51RearMask) == X5DOT1REAR);
}
OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
switch(mDevice->FmtChans)
@ -919,16 +976,21 @@ HRESULT WasapiPlayback::resetProxy()
break;
case DevFmtX51:
OutputType.Format.nChannels = 6;
OutputType.dwChannelMask = X5DOT1;
OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1;
break;
case DevFmtX61:
OutputType.Format.nChannels = 7;
OutputType.dwChannelMask = X6DOT1;
break;
case DevFmtX71:
case DevFmtX3D71:
OutputType.Format.nChannels = 8;
OutputType.dwChannelMask = X7DOT1;
break;
case DevFmtX714:
OutputType.Format.nChannels = 12;
OutputType.dwChannelMask = X7DOT1DOT4;
break;
}
switch(mDevice->FmtType)
{
@ -973,7 +1035,7 @@ HRESULT WasapiPlayback::resetProxy()
hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx);
if(FAILED(hr))
{
ERR("Failed to check format support: 0x%08lx\n", hr);
WARN("Failed to check format support: 0x%08lx\n", hr);
hr = mClient->GetMixFormat(&wfx);
}
if(FAILED(hr))
@ -993,7 +1055,11 @@ HRESULT WasapiPlayback::resetProxy()
CoTaskMemFree(wfx);
wfx = nullptr;
mDevice->Frequency = OutputType.Format.nSamplesPerSec;
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
@ -1002,34 +1068,43 @@ HRESULT WasapiPlayback::resetProxy()
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);
chansok = (chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask));
break;
case DevFmtStereo:
chansok = (chancount >= 2 && (chanmask&StereoMask) == STEREO);
chansok = (chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask));
break;
case DevFmtQuad:
chansok = (chancount >= 4 && (chanmask&QuadMask) == QUAD);
chansok = (chancount >= 4 && ((chanmask&QuadMask) == QUAD || !chanmask));
break;
case DevFmtX51:
chansok = (chancount >= 6 && ((chanmask&X51Mask) == X5DOT1
|| (chanmask&X51RearMask) == X5DOT1REAR));
|| (chanmask&X51RearMask) == X5DOT1REAR || !chanmask));
break;
case DevFmtX61:
chansok = (chancount >= 7 && (chanmask&X61Mask) == X6DOT1);
chansok = (chancount >= 7 && ((chanmask&X61Mask) == X6DOT1 || !chanmask));
break;
case DevFmtX71:
chansok = (chancount >= 8 && (chanmask&X71Mask) == X7DOT1);
case DevFmtX3D71:
chansok = (chancount >= 8 && ((chanmask&X71Mask) == X7DOT1 || !chanmask));
break;
case DevFmtX714:
chansok = (chancount >= 12 && ((chanmask&X714Mask) == X7DOT1DOT4 || !chanmask));
case DevFmtAmbi3D:
break;
}
}
if(!chansok)
{
if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
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;
@ -1038,9 +1113,9 @@ HRESULT WasapiPlayback::resetProxy()
mDevice->FmtChans = DevFmtX51;
else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
mDevice->FmtChans = DevFmtQuad;
else if(chancount >= 2 && (chanmask&StereoMask) == STEREO)
else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask))
mDevice->FmtChans = DevFmtStereo;
else if(chancount >= 1 && (chanmask&MonoMask) == MONO)
else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask))
mDevice->FmtChans = DevFmtMono;
else
{
@ -1082,12 +1157,12 @@ HRESULT WasapiPlayback::resetProxy()
}
OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
}
mFrameStep = OutputType.Format.nChannels;
mFormat = OutputType;
const EndpointFormFactor formfactor{get_device_formfactor(mMMDev.get())};
mDevice->Flags.set(DirectEar, (formfactor == Headphones || formfactor == Headset));
setChannelOrderFromWFXMask(OutputType.dwChannelMask);
setDefaultWFXChannelOrder();
hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
buf_time.count(), 0, &OutputType.Format, nullptr);
@ -1111,8 +1186,31 @@ HRESULT WasapiPlayback::resetProxy()
/* Find the nearest multiple of the period size to the update size */
if(min_per < per_time)
min_per *= maxi64((per_time + min_per/2) / min_per, 1);
mDevice->UpdateSize = minu(RefTime2Samples(min_per, mDevice->Frequency), buffer_len/2);
mDevice->BufferSize = buffer_len;
mOrigBufferSize = buffer_len;
mOrigUpdateSize = minu(RefTime2Samples(min_per, mFormat.Format.nSamplesPerSec), buffer_len/2);
mDevice->BufferSize = static_cast<uint>(uint64_t{buffer_len} * mDevice->Frequency /
mFormat.Format.nSamplesPerSec);
mDevice->UpdateSize = minu(RefTime2Samples(min_per, mDevice->Frequency),
mDevice->BufferSize/2);
mResampler = nullptr;
mResampleBuffer = nullptr;
mBufferFilled = 0;
if(mDevice->Frequency != mFormat.Format.nSamplesPerSec)
{
mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType,
mFormat.Format.nChannels, mDevice->Frequency, mFormat.Format.nSamplesPerSec,
Resampler::FastBSinc24);
mResampleBuffer = std::make_unique<char[]>(size_t{mDevice->UpdateSize} *
mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8);
TRACE("Created converter for %s/%s format, dst: %luhz (%u), src: %uhz (%u)\n",
DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
mFormat.Format.nSamplesPerSec, mOrigUpdateSize, mDevice->Frequency,
mDevice->UpdateSize);
}
hr = mClient->SetEventHandle(mNotifyEvent);
if(FAILED(hr))
@ -1189,8 +1287,14 @@ ClockLatency WasapiPlayback::getClockLatency()
std::lock_guard<std::mutex> _{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice);
ret.Latency = std::chrono::seconds{mPadding.load(std::memory_order_relaxed)};
ret.Latency /= mDevice->Frequency;
ret.Latency = seconds{mPadding.load(std::memory_order_relaxed)};
ret.Latency /= mFormat.Format.nSamplesPerSec;
if(mResampler)
{
auto extra = mResampler->currentInputDelay();
ret.Latency += std::chrono::duration_cast<nanoseconds>(extra) / mDevice->Frequency;
ret.Latency += nanoseconds{seconds{mBufferFilled}} / mDevice->Frequency;
}
return ret;
}
@ -1234,7 +1338,10 @@ struct WasapiCapture final : public BackendBase, WasapiProxy {
WasapiCapture::~WasapiCapture()
{
if(SUCCEEDED(mOpenStatus))
{
pushMessage(MsgType::CloseDevice).wait();
DeinitThread();
}
mOpenStatus = E_FAIL;
if(mNotifyEvent != nullptr)
@ -1337,35 +1444,44 @@ FORCE_ALIGN int WasapiCapture::recordProc()
void WasapiCapture::open(const char *name)
{
HRESULT hr{S_OK};
if(SUCCEEDED(mOpenStatus))
throw al::backend_exception{al::backend_error::DeviceError,
"Unexpected duplicate open call"};
mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if(mNotifyEvent == nullptr)
{
ERR("Failed to create notify event: %lu\n", GetLastError());
hr = E_FAIL;
ERR("Failed to create notify events: %lu\n", GetLastError());
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to create notify events"};
}
if(SUCCEEDED(hr))
{
if(name)
{
if(CaptureDevices.empty())
pushMessage(MsgType::EnumerateCapture);
if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0)
{
name += DevNameHeadLen;
if(*name == '\0')
name = nullptr;
}
}
hr = pushMessage(MsgType::OpenDevice, name).get();
}
mOpenStatus = hr;
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(CaptureDevices.empty())
pushMessage(MsgType::EnumerateCapture);
if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0)
{
name += DevNameHeadLen;
if(*name == '\0')
name = nullptr;
}
}
mOpenStatus = pushMessage(MsgType::OpenDevice, name).get();
if(FAILED(mOpenStatus))
{
DeinitThread();
throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
hr};
mOpenStatus};
}
hr = pushMessage(MsgType::ResetDevice).get();
if(FAILED(hr))
@ -1443,11 +1559,32 @@ HRESULT WasapiCapture::resetProxy()
}
mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)};
WAVEFORMATEX *wfx;
hr = mClient->GetMixFormat(&wfx);
if(FAILED(hr))
{
ERR("Failed to get capture format: 0x%08lx\n", hr);
return hr;
}
TraceFormat("Device capture format", wfx);
WAVEFORMATEXTENSIBLE InputType{};
if(!MakeExtensible(&InputType, wfx))
{
CoTaskMemFree(wfx);
return E_FAIL;
}
CoTaskMemFree(wfx);
wfx = nullptr;
const bool isRear51{InputType.Format.nChannels == 6
&& (InputType.dwChannelMask&X51RearMask) == X5DOT1REAR};
// Make sure buffer is at least 100ms in size
ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency};
buf_time = std::max(buf_time, ReferenceTime{milliseconds{100}});
WAVEFORMATEXTENSIBLE InputType{};
InputType = {};
InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
switch(mDevice->FmtChans)
{
@ -1465,7 +1602,7 @@ HRESULT WasapiCapture::resetProxy()
break;
case DevFmtX51:
InputType.Format.nChannels = 6;
InputType.dwChannelMask = X5DOT1;
InputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1;
break;
case DevFmtX61:
InputType.Format.nChannels = 7;
@ -1475,7 +1612,12 @@ HRESULT WasapiCapture::resetProxy()
InputType.Format.nChannels = 8;
InputType.dwChannelMask = X7DOT1;
break;
case DevFmtX714:
InputType.Format.nChannels = 12;
InputType.dwChannelMask = X7DOT1DOT4;
break;
case DevFmtX3D71:
case DevFmtAmbi3D:
return E_FAIL;
}
@ -1512,11 +1654,15 @@ HRESULT WasapiCapture::resetProxy()
InputType.Format.cbSize = sizeof(InputType) - sizeof(InputType.Format);
TraceFormat("Requesting capture format", &InputType.Format);
WAVEFORMATEX *wfx;
hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &InputType.Format, &wfx);
if(FAILED(hr))
{
ERR("Failed to check format support: 0x%08lx\n", hr);
WARN("Failed to check capture format support: 0x%08lx\n", hr);
hr = mClient->GetMixFormat(&wfx);
}
if(FAILED(hr))
{
ERR("Failed to find a supported capture format: 0x%08lx\n", hr);
return hr;
}
@ -1556,7 +1702,10 @@ HRESULT WasapiCapture::resetProxy()
case DevFmtX61:
return (chancount == 7 && (chanmask == 0 || (chanmask&X61Mask) == X6DOT1));
case DevFmtX71:
case DevFmtX3D71:
return (chancount == 8 && (chanmask == 0 || (chanmask&X71Mask) == X7DOT1));
case DevFmtX714:
return (chancount == 12 && (chanmask == 0 || (chanmask&X714Mask) == X7DOT1DOT4));
case DevFmtAmbi3D:
return (chanmask == 0 && chancount == device->channelsFromFmt());
}
@ -1632,8 +1781,9 @@ HRESULT WasapiCapture::resetProxy()
if(mDevice->Frequency != InputType.Format.nSamplesPerSec || mDevice->FmtType != srcType)
{
mSampleConv = CreateSampleConverter(srcType, mDevice->FmtType, mDevice->channelsFromFmt(),
InputType.Format.nSamplesPerSec, mDevice->Frequency, Resampler::FastBSinc24);
mSampleConv = SampleConverter::Create(srcType, mDevice->FmtType,
mDevice->channelsFromFmt(), InputType.Format.nSamplesPerSec, mDevice->Frequency,
Resampler::FastBSinc24);
if(!mSampleConv)
{
ERR("Failed to create converter for %s format, dst: %s %uhz, src: %s %luhz\n",
@ -1757,11 +1907,31 @@ bool WasapiBackendFactory::init()
if(FAILED(InitResult)) try
{
std::promise<HRESULT> promise;
auto future = promise.get_future();
auto res = std::async(std::launch::async, []() -> HRESULT
{
HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)};
if(FAILED(hr))
{
WARN("Failed to initialize COM: 0x%08lx\n", hr);
return hr;
}
std::thread{&WasapiProxy::messageHandler, &promise}.detach();
InitResult = future.get();
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;
});
InitResult = res.get();
}
catch(...) {
}
@ -1774,7 +1944,17 @@ 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;
switch(type)
{
case BackendType::Playback:

View file

@ -234,7 +234,7 @@ bool WaveBackend::reset()
fseek(mFile, 0, SEEK_SET);
clearerr(mFile);
if(GetConfigValueBool(nullptr, "wave", "bformat", 0))
if(GetConfigValueBool(nullptr, "wave", "bformat", false))
{
mDevice->FmtChans = DevFmtAmbi3D;
mDevice->mAmbiOrder = 1;
@ -265,6 +265,12 @@ bool WaveBackend::reset()
case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break;
case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break;
case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break;
case DevFmtX714:
chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400 | 0x1000 | 0x4000
| 0x8000 | 0x20000;
break;
/* NOTE: Same as 7.1. */
case DevFmtX3D71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break;
case DevFmtAmbi3D:
/* .amb output requires FuMa */
mDevice->mAmbiOrder = minu(mDevice->mAmbiOrder, 3);

View file

@ -301,23 +301,16 @@ bool WinMMPlayback::reset()
return false;
}
uint chanmask{};
if(mFormat.nChannels >= 2)
{
mDevice->FmtChans = DevFmtStereo;
chanmask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
}
else if(mFormat.nChannels == 1)
{
mDevice->FmtChans = DevFmtMono;
chanmask = SPEAKER_FRONT_CENTER;
}
else
{
ERR("Unhandled channel count: %d\n", mFormat.nChannels);
return false;
}
setChannelOrderFromWFXMask(chanmask);
setDefaultWFXChannelOrder();
uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()};
@ -476,6 +469,8 @@ void WinMMCapture::open(const char *name)
case DevFmtX51:
case DevFmtX61:
case DevFmtX71:
case DevFmtX714:
case DevFmtX3D71:
case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
DevFmtChannelsString(mDevice->FmtChans)};