首页
/ OBS Studio libobs 编码器 API 实战解析:从 obs_encoder_info 到 ROI、参考计数与数据包流转

OBS Studio libobs 编码器 API 实战解析:从 obs_encoder_info 到 ROI、参考计数与数据包流转

2026-09-05 19:38:52作者:裘旻烁

本文以 docs/sphinx/reference-encoders.rst 这份编码器 API 参考为骨架,系统讲解 OBS 核心库 libobs 中 obs_encoder_t 的完整对象模型:编码器定义结构 obs_encoder_info、数据包 encoder_packet、原始帧 encoder_frame、ROI 兴趣区域、注册/创建流程、引用计数、缩放与格式协商、附加数据与 priming samples,以及仓库中 x264、libfdk 等真实编码器插件的实现印证。读完后,你可以理解一个编码器插件在 OBS 内部是如何被注册、创建、喂帧、出包并最终交付给输出链路的。

1. 编码器是什么:libobs 中的 obs_encoder_t

官方参考文档的开头定义:编码器是 OBS 特有的视频/音频编码器实现,用于配合"使用编码器的输出"工作,x264、NVENC、Quicksync 都是编码器实现的例子;其专用头文件是 libobs/obs-encoder.h

从源码结构看,编码器是 libobs 中与 source、output、service 并列的一类上下文对象。obs_encoder_t 是一个引用计数对象,另有 obs_weak_encoder_t 弱引用对象,两者均声明在 libobs/obs-encoder.h

#include <obs.h>

struct obs_encoder;
typedef struct obs_encoder obs_encoder_t;

头文件对编码器接口的注释写得很直白:

Encoders have a limited usage with OBS. You are not generally supposed to implement every encoder out there. Generally, these are limited or specific encoders for h264/aac for streaming and recording.(编码器通常只针对流媒体/录像场景下的 h264/aac 等特定编码器,见 libobs/obs-encoder.h 的接口注释)

编码器被分为两种类型(enum obs_encoder_type):

  • OBS_ENCODER_VIDEO — 视频编码器
  • OBS_ENCODER_AUDIO — 音频编码器

2. 编码器定义结构 obs_encoder_info:一个插件要交出的"全部家当"

struct obs_encoder_info 是编码器插件与 libobs 之间的契约,定义在 libobs/obs-encoder.h。参考文档将其成员分为"必需"与"可选"两类,下表完整覆盖文档中的全部字段:

成员 必需/可选 作用
const char *id 必需 编码器唯一字符串标识
enum obs_encoder_type type 必需 OBS_ENCODER_VIDEOOBS_ENCODER_AUDIO
const char *codec 必需 编解码器字符串,如 "h264"
const char *(*get_name)(void *type_data) 必需 返回编码器类型的翻译后显示名
void *(*create)(obs_data_t *settings, obs_encoder_t *encoder) 必需 创建编码器实现数据(失败返回 NULL)
void (*destroy)(void *data) 必需 销毁实现数据
bool (*encode)(void *data, struct encoder_frame *frame, struct encoder_packet *packet, bool *received_packet) 必需 编码入口:输入原始帧,输出编码包;received_packet 置 true 表示收到了包,返回值 false 表示严重失败
size_t (*get_frame_size)(void *data) 音频编码器 返回音频帧大小,如 AAC 为 1024
void (*get_defaults)(obs_data_t *settings) 可选 obs_data_set_default* 系列函数写入默认设置
obs_properties_t *(*get_properties)(void *data) 可选 返回属性集,供前端自动生成 UI 控件;注意 data 可能为 NULL(在编码器"类型"上调用 obs_get_encoder_properties 时),须妥善处理
void (*update)(void *data, obs_data_t *settings) 可选 运行中更新设置(如动态改码率)
bool (*get_extra_data)(void *data, uint8_t **extra_data, size_t *size) 可选 返回附加数据(通常是流头)
bool (*get_sei_data)(void *data, uint8_t **sei_data, size_t *size) 可选 返回视频编码器的 SEI 数据
void (*get_audio_info)(void *data, struct audio_convert_info *info) 可选 声明期望的音频格式/采样率,可要求后端自动转换后再送编码器
void (*get_video_info)(void *data, struct video_scale_info *info) 可选 声明期望的视频格式/尺寸,要求后端自动转换
void *type_data / void (*free_type_data)(void *type_data) 类型级私有数据,用于"同一套回调服务多种编码器类型"时的区分,与 per-context 的实现数据不同
uint32_t caps 能力位标志(见下文)
size_t (*get_priming_samples)(void *data) 可选(音频) 返回回放时须跳过的 priming samples(AAC/Opus 等无损之外编解码器的编码器延迟)

