第 25 课:直播平台是怎么接入的 —— obs_service
# 第 25 课:直播平台是怎么接入的 —— obs_service
嘿,我是小方。 上一课结尾,
rtmp_output从一个obs_service拿到了"服务器地址 + 推流码"。这一课就讲这个obs_service—— 它怎么替你"记住"成百上千个直播平台的服务器、码率上限、编码限制,甚至一键登录自动填推流码。 这一课有一个特别漂亮的设计思想,我会重点讲:平台信息是"数据"不是"代码" —— OBS 支持约 84 个平台,却不是写了 84 份代码,而是一份代码 + 一个 JSON 文件。理解了这一点,你会对"怎么优雅地支持超多平台"有新认识。这也是模块 6(打包与送出)的收官课,末尾我会把这一路的抽象串起来复盘。
# 25.1 先看现象:选个"Twitch",怎么就全知道了?
📍我们在哪:上一课
rtmp_output说"从obs_service拿地址 + 推流码"。这一课就来揭开这个obs_service—— 先看它带来的"魔法"现象。

你在 OBS"设置 → 直播"里,**服务(平台)**下拉选"Twitch",然后神奇的事发生了:
- 服务器下拉里自动出现一堆可选(香港、美西……),地址
rtmp://live-hkg…你根本不用查; - 界面提示该平台建议码率 ≤ 6000、关键帧 2 秒、只支持 H.264;
- 换个平台(比如 B 站),这些又全变;
- 有的平台甚至能**"连接账号"一键登录**,推流码自动填好。
它凭什么什么都知道? 服务器地址、码率上限、编码限制,它怎么就门儿清?换平台又怎么全跟着变?
答案就是这一课的主角 obs_service —— 你在下拉里选的那个平台,背后是一个 obs_service 对象,它替你记住了这个平台的一切。(选"自定义"则相反:服务器、推流码全靠你自己填,也没有任何建议 —— 25.5 讲。)
而它这份"记性"的秘密,藏在一个 JSON 文件里。
# 25.2 关键洞察:平台信息是"数据",不是"代码"
📍这一课最重要的一句话:先别急着看代码。先理解这个设计思想,后面一切都顺了。

OBS 支持约 84 个直播平台。假如每个平台写一份代码(Twitch.c、YouTube.c、B站.c……),会怎样?
- 每加一个平台、每改一次服务器地址,都要改代码、重新编译、发新版 OBS;
- 平台一多就爆炸,根本维护不过来。行不通。
OBS 的做法:一份代码 + 一份数据。
- 一份代码 =
rtmp_common(一个obs_service类型)。它只会一件事:读 JSON、照着回答。 - 一份数据 =
services.json。里面是 84 个平台的清单:每个平台的服务器地址、码率上限、编码要求……全是数据。 - 加平台 / 改地址 = 改这个 JSON 文件,不用改代码、不用重新编译(甚至能联网自动更新,25.7)。
这就是"数据驱动(data-driven)":把"会变的东西(平台清单)"抽成数据,"不变的逻辑(读清单)"才是代码。 这是软件设计里非常常见、非常有用的一招 —— 记住它,你以后设计"要支持很多种类似东西"的系统时,都该想想能不能这么干。
下面先看这份"数据"长什么样。
# 25.3 services.json 长什么样(一个平台 = 一段数据)
📍接上一节:既然平台是数据,那就直接看数据。约 84 个平台、3594 行,全是同一种条目 —— 看懂一条,就看懂了全部。

以 Twitch 那一条为例(plugins/rtmp-services/data/services.json:5):
{
"name": "Twitch", // 平台名(下拉里显示的)
"servers": [ // 服务器列表(多个区域)
{ "name": "Asia: Hong Kong",
"url": "rtmp://live-hkg.twitch.tv/app" }, // ← 地址在这
{ "name": "US West", "url": "..." }
],
"recommended": { // 推荐 / 限制
"keyint": 2, // 关键帧 2 秒
"max video bitrate": 6000, // 码率上限
"max audio bitrate": 320
},
"supported video codecs": ["h264"] // 只支持 H.264
}
2
3
4
5
6
7
8
9
10
11
12
13
14
每个字段,回答了 25.1 那些"它怎么知道"的问题:
servers→ 服务器下拉框 + 地址(第 24 课那个rtmp://地址,就来自这里的servers[].url);max video bitrate→ 码率上限(超了就警告/夹住);keyint→ 强制关键帧间隔;supported video codecs→ 允许的编码(x264 输出的得是这些之一)。
整个文件开头有个 "format_version": 5(:3),84 个平台条目全长这样。平台的一切知识,就是这么一段段 JSON。
现在看"读这份数据"的那份代码 —— obs_service。
# 25.4 obs_service:又见那个熟悉的"vtable + 回调"
📍进代码了:读 JSON、回答问题的,是一个
obs_service。而它的结构,你已经见过四次了。

// libobs/obs-service.h:47(节选)
struct obs_service_info {
const char *id; // "rtmp_common" / "rtmp_custom"
void *(*create)(obs_data_t *settings, obs_service_t *service);
void (*destroy)(void *data);
...
const char *(*get_connect_info)(void *data, uint32_t type); // ★ 给地址 / 推流码(第 24 课)
void (*get_max_bitrate)(void *data, int *video_bitrate, int *audio_bitrate); // 码率上限
const char **(*get_supported_video_codecs)(void *data); // 支持哪些编码
void (*get_max_fps)(void *data, int *fps); // 帧率上限
void (*apply_encoder_settings)(void *data, obs_data_t *v, obs_data_t *a); // ★ 把限制写进编码器
bool (*can_try_to_connect)(void *data); // 现在能连吗(校验)
};
2
3
4
5
6
7
8
9
10
11
12
13
又是一张登记表 + 一套回调,套路和前面完全一样(找 id → 拷 vtable → info.create 建实例 → 引擎照表调;obs-service.c:75/:91/:92)。只是它的回调特别"懂行"——全是"这个平台的知识":地址、码率上限、支持的编码、要不要强制某些参数……
🗒️ 一点如实说明(避免你被我早先的说法误导):用
obs_register_*登记的其实是 4 类:源(第 6 课)、输出(第 22 课)、编码器(第 19 课)、服务(本课),它们分别在obs-module.c的:956 / :1061 / :1126 / :1168注册。图形后端(第 11 课)用的是"同一个思路",但走的是os_dlopen动态加载,不算obs_register登记表。 所以严格说"登记表"有 4 张;但"vtable + 回调"这个套路,你已经见过五处了(源/图形后端/编码器/输出/服务)—— 这才是贯穿全课的那把万能钥匙。
代码里的 obs_service 类型,其实只有两种。
# 25.5 两条路:选平台(rtmp_common)vs 自定义(rtmp_custom)
📍接上一节:登记表要有人来填。填它的,代码里只有两个 —— 对应你在"服务"下拉里的两种选择。

rtmp-services 插件在 obs_module_load 里,只注册了两个 obs_service 类型(rtmp-services-main.c:125):
// plugins/rtmp-services/rtmp-services-main.c:125
obs_register_service(&rtmp_common_service); // 选平台(读 JSON)
obs_register_service(&rtmp_custom_service); // 自定义(自己填)
2
3
- 选平台 →
rtmp_common(rtmp-common.c:1160):你从下拉里选"Twitch / B 站 / …",它去services.json里按名字找到这个平台(find_service,:607),读出服务器地址、码率上限、编码要求……地址自动填、码率超了会提醒/夹住、编码限制自动应用,省心、不易配错。覆盖约 84 个主流平台。 - 自定义 →
rtmp_custom(rtmp-custom.c:179):你自己填一个服务器地址 + 推流码,它只是原样存下来(rtmp-custom.c:25)——不读 JSON、没有任何码率建议 / 编码限制。用在:JSON 里没有的平台、自建服务器(nginx-rtmp)、SRT/RIST 等高级场景。"你是高手,自己负责"的路。
两者都是 obs_service,填的是同一张登记表,只是 .id 不同、回调实现不同。 而对 rtmp_output 来说毫无区别:它照样调 obs_service_get_connect_info 拿"地址 + 推流码"(第 24 课)—— 这就是抽象的好处,上层不关心你选的是哪种服务。
那 rtmp_common 从 JSON 读到的信息,具体怎么"送"到各个地方?
# 25.6 平台的"知识",怎么送到该去的地方
📍接上一节:一个
obs_service实例读了 JSON,它读到的东西不止给输出用,而是同时喂给四处。

一个 obs_service 实例(你选的那个平台),同时给四个地方供货:
- ① 给输出(
rtmp_output,第 24 课):get_connect_info→ 地址 + 推流码(obs-service.c:448转调info.get_connect_info;rtmp_common里从 JSON 的servers[].url取,rtmp-common.c:824)。 - ② 给前端界面:
get_max_bitrate(:402,读 JSON 的max video bitrate,rtmp-common.c:958)、get_supported_video_codecs(:416,读supported video codecs,rtmp-common.c:1019)→ 界面据此警告"码率太高"、限制编码下拉。 - ③ 给编码器设置:
apply_encoder_settings(:283)→rtmp_common_apply_settings(rtmp-common.c:813)强制 keyint、强制 CBR、把码率夹到上限以内(apply_video_encoder_settings,:721)。这就是"选了 Twitch,你的编码器参数自动被规整"的真相。 - ④ 给开播前校验:
can_try_to_connect(:458)+initialize(:271)→ 在obs_output_start(obs-output.c:396)真正连接之前校验(:403):不满足就不让开播。
所以"选一个平台"= 让一个 obs_service 实例,同时给输出"地址"、给界面"限制"、给编码器"参数"、给开播"校验"。 libobs 里全是一行行 obs_service_get_xxx 包装函数,内部就转调 info.get_xxx 那个回调(obs-service.c)——又是我们熟悉的"引擎照表调"。
# 25.7 services.json 从哪来?会自动更新吗?
📍补一个实际问题:既然平台是数据,那这份数据从哪来、会不会过时?这正是"数据驱动"最大的好处所在。

有两份 services.json,一份保底、一份保鲜:
- ① 自带版(bundled):OBS 安装包里就带一份
services.json(obs_module_file("services.json"))。保证:断网也能用、刚装好就有平台列表。 - ② 缓存版(自动更新):联网时,OBS 后台下载更新的
services.json存到缓存(obs_module_config_path(...);更新逻辑rtmp-services-main.c:107,会校验format_version=5和版本号)。 - ③ 用哪个?:优先用"缓存版"(更新),没有 / 校验失败,才回退到"自带版"(
open_services_file,rtmp-common.c:379)。
为什么要搞这么一套? 直播平台经常变:换服务器地址、调整码率上限、新平台上线、老平台下线……如果这些写在代码里,你就得等 OBS 发新版才能用 —— 太慢。做成可自动更新的数据文件:平台变了,OBS 后台悄悄拉一份新 JSON,你无感就用上了。
这正是"数据驱动(25.2)"的最大好处:会变的部分能独立更新,不牵动程序本身。
# 25.8 "一键登录":OAuth 帮你把推流码填好
📍补最后一块:25.1 提到有的平台能"一键登录"。这是前端的一层便利,顺带讲清,你就把 25.1 的现象全解释完了。

有些平台(Twitch / YouTube / Restream)支持**"连接账号"**,不用你手动去平台后台复制推流码:
- 点"连接账号"(OBS 直播设置里);
- 弹出浏览器,去平台登录 + 授权(OAuth 授权页);
- 平台发回令牌,OBS 拿到一个临时凭证 / 推流码;
- 自动填进 service:
obs_service_update把它写进 service 的"key"设置。
这段是**前端(Qt)**的事:基类 Auth / OAuthStreamKey(frontend/oauth/),子类 TwitchAuth / YoutubeAuth / RestreamAuth;OnStreamConfig() 把令牌 obs_data_set_string(settings, "key", …) 后 obs_service_update(OAuth.cpp:196)。
闭环:OAuth 把推流码写进 service 的 "key" 设置 → 开播时,rtmp_output 照样用 obs_service_get_connect_info(…, STREAM_KEY) 把它读出来(第 24 课)。所以"一键登录"只是"帮你填推流码"的前端便利,底层推流那套一点没变。
到这,25.1 那些"魔法"就全解释清楚了:地址来自 JSON、限制来自 JSON、一键登录来自 OAuth,而串起这一切的,是 obs_service。
# 25.9 模块 6 收官:打包与送出,全讲完了

从"两条编码流"到"文件 / 直播间",模块 6 这一路的抽象都认全了:
- 第 22 课:容器(盒子)+
obs_output(统一输出接口); - 第 23 课:录制到文件(libavformat 三部曲);
- 第 24 课:推流 RTMP(握手 + 丢帧保命);
- 第 25 课:直播平台接入(
obs_service+ JSON 数据驱动)。
而站得更高看,这门课贯穿始终的一个模式 —— "vtable(登记表)+ 回调" —— 你已经见过五次了:
| 抽象 | 课 | API | 管什么 |
|---|---|---|---|
| 源 | 第 6 课 | obs_source | 一切输入/滤镜/转场 |
| 图形后端 | 第 11 课 | gs_exports | D3D11 / OpenGL / Metal |
| 编码器 | 第 19 课 | obs_encoder | x264 / NVENC |
| 输出 | 第 22 课 | obs_output | 录制 / 推流 |
| 服务 | 本课 | obs_service | 直播平台 |
带走一句话:认识了这个"登记表 + 回调"模式,你就有了读懂 OBS 任何子系统的万能钥匙。 遇到没讲过的子系统,先找它的 xxx_info 结构体、找注册它的地方、找引擎在哪调这些回调 —— 十有八九就是这套。
音视频的"原理 + OBS 实现"这条主线,到这里就打通了。 下一模块(模块 7)换个视角:从"界面"回看全局,再完整追踪一帧画面的一生,把整条流水线串成一个故事。
# 25.10 本课小结
- 现象:选个平台 → 服务器地址自动填、码率/关键帧/编码限制自动提示、甚至一键登录。背后是一个
obs_service对象替你"记住"了这个平台。 - 关键洞察(数据驱动):约 84 个平台不是 84 份代码,而是一份代码(
rtmp_common)+ 一份数据(services.json)。加平台/改地址 = 改 JSON,不用改代码。"会变的抽成数据,不变的逻辑才是代码。" - services.json:每个平台一段 JSON ——
name+servers[](name+url)+recommended{keyint, max video bitrate, max audio bitrate}+supported video codecs。format_version: 5,约 84 个平台。第 24 课的服务器地址就来自servers[].url。 obs_service= 熟悉的 vtable + 回调(obs-service.h:47):id/create/destroy+ 一堆"平台知识"回调get_connect_info(地址+推流码)、get_max_bitrate、get_supported_video_codecs、apply_encoder_settings(把限制写进编码器)、can_try_to_connect(校验)。用 obs_register_ 登记的是 4 类(源/输出/编码器/服务);"vtable+回调"这个套路则出现了五处(加上图形后端,它走 os_dlopen)。*- 两个服务类型:
rtmp_common(rtmp-common.c:1160,选平台→读 JSON→自动填/限制)vsrtmp_custom(rtmp-custom.c:179,自定义→原样存 URL+key→无限制,高级/自建/SRT 场景)。都注册于rtmp-services-main.c:125。对rtmp_output无区别(照调get_connect_info)。 - 信息流向:一个
obs_service实例同时供货四处 —— 输出(get_connect_info→地址/key)、界面(get_max_bitrate/get_supported_video_codecs→警告/限制)、编码器(apply_encoder_settings→强制 keyint/CBR/夹码率,rtmp-common.c:721)、开播校验(can_try_to_connect+initialize,obs-output.c:403)。libobs 的obs_service_get_xxx包装 → 转调info.get_xxx。 - JSON 来源 + 自动更新:自带版(
obs_module_file,保底)+ 缓存版(obs_module_config_path,联网自动下载更新,校验 format_version);优先缓存、回退自带(open_services_file,rtmp-common.c:379)。好处:平台变了不用等 OBS 发新版。 - OAuth 一键登录:前端(
frontend/oauth/,TwitchAuth/YoutubeAuth/RestreamAuth)走 OAuth 授权 →OnStreamConfig把推流码写进 service 的"key"(OAuth.cpp:196)→ 开播时rtmp_output照常读出。只是"帮你填推流码"的便利。 - 模块 6 完。五处"vtable + 回调":源(6)/图形后端(11)/编码器(19)/输出(22)/服务(25)。
# 25.11 动手 / 观察(本课作业)
- 找到 services.json:在你电脑的 OBS 安装目录里找到
data/obs-plugins/rtmp-services/services.json,搜索你常用的平台(如 "Bilibili"),看它的servers和recommended。 - 理解数据驱动:用自己的话说清 —— 为什么 OBS 支持几百个平台却只有两个 service 类型的代码?"数据驱动"好在哪?
- 对比 common vs custom:在 OBS 直播设置里,分别选一个平台和"自定义",观察"服务器"和"码率提示"的区别。什么时候该用"自定义"?
- 观察码率被夹:选一个有码率上限的平台(如 Twitch 6000),把视频码率设成 20000,看 OBS 是否提示/限制(对应
apply_encoder_settings的夹取)。 - 翻源码:打开
plugins/rtmp-services/rtmp-services-main.c:125看两个obs_register_service;再到rtmp-common.c:1160看rtmp_common_service那张表;最后到rtmp-common.c:958看get_max_bitrate怎么从 JSON 读max video bitrate。
# 下一课预告
到这里,整条引擎流水线(采集 → 合成 → 编码 → 封装 → 输出)你都走完了。但有一个视角我们一直"从底往上"看,还没"从上往下"看过:用户点的那些按钮、拖的那些滑块,是怎么驱动这台引擎的? 那个你天天在用的界面,又是怎么搭起来的?
第 26 课:界面是怎么搭起来的 —— OBSApp、OBSBasic —— 我们进入模块 7:把它们串起来,从前端(C++/Qt6)的视角回看全局:程序入口 OBSApp、主窗口 OBSBasic、以及界面和 libobs 引擎之间那道桥。下节课见。
📁 本课配图:
imgs/25-01~imgs/25-09📌 源码锚点:libobs/obs-service.h:47(struct obs_service_info)、:104(get_connect_info)、:96(get_max_bitrate)、:98(get_supported_video_codecs)、:84(apply_encoder_settings)、:106(can_try_to_connect)、:37-45(OBS_SERVICE_CONNECT_INFO_* 枚举);libobs/obs-module.c:1168(obs_register_service_s;另 3 张登记表 :956 源 / :1061 输出 / :1126 编码器);libobs/obs-internal.h:1474(struct obs_service);libobs/obs-service.c:103(obs_service_create)、:91/:92(拷 vtable + info.create)、:448/:455(obs_service_get_connect_info)、:402(get_max_bitrate)、:416(get_supported_video_codecs)、:283(apply_encoder_settings)、:271(initialize)、:458(can_try_to_connect);libobs/obs-output.c:1148/:1158(set_service)、:1162(get_service)、:396/:403(start 前校验 can_try_to_connect + initialize);plugins/rtmp-services/rtmp-services-main.c:93(obs_module_load)、:125-126(注册两个 service)、:107(自动更新)、:36(confirm_service_file 校验);rtmp-common.c:1160(rtmp_common_service 表)、:379(open_services_file 缓存优先)、:332(open_json_file + format_version 校验)、:607(find_service 按名字匹配)、:824(url 来自 JSON server)、:958(get_max_bitrate 读 JSON)、:1019(get_supported_video_codecs 读 JSON)、:813/:721(apply_settings → 强制 keyint/CBR/夹码率);rtmp-custom.c:179(rtmp_custom_service)、:25(原样存 server+key);plugins/rtmp-services/data/services.json:3(format_version 5)、:5(Twitch 条目)、:198(recommended);data/package.json(更新 url + 版本); 前端 OAuth:frontend/oauth/Auth.hpp:5、OAuth.hpp:41(OAuthStreamKey)、TwitchAuth.hpp/YoutubeAuth.hpp/RestreamAuth.hpp、OAuth.cpp:196(OnStreamConfig 写入 key)。 注:"数据驱动"设计思想、OAuth 流程为通用背景知识,平台清单为 OBS 数据文件内容。
本留言区仅对应当前文章,欢迎补充观点、提出问题或帮助修正文中疏漏。 留言由 GitHub/Gitalk 提供,需要使用 GitHub 登录。
社区交流
讨论与留言