feat: add streamer-owned recording control service

Introduce a dedicated streamer-side recording control plane instead of sharing the producer recorder API.

- register streamer-owned recorder endpoints as a NATS micro service
- add explicit MP4 and MCAP recorder control protobufs and subject helpers
- wire recorder lifecycle handling into the pipeline runtime
- add MP4 writer and depth-alignment support files used by the new recording flow
This commit is contained in:
2026-04-12 20:21:33 +08:00
parent 4f016d9cef
commit 213adee887
11 changed files with 2474 additions and 400 deletions
@@ -0,0 +1,93 @@
#pragma once
#include "proto/cvmmap_streamer/recorder_control.pb.h"
#include <cstdint>
#include <expected>
#include <functional>
#include <memory>
#include <span>
#include <string>
namespace cvmmap_streamer::protocol {
enum class RpcErrorCode : std::uint8_t {
InvalidRequest,
Unsupported,
Busy,
Internal,
};
struct RpcError {
RpcErrorCode code{RpcErrorCode::Internal};
std::string message{};
};
struct NatsRequestReplyServerOptions {
std::string nats_url{};
std::string instance_name{};
std::string namespace_name{};
std::string ipc_prefix{};
std::string base_name{};
std::string nats_target_key{};
std::string backend{"cvmmap-streamer"};
std::string recording_formats{};
};
class NatsRequestReplyServer {
public:
explicit NatsRequestReplyServer(NatsRequestReplyServerOptions options);
~NatsRequestReplyServer();
NatsRequestReplyServer(const NatsRequestReplyServer &) = delete;
NatsRequestReplyServer &operator=(const NatsRequestReplyServer &) = delete;
template <class Request, class Response>
void register_proto_endpoint(
std::string endpoint_name,
std::string subject,
std::function<std::expected<Response, RpcError>(const Request &)> handler) {
register_raw_endpoint(
std::move(endpoint_name),
std::move(subject),
[handler = std::move(handler)](std::span<const std::uint8_t> payload) {
Request request;
Response response;
if (!request.ParseFromArray(
payload.data(),
static_cast<int>(payload.size()))) {
response.set_code(cvmmap_streamer::proto::RPC_CODE_INVALID_REQUEST);
response.set_error_message("failed to parse protobuf request");
return response.SerializeAsString();
}
auto handled = handler(request);
if (!handled) {
response.set_code(to_proto_rpc_code(handled.error().code));
response.set_error_message(handled.error().message);
return response.SerializeAsString();
}
return handled->SerializeAsString();
});
}
[[nodiscard]]
bool start();
void stop();
private:
struct Endpoint;
struct Impl;
void register_raw_endpoint(
std::string endpoint_name,
std::string subject,
std::function<std::string(std::span<const std::uint8_t>)> handler);
static cvmmap_streamer::proto::RpcCode to_proto_rpc_code(RpcErrorCode code);
std::unique_ptr<Impl> impl_;
};
} // namespace cvmmap_streamer::protocol
@@ -0,0 +1,36 @@
#pragma once
#include <string>
#include <string_view>
namespace cvmmap_streamer::protocol {
inline std::string streamer_subject_prefix(std::string_view target_key) {
return std::string("cvmmap.") + std::string(target_key) + ".streamer";
}
inline std::string subject_recorder_mp4_start(std::string_view target_key) {
return streamer_subject_prefix(target_key) + ".recorder.mp4.start";
}
inline std::string subject_recorder_mp4_stop(std::string_view target_key) {
return streamer_subject_prefix(target_key) + ".recorder.mp4.stop";
}
inline std::string subject_recorder_mp4_status(std::string_view target_key) {
return streamer_subject_prefix(target_key) + ".recorder.mp4.status";
}
inline std::string subject_recorder_mcap_start(std::string_view target_key) {
return streamer_subject_prefix(target_key) + ".recorder.mcap.start";
}
inline std::string subject_recorder_mcap_stop(std::string_view target_key) {
return streamer_subject_prefix(target_key) + ".recorder.mcap.stop";
}
inline std::string subject_recorder_mcap_status(std::string_view target_key) {
return streamer_subject_prefix(target_key) + ".recorder.mcap.status";
}
} // namespace cvmmap_streamer::protocol