]> git.sesse.net Git - nageru/blobdiff - nageru/decklink_output.cpp
Collapse all the 10-bit flags.
[nageru] / nageru / decklink_output.cpp
index 28f433a8872945b3191608cc7283eea5fea4669e..4c478fdf8e667353ef5de338a6cbd82a20df878b 100644 (file)
@@ -12,9 +12,9 @@
 #include "decklink_output.h"
 #include "decklink_util.h"
 #include "flags.h"
-#include "metrics.h"
+#include "shared/metrics.h"
 #include "print_latency.h"
-#include "timebase.h"
+#include "shared/timebase.h"
 #include "v210_converter.h"
 
 using namespace movit;
@@ -82,8 +82,16 @@ DeckLinkOutput::DeckLinkOutput(ResourcePool *resource_pool, QSurface *surface, u
        });
 }
 
-bool DeckLinkOutput::set_device(IDeckLink *decklink)
+DeckLinkOutput::~DeckLinkOutput()
 {
+       if (output != nullptr) {
+               output->Release();
+       }
+}
+
+bool DeckLinkOutput::set_device(IDeckLink *decklink, IDeckLinkInput *input_arg)
+{
+       input = input_arg;
        if (decklink->QueryInterface(IID_IDeckLinkOutput, (void**)&output) != S_OK) {
                fprintf(stderr, "Warning: Card %u has no outputs\n", card_index);
                return false;
@@ -123,14 +131,15 @@ bool DeckLinkOutput::set_device(IDeckLink *decklink)
        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);
-               exit(1);
+               abort();
        }
 
        should_quit.unquit();
@@ -141,19 +150,19 @@ void DeckLinkOutput::start_output(uint32_t mode, int64_t base_pts)
        IDeckLinkConfiguration *config = nullptr;
        if (output->QueryInterface(IID_IDeckLinkConfiguration, (void**)&config) != S_OK) {
                fprintf(stderr, "Failed to get configuration interface for output card\n");
-               exit(1);
+               abort();
        }
        if (config->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true) != S_OK) {
                fprintf(stderr, "Failed to set low latency output\n");
-               exit(1);
+               abort();
        }
        if (config->SetInt(bmdDeckLinkConfigVideoOutputConnection, video_connection) != S_OK) {
                fprintf(stderr, "Failed to set video output connection for card %u\n", card_index);
-               exit(1);
+               abort();
        }
-       if (config->SetFlag(bmdDeckLinkConfigUse1080pNotPsF, true) != S_OK) {
+       if (config->SetFlag(bmdDeckLinkConfigOutput1080pAsPsF, true) != S_OK) {
                fprintf(stderr, "Failed to set PsF flag for card\n");
-               exit(1);
+               abort();
        }
        if (config->SetFlag(bmdDeckLinkConfigSMPTELevelAOutput, true) != S_OK) {
                // This affects at least some no-name SDI->HDMI converters.
@@ -163,16 +172,16 @@ void DeckLinkOutput::start_output(uint32_t mode, int64_t base_pts)
 
        BMDDisplayModeSupport support;
        IDeckLinkDisplayMode *display_mode;
-       BMDPixelFormat pixel_format = global_flags.ten_bit_output ? bmdFormat10BitYUV : bmdFormat8BitYUV;
+       BMDPixelFormat pixel_format = global_flags.bit_depth > 8 ? bmdFormat10BitYUV : bmdFormat8BitYUV;
        if (output->DoesSupportVideoMode(mode, pixel_format, bmdVideoOutputFlagDefault,
                                         &support, &display_mode) != S_OK) {
                fprintf(stderr, "Couldn't ask for format support\n");
-               exit(1);
+               abort();
        }
 
        if (support == bmdDisplayModeNotSupported) {
                fprintf(stderr, "Requested display mode not supported\n");
-               exit(1);
+               abort();
        }
 
        current_mode_flags = display_mode->GetFlags();
@@ -181,7 +190,7 @@ void DeckLinkOutput::start_output(uint32_t mode, int64_t base_pts)
        BMDTimeScale time_scale;
        if (display_mode->GetFrameRate(&time_value, &time_scale) != S_OK) {
                fprintf(stderr, "Couldn't get frame rate\n");
-               exit(1);
+               abort();
        }
 
        metric_decklink_output_width_pixels = width;
@@ -193,23 +202,36 @@ void DeckLinkOutput::start_output(uint32_t mode, int64_t base_pts)
 
        display_mode->Release();
 
+       if (input != nullptr) {
+               if (input->DisableVideoInput() != S_OK) {
+                       fprintf(stderr, "Warning: Failed to disable video input for card %d\n", card_index);
+               }
+               if (input->DisableAudioInput() != S_OK) {
+                       fprintf(stderr, "Warning: Failed to disable audio input for card %d\n", card_index);
+               }
+       }
+
        HRESULT result = output->EnableVideoOutput(mode, bmdVideoOutputFlagDefault);
        if (result != S_OK) {
                fprintf(stderr, "Couldn't enable output with error 0x%x\n", result);
-               exit(1);
+               abort();
        }
        if (output->SetScheduledFrameCompletionCallback(this) != S_OK) {
                fprintf(stderr, "Couldn't set callback\n");
-               exit(1);
+               abort();
        }
        assert(OUTPUT_FREQUENCY == 48000);
        if (output->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped) != S_OK) {
                fprintf(stderr, "Couldn't enable audio output\n");
-               exit(1);
+               abort();
        }
-       if (output->BeginAudioPreroll() != S_OK) {
-               fprintf(stderr, "Couldn't begin audio preroll\n");
-               exit(1);
+       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]{
@@ -218,7 +240,7 @@ void DeckLinkOutput::start_output(uint32_t mode, int64_t base_pts)
                if (!make_current(context, this->surface)) {
                        printf("display=%p surface=%p context=%p curr=%p err=%d\n", eglGetCurrentDisplay(), this->surface, context, eglGetCurrentContext(),
                                eglGetError());
-                       exit(1);
+                       abort();
                }
                present_thread_func();
                delete_context(context);
@@ -236,14 +258,16 @@ 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();
 
        // Wait until all frames are accounted for, and free them.
        {
                unique_lock<mutex> lock(frame_queue_mutex);
-               while (!(frame_freelist.empty() && num_frames_in_flight == 0)) {
+               while (!(frame_freelist.empty() && scheduled_frames.empty())) {
                        frame_queues_changed.wait(lock, [this]{ return !frame_freelist.empty(); });
                        frame_freelist.pop();
                }
@@ -273,7 +297,7 @@ void DeckLinkOutput::send_frame(GLuint y_tex, GLuint cbcr_tex, YCbCrLumaCoeffici
        }
 
        unique_ptr<Frame> frame = get_frame();
-       if (global_flags.ten_bit_output) {
+       if (global_flags.bit_depth > 8) {
                chroma_subsampler->create_v210(y_tex, cbcr_tex, width, height, frame->uyvy_tex);
        } else {
                chroma_subsampler->create_uyvy(y_tex, cbcr_tex, width, height, frame->uyvy_tex);
@@ -286,7 +310,7 @@ void DeckLinkOutput::send_frame(GLuint y_tex, GLuint cbcr_tex, YCbCrLumaCoeffici
        glBindBuffer(GL_PIXEL_PACK_BUFFER, frame->pbo);
        check_error();
 
-       if (global_flags.ten_bit_output) {
+       if (global_flags.bit_depth > 8) {
                glBindTexture(GL_TEXTURE_2D, frame->uyvy_tex);
                check_error();
                glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, BUFFER_OFFSET(0));
@@ -317,7 +341,7 @@ void DeckLinkOutput::send_frame(GLuint y_tex, GLuint cbcr_tex, YCbCrLumaCoeffici
        frame->duration = duration;
 
        {
-               unique_lock<mutex> lock(frame_queue_mutex);
+               lock_guard<mutex> lock(frame_queue_mutex);
                pending_video_frames.push(move(frame));
        }
        frame_queues_changed.notify_all();
@@ -331,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=%ld) 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/%ld)\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;
@@ -366,11 +397,11 @@ void DeckLinkOutput::wait_for_frame(int64_t pts, int *dropped_frames, int64_t *f
        if (!playback_started) {
                if (output->EndAudioPreroll() != S_OK) {
                        fprintf(stderr, "Could not end audio preroll\n");
-                       exit(1);  // TODO
+                       abort();  // TODO
                }
                if (output->StartScheduledPlayback(base_pts, TIMEBASE, 1.0) != S_OK) {
                        fprintf(stderr, "Could not start playback\n");
-                       exit(1);  // TODO
+                       abort();  // TODO
                }
                playback_started = true;
        }
@@ -418,7 +449,7 @@ uint32_t DeckLinkOutput::pick_video_mode(uint32_t mode) const
        }
 
        // 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 pair<int, int> &desired : vector<pair<int, int>>{ { 60000, 1001 }, { 60, 1 }, { 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;
@@ -457,17 +488,17 @@ HRESULT DeckLinkOutput::ScheduledFrameCompleted(/* in */ IDeckLinkVideoFrame *co
                ++metric_decklink_output_completed_frames_completed;
                break;
        case bmdOutputFrameDisplayedLate:
-               fprintf(stderr, "Output frame displayed late (pts=%ld)\n", frame->pts);
+               fprintf(stderr, "Output frame displayed late (pts=%" PRId64 ")\n", frame->pts);
                fprintf(stderr, "Consider increasing --output-buffer-frames if this persists.\n");
                ++metric_decklink_output_completed_frames_late;
                break;
        case bmdOutputFrameDropped:
-               fprintf(stderr, "Output frame was dropped (pts=%ld)\n", frame->pts);
+               fprintf(stderr, "Output frame was dropped (pts=%" PRId64 ")\n", frame->pts);
                fprintf(stderr, "Consider increasing --output-buffer-frames if this persists.\n");
                ++metric_decklink_output_completed_frames_dropped;
                break;
        case bmdOutputFrameFlushed:
-               fprintf(stderr, "Output frame was flushed (pts=%ld)\n", frame->pts);
+               fprintf(stderr, "Output frame was flushed (pts=%" PRId64 ")\n", frame->pts);
                ++metric_decklink_output_completed_frames_flushed;
                break;
        default:
@@ -482,7 +513,8 @@ HRESULT DeckLinkOutput::ScheduledFrameCompleted(/* in */ IDeckLinkVideoFrame *co
        {
                lock_guard<mutex> lock(frame_queue_mutex);
                frame_freelist.push(unique_ptr<Frame>(frame));
-               --num_frames_in_flight;
+               assert(scheduled_frames.count(frame));
+               scheduled_frames.erase(frame);
                --metric_decklink_output_inflight_frames;
        }
 
@@ -508,7 +540,7 @@ unique_ptr<DeckLinkOutput::Frame> DeckLinkOutput::get_frame()
        unique_ptr<Frame> frame(new Frame);
 
        size_t stride;
-       if (global_flags.ten_bit_output) {
+       if (global_flags.bit_depth > 8) {
                stride = v210Converter::get_v210_stride(width);
                GLint v210_width = stride / sizeof(uint32_t);
                frame->uyvy_tex = resource_pool->create_2d_texture(GL_RGB10_A2, v210_width, height);
@@ -543,17 +575,15 @@ void DeckLinkOutput::present_thread_func()
        for ( ;; ) {
                unique_ptr<Frame> frame;
                {
-                        unique_lock<mutex> lock(frame_queue_mutex);
-                        frame_queues_changed.wait(lock, [this]{
-                                return should_quit.should_quit() || !pending_video_frames.empty();
-                        });
-                        if (should_quit.should_quit()) {
+                       unique_lock<mutex> lock(frame_queue_mutex);
+                       frame_queues_changed.wait(lock, [this]{
+                               return should_quit.should_quit() || !pending_video_frames.empty();
+                       });
+                       if (should_quit.should_quit()) {
                                return;
                        }
                        frame = move(pending_video_frames.front());
                        pending_video_frames.pop();
-                       ++num_frames_in_flight;
-                       ++metric_decklink_output_inflight_frames;
                }
 
                for ( ;; ) {
@@ -568,7 +598,7 @@ void DeckLinkOutput::present_thread_func()
                check_error();
                frame->fence.reset();
 
-               if (global_flags.ten_bit_output) {
+               if (global_flags.bit_depth > 8) {
                        memcpy(frame->uyvy_ptr_local.get(), frame->uyvy_ptr, v210Converter::get_v210_stride(width) * height);
                } else {
                        memcpy(frame->uyvy_ptr_local.get(), frame->uyvy_ptr, width * height * 2);
@@ -577,18 +607,25 @@ 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);
-               if (res == S_OK) {
-                       frame.release();  // Owned by the driver now.
-               } 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));
-                       --num_frames_in_flight;
-                       --metric_decklink_output_inflight_frames;
                }
        }
 }
@@ -655,7 +692,7 @@ long DeckLinkOutput::Frame::GetHeight()
 
 long DeckLinkOutput::Frame::GetRowBytes()
 {
-       if (global_flags.ten_bit_output) {
+       if (global_flags.bit_depth > 8) {
                return v210Converter::get_v210_stride(global_flags.width);
        } else {
                return global_flags.width * 2;
@@ -664,7 +701,7 @@ long DeckLinkOutput::Frame::GetRowBytes()
 
 BMDPixelFormat DeckLinkOutput::Frame::GetPixelFormat()
 {
-       if (global_flags.ten_bit_output) {
+       if (global_flags.bit_depth > 8) {
                return bmdFormat10BitYUV;
        } else {
                return bmdFormat8BitYUV;