OBS Studio 服务 API 详解:obs_service_t 对象模型、obs_service_info 结构与 rtmp_custom 实战实现
本文围绕 OBS Studio 的 Service API(obs_service_t)展开,完整覆盖服务类型定义结构 obs_service_info 的全部回调成员、服务对象的引用计数与弱引用模型、连接信息类型枚举(get_connect_info),以及 29.1 版本新增的协议/编解码器/输出类型相关 API;并结合仓库中 plugins/rtmp-services 插件的 rtmp_custom 服务实现,讲解一套可直接参考的服务注册、属性构建与编码器设置改写流程。读完本文后,你将具备为 OBS Studio 编写自定义流媒体服务(如对接任意 RTMP/SRT 推流平台)所需的 API 级能力。
一、什么是 Service:输出与推流平台之间的抽象
Service 是流媒体服务(streaming service)的自定义实现,它与"用于推流的输出"(output)配合使用。例如可以编写一个专门针对 Twitch 的服务实现,另一个针对 YouTube 的实现,从而能够登录并使用各平台 API 获取 RTMP 服务器地址、控制频道等操作。libobs/obs-service.h 是实现服务时专用的头文件。
需要说明的一点:官方文档 docs/sphinx/reference-services.rst 中带有作者注记——“the service API is incomplete as of this writing”(撰写时该 API 尚未完整)。因此阅读本 API 时应把它视为仍在演进中的扩展点:头文件中存在文档未列出的成员(后文会指出),且注册入口采用了带结构体大小的 ABI 稳定设计。
从仓库源码结构看,该 API 的主要使用方是 plugins/rtmp-services 插件,其 service-specific 目录下实现了 twitch、amazon-ivs、dacast、nimotv、showroom 等平台服务,rtmp-custom.c 则实现了通用的"自定义推流服务器"服务。这些服务的共同点是把"平台账号信息如何转化为推流连接参数"这一平台特定逻辑封装到服务内部,而推流输出(FFmpeg output)只通过 API 向服务查询 URL、Key 等信息,二者解耦。
核心对象类型为:
obs_service_t:引用计数(reference-counted)的服务对象;obs_weak_service_t:服务对象的弱引用(weak reference)。
#include <obs.h>
二、obs_service_info:服务定义结构逐成员解析
服务类型由 struct obs_service_info 定义,通过 obs_register_service() 注册。完整结构定义见 libobs/obs-service.h。各成员按是否必填、是否可选分组说明如下。
2.1 必填成员
| 成员 | 说明 |
|---|---|
const char *id |
服务类型的全局唯一字符串标识(必填),如 "rtmp_custom" |
const char *(*get_name)(void *type_data) |
返回服务类型的本地化(翻译)显示名称,type_data 为结构自身的 type_data 字段 |
void *(*create)(obs_data_t *settings, obs_service_t *service) |
创建服务实现数据(implementation data),参数为初始化设置与所属服务对象,返回实现数据指针 |
void (*destroy)(void *data) |
销毁实现数据,与 create 配对使用 |
注意区分两组"数据":实现数据(create/destroy 管理)是某个服务实例的内部状态;type_data(type_data/free_type_data)是服务类型级的私有数据,用于在同一套回调被多个不同服务类型复用时区分彼此,可选提供。
2.2 可选回调成员
| 成员 | 说明 |
|---|---|
void (*update)(void *data, obs_data_t *settings) |
服务设置被更新时回调,传入新设置(可选) |
void (*get_defaults)(obs_data_t *settings) |
向 settings 中写入默认设置,应调用 obs_data_set_default* 系列函数(可选) |
obs_properties_t *(*get_properties)(void *data) |
返回服务的属性(properties)信息,前端可据此自动生成设置界面控件(可选) |
bool (*initialize)(void *data, obs_output_t *output) |
在输出准备启动、编码器与输出初始化之前调用;返回 true 允许输出启动,false 阻止启动(可选) |
const char *(*get_url)(void *data) |
返回推流 URL |
const char *(*get_key)(void *data) |
返回推流 Key |
const char *(*get_username)(void *data) |
返回用户名(可选) |
const char *(*get_password)(void *data) |
返回密码(可选) |
void (*apply_encoder_settings)(void *data, obs_data_t *video_encoder_settings, obs_data_t *audio_encoder_settings) |
应用该服务特有的编码器设置。例如某服务要求特定关键帧间隔或存在码率上限时,可在前端调用 obs_service_apply_encoder_settings() 的情况下修改视频/音频编码器设置(可选) |
const char *(*get_output_type)(void *data) |
返回该服务推荐(preferred)的输出类型(可选) |
const char **(*get_supported_video_codecs)(void *data) |
返回支持的视频编解码器字符串指针数组。数组应由插件持有,调用方不负责释放(通常建议用 strlist_split 生成)(可选) |
const char **(*get_supported_audio_codecs)(void *data) |
同上,音频编解码器(可选,29.1 新增) |
const char *(*get_protocol)(void *data) |
返回服务使用的协议(可选,29.1 新增) |
const char *(*get_connect_info)(void *data, uint32_t type) |
按类型值输出连接信息(可选,29.1 新增,见第三节) |
bool (*can_try_to_connect)(void *data) |
返回服务是否已具备连接所需的全部信息(可选,29.1 新增)。未设置时 obs_service_can_try_to_connect() 默认返回 true |
2.3 头文件中存在、而参考文档未列出的成员
对比 libobs/obs-service.h,结构体还包含以下成员,阅读源码实现时会遇到,此处如实说明(其中部分尚未体现在参考文档中):
void (*activate)(void *data, obs_data_t *settings)/void (*deactivate)(void *data):服务被绑定到输出并激活/去激活时调用。在 libobs/obs-service.c 中,obs_service_activate()校验服务已分配给输出(service->output)后才触发activate回调,obs_service_deactivate()则对称地触发deactivate;void (*get_supported_resolutions)(void *data, struct obs_service_resolution **resolutions, size_t *count)、void (*get_max_fps)(void *data, int *fps)、void (*get_max_bitrate)(void *data, int *video_bitrate, int *audio_bitrate):分别上报服务支持的分辨率列表、最大帧率与最大码率,对应 libobs/obs-service.c 中的obs_service_get_supported_resolutions()、obs_service_get_max_fps()、obs_service_get_max_bitrate(),未实现时输出默认零值;bool (*deprecated_1)():废弃占位成员,用于保持结构布局兼容。
2.4 ABI 稳定设计:obs_register_service_s
注册函数在头文件中定义为带结构体大小的显式版本:
EXPORT void obs_register_service_s(const struct obs_service_info *info, size_t size);
#define obs_register_service(info) obs_register_service_s(info, sizeof(struct obs_service_info))
(见 libobs/obs-service.h)。注册时携带 sizeof(struct obs_service_info),便于未来在结构体尾部追加成员时保持 ABI 兼容——这是理解上面"deprecated 占位成员"存在意义的关键。
参考文档中出现的 get_defaults2 / get_properties2(带 type_data 的版本)属于同一演进脉络的变体签名,仓库当前头文件中对应的是不带 type_data 的单参数版本。
三、get_connect_info:面向协议的连接信息类型枚举
29.1 引入的 get_connect_info 回调让输出(output)按类型向服务索取连接信息,枚举值定义在 libobs/obs-service.h:
| 枚举 | 值 | 含义 |
|---|---|---|
OBS_SERVICE_CONNECT_INFO_SERVER_URL |
0 | 服务器 URL |
OBS_SERVICE_CONNECT_INFO_STREAM_ID |
2 | 流 ID |
OBS_SERVICE_CONNECT_INFO_STREAM_KEY |
2 | Stream key,是 STREAM_ID 的别名 |
OBS_SERVICE_CONNECT_INFO_USERNAME |
4 | 用户名 |
OBS_SERVICE_CONNECT_INFO_PASSWORD |
6 | 密码 |
OBS_SERVICE_CONNECT_INFO_ENCRYPT_PASSPHRASE |
8 | 加密口令 |
OBS_SERVICE_CONNECT_INFO_BEARER_TOKEN |
10 | Bearer Token(头文件中定义) |
奇数值类型保留给第三方协议(头文件注释:“Odd numbers are reserved for custom info from third-party protocols”)。根据协议不同,服务需要提供输出建立连接所需的信息;无关或未使用的类型可返回 NULL。
仓库中的实现示例是 rtmp-custom.c 的 rtmp_custom_get_connect_info,它对不同协议做了差异化映射:SRT 协议把 password 同时作为 ENCRYPT_PASSPHRASE,RIST 协议把推流 key 作为口令,BEARER_TOKEN 返回 NULL:
static const char *rtmp_custom_get_connect_info(void *data, uint32_t type)
{
switch ((enum obs_service_connect_info)type) {
case OBS_SERVICE_CONNECT_INFO_SERVER_URL:
return rtmp_custom_url(data);
case OBS_SERVICE_CONNECT_INFO_STREAM_ID:
return rtmp_custom_key(data);
case OBS_SERVICE_CONNECT_INFO_USERNAME:
return rtmp_custom_username(data);
case OBS_SERVICE_CONNECT_INFO_PASSWORD:
return rtmp_custom_password(data);
case OBS_SERVICE_CONNECT_INFO_ENCRYPT_PASSPHRASE: {
const char *protocol = rtmp_custom_get_protocol(data);
if ((strcmp(protocol, "SRT") == 0))
return rtmp_custom_password(data);
else if ((strcmp(protocol, "RIST") == 0))
return rtmp_custom_key(data);
break;
}
case OBS_SERVICE_CONNECT_INFO_BEARER_TOKEN:
return NULL;
}
return NULL;
}
四、通用服务函数:完整 API 一览
以下为参考文档 docs/sphinx/reference-services.rst 列出的通用函数,实现集中在 libobs/obs-service.c。
4.1 注册与创建
void obs_register_service(struct obs_service_info *info);
注册服务类型,通常在 obs_module_load() 或程序初始化阶段调用。
const char *obs_service_get_display_name(const char *id);
调用 get_name 回调,返回服务类型的本地化显示名;类型不存在时返回 NULL(实现见 libobs/obs-service.c)。
obs_service_t *obs_service_create(const char *id, const char *name,
obs_data_t *settings, obs_data_t *hotkey_data);
以指定设置创建服务:id 为类型标识;name 为期望的服务名,若重名会被自动处理为唯一名;settings 可为 NULL;hotkey_data 为已保存的热键数据,可为 NULL;失败(如类型未注册)返回 NULL。
从源码看(libobs/obs-service.c),创建过程分三步:通过 find_service(id) 在已注册类型数组中线性查找定义,未找到即记录 Service '%s' not found 错误日志并返回 NULL;随后初始化引用计数上下文并调用 info.create 生成实现数据;最后把服务插入全局服务链表(受 services_mutex 保护)。
4.2 引用计数与弱引用
obs_service_t *obs_service_get_ref(obs_service_t *service);
void obs_service_release(obs_service_t *service);
obs_weak_service_t *obs_service_get_weak_service(obs_service_t *service);
obs_service_t *obs_weak_service_get_service(obs_weak_service_t *weak);
void obs_weak_service_addref(obs_weak_service_t *weak);
void obs_weak_service_release(obs_weak_service_t *weak);
obs_service_get_ref:对象仍有效时返回加引后的引用,否则返回NULL;obs_service_release:释放一个强引用,最后一个引用释放时对象销毁;- 弱引用一对函数用于强/弱引用互转;服务销毁后
obs_weak_service_get_service返回NULL。
libobs/obs-service.c 中 obs_service_release 的实现细节值得注意:释放强引用前先取 context.control 对应的弱引用做 obs_ref_release,归零时按"先 obs_service_destroy、再 obs_weak_service_release"的固定顺序执行,注释指出这是为了保证 obs.c 中 get_context_by_name 所依赖的弱引用在上下文仍列于列表中时保持存活。
另一个生命周期细节:obs_service_destroy 并不立即销毁对象(libobs/obs-service.c),若服务当前 active(正在被输出使用),仅标记 destroy = true 并延迟销毁,直到 obs_service_deactivate 发现 destroy 标记时(libobs/obs-service.c)才真正调用 actually_destroy_service,后者会调用实现侧的 destroy 回调、解除与 output 的绑定并释放上下文。
4.3 名称、设置与属性
const char *obs_service_get_name(const obs_service_t *service);
obs_data_t *obs_service_defaults(const char *id);
obs_properties_t *obs_service_properties(const obs_service_t *service);
obs_properties_t *obs_get_service_properties(const char *id);
obs_data_t *obs_service_get_settings(const obs_service_t *service);
void obs_service_update(obs_service_t *service, obs_data_t *settings);
obs_service_get_name:返回服务名;obs_service_defaults(id):返回该服务类型默认设置的一个加引副本(内部调用get_defaults),用obs_data_release()释放;- 两个 properties 函数分别面向"已有服务实例"和"服务类型 id",用于按需自动生成设置界面控件,返回的属性列表用
obs_properties_destroy()释放。实现上(libobs/obs-service.c),obs_get_service_properties会先取默认设置再通过obs_properties_apply_settings应用到属性上,保证界面初始状态与默认值一致; obs_service_get_settings:返回服务当前设置的一个加引副本,obs_data_release()释放;obs_service_update:更新服务上下文设置,先用obs_data_apply合并进上下文设置,再触发update回调。
4.4 编码器设置与 29.1 新增查询函数
void obs_service_apply_encoder_settings(obs_service_t *service,
obs_data_t *video_encoder_settings,
obs_data_t *audio_encoder_settings);
应用服务特有的视频编码器设置;两个参数均可为 NULL,但两者皆为 NULL 时不触发回调(见 libobs/obs-service.c 的实现)。
const char **obs_service_get_supported_video_codecs(const obs_service_t *service);
const char **obs_service_get_supported_audio_codecs(const obs_service_t *service);
const char *obs_service_get_protocol(const obs_service_t *service);
const char *obs_service_get_preferred_output_type(const obs_service_t *service);
const char *obs_service_get_connect_info(const obs_service_t *service, uint32_t type);
bool obs_service_can_try_to_connect(const obs_service_t *service);
- 两个编解码器函数返回以
NULL结尾的字符串指针数组,不需要调用方释放;未实现时返回NULL; obs_service_get_protocol(29.1):返回服务当前使用的协议;obs_service_get_preferred_output_type(29.1):返回该服务偏好的输出类型;obs_service_get_connect_info(29.1):按get_connect_info回调的类型值查询连接信息;obs_service_can_try_to_connect(29.1):判断服务是否具备全部连接信息;can_try_to_connect回调未设置时返回true(libobs/obs-service.c 印证了这一默认行为)。
五、实战:rtmp_custom 服务的完整实现剖析
plugins/rtmp-services/rtmp-custom.c 是一个麻雀虽小、五脏俱全的服务实现,覆盖了创建/销毁/更新、属性构建、协议判定、编码器设置改写与连接信息上报的完整链路。
5.1 实现数据结构与生命周期
struct rtmp_custom {
char *server, *key;
bool use_auth;
char *username, *password;
};
create 中先 bzalloc 再直接复用 update 完成初始化,update 里先释放旧字符串再 bstrdup 新值,destroy 负责对称释放——这是服务实现数据管理的最简可靠模式(rtmp-custom.c)。
5.2 属性构建与联动可见性
rtmp_custom_properties 构建了 server(URL 文本框)、key(密码框)、use_auth(开关)、username、password 五个属性,并通过修改回调实现联动:勾选"使用认证"时才显示用户名/密码框:
static bool use_auth_modified(obs_properties_t *ppts, obs_property_t *p, obs_data_t *settings)
{
bool use_auth = obs_data_get_bool(settings, "use_auth");
p = obs_properties_get(ppts, "username");
obs_property_set_visible(p, use_auth);
p = obs_properties_get(ppts, "password");
obs_property_set_visible(p, use_auth);
return true;
}
这与 obs_service_info.get_properties 的用途完全对应:前端(如 OBS 的"设置 → 流"页面)拿到 obs_properties_t 后即可自动生成控件,obs_property_set_modified_callback 保证设置间的依赖关系在界面层生效。
5.3 协议判定
rtmp_custom_get_protocol 通过 URL 前缀判定协议(rtmp-custom.c):
#define RTMPS_PREFIX "rtmps://"
#define SRT_PREFIX "srt://"
#define RIST_PREFIX "rist://"
rtmps:// → "RTMPS",srt:// → "SRT",rist:// → "RIST",其余一律 "RTMP"。这一返回值同时被 obs_service_get_protocol 与编码器设置改写两处消费。
5.4 用 apply_encoder_settings 改写编码器行为
static void rtmp_custom_apply_settings(void *data, obs_data_t *video_settings, obs_data_t *audio_settings)
{
struct rtmp_custom *service = data;
const char *protocol = rtmp_custom_get_protocol(service);
bool has_mpegts = false;
bool is_rtmp = false;
if (strcmp(protocol, "SRT") == 0 || strcmp(protocol, "RIST") == 0)
has_mpegts = true;
if (strcmp(protocol, "RTMP") == 0 || strcmp(protocol, "RTMPS") == 0)
is_rtmp = true;
if (!is_rtmp && video_settings != NULL)
obs_data_set_bool(video_settings, "repeat_headers", true);
if (has_mpegts && audio_settings != NULL)
obs_data_set_bool(audio_settings, "set_to_ADTS", true);
}
这正是文档中 apply_encoder_settings 成员描述场景的具体体现:非 RTMP 协议下打开视频设置里的 repeat_headers(重复发送头部),SRT/RIST(MPEG-TS 载荷)下把音频设置为 set_to_ADTS。前端在输出启动前调用 obs_service_apply_encoder_settings(),服务即可在不改变用户设置界面默认值的前提下注入平台/协议必需的编码参数。
5.5 服务类型定义与注册
struct obs_service_info rtmp_custom_service = {
.id = "rtmp_custom",
.get_name = rtmp_custom_name,
.create = rtmp_custom_create,
.destroy = rtmp_custom_destroy,
.update = rtmp_custom_update,
.get_properties = rtmp_custom_properties,
.get_protocol = rtmp_custom_get_protocol,
.get_url = rtmp_custom_url,
.get_key = rtmp_custom_key,
.get_connect_info = rtmp_custom_get_connect_info,
.get_username = rtmp_custom_username,
.get_password = rtmp_custom_password,
.apply_encoder_settings = rtmp_custom_apply_settings,
.can_try_to_connect = rtmp_custom_can_try_to_connect,
};
该静态定义由插件入口 rtmp-services-main.c 在模块加载阶段通过 obs_register_service(&rtmp_custom_service) 注册,与文档所述"通常在 obs_module_load() 或程序初始化阶段注册"一致。同插件还注册了 service-specific/twitch.c 等面向具体平台的服务——例如 Twitch 服务维护本地 ingest 缓存(twitch_ingests.json),其连接信息同样经由服务接口向上暴露。can_try_to_connect 的实现非常直观:服务器地址非空即认为可以尝试连接(rtmp-custom.c)。
六、服务与输出的协作流程
把文档与源码串起来,一次推流启动中服务参与的时序大致如下:
- 注册期:模块加载时
obs_register_service登记obs_service_info; - 创建期:前端调用
obs_service_create得到obs_service_t,create回调生成实现数据,服务进入全局服务列表; - 绑定期:服务被分配给输出(
obs_service_activate触发activate回调,此时若尚未绑定输出会记录告警日志); - 启动期:
obs_service_initialize在编码器与输出初始化之前调用initialize回调,返回false可阻止输出启动;前端随后按需调用obs_service_apply_encoder_settings与obs_service_get_preferred_output_type、obs_service_get_connect_info等; - 结束期:
obs_service_deactivate触发deactivate;若对象已被标记销毁则延迟执行真正的销毁,保证活跃期间对象安全。
七、小结与参考
- 编写服务的最小集合是
id+get_name+create+destroy,再按需补齐update/get_properties(界面)、get_url/get_key/get_connect_info(连接信息)、apply_encoder_settings(协议相关编码参数)与can_try_to_connect(连接就绪判断); - 29.1 新增的协议、编解码器、连接信息三类 API 是"输出按协议向服务取参数"机制的基础,奇数连接信息类型值预留给第三方协议;
- 服务对象遵循引用计数 + 弱引用模型,且销毁对活跃对象延迟执行,长生命周期持有方应优先使用弱引用并在使用前加引转换;
- 深入阅读建议路径:docs/sphinx/reference-services.rst(API 参考文档)→ libobs/obs-service.h(类型与枚举定义)→ libobs/obs-service.c(引用计数与生命周期实现)→ plugins/rtmp-services/rtmp-custom.c 与 plugins/rtmp-services/service-specific/twitch.c(两种风格的服务实现范例)。
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 StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00