mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-11-12 23:07:21 +00:00
[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:
253
idd/LGAudio/Adapter.cpp
Normal file
253
idd/LGAudio/Adapter.cpp
Normal 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
76
idd/LGAudio/Adapter.h
Normal 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);
|
||||
57
idd/LGAudio/AudioClock.cpp
Normal file
57
idd/LGAudio/AudioClock.cpp
Normal 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
70
idd/LGAudio/AudioClock.h
Normal 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
79
idd/LGAudio/Common.h
Normal 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
99
idd/LGAudio/Driver.cpp
Normal 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
455
idd/LGAudio/Formats.cpp
Normal 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
125
idd/LGAudio/Formats.h
Normal 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
22
idd/LGAudio/Guids.cpp
Normal 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
23
idd/LGAudio/Guids.h
Normal 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
125
idd/LGAudio/LGAudio.inx
Normal 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
31
idd/LGAudio/LGAudio.rc
Normal 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
104
idd/LGAudio/LGAudio.vcxproj
Normal 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>
|
||||
46
idd/LGAudio/LGAudio.vcxproj.filters
Normal file
46
idd/LGAudio/LGAudio.vcxproj.filters
Normal 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>
|
||||
231
idd/LGAudio/MiniportTopology.cpp
Normal file
231
idd/LGAudio/MiniportTopology.cpp
Normal 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;
|
||||
}
|
||||
56
idd/LGAudio/MiniportTopology.h
Normal file
56
idd/LGAudio/MiniportTopology.h
Normal 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;
|
||||
};
|
||||
326
idd/LGAudio/MiniportWaveRT.cpp
Normal file
326
idd/LGAudio/MiniportWaveRT.cpp
Normal 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);
|
||||
}
|
||||
71
idd/LGAudio/MiniportWaveRT.h
Normal file
71
idd/LGAudio/MiniportWaveRT.h
Normal 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;
|
||||
};
|
||||
1026
idd/LGAudio/MiniportWaveRTStream.cpp
Normal file
1026
idd/LGAudio/MiniportWaveRTStream.cpp
Normal file
File diff suppressed because it is too large
Load Diff
163
idd/LGAudio/MiniportWaveRTStream.h
Normal file
163
idd/LGAudio/MiniportWaveRTStream.h
Normal 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
323
idd/LGAudio/RingBuffer.cpp
Normal 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
103
idd/LGAudio/RingBuffer.h
Normal 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;
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user