encoders/ffmpeg: Implement AMF H.264 and H.265 handlers

Adds support for the AMD Advanced Media Framework H.264 and H.265 encoders via FFmpeg. The majority of settings are supported, and the UI/UX experience mimics that of the NVENC implementation. Various settings are left out due to their complexity and should be controlled via the custom parameters field.
This commit is contained in:
Michael Fabian 'Xaymar' Dirks 2020-06-21 19:14:31 +02:00
parent 569fa56b1d
commit ff3f8cff03
9 changed files with 1232 additions and 0 deletions

View file

@ -916,6 +916,12 @@ if(NOT ${PREFIX}DISABLE_ENCODER_FFMPEG)
"source/encoders/handlers/nvenc_h264_handler.cpp"
"source/encoders/handlers/nvenc_hevc_handler.hpp"
"source/encoders/handlers/nvenc_hevc_handler.cpp"
"source/encoders/handlers/amf_shared.hpp"
"source/encoders/handlers/amf_shared.cpp"
"source/encoders/handlers/amf_h264_handler.hpp"
"source/encoders/handlers/amf_h264_handler.cpp"
"source/encoders/handlers/amf_hevc_handler.hpp"
"source/encoders/handlers/amf_hevc_handler.cpp"
)
list(APPEND PROJECT_DEFINITIONS
ENABLE_ENCODER_FFMPEG

View file

@ -419,6 +419,53 @@ FFmpegEncoder.KeyFrames.IntervalType.Description="Keyframe interval type"
FFmpegEncoder.KeyFrames.Interval.Description="Distance between key frames, in frames or seconds."
FFmpegEncoder.KeyFrames.Interval="Interval"
# Encoder: AMF
FFmpegEncoder.AMF.Preset="Preset"
FFmpegEncoder.AMF.Preset.Description="The preset to use for encoding, which sets up internal values in the driver for encoding.\nThe names roughly match the expected encoding speed, but quality varies.\nIt is entirely possible to get a better quality with Balanced than with Quality at low bitrates."
FFmpegEncoder.AMF.Preset.Speed="Speed"
FFmpegEncoder.AMF.Preset.Balanced="Balanced"
FFmpegEncoder.AMF.Preset.Quality="Quality"
FFmpegEncoder.AMF.RateControl="Rate Control Options"
FFmpegEncoder.AMF.RateControl.Mode="Mode"
FFmpegEncoder.AMF.RateControl.Mode.Description="Rate control mode selection"
FFmpegEncoder.AMF.RateControl.Mode.CQP="Constant Quantization Parameter"
FFmpegEncoder.AMF.RateControl.Mode.CQP.Description="A flat compression ratio with no regard for bit rates."
FFmpegEncoder.AMF.RateControl.Mode.VBR_PEAK="Variable Bitrate (Peak Constrained)"
FFmpegEncoder.AMF.RateControl.Mode.VBR_PEAK.Description="TODO"
FFmpegEncoder.AMF.RateControl.Mode.VBR_LATENCY="Variable Bitrate (Latency Constrained)"
FFmpegEncoder.AMF.RateControl.Mode.VBR_LATENCY.Description="TODO"
FFmpegEncoder.AMF.RateControl.Mode.CBR="Constant Bitrate"
FFmpegEncoder.AMF.RateControl.Mode.CBR.Description="Compresses footage so that it matches the target bitrate over the duration of\none second. This comes at a cost in quality during high motion scenes or\nscenes with flickering brightness like often seen in RPGs."
FFmpegEncoder.AMF.RateControl.LookAhead="Look Ahead"
FFmpegEncoder.AMF.RateControl.LookAhead.Description="Look ahead in the encoding, slightly increasing latency but improving bitrate distribution over time."
FFmpegEncoder.AMF.RateControl.FrameSkipping="Frame Skipping"
FFmpegEncoder.AMF.RateControl.FrameSkipping.Description="Allow skipping frames to meet the bitrate target/maximum."
FFmpegEncoder.AMF.RateControl.Limits="Limits"
FFmpegEncoder.AMF.RateControl.Limits.BufferSize="Buffer Size"
FFmpegEncoder.AMF.RateControl.Limits.BufferSize.Description="Specifies the buffer size used for bitrate constrained modes.\nIdeally set to (KeyFrame Interval In Seconds * Bitrate Target), so at 2 seconds and 6000 kbit it should be 12000."
FFmpegEncoder.AMF.RateControl.Limits.Bitrate.Target="Target Bitrate"
FFmpegEncoder.AMF.RateControl.Limits.Bitrate.Maximum="Maximum Bitrate"
FFmpegEncoder.AMF.RateControl.QP="Quantization Parameters"
FFmpegEncoder.AMF.RateControl.QP.I="I-Frame QP"
FFmpegEncoder.AMF.RateControl.QP.I.Description="Quantization parameter for I-Frames.\nSmaller values mean better quality in exchange for higher bitrate, while higher values mean less bitrate in exchange for less quality."
FFmpegEncoder.AMF.RateControl.QP.P="P-Frame QP"
FFmpegEncoder.AMF.RateControl.QP.P.Description="Quantization parameter for P-Frames.\nSmaller values mean better quality in exchange for higher bitrate, while higher values mean less bitrate in exchange for less quality."
FFmpegEncoder.AMF.RateControl.QP.B="B-Frame QP"
FFmpegEncoder.AMF.RateControl.QP.B.Description="Quantization parameter for B-Frames.\nSmaller values mean better quality in exchange for higher bitrate, while higher values mean less bitrate in exchange for less quality."
FFmpegEncoder.AMF.Other="Other Options"
FFmpegEncoder.AMF.Other.BFrames="Maximum B-Frames"
FFmpegEncoder.AMF.Other.BFrames.Description="Maximum number of B-Frames to insert into the encoded bitstream.\nActual number of B-Frames may be lower depending on content and lookahead settings.\nB-Frames may not be supported on all AMD GPUs."
FFmpegEncoder.AMF.Other.BFrameReferences="B-Frame References"
FFmpegEncoder.AMF.Other.BFrameReferences.Description="Enable references to B-Frames, which can improve quality but reduces encoder performance drastically."
FFmpegEncoder.AMF.Other.ReferenceFrames="Reference Frames"
FFmpegEncoder.AMF.Other.ReferenceFrames.Description="Maximum number of reference frames to create in the encoded footage.\nActual number may be lower and is decided by the encoder."
FFmpegEncoder.AMF.Other.EnforceHRD="Enforce HRD"
FFmpegEncoder.AMF.Other.EnforceHRD.Description="TODO"
FFmpegEncoder.AMF.Other.VBAQ="VBAQ"
FFmpegEncoder.AMF.Other.VBAQ.Description="Enable 'Variance (Based) Adaptive Quantization', which improves the bitrate distribution over the frame by focusing areas which need the detail, like edges and foliage.\nThis option is only available on newer AMD GPUs."
FFmpegEncoder.AMF.Other.AccessUnitDelimiter="Access Unit Delimiter"
FFmpegEncoder.AMF.Other.AccessUnitDelimiter.Description="Enable insertion of an Access Unit Delimiter."
# Encoder: NVENC
FFmpegEncoder.NVENC.Preset="Preset"
FFmpegEncoder.NVENC.Preset.Description="Presets are NVIDIA's preconfigured default settings.\nThe values set via the preset are overridden by parameters below, unless they are set to 'Default' or '-1'."

View file

@ -24,6 +24,8 @@
#include <sstream>
#include "codecs/hevc.hpp"
#include "ffmpeg/tools.hpp"
#include "handlers/amf_h264_handler.hpp"
#include "handlers/amf_hevc_handler.hpp"
#include "handlers/debug_handler.hpp"
#include "handlers/nvenc_h264_handler.hpp"
#include "handlers/nvenc_hevc_handler.hpp"
@ -1115,6 +1117,8 @@ ffmpeg_manager::ffmpeg_manager() : _factories(), _handlers(), _debug_handler()
register_handler("prores_aw", ::std::make_shared<handler::prores_aw_handler>());
register_handler("h264_nvenc", ::std::make_shared<handler::nvenc_h264_handler>());
register_handler("hevc_nvenc", ::std::make_shared<handler::nvenc_hevc_handler>());
register_handler("h264_amf", ::std::make_shared<handler::amf_h264_handler>());
register_handler("hevc_amf", ::std::make_shared<handler::amf_hevc_handler>());
}
ffmpeg_manager::~ffmpeg_manager()

