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.
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.
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.
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.
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.
wasapi_relink operates in four distinct modes to handle different types of applications.
Target: Applications and games that use event‑driven (callback) WASAPI audio and behave well.
Method:
Hooks IAudioClient::Initialize.
Ignores the application's buffer request and instead calls IAudioClient3::InitializeSharedAudioStream to initialize a low-latency stream.
This provides the application with a much smaller buffer (e.g., 2ms), dramatically reducing latency.
You can specify a smaller reported buffer length to further reduce latency.
Target: Programs that break if you only shrink their buffer, and Ringbuf mode doesn't work well with them.
Method:
Hooks IMMDevice::Activate and creates two IAudioClient3 instances.
Instance A (App‑facing): Is initialized in a regular Shared mode that the program expects.
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.
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.
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.
Target: Programs that use polling or fixed‑size mixing and/or have problematic timing behavior.
Method:
Intercepts Initialize / InitializeSharedAudioStream and always calls InitializeSharedAudioStream with the tool’s low‑latency period.
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.
Injection and inverse modes
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.
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.).
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.
This library does not include its own DLL injector. You must use an external tool or stub generator.
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.
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.
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).
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
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.
[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.
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.
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.
Use this guide to diagnose and fix common audio issues.
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.
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.
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):
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.
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.
Phenomenon: Audio playback is normal at samplerate A (e.g. 48000Hz), but pops at samplerate B (e.g
$ claude mcp add wasapi_relink \
-- python -m otcore.mcp_server <graph>