Merge 7d4eca3c46 into 2ba0f2e9f6
This commit is contained in:
commit
d7879578a6
|
|
@ -0,0 +1,6 @@
|
||||||
|
build
|
||||||
|
install
|
||||||
|
log
|
||||||
|
.git
|
||||||
|
*.pyc
|
||||||
|
__pycache__
|
||||||
|
|
@ -5,3 +5,5 @@ build/
|
||||||
|
|
||||||
# VSC stuff
|
# VSC stuff
|
||||||
.vscode
|
.vscode
|
||||||
|
# Local python virtualenv
|
||||||
|
.venv/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
FROM ros:humble-ros-base AS build
|
||||||
|
SHELL ["/bin/bash", "-lc"]
|
||||||
|
WORKDIR /ws
|
||||||
|
|
||||||
|
# Install build dependencies
|
||||||
|
COPY apt-requirements.txt /tmp/apt-build.txt
|
||||||
|
RUN apt-get update \
|
||||||
|
&& grep -Ev '^[[:space:]]*($|#)' /tmp/apt-build.txt | xargs apt-get install -y --no-install-recommends \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* /tmp/apt-build.txt
|
||||||
|
|
||||||
|
# Copy package manifest and install rosdeps
|
||||||
|
COPY package.xml ./package.xml
|
||||||
|
RUN add-apt-repository multiverse || true
|
||||||
|
RUN source /opt/ros/humble/setup.bash \
|
||||||
|
&& rosdep update || true \
|
||||||
|
&& rosdep install -i --from-paths . --rosdistro humble -y || true
|
||||||
|
|
||||||
|
# Copy sources and build
|
||||||
|
COPY . .
|
||||||
|
RUN source /opt/ros/humble/setup.bash \
|
||||||
|
&& CCACHE_DIR=/ccache mkdir -p /ccache \
|
||||||
|
&& chmod 777 /ccache \
|
||||||
|
&& colcon build --parallel-workers $(nproc) \
|
||||||
|
--cmake-args -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
||||||
|
|
||||||
|
FROM ros:humble-ros-base
|
||||||
|
WORKDIR /ws
|
||||||
|
## Install minimal runtime packages required by the built binaries
|
||||||
|
COPY apt-runtime.txt /tmp/apt-runtime.txt
|
||||||
|
RUN apt-get update \
|
||||||
|
&& grep -Ev '^[[:space:]]*($|#)' /tmp/apt-runtime.txt | xargs apt-get install -y --no-install-recommends \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* /tmp/apt-runtime.txt
|
||||||
|
|
||||||
|
COPY --from=build /ws/install /ws/install
|
||||||
|
COPY docker-entrypoint.sh /ros_entrypoint.sh
|
||||||
|
RUN chmod +x /ros_entrypoint.sh
|
||||||
|
# Bake FastDDS UDP-only profile into the image so the shared-memory transport
|
||||||
|
# fix applies regardless of how the container is started (compose or docker run).
|
||||||
|
COPY fastdds_no_shm.xml /ws/fastdds_no_shm.xml
|
||||||
|
ENV ROS_DISTRO=humble
|
||||||
|
ENV FASTRTPS_DEFAULT_PROFILES_FILE=/ws/fastdds_no_shm.xml
|
||||||
|
|
||||||
|
## Make ROS and workspace overlays available for interactive shells
|
||||||
|
RUN echo "source /opt/ros/${ROS_DISTRO}/setup.bash || true" > /etc/profile.d/ros2.sh \
|
||||||
|
&& echo "[ -f /ws/install/setup.bash ] && source /ws/install/setup.bash" >> /etc/profile.d/ros2.sh \
|
||||||
|
&& chmod +x /etc/profile.d/ros2.sh
|
||||||
|
|
||||||
|
# Also source in non-login interactive bash shells
|
||||||
|
RUN echo "source /opt/ros/${ROS_DISTRO}/setup.bash || true" >> /etc/bash.bashrc \
|
||||||
|
&& echo "[ -f /ws/install/setup.bash ] && source /ws/install/setup.bash" >> /etc/bash.bashrc
|
||||||
|
|
||||||
|
ENTRYPOINT ["/ros_entrypoint.sh"]
|
||||||
|
CMD ["image2rtsp.launch.py"]
|
||||||
24
README.md
24
README.md
|
|
@ -14,10 +14,34 @@ You are reading now the README for a **default** ROS2 package. If you want to us
|
||||||
## Dependencies
|
## Dependencies
|
||||||
- ROS2 Humble
|
- ROS2 Humble
|
||||||
|
|
||||||
|
- Dependency files in this repository:
|
||||||
|
- `apt-requirements.txt`: build-only system dependencies.
|
||||||
|
- `apt-runtime.txt`: runtime system dependencies (minimal default set).
|
||||||
|
- `requirements.txt`: pip dependencies (currently none required).
|
||||||
|
- OpenCV policy: use APT package `python3-opencv` (do not install `opencv-python` via pip by default).
|
||||||
|
|
||||||
|
- Optional debug-only packages:
|
||||||
|
- `ros-humble-ros2cli`
|
||||||
|
- `net-tools`
|
||||||
|
- `iputils-ping`
|
||||||
|
- These are listed as commented entries in `apt-runtime.txt` and are not installed by default.
|
||||||
|
|
||||||
- gstreamer libs:
|
- gstreamer libs:
|
||||||
```bash
|
```bash
|
||||||
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
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For a minimal host setup using repository-managed lists:
|
||||||
|
```bash
|
||||||
|
sudo apt-get update
|
||||||
|
grep -Ev '^[[:space:]]*($|#)' apt-requirements.txt | xargs sudo apt-get install -y --no-install-recommends
|
||||||
|
grep -Ev '^[[:space:]]*($|#)' apt-runtime.txt | xargs sudo apt-get install -y --no-install-recommends
|
||||||
|
```
|
||||||
|
|
||||||
|
Install debug tools only when needed:
|
||||||
|
```bash
|
||||||
|
sudo apt-get install -y --no-install-recommends ros-humble-ros2cli net-tools iputils-ping
|
||||||
|
```
|
||||||
## Install
|
## Install
|
||||||
- Navigate to the root directory, create a new directory named `ros2_ws/src`, and then change the current working directory to `ros2_ws/src`:
|
- Navigate to the root directory, create a new directory named `ros2_ws/src`, and then change the current working directory to `ros2_ws/src`:
|
||||||
```bashrc
|
```bashrc
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
python3-colcon-common-extensions
|
||||||
|
build-essential
|
||||||
|
cmake
|
||||||
|
pkg-config
|
||||||
|
libopencv-dev
|
||||||
|
python3-rosdep
|
||||||
|
python3-rosdistro
|
||||||
|
software-properties-common
|
||||||
|
libgstreamer1.0-dev
|
||||||
|
libgstreamer-plugins-base1.0-dev
|
||||||
|
libgstreamer-plugins-bad1.0-dev
|
||||||
|
libgstreamer-plugins-good1.0-dev
|
||||||
|
libgstrtspserver-1.0-dev
|
||||||
|
ninja-build
|
||||||
|
ccache
|
||||||
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
libgstreamer1.0-0
|
||||||
|
gstreamer1.0-plugins-base
|
||||||
|
gstreamer1.0-plugins-good
|
||||||
|
gstreamer1.0-plugins-bad
|
||||||
|
gstreamer1.0-plugins-ugly
|
||||||
|
libgstrtspserver-1.0-0
|
||||||
|
python3-opencv
|
||||||
|
ros-humble-rclpy
|
||||||
|
|
||||||
|
# Optional debug-only tools (not required for normal runtime)
|
||||||
|
# Uncomment/install manually only when troubleshooting:
|
||||||
|
# ros-humble-ros2cli
|
||||||
|
# net-tools
|
||||||
|
# iputils-ping
|
||||||
|
|
@ -3,12 +3,12 @@
|
||||||
|
|
||||||
# If the source is a ros2 topic (default case)
|
# If the source is a ros2 topic (default case)
|
||||||
compressed: False
|
compressed: False
|
||||||
topic: "color/image_raw"
|
topic: "camera/image"
|
||||||
default_pipeline: |
|
default_pipeline: |
|
||||||
( appsrc name=imagesrc do-timestamp=true min-latency=0 max-latency=0 max-bytes=1000 is-live=true !
|
( appsrc name=imagesrc do-timestamp=true min-latency=0 max-latency=0 max-bytes=1000 is-live=true !
|
||||||
videoconvert !
|
videoconvert !
|
||||||
videoscale !
|
videoscale !
|
||||||
video/x-raw, framerate=30/1, width=640, height=480 !
|
video/x-raw, framerate=8/1 !
|
||||||
x264enc tune=zerolatency bitrate=500 key-int-max=30 !
|
x264enc tune=zerolatency bitrate=500 key-int-max=30 !
|
||||||
video/x-h264, profile=baseline !
|
video/x-h264, profile=baseline !
|
||||||
rtph264pay name=pay0 pt=96 )
|
rtph264pay name=pay0 pt=96 )
|
||||||
|
|
@ -31,9 +31,9 @@
|
||||||
# Notice: Here the framerate might be set to the camera framerate, otherwise "503 Service Unavailable" error will appear.
|
# Notice: Here the framerate might be set to the camera framerate, otherwise "503 Service Unavailable" error will appear.
|
||||||
|
|
||||||
# RTSP setup
|
# RTSP setup
|
||||||
mountpoint: "/back"
|
mountpoint: "/live"
|
||||||
port: "8554"
|
port: "8554"
|
||||||
local_only: True # True = rtsp://127.0.0.1:portAndMountpoint (The stream is accessible only from the local machine)
|
local_only: False # True = rtsp://127.0.0.1:portAndMountpoint (The stream is accessible only from the local machine)
|
||||||
# False = rtsp://0.0.0.0:portAndMountpoint (The stream is accessible from the outside)
|
# False = rtsp://0.0.0.0:portAndMountpoint (The stream is accessible from the outside)
|
||||||
# For example, to access the stream running on the machine with IP = 192.168.20.20,
|
# For example, to access the stream running on the machine with IP = 192.168.20.20,
|
||||||
# use rtsp://192.186.20.20:portAndMountpoint
|
# use rtsp://192.186.20.20:portAndMountpoint
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
services:
|
||||||
|
image2rtsp:
|
||||||
|
build: .
|
||||||
|
image: image2rtsp:latest
|
||||||
|
# Use host networking so the container binds to the host interfaces directly
|
||||||
|
# (Linux only). Remove port mapping when using host networking.
|
||||||
|
network_mode: "host"
|
||||||
|
environment:
|
||||||
|
- ROS_DISTRO=humble
|
||||||
|
# Propagate host ROS environment so DDS/discovery match (set on host if needed)
|
||||||
|
- ROS_DOMAIN_ID=${ROS_DOMAIN_ID:-0}
|
||||||
|
- RMW_IMPLEMENTATION=${RMW_IMPLEMENTATION:-}
|
||||||
|
# GStreamer debug level (uncomment for verbose GStreamer logging)
|
||||||
|
# - GST_DEBUG=${GST_DEBUG:-}
|
||||||
|
# FASTRTPS_DEFAULT_PROFILES_FILE is set in the Dockerfile (baked into image)
|
||||||
|
# Ensure the node binds to non-localhost interface (disable local_only)
|
||||||
|
# and subscribe to the actual image topic available on the host
|
||||||
|
|
||||||
|
#log_level:= info, debug, warning, error, fatal
|
||||||
|
command: ["image2rtsp.launch.py", "local_only:=false", "topic:=/camera/image", "log_level:=info"]
|
||||||
|
|
||||||
|
|
||||||
|
restart: unless-stopped
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Source ROS and workspace overlays then exec ros2 launch with provided args
|
||||||
|
if [ -n "${ROS_DISTRO:-}" ]; then
|
||||||
|
source "/opt/ros/${ROS_DISTRO}/setup.bash" || true
|
||||||
|
else
|
||||||
|
source /opt/ros/humble/setup.bash || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f /ws/install/setup.bash ]; then
|
||||||
|
source /ws/install/setup.bash
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec ros2 launch image2rtsp "$@"
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!-- Force UDP transport only.
|
||||||
|
Shared memory transport fails between host and Docker container (different
|
||||||
|
PID namespaces) even with network_mode: host. Disabling it forces FastDDS
|
||||||
|
to use UDPv4, which works correctly over the loopback interface. -->
|
||||||
|
<profiles xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles">
|
||||||
|
<transport_descriptors>
|
||||||
|
<transport_descriptor>
|
||||||
|
<transport_id>UDPv4Transport</transport_id>
|
||||||
|
<type>UDPv4</type>
|
||||||
|
</transport_descriptor>
|
||||||
|
</transport_descriptors>
|
||||||
|
<participant profile_name="default_participant" is_default_profile="true">
|
||||||
|
<rtps>
|
||||||
|
<userTransports>
|
||||||
|
<transport_id>UDPv4Transport</transport_id>
|
||||||
|
</userTransports>
|
||||||
|
<useBuiltinTransports>false</useBuiltinTransports>
|
||||||
|
</rtps>
|
||||||
|
</participant>
|
||||||
|
</profiles>
|
||||||
|
|
@ -15,6 +15,8 @@ class Image2rtsp : public rclcpp::Node{
|
||||||
public:
|
public:
|
||||||
Image2rtsp();
|
Image2rtsp();
|
||||||
GstRTSPServer *rtsp_server;
|
GstRTSPServer *rtsp_server;
|
||||||
|
uint framerate;
|
||||||
|
GstAppSrc *appsrc;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
string topic;
|
string topic;
|
||||||
|
|
@ -23,14 +25,12 @@ private:
|
||||||
string pipeline;
|
string pipeline;
|
||||||
string default_pipeline;
|
string default_pipeline;
|
||||||
string camera_pipeline;
|
string camera_pipeline;
|
||||||
uint framerate;
|
|
||||||
bool local_only;
|
bool local_only;
|
||||||
bool camera;
|
bool camera;
|
||||||
bool compressed;
|
bool compressed;
|
||||||
GstAppSrc *appsrc;
|
|
||||||
|
|
||||||
void video_mainloop_start();
|
void video_mainloop_start();
|
||||||
void rtsp_server_add_url(const char *url, const char *sPipeline, GstElement **appsrc);
|
void rtsp_server_add_url(const char *url, const char *sPipeline);
|
||||||
void topic_callback(const sensor_msgs::msg::Image::SharedPtr msg);
|
void topic_callback(const sensor_msgs::msg::Image::SharedPtr msg);
|
||||||
void compressed_topic_callback(const sensor_msgs::msg::CompressedImage::SharedPtr msg);
|
void compressed_topic_callback(const sensor_msgs::msg::CompressedImage::SharedPtr msg);
|
||||||
uint extract_framerate(const std::string& pipeline, uint default_framerate);
|
uint extract_framerate(const std::string& pipeline, uint default_framerate);
|
||||||
|
|
@ -40,7 +40,7 @@ private:
|
||||||
rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr subscription_compressed_;
|
rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr subscription_compressed_;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void media_configure(GstRTSPMediaFactory *factory, GstRTSPMedia *media, GstElement **appsrc);
|
static void media_configure(GstRTSPMediaFactory *factory, GstRTSPMedia *media, gpointer user_data);
|
||||||
static void *mainloop(void *arg);
|
static void *mainloop(void *arg);
|
||||||
static gboolean session_cleanup(Image2rtsp *node, rclcpp::Logger logger, gboolean ignored);
|
static gboolean session_cleanup(Image2rtsp *node, rclcpp::Logger logger, gboolean ignored);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import os
|
import os
|
||||||
from ament_index_python.packages import get_package_share_directory
|
from ament_index_python.packages import get_package_share_directory
|
||||||
from launch import LaunchDescription
|
from launch import LaunchDescription
|
||||||
|
from launch.actions import DeclareLaunchArgument
|
||||||
|
from launch.substitutions import LaunchConfiguration
|
||||||
from launch_ros.actions import Node
|
from launch_ros.actions import Node
|
||||||
|
|
||||||
def generate_launch_description():
|
def generate_launch_description():
|
||||||
|
|
@ -10,11 +12,19 @@ def generate_launch_description():
|
||||||
'parameters.yaml'
|
'parameters.yaml'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
log_level = LaunchConfiguration('log_level')
|
||||||
|
|
||||||
return LaunchDescription([
|
return LaunchDescription([
|
||||||
|
DeclareLaunchArgument(
|
||||||
|
'log_level',
|
||||||
|
default_value='warn',
|
||||||
|
description='ROS logger level (debug, info, warn, error, fatal)'
|
||||||
|
),
|
||||||
Node(
|
Node(
|
||||||
package='image2rtsp',
|
package='image2rtsp',
|
||||||
executable='image2rtsp',
|
executable='image2rtsp',
|
||||||
name='image2rtsp',
|
name='image2rtsp',
|
||||||
parameters=[config]
|
parameters=[config],
|
||||||
|
arguments=['--ros-args', '--log-level', log_level]
|
||||||
)
|
)
|
||||||
])
|
])
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
# No required pip dependencies.
|
||||||
|
# OpenCV is provided via apt package: python3-opencv.
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Minimal launcher: runs a package launch with optional args.
|
||||||
|
exec ros2 launch image2rtsp "${1:-image2rtsp.launch.py}" "${@:2}"
|
||||||
|
|
@ -49,15 +49,15 @@ Image2rtsp::Image2rtsp() : Node("image2rtsp"){
|
||||||
if (camera == false){
|
if (camera == false){
|
||||||
if (compressed == false){
|
if (compressed == false){
|
||||||
subscription_ = this->create_subscription<sensor_msgs::msg::Image>(topic, 10, std::bind(&Image2rtsp::topic_callback, this, _1));
|
subscription_ = this->create_subscription<sensor_msgs::msg::Image>(topic, 10, std::bind(&Image2rtsp::topic_callback, this, _1));
|
||||||
RCLCPP_INFO(this->get_logger(), "Subscribing to sensor_msgs::msg::Image");
|
RCLCPP_DEBUG(this->get_logger(), "Subscribing to sensor_msgs::msg::Image");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
subscription_compressed_ = this->create_subscription<sensor_msgs::msg::CompressedImage>(topic, 10, std::bind(&Image2rtsp::compressed_topic_callback, this, _1));
|
subscription_compressed_ = this->create_subscription<sensor_msgs::msg::CompressedImage>(topic, 10, std::bind(&Image2rtsp::compressed_topic_callback, this, _1));
|
||||||
RCLCPP_INFO(this->get_logger(), "Subscribing to sensor_msgs::msg::CompressedImage");
|
RCLCPP_DEBUG(this->get_logger(), "Subscribing to sensor_msgs::msg::CompressedImage");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
RCLCPP_INFO(this->get_logger(), "Trying to access camera device");
|
RCLCPP_DEBUG(this->get_logger(), "Trying to access camera device");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the RTSP server
|
// Start the RTSP server
|
||||||
|
|
@ -67,9 +67,15 @@ Image2rtsp::Image2rtsp() : Node("image2rtsp"){
|
||||||
|
|
||||||
pipeline = camera ? camera_pipeline : default_pipeline;
|
pipeline = camera ? camera_pipeline : default_pipeline;
|
||||||
framerate = extract_framerate(pipeline, 30);
|
framerate = extract_framerate(pipeline, 30);
|
||||||
rtsp_server_add_url(mountpoint.c_str(), pipeline.c_str(), camera ? nullptr : (GstElement **)&appsrc);
|
rtsp_server_add_url(mountpoint.c_str(), pipeline.c_str());
|
||||||
|
|
||||||
RCLCPP_INFO(this->get_logger(), "Stream available at rtsp://%s:%s%s", gst_rtsp_server_get_address(rtsp_server), port.c_str(), mountpoint.c_str());
|
const char *server_address = gst_rtsp_server_get_address(rtsp_server);
|
||||||
|
if (local_only) {
|
||||||
|
RCLCPP_DEBUG(this->get_logger(), "Stream available at rtsp://%s:%s%s", server_address, port.c_str(), mountpoint.c_str());
|
||||||
|
} else {
|
||||||
|
RCLCPP_DEBUG(this->get_logger(), "RTSP server bound to %s:%s%s", server_address, port.c_str(), mountpoint.c_str());
|
||||||
|
RCLCPP_DEBUG(this->get_logger(), "Connect clients using rtsp://<host-ip>:%s%s (0.0.0.0 is bind-only)", port.c_str(), mountpoint.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint Image2rtsp::extract_framerate(const std::string& pipeline, uint default_framerate = 30) {
|
uint Image2rtsp::extract_framerate(const std::string& pipeline, uint default_framerate = 30) {
|
||||||
|
|
@ -99,7 +105,7 @@ uint Image2rtsp::extract_framerate(const std::string& pipeline, uint default_fra
|
||||||
RCLCPP_WARN(this->get_logger(), "Invalid framerate value %d, using default: %d", framerate, default_framerate);
|
RCLCPP_WARN(this->get_logger(), "Invalid framerate value %d, using default: %d", framerate, default_framerate);
|
||||||
return default_framerate;
|
return default_framerate;
|
||||||
}
|
}
|
||||||
RCLCPP_INFO(this->get_logger(), "Using set framerate %d", framerate);
|
RCLCPP_DEBUG(this->get_logger(), "Using set framerate %d", framerate);
|
||||||
return framerate;
|
return framerate;
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
RCLCPP_WARN(this->get_logger(), "Failed to parse framerate '%s', using default: %d", framerate_str.c_str(), default_framerate);
|
RCLCPP_WARN(this->get_logger(), "Failed to parse framerate '%s', using default: %d", framerate_str.c_str(), default_framerate);
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ GstRTSPServer *Image2rtsp::rtsp_server_create(const std::string &port, const boo
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Image2rtsp::rtsp_server_add_url(const char *url, const char *sPipeline, GstElement **appsrc){
|
void Image2rtsp::rtsp_server_add_url(const char *url, const char *sPipeline){
|
||||||
GstRTSPMountPoints *mounts;
|
GstRTSPMountPoints *mounts;
|
||||||
GstRTSPMediaFactory *factory;
|
GstRTSPMediaFactory *factory;
|
||||||
|
|
||||||
|
|
@ -55,9 +55,12 @@ void Image2rtsp::rtsp_server_add_url(const char *url, const char *sPipeline, Gst
|
||||||
|
|
||||||
/* notify when our media is ready, This is called whenever someone asks for
|
/* notify when our media is ready, This is called whenever someone asks for
|
||||||
* the media and a new pipeline is created */
|
* the media and a new pipeline is created */
|
||||||
g_signal_connect(factory, "media-configure", (GCallback)media_configure, appsrc);
|
// Pass `this` as user_data so media_configure can access node state and push a preroll frame
|
||||||
|
g_signal_connect(factory, "media-configure", (GCallback)media_configure, this);
|
||||||
|
|
||||||
gst_rtsp_media_factory_set_shared(factory, TRUE);
|
// Use non-shared media factory so each client gets its own pipeline
|
||||||
|
// This avoids prerolling a shared pipeline without available appsrc data
|
||||||
|
gst_rtsp_media_factory_set_shared(factory, FALSE);
|
||||||
|
|
||||||
/* attach the factory to the url */
|
/* attach the factory to the url */
|
||||||
gst_rtsp_mount_points_add_factory(mounts, url, factory);
|
gst_rtsp_mount_points_add_factory(mounts, url, factory);
|
||||||
|
|
@ -66,17 +69,30 @@ void Image2rtsp::rtsp_server_add_url(const char *url, const char *sPipeline, Gst
|
||||||
g_object_unref(mounts);
|
g_object_unref(mounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void media_configure(GstRTSPMediaFactory *factory, GstRTSPMedia *media, GstElement **appsrc){
|
static void media_configure(GstRTSPMediaFactory *factory, GstRTSPMedia *media, gpointer user_data){
|
||||||
if(appsrc){
|
Image2rtsp *node = static_cast<Image2rtsp*>(user_data);
|
||||||
GstElement *pipeline = gst_rtsp_media_get_element(media);
|
GstElement *pipeline = gst_rtsp_media_get_element(media);
|
||||||
|
GstElement *imagesrc = gst_bin_get_by_name(GST_BIN(pipeline), "imagesrc");
|
||||||
|
|
||||||
*appsrc = gst_bin_get_by_name(GST_BIN(pipeline), "imagesrc");
|
if (imagesrc){
|
||||||
|
/* store appsrc in node for later pushes */
|
||||||
|
node->appsrc = GST_APP_SRC(imagesrc);
|
||||||
|
|
||||||
/* this instructs appsrc that we will be dealing with timed buffer */
|
/* instruct appsrc that we will be dealing with timed buffers */
|
||||||
gst_util_set_object_arg(G_OBJECT(*appsrc), "format", "time");
|
gst_util_set_object_arg(G_OBJECT(node->appsrc), "format", "time");
|
||||||
|
|
||||||
|
/* mark stream-type to not require preroll and reduce buffering */
|
||||||
|
gst_app_src_set_stream_type(node->appsrc, GST_APP_STREAM_TYPE_STREAM);
|
||||||
|
gst_app_src_set_max_buffers(node->appsrc, 0);
|
||||||
|
gst_app_src_set_max_bytes(node->appsrc, 0);
|
||||||
|
gst_app_src_set_max_time(node->appsrc, 0);
|
||||||
|
|
||||||
|
/* caps and first buffer are set by topic_callback from the real image
|
||||||
|
* so that caps always match the actual camera resolution/format.
|
||||||
|
* Pushing a dummy preroll with wrong caps breaks x264enc mid-stream. */
|
||||||
gst_object_unref(pipeline);
|
gst_object_unref(pipeline);
|
||||||
}else{
|
return;
|
||||||
|
} else {
|
||||||
guint i, n_streams;
|
guint i, n_streams;
|
||||||
n_streams = gst_rtsp_media_n_streams(media);
|
n_streams = gst_rtsp_media_n_streams(media);
|
||||||
|
|
||||||
|
|
@ -149,7 +165,7 @@ static gboolean session_cleanup(Image2rtsp *node, rclcpp::Logger logger, gboolea
|
||||||
{
|
{
|
||||||
char s[32];
|
char s[32];
|
||||||
snprintf(s, 32, (char *)"Sessions cleaned: %d", num);
|
snprintf(s, 32, (char *)"Sessions cleaned: %d", num);
|
||||||
RCLCPP_INFO(node->get_logger(), s);
|
RCLCPP_DEBUG(node->get_logger(), s);
|
||||||
}
|
}
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
@ -159,9 +175,11 @@ void Image2rtsp::topic_callback(const sensor_msgs::msg::Image::SharedPtr msg){
|
||||||
GstCaps *caps; // image properties. see return of Image2rtsp::gst_caps_new_from_image
|
GstCaps *caps; // image properties. see return of Image2rtsp::gst_caps_new_from_image
|
||||||
char *gst_type, *gst_format = (char *)"";
|
char *gst_type, *gst_format = (char *)"";
|
||||||
if (appsrc != NULL){
|
if (appsrc != NULL){
|
||||||
|
RCLCPP_DEBUG(this->get_logger(), "Received image %dx%d, encoding=%s", msg->width, msg->height, msg->encoding.c_str());
|
||||||
// Set caps from message
|
// Set caps from message
|
||||||
caps = gst_caps_new_from_image(msg);
|
caps = gst_caps_new_from_image(msg);
|
||||||
gst_app_src_set_caps(appsrc, caps);
|
gst_app_src_set_caps(appsrc, caps);
|
||||||
|
gst_caps_unref(caps);
|
||||||
buf = gst_buffer_new_allocate(nullptr, msg->data.size(), nullptr);
|
buf = gst_buffer_new_allocate(nullptr, msg->data.size(), nullptr);
|
||||||
gst_buffer_fill(buf, 0, msg->data.data(), msg->data.size());
|
gst_buffer_fill(buf, 0, msg->data.data(), msg->data.size());
|
||||||
GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_LIVE);
|
GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_LIVE);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue