[Wake/VAD/STT 与 Live runtime]:完成真实重复语音运行,包含云端ASR/TTS开关、run-live和临时上下文测试
This commit is contained in:
+109
-2
@@ -1,12 +1,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from .models import AudioFrame, AudioSegment, ErrorCode, ProviderError, VadResult
|
||||
from .speech_models import vad_model_path
|
||||
|
||||
|
||||
class EnergyVadProvider:
|
||||
def __init__(self, threshold: int = 0) -> None:
|
||||
def __init__(self, threshold: int = 500) -> None:
|
||||
self.threshold = threshold
|
||||
self.loaded = False
|
||||
self._speech_ms = 0
|
||||
@@ -47,12 +50,116 @@ class EnergyVadProvider:
|
||||
return bool(frame.metadata["speech"])
|
||||
if not frame.pcm:
|
||||
return False
|
||||
try:
|
||||
import struct
|
||||
|
||||
sample_count = len(frame.pcm) // 2
|
||||
if sample_count:
|
||||
samples = struct.unpack("<" + "h" * sample_count, frame.pcm[: sample_count * 2])
|
||||
return max(abs(sample) for sample in samples) > self.threshold
|
||||
except Exception:
|
||||
pass
|
||||
return any(abs(byte - 128) > self.threshold for byte in frame.pcm)
|
||||
|
||||
|
||||
class SherpaOnnxVadProvider:
|
||||
def __init__(self, models_dir: str | Path, threshold: float = 0.5, sherpa_module: Any | None = None) -> None:
|
||||
self.models_dir = Path(models_dir)
|
||||
self.threshold = threshold
|
||||
self.loaded = False
|
||||
self._sherpa = sherpa_module
|
||||
self._model: Any | None = None
|
||||
self._speech_ms = 0
|
||||
self._silence_ms = 0
|
||||
|
||||
def load(self) -> None:
|
||||
model_path = vad_model_path(self.models_dir)
|
||||
if not model_path.exists():
|
||||
raise ProviderError(
|
||||
ErrorCode.VAD_MODEL_LOAD_FAILED,
|
||||
f"sherpa-onnx VAD model path does not exist: {model_path}",
|
||||
False,
|
||||
"sherpa-onnx-vad",
|
||||
"vad",
|
||||
)
|
||||
sherpa_onnx = self._sherpa
|
||||
if sherpa_onnx is None:
|
||||
try:
|
||||
import sherpa_onnx # type: ignore[import-not-found]
|
||||
except Exception as exc:
|
||||
raise ProviderError(
|
||||
ErrorCode.VAD_MODEL_LOAD_FAILED,
|
||||
f"sherpa_onnx is not available: {exc}",
|
||||
False,
|
||||
"sherpa-onnx-vad",
|
||||
"vad",
|
||||
) from exc
|
||||
try:
|
||||
config = sherpa_onnx.VadModelConfig(
|
||||
silero_vad=sherpa_onnx.SileroVadModelConfig(model=str(model_path), threshold=self.threshold),
|
||||
sample_rate=16000,
|
||||
)
|
||||
self._model = sherpa_onnx.VadModel.create(config)
|
||||
except Exception as exc:
|
||||
raise ProviderError(
|
||||
ErrorCode.VAD_MODEL_LOAD_FAILED,
|
||||
f"failed to load sherpa-onnx VAD model: {exc}",
|
||||
False,
|
||||
"sherpa-onnx-vad",
|
||||
"vad",
|
||||
) from exc
|
||||
self.loaded = True
|
||||
|
||||
def analyze(self, frame: AudioFrame) -> VadResult:
|
||||
if not self.loaded or self._model is None:
|
||||
raise ProviderError(
|
||||
ErrorCode.VAD_MODEL_LOAD_FAILED,
|
||||
"sherpa-onnx VAD provider is not loaded",
|
||||
False,
|
||||
"sherpa-onnx-vad",
|
||||
"vad",
|
||||
)
|
||||
try:
|
||||
import numpy as np
|
||||
|
||||
samples = np.frombuffer(frame.pcm, dtype=np.int16).astype(np.float32) / 32768.0
|
||||
window_size = int(self._model.window_size())
|
||||
if samples.size < window_size:
|
||||
samples = np.pad(samples, (0, window_size - samples.size))
|
||||
elif samples.size > window_size:
|
||||
samples = samples[-window_size:]
|
||||
is_speech = bool(self._model.is_speech(samples))
|
||||
except Exception as exc:
|
||||
raise ProviderError(
|
||||
ErrorCode.VAD_MODEL_LOAD_FAILED,
|
||||
f"sherpa-onnx VAD analysis failed: {exc}",
|
||||
True,
|
||||
"sherpa-onnx-vad",
|
||||
"vad",
|
||||
) from exc
|
||||
frame_ms = int(frame.metadata.get("duration_ms", 20))
|
||||
if is_speech:
|
||||
self._speech_ms += frame_ms
|
||||
self._silence_ms = 0
|
||||
else:
|
||||
self._silence_ms += frame_ms
|
||||
return VadResult(
|
||||
is_speech=is_speech,
|
||||
confidence=0.9 if is_speech else 0.1,
|
||||
speech_ms=self._speech_ms,
|
||||
silence_ms=self._silence_ms,
|
||||
)
|
||||
|
||||
def reset(self) -> None:
|
||||
self._speech_ms = 0
|
||||
self._silence_ms = 0
|
||||
if self._model is not None:
|
||||
self._model.reset()
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class VadRecorder:
|
||||
provider: EnergyVadProvider
|
||||
provider: Any
|
||||
min_duration_ms: int = 300
|
||||
end_silence_ms: int = 200
|
||||
no_speech_timeout_ms: int = 1000
|
||||
|
||||
Reference in New Issue
Block a user