GigE Vision Cameras

Experimental

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

This page shows how to find, control and receive images from a real GigE Vision camera with the standalone library libmediax_gige (namespace mediax::gev, headers under <mediax/gev/...>). The library does not depend on RTP, SAP or libmediax. See GigE Vision for the protocol background and the camera emulation classes.

Overview

Discover()            GVCP broadcast on UDP 3956  ->  DeviceInfo (IP, MAC, model, serial)
Camera::Open(ip)      GVCP to the camera          ->  identification, GenICam description
StartAcquisition()    take control, heartbeat, set the stream destination, AcquisitionStart
                      GVSP packets (leader, payload, trailer)  ->  Frame (callback or PopFrame)
StopAcquisition()     AcquisitionStop, give up control
Task API
Find cameras mediax::gev::Discover()
Open a camera mediax::gev::Camera::Open()
Read identification Camera::ReadDeviceInfo()
Read or write features Camera::Features() (NodeMap) with Camera::Access()
Read the current format Camera::GetImageFormat()
Receive images Camera::StartAcquisition(), Camera::PopFrame() or a callback
Check for packet loss Camera::Stats(), Frame::complete
Convert images for display mediax::gev::ConvertToRgb8()

Every call that can fail returns a Status or a Result<T>. No exception crosses the API. Check ok() and read message() for the reason.

Building

Build the library on its own, or as part of the MediaX tree:

# Standalone
cmake -S gige -B build-gige -DBUILD_TESTING=ON
cmake --build build-gige -j$(nproc)

# Inside the MediaX tree
cmake -B build -DBUILD_GIGE_LIB=ON -DEXAMPLES=ON ..

Use it from your own project:

find_package(MediaXGige REQUIRED)
add_executable(my_camera_app main.cc)
target_link_libraries(my_camera_app PRIVATE MediaX::Gige)   # or MediaX::GigeStatic

or with pkg-config:

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

The library needs only a C++17 compiler, POSIX sockets and pthreads.

Find Cameras

Discover() broadcasts a GVCP discovery command on every network interface and collects the replies. The address of a device is the source address of its reply, so a camera that reports 0.0.0.0 is still reachable.

#include <mediax/gev/gev.h>

mediax::gev::DiscoveryOptions options;
options.timeout_ms = 1000;                       // how long to wait for replies
// options.unicast_targets = {"10.0.5.20"};      // ask these addresses directly (routed networks, loopback)

for (const auto& device : mediax::gev::Discover(options)) {
  std::cout << device.ip << "  " << device.mac << "  " << device.manufacturer << " " << device.model
            << "  serial " << device.serial << "  name '" << device.user_name << "'\n";
}
DiscoveryOptions field Default Description
timeout_ms 600 How long to wait for replies
port 3956 GVCP port of the devices
broadcast true Send to the broadcast address of every interface
unicast_targets empty Addresses to ask directly, for devices that are not on a local subnet

The gev_discover example does the same from the command line:

./gev_discover                 # broadcast
./gev_discover 10.0.5.20       # also ask a specific address

Note

Discovery only finds cameras that are on the same subnet as the host, or that you list in unicast_targets. Setting a camera's IP address (FORCEIP) is not implemented, so give the camera a suitable address with the vendor's tool first.

Open a Camera

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

const auto info = camera.value()->ReadDeviceInfo();
if (info) {
  std::cout << info.value().manufacturer << " " << info.value().model
            << " (serial " << info.value().serial << ", GigE Vision "
            << info.value().spec_major << "." << info.value().spec_minor << ")\n";
}

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

Camera::Open() only talks to the device. It does not take control, so other applications can still read the camera.

CameraOptions field Default Description
gvcp_port 3956 GVCP port of the device
reply_timeout_ms 500 How long to wait for each reply
retries 3 Times a command is sent before giving up

Acquire Images

StartAcquisition() does everything needed to start a stream:

  1. Takes control of the device and starts the heartbeat that keeps control.
  2. Opens a UDP socket to receive the stream.
  3. Points the device stream channel at this host and sets the packet size.
  4. Sets AcquisitionMode to Continuous, locks the transport parameters (TLParamsLocked) and runs the AcquisitionStart command.

StopAcquisition() (or destroying the camera) runs AcquisitionStop, unlocks the parameters, gives up control and closes the socket.

Poll for images

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

