├── nodelet_plugins.xml
├── launch
└── rtsp_streams.launch
├── config
└── stream_setup.yaml
├── .gitignore
├── include
└── image2rtsp.h
├── LICENSE
├── package.xml
├── README.md
├── src
├── video.cpp
└── image2rtsp.cpp
└── CMakeLists.txt
/nodelet_plugins.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Nodelet to transfert the /camera/image topic from openni2_camera over rtsp.
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/launch/rtsp_streams.launch:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/config/stream_setup.yaml:
--------------------------------------------------------------------------------
1 | # Set up your streams to rtsp here.
2 | port: "8554"
3 | streams: # Cannot rename - must leave this as is.
4 |
5 | stream-x:
6 | type: cam
7 | source: "v4l2src device=/dev/video0 ! videoconvert ! videoscale ! video/x-raw,framerate=15/1,width=1280,height=720"
8 | mountpoint: /front
9 | bitrate: 500
10 |
11 | stream-yay:
12 | type: topic
13 | source: /usb_cam1/image_raw
14 | mountpoint: /back
15 | caps: video/x-raw,framerate=10/1,width=640,height=480
16 | bitrate: 500
17 |
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | devel/
2 | logs/
3 | build/
4 | bin/
5 | lib/
6 | msg_gen/
7 | srv_gen/
8 | msg/*Action.msg
9 | msg/*ActionFeedback.msg
10 | msg/*ActionGoal.msg
11 | msg/*ActionResult.msg
12 | msg/*Feedback.msg
13 | msg/*Goal.msg
14 | msg/*Result.msg
15 | msg/_*.py
16 | build_isolated/
17 | devel_isolated/
18 |
19 | # Generated by dynamic reconfigure
20 | *.cfgc
21 | /cfg/cpp/
22 | /cfg/*.py
23 |
24 | # Ignore generated docs
25 | *.dox
26 | *.wikidoc
27 |
28 | # eclipse stuff
29 | .project
30 | .cproject
31 |
32 | # qcreator stuff
33 | CMakeLists.txt.user
34 |
35 | srv/_*.py
36 | *.pcd
37 | *.pyc
38 | qtcreator-*
39 | *.user
40 |
41 | /planning/cfg
42 | /planning/docs
43 | /planning/src
44 |
45 | *~
46 |
47 | # Emacs
48 | .#*
49 |
50 | # Catkin custom files
51 | CATKIN_IGNORE
52 |
--------------------------------------------------------------------------------
/include/image2rtsp.h:
--------------------------------------------------------------------------------
1 | #ifndef IMAGE_TO_RTSP_H
2 | #define IMAGE_TO_RTSP_H
3 |
4 | namespace image2rtsp {
5 | class Image2RTSPNodelet : public nodelet::Nodelet {
6 | public:
7 | GstRTSPServer *rtsp_server;
8 | void onInit();
9 | void url_connected(std::string url);
10 | void url_disconnected(std::string url);
11 | void print_info(char *s);
12 | void print_error(char *s);
13 |
14 | private:
15 | std::string port;
16 | std::map subs;
17 | std::map appsrc;
18 | std::map num_of_clients;
19 | GstCaps* gst_caps_new_from_image(const sensor_msgs::Image::ConstPtr &msg);
20 | void imageCallback(const sensor_msgs::Image::ConstPtr& msg, const std::string& topic);
21 | void video_mainloop_start();
22 | void rtsp_server_add_url(const char *url, const char *sPipeline, GstElement **appsrc);
23 | GstRTSPServer *rtsp_server_create(const std::string& port);
24 | };
25 | }
26 |
27 | #endif
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ros_rtsp
4 | 0.0.0
5 | The ros_rtsp package
6 |
7 |
8 |
9 |
10 | samar
11 |
12 |
13 |
14 |
15 |
16 | TODO
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | catkin
52 | roscpp
53 | sensor_msgs
54 | nodelet
55 | roscpp
56 | sensor_msgs
57 | roscpp
58 | sensor_msgs
59 | nodelet
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ros_rtsp
2 | ROS package to subscribe to an ROS Image topic (and as many other video sources as you want) and serve it up as a RTSP video feed with different mount points.
3 | Should provide a real-time video feed (or as close as possible).
4 |
5 | This is still very much a work in progress. Developing on Ubuntu 16.04 and 18.04 with ROS kinetic and melodic.
6 |
7 |
8 | ## Dependencies
9 | - ROS
10 |
11 | - gstreamer libs:
12 | ```bash
13 | sudo apt-get install libgstreamer-plugins-base1.0-dev libgstreamer-plugins-good1.0-dev libgstreamer-plugins-bad1.0-dev libgstrtspserver-1.0-dev gstreamer1.0-plugins-ugly gstreamer1.0-plugins-bad
14 | ```
15 |
16 | ## Build into your catkin workspace
17 | Navigate to your catkin workspace `src` folder. I.e. `cd ~/catkin_ws/src`.
18 | Clone this package to the repository:
19 | ```bash
20 | git clone https://github.com/CircusMonkey/ros_rtsp.git
21 | ```
22 |
23 | Navigate back to the catkin workspace root and make the package:
24 | ```bash
25 | cd ..
26 | catkin_make pkg:=ros_rtsp
27 | ```
28 |
29 | ## Stream Setup
30 | Change the `config/stream_setup.yaml` to suit your required streams.
31 |
32 | ```yaml
33 | # Set up your streams for rtsp here.
34 | streams:
35 |
36 | # Example v4l2 camera stream
37 | stream-1: # Can name this whatever you choose
38 | type: cam # cam - Will not look in ROS for a image. The video src is set in the 'source' parameter.
39 | source: "v4l2src device=/dev/video0 ! videoconvert ! videoscale ! video/x-raw,framerate=15/1,width=1280,height=720" # You can enter any valid gstreamer source and caps here as long as it ends in raw video
40 | mountpoint: /front # Choose the mountpoint for the rtsp stream. This will be able to be accessed from rtsp:///front
41 | bitrate: 800 # bitrate for the h264 encoding.
42 |
43 | # Example ROS Image topic stream
44 | stream2:
45 | type: topic # topic - Image is sourced from a sensor_msgs::Image topic
46 | source: /usb_cam0/image_raw # The ROS topic to subscribe to
47 | mountpoint: /back # Choose the mountpoint for the rtsp stream. This will be able to be accessed from rtsp:///back
48 | caps: video/x-raw,framerate=10/1,width=640,height=480 # Set the caps to be applied after getting the ROS Image and before the x265 encoder.
49 | bitrate: 500
50 | ```
51 | Add as many streams as you require.
52 |
53 | ## Checking the streams
54 | Launch the streams from the ROS launch file:
55 | ```bash
56 | roslaunch ros_rtsp rtsp_streams.launch
57 | ```
58 |
59 | In the following examples, replace the `rtsp://127.0.0.1:8554/front` with your servers IP address and mount point `rtsp://YOUR_IP:8554/MOUNT_POINT`.
60 |
61 | ### gstreamer
62 | Use `gst-launch-1.0`. You will need to install gstreamer for your client system. See https://gstreamer.freedesktop.org/documentation/installing/index.html
63 | ```bash
64 | gst-launch-1.0 -v rtspsrc location=rtsp://127.0.0.1:8554/front drop-on-latency=true use-pipeline-clock=true do-retransmission=false latency=0 protocols=GST_RTSP_LOWER_TRANS_UDP ! rtph264depay ! h264parse ! avdec_h264 ! autovideosink sync=true
65 | ```
66 |
67 | ### mpv
68 | ```bash
69 | mpv --no-cache --untimed --no-demuxer-thread --vd-lavc-threads=1 rtsp://127.0.0.1:8554/front
70 | ```
71 |
72 | ### VLC
73 | VLC adds way too much latency. Please don't use it for this purpose. If you want to try, this is the command that was the least slow (Let me know if you find a better command):
74 | ```bash
75 | cvlc --no-audio --mux none --demux none --deinterlace 0 --no-autoscale --avcodec-hw=any --no-auto-preparse --sout-rtp-proto=udp --network-caching=300 --realrtsp-caching=0 --sout-udp-caching=0 --clock-jitter=0 --rtp-max-misorder=0 rtsp://127.0.0.1:8554/front :udp-timeout=0
76 | ```
77 | If you wish to use the VLC mobile app to stream on Android or iOS, navigate to the network or streams menu and type in your server URL and mountpoint e.g. `rtsp://127.0.0.1:8554/front`
78 |
79 |
80 | ## Debugging
81 | - If too much latency is encounted with multiple streams running, the server computer may be maxing out its processor trying to encode all the streams. Try reducing the resolution of the source caps.
82 | - The ROS Image topic stream may be buggy with framerates too fast for the Image publisher and the buffer writing. Stick with 10/1 fps unless you want to debug? :)
83 | - If too many frames are being dropped, it is likely due to network bandwidth. Try dropping the bitrate.
84 | - If the ROS topic isn't available, you will get a `can't prepare media` error after a delay.
85 |
--------------------------------------------------------------------------------
/src/video.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include "sensor_msgs/Image.h"
12 | #include
13 |
14 | using namespace std;
15 | using namespace image2rtsp;
16 |
17 |
18 | static void *mainloop(void *arg) {
19 | GMainLoop *loop = g_main_loop_new(NULL, FALSE);
20 |
21 | g_main_loop_run(loop);
22 |
23 | g_main_destroy(loop);
24 | return NULL;
25 | }
26 |
27 |
28 | void Image2RTSPNodelet::video_mainloop_start() {
29 | pthread_t tloop;
30 |
31 | gst_init(NULL, NULL);
32 | pthread_create(&tloop, NULL, &mainloop, NULL);
33 | }
34 |
35 |
36 | static void client_options(GstRTSPClient *client, GstRTSPContext *state, Image2RTSPNodelet *nodelet) {
37 | if (state->uri) {
38 | nodelet->url_connected(state->uri->abspath);
39 | }
40 | }
41 |
42 |
43 | static void client_teardown(GstRTSPClient *client, GstRTSPContext *state, Image2RTSPNodelet *nodelet) {
44 | if (state->uri) {
45 | nodelet->url_disconnected(state->uri->abspath);
46 | }
47 | }
48 |
49 |
50 | static void new_client(GstRTSPServer *server, GstRTSPClient *client, Image2RTSPNodelet *nodelet) {
51 | nodelet->print_info((char *)"New RTSP client");
52 | g_signal_connect(client, "options-request", G_CALLBACK(client_options), nodelet);
53 | g_signal_connect(client, "teardown-request", G_CALLBACK(client_teardown), nodelet);
54 | }
55 |
56 | /* this function is periodically run to clean up the expired sessions from the pool. */
57 | static gboolean session_cleanup(Image2RTSPNodelet *nodelet, gboolean ignored)
58 | {
59 | GstRTSPServer *server = nodelet->rtsp_server;
60 | GstRTSPSessionPool *pool;
61 | int num;
62 |
63 | pool = gst_rtsp_server_get_session_pool(server);
64 | num = gst_rtsp_session_pool_cleanup(pool);
65 | g_object_unref(pool);
66 |
67 | if (num > 0) {
68 | char s[32];
69 | snprintf(s, 32, (char *)"Sessions cleaned: %d", num);
70 | nodelet->print_info(s);
71 | }
72 |
73 | return TRUE;
74 | }
75 |
76 | GstRTSPServer *Image2RTSPNodelet::rtsp_server_create(const std::string& port) {
77 | GstRTSPServer *server;
78 |
79 | /* create a server instance */
80 | server = gst_rtsp_server_new();
81 |
82 | // char *port = (char *) port;
83 | g_object_set(server, "service", port.c_str(), NULL);
84 |
85 | /* attach the server to the default maincontext */
86 | gst_rtsp_server_attach(server, NULL);
87 |
88 | g_signal_connect(server, "client-connected", G_CALLBACK(new_client), this);
89 |
90 | /* add a timeout for the session cleanup */
91 | g_timeout_add_seconds(2, (GSourceFunc)session_cleanup, this);
92 |
93 | return server;
94 | }
95 |
96 |
97 | /* called when a new media pipeline is constructed. We can query the
98 | * pipeline and configure our appsrc */
99 | static void media_configure(GstRTSPMediaFactory *factory, GstRTSPMedia *media, GstElement **appsrc)
100 | { if (appsrc) {
101 | GstElement *pipeline = gst_rtsp_media_get_element(media);
102 |
103 | *appsrc = gst_bin_get_by_name(GST_BIN(pipeline), "imagesrc");
104 |
105 | /* this instructs appsrc that we will be dealing with timed buffer */
106 | gst_util_set_object_arg(G_OBJECT(*appsrc), "format", "time");
107 |
108 | gst_object_unref(pipeline);
109 | }
110 | else
111 | {
112 | guint i, n_streams;
113 | n_streams = gst_rtsp_media_n_streams (media);
114 |
115 | for (i = 0; i < n_streams; i++) {
116 | GstRTSPAddressPool *pool;
117 | GstRTSPStream *stream;
118 | gchar *min, *max;
119 |
120 | stream = gst_rtsp_media_get_stream (media, i);
121 |
122 | /* make a new address pool */
123 | pool = gst_rtsp_address_pool_new ();
124 |
125 | min = g_strdup_printf ("224.3.0.%d", (2 * i) + 1);
126 | max = g_strdup_printf ("224.3.0.%d", (2 * i) + 2);
127 | gst_rtsp_address_pool_add_range (pool, min, max,
128 | 5000 + (10 * i), 5010 + (10 * i), 1);
129 | g_free (min);
130 | g_free (max);
131 |
132 | gst_rtsp_stream_set_address_pool (stream, pool);
133 | g_object_unref (pool);
134 | }
135 | }
136 | }
137 |
138 |
139 | void Image2RTSPNodelet::rtsp_server_add_url(const char *url, const char *sPipeline, GstElement **appsrc) {
140 | GstRTSPMountPoints *mounts;
141 | GstRTSPMediaFactory *factory;
142 |
143 | /* get the mount points for this server, every server has a default object
144 | * that be used to map uri mount points to media factories */
145 | mounts = gst_rtsp_server_get_mount_points(rtsp_server);
146 |
147 | /* make a media factory for a test stream. The default media factory can use
148 | * gst-launch syntax to create pipelines.
149 | * any launch line works as long as it contains elements named pay%d. Each
150 | * element with pay%d names will be a stream */
151 | factory = gst_rtsp_media_factory_new();
152 | gst_rtsp_media_factory_set_launch(factory, sPipeline);
153 |
154 | /* notify when our media is ready, This is called whenever someone asks for
155 | * the media and a new pipeline is created */
156 | g_signal_connect(factory, "media-configure", (GCallback)media_configure, appsrc);
157 |
158 | gst_rtsp_media_factory_set_shared(factory, TRUE);
159 |
160 | /* attach the factory to the url */
161 | gst_rtsp_mount_points_add_factory(mounts, url, factory);
162 |
163 | /* don't need the ref to the mounts anymore */
164 | g_object_unref(mounts);
165 | }
166 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8.3)
2 | project(ros_rtsp)
3 |
4 | ## Find catkin macros and libraries
5 | ## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
6 | ## is used, also find other catkin packages
7 | find_package(OpenCV REQUIRED)
8 | find_package(PkgConfig REQUIRED)
9 | pkg_check_modules(GST REQUIRED
10 | gstreamer-1.0
11 | gstreamer-app-1.0
12 | gstreamer-rtsp-server-1.0
13 | )
14 |
15 |
16 | find_package(catkin REQUIRED COMPONENTS
17 | roscpp
18 | sensor_msgs
19 | nodelet
20 | )
21 |
22 | ## System dependencies are found with CMake's conventions
23 | # find_package(Boost REQUIRED COMPONENTS system)
24 |
25 |
26 | ## Uncomment this if the package has a setup.py. This macro ensures
27 | ## modules and global scripts declared therein get installed
28 | ## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html
29 | # catkin_python_setup()
30 |
31 | ################################################
32 | ## Declare ROS messages, services and actions ##
33 | ################################################
34 |
35 | ## To declare and build messages, services or actions from within this
36 | ## package, follow these steps:
37 | ## * Let MSG_DEP_SET be the set of packages whose message types you use in
38 | ## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...).
39 | ## * In the file package.xml:
40 | ## * add a build_depend tag for "message_generation"
41 | ## * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET
42 | ## * If MSG_DEP_SET isn't empty the following dependency has been pulled in
43 | ## but can be declared for certainty nonetheless:
44 | ## * add a exec_depend tag for "message_runtime"
45 | ## * In this file (CMakeLists.txt):
46 | ## * add "message_generation" and every package in MSG_DEP_SET to
47 | ## find_package(catkin REQUIRED COMPONENTS ...)
48 | ## * add "message_runtime" and every package in MSG_DEP_SET to
49 | ## catkin_package(CATKIN_DEPENDS ...)
50 | ## * uncomment the add_*_files sections below as needed
51 | ## and list every .msg/.srv/.action file to be processed
52 | ## * uncomment the generate_messages entry below
53 | ## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...)
54 |
55 | ## Generate messages in the 'msg' folder
56 | # add_message_files(
57 | # FILES
58 | # Message1.msg
59 | # Message2.msg
60 | # )
61 |
62 | ## Generate services in the 'srv' folder
63 | # add_service_files(
64 | # FILES
65 | # Service1.srv
66 | # Service2.srv
67 | # )
68 |
69 | ## Generate actions in the 'action' folder
70 | # add_action_files(
71 | # FILES
72 | # Action1.action
73 | # Action2.action
74 | # )
75 |
76 | ## Generate added messages and services with any dependencies listed here
77 | # generate_messages(
78 | # DEPENDENCIES
79 | # sensor_msgs# std_msgs
80 | # )
81 |
82 | ################################################
83 | ## Declare ROS dynamic reconfigure parameters ##
84 | ################################################
85 |
86 | ## To declare and build dynamic reconfigure parameters within this
87 | ## package, follow these steps:
88 | ## * In the file package.xml:
89 | ## * add a build_depend and a exec_depend tag for "dynamic_reconfigure"
90 | ## * In this file (CMakeLists.txt):
91 | ## * add "dynamic_reconfigure" to
92 | ## find_package(catkin REQUIRED COMPONENTS ...)
93 | ## * uncomment the "generate_dynamic_reconfigure_options" section below
94 | ## and list every .cfg file to be processed
95 |
96 | ## Generate dynamic reconfigure parameters in the 'cfg' folder
97 | # generate_dynamic_reconfigure_options(
98 | # cfg/DynReconf1.cfg
99 | # cfg/DynReconf2.cfg
100 | # )
101 |
102 | ###################################
103 | ## catkin specific configuration ##
104 | ###################################
105 | ## The catkin_package macro generates cmake config files for your package
106 | ## Declare things to be passed to dependent projects
107 | ## INCLUDE_DIRS: uncomment this if your package contains header files
108 | ## LIBRARIES: libraries you create in this project that dependent projects also need
109 | ## CATKIN_DEPENDS: catkin_packages dependent projects also need
110 | ## DEPENDS: system dependencies of this project that dependent projects also need
111 | catkin_package(
112 | # INCLUDE_DIRS include
113 | # LIBRARIES ros_rtsp
114 | CATKIN_DEPENDS roscpp sensor_msgs
115 | # DEPENDS system_lib
116 | )
117 |
118 | ###########
119 | ## Build ##
120 | ###########
121 |
122 | ## Specify additional locations of header files
123 | ## Your package locations should be listed before other locations
124 | include_directories(
125 | include
126 | ${catkin_INCLUDE_DIRS}
127 | ${GST_INCLUDE_DIRS}
128 | )
129 |
130 | ## Declare a C++ library
131 | # add_library(${PROJECT_NAME}
132 | # src/${PROJECT_NAME}/ros_rtsp.cpp
133 | # )
134 | add_library(image_to_rtsp_nodelet src/image2rtsp.cpp src/video.cpp)
135 |
136 |
137 | ## Add cmake target dependencies of the library
138 | ## as an example, code may need to be generated before libraries
139 | ## either from message generation or dynamic reconfigure
140 | # add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
141 |
142 | ## Declare a C++ executable
143 | ## With catkin_make all packages are built within a single CMake context
144 | ## The recommended prefix ensures that target names across packages don't collide
145 |
146 | ## Rename C++ executable without prefix
147 | ## The above recommended prefix causes long target names, the following renames the
148 | ## target back to the shorter version for ease of user use
149 | ## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node"
150 | # set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "")
151 |
152 | ## Add cmake target dependencies of the executable
153 | ## same as for the library above
154 |
155 | ## Specify libraries to link a library or executable target against
156 | target_link_libraries(image_to_rtsp_nodelet
157 | ${catkin_LIBRARIES}
158 | ${GST_LIBRARIES}
159 | )
160 |
161 |
162 | #############
163 | ## Install ##
164 | #############
165 |
166 | # all install targets should use catkin DESTINATION variables
167 | # See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html
168 |
169 | install(TARGETS image_to_rtsp_nodelet
170 | LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
171 | )
172 |
173 | install(DIRECTORY include/
174 | DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
175 | FILES_MATCHING PATTERN "*.h"
176 | )
177 |
178 | install(DIRECTORY launch config
179 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
180 | )
181 |
182 | install(FILES nodelet_plugins.xml
183 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
184 | )
185 |
186 | #############
187 | ## Testing ##
188 | #############
189 |
190 | ## Add gtest based cpp test target and link libraries
191 | # catkin_add_gtest(${PROJECT_NAME}-test test/test_ros_rtsp.cpp)
192 | # if(TARGET ${PROJECT_NAME}-test)
193 | # target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME})
194 | # endif()
195 |
196 | ## Add folders to be run by python nosetests
197 | # catkin_add_nosetests(test)
198 |
--------------------------------------------------------------------------------
/src/image2rtsp.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include "sensor_msgs/Image.h"
10 | #include
11 | #include
12 |
13 |
14 | using namespace std;
15 | using namespace image2rtsp;
16 |
17 |
18 | void Image2RTSPNodelet::onInit() {
19 | string pipeline, mountpoint, bitrate, caps;
20 | string pipeline_tail = " key-int-max=30 ! video/x-h264, profile=baseline ! rtph264pay name=pay0 pt=96 )"; // Gets completed based on rosparams below
21 |
22 | NODELET_DEBUG("Initializing image2rtsp nodelet...");
23 |
24 | if (getenv((char*)"GST_DEBUG") == NULL) {
25 | // set GST_DEBUG to warning if unset
26 | putenv((char*)"GST_DEBUG=*:1");
27 | }
28 |
29 | ros::NodeHandle& nh = getPrivateNodeHandle();
30 |
31 | // Get the parameters from the rosparam server
32 | XmlRpc::XmlRpcValue streams;
33 | nh.getParam("streams", streams);
34 | ROS_ASSERT(streams.getType() == XmlRpc::XmlRpcValue::TypeStruct);
35 | ROS_DEBUG("Number of RTSP streams: %d", streams.size());
36 | nh.getParam("port", this->port);
37 |
38 | video_mainloop_start();
39 | rtsp_server = rtsp_server_create(port);
40 |
41 | // Go through and parse each stream
42 | for(XmlRpc::XmlRpcValue::ValueStruct::const_iterator it = streams.begin(); it != streams.end(); ++it)
43 | {
44 | XmlRpc::XmlRpcValue stream = streams[it->first];
45 | ROS_DEBUG_STREAM("Found stream: " << (std::string)(it->first) << " ==> " << stream);
46 |
47 | // Convert to string for ease of use
48 | mountpoint = static_cast(stream["mountpoint"]);
49 | bitrate = std::to_string(static_cast(stream["bitrate"]));
50 |
51 | // uvc camera?
52 | if (stream["type"]=="cam")
53 | {
54 | pipeline = "( " + static_cast(stream["source"]) + " ! x264enc tune=zerolatency bitrate=" + bitrate + pipeline_tail;
55 |
56 | rtsp_server_add_url(mountpoint.c_str(), pipeline.c_str(), NULL);
57 | }
58 | // ROS Image topic?
59 | else if (stream["type"]=="topic")
60 | {
61 | /* Keep record of number of clients connected to each topic.
62 | * so we know to stop subscribing when no-one is connected. */
63 | num_of_clients[mountpoint] = 0;
64 | appsrc[mountpoint] = NULL;
65 | caps = static_cast(stream["caps"]);
66 |
67 | // Setup the full pipeline
68 | pipeline = "( appsrc name=imagesrc do-timestamp=true min-latency=0 max-latency=0 max-bytes=1000 is-live=true ! videoconvert ! videoscale ! " + caps + " ! x264enc tune=zerolatency bitrate=" + bitrate + pipeline_tail;
69 |
70 | // Add the pipeline to the rtsp server
71 | rtsp_server_add_url(mountpoint.c_str(), pipeline.c_str(), (GstElement **)&(appsrc[mountpoint]));
72 | }
73 | else
74 | {
75 | ROS_ERROR("Undefined stream type. Check your stream_setup.yaml file.");
76 | }
77 | NODELET_INFO("Stream available at rtsp://%s:%s%s", gst_rtsp_server_get_address(rtsp_server), port.c_str(), mountpoint.c_str());
78 | }
79 | }
80 |
81 | /* Modified from https://github.com/ProjectArtemis/gst_video_server/blob/master/src/server_nodelet.cpp */
82 | GstCaps* Image2RTSPNodelet::gst_caps_new_from_image(const sensor_msgs::Image::ConstPtr &msg)
83 | {
84 | // http://gstreamer.freedesktop.org/data/doc/gstreamer/head/pwg/html/section-types-definitions.html
85 | static const ros::M_string known_formats = {{
86 | {sensor_msgs::image_encodings::RGB8, "RGB"},
87 | {sensor_msgs::image_encodings::RGB16, "RGB16"},
88 | {sensor_msgs::image_encodings::RGBA8, "RGBA"},
89 | {sensor_msgs::image_encodings::RGBA16, "RGBA16"},
90 | {sensor_msgs::image_encodings::BGR8, "BGR"},
91 | {sensor_msgs::image_encodings::BGR16, "BGR16"},
92 | {sensor_msgs::image_encodings::BGRA8, "BGRA"},
93 | {sensor_msgs::image_encodings::BGRA16, "BGRA16"},
94 | {sensor_msgs::image_encodings::MONO8, "GRAY8"},
95 | {sensor_msgs::image_encodings::MONO16, "GRAY16_LE"},
96 | }};
97 |
98 | if (msg->is_bigendian) {
99 | ROS_ERROR("GST: big endian image format is not supported");
100 | return nullptr;
101 | }
102 |
103 | auto format = known_formats.find(msg->encoding);
104 | if (format == known_formats.end()) {
105 | ROS_ERROR("GST: image format '%s' unknown", msg->encoding.c_str());
106 | return nullptr;
107 | }
108 |
109 | return gst_caps_new_simple("video/x-raw",
110 | "format", G_TYPE_STRING, format->second.c_str(),
111 | "width", G_TYPE_INT, msg->width,
112 | "height", G_TYPE_INT, msg->height,
113 | "framerate", GST_TYPE_FRACTION, 10, 1,
114 | nullptr);
115 | }
116 |
117 |
118 | void Image2RTSPNodelet::imageCallback(const sensor_msgs::Image::ConstPtr& msg, const std::string& topic) {
119 | GstBuffer *buf;
120 |
121 | GstCaps *caps;
122 | char *gst_type, *gst_format=(char *)"";
123 | // g_print("Image encoding: %s\n", msg->encoding.c_str());
124 | if (appsrc[topic] != NULL) {
125 | // Set caps from message
126 | caps = gst_caps_new_from_image(msg);
127 | gst_app_src_set_caps(appsrc[topic], caps);
128 |
129 | buf = gst_buffer_new_allocate(nullptr, msg->data.size(), nullptr);
130 | gst_buffer_fill(buf, 0, msg->data.data(), msg->data.size());
131 | GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_LIVE);
132 |
133 | gst_app_src_push_buffer(appsrc[topic], buf);
134 | }
135 | }
136 |
137 |
138 | void Image2RTSPNodelet::url_connected(string url) {
139 | std::string mountpoint, source, type;
140 |
141 | NODELET_INFO("Client connected: %s", url.c_str());
142 | ros::NodeHandle& nh = getPrivateNodeHandle();
143 |
144 | // Get the parameters from the rosparam server
145 | XmlRpc::XmlRpcValue streams;
146 | nh.getParam("streams", streams);
147 | ROS_ASSERT(streams.getType() == XmlRpc::XmlRpcValue::TypeStruct);
148 |
149 | // Go through and parse each stream
150 | for(XmlRpc::XmlRpcValue::ValueStruct::const_iterator it = streams.begin(); it != streams.end(); ++it)
151 | {
152 | XmlRpc::XmlRpcValue stream = streams[it->first];
153 | type = static_cast(stream["type"]);
154 | mountpoint = static_cast(stream["mountpoint"]);
155 | source = static_cast(stream["source"]);
156 |
157 | // Check which stream the client has connected to
158 | if (type=="topic" && url==mountpoint) {
159 |
160 | if (num_of_clients[url]==0) {
161 | // Subscribe to the ROS topic
162 | subs[url] = nh.subscribe(source, 1, boost::bind(&Image2RTSPNodelet::imageCallback, this, boost::placeholders::_1, url));
163 | }
164 | num_of_clients[url]++;
165 | }
166 | }
167 | }
168 |
169 | void Image2RTSPNodelet::url_disconnected(string url) {
170 | std::string mountpoint;
171 |
172 | NODELET_INFO("Client disconnected: %s", url.c_str());
173 | ros::NodeHandle& nh = getPrivateNodeHandle();
174 |
175 | // Get the parameters from the rosparam server
176 | XmlRpc::XmlRpcValue streams;
177 | nh.getParam("streams", streams);
178 | ROS_ASSERT(streams.getType() == XmlRpc::XmlRpcValue::TypeStruct);
179 |
180 | // Go through and parse each stream
181 | for(XmlRpc::XmlRpcValue::ValueStruct::const_iterator it = streams.begin(); it != streams.end(); ++it)
182 | {
183 | XmlRpc::XmlRpcValue stream = streams[it->first];
184 | mountpoint = static_cast(stream["mountpoint"]);
185 |
186 | // Check which stream the client has disconnected from
187 | if (url==mountpoint) {
188 | if (num_of_clients[url] > 0) num_of_clients[url]--;
189 | if (num_of_clients[url] == 0) {
190 | // No-one else is connected. Stop the subscription.
191 | subs[url].shutdown();
192 | appsrc[url] = NULL;
193 | }
194 | }
195 | }
196 | }
197 |
198 | void Image2RTSPNodelet::print_info(char *s) {
199 | NODELET_INFO("%s",s);
200 | }
201 |
202 | void Image2RTSPNodelet::print_error(char *s) {
203 | NODELET_ERROR("%s",s);
204 | }
205 |
206 | PLUGINLIB_EXPORT_CLASS(image2rtsp::Image2RTSPNodelet, nodelet::Nodelet)
207 |
--------------------------------------------------------------------------------