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);
// 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;
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();
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);
+ }
}
}
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;
}
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 };
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) {
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.
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;
// 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;