Files
oak-editor/docs/zh/modularization-plan/03-oliveplugin.md
T

306 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# liboliveplugin.so — OFX 插件宿主支持
> **依赖**`libolivecore.so`
> **外部依赖**`third_party/openfx/HostSupport``OfxHost` 静态库),expatQt::Core
> **包含源码**`app/pluginSupport/`
> **当前状态**:单体 OBJECT 库的一部分,通过 `target_link_libraries(olive-editor PUBLIC OfxHost)` 隐式链接
> **改造难度**:⭐⭐(较简单,接口相对独立)
---
## 1. 当前状态分析
`app/pluginSupport/` 实现 OFXOpenFX)标准的 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: 隔离 PluginNode1 天)
- [ ]`app/node/plugins/PluginNode` 移动到 `app/node/` 下(或保持原位,但确保它编译进 `libolivenode.so` 而非 `liboliveplugin.so`)。
- [ ] 确认 `PluginNode``pluginSupport/` 的依赖方向:PluginNode 使用 pluginSupport 的类,而非相反。
**验收标准**`liboliveplugin.so` 编译时不包含任何 `node/` 下的源文件。
### Step 1: 构建 liboliveplugin.so1 天)
- [ ] 创建 `app/pluginSupport/CMakeLists.txt`(若尚无)。
- [ ]`pluginSupport/` 的源文件从主 OBJECT 库中移出,单独构建为 `oliveplugin SHARED`
- [ ] 确保 `OfxHost` 静态库先被构建(`third_party/openfx/HostSupport`)。
**验收标准**`liboliveplugin.so` 编译成功,能通过 `dlsym` 找到 `olive_plugin_api_version`
### Step 2: 最小 C API2 天)
- [ ] 先实现最必需的接口:
- `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 实例句柄,状态完全隔离。 |