Skip to content

Commit cea8e8e

Browse files
committed
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 48215bb commit cea8e8e

File tree

5 files changed

+128
-67
lines changed

5 files changed

+128
-67
lines changed

CMakeLists.txt

Lines changed: 35 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)
@@ -43,7 +44,7 @@ include_directories(include
4344
)
4445

4546
## Declare a cpp executable
46-
add_executable(${PROJECT_NAME}
47+
add_library(${PROJECT_NAME}_lib SHARED
4748
src/web_video_server.cpp
4849
src/image_streamer.cpp
4950
src/libav_streamer.cpp
@@ -57,12 +58,8 @@ 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
65-
target_link_libraries(${PROJECT_NAME}
62+
target_link_libraries(${PROJECT_NAME}_lib
6663
async_web_server_cpp::async_web_server_cpp
6764
cv_bridge::cv_bridge
6865
image_transport::image_transport
@@ -76,12 +73,43 @@ target_link_libraries(${PROJECT_NAME}
7673
${swscale_LIBRARIES}
7774
)
7875

76+
## Declare a cpp executable
77+
add_executable(${PROJECT_NAME}
78+
src/web_video_server_node.cpp
79+
)
80+
81+
target_link_libraries(${PROJECT_NAME}
82+
${PROJECT_NAME}_lib
83+
)
84+
85+
ament_target_dependencies(${PROJECT_NAME}_lib
86+
rclcpp_components
87+
sensor_msgs
88+
)
89+
90+
rclcpp_components_register_nodes(${PROJECT_NAME}_lib "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}_lib
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+
install(
112+
TARGETS ${PROJECT_NAME}
85113
DESTINATION lib/${PROJECT_NAME}
86114
)
87115

include/web_video_server/web_video_server.hpp

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,25 +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-
7671
void setup_cleanup_inactive_streams();
7772

7873
bool handle_request(
@@ -104,7 +99,6 @@ class WebVideoServer
10499
void restreamFrames(double max_age);
105100
void cleanup_inactive_streams();
106101

107-
rclcpp::Node::SharedPtr node_;
108102
rclcpp::WallTimer<rclcpp::VoidCallbackType>::SharedPtr cleanup_timer_;
109103

110104
// Parameters

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 & 52 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,26 @@ 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("ros_threads", 2);
65+
declare_parameter("publish_rate", -1.0);
66+
declare_parameter("default_stream_type", "mjpeg");
67+
68+
get_parameter("port", port_);
69+
get_parameter("verbose", verbose_);
70+
get_parameter("address", address_);
6971
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_);
72+
get_parameter("server_threads", server_threads);
73+
get_parameter("ros_threads", ros_threads_);
74+
get_parameter("publish_rate", publish_rate_);
75+
get_parameter("default_stream_type", default_stream_type_);
7476

7577
stream_types_["mjpeg"] = std::make_shared<MjpegStreamerType>();
7678
stream_types_["png"] = std::make_shared<PngStreamerType>();
@@ -102,33 +104,26 @@ WebVideoServer::WebVideoServer(rclcpp::Node::SharedPtr & node)
102104
);
103105
} catch (boost::exception & e) {
104106
RCLCPP_ERROR(
105-
node_->get_logger(), "Exception when creating the web server! %s:%d",
107+
get_logger(), "Exception when creating the web server! %s:%d",
106108
address_.c_str(), port_);
107109
throw;
108110
}
111+
RCLCPP_INFO(get_logger(), "Waiting For connections on %s:%d", address_.c_str(), port_);
112+
if (publish_rate_ > 0) {
113+
create_wall_timer(1s / publish_rate_, [this]() {restreamFrames(1.0 / publish_rate_);});
114+
}
115+
server_->run();
109116
}
110117

111118
WebVideoServer::~WebVideoServer()
112119
{
120+
server_->stop();
113121
}
114122

115123
void WebVideoServer::setup_cleanup_inactive_streams()
116124
{
117125
std::function<void()> callback = std::bind(&WebVideoServer::cleanup_inactive_streams, this);
118-
cleanup_timer_ = node_->create_wall_timer(500ms, callback);
119-
}
120-
121-
void WebVideoServer::spin()
122-
{
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();
131-
server_->stop();
126+
cleanup_timer_ = create_wall_timer(500ms, callback);
132127
}
133128

134129
void WebVideoServer::restreamFrames(double max_age)
@@ -149,7 +144,7 @@ void WebVideoServer::cleanup_inactive_streams()
149144
[](const std::shared_ptr<ImageStreamer> & streamer) {return !streamer->isInactive();});
150145
if (verbose_) {
151146
for (auto itr = new_end; itr < image_subscribers_.end(); ++itr) {
152-
RCLCPP_INFO(node_->get_logger(), "Removed Stream: %s", (*itr)->getTopic().c_str());
147+
RCLCPP_INFO(get_logger(), "Removed Stream: %s", (*itr)->getTopic().c_str());
153148
}
154149
}
155150
image_subscribers_.erase(new_end, image_subscribers_.end());
@@ -162,13 +157,13 @@ bool WebVideoServer::handle_request(
162157
const char * end)
163158
{
164159
if (verbose_) {
165-
RCLCPP_INFO(node_->get_logger(), "Handling Request: %s", request.uri.c_str());
160+
RCLCPP_INFO(get_logger(), "Handling Request: %s", request.uri.c_str());
166161
}
167162

168163
try {
169164
return handler_group_(request, connection, begin, end);
170165
} catch (std::exception & e) {
171-
RCLCPP_WARN(node_->get_logger(), "Error Handling Request: %s", e.what());
166+
RCLCPP_WARN(get_logger(), "Error Handling Request: %s", e.what());
172167
return false;
173168
}
174169
}
@@ -184,7 +179,7 @@ bool WebVideoServer::handle_stream(
184179
// Fallback for topics without corresponding compressed topics
185180
if (type == std::string("ros_compressed")) {
186181
std::string compressed_topic_name = topic + "/compressed";
187-
auto tnat = node_->get_topic_names_and_types();
182+
auto tnat = get_topic_names_and_types();
188183
bool did_find_compressed_topic = false;
189184
for (auto topic_and_types : tnat) {
190185
if (topic_and_types.second.size() > 1) {
@@ -201,13 +196,13 @@ bool WebVideoServer::handle_stream(
201196
}
202197
if (!did_find_compressed_topic) {
203198
RCLCPP_WARN(
204-
node_->get_logger(),
199+
get_logger(),
205200
"Could not find compressed image topic for %s, falling back to mjpeg", topic.c_str());
206201
type = "mjpeg";
207202
}
208203
}
209204
std::shared_ptr<ImageStreamer> streamer = stream_types_[type]->create_streamer(
210-
request, connection, node_);
205+
request, connection, shared_from_this());
211206
streamer->start();
212207
std::scoped_lock lock(subscriber_mutex_);
213208
image_subscribers_.push_back(streamer);
@@ -224,7 +219,7 @@ bool WebVideoServer::handle_snapshot(
224219
const char * end)
225220
{
226221
std::shared_ptr<ImageStreamer> streamer = std::make_shared<JpegSnapshotStreamer>(
227-
request, connection, node_);
222+
request, connection, shared_from_this());
228223
streamer->start();
229224

230225
std::scoped_lock lock(subscriber_mutex_);
@@ -243,7 +238,7 @@ bool WebVideoServer::handle_stream_viewer(
243238
// Fallback for topics without corresponding compressed topics
244239
if (type == std::string("ros_compressed")) {
245240
std::string compressed_topic_name = topic + "/compressed";
246-
auto tnat = node_->get_topic_names_and_types();
241+
auto tnat = get_topic_names_and_types();
247242
bool did_find_compressed_topic = false;
248243
for (auto topic_and_types : tnat) {
249244
if (topic_and_types.second.size() > 1) {
@@ -260,7 +255,7 @@ bool WebVideoServer::handle_stream_viewer(
260255
}
261256
if (!did_find_compressed_topic) {
262257
RCLCPP_WARN(
263-
node_->get_logger(),
258+
get_logger(),
264259
"Could not find compressed image topic for %s, falling back to mjpeg", topic.c_str());
265260
type = "mjpeg";
266261
}
@@ -292,7 +287,7 @@ bool WebVideoServer::handle_list_streams(
292287
{
293288
std::vector<std::string> image_topics;
294289
std::vector<std::string> camera_info_topics;
295-
auto tnat = node_->get_topic_names_and_types();
290+
auto tnat = get_topic_names_and_types();
296291
for (auto topic_and_types : tnat) {
297292
if (topic_and_types.second.size() > 1) {
298293
// skip over topics with more than one type
@@ -380,14 +375,9 @@ bool WebVideoServer::handle_list_streams(
380375

381376
} // namespace web_video_server
382377

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();
378+
#include "rclcpp_components/register_node_macro.hpp"
391379

392-
return 0;
393-
}
380+
// Register the component with class_loader.
381+
// This acts as a sort of entry point, allowing the component to be discoverable when its library
382+
// is being loaded into a running process.
383+
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+
node->setup_cleanup_inactive_streams();
39+
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)