11 KiB
11 KiB
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
#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 实现要点
// 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
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/rescanolive_plugin_host_plugin_count/get_pluginolive_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 实例句柄,状态完全隔离。 |