feat(fps): support x-nv-video[0].clientRefreshRateX100 for requesting fractional NTSC framerates (#4019)
This commit is contained in:
parent
8dd75c4829
commit
6ed0c7a8f2
7 changed files with 80 additions and 1 deletions
|
|
@ -225,6 +225,11 @@ namespace nvenc {
|
|||
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;
|
||||
}
|
||||
|
||||
NV_ENC_PRESET_CONFIG preset_config = {min_struct_version(NV_ENC_PRESET_CONFIG_VER), {min_struct_version(NV_ENC_CONFIG_VER, 7, 8)}};
|
||||
if (nvenc_failed(nvenc->nvEncGetEncodePresetConfigEx(encoder, init_params.encodeGUID, init_params.presetGUID, init_params.tuningInfo, &preset_config))) {
|
||||
|
|
|
|||
|
|
@ -173,6 +173,7 @@ namespace platf::dxgi {
|
|||
int height_before_rotation;
|
||||
|
||||
int client_frame_rate;
|
||||
DXGI_RATIONAL client_frame_rate_strict;
|
||||
|
||||
DXGI_FORMAT capture_format;
|
||||
D3D_FEATURE_LEVEL feature_level;
|
||||
|
|
|
|||
|
|
@ -121,7 +121,13 @@ namespace platf::dxgi {
|
|||
display->display_refresh_rate = dup_desc.ModeDesc.RefreshRate;
|
||||
double display_refresh_rate_decimal = (double) display->display_refresh_rate.Numerator / display->display_refresh_rate.Denominator;
|
||||
BOOST_LOG(info) << "Display refresh rate [" << display_refresh_rate_decimal << "Hz]";
|
||||
BOOST_LOG(info) << "Requested frame rate [" << display->client_frame_rate << "fps]";
|
||||
if (display->client_frame_rate_strict.Numerator > 0) {
|
||||
int num = display->client_frame_rate_strict.Numerator;
|
||||
int den = display->client_frame_rate_strict.Denominator;
|
||||
BOOST_LOG(info) << "Requested frame rate [" << num << "/" << den << " exactly " << av_q2d(AVRational {num, den}) << " fps]";
|
||||
} else {
|
||||
BOOST_LOG(info) << "Requested frame rate [" << display->client_frame_rate << "fps]";
|
||||
}
|
||||
display->display_refresh_rate_rounded = lround(display_refresh_rate_decimal);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -196,6 +202,10 @@ namespace platf::dxgi {
|
|||
|
||||
capture_e display_base_t::capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) {
|
||||
auto adjust_client_frame_rate = [&]() -> DXGI_RATIONAL {
|
||||
// Use exactly the requested rate if the client sent an X100 value
|
||||
if (client_frame_rate_strict.Numerator > 0) {
|
||||
return client_frame_rate_strict;
|
||||
}
|
||||
// Adjust capture frame interval when display refresh rate is not integral but very close to requested fps.
|
||||
if (display_refresh_rate.Denominator > 1) {
|
||||
DXGI_RATIONAL candidate = display_refresh_rate;
|
||||
|
|
@ -705,6 +715,12 @@ 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);
|
||||
client_frame_rate_strict = DXGI_RATIONAL {static_cast<UINT>(fps.num), static_cast<UINT>(fps.den)};
|
||||
}
|
||||
|
||||
dxgi::output6_t output6 {};
|
||||
status = output->QueryInterface(IID_IDXGIOutput6, (void **) &output6);
|
||||
if (SUCCEEDED(status)) {
|
||||
|
|
|
|||
|
|
@ -952,6 +952,7 @@ namespace rtsp_stream {
|
|||
args.try_emplace("x-ss-general.encryptionEnabled"sv, "0"sv);
|
||||
args.try_emplace("x-ss-video[0].chromaSamplingType"sv, "0"sv);
|
||||
args.try_emplace("x-ss-video[0].intraRefresh"sv, "0"sv);
|
||||
args.try_emplace("x-nv-video[0].clientRefreshRateX100"sv, "0"sv);
|
||||
|
||||
stream::config_t config;
|
||||
|
||||
|
|
@ -981,6 +982,7 @@ namespace rtsp_stream {
|
|||
config.monitor.height = util::from_view(args.at("x-nv-video[0].clientViewportHt"sv));
|
||||
config.monitor.width = util::from_view(args.at("x-nv-video[0].clientViewportWd"sv));
|
||||
config.monitor.framerate = util::from_view(args.at("x-nv-video[0].maxFPS"sv));
|
||||
config.monitor.framerateX100 = util::from_view(args.at("x-nv-video[0].clientRefreshRateX100"sv));
|
||||
config.monitor.bitrate = util::from_view(args.at("x-nv-vqos[0].bw.maximumBitrateKbps"sv));
|
||||
config.monitor.slicesPerFrame = util::from_view(args.at("x-nv-video[0].videoEncoderSlicesPerFrame"sv));
|
||||
config.monitor.numRefFrames = util::from_view(args.at("x-nv-video[0].maxNumReferenceFrames"sv));
|
||||
|
|
|
|||
|
|
@ -1532,6 +1532,11 @@ namespace video {
|
|||
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};
|
||||
}
|
||||
|
||||
switch (config.videoFormat) {
|
||||
case 0:
|
||||
|
|
|
|||
22
src/video.h
22
src/video.h
|
|
@ -24,6 +24,7 @@ namespace video {
|
|||
int width; // Video width in pixels
|
||||
int height; // Video height in pixels
|
||||
int framerate; // Requested framerate, used in individual frame bitrate budget calculation
|
||||
int framerateX100; // Optional field for streaming at NTSC or similar rates e.g. 59.94 = 5994
|
||||
int bitrate; // Video bitrate in kilobits (1000 bits) for requested framerate
|
||||
int slicesPerFrame; // Number of slices per frame
|
||||
int numRefFrames; // Max number of reference frames
|
||||
|
|
@ -352,4 +353,25 @@ namespace video {
|
|||
* @warning This is only safe to call when there is no client actively streaming.
|
||||
*/
|
||||
int probe_encoders();
|
||||
|
||||
// Several NTSC standard refresh rates are hardcoded here, because their
|
||||
// true rate requires a denominator of 1001. ffmpeg's av_d2q() would assume it could
|
||||
// reduce 29.97 to 2997/100 but this would be slightly wrong. We also include
|
||||
// support for 23.976 film in case someone wants to stream a film at the perfect
|
||||
// framerate.
|
||||
inline AVRational framerateX100_to_rational(const int framerateX100) {
|
||||
if (framerateX100 % 2997 == 0) {
|
||||
// Multiples of NTSC 29.97 e.g. 59.94, 119.88
|
||||
return AVRational {(framerateX100 / 2997) * 30000, 1001};
|
||||
}
|
||||
switch (framerateX100) {
|
||||
case 2397: // the other weird NTSC framerate, assume these want 23.976 film
|
||||
case 2398:
|
||||
return AVRational {24000, 1001};
|
||||
default:
|
||||
// any other fractional rate can be reduced by ffmpeg. Max is set to 1 << 26 based on docs:
|
||||
// "rational numbers with |num| <= 1<<26 && |den| <= 1<<26 can be recovered exactly from their double representation"
|
||||
return av_d2q((double) framerateX100 / 100.0f, 1 << 26);
|
||||
}
|
||||
}
|
||||
} // namespace video
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue