简介一款基于C#开发的音源分离工具源码面向音乐制作人、音频工程师和音视频后期爱好者。工具依托深度学习算法可将任意歌曲或音频文件拆分为鼓点、贝斯、人声等独立轨道既可辅助重新混音也能去除录音中的鸟鸣、路声、喇叭声等背景噪声。压缩包共一百一十五个文件整体仅一点九五兆字节包含C#源码、XML配置、资源文件、动态链接库、可执行程序以及解决方案工程等类型打开工程即可查看界面布局、音频处理与模型调用相关代码。已有六十人学习下载。阅读源码能够完整梳理音频轨道分离流程、模型加载与推理方式、结果导出策略以及图形界面事件处理等核心实现适合希望将音源分离能力集成到自有工具或学习C#桌面应用架构的开发者参考。该工具还提供简洁直观的操作界面让初学者也能快速上手。程序运行需另配约一点二吉字节的完整模型文件这是执行音源分离所依赖的必要外部条件。1. SpleeterGUI 音源分离工具把 Python 的音源分离能力装进 C# 桌面壳做音乐后期的人常遇到这种场景拿到一段带鸟鸣、路声的现场录音想把干净人声抠出来重新混音或者想扒一首歌的分轨提取鼓点和贝斯。SpleeterGUI 就是干这事的——用 C# 写的桌面工具底层调用 Deezer 开源的 Spleeter 音源分离引擎把歌曲拆成鼓点、贝斯、人声等独立轨道。它解决的痛点很直接原版是 Python 命令行要装依赖、记参数C# 打包后双击就能用也便于集成进自己的工具链。适合音频后期、音乐制作人以及想参考 C# 调用 Python 推理方案的程序员。注意这里的“分离”不是简单滤波而是模型对频谱的掩码重构理解这点对后面调参很关键。2. 分离原理与模型选型2-stem、4-stem、5-stem 该选哪个2.1 Spleeter 的 U-Net 分离原理为什么它不是普通滤波器Spleeter 的核心是一个 U-Net 结构的卷积神经网络输入是音频的短时傅里叶变换STFT幅度谱输出是对每个声源的掩码。处理时先对音频加窗分帧做 STFT得到二维的时频图网络预测每个时频单元的“属于某个声源的概率”然后把原频谱乘上这个掩码再通过逆 STFT 重构出该声源的波形。因为掩码是学习出来的所以它能把同频段重叠的音色分开这一点传统高通、低通、噪声门都做不到。鸟鸣和人声都在中高频滤波器一刀切会把两者都砍掉而 Spleeter 能靠上下文的声学特征把它们掰开——当然掰得干不干净取决于训练数据和模型复杂度。理解 Spleeter 的瓶颈也很重要。它训练的素材以 44.1kHz 采样率的主流流行音乐为主模型对非音乐的语音、纯环境声并不擅长。比如你丢一段演讲录音进去想“去鸟鸣”模型会把人声以外的一切都归到伴奏轨道鸟鸣、路声、喇叭声全黏在一起。我一开始在 SpleeterGUI 里去录音里的喇叭声结果喇叭声确实从人声里消失了但和贝斯、鼓声混在同一轨并没有“干净去除”。这个认知要放在前面否则期望会落空。模型训练数据决定了它的“音源”概念。Spleeter 能较好分离的声源是鼓、贝斯、人声、钢琴、其他乐器组。如果你输入一段只有一个人讲话的录音它依然会尝试输出一个“伴奏”轨道里面全是环境噪声和语音残留。所以在使用 SpleeterGUI 之前先判断你要处理的东西是不是音乐或带音乐伴奏的录音如果是纯语音降噪建议去找专门的语音增强模型而不是用音源分离工具硬扛。2.2 模型选型2-stems、4-stems 还是 5-stemsSpleeter 官方提供了 2-stems人声/伴奏、4-stems鼓点/贝斯/人声/其他、5-stems鼓点/贝斯/人声/钢琴/其他三种预训练模型。选型不复杂核心看你要拿分离结果做什么。下面这个表是我在 GUI 里直接作为“帮助文档”展示给用户的模型输出轨道典型用途处理速度2-stemsvocals, accompaniment去人声做伴奏、提取人声最快内存占用低4-stemsdrums, bass, vocals, other重新混音、扒带、去环境声中等5-stemsdrums, bass, vocals, piano, other需要钢琴轨的翻奏、混音最慢内存占用高我的经验如果只是做卡拉 OK 伴奏用 2-stems如果做 remix 或需要单独调鼓点和贝斯用 4-stems钢琴是刚需才上 5-stems。注意 5-stems 模型文件比 4-stems 大不少首次运行要下载权重网络不好时可能卡很久。SpleeterGUI 里最好把模型下载做成手动触发的并且在界面显示下载进度否则用户以为程序卡死了。这是 C# 开发里最常见的“黑匣子”问题——子进程在跑UI 不知道就会表现成假死。还有一个容易忽略的点stems 数量影响输出轨道的命名。2-stems 输出vocals.wav和accompaniment.wav4-stems 输出vocals.wav、drums.wav、bass.wav、other.wav5-stems 多一个piano.wav。如果你的后续脚本依赖文件名这部分是硬编码的升级模型后要检查一致性。在 C# 源码里模型选择通常只是一个枚举到字符串的映射。我一般会写一个静态方法供 UI 和进程封装层共用public static string GetSpleeterModel(int stems) stems switch { 2 spleeter:2stems, 4 spleeter:4stems, 5 spleeter:5stems, _ throw new ArgumentOutOfRangeException(nameof(stems)) };这个方法把下拉框里的数字变成 Spleeter CLI 接受的模型名。注意spleeter:2stems里的冒号是必需的少一个字符都会让 Separator 报“model not found”。我在给别人 review 代码时见过有人把冒号替换成下划线导致一启动就崩——这类低级错误最好用单元测试拦住。2.3 音频格式、采样率与输出参数分离前先做归一化Spleeter 官方要求输入音频被重采样到 44100 Hz输出默认是 WAV 格式16-bit PCM。GUI 上常见可调参数有三个stems 数、输出格式、输出码率。输出格式支持 WAV 或 MP3如果选 MP3码率参数才有效常见 192k 或 320k。我看到不少新手直接拿微信语音的 32kHz 音频去分离出来的结果高频毛刺严重这不是模型问题是采样率没对齐。SpleeterGUI 里最好在调用前自动重采样先用 NAudio 读取音频如果采样率不是 44100就先转格式再丢给 Python 进程。这个预处理比调任何参数都重要。常见的转换命令是用 ffmpeg 兜底因为 C# 侧用 NAudio 解码 MP3 可以但遇到 HE-AAC、Opus 这些格式还是会翻车。我在 GUI 里会封装一个方法把任意输入先转成临时 WAVffmpeg -i input.mkv -vn -ar 44100 -ac 2 -sample_fmt s16 temp.wav参数说明-vn表示忽略视频轨防止带画面的 MV 把视频流也读进来-ar 44100强制重采样率-ac 2转成双声道-sample_fmt s16输出 16-bit PCM。转换后的临时 WAV 放在系统临时目录分离完成后再删除。这样即使原文件是 48kHz 的 FLAC 或者 22.05kHz 的语音录音也能保证 Spleeter 拿到它最熟悉的输入格式。位深方面建议输出用 16-bit 或 24-bit。16-bit 兼容性好24-bit 动态范围更宽适合后续进 DAW 做混音。如果你的拖入文件是 MP3 或 AAC需要先解码成 PCM这一步 ffmpeg 也会自动完成。还有一点Spleeter 引擎内部固定用 44100 Hz 的傅里叶变换窗口所以输出文件的采样率也是 44100 Hz。如果你希望最终工程是 48kHz分离后还要在 DAW 或转码工具里再重采样一次别在 GUI 里加一个“输出采样率”选项——那是无效参数我最初加过后来发现底层根本不会变干脆去掉免得用户以为模型坏了。3. 拆解 SpleeterGUI 的 C# 源码调用链、参数传递与进度回调3.1 源码工程结构界面、进程封装、Python 脚本怎么分工一个能跑的 SpleeterGUI 不会把所有逻辑塞进 WinForms 的按钮事件里。我见过最省心的工程分四层UI 层MainForm.cs、进程封装层SpleeterProcess.cs、配置层AppConfig.cs、外部脚本spleeter_run.py。目录大致这样SpleeterGUI/ ├── SpleeterGUI.sln ├── SpleeterGUI/ │ ├── MainForm.cs │ ├── SpleeterProcess.cs │ ├── AppConfig.cs │ └── bin/Release/ ├── python/ │ └── spleeter_run.py └── models/ # 首次运行时由 spleeter 自动下载MainForm 只负责收集用户输入拖入的音频路径、stems 下拉框、输出目录、GPU 开关。点“开始分离”后它把参数塞进一个AppConfig对象然后调用SpleeterProcess.Start(config)进程封装层再启动 Python。这样 UI 线程不会被阻塞分离过程中用户可以取消或同时处理下一首。这个分层是 C# 上位机开发里的老套路界面归界面业务归业务进程归进程。别在按钮点击事件里WaitForExit那是典型的入门翻车点界面直接卡死连取消都点不了。Python 脚本那边不要写太复杂它只做三件事解析参数、调用spleeter.separate、把异常以 UTF-8 打到 stderr。核心代码很短但要注意 Windows 下 Python 默认编码我一般会在脚本第一行加# -*- coding: utf-8 -*-并且在重定向输出时显式指定编码否则 C# 读到的中文路径会变成乱码。# spleeter_run.py import argparse import sys from spleeter.separator import Separator def main(): parser argparse.ArgumentParser() parser.add_argument(-i, --input, requiredTrue, helpinput audio path) parser.add_argument(-o, --output, requiredTrue, helpoutput directory) parser.add_argument(-m, --model, defaultspleeter:4stems, helpmodel name) args parser.parse_args() separator Separator(args.model, stft_backendlibrosa) separator.separate_to_file(args.input, args.output) return 0 if __name__ __main__: sys.exit(main())这个脚本不输出百分比但 Python 的异常堆栈会走 stderr被 C# 重定向读到。stft_backend可以设为tensorflow或librosa某些环境用librosa能绕开 TensorFlow 算子的兼容问题。注意separate_to_file的第二个参数是输出目录不是文件路径它会在目录下再建一个以模型名命名的子文件夹。我在 C# 侧处理输出文件时会等这个子目录出现而不是等固定的文件路径否则可能拿到空结果。3.2 C# 启动 Python 子进程参数用引号包住输出用异步读最核心的一段代码在SpleeterProcess.cs我习惯写成这样public async Taskint StartAsync(AppConfig cfg, CancellationToken token) { var psi new ProcessStartInfo { FileName cfg.PythonPath, Arguments BuildArguments(cfg), UseShellExecute false, RedirectStandardOutput true, RedirectStandardError true, CreateNoWindow true, StandardOutputEncoding Encoding.UTF8, StandardErrorEncoding Encoding.UTF8 }; using var proc new Process { StartInfo psi }; proc.OutputDataReceived (_, e) OnOutput(e.Data); proc.ErrorDataReceived (_, e) OnError(e.Data); proc.Start(); proc.BeginOutputReadLine(); proc.BeginErrorReadLine(); await proc.WaitForExitAsync(token); return proc.ExitCode; }这里的三个细节决定你后面能不能睡个好觉。第一UseShellExecute false必须设否则无法重定向输入输出。第二输出和错误流必须异步读因为 Spleeter 在处理时会打很多日志如果不用BeginOutputReadLine管道缓冲区很快被写满子进程会阻塞表现就是“分离到一半卡住”。第三StandardOutputEncoding和StandardErrorEncoding在 .NET 里要显式设成 UTF-8否则从 Python stderr 读到的FileNotFoundError会变成乱码排查时非常痛苦。BuildArguments这个函数是拼参数字符串我用的是Liststring加双引号private static string BuildArguments(AppConfig cfg) { var args new Liststring { $\{cfg.ScriptPath}\, $-i \{cfg.InputFile}\, $-o \{cfg.OutputDir}\, $-m {GetSpleeterModel(cfg.Stems)} }; if (cfg.Bitrate 0) args.Add($-b {cfg.Bitrate}k); return string.Join( , args); }参数说明-i是输入音频-o是输出目录-m是模型名-b是 MP3 码率。每个路径都用引号包住是为了防 Windows 路径里的空格。很多人的程序在C:\Users\张三\My Music这类路径上莫名其妙失败十有八九是引号没包。如果你在 C# 里用了ProcessStartInfo.ArgumentList属性那引号可以省略但注意这个属性只在 .NET Core 3.0 及以上才有如果目标框架是 .NET Framework 4.8还是老老实实用Arguments字符串拼。3.3 进度回调与取消Spleeter 没有百分比但你能做出“假进度”Spleeter 本身不输出百分比它只在分离开始和结束时打一行Separating ...。所以 GUI 里的进度条有两个选择要么做一个不确定进度条ProgressBarStyle.Marquee要么自己估算。我一般用后者先拿 NAudio 读出音频总时长再按 Spleeter 的大体处理速度估算比如 10 分钟音频大约需要 40 秒进度条每 500ms 加 1%最后 5% 留成缓冲。这不是精确的但用户至少知道程序没死。千万别做“到 99% 再卡 10 秒”的进度条那会被骂。取消也不复杂WaitForExitAsync(token)配合CancellationTokenSource点“取消”时先proc.Kill(entireProcessTree: true)再清理临时 WAV。注意Kill要带entireProcessTree: true否则 Python 的子进程比如 ffmpeg还活着临时文件可能被占用删不掉。这是血泪经验我第一次没加这个参数临时目录里堆了几十 GB 的残留音频。3.4 输出文件的后处理文件移动、重命名与错误日志分离完成后Spleeter 会在输出目录下按model_name/生成子目录比如output/4stems/vocals.wav。这个结构对用户不友好我一般会在 C# 里把结果移动到用户选定的文件夹并把4stems这一层去掉。移动前先校验四个轨道文件都存在如果缺一个说明分离中途失败了这时候不要用用户手动重试直接把 stderr 日志写到一个spleeter_errors.log文件并在界面上弹一个带日志路径的对话框。用户可以把 log 发给你你一眼能看出是 Python 模块找不到还是 OOM。这里有个小坑Spleeter 输出文件名是全小写Vocals.wav不会出现。如果你的后续流程是大小写敏感的比如 Linux 服务器上传没问题但在 Windows 上后续用 DAW 打开时没什么区别。如果做自动重命名最好先确认目标文件名的唯一性避免覆盖。我就是因为在移动文件时用了File.Move(src, dest, true)结果把用户之前手动整理好的同名文件直接覆盖了。现在我的习惯是先检查目标目录是否存在同名文件有就加_1后缀不做覆盖。4. 避坑指南SpleeterGUI 最常见的 5 个运行故障与排查下面的五条不是我从文档里抄的而是我在维护一个内部工具时从用户反馈里一条条筛出来的。每条按现象、原因、解决的顺序写方便你照着排查。前两条问题最隐蔽后三条基本是环境问题。4.1 现象双击 exe 没反应进程一闪而过原因GUI 主窗体初始化时抛了未处理异常最常见的是目标机器缺 .NET 运行环境或者程序集加载失败。WinForms 默认不弹窗直接进程退出。解决在Program.cs的Main方法里包一层全局异常捕获把异常写入crash.log再MessageBox.Show。[STAThread] static void Main() { Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); Application.ThreadException (s, e) LogAndShow(e.Exception); AppDomain.CurrentDomain.UnhandledException (s, e) LogAndShow(e.ExceptionObject as Exception); Application.Run(new MainForm()); }这个代码虽然简单但能救回至少一半事故。用户拿到一个 exe双击没反应第一反应是“这软件是坏的”有了crash.log你才能远程定位是缺 VC 运行库还是 .NET 版本不对。另外发布时建议用 .NET 自包含模式把运行时一起打进去虽然体积大几十 MB但省去对方装框架的麻烦。还要检查目标平台如果用 x86 发布而用户系统是 x64程序能启动但加载到某些依赖库时可能直接崩建议一律 x64。4.2 现象分离结果全是金属声、机器声人声像外星人原因最常见的是 TensorFlow 和 Spleeter 版本不匹配或者模型权重下载不完整。Spleeter 官方曾有过 TensorFlow 2.x 与 1.x 的 API 差异问题某些第三方打包的版本用了旧模型文件推理时输出 NaN。解决先确认 Python 环境的包版本pip show spleeter tensorflow然后跑官方示例音频spleeter separate -i 2stems_example.mp3 -o output -p spleeter:2stems。如果官方示例没有金属声而你的音频有就是输入的问题。另外别把采样率低于 44.1kHz 的音频直接丢进去先重采样。还有一点处理 5-stems 时钢琴和其他乐器在频谱上重叠多掩码不干净听起来会有梳状滤波感这不是 bug是模型局限。我遇到过最诡异的一次分离结果偶尔正常偶尔全是噪声最后发现是内存不足导致 TensorFlow 的算子执行出错但 Python 没抛异常继续写入了 NaN 的 PCM 数据。C# 侧要主动检查输出 WAV 文件的静音段长度如果某个轨道全是-32768或32767这种极值就判定失败并提示用户重试。4.3 现象处理长音频时内存暴涨系统卡死原因Spleeter 的 STFT 特征和掩码计算需要大量内存。处理 10 分钟的音频 4-stems峰值占用可能超过 1.5GB如果 C# GUI 本身又占一块32 位的 exe 只有 2GB 地址空间直接 OutOfMemory。解决在 Visual Studio 的项目属性里把目标平台设为 x64不要用 AnyCPU。另外在 GUI 里对输入音频长度做限制或拆分。我一般超过 15 分钟的音频会先切成 5 分钟一段分别处理再拼接。拼接时要注意交叉淡入淡出直接在波形中间接起来会有咔哒声用 100ms 的交叉淡化能消除。还有一点Windows 的页面文件如果太小Python 进程可能在分配内存时被杀。常见表现是 C# 的WaitForExitAsync正常返回但 Python 退出码是负数比如 -1073741571超时被杀。解决办法是让 GUI 在启动前检查系统可用内存少于 2GB 就直接提示用户关掉其他大程序而不是硬扛。4.4 现象输入路径含中文或空格时Python 报找不到文件原因Windows 控制台默认代码页是 GBKPython 3 默认 UTF-8重定向输出时如果是 GBK中文路径会被转得面目全非或者 C# 拼接参数时没加引号导致路径里的空格把参数截断。解决所有命令行参数用双引号包住C# 里StandardOutputEncoding设 UTF-8Python 脚本内用os.path.abspath再解码。更稳的方案是让 Python 脚本从一个临时配置文件读取输入输出路径而不是从命令行传。例如在 C# 里写一个 JSON 配置Python 读取后路径就不受编码影响。这是我从一个 C# 上位机项目里学来的传参只传配置文件路径具体参数全放文件里兼容性最好。我在自己的工具里是这么做的C# 把参数写成 UTF-8 的config.json然后命令行只传这个文件的路径。Python 端用json.load读入路径用pathlib.Path处理。这样即使目录叫D:\音乐\新建文件夹 (2)也不会出问题。如果坚持用命令行传参记得在 C# 侧先用Path.GetFullPath规范化路径再包引号。4.5 现象选了 GPU 反而比 CPU 慢或者 GPU 设备找不到原因TensorFlow 的 GPU 支持需要 CUDA、cuDNN 版本严格匹配而且显存不足时会自动回退到 CPU日志里有一行Could not create CUDA device。解决先用nvidia-smi确认显卡和驱动版本再按 TensorFlow 官方要求装对应 CUDA。C# 侧可以在启动前检测环境nvidia-smi --query-gpuname,memory.total --formatcsv如果这条命令返回错误就不要让用户勾选 GPU。另外Spleeter 模型很小普通 GTX 1650 和 i7 的 CPU 差距不大短音频用 GPU 反而不值。我会在 GUI 的“GPU 加速”旁边加一句提示“仅建议 10 分钟以上音频开启”省得用户以为我写了个假开关。GPU 检测的另一个坑是笔记本双显卡TensorFlow 可能选到核显或集成显卡导致推理极慢。C# 侧可以用CUDA_VISIBLE_DEVICES环境变量强制指定显卡序号但不同机器的序号不一样所以最好把显卡列表查出来让用户选。如果没有nvidia-smi那基本可以断定这台机器不具备 TensorFlow GPU 加速条件直接禁用 GPU 开关。5. 实战进阶用分离轨道做重新混音与去除环境噪声5.1 重新混音把 4-stems 拉进 DAW 后怎么处理分离出的 4 轨是干声没有混响和压缩。做 remix 时我一般是先给鼓点轨加 transient shaper 增强打击感贝斯轨侧链压缩到鼓点上人声轨修一修音准再挂混响。注意 4 个轨道采样率一致工程新建时选 44.1kHz否则和弦对不齐。分离出的鼓点轨往往还带一点底鼓的谐波泄漏可以用高通滤波器在 40Hz 处切掉次声波减少共振。5.2 去除鸟鸣、路声和喇叭声不要直接删 other 轨前面说过环境声被 Spleeter 丢进 other 轨。正确做法是先用 4-stems 分离把 other 轨单独导出听如果环境声集中在 other就在 DAW 里对 other 轨做频段衰减比如鸟鸣集中在 3-8kHz再和原混合镜像相位相减或者直接不输出 other 轨只用 vocals、drums、bass 三轨重建伴奏。如果环境声也漏进了人声轨那就只能靠 RX 的 spectral repair 修补。Spleeter 不是万能去噪器这是模型边界早点接受它。更稳妥的做法是处理前先试听把 input 拖进 DAW用频谱图看看鸟鸣、喇叭声出现在哪个时间点再做局部去除。SpleeterGUI 的批量模式适合直接扔十首歌但处理现场录音时我建议一首首看不要迷信一键分离。5.3 验证分离质量的土办法相位抵消和频谱对比我每次分离完不会急着用耳朵判断而是用相位抵消把原曲和分离出的所有轨道同时播放反转其中一轨相位如果剩下的是“空洞的混响尾音”说明分离主要成分正确如果听到明显的乐器残留或刮擦声说明掩码不干净。再用频谱图看低频贝斯轨在 200Hz 以下应该是连续的人声轨在低频段应该很干净否则就是交叉泄漏。这个方法不依赖 ground truth比看 SDR 指标更直观。我用 SpleeterGUI 有半年了最大的教训是永远不要在一个危险的安装环境里测试发布包。每次发新版本我在干净的 Windows 虚拟机上跑一遍“全默认流程”不做任何手动配置很多用户反馈的问题其实一步就能复现。希望帮到你。本文还有配套的精品资源点击获取