2.1 源码中的增强:get_defaults2 / get_properties2 与 GPU 纹理编码

当前仓库头文件在文档所述基础之上还有若干版本演进字段,值得实现者注意:

  • get_defaults2(settings, type_data)get_properties2(data, type_data):带 type_data 的 v2 版本回调。从 libobs/obs-encoder.h 的注释看,若 get_defaultsget_defaults2 同时定义,会先调用 get_defaults 再调用 get_defaults2libobs/obs-encoder.cinit_encoder 中正是按此顺序执行的。
  • encode_texture / encode_texture2:纹理直通编码回调(配合 OBS_ENCODER_CAP_PASS_TEXTURE),输入 encoder_texture(最多 4 个平面纹理 + Windows 共享句柄),绕过 CPU 拷贝路径,供 NVENC 等 GPU 编码器使用。
  • struct encoder_packet_time:记录每帧四个事件时间戳 cts(渲染完成)、fer(提交编码)、ferc(编码完成)、pir(混流交织),时间基为 os_gettime_ns(),用于计算端到端延迟,时序恒为 CTS → FER → FERC → PIR(libobs/obs-encoder.h)。

2.2 caps 能力位:文档与头文件的全量对照

参考文档列出 caps 可为 0 或以下值的按位或组合:OBS_ENCODER_CAP_DEPRECATED(已弃用)、OBS_ENCODER_CAP_ROI(支持 ROI)、OBS_ENCODER_CAP_SCALING(编码器自带缩放逻辑,希望收到未缩放的帧)。当前仓库头文件定义共 7 个标志(libobs/obs-encoder.h):

#define OBS_ENCODER_CAP_DEPRECATED (1 << 0)
#define OBS_ENCODER_CAP_PASS_TEXTURE (1 << 1)
#define OBS_ENCODER_CAP_DYN_BITRATE (1 << 2)
#define OBS_ENCODER_CAP_INTERNAL (1 << 3)
#define OBS_ENCODER_CAP_ROI (1 << 4)
#define OBS_ENCODER_CAP_SCALING (1 << 5)
#define OBS_ENCODER_CAP_MULTITRACK_DYN_BITRATE (1 << 6)

其中 PASS_TEXTURE(GPU 纹理直通)、DYN_BITRATE(动态码率)、INTERNALMULTITRACK_DYN_BITRATE 是文档未列出的后续演进能力。libobs 对 caps 的实际消费逻辑可参考 get_video_infoOBS_ENCODER_CAP_SCALING 时强制按原始分辨率取帧,见第 7 节)与 gpu_encode_availableOBS_ENCODER_CAP_PASS_TEXTURE 且视频混流提供 NV12/P010 纹理时启用 GPU 编码,libobs/obs-encoder.c)。此外,创建带 OBS_ENCODER_CAP_DEPRECATED 的编码器时,libobs 会在日志中打印弃用告警(libobs/obs-encoder.c)。

3. encoder_packet:编码器的输出单元

struct encoder_packetlibobs/obs-encoder.h)承载编码产物。参考文档把字段分为"编码器应填写"与"编码器不应填写"两组,这一边界在实现中非常关键:

编码器负责填写

  • uint8_t *data — 包数据(引用计数内存,须用 obs_encoder_packet_ref/release 管理,见第 11 节)
  • size_t size — 包大小
  • int64_t pts / int64_t dts — 显示/解码时间戳
  • int32_t timebase_num / timebase_den — 时间基
  • enum obs_encoder_type typeOBS_ENCODER_VIDEOOBS_ENCODER_AUDIO
  • bool keyframe — 是否关键帧

