根据官方教程,我摸索出了一些规律,就是根据模型安装后端,qnn版本对齐就行。
我根据我实际情况构建了一个目录环境,然后下载了中文和英文的模型
下载命令都是一个套路的,我下载后解压到model目录里面,创建了中文zh目录和英文en目录。
下载命令非常简单:
mms login
cd models/
mms get -m MeloTTS-Chinese -p FP16 -b qnn2.31 -c qcs8550 -d ./
unzip MeloTTS-Chinese_qcs8550_fp16.zip -d ./zh/
unzip MeloTTS-English_qcs8550_fp16.zip -d ./en/
cp -r /usr/local/share/aidvoice/examples ../
cd ../examples/tts/cpp/
mkdir build && cd build && cmake ../ && make -j $(nproc)
./test_tts -m ../../../../models/zh/models/QCS8550/FP16/
就生成了一个wav文件,播放了一下,有那么点儿味道。然后我就打开examples里面的代码开始分析代码的内容,下面是我的一些发现:
首先, 这不是 MeloTTS 的开源实现仓库,而是 AidLux AidVoice 闭源 SDK 的示例工程包:
-
TTS 引擎本体在系统库
/usr/local/lib/libaidvoice_speech.so,源码不可见; -
唯一的公开接口头文件:
/usr/local/include/aidlux/aidvoice/aidvoice_speech.hpp; -
本仓库只包含:模型文件 + C++/Python 示例代码 + 说明文档;
-
推理后端是 AidLite + 高通 QNN(HTP/NPU),不是 OpenVINO 也不是 onnxruntime;
-
模型格式为
.aidem(FP16),跑在 NPU 上,速度嘎嘎快。
最原始拉下来的模型目录结构:
melotts-english/
├── models/QCS8550/FP16/
│ ├── config.json # {“type”: “TYPE_MELOTTS_ENGLISH”},SDK 据此识别模型类型
│ ├── encoder_htp.bin.aidem # 17MB 文本编码器(含 BERT)
│ ├── flow_htp.bin.aidem # 28MB Flow(VITS 流模型,声学建模)
│ ├── decoder_htp.bin.aidem # 21MB 解码器/声码器(输出波形)
│ └── assets/
│ ├── tokenizer.json # BERT WordPiece 词表(英文,30522 词)
│ ├── config.json # BertForMaskedLM 配置
│ ├── cmudict_cache.txt # CMU 英文发音词典(G2P 用)
│ └── punc.dic # 标点字典
└── examples/
├── tts/cpp/
│ ├── CMakeLists.txt
│ ├── readme.txt
│ ├── test_tts.cpp # 示例1:硬编码一句话 → 合成 + 播放 + 存 wav
│ ├── test_file.cpp # 示例2:读 txt 文件 → 分片合成 + 播放
│ ├── text_file.txt # 示例输入文本
│ └── build/ # 你当前的编译输出目录(test_tts / test_file 已编译好)
├── tts/python/test_tts.py # Python 版示例(功能更全,支持命令行直接传文本)
└── asr/ # ASR 语音识别示例(与 TTS 无关)
我根据实际情况改了一下,你们也可以根据自己的情况修改。只要能找到目录所在目录就行。
构建流程
依赖(已装则跳过):
aid-pkg install aidlite-sdk
aid-pkg install aidlite-qnn236
sudo apt install libpulse-dev libasound2-dev # 音频播放
编译:
cd /home/aidlux/melotts-english/examples/tts/cpp
mkdir -p build && cd build
cmake .. && make # 生成 test_tts 和 test_file
CMakeLists.txt 固定引用 /usr/local/include 和 /usr/local/lib,链接 aidvoice_speech 库,无需额外配置。
然后看了readme.md 后对两个示例程序的区别与用法有点儿自己的见解。
常用命令:
# 列出可用的播放设备(扬声器)id
sudo ./test_tts -c
# 播放模式运行(-i 是播放设备 id,不是麦克风)
sudo ./test_tts -m /home/aidlux/melotts-english/models/QCS8550/FP16 -i <设备id>
# 读文件合成
sudo ./test_file -m /home/aidlux/melotts-english/models/QCS8550/FP16 -i <设备id> -f <文本路径>
注意:
-
必须带
-m:代码里默认模型路径指向/home/aidlux/model_farm_melotts_en_woman_...,本机不存在,因为我改了目录了。 -
readme.txt 里
cmake..是笔误,应为cmake .. ,另外有个sudo写成了suduo了,望修复一下。 -
播放需要 sudo(访问音频设备);只生成 wav 不播放可不带
-i试试。
我猜测文本到wav的内部流程可能是下面的内容,不确定,不过有大佬知道的话,请批评指正哈!
虽然SDK 闭源,但从模型文件和接口可还原完整链路(即 MeloTTS 的 VITS 三段结构):
首先是输入文本
然后到→ 文本规整 / 标点处理(punc.dic)
这里英文版本的话它是使用BERT做分词的→ BERT WordPiece 分词(assets/tokenizer.json)
→ G2P 字素转音素(英文查 CMU 词典 cmudict_cache.txt)
→ encoder_htp.bin.aidem (文本/BERT 编码,NPU)
→ flow_htp.bin.aidem (VITS Flow 声学建模,NPU)
→ decoder_htp.bin.aidem (声码器,输出波形,NPU)
→ 回调 onResult 返回 float PCM(48kHz / 16bit / 单声道)
→ 落盘 wav 和/或实时播放
所以看程序的aidvoice_speech.hpp`头文件就可以发现,只需要跟5个api打交道就行。
| API | 作用 |
|---|---|
create_tts(FeatureConfig) |
创建实例(配置模型路径等) |
set_mode() |
TYPE_WHOLE 整句 / TYPE_FRAGMENT 分片 |
set_callback() |
注册回调,接收合成结果 |
init() |
加载模型(只调一次,之后可反复 write) |
write(vector<string>) |
送入文本,触发合成 |
stop() / tts_destroy() |
停止、销毁 |
这里TYPE_FRAGEMENT我用的时候发现一段文字会生成N多个wav片段,后面需要把所有wav整合到一起的。
回调结构 TTSResult 里有:audio_data(PCM 数据)、audio_name(wav 文件名)、audio_time(时长)、seq(分片序号)、status(TYPE_PARTIAL 分片中 / TYPE_FINAL 最后一片)。
我的文档是mytest.txt 我晚上随便抄了一段内容。
如何用 mytest.txt 生成 wav —— 怎么做,有什么坑呢?你听我娓娓道来!
直接可用的命令
test_file 已支持任意文本文件,不用改代码:
cd /home/aidlux/melotts-english/examples/tts/cpp/build
./test_file -m /home/aidlux/melotts-english/models/QCS8550/FP16 -f ./mytest.txt
坑 1:mytest.txt 是中文,我根据官方说明下载的模型是英文专用,哈哈,自己坑了自己一把。
当前模型包(config.json 里 TYPE_MELOTTS_ENGLISH)的 G2P 用的是英文 CMU 词典,BERT 词表也是英文的。直接喂中文文本大概率合成失败或输出乱音。两个解决方案:
-
方案 A(简单):把 mytest.txt 内容改成英文,例如把之前准备的文档翻译成英文再合成;
-
方案 B(换模型):SDK 支持中文(头文件里有
TYPE_MELOTTS_CHINESE枚举),获取中文 MeloTTS 模型包后-m指向它即可,代码一行都不用改(SDK 按模型目录的 config.json 自动切换分词和 G2P 管线)。我就把模型都下载了。
坑 2:test_file 默认不保存 wav
test_file.cpp 没有调用 set_out_audio_path(),它面向"播放"场景。要存储为 wav,改一行:
test_file.cpp 第 134 行附近(set_mode 调用处),参照 test_tts.cpp:112 补上:
tts->set_out_audio_path("./"); // wav 输出到当前目录
然后重新 make 即可。
如果要改test_tt合成的文本的话,就去修改 test_tts.cpp:119 的str_vec:
然后重新编译就行。
后续我想试试看:
做交互式应用:LLM 文本 → TTS → 实时播放
我感觉应该完全可行,SDK 的设计就支持这个场景。想要实现的描述的链路(图片 → LLM 解析出文字描述 → 文字合成语音 → 实时播放)
因为我看代码里面init() 一次加载模型,之后反复调 write() 合成,不用每次重新加载——这是交互式应用的基础,不用每次加载模型浪费时间,第二,就是TYPE_FRAGMENT 分片模式下,SDK 把长文本切片,每合成一片就触发一次 onResult 回调(带 seq 序号),你可以一边合成一边播放,不用等整段话合成完,更加自然流畅了。
- 两种播放方式:
-
- 代码里面可以调
set_play_audio(设备id),SDK 内部直接播(好像需要 sudo权限,我试过没sudo不让操作硬件);
- 代码里面可以调
-
- 回调里自己拿
audio_data(float PCM)喂给你自己的播放器/网络流, 你可以直接推流到你的设备上,例如让树莓派接个蓝牙音箱,然后推流过去给树莓派,让树莓派播放。反正走网络你想怎么搞怎么搞了,哈哈,下一篇我就准备实时交互式应用哈哈。敬请期待。
- 回调里自己拿
-






