Skip to content

Commit

Permalink
Added a RTSP streaming example (#19)
Browse files Browse the repository at this point in the history
Co-authored-by: Jarno Ralli <jarno.ralli@fogsphere.com>
  • Loading branch information
JarnoRalli and Jarno Ralli authored Dec 27, 2024
1 parent 60ff893 commit 55db4e1
Show file tree
Hide file tree
Showing 4 changed files with 270 additions and 2 deletions.
20 changes: 20 additions & 0 deletions docker/Dockerfile-rtsp-server
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM ubuntu:20.04

RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
gstreamer1.0-tools \
gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad \
gstreamer1.0-plugins-ugly \
gstreamer1.0-libav \
libgirepository1.0-dev \
python3-gi python3-pip

RUN apt-get install -y \
gstreamer1.0-rtsp \
gir1.2-gst-rtsp-server-1.0 \
gir1.2-gstreamer-1.0 \
ffmpeg \
net-tools

15 changes: 13 additions & 2 deletions docker/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Docker Images

This directory contains docker files used for generating docker containers where the examples can be run.
This directory contains docker files used for generating docker images where the examples can be run.

* [Dockerfile-deepstream](Dockerfile-deepstream)
* Docker container with DeepStream 6.1.1 plus samples and DeepStream Python bindings
Expand All @@ -14,6 +14,14 @@ This directory contains docker files used for generating docker containers where
* mesa-utils for glxinfo
* cuda-tookit
* tensorrt-dev
* [Dockerfile-rtsp-server](Dockerfile-rtsp-server)
* Docker container with RTSP GStreamer components
* Based on ubuntu:20.04
* gstreamer1.0-plugins-base
* gstreamer1.0-plugins-good
* gstreamer1.0-plugins-bad
* gstreamer1.0-plugins-ugly
* gstreamer1.0-rtsp

# 1 Creating Docker Images

Expand All @@ -24,7 +32,8 @@ Following sections show how to:

## 1.1 Installing Docker

Before creating the docker image, you need to install Nvidia's Container Toolkit. Instructions can be found here:
If you want to create a Docker image that uses Nvidia's GPU, you first need to install Nvidia's Container Toolkit.
Instructions can be found here:

* https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html

Expand Down Expand Up @@ -64,3 +73,5 @@ After this you can create the docker image used in the examples.

```bash
docker build -t nvidia-deepstream-samples -f ./Dockerfile-deepstream .
```

56 changes: 56 additions & 0 deletions gst-examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,15 @@ gst-inspect-1.0

# 2 Examples

## 2.1 Playback

* [gst-qtdemux-h264.py](gst-qtdemux-h264.py)
* Plays back h264 encoded video stream from a file (e.g. mp4).
* [gst-qtdemux-h264-avdec_aac.py](gst-qtdemux-h264-avdec_aac.py)
* Plays back h264 encoded video stream and MPEG-4 AAC encoded audio stream from a file (e.g. mp4).

## 2.2 PyTorch

* [gst-pytorch-example-1.py](gst-pytorch-example-1.py)
* Captures frames from a GStreamer pipeline and passes those to a SSD-detector.
* Uses [nvtx](https://docs.nvidia.com/nvtx/index.html) to mark sections of the code so that Nsight-systems can be used
Expand All @@ -30,3 +35,54 @@ gst-inspect-1.0
* Same as above, but post-processing is done by first transferring the `locs` and `labels` tensors
from gpu- to cpu-memory, and then applying post-processing. Otherwise the tensors are fetched element-wise, making
the memory transfers highly inefficient.

## 2.3 RTSP Server

This example launches an RTSP server and streams a video over RTSP, encoded in H264. Easiest way to run the program is to execute it
inside the Docker container [Dockerfile-rtsp-server](../docker/Dockerfile-rtsp-server). For more instructions regarding how to build
the image, take a look at the [README.md](../docker/README.md). Once you have created the Docker image, you can start the container
by running the following command from the directory where the file `gst-rtsp-server.py` is found.

```bash
docker run -p 8554:8554 -v $(pwd):/home -it gst-rtsp-server bash
```

Above command exposes the port 8554 from the container and mounts the directory where the command is run to a directory `/home` inside
the container. You can copy the video that you want to stream to the same directory where the `gst-rtsp-server.py` is found, and
thus make it available to the container. Once inside the container, run the following to start streaming:

```bash
cd /home
python3 gst-rtsp-server.py --file=<PATH-TO-VIDEO-FILE>
```

### 2.3.1 Show the RTSP Stream Using GStreamer

You can display the RTSP stream using only GStreamer plug-ins by running the following from the host machine:

```bash
gst-launch-1.0 rtspsrc location=rtsp://localhost:8554/test protocols=tcp latency=200 ! \
rtph264depay ! h264parse ! avdec_h264 ! videoconvert ! timeoverlay ! autovideosink
```

### 2.3.2 Show the RTSP Stream Using Deepstream From X86/X64

You can display the RTSP stream using GStreamer and Deepstream by running the following from a non-Jetson device:

```bash
gst-launch-1.0 rtspsrc location=rtsp://localhost:8554/test protocols=tcp latency=500 ! \
rtph264depay ! h264parse ! nvv4l2decoder ! queue ! nvvideoconvert ! queue ! \
mux.sink_1 nvstreammux name=mux width=1920 height=1080 batch-size=1 live-source=1 ! \
queue ! nvvideoconvert ! queue ! nvdsosd ! queue ! nveglglessink
```

### 2.3.2 Show the RTSP Stream Using Deepstream From Jetson

You can display the RTSP stream using GStreamer and Deepstream by running the following from a Jetson device:

```bash
gst-launch-1.0 rtspsrc location=rtsp://localhost:8554/test protocols=tcp latency=500 ! \
rtph264depay ! h264parse ! nvv4l2decoder ! queue ! nvvideoconvert ! queue ! \
mux.sink_1 nvstreammux name=mux width=1920 height=1080 batch-size=1 live-source=1 ! \
queue ! nvvideoconvert ! queue ! nvdsosd ! queue ! nvegltransform ! nveglglessink
```
181 changes: 181 additions & 0 deletions gst-examples/gst-rtsp-server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
"""
RTSP Server Script
This script creates an RTSP server that streams a media file specified via command-line arguments.
The stream can be played back using an RTSP-compatible player like VLC or ffplay.
Example usage to start the server:
python3 rtsp_server.py --file /path/to/media/file.mp4
To playback the stream, use one of the following:
- ffplay rtsp://localhost:8554/test
- gst-launch-1.0 rtspsrc location=rtsp://127.0.0.1:8554/camera1 protocols=tcp latency=500 !
rtph264depay ! h264parse ! nvv4l2decoder ! queue ! nvvideoconvert ! queue !
mux.sink_1 nvstreammux name=mux width=1920 height=1080 batch-size=1 live-source=1 ! queue !
nvvideoconvert ! queue ! nvdsosd ! queue ! nveglglessink
The server will stream the media file over the RTSP protocol, converting raw video to H.264 format.
"""

import gi
import os
import argparse
from gi.repository import Gst, GstRtspServer, GLib

gi.require_version("Gst", "1.0")
gi.require_version("GstRtspServer", "1.0")


class RTSPServer:
"""RTSP Server to stream media files over RTSP protocol.
Parameters
----------
file_path : str
The path to the media file to be streamed.
Raises
------
FileNotFoundError
If the specified file path does not exist.
"""

def __init__(self, file_path: str) -> None:
if not os.path.isfile(file_path):
raise FileNotFoundError(f"File not found: {file_path}")

Gst.init(None)

server = GstRtspServer.RTSPServer()
server.set_address("0.0.0.0")
server.props.service = "8554"

factory = GstRtspServer.RTSPMediaFactory()
launch_str = f"""
filesrc location="{file_path}" !
decodebin name=decodebin !
queue !
videoconvert !
x264enc !
h264parse !
rtph264pay name=pay0 pt=96 config-interval=1
"""
factory.set_launch(launch_str)
factory.set_shared(True)
factory.set_eos_shutdown(False) # Ensure the pipeline is created immediately

# factory.connect("media-configure", self.on_media_configure)

server.get_mount_points().add_factory("/test", factory)
server.attach(None)
print("RTSP server is running on rtsp://0.0.0.0:8554/test")

def on_media_configure(
self, factory: GstRtspServer.RTSPMediaFactory, media: GstRtspServer.RTSPMedia
) -> None:
"""Configure the media pipeline when it is created.
Parameters
----------
factory : GstRtspServer.RTSPMediaFactory
The media factory that triggered this event.
media : GstRtspServer.RTSPMedia
The media object that contains the pipeline.
"""
print("Media is being configured.")
pipeline = media.get_element()
bus = pipeline.get_bus()

bus.add_signal_watch()
bus.connect("message::state-changed", self.on_state_changed)
bus.connect("message::error", self.on_error)
bus.connect("message::eos", self.on_eos)

decodebin = pipeline.get_by_name("decodebin")
if decodebin is not None:
decodebin.connect("pad-added", self.on_pad_added)

def on_pad_added(self, element: Gst.Element, pad: Gst.Pad) -> None:
"""Handle the addition of a new pad from decodebin.
Parameters
----------
element : Gst.Element
The element that generated the pad.
pad : Gst.Pad
The pad that was added.
"""
print(f"New pad '{pad.get_name()}' added.")
caps = pad.query_caps(None)
structure_name = caps.get_structure(0).get_name()
print(f"Pad Caps: {structure_name}")

if "video" in structure_name:
print("Video pad detected, linking elements.")
sink = element.get_static_pad("sink")
if pad.can_link(sink):
print(f"Linking pad {pad.get_name()} to sink pad.")
pad.link(sink)
else:
print(
f"Skipping non-video pad: {pad.get_name()} with caps: {structure_name}"
)

def on_state_changed(self, bus: Gst.Bus, message: Gst.Message) -> None:
"""Handle state changes of the GStreamer pipeline.
Parameters
----------
bus : Gst.Bus
The bus associated with the pipeline.
message : Gst.Message
The message describing the state change.
"""
if message.src.get_name() == "pipeline0":
old, new, pending = message.parse_state_changed()
print(f"Pipeline state changed: {old.value_nick} -> {new.value_nick}")
if new == Gst.State.PAUSED or new == Gst.State.PLAYING:
print("Pipeline is ready, attaching bus watch.")

def on_error(self, bus: Gst.Bus, message: Gst.Message) -> None:
"""Handle error messages from the GStreamer bus.
Parameters
----------
bus : Gst.Bus
The bus associated with the pipeline.
message : Gst.Message
The message describing the error.
"""
err, debug = message.parse_error()
print(f"ERROR: {err}, Debug info: {debug}")

def on_eos(self, bus: Gst.Bus, message: Gst.Message) -> None:
"""Handle end-of-stream (EOS) messages from the GStreamer bus.
Parameters
----------
bus : Gst.Bus
The bus associated with the pipeline.
message : Gst.Message
The message indicating that the end of the stream has been reached.
"""
print("End of stream reached!")


if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="RTSP Server to stream a media file over RTSP protocol."
)
parser.add_argument(
"--file", type=str, required=True, help="Path to the media file to be streamed."
)
args = parser.parse_args()

file_path: str = args.file
try:
server = RTSPServer(file_path)
loop = GLib.MainLoop() # Start the GLib main loop
loop.run()
except FileNotFoundError as e:
print(f"ERROR: {e}")

0 comments on commit 55db4e1

Please sign in to comment.