首页
/ OBS Studio libobs Canvas API(obs_canvas_t)完整参考:多画布渲染体系的设计与使用

OBS Studio libobs Canvas API(obs_canvas_t)完整参考:多画布渲染体系的设计与使用

2026-09-06 21:02:05作者:魏献源Searcher

本文围绕 OBS Studio 的 Canvas API 参考文档 reference-canvases.rst 展开,系统讲解 libobs 中引用计数画布对象 obs_canvas_t 的完整 API:信号、标志位、创建/销毁、保存/加载、引用计数、通道(Channel)、场景归属与视频输出等全部接口。结合 libobs/obs-canvas.clibobs/obs.h 等源码实现,读者可以掌握如何在插件或前端中正确使用多画布能力(如独立的预览/设备输出画布),并理解每个参数在底层实际影响的行为。需要特别注意:文档原文以醒目的警告框声明——Canvas 仍处于早期实现阶段,API 视为不稳定(unstable),可能不经警告就发生变化,插件开发者须格外谨慎

1. 什么是 Canvas:概念与类型定义

Canvas(画布)是 libobs 中新引入的一等对象,官方文档给出的定义为:

Canvases are reference-counted objects that contain scenes and define how those are rendered. They provide a video object which can be used with encoders or raw outputs. (画布是引用计数的对象,包含场景并定义场景的渲染方式;它提供一个可用于编码器或原始输出的视频对象。)

从源码结构看(libobs/obs-internal.h),画布的内部结构印证了这一设计:

struct obs_canvas {
	struct obs_context_data context;      /* 通用上下文:名称、UUID、私有标志、信号处理器 */
	uint32_t flags;                      /* obs_canvas_flags 组合 */
	struct obs_video_info ovi;           /* 该画布的视频信息,FPS 字段被忽略 */
	struct obs_source *sources;          /* 哈希表:归属于该画布的场景(和组)源 */
	pthread_mutex_t sources_mutex;
	struct obs_view view;                /* 视图上下文:MAX_CHANNELS 个通道的源 */
	struct obs_core_video_mix *mix;      /* 视频混合器:画布实际的"视频输出" */
};

注释明确写道:"canvas objects mainly act as a proxy for the existing view and video mix objects"(画布对象目前主要是既有 view 与 video mix 对象的代理)。也就是说,一个画布 = 一组归属于它的场景源(sources)+ 一个多通道视图(view)+ 一个独立的视频混合器(mix)。mix 即文档所说的"可交给编码器或原始输出的 video 对象"。

核心类型

#include <obs.h>

// 引用计数的画布(强引用)
obs_canvas_t *canvas;
// 画布的弱引用(不会阻止对象释放)
obs_weak_canvas_t *weak;

主画布(Main Canvas)

libobs 内部始终维护一个主画布,用于默认的视频输出。从源码看(libobs/obs.c),obs_init 时通过 obs_create_main_canvas() 创建它;其实现(libobs/obs-canvas.c)固定了两点:

static const char *MAIN_CANVAS_NAME = "Main";
static const char *MAIN_CANVAS_UUID = "6c69626f-6273-4c00-9d88-c5136d61696e";

obs_canvas_t *obs_create_main_canvas(void)
{
	const uint32_t main_flags = MAIN | PROGRAM;
	return obs_canvas_create_internal(MAIN_CANVAS_NAME, MAIN_CANVAS_UUID, NULL, main_flags, false);
}

即主画布名称恒为 "Main",UUID 固定为 6c69626f-6273-4c00-9d88-c5136d61696e,且标志为 MAIN | PROGRAM。创建时不传视频信息(ovi 为 NULL),说明画布可以没有自己的 mix——主画布的视频由全局 obs_reset_video() 驱动。

2. Canvas 信号(Signals)

文档定义了画布可用的 7 个信号。对照 libobs/obs-canvas.c 中的 canvas_signals 数组,每个画布对象拥有自己的 signal_handler_t,可注册以下信号:

信号 参数 触发时机
remove ptr canvas 对画布调用 obs_canvas_remove()
destroy ptr canvas 画布即将被销毁时
video_reset ptr canvas 调用 obs_reset_video()obs_canvas_reset_video() 之后,画布的视频混合被重建时
source_add ptr canvas, ptr source 源被加入画布时
source_remove ptr canvas, ptr source 源从画布移除时
rename ptr canvas, string new_name, string prev_name 画布被重命名时
channel_change ptr canvas, int channel, in out ptr source, ptr prev_source 通道上的源被更换时

两个值得注意的源码级细节:

  1. in out 修饰符的含义channel_change 中的 source 参数是 in out,源码注释解释(libobs/obs-canvas.c):为了兼容原始实现,允许回调在 channel_change 中直接改写 source 指针来覆盖最终绑定的源("This isn't used anywhere in OBS itself"——OBS 自身未使用此特性)。
  2. 主画布信号会同时广播到全局canvas_dosignal()libobs/obs-canvas.c)在 signal_obs 参数非空且画布非私有时,会同时向 obs->signals(全局信号处理器)和画布自身的信号处理器发信号。此外 obs_canvas_set_channel 中,只有当画布带 MAIN 标志时,channel_change 才会额外广播到全局(libobs/obs-canvas.c)——这与旧的 OBS_FRONTEND_PRIVATE_OUTPUT_CHANGED 语义衔接。

获取信号处理器本身可用 obs_canvas_get_signal_handler(canvas)libobs/obs.h)。

3. Canvas 标志位(Flags)

画布的行为通过创建时传入的 flags 参数控制。取值可以是 0 或以下值的按位或(OR)组合,定义见 libobs/obs.h

enum obs_canvas_flags {
	MAIN      = 1 << 0,  // 主画布:由 libobs 创建,不可重命名、不可重置,用户不能设置
	ACTIVATE  = 1 << 1,  // 画布中的源在可见时会变为激活状态
	MIX_AUDIO = 1 << 2,  // 该画布通道中的音频会混入音频输出
	SCENE_REF = 1 << 3,  // 画布持有场景源的(强)引用
	EPHEMERAL = 1 << 4,  // 表示该画布不应被保存

	/* Presets */
	PROGRAM = ACTIVATE | MIX_AUDIO | SCENE_REF,
	PREVIEW = EPHEMERAL,
	DEVICE  = ACTIVATE | EPHEMERAL,
};
标志 含义 源码行为印证
MAIN 主画布,不可重命名、不可重置 obs_canvas_set_name 遇到 MAIN 直接返回(obs-canvas.c L499-L500);obs_canvas_reset_videoMAIN 画布返回 false
ACTIVATE 源可见即激活 创建时决定画布视图类型:flags & ACTIVATE ? MAIN_VIEW : AUX_VIEWobs-canvas.c L159),MAIN_VIEW 下源可见会真正激活采集/播放
MIX_AUDIO 通道音频混入输出 决定 mix 的 mix_audio 字段:mix->mix_audio = (flags & MIX_AUDIO) != 0obs-canvas.c L168
SCENE_REF 持有场景源引用 插入源时若是场景则 obs_source_get_ref,销毁画布时统一释放(obs-canvas.c L324-L334L212-L219
EPHEMERAL 不参与保存 obs_save_canvasEPHEMERAL 画布返回 NULLobs-canvas.c L237-L240

三个预置组合:

  • PROGRAM = ACTIVATE | MIX_AUDIO | SCENE_REF——典型的"节目输出"画布,也是主画布实际使用的组合(MAIN | PROGRAM);
  • PREVIEW = EPHEMERAL——临时预览画布;
  • DEVICE = ACTIVATE | EPHEMERAL——虚拟设备类画布:源可见即激活,但不保存。

一个容易踩坑的事实:obs_canvas_create / obs_canvas_create_private 都会先执行 flags &= ~MAINobs-canvas.c L194-L204),即用户无法创建出带 MAIN 标志的画布,主画布只能由 libobs 自己产生。

4. 通用画布函数

4.1 获取主画布

/** Get a strong reference to the main OBS canvas. */
obs_canvas_t *obs_get_main_canvas(void);

获取主画布的强引用。实现非常简洁(libobs/obs.c):

obs_canvas_t *obs_get_main_canvas(void)
{
	return obs_canvas_get_ref(obs->data.main_canvas);
}

注意它每次调用都会增加一次引用计数,用完必须 obs_canvas_release

4.2 创建画布

/** Creates a new canvas. */
obs_canvas_t *obs_canvas_create(const char *name, struct obs_video_info *ovi, uint32_t flags);
/** Creates a new private canvas. */
obs_canvas_t *obs_canvas_create_private(const char *name, struct obs_video_info *ovi, uint32_t flags);
参数 说明
name 名称。普通画布会去重(重名时自动改名);私有画布不去重
ovi 该画布视频输出使用的视频配置(struct obs_video_info)。可以为 NULL——源码注释"A canvas can be created without a mix"(画布可以在没有 mix 的情况下创建)
flags 第 3 节所述的画布标志(MAIN 位会被强制清除)
返回值 画布对象(强引用,创建成功非 NULL)

两者差异的本质在内部实现(obs-canvas.c L138-L186):私有画布(private=true)只注册进按 UUID 查找的哈希表 canvases不注册进按名称查找的 named_canvases,也不发出全局 canvas_create 信号;普通画布两者皆做。因此私有画布不会被 obs_get_canvas_by_name 找到,适合插件内部使用。

创建时的关键步骤依次为:

  1. bzalloc 分配并保存 flags
  2. obs_context_data_init 初始化名称/UUID/私有标志/信号处理器;
  3. signal_handler_add_array 挂上第 2 节的画布信号;
  4. obs_view_initACTIVATE 标志选择 MAIN_VIEW / AUX_VIEW
  5. 若传入了 ovi,调用 obs_create_video_mix(ovi) 创建 mix,绑定画布的 view、按 MIX_AUDIO 设置混音开关,并加锁推入全局 obs->video.mixes 数组(libobs 的核心视频渲染循环会渲染所有 mix);
  6. 按 UUID(及名称)注册到全局查找表。

4.3 标记移除与查询移除状态

/** Signal that references to canvas should be released and mark the canvas as removed. */
void obs_canvas_remove(obs_canvas_t *canvas);
/** Returns if a canvas is marked as removed (i.e., should no longer be used). */
bool obs_canvas_removed(obs_canvas_t *canvas);

obs_canvas_remove 不立即销毁画布,而是打上一个内部标志并触发 remove 信号(obs-canvas.c L565-L582):

/* Internal flag to mark a canvas as removed */
static const uint32_t REMOVED = 1u << 31;   // 内部标志,不属于公开 API

void obs_canvas_remove(obs_canvas_t *canvas)
{
	/* Do not allow removing the main canvas, or canvases already marked as removed. */
	if (canvas->flags & (REMOVED | MAIN))
		return;
	...
}

要点:主画布不可被 remove;重复 remove 无效;obs_canvas_removed 即检测这个第 31 位内部标志。这是一种"软删除"语义——画布仍可通过强引用访问,但应视为不再使用(渲染循环等也会跳过已移除对象)。

4.4 名称、UUID 与标志的读写

void          obs_canvas_set_name(obs_canvas_t *canvas, const char *name);
const char    *obs_canvas_get_name(const obs_canvas_t *canvas);
const char    *obs_canvas_get_uuid(const obs_canvas_t *canvas);
uint32_t      obs_canvas_get_flags(const obs_canvas_t *canvas);
  • set_name:空字符串无效;MAIN 画布拒绝改名;名称相同则跳过。非私有画布改名时(普通画布)额外广播全局 canvas_rename 信号,画布自身始终触发 rename 信号(obs-canvas.c L495-L523)。
  • get_name / get_uuid:直接返回内部 context.name / context.uuid 指针(生命周期与画布一致,勿自行释放)。
  • get_flags:返回创建时(加载时)写入的 flags 原始值。

