OBS Studio 模块(Module)API 参考:libobs 插件的声明、导出与加载全解
OBS Studio 的可扩展性建立在 libobs 的模块(Module)机制之上:模块本质上是一个共享库(.so/.dylib/.dll),向 libobs 注入源(source)、编码器(encoder)、输出(output)和推流服务(service)等自定义功能。本文基于仓库官方 API 参考文档 reference-modules.rst 完整展开:先讲模块对象的定义与声明宏,再逐一解析插件必须导出/可选导出的函数,最后覆盖前端(frontend)用于发现、打开和批量加载模块的整套 API,并结合 libobs/obs-module.h 与 libobs/obs-module.c 的源码实现印证其底层行为。读完本文,你将掌握编写、本地化、注册一个 libobs 插件模块的全部 API 契约,以及前端如何安全地驱动插件生命周期。
1. 模块对象:obs_module_t
文档首先定义了核心类型:
#include <obs-module.h>
obs_module_t 是一个模块对象(不采用引用计数)。在 libobs 内部它由模块管理器统一持有,插件作者通常不需要手动创建或释放它——你通过 obs_current_module() 获取当前模块指针即可。
模块的定位在 reference-core.rst 所在 API 参考目录中与核心对象、图形、媒体 I/O 等参考并列,是整个插件体系(源、编码器、输出、服务)的宿主容器。
2. 模块宏(Module Macros)
2.1 OBS_DECLARE_MODULE():必需的模块声明
OBS_DECLARE_MODULE() 是每个 libobs 插件都必需的宏,用于声明一个 libobs 模块,并导出模块自身、OBS 版本等核心函数。
看 libobs/obs-module.h 中该宏的真实展开内容,它实际生成了三样东西:
#define OBS_DECLARE_MODULE() \
static obs_module_t *obs_module_pointer; \
MODULE_EXPORT void obs_module_set_pointer(obs_module_t *module); \
void obs_module_set_pointer(obs_module_t *module) \
{ obs_module_pointer = module; } \
obs_module_t *obs_current_module(void) \
{ return obs_module_pointer; } \
MODULE_EXPORT uint32_t obs_module_ver(void); \
uint32_t obs_module_ver(void) \
{ return LIBOBS_API_VER; }
要点:
obs_module_set_pointer被 libobs 在打开模块时调用,把模块指针注入插件;obs_current_module()因此成为插件内"当前模块"的获取途径(即 2.3 节外函数);obs_module_ver()返回LIBOBS_API_VER,这就是加载时版本兼容性检查的依据(见第 5 节源码分析)。
2.2 OBS_MODULE_USE_DEFAULT_LOCALE():标准 ini 本地化
OBS_MODULE_USE_DEFAULT_LOCALE(module_name, default_locale) 是一个辅助宏,使用标准 ini 文件格式做本地化。它自动初始化/销毁本地化数据,并自动提供 obs_module_text() 等模块外函数,让你以最小代价获取本地化字符串。
从 libobs/obs-module.h 的宏展开看,它一次性实现了四个函数:
| 生成函数 | 作用 |
|---|---|
obs_module_text(val) |
查表返回翻译串;查不到时原样返回 val(fallback 到英文键名) |
obs_module_get_string(val, &out) |
查表,成功返回 true,失败 false |
obs_module_set_locale(locale) |
用 obs_module_load_locale(obs_current_module(), default_locale, locale) 重建 lookup_t |
obs_module_free_locale() |
销毁 lookup_t 并置空 |
仓库中所有内置 C 插件都按此模式使用,例如 plugins/image-source/image-source.c:
OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("image-source", "en-US")
bool obs_module_load(void)
{
obs_register_source(&image_source_info);
obs_register_source(&slideshow_info);
return true;
}
第一个参数是插件名(用于定位数据目录下的 locale 子目录),第二个是回退语言。本地化数据以 ini 文件存放于插件数据目录,例如 plugins/image-source/data/locale/ 下按语言组织的翻译文件。
3. 模块导出函数(Module Exports)
以下函数是插件模块可以(或必须)导出、用于与 libobs 及前端通信的符号。
3.1 obs_module_load() —— 必需
bool obs_module_load(void);
必需导出。模块被加载(初始化)时调用。在此实现中加载模块的所有 source/encoder/output/service,或任何需要在启动时完成的工作。返回 true 继续加载;返回 false 表示失败,libobs 会放弃该模块并记录告警日志(见 libobs/obs-module.c 中 obs_init_module 对返回值 false 的处理:blog(LOG_WARNING, "Failed to initialize module ..."))。
典型实现见上文 image-source 示例:把模块持有的 struct obs_source_info 逐个 obs_register_source() 注册。其他模块类型对应 obs_register_encoder()、obs_register_output()、obs_register_service()。
3.2 obs_module_unload() —— 可选但关键
void obs_module_unload(void);
可选导出。libobs 关闭、模块即将卸载前调用。此时所有 libobs 对象仍然有效。文档给出的三条纪律非常重要:
- 用此函数保存用户设置,并释放模块自身持有的对 libobs 对象(sources、canvases、outputs、encoders、services)的强引用;
- 不要释放/销毁仍在使用中的模块所提供对象——返回后 libobs 可能还会调用模块的回调(如
destroy)来清理剩余实例; - 函数返回后,除 libobs 回调之外,不得再发起任何 libobs API 调用。
3.3 obs_module_post_load() —— 可选
void obs_module_post_load(void);
可选导出。所有模块都完成加载后调用,适合做依赖其他插件的初始化(如 A 插件要使用 B 插件提供的功能,B 可能尚未注册)。
3.4 本地化相关导出
void obs_module_set_locale(const char *locale); // 设置 locale 语言并加载 locale 数据
void obs_module_free_locale(void); // 模块销毁时释放 locale 数据
若使用了 OBS_MODULE_USE_DEFAULT_LOCALE,这两个函数已被宏自动生成,无需手写。
3.5 元信息导出(可选)
const char *obs_module_name(void); // 模块全名
const char *obs_module_description(void); // 模块描述
均为可选。实际插件中常用于描述插件用途,例如 image-source 返回 "Image/color/slideshow sources"(plugins/image-source/image-source.c)。libobs/obs-module.h 还额外提供了 OBS_MODULE_AUTHOR(name) 宏来导出作者信息,前端"插件管理器"类界面即靠这些元信息展示插件卡片。
4. 模块外函数(Module Externs)
以下函数在整个模块内部可用,无需显式传模块指针:
| 函数 | 说明 |
|---|---|
const char *obs_module_text(const char *lookup_string) |
返回本地化字符串 |
bool obs_module_get_string(const char *lookup_string, const char **translated_string) |
本地化查表助手;找到返回 true,否则 false |
obs_module_t *obs_current_module(void) |
返回当前模块指针(由 OBS_DECLARE_MODULE 提供) |
char *obs_module_file(const char *file) |
返回当前模块数据文件的绝对位置,用 bfree() 释放;等价于 obs_find_module_file(obs_current_module(), file) |
char *obs_module_config_path(const char *file) |
返回当前模块配置文件的绝对位置(无论文件是否存在),配置目录未设置时返回 NULL;用 bfree() 释放;等价于 obs_module_get_config_path(obs_current_module(), file) |
后两个宏在 libobs/obs-module.h 中定义为:
#define obs_module_file(file) obs_find_module_file(obs_current_module(), file)
#define obs_module_config_path(file) obs_module_get_config_path(obs_current_module(), file)
它们是插件定位自身资源(effect 文件、locale 文件、配置文件等)的标准入口,替代了手工拼接路径的做法。
5. 前端模块函数(Frontend Module Functions)
这一组函数由前端(如 OBS Studio 主程序)用于加载插件并获取插件信息。下面逐一说明,并用 libobs/obs-module.c 的实现印证。
5.1 obs_open_module():打开单个模块
int obs_open_module(obs_module_t **module, const char *path, const char *data_path);
从指定路径直接打开插件模块。若模块已存在,则直接成功并返回已有模块的指针。注意:它只加载模块映像(加载符号表),并不初始化模块;要初始化需再调用 obs_init_module()。
参数:
module:输出的模块指针;path:模块库文件路径;省略扩展名时自动使用操作系统对应的扩展名(.so / .dylib / .dll);data_path:模块数据文件目录(无则传NULL)。
返回码(定义见 libobs/obs-defs.h):
| 宏 | 值 | 含义 |
|---|---|---|
MODULE_SUCCESS |
0 | 成功 |
MODULE_ERROR |
-1 | 通用错误 |
MODULE_FAILED_TO_OPEN |
-2 | 模块打开失败(未找到或符号缺失;旧名 MODULE_FILE_NOT_FOUND 已标记废弃) |
MODULE_MISSING_EXPORTS |
-3 | 缺少必需的导出符号 |
MODULE_INCOMPATIBLE_VER |
-4 | 版本不兼容 |
MODULE_HARDCODED_SKIP |
-5 | 被硬编码规则跳过(例如 macOS 上已废弃的旧版 obs-browser 插件) |
从 libobs/obs-module.c 的 obs_open_module 实现可以看到完整的打开流程:
- 参数校验(
module/path/全局obs任一为空直接MODULE_ERROR); - macOS 上有硬编码跳过逻辑:路径同时包含
Library/Application Support/obs-studio和obs-browser时返回MODULE_HARDCODED_SKIP——这正是文档中MODULE_HARDCODED_SKIP示例的来源; os_dlopen(path)打开库映像,失败返回MODULE_FAILED_TO_OPEN;load_module_exports(&mod, path)解析并绑定导出函数,失败返回MODULE_MISSING_EXPORTS;- 版本检查:取
obs_module_ver()的高 32 位(忽略 patch 版本),若大于当前LIBOBS_API_VER则判定"用更新的 libobs 编译",返回MODULE_INCOMPATIBLE_VER; - 填充
bin_path/文件名/模块名/data_path,初始化 sources/outputs/encoders/services 四个动态数组,并调用obs_module_load_metadata(); - 元数据加载:若
data_path下存在manifest.json,解析其中的display_name、id、version、os_arch、description、urls等字段(libobs/obs-module.c),供插件管理器展示; - 最后通过
mod.set_pointer(*module)把模块指针注入插件,并立即用obs->locale调用插件的set_locale。
5.2 obs_init_module():初始化模块
bool obs_init_module(obs_module_t *module);
初始化模块,实际调用其 obs_module_load 导出。返回 true 表示加载成功。
实现(libobs/obs-module.c)中有两处细节值得注意:
- 已加载的模块直接返回
true(幂等); - 调用
obs_module_load期间设置全局loadingModule上下文,并用 profiler 记录每个模块的初始化耗时——这就是obs_module_load中注册资源时可安全使用注册 API 的原因。
5.3 模块信息查询函数
void obs_log_loaded_modules(void); // 在日志中打印已加载模块列表
const char *obs_get_module_file_name(obs_module_t *module); // 模块文件名
const char *obs_get_module_name(obs_module_t *module); // 模块全名(无则 NULL)
const char *obs_get_module_author(obs_module_t *module); // 作者
const char *obs_get_module_description(obs_module_t *module); // 描述
const char *obs_get_module_binary_path(obs_module_t *module); // 二进制路径
const char *obs_get_module_data_path(obs_module_t *module); // 数据路径
void *obs_get_module_lib(obs_module_t *module); // 模块底层库句柄(dlopen 句柄)
其中 obs_get_module_name 的取值优先级从实现可见(libobs/obs-module.c):优先取 manifest.json 的 display_name,否则回退到插件导出的 obs_module_name()。
5.4 批量发现与加载
obs_add_module_path()
void obs_add_module_path(const char *bin, const char *data);
为 obs_find_modules 添加模块搜索路径。路径字符串中的 %module% 占位符会在实际使用时被替换为模块名。
obs_find_modules() / obs_find_modules2()
在已添加的搜索路径中查找所有模块,结果通过回调逐条返回:
struct obs_module_info {
const char *bin_path;
const char *data_path;
};
typedef void (*obs_find_module_callback_t)(void *param,
const struct obs_module_info *info);
struct obs_module_info2 {
const char *bin_path;
const char *data_path;
const char *name;
};
typedef void (*obs_find_module_callback2_t)(void *param,
const struct obs_module_info2 *info);
obs_find_modules2 是 v2 版本,回调结构体额外携带模块 name 字段。两个类型定义同时可在 libobs/obs.h 中找到。
obs_load_all_modules() / obs_load_all_modules2()
void obs_load_all_modules(void);
void obs_load_all_modules2(struct obs_module_failure_info *mfi);
便利函数:自动从所有模块路径加载全部模块。v2 版本额外提供加载失败信息:
struct obs_module_failure_info {
char **failed_modules; // 加载失败的模块列表(字符串指针数组)
size_t count;
};
释放方式:调用 obs_module_failure_info_free(mfi),或直接对 failed_modules 成员 bfree() 即可。实现位于 libobs/obs-module.c。
obs_add_safe_module() 与 Safe Mode
void obs_add_safe_module(const char *name);
将 name 加入安全模式(Safe Mode)允许加载的模块白名单;若白名单为空,则允许所有模块。name 是去掉扩展名的文件名。自 30.0 版本引入。
源码中可以看到其判定逻辑(libobs/obs-module.c):is_safe_module() 在 obs->safe_modules 为空时对任意模块返回允许,否则做精确名字比较——这解释了文档"空列表即全放行"的语义。
其他生命周期函数
void obs_module_failure_info_free(struct obs_module_failure_info *mfi); // 释放失败信息(内部 bfree failed_modules)
void obs_post_load_modules(void); // 通知所有模块"全部加载完成",即批量回调各插件的 obs_module_post_load
obs_enum_modules():枚举已加载模块
void obs_enum_modules(obs_enum_module_callback_t callback, void *param);
typedef void (*obs_enum_module_callback_t)(void *param, obs_module_t *module);
枚举当前所有已加载模块,前端遍历插件列表(如插件管理器界面)的基础设施。
5.5 模块文件/配置路径查询(前端视角)
char *obs_find_module_file(obs_module_t *module, const char *file);
char *obs_module_get_config_path(obs_module_t *module, const char *file);
obs_find_module_file:返回插件模块数据文件的位置;未找到返回NULL;字符串用bfree()释放。obs_module_get_config_path:返回插件模块配置文件的路径(无论文件是否存在);配置目录未设置返回NULL。
两者文档都特别注明:模块内部应使用 obs-module.h 中定义的 obs_module_file() / obs_module_config_path() 宏,它们省去显式传模块指针,是更优雅的用法。
6. 完整示例:一个最小插件的 API 契约
综合以上内容,一个注册单个源、启用标准本地化的最小模块,所需 API 面如下(示例取自 libobs/obs-module.h 的文档内嵌示例,与 plugins/image-source/image-source.c 的真实结构一致):
#include <obs-module.h>
OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("my-plugin", "en-US")
extern struct obs_source_info my_source;
bool obs_module_load(void)
{
obs_register_source(&my_source);
return true;
}
各部分对应的 API 职责:
OBS_DECLARE_MODULE():导出obs_module_set_pointer/obs_current_module/obs_module_ver;OBS_MODULE_USE_DEFAULT_LOCALE:自动生成obs_module_text、obs_module_get_string、obs_module_set_locale、obs_module_free_locale;obs_module_load():唯一的必需导出,返回false即加载失败;- 其余(
obs_module_unload、obs_module_post_load、obs_module_name、obs_module_description)均按需导出; - 可选地在数据目录放置
manifest.json提供display_name/id/version/description等元数据,供前端展示。
7. 小结:模块加载全链路
把前端函数按实际执行顺序串起来,即为 libobs 插件的完整生命周期:
obs_add_module_path(bin, data) // 注册搜索路径(支持 %module% 占位)
→ obs_find_modules(2)(callback) // 发现候选模块
→ obs_add_safe_module(name) // (可选)安全模式白名单
→ obs_open_module(&mod, path, data) // dlopen + 导出检查 + 版本检查 + manifest 元数据
→ obs_init_module(mod) // 调用插件 obs_module_load()
→ obs_post_load_modules() // 触发各插件 obs_module_post_load()
→ obs_log_loaded_modules() // 日志留痕
任何一步失败的诊断入口都有据可查:obs_open_module 的返回码区分"找不到/符号缺失/版本过新/被硬编码跳过"四类失败;obs_load_all_modules2 + struct obs_module_failure_info 批量给出失败模块清单;而 obs_log_loaded_modules 与 obs_find_modules2 则提供了成功侧的审计能力。
适用前提:本文所有行为均以当前仓库
master代码为准,API 声明集中在 libobs/obs.h 与 libobs/obs-module.h,实现在 libobs/obs-module.c。obs_add_safe_module自 30.0 引入;MODULE_FILE_NOT_FOUND已由MODULE_FAILED_TO_OPEN取代并标记废弃。
参考文档:docs/sphinx/reference-modules.rst,同级 API 参考还包括 Core、核心对象、Graphics 与 Media I/O。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00