Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mpc-hc/sanear.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Marsev <alex.marsev@gmail.com>2016-02-22 09:33:57 +0300
committerAlex Marsev <alex.marsev@gmail.com>2016-02-22 14:39:43 +0300
commitaf6d327bc3682977488d8c8f6f193592d4a927ed (patch)
treea21c36bd442ed4bc73515dd8b064a78806040e33
parent7aa37f526b94fb4b5b4b6c806cb91db4996a1d99 (diff)
Surrender exclusive-mode device after inactivity
Now you can pause, make a skype call, then unpause. Just for example.
-rw-r--r--src/AudioDevice.h3
-rw-r--r--src/AudioDeviceEvent.cpp171
-rw-r--r--src/AudioDeviceEvent.h11
-rw-r--r--src/AudioDeviceManager.cpp68
-rw-r--r--src/AudioDeviceManager.h1
-rw-r--r--src/AudioDevicePush.cpp6
-rw-r--r--src/AudioDevicePush.h2
-rw-r--r--src/AudioRenderer.cpp11
-rw-r--r--src/Utils.h7
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<bool(std::shared_ptr<AudioDeviceBackend>&)>;
+ virtual bool RenewInactive(const RenewBackendFunction& renewBackend, int64_t& position) = 0;
+
protected:
std::shared_ptr<AudioDeviceBackend> 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<HANDLE>(m_wake) == NULL)
+ if (static_cast<HANDLE>(m_wake) == NULL ||
+ static_cast<HANDLE>(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<AudioDeviceBackend>& 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<WCHAR, CoTaskMemFreeDeleter>& id)
{
@@ -583,6 +630,27 @@ namespace SaneAudioRenderer
}
}
+ bool AudioDeviceManager::RenewInactiveDevice(AudioDevice& device, int64_t& position)
+ {
+ auto renewFunction = [this](std::shared_ptr<AudioDeviceBackend>& 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<WCHAR, CoTaskMemFreeDeleter> 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<AudioDevice> CreateDevice(SharedWaveFormat format, bool realtime, ISettings* pSettings);
+ bool RenewInactiveDevice(AudioDevice& device, int64_t& position);
uint32_t GetDefaultDeviceSerial() { return m_defaultDeviceSerial; }
std::unique_ptr<WCHAR, CoTaskMemFreeDeleter> 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 <typename... T>
+ inline HRESULT WaitForAny(DWORD timeout, T&... objects)
+ {
+ std::array<HANDLE, sizeof...(objects)> handles = {objects...};
+ return WaitForMultipleObjects(sizeof...(objects), handles.data(), FALSE, timeout);
+ }
}