82 lines
2.7 KiB
Markdown
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.
|