4. Examples

Minimal C++ publisher

The snippet below sends a single grayscale frame using RtmsSender. It binds to 0.0.0.0 (any local address) and multicasts to 239.0.0.1:5500 with timestamp-only extended info. Error handling is omitted for brevity.

#include <ifw/rtmslib_llnetio/rtmsSender.hpp>
#include <vector>

using namespace ifw::rtmslib;

int main() {
    const std::string src_ip = "0.0.0.0";      // bind locally
    const std::string dest_ip = "239.0.0.1";   // multicast or unicast
    const int dest_port = 5500;
    const int8_t bytes_per_pixel = 2;           // uint16 payload

    // Example 64x64 image
    const std::size_t width = 64;
    const std::size_t height = 64;
    const std::size_t raw_size = width * height * bytes_per_pixel;
    std::vector<uint8_t> payload(raw_size, 0xff);

    SenderConfig cfg;
    cfg.topic_id = 10;
    cfg.ext_info_mode = ExtendedInfoMode::TimestampOnly;

    RtmsSender sender(src_ip, dest_ip, dest_port, bytes_per_pixel, raw_size, cfg);

    // Timestamp-only extended info
    SampleExtInfo ext_info;
    ext_info.SetTimestamp(1699999999.0);

    return sender.SendSample(ext_info, payload);
}

Minimal C++ subscriber

Derive from RtmsReceiver and implement HandleSampleUser() to process each sample. The example below logs the sample id and copies statistics while honouring the extended info contract.

#include <ifw/rtmslib_llnetio/rtmsReceiver.hpp>
#include <iostream>

using namespace ifw::rtmslib;
using ifw::fnd::datatype::DataType;

class LoggingReceiver : public RtmsReceiver {
public:
    using RtmsReceiver::RtmsReceiver;

    void HandleSampleUser(SampleExtInfo& ext, std::vector<uint8_t>& payload) override {
        std::cout << "Received sample " << GetSampleId()
                  << " with " << payload.size() << " bytes" << std::endl;
        if (ext.GetExtInfoSize() > 0) {
            std::cout << "  timestamp: " << ext.GetTimestamp()
                      << " width: " << ext.GetWidth()
                      << " height: " << ext.GetHeight() << std::endl;
        }
    }
};

int main() {
    ReceiverConfig cfg;
    cfg.topic_id = 10;
    cfg.ext_info_mode = ExtendedInfoMode::TimestampOnly;

    LoggingReceiver receiver("239.0.0.1", "eth0", 5500, DataType::UINT16, cfg);

    // Block until one sample arrives or a timeout occurs.
    SampleExtInfo ext;
    std::vector<uint8_t> payload;
    receiver.ReceiveSample(ext, payload, std::chrono::seconds(5));
    receiver.StopReceiver();
}

Tips for integration

  • Keep sender and receiver ext_info_mode aligned; a mismatch will be detected and logged, and samples may be discarded.

  • packet_send_delay in SenderConfig paces individual packets to avoid send-buffer overruns at small MTUs or high frame rates - see Tuning for high packet rates below.

  • ignore_connection_refused lets publishers start before any subscribers are listening; logs are throttled to avoid flooding.

  • Leverage SampleExtInfo to propagate geometry and datatype so subscribers can adapt to image size changes without restarts.

Tuning for high packet rates

A single large sample is split into many UDP packets (one leader, many payload packets, one trailer). At high frame rates or small MTUs the sender can offer packets faster than the kernel drains them onto the wire, filling the socket send buffer. When that happens send returns EAGAIN (“Resource temporarily unavailable”).

The library handles this in two ways:

  • Automatic retry. Each packet - leader, payload and trailer - is retried on EAGAIN (bounded, with a short back-off) so a transient burst does not drop the sample. These retries are logged (throttled) as, e.g.:

    sample 12345: Payload packet 7/40 send EAGAIN, retry 1/99... (Resource temporarily unavailable)
    

    An occasional such line under load is normal back-pressure; a sustained flood means the sender is over-running the link or the buffer.

  • Pacing. Set packet_send_delay (microseconds) to spread the per-sample burst over time so the buffer never fills. Pick a value so that delay x packets_per_sample is a useful fraction of the inter-frame interval - e.g. a 2048x2048 16-bit frame at 10 Hz is ~5800 packets in a 100 ms budget, so a few microseconds per packet spreads it comfortably while leaving the link idle most of the frame. Pacing is a deadline-based busy-spin: it is accurate well below the kernel timer tick and does not drift, but it spins a CPU for the delay, so keep delays short and run the sender on a dedicated thread/core.

If EAGAIN persists even with pacing, the offered data rate may simply exceed the link: prefer jumbo frames (MTU 9000, ~6x fewer packets) or enlarging the socket send buffer (net.core.wmem_max / wmem_default) over very large per-packet delays.