From b55e4b283b3cc90249345e7ba189b8f248ec4a74 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Sat, 10 Sep 2022 14:45:20 +0200 Subject: [PATCH] encoder/ffmpeg: Add split framerate with integer fractions It seems to be possible to encode with a different framerate than what libOBS is configured for. While technically any framerate appears to be possible, it is currently limited to integer fractions only in order to make the implementation much easier. Integer fractions only require skipping N frames and multiplying the denominator by N, where N is the configured integer. For sanity reasons, the limit of N is currently 10. This allows power users to split their streaming and recording framerates with relative ease, and opt for things such as: - 30 FPS (1/4) streaming with 120 FPS (1/1) recording. - 30 FPS (1/10) streaming with 300 FPS (1/1) recording. - 30 FPS (1/10) streaming with 100 FPS (1/3) recording. - and so on. While some of these combinations are just stupid, they are now available to power users. --- data/locale/en-US.ini | 1 + source/encoders/encoder-ffmpeg.cpp | 46 ++++++++++++++++++++++++++++-- source/encoders/encoder-ffmpeg.hpp | 3 +- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index e281354b..beeb8097 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -137,6 +137,7 @@ Encoder.FFmpeg.KeyFrames.IntervalType="Interval Type" Encoder.FFmpeg.KeyFrames.IntervalType.Frames="Frames" Encoder.FFmpeg.KeyFrames.IntervalType.Seconds="Seconds" Encoder.FFmpeg.KeyFrames.Interval="Interval" +Encoder.FFmpeg.Framerate="Framerate Override" # Encoder/FFmpeg/AMF Encoder.FFmpeg.AMF.Deprecated="This encoder is deprecated and will be removed soon. Users are urged to migrate to the integrated 'AMD HW H.264 (AVC)' or 'AMD HW H.265 (HEVC)' encoder as soon as possible." diff --git a/source/encoders/encoder-ffmpeg.cpp b/source/encoders/encoder-ffmpeg.cpp index 3202a7b1..8ea383be 100644 --- a/source/encoders/encoder-ffmpeg.cpp +++ b/source/encoders/encoder-ffmpeg.cpp @@ -1,5 +1,5 @@ // FFMPEG Video Encoder Integration for OBS Studio -// Copyright (c) 2019 Michael Fabian Dirks +// Copyright (c) 2019-2022 Michael Fabian Dirks // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -74,6 +74,8 @@ extern "C" { #define ST_KEY_FFMPEG_CUSTOMSETTINGS "FFmpeg.CustomSettings" #define ST_I18N_FFMPEG_THREADS ST_I18N_FFMPEG ".Threads" #define ST_KEY_FFMPEG_THREADS "FFmpeg.Threads" +#define ST_I18N_FFMPEG_FRAMERATE ST_I18N_FFMPEG ".Framerate" +#define ST_KEY_FFMPEG_FRAMERATE "FFmpeg.Framerate" #define ST_I18N_FFMPEG_GPU ST_I18N_FFMPEG ".GPU" #define ST_KEY_FFMPEG_GPU "FFmpeg.GPU" @@ -145,6 +147,14 @@ ffmpeg_instance::ffmpeg_instance(obs_data_t* settings, obs_encoder_t* self, bool initialize_sw(settings); } + { // Set up framerate division. + _framerate_divisor = obs_data_get_int(settings, ST_KEY_FFMPEG_FRAMERATE); + + _context->ticks_per_frame = 1; + _context->time_base.num *= _framerate_divisor; + _context->framerate.den *= _framerate_divisor; + } + // Update settings update(settings); @@ -263,8 +273,10 @@ bool ffmpeg_instance::update(obs_data_t* settings) bool is_seconds = (kf_type == 0); if (is_seconds) { - _context->gop_size = static_cast(obs_data_get_double(settings, ST_KEY_KEYFRAMES_INTERVAL_SECONDS) - * (ovi.fps_num / ovi.fps_den)); + double framerate = + static_cast(ovi.fps_num) / (static_cast(ovi.fps_den) * _framerate_divisor); + _context->gop_size = + static_cast(obs_data_get_double(settings, ST_KEY_KEYFRAMES_INTERVAL_SECONDS) * framerate); } else { _context->gop_size = static_cast(obs_data_get_int(settings, ST_KEY_KEYFRAMES_INTERVAL_FRAMES)); } @@ -377,6 +389,10 @@ bool ffmpeg_instance::encode_audio(struct encoder_frame* frame, struct encoder_p bool ffmpeg_instance::encode_video(struct encoder_frame* frame, struct encoder_packet* packet, bool* received_packet) { + if ((_framerate_divisor > 1) && (frame->pts % _framerate_divisor != 0)) { + return true; + } + std::shared_ptr vframe = pop_free_frame(); // Retrieve an empty frame. // Convert frame. @@ -413,6 +429,11 @@ bool ffmpeg_instance::encode_video(struct encoder_frame* frame, struct encoder_p bool ffmpeg_instance::encode_video(uint32_t handle, int64_t pts, uint64_t lock_key, uint64_t* next_key, struct encoder_packet* packet, bool* received_packet) { + if ((_framerate_divisor > 1) && (pts % _framerate_divisor != 0)) { + *next_key = lock_key; + return true; + } + #ifdef D_PLATFORM_WINDOWS if (handle == GS_INVALID_HANDLE) { DLOG_ERROR("Received invalid handle."); @@ -1145,6 +1166,25 @@ obs_properties_t* ffmpeg_factory::get_properties2(instance_t* data) auto p = obs_properties_add_int_slider(grp, ST_KEY_FFMPEG_THREADS, D_TRANSLATE(ST_I18N_FFMPEG_THREADS), 0, static_cast(std::thread::hardware_concurrency()) * 2, 1); } + + { // Frame Skipping + obs_video_info ovi; + if (!obs_get_video_info(&ovi)) { + throw std::runtime_error("obs_get_video_info failed unexpectedly."); + } + + auto p = obs_properties_add_list(grp, ST_KEY_FFMPEG_FRAMERATE, D_TRANSLATE(ST_I18N_FFMPEG_FRAMERATE), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + // For now, an arbitrary limit of 1/10th the Framerate should be fine. + std::vector buf{size_t{256}, 0, std::allocator()}; + for (uint32_t divisor = 1; divisor <= 10; divisor++) { + double fps_num = static_cast(ovi.fps_num) / static_cast(divisor); + double fps = fps_num / static_cast(ovi.fps_den); + snprintf(buf.data(), buf.size(), "%8.2f (%" PRIu32 "/%" PRIu32 ")", fps, ovi.fps_num, + ovi.fps_den * divisor); + obs_property_list_add_int(p, buf.data(), divisor); + } + } }; return props; diff --git a/source/encoders/encoder-ffmpeg.hpp b/source/encoders/encoder-ffmpeg.hpp index bd0a5d78..96704cb1 100644 --- a/source/encoders/encoder-ffmpeg.hpp +++ b/source/encoders/encoder-ffmpeg.hpp @@ -1,5 +1,5 @@ // FFMPEG Video Encoder Integration for OBS Studio -// Copyright (c) 2019 Michael Fabian Dirks +// Copyright (c) 2019-2022 Michael Fabian Dirks // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -62,6 +62,7 @@ namespace streamfx::encoder::ffmpeg { std::size_t _lag_in_frames; std::size_t _sent_frames; + std::size_t _framerate_divisor; // Extra Data bool _have_first_frame;