幽冥大陆(七十七)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

相关推荐
可问春风_ren12 分钟前
前端文件上传详细解析
前端·ecmascript·reactjs·js
羊小猪~~1 小时前
【QT】--文件操作
前端·数据库·c++·后端·qt·qt6.3
晚风资源组2 小时前
CSS文字和图片在容器内垂直居中的简单方法
前端·css·css3
Miketutu2 小时前
Flutter学习 - 组件通信与网络请求Dio
开发语言·前端·javascript
cjp5604 小时前
018.C#管道服务,本机两软件间通讯交互
开发语言·c#
光影少年4 小时前
前端如何调用gpu渲染,提升gpu渲染
前端·aigc·web·ai编程
Surplusx4 小时前
运用VS Code前端开发工具完成网页头部导航栏
前端·html
小宇的天下5 小时前
Calibre 3Dstack --每日一个命令day13【enclosure】(3-13)
服务器·前端·数据库
故事不长丨5 小时前
C#log4net详解:从入门到精通,配置、实战与框架对比
c#·.net·wpf·log4net·日志·winform·日志系统
一只小bit6 小时前
Qt 文件:QFile 文件读写与管理教程
前端·c++·qt·gui