编码器不应设置(由 libobs 内部解析/填充)

  • int64_t dts_usec — 微秒 DTS
  • int64_t sys_dts_usec — 系统时间(微秒)
  • int priority — 包优先级(文档注明"已不再使用")
  • int drop_priority — 丢包优先级:若本包被丢弃,后续包必须达到此优先级或更高才能继续传输
  • size_t track_idx — 音轨索引
  • obs_encoder_t *encoder — 该包所属的编码器对象

这一设计让输出层(如 plugins/obs-outputs 中的 RTMP/FFmpeg muxer)可以统一做时间戳解析、音轨路由与丢包决策,而不依赖具体编码器。

4. encoder_frame:编码器的输入单元

struct encoder_framelibobs/obs-encoder.h)是送入 encode 回调的原始帧/音频数据:

  • uint8_t *data[MAX_AV_PLANES] — 原始视频/音频数据(多平面)
  • uint32_t linesize[MAX_AV_PLANES] — 每个平面的行字节数
  • uint32_t frames — 音频帧数(仅音频有意义)
  • int64_t pts — 显示时间戳

音频编码器的调用节奏由 get_frame_size 决定:libobs 按该帧大小把混合器音频切片后逐次送入 encode。仓库中的 plugins/obs-libfdk/obs-libfdk.c 即通过 .get_frame_size = libfdk_frame_size 声明了 AAC 的帧大小。

5. obs_encoder_roi:区域兴趣(ROI)机制

参考文档中的 struct obs_encoder_roi(30.1 版本加入)与 obs_encoder_add_roi 等一组函数,是 libobs 中较新的能力,定义于 libobs/obs-encoder.h

  • top / bottom / left / right:矩形边界,以距输入视频上、左边缘的像素数表示(行/列 0 为原点)
  • float priority:取值 *-1.0f*1.0f,由编码器翻译成其私有的量化值;大于 0 表示提升该区域质量,小于 0 表示降低,且"并非所有编码器都支持负值,负值可能被忽略"

配套 API(均标注 versionadded 30.1):

  • bool obs_encoder_add_roi(obs_encoder_t *encoder, const struct obs_encoder_roi *roi) — 添加 ROI,成功返回 true
  • bool obs_encoder_has_roi(const obs_encoder_t *encoder) — 是否已设置 ROI
  • void obs_encoder_clear_roi(obs_encoder_t *encoder) — 清空全部 ROI
  • void obs_encoder_enum_roi(obs_encoder_t *encoder, void (*enum_proc)(void *, struct obs_encoder_roi *), void *param) — 按添加的逆序(最新到最旧)回调枚举;若编码器启用了缩放,回调收到的结构体会按缩放比例换算
  • uint32_t obs_encoder_get_roi_increment(const obs_encoder_t *encoder) — ROI 列表的版本号,编码器应在该值变化时刷新自身 ROI 配置

源码实现给出了文档未明说的校验规则libobs/obs-encoder.c):

  1. roi 为 NULL 直接失败;
  2. 编码器类型未声明 OBS_ENCODER_CAP_ROI 则失败;
  3. 区域小于 16x16(最小编码块)则失败:roi->bottom - roi->top < 16 || roi->right - roi->left < 16
  4. priority 超出 [-1.0f, 1.0f] 则失败。

obs_encoder_enum_roilibobs/obs-encoder.c)在 scaled_width/scaled_height 均非零时,按"缩放后尺寸 / 视频输出尺寸"计算 scale_x/scale_y,构造按输出分辨率换算过的 scaled_roi 交给回调——这与参考文档中"编码器启用缩放时,回调结构体会相应缩放"的说明完全对应。ROI 的增删改都会使 roi_increment 自增,编码器可将其作为"是否需要重新下发 ROI"的廉价判断依据。

6. 注册与创建:从 obs_register_encoder 到编码器上下文

6.1 注册

参考文档给出的签名是 void obs_register_encoder(struct obs_encoder_info *info),"通常用于 obs_module_load() 或程序初始化阶段"。在当前仓库头文件中,它是一个结构体大小自校验的宏libobs/obs-encoder.h):

EXPORT void obs_register_encoder_s(const struct obs_encoder_info *info, size_t size);

