LibMISB - Examples - RTSP Streaming Example
| LibMISB |
|---|
| Introduction |
| Supported Standards |
| Getting Started |
| Examples |
| Evaluating |
| Contact Us |
Introduction
This example demonstrates how to stream live video together with MISB-style KLV metadata over RTSP. Metadata is injected and multiplexed with H.264 video into an MPEG Transport Stream, and the resulting stream can be exposed to RTSP clients.
Links RidgeRun products required to run the example:
LibMISB
GStreamer in-band metada support
The next steps will guide you to build the code and install the libraries
Before building the application
Please install the following libraries:
sudo apt install -y gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-plugins-bad gstreamer1.0-libav libgstrtspserver-1.0-dev libglib2.0-dev
This example was tested on GStreamer version 1.22.6.
Code
Open a terminal and create a file called sender.cpp and add the following contents:
/* Copyright (C) 2025 RidgeRun, LLC (http://www.ridgerun.com)
* All Rights Reserved.
*
* The contents of this software are proprietary and confidential to RidgeRun,
* LLC. No part of this program may be photocopied, reproduced or translated
* into another programming language without prior written consent of
* RidgeRun, LLC. The user is free to modify the source code after obtaining
* a software license from RidgeRun. All source code changes must be provided
* back to RidgeRun without any encumbrance.
*/
#include <gst/gst.h>
#include <gst/rtsp-server/rtsp-server.h>
#include <glib.h>
#include <glib-unix.h>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#define PIPELINE_DESCRIPTION \
"metasrc name=meta ! meta/x-klv,stream_type=21 ! mpegtsmux name=mux ! " \
"rtpmp2tpay name=pay0 pt=33 " \
"videotestsrc is-live=true ! video/x-raw,width=640,height=480,framerate=30/1 ! queue ! " \
"x264enc key-int-max=1 ! mux."
#define METADATA_PERIOD_SECS 1
static gboolean handle_signal(gpointer data)
{
GMainLoop *loop = (GMainLoop*)data;
g_main_loop_quit(loop);
return TRUE;
}
static bool read_file_bytes(const char *path, std::vector<guint8> &out)
{
out.clear();
std::ifstream f(path, std::ios::binary);
if (!f.is_open()) return false;
f.seekg(0, std::ios::end);
std::streamsize sz = f.tellg();
f.seekg(0, std::ios::beg);
if (sz <= 0) return false;
out.resize((size_t)sz);
if (!f.read((char*)out.data(), sz)) return false;
return true;
}
static void print_first_bytes(const guint8 *data, guint len, guint max_bytes)
{
guint n = (len < max_bytes) ? len : max_bytes;
for (guint i = 0; i < n; ++i) g_print("%02x ", data[i]);
g_print("\n");
}
static void prepare_metadata_from_klv_file(GstElement *metasrc, const char *klv_bin_file)
{
std::vector<guint8> bytes;
if (!read_file_bytes(klv_bin_file, bytes)) {
g_printerr("Failed to read KLV file: %s\n", klv_bin_file);
return;
}
g_print("Loaded %zu bytes from %s\n", bytes.size(), klv_bin_file);
g_print("First 32 bytes: ");
print_first_bytes(bytes.data(), (guint)bytes.size(), 32);
guint8 *copy = (guint8*)g_malloc(bytes.size());
memcpy(copy, bytes.data(), bytes.size());
GByteArray *barray = g_byte_array_new_take(copy, (guint)bytes.size());
g_object_set(G_OBJECT(metasrc), "metadata-binary", barray, NULL);
g_boxed_free(G_TYPE_BYTE_ARRAY, barray);
g_object_set(G_OBJECT(metasrc), "period", METADATA_PERIOD_SECS, NULL);
}
static void media_configure(GstRTSPMediaFactory *factory, GstRTSPMedia *media, gpointer user_data)
{
(void)factory;
const char *klv_bin_file = (const char*)user_data;
GstElement *pipeline = gst_rtsp_media_get_element(media);
GstElement *metasrc = gst_bin_get_by_name(GST_BIN(pipeline), "meta");
if (metasrc) {
prepare_metadata_from_klv_file(metasrc, klv_bin_file);
g_object_unref(metasrc);
} else {
g_printerr("Could not find metasrc element in the pipeline!\n");
}
g_object_unref(pipeline);
}
int main(int argc, char *argv[])
{
const char *klv_bin_file = argv[1];
gst_init(&argc, &argv);
GMainLoop *loop = g_main_loop_new(NULL, FALSE);
g_unix_signal_add(SIGINT, (GSourceFunc)handle_signal, loop);
GstRTSPServer *server = gst_rtsp_server_new();
GstRTSPMountPoints *mounts = gst_rtsp_server_get_mount_points(server);
GstRTSPMediaFactory *factory = gst_rtsp_media_factory_new();
gst_rtsp_media_factory_set_launch(factory, PIPELINE_DESCRIPTION);
g_signal_connect(factory, "media-configure", (GCallback)media_configure, (gpointer)klv_bin_file);
gst_rtsp_mount_points_add_factory(mounts, "/test", factory);
g_object_unref(mounts);
gst_rtsp_server_attach(server, NULL);
g_print("stream ready at rtsp://127.0.0.1:8554/test\n");
g_print("Injecting metadata from KLV file: %s\n", klv_bin_file);
g_main_loop_run(loop);
g_main_loop_unref(loop);
g_object_unref(server);
return 0;
}
Then compile the code with the following command:
g++ -o sender sender.cpp `pkg-config --cflags --libs gstreamer-1.0 gstreamer-app-1.0 gstreamer-rtsp-server-1.0 glib-2.0 misb-0.0`
Run this command as well to use the variable later:
export SENDER_DIR=$PWD
Now create a file named receiver.cpp and add the following contents:
/* Copyright (C) 2025 RidgeRun, LLC (http://www.ridgerun.com)
* All Rights Reserved.
*
* The contents of this software are proprietary and confidential to RidgeRun,
* LLC. No part of this program may be photocopied, reproduced or translated
* into another programming language without prior written consent of
* RidgeRun, LLC. The user is free to modify the source code after obtaining
* a software license from RidgeRun. All source code changes must be provided
* back to RidgeRun without any encumbrance.
*/
#include <gst/gst.h>
#include <gst/app/gstappsink.h>
#include <glib-unix.h>
#include <cstdint>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
static const uint8_t KEY_PREFIX[5] = {0x06, 0x0E, 0x2B, 0x34, 0x02};
struct global_s {
GstElement* pipeline = nullptr;
GstElement* appsink = nullptr;
GMainLoop* loop = nullptr;
std::string out_prefix;
uint64_t packets = 0;
};
static std::string make_name(const std::string& prefix, uint64_t num_packets) {
std::ostringstream filename;
filename << prefix << "_" << std::setfill('0') << std::setw(4) << num_packets << ".bin";
return filename.str();
}
static GstFlowReturn on_new_sample(GstAppSink* sink, gpointer user_data) {
auto* gs = static_cast<global_s*>(user_data);
GstSample* sample = gst_app_sink_pull_sample(sink);
if (!sample) return GST_FLOW_OK;
GstBuffer* buf = gst_sample_get_buffer(sample);
if (!buf) {
gst_sample_unref(sample);
return GST_FLOW_OK;
}
GstMapInfo map;
if (!gst_buffer_map(buf, &map, GST_MAP_READ)) {
gst_sample_unref(sample);
return GST_FLOW_OK;
}
const std::string output_file_name = make_name(gs->out_prefix, gs->packets);
std::ofstream out(output_file_name, std::ios::binary);
out.write(reinterpret_cast<const char*>(KEY_PREFIX), sizeof(KEY_PREFIX));
out.write(reinterpret_cast<const char*>(map.data), (std::streamsize)map.size);
out.close();
gs->packets++;
gst_buffer_unmap(buf, &map);
gst_sample_unref(sample);
return GST_FLOW_OK;
}
static gboolean on_sigint(gpointer user_data) {
auto* gs = static_cast<global_s*>(user_data);
if (gs->pipeline) gst_element_send_event(gs->pipeline, gst_event_new_eos());
if (gs->loop) g_main_loop_quit(gs->loop);
return G_SOURCE_REMOVE;
}
static gboolean on_bus_msg(GstBus*, GstMessage* msg, gpointer user_data) {
auto* gs = static_cast<global_s*>(user_data);
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_ERROR: {
GError* err = nullptr;
gchar* dbg = nullptr;
gst_message_parse_error(msg, &err, &dbg);
std::cerr << "GST ERROR: " << (err ? err->message : "unknown") << "\n";
if (dbg) std::cerr << "Debug: " << dbg << "\n";
if (err) g_error_free(err);
if (dbg) g_free(dbg);
if (gs->loop) g_main_loop_quit(gs->loop);
break;
}
case GST_MESSAGE_EOS:
std::cerr << "EOS\n";
if (gs->loop) g_main_loop_quit(gs->loop);
break;
default:
break;
}
return TRUE;
}
int main(int argc, char** argv) {
const std::string url = argv[1];
const std::string prefix = argv[2];
gst_init(&argc, &argv);
global_s gs;
gs.out_prefix = prefix;
gs.loop = g_main_loop_new(nullptr, FALSE);
std::string pipeline_string =
"rtspsrc name=src location=\"" + url + "\" protocols=tcp latency=200 ! "
"rtpmp2tdepay ! tsdemux name=demux "
"demux. ! queue ! h264parse ! fakesink sync=false "
"demux. ! queue ! meta/x-klv ! appsink name=klvsink emit-signals=true sync=false";
GError* err = nullptr;
gs.pipeline = gst_parse_launch(pipeline_string.c_str(), &err);
if (!gs.pipeline) {
std::cerr << "gst_parse_launch failed: " << (err ? err->message : "unknown") << "\n";
if (err) g_error_free(err);
return 2;
}
gs.appsink = gst_bin_get_by_name(GST_BIN(gs.pipeline), "klvsink");
if (!gs.appsink) {
std::cerr << "Could not find appsink named 'klvsink' in pipeline.\n";
gst_object_unref(gs.pipeline);
return 3;
}
g_signal_connect(gs.appsink, "new-sample", G_CALLBACK(on_new_sample), &gs);
GstBus* bus = gst_element_get_bus(gs.pipeline);
gst_bus_add_watch(bus, on_bus_msg, &gs);
gst_object_unref(bus);
g_unix_signal_add(SIGINT, on_sigint, &gs);
std::cerr << "Writing packets with prefix: " << prefix << "\n";
gst_element_set_state(gs.pipeline, GST_STATE_PLAYING);
g_main_loop_run(gs.loop);
gst_element_set_state(gs.pipeline, GST_STATE_NULL);
if (gs.appsink) gst_object_unref(gs.appsink);
if (gs.pipeline) gst_object_unref(gs.pipeline);
if (gs.loop) g_main_loop_unref(gs.loop);
std::cerr << "Done. Packets written: " << gs.packets << "\n";
return 0;
}
And compile the code with the following command:
g++ -o receiver receiver.cpp $(pkg-config --cflags --libs gstreamer-1.0 gstreamer-app-1.0 glib-2.0) -lgobject-2.0
How to run the demo
1. Execute the sender.
First, go to the libmisb example's directory where the misb-converter was built and generate a klv.bin file:
cd libmisb/builddir/examples/libmisb ./misb-converter --verbose --encode -i misb_ST0601_sample.json -o klv.bin
Then copy the klv.bin file to the directory where there sender code was compiled:
cp klv.bin $SENDER_DIR
Execute the sender app:
cd $SENDER_DIR ./sender klv.bin
The following output should come up:
stream ready at rtsp://127.0.0.1:8554/test Injecting metadata from KLV file: klv.bin
2. Open a different terminal to execute the receiver.
Execute the receiver:
./receiver rtsp://127.0.0.1:8554/test pkt0601
It takes two arguments. The first argument is the URL where the rtsp stream is. The second argument is the prefix for each packet of metadata that will be saved. It will save a packet each second with the name pkt0601_XXXX.bin, where the X's are substituted with the packet number. The output should be:
Writing packets with prefix: pkt0601
and on the sender terminal you should see:
Loaded 244 bytes from klv.bin First 32 bytes: 06 0e 2b 34 02 0b 01 01 0e 01 03 01 01 00 00 00 81 e2 02 08 00 04 59 f9 ae 20 22 a8 03 09 4d 49
To stop it, press ctrl+c keys. It should show the following message:
Done. Packets written: 9
Expected results
You will see a new Window with a videotestsrc pattern as this one:

Also, on the receiver's folder, there should be 9 packets:
ls pkt0601_0000.bin pkt0601_0001.bin pkt0601_0002.bin pkt0601_0003.bin pkt0601_0004.bin pkt0601_0005.bin pkt0601_0006.bin pkt0601_0007.bin pkt0601_0008.bin receiver
Each of these bin files can be decoded back to a JSON format with the misb-converter. Copy the desired bin file to libmisb's examples directory and decode it with misb-converter:
cp pkt0601_0003.bin libmisb/builddir/examples/libmisb cd libmisb/builddir/examples/libmisb #decode ./misb-converter --decode -i pkt0601_0003.bin -o output-0601.json
The resulting file is output-0601.json with the following contents:
cat output-0601.json
{"key": "060E2B34020B01010E01030101000000", "items": [{"tag": "2", "value": "Oct. 24, 2008. 00:13:29.913"}, {"tag": "3", "value": "MISSION01"}, {"tag": "4", "value": "AF-101"}, {"tag": "5", "value": "159.974365"}, {"tag": "15", "value": "14190.7197"}, {"tag": "48", "value": [{"tag": "1", "value": "1"}, {"tag": "2", "value": "1"}, {"tag": "3", "value": "//USA"}, {"tag": "4", "value": "TS/SI/CompartmentA//"}, {"tag": "5", "value": "FOUO"}, {"tag": "6", "value": "506 82 81"}, {"tag": "12", "value": "1"}, {"tag": "13", "value": "USA"}, {"tag": "22", "value": "10"}]}, {"tag": "74", "value": [{"tag": "2", "value": "Apr. 19, 2001. 04:25:21.000"}, {"tag": "101", "value": [{"tag": "targetId", "value": "1234"}, {"tag": "1", "value": "409600"}, {"tag": "3", "value": "409600"}, {"tag": "5", "value": "80"}, {"tag": "102", "value": "N/A"}]}, {"tag": "4", "value": "6"}, {"tag": "6", "value": "14"}, {"tag": "7", "value": "N/A"}, {"tag": "8", "value": "1280"}, {"tag": "9", "value": "720"}]}, {"tag": "94", "value": "0170:F592-F023-7336-4AF8-AA91-62C0-0F2E-B2DA/16B7-4341-0008-41A0-BE36-5B5A-B96A-3645:D3"}, {"tag": "96", "value": "13898.546300"}, {"tag": "104", "value": "34567.349976"}, {"tag": "65", "value": "19"}]}
Each packet bin file will have the full metadata contents of the original klv.bin file that was streamed.
Considerations:
- This demo assumes that the first 5 bytes of the key are: 060E2B3402.
- It has been tested with standards ST0601, ST0903 and ST0102.
</noinclude