Class VideoManager#
Defined in File video_manager.hpp
Nested Relationships#
Nested Types#
Inheritance Relationships#
Base Type#
public QObject
Class Documentation#
-
class VideoManager : public QObject
Orchestrates one VideoGrabber + VideoEncoder pair per configured camera.
VideoManager owns the complete video pipeline for a session. It matches the lifecycle of the cameras: open them once at startup, then start/stop recording repeatedly without reopening.
Frames are passed between threads through a lock-free SPSC RingBuffer. All public methods of VideoManager itself must be called from the main thread.
- Thread model
Each camera runs two background threads:
VideoGrabber — grabs frames from Pylon (or generates test patterns).
VideoEncoder — encodes frames via FFmpeg and writes the timestamp CSV.
See also
VideoGrabber, VideoEncoder, FrameTimestampWriter, RecordManager
- Lifecycle
VideoManager vm; int opened = vm.open(settings.video); // opens cameras once // Start/stop can repeat throughout the session vm.start("recordings/2026-06-04_14-32-05", "video", settings.video); // ... recording in progress ... vm.stop(); // blocks until all encoders have flushed and closed their files vm.close(); // release camera handles (called by destructor)
Public Functions
-
explicit VideoManager(QObject *parent = nullptr)
-
~VideoManager() override
-
int open(const VideoSettings &settings)
Opens camera devices (or stub generators) for all configured cameras.
- Parameters:
settings – Video settings from AppSettings. The
camerasvector determines how many devices are opened.- Returns:
The number of cameras successfully opened. May be less than
settings.cameras.size()if some devices fail.
-
void close()
Closes all open camera handles.
Calls stop() first if a recording is active.
-
void start_preview()
Starts the grab loop for all cameras without creating encoders.
Call this after open() to see live preview frames in the QML monitor before any recording session begins. Safe to call if grabbers are already running (no-op for those cameras).
-
void start(const QString &sessionDir, const QString &videoBasename, const VideoSettings &settings)
Starts grabbing and encoding for all open cameras.
Output files:
<sessionDir>/<videoBasename>_N.mp4<sessionDir>/timestamps_camN.csv
- Parameters:
sessionDir – Absolute path to the session folder (must exist).
videoBasename – Basename for video files (e.g.
"video").settings – Video settings (codec, preset, per-camera FPS/resolution).
-
void stop()
Stops all grabbers and encoders, flushing and closing every file.
Blocks until all encoder threads have exited (up to 10 s per camera).
-
void apply_live_params(int configIndex)
Re-applies exposure/gain/gamma/black-level/white-balance/ auto-target/digital-shift for one camera to already-open hardware, without stopping or reopening it.
Call this after the caller has mutated the corresponding CameraParameters in place (e.g. via the settings UI). Structural parameters (resolution, pixel format, frame rate, hardware trigger) still require a full close()+open() to take effect and are not touched by this call.
- Parameters:
configIndex – Position in the configured settings.cameras array (VideoManager::CameraUnit::configIndex), not the camera’s position among successfully opened units. No-op if no open unit matches.
-
void request_calibration_frame(int configIndex, uint64_t token = 0)
Requests one full-resolution frame from the given camera for room (extrinsic) calibration — see VideoGrabber::request_calibration_frame(). Delivered asynchronously via calibration_frame_ready(), echoing token back verbatim so a caller that requests one frame per “shot” can reject a reply for a shot it has already moved past. No-op if no open unit matches configIndex.
-
bool is_recording() const
- Returns:
truewhile a recording session is active.
-
bool is_previewing() const
- Returns:
truewhile a live preview session is active (started via start_preview(), not yet stopped by start()/close()). Used to guard actions that would race with a running ActionCommandTicker, which touches Pylon’s CTlFactory from a background thread for as long as either preview or recording keeps it alive.
-
int camera_count() const
- Returns:
The number of cameras that were successfully opened.
-
int64_t total_frames_encoded() const
- Returns:
Total frames encoded across all cameras since the last start().
-
int64_t total_frames_dropped() const
- Returns:
Total frames dropped (ring buffer overflow) since last start().
-
CameraStats camera_stats(int index) const
Returns a performance snapshot for one camera.
- Parameters:
index – Zero-based camera index. Returns a zeroed struct if out of range.
-
int64_t action_ticks_fired() const
- Returns:
The number of GigE Vision Action Command ticks fired during this camera group’s most recently attempted arm_and_fire_action_commands() call (recording or preview, whichever ran most recently), or -1 if that call didn’t use Action1 triggering at all — reset to -1 at the start of every arm_and_fire_action_commands() call, before a ticker is even created, so a session that doesn’t use Action1 never reports a stale count left over from an earlier session that did. Snapshotted in stop_action_ticker() right before the ticker is destroyed, since the live tick count itself is otherwise lost the instant the ticker object goes away. This is one shared, group-wide count — see camera_action_command_ready() for whether it’s even meaningful for a particular camera. Combined with a camera’s own frames_grabbed() by SessionHealthReport to report how many trigger broadcasts a camera missed, not just how many frames it captured.
-
bool camera_action_command_ready(int index) const
- Returns:
Whether this camera was Action1-ready (and therefore part of the Action Command target group whose shared tick count action_ticks_fired() reports) as of its last open()/probe. False for out-of-range indices and for any camera not opened for Action1 triggering, in which case action_ticks_fired()’s count is unrelated to it and callers must not apply it to that camera (a session can mix Action1-armed and free-running cameras).
-
const std::vector<RecordingCameraSnapshot> &last_recording_snapshot() const
- Returns:
One entry per camera that was actually open for the recording that just ended, captured at the end of stop(). Cameras that were configured but never opened (duplicate serial, failed open(), dead link) have no entry at all — callers wanting to report them must reconcile against settings.cameras via each entry’s configIndex. Empty until the first stop().
-
int64_t last_recording_action_ticks() const
- Returns:
action_ticks_fired() as captured at the end of the recording that just ended, for the same reason last_recording_snapshot() exists: the live value is reset to -1 by the preview restart that follows recording_stopped. -1 if that recording didn’t use Action1 triggering.
-
void clear_recording_snapshot()
Discards the previous recording’s snapshot.
Must be called when a recording starts, not only relied on being overwritten when one stops: stop() early-returns unless this manager is actually recording, and a session can end without it ever having started (video disabled for the session, or start() bailing because no camera is open). Without this, such a session’s health report would present the previous recording’s counters as its own.
Signals
-
void camera_opened(int cameraIndex, int width, int height, double fps)
Emitted on the main thread when a camera device is successfully opened.
-
void camera_closed(int cameraIndex)
Emitted when a camera device is closed.
-
void frame_dropped(int cameraIndex, int64_t frameId)
Emitted each time a frame is dropped because the ring buffer was full.
- Parameters:
cameraIndex – Which camera dropped the frame.
frameId – The frame counter value of the dropped frame.
-
void camera_error(int cameraIndex, QString message)
Emitted when a camera grab or encode error occurs.
-
void recording_stopped()
Emitted when all encoders have finished (files closed and flushed).
-
void frame_preview(int cameraIndex, QImage frame)
Throttled (~15 fps) BGR preview for live QML display.
-
void calibration_frame_ready(int cameraIndex, QImage frame, uint64_t token)
Full-resolution frame delivered in response to request_calibration_frame(). token is whatever was passed to request_calibration_frame().
-
void action_command_capability(int cameraIndex, bool supported)
Passthrough of VideoGrabber::action_command_capability() — reports whether a camera’s firmware supports GigE Vision Action Command triggering, once probed at open() time. Only fires for cameras that actually requested Action1 triggering (hwTriggerEnabled && hwTriggerSource == “Action1”).
-
void achievable_fps_changed(int cameraIndex, double fps)
Passthrough of VideoGrabber::achievable_fps_changed() — the camera’s own measured ResultingFrameRate, for the achievable-rate readout beside the exposure controls.
cameraIndexis a config index (a position in settings.cameras), like every other signal here and unlike camera_stats()’s unit index. A closing camera reports -1.0 so a stale reading can’t outlive the session that measured it.
-
struct CameraStats
Per-camera real-time performance snapshot.
Public Members
-
double fps = 0.0
Measured grab rate (frames/s).
-
int64_t framesGrabbed = 0
Total frames grabbed since start().
-
int64_t framesEncoded = 0
Total frames encoded since start().
-
int64_t framesDropped = 0
Frames lost to ring-buffer overflow.
-
int ringFillPct = 0
Ring buffer fill level, 0–100.
-
bool grabberRunning = false
-
int64_t lastFrameElapsedNs = -1
elapsed_ns() of the most recent frame, -1 if none yet.
-
double configuredFps = 0.0
VideoGrabber::configured_fps().
-
double achievableFps = -1.0
VideoGrabber::achievable_fps(), -1 = not yet measured.
-
int64_t incompleteFrames = 0
VideoGrabber::incomplete_frames_total() (GVSP packet loss).
-
double fps = 0.0
-
struct RecordingCameraSnapshot
One camera’s final counters for the recording that just ended.
Deliberately a snapshot, not a live read: every live counter above is zeroed again by VideoGrabber::start_grabbing() the moment preview resumes, and Application::initialize() connects its own preview-restart handler to RecordManager::recording_stopped before MainWindow exists, so it always runs first. Anything reading camera_stats() from a recording_stopped handler therefore sees zeros for every camera. Use last_recording_snapshot() instead for post-recording reporting.
Public Members
-
int configIndex = 0
Position in settings.cameras this unit was opened from — NOT its position in the internal (compacted) unit list. See CameraUnit::configIndex.
-
int64_t framesGrabbed = 0
-
int64_t framesEncoded = 0
-
int64_t framesDropped = 0
-
int64_t incompleteFrames = 0
-
double configuredFps = 0.0
-
double achievableFps = -1.0
-1 = not measured
-
bool actionCommandReady = false
-
int configIndex = 0