Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions src/nvenc/nvenc_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,13 +222,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
Expand Down
2 changes: 1 addition & 1 deletion src/platform/linux/cuda.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,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};

Expand Down
2 changes: 1 addition & 1 deletion src/platform/linux/kmsgrab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ namespace platf {
}

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;
Expand Down
13 changes: 5 additions & 8 deletions src/platform/linux/pipewire.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -675,15 +675,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;

Expand Down
13 changes: 5 additions & 8 deletions src/platform/linux/wlgrab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,12 @@ namespace wl {
public:
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;
Expand Down
2 changes: 1 addition & 1 deletion src/platform/linux/x11grab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ namespace platf {
return -1;
}

delay = std::chrono::nanoseconds {1s} / config.framerate;
delay = ::video::capture_frame_interval(config);

xwindow = DefaultRootWindow(xdisplay.get());

Expand Down
2 changes: 1 addition & 1 deletion src/platform/windows/display_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -720,7 +720,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)};
}

Expand Down
10 changes: 3 additions & 7 deletions src/video.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1678,13 +1678,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:
Expand Down
24 changes: 24 additions & 0 deletions src/video.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
*/
#pragma once

// standard includes
#include <chrono>

// local includes
#include "input.h"
#include "platform/common.h"
Expand Down Expand Up @@ -386,4 +389,25 @@ 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.
*/
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.
*/
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
45 changes: 45 additions & 0 deletions tests/unit/test_video.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
);
Loading