aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2022-11-12 23:22:48 -0800
committerChris Robinson <[email protected]>2022-11-12 23:22:48 -0800
commit936f7d8401a8bcc08b514ff01a68b0f14df24819 (patch)
tree16d87c4ee136cbb968775f68287d540394e04595
parent695e4ed6b282c98eb23897c464c5cbc405273ade (diff)
Add a resampler to WASAPI output
This allows mixing at various samples rates that WASAPI doesn't otherwise support. This is mostly helpful for users that have unnecessarily high device rates (96 or 192khz), and lets the ALC_FREQUENCY attribute or frequency config option set a lower mixing rate for more efficient processing.
-rw-r--r--alc/backends/wasapi.cpp85
1 files changed, 75 insertions, 10 deletions
diff --git a/alc/backends/wasapi.cpp b/alc/backends/wasapi.cpp
index b1f07fdc..f378ad6a 100644
--- a/alc/backends/wasapi.cpp
+++ b/alc/backends/wasapi.cpp
@@ -86,6 +86,7 @@ DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x
namespace {
+using std::chrono::nanoseconds;
using std::chrono::milliseconds;
using std::chrono::seconds;
@@ -652,7 +653,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;
@@ -691,8 +697,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{mDevice->frameSizeFromFmt()};
+ const uint update_size{mOrigUpdateSize};
+ const UINT32 buffer_len{mOrigBufferSize};
while(!mKillNow.load(std::memory_order_relaxed))
{
UINT32 written;
@@ -718,9 +725,37 @@ FORCE_ALIGN int WasapiPlayback::mixerProc()
hr = mRender->GetBuffer(len, &buffer);
if(SUCCEEDED(hr))
{
+ if(mResampler)
+ {
+ std::lock_guard<std::mutex> _{mMutex};
+ for(UINT32 done{0};done < len;)
+ {
+ if(mBufferFilled == 0)
+ {
+ mDevice->renderSamples(mResampleBuffer.get(), mDevice->UpdateSize,
+ mFormat.Format.nChannels);
+ mBufferFilled = mDevice->UpdateSize;
+ }
+
+ const void *src{mResampleBuffer.get()};
+ uint srclen{mBufferFilled};
+ uint got{mResampler->convert(&src, &srclen, buffer, len-done)};
+ buffer += got*frame_size;
+ done += got;
+
+ mPadding.store(written + done, std::memory_order_relaxed);
+ if(srclen)
+ {
+ const char *bsrc{static_cast<const char*>(src)};
+ std::copy(bsrc, bsrc + srclen*frame_size, mResampleBuffer.get());
+ }
+ mBufferFilled = srclen;
+ }
+ }
+ else
{
std::lock_guard<std::mutex> _{mMutex};
- mDevice->renderSamples(buffer, len, mFrameStep);
+ mDevice->renderSamples(buffer, len, mFormat.Format.nChannels);
mPadding.store(written + len, std::memory_order_relaxed);
}
hr = mRender->ReleaseBuffer(len, 0);
@@ -1019,7 +1054,8 @@ HRESULT WasapiPlayback::resetProxy()
CoTaskMemFree(wfx);
wfx = nullptr;
- mDevice->Frequency = OutputType.Format.nSamplesPerSec;
+ mDevice->Frequency = minu(mDevice->Frequency, mFormat.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
@@ -1117,7 +1153,7 @@ 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));
@@ -1146,8 +1182,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 = CreateSampleConverter(mDevice->FmtType, mDevice->FmtType,
+ mFormat.Format.nChannels, mDevice->Frequency, mFormat.Format.nSamplesPerSec,
+ Resampler::FastBSinc24);
+ mResampleBuffer = std::make_unique<char[]>(size_t{mDevice->UpdateSize} *
+ mDevice->frameSizeFromFmt());
+
+ 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))
@@ -1224,8 +1283,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;
}