Quick Start

Get started with MediaX in 5 minutes.

Transmitting Video

C++ Example

#include "rtp/rtp.h"

int main() {
  // Create a transmitter with SAP/SDP announcements
  mediax::RtpSapTransmit<mediax::rtp::uncompressed::RtpUncompressedPayloader> rtp(
      "239.192.1.1",    // Multicast IP address
      5004,             // Port
      "my-stream",      // Session name
      640, 480,         // Width x Height
      30,               // Framerate
      "RGB24"           // Colorspace
  );

  // Get a test pattern buffer
  std::vector<uint8_t> &buffer = rtp.GetBufferTestPattern();

  // Transmit 100 frames
  for (int i = 0; i < 100; i++) {
    rtp.Transmit(buffer.data(), false);
  }

  return 0;
}

Python Example

import sys
sys.path.append('/usr/local/lib/python3/dist-packages')

from mediax import rtp_uncompressed_payloader
from mediax import rtp_types

# Create payloader
payloader = rtp_uncompressed_payloader.RtpUncompressedPayloader()

# Configure stream
stream_info = rtp_types.StreamInformation()
stream_info.session_name = "my-stream"
stream_info.hostname = "239.192.1.1"
stream_info.port = 5004
stream_info.width = 640
stream_info.height = 480
stream_info.framerate = 30
stream_info.encoding = rtp_types.ColourspaceType_kColourspaceRgb24

payloader.SetStreamInfo(stream_info)
payloader.Open()
payloader.Start()

# Transmit frame data (any contiguous buffer: bytes, bytearray or a numpy array)
frame_data = bytes(640 * 480 * 3)  # RGB24 buffer
payloader.Transmit(frame_data)

payloader.Stop()
payloader.Close()

Receiving Video

C++ Example

The RtpSapRecieve wrapper listens for a SAP announcement for the named session, then automatically opens the depayloader once the stream is found.

#include <chrono>
#include <iostream>
#include <thread>
#include <vector>
#include "rtp/rtp.h"
#include "uncompressed/rtp_uncompressed_depayloader.h"

