幽冥大陆(七十七)C# 调用 中文huayan-medium.onnx —东方仙盟练气期

全可行的(无需依赖外部可执行文件,更灵活轻量化)。下面我会详细提供完整的实现方案,包括核心依赖、步骤和可直接运行的代码:

一、核心前提:必备依赖库

C# 直接操作 ONNX 模型,核心依赖 Microsoft.ML.OnnxRuntime(ONNX 运行时库,官方维护,跨平台),同时需要辅助库处理音频数据,具体安装方式如下:

1. 安装 NuGet 包(两种方式)

方式 1:Visual Studio 图形界面安装
  1. 右键你的 C# 项目 → 选择「管理 NuGet 程序包」
  2. 切换到「浏览」标签,搜索以下包并依次安装(选择稳定版,建议最新版):
    • Microsoft.ML.OnnxRuntime(核心:加载和运行 ONNX 模型)
    • System.Numerics.Vectors(辅助:高效数值计算,可选但推荐)
    • NAudio(辅助:将模型输出转换为可播放的 WAV 音频,必选)
方式 2:Package Manager Console 命令安装

bash

运行

复制代码
Install-Package Microsoft.ML.OnnxRuntime
Install-Package NAudio
Install-Package System.Numerics.Vectors

二、核心原理说明

直接调用 zh_CN-huayan-medium.onnx 并非简单的 "加载模型即可合成",Piper 的 ONNX 模型有固定的输入输出规范,核心逻辑如下:

  1. 输入 :并非原始中文文本,而是经过文本预处理后的「音素 ID 序列」(Piper 对中文文本有专属的分词、音素映射规则,需遵循其规范);
  2. 模型推理:将音素 ID 序列传入 ONNX 模型,得到原始的语音波形数据(浮点型数组);
  3. 输出后处理:将浮点型波形数据转换为 16 位整型数据,再封装为 WAV 音频格式(可直接播放);
  4. 关键:zh_CN-huayan-medium.onnx.json 配置文件必须存在(用于获取音素映射、采样率、音频格式等关键参数,需与 .onnx 模型放在同一目录)。

三、完整 C# 实现代码(直接调用 ONNX)

以下代码实现了「文本预处理 → ONNX 模型推理 → 音频后处理 → 生成 WAV 文件」的全流程,可直接复制到项目中运行(需修改文件路径):

1. 完整代码

csharp

运行

复制代码
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Text;
using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;
using NAudio.Wave;

namespace PiperOnnxDirectCall
{
    class Program
    {
        // 核心配置(根据你的实际路径修改)
        private static readonly string OnnxModelPath = @"D:\ai\tiny_tts\piper\zh_CN-huayan-medium.onnx";
        private static readonly string OnnxConfigPath = @"D:\ai\tiny_tts\piper\zh_CN-huayan-medium.onnx.json";
        private static readonly string OutputWavPath = @"D:\ai\tiny_tts\piper\direct_onnx_output.wav";
        private static readonly int SampleRate = 22050; // Piper 华彦音库默认采样率(可从 json 配置中读取)

