mirror of
https://github.com/LizardByte/Sunshine.git
synced 2026-08-07 10:20:46 +00:00
feat(linux): pace capture at exact fractional NTSC framerates (#5282)
This commit is contained in:
committed by
GitHub
parent
9cb358952c
commit
bba6c6cac0
@@ -226,13 +226,9 @@ namespace nvenc {
|
||||
init_params.darWidth = encoder_params.width;
|
||||
init_params.encodeHeight = encoder_params.height;
|
||||
init_params.darHeight = encoder_params.height;
|
||||
init_params.frameRateNum = client_config.framerate;
|
||||
init_params.frameRateDen = 1;
|
||||
if (client_config.framerateX100 > 0) {
|
||||
AVRational fps = video::framerateX100_to_rational(client_config.framerateX100);
|
||||
init_params.frameRateNum = fps.num;
|
||||
init_params.frameRateDen = fps.den;
|
||||
}
|
||||
const AVRational fps = video::framerate_to_rational(client_config);
|
||||
init_params.frameRateNum = fps.num;
|
||||
init_params.frameRateDen = fps.den;
|
||||
|
||||
if (client_config.videoFormat > 0 && get_encoder_cap(NV_ENC_CAPS_NUM_ENCODER_ENGINES) > 1) {
|
||||
// SFE supports HEVC/AV1 if you have more than 1 nvenc block
|
||||
|
||||
@@ -992,7 +992,7 @@ namespace cuda {
|
||||
}
|
||||
}
|
||||
|
||||
delay = std::chrono::nanoseconds {1s} / config.framerate;
|
||||
delay = ::video::capture_frame_interval(config);
|
||||
|
||||
capture_params = NVFBC_CREATE_CAPTURE_SESSION_PARAMS {NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER};
|
||||
|
||||
|
||||
@@ -873,7 +873,7 @@ namespace platf {
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(const std::string &display_name, const ::video::config_t &config) {
|
||||
delay = std::chrono::nanoseconds {1s} / config.framerate;
|
||||
delay = ::video::capture_frame_interval(config);
|
||||
|
||||
int monitor_index = util::from_view(display_name);
|
||||
int monitor = 0;
|
||||
|
||||
@@ -806,15 +806,12 @@ namespace pipewire {
|
||||
int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
|
||||
// calculate frame interval we should capture at
|
||||
framerate = config.framerate;
|
||||
if (config.framerateX100 > 0) {
|
||||
AVRational fps_strict = ::video::framerateX100_to_rational(config.framerateX100);
|
||||
delay = std::chrono::nanoseconds(
|
||||
(static_cast<int64_t>(fps_strict.den) * 1'000'000'000LL) / fps_strict.num
|
||||
);
|
||||
BOOST_LOG(info) << "[pipewire] Requested frame rate [" << fps_strict.num << "/" << fps_strict.den << ", approx. " << av_q2d(fps_strict) << " fps]";
|
||||
delay = ::video::capture_frame_interval(config);
|
||||
const AVRational fps = ::video::framerate_to_rational(config);
|
||||
if (fps.den != 1) {
|
||||
BOOST_LOG(info) << "[pipewire] Requested frame rate [" << fps.num << "/" << fps.den << ", approx. " << av_q2d(fps) << " fps]";
|
||||
} else {
|
||||
delay = std::chrono::nanoseconds {1s} / framerate;
|
||||
BOOST_LOG(info) << "[pipewire] Requested frame rate [" << framerate << "fps]";
|
||||
BOOST_LOG(info) << "[pipewire] Requested frame rate [" << fps.num << "fps]";
|
||||
}
|
||||
mem_type = hwdevice_type;
|
||||
|
||||
|
||||
@@ -47,15 +47,12 @@ namespace wl {
|
||||
*/
|
||||
int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
|
||||
// calculate frame interval we should capture at
|
||||
if (config.framerateX100 > 0) {
|
||||
AVRational fps_strict = ::video::framerateX100_to_rational(config.framerateX100);
|
||||
delay = std::chrono::nanoseconds(
|
||||
(static_cast<int64_t>(fps_strict.den) * 1'000'000'000LL) / fps_strict.num
|
||||
);
|
||||
BOOST_LOG(info) << "[wlgrab] Requested frame rate [" << fps_strict.num << "/" << fps_strict.den << ", approx. " << av_q2d(fps_strict) << " fps]";
|
||||
delay = ::video::capture_frame_interval(config);
|
||||
const AVRational fps = ::video::framerate_to_rational(config);
|
||||
if (fps.den != 1) {
|
||||
BOOST_LOG(info) << "[wlgrab] Requested frame rate [" << fps.num << "/" << fps.den << ", approx. " << av_q2d(fps) << " fps]";
|
||||
} else {
|
||||
delay = std::chrono::nanoseconds {1s} / config.framerate;
|
||||
BOOST_LOG(info) << "[wlgrab] Requested frame rate [" << config.framerate << "fps]";
|
||||
BOOST_LOG(info) << "[wlgrab] Requested frame rate [" << fps.num << "fps]";
|
||||
}
|
||||
|
||||
mem_type = hwdevice_type;
|
||||
|
||||
@@ -499,7 +499,7 @@ namespace platf {
|
||||
return -1;
|
||||
}
|
||||
|
||||
delay = std::chrono::nanoseconds {1s} / config.framerate;
|
||||
delay = ::video::capture_frame_interval(config);
|
||||
|
||||
xwindow = DefaultRootWindow(xdisplay.get());
|
||||
|
||||
|
||||
@@ -728,7 +728,7 @@ namespace platf::dxgi {
|
||||
client_frame_rate = config.framerate;
|
||||
client_frame_rate_strict = {0, 0};
|
||||
if (config.framerateX100 > 0) {
|
||||
AVRational fps = ::video::framerateX100_to_rational(config.framerateX100);
|
||||
const AVRational fps = ::video::framerate_to_rational(config);
|
||||
client_frame_rate_strict = DXGI_RATIONAL {static_cast<UINT>(fps.num), static_cast<UINT>(fps.den)};
|
||||
}
|
||||
|
||||
|
||||
+3
-7
@@ -1954,13 +1954,9 @@ namespace video {
|
||||
ctx.reset(avcodec_alloc_context3(codec));
|
||||
ctx->width = config.width;
|
||||
ctx->height = config.height;
|
||||
ctx->time_base = AVRational {1, config.framerate};
|
||||
ctx->framerate = AVRational {config.framerate, 1};
|
||||
if (config.framerateX100 > 0) {
|
||||
AVRational fps = video::framerateX100_to_rational(config.framerateX100);
|
||||
ctx->framerate = fps;
|
||||
ctx->time_base = AVRational {fps.den, fps.num};
|
||||
}
|
||||
const AVRational fps = video::framerate_to_rational(config);
|
||||
ctx->framerate = fps;
|
||||
ctx->time_base = AVRational {fps.den, fps.num};
|
||||
|
||||
switch (config.videoFormat) {
|
||||
case 0:
|
||||
|
||||
+30
@@ -4,6 +4,9 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <chrono>
|
||||
|
||||
// local includes
|
||||
#include "input.h"
|
||||
#include "platform/common.h"
|
||||
@@ -640,4 +643,31 @@ namespace video {
|
||||
return av_d2q((double) framerateX100 / 100.0f, 1 << 26);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Requested framerate as an exact rational.
|
||||
* Uses the exact fractional rate when the client provided an X100 value,
|
||||
* otherwise the integer framerate over 1.
|
||||
*
|
||||
* @param config Stream configuration carrying the requested framerate.
|
||||
* @return Requested frame rate as a rational.
|
||||
*/
|
||||
inline AVRational framerate_to_rational(const config_t &config) {
|
||||
if (config.framerateX100 > 0) {
|
||||
return framerateX100_to_rational(config.framerateX100);
|
||||
}
|
||||
return AVRational {config.framerate, 1};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Capture frame interval for the requested framerate.
|
||||
* Uses the exact fractional rate when the client provided an X100 value.
|
||||
*
|
||||
* @param config Stream configuration carrying the requested framerate.
|
||||
* @return Duration of a single frame interval in nanoseconds.
|
||||
*/
|
||||
inline std::chrono::nanoseconds capture_frame_interval(const config_t &config) {
|
||||
const AVRational fps = framerate_to_rational(config);
|
||||
return std::chrono::nanoseconds {(static_cast<int64_t>(fps.den) * 1'000'000'000LL) / fps.num};
|
||||
}
|
||||
} // namespace video
|
||||
|
||||
@@ -76,3 +76,48 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
std::make_tuple(9498, AVRational {4749, 50}) // from my LG 27GN950
|
||||
)
|
||||
);
|
||||
|
||||
struct FramerateToRationalTest: testing::TestWithParam<std::tuple<int, int, AVRational>> {};
|
||||
|
||||
TEST_P(FramerateToRationalTest, Run) {
|
||||
const auto &[framerate, framerateX100, expected] = GetParam();
|
||||
video::config_t config {};
|
||||
config.framerate = framerate;
|
||||
config.framerateX100 = framerateX100;
|
||||
auto res = video::framerate_to_rational(config);
|
||||
ASSERT_EQ(0, av_cmp_q(res, expected)) << "expected "
|
||||
<< expected.num << "/" << expected.den
|
||||
<< ", got "
|
||||
<< res.num << "/" << res.den;
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
FramerateToRationalTests,
|
||||
FramerateToRationalTest,
|
||||
testing::Values(
|
||||
std::make_tuple(60, 0, AVRational {60, 1}), // no X100 value, fall back to integer framerate
|
||||
std::make_tuple(60, 5994, AVRational {60000, 1001}),
|
||||
std::make_tuple(120, 11988, AVRational {120000, 1001}),
|
||||
std::make_tuple(24, 2398, AVRational {24000, 1001})
|
||||
)
|
||||
);
|
||||
|
||||
struct CaptureFrameIntervalTest: testing::TestWithParam<std::tuple<int, int, std::chrono::nanoseconds>> {};
|
||||
|
||||
TEST_P(CaptureFrameIntervalTest, Run) {
|
||||
const auto &[framerate, framerateX100, expected] = GetParam();
|
||||
video::config_t config {};
|
||||
config.framerate = framerate;
|
||||
config.framerateX100 = framerateX100;
|
||||
ASSERT_EQ(expected, video::capture_frame_interval(config));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
CaptureFrameIntervalTests,
|
||||
CaptureFrameIntervalTest,
|
||||
testing::Values(
|
||||
std::make_tuple(60, 0, std::chrono::nanoseconds {16666666}),
|
||||
std::make_tuple(60, 5994, std::chrono::nanoseconds {16683333}), // 1e9 * 1001 / 60000
|
||||
std::make_tuple(120, 11988, std::chrono::nanoseconds {8341666}) // 1e9 * 1001 / 120000
|
||||
)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user