decklink in timing fix

This commit is contained in:
JohannesItten
2026-07-09 23:35:31 +03:00
parent a3d0a338c5
commit e489a730b8
+24 -15
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));
// --- 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<unsigned long long>(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<size_t>(samples_written), &slice);
if (ast == MXL_STATUS_OK) {
for (int ch = 0; ch < channels; ++ch) {
const uint8_t* src = reinterpret_cast<const uint8_t*>(
const uint8_t* src = reinterpret_cast<const uint8_t*>(
audio_buf.data() + ch * max_audio_samples);
uint8_t* dst0 = static_cast<uint8_t*>(
uint8_t* dst0 = static_cast<uint8_t*>(
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<size_t>(samples_written) * sizeof(float);
@@ -153,14 +167,9 @@ class DeckLinkInNode : public dmf::NodeBase {
}
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);
if (audio_writer) mxlReleaseFlowWriter(instance(), audio_writer);
}