encoders/generic: Implement generic encoder for all ffmpeg encoders

This commit is contained in:
Michael Fabian 'Xaymar' Dirks
2019-07-06 13:08:23 +02:00
parent 143b7f585f
commit 9a58a6a5ff
5 changed files with 898 additions and 11 deletions
+3 -1
View File
@@ -52,7 +52,7 @@ endif()
# Define Project
project(
xmr-ffmpeg-encoders
obs-ffmpeg-encoder
VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}.${VERSION_TWEAK}
)
set(PROJECT_FULL_NAME "FFMPEG Encoder for OBS Studio")
@@ -268,6 +268,8 @@ set(PROJECT_PRIVATE
"${PROJECT_SOURCE_DIR}/source/plugin.hpp"
"${PROJECT_SOURCE_DIR}/source/utility.cpp"
"${PROJECT_SOURCE_DIR}/source/utility.hpp"
"${PROJECT_SOURCE_DIR}/source/encoders/generic.hpp"
"${PROJECT_SOURCE_DIR}/source/encoders/generic.cpp"
"${PROJECT_SOURCE_DIR}/source/encoders/prores_aw.hpp"
"${PROJECT_SOURCE_DIR}/source/encoders/prores_aw.cpp"
"${PROJECT_SOURCE_DIR}/source/ffmpeg/avframe-queue.cpp"
+39 -10
View File
@@ -1,15 +1,44 @@
Bitrate="Bitrate"
Bitrate.Description="Bitrate in kbit/s"
KeyFrame.Type="Key Frame Type"
KeyFrame.Type.Frames="Frames"
KeyFrame.Type.Seconds="Seconds"
KeyFrame.Interval="Key Frame Interval"
KeyFrame.Interval.Description="Interval in which a Key Frame is placed."
CustomParameters="Custom Parameters"
CustomParameters.Description="Format: key1=val1 key2=val2 key3='val3 val4'"
# ProRes
ProRes.Profile.Proxy="Proxy (PXY)"
ProRes.Profile.Light="Light (LT)"
ProRes.Profile.Standard="Standard"
ProRes.Profile.HighQuality="High Quality (HQ)"
# Rate Control
RateControl="Rate Control"
RateControl.Bitrate="Bitrate"
RateControl.Bitrate.Description="Bitrate in kbit/s. Not supported by all encoders."
RateControl.Profile="Profile"
RateControl.Profile.None="None"
RateControl.KeyFrame="Key-Frames / Group of Pictures (GOP)"
RateControl.KeyFrame.Type="Key-Frame Interval Type"
RateControl.KeyFrame.Type.Description="Defines how the interval value should be read."
RateControl.KeyFrame.Type.Frames="Frames"
RateControl.KeyFrame.Type.Seconds="Seconds"
RateControl.KeyFrame.Interval="Key Frame Interval"
RateControl.KeyFrame.Interval.Description="Interval in which a Key Frame is placed. Not all encoders support this."
# Threading
MultiThreading="Multi-Threading"
MultiThreading.Model="Threading Model"
MultiThreading.Model.Description="Some encoders offer multi threading capabilities which can take advantage of multi-core systems."
MultiThreading.Model.Automatic="Automatic"
MultiThreading.Model.None="Single Threaded"
MultiThreading.Model.Frame="Frame-Threading"
MultiThreading.Model.Slice="Slice-Threading"
MultiThreading.ThreadCount="Number of Threads"
MultiThreading.ThreadCount.Description="The number of threads to use, with 0 being automatic.\nEncoders with 'Automatic-Threading' in the name control threads themselves and will behave different with the 0 value."
MultiThreading.FrameQueue="Enable Frame Queue"
MultiThreading.FrameQueue.Description="Some encoders require us to keep a frame in memory while it is being processed by the encoder.\nThis option fixes corruption due to this, but adds significant latency and memory usage."
# FFmpeg
FFmpeg="FFmpeg Options"
FFmpeg.CustomSettings="Custom Settings"
FFmpeg.CustomSettings.Description="Custom settings that override any detected options above, use with caution.\nThe input should be in the format 'key=value;key=value;...'."
FFmpeg.StandardCompliance="Standard Compliance"
FFmpeg.StandardCompliance.Description="How strict should the encoder keep to the standard? A strictness below 'Normal' may cause issues with playback."
FFmpeg.StandardCompliance.VeryStrict="Very Strict"
FFmpeg.StandardCompliance.Strict="Strict"
FFmpeg.StandardCompliance.Normal="Normal"
FFmpeg.StandardCompliance.Unofficial="Unofficial"
FFmpeg.StandardCompliance.Experimental="Experimental"
+742
View File
@@ -0,0 +1,742 @@
// FFMPEG Video Encoder Integration for OBS Studio
// Copyright (C) 2018 - 2019 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 "generic.hpp"
#include <iomanip>
#include <sstream>
#include <thread>
#include "ffmpeg/tools.hpp"
#include "plugin.hpp"
#include "utility.hpp"
extern "C" {
#include <obs-module.h>
#pragma warning(push)
#pragma warning(disable : 4244)
#include "libavutil/dict.h"
#include "libavutil/frame.h"
#include "libavutil/opt.h"
#pragma warning(pop)
}
// Rate Control
#define P_RATECONTROL "RateControl"
#define P_RATECONTROL_PROFILE "RateControl.Profile"
#define P_RATECONTROL_BITRATE "RateControl.Bitrate"
#define P_RATECONTROL_MAXBITRATE "RateControl.MaxBitrate"
#define P_RATECONTROL_VBVBUFFER "RateControl.VBVBuffer"
#define P_RATECONTROL_KEYFRAME "RateControl.KeyFrame"
#define P_RATECONTROL_KEYFRAME_TYPE "RateControl.KeyFrame.Type"
#define P_RATECONTROL_KEYFRAME_INTERVAL "RateControl.KeyFrame.Interval"
// Threading
#define P_MULTITHREADING "MultiThreading"
#define P_MULTITHREADING_MODEL "MultiThreading.Model"
#define P_MULTITHREADING_THREADCOUNT "MultiThreading.ThreadCount"
#define P_MULTITHREADING_FRAMEQUEUE "MultiThreading.FrameQueue"
// FFmpeg
#define P_FFMPEG "FFmpeg"
#define P_FFMPEG_CUSTOMSETTINGS "FFmpeg.CustomSettings"
#define P_FFMPEG_STANDARDCOMPLIANCE "FFmpeg.StandardCompliance"
enum class keyframe_type { Seconds, Frames };
encoder::generic_factory::generic_factory(AVCodec* codec) : avcodec_ptr(codec), info() {}
encoder::generic_factory::~generic_factory() {}
void encoder::generic_factory::register_encoder()
{
// Generate unique name from given Id
{
std::stringstream sstr;
sstr << "ffmpeg-" << avcodec_ptr->name << "-0x" << std::uppercase << std::setfill('0') << std::setw(8)
<< std::hex << avcodec_ptr->capabilities;
this->info.uid = sstr.str();
}
// Also generate a human readable name while we're at it.
// TODO: Figure out a way to translate from names to other names.
{
std::stringstream sstr;
sstr << "[FFmpeg] " << (avcodec_ptr->long_name ? avcodec_ptr->long_name : avcodec_ptr->name) << " ("
<< avcodec_ptr->name << ")";
std::string caps = ffmpeg::tools::translate_encoder_capabilities(avcodec_ptr->capabilities);
if (caps.length() != 0) {
sstr << " (" << caps << ")";
}
this->info.readable_name = sstr.str();
}
// Assign codec (ffmpeg name).
this->info.codec = avcodec_ptr->name;
this->info.oei.id = this->info.uid.c_str();
this->info.oei.codec = this->info.codec.c_str();
// Detect encoder type (only Video and Audio supported)
if (avcodec_ptr->type == AVMediaType::AVMEDIA_TYPE_VIDEO) {
this->info.oei.type = obs_encoder_type::OBS_ENCODER_VIDEO;
} else if (avcodec_ptr->type == AVMediaType::AVMEDIA_TYPE_AUDIO) {
this->info.oei.type = obs_encoder_type::OBS_ENCODER_AUDIO;
} else {
throw std::invalid_argument("unsupported codec type");
}
// Register functions.
this->info.oei.create = [](obs_data_t* settings, obs_encoder_t* encoder) {
try {
return reinterpret_cast<void*>(new generic(settings, encoder));
} catch (std::exception const& e) {
PLOG_ERROR("exception: %s", e.what());
throw e;
} catch (...) {
PLOG_ERROR("unknown exception");
throw;
}
};
this->info.oei.destroy = [](void* ptr) {
try {
delete reinterpret_cast<generic*>(ptr);
} catch (std::exception const& e) {
PLOG_ERROR("exception: %s", e.what());
throw e;
} catch (...) {
PLOG_ERROR("unknown exception");
throw;
}
};
this->info.oei.get_name = [](void* type_data) {
try {
return reinterpret_cast<generic_factory*>(type_data)->get_name();
} catch (std::exception const& e) {
PLOG_ERROR("exception: %s", e.what());
throw e;
} catch (...) {
PLOG_ERROR("unknown exception");
throw;
}
};
this->info.oei.get_defaults2 = [](obs_data_t* settings, void* type_data) {
try {
reinterpret_cast<generic_factory*>(type_data)->get_defaults(settings);
} catch (std::exception const& e) {
PLOG_ERROR("exception: %s", e.what());
throw e;
} catch (...) {
PLOG_ERROR("unknown exception");
throw;
}
};
this->info.oei.get_properties2 = [](void* ptr, void* type_data) {
try {
obs_properties_t* props = obs_properties_create();
if (type_data != nullptr) {
reinterpret_cast<generic_factory*>(type_data)->get_properties(props);
}
if (ptr != nullptr) {
reinterpret_cast<generic*>(ptr)->get_properties(props);
}
return props;
} catch (std::exception const& e) {
PLOG_ERROR("exception: %s", e.what());
throw e;
} catch (...) {
PLOG_ERROR("unknown exception");
throw;
}
};
this->info.oei.update = [](void* ptr, obs_data_t* settings) {
try {
return reinterpret_cast<generic*>(ptr)->update(settings);
} catch (std::exception const& e) {
PLOG_ERROR("exception: %s", e.what());
throw e;
} catch (...) {
PLOG_ERROR("unknown exception");
throw;
}
};
this->info.oei.encode = [](void* ptr, struct encoder_frame* frame, struct encoder_packet* packet,
bool* received_packet) {
try {
return reinterpret_cast<generic*>(ptr)->encode(frame, packet, received_packet);
} catch (std::exception const& e) {
PLOG_ERROR("exception: %s", e.what());
throw e;
} catch (...) {
PLOG_ERROR("unknown exception");
throw;
}
};
this->info.oei.get_audio_info = [](void* ptr, struct audio_convert_info* info) {
try {
reinterpret_cast<generic*>(ptr)->get_audio_info(info);
} catch (std::exception const& e) {
PLOG_ERROR("exception: %s", e.what());
throw e;
} catch (...) {
PLOG_ERROR("unknown exception");
throw;
}
};
this->info.oei.get_frame_size = [](void* ptr) {
try {
return reinterpret_cast<generic*>(ptr)->get_frame_size();
} catch (std::exception const& e) {
PLOG_ERROR("exception: %s", e.what());
throw e;
} catch (...) {
PLOG_ERROR("unknown exception");
throw;
}
};
this->info.oei.get_video_info = [](void* ptr, struct video_scale_info* info) {
try {
reinterpret_cast<generic*>(ptr)->get_video_info(info);
} catch (std::exception const& e) {
PLOG_ERROR("exception: %s", e.what());
throw e;
} catch (...) {
PLOG_ERROR("unknown exception");
throw;
}
};
this->info.oei.get_sei_data = [](void* ptr, uint8_t** sei_data, size_t* size) {
try {
return reinterpret_cast<generic*>(ptr)->get_sei_data(sei_data, size);
} catch (std::exception const& e) {
PLOG_ERROR("exception: %s", e.what());
throw e;
} catch (...) {
PLOG_ERROR("unknown exception");
throw;
}
};
this->info.oei.get_extra_data = [](void* ptr, uint8_t** extra_data, size_t* size) {
try {
return reinterpret_cast<generic*>(ptr)->get_extra_data(extra_data, size);
} catch (std::exception const& e) {
PLOG_ERROR("exception: %s", e.what());
throw e;
} catch (...) {
PLOG_ERROR("unknown exception");
throw;
}
};
this->info.oei.encode_texture = [](void* ptr, uint32_t handle, int64_t pts, uint64_t lock_key,
uint64_t* next_key, struct encoder_packet* packet, bool* received_packet) {
try {
return reinterpret_cast<generic*>(ptr)->encode_texture(handle, pts, lock_key, next_key, packet,
received_packet);
} catch (std::exception const& e) {
PLOG_ERROR("exception: %s", e.what());
throw e;
} catch (...) {
PLOG_ERROR("unknown exception");
throw;
}
};
// Finally store ourself as type data.
this->info.oei.type_data = this;
obs_register_encoder(&this->info.oei);
PLOG_INFO("Registered encoder #%llX with name '%s' and long name '%s' and caps %llX", avcodec_ptr,
avcodec_ptr->name, avcodec_ptr->long_name, avcodec_ptr->capabilities);
}
const char* encoder::generic_factory::get_name()
{
return this->info.readable_name.c_str();
}
void encoder::generic_factory::get_defaults(obs_data_t* settings)
{
AVCodecContext* ctx = avcodec_alloc_context3(this->avcodec_ptr);
if (ctx->priv_data) {
const AVOption* opt = nullptr;
while ((opt = av_opt_next(ctx->priv_data, opt)) != nullptr) {
if (opt->type == AV_OPT_TYPE_CONST && opt->unit != nullptr) {
continue;
}
switch (opt->type) {
case AV_OPT_TYPE_BOOL:
obs_data_set_default_bool(settings, opt->name, !!opt->default_val.i64);
break;
case AV_OPT_TYPE_INT:
case AV_OPT_TYPE_INT64:
case AV_OPT_TYPE_UINT64:
obs_data_set_default_int(settings, opt->name, opt->default_val.i64);
break;
case AV_OPT_TYPE_FLOAT:
case AV_OPT_TYPE_DOUBLE:
obs_data_set_default_double(settings, opt->name, opt->default_val.dbl);
break;
case AV_OPT_TYPE_STRING:
obs_data_set_default_string(settings, opt->name, opt->default_val.str);
break;
}
}
}
{ // Integrated Options
// Rate Control
obs_data_set_default_int(settings, P_RATECONTROL_PROFILE, 0);
obs_data_set_default_int(settings, P_RATECONTROL_BITRATE, 2500);
obs_data_set_default_int(settings, P_RATECONTROL_KEYFRAME_TYPE, 0);
obs_data_set_default_double(settings, P_RATECONTROL_KEYFRAME_INTERVAL ".Seconds", 2.0);
obs_data_set_default_int(settings, P_RATECONTROL_KEYFRAME_INTERVAL ".Frames", 300);
// Threading
obs_data_set_default_int(settings, P_MULTITHREADING_MODEL, -1);
obs_data_set_default_bool(settings, P_MULTITHREADING_FRAMEQUEUE, true);
obs_data_set_default_int(settings, P_MULTITHREADING_THREADCOUNT, 0);
// FFmpeg
obs_data_set_default_string(settings, P_FFMPEG_CUSTOMSETTINGS, "");
obs_data_set_default_int(settings, P_FFMPEG_STANDARDCOMPLIANCE, FF_COMPLIANCE_STRICT);
}
}
void encoder::generic_factory::get_properties(obs_properties_t* props)
{
// Encoder Options
AVCodecContext* ctx = avcodec_alloc_context3(this->avcodec_ptr);
if (ctx->priv_data) {
std::map<std::string, std::pair<obs_property_t*, const AVOption*>> unit_property_map;
const AVOption* opt = nullptr;
while ((opt = av_opt_next(ctx->priv_data, opt)) != nullptr) {
obs_property_t* p = nullptr;
// Constants are parts of a unit, not actual options.
if (opt->type == AV_OPT_TYPE_CONST) {
auto unit = unit_property_map.find(opt->unit);
if (unit == unit_property_map.end()) {
continue;
}
switch (unit->second.second->type) {
case AV_OPT_TYPE_INT:
case AV_OPT_TYPE_INT64:
obs_property_list_add_int(unit->second.first, opt->name, opt->default_val.i64);
break;
case AV_OPT_TYPE_FLOAT:
case AV_OPT_TYPE_DOUBLE:
obs_property_list_add_float(unit->second.first, opt->name,
opt->default_val.dbl);
break;
case AV_OPT_TYPE_STRING:
obs_property_list_add_string(unit->second.first, opt->name,
opt->default_val.str);
break;
default:
throw std::runtime_error("unhandled const type");
}
continue;
}
switch (opt->type) {
case AV_OPT_TYPE_BOOL:
p = obs_properties_add_bool(props, opt->name, opt->name);
break;
case AV_OPT_TYPE_INT:
case AV_OPT_TYPE_INT64:
case AV_OPT_TYPE_UINT64:
if (opt->unit != nullptr) {
p = obs_properties_add_list(props, opt->name, opt->name, OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
unit_property_map.emplace(opt->unit,
std::pair<obs_property_t*, const AVOption*>{p, opt});
} else {
p = obs_properties_add_int(props, opt->name, opt->name, opt->min, opt->max, 1);
}
break;
case AV_OPT_TYPE_FLOAT:
case AV_OPT_TYPE_DOUBLE:
if (opt->unit != nullptr) {
p = obs_properties_add_list(props, opt->name, opt->name, OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_FLOAT);
unit_property_map.emplace(opt->unit,
std::pair<obs_property_t*, const AVOption*>{p, opt});
} else {
p = obs_properties_add_float(props, opt->name, opt->name, opt->min, opt->max,
0.01);
}
break;
case AV_OPT_TYPE_STRING:
if (opt->unit != nullptr) {
p = obs_properties_add_list(props, opt->name, opt->name, OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_STRING);
unit_property_map.emplace(opt->unit,
std::pair<obs_property_t*, const AVOption*>{p, opt});
} else {
p = obs_properties_add_text(props, opt->name, opt->name,
obs_text_type::OBS_TEXT_DEFAULT);
}
break;
case AV_OPT_TYPE_FLAGS:
case AV_OPT_TYPE_RATIONAL:
case AV_OPT_TYPE_VIDEO_RATE:
case AV_OPT_TYPE_BINARY:
case AV_OPT_TYPE_DICT:
case AV_OPT_TYPE_IMAGE_SIZE:
case AV_OPT_TYPE_PIXEL_FMT:
case AV_OPT_TYPE_SAMPLE_FMT:
case AV_OPT_TYPE_DURATION:
case AV_OPT_TYPE_COLOR:
case AV_OPT_TYPE_CHANNEL_LAYOUT:
PLOG_WARNING("Skipped option '%s' for codec '%s' as option type is not supported.",
opt->name, this->info.uid);
break;
}
if ((opt->flags & AV_OPT_FLAG_READONLY) && (p != nullptr)) {
obs_property_set_enabled(p, false);
}
}
}
avcodec_free_context(&ctx);
// FFmpeg Options
{ /// Rate Control
auto prs = obs_properties_create();
{
auto grp = obs_properties_create();
auto p1 = obs_properties_add_list(grp, P_RATECONTROL_KEYFRAME_TYPE,
TRANSLATE(P_RATECONTROL_KEYFRAME_TYPE), OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_property_set_long_description(p1, TRANSLATE(DESC(P_RATECONTROL_KEYFRAME_TYPE)));
obs_property_set_modified_callback2(p1, modified_ratecontrol_properties, this);
obs_property_list_add_int(p1, TRANSLATE(P_RATECONTROL_KEYFRAME_TYPE ".Seconds"),
static_cast<int64_t>(keyframe_type::Seconds));
obs_property_list_add_int(p1, TRANSLATE(P_RATECONTROL_KEYFRAME_TYPE ".Frames"),
static_cast<int64_t>(keyframe_type::Frames));
auto p2 = obs_properties_add_float(grp, P_RATECONTROL_KEYFRAME_INTERVAL ".Seconds",
TRANSLATE(P_RATECONTROL_KEYFRAME_INTERVAL), 0.0,
std::numeric_limits<double_t>::max(), 0.01);
obs_property_set_long_description(p2, TRANSLATE(DESC(P_RATECONTROL_KEYFRAME_INTERVAL)));
auto p3 = obs_properties_add_int(grp, P_RATECONTROL_KEYFRAME_INTERVAL ".Frames",
TRANSLATE(P_RATECONTROL_KEYFRAME_INTERVAL), 0,
std::numeric_limits<int32_t>::max(), 1);
obs_property_set_long_description(p3, TRANSLATE(DESC(P_RATECONTROL_KEYFRAME_INTERVAL)));
obs_properties_add_group(prs, P_RATECONTROL_KEYFRAME, TRANSLATE(P_RATECONTROL_KEYFRAME),
OBS_GROUP_NORMAL, grp);
}
{
auto p = obs_properties_add_int(prs, P_RATECONTROL_BITRATE, TRANSLATE(P_RATECONTROL_BITRATE), 1,
std::numeric_limits<int32_t>::max(), 1);
obs_property_set_long_description(p, TRANSLATE(DESC(P_RATECONTROL_BITRATE)));
}
{
auto p = obs_properties_add_list(prs, P_RATECONTROL_PROFILE, TRANSLATE(P_RATECONTROL_PROFILE),
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
if (this->avcodec_ptr->profiles) {
auto profile = this->avcodec_ptr->profiles;
obs_property_list_add_int(p, TRANSLATE(P_RATECONTROL_PROFILE ".None"),
FF_PROFILE_UNKNOWN);
while (profile->profile != FF_PROFILE_UNKNOWN) {
obs_property_list_add_int(p, profile->name, profile->profile);
profile++;
}
}
}
obs_properties_add_group(props, P_RATECONTROL, TRANSLATE(P_RATECONTROL), OBS_GROUP_NORMAL, prs);
};
{ /// Threading
auto prs = obs_properties_create();
{
auto p = obs_properties_add_list(prs, P_MULTITHREADING_MODEL, TRANSLATE(P_MULTITHREADING_MODEL),
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
obs_property_set_long_description(p, TRANSLATE(DESC(P_MULTITHREADING_MODEL)));
obs_property_set_modified_callback2(p, modified_threading_properties, this);
obs_property_list_add_int(p, TRANSLATE(P_MULTITHREADING_MODEL ".Automatic"), -1);
obs_property_list_add_int(p, TRANSLATE(P_MULTITHREADING_MODEL ".None"), 0);
{
auto idx = obs_property_list_add_int(p, TRANSLATE(P_MULTITHREADING_MODEL ".Frame"),
FF_THREAD_FRAME);
obs_property_list_item_disable(
p, idx, !(this->avcodec_ptr->capabilities & AV_CODEC_CAP_FRAME_THREADS));
}
{
auto idx = obs_property_list_add_int(p, TRANSLATE(P_MULTITHREADING_MODEL ".Slice"),
FF_THREAD_SLICE);
obs_property_list_item_disable(
p, idx, !(this->avcodec_ptr->capabilities & AV_CODEC_CAP_SLICE_THREADS));
}
};
{
auto p = obs_properties_add_int_slider(prs, P_MULTITHREADING_THREADCOUNT,
TRANSLATE(P_MULTITHREADING_THREADCOUNT), 0,
std::thread::hardware_concurrency() * 4, 1);
obs_property_set_long_description(p, TRANSLATE(DESC(P_MULTITHREADING_THREADCOUNT)));
};
{
auto p = obs_properties_add_bool(prs, P_MULTITHREADING_FRAMEQUEUE,
TRANSLATE(P_MULTITHREADING_FRAMEQUEUE));
obs_property_set_long_description(p, TRANSLATE(DESC(P_MULTITHREADING_FRAMEQUEUE)));
};
obs_properties_add_group(props, P_MULTITHREADING, TRANSLATE(P_MULTITHREADING), OBS_GROUP_NORMAL, prs);
};
{
auto prs = obs_properties_create();
{
auto p =
obs_properties_add_text(prs, P_FFMPEG_CUSTOMSETTINGS, TRANSLATE(P_FFMPEG_CUSTOMSETTINGS),
obs_text_type::OBS_TEXT_DEFAULT);
obs_property_set_long_description(p, TRANSLATE(DESC(P_FFMPEG_CUSTOMSETTINGS)));
}
{
auto p = obs_properties_add_list(prs, P_FFMPEG_STANDARDCOMPLIANCE,
TRANSLATE(P_FFMPEG_STANDARDCOMPLIANCE), OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_property_set_long_description(p, TRANSLATE(DESC(P_FFMPEG_STANDARDCOMPLIANCE)));
obs_property_list_add_int(p, TRANSLATE(P_FFMPEG_STANDARDCOMPLIANCE ".VeryStrict"),
FF_COMPLIANCE_VERY_STRICT);
obs_property_list_add_int(p, TRANSLATE(P_FFMPEG_STANDARDCOMPLIANCE ".Strict"),
FF_COMPLIANCE_STRICT);
obs_property_list_add_int(p, TRANSLATE(P_FFMPEG_STANDARDCOMPLIANCE ".Normal"),
FF_COMPLIANCE_NORMAL);
obs_property_list_add_int(p, TRANSLATE(P_FFMPEG_STANDARDCOMPLIANCE ".Unofficial"),
FF_COMPLIANCE_UNOFFICIAL);
obs_property_list_add_int(p, TRANSLATE(P_FFMPEG_STANDARDCOMPLIANCE ".Experimental"),
FF_COMPLIANCE_EXPERIMENTAL);
}
obs_properties_add_group(props, P_FFMPEG, TRANSLATE(P_FFMPEG), OBS_GROUP_NORMAL, prs);
};
}
AVCodec* encoder::generic_factory::get_avcodec()
{
return this->avcodec_ptr;
}
bool encoder::generic_factory::modified_ratecontrol_properties(void* priv, obs_properties_t* props,
obs_property_t* prop, obs_data_t* settings)
{
keyframe_type kft = static_cast<keyframe_type>(obs_data_get_int(settings, P_RATECONTROL_KEYFRAME_TYPE));
obs_property_set_visible(obs_properties_get(props, P_RATECONTROL_KEYFRAME_INTERVAL ".Seconds"),
kft == keyframe_type::Seconds);
obs_property_set_visible(obs_properties_get(props, P_RATECONTROL_KEYFRAME_INTERVAL ".Frames"),
kft == keyframe_type::Frames);
return true;
}
bool encoder::generic_factory::modified_threading_properties(void* priv, obs_properties_t* props, obs_property_t* prop,
obs_data_t* settings)
{
int64_t tt = obs_data_get_int(settings, P_MULTITHREADING_MODEL);
obs_property_set_visible(obs_properties_get(props, P_MULTITHREADING_THREADCOUNT), tt != 0);
return true;
}
encoder::generic::generic(obs_data_t* settings, obs_encoder_t* encoder) : self(encoder)
{
this->factory = reinterpret_cast<generic_factory*>(obs_encoder_get_type_data(self));
// Verify that the codec actually still exists.
this->codec = avcodec_find_encoder(this->factory->get_avcodec()->id);
if (!this->codec) {
PLOG_ERROR("Failed to find encoder for codec '%s'.", this->factory->get_avcodec()->name);
throw std::runtime_error("failed to find codec");
}
// Initialize context.
this->context = avcodec_alloc_context3(this->codec);
if (!this->context) {
PLOG_ERROR("Failed to create context for encoder '%s'.", this->codec->name);
throw std::runtime_error("failed to create context");
}
// Settings
/// Rate Control
this->context->profile = obs_data_get_int(settings, P_RATECONTROL_PROFILE);
this->context->bit_rate = obs_data_get_int(settings, P_RATECONTROL_BITRATE);
this->context->strict_std_compliance = obs_data_get_int(settings, P_FFMPEG_STANDARDCOMPLIANCE);
this->context->debug = 0;
/// Threading
{
int64_t tt = obs_data_get_int(settings, P_MULTITHREADING_MODEL);
if (tt == 0) {
this->context->thread_count = 1;
this->context->thread_type = 0;
} else if (tt == -1) {
if (this->codec->capabilities & AV_CODEC_CAP_SLICE_THREADS) {
this->context->thread_type = FF_THREAD_SLICE;
} else if (this->codec->capabilities & AV_CODEC_CAP_FRAME_THREADS) {
this->context->thread_type = FF_THREAD_FRAME;
} else {
this->context->thread_type = 0;
}
} else {
this->context->thread_type = tt;
}
if (tt != 0) {
this->context->thread_count = obs_data_get_int(settings, P_MULTITHREADING_THREADCOUNT);
if (!(this->codec->capabilities & AV_CODEC_CAP_AUTO_THREADS)) {
if (this->context->thread_count == 0) {
this->context->thread_count = std::thread::hardware_concurrency();
}
}
}
}
// Video and Audio exclusive setup
if (this->codec->type == AVMEDIA_TYPE_VIDEO) {
// FFmpeg Video Settings
auto encvideo = obs_encoder_video(this->self);
auto voi = video_output_get_info(encvideo);
/// Resolution and framerate.
this->context->width = voi->width;
this->context->height = voi->height;
this->context->time_base.num = voi->fps_num;
this->context->time_base.den = voi->fps_den;
this->context->ticks_per_frame = 1;
/// Group of Pictures
if (static_cast<keyframe_type>(obs_data_get_int(settings, P_RATECONTROL_KEYFRAME_TYPE))
== keyframe_type::Frames) {
this->context->gop_size = obs_data_get_int(settings, P_RATECONTROL_KEYFRAME_INTERVAL ".Frames");
} else {
double_t real_gop = obs_data_get_double(settings, P_RATECONTROL_KEYFRAME_INTERVAL ".Seconds");
this->context->gop_size = static_cast<int64_t>(real_gop * voi->fps_num / voi->fps_den);
}
/// Color, Profile
this->context->colorspace = ffmpeg::tools::obs_videocolorspace_to_avcolorspace(voi->colorspace);
this->context->color_range = ffmpeg::tools::obs_videorangetype_to_avcolorrange(voi->range);
this->context->pix_fmt = ffmpeg::tools::obs_videoformat_to_avpixelformat(voi->format);
this->context->field_order = AV_FIELD_PROGRESSIVE;
} else if (this->codec->type == AVMEDIA_TYPE_AUDIO) {
}
// Update settings
this->update(settings);
// Initialize
int res = avcodec_open2(this->context, this->codec, NULL);
if (res < 0) {
PLOG_ERROR("Failed to initialize encoder '%s' due to error code %lld: %s", this->codec->name, res,
ffmpeg::tools::get_error_description(res));
throw std::runtime_error(ffmpeg::tools::get_error_description(res));
}
// Video/Audio exclusive setup part 2.
if (this->codec->type == AVMEDIA_TYPE_VIDEO) {
// Create Scaler
swscale.set_source_size(this->context->width, this->context->height);
swscale.set_target_size(this->context->width, this->context->height);
swscale.set_source_format(this->context->pix_fmt);
swscale.set_target_format(this->context->pix_fmt);
swscale.set_source_color(this->context->color_range, this->context->colorspace);
swscale.set_target_color(this->context->color_range, this->context->colorspace);
if (!swscale.initialize(SWS_FAST_BILINEAR)) {
PLOG_ERROR(
" Failed to initialize Software Scaler for pixel format '%s' with color space '%s' and "
"range '%s'.",
ffmpeg::tools::get_pixel_format_name(this->context->pix_fmt),
ffmpeg::tools::get_color_space_name(this->context->colorspace),
swscale.is_source_full_range() ? "Full" : "Partial");
throw std::runtime_error("failed to initialize swscaler.");
}
// Create Frame queue
frame_queue.set_pixel_format(this->context->pix_fmt);
frame_queue.set_resolution(this->context->width, this->context->height);
if (obs_data_get_bool(settings, P_MULTITHREADING_FRAMEQUEUE)) {
if (this->context->thread_count > 0) {
this->frame_queue.precache(this->context->thread_count);
} else {
if (this->context->thread_type != 0) {
this->frame_queue.precache(std::thread::hardware_concurrency());
} else {
this->frame_queue.precache(1);
}
}
} else {
this->frame_queue.precache(1);
}
} else if (this->codec->type == AVMEDIA_TYPE_AUDIO) {
}
// Create Packet
this->current_packet = av_packet_alloc();
if (!this->current_packet) {
PLOG_ERROR("Failed to allocate packet storage.");
throw std::runtime_error("Failed to allocate packet storage.");
}
}
encoder::generic::~generic()
{
this->frame_queue.clear();
this->frame_queue_used.clear();
this->swscale.finalize();
if (this->context) {
avcodec_close(this->context);
avcodec_free_context(&this->context);
}
}
void encoder::generic::get_properties(obs_properties_t* props) {}
bool encoder::generic::update(obs_data_t* settings)
{
return false;
}
bool encoder::generic::encode(encoder_frame* frame, encoder_packet* packet, bool* received_packet)
{
return false;
}
void encoder::generic::get_audio_info(audio_convert_info* info) {}
size_t encoder::generic::get_frame_size()
{
return size_t();
}
void encoder::generic::get_video_info(video_scale_info* info) {}
bool encoder::generic::get_sei_data(uint8_t** sei_data, size_t* size)
{
return false;
}
bool encoder::generic::get_extra_data(uint8_t** extra_data, size_t* size)
{
if (!this->context->extradata) {
return false;
}
*extra_data = this->context->extradata;
*size = this->context->extradata_size;
return true;
}
bool encoder::generic::encode_texture(uint32_t handle, int64_t pts, uint64_t lock_key, uint64_t* next_key,
encoder_packet* packet, bool* received_packet)
{
return false;
}
+95
View File
@@ -0,0 +1,95 @@
// FFMPEG Video Encoder Integration for OBS Studio
// Copyright (C) 2018 - 2019 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 <encoder.hpp>
#include "ffmpeg/avframe-queue.hpp"
#include "ffmpeg/swscale.hpp"
namespace encoder {
class generic_factory {
struct info {
std::string uid;
std::string codec;
std::string readable_name;
obs_encoder_info oei;
} info;
AVCodec* avcodec_ptr;
public:
generic_factory(AVCodec* codec);
virtual ~generic_factory();
void register_encoder();
const char* get_name();
void get_defaults(obs_data_t* settings);
void get_properties(obs_properties_t* props);
AVCodec* get_avcodec();
public:
static bool modified_ratecontrol_properties(void* priv, obs_properties_t* props, obs_property_t* prop,
obs_data_t* settings);
static bool modified_threading_properties(void* priv, obs_properties_t* props, obs_property_t* prop,
obs_data_t* settings);
};
class generic {
obs_encoder_t* self;
generic_factory* factory;
AVCodec* codec;
AVCodecContext* context;
ffmpeg::avframe_queue frame_queue;
ffmpeg::avframe_queue frame_queue_used;
ffmpeg::swscale swscale;
AVPacket* current_packet = nullptr;
public:
generic(obs_data_t* settings, obs_encoder_t* encoder);
virtual ~generic();
// Shared
void get_properties(obs_properties_t* props);
bool update(obs_data_t* settings);
bool encode(struct encoder_frame* frame, struct encoder_packet* packet, bool* received_packet);
// Audio only
void get_audio_info(struct audio_convert_info* info);
size_t get_frame_size();
// Video only
void get_video_info(struct video_scale_info* info);
bool get_sei_data(uint8_t** sei_data, size_t* size);
bool get_extra_data(uint8_t** extra_data, size_t* size);
// GPU Video only
bool encode_texture(uint32_t handle, int64_t pts, uint64_t lock_key, uint64_t* next_key,
struct encoder_packet* packet, bool* received_packet);
};
} // namespace encoder
+19
View File
@@ -16,10 +16,12 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
#include "plugin.hpp"
#include <memory>
#include <obs-module.h>
#include <obs.h>
#include "utility.hpp"
#include "encoders/generic.hpp"
#include "encoders/prores_aw.hpp"
extern "C" {
@@ -29,10 +31,27 @@ extern "C" {
#pragma warning(pop)
}
static std::map<AVCodec*, std::shared_ptr<encoder::generic_factory>> generic_factories;
MODULE_EXPORT bool obs_module_load(void)
{
try {
avcodec_register_all();
// Register all codecs.
AVCodec* cdc = nullptr;
while ((cdc = av_codec_next(cdc)) != nullptr) {
if ((!cdc->encode2) && (!cdc->send_frame))
continue;
if ((cdc->type == AVMediaType::AVMEDIA_TYPE_AUDIO)
|| (cdc->type == AVMediaType::AVMEDIA_TYPE_VIDEO)) {
auto ptr = std::make_shared<encoder::generic_factory>(cdc);
ptr->register_encoder();
generic_factories.emplace(cdc, ptr);
}
}
obsffmpeg::encoder::prores_aw::initialize();
return true;
} catch (std::exception ex) {