云计算百科
云计算领域专业知识百科平台

#移远产品体验官 Quectel Pi M2 实战:离线 TTS 从 11 秒压到 0.4 秒

环境:Quectel Pi M2(瑞芯微 RK3576,4×A72@2.2GHz + 4×A53@2.0GHz,6 TOPS NPU,3.8 GB RAM 无 swap)/ Debian 13 (trixie) aarch64,内核 6.1.118-rt36
目标:把「完全离线中文语音助手」的语音播报延迟压到能自然对话的程度
关键词:sherpa-onnx、VITS、Kokoro、Matcha、Vocos、RKNN、RK3576、RTF、首声延迟、NPU 声码器

前言

这是 Quectel Pi M2 离线语音助手系列实践的一篇。前面的链路已经打通:

  • 第一篇:板载麦克风录音 + 蓝牙音箱播放
  • 第二篇:中文语音唤醒(KWS)
  • 第三篇:本地大模型 RKLLM 在 NPU 上跑通(0.5B 26~29 tok/s)

本篇是第四篇:离线 TTS 低延迟优化。

说明:ASR(Paraformer)+ 意图工具层 + 整机闭环 那篇还没发,放到后面单独写。本篇只借用它的时延数据做整机预算,不展开讲那部分的实现。

链路是:

唤醒(KWS) → VAD 录音 → ASR(Paraformer) → LLM(RKLLM/NPU) → TTS → 播放

唤醒、ASR、LLM 都很快,唯独 TTS 一开口就露馅:用户说完话,板子要沉默 8~11 秒才出声,一句 10 秒的回答要合成 17 秒。

这篇文章记录了我把这块硬骨头啃下来的全过程:四条技术路线、每一版的实测数字、以及最后落地的方案。先说结论,方便你判断要不要往下读:

方案计算单元端到端 RTF首声延迟结论
VITS aishell3 int8(整句合成) CPU 1.7 ~ 1.9 8 ~ 11 s 基线,不可用
VITS + 切句流水线 CPU 1.55 ~ 1.75 2.5 ~ 3.5 s 能用,但块间有静音空档
Kokoro int8 CPU 3.6 ~ 4.0 更慢 排除
原厂 RKNN TTS / MMS-TTS NPU — — flow 算子无法有效加速,排除
Matcha + Vocos(RKNN) NPU 0.099 ~ 0.104 0.37 ~ 0.43 s 最终采用

最终效果:整机链路 ASR 0.18 s → LLM 0.60 s → TTS 首声 0.41 s,RTF 0.14 ~ 0.18,合成速度约为实时播放的 6~7 倍,首声比原来的 VITS 快了 20 倍以上。


一、先把指标定义清楚

聊 TTS 性能如果只盯「一句话合成要几秒」,很容易被带偏。本文统一用三个指标:

指标定义为什么重要
首声延迟 文本就绪 → 第一个音频帧进入播放器的耗时 用户体感「卡不卡」的唯一指标
RTF 合成耗时 ÷ 音频时长 < 1 才能跟上实时播放;> 1 一定积压
块间空档 分段合成时,上一段播完到下一段出声的间隔 决定「听起来像不像机器人喘气」

关键认知:VITS / Matcha 这类非流式声学模型,必须把一整段文本合成完才能出声。所以对整句合成来说:

首声延迟 ≈ 整段音频时长 × RTF

RTF = 1.7 意味着「说 10 秒的话,要先算 17 秒」。这就是 8~11 秒沉默的数学解释 —— 不是哪一行代码慢,是整段合成 + 算力不足叠加的结果。


二、基线:sherpa-onnx VITS aishell3(CPU)

最初用的就是 sherpa-onnx 官方现成方案:vits-zh-aishell3.int8.onnx,CPU 推理。

import sherpa_onnx

cfg = sherpa_onnx.OfflineTtsConfig(
model=sherpa_onnx.OfflineTtsModelConfig(
vits=sherpa_onnx.OfflineTtsVitsModelConfig(
model="vits-aishell3.int8.onnx",
lexicon="lexicon.txt",
tokens="tokens.txt",
noise_scale=0.667, noise_scale_w=0.8, length_scale=1.0,
),
provider="cpu", num_threads=8,
),
rule_fsts="date.fst,number.fst,phone.fst,new_heteronym.fst",
max_num_sentences=1,
)
tts = sherpa_onnx.OfflineTts(cfg)

