vad: use exponential smoothed volume to improve speech detection

This commit is contained in:
Aleix Conchillo Flaqué
2024-05-17 17:13:31 -07:00
parent 455ec4f1fd
commit 3e13678f23
3 changed files with 27 additions and 16 deletions

View File

@@ -13,10 +13,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `VADParams` so you can control voice confidence level and others. - Added `VADParams` so you can control voice confidence level and others.
- `VADAnalyzer` now uses an exponential smoothing to avoid sudden changes. - `VADAnalyzer` now uses an exponential smoothed volume to improve speech
detection. This is useful when voice confidence is high (because there's
someone talking near you) but volume is low.
### Fixed ### Fixed
- Fixed an issue where TTSService was not pushing TextFrames downstream.
- Fixed issues with Ctrl-C program termination. - Fixed issues with Ctrl-C program termination.
- Fixed an issue that was causing `StopTaskFrame` to actually not exit the - Fixed an issue that was causing `StopTaskFrame` to actually not exit the

View File

@@ -94,7 +94,7 @@ class STTService(AIService):
"""STTService is a base class for speech-to-text services.""" """STTService is a base class for speech-to-text services."""
def __init__(self, def __init__(self,
min_rms: int = 75, min_rms: int = 100,
max_silence_secs: float = 0.3, max_silence_secs: float = 0.3,
max_buffer_secs: float = 1.5, max_buffer_secs: float = 1.5,
sample_rate: int = 16000, sample_rate: int = 16000,
@@ -107,8 +107,8 @@ class STTService(AIService):
self._num_channels = num_channels self._num_channels = num_channels
(self._content, self._wave) = self._new_wave() (self._content, self._wave) = self._new_wave()
self._silence_num_frames = 0 self._silence_num_frames = 0
# Exponential smoothing # Volume exponential smoothing
self._smoothing_factor = 0.08 self._smoothing_factor = 0.5
self._prev_rms = 1 - self._smoothing_factor self._prev_rms = 1 - self._smoothing_factor
@abstractmethod @abstractmethod

View File

@@ -4,6 +4,9 @@
# SPDX-License-Identifier: BSD 2-Clause License # SPDX-License-Identifier: BSD 2-Clause License
# #
import array
import math
from abc import abstractmethod from abc import abstractmethod
from enum import Enum from enum import Enum
@@ -20,9 +23,10 @@ class VADState(Enum):
class VADParams(BaseModel): class VADParams(BaseModel):
confidence: float = 0.5 confidence: float = 0.6
start_secs: float = 0.2 start_secs: float = 0.2
stop_secs: float = 0.8 stop_secs: float = 0.8
min_rms: int = 1000
class VADAnalyzer: class VADAnalyzer:
@@ -43,9 +47,9 @@ class VADAnalyzer:
self._vad_buffer = b"" self._vad_buffer = b""
# Exponential smoothing # Volume exponential smoothing
self._smoothing_factor = 0.6 self._smoothing_factor = 0.5
self._prev_confidence = 1 - self._smoothing_factor self._prev_rms = 1 - self._smoothing_factor
@property @property
def sample_rate(self): def sample_rate(self):
@@ -59,10 +63,13 @@ class VADAnalyzer:
def voice_confidence(self, buffer) -> float: def voice_confidence(self, buffer) -> float:
pass pass
def _smoothed_confidence(self, audio_frames, prev_confidence, factor): def _get_smoothed_volume(self, audio: bytes, prev_rms: float, factor: float) -> float:
confidence = self.voice_confidence(audio_frames) # https://docs.python.org/3/library/array.html
smoothed = exp_smoothing(confidence, prev_confidence, factor) audio_array = array.array('h', audio)
return smoothed squares = [sample**2 for sample in audio_array]
mean = sum(squares) / len(audio_array)
rms = math.sqrt(mean)
return exp_smoothing(rms, prev_rms, factor)
def analyze_audio(self, buffer) -> VADState: def analyze_audio(self, buffer) -> VADState:
self._vad_buffer += buffer self._vad_buffer += buffer
@@ -74,11 +81,11 @@ class VADAnalyzer:
audio_frames = self._vad_buffer[:num_required_bytes] audio_frames = self._vad_buffer[:num_required_bytes]
self._vad_buffer = self._vad_buffer[num_required_bytes:] self._vad_buffer = self._vad_buffer[num_required_bytes:]
confidence = self._smoothed_confidence( confidence = self.voice_confidence(audio_frames)
audio_frames, self._prev_confidence, self._smoothing_factor) rms = self._get_smoothed_volume(audio_frames, self._prev_rms, self._smoothing_factor)
self._prev_confidence = confidence self._prev_rms = rms
speaking = confidence >= self._params.confidence speaking = confidence >= self._params.confidence and rms >= self._params.min_rms
if speaking: if speaking:
match self._vad_state: match self._vad_state: