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:
- Takes control of the device and starts the heartbeat that keeps control.
- Opens a UDP socket to receive the stream.
- Points the device stream channel at this host and sets the packet size.
- Sets
AcquisitionModetoContinuous, locks the transport parameters (TLParamsLocked) and runs theAcquisitionStartcommand.
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_ipto the address on the camera network. - Allow UDP 3956 (GVCP) and the stream port. Set
StreamOptions::portto 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_maxunless 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¶
- GigE Vision - protocols, pixel format table and the
mediax::gigeclasses insidelibmediax - Building from Source - CMake options
- SAP/SDP Discovery