MCPcopy Index your code
hub / github.com/Litttlefish/wasapi_relink

github.com/Litttlefish/wasapi_relink @0.14.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release 0.14.0 ↗ · + Follow
761 symbols 1,133 edges 13 files 2 documented · 0%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

WASAPI Relink

English | 中文

wasapi_relink is a hook library that modifies the behavior of WASAPI (Windows Audio Session API) Shared streams, "grafting" them onto modern, low-latency interfaces to drastically minimize audio latency.

Purpose

Many applications and games use WASAPI's Shared mode for audio playback. While it offers good compatibility, this mode only allows for a minimum buffer size of 10ms, preventing further improvement in latency.

wasapi_relink intercepts the application's audio requests and forces them to use much smaller buffers by leveraging the IAudioClient3 interface, achieving a low-latency experience similar to Exclusive mode without sacrificing Shared mode's convenience.

Reminder: Your Driver is Everything

This tool is 100% dependent on your audio driver. The target_period_hus setting in config is a request, not a command.

Your audio driver reports a supported buffer range (i.e. GetSharedModeEnginePeriod), and wasapi_relink will clamp the request to fit within that range, this problem is most obvious when using Realtek onboard soundcards, see below.

The Realtek Problem

Most stock Realtek audio drivers (the ones from the manufacturer) lock the buffer range to 10ms exact.

If your driver does this, wasapi_relink will have no effect, as it cannot request a buffer smaller than 10ms.

The Solution

You must replace the Realtek driver with Windows generic "High Definition Audio Device" driver. This standard Microsoft driver typically allows a 2ms-10ms range, enabling wasapi_relink to successfully request a 2ms buffer.

Or, use a professional soundcard or external audio interface that supports low-latency.

P.S.: Currently, there don't seem to be many sound cards that are compatible with Low Latency Shared mode. The best option right now is probably to use the USB dongle that comes with Windows' built-in USB 2.0 driver.

How It Works: The "Grafting"

wasapi_relink operates in four distinct modes to handle different types of applications.

Normal Mode (For modern apps)

Target: Applications and games that use event‑driven (callback) WASAPI audio and behave well.

Method:

  1. Hooks IAudioClient::Initialize.

  2. Ignores the application's buffer request and instead calls IAudioClient3::InitializeSharedAudioStream to initialize a low-latency stream.

  3. This provides the application with a much smaller buffer (e.g., 2ms), dramatically reducing latency.

  4. You can specify a smaller reported buffer length to further reduce latency.

Compat Mode (For compatibility with legacy/poorly coded apps)

Target: Programs that break if you only shrink their buffer, and Ringbuf mode doesn't work well with them.

Method:

  1. Hooks IMMDevice::Activate and creates two IAudioClient3 instances.

  2. Instance A (App‑facing): Is initialized in a regular Shared mode that the program expects.

  3. Instance B (Low‑latency): Is initialized with IAudioClient3::InitializeSharedAudioStreamusing the tool’s low‑latency period. This client may not be used by the app at all; its main job is to influence the Windows audio engine.

  4. Engine‑side effect: According to Microsoft’s low‑latency documentation, when any application on an endpoint requests small buffers via IAudioClient3, the audio engine switches to that small period for all shared‑mode streams on the same endpoint. By keeping the low‑latency client alive, wasapi_relink forces the engine to run at the small period even though the “main” app client is using a larger buffer.

  5. Prefill Deception: Before IAudioClient::Start, many apps will write “silent” prefill data. wasapi_relink intercepts this via the wrapped IAudioRenderClient, then writes a smaller prefill (sized to the real engine period) into the hardware client, non-silent data won't be affected.'

Result: The app continues to work as if it had its original large buffer.

  • Worst-case: Latency is doubled comparing to normal mode (the sum of the pre-filled buffer and the device buffer).
  • Best-case: Latency is close to the normal mode.

Ringbuf Mode (Most powerful)

Target: Programs that use polling or fixed‑size mixing and/or have problematic timing behavior.

