obs-StreamFX/source/encoders/encoder-aom-av1.cpp
2023-04-05 18:51:15 +02:00

1683 lines
64 KiB
C++

// Copyright (c) 2021 Michael Fabian Dirks <info@xaymar.com>
//
// 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 <filesystem>
#include <thread>
#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 "<encoder::aom::av1> "
#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_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<uint16_t>(obs_encoder_get_width(_self));
_settings.height = static_cast<uint16_t>(obs_encoder_get_height(_self));
_settings.fps.num = static_cast<uint32_t>(video_info->fps_num);
_settings.fps.den = static_cast<uint32_t>(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<codec::av1::profile>(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 == VIDEO_FORMAT_I422);
bool need_high = (_settings.color_format == VIDEO_FORMAT_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<aom_rc_mode>(obs_data_get_int(settings, ST_KEY_RATECONTROL_MODE));
_settings.rc_lookahead = static_cast<int8_t>(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<int8_t>(threads);
} else {
_settings.threads = std::thread::hardware_concurrency();
}
_settings.rowmultithreading =
static_cast<int8_t>(obs_data_get_int(settings, ST_KEY_ADVANCED_ROWMULTITHREADING));
}
{ // Tiling
_settings.tile_columns = static_cast<int8_t>(obs_data_get_int(settings, ST_KEY_ADVANCED_TILE_COLUMNS));
_settings.tile_rows = static_cast<int8_t>(obs_data_get_int(settings, ST_KEY_ADVANCED_TILE_ROWS));
}
{ // Tuning
_settings.tune_metric =
static_cast<aom_tune_metric>(obs_data_get_int(settings, ST_KEY_ADVANCED_TUNE_METRIC));
_settings.tune_content =
static_cast<aom_tune_content>(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<std::chrono::microseconds>(_profiler_copy->percentile(0.999)).count(),
std::chrono::duration_cast<std::chrono::microseconds>(_profiler_copy->percentile(0.990)).count(),
std::chrono::duration_cast<std::chrono::microseconds>(_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<std::chrono::microseconds>(_profiler_encode->percentile(0.999)).count(),
std::chrono::duration_cast<std::chrono::microseconds>(_profiler_encode->percentile(0.990)).count(),
std::chrono::duration_cast<std::chrono::microseconds>(_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<std::chrono::microseconds>(_profiler_packet->percentile(0.999)).count(),
std::chrono::duration_cast<std::chrono::microseconds>(_profiler_packet->percentile(0.990)).count(),
std::chrono::duration_cast<std::chrono::microseconds>(_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<decltype(Y)>(X); \
}
{ // Generate Dynamic Settings
{ // Encoder
_settings.preset = static_cast<int8_t>(obs_data_get_int(settings, ST_KEY_ENCODER_CPUUSAGE));
}
{ // Rate Control
_settings.rc_bitrate = static_cast<int32_t>(obs_data_get_int(settings, ST_KEY_RATECONTROL_LIMITS_BITRATE));
_settings.rc_bitrate_overshoot =
static_cast<int32_t>(obs_data_get_int(settings, ST_KEY_RATECONTROL_LIMITS_BITRATE_UNDERSHOOT));
_settings.rc_bitrate_undershoot =
static_cast<int32_t>(obs_data_get_int(settings, ST_KEY_RATECONTROL_LIMITS_BITRATE_OVERSHOOT));
_settings.rc_quality = static_cast<int32_t>(obs_data_get_int(settings, ST_KEY_RATECONTROL_LIMITS_QUALITY));
_settings.rc_quantizer_min =
static_cast<int32_t>(obs_data_get_int(settings, ST_KEY_RATECONTROL_LIMITS_QUANTIZER_MINIMUM));
_settings.rc_quantizer_max =
static_cast<int32_t>(obs_data_get_int(settings, ST_KEY_RATECONTROL_LIMITS_QUANTIZER_MAXIMUM));
_settings.rc_buffer_ms = static_cast<int32_t>(obs_data_get_int(settings, ST_KEY_RATECONTROL_BUFFER_SIZE));
_settings.rc_buffer_initial_ms =
static_cast<int32_t>(obs_data_get_int(settings, ST_KEY_RATECONTROL_BUFFER_SIZE_INITIAL));
_settings.rc_buffer_optimal_ms =
static_cast<int32_t>(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<unsigned int>(
std::lround(obs_data_get_double(settings, ST_KEY_KEYFRAMES_INTERVAL_SECONDS)
* static_cast<double>(obsFPSnum) / static_cast<double>(obsFPSden)));
} else {
_settings.kf_distance_max =
static_cast<unsigned int>(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<unsigned int>(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<unsigned int>(_settings.profile);
}
{ // Rate Control
// Mode
_cfg.rc_end_usage = static_cast<aom_rc_mode>(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<double>(_settings.fps.num) / static_cast<float>(_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<uint8_t*>(_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<uint8_t*>(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<std::filesystem::path> 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<decltype(lib##X)>(_library->load_symbol(std::string(#X))); \
}
#define _LOAD_SYMBOL_(X, Y) \
{ \
lib##X = reinterpret_cast<decltype(lib##X)>(_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> _aom_av1_factory_instance = nullptr;
void aom_av1_factory::initialize()
try {
if (!_aom_av1_factory_instance) {
_aom_av1_factory_instance = std::make_shared<aom_av1_factory>();
}
} 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> 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<long long>(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<long long>(codec::av1::profile::UNKNOWN));
}
{ // Rate-Control
obs_data_set_default_int(settings, ST_KEY_RATECONTROL_MODE, static_cast<long long>(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<long long>(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<long long>(aom_rc_mode::AOM_Q));
}
{ // Based on the Rate Control Mode, show and hide options.
auto mode = static_cast<aom_rc_mode>(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<long long>(AOM_USAGE_GOOD_QUALITY));
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_USAGE_REALTIME),
static_cast<long long>(AOM_USAGE_REALTIME));
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ENCODER_USAGE_ALLINTRA),
static_cast<long long>(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_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<long long>(codec::av1::profile::UNKNOWN));
obs_property_list_add_int(p, codec::av1::profile_to_string(codec::av1::profile::MAIN),
static_cast<long long>(codec::av1::profile::MAIN));
obs_property_list_add_int(p, codec::av1::profile_to_string(codec::av1::profile::HIGH),
static_cast<long long>(codec::av1::profile::HIGH));
obs_property_list_add_int(p, codec::av1::profile_to_string(codec::av1::profile::PROFESSIONAL),
static_cast<long long>(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<long long>(AOM_VBR));
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_RATECONTROL_MODE_CBR), static_cast<long long>(AOM_CBR));
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_RATECONTROL_MODE_CQ), static_cast<long long>(AOM_CQ));
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_RATECONTROL_MODE_Q), static_cast<long long>(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<int32_t>::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<int32_t>::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,
1000, 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<int32_t>::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<int32_t>::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<int32_t>::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<uint16_t>::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<int32_t>::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<int32_t>::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<long long>(AOM_TUNE_PSNR));
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_METRIC_SSIM),
static_cast<long long>(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<long long>(AOM_TUNE_VMAF_WITH_PREPROCESSING));
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_METRIC_VMAFWITHOUTPREPROCESSING),
static_cast<long long>(AOM_TUNE_VMAF_WITHOUT_PREPROCESSING));
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_METRIC_VMAFMAXGAIN),
static_cast<long long>(AOM_TUNE_VMAF_MAX_GAIN));
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_METRIC_VMAFNEGMAXGAIN),
static_cast<long long>(AOM_TUNE_VMAF_NEG_MAX_GAIN));
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_METRIC_BUTTERAUGLI),
static_cast<long long>(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<long long>(AOM_CONTENT_DEFAULT));
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_CONTENT_SCREEN),
static_cast<long long>(AOM_CONTENT_SCREEN));
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ADVANCED_TUNE_CONTENT_FILM),
static_cast<long long>(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