encoder: Delay init until resolution, framerate and color are finalized

Fixes a crash due to reading/writing out of bounds when the resolution, color and framerate end up different than initially expected during the creation of the encoder. While this does change the error message that appears when the encoder can't be initialized, it is better than outright crashing OBS Studio.
This commit is contained in:
Michael Fabian 'Xaymar' Dirks
2019-08-11 21:39:18 +02:00
parent a4b3131c50
commit 5c8939b4a8
2 changed files with 202 additions and 352 deletions
+196 -352
View File
@@ -21,15 +21,15 @@
#include "encoder.hpp"
#include <iomanip>
#include <map>
#include <set>
#include <sstream>
#include <thread>
#include <util/profiler.hpp>
#include "codecs/hevc.hpp"
#include "ffmpeg/tools.hpp"
#include "plugin.hpp"
#include "strings.hpp"
#include "utility.hpp"
#include "codecs/hevc.hpp"
extern "C" {
#include <obs-avc.h>
@@ -56,7 +56,7 @@ enum class keyframe_type { SECONDS, FRAMES };
obsffmpeg::encoder_factory::encoder_factory(const AVCodec* codec) : avcodec_ptr(codec), info()
{
// Unique Id is FFmpeg name.
this->info.uid = avcodec_ptr->name;
info.uid = avcodec_ptr->name;
// Also generate a human readable name while we're at it.
{
@@ -68,32 +68,32 @@ obsffmpeg::encoder_factory::encoder_factory(const AVCodec* codec) : avcodec_ptr(
if (avcodec_ptr->long_name) {
sstr << " (" << avcodec_ptr->name << ")";
}
this->info.readable_name = sstr.str();
info.readable_name = sstr.str();
// Allow UI Handler to replace visible name.
obsffmpeg::find_codec_handler(this->avcodec_ptr->name)
->override_visible_name(this->avcodec_ptr, this->info.readable_name);
obsffmpeg::find_codec_handler(avcodec_ptr->name)
->override_visible_name(avcodec_ptr, info.readable_name);
}
// Assign Ids.
{
const AVCodecDescriptor* desc = avcodec_descriptor_get(this->avcodec_ptr->id);
const AVCodecDescriptor* desc = avcodec_descriptor_get(avcodec_ptr->id);
if (desc) {
this->info.codec = desc->name;
info.codec = desc->name;
} else {
// Fall back to encoder name in the case that FFmpeg itself doesn't know
// what codec this actually is.
this->info.codec = avcodec_ptr->name;
info.codec = avcodec_ptr->name;
}
}
this->info.oei.id = this->info.uid.c_str();
this->info.oei.codec = this->info.codec.c_str();
info.oei.id = info.uid.c_str();
info.oei.codec = info.codec.c_str();
#ifndef _DEBUG
// Is this a deprecated encoder?
if (!obsffmpeg::has_codec_handler(avcodec_ptr->name)) {
this->info.oei.caps |= OBS_ENCODER_CAP_DEPRECATED;
info.oei.caps |= OBS_ENCODER_CAP_DEPRECATED;
}
#endif
}
@@ -104,15 +104,15 @@ void obsffmpeg::encoder_factory::register_encoder()
{
// 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;
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;
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) {
info.oei.create = [](obs_data_t* settings, obs_encoder_t* encoder) {
try {
return reinterpret_cast<void*>(new obsffmpeg::encoder(settings, encoder));
} catch (std::exception const& e) {
@@ -123,7 +123,7 @@ void obsffmpeg::encoder_factory::register_encoder()
return reinterpret_cast<void*>(0);
}
};
this->info.oei.destroy = [](void* ptr) {
info.oei.destroy = [](void* ptr) {
try {
delete reinterpret_cast<encoder*>(ptr);
} catch (std::exception const& e) {
@@ -134,7 +134,7 @@ void obsffmpeg::encoder_factory::register_encoder()
throw;
}
};
this->info.oei.get_name = [](void* type_data) {
info.oei.get_name = [](void* type_data) {
try {
return reinterpret_cast<encoder_factory*>(type_data)->get_name();
} catch (std::exception const& e) {
@@ -145,7 +145,7 @@ void obsffmpeg::encoder_factory::register_encoder()
throw;
}
};
this->info.oei.get_defaults2 = [](obs_data_t* settings, void* type_data) {
info.oei.get_defaults2 = [](obs_data_t* settings, void* type_data) {
try {
reinterpret_cast<encoder_factory*>(type_data)->get_defaults(settings);
} catch (std::exception const& e) {
@@ -156,7 +156,7 @@ void obsffmpeg::encoder_factory::register_encoder()
throw;
}
};
this->info.oei.get_properties2 = [](void* ptr, void* type_data) {
info.oei.get_properties2 = [](void* ptr, void* type_data) {
try {
obs_properties_t* props = obs_properties_create();
if (type_data != nullptr) {
@@ -174,7 +174,7 @@ void obsffmpeg::encoder_factory::register_encoder()
throw;
}
};
this->info.oei.update = [](void* ptr, obs_data_t* settings) {
info.oei.update = [](void* ptr, obs_data_t* settings) {
try {
return reinterpret_cast<encoder*>(ptr)->update(settings);
} catch (std::exception const& e) {
@@ -185,7 +185,7 @@ void obsffmpeg::encoder_factory::register_encoder()
throw;
}
};
this->info.oei.get_sei_data = [](void* ptr, uint8_t** sei_data, size_t* size) {
info.oei.get_sei_data = [](void* ptr, uint8_t** sei_data, size_t* size) {
try {
return reinterpret_cast<encoder*>(ptr)->get_sei_data(sei_data, size);
} catch (std::exception const& e) {
@@ -196,7 +196,7 @@ void obsffmpeg::encoder_factory::register_encoder()
throw;
}
};
this->info.oei.get_extra_data = [](void* ptr, uint8_t** extra_data, size_t* size) {
info.oei.get_extra_data = [](void* ptr, uint8_t** extra_data, size_t* size) {
try {
return reinterpret_cast<encoder*>(ptr)->get_extra_data(extra_data, size);
} catch (std::exception const& e) {
@@ -208,8 +208,8 @@ void obsffmpeg::encoder_factory::register_encoder()
}
};
if (this->avcodec_ptr->type == AVMediaType::AVMEDIA_TYPE_VIDEO) {
this->info.oei.get_video_info = [](void* ptr, struct video_scale_info* info) {
if (avcodec_ptr->type == AVMediaType::AVMEDIA_TYPE_VIDEO) {
info.oei.get_video_info = [](void* ptr, struct video_scale_info* info) {
try {
reinterpret_cast<encoder*>(ptr)->get_video_info(info);
} catch (std::exception const& e) {
@@ -220,8 +220,8 @@ void obsffmpeg::encoder_factory::register_encoder()
throw;
}
};
this->info.oei.encode = [](void* ptr, struct encoder_frame* frame, struct encoder_packet* packet,
bool* received_packet) {
info.oei.encode = [](void* ptr, struct encoder_frame* frame, struct encoder_packet* packet,
bool* received_packet) {
try {
return reinterpret_cast<encoder*>(ptr)->video_encode(frame, packet, received_packet);
} catch (std::exception const& e) {
@@ -232,9 +232,8 @@ void obsffmpeg::encoder_factory::register_encoder()
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) {
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<encoder*>(ptr)->video_encode_texture(
handle, pts, lock_key, next_key, packet, received_packet);
@@ -247,8 +246,8 @@ void obsffmpeg::encoder_factory::register_encoder()
}
};
} else if (this->avcodec_ptr->type == AVMediaType::AVMEDIA_TYPE_AUDIO) {
this->info.oei.get_audio_info = [](void* ptr, struct audio_convert_info* info) {
} else if (avcodec_ptr->type == AVMediaType::AVMEDIA_TYPE_AUDIO) {
info.oei.get_audio_info = [](void* ptr, struct audio_convert_info* info) {
try {
reinterpret_cast<encoder*>(ptr)->get_audio_info(info);
} catch (std::exception const& e) {
@@ -259,7 +258,7 @@ void obsffmpeg::encoder_factory::register_encoder()
throw;
}
};
this->info.oei.get_frame_size = [](void* ptr) {
info.oei.get_frame_size = [](void* ptr) {
try {
return reinterpret_cast<encoder*>(ptr)->get_frame_size();
} catch (std::exception const& e) {
@@ -270,8 +269,8 @@ void obsffmpeg::encoder_factory::register_encoder()
throw;
}
};
this->info.oei.encode = [](void* ptr, struct encoder_frame* frame, struct encoder_packet* packet,
bool* received_packet) {
info.oei.encode = [](void* ptr, struct encoder_frame* frame, struct encoder_packet* packet,
bool* received_packet) {
try {
return reinterpret_cast<encoder*>(ptr)->audio_encode(frame, packet, received_packet);
} catch (std::exception const& e) {
@@ -285,28 +284,28 @@ void obsffmpeg::encoder_factory::register_encoder()
}
// Finally store ourself as type data.
this->info.oei.type_data = this;
info.oei.type_data = this;
obs_register_encoder(&this->info.oei);
obs_register_encoder(&info.oei);
PLOG_DEBUG("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* obsffmpeg::encoder_factory::get_name()
{
return this->info.readable_name.c_str();
return info.readable_name.c_str();
}
void obsffmpeg::encoder_factory::get_defaults(obs_data_t* settings)
{
{ // Handler
auto ptr = obsffmpeg::find_codec_handler(this->avcodec_ptr->name);
auto ptr = obsffmpeg::find_codec_handler(avcodec_ptr->name);
if (ptr) {
ptr->get_defaults(settings, this->avcodec_ptr, nullptr);
ptr->get_defaults(settings, avcodec_ptr, nullptr);
}
}
if ((this->avcodec_ptr->capabilities & AV_CODEC_CAP_INTRA_ONLY) == 0) {
if ((avcodec_ptr->capabilities & AV_CODEC_CAP_INTRA_ONLY) == 0) {
obs_data_set_default_int(settings, S_KEYFRAMES_INTERVALTYPE, 0);
obs_data_set_default_double(settings, S_KEYFRAMES_INTERVAL_SECONDS, 2.0);
obs_data_set_default_int(settings, S_KEYFRAMES_INTERVAL_FRAMES, 300);
@@ -332,13 +331,13 @@ static bool modified_keyframes(obs_properties_t* props, obs_property_t*, obs_dat
void obsffmpeg::encoder_factory::get_properties(obs_properties_t* props)
{
{ // Handler
auto ptr = obsffmpeg::find_codec_handler(this->avcodec_ptr->name);
auto ptr = obsffmpeg::find_codec_handler(avcodec_ptr->name);
if (ptr) {
ptr->get_properties(props, this->avcodec_ptr, nullptr);
ptr->get_properties(props, avcodec_ptr, nullptr);
}
}
if ((this->avcodec_ptr->capabilities & AV_CODEC_CAP_INTRA_ONLY) == 0) {
if ((avcodec_ptr->capabilities & AV_CODEC_CAP_INTRA_ONLY) == 0) {
// Key-Frame Options
obs_properties_t* grp = props;
if (!obsffmpeg::are_property_groups_broken()) {
@@ -385,18 +384,18 @@ void obsffmpeg::encoder_factory::get_properties(obs_properties_t* props)
obs_text_type::OBS_TEXT_DEFAULT);
obs_property_set_long_description(p, TRANSLATE(DESC(ST_FFMPEG_CUSTOMSETTINGS)));
}
if (this->avcodec_ptr->pix_fmts) {
if (avcodec_ptr->pix_fmts) {
auto p = obs_properties_add_list(grp, ST_FFMPEG_COLORFORMAT, TRANSLATE(ST_FFMPEG_COLORFORMAT),
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
obs_property_set_long_description(p, TRANSLATE(DESC(ST_FFMPEG_COLORFORMAT)));
obs_property_list_add_int(p, TRANSLATE(S_STATE_AUTOMATIC),
static_cast<int64_t>(AV_PIX_FMT_NONE));
for (auto ptr = this->avcodec_ptr->pix_fmts; *ptr != AV_PIX_FMT_NONE; ptr++) {
for (auto ptr = avcodec_ptr->pix_fmts; *ptr != AV_PIX_FMT_NONE; ptr++) {
obs_property_list_add_int(p, ffmpeg::tools::get_pixel_format_name(*ptr),
static_cast<int64_t>(*ptr));
}
}
if (this->avcodec_ptr->capabilities & (AV_CODEC_CAP_FRAME_THREADS | AV_CODEC_CAP_SLICE_THREADS)) {
if (avcodec_ptr->capabilities & (AV_CODEC_CAP_FRAME_THREADS | AV_CODEC_CAP_SLICE_THREADS)) {
auto p = obs_properties_add_int_slider(grp, ST_FFMPEG_THREADS, TRANSLATE(ST_FFMPEG_THREADS), 0,
std::thread::hardware_concurrency() * 2, 1);
obs_property_set_long_description(p, TRANSLATE(DESC(ST_FFMPEG_THREADS)));
@@ -422,217 +421,167 @@ void obsffmpeg::encoder_factory::get_properties(obs_properties_t* props)
const AVCodec* obsffmpeg::encoder_factory::get_avcodec()
{
return this->avcodec_ptr;
return avcodec_ptr;
}
obsffmpeg::encoder::encoder(obs_data_t* settings, obs_encoder_t* encoder)
: _self(encoder), _lag_in_frames(0), _count_send_frames(0), _have_first_frame(false)
: _self(encoder), _lag_in_frames(0), _count_send_frames(0), _have_first_frame(false), _initialized(false)
{
this->_factory = reinterpret_cast<encoder_factory*>(obs_encoder_get_type_data(_self));
_factory = reinterpret_cast<encoder_factory*>(obs_encoder_get_type_data(_self));
// Verify that the codec actually still exists.
this->_codec = avcodec_find_encoder_by_name(this->_factory->get_avcodec()->name);
if (!this->_codec) {
PLOG_ERROR("Failed to find encoder for codec '%s'.", this->_factory->get_avcodec()->name);
_codec = avcodec_find_encoder_by_name(_factory->get_avcodec()->name);
if (!_codec) {
PLOG_ERROR("Failed to find encoder for codec '%s'.", _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);
_context = avcodec_alloc_context3(_codec);
if (!_context) {
PLOG_ERROR("Failed to create context for encoder '%s'.", _codec->name);
throw std::runtime_error("failed to create context");
}
// Settings
/// Rate Control
this->_context->strict_std_compliance =
static_cast<int>(obs_data_get_int(settings, ST_FFMPEG_STANDARDCOMPLIANCE));
this->_context->debug = 0;
_context->strict_std_compliance = static_cast<int>(obs_data_get_int(settings, ST_FFMPEG_STANDARDCOMPLIANCE));
_context->debug = 0;
/// Threading
if (this->_codec->capabilities
if (_codec->capabilities
& (AV_CODEC_CAP_AUTO_THREADS | AV_CODEC_CAP_FRAME_THREADS | AV_CODEC_CAP_SLICE_THREADS)) {
if (this->_codec->capabilities & AV_CODEC_CAP_FRAME_THREADS) {
this->_context->thread_type |= FF_THREAD_FRAME;
if (_codec->capabilities & AV_CODEC_CAP_FRAME_THREADS) {
_context->thread_type |= FF_THREAD_FRAME;
}
if (this->_codec->capabilities & AV_CODEC_CAP_SLICE_THREADS) {
this->_context->thread_type |= FF_THREAD_SLICE;
if (_codec->capabilities & AV_CODEC_CAP_SLICE_THREADS) {
_context->thread_type |= FF_THREAD_SLICE;
}
int64_t threads = obs_data_get_int(settings, ST_FFMPEG_THREADS);
if (threads > 0) {
this->_context->thread_count = static_cast<int>(threads);
this->_lag_in_frames = this->_context->thread_count;
_context->thread_count = static_cast<int>(threads);
_lag_in_frames = _context->thread_count;
} else {
this->_context->thread_count = std::thread::hardware_concurrency();
this->_lag_in_frames = this->_context->thread_count;
_context->thread_count = std::thread::hardware_concurrency();
_lag_in_frames = _context->thread_count;
}
}
// 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
this->_context->width = voi->width;
this->_context->height = voi->height;
this->_swscale.set_source_size(this->_context->width, this->_context->height);
this->_swscale.set_target_size(this->_context->width, this->_context->height);
// Color
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->field_order = AV_FIELD_PROGRESSIVE;
this->_swscale.set_source_color(this->_context->color_range, this->_context->colorspace);
this->_swscale.set_target_color(this->_context->color_range, this->_context->colorspace);
// Pixel Format
{
// Due to unsupported color formats and ffmpeg not automatically converting formats from A to B,
// we have to detect the closest format that we can still use and initialize our swscale instance
// these formats. This has a massive cost attached unfortunately.
AVPixelFormat source = ffmpeg::tools::obs_videoformat_to_avpixelformat(voi->format);
AVPixelFormat target = AV_PIX_FMT_NONE;
// Prefer lossless or zero-change formats.
for (auto ptr = this->_codec->pix_fmts; *ptr != AV_PIX_FMT_NONE; ptr++) {
switch (source) {
case AV_PIX_FMT_RGBA:
switch (*ptr) {
case AV_PIX_FMT_RGB0:
case AV_PIX_FMT_RGBA:
target = *ptr;
break;
case AV_PIX_FMT_BGR0:
case AV_PIX_FMT_BGRA:
target = *ptr;
break;
}
break;
case AV_PIX_FMT_BGRA:
case AV_PIX_FMT_BGR0:
switch (*ptr) {
case AV_PIX_FMT_RGB0:
case AV_PIX_FMT_RGBA:
target = *ptr;
break;
case AV_PIX_FMT_BGR0:
case AV_PIX_FMT_BGRA:
target = *ptr;
break;
}
break;
case AV_PIX_FMT_YUV444P:
case AV_PIX_FMT_YUYV422:
case AV_PIX_FMT_YVYU422:
case AV_PIX_FMT_UYVY422:
case AV_PIX_FMT_YUV420P:
case AV_PIX_FMT_NV12:
if (*ptr == source) {
target = *ptr;
}
break;
}
}
if (target == AV_PIX_FMT_NONE) {
int loss = 0;
target =
avcodec_find_best_pix_fmt_of_list(this->_codec->pix_fmts, source, false, &loss);
}
this->_context->pix_fmt = target;
this->_swscale.set_source_format(source);
this->_swscale.set_target_format(this->_context->pix_fmt);
PLOG_INFO("Automatically detected target format '%s' for source format '%s'.",
ffmpeg::tools::get_pixel_format_name(target),
ffmpeg::tools::get_pixel_format_name(source));
}
AVPixelFormat color_format_override =
static_cast<AVPixelFormat>(obs_data_get_int(settings, ST_FFMPEG_COLORFORMAT));
if (color_format_override != AV_PIX_FMT_NONE) {
// User specified override for color format.
this->_context->pix_fmt = color_format_override;
this->_swscale.set_target_format(this->_context->pix_fmt);
PLOG_INFO("User specified target format override '%s'.",
ffmpeg::tools::get_pixel_format_name(this->_context->pix_fmt));
}
// Framerate
this->_context->time_base.num = voi->fps_den;
this->_context->time_base.den = voi->fps_num;
this->_context->ticks_per_frame = 1;
} else if (this->_codec->type == AVMEDIA_TYPE_AUDIO) {
}
// Update settings
this->update(settings);
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
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),
this->_swscale.is_source_full_range() ? "Full" : "Partial");
throw std::runtime_error("failed to initialize swscaler.");
}
// Create Frame queue
this->_frame_queue.set_pixel_format(this->_context->pix_fmt);
this->_frame_queue.set_resolution(this->_context->width, this->_context->height);
this->_frame_queue.precache(2);
} else if (this->_codec->type == AVMEDIA_TYPE_AUDIO) {
}
av_init_packet(&this->_current_packet);
av_new_packet(&this->_current_packet, 8 * 1024 * 1024); // 8 MB precached Packet size.
av_init_packet(&_current_packet);
av_new_packet(&_current_packet, 8 * 1024 * 1024); // 8 MB precached Packet size.
}
obsffmpeg::encoder::~encoder()
{
if (this->_context) {
if (_context) {
// Flush encoders that require it.
if ((this->_codec->capabilities & AV_CODEC_CAP_DELAY) != 0) {
avcodec_send_frame(this->_context, nullptr);
while (avcodec_receive_packet(this->_context, &this->_current_packet) >= 0) {
avcodec_send_frame(this->_context, nullptr);
if ((_codec->capabilities & AV_CODEC_CAP_DELAY) != 0) {
avcodec_send_frame(_context, nullptr);
while (avcodec_receive_packet(_context, &_current_packet) >= 0) {
avcodec_send_frame(_context, nullptr);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
// Close and free context.
avcodec_close(this->_context);
avcodec_free_context(&this->_context);
avcodec_close(_context);
avcodec_free_context(&_context);
}
av_packet_unref(&this->_current_packet);
av_packet_unref(&_current_packet);
this->_frame_queue.clear();
this->_frame_queue_used.clear();
this->_swscale.finalize();
_frame_queue.clear();
_frame_queue_used.clear();
_swscale.finalize();
}
bool obsffmpeg::encoder::initialize()
{
if (_initialized)
return true;
// Detect supported
bool is_format_supported = false;
for (auto ptr = _codec->pix_fmts; *ptr != AV_PIX_FMT_NONE; ptr++) {
if (*ptr == _format) {
is_format_supported = true;
}
}
if (!is_format_supported) {
std::stringstream sstr;
sstr << "The color format " << ffmpeg::tools::get_pixel_format_name(_format)
<< " is not supported by the encoder.";
throw std::exception(sstr.str().c_str());
}
if (_codec->type == AVMEDIA_TYPE_VIDEO) {
auto encvideo = obs_encoder_video(_self);
auto voi = video_output_get_info(encvideo);
// Set Resolution
_context->width = static_cast<int>(_resolution.first);
_context->height = static_cast<int>(_resolution.second);
// Set Color Space, Format and Range
_context->colorspace = ffmpeg::tools::obs_videocolorspace_to_avcolorspace(voi->colorspace);
_context->color_range = ffmpeg::tools::obs_videorangetype_to_avcolorrange(voi->range);
_context->pix_fmt = _format;
// Set Framerate and Field Order
_context->field_order = AV_FIELD_PROGRESSIVE;
_context->time_base.num = voi->fps_den;
_context->time_base.den = voi->fps_num;
_context->ticks_per_frame = 1;
}
// Initialize
int res = avcodec_open2(_context, _codec, NULL);
if (res < 0) {
std::stringstream sstr;
sstr << "Failed to initalized encoder '" << _codec->name
<< "' due to error: " << ffmpeg::tools::get_error_description(res) << "(" << res
<< ")";
throw std::runtime_error(sstr.str().c_str());
}
// Initialize Scaler and Frame Queue
if (_codec->type == AVMEDIA_TYPE_VIDEO) {
_swscale.set_source_format(_format);
_swscale.set_target_format(_context->pix_fmt);
_swscale.set_target_size(_context->width, _context->height);
_swscale.set_source_size(_context->width, _context->height);
_swscale.set_target_size(_context->width, _context->height);
_swscale.set_source_color(_context->color_range, _context->colorspace);
_swscale.set_target_color(_context->color_range, _context->colorspace);
// Create Scaler
if (!_swscale.initialize(SWS_FAST_BILINEAR)) {
std::stringstream sstr;
sstr << "Failed to initialize frame scaler for pixel format '"
<< ffmpeg::tools::get_pixel_format_name(_context->pix_fmt) << "' with color space '"
<< ffmpeg::tools::get_color_space_name(_context->colorspace) << "' and "
<< (_swscale.is_source_full_range() ? "full" : "partial") << " color range.";
throw std::runtime_error(sstr.str().c_str());
}
// Create Frame queue
_frame_queue.set_pixel_format(_context->pix_fmt);
_frame_queue.set_resolution(_context->width, _context->height);
_frame_queue.precache(2);
}
_initialized = true;
return true;
}
void obsffmpeg::encoder::get_properties(obs_properties_t* props)
{
{ // Handler
auto ptr = obsffmpeg::find_codec_handler(this->_codec->name);
auto ptr = obsffmpeg::find_codec_handler(_codec->name);
if (ptr) {
ptr->get_properties(props, this->_codec, this->_context);
ptr->get_properties(props, _codec, _context);
}
}
@@ -649,13 +598,13 @@ void obsffmpeg::encoder::get_properties(obs_properties_t* props)
bool obsffmpeg::encoder::update(obs_data_t* settings)
{
{ // Handler
auto ptr = obsffmpeg::find_codec_handler(this->_codec->name);
auto ptr = obsffmpeg::find_codec_handler(_codec->name);
if (ptr) {
ptr->update(settings, this->_codec, this->_context);
ptr->update(settings, _codec, _context);
}
}
if ((this->_codec->capabilities & AV_CODEC_CAP_INTRA_ONLY) == 0) {
if ((_codec->capabilities & AV_CODEC_CAP_INTRA_ONLY) == 0) {
// Key-Frame Options
obs_video_info ovi;
if (!obs_get_video_info(&ovi)) {
@@ -676,8 +625,8 @@ bool obsffmpeg::encoder::update(obs_data_t* settings)
{ // FFmpeg
// Apply custom options.
av_opt_set_from_string(this->_context->priv_data,
obs_data_get_string(settings, ST_FFMPEG_CUSTOMSETTINGS), nullptr, "=", ";");
av_opt_set_from_string(_context->priv_data, obs_data_get_string(settings, ST_FFMPEG_CUSTOMSETTINGS),
nullptr, "=", ";");
}
return false;
}
@@ -696,132 +645,18 @@ bool obsffmpeg::encoder::audio_encode(encoder_frame*, encoder_packet*, bool*)
void obsffmpeg::encoder::get_video_info(video_scale_info* vsi)
{
// Apply Video Format override.
{
obs_data_t* settings = obs_encoder_get_settings(this->_self);
AVPixelFormat format_av = static_cast<AVPixelFormat>(obs_data_get_int(settings, ST_FFMPEG_COLORFORMAT));
if (format_av != AV_PIX_FMT_NONE) {
video_format format_obs = ffmpeg::tools::avpixelformat_to_obs_videoformat(format_av);
if (format_obs != VIDEO_FORMAT_NONE) {
vsi->format = format_obs;
return;
}
}
obs_data_t* settings = obs_encoder_get_settings(_self);
AVPixelFormat avformat = static_cast<AVPixelFormat>(obs_data_get_int(settings, ST_FFMPEG_COLORFORMAT));
video_format obsformat = ffmpeg::tools::avpixelformat_to_obs_videoformat(avformat);
if (obsformat != VIDEO_FORMAT_NONE) {
vsi->format = obsformat;
}
// There was no override, make sure that we got a useful video format that requires no conversion.
std::map<video_format, bool> supported;
for (auto ptr = this->_codec->pix_fmts; *ptr != AV_PIX_FMT_NONE; ptr++) {
switch (*ptr) {
case AV_PIX_FMT_RGBA:
case AV_PIX_FMT_RGB0:
supported.insert_or_assign(VIDEO_FORMAT_RGBA, true);
break;
case AV_PIX_FMT_BGRA:
supported.insert_or_assign(VIDEO_FORMAT_BGRA, true);
break;
case AV_PIX_FMT_BGR0:
supported.insert_or_assign(VIDEO_FORMAT_BGRX, true);
break;
case AV_PIX_FMT_YUV420P:
supported.insert_or_assign(VIDEO_FORMAT_I420, true);
break;
case AV_PIX_FMT_NV12:
supported.insert_or_assign(VIDEO_FORMAT_NV12, true);
break;
case AV_PIX_FMT_YVYU422:
supported.insert_or_assign(VIDEO_FORMAT_YVYU, true);
break;
case AV_PIX_FMT_UYVY422:
supported.insert_or_assign(VIDEO_FORMAT_UYVY, true);
break;
case AV_PIX_FMT_YUYV422:
supported.insert_or_assign(VIDEO_FORMAT_YUY2, true);
break;
case AV_PIX_FMT_YUV444P:
supported.insert_or_assign(VIDEO_FORMAT_I444, true);
break;
case AV_PIX_FMT_GRAY8:
supported.insert_or_assign(VIDEO_FORMAT_Y800, true);
break;
}
}
// If the video format we target is supported, don't change anything.
if (supported.count(vsi->format) > 0) {
return;
}
// If not, find the next best video format.
// This is not the best code to do this, but if it works, it's better than nothing.
std::vector<video_format> best_quality;
if (vsi->format == VIDEO_FORMAT_NV12) {
// 4:2:0
best_quality = {
VIDEO_FORMAT_NV12, VIDEO_FORMAT_I420, VIDEO_FORMAT_YVYU, VIDEO_FORMAT_YUY2, VIDEO_FORMAT_UYVY,
VIDEO_FORMAT_RGBA, VIDEO_FORMAT_BGRA, VIDEO_FORMAT_BGRX, VIDEO_FORMAT_I444, VIDEO_FORMAT_Y800,
};
} else if (vsi->format == VIDEO_FORMAT_I420) {
// 4:2:0
best_quality = {
VIDEO_FORMAT_I420, VIDEO_FORMAT_NV12, VIDEO_FORMAT_YVYU, VIDEO_FORMAT_YUY2, VIDEO_FORMAT_UYVY,
VIDEO_FORMAT_RGBA, VIDEO_FORMAT_BGRA, VIDEO_FORMAT_BGRX, VIDEO_FORMAT_I444, VIDEO_FORMAT_Y800,
};
} else if (vsi->format == VIDEO_FORMAT_YVYU) {
// 4:2:2
best_quality = {
VIDEO_FORMAT_YVYU, VIDEO_FORMAT_YUY2, VIDEO_FORMAT_UYVY, VIDEO_FORMAT_RGBA, VIDEO_FORMAT_BGRA,
VIDEO_FORMAT_BGRX, VIDEO_FORMAT_I444, VIDEO_FORMAT_NV12, VIDEO_FORMAT_I420, VIDEO_FORMAT_Y800,
};
} else if (vsi->format == VIDEO_FORMAT_YUY2) {
// 4:2:2
best_quality = {
VIDEO_FORMAT_YUY2, VIDEO_FORMAT_YVYU, VIDEO_FORMAT_UYVY, VIDEO_FORMAT_RGBA, VIDEO_FORMAT_BGRA,
VIDEO_FORMAT_BGRX, VIDEO_FORMAT_I444, VIDEO_FORMAT_NV12, VIDEO_FORMAT_I420, VIDEO_FORMAT_Y800,
};
} else if (vsi->format == VIDEO_FORMAT_UYVY) {
// 4:2:2
best_quality = {
VIDEO_FORMAT_UYVY, VIDEO_FORMAT_YVYU, VIDEO_FORMAT_YUY2, VIDEO_FORMAT_RGBA, VIDEO_FORMAT_BGRA,
VIDEO_FORMAT_BGRX, VIDEO_FORMAT_I444, VIDEO_FORMAT_NV12, VIDEO_FORMAT_I420, VIDEO_FORMAT_Y800,
};
} else if (vsi->format == VIDEO_FORMAT_I444) {
// 4:4:4
best_quality = {
VIDEO_FORMAT_I444, VIDEO_FORMAT_RGBA, VIDEO_FORMAT_BGRA, VIDEO_FORMAT_BGRX, VIDEO_FORMAT_UYVY,
VIDEO_FORMAT_YVYU, VIDEO_FORMAT_YUY2, VIDEO_FORMAT_NV12, VIDEO_FORMAT_I420, VIDEO_FORMAT_Y800,
};
} else if (vsi->format == VIDEO_FORMAT_RGBA) {
// Packed Non-Planar
best_quality = {
VIDEO_FORMAT_RGBA, VIDEO_FORMAT_BGRA, VIDEO_FORMAT_BGRX, VIDEO_FORMAT_UYVY, VIDEO_FORMAT_YVYU,
VIDEO_FORMAT_YUY2, VIDEO_FORMAT_I444, VIDEO_FORMAT_NV12, VIDEO_FORMAT_I420, VIDEO_FORMAT_Y800,
};
} else if (vsi->format == VIDEO_FORMAT_BGRA) {
// Packed Non-Planar
best_quality = {
VIDEO_FORMAT_BGRA, VIDEO_FORMAT_RGBA, VIDEO_FORMAT_BGRX, VIDEO_FORMAT_UYVY, VIDEO_FORMAT_YVYU,
VIDEO_FORMAT_YUY2, VIDEO_FORMAT_I444, VIDEO_FORMAT_NV12, VIDEO_FORMAT_I420, VIDEO_FORMAT_Y800,
};
} else if (vsi->format == VIDEO_FORMAT_BGRX) {
// Packed Non-Planar
best_quality = {
VIDEO_FORMAT_BGRX, VIDEO_FORMAT_RGBA, VIDEO_FORMAT_BGRA, VIDEO_FORMAT_UYVY, VIDEO_FORMAT_YVYU,
VIDEO_FORMAT_YUY2, VIDEO_FORMAT_I444, VIDEO_FORMAT_NV12, VIDEO_FORMAT_I420, VIDEO_FORMAT_Y800,
};
} else if (vsi->format == VIDEO_FORMAT_Y800) {
// Packed Non-Planar
best_quality = {
VIDEO_FORMAT_Y800, VIDEO_FORMAT_RGBA, VIDEO_FORMAT_BGRA, VIDEO_FORMAT_BGRX, VIDEO_FORMAT_UYVY,
VIDEO_FORMAT_YVYU, VIDEO_FORMAT_YUY2, VIDEO_FORMAT_I444, VIDEO_FORMAT_NV12, VIDEO_FORMAT_I420,
};
}
for (auto v : best_quality) {
if (supported.count(v) > 0) {
vsi->format = v;
return;
}
}
_resolution.first = vsi->width;
_resolution.second = vsi->height;
_format = ffmpeg::tools::obs_videoformat_to_avpixelformat(vsi->format);
}
bool obsffmpeg::encoder::get_sei_data(uint8_t** data, size_t* size)
@@ -876,6 +711,15 @@ static inline void copy_data(encoder_frame* frame, AVFrame* vframe)
bool obsffmpeg::encoder::video_encode(encoder_frame* frame, encoder_packet* packet, bool* received_packet)
{
// Ensure that the encoder was initialized.
try {
if (!initialize())
return false;
} catch (std::exception& ex) {
PLOG_ERROR("%s", ex.what());
return false;
}
// Convert frame.
std::shared_ptr<AVFrame> vframe = _frame_queue.pop(); // Retrieve an empty frame.
{
@@ -883,10 +727,10 @@ bool obsffmpeg::encoder::video_encode(encoder_frame* frame, encoder_packet* pack
ScopeProfiler profile("convert");
#endif
vframe->height = this->_context->height;
vframe->format = this->_context->pix_fmt;
vframe->color_range = this->_context->color_range;
vframe->colorspace = this->_context->colorspace;
vframe->height = _context->height;
vframe->format = _context->pix_fmt;
vframe->color_range = _context->color_range;
vframe->colorspace = _context->colorspace;
vframe->pts = frame->pts;
if ((_swscale.is_source_full_range() == _swscale.is_target_full_range())
@@ -895,7 +739,7 @@ bool obsffmpeg::encoder::video_encode(encoder_frame* frame, encoder_packet* pack
copy_data(frame, vframe.get());
} else {
int res = _swscale.convert(reinterpret_cast<uint8_t**>(frame->data),
reinterpret_cast<int*>(frame->linesize), 0, this->_context->height,
reinterpret_cast<int*>(frame->linesize), 0, _context->height,
vframe->data, vframe->linesize);
if (res <= 0) {
PLOG_ERROR("Failed to convert frame: %s (%ld).",
@@ -1002,7 +846,7 @@ bool obsffmpeg::encoder::video_encode_texture(uint32_t, int64_t, uint64_t, uint6
int obsffmpeg::encoder::receive_packet(bool* received_packet, struct encoder_packet* packet)
{
int res = avcodec_receive_packet(this->_context, &this->_current_packet);
int res = avcodec_receive_packet(_context, &_current_packet);
if (res == 0) {
if (!_have_first_frame) {
if (_codec->id == AV_CODEC_ID_H264) {
@@ -1038,11 +882,11 @@ int obsffmpeg::encoder::receive_packet(bool* received_packet, struct encoder_pac
}
packet->type = OBS_ENCODER_VIDEO;
packet->pts = this->_current_packet.pts;
packet->dts = this->_current_packet.dts;
packet->data = this->_current_packet.data;
packet->size = this->_current_packet.size;
packet->keyframe = !!(this->_current_packet.flags & AV_PKT_FLAG_KEY);
packet->pts = _current_packet.pts;
packet->dts = _current_packet.dts;
packet->data = _current_packet.data;
packet->size = _current_packet.size;
packet->keyframe = !!(_current_packet.flags & AV_PKT_FLAG_KEY);
packet->drop_priority = packet->keyframe ? 0 : 1;
*received_packet = true;
@@ -1057,7 +901,7 @@ int obsffmpeg::encoder::receive_packet(bool* received_packet, struct encoder_pac
int obsffmpeg::encoder::send_frame(std::shared_ptr<AVFrame> const frame)
{
int res = avcodec_send_frame(this->_context, frame.get());
int res = avcodec_send_frame(_context, frame.get());
switch (res) {
case 0:
_frame_queue_used.push(frame);
+6
View File
@@ -69,6 +69,10 @@ namespace obsffmpeg {
const AVCodec* _codec;
AVCodecContext* _context;
bool _initialized;
std::pair<size_t, size_t> _resolution;
AVPixelFormat _format;
ffmpeg::avframe_queue _frame_queue;
ffmpeg::avframe_queue _frame_queue_used;
ffmpeg::swscale _swscale;
@@ -86,6 +90,8 @@ namespace obsffmpeg {
encoder(obs_data_t* settings, obs_encoder_t* encoder);
virtual ~encoder();
bool initialize();
public: // OBS API
// Shared
void get_properties(obs_properties_t* props);