From: Steinar H. Gunderson Date: Wed, 8 Feb 2017 20:38:12 +0000 (+0100) Subject: Make it possible to choose the DeckLink output video mode through the GUI. X-Git-Tag: 1.5.0~49 X-Git-Url: https://git.sesse.net/?p=nageru;a=commitdiff_plain;h=9aabf53d2ee424e66e3d51039a7a8ecce25f3516 Make it possible to choose the DeckLink output video mode through the GUI. --- diff --git a/decklink_output.cpp b/decklink_output.cpp index 38f44ee..55d29c5 100644 --- a/decklink_output.cpp +++ b/decklink_output.cpp @@ -302,6 +302,33 @@ void DeckLinkOutput::wait_for_frame(int64_t pts, int *dropped_frames, int64_t *f fprintf(stderr, "Dropped %d output frames; skipping.\n", *dropped_frames); } +uint32_t DeckLinkOutput::pick_video_mode(uint32_t mode) const +{ + if (video_modes.count(mode)) { + return mode; + } + + // Prioritize 59.94 > 60 > 29.97. If none of those are found, then pick the highest one. + for (const pair &desired : vector>{ { 60000, 1001 }, { 60, 0 }, { 30000, 1001 } }) { + for (const auto &it : video_modes) { + if (it.second.frame_rate_num * desired.second == desired.first * it.second.frame_rate_den) { + return it.first; + } + } + } + + uint32_t best_mode = 0; + double best_fps = 0.0; + for (const auto &it : video_modes) { + double fps = double(it.second.frame_rate_num) / it.second.frame_rate_den; + if (fps > best_fps) { + best_mode = it.first; + best_fps = fps; + } + } + return best_mode; +} + HRESULT DeckLinkOutput::ScheduledFrameCompleted(/* in */ IDeckLinkVideoFrame *completedFrame, /* in */ BMDOutputFrameCompletionResult result) { Frame *frame = static_cast(completedFrame); diff --git a/decklink_output.h b/decklink_output.h index d3bafca..d5e743a 100644 --- a/decklink_output.h +++ b/decklink_output.h @@ -56,6 +56,9 @@ public: // Analogous to CaptureInterface. Will only return modes that have the right width/height. std::map get_available_video_modes() const { return video_modes; } + // If the given mode is supported, return it. If not, pick some “best” valid mode. + uint32_t pick_video_mode(uint32_t mode) const; + // IUnknown. HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv) override; ULONG STDMETHODCALLTYPE AddRef() override; diff --git a/glwidget.cpp b/glwidget.cpp index 9aba1b3..3494338 100644 --- a/glwidget.cpp +++ b/glwidget.cpp @@ -141,7 +141,7 @@ void GLWidget::show_live_context_menu(const QPoint &pos) if (current_card == -1) { none_action->setChecked(true); } - none_action->setData(-1); + none_action->setData(QList{"output_card", -1}); card_submenu.addAction(none_action); unsigned num_cards = global_mixer->get_num_cards(); @@ -156,18 +156,51 @@ void GLWidget::show_live_context_menu(const QPoint &pos) if (current_card == int(card_index)) { action->setChecked(true); } - action->setData(card_index); + action->setData(QList{"output_card", card_index}); card_submenu.addAction(action); } card_submenu.setTitle("HDMI/SDI output"); menu.addMenu(&card_submenu); + // Add a submenu for choosing the output resolution. Since this is + // card-dependent, it is disabled if we haven't chosen a card + // (but it's still there so that the user will know it exists). + QMenu resolution_submenu; + QActionGroup resolution_group(&resolution_submenu); + if (current_card == -1) { + resolution_submenu.setEnabled(false); + } else { + uint32_t current_mode = global_mixer->get_output_video_mode(); + map video_modes = global_mixer->get_available_output_video_modes(); + for (const auto &mode : video_modes) { + QString description(QString::fromStdString(mode.second.name)); + QAction *action = new QAction(description, &resolution_group); + action->setCheckable(true); + if (current_mode == mode.first) { + action->setChecked(true); + } + action->setData(QList{"video_mode", mode.first}); + resolution_submenu.addAction(action); + } + } + + resolution_submenu.setTitle("HDMI/SDI output resolution"); + menu.addMenu(&resolution_submenu); + // Show the menu and look at the result. QAction *selected_item = menu.exec(global_pos); if (selected_item != nullptr) { - int output_card = selected_item->data().toInt(nullptr); - global_mixer->set_output_card(output_card); + QList selected = selected_item->data().toList(); + if (selected[0].toString() == "output_card") { + unsigned output_card = selected[1].toUInt(nullptr); + global_mixer->set_output_card(output_card); + } else if (selected[0].toString() == "video_mode") { + uint32_t mode = selected[1].toUInt(nullptr); + global_mixer->set_output_video_mode(mode); + } else { + assert(false); + } } } diff --git a/mixer.cpp b/mixer.cpp index b28da27..f82650e 100644 --- a/mixer.cpp +++ b/mixer.cpp @@ -297,7 +297,8 @@ void Mixer::set_output_card_internal(int card_index) configure_card(card_index, fake_capture, /*is_fake_capture=*/true, card->output.release()); card->queue_length_policy.reset(card_index); card->capture->start_bm_capture(); - card->output->start_output(bmdModeHD720p5994, pts_int); // FIXME + 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; } @@ -597,6 +598,13 @@ void Mixer::thread_func() if (desired_output_card_index != output_card_index) { set_output_card_internal(desired_output_card_index); } + if (output_card_index != -1 && + desired_output_video_mode != output_video_mode) { + DeckLinkOutput *output = cards[output_card_index].output.get(); + output->end_output(); + desired_output_video_mode = output_video_mode = output->pick_video_mode(desired_output_video_mode); + output->start_output(desired_output_video_mode, pts_int); + } CaptureCard::NewFrame new_frames[MAX_VIDEO_CARDS]; bool has_new_frame[MAX_VIDEO_CARDS] = { false }; @@ -1034,6 +1042,13 @@ void Mixer::start_mode_scanning(unsigned card_index) last_mode_scan_change[card_index] = steady_clock::now(); } +map Mixer::get_available_output_video_modes() const +{ + assert(desired_output_card_index != -1); + unique_lock lock(card_mutex); + return cards[desired_output_card_index].output->get_available_video_modes(); +} + Mixer::OutputChannel::~OutputChannel() { if (has_current_frame) { diff --git a/mixer.h b/mixer.h index d70c65b..bf388b1 100644 --- a/mixer.h +++ b/mixer.h @@ -312,6 +312,16 @@ public: desired_output_card_index = card_index; } + std::map get_available_output_video_modes() const; + + uint32_t get_output_video_mode() const { + return desired_output_video_mode; + } + + void set_output_video_mode(uint32_t mode) { + desired_output_video_mode = mode; + } + private: void configure_card(unsigned card_index, bmusb::CaptureInterface *capture, bool is_fake_capture, DeckLinkOutput *output); void set_output_card_internal(int card_index); // Should only be called from the mixer thread. @@ -338,13 +348,15 @@ private: std::atomic audio_source_channel{0}; std::atomic master_clock_channel{0}; // Gets overridden by if set. int output_card_index = -1; // -1 for none. + uint32_t output_video_mode = -1; - // The mechanics of changing the output card are so intricately connected + // The mechanics of changing the output card and modes are so intricately connected // with the work the mixer thread is doing. Thus, we don't change it directly, // we just set this variable instead, which signals to the mixer thread that // it should do the change before the next frame. This simplifies locking // considerations immensely. std::atomic desired_output_card_index{-1}; + std::atomic desired_output_video_mode{0}; std::unique_ptr display_chain; std::unique_ptr chroma_subsampler; @@ -359,7 +371,7 @@ private: // frame rate is integer, will always stay zero. unsigned fractional_samples = 0; - std::mutex card_mutex; + mutable std::mutex card_mutex; bool has_bmusb_thread = false; struct CaptureCard { std::unique_ptr capture;