docs:Add plan of spilting into multi-library
This commit is contained in:
@@ -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 实例句柄,状态完全隔离。 |
|
||||
Reference in New Issue
Block a user