mirror of
https://github.com/thinkonmay/sunshine-sdk.git
synced 2026-08-07 22:26:01 +00:00
constant framerate
This commit is contained in:
parent
145a0fe70f
commit
925d69d848
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
11
src/main.cpp
11
src/main.cpp
@ -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++;
|
||||
}
|
||||
|
||||
|
||||
@ -49,6 +49,7 @@ MAIL(switch_display);
|
||||
MAIL(toggle_cursor);
|
||||
MAIL(idr);
|
||||
MAIL(bitrate);
|
||||
MAIL(framerate);
|
||||
MAIL(hdr);
|
||||
|
||||
#undef MAIL
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user