encoders/generic: Allow overriding color format

Some encoders don't have all options available with the automatically detected color format, so allowing the user to override the detected color format opens up more options.
This commit is contained in:
Michael Fabian 'Xaymar' Dirks
2019-07-07 14:26:51 +02:00
parent d1ff4a7adc
commit df63677529
2 changed files with 52 additions and 70 deletions
+48 -57
View File
@@ -34,19 +34,13 @@ extern "C" {
#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"
// Generic
#define P_AUTOMATIC "Automatic"
// FFmpeg
#define P_FFMPEG "FFmpeg"
#define P_FFMPEG_CUSTOMSETTINGS "FFmpeg.CustomSettings"
#define P_FFMPEG_COLORFORMAT "FFmpeg.ColorFormat"
#define P_FFMPEG_STANDARDCOMPLIANCE "FFmpeg.StandardCompliance"
enum class keyframe_type { Seconds, Frames };
@@ -297,6 +291,7 @@ void encoder::generic_factory::get_defaults(obs_data_t* settings)
{ // Integrated Options
// FFmpeg
obs_data_set_default_string(settings, P_FFMPEG_CUSTOMSETTINGS, "");
obs_data_set_default_int(settings, P_FFMPEG_COLORFORMAT, AV_PIX_FMT_NONE);
obs_data_set_default_int(settings, P_FFMPEG_STANDARDCOMPLIANCE, FF_COMPLIANCE_STRICT);
}
}
@@ -318,6 +313,15 @@ void encoder::generic_factory::get_properties(obs_properties_t* props)
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_COLORFORMAT, TRANSLATE(P_FFMPEG_COLORFORMAT),
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
obs_property_set_long_description(p, TRANSLATE(DESC(P_FFMPEG_COLORFORMAT)));
obs_property_list_add_int(p, TRANSLATE(P_AUTOMATIC), AV_PIX_FMT_NONE);
for (auto ptr = this->avcodec_ptr->pix_fmts; *ptr != AV_PIX_FMT_NONE; ptr++) {
obs_property_list_add_int(p, ffmpeg::tools::get_pixel_format_name(*ptr), *ptr);
}
}
{
auto p = obs_properties_add_list(prs, P_FFMPEG_STANDARDCOMPLIANCE,
TRANSLATE(P_FFMPEG_STANDARDCOMPLIANCE), OBS_COMBO_TYPE_LIST,
@@ -343,17 +347,6 @@ AVCodec* encoder::generic_factory::get_avcodec()
return this->avcodec_ptr;
}
bool encoder::generic_factory::modified_ratecontrol_properties(void*, obs_properties_t* props, obs_property_t*,
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;
}
encoder::generic::generic(obs_data_t* settings, obs_encoder_t* encoder)
: self(encoder), lag_in_frames(0), frame_count(0)
{
@@ -379,10 +372,10 @@ encoder::generic::generic(obs_data_t* settings, obs_encoder_t* encoder)
static_cast<int>(obs_data_get_int(settings, P_FFMPEG_STANDARDCOMPLIANCE));
this->context->debug = 0;
/// Threading
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) {
if (this->codec->capabilities & AV_CODEC_CAP_FRAME_THREADS) {
this->context->thread_type = FF_THREAD_FRAME;
} else if (this->codec->capabilities & AV_CODEC_CAP_SLICE_THREADS) {
this->context->thread_type = FF_THREAD_SLICE;
} else {
this->context->thread_type = 0;
}
@@ -428,23 +421,24 @@ encoder::generic::generic(obs_data_t* settings, obs_encoder_t* encoder)
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));
}
if (obs_data_get_int(settings, P_FFMPEG_COLORFORMAT) != AV_PIX_FMT_NONE) {
// User specified override for color format.
this->context->pix_fmt =
static_cast<AVPixelFormat>(obs_data_get_int(settings, P_FFMPEG_COLORFORMAT));
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_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 =
static_cast<int>(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<int>(real_gop * voi->fps_num / voi->fps_den);
}
} else if (this->codec->type == AVMEDIA_TYPE_AUDIO) {
}
@@ -475,7 +469,7 @@ encoder::generic::generic(obs_data_t* settings, obs_encoder_t* encoder)
// 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(std::thread::hardware_concurrency());
this->frame_queue.precache(std::thread::hardware_concurrency() / 4);
} else if (this->codec->type == AVMEDIA_TYPE_AUDIO) {
}
@@ -557,23 +551,6 @@ bool encoder::generic::get_extra_data(uint8_t** extra_data, size_t* size)
bool encoder::generic::video_encode(encoder_frame* frame, encoder_packet* packet, bool* received_packet)
{
// Try and receive packet early.
{
ScopeProfiler profile_inner("recieve_early");
int res = receive_packet(received_packet, packet);
switch (res) {
case 0:
case AVERROR(EAGAIN):
case AVERROR(EOF):
break;
default:
PLOG_ERROR("Failed to receive packet: %s (%ld).", ffmpeg::tools::get_error_description(res),
res);
return false;
}
}
// Convert frame.
std::shared_ptr<AVFrame> vframe = frame_queue.pop(); // Retrieve an empty frame.
{
@@ -599,7 +576,11 @@ bool encoder::generic::video_encode(encoder_frame* frame, encoder_packet* packet
bool recv_packet = false;
bool should_lag = (lag_in_frames - frame_count) <= 0;
while (!sent_frame || (should_lag && !recv_packet)) {
auto loop_begin = std::chrono::high_resolution_clock::now();
auto loop_end = loop_begin + std::chrono::milliseconds(50);
while ((!sent_frame || (should_lag && !recv_packet))
&& !(std::chrono::high_resolution_clock::now() > loop_end)) {
if (!sent_frame) {
ScopeProfiler profile_inner("send");
@@ -608,7 +589,9 @@ bool encoder::generic::video_encode(encoder_frame* frame, encoder_packet* packet
int res = send_frame(vframe);
switch (res) {
case 0:
PLOG_DEBUG("Sent frame at PTS %lld.", frame->pts);
sent_frame = true;
frame_count++;
break;
case AVERROR(EAGAIN):
// This means we should call receive_packet again, but what do we do with that data?
@@ -617,6 +600,9 @@ bool encoder::generic::video_encode(encoder_frame* frame, encoder_packet* packet
PLOG_ERROR(
"Skipped frame due to EAGAIN when a packet was already returned.");
sent_frame = true;
frame_count++;
} else {
PLOG_DEBUG("Received EAGAIN, but had no input packet yet");
}
break;
case AVERROR(EOF):
@@ -630,18 +616,25 @@ bool encoder::generic::video_encode(encoder_frame* frame, encoder_packet* packet
}
}
if (!*received_packet) {
if (!recv_packet) {
ScopeProfiler profile_inner("recieve");
int res = receive_packet(received_packet, packet);
switch (res) {
case 0:
PLOG_DEBUG("Received packet with PTS %lld.", this->current_packet->pts);
recv_packet = true;
break;
case AVERROR(EOF):
PLOG_ERROR("Received end of file.");
recv_packet = true;
break;
case AVERROR(EAGAIN):
if (sent_frame) {
PLOG_DEBUG("Not enough input frames yet.");
recv_packet = true;
} else {
PLOG_DEBUG("Not enough input frames, waiting on new frame.");
}
break;
default:
@@ -680,9 +673,7 @@ int encoder::generic::receive_packet(bool* received_packet, struct encoder_packe
{
std::shared_ptr<AVFrame> uframe = frame_queue_used.pop_only();
if (frame_queue.empty()) {
frame_queue.push(uframe);
}
frame_queue.push(uframe);
}
}
return res;