From: Steinar H. Gunderson Date: Sun, 18 Mar 2018 22:26:00 +0000 (+0100) Subject: Fix an issue where we could discard a CEF frame in a situation where no new one would... X-Git-Tag: 1.7.1~17 X-Git-Url: https://git.sesse.net/?p=nageru;a=commitdiff_plain;h=3196324d54748a391c7c333191769f2624e3e022;hp=06608bb52fa8788a7a6498198143fc38fa83df44 Fix an issue where we could discard a CEF frame in a situation where no new one would come, and thus be stuck on an old frame forever. --- diff --git a/cef_capture.cpp b/cef_capture.cpp index 853c6b4..f692c85 100644 --- a/cef_capture.cpp +++ b/cef_capture.cpp @@ -97,6 +97,19 @@ void CEFCapture::resize(unsigned width, unsigned height) this->height = height; } +void CEFCapture::request_new_frame() +{ + // By adding a delay, we make sure we don't get a new frame + // delivered immediately (we probably already are on the UI thread), + // where we couldn't really deal with it. + post_to_cef_ui_thread([this] { + lock_guard lock(browser_mutex); + if (browser != nullptr) { // Could happen if we are shutting down. + browser->GetHost()->Invalidate(PET_VIEW); + } + }, 16); +} + void CEFCapture::OnPaint(const void *buffer, int width, int height) { steady_clock::time_point timestamp = steady_clock::now(); @@ -116,16 +129,7 @@ void CEFCapture::OnPaint(const void *buffer, int width, int height) // (CEF only sends OnPaint when there are actual changes, // so we need to do this explicitly, or we could be stuck on an // old frame forever if the image doesn't change.) - // - // By adding a delay, we make sure we don't get a new frame - // delivered immediately (we probably already are on the UI thread), - // where we couldn't really deal with it. - post_to_cef_ui_thread([this] { - lock_guard lock(browser_mutex); - if (browser != nullptr) { // Could happen if we are shutting down. - browser->GetHost()->Invalidate(PET_VIEW); - } - }, 16); + request_new_frame(); ++timecode; } else { assert(video_frame.size >= unsigned(width * height * 4)); diff --git a/cef_capture.h b/cef_capture.h index 0d9bd52..30cc487 100644 --- a/cef_capture.h +++ b/cef_capture.h @@ -86,6 +86,7 @@ public: void set_max_fps(int max_fps); void execute_javascript_async(const std::string &js); void resize(unsigned width, unsigned height); + void request_new_frame(); // Callbacks from NageruCEFClient. void OnPaint(const void *buffer, int width, int height); diff --git a/mixer.cpp b/mixer.cpp index e1d9ca9..d092516 100644 --- a/mixer.cpp +++ b/mixer.cpp @@ -523,6 +523,8 @@ void Mixer::configure_card(unsigned card_index, CaptureInterface *capture, CardT } card->capture.reset(capture); card->is_fake_capture = (card_type == CardType::FAKE_CAPTURE); + card->is_cef_capture = (card_type == CardType::CEF_INPUT); + card->may_have_dropped_last_frame = false; card->type = card_type; if (card->output.get() != output) { card->output.reset(output); @@ -940,6 +942,7 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, new_frame.received_timestamp = video_frame.received_timestamp; // Ignore the audio timestamp. card->new_frames.push_back(move(new_frame)); card->jitter_history.frame_arrived(video_frame.received_timestamp, frame_length, dropped_frames); + card->may_have_dropped_last_frame = false; } card->new_frames_changed.notify_all(); } @@ -1116,6 +1119,10 @@ void Mixer::trim_queue(CaptureCard *card, size_t safe_queue_length) card->new_frames_changed.notify_all(); --queue_length; ++dropped_frames; + + if (queue_length == 0 && card->is_cef_capture) { + card->may_have_dropped_last_frame = true; + } } card->metric_input_dropped_frames_jitter += dropped_frames; @@ -1180,6 +1187,14 @@ start: CaptureCard *card = &cards[card_index]; if (card->new_frames.empty()) { // Starvation. ++card->metric_input_duped_frames; + if (card->is_cef_capture && card->may_have_dropped_last_frame) { + // Unlike other sources, CEF is not guaranteed to send us a steady + // stream of frames, so we'll have to ask it to repaint the frame + // we dropped. (may_have_dropped_last_frame is set whenever we + // trim the queue completely away, and cleared when we actually + // get a new frame.) + ((CEFCapture *)card->capture.get())->request_new_frame(); + } } else { new_frames[card_index] = move(card->new_frames.front()); has_new_frame[card_index] = true; diff --git a/mixer.h b/mixer.h index effc2a3..289ce45 100644 --- a/mixer.h +++ b/mixer.h @@ -487,6 +487,15 @@ private: CardType type; std::unique_ptr output; + // CEF only delivers frames when it actually has a change. + // If we trim the queue for latency reasons, we could thus + // end up in a situation trimming a frame that was meant to + // be displayed for a long time, which is really suboptimal. + // Thus, if we drop the last frame we have, may_have_dropped_last_frame + // is set to true, and the next starvation event will trigger + // us requestin a CEF repaint. + bool is_cef_capture, may_have_dropped_last_frame = false; + // If this card is used for output (ie., output_card_index points to it), // it cannot simultaneously be uesd for capture, so gets replaced // by a FakeCapture. However, since reconstructing the real capture object