View file

@ -0,0 +1,187 @@
/*
* Modern effects for a modern Streamer
* Copyright (C) 2020 Michael Fabian Dirks
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "amf_h264_handler.hpp"
#include "../codecs/h264.hpp"
#include "../encoder-ffmpeg.hpp"
#include "amf_shared.hpp"
#include "ffmpeg/tools.hpp"
extern "C" {
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4242 4244 4365)
#endif
#include <libavutil/opt.h>
#ifdef _MSC_VER
#pragma warning(pop)
#endif
}
// Settings
#define KEY_PROFILE "H264.Profile"
#define KEY_LEVEL "H264.Level"
using namespace streamfx::encoder::ffmpeg::handler;
using namespace streamfx::encoder::codec::h264;
static std::map<profile, std::string> profiles{
{profile::CONSTRAINED_BASELINE, "constrained_baseline"},
{profile::MAIN, "main"},
{profile::HIGH, "high"},
};
static std::map<level, std::string> levels{
{level::L1_0, "1.0"}, {level::L1_0b, "1.0b"}, {level::L1_1, "1.1"}, {level::L1_2, "1.2"}, {level::L1_3, "1.3"},
{level::L2_0, "2.0"}, {level::L2_1, "2.1"}, {level::L2_2, "2.2"}, {level::L3_0, "3.0"}, {level::L3_1, "3.1"},
{level::L3_2, "3.2"}, {level::L4_0, "4.0"}, {level::L4_1, "4.1"}, {level::L4_2, "4.2"}, {level::L5_0, "5.0"},
{level::L5_1, "5.1"}, {level::L5_2, "5.2"}, {level::L6_0, "6.0"}, {level::L6_1, "6.1"}, {level::L6_2, "6.2"},
};
void amf_h264_handler::adjust_info(ffmpeg_factory* factory, const AVCodec* codec, std::string& id, std::string& name,
std::string& codec_id)
{
name = "AMD AMF H.264/AVC (via FFmpeg)";
if (!amf::is_available())
factory->get_info()->caps |= OBS_ENCODER_CAP_DEPRECATED;
}
void amf_h264_handler::get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context, bool hw_encode)
{
amf::get_defaults(settings, codec, context);
obs_data_set_default_int(settings, KEY_PROFILE, static_cast<int64_t>(profile::HIGH));
obs_data_set_default_int(settings, KEY_LEVEL, static_cast<int64_t>(level::UNKNOWN));
}
bool amf_h264_handler::has_keyframe_support(ffmpeg_factory* instance)
{
return true;
}
bool amf_h264_handler::is_hardware_encoder(ffmpeg_factory* instance)
{
return true;
}
bool amf_h264_handler::has_threading_support(ffmpeg_factory* instance)
{
return false;
}
bool amf_h264_handler::has_pixel_format_support(ffmpeg_factory* instance)
{
return false;
}
void amf_h264_handler::get_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context,
bool hw_encode)
{
if (!context) {
this->get_encoder_properties(props, codec);
} else {
this->get_runtime_properties(props, codec, context);
}
}
void amf_h264_handler::update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context)
{
amf::update(settings, codec, context);
{
auto found = profiles.find(static_cast<profile>(obs_data_get_int(settings, KEY_PROFILE)));
if (found != profiles.end()) {
av_opt_set(context->priv_data, "profile", found->second.c_str(), 0);
}
}
{
auto found = levels.find(static_cast<level>(obs_data_get_int(settings, KEY_LEVEL)));
if (found != levels.end()) {
av_opt_set(context->priv_data, "level", found->second.c_str(), 0);
} else {
av_opt_set(context->priv_data, "level", "auto", 0);
}
}
}
void amf_h264_handler::override_update(ffmpeg_instance* instance, obs_data_t* settings)
{
amf::override_update(instance, settings);
}
void amf_h264_handler::log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context)
{
amf::log_options(settings, codec, context);
DLOG_INFO("[%s] H.264/AVC:", codec->name);
::ffmpeg::tools::print_av_option_string2(context, context->priv_data, "profile", " Profile",
[](int64_t v, std::string_view o) { return std::string(o); });
::ffmpeg::tools::print_av_option_string2(context, context->priv_data, "level", " Level",
[](int64_t v, std::string_view o) { return std::string(o); });
}
void amf_h264_handler::get_encoder_properties(obs_properties_t* props, const AVCodec* codec)
{
amf::get_properties_pre(props, codec);
{
obs_properties_t* grp = obs_properties_create();
obs_properties_add_group(props, P_H264, D_TRANSLATE(P_H264), OBS_GROUP_NORMAL, grp);
{
auto p = obs_properties_add_list(grp, KEY_PROFILE, D_TRANSLATE(P_H264_PROFILE), OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(P_H264_PROFILE)));
obs_property_list_add_int(p, D_TRANSLATE(S_STATE_DEFAULT), static_cast<int64_t>(profile::UNKNOWN));
for (auto const kv : profiles) {
std::string trans = std::string(P_H264_PROFILE) + "." + kv.second;
obs_property_list_add_int(p, D_TRANSLATE(trans.c_str()), static_cast<int64_t>(kv.first));
}
}
{
auto p = obs_properties_add_list(grp, KEY_LEVEL, D_TRANSLATE(P_H264_LEVEL), OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(P_H264_LEVEL)));
obs_property_list_add_int(p, D_TRANSLATE(S_STATE_AUTOMATIC), static_cast<int64_t>(level::UNKNOWN));
for (auto const kv : levels) {
obs_property_list_add_int(p, kv.second.c_str(), static_cast<int64_t>(kv.first));
}
}
}
amf::get_properties_post(props, codec);
}
void amf_h264_handler::get_runtime_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context)
{
amf::get_runtime_properties(props, codec, context);
}
void amf_h264_handler::migrate(obs_data_t* settings, std::uint64_t version, const AVCodec* codec,
AVCodecContext* context)
{
amf::migrate(settings, version, codec, context);
}
void amf_h264_handler::override_colorformat(AVPixelFormat& target_format, obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context)
{
target_format = AV_PIX_FMT_NV12;
}

View file

@ -0,0 +1,74 @@
/*
* Modern effects for a modern Streamer
* Copyright (C) 2020 Michael Fabian Dirks
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#pragma once
#include "common.hpp"
#include "handler.hpp"
extern "C" {
#pragma warning(push)
#pragma warning(disable : 4244)
#include <libavcodec/avcodec.h>
#pragma warning(pop)
}
namespace streamfx::encoder::ffmpeg::handler {
class amf_h264_handler : public handler {
public:
virtual ~amf_h264_handler(){};
public /*factory*/:
void adjust_info(ffmpeg_factory* factory, const AVCodec* codec, std::string& id, std::string& name,
std::string& codec_id) override;
void get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context, bool hw_encode) override;
public /*support tests*/:
bool has_keyframe_support(ffmpeg_factory* instance) override;
bool is_hardware_encoder(ffmpeg_factory* instance) override;
bool has_threading_support(ffmpeg_factory* instance) override;
bool has_pixel_format_support(ffmpeg_factory* instance) override;
public /*settings*/:
void get_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context,
bool hw_encode) override;
void migrate(obs_data_t* settings, std::uint64_t version, const AVCodec* codec,
AVCodecContext* context) override;
void update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context) override;
void override_update(ffmpeg_instance* instance, obs_data_t* settings) override;
void log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context) override;
public /*instance*/:
void override_colorformat(AVPixelFormat& target_format, obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context) override;
private:
void get_encoder_properties(obs_properties_t* props, const AVCodec* codec);
void get_runtime_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context);
};
} // namespace streamfx::encoder::ffmpeg::handler

View file

@ -0,0 +1,200 @@
/*
* Modern effects for a modern Streamer
* Copyright (C) 2017-2018 Michael Fabian Dirks
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "amf_hevc_handler.hpp"
#include "strings.hpp"
#include "../codecs/hevc.hpp"
#include "../encoder-ffmpeg.hpp"
#include "amf_shared.hpp"
#include "ffmpeg/tools.hpp"
#include "plugin.hpp"
extern "C" {
#include <obs-module.h>
#pragma warning(push)
#pragma warning(disable : 4242 4244 4365)
#include <libavutil/opt.h>
#pragma warning(pop)
}
// Settings
#define KEY_PROFILE "H265.Profile"
#define KEY_TIER "H265.Tier"
#define KEY_LEVEL "H265.Level"
using namespace streamfx::encoder::ffmpeg::handler;
using namespace streamfx::encoder::codec::hevc;
static std::map<profile, std::string> profiles{
{profile::MAIN, "main"},
};
static std::map<tier, std::string> tiers{
{tier::MAIN, "main"},
{tier::HIGH, "high"},
};
static std::map<level, std::string> levels{
{level::L1_0, "1.0"}, {level::L2_0, "2.0"}, {level::L2_1, "2.1"}, {level::L3_0, "3.0"}, {level::L3_1, "3.1"},
{level::L4_0, "4.0"}, {level::L4_1, "4.1"}, {level::L5_0, "5.0"}, {level::L5_1, "5.1"}, {level::L5_2, "5.2"},
{level::L6_0, "6.0"}, {level::L6_1, "6.1"}, {level::L6_2, "6.2"},
};
void amf_hevc_handler::adjust_info(ffmpeg_factory* factory, const AVCodec* codec, std::string& id, std::string& name,
std::string& codec_id)
{
name = "AMD AMF H.265/HEVC (via FFmpeg)";
if (!amf::is_available())
factory->get_info()->caps |= OBS_ENCODER_CAP_DEPRECATED;
}
void amf_hevc_handler::get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context, bool)
{
amf::get_defaults(settings, codec, context);
obs_data_set_default_int(settings, KEY_PROFILE, static_cast<int64_t>(profile::MAIN));
obs_data_set_default_int(settings, KEY_TIER, static_cast<int64_t>(profile::MAIN));
obs_data_set_default_int(settings, KEY_LEVEL, static_cast<int64_t>(level::UNKNOWN));
}
bool amf_hevc_handler::has_keyframe_support(ffmpeg_factory*)
{
return true;
}
bool amf_hevc_handler::is_hardware_encoder(ffmpeg_factory* instance)
{
return true;
}
bool amf_hevc_handler::has_threading_support(ffmpeg_factory* instance)
{
return false;
}
bool amf_hevc_handler::has_pixel_format_support(ffmpeg_factory* instance)
{
return false;
}
void amf_hevc_handler::get_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context, bool)
{
if (!context) {
this->get_encoder_properties(props, codec);
} else {
this->get_runtime_properties(props, codec, context);
}
}
void amf_hevc_handler::update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context)
{
amf::update(settings, codec, context);
{ // HEVC Options
auto found = profiles.find(static_cast<profile>(obs_data_get_int(settings, KEY_PROFILE)));
if (found != profiles.end()) {
av_opt_set(context->priv_data, "profile", found->second.c_str(), 0);
}
}
{
auto found = tiers.find(static_cast<tier>(obs_data_get_int(settings, KEY_TIER)));
if (found != tiers.end()) {
av_opt_set(context->priv_data, "tier", found->second.c_str(), 0);
}
}
{
auto found = levels.find(static_cast<level>(obs_data_get_int(settings, KEY_LEVEL)));
if (found != levels.end()) {
av_opt_set(context->priv_data, "level", found->second.c_str(), 0);
} else {
av_opt_set(context->priv_data, "level", "auto", 0);
}
}
}
void amf_hevc_handler::override_update(ffmpeg_instance* instance, obs_data_t* settings)
{
amf::override_update(instance, settings);
}
void amf_hevc_handler::log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context)
{
amf::log_options(settings, codec, context);
DLOG_INFO("[%s] H.265/HEVC:", codec->name);
::ffmpeg::tools::print_av_option_string2(context, "profile", " Profile",
[](int64_t v, std::string_view o) { return std::string(o); });
::ffmpeg::tools::print_av_option_string2(context, "level", " Level",
[](int64_t v, std::string_view o) { return std::string(o); });
::ffmpeg::tools::print_av_option_string2(context, "tier", " Tier",
[](int64_t v, std::string_view o) { return std::string(o); });
}
void amf_hevc_handler::get_encoder_properties(obs_properties_t* props, const AVCodec* codec)
{
amf::get_properties_pre(props, codec);
{
obs_properties_t* grp = obs_properties_create();
obs_properties_add_group(props, P_HEVC, D_TRANSLATE(P_HEVC), OBS_GROUP_NORMAL, grp);
{
auto p = obs_properties_add_list(grp, KEY_PROFILE, D_TRANSLATE(P_HEVC_PROFILE), OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(P_HEVC_PROFILE)));
obs_property_list_add_int(p, D_TRANSLATE(S_STATE_DEFAULT), static_cast<int64_t>(profile::UNKNOWN));
for (auto const kv : profiles) {
std::string trans = std::string(P_HEVC_PROFILE) + "." + kv.second;
obs_property_list_add_int(p, D_TRANSLATE(trans.c_str()), static_cast<int64_t>(kv.first));
}
}
{
auto p = obs_properties_add_list(grp, KEY_TIER, D_TRANSLATE(P_HEVC_TIER), OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(P_HEVC_TIER)));
obs_property_list_add_int(p, D_TRANSLATE(S_STATE_DEFAULT), static_cast<int64_t>(tier::UNKNOWN));
for (auto const kv : tiers) {
std::string trans = std::string(P_HEVC_TIER) + "." + kv.second;
obs_property_list_add_int(p, D_TRANSLATE(trans.c_str()), static_cast<int64_t>(kv.first));
}
}
{
auto p = obs_properties_add_list(grp, KEY_LEVEL, D_TRANSLATE(P_HEVC_LEVEL), OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(P_HEVC_LEVEL)));
obs_property_list_add_int(p, D_TRANSLATE(S_STATE_AUTOMATIC), static_cast<int64_t>(level::UNKNOWN));
for (auto const kv : levels) {
obs_property_list_add_int(p, kv.second.c_str(), static_cast<int64_t>(kv.first));
}
}
}
amf::get_properties_post(props, codec);
}
void amf_hevc_handler::get_runtime_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context)
{
amf::get_runtime_properties(props, codec, context);
}
void streamfx::encoder::ffmpeg::handler::amf_hevc_handler::migrate(obs_data_t* settings, std::uint64_t version,
const AVCodec* codec, AVCodecContext* context)
{
amf::migrate(settings, version, codec, context);
}

View file

@ -0,0 +1,68 @@
/*
* Modern effects for a modern Streamer
* Copyright (C) 2017-2018 Michael Fabian Dirks
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#pragma once
#include "handler.hpp"
extern "C" {
#pragma warning(push)
#pragma warning(disable : 4244)
#include <libavcodec/avcodec.h>
#pragma warning(pop)
}
namespace streamfx::encoder::ffmpeg::handler {
class amf_hevc_handler : public handler {
public:
virtual ~amf_hevc_handler(){};
public /*factory*/:
virtual void adjust_info(ffmpeg_factory* factory, const AVCodec* codec, std::string& id, std::string& name,
std::string& codec_id);
virtual void get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context, bool hw_encode);
public /*support tests*/:
virtual bool has_keyframe_support(ffmpeg_factory* instance);
virtual bool is_hardware_encoder(ffmpeg_factory* instance);
virtual bool has_threading_support(ffmpeg_factory* instance);
virtual bool has_pixel_format_support(ffmpeg_factory* instance);
public /*settings*/:
virtual void get_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context,
bool hw_encode);
virtual void migrate(obs_data_t* settings, std::uint64_t version, const AVCodec* codec,
AVCodecContext* context);
virtual void update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context);
virtual void override_update(ffmpeg_instance* instance, obs_data_t* settings);
virtual void log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context);
private:
void get_encoder_properties(obs_properties_t* props, const AVCodec* codec);
void get_runtime_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context);
};
} // namespace streamfx::encoder::ffmpeg::handler

