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

68 lines
2.7 KiB
Markdown

# 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()`: `EnableVideoOutput``EnableAudioOutput` → 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.
```cpp
ScheduleVideoFrame(frame, frame_count * fps_den, fps_den, fps_num);
frame_count++;
```