Skip to main content

4.1.2 TTS

1. 模块概述

TTS 组件提供统一的本地语音合成接口,负责把文本合成为 WAV/PCM 音频。组件位于 components/model_zoo/tts,提供 C++ API、Python 绑定、文件合成 demo 和流式合成 demo,可被 omni_agent 用作语音回复输出。

支持的后端:

后端语言采样率说明
matcha:zh中文22050HzMatcha-TTS 中文模型。
matcha:en英文22050HzMatcha-TTS 英文模型,依赖 espeak-ng 做英文音素处理。
matcha:zh-en中英混合16000HzMatcha-TTS 中英混合模型,适合机器人对话。
kokoro / kokoro:<voice>中文/英文24000HzKokoro 多音色模型。

典型数据链路:

输入文本 -> 分词/音素处理 -> TTS 后端 -> WAV/PCM -> AudioPlayer 播放

主要目录:

路径说明
components/model_zoo/tts/include/tts_service.hC++ 对外 API。
components/model_zoo/tts/src/backends/matcha/Matcha-TTS 后端。
components/model_zoo/tts/src/backends/kokoro/Kokoro 后端和音色管理。
components/model_zoo/tts/examples/tts_file_demo.cppC++ 文件合成示例。
components/model_zoo/tts/examples/tts_stream_demo.cppC++ 流式合成播放示例。
components/model_zoo/tts/python/Python 包 spacemit_tts 与示例。

演示视频

Matcha-TTS 中英混合演示 GIF

2. 环境准备

前置条件

SDK 源码获取和基础编译环境配置统一参考 2.3-构建编译。完成 SDK 初始化后,回到本文继续执行“构建编译”。

后续命令默认在 spacemit_robot SDK 根目录执行。

构建编译

语音组件使用 target/k3-com260-omni-agent.json 目标配置。系统缺少依赖时先安装:

sudo apt install libsndfile1-dev libfftw3-dev espeak-ng

在 SDK 根目录加载环境后编译 TTS:

source build/envsetup.sh
lunch k3-com260-omni-agent
cd components/model_zoo/tts
mm

流式播放和本文回放命令还依赖 audio 组件和 PortAudio。如当前环境没有 audio_demo,继续编译 audio:

cd ../../multimedia/audio
mm

构建产物会把 tts_file_demotts_stream_demoaudio_demo 安装到 output/staging/bin。需要 Python 示例时,在 SDK 根目录执行:

m -py

注意:m -py / mm -py 是构建 wheel,使用系统 Python 环境执行;不要在 ~/.comm-env 中执行。~/.comm-env 只用于后面的 pip install 和运行 Python 示例。

运行 Python 示例前,先安装虚拟环境依赖,并创建、激活 ~/.comm-env

sudo apt install python3-venv python-is-python3 python3-pip
python3 -m venv ~/.comm-env
source ~/.comm-env/bin/activate

然后二选一安装 Python 包。方式一,直接安装发布包:

python -m pip install spacemit-tts \
--index-url https://git.spacemit.com/api/v4/projects/33/packages/pypi/simple

方式二,回到 SDK 根目录安装本地构建出的 wheel:

python -m pip install output/wheels/components_model_zoo_tts/spacemit_tts-*.whl \
--index-url https://git.spacemit.com/api/v4/projects/33/packages/pypi/simple

安装后可 import spacemit_tts

模型默认放在 ~/.cache/models/tts/ 下,首次运行会按后端检查模型。Matcha 中文还会使用 cppjieba 词典,matcha:zh-en 和 Kokoro 会使用 cpp-pinyin 词典;相关第三方资源通常缓存在 ~/.cache/thirdparty/

3. 示例使用

3.1 文件合成

tts_file_demo -p "今天学Python" -l matcha:zh-en

预期输出包含引擎名称、采样率、音频时长、处理时间、RTF,并保存 output.wav。可以用 audio 组件回放:

audio_demo play ./output.wav -c 2 -d 1

常用后端示例:

tts_file_demo -p "你好世界" -l matcha:zh
tts_file_demo -p "Hello" -l matcha:en
tts_file_demo -p "今天学Python" -l matcha:zh-en
tts_file_demo -p "你好" -l kokoro
tts_file_demo -p "你好" -l kokoro:yunxi

3.2 自定义发音 lexicon

tts_file_demo 支持通过 --lexicon 修正中文多音字或指定英文热词读法。中文 phoneme 使用带声调拼音;matcha:zh-en 的英文热词可用 locale=en 交给 espeak-ng 生成 IPA。

tts_file_demo -p "你好,我是 SpaceMIT 的语音合成模型,很高兴为你服务" \
-l matcha:zh-en \
--lexicon "为你:wei4 ni3:zh,SpaceMIT:space meet:en"

3.3 C++ 流式合成

tts_stream_demo -p "你好" -e matcha:zh-en

该示例会把输入文本切分成句子,边模拟 LLM 输出边合成音频并播放。日志包含 开始流式合成[合成] 第 1 句完成[播放] 重采样播放完成

3.4 Python 多引擎 demo

cd components/model_zoo/tts/python/examples
python tts_file_demo.py

示例会依次创建 Matcha 中文、英文、中英混合和 Kokoro 引擎,输出多个 WAV 文件。流式 Python 示例:

python tts_stream_demo.py -p "你好"

4. 应用开发

本章面向应用开发者,说明如何在自己的 C++ / Python 应用中集成 TTS 组件。完整接口以 components/model_zoo/tts/include/tts_service.h 为准;本节只介绍常用公开接口和典型调用方式。TTS 的 C++ 入口是 SpacemiT::TtsEngine,Python 入口是 spacemit_tts.Engine

4.1 接口说明

TTS 组件的核心入口是 SpacemiT::TtsEngine。应用侧通过该类创建合成引擎、选择后端,并发起阻塞合成、流式合成或双向流合成请求。

4.1.1 常用数据结构

类型说明
TtsConfig引擎配置。常用字段:backendmodel_dirvoice(Kokoro 音色名)、speaker_id(多说话人模型)、sample_ratespeech_ratepitchvolume([0,100])、lexiconnum_threads。可用 Preset(name) 创建预置配置;含链式 withSpeed/withSpeaker/withVolume
BackendType后端枚举。MATCHA_ZH / MATCHA_EN(22050Hz)、MATCHA_ZH_EN(16000Hz,中英混合)、KOKORO(24000Hz,多音色)、COSYVOICE / VITS / PIPER / CUSTOM(保留)。不同后端默认采样率不同,接 audio 播放需重采样
AudioFormat输出音频格式。PCMWAVMP3OGG 为预留。
PronunciationEntry自定义发音条目。字段 word(要纠正的词)、phoneme(拼音或音素)、locale(默认 zh,可选 en 仅对 matcha:zh-en 有效)。
TtsEngineResult合成结果。提供 GetAudioData()(bytes)/ GetAudioFloat() / GetAudioInt16()GetSampleRate()GetDurationMs()GetProcessingTimeMs()GetRTF()IsSuccess()GetMessage()IsEmpty()IsSentenceEnd()SaveToFile(path)
TtsResultCallback流式回调基类。覆写 OnOpen()OnEvent(result)OnComplete()OnError(message)OnClose()。回调由 TTS 引擎内部线程触发,自有状态需自行加锁。
TtsEngine::DuplexStream双向流句柄。提供 SendText(text) 持续推送文本片段、Complete() 表示文本流结束、IsActive() 查询状态;典型用于 LLM 流式输出对接 TTS。

4.1.2 引擎初始化与预设

接口说明参数返回值
TtsEngine(backend, model_dir)通过后端枚举快速构造,模型路径为空时使用默认缓存路径。backendBackendTypemodel_dir:可选模型目录。引擎实例。
TtsEngine(TtsConfig)用完整配置构造,可指定语速、音色、采样率、发音词典等。configTtsConfig 实例。引擎实例。
TtsConfig::Preset(name)静态工厂,返回指定预设的 TtsConfig,可在其上覆写字段。name:matcha_zh / matcha_en / matcha_zh_en / kokoro 等。TtsConfig
TtsConfig::AvailablePresets()静态方法,返回当前可用的预设名称列表。无。std::vector<std::string>
IsInitialized() / GetEngineName() / GetBackendType() / GetSampleRate() / GetNumSpeakers() / GetConfig()状态与配置查询。无。各自类型。

4.1.3 阻塞合成

接口说明参数返回值
Call(text, config=TtsConfig())一次性合成完整文本,返回包含全部音频的结果对象。text:要合成的文本;config:可选覆盖配置。shared_ptr<TtsEngineResult>,失败返回 nullptrIsSuccess()==false
CallToFile(text, file_path)合成并直接写 WAV 文件,免去手动 SaveToFile()text:要合成的文本;file_path:输出 WAV 路径。bool

4.1.4 流式合成

接口说明参数返回值
StreamingCall(text, callback, config=TtsConfig())一次性输入文本,分句合成;每句通过 OnEvent 回调返回一个 chunk,IsSentenceEnd() 区分中间/句末。text:完整文本;callbackshared_ptr<TtsResultCallback>config:可选覆盖配置。无直接返回;输出通过 callback。
StartDuplexStream(callback, config=TtsConfig())启动双向流会话,返回 DuplexStream 句柄;适合上游持续产文本(例如 LLM 流式输出)。callbackshared_ptr<TtsResultCallback>config:可选覆盖配置。shared_ptr<DuplexStream>
DuplexStream::SendText(text)把一段文本片段推入流,引擎内部会做切句和合成。可多次调用。text:文本片段。无。
DuplexStream::Complete()标记文本流结束;引擎合成完剩余文本后触发 OnComplete无。无。

4.1.5 运行时配置

接口说明参数返回值
SetSpeed(speed) / SetSpeaker(speaker_id) / SetVolume(volume)运行时切换语速、说话人、音量;下一次 Call() 起生效。speed:>1 加速、<1 减速;speaker_id:多说话人模型索引;volume:[0, 100]。无。
UpdateLexicon(entries)更新自定义发音词典,立即生效,影响后续合成。entriesvector<PronunciationEntry>无。
GetLastRequestId()取最近一次合成的请求标识,便于日志关联。无。string

4.2 C++ 调用示例

以下示例默认已完成 §2 构建;模型在 ~/.cache/models/tts/ 下对应子目录(matcha-tts / kokoro / ...)。

TTS 组件编译后会把 tts_service.hlibtts_service_cpp.so 安装到 output/staging。下游组件链接 TTS 库的 CMake 写法:

add_executable(my_app main.cpp)

find_library(TTS_SERVICE_LIB NAMES tts_service_cpp
PATHS ${CMAKE_INSTALL_PREFIX}/lib NO_DEFAULT_PATH)
find_path(TTS_SERVICE_INCLUDE_DIR NAMES tts_service.h
PATHS ${CMAKE_INSTALL_PREFIX}/include NO_DEFAULT_PATH)
if(NOT TTS_SERVICE_LIB OR NOT TTS_SERVICE_INCLUDE_DIR)
message(FATAL_ERROR "tts_service_cpp not found. Build components/model_zoo/tts first.")
endif()

target_include_directories(my_app PRIVATE ${TTS_SERVICE_INCLUDE_DIR})
target_link_libraries(my_app PRIVATE ${TTS_SERVICE_LIB})

包依赖建议在当前组件的 package.xml 中声明 <depend>tts</depend>

4.2.1 一次性合成存 WAV

适用场景:批量离线合成、固定提示音生成、脚本化测试。

调用步骤:

  1. TtsConfig::Preset("matcha_zh") 创建预设配置,按需覆写 speech_ratevolumesample_rate
  2. 构造 TtsEngine 并通过 IsInitialized() 检查。
  3. 调用 CallToFile(text, path) 直接落盘,或 Call(text) 取结果再 SaveToFile()
#include <iostream>
#include "tts_service.h"

int main() {
SpacemiT::TtsConfig config = SpacemiT::TtsConfig::Preset("matcha_zh");
config.speech_rate = 1.0f;

SpacemiT::TtsEngine engine(config);
if (!engine.IsInitialized()) {
std::cerr << "TTS 引擎初始化失败" << std::endl;
return 1;
}

if (!engine.CallToFile("你好,欢迎使用语音合成。", "hello.wav")) {
std::cerr << "合成失败" << std::endl;
return 1;
}
std::cout << "已生成 hello.wav,采样率 "
<< engine.GetSampleRate() << " Hz" << std::endl;
return 0;
}

完整版含命令行参数解析和交互模式,见 components/model_zoo/tts/examples/tts_file_demo.cpp

4.2.2 PCM 内存合成 + 直送播放器

适用场景:合成结果不落盘,直接送 SpacemitAudio::AudioPlayer 播放;或塞入应用自有音频管线(队列、网络发送等)。

调用步骤:

  1. 构造引擎并 Call(text)TtsEngineResult
  2. 通过 GetAudioInt16() 拿 PCM16 样本,GetSampleRate() 拿采样率。
  3. AudioPlayer 用对应采样率播放,把样本转为字节写入。
#include <vector>
#include "tts_service.h"
#include "audio_base.hpp"

void SpeakOnce(const std::string& text) {
SpacemiT::TtsEngine engine(SpacemiT::TtsConfig::Preset("matcha_zh"));
auto result = engine.Call(text);
if (!result || !result->IsSuccess()) return;

std::vector<int16_t> pcm = result->GetAudioInt16();
int sample_rate = result->GetSampleRate();

SpacemitAudio::AudioPlayer player(-1);
player.Start(sample_rate, 1);
const auto* bytes = reinterpret_cast<const uint8_t*>(pcm.data());
player.Write(bytes, pcm.size() * sizeof(int16_t));
player.Stop();
player.Close();
}

若播放设备只支持 48kHz(K3 默认),先用 Resampler 把 22050 / 16000 / 24000 转成 48000;具体见 6.3.3-audio §4.2.5。

4.2.3 流式合成边播

适用场景:长文本或低延迟回复,需要"边合成边播",避免等整段合成完才出声;用户打断(barge-in)也依赖流式接口。

调用步骤:

  1. 实现 TtsResultCallback 子类,覆写 OnEvent 把 chunk 推入播放队列;OnComplete 通知主线程合成结束。
  2. 构造引擎,调用 StreamingCall(text, callback);该函数会启动后台合成。
  3. 主线程从队列取 PCM 写入 AudioPlayer,达到边合成边播效果。
#include <atomic>
#include <iostream>
#include <memory>
#include <mutex>
#include <queue>

#include "tts_service.h"
#include "audio_base.hpp"

class PlaybackCallback : public SpacemiT::TtsResultCallback {
public:
void OnEvent(std::shared_ptr<SpacemiT::TtsEngineResult> result) override {
if (!result || !result->IsSuccess()) return;
std::lock_guard<std::mutex> lk(mu_);
chunks_.push(result->GetAudioInt16());
sample_rate_ = result->GetSampleRate();
}
void OnComplete() override { done_ = true; }
void OnError(const std::string& msg) override {
std::cerr << "[TTS] " << msg << std::endl;
done_ = true;
}

std::mutex mu_;
std::queue<std::vector<int16_t>> chunks_;
std::atomic<bool> done_{false};
int sample_rate_ = 0;
};

int main() {
SpacemiT::TtsEngine engine(SpacemiT::TtsConfig::Preset("matcha_zh"));
auto cb = std::make_shared<PlaybackCallback>();

engine.StreamingCall("你好世界。今天天气很好。", cb);

SpacemitAudio::AudioPlayer player(-1);
bool started = false;
while (!cb->done_ || !cb->chunks_.empty()) {
std::vector<int16_t> pcm;
{
std::lock_guard<std::mutex> lk(cb->mu_);
if (!cb->chunks_.empty()) { pcm = std::move(cb->chunks_.front()); cb->chunks_.pop(); }
}
if (pcm.empty()) continue;
if (!started) { player.Start(cb->sample_rate_, 1); started = true; }
player.Write(reinterpret_cast<const uint8_t*>(pcm.data()),
pcm.size() * sizeof(int16_t));
}
player.Stop();
player.Close();
return 0;
}

