From af6d327bc3682977488d8c8f6f193592d4a927ed Mon Sep 17 00:00:00 2001 From: Alex Marsev Date: Mon, 22 Feb 2016 09:33:57 +0300 Subject: Surrender exclusive-mode device after inactivity Now you can pause, make a skype call, then unpause. Just for example. --- src/AudioDevice.h | 3 + src/AudioDeviceEvent.cpp | 171 ++++++++++++++++++++++++++++++++++++++++++--- src/AudioDeviceEvent.h | 11 +++ src/AudioDeviceManager.cpp | 68 ++++++++++++++++++ src/AudioDeviceManager.h | 1 + src/AudioDevicePush.cpp | 6 ++ src/AudioDevicePush.h | 2 + src/AudioRenderer.cpp | 11 ++- src/Utils.h | 7 ++ 9 files changed, 268 insertions(+), 12 deletions(-) diff --git a/src/AudioDevice.h b/src/AudioDevice.h index 40e6c2d..2617ca4 100644 --- a/src/AudioDevice.h +++ b/src/AudioDevice.h @@ -70,6 +70,9 @@ namespace SaneAudioRenderer bool IgnoredSystemChannelMixer() const { return m_backend->ignoredSystemChannelMixer; } + using RenewBackendFunction = std::function&)>; + virtual bool RenewInactive(const RenewBackendFunction& renewBackend, int64_t& position) = 0; + protected: std::shared_ptr m_backend; diff --git a/src/AudioDeviceEvent.cpp b/src/AudioDeviceEvent.cpp index f7a2642..574c57e 100644 --- a/src/AudioDeviceEvent.cpp +++ b/src/AudioDeviceEvent.cpp @@ -20,8 +20,11 @@ namespace SaneAudioRenderer assert(backend->eventMode); m_backend = backend; - if (static_cast(m_wake) == NULL) + if (static_cast(m_wake) == NULL || + static_cast(m_observeInactivityWake) == NULL) + { throw E_OUTOFMEMORY; + } ThrowIfFailed(backend->audioClient->SetEventHandle(m_wake)); @@ -75,11 +78,16 @@ namespace SaneAudioRenderer int64_t AudioDeviceEvent::GetPosition() { + CAutoLock renewLock(&m_renewMutex); + + if (m_awaitingRenew) + return m_renewPosition; + UINT64 deviceClockFrequency, deviceClockPosition; ThrowIfFailed(m_backend->audioClock->GetFrequency(&deviceClockFrequency)); ThrowIfFailed(m_backend->audioClock->GetPosition(&deviceClockPosition, nullptr)); - return llMulDiv(deviceClockPosition, OneSecond, deviceClockFrequency, 0); + return m_renewPosition + llMulDiv(deviceClockPosition, OneSecond, deviceClockFrequency, 0); } int64_t AudioDeviceEvent::GetEnd() @@ -99,6 +107,8 @@ namespace SaneAudioRenderer { CAutoLock threadLock(&m_threadMutex); + m_observeInactivity = false; + if (m_sentFrames == 0) { m_queuedStart = true; @@ -124,10 +134,23 @@ namespace SaneAudioRenderer { CAutoLock threadLock(&m_threadMutex); + m_queuedStart = false; - } - m_backend->audioClient->Stop(); + CAutoLock renewLock(&m_renewMutex); + + if (m_awaitingRenew) + return; + + m_backend->audioClient->Stop(); + + if (!m_backend->bitstream) + { + m_observeInactivity = true; + m_activityPointCounter = GetPerformanceCounter(); + m_observeInactivityWake.Set(); + } + } } void AudioDeviceEvent::Reset() @@ -137,7 +160,13 @@ namespace SaneAudioRenderer { CAutoLock threadLock(&m_threadMutex); - m_backend->audioClient->Reset(); + CAutoLock renewLock(&m_renewMutex); + + if (!m_awaitingRenew) + m_backend->audioClient->Reset(); + + m_renewPosition = 0; + m_renewSilenceFrames = 0; m_endOfStream = false; m_endOfStreamPos = 0; @@ -151,7 +180,56 @@ namespace SaneAudioRenderer m_bufferFrames = 0; m_buffer.clear(); } + + if (m_observeInactivity) + m_activityPointCounter = GetPerformanceCounter(); + } + } + + bool AudioDeviceEvent::RenewInactive(const RenewBackendFunction& renewBackend, int64_t& position) + { + CAutoLock threadLock(&m_threadMutex); + + m_observeInactivity = false; + + if (m_error) + return false; + + CAutoLock renewLock(&m_renewMutex); + + if (m_awaitingRenew) + { + DebugOut(ClassName(this), "renew"); + + if (!renewBackend(m_backend)) + return false; + + ThrowIfFailed(m_backend->audioClient->SetEventHandle(m_wake)); + m_awaitingRenew = false; + + if (m_renewSilenceFrames > 0) + { + DebugOut(ClassName(this), m_renewSilenceFrames, "frames of silence before renew"); + + DspChunk chunk = DspChunk(m_backend->dspFormat, m_backend->waveFormat->nChannels, + m_renewSilenceFrames, m_backend->waveFormat->nSamplesPerSec); + + ZeroMemory(chunk.GetData(), chunk.GetSize()); + + { + CAutoLock bufferLock(&m_bufferMutex); + m_buffer.emplace_front(std::move(chunk)); + m_bufferFrames += m_renewSilenceFrames; + } + + m_renewPosition -= llMulDiv(m_renewSilenceFrames, OneSecond, + m_backend->waveFormat->nSamplesPerSec, 0); + } + + position = m_renewPosition; } + + return true; } void AudioDeviceEvent::EventFeed() @@ -167,13 +245,21 @@ namespace SaneAudioRenderer if (taskHandle == NULL) SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); - while (!m_exit) + for (DWORD waitTime = INFINITE;;) { - { - CAutoLock threadLock(&m_threadMutex); + HRESULT waitResult = WaitForAny(waitTime, m_wake, m_observeInactivityWake); + + if (m_exit || m_error) + break; - if (!m_error) + switch (waitResult) + { + case WAIT_OBJECT_0: { + CAutoLock threadLock(&m_threadMutex); + + assert(m_sentFrames > 0 || m_queuedStart); + try { PushBufferToDevice(); @@ -189,10 +275,70 @@ namespace SaneAudioRenderer { m_error = true; } + + break; } - } - m_wake.Wait(); + case WAIT_OBJECT_0 + 1: + case WAIT_TIMEOUT: + { + CAutoLock threadLock(&m_threadMutex); + + waitTime = INFINITE; + + if (m_observeInactivity) + { + int64_t delay = GetPerformanceFrequency() / 5; // 200ms + int64_t remaining = m_activityPointCounter + delay - GetPerformanceCounter(); + + if (remaining > 0) + { + waitTime = (DWORD)llMulDiv(remaining, 1000, GetPerformanceFrequency(), 0) + 1; + } + else + { + CAutoLock renewLock(&m_renewMutex); + + DebugOut(ClassName(this), "awaiting renew"); + + const uint32_t rate = m_backend->waveFormat->nSamplesPerSec; + + int64_t currentPosition = GetPosition(); + m_renewPosition = llMulDiv(m_receivedFrames - m_bufferFrames, OneSecond, rate, 0); + + try + { + int64_t renewSilence = m_renewPosition - currentPosition; + if (renewSilence > 0) + m_renewSilenceFrames = (size_t)llMulDiv(renewSilence, rate, OneSecond, 0); + } + catch (HRESULT) + { + m_renewSilenceFrames = 0; + } + + assert(CheckLastInstances()); + m_backend->audioClock = nullptr; + m_backend->audioRenderClient = nullptr; + m_backend->audioClient = nullptr; + + m_sentFrames = 0; + + m_awaitingRenew = true; + m_observeInactivity = false; + } + + } + + break; + } + + default: + { + DebugOut(ClassName(this), "wait error"); + m_error = true; + } + } } if (taskHandle != NULL) @@ -216,7 +362,10 @@ namespace SaneAudioRenderer CAutoLock bufferLock(&m_bufferMutex); if (deviceFrames > m_bufferFrames && !m_endOfStream && !m_backend->realtime) + { + DebugOut(ClassName(this), "buffer underrun"); return; + } BYTE* deviceBuffer; ThrowIfFailed(m_backend->audioRenderClient->GetBuffer(deviceFrames, &deviceBuffer)); diff --git a/src/AudioDeviceEvent.h b/src/AudioDeviceEvent.h index e87422e..ad986f8 100644 --- a/src/AudioDeviceEvent.h +++ b/src/AudioDeviceEvent.h @@ -27,6 +27,8 @@ namespace SaneAudioRenderer void Stop() override; void Reset() override; + bool RenewInactive(const RenewBackendFunction& renewBackend, int64_t& position) override; + private: void EventFeed(); @@ -53,5 +55,14 @@ namespace SaneAudioRenderer size_t m_bufferFrames = 0; bool m_queuedStart = false; + + bool m_observeInactivity = false; + CAMEvent m_observeInactivityWake; + int64_t m_activityPointCounter = 0; + + CCritSec m_renewMutex; + bool m_awaitingRenew = false; + int64_t m_renewPosition = 0; + size_t m_renewSilenceFrames = 0; }; } diff --git a/src/AudioDeviceManager.cpp b/src/AudioDeviceManager.cpp index 9ac1c52..69eb544 100644 --- a/src/AudioDeviceManager.cpp +++ b/src/AudioDeviceManager.cpp @@ -415,6 +415,53 @@ namespace SaneAudioRenderer } } + HRESULT RecreateAudioDeviceBackend(IMMDeviceEnumerator* pEnumerator, + std::shared_ptr& backend) + { + try + { + // Recreate + backend->audioClient = nullptr; + CreateAudioClient(pEnumerator, *backend); + + // Initialize + { + AUDCLNT_SHAREMODE mode = backend->exclusive ? AUDCLNT_SHAREMODE_EXCLUSIVE : + AUDCLNT_SHAREMODE_SHARED; + + DWORD flags = AUDCLNT_STREAMFLAGS_NOPERSIST; + if (backend->eventMode) + flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK; + + REFERENCE_TIME bufferDuration = llMulDiv(backend->deviceBufferSize, OneSecond, + backend->waveFormat->nSamplesPerSec, 0); + + REFERENCE_TIME periodicy = 0; + if (backend->exclusive && backend->eventMode) + periodicy = bufferDuration; + + ThrowIfFailed(backend->audioClient->Initialize(mode, flags, bufferDuration, + periodicy, &(*backend->waveFormat), nullptr)); + } + + // Get services + ThrowIfFailed(backend->audioClient->GetService(IID_PPV_ARGS(&backend->audioRenderClient))); + ThrowIfFailed(backend->audioClient->GetService(IID_PPV_ARGS(&backend->audioClock))); + ThrowIfFailed(backend->audioClient->GetStreamLatency(&backend->deviceLatency)); + ThrowIfFailed(backend->audioClient->GetBufferSize(&backend->deviceBufferSize)); + } + catch (std::bad_alloc&) + { + return E_OUTOFMEMORY; + } + catch (HRESULT ex) + { + return ex; + } + + return S_OK; + } + HRESULT GetDefaultDeviceIdInternal(IMMDeviceEnumerator* pEnumerator, std::unique_ptr& id) { @@ -583,6 +630,27 @@ namespace SaneAudioRenderer } } + bool AudioDeviceManager::RenewInactiveDevice(AudioDevice& device, int64_t& position) + { + auto renewFunction = [this](std::shared_ptr& backend) -> bool + { + m_function = [&] { return RecreateAudioDeviceBackend(m_enumerator, backend); }; + m_wake.Set(); + m_done.Wait(); + + return SUCCEEDED(m_result); + }; + + try + { + return device.RenewInactive(renewFunction, position); + } + catch (HRESULT) + { + return false; + } + } + std::unique_ptr AudioDeviceManager::GetDefaultDeviceId() { assert(m_enumerator); diff --git a/src/AudioDeviceManager.h b/src/AudioDeviceManager.h index 7cff1b5..2ed3eec 100644 --- a/src/AudioDeviceManager.h +++ b/src/AudioDeviceManager.h @@ -41,6 +41,7 @@ namespace SaneAudioRenderer bool BitstreamFormatSupported(SharedWaveFormat format, ISettings* pSettings); std::unique_ptr CreateDevice(SharedWaveFormat format, bool realtime, ISettings* pSettings); + bool RenewInactiveDevice(AudioDevice& device, int64_t& position); uint32_t GetDefaultDeviceSerial() { return m_defaultDeviceSerial; } std::unique_ptr GetDefaultDeviceId(); diff --git a/src/AudioDevicePush.cpp b/src/AudioDevicePush.cpp index 06fc553..87795c8 100644 --- a/src/AudioDevicePush.cpp +++ b/src/AudioDevicePush.cpp @@ -121,6 +121,12 @@ namespace SaneAudioRenderer m_endOfStreamPos = 0; } + bool AudioDevicePush::RenewInactive(const RenewBackendFunction& renewBackend, int64_t& position) + { + position = 0; + return true; + } + void AudioDevicePush::SilenceFeed() { SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); diff --git a/src/AudioDevicePush.h b/src/AudioDevicePush.h index a27dc50..7b431e2 100644 --- a/src/AudioDevicePush.h +++ b/src/AudioDevicePush.h @@ -27,6 +27,8 @@ namespace SaneAudioRenderer void Stop() override; void Reset() override; + bool RenewInactive(const RenewBackendFunction& renewBackend, int64_t& position) override; + private: void SilenceFeed(); diff --git a/src/AudioRenderer.cpp b/src/AudioRenderer.cpp index f5b67cc..a7d50c9 100644 --- a/src/AudioRenderer.cpp +++ b/src/AudioRenderer.cpp @@ -478,6 +478,15 @@ namespace SaneAudioRenderer { try { + // Renew (effectively recreate) the device if it's been freed because of inactivity. + int64_t deviceRenewPosition = 0; + if (!m_deviceManager.RenewInactiveDevice(*m_device, deviceRenewPosition)) + { + DebugOut(ClassName(this), "failed to renew device"); + ClearDevice(); + return; + } + // Try to minimize clock slaving initial jitter. { REFERENCE_TIME silence = m_startClockOffset - (m_myClock.GetPrivateTime() - m_startTime) + @@ -508,7 +517,7 @@ namespace SaneAudioRenderer } m_guidedReclockOffset = 0; - m_myClock.SlaveClockToAudio(m_device->GetClock(), m_startTime + m_startClockOffset); + m_myClock.SlaveClockToAudio(m_device->GetClock(), m_startTime + m_startClockOffset + deviceRenewPosition); m_clockCorrection = 0; m_device->Start(); } diff --git a/src/Utils.h b/src/Utils.h index 51098ec..466fc04 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -155,4 +155,11 @@ namespace SaneAudioRenderer VER_MINORVERSION, VER_GREATER_EQUAL); return !!VerifyVersionInfo(&info, VER_MAJORVERSION | VER_MINORVERSION, rule); } + + template + inline HRESULT WaitForAny(DWORD timeout, T&... objects) + { + std::array handles = {objects...}; + return WaitForMultipleObjects(sizeof...(objects), handles.data(), FALSE, timeout); + } } -- cgit v1.2.3