From b75b90ff1d086403b069dcac59b598059001ac59 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Wed, 2 Sep 2026 05:22:43 +1000 Subject: [PATCH] [idd] audio: publish render data promptly Publish committed WaveRT render frames on a 4 kHz timer while keeping packet completion and notification semantics unchanged. Serialize clock sampling with position updates to prevent concurrent queries from replaying elapsed time. --- idd/LGAudio/Adapter.cpp | 6 ++ idd/LGAudio/Adapter.h | 3 + idd/LGAudio/MiniportWaveRTStream.cpp | 138 ++++++++++++++++++++++++--- idd/LGAudio/MiniportWaveRTStream.h | 8 +- 4 files changed, 139 insertions(+), 16 deletions(-) diff --git a/idd/LGAudio/Adapter.cpp b/idd/LGAudio/Adapter.cpp index aec3348c..28946e70 100644 --- a/idd/LGAudio/Adapter.cpp +++ b/idd/LGAudio/Adapter.cpp @@ -263,6 +263,12 @@ void CAudioAdapter::WriteRenderFrames( BrokerDeviceNotifyRenderAvailable(this, bytes, droppedBytes); } +_IRQL_requires_max_(DISPATCH_LEVEL) +void CAudioAdapter::MarkRenderDiscontinuity() +{ + InterlockedExchange(&m_renderDiscontinuity, 1); +} + _IRQL_requires_max_(DISPATCH_LEVEL) void CAudioAdapter::ReadCaptureFrames( _In_ const FormatFingerprint &format, diff --git a/idd/LGAudio/Adapter.h b/idd/LGAudio/Adapter.h index bca4820a..276b825a 100644 --- a/idd/LGAudio/Adapter.h +++ b/idd/LGAudio/Adapter.h @@ -67,6 +67,9 @@ public: _In_reads_bytes_(bytes) const BYTE *data, _In_ ULONG bytes); + _IRQL_requires_max_(DISPATCH_LEVEL) + void MarkRenderDiscontinuity(); + _IRQL_requires_max_(DISPATCH_LEVEL) void ReadCaptureFrames( _In_ const FormatFingerprint &format, diff --git a/idd/LGAudio/MiniportWaveRTStream.cpp b/idd/LGAudio/MiniportWaveRTStream.cpp index 445c9c4c..6605a448 100644 --- a/idd/LGAudio/MiniportWaveRTStream.cpp +++ b/idd/LGAudio/MiniportWaveRTStream.cpp @@ -23,6 +23,8 @@ namespace { +constexpr ULONG kRenderPublicationRateHz = 4000; + ULONGLONG MinUlonglong(ULONGLONG left, ULONGLONG right) { return left < right ? left : right; @@ -50,6 +52,7 @@ CMiniportWaveRTStream::CMiniportWaveRTStream( m_bufferSize(0), m_notificationCount(0), m_packetSize(0), + m_renderPublicationSliceBytes(0), m_notificationEvents{}, m_notificationEventCount(0), m_clockFrequency(0), @@ -61,6 +64,7 @@ CMiniportWaveRTStream::CMiniportWaveRTStream( m_pendingBytes(0), m_linearPosition(0), m_cyclicPosition(0), + m_renderPublishedPosition(0), m_completedPackets(0), m_lastReadPacket(~0ULL), m_lastWritePacket(MAXULONG), @@ -205,12 +209,14 @@ NTSTATUS CMiniportWaveRTStream::SetState(_In_ KSSTATE state) if (state == KSSTATE_RUN) { - const AudioClockSnapshot clock = m_adapter->GetClockSnapshot(); - if (!clock.Frequency || !clock.QpcFrequency) - return STATUS_DEVICE_HARDWARE_ERROR; - KIRQL oldIrql; KeAcquireSpinLock(&m_positionLock, &oldIrql); + const AudioClockSnapshot clock = m_adapter->GetClockSnapshot(); + if (!clock.Frequency || !clock.QpcFrequency) + { + KeReleaseSpinLock(&m_positionLock, oldIrql); + return STATUS_DEVICE_HARDWARE_ERROR; + } if (!m_buffer || !m_bufferSize) { KeReleaseSpinLock(&m_positionLock, oldIrql); @@ -251,9 +257,9 @@ NTSTATUS CMiniportWaveRTStream::SetState(_In_ KSSTATE state) return STATUS_SUCCESS; } - const AudioClockSnapshot clock = m_adapter->GetClockSnapshot(); KIRQL oldIrql; KeAcquireSpinLock(&m_positionLock, &oldIrql); + const AudioClockSnapshot clock = m_adapter->GetClockSnapshot(); if (m_state == KSSTATE_RUN) AdvanceLocked(clock); m_state = state; @@ -269,6 +275,7 @@ NTSTATUS CMiniportWaveRTStream::SetState(_In_ KSSTATE state) m_pendingBytes = 0; m_linearPosition = 0; m_cyclicPosition = 0; + m_renderPublishedPosition = 0; m_completedPackets = 0; m_lastReadPacket = ~0ULL; m_lastWritePacket = MAXULONG; @@ -290,9 +297,9 @@ NTSTATUS CMiniportWaveRTStream::GetPosition( if (!position) return STATUS_INVALID_PARAMETER; - const AudioClockSnapshot clock = m_adapter->GetClockSnapshot(); KIRQL oldIrql; KeAcquireSpinLock(&m_positionLock, &oldIrql); + const AudioClockSnapshot clock = m_adapter->GetClockSnapshot(); AdvanceLocked(clock); ULONG reportedPosition = m_cyclicPosition; if (m_notificationCount && m_bufferSize) @@ -434,9 +441,9 @@ NTSTATUS CMiniportWaveRTStream::GetReadPacket( !performanceCounterValue || !moreData) return STATUS_INVALID_PARAMETER; - const AudioClockSnapshot clock = m_adapter->GetClockSnapshot(); KIRQL oldIrql; KeAcquireSpinLock(&m_positionLock, &oldIrql); + const AudioClockSnapshot clock = m_adapter->GetClockSnapshot(); if (!m_notificationCount) { KeReleaseSpinLock(&m_positionLock, oldIrql); @@ -496,9 +503,9 @@ NTSTATUS CMiniportWaveRTStream::SetWritePacket( if (flags & ~allowedFlags) return STATUS_INVALID_PARAMETER; - const AudioClockSnapshot clock = m_adapter->GetClockSnapshot(); KIRQL oldIrql; KeAcquireSpinLock(&m_positionLock, &oldIrql); + const AudioClockSnapshot clock = m_adapter->GetClockSnapshot(); if (!m_notificationCount) { KeReleaseSpinLock(&m_positionLock, oldIrql); @@ -548,7 +555,8 @@ NTSTATUS CMiniportWaveRTStream::SetWritePacket( } m_lastWritePacket = packetNumber; - if (m_eosReceived && m_state == KSSTATE_RUN) + PublishRenderFramesLocked(); + if (m_state == KSSTATE_RUN) ArmTimerLocked(); KeReleaseSpinLock(&m_positionLock, oldIrql); return STATUS_SUCCESS; @@ -560,9 +568,9 @@ NTSTATUS CMiniportWaveRTStream::GetOutputStreamPresentationPosition( if (m_kind != EndpointKind::Render || !position) return STATUS_INVALID_PARAMETER; - const AudioClockSnapshot clock = m_adapter->GetClockSnapshot(); KIRQL oldIrql; KeAcquireSpinLock(&m_positionLock, &oldIrql); + const AudioClockSnapshot clock = m_adapter->GetClockSnapshot(); if (!m_notificationCount) { KeReleaseSpinLock(&m_positionLock, oldIrql); @@ -599,9 +607,9 @@ NTSTATUS CMiniportWaveRTStream::GetPacketCount( if (m_kind != EndpointKind::Render || !packetCount) return STATUS_INVALID_PARAMETER; - const AudioClockSnapshot clock = m_adapter->GetClockSnapshot(); KIRQL oldIrql; KeAcquireSpinLock(&m_positionLock, &oldIrql); + const AudioClockSnapshot clock = m_adapter->GetClockSnapshot(); if (!m_notificationCount) { KeReleaseSpinLock(&m_positionLock, oldIrql); @@ -687,6 +695,16 @@ NTSTATUS CMiniportWaveRTStream::AllocateBuffer( m_notificationCount = notificationCount; m_packetSize = notificationCount ? requestedSize / notificationCount : 0; + m_renderPublicationSliceBytes = 0; + if (m_kind == EndpointKind::Render && notificationCount) + { + const ULONG sampleRate = + m_format.WaveFormatExt.Format.nSamplesPerSec; + const ULONG publicationFrames = + (sampleRate + kRenderPublicationRateHz - 1) / + kRenderPublicationRateHz; + m_renderPublicationSliceBytes = publicationFrames * blockAlign; + } KeReleaseSpinLock(&m_positionLock, oldIrql); *audioBufferMdl = mdl; @@ -719,6 +737,8 @@ void CMiniportWaveRTStream::FreeBuffer(_In_opt_ PMDL audioBufferMdl) m_bufferSize = 0; m_notificationCount = 0; m_packetSize = 0; + m_renderPublicationSliceBytes = 0; + m_renderPublishedPosition = 0; KeReleaseSpinLock(&m_positionLock, oldIrql); if (buffer) @@ -754,6 +774,21 @@ void CMiniportWaveRTStream::ArmTimerLocked() targetBytes = remaining; } + // Render PCM is already immutable once SetWritePacket commits its WaveRT + // packet. Publish the elapsed part at 4 kHz while keeping targetBytes at + // the original packet boundary for completion and notification purposes. + if (m_kind == EndpointKind::Render && m_renderPublicationSliceBytes && + RenderPacketCommittedLocked(m_renderPublishedPosition)) + { + const ULONGLONG publishedBytes = + m_renderPublishedPosition > m_linearPosition ? + m_renderPublishedPosition - m_linearPosition : 0; + const ULONGLONG publicationTarget = + publishedBytes + m_renderPublicationSliceBytes; + if (publicationTarget < targetBytes) + targetBytes = publicationTarget; + } + const ULONGLONG bytesUntilTarget = targetBytes > m_pendingBytes ? targetBytes - m_pendingBytes : 0; const ULONGLONG framesUntilTarget = @@ -851,6 +886,7 @@ void CMiniportWaveRTStream::AdvanceEventPacketsLocked( const ULONGLONG skippedPackets = readyPackets - m_notificationCount; const ULONGLONG skippedBytes = skippedPackets * m_packetSize; + const ULONGLONG oldLinearPosition = m_linearPosition; if (m_kind == EndpointKind::Capture) m_adapter->DiscardCaptureFrames(m_fingerprint, skippedBytes); @@ -860,10 +896,23 @@ void CMiniportWaveRTStream::AdvanceEventPacketsLocked( m_cyclicPosition = static_cast( (m_cyclicPosition + (skippedBytes % m_bufferSize)) % m_bufferSize); + if (m_kind == EndpointKind::Render && + m_renderPublishedPosition < m_linearPosition) + { + // A delayed timer can make the WaveRT buffer wrap after the prefix + // of this packet was already published. Mark the resulting splice; + // a wholly uncommitted packet keeps the legacy silent-drop behavior. + if (m_renderPublishedPosition > oldLinearPosition) + m_adapter->MarkRenderDiscontinuity(); + m_renderPublishedPosition = m_linearPosition; + } m_completedPackets = m_linearPosition / m_packetSize; } } + if (m_kind == EndpointKind::Render) + PublishRenderFramesLocked(); + while (m_state == KSSTATE_RUN) { ULONGLONG transferBytes = m_packetSize; @@ -887,8 +936,12 @@ void CMiniportWaveRTStream::AdvanceEventPacketsLocked( const ULONGLONG oldPacketCount = m_completedPackets; if (transferBytes) { - TransferFramesLocked(transferBytes); + TransferFramesLocked(transferBytes, + m_kind != EndpointKind::Render); m_pendingBytes -= transferBytes; + if (m_kind == EndpointKind::Render && + m_renderPublishedPosition < m_linearPosition) + m_renderPublishedPosition = m_linearPosition; } UpdatePresentationQpcLocked(clock); @@ -910,6 +963,59 @@ void CMiniportWaveRTStream::AdvanceEventPacketsLocked( } } +bool CMiniportWaveRTStream::RenderPacketCommittedLocked( + _In_ ULONGLONG position) const +{ + if (m_kind != EndpointKind::Render || !m_notificationCount || + !m_packetSize) + return false; + + const ULONG packet = static_cast(position / m_packetSize); + return static_cast(m_lastWritePacket - packet) >= 0; +} + +void CMiniportWaveRTStream::PublishRenderFramesLocked() +{ + if (m_kind != EndpointKind::Render || !m_notificationCount || + !m_packetSize || !m_renderPublicationSliceBytes || !m_bufferSize) + return; + + if (m_renderPublishedPosition < m_linearPosition) + m_renderPublishedPosition = m_linearPosition; + + ULONGLONG availableEnd = m_linearPosition + m_pendingBytes; + if (m_eosReceived && availableEnd > m_eosLinearPosition) + availableEnd = m_eosLinearPosition; + + while (m_renderPublishedPosition < availableEnd && + RenderPacketCommittedLocked(m_renderPublishedPosition)) + { + const ULONGLONG packet = + m_renderPublishedPosition / m_packetSize; + const ULONGLONG packetEnd = (packet + 1) * m_packetSize; + const ULONGLONG copyEnd = MinUlonglong(availableEnd, packetEnd); + const ULONGLONG bytes = copyEnd - m_renderPublishedPosition; + ULONG offset = static_cast( + m_renderPublishedPosition % m_bufferSize); + ULONGLONG remaining = bytes; + + // Timer slippage may make more than one 0.25 ms slice available. Copy all + // elapsed committed data now, but never cross a WaveRT packet commit + // fence or the cyclic-buffer boundary in one adapter operation. + while (remaining) + { + const ULONG untilWrap = m_bufferSize - offset; + const ULONG chunk = static_cast(MinUlonglong( + remaining, untilWrap)); + m_adapter->WriteRenderFrames(m_buffer + offset, chunk); + remaining -= chunk; + offset = (offset + chunk) % m_bufferSize; + } + + m_renderPublishedPosition = copyEnd; + } +} + void CMiniportWaveRTStream::UpdatePresentationQpcLocked( _In_ const AudioClockSnapshot &clock) { @@ -936,7 +1042,9 @@ void CMiniportWaveRTStream::UpdatePresentationQpcLocked( clock.QpcPosition - ticksSinceBoundary : 0; } -void CMiniportWaveRTStream::TransferFramesLocked(_In_ ULONGLONG bytes) +void CMiniportWaveRTStream::TransferFramesLocked( + _In_ ULONGLONG bytes, + _In_ bool transferRender) { if (!bytes || !m_bufferSize) return; @@ -982,7 +1090,7 @@ void CMiniportWaveRTStream::TransferFramesLocked(_In_ ULONGLONG bytes) // slot. On an underrun, neither read nor modify a slot that the client // may still be updating. The capture side zero-fills the resulting // absence when the render-to-capture FIFO is exhausted. - if (renderDataCommitted) + if (transferRender && renderDataCommitted) m_adapter->WriteRenderFrames(m_buffer + offset, chunk); } else @@ -1022,9 +1130,9 @@ void CMiniportWaveRTStream::TimerCallback( if (!stream) return; + KeAcquireSpinLockAtDpcLevel(&stream->m_positionLock); const AudioClockSnapshot clock = stream->m_adapter->GetClockSnapshot(); - KeAcquireSpinLockAtDpcLevel(&stream->m_positionLock); stream->AdvanceLocked(clock); if (stream->m_state == KSSTATE_RUN) stream->ArmTimerLocked(); diff --git a/idd/LGAudio/MiniportWaveRTStream.h b/idd/LGAudio/MiniportWaveRTStream.h index 6c4c2da5..fb7a892b 100644 --- a/idd/LGAudio/MiniportWaveRTStream.h +++ b/idd/LGAudio/MiniportWaveRTStream.h @@ -117,9 +117,13 @@ private: void AdvanceLocked(_In_ const AudioClockSnapshot &clock); void AdvanceEventPacketsLocked( _In_ const AudioClockSnapshot &clock); + bool RenderPacketCommittedLocked(_In_ ULONGLONG position) const; + void PublishRenderFramesLocked(); void UpdatePresentationQpcLocked( _In_ const AudioClockSnapshot &clock); - void TransferFramesLocked(_In_ ULONGLONG bytes); + void TransferFramesLocked( + _In_ ULONGLONG bytes, + _In_ bool transferRender = true); void SignalNotificationsLocked(); volatile LONG m_refCount; @@ -142,6 +146,7 @@ private: ULONG m_bufferSize; ULONG m_notificationCount; ULONG m_packetSize; + ULONG m_renderPublicationSliceBytes; PKEVENT m_notificationEvents[8]; ULONG m_notificationEventCount; @@ -154,6 +159,7 @@ private: ULONGLONG m_pendingBytes; ULONGLONG m_linearPosition; ULONG m_cyclicPosition; + ULONGLONG m_renderPublishedPosition; ULONGLONG m_completedPackets; ULONGLONG m_lastReadPacket; ULONG m_lastWritePacket;