首页
/ OBS Studio 服务 API 详解:obs_service_t 对象模型、obs_service_info 结构与 rtmp_custom 实战实现

OBS Studio 服务 API 详解:obs_service_t 对象模型、obs_service_info 结构与 rtmp_custom 实战实现

2026-09-04 11:09:16作者:董宙帆

本文围绕 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 目录下实现了 twitchamazon-ivsdacastnimotvshowroom 等平台服务,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_datatype_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.crtmp_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 可为 NULLhotkey_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.cobs_service_release 的实现细节值得注意:释放强引用前先取 context.control 对应的弱引用做 obs_ref_release,归零时按"先 obs_service_destroy、再 obs_weak_service_release"的固定顺序执行,注释指出这是为了保证 obs.cget_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 回调未设置时返回 truelibobs/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(开关)、usernamepassword 五个属性,并通过修改回调实现联动:勾选"使用认证"时才显示用户名/密码框:

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)。

六、服务与输出的协作流程

把文档与源码串起来,一次推流启动中服务参与的时序大致如下:

  1. 注册期:模块加载时 obs_register_service 登记 obs_service_info
  2. 创建期:前端调用 obs_service_create 得到 obs_service_tcreate 回调生成实现数据,服务进入全局服务列表;
  3. 绑定期:服务被分配给输出(obs_service_activate 触发 activate 回调,此时若尚未绑定输出会记录告警日志);
  4. 启动期obs_service_initialize 在编码器与输出初始化之前调用 initialize 回调,返回 false 可阻止输出启动;前端随后按需调用 obs_service_apply_encoder_settingsobs_service_get_preferred_output_typeobs_service_get_connect_info 等;
  5. 结束期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.cplugins/rtmp-services/service-specific/twitch.c(两种风格的服务实现范例)。
登录后查看全文
热门项目推荐
相关项目推荐