-
Notifications
You must be signed in to change notification settings - Fork 0
made it work on orin #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
cbb510c
88da822
a122aa8
0b69444
047df8b
5ba8839
7e9305e
d59c625
a268b97
ab6891d
dfc95e9
d6d4d79
7dd6045
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| cmake_minimum_required(VERSION 3.8) | ||
| 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(PkgConfig REQUIRED) | ||
| pkg_check_modules(GST REQUIRED | ||
| gstreamer-1.0 | ||
| gstreamer-app-1.0 | ||
| ) | ||
|
|
||
| add_library(gstreamer_from_ros_component SHARED | ||
| src/gstreamer_from_ros_node.cpp | ||
| ) | ||
|
|
||
| target_include_directories(gstreamer_from_ros_component PUBLIC | ||
| include | ||
| ${GST_INCLUDE_DIRS} | ||
| ) | ||
|
|
||
| target_link_libraries(gstreamer_from_ros_component | ||
| ${GST_LIBRARIES} | ||
| ) | ||
|
|
||
| ament_target_dependencies(gstreamer_from_ros_component | ||
| rclcpp | ||
| rclcpp_components | ||
| sensor_msgs | ||
| ) | ||
|
|
||
| rclcpp_components_register_node(gstreamer_from_ros_component | ||
| PLUGIN "gstreamer_from_ros::GStreamerFromRos" | ||
| EXECUTABLE gstreamer_from_ros_node | ||
| ) | ||
|
|
||
| install(TARGETS gstreamer_from_ros_component | ||
| ARCHIVE DESTINATION lib | ||
| LIBRARY DESTINATION lib | ||
| RUNTIME DESTINATION lib/${PROJECT_NAME} | ||
| ) | ||
|
|
||
| install(DIRECTORY include/ | ||
| DESTINATION include/ | ||
| ) | ||
|
|
||
| install(DIRECTORY config launch | ||
| DESTINATION share/${PROJECT_NAME} | ||
| ) | ||
|
|
||
| ament_package() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| /**: | ||
| ros__parameters: | ||
| input_topic: "/blackfly_s/image_raw" | ||
| destination_ip: "127.0.0.1" | ||
| destination_port: 5001 | ||
|
|
||
| expected_input_fps: 15 | ||
| bitrate: 500000 | ||
| preset_level: 1 | ||
| iframe_interval: 15 | ||
| control_rate: 1 | ||
| pt: 96 | ||
| config_interval: 1 | ||
| input_format: "RGB" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| #ifndef GSTREAMER_FROM_ROS__GSTREAMER_FROM_ROS_HPP_ | ||
| #define GSTREAMER_FROM_ROS__GSTREAMER_FROM_ROS_HPP_ | ||
|
|
||
| #include <string> | ||
|
|
||
| #include <rclcpp/rclcpp.hpp> | ||
| #include <sensor_msgs/msg/image.hpp> | ||
|
|
||
| #include <gst/app/gstappsrc.h> | ||
| #include <gst/gst.h> | ||
|
|
||
| namespace gstreamer_from_ros { | ||
|
|
||
| class GStreamerFromRos : public rclcpp::Node { | ||
| public: | ||
| explicit GStreamerFromRos(const rclcpp::NodeOptions& options); | ||
| ~GStreamerFromRos(); | ||
|
|
||
| private: | ||
| void create_pipeline(); | ||
| void imageCb(const sensor_msgs::msg::Image::SharedPtr msg); | ||
|
|
||
| rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_; | ||
| rclcpp::TimerBase::SharedPtr timer_; | ||
| int bitrate_; | ||
| int preset_level_; | ||
| int iframe_interval_; | ||
| int control_rate_; | ||
| int pt_; | ||
| int config_interval_; | ||
| int expected_input_fps_; | ||
| std::string input_format_; | ||
| bool hw_encoder_; | ||
|
|
||
| GstElement* pipeline_; | ||
| GstElement* appsrc_; | ||
|
|
||
| bool pipeline_started_; | ||
|
|
||
| std::string input_topic_; | ||
| std::string destination_ip_; | ||
| int destination_port_; | ||
| }; | ||
|
|
||
| } // namespace gstreamer_from_ros | ||
|
|
||
| #endif // GSTREAMER_FROM_ROS__GSTREAMER_FROM_ROS_HPP_ |
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should also be able to configure the input topic from here on runtime (we want to launch multiple instances of this with different input topic) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| from launch import LaunchDescription | ||
| from launch.actions import DeclareLaunchArgument, OpaqueFunction | ||
| from launch.substitutions import LaunchConfiguration | ||
| from launch_ros.actions import ComposableNodeContainer | ||
| from launch_ros.descriptions import ComposableNode | ||
|
|
||
|
|
||
| def _launch_setup(context, *args, **kwargs): | ||
| use_nvidia = LaunchConfiguration('gst_nvidia_encoder').perform(context).lower() == 'true' | ||
|
|
||
| container = ComposableNodeContainer( | ||
| name='gstreamer_from_ros_container', | ||
| namespace='', | ||
| package='rclcpp_components', | ||
| executable='component_container_mt', | ||
| composable_node_descriptions=[ | ||
| ComposableNode( | ||
| package='gstreamer_from_ros', | ||
| plugin='gstreamer_from_ros::GStreamerFromRos', | ||
| name='gstreamer_from_ros_node', | ||
| parameters=[{ | ||
| 'input_topic': '/zed_node/left/image_rect_color', | ||
| '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, | ||
| 'input_format': 'RGB', | ||
| 'hw_encoder': use_nvidia, | ||
| }], | ||
| ), | ||
| ], | ||
| output='screen', | ||
| ) | ||
|
|
||
| return [container] | ||
|
|
||
|
|
||
| def generate_launch_description(): | ||
| return LaunchDescription([ | ||
| DeclareLaunchArgument( | ||
| 'gst_nvidia_encoder', | ||
| default_value='true', | ||
| description='Use NVIDIA hardware H.265 encoder (nvv4l2h265enc). ' | ||
| 'Set false to use software x265enc.', | ||
| ), | ||
| OpaqueFunction(function=_launch_setup), | ||
| ]) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| <?xml version="1.0"?> | ||
| <package format="3"> | ||
| <name>gstreamer_from_ros</name> | ||
| <version>0.1.0</version> | ||
| <description>takes image form ROS node and sends it via gstreamer</description> | ||
|
|
||
| <maintainer email="gardeg@github.com">Gard</maintainer> | ||
| <license>MIT</license> | ||
|
|
||
| <buildtool_depend>ament_cmake</buildtool_depend> | ||
|
|
||
| <depend>rclcpp</depend> | ||
| <depend>rclcpp_components</depend> | ||
| <depend>sensor_msgs</depend> | ||
|
|
||
|
|
||
| <build_depend>pkg-config</build_depend> | ||
| <exec_depend>pkg-config</exec_depend> | ||
|
|
||
| <build_depend>libgstreamer1.0-dev</build_depend> | ||
| <build_depend>libgstreamer-plugins-base1.0-dev</build_depend> | ||
| <exec_depend>libgstreamer1.0-dev</exec_depend> | ||
| <exec_depend>libgstreamer-plugins-base1.0-dev</exec_depend> | ||
|
|
||
| <export> | ||
| <build_type>ament_cmake</build_type> | ||
| </export> | ||
| </package> |
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a fan of having default value for IP, Port and input topic here, can lead to unexpeceted behavior |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| #include "gstreamer_from_ros/gstreamer_from_ros.hpp" | ||
|
|
||
| #include <rclcpp_components/register_node_macro.hpp> | ||
|
|
||
| namespace gstreamer_from_ros { | ||
|
|
||
| GStreamerFromRos::GStreamerFromRos(const rclcpp::NodeOptions& options) | ||
| : Node("gstreamer_from_ros_node", options), | ||
| pipeline_(nullptr), | ||
| appsrc_(nullptr), | ||
| pipeline_started_(false) { | ||
| gst_init(nullptr, nullptr); | ||
|
|
||
| input_topic_ = declare_parameter<std::string>("input_topic", ""); | ||
| destination_ip_ = declare_parameter<std::string>("destination_ip", ""); | ||
| destination_port_ = declare_parameter<int>("destination_port", 5000); | ||
| bitrate_ = declare_parameter<int>("bitrate", 500000); | ||
| preset_level_ = declare_parameter<int>("preset_level", 1); | ||
| iframe_interval_ = declare_parameter<int>("iframe_interval", 15); | ||
| control_rate_ = declare_parameter<int>("control_rate", 1); | ||
| pt_ = declare_parameter<int>("pt", 96); | ||
| config_interval_ = declare_parameter<int>("config_interval", 1); | ||
| expected_input_fps_ = declare_parameter<int>("expected_input_fps", 15); | ||
| input_format_ = declare_parameter<std::string>("input_format", "RGB"); | ||
| hw_encoder_ = declare_parameter<bool>("hw_encoder", true); | ||
|
|
||
| auto qos = rclcpp::QoS(rclcpp::KeepLast(3)).best_effort().durability_volatile(); | ||
|
|
||
| sub_ = create_subscription<sensor_msgs::msg::Image>( | ||
| 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'", | ||
| input_topic_.c_str()); | ||
| }); | ||
|
|
||
| create_pipeline(); | ||
| } | ||
|
|
||
| GStreamerFromRos::~GStreamerFromRos() { | ||
| if (pipeline_) { | ||
| gst_element_set_state(pipeline_, GST_STATE_NULL); | ||
| gst_object_unref(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"); | ||
| GstElement* parser = gst_element_factory_make("h265parse", "parser"); | ||
| GstElement* pay = gst_element_factory_make("rtph265pay", "pay"); | ||
| GstElement* sink = gst_element_factory_make("udpsink", "sink"); | ||
|
|
||
| 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; | ||
| } | ||
|
|
||
| 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); | ||
| } | ||
|
|
||
| g_object_set(pay, "config-interval", config_interval_, "pt", pt_, 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, | ||
| 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 GStreamerFromRos::imageCb(const sensor_msgs::msg::Image::SharedPtr msg) { | ||
| static size_t frame_count = 0; | ||
| frame_count++; | ||
|
|
||
| if (!pipeline_started_) { | ||
| GstCaps* caps = gst_caps_new_simple( | ||
| "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, expected_input_fps_, 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; | ||
| timer_->cancel(); | ||
|
|
||
| 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); | ||
| 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); | ||
| } | ||
|
|
||
| } // namespace gstreamer_from_ros | ||
|
|
||
| RCLCPP_COMPONENTS_REGISTER_NODE(gstreamer_from_ros::GStreamerFromRos) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💀