]> git.sesse.net Git - nageru/commitdiff
Make it possible to choose the DeckLink output video mode through the GUI.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Wed, 8 Feb 2017 20:38:12 +0000 (21:38 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Wed, 8 Feb 2017 21:05:52 +0000 (22:05 +0100)
decklink_output.cpp
decklink_output.h
glwidget.cpp
mixer.cpp
mixer.h

index 38f44ee96c4d614e2d9ed2729ad2cc42e185eec8..55d29c5aad38c8d5be43f30ef677a2c9bb9ef032 100644 (file)
@@ -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<int, int> &desired : vector<pair<int, int>>{ { 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<Frame *>(completedFrame);
index d3bafcae60ae46b78521cdfc057e366f866afece..d5e743aa2e801cc91d1156f082c2fe15e34a17ef 100644 (file)
@@ -56,6 +56,9 @@ public:
        // Analogous to CaptureInterface. Will only return modes that have the right width/height.
        std::map<uint32_t, bmusb::VideoMode> 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;
index 9aba1b3d61aa144d1f1d7c8ebc1cc832751c5b22..34943389e6aa0b3429ea8d8073f1a374a6ea9e46 100644 (file)
@@ -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<QVariant>{"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<QVariant>{"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<uint32_t, bmusb::VideoMode> 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<QVariant>{"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<QVariant> 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);
+               }
        }
 }
 
index b28da27e90f3781180dab3068b433135b739a1f2..f82650ea906948667f242e2b0941e0bba32aaed4 100644 (file)
--- 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<uint32_t, bmusb::VideoMode> Mixer::get_available_output_video_modes() const
+{
+       assert(desired_output_card_index != -1);
+       unique_lock<mutex> 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 d70c65b8a70ec71e900914119316535fac26772a..bf388b1cece6edf6f1b98928ba70c023a784e535 100644 (file)
--- a/mixer.h
+++ b/mixer.h
@@ -312,6 +312,16 @@ public:
                desired_output_card_index = card_index;
        }
 
+       std::map<uint32_t, bmusb::VideoMode> 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<unsigned> audio_source_channel{0};
        std::atomic<int> master_clock_channel{0};  // Gets overridden by <output_card_index> 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<int> desired_output_card_index{-1};
+       std::atomic<uint32_t> desired_output_video_mode{0};
 
        std::unique_ptr<movit::EffectChain> display_chain;
        std::unique_ptr<ChromaSubsampler> 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<bmusb::CaptureInterface> capture;