[idd] audio: add virtual playback and capture driver

Add a WaveRT adapter with render and capture endpoints backed by a
local ring buffer and shared presentation clock.

Advertise PCM formats through 192 kHz, eight channels, and packed
24-bit samples. Register LGAudio in the IDD solution and package it as
Looking Glass Audio.
This commit is contained in:
Geoffrey McRae
2026-09-01 22:00:00 +10:00
parent 1b485305d9
commit 7c24b585af
23 changed files with 3876 additions and 0 deletions

253
idd/LGAudio/Adapter.cpp Normal file
View File

@@ -0,0 +1,253 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "Adapter.h"
#include "Formats.h"
#include "MiniportTopology.h"
#include "MiniportWaveRT.h"
namespace
{
NTSTATUS InstallTopologySubdevice(
_In_ PDEVICE_OBJECT deviceObject,
_In_ PIRP irp,
_In_ PRESOURCELIST resourceList,
_In_ CAudioAdapter *adapter,
_In_ EndpointKind kind,
_In_ const wchar_t *name,
_Outptr_ PUNKNOWN *portUnknown)
{
*portUnknown = nullptr;
PPORT port = nullptr;
NTSTATUS status = PcNewPort(&port, CLSID_PortTopology);
if (!NT_SUCCESS(status))
return status;
CMiniportTopology *miniport = new(NonPagedPoolNx, LGAUDIO_POOL_TAG)
CMiniportTopology(kind);
if (!miniport)
{
port->Release();
return STATUS_INSUFFICIENT_RESOURCES;
}
status = port->Init(deviceObject, irp,
static_cast<IMiniportTopology *>(miniport), adapter, resourceList);
if (NT_SUCCESS(status))
status = PcRegisterSubdevice(deviceObject,
const_cast<PWSTR>(name), port);
miniport->Release();
if (!NT_SUCCESS(status))
{
port->Release();
return status;
}
*portUnknown = port;
return STATUS_SUCCESS;
}
NTSTATUS InstallWaveSubdevice(
_In_ PDEVICE_OBJECT deviceObject,
_In_ PIRP irp,
_In_ PRESOURCELIST resourceList,
_In_ CAudioAdapter *adapter,
_In_ EndpointKind kind,
_In_ const wchar_t *name,
_Outptr_ PUNKNOWN *portUnknown)
{
*portUnknown = nullptr;
PPORT port = nullptr;
NTSTATUS status = PcNewPort(&port, CLSID_PortWaveRT);
if (!NT_SUCCESS(status))
return status;
CMiniportWaveRT *miniport = new(NonPagedPoolNx, LGAUDIO_POOL_TAG)
CMiniportWaveRT(adapter, kind);
if (!miniport)
{
port->Release();
return STATUS_INSUFFICIENT_RESOURCES;
}
status = port->Init(deviceObject, irp,
static_cast<IMiniportWaveRT *>(miniport), adapter, resourceList);
if (NT_SUCCESS(status))
status = PcRegisterSubdevice(deviceObject,
const_cast<PWSTR>(name), port);
miniport->Release();
if (!NT_SUCCESS(status))
{
port->Release();
return status;
}
*portUnknown = port;
return STATUS_SUCCESS;
}
}
CAudioAdapter::CAudioAdapter() :
m_refCount(1)
{
}
CAudioAdapter::~CAudioAdapter()
{
m_ring.Destroy();
}
NTSTATUS CAudioAdapter::Initialize()
{
NTSTATUS status = m_clock.Initialize();
if (NT_SUCCESS(status))
status = m_ring.Init();
return status;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
NTSTATUS CAudioAdapter::ConfigureRender(
_In_ const FormatFingerprint &format)
{
return m_ring.Configure(format);
}
_IRQL_requires_max_(DISPATCH_LEVEL)
void CAudioAdapter::ResetCapture()
{
m_ring.Reset();
}
_IRQL_requires_max_(DISPATCH_LEVEL)
void CAudioAdapter::WriteRenderFrames(
_In_reads_bytes_(bytes) const BYTE *data,
_In_ ULONG bytes)
{
m_ring.Push(data, bytes);
}
_IRQL_requires_max_(DISPATCH_LEVEL)
void CAudioAdapter::ReadCaptureFrames(
_In_ const FormatFingerprint &format,
_Out_writes_bytes_(bytes) BYTE *data,
_In_ ULONG bytes)
{
m_ring.Pop(format, data, bytes);
}
_IRQL_requires_max_(DISPATCH_LEVEL)
void CAudioAdapter::DiscardCaptureFrames(
_In_ const FormatFingerprint &format,
_In_ ULONGLONG bytes)
{
m_ring.Discard(format, bytes);
}
_IRQL_requires_max_(DISPATCH_LEVEL)
AudioClockSnapshot CAudioAdapter::GetClockSnapshot() const
{
return m_clock.GetSnapshot();
}
NTSTATUS CAudioAdapter::QueryInterface(
_In_ REFIID interfaceId,
_COM_Outptr_ void **object)
{
if (!object)
return STATUS_INVALID_PARAMETER;
*object = nullptr;
if (!GuidEqual(interfaceId, IID_IUnknown))
return STATUS_NOINTERFACE;
*object = static_cast<IUnknown *>(this);
AddRef();
return STATUS_SUCCESS;
}
ULONG CAudioAdapter::AddRef()
{
return static_cast<ULONG>(InterlockedIncrement(&m_refCount));
}
ULONG CAudioAdapter::Release()
{
const LONG count = InterlockedDecrement(&m_refCount);
if (!count)
delete this;
return static_cast<ULONG>(count);
}
NTSTATUS StartAudioDevice(
_In_ PDEVICE_OBJECT deviceObject,
_In_ PIRP irp,
_In_ PRESOURCELIST resourceList)
{
PUNKNOWN topologyRender = nullptr;
PUNKNOWN waveRender = nullptr;
PUNKNOWN topologyCapture = nullptr;
PUNKNOWN waveCapture = nullptr;
CAudioAdapter *adapter = new(NonPagedPoolNx, LGAUDIO_POOL_TAG)
CAudioAdapter();
if (!adapter)
return STATUS_INSUFFICIENT_RESOURCES;
NTSTATUS status = adapter->Initialize();
if (NT_SUCCESS(status))
status = InstallTopologySubdevice(deviceObject, irp, resourceList,
adapter, EndpointKind::Render, kTopologyRenderName, &topologyRender);
if (NT_SUCCESS(status))
status = InstallWaveSubdevice(deviceObject, irp, resourceList,
adapter, EndpointKind::Render, kWaveRenderName, &waveRender);
if (NT_SUCCESS(status))
status = InstallTopologySubdevice(deviceObject, irp, resourceList,
adapter, EndpointKind::Capture, kTopologyCaptureName,
&topologyCapture);
if (NT_SUCCESS(status))
status = InstallWaveSubdevice(deviceObject, irp, resourceList,
adapter, EndpointKind::Capture, kWaveCaptureName, &waveCapture);
if (NT_SUCCESS(status))
status = PcRegisterPhysicalConnection(deviceObject, waveRender,
kRenderWaveBridgePin, topologyRender, kRenderTopologyBridgePin);
if (NT_SUCCESS(status))
status = PcRegisterPhysicalConnection(deviceObject, topologyCapture,
kCaptureTopologyBridgePin, waveCapture, kCaptureWaveBridgePin);
SafeRelease(waveCapture);
SafeRelease(topologyCapture);
SafeRelease(waveRender);
SafeRelease(topologyRender);
adapter->Release();
if (!NT_SUCCESS(status))
LGAUDIO_TRACE_ERROR("device start failed: 0x%08X", status);
else
LGAUDIO_TRACE_INFO("render and capture endpoints registered (%s)",
"Looking Glass Audio");
return status;
}

76
idd/LGAudio/Adapter.h Normal file
View File

@@ -0,0 +1,76 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include "AudioClock.h"
#include "Common.h"
#include "RingBuffer.h"
class CAudioAdapter final : public IUnknown
{
public:
CAudioAdapter();
~CAudioAdapter();
NTSTATUS Initialize();
_IRQL_requires_max_(DISPATCH_LEVEL)
NTSTATUS ConfigureRender(
_In_ const FormatFingerprint &format);
_IRQL_requires_max_(DISPATCH_LEVEL)
void ResetCapture();
_IRQL_requires_max_(DISPATCH_LEVEL)
void WriteRenderFrames(
_In_reads_bytes_(bytes) const BYTE *data,
_In_ ULONG bytes);
_IRQL_requires_max_(DISPATCH_LEVEL)
void ReadCaptureFrames(
_In_ const FormatFingerprint &format,
_Out_writes_bytes_(bytes) BYTE *data,
_In_ ULONG bytes);
_IRQL_requires_max_(DISPATCH_LEVEL)
void DiscardCaptureFrames(
_In_ const FormatFingerprint &format,
_In_ ULONGLONG bytes);
_IRQL_requires_max_(DISPATCH_LEVEL)
AudioClockSnapshot GetClockSnapshot() const;
STDMETHODIMP_(NTSTATUS) QueryInterface(
_In_ REFIID interfaceId,
_COM_Outptr_ void **object) override;
STDMETHODIMP_(ULONG) AddRef() override;
STDMETHODIMP_(ULONG) Release() override;
private:
volatile LONG m_refCount;
CAudioClock m_clock;
CAudioRingBuffer m_ring;
};
NTSTATUS StartAudioDevice(
_In_ PDEVICE_OBJECT deviceObject,
_In_ PIRP irp,
_In_ PRESOURCELIST resourceList);

View File

@@ -0,0 +1,57 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "AudioClock.h"
namespace LGAudio
{
CAudioClock::CAudioClock() :
m_frequency(0)
{
}
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS CAudioClock::Initialize()
{
LARGE_INTEGER frequency{};
KeQueryPerformanceCounter(&frequency);
if (frequency.QuadPart <= 0)
return STATUS_DEVICE_HARDWARE_ERROR;
m_frequency = static_cast<ULONGLONG>(frequency.QuadPart);
return STATUS_SUCCESS;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
AudioClockSnapshot CAudioClock::GetSnapshot() const
{
const LARGE_INTEGER qpc = KeQueryPerformanceCounter(nullptr);
const ULONGLONG position = static_cast<ULONGLONG>(qpc.QuadPart);
// The fallback domains are identical, making the correlation exact.
return
{
position,
m_frequency,
position,
m_frequency
};
}
}

70
idd/LGAudio/AudioClock.h Normal file
View File

@@ -0,0 +1,70 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include "Common.h"
namespace LGAudio
{
/**
* A correlated sample of the audio time domain and the Windows QPC time
* domain. Position drives audio progress; QpcPosition timestamps that
* position for WaveRT clients.
*/
struct AudioClockSnapshot
{
ULONGLONG Position;
ULONGLONG Frequency;
ULONGLONG QpcPosition;
ULONGLONG QpcFrequency;
};
/**
* Adapter-owned clock shared by all render and capture streams.
*
* The local fallback uses QPC for both domains. A transport-disciplined
* implementation can later synthesize a continuous, monotonic Position
* while preserving its correlation with the local QPC fields. Before a
* non-unity clock rate is enabled, the stream deadline and timestamp
* conversions must consume an explicit fixed-point affine mapping between
* the two domains.
*/
class CAudioClock final
{
public:
CAudioClock();
CAudioClock(const CAudioClock &) = delete;
CAudioClock & operator=(const CAudioClock &) = delete;
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS Initialize();
_IRQL_requires_max_(DISPATCH_LEVEL)
AudioClockSnapshot GetSnapshot() const;
private:
ULONGLONG m_frequency;
};
}
using LGAudio::AudioClockSnapshot;
using LGAudio::CAudioClock;

79
idd/LGAudio/Common.h Normal file
View File

@@ -0,0 +1,79 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
extern "C"
{
#include <ntddk.h>
#include <portcls.h>
#include <ks.h>
#include <ksmedia.h>
}
#define LGAUDIO_POOL_TAG 'uAGL'
#define LGAUDIO_RING_TAG 'rAGL'
#define LGAUDIO_TRACE_ERROR(format, ...) \
KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, \
"LGAudio: " format "\n", __VA_ARGS__))
#define LGAUDIO_TRACE_INFO(format, ...) \
KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, \
"LGAudio: " format "\n", __VA_ARGS__))
void * __cdecl operator new(size_t size, POOL_TYPE poolType, ULONG tag);
void * __cdecl operator new(size_t size, POOL_FLAGS poolFlags, ULONG tag);
void __cdecl operator delete(void *memory) noexcept;
void __cdecl operator delete(void *memory, size_t size) noexcept;
void __cdecl operator delete(void *memory, POOL_TYPE poolType,
ULONG tag) noexcept;
void __cdecl operator delete(void *memory, POOL_FLAGS poolFlags,
ULONG tag) noexcept;
template<typename T>
inline void SafeRelease(T *&object)
{
if (object)
{
object->Release();
object = nullptr;
}
}
inline bool GuidEqual(REFGUID left, REFGUID right)
{
return !!IsEqualGUIDAligned(left, right);
}
constexpr ULONG kRenderWaveHostPin = 0;
constexpr ULONG kRenderWaveBridgePin = 1;
constexpr ULONG kRenderTopologyBridgePin = 0;
constexpr ULONG kRenderTopologyEndpointPin = 1;
constexpr ULONG kCaptureWaveBridgePin = 0;
constexpr ULONG kCaptureWaveHostPin = 1;
constexpr ULONG kCaptureTopologyEndpointPin = 0;
constexpr ULONG kCaptureTopologyBridgePin = 1;
constexpr wchar_t kWaveRenderName[] = L"WaveRender";
constexpr wchar_t kTopologyRenderName[] = L"TopologyRender";
constexpr wchar_t kWaveCaptureName[] = L"WaveCapture";
constexpr wchar_t kTopologyCaptureName[] = L"TopologyCapture";

