+ 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.
+ for (unsigned card_index = 0; card_index < num_cards; ++card_index) {
+ CaptureCard *card = &cards[card_index];
+ if (card->capture->get_disconnected()) {
+ fprintf(stderr, "Card %u went away, replacing with a fake card.\n", card_index);
+ FakeCapture *capture = new FakeCapture(global_flags.width, global_flags.height, FAKE_FPS, OUTPUT_FREQUENCY, card_index, global_flags.fake_cards_audio);
+ configure_card(card_index, capture, CardType::FAKE_CAPTURE, /*output=*/nullptr);
+ card->queue_length_policy.reset(card_index);
+ card->capture->start_bm_capture();
+ }
+ }
+
+ // Check for cards that have been connected since last frame.
+ vector<libusb_device *> hotplugged_cards_copy;
+ {
+ lock_guard<mutex> lock(hotplug_mutex);
+ swap(hotplugged_cards, hotplugged_cards_copy);
+ }
+ for (libusb_device *new_dev : hotplugged_cards_copy) {
+ // Look for a fake capture card where we can stick this in.
+ int free_card_index = -1;
+ for (unsigned card_index = 0; card_index < num_cards; ++card_index) {
+ if (cards[card_index].is_fake_capture) {
+ free_card_index = card_index;
+ break;
+ }
+ }
+
+ if (free_card_index == -1) {
+ fprintf(stderr, "New card plugged in, but no free slots -- ignoring.\n");
+ libusb_unref_device(new_dev);
+ } else {
+ // BMUSBCapture takes ownership.
+ fprintf(stderr, "New card plugged in, choosing slot %d.\n", free_card_index);
+ CaptureCard *card = &cards[free_card_index];
+ BMUSBCapture *capture = new BMUSBCapture(free_card_index, new_dev);
+ configure_card(free_card_index, capture, CardType::LIVE_CARD, /*output=*/nullptr);
+ card->queue_length_policy.reset(free_card_index);
+ capture->set_card_disconnected_callback(bind(&Mixer::bm_hotplug_remove, this, free_card_index));
+ capture->start_bm_capture();
+ }
+ }
+}
+
+
+void Mixer::schedule_audio_resampling_tasks(unsigned dropped_frames, int num_samples_per_frame, int length_per_frame, bool is_preroll, steady_clock::time_point frame_timestamp)
+{
+ // Resample the audio as needed, including from previously dropped frames.
+ assert(num_cards > 0);
+ for (unsigned frame_num = 0; frame_num < dropped_frames + 1; ++frame_num) {
+ const bool dropped_frame = (frame_num != dropped_frames);
+ {
+ // Signal to the audio thread to process this frame.
+ // Note that if the frame is a dropped frame, we signal that
+ // we don't want to use this frame as base for adjusting
+ // the resampler rate. The reason for this is that the timing
+ // of these frames is often way too late; they typically don't
+ // “arrive” before we synthesize them. Thus, we could end up
+ // in a situation where we have inserted e.g. five audio frames
+ // into the queue before we then start pulling five of them
+ // back out. This makes ResamplingQueue overestimate the delay,
+ // causing undue resampler changes. (We _do_ use the last,
+ // non-dropped frame; perhaps we should just discard that as well,
+ // since dropped frames are expected to be rare, and it might be
+ // better to just wait until we have a slightly more normal situation).
+ unique_lock<mutex> lock(audio_mutex);
+ bool adjust_rate = !dropped_frame && !is_preroll;
+ audio_task_queue.push(AudioTask{pts_int, num_samples_per_frame, adjust_rate, frame_timestamp});
+ audio_task_queue_changed.notify_one();
+ }
+ if (dropped_frame) {
+ // For dropped frames, increase the pts. Note that if the format changed
+ // in the meantime, we have no way of detecting that; we just have to
+ // assume the frame length is always the same.
+ pts_int += length_per_frame;
+ }
+ }
+}
+
+void Mixer::render_one_frame(int64_t duration)
+{
+ // Determine the time code for this frame before we start rendering.
+ string timecode_text = timecode_renderer->get_timecode_text(double(pts_int) / TIMEBASE, frame_num);
+ if (display_timecode_on_stdout) {
+ printf("Timecode: '%s'\n", timecode_text.c_str());
+ }
+
+ // Update Y'CbCr settings for all cards.
+ {
+ unique_lock<mutex> lock(card_mutex);
+ for (unsigned card_index = 0; card_index < num_cards; ++card_index) {
+ YCbCrInterpretation *interpretation = &ycbcr_interpretation[card_index];
+ input_state.ycbcr_coefficients_auto[card_index] = interpretation->ycbcr_coefficients_auto;
+ input_state.ycbcr_coefficients[card_index] = interpretation->ycbcr_coefficients;
+ input_state.full_range[card_index] = interpretation->full_range;
+ }
+ }
+
+ // Get the main chain from the theme, and set its state immediately.
+ Theme::Chain theme_main_chain = theme->get_chain(0, pts(), global_flags.width, global_flags.height, input_state);
+ EffectChain *chain = theme_main_chain.chain;
+ theme_main_chain.setup_chain();
+ //theme_main_chain.chain->enable_phase_timing(true);
+
+ // If HDMI/SDI output is active and the user has requested auto mode,
+ // its mode overrides the existing Y'CbCr setting for the chain.
+ YCbCrLumaCoefficients ycbcr_output_coefficients;
+ if (global_flags.ycbcr_auto_coefficients && output_card_index != -1) {
+ ycbcr_output_coefficients = cards[output_card_index].output->preferred_ycbcr_coefficients();
+ } else {
+ ycbcr_output_coefficients = global_flags.ycbcr_rec709_coefficients ? YCBCR_REC_709 : YCBCR_REC_601;
+ }
+
+ // TODO: Reduce the duplication against theme.cpp.
+ YCbCrFormat output_ycbcr_format;
+ output_ycbcr_format.chroma_subsampling_x = 1;
+ output_ycbcr_format.chroma_subsampling_y = 1;
+ output_ycbcr_format.luma_coefficients = ycbcr_output_coefficients;
+ output_ycbcr_format.full_range = false;
+ output_ycbcr_format.num_levels = 1 << global_flags.x264_bit_depth;
+ chain->change_ycbcr_output_format(output_ycbcr_format);
+
+ // Render main chain. If we're using zerocopy Quick Sync encoding
+ // (the default case), we take an extra copy of the created outputs,
+ // so that we can display it back to the screen later (it's less memory
+ // bandwidth than writing and reading back an RGBA texture, even at 16-bit).
+ // Ideally, we'd like to avoid taking copies and just use the main textures
+ // for display as well, but they're just views into VA-API memory and must be
+ // unmapped during encoding, so we can't use them for display, unfortunately.
+ GLuint y_tex, cbcr_full_tex, cbcr_tex;
+ GLuint y_copy_tex, cbcr_copy_tex = 0;
+ GLuint y_display_tex, cbcr_display_tex;
+ GLenum y_type = (global_flags.x264_bit_depth > 8) ? GL_R16 : GL_R8;
+ GLenum cbcr_type = (global_flags.x264_bit_depth > 8) ? GL_RG16 : GL_RG8;
+ const bool is_zerocopy = video_encoder->is_zerocopy();
+ if (is_zerocopy) {
+ cbcr_full_tex = resource_pool->create_2d_texture(cbcr_type, global_flags.width, global_flags.height);
+ y_copy_tex = resource_pool->create_2d_texture(y_type, global_flags.width, global_flags.height);
+ cbcr_copy_tex = resource_pool->create_2d_texture(cbcr_type, global_flags.width / 2, global_flags.height / 2);
+
+ y_display_tex = y_copy_tex;
+ cbcr_display_tex = cbcr_copy_tex;
+
+ // y_tex and cbcr_tex will be given by VideoEncoder.
+ } else {
+ cbcr_full_tex = resource_pool->create_2d_texture(cbcr_type, global_flags.width, global_flags.height);
+ y_tex = resource_pool->create_2d_texture(y_type, global_flags.width, global_flags.height);
+ cbcr_tex = resource_pool->create_2d_texture(cbcr_type, global_flags.width / 2, global_flags.height / 2);
+
+ y_display_tex = y_tex;
+ cbcr_display_tex = cbcr_tex;
+ }
+
+ const int64_t av_delay = lrint(global_flags.audio_queue_length_ms * 0.001 * TIMEBASE); // Corresponds to the delay in ResamplingQueue.
+ bool got_frame = video_encoder->begin_frame(pts_int + av_delay, duration, ycbcr_output_coefficients, theme_main_chain.input_frames, &y_tex, &cbcr_tex);
+ assert(got_frame);
+
+ GLuint fbo;
+ if (is_zerocopy) {
+ fbo = resource_pool->create_fbo(y_tex, cbcr_full_tex, y_copy_tex);
+ } else {
+ fbo = resource_pool->create_fbo(y_tex, cbcr_full_tex);
+ }