5. 保存 / 加载函数

/** Saves a canvas to settings data */
obs_data_t *obs_save_canvas(obs_canvas_t *canvas);
/** Loads a canvas from settings data */
obs_canvas_t *obs_load_canvas(obs_data_t *data);

序列化格式可以直接从 obs_save_canvas 读出,只有 4 个键:

类型 内容
name string 画布名称
uuid string 画布 UUID
private bool 是否私有画布
flags int 标志位组合

两条规则:

  1. EPHEMERAL 或已被 REMOVED 标记的画布,obs_save_canvas 返回 NULL——临时画布根本不进入存档;
  2. obs_load_canvas 读回这 4 个字段后调用内部创建函数,并且同样会先清掉 MAINobs-canvas.c L252-L261),所以存档永远无法伪造一个主画布;加载时不恢复视频信息(ovi 传 NULL),视频配置需要后续 obs_reset_video() / obs_canvas_reset_video() 重建。

6. 引用计数函数

画布是引用计数对象,API 与 libobs 其他对象(source 等)同构,共 6 个函数:

obs_canvas_t       *obs_canvas_get_ref(obs_canvas_t *canvas);            // 增加强引用
void                obs_canvas_release(obs_canvas_t *canvas);           // 释放强引用
void                obs_weak_canvas_addref(obs_weak_canvas_t *weak);    // 增加弱引用
void                obs_weak_canvas_release(obs_weak_canvas_t *weak);   // 释放弱引用
obs_weak_canvas_t  *obs_canvas_get_weak_canvas(obs_canvas_t *canvas);   // 强引用 → 弱引用
obs_canvas_t       *obs_weak_canvas_get_canvas(obs_weak_canvas_t *weak); // 弱引用 → 强引用(可能失败)

弱引用的底层结构见 libobs/obs-internal.h

struct obs_weak_ref { long refs; };   // refs < 0 表示对象已过期
struct obs_weak_canvas {
	struct obs_weak_ref ref;
	struct obs_canvas *canvas;
};

源码印证了两个关键行为:

  • 强引用归零即销毁obs_canvas_release 中当 obs_ref_release 返回真(引用归零)时调用 obs_canvas_destroy 并释放 weak 控制块(obs-canvas.c L73-L88)。
  • 弱引用换强引用可能失败obs_weak_canvas_get_canvas 用原子 CAS 提升引用;若对象已过期(owners < 0)则返回 NULLobs-canvas.c L125-L134)。这就是信号回调里保存 ptr canvas 参数的标准做法——先拿弱引用防自引用泄漏,回调内再 obs_weak_canvas_get_canvas 换回强引用。
  • 还有一个防御性细节:核心已关闭(obs == NULL)时调用 obs_canvas_release 只会打印告警而不会崩溃(obs-canvas.c L75-L78),提示插件在 OBS_SHUTDOWN 阶段的引用管理要自行把关。

7. 画布通道(Channel)函数

/** Sets the source to be used for a canvas channel. */
void obs_canvas_set_channel(obs_canvas_t *canvas, uint32_t channel, obs_source_t *source);
/** Gets the source currently in use for a canvas channel. */
obs_source_t *obs_canvas_get_channel(obs_canvas_t *canvas, uint32_t channel);

通道即 obs_view 中的 channels[MAX_CHANNELS] 数组,MAX_CHANNELS 固定为 64libobs/obs-defs.h)。obs_canvas_set_channel 的完整语义(obs-canvas.c L431-L482):

  1. channel >= MAX_CHANNELS 时直接忽略;
  2. sourceobs_source_get_ref(通道持有强引用);若与当前相同,直接释放并返回;
  3. 组装 calldata(canvaschannelprev_sourcesource)后触发画布级 channel_change 信号;MAIN 画布还会把 channel_change 广播到全局信号处理器;
  4. 回读可能被回调改写的 sourcein out 特性),写入 view->channels[channel]
  5. 对新源 obs_source_activate(source, view->type),对旧源 obs_source_deactivate + obs_source_release

obs_canvas_get_channelobs_view_get_sourceobs-view.c L74-L89),返回的源引用计数已递增,用完需 obs_source_release;越界或无源时返回 NULL

激活语义与 ACTIVATE 标志联动:因为创建时 view 类型由 ACTIVATE 决定(MAIN_VIEW vs AUX_VIEW),是否真正激活采集设备取决于画布标志——这正是 PREVIEW/DEVICE 预置组合的行为差异所在。

8. 画布源(场景)管理函数

/** Create scene attached to a canvas. */
obs_scene_t *obs_canvas_scene_create(obs_canvas_t *canvas, const char *name);
/** Remove a scene from a canvas. */
void obs_canvas_scene_remove(obs_scene_t *scene);
/** Move scene to another canvas, detaching it from the previous one and deduplicating the name if needed. */
void obs_canvas_move_scene(obs_scene_t *scene, obs_canvas_t *dst);
/** Enumerates scenes belonging to a canvas. */
void obs_canvas_enum_scenes(obs_canvas_t *canvas, bool (*enum_proc)(void *, obs_source_t *), void *param);
/** Gets a canvas source by its name. */
obs_source_t *obs_canvas_get_source_by_name(obs_canvas_t *canvas, const char *name);
/** Gets a canvas scene by its name. */
obs_scene_t *obs_canvas_get_scene_by_name(obs_canvas_t *canvas, const char *name);
  • obs_canvas_scene_create:内部是 obs_source_create_canvas(canvas, "scene", name, NULL, NULL)obs-canvas.c L484-L488)——场景本质上就是创建时直接绑定到指定画布的 source。新场景会自动进入 source_add 信号流程。
  • obs_canvas_scene_remove:即 obs_canvas_remove_source,从画布的 sources 哈希表摘除、触发 source_remove、按 SCENE_REF 释放场景的强引用(obs-canvas.c L356-L374);若被移除的是(group),还会遍历该画布其他场景把组从各场景中清出。
  • obs_canvas_move_scene:先 obs_canvas_remove_sourceobs_canvas_insert_source(dst, ...),名称冲突时自动去重;还会把场景内的组一并迁移到新画布(obs-canvas.c L540-L563)。这是把场景从主画布搬到独立输出画布(或反向)的关键接口。
  • obs_canvas_enum_scenes:回调返回 true 继续枚举、false 结束。
  • obs_canvas_get_source_by_name / obs_canvas_get_scene_by_name:按名在画布的 sources 表中查找。返回值引用计数已递增,前者用 obs_source_release() 释放,后者用 obs_scene_release() 释放。

关于 SCENE_REF 标志值得强调:只有场景源(scene)会被画布额外持有强引用(obs_canvas_insert_sourceflags & SCENE_REF && obs_source_is_scene(source)obs_source_get_refobs-canvas.c L324-L327)。这意味着 PROGRAM 画布天然保证场景不消失,而 DEVICE/PREVIEW 画布则把场景的生命周期交还给使用者自己管理。

9. 画布视频函数

/** Reset a canvas's video configuration. */
bool obs_canvas_reset_video(obs_canvas_t *canvas, struct obs_video_info *ovi);
/** Returns true if the canvas video is configured. */
bool obs_canvas_has_video(obs_canvas_t *canvas);
/** Get canvas video output */
video_t *obs_canvas_get_video(const obs_canvas_t *canvas);
/** Get canvas video info (if any) */
bool obs_canvas_get_video_info(const obs_canvas_t *canvas, struct obs_video_info *ovi);
/** Render the canvas's view. Must be called on the graphics thread. */
void obs_canvas_render(obs_canvas_t *canvas);

9.1 重置视频配置

obs_canvas_reset_video 内部先做校验(obs-canvas.c L404-L410):

