Skip to content

Commit f3d5f94

Browse files
authored
Separate web_video_server into a component and an executable (#168)
* Separate web_video_server into a component and an executable The [ROS 2 (Humble) docs](https://docs.ros.org/en/humble/Concepts/Intermediate/About-Composition.html#writing-a-component) recommend writing applications as components: > By making the process layout a deploy-time decision the user can > choose between: > > * running multiple nodes in separate processes with the benefits > of process/fault isolation as well as easier debugging of individual > nodes and > > * running multiple nodes in a single process with the lower overhead > and optionally more efficient communication. The default deployment scheme for the `web_video_server` node remains the same: i.e., using `ros2 run web_video_server web_video_server {parameter-args-here}`. Having a component library available adds flexibility for users, particularly for those users looking to use intra-process memory for published camera topics and the web video server.
1 parent 654fe26 commit f3d5f94

File tree

5 files changed

+130
-73
lines changed

5 files changed

+130
-73
lines changed

CMakeLists.txt

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ find_package(async_web_server_cpp REQUIRED)
77
find_package(cv_bridge REQUIRED)
88
find_package(image_transport REQUIRED)
99
find_package(rclcpp REQUIRED)
10+
find_package(rclcpp_components REQUIRED)
1011
find_package(sensor_msgs REQUIRED)
1112

1213
find_package(OpenCV REQUIRED)
@@ -42,8 +43,8 @@ include_directories(include
4243
${swscale_INCLUDE_DIRS}
4344
)
4445

45-
## Declare a cpp executable
46-
add_executable(${PROJECT_NAME}
46+
## Declare a cpp library
47+
add_library(${PROJECT_NAME} SHARED
4748
src/web_video_server.cpp
4849
src/image_streamer.cpp
4950
src/libav_streamer.cpp
@@ -57,10 +58,6 @@ add_executable(${PROJECT_NAME}
5758
src/utils.cpp
5859
)
5960

60-
ament_target_dependencies(${PROJECT_NAME}
61-
sensor_msgs
62-
)
63-
6461
## Specify libraries to link a library or executable target against
6562
target_link_libraries(${PROJECT_NAME}
6663
async_web_server_cpp::async_web_server_cpp
@@ -76,12 +73,44 @@ target_link_libraries(${PROJECT_NAME}
7673
${swscale_LIBRARIES}
7774
)
7875

76+
## Declare a cpp executable
77+
add_executable(${PROJECT_NAME}_node
78+
src/web_video_server_node.cpp
79+
)
80+
81+
target_link_libraries(${PROJECT_NAME}_node
82+
${PROJECT_NAME}
83+
)
84+
85+
ament_target_dependencies(${PROJECT_NAME}
86+
rclcpp_components
87+
sensor_msgs
88+
)
89+
90+
rclcpp_components_register_nodes(${PROJECT_NAME} "web_video_server::WebVideoServer")
91+
7992
#############
8093
## Install ##
8194
#############
8295

8396
## Mark executables and/or libraries for installation
84-
install(TARGETS ${PROJECT_NAME}
97+
install(
98+
DIRECTORY include/
99+
DESTINATION include/${PROJECT_NAME}
100+
)
101+
102+
install(
103+
TARGETS ${PROJECT_NAME}
104+
EXPORT export_${PROJECT_NAME}
105+
LIBRARY DESTINATION lib
106+
ARCHIVE DESTINATION lib
107+
RUNTIME DESTINATION bin
108+
)
109+
ament_export_targets(export_${PROJECT_NAME} HAS_LIBRARY_TARGET)
110+
111+
set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
112+
install(
113+
TARGETS ${PROJECT_NAME}_node
85114
DESTINATION lib/${PROJECT_NAME}
86115
)
87116

include/web_video_server/web_video_server.hpp

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,27 +54,20 @@ namespace web_video_server
5454
* @class WebVideoServer
5555
* @brief
5656
*/
57-
class WebVideoServer
57+
class WebVideoServer : public rclcpp::Node
5858
{
5959
public:
6060
/**
6161
* @brief Constructor
6262
* @return
6363
*/
64-
explicit WebVideoServer(rclcpp::Node::SharedPtr & node);
64+
explicit WebVideoServer(const rclcpp::NodeOptions & options);
6565

6666
/**
6767
* @brief Destructor - Cleans up
6868
*/
6969
virtual ~WebVideoServer();
7070

71-
/**
72-
* @brief Starts the server and spins
73-
*/
74-
void spin();
75-
76-
void setup_cleanup_inactive_streams();
77-
7871
bool handle_request(
7972
const async_web_server_cpp::HttpRequest & request,
8073
async_web_server_cpp::HttpConnectionPtr connection,
@@ -104,8 +97,7 @@ class WebVideoServer
10497
void restreamFrames(double max_age);
10598
void cleanup_inactive_streams();
10699

107-
rclcpp::Node::SharedPtr node_;
108-
rclcpp::WallTimer<rclcpp::VoidCallbackType>::SharedPtr cleanup_timer_;
100+
rclcpp::TimerBase::SharedPtr cleanup_timer_;
109101

110102
// Parameters
111103
int ros_threads_;

package.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
<buildtool_depend>ament_cmake_ros</buildtool_depend>
1919

2020
<build_depend>rclcpp</build_depend>
21+
<build_depend>rclcpp_components</build_depend>
2122
<build_depend>cv_bridge</build_depend>
2223
<build_depend>image_transport</build_depend>
2324
<build_depend>async_web_server_cpp</build_depend>
2425
<build_depend>ffmpeg</build_depend>
2526
<build_depend>sensor_msgs</build_depend>
2627

2728
<exec_depend>rclcpp</exec_depend>
29+
<exec_depend>rclcpp_components</exec_depend>
2830
<exec_depend>cv_bridge</exec_depend>
2931
<exec_depend>image_transport</exec_depend>
3032
<exec_depend>async_web_server_cpp</exec_depend>

src/web_video_server.cpp

Lines changed: 42 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
#include <boost/algorithm/string/predicate.hpp>
3737
#include <opencv2/opencv.hpp>
3838

39+
#include "rclcpp/rclcpp.hpp"
40+
3941
#include "sensor_msgs/image_encodings.hpp"
4042
#include "web_video_server/ros_compressed_streamer.hpp"
4143
#include "web_video_server/jpeg_streamers.hpp"
@@ -51,26 +53,24 @@ using namespace boost::placeholders; // NOLINT
5153
namespace web_video_server
5254
{
5355

54-
WebVideoServer::WebVideoServer(rclcpp::Node::SharedPtr & node)
55-
: node_(node), handler_group_(
56+
WebVideoServer::WebVideoServer(const rclcpp::NodeOptions & options)
57+
: rclcpp::Node("web_video_server", options), handler_group_(
5658
async_web_server_cpp::HttpReply::stock_reply(async_web_server_cpp::HttpReply::not_found))
5759
{
58-
node_->declare_parameter("port", 8080);
59-
node_->declare_parameter("verbose", true);
60-
node_->declare_parameter("address", "0.0.0.0");
61-
node_->declare_parameter("server_threads", 1);
62-
node_->declare_parameter("ros_threads", 2);
63-
node_->declare_parameter("publish_rate", -1.0);
64-
node_->declare_parameter("default_stream_type", "mjpeg");
65-
66-
node_->get_parameter("port", port_);
67-
node_->get_parameter("verbose", verbose_);
68-
node_->get_parameter("address", address_);
60+
declare_parameter("port", 8080);
61+
declare_parameter("verbose", true);
62+
declare_parameter("address", "0.0.0.0");
63+
declare_parameter("server_threads", 1);
64+
declare_parameter("publish_rate", -1.0);
65+
declare_parameter("default_stream_type", "mjpeg");
66+
67+
get_parameter("port", port_);
68+
get_parameter("verbose", verbose_);
69+
get_parameter("address", address_);
6970
int server_threads;
70-
node_->get_parameter("server_threads", server_threads);
71-
node_->get_parameter("ros_threads", ros_threads_);
72-
node_->get_parameter("publish_rate", publish_rate_);
73-
node_->get_parameter("default_stream_type", default_stream_type_);
71+
get_parameter("server_threads", server_threads);
72+
get_parameter("publish_rate", publish_rate_);
73+
get_parameter("default_stream_type", default_stream_type_);
7474

7575
stream_types_["mjpeg"] = std::make_shared<MjpegStreamerType>();
7676
stream_types_["png"] = std::make_shared<PngStreamerType>();
@@ -102,32 +102,24 @@ WebVideoServer::WebVideoServer(rclcpp::Node::SharedPtr & node)
102102
);
103103
} catch (boost::exception & e) {
104104
RCLCPP_ERROR(
105-
node_->get_logger(), "Exception when creating the web server! %s:%d",
105+
get_logger(), "Exception when creating the web server! %s:%d",
106106
address_.c_str(), port_);
107107
throw;
108108
}
109-
}
110109

111-
WebVideoServer::~WebVideoServer()
112-
{
113-
}
110+
RCLCPP_INFO(get_logger(), "Waiting For connections on %s:%d", address_.c_str(), port_);
114111

115-
void WebVideoServer::setup_cleanup_inactive_streams()
116-
{
117-
std::function<void()> callback = std::bind(&WebVideoServer::cleanup_inactive_streams, this);
118-
cleanup_timer_ = node_->create_wall_timer(500ms, callback);
112+
if (publish_rate_ > 0) {
113+
create_wall_timer(1s / publish_rate_, [this]() {restreamFrames(1.0 / publish_rate_);});
114+
}
115+
116+
cleanup_timer_ = create_wall_timer(500ms, [this]() {cleanup_inactive_streams();});
117+
118+
server_->run();
119119
}
120120

121-
void WebVideoServer::spin()
121+
WebVideoServer::~WebVideoServer()
122122
{
123-
server_->run();
124-
RCLCPP_INFO(node_->get_logger(), "Waiting For connections on %s:%d", address_.c_str(), port_);
125-
rclcpp::executors::MultiThreadedExecutor spinner(rclcpp::ExecutorOptions(), ros_threads_);
126-
spinner.add_node(node_);
127-
if (publish_rate_ > 0) {
128-
node_->create_wall_timer(1s / publish_rate_, [this]() {restreamFrames(1.0 / publish_rate_);});
129-
}
130-
spinner.spin();
131123
server_->stop();
132124
}
133125

@@ -149,7 +141,7 @@ void WebVideoServer::cleanup_inactive_streams()
149141
[](const std::shared_ptr<ImageStreamer> & streamer) {return !streamer->isInactive();});
150142
if (verbose_) {
151143
for (auto itr = new_end; itr < image_subscribers_.end(); ++itr) {
152-
RCLCPP_INFO(node_->get_logger(), "Removed Stream: %s", (*itr)->getTopic().c_str());
144+
RCLCPP_INFO(get_logger(), "Removed Stream: %s", (*itr)->getTopic().c_str());
153145
}
154146
}
155147
image_subscribers_.erase(new_end, image_subscribers_.end());
@@ -162,13 +154,13 @@ bool WebVideoServer::handle_request(
162154
const char * end)
163155
{
164156
if (verbose_) {
165-
RCLCPP_INFO(node_->get_logger(), "Handling Request: %s", request.uri.c_str());
157+
RCLCPP_INFO(get_logger(), "Handling Request: %s", request.uri.c_str());
166158
}
167159

168160
try {
169161
return handler_group_(request, connection, begin, end);
170162
} catch (std::exception & e) {
171-
RCLCPP_WARN(node_->get_logger(), "Error Handling Request: %s", e.what());
163+
RCLCPP_WARN(get_logger(), "Error Handling Request: %s", e.what());
172164
return false;
173165
}
174166
}
@@ -184,7 +176,7 @@ bool WebVideoServer::handle_stream(
184176
// Fallback for topics without corresponding compressed topics
185177
if (type == std::string("ros_compressed")) {
186178
std::string compressed_topic_name = topic + "/compressed";
187-
auto tnat = node_->get_topic_names_and_types();
179+
auto tnat = get_topic_names_and_types();
188180
bool did_find_compressed_topic = false;
189181
for (auto topic_and_types : tnat) {
190182
if (topic_and_types.second.size() > 1) {
@@ -201,13 +193,13 @@ bool WebVideoServer::handle_stream(
201193
}
202194
if (!did_find_compressed_topic) {
203195
RCLCPP_WARN(
204-
node_->get_logger(),
196+
get_logger(),
205197
"Could not find compressed image topic for %s, falling back to mjpeg", topic.c_str());
206198
type = "mjpeg";
207199
}
208200
}
209201
std::shared_ptr<ImageStreamer> streamer = stream_types_[type]->create_streamer(
210-
request, connection, node_);
202+
request, connection, shared_from_this());
211203
streamer->start();
212204
std::scoped_lock lock(subscriber_mutex_);
213205
image_subscribers_.push_back(streamer);
@@ -224,7 +216,7 @@ bool WebVideoServer::handle_snapshot(
224216
const char * end)
225217
{
226218
std::shared_ptr<ImageStreamer> streamer = std::make_shared<JpegSnapshotStreamer>(
227-
request, connection, node_);
219+
request, connection, shared_from_this());
228220
streamer->start();
229221

230222
std::scoped_lock lock(subscriber_mutex_);
@@ -243,7 +235,7 @@ bool WebVideoServer::handle_stream_viewer(
243235
// Fallback for topics without corresponding compressed topics
244236
if (type == std::string("ros_compressed")) {
245237
std::string compressed_topic_name = topic + "/compressed";
246-
auto tnat = node_->get_topic_names_and_types();
238+
auto tnat = get_topic_names_and_types();
247239
bool did_find_compressed_topic = false;
248240
for (auto topic_and_types : tnat) {
249241
if (topic_and_types.second.size() > 1) {
@@ -260,7 +252,7 @@ bool WebVideoServer::handle_stream_viewer(
260252
}
261253
if (!did_find_compressed_topic) {
262254
RCLCPP_WARN(
263-
node_->get_logger(),
255+
get_logger(),
264256
"Could not find compressed image topic for %s, falling back to mjpeg", topic.c_str());
265257
type = "mjpeg";
266258
}
@@ -292,7 +284,7 @@ bool WebVideoServer::handle_list_streams(
292284
{
293285
std::vector<std::string> image_topics;
294286
std::vector<std::string> camera_info_topics;
295-
auto tnat = node_->get_topic_names_and_types();
287+
auto tnat = get_topic_names_and_types();
296288
for (auto topic_and_types : tnat) {
297289
if (topic_and_types.second.size() > 1) {
298290
// skip over topics with more than one type
@@ -380,14 +372,9 @@ bool WebVideoServer::handle_list_streams(
380372

381373
} // namespace web_video_server
382374

383-
int main(int argc, char ** argv)
384-
{
385-
rclcpp::init(argc, argv);
386-
auto node = std::make_shared<rclcpp::Node>("web_video_server");
387-
388-
web_video_server::WebVideoServer server(node);
389-
server.setup_cleanup_inactive_streams();
390-
server.spin();
375+
#include "rclcpp_components/register_node_macro.hpp"
391376

392-
return 0;
393-
}
377+
// Register the component with class_loader.
378+
// This acts as a sort of entry point, allowing the component to be discoverable when its library
379+
// is being loaded into a running process.
380+
RCLCPP_COMPONENTS_REGISTER_NODE(web_video_server::WebVideoServer)

src/web_video_server_node.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) 2014, Worcester Polytechnic Institute
2+
// Copyright (c) 2024, The Robot Web Tools Contributors
3+
// All rights reserved.
4+
//
5+
// Redistribution and use in source and binary forms, with or without
6+
// modification, are permitted provided that the following conditions are met:
7+
//
8+
// * Redistributions of source code must retain the above copyright
9+
// notice, this list of conditions and the following disclaimer.
10+
//
11+
// * Redistributions in binary form must reproduce the above copyright
12+
// notice, this list of conditions and the following disclaimer in the
13+
// documentation and/or other materials provided with the distribution.
14+
//
15+
// * Neither the name of the copyright holder nor the names of its
16+
// contributors may be used to endorse or promote products derived from
17+
// this software without specific prior written permission.
18+
//
19+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22+
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23+
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24+
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25+
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26+
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27+
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28+
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29+
// POSSIBILITY OF SUCH DAMAGE.
30+
#include "rclcpp/rclcpp.hpp"
31+
32+
#include "web_video_server/web_video_server.hpp"
33+
34+
int main(int argc, char ** argv)
35+
{
36+
rclcpp::init(argc, argv);
37+
auto node = std::make_shared<web_video_server::WebVideoServer>(rclcpp::NodeOptions());
38+
39+
node->declare_parameter("ros_threads", 2);
40+
int ros_threads;
41+
node->get_parameter("ros_threads", ros_threads);
42+
rclcpp::executors::MultiThreadedExecutor spinner(rclcpp::ExecutorOptions(), ros_threads);
43+
spinner.add_node(node);
44+
spinner.spin();
45+
46+
return 0;
47+
}

0 commit comments

Comments
 (0)