decklink in timing fix
This commit is contained in:
+24
-15
@@ -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 ---
|
||||||
const mxlRational video_rate = {fps_num, fps_den};
|
// video_index is determined AFTER each hardware frame arrives so it reflects
|
||||||
const mxlRational audio_rate = {receiver.audio_info.sample_rate, 1};
|
// the actual TAI slot the frame landed in. wait_for_frame() is the natural
|
||||||
uint64_t video_index = mxlGetCurrentIndex(&video_rate);
|
// pacer — no separate sleep needed.
|
||||||
uint64_t audio_index = has_audio ? mxlGetCurrentIndex(&audio_rate) : 0;
|
const mxlRational video_rate = {fps_num, fps_den};
|
||||||
log("start video_index=%llu", static_cast<unsigned long long>(video_index));
|
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 ---
|
// --- 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,18 +127,26 @@ 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(
|
||||||
audio_writer, audio_index, static_cast<size_t>(samples_written), &slice);
|
audio_writer, audio_index, static_cast<size_t>(samples_written), &slice);
|
||||||
if (ast == MXL_STATUS_OK) {
|
if (ast == MXL_STATUS_OK) {
|
||||||
for (int ch = 0; ch < channels; ++ch) {
|
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);
|
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;
|
slice.base.fragments[0].pointer) + ch * slice.stride;
|
||||||
const size_t frag0_bytes = slice.base.fragments[0].size;
|
const size_t frag0_bytes = slice.base.fragments[0].size;
|
||||||
const size_t total_bytes = static_cast<size_t>(samples_written) * sizeof(float);
|
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);
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user