背景:为什么放弃 Electron

Huafeirong Studio 最初基于 Electron + Vite + React + TypeScript 构建,前端用 Web 技术栈,底层通过 C++ Addon (Node.js N-API) 调用 libobs 视频引擎。项目推进到 60+ 源文件后,我们遇到了几个根本性问题:

核心洞察 对于视频处理密集型桌面应用,Electron 的本质开销不在于 JS 引擎,而在于渲染管线的进程隔离。任何需要 GPU 纹理共享的场景,原生方案都是更优的选择。

新架构设计

Native 版采用经典的两层架构:C++ 引擎层负责所有视频处理,C# WinUI 3 负责用户界面。两者通过 C API + P/Invoke 桥接:

┌─────────────────────────────────────────┐
│             WinUI 3 GUI (C#)             │
│  ┌─────────┐ ┌──────────┐ ┌──────────┐  │
│  │ MainWin │ │Broadcast │ │ Settings │  │
│  └─────────┘ └──────────┘ └──────────┘  │
│         │ P/Invoke (engine.dll)          │
├─────────┼───────────────────────────────┤
│  C API  │  engine_api.h                 │
│         │  engine_init / engine_shutdown │
│         │  engine_* (116 API functions)  │
├─────────┼───────────────────────────────┤
│  Engine │  libobs-hfr (C++20)           │
│  ┌──────┐ ┌───────┐ ┌────────┐         │
│  │Video │ │Audio  │ │Output  │         │
│  │Pipe  │ │Mixer  │ │Stream  │         │
│  └──────┘ └───────┘ └────────┘         │
│         │                                │
│  ┌──────┴──────────────────────────┐    │
│  │       libobs (OBS Studio SDK)     │    │
│  └──────────────────────────────────┘    │
└─────────────────────────────────────────┘

跨语言桥接:C API + P/Invoke

核心设计原则:C API 是最稳定的 FFI (Foreign Function Interface) 边界。我们选择 extern "C" 导出纯 C 函数,而非 C++/CLI 或 COM,原因如下:

  1. 单一定义规则:extern "C" 避免 C++ name mangling,P/Invoke 的 EntryPoint 匹配确定性高
  2. ABI 稳定:C ABI 在所有主流编译器中一致,不依赖 C++ 标准库版本
  3. 内存安全:DLL 边界不传递 C++ 对象,只传递 POD 结构体和 opaque handle

一个典型的 API 调用链:

// C API (engine_api.h)
extern "C" __declspec(dllexport) int engine_scene_create(const char* name);

// C# P/Invoke
[DllImport("engine.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int engine_scene_create(string name);

// C# 封装
public Scene CreateScene(string name) {
    var id = engine_scene_create(name);
    if (id < 0) throw new EngineException(engine_get_last_error());
    return new Scene { Id = id, Name = name };
}

性能对比

在相同硬件条件下(i7-13700K + RTX 4070),Electron 版 vs Native 版的关键指标:

指标Electron 版Native 版改善
启动时间3.2s0.8s4x
内存占用(空闲)320 MB85 MB3.8x
视频预览延迟32ms2ms16x
1080p60 编码 CPU18%8%2.3x
GPU 纹理共享❌ 慢路径✅ D3D11 共享
包体积180 MB42 MB4.3x
注意 性能数据来自开发环境测试,正式发布前需要在真实使用场景下进行完整的压力测试(多场景 + 多源 + 推流/录制同时进行)。

关键技术决策

1. CMake + ninja 构建 C++ 引擎

放弃 Visual Studio .sln 手动管理,采用 CMake 生成 ninja 构建文件。好处是 CI/CD 友好、增量编译更快、依赖管理清晰。

cmake -G Ninja \
  -DCMAKE_CXX_STANDARD=20 \
  -DCMAKE_BUILD_TYPE=Release \
  -DOBS_SOURCE_DIR=../obs-studio \
  -B build && \
cmake --build build

2. SwapChainPanel 原生集成

WinUI 3 的 SwapChainPanel 提供了 D3D11 交换链的原生集成能力。我们通过 libobs 的 obs_display_create 将渲染目标直接绑定到 SwapChainPanel 的交换链,实现了零拷贝视频预览。这比 Electron 版需要 Read-back → Copy → Canvas 的路径快了 16 倍。

3. 双预览管线 (PVW/PGM)

专业导播需要预监 (Preview/PVW) 和节目 (Program/PGM) 两个独立视频预览。我们扩展了 obs_source_info,在引擎层创建两个独立 display context,通过 SwapChainPanel 分别渲染到不同 UI 区域。

4. 转场特效引擎

基于 libobs 的 source transition 机制,实现了 cut(硬切)、fade(淡入淡出)、swipe(推拉)三种转场,支持自定义时长(200-2000ms)。转场方向支持 left/right/up/down 四个方向。

116 个引擎 API

Native 版共导出 116 个 C API 函数,覆盖以下功能域:

功能域API 数量核心能力
生命周期4init / shutdown / reset / get_version
场景管理8创建/删除/重命名/切换/预览/收藏夹
源管理18添加/删除/属性/变换/层级/12 种源类型
音频控制12混音/静音/音量/VU表/滤镜/监听
视频管线6PVW/PGM 预览/渲染/输出设置
转场特效8类型/时长/方向/颜色/自定义参数
推流录制14RTMP 推流/录制/编码器配置/状态
美颜滤镜10磨皮/美白/大眼/瘦脸/自定义强度
虚拟摄像头4启动/停止/状态/输出设备
快捷键6注册/绑定/触发/清除/列表
其他26诊断/统计/DLL管理/错误处理

踩过的坑

坑 1: NuGet restore 崩溃

沙箱环境中的 dotnet CLI NuGet restore 存在 ArgumentNullException 问题。最终方案:使用 VS2022 MSBuild 直接 restore/build,绕过了 CLI 的 NuGet 集成 bug。

坑 2: XAML 编译器与 WindowsAppSDK 版本不匹配

WinUI 3 的 XAML 编译需要版本精确匹配的 WinRT runtime。在非标准环境中,XamlCompiler.exe 会因找不到正确的 Windows SDK 而崩溃。解决方案是切换到进程内编译模式。

坑 3: DLL 运行时依赖地狱

engine.dll 依赖 20+ OBS/OBS-plugins DLL。初版部署脚本遗漏了 5 个运行时 DLL(avcodec/avformat/swresample/swscale/avutil),导致 GUI 启动即崩溃。现在的 package.ps1 脚本自动扫描并部署所有依赖。

总结

Electron → Native 迁移是一次正确的架构决策。对于视频处理桌面软件,原生 + GPU 纹理共享带来的性能提升是质的飞跃。116 个 C API 通过 P/Invoke 与 C# WinUI 3 无缝桥接,代码清晰、维护成本可控。

下一步计划: