OBS Studio obs_data_t 数据设置 API 详解:源/编码器配置的 JSON 存储、默认值与安全读写机制
本篇以 OBS Studio 官方 Sphinx 文档 reference-settings.rst 中“Data Settings API Reference (obs_data_t)”一节为核心,完整讲解 obs_data_t / obs_data_array_t 数据设置对象的设计目标、全部 API 分组(通用函数、Set/Get 函数、默认值函数、自动选择函数、数组函数),并结合 libobs/obs-data.c 与 libobs/obs-data.h 的源码实现,说明其引用计数、JSON 序列化、备份回退等底层机制,帮助你为 OBS 插件开发(source、encoder、output、service)写出正确且可持久化的配置处理代码。
一、obs_data_t 是什么:比 JSON 对象更强的配置容器
官方文档对该 API 的定位是:
Data settings objects are reference-counted objects that store values in a string-table or array. They're similar to Json objects, but additionally allow additional functionality such as default or auto-selection values. Data is saved/loaded to/from Json text and Json text files.
即:数据设置对象是引用计数的对象,内部以“字符串表(string-table)”或数组形式存储值;类似 JSON 对象,但额外支持**默认值(default)与自动选择值(autoselect)**等能力,并可保存到/加载自 JSON 文本或 JSON 文件。
从源码结构看,这三个核心类型定义在 libobs/obs-data.h 中:
struct obs_data;
struct obs_data_item;
struct obs_data_array;
typedef struct obs_data obs_data_t;
typedef struct obs_data_item obs_data_item_t;
typedef struct obs_data_array obs_data_array_t;
其内部结构在 libobs/obs-data.c 中实现:
struct obs_data_item {
volatile long ref;
const char *name;
struct obs_data *parent;
UT_hash_handle hh; // uthash 哈希表句柄
enum obs_data_type type;
size_t name_len, data_len, data_size,
default_len, default_size, autoselect_size, capacity;
};
struct obs_data {
volatile long ref; // 引用计数
char *json; // 缓存的 JSON 字符串
struct obs_data_item *items;
};
struct obs_data_array {
volatile long ref;
DARRAY(obs_data_t *) objects; // 动态数组
};
几个值得注意的实现事实:
- 值类型枚举:
enum obs_data_type定义了OBS_DATA_NULL / STRING / NUMBER / BOOLEAN / OBJECT / ARRAY六种类型;数字类型进一步区分为OBS_DATA_NUM_INT与OBS_DATA_NUM_DOUBLE(见 libobs/obs-data.h)。 - 单项只分配一次内存:
obs_data_item的 name、user 值、default 值、autoselect 值按对齐连续排在同一块分配之后(源码注释 "designed to be one allocation only",见 libobs/obs-data.c),减少碎片化分配。 - JSON 字符串缓存在对象内:
struct obs_data中的json字段保存最近一次生成的 JSON 文本,对应obs_data_get_last_json()的语义。
头文件顶部注释明确了用途:"This is used for retrieving or setting the data settings for things such as sources, encoders, etc. This is designed for JSON serialization."(见 libobs/obs-data.h)——它是 source/encoder 等对象设置的标准存取层。
二、通用函数:创建、销毁与 JSON 互转
文档 “General Functions” 一节的 API 与源码对应关系如下(声明见 libobs/obs-data.h):
2.1 创建与引用计数
obs_data_t *obs_data_create();
返回一个新的数据对象引用,需以 obs_data_release() 释放。实现见 libobs/obs-data.c:bzalloc 零初始化后 ref = 1。
obs_data_t *obs_data_create_from_json(const char *json_string);
obs_data_t *obs_data_create_from_json_file(const char *json_file);
obs_data_t *obs_data_create_from_json_file_safe(const char *json_file, const char *backup_ext);
obs_data_create_from_json:从 JSON 字符串创建。源码中使用 jansson 解析并带JSON_REJECT_DUPLICATES标志(libobs/obs-data.c)——重复键的 JSON 会解析失败,失败时输出LOG_ERROR日志并返回NULL。obs_data_create_from_json_file:读取 UTF-8 文件后调用上述字符串版本。obs_data_create_from_json_file_safe:加载失败的备份回退机制。当主文件损坏或加载失败且提供了backup_ext时,会拼接备份文件路径(若非.开头会自动补.)、检查其存在性,打印警告日志 “attempting backup file”,将备份文件os_rename回主文件再重新加载(libobs/obs-data.c)。这是场景文件等关键配置防损坏的第一道防线。
引用计数操作:
void obs_data_addref(obs_data_t *data);
void obs_data_release(obs_data_t *data);
addref 对空指针安全;release 在计数归零时调用 obs_data_destroy,遍历哈希表逐个 obs_data_item_detach 并释放,且注释特别提示 JSON 文本不能用 libobs 的 bfree 释放,因为它由 jansson 分配(libobs/obs-data.c)。
另外,头文件提供了一个内联便捷函数 obs_data_newref:对非空指针加引用,对 NULL 则创建新对象(libobs/obs-data.h),常用于“取嵌套对象再回填”的赋值场景。
2.2 JSON 序列化:四种 get_json 变体
const char *obs_data_get_json(obs_data_t *data);
const char *obs_data_get_json_with_defaults(obs_data_t *data);
const char *obs_data_get_json_pretty(obs_data_t *data);
const char *obs_data_get_json_pretty_with_defaults(obs_data_t *data);
const char *obs_data_get_last_json(obs_data_t *data);
文档说明:生成的字符串分配在数据对象内部,无需手动 free。源码 libobs/obs-data.c 显示它们共用 obs_data_get_json_internal(data, pretty, with_defaults):
pretty时 jansson 使用JSON_PRESERVE_ORDER | JSON_INDENT(4),否则用JSON_PRESERVE_ORDER | JSON_COMPACT;- 每次生成前先
free(data->json)清掉旧缓存,再用json_dumps重新写入data->json——因此对象中始终只缓存最近一次的 JSON 文本,obs_data_get_last_json()只是返回这个缓存(不生成新字符串); - 关键语义:默认序列化(
with_defaults == false)时,没有 user value 的项会被跳过(源码行 578:if (!with_defaults && !obs_data_item_has_user_value(item)) continue;)。这正是..._with_defaults变体的价值——把默认值一并落盘。
2.3 保存与合并
bool obs_data_save_json(obs_data_t *data, const char *file);
bool obs_data_save_json_safe(obs_data_t *data, const char *file, const char *temp_ext, const char *backup_ext);
obs_data_save_json:先生成 JSON,再用os_quick_write_utf8_file写入;JSON 为空返回false(libobs/obs-data.c)。obs_data_save_json_safe:走os_quick_write_utf8_file_safe,覆盖旧文件前先按backup_ext备份,防止写坏配置文件;源码中还额外提供了文档未列出的obs_data_save_json_pretty_safe(缩进美化 + 安全写,libobs/obs-data.c)。
void obs_data_apply(obs_data_t *target, obs_data_t *apply_data);
将 apply_data 合并进 target。实现(libobs/obs-data.c)遍历 apply_data 的哈希表逐项 copy_item;对同一对象(target == apply_data)直接返回以避免自引用死锁。copy_item 对 OBJECT/ARRAY 类型会深拷贝子树,即 apply 是深合并而非指针赋值。
void obs_data_erase(obs_data_t *data, const char *name); // 删除名为 name 的 user 数据项
void obs_data_clear(obs_data_t *data); // 清空全部 user 数据
注意 obs_data_clear 的实现是 clear_item(libobs/obs-data.c):它只重置每个 item 的 user 值部分(释放嵌套对象/数组的引用),而保留 default/autoselect 值与 item 本身——与 obs_data_erase(整个 item 从哈希表摘除)语义不同,插件中“恢复默认”应使用 clear 而不是 erase。
三、Set / Get 函数:六种标量与容器类型
文档 “Set Functions” 与 “Get Functions” 两组 API 构成配置读写的基本面:
// Set
void obs_data_set_string(obs_data_t *data, const char *name, const char *val);
void obs_data_set_int(obs_data_t *data, const char *name, long long val);
void obs_data_set_double(obs_data_t *data, const char *name, double val);
void obs_data_set_bool(obs_data_t *data, const char *name, bool val);
void obs_data_set_obj(obs_data_t *data, const char *name, obs_data_t *obj);
void obs_data_set_array(obs_data_t *data, const char *name, obs_data_array_t *array);
// Get
const char *obs_data_get_string(obs_data_t *data, const char *name);
long long obs_data_get_int(obs_data_t *data, const char *name);
double obs_data_get_double(obs_data_t *data, const char *name);
bool obs_data_get_bool(obs_data_t *data, const char *name);
obs_data_t *obs_data_get_obj(obs_data_t *data, const char *name); // 返回已加引用的引用
obs_data_array_t*obs_data_get_array(obs_data_t *data, const char *name); // 返回已加引用的引用
实现要点(libobs/obs-data.c):
- 内部统一的
set_item_t函数指针机制让 user/default/autoselect 三层共享同一套写入逻辑; obs_data_set_string对NULL值写入空串""(不会存 NULL);- 整数与双精度共用
OBS_DATA_NUMBER类型,用struct obs_data_number({type, union{int_val, double_val}})区分存储; obs_data_get_obj/obs_data_get_array返回的是“增量引用”(incremented reference)——文档与源码都强调必须分别用obs_data_release()/obs_data_array_release()释放,这是插件开发中最常见的泄漏点。
文档还特别标注了一个前端行为细节:
Note: If the data object was generated from a OBS_COMBO_TYPE_EDITABLE property, the property's
namewill be returned instead of itsval.
即由可编辑下拉框(OBS_COMBO_TYPE_EDITABLE)属性生成的设置,obs_data_get_string 返回的是选项显示名(name)而不是内部值(val),插件读取此类设置时要知晓这一点。
3.1 一个最小可用的插件配置读写示例
结合以上语义,一个典型的 source 插件 update 回调处理可以写成:
#include <obs.h>
static void my_source_video_tick(struct obs_source *source)
{
obs_data_t *settings = obs_source_get_settings(source);
long long width = obs_data_get_int(settings, "width"); // 未设置时返回 0
bool enabled = obs_data_get_bool(settings, "enabled");
const char *path = obs_data_get_string(settings, "path");
obs_data_release(settings); // obs_source_get_settings 需显式释放
}
保存/加载时优先使用 _safe 变体:obs_data_create_from_json_file_safe(path, "bak") 读取、obs_data_save_json_safe(data, path, "temp", "bak") 写入,可获得“临时文件 + 备份”的双重保护。
四、默认值(Default Values):未设置时的取值依据
文档 “Default Value Functions” 一节的定义:Default values are used to determine what value will be given if a value is not set.(默认值用于决定某个值未被设置时应取什么值。)
4.1 API 一览
obs_data_t *obs_data_get_defaults(obs_data_t *data); // 收集所有默认值(递归含嵌套对象)
void obs_data_set_default_string(obs_data_t *data, const char *name, const char *val);
const char *obs_data_get_default_string(obs_data_t *data, const char *name);
void obs_data_set_default_int(obs_data_t *data, const char *name, long long val);
long long obs_data_get_default_int(obs_data_t *data, const char *name);
void obs_data_set_default_double(obs_data_t *data, const char *name, double val);
double obs_data_get_default_double(obs_data_t *data, const char *name);
void obs_data_set_default_bool(obs_data_t *data, const char *name, bool val);
bool obs_data_get_default_bool(obs_data_t *data, const char *name);
void obs_data_set_default_obj(obs_data_t *data, const char *name, obs_data_t *obj);
obs_data_t *obs_data_get_default_obj(obs_data_t *data, const char *name); // 需 release
void obs_data_set_default_array(obs_data_t *data, const char *name, obs_data_array_t *arr);
obs_data_array_t *obs_data_get_default_array(obs_data_t *data, const char *name);
4.2 源码中的递归收集逻辑
obs_data_get_defaults 的实现(libobs/obs-data.c)值得细读:它遍历所有 item,按类型分发——
- STRING / NUMBER / BOOLEAN:读取 default 值写入新对象;
- OBJECT:递归调用自身处理子对象(
obs_data_get_defaults(val)); - ARRAY:通过回调
get_defaults_array_cb逐个递归收集数组元素的默认值并push_back到新数组。
这与文档“all default values (recursively for all objects as well)”的描述一致。
4.3 默认值在序列化中的体现
obs_data_get_json_with_defaults / obs_data_get_json_pretty_with_defaults 在 obs_data_to_json(data, with_defaults = true) 时不再跳过没有 user value 的项(libobs/obs-data.c),因此导出的 JSON 会包含全部默认值——常用于“导出完整配置文件”或与用户当前值做差异比对。
另外,头文件中与默认值配套的状态检查/清除函数(文档未单列、但属于同一数据模型):
bool obs_data_has_user_value(obs_data_t *data, const char *name);
bool obs_data_has_default_value(obs_data_t *data, const char *name);
void obs_data_unset_user_value(obs_data_t *data, const char *name);
void obs_data_unset_default_value(obs_data_t *data, const char *name);
五、Autoselect 值:应用层的“强制修正”通道(已弃用)
文档 “Autoselect Functions” 的定义是:Autoselect values are optionally used to determine what values should be used to ensure functionality if the currently set values are inappropriate or invalid.(当当前设置的值不恰当或无效时,可选地用于确定应使用什么值以保证功能可用。)
对应 API 与 default 完全对称:obs_data_set/get_autoselect_string/int/double/bool/obj/array,其中 obs_data_set_autoselect_array 标注 versionadded:: 30.1。
需要特别注意的事实:在 libobs/obs-data.h 中,全部 autoselect setter/getter 均带有 OBS_DEPRECATED 标记,头文件注释说明其用途是 "Application overrides — Use these to communicate the actual values of settings in case the user settings aren't appropriate"。即:旧版 OBS 曾用 autoselect 值在“用户设置无效时”向插件通报应用实际采用的值,而当前代码库已将其标记为弃用接口。新插件开发不应依赖该通道,可参考 obs_data_has_autoselect_value 同样被标记弃用(libobs/obs-data.h)这一事实确认其整体退出趋势。文档中保留了这一节,是因为序列化兼容性与旧插件仍在引用这些符号。
六、Array Functions:数据数组的引用计数操作
obs_data_array_t *obs_data_array_create();
void obs_data_array_addref(obs_data_array_t *array);
void obs_data_array_release(obs_data_array_t *array);
size_t obs_data_array_count(obs_data_array_t *array);
obs_data_t *obs_data_array_item(obs_data_array_t *array, size_t idx); // 返回已加引用的引用
size_t obs_data_array_push_back(obs_data_array_t *array, obs_data_t *obj);
void obs_data_array_insert(obs_data_array_t *array, size_t idx, obs_data_t *obj);
void obs_data_array_erase(obs_data_array_t *array, size_t idx);
源码(libobs/obs-data.c)验证了以下语义:
- 数组持有元素的引用:
push_back/insert会对obj执行os_atomic_inc_long(&obj->ref);erase会对被移除元素obs_data_release;数组destroy时逐个释放所有元素。因此把同一个obs_data_t挂进数组后,原句柄仍可独立释放; obs_data_array_item越界安全:索引越界返回NULL,有效项返回前加引用,调用方必须obs_data_release;push_back返回下标(size_t),非法参数返回 0;- 头文件还额外提供
obs_data_array_push_back_array(批量拼接另一数组)与obs_data_array_enum(回调遍历),后者被obs_data_get_defaults内部使用(libobs/obs-data.h)。
JSON 解析侧与之呼应:obs_data_add_json_array(libobs/obs-data.c)在从 JSON 构建数组时只接受 object 类型的数组元素(json_is_object 过滤),标量元素会被静默跳过。
七、典型工作流:从 JSON 文件到插件设置
把各节串起来,OBS 插件中处理设置的标准生命周期是:
- 加载:
obs_data_create_from_json_file_safe(file, "bak")—— 损坏时自动回退备份(libobs/obs-data.c); - 读取:
obs_data_get_int/bool/string/obj读取 user 值;需要兜底时用obs_data_get_default_*或自行判断obs_data_has_user_value; - 修改:
obs_data_set_*写 user 值;obs_data_unset_user_value恢复为“未设置”;obs_data_apply合并补丁数据; - 序列化/保存:
obs_data_get_json_pretty()得到缓存的 JSON 文本(无需 free);obs_data_save_json_safe(data, file, "temp", "bak")原子落盘并备份旧文件; - 释放:所有
get_obj/get_array/array_item返回的增量引用、以及create/create_from_json*的根引用,最终都要配对obs_data_release/obs_data_array_release。
八、参考文件
| 内容 | 路径 |
|---|---|
| API 参考文档(本文核心) | reference-settings.rst |
| 公共头文件(全部声明 + 弃用标记) | libobs/obs-data.h |
| 实现(结构、JSON 互转、apply/erase/clear、数组) | libobs/obs-data.c |
| C++ 封装(obs_data 智能引用) | libobs/obs.hpp |
| 属性系统(settings 的 UI 来源) | reference-properties.rst、libobs/obs-properties.h |
| 配置场景/源对象的文档 | reference-scenes.rst、reference-sources.rst |
以上 API 以当前仓库 libobs/obs-data.h 的实际声明为准;使用前提链接 libobs(CMake 构建中由 libobs/CMakeLists.txt 组织),且注意 autoselect 一族接口在当前代码库中已被标记弃用,新代码应避免使用。
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