huri-module-vad 0.1.0.dev0__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.
- huri_module_vad-0.1.0.dev0/PKG-INFO +39 -0
- huri_module_vad-0.1.0.dev0/README.md +27 -0
- huri_module_vad-0.1.0.dev0/huri_module_vad.egg-info/PKG-INFO +39 -0
- huri_module_vad-0.1.0.dev0/huri_module_vad.egg-info/SOURCES.txt +12 -0
- huri_module_vad-0.1.0.dev0/huri_module_vad.egg-info/dependency_links.txt +1 -0
- huri_module_vad-0.1.0.dev0/huri_module_vad.egg-info/entry_points.txt +2 -0
- huri_module_vad-0.1.0.dev0/huri_module_vad.egg-info/requires.txt +5 -0
- huri_module_vad-0.1.0.dev0/huri_module_vad.egg-info/top_level.txt +1 -0
- huri_module_vad-0.1.0.dev0/huri_vad/__init__.py +0 -0
- huri_module_vad-0.1.0.dev0/huri_vad/huri_module.toml +7 -0
- huri_module_vad-0.1.0.dev0/huri_vad/module.py +69 -0
- huri_module_vad-0.1.0.dev0/pyproject.toml +27 -0
- huri_module_vad-0.1.0.dev0/setup.cfg +4 -0
- huri_module_vad-0.1.0.dev0/tests/test_vad.py +51 -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,27 @@
|
|
|
1
|
+
# huri-module-vad
|
|
2
|
+
|
|
3
|
+
Voice activity detection (WebRTC VAD): microphone frames in, speech segments out.
|
|
4
|
+
|
|
5
|
+
| Module | Topics | What it does |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `vad` | `huri.audio.in` → `huri.voice` | Detects speech in microphone frames (WebRTC VAD) |
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pip install -e packages/huri-module-vad # from a huri-modules checkout, next to huri-core and huri-events
|
|
11
|
+
HURI_MODULES=vad ... # enable it on the server
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
A distribution of
|
|
15
|
+
[huri-modules](https://github.com/Lucy-Robotics/huri-modules), versioned in its
|
|
16
|
+
`pyproject.toml` (see RELEASING.md there). It depends on `huri-core` and
|
|
17
|
+
`huri-events`, which are not on PyPI: without a checkout, install them from git
|
|
18
|
+
in the same pip command, so pip never looks them up on an index:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
ref=0.1.0 # a huri-modules release tag
|
|
22
|
+
core=0.4.0 # HURI_CORE_REF in that release's versions.env
|
|
23
|
+
pip install \
|
|
24
|
+
"huri-core[server] @ git+https://github.com/Lucy-Robotics/huri-core.git@$core" \
|
|
25
|
+
"huri-events @ git+https://github.com/Lucy-Robotics/huri-modules.git@$ref#subdirectory=packages/huri-events" \
|
|
26
|
+
"huri-module-vad @ git+https://github.com/Lucy-Robotics/huri-modules.git@$ref#subdirectory=packages/huri-module-vad"
|
|
27
|
+
```
|
|
@@ -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,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
huri_module_vad.egg-info/PKG-INFO
|
|
4
|
+
huri_module_vad.egg-info/SOURCES.txt
|
|
5
|
+
huri_module_vad.egg-info/dependency_links.txt
|
|
6
|
+
huri_module_vad.egg-info/entry_points.txt
|
|
7
|
+
huri_module_vad.egg-info/requires.txt
|
|
8
|
+
huri_module_vad.egg-info/top_level.txt
|
|
9
|
+
huri_vad/__init__.py
|
|
10
|
+
huri_vad/huri_module.toml
|
|
11
|
+
huri_vad/module.py
|
|
12
|
+
tests/test_vad.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
huri_vad
|
|
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"
|
|
@@ -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
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64,<82"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "huri-module-vad"
|
|
7
|
+
version = "0.1.0.dev0"
|
|
8
|
+
description = "HuRI module: Voice activity detection (WebRTC VAD): microphone frames in, speech segments out"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"huri-core[server]>=0.4.0.dev0,<0.5",
|
|
13
|
+
"huri-events>=0.1.0.dev0,<0.2",
|
|
14
|
+
"numpy>=1.26,<2",
|
|
15
|
+
"webrtcvad==2.0.10",
|
|
16
|
+
# webrtcvad imports pkg_resources, removed in setuptools 82
|
|
17
|
+
"setuptools<82",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.entry-points."huri.modules"]
|
|
21
|
+
vad = "huri_vad.module:VAD"
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
include = ["huri_vad*"]
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.package-data]
|
|
27
|
+
huri_vad = ["huri_module.toml", "manifests/*/huri_module.toml"]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""VAD's speech/silence logic, with WebRTC's detector scripted."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pytest
|
|
5
|
+
from huri_events.speech import Voice
|
|
6
|
+
from huri_vad.module import VAD
|
|
7
|
+
|
|
8
|
+
from huri_core.core.events import BytesEvent
|
|
9
|
+
from huri_core.testing import ModuleHarness
|
|
10
|
+
|
|
11
|
+
FRAME = (np.full(320, 16384, dtype=np.int16)).tobytes() # 20 ms at 16 kHz
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Scripted:
|
|
15
|
+
def __init__(self, decisions):
|
|
16
|
+
self.decisions = list(decisions)
|
|
17
|
+
|
|
18
|
+
def is_speech(self, frame, sample_rate):
|
|
19
|
+
return self.decisions.pop(0)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def vad(decisions, silence_duration=0.05):
|
|
23
|
+
harness = ModuleHarness(VAD, silence_duration=silence_duration)
|
|
24
|
+
harness.module.vad = Scripted(decisions)
|
|
25
|
+
return harness
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_speech_frames_become_normalized_voice():
|
|
29
|
+
(voice,) = vad([True]).run(BytesEvent(FRAME))
|
|
30
|
+
assert voice.data.dtype == np.float32 and voice.data.size == 320
|
|
31
|
+
assert voice.data[0] == pytest.approx(0.5)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_end_of_utterance_is_sent_once_after_enough_silence():
|
|
35
|
+
# 0.05 s of silence = more than 2 frames of 20 ms.
|
|
36
|
+
out = vad([True] + [False] * 6).run(*[BytesEvent(FRAME)] * 7)
|
|
37
|
+
assert [v.data is None for v in out] == [False, True]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_silence_before_any_speech_sends_nothing():
|
|
41
|
+
assert vad([False] * 5).run(*[BytesEvent(FRAME)] * 5) == []
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_block_duration_must_be_one_webrtc_accepts():
|
|
45
|
+
with pytest.raises(RuntimeError, match="block duration"):
|
|
46
|
+
ModuleHarness(VAD, block_duration=0.025)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_real_detector_runs_on_a_frame():
|
|
50
|
+
out = ModuleHarness(VAD).run(BytesEvent(bytes(640))) # digital silence
|
|
51
|
+
assert out == [] or isinstance(out[0], Voice)
|