Skip to main content

VoxLock

The best performing voice isolator there is. VoxLock is an open-source voice isolation model: it keeps the caller's voice and removes the other voices in the room before speech-to-text hears them. Made for voice agents and phone calls: one input, the call audio, and it picks out the caller by itself. It streams on a CPU with 24 ms of algorithmic latency.

Write-up · Model on Hugging Face · Try it in your browser

PyPI Hugging Face Krisp benchmark WER 4.95% License: Apache-2.0 LiveKit Agents plugin Pipecat filter Tests

https://github.com/user-attachments/assets/d32cf251-b461-4a91-93b1-21a8bd6ad840

Results

VoxLock has the lowest word error rate and leaks the fewest background words on the Krisp Voice Isolation Benchmark
Model Access Word error rate % Background words leaked / min
VoxLock open 4.95 0.11
Krisp VI 2.5 Default paid 5.30 1.52
Krisp VI 2.5 Balanced paid 5.32 2.02
Krisp VI 2.5 HD paid 5.79 2.74
Krisp VI 2.5 Lite paid 6.21 4.52
ai-coustics Voice Focus 2.2 L paid 6.84 0.66
ai-coustics Voice Focus 2.2 S paid 6.91 1.00
Sanas Voice Isolation SE 1.0 paid 9.38 13.76
REAL-TSE causal (VoxLock's starting checkpoint) open 9.81 13.73
Hush open 12.09 19.58
DeepFilterNet3 open 13.30 22.77
TIGER open 14.72 37.24
No processing 23.21 66.48
SAM-Audio small (Meta) open 33.77 21.15
ElevenLabs Voice Isolator** paid 43.79** 144.6**

265 real recordings from the Krisp Voice Isolation Benchmark, transcribed with Parakeet TDT 0.6B v2 and scored with the benchmark's published method. Background words leaked counts words transcribed while only the background was talking, per minute of that audio.

** ElevenLabs Voice Isolator, run on the 7 of these recordings with the most background speech, not all 265. On the same 7, no processing scores 45.82% and 154.3 leaked words per minute, and VoxLock 2.98% and 0.0.

On real AMI meetings not used in training, with the headset wearer as the caller:

Model Access Word error rate % Background words leaked / min
VoxLock open 10.12 2.12
ai-coustics Voice Focus 2.2 S paid 11.85 1.06
ai-coustics Voice Focus 2.2 L paid 12.13 1.72
Sanas Voice Isolation SE 1.0 paid 13.30 5.30
TIGER open 22.84 36.85
DeepFilterNet3 open 26.78 38.84
Hush open 33.00 68.93
No processing 33.03 78.34

Caller words missed, deletions, insertions and 95% intervals: bench/results.

Use

pip install voxlock
voxlock call.wav -o caller.wav

Any sample rate, mono or stereo. The model downloads from Hugging Face on first use: 74 MB, the 8-bit version (see Quantized models). voxlock download fetches it ahead of time, e.g. while building a Docker image.

import voxlock

vl = voxlock.VoxLock()                     # precision="fp32" for the benchmarked model
call = voxlock.load_audio("call.wav")      # 16 kHz mono float32
caller = vl.isolate(call)                  # same length as the input

VoxLock keeps the voice in a 3-second sample of the caller. isolate takes the loudest 3 seconds in the first 15 seconds of the call, which is how it was benchmarked. Pass sample= to choose it yourself: at least 3 seconds, and a longer sample gives its loudest 3 seconds in the first 30.

Streaming

Push the call in as it arrives, as 16 kHz float32 chunks of any size. Each push returns the isolated audio that is ready, about 24 ms behind the input.

vl = voxlock.VoxLock(threads=1)
stream = vl.live()                         # finds the caller by itself
for chunk in call_audio:                   # e.g. 40 ms = 640 samples
    send(stream.push(chunk))
send(stream.flush())                       # at the end of the call

live() passes the audio through unchanged until it has heard 3 seconds of speech, takes that speech as the voice sample and isolates from then on. On our test calls it switched over a median of 3.6 seconds in. At the switch it first runs the model over the call so far (up to 30 seconds), so the model never starts cold in the middle of a sentence; that is a one-time 2 to 5 seconds of work on one CPU thread. On the 265 Krisp recordings live() scores 5.15% word error rate with the fp32 model, against 4.95% on the whole file. The Pipecat and LiveKit filters do that work on their own thread, so the call's audio never waits for it: it goes out unprocessed for the few seconds the model takes to catch up.

If you already have a sample, from earlier in the call or from a previous call, isolation starts with the first chunk:

stream = vl.stream(sample)                 # about 3 s of the caller, 16 kHz mono

Streaming gives the same output as isolate with the same sample, to float rounding with fp32; int8 differs slightly, since its 8-bit scaling depends on the chunk size. On one CPU thread it keeps up with chunks of 40 ms or more, and with 20 ms chunks on two threads (see Performance). Larger chunks use less CPU and add their length to the delay.

Pipecat

pip install "voxlock[pipecat]"
from pipecat.transports.base_transport import TransportParams
from voxlock.pipecat import VoxLockFilter

params = TransportParams(audio_in_enabled=True, audio_in_filter=VoxLockFilter())

The filter cleans the caller's audio before VAD and speech-to-text, at any transport sample rate (Twilio's 8 kHz included). Like live(), it passes audio through until it has heard 3 seconds of the caller; pass voice_sample= (at least 3 seconds) to isolate from the start. Defaults: chunk_ms=80, threads=1, precision="int8", which takes 61% of one CPU core, so a single vCPU such as Pipecat Cloud's agent-2x runs it. Half a vCPU (agent-1x) is too little.

The model runs on its own thread, so the transport never waits for it. Each frame comes back whole, a fixed delay behind: two chunks plus 24 ms, 184 ms at 16 kHz (resampling adds 39 ms at 48 kHz and 120 ms at Twilio's 8 kHz). chunk_ms=40 makes it 104 ms for 77% of a core. A full voice agent: examples/pipecat_bot.py. Tested with Pipecat 1.15.0.

LiveKit

pip install "voxlock[livekit]"            # or: pip install livekit-plugins-voxlock
from livekit.agents import room_io
from voxlock.livekit import VoxLockFilter   # or: from livekit.plugins import voxlock; voxlock.VoxLockFilter()

await session.start(
    agent,
    room=ctx.room,
    room_options=room_io.RoomOptions(
        audio_input=room_io.AudioInputOptions(sample_rate=16000, noise_cancellation=VoxLockFilter()),
    ),
)

VoxLockFilter is a LiveKit frame processor, one per session. It cleans the caller's audio before VAD, turn detection and speech-to-text. Like live(), it passes audio through until it has heard 3 seconds of the caller (voice_sample= isolates from the start), and the caller keeps their voice lock when their track changes, e.g. a microphone toggle; a different participant starts over.

The model runs on its own thread: the agent's event loop spends about 0.3 ms per frame on it, and the call takes 67% to 69% of one CPU core with LiveKit's 50 ms frames (60 s of Krisp recordings on the Xeon in Performance, bench/livekit_cpu.py). Each frame comes back whole, a fixed delay behind: two frames plus 24 ms, 124 ms in a 16 kHz session (a 24 or 48 kHz session adds up to 39 ms of resampling). Audio the caller recorded before the agent joined (pre-connect audio) passes through unchanged.

Load the model once per worker process with preload() in your prewarm function, and fetch it at build time with python agent.py download-files or voxlock download. In console mode LiveKit reads the microphone without a room, so the filter does not run there: test it in a room. A full voice agent: examples/livekit_agent.py. Tested with livekit-agents 1.8.10, end to end through a LiveKit server: the other voice 45 to 49 dB down in what the agent hears.

sherpa-onnx

VoxLock is built into the speech denoisers of sherpa-onnx from version 2.0.1, in C++ with no Python, for whole files and for streams. Download the models with sherpa-onnx's meta data from this release, then:

sherpa-onnx-offline-denoiser \
  --speech-denoiser-voxlock-enroll-model=voxlock_enroll.onnx \
  --speech-denoiser-voxlock-chunk-model=voxlock_chunk_int8.onnx \
  --input-wav=call.wav --output-wav=caller.wav

sherpa-onnx-online-denoiser takes the same models and streams. Both take the voice sample from the audio as this package does; --speech-denoiser-voxlock-voice-sample=caller.wav gives it instead.

PyTorch

git clone https://github.com/arbazsiddiqui/voxlock && cd voxlock
pip install -r requirements.txt
huggingface-cli download arbazsiddiqui/voxlock model.safetensors config.yaml --local-dir ckpt
python isolate.py --ckpt ckpt --in call.wav --out caller.wav

Performance

One live call through VoxLock.stream with the default int8 model on one CPU thread: onnxruntime 1.31.1 on a Google Cloud g2-standard-8 (Intel Xeon @ 2.20 GHz), 60 seconds of AMI meeting audio.

Chunk CPU per call Real-time factor
20 ms 110% of a core 1.10
40 ms 77% of a core 0.77
80 ms 61% of a core 0.61

Below 1 keeps up with the call. 20 ms chunks keep up on two threads (0.86). The model has 27M parameters and 24 ms of algorithmic latency, plus the chunk. On the same machine's NVIDIA L4 GPU (fp32, CUDA), a whole recording runs 40 times faster than real time.

Quantized models

The ONNX model comes in four precisions, all in the Hugging Face repository. Choose with VoxLock(precision=...), voxlock --precision ... or VoxLockFilter(precision=...).

Precision Quality Files Download Word error rate % Background words leaked / min CPU per call, 20 ms chunks
int8 (default) 99.8% enroll.onnx, chunk_int8.onnx 74 MB 5.11 0.12 110%
int8-full 99.1% enroll.onnx, chunk_int8_full.onnx 71 MB 5.85 0.04 110%
fp16 100%* enroll_fp16.onnx, chunk_fp16.onnx 47 MB 4.95* 0.11* 163%
fp32 100% enroll.onnx, chunk.onnx 94 MB 4.95 0.11 165%

Quality: the share of fp32's correctly transcribed words each precision keeps, on the 265 Krisp recordings. CPU per call: one live call on one thread of the Xeon in Performance.

* fp16 is the float32 model with its weights rounded to float16. Its output matches float32 to over 70 dB, so it carries the float32 scores. The browser demo uses it.

enroll runs once on the voice sample, chunk runs on each chunk of the call and carries the LSTM state.

Training

A 27M causal band-split RNN target-speaker extractor, fine-tuned from the REAL-TSE challenge baseline on a single NVIDIA L4:

REAL-TSE tfmap_context_causal_100
  -> simulated calls        4,000 steps   LibriSpeech callers + background talkers in simulated rooms
  -> real AMI crosstalk     4,000 steps   headset recordings from 40 meetings
  -> long calls (30 s)        800 steps   callers who talk throughout
  -> rooms and distances     400 steps   callers near or far from the mic, background voices dry or in the room, room noise

Repository

Path
voxlock/ the pip install voxlock package: whole files, streaming, command line, Pipecat and LiveKit filters
integrations/ livekit-plugins-voxlock, the LiveKit plugin package
examples/ Pipecat and LiveKit voice agents with VoxLock
tests/ unit tests, and the Pipecat and LiveKit filters run as each framework runs them
isolate.py PyTorch inference
export_onnx.py ONNX export
demo/ the browser demo (python demo/serve.py)
bench/ benchmark harness, vendor runners, per-file results
train/ training scripts and data preparation

Acknowledgements

Built on the REAL-TSE challenge baseline and WeSep by Shuai Wang, Ke Zhang and co-authors, with WeSpeaker's ECAPA model. Trained on LibriSpeech and the AMI Meeting Corpus.

License

Apache-2.0.

Metadata

Release files for voxlock 1.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for voxlock 1.3.0
File Size Uploaded Provenance Actions
voxlock-1.3.0.tar.gz 33.2 kB Without provenance Details for voxlock-1.3.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for voxlock 1.3.0
File Interpreter ABI Platform Actions
voxlock-1.3.0-py3-none-any.whl Python 3 none any Details for voxlock-1.3.0-py3-none-any.whl

Total release size: 59.0 kB

Release files / voxlock-1.3.0.tar.gz

Download URL voxlock-1.3.0.tar.gz
Size 33.2 kB
Tags Source
SHA-256 checksum
How to use checksums
5a6f6bce38ff53d309855df564b18a56ab5b4fc1918c6ec8af5cbd270d3f1bc8
BLAKE2b-256 checksum
How to use checksums
627f567ec9efcefa3c983005a7e3117231817daa4063d82b313eb1d7c0ce6392
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.17

Release files / voxlock-1.3.0-py3-none-any.whl

Download URL voxlock-1.3.0-py3-none-any.whl
Size 25.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
15aedfb0edc03c0b4dc57adc5fdb3932027a84673886767b6d72840128b39f75
BLAKE2b-256 checksum
How to use checksums
2ecdf0d8ff17c7a7c1472c57a26d4a32b3a14e810a40e4db7bb0153b134eb958
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.17

Security for voxlock 1.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page