diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index 92d7136a..d6d5c5de 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -198,9 +198,33 @@ enum class capture_e : int { class display_t { public: + /** + * When display has a new image ready, this callback will be called with the new image. + * + * On Break --> + * Returns nullptr + * + * On Success --> + * Returns the image object that should be filled next. + * This may or may not be the image send with the callback + */ + using snapshot_cb_t = std::function(std::shared_ptr &img)>; + display_t() noexcept : offset_x { 0 }, offset_y { 0 } {} - virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) = 0; - virtual std::shared_ptr alloc_img() = 0; + + /** + * snapshot_cb --> the callback + * std::shared_ptr img --> The first image to use + * bool *cursor --> A pointer to the flag that indicates wether the cursor should be captured as well + * + * Returns either: + * capture_e::ok when stopping + * capture_e::error on error + * capture_e::reinit when need of reinitialization + */ + virtual capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) = 0; + + virtual std::shared_ptr alloc_img() = 0; virtual int dummy_img(img_t *img) = 0; @@ -247,7 +271,7 @@ std::string from_sockaddr(const sockaddr *const); std::pair from_sockaddr_ex(const sockaddr *const); std::unique_ptr audio_control(); -std::shared_ptr display(mem_type_e hwdevice_type); +std::shared_ptr display(mem_type_e hwdevice_type, int framerate); input_t input(); void move_mouse(input_t &input, int deltaX, int deltaY); diff --git a/sunshine/platform/windows/display.h b/sunshine/platform/windows/display.h index bd920499..ba7a771e 100644 --- a/sunshine/platform/windows/display.h +++ b/sunshine/platform/windows/display.h @@ -74,7 +74,9 @@ public: class display_base_t : public display_t { public: - int init(); + int init(int framerate); + + std::chrono::nanoseconds delay; factory1_t factory; adapter_t adapter; @@ -100,11 +102,14 @@ public: class display_ram_t : public display_base_t { public: - capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) override; + capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; + capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); + + std::shared_ptr alloc_img() override; int dummy_img(img_t *img) override; - int init(); + int init(int framerate); cursor_t cursor; D3D11_MAPPED_SUBRESOURCE img_info; @@ -113,7 +118,8 @@ public: class display_vram_t : public display_base_t, public std::enable_shared_from_this { public: - capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) override; + capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; + capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); std::shared_ptr alloc_img() override; int dummy_img(img_t *img_base) override; diff --git a/sunshine/platform/windows/display_base.cpp b/sunshine/platform/windows/display_base.cpp index 3fde7a68..69e7c972 100644 --- a/sunshine/platform/windows/display_base.cpp +++ b/sunshine/platform/windows/display_base.cpp @@ -73,7 +73,7 @@ duplication_t::~duplication_t() { release_frame(); } -int display_base_t::init() { +int display_base_t::init(int framerate) { /* Uncomment when use of IDXGIOutput5 is implemented std::call_once(windows_cpp_once_flag, []() { DECLARE_HANDLE(DPI_AWARENESS_CONTEXT); @@ -94,6 +94,8 @@ int display_base_t::init() { // Ensure we can duplicate the current display syncThreadDesktop(); + delay = std::chrono::nanoseconds { 1s } / framerate; + // Get rectangle of full desktop for absolute mouse coordinates env_width = GetSystemMetrics(SM_CXVIRTUALSCREEN); env_height = GetSystemMetrics(SM_CYVIRTUALSCREEN); @@ -431,18 +433,18 @@ const char *format_str[] = { } // namespace platf::dxgi namespace platf { -std::shared_ptr display(mem_type_e hwdevice_type) { +std::shared_ptr display(mem_type_e hwdevice_type, int framerate) { if(hwdevice_type == mem_type_e::dxgi) { auto disp = std::make_shared(); - if(!disp->init()) { + if(!disp->init(framerate)) { return disp; } } else if(hwdevice_type == mem_type_e::system) { auto disp = std::make_shared(); - if(!disp->init()) { + if(!disp->init(framerate)) { return disp; } } diff --git a/sunshine/platform/windows/display_ram.cpp b/sunshine/platform/windows/display_ram.cpp index cb3930d4..fbca21f2 100644 --- a/sunshine/platform/windows/display_ram.cpp +++ b/sunshine/platform/windows/display_ram.cpp @@ -165,6 +165,36 @@ void blend_cursor(const cursor_t &cursor, img_t &img) { } } +capture_e display_ram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) { + auto next_frame = std::chrono::steady_clock::now(); + + while(img) { + auto now = std::chrono::steady_clock::now(); + while(next_frame > now) { + now = std::chrono::steady_clock::now(); + } + next_frame = now + delay; + + auto status = snapshot(img.get(), 1000ms, *cursor); + switch(status) { + case platf::capture_e::reinit: + case platf::capture_e::error: + return status; + case platf::capture_e::timeout: + std::this_thread::sleep_for(1ms); + continue; + case platf::capture_e::ok: + img = snapshot_cb(img); + break; + default: + BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; + return status; + } + } + + return capture_e::ok; +} + capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) { auto img = (img_t *)img_base; @@ -263,8 +293,8 @@ int display_ram_t::dummy_img(platf::img_t *img) { return 0; } -int display_ram_t::init() { - if(display_base_t::init()) { +int display_ram_t::init(int framerate) { + if(display_base_t::init(framerate)) { return -1; } diff --git a/sunshine/platform/windows/display_vram.cpp b/sunshine/platform/windows/display_vram.cpp index bb77bbda..bbbd9334 100644 --- a/sunshine/platform/windows/display_vram.cpp +++ b/sunshine/platform/windows/display_vram.cpp @@ -693,6 +693,36 @@ public: std::vector *hwdevices_p; }; +capture_e display_vram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) { + auto next_frame = std::chrono::steady_clock::now(); + + while(img) { + auto now = std::chrono::steady_clock::now(); + while(next_frame > now) { + now = std::chrono::steady_clock::now(); + } + next_frame = now + delay; + + auto status = snapshot(img.get(), 1000ms, *cursor); + switch(status) { + case platf::capture_e::reinit: + case platf::capture_e::error: + return status; + case platf::capture_e::timeout: + std::this_thread::sleep_for(1ms); + continue; + case platf::capture_e::ok: + img = snapshot_cb(img); + break; + default: + BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; + return status; + } + } + + return capture_e::ok; +} + capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) { auto img = (img_d3d_t *)img_base; diff --git a/sunshine/video.cpp b/sunshine/video.cpp index f98750c6..af0d3b36 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -367,9 +367,6 @@ struct sync_session_ctx_t { struct sync_session_t { sync_session_ctx_t *ctx; - std::chrono::steady_clock::time_point next_frame; - std::chrono::nanoseconds delay; - platf::img_t *img_tmp; std::shared_ptr hwdevice; session_t session; @@ -380,7 +377,7 @@ using encode_e = platf::capture_e; struct capture_ctx_t { img_event_t images; - std::chrono::nanoseconds delay; + int framerate; }; struct capture_thread_async_ctx_t { @@ -554,11 +551,11 @@ static std::vector encoders { software }; -void reset_display(std::shared_ptr &disp, AVHWDeviceType type) { +void reset_display(std::shared_ptr &disp, AVHWDeviceType type, int framerate) { // We try this twice, in case we still get an error on reinitialization for(int x = 0; x < 2; ++x) { disp.reset(); - disp = platf::display(map_dev_type(type)); + disp = platf::display(map_dev_type(type), framerate); if(disp) { break; } @@ -586,9 +583,11 @@ void captureThread( } }); - std::chrono::nanoseconds delay = 1s; + if(auto capture_ctx = capture_ctx_queue->pop()) { + capture_ctxs.emplace_back(std::move(*capture_ctx)); + } - auto disp = platf::display(map_dev_type(encoder.dev_type)); + auto disp = platf::display(map_dev_type(encoder.dev_type), capture_ctxs.front().framerate); if(!disp) { return; } @@ -605,30 +604,35 @@ void captureThread( } } - if(auto capture_ctx = capture_ctx_queue->pop()) { - capture_ctxs.emplace_back(std::move(*capture_ctx)); - - delay = capture_ctxs.back().delay; - } - - auto next_frame = std::chrono::steady_clock::now(); while(capture_ctx_queue->running()) { - while(capture_ctx_queue->peek()) { - capture_ctxs.emplace_back(std::move(*capture_ctx_queue->pop())); + auto status = disp->capture([&](std::shared_ptr &img) -> std::shared_ptr { + while(capture_ctx_queue->peek()) { + capture_ctxs.emplace_back(std::move(*capture_ctx_queue->pop())); + } - delay = std::min(delay, capture_ctxs.back().delay); - } + KITTY_WHILE_LOOP(auto capture_ctx = std::begin(capture_ctxs), capture_ctx != std::end(capture_ctxs), { + if(!capture_ctx->images->running()) { + capture_ctx = capture_ctxs.erase(capture_ctx); - auto &img = *round_robin++; - while(img.use_count() > 1) {} + continue; + } + + capture_ctx->images->raise(img); + ++capture_ctx; + }) + + if(!capture_ctx_queue->running()) { + return nullptr; + } + + auto &next_img = *round_robin++; + while(next_img.use_count() > 1) {} + + return next_img; + }, + *round_robin++, &display_cursor); - auto now = std::chrono::steady_clock::now(); - while(next_frame > now) { - now = std::chrono::steady_clock::now(); - } - next_frame = now + delay; - auto status = disp->snapshot(img.get(), 1000ms, display_cursor); switch(status) { case platf::capture_e::reinit: { reinit_event.raise(true); @@ -647,7 +651,7 @@ void captureThread( } while(capture_ctx_queue->running()) { - reset_display(disp, encoder.dev_type); + reset_display(disp, encoder.dev_type, capture_ctxs.front().framerate); if(disp) { break; @@ -672,33 +676,13 @@ void captureThread( continue; } case platf::capture_e::error: - return; - case platf::capture_e::timeout: - std::this_thread::sleep_for(1ms); - continue; case platf::capture_e::ok: - break; + case platf::capture_e::timeout: + return; default: BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; return; } - - KITTY_WHILE_LOOP(auto capture_ctx = std::begin(capture_ctxs), capture_ctx != std::end(capture_ctxs), { - if(!capture_ctx->images->running()) { - auto tmp_delay = capture_ctx->delay; - capture_ctx = capture_ctxs.erase(capture_ctx); - - if(tmp_delay == delay) { - delay = std::min_element(std::begin(capture_ctxs), std::end(capture_ctxs), [](const auto &l, const auto &r) { - return l.delay < r.delay; - })->delay; - } - continue; - } - - capture_ctx->images->raise(img); - ++capture_ctx; - }) } } @@ -1074,10 +1058,7 @@ input::touch_port_t make_port(platf::display_t *display, const config_t &config) std::optional make_synced_session(platf::display_t *disp, const encoder_t &encoder, platf::img_t &img, sync_session_ctx_t &ctx) { sync_session_t encode_session; - encode_session.ctx = &ctx; - encode_session.next_frame = std::chrono::steady_clock::now(); - - encode_session.delay = std::chrono::nanoseconds { 1s } / ctx.config.framerate; + encode_session.ctx = &ctx; auto pix_fmt = ctx.config.dynamicRange == 0 ? map_pix_fmt(encoder.static_pix_fmt) : map_pix_fmt(encoder.dynamic_pix_fmt); auto hwdevice = disp->make_hwdevice(pix_fmt); @@ -1093,7 +1074,6 @@ std::optional make_synced_session(platf::display_t *disp, const return std::nullopt; } - encode_session.img_tmp = &img; encode_session.hwdevice = std::move(hwdevice); encode_session.session = std::move(*session); @@ -1105,8 +1085,19 @@ encode_e encode_run_sync(std::vector> &synce std::shared_ptr disp; + if(synced_session_ctxs.empty()) { + auto ctx = encode_session_ctx_queue.pop(); + if(!ctx) { + return encode_e::ok; + } + + synced_session_ctxs.emplace_back(std::make_unique(std::move(*ctx))); + } + + int framerate = synced_session_ctxs.front()->config.framerate; + while(encode_session_ctx_queue.running()) { - reset_display(disp, encoder.dev_type); + reset_display(disp, encoder.dev_type, framerate); if(disp) { break; } @@ -1119,9 +1110,7 @@ encode_e encode_run_sync(std::vector> &synce } auto img = disp->alloc_img(); - - auto img_tmp = img.get(); - if(disp->dummy_img(img_tmp)) { + if(disp->dummy_img(img.get())) { return encode_e::error; } @@ -1135,109 +1124,83 @@ encode_e encode_run_sync(std::vector> &synce synced_sessions.emplace_back(std::move(*synced_session)); } - auto next_frame = std::chrono::steady_clock::now(); + auto ec = platf::capture_e::ok; while(encode_session_ctx_queue.running()) { - while(encode_session_ctx_queue.peek()) { - auto encode_session_ctx = encode_session_ctx_queue.pop(); - if(!encode_session_ctx) { - return encode_e::ok; - } - - synced_session_ctxs.emplace_back(std::make_unique(std::move(*encode_session_ctx))); - - auto encode_session = make_synced_session(disp.get(), encoder, *img, *synced_session_ctxs.back()); - if(!encode_session) { - return encode_e::error; - } - - synced_sessions.emplace_back(std::move(*encode_session)); - - next_frame = std::chrono::steady_clock::now(); - } - - auto delay = std::max(0ms, std::chrono::duration_cast(next_frame - std::chrono::steady_clock::now())); - - auto status = disp->snapshot(img.get(), delay, display_cursor); - switch(status) { - case platf::capture_e::reinit: - case platf::capture_e::error: - return status; - case platf::capture_e::timeout: - break; - case platf::capture_e::ok: - img_tmp = img.get(); - break; - } - - auto now = std::chrono::steady_clock::now(); - - next_frame = now + 1s; - KITTY_WHILE_LOOP(auto pos = std::begin(synced_sessions), pos != std::end(synced_sessions), { - auto frame = pos->session.device->frame; - auto ctx = pos->ctx; - if(ctx->shutdown_event->peek()) { - // Let waiting thread know it can delete shutdown_event - ctx->join_event->raise(true); - - pos = synced_sessions.erase(pos); - synced_session_ctxs.erase(std::find_if(std::begin(synced_session_ctxs), std::end(synced_session_ctxs), [&ctx_p = ctx](auto &ctx) { - return ctx.get() == ctx_p; - })); - - if(synced_sessions.empty()) { - return encode_e::ok; + auto snapshot_cb = [&](std::shared_ptr &img) -> std::shared_ptr { + while(encode_session_ctx_queue.peek()) { + auto encode_session_ctx = encode_session_ctx_queue.pop(); + if(!encode_session_ctx) { + return nullptr; } - continue; + synced_session_ctxs.emplace_back(std::make_unique(std::move(*encode_session_ctx))); + + auto encode_session = make_synced_session(disp.get(), encoder, *img, *synced_session_ctxs.back()); + if(!encode_session) { + ec = platf::capture_e::error; + return nullptr; + } + + synced_sessions.emplace_back(std::move(*encode_session)); } - if(ctx->idr_events->peek()) { - frame->pict_type = AV_PICTURE_TYPE_I; - frame->key_frame = 1; + KITTY_WHILE_LOOP(auto pos = std::begin(synced_sessions), pos != std::end(synced_sessions), { + auto frame = pos->session.device->frame; + auto ctx = pos->ctx; + if(ctx->shutdown_event->peek()) { + // Let waiting thread know it can delete shutdown_event + ctx->join_event->raise(true); - ctx->idr_events->pop(); - } + pos = synced_sessions.erase(pos); + synced_session_ctxs.erase(std::find_if(std::begin(synced_session_ctxs), std::end(synced_session_ctxs), [&ctx_p = ctx](auto &ctx) { + return ctx.get() == ctx_p; + })); - if(img_tmp) { - pos->img_tmp = img_tmp; - } + if(synced_sessions.empty()) { + return nullptr; + } - auto timeout = now > pos->next_frame; - if(timeout) { - pos->next_frame += pos->delay; - } + continue; + } - next_frame = std::min(next_frame, pos->next_frame); + if(ctx->idr_events->peek()) { + frame->pict_type = AV_PICTURE_TYPE_I; + frame->key_frame = 1; - if(!timeout) { - ++pos; - continue; - } + ctx->idr_events->pop(); + } - if(pos->img_tmp) { - if(pos->hwdevice->convert(*pos->img_tmp)) { + if(pos->hwdevice->convert(*img)) { BOOST_LOG(error) << "Could not convert image"sv; ctx->shutdown_event->raise(true); continue; } - pos->img_tmp = nullptr; - } - if(encode(ctx->frame_nr++, pos->session, frame, ctx->packets, ctx->channel_data)) { - BOOST_LOG(error) << "Could not encode video packet"sv; - ctx->shutdown_event->raise(true); + if(encode(ctx->frame_nr++, pos->session, frame, ctx->packets, ctx->channel_data)) { + BOOST_LOG(error) << "Could not encode video packet"sv; + ctx->shutdown_event->raise(true); - continue; - } + continue; + } - frame->pict_type = AV_PICTURE_TYPE_NONE; - frame->key_frame = 0; + frame->pict_type = AV_PICTURE_TYPE_NONE; + frame->key_frame = 0; - ++pos; - }) + ++pos; + }) - img_tmp = nullptr; + return img; + }; + + auto status = disp->capture(std::move(snapshot_cb), img, &display_cursor); + switch(status) { + case platf::capture_e::reinit: + case platf::capture_e::error: + case platf::capture_e::ok: + case platf::capture_e::timeout: + return ec != platf::capture_e::ok ? ec : status; + } } return encode_e::ok; @@ -1284,9 +1247,8 @@ void capture_async( return; } - auto delay = std::chrono::floor(1s) / config.framerate; ref->capture_ctx_queue->raise(capture_ctx_t { - images, delay }); + images, config.framerate }); if(!ref->capture_ctx_queue->running()) { return; @@ -1376,7 +1338,7 @@ enum validate_flag_e { }; int validate_config(std::shared_ptr &disp, const encoder_t &encoder, const config_t &config) { - reset_display(disp, encoder.dev_type); + reset_display(disp, encoder.dev_type, config.framerate); if(!disp) { return -1; }