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.darWidth = encoder_params.width;
|
||||||
init_params.encodeHeight = encoder_params.height;
|
init_params.encodeHeight = encoder_params.height;
|
||||||
init_params.darHeight = encoder_params.height;
|
init_params.darHeight = encoder_params.height;
|
||||||
init_params.frameRateNum = client_config.framerate;
|
const AVRational fps = video::framerate_to_rational(client_config);
|
||||||
init_params.frameRateDen = 1;
|
init_params.frameRateNum = fps.num;
|
||||||
if (client_config.framerateX100 > 0) {
|
init_params.frameRateDen = fps.den;
|
||||||
AVRational fps = video::framerateX100_to_rational(client_config.framerateX100);
|
|
||||||
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) {
|
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
|
// 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};
|
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.
|
* @return 0 on success; nonzero or negative platform status on failure.
|
||||||
*/
|
*/
|
||||||
int init(const std::string &display_name, const ::video::config_t &config) {
|
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_index = util::from_view(display_name);
|
||||||
int monitor = 0;
|
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) {
|
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
|
// calculate frame interval we should capture at
|
||||||
framerate = config.framerate;
|
framerate = config.framerate;
|
||||||
if (config.framerateX100 > 0) {
|
delay = ::video::capture_frame_interval(config);
|
||||||
AVRational fps_strict = ::video::framerateX100_to_rational(config.framerateX100);
|
const AVRational fps = ::video::framerate_to_rational(config);
|
||||||
delay = std::chrono::nanoseconds(
|
if (fps.den != 1) {
|
||||||
(static_cast<int64_t>(fps_strict.den) * 1'000'000'000LL) / fps_strict.num
|
BOOST_LOG(info) << "[pipewire] Requested frame rate [" << fps.num << "/" << fps.den << ", approx. " << av_q2d(fps) << " fps]";
|
||||||
);
|
|
||||||
BOOST_LOG(info) << "[pipewire] Requested frame rate [" << fps_strict.num << "/" << fps_strict.den << ", approx. " << av_q2d(fps_strict) << " fps]";
|
|
||||||
} else {
|
} else {
|
||||||
delay = std::chrono::nanoseconds {1s} / framerate;
|
BOOST_LOG(info) << "[pipewire] Requested frame rate [" << fps.num << "fps]";
|
||||||
BOOST_LOG(info) << "[pipewire] Requested frame rate [" << framerate << "fps]";
|
|
||||||
}
|
}
|
||||||
mem_type = hwdevice_type;
|
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) {
|
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
|
// calculate frame interval we should capture at
|
||||||
if (config.framerateX100 > 0) {
|
delay = ::video::capture_frame_interval(config);
|
||||||
AVRational fps_strict = ::video::framerateX100_to_rational(config.framerateX100);
|
const AVRational fps = ::video::framerate_to_rational(config);
|
||||||
delay = std::chrono::nanoseconds(
|
if (fps.den != 1) {
|
||||||
(static_cast<int64_t>(fps_strict.den) * 1'000'000'000LL) / fps_strict.num
|
BOOST_LOG(info) << "[wlgrab] Requested frame rate [" << fps.num << "/" << fps.den << ", approx. " << av_q2d(fps) << " fps]";
|
||||||
);
|
|
||||||
BOOST_LOG(info) << "[wlgrab] Requested frame rate [" << fps_strict.num << "/" << fps_strict.den << ", approx. " << av_q2d(fps_strict) << " fps]";
|
|
||||||
} else {
|
} else {
|
||||||
delay = std::chrono::nanoseconds {1s} / config.framerate;
|
BOOST_LOG(info) << "[wlgrab] Requested frame rate [" << fps.num << "fps]";
|
||||||
BOOST_LOG(info) << "[wlgrab] Requested frame rate [" << config.framerate << "fps]";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mem_type = hwdevice_type;
|
mem_type = hwdevice_type;
|
||||||
|
|||||||
@@ -499,7 +499,7 @@ namespace platf {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
delay = std::chrono::nanoseconds {1s} / config.framerate;
|
delay = ::video::capture_frame_interval(config);
|
||||||
|
|
||||||
xwindow = DefaultRootWindow(xdisplay.get());
|
xwindow = DefaultRootWindow(xdisplay.get());
|
||||||
|
|
||||||
|
|||||||
@@ -728,7 +728,7 @@ namespace platf::dxgi {
|
|||||||
client_frame_rate = config.framerate;
|
client_frame_rate = config.framerate;
|
||||||
client_frame_rate_strict = {0, 0};
|
client_frame_rate_strict = {0, 0};
|
||||||
if (config.framerateX100 > 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)};
|
client_frame_rate_strict = DXGI_RATIONAL {static_cast<UINT>(fps.num), static_cast<UINT>(fps.den)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1954,13 +1954,9 @@ namespace video {
|
|||||||
ctx.reset(avcodec_alloc_context3(codec));
|
ctx.reset(avcodec_alloc_context3(codec));
|
||||||
ctx->width = config.width;
|
ctx->width = config.width;
|
||||||
ctx->height = config.height;
|
ctx->height = config.height;
|
||||||
ctx->time_base = AVRational {1, config.framerate};
|
const AVRational fps = video::framerate_to_rational(config);
|
||||||
ctx->framerate = AVRational {config.framerate, 1};
|
ctx->framerate = fps;
|
||||||
if (config.framerateX100 > 0) {
|
ctx->time_base = AVRational {fps.den, fps.num};
|
||||||
AVRational fps = video::framerateX100_to_rational(config.framerateX100);
|
|
||||||
ctx->framerate = fps;
|
|
||||||
ctx->time_base = AVRational {fps.den, fps.num};
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (config.videoFormat) {
|
switch (config.videoFormat) {
|
||||||
case 0:
|
case 0:
|
||||||
|
|||||||
30
src/video.h
30
src/video.h
@@ -4,6 +4,9 @@
|
|||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
// standard includes
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
// local includes
|
// local includes
|
||||||
#include "input.h"
|
#include "input.h"
|
||||||
#include "platform/common.h"
|
#include "platform/common.h"
|
||||||
@@ -640,4 +643,31 @@ namespace video {
|
|||||||
return av_d2q((double) framerateX100 / 100.0f, 1 << 26);
|
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
|
} // namespace video
|
||||||
|
|||||||
@@ -76,3 +76,48 @@ INSTANTIATE_TEST_SUITE_P(
|
|||||||
std::make_tuple(9498, AVRational {4749, 50}) // from my LG 27GN950
|
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