From b72b60b9c4d5b99b74630d387b092da2823aadd9 Mon Sep 17 00:00:00 2001 From: JohannesItten Date: Mon, 13 Jul 2026 19:06:11 +0300 Subject: [PATCH] MTL implementation md --- mtl.md | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 mtl.md diff --git a/mtl.md b/mtl.md new file mode 100644 index 0000000..7538c4d --- /dev/null +++ b/mtl.md @@ -0,0 +1,81 @@ +# 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(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.