refactor(streamer): adopt proxy backends and typed statuses

This commit is contained in:
2026-03-10 23:29:59 +08:00
parent 6af97ee5d3
commit 0ad6887095
22 changed files with 1686 additions and 275 deletions
+73 -8
View File
@@ -123,6 +123,20 @@ std::expected<RtmpMode, std::string> parse_rtmp_mode(std::string_view raw) {
return std::unexpected("invalid rtmp mode: '" + std::string(raw) + "' (expected: enhanced|domestic)");
}
std::expected<RtmpTransportType, std::string> parse_rtmp_transport(std::string_view raw) {
if (raw == "libavformat") {
return RtmpTransportType::Libavformat;
}
if (raw == "ffmpeg_process" || raw == "ffmpeg-process") {
return RtmpTransportType::FfmpegProcess;
}
if (raw == "legacy_custom" || raw == "legacy-custom") {
return RtmpTransportType::LegacyCustom;
}
return std::unexpected(
"invalid rtmp transport: '" + std::string(raw) + "' (expected: libavformat|ffmpeg_process|legacy_custom)");
}
std::expected<EncoderBackendType, std::string> parse_encoder_backend(std::string_view raw) {
if (raw == "auto") {
return EncoderBackendType::Auto;
@@ -328,6 +342,16 @@ std::expected<void, std::string> apply_toml_file(RuntimeConfig &config, const st
config.outputs.rtmp.enabled = true;
config.outputs.rtmp.urls = std::move(*values);
}
if (auto value = toml_value<std::string>(table, "outputs.rtmp.transport")) {
auto parsed = parse_rtmp_transport(*value);
if (!parsed) {
return std::unexpected(parsed.error());
}
config.outputs.rtmp.transport = *parsed;
}
if (auto value = toml_value<std::string>(table, "outputs.rtmp.ffmpeg_path")) {
config.outputs.rtmp.ffmpeg_path = *value;
}
if (auto value = toml_value<std::string>(table, "outputs.rtmp.mode")) {
auto parsed = parse_rtmp_mode(*value);
if (!parsed) {
@@ -484,6 +508,18 @@ std::string_view to_string(RtmpMode mode) {
return "unknown";
}
std::string_view to_string(RtmpTransportType transport) {
switch (transport) {
case RtmpTransportType::Libavformat:
return "libavformat";
case RtmpTransportType::FfmpegProcess:
return "ffmpeg_process";
case RtmpTransportType::LegacyCustom:
return "legacy_custom";
}
return "unknown";
}
std::string_view to_string(EncoderBackendType backend) {
switch (backend) {
case EncoderBackendType::Auto:
@@ -530,6 +566,8 @@ std::expected<RuntimeConfig, std::string> parse_runtime_config(int argc, char **
std::string encoder_backend_raw{};
std::string encoder_device_raw{};
std::string rtmp_mode_raw{};
std::string rtmp_transport_raw{};
std::string rtmp_ffmpeg_path_raw{};
std::vector<std::string> rtmp_urls_raw{};
std::string rtp_endpoint_raw{};
std::string rtp_payload_type_raw{};
@@ -565,6 +603,8 @@ std::expected<RuntimeConfig, std::string> parse_runtime_config(int argc, char **
app.add_option("--encoder-device", encoder_device_raw);
app.add_flag("--rtmp", rtmp_enabled);
app.add_option("--rtmp-url", rtmp_urls_raw);
app.add_option("--rtmp-transport", rtmp_transport_raw);
app.add_option("--rtmp-ffmpeg", rtmp_ffmpeg_path_raw);
app.add_option("--rtmp-mode", rtmp_mode_raw);
app.add_flag("--rtp", rtp_enabled);
app.add_option("--rtp-endpoint", rtp_endpoint_raw);
@@ -642,6 +682,16 @@ std::expected<RuntimeConfig, std::string> parse_runtime_config(int argc, char **
config.outputs.rtmp.enabled = true;
config.outputs.rtmp.urls = std::move(rtmp_urls_raw);
}
if (!rtmp_transport_raw.empty()) {
auto parsed = parse_rtmp_transport(rtmp_transport_raw);
if (!parsed) {
return std::unexpected(parsed.error());
}
config.outputs.rtmp.transport = *parsed;
}
if (!rtmp_ffmpeg_path_raw.empty()) {
config.outputs.rtmp.ffmpeg_path = rtmp_ffmpeg_path_raw;
}
if (!rtmp_mode_raw.empty()) {
auto parsed = parse_rtmp_mode(rtmp_mode_raw);
if (!parsed) {
@@ -781,15 +831,29 @@ std::expected<void, std::string> validate_runtime_config(const RuntimeConfig &co
return std::unexpected("invalid RTMP config: URL must not be empty");
}
}
if (config.outputs.rtmp.mode == RtmpMode::Domestic && config.encoder.codec != CodecType::H265) {
return std::unexpected("invalid mode matrix: domestic RTMP mode requires codec h265");
}
if (config.encoder.backend == EncoderBackendType::FFmpeg && config.outputs.rtmp.enabled) {
return std::unexpected("invalid backend/output matrix: RTMP is only supported by gstreamer_legacy in this build");
}
if (config.encoder.backend == EncoderBackendType::GStreamerLegacy && config.record.mcap.enabled) {
return std::unexpected("invalid backend/output matrix: MCAP recording requires the ffmpeg encoded access-unit path");
}
if (config.outputs.rtmp.enabled) {
if (config.outputs.rtmp.transport == RtmpTransportType::LegacyCustom) {
if (config.outputs.rtmp.mode == RtmpMode::Domestic && config.encoder.codec != CodecType::H265) {
return std::unexpected("invalid mode matrix: domestic RTMP mode requires codec h265");
}
if (config.encoder.backend != EncoderBackendType::GStreamerLegacy) {
return std::unexpected("invalid backend/output matrix: legacy_custom RTMP requires encoder.backend=gstreamer_legacy");
}
} else {
if (config.outputs.rtmp.mode != RtmpMode::Enhanced) {
return std::unexpected("invalid RTMP config: non-legacy RTMP transports only support rtmp.mode=enhanced");
}
if (config.encoder.backend != EncoderBackendType::FFmpeg) {
return std::unexpected("invalid backend/output matrix: RTMP transports libavformat and ffmpeg_process require encoder.backend=ffmpeg");
}
if (config.outputs.rtmp.transport == RtmpTransportType::FfmpegProcess && config.outputs.rtmp.ffmpeg_path.empty()) {
return std::unexpected("invalid RTMP config: ffmpeg_process transport requires a non-empty ffmpeg path");
}
}
}
if (config.outputs.rtp.enabled) {
if (!config.outputs.rtp.endpoint || config.outputs.rtp.endpoint->empty()) {
@@ -831,8 +895,8 @@ std::expected<void, std::string> validate_runtime_config(const RuntimeConfig &co
if (config.encoder.backend == EncoderBackendType::GStreamerLegacy) {
return std::unexpected("invalid backend config: gstreamer_legacy backend requested but GStreamer support is not compiled");
}
if (config.outputs.rtmp.enabled) {
return std::unexpected("invalid output config: RTMP requires GStreamer legacy support, which is not compiled");
if (config.outputs.rtmp.enabled && config.outputs.rtmp.transport == RtmpTransportType::LegacyCustom) {
return std::unexpected("invalid output config: legacy_custom RTMP requires GStreamer support, which is not compiled");
}
#endif
@@ -849,6 +913,7 @@ std::string summarize_runtime_config(const RuntimeConfig &config) {
ss << ", encoder.gop=" << config.encoder.gop;
ss << ", encoder.b_frames=" << config.encoder.b_frames;
ss << ", rtmp.enabled=" << (config.outputs.rtmp.enabled ? "true" : "false");
ss << ", rtmp.transport=" << to_string(config.outputs.rtmp.transport);
ss << ", rtmp.mode=" << to_string(config.outputs.rtmp.mode);
ss << ", rtmp.urls=" << config.outputs.rtmp.urls.size();
ss << ", rtp.enabled=" << (config.outputs.rtp.enabled ? "true" : "false");