[本地语音降噪]:完成本地语音链路和噪音过滤,包含降噪模型、本地ASR和实时字幕稳定策略
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from .models import AudioFrame, ErrorCode, ProviderError
|
||||
from .speech_models import denoiser_model_path
|
||||
|
||||
|
||||
class NoopAudioPreprocessor:
|
||||
def load(self) -> None:
|
||||
return None
|
||||
|
||||
def reset(self) -> None:
|
||||
return None
|
||||
|
||||
def process_frame(self, frame: AudioFrame) -> AudioFrame:
|
||||
return frame
|
||||
|
||||
def flush(self) -> list[AudioFrame]:
|
||||
return []
|
||||
|
||||
|
||||
class SherpaOnnxDenoiserPreprocessor:
|
||||
def __init__(
|
||||
self,
|
||||
models_dir: str | Path,
|
||||
*,
|
||||
sherpa_module: Any | None = None,
|
||||
) -> None:
|
||||
self.models_dir = Path(models_dir)
|
||||
self._sherpa = sherpa_module
|
||||
self._denoiser: Any | None = None
|
||||
self.loaded = False
|
||||
|
||||
def load(self) -> None:
|
||||
model = denoiser_model_path(self.models_dir)
|
||||
if not model.exists():
|
||||
raise ProviderError(
|
||||
ErrorCode.NOISE_FILTER_MODEL_MISSING,
|
||||
f"sherpa-onnx denoiser model is missing: {model}",
|
||||
False,
|
||||
"sherpa-onnx-gtcrn",
|
||||
"audio-preprocess",
|
||||
)
|
||||
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.NOISE_FILTER_FAILED,
|
||||
f"sherpa_onnx is not available: {exc}",
|
||||
False,
|
||||
"sherpa-onnx-gtcrn",
|
||||
"audio-preprocess",
|
||||
) from exc
|
||||
try:
|
||||
gtcrn = sherpa_onnx.OfflineSpeechDenoiserGtcrnModelConfig(model=str(model))
|
||||
model_config = sherpa_onnx.OfflineSpeechDenoiserModelConfig(
|
||||
gtcrn=gtcrn,
|
||||
num_threads=1,
|
||||
provider="cpu",
|
||||
)
|
||||
config = sherpa_onnx.OnlineSpeechDenoiserConfig(model=model_config)
|
||||
self._denoiser = sherpa_onnx.OnlineSpeechDenoiser(config)
|
||||
except Exception as exc:
|
||||
raise ProviderError(
|
||||
ErrorCode.NOISE_FILTER_FAILED,
|
||||
f"failed to load sherpa-onnx denoiser: {exc}",
|
||||
False,
|
||||
"sherpa-onnx-gtcrn",
|
||||
"audio-preprocess",
|
||||
) from exc
|
||||
self.loaded = True
|
||||
|
||||
def reset(self) -> None:
|
||||
if self._denoiser is not None and hasattr(self._denoiser, "reset"):
|
||||
self._denoiser.reset()
|
||||
|
||||
def process_frame(self, frame: AudioFrame) -> AudioFrame:
|
||||
if not self.loaded or self._denoiser is None:
|
||||
raise ProviderError(
|
||||
ErrorCode.NOISE_FILTER_FAILED,
|
||||
"sherpa-onnx denoiser is not loaded",
|
||||
False,
|
||||
"sherpa-onnx-gtcrn",
|
||||
"audio-preprocess",
|
||||
)
|
||||
try:
|
||||
import numpy as np
|
||||
|
||||
samples = np.frombuffer(frame.pcm, dtype=np.int16).astype(np.float32) / 32768.0
|
||||
if frame.channels > 1 and samples.size:
|
||||
samples = samples.reshape(-1, frame.channels).mean(axis=1)
|
||||
denoised = self._denoiser.run(samples, frame.sample_rate)
|
||||
output_samples = np.asarray(getattr(denoised, "samples"), dtype=np.float32)
|
||||
output_sample_rate = int(getattr(denoised, "sample_rate", frame.sample_rate))
|
||||
clipped = np.clip(output_samples, -1.0, 1.0)
|
||||
pcm = (clipped * 32767.0).astype(np.int16).tobytes()
|
||||
except ProviderError:
|
||||
raise
|
||||
except Exception as exc:
|
||||
raise ProviderError(
|
||||
ErrorCode.NOISE_FILTER_FAILED,
|
||||
f"sherpa-onnx denoiser failed: {exc}",
|
||||
True,
|
||||
"sherpa-onnx-gtcrn",
|
||||
"audio-preprocess",
|
||||
) from exc
|
||||
metadata = dict(frame.metadata)
|
||||
metadata["denoised"] = True
|
||||
metadata["noise_filter_provider"] = "sherpa_onnx_gtcrn"
|
||||
return AudioFrame(
|
||||
pcm=pcm,
|
||||
sample_rate=output_sample_rate,
|
||||
channels=1,
|
||||
timestamp_ms=frame.timestamp_ms,
|
||||
frame_id=frame.frame_id,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
def flush(self) -> list[AudioFrame]:
|
||||
if self._denoiser is None or not hasattr(self._denoiser, "flush"):
|
||||
return []
|
||||
try:
|
||||
import numpy as np
|
||||
|
||||
denoised = self._denoiser.flush()
|
||||
samples = np.asarray(getattr(denoised, "samples"), dtype=np.float32)
|
||||
if samples.size == 0:
|
||||
return []
|
||||
sample_rate = int(getattr(denoised, "sample_rate", 16000))
|
||||
pcm = (np.clip(samples, -1.0, 1.0) * 32767.0).astype(np.int16).tobytes()
|
||||
except Exception as exc:
|
||||
raise ProviderError(
|
||||
ErrorCode.NOISE_FILTER_FAILED,
|
||||
f"sherpa-onnx denoiser flush failed: {exc}",
|
||||
True,
|
||||
"sherpa-onnx-gtcrn",
|
||||
"audio-preprocess",
|
||||
) from exc
|
||||
return [
|
||||
AudioFrame(
|
||||
pcm=pcm,
|
||||
sample_rate=sample_rate,
|
||||
channels=1,
|
||||
timestamp_ms=0,
|
||||
frame_id=0,
|
||||
metadata={
|
||||
"denoised": True,
|
||||
"noise_filter_provider": "sherpa_onnx_gtcrn",
|
||||
"flush": True,
|
||||
},
|
||||
)
|
||||
]
|
||||
Reference in New Issue
Block a user