diff --git a/Dockerfile b/Dockerfile
index 16465e1..80d744f 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -35,7 +35,11 @@ RUN apt-get update \
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 \
diff --git a/apt-requirements.txt b/apt-requirements.txt
index b74768a..67fd704 100644
--- a/apt-requirements.txt
+++ b/apt-requirements.txt
@@ -3,6 +3,7 @@ build-essential
cmake
pkg-config
libopencv-dev
+python3-opencv
python3-rosdep
python3-rosdistro
software-properties-common
diff --git a/apt-runtime.txt b/apt-runtime.txt
index c9c63e7..982f1f8 100644
--- a/apt-runtime.txt
+++ b/apt-runtime.txt
@@ -1,6 +1,11 @@
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-ros2cli
+ros-humble-rclpy
+net-tools
+iputils-ping
diff --git a/docker-compose.yml b/docker-compose.yml
index 8598f32..149a92a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -12,10 +12,11 @@ services:
- 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
- command: ["image2rtsp.launch.py", "local_only:=false", "topic:=/camera/image"]
+ command: ["image2rtsp.launch.py", "local_only:=false", "topic:=/camera/image", "log_level:=debug"]
restart: unless-stopped
diff --git a/fastdds_no_shm.xml b/fastdds_no_shm.xml
new file mode 100644
index 0000000..a67988c
--- /dev/null
+++ b/fastdds_no_shm.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+ UDPv4Transport
+ UDPv4
+
+
+
+
+
+ UDPv4Transport
+
+ false
+
+
+
diff --git a/launch/image2rtsp.launch.py b/launch/image2rtsp.launch.py
index 7397979..7e45101 100644
--- a/launch/image2rtsp.launch.py
+++ b/launch/image2rtsp.launch.py
@@ -1,6 +1,8 @@
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
+from launch.actions import DeclareLaunchArgument
+from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
@@ -10,13 +12,19 @@ def generate_launch_description():
'parameters.yaml'
)
+ log_level = LaunchConfiguration('log_level')
+
return LaunchDescription([
+ DeclareLaunchArgument(
+ 'log_level',
+ default_value='warn',
+ description='ROS logger level (debug, info, warn, error, fatal)'
+ ),
Node(
package='image2rtsp',
executable='image2rtsp',
name='image2rtsp',
parameters=[config],
- # Reduce runtime logging verbosity to WARN to avoid info spam in container logs
- arguments=['--ros-args', '--log-level', 'warn']
+ arguments=['--ros-args', '--log-level', log_level]
)
])
\ No newline at end of file
diff --git a/src/image2rtsp.cpp b/src/image2rtsp.cpp
index 21b297f..7d78eda 100644
--- a/src/image2rtsp.cpp
+++ b/src/image2rtsp.cpp
@@ -69,7 +69,13 @@ Image2rtsp::Image2rtsp() : Node("image2rtsp"){
framerate = extract_framerate(pipeline, 30);
rtsp_server_add_url(mountpoint.c_str(), pipeline.c_str());
- RCLCPP_DEBUG(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://:%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) {
diff --git a/src/video.cpp b/src/video.cpp
index 7a94285..cd37e2c 100644
--- a/src/video.cpp
+++ b/src/video.cpp
@@ -87,29 +87,9 @@ static void media_configure(GstRTSPMediaFactory *factory, GstRTSPMedia *media, g
gst_app_src_set_max_bytes(node->appsrc, 0);
gst_app_src_set_max_time(node->appsrc, 0);
- /* create a minimal dummy preroll frame to satisfy pipeline preroll */
- guint width = 2;
- guint height = 2;
- guint fr = node->framerate > 0 ? node->framerate : 30;
- GstCaps *caps = gst_caps_new_simple("video/x-raw",
- "format", G_TYPE_STRING, "RGB",
- "width", G_TYPE_INT, width,
- "height", G_TYPE_INT, height,
- "framerate", GST_TYPE_FRACTION, fr, 1,
- NULL);
- gst_app_src_set_caps(node->appsrc, caps);
-
- gsize buf_size = width * height * 3;
- GstBuffer *buf = gst_buffer_new_allocate(NULL, buf_size, NULL);
- GstMapInfo map;
- if (gst_buffer_map(buf, &map, GST_MAP_WRITE)){
- if (map.data) memset(map.data, 64, buf_size);
- gst_buffer_unmap(buf, &map);
- }
- GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_LIVE);
- gst_app_src_push_buffer(node->appsrc, buf);
-
- gst_caps_unref(caps);
+ /* 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);
return;
} else {
@@ -199,6 +179,7 @@ void Image2rtsp::topic_callback(const sensor_msgs::msg::Image::SharedPtr msg){
// Set caps from message
caps = gst_caps_new_from_image(msg);
gst_app_src_set_caps(appsrc, caps);
+ gst_caps_unref(caps);
buf = gst_buffer_new_allocate(nullptr, msg->data.size(), nullptr);
gst_buffer_fill(buf, 0, msg->data.data(), msg->data.size());
GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_LIVE);