X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=nageru%2Fdecklink_output.cpp;h=4c478fdf8e667353ef5de338a6cbd82a20df878b;hb=f34a3e1bbc207541842e0b54d5418d95bafc8e5b;hp=6b21edb0c45d066ebf3d8d68a6f8ed27410f4f8d;hpb=0eaa4d8a29782ae20d7b54fd8334371e724f1c57;p=nageru diff --git a/nageru/decklink_output.cpp b/nageru/decklink_output.cpp index 6b21edb..4c478fd 100644 --- a/nageru/decklink_output.cpp +++ b/nageru/decklink_output.cpp @@ -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,10 +131,11 @@ 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); @@ -151,7 +160,7 @@ void DeckLinkOutput::start_output(uint32_t mode, int64_t base_pts) fprintf(stderr, "Failed to set video output connection for card %u\n", card_index); 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"); abort(); } @@ -163,7 +172,7 @@ 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"); @@ -193,6 +202,15 @@ 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); @@ -207,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]{ @@ -236,23 +258,20 @@ 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 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(); } } - - if (output != nullptr) { - output->Release(); - output = nullptr; - } } void DeckLinkOutput::send_frame(GLuint y_tex, GLuint cbcr_tex, YCbCrLumaCoefficients output_ycbcr_coefficients, const vector &input_frames, int64_t pts, int64_t duration) @@ -278,7 +297,7 @@ void DeckLinkOutput::send_frame(GLuint y_tex, GLuint cbcr_tex, YCbCrLumaCoeffici } unique_ptr 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); @@ -291,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)); @@ -336,13 +355,20 @@ void DeckLinkOutput::send_audio(int64_t pts, const std::vector &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; @@ -423,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 &desired : vector>{ { 60000, 1001 }, { 60, 0 }, { 30000, 1001 } }) { + for (const pair &desired : vector>{ { 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; @@ -487,7 +513,8 @@ HRESULT DeckLinkOutput::ScheduledFrameCompleted(/* in */ IDeckLinkVideoFrame *co { lock_guard lock(frame_queue_mutex); frame_freelist.push(unique_ptr(frame)); - --num_frames_in_flight; + assert(scheduled_frames.count(frame)); + scheduled_frames.erase(frame); --metric_decklink_output_inflight_frames; } @@ -513,7 +540,7 @@ unique_ptr DeckLinkOutput::get_frame() unique_ptr 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); @@ -548,17 +575,15 @@ void DeckLinkOutput::present_thread_func() for ( ;; ) { unique_ptr frame; { - unique_lock 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 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 ( ;; ) { @@ -573,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); @@ -582,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 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; } } } @@ -660,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; @@ -669,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;