huri-module-vad 0.1.0.dev0__py3-none-any.whl
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.
- huri_module_vad-0.1.0.dev0.dist-info/METADATA +39 -0
- huri_module_vad-0.1.0.dev0.dist-info/RECORD +8 -0
- huri_module_vad-0.1.0.dev0.dist-info/WHEEL +5 -0
- huri_module_vad-0.1.0.dev0.dist-info/entry_points.txt +2 -0
- huri_module_vad-0.1.0.dev0.dist-info/top_level.txt +1 -0
- huri_vad/__init__.py +0 -0
- huri_vad/huri_module.toml +7 -0
- huri_vad/module.py +69 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: huri-module-vad
|
|
3
|
+
Version: 0.1.0.dev0
|
|
4
|
+
Summary: HuRI module: Voice activity detection (WebRTC VAD): microphone frames in, speech segments out
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: huri-core[server]<0.5,>=0.4.0.dev0
|
|
8
|
+
Requires-Dist: huri-events<0.2,>=0.1.0.dev0
|
|
9
|
+
Requires-Dist: numpy<2,>=1.26
|
|
10
|
+
Requires-Dist: webrtcvad==2.0.10
|
|
11
|
+
Requires-Dist: setuptools<82
|
|
12
|
+
|
|
13
|
+
# huri-module-vad
|
|
14
|
+
|
|
15
|
+
Voice activity detection (WebRTC VAD): microphone frames in, speech segments out.
|
|
16
|
+
|
|
17
|
+
| Module | Topics | What it does |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `vad` | `huri.audio.in` → `huri.voice` | Detects speech in microphone frames (WebRTC VAD) |
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
pip install -e packages/huri-module-vad # from a huri-modules checkout, next to huri-core and huri-events
|
|
23
|
+
HURI_MODULES=vad ... # enable it on the server
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
A distribution of
|
|
27
|
+
[huri-modules](https://github.com/Lucy-Robotics/huri-modules), versioned in its
|
|
28
|
+
`pyproject.toml` (see RELEASING.md there). It depends on `huri-core` and
|
|
29
|
+
`huri-events`, which are not on PyPI: without a checkout, install them from git
|
|
30
|
+
in the same pip command, so pip never looks them up on an index:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
ref=0.1.0 # a huri-modules release tag
|
|
34
|
+
core=0.4.0 # HURI_CORE_REF in that release's versions.env
|
|
35
|
+
pip install \
|
|
36
|
+
"huri-core[server] @ git+https://github.com/Lucy-Robotics/huri-core.git@$core" \
|
|
37
|
+
"huri-events @ git+https://github.com/Lucy-Robotics/huri-modules.git@$ref#subdirectory=packages/huri-events" \
|
|
38
|
+
"huri-module-vad @ git+https://github.com/Lucy-Robotics/huri-modules.git@$ref#subdirectory=packages/huri-module-vad"
|
|
39
|
+
```
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
huri_vad/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
huri_vad/huri_module.toml,sha256=YtYluQ3RDV7JaLZeU2oGSUria7OpTY86v2qSfPbaLyo,337
|
|
3
|
+
huri_vad/module.py,sha256=XMXHcSU66aqG0MOKkwlhjPAOnITsqkm9aib5w6r_W1w,2352
|
|
4
|
+
huri_module_vad-0.1.0.dev0.dist-info/METADATA,sha256=-CI5OfxKN9crpIUT4T5y6cTDD5yzgII43P2Uh2rYHOU,1592
|
|
5
|
+
huri_module_vad-0.1.0.dev0.dist-info/WHEEL,sha256=YLJXdYXQ2FQ0Uqn2J-6iEIC-3iOey8lH3xCtvFLkd8Q,91
|
|
6
|
+
huri_module_vad-0.1.0.dev0.dist-info/entry_points.txt,sha256=rvYkPeqJUyKUMRG7MYJptdWp7epBANLp6xgIrZC6kQo,41
|
|
7
|
+
huri_module_vad-0.1.0.dev0.dist-info/top_level.txt,sha256=Viv-8JWNDXL5jm86N5Q6e_boQf2Sd3Q6sife2hSxFRM,9
|
|
8
|
+
huri_module_vad-0.1.0.dev0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
huri_vad
|
huri_vad/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
name = "vad"
|
|
2
|
+
summary = "Detects speech in microphone frames (WebRTC VAD)"
|
|
3
|
+
input = { topic = "huri.audio.in", event = "huri_core.core.events:BytesEvent" }
|
|
4
|
+
output = { topic = "huri.voice", event = "huri_events.speech:Voice" }
|
|
5
|
+
events = ["huri-events>=0.1,<0.2"]
|
|
6
|
+
requires = { gpu = false, actuation = false }
|
|
7
|
+
huri_core = ">=0.4.0.dev0,<0.5"
|
huri_vad/module.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import webrtcvad
|
|
5
|
+
from huri_events.speech import Voice
|
|
6
|
+
|
|
7
|
+
from huri_core.core.events import BytesEvent
|
|
8
|
+
from huri_core.core.module import Module
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class VAD(Module):
|
|
12
|
+
"""VAD Module
|
|
13
|
+
|
|
14
|
+
Detect voice and silence using WebRTC VAD.
|
|
15
|
+
|
|
16
|
+
input: huri.audio.in,
|
|
17
|
+
output: huri.voice
|
|
18
|
+
|
|
19
|
+
:vad_agressiveness: from 0 (low) to 3 (high, can distord audio).
|
|
20
|
+
:silence_duration: how many seconds will a no voice be considered a silence.
|
|
21
|
+
:sample_rate: size of received chunk of audio. Usually 8000, 16000 or 48000.
|
|
22
|
+
:block_duration: size of received chunk of audio (in s).
|
|
23
|
+
Can only be 0.010, 0.020 and 0.030.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
# Inbound microphone frames travel on their own topic so the TTS-output
|
|
27
|
+
# "audio" topic (consumed by MOV and the client Sender) never collides
|
|
28
|
+
# with mic input — otherwise raw mic bytes get echoed back to the client.
|
|
29
|
+
input_type = "huri.audio.in"
|
|
30
|
+
output_type = "huri.voice"
|
|
31
|
+
input_event = BytesEvent
|
|
32
|
+
output_event = Voice
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
vad_agressiveness: int = 3,
|
|
37
|
+
silence_duration: float = 3, # s
|
|
38
|
+
sample_rate: int = 16000,
|
|
39
|
+
block_duration: float = 0.020, # s
|
|
40
|
+
):
|
|
41
|
+
super().__init__()
|
|
42
|
+
|
|
43
|
+
if block_duration not in [0.010, 0.020, 0.030]:
|
|
44
|
+
raise RuntimeError("block duration must be 0.010, 0.020 or 0.030 s")
|
|
45
|
+
|
|
46
|
+
self.sample_rate: int = sample_rate
|
|
47
|
+
self.block_size: int = int(block_duration * sample_rate) # ms
|
|
48
|
+
|
|
49
|
+
self.silence_frames_size: int = int(silence_duration * sample_rate)
|
|
50
|
+
self.silence_frames_count: int = -1
|
|
51
|
+
|
|
52
|
+
self.vad = webrtcvad.Vad(vad_agressiveness)
|
|
53
|
+
|
|
54
|
+
async def process(self, data: BytesEvent) -> Optional[Voice]:
|
|
55
|
+
if self.vad.is_speech(data.data, self.sample_rate) is True:
|
|
56
|
+
self.silence_frames_count = 0
|
|
57
|
+
|
|
58
|
+
audio_array = np.frombuffer(data.data, dtype=np.int16)
|
|
59
|
+
audio_array_float = audio_array.astype(np.float32) / 32768.0
|
|
60
|
+
|
|
61
|
+
return Voice(audio_array_float)
|
|
62
|
+
else:
|
|
63
|
+
if self.silence_frames_count != -1:
|
|
64
|
+
self.silence_frames_count += self.block_size
|
|
65
|
+
if self.silence_frames_count > self.silence_frames_size:
|
|
66
|
+
self.silence_frames_count = -1 # sent only once
|
|
67
|
+
|
|
68
|
+
return Voice(None)
|
|
69
|
+
return None
|