        static void Main(string[] args)
        {
            try
            {
                // 1. 待合成的中文文本
                string textToSpeak = "大家好,这是C#直接调用Piper ONNX模型的语音合成测试。";
                Console.WriteLine("开始预处理文本...");

                // 2. 文本预处理:转换为音素 ID 序列(核心步骤,模拟 Piper 的文本处理逻辑)
                // 注:此处为简化版音素映射(完整映射需参考 Piper 中文音素表,可从官方仓库获取完整字典)
                List<long> phonemeIds = TextToPhonemeIds(textToSpeak);
                if (phonemeIds.Count == 0)
                {
                    Console.WriteLine("文本预处理失败,无有效音素 ID!");
                    return;
                }
                Console.WriteLine($"文本预处理完成,音素 ID 数量:{phonemeIds.Count}");

                // 3. 加载 ONNX 模型并创建推理会话
                Console.WriteLine("正在加载 zh_CN-huayan-medium.onnx 模型...");
                using (var session = new InferenceSession(OnnxModelPath))
                {
                    // 4. 构造模型输入(Piper ONNX 模型输入为 "input",类型为 long 张量,形状 [1, N])
                    var inputTensor = new DenseTensor<long>(phonemeIds.ToArray(), new[] { 1, phonemeIds.Count });
                    var inputs = new List<NamedOnnxValue>
                    {
                        NamedOnnxValue.CreateFromTensor("input", inputTensor)
                    };

                    // 5. 执行模型推理,获取输出
                    Console.WriteLine("正在执行 ONNX 模型推理...");
                    using (var outputs = session.Run(inputs))
                    {
                        // 6. 提取推理结果(Piper ONNX 模型输出为 "output",类型为 float 张量)
                        var outputTensor = outputs.First().AsTensor<float>();
                        var audioData = outputTensor.ToArray();

                        // 7. 音频后处理:将 float 波形转换为 WAV 文件
                        Console.WriteLine("正在生成 WAV 音频文件...");
                        ConvertFloatAudioToWav(audioData, SampleRate, OutputWavPath);

                        Console.WriteLine($"语音合成成功!输出文件:{OutputWavPath}");
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"合成失败:{ex.Message}");
            }

            Console.WriteLine("按任意键退出...");
            Console.ReadKey();
        }

        /// <summary>
        /// 文本转音素 ID(简化版,完整版本需参考 Piper 中文音素映射)
        /// </summary>
        /// <param name="text">待合成中文文本</param>
        /// <returns>音素 ID 序列</returns>
        private static List<long> TextToPhonemeIds(string text)
        {
            // 此处为简化实现,实际需:
            // 1. 中文分词(参考 Piper 中文处理逻辑)
            // 2. 汉字转拼音(带声调)
            // 3. 拼音转音素(参考 zh_CN-huayan-medium 的音素表)
            // 4. 音素映射为对应的 ID(从 .onnx.json 配置中读取音素字典)
            // 临时:构造测试音素 ID 序列(确保模型能正常运行,实际需替换为完整逻辑)
            List<long> phonemeIds = new List<long>();

            // 简单映射:每个汉字对应一个测试音素 ID(可根据实际音素表修改)
            foreach (char c in text)
            {
                // 过滤空白字符
                if (char.IsWhiteSpace(c)) continue;
                // 临时 ID(实际需替换为真实音素 ID)
                phonemeIds.Add(c % 100); // 仅用于测试,实际需精准映射
            }

            // 添加结束符(Piper 要求,可从 json 配置中获取结束符 ID)
            phonemeIds.Add(0);

            return phonemeIds;
        }

        /// <summary>
        /// 将 float 类型音频波形转换为 WAV 文件
        /// </summary>
        /// <param name="floatAudio">模型输出的 float 音频数据</param>
        /// <param name="sampleRate">采样率(华彦音库默认 22050)</param>
        /// <param name="outputPath">输出 WAV 路径</param>
        private static void ConvertFloatAudioToWav(float[] floatAudio, int sampleRate, string outputPath)
        {
            // 1. 将 float 数据(-1 ~ 1)转换为 16 位整型(-32768 ~ 32767)
            short[] int16Audio = new short[floatAudio.Length];
            for (int i = 0; i < floatAudio.Length; i++)
            {
                // 裁剪超出范围的值
                float value = Math.Clamp(floatAudio[i], -1f, 1f);
                // 转换为 16 位整型
                int16Audio[i] = (short)(value * short.MaxValue);
            }

            // 2. 使用 NAudio 写入 WAV 文件
            using (var writer = new WaveFileWriter(outputPath, new WaveFormat(sampleRate, 16, 1)))
            {
                writer.WriteSamples(int16Audio, 0, int16Audio.Length);
            }
        }
    }
}

2. 关键说明

(1)文本预处理的重要性

代码中 TextToPhonemeIds 方法是简化版,实际生产环境中需要实现完整的 Piper 中文文本处理逻辑:

  1. 参考 Piper 官方中文处理代码(https://github.com/rhasspy/piper/blob/master/piper/phonemize/zh.py);
  2. 实现汉字 → 拼音(带声调)→ 音素的转换;
  3. zh_CN-huayan-medium.onnx.json 中读取音素与 ID 的映射字典,完成精准映射。
(2)模型输入输出规范

zh_CN-huayan-medium.onnx 作为 Piper 训练的模型,有固定的输入输出:

  • 输入:名称 input,类型 long(int64),张量形状 [1, N](N 为音素 ID 数量);
  • 输出:名称 output,类型 float,张量形状 [1, M](M 为音频采样点数量);
  • 采样率:默认 22050 Hz(可从 .onnx.json 配置文件的 audio.sample_rate 字段读取)。
(3)音频后处理

ONNX 模型输出的是 float 类型的原始波形数据(取值范围 -1, 1),需要转换为 WAV 格式的 16 位整型数据(取值范围 -32768, 32767),代码中 ConvertFloatAudioToWav 方法已实现该功能(依赖 NAudio 库)。

四、常见问题排查

  1. 模型加载失败
    • 原因:ONNX 模型路径错误,或 Microsoft.ML.OnnxRuntime 版本与模型不兼容;
    • 解决:确认路径正确,尝试升级 / 降级 Microsoft.ML.OnnxRuntime 版本。
  2. 推理报错(输入形状不匹配)
    • 原因:输入张量形状不符合 [1, N] 要求,或音素 ID 格式错误;
    • 解决:确保输入张量为 2 维(批次大小 1,序列长度 N),数据类型为 long
  3. WAV 文件无声音
    • 原因:文本预处理不完整(音素 ID 无效),或音频采样率设置错误;
    • 解决:完善音素映射逻辑,确认采样率与 .onnx.json 配置一致。
  4. 缺少 .onnx.json 文件
    • 原因:未将配置文件与 ONNX 模型放在同一目录,导致无法获取音素映射、采样率等参数;
    • 解决:将 zh_CN-huayan-medium.onnx.json 放在 ONNX 模型同级目录。

阿雪技术观

在科技发展浪潮中,我们不妨积极投身技术共享。不满足于做受益者,更要主动担当贡献者。无论是分享代码、撰写技术博客,还是参与开源项目维护改进,每一个微小举动都可能蕴含推动技术进步的巨大能量。东方仙盟是汇聚力量的天地,我们携手在此探索硅基生命,为科技进步添砖加瓦。

Hey folks, in this wild tech - driven world, why not dive headfirst into the whole tech - sharing scene? Don't just be the one reaping all the benefits; step up and be a contributor too. Whether you're tossing out your code snippets, hammering out some tech blogs, or getting your hands dirty with maintaining and sprucing up open - source projects, every little thing you do might just end up being a massive force that pushes tech forward. And guess what? The Eastern FairyAlliance is this awesome place where we all come together. We're gonna team up and explore the whole silicon - based life thing, and in the process, we'll be fueling the growth of technology

相关推荐
天平2 小时前
油猴脚本创建webworker踩坑记录
前端·javascript·typescript
原则猫4 小时前
前端基础大厦
前端
陈随易5 小时前
编程语言级别的Skill市场,AI Agent 的未来形态
前端·后端·程序员
SoaringHeart6 小时前
Flutter进阶:基于 EasyRefresh 的下拉刷新封装 n_easy_refresh_mixin.dart
前端·flutter
IT_陈寒7 小时前
Vite的热更新突然不香了,排查三小时差点砸键盘
前端·人工智能·后端
子兮曰8 小时前
Agency-Agents 深度解析:400+ AI 专家的"梦之队"如何重塑开发工作流
前端·后端·vibecoding
竹林8189 小时前
用 The Graph 查询链上数据实战:从手搓 RPC 到 Subgraph,我的 NFT 项目数据加载快了 10 倍
前端·javascript
妙码生花9 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(十九):点选验证码代码逐行目检
前端·后端·go
Awu122710 小时前
⚡从零开发 Agent CLI(五)实现一个可治理、可扩展的工具系统
前端·人工智能·claude
咪库咪库咪10 小时前
Vue3-生命周期
前端