#define obs_register_encoder(info) obs_register_encoder_s(info, sizeof(struct obs_encoder_info))

这样即使插件编译时使用的头文件与运行时 libobs 版本不一致(结构体大小不同),也能及时发现 ABI 不匹配。仓库中真实调用示例:plugins/obs-x264/obs-x264-plugin-main.c 在模块加载时执行 obs_register_encoder(&obs_x264_encoder)。同样注册编码器的插件还包括 plugins/obs-libfdk/obs-libfdk.cplugins/obs-nvenc/nvenc.cplugins/obs-qsv11/obs-qsv11-plugin-main.cplugins/obs-ffmpeg/obs-ffmpeg.cplugins/mac-videotoolbox/encoder.cplugins/win-dshow/win-dshow-encoder.cpp

6.2 创建

参考文档定义了两个创建入口:

obs_encoder_t *obs_video_encoder_create(const char *id, const char *name,
                                        obs_data_t *settings, obs_data_t *hotkey_data);
obs_encoder_t *obs_audio_encoder_create(const char *id, const char *name,
                                        obs_data_t *settings, size_t mixer_idx,
                                        obs_data_t *hotkey_data);

参数语义:id 为编码器类型标识;name 为期望名称,重名时 libobs 会自动改为唯一名settings 为初始设置或 NULL;音频版多出的 mixer_idx 指定该音频编码器从哪个音频混音器捕获;hotkey_data 为已保存的快捷键数据或 NULL;返回新编码器引用,失败返回 NULL,释放须用 obs_encoder_release

源码侧的实现链路(libobs/obs-encoder.c)值得细看:

  1. find_encoder(id) 在全局 obs->encoder_types 中按 id 线性查找定义;若找到但 type 不匹配(如对音频 id 调视频创建函数)直接返回 NULL;
  2. 找不到定义时不会立刻失败,而是构造一个只含 id/type 的占位 obs_encoder_infoowns_info_id 置位),并记录错误日志 Encoder ID '%s' not found——这是 libobs "宽进"的上下文语义;
  3. init_encoder 初始化各互斥锁与上下文,随后按序调用 get_defaults / get_defaults2 把默认设置合并进 context.settingslibobs/obs-encoder.c);
  4. 视频编码器初始化 frame_rate_divisor = 1(支持整帧抽稀降帧的输出场景);
  5. 若类型带 OBS_ENCODER_CAP_DEPRECATED,打印弃用警告。

7. 设置、属性与运行时更新

参考文档中一组函数构成"设置三件套":

  • obs_data_t *obs_encoder_defaults(const char *id) / obs_data_t *obs_encoder_get_defaults(const obs_encoder_t *encoder) — 按类型或实例获取默认设置(引用计数对象,用 obs_data_release 释放)。典型用法:obs_data_t *defaults = obs_encoder_defaults("obs_x264"); ... obs_data_release(defaults);
  • obs_properties_t *obs_encoder_properties(const obs_encoder_t *encoder) / obs_properties_t *obs_get_encoder_properties(const char *id) — 获取属性集,供前端(如 OBS Studio 前端)自动生成设置 UI 控件;返回的属性列表用 obs_properties_destroy 释放。以 x264 为例,其定义中挂了 .get_properties = obs_x264_props, .get_defaults = obs_x264_defaultsplugins/obs-x264/obs-x264.c),这正是文档所述"属性可选用于自动生成用户界面控件"的落地。
  • void obs_encoder_update(obs_encoder_t *encoder, obs_data_t *settings) / obs_data_t *obs_encoder_get_settings(const obs_encoder_t *encoder) — 更新/读取当前设置;编码器实现侧对应 update 回调,文档特别指出它"通常用于活动状态下改码率"这类动态更新。

另有 obs_encoder_get_signal_handler / obs_encoder_get_proc_handler 返回编码器的信号处理器与过程处理器,两者生命周期由 libobs 管理,不应手动释放

8. 绑定媒体、缩放与格式协商

