libobs: Introduce Canvases

This adds a new obs_canvas object that acts as a shareable
(reference-counted) owner of views, mixes, and (optionally) scenes.

This is a step towards faciliatating multi-canvas and multi-output
features in OBS Studio.

It solves a number of complications that exist with the manual approach
of using views, such as audio mixing, source active-state tracking, and
scenes not havinga  reliable way of identifying the actual available
canvas size.
This commit is contained in:
Dennis Sädtler 2025-01-26 22:13:33 +01:00 • committed by Ryan Foster
commit 3d57b1fd63
10 changed files with 1043 additions and 132 deletions

View file

@ -38,6 +38,7 @@ target_sources(
obs-av1.h
obs-avc.c
obs-avc.h
obs-canvas.c
obs-config.h
obs-data.c
obs-data.h

View file

@ -512,7 +512,7 @@ bool audio_callback(void *param, uint64_t start_ts_in, uint64_t end_ts_in, uint6
obs_source_enum_active_tree(source, push_audio_tree, audio);
push_audio_tree(NULL, source, audio);
if (obs->video.mixes.array[j] == obs->video.main_mix)
if (obs->video.mixes.array[j]->mix_audio)
da_push_back(audio->root_nodes, &source);
}
pthread_mutex_unlock(&view->channels_mutex);

555
libobs/obs-canvas.c Normal file
View file

@ -0,0 +1,555 @@
/******************************************************************************
Copyright (C) 2025 by Dennis Sädtler <saedtler@twitch.tv>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "obs.h"
#include "obs-internal.h"
#include "obs-scene.h"
/* The primary canvas has static name/uuid. */
static const char *MAIN_CANVAS_NAME = "Main";
static const char *MAIN_CANVAS_UUID = "6c69626f-6273-4c00-9d88-c5136d61696e";
/* Internal flag to mark a canvas as removed */
static const uint32_t REMOVED = 1u << 31;
/*** Signals ***/
static const char *canvas_signals[] = {
"void destroy(ptr canvas)",
"void remove(ptr canvas)",
"void video_reset(ptr canvas)",
"void source_add(ptr canvas, ptr source)",
"void source_remove(ptr canvas, ptr source)",
"void rename(ptr source, string new_name, string prev_name)",
"void channel_change(ptr canvas, int channel, in out ptr source, ptr prev_source)",
NULL,
};
static inline void canvas_dosignal(obs_canvas_t *canvas, const char *signal_obs, const char *signal_source)
{
struct calldata data;
uint8_t stack[128];
calldata_init_fixed(&data, stack, sizeof(stack));
calldata_set_ptr(&data, "canvas", canvas);
if (signal_obs)
signal_handler_signal(obs->signals, signal_obs, &data);
if (signal_source)
signal_handler_signal(canvas->context.signals, signal_source, &data);
}
static inline void canvas_dosignal_source(const char *signal, obs_canvas_t *canvas, obs_source_t *source)
{
struct calldata data;
uint8_t stack[128];
calldata_init_fixed(&data, stack, sizeof(stack));
calldata_set_ptr(&data, "canvas", canvas);
calldata_set_ptr(&data, "source", source);
signal_handler_signal(source->context.signals, signal, &data);
}
/*** Reference Counting ***/
void obs_canvas_release(obs_canvas_t *canvas)
{
if (!obs && canvas) {
blog(LOG_WARNING, "Tried to release a canvas when the OBS core is shut down!");
return;
}
if (!canvas)
return;
obs_weak_canvas_t *control = (obs_weak_canvas_t *)canvas->context.control;
if (obs_ref_release(&control->ref)) {
obs_canvas_destroy(canvas);
obs_weak_canvas_release(control);
}
}
void obs_weak_canvas_addref(obs_weak_canvas_t *weak)
{
if (!weak)
return;
obs_weak_ref_addref(&weak->ref);
}
void obs_weak_canvas_release(obs_weak_canvas_t *weak)
{
if (!weak)
return;
if (obs_weak_ref_release(&weak->ref))
bfree(weak);
}
obs_canvas_t *obs_canvas_get_ref(obs_canvas_t *canvas)
{
if (!canvas)
return NULL;
return obs_weak_canvas_get_canvas((obs_weak_canvas_t *)canvas->context.control);
}
obs_weak_canvas_t *obs_canvas_get_weak_canvas(obs_canvas_t *canvas)
{
if (!canvas)
return NULL;
obs_weak_canvas_t *weak = (obs_weak_canvas_t *)canvas->context.control;
obs_weak_canvas_addref(weak);
return weak;
}
obs_canvas_t *obs_weak_canvas_get_canvas(obs_weak_canvas_t *weak)
{
if (!weak)
return NULL;
if (obs_weak_ref_get_ref(&weak->ref))
return weak->canvas;
return NULL;
}
/*** Creation / Destruction ***/
static obs_canvas_t *obs_canvas_create_internal(const char *name, const char *uuid, struct obs_video_info *ovi,
uint32_t flags, bool private)
{
struct obs_canvas *canvas = bzalloc(sizeof(struct obs_canvas));
canvas->flags = flags;
if (!obs_context_data_init(&canvas->context, OBS_OBJ_TYPE_CANVAS, NULL, name, uuid, NULL, private))
return NULL;
if (!signal_handler_add_array(canvas->context.signals, canvas_signals)) {
obs_context_data_free(&canvas->context);
bfree(canvas);
return NULL;
}
if (pthread_mutex_init_recursive(&canvas->sources_mutex) != 0) {
obs_context_data_free(&canvas->context);
bfree(canvas);
return NULL;
}
obs_view_init(&canvas->view, flags & ACTIVATE ? MAIN_VIEW : AUX_VIEW);
obs_context_init_control(&canvas->context, canvas, (obs_destroy_cb)obs_canvas_destroy);
/* A canvas can be created without a mix. */
if (ovi) {
canvas->ovi = *ovi;
canvas->mix = obs_create_video_mix(ovi);
if (canvas->mix) {
canvas->mix->view = &canvas->view;
canvas->mix->mix_audio = (flags & MIX_AUDIO) != 0;
pthread_mutex_lock(&obs->video.mixes_mutex);
da_push_back(obs->video.mixes, &canvas->mix);
pthread_mutex_unlock(&obs->video.mixes_mutex);
}
}
obs_context_data_insert_uuid(&canvas->context, &obs->data.canvases_mutex, &obs->data.canvases);
if (!private) {
obs_context_data_insert_name(&canvas->context, &obs->data.canvases_mutex, &obs->data.named_canvases);
canvas_dosignal(canvas, "canvas_create", NULL);
}
blog(LOG_DEBUG, "%scanvas '%s' (%s) created", private ? "private " : "", canvas->context.name,
canvas->context.uuid);
return canvas;
}
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);
}
obs_canvas_t *obs_canvas_create(const char *name, struct obs_video_info *ovi, uint32_t flags)
{
flags &= ~MAIN; /* Prevent user from creating a MAIN canvas. */
return obs_canvas_create_internal(name, NULL, ovi, flags, false);
}
obs_canvas_t *obs_canvas_create_private(const char *name, struct obs_video_info *ovi, uint32_t flags)
{
flags &= ~MAIN; /* Prevent user from creating a MAIN canvas. */
return obs_canvas_create_internal(name, NULL, ovi, flags, true);
}
void obs_canvas_destroy(obs_canvas_t *canvas)
{
canvas_dosignal(canvas, "canvas_destroy", "destroy");
obs_canvas_clear_mix(canvas);
obs_source_t *source = canvas->sources;
while (source) {
/* Canvases can hold strong refs to scene sources, release them here. */
if (canvas->flags & SCENE_REF && obs_source_is_scene(source))
obs_source_release(source);
source = source->context.hh.next;
}
obs_context_data_remove_uuid(&canvas->context, &obs->data.canvases_mutex, &obs->data.canvases);
if (!canvas->context.private) {
obs_context_data_remove_name(&canvas->context, &obs->data.canvases_mutex, &obs->data.named_canvases);
}
blog(LOG_DEBUG, "%scanvas '%s' (%s) destroyed", canvas->context.private ? "private " : "", canvas->context.name,
canvas->context.uuid);
pthread_mutex_destroy(&canvas->sources_mutex);
obs_context_data_free(&canvas->context);
obs_view_free(&canvas->view);
bfree(canvas);
}
/*** Saving / Loading ***/
obs_data_t *obs_save_canvas(obs_canvas_t *canvas)
{
if (canvas->flags & (EPHEMERAL | REMOVED))
return NULL;
obs_data_t *canvas_data = obs_data_create();
obs_data_set_string(canvas_data, "name", canvas->context.name);
obs_data_set_string(canvas_data, "uuid", canvas->context.uuid);
obs_data_set_bool(canvas_data, "private", canvas->context.private);
obs_data_set_int(canvas_data, "flags", canvas->flags);
return canvas_data;
}
obs_canvas_t *obs_load_canvas(obs_data_t *data)
{
const char *name = obs_data_get_string(data, "name");
const char *uuid = obs_data_get_string(data, "uuid");
const bool private = obs_data_get_bool(data, "private");
uint32_t flags = (uint32_t)obs_data_get_int(data, "flags");
flags &= ~MAIN; /* Prevent user from creating a MAIN canvas. */
return obs_canvas_create_internal(name, uuid, NULL, flags, private);
}
/*** Internal API ***/
/* Free canvas mix (if any) */
void obs_canvas_clear_mix(obs_canvas_t *canvas)
{
if (!canvas->mix)
return;
pthread_mutex_lock(&obs->video.mixes_mutex);
for (size_t i = 0; i < obs->video.mixes.num; i++) {
struct obs_core_video_mix *mix = obs->video.mixes.array[i];
if (mix == canvas->mix) {
da_erase(obs->video.mixes, i);
obs_free_video_mix(mix);
break;
}
}
pthread_mutex_unlock(&obs->video.mixes_mutex);
canvas->mix = NULL;
}
/* Clear mixes attached to canvases */
void obs_free_canvas_mixes(void)
{
pthread_mutex_lock(&obs->data.canvases_mutex);
struct obs_context_data *ctx, *tmp;
HASH_ITER (hh, (struct obs_context_data *)obs->data.canvases, ctx, tmp) {
obs_canvas_t *canvas = (obs_canvas_t *)ctx;
obs_canvas_clear_mix(canvas);
}
pthread_mutex_unlock(&obs->data.canvases_mutex);
}
bool obs_canvas_reset_video_internal(obs_canvas_t *canvas, struct obs_video_info *ovi)
{
obs_canvas_clear_mix(canvas);
if (ovi)
canvas->ovi = *ovi;
canvas->mix = obs_create_video_mix(&canvas->ovi);
if (canvas->mix) {
canvas->mix->view = &canvas->view;
canvas->mix->mix_audio = (canvas->flags & MIX_AUDIO) != 0;
pthread_mutex_lock(&obs->video.mixes_mutex);
da_push_back(obs->video.mixes, &canvas->mix);
pthread_mutex_unlock(&obs->video.mixes_mutex);
}
canvas_dosignal(canvas, "canvas_video_reset", "video_reset");
return !!canvas->mix;
}
void obs_canvas_insert_source(obs_canvas_t *canvas, obs_source_t *source)
{
if (canvas->flags & SCENE_REF && obs_source_is_scene(source))
obs_source_get_ref(source);
if (source->canvas)
obs_canvas_remove_source(source);
source->canvas = obs_canvas_get_weak_canvas(canvas);
obs_context_data_insert_name(&source->context, &canvas->sources_mutex, &canvas->sources);
canvas_dosignal_source("source_add", canvas, source);
}
static bool remove_groups_items_cb(obs_scene_t *scene, obs_sceneitem_t *item, void *param)
{
UNUSED_PARAMETER(scene);
obs_source_t *source = param;
if (item->source == source)
obs_sceneitem_remove(item);
return true;
}
static bool remove_groups_enum_cb(void *param, obs_source_t *scene_source)
{
obs_source_t *source = param;
obs_scene_t *scene = obs_scene_from_source(scene_source);
obs_scene_enum_items(scene, remove_groups_items_cb, source);
return true;
}
void obs_canvas_remove_source(obs_source_t *source)
{
obs_canvas_t *canvas = obs_weak_canvas_get_canvas(source->canvas);
if (canvas) {
obs_weak_canvas_release(source->canvas);
obs_context_data_remove_name(&source->context, &canvas->sources_mutex, &canvas->sources);
canvas_dosignal_source("source_remove", canvas, source);
if (canvas->flags & SCENE_REF && obs_source_is_scene(source))
obs_source_release(source);
/* If source is a group, also remove it from all other scenes in the old canvas */
if (obs_source_is_group(source))
obs_canvas_enum_scenes(canvas, remove_groups_enum_cb, source);
obs_canvas_release(canvas);
}
source->canvas = NULL;
}
/*** Public Canvas Object API ***/
bool obs_canvas_reset_video(obs_canvas_t *canvas, struct obs_video_info *ovi)
{
if (canvas->flags & MAIN || obs_video_active())
return false;
return obs_canvas_reset_video_internal(canvas, ovi);
}
video_t *obs_canvas_get_video(const obs_canvas_t *canvas)
{
return canvas->mix ? canvas->mix->video : NULL;
}
bool obs_canvas_get_video_info(const obs_canvas_t *canvas, struct obs_video_info *ovi)
{
if (!obs->video.graphics || !canvas->mix)
return false;
*ovi = canvas->ovi;
return true;
}
void obs_canvas_set_channel(obs_canvas_t *canvas, uint32_t channel, obs_source_t *source)
{
assert(channel < MAX_CHANNELS);
if (channel >= MAX_CHANNELS)
return;
struct obs_view *view = &canvas->view;
pthread_mutex_lock(&view->channels_mutex);
source = obs_source_get_ref(source);
obs_source_t *prev_source = view->channels[channel];
if (source == prev_source) {
obs_source_release(source);
pthread_mutex_unlock(&view->channels_mutex);
return;
}
struct calldata params = {0};
calldata_set_ptr(&params, "canvas", canvas);
calldata_set_int(&params, "channel", channel);
calldata_set_ptr(&params, "prev_source", prev_source);
calldata_set_ptr(&params, "source", source);
signal_handler_signal(canvas->context.signals, "channel_change", &params);
if (canvas->flags & MAIN)
signal_handler_signal(obs->signals, "channel_change", &params);
/* For some reason the original implementation allows overriding the source from the callback,
* so just in case support that here as well. This isn't used anywhere in OBS itself. */
calldata_get_ptr(&params, "source", &source);
view->channels[channel] = source;
calldata_free(&params);
pthread_mutex_unlock(&view->channels_mutex);
if (source)
obs_source_activate(source, view->type);
if (prev_source) {
obs_source_deactivate(prev_source, view->type);
obs_source_release(prev_source);
}
}
obs_source_t *obs_canvas_get_channel(obs_canvas_t *canvas, uint32_t channel)
{
return obs_view_get_source(&canvas->view, channel);
}
obs_scene_t *obs_canvas_scene_create(obs_canvas_t *canvas, const char *name)
{
struct obs_source *source = obs_source_create_canvas(canvas, "scene", name, NULL, NULL);
return source->context.data;
}
void obs_canvas_scene_remove(obs_scene_t *scene)
{
obs_canvas_remove_source(scene->source);
}
void obs_canvas_set_name(obs_canvas_t *canvas, const char *name)
{
if (!name || !*name)
return;
if (canvas->flags & MAIN) /* Do not allow renaming main canvases. */
return;
if (strcmp(name, canvas->context.name) == 0)
return;
char *prev_name = bstrdup(canvas->context.name);
if (canvas->context.private)
obs_context_data_setname(&canvas->context, name);
else
obs_context_data_setname_ht(&canvas->context, name, &obs->data.named_canvases);
struct calldata data;
calldata_init(&data);
calldata_set_ptr(&data, "canvas", canvas);
calldata_set_string(&data, "new_name", canvas->context.name);
calldata_set_string(&data, "prev_name", prev_name);
signal_handler_signal(canvas->context.signals, "rename", &data);
if (!canvas->context.private)
signal_handler_signal(obs->signals, "canvas_rename", &data);
calldata_free(&data);
bfree(prev_name);
}
const char *obs_canvas_get_name(const obs_canvas_t *canvas)
{
return canvas->context.name;
}
const char *obs_canvas_get_uuid(const obs_canvas_t *canvas)
{
return canvas->context.uuid;
}
uint32_t obs_canvas_get_flags(const obs_canvas_t *canvas)
{
return canvas->flags;
}
static bool enum_move_cb(obs_scene_t *scene, obs_sceneitem_t *item, void *param)
{
UNUSED_PARAMETER(scene);
obs_canvas_t *dst = param;
obs_source_t *source = item->source;
if (obs_source_is_group(source)) {
obs_canvas_remove_source(source);
obs_canvas_insert_source(dst, source);
}
return true;
}
void obs_canvas_move_scene(obs_scene_t *scene, obs_canvas_t *dst)
{
obs_source_t *source = scene->source;
obs_canvas_remove_source(source);
obs_canvas_insert_source(dst, source);
/* Also move all groups within this scene */
obs_scene_enum_items(scene, enum_move_cb, dst);
}
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;
obs_canvas_t *c = obs_canvas_get_ref(canvas);
if (c) {
c->flags |= REMOVED;
canvas_dosignal(c, "canvas_remove", "remove");
obs_canvas_release(c);
}
}
bool obs_canvas_removed(obs_canvas_t *canvas)
{
return (canvas->flags & REMOVED) != 0;
}
bool obs_canvas_has_video(obs_canvas_t *canvas)
{
return canvas->mix != NULL;
}
void obs_canvas_render(obs_canvas_t *canvas)
{
obs_view_render(&canvas->view);
}

