首页
/ OBS Studio Source Profiler(source_profiler)深入解析:libobs 中诊断单个 Source 性能的完整 API 指南

OBS Studio Source Profiler(source_profiler)深入解析:libobs 中诊断单个 Source 性能的完整 API 指南

2026-09-04 20:06:44作者:宗隆裙

本文以 OBS Studio 官方文档 docs/sphinx/reference-libobs-util-source-profiler.rst 为核心,结合 libobs/util/source-profiler.hlibobs/util/source-profiler.c 的实现源码,系统讲解 source profiler 的数据结构、四个公开 API 函数、5 秒采样窗口的产生机制以及 GPU 计时的底层原理。读完本文后,你可以在自研插件或前端中按 source 粒度定位 tick/render 耗时瓶颈,并正确理解每个结果字段的含义与精度限制。

1. Source Profiler 是什么:按 Source 粒度的性能诊断

OBS Studio 的 libobs 内置两套性能分析设施:

  • 通用 Profilerutil/profiler.h):面向整个程序的性能与效率分析,提供 profile 节点树、快照与 CSV 导出,见 Profiler 文档
  • Source Profilerutil/source-profiler.h):本文主题。它专门回答一个更细的问题——某个具体 source(场景项)自身的 tick 与 render 函数花了多少时间,以及异步 source(如媒体源)的输入/渲染帧率是否稳定。

官方文档给出的定位是:

The source profiler is used to get information about individual source's performance.

它的价值在于:当预览掉帧或推流卡顿发生时,通用 profiler 只能告诉你“某帧整体偏慢”,而 source profiler 能指出是哪个 source 的 tick 或渲染 pass 拖慢了一帧

头文件位于 source-profiler.h,实现位于 source-profiler.c。使用方只需:

#include <util/source-profiler.h>

2. 核心数据结构 profiler_result_t

文档定义的结果结构 profiler_result(即 profiler_result_t)是所有 API 的输出载体。官方文档对每个字段组的说明如下,这里逐组继承并结合当前头文件源码(source-profiler.h)补全:

字段组 文档语义 源码注释与补充
tick_avg / render_avg 采样时间窗(5 秒)内该 source tick 与 render 函数执行的平均耗时(ns) 注意:tick 每帧只执行一次,但 render 可能一帧执行多次(source 被多个 display/输出同时绘制时)
tick_max / render_max 采样窗内 tick 与 render 的最大耗时(ns) 用于发现偶发尖峰,比平均值更能暴露卡顿
render_gpu_avg / render_gpu_max GPU 渲染执行的平均/最大耗时 macOS 不支持 GPU 计时;且由于 GPU 负载/时钟频率波动,精度有限
render_sum / render_gpu_sum 一帧内该 source 所有 CPU/GPU 渲染 pass 耗时的总和,再对采样窗取平均 文档示例:若某 source 每帧被渲染 2 次、render_avg 恒为 1000000(1 ms),则 render_sum2000000(2 ms)
async_fps(文档名) obs_source_output_video2() 提交的异步帧时间差计算出的帧率 仅对异步 source 有效(如 Media Source)

需要指出的一点:文档中的 double async_fps 字段在当前源码中已扩展为更细粒度的四个字段(见 source-profiler.h):

typedef struct profiler_result {
	/* Tick times in ns */
	uint64_t tick_avg;
	uint64_t tick_max;

	/* Average and max render times for CPU and GPU in ns */
	uint64_t render_avg;
	uint64_t render_max;
	uint64_t render_gpu_avg;
	uint64_t render_gpu_max;

	/* Average of the sum of all render passes in a frame in ns
	 * (a source can be rendered more than once per frame). */
	uint64_t render_sum;
	uint64_t render_gpu_sum;

	/* FPS of submitted async input */
	double async_input;
	/* Actually rendered async frames */
	double async_rendered;

	/* Best and worst frame times of input/output in ns */
	uint64_t async_input_best;
	uint64_t async_input_worst;
	uint64_t async_rendered_best;
	uint64_t async_rendered_worst;
} profiler_result_t;

即当前实现同时给出输入侧帧率async_input,由提交帧的时间戳差计算)与实际被渲染侧帧率async_rendered,由渲染时读到的最后异步时间戳计算),以及各自的最好/最差帧间隔。这一区分能帮你判断:媒体源是“解码/解码提交掉帧”,还是“提交正常但渲染侧丢帧”。

关于所有耗时字段:单位为纳秒(ns),采样窗口固定为 5 秒(见下一节源码说明)。

3. 四个公开 API 函数

文档定义了四个 EXPORT 函数,签名与 source-profiler.h 完全一致:

3.1 source_profiler_enable(bool enable)

开启/关闭 source profiler。从源码 source-profiler.c#L223-L226 可以看到,该调用只是设置 volatile 标志 enable_next真正的开关在下一帧由图形线程的 source_profiler_frame_begin() 应用

void source_profiler_enable(bool enable)
{
	enable_next = enable;
}