99
idd/LGAudio/Driver.cpp Normal file
View File

@@ -0,0 +1,99 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "Common.h"
#include "Adapter.h"
extern "C" DRIVER_INITIALIZE DriverEntry;
namespace
{
NTSTATUS AddAudioDevice(
_In_ PDRIVER_OBJECT driverObject,
_In_ PDEVICE_OBJECT physicalDeviceObject)
{
constexpr ULONG kSubdeviceCount = 4;
return PcAddAdapterDevice(driverObject, physicalDeviceObject,
StartAudioDevice, kSubdeviceCount, 0);
}
}
extern "C" NTSTATUS DriverEntry(
_In_ PDRIVER_OBJECT driverObject,
_In_ PUNICODE_STRING registryPath)
{
return PcInitializeAdapterDriver(driverObject, registryPath,
AddAudioDevice);
}
#pragma warning(push)
#pragma warning(disable: 4996)
void * __cdecl operator new(
size_t size,
POOL_TYPE poolType,
ULONG tag)
{
void *memory = ExAllocatePoolWithTag(poolType, size, tag);
if (memory)
RtlZeroMemory(memory, size);
return memory;
}
void * __cdecl operator new(
size_t size,
POOL_FLAGS poolFlags,
ULONG tag)
{
const POOL_TYPE poolType = (poolFlags & POOL_FLAG_PAGED) ?
PagedPool : NonPagedPoolNx;
return operator new(size, poolType, tag);
}
#pragma warning(pop)
void __cdecl operator delete(void *memory) noexcept
{
if (memory)
ExFreePool(memory);
}
void __cdecl operator delete(void *memory, size_t size) noexcept
{
UNREFERENCED_PARAMETER(size);
operator delete(memory);
}
void __cdecl operator delete(
void *memory,
POOL_TYPE poolType,
ULONG tag) noexcept
{
UNREFERENCED_PARAMETER(poolType);
UNREFERENCED_PARAMETER(tag);
operator delete(memory);
}
void __cdecl operator delete(
void *memory,
POOL_FLAGS poolFlags,
ULONG tag) noexcept
{
UNREFERENCED_PARAMETER(poolFlags);
UNREFERENCED_PARAMETER(tag);
operator delete(memory);
}

455
idd/LGAudio/Formats.cpp Normal file
View File

