/* * Modern effects for a modern Streamer * Copyright (C) 2020 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 */ #pragma once #include "common.hpp" #include "plugin.hpp" namespace streamfx::obs { class encoder_instance { protected: obs_encoder_t* _self; public: encoder_instance(obs_data_t* settings, obs_encoder_t* self, bool is_hw) : _self(self) {} virtual ~encoder_instance(){}; virtual void migrate(obs_data_t* settings, uint64_t version) {} virtual bool update(obs_data_t* settings) { return false; } virtual bool encode(struct encoder_frame* frame, struct encoder_packet* packet, bool* received_packet) { auto type = obs_encoder_get_type(_self); if (type == OBS_ENCODER_VIDEO) { return encode_video(frame, packet, received_packet); } else if (type == OBS_ENCODER_AUDIO) { return encode_audio(frame, packet, received_packet); } return false; } virtual bool encode_audio(struct encoder_frame* frame, struct encoder_packet* packet, bool* received_packet) { return false; }; virtual bool encode_video(struct encoder_frame* frame, struct encoder_packet* packet, bool* received_packet) { return false; }; virtual bool encode_video(uint32_t handle, int64_t pts, uint64_t lock_key, uint64_t* next_key, struct encoder_packet* packet, bool* received_packet) { return false; }; virtual size_t get_frame_size() { return 0; } virtual bool get_extra_data(uint8_t** extra_data, size_t* size) { return false; } virtual bool get_sei_data(uint8_t** sei_data, size_t* size) { return false; } virtual void get_audio_info(struct audio_convert_info* info) {} virtual void get_video_info(struct video_scale_info* info) {} virtual obs_encoder_t* get() { return _self; } }; template class encoder_factory { public: typedef _factory factory_t; typedef _instance instance_t; protected: obs_encoder_info _info = {}; obs_encoder_info _info_fallback = {}; std::string _info_fallback_id; std::map> _proxies; std::set _proxy_names; public: encoder_factory() { _info.type_data = this; _info.get_name = _get_name; _info.create = _create_hw; _info.destroy = _destroy; _info.get_defaults2 = _get_defaults2; _info.get_properties2 = _get_properties2; _info.update = _update; _info.encode = _encode; _info.get_extra_data = _get_extra_data; _info.get_sei_data = _get_sei_data; } virtual ~encoder_factory() = default; void finish_setup() { if (_info.type == OBS_ENCODER_AUDIO) { _info.get_frame_size = _get_frame_size; _info.get_audio_info = _get_audio_info; } else if (_info.type == OBS_ENCODER_VIDEO) { _info.get_video_info = _get_video_info; } if (_info.caps & OBS_ENCODER_CAP_PASS_TEXTURE) { _info.encode_texture = _encode_texture; memcpy(&_info_fallback, &_info, sizeof(obs_encoder_info)); _info_fallback_id = std::string(_info.id) + "_sw"; _info_fallback.id = _info_fallback_id.c_str(); _info_fallback.caps = (_info_fallback.caps & ~OBS_ENCODER_CAP_PASS_TEXTURE) | OBS_ENCODER_CAP_DEPRECATED; _info_fallback.create = _create; _info_fallback.encode_texture = nullptr; obs_register_encoder(&_info_fallback); } else { _info.create = _create; } obs_register_encoder(&_info); } void register_proxy(std::string_view name) { auto iter = _proxy_names.emplace(name); // Create proxy. std::shared_ptr proxy = std::make_shared(); memcpy(proxy.get(), &_info, sizeof(obs_encoder_info)); proxy->id = iter.first->c_str(); proxy->caps |= OBS_ENCODER_CAP_DEPRECATED; obs_register_encoder(proxy.get()); _proxies.emplace(name, proxy); } private: void _migrate(obs_data_t* settings, encoder_instance* instance) { uint64_t version = static_cast(obs_data_get_int(settings, S_VERSION)); migrate(settings, version); if (instance) { instance->migrate(settings, version); } obs_data_set_int(settings, S_VERSION, static_cast(STREAMFX_VERSION)); obs_data_set_string(settings, S_COMMIT, STREAMFX_VERSION_BUILD); } private /* Factory */: static const char* _get_name(void* type_data) noexcept { try { if (type_data) return reinterpret_cast(type_data)->get_name(); return nullptr; } catch (const std::exception& ex) { DLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); return nullptr; } catch (...) { DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); return nullptr; } } static void* _create(obs_data_t* settings, obs_encoder_t* encoder) noexcept { try { auto* fac = reinterpret_cast(obs_encoder_get_type_data(encoder)); return fac->create(settings, encoder, false); } catch (const std::exception& ex) { DLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); return nullptr; } catch (...) { DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); return nullptr; } } static void* _create_hw(obs_data_t* settings, obs_encoder_t* encoder) noexcept { try { auto* fac = reinterpret_cast(obs_encoder_get_type_data(encoder)); try { return fac->create(settings, encoder, true); } catch (...) { return obs_encoder_create_rerouted(encoder, fac->_info_fallback.id); } } catch (const std::exception& ex) { DLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); return nullptr; } catch (...) { DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); return nullptr; } } static void _get_defaults2(obs_data_t* settings, void* type_data) noexcept { try { if (type_data) reinterpret_cast(type_data)->get_defaults2(settings); } catch (const std::exception& ex) { DLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); } catch (...) { DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); } } static bool _properties_migrate_settings(void* priv, obs_properties_t*, obs_property_t* p, obs_data_t* settings) noexcept { try { obs_property_set_visible(p, false); reinterpret_cast(priv)->_migrate(settings, nullptr); return true; } catch (const std::exception& ex) { DLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); return false; } catch (...) { DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); return false; } } static obs_properties_t* _get_properties2(void* data, void* type_data) noexcept { try { if (type_data) { auto props = reinterpret_cast(type_data)->get_properties2(reinterpret_cast(data)); { // Support for permanent settings migration. auto p = obs_properties_add_int( props, S_VERSION, "If you can see this, something went horribly wrong.", std::numeric_limits::lowest(), std::numeric_limits::max(), 1); obs_property_set_modified_callback2(p, _properties_migrate_settings, type_data); } return props; } return nullptr; } catch (const std::exception& ex) { DLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); return nullptr; } catch (...) { DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); return nullptr; } } private /* Instance */: static void _destroy(void* data) noexcept { try { if (data) delete reinterpret_cast(data); } catch (const std::exception& ex) { DLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); } catch (...) { DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); } } static bool _update(void* data, obs_data_t* settings) noexcept { try { auto priv = reinterpret_cast(data); if (priv) { reinterpret_cast(obs_encoder_get_type_data(priv->get()))->_migrate(settings, priv); return priv->update(settings); } return false; } catch (const std::exception& ex) { DLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); return false; } catch (...) { DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); return false; } } static bool _encode(void* data, struct encoder_frame* frame, struct encoder_packet* packet, bool* received_packet) noexcept { try { if (data) return reinterpret_cast(data)->encode_video(frame, packet, received_packet); return false; } catch (const std::exception& ex) { DLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); return false; } catch (...) { DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); return false; } } static bool _encode_texture(void* data, uint32_t handle, int64_t pts, uint64_t lock_key, uint64_t* next_key, struct encoder_packet* packet, bool* received_packet) noexcept { try { if (data) return reinterpret_cast(data)->encode_video(handle, pts, lock_key, next_key, packet, received_packet); return false; } catch (const std::exception& ex) { DLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); return false; } catch (...) { DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); return false; } } static size_t _get_frame_size(void* data) noexcept { try { if (data) return reinterpret_cast(data)->get_frame_size(); return 0; } catch (const std::exception& ex) { DLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); return 0; } catch (...) { DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); return 0; } } static bool _get_extra_data(void* data, uint8_t** extra_data, size_t* size) noexcept { try { if (data) return reinterpret_cast(data)->get_extra_data(extra_data, size); return false; } catch (const std::exception& ex) { DLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); return false; } catch (...) { DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); return false; } } static bool _get_sei_data(void* data, uint8_t** sei_data, size_t* size) noexcept { try { if (data) return reinterpret_cast(data)->get_sei_data(sei_data, size); return false; } catch (const std::exception& ex) { DLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); return false; } catch (...) { DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); return false; } } static void _get_audio_info(void* data, struct audio_convert_info* info) noexcept { try { if (data) reinterpret_cast(data)->get_audio_info(info); } catch (const std::exception& ex) { DLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); } catch (...) { DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); } } static void _get_video_info(void* data, struct video_scale_info* info) noexcept { try { if (data) reinterpret_cast(data)->get_video_info(info); } catch (const std::exception& ex) { DLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); } catch (...) { DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); } } public: virtual const char* get_name() { return "Not Yet Implemented"; } virtual void* create(obs_data_t* settings, obs_encoder_t* encoder, bool is_hw) { return reinterpret_cast(new instance_t(settings, encoder, is_hw)); } virtual void get_defaults2(obs_data_t* data) {} virtual void migrate(obs_data_t* data, uint64_t version) {} virtual obs_properties_t* get_properties2(instance_t* data) { return nullptr; } }; } // namespace streamfx::obs