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.
This commit is contained in:
Michael Fabian 'Xaymar' Dirks 2022-09-10 14:45:20 +02:00
parent bbac990644
commit b55e4b283b
3 changed files with 46 additions and 4 deletions

View file

@ -137,6 +137,7 @@ Encoder.FFmpeg.KeyFrames.IntervalType="Interval Type"
Encoder.FFmpeg.KeyFrames.IntervalType.Frames="Frames" Encoder.FFmpeg.KeyFrames.IntervalType.Frames="Frames"
Encoder.FFmpeg.KeyFrames.IntervalType.Seconds="Seconds" Encoder.FFmpeg.KeyFrames.IntervalType.Seconds="Seconds"
Encoder.FFmpeg.KeyFrames.Interval="Interval" Encoder.FFmpeg.KeyFrames.Interval="Interval"
Encoder.FFmpeg.Framerate="Framerate Override"
# Encoder/FFmpeg/AMF # 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." 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."

View file

@ -1,5 +1,5 @@
// FFMPEG Video Encoder Integration for OBS Studio // FFMPEG Video Encoder Integration for OBS Studio
// Copyright (c) 2019 Michael Fabian Dirks <info@xaymar.com> // Copyright (c) 2019-2022 Michael Fabian Dirks <info@xaymar.com>
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // 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_KEY_FFMPEG_CUSTOMSETTINGS "FFmpeg.CustomSettings"
#define ST_I18N_FFMPEG_THREADS ST_I18N_FFMPEG ".Threads" #define ST_I18N_FFMPEG_THREADS ST_I18N_FFMPEG ".Threads"
#define ST_KEY_FFMPEG_THREADS "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_I18N_FFMPEG_GPU ST_I18N_FFMPEG ".GPU"
#define ST_KEY_FFMPEG_GPU "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); 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
update(settings); update(settings);
@ -263,8 +273,10 @@ bool ffmpeg_instance::update(obs_data_t* settings)
bool is_seconds = (kf_type == 0); bool is_seconds = (kf_type == 0);
if (is_seconds) { if (is_seconds) {
_context->gop_size = static_cast<int>(obs_data_get_double(settings, ST_KEY_KEYFRAMES_INTERVAL_SECONDS) double framerate =
* (ovi.fps_num / ovi.fps_den)); static_cast<double>(ovi.fps_num) / (static_cast<double>(ovi.fps_den) * _framerate_divisor);
_context->gop_size =
static_cast<int>(obs_data_get_double(settings, ST_KEY_KEYFRAMES_INTERVAL_SECONDS) * framerate);
} else { } else {
_context->gop_size = static_cast<int>(obs_data_get_int(settings, ST_KEY_KEYFRAMES_INTERVAL_FRAMES)); _context->gop_size = static_cast<int>(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) 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<AVFrame> vframe = pop_free_frame(); // Retrieve an empty frame. std::shared_ptr<AVFrame> vframe = pop_free_frame(); // Retrieve an empty frame.
// Convert 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, 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) 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 #ifdef D_PLATFORM_WINDOWS
if (handle == GS_INVALID_HANDLE) { if (handle == GS_INVALID_HANDLE) {
DLOG_ERROR("Received 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, auto p = obs_properties_add_int_slider(grp, ST_KEY_FFMPEG_THREADS, D_TRANSLATE(ST_I18N_FFMPEG_THREADS), 0,
static_cast<int64_t>(std::thread::hardware_concurrency()) * 2, 1); static_cast<int64_t>(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<char> buf{size_t{256}, 0, std::allocator<char>()};
for (uint32_t divisor = 1; divisor <= 10; divisor++) {
double fps_num = static_cast<double>(ovi.fps_num) / static_cast<double>(divisor);
double fps = fps_num / static_cast<double>(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; return props;

View file

@ -1,5 +1,5 @@
// FFMPEG Video Encoder Integration for OBS Studio // FFMPEG Video Encoder Integration for OBS Studio
// Copyright (c) 2019 Michael Fabian Dirks <info@xaymar.com> // Copyright (c) 2019-2022 Michael Fabian Dirks <info@xaymar.com>
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // 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 _lag_in_frames;
std::size_t _sent_frames; std::size_t _sent_frames;
std::size_t _framerate_divisor;
// Extra Data // Extra Data
bool _have_first_frame; bool _have_first_frame;