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:
parent
66da1e42c7
commit
3d57b1fd63
10 changed files with 1043 additions and 132 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
555
libobs/obs-canvas.c
Normal 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(¶ms, "canvas", canvas);
|
||||
calldata_set_int(¶ms, "channel", channel);
|
||||
calldata_set_ptr(¶ms, "prev_source", prev_source);
|
||||
calldata_set_ptr(¶ms, "source", source);
|
||||
|
||||
signal_handler_signal(canvas->context.signals, "channel_change", ¶ms);
|
||||
if (canvas->flags & MAIN)
|
||||
signal_handler_signal(obs->signals, "channel_change", ¶ms);
|
||||
|
||||
/* 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(¶ms, "source", &source);
|
||||
view->channels[channel] = source;
|
||||
|
||||
calldata_free(¶ms);
|
||||
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);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
272
libobs/obs.c
272
libobs/obs.c
|
|
@ -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(¶ms, "channel", channel);
|
||||
calldata_set_ptr(¶ms, "prev_source", prev_source);
|
||||
calldata_set_ptr(¶ms, "source", source);
|
||||
signal_handler_signal(obs->signals, "channel_change", ¶ms);
|
||||
calldata_get_ptr(¶ms, "source", &source);
|
||||
calldata_free(¶ms);
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
114
libobs/obs.h
114
libobs/obs.h
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue