]> git.sesse.net Git - nageru/blobdiff - decklink_output.cpp
Make it possible to choose the DeckLink output video mode through the GUI.
[nageru] / decklink_output.cpp
index 7fa689e547610c0fb14696349a87f3e445e42422..55d29c5aad38c8d5be43f30ef677a2c9bb9ef032 100644 (file)
@@ -1,6 +1,7 @@
 #include <movit/effect_util.h>
 #include <movit/util.h>
 #include <movit/resource_pool.h>  // Must be above the Xlib includes.
+#include <pthread.h>
 
 #include <epoxy/egl.h>
 
@@ -63,8 +64,10 @@ void DeckLinkOutput::set_device(IDeckLink *decklink)
 void DeckLinkOutput::start_output(uint32_t mode, int64_t base_pts)
 {
        assert(output);
+       assert(!playback_initiated);
 
        should_quit = false;
+       playback_initiated = true;
        playback_started = false;
        this->base_pts = base_pts;
 
@@ -153,9 +156,14 @@ void DeckLinkOutput::start_output(uint32_t mode, int64_t base_pts)
 
 void DeckLinkOutput::end_output()
 {
+       if (!playback_initiated) {
+               return;
+       }
+
        should_quit = true;
        frame_queues_changed.notify_all();
        present_thread.join();
+       playback_initiated = false;
 
        output->StopScheduledPlayback(0, nullptr, 0);
        output->DisableVideoOutput();
@@ -173,6 +181,8 @@ 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)
 {
+       assert(!should_quit);
+
        unique_ptr<Frame> frame = move(get_frame());
        chroma_subsampler->create_uyvy(y_tex, cbcr_tex, width, height, frame->uyvy_tex);
 
@@ -232,8 +242,10 @@ 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)
+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);
+
        *dropped_frames = 0;
        *frame_duration = this->frame_duration;
 
@@ -243,9 +255,12 @@ void DeckLinkOutput::wait_for_frame(int64_t pts, int *dropped_frames, int64_t *f
 
        // While prerolling, we send out frames as quickly as we can.
        if (target_time < base_pts) {
+               *is_preroll = true;
                return;
        }
 
+       *is_preroll = !playback_started;
+
        if (!playback_started) {
                if (output->EndAudioPreroll() != S_OK) {
                        fprintf(stderr, "Could not end audio preroll\n");
@@ -262,11 +277,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;
        }
 
@@ -281,9 +297,38 @@ 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;
+}
+
 HRESULT DeckLinkOutput::ScheduledFrameCompleted(/* in */ IDeckLinkVideoFrame *completedFrame, /* in */ BMDOutputFrameCompletionResult result)
 {
        Frame *frame = static_cast<Frame *>(completedFrame);
@@ -354,6 +399,7 @@ unique_ptr<DeckLinkOutput::Frame> DeckLinkOutput::get_frame()
 
 void DeckLinkOutput::present_thread_func()
 {
+       pthread_setname_np(pthread_self(), "DeckLinkOutput");
        for ( ;; ) {
                unique_ptr<Frame> frame;
                {