/* * 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 #include #include #include #include #include #include #include "obs/gs/gs-helper.hpp" #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 #include #include #ifdef _MSC_VER #pragma warning(pop) #endif #define ST "Source.Mirror" #define ST_SOURCE ST ".Source" #define ST_SOURCE_AUDIO ST_SOURCE ".Audio" #define ST_SOURCE_AUDIO_LAYOUT ST_SOURCE_AUDIO ".Layout" #define ST_SOURCE_AUDIO_LAYOUT_(x) ST_SOURCE_AUDIO_LAYOUT "." D_VSTR(x) using namespace source; void mirror::mirror_instance::release() { _source_item.reset(); if (_source) { _source->events.rename.clear(); _source->events.audio_data.clear(); } _source.reset(); _source_name.clear(); } void mirror::mirror_instance::acquire(std::string source_name) { using namespace std::placeholders; // Try and get source by name. std::shared_ptr source = std::shared_ptr( obs_get_source_by_name(source_name.c_str()), [](obs_source_t* ref) { obs_source_release(ref); }); if (!source) { // If we failed, just exit early. return; } else if (source.get() == _self) { // Otherwise, if we somehow found self, also early exit. return; } // We seem to have a true link to a source, let's add it to our rendering. obs_sceneitem_t* item = obs_scene_add(obs_scene_from_source(_scene.get()), source.get()); if (!item) { // Can't add this source to our scene, probably due to one or more issues with it. return; } // It seems everything has worked out, so let's update our state. _source = std::make_shared(source.get(), true, true); _source_name = obs_source_get_name(source.get()); _source_item = std::shared_ptr(item, [](obs_sceneitem_t* ref) { obs_sceneitem_remove(ref); }); // And let's hook up all our events too. _source->events.rename.add(std::bind(&mirror::mirror_instance::on_source_rename, this, _1, _2, _3)); if ((obs_source_get_output_flags(this->_source->get()) & OBS_SOURCE_AUDIO) != 0) _source->events.audio_data.add(std::bind(&mirror::mirror_instance::on_audio_data, this, _1, _2, _3)); } mirror::mirror_instance::mirror_instance(obs_data_t* settings, obs_source_t* self) : obs::source_instance(settings, self), _source(), _source_name(), _audio_enabled(), _audio_layout(), _audio_kill_thread(), _audio_have_output() { // Create Internal Scene _scene = std::shared_ptr(obs_scene_get_source(obs_scene_create_private("")), [](obs_source_t* ref) { obs_source_release(ref); }); // Spawn Audio Thread _audio_thread = std::thread(std::bind(&mirror::mirror_instance::audio_output_cb, this)); update(settings); } mirror::mirror_instance::~mirror_instance() { release(); // Kill Audio Thread _audio_kill_thread = true; _audio_notify.notify_all(); if (_audio_thread.joinable()) { _audio_thread.join(); } // Delete Internal Scene _scene.reset(); } uint32_t mirror::mirror_instance::get_width() { return _source_size.first; } uint32_t mirror::mirror_instance::get_height() { return _source_size.second; } static void convert_config(obs_data_t* data) { uint64_t version = static_cast(obs_data_get_int(data, S_VERSION)); switch (version) { case 0: obs_data_set_int(data, ST_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; } obs_data_set_int(data, S_VERSION, STREAMFX_VERSION); obs_data_set_string(data, S_COMMIT, STREAMFX_COMMIT); } void mirror::mirror_instance::update(obs_data_t* data) { convert_config(data); if (this->_source_name != obs_data_get_string(data, ST_SOURCE)) { // Mirrored source was changed, release and reacquire. release(); // Acquire the new source. acquire(obs_data_get_string(data, ST_SOURCE)); } // Audio this->_audio_enabled = obs_data_get_bool(data, ST_SOURCE_AUDIO); this->_audio_layout = static_cast(obs_data_get_int(data, ST_SOURCE_AUDIO_LAYOUT)); } void mirror::mirror_instance::load(obs_data_t* data) { this->update(data); } void mirror::mirror_instance::save(obs_data_t* data) { if (_source) { obs_data_set_string(data, ST_SOURCE, obs_source_get_name(_source->get())); } } void mirror::mirror_instance::video_tick(float time) { if (_source && ((obs_source_get_output_flags(_source->get()) & OBS_SOURCE_VIDEO) != 0)) { _source_size.first = _source->width(); _source_size.second = _source->height(); } else { _source_size.first = 0; _source_size.second = 0; } if (_source_item && ((obs_source_get_output_flags(_source->get()) & OBS_SOURCE_VIDEO) != 0)) { obs_transform_info info; /// Position, Rotation, Scale, Alignment, Bounding Box vec2_set(&info.pos, 0, 0); info.rot = 0; vec2_set(&info.scale, 1., 1.); info.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; vec2_set(&info.bounds, static_cast(_source_size.first), static_cast(_source_size.second)); info.bounds_alignment = 0; info.bounds_type = OBS_BOUNDS_STRETCH; obs_sceneitem_set_info(_source_item.get(), &info); obs_sceneitem_force_update_transform(_source_item.get()); obs_sceneitem_set_scale_filter(_source_item.get(), OBS_SCALE_DISABLE); } } void mirror::mirror_instance::video_render(gs_effect_t* effect) { if (!_source || !_source_item) return; if ((obs_source_get_output_flags(_source->get()) & OBS_SOURCE_VIDEO) == 0) return; obs_source_video_render(_scene.get()); } void mirror::mirror_instance::audio_output_cb() noexcept try { std::unique_lock ulock(this->_audio_lock_outputter); while (!this->_audio_kill_thread) { this->_audio_notify.wait(ulock, [this]() { return this->_audio_have_output || this->_audio_kill_thread; }); if (this->_audio_have_output) { // Get used audio element std::shared_ptr mad; { std::lock_guard capture_lock(this->_audio_lock_capturer); if (_audio_data_queue.size() > 0) { mad = _audio_data_queue.front(); _audio_data_queue.pop(); } if (_audio_data_queue.size() == 0) { this->_audio_have_output = false; } } if (mad) { ulock.unlock(); obs_source_output_audio(this->_self, &mad->audio); ulock.lock(); { std::lock_guard capture_lock(this->_audio_lock_capturer); _audio_data_free_queue.push(mad); } } } } } catch (const std::exception& ex) { LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); } catch (...) { LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); } void mirror::mirror_instance::enum_active_sources(obs_source_enum_proc_t enum_callback, void* param) { /* if (_scene) { enum_callback(_self, _scene.get(), param); }*/ if (_source) { enum_callback(_self, _source->get(), param); } } void mirror::mirror_instance::enum_all_sources(obs_source_enum_proc_t enum_callback, void* param) { /* if (_scene) { enum_callback(_self, _scene.get(), param); }*/ if (_source) { enum_callback(_self, _source->get(), param); } } void mirror::mirror_instance::on_source_rename(obs::deprecated_source* source, std::string, std::string) { obs_source_save(_self); } void mirror::mirror_instance::on_audio_data(obs::deprecated_source*, const audio_data* audio, bool) { if (!this->_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 mad; { // Get free audio data element. std::lock_guard capture_lock(this->_audio_lock_capturer); if (_audio_data_free_queue.size() > 0) { mad = _audio_data_free_queue.front(); _audio_data_free_queue.pop(); } else { mad = std::make_shared(); 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(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(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; if (this->_audio_layout != SPEAKERS_UNKNOWN) { mad->audio.speakers = this->_audio_layout; } else { mad->audio.speakers = aoi->speakers; } } { // Push used audio data element. std::lock_guard capture_lock(this->_audio_lock_capturer); _audio_data_queue.push(mad); } { // Signal other side. std::lock_guard output_lock(this->_audio_lock_outputter); this->_audio_have_output = true; } this->_audio_notify.notify_all(); } std::shared_ptr mirror::mirror_factory::factory_instance; mirror::mirror_factory::mirror_factory() { _info.id = "obs-stream-effects-source-mirror"; _info.type = OBS_SOURCE_TYPE_INPUT; _info.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_AUDIO; set_have_active_child_sources(true); finish_setup(); } mirror::mirror_factory::~mirror_factory() {} const char* mirror::mirror_factory::get_name() { return D_TRANSLATE(ST); } void mirror::mirror_factory::get_defaults2(obs_data_t* data) { obs_data_set_default_string(data, ST_SOURCE, ""); obs_data_set_default_bool(data, ST_SOURCE_AUDIO, false); obs_data_set_default_int(data, ST_SOURCE_AUDIO_LAYOUT, static_cast(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_SOURCE_AUDIO) == p) { bool show = obs_data_get_bool(data, ST_SOURCE_AUDIO); obs_property_set_visible(obs_properties_get(pr, ST_SOURCE_AUDIO_LAYOUT), show); return true; } return false; } catch (...) { return false; } obs_properties_t* mirror::mirror_factory::get_properties2(mirror::mirror_instance* data) { obs_properties_t* pr = obs_properties_create(); obs_property_t* p = nullptr; { p = obs_properties_add_list(pr, ST_SOURCE, D_TRANSLATE(ST_SOURCE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_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*) { 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, obs_source_t*) { 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_SOURCE_AUDIO, D_TRANSLATE(ST_SOURCE_AUDIO)); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_SOURCE_AUDIO))); obs_property_set_modified_callback(p, modified_properties); } { p = obs_properties_add_list(pr, ST_SOURCE_AUDIO_LAYOUT, D_TRANSLATE(ST_SOURCE_AUDIO_LAYOUT), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); obs_property_list_add_int(p, D_TRANSLATE(ST_SOURCE_AUDIO_LAYOUT_(Unknown)), static_cast(SPEAKERS_UNKNOWN)); obs_property_list_add_int(p, D_TRANSLATE(ST_SOURCE_AUDIO_LAYOUT_(Mono)), static_cast(SPEAKERS_MONO)); obs_property_list_add_int(p, D_TRANSLATE(ST_SOURCE_AUDIO_LAYOUT_(Stereo)), static_cast(SPEAKERS_STEREO)); obs_property_list_add_int(p, D_TRANSLATE(ST_SOURCE_AUDIO_LAYOUT_(StereoLFE)), static_cast(SPEAKERS_2POINT1)); obs_property_list_add_int(p, D_TRANSLATE(ST_SOURCE_AUDIO_LAYOUT_(Quadraphonic)), static_cast(SPEAKERS_4POINT0)); obs_property_list_add_int(p, D_TRANSLATE(ST_SOURCE_AUDIO_LAYOUT_(QuadraphonicLFE)), static_cast(SPEAKERS_4POINT1)); obs_property_list_add_int(p, D_TRANSLATE(ST_SOURCE_AUDIO_LAYOUT_(Surround)), static_cast(SPEAKERS_5POINT1)); obs_property_list_add_int(p, D_TRANSLATE(ST_SOURCE_AUDIO_LAYOUT_(FullSurround)), static_cast(SPEAKERS_7POINT1)); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_SOURCE_AUDIO_LAYOUT))); } return pr; }