View file

@ -0,0 +1,517 @@
// FFMPEG Video Encoder Integration for OBS Studio
// Copyright (c) 2020 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 "amf_shared.hpp"
#include "ffmpeg/tools.hpp"
extern "C" {
#pragma warning(push)
#pragma warning(disable : 4244)
#include <libavutil/opt.h>
#pragma warning(pop)
}
// Translation
#define ST_I18N "FFmpegEncoder.AMF"
#define ST_I18N_PRESET ST_I18N ".Preset"
#define ST_I18N_PRESET_(x) ST_I18N_PRESET "." x
#define ST_I18N_RATECONTROL "FFmpegEncoder.AMF.RateControl"
#define ST_I18N_RATECONTROL_MODE ST_I18N_RATECONTROL ".Mode"
#define ST_I18N_RATECONTROL_MODE_(x) ST_I18N_RATECONTROL_MODE "." x
#define ST_I18N_RATECONTROL_LOOKAHEAD ST_I18N_RATECONTROL ".LookAhead"
#define ST_I18N_RATECONTROL_FRAMESKIPPING ST_I18N_RATECONTROL ".FrameSkipping"
#define ST_I18N_RATECONTROL_LIMITS ST_I18N_RATECONTROL ".Limits"
#define ST_I18N_RATECONTROL_LIMITS_BUFFERSIZE ST_I18N_RATECONTROL_LIMITS ".BufferSize"
#define ST_I18N_RATECONTROL_LIMITS_BITRATE ST_I18N_RATECONTROL_LIMITS ".Bitrate"
#define ST_I18N_RATECONTROL_LIMITS_BITRATE_TARGET ST_I18N_RATECONTROL_LIMITS_BITRATE ".Target"
#define ST_I18N_RATECONTROL_LIMITS_BITRATE_MAXIMUM ST_I18N_RATECONTROL_LIMITS_BITRATE ".Maximum"
#define ST_I18N_RATECONTROL_QP ST_I18N_RATECONTROL ".QP"
#define ST_I18N_RATECONTROL_QP_I ST_I18N_RATECONTROL_QP ".I"
#define ST_I18N_RATECONTROL_QP_P ST_I18N_RATECONTROL_QP ".P"
#define ST_I18N_RATECONTROL_QP_B ST_I18N_RATECONTROL_QP ".B"
#define ST_I18N_OTHER ST_I18N ".Other"
#define ST_I18N_OTHER_BFRAMES ST_I18N_OTHER ".BFrames"
#define ST_I18N_OTHER_BFRAMEREFERENCES ST_I18N_OTHER ".BFrameReferences"
#define ST_I18N_OTHER_REFERENCEFRAMES ST_I18N_OTHER ".ReferenceFrames"
#define ST_I18N_OTHER_ENFORCEHRD ST_I18N_OTHER ".EnforceHRD"
#define ST_I18N_OTHER_VBAQ ST_I18N_OTHER ".VBAQ"
#define ST_I18N_OTHER_ACCESSUNITDELIMITER ST_I18N_OTHER ".AccessUnitDelimiter"
// Settings
#define ST_KEY_PRESET "Preset"
#define ST_KEY_RATECONTROL_MODE "RateControl.Mode"
#define ST_KEY_RATECONTROL_LOOKAHEAD "RateControl.LookAhead"
#define ST_KEY_RATECONTROL_FRAMESKIPPING "RateControl.FrameSkipping"
#define ST_KEY_RATECONTROL_LIMITS_BUFFERSIZE "RateControl.Limits.BufferSize"
#define ST_KEY_RATECONTROL_LIMITS_BITRATE_TARGET "RateControl.Limits.Bitrate.Target"
#define ST_KEY_RATECONTROL_LIMITS_BITRATE_MAXIMUM "RateControl.Limits.Bitrate.Maximum"
#define ST_KEY_RATECONTROL_QP_I "RateControl.QP.I"
#define ST_KEY_RATECONTROL_QP_P "RateControl.QP.P"
#define ST_KEY_RATECONTROL_QP_B "RateControl.QP.B"
#define ST_KEY_OTHER_BFRAMES "Other.BFrames"
#define ST_KEY_OTHER_BFRAMEREFERENCES "Other.BFrameReferences"
#define ST_KEY_OTHER_REFERENCEFRAMES "Other.ReferenceFrames"
#define ST_KEY_OTHER_ENFORCEHRD "Other.EnforceHRD"
#define ST_KEY_OTHER_VBAQ "Other.VBAQ"
#define ST_KEY_OTHER_ACCESSUNITDELIMITER "Other.AccessUnitDelimiter"
using namespace streamfx::encoder::ffmpeg::handler;
std::map<amf::preset, std::string> amf::presets{
{amf::preset::SPEED, ST_I18N_PRESET_("Speed")},
{amf::preset::BALANCED, ST_I18N_PRESET_("Balanced")},
{amf::preset::QUALITY, ST_I18N_PRESET_("Quality")},
};
std::map<amf::preset, std::string> amf::preset_to_opt{
{amf::preset::SPEED, "speed"},
{amf::preset::BALANCED, "balanced"},
{amf::preset::QUALITY, "quality"},
};
std::map<amf::ratecontrolmode, std::string> amf::ratecontrolmodes{
{amf::ratecontrolmode::CQP, ST_I18N_RATECONTROL_MODE_("CQP")},
{amf::ratecontrolmode::CBR, ST_I18N_RATECONTROL_MODE_("CBR")},
{amf::ratecontrolmode::VBR_PEAK, ST_I18N_RATECONTROL_MODE_("VBR_PEAK")},
{amf::ratecontrolmode::VBR_LATENCY, ST_I18N_RATECONTROL_MODE_("VBR_LATENCY")},
};
std::map<amf::ratecontrolmode, std::string> amf::ratecontrolmode_to_opt{
{amf::ratecontrolmode::CQP, "cqp"},
{amf::ratecontrolmode::CBR, "cbr"},
{amf::ratecontrolmode::VBR_PEAK, "vbr_peak"},
{amf::ratecontrolmode::VBR_LATENCY, "vbr_latency"},
};
bool streamfx::encoder::ffmpeg::handler::amf::is_available()
{
#if defined(D_PLATFORM_WINDOWS)
#if defined(D_PLATFORM_64BIT)
std::filesystem::path lib_name = std::filesystem::u8path("amfrt64.dll");
#else
std::filesystem::path lib_name = std::filesystem::u8path("amfrt32.dll");
#endif
#elif defined(D_PLATFORM_LINUX)
#if defined(D_PLATFORM_64BIT)
std::filesystem::path lib_name = std::filesystem::u8path("libamfrt64.so.1");
#else
std::filesystem::path lib_name = std::filesystem::u8path("libamfrt32.so.1");
#endif
#endif
try {
util::library::load(lib_name);
return true;
} catch (...) {
return false;
}
}
void amf::get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context)
{
obs_data_set_default_int(settings, ST_KEY_PRESET, static_cast<int64_t>(amf::preset::BALANCED));
obs_data_set_default_int(settings, ST_KEY_RATECONTROL_MODE, static_cast<int64_t>(ratecontrolmode::CBR));
obs_data_set_default_int(settings, ST_KEY_RATECONTROL_LOOKAHEAD, -1);
obs_data_set_default_int(settings, ST_KEY_RATECONTROL_FRAMESKIPPING, -1);
//ob
obs_data_set_default_int(settings, ST_KEY_RATECONTROL_LIMITS_BITRATE_TARGET, 6000);
obs_data_set_default_int(settings, ST_KEY_RATECONTROL_LIMITS_BITRATE_MAXIMUM, 0);
obs_data_set_default_int(settings, ST_KEY_RATECONTROL_LIMITS_BUFFERSIZE, 12000);
obs_data_set_default_int(settings, ST_KEY_RATECONTROL_QP_I, -1);
obs_data_set_default_int(settings, ST_KEY_RATECONTROL_QP_P, -1);
obs_data_set_default_int(settings, ST_KEY_RATECONTROL_QP_B, -1);
obs_data_set_default_int(settings, ST_KEY_OTHER_BFRAMES, -1);
obs_data_set_default_int(settings, ST_KEY_OTHER_BFRAMEREFERENCES, -1);
obs_data_set_default_int(settings, ST_KEY_OTHER_REFERENCEFRAMES, -1);
obs_data_set_default_int(settings, ST_KEY_OTHER_ENFORCEHRD, -1);
obs_data_set_default_int(settings, ST_KEY_OTHER_VBAQ, -1);
obs_data_set_default_int(settings, ST_KEY_OTHER_ACCESSUNITDELIMITER, -1);
// Replay Buffer
obs_data_set_default_int(settings, "bitrate", 0);
}
static bool modified_ratecontrol(obs_properties_t* props, obs_property_t*, obs_data_t* settings) noexcept
{
bool have_bitrate = false;
bool have_bitrate_range = false;
bool have_qp = false;
amf::ratecontrolmode rc = static_cast<amf::ratecontrolmode>(obs_data_get_int(settings, ST_KEY_RATECONTROL_MODE));
switch (rc) {
case amf::ratecontrolmode::CQP:
have_qp = true;
break;
case amf::ratecontrolmode::INVALID:
case amf::ratecontrolmode::CBR:
have_bitrate = true;
break;
case amf::ratecontrolmode::VBR_PEAK:
case amf::ratecontrolmode::VBR_LATENCY:
have_bitrate = true;
have_bitrate_range = true;
break;
}
obs_property_set_visible(obs_properties_get(props, ST_I18N_RATECONTROL_LIMITS), have_bitrate);
obs_property_set_visible(obs_properties_get(props, ST_KEY_RATECONTROL_LIMITS_BUFFERSIZE), have_bitrate);
obs_property_set_visible(obs_properties_get(props, ST_KEY_RATECONTROL_LIMITS_BITRATE_TARGET), have_bitrate);
obs_property_set_visible(obs_properties_get(props, ST_KEY_RATECONTROL_LIMITS_BITRATE_MAXIMUM), have_bitrate_range);
obs_property_set_visible(obs_properties_get(props, ST_I18N_RATECONTROL_QP), have_qp);
obs_property_set_visible(obs_properties_get(props, ST_KEY_RATECONTROL_QP_I), have_qp);
obs_property_set_visible(obs_properties_get(props, ST_KEY_RATECONTROL_QP_P), have_qp);
obs_property_set_visible(obs_properties_get(props, ST_KEY_RATECONTROL_QP_B), have_qp);
return true;
}
void amf::get_properties_pre(obs_properties_t* props, const AVCodec* codec)
{
auto p = obs_properties_add_list(props, ST_KEY_PRESET, D_TRANSLATE(ST_I18N_PRESET), OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_I18N_PRESET)));
for (auto kv : presets) {
obs_property_list_add_int(p, D_TRANSLATE(kv.second.c_str()), static_cast<int64_t>(kv.first));
}
}
void amf::get_properties_post(obs_properties_t* props, const AVCodec* codec)
{
{ // Rate Control
obs_properties_t* grp = obs_properties_create();
obs_properties_add_group(props, ST_I18N_RATECONTROL, D_TRANSLATE(ST_I18N_RATECONTROL), OBS_GROUP_NORMAL, grp);
{
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_long_description(p, D_TRANSLATE(D_DESC(ST_I18N_RATECONTROL_MODE)));
obs_property_set_modified_callback(p, modified_ratecontrol);
for (auto kv : ratecontrolmodes) {
obs_property_list_add_int(p, D_TRANSLATE(kv.second.c_str()), static_cast<int64_t>(kv.first));
}
}
{
auto p = util::obs_properties_add_tristate(grp, ST_KEY_RATECONTROL_LOOKAHEAD,
D_TRANSLATE(ST_I18N_RATECONTROL_LOOKAHEAD));
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_I18N_RATECONTROL_LOOKAHEAD)));
}
{
auto p = util::obs_properties_add_tristate(grp, ST_KEY_RATECONTROL_FRAMESKIPPING,
D_TRANSLATE(ST_I18N_RATECONTROL_FRAMESKIPPING));
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_I18N_RATECONTROL_FRAMESKIPPING)));
}
}
{
obs_properties_t* grp = obs_properties_create();
obs_properties_add_group(props, ST_I18N_RATECONTROL_LIMITS, D_TRANSLATE(ST_I18N_RATECONTROL_LIMITS),
OBS_GROUP_NORMAL, grp);
{
auto p = obs_properties_add_int(grp, ST_KEY_RATECONTROL_LIMITS_BITRATE_TARGET,
D_TRANSLATE(ST_I18N_RATECONTROL_LIMITS_BITRATE_TARGET), -1,
std::numeric_limits<std::int32_t>::max(), 1);
obs_property_int_set_suffix(p, " kbit/s");
}
{
auto p = obs_properties_add_int(grp, ST_KEY_RATECONTROL_LIMITS_BITRATE_MAXIMUM,
D_TRANSLATE(ST_I18N_RATECONTROL_LIMITS_BITRATE_MAXIMUM), -1,
std::numeric_limits<std::int32_t>::max(), 1);
obs_property_int_set_suffix(p, " kbit/s");
}
{
auto p = obs_properties_add_int(grp, ST_KEY_RATECONTROL_LIMITS_BUFFERSIZE,
D_TRANSLATE(ST_I18N_RATECONTROL_LIMITS_BUFFERSIZE), 0,
std::numeric_limits<std::int32_t>::max(), 1);
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_I18N_RATECONTROL_LIMITS_BUFFERSIZE)));
obs_property_int_set_suffix(p, " kbit");
}
}
{
obs_properties_t* grp = obs_properties_create();
obs_properties_add_group(props, ST_I18N_RATECONTROL_QP, D_TRANSLATE(ST_I18N_RATECONTROL_QP), OBS_GROUP_NORMAL,
grp);
{
auto p = obs_properties_add_int_slider(grp, ST_KEY_RATECONTROL_QP_I, D_TRANSLATE(ST_I18N_RATECONTROL_QP_I),
-1, 51, 1);
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_I18N_RATECONTROL_QP_I)));
}
{
auto p = obs_properties_add_int_slider(grp, ST_KEY_RATECONTROL_QP_P, D_TRANSLATE(ST_I18N_RATECONTROL_QP_P),
-1, 51, 1);
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_I18N_RATECONTROL_QP_P)));
}
if (std::string_view("amf_h264") == codec->name) {
auto p = obs_properties_add_int_slider(grp, ST_KEY_RATECONTROL_QP_B, D_TRANSLATE(ST_I18N_RATECONTROL_QP_B),
-1, 51, 1);
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_I18N_RATECONTROL_QP_B)));
}
}
{
obs_properties_t* grp = obs_properties_create();
obs_properties_add_group(props, ST_I18N_OTHER, D_TRANSLATE(ST_I18N_OTHER), OBS_GROUP_NORMAL, grp);
{
auto p =
obs_properties_add_int_slider(grp, ST_KEY_OTHER_BFRAMES, D_TRANSLATE(ST_I18N_OTHER_BFRAMES), -1, 4, 1);
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_I18N_OTHER_BFRAMES)));
obs_property_int_set_suffix(p, " frames");
}
{
auto p = util::obs_properties_add_tristate(grp, ST_KEY_OTHER_BFRAMEREFERENCES,
D_TRANSLATE(ST_I18N_OTHER_BFRAMEREFERENCES));
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_I18N_OTHER_BFRAMEREFERENCES)));
}
{
auto p = obs_properties_add_int_slider(grp, ST_KEY_OTHER_REFERENCEFRAMES,
D_TRANSLATE(ST_I18N_OTHER_REFERENCEFRAMES), -1, 16, 1);
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_I18N_OTHER_REFERENCEFRAMES)));
obs_property_int_set_suffix(p, " frames");
}
{
auto p =
util::obs_properties_add_tristate(grp, ST_KEY_OTHER_ENFORCEHRD, D_TRANSLATE(ST_I18N_OTHER_ENFORCEHRD));
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_I18N_OTHER_ENFORCEHRD)));
}
{
auto p = util::obs_properties_add_tristate(grp, ST_KEY_OTHER_VBAQ, D_TRANSLATE(ST_I18N_OTHER_VBAQ));
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_I18N_OTHER_VBAQ)));
}
{
auto p = util::obs_properties_add_tristate(grp, ST_KEY_OTHER_ACCESSUNITDELIMITER,
D_TRANSLATE(ST_I18N_OTHER_ACCESSUNITDELIMITER));
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_I18N_OTHER_ACCESSUNITDELIMITER)));
}
}
}
void amf::update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context)
{
// Alway enable loop filter.
context->flags |= AV_CODEC_FLAG_LOOP_FILTER;
// Always transcoding. Other usage options cause problems.
av_opt_set(context->priv_data, "usage", "transcoding", AV_OPT_SEARCH_CHILDREN);
{ // Presets
preset c_preset = static_cast<preset>(obs_data_get_int(settings, ST_KEY_PRESET));
auto found = preset_to_opt.find(c_preset);
if (found != preset_to_opt.end()) {
av_opt_set(context->priv_data, "quality", found->second.c_str(), AV_OPT_SEARCH_CHILDREN);
} else {
av_opt_set(context->priv_data, "quality", nullptr, AV_OPT_SEARCH_CHILDREN);
}
}
{ // Rate Control
bool have_bitrate = false;
bool have_bitrate_range = false;
bool have_qp = false;
ratecontrolmode rc = static_cast<ratecontrolmode>(obs_data_get_int(settings, ST_KEY_RATECONTROL_MODE));
auto rcopt = ratecontrolmode_to_opt.find(rc);
if (rcopt != ratecontrolmode_to_opt.end()) {
av_opt_set(context->priv_data, "rc", rcopt->second.c_str(), AV_OPT_SEARCH_CHILDREN);
} else {
have_bitrate = true;
av_opt_set(context->priv_data, "rc", "cbr", AV_OPT_SEARCH_CHILDREN);
}
av_opt_set_int(context->priv_data, "filler_data", 0, AV_OPT_SEARCH_CHILDREN);
switch (rc) {
case ratecontrolmode::CQP:
have_qp = true;
break;
case ratecontrolmode::INVALID:
case ratecontrolmode::CBR:
have_bitrate = true;
av_opt_set_int(context->priv_data, "filler_data", 1, AV_OPT_SEARCH_CHILDREN);
break;
case ratecontrolmode::VBR_PEAK:
case ratecontrolmode::VBR_LATENCY:
have_bitrate_range = true;
have_bitrate = true;
break;
}
// Look Ahead (Pre-analysis, single frame lookahead)
if (int la = static_cast<int>(obs_data_get_int(settings, ST_KEY_RATECONTROL_LOOKAHEAD));
!util::is_tristate_default(la)) {
av_opt_set_int(context->priv_data, "preanalysis", la, AV_OPT_SEARCH_CHILDREN);
}
// Frame Skipping (Drop frames to maintain bitrate limits)
if (int la = static_cast<int>(obs_data_get_int(settings, ST_KEY_RATECONTROL_FRAMESKIPPING));
!util::is_tristate_default(la)) {
if (std::string_view("amf_h264") == codec->name) {
av_opt_set_int(context->priv_data, "frame_skipping", la, AV_OPT_SEARCH_CHILDREN);
} else {
av_opt_set_int(context->priv_data, "skip_frame", la, AV_OPT_SEARCH_CHILDREN);
}
}
if (have_bitrate) {
int64_t v = obs_data_get_int(settings, ST_KEY_RATECONTROL_LIMITS_BITRATE_TARGET);
if (v > -1) {
context->bit_rate = static_cast<int>(v * 1000);
context->rc_max_rate = context->bit_rate;
// Support for Replay Buffer
obs_data_set_int(settings, "bitrate", v);
} else {
obs_data_set_int(settings, "bitrate", context->bit_rate);
}
} else {
context->bit_rate = 0;
}
if (have_bitrate_range) {
if (int64_t max = obs_data_get_int(settings, ST_KEY_RATECONTROL_LIMITS_BITRATE_MAXIMUM); max > -1)
context->rc_max_rate = static_cast<int>(max * 1000);
} else {
context->rc_max_rate = 0;
}
// Buffer Size
if (have_bitrate || have_bitrate_range) {
if (int64_t v = obs_data_get_int(settings, ST_KEY_RATECONTROL_LIMITS_BUFFERSIZE); v > -1)
context->rc_buffer_size = static_cast<int>(v * 1000);
} else {
context->rc_buffer_size = 0;
}
// QP Settings
if (have_qp) {
if (int64_t qp = obs_data_get_int(settings, ST_KEY_RATECONTROL_QP_I); qp > -1) {
av_opt_set_int(context->priv_data, "qp_i", static_cast<int>(qp), AV_OPT_SEARCH_CHILDREN);
}
if (int64_t qp = obs_data_get_int(settings, ST_KEY_RATECONTROL_QP_P); qp > -1) {
av_opt_set_int(context->priv_data, "qp_p", static_cast<int>(qp), AV_OPT_SEARCH_CHILDREN);
}
if (std::string_view("amf_h264") == codec->name) {
if (int64_t qp = obs_data_get_int(settings, ST_KEY_RATECONTROL_QP_B); qp > -1) {
av_opt_set_int(context->priv_data, "qp_b", static_cast<int>(qp), AV_OPT_SEARCH_CHILDREN);
}
}
}
}
{ // Other
if (std::string_view("amf_h264") == codec->name) {
if (int64_t bf = obs_data_get_int(settings, ST_KEY_OTHER_BFRAMES); bf > -1) {
context->max_b_frames = static_cast<int>(bf);
}
if (int64_t zl = obs_data_get_int(settings, ST_KEY_OTHER_BFRAMEREFERENCES);
!util::is_tristate_default(zl)) {
av_opt_set_int(context->priv_data, "bf_ref", zl, AV_OPT_SEARCH_CHILDREN);
}
}
if (int64_t refs = obs_data_get_int(settings, ST_KEY_OTHER_REFERENCEFRAMES); refs > -1) {
context->refs = static_cast<int>(refs);
}
if (int64_t v = obs_data_get_int(settings, ST_KEY_OTHER_ENFORCEHRD); !util::is_tristate_default(v)) {
av_opt_set_int(context->priv_data, "enforce_hrd", v, AV_OPT_SEARCH_CHILDREN);
}
if (int64_t v = obs_data_get_int(settings, ST_KEY_OTHER_VBAQ); !util::is_tristate_default(v)) {
av_opt_set_int(context->priv_data, "vbaq", v, AV_OPT_SEARCH_CHILDREN);
}
if (int64_t v = obs_data_get_int(settings, ST_KEY_OTHER_ACCESSUNITDELIMITER); !util::is_tristate_default(v)) {
av_opt_set_int(context->priv_data, "aud", v, AV_OPT_SEARCH_CHILDREN);
}
av_opt_set_int(context->priv_data, "me_half_pel", 1, AV_OPT_SEARCH_CHILDREN);
av_opt_set_int(context->priv_data, "me_quarter_pel", 1, AV_OPT_SEARCH_CHILDREN);
}
}
void amf::log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context)
{
using namespace ::ffmpeg;
DLOG_INFO("[%s] AMD AMF:", codec->name);
tools::print_av_option_string2(context, "usage", " Usage",
[](int64_t v, std::string_view o) { return std::string(o); });
tools::print_av_option_string2(context, "quality", " Preset",
[](int64_t v, std::string_view o) { return std::string(o); });
tools::print_av_option_string2(context, "rc", " Rate Control",
[](int64_t v, std::string_view o) { return std::string(o); });
tools::print_av_option_bool(context, "preanalysis", " Look-Ahead");
if (std::string_view("amf_h264") == codec->name) {
tools::print_av_option_bool(context, "frame_skipping", " Frame Skipping");
} else {
tools::print_av_option_bool(context, "skip_frame", " Frame Skipping");
}
tools::print_av_option_bool(context, "filler_data", " Filler Data");
DLOG_INFO("[%s] Bitrate:", codec->name);
tools::print_av_option_int(context, "b", " Target", "bits/sec");
tools::print_av_option_int(context, "maxrate", " Maximum", "bits/sec");
tools::print_av_option_int(context, "bufsize", " Buffer", "bits");
DLOG_INFO("[%s] Quantization Parameters:", codec->name);
tools::print_av_option_int(context, "qp_i", " I-Frame", "");
tools::print_av_option_int(context, "qp_p", " P-Frame", "");
if (std::string_view("amf_h264") == codec->name) { // B-Frames
tools::print_av_option_int(context, "qp_b", " B-Frame", "");
tools::print_av_option_int(context, "bf", " B-Frames", "Frames");
tools::print_av_option_int(context, "bf_delta_qp", " Delta QP", "");
tools::print_av_option_bool(context, "bf_ref", " References");
tools::print_av_option_int(context, "bf_ref_delta_qp", " Delta QP", "");
}
DLOG_INFO("[%s] Other:", codec->name);
tools::print_av_option_int(context, "refs", " Reference Frames", "Frames");
tools::print_av_option_bool(context, "enforce_hrd", " Enforce HRD");
tools::print_av_option_bool(context, "vbaq", " VBAQ");
tools::print_av_option_bool(context, "aud", " Access Unit Delimiter");
tools::print_av_option_int(context, "max_au_size", " Maximum Size", "");
tools::print_av_option_bool(context, "me_half_pel", " Half-Pel Motion Estimation");
tools::print_av_option_bool(context, "me_quarter_pel", " Quarter-Pel Motion Estimation");
}
void streamfx::encoder::ffmpeg::handler::amf::get_runtime_properties(obs_properties_t* props, const AVCodec* codec,
AVCodecContext* context)
{}
void streamfx::encoder::ffmpeg::handler::amf::migrate(obs_data_t* settings, uint64_t version, const AVCodec* codec,
AVCodecContext* context)
{}
void streamfx::encoder::ffmpeg::handler::amf::override_update(ffmpeg_instance* instance, obs_data_t* settings) {}

