refactor(streamer): remove gstreamer and legacy rtmp paths
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user