livekit-plugins-denoise 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- livekit_plugins_denoise-0.1.0/.gitignore +26 -0
- livekit_plugins_denoise-0.1.0/LICENSE +21 -0
- livekit_plugins_denoise-0.1.0/NOTICE +11 -0
- livekit_plugins_denoise-0.1.0/PKG-INFO +136 -0
- livekit_plugins_denoise-0.1.0/README.md +103 -0
- livekit_plugins_denoise-0.1.0/docs/technical-notes.md +39 -0
- livekit_plugins_denoise-0.1.0/examples/sip_agent.py +120 -0
- livekit_plugins_denoise-0.1.0/livekit/plugins/telephony_denoise/__init__.py +32 -0
- livekit_plugins_denoise-0.1.0/livekit/plugins/telephony_denoise/_buffers.py +53 -0
- livekit_plugins_denoise-0.1.0/livekit/plugins/telephony_denoise/_resample.py +257 -0
- livekit_plugins_denoise-0.1.0/livekit/plugins/telephony_denoise/echo_reference.py +159 -0
- livekit_plugins_denoise-0.1.0/livekit/plugins/telephony_denoise/log.py +3 -0
- livekit_plugins_denoise-0.1.0/livekit/plugins/telephony_denoise/neural.py +222 -0
- livekit_plugins_denoise-0.1.0/livekit/plugins/telephony_denoise/processor.py +455 -0
- livekit_plugins_denoise-0.1.0/livekit/plugins/telephony_denoise/py.typed +0 -0
- livekit_plugins_denoise-0.1.0/livekit/plugins/telephony_denoise/version.py +1 -0
- livekit_plugins_denoise-0.1.0/pyproject.toml +71 -0
- livekit_plugins_denoise-0.1.0/tests/test_buffers_and_resample.py +34 -0
- livekit_plugins_denoise-0.1.0/tests/test_options_and_processor.py +57 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
env/
|
|
13
|
+
|
|
14
|
+
# Test / tooling caches
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
.mypy_cache/
|
|
17
|
+
.ruff_cache/
|
|
18
|
+
|
|
19
|
+
# Generated denoiser output
|
|
20
|
+
*.denoised.wav
|
|
21
|
+
|
|
22
|
+
# Editor / OS
|
|
23
|
+
.vscode/
|
|
24
|
+
.idea/
|
|
25
|
+
.DS_Store
|
|
26
|
+
Thumbs.db
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 BotifyNow
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
livekit-plugins-denoise
|
|
2
|
+
Copyright 2026 BotifyNow
|
|
3
|
+
|
|
4
|
+
This package depends on deepfilter-stream, which bundles DeepFilterNet3 model
|
|
5
|
+
weights. deepfilter-stream and DeepFilterNet are dual-licensed under MIT or
|
|
6
|
+
Apache-2.0 at the recipient's option.
|
|
7
|
+
|
|
8
|
+
DeepFilterNet attribution:
|
|
9
|
+
H. Schröter, T. Rosenkranz, A. N. Escalante-B., and A. Maier,
|
|
10
|
+
"DeepFilterNet: A Low Complexity Speech Enhancement Framework for Full-Band
|
|
11
|
+
Audio based on Deep Filtering", IWAENC 2022.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: livekit-plugins-denoise
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Self-hosted noise and echo suppression for LiveKit SIP telephony
|
|
5
|
+
Project-URL: Source, https://github.com/jaffarjawed/livekit-plugins-denoise
|
|
6
|
+
Project-URL: Issues, https://github.com/jaffarjawed/livekit-plugins-denoise/issues
|
|
7
|
+
Author: BotifyNow
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
License-File: NOTICE
|
|
11
|
+
Keywords: audio,echo-cancellation,livekit,noise-cancellation,sip,telephony,webrtc
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Communications :: Telephony
|
|
21
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: deepfilter-stream<0.2,>=0.1.0
|
|
24
|
+
Requires-Dist: livekit-agents<2.0,>=1.6.0
|
|
25
|
+
Requires-Dist: livekit<2.0,>=1.1.0
|
|
26
|
+
Requires-Dist: numpy>=1.26
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-asyncio<2,>=1.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
31
|
+
Requires-Dist: twine>=6; extra == 'dev'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# livekit-plugins-denoise
|
|
35
|
+
|
|
36
|
+
Self-hosted noise suppression and acoustic echo cancellation for LiveKit SIP
|
|
37
|
+
agents.
|
|
38
|
+
|
|
39
|
+
- Removes background noise with DeepFilterNet3 or low-latency WebRTC noise
|
|
40
|
+
suppression.
|
|
41
|
+
- Removes the agent's voice when it returns through a caller's handset or
|
|
42
|
+
carrier path.
|
|
43
|
+
- Runs in your agent process: no per-minute denoising fee and no language
|
|
44
|
+
dependency.
|
|
45
|
+
|
|
46
|
+
This is an independent, community-maintained package and is not affiliated
|
|
47
|
+
with or endorsed by LiveKit, Inc.
|
|
48
|
+
|
|
49
|
+
## Why this exists
|
|
50
|
+
|
|
51
|
+
LiveKit's managed `BVCTelephony` noise-cancellation option is billed per minute.
|
|
52
|
+
This package is a self-hosted, MIT-licensed alternative for teams that want
|
|
53
|
+
noise suppression and echo cancellation in their own agent process without a
|
|
54
|
+
per-minute denoising charge. You still pay for your own compute, telephony, and
|
|
55
|
+
other services.
|
|
56
|
+
|
|
57
|
+
## Install
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
python -m pip install livekit-plugins-denoise
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
For local development:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
python -m pip install -e ".[dev]"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Enable noise suppression and echo cancellation
|
|
70
|
+
|
|
71
|
+
Create one `TelephonyDenoiser` for each call. `EchoReferenceTap` is required
|
|
72
|
+
for echo cancellation because it supplies the agent's outgoing audio as the
|
|
73
|
+
far-end reference.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from livekit.agents import room_io
|
|
77
|
+
from livekit.plugins import telephony_denoise
|
|
78
|
+
|
|
79
|
+
denoiser = telephony_denoise.TelephonyDenoiser(
|
|
80
|
+
telephony_denoise.DenoiseOptions(
|
|
81
|
+
echo_cancellation=True,
|
|
82
|
+
noise_suppression=True,
|
|
83
|
+
high_pass_filter=True,
|
|
84
|
+
auto_gain_control=True,
|
|
85
|
+
enhancer="deepfilter", # use "webrtc" for lower latency
|
|
86
|
+
stream_delay_ms=120,
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
await session.start(
|
|
91
|
+
agent=agent,
|
|
92
|
+
room=room,
|
|
93
|
+
room_options=room_io.RoomOptions(
|
|
94
|
+
audio_input=room_io.AudioInputOptions(
|
|
95
|
+
noise_cancellation=denoiser,
|
|
96
|
+
auto_gain_control=False, # do not stack AGC
|
|
97
|
+
),
|
|
98
|
+
),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Add this after session.start(); RoomIO replaces the output during startup.
|
|
102
|
+
session.output.audio = telephony_denoise.EchoReferenceTap(
|
|
103
|
+
denoiser, next_in_chain=session.output.audio
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
To load the neural model before the first call:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
def setup(proc):
|
|
111
|
+
telephony_denoise.prewarm()
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
See [examples/sip_agent.py](examples/sip_agent.py) for a complete SIP agent.
|
|
115
|
+
|
|
116
|
+
## Configuration
|
|
117
|
+
|
|
118
|
+
`DenoiseOptions` lets you control these features independently:
|
|
119
|
+
|
|
120
|
+
| Option | Default | Purpose |
|
|
121
|
+
| --- | --- | --- |
|
|
122
|
+
| `echo_cancellation` | `True` | Cancels the agent's returned audio; requires `EchoReferenceTap`. |
|
|
123
|
+
| `noise_suppression` | `True` | Removes background noise. |
|
|
124
|
+
| `enhancer` | `"deepfilter"` | Use `"webrtc"` when minimizing latency is more important. |
|
|
125
|
+
| `high_pass_filter` | `True` | Reduces low-frequency rumble and hum. |
|
|
126
|
+
| `auto_gain_control` | `True` | Helps keep quiet callers audible. |
|
|
127
|
+
| `stream_delay_ms` | `120` | Starting delay estimate for SIP echo paths. |
|
|
128
|
+
|
|
129
|
+
For tuning, supported formats, model download behavior, and operating notes, see
|
|
130
|
+
[Technical notes](docs/technical-notes.md).
|
|
131
|
+
|
|
132
|
+
## Community
|
|
133
|
+
|
|
134
|
+
Issues, documentation improvements, and pull requests are welcome. See
|
|
135
|
+
[CONTRIBUTING.md](CONTRIBUTING.md). This project is MIT licensed; dependency
|
|
136
|
+
attribution is in [NOTICE](NOTICE).
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# livekit-plugins-denoise
|
|
2
|
+
|
|
3
|
+
Self-hosted noise suppression and acoustic echo cancellation for LiveKit SIP
|
|
4
|
+
agents.
|
|
5
|
+
|
|
6
|
+
- Removes background noise with DeepFilterNet3 or low-latency WebRTC noise
|
|
7
|
+
suppression.
|
|
8
|
+
- Removes the agent's voice when it returns through a caller's handset or
|
|
9
|
+
carrier path.
|
|
10
|
+
- Runs in your agent process: no per-minute denoising fee and no language
|
|
11
|
+
dependency.
|
|
12
|
+
|
|
13
|
+
This is an independent, community-maintained package and is not affiliated
|
|
14
|
+
with or endorsed by LiveKit, Inc.
|
|
15
|
+
|
|
16
|
+
## Why this exists
|
|
17
|
+
|
|
18
|
+
LiveKit's managed `BVCTelephony` noise-cancellation option is billed per minute.
|
|
19
|
+
This package is a self-hosted, MIT-licensed alternative for teams that want
|
|
20
|
+
noise suppression and echo cancellation in their own agent process without a
|
|
21
|
+
per-minute denoising charge. You still pay for your own compute, telephony, and
|
|
22
|
+
other services.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
python -m pip install livekit-plugins-denoise
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
For local development:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
python -m pip install -e ".[dev]"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Enable noise suppression and echo cancellation
|
|
37
|
+
|
|
38
|
+
Create one `TelephonyDenoiser` for each call. `EchoReferenceTap` is required
|
|
39
|
+
for echo cancellation because it supplies the agent's outgoing audio as the
|
|
40
|
+
far-end reference.
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from livekit.agents import room_io
|
|
44
|
+
from livekit.plugins import telephony_denoise
|
|
45
|
+
|
|
46
|
+
denoiser = telephony_denoise.TelephonyDenoiser(
|
|
47
|
+
telephony_denoise.DenoiseOptions(
|
|
48
|
+
echo_cancellation=True,
|
|
49
|
+
noise_suppression=True,
|
|
50
|
+
high_pass_filter=True,
|
|
51
|
+
auto_gain_control=True,
|
|
52
|
+
enhancer="deepfilter", # use "webrtc" for lower latency
|
|
53
|
+
stream_delay_ms=120,
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
await session.start(
|
|
58
|
+
agent=agent,
|
|
59
|
+
room=room,
|
|
60
|
+
room_options=room_io.RoomOptions(
|
|
61
|
+
audio_input=room_io.AudioInputOptions(
|
|
62
|
+
noise_cancellation=denoiser,
|
|
63
|
+
auto_gain_control=False, # do not stack AGC
|
|
64
|
+
),
|
|
65
|
+
),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Add this after session.start(); RoomIO replaces the output during startup.
|
|
69
|
+
session.output.audio = telephony_denoise.EchoReferenceTap(
|
|
70
|
+
denoiser, next_in_chain=session.output.audio
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
To load the neural model before the first call:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
def setup(proc):
|
|
78
|
+
telephony_denoise.prewarm()
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
See [examples/sip_agent.py](examples/sip_agent.py) for a complete SIP agent.
|
|
82
|
+
|
|
83
|
+
## Configuration
|
|
84
|
+
|
|
85
|
+
`DenoiseOptions` lets you control these features independently:
|
|
86
|
+
|
|
87
|
+
| Option | Default | Purpose |
|
|
88
|
+
| --- | --- | --- |
|
|
89
|
+
| `echo_cancellation` | `True` | Cancels the agent's returned audio; requires `EchoReferenceTap`. |
|
|
90
|
+
| `noise_suppression` | `True` | Removes background noise. |
|
|
91
|
+
| `enhancer` | `"deepfilter"` | Use `"webrtc"` when minimizing latency is more important. |
|
|
92
|
+
| `high_pass_filter` | `True` | Reduces low-frequency rumble and hum. |
|
|
93
|
+
| `auto_gain_control` | `True` | Helps keep quiet callers audible. |
|
|
94
|
+
| `stream_delay_ms` | `120` | Starting delay estimate for SIP echo paths. |
|
|
95
|
+
|
|
96
|
+
For tuning, supported formats, model download behavior, and operating notes, see
|
|
97
|
+
[Technical notes](docs/technical-notes.md).
|
|
98
|
+
|
|
99
|
+
## Community
|
|
100
|
+
|
|
101
|
+
Issues, documentation improvements, and pull requests are welcome. See
|
|
102
|
+
[CONTRIBUTING.md](CONTRIBUTING.md). This project is MIT licensed; dependency
|
|
103
|
+
attribution is in [NOTICE](NOTICE).
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Technical notes
|
|
2
|
+
|
|
3
|
+
## Choosing an enhancer
|
|
4
|
+
|
|
5
|
+
`enhancer="deepfilter"` is the default and generally removes more complex
|
|
6
|
+
background noise. It downloads DeepFilterNet3 model weights (about 13 MB) on
|
|
7
|
+
first use, so call `telephony_denoise.prewarm()` during worker startup.
|
|
8
|
+
|
|
9
|
+
Use `enhancer="webrtc"` when lower latency and call density matter more than
|
|
10
|
+
neural suppression quality.
|
|
11
|
+
|
|
12
|
+
## Echo cancellation
|
|
13
|
+
|
|
14
|
+
Noise suppression works without additional wiring. Echo cancellation does not:
|
|
15
|
+
attach `EchoReferenceTap` after `session.start()` so the denoiser receives the
|
|
16
|
+
audio the caller actually hears. Create one denoiser per call; its echo and
|
|
17
|
+
noise state must not be shared between calls.
|
|
18
|
+
|
|
19
|
+
`stream_delay_ms=120` is a useful SIP starting point. Tune it using recordings
|
|
20
|
+
from your carrier route if returned agent audio remains audible.
|
|
21
|
+
|
|
22
|
+
## Audio and pipeline constraints
|
|
23
|
+
|
|
24
|
+
- Inbound telephony audio must be mono.
|
|
25
|
+
- Supported input rates are multiples of 100 Hz, such as 8, 16, 24, 32, and
|
|
26
|
+
48 kHz. Unsupported audio passes through unfiltered rather than failing a
|
|
27
|
+
call.
|
|
28
|
+
- Do not stack this denoiser with another echo or noise canceller; competing
|
|
29
|
+
filters can degrade speech quality.
|
|
30
|
+
- If this plugin owns AGC, set LiveKit's `AudioInputOptions.auto_gain_control`
|
|
31
|
+
to `False`.
|
|
32
|
+
|
|
33
|
+
## Dependencies and licensing
|
|
34
|
+
|
|
35
|
+
This package depends on `livekit`, `livekit-agents`, `numpy`, and
|
|
36
|
+
`deepfilter-stream`. It is MIT licensed. `deepfilter-stream` and DeepFilterNet
|
|
37
|
+
are dual-licensed under MIT or Apache-2.0; the required attribution is in the
|
|
38
|
+
project [NOTICE](../NOTICE). The DeepFilterNet3 model is downloaded by the
|
|
39
|
+
dependency and is not redistributed in this package.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""LiveKit SIP telephony agent with self-hosted noise and echo suppression.
|
|
2
|
+
|
|
3
|
+
The audio filtering is the point of this file; the speech models are ordinary
|
|
4
|
+
and meant to be swapped for whatever you already use.
|
|
5
|
+
|
|
6
|
+
Three wires do the work:
|
|
7
|
+
|
|
8
|
+
1. The model is loaded in `setup`, once per worker process, so no call pays
|
|
9
|
+
for the download and ONNX session build inside its first audio frame.
|
|
10
|
+
|
|
11
|
+
2. The denoiser is handed to `AudioInputOptions.noise_cancellation`, so
|
|
12
|
+
LiveKit runs it on every inbound frame from the caller before speech
|
|
13
|
+
recognition sees it.
|
|
14
|
+
|
|
15
|
+
3. The agent's own outgoing speech is tapped into the same denoiser, which is
|
|
16
|
+
what lets it cancel the echo of its own voice coming back down the line.
|
|
17
|
+
|
|
18
|
+
Run it with:
|
|
19
|
+
|
|
20
|
+
python examples/sip_agent.py dev
|
|
21
|
+
|
|
22
|
+
Inbound calls reach the agent through a SIP trunk and dispatch rule, which are
|
|
23
|
+
configured on the LiveKit side rather than here.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import logging
|
|
29
|
+
|
|
30
|
+
from livekit.agents import (
|
|
31
|
+
Agent,
|
|
32
|
+
AgentServer,
|
|
33
|
+
AgentSession,
|
|
34
|
+
JobContext,
|
|
35
|
+
JobProcess,
|
|
36
|
+
cli,
|
|
37
|
+
room_io,
|
|
38
|
+
)
|
|
39
|
+
from livekit.plugins import telephony_denoise
|
|
40
|
+
|
|
41
|
+
logger = logging.getLogger("sip-agent")
|
|
42
|
+
|
|
43
|
+
# Narrowband phone audio, so there is no point paying for a wider pipeline.
|
|
44
|
+
INPUT_SAMPLE_RATE = 16000
|
|
45
|
+
|
|
46
|
+
INSTRUCTIONS = """You are a helpful voice assistant speaking with a caller on
|
|
47
|
+
the phone. Keep replies short and conversational. Reply in the language the
|
|
48
|
+
caller uses."""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def setup(proc: JobProcess) -> None:
|
|
52
|
+
# Runs once per worker process, before any call is accepted.
|
|
53
|
+
telephony_denoise.prewarm()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
server = AgentServer(setup_fnc=setup)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@server.rtc_session()
|
|
60
|
+
async def entrypoint(ctx: JobContext) -> None:
|
|
61
|
+
await ctx.connect()
|
|
62
|
+
|
|
63
|
+
denoiser = telephony_denoise.TelephonyDenoiser(
|
|
64
|
+
telephony_denoise.DenoiseOptions(
|
|
65
|
+
# Cancels the agent's own voice echoing back off the caller's
|
|
66
|
+
# handset or the carrier's hybrid.
|
|
67
|
+
echo_cancellation=True,
|
|
68
|
+
# Suppresses background noise via DeepFilterNet3 by default
|
|
69
|
+
# (gym, café, babble, hiss — unknown caller environments).
|
|
70
|
+
noise_suppression=True,
|
|
71
|
+
# Removes rumble and mains hum below the voice band.
|
|
72
|
+
high_pass_filter=True,
|
|
73
|
+
# Evens out callers who are too close to or far from the handset.
|
|
74
|
+
auto_gain_control=True,
|
|
75
|
+
# Tune this to your typical SIP round-trip delay.
|
|
76
|
+
stream_delay_ms=120,
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
# Build the filter now rather than on the first frame from the caller, so
|
|
80
|
+
# the greeting below can be used as an echo reference from its first word.
|
|
81
|
+
denoiser.prepare(INPUT_SAMPLE_RATE)
|
|
82
|
+
|
|
83
|
+
session = AgentSession(
|
|
84
|
+
# Multilingual models keep the whole pipeline language-agnostic, to
|
|
85
|
+
# match the filter. Swap these for your own plugins or providers.
|
|
86
|
+
stt="deepgram/nova-3:multi",
|
|
87
|
+
llm="openai/gpt-4o-mini",
|
|
88
|
+
tts="cartesia/sonic-2",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
await session.start(
|
|
92
|
+
agent=Agent(instructions=INSTRUCTIONS),
|
|
93
|
+
room=ctx.room,
|
|
94
|
+
room_options=room_io.RoomOptions(
|
|
95
|
+
audio_input=room_io.AudioInputOptions(
|
|
96
|
+
noise_cancellation=denoiser,
|
|
97
|
+
sample_rate=INPUT_SAMPLE_RATE,
|
|
98
|
+
# The denoiser already applies gain control; running LiveKit's
|
|
99
|
+
# as well would compress the signal twice.
|
|
100
|
+
auto_gain_control=False,
|
|
101
|
+
),
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Must come after start(): RoomIO installs its own audio output during
|
|
106
|
+
# start and would overwrite anything set beforehand.
|
|
107
|
+
session.output.audio = telephony_denoise.EchoReferenceTap(
|
|
108
|
+
denoiser, next_in_chain=session.output.audio
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
logger.info("call ready, audio filter active: %s", denoiser.enhancer)
|
|
112
|
+
|
|
113
|
+
await session.generate_reply(
|
|
114
|
+
instructions="Greet the caller briefly and ask how you can help."
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if __name__ == "__main__":
|
|
119
|
+
logging.basicConfig(level=logging.INFO)
|
|
120
|
+
cli.run_app(server)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Self-hosted noise and echo suppression for LiveKit SIP telephony."""
|
|
2
|
+
|
|
3
|
+
from livekit.agents import Plugin
|
|
4
|
+
|
|
5
|
+
from .echo_reference import EchoReferenceTap
|
|
6
|
+
from .log import logger
|
|
7
|
+
from .neural import prewarm
|
|
8
|
+
from .processor import DenoiseOptions, Enhancer, TelephonyDenoiser
|
|
9
|
+
from .version import __version__
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"DenoiseOptions",
|
|
13
|
+
"EchoReferenceTap",
|
|
14
|
+
"Enhancer",
|
|
15
|
+
"TelephonyDenoiser",
|
|
16
|
+
"__version__",
|
|
17
|
+
"prewarm",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TelephonyDenoisePlugin(Plugin):
|
|
22
|
+
def __init__(self) -> None:
|
|
23
|
+
super().__init__(__name__, __version__, __package__, logger)
|
|
24
|
+
|
|
25
|
+
def download_files(self) -> None:
|
|
26
|
+
# Fetches the DeepFilterNet3 weights so `lk agent build` bakes them into
|
|
27
|
+
# the image; otherwise the first call of a fresh worker pays for the
|
|
28
|
+
# download on the event loop, inside its first 10 ms frame.
|
|
29
|
+
prewarm()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
Plugin.register_plugin(TelephonyDenoisePlugin())
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Small int16 PCM helpers used by the processing pipeline."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Int16Buffer:
|
|
9
|
+
"""A FIFO of interleaved int16 samples."""
|
|
10
|
+
|
|
11
|
+
__slots__ = ("_buf",)
|
|
12
|
+
|
|
13
|
+
def __init__(self) -> None:
|
|
14
|
+
self._buf = np.zeros(0, dtype=np.int16)
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def size(self) -> int:
|
|
18
|
+
return int(self._buf.size)
|
|
19
|
+
|
|
20
|
+
def append(self, data: np.ndarray) -> None:
|
|
21
|
+
# `np.concatenate(..., dtype=np.int16)` casts same_kind, so an int32
|
|
22
|
+
# caller would be truncated into garbage PCM without a word. Only int16
|
|
23
|
+
# is ever correct here, so insist on it.
|
|
24
|
+
if data.dtype != np.int16:
|
|
25
|
+
raise TypeError(f"expected int16 samples, got {data.dtype}")
|
|
26
|
+
self._buf = np.concatenate((self._buf, data))
|
|
27
|
+
|
|
28
|
+
def take(self, count: int) -> np.ndarray:
|
|
29
|
+
out = self._buf[:count].copy()
|
|
30
|
+
self._buf = self._buf[count:]
|
|
31
|
+
return out
|
|
32
|
+
|
|
33
|
+
def clear(self) -> None:
|
|
34
|
+
self._buf = np.zeros(0, dtype=np.int16)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def to_mono(pcm: np.ndarray, src_channels: int) -> np.ndarray:
|
|
38
|
+
"""Downmix interleaved int16 audio when needed."""
|
|
39
|
+
|
|
40
|
+
if src_channels < 1:
|
|
41
|
+
raise ValueError(f"src_channels must be positive, got {src_channels}")
|
|
42
|
+
if src_channels == 1:
|
|
43
|
+
return pcm
|
|
44
|
+
if pcm.size % src_channels:
|
|
45
|
+
raise ValueError(
|
|
46
|
+
f"{pcm.size} samples is not a whole number of {src_channels}ch frames"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
frames = pcm.reshape(-1, src_channels).astype(np.int32)
|
|
50
|
+
# Round rather than truncate: `astype` rounds toward zero, which biases
|
|
51
|
+
# every downmixed sample toward silence and adds a DC-free crackle.
|
|
52
|
+
mean = frames.sum(axis=1) / src_channels
|
|
53
|
+
return np.rint(mean).astype(np.int16)
|