完全指南:多语言合成、流式输出与零样本声音克隆)
Moonshine Voice 文本转语音TTS完全指南多语言合成、流式输出与零样本声音克隆【免费下载链接】moonshineVery low latency speech to text, intent recognition, and text to speech, for building voice agents and interfaces项目地址: https://gitcode.com/GitHub_Trending/moonshine3/moonshineMoonshine 的TextToSpeech模块是面向语音 Agent 与语音交互界面的端侧 TTS 能力支持多语言合成、流式“边说边生成”输出以及基于 ZipVoice 的零样本声音克隆。本文基于 docs/using/text-to-speech.md 展开结合 Python 绑定源码与 C 核心实现带你从最基础的“说一句话”出发逐步掌握链式配置、流式推流、麦克风克隆与音素G2P转换的完整用法并理解其背后的架构取舍。快速上手配置、加载与发声链式配置与say()TextToSpeech采用链式 setter 进行配置先配置合成器再调用load()获取并打开声音最后把文本交给say()在默认音频设备上朗读from moonshine_voice import TextToSpeech tts TextToSpeech().language(fr) tts.load() tts.say(Bonjour, mon ami) tts.wait() # block until playback finishes从 language-bindings/python/src/moonshine_voice/tts.py 的源码可以看出TextToSpeech的构造函数不接受任何参数传入参数会直接抛出带迁移说明的TypeError所有配置都必须通过 setter 完成包括Setter作用默认值 / 说明.language(code)设置合成语言如en、en_us、fr默认en.voice(voice_id)指定目录声音 id如kokoro_af_heart调用后会清除克隆模式无不指定则用该语言的默认声音.models_from(directory, downloadFalse)从指定目录加载声音资源downloadTrue时把该目录当作缓存根目录默认使用系统缓存.cloning(enabledTrue)把合成器切换为 ZipVoice 克隆引擎必须在load()之前调用默认关闭.on_progress(handler)下载进度回调(fraction, file)形式无.options(mapping)链式 setter 未覆盖的底层选项的逃生舱空字典.output_device(device)指定 PortAudio 输出设备接受设备索引或名称子串系统默认输出.volume(volume)播放增益不调整.debug(enabledTrue)向 stderr 输出合成与播放追踪日志关闭值得注意的是声音 id 的前缀决定了底层使用的声码器kokoro_、piper_、zipvoice_分别对应 Kokoro、PiperTTS 与 ZipVoice见 tts.py 的类文档。language()传入的标签会在下载前经过normalize_moonshine_language_tag归一化-、空格统一转为_并对照原生 TTS 目录校验非法语言会抛出MoonshineTtsLanguageError并列出可用标签。load()与下载进度load()是阻塞的因为首次调用可能需要下载数百 MB 的声音资源Kokoro 模型、Piper 声音、G2P 词库等。可以通过on_progress()驱动进度条tts TextToSpeech().language(fr).on_progress(lambda fraction, file: print(f{fraction:.0%})) tts.load()底层实现里tts.pyload()会根据资源解析流程_ensure_assets()决定是命中本地缓存还是从https://download.moonshine.ai/tts/下载同一合成器重复调用load()是无害的 no-op。下载的资源按get_cache_dir()/download.moonshine.ai/tts目录布局缓存见 download.py后续调用无需联网。synthesize()不要声卡也能拿到音频在没有音频输出设备、或需要进一步后处理的机器上可以直接用synthesize()取回 PCM 采样from moonshine_voice import TextToSpeech tts TextToSpeech().language(en-us) tts.load() audio_data, sample_rate tts.synthesize(Howdy, partner)audio_data是[-1, 1]范围的 float 采样列表sample_rate是采样率Hz。synthesize()还支持speed、volume与透传的options参数此外还有synthesize_from_phonemes(phonemes, ...)可直接把 IPA 音素串合成为语音跳过 G2P 转换便于先检查或修改音素例如修正人名的发音。发现语言与声音list_tts_languages/list_tts_voicesTTS 支持多语言。运行list_tts_languages()可以查看当前目录支持的全部语言from moonshine_voice import list_tts_languages list_tts_languages() [ar-msa, de-de, en-gb, en-us, es-ar, es-es, es-mx, fr-fr, hi-in, it-it, ja-jp, ko-kr, nl-nl, pt-br, pt-pt, ru-ru, tr-tr, uk-ua, vi-vn, zh-hans]对每种语言可以进一步列出可用的声音from moonshine_voice import list_tts_voices list_tts_voices(ru) {present: [], downloadable: [piper_ru_RU-denis-medium, piper_ru_RU-dmitri-medium, piper_ru_RU-irina-medium, piper_ru_RU-ruslan-medium]}返回值是{present: [...], downloadable: [...]}结构present表示已下载到本地的声音downloadable表示目录中存在但尚未下载。把downloadable中的 id 传给voice()后Moonshine 会自动下载到缓存之后即可离线使用。这一逻辑在 download.py 中由list_tts_voices实现它查询原生目录moonshine_get_tts_voices并按磁盘状态把声音分为found/missing两组。流式输出边说边生成stream()按块取回音频当文本来自语言模型时等完整回复生成完毕再开口会浪费模型生成期间的等待时间。把文本随到随推有足够内容即可立即拿到音频tts TextToSpeech().language(en-us) tts.load() for chunk in tts.stream(llm_tokens()): print(f{len(chunk.samples) / chunk.sample_rate:.2f}s: {chunk.text})流式没有需要打开或关闭的 session 对象一个合成器只有一份模型、一次只说一件事所以推入文本即开始回复结束输入即完成回复。把文本直接传给stream()会替你完成“推入并结束输入”如果想自行驱动可以在另一个线程调用push_text()然后无参迭代stream()。文本缓冲、flush 与生命周期文本会被暂存到出现完整句子或短语为止因为韵律prosody依赖从句边界——一个字一个字喂给合成器得到的是一串列表而不是一句话。围绕这一设计有四个核心方法方法作用push_text(text)追加文本到正在生成的回复没有回复则开启一个新回复片段按原样拼接逐 token 喂 LLM 输出也能正确还原单词flush()把尚未形成完整句子的缓冲内容也拿去合成end_input()声明不再有文本输入让回复收尾cancel_stream()放弃当前回复打断路径用户插话时调用因为回复在播放期间占用模型say()和synthesize()会在流式回复结束或被取消之前拒绝调用源码中is_streaming属性即用于查询这一状态见 tts.py。say_stream()边生成边播放如果不想自己处理音频块而是希望流式块直接进入与say()相同的播放队列用say_stream()with tts.say_stream() as speech: for token in llm_tokens(): tts.push_text(token) tts.end_input() speech.wait()AgentFlow也提供同样的方法回复可以在模型书写的同时被朗读出来with agent.say_stream() as push: for token in llm.stream(prompt): push(token)从源码看say_stream()通过_ChunkPump后台线程轮询原生层的moonshine_tts_next_chunk把每个TtsChunk转成_PlayItem塞进容量为 1 的播放队列由播放线程写入同一个持久化输出流——多句回复因此能连成一段连续语音而不是句与句之间经历设备关停重开。TtsChunk每个块携带什么每个流式块都带有采样、采样率、覆盖文本、话语 id 以及是否为该话语最后一个块dataclass class TtsChunk: samples: List[float] # PCM float 采样 sample_rate: int # 采样率 Hz text: str # 该块覆盖的文本按声学帧切分时为 utterance_id: int # 从 1 开始计数 is_final: bool # 是否为该话语的最后一个块话语从 1 开始编号因此即使在回复被 flush 后重新填充id 依然可比对当一个块是按声学帧而非可判定的字符跨度切分时text为空字符串见 tts.py 的TtsChunk定义。同样的接口在所有语言绑定中都存在TypeScript、Swift、Java 里是pushText/flush/endInput/cancelStream加onChunk消费者TypeScript 另有 async iteratorSwift 另有AsyncThrowingStream。延迟与电平流式为什么快流式的大部分延迟收益来自不等模型以每秒 50 个 token 的速度书写一段四句回复流式场景下首段音频远小于 1 秒即可到达而等待完整回复再合成则需要 2 秒以上——其中几乎全部差距都是模型书写时间而非合成器合成时间。两个引擎还会把首个 chunk 切到句子之下所以一个很长的开场句也会在全部解码完成前就开始播放。随着回复推进chunk 会越来越大短的首块换来快速启动较长的后续块摊薄每块的固定开销。Kokoro 的子句块带有交叉淡化crossfade因为它的解码器会对交给它的任意跨度做归一化无法无缝拼接Piper 的块则逐样本还原整个渲染结果。流式音频的音量按每个声音单独测量的数值进行平衡levelling因为say()使用的峰值归一化需要一个完整的波形而流式永远拿不到完整波形。因此流式响度会接近say()而不是完全一致——通常相差 1~2 dB遇到异常安静的话句会略多一些。零样本声音克隆clone_from()从音频文件克隆集成的 ZipVoice 模型可以仅凭一小段音频模仿某人的声音。把片段传给clone_from()可以传.wav文件路径也可以传(pcm, sample_rate)的单声道 float 采样对from moonshine_voice import TextToSpeech import importlib.resources; clone_path importlib.resources.files(moonshine_voice.assets).joinpath(clone-test.wav) clone_transcript Ever tried. Ever failed. No matter. Try Again. Fail again. Fail better. tts TextToSpeech().language(en-us).cloning() tts.load() tts.clone_from(clone_path, transcriptclone_transcript) tts.say(Ask not what your country can do for you, but what you can do for your country) tts.wait()cloning()会提前告诉load()去获取 ZipVoice 及其克隆用 ASR 资源这样clone_from()之后只负责替换参考片段。必须在load()之前调用cloning()——否则clone_from()/start_cloning()会抛出明确的错误。目录声音与克隆互斥voice()会清除克隆模式cloning()会清除目录声音源码中_require_cloning_mode负责这一校验见 tts.py。参考片段可以来自仓库自带的示例音频 language-bindings/python/src/moonshine_voice/assets/clone-test.wav。transcript是可选的当省略时Moonshine 会在克隆前用其 ASR 模型自动转写该片段首次使用会多花几秒。片段归一化逻辑支持 16/24-bit PCM、立体声自动混为单声道见_normalize_clone_argumenttts.py。start_cloning()从麦克风克隆start_cloning()返回一个VoiceClone它会持续监听直到听到足够的可用语音clone tts.start_cloning() clone.on_ready(lambda: print(Got it, you can stop talking.)) clone.from_microphone() tts.clone_from(clone)从录音中挑选片段使用的是 Moonshine 内置的语音活动检测器VAD它被编译进库中因此这一步不需要下载任何东西。from_microphone()会阻塞直到片段就绪或经过 20 秒on_progress()回调报告已录音时长与目前找到的语音量。VoiceClone的默认参数为参考片段时长 4 秒、最少语音 2 秒start_cloning(clip_duration_seconds4.0, minimum_speech_seconds2.0)录音采样率为 16 kHz见 voice_clone.py。由于“可用片段”的判定逻辑在核心层完成浏览器、Python、iOS、Android 各绑定对“什么样的片段算好片段”的判定完全一致。命令行克隆也可以从命令行尝试克隆。由于未必总是能方便地拿到想克隆语音的干净转写文本可以省略transcript让 Moonshine 自动生成——API 与命令行都支持python3 -m moonshine_voice.tts \ --clone clone-test.wav \ --text I am so excited about Moonshine Voices text to speech命令行入口还支持--language默认en_us、--voice、--clone-transcript、--out写出 mono PCM16 WAV、--device、--asset-root与可重复的--options KEYVALUE如--options speed1.1。当播放失败且指定了--out时会自动回退写出out.wav见 tts.py 的 CLI 实现。可选声音一览以下每个条目都是可以传给voice()的声音名。这些声音均以“Welcome to Moonshine Voice text to speech”为示例文本录制可在仓库的 docs/audio 目录下试听对应 WAV 文件。ZipVoiceZipVoice 声音由 k2-fsa 团队的高质量 flow-matching TTS 模型 ZipVoice 的零样本声音克隆能力创建。它比 Kokoro 或 PiperTTS 生成慢得多但支持声音克隆且语音更真实。zipvoice_american_female试听zipvoice_american_male试听zipvoice_australian_male试听zipvoice_canadian_female试听zipvoice_canadian_male试听zipvoice_english_female试听zipvoice_english_male试听zipvoice_indian_female试听zipvoice_indian_male试听zipvoice_irish_female试听zipvoice_irish_male试听zipvoice_new_zealand_female试听zipvoice_northern_irish_female试听zipvoice_south_african_female试听zipvoice_south_african_male试听KokoroKokoro 是一个 8200 万参数的开源权重 TTS 模型质量可与大得多的模型相当。American FemaleAmerican MaleBritish FemaleBritish Malekokoro_af_alloy试听kokoro_am_adam试听kokoro_bf_alice试听kokoro_bm_daniel试听kokoro_af_aoede试听kokoro_am_echo试听kokoro_bf_emma试听kokoro_bm_fable试听kokoro_af_bella试听kokoro_am_eric试听kokoro_bf_isabella试听kokoro_bm_george试听kokoro_af_heart试听kokoro_am_fenrir试听kokoro_bf_lily试听kokoro_bm_lewis试听kokoro_af_jessica试听kokoro_am_liam试听kokoro_af_kore试听kokoro_am_michael试听kokoro_af_nicole试听kokoro_am_onyx试听kokoro_af_nova试听kokoro_am_puck试听kokoro_af_river试听kokoro_am_santa试听kokoro_af_sarah试听kokoro_af_sky试听Piper TTSPiper 项目提供了超过一百个轻量级声音覆盖 Moonshine 支持的全部语言。你可以在 Piper 官方声音示例页试听每一个 Piper 声音并通过list_tts_voices()返回的piper_前缀声音名在 Moonshine 中使用其中任何一个。字素到音素Grapheme to Phoneme为什么 Moonshine 要自研 G2P从声音名可以看出Moonshine Voice 使用了 Kokoro 与 PiperTTS 的模型。Kokoro 和 Piper 都用 espeak-ng 把文本串转换成音素国际音标 IPA 表示的声音符号。espeak-ng 采用 GPL 许可虽然它是优秀的自由软件但其条款使得它难以被整合进那些不愿以类似许可开源的应用中。云端场景问题不大——很多 espeak-ng 的用途可以通过调用外部可执行程序实现但很多边缘操作系统无法这样做因为要在 iOS 或 Android 上包含代码唯一方式是链接进应用而这会要求调用代码开源。为了让更广泛的用途成为可能Moonshine 从零开发了自己的字素转音素G2P模块实现位于 core/moonshine-tts与代码库其余部分一样以 MIT 许可发布。每种语言都需要不同的流程把书写形式转换为语音且常因方言而异——这正是 espeak-ng 被广泛使用的原因它经过多年工作把语言知识编码进一套复杂规则中其中许多是需要大量测试才能做对的启发式规则。Moonshine Voice 的 G2P 引擎仍然年轻需要类似的调优来覆盖各种语言的变体。以下是当前跨语言的可懂度结果CER越低越好评测脚本为 scripts/tts_g2p_intelligibility.pyLanguageMoonshine CERReference CERar_msa20.8%15.3%de_de18.3%9.2%en_us12.6%9.8%es_ar7.9%10.6%es_es4.2%4.5%es_mx3.2%2.6%fr_fr14.8%9.4%hi_in26.5%15.9%it_it24.2%11.4%ja_jp38.1%16.8%ko_kr25.0%18.6%nl_nl15.9%3.3%pt_br19.7%4.9%pt_pt43.8%24.6%ru_ru16.9%5.0%tr_tr8.9%7.9%uk_ua27.7%15.6%vi_vn79.0%36.5%zh_hans37.8%32.6%直接调用 G2P如果只需要字素到音素的能力而不要语音合成可以直接调用from moonshine_voice import GraphemeToPhonemizer g2p GraphemeToPhonemizer(en-us) g2p.to_ipa(Hello world) həlˈoʊ wˈɝld从 g2p.py 的源码看GraphemeToPhonemizer在构造时会通过download_g2p_assets下载对应语言的 G2P 资源词库、OOV 模型、POS 模型等然后调用 C 层的moonshine_create_grapheme_to_phonemizer_from_files创建句柄支持asset_root指定缓存目录、downloadFalse离线使用既有资源也支持options透传如path_root、tts_root等底层键。命令行方式为python3 -m moonshine_voice.g2p --language en_us --text Hello world并强制以 UTF-8 输出以兼容 Windows 控制台。底层资源组织所有 TTS / G2P 运行时数据以“每语言一捆”的形式组织在 core/moonshine-tts/data其 README 详细说明了各类资源阿拉伯语 MSA 的 tashkīl ONNX、英语的 CMU 风格词库 OOV ONNX、法语的 lexicon liaison POS CSV、日语的 lexicon char-LUW UPOS ONNX、中文的 lexicon RoBERTa UPOS ONNX以及kokoro/目录下的 Kokoro-82M ONNX 模型与.kokorovoice捆绑。该 README 还解释了模型的三种存储布局stem.model.ortstem.weights.ort双模型、单一.ort、.onnx及其取舍Kokoro 与 int8 权重的 Piper 声音采用权重分离布局使反量化只在加载时执行一次英文 OOV 模型用 float 权重因此采用完整优化的单一.ort而中文与阿拉伯语 G2P 变换器的权重喂给MatMulORT 仅在操作数为常量时才预打包因此保持.onnx形式以避免推理约 2.2 倍的代价。Piper 声音在发布前被切分为.upstream.*音素→声学帧与.generator.*帧→音频两段这正是在回复尚未合成完成时就能开始播放的底层机制。播放管线与设备处理源码视角say()的播放由两个后台线程驱动见 tts.py合成线程从_say_queue取出句子调用synthesize()播放线程从容量为 1 的_play_queue取出_PlayItem写入持久化的 PortAudio 输出流。长文本会先在split_say_utterances中按句子边界.!?后跟空白。؟।无需后随空格Dr.这类称呼与J. R. R. Tolkien这类缩写不会误断句切分使第一句可以在后续句子合成时就开始播放。输出流在队列空闲 1 秒后关闭_OUTPUT_STREAM_IDLE_SECONDS以免在 Linux/ALSA 独占设备上长期占用声卡若设备不接受合成器的原生采样率会按候选表48k/96k/192k/44.1k…探测并做线性插值重采样。is_talking()与wait()依据播放尾时间_playback_tail_until判断音频是否真正从扬声器播完而不是只看队列是否为空stop()则清空两个队列并abort当前输出流。调试无声问题时可调用list_output_devices()查看所有可用输出设备格式[idx] name (hostapi: NAME, channels, default_sr)默认设备带*标记再用output_device()或 CLI 的--device固定到正确的设备——这在树莓派上尤其常见因为 PortAudio 默认设备往往是 HDMI 而不是接好音箱的 3.5mm 耳机孔。小结Moonshine Voice 的 TTS 能力由一条完整的链路构成Python 绑定language-bindings/python/src/moonshine_voice/tts.py负责链式配置、资源下载、流式泵与播放线程C 核心core/moonshine-tts提供合成器、G2P、句子切分与分阶段解码数据资源core/moonshine-tts/data以按语言捆绑的目录发布。无论你要做最简的“说一句话”、LLM 驱动的边写边说、麦克风零样本克隆还是独立的 IPA 音素转换都可以只引入本模块独立使用不必依赖转录或 Agent 模块。【免费下载链接】moonshineVery low latency speech to text, intent recognition, and text to speech, for building voice agents and interfaces项目地址: https://gitcode.com/GitHub_Trending/moonshine3/moonshine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考