[idd] audio: add the mediated PCM data plane

Expose cancel-safe direct-I/O queues for reading rendered frames and
writing captured frames through the private broker endpoint.

Separate render and capture storage, publish format and state
generations, and preserve local loopback while no broker is bound.
This commit is contained in:
Geoffrey McRae
2026-09-02 00:02:57 +10:00
parent e55cb8bd61
commit 5e07786468
6 changed files with 1322 additions and 63 deletions

View File

@@ -112,33 +112,140 @@ NTSTATUS InstallWaveSubdevice(
}
CAudioAdapter::CAudioAdapter() :
m_refCount(1)
m_refCount(1),
m_brokerActive(0),
m_renderDiscontinuity(0),
m_renderState{},
m_captureState{}
{
KeInitializeSpinLock(&m_transportLock);
KeInitializeSpinLock(&m_streamStateLock);
m_renderState.state = KSSTATE_STOP;
m_captureState.state = KSSTATE_STOP;
}
CAudioAdapter::~CAudioAdapter()
{
m_ring.Destroy();
m_captureRing.Destroy();
m_renderRing.Destroy();
}
NTSTATUS CAudioAdapter::Initialize()
{
NTSTATUS status = m_clock.Initialize();
if (NT_SUCCESS(status))
status = m_ring.Init();
status = m_renderRing.Init();
if (NT_SUCCESS(status))
status = m_captureRing.Init();
return status;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
NTSTATUS CAudioAdapter::ConfigureRender(
NTSTATUS CAudioAdapter::ConfigureStream(
_In_ EndpointKind kind,
_In_ const FormatFingerprint &format)
{
return m_ring.Configure(format);
CAudioRingBuffer &ring = kind == EndpointKind::Render ?
m_renderRing : m_captureRing;
KIRQL transportIrql;
KeAcquireSpinLock(&m_transportLock, &transportIrql);
const NTSTATUS status = ring.Configure(format);
if (!NT_SUCCESS(status))
{
KeReleaseSpinLock(&m_transportLock, transportIrql);
return status;
}
KIRQL oldIrql;
KeAcquireSpinLock(&m_streamStateLock, &oldIrql);
AudioStreamSnapshot &stream = kind == EndpointKind::Render ?
m_renderState : m_captureState;
stream.format = format;
stream.formatValid = true;
++stream.generation;
KeReleaseSpinLock(&m_streamStateLock, oldIrql);
KeReleaseSpinLock(&m_transportLock, transportIrql);
BrokerDeviceNotifyStreamChanged(this, kind);
return STATUS_SUCCESS;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
void CAudioAdapter::SetStreamState(
_In_ EndpointKind kind,
_In_ KSSTATE state)
{
bool changed;
bool reset;
KIRQL transportIrql;
KeAcquireSpinLock(&m_transportLock, &transportIrql);
KIRQL oldIrql;
KeAcquireSpinLock(&m_streamStateLock, &oldIrql);
AudioStreamSnapshot &stream = kind == EndpointKind::Render ?
m_renderState : m_captureState;
changed = stream.state != state;
reset = changed && state == KSSTATE_STOP;
stream.state = state;
if (reset)
++stream.generation;
KeReleaseSpinLock(&m_streamStateLock, oldIrql);
if (reset)
{
if (kind == EndpointKind::Render)
m_renderRing.Reset();
else
m_captureRing.Reset();
}
KeReleaseSpinLock(&m_transportLock, transportIrql);
if (changed)
BrokerDeviceNotifyStreamChanged(this, kind);
}
_IRQL_requires_max_(DISPATCH_LEVEL)
void CAudioAdapter::GetStreamSnapshot(
_In_ EndpointKind kind,
_Out_ AudioStreamSnapshot *snapshot)
{
if (!snapshot)
return;
KIRQL oldIrql;
KeAcquireSpinLock(&m_streamStateLock, &oldIrql);
*snapshot = kind == EndpointKind::Render ?
m_renderState : m_captureState;
KeReleaseSpinLock(&m_streamStateLock, oldIrql);
}
_IRQL_requires_max_(DISPATCH_LEVEL)
void CAudioAdapter::SetBrokerActive(_In_ bool active)
{
KIRQL oldIrql;
KeAcquireSpinLock(&m_transportLock, &oldIrql);
const LONG value = active ? 1 : 0;
if (InterlockedExchange(&m_brokerActive, value) == value)
{
KeReleaseSpinLock(&m_transportLock, oldIrql);
return;
}
m_captureRing.Reset();
m_renderRing.Reset();
InterlockedExchange(&m_renderDiscontinuity, active ? 1 : 0);
KeReleaseSpinLock(&m_transportLock, oldIrql);
}
_IRQL_requires_max_(DISPATCH_LEVEL)
void CAudioAdapter::ResetCapture()
{
m_ring.Reset();
KIRQL oldIrql;
KeAcquireSpinLock(&m_transportLock, &oldIrql);
if (InterlockedCompareExchange(&m_brokerActive, 0, 0))
m_captureRing.Reset();
else
m_renderRing.Reset();
KeReleaseSpinLock(&m_transportLock, oldIrql);
}
_IRQL_requires_max_(DISPATCH_LEVEL)
@@ -146,7 +253,14 @@ void CAudioAdapter::WriteRenderFrames(
_In_reads_bytes_(bytes) const BYTE *data,
_In_ ULONG bytes)
{
m_ring.Push(data, bytes);
ULONG droppedBytes = 0;
KIRQL oldIrql;
KeAcquireSpinLock(&m_transportLock, &oldIrql);
m_renderRing.Push(data, bytes, &droppedBytes);
if (droppedBytes)
InterlockedExchange(&m_renderDiscontinuity, 1);
KeReleaseSpinLock(&m_transportLock, oldIrql);
BrokerDeviceNotifyRenderAvailable(this, bytes, droppedBytes);
}
_IRQL_requires_max_(DISPATCH_LEVEL)
@@ -155,7 +269,16 @@ void CAudioAdapter::ReadCaptureFrames(
_Out_writes_bytes_(bytes) BYTE *data,
_In_ ULONG bytes)
{
m_ring.Pop(format, data, bytes);
KIRQL oldIrql;
KeAcquireSpinLock(&m_transportLock, &oldIrql);
const bool brokerActive =
InterlockedCompareExchange(&m_brokerActive, 0, 0) != 0;
CAudioRingBuffer &ring = brokerActive ?
m_captureRing : m_renderRing;
const ULONG copied = ring.Pop(format, data, bytes);
KeReleaseSpinLock(&m_transportLock, oldIrql);
if (brokerActive && copied < bytes)
BrokerDeviceRecordCaptureUnderrun(this, bytes - copied);
}
_IRQL_requires_max_(DISPATCH_LEVEL)
@@ -163,7 +286,107 @@ void CAudioAdapter::DiscardCaptureFrames(
_In_ const FormatFingerprint &format,
_In_ ULONGLONG bytes)
{
m_ring.Discard(format, bytes);
KIRQL oldIrql;
KeAcquireSpinLock(&m_transportLock, &oldIrql);
const bool brokerActive =
InterlockedCompareExchange(&m_brokerActive, 0, 0) != 0;
CAudioRingBuffer &ring = brokerActive ?
m_captureRing : m_renderRing;
const ULONG discarded = ring.Discard(format, bytes);
KeReleaseSpinLock(&m_transportLock, oldIrql);
if (brokerActive && bytes > discarded)
BrokerDeviceRecordCaptureUnderrun(this, bytes - discarded);
}
_IRQL_requires_max_(DISPATCH_LEVEL)
NTSTATUS CAudioAdapter::ReadRenderFrames(
_In_ const FormatFingerprint &format,
_In_ ULONGLONG streamGeneration,
_Out_writes_bytes_(capacity) BYTE *data,
_In_ ULONG capacity,
_Out_ PULONG bytesRead,
_Out_ bool *discontinuity)
{
if (!data || !bytesRead || !discontinuity || !format.blockAlign)
return STATUS_INVALID_PARAMETER;
*bytesRead = 0;
*discontinuity = false;
KIRQL transportIrql;
KeAcquireSpinLock(&m_transportLock, &transportIrql);
KIRQL stateIrql;
KeAcquireSpinLock(&m_streamStateLock, &stateIrql);
const bool active =
InterlockedCompareExchange(&m_brokerActive, 0, 0) != 0 &&
m_renderState.state == KSSTATE_RUN &&
m_renderState.formatValid &&
m_renderState.generation == streamGeneration &&
EqualFormatFingerprint(m_renderState.format, format);
KeReleaseSpinLock(&m_streamStateLock, stateIrql);
if (!active)
{
KeReleaseSpinLock(&m_transportLock, transportIrql);
return STATUS_DEVICE_NOT_READY;
}
ULONG bytes = m_renderRing.Available();
if (bytes > capacity)
bytes = capacity;
bytes -= bytes % format.blockAlign;
if (!bytes)
{
KeReleaseSpinLock(&m_transportLock, transportIrql);
return STATUS_SUCCESS;
}
const ULONG copied = m_renderRing.Pop(format, data, bytes);
if (copied)
{
*discontinuity = InterlockedExchange(
&m_renderDiscontinuity, 0) != 0;
}
*bytesRead = copied;
KeReleaseSpinLock(&m_transportLock, transportIrql);
return STATUS_SUCCESS;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
NTSTATUS CAudioAdapter::WriteCaptureFrames(
_In_ const FormatFingerprint &format,
_In_ ULONGLONG streamGeneration,
_In_ bool discontinuity,
_In_reads_bytes_(bytes) const BYTE *data,
_In_ ULONG bytes,
_Out_ PULONG droppedBytes)
{
if (!data || !droppedBytes)
return STATUS_INVALID_PARAMETER;
*droppedBytes = 0;
KIRQL transportIrql;
KeAcquireSpinLock(&m_transportLock, &transportIrql);
KIRQL stateIrql;
KeAcquireSpinLock(&m_streamStateLock, &stateIrql);
const bool active =
InterlockedCompareExchange(&m_brokerActive, 0, 0) != 0 &&
m_captureState.state == KSSTATE_RUN &&
m_captureState.formatValid &&
m_captureState.generation == streamGeneration &&
EqualFormatFingerprint(m_captureState.format, format);
KeReleaseSpinLock(&m_streamStateLock, stateIrql);
if (!active)
{
KeReleaseSpinLock(&m_transportLock, transportIrql);
return STATUS_DEVICE_NOT_READY;
}
if (discontinuity)
m_captureRing.Reset();
const ULONG written = m_captureRing.Push(data, bytes, droppedBytes);
KeReleaseSpinLock(&m_transportLock, transportIrql);
return written == bytes ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER;
}
_IRQL_requires_max_(DISPATCH_LEVEL)

View File

@@ -24,6 +24,15 @@
#include "Common.h"
#include "RingBuffer.h"
struct AudioStreamSnapshot
{
KSSTATE state;
bool formatValid;
UCHAR reserved[3];
ULONGLONG generation;
FormatFingerprint format;
};
class CAudioAdapter final : public IUnknown
{
public:
@@ -33,9 +42,23 @@ public:
NTSTATUS Initialize();
_IRQL_requires_max_(DISPATCH_LEVEL)
NTSTATUS ConfigureRender(
NTSTATUS ConfigureStream(
_In_ EndpointKind kind,
_In_ const FormatFingerprint &format);
_IRQL_requires_max_(DISPATCH_LEVEL)
void SetStreamState(
_In_ EndpointKind kind,
_In_ KSSTATE state);
_IRQL_requires_max_(DISPATCH_LEVEL)
void GetStreamSnapshot(
_In_ EndpointKind kind,
_Out_ AudioStreamSnapshot *snapshot);
_IRQL_requires_max_(DISPATCH_LEVEL)
void SetBrokerActive(_In_ bool active);
_IRQL_requires_max_(DISPATCH_LEVEL)
void ResetCapture();
@@ -55,6 +78,24 @@ public:
_In_ const FormatFingerprint &format,
_In_ ULONGLONG bytes);
_IRQL_requires_max_(DISPATCH_LEVEL)
NTSTATUS ReadRenderFrames(
_In_ const FormatFingerprint &format,
_In_ ULONGLONG streamGeneration,
_Out_writes_bytes_(capacity) BYTE *data,
_In_ ULONG capacity,
_Out_ PULONG bytesRead,
_Out_ bool *discontinuity);
_IRQL_requires_max_(DISPATCH_LEVEL)
NTSTATUS WriteCaptureFrames(
_In_ const FormatFingerprint &format,
_In_ ULONGLONG streamGeneration,
_In_ bool discontinuity,
_In_reads_bytes_(bytes) const BYTE *data,
_In_ ULONG bytes,
_Out_ PULONG droppedBytes);
_IRQL_requires_max_(DISPATCH_LEVEL)
AudioClockSnapshot GetClockSnapshot() const;
@@ -66,8 +107,15 @@ public:
private:
volatile LONG m_refCount;
volatile LONG m_brokerActive;
volatile LONG m_renderDiscontinuity;
KSPIN_LOCK m_transportLock;
KSPIN_LOCK m_streamStateLock;
AudioStreamSnapshot m_renderState;
AudioStreamSnapshot m_captureState;
CAudioClock m_clock;
CAudioRingBuffer m_ring;
CAudioRingBuffer m_renderRing;
CAudioRingBuffer m_captureRing;
};
NTSTATUS StartAudioDevice(

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,7 @@
#pragma once
#include "Common.h"
#include "Formats.h"
class CAudioAdapter;
@@ -75,3 +76,19 @@ CAudioAdapter *BrokerDeviceAcquireAdapter(
_IRQL_requires_max_(DISPATCH_LEVEL)
void BrokerDeviceReleaseAdapter();
_IRQL_requires_max_(DISPATCH_LEVEL)
void BrokerDeviceNotifyStreamChanged(
_In_ CAudioAdapter *adapter,
_In_ EndpointKind kind);
_IRQL_requires_max_(DISPATCH_LEVEL)
void BrokerDeviceNotifyRenderAvailable(
_In_ CAudioAdapter *adapter,
_In_ ULONG bytes,
_In_ ULONG droppedBytes);
_IRQL_requires_max_(DISPATCH_LEVEL)
void BrokerDeviceRecordCaptureUnderrun(
_In_ CAudioAdapter *adapter,
_In_ ULONGLONG bytes);

View File

@@ -114,8 +114,7 @@ NTSTATUS CMiniportWaveRTStream::Initialize(
m_format = *format;
m_fingerprint = fingerprint;
if (m_kind == EndpointKind::Render)
status = m_adapter->ConfigureRender(m_fingerprint);
status = m_adapter->ConfigureStream(m_kind, m_fingerprint);
return status;
}
@@ -195,8 +194,7 @@ NTSTATUS CMiniportWaveRTStream::SetFormat(
m_fingerprint = fingerprint;
KeReleaseSpinLock(&m_positionLock, oldIrql);
if (m_kind == EndpointKind::Render)
status = m_adapter->ConfigureRender(m_fingerprint);
status = m_adapter->ConfigureStream(m_kind, m_fingerprint);
return status;
}
@@ -244,6 +242,8 @@ NTSTATUS CMiniportWaveRTStream::SetState(_In_ KSSTATE state)
if (resetCapture)
m_adapter->ResetCapture();
m_adapter->SetStreamState(m_kind, KSSTATE_RUN);
KeAcquireSpinLock(&m_positionLock, &oldIrql);
if (m_state == KSSTATE_RUN)
ArmTimerLocked();
@@ -280,6 +280,7 @@ NTSTATUS CMiniportWaveRTStream::SetState(_In_ KSSTATE state)
KeReleaseSpinLock(&m_positionLock, oldIrql);
StopTimer();
m_adapter->SetStreamState(m_kind, state);
return STATUS_SUCCESS;
}
@@ -901,6 +902,7 @@ void CMiniportWaveRTStream::AdvanceEventPacketsLocked(
{
m_eosNotificationSent = true;
m_state = KSSTATE_PAUSE;
m_adapter->SetStreamState(m_kind, KSSTATE_PAUSE);
m_pendingBytes = 0;
m_frameRemainder = 0;
break;
@@ -944,6 +946,7 @@ void CMiniportWaveRTStream::TransferFramesLocked(_In_ ULONGLONG bytes)
if (m_linearPosition >= m_eosLinearPosition)
{
m_state = KSSTATE_PAUSE;
m_adapter->SetStreamState(m_kind, KSSTATE_PAUSE);
return;
}
@@ -997,7 +1000,10 @@ void CMiniportWaveRTStream::TransferFramesLocked(_In_ ULONGLONG bytes)
if (m_kind == EndpointKind::Render && m_eosReceived &&
m_linearPosition >= m_eosLinearPosition)
{
m_state = KSSTATE_PAUSE;
m_adapter->SetStreamState(m_kind, KSSTATE_PAUSE);
}
}
void CMiniportWaveRTStream::SignalNotificationsLocked()

View File

@@ -44,11 +44,12 @@ static const GUID LGAUDIO_BROKER_DEVICE_CLASS_GUID =
{ 0xb4, 0x90, 0x28, 0xc5, 0x30, 0xd2, 0x38, 0xcd } };
static constexpr UINT32 LGAUDIO_BROKER_ABI_VERSION = 1U;
// The transfer limits in LGAudioBrokerCaps exclude the packet header.
static constexpr UINT32 LGAUDIO_BROKER_MAX_PAYLOAD_BYTES = 64U * 1024U;
enum LGAudioBrokerCapability : UINT32
{
LGAUDIO_BROKER_CAP_CONTROL = 1U << 0,
// These remain clear until the corresponding data plane is implemented.
LGAUDIO_BROKER_CAP_RENDER_READ = 1U << 1,
LGAUDIO_BROKER_CAP_CAPTURE_WRITE = 1U << 2,
LGAUDIO_BROKER_CAP_CLOCK_CONTROL = 1U << 3
@@ -70,6 +71,22 @@ enum LGAudioBrokerStreamStateValue : UINT32
LGAUDIO_BROKER_STREAM_RUNNING = 4
};
enum LGAudioBrokerStreamFlag : UINT32
{
LGAUDIO_BROKER_STREAM_FORMAT_VALID = 1U << 0
};
enum LGAudioBrokerPacketDirection : UINT32
{
LGAUDIO_BROKER_PACKET_RENDER = 1,
LGAUDIO_BROKER_PACKET_CAPTURE = 2
};
enum LGAudioBrokerPacketFlag : UINT32
{
LGAUDIO_BROKER_PACKET_DISCONTINUITY = 1U << 0
};
static constexpr UINT32 IOCTL_LGAUDIO_BROKER_QUERY_CAPS =
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x900, METHOD_BUFFERED,
FILE_READ_ACCESS | FILE_WRITE_ACCESS);
@@ -106,12 +123,33 @@ struct LGAudioBrokerFormat
struct LGAudioBrokerStreamState
{
// Changes on format configuration and each transition to STOP.
UINT64 generation;
UINT32 state;
UINT32 flags;
LGAudioBrokerFormat format;
};
struct LGAudioBrokerPacketHeader
{
// READ returns this header followed by render payload. WRITE accepts this
// header followed by capture payload. size is always the header size.
UINT32 size;
UINT32 version;
UINT32 flags;
UINT32 direction;
UINT64 adapterGeneration;
UINT64 bindingGeneration;
UINT64 streamGeneration;
// Render sequence is monotonic for the driver lifetime. Capture starts at
// zero for each binding/stream-generation pair and must be contiguous.
UINT64 sequence;
LGAudioBrokerFormat format;
UINT32 frameCount;
UINT32 payloadBytes;
UINT32 reserved[2];
};
struct LGAudioBrokerCaps
{
UINT32 size;
@@ -160,7 +198,15 @@ struct LGAudioBrokerStats
UINT64 unbindCount;
UINT64 cleanupCount;
UINT64 rejectedRequestCount;
UINT64 reserved[2];
UINT64 renderPacketCount;
UINT64 renderByteCount;
UINT64 renderDropBytes;
UINT64 capturePacketCount;
UINT64 captureByteCount;
UINT64 captureDropBytes;
UINT64 captureUnderrunBytes;
UINT64 cancelledReadCount;
UINT64 reserved[4];
};
#pragma pack(pop)
@@ -171,11 +217,16 @@ static_assert(sizeof(LGAudioBrokerFormat) == 16,
"LGAudio broker format layout changed");
static_assert(sizeof(LGAudioBrokerStreamState) == 32,
"LGAudio broker stream-state layout changed");
static_assert(sizeof(LGAudioBrokerPacketHeader) == 80,
"LGAudio broker packet layout changed");
static_assert(sizeof(LGAudioBrokerCaps) == 64,
"LGAudio broker capabilities layout changed");
static_assert(sizeof(LGAudioBrokerState) == 104,
"LGAudio broker state layout changed");
static_assert(sizeof(LGAudioBrokerBind) == 48,
"LGAudio broker binding layout changed");
static_assert(sizeof(LGAudioBrokerStats) == 80,
static_assert(sizeof(LGAudioBrokerStats) == 160,
"LGAudio broker statistics layout changed");
static_assert(LGAUDIO_BROKER_MAX_PAYLOAD_BYTES >=
(192000U / 100U) * 8U * 3U,
"LGAudio broker payload cannot hold 10 ms at the maximum format");