int main() {
  // Create a receiver — waits for SAP announcement for "my-stream"
  mediax::RtpSapRecieve<mediax::rtp::uncompressed::RtpUncompressedDepayloader> rtp(
      "239.192.1.1", 5004, "my-stream", 480, 640, 30, "RGB24");

  // Poll for frames — blocks up to 80 ms per call
  mediax::rtp::RtpFrameData frame;
  std::vector<uint8_t>& buffer = rtp.GetBuffer();

  for (int i = 0; i < 100; i++) {
    if (rtp.Receive(&frame, buffer.size())) {
      std::cout << "Received frame " << i
                << " (" << frame.resolution.width
                << "x" << frame.resolution.height << ")\n";
    } else {
      // SAP not yet seen or timeout — wait and retry
      std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
  }

  return 0;
}

Receive without known stream parameters

If you do not know the stream's IP/port/resolution in advance, use the SapListener to discover streams first (see SAP/SDP Discovery), then construct RtpSapRecieve with the discovered StreamInformation.

Discovering Streams

Use SAP/SDP to discover available streams on the network:

import sys
sys.path.append('/usr/local/lib/python3/dist-packages')

from mediax import sap_listener
import time

# Get SAP listener instance
listener = sap_listener.SapListener.GetInstance()
listener.Start()

# Wait for announcements
time.sleep(5)

listener.Stop()

# Get discovered streams
streams = listener.GetSapAnnouncements()
for name, sdp in streams.items():
    print(f"Found: {sdp.session_name} at {sdp.ip_address}:{sdp.port}")

Command Line Tools

MediaX includes command-line tools for testing:

# Transmit a test pattern
./rtp-transmit --ipaddr 239.192.1.1 --port 5004 --width 640 --height 480

# Receive and display video
./rtp-receive --ipaddr 239.192.1.1 --port 5004

GigE Vision Quick Start

Experimental

GigE Vision support is experimental. Validate it with your own cameras before relying on it.

MediaX has two GigE Vision implementations:

Library Namespace Use it for
libmediax_gige (standalone) mediax::gev Real GigE Vision cameras and third party GigE Vision software
libmediax (BUILD_GIGE) mediax::gige MediaX to MediaX streaming, see the end of this section

This quick start uses the standalone library, which follows the GigE Vision wire format exactly. It needs only a C++17 compiler, POSIX sockets and pthreads.

1. Build

# Standalone, binaries are written to build-gige/bin
cmake -S gige -B build-gige
cmake --build build-gige -j$(nproc)

# Or inside the MediaX tree, binaries are written to build/bin
cmake -B build -DBUILD_GIGE_LIB=ON -DEXAMPLES=ON ..
make -C build -j$(nproc)

2. Try it without a camera

gev_transmit emulates a camera and sends a moving colour-bar pattern. Run it in one terminal and read it from another, on the same machine or a different one:

# Terminal 1: the emulated camera, needs UDP port 3956
./gev_transmit 1280 720 30

# Terminal 2: find it, then receive 50 frames and save the first as a PPM image
./gev_discover 127.0.0.1
./gev_receive 127.0.0.1 50 first.ppm

gev_receive prints the device and its image format, then a summary:

Astute Systems MediaX GigE Vision (serial 0000001)
1280x720 RGB8 30 fps
Wrote first.ppm
50 frames, 0 incomplete, 0 packets lost

3. Find a real camera

Connect the camera and host to the same subnet, then list the cameras:

./gev_discover                  # broadcast on every interface
./gev_discover 10.0.5.20        # also ask a camera on another subnet directly

The same from C++:

#include <iostream>
#include <mediax/gev/gev.h>

int main() {
  for (const auto& device : mediax::gev::Discover()) {
    std::cout << device.ip << "  " << device.mac << "  " << device.manufacturer << " " << device.model
              << "  serial " << device.serial << "\n";
  }
  return 0;
}

4. Receive video from a camera

Open the camera, start the acquisition and pull frames from the queue:

#include <iostream>
#include <mediax/gev/gev.h>

int main() {
  auto camera = mediax::gev::Camera::Open("192.168.1.201");  // address from gev_discover
  if (!camera) {
    std::cerr << "Cannot open the camera: " << camera.message() << "\n";
    return 1;
  }

  const auto format = camera.value()->GetImageFormat();
  if (format) {
    std::cout << format.value().width << "x" << format.value().height << " "
              << mediax::gev::PixelFormatName(format.value().pixel_format) << "\n";
  }

  const mediax::gev::Status started = camera.value()->StartAcquisition();
  if (!started) {
    std::cerr << "Cannot start the acquisition: " << started.message() << "\n";
    return 1;
  }

  mediax::gev::Frame frame;
  for (int i = 0; i < 100 && camera.value()->PopFrame(&frame, 2000 /* ms */); i++) {
    std::cout << "Frame " << i << ": " << frame.width << "x" << frame.height << "  " << frame.data.size()
              << " bytes" << (frame.complete ? "" : "  (packets lost)") << "\n";
  }

  camera.value()->StopAcquisition();
  const mediax::gev::StreamStats stats = camera.value()->Stats();
  std::cout << stats.frames << " frames, " << stats.lost_packets << " packets lost\n";
  return 0;
}

Build it against the installed library:

g++ -std=c++17 receive.cc $(pkg-config --cflags --libs mediax_gige) -o receive

or with CMake:

find_package(MediaXGige REQUIRED)
add_executable(receive receive.cc)
target_link_libraries(receive PRIVATE MediaX::Gige)

5. Receive with a callback

Pass a callback to StartAcquisition() instead of polling. It runs on the receive thread, so hand the frame to another thread and return quickly:

#include <mediax/gev/gev.h>

mediax::gev::StreamOptions stream;
stream.port = 55100;                    // fixed receive port, easier to open in a firewall
stream.destination_ip = "192.168.1.10"; // the host address on the camera network (optional)

camera.value()->StartAcquisition(stream, [](mediax::gev::Frame&& frame) {
  if (!frame.complete) return;          // skip images with lost packets
  // push frame.data to your own queue or display thread
});

6. Convert a frame for display

Frames are delivered exactly as the camera sent them (Mono8, Bayer, RGB8, YUV 4:2:2 and so on). ConvertToRgb8() turns the common formats into RGB8:

std::vector<uint8_t> rgb;
if (mediax::gev::CanConvertToRgb8(frame.pixel_format) && mediax::gev::ConvertToRgb8(frame, &rgb)) {
  // rgb holds width * height * 3 bytes, ready for a display or an encoder
}

To stream the camera on as H.264 or uncompressed RTP, pass the RGB8 data to a MediaX payloader as shown in Transmitting Video.

7. Send video as a GigE Vision camera

mediax::gev::Transmitter makes your application look like a camera. Any GigE Vision host can discover it, take control and start the stream:

#include <chrono>
#include <thread>
#include <vector>
#include <mediax/gev/gev.h>

int main() {
  mediax::gev::TransmitterConfig config;
  config.user_name = "my-camera";
  config.format.width = 1280;
  config.format.height = 720;
  config.format.frame_rate = 30.0;
  config.format.pixel_format = mediax::gev::Code(mediax::gev::PixelFormat::kRgb8);

  mediax::gev::Transmitter transmitter;
  const mediax::gev::Status opened = transmitter.Open(config);  // needs UDP port 3956
  if (!opened) return 1;

  std::vector<uint8_t> frame(transmitter.FrameBytes());
  for (;;) {
    // ... fill frame with your RGB8 image ...
    transmitter.SendFrame(frame.data(), frame.size());  // does nothing until a host starts the acquisition
    std::this_thread::sleep_for(std::chrono::milliseconds(33));
  }
}

8. Troubleshooting

Symptom Check
No device found Same subnet as the camera? UDP 3956 allowed? Try gev_discover <camera ip>
"could not take control" Another application, such as the vendor viewer, holds the camera. Close it
Acquisition starts but no frames Firewall blocking the stream port? Set StreamOptions::port and allow it
Packets lost, incomplete frames sudo sysctl -w net.core.rmem_max=33554432, use jumbo frames, a dedicated NIC

See the GigE Vision Cameras guide for features, stream options, network setup and the full troubleshooting table.

MediaX to MediaX streaming (mediax::gige)

The classes in libmediax (requires -DBUILD_GIGE=ON) stream between MediaX applications. They use the older mediax::gige API and are not intended for third party cameras:

#include <iostream>
#include "gige/gige_receiver.h"
#include "gige/gvsp/gvsp_receiver.h"

int main() {
  mediax::gige::GigEReceiver receiver;
  receiver.SetReceivePort(55100);

  if (!receiver.Connect("192.168.1.100")) {  // camera IP
    std::cerr << "Connect failed\n";
    return 1;
  }
  receiver.Open();  // sends AcquisitionStart

  mediax::gige::gvsp::GvspFrameData frame;
  for (int i = 0; i < 100; i++) {
    if (receiver.Receive(&frame, 2000))
      std::cout << "Frame " << i << ": "
                << frame.width << "x" << frame.height
                << "  " << frame.pixels.size() << " bytes\n";
  }

  receiver.Close();
  return 0;
}

See the GigE Vision user guide for protocol details and the GigE Vision Cameras guide for working with real cameras.

Next Steps

Support

Questions about getting started?