幽冥大陆(七十七)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 小时前
用 NAudio 做一个音频播放器及原理
前端
longze_72 小时前
google A2UI Windows 源码
ui·google·a2ui·front end
wei yun liang2 小时前
4.数据类型
前端·javascript·css3
唐青枫2 小时前
深入理解 C#.NET IEnumerable<T>:一切集合的起点
c#·.net
奥升新能源平台2 小时前
奥升充电平台OCPP协议解析
前端
JinSo6 小时前
我的2025年度总结:EasyEditor
前端·程序员
FL16238631297 小时前
[C#][winform]基于yolov8的水表读数检测与识别系统C#源码+onnx模型+评估指标曲线+精美GUI界面
开发语言·yolo·c#
喝拿铁写前端10 小时前
前端开发者使用 AI 的能力层级——从表面使用到工程化能力的真正分水岭
前端·人工智能·程序员
wuhen_n10 小时前
LeetCode -- 15. 三数之和(中等)
前端·javascript·算法·leetcode