1.本地模型

选用shepa-onnx,它是Apache 2.0 协议的离线语音推理部署框架,仅做模型推理、不负责模型训练,底层基于 ONNX Runtime 运行 ONNX 格式语音模型,核心解决各类语音模型跨端离线部署难题。

2.核心特点

  • 纯本地离线
音频全设备本地运算,不上传云端,隐私性强、无网络延迟。
  • 跨全平台
Windows/macOS/Linux、Android/iOS、鸿蒙、树莓派、RK3588 等嵌入式、浏览器 Wasm、服务器;支持 RKNN、QNN、昇腾等 NPU 硬件加速。
  • 多语言开发接口
C++、Python、Java、Go、Rust、C#、Kotlin、Swift、Dart 等十几种绑定,适配移动端、后端、嵌入式开发。
  • 轻量化无重型依赖
不需要 PyTorch/TensorFlow,仅依赖 ONNX Runtime,包体小、启动速度快。

3. 覆盖完整语音能力

  • ASR 语音识别:流式 / 非流式,支持 SenseVoice、Whisper、Paraformer、Zipformer 等主流模型
  • TTS 语音合成:Matcha-TTS、Piper、VITS 等
  • 配套工具:Silero VAD 语音端点检测、说话人分离 / 声纹识别、关键词唤醒、语音降噪、语种识别、自动加标点

4. 典型使用场景

本地实时字幕、手机离线语音输入法、智能音箱 / 车载离线语音、录音笔转写、浏览器语音工具、嵌入式工控语音交互、隐私优先的本地语音助手。

5.RK3588上部署

5.1 在rk3588上安装 sherpa-onnx

pip install sherpa-onnx

5.2 下载预转换的onnx模型

从 GitHub Releases 下载预转换的 ONNX 模型,这一步可以在虚拟机上下载,然后再把需要的文件转到板子上

# SenseVoice 模型(中英日韩粤,229MB int8版本),模型来源和SenseVoice相同
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17.tar.bz2

tar xvf sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17.tar.bz2

解压后内容如下:

我们在rk3588上只需要用到 model.int8.onnx、tokens.txt、test_wavs

把这几个文件依次转到板子上

adb push model.int8.onnx /userdata/aidemo/sensevoice
adb push tokens.txt /userdata/aidemo/sensevoice
adb push test_wavs/ /userdata/aidemo/sensevoice

5.3 离线语音识别测试

新建test.py,内容如下

import sherpa_onnx
import wave
import numpy as np
#创建识别器(指定本地路径,零联网)
recognizer = sherpa_onnx.OfflineRecognizer.from_sense_voice(
    model="./model.int8.onnx",
    tokens="./tokens.txt",
    num_threads=4,
    use_itn=True,    #启用逆文本正则化(数字、日期等转换)
    language="zh",   #语言:zh/en/ja/ko/yue/auto
)
#读取音频
def read_wave(wav_path):
    with wave.open(wav_path, 'rb') as wf:
        sample_rate = wf.getframerate()
        data = wf.readframes(wf.getnframes())
        samples = np.frombuffer(data, dtype=np.int16).astype(np.float32) / 32768.0
    return samples, sample_rate

samples, sample_rate = read_wave("./test_wavs/zh.wav")
stream = recognizer.create_stream()
stream.accept_waveform(sample_rate, samples)
recognizer.decode_stream(stream)

print(stream.result.text)

文件结构如下:

测试结果如下:

5.4 麦克风实时本地识别

5.4.1简单识别

新建real_time.py,内容如下:

