Skip to content
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
bd73182
Reorganize files
bjsowa Oct 9, 2025
bf4af8e
Clean up include directives by including only what's used
bjsowa Oct 9, 2025
26c1322
Clean up package.xml depend tags
bjsowa Oct 9, 2025
17ea6f6
Load streamer plugins using pluginlib
bjsowa Oct 10, 2025
e5903e0
Move checking for compressed topics to the plugin itself
bjsowa Oct 10, 2025
1da7991
Add snapshot streamer plugins
bjsowa Oct 10, 2025
ec8347c
Add ros_compressed snapshot streamer plugin
bjsowa Oct 10, 2025
72d483e
Detect content type in ros_compressed snapshot streamer
bjsowa Oct 10, 2025
18be8e2
Fix libav streamer factory
bjsowa Oct 10, 2025
90f3ed1
Update listing available topics
bjsowa Oct 10, 2025
3437fee
List snapshot streamers
bjsowa Oct 10, 2025
0e8f668
Add missing includes
bjsowa Oct 10, 2025
c4a90b2
Revise class naming conventions
bjsowa Oct 10, 2025
0e12cc8
Move base classes to web_video_server_streamers library
bjsowa Oct 10, 2025
bd1489b
Reformat for humble
bjsowa Oct 10, 2025
f2467b8
Add some docstrings
bjsowa Oct 12, 2025
1d41e26
Use snake_case for method names
bjsowa Oct 12, 2025
db07bd9
boundry -> boundary
bjsowa Oct 12, 2025
bf22ec0
decodeImage -> decode_image
bjsowa Oct 13, 2025
55a53cb
Move base streamer classes to streamers namespace
bjsowa Oct 18, 2025
7121805
Reduce duplicated code
bjsowa Oct 19, 2025
e9cfad2
Add virtual destructor
bjsowa Oct 19, 2025
f0103d0
Include what you use
bjsowa Oct 19, 2025
463b756
Update copyright headers
bjsowa Oct 19, 2025
c0177a4
Export project dependencies
bjsowa Oct 19, 2025
54bbd89
Add missing copyright header
bjsowa Oct 19, 2025
e2752fb
Pass reference to get_available_topics
bjsowa Oct 19, 2025
7c39972
Remove redundant ImageTransport object
bjsowa Oct 19, 2025
36cbb92
Fix restream frames feature
bjsowa Oct 20, 2025
1c7502c
Pass weak node pointer to streamers to avoid cyclic dependency
bjsowa Oct 20, 2025
79b965f
Fix formatting for humble
bjsowa Oct 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 46 additions & 19 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ find_package(ament_cmake_ros REQUIRED)
find_package(async_web_server_cpp REQUIRED)
find_package(cv_bridge REQUIRED)
find_package(image_transport REQUIRED)
find_package(pluginlib REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_components REQUIRED)
find_package(rmw REQUIRED)
find_package(sensor_msgs REQUIRED)

find_package(OpenCV REQUIRED)
Expand Down Expand Up @@ -39,36 +41,60 @@ if(${cv_bridge_VERSION} VERSION_LESS "3.3.0")
add_compile_definitions(CV_BRIDGE_USES_OLD_HEADERS)
endif()

## Specify additional locations of header files
include_directories(include
${avcodec_INCLUDE_DIRS}
${avformat_INCLUDE_DIRS}
${avutil_INCLUDE_DIRS}
${swscale_INCLUDE_DIRS}
)

## Declare a cpp library
add_library(${PROJECT_NAME} SHARED
src/web_video_server.cpp
src/image_streamer.cpp
src/libav_streamer.cpp
src/vp8_streamer.cpp
src/h264_streamer.cpp
src/vp9_streamer.cpp
src/multipart_stream.cpp
src/ros_compressed_streamer.cpp
src/jpeg_streamers.cpp
src/png_streamers.cpp
src/streamer.cpp
src/utils.cpp
)

target_include_directories(${PROJECT_NAME}
PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:include/${PROJECT_NAME}>"
)

