diff --git a/CMakeLists.txt b/CMakeLists.txt index d72cbc2..21c9717 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ find_package(rclcpp REQUIRED) find_package(sensor_msgs REQUIRED) find_package(OpenCV REQUIRED) -find_package(Boost REQUIRED COMPONENTS thread) +find_package(Boost REQUIRED COMPONENTS system) find_package(PkgConfig REQUIRED) pkg_check_modules(avcodec libavcodec REQUIRED) @@ -36,7 +36,6 @@ endif() ## Specify additional locations of header files include_directories(include - ${Boost_INCLUDE_DIRS} ${avcodec_INCLUDE_DIRS} ${avformat_INCLUDE_DIRS} ${avutil_INCLUDE_DIRS} @@ -68,7 +67,8 @@ target_link_libraries(${PROJECT_NAME} cv_bridge::cv_bridge image_transport::image_transport rclcpp::rclcpp - ${Boost_LIBRARIES} + Boost::boost + Boost::system ${OpenCV_LIBS} ${avcodec_LIBRARIES} ${avformat_LIBRARIES} diff --git a/include/web_video_server/h264_streamer.hpp b/include/web_video_server/h264_streamer.hpp index 6539ffc..3df01b4 100644 --- a/include/web_video_server/h264_streamer.hpp +++ b/include/web_video_server/h264_streamer.hpp @@ -29,6 +29,7 @@ #pragma once +#include #include #include "image_transport/image_transport.hpp" @@ -57,7 +58,7 @@ class H264StreamerType : public LibavStreamerType { public: H264StreamerType(); - virtual boost::shared_ptr create_streamer( + std::shared_ptr create_streamer( const async_web_server_cpp::HttpRequest & request, async_web_server_cpp::HttpConnectionPtr connection, rclcpp::Node::SharedPtr node); diff --git a/include/web_video_server/image_streamer.hpp b/include/web_video_server/image_streamer.hpp index 974b9ee..74c75f9 100644 --- a/include/web_video_server/image_streamer.hpp +++ b/include/web_video_server/image_streamer.hpp @@ -30,6 +30,7 @@ #pragma once +#include #include #include @@ -106,7 +107,7 @@ class ImageTransportImageStreamer : public ImageStreamer rclcpp::Time last_frame; cv::Mat output_size_image; - boost::mutex send_mutex_; + std::mutex send_mutex_; private: image_transport::ImageTransport it_; @@ -118,7 +119,7 @@ class ImageTransportImageStreamer : public ImageStreamer class ImageStreamerType { public: - virtual boost::shared_ptr create_streamer( + virtual std::shared_ptr create_streamer( const async_web_server_cpp::HttpRequest & request, async_web_server_cpp::HttpConnectionPtr connection, rclcpp::Node::SharedPtr node) = 0; diff --git a/include/web_video_server/jpeg_streamers.hpp b/include/web_video_server/jpeg_streamers.hpp index 47ab944..6946215 100644 --- a/include/web_video_server/jpeg_streamers.hpp +++ b/include/web_video_server/jpeg_streamers.hpp @@ -30,6 +30,7 @@ #pragma once +#include #include #include "image_transport/image_transport.hpp" @@ -61,7 +62,7 @@ class MjpegStreamer : public ImageTransportImageStreamer class MjpegStreamerType : public ImageStreamerType { public: - boost::shared_ptr create_streamer( + std::shared_ptr create_streamer( const async_web_server_cpp::HttpRequest & request, async_web_server_cpp::HttpConnectionPtr connection, rclcpp::Node::SharedPtr node); diff --git a/include/web_video_server/libav_streamer.hpp b/include/web_video_server/libav_streamer.hpp index aeed2b2..2f4db10 100644 --- a/include/web_video_server/libav_streamer.hpp +++ b/include/web_video_server/libav_streamer.hpp @@ -42,6 +42,7 @@ extern "C" #include } +#include #include #include "image_transport/image_transport.hpp" @@ -78,7 +79,7 @@ class LibavStreamer : public ImageTransportImageStreamer AVFrame * frame_; struct SwsContext * sws_context_; rclcpp::Time first_image_timestamp_; - boost::mutex encode_mutex_; + std::mutex encode_mutex_; std::string format_name_; std::string codec_name_; @@ -98,7 +99,7 @@ class LibavStreamerType : public ImageStreamerType const std::string & format_name, const std::string & codec_name, const std::string & content_type); - boost::shared_ptr create_streamer( + std::shared_ptr create_streamer( const async_web_server_cpp::HttpRequest & request, async_web_server_cpp::HttpConnectionPtr connection, rclcpp::Node::SharedPtr node); diff --git a/include/web_video_server/png_streamers.hpp b/include/web_video_server/png_streamers.hpp index f871f4f..14f370e 100644 --- a/include/web_video_server/png_streamers.hpp +++ b/include/web_video_server/png_streamers.hpp @@ -29,6 +29,7 @@ #pragma once +#include #include #include "image_transport/image_transport.hpp" @@ -61,7 +62,7 @@ class PngStreamer : public ImageTransportImageStreamer class PngStreamerType : public ImageStreamerType { public: - boost::shared_ptr create_streamer( + std::shared_ptr create_streamer( const async_web_server_cpp::HttpRequest & request, async_web_server_cpp::HttpConnectionPtr connection, rclcpp::Node::SharedPtr node); diff --git a/include/web_video_server/ros_compressed_streamer.hpp b/include/web_video_server/ros_compressed_streamer.hpp index 6b3af51..aceb076 100644 --- a/include/web_video_server/ros_compressed_streamer.hpp +++ b/include/web_video_server/ros_compressed_streamer.hpp @@ -30,6 +30,7 @@ #pragma once +#include #include #include "sensor_msgs/msg/compressed_image.hpp" @@ -63,14 +64,14 @@ class RosCompressedStreamer : public ImageStreamer rclcpp::Subscription::SharedPtr image_sub_; rclcpp::Time last_frame; sensor_msgs::msg::CompressedImage::ConstSharedPtr last_msg; - boost::mutex send_mutex_; + std::mutex send_mutex_; std::string qos_profile_name_; }; class RosCompressedStreamerType : public ImageStreamerType { public: - boost::shared_ptr create_streamer( + std::shared_ptr create_streamer( const async_web_server_cpp::HttpRequest & request, async_web_server_cpp::HttpConnectionPtr connection, rclcpp::Node::SharedPtr node); diff --git a/include/web_video_server/vp8_streamer.hpp b/include/web_video_server/vp8_streamer.hpp index 97b1bdd..ad6fd2f 100644 --- a/include/web_video_server/vp8_streamer.hpp +++ b/include/web_video_server/vp8_streamer.hpp @@ -30,6 +30,7 @@ #pragma once +#include #include #include "image_transport/image_transport.hpp" @@ -60,7 +61,7 @@ class Vp8StreamerType : public LibavStreamerType { public: Vp8StreamerType(); - virtual boost::shared_ptr create_streamer( + std::shared_ptr create_streamer( const async_web_server_cpp::HttpRequest & request, async_web_server_cpp::HttpConnectionPtr connection, rclcpp::Node::SharedPtr node); diff --git a/include/web_video_server/vp9_streamer.hpp b/include/web_video_server/vp9_streamer.hpp index ca94df6..5491db7 100644 --- a/include/web_video_server/vp9_streamer.hpp +++ b/include/web_video_server/vp9_streamer.hpp @@ -29,6 +29,8 @@ #pragma once +#include + #include "image_transport/image_transport.hpp" #include "web_video_server/libav_streamer.hpp" #include "async_web_server_cpp/http_request.hpp" @@ -54,7 +56,7 @@ class Vp9StreamerType : public LibavStreamerType { public: Vp9StreamerType(); - virtual boost::shared_ptr create_streamer( + std::shared_ptr create_streamer( const async_web_server_cpp::HttpRequest & request, async_web_server_cpp::HttpConnectionPtr connection, rclcpp::Node::SharedPtr node); diff --git a/include/web_video_server/web_video_server.hpp b/include/web_video_server/web_video_server.hpp index e78273d..bbaf33c 100644 --- a/include/web_video_server/web_video_server.hpp +++ b/include/web_video_server/web_video_server.hpp @@ -31,6 +31,7 @@ #pragma once #include +#include #include #include @@ -114,12 +115,12 @@ class WebVideoServer bool verbose_; std::string default_stream_type_; - boost::shared_ptr server_; + std::shared_ptr server_; async_web_server_cpp::HttpRequestHandlerGroup handler_group_; - std::vector> image_subscribers_; - std::map> stream_types_; - boost::mutex subscriber_mutex_; + std::vector> image_subscribers_; + std::map> stream_types_; + std::mutex subscriber_mutex_; }; } // namespace web_video_server diff --git a/src/h264_streamer.cpp b/src/h264_streamer.cpp index 6e195de..05e487f 100644 --- a/src/h264_streamer.cpp +++ b/src/h264_streamer.cpp @@ -69,12 +69,12 @@ H264StreamerType::H264StreamerType() { } -boost::shared_ptr H264StreamerType::create_streamer( +std::shared_ptr H264StreamerType::create_streamer( const async_web_server_cpp::HttpRequest & request, async_web_server_cpp::HttpConnectionPtr connection, rclcpp::Node::SharedPtr node) { - return boost::shared_ptr(new H264Streamer(request, connection, node)); + return std::make_shared(request, connection, node); } } // namespace web_video_server diff --git a/src/image_streamer.cpp b/src/image_streamer.cpp index a698754..5867db6 100644 --- a/src/image_streamer.cpp +++ b/src/image_streamer.cpp @@ -117,7 +117,7 @@ void ImageTransportImageStreamer::restreamFrame(double max_age) } try { if (last_frame + rclcpp::Duration::from_seconds(max_age) < node_->now() ) { - boost::mutex::scoped_lock lock(send_mutex_); + std::scoped_lock lock(send_mutex_); // don't update last_frame, it may remain an old value. sendImage(output_size_image, node_->now()); } @@ -184,7 +184,7 @@ void ImageTransportImageStreamer::imageCallback(const sensor_msgs::msg::Image::C cv::flip(img, img, true); } - boost::mutex::scoped_lock lock(send_mutex_); // protects output_size_image + std::scoped_lock lock(send_mutex_); // protects output_size_image if (output_width_ != input_width || output_height_ != input_height) { cv::Mat img_resized; cv::Size new_size(output_width_, output_height_); diff --git a/src/jpeg_streamers.cpp b/src/jpeg_streamers.cpp index 55c0f17..9123e36 100644 --- a/src/jpeg_streamers.cpp +++ b/src/jpeg_streamers.cpp @@ -47,7 +47,7 @@ MjpegStreamer::MjpegStreamer( MjpegStreamer::~MjpegStreamer() { this->inactive_ = true; - boost::mutex::scoped_lock lock(send_mutex_); // protects sendImage. + std::scoped_lock lock(send_mutex_); // protects sendImage. } void MjpegStreamer::sendImage(const cv::Mat & img, const rclcpp::Time & time) @@ -62,12 +62,12 @@ void MjpegStreamer::sendImage(const cv::Mat & img, const rclcpp::Time & time) stream_.sendPartAndClear(time, "image/jpeg", encoded_buffer); } -boost::shared_ptr MjpegStreamerType::create_streamer( +std::shared_ptr MjpegStreamerType::create_streamer( const async_web_server_cpp::HttpRequest & request, async_web_server_cpp::HttpConnectionPtr connection, rclcpp::Node::SharedPtr node) { - return boost::shared_ptr(new MjpegStreamer(request, connection, node)); + return std::make_shared(request, connection, node); } std::string MjpegStreamerType::create_viewer(const async_web_server_cpp::HttpRequest & request) @@ -91,7 +91,7 @@ JpegSnapshotStreamer::JpegSnapshotStreamer( JpegSnapshotStreamer::~JpegSnapshotStreamer() { this->inactive_ = true; - boost::mutex::scoped_lock lock(send_mutex_); // protects sendImage. + std::scoped_lock lock(send_mutex_); // protects sendImage. } void JpegSnapshotStreamer::sendImage(const cv::Mat & img, const rclcpp::Time & time) @@ -115,7 +115,7 @@ void JpegSnapshotStreamer::sendImage(const cv::Mat & img, const rclcpp::Time & t .header("Pragma", "no-cache") .header("Content-type", "image/jpeg") .header("Access-Control-Allow-Origin", "*") - .header("Content-Length", boost::lexical_cast(encoded_buffer.size())) + .header("Content-Length", std::to_string(encoded_buffer.size())) .write(connection_); connection_->write_and_clear(encoded_buffer); inactive_ = true; diff --git a/src/libav_streamer.cpp b/src/libav_streamer.cpp index 16ea546..126fa6b 100644 --- a/src/libav_streamer.cpp +++ b/src/libav_streamer.cpp @@ -217,7 +217,7 @@ void LibavStreamer::initializeEncoder() void LibavStreamer::sendImage(const cv::Mat & img, const rclcpp::Time & time) { - boost::mutex::scoped_lock lock(encode_mutex_); + std::scoped_lock lock(encode_mutex_); if (0 == first_image_timestamp_.nanoseconds()) { first_image_timestamp_ = time; } @@ -303,13 +303,14 @@ LibavStreamerType::LibavStreamerType( { } -boost::shared_ptr LibavStreamerType::create_streamer( +std::shared_ptr LibavStreamerType::create_streamer( const async_web_server_cpp::HttpRequest & request, async_web_server_cpp::HttpConnectionPtr connection, rclcpp::Node::SharedPtr node) { - return boost::shared_ptr( - new LibavStreamer(request, connection, node, format_name_, codec_name_, content_type_)); + return std::make_shared( + request, connection, node, format_name_, codec_name_, + content_type_); } std::string LibavStreamerType::create_viewer(const async_web_server_cpp::HttpRequest & request) diff --git a/src/multipart_stream.cpp b/src/multipart_stream.cpp index 86ccbfc..c43de8a 100644 --- a/src/multipart_stream.cpp +++ b/src/multipart_stream.cpp @@ -69,8 +69,7 @@ void MultipartStream::sendPartHeader( headers->push_back(async_web_server_cpp::HttpHeader("X-Timestamp", stamp)); headers->push_back( async_web_server_cpp::HttpHeader( - "Content-Length", - boost::lexical_cast(payload_size))); + "Content-Length", std::to_string(payload_size))); connection_->write(async_web_server_cpp::HttpReply::to_buffers(*headers), headers); } diff --git a/src/png_streamers.cpp b/src/png_streamers.cpp index 8c29183..d801097 100644 --- a/src/png_streamers.cpp +++ b/src/png_streamers.cpp @@ -52,7 +52,7 @@ PngStreamer::PngStreamer( PngStreamer::~PngStreamer() { this->inactive_ = true; - boost::mutex::scoped_lock lock(send_mutex_); // protects sendImage. + std::scoped_lock lock(send_mutex_); // protects sendImage. } cv::Mat PngStreamer::decodeImage(const sensor_msgs::msg::Image::ConstSharedPtr & msg) @@ -78,12 +78,12 @@ void PngStreamer::sendImage(const cv::Mat & img, const rclcpp::Time & time) stream_.sendPartAndClear(time, "image/png", encoded_buffer); } -boost::shared_ptr PngStreamerType::create_streamer( +std::shared_ptr PngStreamerType::create_streamer( const async_web_server_cpp::HttpRequest & request, async_web_server_cpp::HttpConnectionPtr connection, rclcpp::Node::SharedPtr node) { - return boost::shared_ptr(new PngStreamer(request, connection, node)); + return std::make_shared(request, connection, node); } std::string PngStreamerType::create_viewer(const async_web_server_cpp::HttpRequest & request) @@ -107,7 +107,7 @@ PngSnapshotStreamer::PngSnapshotStreamer( PngSnapshotStreamer::~PngSnapshotStreamer() { this->inactive_ = true; - boost::mutex::scoped_lock lock(send_mutex_); // protects sendImage. + std::scoped_lock lock(send_mutex_); // protects sendImage. } cv::Mat PngSnapshotStreamer::decodeImage(const sensor_msgs::msg::Image::ConstSharedPtr & msg) @@ -142,7 +142,7 @@ void PngSnapshotStreamer::sendImage(const cv::Mat & img, const rclcpp::Time & ti .header("Pragma", "no-cache") .header("Content-type", "image/png") .header("Access-Control-Allow-Origin", "*") - .header("Content-Length", boost::lexical_cast(encoded_buffer.size())) + .header("Content-Length", std::to_string(encoded_buffer.size())) .write(connection_); connection_->write_and_clear(encoded_buffer); inactive_ = true; diff --git a/src/ros_compressed_streamer.cpp b/src/ros_compressed_streamer.cpp index d1f6f3c..8c906bf 100644 --- a/src/ros_compressed_streamer.cpp +++ b/src/ros_compressed_streamer.cpp @@ -45,7 +45,7 @@ RosCompressedStreamer::RosCompressedStreamer( RosCompressedStreamer::~RosCompressedStreamer() { this->inactive_ = true; - boost::mutex::scoped_lock lock(send_mutex_); // protects sendImage. + std::scoped_lock lock(send_mutex_); // protects sendImage. } void RosCompressedStreamer::start() @@ -81,7 +81,7 @@ void RosCompressedStreamer::restreamFrame(double max_age) } if (last_frame + rclcpp::Duration::from_seconds(max_age) < node_->now() ) { - boost::mutex::scoped_lock lock(send_mutex_); + std::scoped_lock lock(send_mutex_); sendImage(last_msg, node_->now() ); // don't update last_frame, it may remain an old value. } } @@ -128,19 +128,19 @@ void RosCompressedStreamer::sendImage( void RosCompressedStreamer::imageCallback( const sensor_msgs::msg::CompressedImage::ConstSharedPtr msg) { - boost::mutex::scoped_lock lock(send_mutex_); // protects last_msg and last_frame + std::scoped_lock lock(send_mutex_); // protects last_msg and last_frame last_msg = msg; last_frame = rclcpp::Time(msg->header.stamp); sendImage(last_msg, last_frame); } -boost::shared_ptr RosCompressedStreamerType::create_streamer( +std::shared_ptr RosCompressedStreamerType::create_streamer( const async_web_server_cpp::HttpRequest & request, async_web_server_cpp::HttpConnectionPtr connection, rclcpp::Node::SharedPtr node) { - return boost::shared_ptr(new RosCompressedStreamer(request, connection, node)); + return std::make_shared(request, connection, node); } std::string RosCompressedStreamerType::create_viewer( diff --git a/src/vp8_streamer.cpp b/src/vp8_streamer.cpp index 1ff224c..c4c1d0c 100644 --- a/src/vp8_streamer.cpp +++ b/src/vp8_streamer.cpp @@ -56,8 +56,8 @@ void Vp8Streamer::initializeEncoder() av_opt_map["drop_frame"] = "1"; av_opt_map["error-resilient"] = "1"; - for (AvOptMap::iterator itr = av_opt_map.begin(); itr != av_opt_map.end(); ++itr) { - av_opt_set(codec_context_->priv_data, itr->first.c_str(), itr->second.c_str(), 0); + for (auto & opt : av_opt_map) { + av_opt_set(codec_context_->priv_data, opt.first.c_str(), opt.second.c_str(), 0); } // Buffering settings @@ -75,12 +75,12 @@ Vp8StreamerType::Vp8StreamerType() { } -boost::shared_ptr Vp8StreamerType::create_streamer( +std::shared_ptr Vp8StreamerType::create_streamer( const async_web_server_cpp::HttpRequest & request, async_web_server_cpp::HttpConnectionPtr connection, rclcpp::Node::SharedPtr node) { - return boost::shared_ptr(new Vp8Streamer(request, connection, node)); + return std::make_shared(request, connection, node); } } // namespace web_video_server diff --git a/src/vp9_streamer.cpp b/src/vp9_streamer.cpp index 36d5e59..65511a7 100644 --- a/src/vp9_streamer.cpp +++ b/src/vp9_streamer.cpp @@ -57,12 +57,12 @@ Vp9StreamerType::Vp9StreamerType() { } -boost::shared_ptr Vp9StreamerType::create_streamer( +std::shared_ptr Vp9StreamerType::create_streamer( const async_web_server_cpp::HttpRequest & request, async_web_server_cpp::HttpConnectionPtr connection, rclcpp::Node::SharedPtr node) { - return boost::shared_ptr(new Vp9Streamer(request, connection, node)); + return std::make_shared(request, connection, node); } } // namespace web_video_server diff --git a/src/web_video_server.cpp b/src/web_video_server.cpp index 27d1515..3bd67b9 100644 --- a/src/web_video_server.cpp +++ b/src/web_video_server.cpp @@ -33,8 +33,6 @@ #include #include -#include -#include #include #include @@ -74,13 +72,12 @@ WebVideoServer::WebVideoServer(rclcpp::Node::SharedPtr & node) node_->get_parameter("publish_rate", publish_rate_); node_->get_parameter("default_stream_type", default_stream_type_); - stream_types_["mjpeg"] = boost::shared_ptr(new MjpegStreamerType()); - stream_types_["png"] = boost::shared_ptr(new PngStreamerType()); - stream_types_["ros_compressed"] = - boost::shared_ptr(new RosCompressedStreamerType()); - stream_types_["vp8"] = boost::shared_ptr(new Vp8StreamerType()); - stream_types_["h264"] = boost::shared_ptr(new H264StreamerType()); - stream_types_["vp9"] = boost::shared_ptr(new Vp9StreamerType()); + stream_types_["mjpeg"] = std::make_shared(); + stream_types_["png"] = std::make_shared(); + stream_types_["ros_compressed"] = std::make_shared(); + stream_types_["vp8"] = std::make_shared(); + stream_types_["h264"] = std::make_shared(); + stream_types_["vp9"] = std::make_shared(); handler_group_.addHandlerForPath( "/", @@ -98,7 +95,7 @@ WebVideoServer::WebVideoServer(rclcpp::Node::SharedPtr & node) try { server_.reset( new async_web_server_cpp::HttpServer( - address_, boost::lexical_cast(port_), + address_, std::to_string(port_), boost::bind(&WebVideoServer::handle_request, this, _1, _2, _3, _4), server_threads ) @@ -136,25 +133,22 @@ void WebVideoServer::spin() void WebVideoServer::restreamFrames(double max_age) { - boost::mutex::scoped_lock lock(subscriber_mutex_); + std::scoped_lock lock(subscriber_mutex_); - typedef std::vector>::iterator itr_type; - - for (itr_type itr = image_subscribers_.begin(); itr < image_subscribers_.end(); ++itr) { - (*itr)->restreamFrame(max_age); + for (auto & subscriber : image_subscribers_) { + subscriber->restreamFrame(max_age); } } void WebVideoServer::cleanup_inactive_streams() { - boost::mutex::scoped_lock lock(subscriber_mutex_, boost::try_to_lock); + std::unique_lock lock(subscriber_mutex_, std::try_to_lock); if (lock) { - typedef std::vector>::iterator itr_type; - itr_type new_end = std::partition( + auto new_end = std::partition( image_subscribers_.begin(), image_subscribers_.end(), - !boost::bind(&ImageStreamer::isInactive, _1)); + [](const std::shared_ptr & streamer) {return !streamer->isInactive();}); if (verbose_) { - for (itr_type itr = new_end; itr < image_subscribers_.end(); ++itr) { + for (auto itr = new_end; itr < image_subscribers_.end(); ++itr) { RCLCPP_INFO(node_->get_logger(), "Removed Stream: %s", (*itr)->getTopic().c_str()); } } @@ -212,11 +206,10 @@ bool WebVideoServer::handle_stream( type = "mjpeg"; } } - boost::shared_ptr streamer = stream_types_[type]->create_streamer( - request, - connection, node_); + std::shared_ptr streamer = stream_types_[type]->create_streamer( + request, connection, node_); streamer->start(); - boost::mutex::scoped_lock lock(subscriber_mutex_); + std::scoped_lock lock(subscriber_mutex_); image_subscribers_.push_back(streamer); } else { async_web_server_cpp::HttpReply::stock_reply(async_web_server_cpp::HttpReply::not_found)( @@ -230,10 +223,11 @@ bool WebVideoServer::handle_snapshot( async_web_server_cpp::HttpConnectionPtr connection, const char * begin, const char * end) { - boost::shared_ptr streamer(new JpegSnapshotStreamer(request, connection, node_)); + std::shared_ptr streamer = std::make_shared( + request, connection, node_); streamer->start(); - boost::mutex::scoped_lock lock(subscriber_mutex_); + std::scoped_lock lock(subscriber_mutex_); image_subscribers_.push_back(streamer); return true; } @@ -327,8 +321,7 @@ bool WebVideoServer::handle_list_streams( "ROS Image Topic List" "

Available ROS Image Topics:

"); connection->write("
    "); - BOOST_FOREACH(std::string & camera_info_topic, camera_info_topics) - { + for (std::string & camera_info_topic : camera_info_topics) { if (boost::algorithm::ends_with(camera_info_topic, "/camera_info")) { std::string base_topic = camera_info_topic.substr( 0,