Compare commits
46 Commits
ros2_compo
...
master
| Author | SHA1 | Date |
|---|---|---|
|
|
732ad5b9a3 | |
|
|
7eda1b11f7 | |
|
|
2ba0f2e9f6 | |
|
|
723154e488 | |
|
|
8f84aa66b2 | |
|
|
8a37d6600e | |
|
|
712d5f8362 | |
|
|
c812ce9ada | |
|
|
047f42084c | |
|
|
9d7c6a10c3 | |
|
|
4904df248c | |
|
|
1cc4da5710 | |
|
|
e0e8fb180d | |
|
|
115b01c939 | |
|
|
9ac5a8483c | |
|
|
9fc0e830ac | |
|
|
760fb3f3e4 | |
|
|
9f37c03e69 | |
|
|
8ef1badf07 | |
|
|
5c0ab7c7a7 | |
|
|
689e65272e | |
|
|
bf1afde1a8 | |
|
|
4d5c2b4055 | |
|
|
6ac41e7c42 | |
|
|
c7ce1f4899 | |
|
|
4696974dd6 | |
|
|
107ba91256 | |
|
|
895053f69c | |
|
|
810a56c215 | |
|
|
72f73b2ced | |
|
|
a8f4a833e9 | |
|
|
39576d9ce3 | |
|
|
99ae6e8c5a | |
|
|
12f70a1b72 | |
|
|
cfa57e8298 | |
|
|
a8d8bb5572 | |
|
|
602606c3db | |
|
|
56b996397a | |
|
|
99bd6663b9 | |
|
|
1c40e092cc | |
|
|
10166b324f | |
|
|
c178eb97dd | |
|
|
48b8fcf1c0 | |
|
|
fd00c5a1fa | |
|
|
1ffce2b6bd | |
|
|
e68cfccc50 |
|
|
@ -0,0 +1,12 @@
|
|||
cff-version: 1.2.0
|
||||
preferred-citation:
|
||||
type: software
|
||||
message: If you use this software, please cite it as below.
|
||||
authors:
|
||||
- family-names: Maladzenkau
|
||||
given-names: Dzmitry
|
||||
title: "image2rtsp"
|
||||
version: 1.0
|
||||
date-released: 2023-27-11
|
||||
license: BSD 3-Clause
|
||||
url: "https://github.com/45kmh/image2rtsp"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_minimum_required(VERSION 4.2)
|
||||
project(image2rtsp)
|
||||
|
||||
# Default to C99
|
||||
|
|
@ -11,6 +11,9 @@ if(NOT CMAKE_CXX_STANDARD)
|
|||
set(CMAKE_CXX_STANDARD 14)
|
||||
endif()
|
||||
|
||||
# Suppress all warnings
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w")
|
||||
|
||||
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
add_compile_options(-Wall -Wextra -Wpedantic)
|
||||
endif()
|
||||
|
|
@ -18,66 +21,44 @@ endif()
|
|||
# find dependencies
|
||||
find_package(ament_cmake REQUIRED)
|
||||
find_package(rclcpp REQUIRED)
|
||||
find_package(rclcpp_components REQUIRED)
|
||||
find_package(sensor_msgs REQUIRED)
|
||||
find_package(rcutils REQUIRED)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
find_package(OpenCV REQUIRED)
|
||||
pkg_check_modules(GST REQUIRED
|
||||
gstreamer-1.0
|
||||
gstreamer-app-1.0
|
||||
gstreamer-rtsp-server-1.0
|
||||
)
|
||||
include_directories(${OpenCV_INCLUDE_DIRS})
|
||||
|
||||
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
||||
file(GLOB SOURCES src/video.cpp)
|
||||
|
||||
add_executable(image2rtsp src/image2rtsp.cpp ${SOURCES})
|
||||
target_link_libraries(image2rtsp
|
||||
rclcpp::rclcpp
|
||||
${sensor_msgs_TARGETS}
|
||||
)
|
||||
|
||||
include_directories(
|
||||
include
|
||||
${GST_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
# create ament index resource which references the libraries in the binary dir
|
||||
set(node_plugins "")
|
||||
|
||||
add_library(image2rtsp SHARED
|
||||
src/image2rtsp.cpp src/video.cpp)
|
||||
|
||||
ament_target_dependencies(image2rtsp rclcpp sensor_msgs)
|
||||
target_compile_definitions(image2rtsp
|
||||
PRIVATE "IMAGE2RTSP_BUILDING_DLL")
|
||||
|
||||
target_link_libraries(image2rtsp
|
||||
${sensor_msgs_TARGETS}
|
||||
${GST_LIBRARIES})
|
||||
|
||||
ament_target_dependencies(image2rtsp
|
||||
"rclcpp"
|
||||
"rclcpp_components"
|
||||
"sensor_msgs")
|
||||
rclcpp_components_register_nodes(image2rtsp "Image2rtsp")
|
||||
set(node_plugins "${node_plugins}Image2rtsp;$<TARGET_FILE:image2rtsp>\n")
|
||||
|
||||
if(BUILD_TESTING)
|
||||
find_package(ament_lint_auto REQUIRED)
|
||||
# the following line skips the linter which checks for copyrights
|
||||
# uncomment the line when a copyright and license is not present in all source files
|
||||
#set(ament_cmake_copyright_FOUND TRUE)
|
||||
# the following line skips cpplint (only works in a git repo)
|
||||
# uncomment the line when this package is not in a git repo
|
||||
#set(ament_cmake_cpplint_FOUND TRUE)
|
||||
ament_lint_auto_find_test_dependencies()
|
||||
endif()
|
||||
|
||||
${GST_LIBRARIES}
|
||||
${OpenCV_LIBS}
|
||||
)
|
||||
|
||||
install(TARGETS
|
||||
image2rtsp
|
||||
ARCHIVE DESTINATION lib
|
||||
LIBRARY DESTINATION lib
|
||||
RUNTIME DESTINATION bin)
|
||||
DESTINATION lib/${PROJECT_NAME}
|
||||
)
|
||||
|
||||
install(DIRECTORY
|
||||
install(
|
||||
DIRECTORY
|
||||
config
|
||||
launch
|
||||
DESTINATION share/${PROJECT_NAME}
|
||||
)
|
||||
)
|
||||
|
||||
# Install Python modules
|
||||
ament_python_install_package(python)
|
||||
|
||||
|
|
@ -87,4 +68,5 @@ install(PROGRAMS
|
|||
DESTINATION lib/${PROJECT_NAME}
|
||||
)
|
||||
|
||||
|
||||
ament_package()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
Copyright <2023> <Dzmitry Maladzenkau>
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
98
README.md
98
README.md
|
|
@ -2,17 +2,17 @@
|
|||
This project is a migration from ROS1 to ROS2. The original code was developed by [CircusMonkey](https://github.com/CircusMonkey/ros_rtsp/tree/master). I would like to express my gratitude for his contribution.
|
||||
|
||||
## image2rtsp
|
||||
This project enables the conversion of a selected ROS2 topic of type `sensor_msgs::msg::Image` into an `RTSP` stream, with an anticipated delay of approximately 0,3-0,4s. The generated stream can be utilized for various purposes such as remote control, object detection tasks, monitoring, and more.
|
||||
This project enables the conversion of a selected ROS2 topic of type `sensor_msgs::msg::Image` or `sensor_msgs::msg::CompressedImage` into an `RTSP` stream, with an anticipated delay of approximately 0,3-0,4s. It also supports usb camera as a direct source. The generated stream can be utilized for various purposes such as remote control, object detection tasks, monitoring and more.
|
||||
|
||||
Currently supported `sensor_msgs::msg::Image` formats: "**rgb8**", "**rgba8**", "**rgb16**", "**rgba16**", "**bgr8**", "**bgra8**", "**bgr16**", "**bgra16**", "**mono8**", "**mono16**", "**yuv422_yuy2**".
|
||||
(if you need some specific unsupported format, create an issue and i will try to add it as soon as possible)
|
||||
Currently supported and tested `sensor_msgs::msg::Image` formats: "**rgb8**", "**rgba8**", "**rgb16**", "**rgba16**", "**bgr8**", "**bgra8**", "**bgr16**", "**bgra16**", "**mono8**", "**mono16**", "**yuv422_yuy2**".
|
||||
|
||||
The development is being carried out on Ubuntu 20.04 with ROS2 Foxy, also tested on Ubuntu 22.04 with ROS2 Humble.
|
||||
Supported and tested `sensor_msgs::msg::CompressedImage` formats: "**rgb8; jpeg compressed bgr8**". Other formats may work as well with some color scheme deviations. Please open an issue in this case and attach your **ros2 bag**, so i can fix it. If you need some specific unsupported format, create an issue and i will try to add it as soon as possible, but normally it takes pretty long, so dont hesitate to create a PR.
|
||||
|
||||
You are reading now the README for a package written as a ROS2 **component**. To know about ROS2 components you can look into the official documentation ([link1](https://docs.ros.org/en/foxy/Concepts/About-Composition.html), [link2](https://docs.ros.org/en/foxy/Tutorials/Intermediate/Composition.html)). If you want to use this package as a default ROS2 package, checkout `master` branch.
|
||||
The development is being carried out on Ubuntu 22.04 with ROS2 Humble. Tested with Intel RealSense d435i.
|
||||
|
||||
You are reading now the README for a **default** ROS2 package. If you want to use this package written as a ROS2 component, checkout `ros2_component` branch.
|
||||
## Dependencies
|
||||
- ROS2 Foxy/Humble
|
||||
- ROS2 Humble
|
||||
|
||||
- gstreamer libs:
|
||||
```bash
|
||||
|
|
@ -25,61 +25,77 @@ sudo apt-get install libgstreamer-plugins-base1.0-dev libgstreamer-plugins-good1
|
|||
mkdir -p ros2_ws/src
|
||||
cd ros2_ws/src/
|
||||
```
|
||||
- Clone the package and navigate into the directory `image2rtsp`:
|
||||
- Clone the package and then navigate into the directory `image2rtsp`:
|
||||
```bashrc
|
||||
git clone https://github.com/maladzenkau/image2rtsp.git -b ros2_component
|
||||
git clone https://github.com/maladzenkau/image2rtsp.git --single-branch
|
||||
```
|
||||
- Check the framerate of the topic to be subscribed:
|
||||
- Adjust `parameters.yaml` according to your needs:
|
||||
```bashrc
|
||||
ros2 topic hz /someTopic
|
||||
```
|
||||
- All the parameters are hardcoded in `image2rtsp.hpp`, so be sure to change them before adding the `Image2rtsp` component into `ComponentManager`:
|
||||
```bashrc
|
||||
gedit ~/ros2_ws/src/image2rtsp/include/image2rtsp.hpp
|
||||
gedit ~/ros2_ws/src/image2rtsp/config/parameters.yaml
|
||||
```
|
||||
# Example ROS2 Image topic stream
|
||||
topic: "/color/image_raw" # The ROS2 topic to subscribe to
|
||||
mountpoint: "rs" # Choose the mountpoint for the rtsp stream.
|
||||
# This will be able to be accessed from rtsp://<server_ip>/portAndMountpoint
|
||||
bitrate: "500"
|
||||
framerate = "30";
|
||||
caps: "video/x-raw,framerate=" + framerate + "/1,width=1280,height=720";
|
||||
# Set the caps to be applied after getting the ROS2 Image and before the x265 encoder.
|
||||
port: "8554"
|
||||
local_only: True # True = rtsp://127.0.0.1:port (The stream is accessible only from the local machine)
|
||||
# False = rtsp://0.0.0.0:portAndMountpoint (The stream is accessible from the outside)
|
||||
# For example, to access the stream running on the machine with IP = 192.168.20.20,
|
||||
# use rtsp://192.186.20.20:portAndMountpoint
|
||||
# If the source is a ros2 topic (default case)
|
||||
compressed: False
|
||||
topic: "color/image_raw"
|
||||
default_pipeline: |
|
||||
( appsrc name=imagesrc do-timestamp=true min-latency=0
|
||||
max-latency=0 max-bytes=1000 is-live=true !
|
||||
videoconvert !
|
||||
videoscale !
|
||||
video/x-raw, framerate=30/1, width=640, height=480 !
|
||||
x264enc tune=zerolatency bitrate=500 key-int-max=30 !
|
||||
video/x-h264, profile=baseline !
|
||||
rtph264pay name=pay0 pt=96 )
|
||||
|
||||
# Notice: The framerate setting does not affect the RTSP stream — it entirely depends on the ros2 topic frequency.
|
||||
# It is included in the pipeline and code for structural reasons. You can likely remove it from the pipeline without impacting the package's behavior.
|
||||
|
||||
|
||||
# If camera serves as a source
|
||||
camera: False
|
||||
camera_pipeline: |
|
||||
( v4l2src device=/dev/video0 !
|
||||
videoconvert !
|
||||
videoscale !
|
||||
video/x-raw, framerate=30/1, width=640, height=480 !
|
||||
x264enc tune=zerolatency bitrate=500 key-int-max=30 !
|
||||
video/x-h264, profile=baseline !
|
||||
rtph264pay name=pay0 pt=96 )
|
||||
|
||||
# Notice: Here the framerate might be set to the camera framerate, otherwise "503 Service Unavailable" error will appear.
|
||||
|
||||
# RTSP setup
|
||||
mountpoint: "/back"
|
||||
port: "8554"
|
||||
local_only: True # True = rtsp://127.0.0.1:portAndMountpoint (The stream is accessible only from the local machine)
|
||||
# False = rtsp://0.0.0.0:portAndMountpoint (The stream is accessible from the outside)
|
||||
# For example, to access the stream running on the machine with IP = 192.168.20.20,
|
||||
# use rtsp://192.186.20.20:portAndMountpoint
|
||||
|
||||
- Save your configuration and navigate to `ros2_ws` colcon root, source and build the package:
|
||||
```bashrc
|
||||
cd ~/ros2_ws/
|
||||
colcon build --packages-select image2rtsp
|
||||
```
|
||||
## Run
|
||||
- Open another shell, source local setup and run `ComponentManager`:
|
||||
```bashrc
|
||||
cd ~/ros2_ws/
|
||||
source install/setup.bash
|
||||
ros2 run rclcpp_components component_container
|
||||
```
|
||||
- Add a component with a publisher node first.
|
||||
- Source `install` and add the `Image2rtsp` component:
|
||||
- Source `install` and launch the package:
|
||||
```bashrc
|
||||
source install/setup.bash
|
||||
ros2 component load /ComponentManager image2rtsp Image2rtsp
|
||||
```
|
||||
OR to enable intra-process communication:
|
||||
```bashrc
|
||||
ros2 component load /ComponentManager image2rtsp Image2rtsp -e use_intra_process_comms:=true
|
||||
ros2 launch image2rtsp image2rtsp.launch.py
|
||||
```
|
||||
Don't use **`ros2 run`**!
|
||||
|
||||
## Check the stream
|
||||
To check the stream, follow the instructions for gstreamer, mpv or VLC provided by [CircusMonkey](https://github.com/CircusMonkey/ros_rtsp/blob/master/README.md) or use python script provided in this package (ensure before that the open-cv library is installed, if not `pip install opencv-python`). Ensure that components with publisher and subscriber nodes are loaded in the `ComponentManager`. Then:
|
||||
To check the stream, follow the instructions for gstreamer, mpv or VLC provided by [CircusMonkey](https://github.com/CircusMonkey/ros_rtsp/blob/master/README.md) or use python script provided in this package (ensure before that the open-cv library is installed, if not `pip install opencv-python`). Open new terminal, ensure that the topic to be converted exists and the RTSP stream is running. Then:
|
||||
```bash
|
||||
gedit ~/ros2_ws/src/image2rtsp/python/rtsp.py
|
||||
```
|
||||
Replace the `rtsp://0.0.0.0:8554/rs` with your server's IP address, port and mount point `rtsp://YOUR_IP:PORT/MOUNT_POINT`. Save and run:
|
||||
Replace the `rtsp://127.0.0.1:8554/back` with your server's IP address, port and mount point `rtsp://YOUR_IP:PORT/MOUNT_POINT`. Save and run:
|
||||
```bash
|
||||
cd ~/ros2_ws/
|
||||
source install/setup.bash
|
||||
ros2 launch image2rtsp rtsp.launch.py
|
||||
```
|
||||
## Note
|
||||
|
||||
- The YAML configuration allows you to fully customize the pipeline according to your needs (Useful insights can be found, for example, [here](https://github.com/maladzenkau/image2rtsp/pull/9)). This package does not provide any built-in acceleration. As its stability has not been validated across a wide range of Linux systems using advanced hardware or software techniques, support for such configurations is left to the user. There are no plans to update the package to support GPU/CPU acceleration. Please do not open issues related to software/hardware acceleration if they are directly related to the GStreamer pipeline itself.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
/image2rtsp:
|
||||
ros__parameters:
|
||||
|
||||
# If the source is a ros2 topic (default case)
|
||||
compressed: False
|
||||
topic: "color/image_raw"
|
||||
default_pipeline: |
|
||||
( appsrc name=imagesrc do-timestamp=true min-latency=0 max-latency=0 max-bytes=1000 is-live=true !
|
||||
videoconvert !
|
||||
videoscale !
|
||||
video/x-raw, framerate=30/1, width=640, height=480 !
|
||||
x264enc tune=zerolatency bitrate=500 key-int-max=30 !
|
||||
video/x-h264, profile=baseline !
|
||||
rtph264pay name=pay0 pt=96 )
|
||||
|
||||
# Notice: The framerate setting does not affect the RTSP stream — it entirely depends on the ros2 topic frequency.
|
||||
# It is included in the pipeline and code for structural reasons. You can likely remove it from the pipeline without impacting the package's behavior.
|
||||
|
||||
|
||||
# If camera serves as a source
|
||||
camera: False
|
||||
camera_pipeline: |
|
||||
( v4l2src device=/dev/video0 !
|
||||
videoconvert !
|
||||
videoscale !
|
||||
video/x-raw, framerate=30/1, width=640, height=480 !
|
||||
x264enc tune=zerolatency bitrate=500 key-int-max=30 !
|
||||
video/x-h264, profile=baseline !
|
||||
rtph264pay name=pay0 pt=96 )
|
||||
|
||||
# Notice: Here the framerate might be set to the camera framerate, otherwise "503 Service Unavailable" error will appear.
|
||||
|
||||
# RTSP setup
|
||||
mountpoint: "/back"
|
||||
port: "8554"
|
||||
local_only: True # True = rtsp://127.0.0.1:portAndMountpoint (The stream is accessible only from the local machine)
|
||||
# False = rtsp://0.0.0.0:portAndMountpoint (The stream is accessible from the outside)
|
||||
# For example, to access the stream running on the machine with IP = 192.168.20.20,
|
||||
# use rtsp://192.186.20.20:portAndMountpoint
|
||||
|
|
@ -1,47 +1,47 @@
|
|||
#ifndef IMAGE2RTSP__IMAGE2RTSP_HPP_
|
||||
#define IMAGE2RTSP__IMAGE2RTSP_HPP_
|
||||
#ifndef IMAGE2RTSP_IMAGE2RTSP_HPP
|
||||
#define IMAGE2RTSP_IMAGE2RTSP_HPP
|
||||
|
||||
#include "visibility_control.h"
|
||||
#include <gst/gst.h>
|
||||
#include <gst/rtsp-server/rtsp-server.h>
|
||||
#include <rclcpp/rclcpp.hpp>
|
||||
#include <string>
|
||||
#include "sensor_msgs/msg/image.hpp"
|
||||
#include <gst/app/gstappsrc.h>
|
||||
#include "sensor_msgs/msg/compressed_image.hpp"
|
||||
#include <opencv2/opencv.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class Image2rtsp : public rclcpp::Node
|
||||
{
|
||||
class Image2rtsp : public rclcpp::Node{
|
||||
public:
|
||||
IMAGE2RTSP_PUBLIC
|
||||
explicit Image2rtsp(const rclcpp::NodeOptions & options);
|
||||
GstRTSPServer *rtsp_server;
|
||||
Image2rtsp();
|
||||
GstRTSPServer *rtsp_server;
|
||||
|
||||
private:
|
||||
string topic = "/color/image_raw";
|
||||
string mountpoint = "/rs";
|
||||
string bitrate = "500";
|
||||
string framerate = "30";
|
||||
string caps = "video/x-raw,framerate=" + framerate + "/1,width=1280,height=720";
|
||||
string port = "8554";
|
||||
string pipeline;
|
||||
string pipeline_head;
|
||||
string pipeline_tail;
|
||||
bool local_only = false;
|
||||
GstAppSrc *appsrc;
|
||||
string topic;
|
||||
string mountpoint;
|
||||
string port;
|
||||
string pipeline;
|
||||
string default_pipeline;
|
||||
string camera_pipeline;
|
||||
uint framerate;
|
||||
bool local_only;
|
||||
bool camera;
|
||||
bool compressed;
|
||||
GstAppSrc *appsrc;
|
||||
|
||||
void video_mainloop_start();
|
||||
void rtsp_server_add_url(const char *url, const char *sPipeline, GstElement **appsrc);
|
||||
void topic_callback(const sensor_msgs::msg::Image::SharedPtr msg);
|
||||
GstRTSPServer *rtsp_server_create(const string &port, const bool local_only);
|
||||
GstCaps *gst_caps_new_from_image(const sensor_msgs::msg::Image::SharedPtr &msg);
|
||||
rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr subscription_;
|
||||
void video_mainloop_start();
|
||||
void rtsp_server_add_url(const char *url, const char *sPipeline, GstElement **appsrc);
|
||||
void topic_callback(const sensor_msgs::msg::Image::SharedPtr msg);
|
||||
void compressed_topic_callback(const sensor_msgs::msg::CompressedImage::SharedPtr msg);
|
||||
uint extract_framerate(const std::string& pipeline, uint default_framerate);
|
||||
GstRTSPServer *rtsp_server_create(const string &port, const bool local_only);
|
||||
GstCaps *gst_caps_new_from_image(const sensor_msgs::msg::Image::SharedPtr &msg);
|
||||
rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr subscription_;
|
||||
rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr subscription_compressed_;
|
||||
};
|
||||
|
||||
static void media_configure(GstRTSPMediaFactory *factory, GstRTSPMedia *media, GstElement **appsrc);
|
||||
static void *mainloop(void *arg);
|
||||
static gboolean session_cleanup(Image2rtsp *node, rclcpp::Logger logger, gboolean ignored);
|
||||
|
||||
|
||||
#endif // IMAGE2RTSP__IMAGE2RTSP_HPP_
|
||||
#endif // IMAGE2RTSP_IMAGE2RTSP_HPP
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
#ifndef IMAGE2RTSP__VISIBILITY_CONTROL_H_
|
||||
#define IMAGE2RTSP__VISIBILITY_CONTROL_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
// This logic was borrowed (then namespaced) from the examples on the gcc wiki:
|
||||
// https://gcc.gnu.org/wiki/Visibility
|
||||
|
||||
#if defined _WIN32 || defined __CYGWIN__
|
||||
#ifdef __GNUC__
|
||||
#define IMAGE2RTSP_EXPORT __attribute__ ((dllexport))
|
||||
#define IMAGE2RTSP_IMPORT __attribute__ ((dllimport))
|
||||
#else
|
||||
#define IMAGE2RTSP_EXPORT __declspec(dllexport)
|
||||
#define IMAGE2RTSP_IMPORT __declspec(dllimport)
|
||||
#endif
|
||||
#ifdef IMAGE2RTSP_BUILDING_DLL
|
||||
#define IMAGE2RTSP_PUBLIC IMAGE2RTSP_EXPORT
|
||||
#else
|
||||
#define IMAGE2RTSP_PUBLIC IMAGE2RTSP_IMPORT
|
||||
#endif
|
||||
#define IMAGE2RTSP_PUBLIC_TYPE IMAGE2RTSP_PUBLIC
|
||||
#define IMAGE2RTSP_LOCAL
|
||||
#else
|
||||
#define IMAGE2RTSP_EXPORT __attribute__ ((visibility("default")))
|
||||
#define IMAGE2RTSP_IMPORT
|
||||
#if __GNUC__ >= 4
|
||||
#define IMAGE2RTSP_PUBLIC __attribute__ ((visibility("default")))
|
||||
#define IMAGE2RTSP_LOCAL __attribute__ ((visibility("hidden")))
|
||||
#else
|
||||
#define IMAGE2RTSP_PUBLIC
|
||||
#define IMAGE2RTSP_LOCAL
|
||||
#endif
|
||||
#define IMAGE2RTSP_PUBLIC_TYPE
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // IMAGE2RTSP__VISIBILITY_CONTROL_H_
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import os
|
||||
from ament_index_python.packages import get_package_share_directory
|
||||
from launch import LaunchDescription
|
||||
from launch_ros.actions import Node
|
||||
|
||||
def generate_launch_description():
|
||||
config = os.path.join(
|
||||
get_package_share_directory('image2rtsp'),
|
||||
'config',
|
||||
'parameters.yaml'
|
||||
)
|
||||
|
||||
return LaunchDescription([
|
||||
Node(
|
||||
package='image2rtsp',
|
||||
executable='image2rtsp',
|
||||
name='image2rtsp',
|
||||
parameters=[config]
|
||||
)
|
||||
])
|
||||
|
|
@ -3,16 +3,15 @@
|
|||
<package format="3">
|
||||
<name>image2rtsp</name>
|
||||
<version>0.0.0</version>
|
||||
<description>takes ROS2 topics as a source and provides rtsp video stream as a sink</description>
|
||||
<description>topic to RTSP stream package</description>
|
||||
<maintainer email="dmalad@rptu.de">dmitry</maintainer>
|
||||
<license>BSD</license>
|
||||
|
||||
<buildtool_depend>ament_cmake</buildtool_depend>
|
||||
|
||||
<depend>rclcpp</depend>
|
||||
<depend>rclcpp_components</depend>
|
||||
<depend>sensor_msgs</depend>
|
||||
|
||||
<depend>python3-opencv</depend>
|
||||
|
||||
<build_depend>gstreamer1.0-plugins-base</build_depend>
|
||||
<build_depend>gstreamer1.0-plugins-good</build_depend>
|
||||
|
|
@ -23,6 +22,8 @@
|
|||
<test_depend>ament_lint_auto</test_depend>
|
||||
<test_depend>ament_lint_common</test_depend>
|
||||
|
||||
<exec_depend>ros2launch</exec_depend>
|
||||
|
||||
<export>
|
||||
<build_type>ament_cmake</build_type>
|
||||
</export>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import cv2
|
||||
|
||||
cap = cv2.VideoCapture('rtsp://0.0.0.0:8554/rs')
|
||||
cap = cv2.VideoCapture('rtsp://127.0.0.1:8554/back')
|
||||
|
||||
while True:
|
||||
ret, frame = cap.read()
|
||||
|
|
|
|||
|
|
@ -1,27 +1,116 @@
|
|||
#include "../include/image2rtsp.hpp"
|
||||
#include "rclcpp/rclcpp.hpp"
|
||||
#include "sensor_msgs/msg/image.hpp"
|
||||
#include <gst/gst.h>
|
||||
#include <gst/rtsp-server/rtsp-server.h>
|
||||
#include <gst/app/gstappsrc.h>
|
||||
#include "../include/image2rtsp.hpp"
|
||||
|
||||
using std::placeholders::_1;
|
||||
|
||||
Image2rtsp::Image2rtsp(const rclcpp::NodeOptions & options) : Node("image2rtsp"){
|
||||
Image2rtsp::Image2rtsp() : Node("image2rtsp"){
|
||||
// Declare and get the parameters
|
||||
this->declare_parameter("topic", "/color/image_raw");
|
||||
this->declare_parameter("mountpoint", "/back");
|
||||
this->declare_parameter("port", "8554");
|
||||
this->declare_parameter("local_only", true);
|
||||
this->declare_parameter("camera", false);
|
||||
this->declare_parameter("compressed", false);
|
||||
|
||||
this->declare_parameter("default_pipeline", R"(
|
||||
( appsrc name=imagesrc do-timestamp=true min-latency=0 max-latency=0 max-bytes=1000 is-live=true !
|
||||
videoconvert !
|
||||
videoscale !
|
||||
video/x-raw, framerate=30/1, width=640, height=480 !
|
||||
x264enc tune=zerolatency bitrate=500 key-int-max=30 !
|
||||
video/x-h264, profile=baseline !
|
||||
rtph264pay name=pay0 pt=96 )
|
||||
)");
|
||||
|
||||
this->declare_parameter("camera_pipeline", R"(
|
||||
( v4l2src device=/dev/video0 !
|
||||
videoconvert !
|
||||
videoscale !
|
||||
video/x-raw, framerate=30/1, width=640, height=480 !
|
||||
x264enc tune=zerolatency bitrate=500 key-int-max=30 !
|
||||
video/x-h264, profile=baseline !
|
||||
rtph264pay name=pay0 pt=96 )
|
||||
)");
|
||||
|
||||
topic = this->get_parameter("topic").as_string();
|
||||
mountpoint = this->get_parameter("mountpoint").as_string();
|
||||
port = this->get_parameter("port").as_string();
|
||||
local_only = this->get_parameter("local_only").as_bool();
|
||||
camera = this->get_parameter("camera").as_bool();
|
||||
compressed = this->get_parameter("compressed").as_bool();
|
||||
default_pipeline = this->get_parameter("default_pipeline").as_string();
|
||||
camera_pipeline = this->get_parameter("camera_pipeline").as_string();
|
||||
|
||||
// Start the subscription
|
||||
subscription_ = this->create_subscription<sensor_msgs::msg::Image>(topic, 10, std::bind(&Image2rtsp::topic_callback, this, _1));
|
||||
if (camera == false){
|
||||
if (compressed == false){
|
||||
subscription_ = this->create_subscription<sensor_msgs::msg::Image>(topic, 10, std::bind(&Image2rtsp::topic_callback, this, _1));
|
||||
RCLCPP_INFO(this->get_logger(), "Subscribing to sensor_msgs::msg::Image");
|
||||
}
|
||||
else {
|
||||
subscription_compressed_ = this->create_subscription<sensor_msgs::msg::CompressedImage>(topic, 10, std::bind(&Image2rtsp::compressed_topic_callback, this, _1));
|
||||
RCLCPP_INFO(this->get_logger(), "Subscribing to sensor_msgs::msg::CompressedImage");
|
||||
}
|
||||
}
|
||||
else {
|
||||
RCLCPP_INFO(this->get_logger(), "Trying to access camera device");
|
||||
}
|
||||
|
||||
// Start the RTSP server
|
||||
video_mainloop_start();
|
||||
rtsp_server = rtsp_server_create(port, local_only);
|
||||
appsrc = NULL;
|
||||
// Setup the pipeline
|
||||
pipeline_head = "( appsrc name=imagesrc do-timestamp=true min-latency=0 max-latency=0 max-bytes=1000 is-live=true ! videoconvert ! videoscale ! ";
|
||||
pipeline_tail = "key-int-max=30 ! video/x-h264, profile=baseline ! rtph264pay name=pay0 pt=96 )";
|
||||
pipeline = pipeline_head + caps + " ! x264enc tune=zerolatency bitrate=" + bitrate + pipeline_tail;
|
||||
rtsp_server_add_url(mountpoint.c_str(), pipeline.c_str(), (GstElement **)&(appsrc));
|
||||
|
||||
pipeline = camera ? camera_pipeline : default_pipeline;
|
||||
framerate = extract_framerate(pipeline, 30);
|
||||
rtsp_server_add_url(mountpoint.c_str(), pipeline.c_str(), camera ? nullptr : (GstElement **)&appsrc);
|
||||
|
||||
RCLCPP_INFO(this->get_logger(), "Stream available at rtsp://%s:%s%s", gst_rtsp_server_get_address(rtsp_server), port.c_str(), mountpoint.c_str());
|
||||
}
|
||||
|
||||
#include "rclcpp_components/register_node_macro.hpp"
|
||||
RCLCPP_COMPONENTS_REGISTER_NODE(Image2rtsp)
|
||||
uint Image2rtsp::extract_framerate(const std::string& pipeline, uint default_framerate = 30) {
|
||||
std::string search_str = "framerate=";
|
||||
size_t pos = pipeline.find(search_str);
|
||||
if (pos == std::string::npos) {
|
||||
RCLCPP_WARN(this->get_logger(), "Framerate not found in pipeline, using default: %d", default_framerate);
|
||||
return default_framerate;
|
||||
}
|
||||
|
||||
pos += search_str.length();
|
||||
|
||||
size_t end_pos = pipeline.find_first_of("/,", pos);
|
||||
if (end_pos == std::string::npos) {
|
||||
RCLCPP_WARN(this->get_logger(), "Invalid framerate format in pipeline, using default: %d", default_framerate);
|
||||
return default_framerate;
|
||||
}
|
||||
|
||||
std::string framerate_str = pipeline.substr(pos, end_pos - pos);
|
||||
|
||||
framerate_str.erase(0, framerate_str.find_first_not_of(" \t"));
|
||||
framerate_str.erase(framerate_str.find_last_not_of(" \t") + 1);
|
||||
|
||||
try {
|
||||
uint framerate = std::stoi(framerate_str);
|
||||
if (framerate <= 0) {
|
||||
RCLCPP_WARN(this->get_logger(), "Invalid framerate value %d, using default: %d", framerate, default_framerate);
|
||||
return default_framerate;
|
||||
}
|
||||
RCLCPP_INFO(this->get_logger(), "Using set framerate %d", framerate);
|
||||
return framerate;
|
||||
} catch (const std::exception& e) {
|
||||
RCLCPP_WARN(this->get_logger(), "Failed to parse framerate '%s', using default: %d", framerate_str.c_str(), default_framerate);
|
||||
return default_framerate;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]){
|
||||
rclcpp::init(argc, argv);
|
||||
auto node = std::make_shared<Image2rtsp>();
|
||||
rclcpp::spin(node);
|
||||
rclcpp::shutdown();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,14 +4,10 @@
|
|||
#include <rclcpp/rclcpp.hpp>
|
||||
|
||||
#include "../include/image2rtsp.hpp"
|
||||
|
||||
extern "C" {
|
||||
#include "../include/image_encodings.h"
|
||||
}
|
||||
#include "../include/image_encodings.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
static void *mainloop(void *arg){
|
||||
GMainLoop *loop = g_main_loop_new(NULL, FALSE);
|
||||
g_main_loop_run(loop);
|
||||
|
|
@ -136,7 +132,7 @@ GstCaps *Image2rtsp::gst_caps_new_from_image(const sensor_msgs::msg::Image::Shar
|
|||
"format", G_TYPE_STRING, format->second.c_str(),
|
||||
"width", G_TYPE_INT, msg->width,
|
||||
"height", G_TYPE_INT, msg->height,
|
||||
"framerate", GST_TYPE_FRACTION, stoi(framerate), 1,
|
||||
"framerate", GST_TYPE_FRACTION, framerate, 1,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
|
|
@ -172,3 +168,43 @@ void Image2rtsp::topic_callback(const sensor_msgs::msg::Image::SharedPtr msg){
|
|||
gst_app_src_push_buffer(appsrc, buf);
|
||||
}
|
||||
}
|
||||
|
||||
void Image2rtsp::compressed_topic_callback(const sensor_msgs::msg::CompressedImage::SharedPtr msg){
|
||||
if (appsrc == NULL) return;
|
||||
// Decompress the image
|
||||
cv::Mat img = cv::imdecode(cv::Mat(msg->data.to_vector()), cv::IMREAD_UNCHANGED);
|
||||
if (img.empty()) {
|
||||
RCLCPP_ERROR(this->get_logger(), "Failed to decompress image");
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the GStreamer caps
|
||||
std::string gst_format;
|
||||
switch (img.type()) {
|
||||
case CV_8UC3: gst_format = "BGR"; break; // BGR images
|
||||
case CV_8UC4: gst_format = "RGBA"; break; // RGBA images
|
||||
case CV_8UC1: gst_format = "GRAY8"; break; // Grayscale images
|
||||
default:
|
||||
RCLCPP_ERROR(this->get_logger(), "Unsupported image type");
|
||||
return;
|
||||
}
|
||||
|
||||
GstCaps *caps = gst_caps_new_simple("video/x-raw",
|
||||
"format", G_TYPE_STRING, gst_format.c_str(),
|
||||
"width", G_TYPE_INT, img.cols,
|
||||
"height", G_TYPE_INT, img.rows,
|
||||
"framerate", GST_TYPE_FRACTION, framerate, 1,
|
||||
nullptr);
|
||||
|
||||
// Set caps on appsrc
|
||||
gst_app_src_set_caps(appsrc, caps);
|
||||
gst_caps_unref(caps);
|
||||
|
||||
// Create a GstBuffer and fill it with the image data
|
||||
GstBuffer *buf = gst_buffer_new_allocate(nullptr, img.total() * img.elemSize(), nullptr);
|
||||
gst_buffer_fill(buf, 0, img.data, img.total() * img.elemSize());
|
||||
GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_LIVE);
|
||||
|
||||
// Push the buffer to GStreamer
|
||||
gst_app_src_push_buffer(appsrc, buf);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue