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

相关推荐
怕浪猫38 分钟前
分享一个做视频的skill,这条白板视频,每一笔都是代码画的
前端·javascript·面试
aa小小1 小时前
大屏自适应缩放方案
前端·数据可视化
陪我去看海2 小时前
被要求猛出小程序,做了这个多平台多环境的部署替我承受压力
前端·微信小程序·抖音小程序
Behavior2 小时前
Unity 手游动态更换 App 图标 — Android 与 iOS 双端技术方案
c#·unity3d·游戏开发
Frag0ut2 小时前
告别插件时代:HTML5 视频播放对比 Flash/Silverlight/ActiveX 的性能与技术优势解析
前端·html5·视频播放·flash·activex·流媒体技术·silverlight
用户818618028002 小时前
MongoDB文档模型设计——从建模到索引
前端
周杰伦fans2 小时前
构建真正智能的CAD AI绘图代理
开发语言·人工智能·c#
無名路人3 小时前
小程序点餐页吸顶滚动之分类按需加载,上划切换
前端·vue.js·微信小程序
竹林8183 小时前
把神经网络塞进一个浏览器标签页:端侧视觉 AI 的工程真相
前端·浏览器
乘风gg4 小时前
别跟风 AI 副业,工程师最该学的是 AI Coding
前端·ai编程·claude