mediax::gev::Frame frame;
while (running && camera.value()->PopFrame(&frame, 2000 /* ms */)) {
  std::cout << frame.width << "x" << frame.height << " "
            << mediax::gev::PixelFormatName(frame.pixel_format)
            << (frame.complete ? "" : "  INCOMPLETE") << "\n";
}

camera.value()->StopAcquisition();

Images are queued, up to 8 are kept, and the oldest is dropped when the queue is full. Call PopFrame() often enough to keep up with the frame rate.

Receive with a callback

camera.value()->StartAcquisition(
    mediax::gev::StreamOptions(),
    [](mediax::gev::Frame&& frame) {
      // Runs on the receive thread. Hand the frame to another thread and return quickly.
      queue.Push(std::move(frame));
    });

The callback runs on the receive thread, so a slow callback delays the reception of packets. With a callback set, PopFrame() is not used.

The Frame structure

Field Description
width, height Image size in pixels
pixel_format PFNC pixel format code of data (PixelFormatName() names it)
data The pixels as sent by the camera, no conversion applied
timestamp_ns Device timestamp of the image in nanoseconds
block_id Block (frame) identifier from the stream
complete false if stream packets of the image were lost
missing_packets Number of stream packets that never arrived

Stream options

mediax::gev::StreamOptions stream;
stream.port = 55100;                      // fixed receive port (handy for firewall rules), 0 = any
stream.destination_ip = "192.168.1.10";   // which host address the camera sends to
stream.packet_size = 9000;                // needs jumbo frames on the whole path
stream.packet_delay = 0;                  // inter-packet delay in device timestamp ticks
stream.drop_incomplete_frames = true;     // do not deliver frames with missing packets
camera.value()->StartAcquisition(stream);
StreamOptions field Default Description
port 0 Local UDP port to receive on, 0 lets the system choose
destination_ip empty Address the camera sends to. Empty uses the local address that reaches the camera
packet_size 1500 Stream packet size to ask for, including IP and UDP headers. The camera may settle on less
packet_delay 0 Inter-packet delay to ask for in device timestamp ticks, 0 sends back to back
socket_buffer_bytes 32 MiB Socket receive buffer to ask for
conceal_lost_packets true Fill packets that never arrived from the previous frame
drop_incomplete_frames false Do not deliver frames with missing packets

The packet size is requested with the "do not fragment" flag. A camera that cannot honour the flag is asked again without it, and the size it settles on is read back and used by the receiver.

Lost packets

With conceal_lost_packets on, a missing packet is filled from the previous image and the frame is still delivered with complete == false. Turn it off to leave the missing data zero, or turn on drop_incomplete_frames if you only want whole images. Packet resend is not implemented.

Stream statistics

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

StreamStats also reports packets, bytes and rejected_packets (packets with a bad header or an unsupported format).

GenICam Features

Cameras describe their features (Width, Height, PixelFormat, ExposureTime, Gain and so on) in a GenICam XML document stored on the camera, plain or zipped. Camera::Features() reads it on first use and gives you a NodeMap. Pass the register access of the camera, from Camera::Access(), when you read or write a feature.

auto features = camera.value()->Features();
if (!features) {
  std::cerr << features.message() << "\n";
  return 1;
}
const mediax::gev::NodeMap& map = *features.value();
const mediax::gev::RegisterAccess io = camera.value()->Access();

// Read
if (map.Has("ExposureTime")) {
  const auto exposure = map.GetFloat("ExposureTime", io);
  if (exposure) std::cout << "Exposure " << exposure.value() << " us\n";
}
const auto width = map.GetInteger("Width", io);
const auto format = map.GetEnumeration("PixelFormat", io);      // symbolic name, e.g. "Mono8"
const auto formats = map.EnumerationEntries("PixelFormat");     // everything it can be set to

// Write (needs control of the device)
map.SetEnumeration("PixelFormat", "BayerRG8", io);
map.SetInteger("Gain", 10, io);
map.Execute("TriggerSoftware", io);                              // run a Command feature
NodeMap method Use
Has(name), Names(), TypeOf(name) Look at what the camera offers
GetInteger, GetFloat, GetString Read Integer, Float and String features
GetEnumeration, EnumerationEntries Read the current entry and list the possible entries
SetInteger, SetEnumeration Write a feature
Execute Run a Command feature

Supported node types are Integer, IntReg, MaskedIntReg, StructReg, Boolean, Enumeration, Float, FloatReg, StringReg and Command. Features built on nodes that need an expression evaluator (SwissKnife, Converter) return an error when you use them.

Writing features needs control

