constant framerate

This commit is contained in:
pigeatgarlic 2023-12-25 22:41:56 +07:00
parent 145a0fe70f
commit 925d69d848
7 changed files with 45 additions and 121 deletions

View File

@ -25,7 +25,7 @@ struct _VideoPipeline {
safe::mail_t mail;
};
extern VideoPipeline *__cdecl StartQueue(int video_codec, char *display_name) {
extern VideoPipeline *__cdecl StartQueue(int video_codec) {
static bool init = false;
if (!init) {
// If any of the following fail, we log an error and continue event
@ -43,7 +43,7 @@ extern VideoPipeline *__cdecl StartQueue(int video_codec, char *display_name) {
static VideoPipeline pipeline = {};
pipeline.mail = std::make_shared<safe::mail_raw_t>();
pipeline.monitor = {1920, 1080, 120, 6000, 1, 0, 1, 0, 0};
pipeline.monitor = {1920, 1080, 60, 6000, 1, 0, 1, 0, 0};
pipeline.start = std::chrono::steady_clock::now();
switch (video_codec) {
@ -71,7 +71,6 @@ extern VideoPipeline *__cdecl StartQueue(int video_codec, char *display_name) {
[&]() { video::capture(pipeline.mail, pipeline.monitor, NULL); }};
thread.detach();
RaiseEventS(&pipeline, CHANGE_DISPLAY, display_name);
return &pipeline;
}
@ -120,6 +119,9 @@ void __cdecl RaiseEvent(VideoPipeline *pipeline, EventType event, int value) {
case CHANGE_BITRATE: // IDR FRAME
pipeline->mail->event<int>(mail::bitrate)->raise(value);
break;
case CHANGE_FRAMERATE: // IDR FRAME
pipeline->mail->event<int>(mail::framerate)->raise(value);
break;
default:
break;
}

View File

@ -8,6 +8,7 @@ typedef struct _VideoPipeline VideoPipeline;
typedef enum _EventType {
POINTER_VISIBLE,
CHANGE_BITRATE,
CHANGE_FRAMERATE,
CHANGE_DISPLAY,
IDR_FRAME,
@ -20,8 +21,7 @@ typedef enum _Codec {
AV1,
} Codec;
__declspec(dllexport) VideoPipeline* __cdecl StartQueue(int video_codec,
char* display_name);
__declspec(dllexport) VideoPipeline* __cdecl StartQueue(int video_codec);
__declspec(dllexport) int __cdecl PopFromQueue(VideoPipeline* pipeline,
void* data, int* duration);
@ -38,7 +38,7 @@ __declspec(dllexport) void __cdecl WaitEvent(VideoPipeline* pipeline,
__declspec(dllexport) int __cdecl PeekEvent(VideoPipeline* pipeline,
EventType event);
typedef VideoPipeline* (*STARTQUEUE)(int video_codec, char* display_name);
typedef VideoPipeline* (*STARTQUEUE)(int video_codec);
typedef int (*POPFROMQUEUE)(VideoPipeline* pipeline, void* data, int* duration);

View File

@ -47,12 +47,13 @@ int main(int argc, char *argv[]) {
// second encode session
for (int i = 0; i < 30; i++) {
VideoPipeline *pipeline = callstart(1, "\\\\.\\DISPLAY1");
VideoPipeline *pipeline = callstart(1);
auto video = std::thread{[&]() {
// Video traffic is sent on this thread
int duration = 0;
void *data = malloc(100 * 1000 * 1000);
auto start = std::chrono::system_clock::now();
int count = 0;
while (true) {
int size = callpop(pipeline, data, &duration);
@ -61,10 +62,14 @@ int main(int argc, char *argv[]) {
} else if (count % 100 == 0) {
callraise(pipeline, CHANGE_BITRATE, 2000);
} else if (count % 100 == 50) {
callraiseS(pipeline, CHANGE_DISPLAY, "\\\\.\\DISPLAY1");
// callraiseS(pipeline, CHANGE_DISPLAY, "\\\\.\\DISPLAY1");
callraise(pipeline, CHANGE_FRAMERATE, 50);
}
printf("received packet with size %d\n", size);
auto end = std::chrono::system_clock::now();
auto time = std::chrono::duration<double, std::milli>(end - start);
start = end;
printf("received packet with size %d, timestamp %f\n", size,time.count());
count++;
}

View File

@ -49,6 +49,7 @@ MAIL(switch_display);
MAIL(toggle_cursor);
MAIL(idr);
MAIL(bitrate);
MAIL(framerate);
MAIL(hdr);
#undef MAIL

View File

@ -407,12 +407,6 @@ bool nvenc_base::create_encoder(const nvenc_config &config,
return false;
}
{
// auto f = stat_trackers::one_digit_after_decimal();
// BOOST_LOG(debug) << "NvEnc: requested encoded frame size " << f %
// (client_config.bitrate / 8. / client_config.framerate) << " kB";
}
{
std::string extra;
if (init_params.enableEncodeAsync) extra += " async";

View File

@ -127,37 +127,6 @@ void display_base_t::high_precision_sleep(std::chrono::nanoseconds duration) {
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 {
// 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;
if (client_frame_rate % display_refresh_rate_rounded == 0) {
candidate.Numerator *=
client_frame_rate / display_refresh_rate_rounded;
} else if (display_refresh_rate_rounded % client_frame_rate == 0) {
candidate.Denominator *=
display_refresh_rate_rounded / client_frame_rate;
}
double candidate_rate =
(double)candidate.Numerator / candidate.Denominator;
// Can only decrease requested fps, otherwise client may start
// accumulating frames and suffer increased latency.
if (client_frame_rate > candidate_rate &&
candidate_rate / client_frame_rate > 0.99) {
BOOST_LOG(info) << "Adjusted capture rate to " << candidate_rate
<< "fps to better match display";
return candidate;
}
}
return {(uint32_t)client_frame_rate, 1};
};
DXGI_RATIONAL client_frame_rate_adjusted = adjust_client_frame_rate();
std::optional<std::chrono::steady_clock::time_point>
frame_pacing_group_start;
uint32_t frame_pacing_group_frames = 0;
// Keep the display awake during capture. If the display goes to sleep
// during capture, best case is that capture stops until it powers back on.
@ -168,8 +137,6 @@ capture_e display_base_t::capture(
auto clear_display_required =
util::fail_guard([]() { SetThreadExecutionState(ES_CONTINUOUS); });
// stat_trackers::min_max_avg_tracker<double> sleep_overshoot_tracker;
while (true) {
// This will return false if the HDR state changes or for any number of
// other display or GPU changes. We should reinit to examine the updated
@ -179,79 +146,9 @@ capture_e display_base_t::capture(
return platf::capture_e::reinit;
}
platf::capture_e status = capture_e::ok;
std::shared_ptr<img_t> img_out;
// Try to continue frame pacing group, snapshot() is called with zero
// timeout after waiting for client frame interval
if (frame_pacing_group_start) {
const uint32_t seconds = (uint64_t)frame_pacing_group_frames *
client_frame_rate_adjusted.Denominator /
client_frame_rate_adjusted.Numerator;
const uint32_t remainder = (uint64_t)frame_pacing_group_frames *
client_frame_rate_adjusted.Denominator %
client_frame_rate_adjusted.Numerator;
const auto sleep_target = *frame_pacing_group_start +
std::chrono::nanoseconds(1s) * seconds +
std::chrono::nanoseconds(1s) * remainder /
client_frame_rate_adjusted.Numerator;
const auto sleep_period =
sleep_target - std::chrono::steady_clock::now();
if (sleep_period <= 0ns) {
// We missed next frame time, invalidating current frame pacing
// group
frame_pacing_group_start = std::nullopt;
frame_pacing_group_frames = 0;
status = capture_e::timeout;
} else {
high_precision_sleep(sleep_period);
// if (config::sunshine.min_log_level <= 1) {
// // Print sleep overshoot stats to debug log every 20
// seconds auto print_info = [&](double min_overshoot, double
// max_overshoot, double avg_overshoot) {
// auto f = stat_trackers::one_digit_after_decimal();
// BOOST_LOG(debug) << "Sleep overshoot (min/max/avg): " <<
// f % min_overshoot << "ms/" << f % max_overshoot << "ms/"
// << f % avg_overshoot << "ms";
// };
// std::chrono::nanoseconds overshoot_ns =
// std::chrono::steady_clock::now() - sleep_target;
// sleep_overshoot_tracker.collect_and_callback_on_interval(overshoot_ns.count()
// / 1000000., print_info, 20s);
// }
status = snapshot(pull_free_image_cb, img_out, 0ms, *cursor);
if (status == capture_e::ok && img_out) {
frame_pacing_group_frames += 1;
} else {
frame_pacing_group_start = std::nullopt;
frame_pacing_group_frames = 0;
}
}
}
// Start new frame pacing group if necessary, snapshot() is called with
// non-zero timeout
if (status == capture_e::timeout ||
(status == capture_e::ok && !frame_pacing_group_start)) {
status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor);
if (status == capture_e::ok && img_out) {
frame_pacing_group_start = img_out->frame_timestamp;
if (!frame_pacing_group_start) {
BOOST_LOG(warning)
<< "snapshot() provided image without timestamp";
frame_pacing_group_start = std::chrono::steady_clock::now();
}
frame_pacing_group_frames = 1;
}
}
platf::capture_e status = capture_e::ok;
status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor);
switch (status) {
case platf::capture_e::reinit:
case platf::capture_e::error:

View File

@ -1663,6 +1663,7 @@ void encode_run(int &frame_nr, // Store progress of the frame number
auto packets = mail->queue<packet_t>(mail::video_packets);
auto idr_events = mail->event<bool>(mail::idr);
auto bitrate_events = mail->event<int>(mail::bitrate);
auto framerate_events = mail->event<int>(mail::framerate);
{
// Load a dummy image into the AVFrame to ensure we have something to
@ -1676,7 +1677,14 @@ void encode_run(int &frame_nr, // Store progress of the frame number
}
}
auto timer = CreateWaitableTimerEx(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
if (!timer)
timer = CreateWaitableTimerEx(nullptr, nullptr, 0, TIMER_ALL_ACCESS);
std::optional<std::chrono::steady_clock::time_point> frame_timestamp;
auto timestamp = std::chrono::steady_clock::now();
auto cycle = std::chrono::nanoseconds(1s) / config->framerate;
while (true) {
if (mail->event<bool>(mail::shutdown)->peek()) {
BOOST_LOG(info) << "Async shutdown event raised";
@ -1688,6 +1696,11 @@ void encode_run(int &frame_nr, // Store progress of the frame number
BOOST_LOG(info) << "bitrate changed to "sv << bitrate;
config->bitrate = bitrate;
break;
} else if (framerate_events->peek()) {
auto framerate = framerate_events->pop().value();
BOOST_LOG(info) << "framerate changed to "sv << framerate;
config->framerate = framerate;
break;
}
if (idr_events->peek()) {
@ -1697,7 +1710,7 @@ void encode_run(int &frame_nr, // Store progress of the frame number
} else if (images->peek()) {
// Encode at a minimum of 10 FPS to avoid image quality issues
// with static content
if (auto img = images->pop(1ms)) {
if (auto img = images->pop()) {
frame_timestamp = img->frame_timestamp;
if (session->convert(*img)) {
BOOST_LOG(error) << "Could not convert image"sv;
@ -1708,12 +1721,23 @@ void encode_run(int &frame_nr, // Store progress of the frame number
}
}
auto sleep_period = (std::chrono::nanoseconds(1s) / config->framerate - cycle).count();
if(sleep_period > 0) {
LARGE_INTEGER due_time;
due_time.QuadPart = sleep_period / -100;
SetWaitableTimer(timer, &due_time, 0, nullptr, nullptr, false);
WaitForSingleObject(timer, INFINITE);
}
if (encode(frame_nr++, *session, packets, channel_data,
frame_timestamp)) {
BOOST_LOG(error) << "Could not encode video packet"sv;
return;
}
auto now = std::chrono::steady_clock::now();
cycle = now - timestamp;
timestamp = now;
session->request_normal_frame();
}
}
@ -1841,6 +1865,7 @@ void update_resolution(config_t *config, const std::string &display_name) {
void capture(safe::mail_t mail, config_t config, void *channel_data) {
auto idr_events = mail->event<bool>(mail::idr);
auto shutdown = mail->event<bool>(mail::shutdown);
idr_events->raise(true);
capture_async(mail, config, channel_data);
}