docs:Add plan of spilting into multi-library

This commit is contained in:
2026-05-21 20:31:17 +08:00
parent 295c5eaa24
commit 6b94ceea14
13 changed files with 5651 additions and 0 deletions
@@ -0,0 +1,305 @@
# liboliveplugin.so — OFX 插件宿主支持
> **依赖**:`libolivecore.so`
> **外部依赖**:`third_party/openfx/HostSupport`(`OfxHost` 静态库),expat,Qt::Core
> **包含源码**:`app/pluginSupport/`
> **当前状态**:单体 OBJECT 库的一部分,通过 `target_link_libraries(olive-editor PUBLIC OfxHost)` 隐式链接
> **改造难度**:⭐⭐(较简单,接口相对独立)
---
## 1. 当前状态分析
`app/pluginSupport/` 实现 OFX(OpenFX)标准的 Host 端接口,使 Olive 能够加载第三方插件(如 Sapphire、Neat Video 等)。
| 组件 | 说明 |
|---|---|
| `OliveHost` | OFX Host 接口主实现 |
| `PluginInstance` | 单个插件实例管理 |
| `OliveClip` / `OliveClipInstance` | OFX Clip 接口封装 |
| `OliveParam` / `OliveParamInstance` | OFX 参数接口封装 |
| `node/plugins/PluginNode` | OFX 插件在节点图中的封装节点 |
**特点**:
- `pluginSupport/` 与 `node/plugins/PluginNode` 存在双向依赖。
- OFX Host 支持库(`third_party/openfx/HostSupport`)是第三方代码,不应修改其接口。
- 插件渲染需要 OpenGL 上下文,因此 `liboliveplugin.so` 需要与渲染层协作。
**决策**:由于 `PluginNode` 继承自 `Node`(在 `libolivenode.so` 中),`PluginNode` 应留在 `libolivenode.so` 中。`liboliveplugin.so` 只包含纯 Host 支持代码(`pluginSupport/`),通过 C API 向 `libolivenode.so` 暴露插件加载和管理能力。
---
## 2. C API 设计
### 2.1 头文件:`c_api/include/olive/plugin_api.h`
```c
#ifndef OLIVE_PLUGIN_API_H
#define OLIVE_PLUGIN_API_H
#ifdef __cplusplus
extern "C" {
#endif
#include "core_api.h"
#define OLIVE_PLUGIN_API_VERSION 1
#ifdef OLIVE_BUILDING_PLUGIN
# define OLIVE_PLUGIN_API __attribute__((visibility("default")))
#else
# define OLIVE_PLUGIN_API
#endif
/* ========== 类型前向声明 ========== */
typedef struct OlivePluginHost OlivePluginHost;
typedef struct OlivePlugin OlivePlugin;
typedef struct OlivePluginInstance OlivePluginInstance;
typedef struct OlivePluginParam OlivePluginParam;
/* ========== API 版本 ========== */
OLIVE_PLUGIN_API int olive_plugin_api_version(void);
/* ========== Host 生命周期 ========== */
OLIVE_PLUGIN_API OlivePluginHost* olive_plugin_host_create(void);
OLIVE_PLUGIN_API void olive_plugin_host_destroy(OlivePluginHost* host);
// 设置插件搜索路径(可多次调用添加多个路径)
OLIVE_PLUGIN_API int olive_plugin_host_add_path(OlivePluginHost* host, const char* path);
// 扫描所有路径,加载可用插件
OLIVE_PLUGIN_API int olive_plugin_host_rescan(OlivePluginHost* host);
// 获取已加载插件数量
OLIVE_PLUGIN_API int olive_plugin_host_plugin_count(OlivePluginHost* host);
// 获取指定索引的插件
OLIVE_PLUGIN_API OlivePlugin* olive_plugin_host_get_plugin(OlivePluginHost* host, int index);
/* ========== Plugin 信息 ========== */
OLIVE_PLUGIN_API const char* olive_plugin_get_id(OlivePlugin* plugin);
OLIVE_PLUGIN_API const char* olive_plugin_get_name(OlivePlugin* plugin);
OLIVE_PLUGIN_API const char* olive_plugin_get_group(OlivePlugin* plugin); // 分类,如 "Filter/Blur"
OLIVE_PLUGIN_API int olive_plugin_is_hardware_rendering_supported(OlivePlugin* plugin);
/* ========== Plugin Instance ========== */
OLIVE_PLUGIN_API OlivePluginInstance* olive_plugin_instance_create(OlivePlugin* plugin,
int width,
int height);
OLIVE_PLUGIN_API void olive_plugin_instance_destroy(OlivePluginInstance* instance);
// 参数操作(通过字符串名称)
OLIVE_PLUGIN_API int olive_plugin_instance_set_param_int(OlivePluginInstance* instance,
const char* param_name,
int value);
OLIVE_PLUGIN_API int olive_plugin_instance_set_param_double(OlivePluginInstance* instance,
const char* param_name,
double value);
OLIVE_PLUGIN_API int olive_plugin_instance_set_param_string(OlivePluginInstance* instance,
const char* param_name,
const char* value);
// 渲染一帧(输入/输出均为 Frame)
OLIVE_PLUGIN_API int olive_plugin_instance_render(OlivePluginInstance* instance,
OliveRational time,
OliveFrame* input_frame,
OliveFrame** output_frame);
/* ========== Param 枚举(用于 UI 构建控件) ========== */
OLIVE_PLUGIN_API int olive_plugin_instance_param_count(OlivePluginInstance* instance);
OLIVE_PLUGIN_API OlivePluginParam* olive_plugin_instance_get_param(OlivePluginInstance* instance,
int index);
OLIVE_PLUGIN_API const char* olive_plugin_param_get_name(OlivePluginParam* param);
OLIVE_PLUGIN_API const char* olive_plugin_param_get_label(OlivePluginParam* param);
OLIVE_PLUGIN_API int olive_plugin_param_get_type(OlivePluginParam* param); // 0=int, 1=double, 2=string, 3=bool, 4=color, 5=choice
OLIVE_PLUGIN_API int olive_plugin_param_get_int_min(OlivePluginParam* param);
OLIVE_PLUGIN_API int olive_plugin_param_get_int_max(OlivePluginParam* param);
OLIVE_PLUGIN_API double olive_plugin_param_get_double_min(OlivePluginParam* param);
OLIVE_PLUGIN_API double olive_plugin_param_get_double_max(OlivePluginParam* param);
OLIVE_PLUGIN_API int olive_plugin_param_get_choice_count(OlivePluginParam* param);
OLIVE_PLUGIN_API const char* olive_plugin_param_get_choice_label(OlivePluginParam* param, int index);
#ifdef __cplusplus
}
#endif
#endif // OLIVE_PLUGIN_API_H
```
### 2.2 实现要点
```cpp
// c_api/src/plugin_api.cpp
#include "olive/plugin_api.h"
#include "pluginSupport/olivehost.h"
#include "pluginSupport/plugininstance.h"
#include "pluginSupport/oliveparam.h"
#include "codec/frame.h"
struct OlivePluginHost {
olive::OliveHost* impl;
};
struct OlivePlugin {
olive::Plugin* impl; // 内部插件描述对象
};
struct OlivePluginInstance {
olive::PluginInstance* impl;
};
// ...
extern "C" {
OlivePluginHost* olive_plugin_host_create(void) {
try {
auto* h = new OlivePluginHost();
h->impl = new olive::OliveHost();
return h;
} catch (...) {
return nullptr;
}
}
void olive_plugin_host_destroy(OlivePluginHost* host) {
if (host) {
delete host->impl;
delete host;
}
}
int olive_plugin_host_add_path(OlivePluginHost* host, const char* path) {
if (!host || !path) return OLIVE_ERROR_INVALID;
try {
host->impl->AddPath(QString::fromUtf8(path));
return OLIVE_OK;
} catch (...) {
return OLIVE_ERROR_GENERIC;
}
}
int olive_plugin_host_rescan(OlivePluginHost* host) {
if (!host) return OLIVE_ERROR_INVALID;
try {
host->impl->RescanPlugins();
return OLIVE_OK;
} catch (...) {
return OLIVE_ERROR_GENERIC;
}
}
// ... 其他封装类似 ...
} // extern "C"
```
---
## 3. CMake 改造
### 3.1 `app/pluginSupport/CMakeLists.txt`
```cmake
set(PLUGIN_INTERNAL_SOURCES
olivehost.cpp olivehost.h
plugininstance.cpp plugininstance.h
oliveclip.cpp oliveclip.h
oliveclipinstance.cpp oliveclipinstance.h
oliveparam.cpp oliveparam.h
oliveparaminstance.cpp oliveparaminstance.h
# ...
)
set(PLUGIN_API_SOURCES
${CMAKE_SOURCE_DIR}/c_api/src/plugin_api.cpp
)
add_library(oliveplugin SHARED
${PLUGIN_INTERNAL_SOURCES}
${PLUGIN_API_SOURCES}
)
target_compile_definitions(oliveplugin PRIVATE OLIVE_BUILDING_PLUGIN)
target_include_directories(oliveplugin
PRIVATE
${CMAKE_SOURCE_DIR}/app
${CMAKE_SOURCE_DIR}/third_party/openfx/include
${CMAKE_SOURCE_DIR}/third_party/openfx/HostSupport/include
${CMAKE_SOURCE_DIR}/c_api/include
PUBLIC
$<INSTALL_INTERFACE:include>
)
target_link_libraries(oliveplugin
PUBLIC
olivecore
OfxHost # third_party/openfx/HostSupport 构建的目标
EXPAT::EXPAT
Qt${QT_VERSION_MAJOR}::Core
)
set_target_properties(oliveplugin PROPERTIES
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN YES
)
install(TARGETS oliveplugin DESTINATION lib)
install(FILES ${CMAKE_SOURCE_DIR}/c_api/include/olive/plugin_api.h DESTINATION include/olive)
```
---
## 4. 小步快跑实施步骤
### Step 0: 隔离 PluginNode(1 天)
- [ ] 将 `app/node/plugins/PluginNode` 移动到 `app/node/` 下(或保持原位,但确保它编译进 `libolivenode.so` 而非 `liboliveplugin.so`)。
- [ ] 确认 `PluginNode` 对 `pluginSupport/` 的依赖方向:PluginNode 使用 pluginSupport 的类,而非相反。
**验收标准**:`liboliveplugin.so` 编译时不包含任何 `node/` 下的源文件。
### Step 1: 构建 liboliveplugin.so(1 天)
- [ ] 创建 `app/pluginSupport/CMakeLists.txt`(若尚无)。
- [ ] 将 `pluginSupport/` 的源文件从主 OBJECT 库中移出,单独构建为 `oliveplugin SHARED`。
- [ ] 确保 `OfxHost` 静态库先被构建(`third_party/openfx/HostSupport`)。
**验收标准**:`liboliveplugin.so` 编译成功,能通过 `dlsym` 找到 `olive_plugin_api_version`。
### Step 2: 最小 C API(2 天)
- [ ] 先实现最必需的接口:
- `olive_plugin_host_create/destroy/add_path/rescan`
- `olive_plugin_host_plugin_count/get_plugin`
- `olive_plugin_get_id/name`
- [ ] 暂不实现:渲染接口(`olive_plugin_instance_render`)、参数枚举。
**验收标准**:主进程可以扫描 OFX 插件目录并列出所有插件名称。
### Step 3: 扩展渲染接口(2 天)
- [ ] 实现 `olive_plugin_instance_create/destroy`。
- [ ] 实现 `olive_plugin_instance_render`(输入输出 `OliveFrame*`)。
- [ ] 此步骤需要 `libolivecodec.so` 的 `OliveFrame` 定义已就绪。
**验收标准**:可以创建一个 OFX 插件实例,传入一帧,获取处理后的一帧。
### Step 4: 参数枚举(2 天)
- [ ] 实现参数枚举接口,使 UI 层可以通过 C API 自动构建参数控件。
- [ ] 编写测试:加载一个已知插件(如 OFX 示例插件),验证参数数量与类型正确。
---
## 5. 风险与回退
| 风险 | 对策 |
|---|---|
| `OfxHost` 静态库中的符号与动态库导出冲突 | `OfxHost` 保持静态链接进 `liboliveplugin.so`,其符号不对外导出(`hidden` 可见性)。 |
| OFX 插件需要 OpenGL 上下文 | 渲染接口 `olive_plugin_instance_render` 需要传入或绑定 GL 上下文。在"用完即弃"的渲染子进程模型中,这天然解决:子进程自己创建 GL 上下文,插件在其上渲染。 |
| `PluginNode` 需要 `PluginInstance` 的 C++ 类 | `PluginNode` 在 `libolivenode.so` 内部,可以直接包含 `pluginSupport/` 的 C++ 头文件(因为 node 库可以在编译时访问 pluginSupport 源码)。只有跨库边界才需要 C API。 |
| OFX 插件多实例状态管理复杂 | C API 中每个 `OlivePluginInstance*` 对应一个独立的 OFX 实例句柄,状态完全隔离。 |