]> git.sesse.net Git - nageru/commitdiff
Support unsynchronized HDMI/SDI output.
authorSteinar H. Gunderson <steinar+nageru@gunderson.no>
Sat, 2 Oct 2021 14:54:58 +0000 (16:54 +0200)
committerSteinar H. Gunderson <steinar+nageru@gunderson.no>
Sat, 2 Oct 2021 14:54:58 +0000 (16:54 +0200)
This is for if you want just a monitor output without synchronizing
your entire stream chain to the output card (ie., you want to keep
some other camera as the master). Sound support is untested, and is
probably going to crackle a fair bit.

There's no GUI support for changing this currently.

nageru/decklink_output.cpp
nageru/decklink_output.h
nageru/flags.cpp
nageru/flags.h
nageru/glwidget.cpp
nageru/mixer.cpp
nageru/mixer.h

index 56a184e99b326cc6ab3107c985f59bf0345bd384..91fbd4095c23c94532823a4158e223845ae43b5c 100644 (file)
@@ -131,10 +131,11 @@ bool DeckLinkOutput::set_device(IDeckLink *decklink, IDeckLinkInput *input_arg)
        return true;
 }
 
-void DeckLinkOutput::start_output(uint32_t mode, int64_t base_pts)
+void DeckLinkOutput::start_output(uint32_t mode, int64_t base_pts, bool is_master_card_arg)
 {
        assert(output);
        assert(!playback_initiated);
+       this->is_master_card = is_master_card_arg;
 
        if (video_modes.empty()) {
                fprintf(stderr, "ERROR: No matching output modes for %dx%d found\n", width, height);
@@ -224,9 +225,13 @@ void DeckLinkOutput::start_output(uint32_t mode, int64_t base_pts)
                fprintf(stderr, "Couldn't enable audio output\n");
                abort();
        }
-       if (output->BeginAudioPreroll() != S_OK) {
-               fprintf(stderr, "Couldn't begin audio preroll\n");
-               abort();
+       if (is_master_card) {
+               if (output->BeginAudioPreroll() != S_OK) {
+                       fprintf(stderr, "Couldn't begin audio preroll\n");
+                       abort();
+               }
+       } else {
+               playback_started = true;
        }
 
        present_thread = thread([this]{
@@ -253,7 +258,9 @@ void DeckLinkOutput::end_output()
        present_thread.join();
        playback_initiated = false;
 
-       output->StopScheduledPlayback(0, nullptr, 0);
+       if (is_master_card) {
+               output->StopScheduledPlayback(0, nullptr, 0);
+       }
        output->DisableVideoOutput();
        output->DisableAudioOutput();
 
@@ -348,13 +355,20 @@ void DeckLinkOutput::send_audio(int64_t pts, const std::vector<float> &samples)
        }
 
        uint32_t frames_written;
-       HRESULT result = output->ScheduleAudioSamples(int_samples.get(), samples.size() / 2,
-               pts, TIMEBASE, &frames_written);
+       HRESULT result;
+       if (is_master_card) {
+               result = output->ScheduleAudioSamples(int_samples.get(), samples.size() / 2,
+                       pts, TIMEBASE, &frames_written);
+       } else {
+               result = output->WriteAudioSamplesSync(int_samples.get(), samples.size() / 2,
+                       &frames_written);
+       }
        if (result != S_OK) {
-               fprintf(stderr, "ScheduleAudioSamples(pts=%" PRId64 ") failed (result=0x%08x)\n", pts, result);
+               fprintf(stderr, "write audio to DeckLink (pts=%" PRId64 ") failed (result=0x%08x)\n", pts, result);
        } else {
-               if (frames_written != samples.size() / 2) {
-                       fprintf(stderr, "ScheduleAudioSamples() returned short write (%u/%zu)\n", frames_written, samples.size() / 2);
+               // Non-master card is not really synchronized on audio at all, so we don't warn on it.
+               if (frames_written != samples.size() / 2 && is_master_card) {
+                       fprintf(stderr, "write audio to DeckLink returned short write (%u/%zu)\n", frames_written, samples.size() / 2);
                }
        }
        metric_decklink_output_scheduled_samples += samples.size() / 2;
@@ -593,16 +607,24 @@ void DeckLinkOutput::present_thread_func()
                // Release any input frames we needed to render this frame.
                frame->input_frames.clear();
 
-               BMDTimeValue pts = frame->pts;
-               BMDTimeValue duration = frame->duration;
-               HRESULT res = output->ScheduleVideoFrame(frame.get(), pts, duration, TIMEBASE);
-               lock_guard<mutex> lock(frame_queue_mutex);
-               if (res == S_OK) {
-                       scheduled_frames.insert(frame.release());  // Owned by the driver now.
-                       ++metric_decklink_output_inflight_frames;
-               } else {
-                       fprintf(stderr, "Could not schedule video frame! (error=0x%08x)\n", res);
+               if (is_master_card) {
+                       BMDTimeValue pts = frame->pts;
+                       BMDTimeValue duration = frame->duration;
+                       HRESULT res = output->ScheduleVideoFrame(frame.get(), pts, duration, TIMEBASE);
+                       lock_guard<mutex> lock(frame_queue_mutex);
+                       if (res == S_OK) {
+                               scheduled_frames.insert(frame.release());  // Owned by the driver now.
+                               ++metric_decklink_output_inflight_frames;
+                       } else {
+                               fprintf(stderr, "Could not schedule video frame! (error=0x%08x)\n", res);
 
+                               frame_freelist.push(move(frame));
+                       }
+               } else {
+                       HRESULT res = output->DisplayVideoFrameSync(frame.get());
+                       if (res != S_OK) {
+                               fprintf(stderr, "Could not schedule video frame! (error=0x%08x)\n", res);
+                       }
                        frame_freelist.push(move(frame));
                }
        }
index f008dc3b32bec8f0c1aba542c4d3de3a8167b047..4aa76beca45df780d86d758401319cdeb7063005 100644 (file)
@@ -46,7 +46,7 @@ public:
        // where you get a freeze if querying an IDeckLinkInput interface
        // on an already-started card.
        bool set_device(IDeckLink *decklink, IDeckLinkInput *input_arg);
-       void start_output(uint32_t mode, int64_t base_pts);  // Mode comes from get_available_video_modes().
+       void start_output(uint32_t mode, int64_t base_pts, bool is_master_card);  // Mode comes from get_available_video_modes().
        void end_output();
 
        void send_frame(GLuint y_tex, GLuint cbcr_tex, movit::YCbCrLumaCoefficients ycbcr_coefficients, const std::vector<RefCountedFrame> &input_frames, int64_t pts, int64_t duration);
@@ -143,7 +143,7 @@ private:
        std::unordered_set<Frame *> scheduled_frames;  // Owned by the driver, so no unique_ptr. Under <frame_queue_mutex>.
 
        std::condition_variable frame_queues_changed;
-       bool playback_initiated = false, playback_started = false;
+       bool playback_initiated = false, playback_started = false, is_master_card = false;
        int64_t base_pts, frame_duration;
        BMDDisplayModeFlags current_mode_flags = 0;
        bool last_frame_had_mode_mismatch = false;
index ed35062427ec01330bc26fa7bcf4cbfaf9a0ff23..42fdb88b22a1b2639d8cf7c954b1284f7ec7ed8d 100644 (file)
@@ -69,6 +69,7 @@ enum LongOption {
        OPTION_OUTPUT_YCBCR_COEFFICIENTS,
        OPTION_OUTPUT_BUFFER_FRAMES,
        OPTION_OUTPUT_SLOP_FRAMES,
+       OPTION_OUTPUT_CARD_UNSYNCHRONIZED,
        OPTION_TIMECODE_STREAM,
        OPTION_TIMECODE_STDOUT,
        OPTION_QUICK_CUT_KEYS,
@@ -230,6 +231,8 @@ void usage(Program program)
                fprintf(stderr, "      --output-slop-frames=NUM    if more less than this number of frames behind for\n");
                fprintf(stderr, "                                    --output-card, try to submit anyway instead of\n");
                fprintf(stderr, "                                    dropping the frame (default 0.5)\n");
+               fprintf(stderr, "      --output-card-unsynchronized  if --output-card is in use, do _not_ use it as\n");
+               fprintf(stderr, "                                  master clock (may give jittery output and audio breakups)\n");
                fprintf(stderr, "      --timecode-stream           show timestamp and timecode in stream\n");
                fprintf(stderr, "      --timecode-stdout           show timestamp and timecode on standard output\n");
                fprintf(stderr, "      --quick-cut-keys            enable direct cutting by Q, W, E, ... keys\n");
@@ -315,6 +318,7 @@ void parse_flags(Program program, int argc, char * const argv[])
                { "output-ycbcr-coefficients", required_argument, 0, OPTION_OUTPUT_YCBCR_COEFFICIENTS },
                { "output-buffer-frames", required_argument, 0, OPTION_OUTPUT_BUFFER_FRAMES },
                { "output-slop-frames", required_argument, 0, OPTION_OUTPUT_SLOP_FRAMES },
+               { "output-card-unsynchronized", no_argument, 0, OPTION_OUTPUT_CARD_UNSYNCHRONIZED },
                { "timecode-stream", no_argument, 0, OPTION_TIMECODE_STREAM },
                { "timecode-stdout", no_argument, 0, OPTION_TIMECODE_STDOUT },
                { "quick-cut-keys", no_argument, 0, OPTION_QUICK_CUT_KEYS },
@@ -549,6 +553,9 @@ void parse_flags(Program program, int argc, char * const argv[])
                case OPTION_OUTPUT_SLOP_FRAMES:
                        global_flags.output_slop_frames = atof(optarg);
                        break;
+               case OPTION_OUTPUT_CARD_UNSYNCHRONIZED:
+                       global_flags.output_card_is_master = false;
+                       break;
                case OPTION_TIMECODE_STREAM:
                        global_flags.display_timecode_in_stream = true;
                        break;
index 9b9976c5db7c572027c7ad314e5196b3d9f644ab..734e290420bb995652ecfd9910003a7f40c57e82 100644 (file)
@@ -64,6 +64,7 @@ struct Flags {
        int output_card = -1;
        double output_buffer_frames = 6.0;
        double output_slop_frames = 0.5;
+       bool output_card_is_master = true;
        int max_input_queue_frames = 6;
        int http_port = DEFAULT_HTTPD_PORT;
        int srt_port = DEFAULT_SRT_PORT;  // -1 for none.
index f5f350a6865188194f7bf58c7a778521cd0dad92..6ff304c831337b2e6db0b4a88eb6351248f3ba32 100644 (file)
@@ -378,7 +378,7 @@ void GLWidget::show_preview_context_menu(unsigned signal_num, const QPoint &pos)
        // And a master clock selector.
        QAction *master_clock_action = new QAction("Use as master clock", &menu);
        master_clock_action->setCheckable(true);
-       if (global_mixer->get_output_card_index() != -1) {
+       if (global_mixer->get_output_card_index() != -1 && global_mixer->get_output_card_is_master()) {
                master_clock_action->setChecked(false);
                master_clock_action->setEnabled(false);
        } else if (global_mixer->get_master_clock() == signal_num) {
index 3a137d68a9b0d39b2a52388f699be0b9fa004c4b..5de75c6db2d826c66bbd050e819362d7ca5eeac9 100644 (file)
@@ -543,6 +543,7 @@ Mixer::Mixer(const QSurfaceFormat &format)
        if (global_flags.enable_alsa_output) {
                alsa.reset(new ALSAOutput(OUTPUT_FREQUENCY, /*num_channels=*/2));
        }
+       output_card_is_master = global_flags.output_card_is_master;
        if (global_flags.output_card != -1) {
                desired_output_card_index = global_flags.output_card;
                set_output_card_internal(global_flags.output_card);
@@ -899,7 +900,7 @@ void Mixer::set_output_card_internal(int card_index)
                card->jitter_history.clear();
                card->capture->start_bm_capture();
                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);
+               card->output->start_output(desired_output_video_mode, pts_int, /*is_master_card=*/output_card_is_master);
        }
        output_card_index = card_index;
        output_jitter_history.clear();
@@ -1286,7 +1287,7 @@ void Mixer::thread_func()
                        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);
+                       output->start_output(desired_output_video_mode, pts_int, /*is_master_card=*/output_card_is_master);
                }
 
                {
@@ -1299,7 +1300,7 @@ void Mixer::thread_func()
 
                bool master_card_is_output;
                unsigned master_card_index;
-               if (output_card_index != -1) {
+               if (output_card_index != -1 && output_card_is_master) {
                        master_card_is_output = true;
                        master_card_index = output_card_index;
                } else {
@@ -1406,7 +1407,7 @@ void Mixer::thread_func()
 
 bool Mixer::input_card_is_master_clock(unsigned card_index, unsigned master_card_index) const
 {
-       if (output_card_index != -1) {
+       if (output_card_index != -1 && output_card_is_master) {
                // The output card (ie., cards[output_card_index].output) is the master clock,
                // so no input card (ie., cards[card_index].capture) is.
                return false;
index 6e9da90c3c0e1a27bc830a18acd1407cf6cfc1f0..99385faeffc00430968ecbc1a40bc40acc286dfa 100644 (file)
@@ -326,6 +326,10 @@ public:
                desired_output_card_index = card_index;
        }
 
+       bool get_output_card_is_master() const {
+               return output_card_is_master;
+       }
+
        std::map<uint32_t, bmusb::VideoMode> get_available_output_video_modes() const;
 
        uint32_t get_output_video_mode() const {
@@ -396,9 +400,10 @@ private:
        std::unique_ptr<movit::ResourcePool> resource_pool;
        std::unique_ptr<Theme> theme;
        std::atomic<unsigned> audio_source_channel{0};
-       std::atomic<int> master_clock_channel{0};  // Gets overridden by <output_card_index> if set.
+       std::atomic<int> master_clock_channel{0};  // Gets overridden by <output_card_index> if output_card_is_master == true.
        int output_card_index = -1;  // -1 for none.
        uint32_t output_video_mode = -1;
+       bool output_card_is_master = false;  // Only relevant if output_card_index != -1.
 
        // 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,