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<recursive_mutex> 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();
// (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<recursive_mutex> 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));
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);
}
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);
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();
}
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;
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;
CardType type;
std::unique_ptr<DeckLinkOutput> 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 <capture> gets replaced
// by a FakeCapture. However, since reconstructing the real capture object