From 16a1a38645985fe3f30be28827eb38f55bedb991 Mon Sep 17 00:00:00 2001 From: crosstyan Date: Tue, 14 Apr 2026 17:13:29 +0800 Subject: [PATCH] Make MCAP and depth support optional --- CMakeLists.txt | 353 ++++++++++++++---- .../cvmmap_streamer/config/runtime_config.hpp | 10 + .../record/mcap_record_sink.hpp | 9 + protoc-wrapper.sh | 2 + src/config/runtime_config.cpp | 184 ++++++--- src/encode/ffmpeg_encoder_backend.cpp | 4 + src/ipc/help.cpp | 6 +- src/pipeline/pipeline_runtime.cpp | 257 +++++++++---- src/record/mcap_record_sink.cpp | 231 ++++++++++-- src/testers/mcap_body_record_tester.cpp | 1 - src/testers/mcap_multi_record_tester.cpp | 54 +-- src/testers/mcap_pose_record_tester.cpp | 53 +-- 12 files changed, 836 insertions(+), 328 deletions(-) create mode 100755 protoc-wrapper.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index f0afc0e..4841fd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,68 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) include(GNUInstallDirs) +function(cvmmap_streamer_append_runtime_dir out_var runtime_library) + execute_process( + COMMAND "${CMAKE_CXX_COMPILER}" -print-file-name=${runtime_library} + OUTPUT_VARIABLE runtime_library_path + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (IS_ABSOLUTE "${runtime_library_path}") + get_filename_component(runtime_library_dir "${runtime_library_path}" DIRECTORY) + set(${out_var} "${${out_var}};${runtime_library_dir}" PARENT_SCOPE) + endif() +endfunction() + +function(cvmmap_streamer_resolve_feature_mode out_var option_name requested_mode available unavailable_reason) + set(_valid_modes AUTO ON OFF) + list(FIND _valid_modes "${requested_mode}" _requested_mode_index) + if (_requested_mode_index EQUAL -1) + message(FATAL_ERROR + "Invalid ${option_name}='${requested_mode}' (expected: AUTO|ON|OFF)") + endif() + + if (requested_mode STREQUAL "OFF") + set(${out_var} FALSE PARENT_SCOPE) + return() + endif() + + if (requested_mode STREQUAL "ON") + if (NOT available) + message(FATAL_ERROR + "${option_name}=ON requires ${unavailable_reason}") + endif() + + set(${out_var} TRUE PARENT_SCOPE) + return() + endif() + + set(${out_var} ${available} PARENT_SCOPE) +endfunction() + +set(CVMMAP_STREAMER_BUILD_RPATH "") +# Build-tree binaries compiled with a non-system GCC need that toolchain's +# C++ runtime on the dynamic loader search path. +cvmmap_streamer_append_runtime_dir(CVMMAP_STREAMER_BUILD_RPATH libstdc++.so.6) +cvmmap_streamer_append_runtime_dir(CVMMAP_STREAMER_BUILD_RPATH libgcc_s.so.1) +list(REMOVE_DUPLICATES CVMMAP_STREAMER_BUILD_RPATH) + +set( + CVMMAP_STREAMER_ENABLE_MCAP + "AUTO" + CACHE STRING + "Enable MCAP recording support: AUTO, ON, or OFF") +set_property(CACHE CVMMAP_STREAMER_ENABLE_MCAP PROPERTY STRINGS AUTO ON OFF) +set( + CVMMAP_STREAMER_ENABLE_MCAP_DEPTH + "AUTO" + CACHE STRING + "Enable MCAP depth recording support: AUTO, ON, or OFF") +set_property(CACHE CVMMAP_STREAMER_ENABLE_MCAP_DEPTH PROPERTY STRINGS AUTO ON OFF) +set( + CVMMAP_STREAMER_MCAP_INCLUDE_DIR + "${CMAKE_CURRENT_LIST_DIR}/third_party/mcap/include" + CACHE PATH + "Path to MCAP headers") + find_package(Threads REQUIRED) find_package(OpenSSL REQUIRED) if (NOT TARGET OpenSSL::SSL AND DEFINED OPENSSL_SSL_LIBRARY) @@ -73,15 +135,110 @@ endif() find_package(ZeroMQ QUIET) find_package(spdlog REQUIRED) find_package(Protobuf REQUIRED) +if (Protobuf_VERSION VERSION_LESS 3.13) + set(_cvmmap_streamer_protoc_wrapper "${CMAKE_BINARY_DIR}/cvmmap-streamer-protoc-wrapper.sh") + file(WRITE "${_cvmmap_streamer_protoc_wrapper}" +"#!/bin/sh\nexec \"${Protobuf_PROTOC_EXECUTABLE}\" --experimental_allow_proto3_optional \"$@\"\n") + file(CHMOD + "${_cvmmap_streamer_protoc_wrapper}" + PERMISSIONS + OWNER_READ OWNER_WRITE OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE) + if (TARGET protobuf::protoc) + set_target_properties(protobuf::protoc PROPERTIES IMPORTED_LOCATION "${_cvmmap_streamer_protoc_wrapper}") + endif() + set(Protobuf_PROTOC_EXECUTABLE "${_cvmmap_streamer_protoc_wrapper}") +endif() + find_package(PkgConfig REQUIRED) find_package(rvl CONFIG QUIET) +set(_CVMMAP_STREAMER_MCAP_HEADERS_AVAILABLE FALSE) +if (EXISTS "${CVMMAP_STREAMER_MCAP_INCLUDE_DIR}/mcap/mcap.hpp") + set(_CVMMAP_STREAMER_MCAP_HEADERS_AVAILABLE TRUE) + if (NOT TARGET mcap::mcap) + add_library(cvmmap_streamer_mcap_headers INTERFACE) + target_include_directories(cvmmap_streamer_mcap_headers INTERFACE "${CVMMAP_STREAMER_MCAP_INCLUDE_DIR}") + add_library(mcap::mcap ALIAS cvmmap_streamer_mcap_headers) + endif() +elseif (NOT TARGET mcap::mcap) + add_library(mcap::mcap INTERFACE IMPORTED GLOBAL) +endif() + add_subdirectory(third_party) pkg_check_modules(FFMPEG REQUIRED IMPORTED_TARGET libavcodec libavformat libavutil libswscale) pkg_check_modules(PROTOBUF_PKG QUIET IMPORTED_TARGET protobuf) -pkg_check_modules(ZSTD REQUIRED IMPORTED_TARGET libzstd) -pkg_check_modules(LZ4 REQUIRED IMPORTED_TARGET liblz4) +pkg_check_modules(ZSTD QUIET IMPORTED_TARGET libzstd) +pkg_check_modules(LZ4 QUIET IMPORTED_TARGET liblz4) + +set(_CVMMAP_STREAMER_MCAP_MISSING_DEPS) +if (NOT _CVMMAP_STREAMER_MCAP_HEADERS_AVAILABLE) + list(APPEND _CVMMAP_STREAMER_MCAP_MISSING_DEPS "MCAP headers at ${CVMMAP_STREAMER_MCAP_INCLUDE_DIR}") +endif() +if (NOT ZSTD_FOUND) + list(APPEND _CVMMAP_STREAMER_MCAP_MISSING_DEPS "pkg-config package libzstd") +endif() +if (NOT LZ4_FOUND) + list(APPEND _CVMMAP_STREAMER_MCAP_MISSING_DEPS "pkg-config package liblz4") +endif() +if (_CVMMAP_STREAMER_MCAP_MISSING_DEPS) + list(JOIN _CVMMAP_STREAMER_MCAP_MISSING_DEPS ", " _CVMMAP_STREAMER_MCAP_UNAVAILABLE_REASON) + set(_CVMMAP_STREAMER_MCAP_AVAILABLE FALSE) +else() + set(_CVMMAP_STREAMER_MCAP_UNAVAILABLE_REASON "MCAP dependencies") + set(_CVMMAP_STREAMER_MCAP_AVAILABLE TRUE) +endif() +cvmmap_streamer_resolve_feature_mode( + CVMMAP_STREAMER_HAS_MCAP_BOOL + CVMMAP_STREAMER_ENABLE_MCAP + "${CVMMAP_STREAMER_ENABLE_MCAP}" + ${_CVMMAP_STREAMER_MCAP_AVAILABLE} + "${_CVMMAP_STREAMER_MCAP_UNAVAILABLE_REASON}") + +set(RVL_LOCAL_ROOT "${CMAKE_CURRENT_LIST_DIR}/../rvl_impl" CACHE PATH "Path to a local rvl_impl checkout") +set(RVL_LOCAL_BUILD "${RVL_LOCAL_ROOT}/build/core" CACHE PATH "Path to local rvl_impl build artifacts") + +set(_CVMMAP_STREAMER_RVL_AVAILABLE FALSE) +set(_CVMMAP_STREAMER_RVL_UNAVAILABLE_REASON "rvl::rvl target or local rvl_impl build artifacts") +if (TARGET rvl::rvl) + set(_CVMMAP_STREAMER_RVL_AVAILABLE TRUE) +elseif ( + EXISTS "${RVL_LOCAL_ROOT}/core/include/rvl/rvl.hpp" + AND EXISTS "${RVL_LOCAL_BUILD}/librvl_core.a") + set(_CVMMAP_STREAMER_RVL_AVAILABLE TRUE) +endif() + +if (CVMMAP_STREAMER_ENABLE_MCAP STREQUAL "OFF" AND CVMMAP_STREAMER_ENABLE_MCAP_DEPTH STREQUAL "ON") + message(FATAL_ERROR + "CVMMAP_STREAMER_ENABLE_MCAP_DEPTH=ON requires CVMMAP_STREAMER_ENABLE_MCAP to be AUTO or ON") +endif() +if (CVMMAP_STREAMER_ENABLE_MCAP_DEPTH STREQUAL "ON" AND NOT CVMMAP_STREAMER_HAS_MCAP_BOOL) + message(FATAL_ERROR + "CVMMAP_STREAMER_ENABLE_MCAP_DEPTH=ON requires MCAP support, but ${_CVMMAP_STREAMER_MCAP_UNAVAILABLE_REASON} is unavailable") +endif() +if (NOT CVMMAP_STREAMER_HAS_MCAP_BOOL AND CVMMAP_STREAMER_ENABLE_MCAP_DEPTH STREQUAL "AUTO") + set(CVMMAP_STREAMER_HAS_MCAP_DEPTH_BOOL FALSE) +else() + cvmmap_streamer_resolve_feature_mode( + CVMMAP_STREAMER_HAS_MCAP_DEPTH_BOOL + CVMMAP_STREAMER_ENABLE_MCAP_DEPTH + "${CVMMAP_STREAMER_ENABLE_MCAP_DEPTH}" + ${_CVMMAP_STREAMER_RVL_AVAILABLE} + "${_CVMMAP_STREAMER_RVL_UNAVAILABLE_REASON}") +endif() + +if (CVMMAP_STREAMER_HAS_MCAP_BOOL) + set(CVMMAP_STREAMER_HAS_MCAP 1) +else() + set(CVMMAP_STREAMER_HAS_MCAP 0) +endif() +if (CVMMAP_STREAMER_HAS_MCAP_DEPTH_BOOL) + set(CVMMAP_STREAMER_HAS_MCAP_DEPTH 1) +else() + set(CVMMAP_STREAMER_HAS_MCAP_DEPTH 0) +endif() if (NOT TARGET cvmmap::client) if ( @@ -104,9 +261,7 @@ if (NOT TARGET cvmmap::client) endif() endif() -if (NOT TARGET rvl::rvl) - set(RVL_LOCAL_ROOT "${CMAKE_CURRENT_LIST_DIR}/../rvl_impl" CACHE PATH "Path to a local rvl_impl checkout") - set(RVL_LOCAL_BUILD "${RVL_LOCAL_ROOT}/build/core") +if (CVMMAP_STREAMER_HAS_MCAP_DEPTH AND NOT TARGET rvl::rvl) if ( EXISTS "${RVL_LOCAL_ROOT}/core/include/rvl/rvl.hpp" AND EXISTS "${RVL_LOCAL_BUILD}/librvl_core.a") @@ -185,21 +340,16 @@ target_link_libraries(cvmmap_streamer_foxglove_proto PUBLIC cvmmap_streamer_prot target_link_libraries(cvmmap_streamer_depth_proto PUBLIC cvmmap_streamer_protobuf) target_link_libraries(cvmmap_streamer_control_proto PUBLIC cvmmap_streamer_protobuf) -add_library(cvmmap_streamer_mcap_runtime STATIC - src/record/mcap_runtime.cpp) -target_include_directories(cvmmap_streamer_mcap_runtime - PUBLIC) -target_link_libraries(cvmmap_streamer_mcap_runtime - PUBLIC - mcap::mcap - PkgConfig::ZSTD - PkgConfig::LZ4) +add_library(cvmmap_streamer_feature_flags INTERFACE) +target_compile_definitions(cvmmap_streamer_feature_flags + INTERFACE + CVMMAP_STREAMER_HAS_MCAP=${CVMMAP_STREAMER_HAS_MCAP} + CVMMAP_STREAMER_HAS_MCAP_DEPTH=${CVMMAP_STREAMER_HAS_MCAP_DEPTH}) add_library(cvmmap_streamer_record_support STATIC src/encode/encoder_backend.cpp src/encode/ffmpeg_encoder_backend.cpp src/record/protobuf_descriptor.cpp - src/record/mcap_record_sink.cpp src/record/mp4_record_writer.cpp) target_include_directories(cvmmap_streamer_record_support PUBLIC @@ -207,14 +357,8 @@ target_include_directories(cvmmap_streamer_record_support "${CMAKE_CURRENT_BINARY_DIR}") target_link_libraries(cvmmap_streamer_record_support PUBLIC - cvmmap_streamer_foxglove_proto - cvmmap_streamer_depth_proto - cvmmap_streamer_mcap_runtime + cvmmap_streamer_feature_flags PkgConfig::FFMPEG - PkgConfig::ZSTD - PkgConfig::LZ4 - rvl::rvl - mcap::mcap msft_proxy4::proxy cvmmap_streamer_protobuf) if (TARGET spdlog::spdlog) @@ -226,6 +370,43 @@ if (TARGET PkgConfig::PROTOBUF_PKG) target_link_libraries(cvmmap_streamer_record_support PUBLIC PkgConfig::PROTOBUF_PKG) endif() +if (CVMMAP_STREAMER_HAS_MCAP) + add_library(cvmmap_streamer_mcap_runtime STATIC + src/record/mcap_runtime.cpp) + target_link_libraries(cvmmap_streamer_mcap_runtime + PUBLIC + cvmmap_streamer_feature_flags + mcap::mcap + PkgConfig::ZSTD + PkgConfig::LZ4) + + add_library(cvmmap_streamer_mcap_record_support STATIC + src/record/mcap_record_sink.cpp) + target_include_directories(cvmmap_streamer_mcap_record_support + PUBLIC + "${CMAKE_CURRENT_LIST_DIR}/include" + "${CMAKE_CURRENT_BINARY_DIR}") + target_link_libraries(cvmmap_streamer_mcap_record_support + PUBLIC + cvmmap_streamer_feature_flags + cvmmap_streamer_record_support + cvmmap_streamer_foxglove_proto + cvmmap_streamer_depth_proto + cvmmap_streamer_mcap_runtime + mcap::mcap) + if (CVMMAP_STREAMER_HAS_MCAP_DEPTH) + target_link_libraries(cvmmap_streamer_mcap_record_support PUBLIC rvl::rvl) + endif() + if (TARGET spdlog::spdlog) + target_link_libraries(cvmmap_streamer_mcap_record_support PUBLIC spdlog::spdlog) + elseif (TARGET spdlog) + target_link_libraries(cvmmap_streamer_mcap_record_support PUBLIC spdlog) + endif() + if (TARGET PkgConfig::PROTOBUF_PKG) + target_link_libraries(cvmmap_streamer_mcap_record_support PUBLIC PkgConfig::PROTOBUF_PKG) + endif() +endif() + add_library(cvmmap_streamer_common STATIC src/ipc/help.cpp src/config/runtime_config.cpp @@ -249,13 +430,14 @@ set(CVMMAP_STREAMER_LINK_DEPS Threads::Threads cvmmap_streamer_record_support PkgConfig::FFMPEG - PkgConfig::ZSTD - PkgConfig::LZ4 cvmmap::client cvmmap::nats CLI11::CLI11 tomlplusplus::tomlplusplus - mcap::mcap) + cvmmap_streamer_feature_flags) +if (CVMMAP_STREAMER_HAS_MCAP) + list(APPEND CVMMAP_STREAMER_LINK_DEPS cvmmap_streamer_mcap_record_support) +endif() if (TARGET cppzmq::cppzmq) list(APPEND CVMMAP_STREAMER_LINK_DEPS cppzmq::cppzmq) @@ -283,6 +465,13 @@ endif() target_link_libraries(cvmmap_streamer_common PUBLIC ${CVMMAP_STREAMER_LINK_DEPS}) +function(cvmmap_streamer_apply_build_rpath target) + if (CVMMAP_STREAMER_BUILD_RPATH) + set_target_properties(${target} PROPERTIES + BUILD_RPATH "${CVMMAP_STREAMER_BUILD_RPATH}") + endif() +endfunction() + function(add_cvmmap_binary target source) add_executable(${target} ${source} ${ARGN}) target_include_directories(${target} @@ -295,6 +484,7 @@ function(add_cvmmap_binary target source) set_target_properties(${target} PROPERTIES OUTPUT_NAME "${target}" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin") + cvmmap_streamer_apply_build_rpath(${target}) endfunction() add_cvmmap_binary(cvmmap_streamer src/main_streamer.cpp) @@ -303,65 +493,68 @@ add_cvmmap_binary(rtp_output_tester src/testers/rtp_output_tester.cpp) add_cvmmap_binary(rtmp_stub_tester src/testers/rtmp_stub_tester.cpp) add_cvmmap_binary(rtmp_output_tester src/testers/rtmp_output_tester.cpp) add_cvmmap_binary(ipc_snapshot_tester src/testers/ipc_snapshot_tester.cpp) -add_cvmmap_binary(mcap_depth_record_tester src/testers/mcap_depth_record_tester.cpp) -add_cvmmap_binary(mcap_body_record_tester src/testers/mcap_body_record_tester.cpp) -add_cvmmap_binary(mcap_body_inspector src/testers/mcap_body_inspector.cpp) -add_cvmmap_binary(mcap_pose_record_tester src/testers/mcap_pose_record_tester.cpp) -add_cvmmap_binary(mcap_multi_record_tester src/testers/mcap_multi_record_tester.cpp) +if (CVMMAP_STREAMER_HAS_MCAP) + add_cvmmap_binary(mcap_body_record_tester src/testers/mcap_body_record_tester.cpp) + add_cvmmap_binary(mcap_body_inspector src/testers/mcap_body_inspector.cpp) + add_cvmmap_binary(mcap_pose_record_tester src/testers/mcap_pose_record_tester.cpp) -add_executable(mcap_reader_tester src/testers/mcap_reader_tester.cpp) -target_include_directories(mcap_reader_tester - PRIVATE - "${CMAKE_CURRENT_LIST_DIR}/include" - "${CMAKE_CURRENT_BINARY_DIR}") -target_link_libraries(mcap_reader_tester - PRIVATE - CLI11::CLI11 - cvmmap_streamer_foxglove_proto - cvmmap_streamer_depth_proto - cvmmap_streamer_mcap_runtime - mcap::mcap - PkgConfig::ZSTD - PkgConfig::LZ4) -if (TARGET spdlog::spdlog) - target_link_libraries(mcap_reader_tester PRIVATE spdlog::spdlog) -elseif (TARGET spdlog) - target_link_libraries(mcap_reader_tester PRIVATE spdlog) -endif() -target_link_libraries(mcap_reader_tester PRIVATE cvmmap_streamer_protobuf) -if (TARGET PkgConfig::PROTOBUF_PKG) - target_link_libraries(mcap_reader_tester PRIVATE PkgConfig::PROTOBUF_PKG) -endif() -set_target_properties(mcap_reader_tester PROPERTIES - OUTPUT_NAME "mcap_reader_tester" - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin") + add_executable(mcap_reader_tester src/testers/mcap_reader_tester.cpp) + target_include_directories(mcap_reader_tester + PRIVATE + "${CMAKE_CURRENT_LIST_DIR}/include" + "${CMAKE_CURRENT_BINARY_DIR}") + target_link_libraries(mcap_reader_tester + PRIVATE + CLI11::CLI11 + cvmmap_streamer_feature_flags + cvmmap_streamer_foxglove_proto + cvmmap_streamer_depth_proto + cvmmap_streamer_mcap_runtime + cvmmap_streamer_protobuf) + if (TARGET spdlog::spdlog) + target_link_libraries(mcap_reader_tester PRIVATE spdlog::spdlog) + elseif (TARGET spdlog) + target_link_libraries(mcap_reader_tester PRIVATE spdlog) + endif() + if (TARGET PkgConfig::PROTOBUF_PKG) + target_link_libraries(mcap_reader_tester PRIVATE PkgConfig::PROTOBUF_PKG) + endif() + set_target_properties(mcap_reader_tester PROPERTIES + OUTPUT_NAME "mcap_reader_tester" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin") + cvmmap_streamer_apply_build_rpath(mcap_reader_tester) -add_executable(mcap_replay_tester src/testers/mcap_replay_tester.cpp) -target_include_directories(mcap_replay_tester - PRIVATE - "${CMAKE_CURRENT_LIST_DIR}/include" - "${CMAKE_CURRENT_BINARY_DIR}") -target_link_libraries(mcap_replay_tester - PRIVATE - Threads::Threads - CLI11::CLI11 - cvmmap_streamer_foxglove_proto - cvmmap_streamer_mcap_runtime - mcap::mcap - PkgConfig::ZSTD - PkgConfig::LZ4) -if (TARGET spdlog::spdlog) - target_link_libraries(mcap_replay_tester PRIVATE spdlog::spdlog) -elseif (TARGET spdlog) - target_link_libraries(mcap_replay_tester PRIVATE spdlog) + add_executable(mcap_replay_tester src/testers/mcap_replay_tester.cpp) + target_include_directories(mcap_replay_tester + PRIVATE + "${CMAKE_CURRENT_LIST_DIR}/include" + "${CMAKE_CURRENT_BINARY_DIR}") + target_link_libraries(mcap_replay_tester + PRIVATE + Threads::Threads + CLI11::CLI11 + cvmmap_streamer_feature_flags + cvmmap_streamer_foxglove_proto + cvmmap_streamer_mcap_runtime + cvmmap_streamer_protobuf) + if (TARGET spdlog::spdlog) + target_link_libraries(mcap_replay_tester PRIVATE spdlog::spdlog) + elseif (TARGET spdlog) + target_link_libraries(mcap_replay_tester PRIVATE spdlog) + endif() + if (TARGET PkgConfig::PROTOBUF_PKG) + target_link_libraries(mcap_replay_tester PRIVATE PkgConfig::PROTOBUF_PKG) + endif() + set_target_properties(mcap_replay_tester PROPERTIES + OUTPUT_NAME "mcap_replay_tester" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin") + cvmmap_streamer_apply_build_rpath(mcap_replay_tester) endif() -target_link_libraries(mcap_replay_tester PRIVATE cvmmap_streamer_protobuf) -if (TARGET PkgConfig::PROTOBUF_PKG) - target_link_libraries(mcap_replay_tester PRIVATE PkgConfig::PROTOBUF_PKG) + +if (CVMMAP_STREAMER_HAS_MCAP_DEPTH) + add_cvmmap_binary(mcap_depth_record_tester src/testers/mcap_depth_record_tester.cpp) + add_cvmmap_binary(mcap_multi_record_tester src/testers/mcap_multi_record_tester.cpp) endif() -set_target_properties(mcap_replay_tester PROPERTIES - OUTPUT_NAME "mcap_replay_tester" - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin") set(CVMMAP_STREAMER_INSTALL_TARGETS cvmmap_streamer) diff --git a/include/cvmmap_streamer/config/runtime_config.hpp b/include/cvmmap_streamer/config/runtime_config.hpp index a722952..b9d6fed 100644 --- a/include/cvmmap_streamer/config/runtime_config.hpp +++ b/include/cvmmap_streamer/config/runtime_config.hpp @@ -8,6 +8,15 @@ #include #include +#ifndef CVMMAP_STREAMER_HAS_MCAP +#define CVMMAP_STREAMER_HAS_MCAP 0 +#endif + +#ifndef CVMMAP_STREAMER_HAS_MCAP_DEPTH +#define CVMMAP_STREAMER_HAS_MCAP_DEPTH 0 +#endif + + namespace cvmmap_streamer { enum class CodecType { @@ -89,6 +98,7 @@ struct OutputsConfig { struct McapRecordConfig { bool enabled{false}; + bool depth_enabled{true}; std::string path{"capture.mcap"}; std::string topic{"/camera/video"}; std::string depth_topic{"/camera/depth"}; diff --git a/include/cvmmap_streamer/record/mcap_record_sink.hpp b/include/cvmmap_streamer/record/mcap_record_sink.hpp index f3afd59..789b542 100644 --- a/include/cvmmap_streamer/record/mcap_record_sink.hpp +++ b/include/cvmmap_streamer/record/mcap_record_sink.hpp @@ -13,6 +13,15 @@ #include #include +#ifndef CVMMAP_STREAMER_HAS_MCAP +#define CVMMAP_STREAMER_HAS_MCAP 0 +#endif + +#ifndef CVMMAP_STREAMER_HAS_MCAP_DEPTH +#define CVMMAP_STREAMER_HAS_MCAP_DEPTH 0 +#endif + + namespace cvmmap_streamer::record { enum class DepthEncoding { diff --git a/protoc-wrapper.sh b/protoc-wrapper.sh new file mode 100755 index 0000000..7c9c4cf --- /dev/null +++ b/protoc-wrapper.sh @@ -0,0 +1,2 @@ +#!/bin/sh +exec /usr/bin/protoc --experimental_allow_proto3_optional "$@" diff --git a/src/config/runtime_config.cpp b/src/config/runtime_config.cpp index a3f550f..8f0b61b 100644 --- a/src/config/runtime_config.cpp +++ b/src/config/runtime_config.cpp @@ -269,10 +269,11 @@ std::optional find_disallowed_boolean_assignment(int argc, char **a std::string_view negative; }; - constexpr std::array kFlagPairs{{ + constexpr std::array kFlagPairs{{ {"--rtmp", "--no-rtmp"}, {"--rtp", "--no-rtp"}, {"--mcap", "--no-mcap"}, + {"--mcap-depth", "--no-mcap-depth"}, {"--realtime-sync", "--no-realtime-sync"}, {"--force-idr-on-reset", "--no-force-idr-on-reset"}, {"--keep-stream-on-reset", "--no-keep-stream-on-reset"}, @@ -292,6 +293,46 @@ std::optional find_disallowed_boolean_assignment(int argc, char **a return std::nullopt; } +bool runtime_supports_mcap() { + return CVMMAP_STREAMER_HAS_MCAP != 0; +} + +bool runtime_supports_mcap_depth() { + return CVMMAP_STREAMER_HAS_MCAP_DEPTH != 0; +} + +std::string describe_mcap_option(std::string_view description) { + if (runtime_supports_mcap()) { + return std::string(description); + } + return std::string(description) + " (unavailable in this build; MCAP requests will fail)"; +} + +std::string describe_mcap_depth_option(std::string_view description) { + if (!runtime_supports_mcap()) { + return std::string(description) + " (unavailable in this build; MCAP support is not compiled in)"; + } + if (!runtime_supports_mcap_depth()) { + return std::string(description) + " (unavailable in this build; MCAP depth requests will fail)"; + } + return std::string(description); +} + +std::expected validate_mcap_capability_request(const McapRecordConfig &config) { + if (!config.enabled) { + return {}; + } + if (!runtime_supports_mcap()) { + return std::unexpected( + "invalid MCAP config: MCAP recording was requested, but this build was compiled without MCAP support"); + } + if (config.depth_enabled && !runtime_supports_mcap_depth()) { + return std::unexpected( + "invalid MCAP config: depth recording was requested, but this build was compiled without MCAP depth support"); + } + return {}; +} + template std::optional toml_value(const toml::table &table, std::string_view path) { auto node = table.at_path(path); @@ -462,6 +503,9 @@ std::expected apply_toml_file(RuntimeConfig &config, const st if (auto value = toml_value(table, "record.mcap.enabled")) { config.record.mcap.enabled = *value; } + if (auto value = toml_value(table, "record.mcap.depth_enabled")) { + config.record.mcap.depth_enabled = *value; + } if (auto value = toml_value(table, "record.mcap.path")) { config.record.mcap.enabled = true; config.record.mcap.path = *value; @@ -710,6 +754,7 @@ std::expected parse_runtime_config(int argc, char ** std::optional rtp_payload_type_override{}; std::optional rtp_sdp_override{}; std::optional mcap_enabled_override{}; + std::optional mcap_depth_enabled_override{}; std::optional mcap_path_override{}; std::optional mcap_topic_override{}; std::optional mcap_depth_topic_override{}; @@ -834,55 +879,89 @@ std::expected parse_runtime_config(int argc, char ** ->check(require_non_empty("--sdp")) ->excludes(rtp_sdp); - app.add_flag("--mcap,!--no-mcap", mcap_enabled_override, "Enable or disable MCAP recording") - ->group("MCAP Record") + const std::string mcap_group = runtime_supports_mcap() + ? "MCAP Record" + : "MCAP Record (unsupported in this build; requests will fail)"; + const std::string mcap_depth_group = !runtime_supports_mcap() + ? "MCAP Depth Record (unsupported in this build; MCAP support is not compiled in)" + : runtime_supports_mcap_depth() + ? "MCAP Depth Record" + : "MCAP Depth Record (unsupported in this build; requests will fail)"; + + app.add_flag("--mcap,!--no-mcap", mcap_enabled_override, describe_mcap_option("Enable or disable MCAP recording")) + ->group(mcap_group) ->default_str(defaults.record.mcap.enabled ? "true" : "false") ->disable_flag_override(); - app.add_option("--mcap-path", mcap_path_override, "MCAP output file path") - ->group("MCAP Record") + app.add_flag( + "--mcap-depth,!--no-mcap-depth", + mcap_depth_enabled_override, + describe_mcap_depth_option("Enable or disable MCAP depth recording")) + ->group(mcap_depth_group) + ->default_str(defaults.record.mcap.depth_enabled ? "true" : "false") + ->disable_flag_override(); + app.add_option("--mcap-path", mcap_path_override, describe_mcap_option("MCAP output file path")) + ->group(mcap_group) ->type_name("PATH") ->check(require_non_empty("--mcap-path")) ->default_str(defaults.record.mcap.path); - app.add_option("--mcap-topic", mcap_topic_override, "Foxglove compressed video topic name") - ->group("MCAP Record") + app.add_option( + "--mcap-topic", + mcap_topic_override, + describe_mcap_option("Foxglove compressed video topic name")) + ->group(mcap_group) ->type_name("TOPIC") ->check(require_non_empty("--mcap-topic")) ->default_str(defaults.record.mcap.topic); - app.add_option("--mcap-depth-topic", mcap_depth_topic_override, "Depth image topic name") - ->group("MCAP Record") + app.add_option( + "--mcap-depth-topic", + mcap_depth_topic_override, + describe_mcap_depth_option("Depth image topic name")) + ->group(mcap_depth_group) ->type_name("TOPIC") ->check(require_non_empty("--mcap-depth-topic")) ->default_str(defaults.record.mcap.depth_topic); - app.add_option("--mcap-calibration-topic", mcap_calibration_topic_override, "RGB camera calibration topic name") - ->group("MCAP Record") + app.add_option( + "--mcap-calibration-topic", + mcap_calibration_topic_override, + describe_mcap_option("RGB camera calibration topic name")) + ->group(mcap_group) ->type_name("TOPIC") ->check(require_non_empty("--mcap-calibration-topic")) ->default_str(defaults.record.mcap.calibration_topic); app.add_option( "--mcap-depth-calibration-topic", mcap_depth_calibration_topic_override, - "Depth camera calibration topic name") - ->group("MCAP Record") + describe_mcap_depth_option("Depth camera calibration topic name")) + ->group(mcap_depth_group) ->type_name("TOPIC") ->check(require_non_empty("--mcap-depth-calibration-topic")) ->default_str(defaults.record.mcap.depth_calibration_topic); - app.add_option("--mcap-pose-topic", mcap_pose_topic_override, "Pose topic name") - ->group("MCAP Record") + app.add_option("--mcap-pose-topic", mcap_pose_topic_override, describe_mcap_option("Pose topic name")) + ->group(mcap_group) ->type_name("TOPIC") ->check(require_non_empty("--mcap-pose-topic")) ->default_str(defaults.record.mcap.pose_topic); - app.add_option("--mcap-body-topic", mcap_body_topic_override, "Body tracking topic name") - ->group("MCAP Record") + app.add_option( + "--mcap-body-topic", + mcap_body_topic_override, + describe_mcap_option("Body tracking topic name")) + ->group(mcap_group) ->type_name("TOPIC") ->check(require_non_empty("--mcap-body-topic")) ->default_str(defaults.record.mcap.body_topic); - app.add_option("--mcap-frame-id", mcap_frame_id_override, "Frame ID written into MCAP messages") - ->group("MCAP Record") + app.add_option( + "--mcap-frame-id", + mcap_frame_id_override, + describe_mcap_option("Frame ID written into MCAP messages")) + ->group(mcap_group) ->type_name("ID") ->check(require_non_empty("--mcap-frame-id")) ->default_str(defaults.record.mcap.frame_id); - app.add_option("--mcap-compression", mcap_compression_override, "MCAP chunk compression mode") - ->group("MCAP Record") + app.add_option( + "--mcap-compression", + mcap_compression_override, + describe_mcap_option("MCAP chunk compression mode")) + ->group(mcap_group) ->type_name("MODE") ->transform(canonicalize_option(canonicalize_mcap_compression)) ->default_str(std::string(to_string(defaults.record.mcap.compression))); @@ -1078,6 +1157,9 @@ std::expected parse_runtime_config(int argc, char ** if (mcap_enabled_override) { config.record.mcap.enabled = *mcap_enabled_override; } + if (mcap_depth_enabled_override) { + config.record.mcap.depth_enabled = *mcap_depth_enabled_override; + } if (queue_size_override) { config.latency.queue_size = *queue_size_override; @@ -1157,30 +1239,41 @@ std::expected validate_runtime_config(const RuntimeConfig &co } } - if (config.record.mcap.enabled && config.record.mcap.path.empty()) { - return std::unexpected("invalid MCAP config: enabled MCAP output requires path"); - } - if (config.record.mcap.topic.empty()) { - return std::unexpected("invalid MCAP config: topic must not be empty"); - } - if (config.record.mcap.depth_topic.empty()) { - return std::unexpected("invalid MCAP config: depth_topic must not be empty"); - } - if (config.record.mcap.calibration_topic.empty()) { - return std::unexpected("invalid MCAP config: calibration_topic must not be empty"); - } - if (!config.record.mcap.depth_calibration_topic.empty() && - config.record.mcap.depth_calibration_topic == config.record.mcap.calibration_topic) { - return std::unexpected("invalid MCAP config: depth_calibration_topic must differ from calibration_topic"); - } - if (config.record.mcap.pose_topic.empty()) { - return std::unexpected("invalid MCAP config: pose_topic must not be empty"); - } - if (config.record.mcap.body_topic.empty()) { - return std::unexpected("invalid MCAP config: body_topic must not be empty"); - } - if (config.record.mcap.frame_id.empty()) { - return std::unexpected("invalid MCAP config: frame_id must not be empty"); + if (config.record.mcap.enabled) { + if (auto capability_validation = validate_mcap_capability_request(config.record.mcap); !capability_validation) { + return std::unexpected(capability_validation.error()); + } + if (config.record.mcap.path.empty()) { + return std::unexpected("invalid MCAP config: enabled MCAP output requires path"); + } + if (config.record.mcap.topic.empty()) { + return std::unexpected("invalid MCAP config: topic must not be empty"); + } + if (config.record.mcap.calibration_topic.empty()) { + return std::unexpected("invalid MCAP config: calibration_topic must not be empty"); + } + if (config.record.mcap.pose_topic.empty()) { + return std::unexpected("invalid MCAP config: pose_topic must not be empty"); + } + if (config.record.mcap.body_topic.empty()) { + return std::unexpected("invalid MCAP config: body_topic must not be empty"); + } + if (config.record.mcap.frame_id.empty()) { + return std::unexpected("invalid MCAP config: frame_id must not be empty"); + } + if (config.record.mcap.depth_enabled) { + if (config.record.mcap.depth_topic.empty()) { + return std::unexpected("invalid MCAP config: depth_topic must not be empty when depth recording is enabled"); + } + if (config.record.mcap.depth_calibration_topic.empty()) { + return std::unexpected( + "invalid MCAP config: depth_calibration_topic must not be empty when depth recording is enabled"); + } + if (config.record.mcap.depth_calibration_topic == config.record.mcap.calibration_topic) { + return std::unexpected( + "invalid MCAP config: depth_calibration_topic must differ from calibration_topic when depth recording is enabled"); + } + } } if (config.latency.queue_size == 0) { @@ -1213,6 +1306,7 @@ std::string summarize_runtime_config(const RuntimeConfig &config) { ss << ", rtp.endpoint=" << (config.outputs.rtp.endpoint ? *config.outputs.rtp.endpoint : ""); ss << ", rtp.payload_type=" << static_cast(config.outputs.rtp.payload_type); ss << ", mcap.enabled=" << (config.record.mcap.enabled ? "true" : "false"); + ss << ", mcap.depth_enabled=" << (config.record.mcap.depth_enabled ? "true" : "false"); ss << ", mcap.path=" << config.record.mcap.path; ss << ", mcap.topic=" << config.record.mcap.topic; ss << ", mcap.depth_topic=" << config.record.mcap.depth_topic; diff --git a/src/encode/ffmpeg_encoder_backend.cpp b/src/encode/ffmpeg_encoder_backend.cpp index c58c50a..0317a7a 100644 --- a/src/encode/ffmpeg_encoder_backend.cpp +++ b/src/encode/ffmpeg_encoder_backend.cpp @@ -218,11 +218,15 @@ public: } frame_->pict_type = frame.force_keyframe ? AV_PICTURE_TYPE_I : AV_PICTURE_TYPE_NONE; +#if defined(AV_FRAME_FLAG_KEY) if (frame.force_keyframe) { frame_->flags |= AV_FRAME_FLAG_KEY; } else { frame_->flags &= ~AV_FRAME_FLAG_KEY; } +#else + frame_->key_frame = frame.force_keyframe ? 1 : 0; +#endif frame_->pts = static_cast(frame.source_timestamp_ns - *first_source_timestamp_ns_); const auto send_result = avcodec_send_frame(context_, frame_); if (send_result < 0) { diff --git a/src/ipc/help.cpp b/src/ipc/help.cpp index 89fb7ab..10f4283 100644 --- a/src/ipc/help.cpp +++ b/src/ipc/help.cpp @@ -9,7 +9,7 @@ namespace cvmmap_streamer { namespace { -constexpr std::array kHelpLines{ +constexpr std::array kHelpLines{ "Usage:", " --help, -h\tshow this message", "", @@ -33,12 +33,14 @@ constexpr std::array kHelpLines{ " --rtmp-transport \tlibavformat|ffmpeg_process", " --rtmp-ffmpeg \tffmpeg binary for ffmpeg_process transport", " --mcap\t\tenable MCAP recording", + " --mcap-depth\t\tenable MCAP depth recording", " --mcap-path \tMCAP output file", " --mcap-topic \tMCAP topic name", - " --mcap-depth-topic \tMCAP depth topic name", + " --mcap-depth-topic \tMCAP depth topic name (implies --mcap)", " --mcap-body-topic \tMCAP body topic name", " --mcap-frame-id \tFoxglove CompressedVideo frame_id", " --mcap-compression \tnone|lz4|zstd", + " record.mcap.depth_enabled\tTOML toggle for optional depth recording", "", "Examples:", " cvmmap_streamer --help", diff --git a/src/pipeline/pipeline_runtime.cpp b/src/pipeline/pipeline_runtime.cpp index 5cdca7e..f1e1250 100644 --- a/src/pipeline/pipeline_runtime.cpp +++ b/src/pipeline/pipeline_runtime.cpp @@ -1,5 +1,6 @@ #include "cvmmap_streamer/config/runtime_config.hpp" #include "cvmmap_streamer/core/frame_source.hpp" +#include "cvmmap_streamer/core/status.hpp" #include "cvmmap_streamer/encode/encoder_backend.hpp" #include "cvmmap_streamer/ipc/contracts.hpp" #include "cvmmap_streamer/metrics/latency_tracker.hpp" @@ -11,6 +12,14 @@ #include "cvmmap_streamer/record/mp4_record_writer.hpp" #include "proto/cvmmap_streamer/recorder_control.pb.h" +#ifndef CVMMAP_STREAMER_HAS_MCAP +#define CVMMAP_STREAMER_HAS_MCAP 0 +#endif + +#ifndef CVMMAP_STREAMER_HAS_MCAP_DEPTH +#define CVMMAP_STREAMER_HAS_MCAP_DEPTH 0 +#endif + #include #include #include @@ -432,6 +441,32 @@ float stream_fps(const encode::EncodedStreamInfo &stream_info) { static_cast(stream_info.frame_rate_den); } +template +[[nodiscard]] bool runtime_mcap_depth_enabled(const Config &config) { + if constexpr (requires { config.record.mcap.depth_enabled; }) { + return config.record.mcap.depth_enabled; + } else { + return true; + } +} + +[[nodiscard]] constexpr bool has_mcap_support() { + return CVMMAP_STREAMER_HAS_MCAP != 0; +} + +[[nodiscard]] constexpr bool has_mcap_depth_support() { + return CVMMAP_STREAMER_HAS_MCAP_DEPTH != 0; +} + +[[nodiscard]] std::string mcap_disabled_message() { + return "MCAP recording support is not compiled into this build"; +} + +[[nodiscard]] std::string mcap_depth_disabled_message() { + return "MCAP depth recording support is not compiled into this build"; +} + +#if CVMMAP_STREAMER_HAS_MCAP struct McapRecorderState { mutable std::mutex mutex{}; RuntimeConfig base_config{}; @@ -449,27 +484,6 @@ struct McapRecorderState { } status{}; }; -struct Mp4RecorderStatus { - bool can_record{false}; - bool is_recording{false}; - bool last_frame_ok{false}; - std::uint32_t frames_ingested{0}; - std::uint32_t frames_encoded{0}; - std::string active_path{}; - std::string error_message{}; -}; - -struct Mp4RecorderState { - mutable std::mutex mutex{}; - RuntimeConfig base_config{}; - std::optional current_frame_info{}; - std::optional current_input_pixel_format{}; - float current_fps{30.0f}; - std::optional writer{}; - std::optional first_frame_timestamp_ns{}; - Mp4RecorderStatus status{}; -}; - [[nodiscard]] protocol::RpcError make_recorder_rpc_error( const protocol::RpcErrorCode code, @@ -502,49 +516,11 @@ RuntimeConfig make_mcap_record_config( return record_config; } -[[nodiscard]] -record::Mp4EncodeTuning make_mp4_encode_tuning(const RuntimeConfig &base_config) { - return record::Mp4EncodeTuning{ - .quality = record::kDefaultMp4Quality, - .gop = base_config.encoder.gop, - .b_frames = base_config.encoder.b_frames, - }; -} - -[[nodiscard]] -std::expected mp4_input_pixel_format( - const ipc::FrameInfo &frame_info) { - if (frame_info.depth != ipc::Depth::U8) { - return std::unexpected("MP4 recorder requires 8-bit color frames"); - } - switch (frame_info.pixel_format) { - case ipc::PixelFormat::BGR: - return record::Mp4InputPixelFormat::Bgr24; - case ipc::PixelFormat::RGB: - return record::Mp4InputPixelFormat::Rgb24; - case ipc::PixelFormat::BGRA: - return record::Mp4InputPixelFormat::Bgra32; - case ipc::PixelFormat::RGBA: - return record::Mp4InputPixelFormat::Rgba32; - case ipc::PixelFormat::GRAY: - return record::Mp4InputPixelFormat::Gray8; - case ipc::PixelFormat::YUV: - case ipc::PixelFormat::YUYV: - return std::unexpected("MP4 recorder does not support packed YUV snapshot frames"); - } - return std::unexpected("MP4 recorder does not support the snapshot pixel format"); -} - void reset_mcap_status_after_stop(McapRecorderState::Status &status) { status.is_recording = false; status.active_path.clear(); } -void reset_mp4_status_after_stop(Mp4RecorderStatus &status) { - status.is_recording = false; - status.active_path.clear(); -} - [[nodiscard]] recorder_pb::McapRecorderState to_proto_mcap_state( const McapRecorderState::Status &status) { @@ -653,6 +629,121 @@ std::expected get_mcap_recording_ recorder_state.status.can_record = true; return recorder_state.status; } +#else +struct McapRecorderState { + struct Status {}; +}; + +[[nodiscard]] +protocol::RpcError make_mcap_unsupported_rpc_error() { + return protocol::RpcError{ + .code = protocol::RpcErrorCode::Unsupported, + .message = mcap_disabled_message(), + }; +} + +[[nodiscard]] +std::expected start_mcap_recording( + McapRecorderState &, + const recorder_pb::McapStartRequest &) { + return std::unexpected(make_mcap_unsupported_rpc_error()); +} + +[[nodiscard]] +std::expected stop_mcap_recording( + McapRecorderState &, + const recorder_pb::McapStopRequest &) { + return std::unexpected(make_mcap_unsupported_rpc_error()); +} + +[[nodiscard]] +std::expected get_mcap_recording_status( + McapRecorderState &, + const recorder_pb::McapStatusRequest &) { + return std::unexpected(make_mcap_unsupported_rpc_error()); +} + +void update_mcap_stream_info( + McapRecorderState &, + const encode::EncodedStreamInfo &) {} + +Status write_mcap_access_unit( + McapRecorderState *, + const encode::EncodedAccessUnit &) { + return unexpected_error(ERR_UNSUPPORTED, mcap_disabled_message()); +} + +Status write_mcap_body_message( + McapRecorderState *, + const record::RawBodyTrackingMessageView &) { + return unexpected_error(ERR_UNSUPPORTED, mcap_disabled_message()); +} + +Status write_mcap_depth_map( + McapRecorderState *, + const record::RawDepthMapView &) { + return unexpected_error(ERR_UNSUPPORTED, mcap_depth_disabled_message()); +} +#endif + +struct Mp4RecorderStatus { + bool can_record{false}; + bool is_recording{false}; + bool last_frame_ok{false}; + std::uint32_t frames_ingested{0}; + std::uint32_t frames_encoded{0}; + std::string active_path{}; + std::string error_message{}; +}; + +struct Mp4RecorderState { + mutable std::mutex mutex{}; + RuntimeConfig base_config{}; + std::optional current_frame_info{}; + std::optional current_input_pixel_format{}; + float current_fps{30.0f}; + std::optional writer{}; + std::optional first_frame_timestamp_ns{}; + Mp4RecorderStatus status{}; +}; + +[[nodiscard]] +record::Mp4EncodeTuning make_mp4_encode_tuning(const RuntimeConfig &base_config) { + return record::Mp4EncodeTuning{ + .quality = record::kDefaultMp4Quality, + .gop = base_config.encoder.gop, + .b_frames = base_config.encoder.b_frames, + }; +} + +[[nodiscard]] +std::expected mp4_input_pixel_format( + const ipc::FrameInfo &frame_info) { + if (frame_info.depth != ipc::Depth::U8) { + return std::unexpected("MP4 recorder requires 8-bit color frames"); + } + switch (frame_info.pixel_format) { + case ipc::PixelFormat::BGR: + return record::Mp4InputPixelFormat::Bgr24; + case ipc::PixelFormat::RGB: + return record::Mp4InputPixelFormat::Rgb24; + case ipc::PixelFormat::BGRA: + return record::Mp4InputPixelFormat::Bgra32; + case ipc::PixelFormat::RGBA: + return record::Mp4InputPixelFormat::Rgba32; + case ipc::PixelFormat::GRAY: + return record::Mp4InputPixelFormat::Gray8; + case ipc::PixelFormat::YUV: + case ipc::PixelFormat::YUYV: + return std::unexpected("MP4 recorder does not support packed YUV snapshot frames"); + } + return std::unexpected("MP4 recorder does not support the snapshot pixel format"); +} + +void reset_mp4_status_after_stop(Mp4RecorderStatus &status) { + status.is_recording = false; + status.active_path.clear(); +} void close_mp4_writer_with_error( Mp4RecorderState &recorder_state, @@ -893,6 +984,7 @@ void write_mp4_frame( recorder_state->status.frames_encoded += 1; } +#if CVMMAP_STREAMER_HAS_MCAP void update_mcap_stream_info( McapRecorderState &recorder_state, const encode::EncodedStreamInfo &stream_info) { @@ -981,6 +1073,7 @@ Status write_mcap_depth_map( recorder_state->status.last_frame_ok = true; return {}; } +#endif [[nodiscard]] Status publish_access_units( @@ -1126,10 +1219,12 @@ int run_pipeline(const RuntimeConfig &config) { std::optional rtp_publisher{}; std::optional rtmp_output{}; +#if CVMMAP_STREAMER_HAS_MCAP McapRecorderState mcap_recorder{}; - Mp4RecorderState mp4_recorder{}; mcap_recorder.base_config = config; mcap_recorder.status.can_record = true; +#endif + Mp4RecorderState mp4_recorder{}; mp4_recorder.base_config = config; cvmmap::NatsControlClient nats_client( input_endpoints->nats_target_key, @@ -1141,7 +1236,7 @@ int run_pipeline(const RuntimeConfig &config) { .ipc_prefix = input_endpoints->ipc_prefix, .base_name = input_endpoints->base_name, .nats_target_key = input_endpoints->nats_target_key, - .recording_formats = "mp4,mcap", + .recording_formats = has_mcap_support() ? "mp4,mcap" : "mp4", }); std::mutex nats_event_mutex{}; std::deque> pending_body_packets{}; @@ -1200,6 +1295,7 @@ int run_pipeline(const RuntimeConfig &config) { } return make_ok_mp4_response(*status); }); +#if CVMMAP_STREAMER_HAS_MCAP recorder_rpc_server.register_proto_endpoint< recorder_pb::McapStartRequest, recorder_pb::McapStartResponse>( @@ -1239,6 +1335,7 @@ int run_pipeline(const RuntimeConfig &config) { } return make_ok_mcap_response(*status); }); +#endif if (!recorder_rpc_server.start()) { spdlog::error("pipeline streamer recorder service failed on '{}'", config.input.nats_url); nats_client.Stop(); @@ -1348,8 +1445,8 @@ int run_pipeline(const RuntimeConfig &config) { live_output_continuity.note_new_session(stream_info); } +#if CVMMAP_STREAMER_HAS_MCAP update_mcap_stream_info(mcap_recorder, stream_info); - update_mp4_source_info(mp4_recorder, target_info, stream_fps(stream_info)); if (config.record.mcap.enabled) { std::lock_guard lock(mcap_recorder.mutex); if (!mcap_recorder.sink) { @@ -1367,6 +1464,12 @@ int run_pipeline(const RuntimeConfig &config) { mcap_recorder.status.active_path = config.record.mcap.path; } } +#else + if (config.record.mcap.enabled) { + return unexpected_error(ERR_UNSUPPORTED, mcap_disabled_message()); + } +#endif + update_mp4_source_info(mp4_recorder, target_info, stream_fps(stream_info)); started = true; restart_pending = false; restart_target_info.reset(); @@ -1481,6 +1584,7 @@ int run_pipeline(const RuntimeConfig &config) { continue; } +#if CVMMAP_STREAMER_HAS_MCAP auto write_body = write_mcap_body_message(&mcap_recorder, record::RawBodyTrackingMessageView{ .timestamp_ns = body_tracking_timestamp_ns(*parsed_body), .bytes = body_bytes, @@ -1490,6 +1594,7 @@ int run_pipeline(const RuntimeConfig &config) { restart_backend(reason, active_info); break; } +#endif } if (backend && !using_encoded_input) { @@ -1532,7 +1637,11 @@ int run_pipeline(const RuntimeConfig &config) { stats, rtp_publisher ? &*rtp_publisher : nullptr, rtmp_output ? &*rtmp_output : nullptr, +#if CVMMAP_STREAMER_HAS_MCAP &mcap_recorder, +#else + nullptr, +#endif keep_live_outputs_on_reset ? &live_output_continuity : nullptr, latency_tracker); if (!drain) { @@ -1656,7 +1765,11 @@ int run_pipeline(const RuntimeConfig &config) { stats, rtp_publisher ? &*rtp_publisher : nullptr, rtmp_output ? &*rtmp_output : nullptr, +#if CVMMAP_STREAMER_HAS_MCAP &mcap_recorder, +#else + nullptr, +#endif keep_live_outputs_on_reset ? &live_output_continuity : nullptr, latency_tracker); if (!publish) { @@ -1678,7 +1791,13 @@ int run_pipeline(const RuntimeConfig &config) { } } - if (!snapshot->depth.empty()) { +#if CVMMAP_STREAMER_HAS_MCAP + if (!snapshot->depth.empty() && config.record.mcap.enabled && runtime_mcap_depth_enabled(config)) { +#if !CVMMAP_STREAMER_HAS_MCAP_DEPTH + const auto reason = "pipeline depth MCAP write failed: " + mcap_depth_disabled_message(); + restart_backend(reason, active_info); + continue; +#else if (snapshot->depth_unit == ipc::DepthUnit::Unknown) { if (!warned_unknown_depth_unit) { spdlog::warn( @@ -1700,7 +1819,9 @@ int run_pipeline(const RuntimeConfig &config) { restart_backend(reason, active_info); continue; } +#endif } +#endif stats.pushed_frames += 1; if (!want_encoded_input) { @@ -1711,7 +1832,11 @@ int run_pipeline(const RuntimeConfig &config) { stats, rtp_publisher ? &*rtp_publisher : nullptr, rtmp_output ? &*rtmp_output : nullptr, +#if CVMMAP_STREAMER_HAS_MCAP &mcap_recorder, +#else + nullptr, +#endif keep_live_outputs_on_reset ? &live_output_continuity : nullptr, latency_tracker); if (!drain) { @@ -1741,7 +1866,11 @@ int run_pipeline(const RuntimeConfig &config) { stats, rtp_publisher ? &*rtp_publisher : nullptr, rtmp_output ? &*rtmp_output : nullptr, +#if CVMMAP_STREAMER_HAS_MCAP &mcap_recorder, +#else + nullptr, +#endif keep_live_outputs_on_reset ? &live_output_continuity : nullptr, latency_tracker); if (!drain) { @@ -1757,10 +1886,12 @@ int run_pipeline(const RuntimeConfig &config) { if (!stop_mp4) { spdlog::warn("pipeline MP4 recorder stop during shutdown failed: {}", stop_mp4.error().message); } +#if CVMMAP_STREAMER_HAS_MCAP auto stop_mcap = stop_mcap_recording(mcap_recorder, recorder_pb::McapStopRequest{}); if (!stop_mcap) { spdlog::warn("pipeline MCAP recorder stop during shutdown failed: {}", stop_mcap.error().message); } +#endif recorder_rpc_server.stop(); spdlog::info( diff --git a/src/record/mcap_record_sink.cpp b/src/record/mcap_record_sink.cpp index 445fc87..b30b70d 100644 --- a/src/record/mcap_record_sink.cpp +++ b/src/record/mcap_record_sink.cpp @@ -1,16 +1,28 @@ -#include - #include "cvmmap_streamer/record/mcap_record_sink.hpp" +#ifndef CVMMAP_STREAMER_HAS_MCAP +#define CVMMAP_STREAMER_HAS_MCAP 0 +#endif + +#ifndef CVMMAP_STREAMER_HAS_MCAP_DEPTH +#define CVMMAP_STREAMER_HAS_MCAP_DEPTH 0 +#endif + +#if CVMMAP_STREAMER_HAS_MCAP +#include + #include "protobuf_descriptor.hpp" #include "proto/cvmmap_streamer/BundleManifest.pb.h" #include "proto/cvmmap_streamer/DepthMap.pb.h" #include "proto/foxglove/CameraCalibration.pb.h" #include "proto/foxglove/CompressedVideo.pb.h" #include "proto/foxglove/PoseInFrame.pb.h" +#if CVMMAP_STREAMER_HAS_MCAP_DEPTH #include +#endif #include +#endif #include #include @@ -26,10 +38,23 @@ namespace cvmmap_streamer::record { +template +[[nodiscard]] bool mcap_depth_requested(const Config &config) { + if constexpr (requires { config.record.mcap.depth_enabled; }) { + return config.record.mcap.depth_enabled; + } else { + return true; + } +} + +#if CVMMAP_STREAMER_HAS_MCAP + namespace { +#if CVMMAP_STREAMER_HAS_MCAP_DEPTH constexpr float kRvlDepthQuantization = 200.0f; constexpr float kMinDepthMaxMeters = 20.0f; +#endif constexpr std::string_view kBodyTrackingMessageEncoding = "cvmmap.body_tracking.v1"; [[nodiscard]] @@ -268,6 +293,7 @@ std::expected, std::string> decoder_config_to_annexb( return avcc_to_annexb(decoder_config); } +#if CVMMAP_STREAMER_HAS_MCAP_DEPTH [[nodiscard]] bool can_encode_lossless_u16_mm(const RawDepthMapView &depth_map) { if (normalize_depth_source_unit(depth_map.source_unit) != ipc::DepthUnit::Millimeter) { @@ -367,6 +393,7 @@ std::expected encode_depth_payload(const RawDe return std::unexpected(std::string("failed to RVL-encode depth map: ") + error.what()); } } +#endif [[nodiscard]] std::expected append_repeated_double( @@ -619,18 +646,26 @@ std::expected McapRecordSink::create( state->writer.addChannel(channel); state->video_channel_id = channel.id; - const auto depth_descriptor_set = build_file_descriptor_set(cvmmap_streamer::DepthMap::descriptor()); - std::string depth_schema_bytes{}; - if (!depth_descriptor_set.SerializeToString(&depth_schema_bytes)) { - return std::unexpected("failed to serialize cvmmap_streamer.DepthMap descriptor set"); +#if CVMMAP_STREAMER_HAS_MCAP_DEPTH + if (mcap_depth_requested(config)) { + const auto depth_descriptor_set = build_file_descriptor_set(cvmmap_streamer::DepthMap::descriptor()); + std::string depth_schema_bytes{}; + if (!depth_descriptor_set.SerializeToString(&depth_schema_bytes)) { + return std::unexpected("failed to serialize cvmmap_streamer.DepthMap descriptor set"); + } + + mcap::Schema depth_schema("cvmmap_streamer.DepthMap", "protobuf", depth_schema_bytes); + state->writer.addSchema(depth_schema); + + mcap::Channel depth_channel(config.record.mcap.depth_topic, "protobuf", depth_schema.id); + state->writer.addChannel(depth_channel); + state->depth_channel_id = depth_channel.id; } - - mcap::Schema depth_schema("cvmmap_streamer.DepthMap", "protobuf", depth_schema_bytes); - state->writer.addSchema(depth_schema); - - mcap::Channel depth_channel(config.record.mcap.depth_topic, "protobuf", depth_schema.id); - state->writer.addChannel(depth_channel); - state->depth_channel_id = depth_channel.id; +#else + if (mcap_depth_requested(config)) { + return std::unexpected("MCAP depth recording support is not compiled into this build"); + } +#endif const auto calibration_descriptor_set = build_file_descriptor_set(foxglove::CameraCalibration::descriptor()); std::string calibration_schema_bytes{}; @@ -661,6 +696,7 @@ std::expected McapRecordSink::create( return sink; } + std::expected McapRecordSink::update_stream_info(const encode::EncodedStreamInfo &stream_info) { if (state_ == nullptr) { return std::unexpected("MCAP sink is not open"); @@ -717,8 +753,14 @@ std::expected McapRecordSink::write_depth_map(const RawDepthM if (state_ == nullptr) { return std::unexpected("MCAP sink is not open"); } + if (state_->depth_channel_id == 0) { + return std::unexpected("MCAP depth recording support is not enabled for this sink"); + } +#if !CVMMAP_STREAMER_HAS_MCAP_DEPTH + static_cast(depth_map); + return std::unexpected("MCAP depth recording support is not compiled into this build"); +#else const auto source_unit = normalize_depth_source_unit(depth_map.source_unit); - auto encoded = encode_depth_payload(depth_map); if (!encoded) { return std::unexpected(encoded.error()); @@ -734,13 +776,20 @@ std::expected McapRecordSink::write_depth_map(const RawDepthM state_->depth_channel_id, state_->depth_sequence, *encoded); +#endif } std::expected McapRecordSink::write_depth_map_u16(const RawDepthMapU16View &depth_map) { if (state_ == nullptr) { return std::unexpected("MCAP sink is not open"); } - + if (state_->depth_channel_id == 0) { + return std::unexpected("MCAP depth recording support is not enabled for this sink"); + } +#if !CVMMAP_STREAMER_HAS_MCAP_DEPTH + static_cast(depth_map); + return std::unexpected("MCAP depth recording support is not compiled into this build"); +#else auto encoded = encode_depth_payload(depth_map); if (!encoded) { return std::unexpected(encoded.error()); @@ -756,6 +805,7 @@ std::expected McapRecordSink::write_depth_map_u16(const RawDe state_->depth_channel_id, state_->depth_sequence, *encoded); +#endif } std::expected McapRecordSink::write_camera_calibration(const RawCameraCalibrationView &calibration) { @@ -777,6 +827,9 @@ std::expected McapRecordSink::write_depth_camera_calibration( if (state_ == nullptr) { return std::unexpected("MCAP sink is not open"); } + if (state_->depth_channel_id == 0) { + return std::unexpected("MCAP depth recording support is not enabled for this sink"); + } return write_calibration_message( state_->writer, calibration.timestamp_ns, @@ -959,9 +1012,14 @@ std::expected validate_new_stream_config( if (config.topic.empty()) { return std::unexpected("video topic is empty"); } - if (config.depth_topic.empty()) { - return std::unexpected("depth topic is empty"); +#if CVMMAP_STREAMER_HAS_MCAP_DEPTH + const bool depth_enabled = !config.depth_topic.empty(); +#else + if (!config.depth_topic.empty()) { + return std::unexpected("MCAP depth recording support is not compiled into this build"); } + constexpr bool depth_enabled = false; +#endif if (config.frame_id.empty()) { return std::unexpected("frame_id is empty"); } @@ -984,9 +1042,11 @@ std::expected validate_new_stream_config( }; std::vector config_topics{}; + config_topics.push_back(config.topic); + if (depth_enabled) { + config_topics.push_back(config.depth_topic); + } for (const auto *topic : { - &config.topic, - &config.depth_topic, &config.calibration_topic, &config.depth_calibration_topic, &config.pose_topic, @@ -1003,16 +1063,9 @@ std::expected validate_new_stream_config( } } - for (const auto *topic : { - &config.topic, - &config.depth_topic, - &config.calibration_topic, - &config.depth_calibration_topic, - &config.pose_topic, - &config.body_topic, - }) { - if (!topic->empty() && topic_in_use(*topic)) { - return std::unexpected("duplicate MCAP topic: " + *topic); + for (const auto &topic : config_topics) { + if (topic_in_use(topic)) { + return std::unexpected("duplicate MCAP topic: " + topic); } } return {}; @@ -1063,6 +1116,7 @@ std::expected MultiMcapRecordSink::create( state->writer.addSchema(video_schema); state->video_schema_id = video_schema.id; +#if CVMMAP_STREAMER_HAS_MCAP_DEPTH const auto depth_descriptor_set = build_file_descriptor_set(cvmmap_streamer::DepthMap::descriptor()); std::string depth_schema_bytes{}; if (!depth_descriptor_set.SerializeToString(&depth_schema_bytes)) { @@ -1071,6 +1125,7 @@ std::expected MultiMcapRecordSink::create( mcap::Schema depth_schema("cvmmap_streamer.DepthMap", "protobuf", depth_schema_bytes); state->writer.addSchema(depth_schema); state->depth_schema_id = depth_schema.id; +#endif if (!state->bundle_topic.empty()) { const auto bundle_descriptor_set = build_file_descriptor_set(cvmmap_streamer::BundleManifest::descriptor()); @@ -1126,9 +1181,13 @@ std::expected MultiMcapRecordSink::a state_->writer.addChannel(video_channel); stream.video_channel_id = video_channel.id; - mcap::Channel depth_channel(config.depth_topic, "protobuf", state_->depth_schema_id); - state_->writer.addChannel(depth_channel); - stream.depth_channel_id = depth_channel.id; +#if CVMMAP_STREAMER_HAS_MCAP_DEPTH + if (!config.depth_topic.empty()) { + mcap::Channel depth_channel(config.depth_topic, "protobuf", state_->depth_schema_id); + state_->writer.addChannel(depth_channel); + stream.depth_channel_id = depth_channel.id; + } +#endif if (auto updated = update_stream_info_impl(stream, stream_info); !updated) { return std::unexpected(updated.error()); @@ -1200,7 +1259,13 @@ std::expected MultiMcapRecordSink::write_depth_map( if (stream == nullptr) { return std::unexpected(error); } - + if (stream->depth_channel_id == 0) { + return std::unexpected("MCAP depth recording support is not enabled for this stream"); + } +#if !CVMMAP_STREAMER_HAS_MCAP_DEPTH + static_cast(depth_map); + return std::unexpected("MCAP depth recording support is not compiled into this build"); +#else const auto source_unit = normalize_depth_source_unit(depth_map.source_unit); auto encoded = encode_depth_payload(depth_map); if (!encoded) { @@ -1217,6 +1282,7 @@ std::expected MultiMcapRecordSink::write_depth_map( stream->depth_channel_id, stream->depth_sequence, *encoded); +#endif } std::expected MultiMcapRecordSink::write_depth_map_u16( @@ -1227,7 +1293,13 @@ std::expected MultiMcapRecordSink::write_depth_map_u16( if (stream == nullptr) { return std::unexpected(error); } - + if (stream->depth_channel_id == 0) { + return std::unexpected("MCAP depth recording support is not enabled for this stream"); + } +#if !CVMMAP_STREAMER_HAS_MCAP_DEPTH + static_cast(depth_map); + return std::unexpected("MCAP depth recording support is not compiled into this build"); +#else auto encoded = encode_depth_payload(depth_map); if (!encoded) { return std::unexpected(encoded.error()); @@ -1243,6 +1315,7 @@ std::expected MultiMcapRecordSink::write_depth_map_u16( stream->depth_channel_id, stream->depth_sequence, *encoded); +#endif } std::expected MultiMcapRecordSink::write_camera_calibration( @@ -1272,6 +1345,9 @@ std::expected MultiMcapRecordSink::write_depth_camera_calibra if (stream == nullptr) { return std::unexpected(error); } + if (stream->depth_channel_id == 0) { + return std::unexpected("MCAP depth recording support is not enabled for this stream"); + } return write_calibration_message( state_->writer, calibration.timestamp_ns, @@ -1407,4 +1483,91 @@ void MultiMcapRecordSink::close() { state_ = nullptr; } +#else + +namespace { + +[[nodiscard]] std::string mcap_unavailable_error() { + return "MCAP recording support is not compiled into this build"; +} + +[[nodiscard]] std::string mcap_depth_unavailable_error() { + return "MCAP depth recording support is not compiled into this build"; +} + +} // namespace + +McapRecordSink::~McapRecordSink() = default; +McapRecordSink::McapRecordSink(McapRecordSink &&other) noexcept = default; +McapRecordSink &McapRecordSink::operator=(McapRecordSink &&other) noexcept = default; + +std::expected McapRecordSink::create( + const RuntimeConfig &, + const encode::EncodedStreamInfo &) { + return std::unexpected(mcap_unavailable_error()); +} + +std::expected McapRecordSink::update_stream_info(const encode::EncodedStreamInfo &) { + return std::unexpected(mcap_unavailable_error()); +} + +std::expected McapRecordSink::write_access_unit(const encode::EncodedAccessUnit &) { + return std::unexpected(mcap_unavailable_error()); +} + +std::expected McapRecordSink::write_depth_map(const RawDepthMapView &) { + return std::unexpected(mcap_depth_unavailable_error()); +} + +std::expected McapRecordSink::write_depth_map_u16(const RawDepthMapU16View &) { + return std::unexpected(mcap_depth_unavailable_error()); +} + +std::expected McapRecordSink::write_camera_calibration(const RawCameraCalibrationView &) { + return std::unexpected(mcap_unavailable_error()); +} + +std::expected McapRecordSink::write_depth_camera_calibration(const RawCameraCalibrationView &) { + return std::unexpected(mcap_depth_unavailable_error()); +} + +std::expected McapRecordSink::write_pose(const RawPoseView &) { + return std::unexpected(mcap_unavailable_error()); +} + +std::expected McapRecordSink::write_body_tracking_message(const RawBodyTrackingMessageView &) { + return std::unexpected(mcap_unavailable_error()); +} + +bool McapRecordSink::is_open() const { + return false; +} + +std::string_view McapRecordSink::path() const { + return {}; +} + +void McapRecordSink::close() {} + +MultiMcapRecordSink::~MultiMcapRecordSink() = default; +MultiMcapRecordSink::MultiMcapRecordSink(MultiMcapRecordSink &&other) noexcept = default; +MultiMcapRecordSink &MultiMcapRecordSink::operator=(MultiMcapRecordSink &&other) noexcept = default; + +std::expected MultiMcapRecordSink::create( + std::string, + McapCompression, + std::string) { + return std::unexpected(mcap_unavailable_error()); +} + +std::expected MultiMcapRecordSink::add_stream( + const McapRecordStreamConfig &, + const encode::EncodedStreamInfo &) { + return std::unexpected(const { + return {}; +} + +void MultiMcapRecordSink::close() {} + +#endif } diff --git a/src/testers/mcap_body_record_tester.cpp b/src/testers/mcap_body_record_tester.cpp index b843f49..caa1b2c 100644 --- a/src/testers/mcap_body_record_tester.cpp +++ b/src/testers/mcap_body_record_tester.cpp @@ -77,7 +77,6 @@ int main(int argc, char **argv) { config.record.mcap.enabled = true; config.record.mcap.path = output_path.string(); config.record.mcap.topic = "/camera/video"; - config.record.mcap.depth_topic = "/camera/depth"; config.record.mcap.body_topic = std::string(kBodyTopic); config.record.mcap.frame_id = "camera"; config.record.mcap.compression = cvmmap_streamer::McapCompression::None; diff --git a/src/testers/mcap_multi_record_tester.cpp b/src/testers/mcap_multi_record_tester.cpp index 4768a0a..96fe0a7 100644 --- a/src/testers/mcap_multi_record_tester.cpp +++ b/src/testers/mcap_multi_record_tester.cpp @@ -1,14 +1,12 @@ #include #include "proto/cvmmap_streamer/BundleManifest.pb.h" -#include "proto/cvmmap_streamer/DepthMap.pb.h" #include "cvmmap_streamer/common.h" #include "cvmmap_streamer/record/mcap_record_sink.hpp" #include "proto/foxglove/CameraCalibration.pb.h" #include "proto/foxglove/CompressedVideo.pb.h" #include "proto/foxglove/PoseInFrame.pb.h" -#include #include #include @@ -86,18 +84,14 @@ int main(int argc, char **argv) { auto zed1 = sink->add_stream(cvmmap_streamer::record::McapRecordStreamConfig{ .topic = "/zed1/video", - .depth_topic = "/zed1/depth", .calibration_topic = "/zed1/calibration", - .depth_calibration_topic = "/zed1/depth_calibration", .pose_topic = "/zed1/pose", .body_topic = "/zed1/body", .frame_id = "zed1", }, stream_info); auto zed2 = sink->add_stream(cvmmap_streamer::record::McapRecordStreamConfig{ .topic = "/zed2/video", - .depth_topic = "/zed2/depth", .calibration_topic = "/zed2/calibration", - .depth_calibration_topic = "/zed2/depth_calibration", .pose_topic = "/zed2/pose", .body_topic = "/zed2/body", .frame_id = "zed2", @@ -107,10 +101,6 @@ int main(int argc, char **argv) { return exit_code(TesterExitCode::CreateError); } - const std::vector depth_pixels{ - 1000, 2000, - 1500, 2500, - }; const std::vector distortion{0.0, 0.0, 0.0, 0.0, 0.0}; const std::vector intrinsic_matrix{ 500.0, 0.0, 320.0, @@ -168,15 +158,6 @@ int main(int argc, char **argv) { return exit_code(TesterExitCode::WriteError); } - if (auto write = sink->write_depth_map_u16(stream_id, cvmmap_streamer::record::RawDepthMapU16View{ - .timestamp_ns = 100, - .width = 2, - .height = 2, - .pixels = depth_pixels, - }); !write) { - spdlog::error("failed to write depth map: {}", write.error()); - return exit_code(TesterExitCode::WriteError); - } if (auto write = sink->write_camera_calibration(stream_id, cvmmap_streamer::record::RawCameraCalibrationView{ .timestamp_ns = 100, @@ -191,19 +172,7 @@ int main(int argc, char **argv) { spdlog::error("failed to write calibration: {}", write.error()); return exit_code(TesterExitCode::WriteError); } - if (auto write = sink->write_depth_camera_calibration(stream_id, cvmmap_streamer::record::RawCameraCalibrationView{ - .timestamp_ns = 100, - .width = 320, - .height = 240, - .distortion_model = "plumb_bob", - .distortion = distortion, - .intrinsic_matrix = intrinsic_matrix, - .rectification_matrix = rectification_matrix, - .projection_matrix = projection_matrix, - }); !write) { - spdlog::error("failed to write depth calibration: {}", write.error()); - return exit_code(TesterExitCode::WriteError); - } + if (auto write = sink->write_pose(stream_id, cvmmap_streamer::record::RawPoseView{ .timestamp_ns = 100, @@ -251,23 +220,6 @@ int main(int argc, char **argv) { continue; } - if (it->schema->name == "cvmmap_streamer.DepthMap") { - cvmmap_streamer::DepthMap depth{}; - if (!depth.ParseFromArray(it->message.data, static_cast(it->message.dataSize))) { - spdlog::error("failed to parse cvmmap_streamer.DepthMap"); - reader.close(); - return exit_code(TesterExitCode::VerificationError); - } - const auto decoded = rvl::decompress_image(std::span( - reinterpret_cast(depth.data().data()), - depth.data().size())); - if (decoded.pixels.size() != depth_pixels.size()) { - spdlog::error("decoded depth pixel count mismatch"); - reader.close(); - return exit_code(TesterExitCode::VerificationError); - } - continue; - } if (it->schema->name == "cvmmap_streamer.BundleManifest") { cvmmap_streamer::BundleManifest bundle{}; @@ -319,14 +271,10 @@ int main(int argc, char **argv) { for (const auto &topic : { "/bundle", "/zed1/video", - "/zed1/depth", "/zed1/calibration", - "/zed1/depth_calibration", "/zed1/pose", "/zed2/video", - "/zed2/depth", "/zed2/calibration", - "/zed2/depth_calibration", "/zed2/pose", }) { if (topic_counts[topic] != 1) { diff --git a/src/testers/mcap_pose_record_tester.cpp b/src/testers/mcap_pose_record_tester.cpp index 5998184..c65755f 100644 --- a/src/testers/mcap_pose_record_tester.cpp +++ b/src/testers/mcap_pose_record_tester.cpp @@ -1,13 +1,11 @@ #include -#include "proto/cvmmap_streamer/DepthMap.pb.h" #include "cvmmap_streamer/common.h" #include "cvmmap_streamer/record/mcap_record_sink.hpp" #include "proto/foxglove/CameraCalibration.pb.h" #include "proto/foxglove/CompressedVideo.pb.h" #include "proto/foxglove/PoseInFrame.pb.h" -#include #include #include @@ -56,7 +54,6 @@ int main(int argc, char **argv) { config.record.mcap.enabled = true; config.record.mcap.path = output_path.string(); config.record.mcap.topic = "/camera/video"; - config.record.mcap.depth_topic = "/camera/depth"; config.record.mcap.calibration_topic = "/camera/calibration"; config.record.mcap.pose_topic = "/camera/pose"; config.record.mcap.frame_id = "camera"; @@ -82,19 +79,6 @@ int main(int argc, char **argv) { return exit_code(TesterExitCode::WriteError); } - const std::vector depth_pixels{ - 1000, 2000, - 0, 1500, - }; - if (auto write = sink->write_depth_map_u16(cvmmap_streamer::record::RawDepthMapU16View{ - .timestamp_ns = 101, - .width = 2, - .height = 2, - .pixels = depth_pixels, - }); !write) { - spdlog::error("failed to write depth map: {}", write.error()); - return exit_code(TesterExitCode::WriteError); - } const std::vector distortion{0.0, 0.0, 0.0, 0.0, 0.0}; const std::vector intrinsic_matrix{ @@ -146,7 +130,6 @@ int main(int argc, char **argv) { } std::uint64_t video_messages{0}; - std::uint64_t depth_messages{0}; std::uint64_t calibration_messages{0}; std::uint64_t pose_messages{0}; @@ -179,35 +162,6 @@ int main(int argc, char **argv) { continue; } - if (it->schema->name == "cvmmap_streamer.DepthMap") { - cvmmap_streamer::DepthMap depth{}; - if (!depth.ParseFromArray(it->message.data, static_cast(it->message.dataSize))) { - spdlog::error("failed to parse cvmmap_streamer.DepthMap"); - reader.close(); - return exit_code(TesterExitCode::VerificationError); - } - if (it->channel->topic != "/camera/depth" || - depth.frame_id() != "camera" || - depth.encoding() != cvmmap_streamer::DepthMap::RVL_U16_LOSSLESS) { - spdlog::error("depth message metadata verification failed"); - reader.close(); - return exit_code(TesterExitCode::VerificationError); - } - const auto decoded = rvl::decompress_image(std::span( - reinterpret_cast(depth.data().data()), - depth.data().size())); - if (decoded.pixels.size() != depth_pixels.size() || - decoded.pixels[0] != 1000 || - decoded.pixels[1] != 2000 || - decoded.pixels[2] != 0 || - decoded.pixels[3] != 1500) { - spdlog::error("depth RVL round-trip verification failed"); - reader.close(); - return exit_code(TesterExitCode::VerificationError); - } - depth_messages += 1; - continue; - } if (it->schema->name == "foxglove.CameraCalibration") { foxglove::CameraCalibration calibration{}; @@ -262,18 +216,17 @@ int main(int argc, char **argv) { reader.close(); - if (video_messages != 1 || depth_messages != 1 || calibration_messages != 1 || pose_messages != 1) { + if (video_messages != 1 || calibration_messages != 1 || pose_messages != 1) { spdlog::error( - "unexpected message counts: video={} depth={} calibration={} pose={}", + "unexpected message counts: video={} calibration={} pose={}", video_messages, - depth_messages, calibration_messages, pose_messages); return exit_code(TesterExitCode::VerificationError); } spdlog::info( - "validated same-file MCAP video+depth+calibration+pose recording at '{}'", + "validated same-file MCAP video+calibration+pose recording at '{}'", output_path.string()); return exit_code(TesterExitCode::Success); }