因此文档强调 “The profiler will then start or stop collecting data with the next rendered frame”,并且开启后会有轻微性能开销(每个 source 的 tick/render 都会多一次 os_gettime_ns() 采样与哈希表读写)。

3.2 source_profiler_gpu_enable(bool enable)

开启/关闭 GPU 计时(macOS 不可用)。注意源码中的联动逻辑(source-profiler.c#L228-L231):

void source_profiler_gpu_enable(bool enable)
{
	gpu_enable_next = enable && enable_next;
}

GPU 计时的前提是主 profiler 已启用——先调用 source_profiler_enable(true),GPU 计时才会随之在下一帧生效。文档也明确 GPU profiling 的性能影响更大(需要创建/同步 GPU timer,见第 5 节)。

3.3 source_profiler_get_result(obs_source_t *source)

返回指定 source 的 profiler_result_t *结果必须由调用者用 bfree() 释放,无数据时返回 NULL。实现(source-profiler.c#L622-L630)就是分配一块内存后转发给 source_profiler_fill_result

profiler_result_t *source_profiler_get_result(obs_source_t *source)
{
	profiler_result_t *ret = bmalloc(sizeof(profiler_result_t));
	if (!source_profiler_fill_result(source, ret)) {
		bfree(ret);
		return NULL;
	}
	return ret;
}

3.4 source_profiler_fill_result(obs_source_t *source, profiler_result_t *result)

将结果填充进调用者预先准备好的结构体,返回 true 表示该 source 存在数据,false 表示不存在。文档明确指出这个函数存在的原因:避免每次查询都分配新内存。对于需要高频轮询(例如每 500 ms 刷新一次面板)的前端,推荐做法是常驻一个 profiler_result_t 对象反复填充。实现见 source-profiler.c#L594-L620:内部持读锁(pthread_rwlock_rdlock)遍历哈希表,调用 calculate_tick / calculate_render / calculate_fps 三个静态函数计算平均值与最大值,异步 source 额外计算两组 fps。

4. 采样窗口为什么是 5 秒:从 source_profiler_reset_video 看实现

文档中反复出现的 “sampled timeframe (5 seconds)” 并非魔法常数,而是由视频帧率推导的。调用链是 obs_reset_video()obs.c#L1594)→ source_profiler_reset_video()

void source_profiler_reset_video(struct obs_video_info *ovi)
{
	double fps = ceil((double)ovi->fps_num / (double)ovi->fps_den);
	profiler_samples = (uint64_t)(fps * 5);

	/* This is fine because the video thread won't be running at this point */
	profiler_shutdown();
}

source-profiler.c#L233-L240)即:采样容量 = ceil(帧率) × 5 帧。以 60 fps 输出为例,每个 source 的统计环形缓冲(ucirclebuf)恰好容纳 300 帧的历史数据,因此结果天然是一个“最近 5 秒”的滚动窗口。这也意味着:

  • 切换视频设置(obs_reset_video)会清空全部采样数据,需要重新积累最多 5 秒才有完整统计;
  • 窗口是滚动式的,源刚创建的前几秒内平均值只基于已有样本(源码中 num < capacity 时照常求平均)。

5. 数据是如何被采集的:渲染主循环中的六个挂点

Source profiler 不依赖你手动埋点,它的采样点直接织入 libobs 的渲染主循环。梳理 obs-video.cobs_graphics_thread_loopobs-video.c#L1094-L1144)可以还原完整流程:

  1. 帧开始source_profiler_frame_begin()(L1102)——应用 enable_next / gpu_enable_next 状态,并按 FRAME_BUFFER_SIZE 轮转 GPU timer range 索引;
  2. Tick 阶段tick_sources() 中逐个 source 调用 source_profiler_source_tick_start() / source_profiler_source_tick_end(s, start)obs-video.c#L81-L83),每次 tick 用 os_gettime_ns() 掐表。由于每帧每个 source 只 tick 一次,这正对应文档“tick 每帧仅一次”的说明;
  3. 渲染阶段source_profiler_render_begin()(L1120)启动帧级 GPU timer range;每个 source 实际绘制时,obs_source_render_async_video 等路径调用 source_profiler_source_render_begin(&timer) 创建并启动一个 gs_timer_t,绘制结束后由 source_profiler_source_render_end() 收尾(obs-source.c#L2602obs-source.c#L2699)。一次渲染一帧可能多次,这正是 render_sumrender_avg 存在差异的原因
  4. 帧收尾source_profiler_render_end()(L1128)结束帧级 timer range;source_profiler_frame_collect()(L1134)把上一帧的原始样本压入各 source 的统计环形缓冲。

第 4 步内部还有两个值得注意的机制(source-profiler.c#L284-L396):

  • 两级缓冲:原始逐帧样本(struct source_samples)以 FRAME_BUFFER_SIZE 帧为周期环形存放,而 FRAME_BUFFER_SIZE 直接取自渲染纹理缓冲 NUM_TEXTURES(定义为 2,见 obs-internal.h#L49),注释写明“Buffer frame data collection to give GPU time to finish rendering”——GPU 是异步的,本帧的 timer 数据要等 1~2 帧后才能安全读取,因此处理延迟 FRAME_BUFFER_SIZE - 1 帧;
  • disjoint 检测:DirectX 计时器在 GPU 负载剧变时会报告 disjoint,此时源码会打印警告并丢弃该轮 GPU 样本(source-profiler.c#L303-L305):blog(LOG_WARNING, "GPU Timers were disjoint, discarding samples.")。这是文档所说“GPU timing ... of limited accuracy”的具体来源之一。

另外两处与生命周期相关的挂点:

  • 异步帧提交:obs_source_output_video_internalobs_source_output_video() / obs_source_output_video2() 的公共入口,见 obs.h#L1418-L1422)一收到帧就调用 source_profiler_async_frame_received(source) 记录时间戳(obs-source.c#L3614),这是 async_input fps 的数据来源;
  • source 释放:obs_source_release 将对象销毁时,source_profiler_remove_source 会在图形线程上调度删除任务(obs_queue_task(OBS_TASK_GRAPHICS, ...)source-profiler.c#L500-L506),避免在非图形线程碰 GPU 资源。

整个模块用 uthash 哈希表 + pthread_rwlock 组织:hm_samples 存逐帧原始样本,hm_entries 存统计环形缓冲,查询走读锁、采集走写锁,因此查询可以在任意线程安全调用,这为“前端定时轮询”的用法提供了基础。

6. 实战用法:在插件/前端中轮询单个 Source 的性能

综合上述 API 约束(下一帧生效、bfree 释放、可复用结构体),一个最小可用示例如下(以 C 写,C++ 前端同理):

#include <util/source-profiler.h>
#include <util/base.h>
#include <math.h>

/* 第一步:启用(可在任意线程调用,下一帧生效) */
source_profiler_enable(true);
source_profiler_gpu_enable(true);   /* 仅在非 macOS 且确认承受得住开销时 */

/* 第二步:周期性(例如每 1~2 秒)查询某个 source */
profiler_result_t result;
if (source_profiler_fill_result(my_source, &result)) {
	uint64_t ms = (uint64_t)(result.render_avg / 1000000.0);
	blog(LOG_INFO, "render_avg=%.0f ms, render_max=%" PRIu64 " ms, "
	     "tick_avg=%" PRIu64 " ms, async_input_fps=%.1f",
	     (double)result.render_avg / 1e6, result.render_max / 1e6,
	     result.tick_avg / 1e6, result.async_input);
}
/* fill_result 复用同一对象,无需 bfree */

/* 若改用 get_result,则必须: */
profiler_result_t *r = source_profiler_get_result(my_source);
if (r) {
	/* ... 使用 r ... */
	bfree(r);   /* 文档明确要求:result must be freed with bfree() */
}

使用时的解读建议:

  • render_avg 稳定、render_max 周期性尖峰 → 存在间歇性卡顿(如媒体源解码抖动);
  • render_sum 明显大于 render_avg → 该 source 一帧被渲染多次(多个预览/输出场景),总 GPU 负担应按 sum 评估;
  • 异步源:async_input 正常而 async_rendered 偏低 → 帧提交没问题,是渲染侧丢弃了帧;反之则上游供给不足;
  • 数据需等待约 5 秒采样窗积累后才稳定;obs_reset_video 后需重新等待。

7. 限制与适用前提

综合文档说明与源码事实,使用 source profiler 需注意:

  1. 平台限制:GPU 计时在 macOS 上不可用(文档明示);帧级 GPU timer range 相关实现注释也标注 “only required for DirectX”(source-profiler.c#L80-L82),即 GPU 字段主要在 Windows/D3D11 路径下有意义;
  2. 精度限制:GPU 计时精度受 GPU 负载/时钟频率波动影响,disjoint 时样本被整体丢弃,因此 render_gpu_* 适合做趋势对比而非绝对测量;
  3. 性能代价:CPU 路径每次 tick/render 都多一次时间戳读取与哈希操作(代价小);GPU 路径每帧创建/同步 timer(代价较大),文档均如实标注;
  4. 生效时机:两个 enable 接口都“下一帧生效”,且关闭 profiler 时会执行 profiler_shutdown() 释放全部缓冲——关闭后再查询 fill_result 会直接返回 false
  5. 采样窗语义:所有 avg/max 都是“最近约 5 秒”滚动窗口的统计(fps × 5 个样本),不是自启用以来的累计值。

8. 小结

OBS Studio 的 source profiler 是 libobs 提供的一套轻量级、按 source 粒度的性能诊断设施:4 个 API(source_profiler_enable / source_profiler_gpu_enable / source_profiler_get_result / source_profiler_fill_result)+ 一个 profiler_result_t 结果结构,覆盖 tick、CPU 渲染、GPU 渲染与异步帧率四类指标,采样窗口由视频帧率自动推导为 5 秒。结合本文给出的源码挂点(obs-video.csource-profiler.c),你可以既按文档正确使用它,也能解释每个字段的来龙去脉,把“哪一帧慢了”进一步定位到“哪个 source 慢了”。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
docsdocs
暂无描述
Markdown
889
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341