From df636775294f258d7b806d86f38c41b06bb11c34 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Sun, 7 Jul 2019 14:26:51 +0200 Subject: [PATCH] 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. --- data/locale/en-US.ini | 17 ++---- source/encoders/generic.cpp | 105 +++++++++++++++++------------------- 2 files changed, 52 insertions(+), 70 deletions(-) diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 5a5c755..65dc6f1 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -4,24 +4,15 @@ 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." +# Generic +Automatic="Automatic" # 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.ColorFormat="Override Color Format" +FFmpeg.ColorFormat.Description="Overriding the color format can unlock higher quality, but might cause additional stress.\nNot all encoders support all color formats, and you might end up causing errors or corrupted video due to this." 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" diff --git a/source/encoders/generic.cpp b/source/encoders/generic.cpp index b3980f4..2781993 100644 --- a/source/encoders/generic.cpp +++ b/source/encoders/generic.cpp @@ -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(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(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(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(obs_data_get_int(settings, P_RATECONTROL_KEYFRAME_TYPE)) - == keyframe_type::Frames) { - this->context->gop_size = - static_cast(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(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 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 uframe = frame_queue_used.pop_only(); - if (frame_queue.empty()) { - frame_queue.push(uframe); - } + frame_queue.push(uframe); } } return res;