Skip to content

Commit be0f092

Browse files
committed
1 parent 52ecb42 commit be0f092

File tree

6 files changed

+63
-33
lines changed

6 files changed

+63
-33
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ non exhaustive list of config options
3232
"microphone": {
3333
"module": "ovos-microphone-plugin-alsa"
3434
},
35+
// wake word verifier plugins will double check a wake word prediction
36+
// they are given a chance to reject wake word activations
37+
"ww_verifiers": {
38+
"ovos-ww-verifier-silero": {
39+
"threshold": 0.1,
40+
// does not make sense to enable if "vad_pre_wake_enabled" is set to true
41+
"enabled": false
42+
}
43+
},
44+
3545
// If enabled will only check for wakeword if VAD also detected speech
3646
// this should reduce false activations
3747
"vad_pre_wake_enabled": true,

ovos_dinkum_listener/service.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import json
1414
import random
1515
import subprocess
16+
import time
17+
import warnings
1618
import wave
1719
from enum import Enum
1820
from hashlib import md5
@@ -24,7 +26,6 @@
2426
from typing import List, Tuple, Optional, Union
2527

2628
import speech_recognition as sr
27-
import time
2829
from ovos_bus_client import MessageBusClient
2930
from ovos_bus_client.message import Message
3031
from ovos_bus_client.session import SessionManager
@@ -37,26 +38,19 @@
3738
from ovos_plugin_manager.templates.vad import VADEngine
3839
from ovos_plugin_manager.utils.tts_cache import hash_sentence
3940
from ovos_plugin_manager.vad import OVOSVADFactory, get_vad_configs
41+
from ovos_plugin_manager.wakewords import find_wake_word_verifier_plugins
4042
from ovos_plugin_manager.wakewords import get_ww_lang_configs, get_ww_supported_langs, get_ww_module_configs
4143
from ovos_utils.fakebus import FakeBus
4244
from ovos_utils.log import LOG, log_deprecation
4345
from ovos_utils.process_utils import ProcessStatus, StatusCallbackMap, ProcessState
46+
from ovos_utils.sound import get_sound_duration
4447

45-
import warnings
4648
from ovos_dinkum_listener._util import _TemplateFilenameFormatter
4749
from ovos_dinkum_listener.plugins import load_stt_module, load_fallback_stt, FakeStreamingSTT
4850
from ovos_dinkum_listener.transformers import AudioTransformersService
4951
from ovos_dinkum_listener.voice_loop import DinkumVoiceLoop, ListeningMode, ListeningState
5052
from ovos_dinkum_listener.voice_loop.hotwords import HotwordContainer
5153

52-
53-
try:
54-
from ovos_utils.sound import get_sound_duration
55-
except ImportError:
56-
57-
def get_sound_duration(*args, **kwargs):
58-
raise ImportError("please install ovos-utils>=0.1.0a25")
59-
6054
# Seconds between systemd watchdog updates
6155
WATCHDOG_DELAY = 0.5
6256

