decklink out plan md
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
# 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++;
|
||||
```
|
||||
Reference in New Issue
Block a user