[本地语音降噪]:完成本地语音链路和噪音过滤,包含降噪模型、本地ASR和实时字幕稳定策略

This commit is contained in:
mkbk
2026-06-17 22:55:33 +08:00
parent a77a172412
commit 8c75fc5baf
22 changed files with 803 additions and 64 deletions
+48 -12
View File
@@ -11,15 +11,20 @@ from .models import ErrorCode, ProviderError
DEFAULT_VAD_URL = "https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/silero_vad.onnx"
DEFAULT_STT_URL = (
"https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/"
"sherpa-onnx-streaming-zipformer-zh-14M-2023-02-23.tar.bz2"
"sherpa-onnx-streaming-zipformer-ctc-zh-int8-2025-06-30.tar.bz2"
)
DEFAULT_STT_DIR = "sherpa-onnx-streaming-zipformer-zh-14M-2023-02-23"
DEFAULT_STT_DIR = "sherpa-onnx-streaming-zipformer-ctc-zh-int8-2025-06-30"
DEFAULT_KWS_URL = (
"https://github.com/k2-fsa/sherpa-onnx/releases/download/kws-models/"
"sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01-mobile.tar.bz2"
)
DEFAULT_KWS_DIR = "sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01-mobile"
DEFAULT_KWS_KEYWORDS = "x iǎo j ié x iǎo j ié @小杰小杰\n"
DEFAULT_DENOISER_URL = (
"https://github.com/k2-fsa/sherpa-onnx/releases/download/"
"speech-enhancement-models/gtcrn_simple.onnx"
)
DEFAULT_DENOISER_PATH = "denoise/gtcrn_simple.onnx"
REQUIRED_MODEL_FILES = (
f"wake/{DEFAULT_KWS_DIR}/tokens.txt",
@@ -29,9 +34,8 @@ REQUIRED_MODEL_FILES = (
"wake/keywords.txt",
"vad/silero_vad.onnx",
f"stt/{DEFAULT_STT_DIR}/tokens.txt",
f"stt/{DEFAULT_STT_DIR}/encoder-epoch-99-avg-1.int8.onnx",
f"stt/{DEFAULT_STT_DIR}/decoder-epoch-99-avg-1.onnx",
f"stt/{DEFAULT_STT_DIR}/joiner-epoch-99-avg-1.int8.onnx",
f"stt/{DEFAULT_STT_DIR}/model.int8.onnx",
DEFAULT_DENOISER_PATH,
)
@@ -68,6 +72,7 @@ def default_manifest() -> dict[str, Any]:
"wake": DEFAULT_KWS_URL,
"vad": DEFAULT_VAD_URL,
"stt": DEFAULT_STT_URL,
"denoiser": DEFAULT_DENOISER_URL,
},
"providers": {
"wake": {
@@ -84,12 +89,14 @@ def default_manifest() -> dict[str, Any]:
"path": "vad/silero_vad.onnx",
},
"stt": {
"type": "sherpa-onnx-streaming-transducer",
"type": "sherpa-onnx-streaming-zipformer2-ctc",
"model_dir": f"stt/{DEFAULT_STT_DIR}",
"tokens": f"stt/{DEFAULT_STT_DIR}/tokens.txt",
"encoder": f"stt/{DEFAULT_STT_DIR}/encoder-epoch-99-avg-1.int8.onnx",
"decoder": f"stt/{DEFAULT_STT_DIR}/decoder-epoch-99-avg-1.onnx",
"joiner": f"stt/{DEFAULT_STT_DIR}/joiner-epoch-99-avg-1.int8.onnx",
"model": f"stt/{DEFAULT_STT_DIR}/model.int8.onnx",
},
"denoiser": {
"type": "sherpa-onnx-gtcrn",
"path": DEFAULT_DENOISER_PATH,
},
},
"required_files": list(REQUIRED_MODEL_FILES),
@@ -127,6 +134,13 @@ def vad_model_path(models_dir: str | Path) -> Path:
return root / str(path)
def denoiser_model_path(models_dir: str | Path) -> Path:
root = Path(models_dir)
manifest = load_manifest(root)
path = manifest.get("providers", {}).get("denoiser", {}).get("path", DEFAULT_DENOISER_PATH)
return root / str(path)
def wake_model_paths(models_dir: str | Path) -> dict[str, Path]:
root = Path(models_dir)
manifest = load_manifest(root)
@@ -144,12 +158,21 @@ def wake_model_paths(models_dir: str | Path) -> dict[str, Path]:
}
def stt_model_paths(model_path: str | Path) -> dict[str, Path]:
def stt_model_paths(model_path: str | Path) -> dict[str, Any]:
root = Path(model_path)
if (root / "manifest.json").exists() or (root / "stt").exists():
manifest = load_manifest(root)
stt = manifest.get("providers", {}).get("stt", {})
stt_type = str(stt.get("type", "sherpa-onnx-streaming-transducer"))
if stt_type == "sherpa-onnx-streaming-zipformer2-ctc":
return {
"type": stt_type,
"model_dir": root / str(stt.get("model_dir", f"stt/{DEFAULT_STT_DIR}")),
"tokens": root / str(stt.get("tokens", f"stt/{DEFAULT_STT_DIR}/tokens.txt")),
"model": root / str(stt.get("model", f"stt/{DEFAULT_STT_DIR}/model.int8.onnx")),
}
return {
"type": stt_type,
"model_dir": root / str(stt.get("model_dir", f"stt/{DEFAULT_STT_DIR}")),
"tokens": root / str(stt.get("tokens", f"stt/{DEFAULT_STT_DIR}/tokens.txt")),
"encoder": root / str(stt.get("encoder", f"stt/{DEFAULT_STT_DIR}/encoder-epoch-99-avg-1.int8.onnx")),
@@ -157,6 +180,7 @@ def stt_model_paths(model_path: str | Path) -> dict[str, Path]:
"joiner": root / str(stt.get("joiner", f"stt/{DEFAULT_STT_DIR}/joiner-epoch-99-avg-1.int8.onnx")),
}
return {
"type": "sherpa-onnx-streaming-transducer",
"model_dir": root,
"tokens": root / "tokens.txt",
"encoder": root / "encoder-epoch-99-avg-1.int8.onnx",
@@ -195,11 +219,23 @@ def model_status_errors(status: SpeechModelStatus) -> list[ProviderError]:
"model-check",
)
)
if status.missing_files:
denoise_missing = tuple(item for item in status.missing_files if item.startswith("denoise/"))
speech_missing = tuple(item for item in status.missing_files if not item.startswith("denoise/"))
if speech_missing:
errors.append(
ProviderError(
ErrorCode.STT_MODEL_MISSING,
"missing speech model files: " + ", ".join(status.missing_files),
"missing speech model files: " + ", ".join(speech_missing),
False,
"speech-models",
"model-check",
)
)
if denoise_missing:
errors.append(
ProviderError(
ErrorCode.NOISE_FILTER_MODEL_MISSING,
"missing denoiser model files: " + ", ".join(denoise_missing),
False,
"speech-models",
"model-check",