@@ -194,7 +188,25 @@ def __init__(self, on_ready=on_ready, on_error=on_error,
194188

195189
self.mic = mic or OVOSMicrophoneFactory.create(microphone_config)
196190

197-
self.hotwords = hotwords or HotwordContainer(self.bus)
191+
verifiers_cfg = self.config.get("listener", {}).get("ww_verifiers", {})
192+
verifier_plugs = {}
193+
for plug_type, plug in find_wake_word_verifier_plugins().items():
194+
cfg = verifiers_cfg.get(plug_type, {})
195+
if not cfg.get("enabled", True): # plugins are enabled by default if installed, unless disabled in config
196+
LOG.debug(f"wakeword verifier plugin disabled: {plug_type}")
197+
continue
198+
try:
199+
verifier_plugs[plug_type] = plug(config=cfg)
200+
except Exception as e:
201+
LOG.exception(f"Failed to load wakeword verifier plugin: {plug_type}")
202+
continue
203+
204+
missing_modules = [k for k, v in verifiers_cfg.items() if v.get("active", True) and k not in verifier_plugs]
205+
if missing_modules:
206+
LOG.warning(f"wake word verifier plugins enabled in config but not loaded: {missing_modules}")
207+
LOG.debug(f"Loaded wake word verifier plugins: {list(verifier_plugs)}")
208+
209+
self.hotwords = hotwords or HotwordContainer(bus=self.bus, verifiers=list(verifier_plugs.values()))
198210
self.vad = vad or OVOSVADFactory.create()
199211
if stt and not isinstance(stt, StreamingSTT):
200212
stt = FakeStreamingSTT(stt)

ovos_dinkum_listener/voice_loop/hotwords.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
11
from enum import Enum
22
from os.path import dirname
33
from threading import Event
4-
from typing import Optional
4+
from typing import Optional, List
55

66
from ovos_config import Configuration
77
from ovos_plugin_manager.wakewords import OVOSWakeWordFactory, HotWordEngine
8+
from ovos_plugin_manager.templates.hotwords import HotWordVerifier
89
from ovos_utils.fakebus import FakeBus
910
from ovos_utils.log import LOG
10-
try:
11-
from ovos_utils.sound import get_sound_duration
12-
except ImportError:
13-
14-
def get_sound_duration(*args, **kwargs):
15-
raise ImportError("please install ovos-utils>=0.1.0a25")
11+
from ovos_utils.sound import get_sound_duration
1612

1713

1814
class HotWordException(RuntimeWarning):
@@ -103,15 +99,15 @@ class HotwordContainer:
10399
_plugins = {}
104100
_loaded = Event()
105101

106-
def __init__(self, bus=FakeBus(), expected_duration=3, sample_rate=16000,
107-
sample_width=2, reload_allowed=True, autoload=False):
102+
def __init__(self, bus=FakeBus(), verifiers: Optional[List[HotWordVerifier]] = None, reload_allowed=True, autoload=False):
108103
self.bus = bus
109104
self.reload_allowed = reload_allowed
110105
self.state = HotwordState.HOTWORD
111106
self.reload_on_failure = False
112107
self.applied_hotwords_config = None
113108
if autoload:
114109
self.load_hotword_engines()
110+
self.verifiers: List[HotWordVerifier] = verifiers or []
115111

116112
def load_hotword_engines(self):
117113
"""
@@ -305,6 +301,13 @@ def get_ww(self, ww: str) -> dict:
305301
meta["engine"] = plug.__class__.__name__
306302
return meta
307303

304+
def verify(self, ww_audio: bytes) -> bool:
305+
for verifier in self.verifiers:
306+
if not verifier.verify(ww_audio):
307+
LOG.debug(f"{verifier.__class__.__name__}: verification failed - discarding wake word detection")
308+
return False
309+
return True
310+
308311
def update(self, chunk: bytes):
309312
"""
310313
Update appropriate engines based on self.state

ovos_dinkum_listener/voice_loop/voice_loop.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,24 @@
1010
# See the License for the specific language governing permissions and
1111
# limitations under the License.
1212
#
13-
13+
import audioop
1414
import time
1515
from collections import deque
1616
from dataclasses import dataclass, field
1717
from enum import Enum
18+
from threading import Event
1819
from typing import Callable, Deque, Optional
1920

20-
import audioop
2121
from ovos_config import Configuration
2222
from ovos_plugin_manager.stt import StreamingSTT
23-
from ovos_plugin_manager.templates.microphone import Microphone
2423
from ovos_plugin_manager.vad import VADEngine
2524
from ovos_utils.log import LOG
26-
27-
from ovos_dinkum_listener.plugins import FakeStreamingSTT
25+
from ovos_bus_client.session import SessionManager
2826
from ovos_dinkum_listener.transformers import AudioTransformersService
2927
from ovos_dinkum_listener.voice_loop.hotwords import HotwordContainer, HotwordState, HotWordException
28+
from ovos_plugin_manager.templates.microphone import Microphone
29+
30+
from ovos_dinkum_listener.plugins import FakeStreamingSTT
3031

3132

3233
class ListeningState(str, Enum):
@@ -554,16 +555,20 @@ def _detect_ww(self, chunk: bytes) -> bool:
554555
LOG.debug(f"Wake word detected={ww}")
555556
ww_data = self.hotwords.get_ww(ww)
556557

558+
hotword_audio_bytes = bytes()
559+
while self.hotword_chunks:
560+
hotword_audio_bytes += self.hotword_chunks.popleft()
561+
562+
self.hotword_chunks.clear()
563+
564+
if not self.hotwords.verify(hotword_audio_bytes):
565+
LOG.debug("wake word verifier plugins discarded detection")
566+
return False
567+
557568
# Callback to handle recorded hotword audio
558569
if self.listenword_audio_callback is not None:
559-
hotword_audio_bytes = bytes()
560-
while self.hotword_chunks:
561-
hotword_audio_bytes += self.hotword_chunks.popleft()
562-
563570
self.listenword_audio_callback(hotword_audio_bytes, ww_data)
564571

565-
self.hotword_chunks.clear()
566-
567572
# Callback to handle wake up
568573
if self.wake_callback is not None:
569574
# emit record_begin

requirements/extras.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
ovos-stt-plugin-server>=0.1.2,<1.0.0
33

44
# VAD plugins
5-
ovos-vad-plugin-silero>=0.0.5,<1.0.0
5+
ovos-vad-plugin-silero>=0.1.0a1,<1.0.0
66

77
# Microphone plugins
88
ovos-microphone-plugin-sounddevice>=0.0.1,<1.0.0

requirements/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ovos-plugin-manager>=1.0.2,<3.0.0
1+
ovos-plugin-manager>=2.1.0a1,<3.0.0
22
ovos-utils>=0.8.1,<1.0.0
33
ovos-config>=1.2.2,<3.0.0
44
ovos_bus_client>=1.3.4,<2.0.0

0 commit comments

Comments
 (0)