本文记录了这套自制程序从无到有的过程、每个程序开发中遇到并解决的具体问题、
以及验证方式。所有结论都基于实际测试,不是推测。
================================================================================
一、总览
一共 6 个程序 + 1 套通用安装器。
程序名 版本 用途
1 音频编码器 1.1 只编码音轨, 不需要视频文件 (HandBrake 的音频
模块只吃视频文件, 这个把音频能力单独拿出来用)
2 批量重命名 2.0 批量改名 + 用 ffprobe 扫描内容生成规范文件名
3 FFmpeg 转码工具 2.0 视频转码, 支持显卡硬件加速, 批量队列
4 音频上混器 1.0 2.0 上混成 5.1 / 7.1 / 天空声道 (最高 7.1.4)
5 声道分离器 1.0 把多声道音轨拆成每声道一个单声道 WAV
6 音频混流器 1.0 把多条单声道拼成一条多声道 (最高 7.1.4)
7 通用安装器 – 一套向导代码, 用条件编译分出上面 6 个安装程序
当前状态:
· 6 个程序都装在 C:\Program Files<程序名>
· 每个都有「便携版」和「安装版」两套包, 已归档到 桌面\自制程序包*.rar
· 全部为中文界面, 标题栏都带署名
================================================================================
二、技术底座 (为什么拷到别的电脑就能用, 不用装环境)
2.1 编译环境
编译器 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe
(Windows 自带的那个, 版本 4.8.9221 —— 全程没装过 Visual Studio,
没装 .NET SDK, 没用过 NuGet)
目标 .NET Framework 4.0 (运行时 v4.0.30319)
平台 /platform:anycpu
语言 C# 5 —— 没有字符串内插、没有 ?. 、没有 async/await
2.2 为什么不用装运行时 (实测确认)
· 目标框架选最老的 4.0 而不是 .NET 5/6/8。
.NET Framework 的 4.x 是原地升级的, 4.0 编的程序在 4.0~4.8.1 上都能跑;
Windows 10 1903+ / Windows 11 内置 4.8。本机是 4.8.09221。
· 只引用微软自带的基础库, 一个第三方包都没有。实测每个 exe 的依赖:
mscorlib / System / System.Windows.Forms / System.Drawing
(+ System.Core, 音频编码器和批量重命名还用了 System.Web.Extensions 解析 JSON)
全部是框架自带的。
· 编译成 AnyCPU。实测 CLI 头标志位 = 0x1 (纯 IL, 不要求 32 位),
所以 32 位系统上以 32 位跑、64 位上以 64 位跑, 一个文件两用。
2.3 ffmpeg 为什么不用装 VC++ 运行库 (实测导入表)
用的是 gyan.dev 静态版。实测 ffmpeg.exe / ffprobe.exe 的导入表里:
· 一个 av*.dll 都没有 (全部静态链进去了)
· 一个 VCRUNTIME140.dll / MSVCP140.dll 都没有
· 只依赖 msvcrt.dll 等 Windows 自带 DLL
· 子系统版本 5.0 (Windows 2000 级别), 兼容面很宽
msvcrt.dll 是操作系统本身的一部分, 所以不需要任何运行库。
2.4 边界 (如实说明)
· 只支持 Windows (WinForms + csc 是 Windows 专用)
· Windows 7 需要手动装一次 .NET Framework 4.x (系统没内置)
· 内置 ffmpeg 是 x64, 32 位 Windows 上跑不了; 音频编码器里有 PE 头位数
检测, 不匹配会提示去下 32 位版本放进 ffmpeg32\ 目录
· ARM64 靠 x64 模拟, 能用但慢一些
================================================================================
三、逐个程序的开发与调试记录
- 音频编码器 版本 1.1
用途 只编码音轨。输入可以是视频文件也可以是纯音频文件, 输出 .mka / 各编码
原生容器。支持 E-AC-3 / AC-3 / TrueHD / DTS / AAC / FLAC / Opus /
MP3 / MP2 / WAV 以及原样直通。
位置 C:\Program Files\音频编码器
源码 C:\Users\何凯\Documents\音频编码器\src\AudioEncoder.cs
【开发中按需求追加的功能】
· 一次把一条音轨编码成多种格式/码率 (附加编码方案), 输出可合并成一个 .mka
· 音轨勾选, 勾几条编几条
· 输出音轨的语言和标题元数据
· 音频位深选择 (WAV/FLAC 有意义, 有损格式没有位深所以不显示)
· 5.1 声道区分 side 和 back (5.1 与 5.1(side) 的环绕声道名不同)
· 载入源音频时检测并标出各声道布局
· 独立的「警告 / 错误」栏 (中文)
· 标题栏加署名
【调试记录】
(1) 快速切换编码器时, 语言下拉框会莫名回退成「不设置」
→ 原因是可编辑 ComboBox 在句柄创建前设置 .Text 会被静默还原,
改成在 Load 之后再补一次 ApplyLanguageFromConfig()。
(2) 命令行参数越界崩溃
→ 合并 WAV 条目后流索引变了, 底层索引 copy 应映射到 10、
wav* 映射到 9, 原来的映射表没跟着更新。
(3) 2.0 小屏修复 (最重要的一项)
症状: 在 1920×1080 + 150% 缩放的屏幕上, 底部的
「开始编码」和日志栏被任务栏挡住, 点都点不到。
实测: 界面按 96 DPI 设计成 916×905, 放大 1.5 倍后是 1374×1357,
而屏幕工作区只有 1920×1008 (逻辑 1280×720)。
更糟的是 MinimumSize 也被放大到 1140×1050, 比工作区还高,
所以既放不下、又不让缩小。
修复: 所有控件搬进一个 AutoScroll 的滚动容器, 窗口尺寸再夹进
Screen.WorkingArea; MinimumSize 一起收, 不再被它顶出屏幕。
验证: 窗口变成 1396×1000, 完全落在工作区内; 滚轮每格 48px,
滚到底后按钮落在 Y=913~952 可见可点。
(4) 修完 (3) 后发现更隐蔽的问题: 窗口底边压在任务栏底下 48px
实测: 窗口底边 1056, 工作区底边 1008 —— 被挡 48px, 正好切掉按钮那排。
原因: 算标题栏高度用的是 Height – ClientSize.Height, 而这个函数在
窗口还没显示出来的时候跑, 差值恒为 0, 等于少扣了一个标题栏。
修复: 量不出来就按 SystemInformation 的边框尺寸估算;
并且在窗口 Shown 之后再夹一次 (那时才量得准)。
验证: 窗口 1396×1000、位置 (262,4)、底边 1004 < 1008, 一点没被挡。
(5) 内置 DPI 自检在 100% / 150% / 200% 之间反复切换, 确认幂等
(重复切到同一档结果不变, 不会累加缩放)。
【验证方式】
· 真实编码: 造含 2 条 AAC 音轨的 mkv → 点「全选」→ 输出
「双音轨测试.mka」, 里面是 2 条 E-AC-3, 时长正确
· 安装版: 装 → 启动 → 卸载, 目录/快捷方式/注册表全清干净
· 源码一度丢失 (桌面文件夹被删且回收站被清空), 最后从
自制程序包\音频编码器 便携版.rar 里把 src\ 提取了回来
- 批量重命名 版本 2.0
用途 批量改文件名。6 条通用规则 (查找替换 / 截断 / 大小写 / 时间戳 /
序号 / 前后缀), 外加「按内容生成名称」—— 用 ffprobe 扫描视频真实
内容, 生成规范化的文件名。
命名格式: 标题 [集数][来源+位深_高度p][视频编码_音频_字幕]
位置 C:\Program Files\批量重命名
源码 C:\Users\何凯\Documents\批量重命名\src\BatchRename.cs
【开发中解决的问题】
(1) 第一版界面太潦草 → 整体重写了 GUI。
(2) 命名规则必须和实际文件对得上。用真实文件验证:
「[MAI] Suzumiya Haruhi no Yuuutsu [26][Ma10p_2160p][x265_3flac_ass].mkv」
用这套规则重新生成, 结果是逐字节完全相同。
(3) 发现一个原始文件名不可信的例子:
桌面上那个 Clannad 的 mkv, 文件名写的是「2flac_ac3」, 但实际扫描
出来有 7 条音轨 (flac/flac/ac3/dts/truehd/pcm_s24le/eac3)。
扫描逻辑是对的 —— 是文件名旧了。
(4) 2.0 小屏修复 (最严重的一项)
症状: 底部的「刷新预览 / 执行重命名 / 撤销上次」整排点不到。
实测: 窗口 1522×1106, 三个按钮在 Y=1092~1137 ——
比窗口自己的底边 (1106) 还低, 更远低于工作区底边 (1008)。
等于这个工具在 150% 缩放的屏幕上根本没法用。
修复: 同音频编码器 —— 可滚动容器 + 窗口夹进工作区。
(5) 新增拖放功能
原版根本没有拖放。2.0 加了「把文件夹(或文件夹里任意文件)拖进窗口」,
自动填好文件夹并刷新预览。
(6) 一次”疑似 bug”的排查 —— 结论是没问题
源码注释里写着「Scale() 会把字体一起放大」, 我也一度怀疑字体被
双重放大了。写了个最小复现实测:
Scale(1.5) 之前: Form 9pt, Label 9pt, Label 高度 24
Scale(1.5) 之后: Form 9pt, Label 9pt, Label 高度 36
结论: Scale() 并不放大字体, 只缩放控件框。
所以这不是 bug, 我把源码里那句错误注释纠正了 —— 没有去”修”一个
不存在的问题。
【验证方式】
· 造两个真实 mkv, 走完整流程:
刷新预览 → 共 2 个文件, 将重命名 2 个
执行重命名 → aaa.mkv 变成「aaa [240p][x265].mkv」
bbb.mkv 变成「bbb [360p][x264].mkv」
撤销上次 → 两个都还原成 aaa.mkv / bbb.mkv
· 安装版装-启-卸全程验证
- FFmpeg 转码工具 版本 2.0
用途 视频转码。H.264 / H.265 / AV1 / VP9 / MPEG-4, 显卡硬件加速
(NVENC / QSV / AMF), CRF 与目标码率双模式, 批量队列, 中文警告栏。
位置 C:\Program Files\FFmpeg 转码工具
源码 C:\Users\何凯\Documents\FFmpegTranscode(Core.cs, MainForm.cs)
【起因与还原过程】
桌面上原来只有别人做的编译好的 FFmpegGui.exe, 没有源码。
我对它做了逆向分析:
· 用反射读出窗体结构: 全部控件字段名、方法名
· 把每一个下拉框的选项、每个编码器的 CRF 范围/预设/质量参数全部 dump 出来
· 按那份清单 1:1 重写
结果: 原程序只有 6 个软件编码器, 但代码里有硬件加速探测逻辑。
【发现原程序的一个缺陷】
原版在这台机器上报告「没有硬件编码」, 但我实测 h264_qsv 是可用的
(ffmpeg -f lavfi … -c:v h264_qsv 返回 0)。
重写版改成启动时真编一帧实测每个编码器, 正确识别出 Intel QSV 并提供
QSV 编码选项。实测 -hwaccel qsv -c:v h264_qsv 比软件编码快一倍。
【调试记录】
(1) 字体双重放大 (1.0 开发时踩的)
我一开始把字号也乘了 DPI 缩放系数, 结果字比控件框还大、标题被裁掉。
原因: 字号是”点”(物理单位), GDI+ 已经按 DPI 放大过一遍了, 再乘一次
就是双重放大。
修复: 只放大控件框, 不缩放字号。
(2) 一个会骗人的交付陷阱: Vorbis / Opus 塞进 MP4
实测: -c:a copy 把 Vorbis 音轨复制进 .mp4, ffmpeg 返回 0 表示成功,
产出的文件也确实是 vorbis-in-mp4 —— 但大多数播放器打不开。
修复: 建了一张按容器的音轨兼容表, 转码前用中文提醒
「MP4/MOV 只有 AAC / MP3 / AC-3 / E-AC-3 / ALAC 靠得住」,
并建议改 .mkv 或改 AAC。
(3) 顺序 bug: 文件列表先于 ffmpeg 检测就绪
带进来的文件在 ffmpeg 检测完成前就被探测了, 于是误报
「找不到 ffprobe」, 而且容器兼容检查拿不到音轨信息。
修复: 检测完成后再收命令行带进来的文件。顺带把那条误报从
[错误] 降级成 [警告]。
(4) 藏在未选中标签页里的文本框, 句柄还没建时写警告会丢内容
修复: 同时存一份 StringBuilder, 句柄没建好就直接整体赋值
(WinForms 会缓存, 建句柄时自动补上)。
验证: 单独写了个最小复现确认这个 WinForms 机制可靠。
(5) 警告栏的内容同时镜像到运行日志, 不切标签页也能看到。
(6) 2.0 小屏修复
实测 1.0: 窗口 1837×1106, 「开始转码」在 Y=10021065 ——649 可见。
只有顶边 6px 露在任务栏上方, 基本点不到。
修复: 同前, 可滚动容器 + 夹进工作区。
验证: 窗口 1837×1000, 滚到底后按钮 Y=586
【验证方式】
· 真实转码: 「源片 测试.mp4」→「源片 测试_转码.mp4」
(中文 + 空格文件名), 命令与输出都正确
· QSV 硬编路径实测通过 (h264_qsv + 全局质量 23)
· 安装版装-启-卸, 其中 ffmpeg\bin 子目录递归复制正常
- 音频上混器 版本 1.0
用途 用 ffmpeg 的 surround 滤镜把立体声上混成多声道。
目标: 4.0 / 5.1 / 7.1 / 5.1.2 / 5.1.4 / 7.1.4
位置 C:\Program Files\音频上混器
源码 C:\Users\何凯\Documents\UpmixTool(UpmixCore.cs, MixForm.cs)
【技术验证阶段挖出的坑 (这些决定了工具怎么设计)】
(1) surround 滤镜的输出上限就是 7.1
实测支持的输出布局: 3.0 / 4.0 / 5.0 / 5.1 / 5.1(side) / 6.1 / 7.0 / 7.1
不支持: 5.1.2 / 5.1.4 / 7.1.4 / 9.1.6 (报 Unsupported upmix)
→ 天空声道只能先用 surround 上混到基础布局, 再用 pan 派生
TFL / TFR / TBL / TBR。
(2) 【最坑的一个】E-AC-3 / AC-3 / TrueHD / DTS 会静默降混
它们声称支持, ffmpeg -f null 也返回成功 —— 但编成真实文件一探测,
全是 6 声道。喂 7.1 给它, 不报错, 直接偷偷降成 5.1。
实测矩阵 (真编码后用 ffprobe 数声道):
5.1 7.1 5.1.2 5.1.4 7.1.4
eac3 OK 降为6 降为6 降为6 降为6
ac3 OK 降为6 降为6 降为6 降为6
truehd OK 降为6 降为6 降为6 降为6
dca OK 降为6 降为6 降为6 降为6
flac OK OK OK 失败 失败
aac OK OK 失败 失败 失败
libopus OK OK 失败 失败 失败
pcm_* OK OK OK OK OK
另外意外发现: TrueHD 和 DTS 在 ffmpeg 里最高也只有 5.1
(帮助文本里写的支持列表就到 5.1(side))。
→ 工具不照抄 ffmpeg 帮助, 而是按这份实测结果建能力表:
选了 7.1 就只列出 FLAC/AAC/Opus/PCM, 不让用户踩坑。
并且每次编完用 ffprobe 复核输出真实声道数, 不一致就报警。
(3) TrueHD / DTS 只认 5.1(side), 不认 5.1
→ 5.1 目标统一用 5.1(side) 作为内部规范形式。
(4) 多声道 PCM 必须用 .wav
实测: 12 声道 PCM 放进 .mka, ffprobe 读回来布局是 unknown (标签丢了,
播放器只能瞎猜); 放进 .wav 是正常的 7.1.4。
→ 程序按编码器自动选容器。
(5) surround 不接受的输入布局
它只吃: stereo / 2.1 / 3.0 / 5.0(side) / 5.1 / 5.1(side)
不接受: mono / quad / 4.0 / 5.0 / 6.1 / 7.1
→ mono 先转立体声; 4 声道之类不支持的会先降混, 并在中文警告栏里
明确说明「这样会丢环绕信息」。
【验证方式】
· 2.0 → 5.1 E-AC-3 : 6 声道 5.1(side) ✓
· 2.0 → 7.1 FLAC : 8 声道 7.1 ✓
· 2.0 → 7.1.4 PCM : 12 声道 7.1.4 ✓ (自动选了 .wav)
· 逐声道量电平确认上混结果不是静音:
FL/FR -24.2dB, FC -62.5dB, SL/SR -53~54dB, LFE -70.8dB
(LFE 偏低是正常的 —— 测试源是纯正弦, 本来就没低频)
· 4 声道源走降混回退路径 + 中文警告 + 输出 6 声道 ✓
- 声道分离器 版本 1.0
用途 把一条多声道音轨原样拆成每个声道一个单声道 WAV, 文件按声道名命名
(01_FL.wav / 02_FR.wav / … / 12_TBR.wav), 支持 2.0 到 7.1.4。
位置 C:\Program Files\声道分离器
源码 C:\Users\何凯\Documents\SplitTool(SplitCore.cs, SplitForm.cs)
【技术验证阶段挖出的坑】
(1) channelsplit 的所有输出 pad 必须全部连接
只映射一部分会报「Filter ‘channelsplit’ has output 1 (FR) unconnected」,
所以做不到”只拆其中几个声道”。
(2) 【静默陷阱】布局声道数和源对不上时, ffmpeg 不报错
实测: 6 声道源配 7.1 布局, ffmpeg 返回 0, 产出 8 个文件 ——
但其中 7 个是静音的 (-91dB), 只有 1 个有内容。
→ 工具在开始前硬校验「布局声道数 == 音轨实际声道数」, 对不上直接
拦下来并给出中文原因, 不产生任何输出。
另外每个 WAV 拆完还会用 ffprobe 复核是不是单声道。
(3) 技术路线选择: channelsplit 还是「多输出 + pan=cN」
两者都测了 —— 都比特级无损, 耗时也一样 (ffmpeg 共用解码器)。
最后选了后者, 因为它不依赖布局名: 9 声道、11 声道这类非标准配置
也能拆, 而 channelsplit 遇到没名字的布局就没辙。
(4) 声道名映射要精确
5.1 用的是 BL/BR (后置环绕), 5.1(side) 用的是 SL/SR (侧置环绕) ——
名字不同, 因为它本来就是不同的声道位置。
(5) 修了一个我自己写出来的 bug: ini 里存的布局设置被忽略
原因: 布局表要等 ffmpeg 检测完才从 ffmpeg -layouts 载入, 而读设置
是在那之前 —— 所以存进去的布局压根匹配不上, 实际跑的是”自动检测”。
修复: 把想要的布局名先记下来, 等布局表载入后再补选一次
(ApplyPendingLayout)。
这个 bug 是靠”误配拦截测试没触发”反向查出来的。
【验证方式】
· 2.0 源 → 01_FL / 02_FR ✓
· 5.1(side) 源 → 01_FL ~ 06_SR ✓
· 7.1.4 源 → 01_FL ~ 12_TBR, 12 个文件全为单声道 ✓
· 无损性: 源各声道 vs 拆出来的文件, 12 个 MD5 逐一完全一致
(拆分不重采样、不混音、不做矩阵运算, 就是原样搬样本)
· 中文命名: 01_前左.wav / 02_前右.wav / 03_中置.wav / 04_低频.wav …
· 误配拦截: 6 声道源强制指定 7.1 布局 → 弹出拦截提示, 不产生输出 ✓
- 音频混流器 版本 1.0
用途 把多条独立的单声道音频拼成一条多声道, 声道位置可自己指定,
最高 7.1.4。并且可以和《音频编码器》联动:
混流完成后询问是否打开编码器并导入结果。
位置 C:\Program Files\音频混流器
源码 C:\Users\何凯\Documents\MixTool(MixCore.cs, MixForm.cs)
【技术验证阶段挖出的坑】
(1) 【关键】join 滤镜会丢一帧, 必须用 amerge
实测: 2 声道时 join 正常; 但 12 声道时输出比源短 1024 个采样
(1.984s → 1.962667s, 正好一帧)。
试过用 apad 补齐 —— 补不回来, join 还是会裁。
换成 amerge 之后:
源 12 声道 7.1.4 MD5 = 51956a0e…
拆开再合回去 MD5 = 51956a0e… 完全一致
不只整轨, 12 个声道逐一对也都一致, 布局和采样格式也一样。
→ 全程用 amerge。
(2) 采样率不一致会被静默重采样
实测: 44.1k 和 48k 混在一起, amerge/join 会悄悄统一到第一个输入的
采样率, 不报错。
→ 给每条输入显式加 aformat 统一采样格式/采样率, 并在界面上明确警告
「这一步是重采样, 会有轻微损失; 想要完全无损请让所有输入保持同一
采样率」。
【调试记录】
(1) 【隐蔽 bug】联动弹窗不出现
我写的 Ui() 辅助函数在后台线程走的是 BeginInvoke —— 异步的。
完成回调里用它读复选框的值, 等读的时候值还是旧的, 于是if (!ask) return; 直接返回, 弹窗根本不弹。
症状很迷惑: 编译不报错、日志一切正常, 就是弹窗静默消失。
修复: 加了同步的 UiGet() (走 Invoke) 专门用来取控件值。
验证: 弹窗出现, 点「是」后编码器启动。
(2) 命令行传入文件夹不展开 (只认文件)
修复: AddPaths 里先判断是不是目录, 是就展开成里面的音频文件。
(3) 右下角状态显示「已找到」, 但下面的提示行还说「还没找到它」
原因: CheckEncoder 填完路径后没刷新提示行。
修复: 补一次 UpdateHint()。
【与声道分离器的无缝对接】
· 拖文件夹进去, 按文件名自动归位。认这些写法:
01_FL.wav / FL.wav / FL_前左.wav / 前左.wav / 1-FL
长的名字优先, 所以 TFL 不会被 FL 抢走。
· 输出路径会自动建议: 如果所有文件都在 xxx_声道\ 里, 输出就放在
上一级、名字去掉 _声道 —— 拆开→合回去是连贯的。
· 缺的声道会补静音 (不会让任务失败), 并提醒缺几个。
【验证方式】
· 往返测试 (用真实工具, 不是手工命令):
声道分离器拆开 12 声道源 → 音频混流器自动归位并合回去
→ 整轨 MD5 与源完全一致, 12 个声道逐一一致, 布局格式一致
· 2.0 目标: 自动匹配 2/2 个声道, 输出 2 声道 stereo, 校验通过
· 联动端到端: 混流完成 → 弹窗 → 点「是」→ 编码器启动
→ 编码器日志里写「已加入 1 个文件, 共 1 个。」(确认真的导入了)
· 安装版装-启-卸全程验证
- 通用安装器 (基础设施)
说明 一套向导代码 (Documents\Setup\Setup.cs), 用条件编译#if APP_AUDIO / #elif APP_RENAME / ... 分出 6 个安装程序。
页面 欢迎 → 使用声明 → 选择目录 → 选项(快捷方式) → 安装中 → 完成
特性 默认装到 C:\Program Files<程序名>, 需要时自动提权;
也可改选用户目录 (完全不需要管理员权限);
写注册表卸载项、创建桌面/开始菜单快捷方式、自带卸载程序。
【调试记录】
(1) 【最花时间的一个】卸载后目录残留
症状: 卸载完, 安装目录里还剩一个「卸载 xxx.exe」删不掉。
排查: 观察 57 秒, cmd 进程数一直是 0 —— 说明那个用来清理的
cmd.exe 压根没被启动。
根因: 清理用的批处理是在 Close() 之后才 Process.Start 的。
Close() 让消息循环退出、进程随即结束, 后台线程会被一起杀掉,
所以 Process.Start 经常来不及执行。
修复: 把「起外部清理进程」挪到 Close() 之前;
给 cmd 指定 %TEMP% 为工作目录, 批处理第一行加 cd /d
(不站在待删目录里, 否则 rmdir 会被占用);
再用 MoveFileEx(MOVEFILE_DELAY_UNTIL_REBOOT) 兜底登记到
下次开机再删。
验证: 卸载后约 2 秒清干净, 目录/快捷方式/注册表全没了,
临时 bat 也没残留。
(2) 装在 Program Files 里卸载不掉
症状: 从资源管理器双击卸载程序, 提示卸载完成, 但其实什么都没删。
原因: 卸载程序是 asInvoker 的, 双击不会提权, 删 Program Files 下的
文件全部失败, 而失败被 try/catch 吞掉了。
修复: OnNextUninstallClick 里补上和安装侧一样的判断 ——
目标目录不可写且当前未提权时, 用 runas 重新启动自己。
(3) 提权重启时 –quiet 没传过去
→ 静默安装到受保护目录时会莫名其妙弹出向导界面。已修。
(4) payload 只支持扁平复制, 不支持子目录
→ 改成递归复制 (保留目录结构) + 递归删除。
这样转码工具的 ffmpeg\bin*.dll 才能在安装后规整地待在子目录里。
(5) 两个安装包的声明页文案串味
音频编码器的声明里写着「批量重命名会真实修改文件名」。
→ 给每个程序加独立的 ExtraNote / KeepNote 文案。
(6) 文案错位: 卸载页写着「点击『下一步』开始卸载」, 但按钮叫「卸载」。
→ 改成「点击下面的『卸载』按钮开始卸载」。
(7) 确认过了: 卸载时点「否」会正确放弃, 卸载器留在原地, 再点一次能正常走完。
【验证方式】
· 6 个分支 (APP_AUDIO / APP_RENAME / APP_TRANSCODE / APP_UPMIX /
APP_SPLIT / APP_MIX) 全部编译通过
· 每个安装包都做过: 装 → 启动 → 卸载 的端到端验证
· 快捷方式的目标/起始位置/图标都对, 卸载时一并删除
· 用户目录安装 (不提权) 和 Program Files 安装 (提权) 两条路都验过
================================================================================
四、跨程序的通用问题与解决方案
这四个问题在多个程序里重复出现, 最后都收敛成了固定做法。
4.1 DPI 缩放: 只缩放控件框, 绝不乘字号
字号是”点”(pt) —— 物理单位, GDI+ 已经按 DPI 放大过一遍。
再乘一次缩放系数就会双重放大, 字比框还大、标题被裁。
做法: 界面按 96 DPI 手写坐标, 启动时把所有控件的 Bounds 乘缩放系数,
字号保持不动。AutoScaleMode 设为 None。
4.2 小屏 / 高缩放: 可滚动容器 + 夹进屏幕工作区
做法 (现在所有程序统一):
· 所有控件放进一个 _content 面板, 外面套一层 AutoScroll 的 _scroll
· 窗口尺寸夹进 Screen.WorkingArea, MinimumSize 一起收
· 在窗口 Shown 之后再夹一次 —— 首次在 OnHandleCreated 里量标题栏高度
会得到 0, 少扣一个标题栏就会压到任务栏底下
· 滚轮处理: 列表/表格自己需要滚动时让它滚自己, 不需要时把滚轮让给整页
· DataGridView 的表头高度 / 行高 / 列宽不跟着 DPI 走, 要显式重设
4.3 后台线程与界面: 取值必须同步
· 更新界面 (日志、进度、状态) 用 BeginInvoke 就行 —— 异步没关系
· 但要读回控件的值 (比如”用户勾了哪个框”), 必须用 Invoke ——
BeginInvoke 是异步的, 立刻读会拿到旧值。
· 这个坑在音频混流器上真实踩到过 (见 6.1)。
4.4 隐藏标签页里的控件句柄可能还没建
往「警告与错误」这类未选中标签页里的文本框写内容, 如果句柄还没创建,
AppendText 会出错或者丢内容。
做法: 同时存一份 StringBuilder, 句柄没建好就直接整体赋值
(WinForms 会缓存, 建句柄时自动补上)。
4.5 数据安全, 统一遵守
· 默认不覆盖同名文件, 自动加 (1) (2) 后缀
· 输出路径等于源文件时自动改名, 绝不覆盖源
· 卸载只删程序本身, 用户生成的文件和设置都保留
· 中止任务时把没写完的半个文件删掉
4.6 静默失败到处都有 —— 关键步骤一律复核
这套工具里最危险的不是”报错”, 而是”看起来成功了但结果是错的”。
已经发现的静默失败:
· ffmpeg 把 Vorbis/Opus 塞进 MP4 而且不报错, 文件大多播放器打不开
· E-AC-3/AC-3/TrueHD/DTS 声道数超限不报错, 偷偷降混成 5.1
· channelsplit 布局声道数不匹配不报错, 产出 7 个静音文件
· join 12 声道时悄悄丢掉末尾一帧
· 采样率不一致时悄悄重采样
做法: 每一步做完都用 ffprobe 复核真实结果 (声道数 / 布局 / 帧数),
不一致就在中文警告栏里说出来, 而不是假装成功。
================================================================================
五、环境与工具链踩坑记录 (PowerShell / Windows 相关)
这些不是程序本身的 bug, 但排查时反复遇到, 记下来省得再踩。
(1) PowerShell 5.1 读没有 BOM 的 UTF-8 .ps1 (含中文) 会解析错误
→ 所有 .ps1 一律存成 UTF-8 带 BOM。
(2) 双引号字符串里的 $变量: 会被当成作用域限定符
例: “$in:chl_out=7.1” 里的 $in: 被解析成作用域变量, 导致参数错乱。
→ 写成 ${in}:chl_out。
(3) 路径里的方括号 [ ] 会被当成通配符
例: 处理 “…7.1_声道\01_FL.wav” 时匹配到 0 个文件。
→ 一律用 -LiteralPath。
(4) Get-ChildItem -Filter 只接受单个字符串, 不能传数组。
(5) 逗号比加号结合得紧
例: ‘X=’ + $x, ‘Y’ 会变成两个数组元素而不是一个字符串。
(6) 从 DPI 不感知的进程量窗口, 得到的是虚拟化坐标
例: 1920×1080 @150% 会被读成 1280×720。
→ 先调 SetThreadDpiAwarenessContext(-4) 让本线程变成 DPI 感知。
(7) 跨进程读 Edit 控件内容: GetWindowText 读不到
→ 用 SendMessage(hwnd, WM_GETTEXT, …) 取。
(8) SendMessage(BM_CLICK) 是同步的
如果目标按钮会弹模态对话框, 自己会被卡住, 看起来像”没反应”。
→ 改用 PostMessage; 需要确认对话框时用 FindWindow 找到它再点。
(9) 在 PowerShell 里调 Win32 的 FindWindow(null, 标题), $null 会被封送成
空字符串而不是 NULL, 永远找不到窗口。
→ 用 C# 包一层, 在代码里传真正的 null。
(10) 从 DPI 不感知的进程 PrintWindow 截图, 只会截到窗口左上角一块。
→ 同样先 SetThreadDpiAwarenessContext(-4)。
================================================================================
六、附录: 另外排查过的两件事 (不属于自制程序)
6.1 DTS-HD 编码软件一直卡在 Pending
结论: 那份 DTSENC.dtshd_log.txt 本身没有任何报错, 它只是编码参数的
快照 (输入文件、声道布局、码率、时间码、输出位置)。
卡住的原因不在日志里, 而在 Windows 系统事件日志里:
12:56:04 系统时间从 2026-10-08 被改成 2019-10-08 (-7 年)
12:57:41 DTS 日志写出来, 时间戳正是 2019/10/08 12:57:41
13:00:45 又改成 2016-10-08
13:10:44 Windows 时间服务把时间拉回 2026-10-08
(偏移 +315,532,819 秒, 约 10 年)
也就是说: 编日志的时候系统时间处于回拨状态。这类授权校验是按日期
走的, 日期对不上时作业队列不干活, 所以一直停在 Pending。
另外确认: 机器上没有任何 DTS 相关的服务在运行。
补充: 回拨系统时间会带来副作用 —— 文件时间戳被写坏 (那份日志就是证据),
而且 Windows 时间服务每次同步都会把时间拽回去, 靠日期撑的状态会
反复失效。
6.2 DTS-HD MA 用免费工具做不出来
实测: ffmpeg 的 dca 编码器只能输出 DTS 核心, 最高 5.1 / 1536 kbps;
DTS-HD 的无损扩展是私有的, 只有官方工具能写。
等价的免费无损替代: TrueHD (ffmpeg 版最高 5.1)、FLAC (最高 8 声道 = 7.1)、
PCM。都是数学上无损的, 装进 .mka 即可。
================================================================================
七、源码与构建入口
源码和构建脚本都放在 C:\Users\何凯\Documents\ 下, 每个程序一个目录:
目录 内容
音频编码器\src\ AudioEncoder.cs + build.ps1
批量重命名\src\ BatchRename.cs + build-rename.ps1
FFmpegTranscode\ Core.cs + MainForm.cs + build.ps1 +
build-setup.ps1 + MakeIcon.cs
UpmixTool\ UpmixCore.cs + MixForm.cs + build.ps1 + MakeIcon.cs
SplitTool\ SplitCore.cs + SplitForm.cs + build.ps1 + MakeIcon.cs
MixTool\ MixCore.cs + MixForm.cs + build.ps1 + MakeIcon.cs
Setup\ Setup.cs (通用安装器, 6 个分支) + setup.manifest
build-2.0-packages.ps1 一次生成转码工具和批量重命名的 2.0 版四个包
重新编译一个程序 (以声道分离器为例):
powershell -ExecutionPolicy Bypass -File “C:\Users\何凯\Documents\SplitTool\build.ps1”
重新编译一个安装程序 (以声道分离器为例):
& “C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe” /target:winexe /platform:anycpu /optimize+ /codepage:65001
/define:APP_SPLIT "/win32manifest:...\Setup\setup.manifest"
“/win32icon:…\SplitTool\app-split.ico” /r:System.dll /r:System.Core.dll /r:System.Windows.Forms.dll /r:System.Drawing.dll
“/out:setup-split.exe” “…\Setup\Setup.cs”
安装器的 6 个分支名: APP_AUDIO / APP_RENAME / APP_TRANSCODE /
APP_UPMIX / APP_SPLIT / APP_MIX
每个便携版包里都带了一份 src\ (源码), 所以就算桌面的工程目录丢了,
从 rar 里也能把源码捞回来 (音频编码器的源码就是这么救回来的)。
================================================================================
—— 日志结束 ——
制作By何凯峰 联系方式:2062677962
================================================================================