添加热词解析
parent
71f8d572c0
commit
f4747f3539
|
|
@ -6,6 +6,9 @@ MODEL_DIR=models
|
|||
# FunASR 流式 ASR 与 VAD 模型。
|
||||
FUNASR_ASR_MODEL=paraformer-zh-streaming
|
||||
FUNASR_VAD_MODEL=fsmn-vad
|
||||
# Contextual 模型在 2-pass 最终识别时应用本会话热词,和流式首遍共享 GPU。
|
||||
FUNASR_HOTWORD_ASR_MODEL=paraformer-zh-contextual
|
||||
FUNASR_HOTWORD_ASR_MODEL_REVISION=v2.0.4
|
||||
FUNASR_DEVICE=cuda:0
|
||||
FUNASR_VAD_DEVICE=cpu
|
||||
# FunASR 原生 WebSocket 服务使用的 CPU 工作线程数。
|
||||
|
|
@ -17,7 +20,7 @@ FUNASR_VAD_PARAGRAPH_MAX_END_SILENCE_MS=5000
|
|||
# 提高此阈值会过滤更多环境噪声,也可能漏掉较轻的语音。
|
||||
FUNASR_VAD_SPEECH_NOISE_THRES=0.6
|
||||
|
||||
# FunASR 原生 WebSocket 流式识别参数。
|
||||
# FunASR Runtime WebSocket 流式与热词最终识别参数。
|
||||
FUNASR_CHUNK_SIZE=0,10,5
|
||||
FUNASR_CHUNK_INTERVAL=10
|
||||
FUNASR_ENCODER_LOOK_BACK=4
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
前端和后端分别启动。后端会托管以下服务:
|
||||
|
||||
- FunASR 原生在线 WebSocket 服务,使用本地流式 ASR 和 FSMN-VAD 模型。浏览器桥接层发送 `mode=online`、分块和回看参数,并在输入结束时发送 `is_speaking=false`,让引擎刷新最后的识别结果。
|
||||
- FunASR WebSocket 服务使用本地流式 ASR 和 FSMN-VAD。未填写热词时发送 `mode=online`;填写热词时发送 `mode=2pass`,流式模型负责实时预览,Contextual Paraformer 在每段语音结束后生成带热词偏置的最终结果。输入结束时发送 `is_speaking=false`,让引擎刷新剩余音频。
|
||||
- CAM++ 辅助服务,为已完成的语音段分配稳定的说话人标签。
|
||||
- 浏览器协议桥接服务,将腾讯演示页面原有的消息格式转换为 FunASR WebSocket 协议。VAD 切分由 FunASR 处理,桥接层不会再运行另一套 ASR/VAD 切分流程。
|
||||
|
||||
|
|
@ -13,10 +13,11 @@
|
|||
将模型放入 `models/`,或在 `.env` 中设置 `MODEL_DIR`。默认模型目录如下:
|
||||
|
||||
- `models/iic/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-online`
|
||||
- `models/iic/speech_paraformer-large-contextual_asr_nat-zh-cn-16k-common-vocab8404`
|
||||
- `models/damo/speech_fsmn_vad_zh-cn-16k-common-pytorch`
|
||||
- `models/iic/speech_campplus_sv_zh-cn_16k-common`,也可使用清单中配置的 `damo/` 模型目录
|
||||
|
||||
若模型目录名称不同,可在 `.env` 中将 `FUNASR_ASR_MODEL` 和 `FUNASR_VAD_MODEL` 设为本地路径。CAM++ 默认按 `model_manifest.json` 查找;需要自定义目录时设置 `CAM_MODEL_PATH`。后端会在对外提供 WebSocket 服务前检查 ASR、VAD 和 CAM++ 三个必需模型。
|
||||
若模型目录名称不同,可在 `.env` 中将 `FUNASR_ASR_MODEL`、`FUNASR_HOTWORD_ASR_MODEL` 和 `FUNASR_VAD_MODEL` 设为本地路径。CAM++ 默认按 `model_manifest.json` 查找;需要自定义目录时设置 `CAM_MODEL_PATH`。后端会在开放 WebSocket 前检查流式 ASR、Contextual 热词 ASR、VAD 和 CAM++ 模型。
|
||||
|
||||
## 安装与启动
|
||||
|
||||
|
|
@ -37,6 +38,6 @@ python frontend\run_frontend.py
|
|||
|
||||
默认前端端口为 `8080`,浏览器后端端口为 `8082`,CAM++ HTTP 端口为 `8010`,仅本机可访问的 FunASR WebSocket 端口为 `10095`。需要调整时,在 `.env` 中同步修改 `FRONTEND_PORT`、`WEB_PORT`、`AUXILIARY_SERVICE_URL`、`FUNASR_NATIVE_WS_HOST` 和 `FUNASR_NATIVE_WS_PORT`。前端进程通过 `BACKEND_INTERNAL_URL` 访问后端;未设置时使用 `http://127.0.0.1:${WEB_PORT}`。
|
||||
|
||||
`FUNASR_DEVICE` 和 `FUNASR_VAD_DEVICE` 分别控制 ASR 与 VAD 的运行设备。若 CAM++ 不与 ASR 共用 GPU,可将 `AUXILIARY_DEVICE` 设为 `cpu`。`FUNASR_CHUNK_SIZE` 和 `FUNASR_CHUNK_INTERVAL` 控制 FunASR 原生分块协议;默认值 `[0,10,5]` 和间隔 `10` 会按 600 毫秒一组发送当前音频块。
|
||||
`FUNASR_DEVICE` 控制流式 ASR 和 Contextual 最终解码模型的设备,默认使用 `cuda:0`;`FUNASR_VAD_DEVICE` 默认使用 CPU。启动器先加载 ASR,再启动 CAM++,优先为识别模型分配显存。若 CAM++ 不与 ASR 共用 GPU,可将 `AUXILIARY_DEVICE` 设为 `cpu`。热词以逗号、顿号或分号分隔,开始会话时传入;流式预览不应用热词,最终结果由 Contextual 模型解码。`FUNASR_CHUNK_SIZE` 和 `FUNASR_CHUNK_INTERVAL` 控制 FunASR 分块协议;默认值 `[0,10,5]` 和间隔 `10` 会按 600 毫秒一组发送当前音频块。
|
||||
|
||||
实时文件输入支持原始 PCM16 或 16 kHz 单声道 PCM WAV。每个完成的语音段都会计算说话人标签;语音过短或接近静音时,CAM++ 仍可能将说话人标为未知。
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ if (-not (Test-Path .env)) { Copy-Item .env.example .env }
|
|||
python scripts/download_models.py --funasr-runtime
|
||||
~~~
|
||||
|
||||
`--funasr-runtime` 会下载流式 Paraformer、FSMN-VAD、CAM++ 和标点模型。若要下载 `model_manifest.json` 中列出的所有可选模型,请运行 `python scripts/download_models.py`。
|
||||
`--funasr-runtime` 会下载流式 Paraformer、Contextual 热词模型、FSMN-VAD、CAM++ 和标点模型。若要下载 `model_manifest.json` 中列出的全部模型资源,请运行 `python scripts/download_models.py`。
|
||||
|
||||
## 启动
|
||||
|
||||
|
|
@ -26,3 +26,5 @@ python frontend/run_frontend.py
|
|||
后端负责启动 FunASR 原生 WebSocket 服务、CAM++ 服务和浏览器协议桥接服务。前端提供腾讯演示页面,并将 `/ws` 和 `/api/stop` 请求转发给后端。模型路径、设备、VAD 静音时长、说话人聚类参数和端口均可在 `.env` 中配置。
|
||||
|
||||
模型目录和运行细节见 [FunASR 使用说明](FUNASR_README.md)。
|
||||
|
||||
测试热词时,在前端左侧“识别配置”的热词输入框中输入多个词,用逗号、顿号或分号分隔,然后开始识别。服务保留 FunASR 流式首遍预览,并在每段语音结束后用 Contextual Paraformer 做 2-pass 最终解码;最终识别模型与流式模型默认优先使用 `cuda:0`。热词在当前会话启动时生效,识别过程中修改输入不会影响当前会话,需停止后重新开始。运行 `python scripts/download_models.py` 会同时下载流式模型、Contextual 热词模型和配置的辅助模型。
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import ssl
|
|||
import os
|
||||
import wave
|
||||
import functools
|
||||
import re
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from scipy.spatial.distance import cosine
|
||||
|
||||
|
|
@ -45,6 +46,18 @@ def _positive_env_int(name: str, default: int) -> int:
|
|||
return value
|
||||
|
||||
|
||||
def normalize_contextual_hotwords(value):
|
||||
"""将会话热词拆成 Contextual Paraformer 接受的空格分隔文本。"""
|
||||
if isinstance(value, (list, tuple)):
|
||||
source = ",".join(str(item) for item in value if item is not None)
|
||||
elif isinstance(value, str):
|
||||
source = value
|
||||
else:
|
||||
return ""
|
||||
terms = re.split(r"[,\uff0c\u3001;\uff1b\r\n]+", source)
|
||||
return " ".join(dict.fromkeys(term.strip() for term in terms if term.strip()))
|
||||
|
||||
|
||||
# 显式设置该值后,将关闭 FunASR 按静音时长动态调整阈值的机制,便于测试。
|
||||
VAD_MAX_END_SILENCE_MS = _positive_env_int("FUNASR_VAD_MAX_END_SILENCE_MS", 800)
|
||||
VAD_PARAGRAPH_MAX_END_SILENCE_MS = _positive_env_int(
|
||||
|
|
@ -163,7 +176,8 @@ parser.add_argument(
|
|||
)
|
||||
parser.add_argument("--concurrent_vad", type=int, default=4, help="Max concurrent VAD generate() calls.")
|
||||
parser.add_argument("--concurrent_asr_online", type=int, default=4, help="Max concurrent streaming ASR generate() calls.")
|
||||
parser.add_argument("--concurrent_asr_offline", type=int, default=2, help="Max concurrent offline ASR generate() calls.")
|
||||
# Contextual Paraformer 将本轮热词暂存在模型对象上,串行离线调用以隔离不同会话热词。
|
||||
parser.add_argument("--concurrent_asr_offline", type=int, default=1, help="Max concurrent offline ASR generate() calls.")
|
||||
parser.add_argument("--concurrent_punc", type=int, default=1, help="Max concurrent punctuation generate() calls.")
|
||||
parser.add_argument("--concurrent_sv", type=int, default=1, help="Max concurrent speaker verification generate() calls.")
|
||||
parser.add_argument(
|
||||
|
|
@ -576,10 +590,15 @@ async def ws_serve(websocket, path=None):
|
|||
]
|
||||
|
||||
if "hotwords" in messagejson:
|
||||
hotword_data = messagejson["hotwords"]
|
||||
hotword_data = normalize_contextual_hotwords(messagejson["hotwords"])
|
||||
if hotword_data and model_asr is None:
|
||||
record_error("热词解码需要启用 FunASR Contextual Paraformer 最终识别模型")
|
||||
if hotword_data:
|
||||
# 普通 Paraformer 流式模型不具备上下文热词解码,热词只传给最终解码器。
|
||||
websocket.status_dict_asr["hotword"] = hotword_data
|
||||
websocket.status_dict_asr_online["hotword"] = hotword_data
|
||||
print(f"热词已更新: {hotword_data}")
|
||||
else:
|
||||
websocket.status_dict_asr.pop("hotword", None)
|
||||
print(f"会话热词已更新: {hotword_data}")
|
||||
|
||||
if "mode" in messagejson:
|
||||
requested_mode = messagejson["mode"]
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import asyncio
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
|
@ -50,6 +51,21 @@ AUXILIARY_KEY = web.AppKey("auxiliary_service", AuxiliaryModelService)
|
|||
SESSION_REGISTRY_KEY = web.AppKey("sessions", dict)
|
||||
|
||||
|
||||
def normalize_hotwords(value: Any) -> str:
|
||||
"""去重并规范化浏览器输入,供 Contextual Paraformer 按空格解析词条。"""
|
||||
if isinstance(value, list):
|
||||
source = ",".join(str(item) for item in value if item is not None)
|
||||
elif isinstance(value, str):
|
||||
source = value
|
||||
else:
|
||||
return ""
|
||||
|
||||
terms = re.split(r"[,\uff0c\u3001;\uff1b\r\n]+", source)
|
||||
# 保持用户输入顺序,并去掉重复项,避免同一热词被重复传给解码端。
|
||||
unique_terms = list(dict.fromkeys(term.strip() for term in terms if term.strip()))
|
||||
return " ".join(unique_terms)
|
||||
|
||||
|
||||
class IncrementalWavDecoder:
|
||||
"""读取流式 PCM WAV 文件头,并逐段返回 16 kHz 单声道 PCM 负载。"""
|
||||
|
||||
|
|
@ -353,11 +369,19 @@ class BrowserSession:
|
|||
self.align_turn_audio_to_vad(message)
|
||||
continue
|
||||
text = str(message.get("text") or "")
|
||||
native_mode = str(message.get("mode") or "")
|
||||
# 2-pass 在线文本只是预览,离线 Contextual ASR 的文本才是热词定稿。
|
||||
offline_final = native_mode in {"2pass-offline", "offline"}
|
||||
if text:
|
||||
# FunASR 在线模式会为每个分块发送新解码出的文本。
|
||||
if offline_final:
|
||||
self.turn_text = text
|
||||
else:
|
||||
# 在线模式逐块发送新增文本;2-pass 在线结果只用于临时预览。
|
||||
self.turn_text += text
|
||||
if message.get("is_final"):
|
||||
final_text = self.turn_text.strip()
|
||||
final_text = (
|
||||
text.strip() if offline_final else self.turn_text.strip()
|
||||
)
|
||||
audio = bytes(self.turn_audio)
|
||||
start_ms = self.turn_start_ms
|
||||
end_ms = start_ms + len(audio) / PCM_BYTES_PER_MS
|
||||
|
|
@ -550,7 +574,7 @@ async def websocket_handler(request: web.Request) -> web.WebSocketResponse:
|
|||
await session.emit({"type": "voice_id", "voice_id": voice_id})
|
||||
await session.emit({"type": "start"})
|
||||
|
||||
# 这是 FunASR 原生 WSS 消息格式;之后每 60 毫秒发送一帧 PCM。
|
||||
# FunASR 的 Contextual Paraformer 在 2-pass 离线定稿阶段应用会话热词。
|
||||
async with websocket_connect(
|
||||
NATIVE_WS_URL,
|
||||
subprotocols=["binary"],
|
||||
|
|
@ -558,9 +582,7 @@ async def websocket_handler(request: web.Request) -> web.WebSocketResponse:
|
|||
close_timeout=3,
|
||||
max_size=None,
|
||||
) as native_ws:
|
||||
await native_ws.send(
|
||||
json.dumps(
|
||||
{
|
||||
native_start = {
|
||||
"mode": "online",
|
||||
"chunk_size": list(CHUNK_SIZE),
|
||||
"chunk_interval": CHUNK_INTERVAL,
|
||||
|
|
@ -574,10 +596,13 @@ async def websocket_handler(request: web.Request) -> web.WebSocketResponse:
|
|||
"audio_fs": SAMPLE_RATE,
|
||||
"wav_name": voice_id,
|
||||
"is_speaking": True,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
)
|
||||
}
|
||||
# Contextual 模型仅在 2-pass 最终解码阶段使用热词;普通在线预览仍走流式模型。
|
||||
hotwords = normalize_hotwords(start.get("hotwords"))
|
||||
if hotwords:
|
||||
native_start["mode"] = "2pass"
|
||||
native_start["hotwords"] = hotwords
|
||||
await native_ws.send(json.dumps(native_start, ensure_ascii=False))
|
||||
native_reader = asyncio.create_task(session.read_native(native_ws))
|
||||
speaker_worker = asyncio.create_task(session.resolve_speakers())
|
||||
|
||||
|
|
|
|||
|
|
@ -147,6 +147,11 @@ def main() -> None:
|
|||
asr = local_model(
|
||||
os.getenv("FUNASR_ASR_MODEL", "paraformer-zh-streaming"), models_dir, "asr"
|
||||
)
|
||||
hotword_asr = local_model(
|
||||
os.getenv("FUNASR_HOTWORD_ASR_MODEL", "paraformer-zh-contextual"),
|
||||
models_dir,
|
||||
"asr",
|
||||
)
|
||||
vad = local_model(os.getenv("FUNASR_VAD_MODEL", "fsmn-vad"), models_dir, "vad")
|
||||
cam = local_cam_model(models_dir)
|
||||
|
||||
|
|
@ -160,7 +165,11 @@ def main() -> None:
|
|||
ncpu = os.getenv("FUNASR_NCPU", str(os.cpu_count() or 4))
|
||||
web_port = int(os.getenv("WEB_PORT", "8082"))
|
||||
|
||||
print(f"Local models: ASR={asr}; VAD={vad}; CAM++={cam}", flush=True)
|
||||
print(
|
||||
f"Local models: streaming ASR={asr}; hotword final ASR={hotword_asr}; "
|
||||
f"VAD={vad}; CAM++={cam}",
|
||||
flush=True,
|
||||
)
|
||||
print(
|
||||
f"Runtime: ASR device={device}; VAD device={vad_device}; native WS={native_url}",
|
||||
flush=True,
|
||||
|
|
@ -178,6 +187,7 @@ def main() -> None:
|
|||
"AUXILIARY_PRELOAD_KINDS": ",".join(sorted(preload_kinds)),
|
||||
"MODEL_DIR": str(models_dir),
|
||||
"FUNASR_ASR_MODEL": str(asr),
|
||||
"FUNASR_HOTWORD_ASR_MODEL": str(hotword_asr),
|
||||
"FUNASR_VAD_MODEL": str(vad),
|
||||
"FUNASR_DEVICE": device,
|
||||
"FUNASR_VAD_DEVICE": vad_device,
|
||||
|
|
@ -191,13 +201,7 @@ def main() -> None:
|
|||
native = None
|
||||
websocket = None
|
||||
try:
|
||||
auxiliary = subprocess.Popen(
|
||||
[sys.executable, "-m", "backend.auxiliary_server"],
|
||||
cwd=PROJECT_ROOT,
|
||||
env=env,
|
||||
)
|
||||
wait_for_health(f"{aux_url}/health", auxiliary, "speaker_embedding_ready")
|
||||
|
||||
# 先加载 ASR 主模型,避免说话人辅助模型抢占显存导致识别模型无法启动。
|
||||
native_args = [
|
||||
sys.executable,
|
||||
str(PROJECT_ROOT / "backend" / "realtime_websocket" / "funasr_native_wss.py"),
|
||||
|
|
@ -206,7 +210,9 @@ def main() -> None:
|
|||
"--port",
|
||||
str(native_port),
|
||||
"--asr_model",
|
||||
"",
|
||||
str(hotword_asr),
|
||||
"--asr_model_revision",
|
||||
os.getenv("FUNASR_HOTWORD_ASR_MODEL_REVISION", "v2.0.4"),
|
||||
"--asr_model_online",
|
||||
str(asr),
|
||||
"--vad_model",
|
||||
|
|
@ -230,6 +236,13 @@ def main() -> None:
|
|||
probe_host = "127.0.0.1" if native_host in {"0.0.0.0", "::"} else native_host
|
||||
wait_for_tcp(probe_host, native_port, native)
|
||||
|
||||
auxiliary = subprocess.Popen(
|
||||
[sys.executable, "-m", "backend.auxiliary_server"],
|
||||
cwd=PROJECT_ROOT,
|
||||
env=env,
|
||||
)
|
||||
wait_for_health(f"{aux_url}/health", auxiliary, "speaker_embedding_ready")
|
||||
|
||||
websocket = subprocess.Popen(
|
||||
[sys.executable, "-m", "backend.run_funasr_demo", "--no-browser"],
|
||||
cwd=PROJECT_ROOT,
|
||||
|
|
@ -241,8 +254,8 @@ def main() -> None:
|
|||
print(f"FunASR browser backend ready: ws://127.0.0.1:{web_port}/ws", flush=True)
|
||||
while True:
|
||||
for label, process in (
|
||||
("CAM++", auxiliary),
|
||||
("FunASR native WS", native),
|
||||
("CAM++", auxiliary),
|
||||
("browser WS adapter", websocket),
|
||||
):
|
||||
code = process.poll()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// ===== 页面元素 =====
|
||||
const elEngineModel = document.getElementById('engineModel');
|
||||
const elSpeakerDiarization = document.getElementById('speakerDiarization');
|
||||
const elHotwords = document.getElementById('hotwords');
|
||||
const elDiarizationLabel = document.getElementById('diarizationLabel');
|
||||
const elSentenceStrategy = document.getElementById('sentenceStrategy');
|
||||
const elBtnStart = document.getElementById('btnStart');
|
||||
|
|
@ -89,6 +90,12 @@ const DEFAULT_SPEED = {
|
|||
const MAX_SPEED = 3.0;
|
||||
const UNSUPPORTED_STREAMING_EXTENSIONS = new Set(['m4a']);
|
||||
|
||||
// 统一前端热词分隔符并去重,后端再转换为 Contextual Paraformer 的输入格式。
|
||||
function normalizeHotwords(value) {
|
||||
const terms = value.split(/[,,、;;\r\n]+/).map(term => term.trim()).filter(Boolean);
|
||||
return [...new Set(terms)].join(',');
|
||||
}
|
||||
|
||||
const SPEAKER_COLORS = ['#4a7dff', '#52c41a', '#faad14', '#ff4d4f', '#9254de', '#13c2c2'];
|
||||
|
||||
let sentenceMap = {};
|
||||
|
|
@ -470,6 +477,7 @@ async function startRecognition() {
|
|||
|
||||
elBtnStart.disabled = true;
|
||||
elBtnStop.disabled = false;
|
||||
elHotwords.disabled = true;
|
||||
sending = true;
|
||||
|
||||
const currentSession = ++sessionId;
|
||||
|
|
@ -489,6 +497,7 @@ async function startRecognition() {
|
|||
engine_model_type: elEngineModel.value,
|
||||
speaker_diarization: useSpeaker ? 1 : 0,
|
||||
sentence_strategy: parseInt(elSentenceStrategy.value),
|
||||
hotwords: normalizeHotwords(elHotwords.value),
|
||||
source: inputMode,
|
||||
voice_format: voiceFormat,
|
||||
file_name: fileName,
|
||||
|
|
@ -620,6 +629,7 @@ function resetControls() {
|
|||
elBtnStart.disabled = false;
|
||||
}
|
||||
elBtnStop.disabled = true;
|
||||
elHotwords.disabled = false;
|
||||
}
|
||||
|
||||
// ===== 发送音频文件 =====
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>语音识别体验 Demo</title>
|
||||
<link rel="stylesheet" href="style.css?v=210">
|
||||
<link rel="stylesheet" href="style.css?v=212">
|
||||
</head>
|
||||
<body>
|
||||
<div class="layout">
|
||||
|
|
@ -39,6 +39,10 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="hotwords">热词 <span class="hint">逗号分隔,用于最终结果修正</span></label>
|
||||
<input type="text" id="hotwords" placeholder="例如:FunASR,实时语音,说话人分离" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-divider"></div>
|
||||
|
|
@ -106,6 +110,6 @@
|
|||
</main>
|
||||
</div>
|
||||
|
||||
<script src="app.js?v=210"></script>
|
||||
<script src="app.js?v=212"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
{
|
||||
"default_model": "iic/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-online",
|
||||
"hotword_model": "iic/speech_paraformer-large-contextual_asr_nat-zh-cn-16k-common-vocab8404",
|
||||
"models": {
|
||||
"iic/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-online": {
|
||||
"alias": "paraformer-zh-streaming",
|
||||
"directory": "iic/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-online",
|
||||
"description": "FunASR Paraformer streaming ASR used by the realtime backend",
|
||||
"description": "FunASR 流式 Paraformer 语音识别模型,用于实时首遍识别并输出中间结果",
|
||||
"required_files": [
|
||||
"configuration.json",
|
||||
"config.yaml"
|
||||
|
|
@ -18,6 +19,22 @@
|
|||
"*.safetensors"
|
||||
],
|
||||
"min_total_size_bytes": 1000000
|
||||
},
|
||||
"iic/speech_paraformer-large-contextual_asr_nat-zh-cn-16k-common-vocab8404": {
|
||||
"alias": "paraformer-zh-contextual",
|
||||
"directory": "iic/speech_paraformer-large-contextual_asr_nat-zh-cn-16k-common-vocab8404",
|
||||
"description": "FunASR Contextual Paraformer 热词模型,用于结合会话热词生成最终识别结果",
|
||||
"revision": "v2.0.4",
|
||||
"required_files": [
|
||||
"configuration.json",
|
||||
"config.yaml"
|
||||
],
|
||||
"any_files": [
|
||||
"*.pt",
|
||||
"*.bin",
|
||||
"*.safetensors"
|
||||
],
|
||||
"min_total_size_bytes": 1000000
|
||||
}
|
||||
},
|
||||
"auxiliary_models": {
|
||||
|
|
@ -25,7 +42,7 @@
|
|||
"alias": "vad",
|
||||
"directory": "damo/speech_fsmn_vad_zh-cn-16k-common-pytorch",
|
||||
"kind": "vad",
|
||||
"description": "FunASR FSMN VAD",
|
||||
"description": "FSMN 语音活动检测模型,用于检测语音起止并切分语音片段",
|
||||
"revision": "v2.0.2",
|
||||
"required_files": [
|
||||
"configuration.json",
|
||||
|
|
@ -39,7 +56,7 @@
|
|||
"alias": "ct-punc",
|
||||
"directory": "iic/punc_ct-transformer_zh-cn-common-vocab272727-pytorch",
|
||||
"kind": "punctuation",
|
||||
"description": "Optional FunASR CT-Transformer punctuation restoration",
|
||||
"description": "CT-Transformer 标点恢复模型,用于为识别文本补充标点符号(可选)",
|
||||
"required_files": [
|
||||
"configuration.json",
|
||||
"config.yaml"
|
||||
|
|
@ -55,7 +72,7 @@
|
|||
"alias": "diarization",
|
||||
"directory": "iic/speech_campplus_speaker-diarization_common",
|
||||
"kind": "diarization",
|
||||
"description": "CAM++ speaker diarization",
|
||||
"description": "CAM++ 音视频说话人分离扩展包,包含音频检测及人脸关联所需组件",
|
||||
"required_files": [
|
||||
"configuration.json",
|
||||
"config.yaml",
|
||||
|
|
@ -70,7 +87,7 @@
|
|||
"alias": "speaker",
|
||||
"directory": "iic/speech_campplus_sv_zh-cn_16k-common",
|
||||
"kind": "speaker_verification",
|
||||
"description": "Configured CAM++ speaker verification",
|
||||
"description": "CAM++ 声纹特征模型,用于提取说话人向量并进行会话内说话人匹配",
|
||||
"revision": "v2.0.2",
|
||||
"required_files": [
|
||||
"configuration.json",
|
||||
|
|
@ -83,7 +100,7 @@
|
|||
"alias": "realtime-speaker",
|
||||
"directory": "iic/speech_eres2netv2_sv_zh-cn_16k-common",
|
||||
"kind": "realtime_speaker_verification",
|
||||
"description": "Realtime speaker verification",
|
||||
"description": "ERes2NetV2 实时声纹验证模型,用于提取和比对说话人特征",
|
||||
"required_files": [
|
||||
"configuration.json"
|
||||
],
|
||||
|
|
@ -96,7 +113,7 @@
|
|||
"alias": "speaker-damo",
|
||||
"directory": "damo/speech_campplus_sv_zh-cn_16k-common",
|
||||
"kind": "speaker_verification",
|
||||
"description": "CAM++ speaker verification dependency",
|
||||
"description": "CAM++ 声纹验证模型资源,供相关说话人识别配置使用",
|
||||
"required_files": [
|
||||
"configuration.json",
|
||||
"config.yaml",
|
||||
|
|
@ -108,7 +125,7 @@
|
|||
"alias": "speaker-transformer",
|
||||
"directory": "damo/speech_campplus-transformer_scl_zh-cn_16k-common",
|
||||
"kind": "speaker_transformer",
|
||||
"description": "CAM++ Transformer dependency",
|
||||
"description": "CAM++ 说话人聚类的 Transformer/SCL 辅助权重,需与声纹模型配套使用",
|
||||
"required_files": [
|
||||
"configuration.json",
|
||||
"campplus_cn_encoder.pt",
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ def main() -> int:
|
|||
auxiliary_group.add_argument(
|
||||
"--funasr-runtime",
|
||||
action="store_true",
|
||||
help="Download/check the configured ASR model, FSMN-VAD, CAM++, and punctuation",
|
||||
help="下载或检查流式 ASR、Contextual 热词 ASR、FSMN-VAD、CAM++ 和标点模型",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
|
|
@ -195,10 +195,17 @@ def main() -> int:
|
|||
models_dir = models_dir.resolve()
|
||||
cache_dir = args.cache_dir.resolve() if args.cache_dir else None
|
||||
selected_assets: list[tuple[str, dict[str, object]]] = []
|
||||
hotword_model_id = str(manifest.get("hotword_model") or "").strip()
|
||||
if args.funasr_runtime:
|
||||
# 标点模型在运行时可选,但下载后可获得完整的本地输出。
|
||||
asr_id = resolve_model_id(args.model, manifest)
|
||||
selected_assets.append((asr_id, manifest["models"][asr_id]))
|
||||
model_ids = [asr_id]
|
||||
if hotword_model_id:
|
||||
model_ids.append(resolve_model_id(hotword_model_id, manifest))
|
||||
selected_assets.extend(
|
||||
(model_id, manifest["models"][model_id])
|
||||
for model_id in dict.fromkeys(model_ids)
|
||||
)
|
||||
assets = auxiliary_models(manifest)
|
||||
vad_id = next(
|
||||
model_id for model_id, config in assets.items()
|
||||
|
|
@ -220,7 +227,13 @@ def main() -> int:
|
|||
else:
|
||||
if not args.auxiliary_only:
|
||||
model_id = resolve_model_id(args.model, manifest)
|
||||
selected_assets.append((model_id, manifest["models"][model_id]))
|
||||
model_ids = [model_id]
|
||||
if hotword_model_id:
|
||||
model_ids.append(resolve_model_id(hotword_model_id, manifest))
|
||||
selected_assets.extend(
|
||||
(selected_id, manifest["models"][selected_id])
|
||||
for selected_id in dict.fromkeys(model_ids)
|
||||
)
|
||||
if not args.skip_auxiliary:
|
||||
selected_assets.extend(auxiliary_models(manifest).items())
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue