]> git.sesse.net Git - nageru/blobdiff - decklink_output.cpp
Support switching Y'CbCr coefficients midway, which will allow doing the Right Thing...
[nageru] / decklink_output.cpp
index a826dfe22ee95c5588f86edebec1c2ad1ce64ed3..8c6672bb8e89d7fcca7eade4badae1035659f026 100644 (file)
@@ -88,6 +88,11 @@ void DeckLinkOutput::start_output(uint32_t mode, int64_t base_pts)
                fprintf(stderr, "Failed to set PsF flag for card\n");
                exit(1);
        }
+       if (config->SetFlag(bmdDeckLinkConfigSMPTELevelAOutput, true) != S_OK) {
+               // This affects at least some no-name SDI->HDMI converters.
+               // Warn, but don't die.
+               fprintf(stderr, "WARNING: Failed to enable SMTPE Level A; resolutions like 1080p60 might have issues.\n");
+       }
 
        BMDDisplayModeSupport support;
        IDeckLinkDisplayMode *display_mode;
@@ -102,14 +107,7 @@ void DeckLinkOutput::start_output(uint32_t mode, int64_t base_pts)
                exit(1);
        }
 
-       BMDDisplayModeFlags flags = display_mode->GetFlags();
-       if ((flags & bmdDisplayModeColorspaceRec601) && global_flags.ycbcr_rec709_coefficients) {
-               fprintf(stderr, "WARNING: Chosen output mode expects Rec. 601 Y'CbCr coefficients.\n");
-               fprintf(stderr, "         Consider --output-ycbcr-coefficients=rec601 (or =auto).\n");
-       } else if ((flags & bmdDisplayModeColorspaceRec709) && !global_flags.ycbcr_rec709_coefficients) {
-               fprintf(stderr, "WARNING: Chosen output mode expects Rec. 709 Y'CbCr coefficients.\n");
-               fprintf(stderr, "         Consider --output-ycbcr-coefficients=rec709 (or =auto).\n");
-       }
+       current_mode_flags = display_mode->GetFlags();
 
        BMDTimeValue time_value;
        BMDTimeScale time_scale;
@@ -179,10 +177,26 @@ void DeckLinkOutput::end_output()
        }
 }
 
-void DeckLinkOutput::send_frame(GLuint y_tex, GLuint cbcr_tex, const vector<RefCountedFrame> &input_frames, int64_t pts, int64_t duration)
+void DeckLinkOutput::send_frame(GLuint y_tex, GLuint cbcr_tex, YCbCrLumaCoefficients output_ycbcr_coefficients, const vector<RefCountedFrame> &input_frames, int64_t pts, int64_t duration)
 {
        assert(!should_quit);
 
+       if ((current_mode_flags & bmdDisplayModeColorspaceRec601) && output_ycbcr_coefficients == YCBCR_REC_709) {
+               if (!last_frame_had_mode_mismatch) {
+                       fprintf(stderr, "WARNING: Chosen output mode expects Rec. 601 Y'CbCr coefficients.\n");
+                       fprintf(stderr, "         Consider --output-ycbcr-coefficients=rec601 (or =auto).\n");
+               }
+               last_frame_had_mode_mismatch = true;
+       } else if ((current_mode_flags & bmdDisplayModeColorspaceRec709) && output_ycbcr_coefficients == YCBCR_REC_601) {
+               if (!last_frame_had_mode_mismatch) {
+                       fprintf(stderr, "WARNING: Chosen output mode expects Rec. 709 Y'CbCr coefficients.\n");
+                       fprintf(stderr, "         Consider --output-ycbcr-coefficients=rec709 (or =auto).\n");
+               }
+               last_frame_had_mode_mismatch = true;
+       } else {
+               last_frame_had_mode_mismatch = false;
+       }
+
        unique_ptr<Frame> frame = move(get_frame());
        chroma_subsampler->create_uyvy(y_tex, cbcr_tex, width, height, frame->uyvy_tex);
 
@@ -242,7 +256,7 @@ void DeckLinkOutput::send_audio(int64_t pts, const std::vector<float> &samples)
        }
 }
 
-void DeckLinkOutput::wait_for_frame(int64_t pts, int *dropped_frames, int64_t *frame_duration, bool *is_preroll)
+void DeckLinkOutput::wait_for_frame(int64_t pts, int *dropped_frames, int64_t *frame_duration, bool *is_preroll, steady_clock::time_point *frame_timestamp)
 {
        assert(!should_quit);
 
@@ -277,11 +291,12 @@ void DeckLinkOutput::wait_for_frame(int64_t pts, int *dropped_frames, int64_t *f
        double playback_speed;
        output->GetScheduledStreamTime(TIMEBASE, &stream_frame_time, &playback_speed);
 
+       *frame_timestamp = steady_clock::now() +
+               nanoseconds((target_time - stream_frame_time) * 1000000000 / TIMEBASE);
+
        // If we're ahead of time, wait for the frame to (approximately) start.
        if (stream_frame_time < target_time) {
-               steady_clock::time_point t = steady_clock::now() +
-                       nanoseconds((target_time - stream_frame_time) * 1000000000 / TIMEBASE);
-               this_thread::sleep_until(t);
+               this_thread::sleep_until(*frame_timestamp);
                return;
        }
 
@@ -296,9 +311,49 @@ void DeckLinkOutput::wait_for_frame(int64_t pts, int *dropped_frames, int64_t *f
        // Oops, we missed by more than one frame. Return immediately,
        // but drop so that we catch up.
        *dropped_frames = (stream_frame_time - target_time + *frame_duration - 1) / *frame_duration;
+       const int64_t ns_per_frame = this->frame_duration * 1000000000 / TIMEBASE;
+       *frame_timestamp += nanoseconds(*dropped_frames * ns_per_frame);
        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;
+}
+
+YCbCrLumaCoefficients DeckLinkOutput::preferred_ycbcr_coefficients() const
+{
+       if (current_mode_flags & bmdDisplayModeColorspaceRec601) {
+               return YCBCR_REC_601;
+       } else {
+               // Don't bother checking bmdDisplayModeColorspaceRec709;
+               // if none is set, 709 is a good default anyway.
+               return YCBCR_REC_709;
+       }
+}
+
 HRESULT DeckLinkOutput::ScheduledFrameCompleted(/* in */ IDeckLinkVideoFrame *completedFrame, /* in */ BMDOutputFrameCompletionResult result)
 {
        Frame *frame = static_cast<Frame *>(completedFrame);
@@ -321,8 +376,8 @@ HRESULT DeckLinkOutput::ScheduledFrameCompleted(/* in */ IDeckLinkVideoFrame *co
                break;
        }
 
-       static int hei = 0;
-       print_latency("DeckLink output latency (frame received → output on HDMI):", frame->received_ts, false, &hei);
+       static int frameno = 0;
+       print_latency("DeckLink output latency (frame received → output on HDMI):", frame->received_ts, false, &frameno);
 
        {
                lock_guard<mutex> lock(frame_queue_mutex);