实测(RK3576,8 线程,22050 Hz 输出):

文本长度音频时长合成耗时RTF首声
短句(约 10 字) 2.5 s 4.2 ~ 4.8 s 1.7 ~ 1.9 4 ~ 5 s
两三句回答(约 40 字) 9 ~ 11 s 16 ~ 20 s 1.7 ~ 1.9 8 ~ 11 s

我先把能调的参数都调了一遍:

  • num_threads:4 / 6 / 8 线程,RTF 从 1.9 只降到 1.7,8 线程以后不再有收益(A72 只有 4 个,超线程调度反而互相抢);
  • length_scale:只是改变语速,不改变 RTF;
  • 锁频:CPU governor 本来就是 performance,没有余量可挖。

结论:CPU 侧已经到顶,瓶颈是模型结构和算力,调参救不了。


三、四条路线逐个实测

3.1 路线 A:Kokoro int8 —— 名气大,但在 RK3576 上更慢

Kokoro 在 x86/手机端口碑很好,sherpa-onnx 1.10.46 也已支持,于是下载了 int8 版本(约 140 MB)实测:

方案线程RTF首声
VITS aishell3 int8 8 1.55 ~ 1.75 8 ~ 11 s
Kokoro int8 8 3.6 ~ 4.0 更慢

RTF 直接翻倍,排除。

原因不复杂:Kokoro 是为 x86/NEON 优化过的模型,参数量和算子结构在 A72 上并不占优;「在别的平台快」不等于「在 RK3576 上快」。

教训:端侧选型必须回到目标板子上实测,社区口碑只能当参考。

3.2 路线 B:原厂 RKNN TTS(MMS-TTS / rknn_model_zoo)—— 方向对,但算子不配合

顺着「把 TTS 挪到 6 TOPS NPU 上」的思路,去翻了 RK 原厂的 rknn_model_zoo 和社区里的 RKNN TTS 方案,结论偏悲观:

  • VITS 结构的 flow(归一化流)算子在 RKNN 上无法有效加速。VITS 的耗时大头是 flow 里的多次耦合变换,这类逐帧、小算子的结构在 NPU 上会被拆成大量碎片调度,只有声码器(HiFi-GAN)那一段能吃到加速,整体存在天花板。
  • 现成可直接用的中文 RKNN TTS 模型极少:多数第三方模型只支持英文,或者需要自己导出 ONNX、改动态 shape、再用匹配版本的 rknn-toolkit2 转换,版本一错就加载失败。
  • MMS-TTS(VITS 结构)同样受 flow 限制,排除。
  • 这次调研没有白做,它帮我锁定了正确的方向:要绕开 flow,选「非 flow 的声学模型 + 可全 NPU 加速的声码器」,也就是下面的 Matcha + Vocos。

    3.3 路线 C:切句流水线(还是 VITS,纯工程优化)

    在换模型之前,先做了一个零成本的工程优化:把长文本切成小句,逐句合成、边合成边播放。

    def split_sentences(text, first=10, hard=18):
    """按标点切小句后贪心打包成合成单元。
    首块压到约 first 字,因为 VITS 非流式,首块越短出声越快;
    后续块放宽到 hard 字,反正它们在播放期间后台合成。"""

    out, buf = [], ""
    for c in _split_clauses(text):
    limit = hard if out else first
    if buf and len(buf) + len(c) > limit:
    out.append(buf); buf, limit = "", hard
    while len(c) > limit:
    k = _safe_cut(c, limit)
    out.append(c[:k]); c, limit = c[k:], hard
    buf += c
    if buf:
    out.append(buf)
    return out

    配合一个「后台合成线程 + 主线程顺序播放」的 PipelinedSpeaker:后台不断把句子合成成 wav,主线程按序 aplay。

    效果:

    指标整句合成切句流水线
    首声 8 ~ 11 s 2.5 ~ 3.5 s
    RTF 1.7 ~ 1.9 1.55 ~ 1.75(不变)
    块间空档 无 有,约 0.2 ~ 0.8 s

    首声降下来了,但 RTF 仍大于 1,意味着只要回答稍长,播放就会「追不上」合成,中间开始出现可听见的静音空档。这条路只能救急,不能当终点。

    切句里有个细节值得抄走:_safe_cut 在切点前会回退,避免把「2026年10月21日」这种数字串劈成两半,也避免把英文单词切开。

    3.4 路线 D:Matcha + Vocos(RKNN,最终方案)

    架构

    rkvoice-stream 0.2.0 提供了一套面向 RK3576 的 Matcha + Vocos 后端,数据流是:

    文本 → 文本前端(CPU, 查表/音素化) → tokens
    → Matcha RKNN(NPU) → mel 频谱
    → Vocos RKNN(NPU) → 线性谱
    → ISTFT(CPU) → PCM 16 kHz

    和 VITS 最大的区别:

    • Matcha 是「条件流匹配」声学模型,推理只需 1 步 ODE,没有 VITS 那种多次 flow 变换;
    • Vocos 是纯卷积声码器,整段可静态编译成 RKNN,全部落到 NPU;
    • 两者都导出成固定 shape,避免动态 shape 在 RKNN 上的额外开销。

    板上的两个模型:

    模型文件大小编译时固定 shape单次耗时
    Matcha 声学模型 matcha-s64.rknn 55.5 MB seq_len=80, x_len=64 → 599 帧 mel(≈9.58 s) 293.6 ms
    Vocos 声码器 vocos-16khz-600.rknn 28.2 MB 600 帧输入,输出 mag/x/y [1,513,600] 54.7 ms

    单次推理计时(rknn-toolkit-lite2 2.3.2 + librknnrt 2.3.2,NPU 950 MHz):

    matcha-s64 : 293.6 ms (599 帧 ≈ 9.58 s 音频)
    vocos-600 : 54.7 ms
    ISTFT (CPU) : ~50 ms

    说明:加载模型时那句 Query dynamic range failed … static shape RKNN model 是正常警告,静态 shape 模型本来就没有动态量化范围,忽略即可。

    端到端实测:

    指标数值
    RTF 0.099 ~ 0.104(约每秒音频 0.1 秒合成)
    首声 0.37 ~ 0.43 s
    相对 VITS 提升 约 15 ~ 17 倍
    第一个坑:尾部「静音 + 孤立杂音」

    固定窗口模型有个副作用:推理时按固定帧数输出,拿到音频后要按估算的 mel 帧数裁剪。而纯中文的帧数估计偏大约 30%~40%,多出来的部分会被声码器渲染成「一段静音 + 末尾一小块杂音」。

    一开始我是用板载 Paraformer 回读合成音频来验音质的,原样输出是:

    你好我是本地的的语音助手三
    今天天气不错出去走走吧嘿

    末尾的「三」「嘿」不是文本里的字,而是固定窗口多估帧产生的残响。解决办法是按能量分段,只保留最后一个足够长的语音段:

    def trim_tail(samples, sr, thr=0.02, win=0.10, keep=0.15):
    """剪掉尾部静音与孤立残响。"""
    n = int(win * sr)
    if len(samples) < 4 * n:
    return samples
    frames = len(samples) // n
    rms = np.sqrt((samples[:frames * n].reshape(frames, n) ** 2).mean(axis=1))
    loud = rms > thr
    runs, start = [], None
    for i, v in enumerate(loud):
    if v and start is None:
    start = i
    elif not v and start is not None:
    runs.append((start, i)); start = None
    if start is not None:
    runs.append((start, frames))
    long_runs = [r for r in runs if r[1] – r[0] >= 4] # ≥0.4 s 才算语音
    if not long_runs:
    return samples
    end = min(len(samples), long_runs[–1][1] * n + int(keep * sr))
    return samples[:end]

    踩坑记录:第一版阈值设了 0.01,结果剪不掉 —— 因为那段杂音的 RMS 是 0.108,反而高于阈值。改成「100 ms 窗 + 能量分段 + 只保留最后一个 ≥0.4 s 的语音段」才彻底干净。

    回读验证(同一个 Paraformer):

    内存共三千八百九十三兆
    可用二千三百四十兆

    第二个坑:别关掉切句

    刚上 Matcha 时我以为「合成这么快,干脆整段一次性合成」,于是把首块/块长都设成 1000 字。结果反而更差:

    切句策略首声合成耗时音频时长
    整段(first=chunk=1000) 1.15 s 更长 11.41 s(估多)
    按句(first=24, chunk=40) 0.41 s 更短 正常

    原因还是那句话:非流式模型的首声 = 首块合成时间。首块越短,出声越快;Matcha 的 RTF 只有 0.1,24 个字的首块合成约 0.4 秒,正好卡在体感「即时应答」的区间。

    3.5 四条路线横向汇总

    路线首声RTF音质结论
    VITS 整句 8 ~ 11 s 1.7 ~ 1.9 好 不可用
    Kokoro int8 更差 3.6 ~ 4.0 一般 排除
    原厂 RKNN TTS 未落地 — — flow 算子受限,排除
    VITS + 切句 2.5 ~ 3.5 s 1.55 ~ 1.75 好 有块间空档,仅救急
    Matcha + Vocos 0.37 ~ 0.43 s 0.10 好(ASR 回读可懂) 采用

    四、落地时绕不过的两件事

    4.1 Python 版本:系统只有 3.13,而官方 RKNN wheel 只到 cp312

    板端系统 Python 是 3.13.5,而且:

    • 没有 pip;
    • apt 源是空的(/var/lib/apt/lists 里只有 auxfiles),装不了包;
    • 没有 gcc/make,源码编译也走不通;
    • sudo 需要密码。

    而 rknn-toolkit-lite2 官方 wheel 只有 cp37 ~ cp312,且内部是 cp312 编译的 .so,不是纯 Python 包,换版本必炸。系统 Python 又不敢动(一堆系统工具依赖它)。

    最后的解法是装一个完全隔离的 CPython 3.12:

    # python-build-standalone 的 install_only 包,解压即用,不碰系统 Python
    # cpython-3.12.15+20261020-aarch64-unknown-linux-gnu-install_only.tar.gz(约 53 MB)
    mkdir -p /userdata/rkllm/py312
    tar -xzf cpython-3.12.15+20261020-aarch64-unknown-linux-gnu-install_only.tar.gz \\
    -C /userdata/rkllm/py312 –strip-components=1

    P=/userdata/rkllm/py312/bin/python3.12
    $P -m pip install sherpa-onnx==1.10.46 numpy soundfile pyyaml rknn-toolkit-lite2==2.3.2

    关键点:

    • librknnrt.so 是 2.3.2、驱动 0.9.8,所以 rknn-toolkit-lite2 也必须锁 2.3.2,三个版本严格对齐;
    • 整个环境装在 /userdata,与系统 Python 完全隔离,不需要 root、不需要 Docker。

    4.2 espeak-ng:没有 root 也要中英混说

    Matcha 的中文走词典(lexicon.txt),英文走 espeak-ng 音素化。板子上没有 root 装 deb,但 deb 本质上就是个 tar 包:

    # 解包(不需要安装)
    dpkg-deb -x libespeak-ng1_1.52.0+dfsg-5build1_arm64.deb /userdata/rkllm/espeak
    dpkg-deb -x libpcaudio0_1.3-1build1_arm64.deb /userdata/rkllm/espeak
    dpkg-deb -x libsonic0_0.2.0-13build2_arm64.deb /userdata/rkllm/espeak

    # 把 .so 拷到一个固定目录
    mkdir -p /userdata/rkllm/lib
    # libespeak-ng.so.1 / libpcaudio.so.0 / libsonic.so.0

    然后不要指望 LD_LIBRARY_PATH(子进程/不同加载时机容易失效),直接在进程启动时按绝对路径预加载:

    _ESPEAK_LIB = "/userdata/rkllm/lib"
    for _so in ("libpcaudio.so.0", "libsonic.so.0", "libespeak-ng.so.1"):
    try:
    ctypes.CDLL(os.path.join(_ESPEAK_LIB, _so), mode=ctypes.RTLD_GLOBAL)
    except OSError:
    pass

    RTLD_GLOBAL 让后续 CDLL("libespeak-ng.so.1")(rkvoice-stream 内部按 SONAME 加载)直接命中已加载的库。

    验证:

    Initialize rc=22050
    SetVoice(en-us) rc=0
    hello -> həlˈoʊ
    Python -> pˈaɪθən

    中英混说实测 RTF 0.071,中英切换正常。

    踩坑:libespeak-ng-libespeak1 是个 2974 字节的过渡空包,里面只有一个 libespeak.so.1 符号链接,装上没有任何用。要装 libespeak-ng1(本文用的 1.52.0)。


    五、最终技术路线

    5.1 整体架构

    ┌──────────── CPU ────────────┐ ┌──────── NPU ────────┐
    麦克风 → KWS(小智小智) → VAD → Paraformer ASR → RKLLM(Qwen2.5-0.5B) → 回复文本
    │
    ┌────────────────────────┘
    ▼
    切句(首块24字/后续40字) → 后台合成线程
    │
    Matcha RKNN ──▶ Vocos RKNN ──▶ ISTFT(CPU)
    │
    trim_tail 剪尾
    │
    aplay 顺序播放

    5.2 关键代码:把 Matcha 适配成 sherpa 风格的 TTS

    上层编排(切句、流水线播放、写 wav)本来是照着 sherpa-onnx 的 generate() 接口写的,所以只需要一个薄适配层,不用改整机代码:

    class MatchaTTS:
    def __init__(self, model_dir="/userdata/rkllm/rksv"):
    sys.path.insert(0, "/userdata/rkllm/rksv/site")
    os.environ.setdefault("TTS_MODEL_DIR", model_dir)
    os.environ.setdefault("MATCHA_MODEL", "models/matcha-s64.rknn")
    os.environ.setdefault("VOCOS_MODEL", "models/vocos-16khz-600.rknn")
    from rkvoice_stream.backends.tts.matcha import create_rknn_tts_backend

    self.engine = create_rknn_tts_backend(model_dir)
    self.engine.load()
    self.sample_rate = 16000

    def generate(self, text, sid=0, speed=1.0):
    audio, _ = self.engine.synthesize(text, speed=speed)
    return _Speech(trim_tail(audio, self.sample_rate), self.sample_rate)

    5.3 切句参数按后端区分

    同一套切句逻辑,VITS 和 Matcha 的最优块长完全不同:

    if args.tts == "matcha":
    first_len, chunk_len = 24, 40 # RTF 0.1,块大一点减少拼接感
    else:
    first_len, chunk_len = 10, 18 # RTF 1.7,首块必须够短

    同时保留 –tts vits 回退开关,换模型出问题时一行参数就能退回 CPU 方案。

    5.4 整机时延预算(0.5B + Matcha,实测)

    整机链路已经在板子上跑通,ASR 与意图工具层的实现细节留到后续那篇,这里只看时间花在哪。

    环节耗时
    ASR(Paraformer,整句) 0.18 s
    LLM(0.5B,短回答,含首 token) 0.60 s
    TTS 首声 0.41 s
    合计(说完话 → 听见第一个字) 约 1.2 s

    长回答(两三句)的 TTS 首声约 0.75 s,因为首块仍然是 24 字。

    冷启动注意:LLM 装载约 1.6 s、Matcha+Vocos 装载约 1.2 s、Paraformer 装载约 8.5 s。做常驻服务时只装一次,不要在每轮对话里重新加载。

    5.5 怎么跑

    # 实时唤醒闭环(默认 Matcha+Vocos)
    /userdata/rkllm/py312/bin/python3.12 -u /userdata/rkllm/assistant.py

    # 不开口,直接喂一句话验证 LLM + TTS + 播放
    /userdata/rkllm/py312/bin/python3.12 -u /userdata/rkllm/assistant.py \\
    –text "现在温度多少"

    # 用录好的 wav 走完整链路(不含唤醒)
    /userdata/rkllm/py312/bin/python3.12 -u /userdata/rkllm/assistant.py \\
    –file /userdata/rkllm/asr_out/cmd_20261017_184654.wav

    # 回退到 CPU VITS
    /userdata/rkllm/py312/bin/python3.12 -u /userdata/rkllm/assistant.py –tts vits


    六、附带解决:让数字「念得像中文」

    换完模型后又冒出一个新问题:TTS 前端会把 ASCII 数字按英文音素化。

    现在是 2026-10-21 22:05
    → "twenty twenty-six, ten, twenty-one, twenty-two oh five"

    时间和系统状态是最常问的,这么念非常出戏。解决办法不是改 TTS,而是在工具层就把数字写成汉字(TTS 只负责念,不负责理解):

    _CN_DIGITS = "零一二三四五六七八九"

    def _cn_year(y):
    return "".join(_CN_DIGITS[int(d)] for d in str(y))

    def cn_date(now):
    return "%s年%s月%s日 星期%s" % (
    _cn_year(now.year), _cn_under100(now.month), _cn_under100(now.day),
    "一二三四五六日"[now.weekday()])

    def cn_time(now):
    # 22:05 → 晚上十点零五分
    ...

    另外三个函数负责其它场景:

    函数用途示例
    cn_int 整数(内存、条数、算式) 3893 → 三千八百九十三
    cn_dec 小数(温度、负载) 45.2 → 四十五点二;3.50 → 三点五
    cn_digits 数字串逐位读(IP) 192.168.0.103 → 一九二点一六八点零点一零三

    工具层改完后,同一条链路实测:

    查询TTS 实际播报
    现在几点 现在是 二零二六年十月二十一日 星期三,晚上十点二十二分
    看看温度 温度 soc 五十点八 度,bigcore 五十二点七 度,npu 四十九点九 度
    内存还剩多少 内存共 三千八百九十三 兆,可用 二千三百四十 兆
    本机 IP 本机 IP 一二七点零点零点一、一九二点一六八点零点一零三
    看看待办 你有 一 条待办:一 明天买牛奶
    20 分钟后提醒我吃药 好的,二十分钟后会提醒你吃药
    12 乘以 8 等于多少 十二 乘以 八 等于 九十六

    再用 Paraformer 回读合成出来的 wav,确认发音是中文而不是英文:

    内存共三千八百九十三兆
    可用二千三百四十兆
    温度 soc 五十点八度
    十二乘以八等于九十六

    首声仍是 0.42 ~ 0.43 s,RTF 0.14 ~ 0.18 —— 中文读法没有带来额外开销。


    七、踩坑清单

    坑现象解法
    matcha.tar.bz2 是截断坏包 14.7 MB,解压报错(真实约 79 MB) 重新完整下载;解压后应有 76 MB 资源目录
    模型路径少一层 models/ 后端静默不加载,_matcha_backend is None,没有任何警告 默认路径写成 models/matcha-s64.rknn
    尾部残响剪不掉 阈值 0.01,杂音 RMS 0.108 100 ms 窗能量分段,保留最后一个 ≥0.4 s 语音段
    关掉切句反而更慢 首声 1.15 s,音频估长到 11.41 s 保持按句切分(24/40)
    libespeak-ng-libespeak1 2974 字节过渡包,装上无效 用 libespeak-ng1 + dpkg-deb -x 解包
    pkill -f assistant.py 把自己的 SSH 会话一起杀了 用 ps -eo pid,args | grep ssistant | grep -v grep 精确找 PID
    系统 Python 3.13 装不了 rknn-toolkit-lite2,无 pip/apt/gcc python-build-standalone CPython 3.12 隔离环境
    Kokoro 看起来很美 实测 RTF 3.6~4.0,比 VITS 还慢 任何模型都必须在目标板上实测

    八、总结与下一步

  • VITS 慢的根因不是线程数,而是非流式 + CPU 算力不足。RTF 1.7 意味着永远追不上实时,调参只能在 1.7~1.9 之间打转。
  • 切句流水线能把首声从 8~11 s 降到 2.5~3.5 s,但 RTF > 1,长回答仍会出现块间空档,只能救急。
  • Kokoro 在 RK3576 上更慢;原厂 RKNN TTS 受 VITS flow 算子限制 —— 「换 NPU」并不自动等于「变快」。
  • Matcha(条件流匹配)+ Vocos(纯卷积声码器)全部静态编译进 RKNN,是这块板子上目前最优解:RTF 0.10、首声 0.4 s、整机 1.2 s 出声。
  • 固定 shape 模型必须配 trim_tail,否则尾部会多出「静音 + 杂音」;同时切句不能省,首块越短首声越快。
  • 中文数字读法要在工具层做,别指望 TTS 前端把 22:05 念成中文。
  • 所有版本要对齐:librknnrt 2.3.2 = rknn-toolkit-lite2 2.3.2 = 模型编译时的 toolkit 2.3.2。
  • 下一步可以做的:

    • 真正的流式 TTS:目前 Matcha 是固定窗口整段出音(一次吐出 4 秒),要更低延迟需要能分块喂声码器的流式实现;
    • 时长估计修正:纯中文的 mel 帧数估计偏大 30%~40%,现在靠 trim_tail 兜底,后续可以按文本长度做线性校正,减少无效计算;
    • 多说话人 / 音色切换:Vocos 本身与音色无关,换 Matcha 说话人 embedding 即可。

    本文为 Quectel Pi M2 离线语音助手系列实践第四篇:TTS 低延迟优化(VITS / Kokoro / Matcha+Vocos 实测对比)。
    系列前作:板载麦克风与蓝牙播放 | 中文语音唤醒(KWS) | RKLLM NPU 推理
    下一篇(计划中):ASR(Paraformer)+ 意图工具层 + 整机闭环

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » #移远产品体验官 Quectel Pi M2 实战:离线 TTS 从 11 秒压到 0.4 秒
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!