From f354c4568c795f3c16467e7e5c615f78a50ba48c Mon Sep 17 00:00:00 2001 From: Shao Su Date: Thu, 26 Feb 2026 17:03:07 -0800 Subject: [PATCH 1/4] enable preview for oak camera --- src/plugins/oak/CMakeLists.txt | 22 ++++ src/plugins/oak/README.md | 2 +- src/plugins/oak/core/oak_camera.cpp | 55 +++++---- src/plugins/oak/core/oak_camera.hpp | 15 ++- src/plugins/oak/core/preview_stream.cpp | 156 ++++++++++++++++++++++++ src/plugins/oak/core/preview_stream.hpp | 56 +++++++++ src/plugins/oak/main.cpp | 7 ++ 7 files changed, 286 insertions(+), 27 deletions(-) create mode 100644 src/plugins/oak/core/preview_stream.cpp create mode 100644 src/plugins/oak/core/preview_stream.hpp diff --git a/src/plugins/oak/CMakeLists.txt b/src/plugins/oak/CMakeLists.txt index 3785a2280..1834b301f 100644 --- a/src/plugins/oak/CMakeLists.txt +++ b/src/plugins/oak/CMakeLists.txt @@ -18,6 +18,7 @@ message(STATUS "Configuring DepthAI...") set(DEPTHAI_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) set(DEPTHAI_BUILD_TESTS OFF CACHE BOOL "" FORCE) set(DEPTHAI_BUILD_DOCS OFF CACHE BOOL "" FORCE) +set(DEPTHAI_OPENCV_SUPPORT OFF CACHE BOOL "" FORCE) FetchContent_Declare( depthai @@ -28,6 +29,22 @@ FetchContent_MakeAvailable(depthai) message(STATUS "Building OAK camera plugin with DepthAI ${depthai_VERSION}") +# ============================================================================== +# SDL2 (for live preview window, bundled via FetchContent) +# ============================================================================== +set(SDL2_VERSION "2.30.11") + +set(SDL_TEST OFF CACHE BOOL "" FORCE) +set(SDL_SHARED ON CACHE BOOL "" FORCE) +set(SDL_STATIC OFF CACHE BOOL "" FORCE) + +FetchContent_Declare( + sdl2 + URL "https://github.com/libsdl-org/SDL/releases/download/release-${SDL2_VERSION}/SDL2-${SDL2_VERSION}.tar.gz" +) +FetchContent_MakeAvailable(sdl2) +message(STATUS "SDL2 ${SDL2_VERSION} — live preview support enabled") + # ============================================================================== # Build OAK Plugin # ============================================================================== @@ -36,6 +53,7 @@ add_executable(camera_plugin_oak core/oak_camera.cpp core/rawdata_writer.cpp core/frame_sink.cpp + core/preview_stream.cpp ) target_link_libraries(camera_plugin_oak @@ -45,8 +63,12 @@ target_link_libraries(camera_plugin_oak mcap::mcap oxr::oxr_core pusherio::pusherio + SDL2::SDL2 ) +# Bundle SDL2 shared library alongside the binary +install(TARGETS SDL2 LIBRARY DESTINATION plugins/oak_camera) + # Set RPATH to find bundled libraries in same directory set_target_properties(camera_plugin_oak PROPERTIES INSTALL_RPATH "$ORIGIN" diff --git a/src/plugins/oak/README.md b/src/plugins/oak/README.md index df14582b3..e054a8304 100644 --- a/src/plugins/oak/README.md +++ b/src/plugins/oak/README.md @@ -22,7 +22,7 @@ DepthAI is fetched and built automatically via FetchContent. The first build tak cd IsaacTeleop # Configure and build -cmake -B build +cmake -B build -DBUILD_PLUGIN_OAK_CAMERA=ON cmake --build build --target camera_plugin_oak --parallel ``` diff --git a/src/plugins/oak/core/oak_camera.cpp b/src/plugins/oak/core/oak_camera.cpp index c746e5118..ffde4aa8d 100644 --- a/src/plugins/oak/core/oak_camera.cpp +++ b/src/plugins/oak/core/oak_camera.cpp @@ -4,6 +4,7 @@ #include "oak_camera.hpp" #include "frame_sink.hpp" +#include "preview_stream.hpp" #include #include @@ -42,18 +43,32 @@ OakCamera::OakCamera(const OakConfig& config, const std::vector& s for (const auto& [socket, name] : sensors) std::cout << " Socket " << static_cast(socket) << ": " << name << std::endl; - create_pipeline(config, streams, sensors); + if (sensors.find(dai::CameraBoardSocket::CAM_A) == sensors.end()) + throw std::runtime_error("Color sensor not found on CAM_A"); + auto color_sensor_name = sensors.find(dai::CameraBoardSocket::CAM_A)->second; + auto color_resolution = color_sensor_name == "OV9782" ? dai::ColorCameraProperties::SensorResolution::THE_800_P : + dai::ColorCameraProperties::SensorResolution::THE_1080_P; - m_device->startPipeline(*m_pipeline); + static constexpr const char* kPreviewStreamName = "ColorPreview"; + + auto pipeline = create_pipeline(config, streams, color_resolution); + + if (config.preview) + m_preview = PreviewStream::try_create(kPreviewStreamName, pipeline, color_resolution); + + m_device->startPipeline(pipeline); for (const auto& s : streams) - { m_queues[s.camera] = m_device->getOutputQueue(core::EnumNameStreamType(s.camera), 8, false); - } + + if (m_preview) + m_preview->setOutputQueue(m_device->getOutputQueue(kPreviewStreamName, 4, false)); std::cout << "OAK camera pipeline started" << std::endl; } +OakCamera::~OakCamera() = default; + dai::DeviceInfo OakCamera::find_device(const std::string& device_id) { auto devices = dai::Device::getAllAvailableDevices(); @@ -83,11 +98,11 @@ dai::DeviceInfo OakCamera::find_device(const std::string& device_id) // Pipeline construction // ============================================================================= -void OakCamera::create_pipeline(const OakConfig& config, - const std::vector& streams, - const std::unordered_map& sensors) +dai::Pipeline OakCamera::create_pipeline(const OakConfig& config, + const std::vector& streams, + dai::ColorCameraProperties::SensorResolution color_resolution) { - m_pipeline = std::make_shared(); + dai::Pipeline pipeline; bool need_color = has_stream(streams, core::StreamType_Color); bool need_mono_left = has_stream(streams, core::StreamType_MonoLeft); @@ -95,7 +110,7 @@ void OakCamera::create_pipeline(const OakConfig& config, auto create_h264_output = [&](dai::Node::Output& source, const char* stream_name) { - auto enc = m_pipeline->create(); + auto enc = pipeline.create(); enc->setDefaultProfilePreset(config.fps, dai::VideoEncoderProperties::Profile::H264_BASELINE); enc->setBitrate(config.bitrate); enc->setQuality(config.quality); @@ -103,7 +118,7 @@ void OakCamera::create_pipeline(const OakConfig& config, enc->setNumBFrames(0); enc->setRateControlMode(dai::VideoEncoderProperties::RateControlMode::CBR); - auto xout = m_pipeline->create(); + auto xout = pipeline.create(); xout->setStreamName(stream_name); source.link(enc->input); @@ -113,16 +128,9 @@ void OakCamera::create_pipeline(const OakConfig& config, // ---- Color camera ---- if (need_color) { - auto it = sensors.find(dai::CameraBoardSocket::CAM_A); - if (it == sensors.end()) - throw std::runtime_error("Color stream requested but no sensor found on CAM_A"); - - auto resolution = it->second == "OV9782" ? dai::ColorCameraProperties::SensorResolution::THE_800_P : - dai::ColorCameraProperties::SensorResolution::THE_1080_P; - - auto camRgb = m_pipeline->create(); + auto camRgb = pipeline.create(); camRgb->setBoardSocket(dai::CameraBoardSocket::CAM_A); - camRgb->setResolution(resolution); + camRgb->setResolution(color_resolution); camRgb->setFps(config.fps); camRgb->setColorOrder(dai::ColorCameraProperties::ColorOrder::BGR); @@ -132,7 +140,7 @@ void OakCamera::create_pipeline(const OakConfig& config, // ---- Mono cameras ---- if (need_mono_left) { - auto monoLeft = m_pipeline->create(); + auto monoLeft = pipeline.create(); monoLeft->setBoardSocket(dai::CameraBoardSocket::CAM_B); monoLeft->setResolution(dai::MonoCameraProperties::SensorResolution::THE_400_P); monoLeft->setFps(config.fps); @@ -142,13 +150,15 @@ void OakCamera::create_pipeline(const OakConfig& config, if (need_mono_right) { - auto monoRight = m_pipeline->create(); + auto monoRight = pipeline.create(); monoRight->setBoardSocket(dai::CameraBoardSocket::CAM_C); monoRight->setResolution(dai::MonoCameraProperties::SensorResolution::THE_400_P); monoRight->setFps(config.fps); create_h264_output(monoRight->out, core::EnumNameStreamType(core::StreamType_MonoRight)); } + + return pipeline; } // ============================================================================= @@ -183,6 +193,9 @@ void OakCamera::update() m_sink->on_frame(frame); ++m_frame_counts[type]; } + + if (m_preview) + m_preview->update(); } // ============================================================================= diff --git a/src/plugins/oak/core/oak_camera.hpp b/src/plugins/oak/core/oak_camera.hpp index f2ab34c60..01f7f1aac 100644 --- a/src/plugins/oak/core/oak_camera.hpp +++ b/src/plugins/oak/core/oak_camera.hpp @@ -17,8 +17,9 @@ namespace plugins namespace oak { -// Forward declaration -- FrameSink is defined in frame_sink.hpp. +// Forward declarations class FrameSink; +class PreviewStream; // ============================================================================ // Stream configuration @@ -41,6 +42,7 @@ struct OakConfig int bitrate = 8'000'000; int quality = 80; int keyframe_frequency = 30; + bool preview = false; }; struct OakFrame @@ -75,6 +77,7 @@ class OakCamera { public: OakCamera(const OakConfig& config, const std::vector& streams, std::unique_ptr sink); + ~OakCamera(); OakCamera(const OakCamera&) = delete; OakCamera& operator=(const OakCamera&) = delete; @@ -89,15 +92,17 @@ class OakCamera private: dai::DeviceInfo find_device(const std::string& device_id); - void create_pipeline(const OakConfig& config, - const std::vector& streams, - const std::unordered_map& sensors); + dai::Pipeline create_pipeline(const OakConfig& config, + const std::vector& streams, + dai::ColorCameraProperties::SensorResolution color_resolution); - std::shared_ptr m_pipeline; std::shared_ptr m_device; std::map> m_queues; + std::unique_ptr m_sink; std::map m_frame_counts; + + std::unique_ptr m_preview; }; } // namespace oak diff --git a/src/plugins/oak/core/preview_stream.cpp b/src/plugins/oak/core/preview_stream.cpp new file mode 100644 index 000000000..043c6ee94 --- /dev/null +++ b/src/plugins/oak/core/preview_stream.cpp @@ -0,0 +1,156 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +#include "preview_stream.hpp" + +#include +#include + +namespace plugins +{ +namespace oak +{ + +struct PreviewStream::Impl +{ + SDL_Window* window = nullptr; + SDL_Renderer* renderer = nullptr; + SDL_Texture* texture = nullptr; + int tex_width = 0; + int tex_height = 0; + + std::shared_ptr queue; + + ~Impl() + { + if (texture) + SDL_DestroyTexture(texture); + if (renderer) + SDL_DestroyRenderer(renderer); + if (window) + SDL_DestroyWindow(window); + SDL_Quit(); + } +}; + +PreviewStream::~PreviewStream() = default; + +std::unique_ptr PreviewStream::try_create(const std::string& name, + dai::Pipeline& pipeline, + dai::ColorCameraProperties::SensorResolution resolution) +{ + // Find existing ColorCamera on CAM_A, or create one + std::shared_ptr camRgb; + for (auto& node : pipeline.getAllNodes()) + { + auto cam = std::dynamic_pointer_cast(node); + if (cam && cam->getBoardSocket() == dai::CameraBoardSocket::CAM_A) + { + camRgb = cam; + break; + } + } + + if (!camRgb) + { + std::cout << "Creating new ColorCamera on CAM_A" << std::endl; + camRgb = pipeline.create(); + camRgb->setBoardSocket(dai::CameraBoardSocket::CAM_A); + camRgb->setResolution(resolution); + camRgb->setColorOrder(dai::ColorCameraProperties::ColorOrder::BGR); + } + + int preview_w = 640; + int preview_h = (resolution == dai::ColorCameraProperties::SensorResolution::THE_800_P) ? 400 : 360; + + camRgb->setPreviewSize(preview_w, preview_h); + camRgb->setInterleaved(true); + + auto xout = pipeline.create(); + xout->setStreamName(name); + camRgb->preview.link(xout->input); + + if (SDL_Init(SDL_INIT_VIDEO) != 0) + { + std::cerr << "Preview: SDL_Init failed: " << SDL_GetError() << std::endl; + return nullptr; + } + + auto impl = std::make_unique(); + + impl->window = SDL_CreateWindow( + name.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, preview_w, preview_h, SDL_WINDOW_SHOWN); + + if (!impl->window) + { + std::cerr << "Preview: SDL_CreateWindow failed: " << SDL_GetError() << std::endl; + return nullptr; + } + + impl->renderer = SDL_CreateRenderer(impl->window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (!impl->renderer) + impl->renderer = SDL_CreateRenderer(impl->window, -1, 0); + + if (!impl->renderer) + { + std::cerr << "Preview: SDL_CreateRenderer failed: " << SDL_GetError() << std::endl; + return nullptr; + } + + auto stream = std::unique_ptr(new PreviewStream()); + stream->m_impl = std::move(impl); + + std::cout << "Color preview enabled (" << preview_w << "x" << preview_h << ")" << std::endl; + return stream; +} + +void PreviewStream::setOutputQueue(std::shared_ptr queue) +{ + m_impl->queue = std::move(queue); +} + +void PreviewStream::update() +{ + auto frame = m_impl->queue->tryGet(); + if (!frame) + return; + + const auto* data = frame->getData().data(); + int width = frame->getWidth(); + int height = frame->getHeight(); + + if (width != m_impl->tex_width || height != m_impl->tex_height) + { + if (m_impl->texture) + SDL_DestroyTexture(m_impl->texture); + + m_impl->texture = + SDL_CreateTexture(m_impl->renderer, SDL_PIXELFORMAT_BGR24, SDL_TEXTUREACCESS_STREAMING, width, height); + + if (!m_impl->texture) + { + std::cerr << "Preview: SDL_CreateTexture failed: " << SDL_GetError() << std::endl; + return; + } + + m_impl->tex_width = width; + m_impl->tex_height = height; + } + + SDL_UpdateTexture(m_impl->texture, nullptr, data, width * 3); + SDL_RenderClear(m_impl->renderer); + SDL_RenderCopy(m_impl->renderer, m_impl->texture, nullptr, nullptr); + SDL_RenderPresent(m_impl->renderer); + + SDL_Event event; + while (SDL_PollEvent(&event)) + { + if (event.type == SDL_QUIT) + return; + if (event.type == SDL_KEYDOWN && (event.key.keysym.sym == SDLK_ESCAPE || event.key.keysym.sym == SDLK_q)) + return; + } +} + +} // namespace oak +} // namespace plugins diff --git a/src/plugins/oak/core/preview_stream.hpp b/src/plugins/oak/core/preview_stream.hpp new file mode 100644 index 000000000..6f258b94c --- /dev/null +++ b/src/plugins/oak/core/preview_stream.hpp @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include +#include + +namespace plugins +{ +namespace oak +{ + +/** + * @brief Self-contained color preview stream. + * + * Owns the full lifecycle: adds preview nodes to the pipeline, opens an SDL2 + * window, connects to the device output queue, and polls/displays frames. + */ +class PreviewStream +{ +public: + ~PreviewStream(); + + PreviewStream(const PreviewStream&) = delete; + PreviewStream& operator=(const PreviewStream&) = delete; + + /** + * @brief Wire preview nodes into an existing pipeline and create the window. + * + * Searches the pipeline for an existing ColorCamera on CAM_A. If none is + * found, creates and configures one. Then attaches preview output nodes. + * + * @return A valid PreviewStream, or nullptr if the SDL window could not be created. + */ + static std::unique_ptr try_create(const std::string& name, + dai::Pipeline& pipeline, + dai::ColorCameraProperties::SensorResolution resolution); + + /** @brief Set the output queue to poll frames from. Call after Device::startPipeline. */ + void setOutputQueue(std::shared_ptr queue); + + /** @brief Poll the queue and display a frame if available. */ + void update(); + +private: + PreviewStream() = default; + + struct Impl; + std::unique_ptr m_impl; +}; + +} // namespace oak +} // namespace plugins diff --git a/src/plugins/oak/main.cpp b/src/plugins/oak/main.cpp index 9e7ac3433..a0563b11c 100644 --- a/src/plugins/oak/main.cpp +++ b/src/plugins/oak/main.cpp @@ -3,6 +3,7 @@ #include "core/frame_sink.hpp" #include "core/oak_camera.hpp" +#include "core/preview_stream.hpp" #include #include @@ -105,6 +106,8 @@ void print_usage(const char* program_name) << "\nMetadata (mutually exclusive):\n" << " --collection-prefix=PREFIX Push metadata via OpenXR tensor extensions\n" << " --mcap-filename=PATH Record metadata to an MCAP file\n" + << "\nPreview:\n" + << " --preview Show live color camera preview via OpenCV window\n" << "\nGeneral:\n" << " --help Show this help message\n" << "\nExamples:\n" @@ -155,6 +158,10 @@ try { camera_config.device_id = arg.substr(12); } + else if (arg == "--preview") + { + camera_config.preview = true; + } else if (arg.find("--collection-prefix=") == 0) { collection_prefix = arg.substr(20); From d3c4cb8955a401a50c1d887d0ab28367691b1409 Mon Sep 17 00:00:00 2001 From: Shao Su Date: Mon, 2 Mar 2026 11:05:28 -0800 Subject: [PATCH 2/4] update the sdlv2 version --- src/plugins/oak/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/oak/CMakeLists.txt b/src/plugins/oak/CMakeLists.txt index 1834b301f..b3edf25a1 100644 --- a/src/plugins/oak/CMakeLists.txt +++ b/src/plugins/oak/CMakeLists.txt @@ -32,7 +32,7 @@ message(STATUS "Building OAK camera plugin with DepthAI ${depthai_VERSION}") # ============================================================================== # SDL2 (for live preview window, bundled via FetchContent) # ============================================================================== -set(SDL2_VERSION "2.30.11") +set(SDL2_VERSION "2.32.10") set(SDL_TEST OFF CACHE BOOL "" FORCE) set(SDL_SHARED ON CACHE BOOL "" FORCE) From ded82fdbae77c1b4d582454f9ac9d1098b44ad6a Mon Sep 17 00:00:00 2001 From: Shao Su Date: Mon, 2 Mar 2026 12:05:47 -0800 Subject: [PATCH 3/4] Resolve AI comments --- src/plugins/oak/CMakeLists.txt | 11 +++---- src/plugins/oak/README.md | 44 +++++++++++++++---------- src/plugins/oak/core/oak_camera.cpp | 2 +- src/plugins/oak/core/preview_stream.cpp | 36 +++++++------------- src/plugins/oak/core/preview_stream.hpp | 8 ++--- src/plugins/oak/main.cpp | 5 ++- 6 files changed, 48 insertions(+), 58 deletions(-) diff --git a/src/plugins/oak/CMakeLists.txt b/src/plugins/oak/CMakeLists.txt index b3edf25a1..77fe2cbc9 100644 --- a/src/plugins/oak/CMakeLists.txt +++ b/src/plugins/oak/CMakeLists.txt @@ -35,8 +35,8 @@ message(STATUS "Building OAK camera plugin with DepthAI ${depthai_VERSION}") set(SDL2_VERSION "2.32.10") set(SDL_TEST OFF CACHE BOOL "" FORCE) -set(SDL_SHARED ON CACHE BOOL "" FORCE) -set(SDL_STATIC OFF CACHE BOOL "" FORCE) +set(SDL_SHARED OFF CACHE BOOL "" FORCE) +set(SDL_STATIC ON CACHE BOOL "" FORCE) FetchContent_Declare( sdl2 @@ -63,13 +63,10 @@ target_link_libraries(camera_plugin_oak mcap::mcap oxr::oxr_core pusherio::pusherio - SDL2::SDL2 + SDL2::SDL2-static ) -# Bundle SDL2 shared library alongside the binary -install(TARGETS SDL2 LIBRARY DESTINATION plugins/oak_camera) - -# Set RPATH to find bundled libraries in same directory +# Set RPATH so the binary finds bundled shared libraries (e.g. libusb) in its own directory set_target_properties(camera_plugin_oak PROPERTIES INSTALL_RPATH "$ORIGIN" ) diff --git a/src/plugins/oak/README.md b/src/plugins/oak/README.md index e054a8304..05ea41a8d 100644 --- a/src/plugins/oak/README.md +++ b/src/plugins/oak/README.md @@ -29,11 +29,23 @@ cmake --build build --target camera_plugin_oak --parallel ## Usage ```bash -# Record to a file (--output is required) -./build/src/plugins/oak/camera_plugin_oak --output=./recordings/session.h264 +# Record a single color stream +./build/src/plugins/oak/camera_plugin_oak --add-stream=camera=Color,output=./color.h264 -# Custom path and camera settings -./build/src/plugins/oak/camera_plugin_oak --output=/tmp/session.h264 --width=1920 --height=1080 --fps=30 --bitrate=15000000 +# Record multiple streams +./build/src/plugins/oak/camera_plugin_oak \ + --add-stream=camera=Color,output=./color.h264 \ + --add-stream=camera=MonoLeft,output=./left.h264 \ + --add-stream=camera=MonoRight,output=./right.h264 + +# Record with a live preview window +./build/src/plugins/oak/camera_plugin_oak \ + --add-stream=camera=Color,output=./color.h264 --preview + +# Record metadata to MCAP +./build/src/plugins/oak/camera_plugin_oak \ + --add-stream=camera=Color,output=./color.h264 \ + --mcap-filename=./metadata.mcap # Show help ./build/src/plugins/oak/camera_plugin_oak --help @@ -45,13 +57,14 @@ Press `Ctrl+C` to stop recording. | Option | Default | Description | |--------|---------|-------------| -| `--width` | 1920 | Frame width | -| `--height` | 1080 | Frame height | -| `--fps` | 30 | Frame rate | -| `--bitrate` | 8000000 | H.264 bitrate (bps) | -| `--quality` | 80 | H.264 quality (1-100) | -| `--output` | (required) | Full path for recording file | -| `--collection-prefix` | oak_camera | Tensor collection prefix for metadata (per-stream IDs: `prefix/StreamName`) | +| `--add-stream=camera=,output=` | (at least one required) | Add a capture stream. `camera` is one of `Color`, `MonoLeft`, `MonoRight`. Repeatable. | +| `--fps=N` | 30 | Frame rate for all streams | +| `--bitrate=N` | 8000000 | H.264 bitrate (bps) | +| `--quality=N` | 80 | H.264 quality (1-100) | +| `--device-id=ID` | first available | OAK device MxId | +| `--preview` | off | Open a live SDL2 window showing the color camera feed | +| `--collection-prefix=PREFIX` | | Push per-frame metadata via OpenXR tensor extensions | +| `--mcap-filename=PATH` | | Record per-frame metadata to an MCAP file | ## Architecture @@ -70,15 +83,10 @@ Press `Ctrl+C` to stop recording. ## Dependencies -**System dependencies** (install before building): - -```bash -sudo apt install libusb-1.0-0-dev -``` - -**Automatically built via CMake:** +All dependencies are built automatically via CMake: - **DepthAI** - OAK camera interface +- **SDL2** - Live preview window (used by `--preview`) ## Output Format diff --git a/src/plugins/oak/core/oak_camera.cpp b/src/plugins/oak/core/oak_camera.cpp index ffde4aa8d..d9dd88af7 100644 --- a/src/plugins/oak/core/oak_camera.cpp +++ b/src/plugins/oak/core/oak_camera.cpp @@ -54,7 +54,7 @@ OakCamera::OakCamera(const OakConfig& config, const std::vector& s auto pipeline = create_pipeline(config, streams, color_resolution); if (config.preview) - m_preview = PreviewStream::try_create(kPreviewStreamName, pipeline, color_resolution); + m_preview = PreviewStream::create(kPreviewStreamName, pipeline, color_resolution); m_device->startPipeline(pipeline); diff --git a/src/plugins/oak/core/preview_stream.cpp b/src/plugins/oak/core/preview_stream.cpp index 043c6ee94..50255ceff 100644 --- a/src/plugins/oak/core/preview_stream.cpp +++ b/src/plugins/oak/core/preview_stream.cpp @@ -5,6 +5,7 @@ #include #include +#include namespace plugins { @@ -29,15 +30,15 @@ struct PreviewStream::Impl SDL_DestroyRenderer(renderer); if (window) SDL_DestroyWindow(window); - SDL_Quit(); + SDL_QuitSubSystem(SDL_INIT_VIDEO); } }; PreviewStream::~PreviewStream() = default; -std::unique_ptr PreviewStream::try_create(const std::string& name, - dai::Pipeline& pipeline, - dai::ColorCameraProperties::SensorResolution resolution) +std::unique_ptr PreviewStream::create(const std::string& name, + dai::Pipeline& pipeline, + dai::ColorCameraProperties::SensorResolution resolution) { // Find existing ColorCamera on CAM_A, or create one std::shared_ptr camRgb; @@ -71,10 +72,7 @@ std::unique_ptr PreviewStream::try_create(const std::string& name camRgb->preview.link(xout->input); if (SDL_Init(SDL_INIT_VIDEO) != 0) - { - std::cerr << "Preview: SDL_Init failed: " << SDL_GetError() << std::endl; - return nullptr; - } + throw std::runtime_error(std::string("Preview: SDL_Init failed: ") + SDL_GetError()); auto impl = std::make_unique(); @@ -82,20 +80,14 @@ std::unique_ptr PreviewStream::try_create(const std::string& name name.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, preview_w, preview_h, SDL_WINDOW_SHOWN); if (!impl->window) - { - std::cerr << "Preview: SDL_CreateWindow failed: " << SDL_GetError() << std::endl; - return nullptr; - } + throw std::runtime_error(std::string("Preview: SDL_CreateWindow failed: ") + SDL_GetError()); impl->renderer = SDL_CreateRenderer(impl->window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); if (!impl->renderer) impl->renderer = SDL_CreateRenderer(impl->window, -1, 0); if (!impl->renderer) - { - std::cerr << "Preview: SDL_CreateRenderer failed: " << SDL_GetError() << std::endl; - return nullptr; - } + throw std::runtime_error(std::string("Preview: SDL_CreateRenderer failed: ") + SDL_GetError()); auto stream = std::unique_ptr(new PreviewStream()); stream->m_impl = std::move(impl); @@ -111,6 +103,9 @@ void PreviewStream::setOutputQueue(std::shared_ptr queue) void PreviewStream::update() { + if (!m_impl->queue) + throw std::runtime_error("Preview: Output queue not set"); + auto frame = m_impl->queue->tryGet(); if (!frame) return; @@ -141,15 +136,6 @@ void PreviewStream::update() SDL_RenderClear(m_impl->renderer); SDL_RenderCopy(m_impl->renderer, m_impl->texture, nullptr, nullptr); SDL_RenderPresent(m_impl->renderer); - - SDL_Event event; - while (SDL_PollEvent(&event)) - { - if (event.type == SDL_QUIT) - return; - if (event.type == SDL_KEYDOWN && (event.key.keysym.sym == SDLK_ESCAPE || event.key.keysym.sym == SDLK_q)) - return; - } } } // namespace oak diff --git a/src/plugins/oak/core/preview_stream.hpp b/src/plugins/oak/core/preview_stream.hpp index 6f258b94c..f40c3fa4e 100644 --- a/src/plugins/oak/core/preview_stream.hpp +++ b/src/plugins/oak/core/preview_stream.hpp @@ -33,11 +33,11 @@ class PreviewStream * Searches the pipeline for an existing ColorCamera on CAM_A. If none is * found, creates and configures one. Then attaches preview output nodes. * - * @return A valid PreviewStream, or nullptr if the SDL window could not be created. + * @throws std::runtime_error if SDL initialisation or window creation fails. */ - static std::unique_ptr try_create(const std::string& name, - dai::Pipeline& pipeline, - dai::ColorCameraProperties::SensorResolution resolution); + static std::unique_ptr create(const std::string& name, + dai::Pipeline& pipeline, + dai::ColorCameraProperties::SensorResolution resolution); /** @brief Set the output queue to poll frames from. Call after Device::startPipeline. */ void setOutputQueue(std::shared_ptr queue); diff --git a/src/plugins/oak/main.cpp b/src/plugins/oak/main.cpp index a0563b11c..62510d8fb 100644 --- a/src/plugins/oak/main.cpp +++ b/src/plugins/oak/main.cpp @@ -3,7 +3,6 @@ #include "core/frame_sink.hpp" #include "core/oak_camera.hpp" -#include "core/preview_stream.hpp" #include #include @@ -107,13 +106,13 @@ void print_usage(const char* program_name) << " --collection-prefix=PREFIX Push metadata via OpenXR tensor extensions\n" << " --mcap-filename=PATH Record metadata to an MCAP file\n" << "\nPreview:\n" - << " --preview Show live color camera preview via OpenCV window\n" + << " --preview Show live color camera preview via SDL2 window\n" << "\nGeneral:\n" << " --help Show this help message\n" << "\nExamples:\n" << " " << program_name << " --add-stream camera=Color,output=./color.h264\n" << " " << program_name - << " --add-stream=camera=Color,output=./color.h264 --add-stream=camera=LeftMono,output=./left.h264 --add-stream=camera=RightMono,output=./right.h264\n"; + << " --add-stream=camera=Color,output=./color.h264 --add-stream=camera=MonoLeft,output=./left.h264 --add-stream=camera=MonoRight,output=./right.h264\n"; } // ============================================================================= From 3d08a8b97a4e34b7848420ea2641d7cd71cb5073 Mon Sep 17 00:00:00 2001 From: Shao Su Date: Tue, 3 Mar 2026 14:43:10 -0800 Subject: [PATCH 4/4] Use depthai 3.3.0 and change the build system --- .github/workflows/build-ubuntu.yml | 32 +++- .github/workflows/build-windows.yml | 31 ++-- CMakeLists.txt | 22 +-- cmake/Hunter/config.cmake | 13 -- cmake/SetupHunter.cmake | 112 -------------- src/plugins/oak/CMakeLists.txt | 187 +++++++++++++++++++++--- src/plugins/oak/README.md | 29 +++- src/plugins/oak/core/oak_camera.cpp | 93 +++++------- src/plugins/oak/core/oak_camera.hpp | 13 +- src/plugins/oak/core/preview_stream.cpp | 40 ++--- src/plugins/oak/core/preview_stream.hpp | 21 ++- vcpkg.json | 28 ++++ 12 files changed, 327 insertions(+), 294 deletions(-) delete mode 100644 cmake/Hunter/config.cmake delete mode 100644 cmake/SetupHunter.cmake create mode 100644 vcpkg.json diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index ef7e43e9a..fc5b80df2 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -39,7 +39,8 @@ jobs: - name: Install Apt dependencies run: | sudo apt-get update - sudo apt-get install -y build-essential cmake libx11-dev clang-format-14 ccache + sudo apt-get install -y build-essential cmake libx11-dev clang-format-14 ccache \ + autoconf automake libtool pkg-config libudev-dev - name: Install patchelf (Release only) if: ${{ matrix.build_type == 'Release' }} @@ -52,14 +53,24 @@ jobs: with: ngc-cli-api-key: ${{ secrets.NGC_TELEOP_CORE_GITHUB_SERVICE_KEY }} - # Hunter cache - caches depthai dependencies (OpenSSL, CURL, etc.) - - name: Cache Hunter packages + # vcpkg - required when BUILD_PLUGIN_OAK_CAMERA=ON for DepthAI v3.x deps + - name: Cache vcpkg uses: actions/cache@v4 with: - path: ~/.hunter - key: hunter-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('cmake/SetupHunter.cmake') }} + path: | + ~/vcpkg + build/vcpkg_installed + key: vcpkg-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('vcpkg.json') }} restore-keys: | - hunter-${{ runner.os }}-${{ runner.arch }}- + vcpkg-${{ runner.os }}-${{ runner.arch }}- + + - name: Setup vcpkg + run: | + if [ ! -f ~/vcpkg/vcpkg ]; then + git clone https://github.com/microsoft/vcpkg ~/vcpkg + ~/vcpkg/bootstrap-vcpkg.sh -disableMetrics + fi + echo "VCPKG_ROOT=$HOME/vcpkg" >> $GITHUB_ENV - name: Cache ccache uses: actions/cache@v4 @@ -75,7 +86,14 @@ jobs: ccache --show-config - name: Configure CMake - run: cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DISAAC_TELEOP_PYTHON_VERSION=${{ matrix.python_version }} -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DBUILD_PLUGIN_OAK_CAMERA=ON + run: > + cmake -B build + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -DISAAC_TELEOP_PYTHON_VERSION=${{ matrix.python_version }} + -DCMAKE_C_COMPILER_LAUNCHER=ccache + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + -DBUILD_PLUGIN_OAK_CAMERA=ON + -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake - name: Build run: cmake --build build --parallel 4 diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 2c66fd21d..22fddff26 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -36,15 +36,29 @@ jobs: - name: Install sccache run: choco install -y sccache - # Hunter cache - caches depthai dependencies - # Note: Hunter uses C:/.hunter on Windows (at drive root) - - name: Cache Hunter packages + # Put cl.exe and other build tools in PATH. + # Must run before vcpkg setup so our VCPKG_ROOT isn't overridden by vcvarsall. + - name: Setup MSVC (Developer Command Prompt) + uses: ilammy/msvc-dev-cmd@v1 + + # vcpkg - required when BUILD_PLUGIN_OAK_CAMERA=ON for DepthAI v3.x deps + - name: Cache vcpkg uses: actions/cache@v4 with: - path: C:/.hunter - key: hunter-${{ runner.os }}-${{ hashFiles('cmake/SetupHunter.cmake') }} + path: | + C:\vcpkg + build\vcpkg_installed + key: vcpkg-${{ runner.os }}-${{ hashFiles('vcpkg.json') }} restore-keys: | - hunter-${{ runner.os }}- + vcpkg-${{ runner.os }}- + + - name: Setup vcpkg + run: | + if (!(Test-Path C:\vcpkg\vcpkg.exe)) { + git clone https://github.com/microsoft/vcpkg C:\vcpkg + C:\vcpkg\bootstrap-vcpkg.bat -disableMetrics + } + echo "VCPKG_ROOT=C:\vcpkg" >> $env:GITHUB_ENV - name: Cache sccache uses: actions/cache@v4 @@ -58,10 +72,6 @@ jobs: run: | sccache --version - # Put cl.exe and other build tools in PATH - - name: Setup MSVC (Developer Command Prompt) - uses: ilammy/msvc-dev-cmd@v1 - - name: Configure CMake # Note: # sccache does not work with VSBuild, so we use Ninja generator here. @@ -77,6 +87,7 @@ jobs: -DCMAKE_CXX_COMPILER="cl.exe" -DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=Embedded -DBUILD_PLUGIN_OAK_CAMERA=ON + -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake - name: Build run: cmake --build build --parallel diff --git a/CMakeLists.txt b/CMakeLists.txt index 5466ab596..c231babf9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 cmake_minimum_required(VERSION 3.20...3.25) @@ -10,22 +10,12 @@ include(cmake/IsaacTeleopVersion.cmake) isaac_teleop_read_version("${CMAKE_CURRENT_SOURCE_DIR}/VERSION" ISAAC_TELEOP_VERSION ISAAC_TELEOP_PYPROJECT_VERSION) # ============================================================================== -# Install Prefix Setup (must be before HunterGate) +# Install Prefix Setup # ============================================================================== -# Set default install prefix early, before HunterGate which may affect -# CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT if(NOT DEFINED CMAKE_INSTALL_PREFIX OR CMAKE_INSTALL_PREFIX STREQUAL "" OR CMAKE_INSTALL_PREFIX MATCHES "^/usr/local") set(CMAKE_INSTALL_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/install" CACHE PATH "Install prefix" FORCE) endif() -# OAK camera plugin uses Hunter/DepthAI; optional so builds like teleop_ros2 can skip it. -option(BUILD_PLUGIN_OAK_CAMERA "Build OAK camera plugin (uses Hunter/DepthAI)" OFF) -# ============================================================================== - -# HunterGate Setup (must be before project()) -# Defines BUILD_PLUGINS option and initializes Hunter for DepthAI dependencies -include(cmake/SetupHunter.cmake) - project(IsaacTeleop VERSION ${ISAAC_TELEOP_VERSION} LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) @@ -51,13 +41,9 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) endif() -# Note: CMAKE_INSTALL_PREFIX is set early (before HunterGate) to avoid issues -# with CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT being affected by Hunter's -# internal project() call. - # Options -# Note: BUILD_PLUGINS is defined in cmake/SetupHunter.cmake before project() -# Note: BUILD_PYTHON_BINDINGS is defined in cmake/SetupPython.cmake +option(BUILD_PLUGINS "Build plugins" ON) +option(BUILD_PLUGIN_OAK_CAMERA "Build OAK camera plugin (requires vcpkg for DepthAI v3.x)" OFF) option(BUILD_EXAMPLES "Build examples" ON) option(BUILD_EXAMPLE_TELEOP_ROS2 "Build only the teleop_ros2 example (e.g. for Docker)" OFF) option(BUILD_TESTING "Build unit tests" ON) diff --git a/cmake/Hunter/config.cmake b/cmake/Hunter/config.cmake deleted file mode 100644 index f8649a804..000000000 --- a/cmake/Hunter/config.cmake +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Hunter package configuration aggregator. -# This file includes project-specific configs for all Hunter-managed dependencies. -# To add a new project's Hunter config, create a config-.cmake file and include it below. - -# DepthAI v2.29.0 dependencies -# Source: https://github.com/luxonis/depthai-core/blob/v2.29.0/cmake/Hunter/config.cmake -include("${CMAKE_CURRENT_LIST_DIR}/config-depthai.cmake") - -# Future project configs can be added here: -# include("${CMAKE_CURRENT_LIST_DIR}/config-another-project.cmake") diff --git a/cmake/SetupHunter.cmake b/cmake/SetupHunter.cmake deleted file mode 100644 index 0abf0a652..000000000 --- a/cmake/SetupHunter.cmake +++ /dev/null @@ -1,112 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# ============================================================================== -# HunterGate Setup (must be before project()) -# ============================================================================== -# Required for DepthAI plugin. Hunter is initialized here so that when DepthAI -# is added via FetchContent, it reuses our Hunter configuration. - -option(BUILD_PLUGINS "Build plugins" ON) -# Hunter/DepthAI are only needed for the OAK camera plugin. -if(BUILD_PLUGINS AND BUILD_PLUGIN_OAK_CAMERA) - # Set variables needed by Hunter config for DepthAI dependencies - option(DEPTHAI_ENABLE_LIBUSB "Enable libusb for DepthAI" ON) - if(WIN32) - set(DEPTHAI_CURL_USE_SCHANNEL ON) - set(DEPTHAI_CURL_USE_OPENSSL OFF) - else() - set(DEPTHAI_CURL_USE_SCHANNEL OFF) - set(DEPTHAI_CURL_USE_OPENSSL ON) - endif() - - # Enable parallel builds for Hunter packages - # This speeds up the initial build of depthai dependencies significantly - include(ProcessorCount) - ProcessorCount(NPROC) - if(NOT NPROC EQUAL 0) - set(HUNTER_JOBS_NUMBER ${NPROC} CACHE STRING "Parallel jobs for Hunter builds") - message(STATUS "Hunter parallel jobs: ${HUNTER_JOBS_NUMBER}") - endif() - - # Download HunterGate.cmake from GitHub if not present or empty - set(HUNTERGATE_VERSION "v0.11.0") - set(HUNTERGATE_SHA1 "c581aaacda7fee2e4586adf39084f24709b8e51f") - set(HUNTERGATE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/HunterGate.cmake") - - set(_huntergate_need_download FALSE) - if(NOT EXISTS "${HUNTERGATE_PATH}") - set(_huntergate_need_download TRUE) - else() - file(SIZE "${HUNTERGATE_PATH}" _huntergate_size) - if(_huntergate_size EQUAL 0) - file(REMOVE "${HUNTERGATE_PATH}") - set(_huntergate_need_download TRUE) - endif() - endif() - - if(_huntergate_need_download) - message(STATUS "Downloading HunterGate.cmake ${HUNTERGATE_VERSION}...") - file(DOWNLOAD - "https://raw.githubusercontent.com/cpp-pm/gate/${HUNTERGATE_VERSION}/cmake/HunterGate.cmake" - "${HUNTERGATE_PATH}" - EXPECTED_HASH SHA1=${HUNTERGATE_SHA1} - STATUS huntergate_download_status - TLS_VERIFY ON - ) - list(GET huntergate_download_status 0 huntergate_status_code) - list(GET huntergate_download_status 1 huntergate_status_msg) - if(NOT huntergate_status_code EQUAL 0) - file(REMOVE "${HUNTERGATE_PATH}") # Clean up failed download - message(FATAL_ERROR "Failed to download HunterGate.cmake: ${huntergate_status_msg}") - endif() - message(STATUS "HunterGate.cmake downloaded successfully") - endif() - - # Download DepthAI Hunter config from GitHub if not present or empty - set(DEPTHAI_VERSION "v2.29.0") - set(DEPTHAI_CONFIG_SHA1 "a88698ab81b7edef79a2446edcf649dc92cddcbc") - set(DEPTHAI_CONFIG_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Hunter/config-depthai.cmake") - - set(_depthai_config_need_download FALSE) - if(NOT EXISTS "${DEPTHAI_CONFIG_PATH}") - set(_depthai_config_need_download TRUE) - else() - file(SIZE "${DEPTHAI_CONFIG_PATH}" _depthai_config_size) - if(_depthai_config_size EQUAL 0) - file(REMOVE "${DEPTHAI_CONFIG_PATH}") - set(_depthai_config_need_download TRUE) - endif() - endif() - - if(_depthai_config_need_download) - message(STATUS "Downloading Hunter config for DepthAI ${DEPTHAI_VERSION}...") - file(DOWNLOAD - "https://raw.githubusercontent.com/luxonis/depthai-core/${DEPTHAI_VERSION}/cmake/Hunter/config.cmake" - "${DEPTHAI_CONFIG_PATH}" - EXPECTED_HASH SHA1=${DEPTHAI_CONFIG_SHA1} - STATUS depthai_config_download_status - TLS_VERIFY ON - ) - list(GET depthai_config_download_status 0 depthai_config_status_code) - list(GET depthai_config_download_status 1 depthai_config_status_msg) - if(NOT depthai_config_status_code EQUAL 0) - file(REMOVE "${DEPTHAI_CONFIG_PATH}") # Clean up failed download - message(FATAL_ERROR "Failed to download DepthAI Hunter config: ${depthai_config_status_msg}") - endif() - message(STATUS "DepthAI Hunter config downloaded successfully") - endif() - - include("${HUNTERGATE_PATH}") - - # Set CMAKE_POLICY_VERSION_MINIMUM as environment variable so it's inherited - # by all child CMake processes (Hunter ExternalProject builds) - # This fixes compatibility with CMake >= 4.0 for packages with old cmake_minimum_required - set(ENV{CMAKE_POLICY_VERSION_MINIMUM} "3.5") - - HunterGate( - URL "https://github.com/cpp-pm/hunter/archive/9d9242b60d5236269f894efd3ddd60a9ca83dd7f.tar.gz" - SHA1 "16cc954aa723bccd16ea45fc91a858d0c5246376" - LOCAL # Uses cmake/Hunter/config.cmake - ) -endif() diff --git a/src/plugins/oak/CMakeLists.txt b/src/plugins/oak/CMakeLists.txt index 77fe2cbc9..b111df13a 100644 --- a/src/plugins/oak/CMakeLists.txt +++ b/src/plugins/oak/CMakeLists.txt @@ -2,32 +2,185 @@ # SPDX-License-Identifier: Apache-2.0 # ============================================================================== -# OAK Camera Plugin +# OAK Camera Plugin (DepthAI v3.x with vcpkg dependencies) # ============================================================================== -# Uses FetchContent with HunterGate initialized at top-level for single-pass build. +# This plugin uses FetchContent for DepthAI and vcpkg for its transitive deps. +# +# Prerequisites: +# 1. Build tools for vcpkg to build libusb (Linux only): +# sudo apt install libudev-dev autoconf automake libtool pkg-config +# +# 2. Install vcpkg (one-time): +# git clone https://github.com/microsoft/vcpkg ~/.vcpkg +# ~/.vcpkg/bootstrap-vcpkg.sh +# echo 'export VCPKG_ROOT=~/.vcpkg' >> ~/.bashrc && source ~/.bashrc +# +# 3. Configure with vcpkg toolchain: +# cmake -B build -DBUILD_PLUGIN_OAK_CAMERA=ON \ +# -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake # ============================================================================== +# ============================================================================== +# Prerequisite Checks +# ============================================================================== + +# Check 1: vcpkg toolchain must be loaded and functional +if(NOT DEFINED VCPKG_INSTALLED_DIR OR NOT DEFINED VCPKG_TARGET_TRIPLET) + message(FATAL_ERROR + "================================================================================\n" + "OAK camera plugin requires vcpkg for DepthAI v3.x dependencies.\n" + "\n" + "Make sure VCPKG_ROOT is set and the toolchain file path is correct:\n" + " export VCPKG_ROOT=~/.vcpkg\n" + "\n" + "Setup vcpkg (one-time):\n" + " git clone https://github.com/microsoft/vcpkg ~/.vcpkg\n" + " ~/.vcpkg/bootstrap-vcpkg.sh\n" + " echo 'export VCPKG_ROOT=~/.vcpkg' >> ~/.bashrc && source ~/.bashrc\n" + "\n" + "Then configure (clean build dir first!):\n" + " rm -rf build\n" + " cmake -B build -DBUILD_PLUGIN_OAK_CAMERA=ON" + " -DCMAKE_TOOLCHAIN_FILE=$$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake\n" + "\n" + "Or disable OAK camera plugin:\n" + " cmake -B build -DBUILD_PLUGIN_OAK_CAMERA=OFF\n" + "================================================================================\n" + ) +endif() + +# Check 2: libudev (Linux only — required for vcpkg to build libusb) +if(UNIX AND NOT APPLE) + find_package(PkgConfig QUIET) + if(PkgConfig_FOUND) + pkg_check_modules(LIBUDEV QUIET libudev) + endif() + + if(NOT LIBUDEV_FOUND) + find_path(LIBUDEV_INCLUDE_DIR NAMES libudev.h) + find_library(LIBUDEV_LIBRARY NAMES udev) + if(LIBUDEV_INCLUDE_DIR AND LIBUDEV_LIBRARY) + set(LIBUDEV_FOUND TRUE) + endif() + endif() + + if(NOT LIBUDEV_FOUND) + message(FATAL_ERROR + "================================================================================\n" + "OAK camera plugin requires libudev-dev for vcpkg to build libusb.\n" + "\n" + "Install on Ubuntu/Debian:\n" + " sudo apt install libudev-dev autoconf automake libtool pkg-config\n" + "\n" + "Or disable OAK camera plugin:\n" + " cmake -B build -DBUILD_PLUGIN_OAK_CAMERA=OFF\n" + "================================================================================\n" + ) + endif() + message(STATUS "Found libudev (vcpkg will build libusb)") +endif() + include(FetchContent) # ============================================================================== -# DepthAI Setup +# DepthAI v3.x Setup via FetchContent # ============================================================================== -message(STATUS "Configuring DepthAI...") +set(DEPTHAI_VERSION "3.3.0") -# Configure DepthAI build options set(DEPTHAI_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) set(DEPTHAI_BUILD_TESTS OFF CACHE BOOL "" FORCE) set(DEPTHAI_BUILD_DOCS OFF CACHE BOOL "" FORCE) +set(DEPTHAI_INSTALL OFF CACHE BOOL "" FORCE) + +# Use system vcpkg, not DepthAI's internal bootstrap +set(DEPTHAI_BOOTSTRAP_VCPKG OFF CACHE BOOL "" FORCE) + +# Use vcpkg-provided packages where available +set(DEPTHAI_JSON_EXTERNAL ON CACHE BOOL "" FORCE) +set(DEPTHAI_LIBNOP_EXTERNAL OFF CACHE BOOL "" FORCE) +set(DEPTHAI_XTENSOR_EXTERNAL OFF CACHE BOOL "" FORCE) + +# Disable optional features we don't need set(DEPTHAI_OPENCV_SUPPORT OFF CACHE BOOL "" FORCE) +set(DEPTHAI_PCL_SUPPORT OFF CACHE BOOL "" FORCE) +set(DEPTHAI_XTENSOR_SUPPORT ON CACHE BOOL "" FORCE) +set(DEPTHAI_ENABLE_BACKWARD OFF CACHE BOOL "" FORCE) +set(DEPTHAI_ENABLE_CURL OFF CACHE BOOL "" FORCE) +set(DEPTHAI_ENABLE_APRIL_TAG OFF CACHE BOOL "" FORCE) +set(DEPTHAI_ENABLE_REMOTE_CONNECTION OFF CACHE BOOL "" FORCE) +set(DEPTHAI_ENABLE_PROTOBUF OFF CACHE BOOL "" FORCE) +set(DEPTHAI_ENABLE_MP4V2 OFF CACHE BOOL "" FORCE) +set(DEPTHAI_DYNAMIC_CALIBRATION_SUPPORT OFF CACHE BOOL "" FORCE) + +# ============================================================================== +# Bridge vcpkg's libusb to DepthAI's expected usb-1.0 target +# ============================================================================== +set(USB10_CONFIG_DIR "${CMAKE_BINARY_DIR}/cmake/usb-1.0") +file(MAKE_DIRECTORY "${USB10_CONFIG_DIR}") +file(WRITE "${USB10_CONFIG_DIR}/usb-1.0Config.cmake" [=[ +if(TARGET usb-1.0) + set(usb-1.0_FOUND TRUE) + return() +endif() + +find_package(PkgConfig REQUIRED) +pkg_check_modules(LIBUSB REQUIRED IMPORTED_TARGET libusb-1.0) + +add_library(usb-1.0 ALIAS PkgConfig::LIBUSB) +set(usb-1.0_FOUND TRUE) +message(STATUS "usb-1.0: using vcpkg libusb via pkg-config") +]=]) + +set(usb-1.0_DIR "${USB10_CONFIG_DIR}" CACHE PATH "" FORCE) +message(STATUS "Generated usb-1.0 config at ${USB10_CONFIG_DIR}") + +# XLink (a DepthAI sub-dependency) includes directly +# without propagating vcpkg's include dirs through its targets. +if(DEFINED VCPKG_INSTALLED_DIR AND DEFINED VCPKG_TARGET_TRIPLET) + include_directories(SYSTEM "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/include") +endif() + +message(STATUS "Fetching DepthAI v${DEPTHAI_VERSION}...") FetchContent_Declare( depthai - URL https://github.com/luxonis/depthai-core/releases/download/v2.29.0/depthai-core-v2.29.0.tar.gz + GIT_REPOSITORY https://github.com/luxonis/depthai-core.git + GIT_TAG v${DEPTHAI_VERSION} + GIT_SHALLOW TRUE ) -FetchContent_MakeAvailable(depthai) +FetchContent_GetProperties(depthai) +if(NOT depthai_POPULATED) + FetchContent_Populate(depthai) + + # Patch v3.3.0 bug: recordConfig should be pipeline->recordConfig + # Guard: only apply if the unpatched pattern is still present (idempotent) + if(DEPTHAI_VERSION VERSION_EQUAL "3.3.0") + set(_patch_file "${depthai_SOURCE_DIR}/src/utility/PipelineImplHelper.cpp") + if(EXISTS "${_patch_file}") + file(READ "${_patch_file}" _patch_content) + string(FIND "${_patch_content}" "pipeline->recordConfig.state = RecordConfig::RecordReplayState::NONE" _already_patched) + if(_already_patched EQUAL -1) + string(REPLACE + "recordConfig.state = RecordConfig::RecordReplayState::NONE" + "pipeline->recordConfig.state = RecordConfig::RecordReplayState::NONE" + _patch_content "${_patch_content}") + file(WRITE "${_patch_file}" "${_patch_content}") + message(STATUS "Applied v3.3.0 recordConfig patch") + else() + message(STATUS "v3.3.0 recordConfig patch already applied") + endif() + endif() + endif() + + # Override export() to no-op — DepthAI's export fails with vcpkg IMPORTED targets + function(export) + endfunction() + + add_subdirectory(${depthai_SOURCE_DIR} ${depthai_BINARY_DIR} EXCLUDE_FROM_ALL) +endif() -message(STATUS "Building OAK camera plugin with DepthAI ${depthai_VERSION}") +message(STATUS "DepthAI v${DEPTHAI_VERSION} ready") # ============================================================================== # SDL2 (for live preview window, bundled via FetchContent) @@ -48,6 +201,8 @@ message(STATUS "SDL2 ${SDL2_VERSION} — live preview support enabled") # ============================================================================== # Build OAK Plugin # ============================================================================== +message(STATUS "Building OAK camera plugin") + add_executable(camera_plugin_oak main.cpp core/oak_camera.cpp @@ -66,7 +221,6 @@ target_link_libraries(camera_plugin_oak SDL2::SDL2-static ) -# Set RPATH so the binary finds bundled shared libraries (e.g. libusb) in its own directory set_target_properties(camera_plugin_oak PROPERTIES INSTALL_RPATH "$ORIGIN" ) @@ -80,18 +234,3 @@ install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/README.md" DESTINATION plugins/oak_camera ) - -# Install libusb from depthai dependencies (required at runtime) -# The library is built by depthai and installed to its dependencies folder -install(CODE " - # Find libusb in depthai's dependency installation - set(LIBUSB_SOURCE \"\${CMAKE_INSTALL_PREFIX}/lib/cmake/depthai/dependencies/lib/libusb-1.0.so\") - set(LIBUSB_DEST \"\${CMAKE_INSTALL_PREFIX}/plugins/oak_camera/libusb-1.0.so\") - - if(EXISTS \"\${LIBUSB_SOURCE}\") - message(STATUS \"Installing libusb-1.0.so from depthai dependencies to plugin directory\") - file(COPY \"\${LIBUSB_SOURCE}\" DESTINATION \"\${CMAKE_INSTALL_PREFIX}/plugins/oak_camera/\") - else() - message(WARNING \"libusb-1.0.so not found at \${LIBUSB_SOURCE}\") - endif() -") diff --git a/src/plugins/oak/README.md b/src/plugins/oak/README.md index 05ea41a8d..350b4c936 100644 --- a/src/plugins/oak/README.md +++ b/src/plugins/oak/README.md @@ -16,13 +16,29 @@ C++ plugin that captures H.264 video from OAK cameras and saves to raw H.264 fil ## Build -DepthAI is fetched and built automatically via FetchContent. The first build takes ~10-15 minutes (mostly DepthAI and its Hunter dependencies), subsequent builds are fast. +DepthAI v3.x is fetched and built automatically via FetchContent. Dependencies are managed +by **vcpkg**. The first build takes ~10-15 minutes (mostly vcpkg deps + DepthAI), subsequent +builds are fast. + +### Prerequisites + +```bash +# Install build tools for vcpkg to build libusb (Linux) +sudo apt install libudev-dev autoconf automake libtool pkg-config + +# Install vcpkg (one-time) +git clone https://github.com/microsoft/vcpkg ~/.vcpkg +~/.vcpkg/bootstrap-vcpkg.sh +echo 'export VCPKG_ROOT=~/.vcpkg' >> ~/.bashrc && source ~/.bashrc +``` + +### Configure and Build ```bash cd IsaacTeleop -# Configure and build -cmake -B build -DBUILD_PLUGIN_OAK_CAMERA=ON +cmake -B build -DBUILD_PLUGIN_OAK_CAMERA=ON \ + -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake cmake --build build --target camera_plugin_oak --parallel ``` @@ -83,10 +99,11 @@ Press `Ctrl+C` to stop recording. ## Dependencies -All dependencies are built automatically via CMake: +Transitive dependencies via **vcpkg**, DepthAI and SDL2 via FetchContent: -- **DepthAI** - OAK camera interface -- **SDL2** - Live preview window (used by `--preview`) +- **DepthAI v3.x** - OAK camera interface (FetchContent) +- **SDL2** - Live preview window (FetchContent, used by `--preview`) +- **vcpkg** - nlohmann-json, spdlog, libusb, openssl, libarchive, etc. ## Output Format diff --git a/src/plugins/oak/core/oak_camera.cpp b/src/plugins/oak/core/oak_camera.cpp index d9dd88af7..5cf3eb2d6 100644 --- a/src/plugins/oak/core/oak_camera.cpp +++ b/src/plugins/oak/core/oak_camera.cpp @@ -36,33 +36,19 @@ OakCamera::OakCamera(const OakConfig& config, const std::vector& s auto device_info = find_device(config.device_id); m_device = std::make_shared(device_info); - std::cout << "Device connected: " << m_device->getMxId() << std::endl; + std::cout << "Device connected: " << m_device->getDeviceInfo().getDeviceId() << std::endl; auto sensors = m_device->getCameraSensorNames(); std::cout << "Sensors found: " << sensors.size() << std::endl; for (const auto& [socket, name] : sensors) std::cout << " Socket " << static_cast(socket) << ": " << name << std::endl; - if (sensors.find(dai::CameraBoardSocket::CAM_A) == sensors.end()) - throw std::runtime_error("Color sensor not found on CAM_A"); - auto color_sensor_name = sensors.find(dai::CameraBoardSocket::CAM_A)->second; - auto color_resolution = color_sensor_name == "OV9782" ? dai::ColorCameraProperties::SensorResolution::THE_800_P : - dai::ColorCameraProperties::SensorResolution::THE_1080_P; - - static constexpr const char* kPreviewStreamName = "ColorPreview"; - - auto pipeline = create_pipeline(config, streams, color_resolution); + m_pipeline = create_pipeline(m_device, config, streams); if (config.preview) - m_preview = PreviewStream::create(kPreviewStreamName, pipeline, color_resolution); - - m_device->startPipeline(pipeline); + m_preview = PreviewStream::create("ColorPreview", *m_pipeline); - for (const auto& s : streams) - m_queues[s.camera] = m_device->getOutputQueue(core::EnumNameStreamType(s.camera), 8, false); - - if (m_preview) - m_preview->setOutputQueue(m_device->getOutputQueue(kPreviewStreamName, 4, false)); + m_pipeline->start(); std::cout << "OAK camera pipeline started" << std::endl; } @@ -71,8 +57,7 @@ OakCamera::~OakCamera() = default; dai::DeviceInfo OakCamera::find_device(const std::string& device_id) { - auto devices = dai::Device::getAllAvailableDevices(); - + auto devices = dai::DeviceBootloader::getAllAvailableDevices(); if (devices.empty()) throw std::runtime_error("No OAK devices found. Check USB connection and udev rules."); @@ -84,9 +69,9 @@ dai::DeviceInfo OakCamera::find_device(const std::string& device_id) for (const auto& device : devices) { - if (device.getMxId() == device_id) + if (device.getDeviceId() == device_id) { - std::cout << "Found device with ID: " << device.getMxId() << std::endl; + std::cout << "Found device with ID: " << device_id << std::endl; return device; } } @@ -95,68 +80,58 @@ dai::DeviceInfo OakCamera::find_device(const std::string& device_id) } // ============================================================================= -// Pipeline construction +// Pipeline construction (DepthAI v3.x) // ============================================================================= -dai::Pipeline OakCamera::create_pipeline(const OakConfig& config, - const std::vector& streams, - dai::ColorCameraProperties::SensorResolution color_resolution) +std::unique_ptr OakCamera::create_pipeline(std::shared_ptr device, + const OakConfig& config, + const std::vector& streams) { - dai::Pipeline pipeline; + auto pipeline = std::make_unique(device); + + static constexpr std::pair kRes1080p = { 1920, 1080 }; + static constexpr std::pair kRes800p = { 1280, 800 }; + static constexpr std::pair kRes400p = { 640, 400 }; bool need_color = has_stream(streams, core::StreamType_Color); bool need_mono_left = has_stream(streams, core::StreamType_MonoLeft); bool need_mono_right = has_stream(streams, core::StreamType_MonoRight); - auto create_h264_output = [&](dai::Node::Output& source, const char* stream_name) + auto create_encoder = [&]() -> std::shared_ptr { - auto enc = pipeline.create(); - enc->setDefaultProfilePreset(config.fps, dai::VideoEncoderProperties::Profile::H264_BASELINE); + auto enc = pipeline->create(); + enc->setDefaultProfilePreset(static_cast(config.fps), dai::VideoEncoderProperties::Profile::H264_BASELINE); enc->setBitrate(config.bitrate); enc->setQuality(config.quality); enc->setKeyframeFrequency(config.keyframe_frequency); enc->setNumBFrames(0); enc->setRateControlMode(dai::VideoEncoderProperties::RateControlMode::CBR); + return enc; + }; - auto xout = pipeline.create(); - xout->setStreamName(stream_name); - - source.link(enc->input); - enc->bitstream.link(xout->input); + auto add_stream = [&](dai::CameraBoardSocket socket, core::StreamType type, std::pair resolution) + { + auto cam = pipeline->create(); + cam->build(socket); + auto output = cam->requestOutput(resolution, dai::ImgFrame::Type::NV12); + auto enc = create_encoder(); + output->link(enc->input); + m_queues[type] = enc->bitstream.createOutputQueue(); }; // ---- Color camera ---- if (need_color) { - auto camRgb = pipeline.create(); - camRgb->setBoardSocket(dai::CameraBoardSocket::CAM_A); - camRgb->setResolution(color_resolution); - camRgb->setFps(config.fps); - camRgb->setColorOrder(dai::ColorCameraProperties::ColorOrder::BGR); - - create_h264_output(camRgb->video, core::EnumNameStreamType(core::StreamType_Color)); + auto color_sensor = device->getCameraSensorNames()[dai::CameraBoardSocket::CAM_A]; + auto color_res = (color_sensor == "OV9782") ? kRes800p : kRes1080p; + add_stream(dai::CameraBoardSocket::CAM_A, core::StreamType_Color, color_res); } // ---- Mono cameras ---- if (need_mono_left) - { - auto monoLeft = pipeline.create(); - monoLeft->setBoardSocket(dai::CameraBoardSocket::CAM_B); - monoLeft->setResolution(dai::MonoCameraProperties::SensorResolution::THE_400_P); - monoLeft->setFps(config.fps); - - create_h264_output(monoLeft->out, core::EnumNameStreamType(core::StreamType_MonoLeft)); - } - + add_stream(dai::CameraBoardSocket::CAM_B, core::StreamType_MonoLeft, kRes400p); if (need_mono_right) - { - auto monoRight = pipeline.create(); - monoRight->setBoardSocket(dai::CameraBoardSocket::CAM_C); - monoRight->setResolution(dai::MonoCameraProperties::SensorResolution::THE_400_P); - monoRight->setFps(config.fps); - - create_h264_output(monoRight->out, core::EnumNameStreamType(core::StreamType_MonoRight)); - } + add_stream(dai::CameraBoardSocket::CAM_C, core::StreamType_MonoRight, kRes400p); return pipeline; } diff --git a/src/plugins/oak/core/oak_camera.hpp b/src/plugins/oak/core/oak_camera.hpp index 01f7f1aac..ae460acf4 100644 --- a/src/plugins/oak/core/oak_camera.hpp +++ b/src/plugins/oak/core/oak_camera.hpp @@ -7,9 +7,9 @@ #include #include +#include #include #include -#include #include namespace plugins @@ -91,13 +91,14 @@ class OakCamera void print_stats() const; private: - dai::DeviceInfo find_device(const std::string& device_id); - dai::Pipeline create_pipeline(const OakConfig& config, - const std::vector& streams, - dai::ColorCameraProperties::SensorResolution color_resolution); + static dai::DeviceInfo find_device(const std::string& device_id); + std::unique_ptr create_pipeline(std::shared_ptr device, + const OakConfig& config, + const std::vector& streams); + std::unique_ptr m_pipeline; std::shared_ptr m_device; - std::map> m_queues; + std::map> m_queues; std::unique_ptr m_sink; std::map m_frame_counts; diff --git a/src/plugins/oak/core/preview_stream.cpp b/src/plugins/oak/core/preview_stream.cpp index 50255ceff..b5afa9620 100644 --- a/src/plugins/oak/core/preview_stream.cpp +++ b/src/plugins/oak/core/preview_stream.cpp @@ -20,7 +20,7 @@ struct PreviewStream::Impl int tex_width = 0; int tex_height = 0; - std::shared_ptr queue; + std::shared_ptr queue; ~Impl() { @@ -36,15 +36,13 @@ struct PreviewStream::Impl PreviewStream::~PreviewStream() = default; -std::unique_ptr PreviewStream::create(const std::string& name, - dai::Pipeline& pipeline, - dai::ColorCameraProperties::SensorResolution resolution) +std::unique_ptr PreviewStream::create(const std::string& name, dai::Pipeline& pipeline) { - // Find existing ColorCamera on CAM_A, or create one - std::shared_ptr camRgb; + // Find existing Camera on CAM_A, or create one + std::shared_ptr camRgb; for (auto& node : pipeline.getAllNodes()) { - auto cam = std::dynamic_pointer_cast(node); + auto cam = std::dynamic_pointer_cast(node); if (cam && cam->getBoardSocket() == dai::CameraBoardSocket::CAM_A) { camRgb = cam; @@ -54,22 +52,13 @@ std::unique_ptr PreviewStream::create(const std::string& name, if (!camRgb) { - std::cout << "Creating new ColorCamera on CAM_A" << std::endl; - camRgb = pipeline.create(); - camRgb->setBoardSocket(dai::CameraBoardSocket::CAM_A); - camRgb->setResolution(resolution); - camRgb->setColorOrder(dai::ColorCameraProperties::ColorOrder::BGR); + camRgb = pipeline.create(); + camRgb->build(dai::CameraBoardSocket::CAM_A); } - int preview_w = 640; - int preview_h = (resolution == dai::ColorCameraProperties::SensorResolution::THE_800_P) ? 400 : 360; - - camRgb->setPreviewSize(preview_w, preview_h); - camRgb->setInterleaved(true); - - auto xout = pipeline.create(); - xout->setStreamName(name); - camRgb->preview.link(xout->input); + constexpr int preview_w = 640; + constexpr int preview_h = 400; + auto previewOutput = camRgb->requestOutput(std::make_pair(preview_w, preview_h), dai::ImgFrame::Type::BGR888i); if (SDL_Init(SDL_INIT_VIDEO) != 0) throw std::runtime_error(std::string("Preview: SDL_Init failed: ") + SDL_GetError()); @@ -89,6 +78,8 @@ std::unique_ptr PreviewStream::create(const std::string& name, if (!impl->renderer) throw std::runtime_error(std::string("Preview: SDL_CreateRenderer failed: ") + SDL_GetError()); + impl->queue = previewOutput->createOutputQueue(); + auto stream = std::unique_ptr(new PreviewStream()); stream->m_impl = std::move(impl); @@ -96,15 +87,10 @@ std::unique_ptr PreviewStream::create(const std::string& name, return stream; } -void PreviewStream::setOutputQueue(std::shared_ptr queue) -{ - m_impl->queue = std::move(queue); -} - void PreviewStream::update() { if (!m_impl->queue) - throw std::runtime_error("Preview: Output queue not set"); + return; auto frame = m_impl->queue->tryGet(); if (!frame) diff --git a/src/plugins/oak/core/preview_stream.hpp b/src/plugins/oak/core/preview_stream.hpp index f40c3fa4e..13282bfbd 100644 --- a/src/plugins/oak/core/preview_stream.hpp +++ b/src/plugins/oak/core/preview_stream.hpp @@ -16,8 +16,8 @@ namespace oak /** * @brief Self-contained color preview stream. * - * Owns the full lifecycle: adds preview nodes to the pipeline, opens an SDL2 - * window, connects to the device output queue, and polls/displays frames. + * Owns the full lifecycle: requests a preview output from an existing Camera + * node on CAM_A, opens an SDL2 window, and polls/displays frames. */ class PreviewStream { @@ -28,19 +28,16 @@ class PreviewStream PreviewStream& operator=(const PreviewStream&) = delete; /** - * @brief Wire preview nodes into an existing pipeline and create the window. + * @brief Wire a preview output into an existing pipeline and create the window. * - * Searches the pipeline for an existing ColorCamera on CAM_A. If none is - * found, creates and configures one. Then attaches preview output nodes. + * Searches the pipeline for an existing Camera node on CAM_A. If none is + * found, creates and builds one. Requests a small BGR output for preview + * and creates the output queue internally. * - * @throws std::runtime_error if SDL initialisation or window creation fails. + * @throws std::runtime_error if no CAM_A node exists and creation fails, + * or if SDL initialisation / window creation fails. */ - static std::unique_ptr create(const std::string& name, - dai::Pipeline& pipeline, - dai::ColorCameraProperties::SensorResolution resolution); - - /** @brief Set the output queue to poll frames from. Call after Device::startPipeline. */ - void setOutputQueue(std::shared_ptr queue); + static std::unique_ptr create(const std::string& name, dai::Pipeline& pipeline); /** @brief Poll the queue and display a frame if available. */ void update(); diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 000000000..c6fee431d --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,28 @@ +{ + "name": "isaac-teleop", + "version": "1.0.0", + "description": "IsaacTeleop with OAK camera support via DepthAI", + "dependencies": [ + "nlohmann-json", + "spdlog", + "zlib", + "bzip2", + "lz4", + "liblzma", + "eigen3", + "yaml-cpp", + "fmt", + "magic-enum", + "neargye-semver", + "fp16", + "libusb", + "openssl", + "libarchive", + { + "name": "cpp-httplib", + "default-features": false, + "features": ["brotli"] + } + ], + "builtin-baseline": "a9eee3b18df395dbb8be71a31bd78ea441056e42" +}