From e489a730b888115cf7de7325fa25865a601c9899 Mon Sep 17 00:00:00 2001 From: JohannesItten Date: Thu, 9 Jul 2026 23:35:31 +0300 Subject: [PATCH] decklink in timing fix --- nodes/decklinkin/main.cpp | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/nodes/decklinkin/main.cpp b/nodes/decklinkin/main.cpp index 78990ea..a7596f6 100644 --- a/nodes/decklinkin/main.cpp +++ b/nodes/decklinkin/main.cpp @@ -95,23 +95,29 @@ class DeckLinkInNode : public dmf::NodeBase { std::vector audio_buf(static_cast(max_audio_samples) * static_cast(channels)); // --- Clock --- - const mxlRational video_rate = {fps_num, fps_den}; - const mxlRational audio_rate = {receiver.audio_info.sample_rate, 1}; - uint64_t video_index = mxlGetCurrentIndex(&video_rate); - uint64_t audio_index = has_audio ? mxlGetCurrentIndex(&audio_rate) : 0; - log("start video_index=%llu", static_cast(video_index)); + // video_index is determined AFTER each hardware frame arrives so it reflects + // the actual TAI slot the frame landed in. wait_for_frame() is the natural + // pacer — no separate sleep needed. + const mxlRational video_rate = {fps_num, fps_den}; + const mxlRational audio_rate = {receiver.audio_info.sample_rate, 1}; + uint64_t audio_index = has_audio ? mxlGetCurrentIndex(&audio_rate) : 0; + uint64_t frame_count = 0, drop_count = 0; + log("ready, waiting for first frame..."); // --- Capture loop --- while (dmf::g_running.load(std::memory_order_relaxed)) { int samples_written = 0; - // DeckLink delivers one video frame + accompanying audio per callback. + // Blocks until DeckLink hardware delivers a frame — this IS the pacing. if (!receiver.wait_for_frame( frame_buf.data(), video_stride, width, height, (has_audio && audio_writer) ? audio_buf.data() : nullptr, max_audio_samples, (has_audio && audio_writer) ? &samples_written : nullptr)) break; + // Resolve TAI index now — after the frame arrived, not before. + const uint64_t video_index = mxlGetCurrentIndex(&video_rate); + // Video grain mxlGrainInfo grain{}; uint8_t* video_buf_ptr = nullptr; @@ -121,18 +127,26 @@ class DeckLinkInNode : public dmf::NodeBase { grain.flags = 0; grain.validSlices = grain.totalSlices; mxlFlowWriterCommitGrain(video_writer, &grain); + frame_count++; + if (frame_count % 25 == 0) + log("heartbeat frames=%llu drops=%llu index=%llu", + frame_count, drop_count, video_index); + } else { + drop_count++; + log("OpenGrain failed (%s) at index=%llu drops=%llu", + dmf::mxl_status_str(vst), video_index, drop_count); } - // Audio samples (same fragment-wrap pattern as videoin) + // Audio samples if (has_audio && audio_writer && samples_written > 0) { mxlMutableWrappedMultiBufferSlice slice{}; mxlStatus ast = mxlFlowWriterOpenSamples( audio_writer, audio_index, static_cast(samples_written), &slice); if (ast == MXL_STATUS_OK) { for (int ch = 0; ch < channels; ++ch) { - const uint8_t* src = reinterpret_cast( + const uint8_t* src = reinterpret_cast( audio_buf.data() + ch * max_audio_samples); - uint8_t* dst0 = static_cast( + uint8_t* dst0 = static_cast( slice.base.fragments[0].pointer) + ch * slice.stride; const size_t frag0_bytes = slice.base.fragments[0].size; const size_t total_bytes = static_cast(samples_written) * sizeof(float); @@ -153,14 +167,9 @@ class DeckLinkInNode : public dmf::NodeBase { } audio_index += static_cast(samples_written); } - - // Pace video to the MXL clock - const uint64_t ns = mxlGetNsUntilIndex(video_index + 1, &video_rate); - if (ns > 0 && ns < 2'000'000'000ULL) mxlSleepForNs(ns); - video_index = mxlGetCurrentIndex(&video_rate); } - log("stopped at video_index=%llu", static_cast(video_index)); + log("stopped frames=%llu drops=%llu", frame_count, drop_count); mxlReleaseFlowWriter(instance(), video_writer); if (audio_writer) mxlReleaseFlowWriter(instance(), audio_writer); }