obs-StreamFX/source/sources/source-mirror.cpp
Michael Fabian 'Xaymar' Dirks 5a3954ae0e project: Fix License, License headers and Copyright information
Fixes several files incorrectly stated a different license from the actual project, as well as the copyright headers included in all files. This change has no effect on the licensing terms, it should clear up a bit of confusion by contributors. Plus the files get a bit smaller, and we have less duplicated information across the entire project.

Overall the project is GPLv2 if not built with Qt, and GPLv3 if it is built with Qt. There are no parts licensed under a different license, all have been adapted from other compatible licenses into GPLv2 or GPLv3.
2023-04-05 18:59:08 +02:00

421 lines
12 KiB
C++

// AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2019-2023 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// Copyright (C) 2022 lainon <GermanAizek@yandex.ru>
// AUTOGENERATED COPYRIGHT HEADER END
#include "source-mirror.hpp"
#include "strings.hpp"
#include <bitset>
#include <cstring>
#include <functional>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <vector>
#include "obs/gs/gs-helper.hpp"
#include "obs/obs-source-tracker.hpp"
#include "obs/obs-tools.hpp"
#include "util/util-logging.hpp"
#ifdef _DEBUG
#define ST_PREFIX "<%s> "
#define D_LOG_ERROR(x, ...) P_LOG_ERROR(ST_PREFIX##x, __FUNCTION_SIG__, __VA_ARGS__)
#define D_LOG_WARNING(x, ...) P_LOG_WARN(ST_PREFIX##x, __FUNCTION_SIG__, __VA_ARGS__)
#define D_LOG_INFO(x, ...) P_LOG_INFO(ST_PREFIX##x, __FUNCTION_SIG__, __VA_ARGS__)
#define D_LOG_DEBUG(x, ...) P_LOG_DEBUG(ST_PREFIX##x, __FUNCTION_SIG__, __VA_ARGS__)
#else
#define ST_PREFIX "<source::mirror> "
#define D_LOG_ERROR(...) P_LOG_ERROR(ST_PREFIX __VA_ARGS__)
#define D_LOG_WARNING(...) P_LOG_WARN(ST_PREFIX __VA_ARGS__)
#define D_LOG_INFO(...) P_LOG_INFO(ST_PREFIX __VA_ARGS__)
#define D_LOG_DEBUG(...) P_LOG_DEBUG(ST_PREFIX __VA_ARGS__)
#endif
// OBS
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4464)
#pragma warning(disable : 4820)
#pragma warning(disable : 5220)
#else
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wall"
#pragma GCC diagnostic ignored "-Wextra"
#endif
#include <media-io/audio-io.h>
#ifdef _MSC_VER
#pragma warning(pop)
#else
#pragma GCC diagnostic pop
#endif
#define ST_I18N "Source.Mirror"
#define ST_I18N_SOURCE ST_I18N ".Source"
#define ST_KEY_SOURCE "Source.Mirror.Source"
#define ST_I18N_SOURCE_AUDIO ST_I18N_SOURCE ".Audio"
#define ST_KEY_SOURCE_AUDIO "Source.Mirror.Audio"
#define ST_I18N_SOURCE_AUDIO_LAYOUT ST_I18N_SOURCE_AUDIO ".Layout"
#define ST_KEY_SOURCE_AUDIO_LAYOUT "Source.Mirror.Audio.Layout"
#define ST_I18N_SOURCE_AUDIO_LAYOUT_(x) ST_I18N_SOURCE_AUDIO_LAYOUT "." D_VSTR(x)
using namespace streamfx::source::mirror;
static constexpr std::string_view HELP_URL = "https://github.com/Xaymar/obs-StreamFX/wiki/Source-Mirror";
mirror_audio_data::mirror_audio_data(const audio_data* audio, speaker_layout layout)
{
// Build a clone of a packet.
audio_t* oad = obs_get_audio();
const audio_output_info* aoi = audio_output_get_info(oad);
osa.frames = audio->frames;
osa.timestamp = audio->timestamp;
osa.speakers = layout;
osa.format = aoi->format;
osa.samples_per_sec = aoi->samples_per_sec;
data.resize(MAX_AV_PLANES);
for (std::size_t idx = 0; idx < MAX_AV_PLANES; idx++) {
if (!audio->data[idx]) {
osa.data[idx] = nullptr;
continue;
}
data[idx].resize(audio->frames * get_audio_bytes_per_channel(osa.format));
memcpy(data[idx].data(), audio->data[idx], data[idx].size());
osa.data[idx] = data[idx].data();
}
}
mirror_instance::mirror_instance(obs_data_t* settings, obs_source_t* self)
: obs::source_instance(settings, self), _source(), _source_child(), _signal_rename(), _audio_enabled(false),
_audio_layout(SPEAKERS_UNKNOWN)
{
update(settings);
}
mirror_instance::~mirror_instance()
{
release();
}
uint32_t mirror_instance::get_width()
{
return _source_size.first ? _source_size.first : 1;
}
uint32_t mirror_instance::get_height()
{
return _source_size.second ? _source_size.second : 1;
}
void mirror_instance::load(obs_data_t* data)
{
update(data);
}
void mirror_instance::migrate(obs_data_t* data, uint64_t version)
{
switch (version) {
case 0:
obs_data_set_int(data, ST_KEY_SOURCE_AUDIO_LAYOUT, obs_data_get_int(data, "Source.Mirror.Audio.Layout"));
obs_data_unset_user_value(data, "Source.Mirror.Audio.Layout");
case STREAMFX_VERSION:
break;
}
}
void mirror_instance::update(obs_data_t* data)
{
// Audio
_audio_enabled = obs_data_get_bool(data, ST_KEY_SOURCE_AUDIO);
_audio_layout = static_cast<speaker_layout>(obs_data_get_int(data, ST_KEY_SOURCE_AUDIO_LAYOUT));
// Acquire new source.
acquire(obs_data_get_string(data, ST_KEY_SOURCE));
}
void mirror_instance::save(obs_data_t* data)
{
if (_source) {
obs_data_set_string(data, ST_KEY_SOURCE, obs_source_get_name(_source.get()));
} else {
obs_data_unset_user_value(data, ST_KEY_SOURCE);
}
}
void mirror_instance::video_tick(float_t time) {}
void mirror_instance::video_render(gs_effect_t* effect)
{
if (!_source)
return;
if ((obs_source_get_output_flags(_source.get()) & OBS_SOURCE_VIDEO) == 0)
return;
#ifdef ENABLE_PROFILING
streamfx::obs::gs::debug_marker gdmp{streamfx::obs::gs::debug_color_source, "Source Mirror '%s' for '%s'",
obs_source_get_name(_self), obs_source_get_name(_source.get())};
#endif
_source_size.first = obs_source_get_width(_source.get());
_source_size.second = obs_source_get_height(_source.get());
obs_source_video_render(_source.get());
}
void mirror_instance::enum_active_sources(obs_source_enum_proc_t cb, void* ptr)
{
if (!_source)
return;
cb(_self, _source.get(), ptr);
}
void mirror_instance::enum_all_sources(obs_source_enum_proc_t cb, void* ptr)
{
if (!_source)
return;
cb(_self, _source.get(), ptr);
}
void mirror_instance::acquire(std::string source_name)
{
try {
release();
// Find source by name if possible.
decltype(_source) source{source_name};
if ((!source) || (source == _self)) { // If we failed, just exit early.
return;
}
// Everything went well, store.
_source_child = std::make_shared<::streamfx::obs::source_active_child>(_self, source);
_source = std::move(source);
_source_size.first = obs_source_get_width(_source);
_source_size.second = obs_source_get_height(_source);
// Listen to any audio the source spews out.
if (_audio_enabled) {
_signal_audio = std::make_shared<obs::audio_signal_handler>(_source);
_signal_audio->event.add(std::bind(&mirror_instance::on_audio, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3));
}
} catch (...) {
release();
}
}
void mirror_instance::release()
{
_signal_audio.reset();
_signal_rename.reset();
_source_child.reset();
_source.release();
}
void mirror_instance::on_audio(::streamfx::obs::source, const audio_data* audio, bool)
{
// Immediately quit if there isn't any actual audio to send out.
if (!_audio_enabled) {
return;
}
// Detect Audio Layout from underlying audio.
speaker_layout detected_layout;
if (_audio_layout != SPEAKERS_UNKNOWN) {
detected_layout = _audio_layout;
} else {
std::bitset<MAX_AV_PLANES> layout_detection;
for (std::size_t idx = 0; idx < MAX_AV_PLANES; idx++) {
layout_detection.set(idx, audio->data[idx] != nullptr);
}
switch (layout_detection.to_ulong()) {
case 0b00000001:
detected_layout = SPEAKERS_MONO;
break;
case 0b00000011:
detected_layout = SPEAKERS_STEREO;
break;
case 0b00000111:
detected_layout = SPEAKERS_2POINT1;
break;
case 0b00001111:
detected_layout = SPEAKERS_4POINT0;
break;
case 0b00011111:
detected_layout = SPEAKERS_4POINT1;
break;
case 0b00111111:
detected_layout = SPEAKERS_5POINT1;
break;
case 0b11111111:
detected_layout = SPEAKERS_7POINT1;
break;
default:
detected_layout = SPEAKERS_UNKNOWN;
break;
}
}
{
std::unique_lock<std::mutex> ul(_audio_queue_lock);
_audio_queue.emplace(audio, detected_layout);
}
// Create a clone of the audio data and push it to the thread pool.
streamfx::threadpool()->push(std::bind(&mirror_instance::audio_output, this, std::placeholders::_1), nullptr);
}
void mirror_instance::audio_output(std::shared_ptr<void> data)
{
std::unique_lock<std::mutex> ul(_audio_queue_lock);
while (_audio_queue.size() > 0) {
obs_source_output_audio(_self, &((_audio_queue.front()).osa));
_audio_queue.pop();
}
}
mirror_factory::mirror_factory()
{
_info.id = S_PREFIX "source-mirror";
_info.type = OBS_SOURCE_TYPE_INPUT;
_info.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW | OBS_SOURCE_AUDIO;
support_active_child_sources(true);
support_child_sources(true);
finish_setup();
register_proxy("obs-stream-effects-source-mirror");
}
mirror_factory::~mirror_factory() {}
const char* mirror_factory::get_name()
{
return D_TRANSLATE(ST_I18N);
}
void mirror_factory::get_defaults2(obs_data_t* data)
{
obs_data_set_default_string(data, ST_KEY_SOURCE, "");
obs_data_set_default_bool(data, ST_KEY_SOURCE_AUDIO, false);
obs_data_set_default_int(data, ST_KEY_SOURCE_AUDIO_LAYOUT, static_cast<int64_t>(SPEAKERS_UNKNOWN));
}
static bool modified_properties(obs_properties_t* pr, obs_property_t* p, obs_data_t* data) noexcept
{
try {
if (obs_properties_get(pr, ST_KEY_SOURCE_AUDIO) == p) {
bool show = obs_data_get_bool(data, ST_KEY_SOURCE_AUDIO);
obs_property_set_visible(obs_properties_get(pr, ST_KEY_SOURCE_AUDIO_LAYOUT), show);
return true;
}
return false;
} catch (...) {
return false;
}
}
obs_properties_t* mirror_factory::get_properties2(mirror_instance* data)
{
obs_properties_t* pr = obs_properties_create();
obs_property_t* p = nullptr;
#ifdef ENABLE_FRONTEND
{
obs_properties_add_button2(pr, S_MANUAL_OPEN, D_TRANSLATE(S_MANUAL_OPEN),
streamfx::source::mirror::mirror_factory::on_manual_open, nullptr);
}
#endif
{
p = obs_properties_add_list(pr, ST_KEY_SOURCE, D_TRANSLATE(ST_I18N_SOURCE), OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_STRING);
obs_property_set_modified_callback(p, modified_properties);
obs_property_list_add_string(p, "", "");
obs::source_tracker::get()->enumerate(
[&p](std::string name, ::streamfx::obs::source) {
std::stringstream sstr;
sstr << name << " (" << D_TRANSLATE(S_SOURCETYPE_SOURCE) << ")";
obs_property_list_add_string(p, sstr.str().c_str(), name.c_str());
return false;
},
obs::source_tracker::filter_sources);
obs::source_tracker::get()->enumerate(
[&p](std::string name, ::streamfx::obs::source) {
std::stringstream sstr;
sstr << name << " (" << D_TRANSLATE(S_SOURCETYPE_SCENE) << ")";
obs_property_list_add_string(p, sstr.str().c_str(), name.c_str());
return false;
},
obs::source_tracker::filter_scenes);
}
{
p = obs_properties_add_bool(pr, ST_KEY_SOURCE_AUDIO, D_TRANSLATE(ST_I18N_SOURCE_AUDIO));
obs_property_set_modified_callback(p, modified_properties);
}
{
p = obs_properties_add_list(pr, ST_KEY_SOURCE_AUDIO_LAYOUT, D_TRANSLATE(ST_I18N_SOURCE_AUDIO_LAYOUT),
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_SOURCE_AUDIO_LAYOUT_(Unknown)),
static_cast<int64_t>(SPEAKERS_UNKNOWN));
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_SOURCE_AUDIO_LAYOUT_(Mono)),
static_cast<int64_t>(SPEAKERS_MONO));
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_SOURCE_AUDIO_LAYOUT_(Stereo)),
static_cast<int64_t>(SPEAKERS_STEREO));
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_SOURCE_AUDIO_LAYOUT_(StereoLFE)),
static_cast<int64_t>(SPEAKERS_2POINT1));
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_SOURCE_AUDIO_LAYOUT_(Quadraphonic)),
static_cast<int64_t>(SPEAKERS_4POINT0));
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_SOURCE_AUDIO_LAYOUT_(QuadraphonicLFE)),
static_cast<int64_t>(SPEAKERS_4POINT1));
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_SOURCE_AUDIO_LAYOUT_(Surround)),
static_cast<int64_t>(SPEAKERS_5POINT1));
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_SOURCE_AUDIO_LAYOUT_(FullSurround)),
static_cast<int64_t>(SPEAKERS_7POINT1));
}
return pr;
}
#ifdef ENABLE_FRONTEND
bool mirror_factory::on_manual_open(obs_properties_t* props, obs_property_t* property, void* data)
{
try {
streamfx::open_url(HELP_URL);
return false;
} catch (const std::exception& ex) {
D_LOG_ERROR("Failed to open manual due to error: %s", ex.what());
return false;
} catch (...) {
D_LOG_ERROR("Failed to open manual due to unknown error.", "");
return false;
}
}
#endif
std::shared_ptr<mirror_factory> _source_mirror_factory_instance;
void streamfx::source::mirror::mirror_factory::initialize()
{
try {
if (!_source_mirror_factory_instance)
_source_mirror_factory_instance = std::make_shared<mirror_factory>();
} catch (const std::exception& ex) {
D_LOG_ERROR("Failed to initialize due to error: %s", ex.what());
} catch (...) {
D_LOG_ERROR("Failed to initialize due to unknown error.", "");
}
}
void streamfx::source::mirror::mirror_factory::finalize()
{
_source_mirror_factory_instance.reset();
}
std::shared_ptr<mirror_factory> streamfx::source::mirror::mirror_factory::get()
{
return std::shared_ptr<mirror_factory>();
}