decklink in timing fix

This commit is contained in:
JohannesItten
2026-07-09 23:35:31 +03:00
parent a3d0a338c5
commit e489a730b8
+19 -10
View File
@@ -95,23 +95,29 @@ class DeckLinkInNode : public dmf::NodeBase {
std::vector<float> audio_buf(static_cast<size_t>(max_audio_samples) * static_cast<size_t>(channels)); std::vector<float> audio_buf(static_cast<size_t>(max_audio_samples) * static_cast<size_t>(channels));
// --- Clock --- // --- Clock ---
// 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 video_rate = {fps_num, fps_den};
const mxlRational audio_rate = {receiver.audio_info.sample_rate, 1}; 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; uint64_t audio_index = has_audio ? mxlGetCurrentIndex(&audio_rate) : 0;
log("start video_index=%llu", static_cast<unsigned long long>(video_index)); uint64_t frame_count = 0, drop_count = 0;
log("ready, waiting for first frame...");
// --- Capture loop --- // --- Capture loop ---
while (dmf::g_running.load(std::memory_order_relaxed)) { while (dmf::g_running.load(std::memory_order_relaxed)) {
int samples_written = 0; 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( if (!receiver.wait_for_frame(
frame_buf.data(), video_stride, width, height, frame_buf.data(), video_stride, width, height,
(has_audio && audio_writer) ? audio_buf.data() : nullptr, (has_audio && audio_writer) ? audio_buf.data() : nullptr,
max_audio_samples, max_audio_samples,
(has_audio && audio_writer) ? &samples_written : nullptr)) break; (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 // Video grain
mxlGrainInfo grain{}; mxlGrainInfo grain{};
uint8_t* video_buf_ptr = nullptr; uint8_t* video_buf_ptr = nullptr;
@@ -121,9 +127,17 @@ class DeckLinkInNode : public dmf::NodeBase {
grain.flags = 0; grain.flags = 0;
grain.validSlices = grain.totalSlices; grain.validSlices = grain.totalSlices;
mxlFlowWriterCommitGrain(video_writer, &grain); 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) { if (has_audio && audio_writer && samples_written > 0) {
mxlMutableWrappedMultiBufferSlice slice{}; mxlMutableWrappedMultiBufferSlice slice{};
mxlStatus ast = mxlFlowWriterOpenSamples( mxlStatus ast = mxlFlowWriterOpenSamples(
@@ -153,14 +167,9 @@ class DeckLinkInNode : public dmf::NodeBase {
} }
audio_index += static_cast<uint64_t>(samples_written); audio_index += static_cast<uint64_t>(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<unsigned long long>(video_index)); log("stopped frames=%llu drops=%llu", frame_count, drop_count);
mxlReleaseFlowWriter(instance(), video_writer); mxlReleaseFlowWriter(instance(), video_writer);
if (audio_writer) mxlReleaseFlowWriter(instance(), audio_writer); if (audio_writer) mxlReleaseFlowWriter(instance(), audio_writer);
} }