@@ -0,0 +1,455 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "Formats.h"
namespace LGAudio
{
namespace
{
constexpr USHORT kExtensibleExtraSize = static_cast<USHORT>(
sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX));
bool IsAudioPcmWaveFormat(_In_ const KSDATAFORMAT * format)
{
return
IsEqualGUIDAligned(format->MajorFormat,
KSDATAFORMAT_TYPE_AUDIO) &&
IsEqualGUIDAligned(format->SubFormat,
KSDATAFORMAT_SUBTYPE_PCM) &&
IsEqualGUIDAligned(format->Specifier,
KSDATAFORMAT_SPECIFIER_WAVEFORMATEX);
}
bool WaveFormatCoreMatches(
_In_ const WAVEFORMATEX & candidate,
_In_ const WAVEFORMATEX & requested)
{
return
candidate.nChannels == requested.nChannels &&
candidate.nSamplesPerSec == requested.nSamplesPerSec &&
candidate.nAvgBytesPerSec == requested.nAvgBytesPerSec &&
candidate.nBlockAlign == requested.nBlockAlign &&
candidate.wBitsPerSample == requested.wBitsPerSample;
}
bool ExtensibleFormatMatches(
_In_ const WAVEFORMATEXTENSIBLE & candidate,
_In_ const WAVEFORMATEXTENSIBLE & requested)
{
return
WaveFormatCoreMatches(candidate.Format, requested.Format) &&
candidate.Samples.wValidBitsPerSample ==
requested.Samples.wValidBitsPerSample &&
candidate.dwChannelMask == requested.dwChannelMask &&
IsEqualGUIDAligned(candidate.SubFormat, requested.SubFormat);
}
void GetFormatList(
_In_ EndpointKind endpoint,
_Outptr_result_buffer_(*count)
KSDATAFORMAT_WAVEFORMATEXTENSIBLE ** formats,
_Out_ PULONG count)
{
if (endpoint == EndpointKind::Render)
{
*formats = g_renderFormats;
*count = kRenderFormatCount;
}
else
{
*formats = g_captureFormats;
*count = kCaptureFormatCount;
}
}
void GetDataRangeList(
_In_ EndpointKind endpoint,
_Outptr_result_buffer_(*count) KSDATARANGE_AUDIO ** ranges,
_Out_ PULONG count)
{
if (endpoint == EndpointKind::Render)
{
*ranges = g_renderDataRanges;
*count = kRenderFormatCount;
}
else
{
*ranges = g_captureDataRanges;
*count = kCaptureFormatCount;
}
}
bool AudioRangeContainsFormat(
_In_ const KSDATARANGE_AUDIO & range,
_In_ const KSDATAFORMAT_WAVEFORMATEXTENSIBLE & format)
{
const WAVEFORMATEX & wave = format.WaveFormatExt.Format;
return
range.MaximumChannels == wave.nChannels &&
range.MinimumBitsPerSample <= wave.wBitsPerSample &&
range.MaximumBitsPerSample >= wave.wBitsPerSample &&
range.MinimumSampleFrequency <= wave.nSamplesPerSec &&
range.MaximumSampleFrequency >= wave.nSamplesPerSec;
}
bool DataRangeIdentifiesFormat(
_In_ const KSDATARANGE_AUDIO & range,
_In_ const KSDATAFORMAT_WAVEFORMATEXTENSIBLE & format)
{
const WAVEFORMATEX & wave = format.WaveFormatExt.Format;
return
range.MaximumChannels == wave.nChannels &&
range.MinimumBitsPerSample == wave.wBitsPerSample &&
range.MaximumBitsPerSample == wave.wBitsPerSample &&
range.MinimumSampleFrequency == wave.nSamplesPerSec &&
range.MaximumSampleFrequency == wave.nSamplesPerSec;
}
}
#define LG_AUDIO_FORMAT( \
sampleRate_, channels_, containerBits_, validBits_, mask_) \
{ \
{ \
sizeof(KSDATAFORMAT_WAVEFORMATEXTENSIBLE), \
0, \
0, \
0, \
STATICGUIDOF(KSDATAFORMAT_TYPE_AUDIO), \
STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM), \
STATICGUIDOF(KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) \
}, \
{ \
{ \
WAVE_FORMAT_EXTENSIBLE, \
channels_, \
sampleRate_, \
sampleRate_ * channels_ * (containerBits_ / 8), \
channels_ * (containerBits_ / 8), \
containerBits_, \
kExtensibleExtraSize \
}, \
validBits_, \
mask_, \
STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM) \
} \
}
#define LG_RENDER_FORMATS(sampleRate_) \
LG_AUDIO_FORMAT(sampleRate_, 2, 16, 16, KSAUDIO_SPEAKER_STEREO), \
LG_AUDIO_FORMAT(sampleRate_, 2, 24, 24, KSAUDIO_SPEAKER_STEREO), \
LG_AUDIO_FORMAT(sampleRate_, 6, 24, 24, \
KSAUDIO_SPEAKER_5POINT1_SURROUND), \
LG_AUDIO_FORMAT(sampleRate_, 8, 24, 24, \
KSAUDIO_SPEAKER_7POINT1_SURROUND)
#define LG_CAPTURE_FORMATS(sampleRate_) \
LG_AUDIO_FORMAT(sampleRate_, 1, 16, 16, KSAUDIO_SPEAKER_MONO), \
LG_RENDER_FORMATS(sampleRate_)
KSDATAFORMAT_WAVEFORMATEXTENSIBLE
g_renderFormats[kRenderFormatCount] =
{
LG_RENDER_FORMATS(48000),
LG_RENDER_FORMATS(44100),
LG_RENDER_FORMATS(88200),
LG_RENDER_FORMATS(96000),
LG_RENDER_FORMATS(176400),
LG_RENDER_FORMATS(192000)
};
KSDATAFORMAT_WAVEFORMATEXTENSIBLE
g_captureFormats[kCaptureFormatCount] =
{
LG_CAPTURE_FORMATS(48000),
LG_CAPTURE_FORMATS(44100),
LG_CAPTURE_FORMATS(88200),
LG_CAPTURE_FORMATS(96000),
LG_CAPTURE_FORMATS(176400),
LG_CAPTURE_FORMATS(192000)
};
#undef LG_CAPTURE_FORMATS
#undef LG_RENDER_FORMATS
#undef LG_AUDIO_FORMAT
#define LG_AUDIO_RANGE(sampleRate_, channels_, containerBits_) \
{ \
{ \
sizeof(KSDATARANGE_AUDIO), \
0, \
0, \
0, \
STATICGUIDOF(KSDATAFORMAT_TYPE_AUDIO), \
STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM), \
STATICGUIDOF(KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) \
}, \
channels_, \
containerBits_, \
containerBits_, \
sampleRate_, \
sampleRate_ \
}
#define LG_RENDER_RANGES(sampleRate_) \
LG_AUDIO_RANGE(sampleRate_, 2, 16), \
LG_AUDIO_RANGE(sampleRate_, 2, 24), \
LG_AUDIO_RANGE(sampleRate_, 6, 24), \
LG_AUDIO_RANGE(sampleRate_, 8, 24)
#define LG_CAPTURE_RANGES(sampleRate_) \
LG_AUDIO_RANGE(sampleRate_, 1, 16), \
LG_RENDER_RANGES(sampleRate_)
KSDATARANGE_AUDIO g_renderDataRanges[kRenderFormatCount] =
{
LG_RENDER_RANGES(48000),
LG_RENDER_RANGES(44100),
LG_RENDER_RANGES(88200),
LG_RENDER_RANGES(96000),
LG_RENDER_RANGES(176400),
LG_RENDER_RANGES(192000)
};
KSDATARANGE_AUDIO g_captureDataRanges[kCaptureFormatCount] =
{
LG_CAPTURE_RANGES(48000),
LG_CAPTURE_RANGES(44100),
LG_CAPTURE_RANGES(88200),
LG_CAPTURE_RANGES(96000),
LG_CAPTURE_RANGES(176400),
LG_CAPTURE_RANGES(192000)
};
#undef LG_CAPTURE_RANGES
#undef LG_RENDER_RANGES
#undef LG_AUDIO_RANGE
#define LG_RENDER_RANGE_POINTERS(base_) \
(PKSDATARANGE)&g_renderDataRanges[(base_)], \
(PKSDATARANGE)&g_renderDataRanges[(base_) + 1], \
(PKSDATARANGE)&g_renderDataRanges[(base_) + 2], \
(PKSDATARANGE)&g_renderDataRanges[(base_) + 3]
#define LG_CAPTURE_RANGE_POINTERS(base_) \
(PKSDATARANGE)&g_captureDataRanges[(base_)], \
(PKSDATARANGE)&g_captureDataRanges[(base_) + 1], \
(PKSDATARANGE)&g_captureDataRanges[(base_) + 2], \
(PKSDATARANGE)&g_captureDataRanges[(base_) + 3], \
(PKSDATARANGE)&g_captureDataRanges[(base_) + 4]
PKSDATARANGE g_renderDataRangePointers[kRenderFormatCount] =
{
LG_RENDER_RANGE_POINTERS(0),
LG_RENDER_RANGE_POINTERS(4),
LG_RENDER_RANGE_POINTERS(8),
LG_RENDER_RANGE_POINTERS(12),
LG_RENDER_RANGE_POINTERS(16),
LG_RENDER_RANGE_POINTERS(20)
};
PKSDATARANGE g_captureDataRangePointers[kCaptureFormatCount] =
{
LG_CAPTURE_RANGE_POINTERS(0),
LG_CAPTURE_RANGE_POINTERS(5),
LG_CAPTURE_RANGE_POINTERS(10),
LG_CAPTURE_RANGE_POINTERS(15),
LG_CAPTURE_RANGE_POINTERS(20),
LG_CAPTURE_RANGE_POINTERS(25)
};
#undef LG_CAPTURE_RANGE_POINTERS
#undef LG_RENDER_RANGE_POINTERS
_IRQL_requires_max_(DISPATCH_LEVEL)
NTSTATUS ValidateFormat(
_In_ EndpointKind endpoint,
_In_ const KSDATAFORMAT * dataFormat,
_Outptr_opt_result_maybenull_
const KSDATAFORMAT_WAVEFORMATEXTENSIBLE ** matchedFormat)
{
if (matchedFormat)
*matchedFormat = nullptr;
if (!dataFormat ||
dataFormat->FormatSize < sizeof(KSDATAFORMAT_WAVEFORMATEX) ||
!IsAudioPcmWaveFormat(dataFormat))
return STATUS_NO_MATCH;
const WAVEFORMATEX * requested =
reinterpret_cast<const WAVEFORMATEX *>(dataFormat + 1);
KSDATAFORMAT_WAVEFORMATEXTENSIBLE * formats;
ULONG count;
GetFormatList(endpoint, &formats, &count);
for (ULONG i = 0; i < count; ++i)
{
const KSDATAFORMAT_WAVEFORMATEXTENSIBLE & candidate = formats[i];
const WAVEFORMATEXTENSIBLE & candidateWave = candidate.WaveFormatExt;
if (!WaveFormatCoreMatches(candidateWave.Format, *requested))
continue;
if (requested->wFormatTag == WAVE_FORMAT_PCM)
{
// A stand-alone WAVEFORMATEX is unambiguous only for these legacy
// mono/stereo, 16-bit formats.
if (candidateWave.Format.wBitsPerSample != 16 ||
candidateWave.Samples.wValidBitsPerSample != 16 ||
candidateWave.Format.nChannels > 2)
continue;
}
else if (requested->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
{
if (dataFormat->FormatSize <
sizeof(KSDATAFORMAT_WAVEFORMATEXTENSIBLE) ||
requested->cbSize < kExtensibleExtraSize)
continue;
const WAVEFORMATEXTENSIBLE * requestedExtensible =
reinterpret_cast<const WAVEFORMATEXTENSIBLE *>(requested);
if (!ExtensibleFormatMatches(candidateWave, *requestedExtensible))
continue;
}
else
continue;
if (matchedFormat)
*matchedFormat = &candidate;
return STATUS_SUCCESS;
}
return STATUS_NO_MATCH;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
NTSTATUS SelectFormatIntersection(
_In_ EndpointKind endpoint,
_In_ const KSDATARANGE * clientDataRange,
_In_ const KSDATARANGE * driverDataRange,
_In_ ULONG outputBufferLength,
_Out_writes_bytes_to_opt_(outputBufferLength, *resultantFormatLength)
PVOID resultantFormat,
_Out_ PULONG resultantFormatLength)
{
if (!clientDataRange || !driverDataRange || !resultantFormatLength)
return STATUS_INVALID_PARAMETER;
*resultantFormatLength = 0;
if (clientDataRange->FormatSize < sizeof(KSDATARANGE_AUDIO) ||
driverDataRange->FormatSize < sizeof(KSDATARANGE_AUDIO) ||
!IsAudioPcmWaveFormat(clientDataRange) ||
!IsAudioPcmWaveFormat(driverDataRange))
return STATUS_NO_MATCH;
const KSDATARANGE_AUDIO & client =
*reinterpret_cast<const KSDATARANGE_AUDIO *>(clientDataRange);
const KSDATARANGE_AUDIO & driver =
*reinterpret_cast<const KSDATARANGE_AUDIO *>(driverDataRange);
KSDATAFORMAT_WAVEFORMATEXTENSIBLE * formats;
KSDATARANGE_AUDIO * ranges;
ULONG formatCount;
ULONG rangeCount;
GetFormatList(endpoint, &formats, &formatCount);
GetDataRangeList(endpoint, &ranges, &rangeCount);
if (formatCount != rangeCount)
return STATUS_INTERNAL_ERROR;
const KSDATAFORMAT_WAVEFORMATEXTENSIBLE * selected = nullptr;
for (ULONG i = 0; i < formatCount; ++i)
{
if (!DataRangeIdentifiesFormat(driver, formats[i]) ||
!DataRangeIdentifiesFormat(ranges[i], formats[i]) ||
!AudioRangeContainsFormat(client, formats[i]))
continue;
selected = &formats[i];
break;
}
if (!selected)
return STATUS_NO_MATCH;
constexpr ULONG requiredSize =
sizeof(KSDATAFORMAT_WAVEFORMATEXTENSIBLE);
*resultantFormatLength = requiredSize;
if (outputBufferLength == 0)
return STATUS_BUFFER_OVERFLOW;
if (outputBufferLength < requiredSize)
return STATUS_BUFFER_TOO_SMALL;
if (!resultantFormat)
return STATUS_INVALID_PARAMETER;
RtlCopyMemory(resultantFormat, selected, requiredSize);
return STATUS_SUCCESS;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
NTSTATUS GetFormatFingerprint(
_In_ const KSDATAFORMAT_WAVEFORMATEXTENSIBLE * dataFormat,
_Out_ FormatFingerprint * fingerprint)
{
if (!dataFormat || !fingerprint)
return STATUS_INVALID_PARAMETER;
const WAVEFORMATEXTENSIBLE & wave = dataFormat->WaveFormatExt;
if (wave.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE ||
wave.Format.cbSize < kExtensibleExtraSize ||
wave.Format.nChannels == 0 ||
wave.Format.nBlockAlign == 0 ||
wave.Format.wBitsPerSample == 0 ||
wave.Samples.wValidBitsPerSample == 0 ||
wave.Samples.wValidBitsPerSample >
wave.Format.wBitsPerSample ||
!IsAudioPcmWaveFormat(&dataFormat->DataFormat) ||
!IsEqualGUIDAligned(wave.SubFormat,
KSDATAFORMAT_SUBTYPE_PCM))
return STATUS_INVALID_PARAMETER;
fingerprint->sampleRate = wave.Format.nSamplesPerSec;
fingerprint->channelMask = wave.dwChannelMask;
fingerprint->channels = wave.Format.nChannels;
fingerprint->containerBits = wave.Format.wBitsPerSample;
fingerprint->validBits = wave.Samples.wValidBitsPerSample;
fingerprint->blockAlign = wave.Format.nBlockAlign;
return STATUS_SUCCESS;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
bool EqualFormatFingerprint(
_In_ const FormatFingerprint & left,
_In_ const FormatFingerprint & right)
{
return
left.sampleRate == right.sampleRate &&
left.channelMask == right.channelMask &&
left.channels == right.channels &&
left.containerBits == right.containerBits &&
left.validBits == right.validBits &&
left.blockAlign == right.blockAlign;
}
}

125
idd/LGAudio/Formats.h Normal file
View File

@@ -0,0 +1,125 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include <portcls.h>
#include <ks.h>
#include <ksmedia.h>
namespace LGAudio
{
enum class EndpointKind : UCHAR
{
Render,
Capture
};
struct FormatFingerprint
{
ULONG sampleRate;
ULONG channelMask;
USHORT channels;
USHORT containerBits;
USHORT validBits;
USHORT blockAlign;
};
constexpr ULONG kSampleRate = 48000;
constexpr ULONG kSampleRateCount = 6;
constexpr ULONG kRenderFormatsPerRate = 4;
constexpr ULONG kCaptureFormatsPerRate = 5;
constexpr ULONG kRenderFormatCount =
kSampleRateCount * kRenderFormatsPerRate;
constexpr ULONG kCaptureFormatCount =
kSampleRateCount * kCaptureFormatsPerRate;
constexpr ULONG kMaximumSampleRate = 192000;
constexpr ULONG kMaximumBlockAlign =
8 * 3;
constexpr ULONG kMaximumBytesPerSecond =
kMaximumSampleRate * kMaximumBlockAlign;
extern KSDATAFORMAT_WAVEFORMATEXTENSIBLE
g_renderFormats[kRenderFormatCount];
extern KSDATAFORMAT_WAVEFORMATEXTENSIBLE
g_captureFormats[kCaptureFormatCount];
extern KSDATARANGE_AUDIO g_renderDataRanges[kRenderFormatCount];
extern KSDATARANGE_AUDIO g_captureDataRanges[kCaptureFormatCount];
extern PKSDATARANGE g_renderDataRangePointers[kRenderFormatCount];
extern PKSDATARANGE g_captureDataRangePointers[kCaptureFormatCount];
/**
* Validate an instantiated WaveRT format against the endpoint's exact
* discrete format list.
*/
_IRQL_requires_max_(DISPATCH_LEVEL)
NTSTATUS ValidateFormat(
_In_ EndpointKind endpoint,
_In_ const KSDATAFORMAT * dataFormat,
_Outptr_opt_result_maybenull_
const KSDATAFORMAT_WAVEFORMATEXTENSIBLE ** matchedFormat);
/**
* Select the exact WAVEFORMATEXTENSIBLE result for an IMiniport data-range
* intersection request.
*/
_IRQL_requires_max_(DISPATCH_LEVEL)
NTSTATUS SelectFormatIntersection(
_In_ EndpointKind endpoint,
_In_ const KSDATARANGE * clientDataRange,
_In_ const KSDATARANGE * driverDataRange,
_In_ ULONG outputBufferLength,
_Out_writes_bytes_to_opt_(outputBufferLength, *resultantFormatLength)
PVOID resultantFormat,
_Out_ PULONG resultantFormatLength);
_IRQL_requires_max_(DISPATCH_LEVEL)
NTSTATUS GetFormatFingerprint(
_In_ const KSDATAFORMAT_WAVEFORMATEXTENSIBLE * dataFormat,
_Out_ FormatFingerprint * fingerprint);
_IRQL_requires_max_(DISPATCH_LEVEL)
bool EqualFormatFingerprint(
_In_ const FormatFingerprint & left,
_In_ const FormatFingerprint & right);
}
// The rest of the driver uses these as its shared, unqualified audio types.
using LGAudio::EndpointKind;
using LGAudio::FormatFingerprint;
using LGAudio::EqualFormatFingerprint;
using LGAudio::GetFormatFingerprint;
using LGAudio::SelectFormatIntersection;
using LGAudio::ValidateFormat;
using LGAudio::g_captureDataRangePointers;
using LGAudio::g_captureDataRanges;
using LGAudio::g_captureFormats;
using LGAudio::g_renderDataRangePointers;
using LGAudio::g_renderDataRanges;
using LGAudio::g_renderFormats;
using LGAudio::kCaptureFormatCount;
using LGAudio::kMaximumBlockAlign;
using LGAudio::kMaximumBytesPerSecond;
using LGAudio::kMaximumSampleRate;
using LGAudio::kRenderFormatCount;
using LGAudio::kSampleRate;
using LGAudio::kSampleRateCount;

22
idd/LGAudio/Guids.cpp Normal file
View File

@@ -0,0 +1,22 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <initguid.h>
#include "Common.h"

23
idd/LGAudio/Guids.h Normal file
View File

@@ -0,0 +1,23 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
// Standard PortCls, KS, and audio GUID storage is emitted by Guids.cpp.

125
idd/LGAudio/LGAudio.inx Normal file
View File

@@ -0,0 +1,125 @@
[Version]
Signature="$Windows NT$"
Class=MEDIA
ClassGuid={4D36E96C-E325-11CE-BFC1-08002BE10318}
Provider=%ProviderName%
DriverVer=08/21/2026,0.1.0.0
CatalogFile=LGAudio.cat
PnpLockDown=1
[SourceDisksNames]
1=%DiskName%,,,
[SourceDisksFiles]
LGAudio.sys=1
[Manufacturer]
%ManufacturerName%=LGAudio,NT$ARCH$.10.0...16299
[LGAudio.NT$ARCH$.10.0...16299]
%DeviceName%=LGAudio_Device,Root\LGAudio
[DestinationDirs]
LGAudio_CopyFiles=13
[LGAudio_CopyFiles]
LGAudio.sys
[LGAudio_Device.NT]
Include=ks.inf,wdmaudio.inf
Needs=KS.Registration,WDMAUDIO.Registration
CopyFiles=LGAudio_CopyFiles
AddReg=LGAudio_Device.AddReg
[LGAudio_Device.AddReg]
HKR,,AssociatedFilters,,"wdmaud,swmidi,redbook"
HKR,,Driver,,LGAudio.sys
HKR,Drivers,SubClasses,,"wave,midi,mixer"
HKR,Drivers\wave\wdmaud.drv,Driver,,wdmaud.drv
HKR,Drivers\midi\wdmaud.drv,Driver,,wdmaud.drv
HKR,Drivers\mixer\wdmaud.drv,Driver,,wdmaud.drv
HKR,Drivers\wave\wdmaud.drv,Description,,%DeviceName%
HKR,Drivers\mixer\wdmaud.drv,Description,,%DeviceName%
[LGAudio_Device.NT.Interfaces]
AddInterface=%KSCATEGORY_AUDIO%,%KSNAME_WaveRender%,LGAudio.WaveRender
AddInterface=%KSCATEGORY_RENDER%,%KSNAME_WaveRender%,LGAudio.WaveRender
AddInterface=%KSCATEGORY_REALTIME%,%KSNAME_WaveRender%,LGAudio.WaveRender
AddInterface=%KSCATEGORY_AUDIO%,%KSNAME_TopologyRender%,LGAudio.TopologyRender
AddInterface=%KSCATEGORY_TOPOLOGY%,%KSNAME_TopologyRender%,LGAudio.TopologyRender
AddInterface=%KSCATEGORY_AUDIO%,%KSNAME_WaveCapture%,LGAudio.WaveCapture
AddInterface=%KSCATEGORY_CAPTURE%,%KSNAME_WaveCapture%,LGAudio.WaveCapture
AddInterface=%KSCATEGORY_REALTIME%,%KSNAME_WaveCapture%,LGAudio.WaveCapture
AddInterface=%KSCATEGORY_AUDIO%,%KSNAME_TopologyCapture%,LGAudio.TopologyCapture
AddInterface=%KSCATEGORY_TOPOLOGY%,%KSNAME_TopologyCapture%,LGAudio.TopologyCapture
[LGAudio.WaveRender]
AddReg=LGAudio.WaveRender.AddReg
[LGAudio.WaveRender.AddReg]
HKR,,CLSID,,%Proxy.CLSID%
HKR,,FriendlyName,,%EndpointName%
[LGAudio.TopologyRender]
AddReg=LGAudio.TopologyRender.AddReg
[LGAudio.TopologyRender.AddReg]
HKR,,CLSID,,%Proxy.CLSID%
HKR,,FriendlyName,,%EndpointName%
HKR,EP\0,%PKEY_AudioEndpoint_Association%,,%KSNODETYPE_ANY%
HKR,EP\0,%PKEY_AudioEndpoint_Supports_EventDriven_Mode%,0x00010001,1
[LGAudio.WaveCapture]
AddReg=LGAudio.WaveCapture.AddReg
[LGAudio.WaveCapture.AddReg]
HKR,,CLSID,,%Proxy.CLSID%
HKR,,FriendlyName,,%EndpointName%
[LGAudio.TopologyCapture]
AddReg=LGAudio.TopologyCapture.AddReg
[LGAudio.TopologyCapture.AddReg]
HKR,,CLSID,,%Proxy.CLSID%
HKR,,FriendlyName,,%EndpointName%
HKR,EP\0,%PKEY_AudioEndpoint_Association%,,%KSNODETYPE_ANY%
HKR,EP\0,%PKEY_AudioEndpoint_Supports_EventDriven_Mode%,0x00010001,1
[LGAudio_Device.NT.Services]
AddService=LGAudio,0x00000002,LGAudio_Service
[LGAudio_Service]
DisplayName=%ServiceName%
ServiceType=1
StartType=3
ErrorControl=1
ServiceBinary=%13%\LGAudio.sys
[LGAudio_Device.NT.HW]
AddReg=LGAudio_HardwareSettings
[LGAudio_HardwareSettings]
HKR,,DeviceType,0x00010001,0x0000001D
HKR,,Security,,"D:P(A;;GA;;;SY)(A;;GRGWGX;;;BA)(A;;GRGWGX;;;WD)(A;;GRGWGX;;;RC)"
[Strings]
ProviderName="Looking Glass"
ManufacturerName="Looking Glass"
DiskName="Looking Glass Audio Installation Disk"
DeviceName="Looking Glass Audio"
EndpointName="Looking Glass Audio"
ServiceName="Looking Glass Audio Driver"
KSNAME_WaveRender="WaveRender"
KSNAME_TopologyRender="TopologyRender"
KSNAME_WaveCapture="WaveCapture"
KSNAME_TopologyCapture="TopologyCapture"
Proxy.CLSID="{17CCA71B-ECD7-11D0-B908-00A0C9223196}"
KSCATEGORY_AUDIO="{6994AD04-93EF-11D0-A3CC-00A0C9223196}"
KSCATEGORY_RENDER="{65E8773E-8F56-11D0-A3B9-00A0C9223196}"
KSCATEGORY_CAPTURE="{65E8773D-8F56-11D0-A3B9-00A0C9223196}"
KSCATEGORY_REALTIME="{EB115FFC-10C8-4964-831D-6DCB02E6F23F}"
KSCATEGORY_TOPOLOGY="{DDA54A40-1E4C-11D1-A050-405705C10000}"
KSNODETYPE_ANY="{00000000-0000-0000-0000-000000000000}"
PKEY_AudioEndpoint_Association="{1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E},2"
PKEY_AudioEndpoint_Supports_EventDriven_Mode="{1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E},7"

31
idd/LGAudio/LGAudio.rc Normal file
View File

@@ -0,0 +1,31 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <windows.h>
#include <ntverp.h>
#define VER_FILETYPE VFT_DRV
#define VER_FILESUBTYPE VFT2_DRV_SOUND
#define VER_FILEDESCRIPTION_STR "Looking Glass Audio Driver"
#define VER_INTERNALNAME_STR "LGAudio.sys"
#define VER_ORIGINALFILENAME_STR "LGAudio.sys"
#define VER_LEGALCOPYRIGHT_STR "Copyright (C) 2026 Looking Glass"
#include "common.ver"

104
idd/LGAudio/LGAudio.vcxproj Normal file
View File

@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="12.0"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{02146E7A-FA7C-4071-8759-8B9A89452367}</ProjectGuid>
<RootNamespace>LGAudio</RootNamespace>
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
<Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
<Platform Condition="'$(Platform)' == ''">x64</Platform>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<TargetVersion>Windows10</TargetVersion>
<DriverTargetPlatform>Universal</DriverTargetPlatform>
<!-- Windows 10 1709 (RS3), matching the INF model floor. -->
<_NT_TARGET_VERSION>0xA000004</_NT_TARGET_VERSION>
<DriverType>WDM</DriverType>
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
<ConfigurationType>Driver</ConfigurationType>
<Driver_SpectreMitigation>Spectre</Driver_SpectreMitigation>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'"
Label="Configuration">
<UseDebugLibraries>true</UseDebugLibraries>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'"
Label="Configuration">
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props"
Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')"
Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<TargetName>LGAudio</TargetName>
<DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
<Inf2CatUseLocalTime>true</Inf2CatUseLocalTime>
<InfVerif_AdditionalOptions>/sw2084</InfVerif_AdditionalOptions>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(ProjectDir);$(DDK_INC_PATH);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_WIN32;_WIN32_WINNT=0x0A00;UNICODE;_UNICODE;PC_IMPLEMENTATION;_USE_WAVERT_;_NEW_DELETE_OPERATORS_;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<LanguageStandard>stdcpp17</LanguageStandard>
<WarningLevel>Level4</WarningLevel>
<TreatWarningAsError>true</TreatWarningAsError>
<ExceptionHandling>false</ExceptionHandling>
</ClCompile>
<ResourceCompile>
<AdditionalIncludeDirectories>$(DDK_INC_PATH);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_WIN32;_WIN32_WINNT=0x0A00;UNICODE;_UNICODE;PC_IMPLEMENTATION;_USE_WAVERT_;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ResourceCompile>
<Link>
<AdditionalDependencies>portcls.lib;stdunk.lib;libcntpr.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<DriverSign>
<FileDigestAlgorithm>SHA256</FileDigestAlgorithm>
</DriverSign>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="Adapter.cpp" />
<ClCompile Include="AudioClock.cpp" />
<ClCompile Include="Driver.cpp" />
<ClCompile Include="Formats.cpp" />
<ClCompile Include="Guids.cpp" />
<ClCompile Include="MiniportTopology.cpp" />
<ClCompile Include="MiniportWaveRT.cpp" />
<ClCompile Include="MiniportWaveRTStream.cpp" />
<ClCompile Include="RingBuffer.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="Adapter.h" />
<ClInclude Include="AudioClock.h" />
<ClInclude Include="Common.h" />
<ClInclude Include="Formats.h" />
<ClInclude Include="Guids.h" />
<ClInclude Include="MiniportTopology.h" />
<ClInclude Include="MiniportWaveRT.h" />
<ClInclude Include="MiniportWaveRTStream.h" />
<ClInclude Include="RingBuffer.h" />
</ItemGroup>
<ItemGroup>
<Inf Include="LGAudio.inx" />
<ResourceCompile Include="LGAudio.rc" />
<FilesToPackage Include="$(TargetPath)" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets" />
</Project>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Driver">
<UniqueIdentifier>{64B60D55-6E76-4D04-9AE1-F23B2ECA8541}</UniqueIdentifier>
</Filter>
<Filter Include="Miniport">
<UniqueIdentifier>{770F1246-D1F5-46C0-B3F6-C645E10435C0}</UniqueIdentifier>
</Filter>
<Filter Include="Utilities">
<UniqueIdentifier>{A1D5B8F7-A246-4E24-8CCB-743901B04FE4}</UniqueIdentifier>
</Filter>
<Filter Include="Package">
<UniqueIdentifier>{5E4626AA-3871-4CDD-B0F0-71315BC11643}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Adapter.cpp"><Filter>Driver</Filter></ClCompile>
<ClCompile Include="AudioClock.cpp"><Filter>Driver</Filter></ClCompile>
<ClCompile Include="Driver.cpp"><Filter>Driver</Filter></ClCompile>
<ClCompile Include="Guids.cpp"><Filter>Driver</Filter></ClCompile>
<ClCompile Include="MiniportTopology.cpp"><Filter>Miniport</Filter></ClCompile>
<ClCompile Include="MiniportWaveRT.cpp"><Filter>Miniport</Filter></ClCompile>
<ClCompile Include="MiniportWaveRTStream.cpp"><Filter>Miniport</Filter></ClCompile>
<ClCompile Include="Formats.cpp"><Filter>Utilities</Filter></ClCompile>
<ClCompile Include="RingBuffer.cpp"><Filter>Utilities</Filter></ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Adapter.h"><Filter>Driver</Filter></ClInclude>
<ClInclude Include="AudioClock.h"><Filter>Driver</Filter></ClInclude>
<ClInclude Include="Common.h"><Filter>Driver</Filter></ClInclude>
<ClInclude Include="Guids.h"><Filter>Driver</Filter></ClInclude>
<ClInclude Include="MiniportTopology.h"><Filter>Miniport</Filter></ClInclude>
<ClInclude Include="MiniportWaveRT.h"><Filter>Miniport</Filter></ClInclude>
<ClInclude Include="MiniportWaveRTStream.h"><Filter>Miniport</Filter></ClInclude>
<ClInclude Include="Formats.h"><Filter>Utilities</Filter></ClInclude>
<ClInclude Include="RingBuffer.h"><Filter>Utilities</Filter></ClInclude>
</ItemGroup>
<ItemGroup>
<Inf Include="LGAudio.inx"><Filter>Package</Filter></Inf>
<ResourceCompile Include="LGAudio.rc">
<Filter>Package</Filter>
</ResourceCompile>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,231 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "MiniportTopology.h"
namespace
{
KSDATARANGE g_AnalogRange =
{
sizeof(KSDATARANGE),
0,
0,
0,
STATICGUIDOF(KSDATAFORMAT_TYPE_AUDIO),
STATICGUIDOF(KSDATAFORMAT_SUBTYPE_ANALOG),
STATICGUIDOF(KSDATAFORMAT_SPECIFIER_NONE)
};
PKSDATARANGE g_AnalogRanges[] =
{
&g_AnalogRange
};
PCPIN_DESCRIPTOR g_RenderPins[] =
{
{
0, 0, 0, nullptr,
{
0, nullptr,
0, nullptr,
ARRAYSIZE(g_AnalogRanges), g_AnalogRanges,
KSPIN_DATAFLOW_IN,
KSPIN_COMMUNICATION_NONE,
&KSCATEGORY_AUDIO,
nullptr,
0
}
},
{
0, 0, 0, nullptr,
{
0, nullptr,
0, nullptr,
ARRAYSIZE(g_AnalogRanges), g_AnalogRanges,
KSPIN_DATAFLOW_OUT,
KSPIN_COMMUNICATION_NONE,
&KSNODETYPE_SPEAKER,
nullptr,
0
}
}
};
PCCONNECTION_DESCRIPTOR g_RenderConnections[] =
{
{
PCFILTER_NODE,
kRenderTopologyBridgePin,
PCFILTER_NODE,
kRenderTopologyEndpointPin
}
};
PCFILTER_DESCRIPTOR g_RenderDescriptor =
{
0,
nullptr,
sizeof(PCPIN_DESCRIPTOR),
ARRAYSIZE(g_RenderPins),
g_RenderPins,
sizeof(PCNODE_DESCRIPTOR),
0,
nullptr,
ARRAYSIZE(g_RenderConnections),
g_RenderConnections,
0,
nullptr
};
PCPIN_DESCRIPTOR g_CapturePins[] =
{
{
0, 0, 0, nullptr,
{
0, nullptr,
0, nullptr,
ARRAYSIZE(g_AnalogRanges), g_AnalogRanges,
KSPIN_DATAFLOW_IN,
KSPIN_COMMUNICATION_NONE,
&KSNODETYPE_MICROPHONE,
nullptr,
0
}
},
{
0, 0, 0, nullptr,
{
0, nullptr,
0, nullptr,
ARRAYSIZE(g_AnalogRanges), g_AnalogRanges,
KSPIN_DATAFLOW_OUT,
KSPIN_COMMUNICATION_NONE,
&KSCATEGORY_AUDIO,
nullptr,
0
}
}
};
PCCONNECTION_DESCRIPTOR g_CaptureConnections[] =
{
{
PCFILTER_NODE,
kCaptureTopologyEndpointPin,
PCFILTER_NODE,
kCaptureTopologyBridgePin
}
};
PCFILTER_DESCRIPTOR g_CaptureDescriptor =
{
0,
nullptr,
sizeof(PCPIN_DESCRIPTOR),
ARRAYSIZE(g_CapturePins),
g_CapturePins,
sizeof(PCNODE_DESCRIPTOR),
0,
nullptr,
ARRAYSIZE(g_CaptureConnections),
g_CaptureConnections,
0,
nullptr
};
}
CMiniportTopology::CMiniportTopology(EndpointKind kind) :
m_refCount(1),
m_kind(kind)
{
}
NTSTATUS CMiniportTopology::QueryInterface(
_In_ REFIID interfaceId,
_COM_Outptr_ void **object)
{
if (!object)
return STATUS_INVALID_PARAMETER;
*object = nullptr;
if (GuidEqual(interfaceId, IID_IUnknown) ||
GuidEqual(interfaceId, IID_IMiniport) ||
GuidEqual(interfaceId, IID_IMiniportTopology))
{
*object = static_cast<IMiniportTopology *>(this);
AddRef();
return STATUS_SUCCESS;
}
return STATUS_NOINTERFACE;
}
ULONG CMiniportTopology::AddRef()
{
return static_cast<ULONG>(InterlockedIncrement(&m_refCount));
}
ULONG CMiniportTopology::Release()
{
const LONG count = InterlockedDecrement(&m_refCount);
if (!count)
delete this;
return static_cast<ULONG>(count);
}
NTSTATUS CMiniportTopology::GetDescription(
_Out_ PPCFILTER_DESCRIPTOR *description)
{
if (!description)
return STATUS_INVALID_PARAMETER;
*description = m_kind == EndpointKind::Render ?
&g_RenderDescriptor : &g_CaptureDescriptor;
return STATUS_SUCCESS;
}
NTSTATUS CMiniportTopology::DataRangeIntersection(
_In_ ULONG pinId,
_In_ PKSDATARANGE dataRange,
_In_ PKSDATARANGE matchingDataRange,
_In_ ULONG outputBufferLength,
_Out_writes_bytes_to_opt_(outputBufferLength, *resultantFormatLength)
void *resultantFormat,
_Out_ ULONG *resultantFormatLength)
{
UNREFERENCED_PARAMETER(pinId);
UNREFERENCED_PARAMETER(dataRange);
UNREFERENCED_PARAMETER(matchingDataRange);
UNREFERENCED_PARAMETER(outputBufferLength);
UNREFERENCED_PARAMETER(resultantFormat);
UNREFERENCED_PARAMETER(resultantFormatLength);
return STATUS_NOT_IMPLEMENTED;
}
NTSTATUS CMiniportTopology::Init(
_In_ PUNKNOWN unknownAdapter,
_In_ PRESOURCELIST resourceList,
_In_ PPORTTOPOLOGY port)
{
UNREFERENCED_PARAMETER(unknownAdapter);
UNREFERENCED_PARAMETER(resourceList);
UNREFERENCED_PARAMETER(port);
return STATUS_SUCCESS;
}

View File

@@ -0,0 +1,56 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include "Common.h"
#include "Formats.h"
class CMiniportTopology final : public IMiniportTopology
{
public:
explicit CMiniportTopology(EndpointKind kind);
~CMiniportTopology() = default;
STDMETHODIMP_(NTSTATUS) QueryInterface(
_In_ REFIID interfaceId,
_COM_Outptr_ void **object) override;
STDMETHODIMP_(ULONG) AddRef() override;
STDMETHODIMP_(ULONG) Release() override;
STDMETHODIMP_(NTSTATUS) GetDescription(
_Out_ PPCFILTER_DESCRIPTOR *description) override;
STDMETHODIMP_(NTSTATUS) DataRangeIntersection(
_In_ ULONG pinId,
_In_ PKSDATARANGE dataRange,
_In_ PKSDATARANGE matchingDataRange,
_In_ ULONG outputBufferLength,
_Out_writes_bytes_to_opt_(outputBufferLength, *resultantFormatLength)
void *resultantFormat,
_Out_ ULONG *resultantFormatLength) override;
STDMETHODIMP_(NTSTATUS) Init(
_In_ PUNKNOWN unknownAdapter,
_In_ PRESOURCELIST resourceList,
_In_ PPORTTOPOLOGY port) override;
private:
volatile LONG m_refCount;
EndpointKind m_kind;
};

View File

@@ -0,0 +1,326 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "MiniportWaveRT.h"
#include "MiniportWaveRTStream.h"
namespace
{
KSDATARANGE g_BridgeRange =
{
sizeof(KSDATARANGE),
0,
0,
0,
STATICGUIDOF(KSDATAFORMAT_TYPE_AUDIO),
STATICGUIDOF(KSDATAFORMAT_SUBTYPE_ANALOG),
STATICGUIDOF(KSDATAFORMAT_SPECIFIER_NONE)
};
PKSDATARANGE g_BridgeRanges[] =
{
&g_BridgeRange
};
PCPIN_DESCRIPTOR g_RenderWavePins[] =
{
{
1, 1, 0, nullptr,
{
0, nullptr,
0, nullptr,
kRenderFormatCount, g_renderDataRangePointers,
KSPIN_DATAFLOW_IN,
KSPIN_COMMUNICATION_SINK,
&KSCATEGORY_AUDIO,
nullptr,
0
}
},
{
0, 0, 0, nullptr,
{
0, nullptr,
0, nullptr,
ARRAYSIZE(g_BridgeRanges), g_BridgeRanges,
KSPIN_DATAFLOW_OUT,
KSPIN_COMMUNICATION_NONE,
&KSCATEGORY_AUDIO,
nullptr,
0
}
}
};
PCCONNECTION_DESCRIPTOR g_RenderWaveConnections[] =
{
{
PCFILTER_NODE,
kRenderWaveHostPin,
PCFILTER_NODE,
kRenderWaveBridgePin
}
};
PCFILTER_DESCRIPTOR g_RenderWaveDescriptor =
{
0,
nullptr,
sizeof(PCPIN_DESCRIPTOR),
ARRAYSIZE(g_RenderWavePins),
g_RenderWavePins,
sizeof(PCNODE_DESCRIPTOR),
0,
nullptr,
ARRAYSIZE(g_RenderWaveConnections),
g_RenderWaveConnections,
0,
nullptr
};
PCPIN_DESCRIPTOR g_CaptureWavePins[] =
{
{
0, 0, 0, nullptr,
{
0, nullptr,
0, nullptr,
ARRAYSIZE(g_BridgeRanges), g_BridgeRanges,
KSPIN_DATAFLOW_IN,
KSPIN_COMMUNICATION_NONE,
&KSCATEGORY_AUDIO,
nullptr,
0
}
},
{
1, 1, 0, nullptr,
{
0, nullptr,
0, nullptr,
kCaptureFormatCount, g_captureDataRangePointers,
KSPIN_DATAFLOW_OUT,
KSPIN_COMMUNICATION_SINK,
&KSCATEGORY_AUDIO,
&KSAUDFNAME_RECORDING_CONTROL,
0
}
}
};
PCNODE_DESCRIPTOR g_CaptureWaveNodes[] =
{
{
0,
nullptr,
&KSNODETYPE_ADC,
nullptr
}
};
PCCONNECTION_DESCRIPTOR g_CaptureWaveConnections[] =
{
{
PCFILTER_NODE,
kCaptureWaveBridgePin,
0,
1
},
{
0,
0,
PCFILTER_NODE,
kCaptureWaveHostPin
}
};
PCFILTER_DESCRIPTOR g_CaptureWaveDescriptor =
{
0,
nullptr,
sizeof(PCPIN_DESCRIPTOR),
ARRAYSIZE(g_CaptureWavePins),
g_CaptureWavePins,
sizeof(PCNODE_DESCRIPTOR),
ARRAYSIZE(g_CaptureWaveNodes),
g_CaptureWaveNodes,
ARRAYSIZE(g_CaptureWaveConnections),
g_CaptureWaveConnections,
0,
nullptr
};
}
CMiniportWaveRT::CMiniportWaveRT(
_In_ CAudioAdapter *adapter,
_In_ EndpointKind kind) :
m_refCount(1),
m_streamOpen(0),
m_adapter(adapter),
m_kind(kind)
{
m_adapter->AddRef();
}
CMiniportWaveRT::~CMiniportWaveRT()
{
m_adapter->Release();
}
NTSTATUS CMiniportWaveRT::QueryInterface(
_In_ REFIID interfaceId,
_COM_Outptr_ void **object)
{
if (!object)
return STATUS_INVALID_PARAMETER;
*object = nullptr;
if (GuidEqual(interfaceId, IID_IUnknown) ||
GuidEqual(interfaceId, IID_IMiniport) ||
GuidEqual(interfaceId, IID_IMiniportWaveRT))
{
*object = static_cast<IMiniportWaveRT *>(this);
AddRef();
return STATUS_SUCCESS;
}
return STATUS_NOINTERFACE;
}
ULONG CMiniportWaveRT::AddRef()
{
return static_cast<ULONG>(InterlockedIncrement(&m_refCount));
}
ULONG CMiniportWaveRT::Release()
{
const LONG count = InterlockedDecrement(&m_refCount);
if (!count)
delete this;
return static_cast<ULONG>(count);
}
NTSTATUS CMiniportWaveRT::GetDescription(
_Out_ PPCFILTER_DESCRIPTOR *description)
{
if (!description)
return STATUS_INVALID_PARAMETER;
*description = m_kind == EndpointKind::Render ?
&g_RenderWaveDescriptor : &g_CaptureWaveDescriptor;
return STATUS_SUCCESS;
}
NTSTATUS CMiniportWaveRT::DataRangeIntersection(
_In_ ULONG pinId,
_In_ PKSDATARANGE dataRange,
_In_ PKSDATARANGE matchingDataRange,
_In_ ULONG outputBufferLength,
_Out_writes_bytes_to_opt_(outputBufferLength, *resultantFormatLength)
void *resultantFormat,
_Out_ ULONG *resultantFormatLength)
{
const ULONG hostPin = m_kind == EndpointKind::Render ?
kRenderWaveHostPin : kCaptureWaveHostPin;
if (pinId != hostPin)
return STATUS_NOT_IMPLEMENTED;
return SelectFormatIntersection(m_kind, dataRange, matchingDataRange,
outputBufferLength, resultantFormat, resultantFormatLength);
}
NTSTATUS CMiniportWaveRT::Init(
_In_ PUNKNOWN unknownAdapter,
_In_ PRESOURCELIST resourceList,
_In_ PPORTWAVERT port)
{
UNREFERENCED_PARAMETER(unknownAdapter);
UNREFERENCED_PARAMETER(resourceList);
UNREFERENCED_PARAMETER(port);
return STATUS_SUCCESS;
}
NTSTATUS CMiniportWaveRT::NewStream(
_Out_ PMINIPORTWAVERTSTREAM *stream,
_In_ PPORTWAVERTSTREAM portStream,
_In_ ULONG pin,
_In_ BOOLEAN capture,
_In_ PKSDATAFORMAT dataFormat)
{
if (!stream || !portStream || !dataFormat)
return STATUS_INVALID_PARAMETER;
*stream = nullptr;
const bool isCapture = m_kind == EndpointKind::Capture;
const ULONG hostPin = isCapture ?
kCaptureWaveHostPin : kRenderWaveHostPin;
if (pin != hostPin || (!!capture != isCapture))
return STATUS_INVALID_PARAMETER;
const KSDATAFORMAT_WAVEFORMATEXTENSIBLE *matchedFormat = nullptr;
NTSTATUS status = ValidateFormat(m_kind, dataFormat, &matchedFormat);
if (!NT_SUCCESS(status))
return status;
if (!matchedFormat)
return STATUS_NO_MATCH;
if (InterlockedCompareExchange(&m_streamOpen, 1, 0) != 0)
return STATUS_DEVICE_BUSY;
CMiniportWaveRTStream *newStream =
new(NonPagedPoolNx, LGAUDIO_POOL_TAG) CMiniportWaveRTStream(
this, m_adapter, portStream, m_kind);
if (!newStream)
{
InterlockedExchange(&m_streamOpen, 0);
return STATUS_INSUFFICIENT_RESOURCES;
}
status = newStream->Initialize(matchedFormat);
if (!NT_SUCCESS(status))
{
newStream->Release();
return status;
}
*stream = static_cast<IMiniportWaveRTStream *>(newStream);
return STATUS_SUCCESS;
}
NTSTATUS CMiniportWaveRT::GetDeviceDescription(
_Out_ PDEVICE_DESCRIPTION deviceDescription)
{
if (!deviceDescription)
return STATUS_INVALID_PARAMETER;
RtlZeroMemory(deviceDescription, sizeof(*deviceDescription));
deviceDescription->Version = DEVICE_DESCRIPTION_VERSION;
deviceDescription->Master = TRUE;
deviceDescription->ScatterGather = TRUE;
deviceDescription->Dma32BitAddresses = TRUE;
deviceDescription->InterfaceType = PCIBus;
deviceDescription->MaximumLength = MAXULONG;
return STATUS_SUCCESS;
}
void CMiniportWaveRT::StreamClosed()
{
InterlockedExchange(&m_streamOpen, 0);
}

View File

@@ -0,0 +1,71 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include "Adapter.h"
#include "Common.h"
#include "Formats.h"
class CMiniportWaveRT final : public IMiniportWaveRT
{
public:
CMiniportWaveRT(
_In_ CAudioAdapter *adapter,
_In_ EndpointKind kind);
~CMiniportWaveRT();
STDMETHODIMP_(NTSTATUS) QueryInterface(
_In_ REFIID interfaceId,
_COM_Outptr_ void **object) override;
STDMETHODIMP_(ULONG) AddRef() override;
STDMETHODIMP_(ULONG) Release() override;
STDMETHODIMP_(NTSTATUS) GetDescription(
_Out_ PPCFILTER_DESCRIPTOR *description) override;
STDMETHODIMP_(NTSTATUS) DataRangeIntersection(
_In_ ULONG pinId,
_In_ PKSDATARANGE dataRange,
_In_ PKSDATARANGE matchingDataRange,
_In_ ULONG outputBufferLength,
_Out_writes_bytes_to_opt_(outputBufferLength, *resultantFormatLength)
void *resultantFormat,
_Out_ ULONG *resultantFormatLength) override;
STDMETHODIMP_(NTSTATUS) Init(
_In_ PUNKNOWN unknownAdapter,
_In_ PRESOURCELIST resourceList,
_In_ PPORTWAVERT port) override;
STDMETHODIMP_(NTSTATUS) NewStream(
_Out_ PMINIPORTWAVERTSTREAM *stream,
_In_ PPORTWAVERTSTREAM portStream,
_In_ ULONG pin,
_In_ BOOLEAN capture,
_In_ PKSDATAFORMAT dataFormat) override;
STDMETHODIMP_(NTSTATUS) GetDeviceDescription(
_Out_ PDEVICE_DESCRIPTION deviceDescription) override;
void StreamClosed();
private:
volatile LONG m_refCount;
volatile LONG m_streamOpen;
CAudioAdapter *m_adapter;
EndpointKind m_kind;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,163 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include "Adapter.h"
#include "Common.h"
#include "Formats.h"
class CMiniportWaveRT;
class CMiniportWaveRTStream final :
public IMiniportWaveRTStreamNotification,
public IMiniportWaveRTInputStream,
public IMiniportWaveRTOutputStream
{
public:
CMiniportWaveRTStream(
_In_ CMiniportWaveRT *miniport,
_In_ CAudioAdapter *adapter,
_In_ PPORTWAVERTSTREAM portStream,
_In_ EndpointKind kind);
~CMiniportWaveRTStream();
NTSTATUS Initialize(
_In_ const KSDATAFORMAT_WAVEFORMATEXTENSIBLE *format);
STDMETHODIMP_(NTSTATUS) QueryInterface(
_In_ REFIID interfaceId,
_COM_Outptr_ void **object) override;
STDMETHODIMP_(ULONG) AddRef() override;
STDMETHODIMP_(ULONG) Release() override;
STDMETHODIMP_(NTSTATUS) SetFormat(
_In_ PKSDATAFORMAT dataFormat) override;
STDMETHODIMP_(NTSTATUS) SetState(_In_ KSSTATE state) override;
STDMETHODIMP_(NTSTATUS) GetPosition(
_Out_ PKSAUDIO_POSITION position) override;
STDMETHODIMP_(NTSTATUS) AllocateAudioBuffer(
_In_ ULONG requestedSize,
_Out_ PMDL *audioBufferMdl,
_Out_ ULONG *actualSize,
_Out_ ULONG *offsetFromFirstPage,
_Out_ MEMORY_CACHING_TYPE *cacheType) override;
STDMETHODIMP_(void) FreeAudioBuffer(
_In_opt_ PMDL audioBufferMdl,
_In_ ULONG bufferSize) override;
STDMETHODIMP_(void) GetHWLatency(
_Out_ PKSRTAUDIO_HWLATENCY latency) override;
STDMETHODIMP_(NTSTATUS) GetPositionRegister(
_Out_ PKSRTAUDIO_HWREGISTER positionRegister) override;
STDMETHODIMP_(NTSTATUS) GetClockRegister(
_Out_ PKSRTAUDIO_HWREGISTER clockRegister) override;
STDMETHODIMP_(NTSTATUS) AllocateBufferWithNotification(
_In_ ULONG notificationCount,
_In_ ULONG requestedSize,
_Out_ PMDL *audioBufferMdl,
_Out_ ULONG *actualSize,
_Out_ ULONG *offsetFromFirstPage,
_Out_ MEMORY_CACHING_TYPE *cacheType) override;
STDMETHODIMP_(void) FreeBufferWithNotification(
_In_ PMDL audioBufferMdl,
_In_ ULONG bufferSize) override;
STDMETHODIMP_(NTSTATUS) RegisterNotificationEvent(
_In_ PKEVENT notificationEvent) override;
STDMETHODIMP_(NTSTATUS) UnregisterNotificationEvent(
_In_ PKEVENT notificationEvent) override;
STDMETHODIMP_(NTSTATUS) GetReadPacket(
_Out_ ULONG *packetNumber,
_Out_ DWORD *flags,
_Out_ ULONG64 *performanceCounterValue,
_Out_ BOOL *moreData) override;
STDMETHODIMP_(NTSTATUS) SetWritePacket(
_In_ ULONG packetNumber,
_In_ DWORD flags,
_In_ ULONG eosPacketLength) override;
STDMETHODIMP_(NTSTATUS) GetOutputStreamPresentationPosition(
_Out_ KSAUDIO_PRESENTATION_POSITION *position) override;
STDMETHODIMP_(NTSTATUS) GetPacketCount(
_Out_ ULONG *packetCount) override;
private:
static void TimerCallback(
_In_ PEX_TIMER timer,
_In_opt_ void *context);
NTSTATUS AllocateBuffer(
_In_ ULONG notificationCount,
_In_ ULONG requestedSize,
_Out_ PMDL *audioBufferMdl,
_Out_ ULONG *actualSize,
_Out_ ULONG *offsetFromFirstPage,
_Out_ MEMORY_CACHING_TYPE *cacheType);
void FreeBuffer(_In_opt_ PMDL audioBufferMdl);
void StopTimer();
void ArmTimerLocked();
void AdvanceLocked(_In_ const AudioClockSnapshot &clock);
void AdvanceEventPacketsLocked(
_In_ const AudioClockSnapshot &clock);
void UpdatePresentationQpcLocked(
_In_ const AudioClockSnapshot &clock);
void TransferFramesLocked(_In_ ULONGLONG bytes);
void SignalNotificationsLocked();
volatile LONG m_refCount;
CMiniportWaveRT *m_miniport;
CAudioAdapter *m_adapter;
PPORTWAVERTSTREAM m_portStream;
EndpointKind m_kind;
KSDATAFORMAT_WAVEFORMATEXTENSIBLE m_format;
FormatFingerprint m_fingerprint;
KSSTATE m_state;
KSPIN_LOCK m_positionLock;
PEX_TIMER m_timer;
bool m_hasRun;
PMDL m_bufferMdl;
BYTE *m_buffer;
bool m_bufferAllocationPending;
ULONG m_bufferSize;
ULONG m_notificationCount;
ULONG m_packetSize;
PKEVENT m_notificationEvents[8];
ULONG m_notificationEventCount;
ULONGLONG m_clockFrequency;
ULONGLONG m_qpcFrequency;
ULONGLONG m_lastClockPosition;
ULONGLONG m_lastQpcPosition;
ULONGLONG m_lastPresentationQpc;
ULONGLONG m_frameRemainder;
ULONGLONG m_pendingBytes;
ULONGLONG m_linearPosition;
ULONG m_cyclicPosition;
ULONGLONG m_completedPackets;
ULONGLONG m_lastReadPacket;
ULONG m_lastWritePacket;
bool m_eosReceived;
bool m_eosNotificationSent;
ULONGLONG m_eosLinearPosition;
};

323
idd/LGAudio/RingBuffer.cpp Normal file
View File

@@ -0,0 +1,323 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "RingBuffer.h"
namespace LGAudio
{
namespace
{
constexpr ULONG kRingPoolTag = 'rAGL';
ULONG MinUlong(_In_ ULONG left, _In_ ULONG right)
{
return left < right ? left : right;
}
}
CAudioRingBuffer::CAudioRingBuffer() :
m_buffer(nullptr),
m_capacity(0),
m_readOffset(0),
m_writeOffset(0),
m_available(0),
m_format{},
m_configured(false)
{
KeInitializeSpinLock(&m_lock);
}
CAudioRingBuffer::~CAudioRingBuffer()
{
Destroy();
}
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS CAudioRingBuffer::Init()
{
if (m_buffer)
return STATUS_SUCCESS;
constexpr ULONG capacity =
(kMaximumBytesPerSecond + kMaximumBlockAlign - 1) /
kMaximumBlockAlign * kMaximumBlockAlign;
#pragma warning(push)
#pragma warning(disable:4996) // Required for pre-2004 Windows 10 releases.
BYTE * buffer = static_cast<BYTE *>(ExAllocatePoolWithTag(
NonPagedPoolNx, capacity, kRingPoolTag));
#pragma warning(pop)
if (!buffer)
return STATUS_INSUFFICIENT_RESOURCES;
RtlZeroMemory(buffer, capacity);
KIRQL oldIrql;
KeAcquireSpinLock(&m_lock, &oldIrql);
if (!m_buffer)
{
m_buffer = buffer;
m_capacity = capacity;
buffer = nullptr;
}
KeReleaseSpinLock(&m_lock, oldIrql);
if (buffer)
ExFreePoolWithTag(buffer, kRingPoolTag);
return STATUS_SUCCESS;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
void CAudioRingBuffer::Destroy()
{
BYTE * buffer;
KIRQL oldIrql;
KeAcquireSpinLock(&m_lock, &oldIrql);
buffer = m_buffer;
m_buffer = nullptr;
m_capacity = 0;
m_readOffset = 0;
m_writeOffset = 0;
m_available = 0;
m_format = {};
m_configured = false;
KeReleaseSpinLock(&m_lock, oldIrql);
if (buffer)
ExFreePoolWithTag(buffer, kRingPoolTag);
}
_IRQL_requires_max_(DISPATCH_LEVEL)
NTSTATUS CAudioRingBuffer::Configure(
_In_ const FormatFingerprint & format)
{
if (format.sampleRate == 0 ||
format.channels == 0 ||
format.containerBits == 0 ||
(format.containerBits % 8) != 0 ||
format.validBits == 0 ||
format.validBits > format.containerBits ||
format.blockAlign !=
format.channels * (format.containerBits / 8))
return STATUS_INVALID_PARAMETER;
const ULONGLONG bytesPerSecond =
static_cast<ULONGLONG>(format.sampleRate) * format.blockAlign;
if (bytesPerSecond > kMaximumBytesPerSecond)
return STATUS_NOT_SUPPORTED;
KIRQL oldIrql;
KeAcquireSpinLock(&m_lock, &oldIrql);
if (!m_buffer || (m_capacity % format.blockAlign) != 0)
{
KeReleaseSpinLock(&m_lock, oldIrql);
return STATUS_DEVICE_NOT_READY;
}
m_format = format;
m_readOffset = 0;
m_writeOffset = 0;
m_available = 0;
m_configured = true;
KeReleaseSpinLock(&m_lock, oldIrql);
return STATUS_SUCCESS;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
void CAudioRingBuffer::Reset()
{
KIRQL oldIrql;
KeAcquireSpinLock(&m_lock, &oldIrql);
m_readOffset = 0;
m_writeOffset = 0;
m_available = 0;
KeReleaseSpinLock(&m_lock, oldIrql);
}
bool CAudioRingBuffer::IsTransferValid(_In_ ULONG bytes) const
{
return
m_buffer && m_configured && m_format.blockAlign != 0 &&
(bytes % m_format.blockAlign) == 0;
}
void CAudioRingBuffer::CopyIntoRing(
_In_reads_bytes_(bytes) const BYTE * data,
_In_ ULONG bytes)
{
const ULONG first = MinUlong(bytes, m_capacity - m_writeOffset);
RtlCopyMemory(m_buffer + m_writeOffset, data, first);
const ULONG second = bytes - first;
if (second)
RtlCopyMemory(m_buffer, data + first, second);
m_writeOffset = (m_writeOffset + bytes) % m_capacity;
}
void CAudioRingBuffer::CopyFromRing(
_Out_writes_bytes_(bytes) BYTE * data,
_In_ ULONG bytes)
{
const ULONG first = MinUlong(bytes, m_capacity - m_readOffset);
RtlCopyMemory(data, m_buffer + m_readOffset, first);
const ULONG second = bytes - first;
if (second)
RtlCopyMemory(data + first, m_buffer, second);
m_readOffset = (m_readOffset + bytes) % m_capacity;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
ULONG CAudioRingBuffer::Push(
_In_reads_bytes_(bytes) const BYTE * data,
_In_ ULONG bytes,
_Out_opt_ PULONG droppedBytes)
{
const ULONG inputBytes = bytes;
if (droppedBytes)
*droppedBytes = 0;
if (bytes == 0)
return 0;
if (!data)
return 0;
KIRQL oldIrql;
KeAcquireSpinLock(&m_lock, &oldIrql);
if (!IsTransferValid(bytes))
{
KeReleaseSpinLock(&m_lock, oldIrql);
return 0;
}
ULONG dropped = 0;
if (bytes >= m_capacity)
{
dropped = m_available + (bytes - m_capacity);
data += bytes - m_capacity;
bytes = m_capacity;
m_readOffset = 0;
m_writeOffset = 0;
m_available = 0;
}
else if (bytes > m_capacity - m_available)
{
dropped = bytes - (m_capacity - m_available);
m_readOffset = (m_readOffset + dropped) % m_capacity;
m_available -= dropped;
}
CopyIntoRing(data, bytes);
m_available += bytes;
KeReleaseSpinLock(&m_lock, oldIrql);
if (droppedBytes)
*droppedBytes = dropped;
// Every source byte was consumed. If input exceeded capacity, the oldest
// part of that input is included in the reported dropped byte count.
return inputBytes;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
ULONG CAudioRingBuffer::Pop(
_In_ const FormatFingerprint &format,
_Out_writes_bytes_(bytes) BYTE *data,
_In_ ULONG bytes)
{
if (bytes == 0)
return 0;
if (!data)
return 0;
KIRQL oldIrql;
KeAcquireSpinLock(&m_lock, &oldIrql);
if (!IsTransferValid(bytes) ||
!EqualFormatFingerprint(m_format, format))
{
KeReleaseSpinLock(&m_lock, oldIrql);
RtlZeroMemory(data, bytes);
return 0;
}
const ULONG copied = MinUlong(bytes, m_available);
if (copied)
{
CopyFromRing(data, copied);
m_available -= copied;
}
KeReleaseSpinLock(&m_lock, oldIrql);
if (copied < bytes)
RtlZeroMemory(data + copied, bytes - copied);
return copied;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
ULONG CAudioRingBuffer::Discard(
_In_ const FormatFingerprint &format,
_In_ ULONGLONG bytes)
{
if (!bytes)
return 0;
KIRQL oldIrql;
KeAcquireSpinLock(&m_lock, &oldIrql);
if (!m_buffer || !m_configured || !m_format.blockAlign ||
(bytes % m_format.blockAlign) != 0 ||
!EqualFormatFingerprint(m_format, format))
{
KeReleaseSpinLock(&m_lock, oldIrql);
return 0;
}
const ULONG discarded = bytes < m_available ?
static_cast<ULONG>(bytes) : m_available;
m_readOffset = (m_readOffset + discarded) % m_capacity;
m_available -= discarded;
KeReleaseSpinLock(&m_lock, oldIrql);
return discarded;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
ULONG CAudioRingBuffer::Available()
{
KIRQL oldIrql;
KeAcquireSpinLock(&m_lock, &oldIrql);
const ULONG available = m_available;
KeReleaseSpinLock(&m_lock, oldIrql);
return available;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
ULONG CAudioRingBuffer::Capacity()
{
KIRQL oldIrql;
KeAcquireSpinLock(&m_lock, &oldIrql);
const ULONG capacity = m_capacity;
KeReleaseSpinLock(&m_lock, oldIrql);
return capacity;
}
}

103
idd/LGAudio/RingBuffer.h Normal file
View File

@@ -0,0 +1,103 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include "Formats.h"
namespace LGAudio
{
/**
* Adapter-owned byte ring used to bridge a render stream to a capture
* stream. The owner must stop its streams before destroying the ring.
*/
class CAudioRingBuffer final
{
public:
CAudioRingBuffer();
~CAudioRingBuffer();
CAudioRingBuffer(const CAudioRingBuffer &) = delete;
CAudioRingBuffer & operator=(const CAudioRingBuffer &) = delete;
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS Init();
_IRQL_requires_max_(DISPATCH_LEVEL)
void Destroy();
/** Configure a new byte layout and discard any queued audio. */
_IRQL_requires_max_(DISPATCH_LEVEL)
NTSTATUS Configure(_In_ const FormatFingerprint & format);
/** Discard queued audio without changing the configured format. */
_IRQL_requires_max_(DISPATCH_LEVEL)
void Reset();
/**
* Consume all input bytes. If necessary, the oldest queued bytes are
* discarded. The return value is bytes consumed from data.
*/
_IRQL_requires_max_(DISPATCH_LEVEL)
ULONG Push(
_In_reads_bytes_(bytes) const BYTE * data,
_In_ ULONG bytes,
_Out_opt_ PULONG droppedBytes = nullptr);
/**
* Pop into data and zero-fill an underrun. The return value is the number
* of bytes obtained from the ring, excluding zero-fill.
*/
_IRQL_requires_max_(DISPATCH_LEVEL)
ULONG Pop(
_In_ const FormatFingerprint &format,
_Out_writes_bytes_(bytes) BYTE *data,
_In_ ULONG bytes);
_IRQL_requires_max_(DISPATCH_LEVEL)
ULONG Discard(
_In_ const FormatFingerprint &format,
_In_ ULONGLONG bytes);
_IRQL_requires_max_(DISPATCH_LEVEL)
ULONG Available();
_IRQL_requires_max_(DISPATCH_LEVEL)
ULONG Capacity();
private:
bool IsTransferValid(_In_ ULONG bytes) const;
void CopyIntoRing(_In_reads_bytes_(bytes) const BYTE * data,
_In_ ULONG bytes);
void CopyFromRing(_Out_writes_bytes_(bytes) BYTE * data,
_In_ ULONG bytes);
KSPIN_LOCK m_lock;
BYTE * m_buffer;
ULONG m_capacity;
ULONG m_readOffset;
ULONG m_writeOffset;
ULONG m_available;
FormatFingerprint m_format;
bool m_configured;
};
}
using CAudioRingBuffer = LGAudio::CAudioRingBuffer;

View File

@@ -12,6 +12,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LGIdd", "LGIdd\LGIdd.vcxpro
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LGInput", "LGInput\LGInput.vcxproj", "{2477B25B-CB62-4AD9-A260-CE5F00D77EEB}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LGAudio", "LGAudio\LGAudio.vcxproj", "{02146E7A-FA7C-4071-8759-8B9A89452367}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LGMP", "..\repos\LGMP\LGMP.vcxproj", "{FFCC376C-4D98-4B50-B431-E1BBC9C67E65}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LGIddHelper", "LGIddHelper\LGIddHelper.vcxproj", "{0045D7AD-3F26-4B87-81CB-78D18839596D}"
@@ -56,6 +58,16 @@ Global
{2477B25B-CB62-4AD9-A260-CE5F00D77EEB}.Release|x64.Build.0 = Release|x64
{2477B25B-CB62-4AD9-A260-CE5F00D77EEB}.Release-LGInput|x64.ActiveCfg = Release|x64
{2477B25B-CB62-4AD9-A260-CE5F00D77EEB}.Release-LGInput|x64.Build.0 = Release|x64
{02146E7A-FA7C-4071-8759-8B9A89452367}.Debug|x64.ActiveCfg = Debug|x64
{02146E7A-FA7C-4071-8759-8B9A89452367}.Debug|x64.Build.0 = Debug|x64
{02146E7A-FA7C-4071-8759-8B9A89452367}.Debug|x64.Deploy.0 = Debug|x64
{02146E7A-FA7C-4071-8759-8B9A89452367}.Debug-LGInput|x64.ActiveCfg = Debug|x64
{02146E7A-FA7C-4071-8759-8B9A89452367}.Debug-LGInput|x64.Build.0 = Debug|x64
{02146E7A-FA7C-4071-8759-8B9A89452367}.Release|x64.ActiveCfg = Release|x64
{02146E7A-FA7C-4071-8759-8B9A89452367}.Release|x64.Build.0 = Release|x64
{02146E7A-FA7C-4071-8759-8B9A89452367}.Release|x64.Deploy.0 = Release|x64
{02146E7A-FA7C-4071-8759-8B9A89452367}.Release-LGInput|x64.ActiveCfg = Release|x64
{02146E7A-FA7C-4071-8759-8B9A89452367}.Release-LGInput|x64.Build.0 = Release|x64
{FFCC376C-4D98-4B50-B431-E1BBC9C67E65}.Debug|x64.ActiveCfg = Debug|x64
{FFCC376C-4D98-4B50-B431-E1BBC9C67E65}.Debug|x64.Build.0 = Debug|x64
{FFCC376C-4D98-4B50-B431-E1BBC9C67E65}.Debug-LGInput|x64.ActiveCfg = Debug|x64