vad: use exponential smoothed volume to improve speech detection
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user