View file

@ -325,6 +325,8 @@ struct obs_core_video_mix {
bool encoder_only_mix;
long encoder_refs;
bool mix_audio;
};
extern struct obs_core_video_mix *obs_create_video_mix(struct obs_video_info *ovi);
@ -377,7 +379,6 @@ struct obs_core_video {
pthread_mutex_t mixes_mutex;
DARRAY(struct obs_core_video_mix *) mixes;
struct obs_core_video_mix *main_mix;
};
extern void add_ready_encoder_group(obs_encoder_t *encoder);
@ -412,6 +413,9 @@ struct obs_core_data {
struct obs_source *sources; /* Lookup by UUID (hh_uuid) */
struct obs_source *public_sources; /* Lookup by name (hh) */
struct obs_canvas *canvases; /* Lookup by UUID (hh_uuid) */
struct obs_canvas *named_canvases; /* Lookup by name (hh) */
/* Linked lists */
struct obs_source *first_audio_source;
struct obs_display *first_display;
@ -426,11 +430,13 @@ struct obs_core_data {
pthread_mutex_t services_mutex;
pthread_mutex_t audio_sources_mutex;
pthread_mutex_t draw_callbacks_mutex;
pthread_mutex_t canvases_mutex;
DARRAY(struct draw_callback) draw_callbacks;
DARRAY(struct rendered_callback) rendered_callbacks;
DARRAY(struct tick_callback) tick_callbacks;
struct obs_view main_view;
/* Main canvas, guaranteed to exist for the lifetime of the program */
struct obs_canvas *main_canvas;
long long unnamed_index;
@ -595,8 +601,8 @@ extern void obs_context_data_insert_name(struct obs_context_data *context, pthre
extern void obs_context_data_insert_uuid(struct obs_context_data *context, pthread_mutex_t *mutex, void *first_uuid);
extern void obs_context_data_remove(struct obs_context_data *context);
extern void obs_context_data_remove_name(struct obs_context_data *context, void *phead);
extern void obs_context_data_remove_uuid(struct obs_context_data *context, void *puuid_head);
extern void obs_context_data_remove_name(struct obs_context_data *context, pthread_mutex_t *mutex, void *phead);
extern void obs_context_data_remove_uuid(struct obs_context_data *context, pthread_mutex_t *mutex, void *puuid_head);
extern void obs_context_wait(struct obs_context_data *context);
@ -644,6 +650,40 @@ static inline bool obs_weak_ref_expired(struct obs_weak_ref *ref)
return owners < 0;
}
/* ------------------------------------------------------------------------- */
/* canvases */
struct obs_weak_canvas {
struct obs_weak_ref ref;
struct obs_canvas *canvas;
};
struct obs_canvas {
struct obs_context_data context;
/* obs_canvas_flags */
uint32_t flags;
/* Video info for this canvas, FPS ignored */
struct obs_video_info ovi;
/* Hash table containing scenes (and groups) associated with this canvas */
struct obs_source *sources;
pthread_mutex_t sources_mutex;
/* For now, canvas objects mainly act as a proxy for the existing view and video mix objects,
* though this may change in the future. */
struct obs_view view;
struct obs_core_video_mix *mix;
};
extern obs_canvas_t *obs_create_main_canvas(void);
extern void obs_canvas_destroy(obs_canvas_t *canvas);
extern void obs_canvas_clear_mix(obs_canvas_t *canvas);
extern void obs_free_canvas_mixes(void);
extern bool obs_canvas_reset_video_internal(obs_canvas_t *canvas, struct obs_video_info *ovi);
extern void obs_canvas_insert_source(obs_canvas_t *canvas, obs_source_t *source);
extern void obs_canvas_remove_source(obs_source_t *source);
/* ------------------------------------------------------------------------- */
/* sources */
@ -894,6 +934,9 @@ struct obs_source {
/* private data */
obs_data_t *private_settings;
/* canvas this source belongs to (only used for scenes) */
obs_weak_canvas_t *canvas;
};
extern struct obs_source_info *get_source_info(const char *id);
@ -912,9 +955,12 @@ struct audio_monitor *audio_monitor_create(obs_source_t *source);
void audio_monitor_reset(struct audio_monitor *monitor);
extern void audio_monitor_destroy(struct audio_monitor *monitor);
extern obs_source_t *obs_source_create_set_last_ver(const char *id, const char *name, const char *uuid,
obs_data_t *settings, obs_data_t *hotkey_data,
extern obs_source_t *obs_source_create_canvas(obs_canvas_t *canvas, const char *id, const char *name,
obs_data_t *settings, obs_data_t *hotkey_data);
extern obs_source_t *obs_source_create_set_last_ver(obs_canvas_t *canvas, const char *id, const char *name,
const char *uuid, obs_data_t *settings, obs_data_t *hotkey_data,
uint32_t last_obs_ver, bool is_private);
extern void obs_source_destroy(struct obs_source *source);
extern void obs_source_addref(obs_source_t *source);
@ -931,6 +977,21 @@ static inline void obs_source_dosignal(struct obs_source *source, const char *si
signal_handler_signal(source->context.signals, signal_source, &data);
}
static inline void obs_source_dosignal_canvas(struct obs_source *source, struct obs_canvas *canvas,
const char *signal_obs, const char *signal_source)
{
struct calldata data;
uint8_t stack[128];
calldata_init_fixed(&data, stack, sizeof(stack));
calldata_set_ptr(&data, "source", source);
calldata_set_ptr(&data, "canvas", canvas);
if (signal_obs && !source->context.private)
signal_handler_signal(obs->signals, signal_obs, &data);
if (signal_source)
signal_handler_signal(source->context.signals, signal_source, &data);
}
/* maximum timestamp variance in nanoseconds */
#define MAX_TS_VAR 2000000000ULL

View file

@ -338,13 +338,18 @@ void add_alignment(struct vec2 *v, uint32_t align, int cx, int cy)
static uint32_t scene_getwidth(void *data);
static uint32_t scene_getheight(void *data);
static uint32_t canvas_getwidth(obs_weak_canvas_t *weak);
static uint32_t canvas_getheight(obs_weak_canvas_t *weak);
static inline void get_scene_dimensions(const obs_sceneitem_t *item, float *x, float *y)
{
obs_scene_t *parent = item->parent;
if (!parent || parent->is_group) {
*x = (float)obs->video.main_mix->ovi.base_width;
*y = (float)obs->video.main_mix->ovi.base_height;
if (!parent) {
*x = (float)obs->data.main_canvas->mix->ovi.base_width;
*y = (float)obs->data.main_canvas->mix->ovi.base_height;
} else if (parent->is_group) {
*x = (float)canvas_getwidth(parent->source->canvas);
*y = (float)canvas_getheight(parent->source->canvas);
} else {
*x = (float)scene_getwidth(parent);
*y = (float)scene_getheight(parent);
@ -1455,13 +1460,41 @@ static void scene_save(void *data, obs_data_t *settings)
obs_data_array_release(array);
}
static uint32_t canvas_getwidth(obs_weak_canvas_t *weak)
{
uint32_t width = 0;
obs_canvas_t *canvas = obs_weak_canvas_get_canvas(weak);
if (canvas) {
width = canvas->ovi.base_width;
obs_canvas_release(canvas);
}
return width;
}
static uint32_t canvas_getheight(obs_weak_canvas_t *weak)
{
uint32_t height = 0;
obs_canvas_t *canvas = obs_weak_canvas_get_canvas(weak);
if (canvas) {
height = canvas->ovi.base_height;
obs_canvas_release(canvas);
}
return height;
}
static uint32_t scene_getwidth(void *data)
{
obs_scene_t *scene = data;
if (scene->custom_size)
return scene->cx;
if (obs->video.main_mix)
return obs->video.main_mix->ovi.base_width;
if (scene->source->canvas)
return canvas_getwidth(scene->source->canvas);
if (obs->data.main_canvas->mix)
return obs->data.main_canvas->mix->ovi.base_width;
return 0;
}
@ -1470,8 +1503,10 @@ static uint32_t scene_getheight(void *data)
obs_scene_t *scene = data;
if (scene->custom_size)
return scene->cy;
if (obs->video.main_mix)
return obs->video.main_mix->ovi.base_height;
if (scene->source->canvas)
return canvas_getheight(scene->source->canvas);
if (obs->data.main_canvas->mix)
return obs->data.main_canvas->mix->ovi.base_height;
return 0;
}
@ -1793,7 +1828,7 @@ const struct obs_source_info scene_info = {
.id = "scene",
.type = OBS_SOURCE_TYPE_SCENE,
.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW | OBS_SOURCE_COMPOSITE | OBS_SOURCE_DO_NOT_DUPLICATE |
OBS_SOURCE_SRGB,
OBS_SOURCE_SRGB | OBS_SOURCE_REQUIRES_CANVAS,
.get_name = scene_getname,
.create = scene_create,
.destroy = scene_destroy,
@ -1812,7 +1847,8 @@ const struct obs_source_info scene_info = {
const struct obs_source_info group_info = {
.id = "group",
.type = OBS_SOURCE_TYPE_SCENE,
.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW | OBS_SOURCE_COMPOSITE | OBS_SOURCE_SRGB,
.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW | OBS_SOURCE_COMPOSITE | OBS_SOURCE_SRGB |
OBS_SOURCE_REQUIRES_CANVAS,
.get_name = group_getname,
.create = scene_create,
.destroy = scene_destroy,
@ -1828,9 +1864,9 @@ const struct obs_source_info group_info = {
.video_get_color_space = scene_video_get_color_space,
};
static inline obs_scene_t *create_id(const char *id, const char *name)
static inline obs_scene_t *create_id(obs_canvas_t *canvas, const char *id, const char *name)
{
struct obs_source *source = obs_source_create(id, name, NULL, NULL);
struct obs_source *source = obs_source_create_canvas(canvas, id, name, NULL, NULL);
return source->context.data;
}
@ -1842,7 +1878,7 @@ static inline obs_scene_t *create_private_id(const char *id, const char *name)
obs_scene_t *obs_scene_create(const char *name)
{
return create_id("scene", name);
return create_id(obs->data.main_canvas, "scene", name);
}
obs_scene_t *obs_scene_create_private(const char *name)
@ -1987,8 +2023,10 @@ obs_scene_t *obs_scene_duplicate(obs_scene_t *scene, const char *name, enum obs_
/* --------------------------------- */
obs_canvas_t *canvas = obs_weak_canvas_get_canvas(scene->source->canvas);
new_scene = make_private ? create_private_id(scene->source->info.id, name)
: create_id(scene->source->info.id, name);
: create_id(canvas, scene->source->info.id, name);
obs_canvas_release(canvas);
new_scene->is_group = scene->is_group;
new_scene->custom_size = scene->custom_size;
@ -3630,9 +3668,11 @@ obs_sceneitem_t *obs_scene_insert_group(obs_scene_t *scene, const char *name, ob
return NULL;
}
obs_scene_t *sub_scene = create_id("group", name);
obs_sceneitem_t *last_item = items ? items[count - 1] : NULL;
obs_canvas_t *canvas = obs_weak_canvas_get_canvas(scene->source->canvas);
obs_scene_t *sub_scene = create_id(canvas, group_info.id, name);
obs_canvas_release(canvas);
obs_sceneitem_t *last_item = items ? items[count - 1] : NULL;
obs_sceneitem_t *item = obs_scene_add_internal(scene, sub_scene->source, last_item, 0);
if (!items || !count) {

View file

@ -164,6 +164,11 @@ static inline bool is_composite_source(const struct obs_source *source)
return source->info.output_flags & OBS_SOURCE_COMPOSITE;
}
static inline bool requires_canvas(const struct obs_source *source)
{
return source->info.output_flags & OBS_SOURCE_REQUIRES_CANVAS;
}
extern char *find_libobs_data_file(const char *file);
/* internal initialization */
@ -218,7 +223,7 @@ static bool obs_source_init(struct obs_source *source)
return true;
}
static void obs_source_init_finalize(struct obs_source *source)
static void obs_source_init_finalize(struct obs_source *source, obs_canvas_t *canvas)
{
if (is_audio_source(source)) {
pthread_mutex_lock(&obs->data.audio_sources_mutex);
@ -233,7 +238,12 @@ static void obs_source_init_finalize(struct obs_source *source)
}
if (!source->context.private) {
obs_context_data_insert_name(&source->context, &obs->data.sources_mutex, &obs->data.public_sources);
if (requires_canvas(source)) {
obs_canvas_insert_source(canvas, source);
} else {
obs_context_data_insert_name(&source->context, &obs->data.sources_mutex,
&obs->data.public_sources);
}
}
obs_context_data_insert_uuid(&source->context, &obs->data.sources_mutex, &obs->data.sources);
}
@ -320,7 +330,7 @@ static void obs_source_init_audio_hotkeys(struct obs_source *source)
static obs_source_t *obs_source_create_internal(const char *id, const char *name, const char *uuid,
obs_data_t *settings, obs_data_t *hotkey_data, bool private,
uint32_t last_obs_ver)
uint32_t last_obs_ver, obs_canvas_t *canvas)
{
struct obs_source *source = bzalloc(sizeof(struct obs_source));
@ -362,6 +372,12 @@ static obs_source_t *obs_source_create_internal(const char *id, const char *name
if (!obs_source_init(source))
goto fail;
/* Scenes need canvases, fall back to using default canvas if none provided here. */
if (requires_canvas(source) && !canvas) {
blog(LOG_WARNING, "Attempted to add Scene without specifying a canvas! Using default canvas instead.");
canvas = obs->data.main_canvas;
}
if (!private)
obs_source_init_audio_hotkeys(source);
@ -377,9 +393,12 @@ static obs_source_t *obs_source_create_internal(const char *id, const char *name
source->flags = source->default_flags;
source->enabled = true;
obs_source_init_finalize(source);
obs_source_init_finalize(source, canvas);
if (!private) {
obs_source_dosignal(source, "source_create", NULL);
if (canvas)
obs_source_dosignal_canvas(source, canvas, "source_create_canvas", NULL);
if (!canvas || canvas == obs->data.main_canvas)
obs_source_dosignal(source, "source_create", NULL);
}
return source;
@ -392,18 +411,25 @@ fail:
obs_source_t *obs_source_create(const char *id, const char *name, obs_data_t *settings, obs_data_t *hotkey_data)
{
return obs_source_create_internal(id, name, NULL, settings, hotkey_data, false, LIBOBS_API_VER);
return obs_source_create_internal(id, name, NULL, settings, hotkey_data, false, LIBOBS_API_VER, NULL);
}
obs_source_t *obs_source_create_private(const char *id, const char *name, obs_data_t *settings)
{
return obs_source_create_internal(id, name, NULL, settings, NULL, true, LIBOBS_API_VER);
return obs_source_create_internal(id, name, NULL, settings, NULL, true, LIBOBS_API_VER, NULL);
}
obs_source_t *obs_source_create_set_last_ver(const char *id, const char *name, const char *uuid, obs_data_t *settings,
obs_data_t *hotkey_data, uint32_t last_obs_ver, bool is_private)
obs_source_t *obs_source_create_canvas(obs_canvas_t *canvas, const char *id, const char *name, obs_data_t *settings,
obs_data_t *hotkey_data)
{
return obs_source_create_internal(id, name, uuid, settings, hotkey_data, is_private, last_obs_ver);
return obs_source_create_internal(id, name, NULL, settings, hotkey_data, false, LIBOBS_API_VER, canvas);
}
obs_source_t *obs_source_create_set_last_ver(obs_canvas_t *canvas, const char *id, const char *name, const char *uuid,
obs_data_t *settings, obs_data_t *hotkey_data, uint32_t last_obs_ver,
bool is_private)
{
return obs_source_create_internal(id, name, uuid, settings, hotkey_data, is_private, last_obs_ver, canvas);
}
static char *get_new_filter_name(obs_source_t *dst, const char *name)
@ -612,9 +638,15 @@ void obs_source_destroy(struct obs_source *source)
while (source->filters.num)
obs_source_filter_remove(source, source->filters.array[0]);
obs_context_data_remove_uuid(&source->context, &obs->data.sources);
if (!source->context.private)
obs_context_data_remove_name(&source->context, &obs->data.public_sources);
obs_context_data_remove_uuid(&source->context, &obs->data.sources_mutex, &obs->data.sources);
if (!source->context.private) {
if (requires_canvas(source)) {
obs_canvas_remove_source(source);
} else {
obs_context_data_remove_name(&source->context, &obs->data.sources_mutex,
&obs->data.public_sources);
}
}
source_profiler_remove_source(source);
@ -794,6 +826,10 @@ void obs_source_remove(obs_source_t *source)
if (s) {
s->removed = true;
obs_source_dosignal(s, "source_remove", "remove");
/* Remove from canvas if there is one. */
if (source->canvas)
obs_canvas_remove_source(s);
obs_source_release(s);
}
}
@ -5829,3 +5865,8 @@ uint64_t obs_source_get_last_async_ts(const obs_source_t *source)
{
return source->async_last_rendered_ts;
}
obs_canvas_t *obs_source_get_canvas(const obs_source_t *source)
{
return obs_weak_canvas_get_canvas(source->canvas);
}

View file

@ -203,6 +203,11 @@ enum obs_media_state {
*/
#define OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES (1 << 16)
/**
* Source requires a canvas to operate
*/
#define OBS_SOURCE_REQUIRES_CANVAS (1 << 17)
/** @} */
typedef void (*obs_source_enum_proc_t)(obs_source_t *parent, obs_source_t *child, void *param);

View file

@ -150,21 +150,11 @@ static inline size_t find_mix_for_view(obs_view_t *view)
return DARRAY_INVALID;
}
static inline void set_main_mix()
{
size_t idx = find_mix_for_view(&obs->data.main_view);
struct obs_core_video_mix *mix = NULL;
if (idx != DARRAY_INVALID)
mix = obs->video.mixes.array[idx];
obs->video.main_mix = mix;
}
video_t *obs_view_add(obs_view_t *view)
{
if (!obs->video.main_mix)
if (!obs->data.main_canvas->mix)
return NULL;
return obs_view_add2(view, &obs->video.main_mix->ovi);
return obs_view_add2(view, &obs->data.main_canvas->mix->ovi);
}
video_t *obs_view_add2(obs_view_t *view, struct obs_video_info *ovi)
@ -180,7 +170,6 @@ video_t *obs_view_add2(obs_view_t *view, struct obs_video_info *ovi)
pthread_mutex_lock(&obs->video.mixes_mutex);
da_push_back(obs->video.mixes, &mix);
set_main_mix();
pthread_mutex_unlock(&obs->video.mixes_mutex);
return mix->video;
@ -196,7 +185,6 @@ void obs_view_remove(obs_view_t *view)
if (obs->video.mixes.array[i]->view == view)
obs->video.mixes.array[i]->view = NULL;
}
set_main_mix();
pthread_mutex_unlock(&obs->video.mixes_mutex);
}

View file

@ -615,8 +615,8 @@ static int obs_init_video_mix(struct obs_video_info *ovi, struct obs_core_video_
* so share FPS settings for aux views */
pthread_mutex_lock(&obs->video.mixes_mutex);
size_t num = obs->video.mixes.num;
if (num && obs->video.main_mix) {
struct obs_video_info main_ovi = obs->video.main_mix->ovi;
if (num && obs->data.main_canvas->mix) {
struct obs_video_info main_ovi = obs->data.main_canvas->mix->ovi;
video->ovi.fps_num = main_ovi.fps_num;
video->ovi.fps_den = main_ovi.fps_den;
}
@ -665,6 +665,27 @@ struct obs_core_video_mix *obs_create_video_mix(struct obs_video_info *ovi)
return video;
}
static bool restore_canvases(void)
{
bool success = true;
pthread_mutex_lock(&obs->data.canvases_mutex);
struct obs_context_data *ctx, *tmp;
HASH_ITER (hh, (struct obs_context_data *)obs->data.canvases, ctx, tmp) {
obs_canvas_t *canvas = (obs_canvas_t *)ctx;
if (canvas->flags & MAIN)
continue;
if (!obs_canvas_reset_video_internal(canvas, NULL)) {
blog(LOG_ERROR, "Failed restoring video mix for canvas '%s'", canvas->context.name);
success = false;
}
}
pthread_mutex_unlock(&obs->data.canvases_mutex);
return success;
}
static int obs_init_video(struct obs_video_info *ovi)
{
struct obs_core_video *video = &obs->video;
@ -678,7 +699,11 @@ static int obs_init_video(struct obs_video_info *ovi)
if (pthread_mutex_init(&video->mixes_mutex, NULL) < 0)
return OBS_VIDEO_FAIL;
if (!obs_view_add2(&obs->data.main_view, ovi))
/* Reset main canvas mix first so it remains first in the rendering order. */
if (!obs_canvas_reset_video_internal(obs->data.main_canvas, ovi))
return OBS_VIDEO_FAIL;
/* Reset mixes for remaining canvases using their existing video info. */
if (!restore_canvases())
return OBS_VIDEO_FAIL;
int errorcode;
@ -939,12 +964,13 @@ static bool obs_init_data(void)
goto fail;
if (pthread_mutex_init_recursive(&obs->data.draw_callbacks_mutex) != 0)
goto fail;
if (!obs_view_init(&data->main_view))
if (pthread_mutex_init_recursive(&obs->data.canvases_mutex) != 0)
goto fail;
data->sources = NULL;
data->public_sources = NULL;
data->canvases = NULL;
data->named_canvases = NULL;
data->private_data = obs_data_create();
data->valid = true;
@ -993,11 +1019,11 @@ static void obs_free_data(void)
data->valid = false;
obs_view_remove(&data->main_view);
obs_main_view_free(&data->main_view);
blog(LOG_INFO, "Freeing OBS context data");
/* Free main canvas */
obs_canvas_release(data->main_canvas);
FREE_OBS_LINKED_LIST(output);
FREE_OBS_LINKED_LIST(encoder);
FREE_OBS_LINKED_LIST(display);
@ -1005,6 +1031,8 @@ static void obs_free_data(void)
FREE_OBS_HASH_TABLE(hh, &data->public_sources, source);
FREE_OBS_HASH_TABLE(hh_uuid, &data->sources, source);
FREE_OBS_HASH_TABLE(hh, &data->named_canvases, canvas);
FREE_OBS_HASH_TABLE(hh_uuid, &data->canvases, canvas);
os_task_queue_wait(obs->destruction_task_thread);
@ -1015,6 +1043,7 @@ static void obs_free_data(void)
pthread_mutex_destroy(&data->encoders_mutex);
pthread_mutex_destroy(&data->services_mutex);
pthread_mutex_destroy(&data->draw_callbacks_mutex);
pthread_mutex_destroy(&data->canvases_mutex);
da_free(data->draw_callbacks);
da_free(data->rendered_callbacks);
da_free(data->tick_callbacks);
@ -1028,6 +1057,7 @@ static void obs_free_data(void)
static const char *obs_signals[] = {
"void source_create(ptr source)",
"void source_create_canvas(ptr source, ptr canvas)",
"void source_destroy(ptr source)",
"void source_remove(ptr source)",
"void source_update(ptr source)",
@ -1043,8 +1073,7 @@ static const char *obs_signals[] = {
"void source_filter_remove(ptr source, ptr filter)",
"void source_rename(ptr source, string new_name, string prev_name)",
"void source_volume(ptr source, in out float volume)",
"void source_volume_level(ptr source, float level, float magnitude, "
"float peak)",
"void source_volume_level(ptr source, float level, float magnitude, float peak)",
"void source_transition_start(ptr source)",
"void source_transition_video_stop(ptr source)",
"void source_transition_stop(ptr source)",
@ -1056,6 +1085,12 @@ static const char *obs_signals[] = {
"void hotkey_unregister(ptr hotkey)",
"void hotkey_bindings_changed(ptr hotkey)",
"void canvas_create(ptr canvas)",
"void canvas_remove(ptr canvas)",
"void canvas_destroy(ptr canvas)",
"void canvas_video_reset(ptr canvas)",
"void canvas_rename(ptr canvas, string new_name, string prev_name)",
"void video_reset()",
NULL,
@ -1189,6 +1224,11 @@ static bool obs_init(const char *locale, const char *module_config_path, profile
if (!obs_init_hotkeys())
return false;
/* Create persistent main canvas. */
obs->data.main_canvas = obs_create_main_canvas();
if (!obs->data.main_canvas)
return false;
obs->destruction_task_thread = os_task_queue_create();
if (!obs->destruction_task_thread)
return false;
@ -1453,6 +1493,7 @@ int obs_reset_video(struct obs_video_info *ovi)
return OBS_VIDEO_INVALID_PARAM;
stop_video();
obs_free_canvas_mixes();
obs_free_video();
/* align to multiple-of-two and SSE alignment sizes */
@ -1571,10 +1612,10 @@ bool obs_reset_audio(const struct obs_audio_info *oai)
bool obs_get_video_info(struct obs_video_info *ovi)
{
if (!obs->video.graphics || !obs->video.main_mix)
if (!obs->video.graphics || !obs->data.main_canvas->mix)
return false;
*ovi = obs->video.main_mix->ovi;
*ovi = obs->data.main_canvas->mix->ovi;
return true;
}
@ -1740,49 +1781,17 @@ audio_t *obs_get_audio(void)
video_t *obs_get_video(void)
{
return obs->video.main_mix->video;
return obs->data.main_canvas->mix->video;
}
obs_source_t *obs_get_output_source(uint32_t channel)
{
return obs_view_get_source(&obs->data.main_view, channel);
return obs_canvas_get_channel(obs->data.main_canvas, channel);
}
void obs_set_output_source(uint32_t channel, obs_source_t *source)
{
assert(channel < MAX_CHANNELS);
if (channel >= MAX_CHANNELS)
return;
struct obs_source *prev_source;
struct obs_view *view = &obs->data.main_view;
struct calldata params = {0};
pthread_mutex_lock(&view->channels_mutex);
source = obs_source_get_ref(source);
prev_source = view->channels[channel];
calldata_set_int(&params, "channel", channel);
calldata_set_ptr(&params, "prev_source", prev_source);
calldata_set_ptr(&params, "source", source);
signal_handler_signal(obs->signals, "channel_change", &params);
calldata_get_ptr(&params, "source", &source);
calldata_free(&params);
view->channels[channel] = source;
pthread_mutex_unlock(&view->channels_mutex);
if (source)
obs_source_activate(source, MAIN_VIEW);
if (prev_source) {
obs_source_deactivate(prev_source, MAIN_VIEW);
obs_source_release(prev_source);
}
obs_canvas_set_channel(obs->data.main_canvas, channel, source);
}
void obs_enum_sources(bool (*enum_proc)(void *, obs_source_t *), void *param)
@ -1790,38 +1799,40 @@ void obs_enum_sources(bool (*enum_proc)(void *, obs_source_t *), void *param)
obs_source_t *source;
pthread_mutex_lock(&obs->data.sources_mutex);
source = obs->data.public_sources;
source = obs->data.sources;
while (source) {
obs_source_t *s = obs_source_get_ref(source);
if (s) {
if (s->info.type == OBS_SOURCE_TYPE_INPUT && !enum_proc(param, s)) {
obs_source_release(s);
break;
} else if (strcmp(s->info.id, group_info.id) == 0 && !enum_proc(param, s)) {
obs_source_release(s);
break;
if (!s->context.private) {
if (s->info.type == OBS_SOURCE_TYPE_INPUT && !enum_proc(param, s)) {
obs_source_release(s);
break;
} else if (strcmp(s->info.id, group_info.id) == 0 && !enum_proc(param, s)) {
obs_source_release(s);
break;
}
}
obs_source_release(s);
}
source = (obs_source_t *)source->context.hh.next;
source = (obs_source_t *)source->context.hh_uuid.next;
}
pthread_mutex_unlock(&obs->data.sources_mutex);
}
void obs_enum_scenes(bool (*enum_proc)(void *, obs_source_t *), void *param)
void obs_canvas_enum_scenes(obs_canvas_t *canvas, bool (*enum_proc)(void *, obs_source_t *), void *param)
{
obs_source_t *source;
pthread_mutex_lock(&obs->data.sources_mutex);
pthread_mutex_lock(&canvas->sources_mutex);
source = obs->data.public_sources;
source = canvas->sources;
while (source) {
obs_source_t *s = obs_source_get_ref(source);
if (s) {
if (source->info.type == OBS_SOURCE_TYPE_SCENE && !enum_proc(param, s)) {
if (obs_source_is_scene(source) && !enum_proc(param, s)) {
obs_source_release(s);
break;
}
@ -1831,7 +1842,12 @@ void obs_enum_scenes(bool (*enum_proc)(void *, obs_source_t *), void *param)
source = (obs_source_t *)source->context.hh.next;
}
pthread_mutex_unlock(&obs->data.sources_mutex);
pthread_mutex_unlock(&canvas->sources_mutex);
}
void obs_enum_scenes(bool (*enum_proc)(void *, obs_source_t *), void *param)
{
obs_canvas_enum_scenes(obs->data.main_canvas, enum_proc, param);
}
static inline void obs_enum(void *pstart, pthread_mutex_t *mutex, void *proc, void *param)
@ -1895,6 +1911,22 @@ void obs_enum_services(bool (*enum_proc)(void *, obs_service_t *), void *param)
obs_enum(&obs->data.first_service, &obs->data.services_mutex, enum_proc, param);
}
void obs_enum_canvases(bool (*enum_proc)(void *, obs_canvas_t *), void *param)
{
struct obs_context_data *start = (struct obs_context_data *)obs->data.named_canvases;
struct obs_context_data *context, *tmp;
pthread_mutex_lock(&obs->data.canvases_mutex);
HASH_ITER (hh, start, context, tmp) {
obs_canvas_t *canvas = (obs_canvas_t *)context;
if (!enum_proc(param, canvas))
break;
}
pthread_mutex_unlock(&obs->data.canvases_mutex);
}
static inline void *get_context_by_name(void *vfirst, const char *name, pthread_mutex_t *mutex, void *(*addref)(void *))
{
struct obs_context_data **first = vfirst;
@ -1958,9 +1990,21 @@ static inline void *obs_service_addref_safe_(void *ref)
return obs_service_get_ref(ref);
}
static inline void *obs_canvas_addref_safe_(void *ref)
{
return obs_canvas_get_ref(ref);
}
obs_source_t *obs_get_source_by_name(const char *name)
{
return get_context_by_name(&obs->data.public_sources, name, &obs->data.sources_mutex, obs_source_addref_safe_);
obs_source_t *source =
get_context_by_name(&obs->data.public_sources, name, &obs->data.sources_mutex, obs_source_addref_safe_);
/* For backwards compat: Also look up source name in main canvas's scenes list. */
if (!source) {
source = get_context_by_name(&obs->data.main_canvas->sources, name,
&obs->data.main_canvas->sources_mutex, obs_source_addref_safe_);
}
return source;
}
obs_source_t *obs_get_source_by_uuid(const char *uuid)
@ -1968,6 +2012,32 @@ obs_source_t *obs_get_source_by_uuid(const char *uuid)
return get_context_by_uuid(&obs->data.sources, uuid, &obs->data.sources_mutex, obs_source_addref_safe_);
}
obs_canvas_t *obs_get_canvas_by_name(const char *name)
{
return get_context_by_name(&obs->data.named_canvases, name, &obs->data.canvases_mutex, obs_canvas_addref_safe_);
}
obs_canvas_t *obs_get_canvas_by_uuid(const char *uuid)
{
return get_context_by_uuid(&obs->data.canvases, uuid, &obs->data.canvases_mutex, obs_canvas_addref_safe_);
}
obs_source_t *obs_canvas_get_source_by_name(obs_canvas_t *canvas, const char *name)
{
return get_context_by_name(&canvas->sources, name, &canvas->sources_mutex, obs_source_addref_safe_);
}
obs_scene_t *obs_canvas_get_scene_by_name(obs_canvas_t *canvas, const char *name)
{
obs_source_t *source = obs_canvas_get_source_by_name(canvas, name);
obs_scene_t *scene = obs_scene_from_source(source);
if (!scene) {
obs_source_release(source);
return NULL;
}
return scene;
}
obs_source_t *obs_get_transition_by_name(const char *name)
{
struct obs_source **first = &obs->data.sources;
@ -2055,16 +2125,17 @@ proc_handler_t *obs_get_proc_handler(void)
return obs->procs;
}
static void obs_render_main_texture_internal(enum gs_blend_type src_c, enum gs_blend_type dest_c,
enum gs_blend_type src_a, enum gs_blend_type dest_a)
static void obs_render_canvas_texture_internal(obs_canvas_t *canvas, enum gs_blend_type src_c,
enum gs_blend_type dest_c, enum gs_blend_type src_a,
enum gs_blend_type dest_a)
{
struct obs_core_video_mix *video;
gs_texture_t *tex;
gs_effect_t *effect;
gs_eparam_t *param;
video = obs->video.main_mix;
if (!video->texture_rendered)
video = canvas->mix;
if (!video || !video->texture_rendered)
return;
const enum gs_color_space source_space = video->render_space;
@ -2108,19 +2179,32 @@ static void obs_render_main_texture_internal(enum gs_blend_type src_c, enum gs_b
void obs_render_main_texture(void)
{
obs_render_main_texture_internal(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA, GS_BLEND_ONE, GS_BLEND_INVSRCALPHA);
obs_render_canvas_texture_internal(obs->data.main_canvas, GS_BLEND_ONE, GS_BLEND_INVSRCALPHA, GS_BLEND_ONE,
GS_BLEND_INVSRCALPHA);
}
void obs_render_main_texture_src_color_only(void)
{
obs_render_main_texture_internal(GS_BLEND_ONE, GS_BLEND_ZERO, GS_BLEND_ONE, GS_BLEND_INVSRCALPHA);
obs_render_canvas_texture_internal(obs->data.main_canvas, GS_BLEND_ONE, GS_BLEND_ZERO, GS_BLEND_ONE,
GS_BLEND_INVSRCALPHA);
}
void obs_render_canvas_texture(obs_canvas_t *canvas)
{
obs_render_canvas_texture_internal(canvas, GS_BLEND_ONE, GS_BLEND_INVSRCALPHA, GS_BLEND_ONE,
GS_BLEND_INVSRCALPHA);
}
void obs_render_canvas_texture_src_color_only(obs_canvas_t *canvas)
{
obs_render_canvas_texture_internal(canvas, GS_BLEND_ONE, GS_BLEND_ZERO, GS_BLEND_ONE, GS_BLEND_INVSRCALPHA);
}
gs_texture_t *obs_get_main_texture(void)
{
struct obs_core_video_mix *video;
video = obs->video.main_mix;
video = obs->data.main_canvas->mix;
if (!video->texture_rendered)
return NULL;
@ -2137,6 +2221,7 @@ static obs_source_t *obs_load_source_type(obs_data_t *source_data, bool is_priva
const char *v_id = obs_data_get_string(source_data, "versioned_id");
obs_data_t *settings = obs_data_get_obj(source_data, "settings");
obs_data_t *hotkeys = obs_data_get_obj(source_data, "hotkeys");
obs_canvas_t *canvas = NULL;
double volume;
double balance;
int64_t sync;
@ -2153,13 +2238,23 @@ static obs_source_t *obs_load_source_type(obs_data_t *source_data, bool is_priva
if (!*v_id)
v_id = id;
source = obs_source_create_set_last_ver(v_id, name, uuid, settings, hotkeys, prev_ver, is_private);
if (strcmp(id, scene_info.id) == 0 || strcmp(id, group_info.id) == 0) {
const char *canvas_uuid = obs_data_get_string(source_data, "canvas_uuid");
canvas = obs_get_canvas_by_uuid(canvas_uuid);
/* Fall back to main canvas if canvas cannot be found. */
if (!canvas) {
canvas = obs_canvas_get_ref(obs->data.main_canvas);
}
}
source = obs_source_create_set_last_ver(canvas, v_id, name, uuid, settings, hotkeys, prev_ver, is_private);
if (source->owns_info_id) {
bfree((void *)source->info.unversioned_id);
source->info.unversioned_id = bstrdup(id);
}
obs_canvas_release(canvas);
obs_data_release(hotkeys);
caps = obs_source_get_output_flags(source);
@ -2322,6 +2417,7 @@ obs_data_t *obs_save_source(obs_source_t *source)
int m_type = (int)obs_source_get_monitoring_type(source);
int di_mode = (int)obs_source_get_deinterlace_mode(source);
int di_order = (int)obs_source_get_deinterlace_field_order(source);
obs_canvas_t *canvas = obs_source_get_canvas(source);
DARRAY(obs_source_t *) filters_copy;
obs_source_save(source);
@ -2356,6 +2452,11 @@ obs_data_t *obs_save_source(obs_source_t *source)
obs_data_set_int(source_data, "deinterlace_field_order", di_order);
obs_data_set_int(source_data, "monitoring_type", m_type);
if (canvas) {
obs_data_set_string(source_data, "canvas_uuid", obs_canvas_get_uuid(canvas));
obs_canvas_release(canvas);
}
obs_data_set_obj(source_data, "private_settings", source->private_settings);
if (source->info.type == OBS_SOURCE_TYPE_TRANSITION)
@ -2403,18 +2504,18 @@ obs_data_array_t *obs_save_sources_filtered(obs_save_source_filter_cb cb, void *
pthread_mutex_lock(&data->sources_mutex);
source = data->public_sources;
source = data->sources;
while (source) {
if ((source->info.type != OBS_SOURCE_TYPE_FILTER) != 0 && !source->removed && !source->temp_removed &&
cb(data_, source)) {
!source->context.private && cb(data_, source)) {
obs_data_t *source_data = obs_save_source(source);
obs_data_array_push_back(array, source_data);
obs_data_release(source_data);
}
source = (obs_source_t *)source->context.hh.next;
source = (obs_source_t *)source->context.hh_uuid.next;
}
pthread_mutex_unlock(&data->sources_mutex);
@ -2499,7 +2600,7 @@ static inline bool obs_context_data_init_wrap(struct obs_context_data *context,
if (uuid && strlen(uuid) == UUID_STR_LENGTH)
context->uuid = bstrdup(uuid);
/* Only automatically generate UUIDs for sources */
else if (type == OBS_OBJ_TYPE_SOURCE)
else if (type == OBS_OBJ_TYPE_SOURCE || type == OBS_OBJ_TYPE_CANVAS)
context->uuid = os_generate_uuid();
context->name = dup_name(name, private);
@ -2656,7 +2757,7 @@ void obs_context_data_remove(struct obs_context_data *context)
}
}
void obs_context_data_remove_name(struct obs_context_data *context, void *phead)
void obs_context_data_remove_name(struct obs_context_data *context, pthread_mutex_t *mutex, void *phead)
{
struct obs_context_data **head = phead;
@ -2665,12 +2766,12 @@ void obs_context_data_remove_name(struct obs_context_data *context, void *phead)
if (!context)
return;
pthread_mutex_lock(context->mutex);
pthread_mutex_lock(mutex);
HASH_DELETE(hh, *head, context);
pthread_mutex_unlock(context->mutex);
pthread_mutex_unlock(mutex);
}
void obs_context_data_remove_uuid(struct obs_context_data *context, void *puuid_head)
void obs_context_data_remove_uuid(struct obs_context_data *context, pthread_mutex_t *mutex, void *puuid_head)
{
struct obs_context_data **uuid_head = puuid_head;
@ -2679,9 +2780,9 @@ void obs_context_data_remove_uuid(struct obs_context_data *context, void *puuid_
if (!context || !context->uuid || !uuid_head)
return;
pthread_mutex_lock(context->mutex);
pthread_mutex_lock(mutex);
HASH_DELETE(hh_uuid, *uuid_head, context);
pthread_mutex_unlock(context->mutex);
pthread_mutex_unlock(mutex);
}
void obs_context_wait(struct obs_context_data *context)
@ -2970,13 +3071,13 @@ void obs_add_raw_video_callback(const struct video_scale_info *conversion,
void obs_add_raw_video_callback2(const struct video_scale_info *conversion, uint32_t frame_rate_divisor,
void (*callback)(void *param, struct video_data *frame), void *param)
{
struct obs_core_video_mix *video = obs->video.main_mix;
struct obs_core_video_mix *video = obs->data.main_canvas->mix;
start_raw_video(video->video, conversion, frame_rate_divisor, callback, param);
}
void obs_remove_raw_video_callback(void (*callback)(void *param, struct video_data *frame), void *param)
{
struct obs_core_video_mix *video = obs->video.main_mix;
struct obs_core_video_mix *video = obs->data.main_canvas->mix;
stop_raw_video(video->video, callback, param);
}
@ -3093,13 +3194,13 @@ bool obs_video_active(void)
bool obs_nv12_tex_active(void)
{
struct obs_core_video_mix *video = obs->video.main_mix;
struct obs_core_video_mix *video = obs->data.main_canvas->mix;
return video->using_nv12_tex;
}
bool obs_p010_tex_active(void)
{
struct obs_core_video_mix *video = obs->video.main_mix;
struct obs_core_video_mix *video = obs->data.main_canvas->mix;
return video->using_p010_tex;
}
@ -3314,3 +3415,8 @@ bool obs_enum_output_protocols(size_t idx, char **protocol)
*protocol = obs->data.protocols.array[idx];
return true;
}
obs_canvas_t *obs_get_main_canvas(void)
{
return obs_canvas_get_ref(obs->data.main_canvas);
}

View file

@ -51,6 +51,7 @@ struct obs_service;
struct obs_module;
struct obs_fader;
struct obs_volmeter;
struct obs_canvas;
typedef struct obs_context_data obs_object_t;
typedef struct obs_display obs_display_t;
@ -65,12 +66,14 @@ typedef struct obs_service obs_service_t;
typedef struct obs_module obs_module_t;
typedef struct obs_fader obs_fader_t;
typedef struct obs_volmeter obs_volmeter_t;
typedef struct obs_canvas obs_canvas_t;
typedef struct obs_weak_object obs_weak_object_t;
typedef struct obs_weak_source obs_weak_source_t;
typedef struct obs_weak_output obs_weak_output_t;
typedef struct obs_weak_encoder obs_weak_encoder_t;
typedef struct obs_weak_service obs_weak_service_t;
typedef struct obs_weak_canvas obs_weak_canvas_t;
#include "obs-missing-files.h"
#include "obs-source.h"
@ -688,6 +691,9 @@ EXPORT void obs_enum_encoders(bool (*enum_proc)(void *, obs_encoder_t *), void *
/** Enumerates encoders */
EXPORT void obs_enum_services(bool (*enum_proc)(void *, obs_service_t *), void *param);
/** Enumerates canvases */
EXPORT void obs_enum_canvases(bool (*enum_proc)(void *, obs_canvas_t *), void *param);
/**
* Gets a source by its name.
*
@ -719,6 +725,11 @@ EXPORT obs_encoder_t *obs_get_encoder_by_name(const char *name);
/** Gets an service by its name. */
EXPORT obs_service_t *obs_get_service_by_name(const char *name);
/** Get a canvas by its name. */
EXPORT obs_canvas_t *obs_get_canvas_by_name(const char *name);
/** Get a canvas by its UUID. */
EXPORT obs_canvas_t *obs_get_canvas_by_uuid(const char *uuid);
enum obs_base_effect {
OBS_EFFECT_DEFAULT, /**< RGB/YUV */
OBS_EFFECT_DEFAULT_RECT, /**< RGB/YUV (using texture_rect) */
@ -747,6 +758,12 @@ EXPORT void obs_render_main_texture(void);
/** Renders the last main output texture ignoring background color */
EXPORT void obs_render_main_texture_src_color_only(void);
/** Renders the last canvas output texture */
EXPORT void obs_render_canvas_texture(obs_canvas_t *canvas);
/** Renders the last main output texture ignoring background color */
EXPORT void obs_render_canvas_texture_src_color_only(obs_canvas_t *canvas);
/** Returns the last main output texture. This can return NULL if the texture
* is unavailable. */
EXPORT gs_texture_t *obs_get_main_texture(void);
@ -790,6 +807,7 @@ enum obs_obj_type {
OBS_OBJ_TYPE_OUTPUT,
OBS_OBJ_TYPE_ENCODER,
OBS_OBJ_TYPE_SERVICE,
OBS_OBJ_TYPE_CANVAS,
};
EXPORT enum obs_obj_type obs_obj_get_type(void *obj);
@ -1482,6 +1500,9 @@ EXPORT enum obs_media_state obs_source_media_get_state(obs_source_t *source);
EXPORT void obs_source_media_started(obs_source_t *source);
EXPORT void obs_source_media_ended(obs_source_t *source);
/** Get canvas this source belongs to (reference incremented) */
EXPORT obs_canvas_t *obs_source_get_canvas(const obs_source_t *source);
/* ------------------------------------------------------------------------- */
/* Transition-specific functions */
enum obs_transition_target {
@ -2484,6 +2505,99 @@ EXPORT void obs_source_frame_copy(struct obs_source_frame *dst, const struct obs
/* Get source icon type */
EXPORT enum obs_icon_type obs_source_get_icon_type(const char *id);
/* ------------------------------------------------------------------------- */
/* Canvases */
/* Canvas flags */
enum obs_canvas_flags {
MAIN = 1 << 0, // Main canvas created by libobs, cannot be renamed or reset, cannot be set by user
ACTIVATE = 1 << 1, // Canvas sources will become active when they are visible
MIX_AUDIO = 1 << 2, // Audio from channels in this canvas will be mixed into the audio output
SCENE_REF = 1 << 3, // Canvas will hold references for scene sources
EPHEMERAL = 1 << 4, // Indicates this canvas is not supposed to be saved
/* Presets */
PROGRAM = ACTIVATE | MIX_AUDIO | SCENE_REF,
PREVIEW = EPHEMERAL,
DEVICE = ACTIVATE | EPHEMERAL,
};
/** Get a strong reference to the main OBS canvas */
EXPORT obs_canvas_t *obs_get_main_canvas(void);
/** Creates a new canvas */
EXPORT obs_canvas_t *obs_canvas_create(const char *name, struct obs_video_info *ovi, uint32_t flags);
/** Creates a new private canvas */
EXPORT obs_canvas_t *obs_canvas_create_private(const char *name, struct obs_video_info *ovi, uint32_t flags);
/** Signal that references to canvas should be released and mark the canvas as removed. */
EXPORT void obs_canvas_remove(obs_canvas_t *canvas);
/** Returns if a canvas is marked as removed (i.e., should no longer be used). */
EXPORT bool obs_canvas_removed(obs_canvas_t *canvas);
/* Canvas properties */
/** Set canvas name */
EXPORT void obs_canvas_set_name(obs_canvas_t *canvas, const char *name);
/** Get canvas name */
EXPORT const char *obs_canvas_get_name(const obs_canvas_t *canvas);
/** Get canvas UUID */
EXPORT const char *obs_canvas_get_uuid(const obs_canvas_t *canvas);
/** Gets flags set on a canvas */
EXPORT uint32_t obs_canvas_get_flags(const obs_canvas_t *canvas);
/* Saving/Loading */
/** Saves a canvas to settings data */
EXPORT obs_data_t *obs_save_canvas(obs_canvas_t *source);
/** Loads a canvas from settings data */
EXPORT obs_canvas_t *obs_load_canvas(obs_data_t *data);
/* Reference counting */
/** Add strong reference */
EXPORT obs_canvas_t *obs_canvas_get_ref(obs_canvas_t *canvas);
/** Release strong reference */
EXPORT void obs_canvas_release(obs_canvas_t *canvas);
/** Add weak reference */
EXPORT void obs_weak_canvas_addref(obs_weak_canvas_t *weak);
/** Release weak reference */
EXPORT void obs_weak_canvas_release(obs_weak_canvas_t *weak);
/** Get weak reference from strong reference */
EXPORT obs_weak_canvas_t *obs_canvas_get_weak_canvas(obs_canvas_t *canvas);
/** Get strong reference from weak reference */
EXPORT obs_canvas_t *obs_weak_canvas_get_canvas(obs_weak_canvas_t *weak);
/* Channels */
/** Sets the source to be used for this canvas. */
EXPORT void obs_canvas_set_channel(obs_canvas_t *canvas, uint32_t channel, obs_source_t *source);
/** Gets the source currently in use for this view context */
EXPORT obs_source_t *obs_canvas_get_channel(obs_canvas_t *canvas, uint32_t channel);
/* Canvas sources */
/** Create scene attached to a canvas */
EXPORT obs_scene_t *obs_canvas_scene_create(obs_canvas_t *canvas, const char *name);
/** Remove a scene from a canvas */
EXPORT 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 */
EXPORT void obs_canvas_move_scene(obs_scene_t *scene, obs_canvas_t *dst);
/** Enumerates scenes belonging to a canvas */
EXPORT void obs_canvas_enum_scenes(obs_canvas_t *canvas, bool (*enum_proc)(void *, obs_source_t *), void *param);
/** Get a canvas source by name */
EXPORT obs_source_t *obs_canvas_get_source_by_name(obs_canvas_t *canvas, const char *name);
/** Get a canvas source by UUID */
EXPORT obs_scene_t *obs_canvas_get_scene_by_name(obs_canvas_t *canvas, const char *name);
/* Canvas video */
/** Reset a canvas's video mix */
EXPORT bool obs_canvas_reset_video(obs_canvas_t *canvas, struct obs_video_info *ovi);
/** Returns true if the canvas video is configured */
EXPORT bool obs_canvas_has_video(obs_canvas_t *canvas);
/** Get canvas video output */
EXPORT video_t *obs_canvas_get_video(const obs_canvas_t *canvas);
/** Get canvas video info (if it exists) */
EXPORT bool obs_canvas_get_video_info(const obs_canvas_t *canvas, struct obs_video_info *ovi);
/** Renders the sources of this canvas's view context */
EXPORT void obs_canvas_render(obs_canvas_t *canvas);
#ifdef __cplusplus
}
#endif