完整版含进度展示与采样率自适应,见 components/model_zoo/tts/examples/tts_stream_demo.cpp

4.2.4 双向流(边输入文本边合成)

适用场景:上游持续产文本(典型如 LLM 流式输出),希望文本一边到达就一边触发合成,整体延迟最小化。

调用步骤:

  1. 实现 TtsResultCallback,处理 chunk(参考 §4.2.3)。
  2. 调用 StartDuplexStream(callback) 取得 DuplexStream
  3. 上游每收到一段文本就 stream->SendText(piece);上游结束时调 stream->Complete(),等待 OnComplete
auto engine = std::make_shared<SpacemiT::TtsEngine>(
SpacemiT::TtsConfig::Preset("matcha_zh_en"));
auto cb = std::make_shared<PlaybackCallback>();

auto stream = engine->StartDuplexStream(cb);
stream->SendText("你好");
stream->SendText("世界,");
stream->SendText("现在开始流式合成。");
stream->Complete(); // 通知文本流结束
// 主线程继续从 cb->chunks_ 取数据播放,与 §4.2.3 相同

4.2.5 自定义发音 lexicon

适用场景:纠正多音字(如"为你"读 wei4 ni3 而非 wei2 ni3)、产品名注音、混合语合成(matcha:zh-en 下指定英文词的英文读法)。

调用步骤:

  1. 准备 vector<PronunciationEntry>,每条含 wordphonemelocale(默认 zh)。
  2. 构造或运行时调 engine.UpdateLexicon(entries)
  3. 后续 Call() / StreamingCall() 都会用更新后的词典。
SpacemiT::TtsEngine engine(SpacemiT::TtsConfig::Preset("matcha_zh_en"));

std::vector<SpacemiT::PronunciationEntry> entries = {
{"为你", "wei4 ni3", "zh"},
{"SpaceMIT", "space meet", "en"},
};
engine.UpdateLexicon(entries);

engine.CallToFile("你好,我是 SpaceMIT 的语音合成模型,很高兴为你服务。",
"lexicon_demo.wav");

locale="zh" 时 phoneme 是带声调拼音(空格分隔);locale="en" 仅对 matcha:zh-en 有效,phoneme 是英文单词或短语,由 espeak-ng 渲染成 IPA。

4.3 Python 示例

Python 包名为 spacemit_tts,安装方式见 §2 中 wheel 安装步骤。导入后直接使用:

import spacemit_tts

4.3.1 文件合成

import spacemit_tts

config = spacemit_tts.Config.preset("matcha_zh")
config.speech_rate = 1.0
config.volume = 80

with spacemit_tts.Engine(config) as engine:
result = engine.synthesize("你好,欢迎使用语音合成。")
print(f"采样率 {result.sample_rate} Hz, "
f"时长 {result.duration_ms} ms, RTF {result.rtf:.3f}")
result.save("hello.wav")

也可以用模块级快捷函数一句话存盘:

import spacemit_tts
spacemit_tts.synthesize_to_file("你好世界", "output.wav")

4.3.2 流式合成

继承 TtsCallback 实现自定义回调,结合 synthesize_streaming(text, callback) 触发流式合成:

import numpy as np
import spacemit_tts

class CollectCallback(spacemit_tts.TtsCallback):
def __init__(self):
super().__init__()
self.chunks = []
self.sample_rate = 0

def on_event(self, result):
if result.is_success and not bool(result) is False:
self.chunks.append(np.array(result.get_audio_int16(),
dtype=np.int16))
self.sample_rate = result.get_sample_rate()

def on_error(self, message: str):
print(f"[TTS] {message}")

cb = CollectCallback()
engine = spacemit_tts.Engine(spacemit_tts.Config.preset("matcha_zh"))
engine.synthesize_streaming("你好世界。今天天气很好。", cb)

audio = np.concatenate(cb.chunks) if cb.chunks else np.empty(0, dtype=np.int16)
print(f"共 {audio.size} 采样点,{cb.sample_rate} Hz")

