Make MCAP and depth support optional
This commit is contained in:
+228
-35
@@ -8,6 +8,68 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|||||||
|
|
||||||
include(GNUInstallDirs)
|
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(Threads REQUIRED)
|
||||||
find_package(OpenSSL REQUIRED)
|
find_package(OpenSSL REQUIRED)
|
||||||
if (NOT TARGET OpenSSL::SSL AND DEFINED OPENSSL_SSL_LIBRARY)
|
if (NOT TARGET OpenSSL::SSL AND DEFINED OPENSSL_SSL_LIBRARY)
|
||||||
@@ -73,15 +135,110 @@ endif()
|
|||||||
find_package(ZeroMQ QUIET)
|
find_package(ZeroMQ QUIET)
|
||||||
find_package(spdlog REQUIRED)
|
find_package(spdlog REQUIRED)
|
||||||
find_package(Protobuf 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(PkgConfig REQUIRED)
|
||||||
find_package(rvl CONFIG QUIET)
|
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)
|
add_subdirectory(third_party)
|
||||||
|
|
||||||
pkg_check_modules(FFMPEG REQUIRED IMPORTED_TARGET libavcodec libavformat libavutil libswscale)
|
pkg_check_modules(FFMPEG REQUIRED IMPORTED_TARGET libavcodec libavformat libavutil libswscale)
|
||||||
pkg_check_modules(PROTOBUF_PKG QUIET IMPORTED_TARGET protobuf)
|
pkg_check_modules(PROTOBUF_PKG QUIET IMPORTED_TARGET protobuf)
|
||||||
pkg_check_modules(ZSTD REQUIRED IMPORTED_TARGET libzstd)
|
pkg_check_modules(ZSTD QUIET IMPORTED_TARGET libzstd)
|
||||||
pkg_check_modules(LZ4 REQUIRED IMPORTED_TARGET liblz4)
|
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 (NOT TARGET cvmmap::client)
|
||||||
if (
|
if (
|
||||||
@@ -104,9 +261,7 @@ if (NOT TARGET cvmmap::client)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT TARGET rvl::rvl)
|
if (CVMMAP_STREAMER_HAS_MCAP_DEPTH AND 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 (
|
if (
|
||||||
EXISTS "${RVL_LOCAL_ROOT}/core/include/rvl/rvl.hpp"
|
EXISTS "${RVL_LOCAL_ROOT}/core/include/rvl/rvl.hpp"
|
||||||
AND EXISTS "${RVL_LOCAL_BUILD}/librvl_core.a")
|
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_depth_proto PUBLIC cvmmap_streamer_protobuf)
|
||||||
target_link_libraries(cvmmap_streamer_control_proto PUBLIC cvmmap_streamer_protobuf)
|
target_link_libraries(cvmmap_streamer_control_proto PUBLIC cvmmap_streamer_protobuf)
|
||||||
|
|
||||||
add_library(cvmmap_streamer_mcap_runtime STATIC
|
add_library(cvmmap_streamer_feature_flags INTERFACE)
|
||||||
src/record/mcap_runtime.cpp)
|
target_compile_definitions(cvmmap_streamer_feature_flags
|
||||||
target_include_directories(cvmmap_streamer_mcap_runtime
|
INTERFACE
|
||||||
PUBLIC)
|
CVMMAP_STREAMER_HAS_MCAP=${CVMMAP_STREAMER_HAS_MCAP}
|
||||||
target_link_libraries(cvmmap_streamer_mcap_runtime
|
CVMMAP_STREAMER_HAS_MCAP_DEPTH=${CVMMAP_STREAMER_HAS_MCAP_DEPTH})
|
||||||
PUBLIC
|
|
||||||
mcap::mcap
|
|
||||||
PkgConfig::ZSTD
|
|
||||||
PkgConfig::LZ4)
|
|
||||||
|
|
||||||
add_library(cvmmap_streamer_record_support STATIC
|
add_library(cvmmap_streamer_record_support STATIC
|
||||||
src/encode/encoder_backend.cpp
|
src/encode/encoder_backend.cpp
|
||||||
src/encode/ffmpeg_encoder_backend.cpp
|
src/encode/ffmpeg_encoder_backend.cpp
|
||||||
src/record/protobuf_descriptor.cpp
|
src/record/protobuf_descriptor.cpp
|
||||||
src/record/mcap_record_sink.cpp
|
|
||||||
src/record/mp4_record_writer.cpp)
|
src/record/mp4_record_writer.cpp)
|
||||||
target_include_directories(cvmmap_streamer_record_support
|
target_include_directories(cvmmap_streamer_record_support
|
||||||
PUBLIC
|
PUBLIC
|
||||||
@@ -207,14 +357,8 @@ target_include_directories(cvmmap_streamer_record_support
|
|||||||
"${CMAKE_CURRENT_BINARY_DIR}")
|
"${CMAKE_CURRENT_BINARY_DIR}")
|
||||||
target_link_libraries(cvmmap_streamer_record_support
|
target_link_libraries(cvmmap_streamer_record_support
|
||||||
PUBLIC
|
PUBLIC
|
||||||
cvmmap_streamer_foxglove_proto
|
cvmmap_streamer_feature_flags
|
||||||
cvmmap_streamer_depth_proto
|
|
||||||
cvmmap_streamer_mcap_runtime
|
|
||||||
PkgConfig::FFMPEG
|
PkgConfig::FFMPEG
|
||||||
PkgConfig::ZSTD
|
|
||||||
PkgConfig::LZ4
|
|
||||||
rvl::rvl
|
|
||||||
mcap::mcap
|
|
||||||
msft_proxy4::proxy
|
msft_proxy4::proxy
|
||||||
cvmmap_streamer_protobuf)
|
cvmmap_streamer_protobuf)
|
||||||
if (TARGET spdlog::spdlog)
|
if (TARGET spdlog::spdlog)
|
||||||
@@ -226,6 +370,43 @@ if (TARGET PkgConfig::PROTOBUF_PKG)
|
|||||||
target_link_libraries(cvmmap_streamer_record_support PUBLIC PkgConfig::PROTOBUF_PKG)
|
target_link_libraries(cvmmap_streamer_record_support PUBLIC PkgConfig::PROTOBUF_PKG)
|
||||||
endif()
|
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
|
add_library(cvmmap_streamer_common STATIC
|
||||||
src/ipc/help.cpp
|
src/ipc/help.cpp
|
||||||
src/config/runtime_config.cpp
|
src/config/runtime_config.cpp
|
||||||
@@ -249,13 +430,14 @@ set(CVMMAP_STREAMER_LINK_DEPS
|
|||||||
Threads::Threads
|
Threads::Threads
|
||||||
cvmmap_streamer_record_support
|
cvmmap_streamer_record_support
|
||||||
PkgConfig::FFMPEG
|
PkgConfig::FFMPEG
|
||||||
PkgConfig::ZSTD
|
|
||||||
PkgConfig::LZ4
|
|
||||||
cvmmap::client
|
cvmmap::client
|
||||||
cvmmap::nats
|
cvmmap::nats
|
||||||
CLI11::CLI11
|
CLI11::CLI11
|
||||||
tomlplusplus::tomlplusplus
|
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)
|
if (TARGET cppzmq::cppzmq)
|
||||||
list(APPEND CVMMAP_STREAMER_LINK_DEPS 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})
|
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)
|
function(add_cvmmap_binary target source)
|
||||||
add_executable(${target} ${source} ${ARGN})
|
add_executable(${target} ${source} ${ARGN})
|
||||||
target_include_directories(${target}
|
target_include_directories(${target}
|
||||||
@@ -295,6 +484,7 @@ function(add_cvmmap_binary target source)
|
|||||||
set_target_properties(${target} PROPERTIES
|
set_target_properties(${target} PROPERTIES
|
||||||
OUTPUT_NAME "${target}"
|
OUTPUT_NAME "${target}"
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin")
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin")
|
||||||
|
cvmmap_streamer_apply_build_rpath(${target})
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
add_cvmmap_binary(cvmmap_streamer src/main_streamer.cpp)
|
add_cvmmap_binary(cvmmap_streamer src/main_streamer.cpp)
|
||||||
@@ -303,11 +493,10 @@ 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_stub_tester src/testers/rtmp_stub_tester.cpp)
|
||||||
add_cvmmap_binary(rtmp_output_tester src/testers/rtmp_output_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(ipc_snapshot_tester src/testers/ipc_snapshot_tester.cpp)
|
||||||
add_cvmmap_binary(mcap_depth_record_tester src/testers/mcap_depth_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_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_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_pose_record_tester src/testers/mcap_pose_record_tester.cpp)
|
||||||
add_cvmmap_binary(mcap_multi_record_tester src/testers/mcap_multi_record_tester.cpp)
|
|
||||||
|
|
||||||
add_executable(mcap_reader_tester src/testers/mcap_reader_tester.cpp)
|
add_executable(mcap_reader_tester src/testers/mcap_reader_tester.cpp)
|
||||||
target_include_directories(mcap_reader_tester
|
target_include_directories(mcap_reader_tester
|
||||||
@@ -317,24 +506,23 @@ target_include_directories(mcap_reader_tester
|
|||||||
target_link_libraries(mcap_reader_tester
|
target_link_libraries(mcap_reader_tester
|
||||||
PRIVATE
|
PRIVATE
|
||||||
CLI11::CLI11
|
CLI11::CLI11
|
||||||
|
cvmmap_streamer_feature_flags
|
||||||
cvmmap_streamer_foxglove_proto
|
cvmmap_streamer_foxglove_proto
|
||||||
cvmmap_streamer_depth_proto
|
cvmmap_streamer_depth_proto
|
||||||
cvmmap_streamer_mcap_runtime
|
cvmmap_streamer_mcap_runtime
|
||||||
mcap::mcap
|
cvmmap_streamer_protobuf)
|
||||||
PkgConfig::ZSTD
|
|
||||||
PkgConfig::LZ4)
|
|
||||||
if (TARGET spdlog::spdlog)
|
if (TARGET spdlog::spdlog)
|
||||||
target_link_libraries(mcap_reader_tester PRIVATE spdlog::spdlog)
|
target_link_libraries(mcap_reader_tester PRIVATE spdlog::spdlog)
|
||||||
elseif (TARGET spdlog)
|
elseif (TARGET spdlog)
|
||||||
target_link_libraries(mcap_reader_tester PRIVATE spdlog)
|
target_link_libraries(mcap_reader_tester PRIVATE spdlog)
|
||||||
endif()
|
endif()
|
||||||
target_link_libraries(mcap_reader_tester PRIVATE cvmmap_streamer_protobuf)
|
|
||||||
if (TARGET PkgConfig::PROTOBUF_PKG)
|
if (TARGET PkgConfig::PROTOBUF_PKG)
|
||||||
target_link_libraries(mcap_reader_tester PRIVATE PkgConfig::PROTOBUF_PKG)
|
target_link_libraries(mcap_reader_tester PRIVATE PkgConfig::PROTOBUF_PKG)
|
||||||
endif()
|
endif()
|
||||||
set_target_properties(mcap_reader_tester PROPERTIES
|
set_target_properties(mcap_reader_tester PROPERTIES
|
||||||
OUTPUT_NAME "mcap_reader_tester"
|
OUTPUT_NAME "mcap_reader_tester"
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin")
|
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)
|
add_executable(mcap_replay_tester src/testers/mcap_replay_tester.cpp)
|
||||||
target_include_directories(mcap_replay_tester
|
target_include_directories(mcap_replay_tester
|
||||||
@@ -345,23 +533,28 @@ target_link_libraries(mcap_replay_tester
|
|||||||
PRIVATE
|
PRIVATE
|
||||||
Threads::Threads
|
Threads::Threads
|
||||||
CLI11::CLI11
|
CLI11::CLI11
|
||||||
|
cvmmap_streamer_feature_flags
|
||||||
cvmmap_streamer_foxglove_proto
|
cvmmap_streamer_foxglove_proto
|
||||||
cvmmap_streamer_mcap_runtime
|
cvmmap_streamer_mcap_runtime
|
||||||
mcap::mcap
|
cvmmap_streamer_protobuf)
|
||||||
PkgConfig::ZSTD
|
|
||||||
PkgConfig::LZ4)
|
|
||||||
if (TARGET spdlog::spdlog)
|
if (TARGET spdlog::spdlog)
|
||||||
target_link_libraries(mcap_replay_tester PRIVATE spdlog::spdlog)
|
target_link_libraries(mcap_replay_tester PRIVATE spdlog::spdlog)
|
||||||
elseif (TARGET spdlog)
|
elseif (TARGET spdlog)
|
||||||
target_link_libraries(mcap_replay_tester PRIVATE spdlog)
|
target_link_libraries(mcap_replay_tester PRIVATE spdlog)
|
||||||
endif()
|
endif()
|
||||||
target_link_libraries(mcap_replay_tester PRIVATE cvmmap_streamer_protobuf)
|
|
||||||
if (TARGET PkgConfig::PROTOBUF_PKG)
|
if (TARGET PkgConfig::PROTOBUF_PKG)
|
||||||
target_link_libraries(mcap_replay_tester PRIVATE PkgConfig::PROTOBUF_PKG)
|
target_link_libraries(mcap_replay_tester PRIVATE PkgConfig::PROTOBUF_PKG)
|
||||||
endif()
|
endif()
|
||||||
set_target_properties(mcap_replay_tester PROPERTIES
|
set_target_properties(mcap_replay_tester PROPERTIES
|
||||||
OUTPUT_NAME "mcap_replay_tester"
|
OUTPUT_NAME "mcap_replay_tester"
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin")
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin")
|
||||||
|
cvmmap_streamer_apply_build_rpath(mcap_replay_tester)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
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(CVMMAP_STREAMER_INSTALL_TARGETS cvmmap_streamer)
|
set(CVMMAP_STREAMER_INSTALL_TARGETS cvmmap_streamer)
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,15 @@
|
|||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#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 {
|
namespace cvmmap_streamer {
|
||||||
|
|
||||||
enum class CodecType {
|
enum class CodecType {
|
||||||
@@ -89,6 +98,7 @@ struct OutputsConfig {
|
|||||||
|
|
||||||
struct McapRecordConfig {
|
struct McapRecordConfig {
|
||||||
bool enabled{false};
|
bool enabled{false};
|
||||||
|
bool depth_enabled{true};
|
||||||
std::string path{"capture.mcap"};
|
std::string path{"capture.mcap"};
|
||||||
std::string topic{"/camera/video"};
|
std::string topic{"/camera/video"};
|
||||||
std::string depth_topic{"/camera/depth"};
|
std::string depth_topic{"/camera/depth"};
|
||||||
|
|||||||
@@ -13,6 +13,15 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
|
#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 {
|
namespace cvmmap_streamer::record {
|
||||||
|
|
||||||
enum class DepthEncoding {
|
enum class DepthEncoding {
|
||||||
|
|||||||
Executable
+2
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
exec /usr/bin/protoc --experimental_allow_proto3_optional "$@"
|
||||||
+123
-29
@@ -269,10 +269,11 @@ std::optional<std::string> find_disallowed_boolean_assignment(int argc, char **a
|
|||||||
std::string_view negative;
|
std::string_view negative;
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr std::array<FlagPair, 6> kFlagPairs{{
|
constexpr std::array<FlagPair, 7> kFlagPairs{{
|
||||||
{"--rtmp", "--no-rtmp"},
|
{"--rtmp", "--no-rtmp"},
|
||||||
{"--rtp", "--no-rtp"},
|
{"--rtp", "--no-rtp"},
|
||||||
{"--mcap", "--no-mcap"},
|
{"--mcap", "--no-mcap"},
|
||||||
|
{"--mcap-depth", "--no-mcap-depth"},
|
||||||
{"--realtime-sync", "--no-realtime-sync"},
|
{"--realtime-sync", "--no-realtime-sync"},
|
||||||
{"--force-idr-on-reset", "--no-force-idr-on-reset"},
|
{"--force-idr-on-reset", "--no-force-idr-on-reset"},
|
||||||
{"--keep-stream-on-reset", "--no-keep-stream-on-reset"},
|
{"--keep-stream-on-reset", "--no-keep-stream-on-reset"},
|
||||||
@@ -292,6 +293,46 @@ std::optional<std::string> find_disallowed_boolean_assignment(int argc, char **a
|
|||||||
return std::nullopt;
|
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<void, std::string> 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 <typename T>
|
template <typename T>
|
||||||
std::optional<T> toml_value(const toml::table &table, std::string_view path) {
|
std::optional<T> toml_value(const toml::table &table, std::string_view path) {
|
||||||
auto node = table.at_path(path);
|
auto node = table.at_path(path);
|
||||||
@@ -462,6 +503,9 @@ std::expected<void, std::string> apply_toml_file(RuntimeConfig &config, const st
|
|||||||
if (auto value = toml_value<bool>(table, "record.mcap.enabled")) {
|
if (auto value = toml_value<bool>(table, "record.mcap.enabled")) {
|
||||||
config.record.mcap.enabled = *value;
|
config.record.mcap.enabled = *value;
|
||||||
}
|
}
|
||||||
|
if (auto value = toml_value<bool>(table, "record.mcap.depth_enabled")) {
|
||||||
|
config.record.mcap.depth_enabled = *value;
|
||||||
|
}
|
||||||
if (auto value = toml_value<std::string>(table, "record.mcap.path")) {
|
if (auto value = toml_value<std::string>(table, "record.mcap.path")) {
|
||||||
config.record.mcap.enabled = true;
|
config.record.mcap.enabled = true;
|
||||||
config.record.mcap.path = *value;
|
config.record.mcap.path = *value;
|
||||||
@@ -710,6 +754,7 @@ std::expected<RuntimeConfig, std::string> parse_runtime_config(int argc, char **
|
|||||||
std::optional<std::uint16_t> rtp_payload_type_override{};
|
std::optional<std::uint16_t> rtp_payload_type_override{};
|
||||||
std::optional<std::string> rtp_sdp_override{};
|
std::optional<std::string> rtp_sdp_override{};
|
||||||
std::optional<bool> mcap_enabled_override{};
|
std::optional<bool> mcap_enabled_override{};
|
||||||
|
std::optional<bool> mcap_depth_enabled_override{};
|
||||||
std::optional<std::string> mcap_path_override{};
|
std::optional<std::string> mcap_path_override{};
|
||||||
std::optional<std::string> mcap_topic_override{};
|
std::optional<std::string> mcap_topic_override{};
|
||||||
std::optional<std::string> mcap_depth_topic_override{};
|
std::optional<std::string> mcap_depth_topic_override{};
|
||||||
@@ -834,55 +879,89 @@ std::expected<RuntimeConfig, std::string> parse_runtime_config(int argc, char **
|
|||||||
->check(require_non_empty("--sdp"))
|
->check(require_non_empty("--sdp"))
|
||||||
->excludes(rtp_sdp);
|
->excludes(rtp_sdp);
|
||||||
|
|
||||||
app.add_flag("--mcap,!--no-mcap", mcap_enabled_override, "Enable or disable MCAP recording")
|
const std::string mcap_group = runtime_supports_mcap()
|
||||||
->group("MCAP Record")
|
? "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")
|
->default_str(defaults.record.mcap.enabled ? "true" : "false")
|
||||||
->disable_flag_override();
|
->disable_flag_override();
|
||||||
app.add_option("--mcap-path", mcap_path_override, "MCAP output file path")
|
app.add_flag(
|
||||||
->group("MCAP Record")
|
"--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")
|
->type_name("PATH")
|
||||||
->check(require_non_empty("--mcap-path"))
|
->check(require_non_empty("--mcap-path"))
|
||||||
->default_str(defaults.record.mcap.path);
|
->default_str(defaults.record.mcap.path);
|
||||||
app.add_option("--mcap-topic", mcap_topic_override, "Foxglove compressed video topic name")
|
app.add_option(
|
||||||
->group("MCAP Record")
|
"--mcap-topic",
|
||||||
|
mcap_topic_override,
|
||||||
|
describe_mcap_option("Foxglove compressed video topic name"))
|
||||||
|
->group(mcap_group)
|
||||||
->type_name("TOPIC")
|
->type_name("TOPIC")
|
||||||
->check(require_non_empty("--mcap-topic"))
|
->check(require_non_empty("--mcap-topic"))
|
||||||
->default_str(defaults.record.mcap.topic);
|
->default_str(defaults.record.mcap.topic);
|
||||||
app.add_option("--mcap-depth-topic", mcap_depth_topic_override, "Depth image topic name")
|
app.add_option(
|
||||||
->group("MCAP Record")
|
"--mcap-depth-topic",
|
||||||
|
mcap_depth_topic_override,
|
||||||
|
describe_mcap_depth_option("Depth image topic name"))
|
||||||
|
->group(mcap_depth_group)
|
||||||
->type_name("TOPIC")
|
->type_name("TOPIC")
|
||||||
->check(require_non_empty("--mcap-depth-topic"))
|
->check(require_non_empty("--mcap-depth-topic"))
|
||||||
->default_str(defaults.record.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")
|
app.add_option(
|
||||||
->group("MCAP Record")
|
"--mcap-calibration-topic",
|
||||||
|
mcap_calibration_topic_override,
|
||||||
|
describe_mcap_option("RGB camera calibration topic name"))
|
||||||
|
->group(mcap_group)
|
||||||
->type_name("TOPIC")
|
->type_name("TOPIC")
|
||||||
->check(require_non_empty("--mcap-calibration-topic"))
|
->check(require_non_empty("--mcap-calibration-topic"))
|
||||||
->default_str(defaults.record.mcap.calibration_topic);
|
->default_str(defaults.record.mcap.calibration_topic);
|
||||||
app.add_option(
|
app.add_option(
|
||||||
"--mcap-depth-calibration-topic",
|
"--mcap-depth-calibration-topic",
|
||||||
mcap_depth_calibration_topic_override,
|
mcap_depth_calibration_topic_override,
|
||||||
"Depth camera calibration topic name")
|
describe_mcap_depth_option("Depth camera calibration topic name"))
|
||||||
->group("MCAP Record")
|
->group(mcap_depth_group)
|
||||||
->type_name("TOPIC")
|
->type_name("TOPIC")
|
||||||
->check(require_non_empty("--mcap-depth-calibration-topic"))
|
->check(require_non_empty("--mcap-depth-calibration-topic"))
|
||||||
->default_str(defaults.record.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")
|
app.add_option("--mcap-pose-topic", mcap_pose_topic_override, describe_mcap_option("Pose topic name"))
|
||||||
->group("MCAP Record")
|
->group(mcap_group)
|
||||||
->type_name("TOPIC")
|
->type_name("TOPIC")
|
||||||
->check(require_non_empty("--mcap-pose-topic"))
|
->check(require_non_empty("--mcap-pose-topic"))
|
||||||
->default_str(defaults.record.mcap.pose_topic);
|
->default_str(defaults.record.mcap.pose_topic);
|
||||||
app.add_option("--mcap-body-topic", mcap_body_topic_override, "Body tracking topic name")
|
app.add_option(
|
||||||
->group("MCAP Record")
|
"--mcap-body-topic",
|
||||||
|
mcap_body_topic_override,
|
||||||
|
describe_mcap_option("Body tracking topic name"))
|
||||||
|
->group(mcap_group)
|
||||||
->type_name("TOPIC")
|
->type_name("TOPIC")
|
||||||
->check(require_non_empty("--mcap-body-topic"))
|
->check(require_non_empty("--mcap-body-topic"))
|
||||||
->default_str(defaults.record.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")
|
app.add_option(
|
||||||
->group("MCAP Record")
|
"--mcap-frame-id",
|
||||||
|
mcap_frame_id_override,
|
||||||
|
describe_mcap_option("Frame ID written into MCAP messages"))
|
||||||
|
->group(mcap_group)
|
||||||
->type_name("ID")
|
->type_name("ID")
|
||||||
->check(require_non_empty("--mcap-frame-id"))
|
->check(require_non_empty("--mcap-frame-id"))
|
||||||
->default_str(defaults.record.mcap.frame_id);
|
->default_str(defaults.record.mcap.frame_id);
|
||||||
app.add_option("--mcap-compression", mcap_compression_override, "MCAP chunk compression mode")
|
app.add_option(
|
||||||
->group("MCAP Record")
|
"--mcap-compression",
|
||||||
|
mcap_compression_override,
|
||||||
|
describe_mcap_option("MCAP chunk compression mode"))
|
||||||
|
->group(mcap_group)
|
||||||
->type_name("MODE")
|
->type_name("MODE")
|
||||||
->transform(canonicalize_option(canonicalize_mcap_compression))
|
->transform(canonicalize_option(canonicalize_mcap_compression))
|
||||||
->default_str(std::string(to_string(defaults.record.mcap.compression)));
|
->default_str(std::string(to_string(defaults.record.mcap.compression)));
|
||||||
@@ -1078,6 +1157,9 @@ std::expected<RuntimeConfig, std::string> parse_runtime_config(int argc, char **
|
|||||||
if (mcap_enabled_override) {
|
if (mcap_enabled_override) {
|
||||||
config.record.mcap.enabled = *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) {
|
if (queue_size_override) {
|
||||||
config.latency.queue_size = *queue_size_override;
|
config.latency.queue_size = *queue_size_override;
|
||||||
@@ -1157,22 +1239,19 @@ std::expected<void, std::string> validate_runtime_config(const RuntimeConfig &co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.record.mcap.enabled && config.record.mcap.path.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");
|
return std::unexpected("invalid MCAP config: enabled MCAP output requires path");
|
||||||
}
|
}
|
||||||
if (config.record.mcap.topic.empty()) {
|
if (config.record.mcap.topic.empty()) {
|
||||||
return std::unexpected("invalid MCAP config: topic must not be 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()) {
|
if (config.record.mcap.calibration_topic.empty()) {
|
||||||
return std::unexpected("invalid MCAP config: calibration_topic must not be 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()) {
|
if (config.record.mcap.pose_topic.empty()) {
|
||||||
return std::unexpected("invalid MCAP config: pose_topic must not be empty");
|
return std::unexpected("invalid MCAP config: pose_topic must not be empty");
|
||||||
}
|
}
|
||||||
@@ -1182,6 +1261,20 @@ std::expected<void, std::string> validate_runtime_config(const RuntimeConfig &co
|
|||||||
if (config.record.mcap.frame_id.empty()) {
|
if (config.record.mcap.frame_id.empty()) {
|
||||||
return std::unexpected("invalid MCAP config: frame_id must not be 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) {
|
if (config.latency.queue_size == 0) {
|
||||||
return std::unexpected("invalid latency config: queue_size must be >= 1");
|
return std::unexpected("invalid latency config: queue_size must be >= 1");
|
||||||
@@ -1213,6 +1306,7 @@ std::string summarize_runtime_config(const RuntimeConfig &config) {
|
|||||||
ss << ", rtp.endpoint=" << (config.outputs.rtp.endpoint ? *config.outputs.rtp.endpoint : "<unset>");
|
ss << ", rtp.endpoint=" << (config.outputs.rtp.endpoint ? *config.outputs.rtp.endpoint : "<unset>");
|
||||||
ss << ", rtp.payload_type=" << static_cast<unsigned>(config.outputs.rtp.payload_type);
|
ss << ", rtp.payload_type=" << static_cast<unsigned>(config.outputs.rtp.payload_type);
|
||||||
ss << ", mcap.enabled=" << (config.record.mcap.enabled ? "true" : "false");
|
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.path=" << config.record.mcap.path;
|
||||||
ss << ", mcap.topic=" << config.record.mcap.topic;
|
ss << ", mcap.topic=" << config.record.mcap.topic;
|
||||||
ss << ", mcap.depth_topic=" << config.record.mcap.depth_topic;
|
ss << ", mcap.depth_topic=" << config.record.mcap.depth_topic;
|
||||||
|
|||||||
@@ -218,11 +218,15 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
frame_->pict_type = frame.force_keyframe ? AV_PICTURE_TYPE_I : AV_PICTURE_TYPE_NONE;
|
frame_->pict_type = frame.force_keyframe ? AV_PICTURE_TYPE_I : AV_PICTURE_TYPE_NONE;
|
||||||
|
#if defined(AV_FRAME_FLAG_KEY)
|
||||||
if (frame.force_keyframe) {
|
if (frame.force_keyframe) {
|
||||||
frame_->flags |= AV_FRAME_FLAG_KEY;
|
frame_->flags |= AV_FRAME_FLAG_KEY;
|
||||||
} else {
|
} else {
|
||||||
frame_->flags &= ~AV_FRAME_FLAG_KEY;
|
frame_->flags &= ~AV_FRAME_FLAG_KEY;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
frame_->key_frame = frame.force_keyframe ? 1 : 0;
|
||||||
|
#endif
|
||||||
frame_->pts = static_cast<std::int64_t>(frame.source_timestamp_ns - *first_source_timestamp_ns_);
|
frame_->pts = static_cast<std::int64_t>(frame.source_timestamp_ns - *first_source_timestamp_ns_);
|
||||||
const auto send_result = avcodec_send_frame(context_, frame_);
|
const auto send_result = avcodec_send_frame(context_, frame_);
|
||||||
if (send_result < 0) {
|
if (send_result < 0) {
|
||||||
|
|||||||
+4
-2
@@ -9,7 +9,7 @@ namespace cvmmap_streamer {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr std::array<std::string_view, 34> kHelpLines{
|
constexpr std::array<std::string_view, 36> kHelpLines{
|
||||||
"Usage:",
|
"Usage:",
|
||||||
" --help, -h\tshow this message",
|
" --help, -h\tshow this message",
|
||||||
"",
|
"",
|
||||||
@@ -33,12 +33,14 @@ constexpr std::array<std::string_view, 34> kHelpLines{
|
|||||||
" --rtmp-transport <mode>\tlibavformat|ffmpeg_process",
|
" --rtmp-transport <mode>\tlibavformat|ffmpeg_process",
|
||||||
" --rtmp-ffmpeg <path>\tffmpeg binary for ffmpeg_process transport",
|
" --rtmp-ffmpeg <path>\tffmpeg binary for ffmpeg_process transport",
|
||||||
" --mcap\t\tenable MCAP recording",
|
" --mcap\t\tenable MCAP recording",
|
||||||
|
" --mcap-depth\t\tenable MCAP depth recording",
|
||||||
" --mcap-path <path>\tMCAP output file",
|
" --mcap-path <path>\tMCAP output file",
|
||||||
" --mcap-topic <topic>\tMCAP topic name",
|
" --mcap-topic <topic>\tMCAP topic name",
|
||||||
" --mcap-depth-topic <topic>\tMCAP depth topic name",
|
" --mcap-depth-topic <topic>\tMCAP depth topic name (implies --mcap)",
|
||||||
" --mcap-body-topic <topic>\tMCAP body topic name",
|
" --mcap-body-topic <topic>\tMCAP body topic name",
|
||||||
" --mcap-frame-id <id>\tFoxglove CompressedVideo frame_id",
|
" --mcap-frame-id <id>\tFoxglove CompressedVideo frame_id",
|
||||||
" --mcap-compression <mode>\tnone|lz4|zstd",
|
" --mcap-compression <mode>\tnone|lz4|zstd",
|
||||||
|
" record.mcap.depth_enabled\tTOML toggle for optional depth recording",
|
||||||
"",
|
"",
|
||||||
"Examples:",
|
"Examples:",
|
||||||
" cvmmap_streamer --help",
|
" cvmmap_streamer --help",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "cvmmap_streamer/config/runtime_config.hpp"
|
#include "cvmmap_streamer/config/runtime_config.hpp"
|
||||||
#include "cvmmap_streamer/core/frame_source.hpp"
|
#include "cvmmap_streamer/core/frame_source.hpp"
|
||||||
|
#include "cvmmap_streamer/core/status.hpp"
|
||||||
#include "cvmmap_streamer/encode/encoder_backend.hpp"
|
#include "cvmmap_streamer/encode/encoder_backend.hpp"
|
||||||
#include "cvmmap_streamer/ipc/contracts.hpp"
|
#include "cvmmap_streamer/ipc/contracts.hpp"
|
||||||
#include "cvmmap_streamer/metrics/latency_tracker.hpp"
|
#include "cvmmap_streamer/metrics/latency_tracker.hpp"
|
||||||
@@ -11,6 +12,14 @@
|
|||||||
#include "cvmmap_streamer/record/mp4_record_writer.hpp"
|
#include "cvmmap_streamer/record/mp4_record_writer.hpp"
|
||||||
#include "proto/cvmmap_streamer/recorder_control.pb.h"
|
#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 <cvmmap/client.hpp>
|
#include <cvmmap/client.hpp>
|
||||||
#include <cvmmap/nats_client.hpp>
|
#include <cvmmap/nats_client.hpp>
|
||||||
#include <cvmmap/parser.hpp>
|
#include <cvmmap/parser.hpp>
|
||||||
@@ -432,6 +441,32 @@ float stream_fps(const encode::EncodedStreamInfo &stream_info) {
|
|||||||
static_cast<float>(stream_info.frame_rate_den);
|
static_cast<float>(stream_info.frame_rate_den);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class Config>
|
||||||
|
[[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 {
|
struct McapRecorderState {
|
||||||
mutable std::mutex mutex{};
|
mutable std::mutex mutex{};
|
||||||
RuntimeConfig base_config{};
|
RuntimeConfig base_config{};
|
||||||
@@ -449,27 +484,6 @@ struct McapRecorderState {
|
|||||||
} status{};
|
} 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<ipc::FrameInfo> current_frame_info{};
|
|
||||||
std::optional<record::Mp4InputPixelFormat> current_input_pixel_format{};
|
|
||||||
float current_fps{30.0f};
|
|
||||||
std::optional<record::Mp4RecordWriter> writer{};
|
|
||||||
std::optional<std::uint64_t> first_frame_timestamp_ns{};
|
|
||||||
Mp4RecorderStatus status{};
|
|
||||||
};
|
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
protocol::RpcError make_recorder_rpc_error(
|
protocol::RpcError make_recorder_rpc_error(
|
||||||
const protocol::RpcErrorCode code,
|
const protocol::RpcErrorCode code,
|
||||||
@@ -502,49 +516,11 @@ RuntimeConfig make_mcap_record_config(
|
|||||||
return 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<record::Mp4InputPixelFormat, std::string> 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) {
|
void reset_mcap_status_after_stop(McapRecorderState::Status &status) {
|
||||||
status.is_recording = false;
|
status.is_recording = false;
|
||||||
status.active_path.clear();
|
status.active_path.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset_mp4_status_after_stop(Mp4RecorderStatus &status) {
|
|
||||||
status.is_recording = false;
|
|
||||||
status.active_path.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
recorder_pb::McapRecorderState to_proto_mcap_state(
|
recorder_pb::McapRecorderState to_proto_mcap_state(
|
||||||
const McapRecorderState::Status &status) {
|
const McapRecorderState::Status &status) {
|
||||||
@@ -653,6 +629,121 @@ std::expected<McapRecorderState::Status, protocol::RpcError> get_mcap_recording_
|
|||||||
recorder_state.status.can_record = true;
|
recorder_state.status.can_record = true;
|
||||||
return recorder_state.status;
|
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<McapRecorderState::Status, protocol::RpcError> start_mcap_recording(
|
||||||
|
McapRecorderState &,
|
||||||
|
const recorder_pb::McapStartRequest &) {
|
||||||
|
return std::unexpected(make_mcap_unsupported_rpc_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
std::expected<McapRecorderState::Status, protocol::RpcError> stop_mcap_recording(
|
||||||
|
McapRecorderState &,
|
||||||
|
const recorder_pb::McapStopRequest &) {
|
||||||
|
return std::unexpected(make_mcap_unsupported_rpc_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
std::expected<McapRecorderState::Status, protocol::RpcError> 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<ipc::FrameInfo> current_frame_info{};
|
||||||
|
std::optional<record::Mp4InputPixelFormat> current_input_pixel_format{};
|
||||||
|
float current_fps{30.0f};
|
||||||
|
std::optional<record::Mp4RecordWriter> writer{};
|
||||||
|
std::optional<std::uint64_t> 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<record::Mp4InputPixelFormat, std::string> 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(
|
void close_mp4_writer_with_error(
|
||||||
Mp4RecorderState &recorder_state,
|
Mp4RecorderState &recorder_state,
|
||||||
@@ -893,6 +984,7 @@ void write_mp4_frame(
|
|||||||
recorder_state->status.frames_encoded += 1;
|
recorder_state->status.frames_encoded += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if CVMMAP_STREAMER_HAS_MCAP
|
||||||
void update_mcap_stream_info(
|
void update_mcap_stream_info(
|
||||||
McapRecorderState &recorder_state,
|
McapRecorderState &recorder_state,
|
||||||
const encode::EncodedStreamInfo &stream_info) {
|
const encode::EncodedStreamInfo &stream_info) {
|
||||||
@@ -981,6 +1073,7 @@ Status write_mcap_depth_map(
|
|||||||
recorder_state->status.last_frame_ok = true;
|
recorder_state->status.last_frame_ok = true;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Status publish_access_units(
|
Status publish_access_units(
|
||||||
@@ -1126,10 +1219,12 @@ int run_pipeline(const RuntimeConfig &config) {
|
|||||||
|
|
||||||
std::optional<protocol::UdpRtpPublisher> rtp_publisher{};
|
std::optional<protocol::UdpRtpPublisher> rtp_publisher{};
|
||||||
std::optional<protocol::RtmpOutput> rtmp_output{};
|
std::optional<protocol::RtmpOutput> rtmp_output{};
|
||||||
|
#if CVMMAP_STREAMER_HAS_MCAP
|
||||||
McapRecorderState mcap_recorder{};
|
McapRecorderState mcap_recorder{};
|
||||||
Mp4RecorderState mp4_recorder{};
|
|
||||||
mcap_recorder.base_config = config;
|
mcap_recorder.base_config = config;
|
||||||
mcap_recorder.status.can_record = true;
|
mcap_recorder.status.can_record = true;
|
||||||
|
#endif
|
||||||
|
Mp4RecorderState mp4_recorder{};
|
||||||
mp4_recorder.base_config = config;
|
mp4_recorder.base_config = config;
|
||||||
cvmmap::NatsControlClient nats_client(
|
cvmmap::NatsControlClient nats_client(
|
||||||
input_endpoints->nats_target_key,
|
input_endpoints->nats_target_key,
|
||||||
@@ -1141,7 +1236,7 @@ int run_pipeline(const RuntimeConfig &config) {
|
|||||||
.ipc_prefix = input_endpoints->ipc_prefix,
|
.ipc_prefix = input_endpoints->ipc_prefix,
|
||||||
.base_name = input_endpoints->base_name,
|
.base_name = input_endpoints->base_name,
|
||||||
.nats_target_key = input_endpoints->nats_target_key,
|
.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::mutex nats_event_mutex{};
|
||||||
std::deque<std::vector<std::uint8_t>> pending_body_packets{};
|
std::deque<std::vector<std::uint8_t>> pending_body_packets{};
|
||||||
@@ -1200,6 +1295,7 @@ int run_pipeline(const RuntimeConfig &config) {
|
|||||||
}
|
}
|
||||||
return make_ok_mp4_response<recorder_pb::Mp4StatusResponse>(*status);
|
return make_ok_mp4_response<recorder_pb::Mp4StatusResponse>(*status);
|
||||||
});
|
});
|
||||||
|
#if CVMMAP_STREAMER_HAS_MCAP
|
||||||
recorder_rpc_server.register_proto_endpoint<
|
recorder_rpc_server.register_proto_endpoint<
|
||||||
recorder_pb::McapStartRequest,
|
recorder_pb::McapStartRequest,
|
||||||
recorder_pb::McapStartResponse>(
|
recorder_pb::McapStartResponse>(
|
||||||
@@ -1239,6 +1335,7 @@ int run_pipeline(const RuntimeConfig &config) {
|
|||||||
}
|
}
|
||||||
return make_ok_mcap_response<recorder_pb::McapStatusResponse>(*status);
|
return make_ok_mcap_response<recorder_pb::McapStatusResponse>(*status);
|
||||||
});
|
});
|
||||||
|
#endif
|
||||||
if (!recorder_rpc_server.start()) {
|
if (!recorder_rpc_server.start()) {
|
||||||
spdlog::error("pipeline streamer recorder service failed on '{}'", config.input.nats_url);
|
spdlog::error("pipeline streamer recorder service failed on '{}'", config.input.nats_url);
|
||||||
nats_client.Stop();
|
nats_client.Stop();
|
||||||
@@ -1348,8 +1445,8 @@ int run_pipeline(const RuntimeConfig &config) {
|
|||||||
live_output_continuity.note_new_session(stream_info);
|
live_output_continuity.note_new_session(stream_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if CVMMAP_STREAMER_HAS_MCAP
|
||||||
update_mcap_stream_info(mcap_recorder, stream_info);
|
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) {
|
if (config.record.mcap.enabled) {
|
||||||
std::lock_guard lock(mcap_recorder.mutex);
|
std::lock_guard lock(mcap_recorder.mutex);
|
||||||
if (!mcap_recorder.sink) {
|
if (!mcap_recorder.sink) {
|
||||||
@@ -1367,6 +1464,12 @@ int run_pipeline(const RuntimeConfig &config) {
|
|||||||
mcap_recorder.status.active_path = config.record.mcap.path;
|
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;
|
started = true;
|
||||||
restart_pending = false;
|
restart_pending = false;
|
||||||
restart_target_info.reset();
|
restart_target_info.reset();
|
||||||
@@ -1481,6 +1584,7 @@ int run_pipeline(const RuntimeConfig &config) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if CVMMAP_STREAMER_HAS_MCAP
|
||||||
auto write_body = write_mcap_body_message(&mcap_recorder, record::RawBodyTrackingMessageView{
|
auto write_body = write_mcap_body_message(&mcap_recorder, record::RawBodyTrackingMessageView{
|
||||||
.timestamp_ns = body_tracking_timestamp_ns(*parsed_body),
|
.timestamp_ns = body_tracking_timestamp_ns(*parsed_body),
|
||||||
.bytes = body_bytes,
|
.bytes = body_bytes,
|
||||||
@@ -1490,6 +1594,7 @@ int run_pipeline(const RuntimeConfig &config) {
|
|||||||
restart_backend(reason, active_info);
|
restart_backend(reason, active_info);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if (backend && !using_encoded_input) {
|
if (backend && !using_encoded_input) {
|
||||||
@@ -1532,7 +1637,11 @@ int run_pipeline(const RuntimeConfig &config) {
|
|||||||
stats,
|
stats,
|
||||||
rtp_publisher ? &*rtp_publisher : nullptr,
|
rtp_publisher ? &*rtp_publisher : nullptr,
|
||||||
rtmp_output ? &*rtmp_output : nullptr,
|
rtmp_output ? &*rtmp_output : nullptr,
|
||||||
|
#if CVMMAP_STREAMER_HAS_MCAP
|
||||||
&mcap_recorder,
|
&mcap_recorder,
|
||||||
|
#else
|
||||||
|
nullptr,
|
||||||
|
#endif
|
||||||
keep_live_outputs_on_reset ? &live_output_continuity : nullptr,
|
keep_live_outputs_on_reset ? &live_output_continuity : nullptr,
|
||||||
latency_tracker);
|
latency_tracker);
|
||||||
if (!drain) {
|
if (!drain) {
|
||||||
@@ -1656,7 +1765,11 @@ int run_pipeline(const RuntimeConfig &config) {
|
|||||||
stats,
|
stats,
|
||||||
rtp_publisher ? &*rtp_publisher : nullptr,
|
rtp_publisher ? &*rtp_publisher : nullptr,
|
||||||
rtmp_output ? &*rtmp_output : nullptr,
|
rtmp_output ? &*rtmp_output : nullptr,
|
||||||
|
#if CVMMAP_STREAMER_HAS_MCAP
|
||||||
&mcap_recorder,
|
&mcap_recorder,
|
||||||
|
#else
|
||||||
|
nullptr,
|
||||||
|
#endif
|
||||||
keep_live_outputs_on_reset ? &live_output_continuity : nullptr,
|
keep_live_outputs_on_reset ? &live_output_continuity : nullptr,
|
||||||
latency_tracker);
|
latency_tracker);
|
||||||
if (!publish) {
|
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 (snapshot->depth_unit == ipc::DepthUnit::Unknown) {
|
||||||
if (!warned_unknown_depth_unit) {
|
if (!warned_unknown_depth_unit) {
|
||||||
spdlog::warn(
|
spdlog::warn(
|
||||||
@@ -1700,7 +1819,9 @@ int run_pipeline(const RuntimeConfig &config) {
|
|||||||
restart_backend(reason, active_info);
|
restart_backend(reason, active_info);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
stats.pushed_frames += 1;
|
stats.pushed_frames += 1;
|
||||||
if (!want_encoded_input) {
|
if (!want_encoded_input) {
|
||||||
@@ -1711,7 +1832,11 @@ int run_pipeline(const RuntimeConfig &config) {
|
|||||||
stats,
|
stats,
|
||||||
rtp_publisher ? &*rtp_publisher : nullptr,
|
rtp_publisher ? &*rtp_publisher : nullptr,
|
||||||
rtmp_output ? &*rtmp_output : nullptr,
|
rtmp_output ? &*rtmp_output : nullptr,
|
||||||
|
#if CVMMAP_STREAMER_HAS_MCAP
|
||||||
&mcap_recorder,
|
&mcap_recorder,
|
||||||
|
#else
|
||||||
|
nullptr,
|
||||||
|
#endif
|
||||||
keep_live_outputs_on_reset ? &live_output_continuity : nullptr,
|
keep_live_outputs_on_reset ? &live_output_continuity : nullptr,
|
||||||
latency_tracker);
|
latency_tracker);
|
||||||
if (!drain) {
|
if (!drain) {
|
||||||
@@ -1741,7 +1866,11 @@ int run_pipeline(const RuntimeConfig &config) {
|
|||||||
stats,
|
stats,
|
||||||
rtp_publisher ? &*rtp_publisher : nullptr,
|
rtp_publisher ? &*rtp_publisher : nullptr,
|
||||||
rtmp_output ? &*rtmp_output : nullptr,
|
rtmp_output ? &*rtmp_output : nullptr,
|
||||||
|
#if CVMMAP_STREAMER_HAS_MCAP
|
||||||
&mcap_recorder,
|
&mcap_recorder,
|
||||||
|
#else
|
||||||
|
nullptr,
|
||||||
|
#endif
|
||||||
keep_live_outputs_on_reset ? &live_output_continuity : nullptr,
|
keep_live_outputs_on_reset ? &live_output_continuity : nullptr,
|
||||||
latency_tracker);
|
latency_tracker);
|
||||||
if (!drain) {
|
if (!drain) {
|
||||||
@@ -1757,10 +1886,12 @@ int run_pipeline(const RuntimeConfig &config) {
|
|||||||
if (!stop_mp4) {
|
if (!stop_mp4) {
|
||||||
spdlog::warn("pipeline MP4 recorder stop during shutdown failed: {}", stop_mp4.error().message);
|
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{});
|
auto stop_mcap = stop_mcap_recording(mcap_recorder, recorder_pb::McapStopRequest{});
|
||||||
if (!stop_mcap) {
|
if (!stop_mcap) {
|
||||||
spdlog::warn("pipeline MCAP recorder stop during shutdown failed: {}", stop_mcap.error().message);
|
spdlog::warn("pipeline MCAP recorder stop during shutdown failed: {}", stop_mcap.error().message);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
recorder_rpc_server.stop();
|
recorder_rpc_server.stop();
|
||||||
|
|
||||||
spdlog::info(
|
spdlog::info(
|
||||||
|
|||||||
+183
-20
@@ -1,16 +1,28 @@
|
|||||||
#include <mcap/writer.hpp>
|
|
||||||
|
|
||||||
#include "cvmmap_streamer/record/mcap_record_sink.hpp"
|
#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 <mcap/writer.hpp>
|
||||||
|
|
||||||
#include "protobuf_descriptor.hpp"
|
#include "protobuf_descriptor.hpp"
|
||||||
#include "proto/cvmmap_streamer/BundleManifest.pb.h"
|
#include "proto/cvmmap_streamer/BundleManifest.pb.h"
|
||||||
#include "proto/cvmmap_streamer/DepthMap.pb.h"
|
#include "proto/cvmmap_streamer/DepthMap.pb.h"
|
||||||
#include "proto/foxglove/CameraCalibration.pb.h"
|
#include "proto/foxglove/CameraCalibration.pb.h"
|
||||||
#include "proto/foxglove/CompressedVideo.pb.h"
|
#include "proto/foxglove/CompressedVideo.pb.h"
|
||||||
#include "proto/foxglove/PoseInFrame.pb.h"
|
#include "proto/foxglove/PoseInFrame.pb.h"
|
||||||
|
#if CVMMAP_STREAMER_HAS_MCAP_DEPTH
|
||||||
#include <rvl/rvl.hpp>
|
#include <rvl/rvl.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <google/protobuf/timestamp.pb.h>
|
#include <google/protobuf/timestamp.pb.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
@@ -26,10 +38,23 @@
|
|||||||
|
|
||||||
namespace cvmmap_streamer::record {
|
namespace cvmmap_streamer::record {
|
||||||
|
|
||||||
|
template <class Config>
|
||||||
|
[[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 {
|
namespace {
|
||||||
|
|
||||||
|
#if CVMMAP_STREAMER_HAS_MCAP_DEPTH
|
||||||
constexpr float kRvlDepthQuantization = 200.0f;
|
constexpr float kRvlDepthQuantization = 200.0f;
|
||||||
constexpr float kMinDepthMaxMeters = 20.0f;
|
constexpr float kMinDepthMaxMeters = 20.0f;
|
||||||
|
#endif
|
||||||
constexpr std::string_view kBodyTrackingMessageEncoding = "cvmmap.body_tracking.v1";
|
constexpr std::string_view kBodyTrackingMessageEncoding = "cvmmap.body_tracking.v1";
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
@@ -268,6 +293,7 @@ std::expected<std::vector<std::uint8_t>, std::string> decoder_config_to_annexb(
|
|||||||
return avcc_to_annexb(decoder_config);
|
return avcc_to_annexb(decoder_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if CVMMAP_STREAMER_HAS_MCAP_DEPTH
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
bool can_encode_lossless_u16_mm(const RawDepthMapView &depth_map) {
|
bool can_encode_lossless_u16_mm(const RawDepthMapView &depth_map) {
|
||||||
if (normalize_depth_source_unit(depth_map.source_unit) != ipc::DepthUnit::Millimeter) {
|
if (normalize_depth_source_unit(depth_map.source_unit) != ipc::DepthUnit::Millimeter) {
|
||||||
@@ -367,6 +393,7 @@ std::expected<EncodedDepthPayload, std::string> encode_depth_payload(const RawDe
|
|||||||
return std::unexpected(std::string("failed to RVL-encode depth map: ") + error.what());
|
return std::unexpected(std::string("failed to RVL-encode depth map: ") + error.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
std::expected<void, std::string> append_repeated_double(
|
std::expected<void, std::string> append_repeated_double(
|
||||||
@@ -619,6 +646,8 @@ std::expected<McapRecordSink, std::string> McapRecordSink::create(
|
|||||||
state->writer.addChannel(channel);
|
state->writer.addChannel(channel);
|
||||||
state->video_channel_id = channel.id;
|
state->video_channel_id = channel.id;
|
||||||
|
|
||||||
|
#if CVMMAP_STREAMER_HAS_MCAP_DEPTH
|
||||||
|
if (mcap_depth_requested(config)) {
|
||||||
const auto depth_descriptor_set = build_file_descriptor_set(cvmmap_streamer::DepthMap::descriptor());
|
const auto depth_descriptor_set = build_file_descriptor_set(cvmmap_streamer::DepthMap::descriptor());
|
||||||
std::string depth_schema_bytes{};
|
std::string depth_schema_bytes{};
|
||||||
if (!depth_descriptor_set.SerializeToString(&depth_schema_bytes)) {
|
if (!depth_descriptor_set.SerializeToString(&depth_schema_bytes)) {
|
||||||
@@ -631,6 +660,12 @@ std::expected<McapRecordSink, std::string> McapRecordSink::create(
|
|||||||
mcap::Channel depth_channel(config.record.mcap.depth_topic, "protobuf", depth_schema.id);
|
mcap::Channel depth_channel(config.record.mcap.depth_topic, "protobuf", depth_schema.id);
|
||||||
state->writer.addChannel(depth_channel);
|
state->writer.addChannel(depth_channel);
|
||||||
state->depth_channel_id = depth_channel.id;
|
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());
|
const auto calibration_descriptor_set = build_file_descriptor_set(foxglove::CameraCalibration::descriptor());
|
||||||
std::string calibration_schema_bytes{};
|
std::string calibration_schema_bytes{};
|
||||||
@@ -661,6 +696,7 @@ std::expected<McapRecordSink, std::string> McapRecordSink::create(
|
|||||||
return sink;
|
return sink;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::expected<void, std::string> McapRecordSink::update_stream_info(const encode::EncodedStreamInfo &stream_info) {
|
std::expected<void, std::string> McapRecordSink::update_stream_info(const encode::EncodedStreamInfo &stream_info) {
|
||||||
if (state_ == nullptr) {
|
if (state_ == nullptr) {
|
||||||
return std::unexpected("MCAP sink is not open");
|
return std::unexpected("MCAP sink is not open");
|
||||||
@@ -717,8 +753,14 @@ std::expected<void, std::string> McapRecordSink::write_depth_map(const RawDepthM
|
|||||||
if (state_ == nullptr) {
|
if (state_ == nullptr) {
|
||||||
return std::unexpected("MCAP sink is not open");
|
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<void>(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);
|
const auto source_unit = normalize_depth_source_unit(depth_map.source_unit);
|
||||||
|
|
||||||
auto encoded = encode_depth_payload(depth_map);
|
auto encoded = encode_depth_payload(depth_map);
|
||||||
if (!encoded) {
|
if (!encoded) {
|
||||||
return std::unexpected(encoded.error());
|
return std::unexpected(encoded.error());
|
||||||
@@ -734,13 +776,20 @@ std::expected<void, std::string> McapRecordSink::write_depth_map(const RawDepthM
|
|||||||
state_->depth_channel_id,
|
state_->depth_channel_id,
|
||||||
state_->depth_sequence,
|
state_->depth_sequence,
|
||||||
*encoded);
|
*encoded);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::expected<void, std::string> McapRecordSink::write_depth_map_u16(const RawDepthMapU16View &depth_map) {
|
std::expected<void, std::string> McapRecordSink::write_depth_map_u16(const RawDepthMapU16View &depth_map) {
|
||||||
if (state_ == nullptr) {
|
if (state_ == nullptr) {
|
||||||
return std::unexpected("MCAP sink is not open");
|
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<void>(depth_map);
|
||||||
|
return std::unexpected("MCAP depth recording support is not compiled into this build");
|
||||||
|
#else
|
||||||
auto encoded = encode_depth_payload(depth_map);
|
auto encoded = encode_depth_payload(depth_map);
|
||||||
if (!encoded) {
|
if (!encoded) {
|
||||||
return std::unexpected(encoded.error());
|
return std::unexpected(encoded.error());
|
||||||
@@ -756,6 +805,7 @@ std::expected<void, std::string> McapRecordSink::write_depth_map_u16(const RawDe
|
|||||||
state_->depth_channel_id,
|
state_->depth_channel_id,
|
||||||
state_->depth_sequence,
|
state_->depth_sequence,
|
||||||
*encoded);
|
*encoded);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::expected<void, std::string> McapRecordSink::write_camera_calibration(const RawCameraCalibrationView &calibration) {
|
std::expected<void, std::string> McapRecordSink::write_camera_calibration(const RawCameraCalibrationView &calibration) {
|
||||||
@@ -777,6 +827,9 @@ std::expected<void, std::string> McapRecordSink::write_depth_camera_calibration(
|
|||||||
if (state_ == nullptr) {
|
if (state_ == nullptr) {
|
||||||
return std::unexpected("MCAP sink is not open");
|
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(
|
return write_calibration_message(
|
||||||
state_->writer,
|
state_->writer,
|
||||||
calibration.timestamp_ns,
|
calibration.timestamp_ns,
|
||||||
@@ -959,9 +1012,14 @@ std::expected<void, std::string> validate_new_stream_config(
|
|||||||
if (config.topic.empty()) {
|
if (config.topic.empty()) {
|
||||||
return std::unexpected("video topic is empty");
|
return std::unexpected("video topic is empty");
|
||||||
}
|
}
|
||||||
if (config.depth_topic.empty()) {
|
#if CVMMAP_STREAMER_HAS_MCAP_DEPTH
|
||||||
return std::unexpected("depth topic is empty");
|
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()) {
|
if (config.frame_id.empty()) {
|
||||||
return std::unexpected("frame_id is empty");
|
return std::unexpected("frame_id is empty");
|
||||||
}
|
}
|
||||||
@@ -984,9 +1042,11 @@ std::expected<void, std::string> validate_new_stream_config(
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::vector<std::string> config_topics{};
|
std::vector<std::string> config_topics{};
|
||||||
|
config_topics.push_back(config.topic);
|
||||||
|
if (depth_enabled) {
|
||||||
|
config_topics.push_back(config.depth_topic);
|
||||||
|
}
|
||||||
for (const auto *topic : {
|
for (const auto *topic : {
|
||||||
&config.topic,
|
|
||||||
&config.depth_topic,
|
|
||||||
&config.calibration_topic,
|
&config.calibration_topic,
|
||||||
&config.depth_calibration_topic,
|
&config.depth_calibration_topic,
|
||||||
&config.pose_topic,
|
&config.pose_topic,
|
||||||
@@ -1003,16 +1063,9 @@ std::expected<void, std::string> validate_new_stream_config(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto *topic : {
|
for (const auto &topic : config_topics) {
|
||||||
&config.topic,
|
if (topic_in_use(topic)) {
|
||||||
&config.depth_topic,
|
return std::unexpected("duplicate MCAP topic: " + 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
@@ -1063,6 +1116,7 @@ std::expected<MultiMcapRecordSink, std::string> MultiMcapRecordSink::create(
|
|||||||
state->writer.addSchema(video_schema);
|
state->writer.addSchema(video_schema);
|
||||||
state->video_schema_id = video_schema.id;
|
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());
|
const auto depth_descriptor_set = build_file_descriptor_set(cvmmap_streamer::DepthMap::descriptor());
|
||||||
std::string depth_schema_bytes{};
|
std::string depth_schema_bytes{};
|
||||||
if (!depth_descriptor_set.SerializeToString(&depth_schema_bytes)) {
|
if (!depth_descriptor_set.SerializeToString(&depth_schema_bytes)) {
|
||||||
@@ -1071,6 +1125,7 @@ std::expected<MultiMcapRecordSink, std::string> MultiMcapRecordSink::create(
|
|||||||
mcap::Schema depth_schema("cvmmap_streamer.DepthMap", "protobuf", depth_schema_bytes);
|
mcap::Schema depth_schema("cvmmap_streamer.DepthMap", "protobuf", depth_schema_bytes);
|
||||||
state->writer.addSchema(depth_schema);
|
state->writer.addSchema(depth_schema);
|
||||||
state->depth_schema_id = depth_schema.id;
|
state->depth_schema_id = depth_schema.id;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!state->bundle_topic.empty()) {
|
if (!state->bundle_topic.empty()) {
|
||||||
const auto bundle_descriptor_set = build_file_descriptor_set(cvmmap_streamer::BundleManifest::descriptor());
|
const auto bundle_descriptor_set = build_file_descriptor_set(cvmmap_streamer::BundleManifest::descriptor());
|
||||||
@@ -1126,9 +1181,13 @@ std::expected<MultiMcapRecordSink::StreamId, std::string> MultiMcapRecordSink::a
|
|||||||
state_->writer.addChannel(video_channel);
|
state_->writer.addChannel(video_channel);
|
||||||
stream.video_channel_id = video_channel.id;
|
stream.video_channel_id = video_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);
|
mcap::Channel depth_channel(config.depth_topic, "protobuf", state_->depth_schema_id);
|
||||||
state_->writer.addChannel(depth_channel);
|
state_->writer.addChannel(depth_channel);
|
||||||
stream.depth_channel_id = depth_channel.id;
|
stream.depth_channel_id = depth_channel.id;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (auto updated = update_stream_info_impl(stream, stream_info); !updated) {
|
if (auto updated = update_stream_info_impl(stream, stream_info); !updated) {
|
||||||
return std::unexpected(updated.error());
|
return std::unexpected(updated.error());
|
||||||
@@ -1200,7 +1259,13 @@ std::expected<void, std::string> MultiMcapRecordSink::write_depth_map(
|
|||||||
if (stream == nullptr) {
|
if (stream == nullptr) {
|
||||||
return std::unexpected(error);
|
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<void>(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);
|
const auto source_unit = normalize_depth_source_unit(depth_map.source_unit);
|
||||||
auto encoded = encode_depth_payload(depth_map);
|
auto encoded = encode_depth_payload(depth_map);
|
||||||
if (!encoded) {
|
if (!encoded) {
|
||||||
@@ -1217,6 +1282,7 @@ std::expected<void, std::string> MultiMcapRecordSink::write_depth_map(
|
|||||||
stream->depth_channel_id,
|
stream->depth_channel_id,
|
||||||
stream->depth_sequence,
|
stream->depth_sequence,
|
||||||
*encoded);
|
*encoded);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::expected<void, std::string> MultiMcapRecordSink::write_depth_map_u16(
|
std::expected<void, std::string> MultiMcapRecordSink::write_depth_map_u16(
|
||||||
@@ -1227,7 +1293,13 @@ std::expected<void, std::string> MultiMcapRecordSink::write_depth_map_u16(
|
|||||||
if (stream == nullptr) {
|
if (stream == nullptr) {
|
||||||
return std::unexpected(error);
|
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<void>(depth_map);
|
||||||
|
return std::unexpected("MCAP depth recording support is not compiled into this build");
|
||||||
|
#else
|
||||||
auto encoded = encode_depth_payload(depth_map);
|
auto encoded = encode_depth_payload(depth_map);
|
||||||
if (!encoded) {
|
if (!encoded) {
|
||||||
return std::unexpected(encoded.error());
|
return std::unexpected(encoded.error());
|
||||||
@@ -1243,6 +1315,7 @@ std::expected<void, std::string> MultiMcapRecordSink::write_depth_map_u16(
|
|||||||
stream->depth_channel_id,
|
stream->depth_channel_id,
|
||||||
stream->depth_sequence,
|
stream->depth_sequence,
|
||||||
*encoded);
|
*encoded);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::expected<void, std::string> MultiMcapRecordSink::write_camera_calibration(
|
std::expected<void, std::string> MultiMcapRecordSink::write_camera_calibration(
|
||||||
@@ -1272,6 +1345,9 @@ std::expected<void, std::string> MultiMcapRecordSink::write_depth_camera_calibra
|
|||||||
if (stream == nullptr) {
|
if (stream == nullptr) {
|
||||||
return std::unexpected(error);
|
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(
|
return write_calibration_message(
|
||||||
state_->writer,
|
state_->writer,
|
||||||
calibration.timestamp_ns,
|
calibration.timestamp_ns,
|
||||||
@@ -1407,4 +1483,91 @@ void MultiMcapRecordSink::close() {
|
|||||||
state_ = nullptr;
|
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, std::string> McapRecordSink::create(
|
||||||
|
const RuntimeConfig &,
|
||||||
|
const encode::EncodedStreamInfo &) {
|
||||||
|
return std::unexpected(mcap_unavailable_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string> McapRecordSink::update_stream_info(const encode::EncodedStreamInfo &) {
|
||||||
|
return std::unexpected(mcap_unavailable_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string> McapRecordSink::write_access_unit(const encode::EncodedAccessUnit &) {
|
||||||
|
return std::unexpected(mcap_unavailable_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string> McapRecordSink::write_depth_map(const RawDepthMapView &) {
|
||||||
|
return std::unexpected(mcap_depth_unavailable_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string> McapRecordSink::write_depth_map_u16(const RawDepthMapU16View &) {
|
||||||
|
return std::unexpected(mcap_depth_unavailable_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string> McapRecordSink::write_camera_calibration(const RawCameraCalibrationView &) {
|
||||||
|
return std::unexpected(mcap_unavailable_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string> McapRecordSink::write_depth_camera_calibration(const RawCameraCalibrationView &) {
|
||||||
|
return std::unexpected(mcap_depth_unavailable_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string> McapRecordSink::write_pose(const RawPoseView &) {
|
||||||
|
return std::unexpected(mcap_unavailable_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string> 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, std::string> MultiMcapRecordSink::create(
|
||||||
|
std::string,
|
||||||
|
McapCompression,
|
||||||
|
std::string) {
|
||||||
|
return std::unexpected(mcap_unavailable_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<MultiMcapRecordSink::StreamId, std::string> MultiMcapRecordSink::add_stream(
|
||||||
|
const McapRecordStreamConfig &,
|
||||||
|
const encode::EncodedStreamInfo &) {
|
||||||
|
return std::unexpected(const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiMcapRecordSink::close() {}
|
||||||
|
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ int main(int argc, char **argv) {
|
|||||||
config.record.mcap.enabled = true;
|
config.record.mcap.enabled = true;
|
||||||
config.record.mcap.path = output_path.string();
|
config.record.mcap.path = output_path.string();
|
||||||
config.record.mcap.topic = "/camera/video";
|
config.record.mcap.topic = "/camera/video";
|
||||||
config.record.mcap.depth_topic = "/camera/depth";
|
|
||||||
config.record.mcap.body_topic = std::string(kBodyTopic);
|
config.record.mcap.body_topic = std::string(kBodyTopic);
|
||||||
config.record.mcap.frame_id = "camera";
|
config.record.mcap.frame_id = "camera";
|
||||||
config.record.mcap.compression = cvmmap_streamer::McapCompression::None;
|
config.record.mcap.compression = cvmmap_streamer::McapCompression::None;
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
#include <mcap/reader.hpp>
|
#include <mcap/reader.hpp>
|
||||||
|
|
||||||
#include "proto/cvmmap_streamer/BundleManifest.pb.h"
|
#include "proto/cvmmap_streamer/BundleManifest.pb.h"
|
||||||
#include "proto/cvmmap_streamer/DepthMap.pb.h"
|
|
||||||
#include "cvmmap_streamer/common.h"
|
#include "cvmmap_streamer/common.h"
|
||||||
#include "cvmmap_streamer/record/mcap_record_sink.hpp"
|
#include "cvmmap_streamer/record/mcap_record_sink.hpp"
|
||||||
#include "proto/foxglove/CameraCalibration.pb.h"
|
#include "proto/foxglove/CameraCalibration.pb.h"
|
||||||
#include "proto/foxglove/CompressedVideo.pb.h"
|
#include "proto/foxglove/CompressedVideo.pb.h"
|
||||||
#include "proto/foxglove/PoseInFrame.pb.h"
|
#include "proto/foxglove/PoseInFrame.pb.h"
|
||||||
|
|
||||||
#include <rvl/rvl.hpp>
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -86,18 +84,14 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
auto zed1 = sink->add_stream(cvmmap_streamer::record::McapRecordStreamConfig{
|
auto zed1 = sink->add_stream(cvmmap_streamer::record::McapRecordStreamConfig{
|
||||||
.topic = "/zed1/video",
|
.topic = "/zed1/video",
|
||||||
.depth_topic = "/zed1/depth",
|
|
||||||
.calibration_topic = "/zed1/calibration",
|
.calibration_topic = "/zed1/calibration",
|
||||||
.depth_calibration_topic = "/zed1/depth_calibration",
|
|
||||||
.pose_topic = "/zed1/pose",
|
.pose_topic = "/zed1/pose",
|
||||||
.body_topic = "/zed1/body",
|
.body_topic = "/zed1/body",
|
||||||
.frame_id = "zed1",
|
.frame_id = "zed1",
|
||||||
}, stream_info);
|
}, stream_info);
|
||||||
auto zed2 = sink->add_stream(cvmmap_streamer::record::McapRecordStreamConfig{
|
auto zed2 = sink->add_stream(cvmmap_streamer::record::McapRecordStreamConfig{
|
||||||
.topic = "/zed2/video",
|
.topic = "/zed2/video",
|
||||||
.depth_topic = "/zed2/depth",
|
|
||||||
.calibration_topic = "/zed2/calibration",
|
.calibration_topic = "/zed2/calibration",
|
||||||
.depth_calibration_topic = "/zed2/depth_calibration",
|
|
||||||
.pose_topic = "/zed2/pose",
|
.pose_topic = "/zed2/pose",
|
||||||
.body_topic = "/zed2/body",
|
.body_topic = "/zed2/body",
|
||||||
.frame_id = "zed2",
|
.frame_id = "zed2",
|
||||||
@@ -107,10 +101,6 @@ int main(int argc, char **argv) {
|
|||||||
return exit_code(TesterExitCode::CreateError);
|
return exit_code(TesterExitCode::CreateError);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<std::uint16_t> depth_pixels{
|
|
||||||
1000, 2000,
|
|
||||||
1500, 2500,
|
|
||||||
};
|
|
||||||
const std::vector<double> distortion{0.0, 0.0, 0.0, 0.0, 0.0};
|
const std::vector<double> distortion{0.0, 0.0, 0.0, 0.0, 0.0};
|
||||||
const std::vector<double> intrinsic_matrix{
|
const std::vector<double> intrinsic_matrix{
|
||||||
500.0, 0.0, 320.0,
|
500.0, 0.0, 320.0,
|
||||||
@@ -168,15 +158,6 @@ int main(int argc, char **argv) {
|
|||||||
return exit_code(TesterExitCode::WriteError);
|
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{
|
if (auto write = sink->write_camera_calibration(stream_id, cvmmap_streamer::record::RawCameraCalibrationView{
|
||||||
.timestamp_ns = 100,
|
.timestamp_ns = 100,
|
||||||
@@ -191,19 +172,7 @@ int main(int argc, char **argv) {
|
|||||||
spdlog::error("failed to write calibration: {}", write.error());
|
spdlog::error("failed to write calibration: {}", write.error());
|
||||||
return exit_code(TesterExitCode::WriteError);
|
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{
|
if (auto write = sink->write_pose(stream_id, cvmmap_streamer::record::RawPoseView{
|
||||||
.timestamp_ns = 100,
|
.timestamp_ns = 100,
|
||||||
@@ -251,23 +220,6 @@ int main(int argc, char **argv) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (it->schema->name == "cvmmap_streamer.DepthMap") {
|
|
||||||
cvmmap_streamer::DepthMap depth{};
|
|
||||||
if (!depth.ParseFromArray(it->message.data, static_cast<int>(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<const std::uint8_t>(
|
|
||||||
reinterpret_cast<const std::uint8_t *>(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") {
|
if (it->schema->name == "cvmmap_streamer.BundleManifest") {
|
||||||
cvmmap_streamer::BundleManifest bundle{};
|
cvmmap_streamer::BundleManifest bundle{};
|
||||||
@@ -319,14 +271,10 @@ int main(int argc, char **argv) {
|
|||||||
for (const auto &topic : {
|
for (const auto &topic : {
|
||||||
"/bundle",
|
"/bundle",
|
||||||
"/zed1/video",
|
"/zed1/video",
|
||||||
"/zed1/depth",
|
|
||||||
"/zed1/calibration",
|
"/zed1/calibration",
|
||||||
"/zed1/depth_calibration",
|
|
||||||
"/zed1/pose",
|
"/zed1/pose",
|
||||||
"/zed2/video",
|
"/zed2/video",
|
||||||
"/zed2/depth",
|
|
||||||
"/zed2/calibration",
|
"/zed2/calibration",
|
||||||
"/zed2/depth_calibration",
|
|
||||||
"/zed2/pose",
|
"/zed2/pose",
|
||||||
}) {
|
}) {
|
||||||
if (topic_counts[topic] != 1) {
|
if (topic_counts[topic] != 1) {
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
#include <mcap/reader.hpp>
|
#include <mcap/reader.hpp>
|
||||||
|
|
||||||
#include "proto/cvmmap_streamer/DepthMap.pb.h"
|
|
||||||
#include "cvmmap_streamer/common.h"
|
#include "cvmmap_streamer/common.h"
|
||||||
#include "cvmmap_streamer/record/mcap_record_sink.hpp"
|
#include "cvmmap_streamer/record/mcap_record_sink.hpp"
|
||||||
#include "proto/foxglove/CameraCalibration.pb.h"
|
#include "proto/foxglove/CameraCalibration.pb.h"
|
||||||
#include "proto/foxglove/CompressedVideo.pb.h"
|
#include "proto/foxglove/CompressedVideo.pb.h"
|
||||||
#include "proto/foxglove/PoseInFrame.pb.h"
|
#include "proto/foxglove/PoseInFrame.pb.h"
|
||||||
|
|
||||||
#include <rvl/rvl.hpp>
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@@ -56,7 +54,6 @@ int main(int argc, char **argv) {
|
|||||||
config.record.mcap.enabled = true;
|
config.record.mcap.enabled = true;
|
||||||
config.record.mcap.path = output_path.string();
|
config.record.mcap.path = output_path.string();
|
||||||
config.record.mcap.topic = "/camera/video";
|
config.record.mcap.topic = "/camera/video";
|
||||||
config.record.mcap.depth_topic = "/camera/depth";
|
|
||||||
config.record.mcap.calibration_topic = "/camera/calibration";
|
config.record.mcap.calibration_topic = "/camera/calibration";
|
||||||
config.record.mcap.pose_topic = "/camera/pose";
|
config.record.mcap.pose_topic = "/camera/pose";
|
||||||
config.record.mcap.frame_id = "camera";
|
config.record.mcap.frame_id = "camera";
|
||||||
@@ -82,19 +79,6 @@ int main(int argc, char **argv) {
|
|||||||
return exit_code(TesterExitCode::WriteError);
|
return exit_code(TesterExitCode::WriteError);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<std::uint16_t> 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<double> distortion{0.0, 0.0, 0.0, 0.0, 0.0};
|
const std::vector<double> distortion{0.0, 0.0, 0.0, 0.0, 0.0};
|
||||||
const std::vector<double> intrinsic_matrix{
|
const std::vector<double> intrinsic_matrix{
|
||||||
@@ -146,7 +130,6 @@ int main(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::uint64_t video_messages{0};
|
std::uint64_t video_messages{0};
|
||||||
std::uint64_t depth_messages{0};
|
|
||||||
std::uint64_t calibration_messages{0};
|
std::uint64_t calibration_messages{0};
|
||||||
std::uint64_t pose_messages{0};
|
std::uint64_t pose_messages{0};
|
||||||
|
|
||||||
@@ -179,35 +162,6 @@ int main(int argc, char **argv) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (it->schema->name == "cvmmap_streamer.DepthMap") {
|
|
||||||
cvmmap_streamer::DepthMap depth{};
|
|
||||||
if (!depth.ParseFromArray(it->message.data, static_cast<int>(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<const std::uint8_t>(
|
|
||||||
reinterpret_cast<const std::uint8_t *>(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") {
|
if (it->schema->name == "foxglove.CameraCalibration") {
|
||||||
foxglove::CameraCalibration calibration{};
|
foxglove::CameraCalibration calibration{};
|
||||||
@@ -262,18 +216,17 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
reader.close();
|
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(
|
spdlog::error(
|
||||||
"unexpected message counts: video={} depth={} calibration={} pose={}",
|
"unexpected message counts: video={} calibration={} pose={}",
|
||||||
video_messages,
|
video_messages,
|
||||||
depth_messages,
|
|
||||||
calibration_messages,
|
calibration_messages,
|
||||||
pose_messages);
|
pose_messages);
|
||||||
return exit_code(TesterExitCode::VerificationError);
|
return exit_code(TesterExitCode::VerificationError);
|
||||||
}
|
}
|
||||||
|
|
||||||
spdlog::info(
|
spdlog::info(
|
||||||
"validated same-file MCAP video+depth+calibration+pose recording at '{}'",
|
"validated same-file MCAP video+calibration+pose recording at '{}'",
|
||||||
output_path.string());
|
output_path.string());
|
||||||
return exit_code(TesterExitCode::Success);
|
return exit_code(TesterExitCode::Success);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user