import sherpa_onnx
import subprocess
import struct
--------------------------
#1. 加载SenseVoice离线识别器(旧版唯一可用 from_sense_voice)
--------------------------
recognizer = sherpa_onnx.OfflineRecognizer.from_sense_voice(
    model="./model.int8.onnx",
    tokens="./tokens.txt",
    num_threads=4,
    language="zh",
    use_itn=True
)
#音频参数
CHUNK = 960
SAMPLE_RATE = 16000
#缓存完整PCM音频,攒够一段送给离线SenseVoice
audio_buffer = bytes()
#每1.5秒做一次识别,可按需调整
SEGMENT_SAMPLES = int(SAMPLE_RATE * 1.5)
SEGMENT_BYTES = SEGMENT_SAMPLES * 2  # S16_LE 每个采样2字节
--------------------------
#2. arecord 原生ALSA录音
--------------------------
arecord_cmd = [
    "arecord",
    "-r", str(SAMPLE_RATE),
    "-c", "1",
    "-f", "S16_LE",
    "-D", "default",
    "-t", "raw"
]
proc = subprocess.Popen(
    arecord_cmd,
    stdout=subprocess.PIPE,
    stderr=subprocess.DEVNULL
)
print("实时语音识别启动,说话等待1.5秒出结果,Ctrl+C退出")
try:
    while True:
        data = proc.stdout.read(CHUNK)
        if not data:
            break
        audio_buffer += data
        # 缓存达到设定长度,执行一次识别
        if len(audio_buffer) >= SEGMENT_BYTES:
            # 把bytes转float32数组(sherpa标准输入格式)
            samples = struct.unpack("<" + "h"*(len(audio_buffer)//2), audio_buffer)
            samples = [s / 32768.0 for s in samples]
        
            stream = recognizer.create_stream()
            stream.accept_waveform(SAMPLE_RATE, samples)
            recognizer.decode_stream(stream)
            text = stream.result.text.strip()
        
            if text:
                print("实时识别:", text)
        
            # 清空缓存,接收下一段语音
            audio_buffer = b""

except KeyboardInterrupt:
    print("\n程序退出")
finally:
    proc.terminate()
    proc.wait()

此时不能区分静音和人声,麦克风数据都会送入模型进行识别,所以不说话时也会输出“我。”等

5.4.2 支持vad 的麦克风实时本地语音识别

VAD = Voice Activity Detection(语音活动检测)​

用来判断“现在有没有人在说话”的技术。

此处给出 python环境对应的requirement.txt方便后续复现环境

aiofiles==23.2.1
annotated-doc==0.0.4
annotated-types==0.7.0
anyio==4.14.0
blinker==1.9.0
brotli==1.2.0
certifi==2026.5.20
click==8.4.1
coloredlogs==15.0.1
contourpy==1.3.2
cycler==0.12.1
exceptiongroup==1.3.1
fastapi==0.137.1
ffmpy==1.0.0
filelock==3.29.4
Flask==3.1.3
flatbuffers==25.12.19
fonttools==4.63.0
fsspec==2026.6.0
gradio==6.18.0
gradio_client==2.5.0
groovy==0.1.2
h11==0.16.0
hf-gradio==0.4.1
hf-xet==1.5.1
httpcore==1.0.9
httpx==0.28.1
huggingface_hub==1.19.0
humanfriendly==10.0
idna==3.18
importlib_resources==6.5.2
itsdangerous==2.2.0
Jinja2==3.1.6
kiwisolver==1.5.0
markdown-it-py==4.2.0
MarkupSafe==2.1.5
matplotlib==3.10.9
mdurl==0.1.2
mpmath==1.3.0
numpy==2.2.6
onnxruntime==1.23.2
orjson==3.11.9
packaging==26.2
pandas==2.3.3
pillow==10.4.0
protobuf==3.18.0
psutil==5.8.0
pydantic==2.13.4
pydantic_core==2.46.4
pydub==0.25.1
Pygments==2.20.0
pyparsing==3.3.2
python-dateutil==2.9.0.post0
python-multipart==0.0.32
pytz==2026.2
PyYAML==6.0.3
rich==15.0.0
ruamel.yaml==0.17.31
ruamel.yaml.clib==0.2.7
ruff==0.15.17
safehttpx==0.1.7
semantic-version==2.10.0
shellingham==1.5.4
sherpa-onnx==1.13.3
sherpa-onnx-core==1.13.3
six==1.16.0
smbus==1.1
starlette==1.3.1
sympy==1.14.0
tomlkit==0.12.0
tqdm==4.68.2
typer==0.25.1
typing-inspection==0.4.2
typing_extensions==4.15.0
tzdata==2026.2
urllib3==2.7.0
uvicorn==0.49.0
websockets==12.0
Werkzeug==3.1.8

下载vad模型文件,后续实时识别需要用到它进行语音活动检测

wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/silero_vad.onnx

文件结构如下:

venv_sherpa_backup 是我用requirement.txt新建的虚拟环境,大家可以不管

测试代码

#!/usr/bin/env python3
import subprocess
import numpy as np
import sherpa_onnx

# ===================== 1、路径参数,按需修改 =====================
SILERO_VAD_MODEL = "./silero_vad.onnx"
SENSEVOICE_MODEL = "./model.int8.onnx"
SENSEVOICE_TOKENS = "./tokens.txt"
SAMPLE_RATE = 16000
CHUNK_SAMPLES = 960    # 每次读取采样点数
CHUNK_BYTES = CHUNK_SAMPLES * 2  # S16_LE 2字节每采样

def create_vad():
    config = sherpa_onnx.VadModelConfig()
    config.silero_vad.model = SILERO_VAD_MODEL
    # 人声静音核心参数
    config.silero_vad.threshold = 0.13
    config.silero_vad.min_speech_duration = 0.2
    config.silero_vad.min_silence_duration = 0.32
    config.silero_vad.max_speech_duration = 5.0
    config.sample_rate = SAMPLE_RATE
    # 缓存30秒音频,官方必填第二个参数
    vad = sherpa_onnx.VoiceActivityDetector(config, buffer_size_in_seconds=30)
    print("[LOG] VAD 初始化完成")
    return vad

def create_recognizer():
    recognizer = sherpa_onnx.OfflineRecognizer.from_sense_voice(
        model=SENSEVOICE_MODEL,
        tokens=SENSEVOICE_TOKENS,
        num_threads=4,
        language="zh",
        use_itn=True
    )
    print("[LOG] SenseVoice 识别器初始化完成")
    return recognizer

def main():
    vad = create_vad()
    recognizer = create_recognizer()

    # arecord 录音进程
    arecord = subprocess.Popen(
        [
            "arecord",
            "-r", str(SAMPLE_RATE),
            "-c", "1",
            "-f", "S16_LE",
            "-D", "default",
            "-t", "raw"
        ],
        stdout=subprocess.PIPE,
        stderr=subprocess.DEVNULL
    )

    print("SenseVoice+SileroVAD 实时识别启动,Ctrl+C 退出\n")
    printed = False
    frame_cnt = 0

    try:
        while True:
            raw = arecord.stdout.read(CHUNK_BYTES)
            frame_cnt += 1
            if not raw:
                print(f"[WARN-{frame_cnt}] 录音读取为空,麦克风异常!")
                break

            # S16_LE bytes -> float32 [-1,1]
            pcm_int16 = np.frombuffer(raw, dtype=np.int16)
            pcm_float = pcm_int16.astype(np.float32) / 32768.0
            #frame_noise_level = np.mean(np.abs(pcm_float))
            #print(f"当前帧噪音幅度:{frame_noise_level:.4f}")
            vad.accept_waveform(pcm_float)

            # 打印人声开始提示(和官方示例逻辑一致)
            if vad.is_speech_detected() and not printed:
                print("[INFO] 检测到人声开始")
                printed = True
            if not vad.is_speech_detected():
                printed = False

            # =====官方标准队列读取,无None问题=====
            while not vad.empty():
                seg = vad.front
                speech_audio = seg.samples
                dur = len(speech_audio) / SAMPLE_RATE
                print(f"\n[SPEECH] 语音片段时长:{dur:.3f}s")

                # 送入SenseVoice识别
                stream = recognizer.create_stream()
                stream.accept_waveform(SAMPLE_RATE, speech_audio)
                recognizer.decode_stream(stream)
                raw_text = stream.result.text.strip()
                print(f"[ASR 原始] |{raw_text}|")

                # 过滤无意义语气词
                filter_set = {"嗯", "啊", "哦", "呃", "。", ",", "、", "!", "?", " "}
                if raw_text and not all(c in filter_set for c in raw_text):
                    print(f"=====识别结果:{raw_text}=====\n")

                vad.pop()  # 弹出已处理片段

    except KeyboardInterrupt:
        print("\n程序正常退出")
    finally:
        arecord.terminate()
        arecord.wait()

if __name__ == "__main__":
    main()

测试结果

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