View file

@ -0,0 +1,129 @@
// FFMPEG Video Encoder Integration for OBS Studio
// Copyright (c) 2020 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.
#pragma once
#include "common.hpp"
#include "handler.hpp"
extern "C" {
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4242 4244 4365)
#endif
#include <libavcodec/avcodec.h>
#ifdef _MSC_VER
#pragma warning(pop)
#endif
}
namespace streamfx::encoder::ffmpeg::handler::amf {
enum class preset : int32_t {
SPEED,
BALANCED,
QUALITY,
INVALID = -1,
};
enum class ratecontrolmode : int64_t {
CQP,
CBR,
VBR_PEAK,
VBR_LATENCY,
INVALID = -1,
};
extern std::map<preset, std::string> presets;
extern std::map<preset, std::string> preset_to_opt;
extern std::map<ratecontrolmode, std::string> ratecontrolmodes;
extern std::map<ratecontrolmode, std::string> ratecontrolmode_to_opt;
bool is_available();
void get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context);
void get_properties_pre(obs_properties_t* props, const AVCodec* codec);
void get_properties_post(obs_properties_t* props, const AVCodec* codec);
void get_runtime_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context);
void migrate(obs_data_t* settings, uint64_t version, const AVCodec* codec, AVCodecContext* context);
void update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context);
void override_update(ffmpeg_instance* instance, obs_data_t* settings);
void log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context);
} // namespace streamfx::encoder::ffmpeg::handler::amf
/* Parameters by their codec specific name.
* '#' denotes a parameter specified via the context itself.
H.264 H.265 Options Done?
usage usage transcoding --
preset preset speed,balanced,quality Defines
profile profile <different> Defines
level level <different> Defines
tier main,high
rc rc cqp,cbr,vbr_peak,vbr_latency Defines
preanalysis preanalysis false,true Defines
vbaq vbaq false,true Defines
enforce_hrd enforce_hrd false,true Defines
filler_data filler_data false,true --
frame_skipping skip_frame false,true Defines
qp_i qp_i range(-1 - 51) Defines
qp_p qp_p range(-1 - 51) Defines
qp_b range(-1 - 51) Defines
#max_b_frames Defines
bf_delta_qp range(-10 - 10) --
bf_ref false,true Defines
bf_ref_delta_qp range(-10 - 10) --
me_half_pel me_half_pel false,true --
me_quarter_pel me_quarter_pel false,true --
aud aud false,true Defines
max_au_size max_au_size range(0 - Inf) --
#refs range(0 - 16?) Defines
#color_range AVCOL_RANGE_JPEG FFmpeg
#bit_rate Defines
#rc_max_rate Defines
#rc_buffer_size Defines
#rc_initial_buffer_occupancy --
#flags AV_CODEC_FLAG_LOOP_FILTER --
#gop_size FFmpeg
*/
// AMF H.264
// intra_refresh_mb: 0 - Inf
// header_spacing: -1 - 1000
// coder: auto, cavlc, cabac
// qmin, qmax (HEVC uses its own settings)
// AMF H.265
// header_insertion_mode: none, gop, idr
// gops_per_idr: 0 - Inf
// min_qp_i: -1 - 51
// max_qp_i: -1 - 51
// min_qp_p: -1 - 51
// max_qp_p: -1 - 51