Make MCAP and depth support optional

This commit is contained in:
2026-04-14 17:13:29 +08:00
parent ddea6b0e3d
commit 16a1a38645
12 changed files with 836 additions and 328 deletions
+194 -63
View File
@@ -1,5 +1,6 @@
#include "cvmmap_streamer/config/runtime_config.hpp"
#include "cvmmap_streamer/core/frame_source.hpp"
#include "cvmmap_streamer/core/status.hpp"
#include "cvmmap_streamer/encode/encoder_backend.hpp"
#include "cvmmap_streamer/ipc/contracts.hpp"
#include "cvmmap_streamer/metrics/latency_tracker.hpp"
@@ -11,6 +12,14 @@
#include "cvmmap_streamer/record/mp4_record_writer.hpp"
#include "proto/cvmmap_streamer/recorder_control.pb.h"
#ifndef CVMMAP_STREAMER_HAS_MCAP
#define CVMMAP_STREAMER_HAS_MCAP 0
#endif
#ifndef CVMMAP_STREAMER_HAS_MCAP_DEPTH
#define CVMMAP_STREAMER_HAS_MCAP_DEPTH 0
#endif
#include <cvmmap/client.hpp>
#include <cvmmap/nats_client.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);
}
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 {
mutable std::mutex mutex{};
RuntimeConfig base_config{};
@@ -449,27 +484,6 @@ struct McapRecorderState {
} status{};
};
struct Mp4RecorderStatus {
bool can_record{false};
bool is_recording{false};
bool last_frame_ok{false};
std::uint32_t frames_ingested{0};
std::uint32_t frames_encoded{0};
std::string active_path{};
std::string error_message{};
};
struct Mp4RecorderState {
mutable std::mutex mutex{};
RuntimeConfig base_config{};
std::optional<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]]
protocol::RpcError make_recorder_rpc_error(
const protocol::RpcErrorCode code,
@@ -502,49 +516,11 @@ RuntimeConfig make_mcap_record_config(
return record_config;
}
[[nodiscard]]
record::Mp4EncodeTuning make_mp4_encode_tuning(const RuntimeConfig &base_config) {
return record::Mp4EncodeTuning{
.quality = record::kDefaultMp4Quality,
.gop = base_config.encoder.gop,
.b_frames = base_config.encoder.b_frames,
};
}
[[nodiscard]]
std::expected<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) {
status.is_recording = false;
status.active_path.clear();
}
void reset_mp4_status_after_stop(Mp4RecorderStatus &status) {
status.is_recording = false;
status.active_path.clear();
}
[[nodiscard]]
recorder_pb::McapRecorderState to_proto_mcap_state(
const McapRecorderState::Status &status) {
@@ -653,6 +629,121 @@ std::expected<McapRecorderState::Status, protocol::RpcError> get_mcap_recording_
recorder_state.status.can_record = true;
return recorder_state.status;
}
#else
struct McapRecorderState {
struct Status {};
};
[[nodiscard]]
protocol::RpcError make_mcap_unsupported_rpc_error() {
return protocol::RpcError{
.code = protocol::RpcErrorCode::Unsupported,
.message = mcap_disabled_message(),
};
}
[[nodiscard]]
std::expected<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(
Mp4RecorderState &recorder_state,
@@ -893,6 +984,7 @@ void write_mp4_frame(
recorder_state->status.frames_encoded += 1;
}
#if CVMMAP_STREAMER_HAS_MCAP
void update_mcap_stream_info(
McapRecorderState &recorder_state,
const encode::EncodedStreamInfo &stream_info) {
@@ -981,6 +1073,7 @@ Status write_mcap_depth_map(
recorder_state->status.last_frame_ok = true;
return {};
}
#endif
[[nodiscard]]
Status publish_access_units(
@@ -1126,10 +1219,12 @@ int run_pipeline(const RuntimeConfig &config) {
std::optional<protocol::UdpRtpPublisher> rtp_publisher{};
std::optional<protocol::RtmpOutput> rtmp_output{};
#if CVMMAP_STREAMER_HAS_MCAP
McapRecorderState mcap_recorder{};
Mp4RecorderState mp4_recorder{};
mcap_recorder.base_config = config;
mcap_recorder.status.can_record = true;
#endif
Mp4RecorderState mp4_recorder{};
mp4_recorder.base_config = config;
cvmmap::NatsControlClient nats_client(
input_endpoints->nats_target_key,
@@ -1141,7 +1236,7 @@ int run_pipeline(const RuntimeConfig &config) {
.ipc_prefix = input_endpoints->ipc_prefix,
.base_name = input_endpoints->base_name,
.nats_target_key = input_endpoints->nats_target_key,
.recording_formats = "mp4,mcap",
.recording_formats = has_mcap_support() ? "mp4,mcap" : "mp4",
});
std::mutex nats_event_mutex{};
std::deque<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);
});
#if CVMMAP_STREAMER_HAS_MCAP
recorder_rpc_server.register_proto_endpoint<
recorder_pb::McapStartRequest,
recorder_pb::McapStartResponse>(
@@ -1239,6 +1335,7 @@ int run_pipeline(const RuntimeConfig &config) {
}
return make_ok_mcap_response<recorder_pb::McapStatusResponse>(*status);
});
#endif
if (!recorder_rpc_server.start()) {
spdlog::error("pipeline streamer recorder service failed on '{}'", config.input.nats_url);
nats_client.Stop();
@@ -1348,8 +1445,8 @@ int run_pipeline(const RuntimeConfig &config) {
live_output_continuity.note_new_session(stream_info);
}
#if CVMMAP_STREAMER_HAS_MCAP
update_mcap_stream_info(mcap_recorder, stream_info);
update_mp4_source_info(mp4_recorder, target_info, stream_fps(stream_info));
if (config.record.mcap.enabled) {
std::lock_guard lock(mcap_recorder.mutex);
if (!mcap_recorder.sink) {
@@ -1367,6 +1464,12 @@ int run_pipeline(const RuntimeConfig &config) {
mcap_recorder.status.active_path = config.record.mcap.path;
}
}
#else
if (config.record.mcap.enabled) {
return unexpected_error(ERR_UNSUPPORTED, mcap_disabled_message());
}
#endif
update_mp4_source_info(mp4_recorder, target_info, stream_fps(stream_info));
started = true;
restart_pending = false;
restart_target_info.reset();
@@ -1481,6 +1584,7 @@ int run_pipeline(const RuntimeConfig &config) {
continue;
}
#if CVMMAP_STREAMER_HAS_MCAP
auto write_body = write_mcap_body_message(&mcap_recorder, record::RawBodyTrackingMessageView{
.timestamp_ns = body_tracking_timestamp_ns(*parsed_body),
.bytes = body_bytes,
@@ -1490,6 +1594,7 @@ int run_pipeline(const RuntimeConfig &config) {
restart_backend(reason, active_info);
break;
}
#endif
}
if (backend && !using_encoded_input) {
@@ -1532,7 +1637,11 @@ int run_pipeline(const RuntimeConfig &config) {
stats,
rtp_publisher ? &*rtp_publisher : nullptr,
rtmp_output ? &*rtmp_output : nullptr,
#if CVMMAP_STREAMER_HAS_MCAP
&mcap_recorder,
#else
nullptr,
#endif
keep_live_outputs_on_reset ? &live_output_continuity : nullptr,
latency_tracker);
if (!drain) {
@@ -1656,7 +1765,11 @@ int run_pipeline(const RuntimeConfig &config) {
stats,
rtp_publisher ? &*rtp_publisher : nullptr,
rtmp_output ? &*rtmp_output : nullptr,
#if CVMMAP_STREAMER_HAS_MCAP
&mcap_recorder,
#else
nullptr,
#endif
keep_live_outputs_on_reset ? &live_output_continuity : nullptr,
latency_tracker);
if (!publish) {
@@ -1678,7 +1791,13 @@ int run_pipeline(const RuntimeConfig &config) {
}
}
if (!snapshot->depth.empty()) {
#if CVMMAP_STREAMER_HAS_MCAP
if (!snapshot->depth.empty() && config.record.mcap.enabled && runtime_mcap_depth_enabled(config)) {
#if !CVMMAP_STREAMER_HAS_MCAP_DEPTH
const auto reason = "pipeline depth MCAP write failed: " + mcap_depth_disabled_message();
restart_backend(reason, active_info);
continue;
#else
if (snapshot->depth_unit == ipc::DepthUnit::Unknown) {
if (!warned_unknown_depth_unit) {
spdlog::warn(
@@ -1700,7 +1819,9 @@ int run_pipeline(const RuntimeConfig &config) {
restart_backend(reason, active_info);
continue;
}
#endif
}
#endif
stats.pushed_frames += 1;
if (!want_encoded_input) {
@@ -1711,7 +1832,11 @@ int run_pipeline(const RuntimeConfig &config) {
stats,
rtp_publisher ? &*rtp_publisher : nullptr,
rtmp_output ? &*rtmp_output : nullptr,
#if CVMMAP_STREAMER_HAS_MCAP
&mcap_recorder,
#else
nullptr,
#endif
keep_live_outputs_on_reset ? &live_output_continuity : nullptr,
latency_tracker);
if (!drain) {
@@ -1741,7 +1866,11 @@ int run_pipeline(const RuntimeConfig &config) {
stats,
rtp_publisher ? &*rtp_publisher : nullptr,
rtmp_output ? &*rtmp_output : nullptr,
#if CVMMAP_STREAMER_HAS_MCAP
&mcap_recorder,
#else
nullptr,
#endif
keep_live_outputs_on_reset ? &live_output_continuity : nullptr,
latency_tracker);
if (!drain) {
@@ -1757,10 +1886,12 @@ int run_pipeline(const RuntimeConfig &config) {
if (!stop_mp4) {
spdlog::warn("pipeline MP4 recorder stop during shutdown failed: {}", stop_mp4.error().message);
}
#if CVMMAP_STREAMER_HAS_MCAP
auto stop_mcap = stop_mcap_recording(mcap_recorder, recorder_pb::McapStopRequest{});
if (!stop_mcap) {
spdlog::warn("pipeline MCAP recorder stop during shutdown failed: {}", stop_mcap.error().message);
}
#endif
recorder_rpc_server.stop();
spdlog::info(