Files
nt-lite/code/kernel/ke_audio.c
T

2098 lines
86 KiB
C

// Kernel audio core. This file owns endpoint identity, logical streams, mixing, and provider timing handoff.
#include <ntlite/atomic.h>
#include <stdbool.h>
#include <intrin.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <ntlite/ddi/io_driver.h>
#include <ntlite/ddk/audio.h>
#include <kernel/ke_audio.h>
#include <kernel/ke_id.h>
#include <kernel/ke_debug.h>
#include <kernel/ke_event.h>
#include <kernel/ke_list.h>
#include <kernel/ke_mutex.h>
#include <kernel/ke_sched.h>
#include <kernel/ke_spinlock.h>
#include <kernel/ke_thread.h>
#include <kernel/ke_time.h>
#include <kernel/mm_pool.h>
#include <kernel/mm_virt.h>
#include <kernel/ob_object.h>
#define KE_AUDIO_ENDPOINT_TAG MM_POOL_TAG('a', 'e', 'p', 't')
#define KE_AUDIO_RING_TAG MM_POOL_TAG('a', 'r', 'n', 'g')
#define KE_AUDIO_PERIOD_STATE_FREE 0u
#define KE_AUDIO_PERIOD_STATE_FILLING 1u
#define KE_AUDIO_PERIOD_STATE_READY 2u
#define KE_AUDIO_PERIOD_STATE_COMPLETED 3u
#define KE_AUDIO_PERIOD_STATE_REAPING 4u
#define KE_AUDIO_TICKS_PER_MILLISECOND 1000ull
typedef struct KE_AUDIO_PERIOD_CONTRIBUTION {
KE_AUDIO_STREAM* Stream;
uint64_t StreamGeneration;
uint32_t SourceFrames;
} KE_AUDIO_PERIOD_CONTRIBUTION;
typedef struct KE_AUDIO_PERIOD_STATE {
uint64_t Generation;
uint32_t ContributionCount;
uint8_t State;
KE_AUDIO_PERIOD_CONTRIBUTION Contributions[KE_AUDIO_MAX_STREAMS_PER_ENDPOINT];
} KE_AUDIO_PERIOD_STATE;
struct KE_AUDIO_STREAM {
OBJECT Header;
MUTEX* Lock;
EVENT* WaitEvent;
LIST_ENTRY EndpointLink;
IO_AUDIO_ENDPOINT* Endpoint;
KE_AUDIO_STREAM_STATE State;
volatile int32_t ProcessHandleCount;
volatile int32_t CloseRequested;
IO_AUDIO_FORMAT Format;
uint8_t* Ring;
uint32_t FrameBytes;
uint32_t ReadFrame;
uint32_t WriteFrame;
uint32_t BufferedFrames;
uint64_t ResamplePositionQ32;
uint64_t ResampleStepQ32;
uint64_t FramesSubmitted;
uint64_t FramesConsumed;
uint64_t FramesPresented;
uint64_t FramesDiscarded;
uint64_t OutputFramesMixed;
uint64_t SilenceFramesInserted;
uint64_t Underruns;
uint64_t ConsecutiveUnderrunPeriods;
uint64_t DrainedGeneration;
uint64_t DiscontinuityGeneration;
uint64_t BufferGeneration;
KSTATUS FailureStatus;
uint32_t WaitMask;
uint32_t WaitWritableFrames;
uint64_t WaitConsumedFrames;
uint64_t WaitPresentedFrames;
uint64_t WaitDrainedGeneration;
uint64_t WaitDiscontinuityGeneration;
float Volume;
float Pan;
bool Muted;
float CurrentLeftGain;
float CurrentRightGain;
float TargetLeftGain;
float TargetRightGain;
float LeftGainStep;
float RightGainStep;
uint32_t GainRampRemaining;
};
struct IO_AUDIO_ENDPOINT {
OBJECT Header;
LIST_ENTRY RegistryLink;
LIST_ENTRY StreamList;
SPINLOCK StreamListLock;
MUTEX* ControlLock;
EVENT* WorkEvent;
EVENT* MixerExitEvent;
THREAD* MixerThread;
SPINLOCK NotifyLock;
uint32_t EndpointId;
IO_AUDIO_ENDPOINT_KIND Kind;
KE_AUDIO_ENDPOINT_STATE State;
uint32_t Capabilities;
bool Available;
bool Registered;
bool HardwareRunning;
bool SkipProviderStop;
IO_AUDIO_REMOVAL_MODE RemovalMode;
DEVICE_OBJECT* Device;
void* ProviderContext;
IO_AUDIO_PROVIDER_OPERATIONS Operations;
uint8_t* PeriodBuffer;
size_t PeriodBufferBytes;
uint32_t PeriodCount;
uint32_t FramesPerPeriod;
size_t PeriodStrideBytes;
IO_AUDIO_FORMAT HardwareFormat;
int32_t HardwareGainMinimumMilliDb;
int32_t HardwareGainMaximumMilliDb;
uint32_t HardwareGainStepMilliDb;
KE_AUDIO_PERIOD_STATE Periods[KE_AUDIO_OUTPUT_PERIOD_COUNT];
uint32_t ActiveStreamCount;
uint64_t IdleDeadlineTicks;
float MasterCurrentGain;
float MasterTargetGain;
float MasterRequestedVolume;
int32_t MasterHardwareGainMilliDb;
float MasterGainStep;
uint32_t MasterRampRemaining;
uint64_t MasterGainGeneration;
bool MasterMuted;
KSTATUS FailureStatus;
uint64_t PeriodsMixed;
uint64_t PeriodsCompleted;
uint64_t FramesPresented;
uint64_t HardwarePositionFrames;
uint64_t InterruptCount;
uint64_t LastPresentationTimestampTicks;
uint64_t SilenceFramesInserted;
uint64_t Underruns;
uint64_t DiscontinuityGeneration;
uint64_t HardwareStarts;
uint64_t HardwareStops;
uint64_t SpuriousNotifications;
uint64_t PendingDiscontinuities;
};
typedef struct KE_AUDIO_CORE_STATS {
volatile int64_t EndpointsRegistered;
volatile int64_t EndpointsUnregistered;
volatile int64_t StreamsCreated;
volatile int64_t StreamsDestroyed;
volatile int64_t Starts;
volatile int64_t Stops;
volatile int64_t Pauses;
volatile int64_t Resumes;
volatile int64_t Drains;
volatile int64_t Resets;
} KE_AUDIO_CORE_STATS;
typedef struct KE_AUDIO_LIFECYCLE_SNAPSHOT {
KE_AUDIO_ENDPOINT_STATE State;
KSTATUS FailureStatus;
IO_AUDIO_REMOVAL_MODE RemovalMode;
bool SkipProviderStop;
} KE_AUDIO_LIFECYCLE_SNAPSHOT;
static SPINLOCK gAudioRegistryLock = SPINLOCK_INIT;
static LIST_ENTRY gAudioEndpointList;
static uint32_t gAudioEndpointCount;
static KE_ID_ALLOCATOR gAudioEndpointIds = KE_ID_ALLOCATOR_INIT(1u);
static uint32_t gAudioDefaultEndpointId;
static bool gAudioInitialized;
static KE_AUDIO_CORE_STATS gAudioCoreStats;
// Stops on one violated internal audio invariant.
static void KiAudioBug(const char* where, const char* reason, uint64_t value) {
KePanic(KE_PANIC_CODE_GENERIC,
"audio invariant failed: %s: %s value=0x%016llX\n",
where ? where : "?",
reason ? reason : "?",
(unsigned long long)value);
}
// Acquires an internal mutex and rejects abandoned ownership as kernel corruption.
static void KiAudioAcquireMutex(MUTEX* mutex, const char* where) {
KSTATUS status = KeAcquireMutex(mutex, KE_WAIT_INFINITE);
if (status != NTLSTATUS_SUCCESS)
KiAudioBug(where, "internal mutex acquisition failed", status);
}
// Releases one internal mutex.
static void KiAudioReleaseMutex(MUTEX* mutex) {
KeReleaseMutex(mutex);
}
// Returns the provider timer in microseconds when available.
static uint64_t KiAudioQueryTicks(void) {
return KeQueryMonotonicTicks();
}
// Returns whether one float is finite without depending on a runtime math library.
static bool KiAudioFloatIsFinite(float value) {
union {
float Float;
uint32_t Bits;
} bits;
bits.Float = value;
return (bits.Bits & 0x7F800000u) != 0x7F800000u;
}
// Clamps one mixer sample to the signed unit interval.
static float KiAudioClampSample(float value) {
const float maximum = 0.99999988079071044921875f;
if (value > maximum) return maximum;
if (value < -1.0f) return -1.0f;
return value;
}
// Converts one normalized mixer sample to signed 16-bit PCM.
static int16_t KiAudioFloatToOutputSample(float value) {
float scaled = KiAudioClampSample(value) * 32768.0f;
int32_t sample = scaled >= 0.0f ? (int32_t)(scaled + 0.5f)
: (int32_t)(scaled - 0.5f);
if (sample > 32767) sample = 32767;
if (sample < -32768) sample = -32768;
return (int16_t)sample;
}
// Approximates one linear gain as milli-decibels without a runtime math dependency.
static int32_t KiAudioMilliDbFromLinearGain(float gain) {
if (gain <= 0.0f)
return INT32_MIN;
union {
float Float;
uint32_t Bits;
} bits;
bits.Float = gain;
uint32_t encodedExponent = (bits.Bits >> 23) & 0xFFu;
uint32_t fraction = bits.Bits & 0x007FFFFFu;
int32_t exponent = 0;
if (encodedExponent) {
exponent = (int32_t)encodedExponent - 127;
} else {
exponent = -126;
while ((fraction & 0x00800000u) == 0) {
fraction <<= 1;
exponent--;
}
fraction &= 0x007FFFFFu;
}
bits.Bits = fraction | 0x3F800000u;
float mantissa = bits.Float;
float y = (mantissa - 1.0f) / (mantissa + 1.0f);
float y2 = y * y;
float logarithm = 2.0f * y *
(1.0f + y2 * (0.3333333333333333f +
y2 * (0.2f + y2 * (0.1428571428571429f + y2 * 0.1111111111111111f))));
float log2Gain = (float)exponent + logarithm * 1.4426950408889634f;
float milliDb = log2Gain * 6020.599913279624f;
return milliDb >= 0.0f ? (int32_t)(milliDb + 0.5f)
: (int32_t)(milliDb - 0.5f);
}
// Approximates one nonpositive milli-decibel residual as a linear gain.
static float KiAudioLinearGainFromMilliDb(int32_t milliDb) {
if (milliDb >= 0)
return 1.0f;
float exponent = (float)milliDb * (1.0f / 6020.599913279624f);
int32_t whole = (int32_t)exponent;
if ((float)whole > exponent)
whole--;
float fraction = exponent - (float)whole;
float z = fraction * 0.6931471805599453f;
float z2 = z * z;
float result = 1.0f + z + z2 * 0.5f + z2 * z * (1.0f / 6.0f) +
z2 * z2 * (1.0f / 24.0f) + z2 * z2 * z * (1.0f / 120.0f);
while (whole < 0) {
result *= 0.5f;
whole++;
}
while (whole > 0) {
result *= 2.0f;
whole--;
}
return result;
}
// Chooses the highest supported nonpositive hardware gain.
static int32_t KiAudioHighestSafeHardwareGain(const IO_AUDIO_ENDPOINT* endpoint) {
if (!(endpoint->Capabilities & IO_AUDIO_ENDPOINT_CAP_HARDWARE_GAIN) ||
!endpoint->Operations.SetGain)
return 0;
int32_t minimum = endpoint->HardwareGainMinimumMilliDb;
int32_t maximum = endpoint->HardwareGainMaximumMilliDb;
int64_t step = endpoint->HardwareGainStepMilliDb;
if (maximum <= 0)
return maximum;
int64_t steps = ((int64_t)0 - minimum) / step;
return (int32_t)((int64_t)minimum + steps * step);
}
// Converts one requested master volume into software gain for the stable hardware gain.
static float KiAudioSoftwareGainForVolume(const IO_AUDIO_ENDPOINT* endpoint,
float requestedVolume) {
if (requestedVolume <= 0.0f)
return 0.0f;
if (!(endpoint->Capabilities & IO_AUDIO_ENDPOINT_CAP_HARDWARE_GAIN) ||
!endpoint->Operations.SetGain)
return requestedVolume;
int32_t requestedMilliDb = KiAudioMilliDbFromLinearGain(requestedVolume);
int64_t residualMilliDb = (int64_t)requestedMilliDb -
endpoint->MasterHardwareGainMilliDb;
if (residualMilliDb > 0)
residualMilliDb = 0;
if (residualMilliDb < -IO_AUDIO_HARDWARE_GAIN_LIMIT_MILLIDB)
return 0.0f;
return KiAudioLinearGainFromMilliDb((int32_t)residualMilliDb);
}
// Returns the byte width of one source frame.
static uint32_t KiAudioFrameBytes(const IO_AUDIO_FORMAT* format) {
if (!format) return 0;
if (format->SampleFormat != IoAudioSampleS16) return 0;
return 2u * format->ChannelCount;
}
// Validates one logical source format accepted by the first mixer contract.
static bool KiAudioValidateSourceFormat(const IO_AUDIO_FORMAT* format) {
if (!format) return false;
if (format->ChannelCount != 1 && format->ChannelCount != 2)
return false;
if (format->SampleFormat != IoAudioSampleS16)
return false;
return format->SampleRate == 11025u ||
format->SampleRate == 22050u ||
format->SampleRate == 44100u ||
format->SampleRate == 48000u;
}
// Validates the fixed endpoint ring contract used by the HDA provider series.
static KSTATUS KiAudioValidateRegistration(
const IO_AUDIO_ENDPOINT_REGISTRATION* registration) {
if (!registration || !registration->PeriodBuffer)
return NTLSTATUS_INVALID_PARAMETER;
if (!registration->Device)
return NTLSTATUS_INVALID_PARAMETER;
if (registration->Kind < IoAudioEndpointLineOut ||
registration->Kind > IoAudioEndpointOtherAnalog ||
(registration->Capabilities & ~IO_AUDIO_ENDPOINT_CAP_VALID_MASK) != 0)
return NTLSTATUS_INVALID_PARAMETER;
if (!registration->Operations.Start || !registration->Operations.Stop)
return NTLSTATUS_INVALID_PARAMETER;
if (registration->HardwareFormat.SampleRate != KE_AUDIO_OUTPUT_SAMPLE_RATE ||
registration->HardwareFormat.ChannelCount != KE_AUDIO_OUTPUT_CHANNELS ||
registration->HardwareFormat.SampleFormat != IoAudioSampleS16)
return NTLSTATUS_NOT_SUPPORTED;
if (registration->PeriodCount != KE_AUDIO_OUTPUT_PERIOD_COUNT ||
registration->FramesPerPeriod != KE_AUDIO_OUTPUT_FRAMES_PER_PERIOD ||
registration->PeriodStrideBytes != KE_AUDIO_OUTPUT_PERIOD_BYTES)
return NTLSTATUS_NOT_SUPPORTED;
if (registration->PeriodBufferBytes <
registration->PeriodStrideBytes * registration->PeriodCount)
return NTLSTATUS_BUFFER_TOO_SMALL;
if (((uintptr_t)registration->PeriodBuffer & (sizeof(uint32_t) - 1u)) != 0)
return NTLSTATUS_INVALID_PARAMETER;
if ((registration->Capabilities & IO_AUDIO_ENDPOINT_CAP_HARDWARE_GAIN) != 0) {
if (!registration->Operations.SetGain || !registration->HardwareGainStepMilliDb ||
registration->HardwareGainStepMilliDb > INT32_MAX ||
registration->HardwareGainMinimumMilliDb > registration->HardwareGainMaximumMilliDb ||
registration->HardwareGainMinimumMilliDb > 0 ||
registration->HardwareGainMinimumMilliDb < -IO_AUDIO_HARDWARE_GAIN_LIMIT_MILLIDB ||
registration->HardwareGainMaximumMilliDb > IO_AUDIO_HARDWARE_GAIN_LIMIT_MILLIDB ||
((int64_t)registration->HardwareGainMaximumMilliDb -
registration->HardwareGainMinimumMilliDb) %
registration->HardwareGainStepMilliDb != 0)
return NTLSTATUS_INVALID_PARAMETER;
} else if (registration->Operations.SetGain ||
registration->HardwareGainMinimumMilliDb ||
registration->HardwareGainMaximumMilliDb ||
registration->HardwareGainStepMilliDb) {
return NTLSTATUS_INVALID_PARAMETER;
}
return NTLSTATUS_SUCCESS;
}
// Returns whether one endpoint lifecycle transition is valid.
static bool KiAudioEndpointTransitionAllowed(KE_AUDIO_ENDPOINT_STATE oldState,
KE_AUDIO_ENDPOINT_STATE newState) {
if (oldState == newState)
return true;
switch (oldState) {
case KeAudioEndpointStarting:
return newState == KeAudioEndpointReady ||
newState == KeAudioEndpointStopping;
case KeAudioEndpointReady:
return newState == KeAudioEndpointStopping ||
newState == KeAudioEndpointFailed;
case KeAudioEndpointFailed:
return newState == KeAudioEndpointStopping;
case KeAudioEndpointStopping:
return newState == KeAudioEndpointRemoved;
case KeAudioEndpointRemoved:
return false;
default:
return false;
}
}
// Changes one endpoint state while the registry lock serializes lifecycle ownership.
static void KiAudioSetEndpointStateLocked(IO_AUDIO_ENDPOINT* endpoint,
KE_AUDIO_ENDPOINT_STATE newState) {
if (!KiAudioEndpointTransitionAllowed(endpoint->State, newState))
KiAudioBug("KiAudioSetEndpointStateLocked", "invalid endpoint transition",
((uint64_t)endpoint->State << 32) | (uint32_t)newState);
endpoint->State = newState;
}
// Returns the fixed endpoint preference rank used until policy moves to a service.
static uint32_t KiAudioEndpointRank(IO_AUDIO_ENDPOINT_KIND kind) {
if (kind == IoAudioEndpointLineOut) return 0;
if (kind == IoAudioEndpointSpeakerHeadphone) return 1;
return 2;
}
// Recomputes the default endpoint while the registry lock is held.
static void KiAudioSelectDefaultEndpointLocked(void) {
uint32_t selectedId = 0;
uint32_t selectedRank = UINT32_MAX;
for (LIST_ENTRY* link = gAudioEndpointList.Fwd;
link != &gAudioEndpointList;
link = link->Fwd) {
IO_AUDIO_ENDPOINT* endpoint = CONTAINING_RECORD(link, IO_AUDIO_ENDPOINT, RegistryLink);
if (endpoint->State != KeAudioEndpointReady || !endpoint->Available)
continue;
uint32_t rank = KiAudioEndpointRank(endpoint->Kind);
if (!selectedId || rank < selectedRank ||
(rank == selectedRank && endpoint->EndpointId < selectedId)) {
selectedId = endpoint->EndpointId;
selectedRank = rank;
}
}
gAudioDefaultEndpointId = selectedId;
}
// References one ready endpoint by stable identifier.
static IO_AUDIO_ENDPOINT* KiAudioReferenceEndpointById(uint32_t endpointId) {
IO_AUDIO_ENDPOINT* result = (IO_AUDIO_ENDPOINT*)0;
uintptr_t flags = KeAcquireSpinlock(&gAudioRegistryLock);
for (LIST_ENTRY* link = gAudioEndpointList.Fwd;
link != &gAudioEndpointList;
link = link->Fwd) {
IO_AUDIO_ENDPOINT* endpoint = CONTAINING_RECORD(link, IO_AUDIO_ENDPOINT, RegistryLink);
if (endpoint->EndpointId != endpointId)
continue;
if (endpoint->State == KeAudioEndpointReady && ObTryReferenceObject(&endpoint->Header))
result = endpoint;
break;
}
KeReleaseSpinlock(&gAudioRegistryLock, flags);
return result;
}
// Signals one stream when an armed progress condition or terminal state is present.
static void KiAudioRefreshStreamEventLocked(KE_AUDIO_STREAM* stream) {
uint32_t writableFrames = KE_AUDIO_STREAM_RING_FRAMES - stream->BufferedFrames;
bool signaled = stream->State == KeAudioStreamFailed ||
stream->State == KeAudioStreamClosed;
if ((stream->WaitMask & KE_AUDIO_WAIT_WRITABLE) &&
writableFrames >= stream->WaitWritableFrames)
signaled = 1;
if ((stream->WaitMask & KE_AUDIO_WAIT_CONSUMED) &&
stream->FramesConsumed >= stream->WaitConsumedFrames)
signaled = 1;
if ((stream->WaitMask & KE_AUDIO_WAIT_PRESENTED) &&
stream->FramesPresented >= stream->WaitPresentedFrames)
signaled = 1;
if ((stream->WaitMask & KE_AUDIO_WAIT_STOPPED) &&
stream->State == KeAudioStreamStopped)
signaled = 1;
if ((stream->WaitMask & KE_AUDIO_WAIT_DRAINED) &&
stream->DrainedGeneration != stream->WaitDrainedGeneration)
signaled = 1;
if ((stream->WaitMask & KE_AUDIO_WAIT_FAILED) &&
stream->State == KeAudioStreamFailed)
signaled = 1;
if ((stream->WaitMask & KE_AUDIO_WAIT_DISCONTINUITY) &&
stream->DiscontinuityGeneration != stream->WaitDiscontinuityGeneration)
signaled = 1;
if (signaled)
KeSignalEvent(stream->WaitEvent);
else
KeClearEvent(stream->WaitEvent);
}
// Updates active-stream timing under the endpoint notification lock.
static void KiAudioAdjustActiveStreams(IO_AUDIO_ENDPOINT* endpoint, int delta) {
uintptr_t flags = KeAcquireSpinlock(&endpoint->NotifyLock);
uint32_t oldCount = endpoint->ActiveStreamCount;
if (delta > 0) {
endpoint->ActiveStreamCount += (uint32_t)delta;
endpoint->IdleDeadlineTicks = 0;
} else {
uint32_t amount = (uint32_t)(-delta);
if (oldCount < amount) {
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
KiAudioBug("KiAudioAdjustActiveStreams", "active stream count underflow", oldCount);
}
endpoint->ActiveStreamCount -= amount;
if (endpoint->ActiveStreamCount == 0) {
uint64_t now = KiAudioQueryTicks();
endpoint->IdleDeadlineTicks = now +
(uint64_t)KE_AUDIO_IDLE_STOP_MILLISECONDS * KE_AUDIO_TICKS_PER_MILLISECOND;
}
}
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
KeSignalEvent(endpoint->WorkEvent);
}
// Returns whether one stream state contributes to the active hardware clock.
static bool KiAudioStreamStateIsActive(KE_AUDIO_STREAM_STATE state) {
return state == KeAudioStreamRunning || state == KeAudioStreamDraining;
}
// Returns whether every frame targeted by one drain has reached a terminal disposition.
static bool KiAudioDrainIsCompleteLocked(const KE_AUDIO_STREAM* stream) {
return stream->BufferedFrames == 0 &&
stream->FramesPresented + stream->FramesDiscarded >= stream->FramesSubmitted;
}
// Returns whether one logical stream transition is valid.
static bool KiAudioStreamTransitionAllowed(KE_AUDIO_STREAM_STATE oldState,
KE_AUDIO_STREAM_STATE newState) {
if (oldState == newState)
return true;
if (newState == KeAudioStreamFailed || newState == KeAudioStreamClosed)
return oldState != KeAudioStreamClosed;
switch (oldState) {
case KeAudioStreamCreated:
return newState == KeAudioStreamRunning ||
newState == KeAudioStreamStopped;
case KeAudioStreamRunning:
return newState == KeAudioStreamPaused ||
newState == KeAudioStreamDraining ||
newState == KeAudioStreamStopped;
case KeAudioStreamPaused:
return newState == KeAudioStreamRunning ||
newState == KeAudioStreamDraining ||
newState == KeAudioStreamStopped;
case KeAudioStreamDraining:
return newState == KeAudioStreamStopped;
case KeAudioStreamStopped:
return newState == KeAudioStreamRunning;
case KeAudioStreamFailed:
case KeAudioStreamClosed:
default:
return false;
}
}
// Changes one stream state and updates endpoint activity in the same locked transaction.
static void KiAudioSetStreamStateLocked(KE_AUDIO_STREAM* stream,
KE_AUDIO_STREAM_STATE newState) {
if (!KiAudioStreamTransitionAllowed(stream->State, newState))
KiAudioBug("KiAudioSetStreamStateLocked", "invalid stream transition",
((uint64_t)stream->State << 32) | (uint32_t)newState);
bool oldActive = KiAudioStreamStateIsActive(stream->State);
bool newActive = KiAudioStreamStateIsActive(newState);
stream->State = newState;
if (oldActive != newActive)
KiAudioAdjustActiveStreams(stream->Endpoint, newActive ? 1 : -1);
}
// Records one completed drain before publishing the stopped state.
static void KiAudioCompleteDrainLocked(KE_AUDIO_STREAM* stream) {
if (stream->State != KeAudioStreamDraining || !KiAudioDrainIsCompleteLocked(stream))
KiAudioBug("KiAudioCompleteDrainLocked", "incomplete drain reached completion", stream->State);
stream->DrainedGeneration++;
KiAudioSetStreamStateLocked(stream, KeAudioStreamStopped);
}
// Computes the target left and right gains for one volume, pan, and mute state.
static void KiAudioComputeStreamTargets(float volume,
float pan,
uint8_t muted,
float* leftOut,
float* rightOut) {
float left = volume;
float right = volume;
if (pan > 0.0f)
left *= 1.0f - pan;
else if (pan < 0.0f)
right *= 1.0f + pan;
if (muted) {
left = 0.0f;
right = 0.0f;
}
*leftOut = left;
*rightOut = right;
}
// Starts a ten-millisecond linear gain ramp while the stream mutex is held.
static void KiAudioBeginStreamGainRampLocked(KE_AUDIO_STREAM* stream) {
KiAudioComputeStreamTargets(stream->Volume,
stream->Pan,
stream->Muted,
&stream->TargetLeftGain,
&stream->TargetRightGain);
stream->GainRampRemaining = KE_AUDIO_GAIN_RAMP_FRAMES;
stream->LeftGainStep =
(stream->TargetLeftGain - stream->CurrentLeftGain) /
(float)KE_AUDIO_GAIN_RAMP_FRAMES;
stream->RightGainStep =
(stream->TargetRightGain - stream->CurrentRightGain) /
(float)KE_AUDIO_GAIN_RAMP_FRAMES;
}
// Advances one stream gain ramp by one output frame.
static void KiAudioAdvanceStreamGainLocked(KE_AUDIO_STREAM* stream) {
if (!stream->GainRampRemaining)
return;
stream->CurrentLeftGain += stream->LeftGainStep;
stream->CurrentRightGain += stream->RightGainStep;
stream->GainRampRemaining--;
if (!stream->GainRampRemaining) {
stream->CurrentLeftGain = stream->TargetLeftGain;
stream->CurrentRightGain = stream->TargetRightGain;
}
}
// Decodes one interleaved source frame into normalized stereo samples.
static void KiAudioDecodeSourceFrame(const KE_AUDIO_STREAM* stream,
uint32_t offset,
float* leftOut,
float* rightOut) {
uint32_t index = (stream->ReadFrame + offset) % KE_AUDIO_STREAM_RING_FRAMES;
const uint8_t* frame = stream->Ring + (size_t)index * stream->FrameBytes;
float left = 0.0f;
float right = 0.0f;
const int16_t* samples = (const int16_t*)frame;
left = (float)samples[0] * (1.0f / 32768.0f);
right = stream->Format.ChannelCount == 2
? (float)samples[1] * (1.0f / 32768.0f)
: left;
*leftOut = left;
*rightOut = right;
}
// Applies one final-handle close request while the stream mutex serializes state.
static bool KiAudioApplyCloseRequestLocked(KE_AUDIO_STREAM* stream) {
if (!NtlAtomicLoad32(&stream->CloseRequested) ||
stream->State == KeAudioStreamClosed)
return stream->State == KeAudioStreamClosed;
uint64_t terminalFrames = stream->FramesPresented + stream->FramesDiscarded;
if (terminalFrames < stream->FramesSubmitted)
stream->FramesDiscarded += stream->FramesSubmitted - terminalFrames;
stream->BufferGeneration++;
KiAudioSetStreamStateLocked(stream, KeAudioStreamClosed);
stream->ReadFrame = 0;
stream->WriteFrame = 0;
stream->BufferedFrames = 0;
stream->ResamplePositionQ32 = 0;
stream->ConsecutiveUnderrunPeriods = 0;
stream->DiscontinuityGeneration++;
KiAudioRefreshStreamEventLocked(stream);
return true;
}
// Mixes one logical stream into one endpoint period.
static uint32_t KiAudioMixStreamPeriod(KE_AUDIO_STREAM* stream,
float* mix,
uint32_t frames,
uint64_t* streamGenerationOut) {
KiAudioAcquireMutex(stream->Lock, "KiAudioMixStreamPeriod");
(void)KiAudioApplyCloseRequestLocked(stream);
if (!KiAudioStreamStateIsActive(stream->State)) {
KiAudioReleaseMutex(stream->Lock);
return 0;
}
uint64_t position = stream->ResamplePositionQ32;
uint32_t silentFrames = 0;
for (uint32_t outputFrame = 0; outputFrame < frames; outputFrame++) {
uint32_t sourceOffset = (uint32_t)(position >> 32);
if (sourceOffset >= stream->BufferedFrames) {
silentFrames += frames - outputFrame;
for (; outputFrame < frames; outputFrame++)
KiAudioAdvanceStreamGainLocked(stream);
break;
}
float left0 = 0.0f;
float right0 = 0.0f;
float left1 = 0.0f;
float right1 = 0.0f;
KiAudioDecodeSourceFrame(stream, sourceOffset, &left0, &right0);
if (sourceOffset + 1u < stream->BufferedFrames)
KiAudioDecodeSourceFrame(stream, sourceOffset + 1u, &left1, &right1);
else {
left1 = left0;
right1 = right0;
}
float fraction = (float)(uint32_t)position * (1.0f / 4294967296.0f);
float left = left0 + (left1 - left0) * fraction;
float right = right0 + (right1 - right0) * fraction;
mix[outputFrame * 2u] += left * stream->CurrentLeftGain;
mix[outputFrame * 2u + 1u] += right * stream->CurrentRightGain;
KiAudioAdvanceStreamGainLocked(stream);
position += stream->ResampleStepQ32;
}
uint32_t consumed = (uint32_t)(position >> 32);
if (consumed > stream->BufferedFrames)
consumed = stream->BufferedFrames;
stream->ReadFrame = (stream->ReadFrame + consumed) % KE_AUDIO_STREAM_RING_FRAMES;
stream->BufferedFrames -= consumed;
stream->FramesConsumed += consumed;
stream->ResamplePositionQ32 = position - ((uint64_t)consumed << 32);
stream->OutputFramesMixed += frames - silentFrames;
if (silentFrames && stream->State == KeAudioStreamRunning) {
stream->SilenceFramesInserted += silentFrames;
stream->Underruns++;
stream->ConsecutiveUnderrunPeriods++;
} else if (!silentFrames && stream->ConsecutiveUnderrunPeriods) {
stream->ConsecutiveUnderrunPeriods = 0;
}
*streamGenerationOut = stream->BufferGeneration;
KiAudioRefreshStreamEventLocked(stream);
KiAudioReleaseMutex(stream->Lock);
return consumed;
}
// Snapshots referenced streams under the nonblocking membership lock.
static uint32_t KiAudioSnapshotStreams(IO_AUDIO_ENDPOINT* endpoint,
KE_AUDIO_STREAM** streams) {
uint32_t count = 0;
uintptr_t flags = KeAcquireSpinlock(&endpoint->StreamListLock);
for (LIST_ENTRY* link = endpoint->StreamList.Fwd;
link != &endpoint->StreamList && count < KE_AUDIO_MAX_STREAMS_PER_ENDPOINT;
link = link->Fwd) {
KE_AUDIO_STREAM* stream = CONTAINING_RECORD(link, KE_AUDIO_STREAM, EndpointLink);
if (ObTryReferenceObject(&stream->Header))
streams[count++] = stream;
}
KeReleaseSpinlock(&endpoint->StreamListLock, flags);
return count;
}
// Applies the endpoint master ramp and writes one interleaved HDA period.
static void KiAudioWriteOutputPeriod(IO_AUDIO_ENDPOINT* endpoint,
uint32_t periodIndex,
float* mix) {
KiAudioAcquireMutex(endpoint->ControlLock, "KiAudioWriteOutputPeriod");
float current = 1.0f;
float target = 1.0f;
float step = 0.0f;
uint32_t remaining = 0;
uint64_t generation = 0;
uintptr_t flags = KeAcquireSpinlock(&endpoint->NotifyLock);
current = endpoint->MasterCurrentGain;
target = endpoint->MasterTargetGain;
step = endpoint->MasterGainStep;
remaining = endpoint->MasterRampRemaining;
generation = endpoint->MasterGainGeneration;
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
int16_t* output = (int16_t*)(endpoint->PeriodBuffer +
(size_t)periodIndex * endpoint->PeriodStrideBytes);
for (uint32_t frame = 0; frame < endpoint->FramesPerPeriod; frame++) {
if (remaining) {
current += step;
remaining--;
if (!remaining)
current = target;
}
output[frame * 2u] = KiAudioFloatToOutputSample(mix[frame * 2u] * current);
output[frame * 2u + 1u] = KiAudioFloatToOutputSample(mix[frame * 2u + 1u] * current);
}
flags = KeAcquireSpinlock(&endpoint->NotifyLock);
if (endpoint->MasterGainGeneration == generation) {
endpoint->MasterCurrentGain = current;
endpoint->MasterRampRemaining = remaining;
if (!remaining)
endpoint->MasterGainStep = 0.0f;
}
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
KiAudioReleaseMutex(endpoint->ControlLock);
}
// Reserves one free hardware period for passive-level mixing.
static bool KiAudioReservePeriod(IO_AUDIO_ENDPOINT* endpoint,
uint32_t* periodIndexOut,
uint64_t* generationOut) {
uintptr_t flags = KeAcquireSpinlock(&endpoint->NotifyLock);
for (uint32_t i = 0; i < endpoint->PeriodCount; i++) {
if (endpoint->Periods[i].State != KE_AUDIO_PERIOD_STATE_FREE)
continue;
if (endpoint->Periods[i].ContributionCount)
KiAudioBug("KiAudioReservePeriod", "free period retains contributions", i);
endpoint->Periods[i].State = KE_AUDIO_PERIOD_STATE_FILLING;
*periodIndexOut = i;
*generationOut = endpoint->Periods[i].Generation;
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
return true;
}
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
return false;
}
// Discards one contribution array after its mixed source frames cannot be presented.
static void KiAudioDiscardContributions(KE_AUDIO_PERIOD_CONTRIBUTION* contributions,
uint32_t contributionCount) {
for (uint32_t i = 0; i < contributionCount; i++) {
KE_AUDIO_PERIOD_CONTRIBUTION* contribution = &contributions[i];
KE_AUDIO_STREAM* stream = contribution->Stream;
KiAudioAcquireMutex(stream->Lock, "KiAudioDiscardContributions");
if (stream->BufferGeneration == contribution->StreamGeneration) {
stream->FramesDiscarded += contribution->SourceFrames;
if (stream->State == KeAudioStreamDraining &&
KiAudioDrainIsCompleteLocked(stream))
KiAudioCompleteDrainLocked(stream);
KiAudioRefreshStreamEventLocked(stream);
}
KiAudioReleaseMutex(stream->Lock);
ObDereferenceObject(&stream->Header);
}
}
// Commits one mixed period only if no missed deadline invalidated the reservation.
static int KiAudioCommitPeriod(IO_AUDIO_ENDPOINT* endpoint,
uint32_t periodIndex,
uint64_t generation,
KE_AUDIO_PERIOD_CONTRIBUTION* contributions,
uint32_t contributionCount) {
uintptr_t flags = KeAcquireSpinlock(&endpoint->NotifyLock);
KE_AUDIO_PERIOD_STATE* period = &endpoint->Periods[periodIndex];
int committed = period->State == KE_AUDIO_PERIOD_STATE_FILLING &&
period->Generation == generation;
if (committed) {
if (period->ContributionCount)
KiAudioBug("KiAudioCommitPeriod", "stale period contributions", periodIndex);
for (uint32_t i = 0; i < contributionCount; i++)
period->Contributions[i] = contributions[i];
period->ContributionCount = contributionCount;
period->State = KE_AUDIO_PERIOD_STATE_READY;
endpoint->PeriodsMixed++;
}
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
if (!committed) {
memset(endpoint->PeriodBuffer + (size_t)periodIndex * endpoint->PeriodStrideBytes,
0,
endpoint->PeriodStrideBytes);
KiAudioDiscardContributions(contributions, contributionCount);
flags = KeAcquireSpinlock(&endpoint->NotifyLock);
if (period->State == KE_AUDIO_PERIOD_STATE_FILLING ||
period->State == KE_AUDIO_PERIOD_STATE_COMPLETED)
period->State = KE_AUDIO_PERIOD_STATE_FREE;
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
}
return committed;
}
// Mixes one free endpoint period from all currently referenced streams.
static int KiAudioMixOnePeriod(IO_AUDIO_ENDPOINT* endpoint) {
uint32_t periodIndex = 0;
uint64_t generation = 0;
if (!KiAudioReservePeriod(endpoint, &periodIndex, &generation))
return 0;
float mix[KE_AUDIO_OUTPUT_FRAMES_PER_PERIOD * KE_AUDIO_OUTPUT_CHANNELS];
memset(mix, 0, sizeof(mix));
KE_AUDIO_PERIOD_CONTRIBUTION contributions[KE_AUDIO_MAX_STREAMS_PER_ENDPOINT];
uint32_t contributionCount = 0;
KE_AUDIO_STREAM* streams[KE_AUDIO_MAX_STREAMS_PER_ENDPOINT];
uint32_t streamCount = KiAudioSnapshotStreams(endpoint, streams);
for (uint32_t i = 0; i < streamCount; i++) {
uint64_t streamGeneration = 0;
uint32_t consumed = KiAudioMixStreamPeriod(streams[i],
mix,
endpoint->FramesPerPeriod,
&streamGeneration);
if (consumed) {
contributions[contributionCount].Stream = streams[i];
contributions[contributionCount].StreamGeneration = streamGeneration;
contributions[contributionCount].SourceFrames = consumed;
contributionCount++;
} else {
ObDereferenceObject(&streams[i]->Header);
}
}
KiAudioWriteOutputPeriod(endpoint, periodIndex, mix);
return KiAudioCommitPeriod(endpoint,
periodIndex,
generation,
contributions,
contributionCount);
}
// Applies one hardware-completed period to per-stream presentation counters.
static bool KiAudioReapOneCompletedPeriod(IO_AUDIO_ENDPOINT* endpoint) {
uint32_t periodIndex = 0;
KE_AUDIO_PERIOD_STATE* period = (KE_AUDIO_PERIOD_STATE*)0;
uintptr_t flags = KeAcquireSpinlock(&endpoint->NotifyLock);
for (uint32_t i = 0; i < endpoint->PeriodCount; i++) {
if (endpoint->Periods[i].State == KE_AUDIO_PERIOD_STATE_COMPLETED) {
periodIndex = i;
period = &endpoint->Periods[i];
period->State = KE_AUDIO_PERIOD_STATE_REAPING;
break;
}
}
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
if (!period)
return false;
for (uint32_t i = 0; i < period->ContributionCount; i++) {
KE_AUDIO_PERIOD_CONTRIBUTION* contribution = &period->Contributions[i];
KE_AUDIO_STREAM* stream = contribution->Stream;
KiAudioAcquireMutex(stream->Lock, "KiAudioReapOneCompletedPeriod");
if (stream->BufferGeneration == contribution->StreamGeneration) {
stream->FramesPresented += contribution->SourceFrames;
if (stream->State == KeAudioStreamDraining &&
KiAudioDrainIsCompleteLocked(stream))
KiAudioCompleteDrainLocked(stream);
KiAudioRefreshStreamEventLocked(stream);
}
KiAudioReleaseMutex(stream->Lock);
ObDereferenceObject(&stream->Header);
}
memset(endpoint->PeriodBuffer + (size_t)periodIndex * endpoint->PeriodStrideBytes,
0,
endpoint->PeriodStrideBytes);
flags = KeAcquireSpinlock(&endpoint->NotifyLock);
period->ContributionCount = 0;
period->State = KE_AUDIO_PERIOD_STATE_FREE;
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
return true;
}
// Counts periods ready for hardware consumption.
static uint32_t KiAudioReadyPeriodCount(IO_AUDIO_ENDPOINT* endpoint) {
uint32_t ready = 0;
uintptr_t flags = KeAcquireSpinlock(&endpoint->NotifyLock);
for (uint32_t i = 0; i < endpoint->PeriodCount; i++) {
if (endpoint->Periods[i].State == KE_AUDIO_PERIOD_STATE_READY)
ready++;
}
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
return ready;
}
// Starts provider DMA after at least two periods are prepared.
static KSTATUS KiAudioStartHardware(IO_AUDIO_ENDPOINT* endpoint) {
KiAudioAcquireMutex(endpoint->ControlLock, "KiAudioStartHardware");
uintptr_t registryFlags = KeAcquireSpinlock(&gAudioRegistryLock);
if (endpoint->State != KeAudioEndpointReady) {
KSTATUS status = KFAILED(endpoint->FailureStatus)
? endpoint->FailureStatus
: NTLSTATUS_DEVICE_REMOVED;
KeReleaseSpinlock(&gAudioRegistryLock, registryFlags);
KiAudioReleaseMutex(endpoint->ControlLock);
return status;
}
KeReleaseSpinlock(&gAudioRegistryLock, registryFlags);
uintptr_t flags = KeAcquireSpinlock(&endpoint->NotifyLock);
if (endpoint->HardwareRunning || endpoint->ActiveStreamCount == 0) {
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
KiAudioReleaseMutex(endpoint->ControlLock);
return NTLSTATUS_SUCCESS;
}
endpoint->HardwareRunning = 1;
endpoint->IdleDeadlineTicks = 0;
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
KSTATUS status = NTLSTATUS_SUCCESS;
int startAttempted = 0;
if (endpoint->Capabilities & IO_AUDIO_ENDPOINT_CAP_HARDWARE_GAIN) {
status = endpoint->Operations.SetGain(endpoint->ProviderContext,
endpoint->MasterHardwareGainMilliDb,
0);
}
if (KSUCCESS(status)) {
startAttempted = 1;
status = endpoint->Operations.Start(endpoint->ProviderContext);
}
if (!KSUCCESS(status) && startAttempted)
endpoint->Operations.Stop(endpoint->ProviderContext);
if (!KSUCCESS(status)) {
flags = KeAcquireSpinlock(&endpoint->NotifyLock);
endpoint->HardwareRunning = 0;
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
KiAudioReleaseMutex(endpoint->ControlLock);
return status;
}
flags = KeAcquireSpinlock(&endpoint->NotifyLock);
endpoint->HardwareStarts++;
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
KiAudioReleaseMutex(endpoint->ControlLock);
return NTLSTATUS_SUCCESS;
}
// Discards one period's unpresented stream contributions during hardware teardown.
static void KiAudioDiscardPeriodContributions(KE_AUDIO_PERIOD_STATE* period) {
KiAudioDiscardContributions(period->Contributions, period->ContributionCount);
period->ContributionCount = 0;
}
// Stops provider DMA and returns every period to a zeroed free state.
static void KiAudioStopHardware(IO_AUDIO_ENDPOINT* endpoint, int hardwareAccessible) {
KiAudioAcquireMutex(endpoint->ControlLock, "KiAudioStopHardware");
uintptr_t flags = KeAcquireSpinlock(&endpoint->NotifyLock);
bool wasRunning = endpoint->HardwareRunning != 0;
endpoint->HardwareRunning = 0;
endpoint->IdleDeadlineTicks = 0;
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
if (wasRunning && hardwareAccessible) {
if (endpoint->Capabilities & IO_AUDIO_ENDPOINT_CAP_HARDWARE_GAIN) {
KSTATUS muteStatus = endpoint->Operations.SetGain(
endpoint->ProviderContext,
endpoint->MasterHardwareGainMilliDb,
1);
if (!KSUCCESS(muteStatus)) {
DbgPrint("audio: endpoint %llu hardware mute failed status=0x%x\n",
(unsigned long long)endpoint->EndpointId,
muteStatus);
}
}
endpoint->Operations.Stop(endpoint->ProviderContext);
}
while (KiAudioReapOneCompletedPeriod(endpoint)) {
}
for (uint32_t i = 0; i < endpoint->PeriodCount; i++)
KiAudioDiscardPeriodContributions(&endpoint->Periods[i]);
memset(endpoint->PeriodBuffer, 0, endpoint->PeriodBufferBytes);
flags = KeAcquireSpinlock(&endpoint->NotifyLock);
for (uint32_t i = 0; i < endpoint->PeriodCount; i++) {
endpoint->Periods[i].State = KE_AUDIO_PERIOD_STATE_FREE;
endpoint->Periods[i].Generation++;
}
endpoint->MasterCurrentGain = endpoint->MasterTargetGain;
endpoint->MasterGainStep = 0.0f;
endpoint->MasterRampRemaining = 0;
endpoint->MasterGainGeneration++;
if (wasRunning)
endpoint->HardwareStops++;
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
KiAudioReleaseMutex(endpoint->ControlLock);
}
// Fails every logical stream while preserving final counters for later queries.
static void KiAudioFailAllStreams(IO_AUDIO_ENDPOINT* endpoint, KSTATUS status) {
KE_AUDIO_STREAM* streams[KE_AUDIO_MAX_STREAMS_PER_ENDPOINT];
uint32_t streamCount = KiAudioSnapshotStreams(endpoint, streams);
for (uint32_t i = 0; i < streamCount; i++) {
KE_AUDIO_STREAM* stream = streams[i];
KiAudioAcquireMutex(stream->Lock, "KiAudioFailAllStreams");
if (stream->State != KeAudioStreamClosed) {
stream->FramesDiscarded += stream->BufferedFrames;
stream->BufferedFrames = 0;
stream->ReadFrame = 0;
stream->WriteFrame = 0;
stream->ResamplePositionQ32 = 0;
KiAudioSetStreamStateLocked(stream, KeAudioStreamFailed);
stream->FailureStatus = status;
stream->BufferGeneration++;
stream->DiscontinuityGeneration++;
KiAudioRefreshStreamEventLocked(stream);
}
KiAudioReleaseMutex(stream->Lock);
ObDereferenceObject(&stream->Header);
}
uintptr_t flags = KeAcquireSpinlock(&endpoint->NotifyLock);
if (endpoint->ActiveStreamCount != 0) {
uint32_t active = endpoint->ActiveStreamCount;
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
KiAudioBug("KiAudioFailAllStreams", "active streams remained after failure", active);
}
endpoint->IdleDeadlineTicks = 0;
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
}
// Applies hardware deadline discontinuities to every live logical stream at passive level.
static void KiAudioApplyPendingDiscontinuities(IO_AUDIO_ENDPOINT* endpoint) {
uintptr_t flags = KeAcquireSpinlock(&endpoint->NotifyLock);
uint64_t pending = endpoint->PendingDiscontinuities;
endpoint->PendingDiscontinuities = 0;
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
if (!pending)
return;
KE_AUDIO_STREAM* streams[KE_AUDIO_MAX_STREAMS_PER_ENDPOINT];
uint32_t streamCount = KiAudioSnapshotStreams(endpoint, streams);
for (uint32_t i = 0; i < streamCount; i++) {
KE_AUDIO_STREAM* stream = streams[i];
KiAudioAcquireMutex(stream->Lock, "KiAudioApplyPendingDiscontinuities");
if (KiAudioStreamStateIsActive(stream->State)) {
stream->Underruns += pending;
stream->SilenceFramesInserted += pending * endpoint->FramesPerPeriod;
stream->DiscontinuityGeneration += pending;
KiAudioRefreshStreamEventLocked(stream);
}
KiAudioReleaseMutex(stream->Lock);
ObDereferenceObject(&stream->Header);
}
}
// Returns one coherent endpoint lifecycle snapshot.
static void KiAudioSnapshotLifecycle(IO_AUDIO_ENDPOINT* endpoint,
KE_AUDIO_LIFECYCLE_SNAPSHOT* snapshot) {
uintptr_t flags = KeAcquireSpinlock(&gAudioRegistryLock);
snapshot->State = endpoint->State;
snapshot->FailureStatus = endpoint->FailureStatus;
snapshot->RemovalMode = endpoint->RemovalMode;
snapshot->SkipProviderStop = endpoint->SkipProviderStop;
KeReleaseSpinlock(&gAudioRegistryLock, flags);
}
// Processes all currently available period and lifecycle work.
static bool KiAudioProcessEndpoint(IO_AUDIO_ENDPOINT* endpoint,
uint64_t* waitMillisecondsOut) {
*waitMillisecondsOut = (uint64_t)-1;
while (KiAudioReapOneCompletedPeriod(endpoint)) {
}
KiAudioApplyPendingDiscontinuities(endpoint);
KE_AUDIO_LIFECYCLE_SNAPSHOT lifecycle;
KiAudioSnapshotLifecycle(endpoint, &lifecycle);
KSTATUS failure = lifecycle.FailureStatus;
if (lifecycle.State == KeAudioEndpointStopping ||
lifecycle.State == KeAudioEndpointFailed ||
lifecycle.State == KeAudioEndpointRemoved) {
bool accessible = !lifecycle.SkipProviderStop &&
lifecycle.RemovalMode != IoAudioSurpriseRemoval;
KiAudioStopHardware(endpoint, accessible);
if (lifecycle.State == KeAudioEndpointRemoved && KSUCCESS(failure))
failure = NTLSTATUS_DEVICE_REMOVED;
if (KSUCCESS(failure))
failure = NTLSTATUS_DEVICE_ERROR;
KiAudioFailAllStreams(endpoint, failure);
return false;
}
uintptr_t flags = KeAcquireSpinlock(&endpoint->NotifyLock);
uint32_t active = endpoint->ActiveStreamCount;
bool running = endpoint->HardwareRunning != 0;
uint64_t idleDeadline = endpoint->IdleDeadlineTicks;
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
if (active && !running) {
while (KiAudioReadyPeriodCount(endpoint) < 2u &&
KiAudioMixOnePeriod(endpoint)) {
}
if (KiAudioReadyPeriodCount(endpoint) >= 2u) {
KSTATUS start = KiAudioStartHardware(endpoint);
if (!KSUCCESS(start)) {
uintptr_t registryFlags = KeAcquireSpinlock(&gAudioRegistryLock);
KiAudioSetEndpointStateLocked(endpoint, KeAudioEndpointFailed);
endpoint->FailureStatus = start;
KiAudioSelectDefaultEndpointLocked();
KeReleaseSpinlock(&gAudioRegistryLock, registryFlags);
KeSignalEvent(endpoint->WorkEvent);
return true;
}
}
}
flags = KeAcquireSpinlock(&endpoint->NotifyLock);
active = endpoint->ActiveStreamCount;
running = endpoint->HardwareRunning;
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
if (active || running) {
while (KiAudioMixOnePeriod(endpoint)) {
}
}
flags = KeAcquireSpinlock(&endpoint->NotifyLock);
active = endpoint->ActiveStreamCount;
running = endpoint->HardwareRunning;
idleDeadline = endpoint->IdleDeadlineTicks;
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
if (!active && running) {
uint64_t now = KiAudioQueryTicks();
if (!idleDeadline) {
flags = KeAcquireSpinlock(&endpoint->NotifyLock);
endpoint->IdleDeadlineTicks = now +
(uint64_t)KE_AUDIO_IDLE_STOP_MILLISECONDS * KE_AUDIO_TICKS_PER_MILLISECOND;
idleDeadline = endpoint->IdleDeadlineTicks;
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
}
if (now >= idleDeadline) {
KiAudioStopHardware(endpoint, 1);
} else {
uint64_t remainingTicks = idleDeadline - now;
*waitMillisecondsOut = (remainingTicks + KE_AUDIO_TICKS_PER_MILLISECOND - 1u) /
KE_AUDIO_TICKS_PER_MILLISECOND;
if (!*waitMillisecondsOut)
*waitMillisecondsOut = 1;
}
}
return true;
}
// Runs one endpoint's real-time mixer and provider lifecycle loop.
static void KiAudioMixerThread(void* argument) {
IO_AUDIO_ENDPOINT* endpoint = (IO_AUDIO_ENDPOINT*)argument;
while (true) {
KeClearEvent(endpoint->WorkEvent);
uint64_t waitMilliseconds = (uint64_t)-1;
if (!KiAudioProcessEndpoint(endpoint, &waitMilliseconds))
break;
KSTATUS waitStatus = NTLSTATUS_SUCCESS;
if (waitMilliseconds == (uint64_t)-1) {
waitStatus = KeWaitForEvent(endpoint->WorkEvent, 0);
} else {
EVENT* events[1] = { endpoint->WorkEvent };
uint32_t selected = 0;
waitStatus = KeWaitForMultipleEvents(events,
1,
0,
waitMilliseconds,
&selected);
}
if (waitStatus != NTLSTATUS_SUCCESS && waitStatus != NTLSTATUS_TIMEOUT)
KiAudioBug("KiAudioMixerThread", "work wait failed", waitStatus);
}
KeSignalEvent(endpoint->MixerExitEvent);
ObDereferenceObject(&endpoint->Header);
}
// Destroys one fully unregistered endpoint after its mixer and streams leave.
static void KiAudioEndpointDestroy(OBJECT* object) {
IO_AUDIO_ENDPOINT* endpoint = CONTAINING_RECORD(object, IO_AUDIO_ENDPOINT, Header);
if (endpoint->Registered || endpoint->MixerThread ||
!LIST_IS_EMPTY(&endpoint->StreamList) || endpoint->HardwareRunning ||
endpoint->ActiveStreamCount)
KiAudioBug("KiAudioEndpointDestroy", "live endpoint reached destruction", endpoint->EndpointId);
if (endpoint->Device)
IoDereferenceDeviceObject(endpoint->Device);
if (endpoint->ControlLock)
ObDereferenceObject(&endpoint->ControlLock->Header);
if (endpoint->WorkEvent)
ObDereferenceObject(&endpoint->WorkEvent->Header);
if (endpoint->MixerExitEvent)
ObDereferenceObject(&endpoint->MixerExitEvent->Header);
}
// Records one process handle that owns the stream.
void KeAudioStreamHandleOpened(KE_AUDIO_STREAM* stream) {
if (!stream)
return;
int32_t handleCount = NtlAtomicIncrement32(&stream->ProcessHandleCount);
if (handleCount <= 0)
KiAudioBug("KeAudioStreamHandleOpened", "invalid process handle count", handleCount);
}
// Requests passive stream closure when the final process handle disappears.
void KeAudioStreamHandleClosed(KE_AUDIO_STREAM* stream) {
if (!stream)
return;
int32_t handleCount = NtlAtomicDecrement32(&stream->ProcessHandleCount);
if (handleCount < 0)
KiAudioBug("KeAudioStreamHandleClosed", "process handle underflow", handleCount);
if (handleCount != 0)
return;
NtlAtomicExchange32(&stream->CloseRequested, 1);
KeSignalEvent(stream->WaitEvent);
if (stream->Endpoint)
KeSignalEvent(stream->Endpoint->WorkEvent);
}
// Destroys one exclusively owned stream without entering blocking primitives.
static void KiAudioStreamDestroy(OBJECT* object) {
KE_AUDIO_STREAM* stream = CONTAINING_RECORD(object, KE_AUDIO_STREAM, Header);
IO_AUDIO_ENDPOINT* endpoint = stream->Endpoint;
if (endpoint) {
uintptr_t flags = KeAcquireSpinlock(&endpoint->StreamListLock);
if (stream->EndpointLink.Fwd != &stream->EndpointLink ||
stream->EndpointLink.Bwd != &stream->EndpointLink)
KeListRemoveChecked(&stream->EndpointLink);
KeReleaseSpinlock(&endpoint->StreamListLock, flags);
}
if (stream->Lock &&
(stream->Lock->Owner || !LIST_IS_EMPTY(&stream->Lock->WaitList)))
KiAudioBug("KiAudioStreamDestroy", "live stream mutex reached destruction",
(uint64_t)(uintptr_t)stream->Lock->Owner);
if (!KiAudioStreamTransitionAllowed(stream->State, KeAudioStreamClosed))
KiAudioBug("KiAudioStreamDestroy", "invalid terminal stream state", stream->State);
bool wasActive = KiAudioStreamStateIsActive(stream->State);
stream->State = KeAudioStreamClosed;
if (wasActive)
KiAudioAdjustActiveStreams(endpoint, -1);
if (stream->Ring)
MmFreePool(stream->Ring);
if (stream->WaitEvent)
ObDereferenceObject(&stream->WaitEvent->Header);
if (stream->Lock)
ObDereferenceObject(&stream->Lock->Header);
if (endpoint)
ObDereferenceObject(&endpoint->Header);
NtlAtomicIncrement64(&gAudioCoreStats.StreamsDestroyed);
}
// Registers one hardware-provider endpoint.
static KSTATUS KiAudioRegisterEndpoint(const IO_AUDIO_ENDPOINT_REGISTRATION* registration,
IO_AUDIO_ENDPOINT** outEndpoint) {
if (!outEndpoint)
return NTLSTATUS_INVALID_PARAMETER;
*outEndpoint = (IO_AUDIO_ENDPOINT*)0;
KSTATUS status = KiAudioValidateRegistration(registration);
if (!KSUCCESS(status))
return status;
IO_AUDIO_ENDPOINT* endpoint = (IO_AUDIO_ENDPOINT*)ObAllocateObject(
ObjectTypeAudioEndpoint,
sizeof(IO_AUDIO_ENDPOINT));
if (!endpoint)
return NTLSTATUS_NO_MEMORY;
endpoint->Header.Destroy = KiAudioEndpointDestroy;
LIST_INIT(&endpoint->RegistryLink);
LIST_INIT(&endpoint->StreamList);
endpoint->StreamListLock = (SPINLOCK)SPINLOCK_INIT;
endpoint->NotifyLock = (SPINLOCK)SPINLOCK_INIT;
endpoint->Kind = registration->Kind;
endpoint->State = KeAudioEndpointStarting;
endpoint->Capabilities = registration->Capabilities;
endpoint->Available = registration->InitiallyAvailable ? 1u : 0u;
endpoint->Device = registration->Device;
endpoint->ProviderContext = registration->ProviderContext;
endpoint->Operations = registration->Operations;
endpoint->PeriodBuffer = (uint8_t*)registration->PeriodBuffer;
endpoint->PeriodBufferBytes = registration->PeriodBufferBytes;
endpoint->PeriodCount = registration->PeriodCount;
endpoint->FramesPerPeriod = registration->FramesPerPeriod;
endpoint->PeriodStrideBytes = registration->PeriodStrideBytes;
endpoint->HardwareFormat = registration->HardwareFormat;
endpoint->HardwareGainMinimumMilliDb = registration->HardwareGainMinimumMilliDb;
endpoint->HardwareGainMaximumMilliDb = registration->HardwareGainMaximumMilliDb;
endpoint->HardwareGainStepMilliDb = registration->HardwareGainStepMilliDb;
endpoint->MasterRequestedVolume = 1.0f;
endpoint->MasterHardwareGainMilliDb = KiAudioHighestSafeHardwareGain(endpoint);
endpoint->MasterCurrentGain = KiAudioSoftwareGainForVolume(endpoint, 1.0f);
endpoint->MasterTargetGain = endpoint->MasterCurrentGain;
endpoint->RemovalMode = IoAudioOrderlyRemoval;
memset(endpoint->PeriodBuffer, 0, endpoint->PeriodBufferBytes);
for (uint32_t i = 0; i < endpoint->PeriodCount; i++)
endpoint->Periods[i].State = KE_AUDIO_PERIOD_STATE_FREE;
if (endpoint->Device)
IoReferenceDeviceObject(endpoint->Device);
status = KeMutexCreate(&endpoint->ControlLock);
if (!KSUCCESS(status)) {
ObDereferenceObject(&endpoint->Header);
return status;
}
status = KeEventCreate(EventManualReset, 0, &endpoint->WorkEvent);
if (!KSUCCESS(status)) {
ObDereferenceObject(&endpoint->Header);
return status;
}
status = KeEventCreate(EventManualReset, 0, &endpoint->MixerExitEvent);
if (!KSUCCESS(status)) {
ObDereferenceObject(&endpoint->Header);
return status;
}
ObReferenceObject(&endpoint->Header);
const KE_THREAD_CREATE_PARAMETERS mixerParameters =
KE_THREAD_CREATE_NAMED_REALTIME(KE_AUDIO_MIXER_REALTIME_PRIORITY, "audio.mixer");
status = KeCreateKernelThread((uintptr_t)KiAudioMixerThread,
(uintptr_t)endpoint,
&mixerParameters,
&endpoint->MixerThread);
if (!KSUCCESS(status)) {
ObDereferenceObject(&endpoint->Header);
ObDereferenceObject(&endpoint->Header);
return status;
}
uintptr_t flags = KeAcquireSpinlock(&gAudioRegistryLock);
if (gAudioEndpointCount >= KE_AUDIO_MAX_ENDPOINTS) {
KiAudioSetEndpointStateLocked(endpoint, KeAudioEndpointStopping);
endpoint->FailureStatus = NTLSTATUS_NO_MEMORY;
KeReleaseSpinlock(&gAudioRegistryLock, flags);
KeSignalEvent(endpoint->WorkEvent);
(void)KeWaitForEvent(endpoint->MixerExitEvent, 0);
ObDereferenceObject(&endpoint->MixerThread->Header);
endpoint->MixerThread = (THREAD*)0;
ObDereferenceObject(&endpoint->Header);
return NTLSTATUS_NO_MEMORY;
}
KSTATUS idStatus = KeAllocateId32(&gAudioEndpointIds, &endpoint->EndpointId);
if (!KSUCCESS(idStatus)) {
KiAudioSetEndpointStateLocked(endpoint, KeAudioEndpointStopping);
endpoint->FailureStatus = idStatus;
KeReleaseSpinlock(&gAudioRegistryLock, flags);
KeSignalEvent(endpoint->WorkEvent);
(void)KeWaitForEvent(endpoint->MixerExitEvent, 0);
ObDereferenceObject(&endpoint->MixerThread->Header);
endpoint->MixerThread = (THREAD*)0;
ObDereferenceObject(&endpoint->Header);
return idStatus;
}
KiAudioSetEndpointStateLocked(endpoint, KeAudioEndpointReady);
endpoint->Registered = 1;
ObReferenceObject(&endpoint->Header);
KeListInsertTailChecked(&gAudioEndpointList, &endpoint->RegistryLink);
gAudioEndpointCount++;
KiAudioSelectDefaultEndpointLocked();
KeReleaseSpinlock(&gAudioRegistryLock, flags);
NtlAtomicIncrement64(&gAudioCoreStats.EndpointsRegistered);
*outEndpoint = endpoint;
return NTLSTATUS_SUCCESS;
}
// Implements the provider-facing endpoint registration operation.
static KSTATUS KiAudioDdkRegisterEndpoint(const IO_AUDIO_ENDPOINT_REGISTRATION* registration,
IO_AUDIO_ENDPOINT** outEndpoint) {
return KiAudioRegisterEndpoint(registration, outEndpoint);
}
// Removes one endpoint and waits until no provider operation can run again.
static void KiAudioDdkUnregisterEndpoint(IO_AUDIO_ENDPOINT* endpoint,
IO_AUDIO_REMOVAL_MODE mode) {
if (!endpoint || (mode != IoAudioOrderlyRemoval && mode != IoAudioSurpriseRemoval))
KiAudioBug("KiAudioDdkUnregisterEndpoint", "invalid unregister request", mode);
uintptr_t flags = KeAcquireSpinlock(&gAudioRegistryLock);
if (!endpoint->Registered) {
KeReleaseSpinlock(&gAudioRegistryLock, flags);
KiAudioBug("KiAudioDdkUnregisterEndpoint", "duplicate unregister", endpoint->EndpointId);
}
KeListRemoveChecked(&endpoint->RegistryLink);
endpoint->Registered = 0;
KiAudioSetEndpointStateLocked(endpoint, KeAudioEndpointStopping);
if (KSUCCESS(endpoint->FailureStatus))
endpoint->FailureStatus = NTLSTATUS_DEVICE_REMOVED;
endpoint->RemovalMode = mode;
endpoint->SkipProviderStop = mode == IoAudioSurpriseRemoval ? 1u : 0u;
gAudioEndpointCount--;
KiAudioSelectDefaultEndpointLocked();
KeReleaseSpinlock(&gAudioRegistryLock, flags);
KeSignalEvent(endpoint->WorkEvent);
(void)KeWaitForEvent(endpoint->MixerExitEvent, 0);
flags = KeAcquireSpinlock(&gAudioRegistryLock);
KiAudioSetEndpointStateLocked(endpoint, KeAudioEndpointRemoved);
KeReleaseSpinlock(&gAudioRegistryLock, flags);
if (!endpoint->MixerThread)
KiAudioBug("KiAudioDdkUnregisterEndpoint", "missing mixer thread", endpoint->EndpointId);
ObDereferenceObject(&endpoint->MixerThread->Header);
endpoint->MixerThread = (THREAD*)0;
NtlAtomicIncrement64(&gAudioCoreStats.EndpointsUnregistered);
ObDereferenceObject(&endpoint->Header);
ObDereferenceObject(&endpoint->Header);
}
// Advances one provider ring period and wakes the real-time mixer.
static void KiAudioDdkNotifyPeriod(IO_AUDIO_ENDPOINT* endpoint,
uint32_t completedPeriodIndex,
uint64_t presentedFrames,
uint64_t hardwarePositionFrames,
uint64_t interruptCount,
uint64_t timestampTicks) {
if (!endpoint || completedPeriodIndex >= endpoint->PeriodCount)
KiAudioBug("KiAudioDdkNotifyPeriod", "invalid period notification", completedPeriodIndex);
uintptr_t flags = KeAcquireSpinlock(&endpoint->NotifyLock);
KE_AUDIO_PERIOD_STATE* period = &endpoint->Periods[completedPeriodIndex];
if (!endpoint->HardwareRunning) {
endpoint->SpuriousNotifications++;
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
return;
}
int missedDeadline = period->State != KE_AUDIO_PERIOD_STATE_READY;
if (missedDeadline) {
endpoint->Underruns++;
endpoint->DiscontinuityGeneration++;
endpoint->SilenceFramesInserted += endpoint->FramesPerPeriod;
endpoint->PendingDiscontinuities++;
}
if (period->State == KE_AUDIO_PERIOD_STATE_COMPLETED ||
period->State == KE_AUDIO_PERIOD_STATE_REAPING) {
endpoint->SpuriousNotifications++;
} else {
period->Generation++;
period->State = KE_AUDIO_PERIOD_STATE_COMPLETED;
}
endpoint->PeriodsCompleted++;
if (presentedFrames < endpoint->FramesPresented) {
endpoint->DiscontinuityGeneration++;
endpoint->PendingDiscontinuities++;
} else {
endpoint->FramesPresented = presentedFrames;
}
if (hardwarePositionFrames < endpoint->HardwarePositionFrames ||
interruptCount < endpoint->InterruptCount) {
endpoint->DiscontinuityGeneration++;
endpoint->PendingDiscontinuities++;
} else {
endpoint->HardwarePositionFrames = hardwarePositionFrames;
endpoint->InterruptCount = interruptCount;
}
endpoint->LastPresentationTimestampTicks = timestampTicks;
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
KeSignalEvent(endpoint->WorkEvent);
}
// Updates one endpoint's jack-derived availability and default selection.
static void KiAudioDdkSetEndpointAvailable(IO_AUDIO_ENDPOINT* endpoint, bool available) {
if (!endpoint || !(endpoint->Capabilities & IO_AUDIO_ENDPOINT_CAP_JACK_SENSE))
KiAudioBug("KiAudioDdkSetEndpointAvailable", "invalid availability update", 0);
uintptr_t flags = KeAcquireSpinlock(&gAudioRegistryLock);
if (endpoint->State == KeAudioEndpointReady) {
endpoint->Available = available ? 1u : 0u;
KiAudioSelectDefaultEndpointLocked();
}
KeReleaseSpinlock(&gAudioRegistryLock, flags);
}
// Fails one endpoint from provider completion or recovery code.
static void KiAudioDdkReportEndpointFailure(IO_AUDIO_ENDPOINT* endpoint,
KSTATUS status,
uint32_t flags) {
if (!endpoint || !KFAILED(status) || (flags & ~IO_AUDIO_FAILURE_VALID_MASK) != 0)
KiAudioBug("KiAudioDdkReportEndpointFailure", "invalid failure report", status);
uintptr_t registryFlags = KeAcquireSpinlock(&gAudioRegistryLock);
if (flags & IO_AUDIO_FAILURE_HARDWARE_INACCESSIBLE)
endpoint->SkipProviderStop = 1;
if (endpoint->State == KeAudioEndpointReady) {
KiAudioSetEndpointStateLocked(endpoint, KeAudioEndpointFailed);
endpoint->FailureStatus = status;
endpoint->Available = 0;
KiAudioSelectDefaultEndpointLocked();
}
KeReleaseSpinlock(&gAudioRegistryLock, registryFlags);
DbgPrint("audio: endpoint %llu failed status=0x%x flags=0x%x\n",
(unsigned long long)endpoint->EndpointId,
status,
flags);
KeSignalEvent(endpoint->WorkEvent);
}
static const NTL_DDK_AUDIO gAudioDdk = {
.RegisterEndpoint = KiAudioDdkRegisterEndpoint,
.UnregisterEndpoint = KiAudioDdkUnregisterEndpoint,
.NotifyPeriod = KiAudioDdkNotifyPeriod,
.SetEndpointAvailable = KiAudioDdkSetEndpointAvailable,
.ReportEndpointFailure = KiAudioDdkReportEndpointFailure,
};
// Initializes the one kernel audio registry.
void KeAudioInitialize(void) {
if (gAudioInitialized)
KiAudioBug("KeAudioInitialize", "duplicate initialization", 0);
LIST_INIT(&gAudioEndpointList);
gAudioInitialized = 1;
}
// Returns the immutable provider-facing audio table.
const NTL_DDK_AUDIO* KeAudioGetDdk(void) {
return &gAudioDdk;
}
// Returns the current default endpoint identifier.
uint32_t KeAudioGetDefaultEndpointId(void) {
uintptr_t flags = KeAcquireSpinlock(&gAudioRegistryLock);
uint32_t endpointId = gAudioDefaultEndpointId;
KeReleaseSpinlock(&gAudioRegistryLock, flags);
return endpointId;
}
// Copies endpoint snapshots without retaining registry locks across passive operations.
KSTATUS KeAudioQueryEndpoints(KE_AUDIO_ENDPOINT_INFO* endpoints,
uint32_t capacity,
uint32_t* countOut) {
if (!countOut || (capacity && !endpoints))
return NTLSTATUS_INVALID_PARAMETER;
IO_AUDIO_ENDPOINT* references[KE_AUDIO_MAX_ENDPOINTS];
uint32_t count = 0;
uint32_t defaultId = 0;
uintptr_t flags = KeAcquireSpinlock(&gAudioRegistryLock);
defaultId = gAudioDefaultEndpointId;
for (LIST_ENTRY* link = gAudioEndpointList.Fwd;
link != &gAudioEndpointList && count < KE_AUDIO_MAX_ENDPOINTS;
link = link->Fwd) {
IO_AUDIO_ENDPOINT* endpoint = CONTAINING_RECORD(link, IO_AUDIO_ENDPOINT, RegistryLink);
if (ObTryReferenceObject(&endpoint->Header))
references[count++] = endpoint;
}
KeReleaseSpinlock(&gAudioRegistryLock, flags);
*countOut = count;
uint32_t copyCount = capacity < count ? capacity : count;
for (uint32_t i = 0; i < count; i++) {
IO_AUDIO_ENDPOINT* endpoint = references[i];
if (i < copyCount) {
KE_AUDIO_ENDPOINT_INFO* info = &endpoints[i];
memset(info, 0, sizeof(*info));
flags = KeAcquireSpinlock(&gAudioRegistryLock);
info->EndpointId = endpoint->EndpointId;
info->Kind = endpoint->Kind;
info->State = endpoint->State;
info->Capabilities = endpoint->Capabilities;
info->HardwareFormat = endpoint->HardwareFormat;
info->PeriodCount = endpoint->PeriodCount;
info->FramesPerPeriod = endpoint->FramesPerPeriod;
info->Available = endpoint->Available;
info->IsDefault = endpoint->EndpointId == defaultId;
info->FailureStatus = endpoint->FailureStatus;
KeReleaseSpinlock(&gAudioRegistryLock, flags);
uintptr_t streamFlags = KeAcquireSpinlock(&endpoint->StreamListLock);
for (LIST_ENTRY* link = endpoint->StreamList.Fwd;
link != &endpoint->StreamList;
link = link->Fwd)
info->StreamCount++;
KeReleaseSpinlock(&endpoint->StreamListLock, streamFlags);
flags = KeAcquireSpinlock(&endpoint->NotifyLock);
info->ActiveStreamCount = endpoint->ActiveStreamCount;
info->HardwareRunning = endpoint->HardwareRunning;
info->MasterMuted = endpoint->MasterMuted;
info->MasterHardwareGainMilliDb = endpoint->MasterHardwareGainMilliDb;
info->MasterRequestedVolume = endpoint->MasterRequestedVolume;
info->PeriodsMixed = endpoint->PeriodsMixed;
info->PeriodsCompleted = endpoint->PeriodsCompleted;
info->FramesPresented = endpoint->FramesPresented;
info->HardwarePositionFrames = endpoint->HardwarePositionFrames;
info->InterruptCount = endpoint->InterruptCount;
info->LastPresentationTimestampTicks = endpoint->LastPresentationTimestampTicks;
info->SilenceFramesInserted = endpoint->SilenceFramesInserted;
info->Underruns = endpoint->Underruns;
info->DiscontinuityGeneration = endpoint->DiscontinuityGeneration;
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
}
ObDereferenceObject(&endpoint->Header);
}
return capacity < count ? NTLSTATUS_BUFFER_TOO_SMALL : NTLSTATUS_SUCCESS;
}
// Creates one logical render stream with a fixed-capacity source ring.
KSTATUS KeAudioCreateStream(uint32_t endpointId,
const IO_AUDIO_FORMAT* format,
KE_AUDIO_STREAM** outStream) {
if (!outStream || !KiAudioValidateSourceFormat(format))
return NTLSTATUS_INVALID_PARAMETER;
*outStream = (KE_AUDIO_STREAM*)0;
IO_AUDIO_ENDPOINT* endpoint = KiAudioReferenceEndpointById(endpointId);
if (!endpoint)
return NTLSTATUS_NOT_FOUND;
KE_AUDIO_STREAM* stream = (KE_AUDIO_STREAM*)ObAllocateObject(
ObjectTypeAudioStream,
sizeof(KE_AUDIO_STREAM));
if (!stream) {
ObDereferenceObject(&endpoint->Header);
return NTLSTATUS_NO_MEMORY;
}
stream->Header.Destroy = KiAudioStreamDestroy;
LIST_INIT(&stream->EndpointLink);
stream->Endpoint = endpoint;
stream->State = KeAudioStreamCreated;
stream->Format = *format;
stream->FrameBytes = KiAudioFrameBytes(format);
stream->ResampleStepQ32 = ((uint64_t)format->SampleRate << 32) /
KE_AUDIO_OUTPUT_SAMPLE_RATE;
stream->WaitMask = KE_AUDIO_WAIT_WRITABLE;
stream->WaitWritableFrames = 1;
stream->Volume = 1.0f;
stream->CurrentLeftGain = 1.0f;
stream->CurrentRightGain = 1.0f;
stream->TargetLeftGain = 1.0f;
stream->TargetRightGain = 1.0f;
KSTATUS status = KeMutexCreate(&stream->Lock);
if (!KSUCCESS(status)) {
ObDereferenceObject(&stream->Header);
return status;
}
status = KeEventCreate(EventManualReset, 1, &stream->WaitEvent);
if (!KSUCCESS(status)) {
ObDereferenceObject(&stream->Header);
return status;
}
stream->Ring = (uint8_t*)MmAllocatePoolTag(
(size_t)KE_AUDIO_STREAM_RING_FRAMES * stream->FrameBytes,
KE_AUDIO_RING_TAG);
if (!stream->Ring) {
ObDereferenceObject(&stream->Header);
return NTLSTATUS_NO_MEMORY;
}
memset(stream->Ring,
0,
(size_t)KE_AUDIO_STREAM_RING_FRAMES * stream->FrameBytes);
uintptr_t registryFlags = KeAcquireSpinlock(&gAudioRegistryLock);
int endpointReady = endpoint->Registered && endpoint->State == KeAudioEndpointReady;
uint32_t streamCount = 0;
uintptr_t streamFlags = KeAcquireSpinlock(&endpoint->StreamListLock);
if (endpointReady) {
for (LIST_ENTRY* link = endpoint->StreamList.Fwd;
link != &endpoint->StreamList;
link = link->Fwd)
streamCount++;
}
if (!endpointReady || streamCount >= KE_AUDIO_MAX_STREAMS_PER_ENDPOINT) {
KSTATUS createStatus = !endpointReady
? (KFAILED(endpoint->FailureStatus)
? endpoint->FailureStatus
: NTLSTATUS_DEVICE_REMOVED)
: NTLSTATUS_NO_MEMORY;
KeReleaseSpinlock(&endpoint->StreamListLock, streamFlags);
KeReleaseSpinlock(&gAudioRegistryLock, registryFlags);
ObDereferenceObject(&stream->Header);
return createStatus;
}
KeListInsertTailChecked(&endpoint->StreamList, &stream->EndpointLink);
KeReleaseSpinlock(&endpoint->StreamListLock, streamFlags);
KeReleaseSpinlock(&gAudioRegistryLock, registryFlags);
NtlAtomicIncrement64(&gAudioCoreStats.StreamsCreated);
*outStream = stream;
return NTLSTATUS_SUCCESS;
}
// Returns the stream event used by future waitable handles.
EVENT* KeAudioStreamWaitEvent(KE_AUDIO_STREAM* stream) {
return stream ? stream->WaitEvent : (EVENT*)0;
}
// Arms one wait condition while holding the stream state that drives its event.
KSTATUS KeAudioArmStreamWait(KE_AUDIO_STREAM* stream,
const KE_AUDIO_STREAM_WAIT_CONDITION* condition) {
if (!stream || !condition || !condition->Mask ||
(condition->Mask & ~KE_AUDIO_WAIT_VALID_MASK) != 0 ||
((condition->Mask & KE_AUDIO_WAIT_WRITABLE) &&
(!condition->WritableFrames ||
condition->WritableFrames > KE_AUDIO_STREAM_RING_FRAMES)))
return NTLSTATUS_INVALID_PARAMETER;
KiAudioAcquireMutex(stream->Lock, "KeAudioArmStreamWait");
if (stream->State == KeAudioStreamClosed) {
KiAudioReleaseMutex(stream->Lock);
return NTLSTATUS_OBJECT_DEAD;
}
stream->WaitMask = condition->Mask;
stream->WaitWritableFrames = condition->WritableFrames;
stream->WaitConsumedFrames = condition->ConsumedFrames;
stream->WaitPresentedFrames = condition->PresentedFrames;
stream->WaitDrainedGeneration = condition->DrainedGeneration;
stream->WaitDiscontinuityGeneration = condition->DiscontinuityGeneration;
KiAudioRefreshStreamEventLocked(stream);
KiAudioReleaseMutex(stream->Lock);
return NTLSTATUS_SUCCESS;
}
// Copies a bounded source-frame run into one stream ring.
KSTATUS KeAudioWriteStream(KE_AUDIO_STREAM* stream,
const void* frames,
uint32_t requestedFrames,
uint32_t* writtenFramesOut) {
if (!stream || !writtenFramesOut || (requestedFrames && !frames))
return NTLSTATUS_INVALID_PARAMETER;
*writtenFramesOut = 0;
if (!requestedFrames)
return NTLSTATUS_SUCCESS;
KiAudioAcquireMutex(stream->Lock, "KeAudioWriteStream");
if (stream->State == KeAudioStreamFailed || stream->State == KeAudioStreamClosed) {
KSTATUS status = stream->FailureStatus ? stream->FailureStatus : NTLSTATUS_OBJECT_DEAD;
KiAudioReleaseMutex(stream->Lock);
return status;
}
if (stream->State == KeAudioStreamDraining) {
KiAudioReleaseMutex(stream->Lock);
return NTLSTATUS_INVALID_PARAMETER;
}
uint32_t writable = KE_AUDIO_STREAM_RING_FRAMES - stream->BufferedFrames;
if (!writable) {
KiAudioRefreshStreamEventLocked(stream);
KiAudioReleaseMutex(stream->Lock);
return NTLSTATUS_WOULD_BLOCK;
}
uint32_t toWrite = requestedFrames < writable ? requestedFrames : writable;
uint32_t firstFrames = KE_AUDIO_STREAM_RING_FRAMES - stream->WriteFrame;
if (firstFrames > toWrite) firstFrames = toWrite;
size_t firstBytes = (size_t)firstFrames * stream->FrameBytes;
memcpy(stream->Ring + (size_t)stream->WriteFrame * stream->FrameBytes,
frames,
firstBytes);
uint32_t secondFrames = toWrite - firstFrames;
if (secondFrames) {
memcpy(stream->Ring,
(const uint8_t*)frames + firstBytes,
(size_t)secondFrames * stream->FrameBytes);
}
stream->WriteFrame = (stream->WriteFrame + toWrite) % KE_AUDIO_STREAM_RING_FRAMES;
stream->BufferedFrames += toWrite;
stream->FramesSubmitted += toWrite;
*writtenFramesOut = toWrite;
KiAudioRefreshStreamEventLocked(stream);
bool active = KiAudioStreamStateIsActive(stream->State);
KiAudioReleaseMutex(stream->Lock);
if (active)
KeSignalEvent(stream->Endpoint->WorkEvent);
return NTLSTATUS_SUCCESS;
}
// Starts one created or stopped stream.
KSTATUS KeAudioStartStream(KE_AUDIO_STREAM* stream) {
if (!stream) return NTLSTATUS_INVALID_PARAMETER;
KiAudioAcquireMutex(stream->Lock, "KeAudioStartStream");
if (stream->State != KeAudioStreamCreated && stream->State != KeAudioStreamStopped) {
KSTATUS status = stream->State == KeAudioStreamFailed
? stream->FailureStatus
: NTLSTATUS_INVALID_PARAMETER;
KiAudioReleaseMutex(stream->Lock);
return status;
}
KiAudioSetStreamStateLocked(stream, KeAudioStreamRunning);
stream->FailureStatus = NTLSTATUS_SUCCESS;
KiAudioRefreshStreamEventLocked(stream);
KiAudioReleaseMutex(stream->Lock);
NtlAtomicIncrement64(&gAudioCoreStats.Starts);
return NTLSTATUS_SUCCESS;
}
// Pauses one running stream without discarding source frames.
KSTATUS KeAudioPauseStream(KE_AUDIO_STREAM* stream) {
if (!stream) return NTLSTATUS_INVALID_PARAMETER;
KiAudioAcquireMutex(stream->Lock, "KeAudioPauseStream");
if (stream->State != KeAudioStreamRunning) {
KSTATUS status = stream->State == KeAudioStreamFailed
? stream->FailureStatus
: NTLSTATUS_INVALID_PARAMETER;
KiAudioReleaseMutex(stream->Lock);
return status;
}
KiAudioSetStreamStateLocked(stream, KeAudioStreamPaused);
KiAudioRefreshStreamEventLocked(stream);
KiAudioReleaseMutex(stream->Lock);
NtlAtomicIncrement64(&gAudioCoreStats.Pauses);
return NTLSTATUS_SUCCESS;
}
// Resumes one paused stream.
KSTATUS KeAudioResumeStream(KE_AUDIO_STREAM* stream) {
if (!stream) return NTLSTATUS_INVALID_PARAMETER;
KiAudioAcquireMutex(stream->Lock, "KeAudioResumeStream");
if (stream->State != KeAudioStreamPaused) {
KSTATUS status = stream->State == KeAudioStreamFailed
? stream->FailureStatus
: NTLSTATUS_INVALID_PARAMETER;
KiAudioReleaseMutex(stream->Lock);
return status;
}
KiAudioSetStreamStateLocked(stream, KeAudioStreamRunning);
KiAudioRefreshStreamEventLocked(stream);
KiAudioReleaseMutex(stream->Lock);
NtlAtomicIncrement64(&gAudioCoreStats.Resumes);
return NTLSTATUS_SUCCESS;
}
// Stops accepting writes and plays all currently buffered frames.
KSTATUS KeAudioDrainStream(KE_AUDIO_STREAM* stream) {
if (!stream) return NTLSTATUS_INVALID_PARAMETER;
KiAudioAcquireMutex(stream->Lock, "KeAudioDrainStream");
if (stream->State == KeAudioStreamRunning ||
stream->State == KeAudioStreamPaused) {
KiAudioSetStreamStateLocked(stream, KeAudioStreamDraining);
if (KiAudioDrainIsCompleteLocked(stream))
KiAudioCompleteDrainLocked(stream);
} else {
KSTATUS status = stream->State == KeAudioStreamFailed
? stream->FailureStatus
: NTLSTATUS_INVALID_PARAMETER;
KiAudioReleaseMutex(stream->Lock);
return status;
}
KiAudioRefreshStreamEventLocked(stream);
KiAudioReleaseMutex(stream->Lock);
KeSignalEvent(stream->Endpoint->WorkEvent);
NtlAtomicIncrement64(&gAudioCoreStats.Drains);
return NTLSTATUS_SUCCESS;
}
// Stops future mixing and discards source frames not yet committed to endpoint periods.
KSTATUS KeAudioStopStream(KE_AUDIO_STREAM* stream) {
if (!stream) return NTLSTATUS_INVALID_PARAMETER;
KiAudioAcquireMutex(stream->Lock, "KeAudioStopStream");
if (stream->State == KeAudioStreamFailed || stream->State == KeAudioStreamClosed) {
KSTATUS status = stream->FailureStatus ? stream->FailureStatus : NTLSTATUS_OBJECT_DEAD;
KiAudioReleaseMutex(stream->Lock);
return status;
}
stream->FramesDiscarded += stream->BufferedFrames;
KiAudioSetStreamStateLocked(stream, KeAudioStreamStopped);
stream->ReadFrame = 0;
stream->WriteFrame = 0;
stream->BufferedFrames = 0;
stream->ResamplePositionQ32 = 0;
stream->ConsecutiveUnderrunPeriods = 0;
stream->DiscontinuityGeneration++;
KiAudioRefreshStreamEventLocked(stream);
KiAudioReleaseMutex(stream->Lock);
KeSignalEvent(stream->Endpoint->WorkEvent);
NtlAtomicIncrement64(&gAudioCoreStats.Stops);
return NTLSTATUS_SUCCESS;
}
// Resets one live stream and invalidates presentation accounting from earlier periods.
KSTATUS KeAudioResetStream(KE_AUDIO_STREAM* stream) {
if (!stream) return NTLSTATUS_INVALID_PARAMETER;
KiAudioAcquireMutex(stream->Lock, "KeAudioResetStream");
if (stream->State == KeAudioStreamFailed || stream->State == KeAudioStreamClosed) {
KSTATUS status = stream->FailureStatus ? stream->FailureStatus : NTLSTATUS_OBJECT_DEAD;
KiAudioReleaseMutex(stream->Lock);
return status;
}
stream->BufferGeneration++;
KiAudioSetStreamStateLocked(stream, KeAudioStreamStopped);
stream->ReadFrame = 0;
stream->WriteFrame = 0;
stream->BufferedFrames = 0;
stream->ResamplePositionQ32 = 0;
stream->FramesSubmitted = 0;
stream->FramesConsumed = 0;
stream->FramesPresented = 0;
stream->FramesDiscarded = 0;
stream->OutputFramesMixed = 0;
stream->SilenceFramesInserted = 0;
stream->Underruns = 0;
stream->ConsecutiveUnderrunPeriods = 0;
stream->DiscontinuityGeneration++;
memset(stream->Ring,
0,
(size_t)KE_AUDIO_STREAM_RING_FRAMES * stream->FrameBytes);
KiAudioRefreshStreamEventLocked(stream);
KiAudioReleaseMutex(stream->Lock);
KeSignalEvent(stream->Endpoint->WorkEvent);
NtlAtomicIncrement64(&gAudioCoreStats.Resets);
return NTLSTATUS_SUCCESS;
}
// Updates per-stream volume, linear balance pan, and mute targets.
KSTATUS KeAudioSetStreamVolume(KE_AUDIO_STREAM* stream,
float volume,
float pan,
bool muted) {
if (!stream || !KiAudioFloatIsFinite(volume) || !KiAudioFloatIsFinite(pan) ||
volume < 0.0f || volume > 1.0f || pan < -1.0f || pan > 1.0f)
return NTLSTATUS_INVALID_PARAMETER;
KiAudioAcquireMutex(stream->Lock, "KeAudioSetStreamVolume");
if (stream->State == KeAudioStreamFailed || stream->State == KeAudioStreamClosed) {
KSTATUS status = stream->FailureStatus ? stream->FailureStatus : NTLSTATUS_OBJECT_DEAD;
KiAudioReleaseMutex(stream->Lock);
return status;
}
stream->Volume = volume;
stream->Pan = pan;
stream->Muted = muted ? 1u : 0u;
if (stream->State == KeAudioStreamCreated || stream->State == KeAudioStreamStopped) {
KiAudioComputeStreamTargets(stream->Volume,
stream->Pan,
stream->Muted,
&stream->CurrentLeftGain,
&stream->CurrentRightGain);
stream->TargetLeftGain = stream->CurrentLeftGain;
stream->TargetRightGain = stream->CurrentRightGain;
stream->GainRampRemaining = 0;
stream->LeftGainStep = 0.0f;
stream->RightGainStep = 0.0f;
} else {
KiAudioBeginStreamGainRampLocked(stream);
}
KiAudioReleaseMutex(stream->Lock);
KeSignalEvent(stream->Endpoint->WorkEvent);
return NTLSTATUS_SUCCESS;
}
// Updates endpoint master software gain and separate logical mute state.
KSTATUS KeAudioSetMasterVolume(uint32_t endpointId, float volume, bool muted) {
if (!KiAudioFloatIsFinite(volume) || volume < 0.0f || volume > 1.0f)
return NTLSTATUS_INVALID_PARAMETER;
IO_AUDIO_ENDPOINT* endpoint = KiAudioReferenceEndpointById(endpointId);
if (!endpoint)
return NTLSTATUS_NOT_FOUND;
DbgPrint("audiodiag: master endpoint=%u waiting control lock\n", endpointId);
KiAudioAcquireMutex(endpoint->ControlLock, "KeAudioSetMasterVolume");
DbgPrint("audiodiag: master endpoint=%u acquired control lock\n", endpointId);
float softwareGain = muted ? 0.0f
: KiAudioSoftwareGainForVolume(endpoint, volume);
uintptr_t registryFlags = KeAcquireSpinlock(&gAudioRegistryLock);
if (endpoint->State != KeAudioEndpointReady) {
KSTATUS failure = endpoint->FailureStatus;
KeReleaseSpinlock(&gAudioRegistryLock, registryFlags);
KiAudioReleaseMutex(endpoint->ControlLock);
ObDereferenceObject(&endpoint->Header);
return KFAILED(failure) ? failure : NTLSTATUS_OBJECT_DEAD;
}
uintptr_t flags = KeAcquireSpinlock(&endpoint->NotifyLock);
endpoint->MasterRequestedVolume = volume;
endpoint->MasterTargetGain = softwareGain;
if (endpoint->HardwareRunning) {
endpoint->MasterGainStep =
(endpoint->MasterTargetGain - endpoint->MasterCurrentGain) /
(float)KE_AUDIO_GAIN_RAMP_FRAMES;
endpoint->MasterRampRemaining = KE_AUDIO_GAIN_RAMP_FRAMES;
} else {
endpoint->MasterCurrentGain = endpoint->MasterTargetGain;
endpoint->MasterGainStep = 0.0f;
endpoint->MasterRampRemaining = 0;
}
endpoint->MasterGainGeneration++;
endpoint->MasterMuted = muted ? 1u : 0u;
KeReleaseSpinlock(&endpoint->NotifyLock, flags);
KeReleaseSpinlock(&gAudioRegistryLock, registryFlags);
KiAudioReleaseMutex(endpoint->ControlLock);
DbgPrint("audiodiag: master endpoint=%u update committed\n", endpointId);
KeSignalEvent(endpoint->WorkEvent);
ObDereferenceObject(&endpoint->Header);
return NTLSTATUS_SUCCESS;
}
// Copies one stream snapshot under its priority-inheriting mutex.
KSTATUS KeAudioQueryStream(KE_AUDIO_STREAM* stream, KE_AUDIO_STREAM_INFO* infoOut) {
if (!stream || !infoOut)
return NTLSTATUS_INVALID_PARAMETER;
KiAudioAcquireMutex(stream->Lock, "KeAudioQueryStream");
memset(infoOut, 0, sizeof(*infoOut));
infoOut->State = stream->State;
infoOut->Format = stream->Format;
infoOut->CapacityFrames = KE_AUDIO_STREAM_RING_FRAMES;
infoOut->BufferedFrames = stream->BufferedFrames;
infoOut->WritableFrames = KE_AUDIO_STREAM_RING_FRAMES - stream->BufferedFrames;
infoOut->FramesSubmitted = stream->FramesSubmitted;
infoOut->FramesConsumed = stream->FramesConsumed;
infoOut->FramesPresented = stream->FramesPresented;
infoOut->FramesDiscarded = stream->FramesDiscarded;
infoOut->OutputFramesMixed = stream->OutputFramesMixed;
infoOut->SilenceFramesInserted = stream->SilenceFramesInserted;
infoOut->Underruns = stream->Underruns;
infoOut->DrainedGeneration = stream->DrainedGeneration;
infoOut->DiscontinuityGeneration = stream->DiscontinuityGeneration;
infoOut->FailureStatus = stream->FailureStatus;
infoOut->Volume = stream->Volume;
infoOut->Pan = stream->Pan;
infoOut->Muted = stream->Muted;
KiAudioReleaseMutex(stream->Lock);
return NTLSTATUS_SUCCESS;
}