Method:

  1. Intercepts Initialize / InitializeSharedAudioStream and always calls InitializeSharedAudioStream with the tool’s low‑latency period.

  2. Creates an internal ring buffer whose capacity is exposed to the app as the reported IAudioClient::GetBufferSize, and IAudioClient::GetCurrentPadding reports how much data is in the ring buffer, not the real engine buffer. The app feels like it’s writing into a large, traditional buffer, while the engine actually runs at the small period.

  3. Injection and inverse modes

  4. If the application doesn't use event-driven flag, wasapi_relink injects AUDCLNT_STREAMFLAGS_EVENTCALLBACK and runs its own consumer thread, which is based on Real-Time Work Queue API, that waits on real engine event and move the data from the ring buffer to IAudioRenderClient on the real client, operating according to the configured reported buffer length.

  5. If the application requested event-driven flag, wasapi_relink takes over the app’s event handle and decides when to call SetEvent to wake the app’s callback. Internally it still uses its own thread to consume from the ring buffer. In effect, the chain becomes:
hardware engine → ringbuf consumer thread → app callback

All silent data before Start() will be discarded.

Result: The app just sees a large, friendly WASAPI client, fully isolated from the engine’s real timing and buffer size, which works even with “broken” timing patterns (fixed‑size blocks, sleep‑based loops, etc.).

Bypass Mode (On demand)

Target: Streams that don't need this tool, such as capture stream, etc..

Method: wasapi_relink no longer wraps client. Instead, it directly returns IAudioClient3.

How to Use (Injection)

This library does not include its own DLL injector. You must use an external tool or stub generator.

Recommended Method: Special K

It is recommended to load wasapi_relink.dll as a Special K "plug-in" (lazy mode).

Built-in Compatibility: wasapi_relink actively detects and skips hooking Special K's own audio requests, ensuring SK's sound related features are not affected.

Use as a Developer Library (Advanced Usage)

An additional purpose of this tool is to act as a "auxiliary" library if the audio library you are using does not support IAudioClient3 low-latency initialization.

No complex injection tools are needed. Simply, early in your program’s startup, explicitly load this DLL with LoadLibrary("wasapi_relink.dll"). Its hooking behavior is automatically performed when the DLL is loaded, thereby "transparently" providing low-latency features to your existing audio library.

Reminder on using stub generator

To facilitate use with stub generators, wasapi_relink.dll additionally exports a blank C function named proxy.

This function does nothing on its own; it exists only as a definitive "entry point".

DO NOT use ole32.dll when generating DLL stubs. Doing so can cause infinite recursion during LoadLibrary/COM activation and lead to unstable or hard-to-debug behavior (e.g., repeated loader calls, crashes, or the target process hanging).

Configuration (redirect_config.toml)

The configuration file redirect_config.toml must be placed in the same directory(or working directory) as the DLL. If an entry is not specified, default value will be used.

# Path for the log file. "" (empty string) defaults to the working directory.
log_path = ""
# Log level: Trace, Debug, Info, Warn, Error, Never
log_level = "Info"
# Log only to stdout (true) or to both stdout and file (false).
only_log_stdout = false

[capture]
# (General) Target device period in 0.1ms units (u16).
# The tool will calculate the closest period *not exceeding* this duration while clamped within driver range.
# e.g., 20 = 2.0ms.
target_period_hus = 20
# (General) Target buffer length (in audio frames) reported to the corresponding samplerate.
# The number will be automatically rounded UP to align with the driver's fundamental period for optimal performance while clamped within range.
target_buffer_len.48000 = 256
# (General) Enable raw process for this stream (bool).
raw = true
# (General) Report spoofed period before client initialize(if any).
force_period = true

[playback]

# Tool mode, available mode: Normal, Compat, Ringbuf, Bypass
mode = "Ringbuf"

# (Ringbuf mode exclusive, Optional) Assign a ring buffer length (in audio frames) to the corresponding samplerate.
# The number will be automatically rounded UP to align with the driver's fundamental period for optimal performance.
ring_buffer_len.48000 = 340 # about 7ms buffer

# (Compat mode exclusive, Optional) Assign a shared stream buffer duration (in 100-nanosecond units) to the corresponding samplerate.
# The number will be directly used as the inner shared buffer, and will be clamped by Windows if set too low.
compat_buffer_dur_hns.48000 = 0 # this will clamp to minimum allowed value
compat_buffer_dur_hns.96000 = 238350


