Files
2026-07-13 19:06:11 +03:00

82 lines
2.7 KiB
Markdown

# ST 2110-20 receiver with Intel MTL
The MTL pattern is structurally identical to `DeckLinkReceiver` — callback-based, MTL manages frame buffers, you drain them into MXL.
## MTL initialization (once per process)
```cpp
mtl_init_params p{};
p.ports[MTL_PORT_P] = "0000:31:00.0"; // PCI address or netdev for AF_XDP
p.num_ports = 1;
p.flags = MTL_FLAG_BIND_NUMA;
// For AF_XDP mode (no DPDK hugepages needed):
p.transport = MTL_TRANSPORT_AF_XDP;
mtl_handle dev = mtl_init(&p);
```
## Create RX session
```cpp
st20_rx_ops ops{};
ops.port.num_port = 1;
memcpy(ops.port.sip_addr[MTL_PORT_P], multicast_ip, 4); // join this group
ops.port.udp_port[MTL_PORT_P] = 20000;
ops.width = 1280;
ops.height = 720;
ops.fps = ST_FPS_P25;
ops.fmt = ST20_FMT_YUV_422_10BIT; // RFC 4175 packed — see note below
ops.framebuff_cnt = 3;
ops.notify_frame_ready = on_frame_ready; // your callback
ops.priv = this;
st20_rx_handle rx = st20_rx_create(dev, &ops);
```
## Callback — replaces `wait_for_frame()`
```cpp
static int on_frame_ready(void* priv, void* frame, st20_rx_frame_meta* meta) {
auto* self = static_cast<St2110InNode*>(priv);
// frame points to a complete assembled frame — MTL did the reassembly
const uint64_t index = mxlGetCurrentIndex(&self->video_rate);
mxlGrainInfo grain{};
uint8_t* buf = nullptr;
if (mxlFlowWriterOpenGrain(self->writer, index, &grain, &buf) == MXL_STATUS_OK) {
memcpy(buf, frame, self->frame_size); // ← see format note
grain.validSlices = grain.totalSlices;
mxlFlowWriterCommitGrain(self->writer, &grain);
}
st20_rx_put_framebuff(self->rx, frame); // return buffer to MTL pool
return 0;
}
```
## Format conversion: RFC 4175 vs V210
ST 2110-20 wire format is RFC 4175 packed 10-bit — **not V210**. They encode the same YCbCr 4:2:2 10-bit data differently:
- RFC 4175: 5 bytes per 2 pixels, big-endian packed
- V210: 4 bytes per 3 luma + 2 chroma, little-endian with padding bits
MXL in this project uses V210. Check MTL's `output_fmt` option — newer MTL versions support `ST_FRAME_FMT_V210` as the output format, which means MTL does the conversion internally. If your version doesn't have it, you'll need a small RFC4175→V210 conversion step before the `memcpy`.
Check `st_frame_fmt` enum in MTL headers for what's available.
## Teardown
```cpp
st20_rx_free(rx);
mtl_uninit(dev);
```
## Main loop
The callback is called from MTL's internal thread (like DeckLink's), so the MXL write happens inside the callback rather than in the main loop. The main loop just blocks on `g_running`:
```cpp
while (dmf::g_running.load(std::memory_order_relaxed))
mxlSleepForNs(10'000'000);
```
MTL drives the pacing, exactly like DeckLink hardware does.