Demo Application

From RidgeRun Developer Wiki




Preferred Partner Logo 3





TL;DR


The RidgeRun Development Suite (RDS) includes a powerful demo application called RR-Media, designed to serve two key purposes:


1. Showcase common use cases

RR-Media offers a collection of ready-to-run demo applications that illustrate how RidgeRun products can be used to address real-world challenges using RDS.

2. Provide a modular Python API

Beyond demonstrations, RR-Media provides a flexible, modular Python-based API that empowers developers to quickly build and iterate on custom applications. It serves as a practical foundation for creating Minimum Viable Products (MVPs), significantly reducing time to market by allowing teams to validate ideas and integrate RidgeRun technologies with minimal setup.


Running Demo Applications

Info
Before running RR-Media, make sure you followed the Installation and Setup section.


Pipeline Monitoring

RR-Media offers a collection of demo applications showcasing several of RidgeRun's products. In all demos, you will have the option to enable any GstShark tracer or enable performance (framerate and CPU) monitoring of the running example.

GstShark Tracers

When you run the application for the first time, you will see the following message printed:

Do you want to activate GStreamer tracers?  [y/n] (n):

Hit enter or type n to disable tracers.

If you want to enable any tracer, type y and the following menu will be shown:

Do you want to activate GStreamer tracers?  [y/n] (n): y
Select tracers to enable (comma separated)
  1. BITRATE
  2. CPUUSAGE
  3. FRAMERATE
  4. INTERLATENCY
  5. LATENCY
  6. PROCTIME
  7. QUEUELEVEL
  8. SCHEDULETIME
Select tracers to enable (comma separated):

You can select a comma-separated list of tracers that will be executed with any of the demo applications.

When the demo application finishes, you will see a folder with name gstshark_<CURRENT DATE AND TIME>. You can use gstshark-plt to see the tracers results by running the following command:

gstshark-plot gstshark_<CURRENT DATE AND TIME>

That will open a window with plots for the different enabled tracers.

Performance Monitoring

In each of the demo applications, you will find configuration option that reads Performance monitoring (OFF). This option will allow you to enable performance monitoring which will show you periodically CPU usage and framerate. Select that option to enable/disable performance monitoring and the following menu will appear:

  1. ON
  2. OFF
Enter your choice [1/2] (2):

Select 1 (ON) or 2 (OFF) to enable or disable performance monitoring. Then you can start the demo application. If enabled, periodic messages like this will be shown:

2025-07-08 17:19:28,443 - toolkit-demo - plugin.py      67 -    INFO: perf: perf_test; timestamp: 22:29:26.333775664; bps: 27648000.000; mean_bps: 27648000.000; fps: 30.000; mean_fps: 29.999; cpu: 17; 
2025-07-08 17:19:29,443 - toolkit-demo - plugin.py      67 -    INFO: perf: perf_test; timestamp: 22:29:27.333821848; bps: 27648000.000; mean_bps: 27648000.000; fps: 29.999; mean_fps: 29.999; cpu: 12; 
2025-07-08 17:19:30,477 - toolkit-demo - plugin.py      67 -    INFO: perf: perf_test; timestamp: 22:29:28.367237777; bps: 28569600.000; mean_bps: 27955200.000; fps: 29.998; mean_fps: 29.999; cpu: 11;

Running RR-Media

Info

Some of the demo applications make use of GstDaemon to execute GStreamer pipelines so make sure you execute it before RR-Media:

gstd &


Start RR-Media with the following command:

rr-media

Once executed, you will be asked whether or not you want to enable GStreamer tracers, and after that, the following menu will be shown:

Available Plugins
1. Gstd Dynamic Source Switcher
2. Metadata Demo
3. Sample Plugin
4. Dynamic Source Switcher
5. Stitcher Plugin
6. Stitcher 360 + PTZ Plugin
7. Birds Eye View (BEV)
0. Exit

Pick one of the demos and follow the corresponding instructions.

1. Gstd Dynamic Source Switcher: Dynamis sour switching using Gstreamer Daemon.

2. Metadata Demo: Test SEI, OBU and TS Metadata injection.

3. Sample Plugin: Sample demo showing a simple test pattern.

4. Dynamic Source Switcher: Dynamic source switching using RR-Media API.

5. Stitcher Plugin: 6 camera video stitching application.

6. Stitcher 360 + PTZ Plugin: 360 degree panoramic video demonstration.

7. Birds Eye View (BEV): Aerial (Birds Eye View) video demonstration.


Info
Go to use cases section for more details on each demo.


RR-Media API

Documentation


Using RR-Media API

RR-Media is designed as a modular, Python-based API that simplifies the process of building multimedia applications using RidgeRun products. Each component is encapsulated as a reusable "block" that can be easily interconnected—enabling faster, more intuitive prototyping without requiring prior GStreamer knowledge.

Think of your final application as a graph of functional blocks: you simply select the ones you need and connect them to bring your application to life—streamlining development and accelerating time to MVP.

The steps to build your application are as follows (read the API documentation for more details):

1. Import RR-Media

from rrmedia.media.core.factory import ModuleFactory
from rrmedia.media.core.graph import ModuleGraph

2. Create a ModuleGraph object

# Create graph
graph = ModuleGraph()

3. Use ModuleFactory to create your modules instances

# Source
source = ModuleFactory.create(
    "gst.source.file",
    location="video/file/location",
    name="input"
    )

# Video sink
sink = ModuleFactory.create(
    "jetson.sink.video",
    name="video_sink",
    extra_latency=110000000
    )


Info
Use ModuleFactory.list_available() to list the available modules


4. Add your modules to the graph

# Add modules
graph.add(source)
graph.add(sink)

5. Connect your modules

# Connect modules
graph.connect("input", "video_sink")

6. (OPTIONAL) Print the pipeline used by the graph

# Print pipeline
print("Graph pipeline: %s", graph.dump_launch())

7. Start playback

# Start playback
graph.play()

# Start loop (this is a blocking function)
graph.loop()

Your final application will look like this:

Sample Graph
from rrmedia.media.core.factory import ModuleFactory
from rrmedia.media.core.graph import ModuleGraph

# Create graph
graph = ModuleGraph()

# Source
source = ModuleFactory.create(
    "gst.source.file",
    location="video/file/location",
    name="input"
    )

# Video sink
sink = ModuleFactory.create(
    "jetson.sink.video",
    name="video_sink",
    extra_latency=110000000
    )

# Add modules
graph.add(source)
graph.add(sink)

# Connect modules
graph.connect("input", "video_sink")

# Print pipeline
print("Graph pipeline: %s", graph.dump_launch())

# Start playback
graph.play()

# Start loop (this is a blocking function)
graph.loop()


Info

Once the graph is playing, you can call pause() or stop() or change module properties.

Consult RR-Media API documentation for available modules and their specific options.


Customizing RR-Media

Info
  • Contact ridgerun for RR-Media source code.
  • For this section, GStreamer knowledge is required.
  • Visit [RR-Media api documentation] for more details.


The base of RR-Media are the Modules. A module is a class that encapsulates a portion of a GStreamer pipeline. The following picture shows the class diagram for the Modules.

RR Media Module Class Diagram
RR Media Module Class Diagram

In the class diagram, 4 base classes can be highlighted:

  • SourceModule: For source modules. These are modules that only source data to other modules.
  • SinkModule: For sink modules. These are modules that consume data from other modules.
  • FilterModule: For filter modules. These are modules that consume data, perform processing, and then source the processed data to other modules.
  • MisoModule: These are modules with multiple inputs and one output (a mux would be a Miso module).

Create your Module

The following is an example of a very simple module:

from rrmedia.media.core.base import SourceModule
from rrmedia.media.core.factory import register_module

@register_module("gst.source.test")
class GstTestSource(SourceModule):
    def __init__(self, pattern: str, width: int = 320, height: int = 240, name: str = None):
        actual_name = name or f"test_src_{id(self) & 0xFFFF}"
        SourceModule.__init__(self, name=actual_name)
        
        self._width = width
        self._height = height
        self._pattern = pattern

    @property
    def launch_description(self) -> str:
        description = (
            f"videotestsrc is-live=true name={self.module_name} pattern={self._pattern} ! "
            f"video/x-raw,width={self._width},height={self._height}"
        )
        return description

Now let's walk through the code:

1. Module registration

@register_module("gst.source.test")

The first thing you will notice is the register_module decorator. Use it to register your module with the ModuleFactory with a unique name. In this case, gst.source.test.

2. Create your Module Class

class GstTestSource(SourceModule):
    def __init__(self, pattern: str, width: int = 320, height: int = 240, name: str = None):
        actual_name = name or f"test_src_{id(self) & 0xFFFF}"
        SourceModule.__init__(self, name=actual_name)
        
        self._width = width
        self._height = height
        self._pattern = pattern

Inherit from one of the available base classes depending on your module type and add any necessary argument to your constructor. In our example we are using SourceModule.

3. Implement the launch_description property

    @property
    def launch_description(self) -> str:
        description = (
            f"videotestsrc is-live=true name={self.module_name} pattern={self._pattern} ! "
            f"video/x-raw,width={self._width},height={self._height}"
        )
        return description

Here you will implement your gst-launch-like pipeline description. In our example, a simple videotestsrc with caps.

After this, put your module into the rrmedia/media/modules directory and you are done. After this, you should be able to instantiate your module from any application by using ModuleFactory:

from rrmedia.media.core.factory import ModuleFactory

my_module = ModuleFactory.create("gst.source.test", pattern=0, width=1280, height=720, name="my_source")

Then you can add it to a graph and connect it to other modules.

from rrmedia.media.core.graph import ModuleGraph

graph = ModuleGraph()

graph.add(my_module)
graph.connect("my_module", "other_module")


Configurable Modules

In the previous example, the module properties can be set at creation time but can't be changed later. That could be enough for some use cases, but in other situations, you might want to later change any property of the module. Let's enhance our previous example to allow that:

from rrmedia.media.core.base import Configurable, SourceModule
from rrmedia.media.core.factory import register_module


@register_module("gst.source.test")
class GstTestSource(SourceModule, Configurable):
    def __init__(self, pattern: str, width: int = 320, height: int = 240, name: str = None):
        actual_name = name or f"test_src_{id(self) & 0xFFFF}"
        SourceModule.__init__(self, name=actual_name)
        Configurable.__init__(self)

        self._props = {
            'pattern': pattern,
            'width': width,
            'height': height
        }

    def set_property_runtime(self, prop: str, value):

        if prop == 'pattern':
            return super().set_property_runtime(prop, value)

        return self

    @property
    def launch_description(self) -> str:
        description = (
            f"videotestsrc is-live=true name={self.module_name} pattern={self._props['pattern']} ! "
            f"video/x-raw,width={self._props['width']},height={self._props['height']}"
        )
        return description

You might notice 3 main differences:

1. We are inheriting from Configurable

class GstTestSource(SourceModule, Configurable):
    def __init__(self, pattern: str, width: int = 320, height: int = 240, name: str = None):
        actual_name = name or f"test_src_{id(self) & 0xFFFF}"
        SourceModule.__init__(self, name=actual_name)
        Configurable.__init__(self)

This gives us the logic to allow changing properties and now we inherit 2 more functions: set_property and set_property_runtime.

2. We are now using an internal variable _props:

class GstTestSource(SourceModule, Configurable):
    def __init__(self, pattern: str, width: int = 320, height: int = 240, name: str = None):
        actual_name = name or f"test_src_{id(self) & 0xFFFF}"
        SourceModule.__init__(self, name=actual_name)
        Configurable.__init__(self)

        self._props = {
            'pattern': pattern,
            'width': width,
            'height': height
        }

This is also given and used by the Configurable class. Whenever the set_property function is called, this array will be updated.

3. Implementation of set_property_runtime


    def set_property_runtime(self, prop: str, value):

        if prop == 'pattern':
            return super().set_property_runtime(prop, value)

        return self

In this particular example, we only want the pattern property to change at runtime (after running graph.play()), that's why we overrode set_property_runtime to allow to change only the pattern property. The remaining properties will be updated in the _props array but the running pipeline won't be updated. If no set_property function is overrode, then all the properties are allowed to change by default.


Info
The underlying logic will call GStreamer set_property for the GStreamer element with the name of this module, that's why in the example we used self.module_name as the name for the videotestsrc element.


Miso Modules

Miso modules can be implemented in the same way as the previous examples, just inherit from MisoModuel class. In the same way, they can also be configurable. The only different is the addition of the get_sink_template function. This allows the module to return custom descriptions for each connection that is done. See the following code as an example:

    def get_sink_template(self, index: int = None) -> str:
        pad_description = f"queue ! nvvidconv ! {self. module_name}."
        if index is not None:
            pad_description += f"sink_{index}"

        return pad_description

as can be seen, the function returns f"queue ! nvvidconv ! {self. module_name}.sink_{index}", this makes this piece of code to be added to each of the connections done to the module. The index argument comes form the index argument in ModuleGraph.connect()

graph.connect("some_module", "some_miso_module", 1)

This will call get_sink_template with index=1. This allows control over which module will be attached to which pad in the sink module.

If no get_sink_template is implemented, by default the pad description will be module_name.sink_{index} if index is given or just module_name. if no index is given.


Info
You can check rrmedia/media/modules/stitcher.py for a full example.