# 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 $ ) 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 实例句柄,状态完全隔离。 |