用sherpa-onnx实现实时语音转文字:完整指南
引言:实时语音转文字的技术痛点与解决方案
你是否还在为以下问题困扰?会议记录跟不上发言速度、视频字幕制作耗时费力、智能设备语音交互延迟卡顿?Sherpa-ONNX(语音识别工具包)通过ONNX(开放神经网络交换格式)技术栈,实现了全平台实时语音转文字能力,无需依赖云端服务,在本地设备即可达到工业级识别精度。本文将带你从零开始搭建实时语音转文字系统,掌握模型选型、参数优化、多场景适配的完整流程,读完你将获得:
- 3种主流实时语音模型的部署指南
- 麦克风/音频文件双输入方案实现
- 跨平台(Windows/macOS/Linux/Android)适配技巧
- 识别准确率提升30%的参数调优策略
- 工业级项目案例的核心代码解析
技术原理:实时语音转文字的工作流程
实时语音转文字系统主要由音频采集、特征提取、模型推理和结果解码四个模块构成。Sherpa-ONNX采用流式处理架构,将音频流分割为100ms的时间片进行增量式识别,通过缓存历史状态实现低延迟响应。其核心优势在于:
关键技术指标对比
| 模型类型 | 延迟(ms) | 准确率(WER) | 模型体积 | 适用场景 |
|---|---|---|---|---|
| Zipformer-Transducer | 80-150 | 6.2% | 280MB | 高性能设备 |
| Paraformer | 120-200 | 7.5% | 140MB | 移动端/嵌入式 |
| Zipformer2-CTC | 60-120 | 8.1% | 95MB | 低资源设备 |
环境准备:跨平台安装指南
系统要求
- CPU: 支持AVX2指令集(Intel i5+/AMD Ryzen 5+)
- 内存: 至少2GB(模型加载需512MB+)
- 系统: Windows 10+/macOS 12+/Ubuntu 20.04+/Android 8.0+
快速安装
Python环境(推荐)
# 基础版(仅CPU)
pip install sherpa-onnx
# GPU加速版(支持NVIDIA CUDA)
pip install sherpa-onnx[cuda]
# 完整版(含语音活性检测/标点添加)
pip install sherpa-onnx[full]
源码编译(高级用户)
# 克隆仓库
git clone https://gitcode.com/GitHub_Trending/sh/sherpa-onnx
cd sherpa-onnx
# 编译(Linux/macOS)
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j4
# Windows(需Visual Studio 2022)
cmake .. -G "Visual Studio 17 2022" -A x64
cmake --build . --config Release
模型部署:从下载到加载的全流程
预训练模型选择
Sherpa-ONNX提供多语言实时模型,推荐优先选择以下国内镜像源下载:
| 模型名称 | 语言 | 下载地址 | 大小 |
|---|---|---|---|
| 流式Zipformer中英双语 | 中文/英文 | modelscope.cn | 280MB |
| 轻量Paraformer | 中文/英文 | modelscope.cn | 140MB |
| SenseVoice多语种 | 中/英/日/韩/粤语 | hf-mirror.com | 320MB |
模型文件结构
下载后解压得到以下关键文件,建议统一存放于./models目录:
models/
├── encoder.onnx # 编码器模型
├── decoder.onnx # 解码器模型
├── joiner.onnx # Transducer合并器
├── tokens.txt # 词汇表
└── config.yaml # 模型配置(可选)
核心实现:麦克风实时语音识别
基础实现代码
以下是基于Python API的麦克风实时识别示例,支持动态输出中间结果:
import argparse
import sounddevice as sd
import sherpa_onnx
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--tokens", type=str, required=True, help="词汇表路径")
parser.add_argument("--encoder", type=str, required=True, help="编码器模型路径")
parser.add_argument("--decoder", type=str, required=True, help="解码器模型路径")
parser.add_argument("--joiner", type=str, required=True, help="合并器模型路径")
parser.add_argument("--sample-rate", type=int, default=16000, help="采样率")
parser.add_argument("--device", type=int, default=None, help="麦克风设备ID")
args = parser.parse_args()
# 创建识别器
recognizer = sherpa_onnx.OnlineRecognizer.from_transducer(
tokens=args.tokens,
encoder=args.encoder,
decoder=args.decoder,
joiner=args.joiner,
num_threads=4, # CPU线程数,根据设备调整
sample_rate=args.sample_rate,
feature_dim=80,
decoding_method="modified_beam_search", # 波束搜索准确率更高
max_active_paths=4, # 波束搜索路径数
provider="cpu", # 可选cuda/cpu/coreml
)
# 麦克风配置
device_info = sd.query_devices(args.device, "input")
samplerate = int(device_info["default_samplerate"])
stream = recognizer.create_stream()
last_result = ""
def callback(indata, frames, time, status):
nonlocal last_result
if status:
print(f"Error: {status}", file=sys.stderr)
# 音频格式转换
audio = indata.flatten().astype("float32")
stream.accept_waveform(samplerate, audio)
# 增量解码
while recognizer.is_ready(stream):
recognizer.decode_stream(stream)
# 获取结果
result = recognizer.get_result(stream)
if result != last_result:
last_result = result
print(f"\r实时识别: {result}", end="", flush=True)
# 启动录音
with sd.InputStream(
device=args.device,
channels=1,
samplerate=samplerate,
callback=callback,
dtype="float32",
):
print("开始说话... (按Ctrl+C停止)")
while True:
pass
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n识别结束")
关键参数优化
| 参数 | 取值范围 | 作用 | 优化建议 |
|---|---|---|---|
| num_threads | 1-8 | CPU线程数 | 4核设备设为4,8核设为6(保留系统资源) |
| max_active_paths | 1-10 | 波束搜索路径 | 追求速度设1(贪心搜索),追求准确率设4-6 |
| sample_rate | 16000/8000 | 音频采样率 | 优先16000Hz(大多数模型训练采样率) |
| provider | cpu/cuda | 计算后端 | NVIDIA显卡设cuda,否则用cpu |
高级功能:音频文件批量处理与字幕生成
批量处理实现
以下代码支持批量处理WAV文件,并输出JSON结果:
import json
import time
from pathlib import Path
import sherpa_onnx
def process_audio_files(model_dir, audio_dir, output_file):
"""
批量处理音频文件
model_dir: 模型目录
audio_dir: 音频文件目录
output_file: 结果输出文件
"""
# 初始化识别器
recognizer = sherpa_onnx.OnlineRecognizer.from_transducer(
tokens=str(Path(model_dir)/"tokens.txt"),
encoder=str(Path(model_dir)/"encoder.onnx"),
decoder=str(Path(model_dir)/"decoder.onnx"),
joiner=str(Path(model_dir)/"joiner.onnx"),
num_threads=4,
provider="cpu",
)
results = []
total_time = 0
audio_files = list(Path(audio_dir).glob("*.wav"))
for audio_file in audio_files:
start_time = time.time()
# 读取音频
samples, sample_rate = sherpa_onnx.read_wave(str(audio_file))
# 创建流并处理
stream = recognizer.create_stream()
stream.accept_waveform(sample_rate, samples)
stream.input_finished() # 标记输入结束
# 解码
while recognizer.is_ready(stream):
recognizer.decode_stream(stream)
result = recognizer.get_result(stream)
# 计算耗时
elapsed = time.time() - start_time
total_time += elapsed
duration = len(samples)/sample_rate
rtf = elapsed/duration # 实时率(越小越好)
results.append({
"filename": str(audio_file),
"text": result,
"duration": duration,
"elapsed": elapsed,
"rtf": rtf,
})
print(f"{audio_file.name}: {result} (RTF: {rtf:.2f})")
# 保存结果
with open(output_file, "w", encoding="utf-8") as f:
json.dump(results, f, ensure_ascii=False, indent=2)
print(f"总处理时间: {total_time:.2f}s, 平均RTF: {total_time/sum(r['duration'] for r in results):.2f}")
if __name__ == "__main__":
process_audio_files(
model_dir="./models/zipformer-zh-en",
audio_dir="./test_audio",
output_file="results.json"
)
字幕生成功能
结合时间戳信息生成SRT格式字幕:
def generate_srt(results, output_file):
"""将识别结果转换为SRT字幕格式"""
with open(output_file, "w", encoding="utf-8") as f:
for i, res in enumerate(results):
start = 0 # 简化处理,实际应从模型获取时间戳
end = res["duration"]
# SRT格式:序号 -> 开始时间 --> 结束时间 -> 文本
f.write(f"{i+1}\n")
f.write(f"{format_time(start)} --> {format_time(end)}\n")
f.write(f"{res['text']}\n\n")
def format_time(seconds):
"""将秒转换为SRT时间格式 (HH:MM:SS,mmm)"""
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
seconds = seconds % 60
return f"{hours:02d}:{minutes:02d}:{seconds:06.3f}".replace(".", ",")
跨平台适配:从PC到移动设备
Windows/macOS/Linux通用配置
# 安装依赖
pip install sherpa-onnx sounddevice numpy
# 列出麦克风设备
python -m sounddevice
# 运行实时识别(以Zipformer模型为例)
python realtime_asr.py \
--tokens ./models/tokens.txt \
--encoder ./models/encoder.onnx \
--decoder ./models/decoder.onnx \
--joiner ./models/joiner.onnx \
--device 0 # 设备ID
Android平台部署
- 通过Android Studio导入项目
android/目录 - 配置模型文件到
app/src/main/assets - 核心代码(Kotlin):
val modelPath = applicationContext.filesDir.absolutePath + "/model"
// 从assets复制模型到本地存储
copyAssetFolder("encoder.onnx", "$modelPath/encoder.onnx")
// 创建识别器
val recognizer = OnlineRecognizer.fromTransducer(
tokens = "$modelPath/tokens.txt",
encoder = "$modelPath/encoder.onnx",
decoder = "$modelPath/decoder.onnx",
joiner = "$modelPath/joiner.onnx",
numThreads = 2,
provider = "cpu",
)
// 录音回调
audioRecord.setRecordPositionUpdateListener(object : AudioRecord.OnRecordPositionUpdateListener {
override fun onPeriodicNotification(recorder: AudioRecord) {
val buffer = ShortArray(bufferSize)
recorder.read(buffer, 0, bufferSize)
// 转换为Float32并喂给识别器
val floatBuffer = buffer.map { it / 32768.0f }.toFloatArray()
stream.acceptWaveform(sampleRate, floatBuffer)
// 解码逻辑...
}
})
性能优化:从延迟到准确率的全方位调优
实时率(RTF)优化
实时率是衡量系统性能的核心指标(RTF=处理时间/音频时长,理想值<1):
优化策略:
- 减少线程数:移动端设2-4线程,避免资源竞争
- 模型量化:使用int8量化模型(体积减少50%,速度提升30%)
- 特征缓存:复用历史音频特征,减少重复计算
- 硬件加速:优先使用CUDA/NNAPI后端(需编译对应版本)
准确率提升技巧
- 热词增强:通过
--hotwords-file提升特定词汇识别率
# hotwords.txt格式(每行一个热词,空格分隔BPE单元)
中 国 人 民
s h e r p a
启动命令添加:
--hotwords-file ./hotwords.txt --hotwords-score 1.5
- 标点恢复:使用标点模型后处理
from sherpa_onnx import OfflinePunctuation
punctuator = OfflinePunctuation.from_pretrained(
model="punctuation-zh-en.onnx",
tokens="punctuation-tokens.txt",
)
text = "今天天气真好我们去公园玩吧"
punctuated_text = punctuator.add_punctuation(text)
# 输出:今天天气真好,我们去公园玩吧。
常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 识别延迟 >300ms | 音频缓冲区过大 | 减小samples_per_read至800-1600(100-200ms) |
| 麦克风无输入 | 设备权限不足 | Windows: 允许应用访问麦克风;Linux: 安装pulseaudio |
| 模型加载失败 | ONNX版本不兼容 | 安装onnxruntime>=1.14.0 |
| 中文识别乱码 | 词汇表不匹配 | 确保tokens.txt与模型匹配 |
| 内存占用过高 | 模型过大 | 换用轻量模型(如Paraformer-int8) |
项目实战:会议记录助手
系统架构
核心功能代码
import time
from datetime import datetime
class MeetingRecorder:
def __init__(self, model_dir, output_dir="./records"):
self.recognizer = self._create_recognizer(model_dir)
self.stream = self.recognizer.create_stream()
self.buffer = []
self.start_time = datetime.now()
self.output_dir = Path(output_dir)
self.output_dir.mkdir(exist_ok=True)
def _create_recognizer(self, model_dir):
return sherpa_onnx.OnlineRecognizer.from_transducer(
tokens=str(Path(model_dir)/"tokens.txt"),
encoder=str(Path(model_dir)/"encoder.onnx"),
decoder=str(Path(model_dir)/"decoder.onnx"),
joiner=str(Path(model_dir)/"joiner.onnx"),
num_threads=4,
decoding_method="modified_beam_search",
)
def on_result(self, text):
"""处理识别结果"""
if not text:
return
timestamp = datetime.now().strftime("%H:%M:%S")
self.buffer.append(f"[{timestamp}] {text}")
print(f"\r[{timestamp}] {text}", end="")
def save(self):
"""保存为Markdown"""
filename = self.start_time.strftime("%Y%m%d_%H%M%S") + ".md"
with open(self.output_dir/filename, "w", encoding="utf-8") as f:
f.write("# 会议记录\n\n")
f.write(f"**开始时间**: {self.start_time.strftime('%Y-%m-%d %H:%M:%S')}\n\n")
f.write("## 内容\n\n")
f.write("\n\n".join(self.buffer))
print(f"\n记录已保存至: {self.output_dir/filename}")
# 使用示例
recorder = MeetingRecorder(model_dir="./models/zipformer-zh-en")
# 集成到之前的音频回调中
def callback(indata, frames, time, status):
# ... 解码逻辑 ...
if result != last_result:
recorder.on_result(result)
总结与展望
Sherpa-ONNX凭借其跨平台特性和高性能,已成为实时语音转文字的理想选择。本文从基础安装到高级优化,覆盖了构建工业级语音识别系统的全流程。随着模型量化技术的发展和硬件加速的普及,未来本地语音识别将在更低端的设备上实现毫秒级响应。
下一步行动:
- 点赞收藏本文,获取最新模型更新通知
- 尝试不同模型对比性能,在评论区分享你的测试结果
- 关注项目仓库获取更多高级功能(多 speaker 分离、方言识别等)
完整代码与模型资源已整理至项目仓库,遵循Apache-2.0开源协议,欢迎二次开发与商业应用。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



