OBS Studio 输出 API 详解:从 obs_output_info 到数据捕获的完整机制
本文围绕 libobs 的 Output API 参考文档展开,系统讲解 obs_output_t 输出对象的定义结构(obs_output_info)、输出信号体系、通用控制函数以及输出实现内部使用的数据捕获接口,并结合仓库中 obs-output.h、obs-output.c 和 obs-outputs 插件 的真实实现,帮助开发者从零实现并正确驱动一个 OBS 输出。
1. 输出对象与对象模型
输出(Output)是 libobs 中负责把当前正在渲染的音频/视频数据送往外部目的地的对象。直播推流和录像是两种最常见的输出类型,但这并非全部——输出既可以接收原始(raw)音视频数据,也可以接收已编码(encoded)的数据。实现输出所依赖的专用头文件是 libobs/obs-output.h,核心头文件入口为 obs.h:
#include <obs.h>
输出对象是引用计数对象,libobs 提供两种引用形态:
obs_output_t:强引用,引用计数为 0 时对象销毁;obs_weak_output_t:弱引用,用于在回调、异步线程中避免悬空指针。
配套的弱引用管理函数包括:
| 函数 | 作用 |
|---|---|
obs_output_get_ref(output) |
若对象仍有效则返回加一后的强引用,否则返回 NULL |
obs_output_release(output) |
释放一个强引用,最后一个引用释放后销毁对象 |
obs_output_get_weak_output(output) / obs_weak_output_get_output(weak) |
强引用与弱引用互相转换;对象已销毁时后者返回 NULL |
obs_weak_output_addref(weak) / obs_weak_output_release(weak) |
增加/释放弱引用 |
obs_weak_output_references_output(weak, output) |
判断弱引用是否指向指定输出 |
这种强/弱双引用模式是 libobs 中所有长生命周期对象(source、encoder、service、output)的统一做法,跨线程持有输出时必须先转弱引用。
2. 输出定义结构 obs_output_info
obs_output_info 是注册输出类型时填写的定义结构,完整字段与 obs-output.h 中的 struct obs_output_info 一一对应。
2.1 能力标志 flags(必填)
flags 是位或组合的能力标志(文档作者注:此名应叫 capability_flags),由 obs-output.h 定义:
#define OBS_OUTPUT_VIDEO (1 << 0)
#define OBS_OUTPUT_AUDIO (1 << 1)
#define OBS_OUTPUT_AV (OBS_OUTPUT_VIDEO | OBS_OUTPUT_AUDIO)
#define OBS_OUTPUT_ENCODED (1 << 2)
#define OBS_OUTPUT_SERVICE (1 << 3)
#define OBS_OUTPUT_MULTI_TRACK (1 << 4)
#define OBS_OUTPUT_CAN_PAUSE (1 << 5)
各标志的语义与约束:
- OBS_OUTPUT_VIDEO / OBS_OUTPUT_AUDIO / OBS_OUTPUT_AV:声明输出能接收视频、音频或两者(AV 为前两者组合)。
- OBS_OUTPUT_ENCODED:输出接收已编码数据。设置该标志后,输出必须通过
obs_output_set_video_encoder()和/或obs_output_set_audio_encoder()绑定编码器才能启动。 - OBS_OUTPUT_SERVICE:输出需要一个服务对象。设置后必须通过
obs_output_set_service()绑定 service 才能启动,通常用于向特定平台推流的直播输出。 - OBS_OUTPUT_MULTI_TRACK:支持多音轨,即同时输出多路编码音频轨。
- OBS_OUTPUT_CAN_PAUSE:支持暂停。暂停时原始或编码的音视频数据会在最接近的视频帧处精确停止,音频数据会按该视频帧时间点被精确截断。
2.2 回调函数族
必填回调:
| 回调 | 说明 |
|---|---|
get_name(type_data) |
返回输出类型的本地化显示名 |
create(settings, output) |
创建输出的实现数据(implementation data),返回实现指针 |
destroy(data) |
销毁实现数据 |
start(data) |
启动输出。允许先起线程、立即返回 true,之后通过信号通知失败 |
stop(data, ts) |
请求在指定时间 ts 停止。实际停止发生在 obs_output_end_data_capture() 或 obs_output_signal_stop() 被调用时;ts 为 0 表示立即停止 |
数据回调(按输出类型二选一):
raw_video(data, frame)/raw_audio(data, frames):接收原始视频/音频帧,仅用于非编码输出。注意raw_audio必须用于单轨原始输出;raw_audio2(data, idx, frames):多轨原始输出的音频回调,idx为音轨索引;encoded_packet(data, packet):接收编码后的视频/音频包,仅用于编码输出。包总是按单调时间戳顺序送达;若packet为NULL,说明发生了编码器错误,输出应调用obs_output_signal_stop()并传入错误码OBS_OUTPUT_ENCODE_ERROR。
可选回调与成员:
| 成员 | 说明 |
|---|---|
update(data, settings) |
运行时更新设置 |
get_defaults(settings) / get_defaults2(type_data, settings) |
通过 obs_data_set_default* 系列函数填充默认设置 |
get_properties(data) / get_properties2(data, type_data) |
返回属性表,供前端自动生成设置 UI |
unused1 |
已弃用的占位回调 |
get_total_bytes(data) |
返回自启动以来处理的总字节数 |
get_dropped_frames(data) |
返回因网络拥塞丢掉的帧数 |
type_data / free_type_data(type_data) |
私有类型数据。它与实现数据不同,用于在多个输出类型复用同一组回调时加以区分 |
get_congestion(data) |
返回当前拥塞度(0.0f~1.0f),用于可视化推流输出的积压情况 |
get_connect_time_ms(data) |
返回连接服务器所耗毫秒数 |
encoded_video_codecs / encoded_audio_codecs |
以分号分隔声明支持的编码器;设置 OBS_OUTPUT_SERVICE 时必填,否则建议填写 |
protocols |
以分号分隔声明支持的协议;仅在设置 OBS_OUTPUT_SERVICE 时必填(29.1 起) |
仓库中 RTMP 输出的真实定义展示了这些字段的典型填法(rtmp-stream.c):
struct obs_output_info rtmp_output_info = {
.id = "rtmp_output",
.flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_SERVICE | OBS_OUTPUT_MULTI_TRACK_AV,
.protocols = "RTMP;RTMPS",
.encoded_video_codecs = "h264;hevc;av1",
.encoded_audio_codecs = "aac",
.get_name = rtmp_stream_getname,
.create = rtmp_stream_create,
...
.encoded_packet = rtmp_stream_data,
.get_congestion = rtmp_stream_congestion,
.get_connect_time_ms = rtmp_stream_connect_time,
};
而录像输出 mp4_output 则演示了另一组组合:无 service、支持暂停(mp4-output.c):
struct obs_output_info mp4_output_info = {
.id = "mp4_output",
.flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_MULTI_TRACK_AV | OBS_OUTPUT_CAN_PAUSE,
.encoded_video_codecs = "h264;hevc;av1",
.encoded_audio_codecs = "aac;alac;flac;opus",
...
};
3. 输出信号(Signals)
每个输出对象自带信号处理器,用于通知前端输出的生命周期状态。可用 obs_output_get_signal_handler() 获取(其生命周期由 libobs 管理,不应手动释放)。
信号清单:
| 信号 | 触发时机 |
|---|---|
starting (ptr output) |
输出正在启动 |
start (ptr output) |
输出已启动 |
activate (ptr output) |
输出激活,开始捕获数据 |
deactivate (ptr output) |
输出去激活,停止捕获数据 |
stopping (ptr output) |
输出正在停止 |
stop (ptr output, int code) |
输出已停止,code 为停止原因码 |
pause / unpause (ptr output) |
输出暂停 / 取消暂停 |
reconnect (ptr output) |
输出正在重连 |
reconnect_success (ptr output) |
重连成功 |
stop 信号的 code 参数取值在 obs-defs.h 中有明确定义:
#define OBS_OUTPUT_SUCCESS 0 /* 成功停止 */
#define OBS_OUTPUT_BAD_PATH -1 /* 路径无效 */
#define OBS_OUTPUT_CONNECT_FAILED -2 /* 连接服务器失败 */
#define OBS_OUTPUT_INVALID_STREAM -3 /* 流地址无效 */
#define OBS_OUTPUT_ERROR -4 /* 一般错误 */
#define OBS_OUTPUT_DISCONNECTED -5 /* 意外断开 */
#define OBS_OUTPUT_UNSUPPORTED -6 /* 设置/格式/编码器不受支持 */
#define OBS_OUTPUT_NO_SPACE -7 /* 磁盘空间不足 */
#define OBS_OUTPUT_ENCODE_ERROR -8 /* 编码器错误 */
从源码结构看,输出内部还维护 active、reconnecting、stopping、delay_active 等原子状态位(见 obs-output.c),配合上述信号构成完整的状态机。
4. 注册与创建
4.1 注册
void obs_register_output(struct obs_output_info *info);
通常在 obs_module_load() 或程序初始化阶段调用。仓库中的 obs-outputs 插件 即是在模块加载时集中注册 rtmp_output、flv_output、mp4_output、null_output 等类型。注意 obs-output.h 中实际导出的是带结构体尺寸参数的 obs_register_output_s(info, size),obs_register_output 只是自动传入 sizeof(struct obs_output_info) 的宏,这一设计允许库在不破坏 ABI 的前提下扩展结构体字段(如 raw_audio2、protocols)。
4.2 创建与查询
obs_output_t *obs_output_create(const char *id, const char *name,
obs_data_t *settings, obs_data_t *hotkey_data);
id:输出类型字符串标识;name:期望的名称,若非唯一会被自动改名为唯一值;settings:初始化设置,可为NULL;hotkey_data:已保存的快捷键数据,可为NULL。
创建成功后必须用 obs_output_release() 配对释放。其他查询函数:
obs_output_get_display_name(id):调用get_name回调返回本地化显示名;obs_output_get_name(output)/obs_output_get_id(output):获取实例名与类型标识;obs_output_defaults(id):返回默认设置(需obs_data_release()释放);obs_output_properties(output)/obs_get_output_properties(id):返回属性表(用obs_properties_destroy()释放),前端可用它自动生成设置界面控件;obs_output_update(output, settings):更新实例设置。
5. 生命周期控制:启动、停止、暂停
5.1 启动与停止
bool obs_output_start(obs_output_t *output);
void obs_output_stop(obs_output_t *output);
void obs_output_force_stop(obs_output_t *output);
bool obs_output_active(const obs_output_t *output);
obs_output_start返回true表示启动成功;失败时可通过obs_output_get_last_error()获取具体错误字符串。由于start回调允许异步失败(先返回true再用信号报错),启动结果最终体现在start/stop信号上;obs_output_stop是优雅停止:输出会先把调用时刻之前的数据全部发送完毕,停止成功后才发出stop信号;obs_output_force_stop则跳过等待,尝试立即停止。
5.2 暂停
bool obs_output_can_pause(const obs_output_t *output);
bool obs_output_pause(obs_output_t *output, bool pause);
bool obs_output_paused(const obs_output_t *output);
仅当定义时声明 OBS_OUTPUT_CAN_PAUSE 时才支持。仓库中的 mp4_output、mov_output 都声明了该标志(mp4-output.c),这正是 OBS 录像界面支持"暂停录制"的底层能力来源。
5.3 延迟(Delay)
void obs_output_set_delay(obs_output_t *output, uint32_t delay_sec, uint32_t flags);
uint32_t obs_output_get_delay(const obs_output_t *output);
uint32_t obs_output_get_active_delay(const obs_output_t *output);
set_delay 设置秒级延迟;若延迟已在生效中,新值只影响下一次激活。flags 可取 0 或 OBS_OUTPUT_DELAY_PRESERVE——重连时从断点继续,但代价是等待重连期间延迟缓冲区持续增长、消耗额外内存。get_active_delay 返回当前实际生效的延迟值(开启 PRESERVE 后该值可能增长)。
5.4 重连
void obs_output_set_reconnect_settings(obs_output_t *output,
int retry_count, int retry_sec);
bool obs_output_reconnecting(const obs_output_t *output);
retry_count 为 0 表示禁用重连;重试间隔每次翻倍以避免压垮服务端。从源码看,libobs 对重连等待还设有上限与增长因子:RECONNECT_RETRY_MAX_MSEC(15 分钟)与 RECONNECT_RETRY_BASE_EXP(1.5f),见 obs-output.c——文档描述的"翻倍"是对外语义,内部实现采用 1.5 倍指数增长并以 15 分钟封顶。
31.1 起还提供 obs_output_set_reconnect_callback(),回调在每次重连决策前被调用,可用于动态更新过期的推流密钥,返回 false 可放弃重连;传入 NULL 即移除回调。
6. 数据路由:媒体、混音器、编码器与 Service
6.1 原始输出的媒体绑定
void obs_output_set_media(obs_output_t *output, video_t *video, audio_t *audio);
video_t *obs_output_video(const obs_output_t *output);
audio_t *obs_output_audio(const obs_output_t *output);
set_media 通常为输出绑定 obs_get_video() / obs_get_audio(),使原始输出能挂接到原始视频/音频帧回调上。编码输出不需要此步,其数据来自编码器。
6.2 音频混音器选择
void obs_output_set_mixer(obs_output_t *output, size_t mixer_idx);
size_t obs_output_get_mixer(const obs_output_t *output);
void obs_output_set_mixers(obs_output_t *output, size_t mixers);
size_t obs_output_get_mixers(const obs_output_t *output);
这两个接口仅对非编码输出有意义,用于选择参与该输出的音频混音轨。set_mixer 等价于"掩码中只保留指定索引";set_mixers 使用位掩码,单轨输出会取掩码中第一个置位的轨道(若掩码为空则取第一轨)。
6.3 编码器与 Service 绑定
void obs_output_set_video_encoder(obs_output_t *output, obs_encoder_t *encoder);
void obs_output_set_audio_encoder(obs_output_t *output, obs_encoder_t *encoder, size_t idx);
obs_encoder_t *obs_output_get_video_encoder(const obs_output_t *output);
obs_encoder_t *obs_output_get_audio_encoder(const obs_output_t *output, size_t idx);
void obs_output_set_service(obs_output_t *output, obs_service_t *service);
obs_service_t *obs_output_get_service(const obs_output_t *output);
- 音频编码器按
idx索引,支持多音频流场景; - getter 返回的引用不加引用计数,不要自行释放;
- service 绑定针对 RTMP 这类需要推送目标的服务型输出。
obs-output.h 中还定义了两个上限常量:MAX_OUTPUT_AUDIO_ENCODERS 6、MAX_OUTPUT_VIDEO_ENCODERS 10,即多轨场景下可同时挂载的音频/视频编码器数量上限。
6.4 缩放、码流统计与拥塞
void obs_output_set_preferred_size(obs_output_t *output, uint32_t width, uint32_t height);
uint32_t obs_output_get_width(const obs_output_t *output);
uint32_t obs_output_get_height(const obs_output_t *output);
uint64_t obs_output_get_total_bytes(const obs_output_t *output);
int obs_output_get_frames_dropped(const obs_output_t *output);
int obs_output_get_total_frames(const obs_output_t *output);
float obs_output_get_congestion(const obs_output_t *output);
int obs_output_get_connect_time_ms(const obs_output_t *output);
set_preferred_size 设置首选缩放分辨率(宽高均为 0 表示禁用缩放):若输出使用编码器,启动前会调用 obs_encoder_set_scaled_size;若编码器已激活则仅告警并不生效。get_congestion 返回 0.0f(无拥塞)到 1.0f(完全拥塞)的积压度量,是推流状态栏的核心数据来源。
6.5 字幕
void obs_output_output_caption_text1(obs_output_t *output, const char *text);
void obs_output_output_caption_text2(obs_output_t *output, const char *text,
double display_duration);
向输出注入字幕文本。text1 等价于 display_duration 固定为 2.0 秒的 text2;display_duration 是该条字幕进入队列后的最短显示时长。libobs 内部依赖 deps/libcaption 完成字幕封装。
7. 协议与编码器能力查询(29.1 起)
const char *obs_output_get_supported_video_codecs(const obs_output_t *output);
const char *obs_get_output_supported_video_codecs(const char *id);
const char *obs_output_get_supported_audio_codecs(const obs_output_t *output);
const char *obs_get_output_supported_audio_codecs(const char *id);
const char *obs_output_get_protocols(const obs_output_t *output);
bool obs_is_output_protocol_registered(const char *protocol);
bool obs_enum_output_protocols(size_t idx, char **protocol);
void obs_enum_output_types_with_protocol(const char *protocol, void *data,
bool (*enum_cb)(void *data, const char *id));
uint32_t obs_output_get_flags(const obs_output_t *output);
uint32_t obs_get_output_flags(const char *id);
- 前四个函数返回分号分隔的支持编码器列表(实例级与类型级两种入口);
get_protocols返回分号分隔的协议列表,非OBS_OUTPUT_SERVICE输出恒为NULL;- 协议注册表 API 允许前端按协议(如 RTMPS)反查可用的输出类型:
obs_is_output_protocol_registered判断、obs_enum_output_protocols枚举全部协议、obs_enum_output_types_with_protocol通过回调枚举指定协议下的所有输出类型 id,回调返回false可提前终止枚举。
这套能力让上层程序(如脚本插件)可以在运行时决定"能否以 RTMPS 推流",而无需硬编码输出类型 id。
8. 输出内部使用的函数(实现者视角)
以下函数是输出实现内部调用的工具函数,用于把 libobs 的原始媒体流接入自己的管线。
8.1 错误上报
void obs_output_set_last_error(obs_output_t *output, const char *message);
const char *obs_output_get_last_error(obs_output_t *output);
设置/获取展示给用户的本地化错误信息,常用于连接失败、无法连接等场景,可配合 obs_output_signal_stop() 的错误码提供更细粒度的诊断信息。
8.2 启动前的三步握手
编码输出的 start 回调内部应按顺序调用:
bool obs_output_can_begin_data_capture(const obs_output_t *output, int flags);
bool obs_output_initialize_encoders(obs_output_t *output, int flags);
bool obs_output_begin_data_capture(const obs_output_t *output, int flags);
void obs_output_end_data_capture(obs_output_t *output);
void obs_output_signal_stop(obs_output_t *output, int code);
can_begin_data_capture:在任何输出状态初始化之前先检查能否开始捕获,flags为保留位,传 0;initialize_encoders:编码输出必须在begin_data_capture之前调用,用于初始化关联的编码器/service;begin_data_capture:真正开始从原始媒体或编码器接收数据,输出在内部"激活",随后视频/音频数据开始送达回调;end_data_capture:结束捕获,数据停止送达,输出以OBS_OUTPUT_SUCCESS码触发stop信号;signal_stop:异常停止入口——服务器掉线、写文件出错等场景下,用它带着错误码终止捕获并触发带码的stop信号。
8.3 原始输出的音视频转换
void obs_output_set_video_conversion(obs_output_t *output,
const struct video_scale_info *conversion);
const struct video_scale_info *obs_output_get_video_conversion(obs_output_t *output);
void obs_output_set_audio_conversion(obs_output_t *output,
const struct audio_convert_info *conversion);
仅原始输出使用,用于声明自己期望的输入格式,libobs 会代为完成格式转换。相关数据类型的核心定义:
struct video_scale_info {
enum video_format format; /* 如 VIDEO_FORMAT_I420、NV12、YUY2、RGBA 等 */
uint32_t width;
uint32_t height;
enum video_range_type range; /* PARTIAL / FULL */
enum video_colorspace colorspace; /* 601 / 709 / 2100_PQ / 2100_HLG 等 */
};
struct audio_convert_info {
uint32_t samples_per_sec;
enum audio_format format; /* 16BIT、FLOAT 及各自 PLANAR 变体 */
enum speaker_layout speakers; /* MONO、STEREO、5POINT1、7POINT1 等 */
};
video_format 枚举覆盖 4:2:0/4:2:2/4:4:4 的平面与打包格式、灰度、10/12/16 bit 高位深格式(I010、P010、I210、P216、P416、V210 等),色彩空间包含 BT.601、BT.709、BT.2100 PQ/HLG。
8.4 暂停偏移
uint64_t obs_output_get_pause_offset(obs_output_t *output);
返回当前暂停偏移量。原始输出在使用"计算时间戳"(calculated timestamps)计算系统时间戳时需要减去该偏移——文档以 FFmpeg 输出为例说明:若支持暂停的录像中途暂停过,写出的时间轴需要补偿暂停期间的时间空洞。
8.5 包级回调(31.0 起)
void obs_output_add_packet_callback(obs_output_t *output,
void (*packet_cb)(obs_output_t *output, struct encoder_packet *pkt,
struct encoder_packet_time *pkt_time, void *param),
void *param);
void obs_output_remove_packet_callback(obs_output_t *output, void (*packet_cb)(...), void *param);
add_packet_callback 在每个压缩包发送至 service 之前触发回调,是 libobs 官方推荐的所有"包级处理"(如水印注入、码流改写)扩展方式——这些逻辑不必内置于 libobs 核心。使用约束:
- 若需重新分配包缓冲区,必须使用
libobs/util/bmem.h中的函数,否则内存泄漏; - 严禁对包缓冲区调用
memset()清零,因为缓冲区数据要供后续回调继续处理。
9. 小结:从注册到收包的最小闭环
综合文档与仓库实现,一个编码输出的标准生命周期为:
- 填写
obs_output_info(flags、回调、编码器/协议声明),在模块加载时obs_register_output; - 前端
obs_output_create创建实例,经set_video_encoder/set_audio_encoder/set_service绑定依赖; obs_output_start→start回调中依次can_begin_data_capture→initialize_encoders→begin_data_capture,发出activate;- 编码包按单调时间戳顺序经
encoded_packet回调送达(31.0 起还可叠加add_packet_callback); obs_output_stop或异常时signal_stop→deactivate→ 带错误码的stop信号。
仓库中的 obs-outputs 插件 提供了 rtmp、flv、mp4、mov、null 等完整参照实现,配合 obs-outputs.c 的注册入口与 rtmp-stream.c、mp4-output.c 的定义结构,是阅读 Output API 文档时最值得对照的源码样本。
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