8.1 绑定与查询

  • void obs_encoder_set_video(obs_encoder_t *encoder, video_t *video) / void obs_encoder_set_audio(obs_encoder_t *encoder, audio_t *audio) — 设置编码器要捕获的原始视频/音频处理器;
  • video_t *obs_encoder_video(...) / audio_t *obs_encoder_audio(...) — 查询已绑定的媒体处理器;video_t *obs_encoder_parent_video(const obs_encoder_t *) 返回"原始"视频处理器,即不受 FPS 分频器影响的那个;
  • bool obs_encoder_active(const obs_encoder_t *encoder) — 是否处于活动(编码)状态;
  • 视频类:obs_encoder_get_width / obs_encoder_get_height 返回编码图像的宽/高;音频类:obs_encoder_get_sample_rate 返回音频采样率,obs_encoder_get_frame_size 返回音频包帧大小,obs_encoder_get_mixer_index 返回该编码器所编码音轨对应的混音器索引;
  • 名称与类型:obs_encoder_set_name(非私有且重名时自动改唯一名)、obs_encoder_get_nameobs_encoder_get_codec / obs_get_encoder_codec(id)obs_encoder_get_type / obs_get_encoder_type(id)

8.2 缩放:set_scaled_size 与 CAP_SCALING 的协作

  • void obs_encoder_set_scaled_size(obs_encoder_t *encoder, uint32_t width, uint32_t height) — 设置视频编码器的缩放输出分辨率,宽高置 0 即禁用缩放;编码器处于活动状态时调用此函数只会触发警告并无效
  • bool obs_encoder_scaling_enabled(const obs_encoder_t *encoder) — 是否启用了编码前(CPU 侧)缩放。

源码中缩放策略分两层(libobs/obs-encoder.c):

  1. get_video_info 里,若编码器声明了 OBS_ENCODER_CAP_SCALING,则强制 info.width/height 取视频原始尺寸——即"编码器自带缩放,希望收原始帧"的文档语义在此落地;注释还说明 GPU 缩放启用时 voi 已含缩放尺寸,GPU 缩放优先于自缩放libobs/obs-encoder.c);
  2. maybe_set_up_gpu_rescale 则实现 GPU 侧重缩放:根据 scaled_width/heightpreferred_format/colorspace/rangegpu_scale_type,在核心"视频混流"(video mix)池中查找宽高/格式/色彩空间/范围/缩放类型全部匹配的混流;命中则共享(encoder_refs 自增),未命中则新建一个 encoder_only_mix 并挂到编码器上(libobs/obs-encoder.c)。

8.3 首选格式

  • void obs_encoder_set_preferred_video_format(obs_encoder_t *encoder, enum video_format format) / enum video_format obs_encoder_get_preferred_video_format(const obs_encoder_t *encoder) — 设置首选视频格式:编码器可用该格式时,输出格式与首选不符则强制转换;设为 VIDEO_FORMAT_NONE 恢复默认行为("仅在绝对必要时才转换")。源码中该 setter 对非视频编码器直接忽略(libobs/obs-encoder.c)。

9. 附加数据、SEI 与 priming samples

  • bool obs_encoder_get_extra_data(const obs_encoder_t *encoder, uint8_t **extra_data, size_t *size) — 获取编码器的附加数据(通常是流头,如 H.264 的 SPS/PPS),成功返回 true。实现侧对应 get_extra_data 回调;muxer 在写容器头/流开头时会用到它;
  • 实现侧还有 get_sei_data 回调用于输出 SEI 数据(文档标注"有 SEI 数据的视频编码器");
  • uint32_t obs_encoder_get_priming_samples(const obs_encoder_t *encoder) — 32.1 版本加入,获取回放编码音频时须跳过的样本数,即 AAC/Opus 常说的 "encoder delay / priming samples"。实现直接转发 get_priming_samples 回调,未实现则返回 0(libobs/obs-encoder.c)。仓库中 plugins/obs-libfdk/obs-libfdk.c 通过 .get_priming_samples = libfdk_encoder_delay 提供 AAC 延迟,plugins/obs-outputs/mp4-mux.c 在计算 MP4 总时长时即有"减去 priming delay"的逻辑——这正是文档所述"仅 AAC、Opus 等有损音频编解码器需要"的典型消费方。

10. 引用计数与弱引用