bool obs_canvas_reset_video(obs_canvas_t *canvas, struct obs_video_info *ovi)
{
	if (!ovi || canvas->flags & MAIN || obs_video_active())
		return false;
	return obs_canvas_reset_video_internal(canvas, ovi);
}

即三种情况直接失败:未传 ovi、画布是 MAIN 画布(主画布视频由全局 obs_reset_video() 管理)、全局视频已经处于活动状态。成功后内部流程(obs_canvas_reset_video_internalobs-canvas.c L304-L322):清除旧 mix → 保存新 oviobs_create_video_mix 建新 mix 并推入全局 mix 列表 → 触发 video_reset 信号。文档特别注明:ovi 中的帧率字段被忽略,实际使用全局渲染帧率(内部结构体注释亦写明 "FPS ignored",obs-internal.h L728-L729)。

9.2 查询与渲染

  • obs_canvas_has_videocanvas->mix != NULLobs-canvas.c L584-L587);
  • obs_canvas_get_video:返回 canvas->mix->video,即可直接 video_open / 交给编码器或原始输出(obs_output)的 video_tobs-canvas.c L412-L415);
  • obs_canvas_get_video_info:要求全局图形线程已初始化且画布有 mix,否则返回 false,成功则拷贝出 mix 的 oviobs-canvas.c L417-L424);
  • obs_canvas_render:转发到 obs_view_renderobs-view.c L118-L141),按序遍历全部 MAX_CHANNELS 个通道,对未移除的源逐一 obs_source_video_render,对已移除的源就地释放并清空槽位。必须在图形线程调用

10. 典型使用模式与注意事项

综合以上 API,一个"创建独立视频输出画布"的插件侧流程大致如下(所有调用均来自文档所列 API):

struct obs_video_info ovi;
// 填好 base_width / output_width / fps 等后:

obs_canvas_t *canvas = obs_canvas_create("My Output", &ovi, DEVICE);
// 在画布上建场景、挂源,并把当前场景源绑到通道 0
obs_scene_t *scene = obs_canvas_scene_create(canvas, "Scene 1");
obs_canvas_set_channel(canvas, 0, (obs_source_t *)scene);

video_t *video = obs_canvas_get_video(canvas);  // 交给编码器 / 原始输出
// 图形线程中每帧:obs_canvas_render(canvas);

// 不再需要时:
obs_canvas_remove(canvas);
obs_canvas_release(canvas);
obs_scene_release(scene);

使用前的检查清单(对照本文各节):

  1. API 不稳定:Canvas 仍在早期阶段,文档明确提示插件开发者"proceed with great caution";
  2. 引用纪律obs_get_main_canvasobs_canvas_get_refobs_canvas_get_channelobs_canvas_get_source_by_nameobs_canvas_get_scene_by_name 都会递增引用,务必配对释放;
  3. MAIN 画布不可重命名、reset、remove;用户创建画布时 MAIN 位会被强制清除;
  4. EPHEMERAL 画布不会被保存obs_save_canvas 对其返回 NULL
  5. 通道上限 64MAX_CHANNELS),越界调用被静默忽略;
  6. obs_canvas_reset_video 在全局视频活动期或对主画布调用会返回 false
  7. obs_canvas_render 只能跑在图形线程;
  8. 私有画布不入名称表、不发全局 canvas_create,适合插件内部使用,但无法通过 obs_get_canvas_by_name 找回。

11. 参考文件

文件 内容
docs/sphinx/reference-canvases.rst 本文对应的官方 Canvas API 参考文档
libobs/obs-canvas.c Canvas 全部公开 API 的实现
libobs/obs.h Canvas API 声明与 enum obs_canvas_flags 定义
libobs/obs-internal.h struct obs_canvasstruct obs_weak_canvas 内部结构
libobs/obs-view.c 画布背后的 view/通道实现(激活、去激活、渲染)
libobs/obs-defs.h MAX_CHANNELS 64 通道数上限
libobs/obs.c 主画布在 obs_init 中的创建位置
登录后查看全文
热门项目推荐
相关项目推荐