mirror of
https://github.com/Xaymar/obs-StreamFX
synced 2024-11-13 07:15:06 +00:00
2c94852ba8
Adds a new property to control the alignment of the source within the calculated boundary when rescaling the source. Also fixes the permanently left aligned mirror at the same time.
735 lines
25 KiB
C++
735 lines
25 KiB
C++
/*
|
|
* Modern effects for a modern Streamer
|
|
* Copyright (C) 2017 Michael Fabian Dirks
|
|
*
|
|
* 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, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include "source-mirror.hpp"
|
|
#include <bitset>
|
|
#include <cstring>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <vector>
|
|
#include "obs/obs-source-tracker.hpp"
|
|
#include "obs/obs-tools.hpp"
|
|
#include "strings.hpp"
|
|
|
|
// OBS
|
|
#ifdef _MSC_VER
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 4201)
|
|
#endif
|
|
#include <media-io/audio-io.h>
|
|
#include <obs-config.h>
|
|
#ifdef _MSC_VER
|
|
#pragma warning(pop)
|
|
#endif
|
|
|
|
#define S_SOURCE_MIRROR "Source.Mirror"
|
|
#define P_SOURCE "Source.Mirror.Source"
|
|
#define P_SOURCE_SIZE "Source.Mirror.Source.Size"
|
|
#define P_SOURCE_AUDIO "Source.Mirror.Source.Audio"
|
|
#define P_SCALING "Source.Mirror.Scaling"
|
|
#define P_SCALING_METHOD "Source.Mirror.Scaling.Method"
|
|
#define P_SCALING_METHOD_POINT "Source.Mirror.Scaling.Method.Point"
|
|
#define P_SCALING_METHOD_BILINEAR "Source.Mirror.Scaling.Method.Bilinear"
|
|
#define P_SCALING_METHOD_BICUBIC "Source.Mirror.Scaling.Method.Bicubic"
|
|
#define P_SCALING_METHOD_LANCZOS "Source.Mirror.Scaling.Method.Lanczos"
|
|
#define P_SCALING_SIZE "Source.Mirror.Scaling.Size"
|
|
#define P_SCALING_TRANSFORMKEEPORIGINAL "Source.Mirror.Scaling.TransformKeepOriginal"
|
|
#define P_SCALING_BOUNDS "Source.Mirror.Scaling.Bounds"
|
|
#define P_SCALING_BOUNDS_STRETCH "Source.Mirror.Scaling.Bounds.Stretch"
|
|
#define P_SCALING_BOUNDS_FIT "Source.Mirror.Scaling.Bounds.Fit"
|
|
#define P_SCALING_BOUNDS_FILL "Source.Mirror.Scaling.Bounds.Fill"
|
|
#define P_SCALING_BOUNDS_FILLWIDTH "Source.Mirror.Scaling.Bounds.FillWidth"
|
|
#define P_SCALING_BOUNDS_FILLHEIGHT "Source.Mirror.Scaling.Bounds.FillHeight"
|
|
#define P_SCALING_ALIGNMENT "Source.Mirror.Scaling.Alignment"
|
|
|
|
// Initializer & Finalizer
|
|
INITIALIZER(SourceMirrorInit)
|
|
{
|
|
initializerFunctions.push_back([] { source::mirror::mirror_factory::initialize(); });
|
|
finalizerFunctions.push_back([] { source::mirror::mirror_factory::finalize(); });
|
|
}
|
|
|
|
static std::shared_ptr<source::mirror::mirror_factory> factory_instance = nullptr;
|
|
|
|
void source::mirror::mirror_factory::initialize()
|
|
{
|
|
factory_instance = std::make_shared<source::mirror::mirror_factory>();
|
|
}
|
|
|
|
void source::mirror::mirror_factory::finalize()
|
|
{
|
|
factory_instance.reset();
|
|
}
|
|
|
|
std::shared_ptr<source::mirror::mirror_factory> source::mirror::mirror_factory::get()
|
|
{
|
|
return factory_instance;
|
|
}
|
|
|
|
source::mirror::mirror_factory::mirror_factory()
|
|
{
|
|
memset(&osi, 0, sizeof(obs_source_info));
|
|
osi.id = "obs-stream-effects-source-mirror";
|
|
osi.type = OBS_SOURCE_TYPE_INPUT;
|
|
osi.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_AUDIO | OBS_SOURCE_CUSTOM_DRAW;
|
|
|
|
osi.get_name = get_name;
|
|
osi.get_defaults = get_defaults;
|
|
osi.get_properties = get_properties;
|
|
osi.get_width = get_width;
|
|
osi.get_height = get_height;
|
|
osi.create = create;
|
|
osi.destroy = destroy;
|
|
osi.update = update;
|
|
osi.activate = activate;
|
|
osi.deactivate = deactivate;
|
|
osi.video_tick = video_tick;
|
|
osi.video_render = video_render;
|
|
osi.enum_active_sources = enum_active_sources;
|
|
osi.load = load;
|
|
osi.save = save;
|
|
|
|
obs_register_source(&osi);
|
|
}
|
|
|
|
source::mirror::mirror_factory::~mirror_factory() {}
|
|
|
|
const char* source::mirror::mirror_factory::get_name(void*)
|
|
{
|
|
return P_TRANSLATE(S_SOURCE_MIRROR);
|
|
}
|
|
|
|
void source::mirror::mirror_factory::get_defaults(obs_data_t* data)
|
|
{
|
|
obs_data_set_default_string(data, P_SOURCE, "");
|
|
obs_data_set_default_bool(data, P_SOURCE_AUDIO, false);
|
|
obs_data_set_default_bool(data, P_SCALING, false);
|
|
obs_data_set_default_string(data, P_SCALING_SIZE, "100x100");
|
|
obs_data_set_default_int(data, P_SCALING_METHOD, (int64_t)obs_scale_type::OBS_SCALE_BILINEAR);
|
|
obs_data_set_default_bool(data, P_SCALING_TRANSFORMKEEPORIGINAL, false);
|
|
obs_data_set_default_int(data, P_SCALING_BOUNDS, (int64_t)obs_bounds_type::OBS_BOUNDS_STRETCH);
|
|
obs_data_set_default_int(data, P_SCALING_ALIGNMENT, OBS_ALIGN_CENTER);
|
|
}
|
|
|
|
bool source::mirror::mirror_factory::modified_properties(obs_properties_t* pr, obs_property_t* p, obs_data_t* data)
|
|
{
|
|
if (obs_properties_get(pr, P_SOURCE) == p) {
|
|
obs_source_t* target = obs_get_source_by_name(obs_data_get_string(data, P_SOURCE));
|
|
if (target) {
|
|
std::vector<char> buf(256);
|
|
snprintf(buf.data(), buf.size(), "%" PRIu32 "x%" PRIu32, obs_source_get_width(target),
|
|
obs_source_get_height(target));
|
|
obs_data_set_string(data, P_SOURCE_SIZE, buf.data());
|
|
} else {
|
|
obs_data_set_string(data, P_SOURCE_SIZE, "0x0");
|
|
}
|
|
obs_source_release(target);
|
|
}
|
|
|
|
if (obs_properties_get(pr, P_SCALING) == p) {
|
|
bool show = obs_data_get_bool(data, P_SCALING);
|
|
obs_property_set_visible(obs_properties_get(pr, P_SCALING_METHOD), show);
|
|
obs_property_set_visible(obs_properties_get(pr, P_SCALING_SIZE), show);
|
|
obs_property_set_visible(obs_properties_get(pr, P_SCALING_BOUNDS), show);
|
|
obs_property_set_visible(obs_properties_get(pr, P_SCALING_ALIGNMENT), show);
|
|
return true;
|
|
}
|
|
|
|
if (obs_properties_get(pr, P_SCALING_BOUNDS) == p) {
|
|
obs_bounds_type scaling_type = static_cast<obs_bounds_type>(obs_data_get_int(data, P_SCALING_BOUNDS));
|
|
obs_property_t* p2 = obs_properties_get(pr, P_SCALING_BOUNDS);
|
|
switch (scaling_type) {
|
|
case obs_bounds_type::OBS_BOUNDS_STRETCH:
|
|
obs_property_set_long_description(p2, P_TRANSLATE(P_DESC(P_SCALING_BOUNDS_STRETCH)));
|
|
break;
|
|
case obs_bounds_type::OBS_BOUNDS_SCALE_INNER:
|
|
obs_property_set_long_description(p2, P_TRANSLATE(P_DESC(P_SCALING_BOUNDS_FIT)));
|
|
break;
|
|
case obs_bounds_type::OBS_BOUNDS_SCALE_OUTER:
|
|
obs_property_set_long_description(p2, P_TRANSLATE(P_DESC(P_SCALING_BOUNDS_FILL)));
|
|
break;
|
|
case obs_bounds_type::OBS_BOUNDS_SCALE_TO_WIDTH:
|
|
obs_property_set_long_description(p2, P_TRANSLATE(P_DESC(P_SCALING_BOUNDS_FILLWIDTH)));
|
|
break;
|
|
case obs_bounds_type::OBS_BOUNDS_SCALE_TO_HEIGHT:
|
|
obs_property_set_long_description(p2, P_TRANSLATE(P_DESC(P_SCALING_BOUNDS_FILLHEIGHT)));
|
|
break;
|
|
default:
|
|
obs_property_set_long_description(p2, P_TRANSLATE(P_DESC(P_SCALING_BOUNDS)));
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
obs_properties_t* source::mirror::mirror_factory::get_properties(void*)
|
|
{
|
|
obs_properties_t* pr = obs_properties_create();
|
|
obs_property_t* p = nullptr;
|
|
|
|
p = obs_properties_add_list(pr, P_SOURCE, P_TRANSLATE(P_SOURCE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
|
|
obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_SOURCE)));
|
|
obs_property_set_modified_callback(p, modified_properties);
|
|
obs_property_list_add_string(p, "", "");
|
|
obs::source_tracker::get()->enumerate(
|
|
[&p](std::string name, obs_source_t*) {
|
|
obs_property_list_add_string(p, std::string(name + " (Source)").c_str(), name.c_str());
|
|
return false;
|
|
},
|
|
obs::source_tracker::filter_sources);
|
|
obs::source_tracker::get()->enumerate(
|
|
[&p](std::string name, obs_source_t*) {
|
|
obs_property_list_add_string(p, std::string(name + " (Scene)").c_str(), name.c_str());
|
|
return false;
|
|
},
|
|
obs::source_tracker::filter_scenes);
|
|
|
|
p = obs_properties_add_text(pr, P_SOURCE_SIZE, P_TRANSLATE(P_SOURCE_SIZE), OBS_TEXT_DEFAULT);
|
|
obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_SOURCE_SIZE)));
|
|
obs_property_set_enabled(p, false);
|
|
|
|
p = obs_properties_add_bool(pr, P_SOURCE_AUDIO, P_TRANSLATE(P_SOURCE_AUDIO));
|
|
obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_SOURCE_AUDIO)));
|
|
|
|
p = obs_properties_add_bool(pr, P_SCALING, P_TRANSLATE(P_SCALING));
|
|
obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_SCALING)));
|
|
obs_property_set_modified_callback(p, modified_properties);
|
|
|
|
p = obs_properties_add_list(pr, P_SCALING_METHOD, P_TRANSLATE(P_SCALING_METHOD), OBS_COMBO_TYPE_LIST,
|
|
OBS_COMBO_FORMAT_INT);
|
|
obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_SCALING_METHOD)));
|
|
obs_property_set_modified_callback(p, modified_properties);
|
|
obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_METHOD_POINT), (int64_t)obs_scale_type::OBS_SCALE_POINT);
|
|
obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_METHOD_BILINEAR), (int64_t)obs_scale_type::OBS_SCALE_BILINEAR);
|
|
obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_METHOD_BICUBIC), (int64_t)obs_scale_type::OBS_SCALE_BICUBIC);
|
|
obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_METHOD_LANCZOS), (int64_t)obs_scale_type::OBS_SCALE_LANCZOS);
|
|
|
|
p = obs_properties_add_text(pr, P_SCALING_SIZE, P_TRANSLATE(P_SCALING_SIZE), OBS_TEXT_DEFAULT);
|
|
obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_SCALING_SIZE)));
|
|
|
|
p = obs_properties_add_bool(pr, P_SCALING_TRANSFORMKEEPORIGINAL, P_TRANSLATE(P_SCALING_TRANSFORMKEEPORIGINAL));
|
|
obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_SCALING_TRANSFORMKEEPORIGINAL)));
|
|
|
|
p = obs_properties_add_list(pr, P_SCALING_BOUNDS, P_TRANSLATE(P_SCALING_BOUNDS), OBS_COMBO_TYPE_LIST,
|
|
OBS_COMBO_FORMAT_INT);
|
|
obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_SCALING_BOUNDS)));
|
|
obs_property_set_modified_callback(p, modified_properties);
|
|
obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_BOUNDS_STRETCH), (int64_t)obs_bounds_type::OBS_BOUNDS_STRETCH);
|
|
obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_BOUNDS_FIT), (int64_t)obs_bounds_type::OBS_BOUNDS_SCALE_INNER);
|
|
obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_BOUNDS_FILL), (int64_t)obs_bounds_type::OBS_BOUNDS_SCALE_OUTER);
|
|
obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_BOUNDS_FILLWIDTH),
|
|
(int64_t)obs_bounds_type::OBS_BOUNDS_SCALE_TO_WIDTH);
|
|
obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_BOUNDS_FILLHEIGHT),
|
|
(int64_t)obs_bounds_type::OBS_BOUNDS_SCALE_TO_HEIGHT);
|
|
|
|
p = obs_properties_add_list(pr, P_SCALING_ALIGNMENT, P_TRANSLATE(P_SCALING_ALIGNMENT), OBS_COMBO_TYPE_LIST,
|
|
OBS_COMBO_FORMAT_INT);
|
|
obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_SCALING_ALIGNMENT)));
|
|
obs_property_list_add_int(p, obs_module_recursive_text("\\@" S_ALIGNMENT_LEFT "\\@ \\@" S_ALIGNMENT_TOP "\\@"),
|
|
OBS_ALIGN_LEFT | OBS_ALIGN_TOP);
|
|
obs_property_list_add_int(p, obs_module_recursive_text("\\@" S_ALIGNMENT_TOP "\\@"), OBS_ALIGN_TOP);
|
|
obs_property_list_add_int(p, obs_module_recursive_text("\\@" S_ALIGNMENT_RIGHT "\\@ \\@" S_ALIGNMENT_TOP "\\@"),
|
|
OBS_ALIGN_RIGHT | OBS_ALIGN_TOP);
|
|
obs_property_list_add_int(p, obs_module_recursive_text("\\@" S_ALIGNMENT_LEFT "\\@"), OBS_ALIGN_LEFT);
|
|
obs_property_list_add_int(p, obs_module_recursive_text("\\@" S_ALIGNMENT_CENTER "\\@"), OBS_ALIGN_CENTER);
|
|
obs_property_list_add_int(p, obs_module_recursive_text("\\@" S_ALIGNMENT_RIGHT "\\@"), OBS_ALIGN_RIGHT);
|
|
obs_property_list_add_int(p, obs_module_recursive_text("\\@" S_ALIGNMENT_LEFT "\\@ \\@" S_ALIGNMENT_BOTTOM "\\@"),
|
|
OBS_ALIGN_LEFT | OBS_ALIGN_BOTTOM);
|
|
obs_property_list_add_int(p, obs_module_recursive_text("\\@" S_ALIGNMENT_BOTTOM "\\@"), OBS_ALIGN_BOTTOM);
|
|
obs_property_list_add_int(p, obs_module_recursive_text("\\@" S_ALIGNMENT_RIGHT "\\@ \\@" S_ALIGNMENT_BOTTOM "\\@"),
|
|
OBS_ALIGN_RIGHT | OBS_ALIGN_BOTTOM);
|
|
|
|
return pr;
|
|
}
|
|
|
|
void* source::mirror::mirror_factory::create(obs_data_t* data, obs_source_t* source)
|
|
{
|
|
return new source::mirror::mirror_instance(data, source);
|
|
}
|
|
|
|
void source::mirror::mirror_factory::destroy(void* p)
|
|
{
|
|
if (p) {
|
|
delete static_cast<source::mirror::mirror_instance*>(p);
|
|
}
|
|
}
|
|
|
|
uint32_t source::mirror::mirror_factory::get_width(void* p)
|
|
{
|
|
if (p) {
|
|
return static_cast<source::mirror::mirror_instance*>(p)->get_width();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint32_t source::mirror::mirror_factory::get_height(void* p)
|
|
{
|
|
if (p) {
|
|
return static_cast<source::mirror::mirror_instance*>(p)->get_height();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void source::mirror::mirror_factory::update(void* p, obs_data_t* data)
|
|
{
|
|
if (p) {
|
|
static_cast<source::mirror::mirror_instance*>(p)->update(data);
|
|
}
|
|
}
|
|
|
|
void source::mirror::mirror_factory::activate(void* p)
|
|
{
|
|
if (p) {
|
|
static_cast<source::mirror::mirror_instance*>(p)->activate();
|
|
}
|
|
}
|
|
|
|
void source::mirror::mirror_factory::deactivate(void* p)
|
|
{
|
|
if (p) {
|
|
static_cast<source::mirror::mirror_instance*>(p)->deactivate();
|
|
}
|
|
}
|
|
|
|
void source::mirror::mirror_factory::video_tick(void* p, float t)
|
|
{
|
|
if (p) {
|
|
static_cast<source::mirror::mirror_instance*>(p)->video_tick(t);
|
|
}
|
|
}
|
|
|
|
void source::mirror::mirror_factory::video_render(void* p, gs_effect_t* ef)
|
|
{
|
|
if (p) {
|
|
static_cast<source::mirror::mirror_instance*>(p)->video_render(ef);
|
|
}
|
|
}
|
|
|
|
void source::mirror::mirror_factory::enum_active_sources(void* p, obs_source_enum_proc_t enum_callback, void* param)
|
|
{
|
|
if (p) {
|
|
static_cast<source::mirror::mirror_instance*>(p)->enum_active_sources(enum_callback, param);
|
|
}
|
|
}
|
|
|
|
void source::mirror::mirror_factory::load(void* p, obs_data_t* d)
|
|
{
|
|
if (p) {
|
|
static_cast<source::mirror::mirror_instance*>(p)->load(d);
|
|
}
|
|
}
|
|
|
|
void source::mirror::mirror_factory::save(void* p, obs_data_t* d)
|
|
{
|
|
if (p) {
|
|
static_cast<source::mirror::mirror_instance*>(p)->save(d);
|
|
}
|
|
}
|
|
|
|
void source::mirror::mirror_instance::release_input()
|
|
{
|
|
// Clear any references to the previous source.
|
|
if (this->m_source_item) {
|
|
obs_sceneitem_remove(this->m_source_item);
|
|
this->m_source_item = nullptr;
|
|
}
|
|
this->m_source.reset();
|
|
}
|
|
|
|
void source::mirror::mirror_instance::acquire_input(std::string source_name)
|
|
{
|
|
// Acquire new reference to current source.
|
|
obs_source_t* ref_source = obs_get_source_by_name(source_name.c_str());
|
|
if (!ref_source) {
|
|
// Early-Exit: Unable to find a source with this name, likely has been released.
|
|
#ifdef _DEBUG
|
|
P_LOG_DEBUG("<Source Mirror:%s> Unable to find target source '%s'.", obs_source_get_name(this->m_self),
|
|
source_name.c_str());
|
|
#endif
|
|
return;
|
|
} else if (ref_source == this->m_self) {
|
|
// Early-Exit: Attempted self-mirror (recursion).
|
|
#ifdef _DEBUG
|
|
P_LOG_DEBUG("<Source Mirror:%s> Attempted to mirror self.", obs_source_get_name(this->m_self));
|
|
#endif
|
|
obs_source_release(ref_source);
|
|
return;
|
|
}
|
|
|
|
std::shared_ptr<obs::source> new_source = std::make_shared<obs::source>(ref_source, true, false);
|
|
|
|
// It looks like everything is in order, so continue now.
|
|
this->m_source_item = obs_scene_add(obs_scene_from_source(this->m_scene->get()), new_source->get());
|
|
if (!this->m_source_item) {
|
|
// Late-Exit: OBS detected something bad, so no further action will be taken.
|
|
#ifdef _DEBUG
|
|
P_LOG_DEBUG("<Source Mirror:%s> Attempted recursion with scene '%s'.", obs_source_get_name(this->m_self),
|
|
source_name.c_str());
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// If everything worked fine, we now set everything up.
|
|
this->m_source = new_source;
|
|
this->m_source->events.rename += std::bind(&source::mirror::mirror_instance::on_source_rename, this,
|
|
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
|
|
if ((obs_source_get_output_flags(this->m_source->get()) & OBS_SOURCE_AUDIO) != 0) {
|
|
this->m_source->events.audio_data +=
|
|
std::bind(&source::mirror::mirror_instance::on_audio_data, this, std::placeholders::_1,
|
|
std::placeholders::_2, std::placeholders::_3);
|
|
}
|
|
}
|
|
|
|
source::mirror::mirror_instance::mirror_instance(obs_data_t*, obs_source_t* src)
|
|
: m_self(src), m_active(true), m_tick(0), m_scene_rendered(false), m_rescale_enabled(false), m_rescale_width(1),
|
|
m_rescale_height(1), m_rescale_keep_orig_size(false), m_rescale_type(obs_scale_type::OBS_SCALE_BICUBIC),
|
|
m_rescale_bounds(obs_bounds_type::OBS_BOUNDS_STRETCH), m_audio_enabled(false), m_audio_kill_thread(false),
|
|
m_audio_have_output(false), m_source_item(nullptr)
|
|
{
|
|
// Initialize Video Rendering
|
|
this->m_scene =
|
|
std::make_shared<obs::source>(obs_scene_get_source(obs_scene_create_private("Source Mirror Internal Scene")));
|
|
this->m_scene_texture_renderer =
|
|
std::make_shared<gfx::source_texture>(this->m_scene, std::make_shared<obs::source>(this->m_self, false, false));
|
|
|
|
// Initialize Audio Rendering
|
|
this->m_audio_thread = std::thread(std::bind(&source::mirror::mirror_instance::audio_output_cb, this));
|
|
}
|
|
|
|
source::mirror::mirror_instance::~mirror_instance()
|
|
{
|
|
release_input();
|
|
|
|
// Finalize Audio Rendering
|
|
this->m_audio_kill_thread = true;
|
|
this->m_audio_notify.notify_all();
|
|
if (this->m_audio_thread.joinable()) {
|
|
this->m_audio_thread.join();
|
|
}
|
|
|
|
// Finalize Video Rendering
|
|
this->m_scene_texture_renderer.reset();
|
|
this->m_scene.reset();
|
|
}
|
|
|
|
uint32_t source::mirror::mirror_instance::get_width()
|
|
{
|
|
if (m_source) {
|
|
if ((obs_source_get_output_flags(this->m_source->get()) & OBS_SOURCE_VIDEO) == 0) {
|
|
return 0;
|
|
}
|
|
if (this->m_rescale_enabled && this->m_rescale_width > 0 && !this->m_rescale_keep_orig_size) {
|
|
return this->m_rescale_width;
|
|
} else {
|
|
return m_source->width();
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint32_t source::mirror::mirror_instance::get_height()
|
|
{
|
|
if (m_source) {
|
|
if ((obs_source_get_output_flags(this->m_source->get()) & OBS_SOURCE_VIDEO) == 0) {
|
|
return 0;
|
|
}
|
|
if (this->m_rescale_enabled && this->m_rescale_height > 0 && !this->m_rescale_keep_orig_size) {
|
|
return this->m_rescale_height;
|
|
} else {
|
|
return m_source->height();
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void source::mirror::mirror_instance::update(obs_data_t* data)
|
|
{
|
|
{ // User changed the source we are tracking.
|
|
release_input();
|
|
this->m_source_name = obs_data_get_string(data, P_SOURCE);
|
|
if (this->m_source_name.length() > 0) {
|
|
acquire_input(this->m_source_name);
|
|
}
|
|
}
|
|
|
|
// Audio
|
|
this->m_audio_enabled = obs_data_get_bool(data, P_SOURCE_AUDIO);
|
|
|
|
// Rescaling
|
|
this->m_rescale_enabled = obs_data_get_bool(data, P_SCALING);
|
|
if (this->m_rescale_enabled) { // Parse rescaling settings.
|
|
uint32_t width, height;
|
|
|
|
// Read value.
|
|
const char* size = obs_data_get_string(data, P_SCALING_SIZE);
|
|
const char* xpos = strchr(size, 'x');
|
|
if (xpos != nullptr) {
|
|
// Width
|
|
width = strtoul(size, nullptr, 10);
|
|
if (errno == ERANGE || width == 0) {
|
|
this->m_rescale_enabled = false;
|
|
this->m_rescale_width = 1;
|
|
} else {
|
|
this->m_rescale_width = width;
|
|
}
|
|
|
|
height = strtoul(xpos + 1, nullptr, 10);
|
|
if (errno == ERANGE || height == 0) {
|
|
this->m_rescale_enabled = false;
|
|
this->m_rescale_height = 1;
|
|
} else {
|
|
this->m_rescale_height = height;
|
|
}
|
|
} else {
|
|
this->m_rescale_enabled = false;
|
|
this->m_rescale_width = 1;
|
|
this->m_rescale_height = 1;
|
|
}
|
|
|
|
this->m_rescale_keep_orig_size = obs_data_get_bool(data, P_SCALING_TRANSFORMKEEPORIGINAL);
|
|
this->m_rescale_type = (obs_scale_type)obs_data_get_int(data, P_SCALING_METHOD);
|
|
this->m_rescale_bounds = (obs_bounds_type)obs_data_get_int(data, P_SCALING_BOUNDS);
|
|
this->m_rescale_alignment = obs_data_get_int(data, P_SCALING_ALIGNMENT);
|
|
}
|
|
|
|
}
|
|
|
|
void source::mirror::mirror_instance::activate()
|
|
{
|
|
this->m_active = true;
|
|
|
|
// No source, delayed acquire.
|
|
if (!this->m_source_item && this->m_source_name.length() > 0) {
|
|
this->acquire_input(this->m_source_name.c_str());
|
|
}
|
|
}
|
|
|
|
void source::mirror::mirror_instance::deactivate()
|
|
{
|
|
this->m_active = false;
|
|
}
|
|
|
|
void source::mirror::mirror_instance::video_tick(float time)
|
|
{
|
|
this->m_tick += time;
|
|
if (this->m_tick > 0.1f) {
|
|
this->m_tick -= 0.1f;
|
|
|
|
// No source, delayed acquire.
|
|
if (!this->m_source_item && this->m_source_name.length() > 0) {
|
|
this->acquire_input(this->m_source_name.c_str());
|
|
}
|
|
}
|
|
|
|
// Update Scene Item Boundaries
|
|
if ((this->m_source_item && this->m_source)
|
|
&& ((obs_source_get_output_flags(this->m_source->get()) & OBS_SOURCE_VIDEO) != 0)) {
|
|
obs_transform_info info;
|
|
info.pos.x = 0;
|
|
info.pos.y = 0;
|
|
info.rot = 0;
|
|
info.scale.x = 1.f;
|
|
info.scale.y = 1.f;
|
|
info.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP;
|
|
info.bounds.x = float_t(this->get_width());
|
|
info.bounds.y = float_t(this->get_height());
|
|
info.bounds_alignment = m_rescale_alignment;
|
|
info.bounds_type = obs_bounds_type::OBS_BOUNDS_STRETCH;
|
|
if (this->m_rescale_enabled) {
|
|
info.bounds_type = this->m_rescale_bounds;
|
|
}
|
|
obs_sceneitem_set_info(this->m_source_item, &info);
|
|
obs_sceneitem_force_update_transform(this->m_source_item);
|
|
obs_sceneitem_set_scale_filter(this->m_source_item, this->m_rescale_enabled ? this->m_rescale_type
|
|
: obs_scale_type::OBS_SCALE_POINT);
|
|
}
|
|
|
|
m_scene_rendered = false;
|
|
}
|
|
|
|
void source::mirror::mirror_instance::video_render(gs_effect_t* effect)
|
|
{
|
|
if ((this->m_rescale_width == 0) || (this->m_rescale_height == 0) || !this->m_source_item
|
|
|| !this->m_scene_texture_renderer || !this->m_source) {
|
|
return;
|
|
}
|
|
|
|
// Don't bother rendering sources that aren't video.
|
|
if ((obs_source_get_output_flags(this->m_source->get()) & OBS_SOURCE_VIDEO) == 0) {
|
|
return;
|
|
}
|
|
|
|
// Only re-render the scene if there was a video_tick, saves GPU cycles.
|
|
if (!m_scene_rendered) {
|
|
// Override render size if rescaling is enabled.
|
|
uint32_t render_width = this->m_source->width();
|
|
uint32_t render_height = this->m_source->height();
|
|
if (m_rescale_enabled) {
|
|
render_width = m_rescale_width;
|
|
render_height = m_rescale_height;
|
|
}
|
|
|
|
try {
|
|
m_scene_texture = this->m_scene_texture_renderer->render(render_width, render_height);
|
|
m_scene_rendered = true;
|
|
} catch (...) {
|
|
// If we fail to render the source, just render nothing.
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (m_scene_texture) {
|
|
// Use default effect unless we are provided a different effect.
|
|
if (!effect) {
|
|
effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
|
|
}
|
|
|
|
// Render the cached scene texture.
|
|
gs_effect_set_texture(gs_effect_get_param_by_name(effect, "image"), m_scene_texture->get_object());
|
|
while (gs_effect_loop(effect, "Draw")) {
|
|
gs_draw_sprite(m_scene_texture->get_object(), 0, this->get_width(), this->get_height());
|
|
}
|
|
}
|
|
}
|
|
|
|
void source::mirror::mirror_instance::audio_output_cb()
|
|
{
|
|
std::unique_lock<std::mutex> ulock(this->m_audio_lock_outputter);
|
|
|
|
while (!this->m_audio_kill_thread) {
|
|
this->m_audio_notify.wait(ulock, [this]() { return this->m_audio_have_output || this->m_audio_kill_thread; });
|
|
|
|
if (this->m_audio_have_output) { // Get used audio element
|
|
std::shared_ptr<mirror_audio_data> mad;
|
|
{
|
|
std::lock_guard<std::mutex> capture_lock(this->m_audio_lock_capturer);
|
|
if (m_audio_data_queue.size() > 0) {
|
|
mad = m_audio_data_queue.front();
|
|
m_audio_data_queue.pop();
|
|
}
|
|
if (m_audio_data_queue.size() == 0) {
|
|
this->m_audio_have_output = false;
|
|
}
|
|
}
|
|
|
|
if (mad) {
|
|
ulock.unlock();
|
|
obs_source_output_audio(this->m_self, &mad->audio);
|
|
ulock.lock();
|
|
|
|
{
|
|
std::lock_guard<std::mutex> capture_lock(this->m_audio_lock_capturer);
|
|
m_audio_data_free_queue.push(mad);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void source::mirror::mirror_instance::enum_active_sources(obs_source_enum_proc_t enum_callback, void* param)
|
|
{
|
|
if (this->m_scene) {
|
|
enum_callback(this->m_self, this->m_scene->get(), param);
|
|
}
|
|
}
|
|
|
|
void source::mirror::mirror_instance::load(obs_data_t* data)
|
|
{
|
|
this->update(data);
|
|
}
|
|
|
|
void source::mirror::mirror_instance::save(obs_data_t* data)
|
|
{
|
|
if (!this->m_source_item || !this->m_source) {
|
|
return;
|
|
}
|
|
obs_data_set_string(data, P_SOURCE, obs_source_get_name(m_source->get()));
|
|
}
|
|
|
|
void source::mirror::mirror_instance::on_source_rename(obs::source* source, std::string, std::string)
|
|
{
|
|
obs_data_t* ref = obs_source_get_settings(this->m_self);
|
|
obs_data_set_string(ref, P_SOURCE, obs_source_get_name(source->get()));
|
|
obs_source_update(this->m_self, ref);
|
|
obs_data_release(ref);
|
|
}
|
|
|
|
void source::mirror::mirror_instance::on_audio_data(obs::source*, const audio_data* audio, bool)
|
|
{
|
|
if (!this->m_audio_enabled) {
|
|
return;
|
|
}
|
|
|
|
audio_t* aud = obs_get_audio();
|
|
if (!aud) {
|
|
return;
|
|
}
|
|
audio_output_info const* aoi = audio_output_get_info(aud);
|
|
if (!aoi) {
|
|
return;
|
|
}
|
|
|
|
std::shared_ptr<mirror_audio_data> mad;
|
|
{ // Get free audio data element.
|
|
std::lock_guard<std::mutex> capture_lock(this->m_audio_lock_capturer);
|
|
if (m_audio_data_free_queue.size() > 0) {
|
|
mad = m_audio_data_free_queue.front();
|
|
m_audio_data_free_queue.pop();
|
|
} else {
|
|
mad = std::make_shared<mirror_audio_data>();
|
|
mad->data.resize(MAX_AUDIO_CHANNELS);
|
|
for (size_t idx = 0; idx < mad->data.size(); idx++) {
|
|
mad->data[idx].resize(AUDIO_OUTPUT_FRAMES);
|
|
}
|
|
}
|
|
}
|
|
|
|
{ // Copy data
|
|
std::bitset<8> layout;
|
|
for (size_t plane = 0; plane < MAX_AV_PLANES; plane++) {
|
|
float* samples = reinterpret_cast<float_t*>(audio->data[plane]);
|
|
if (!samples) {
|
|
mad->audio.data[plane] = nullptr;
|
|
continue;
|
|
}
|
|
layout.set(plane);
|
|
|
|
memcpy(mad->data[plane].data(), audio->data[plane], audio->frames * sizeof(float_t));
|
|
mad->audio.data[plane] = reinterpret_cast<uint8_t*>(mad->data[plane].data());
|
|
}
|
|
mad->audio.format = aoi->format;
|
|
mad->audio.frames = audio->frames;
|
|
mad->audio.timestamp = audio->timestamp;
|
|
mad->audio.samples_per_sec = aoi->samples_per_sec;
|
|
mad->audio.speakers = aoi->speakers;
|
|
}
|
|
|
|
{ // Push used audio data element.
|
|
std::lock_guard<std::mutex> capture_lock(this->m_audio_lock_capturer);
|
|
m_audio_data_queue.push(mad);
|
|
}
|
|
|
|
{ // Signal other side.
|
|
std::lock_guard<std::mutex> output_lock(this->m_audio_lock_outputter);
|
|
this->m_audio_have_output = true;
|
|
}
|
|
this->m_audio_notify.notify_all();
|
|
}
|