也可直接复用包内置回调(PrintCallback / SaveCallback / CollectCallback),无需自己继承 ABC。完整流式 demo 见 components/model_zoo/tts/python/examples/tts_stream_demo.py

4.3.3 C++ ↔ Python 接口对照

C++(SpacemiT::Python(spacemit_tts.备注
TtsConfig + Preset(name)Config(backend, model_dir)Config.preset(name)Python 字段通过属性 setter(speech_rate / volume / speaker_id / sample_rate / pitch)或链式 with_speed/with_speaker/with_volume
TtsEngine(config) + IsInitialized()Engine(config)with Engine(config) as enginePython 构造时直接初始化,无需显式 initialize()engine.is_initialized 查询状态。
Call(text)Engine.synthesize(text) → Resultsynthesize(text) 模块级快捷函数
CallToFile(text, path)Engine.synthesize_to_file(text, path) → boolsynthesize_to_file(text, path)
StreamingCall(text, callback)Engine.synthesize_streaming(text, callback)callback 需为 TtsCallback 子类实例。
StartDuplexStream(callback)当前 Python 包未暴露双向流场景请使用 C++ API。
TtsResultCallbackTtsCallback(ABC)/ PrintCallback / SaveCallback / CollectCallback后三者为内置便捷实现。
SetSpeed/SetSpeaker/SetVolume/UpdateLexiconengine.set_speed(speed) / set_speaker(id) / set_volume(vol) / update_lexicon(entries)entries 接受 dict 列表({"word": ..., "phoneme": ..., "locale": ...})或 PronunciationEntry 实例。
TtsEngineResult::GetAudioFloat/Int16/Data 等 getterresult.audio_float / audio_int16 / audio_bytes / sample_rate / duration_ms / rtf 属性Python 侧用属性而非方法;result.save(path) 等价 SaveToFile

更多 Python 示例(含 Kokoro 多音色、双语切换)见 components/model_zoo/tts/python/examples/

5. 调试指南

调试 TTS 时先确认后端、文本处理和播放链路:

  • 文件合成优先用 tts_file_demo 验证,确认输出 WAV 可被 audio_demo play 正常播放。
  • 英文或中英混合文本先确认 espeak-ng、lexicon 和词典资源是否可用。
  • 播放异常时记录模型输出采样率和播放设备采样率,必要时显式重采样。
  • 性能评估以模型 warmup 后的合成 RTF 为准,Kokoro 首次初始化时间应单独记录。

6. 常见问题

现象可能原因处理
首次合成明显慢模型加载和 warmup 开销以 warmup 后的合成 RTF 评估性能。
英文发音异常espeak-ng 缺失或热词未配置安装 espeak-ng,必要时用 --lexicon 指定读法。
播放速度或音调异常播放采样率与模型采样率不一致audio_demo playAudioPlayer 播放时开启重采样。
Kokoro 初始化很慢模型和音色加载开销较大应用启动阶段预初始化,不要在每轮对话里反复创建引擎。

附录:K3 实测数据

以下数据基于 K3 平台量化模型构建(tts_file_demo --provider auto)实测。每项运行 3 次,表中取 RTF 中位数对应结果;引擎初始化与 warmup 不计入处理时间。

默认 provider 路由:matcha:zh / matcha:en 使用 SpaceMIT EP 跑声学模型、CPU 跑 vocoder;matcha:zh-en 使用 CPU 跑声学模型和 vocoder;Kokoro 当前使用 CPU 路径。

后端-l 参数测试文本音频时长处理时间RTF
MATCHA_ZHmatcha:zh"这是一个语音合成测试"2565ms575ms0.22
MATCHA_ENmatcha:en"This is a longer English speech synthesis benchmark sentence for measuring real time factor on the K3 platform."6873ms1608ms0.23
MATCHA_ZH_ENmatcha:zh-en"今天学Python"1920ms542ms0.28
KOKOROkokoro"你好"2075ms6582ms3.17

测试命令tts_file_demo -p "<text>" -l <engine> --provider auto