14 KiB
10 周小步快跑实施路线图
本路线图为渐进式、可回退、可并行的实施计划。每一周都有明确的交付物和验收标准。任何一周的任务如果超时或遇到阻碍,都可以独立回退或跳过,不影响其他周的进度。
关键原则
- 不改现有代码,只增代码:每一周的改造都通过新增文件(
c_api/、app/render/renderer_main.cpp等)完成,现有源文件尽量不动。只有必须解耦的地方才修改现有头文件。 - 编译开关控制:通过 CMake 选项
-DOLIVE_DYNAMIC_MODULES=ON控制是否走动态库路径。默认 OFF,确保主干始终可编译可运行。 - 每周可独立验证:每周结束都有一个可运行的版本,即使后续周不开始,当前成果也是有价值的。
- 先易后难:从耦合最低的模块(core、codec)开始,积累经验和工具链,最后攻克最难的 node/render。
人员分工建议(假设 2–3 人)
| 角色 | 负责内容 |
|---|---|
| 基础设施工程师 | ModuleLoader、CMake 改造、CI 适配、C API 规范执行 |
| 编解码工程师 | libolivecore.so、libolivecodec.so、liboliveaudio.so |
| 渲染工程师 | libolivenode.so 解耦、liboliverender.so、olive-renderer 多进程 |
| UI 工程师 | liboliveui.so、主进程集成、ViewerWidget 改造 |
注意:初期阶段(Week 1–3)只需 1 人负责基础设施 + 1 人负责编解码即可。渲染和 UI 改造在 Week 4 之后全面展开。
Week 1:基础设施与工具链(1 人主责)
目标
建立 C API 基础设施,验证显式加载工具链在所有目标平台正常工作。
任务清单
- T1.1 创建目录结构
c_api/include/olive/,c_api/src/,c_api/tests/。 - T1.2 编写
ModuleLoader类(c_api/src/module_loader.h/cpp),支持 POSIX + Windows。 - T1.3 定义全局 C API 规范文件:
c_api/include/olive/core_api.h的基础部分(OliveResult,OliveRational, 内存管理函数)。 - T1.4 编写最小测试动态库
c_api/tests/test_module/(只导出一个int test_add(int, int)),验证ModuleLoader可以正确加载和调用。 - T1.5 在 CMake 中新增
OLIVE_DYNAMIC_MODULES选项(默认 OFF)。 - T1.6 在 CI(GitHub Actions)中增加一个 job:开启
OLIVE_DYNAMIC_MODULES=ON编译,验证 Linux/macOS/Windows 三平台。
验收标准
# 运行测试
./tests/c_api/test_module_loader
# 输出:
# [PASS] Load test_module.so
# [PASS] Call test_add(2, 3) = 5
# [PASS] Unload test_module.so
回退策略
若 ModuleLoader 在某平台工作异常,该周可仅完成 POSIX 平台,Windows 平台延后处理。
Week 2:libolivecore.so(1 人主责)
目标
将 ext/core/ 从静态库改造为显式加载的动态库,建立第一个完整的 C API。
任务清单
- T2.1 修改
ext/core/CMakeLists.txt:add_library(olivecore SHARED ...),添加CXX_VISIBILITY_PRESET hidden。 - T2.2 为
ext/core/中需要跨库使用的类添加导出宏(或保持 C API 头文件中的OLIVE_CORE_API)。 - T2.3 完成
c_api/include/olive/core_api.h:Rational, Color, TimeRange, Timecode, PixelFormat, SampleBuffer, VideoParams, AudioParams。 - T2.4 编写
c_api/src/core_api.cpp,封装所有上述类型。 - T2.5 编写单元测试
tests/c_api/test_core_api.cpp。 - T2.6 在主进程
Core::Start()中尝试显式加载libolivecore.so,失败时打印警告但不阻塞启动。
验收标准
ModuleLoader loader;
loader.Load("core", "./libolivecore.so");
auto make = loader.GetFunction<OliveRational(*)(int64_t,int64_t)>("core", "olive_rational_make");
auto add = loader.GetFunction<OliveRational(*)(OliveRational,OliveRational)>("core", "olive_rational_add");
OliveRational r = add(make(1,2), make(1,3));
assert(r.num == 5 && r.den == 6);
回退策略
若 C API 封装工作量超预期,本周可只完成 Rational + TimeRange 的最小子集,其余类型后续补充。
Week 3:libolivecodec.so(1 人主责)
目标
将 app/codec/ + app/common/ 封装为显式加载动态库,实现解码器的 C API。
任务清单
- T3.1 将
app/common/的源文件并入app/codec/CMakeLists.txt。 - T3.2 创建
libolivecodec.so的 SHARED 目标。 - T3.3 编写
c_api/include/olive/codec_api.h(最小子集):MediaInfo, Decoder, Frame。 - T3.4 编写
c_api/src/codec_api.cpp。 - T3.5 编写测试:加载视频文件 → 解码第一帧 → 验证宽高 > 0。
- T3.6 在主进程中显式加载
libolivecodec.so。
验收标准
auto decoder = olive_decoder_create(nullptr);
olive_decoder_open(decoder, "test.mp4", 0);
OliveFrame* frame = nullptr;
olive_decoder_decode_video(decoder, olive_rational_make(0,1), &frame);
assert(olive_frame_width(frame) == 1920);
olive_frame_destroy(frame);
olive_decoder_destroy(decoder);
回退策略
若 common/ 中有代码依赖 node/ 或 render/,先将这些代码移回主库,再继续。
Week 4:libolivenode.so 解耦(2 人并行,最关键的一周)
目标
解决 Node.h 对 render/ 的头文件依赖,为 libolivenode.so 的独立编译扫清障碍。
任务清单
- T4.1 创建
app/node/nodecachecallbacks.h,定义NodeCacheCallbacks纯虚接口。 - T4.2 修改
app/node/node.h:- 移除
#include "render/rendercache.h"。 - 添加
NodeCacheCallbacks* cache_callbacks_和SetCacheCallbacks()。 - 将
InvalidateCache相关逻辑改为调用cache_callbacks_->InvalidateCache()。
- 移除
- T4.3 创建
app/node/jobtypes.h,定义NodeJobType和NodeJobData。 - T4.4 修改
Node.h中的ProcessXxx虚函数签名,使用NodeJobData。 - T4.5 修改
RenderProcessor,适配新的NodeCacheCallbacks和NodeJobData。 - T4.6 验证
app/node/目录可以独立编译(写一个临时 CMake 测试)。
验收标准
cd /tmp && cmake /path/to/oak/app/node && make
# 编译成功,不报错
回退策略
若解耦工作量超预期:允许 node/ 和 render/ 暂时合并为 libolive-engine.so。这是最重要的回退策略——宁可合并也不阻塞进度。合并后仍可继续封装 C API,后续再拆分。
Week 5:libolivenode.so C API + liboliveplugin.so(2 人并行)
目标
完成节点图系统的 C API,并将 OFX 插件宿主独立为动态库。
任务清单(节点图工程师)
- T5.1 创建
libolivenode.so,聚合node/,timeline/,undo/,config/。 - T5.2 编写
c_api/include/olive/node_api.h和c_api/src/node_api.cpp。 - T5.3 实现最小 C API:NodeGraph create/destroy, load_xml/save_xml, add_node, connect, param set/get。
- T5.4 编写测试:用 C API 构建一个 Generator -> ViewerOutput 的图,序列化后反序列化验证。
任务清单(插件工程师)
- T5.5 将
pluginSupport/从主 OBJECT 库移出,创建liboliveplugin.so。 - T5.6 编写
c_api/include/olive/plugin_api.h(最小子集):host create/destroy, add_path, rescan, plugin count/get。 - T5.7 验证主进程可以扫描 OFX 插件目录并列出插件名称。
验收标准
// 节点图测试
OliveNodeGraph* g = olive_node_graph_create();
olive_node_graph_add_node(g, "SolidGenerator", "Solid1");
olive_node_graph_add_node(g, "ViewerOutput", "Viewer1");
OliveNode* solid = olive_node_graph_find_node(g, "Solid1");
OliveNode* viewer = olive_node_graph_find_node(g, "Viewer1");
olive_node_connect(solid, 0, viewer, 0);
size_t len;
char* xml = olive_node_graph_save_xml(g, &len);
assert(len > 0);
olive_core_free(xml);
回退策略
若 node/ C API 工作量过大,优先保证 load_xml / save_xml / find_node 三个函数(这是渲染子进程最需要的),其余延后。
Week 6:olive-renderer 单帧端到端(2 人并行)
目标
实现第一个可用的 olive-renderer 可执行文件,能渲染一帧测试图。
任务清单
- T6.1 编写
app/render/renderer_main.cpp,实现命令行解析。 - T6.2 实现
olive_shm_create/open/close/unlink(POSIX + Windows)。 - T6.3 在
olive-renderer中集成:加载 XML → 初始化 OpenGL → 渲染 → 写入 SHM → 输出 JSON。 - T6.4 编写主进程中的
RenderProcessLauncher::RenderFrameSync。 - T6.5 端到端测试:主进程启动
olive-renderer渲染一帧纯色,验证 SHM 中的像素值正确。
验收标准
# 命令行直接测试
olive-renderer --mode=frame --node-graph=test_solid.xml --time=0/1 \
--video-params='{"width":100,"height":100,"format":"rgba32f"}' \
--output-shm=/olive_test --output-shm-size=160000
# 输出:
# {"status":"ok","width":100,"height":100,"format":"rgba32f",...}
回退策略
若 OpenGL 离屏上下文初始化在某平台失败,该平台暂时使用 --backend=dummy(只测试进程模型,不测试实际渲染)。
Week 7:ViewerWidget 集成 + 用完即弃验证(2 人并行)
目标
将 olive-renderer 集成到主进程的 Viewer 中,验证"用完即弃"模型在实际场景中的可行性。
任务清单
- T7.1 修改
ViewerWidget:从RenderManager::RenderFrame()改为启动olive-renderer。 - T7.2 实现异步完成回调:
OnRenderProcessFinished()读取 SHM 并更新 Texture。 - T7.3 处理子进程崩溃:崩溃时忽略该帧,保持上一帧显示,记录日志。
- T7.4 测量实际性能:拖动时间线时的帧率、CPU 占用、进程启动耗时。
- T7.5 若性能不达标,实现批处理模式(
--mode=batch)。
验收标准
- 打开一个简单项目(单轨道 + 纯色生成器),拖动时间线,Viewer 实时更新。
kill -9一个渲染子进程,主进程不崩溃,Viewer 保持显示。
回退策略
若"用完即弃"性能完全不可接受(如帧率 < 5fps),立即切换为批处理模式或进程池模式(预启动 N 个进程,循环使用,每个进程渲染一批后自杀)。
Week 8:liboliveaudio.so + liboliveui.so(2 人并行)
目标
完成音频库和 UI 库的动态库拆分。
任务清单(音频工程师)
- T8.1 创建
liboliveaudio.so。 - T8.2 编写
audio_api.h/cpp(最小子集):manager init/play/pause/push_buffer。 - T8.3 验证音频播放通过 C API 正常工作。
任务清单(UI 工程师)
- T8.1 聚合所有 UI 源文件,创建
liboliveui.so。 - T8.2 解耦
Core类中的 UI 逻辑(StartGUI()迁移到liboliveui.so内部)。 - T8.3 主进程显式加载
liboliveui.so,成功启动主窗口。 - T8.4 编写
ui_api.h(最小子集):app create/exec, main_window show, open_project。
验收标准
- 主程序通过显式加载
liboliveui.so启动,界面正常。 - 音频播放正常(可听到声音)。
Week 9:导出集成 + 稳定性打磨(2 人并行)
目标
将导出流程集成到多进程渲染模型,全面稳定性测试。
任务清单
- T9.1 修改导出任务(
task/export/),使用olive-renderer逐帧/逐批渲染。 - T9.2 导出天然适合批处理:一次性发送 10–50 帧给子进程。
- T9.3 编写压力测试:连续渲染 100 帧,验证无内存泄漏、无共享内存泄漏。
- T9.4 测试 OFX 插件在子进程中的渲染(选择几个免费 OFX 插件测试)。
- T9.5 测试崩溃场景:
kill -9随机子进程,验证主进程稳定。
验收标准
- 成功导出一个 10 秒视频(300 帧),画面正确。
- 连续启动 100 个渲染子进程,系统无共享内存泄漏(
ls /dev/shm/检查)。
Week 10:打包适配 + 文档 + 性能优化(2 人并行)
目标
完成打包脚本适配,编写用户文档,进行最终性能优化。
任务清单
- T10.1 更新 macOS 打包脚本:确保
libolive*.dylib和olive-renderer被打入.appBundle,@rpath设置正确。 - T10.2 更新 Windows 打包脚本:确保
.dll和olive-renderer.exe在_installer 中。 - T10.3 更新 Linux AppImage 打包:确保动态库在 AppImage 内可加载。
- T10.4 更新
docs/build.md和docs/build-macos-zh.md,说明新的运行时依赖。 - T10.5 性能优化:
- 节点图 XML 缓存(相同图只序列化一次)。
- 共享内存预分配池(避免反复创建/销毁)。
- 批处理大小动态调整(根据上一批的渲染时间调整下一批的帧数)。
- T10.6 全面回归测试:导入、编辑、预览、导出、Undo/Redo、保存/加载项目。
验收标准
- 在三平台上都能通过
make install或打包脚本生成可分发包。 - 新用户按照
build.md可以成功编译并运行。 - 与 Week 0(改造前)相比,导出速度不差于 90%,预览帧率不差于 70%。
并行工作流
Week 1: [基础设施]
Week 2: [core] (依赖 W1)
Week 3: [codec] (依赖 W2)
Week 4: [node 解耦] (可并行 W3, 但建议 W3 后启动)
Week 5: [node C API] + [plugin] (依赖 W4)
Week 6: [renderer] (依赖 W5)
Week 7: [Viewer 集成] (依赖 W6)
Week 8: [audio] + [ui] (可并行 W7)
Week 9: [导出集成] (依赖 W7)
Week 10: [打包/文档/优化] (依赖 W9)
最大并行度:Week 8 时可以有 3 人同时工作(1 人 audio,1 人 ui,1 人优化 renderer)。
回退总策略
| 场景 | 回退方案 |
|---|---|
| 某周任务无法按时完成 | 将该周剩余任务移到下一周,当前周只交付已完成部分。 |
node/ 解耦完全不可行 |
将 node/ + render/ 合并为 libolive-engine.so,后续再拆分。 |
| "用完即弃"性能完全不可接受 | 切换为"批处理模式"(每进程渲染 N 帧),或"进程池模式"(预启动 N 个进程)。 |
| 动态库在某平台加载失败 | 该平台暂时保持静态链接,其他平台先用动态库。 |
| C API 维护成本过高 | 保留 C API 用于子进程通信,主进程内部恢复直接 C++ 链接(但库仍编译为动态库,由操作系统隐式加载)。 |
| 项目期限紧张 | 优先完成 olive-renderer 多进程(核心价值),动态库拆分可以延后。 |