7#include "CppTimerStdFuncCallback.h"
12#include <condition_variable>
22#if defined(ARGUS_HAVE_LIBCAM2OPENCV)
23#include <libcam2opencv.h>
28constexpr int kTargetFrameWidth = 640;
29constexpr int kTargetFrameHeight = 480;
30constexpr int kTargetFrameRate = 30;
31constexpr std::chrono::milliseconds kEmptyFrameRetryBackoff{20};
33bool waitForRetryBackoff(std::chrono::nanoseconds delay) {
34 if (delay.count() <= 0) {
39 std::condition_variable condition;
42 CppTimerCallback timer;
43 timer.registerEventCallback([&]() {
44 std::lock_guard<std::mutex> lock(mutex);
46 condition.notify_one();
50 timer.startns(
static_cast<long>(delay.count()), ONESHOT);
55 std::unique_lock<std::mutex> lock(mutex);
56 condition.wait(lock, [&]() {
return fired; });
61std::vector<std::string> buildLibcameraPipelines() {
64 "libcamerasrc ! video/x-raw,format=NV12,width=640,height=480,framerate=30/1 "
65 "! videoconvert ! video/x-raw,format=BGR "
66 "! appsink drop=true max-buffers=1 sync=false",
67 "libcamerasrc ! video/x-raw,width=640,height=480,framerate=30/1 "
68 "! videoconvert ! video/x-raw,format=BGR "
69 "! appsink drop=true max-buffers=1 sync=false",
70 "libcamerasrc ! videoconvert ! video/x-raw,format=BGR "
71 "! appsink drop=true max-buffers=1 sync=false"};
74bool tryOpen(cv::VideoCapture& cap,
75 const std::string& label,
76 const std::function<
bool()>& opener) {
78 std::cerr <<
"[CameraCapture] Open failed via " << label << std::endl;
82 std::cout <<
"[CameraCapture] Opened via " << label;
84 std::cout <<
" (backend: " << cap.getBackendName() <<
")";
86 std::cout << std::endl;
90bool isLibcamerifyActive() {
91 const char* ld_preload = std::getenv(
"LD_PRELOAD");
92 if (ld_preload ==
nullptr) {
96 const std::string preload(ld_preload);
97 return preload.find(
"v4l2-compat") != std::string::npos ||
98 preload.find(
"libcamerify") != std::string::npos ||
99 preload.find(
"libcamera") != std::string::npos;
102std::string buildV4l2DevicePath(
int camera_index) {
103 return "/dev/video" + std::to_string(camera_index);
107 switch (preference) {
113 return "libcamera2opencv";
119 bool* recognised =
nullptr) {
120 if (recognised !=
nullptr) {
124 if (raw_value ==
nullptr) {
125 if (recognised !=
nullptr) {
131 std::string value(raw_value);
132 std::transform(value.begin(), value.end(), value.begin(), [](
unsigned char c) {
133 return static_cast<char>(std::tolower(c));
136 if (value ==
"auto") {
139 if (value ==
"opencv" || value ==
"videocapture" || value ==
"opencvvideocapture") {
142 if (value ==
"libcamera2opencv" || value ==
"libcamera" ||
143 value ==
"cam2opencv") {
147 if (recognised !=
nullptr) {
156 return requested_preference;
159 bool recognised =
false;
160 const auto env_preference =
161 parseBackendPreference(std::getenv(
"ARGUS_CAMERA_BACKEND"), &recognised);
163 return env_preference;
169cv::Mat normaliseCapturedFrame(
const cv::Mat& frame) {
174 if (frame.cols == kTargetFrameWidth && frame.rows == kTargetFrameHeight) {
175 return frame.clone();
181 cv::Size(kTargetFrameWidth, kTargetFrameHeight),
194 virtual bool isOpen() const noexcept = 0;
205 const bool libcamerify_active = isLibcamerifyActive();
206 if (libcamerify_active) {
207 std::cout <<
"[CameraCapture] libcamerify detected via LD_PRELOAD. "
208 "Enforcing V4L2-first camera open policy."
213 if (libcamerify_active) {
214 const std::string device_path = buildV4l2DevicePath(camera_index);
215 opened = tryOpen(cap_,
"V4L2 device path " + device_path, [&]() {
216 return cap_.open(device_path, cv::CAP_V4L2);
219 opened = tryOpen(cap_,
"V4L2 index " + std::to_string(camera_index), [&]() {
220 return cap_.open(camera_index, cv::CAP_V4L2);
224 opened = tryOpen(cap_,
"V4L2 index " + std::to_string(camera_index), [&]() {
225 return cap_.open(camera_index, cv::CAP_V4L2);
228 opened = tryOpen(cap_,
229 "default backend index " + std::to_string(camera_index),
230 [&]() {
return cap_.open(camera_index); });
234 if (!opened && !libcamerify_active) {
235 const std::vector<std::string> pipelines = buildLibcameraPipelines();
236 for (std::size_t index = 0; index < pipelines.size() && !opened; ++index) {
237 const std::string label =
238 "GStreamer/libcamerasrc pipeline " + std::to_string(index + 1);
239 opened = tryOpen(cap_, label, [&]() {
240 return cap_.open(pipelines[index], cv::CAP_GSTREAMER);
246 if (libcamerify_active) {
247 std::cerr <<
"ERROR: Cannot open camera in libcamerify mode "
248 "(tried /dev/videoN and V4L2 index only)."
252 <<
"ERROR: Cannot open camera. Tried V4L2, default backend, and libcamerasrc."
257 if (cap_.isOpened() && !cap_.set(cv::CAP_PROP_BUFFERSIZE, 1)) {
259 <<
"[CameraCapture] Warning: CAP_PROP_BUFFERSIZE unsupported by backend."
263 if (cap_.isOpened()) {
264 (void)cap_.set(cv::CAP_PROP_FRAME_WIDTH, kTargetFrameWidth);
265 (void)cap_.set(cv::CAP_PROP_FRAME_HEIGHT, kTargetFrameHeight);
266 (void)cap_.set(cv::CAP_PROP_FPS, kTargetFrameRate);
267 (void)cap_.set(cv::CAP_PROP_READ_TIMEOUT_MSEC, 2000);
272 if (cap_.isOpened()) {
278 return cap_.isOpened();
282 if (!cap_.isOpened()) {
286 constexpr int kMaxReadAttempts = 4;
288 for (
int attempt = 0; attempt < kMaxReadAttempts; ++attempt) {
297 (void)waitForRetryBackoff(kEmptyFrameRetryBackoff);
301 std::cerr <<
"WARNING: Empty frame captured (backend: ";
302 if (cap_.isOpened()) {
303 std::cerr << cap_.getBackendName();
305 std::cerr <<
"closed";
307 std::cerr <<
")." << std::endl;
311 const auto steady_now = std::chrono::steady_clock::now();
312 const auto now = std::chrono::system_clock::now();
314 output_event.
timestamp_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
315 now.time_since_epoch())
321 if (!cap_.isOpened()) {
324 return cap_.getBackendName();
328 return "OpenCV VideoCapture";
332 cv::VideoCapture cap_;
335#if defined(ARGUS_HAVE_LIBCAM2OPENCV)
338 explicit Libcamera2OpenCvBackend(
int camera_index,
float focus_position = -1.0f) {
339 Libcam2OpenCVSettings settings;
340 settings.cameraIndex =
static_cast<unsigned int>(std::max(camera_index, 0));
341 settings.framerate = kTargetFrameRate;
342 settings.lensPosition = focus_position;
343 current_focus_position_ = focus_position;
345 camera_.registerCallback([
this](
const cv::Mat& frame,
346 const libcamera::ControlList&) {
347 std::lock_guard<std::mutex> lock(mutex_);
348 latest_frame_ = normaliseCapturedFrame(frame);
349 latest_capture_timestamp_ = std::chrono::steady_clock::now();
350 const auto now = std::chrono::system_clock::now();
351 latest_timestamp_ms_ =
352 std::chrono::duration_cast<std::chrono::milliseconds>(
353 now.time_since_epoch())
356 condition_.notify_one();
360 camera_.start(settings);
362 std::cout <<
"[CameraCapture] Opened via libcamera2opencv callback backend"
364 }
catch (
const std::exception& exception) {
365 last_error_ = exception.what();
366 std::cerr <<
"[CameraCapture] libcamera2opencv start failed: "
367 << last_error_ << std::endl;
370 last_error_ =
"unknown libcamera2opencv exception";
371 std::cerr <<
"[CameraCapture] libcamera2opencv start failed: "
372 << last_error_ << std::endl;
377 ~Libcamera2OpenCvBackend()
override {
381 bool isOpen() const noexcept
override {
390 std::unique_lock<std::mutex> lock(mutex_);
391 const bool ready = condition_.wait_for(
393 std::chrono::milliseconds(2000),
394 [&]() {
return frame_sequence_ > delivered_sequence_ || !open_; });
396 if (!ready || (!open_ && frame_sequence_ <= delivered_sequence_)) {
397 std::cerr <<
"WARNING: Empty frame captured (backend: libcamera2opencv)."
402 output_event.
image_data = latest_frame_.clone();
405 delivered_sequence_ = frame_sequence_;
410 return open_ ?
"LIBCAMERA2OPENCV" :
"CLOSED";
414 return "libcamera2opencv";
418 std::lock_guard<std::mutex> lock(mutex_);
419 current_focus_position_ = position;
424 std::lock_guard<std::mutex> lock(mutex_);
425 return current_focus_position_;
429 void safeStop() noexcept {
438 std::lock_guard<std::mutex> lock(mutex_);
441 condition_.notify_all();
444 Libcam2OpenCV camera_;
445 mutable std::mutex mutex_;
446 std::condition_variable condition_;
447 cv::Mat latest_frame_;
448 long long latest_timestamp_ms_{0};
449 std::chrono::steady_clock::time_point latest_capture_timestamp_{};
450 std::uint64_t frame_sequence_{0};
451 std::uint64_t delivered_sequence_{0};
453 std::string last_error_;
454 float current_focus_position_{-1.0f};
459 const auto effective_preference =
462 switch (effective_preference) {
464#if defined(ARGUS_HAVE_LIBCAM2OPENCV)
468 if (backend->isOpen()) {
471 std::cerr <<
"[CameraCapture] libcamera2opencv backend unavailable; "
472 "falling back to OpenCV VideoCapture."
478 auto backend = std::make_unique<OpenCvVideoCaptureBackend>(options.
camera_index);
479 if (backend->isOpen()) {
485#if defined(ARGUS_HAVE_LIBCAM2OPENCV)
489 if (backend->isOpen()) {
495 std::cerr <<
"[CameraCapture] requested backend '"
496 << backendPreferenceToString(effective_preference)
497 <<
"' is not available in this build. "
498 "Configure with -DARGUS_ENABLE_LIBCAMERA2OPENCV=ON "
499 "and install the required libcamera/turbojpeg dependencies."
511 : backend_preference_(options.backend_preference), current_focus_position_(options.focus_position) {
521 return backend_->waitForNextFrame(output_event);
528 return backend_->backendName();
533 return "UNAVAILABLE";
535 return backend_->backendImplementation();
539 return backend_preference_;
544 position = std::max(-1.0f, std::min(1.0f, position));
545 current_focus_position_ = position;
550 return backend_->setFocusPosition(position);
555 return current_focus_position_;
557 return backend_->getFocusPosition();
std::unique_ptr< CameraCaptureBackend > makeCameraBackend(CameraCapture::Options options)
Camera frame acquisition interface for ARGUS.
virtual std::string backendImplementation() const =0
virtual bool isOpen() const noexcept=0
virtual float getFocusPosition() const
virtual bool waitForNextFrame(FrameEvent &output_event)=0
virtual std::string backendName() const =0
virtual bool setFocusPosition(float position)
virtual ~CameraCaptureBackend()=default
Camera capture facade with pluggable backend implementations.
BackendPreference backendPreference() const noexcept
Return requested backend preference.
BackendPreference
Backend selection policy.
@ Auto
Auto-select backend based on platform/runtime constraints.
@ OpenCvVideoCapture
Force OpenCV VideoCapture backend.
@ Libcamera2OpenCv
Force libcamera2opencv callback backend.
bool setFocusPosition(float position)
Set camera focus position when backend/hardware supports it.
std::string backendName() const
Return active backend name.
float getFocusPosition() const
Get current requested focus position.
bool waitForNextFrame(FrameEvent &output_event)
Wait for the next captured frame.
~CameraCapture()
Release camera hardware resources.
std::string backendImplementation() const
Return active backend implementation family.
CameraCapture(int camera_index=0)
Construct using explicit camera index.
OpenCvVideoCaptureBackend(int camera_index)
std::string backendImplementation() const override
~OpenCvVideoCaptureBackend() override
std::string backendName() const override
bool isOpen() const noexcept override
bool waitForNextFrame(FrameEvent &output_event) override
Construction options for camera capture.
float focus_position
Focus request: -1.0 autofocus, 0.0 closest, 1.0 farthest.
int camera_index
Camera index passed to backend.
BackendPreference backend_preference
Requested backend policy.
Single captured frame and its timing metadata.
long long timestamp_ms
Epoch-style timestamp for log compatibility.
cv::Mat image_data
Captured image.
std::chrono::steady_clock::time_point capture_timestamp
Monotonic capture time.