+ video_encoder.reset(nullptr);
+}
+
+void Mixer::configure_card(unsigned card_index, CaptureInterface *capture, CardType card_type, DeckLinkOutput *output)
+{
+ printf("Configuring card %d...\n", card_index);
+
+ CaptureCard *card = &cards[card_index];
+ if (card->capture != nullptr) {
+ card->capture->stop_dequeue_thread();
+ }
+ 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);
+ }
+
+ PixelFormat pixel_format;
+ if (card_type == CardType::FFMPEG_INPUT) {
+ pixel_format = capture->get_current_pixel_format();
+ } else if (card_type == CardType::CEF_INPUT) {
+ pixel_format = PixelFormat_8BitBGRA;
+ } else if (global_flags.ten_bit_input) {
+ pixel_format = PixelFormat_10BitYCbCr;
+ } else {
+ pixel_format = PixelFormat_8BitYCbCr;
+ }
+
+ card->capture->set_frame_callback(bind(&Mixer::bm_frame, this, card_index, _1, _2, _3, _4, _5, _6, _7));
+ if (card->frame_allocator == nullptr) {
+ card->frame_allocator.reset(new PBOFrameAllocator(pixel_format, 8 << 20, global_flags.width, global_flags.height)); // 8 MB.
+ }
+ card->capture->set_video_frame_allocator(card->frame_allocator.get());
+ if (card->surface == nullptr) {
+ card->surface = create_surface_with_same_format(mixer_surface);
+ }
+ while (!card->new_frames.empty()) card->new_frames.pop_front();
+ card->last_timecode = -1;
+ card->capture->set_pixel_format(pixel_format);
+ card->capture->configure_card();
+
+ // NOTE: start_bm_capture() happens in thread_func().
+
+ DeviceSpec device;
+ if (card_type == CardType::FFMPEG_INPUT) {
+ device = DeviceSpec{InputSourceType::FFMPEG_VIDEO_INPUT, card_index - num_cards};
+ } else {
+ device = DeviceSpec{InputSourceType::CAPTURE_CARD, card_index};
+ }
+ audio_mixer->reset_resampler(device);
+ audio_mixer->set_display_name(device, card->capture->get_description());
+ audio_mixer->trigger_state_changed_callback();
+
+ // Unregister old metrics, if any.
+ if (!card->labels.empty()) {
+ const vector<pair<string, string>> &labels = card->labels;
+ card->jitter_history.unregister_metrics(labels);
+ card->queue_length_policy.unregister_metrics(labels);
+ global_metrics.remove("input_received_frames", labels);
+ global_metrics.remove("input_dropped_frames_jitter", labels);
+ global_metrics.remove("input_dropped_frames_error", labels);
+ global_metrics.remove("input_dropped_frames_resets", labels);
+ global_metrics.remove("input_queue_length_frames", labels);
+ global_metrics.remove("input_queue_duped_frames", labels);
+
+ global_metrics.remove("input_has_signal_bool", labels);
+ global_metrics.remove("input_is_connected_bool", labels);
+ global_metrics.remove("input_interlaced_bool", labels);
+ global_metrics.remove("input_width_pixels", labels);
+ global_metrics.remove("input_height_pixels", labels);
+ global_metrics.remove("input_frame_rate_nom", labels);
+ global_metrics.remove("input_frame_rate_den", labels);
+ global_metrics.remove("input_sample_rate_hz", labels);
+ }
+
+ // Register metrics.
+ vector<pair<string, string>> labels;
+ char card_name[64];
+ snprintf(card_name, sizeof(card_name), "%d", card_index);
+ labels.emplace_back("card", card_name);
+
+ switch (card_type) {
+ case CardType::LIVE_CARD:
+ labels.emplace_back("cardtype", "live");
+ break;
+ case CardType::FAKE_CAPTURE:
+ labels.emplace_back("cardtype", "fake");
+ break;
+ case CardType::FFMPEG_INPUT:
+ labels.emplace_back("cardtype", "ffmpeg");
+ break;
+ case CardType::CEF_INPUT:
+ labels.emplace_back("cardtype", "cef");
+ break;
+ default:
+ assert(false);
+ }
+ card->jitter_history.register_metrics(labels);
+ card->queue_length_policy.register_metrics(labels);
+ global_metrics.add("input_received_frames", labels, &card->metric_input_received_frames);
+ global_metrics.add("input_dropped_frames_jitter", labels, &card->metric_input_dropped_frames_jitter);
+ global_metrics.add("input_dropped_frames_error", labels, &card->metric_input_dropped_frames_error);
+ global_metrics.add("input_dropped_frames_resets", labels, &card->metric_input_resets);
+ global_metrics.add("input_queue_length_frames", labels, &card->metric_input_queue_length_frames, Metrics::TYPE_GAUGE);
+ global_metrics.add("input_queue_duped_frames", labels, &card->metric_input_duped_frames);
+
+ global_metrics.add("input_has_signal_bool", labels, &card->metric_input_has_signal_bool, Metrics::TYPE_GAUGE);
+ global_metrics.add("input_is_connected_bool", labels, &card->metric_input_is_connected_bool, Metrics::TYPE_GAUGE);
+ global_metrics.add("input_interlaced_bool", labels, &card->metric_input_interlaced_bool, Metrics::TYPE_GAUGE);
+ global_metrics.add("input_width_pixels", labels, &card->metric_input_width_pixels, Metrics::TYPE_GAUGE);
+ global_metrics.add("input_height_pixels", labels, &card->metric_input_height_pixels, Metrics::TYPE_GAUGE);
+ global_metrics.add("input_frame_rate_nom", labels, &card->metric_input_frame_rate_nom, Metrics::TYPE_GAUGE);
+ global_metrics.add("input_frame_rate_den", labels, &card->metric_input_frame_rate_den, Metrics::TYPE_GAUGE);
+ global_metrics.add("input_sample_rate_hz", labels, &card->metric_input_sample_rate_hz, Metrics::TYPE_GAUGE);
+ card->labels = labels;
+}
+
+void Mixer::set_output_card_internal(int card_index)
+{
+ // We don't really need to take card_mutex, since we're in the mixer
+ // thread and don't mess with any queues (which is the only thing that happens
+ // from other threads), but it's probably the safest in the long run.
+ unique_lock<mutex> lock(card_mutex);
+ if (output_card_index != -1) {
+ // Switch the old card from output to input.
+ CaptureCard *old_card = &cards[output_card_index];
+ old_card->output->end_output();
+
+ // Stop the fake card that we put into place.
+ // This needs to _not_ happen under the mutex, to avoid deadlock
+ // (delivering the last frame needs to take the mutex).
+ CaptureInterface *fake_capture = old_card->capture.get();
+ lock.unlock();
+ fake_capture->stop_dequeue_thread();
+ lock.lock();
+ old_card->capture = move(old_card->parked_capture); // TODO: reset the metrics
+ old_card->is_fake_capture = false;
+ old_card->capture->start_bm_capture();
+ }
+ if (card_index != -1) {
+ CaptureCard *card = &cards[card_index];
+ CaptureInterface *capture = card->capture.get();
+ // TODO: DeckLinkCapture::stop_dequeue_thread can actually take
+ // several seconds to complete (blocking on DisableVideoInput);
+ // see if we can maybe do it asynchronously.
+ lock.unlock();
+ capture->stop_dequeue_thread();
+ lock.lock();
+ card->parked_capture = move(card->capture);
+ CaptureInterface *fake_capture = new FakeCapture(global_flags.width, global_flags.height, FAKE_FPS, OUTPUT_FREQUENCY, card_index, global_flags.fake_cards_audio);
+ configure_card(card_index, fake_capture, CardType::FAKE_CAPTURE, card->output.release());
+ card->queue_length_policy.reset(card_index);
+ card->capture->start_bm_capture();
+ desired_output_video_mode = output_video_mode = card->output->pick_video_mode(desired_output_video_mode);
+ card->output->start_output(desired_output_video_mode, pts_int);
+ }
+ output_card_index = card_index;
+ output_jitter_history.clear();