参考文档给出的对象生命周期 API 与源码逐条对应:

  • obs_encoder_t *obs_encoder_get_ref(obs_encoder_t *encoder) — 仍有效则返回自增后的引用,否则 NULL;
  • void obs_encoder_release(obs_encoder_t *encoder) — 释放一次引用,最后一次释放时销毁编码器;
  • obs_weak_encoder_t *obs_encoder_get_weak_encoder(obs_encoder_t *encoder) / obs_encoder_t *obs_weak_encoder_get_encoder(obs_weak_encoder_t *weak) — 强引用换弱引用、弱引用换强引用;编码器已销毁时后者返回 NULL,这是跨线程持有编码器指针的安全方式;
  • void obs_weak_encoder_addref(obs_weak_encoder_t *weak) / void obs_weak_encoder_release(obs_weak_encoder_t *weak) — 弱引用自身也带引用计数。

源码中 obs_encoder_release 的销毁顺序有明确注释:先销毁编码器上下文,再释放 weak 控制块,因为 obs.c 中按名称查找上下文依赖 weak 引用在上下文还在列表期间保持存活(libobs/obs-encoder.c)。

11. 编码器侧工具:数据包引用计数

参考文档"Functions used by encoders"一节给出两个工具:

  • void obs_encoder_packet_ref(struct encoder_packet *dst, struct encoder_packet *src) — 把 src 包引用给 dst(对 data 前 8 字节处的引用计数原子自增后整体拷贝);
  • void obs_encoder_packet_release(struct encoder_packet *packet) — 释放包引用,计数归零时释放 data 内存,并把 packet 清零。

实现见 libobs/obs-encoder.c。这意味着编码器把包交给输出后,libobs 内部(如录制 + 推流多路复用、延迟输出等场景)可以安全地拷贝、排队同一份包数据,无需编码器关心其生存期。

12. 端到端串起来:一次视频编码的数据流

结合参考文档与源码,一条完整链路的骨架是:

  1. 注册:插件在 obs_module_loadobs_register_encoder(&info)
  2. 创建:宿主 obs_video_encoder_create("obs_x264", "encoder", settings, NULL),libobs 合并 get_defaults/get_defaults2 建立上下文;
  3. 绑定obs_encoder_set_video 挂上 video_tadd_connection 中,若 OBS_ENCODER_CAP_PASS_TEXTURE 且核心混流提供 NV12/P010 纹理则走 GPU 编码(start_gpu_encode),否则按 get_video_info 结果(含 CAP_SCALINGset_scaled_size 协商出的尺寸/格式)启动原始视频路径(libobs/obs-encoder.c);
  4. 喂帧出包:libobs 按 encoder_frame 调用 encode 回调;实现者填充 encoder_packet(data/size/pts/dts/timebase/type/keyframe),置 received_packet
  5. 下游消费:输出层读取 extra_data、SEI、ROI(经 obs_encoder_enum_roi + obs_encoder_get_roi_increment 增量刷新)、priming samples 等,把包交给 muxer。

13. 适用前提与小结

本文所有字段、函数与行为均以当前仓库的 docs/sphinx/reference-encoders.rstlibobs/obs-encoder.hlibobs/obs-encoder.c 为准。需要注意的适用前提:

  • 参考文档是 Sphinx API 参考,.. versionadded:: 30.1(ROI 系列)与 .. versionadded:: 32.1(priming samples)标明了对应 API 的引入版本,旧代码库中可能没有这些接口;
  • 头文件中 encode_textureencoder_packet_timeget_defaults2/get_properties2 等属于比文档更新的能力面,实现新插件时应以 libobs/obs-encoder.h 的实际定义为准;
  • 编码器的定位是"流媒体/录像场景的特定编码器"(头文件注释原话),它不是通用转码框架;
  • encoder_packet 中标注"should not be set"的字段是 libobs 内部契约,插件自行填写可能破坏输出层的时间戳解析与丢包策略。

掌握 obs_encoder_info 的回调契约、encoder_packet 的字段边界、ROI 的校验与枚举语义、以及引用计数/弱引用的配对释放规则,就具备了在 OBS 生态中阅读乃至实现一个编码器插件所需的全部 API 知识。

登录后查看全文
热门项目推荐
相关项目推荐