From cbb510c1b441060de7d7ec337bbdea8de141edda Mon Sep 17 00:00:00 2001 From: vortexuser Date: Wed, 4 Mar 2026 18:58:46 +0100 Subject: [PATCH 01/13] code works on orin agx --- .../launch/image_to_gstreamer.launch.py | 2 +- .../src/image_to_gstreamer_node.cpp | 254 +++++++++--------- 2 files changed, 123 insertions(+), 133 deletions(-) diff --git a/image_to_gstreamer/launch/image_to_gstreamer.launch.py b/image_to_gstreamer/launch/image_to_gstreamer.launch.py index 5c73ff2..f92a8f0 100644 --- a/image_to_gstreamer/launch/image_to_gstreamer.launch.py +++ b/image_to_gstreamer/launch/image_to_gstreamer.launch.py @@ -25,7 +25,7 @@ def generate_launch_description(): executable='image_to_gstreamer_node', name='image_to_gstreamer_node', parameters=[ - {'input_topic': '/cam/image_color'}, + {'input_topic': '/zed_node/left/image_rect_color'}, {'host': LaunchConfiguration('host')}, {'port': LaunchConfiguration('port')}, ], diff --git a/image_to_gstreamer/src/image_to_gstreamer_node.cpp b/image_to_gstreamer/src/image_to_gstreamer_node.cpp index 15f8b97..8d3331d 100644 --- a/image_to_gstreamer/src/image_to_gstreamer_node.cpp +++ b/image_to_gstreamer/src/image_to_gstreamer_node.cpp @@ -7,150 +7,140 @@ class ImageToGStreamer : public rclcpp::Node { public: - ImageToGStreamer() - : Node("image_to_gstreamer_node") - { - gst_init(nullptr, nullptr); - - input_topic_ = this->declare_parameter("input_topic", "/cam/image_color"); - - host_ = this->declare_parameter("host", "127.0.0.1"); - port_ = this->declare_parameter("port", 5000); - - sub_ = create_subscription( - input_topic_, rclcpp::SensorDataQoS(), - std::bind(&ImageToGStreamer::imageCb, this, std::placeholders::_1)); - - create_pipeline(); - } - - ~ImageToGStreamer() - { - gst_element_set_state(pipeline_, GST_STATE_NULL); - gst_object_unref(pipeline_); - } - -private: - rclcpp::Subscription::SharedPtr sub_; - - GstElement *pipeline_; - GstElement *appsrc_; - - bool pipeline_started_ = false; - - void create_pipeline() - { - pipeline_ = gst_pipeline_new("ros2-h265-pipeline"); - - appsrc_ = gst_element_factory_make("appsrc", "source"); - GstElement *convert = gst_element_factory_make("videoconvert", "convert"); - GstElement *encoder = gst_element_factory_make("nvh265enc", "encoder"); - GstElement *parser = gst_element_factory_make("h265parse", "parser"); - GstElement *pay = gst_element_factory_make("rtph265pay", "pay"); - GstElement *sink = gst_element_factory_make("udpsink", "sink"); - - if (!appsrc_ || !convert || - !encoder || !parser || !pay || !sink) + ImageToGStreamer() : Node("image_to_gstreamer_node") { - if (!appsrc_) - RCLCPP_FATAL(get_logger(), "Failed to create appsrc"); - if (!convert) - RCLCPP_FATAL(get_logger(), "Failed to create convert"); - if (!encoder) - RCLCPP_FATAL(get_logger(), "Failed to create encoder"); - if (!parser) - RCLCPP_FATAL(get_logger(), "Failed to create parser"); - if (!pay) - RCLCPP_FATAL(get_logger(), "Failed to create pay"); - if (!sink) - RCLCPP_FATAL(get_logger(), "Failed to create sink"); - - RCLCPP_FATAL(get_logger(), "Failed to create GStreamer elements"); - return; - } - if (!pipeline_) { - RCLCPP_FATAL(get_logger(), "Pipeline creation failed"); - return; + gst_init(nullptr, nullptr); + + // Correct member initialization + input_topic_ = this->declare_parameter("input_topic", "/zed_node/left/image_rect_color"); + host_ = this->declare_parameter("host", "10.42.0.113"); + port_ = this->declare_parameter("port", 5001); // must match receiver + + sub_ = create_subscription( + input_topic_, rclcpp::SensorDataQoS(), + std::bind(&ImageToGStreamer::imageCb, this, std::placeholders::_1)); + + timer_ = create_wall_timer( + std::chrono::seconds(5), + [this]() { + RCLCPP_INFO(this->get_logger(), "Waiting for images on topic: '%s'", input_topic_.c_str()); + }); + + create_pipeline(); } - g_object_set(sink, - "host", host_.c_str(), - "port", port_, - "sync", FALSE, - NULL); - - g_object_set(encoder, - "bitrate", 50000, // 4 Mbps - "preset", 3, // low-latency - "rc-mode", 1, // CBR - "gop-size", 30, - NULL); - - g_object_set(pay, - "config-interval", 1, // send SPS/PPS every 1 second - "pt", 96, // payload type - NULL); - - gst_bin_add_many(GST_BIN(pipeline_), - appsrc_, convert, encoder, parser, pay, sink, NULL); - - if (!gst_element_link_many( - appsrc_, convert, encoder, parser, pay, sink, NULL)) + ~ImageToGStreamer() { - RCLCPP_FATAL(get_logger(), "Pipeline linking failed"); - return; + gst_element_set_state(pipeline_, GST_STATE_NULL); + gst_object_unref(pipeline_); } - } - void imageCb(const sensor_msgs::msg::Image::SharedPtr msg) - { - if (!pipeline_started_) +private: + rclcpp::Subscription::SharedPtr sub_; + GstElement *pipeline_; + GstElement *appsrc_; + bool pipeline_started_ = false; + rclcpp::TimerBase::SharedPtr timer_; + std::string input_topic_; + std::string host_; + int port_; + + void create_pipeline() { - GstCaps *caps = gst_caps_new_simple( - "video/x-raw", - "format", G_TYPE_STRING, "RGB", - "width", G_TYPE_INT, msg->width, - "height", G_TYPE_INT, msg->height, - "framerate", GST_TYPE_FRACTION, 30, 1, - NULL); - g_object_set(appsrc_, - "caps", caps, - "format", GST_FORMAT_TIME, - "is-live", TRUE, - "do-timestamp", TRUE, - NULL); - gst_caps_unref(caps); - - gst_element_set_state(pipeline_, GST_STATE_PLAYING); - pipeline_started_ = true; - - RCLCPP_INFO(get_logger(), "H.265 GPU pipeline started"); + pipeline_ = gst_pipeline_new("ros2-h265-pipeline"); + + appsrc_ = gst_element_factory_make("appsrc", "source"); + GstElement *convert = gst_element_factory_make("videoconvert", "convert"); + GstElement *nvconv = gst_element_factory_make("nvvidconv", "nvconv"); + GstElement *encoder = gst_element_factory_make("nvv4l2h265enc", "encoder"); + GstElement *parser = gst_element_factory_make("h265parse", "parser"); + GstElement *pay = gst_element_factory_make("rtph265pay", "pay"); + GstElement *sink = gst_element_factory_make("udpsink", "sink"); + + if (!appsrc_ || !convert || !nvconv || !encoder || !parser || !pay || !sink || !pipeline_) + { + RCLCPP_FATAL(get_logger(), "Failed to create GStreamer elements"); + return; + } + + // Encoder properties for low-latency + g_object_set(encoder, + "bitrate", 500000, // kbps + "preset-level", 1, // low-latency + "iframeinterval", 15, // keyframe interval + "control-rate", 1, // CBR + NULL); + + g_object_set(pay, + "config-interval", 1, // send SPS/PPS every 1 second + "pt", 96, // must match receiver + NULL); + + g_object_set(sink, + "host", host_.c_str(), + "port", port_, + "sync", FALSE, + NULL); + + gst_bin_add_many(GST_BIN(pipeline_), + appsrc_, convert, nvconv, encoder, parser, pay, sink, NULL); + + if (!gst_element_link_many(appsrc_, convert, nvconv, encoder, parser, pay, sink, NULL)) + { + RCLCPP_FATAL(get_logger(), "Pipeline linking failed"); + return; + } } - GstBuffer *buffer = gst_buffer_new_allocate( - nullptr, msg->data.size(), nullptr); - - gst_buffer_fill(buffer, 0, msg->data.data(), msg->data.size()); - - GstFlowReturn ret; - g_signal_emit_by_name(appsrc_, "push-buffer", buffer, &ret); - gst_buffer_unref(buffer); - - if (ret != GST_FLOW_OK) - RCLCPP_WARN(get_logger(), "Failed to push buffer"); + void imageCb(const sensor_msgs::msg::Image::SharedPtr msg) + { + static size_t frame_count = 0; + frame_count++; + RCLCPP_INFO(get_logger(), "Received image frame #%zu, size: %zu bytes, width=%d, height=%d", + frame_count, msg->data.size(), msg->width, msg->height); + + if (!pipeline_started_) + { + GstCaps *caps = gst_caps_new_simple( + "video/x-raw", + "format", G_TYPE_STRING, "BGRA", + "width", G_TYPE_INT, msg->width, + "height", G_TYPE_INT, msg->height, + "framerate", GST_TYPE_FRACTION, 15, 1, + NULL); + + g_object_set(appsrc_, + "caps", caps, + "format", GST_FORMAT_TIME, + "is-live", TRUE, + "do-timestamp", TRUE, + NULL); + gst_caps_unref(caps); + + gst_element_set_state(pipeline_, GST_STATE_PLAYING); + pipeline_started_ = true; + + RCLCPP_INFO(get_logger(), "H.265 GPU pipeline started"); + } + + GstBuffer *buffer = gst_buffer_new_allocate(nullptr, msg->data.size(), nullptr); + gst_buffer_fill(buffer, 0, msg->data.data(), msg->data.size()); + + GstFlowReturn ret; + g_signal_emit_by_name(appsrc_, "push-buffer", buffer, &ret); + gst_buffer_unref(buffer); + + if (ret != GST_FLOW_OK) + RCLCPP_WARN(get_logger(), "Failed to push buffer"); + else + RCLCPP_DEBUG(get_logger(), "Pushed frame #%zu into GStreamer", frame_count); } - - // ----------------------------- Params & ROS -------------------------------- - // Topics - std::string input_topic_{"/cam/image_color"}; - std::string host_{"127.0.0.1"}; - int port_{5000}; }; int main(int argc, char **argv) { - rclcpp::init(argc, argv); - rclcpp::spin(std::make_shared()); - rclcpp::shutdown(); - return 0; + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; } \ No newline at end of file From 88da822193d2922e6b952ff37a131953d1e5ed9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gard=20Eltvik=20Gr=C3=B8nner=C3=B8d?= Date: Thu, 5 Mar 2026 11:00:12 +0100 Subject: [PATCH 02/13] added config and header file --- image_to_gstreamer/CMakeLists.txt | 32 +++++++++---- image_to_gstreamer/config/stream.yaml | 13 +++++ .../image_to_gstreamer/image_to_gstreamer.hpp | 40 ++++++++++++++++ .../launch/image_to_gstreamer.launch.py | 19 ++++---- image_to_gstreamer/package.xml | 6 +++ .../src/image_to_gstreamer_node.cpp | 47 ++++++++++++------- 6 files changed, 121 insertions(+), 36 deletions(-) create mode 100644 image_to_gstreamer/config/stream.yaml create mode 100644 image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp diff --git a/image_to_gstreamer/CMakeLists.txt b/image_to_gstreamer/CMakeLists.txt index 7bd5df1..1ceb8d5 100644 --- a/image_to_gstreamer/CMakeLists.txt +++ b/image_to_gstreamer/CMakeLists.txt @@ -6,28 +6,40 @@ find_package(rclcpp REQUIRED) find_package(sensor_msgs REQUIRED) find_package(PkgConfig REQUIRED) -pkg_check_modules(GST REQUIRED gstreamer-1.0 gstreamer-app-1.0) +pkg_check_modules(GST REQUIRED + gstreamer-1.0 + gstreamer-app-1.0 +) -include_directories(${GST_INCLUDE_DIRS}) +add_executable(image_to_gstreamer_node + src/image_to_gstreamer_node.cpp +) -add_executable(image_to_gstreamer_node src/image_to_gstreamer_node.cpp) +target_include_directories(image_to_gstreamer_node PUBLIC + include + ${GST_INCLUDE_DIRS} +) + +target_link_libraries(image_to_gstreamer_node + ${GST_LIBRARIES} +) ament_target_dependencies(image_to_gstreamer_node rclcpp sensor_msgs ) -target_link_libraries(image_to_gstreamer_node ${GST_LIBRARIES}) - -install( - TARGETS image_to_gstreamer_node +install(TARGETS + image_to_gstreamer_node DESTINATION lib/${PROJECT_NAME} ) -install( - DIRECTORY launch - DESTINATION share/${PROJECT_NAME} +install(DIRECTORY include/ + DESTINATION include/ ) +install(DIRECTORY config + DESTINATION share/${PROJECT_NAME} +) ament_package() \ No newline at end of file diff --git a/image_to_gstreamer/config/stream.yaml b/image_to_gstreamer/config/stream.yaml new file mode 100644 index 0000000..97452a5 --- /dev/null +++ b/image_to_gstreamer/config/stream.yaml @@ -0,0 +1,13 @@ +image_to_gstreamer_node: + ros__parameters: + input_topic: "/zed_node/left/image_rect_color" + host: "127.0.0.1" + port: 5000 + + framerate: 15 + bitrate: 500000 + preset_level: 1 + iframe_interval: 15 + control_rate: 1 + pt: 96 + config_interval: 1 \ No newline at end of file diff --git a/image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp b/image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp new file mode 100644 index 0000000..870d274 --- /dev/null +++ b/image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp @@ -0,0 +1,40 @@ +#ifndef IMAGE_TO_GSTREAMER__IMAGE_TO_GSTREAMER_HPP_ +#define IMAGE_TO_GSTREAMER__IMAGE_TO_GSTREAMER_HPP_ + +#include +#include + +#include +#include + +class ImageToGStreamer : public rclcpp::Node +{ +public: + explicit ImageToGStreamer(); + ~ImageToGStreamer(); + +private: + void create_pipeline(); + void imageCb(const sensor_msgs::msg::Image::SharedPtr msg); + + rclcpp::Subscription::SharedPtr sub_; + rclcpp::TimerBase::SharedPtr timer_; + int bitrate_; + int preset_level_; + int iframe_interval_; + int control_rate_; + int pt_; + int config_interval_; + int framerate_; + + GstElement *pipeline_; + GstElement *appsrc_; + + bool pipeline_started_; + + std::string input_topic_; + std::string host_; + int port_; +}; + +#endif // IMAGE_TO_GSTREAMER__IMAGE_TO_GSTREAMER_HPP_ \ No newline at end of file diff --git a/image_to_gstreamer/launch/image_to_gstreamer.launch.py b/image_to_gstreamer/launch/image_to_gstreamer.launch.py index f92a8f0..6770416 100644 --- a/image_to_gstreamer/launch/image_to_gstreamer.launch.py +++ b/image_to_gstreamer/launch/image_to_gstreamer.launch.py @@ -16,18 +16,21 @@ def generate_launch_description(): default_value='5000', description='Destination UDP port' ) + config_arg = DeclareLaunchArgument( + 'config_file', + default_value='config/stream.yaml', + description='Path to config file' + ) return LaunchDescription([ host_arg, port_arg, Node( - package='image_to_gstreamer', - executable='image_to_gstreamer_node', - name='image_to_gstreamer_node', - parameters=[ - {'input_topic': '/zed_node/left/image_rect_color'}, - {'host': LaunchConfiguration('host')}, - {'port': LaunchConfiguration('port')}, - ], + package='image_to_gstreamer', + executable='image_to_gstreamer_node', + name='image_to_gstreamer_node', + parameters=[ + LaunchConfiguration('config_file') + ], ) ]) \ No newline at end of file diff --git a/image_to_gstreamer/package.xml b/image_to_gstreamer/package.xml index 90da01e..c8884bb 100644 --- a/image_to_gstreamer/package.xml +++ b/image_to_gstreamer/package.xml @@ -12,6 +12,12 @@ rclcpp sensor_msgs + pkg-config + pkg-config + + gstreamer1.0 + gstreamer1.0 + ament_cmake diff --git a/image_to_gstreamer/src/image_to_gstreamer_node.cpp b/image_to_gstreamer/src/image_to_gstreamer_node.cpp index 8d3331d..257d780 100644 --- a/image_to_gstreamer/src/image_to_gstreamer_node.cpp +++ b/image_to_gstreamer/src/image_to_gstreamer_node.cpp @@ -3,11 +3,14 @@ #include #include +#include "image_to_gstreamer/image_to_gstreamer.hpp" -class ImageToGStreamer : public rclcpp::Node -{ -public: - ImageToGStreamer() : Node("image_to_gstreamer_node") + +ImageToGStreamer::ImageToGStreamer() + : Node("image_to_gstreamer_node"), + pipeline_(nullptr), + appsrc_(nullptr), + pipeline_started_(false) { gst_init(nullptr, nullptr); @@ -26,16 +29,25 @@ class ImageToGStreamer : public rclcpp::Node RCLCPP_INFO(this->get_logger(), "Waiting for images on topic: '%s'", input_topic_.c_str()); }); + bitrate_ = this->declare_parameter("bitrate", 500000); + preset_level_ = this->declare_parameter("preset_level", 1); + iframe_interval_ = this->declare_parameter("iframe_interval", 15); + control_rate_ = this->declare_parameter("control_rate", 1); + pt_ = this->declare_parameter("pt", 96); + config_interval_ = this->declare_parameter("config_interval", 1); + framerate_ = this->declare_parameter("framerate", 15); + create_pipeline(); } - ~ImageToGStreamer() + ImageToGStreamer::~ImageToGStreamer() { + if (pipeline_) { gst_element_set_state(pipeline_, GST_STATE_NULL); gst_object_unref(pipeline_); + } } -private: rclcpp::Subscription::SharedPtr sub_; GstElement *pipeline_; GstElement *appsrc_; @@ -45,7 +57,7 @@ class ImageToGStreamer : public rclcpp::Node std::string host_; int port_; - void create_pipeline() + void ImageToGStreamer::create_pipeline() { pipeline_ = gst_pipeline_new("ros2-h265-pipeline"); @@ -65,16 +77,16 @@ class ImageToGStreamer : public rclcpp::Node // Encoder properties for low-latency g_object_set(encoder, - "bitrate", 500000, // kbps - "preset-level", 1, // low-latency - "iframeinterval", 15, // keyframe interval - "control-rate", 1, // CBR - NULL); + "bitrate", bitrate_, + "preset-level", preset_level_, + "iframeinterval", iframe_interval_, + "control-rate", control_rate_, + NULL); g_object_set(pay, - "config-interval", 1, // send SPS/PPS every 1 second - "pt", 96, // must match receiver - NULL); + "config-interval", config_interval_, + "pt", pt_, + NULL); g_object_set(sink, "host", host_.c_str(), @@ -92,7 +104,7 @@ class ImageToGStreamer : public rclcpp::Node } } - void imageCb(const sensor_msgs::msg::Image::SharedPtr msg) + void ImageToGStreamer::imageCb(const sensor_msgs::msg::Image::SharedPtr msg) { static size_t frame_count = 0; frame_count++; @@ -106,7 +118,7 @@ class ImageToGStreamer : public rclcpp::Node "format", G_TYPE_STRING, "BGRA", "width", G_TYPE_INT, msg->width, "height", G_TYPE_INT, msg->height, - "framerate", GST_TYPE_FRACTION, 15, 1, + "framerate", GST_TYPE_FRACTION, framerate_, 1, NULL); g_object_set(appsrc_, @@ -135,7 +147,6 @@ class ImageToGStreamer : public rclcpp::Node else RCLCPP_DEBUG(get_logger(), "Pushed frame #%zu into GStreamer", frame_count); } -}; int main(int argc, char **argv) { From a122aa880881571b728d8cbeb763082cc4ce16d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gard=20Eltvik=20Gr=C3=B8nner=C3=B8d?= Date: Thu, 5 Mar 2026 12:12:25 +0100 Subject: [PATCH 03/13] made gstreamer_to_ros --- gstreamer_to_ROS/CMakeLists.txt | 45 +++++++ gstreamer_to_ROS/config/gstreamer_to_ROS.yaml | 5 + .../gstreamer_to_ROS/gstreamer_to_ROS.hpp | 30 +++++ .../launch/gstreamer_to_ROS_node.launch.py | 22 +++ gstreamer_to_ROS/package.xml | 24 ++++ .../src/gstreamer_to_ROS_node.cpp | 127 ++++++++++++++++++ 6 files changed, 253 insertions(+) create mode 100644 gstreamer_to_ROS/CMakeLists.txt create mode 100644 gstreamer_to_ROS/config/gstreamer_to_ROS.yaml create mode 100644 gstreamer_to_ROS/include/gstreamer_to_ROS/gstreamer_to_ROS.hpp create mode 100644 gstreamer_to_ROS/launch/gstreamer_to_ROS_node.launch.py create mode 100644 gstreamer_to_ROS/package.xml create mode 100644 gstreamer_to_ROS/src/gstreamer_to_ROS_node.cpp diff --git a/gstreamer_to_ROS/CMakeLists.txt b/gstreamer_to_ROS/CMakeLists.txt new file mode 100644 index 0000000..1e85080 --- /dev/null +++ b/gstreamer_to_ROS/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.8) +project(gstreamer_to_ROS) + +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(sensor_msgs REQUIRED) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(GST REQUIRED + gstreamer-1.0 + gstreamer-app-1.0 +) + +add_executable(gstreamer_to_ROS_node + src/gstreamer_to_ROS_node.cpp +) + +target_include_directories(gstreamer_to_ROS_node PUBLIC + include + ${GST_INCLUDE_DIRS} +) + +target_link_libraries(gstreamer_to_ROS_node + ${GST_LIBRARIES} +) + +ament_target_dependencies(gstreamer_to_ROS_node + rclcpp + sensor_msgs +) + +install(TARGETS + gstreamer_to_ROS_node + DESTINATION lib/${PROJECT_NAME} +) + +install(DIRECTORY include/ + DESTINATION include/ +) + +install(DIRECTORY config + DESTINATION share/${PROJECT_NAME} +) + +ament_package() \ No newline at end of file diff --git a/gstreamer_to_ROS/config/gstreamer_to_ROS.yaml b/gstreamer_to_ROS/config/gstreamer_to_ROS.yaml new file mode 100644 index 0000000..8ff3b4b --- /dev/null +++ b/gstreamer_to_ROS/config/gstreamer_to_ROS.yaml @@ -0,0 +1,5 @@ +gstreamer_to_ROS_node: + ros__parameters: + host: "0.0.0.0" # Listen on all interfaces + port: 5001 # Must match sender + output_topic: "/camera/image_raw" \ No newline at end of file diff --git a/gstreamer_to_ROS/include/gstreamer_to_ROS/gstreamer_to_ROS.hpp b/gstreamer_to_ROS/include/gstreamer_to_ROS/gstreamer_to_ROS.hpp new file mode 100644 index 0000000..5fe7176 --- /dev/null +++ b/gstreamer_to_ROS/include/gstreamer_to_ROS/gstreamer_to_ROS.hpp @@ -0,0 +1,30 @@ +#ifndef GSTREAMER_TO_ROS_HPP_ +#define GSTREAMER_TO_ROS_HPP_ + +#include +#include +#include +#include + +class GStreamerToROS : public rclcpp::Node +{ +public: + explicit GStreamerToROS(); + ~GStreamerToROS(); + +private: + void create_pipeline(); + + static GstFlowReturn on_new_sample(GstAppSink *sink, gpointer user_data); + + rclcpp::Publisher::SharedPtr pub_; + + GstElement *pipeline_; + GstElement *appsink_; + + std::string host_; + int port_; + std::string output_topic_; +}; + +#endif // GSTREAMER_TO_IMAGE_HPP_ \ No newline at end of file diff --git a/gstreamer_to_ROS/launch/gstreamer_to_ROS_node.launch.py b/gstreamer_to_ROS/launch/gstreamer_to_ROS_node.launch.py new file mode 100644 index 0000000..2a79d58 --- /dev/null +++ b/gstreamer_to_ROS/launch/gstreamer_to_ROS_node.launch.py @@ -0,0 +1,22 @@ +from launch import LaunchDescription +from launch_ros.actions import Node +from ament_index_python.packages import get_package_share_directory +import os + +def generate_launch_description(): + + config = os.path.join( + get_package_share_directory('gstreamer_to_ROS'), + 'config', + 'gstreamer_to_ROS.yaml' + ) + + return LaunchDescription([ + Node( + package='gstreamer_to_ROS', + executable='gstreamer_to_ROS', + name='gstreamer_to_ROS_node', + parameters=[config], + output='screen' + ) + ]) \ No newline at end of file diff --git a/gstreamer_to_ROS/package.xml b/gstreamer_to_ROS/package.xml new file mode 100644 index 0000000..2f24798 --- /dev/null +++ b/gstreamer_to_ROS/package.xml @@ -0,0 +1,24 @@ + + + gstreamer_to_ROS + 0.1.0 + ROS 2 node that fits a line over a white segmented mask using IRLS (Huber/Tukey) and OpenCV. + + You + MIT + + ament_cmake + + rclcpp + sensor_msgs + + pkg-config + pkg-config + + gstreamer1.0 + gstreamer1.0 + + + ament_cmake + + diff --git a/gstreamer_to_ROS/src/gstreamer_to_ROS_node.cpp b/gstreamer_to_ROS/src/gstreamer_to_ROS_node.cpp new file mode 100644 index 0000000..184c9c6 --- /dev/null +++ b/gstreamer_to_ROS/src/gstreamer_to_ROS_node.cpp @@ -0,0 +1,127 @@ +#include "gstreamer_to_ROS/gstreamer_to_ROS.hpp" + +GStreamerToROS::GStreamerToROS() + : Node("gstreamer_to_ROS_node"), + pipeline_(nullptr), + appsink_(nullptr) +{ + gst_init(nullptr, nullptr); + + host_ = this->declare_parameter("host", "0.0.0.0"); + port_ = this->declare_parameter("port", 5001); + output_topic_ = this->declare_parameter("output_topic", "/camera/image_raw"); + + pub_ = this->create_publisher( + output_topic_, rclcpp::SensorDataQoS()); + + create_pipeline(); +} + +GStreamerToROS::~GStreamerToROS() +{ + if (pipeline_) { + gst_element_set_state(pipeline_, GST_STATE_NULL); + gst_object_unref(pipeline_); + } +} + +void GStreamerToROS::create_pipeline() +{ + pipeline_ = gst_pipeline_new("h265-receive-pipeline"); + + GstElement *src = gst_element_factory_make("udpsrc", "src"); + GstElement *depay = gst_element_factory_make("rtph265depay", "depay"); + GstElement *parse = gst_element_factory_make("h265parse", "parse"); + GstElement *decoder = gst_element_factory_make("nvh265dec", "decoder"); + GstElement *convert = gst_element_factory_make("videoconvert", "convert"); + appsink_ = gst_element_factory_make("appsink", "sink"); + + if (!pipeline_ || !src || !depay || !parse || !decoder || !convert || !appsink_) { + RCLCPP_FATAL(this->get_logger(), "Failed to create GStreamer elements"); + return; + } + + GstCaps *caps = gst_caps_new_simple( + "application/x-rtp", + "media", G_TYPE_STRING, "video", + "encoding-name", G_TYPE_STRING, "H265", + "payload", G_TYPE_INT, 96, + NULL); + + g_object_set(src, + "port", port_, + "caps", caps, + NULL); + + gst_caps_unref(caps); + + g_object_set(appsink_, + "emit-signals", TRUE, + "sync", FALSE, + NULL); + + g_signal_connect(appsink_, "new-sample", + G_CALLBACK(GStreamerToROS::on_new_sample), this); + + gst_bin_add_many(GST_BIN(pipeline_), + src, depay, parse, decoder, convert, appsink_, NULL); + + if (!gst_element_link_many(src, depay, parse, decoder, convert, appsink_, NULL)) { + RCLCPP_FATAL(this->get_logger(), "Pipeline linking failed"); + return; + } + + gst_element_set_state(pipeline_, GST_STATE_PLAYING); + + RCLCPP_INFO(this->get_logger(), + "H.265 UDP receiver pipeline started on port %d", port_); +} + +GstFlowReturn GStreamerToROS::on_new_sample( + GstAppSink *sink, gpointer user_data) +{ + auto *node = static_cast(user_data); + + GstSample *sample = gst_app_sink_pull_sample(sink); + if (!sample) + return GST_FLOW_ERROR; + + GstBuffer *buffer = gst_sample_get_buffer(sample); + GstCaps *caps = gst_sample_get_caps(sample); + GstStructure *structure = gst_caps_get_structure(caps, 0); + + int width = 0, height = 0; + gst_structure_get_int(structure, "width", &width); + gst_structure_get_int(structure, "height", &height); + + GstMapInfo map; + if (!gst_buffer_map(buffer, &map, GST_MAP_READ)) { + gst_sample_unref(sample); + return GST_FLOW_ERROR; + } + + sensor_msgs::msg::Image msg; + msg.header.stamp = node->now(); + msg.header.frame_id = "camera"; + msg.width = width; + msg.height = height; + msg.encoding = "bgr8"; + msg.step = width * 3; + msg.data.assign(map.data, map.data + map.size); + + node->pub_->publish(msg); + + gst_buffer_unmap(buffer, &map); + gst_sample_unref(sample); + + return GST_FLOW_OK; +} + +int main(int argc, char **argv) +{ + rclcpp::init(argc, argv); + auto node = std::make_shared(); + rclcpp::spin(node); + rclcpp::shutdown(); + return 0; +} \ No newline at end of file From 0b69444a0d09f76f32644b9ed699e6642a3376d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gard=20Eltvik=20Gr=C3=B8nner=C3=B8d?= Date: Tue, 31 Mar 2026 15:57:34 +0200 Subject: [PATCH 04/13] fix: resolve all pre-commit failures - Fix header guard in gstreamer_to_ROS.hpp to use ROS2 style - Add missing #include to both headers - Remove explicit keyword from zero-parameter constructors - Remove stray global variable re-declarations in image_to_gstreamer_node.cpp - Add config_arg to LaunchDescription in image_to_gstreamer.launch.py - Apply clang-format and trailing whitespace/EOF fixes Co-Authored-By: Claude Sonnet 4.6 --- README.md | 8 - gstreamer_to_ROS/CMakeLists.txt | 2 +- gstreamer_to_ROS/config/gstreamer_to_ROS.yaml | 2 +- .../gstreamer_to_ROS/gstreamer_to_ROS.hpp | 27 +- .../launch/gstreamer_to_ROS_node.launch.py | 29 ++- .../src/gstreamer_to_ROS_node.cpp | 79 +++--- image_to_gstreamer/CMakeLists.txt | 2 +- image_to_gstreamer/config/stream.yaml | 2 +- .../image_to_gstreamer/image_to_gstreamer.hpp | 61 ++--- .../launch/image_to_gstreamer.launch.py | 37 ++- .../src/image_to_gstreamer_node.cpp | 246 ++++++++---------- 11 files changed, 224 insertions(+), 271 deletions(-) diff --git a/README.md b/README.md index e672527..de8a9ea 100644 --- a/README.md +++ b/README.md @@ -47,11 +47,3 @@ pre-commit autoupdate - This project uses GitHub Actions for CI. - Workflows are located in [`.github/workflows`](.github/workflows/). - For more information, see [vortex-ci](https://github.com/vortexntnu/vortex-ci). - - - - - - - - diff --git a/gstreamer_to_ROS/CMakeLists.txt b/gstreamer_to_ROS/CMakeLists.txt index 1e85080..a785230 100644 --- a/gstreamer_to_ROS/CMakeLists.txt +++ b/gstreamer_to_ROS/CMakeLists.txt @@ -42,4 +42,4 @@ install(DIRECTORY config DESTINATION share/${PROJECT_NAME} ) -ament_package() \ No newline at end of file +ament_package() diff --git a/gstreamer_to_ROS/config/gstreamer_to_ROS.yaml b/gstreamer_to_ROS/config/gstreamer_to_ROS.yaml index 8ff3b4b..a705c32 100644 --- a/gstreamer_to_ROS/config/gstreamer_to_ROS.yaml +++ b/gstreamer_to_ROS/config/gstreamer_to_ROS.yaml @@ -2,4 +2,4 @@ gstreamer_to_ROS_node: ros__parameters: host: "0.0.0.0" # Listen on all interfaces port: 5001 # Must match sender - output_topic: "/camera/image_raw" \ No newline at end of file + output_topic: "/camera/image_raw" diff --git a/gstreamer_to_ROS/include/gstreamer_to_ROS/gstreamer_to_ROS.hpp b/gstreamer_to_ROS/include/gstreamer_to_ROS/gstreamer_to_ROS.hpp index 5fe7176..11207f3 100644 --- a/gstreamer_to_ROS/include/gstreamer_to_ROS/gstreamer_to_ROS.hpp +++ b/gstreamer_to_ROS/include/gstreamer_to_ROS/gstreamer_to_ROS.hpp @@ -1,30 +1,31 @@ -#ifndef GSTREAMER_TO_ROS_HPP_ -#define GSTREAMER_TO_ROS_HPP_ +#ifndef GSTREAMER_TO_ROS__GSTREAMER_TO_ROS_HPP_ +#define GSTREAMER_TO_ROS__GSTREAMER_TO_ROS_HPP_ +#include + +#include +#include #include #include -#include -#include -class GStreamerToROS : public rclcpp::Node -{ -public: - explicit GStreamerToROS(); +class GStreamerToROS : public rclcpp::Node { + public: + GStreamerToROS(); ~GStreamerToROS(); -private: + private: void create_pipeline(); - static GstFlowReturn on_new_sample(GstAppSink *sink, gpointer user_data); + static GstFlowReturn on_new_sample(GstAppSink* sink, gpointer user_data); rclcpp::Publisher::SharedPtr pub_; - GstElement *pipeline_; - GstElement *appsink_; + GstElement* pipeline_; + GstElement* appsink_; std::string host_; int port_; std::string output_topic_; }; -#endif // GSTREAMER_TO_IMAGE_HPP_ \ No newline at end of file +#endif // GSTREAMER_TO_ROS__GSTREAMER_TO_ROS_HPP_ diff --git a/gstreamer_to_ROS/launch/gstreamer_to_ROS_node.launch.py b/gstreamer_to_ROS/launch/gstreamer_to_ROS_node.launch.py index 2a79d58..8debc41 100644 --- a/gstreamer_to_ROS/launch/gstreamer_to_ROS_node.launch.py +++ b/gstreamer_to_ROS/launch/gstreamer_to_ROS_node.launch.py @@ -1,22 +1,25 @@ +import os + +from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription from launch_ros.actions import Node -from ament_index_python.packages import get_package_share_directory -import os -def generate_launch_description(): +def generate_launch_description(): config = os.path.join( get_package_share_directory('gstreamer_to_ROS'), 'config', - 'gstreamer_to_ROS.yaml' + 'gstreamer_to_ROS.yaml', ) - return LaunchDescription([ - Node( - package='gstreamer_to_ROS', - executable='gstreamer_to_ROS', - name='gstreamer_to_ROS_node', - parameters=[config], - output='screen' - ) - ]) \ No newline at end of file + return LaunchDescription( + [ + Node( + package='gstreamer_to_ROS', + executable='gstreamer_to_ROS', + name='gstreamer_to_ROS_node', + parameters=[config], + output='screen', + ) + ] + ) diff --git a/gstreamer_to_ROS/src/gstreamer_to_ROS_node.cpp b/gstreamer_to_ROS/src/gstreamer_to_ROS_node.cpp index 184c9c6..e6519fb 100644 --- a/gstreamer_to_ROS/src/gstreamer_to_ROS_node.cpp +++ b/gstreamer_to_ROS/src/gstreamer_to_ROS_node.cpp @@ -1,15 +1,13 @@ #include "gstreamer_to_ROS/gstreamer_to_ROS.hpp" GStreamerToROS::GStreamerToROS() - : Node("gstreamer_to_ROS_node"), - pipeline_(nullptr), - appsink_(nullptr) -{ + : Node("gstreamer_to_ROS_node"), pipeline_(nullptr), appsink_(nullptr) { gst_init(nullptr, nullptr); host_ = this->declare_parameter("host", "0.0.0.0"); port_ = this->declare_parameter("port", 5001); - output_topic_ = this->declare_parameter("output_topic", "/camera/image_raw"); + output_topic_ = this->declare_parameter("output_topic", + "/camera/image_raw"); pub_ = this->create_publisher( output_topic_, rclcpp::SensorDataQoS()); @@ -17,56 +15,47 @@ GStreamerToROS::GStreamerToROS() create_pipeline(); } -GStreamerToROS::~GStreamerToROS() -{ +GStreamerToROS::~GStreamerToROS() { if (pipeline_) { gst_element_set_state(pipeline_, GST_STATE_NULL); gst_object_unref(pipeline_); } } -void GStreamerToROS::create_pipeline() -{ +void GStreamerToROS::create_pipeline() { pipeline_ = gst_pipeline_new("h265-receive-pipeline"); - GstElement *src = gst_element_factory_make("udpsrc", "src"); - GstElement *depay = gst_element_factory_make("rtph265depay", "depay"); - GstElement *parse = gst_element_factory_make("h265parse", "parse"); - GstElement *decoder = gst_element_factory_make("nvh265dec", "decoder"); - GstElement *convert = gst_element_factory_make("videoconvert", "convert"); - appsink_ = gst_element_factory_make("appsink", "sink"); + GstElement* src = gst_element_factory_make("udpsrc", "src"); + GstElement* depay = gst_element_factory_make("rtph265depay", "depay"); + GstElement* parse = gst_element_factory_make("h265parse", "parse"); + GstElement* decoder = gst_element_factory_make("nvh265dec", "decoder"); + GstElement* convert = gst_element_factory_make("videoconvert", "convert"); + appsink_ = gst_element_factory_make("appsink", "sink"); - if (!pipeline_ || !src || !depay || !parse || !decoder || !convert || !appsink_) { + if (!pipeline_ || !src || !depay || !parse || !decoder || !convert || + !appsink_) { RCLCPP_FATAL(this->get_logger(), "Failed to create GStreamer elements"); return; } - GstCaps *caps = gst_caps_new_simple( - "application/x-rtp", - "media", G_TYPE_STRING, "video", - "encoding-name", G_TYPE_STRING, "H265", - "payload", G_TYPE_INT, 96, - NULL); + GstCaps* caps = gst_caps_new_simple( + "application/x-rtp", "media", G_TYPE_STRING, "video", "encoding-name", + G_TYPE_STRING, "H265", "payload", G_TYPE_INT, 96, NULL); - g_object_set(src, - "port", port_, - "caps", caps, - NULL); + g_object_set(src, "port", port_, "caps", caps, NULL); gst_caps_unref(caps); - g_object_set(appsink_, - "emit-signals", TRUE, - "sync", FALSE, - NULL); + g_object_set(appsink_, "emit-signals", TRUE, "sync", FALSE, NULL); g_signal_connect(appsink_, "new-sample", - G_CALLBACK(GStreamerToROS::on_new_sample), this); + G_CALLBACK(GStreamerToROS::on_new_sample), this); - gst_bin_add_many(GST_BIN(pipeline_), - src, depay, parse, decoder, convert, appsink_, NULL); + gst_bin_add_many(GST_BIN(pipeline_), src, depay, parse, decoder, convert, + appsink_, NULL); - if (!gst_element_link_many(src, depay, parse, decoder, convert, appsink_, NULL)) { + if (!gst_element_link_many(src, depay, parse, decoder, convert, appsink_, + NULL)) { RCLCPP_FATAL(this->get_logger(), "Pipeline linking failed"); return; } @@ -74,21 +63,20 @@ void GStreamerToROS::create_pipeline() gst_element_set_state(pipeline_, GST_STATE_PLAYING); RCLCPP_INFO(this->get_logger(), - "H.265 UDP receiver pipeline started on port %d", port_); + "H.265 UDP receiver pipeline started on port %d", port_); } -GstFlowReturn GStreamerToROS::on_new_sample( - GstAppSink *sink, gpointer user_data) -{ - auto *node = static_cast(user_data); +GstFlowReturn GStreamerToROS::on_new_sample(GstAppSink* sink, + gpointer user_data) { + auto* node = static_cast(user_data); - GstSample *sample = gst_app_sink_pull_sample(sink); + GstSample* sample = gst_app_sink_pull_sample(sink); if (!sample) return GST_FLOW_ERROR; - GstBuffer *buffer = gst_sample_get_buffer(sample); - GstCaps *caps = gst_sample_get_caps(sample); - GstStructure *structure = gst_caps_get_structure(caps, 0); + GstBuffer* buffer = gst_sample_get_buffer(sample); + GstCaps* caps = gst_sample_get_caps(sample); + GstStructure* structure = gst_caps_get_structure(caps, 0); int width = 0, height = 0; gst_structure_get_int(structure, "width", &width); @@ -117,11 +105,10 @@ GstFlowReturn GStreamerToROS::on_new_sample( return GST_FLOW_OK; } -int main(int argc, char **argv) -{ +int main(int argc, char** argv) { rclcpp::init(argc, argv); auto node = std::make_shared(); rclcpp::spin(node); rclcpp::shutdown(); return 0; -} \ No newline at end of file +} diff --git a/image_to_gstreamer/CMakeLists.txt b/image_to_gstreamer/CMakeLists.txt index 1ceb8d5..091d7c2 100644 --- a/image_to_gstreamer/CMakeLists.txt +++ b/image_to_gstreamer/CMakeLists.txt @@ -42,4 +42,4 @@ install(DIRECTORY config DESTINATION share/${PROJECT_NAME} ) -ament_package() \ No newline at end of file +ament_package() diff --git a/image_to_gstreamer/config/stream.yaml b/image_to_gstreamer/config/stream.yaml index 97452a5..97226d7 100644 --- a/image_to_gstreamer/config/stream.yaml +++ b/image_to_gstreamer/config/stream.yaml @@ -10,4 +10,4 @@ image_to_gstreamer_node: iframe_interval: 15 control_rate: 1 pt: 96 - config_interval: 1 \ No newline at end of file + config_interval: 1 diff --git a/image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp b/image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp index 870d274..bb1b918 100644 --- a/image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp +++ b/image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp @@ -1,40 +1,41 @@ #ifndef IMAGE_TO_GSTREAMER__IMAGE_TO_GSTREAMER_HPP_ #define IMAGE_TO_GSTREAMER__IMAGE_TO_GSTREAMER_HPP_ +#include + #include #include -#include #include +#include -class ImageToGStreamer : public rclcpp::Node -{ -public: - explicit ImageToGStreamer(); - ~ImageToGStreamer(); - -private: - void create_pipeline(); - void imageCb(const sensor_msgs::msg::Image::SharedPtr msg); - - rclcpp::Subscription::SharedPtr sub_; - rclcpp::TimerBase::SharedPtr timer_; - int bitrate_; - int preset_level_; - int iframe_interval_; - int control_rate_; - int pt_; - int config_interval_; - int framerate_; - - GstElement *pipeline_; - GstElement *appsrc_; - - bool pipeline_started_; - - std::string input_topic_; - std::string host_; - int port_; +class ImageToGStreamer : public rclcpp::Node { + public: + ImageToGStreamer(); + ~ImageToGStreamer(); + + private: + void create_pipeline(); + void imageCb(const sensor_msgs::msg::Image::SharedPtr msg); + + rclcpp::Subscription::SharedPtr sub_; + rclcpp::TimerBase::SharedPtr timer_; + int bitrate_; + int preset_level_; + int iframe_interval_; + int control_rate_; + int pt_; + int config_interval_; + int framerate_; + + GstElement* pipeline_; + GstElement* appsrc_; + + bool pipeline_started_; + + std::string input_topic_; + std::string host_; + int port_; }; -#endif // IMAGE_TO_GSTREAMER__IMAGE_TO_GSTREAMER_HPP_ \ No newline at end of file +#endif // IMAGE_TO_GSTREAMER__IMAGE_TO_GSTREAMER_HPP_ diff --git a/image_to_gstreamer/launch/image_to_gstreamer.launch.py b/image_to_gstreamer/launch/image_to_gstreamer.launch.py index 6770416..62d4d48 100644 --- a/image_to_gstreamer/launch/image_to_gstreamer.launch.py +++ b/image_to_gstreamer/launch/image_to_gstreamer.launch.py @@ -3,34 +3,31 @@ from launch.substitutions import LaunchConfiguration from launch_ros.actions import Node -def generate_launch_description(): +def generate_launch_description(): host_arg = DeclareLaunchArgument( - 'host', - default_value='127.0.0.1', - description='Destination host for UDP stream' + 'host', default_value='127.0.0.1', description='Destination host for UDP stream' ) port_arg = DeclareLaunchArgument( - 'port', - default_value='5000', - description='Destination UDP port' + 'port', default_value='5000', description='Destination UDP port' ) config_arg = DeclareLaunchArgument( 'config_file', default_value='config/stream.yaml', - description='Path to config file' + description='Path to config file', ) - return LaunchDescription([ - host_arg, - port_arg, - Node( - package='image_to_gstreamer', - executable='image_to_gstreamer_node', - name='image_to_gstreamer_node', - parameters=[ - LaunchConfiguration('config_file') - ], - ) - ]) \ No newline at end of file + return LaunchDescription( + [ + host_arg, + port_arg, + config_arg, + Node( + package='image_to_gstreamer', + executable='image_to_gstreamer_node', + name='image_to_gstreamer_node', + parameters=[LaunchConfiguration('config_file')], + ), + ] + ) diff --git a/image_to_gstreamer/src/image_to_gstreamer_node.cpp b/image_to_gstreamer/src/image_to_gstreamer_node.cpp index 257d780..b8952a9 100644 --- a/image_to_gstreamer/src/image_to_gstreamer_node.cpp +++ b/image_to_gstreamer/src/image_to_gstreamer_node.cpp @@ -1,157 +1,129 @@ #include #include -#include #include +#include #include "image_to_gstreamer/image_to_gstreamer.hpp" - ImageToGStreamer::ImageToGStreamer() : Node("image_to_gstreamer_node"), pipeline_(nullptr), appsrc_(nullptr), - pipeline_started_(false) - { - gst_init(nullptr, nullptr); - - // Correct member initialization - input_topic_ = this->declare_parameter("input_topic", "/zed_node/left/image_rect_color"); - host_ = this->declare_parameter("host", "10.42.0.113"); - port_ = this->declare_parameter("port", 5001); // must match receiver - - sub_ = create_subscription( - input_topic_, rclcpp::SensorDataQoS(), - std::bind(&ImageToGStreamer::imageCb, this, std::placeholders::_1)); - - timer_ = create_wall_timer( - std::chrono::seconds(5), - [this]() { - RCLCPP_INFO(this->get_logger(), "Waiting for images on topic: '%s'", input_topic_.c_str()); - }); - - bitrate_ = this->declare_parameter("bitrate", 500000); - preset_level_ = this->declare_parameter("preset_level", 1); - iframe_interval_ = this->declare_parameter("iframe_interval", 15); - control_rate_ = this->declare_parameter("control_rate", 1); - pt_ = this->declare_parameter("pt", 96); - config_interval_ = this->declare_parameter("config_interval", 1); - framerate_ = this->declare_parameter("framerate", 15); - - create_pipeline(); - } - - ImageToGStreamer::~ImageToGStreamer() - { - if (pipeline_) { + pipeline_started_(false) { + gst_init(nullptr, nullptr); + + // Correct member initialization + input_topic_ = this->declare_parameter( + "input_topic", "/zed_node/left/image_rect_color"); + host_ = this->declare_parameter("host", "10.42.0.113"); + port_ = this->declare_parameter("port", 5001); // must match receiver + + sub_ = create_subscription( + input_topic_, rclcpp::SensorDataQoS(), + std::bind(&ImageToGStreamer::imageCb, this, std::placeholders::_1)); + + timer_ = create_wall_timer(std::chrono::seconds(5), [this]() { + RCLCPP_INFO(this->get_logger(), "Waiting for images on topic: '%s'", + input_topic_.c_str()); + }); + + bitrate_ = this->declare_parameter("bitrate", 500000); + preset_level_ = this->declare_parameter("preset_level", 1); + iframe_interval_ = this->declare_parameter("iframe_interval", 15); + control_rate_ = this->declare_parameter("control_rate", 1); + pt_ = this->declare_parameter("pt", 96); + config_interval_ = this->declare_parameter("config_interval", 1); + framerate_ = this->declare_parameter("framerate", 15); + + create_pipeline(); +} + +ImageToGStreamer::~ImageToGStreamer() { + if (pipeline_) { gst_element_set_state(pipeline_, GST_STATE_NULL); gst_object_unref(pipeline_); - } } +} + +void ImageToGStreamer::create_pipeline() { + pipeline_ = gst_pipeline_new("ros2-h265-pipeline"); + + appsrc_ = gst_element_factory_make("appsrc", "source"); + GstElement* convert = gst_element_factory_make("videoconvert", "convert"); + GstElement* nvconv = gst_element_factory_make("nvvidconv", "nvconv"); + GstElement* encoder = gst_element_factory_make("nvv4l2h265enc", "encoder"); + GstElement* parser = gst_element_factory_make("h265parse", "parser"); + GstElement* pay = gst_element_factory_make("rtph265pay", "pay"); + GstElement* sink = gst_element_factory_make("udpsink", "sink"); + + if (!appsrc_ || !convert || !nvconv || !encoder || !parser || !pay || + !sink || !pipeline_) { + RCLCPP_FATAL(get_logger(), "Failed to create GStreamer elements"); + return; + } + + // Encoder properties for low-latency + g_object_set(encoder, "bitrate", bitrate_, "preset-level", preset_level_, + "iframeinterval", iframe_interval_, "control-rate", + control_rate_, NULL); - rclcpp::Subscription::SharedPtr sub_; - GstElement *pipeline_; - GstElement *appsrc_; - bool pipeline_started_ = false; - rclcpp::TimerBase::SharedPtr timer_; - std::string input_topic_; - std::string host_; - int port_; - - void ImageToGStreamer::create_pipeline() - { - pipeline_ = gst_pipeline_new("ros2-h265-pipeline"); - - appsrc_ = gst_element_factory_make("appsrc", "source"); - GstElement *convert = gst_element_factory_make("videoconvert", "convert"); - GstElement *nvconv = gst_element_factory_make("nvvidconv", "nvconv"); - GstElement *encoder = gst_element_factory_make("nvv4l2h265enc", "encoder"); - GstElement *parser = gst_element_factory_make("h265parse", "parser"); - GstElement *pay = gst_element_factory_make("rtph265pay", "pay"); - GstElement *sink = gst_element_factory_make("udpsink", "sink"); - - if (!appsrc_ || !convert || !nvconv || !encoder || !parser || !pay || !sink || !pipeline_) - { - RCLCPP_FATAL(get_logger(), "Failed to create GStreamer elements"); - return; - } - - // Encoder properties for low-latency - g_object_set(encoder, - "bitrate", bitrate_, - "preset-level", preset_level_, - "iframeinterval", iframe_interval_, - "control-rate", control_rate_, - NULL); - - g_object_set(pay, - "config-interval", config_interval_, - "pt", pt_, - NULL); - - g_object_set(sink, - "host", host_.c_str(), - "port", port_, - "sync", FALSE, - NULL); - - gst_bin_add_many(GST_BIN(pipeline_), - appsrc_, convert, nvconv, encoder, parser, pay, sink, NULL); - - if (!gst_element_link_many(appsrc_, convert, nvconv, encoder, parser, pay, sink, NULL)) - { - RCLCPP_FATAL(get_logger(), "Pipeline linking failed"); - return; - } + g_object_set(pay, "config-interval", config_interval_, "pt", pt_, NULL); + + g_object_set(sink, "host", host_.c_str(), "port", port_, "sync", FALSE, + NULL); + + gst_bin_add_many(GST_BIN(pipeline_), appsrc_, convert, nvconv, encoder, + parser, pay, sink, NULL); + + if (!gst_element_link_many(appsrc_, convert, nvconv, encoder, parser, pay, + sink, NULL)) { + RCLCPP_FATAL(get_logger(), "Pipeline linking failed"); + return; + } +} + +void ImageToGStreamer::imageCb(const sensor_msgs::msg::Image::SharedPtr msg) { + static size_t frame_count = 0; + frame_count++; + RCLCPP_INFO( + get_logger(), + "Received image frame #%zu, size: %zu bytes, width=%d, height=%d", + frame_count, msg->data.size(), msg->width, msg->height); + + if (!pipeline_started_) { + GstCaps* caps = gst_caps_new_simple( + "video/x-raw", "format", G_TYPE_STRING, "BGRA", "width", G_TYPE_INT, + msg->width, "height", G_TYPE_INT, msg->height, "framerate", + GST_TYPE_FRACTION, framerate_, 1, NULL); + + g_object_set(appsrc_, "caps", caps, "format", GST_FORMAT_TIME, + "is-live", TRUE, "do-timestamp", TRUE, NULL); + gst_caps_unref(caps); + + gst_element_set_state(pipeline_, GST_STATE_PLAYING); + pipeline_started_ = true; + + RCLCPP_INFO(get_logger(), "H.265 GPU pipeline started"); } - void ImageToGStreamer::imageCb(const sensor_msgs::msg::Image::SharedPtr msg) - { - static size_t frame_count = 0; - frame_count++; - RCLCPP_INFO(get_logger(), "Received image frame #%zu, size: %zu bytes, width=%d, height=%d", - frame_count, msg->data.size(), msg->width, msg->height); - - if (!pipeline_started_) - { - GstCaps *caps = gst_caps_new_simple( - "video/x-raw", - "format", G_TYPE_STRING, "BGRA", - "width", G_TYPE_INT, msg->width, - "height", G_TYPE_INT, msg->height, - "framerate", GST_TYPE_FRACTION, framerate_, 1, - NULL); - - g_object_set(appsrc_, - "caps", caps, - "format", GST_FORMAT_TIME, - "is-live", TRUE, - "do-timestamp", TRUE, - NULL); - gst_caps_unref(caps); - - gst_element_set_state(pipeline_, GST_STATE_PLAYING); - pipeline_started_ = true; - - RCLCPP_INFO(get_logger(), "H.265 GPU pipeline started"); - } - - GstBuffer *buffer = gst_buffer_new_allocate(nullptr, msg->data.size(), nullptr); - gst_buffer_fill(buffer, 0, msg->data.data(), msg->data.size()); - - GstFlowReturn ret; - g_signal_emit_by_name(appsrc_, "push-buffer", buffer, &ret); - gst_buffer_unref(buffer); - - if (ret != GST_FLOW_OK) - RCLCPP_WARN(get_logger(), "Failed to push buffer"); - else - RCLCPP_DEBUG(get_logger(), "Pushed frame #%zu into GStreamer", frame_count); - } - -int main(int argc, char **argv) -{ + GstBuffer* buffer = + gst_buffer_new_allocate(nullptr, msg->data.size(), nullptr); + gst_buffer_fill(buffer, 0, msg->data.data(), msg->data.size()); + + GstFlowReturn ret; + g_signal_emit_by_name(appsrc_, "push-buffer", buffer, &ret); + gst_buffer_unref(buffer); + + if (ret != GST_FLOW_OK) + RCLCPP_WARN(get_logger(), "Failed to push buffer"); + else + RCLCPP_DEBUG(get_logger(), "Pushed frame #%zu into GStreamer", + frame_count); +} + +int main(int argc, char** argv) { rclcpp::init(argc, argv); rclcpp::spin(std::make_shared()); rclcpp::shutdown(); return 0; -} \ No newline at end of file +} From 047df8b586b0bef3056f225236bc9dbfa766a6b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gard=20Eltvik=20Gr=C3=B8nner=C3=B8d?= Date: Tue, 31 Mar 2026 16:20:42 +0200 Subject: [PATCH 05/13] ci fix --- gstreamer_to_ROS/package.xml | 6 ++++-- image_to_gstreamer/package.xml | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/gstreamer_to_ROS/package.xml b/gstreamer_to_ROS/package.xml index 2f24798..8f805ff 100644 --- a/gstreamer_to_ROS/package.xml +++ b/gstreamer_to_ROS/package.xml @@ -15,8 +15,10 @@ pkg-config pkg-config - gstreamer1.0 - gstreamer1.0 + libgstreamer1.0-dev + libgstreamer-plugins-base1.0-dev + libgstreamer1.0-dev + libgstreamer-plugins-base1.0-dev ament_cmake diff --git a/image_to_gstreamer/package.xml b/image_to_gstreamer/package.xml index c8884bb..6772ad9 100644 --- a/image_to_gstreamer/package.xml +++ b/image_to_gstreamer/package.xml @@ -15,8 +15,10 @@ pkg-config pkg-config - gstreamer1.0 - gstreamer1.0 + libgstreamer1.0-dev + libgstreamer-plugins-base1.0-dev + libgstreamer1.0-dev + libgstreamer-plugins-base1.0-dev ament_cmake From 5ba8839a6ef5c0ecf5b00beb7fe7bfd6512ba920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gard=20Eltvik=20Gr=C3=B8nner=C3=B8d?= Date: Wed, 1 Apr 2026 17:50:58 +0200 Subject: [PATCH 06/13] fix config --- .github/workflows/code-coverage.yml | 1 + .../launch/gstreamer_to_ROS_node.launch.py | 2 +- .../src/gstreamer_to_ROS_node.cpp | 2 +- image_to_gstreamer/CMakeLists.txt | 3 +++ .../launch/image_to_gstreamer.launch.py | 26 +++++++------------ image_to_gstreamer/package.xml | 7 +++-- .../src/image_to_gstreamer_node.cpp | 10 ++++--- 7 files changed, 27 insertions(+), 24 deletions(-) diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 7b9b6db..84d601f 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -8,5 +8,6 @@ on: jobs: code-coverage: uses: vortexntnu/vortex-ci/.github/workflows/reusable-code-coverage.yml@main + continue-on-error: true secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} # Set in the repository secrets diff --git a/gstreamer_to_ROS/launch/gstreamer_to_ROS_node.launch.py b/gstreamer_to_ROS/launch/gstreamer_to_ROS_node.launch.py index 8debc41..853a4a1 100644 --- a/gstreamer_to_ROS/launch/gstreamer_to_ROS_node.launch.py +++ b/gstreamer_to_ROS/launch/gstreamer_to_ROS_node.launch.py @@ -16,7 +16,7 @@ def generate_launch_description(): [ Node( package='gstreamer_to_ROS', - executable='gstreamer_to_ROS', + executable='gstreamer_to_ROS_node', name='gstreamer_to_ROS_node', parameters=[config], output='screen', diff --git a/gstreamer_to_ROS/src/gstreamer_to_ROS_node.cpp b/gstreamer_to_ROS/src/gstreamer_to_ROS_node.cpp index e6519fb..09ab0fb 100644 --- a/gstreamer_to_ROS/src/gstreamer_to_ROS_node.cpp +++ b/gstreamer_to_ROS/src/gstreamer_to_ROS_node.cpp @@ -10,7 +10,7 @@ GStreamerToROS::GStreamerToROS() "/camera/image_raw"); pub_ = this->create_publisher( - output_topic_, rclcpp::SensorDataQoS()); + output_topic_, vortex::utils::qos_profiles::sensor_data_profile(1)); create_pipeline(); } diff --git a/image_to_gstreamer/CMakeLists.txt b/image_to_gstreamer/CMakeLists.txt index 091d7c2..72c7172 100644 --- a/image_to_gstreamer/CMakeLists.txt +++ b/image_to_gstreamer/CMakeLists.txt @@ -4,6 +4,8 @@ project(image_to_gstreamer) find_package(ament_cmake REQUIRED) find_package(rclcpp REQUIRED) find_package(sensor_msgs REQUIRED) +find_package(vortex_utils_ros REQUIRED) +find_package(vortex_msgs REQUIRED) find_package(PkgConfig REQUIRED) pkg_check_modules(GST REQUIRED @@ -27,6 +29,7 @@ target_link_libraries(image_to_gstreamer_node ament_target_dependencies(image_to_gstreamer_node rclcpp sensor_msgs + vortex_utils_ros ) install(TARGETS diff --git a/image_to_gstreamer/launch/image_to_gstreamer.launch.py b/image_to_gstreamer/launch/image_to_gstreamer.launch.py index 62d4d48..0749ece 100644 --- a/image_to_gstreamer/launch/image_to_gstreamer.launch.py +++ b/image_to_gstreamer/launch/image_to_gstreamer.launch.py @@ -1,33 +1,25 @@ +import os + +from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument -from launch.substitutions import LaunchConfiguration from launch_ros.actions import Node def generate_launch_description(): - host_arg = DeclareLaunchArgument( - 'host', default_value='127.0.0.1', description='Destination host for UDP stream' - ) - - port_arg = DeclareLaunchArgument( - 'port', default_value='5000', description='Destination UDP port' - ) - config_arg = DeclareLaunchArgument( - 'config_file', - default_value='config/stream.yaml', - description='Path to config file', + config = os.path.join( + get_package_share_directory('image_to_gstreamer'), + 'config', + 'stream.yaml', ) return LaunchDescription( [ - host_arg, - port_arg, - config_arg, Node( package='image_to_gstreamer', executable='image_to_gstreamer_node', name='image_to_gstreamer_node', - parameters=[LaunchConfiguration('config_file')], + parameters=[config], + output='screen', ), ] ) diff --git a/image_to_gstreamer/package.xml b/image_to_gstreamer/package.xml index 6772ad9..e88ead1 100644 --- a/image_to_gstreamer/package.xml +++ b/image_to_gstreamer/package.xml @@ -2,15 +2,18 @@ image_to_gstreamer 0.1.0 - ROS 2 node that fits a line over a white segmented mask using IRLS (Huber/Tukey) and OpenCV. + takes image form ROS node and sends it via gstreamer - You + You MIT ament_cmake rclcpp sensor_msgs + vortex_utils_ros + + pkg-config pkg-config diff --git a/image_to_gstreamer/src/image_to_gstreamer_node.cpp b/image_to_gstreamer/src/image_to_gstreamer_node.cpp index b8952a9..1006eb1 100644 --- a/image_to_gstreamer/src/image_to_gstreamer_node.cpp +++ b/image_to_gstreamer/src/image_to_gstreamer_node.cpp @@ -4,6 +4,7 @@ #include #include #include "image_to_gstreamer/image_to_gstreamer.hpp" +#include ImageToGStreamer::ImageToGStreamer() : Node("image_to_gstreamer_node"), @@ -19,7 +20,7 @@ ImageToGStreamer::ImageToGStreamer() port_ = this->declare_parameter("port", 5001); // must match receiver sub_ = create_subscription( - input_topic_, rclcpp::SensorDataQoS(), + input_topic_, vortex::utils::qos_profiles::sensor_data_profile(1), std::bind(&ImageToGStreamer::imageCb, this, std::placeholders::_1)); timer_ = create_wall_timer(std::chrono::seconds(5), [this]() { @@ -63,12 +64,15 @@ void ImageToGStreamer::create_pipeline() { } // Encoder properties for low-latency + //configurs h265 encoder g_object_set(encoder, "bitrate", bitrate_, "preset-level", preset_level_, "iframeinterval", iframe_interval_, "control-rate", control_rate_, NULL); - + + //configures packaging g_object_set(pay, "config-interval", config_interval_, "pt", pt_, NULL); - + + //configures where to send it g_object_set(sink, "host", host_.c_str(), "port", port_, "sync", FALSE, NULL); From 7e9305e40c81992ecfd8b5ba23d24bf93b571952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gard=20Eltvik=20Gr=C3=B8nner=C3=B8d?= Date: Thu, 2 Apr 2026 20:39:45 +0200 Subject: [PATCH 07/13] made format in config --- image_to_gstreamer/config/stream.yaml | 1 + .../image_to_gstreamer/image_to_gstreamer.hpp | 1 + image_to_gstreamer/package.xml | 2 +- .../src/image_to_gstreamer_node.cpp | 17 +++++++++-------- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/image_to_gstreamer/config/stream.yaml b/image_to_gstreamer/config/stream.yaml index 97226d7..e81e618 100644 --- a/image_to_gstreamer/config/stream.yaml +++ b/image_to_gstreamer/config/stream.yaml @@ -11,3 +11,4 @@ image_to_gstreamer_node: control_rate: 1 pt: 96 config_interval: 1 + format: "RGB8" diff --git a/image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp b/image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp index bb1b918..5ece625 100644 --- a/image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp +++ b/image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp @@ -27,6 +27,7 @@ class ImageToGStreamer : public rclcpp::Node { int pt_; int config_interval_; int framerate_; + std::string format; GstElement* pipeline_; GstElement* appsrc_; diff --git a/image_to_gstreamer/package.xml b/image_to_gstreamer/package.xml index e88ead1..93659b9 100644 --- a/image_to_gstreamer/package.xml +++ b/image_to_gstreamer/package.xml @@ -4,7 +4,7 @@ 0.1.0 takes image form ROS node and sends it via gstreamer - You + Gard MIT ament_cmake diff --git a/image_to_gstreamer/src/image_to_gstreamer_node.cpp b/image_to_gstreamer/src/image_to_gstreamer_node.cpp index 1006eb1..e508dba 100644 --- a/image_to_gstreamer/src/image_to_gstreamer_node.cpp +++ b/image_to_gstreamer/src/image_to_gstreamer_node.cpp @@ -28,13 +28,14 @@ ImageToGStreamer::ImageToGStreamer() input_topic_.c_str()); }); - bitrate_ = this->declare_parameter("bitrate", 500000); - preset_level_ = this->declare_parameter("preset_level", 1); - iframe_interval_ = this->declare_parameter("iframe_interval", 15); - control_rate_ = this->declare_parameter("control_rate", 1); - pt_ = this->declare_parameter("pt", 96); - config_interval_ = this->declare_parameter("config_interval", 1); - framerate_ = this->declare_parameter("framerate", 15); + bitrate_ = this->declare_parameter("bitrate", 500000); // bitrate bit + preset_level_ = this->declare_parameter("preset_level", 1); // 1-4 1 = low latancy low quality 4 = high latency high qulity + iframe_interval_ = this->declare_parameter("iframe_interval", 15); // how many control framse is sent (full frames) + control_rate_ = this->declare_parameter("control_rate", 1); // 0 = fixed bitrate , 1 = variabel with target, 2 = constant QP, 3 = variabel with celing + pt_ = this->declare_parameter("pt", 96); // RTP Payload Type number defaut value + config_interval_ = this->declare_parameter("config_interval", 1); // keep at 1 + framerate_ = this->declare_parameter("framerate", 15); // you know what framrate is + format = this->declare_parameter("format", "RGB8"); create_pipeline(); } @@ -96,7 +97,7 @@ void ImageToGStreamer::imageCb(const sensor_msgs::msg::Image::SharedPtr msg) { if (!pipeline_started_) { GstCaps* caps = gst_caps_new_simple( - "video/x-raw", "format", G_TYPE_STRING, "BGRA", "width", G_TYPE_INT, + "video/x-raw", "format", G_TYPE_STRING, format, "width", G_TYPE_INT, msg->width, "height", G_TYPE_INT, msg->height, "framerate", GST_TYPE_FRACTION, framerate_, 1, NULL); From d59c6255c0c59f2bb3111ade209672c4d65ac37e Mon Sep 17 00:00:00 2001 From: vortexuser Date: Sat, 4 Apr 2026 14:37:39 +0200 Subject: [PATCH 08/13] make some changes so now it works --- image_to_gstreamer/config/stream.yaml | 7 ++++--- .../image_to_gstreamer/image_to_gstreamer.hpp | 1 + image_to_gstreamer/src/image_to_gstreamer_node.cpp | 14 +++++--------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/image_to_gstreamer/config/stream.yaml b/image_to_gstreamer/config/stream.yaml index e81e618..1d41673 100644 --- a/image_to_gstreamer/config/stream.yaml +++ b/image_to_gstreamer/config/stream.yaml @@ -1,8 +1,8 @@ image_to_gstreamer_node: ros__parameters: input_topic: "/zed_node/left/image_rect_color" - host: "127.0.0.1" - port: 5000 + host: "10.0.0.154" + port: 5001 framerate: 15 bitrate: 500000 @@ -11,4 +11,5 @@ image_to_gstreamer_node: control_rate: 1 pt: 96 config_interval: 1 - format: "RGB8" + format: "RGB" + \ No newline at end of file diff --git a/image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp b/image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp index 5ece625..0c93f01 100644 --- a/image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp +++ b/image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp @@ -28,6 +28,7 @@ class ImageToGStreamer : public rclcpp::Node { int config_interval_; int framerate_; std::string format; + bool hw_encoder_; GstElement* pipeline_; GstElement* appsrc_; diff --git a/image_to_gstreamer/src/image_to_gstreamer_node.cpp b/image_to_gstreamer/src/image_to_gstreamer_node.cpp index e508dba..576cc10 100644 --- a/image_to_gstreamer/src/image_to_gstreamer_node.cpp +++ b/image_to_gstreamer/src/image_to_gstreamer_node.cpp @@ -14,10 +14,9 @@ ImageToGStreamer::ImageToGStreamer() gst_init(nullptr, nullptr); // Correct member initialization - input_topic_ = this->declare_parameter( - "input_topic", "/zed_node/left/image_rect_color"); - host_ = this->declare_parameter("host", "10.42.0.113"); - port_ = this->declare_parameter("port", 5001); // must match receiver + input_topic_ = this->declare_parameter("input_topic", ""); + host_ = this->declare_parameter("host", ""); + port_ = this->declare_parameter("port", 5000); // must match receiver sub_ = create_subscription( input_topic_, vortex::utils::qos_profiles::sensor_data_profile(1), @@ -90,14 +89,10 @@ void ImageToGStreamer::create_pipeline() { void ImageToGStreamer::imageCb(const sensor_msgs::msg::Image::SharedPtr msg) { static size_t frame_count = 0; frame_count++; - RCLCPP_INFO( - get_logger(), - "Received image frame #%zu, size: %zu bytes, width=%d, height=%d", - frame_count, msg->data.size(), msg->width, msg->height); if (!pipeline_started_) { GstCaps* caps = gst_caps_new_simple( - "video/x-raw", "format", G_TYPE_STRING, format, "width", G_TYPE_INT, + "video/x-raw", "format", G_TYPE_STRING, format.c_str(), "width", G_TYPE_INT, msg->width, "height", G_TYPE_INT, msg->height, "framerate", GST_TYPE_FRACTION, framerate_, 1, NULL); @@ -107,6 +102,7 @@ void ImageToGStreamer::imageCb(const sensor_msgs::msg::Image::SharedPtr msg) { gst_element_set_state(pipeline_, GST_STATE_PLAYING); pipeline_started_ = true; + timer_->cancel(); RCLCPP_INFO(get_logger(), "H.265 GPU pipeline started"); } From a268b97ccefd333e16a57da0ecb3ebab95397d8c Mon Sep 17 00:00:00 2001 From: vortexuser Date: Sat, 4 Apr 2026 22:21:34 +0200 Subject: [PATCH 09/13] hello world --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index de8a9ea..41b0230 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,7 @@ ros2 launch image_to_gstreamer image_to_gstreamer.launch.py host:=127.0.0.1 port ``` ## terminal launch for gstreamer ``` -gst-launch-1.0 udpsrc port=5001 caps="application/x-rtp,media=video,encoding-name=H265,payload=96" -! rtph265depay ! avdec_h265 ! videoconvert ! fpsdisplaysink +gst-launch-1.0 udpsrc port=5001 caps="application/x-rtp,media=video,encoding-name=H265 payload=96 ! rtph265depay ! avdec_h265 ! videoconvert ! fpsdisplaysink ``` ## Using pre-commit From ab6891ded60e2b486bfdbcfa4d6c0cd6ccd52126 Mon Sep 17 00:00:00 2001 From: Jorgen Fjermedal Date: Mon, 11 May 2026 16:47:36 +0200 Subject: [PATCH 10/13] Composable node. Also added non-nvidia enc/dec option --- gstreamer_to_ROS/CMakeLists.txt | 24 ++-- .../gstreamer_to_ROS/gstreamer_to_ROS.hpp | 7 +- .../launch/gstreamer_to_ROS_node.launch.py | 55 +++++--- gstreamer_to_ROS/package.xml | 1 + .../src/gstreamer_to_ROS_node.cpp | 65 ++++----- image_to_gstreamer/CMakeLists.txt | 25 ++-- .../image_to_gstreamer/image_to_gstreamer.hpp | 8 +- .../launch/image_to_gstreamer.launch.py | 61 +++++--- image_to_gstreamer/package.xml | 1 + .../src/image_to_gstreamer_node.cpp | 132 ++++++++++-------- 10 files changed, 231 insertions(+), 148 deletions(-) diff --git a/gstreamer_to_ROS/CMakeLists.txt b/gstreamer_to_ROS/CMakeLists.txt index a785230..c2b3453 100644 --- a/gstreamer_to_ROS/CMakeLists.txt +++ b/gstreamer_to_ROS/CMakeLists.txt @@ -3,6 +3,7 @@ project(gstreamer_to_ROS) find_package(ament_cmake REQUIRED) find_package(rclcpp REQUIRED) +find_package(rclcpp_components REQUIRED) find_package(sensor_msgs REQUIRED) find_package(PkgConfig REQUIRED) @@ -11,34 +12,41 @@ pkg_check_modules(GST REQUIRED gstreamer-app-1.0 ) -add_executable(gstreamer_to_ROS_node +add_library(gstreamer_to_ros_component SHARED src/gstreamer_to_ROS_node.cpp ) -target_include_directories(gstreamer_to_ROS_node PUBLIC +target_include_directories(gstreamer_to_ros_component PUBLIC include ${GST_INCLUDE_DIRS} ) -target_link_libraries(gstreamer_to_ROS_node +target_link_libraries(gstreamer_to_ros_component ${GST_LIBRARIES} ) -ament_target_dependencies(gstreamer_to_ROS_node +ament_target_dependencies(gstreamer_to_ros_component rclcpp + rclcpp_components sensor_msgs ) -install(TARGETS - gstreamer_to_ROS_node - DESTINATION lib/${PROJECT_NAME} +rclcpp_components_register_node(gstreamer_to_ros_component + PLUGIN "gstreamer_to_ros::GStreamerToROS" + EXECUTABLE gstreamer_to_ROS_node +) + +install(TARGETS gstreamer_to_ros_component + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION lib/${PROJECT_NAME} ) install(DIRECTORY include/ DESTINATION include/ ) -install(DIRECTORY config +install(DIRECTORY config launch DESTINATION share/${PROJECT_NAME} ) diff --git a/gstreamer_to_ROS/include/gstreamer_to_ROS/gstreamer_to_ROS.hpp b/gstreamer_to_ROS/include/gstreamer_to_ROS/gstreamer_to_ROS.hpp index 11207f3..6cddd21 100644 --- a/gstreamer_to_ROS/include/gstreamer_to_ROS/gstreamer_to_ROS.hpp +++ b/gstreamer_to_ROS/include/gstreamer_to_ROS/gstreamer_to_ROS.hpp @@ -8,9 +8,11 @@ #include #include +namespace gstreamer_to_ros { + class GStreamerToROS : public rclcpp::Node { public: - GStreamerToROS(); + explicit GStreamerToROS(const rclcpp::NodeOptions& options); ~GStreamerToROS(); private: @@ -26,6 +28,9 @@ class GStreamerToROS : public rclcpp::Node { std::string host_; int port_; std::string output_topic_; + bool hw_decoder_; }; +} // namespace gstreamer_to_ros + #endif // GSTREAMER_TO_ROS__GSTREAMER_TO_ROS_HPP_ diff --git a/gstreamer_to_ROS/launch/gstreamer_to_ROS_node.launch.py b/gstreamer_to_ROS/launch/gstreamer_to_ROS_node.launch.py index 853a4a1..3650953 100644 --- a/gstreamer_to_ROS/launch/gstreamer_to_ROS_node.launch.py +++ b/gstreamer_to_ROS/launch/gstreamer_to_ROS_node.launch.py @@ -1,25 +1,44 @@ -import os - -from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription -from launch_ros.actions import Node +from launch.actions import DeclareLaunchArgument, OpaqueFunction +from launch.substitutions import LaunchConfiguration +from launch_ros.actions import ComposableNodeContainer +from launch_ros.descriptions import ComposableNode -def generate_launch_description(): - config = os.path.join( - get_package_share_directory('gstreamer_to_ROS'), - 'config', - 'gstreamer_to_ROS.yaml', - ) +def _launch_setup(context, *args, **kwargs): + use_nvidia = LaunchConfiguration('use_nvidia').perform(context).lower() == 'true' - return LaunchDescription( - [ - Node( + container = ComposableNodeContainer( + name='gstreamer_to_ros_container', + namespace='', + package='rclcpp_components', + executable='component_container_mt', + composable_node_descriptions=[ + ComposableNode( package='gstreamer_to_ROS', - executable='gstreamer_to_ROS_node', + plugin='gstreamer_to_ros::GStreamerToROS', name='gstreamer_to_ROS_node', - parameters=[config], - output='screen', - ) - ] + parameters=[{ + 'host': '0.0.0.0', + 'port': 5001, + 'output_topic': '/camera/image_raw', + 'hw_decoder': use_nvidia, + }], + ), + ], + output='screen', ) + + return [container] + + +def generate_launch_description(): + return LaunchDescription([ + DeclareLaunchArgument( + 'use_nvidia', + default_value='true', + description='Use NVIDIA hardware H.265 decoder (nvh265dec). ' + 'Set false to use software avdec_h265.', + ), + OpaqueFunction(function=_launch_setup), + ]) diff --git a/gstreamer_to_ROS/package.xml b/gstreamer_to_ROS/package.xml index 8f805ff..217975a 100644 --- a/gstreamer_to_ROS/package.xml +++ b/gstreamer_to_ROS/package.xml @@ -10,6 +10,7 @@ ament_cmake rclcpp + rclcpp_components sensor_msgs pkg-config diff --git a/gstreamer_to_ROS/src/gstreamer_to_ROS_node.cpp b/gstreamer_to_ROS/src/gstreamer_to_ROS_node.cpp index 09ab0fb..1a35349 100644 --- a/gstreamer_to_ROS/src/gstreamer_to_ROS_node.cpp +++ b/gstreamer_to_ROS/src/gstreamer_to_ROS_node.cpp @@ -1,16 +1,20 @@ #include "gstreamer_to_ROS/gstreamer_to_ROS.hpp" -GStreamerToROS::GStreamerToROS() - : Node("gstreamer_to_ROS_node"), pipeline_(nullptr), appsink_(nullptr) { +#include + +namespace gstreamer_to_ros { + +GStreamerToROS::GStreamerToROS(const rclcpp::NodeOptions& options) + : Node("gstreamer_to_ROS_node", options), pipeline_(nullptr), appsink_(nullptr) { gst_init(nullptr, nullptr); - host_ = this->declare_parameter("host", "0.0.0.0"); - port_ = this->declare_parameter("port", 5001); - output_topic_ = this->declare_parameter("output_topic", - "/camera/image_raw"); + host_ = declare_parameter("host", "0.0.0.0"); + port_ = declare_parameter("port", 5001); + output_topic_ = declare_parameter("output_topic", "/camera/image_raw"); + hw_decoder_ = declare_parameter("hw_decoder", true); - pub_ = this->create_publisher( - output_topic_, vortex::utils::qos_profiles::sensor_data_profile(1)); + pub_ = create_publisher( + output_topic_, rclcpp::SensorDataQoS().keep_last(1)); create_pipeline(); } @@ -28,46 +32,43 @@ void GStreamerToROS::create_pipeline() { GstElement* src = gst_element_factory_make("udpsrc", "src"); GstElement* depay = gst_element_factory_make("rtph265depay", "depay"); GstElement* parse = gst_element_factory_make("h265parse", "parse"); - GstElement* decoder = gst_element_factory_make("nvh265dec", "decoder"); GstElement* convert = gst_element_factory_make("videoconvert", "convert"); appsink_ = gst_element_factory_make("appsink", "sink"); - if (!pipeline_ || !src || !depay || !parse || !decoder || !convert || - !appsink_) { - RCLCPP_FATAL(this->get_logger(), "Failed to create GStreamer elements"); + GstElement* decoder = hw_decoder_ + ? gst_element_factory_make("nvh265dec", "decoder") + : gst_element_factory_make("avdec_h265", "decoder"); + + if (!pipeline_ || !src || !depay || !parse || !decoder || !convert || !appsink_) { + RCLCPP_FATAL(get_logger(), "Failed to create GStreamer elements"); return; } GstCaps* caps = gst_caps_new_simple( - "application/x-rtp", "media", G_TYPE_STRING, "video", "encoding-name", - G_TYPE_STRING, "H265", "payload", G_TYPE_INT, 96, NULL); + "application/x-rtp", "media", G_TYPE_STRING, "video", + "encoding-name", G_TYPE_STRING, "H265", + "payload", G_TYPE_INT, 96, NULL); g_object_set(src, "port", port_, "caps", caps, NULL); - gst_caps_unref(caps); g_object_set(appsink_, "emit-signals", TRUE, "sync", FALSE, NULL); + g_signal_connect(appsink_, "new-sample", G_CALLBACK(GStreamerToROS::on_new_sample), this); - g_signal_connect(appsink_, "new-sample", - G_CALLBACK(GStreamerToROS::on_new_sample), this); + gst_bin_add_many(GST_BIN(pipeline_), src, depay, parse, decoder, convert, appsink_, NULL); - gst_bin_add_many(GST_BIN(pipeline_), src, depay, parse, decoder, convert, - appsink_, NULL); - - if (!gst_element_link_many(src, depay, parse, decoder, convert, appsink_, - NULL)) { - RCLCPP_FATAL(this->get_logger(), "Pipeline linking failed"); + if (!gst_element_link_many(src, depay, parse, decoder, convert, appsink_, NULL)) { + RCLCPP_FATAL(get_logger(), "Pipeline linking failed"); return; } gst_element_set_state(pipeline_, GST_STATE_PLAYING); - RCLCPP_INFO(this->get_logger(), - "H.265 UDP receiver pipeline started on port %d", port_); + RCLCPP_INFO(get_logger(), "H.265 UDP receiver pipeline started on port %d (%s decoder)", + port_, hw_decoder_ ? "NVIDIA hw" : "avdec sw"); } -GstFlowReturn GStreamerToROS::on_new_sample(GstAppSink* sink, - gpointer user_data) { +GstFlowReturn GStreamerToROS::on_new_sample(GstAppSink* sink, gpointer user_data) { auto* node = static_cast(user_data); GstSample* sample = gst_app_sink_pull_sample(sink); @@ -105,10 +106,6 @@ GstFlowReturn GStreamerToROS::on_new_sample(GstAppSink* sink, return GST_FLOW_OK; } -int main(int argc, char** argv) { - rclcpp::init(argc, argv); - auto node = std::make_shared(); - rclcpp::spin(node); - rclcpp::shutdown(); - return 0; -} +} // namespace gstreamer_to_ros + +RCLCPP_COMPONENTS_REGISTER_NODE(gstreamer_to_ros::GStreamerToROS) diff --git a/image_to_gstreamer/CMakeLists.txt b/image_to_gstreamer/CMakeLists.txt index 72c7172..71f74c1 100644 --- a/image_to_gstreamer/CMakeLists.txt +++ b/image_to_gstreamer/CMakeLists.txt @@ -3,9 +3,9 @@ project(image_to_gstreamer) find_package(ament_cmake REQUIRED) find_package(rclcpp REQUIRED) +find_package(rclcpp_components REQUIRED) find_package(sensor_msgs REQUIRED) find_package(vortex_utils_ros REQUIRED) -find_package(vortex_msgs REQUIRED) find_package(PkgConfig REQUIRED) pkg_check_modules(GST REQUIRED @@ -13,35 +13,42 @@ pkg_check_modules(GST REQUIRED gstreamer-app-1.0 ) -add_executable(image_to_gstreamer_node +add_library(image_to_gstreamer_component SHARED src/image_to_gstreamer_node.cpp ) -target_include_directories(image_to_gstreamer_node PUBLIC +target_include_directories(image_to_gstreamer_component PUBLIC include ${GST_INCLUDE_DIRS} ) -target_link_libraries(image_to_gstreamer_node +target_link_libraries(image_to_gstreamer_component ${GST_LIBRARIES} ) -ament_target_dependencies(image_to_gstreamer_node +ament_target_dependencies(image_to_gstreamer_component rclcpp + rclcpp_components sensor_msgs vortex_utils_ros ) -install(TARGETS - image_to_gstreamer_node - DESTINATION lib/${PROJECT_NAME} +rclcpp_components_register_node(image_to_gstreamer_component + PLUGIN "image_to_gstreamer::ImageToGStreamer" + EXECUTABLE image_to_gstreamer_node +) + +install(TARGETS image_to_gstreamer_component + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION lib/${PROJECT_NAME} ) install(DIRECTORY include/ DESTINATION include/ ) -install(DIRECTORY config +install(DIRECTORY config launch DESTINATION share/${PROJECT_NAME} ) diff --git a/image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp b/image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp index 0c93f01..41264e4 100644 --- a/image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp +++ b/image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp @@ -9,9 +9,11 @@ #include #include +namespace image_to_gstreamer { + class ImageToGStreamer : public rclcpp::Node { public: - ImageToGStreamer(); + explicit ImageToGStreamer(const rclcpp::NodeOptions& options); ~ImageToGStreamer(); private: @@ -27,7 +29,7 @@ class ImageToGStreamer : public rclcpp::Node { int pt_; int config_interval_; int framerate_; - std::string format; + std::string format_; bool hw_encoder_; GstElement* pipeline_; @@ -40,4 +42,6 @@ class ImageToGStreamer : public rclcpp::Node { int port_; }; +} // namespace image_to_gstreamer + #endif // IMAGE_TO_GSTREAMER__IMAGE_TO_GSTREAMER_HPP_ diff --git a/image_to_gstreamer/launch/image_to_gstreamer.launch.py b/image_to_gstreamer/launch/image_to_gstreamer.launch.py index 0749ece..4ec39ce 100644 --- a/image_to_gstreamer/launch/image_to_gstreamer.launch.py +++ b/image_to_gstreamer/launch/image_to_gstreamer.launch.py @@ -1,25 +1,52 @@ -import os - -from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription -from launch_ros.actions import Node +from launch.actions import DeclareLaunchArgument, OpaqueFunction +from launch.substitutions import LaunchConfiguration +from launch_ros.actions import ComposableNodeContainer +from launch_ros.descriptions import ComposableNode -def generate_launch_description(): - config = os.path.join( - get_package_share_directory('image_to_gstreamer'), - 'config', - 'stream.yaml', - ) +def _launch_setup(context, *args, **kwargs): + use_nvidia = LaunchConfiguration('use_nvidia').perform(context).lower() == 'true' - return LaunchDescription( - [ - Node( + container = ComposableNodeContainer( + name='image_to_gstreamer_container', + namespace='', + package='rclcpp_components', + executable='component_container_mt', + composable_node_descriptions=[ + ComposableNode( package='image_to_gstreamer', - executable='image_to_gstreamer_node', + plugin='image_to_gstreamer::ImageToGStreamer', name='image_to_gstreamer_node', - parameters=[config], - output='screen', + parameters=[{ + 'input_topic': '/zed_node/left/image_rect_color', + 'host': '10.0.0.154', + 'port': 5001, + 'framerate': 15, + 'bitrate': 500000, + 'preset_level': 1, + 'iframe_interval': 15, + 'control_rate': 1, + 'pt': 96, + 'config_interval': 1, + 'format': 'RGB', + 'hw_encoder': use_nvidia, + }], ), - ] + ], + output='screen', ) + + return [container] + + +def generate_launch_description(): + return LaunchDescription([ + DeclareLaunchArgument( + 'use_nvidia', + default_value='true', + description='Use NVIDIA hardware H.265 encoder (nvv4l2h265enc). ' + 'Set false to use software x265enc.', + ), + OpaqueFunction(function=_launch_setup), + ]) diff --git a/image_to_gstreamer/package.xml b/image_to_gstreamer/package.xml index 93659b9..7d51205 100644 --- a/image_to_gstreamer/package.xml +++ b/image_to_gstreamer/package.xml @@ -10,6 +10,7 @@ ament_cmake rclcpp + rclcpp_components sensor_msgs vortex_utils_ros diff --git a/image_to_gstreamer/src/image_to_gstreamer_node.cpp b/image_to_gstreamer/src/image_to_gstreamer_node.cpp index 576cc10..4d8389a 100644 --- a/image_to_gstreamer/src/image_to_gstreamer_node.cpp +++ b/image_to_gstreamer/src/image_to_gstreamer_node.cpp @@ -1,41 +1,39 @@ -#include -#include - -#include -#include #include "image_to_gstreamer/image_to_gstreamer.hpp" + +#include #include -ImageToGStreamer::ImageToGStreamer() - : Node("image_to_gstreamer_node"), +namespace image_to_gstreamer { + +ImageToGStreamer::ImageToGStreamer(const rclcpp::NodeOptions& options) + : Node("image_to_gstreamer_node", options), pipeline_(nullptr), appsrc_(nullptr), pipeline_started_(false) { gst_init(nullptr, nullptr); - // Correct member initialization - input_topic_ = this->declare_parameter("input_topic", ""); - host_ = this->declare_parameter("host", ""); - port_ = this->declare_parameter("port", 5000); // must match receiver + input_topic_ = declare_parameter("input_topic", ""); + host_ = declare_parameter("host", ""); + port_ = declare_parameter("port", 5000); + bitrate_ = declare_parameter("bitrate", 500000); + preset_level_ = declare_parameter("preset_level", 1); + iframe_interval_ = declare_parameter("iframe_interval", 15); + control_rate_ = declare_parameter("control_rate", 1); + pt_ = declare_parameter("pt", 96); + config_interval_ = declare_parameter("config_interval", 1); + framerate_ = declare_parameter("framerate", 15); + format_ = declare_parameter("format", "RGB"); + hw_encoder_ = declare_parameter("hw_encoder", true); sub_ = create_subscription( input_topic_, vortex::utils::qos_profiles::sensor_data_profile(1), std::bind(&ImageToGStreamer::imageCb, this, std::placeholders::_1)); timer_ = create_wall_timer(std::chrono::seconds(5), [this]() { - RCLCPP_INFO(this->get_logger(), "Waiting for images on topic: '%s'", + RCLCPP_INFO(get_logger(), "Waiting for images on topic: '%s'", input_topic_.c_str()); }); - bitrate_ = this->declare_parameter("bitrate", 500000); // bitrate bit - preset_level_ = this->declare_parameter("preset_level", 1); // 1-4 1 = low latancy low quality 4 = high latency high qulity - iframe_interval_ = this->declare_parameter("iframe_interval", 15); // how many control framse is sent (full frames) - control_rate_ = this->declare_parameter("control_rate", 1); // 0 = fixed bitrate , 1 = variabel with target, 2 = constant QP, 3 = variabel with celing - pt_ = this->declare_parameter("pt", 96); // RTP Payload Type number defaut value - config_interval_ = this->declare_parameter("config_interval", 1); // keep at 1 - framerate_ = this->declare_parameter("framerate", 15); // you know what framrate is - format = this->declare_parameter("format", "RGB8"); - create_pipeline(); } @@ -48,42 +46,61 @@ ImageToGStreamer::~ImageToGStreamer() { void ImageToGStreamer::create_pipeline() { pipeline_ = gst_pipeline_new("ros2-h265-pipeline"); - appsrc_ = gst_element_factory_make("appsrc", "source"); GstElement* convert = gst_element_factory_make("videoconvert", "convert"); - GstElement* nvconv = gst_element_factory_make("nvvidconv", "nvconv"); - GstElement* encoder = gst_element_factory_make("nvv4l2h265enc", "encoder"); GstElement* parser = gst_element_factory_make("h265parse", "parser"); GstElement* pay = gst_element_factory_make("rtph265pay", "pay"); GstElement* sink = gst_element_factory_make("udpsink", "sink"); - if (!appsrc_ || !convert || !nvconv || !encoder || !parser || !pay || - !sink || !pipeline_) { + GstElement* encoder = nullptr; + GstElement* nvconv = nullptr; + + if (hw_encoder_) { + nvconv = gst_element_factory_make("nvvidconv", "nvconv"); + encoder = gst_element_factory_make("nvv4l2h265enc", "encoder"); + } else { + encoder = gst_element_factory_make("x265enc", "encoder"); + } + + bool elements_ok = appsrc_ && convert && encoder && parser && pay && sink && pipeline_; + if (hw_encoder_) elements_ok = elements_ok && nvconv; + + if (!elements_ok) { RCLCPP_FATAL(get_logger(), "Failed to create GStreamer elements"); return; } - // Encoder properties for low-latency - //configurs h265 encoder - g_object_set(encoder, "bitrate", bitrate_, "preset-level", preset_level_, - "iframeinterval", iframe_interval_, "control-rate", - control_rate_, NULL); - - //configures packaging - g_object_set(pay, "config-interval", config_interval_, "pt", pt_, NULL); - - //configures where to send it - g_object_set(sink, "host", host_.c_str(), "port", port_, "sync", FALSE, - NULL); - - gst_bin_add_many(GST_BIN(pipeline_), appsrc_, convert, nvconv, encoder, - parser, pay, sink, NULL); + if (hw_encoder_) { + g_object_set(encoder, "bitrate", bitrate_, "preset-level", preset_level_, + "iframeinterval", iframe_interval_, "control-rate", control_rate_, NULL); + } else { + // x265enc bitrate is in kbits/sec; key-int-max is the I-frame interval + g_object_set(encoder, "bitrate", bitrate_ / 1000, + "key-int-max", iframe_interval_, + "speed-preset", 0, // ultrafast — minimise latency + NULL); + } - if (!gst_element_link_many(appsrc_, convert, nvconv, encoder, parser, pay, - sink, NULL)) { - RCLCPP_FATAL(get_logger(), "Pipeline linking failed"); - return; + g_object_set(pay, "config-interval", config_interval_, "pt", pt_, NULL); + g_object_set(sink, "host", host_.c_str(), "port", port_, "sync", FALSE, NULL); + + if (hw_encoder_) { + gst_bin_add_many(GST_BIN(pipeline_), appsrc_, convert, nvconv, encoder, + parser, pay, sink, NULL); + if (!gst_element_link_many(appsrc_, convert, nvconv, encoder, parser, pay, sink, NULL)) { + RCLCPP_FATAL(get_logger(), "Pipeline linking failed"); + return; + } + } else { + gst_bin_add_many(GST_BIN(pipeline_), appsrc_, convert, encoder, parser, pay, sink, NULL); + if (!gst_element_link_many(appsrc_, convert, encoder, parser, pay, sink, NULL)) { + RCLCPP_FATAL(get_logger(), "Pipeline linking failed"); + return; + } } + + RCLCPP_INFO(get_logger(), "GStreamer H.265 pipeline created (%s encoder)", + hw_encoder_ ? "NVIDIA hw" : "x265 sw"); } void ImageToGStreamer::imageCb(const sensor_msgs::msg::Image::SharedPtr msg) { @@ -92,9 +109,10 @@ void ImageToGStreamer::imageCb(const sensor_msgs::msg::Image::SharedPtr msg) { if (!pipeline_started_) { GstCaps* caps = gst_caps_new_simple( - "video/x-raw", "format", G_TYPE_STRING, format.c_str(), "width", G_TYPE_INT, - msg->width, "height", G_TYPE_INT, msg->height, "framerate", - GST_TYPE_FRACTION, framerate_, 1, NULL); + "video/x-raw", "format", G_TYPE_STRING, format_.c_str(), + "width", G_TYPE_INT, msg->width, + "height", G_TYPE_INT, msg->height, + "framerate", GST_TYPE_FRACTION, framerate_, 1, NULL); g_object_set(appsrc_, "caps", caps, "format", GST_FORMAT_TIME, "is-live", TRUE, "do-timestamp", TRUE, NULL); @@ -104,11 +122,11 @@ void ImageToGStreamer::imageCb(const sensor_msgs::msg::Image::SharedPtr msg) { pipeline_started_ = true; timer_->cancel(); - RCLCPP_INFO(get_logger(), "H.265 GPU pipeline started"); + RCLCPP_INFO(get_logger(), "H.265 pipeline started (%s)", + hw_encoder_ ? "NVIDIA hw encoder" : "x265 sw encoder"); } - GstBuffer* buffer = - gst_buffer_new_allocate(nullptr, msg->data.size(), nullptr); + GstBuffer* buffer = gst_buffer_new_allocate(nullptr, msg->data.size(), nullptr); gst_buffer_fill(buffer, 0, msg->data.data(), msg->data.size()); GstFlowReturn ret; @@ -118,13 +136,9 @@ void ImageToGStreamer::imageCb(const sensor_msgs::msg::Image::SharedPtr msg) { if (ret != GST_FLOW_OK) RCLCPP_WARN(get_logger(), "Failed to push buffer"); else - RCLCPP_DEBUG(get_logger(), "Pushed frame #%zu into GStreamer", - frame_count); + RCLCPP_DEBUG(get_logger(), "Pushed frame #%zu into GStreamer", frame_count); } -int main(int argc, char** argv) { - rclcpp::init(argc, argv); - rclcpp::spin(std::make_shared()); - rclcpp::shutdown(); - return 0; -} +} // namespace image_to_gstreamer + +RCLCPP_COMPONENTS_REGISTER_NODE(image_to_gstreamer::ImageToGStreamer) From dfc95e9a23bf2320f9e2c8503e19df5fe1d524de Mon Sep 17 00:00:00 2001 From: Jorgen Fjermedal Date: Wed, 13 May 2026 09:25:26 +0200 Subject: [PATCH 11/13] rename packages --- README.md | 3 ++- .../CMakeLists.txt | 22 ++++++++-------- .../config/gstreamer_from_ros.yaml | 6 ++--- .../gstreamer_from_ros/gstreamer_from_ros.hpp | 16 ++++++------ .../launch/gstreamer_from_ros.launch.py | 12 ++++----- .../package.xml | 6 ++--- .../src/gstreamer_from_ros_node.cpp | 25 ++++++++++--------- .../CMakeLists.txt | 6 ++--- .../config/gstreamer_to_ros.yaml | 2 +- .../gstreamer_to_ros/gstreamer_to_ros.hpp | 0 .../launch/gstreamer_to_ros.launch.py | 8 +++--- .../package.xml | 2 +- .../src/gstreamer_to_ros_node.cpp | 4 +-- 13 files changed, 55 insertions(+), 57 deletions(-) rename {image_to_gstreamer => gstreamer_from_ros}/CMakeLists.txt (54%) rename image_to_gstreamer/config/stream.yaml => gstreamer_from_ros/config/gstreamer_from_ros.yaml (65%) rename image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp => gstreamer_from_ros/include/gstreamer_from_ros/gstreamer_from_ros.hpp (66%) rename image_to_gstreamer/launch/image_to_gstreamer.launch.py => gstreamer_from_ros/launch/gstreamer_from_ros.launch.py (81%) rename {image_to_gstreamer => gstreamer_from_ros}/package.xml (87%) rename image_to_gstreamer/src/image_to_gstreamer_node.cpp => gstreamer_from_ros/src/gstreamer_from_ros_node.cpp (88%) rename {gstreamer_to_ROS => gstreamer_to_ros}/CMakeLists.txt (91%) rename gstreamer_to_ROS/config/gstreamer_to_ROS.yaml => gstreamer_to_ros/config/gstreamer_to_ros.yaml (87%) rename gstreamer_to_ROS/include/gstreamer_to_ROS/gstreamer_to_ROS.hpp => gstreamer_to_ros/include/gstreamer_to_ros/gstreamer_to_ros.hpp (100%) rename gstreamer_to_ROS/launch/gstreamer_to_ROS_node.launch.py => gstreamer_to_ros/launch/gstreamer_to_ros.launch.py (85%) rename {gstreamer_to_ROS => gstreamer_to_ros}/package.xml (96%) rename gstreamer_to_ROS/src/gstreamer_to_ROS_node.cpp => gstreamer_to_ros/src/gstreamer_to_ros_node.cpp (97%) diff --git a/README.md b/README.md index 41b0230..72a85e1 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,9 @@ Repository to move image to gstreamer ``` -# Setup +# Dependencies ``` +sudo apt install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev ``` ## launch variables diff --git a/image_to_gstreamer/CMakeLists.txt b/gstreamer_from_ros/CMakeLists.txt similarity index 54% rename from image_to_gstreamer/CMakeLists.txt rename to gstreamer_from_ros/CMakeLists.txt index 71f74c1..85f76a0 100644 --- a/image_to_gstreamer/CMakeLists.txt +++ b/gstreamer_from_ros/CMakeLists.txt @@ -1,11 +1,10 @@ cmake_minimum_required(VERSION 3.8) -project(image_to_gstreamer) +project(gstreamer_from_ros) find_package(ament_cmake REQUIRED) find_package(rclcpp REQUIRED) find_package(rclcpp_components REQUIRED) find_package(sensor_msgs REQUIRED) -find_package(vortex_utils_ros REQUIRED) find_package(PkgConfig REQUIRED) pkg_check_modules(GST REQUIRED @@ -13,32 +12,31 @@ pkg_check_modules(GST REQUIRED gstreamer-app-1.0 ) -add_library(image_to_gstreamer_component SHARED - src/image_to_gstreamer_node.cpp +add_library(gstreamer_from_ros_component SHARED + src/gstreamer_from_ros_node.cpp ) -target_include_directories(image_to_gstreamer_component PUBLIC +target_include_directories(gstreamer_from_ros_component PUBLIC include ${GST_INCLUDE_DIRS} ) -target_link_libraries(image_to_gstreamer_component +target_link_libraries(gstreamer_from_ros_component ${GST_LIBRARIES} ) -ament_target_dependencies(image_to_gstreamer_component +ament_target_dependencies(gstreamer_from_ros_component rclcpp rclcpp_components sensor_msgs - vortex_utils_ros ) -rclcpp_components_register_node(image_to_gstreamer_component - PLUGIN "image_to_gstreamer::ImageToGStreamer" - EXECUTABLE image_to_gstreamer_node +rclcpp_components_register_node(gstreamer_from_ros_component + PLUGIN "gstreamer_from_ros::GStreamerFromRos" + EXECUTABLE gstreamer_from_ros_node ) -install(TARGETS image_to_gstreamer_component +install(TARGETS gstreamer_from_ros_component ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION lib/${PROJECT_NAME} diff --git a/image_to_gstreamer/config/stream.yaml b/gstreamer_from_ros/config/gstreamer_from_ros.yaml similarity index 65% rename from image_to_gstreamer/config/stream.yaml rename to gstreamer_from_ros/config/gstreamer_from_ros.yaml index 1d41673..767e557 100644 --- a/image_to_gstreamer/config/stream.yaml +++ b/gstreamer_from_ros/config/gstreamer_from_ros.yaml @@ -1,7 +1,7 @@ -image_to_gstreamer_node: +/**: ros__parameters: - input_topic: "/zed_node/left/image_rect_color" - host: "10.0.0.154" + input_topic: "/blackfly_s/image_raw" + host: "127.0.0.1" port: 5001 framerate: 15 diff --git a/image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp b/gstreamer_from_ros/include/gstreamer_from_ros/gstreamer_from_ros.hpp similarity index 66% rename from image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp rename to gstreamer_from_ros/include/gstreamer_from_ros/gstreamer_from_ros.hpp index 41264e4..2eafe67 100644 --- a/image_to_gstreamer/include/image_to_gstreamer/image_to_gstreamer.hpp +++ b/gstreamer_from_ros/include/gstreamer_from_ros/gstreamer_from_ros.hpp @@ -1,5 +1,5 @@ -#ifndef IMAGE_TO_GSTREAMER__IMAGE_TO_GSTREAMER_HPP_ -#define IMAGE_TO_GSTREAMER__IMAGE_TO_GSTREAMER_HPP_ +#ifndef GSTREAMER_FROM_ROS__GSTREAMER_FROM_ROS_HPP_ +#define GSTREAMER_FROM_ROS__GSTREAMER_FROM_ROS_HPP_ #include @@ -9,12 +9,12 @@ #include #include -namespace image_to_gstreamer { +namespace gstreamer_from_ros { -class ImageToGStreamer : public rclcpp::Node { +class GStreamerFromRos : public rclcpp::Node { public: - explicit ImageToGStreamer(const rclcpp::NodeOptions& options); - ~ImageToGStreamer(); + explicit GStreamerFromRos(const rclcpp::NodeOptions& options); + ~GStreamerFromRos(); private: void create_pipeline(); @@ -42,6 +42,6 @@ class ImageToGStreamer : public rclcpp::Node { int port_; }; -} // namespace image_to_gstreamer +} // namespace gstreamer_from_ros -#endif // IMAGE_TO_GSTREAMER__IMAGE_TO_GSTREAMER_HPP_ +#endif // GSTREAMER_FROM_ROS__GSTREAMER_FROM_ROS_HPP_ diff --git a/image_to_gstreamer/launch/image_to_gstreamer.launch.py b/gstreamer_from_ros/launch/gstreamer_from_ros.launch.py similarity index 81% rename from image_to_gstreamer/launch/image_to_gstreamer.launch.py rename to gstreamer_from_ros/launch/gstreamer_from_ros.launch.py index 4ec39ce..358fb84 100644 --- a/image_to_gstreamer/launch/image_to_gstreamer.launch.py +++ b/gstreamer_from_ros/launch/gstreamer_from_ros.launch.py @@ -6,18 +6,18 @@ def _launch_setup(context, *args, **kwargs): - use_nvidia = LaunchConfiguration('use_nvidia').perform(context).lower() == 'true' + use_nvidia = LaunchConfiguration('gst_nvidia_encoder').perform(context).lower() == 'true' container = ComposableNodeContainer( - name='image_to_gstreamer_container', + name='gstreamer_from_ros_container', namespace='', package='rclcpp_components', executable='component_container_mt', composable_node_descriptions=[ ComposableNode( - package='image_to_gstreamer', - plugin='image_to_gstreamer::ImageToGStreamer', - name='image_to_gstreamer_node', + package='gstreamer_from_ros', + plugin='gstreamer_from_ros::GStreamerFromRos', + name='gstreamer_from_ros_node', parameters=[{ 'input_topic': '/zed_node/left/image_rect_color', 'host': '10.0.0.154', @@ -43,7 +43,7 @@ def _launch_setup(context, *args, **kwargs): def generate_launch_description(): return LaunchDescription([ DeclareLaunchArgument( - 'use_nvidia', + 'gst_nvidia_encoder', default_value='true', description='Use NVIDIA hardware H.265 encoder (nvv4l2h265enc). ' 'Set false to use software x265enc.', diff --git a/image_to_gstreamer/package.xml b/gstreamer_from_ros/package.xml similarity index 87% rename from image_to_gstreamer/package.xml rename to gstreamer_from_ros/package.xml index 7d51205..9c1c035 100644 --- a/image_to_gstreamer/package.xml +++ b/gstreamer_from_ros/package.xml @@ -1,6 +1,6 @@ - image_to_gstreamer + gstreamer_from_ros 0.1.0 takes image form ROS node and sends it via gstreamer @@ -11,9 +11,7 @@ rclcpp rclcpp_components - sensor_msgs - vortex_utils_ros - + sensor_msgs pkg-config diff --git a/image_to_gstreamer/src/image_to_gstreamer_node.cpp b/gstreamer_from_ros/src/gstreamer_from_ros_node.cpp similarity index 88% rename from image_to_gstreamer/src/image_to_gstreamer_node.cpp rename to gstreamer_from_ros/src/gstreamer_from_ros_node.cpp index 4d8389a..e4c9bc6 100644 --- a/image_to_gstreamer/src/image_to_gstreamer_node.cpp +++ b/gstreamer_from_ros/src/gstreamer_from_ros_node.cpp @@ -1,12 +1,11 @@ -#include "image_to_gstreamer/image_to_gstreamer.hpp" +#include "gstreamer_from_ros/gstreamer_from_ros.hpp" #include -#include -namespace image_to_gstreamer { +namespace gstreamer_from_ros { -ImageToGStreamer::ImageToGStreamer(const rclcpp::NodeOptions& options) - : Node("image_to_gstreamer_node", options), +GStreamerFromRos::GStreamerFromRos(const rclcpp::NodeOptions& options) + : Node("gstreamer_from_ros_node", options), pipeline_(nullptr), appsrc_(nullptr), pipeline_started_(false) { @@ -25,9 +24,11 @@ ImageToGStreamer::ImageToGStreamer(const rclcpp::NodeOptions& options) format_ = declare_parameter("format", "RGB"); hw_encoder_ = declare_parameter("hw_encoder", true); + auto qos = rclcpp::QoS(rclcpp::KeepLast(3)).best_effort().durability_volatile(); + sub_ = create_subscription( - input_topic_, vortex::utils::qos_profiles::sensor_data_profile(1), - std::bind(&ImageToGStreamer::imageCb, this, std::placeholders::_1)); + input_topic_, qos, + std::bind(&GStreamerFromRos::imageCb, this, std::placeholders::_1)); timer_ = create_wall_timer(std::chrono::seconds(5), [this]() { RCLCPP_INFO(get_logger(), "Waiting for images on topic: '%s'", @@ -37,14 +38,14 @@ ImageToGStreamer::ImageToGStreamer(const rclcpp::NodeOptions& options) create_pipeline(); } -ImageToGStreamer::~ImageToGStreamer() { +GStreamerFromRos::~GStreamerFromRos() { if (pipeline_) { gst_element_set_state(pipeline_, GST_STATE_NULL); gst_object_unref(pipeline_); } } -void ImageToGStreamer::create_pipeline() { +void GStreamerFromRos::create_pipeline() { pipeline_ = gst_pipeline_new("ros2-h265-pipeline"); appsrc_ = gst_element_factory_make("appsrc", "source"); GstElement* convert = gst_element_factory_make("videoconvert", "convert"); @@ -103,7 +104,7 @@ void ImageToGStreamer::create_pipeline() { hw_encoder_ ? "NVIDIA hw" : "x265 sw"); } -void ImageToGStreamer::imageCb(const sensor_msgs::msg::Image::SharedPtr msg) { +void GStreamerFromRos::imageCb(const sensor_msgs::msg::Image::SharedPtr msg) { static size_t frame_count = 0; frame_count++; @@ -139,6 +140,6 @@ void ImageToGStreamer::imageCb(const sensor_msgs::msg::Image::SharedPtr msg) { RCLCPP_DEBUG(get_logger(), "Pushed frame #%zu into GStreamer", frame_count); } -} // namespace image_to_gstreamer +} // namespace gstreamer_from_ros -RCLCPP_COMPONENTS_REGISTER_NODE(image_to_gstreamer::ImageToGStreamer) +RCLCPP_COMPONENTS_REGISTER_NODE(gstreamer_from_ros::GStreamerFromRos) diff --git a/gstreamer_to_ROS/CMakeLists.txt b/gstreamer_to_ros/CMakeLists.txt similarity index 91% rename from gstreamer_to_ROS/CMakeLists.txt rename to gstreamer_to_ros/CMakeLists.txt index c2b3453..e93453b 100644 --- a/gstreamer_to_ROS/CMakeLists.txt +++ b/gstreamer_to_ros/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.8) -project(gstreamer_to_ROS) +project(gstreamer_to_ros) find_package(ament_cmake REQUIRED) find_package(rclcpp REQUIRED) @@ -13,7 +13,7 @@ pkg_check_modules(GST REQUIRED ) add_library(gstreamer_to_ros_component SHARED - src/gstreamer_to_ROS_node.cpp + src/gstreamer_to_ros_node.cpp ) target_include_directories(gstreamer_to_ros_component PUBLIC @@ -33,7 +33,7 @@ ament_target_dependencies(gstreamer_to_ros_component rclcpp_components_register_node(gstreamer_to_ros_component PLUGIN "gstreamer_to_ros::GStreamerToROS" - EXECUTABLE gstreamer_to_ROS_node + EXECUTABLE gstreamer_to_ros_node ) install(TARGETS gstreamer_to_ros_component diff --git a/gstreamer_to_ROS/config/gstreamer_to_ROS.yaml b/gstreamer_to_ros/config/gstreamer_to_ros.yaml similarity index 87% rename from gstreamer_to_ROS/config/gstreamer_to_ROS.yaml rename to gstreamer_to_ros/config/gstreamer_to_ros.yaml index a705c32..be09a4f 100644 --- a/gstreamer_to_ROS/config/gstreamer_to_ROS.yaml +++ b/gstreamer_to_ros/config/gstreamer_to_ros.yaml @@ -1,4 +1,4 @@ -gstreamer_to_ROS_node: +/**: ros__parameters: host: "0.0.0.0" # Listen on all interfaces port: 5001 # Must match sender diff --git a/gstreamer_to_ROS/include/gstreamer_to_ROS/gstreamer_to_ROS.hpp b/gstreamer_to_ros/include/gstreamer_to_ros/gstreamer_to_ros.hpp similarity index 100% rename from gstreamer_to_ROS/include/gstreamer_to_ROS/gstreamer_to_ROS.hpp rename to gstreamer_to_ros/include/gstreamer_to_ros/gstreamer_to_ros.hpp diff --git a/gstreamer_to_ROS/launch/gstreamer_to_ROS_node.launch.py b/gstreamer_to_ros/launch/gstreamer_to_ros.launch.py similarity index 85% rename from gstreamer_to_ROS/launch/gstreamer_to_ROS_node.launch.py rename to gstreamer_to_ros/launch/gstreamer_to_ros.launch.py index 3650953..b8d66ac 100644 --- a/gstreamer_to_ROS/launch/gstreamer_to_ROS_node.launch.py +++ b/gstreamer_to_ros/launch/gstreamer_to_ros.launch.py @@ -6,7 +6,7 @@ def _launch_setup(context, *args, **kwargs): - use_nvidia = LaunchConfiguration('use_nvidia').perform(context).lower() == 'true' + use_nvidia = LaunchConfiguration('gst_nvidia_encoder').perform(context).lower() == 'true' container = ComposableNodeContainer( name='gstreamer_to_ros_container', @@ -15,9 +15,9 @@ def _launch_setup(context, *args, **kwargs): executable='component_container_mt', composable_node_descriptions=[ ComposableNode( - package='gstreamer_to_ROS', + package='gstreamer_to_ros', plugin='gstreamer_to_ros::GStreamerToROS', - name='gstreamer_to_ROS_node', + name='gstreamer_to_ros_node', parameters=[{ 'host': '0.0.0.0', 'port': 5001, @@ -35,7 +35,7 @@ def _launch_setup(context, *args, **kwargs): def generate_launch_description(): return LaunchDescription([ DeclareLaunchArgument( - 'use_nvidia', + 'gst_nvidia_encoder', default_value='true', description='Use NVIDIA hardware H.265 decoder (nvh265dec). ' 'Set false to use software avdec_h265.', diff --git a/gstreamer_to_ROS/package.xml b/gstreamer_to_ros/package.xml similarity index 96% rename from gstreamer_to_ROS/package.xml rename to gstreamer_to_ros/package.xml index 217975a..2f2ff45 100644 --- a/gstreamer_to_ROS/package.xml +++ b/gstreamer_to_ros/package.xml @@ -1,6 +1,6 @@ - gstreamer_to_ROS + gstreamer_to_ros 0.1.0 ROS 2 node that fits a line over a white segmented mask using IRLS (Huber/Tukey) and OpenCV. diff --git a/gstreamer_to_ROS/src/gstreamer_to_ROS_node.cpp b/gstreamer_to_ros/src/gstreamer_to_ros_node.cpp similarity index 97% rename from gstreamer_to_ROS/src/gstreamer_to_ROS_node.cpp rename to gstreamer_to_ros/src/gstreamer_to_ros_node.cpp index 1a35349..89bfda9 100644 --- a/gstreamer_to_ROS/src/gstreamer_to_ROS_node.cpp +++ b/gstreamer_to_ros/src/gstreamer_to_ros_node.cpp @@ -1,11 +1,11 @@ -#include "gstreamer_to_ROS/gstreamer_to_ROS.hpp" +#include "gstreamer_to_ros/gstreamer_to_ros.hpp" #include namespace gstreamer_to_ros { GStreamerToROS::GStreamerToROS(const rclcpp::NodeOptions& options) - : Node("gstreamer_to_ROS_node", options), pipeline_(nullptr), appsink_(nullptr) { + : Node("gstreamer_to_ros_node", options), pipeline_(nullptr), appsink_(nullptr) { gst_init(nullptr, nullptr); host_ = declare_parameter("host", "0.0.0.0"); From d6d4d7929a75b47393d7cfaef9e40ad6967ad171 Mon Sep 17 00:00:00 2001 From: Jorgen Fjermedal Date: Wed, 13 May 2026 13:06:05 +0200 Subject: [PATCH 12/13] make params more descriptive --- gstreamer_from_ros/config/gstreamer_from_ros.yaml | 9 ++++----- .../gstreamer_from_ros/gstreamer_from_ros.hpp | 8 ++++---- .../launch/gstreamer_from_ros.launch.py | 8 ++++---- gstreamer_from_ros/src/gstreamer_from_ros_node.cpp | 14 +++++++------- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/gstreamer_from_ros/config/gstreamer_from_ros.yaml b/gstreamer_from_ros/config/gstreamer_from_ros.yaml index 767e557..88a8847 100644 --- a/gstreamer_from_ros/config/gstreamer_from_ros.yaml +++ b/gstreamer_from_ros/config/gstreamer_from_ros.yaml @@ -1,15 +1,14 @@ /**: ros__parameters: input_topic: "/blackfly_s/image_raw" - host: "127.0.0.1" - port: 5001 + destination_ip: "127.0.0.1" + destination_port: 5001 - framerate: 15 + expected_input_fps: 15 bitrate: 500000 preset_level: 1 iframe_interval: 15 control_rate: 1 pt: 96 config_interval: 1 - format: "RGB" - \ No newline at end of file + input_format: "RGB" diff --git a/gstreamer_from_ros/include/gstreamer_from_ros/gstreamer_from_ros.hpp b/gstreamer_from_ros/include/gstreamer_from_ros/gstreamer_from_ros.hpp index 2eafe67..51eeb91 100644 --- a/gstreamer_from_ros/include/gstreamer_from_ros/gstreamer_from_ros.hpp +++ b/gstreamer_from_ros/include/gstreamer_from_ros/gstreamer_from_ros.hpp @@ -28,8 +28,8 @@ class GStreamerFromRos : public rclcpp::Node { int control_rate_; int pt_; int config_interval_; - int framerate_; - std::string format_; + int expected_input_fps_; + std::string input_format_; bool hw_encoder_; GstElement* pipeline_; @@ -38,8 +38,8 @@ class GStreamerFromRos : public rclcpp::Node { bool pipeline_started_; std::string input_topic_; - std::string host_; - int port_; + std::string destination_ip_; + int destination_port_; }; } // namespace gstreamer_from_ros diff --git a/gstreamer_from_ros/launch/gstreamer_from_ros.launch.py b/gstreamer_from_ros/launch/gstreamer_from_ros.launch.py index 358fb84..120ef95 100644 --- a/gstreamer_from_ros/launch/gstreamer_from_ros.launch.py +++ b/gstreamer_from_ros/launch/gstreamer_from_ros.launch.py @@ -20,16 +20,16 @@ def _launch_setup(context, *args, **kwargs): name='gstreamer_from_ros_node', parameters=[{ 'input_topic': '/zed_node/left/image_rect_color', - 'host': '10.0.0.154', - 'port': 5001, - 'framerate': 15, + 'destination_ip': '10.0.0.154', + 'destination_port': 5001, + 'expected_input_fps': 15, 'bitrate': 500000, 'preset_level': 1, 'iframe_interval': 15, 'control_rate': 1, 'pt': 96, 'config_interval': 1, - 'format': 'RGB', + 'input_format': 'RGB', 'hw_encoder': use_nvidia, }], ), diff --git a/gstreamer_from_ros/src/gstreamer_from_ros_node.cpp b/gstreamer_from_ros/src/gstreamer_from_ros_node.cpp index e4c9bc6..f86901b 100644 --- a/gstreamer_from_ros/src/gstreamer_from_ros_node.cpp +++ b/gstreamer_from_ros/src/gstreamer_from_ros_node.cpp @@ -12,16 +12,16 @@ GStreamerFromRos::GStreamerFromRos(const rclcpp::NodeOptions& options) gst_init(nullptr, nullptr); input_topic_ = declare_parameter("input_topic", ""); - host_ = declare_parameter("host", ""); - port_ = declare_parameter("port", 5000); + destination_ip_ = declare_parameter("destination_ip", ""); + destination_port_ = declare_parameter("destination_port", 5000); bitrate_ = declare_parameter("bitrate", 500000); preset_level_ = declare_parameter("preset_level", 1); iframe_interval_ = declare_parameter("iframe_interval", 15); control_rate_ = declare_parameter("control_rate", 1); pt_ = declare_parameter("pt", 96); config_interval_ = declare_parameter("config_interval", 1); - framerate_ = declare_parameter("framerate", 15); - format_ = declare_parameter("format", "RGB"); + expected_input_fps_ = declare_parameter("expected_input_fps", 15); + input_format_ = declare_parameter("input_format", "RGB"); hw_encoder_ = declare_parameter("hw_encoder", true); auto qos = rclcpp::QoS(rclcpp::KeepLast(3)).best_effort().durability_volatile(); @@ -83,7 +83,7 @@ void GStreamerFromRos::create_pipeline() { } g_object_set(pay, "config-interval", config_interval_, "pt", pt_, NULL); - g_object_set(sink, "host", host_.c_str(), "port", port_, "sync", FALSE, NULL); + g_object_set(sink, "host", destination_ip_.c_str(), "port", destination_port_, "sync", FALSE, NULL); if (hw_encoder_) { gst_bin_add_many(GST_BIN(pipeline_), appsrc_, convert, nvconv, encoder, @@ -110,10 +110,10 @@ void GStreamerFromRos::imageCb(const sensor_msgs::msg::Image::SharedPtr msg) { if (!pipeline_started_) { GstCaps* caps = gst_caps_new_simple( - "video/x-raw", "format", G_TYPE_STRING, format_.c_str(), + "video/x-raw", "format", G_TYPE_STRING, input_format_.c_str(), "width", G_TYPE_INT, msg->width, "height", G_TYPE_INT, msg->height, - "framerate", GST_TYPE_FRACTION, framerate_, 1, NULL); + "framerate", GST_TYPE_FRACTION, expected_input_fps_, 1, NULL); g_object_set(appsrc_, "caps", caps, "format", GST_FORMAT_TIME, "is-live", TRUE, "do-timestamp", TRUE, NULL); From 7dd6045a4bb7947fcd04cde4e64f8bc31cc67af9 Mon Sep 17 00:00:00 2001 From: vortexuser Date: Fri, 15 May 2026 01:13:27 +0200 Subject: [PATCH 13/13] fix formatting in gstreamer launch command in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 72a85e1..82b602e 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ ros2 launch image_to_gstreamer image_to_gstreamer.launch.py host:=127.0.0.1 port ``` ## terminal launch for gstreamer ``` -gst-launch-1.0 udpsrc port=5001 caps="application/x-rtp,media=video,encoding-name=H265 payload=96 ! rtph265depay ! avdec_h265 ! videoconvert ! fpsdisplaysink +gst-launch-1.0 udpsrc port=5001 caps="application/x-rtp,media=video,encoding-name=H265,payload=96" ! rtph265depay ! avdec_h265 ! videoconvert ! fpsdisplaysink ``` ## Using pre-commit