Config Details

  • log_path (string): Where to save the log file. Non-directory value means current working directory.

  • log_level (string): Trace, Debug, Info, Warn, Error, Never. Default is Info.
    See performance warning below.

  • only_log_stdout (bool): Controls logging targets.

  • true: Logs only to the standard output (stdout). No log file will be created.
  • false (Default): Logs to both the standard output (stdout) and the file specified by log_path.

    • This option is particularly useful for developers who want to monitor logs in real-time in a terminal or for applications running in containerized environments (like Docker) where capturing stdout is the standard practice.
  • [playback]/[capture]: Separate configs for output and input.

  • mode (string): Normal, Compat, Ringbuf, Bypass. Default is Normal.

  • target_period_hus (u32): Target period for all created low latency shared stream in units of 0.1 milliseconds. The tool will default to the driver's minimum if this is set too low or not specified. You should generally not change this from the default value unless you experience audio pops.

  • target_buffer_len.<samplerate> (u32): Target reported buffer length for the size of low latency shared stream in audio frames (not samples). For example, 256 means 256 frames (512 samples in 2-channel audio). The tool will default to the full buffer if not specified.

    • Note: Valid range is [device_period, full_buffer_len], other value will be clamped. Will automatically round this value UP to the nearest multiple of the driver’s fundamental period.
  • raw (bool): Indicates this stream to use raw processing, which bypasses most APO. Does nothing when mode is Bypass.

  • force_period (bool): Will report spoofed period in GetDevicePeriod calls before Initialize. Does nothing when mode is Bypass.

  • ring_buffer_len.<samplerate> (u32): Target buffer length for the ring buffer in audio frames. It's recommended to set a proper value in Ringbuf mode.

    • Note: The tool will automatically round this value UP to the nearest multiple of fundamental period to ensure smooth streaming and prevent micro-glitches.
  • compat_buffer_dur_hns.<samplerate> (i64): Target buffer size for shared stream in units of 100 nanoseconds. This controls the size of the shared buffer the program actually sees in Compat mode. The tool/Windows will default to the driver’s minimum if this is set too low or not specified. This can help fix audio pops that occur after changing the audio sample rate in Compat mode.

Troubleshooting

Use this guide to diagnose and fix common audio issues.

Audio is "Sliced" or in "Slow-Motion"

Phenomenon: Sound is heavily distorted, stretched, or sounds like it's being "sliced" and played back slowly.

Cause: This is the classic sign of a Poll-driven application running in small buffer. The app's polling logic is fighting the event-driven buffer. This is commonly seen in Unity games.

Solution: Try Compat or Ringbuf mode for the [playback] section.

No sound or crashes with a log

Phenomenon: Completely silent, or simply crashes with wasapi_relink log provided.

Cause: This is mostly because of a Fixed-size application running in small buffer. The app's mixing logic is waiting indefinitely or encountering math errors on the buffer. This is usually seen in Rhythm games.

Solution: Try Ringbuf mode for the [playback] section.

Good Audio with Occasional "Pops" or "Crackles"

Phenomenon: Audio playback is at the correct speed and pitch, but you hear intermittent pops, clicks, or small tearing sounds.

Cause: The buffer is too small for your system/software. Your CPU or the application cannot "feed" the audio driver new data fast enough, resulting in a buffer underrun.

Solutions (Try either one):

  1. Increase Buffer: Slightly increase target_period_hus. If your driver's minimum is 2ms (e.g., 20), try 30 (3ms) or 40 (4ms) until the pops disappear.

  2. Try Compat or Ringbuf Mode: This effectively adds a buffer layer for the application (i.e., the standard buffer from normal Shared mode). In combination with the changes made by those modes, this can potentially resolve the issue.

Audio Pop after changing samplerate in Windows settings

Phenomenon: Audio playback is normal at samplerate A (e.g. 48000Hz), but pops at samplerate B (e.g

Core symbols most depended-on inside this repo

get
called by 37
src/lib.rs
get
called by 31
archive/lib (3).rs
get
called by 29
archive/lib copy 7.rs
get_info
called by 15
archive/lib copy 7.rs
get
called by 13
archive/lib copy 4.rs
get
called by 11
archive/lib_tomerge.rs
param
called by 10
src/lib.rs
get
called by 10
archive/lib copy 6.rs

Shape

Method 514
Class 124
Function 86
Enum 37

Languages

Rust100%

Modules by API surface

archive/lib copy 2.rs71 symbols
archive/lib (3).rs71 symbols
src/lib.rs70 symbols
archive/lib copy 7.rs69 symbols
archive/lib_tomerge.rs68 symbols
archive/lib copy 6.rs68 symbols
archive/lib copy 5.rs67 symbols
archive/lib copy 3.rs67 symbols
archive/lib.rs57 symbols
archive/lib copy.rs56 symbols
archive/lib copy 4.rs56 symbols
archive/lib (2).rs38 symbols

For agents

$ claude mcp add wasapi_relink \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact