+ // If needed, drop frames until the queue is below the safe limit.
+ // We prefer to drop from the head, because all else being equal,
+ // we'd like more recent frames (less latency).
+ unsigned dropped_frames = 0;
+ while (queue_length > safe_queue_length) {
+ assert(!card->new_frames.empty());
+ assert(queue_length > card->new_frames.front().dropped_frames);
+ queue_length -= card->new_frames.front().dropped_frames;
+
+ if (queue_length <= safe_queue_length) {
+ // No need to drop anything.
+ break;
+ }
+
+ card->new_frames.pop_front();
+ 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;
+ card->metric_input_queue_length_frames = queue_length;
+
+#if 0
+ if (dropped_frames > 0) {
+ fprintf(stderr, "Card %u dropped %u frame(s) to keep latency down.\n",
+ card_index, dropped_frames);
+ }
+#endif
+}
+
+pair<string, string> Mixer::get_channels_json()
+{
+ Channels ret;
+ for (int channel_idx = 2; channel_idx < theme->get_num_channels(); ++channel_idx) {
+ Channel *channel = ret.add_channel();
+ channel->set_index(channel_idx);
+ channel->set_name(theme->get_channel_name(channel_idx));
+ channel->set_color(theme->get_channel_color(channel_idx));
+ }
+ string contents;
+ google::protobuf::util::MessageToJsonString(ret, &contents); // Ignore any errors.
+ return make_pair(contents, "text/json");
+}
+
+pair<string, string> Mixer::get_channel_color_http(unsigned channel_idx)
+{
+ return make_pair(theme->get_channel_color(channel_idx), "text/plain");
+}
+
+Mixer::OutputFrameInfo Mixer::get_one_frame_from_each_card(unsigned master_card_index, bool master_card_is_output, CaptureCard::NewFrame new_frames[MAX_VIDEO_CARDS], bool has_new_frame[MAX_VIDEO_CARDS])
+{
+ OutputFrameInfo output_frame_info;
+start:
+ unique_lock<mutex> lock(card_mutex, defer_lock);
+ if (master_card_is_output) {
+ // Clocked to the output, so wait for it to be ready for the next frame.
+ cards[master_card_index].output->wait_for_frame(pts_int, &output_frame_info.dropped_frames, &output_frame_info.frame_duration, &output_frame_info.is_preroll, &output_frame_info.frame_timestamp);
+ lock.lock();
+ } else {
+ // Wait for the master card to have a new frame.
+ // TODO: Add a timeout.
+ output_frame_info.is_preroll = false;
+ lock.lock();
+ cards[master_card_index].new_frames_changed.wait(lock, [this, master_card_index]{ return !cards[master_card_index].new_frames.empty() || cards[master_card_index].capture->get_disconnected(); });
+ }
+
+ if (master_card_is_output) {
+ handle_hotplugged_cards();
+ } else if (cards[master_card_index].new_frames.empty()) {
+ // We were woken up, but not due to a new frame. Deal with it
+ // and then restart.
+ assert(cards[master_card_index].capture->get_disconnected());
+ handle_hotplugged_cards();
+ lock.unlock();
+ goto start;
+ }
+
+ for (unsigned card_index = 0; card_index < num_cards + num_video_inputs + num_html_inputs; ++card_index) {
+ CaptureCard *card = &cards[card_index];
+ if (card->new_frames.empty()) { // Starvation.
+ ++card->metric_input_duped_frames;
+#ifdef HAVE_CEF
+ 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();
+ }
+#endif
+ } else {
+ new_frames[card_index] = move(card->new_frames.front());
+ has_new_frame[card_index] = true;
+ card->new_frames.pop_front();
+ card->new_frames_changed.notify_all();
+ }
+ }
+
+ if (!master_card_is_output) {
+ output_frame_info.frame_timestamp = new_frames[master_card_index].received_timestamp;
+ output_frame_info.dropped_frames = new_frames[master_card_index].dropped_frames;
+ output_frame_info.frame_duration = new_frames[master_card_index].length;
+ }
+
+ if (!output_frame_info.is_preroll) {
+ output_jitter_history.frame_arrived(output_frame_info.frame_timestamp, output_frame_info.frame_duration, output_frame_info.dropped_frames);
+ }
+
+ for (unsigned card_index = 0; card_index < num_cards + num_video_inputs + num_html_inputs; ++card_index) {
+ CaptureCard *card = &cards[card_index];
+ if (has_new_frame[card_index] &&
+ !input_card_is_master_clock(card_index, master_card_index) &&
+ !output_frame_info.is_preroll) {
+ card->queue_length_policy.update_policy(
+ output_frame_info.frame_timestamp,
+ card->jitter_history.get_expected_next_frame(),
+ new_frames[master_card_index].length,
+ output_frame_info.frame_duration,
+ card->jitter_history.estimate_max_jitter(),
+ output_jitter_history.estimate_max_jitter());
+ trim_queue(card, min<int>(global_flags.max_input_queue_frames,
+ card->queue_length_policy.get_safe_queue_length()));
+ }
+ }
+
+ // This might get off by a fractional sample when changing master card
+ // between ones with different frame rates, but that's fine.
+ int num_samples_times_timebase = OUTPUT_FREQUENCY * output_frame_info.frame_duration + fractional_samples;
+ output_frame_info.num_samples = num_samples_times_timebase / TIMEBASE;
+ fractional_samples = num_samples_times_timebase % TIMEBASE;
+ assert(output_frame_info.num_samples >= 0);
+
+ return output_frame_info;
+}
+
+void Mixer::handle_hotplugged_cards()
+{
+ // Check for cards that have been disconnected since last frame.