Files
dmf-studio-rnd/decklinkout-plan.md
2026-07-06 02:25:14 +03:00

2.7 KiB

DeckLink Output Node — Architecture Plan

What you'll build

  • shared/DeckLinkSender.hpp — analogous to DeckLinkReceiver.hpp
  • nodes/decklinkout/main.cpp + CMakeLists.txt

The key difference: scheduled playback

DeckLink output uses a pull model — the card calls you when it needs frames, rather than you reading when a frame arrives.

Steps:

  1. EnableVideoOutput(mode, bmdVideoOutputFlagDefault)
  2. EnableAudioOutput(48kHz, int32, channels, bmdAudioOutputStreamContinuous)
  3. Allocate a pool of ~3 IDeckLinkMutableVideoFrame objects via CreateVideoFrame
  4. Pre-fill and ScheduleVideoFrame those 3 frames to prime the pipeline
  5. StartScheduledPlayback(0, fps_num, 1.0)
  6. The card fires ScheduledFrameCompleted callback when a frame has been displayed — fill it with the next grain and re-schedule it

Audio is pushed separately: ScheduleAudioSamples(buf, count, stream_time, timescale, &written) — no callback, just keep it full.

DeckLinkSender.hpp public API

Use a condition variable the same way as the receiver, but inverted — the callback signals that a frame slot is free:

DeckLinkSender:
  start_output(device_index, width, height, fps_num, fps_den, channels)
  submit_frame(const uint8_t* src, uint32_t stride)   // blocks until a slot is free
  submit_audio(const float* planar, int samples)       // non-blocking push

Implementation steps

  1. nodes/decklinkout/CMakeLists.txt — copy from decklinkin, rename target to dmf-node-decklinkout

  2. shared/DeckLinkSender.hpp

    • enumerate_devices() — identical to receiver
    • start_output(): EnableVideoOutputEnableAudioOutput → allocate 3 frames → pre-fill with black → StartScheduledPlayback
    • OutputCallback (nested private class) implementing IDeckLinkVideoOutputCallback::ScheduledFrameCompleted — pushes freed frame back to a queue, signals a CV
    • submit_frame(): waits for a free frame from the queue, memcpy src → frame, call ScheduleVideoFrame
    • submit_audio(): convert float32 planar → int32 interleaved (reverse of receiver), then call ScheduleAudioSamples
  3. nodes/decklinkout/main.cpp — create MXL flow readers, detect format, create sender, loop reading grains and calling submit_frame / submit_audio

Gotcha: stream time

The stream_time passed to ScheduleVideoFrame must be monotonically increasing in units of fps_num (the timescale passed to StartScheduledPlayback).

Easiest approach: keep a counter and pass frame_count * fps_den as the stream time.

ScheduleVideoFrame(frame, frame_count * fps_den, fps_den, fps_num);
frame_count++;