Make MCAP and depth support optional
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user