## Specify libraries to link a library or executable target against
target_link_libraries(${PROJECT_NAME}
PUBLIC
async_web_server_cpp::async_web_server_cpp
pluginlib::pluginlib
rclcpp::rclcpp
rmw::rmw
Boost::boost
PRIVATE
rclcpp_components::component
)

add_library(${PROJECT_NAME}_streamers SHARED
src/streamers/image_transport_streamer.cpp
src/streamers/libav_streamer.cpp
src/streamers/h264_streamer.cpp
src/streamers/jpeg_streamers.cpp
src/streamers/png_streamers.cpp
src/streamers/ros_compressed_streamer.cpp
src/streamers/vp8_streamer.cpp
src/streamers/vp9_streamer.cpp
)

target_include_directories(${PROJECT_NAME}_streamers
PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:include/${PROJECT_NAME}>"
${avcodec_INCLUDE_DIRS}
${avformat_INCLUDE_DIRS}
${avutil_INCLUDE_DIRS}
${swscale_INCLUDE_DIRS}
)

target_link_libraries(${PROJECT_NAME}_streamers
${PROJECT_NAME}
async_web_server_cpp::async_web_server_cpp
cv_bridge::cv_bridge
image_transport::image_transport
pluginlib::pluginlib
rclcpp::rclcpp
rclcpp_components::component
${sensor_msgs_TARGETS}
Boost::boost
Boost::system
Expand All @@ -90,18 +116,19 @@ target_link_libraries(${PROJECT_NAME}_node

rclcpp_components_register_nodes(${PROJECT_NAME} "web_video_server::WebVideoServer")

pluginlib_export_plugin_description_file(web_video_server plugins.xml)

#############
## Install ##
#############

## Mark executables and/or libraries for installation
install(
DIRECTORY include/
DESTINATION include/${PROJECT_NAME}
)

install(
TARGETS ${PROJECT_NAME}
TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_streamers
EXPORT export_${PROJECT_NAME}
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
Expand Down
24 changes: 15 additions & 9 deletions include/web_video_server/multipart_stream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,15 @@

#pragma once

#include <chrono>
#include <cstddef>
#include <queue>
#include <memory>
#include <vector>
#include <string>

#include "rclcpp/rclcpp.hpp"
#include <boost/asio/buffer.hpp>

#include "async_web_server_cpp/http_connection.hpp"

namespace web_video_server
Expand All @@ -47,34 +50,37 @@ struct PendingFooter
std::weak_ptr<std::string> contents;
};

/**
* Helper class to manage sending multipart HTTP responses.
*/
class MultipartStream
{
public:
MultipartStream(
async_web_server_cpp::HttpConnectionPtr & connection,
const std::string & boundry = "boundarydonotcross",
const std::string & boundary = "boundarydonotcross",
std::size_t max_queue_size = 1);

void sendInitialHeader();
void sendPartHeader(
void send_initial_header();
void send_part_header(
const std::chrono::steady_clock::time_point & time, const std::string & type,
size_t payload_size);
void sendPartFooter(const std::chrono::steady_clock::time_point & time);
void sendPartAndClear(
void send_part_footer(const std::chrono::steady_clock::time_point & time);
void send_part_and_clear(
const std::chrono::steady_clock::time_point & time, const std::string & type,
std::vector<unsigned char> & data);
void sendPart(
void send_part(
const std::chrono::steady_clock::time_point & time, const std::string & type,
const boost::asio::const_buffer & buffer,
async_web_server_cpp::HttpConnection::ResourcePtr resource);

private:
bool isBusy();
bool is_busy();

private:
const std::size_t max_queue_size_;
async_web_server_cpp::HttpConnectionPtr connection_;
std::string boundry_;
std::string boundary_;
std::queue<PendingFooter> pending_footers_;
};

Expand Down
140 changes: 140 additions & 0 deletions include/web_video_server/streamer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright (c) 2014, Worcester Polytechnic Institute
// Copyright (c) 2024, The Robot Web Tools Contributors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * 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.
//
// * 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.

#pragma once

#include <chrono>
#include <memory>
#include <string>
#include <vector>

#include "async_web_server_cpp/http_connection.hpp"
#include "async_web_server_cpp/http_request.hpp"
#include "rclcpp/node.hpp"

namespace web_video_server
{

/**
* @brief A common interface for all streaming plugins.
*/
class StreamerInterface
{
public:
StreamerInterface(
const async_web_server_cpp::HttpRequest & request,
async_web_server_cpp::HttpConnectionPtr connection,
rclcpp::Node::SharedPtr node);
virtual ~StreamerInterface();

/**
* @brief Starts the streaming process.
*/
virtual void start() = 0;

/**
* @brief Returns true if the streamer is inactive and should be deleted.
*
* This could be because the connection was closed or snapshot was successfully sent (in case
* of snapshot streamers).
*/
bool is_inactive()
{
return inactive_;
}

/**
* @brief Restreams the last received image frame if older than max_age.
*/
virtual void restream_frame(std::chrono::duration<double> max_age) = 0;

/**
* @brief Returns the topic being streamed.
*/
std::string get_topic()
{
return topic_;
}

protected:
async_web_server_cpp::HttpConnectionPtr connection_;
async_web_server_cpp::HttpRequest request_;
rclcpp::Node::SharedPtr node_;
bool inactive_;
std::string topic_;
};

/**
* @brief A factory interface for creating Streamer instances.
*/
class StreamerFactoryInterface
{
public:
virtual ~StreamerFactoryInterface() = default;

/**
* @brief Returns the type of streamer created by this factory.
*
* This should match the "type" query parameter used to select the streamer.
*/
virtual std::string get_type() = 0;

/**
* @brief Creates a new Streamer instance.
* @param request The HTTP request that initiated the streamer.
* @param connection The HTTP connection to use for streaming.
* @param node The ROS2 node to use for subscribing to topics.
* @return A shared pointer to the created Streamer instance.
*/
virtual std::shared_ptr<StreamerInterface> create_streamer(
const async_web_server_cpp::HttpRequest & request,
async_web_server_cpp::HttpConnectionPtr connection,
rclcpp::Node::SharedPtr node) = 0;

/**
* @brief Creates HTML code for embedding a viewer for this streamer.
* @param request The HTTP request that initiated the viewer.
*/
virtual std::string create_viewer(const async_web_server_cpp::HttpRequest & request);

/**
* @brief Returns a list of available topics that can be streamed by this streamer.
* @param node The ROS2 node to use for discovering topics.
* @return A vector of topic names.
*/
virtual std::vector<std::string> get_available_topics(rclcpp::Node::SharedPtr node);
};

/**
* @brief A factory interface for creating snapshot Streamer instances.
*/
class SnapshotStreamerFactoryInterface : public StreamerFactoryInterface {};

} // namespace web_video_server
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,19 @@
#include <memory>
#include <string>

#include "image_transport/image_transport.hpp"
#include "web_video_server/libav_streamer.hpp"
#include "async_web_server_cpp/http_request.hpp"
#include "async_web_server_cpp/http_connection.hpp"
#include "rclcpp/node.hpp"

#include "web_video_server/streamer.hpp"
#include "web_video_server/streamers/libav_streamer.hpp"

namespace web_video_server
{
namespace streamers
{

class H264Streamer : public LibavStreamer
class H264Streamer : public LibavStreamerBase
{
public:
H264Streamer(
Expand All @@ -50,18 +54,19 @@ class H264Streamer : public LibavStreamer
~H264Streamer();

protected:
virtual void initializeEncoder();
virtual void initialize_encoder();
std::string preset_;
};

class H264StreamerType : public LibavStreamerType
class H264StreamerFactory : public LibavStreamerFactoryBase
{
public:
H264StreamerType();
std::shared_ptr<ImageStreamer> create_streamer(
std::string get_type() {return "h264";}
std::shared_ptr<StreamerInterface> create_streamer(
const async_web_server_cpp::HttpRequest & request,
async_web_server_cpp::HttpConnectionPtr connection,
rclcpp::Node::SharedPtr node);
};

} // namespace streamers
} // namespace web_video_server
Loading