// Copyright (c) 2021 Michael Fabian Dirks // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #include "encoder-aom-av1.hpp" #include #include #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 " " #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 #define ST_I18N "Encoder.AOM.AV1" // Preset #define ST_I18N_ENCODER ST_I18N ".Encoder" #define ST_I18N_ENCODER_USAGE ST_I18N_ENCODER ".Usage" #define ST_I18N_ENCODER_USAGE_GOODQUALITY ST_I18N_ENCODER_USAGE ".GoodQuality" #define ST_I18N_ENCODER_USAGE_REALTIME ST_I18N_ENCODER_USAGE ".RealTime" #define ST_I18N_ENCODER_USAGE_ALLINTRA ST_I18N_ENCODER_USAGE ".AllIntra" #define ST_KEY_ENCODER_USAGE "Encoder.Usage" #define ST_I18N_ENCODER_CPUUSAGE ST_I18N_ENCODER ".CPUUsage" #define ST_I18N_ENCODER_CPUUSAGE_0 ST_I18N_ENCODER ".CPUUsage.0" #define ST_I18N_ENCODER_CPUUSAGE_1 ST_I18N_ENCODER ".CPUUsage.1" #define ST_I18N_ENCODER_CPUUSAGE_2 ST_I18N_ENCODER ".CPUUsage.2" #define ST_I18N_ENCODER_CPUUSAGE_3 ST_I18N_ENCODER ".CPUUsage.3" #define ST_I18N_ENCODER_CPUUSAGE_4 ST_I18N_ENCODER ".CPUUsage.4" #define ST_I18N_ENCODER_CPUUSAGE_5 ST_I18N_ENCODER ".CPUUsage.5" #define ST_I18N_ENCODER_CPUUSAGE_6 ST_I18N_ENCODER ".CPUUsage.6" #define ST_I18N_ENCODER_CPUUSAGE_7 ST_I18N_ENCODER ".CPUUsage.7" #define ST_I18N_ENCODER_CPUUSAGE_8 ST_I18N_ENCODER ".CPUUsage.8" #define ST_I18N_ENCODER_CPUUSAGE_9 ST_I18N_ENCODER ".CPUUsage.9" #define ST_I18N_ENCODER_CPUUSAGE_10 ST_I18N_ENCODER ".CPUUsage.10" #define ST_KEY_ENCODER_CPUUSAGE "Encoder.CPUUsage" #define ST_KEY_ENCODER_PROFILE "Encoder.Profile" // Rate Control #define ST_I18N_RATECONTROL ST_I18N ".RateControl" #define ST_I18N_RATECONTROL_MODE ST_I18N_RATECONTROL ".Mode" #define ST_I18N_RATECONTROL_MODE_CBR ST_I18N_RATECONTROL_MODE ".CBR" #define ST_I18N_RATECONTROL_MODE_VBR ST_I18N_RATECONTROL_MODE ".VBR" #define ST_I18N_RATECONTROL_MODE_CQ ST_I18N_RATECONTROL_MODE ".CQ" #define ST_I18N_RATECONTROL_MODE_Q ST_I18N_RATECONTROL_MODE ".Q" #define ST_KEY_RATECONTROL_MODE "RateControl.Mode" #define ST_I18N_RATECONTROL_LOOKAHEAD ST_I18N_RATECONTROL ".LookAhead" #define ST_KEY_RATECONTROL_LOOKAHEAD "RateControl.LookAhead" #define ST_I18N_RATECONTROL_LIMITS ST_I18N_RATECONTROL ".Limits" #define ST_I18N_RATECONTROL_LIMITS_BITRATE ST_I18N_RATECONTROL_LIMITS ".Bitrate" #define ST_KEY_RATECONTROL_LIMITS_BITRATE "RateControl.Limits.Bitrate" #define ST_I18N_RATECONTROL_LIMITS_BITRATE_UNDERSHOOT ST_I18N_RATECONTROL_LIMITS_BITRATE ".Undershoot" #define ST_KEY_RATECONTROL_LIMITS_BITRATE_UNDERSHOOT "RateControl.Limits.Bitrate.Undershoot" #define ST_I18N_RATECONTROL_LIMITS_BITRATE_OVERSHOOT ST_I18N_RATECONTROL_LIMITS_BITRATE ".Overshoot" #define ST_KEY_RATECONTROL_LIMITS_BITRATE_OVERSHOOT "RateControl.Limits.Bitrate.Overshoot" #define ST_I18N_RATECONTROL_LIMITS_QUALITY ST_I18N_RATECONTROL_LIMITS ".Quality" #define ST_KEY_RATECONTROL_LIMITS_QUALITY "RateControl.Limits.Quality" #define ST_I18N_RATECONTROL_LIMITS_QUANTIZER_MINIMUM ST_I18N_RATECONTROL_LIMITS ".Quantizer.Minimum" #define ST_KEY_RATECONTROL_LIMITS_QUANTIZER_MINIMUM "RateControl.Limits.Quantizer.Minimum" #define ST_I18N_RATECONTROL_LIMITS_QUANTIZER_MAXIMUM ST_I18N_RATECONTROL_LIMITS ".Quantizer.Maximum" #define ST_KEY_RATECONTROL_LIMITS_QUANTIZER_MAXIMUM "RateControl.Limits.Quantizer.Maximum" #define ST_I18N_RATECONTROL_BUFFER ST_I18N_RATECONTROL ".Buffer" #define ST_I18N_RATECONTROL_BUFFER_SIZE ST_I18N_RATECONTROL_BUFFER ".Size" #define ST_KEY_RATECONTROL_BUFFER_SIZE "RateControl.Buffer.Size" #define ST_I18N_RATECONTROL_BUFFER_SIZE_INITIAL ST_I18N_RATECONTROL_BUFFER_SIZE ".Initial" #define ST_KEY_RATECONTROL_BUFFER_SIZE_INITIAL "RateControl.Buffer.Size.Initial" #define ST_I18N_RATECONTROL_BUFFER_SIZE_OPTIMAL ST_I18N_RATECONTROL_BUFFER_SIZE ".Optimal" #define ST_KEY_RATECONTROL_BUFFER_SIZE_OPTIMAL "RateControl.Buffer.Size.Optimal" // Key-Frames #define ST_I18N_KEYFRAMES ST_I18N ".KeyFrames" #define ST_I18N_KEYFRAMES_INTERVALTYPE ST_I18N_KEYFRAMES ".IntervalType" #define ST_I18N_KEYFRAMES_INTERVALTYPE_SECONDS ST_I18N_KEYFRAMES_INTERVALTYPE ".Seconds" #define ST_I18N_KEYFRAMES_INTERVALTYPE_FRAMES ST_I18N_KEYFRAMES_INTERVALTYPE ".Frames" #define ST_KEY_KEYFRAMES_INTERVALTYPE "KeyFrames.IntervalType" #define ST_I18N_KEYFRAMES_INTERVAL ST_I18N_KEYFRAMES ".Interval" #define ST_KEY_KEYFRAMES_INTERVAL_SECONDS "KeyFrames.Interval.Seconds" #define ST_KEY_KEYFRAMES_INTERVAL_FRAMES "KeyFrames.Interval.Frames" // Advanced #define ST_I18N_ADVANCED ST_I18N ".Advanced" #define ST_I18N_ADVANCED_THREADS ST_I18N_ADVANCED ".Threads" #define ST_KEY_ADVANCED_THREADS "Advanced.Threads" #define ST_I18N_ADVANCED_ROWMULTITHREADING ST_I18N_ADVANCED ".RowMultiThreading" #define ST_KEY_ADVANCED_ROWMULTITHREADING "Advanced.RowMultiThreading" #define ST_I18N_ADVANCED_TILE_COLUMNS ST_I18N_ADVANCED ".Tile.Columns" #define ST_KEY_ADVANCED_TILE_COLUMNS "Advanced.Tile.Columns" #define ST_I18N_ADVANCED_TILE_ROWS ST_I18N_ADVANCED ".Tile.Rows" #define ST_KEY_ADVANCED_TILE_ROWS "Advanced.Tile.Rows" #define ST_I18N_ADVANCED_TUNE ST_I18N_ADVANCED ".Tune" #define ST_I18N_ADVANCED_TUNE_METRIC ST_I18N_ADVANCED_TUNE ".Metric" #define ST_I18N_ADVANCED_TUNE_METRIC_PSNR ST_I18N_ADVANCED_TUNE_METRIC ".PSNR" #define ST_I18N_ADVANCED_TUNE_METRIC_SSIM ST_I18N_ADVANCED_TUNE_METRIC ".SSIM" #define ST_I18N_ADVANCED_TUNE_METRIC_VMAFWITHPREPROCESSING ST_I18N_ADVANCED_TUNE_METRIC ".VMAF.WithPreprocessing" #define ST_I18N_ADVANCED_TUNE_METRIC_VMAFWITHOUTPREPROCESSING ST_I18N_ADVANCED_TUNE_METRIC ".VMAF.WithoutPreProcessing" #define ST_I18N_ADVANCED_TUNE_METRIC_VMAFMAXGAIN ST_I18N_ADVANCED_TUNE_METRIC ".VMAF.MaxGain" #define ST_I18N_ADVANCED_TUNE_METRIC_VMAFNEGMAXGAIN ST_I18N_ADVANCED_TUNE_METRIC ".VMAF.NegMaxGain" #define ST_I18N_ADVANCED_TUNE_METRIC_BUTTERAUGLI ST_I18N_ADVANCED_TUNE_METRIC ".Butteraugli" #define ST_KEY_ADVANCED_TUNE_METRIC "Advanced.Tune.Metric" #define ST_I18N_ADVANCED_TUNE_CONTENT ST_I18N_ADVANCED_TUNE ".Content" #define ST_I18N_ADVANCED_TUNE_CONTENT_SCREEN ST_I18N_ADVANCED_TUNE_CONTENT ".Screen" #define ST_I18N_ADVANCED_TUNE_CONTENT_FILM ST_I18N_ADVANCED_TUNE_CONTENT ".Film" #define ST_KEY_ADVANCED_TUNE_CONTENT "Advanced.Tune.Content" using namespace streamfx::encoder::aom::av1; static constexpr std::string_view HELP_URL = "https://github.com/Xaymar/obs-StreamFX/wiki/Encoder-AOM-AV1"; const char* obs_video_format_to_string(video_format format) { switch (format) { case VIDEO_FORMAT_I420: return "I420"; case VIDEO_FORMAT_NV12: return "NV12"; case VIDEO_FORMAT_YVYU: return "YVYU"; case VIDEO_FORMAT_YUY2: return "YUY"; case VIDEO_FORMAT_UYVY: return "UYVY"; case VIDEO_FORMAT_RGBA: return "RGBA"; case VIDEO_FORMAT_BGRA: return "BGRA"; case VIDEO_FORMAT_BGRX: return "BGRX"; case VIDEO_FORMAT_Y800: return "Y800"; case VIDEO_FORMAT_I444: return "I444"; case VIDEO_FORMAT_BGR3: return "BGR3"; case VIDEO_FORMAT_I422: return "I422"; case VIDEO_FORMAT_I40A: return "I40A"; case VIDEO_FORMAT_I42A: return "I42A"; case VIDEO_FORMAT_YUVA: return "YUVA"; case VIDEO_FORMAT_AYUV: return "AYUV"; default: return "Unknown"; } } const char* aom_color_format_to_string(aom_img_fmt format) { switch (format) { case AOM_IMG_FMT_AOMYV12: return "AOM-YV12"; case AOM_IMG_FMT_AOMI420: return "AOM-I420"; case AOM_IMG_FMT_YV12: return "YV12"; case AOM_IMG_FMT_I420: return "I420"; case AOM_IMG_FMT_I422: return "I422"; case AOM_IMG_FMT_I444: return "I444"; case AOM_IMG_FMT_YV1216: return "YV12-16"; case AOM_IMG_FMT_I42016: return "I420-16"; case AOM_IMG_FMT_I42216: return "I422-16"; case AOM_IMG_FMT_I44416: return "I444-16"; default: return "Unknown"; } } const char* aom_color_trc_to_string(aom_transfer_characteristics_t trc) { switch (trc) { case AOM_CICP_TC_BT_709: return "Bt.709"; case AOM_CICP_TC_BT_470_M: return "Bt.407 M"; case AOM_CICP_TC_BT_470_B_G: return "Bt.407 B/G"; case AOM_CICP_TC_BT_601: return "Bt.601"; case AOM_CICP_TC_SMPTE_240: return "SMPTE 240 M"; case AOM_CICP_TC_LINEAR: return "Linear"; case AOM_CICP_TC_LOG_100: return "Logarithmic (100:1 range)"; case AOM_CICP_TC_LOG_100_SQRT10: return "Logarithmic (100*sqrt(10):1 range)"; case AOM_CICP_TC_IEC_61966: return "IEC 61966-2-4"; case AOM_CICP_TC_BT_1361: return "Bt.1361"; case AOM_CICP_TC_SRGB: return "sRGB"; case AOM_CICP_TC_BT_2020_10_BIT: return "Bt.2020 10b"; case AOM_CICP_TC_BT_2020_12_BIT: return "Bt.2020 12b"; case AOM_CICP_TC_SMPTE_2084: return "Bt.2100 PQ"; case AOM_CICP_TC_SMPTE_428: return "SMPTE ST 428"; case AOM_CICP_TC_HLG: return "Bt.2100 HLG"; default: return "Unknown"; } } const char* aom_rc_mode_to_string(aom_rc_mode mode) { switch (mode) { case AOM_VBR: return "Variable Bitrate (VBR)"; case AOM_CBR: return "Constant Bitrate (CBR)"; case AOM_CQ: return "Constrained Quality (CQ)"; case AOM_Q: return "Constant Quality (Q)"; default: return "Unknown"; } } const char* aom_kf_mode_to_string(aom_kf_mode mode) { switch (mode) { case AOM_KF_AUTO: return "Automatic"; case AOM_KF_DISABLED: return "Disabled"; default: return "Unknown"; } } const char* aom_tune_metric_to_string(aom_tune_metric tune) { switch (tune) { case AOM_TUNE_PSNR: return "PSNR"; case AOM_TUNE_SSIM: return "SSIM"; case AOM_TUNE_VMAF_WITH_PREPROCESSING: return "VMAF w/ pre-processing"; case AOM_TUNE_VMAF_WITHOUT_PREPROCESSING: return "VMAF w/o pre-processing"; case AOM_TUNE_VMAF_MAX_GAIN: return "VMAF max. gain"; case AOM_TUNE_VMAF_NEG_MAX_GAIN: return "VMAF negative max. gain"; case AOM_TUNE_BUTTERAUGLI: return "Butteraugli"; default: return "Unknown"; } } const char* aom_tune_content_to_string(aom_tune_content tune) { switch (tune) { case AOM_CONTENT_DEFAULT: return "Default"; case AOM_CONTENT_FILM: return "Film"; case AOM_CONTENT_SCREEN: return "Screen"; default: return "Unknown"; } } aom_av1_instance::aom_av1_instance(obs_data_t* settings, obs_encoder_t* self, bool is_hw) : obs::encoder_instance(settings, self, is_hw), _factory(aom_av1_factory::get()), _iface(nullptr), _ctx(), _cfg(), _image_index(0), _images(), _global_headers(nullptr), _initialized(false), _settings() { if (is_hw) { throw std::runtime_error("Hardware encoding isn't even registered, how did you get here?"); } // Retrieve encoder interface. _iface = _factory->libaom_codec_av1_cx(); if (!_iface) { throw std::runtime_error("AOM library does not provide AV1 encoder."); } #ifdef ENABLE_PROFILING // Profilers _profiler_copy = streamfx::util::profiler::create(); _profiler_encode = streamfx::util::profiler::create(); _profiler_packet = streamfx::util::profiler::create(); #endif { // Generate Static Configuration { // OBS Information video_scale_info ovsi; video_t* video = obs_encoder_video(_self); const struct video_output_info* video_info = video_output_get_info(video); ovsi.colorspace = video_info->colorspace; ovsi.format = video_info->format; ovsi.range = video_info->range; get_video_info(&ovsi); // Video _settings.width = static_cast(obs_encoder_get_width(_self)); _settings.height = static_cast(obs_encoder_get_height(_self)); _settings.fps.num = static_cast(video_info->fps_num); _settings.fps.den = static_cast(video_info->fps_den); // Color Format switch (ovsi.format) { case VIDEO_FORMAT_I420: _settings.color_format = AOM_IMG_FMT_I420; break; case VIDEO_FORMAT_I422: _settings.color_format = AOM_IMG_FMT_I422; break; case VIDEO_FORMAT_I444: _settings.color_format = AOM_IMG_FMT_I444; break; default: throw std::runtime_error("Something went wrong figuring out our color format."); } // Color Space switch (ovsi.colorspace) { case VIDEO_CS_601: _settings.color_primaries = AOM_CICP_CP_BT_601; _settings.color_trc = AOM_CICP_TC_BT_601; _settings.color_matrix = AOM_CICP_MC_BT_601; break; case VIDEO_CS_709: _settings.color_primaries = AOM_CICP_CP_BT_709; _settings.color_trc = AOM_CICP_TC_BT_709; _settings.color_matrix = AOM_CICP_MC_BT_709; break; case VIDEO_CS_SRGB: _settings.color_primaries = AOM_CICP_CP_BT_709; _settings.color_trc = AOM_CICP_TC_SRGB; _settings.color_matrix = AOM_CICP_MC_BT_709; break; } // Color Range switch (ovsi.range) { case VIDEO_RANGE_FULL: _settings.color_range = AOM_CR_FULL_RANGE; break; case VIDEO_RANGE_PARTIAL: _settings.color_range = AOM_CR_STUDIO_RANGE; break; } // Monochrome _settings.monochrome = (video_info->format == VIDEO_FORMAT_Y800); } { // Encoder _settings.profile = static_cast(obs_data_get_int(settings, ST_KEY_ENCODER_PROFILE)); if (_settings.profile == codec::av1::profile::UNKNOWN) { // Resolve the automatic profile to a proper value. bool need_professional = (_settings.color_format == AOM_IMG_FMT_I422); bool need_high = (_settings.color_format == AOM_IMG_FMT_I444) || _settings.monochrome; if (need_professional) { _settings.profile = codec::av1::profile::PROFESSIONAL; } else if (need_high) { _settings.profile = codec::av1::profile::HIGH; } else { _settings.profile = codec::av1::profile::MAIN; } } } { // Rate Control _settings.rc_mode = static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_MODE)); _settings.rc_lookahead = static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_LOOKAHEAD)); } { // Threading if (auto threads = obs_data_get_int(settings, ST_KEY_ADVANCED_THREADS); threads > 0) { _settings.threads = static_cast(threads); } else { _settings.threads = std::thread::hardware_concurrency(); } _settings.rowmultithreading = static_cast(obs_data_get_int(settings, ST_KEY_ADVANCED_ROWMULTITHREADING)); } { // Tiling _settings.tile_columns = static_cast(obs_data_get_int(settings, ST_KEY_ADVANCED_TILE_COLUMNS)); _settings.tile_rows = static_cast(obs_data_get_int(settings, ST_KEY_ADVANCED_TILE_ROWS)); } { // Tuning if (auto v = obs_data_get_int(settings, ST_KEY_ADVANCED_TUNE_METRIC); v != -1) { _settings.tune_metric = static_cast(v); } _settings.tune_content = static_cast(obs_data_get_int(settings, ST_KEY_ADVANCED_TUNE_CONTENT)); } } // Apply Settings update(settings); // Initialize Encoder if (auto error = _factory->libaom_codec_enc_init_ver(&_ctx, _iface, &_cfg, 0, AOM_ENCODER_ABI_VERSION); error != AOM_CODEC_OK) { const char* errstr = _factory->libaom_codec_err_to_string(error); D_LOG_ERROR("Failed to initialize codec, unexpected error: %s (code %" PRIu32 ")", errstr, error); throw std::runtime_error(errstr); } { // Apply Static Control Settings { // Color Information #ifdef AOM_CTRL_AV1E_SET_COLOR_PRIMARIES if (auto error = _factory->libaom_codec_control(&_ctx, AV1E_SET_COLOR_PRIMARIES, _settings.color_primaries); error != AOM_CODEC_OK) { const char* errstr = _factory->libaom_codec_err_to_string(error); const char* err = _factory->libaom_codec_error(&_ctx); const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // "AV1E_SET_COLOR_PRIMARIES", // (errstr ? errstr : ""), error, // (err ? "\n\tMessage: " : ""), (err ? err : ""), // (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // ); } #else D_LOG_ERROR("AOM library was built without AV1E_SET_COLOR_PRIMARIES, behavior is unknown."); #endif #ifdef AOM_CTRL_AV1E_SET_TRANSFER_CHARACTERISTICS if (auto error = _factory->libaom_codec_control(&_ctx, AV1E_SET_TRANSFER_CHARACTERISTICS, _settings.color_trc); error != AOM_CODEC_OK) { const char* errstr = _factory->libaom_codec_err_to_string(error); const char* err = _factory->libaom_codec_error(&_ctx); const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // "AV1E_SET_TRANSFER_CHARACTERISTICS", // (errstr ? errstr : ""), error, // (err ? "\n\tMessage: " : ""), (err ? err : ""), // (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // ); } #else D_LOG_ERROR("AOM library was built without AV1E_SET_TRANSFER_CHARACTERISTICS, behavior is unknown."); #endif #ifdef AOM_CTRL_AV1E_SET_MATRIX_COEFFICIENTS if (auto error = _factory->libaom_codec_control(&_ctx, AV1E_SET_MATRIX_COEFFICIENTS, _settings.color_matrix); error != AOM_CODEC_OK) { const char* errstr = _factory->libaom_codec_err_to_string(error); const char* err = _factory->libaom_codec_error(&_ctx); const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // "AV1E_SET_MATRIX_COEFFICIENTS", // (errstr ? errstr : ""), error, // (err ? "\n\tMessage: " : ""), (err ? err : ""), // (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // ); } #else D_LOG_ERROR("AOM library was built without AV1E_SET_MATRIX_COEFFICIENTS, behavior is unknown."); #endif #ifdef AOM_CTRL_AV1E_SET_COLOR_RANGE if (auto error = _factory->libaom_codec_control(&_ctx, AV1E_SET_COLOR_RANGE, _settings.color_range); error != AOM_CODEC_OK) { const char* errstr = _factory->libaom_codec_err_to_string(error); const char* err = _factory->libaom_codec_error(&_ctx); const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // "AV1E_SET_COLOR_RANGE", // (errstr ? errstr : ""), error, // (err ? "\n\tMessage: " : ""), (err ? err : ""), // (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // ); } #else D_LOG_ERROR("AOM library was built without AV1_SET_COLOR_RANGE, behavior is unknown."); #endif #ifdef AOM_CTRL_AV1E_SET_CHROMA_SAMPLE_POSITION // !TODO: Consider making this user-controlled. At the moment, this follows the H.264 chroma standard. if (auto error = _factory->libaom_codec_control(&_ctx, AV1E_SET_CHROMA_SAMPLE_POSITION, AOM_CSP_VERTICAL); error != AOM_CODEC_OK) { const char* errstr = _factory->libaom_codec_err_to_string(error); const char* err = _factory->libaom_codec_error(&_ctx); const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // "AV1E_SET_CHROMA_SAMPLE_POSITION", // (errstr ? errstr : ""), error, // (err ? "\n\tMessage: " : ""), (err ? err : ""), // (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // ); } #endif #ifdef AOM_CTRL_AV1E_SET_RENDER_SIZE int32_t size[2] = {_settings.width, _settings.height}; if (auto error = _factory->libaom_codec_control(&_ctx, AV1E_SET_RENDER_SIZE, &size); error != AOM_CODEC_OK) { const char* errstr = _factory->libaom_codec_err_to_string(error); const char* err = _factory->libaom_codec_error(&_ctx); const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // "AV1E_SET_RENDER_SIZE", // (errstr ? errstr : ""), error, // (err ? "\n\tMessage: " : ""), (err ? err : ""), // (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // ); } #endif } // Apply Dynamic Control Settings if (!update(settings)) { throw std::runtime_error("Unexpected error during configuration."); } } // Preallocate global headers. _global_headers = _factory->libaom_codec_get_global_headers(&_ctx); // Allocate frames. _images.resize(_cfg.g_threads); for (auto& image : _images) { _factory->libaom_img_alloc(&image, _settings.color_format, _settings.width, _settings.height, 8); // Color Information. image.fmt = _settings.color_format; image.cp = _settings.color_primaries; image.tc = _settings.color_trc; image.mc = _settings.color_matrix; image.range = _settings.color_range; image.monochrome = _settings.monochrome ? 1 : 0; image.csp = AOM_CSP_VERTICAL; // !TODO: Consider making this user-controlled. // Size image.r_w = image.d_w; image.r_h = image.d_h; image.r_w = image.w; image.r_h = image.h; } // Log Settings log(); // Signal to future update() calls that we are fully initialized. _initialized = true; } aom_av1_instance::~aom_av1_instance() { #ifdef ENABLE_PROFILING // Profiling D_LOG_INFO("Timings | Avg. µs | 99.9ile µs | 99.0ile µs | 95.0ile µs | Samples ", ""); D_LOG_INFO("--------+---------------+---------------+---------------+---------------+----------", ""); D_LOG_INFO("Copy | %13.1f | %13" PRId64 " | %13" PRId64 " | %13" PRId64 " | %9" PRIu64, _profiler_copy->average_duration() / 1000., std::chrono::duration_cast(_profiler_copy->percentile(0.999)).count(), std::chrono::duration_cast(_profiler_copy->percentile(0.990)).count(), std::chrono::duration_cast(_profiler_copy->percentile(0.950)).count(), _profiler_copy->count()); D_LOG_INFO("Encode | %13.1f | %13" PRId64 " | %13" PRId64 " | %13" PRId64 " | %9" PRIu64, _profiler_encode->average_duration() / 1000., std::chrono::duration_cast(_profiler_encode->percentile(0.999)).count(), std::chrono::duration_cast(_profiler_encode->percentile(0.990)).count(), std::chrono::duration_cast(_profiler_encode->percentile(0.950)).count(), _profiler_encode->count()); D_LOG_INFO("Packet | %13.1f | %13" PRId64 " | %13" PRId64 " | %13" PRId64 " | %9" PRIu64, _profiler_packet->average_duration() / 1000., std::chrono::duration_cast(_profiler_packet->percentile(0.999)).count(), std::chrono::duration_cast(_profiler_packet->percentile(0.990)).count(), std::chrono::duration_cast(_profiler_packet->percentile(0.950)).count(), _profiler_packet->count()); #endif // Deallocate global buffer. if (_global_headers) { /* Breaks heap if (_global_headers->buf) { free(_global_headers->buf); _global_headers->buf = nullptr; } free(_global_headers); _global_headers = nullptr; */ } // Deallocate frames. for (auto& image : _images) { _factory->libaom_img_free(&image); } _images.clear(); // Destroy encoder. _factory->libaom_codec_destroy(&_ctx); } void aom_av1_instance::migrate(obs_data_t* settings, uint64_t version) {} bool aom_av1_instance::update(obs_data_t* settings) { video_t* obsVideo = obs_encoder_video(_self); const struct video_output_info* obsVideoInfo = video_output_get_info(obsVideo); uint32_t obsFPSnum = obsVideoInfo->fps_num; uint32_t obsFPSden = obsVideoInfo->fps_den; bool obsMonochrome = (obsVideoInfo->format == VIDEO_FORMAT_Y800); #define SET_IF_NOT_DEFAULT(X, Y) \ if (X != -1) { \ Y = static_cast(X); \ } { // Generate Dynamic Settings { // Encoder _settings.preset = static_cast(obs_data_get_int(settings, ST_KEY_ENCODER_CPUUSAGE)); } { // Rate Control _settings.rc_bitrate = static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_LIMITS_BITRATE)); _settings.rc_bitrate_overshoot = static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_LIMITS_BITRATE_UNDERSHOOT)); _settings.rc_bitrate_undershoot = static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_LIMITS_BITRATE_OVERSHOOT)); _settings.rc_quality = static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_LIMITS_QUALITY)); _settings.rc_quantizer_min = static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_LIMITS_QUANTIZER_MINIMUM)); _settings.rc_quantizer_max = static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_LIMITS_QUANTIZER_MAXIMUM)); _settings.rc_buffer_ms = static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_BUFFER_SIZE)); _settings.rc_buffer_initial_ms = static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_BUFFER_SIZE_INITIAL)); _settings.rc_buffer_optimal_ms = static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_BUFFER_SIZE_OPTIMAL)); } { // Key-Frames int64_t kf_type = obs_data_get_int(settings, ST_KEY_KEYFRAMES_INTERVALTYPE); bool is_seconds = (kf_type == 0); _settings.kf_mode = AOM_KF_AUTO; if (is_seconds) { _settings.kf_distance_max = static_cast( std::lround(obs_data_get_double(settings, ST_KEY_KEYFRAMES_INTERVAL_SECONDS) * static_cast(obsFPSnum) / static_cast(obsFPSden))); } else { _settings.kf_distance_max = static_cast(obs_data_get_int(settings, ST_KEY_KEYFRAMES_INTERVAL_FRAMES)); } _settings.kf_distance_min = _settings.kf_distance_max; } // All-Intra requires us to never set Key-Frames. if (_cfg.g_usage == AOM_USAGE_ALL_INTRA) { _settings.rc_lookahead = 0; _settings.kf_mode = AOM_KF_DISABLED; _settings.kf_distance_min = 0; _settings.kf_distance_max = 0; } } { // Configuration. { // Usage and Defaults _cfg.g_usage = static_cast(obs_data_get_int(settings, ST_KEY_ENCODER_USAGE)); _factory->libaom_codec_enc_config_default(_iface, &_cfg, _cfg.g_usage); } { // Frame Information // Size _cfg.g_w = _settings.width; _cfg.g_h = _settings.height; // Time Base (Rate is inverted Time Base) _cfg.g_timebase.num = _settings.fps.den; _cfg.g_timebase.den = _settings.fps.num; // !INFO: Whenever OBS decides to support anything but 8-bits, let me know. _cfg.g_bit_depth = AOM_BITS_8; _cfg.g_input_bit_depth = AOM_BITS_8; // Monochrome color _cfg.monochrome = _settings.monochrome ? 1 : 0; } { // Encoder // AV1 Profile _cfg.g_profile = static_cast(_settings.profile); } { // Rate Control // Mode _cfg.rc_end_usage = static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_MODE)); // Look-Ahead SET_IF_NOT_DEFAULT(_settings.rc_lookahead, _cfg.g_lag_in_frames); // Limits SET_IF_NOT_DEFAULT(_settings.rc_bitrate, _cfg.rc_target_bitrate); SET_IF_NOT_DEFAULT(_settings.rc_bitrate_overshoot, _cfg.rc_overshoot_pct); SET_IF_NOT_DEFAULT(_settings.rc_bitrate_undershoot, _cfg.rc_undershoot_pct); SET_IF_NOT_DEFAULT(_settings.rc_quantizer_min, _cfg.rc_min_quantizer); SET_IF_NOT_DEFAULT(_settings.rc_quantizer_max, _cfg.rc_max_quantizer); // Buffer SET_IF_NOT_DEFAULT(_settings.rc_buffer_ms, _cfg.rc_buf_sz); SET_IF_NOT_DEFAULT(_settings.rc_buffer_initial_ms, _cfg.rc_buf_initial_sz); SET_IF_NOT_DEFAULT(_settings.rc_buffer_optimal_ms, _cfg.rc_buf_optimal_sz); } { // Key-Frames SET_IF_NOT_DEFAULT(_settings.kf_mode, _cfg.kf_mode); SET_IF_NOT_DEFAULT(_settings.kf_distance_min, _cfg.kf_min_dist); SET_IF_NOT_DEFAULT(_settings.kf_distance_max, _cfg.kf_max_dist); } { // Advanced // Single-Pass _cfg.g_pass = AOM_RC_ONE_PASS; // Threading SET_IF_NOT_DEFAULT(_settings.threads, _cfg.g_threads); } // TODO: Future //_cfg.rc_resize_mode = 0; // "RESIZE_NONE //_cfg.rc_resize_denominator = ?; //_cfg.rc_resize_kf_denominator = ?; //_cfg.rc_superres_mode = AOM_SUPERRES_NONE; //_cfg.rc_superres_denominator = ?; //_cfg.rc_superres_kf_denominator = ?; //_cfg.rc_superres_qthresh = ?; //_cfg.rc_superres_kf_qtresh = ?; //_cfg.rc_dropframe_thresh = ?; //_cfg.fwd_kf_enabled = ?; //_cfg.g_forced_max_frame_width = ?; //_cfg.g_forced_max_frame_height = ?; //_cfg.g_error_resilient = ?; //_cfg.g_lag_in_frames = ?; //_cfg.sframe_dist = ?; //_cfg.sframe_mode = 0; //_cfg.large_scale_tile = 0; //_cfg.full_still_picture_hdr = ?; //_cfg.save_as_annexb = ?; //_cfg.encoder_cfg = ?; // Apply configuration if (_initialized) { if (auto error = _factory->libaom_codec_enc_config_set(&_ctx, &_cfg); error != AOM_CODEC_OK) { const char* errstr = _factory->libaom_codec_err_to_string(error); const char* err = _factory->libaom_codec_error(&_ctx); const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); D_LOG_WARNING("Error changing configuration: %s (code %" PRIu32 ")%s%s%s%s", // (errstr ? errstr : ""), error, // (err ? "\n\tMessage: " : ""), (err ? err : ""), // (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // ); return false; } } } if (_ctx.iface) { // Control { // Encoder #ifdef AOM_CTRL_AOME_SET_CPUUSED if (_settings.preset != -1) { if (auto error = _factory->libaom_codec_control(&_ctx, AOME_SET_CPUUSED, _settings.preset); error != AOM_CODEC_OK) { const char* errstr = _factory->libaom_codec_err_to_string(error); const char* err = _factory->libaom_codec_error(&_ctx); const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // "AOME_SET_CPUUSED", // (errstr ? errstr : ""), error, // (err ? "\n\tMessage: " : ""), (err ? err : ""), // (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // ); } } #endif } { // Rate Control #ifdef AOM_CTRL_AOME_SET_CQ_LEVEL if ((_settings.rc_quality != -1) && ((_settings.rc_mode == AOM_CQ) || (_settings.rc_mode == AOM_Q))) { if (auto error = _factory->libaom_codec_control(&_ctx, AOME_SET_CQ_LEVEL, _settings.rc_quality); error != AOM_CODEC_OK) { const char* errstr = _factory->libaom_codec_err_to_string(error); const char* err = _factory->libaom_codec_error(&_ctx); const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // "AOME_SET_CQ_LEVEL", // (errstr ? errstr : ""), error, // (err ? "\n\tMessage: " : ""), (err ? err : ""), // (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // ); } } #endif } { // Advanced #ifdef AOM_CTRL_AV1E_SET_ROW_MT if (_settings.rowmultithreading != -1) { if (auto error = _factory->libaom_codec_control(&_ctx, AV1E_SET_ROW_MT, _settings.rowmultithreading); error != AOM_CODEC_OK) { const char* errstr = _factory->libaom_codec_err_to_string(error); const char* err = _factory->libaom_codec_error(&_ctx); const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // "AV1E_SET_ROW_MT", // (errstr ? errstr : ""), error, // (err ? "\n\tMessage: " : ""), (err ? err : ""), // (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // ); } } #endif #ifdef AOM_CTRL_AV1E_SET_TILE_COLUMNS if (_settings.tile_columns != -1) { if (auto error = _factory->libaom_codec_control(&_ctx, AV1E_SET_TILE_COLUMNS, _settings.tile_columns); error != AOM_CODEC_OK) { const char* errstr = _factory->libaom_codec_err_to_string(error); const char* err = _factory->libaom_codec_error(&_ctx); const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // "AV1E_SET_TILE_COLUMNS", // (errstr ? errstr : ""), error, // (err ? "\n\tMessage: " : ""), (err ? err : ""), // (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // ); } } #endif #ifdef AOM_CTRL_AV1E_SET_TILE_ROWS if (_settings.tile_rows != -1) { if (auto error = _factory->libaom_codec_control(&_ctx, AV1E_SET_TILE_ROWS, _settings.tile_rows); error != AOM_CODEC_OK) { const char* errstr = _factory->libaom_codec_err_to_string(error); const char* err = _factory->libaom_codec_error(&_ctx); const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // "AV1E_SET_TILE_ROWS", // (errstr ? errstr : ""), error, // (err ? "\n\tMessage: " : ""), (err ? err : ""), // (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // ); } } #endif #ifdef AOM_CTRL_AOME_SET_TUNING if (_settings.tune_metric != -1) { if (auto error = _factory->libaom_codec_control(&_ctx, AOME_SET_TUNING, _settings.tune_metric); error != AOM_CODEC_OK) { const char* errstr = _factory->libaom_codec_err_to_string(error); const char* err = _factory->libaom_codec_error(&_ctx); const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // "AOME_SET_TUNING", // (errstr ? errstr : ""), error, // (err ? "\n\tMessage: " : ""), (err ? err : ""), // (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // ); } } #endif #ifdef AOM_CTRL_AV1E_SET_TUNE_CONTENT if (_settings.tune_content != AOM_CONTENT_DEFAULT) { if (auto error = _factory->libaom_codec_control(&_ctx, AV1E_SET_TUNE_CONTENT, _settings.tune_content); error != AOM_CODEC_OK) { const char* errstr = _factory->libaom_codec_err_to_string(error); const char* err = _factory->libaom_codec_error(&_ctx); const char* errdtl = _factory->libaom_codec_error_detail(&_ctx); D_LOG_WARNING("Error changing '%s': %s (code %" PRIu32 ")%s%s%s%s", // "AV1E_SET_TUNE_CONTENT", // (errstr ? errstr : ""), error, // (err ? "\n\tMessage: " : ""), (err ? err : ""), // (errdtl ? "\n\tDetails: " : ""), (errdtl ? errdtl : "") // ); } } #endif } } #undef SET_IF_NOT_DEFAULT // Log the changed settings. if (_initialized) { log(); } return true; } void aom_av1_instance::log() { D_LOG_INFO("AOM AV1:", ""); D_LOG_INFO(" Video: %" PRIu16 "x%" PRIu16 "@%1.2ffps (%" PRIu32 "/%" PRIu32 ")", _settings.width, _settings.height, static_cast(_settings.fps.num) / static_cast(_settings.fps.den), _settings.fps.num, _settings.fps.den); D_LOG_INFO(" Color: %s/%s/%s%s", aom_color_format_to_string(_settings.color_format), aom_color_trc_to_string(_settings.color_trc), _settings.color_range == AOM_CR_FULL_RANGE ? "Full" : "Partial", _settings.monochrome ? "/Monochrome" : ""); // Rate Control D_LOG_INFO(" Rate Control: %s", aom_rc_mode_to_string(_settings.rc_mode)); D_LOG_INFO(" Look-Ahead: %" PRId8, _settings.rc_lookahead); D_LOG_INFO(" Buffers: %" PRId32 " ms / %" PRId32 " ms / %" PRId32 " ms", _settings.rc_buffer_ms, _settings.rc_buffer_initial_ms, _settings.rc_buffer_optimal_ms); D_LOG_INFO(" Bitrate: %" PRId32 " kbit/s (-%" PRId32 "%% - +%" PRId32 "%%)", _settings.rc_bitrate, _settings.rc_bitrate_undershoot, _settings.rc_bitrate_overshoot); D_LOG_INFO(" Quality: %" PRId8, _settings.rc_quality); D_LOG_INFO(" Quantizer: %" PRId8 " - %" PRId8, _settings.rc_quantizer_min, _settings.rc_quantizer_max); // Key-Frames D_LOG_INFO(" Key-Frames: %s", aom_kf_mode_to_string(_settings.kf_mode)); D_LOG_INFO(" Distance: %" PRId32 " - %" PRId32 " frames", _settings.kf_distance_min, _settings.kf_distance_max); // Advanced D_LOG_INFO(" Advanced: ", ""); D_LOG_INFO(" Threads: %" PRId8, _settings.threads); D_LOG_INFO(" Row-Multi-Threading: %s", _settings.rowmultithreading == -1 ? "Default" : _settings.rowmultithreading == 1 ? "Enabled" : "Disabled"); D_LOG_INFO(" Tiling: %" PRId8 "x%" PRId8, _settings.tile_columns, _settings.tile_rows); D_LOG_INFO(" Tune: %s (Metric), %s (Content)", aom_tune_metric_to_string(_settings.tune_metric), aom_tune_content_to_string(_settings.tune_content)); } bool aom_av1_instance::get_extra_data(uint8_t** extra_data, size_t* size) { if (!_global_headers) { return false; } *extra_data = static_cast(_global_headers->buf); *size = _global_headers->sz; return true; } bool aom_av1_instance::get_sei_data(uint8_t** sei_data, size_t* size) { return get_extra_data(sei_data, size); } void aom_av1_instance::get_video_info(struct video_scale_info* info) { // Fix up color format. auto format = obs_encoder_get_preferred_video_format(_self); if (format == VIDEO_FORMAT_NONE) { format = info->format; } switch (format) { // Perfect matches. case VIDEO_FORMAT_I444: // AOM_IMG_I444. case VIDEO_FORMAT_I422: // AOM_IMG_I422. case VIDEO_FORMAT_I420: // AOM_IMG_I420. break; // 4:2:0 formats case VIDEO_FORMAT_NV12: // Y, UV Interleaved. case VIDEO_FORMAT_I40A: D_LOG_WARNING("Color-format '%s' is not supported, forcing 'I420'...", obs_video_format_to_string(format)); info->format = VIDEO_FORMAT_I420; break; // 4:2:2-like formats case VIDEO_FORMAT_UYVY: case VIDEO_FORMAT_YUY2: case VIDEO_FORMAT_YVYU: case VIDEO_FORMAT_I42A: D_LOG_WARNING("Color-format '%s' is not supported, forcing 'I422'...", obs_video_format_to_string(format)); info->format = VIDEO_FORMAT_I422; break; // 4:4:4 case VIDEO_FORMAT_BGR3: case VIDEO_FORMAT_BGRA: case VIDEO_FORMAT_BGRX: case VIDEO_FORMAT_RGBA: case VIDEO_FORMAT_YUVA: case VIDEO_FORMAT_AYUV: case VIDEO_FORMAT_Y800: // Grayscale, no exact match. D_LOG_WARNING("Color-format '%s' is not supported, forcing 'I444'...", obs_video_format_to_string(format)); info->format = VIDEO_FORMAT_I444; break; } // Fix up color space. if (info->colorspace == VIDEO_CS_DEFAULT) { info->colorspace = VIDEO_CS_SRGB; } // Fix up color range. if (info->range == VIDEO_RANGE_DEFAULT) { info->range = VIDEO_RANGE_PARTIAL; } } bool streamfx::encoder::aom::av1::aom_av1_instance::encode_video(encoder_frame* frame, encoder_packet* packet, bool* received_packet) { // Retrieve current indexed image. auto& image = _images.at(_image_index); { // Copy Image data. #ifdef ENABLE_PROFILING auto profile = _profiler_copy->track(); #endif std::memcpy(image.planes[AOM_PLANE_Y], frame->data[0], frame->linesize[0] * image.h); if (image.fmt == AOM_IMG_FMT_I420) { std::memcpy(image.planes[AOM_PLANE_U], frame->data[1], frame->linesize[1] * image.h / 2); std::memcpy(image.planes[AOM_PLANE_V], frame->data[2], frame->linesize[2] * image.h / 2); } else { std::memcpy(image.planes[AOM_PLANE_U], frame->data[1], frame->linesize[1] * image.h); std::memcpy(image.planes[AOM_PLANE_V], frame->data[2], frame->linesize[2] * image.h); } } { // Try to encode the new image. #ifdef ENABLE_PROFILING auto profile = _profiler_encode->track(); #endif aom_enc_frame_flags_t flags = 0; if (_cfg.g_usage == AOM_USAGE_ALL_INTRA) { flags = AOM_EFLAG_FORCE_KF; } if (auto error = _factory->libaom_codec_encode(&_ctx, &image, frame->pts, 1, flags); error != AOM_CODEC_OK) { const char* errstr = _factory->libaom_codec_err_to_string(error); D_LOG_ERROR("Encoding frame failed with error: %s (code %" PRIu32 ")\n%s\n%s", errstr, error, _factory->libaom_codec_error(&_ctx), _factory->libaom_codec_error_detail(&_ctx)); return false; } else { // Increment the image index. _image_index = (_image_index++) % _images.size(); } } { // Get Packet #ifdef ENABLE_PROFILING auto profile = _profiler_packet->track(); #endif aom_codec_iter_t iter = NULL; for (auto* pkt = _factory->libaom_codec_get_cx_data(&_ctx, &iter); pkt != nullptr; pkt = _factory->libaom_codec_get_cx_data(&_ctx, &iter)) { #ifdef _DEBUG { const char* kind = ""; switch (pkt->kind) { case AOM_CODEC_CX_FRAME_PKT: kind = "Frame"; break; case AOM_CODEC_STATS_PKT: kind = "Stats"; break; case AOM_CODEC_FPMB_STATS_PKT: kind = "FPMB Stats"; break; case AOM_CODEC_PSNR_PKT: kind = "PSNR"; break; case AOM_CODEC_CUSTOM_PKT: kind = "Custom"; break; } D_LOG_DEBUG("\tPacket: Kind=%s", kind) } #endif if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) { // Status packet->type = OBS_ENCODER_VIDEO; packet->keyframe = ((pkt->data.frame.flags & AOM_FRAME_IS_KEY) == AOM_FRAME_IS_KEY) || (_cfg.g_usage == AOM_USAGE_ALL_INTRA); if (packet->keyframe) { // packet->priority = 0; packet->drop_priority = packet->priority; } else if ((pkt->data.frame.flags & AOM_FRAME_IS_DROPPABLE) != AOM_FRAME_IS_DROPPABLE) { // Dropping this frame breaks the bitstream. packet->priority = -1; packet->drop_priority = packet->priority; } else { // This frame can be dropped at will. packet->priority = -2; packet->drop_priority = packet->priority; } // Data packet->data = static_cast(pkt->data.frame.buf); packet->size = pkt->data.frame.sz; // Timestamps //TODO: Temporarily set both to the same until there is a way to figure out actual order. packet->pts = pkt->data.frame.pts; packet->dts = pkt->data.frame.pts; *received_packet = true; } if (*received_packet == true) break; } if (!*received_packet) { packet->type = OBS_ENCODER_VIDEO; packet->data = nullptr; packet->size = 0; packet->pts = -1; packet->dts = -1; #ifdef _DEBUG D_LOG_DEBUG("No Packet", ""); #endif // Not necessarily an error. //return false; } else { #ifdef _DEBUG D_LOG_DEBUG("Packet: Type=%s PTS=%06" PRId64 " DTS=%06" PRId64 " Size=%016" PRIuPTR "", packet->keyframe ? "I" : "P", packet->pts, packet->dts, packet->size); #endif } } return true; } aom_av1_factory::aom_av1_factory() { // Try and load the AOM library. std::vector libs; #ifdef D_PLATFORM_WINDOWS // Try loading from the data directory first. libs.push_back(streamfx::data_file_path("aom.dll")); // MSVC (preferred) libs.push_back(streamfx::data_file_path("libaom.dll")); // Cross-Compile // In any other case, load the system-wide binary. libs.push_back("aom.dll"); libs.push_back("libaom.dll"); #else // Try loading from the data directory first. libs.push_back(streamfx::data_file_path("libaom.so")); // In any other case, load the system-wide binary. libs.push_back("libaom"); #endif for (auto lib : libs) { try { _library = streamfx::util::library::load(lib); if (_library) break; } catch (...) { D_LOG_WARNING("Loading of '%s' failed.", lib.generic_string().c_str()); } } if (!_library) { throw std::runtime_error("Unable to load AOM library."); } // Load all necessary functions. #define _LOAD_SYMBOL(X) \ { \ lib##X = reinterpret_cast(_library->load_symbol(std::string(#X))); \ } #define _LOAD_SYMBOL_(X, Y) \ { \ lib##X = reinterpret_cast(_library->load_symbol(std::string(Y))); \ } _LOAD_SYMBOL(aom_codec_version); _LOAD_SYMBOL(aom_codec_version_str); _LOAD_SYMBOL(aom_codec_version_extra_str); _LOAD_SYMBOL(aom_codec_build_config); _LOAD_SYMBOL(aom_codec_iface_name); _LOAD_SYMBOL(aom_codec_err_to_string); _LOAD_SYMBOL(aom_codec_error); _LOAD_SYMBOL(aom_codec_error_detail); _LOAD_SYMBOL(aom_codec_destroy); _LOAD_SYMBOL(aom_codec_get_caps); _LOAD_SYMBOL(aom_codec_control); _LOAD_SYMBOL(aom_codec_set_option); _LOAD_SYMBOL(aom_obu_type_to_string); _LOAD_SYMBOL(aom_uleb_size_in_bytes); _LOAD_SYMBOL(aom_uleb_decode); _LOAD_SYMBOL(aom_uleb_encode); _LOAD_SYMBOL(aom_uleb_encode_fixed_size); _LOAD_SYMBOL(aom_img_alloc); _LOAD_SYMBOL(aom_img_wrap); _LOAD_SYMBOL(aom_img_alloc_with_border); _LOAD_SYMBOL(aom_img_set_rect); _LOAD_SYMBOL(aom_img_flip); _LOAD_SYMBOL(aom_img_free); _LOAD_SYMBOL(aom_img_plane_width); _LOAD_SYMBOL(aom_img_plane_height); _LOAD_SYMBOL(aom_img_add_metadata); _LOAD_SYMBOL(aom_img_get_metadata); _LOAD_SYMBOL(aom_img_num_metadata); _LOAD_SYMBOL(aom_img_remove_metadata); _LOAD_SYMBOL(aom_img_metadata_alloc); _LOAD_SYMBOL(aom_img_metadata_free); _LOAD_SYMBOL(aom_codec_enc_init_ver); _LOAD_SYMBOL(aom_codec_enc_config_default); _LOAD_SYMBOL(aom_codec_enc_config_set); _LOAD_SYMBOL(aom_codec_get_global_headers); _LOAD_SYMBOL(aom_codec_encode); _LOAD_SYMBOL(aom_codec_set_cx_data_buf); _LOAD_SYMBOL(aom_codec_get_cx_data); _LOAD_SYMBOL(aom_codec_get_preview_frame); _LOAD_SYMBOL(aom_codec_av1_cx); #undef _LOAD_SYMBOL // Register encoder. _info.id = S_PREFIX "aom-av1"; _info.type = obs_encoder_type::OBS_ENCODER_VIDEO; _info.codec = "av1"; _info.caps = OBS_ENCODER_CAP_DYN_BITRATE; finish_setup(); } aom_av1_factory::~aom_av1_factory() {} std::shared_ptr _aom_av1_factory_instance = nullptr; void aom_av1_factory::initialize() try { if (!_aom_av1_factory_instance) { _aom_av1_factory_instance = std::make_shared(); } } catch (std::exception const& ex) { D_LOG_ERROR("Failed to initialize AOM AV1 encoder: %s", ex.what()); } void aom_av1_factory::finalize() { _aom_av1_factory_instance.reset(); } std::shared_ptr aom_av1_factory::get() { return _aom_av1_factory_instance; } const char* aom_av1_factory::get_name() { return "AV1 (via AOM)"; } void* aom_av1_factory::create(obs_data_t* settings, obs_encoder_t* encoder, bool is_hw) { return new aom_av1_instance(settings, encoder, is_hw); } void aom_av1_factory::get_defaults2(obs_data_t* settings) { { // Presets obs_data_set_default_int(settings, ST_KEY_ENCODER_USAGE, static_cast(AOM_USAGE_REALTIME)); obs_data_set_default_int(settings, ST_KEY_ENCODER_CPUUSAGE, -1); obs_data_set_default_int(settings, ST_KEY_ENCODER_PROFILE, static_cast(codec::av1::profile::UNKNOWN)); } { // Rate-Control obs_data_set_default_int(settings, ST_KEY_RATECONTROL_MODE, static_cast(AOM_CBR)); // Limits obs_data_set_default_int(settings, ST_KEY_RATECONTROL_LIMITS_BITRATE, 6000); obs_data_set_default_int(settings, ST_KEY_RATECONTROL_LIMITS_BITRATE_UNDERSHOOT, -1); obs_data_set_default_int(settings, ST_KEY_RATECONTROL_LIMITS_BITRATE_OVERSHOOT, -1); obs_data_set_default_int(settings, ST_KEY_RATECONTROL_LIMITS_QUALITY, -1); obs_data_set_default_int(settings, ST_KEY_RATECONTROL_LIMITS_QUANTIZER_MINIMUM, -1); obs_data_set_default_int(settings, ST_KEY_RATECONTROL_LIMITS_QUANTIZER_MAXIMUM, -1); // Buffer obs_data_set_default_int(settings, ST_KEY_RATECONTROL_BUFFER_SIZE, -1); obs_data_set_default_int(settings, ST_KEY_RATECONTROL_BUFFER_SIZE_INITIAL, -1); obs_data_set_default_int(settings, ST_KEY_RATECONTROL_BUFFER_SIZE_OPTIMAL, -1); } { // Key-Frame Options obs_data_set_default_int(settings, ST_KEY_KEYFRAMES_INTERVALTYPE, 0); obs_data_set_default_double(settings, ST_KEY_KEYFRAMES_INTERVAL_SECONDS, 2.0); obs_data_set_default_int(settings, ST_KEY_KEYFRAMES_INTERVAL_FRAMES, 300); } { // Advanced Options obs_data_set_default_int(settings, ST_KEY_ADVANCED_THREADS, 0); obs_data_set_default_int(settings, ST_KEY_ADVANCED_ROWMULTITHREADING, -1); obs_data_set_default_int(settings, ST_KEY_ADVANCED_TILE_COLUMNS, -1); obs_data_set_default_int(settings, ST_KEY_ADVANCED_TILE_ROWS, -1); obs_data_set_default_int(settings, ST_KEY_ADVANCED_TUNE_METRIC, -1); obs_data_set_default_int(settings, ST_KEY_ADVANCED_TUNE_CONTENT, static_cast(AOM_CONTENT_DEFAULT)); } } static bool modified_usage(obs_properties_t* props, obs_property_t*, obs_data_t* settings) noexcept try { bool is_all_intra = false; if (obs_data_get_int(settings, ST_KEY_ENCODER_USAGE) == AOM_USAGE_ALL_INTRA) { is_all_intra = true; } // All-Intra does not support these. obs_property_set_visible(obs_properties_get(props, ST_KEY_RATECONTROL_LOOKAHEAD), !is_all_intra); obs_property_set_visible(obs_properties_get(props, ST_I18N_KEYFRAMES), !is_all_intra); 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 bool modified_ratecontrol_mode(obs_properties_t* props, obs_property_t*, obs_data_t* settings) noexcept try { bool is_bitrate_visible = false; bool is_overundershoot_visible = false; bool is_quality_visible = false; // Fix rate control mode selection if ALL_INTRA is selected. if (obs_data_get_int(settings, ST_KEY_ENCODER_USAGE) == AOM_USAGE_ALL_INTRA) { obs_data_set_int(settings, ST_KEY_RATECONTROL_MODE, static_cast(aom_rc_mode::AOM_Q)); } { // Based on the Rate Control Mode, show and hide options. auto mode = static_cast(obs_data_get_int(settings, ST_KEY_RATECONTROL_MODE)); if (mode == AOM_CBR) { is_bitrate_visible = true; is_overundershoot_visible = true; } else if (mode == AOM_VBR) { is_bitrate_visible = true; is_overundershoot_visible = true; } else if (mode == AOM_CQ) { is_bitrate_visible = true; is_overundershoot_visible = true; is_quality_visible = true; } else if (mode == AOM_Q) { is_quality_visible = true; } obs_property_set_visible(obs_properties_get(props, ST_KEY_RATECONTROL_LIMITS_BITRATE), is_bitrate_visible); obs_property_set_visible(obs_properties_get(props, ST_KEY_RATECONTROL_LIMITS_BITRATE_UNDERSHOOT), is_overundershoot_visible); obs_property_set_visible(obs_properties_get(props, ST_KEY_RATECONTROL_LIMITS_BITRATE_OVERSHOOT), is_overundershoot_visible); #ifdef AOM_CTRL_AOME_SET_CQ_LEVEL obs_property_set_visible(obs_properties_get(props, ST_KEY_RATECONTROL_LIMITS_QUALITY), is_quality_visible); #endif } 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 bool modified_keyframes(obs_properties_t* props, obs_property_t*, obs_data_t* settings) noexcept try { bool is_seconds = obs_data_get_int(settings, ST_KEY_KEYFRAMES_INTERVALTYPE) == 0; obs_property_set_visible(obs_properties_get(props, ST_KEY_KEYFRAMES_INTERVAL_FRAMES), !is_seconds); obs_property_set_visible(obs_properties_get(props, ST_KEY_KEYFRAMES_INTERVAL_SECONDS), is_seconds); 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; } obs_properties_t* aom_av1_factory::get_properties2(instance_t* data) { obs_properties_t* props = obs_properties_create(); #ifdef ENABLE_FRONTEND { obs_properties_add_button2(props, S_MANUAL_OPEN, D_TRANSLATE(S_MANUAL_OPEN), aom_av1_factory::on_manual_open, this); } #endif { // Presets obs_properties_t* grp = obs_properties_create(); obs_properties_add_group(props, ST_I18N_ENCODER, D_TRANSLATE(ST_I18N_ENCODER), OBS_GROUP_NORMAL, grp); //obs_properties_add_group(props, S_CODEC_AV1, D_TRANSLATE(S_CODEC_AV1), OBS_GROUP_NORMAL, grp); { // Usage auto p = obs_properties_add_list(grp, ST_KEY_ENCODER_USAGE, D_TRANSLATE(ST_I18N_ENCODER_USAGE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); obs_property_set_modified_callback(p, modified_usage); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_USAGE_GOODQUALITY), static_cast(AOM_USAGE_GOOD_QUALITY)); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_USAGE_REALTIME), static_cast(AOM_USAGE_REALTIME)); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_USAGE_ALLINTRA), static_cast(AOM_USAGE_ALL_INTRA)); } #ifdef AOM_CTRL_AOME_SET_CPUUSED { // CPU Usage auto p = obs_properties_add_list(grp, ST_KEY_ENCODER_CPUUSAGE, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); obs_property_list_add_int(p, D_TRANSLATE(S_STATE_DEFAULT), -1); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE_10), 10); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE_9), 9); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE_8), 8); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE_7), 7); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE_6), 6); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE_5), 5); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE_4), 4); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE_3), 3); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE_2), 2); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE_1), 1); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_CPUUSAGE_0), 0); } #endif { // Profile auto p = obs_properties_add_list(grp, ST_KEY_ENCODER_PROFILE, D_TRANSLATE(S_CODEC_AV1_PROFILE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); obs_property_list_add_int(p, D_TRANSLATE(S_STATE_AUTOMATIC), static_cast(codec::av1::profile::UNKNOWN)); obs_property_list_add_int(p, codec::av1::profile_to_string(codec::av1::profile::MAIN), static_cast(codec::av1::profile::MAIN)); obs_property_list_add_int(p, codec::av1::profile_to_string(codec::av1::profile::HIGH), static_cast(codec::av1::profile::HIGH)); obs_property_list_add_int(p, codec::av1::profile_to_string(codec::av1::profile::PROFESSIONAL), static_cast(codec::av1::profile::PROFESSIONAL)); } } { // Rate Control Options obs_properties_t* grp = obs_properties_create(); obs_properties_add_group(props, ST_I18N_RATECONTROL, D_TRANSLATE(ST_I18N_RATECONTROL), OBS_GROUP_NORMAL, grp); { // Mode auto p = obs_properties_add_list(grp, ST_KEY_RATECONTROL_MODE, D_TRANSLATE(ST_I18N_RATECONTROL_MODE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); obs_property_set_modified_callback(p, modified_ratecontrol_mode); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_RATECONTROL_MODE_VBR), static_cast(AOM_VBR)); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_RATECONTROL_MODE_CBR), static_cast(AOM_CBR)); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_RATECONTROL_MODE_CQ), static_cast(AOM_CQ)); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_RATECONTROL_MODE_Q), static_cast(AOM_Q)); } { // Look-Ahead auto p = obs_properties_add_int(grp, ST_KEY_RATECONTROL_LOOKAHEAD, D_TRANSLATE(ST_I18N_RATECONTROL_LOOKAHEAD), -1, std::numeric_limits::max(), 1); obs_property_int_set_suffix(p, " frames"); } { // Limits obs_properties_t* grp2 = obs_properties_create(); obs_properties_add_group(grp, ST_I18N_RATECONTROL_LIMITS, D_TRANSLATE(ST_I18N_RATECONTROL_LIMITS), OBS_GROUP_NORMAL, grp2); { // Bitrate auto p = obs_properties_add_int(grp2, ST_KEY_RATECONTROL_LIMITS_BITRATE, D_TRANSLATE(ST_I18N_RATECONTROL_LIMITS_BITRATE), 0, std::numeric_limits::max(), 1); obs_property_int_set_suffix(p, " kbit/s"); } { // Bitrate Under/Overshoot auto p1 = obs_properties_add_int_slider(grp2, ST_KEY_RATECONTROL_LIMITS_BITRATE_UNDERSHOOT, D_TRANSLATE(ST_I18N_RATECONTROL_LIMITS_BITRATE_UNDERSHOOT), -1, 100, 1); auto p2 = obs_properties_add_int_slider(grp2, ST_KEY_RATECONTROL_LIMITS_BITRATE_OVERSHOOT, D_TRANSLATE(ST_I18N_RATECONTROL_LIMITS_BITRATE_OVERSHOOT), -1, 100, 1); obs_property_float_set_suffix(p1, " %"); obs_property_float_set_suffix(p2, " %"); } #ifdef AOM_CTRL_AOME_SET_CQ_LEVEL { // Quality auto p = obs_properties_add_int_slider(grp2, ST_KEY_RATECONTROL_LIMITS_QUALITY, D_TRANSLATE(ST_I18N_RATECONTROL_LIMITS_QUALITY), -1, 63, 1); } #endif { // Quantizer auto p1 = obs_properties_add_int_slider(grp2, ST_KEY_RATECONTROL_LIMITS_QUANTIZER_MINIMUM, D_TRANSLATE(ST_I18N_RATECONTROL_LIMITS_QUANTIZER_MINIMUM), -1, 63, 1); auto p2 = obs_properties_add_int_slider(grp2, ST_KEY_RATECONTROL_LIMITS_QUANTIZER_MAXIMUM, D_TRANSLATE(ST_I18N_RATECONTROL_LIMITS_QUANTIZER_MAXIMUM), -1, 63, 1); } } { // Buffer obs_properties_t* grp2 = obs_properties_create(); obs_properties_add_group(grp, ST_I18N_RATECONTROL_BUFFER, D_TRANSLATE(ST_I18N_RATECONTROL_BUFFER), OBS_GROUP_NORMAL, grp2); { // Buffer Size auto p = obs_properties_add_int(grp2, ST_KEY_RATECONTROL_BUFFER_SIZE, D_TRANSLATE(ST_I18N_RATECONTROL_BUFFER_SIZE), -1, std::numeric_limits::max(), 1); obs_property_int_set_suffix(p, " ms"); } { // Initial Buffer Size auto p = obs_properties_add_int(grp2, ST_KEY_RATECONTROL_BUFFER_SIZE_INITIAL, D_TRANSLATE(ST_I18N_RATECONTROL_BUFFER_SIZE_INITIAL), -1, std::numeric_limits::max(), 1); obs_property_int_set_suffix(p, " ms"); } { // Optimal Buffer Size auto p = obs_properties_add_int(grp2, ST_KEY_RATECONTROL_BUFFER_SIZE_OPTIMAL, D_TRANSLATE(ST_I18N_RATECONTROL_BUFFER_SIZE_OPTIMAL), -1, std::numeric_limits::max(), 1); obs_property_int_set_suffix(p, " ms"); } } } { // Key-Frame Options obs_properties_t* grp = obs_properties_create(); obs_properties_add_group(props, ST_I18N_KEYFRAMES, D_TRANSLATE(ST_I18N_KEYFRAMES), OBS_GROUP_NORMAL, grp); { // Key-Frame Interval Type auto p = obs_properties_add_list(grp, ST_KEY_KEYFRAMES_INTERVALTYPE, D_TRANSLATE(ST_I18N_KEYFRAMES_INTERVALTYPE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); obs_property_set_modified_callback(p, modified_keyframes); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_KEYFRAMES_INTERVALTYPE_SECONDS), 0); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_KEYFRAMES_INTERVALTYPE_FRAMES), 1); } { // Key-Frame Interval Seconds auto p = obs_properties_add_float(grp, ST_KEY_KEYFRAMES_INTERVAL_SECONDS, D_TRANSLATE(ST_I18N_KEYFRAMES_INTERVAL), 0.00, std::numeric_limits::max(), 0.01); obs_property_float_set_suffix(p, " seconds"); } { // Key-Frame Interval Frames auto p = obs_properties_add_int(grp, ST_KEY_KEYFRAMES_INTERVAL_FRAMES, D_TRANSLATE(ST_I18N_KEYFRAMES_INTERVAL), 0, std::numeric_limits::max(), 1); obs_property_int_set_suffix(p, " frames"); } } { // Advanced Options obs_properties_t* grp = obs_properties_create(); obs_properties_add_group(props, ST_I18N_ADVANCED, D_TRANSLATE(ST_I18N_ADVANCED), OBS_GROUP_NORMAL, grp); { // Threads auto p = obs_properties_add_int(grp, ST_KEY_ADVANCED_THREADS, D_TRANSLATE(ST_I18N_ADVANCED_THREADS), 0, std::numeric_limits::max(), 1); } #ifdef AOM_CTRL_AV1E_SET_ROW_MT { // Row-MT auto p = streamfx::util::obs_properties_add_tristate(grp, ST_KEY_ADVANCED_ROWMULTITHREADING, D_TRANSLATE(ST_I18N_ADVANCED_ROWMULTITHREADING)); } #endif #ifdef AOM_CTRL_AV1E_SET_TILE_COLUMNS { // Tile Columns auto p = obs_properties_add_int_slider(grp, ST_KEY_ADVANCED_TILE_COLUMNS, D_TRANSLATE(ST_I18N_ADVANCED_TILE_COLUMNS), -1, 6, 1); } #endif #ifdef AOM_CTRL_AV1E_SET_TILE_ROWS { // Tile Rows auto p = obs_properties_add_int_slider(grp, ST_KEY_ADVANCED_TILE_ROWS, D_TRANSLATE(ST_I18N_ADVANCED_TILE_ROWS), -1, 6, 1); } #endif { obs_properties_t* grp2 = obs_properties_create(); obs_properties_add_group(grp, ST_I18N_ADVANCED_TUNE, D_TRANSLATE(ST_I18N_ADVANCED_TUNE), OBS_GROUP_NORMAL, grp2); #ifdef AOM_CTRL_AOME_SET_TUNING { // Tuning auto p = obs_properties_add_list(grp2, ST_KEY_ADVANCED_TUNE_METRIC, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_METRIC), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); obs_property_list_add_int(p, D_TRANSLATE(S_STATE_DEFAULT), -1); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_METRIC_PSNR), static_cast(AOM_TUNE_PSNR)); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_METRIC_SSIM), static_cast(AOM_TUNE_SSIM)); #ifdef _DEBUG // These have no actual use outside of debug builds, way too slow! obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_METRIC_VMAFWITHPREPROCESSING), static_cast(AOM_TUNE_VMAF_WITH_PREPROCESSING)); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_METRIC_VMAFWITHOUTPREPROCESSING), static_cast(AOM_TUNE_VMAF_WITHOUT_PREPROCESSING)); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_METRIC_VMAFMAXGAIN), static_cast(AOM_TUNE_VMAF_MAX_GAIN)); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_METRIC_VMAFNEGMAXGAIN), static_cast(AOM_TUNE_VMAF_NEG_MAX_GAIN)); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_METRIC_BUTTERAUGLI), static_cast(AOM_TUNE_BUTTERAUGLI)); #endif } #endif #ifdef AOM_CTRL_AV1E_SET_TUNE_CONTENT { // Content Tuning auto p = obs_properties_add_list(grp2, ST_KEY_ADVANCED_TUNE_CONTENT, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_CONTENT), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); obs_property_list_add_int(p, D_TRANSLATE(S_STATE_DEFAULT), static_cast(AOM_CONTENT_DEFAULT)); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_CONTENT_SCREEN), static_cast(AOM_CONTENT_SCREEN)); obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_CONTENT_FILM), static_cast(AOM_CONTENT_FILM)); } #endif } } return props; } #ifdef ENABLE_FRONTEND bool aom_av1_factory::on_manual_open(obs_properties_t* props, obs_property_t* property, void* data) { streamfx::open_url(HELP_URL); return false; } #endif