refactor(streamer): remove gstreamer and legacy rtmp paths

This commit is contained in:
2026-03-11 16:43:29 +08:00
parent ed3f32ff6e
commit 782af9481c
22 changed files with 817 additions and 3339 deletions
+1 -8
View File
@@ -55,7 +55,7 @@ std::expected<Config, int> parse_args(int argc, char **argv) {
CLI::App app{"rtmp_output_tester - publish synthetic encoded video to RTMP using the configured sink"};
app.add_option("--rtmp-url", config.rtmp_url, "RTMP destination URL")->required();
app.add_option("--transport", config.transport, "RTMP transport backend (libavformat|ffmpeg_process)")
->check(CLI::IsMember({"libavformat", "ffmpeg_process", "legacy_custom"}));
->check(CLI::IsMember({"libavformat", "ffmpeg_process"}));
app.add_option("--codec", config.codec, "Video codec (h264|h265)")
->check(CLI::IsMember({"h264", "h265"}));
app.add_option("--encoder-device", config.encoder_device, "Encoder device (auto|nvidia|software)")
@@ -95,9 +95,6 @@ std::expected<cvmmap_streamer::RtmpTransportType, std::string> parse_transport(s
if (raw == "ffmpeg_process") {
return cvmmap_streamer::RtmpTransportType::FfmpegProcess;
}
if (raw == "legacy_custom") {
return cvmmap_streamer::RtmpTransportType::LegacyCustom;
}
return std::unexpected("unsupported transport");
}
@@ -163,10 +160,6 @@ int main(int argc, char **argv) {
config.outputs.rtmp.transport = *transport;
config.outputs.rtmp.ffmpeg_path = args->ffmpeg_path;
if (config.outputs.rtmp.transport == cvmmap_streamer::RtmpTransportType::LegacyCustom) {
config.encoder.backend = cvmmap_streamer::EncoderBackendType::GStreamerLegacy;
}
cvmmap_streamer::ipc::FrameInfo frame_info{
.width = static_cast<std::uint16_t>(args->width),
.height = static_cast<std::uint16_t>(args->height),
+3 -50
View File
@@ -61,14 +61,12 @@ enum class ExitCode : int {
enum class ExpectMode {
H264,
H265Enhanced,
H265Domestic,
};
enum class VideoSignal {
Unknown,
H264,
H265Enhanced,
H265Domestic,
};
struct Config {
@@ -97,7 +95,6 @@ struct Stats {
std::uint32_t h264_video_messages{0};
std::uint32_t h265_enhanced_video_messages{0};
std::uint32_t h265_domestic_video_messages{0};
std::uint32_t unknown_video_messages{0};
bool mode_mismatch{false};
@@ -184,8 +181,6 @@ std::string_view to_string(ExpectMode mode) {
return "h264";
case ExpectMode::H265Enhanced:
return "h265-enhanced";
case ExpectMode::H265Domestic:
return "h265-domestic";
default:
return "unknown";
}
@@ -198,8 +193,6 @@ std::string_view to_string(VideoSignal signal) {
return "h264";
case VideoSignal::H265Enhanced:
return "h265-enhanced";
case VideoSignal::H265Domestic:
return "h265-domestic";
case VideoSignal::Unknown:
default:
return "unknown";
@@ -214,11 +207,8 @@ std::expected<ExpectMode, std::string> parse_mode(std::string_view raw) {
if (raw == "h265-enhanced") {
return ExpectMode::H265Enhanced;
}
if (raw == "h265-domestic") {
return ExpectMode::H265Domestic;
}
return std::unexpected(std::format(
"invalid mode '{}'; expected: h264 | h265-enhanced | h265-domestic",
"invalid mode '{}'; expected: h264 | h265-enhanced",
raw));
}
@@ -227,7 +217,7 @@ std::expected<Config, std::string> parse_args(int argc, char **argv) {
Config config;
std::string mode_raw;
std::string self_test_send_mode_raw;
const std::vector<std::string> accepted_modes{"h264", "h265-enhanced", "h265-domestic"};
const std::vector<std::string> accepted_modes{"h264", "h265-enhanced"};
CLI::App app{"rtmp_stub_tester - standalone RTMP ingest validator"};
app.allow_extras(false);
@@ -1176,9 +1166,6 @@ VideoSignal classify_video_packet(std::span<const std::uint8_t> payload) {
if (codec_id == 7) {
return VideoSignal::H264;
}
if (codec_id == 12) {
return VideoSignal::H265Domestic;
}
if ((first & 0x80) != 0 && payload.size() >= 5) {
const std::array<std::uint8_t, 4> hvc1{'h', 'v', 'c', '1'};
@@ -1206,9 +1193,6 @@ void update_mode_stats(
case VideoSignal::H265Enhanced:
stats.h265_enhanced_video_messages++;
break;
case VideoSignal::H265Domestic:
stats.h265_domestic_video_messages++;
break;
case VideoSignal::Unknown:
default:
stats.unknown_video_messages++;
@@ -1226,9 +1210,6 @@ void update_mode_stats(
if (expected == ExpectMode::H265Enhanced && actual != VideoSignal::H265Enhanced) {
mismatch = true;
}
if (expected == ExpectMode::H265Domestic && actual != VideoSignal::H265Domestic) {
mismatch = true;
}
if (!mismatch) {
return;
@@ -1252,8 +1233,6 @@ std::uint32_t matching_count(const Stats &stats, ExpectMode mode) {
return stats.h264_video_messages;
case ExpectMode::H265Enhanced:
return stats.h265_enhanced_video_messages;
case ExpectMode::H265Domestic:
return stats.h265_domestic_video_messages;
default:
return 0;
}
@@ -1663,31 +1642,6 @@ send_client_video_mode_packets(int fd, std::uint32_t chunk_size, std::uint32_t s
};
break;
}
case ExpectMode::H265Domestic: {
config_payload = {
0x1c,
0x00,
0x00,
0x00,
0x00,
0x01,
0x01,
0x60,
};
frame_payload = {
0x2c,
0x01,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x01,
0x26,
};
break;
}
default:
return std::unexpected("unsupported self-test mode");
}
@@ -1778,10 +1732,9 @@ void print_summary(const Config &config, const Stats &stats) {
stats.total_video_messages,
stats.total_data_messages,
stats.set_chunk_size_messages);
spdlog::info("Video signaling counts: h264={}, h265-enhanced={}, h265-domestic={}, unknown={}",
spdlog::info("Video signaling counts: h264={}, h265-enhanced={}, unknown={}",
stats.h264_video_messages,
stats.h265_enhanced_video_messages,
stats.h265_domestic_video_messages,
stats.unknown_video_messages);
spdlog::info("Matching count for expected mode: {} (threshold={})",
matching_count(stats, config.expect_mode),
+214
View File
@@ -0,0 +1,214 @@
#include "cvmmap_streamer/config/runtime_config.hpp"
#include "cvmmap_streamer/encode/encoder_backend.hpp"
#include "cvmmap_streamer/ipc/contracts.hpp"
#include "cvmmap_streamer/protocol/rtp_publisher.hpp"
#include <CLI/CLI.hpp>
#include <chrono>
#include <cstdint>
#include <expected>
#include <string>
#include <thread>
#include <vector>
#include <spdlog/spdlog.h>
namespace {
enum class TesterExitCode : int {
Success = 0,
InvalidArgument = 2,
BackendSelectionError = 3,
BackendInitError = 4,
PublisherInitError = 5,
PushError = 6,
DrainError = 7,
FlushError = 8,
};
[[nodiscard]]
constexpr int exit_code(TesterExitCode code) {
return static_cast<int>(code);
}
struct Config {
std::string host{"127.0.0.1"};
std::uint16_t port{5004};
std::uint32_t payload_type{96};
std::string codec{"h264"};
std::string encoder_device{"software"};
std::string sdp_path{};
std::uint32_t frames{48};
std::uint32_t width{320};
std::uint32_t height{240};
std::uint32_t frame_interval_ms{33};
};
[[nodiscard]]
std::expected<Config, int> parse_args(int argc, char **argv) {
Config config{};
CLI::App app{"rtp_output_tester - publish synthetic encoded video to RTP using the FFmpeg encoder path"};
app.add_option("--host", config.host, "RTP destination host")->required();
app.add_option("--port", config.port, "RTP destination port")->required()->check(CLI::Range(1, 65535));
app.add_option("--payload-type", config.payload_type, "RTP payload type (96-127)")->check(CLI::Range(96, 127));
app.add_option("--codec", config.codec, "Video codec (h264|h265)")
->check(CLI::IsMember({"h264", "h265"}));
app.add_option("--encoder-device", config.encoder_device, "Encoder device (auto|nvidia|software)")
->check(CLI::IsMember({"auto", "nvidia", "software"}));
app.add_option("--sdp-path", config.sdp_path, "Optional SDP output path");
app.add_option("--frames", config.frames, "Number of frames to publish")->check(CLI::PositiveNumber);
app.add_option("--width", config.width, "Frame width")->check(CLI::PositiveNumber);
app.add_option("--height", config.height, "Frame height")->check(CLI::PositiveNumber);
app.add_option("--frame-interval-ms", config.frame_interval_ms, "Frame interval in milliseconds")->check(CLI::PositiveNumber);
try {
app.parse(argc, argv);
} catch (const CLI::ParseError &e) {
return std::unexpected(app.exit(e));
}
return config;
}
[[nodiscard]]
std::expected<cvmmap_streamer::CodecType, std::string> parse_codec(std::string_view raw) {
if (raw == "h264") {
return cvmmap_streamer::CodecType::H264;
}
if (raw == "h265") {
return cvmmap_streamer::CodecType::H265;
}
return std::unexpected("unsupported codec");
}
[[nodiscard]]
std::expected<cvmmap_streamer::EncoderDeviceType, std::string> parse_encoder_device(std::string_view raw) {
if (raw == "auto") {
return cvmmap_streamer::EncoderDeviceType::Auto;
}
if (raw == "nvidia") {
return cvmmap_streamer::EncoderDeviceType::Nvidia;
}
if (raw == "software") {
return cvmmap_streamer::EncoderDeviceType::Software;
}
return std::unexpected("unsupported encoder device");
}
void fill_pattern(std::vector<std::uint8_t> &buffer, std::uint32_t width, std::uint32_t height, std::uint32_t frame_index) {
for (std::uint32_t y = 0; y < height; ++y) {
for (std::uint32_t x = 0; x < width; ++x) {
const std::size_t pixel = static_cast<std::size_t>(y) * width * 3 + static_cast<std::size_t>(x) * 3;
buffer[pixel + 0] = static_cast<std::uint8_t>((x + frame_index * 3) & 0xffu);
buffer[pixel + 1] = static_cast<std::uint8_t>((y * 2 + frame_index * 5) & 0xffu);
buffer[pixel + 2] = static_cast<std::uint8_t>(((x + y) / 2 + frame_index * 7) & 0xffu);
}
}
}
}
int main(int argc, char **argv) {
auto args = parse_args(argc, argv);
if (!args) {
return args.error();
}
auto codec = parse_codec(args->codec);
if (!codec) {
spdlog::error("{}", codec.error());
return exit_code(TesterExitCode::InvalidArgument);
}
auto encoder_device = parse_encoder_device(args->encoder_device);
if (!encoder_device) {
spdlog::error("{}", encoder_device.error());
return exit_code(TesterExitCode::InvalidArgument);
}
cvmmap_streamer::RuntimeConfig config = cvmmap_streamer::RuntimeConfig::defaults();
config.encoder.backend = cvmmap_streamer::EncoderBackendType::FFmpeg;
config.encoder.device = *encoder_device;
config.encoder.codec = *codec;
config.encoder.gop = 15;
config.encoder.b_frames = 0;
config.outputs.rtp.enabled = true;
config.outputs.rtp.endpoint = args->host + ":" + std::to_string(args->port);
config.outputs.rtp.host = args->host;
config.outputs.rtp.port = args->port;
config.outputs.rtp.payload_type = static_cast<std::uint8_t>(args->payload_type);
if (!args->sdp_path.empty()) {
config.outputs.rtp.sdp_path = args->sdp_path;
}
cvmmap_streamer::ipc::FrameInfo frame_info{
.width = static_cast<std::uint16_t>(args->width),
.height = static_cast<std::uint16_t>(args->height),
.channels = 3,
.depth = cvmmap_streamer::ipc::Depth::U8,
.pixel_format = cvmmap_streamer::ipc::PixelFormat::BGR,
.buffer_size = args->width * args->height * 3,
};
auto backend = cvmmap_streamer::encode::make_encoder_backend(config);
if (!backend) {
spdlog::error("failed to select encoder backend: {}", cvmmap_streamer::format_error(backend.error()));
return exit_code(TesterExitCode::BackendSelectionError);
}
auto init = (*backend)->init(config, frame_info);
if (!init) {
spdlog::error("failed to initialize encoder backend: {}", cvmmap_streamer::format_error(init.error()));
return exit_code(TesterExitCode::BackendInitError);
}
auto publisher = cvmmap_streamer::protocol::UdpRtpPublisher::create(config);
if (!publisher) {
spdlog::error("failed to initialize RTP publisher: {}", publisher.error());
return exit_code(TesterExitCode::PublisherInitError);
}
std::vector<std::uint8_t> frame_bytes(frame_info.buffer_size, 0);
const auto frame_interval = std::chrono::milliseconds(args->frame_interval_ms);
std::uint64_t timestamp_ns{0};
for (std::uint32_t frame_index = 0; frame_index < args->frames; ++frame_index) {
fill_pattern(frame_bytes, args->width, args->height, frame_index);
auto push = (*backend)->push_frame(cvmmap_streamer::encode::RawVideoFrame{
.info = frame_info,
.source_timestamp_ns = timestamp_ns,
.bytes = std::span<const std::uint8_t>(frame_bytes.data(), frame_bytes.size()),
});
if (!push) {
spdlog::error("encoder push failed at frame {}: {}", frame_index, cvmmap_streamer::format_error(push.error()));
return exit_code(TesterExitCode::PushError);
}
auto drained = (*backend)->drain();
if (!drained) {
spdlog::error("encoder drain failed at frame {}: {}", frame_index, cvmmap_streamer::format_error(drained.error()));
return exit_code(TesterExitCode::DrainError);
}
for (const auto &access_unit : *drained) {
publisher->publish_access_unit(access_unit.annexb_bytes, access_unit.stream_pts_ns);
}
std::this_thread::sleep_for(frame_interval);
timestamp_ns += static_cast<std::uint64_t>(args->frame_interval_ms) * 1'000'000ull;
}
auto flushed = (*backend)->flush();
if (!flushed) {
spdlog::error("encoder flush failed: {}", cvmmap_streamer::format_error(flushed.error()));
return exit_code(TesterExitCode::FlushError);
}
for (const auto &access_unit : *flushed) {
publisher->publish_access_unit(access_unit.annexb_bytes, access_unit.stream_pts_ns);
}
publisher->log_metrics();
(*backend)->shutdown();
return exit_code(TesterExitCode::Success);
}