Reads work at any time. Writes need control of the device, which this API takes in StartAcquisition(). Cameras also refuse changes to Width, Height and PixelFormat while the transport parameters are locked, and StartAcquisition() locks them. Set the image format and similar features with the camera vendor's tool beforehand. Settings such as exposure and gain can be written from the application while acquiring.

For anything the NodeMap does not cover, ReadRegister(), WriteRegister(), ReadMemory() and WriteMemory() access the bootstrap registers and device memory directly.

Pixel Formats and Display

Images are delivered exactly as the camera sent them. The library can convert the common formats to RGB8:

if (mediax::gev::CanConvertToRgb8(frame.pixel_format)) {
  std::vector<uint8_t> rgb;
  const mediax::gev::Status converted = mediax::gev::ConvertToRgb8(frame, &rgb);
  if (converted) {
    // rgb holds width * height * 3 bytes, no row padding
  }
}
Pixel formats Conversion
Mono8, Mono10, Mono12, Mono14, Mono16 Equal red, green and blue
Bayer GR8, RG8, GB8, BG8 Bilinear interpolation
RGB8, BGR8, RGBa8, BGRa8 Channel reordering
YUV 4:2:2 (UYVY, YUYV, YCbCr422_8) Colour conversion

16 bit formats are little-endian as sent by GigE Vision cameras, and 10, 12 and 14 bit samples sit in the least significant bits. The gev_receive example writes the first image to a PPM file:

./gev_receive 192.168.1.201 50 first.ppm

Announce and Find Cameras with SAP

The optional add-on mediax_gige_sap (<mediax/gev/sap.h>) announces and finds devices with SAP/SDP, so a receiver can look a camera up by session name instead of by IP address. It is the only part of the GigE library that links libmediax, and it is built inside the MediaX tree only.

#include <mediax/gev/sap.h>

// Find a camera that was announced as "camera-1"
auto device = mediax::gev::FindDevice("camera-1", std::chrono::seconds(5));
if (device) {
  auto camera = mediax::gev::Camera::Open(device.value().ip);
}

SapAnnouncement::Start() announces a device for as long as the object lives. The SDP media line is m=application <gvcp port> UDP gev.

Network Setup

  • Put the camera and the host on the same subnet and use static IP addresses.
  • Use a dedicated network interface or VLAN for camera traffic. When the host has several interfaces, set StreamOptions::destination_ip to the address on the camera network.
  • Allow UDP 3956 (GVCP) and the stream port. Set StreamOptions::port to a fixed value so one firewall rule covers it.
  • For high resolutions or frame rates, enable jumbo frames: set the interface MTU to 9000 and ask for packet_size = 9000.
  • The kernel limits the receive socket buffer to net.core.rmem_max unless the process is privileged. Raise it to allow the requested 32 MiB:
sudo sysctl -w net.core.rmem_max=33554432
sudo ip link set eth1 mtu 9000

Troubleshooting

Symptom Check
Discover() finds nothing Same subnet? Firewall blocking UDP 3956? Try unicast_targets with the camera address
Camera::Open() fails Correct address and port? Increase reply_timeout_ms
"could not take control of the device" Another application, for example the vendor viewer, holds control. Close it and retry
"could not parse the device description" The camera description is not valid XML or has no features. Raw register access still works
StartAcquisition() works but no frames Firewall blocking the stream port? Wrong destination_ip? Try a fixed port and allow it
PopFrame() times out intermittently Camera frame rate is lower than the timeout. Check the cable, switch and Stats()
lost_packets rises, frames incomplete Raise net.core.rmem_max, use jumbo frames, set packet_delay, or use a dedicated interface
ConvertToRgb8() fails CanConvertToRgb8() returns false for the format, or data is shorter than the image

Limitations

Not yet implemented: packet resend, extended (64-bit) block identifiers, multiple stream channels, events and action commands, and FORCEIP.

Example Applications

Binary Source Description
gev_discover gige/examples/gev_discover.cc List the GigE Vision devices on the network
gev_receive gige/examples/gev_receive.cc Acquire images from a camera, print statistics, save a PPM
gev_transmit gige/examples/gev_transmit.cc Camera emulator that sends a moving colour-bar pattern

gev_transmit is useful for testing without hardware. Run it on one machine and gev_discover and gev_receive against it from another, or from the same one:

./gev_transmit 1280 720 30               # terminal 1, needs UDP 3956
./gev_discover 127.0.0.1                 # terminal 2
./gev_receive 127.0.0.1 50 frame.ppm

See Also