diff options
author | Chris Robinson <[email protected]> | 2022-11-12 23:22:48 -0800 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2022-11-12 23:22:48 -0800 |
commit | 936f7d8401a8bcc08b514ff01a68b0f14df24819 (patch) | |
tree | 16d87c4ee136cbb968775f68287d540394e04595 | |
parent | 695e4ed6b282c98eb23897c464c5cbc405273ade (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.cpp | 85 |
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; } |