语音识别是语音质检系统的核心引擎,选错方案轻则准确率惨不忍睹,重则成本爆炸。这篇聊聊我们在 CVOICE 项目中踩过的坑,以及最终落地的技术方案。
上一篇讲了 CVOICE 的整体架构,今天把镜头拉近,聚焦语音识别(ASR)这层。这是整个系统最"硬核"的部分——模型怎么选、服务怎么部署、性能怎么调优,每一个决策都直接影响质检效果。
我们团队在这块折腾了将近三个月,试过开源方案、也调研过云服务,最终形成了现在的混合部署架构。分享出来,希望能帮你少走点弯路。
CVOICE 电话录音界面
先搞清楚你的需求
选型之前,必须先回答三个问题:
1. 你的录音是什么语言?
中文场景和英文场景完全是两条技术路线。Whisper 在英文上表现优异,但中文的准确率和速度都不如专门的中文模型。如果你的录音里夹杂着中英混杂(比如"Hello,请问您是张先生吗"),那选型就更复杂了。
2. 你的实时性要求是什么?
离线转写(文件上传后异步处理)和实时转写(边录音边出文字)对架构的要求天差地别。CVOICE 目前做的是离线质检,所以我们的方案是基于文件批处理的。如果你需要实时质检,这篇文章只能参考一半。
3. 你的成本预算是多少?
云服务 ASR 按量计费,1 小时录音大概 1-3 元。听起来不贵?但一天 1000 小时录音就是 1000-3000 元,一个月下来 3-9 万。自建 ASR 服务器一次性投入,但 GPU 服务器的月租也不便宜。
我们的业务量中等(日均 500-800 小时录音),最终选择了自建 + 云服务兜底的混合方案。
主流 ASR 方案对比
我们实际测试过的方案有四个:
| 方案 | 中文准确率 | 速度 | 成本 | 部署难度 | | --- | --- | --- | --- | --- | | 阿里云 ASR | 97%+ | 快 | 高(按量计费) | 低 | | FunASR (SenseVoice) | 95%+ | 快 | 低(自建) | 中 | | Whisper (OpenAI) | 85-90% | 中等 | 低(自建) | 低 | | 讯飞听见 | 96%+ | 快 | 极高 | 低 |
阿里云 ASR 准确率最高,但价格也最贵。我们用它来处理一些对准确率要求极高的场景(比如涉及法律纠纷的录音)。
FunASR 是阿里开源的,其中的 SenseVoiceSmall 模型在中文场景下表现非常接近云服务。500MB 的模型体积,4GB 显存就能跑起来,性价比极高。这是我们的主力方案。
Whisper 是 OpenAI 开源的,英文场景无敌,中文差点意思。我们把它作为备选,处理一些英文录音。
讯飞听见 准确率也不错,但价格劝退,我们只在小范围测试过,没上生产环境。
FunASR 部署实战
FunASR 是我们花精力最多的部分,详细讲讲部署过程。
环境准备
我们用的服务器配置:
-
• GPU 模式:NVIDIA RTX 4090(24GB 显存),CUDA 12.1
-
• CPU 模式:8 核 16G 内存,用于低峰期兜底
Docker 镜像是官方提供的,直接拉就行:
# GPU 版本
docker pull registry.cn-hangzhou.aliyuncs.com/funasr_repo/funasr:funasr-runtime-sdk-gpu-0.2.0
# CPU 版本
docker pull registry.cn-hangzhou.aliyuncs.com/funasr_repo/funasr:funasr-runtime-sdk-cpu-0.4.6
启动服务
GPU 模式的启动脚本:
docker run --gpus all -p 8000:8000 \
-v $(pwd)/models:/workspace/models \
-v $(pwd)/audio:/workspace/audio \
registry.cn-hangzhou.aliyuncs.com/funasr_repo/funasr:funasr-runtime-sdk-gpu-0.2.0
注意 -v 挂载的两个目录:
-
•
models:存放下载好的 SenseVoiceSmall 模型,避免每次启动都重新下载 -
•
audio:临时存放待转写的音频文件
模型下载
第一次启动会自动下载模型,但速度很慢。建议提前手动下载:
from funasr import AutoModel
# 下载 SenseVoiceSmall 模型
model = AutoModel(
model="iic/SenseVoiceSmall",
device="cuda",
disable_update=True
)
# 下载标点恢复模型
punc_model = AutoModel(
model="ct-punc",
device="cuda",
disable_update=True
)
下载好的模型默认在 ~/.cache/modelscope/hub/iic/ 目录,把它拷到挂载的 models 目录就行。
转写服务封装
FunASR 提供的是基础能力,我们需要封装一层 HTTP API,让 CVOICE 的 Agent 能方便调用。
API 设计
from fastapi import FastAPI, File, UploadFile
from funasr import AutoModel
from zhconv import convert
import re
app = FastAPI()
# 加载模型(启动时执行)
model = AutoModel(
model="iic/SenseVoiceSmall",
device="cuda",
ncpu=4,
disable_update=True
)
punc_model = AutoModel(
model="ct-punc",
device="cuda",
disable_update=True
)
@app.post("/transcribe")
async def transcribe(audio: UploadFile = File(...)):
"""音频转写接口"""
# 保存上传的文件
audio_path = f"/tmp/{audio.filename}"
with open(audio_path, "wb") as f:
f.write(await audio.read())
try:
# 1. ASR 转写
result = model.generate(
input=audio_path,
language="auto",
use_itn=True
)
text = result[0]["text"]
language = result[0].get("language", "zh")
duration = result[0].get("duration", 0)
# 2. 繁体转简体
text = convert(text, 'zh-cn')
# 3. 清理标记
text = re.sub(r'<[^>]*>', '', text)
text = re.sub(r'[。!?!?,,;;]{2,}', '。', text)
text = re.sub(r'\s+', ' ', text).strip()
# 4. 标点恢复
if punc_model and text:
punc_result = punc_model.generate(input=text)
text = punc_result[0]["text"]
return {
"code": 0,
"text": text,
"language": language,
"duration": duration,
"confidence": result[0].get("confidence", 0.95)
}
except Exception as e:
return {"code": -1, "error": str(e)}
finally:
# 清理临时文件
if os.path.exists(audio_path):
os.remove(audio_path)
关键处理点
繁简转换:台湾、香港客户的录音经常有繁体字,如果不转换,敏感词匹配会失效。用 zhconv 库一行代码搞定。
标记清理:SenseVoice 输出会带语言标记(如 <|zh|><|NEUTRAL|><|Speech|>),需要正则清理掉。
标点恢复:ASR 输出通常没标点,用 ct-punc 模型恢复后,质检员阅读起来舒服很多。
性能调优
GPU 并发配置
RTX 4090 24GB 显存,我们测试出的最佳并发数是 4:
# 服务配置
SERVICE_CONFIG = {
"funasr-gpu-01": {
"url": "http://gpu-01:8000/transcribe",
"concurrency": 4, # 最大并发
"gpu": True,
"timeout": 300 # 5分钟超时
}
}
并发太高会导致显存 OOM,太低又浪费算力。建议从 2 开始逐步增加,观察显存占用和响应时间。
音频预处理
不是所有录音都能直接丢给 ASR,需要预处理:
import ffmpeg
def preprocess_audio(input_path: str, output_path: str):
"""音频预处理:统一转为 16kHz 单声道 WAV"""
try:
(
ffmpeg
.input(input_path)
.output(output_path,
ar=16000, # 采样率 16kHz
ac=1, # 单声道
acodec='pcm_s16le') # 16bit PCM
.run(quiet=True)
)
return True
except ffmpeg.Error as e:
logger.error(f"音频预处理失败: {e}")
return False
统一转为 16kHz 单声道 WAV,能显著提升识别准确率。FunASR 虽然支持多种格式,但标准输入效果更好。
批量处理优化
对于大量录音,别串行处理,用线程池:
from concurrent.futures import ThreadPoolExecutor, as_completed
def batch_transcribe(audio_files: list, max_workers: int = 4):
"""批量转写"""
results = {}
with ThreadPoolExecutor(max_workers=max_workers) as executor:
# 提交所有任务
future_to_file = {
executor.submit(transcribe_single, file): file
for file in audio_files
}
# 收集结果
for future in as_completed(future_to_file):
file = future_to_file[future]
try:
results[file] = future.result()
except Exception as e:
results[file] = {"error": str(e)}
return results
混合调度策略
我们最终的架构是GPU 主力 + CPU 兜底 + 云服务保底的三层结构:
# 转写服务调度器
class TranscriptionScheduler:
def __init__(self):
self.services = [
{"name": "gpu-01", "url": "http://gpu-01:8000", "type": "gpu", "load": 0},
{"name": "gpu-02", "url": "http://gpu-02:8000", "type": "gpu", "load": 0},
{"name": "cpu-01", "url": "http://cpu-01:8000", "type": "cpu", "load": 0},
]
self.aliyun_client = AliyunASRClient() # 云服务客户端
def schedule(self, audio_file: str, priority: str = "normal") -> dict:
"""调度转写请求"""
# 高优先级任务直接走云服务
if priority == "high":
return self.aliyun_client.transcribe(audio_file)
# 普通任务:优先 GPU,满了转 CPU
gpu_services = [s for s in self.services if s["type"] == "gpu" and s["load"] < 4]
if gpu_services:
service = min(gpu_services, key=lambda x: x["load"])
return self._call_service(service, audio_file)
# GPU 满了,看 CPU
cpu_services = [s for s in self.services if s["type"] == "cpu" and s["load"] < 2]
if cpu_services:
service = cpu_services[0]
return self._call_service(service, audio_file)
# 都满了, fallback 到云服务
return self.aliyun_client.transcribe(audio_file)
调度逻辑很简单:
-
• 普通录音走自建服务(GPU 优先,满了转 CPU)
-
• 高优先级录音(如涉及投诉、法律风险)直接走阿里云 ASR
-
• 高峰期自建服务满载时,自动降级到云服务
这样既保证了成本可控,又能在关键时刻不掉链子。
常见问题排查
显存 OOM
现象:服务突然崩溃,日志显示 CUDA out of memory。
解决:降低并发数,或者改用 CPU 模式处理部分任务。也可以尝试用 torch.cuda.empty_cache() 定期清理显存。
识别结果为空
现象:返回的 text 是空字符串。
排查:检查音频格式是否正确,FunASR 对采样率、声道数敏感。用 ffprobe 查看音频信息,必要时用 ffmpeg 转换。
语速太快识别率低
现象:客服说话像机关枪,识别结果乱码。
解决:这是 ASR 的通病。可以尝试在预处理阶段调整音频速度(atempo=0.8),或者换用专门针对快语速优化的模型。
中英混杂识别差
现象:录音里中英文夹杂,识别结果中英文都错乱。
解决:SenseVoice 支持语言自动检测,但中英混杂场景确实容易出问题。建议按段落切分,分别指定语言参数处理。
写在最后
语音识别是语音质检系统的底座,底座不稳,上面的敏感词匹配、话术评分都是空中楼阁。
我们在 CVOICE 项目中的经验是:
下一篇我们聊聊敏感词匹配引擎的实现,包括 Trie 树优化、语义匹配、以及如何处理"变体词"(比如把"诈骗"写成"诈.骗"或"诈 骗")。
CVOICE 质检规则配置
全文约 3400 字