LibMISB/Examples/Example Application

From RidgeRun Developer Wiki

Example Application

This application comes from the other usage applications, but applied in an scenario where you want to codify some information, then a binary file that will be used as the input for a GStreamer application, and then stream the information over UDP by setting a IP Address and a port, to finally get the information in another GStreamer pipeline in a different terminal, using the network port set in the the execution of the GStreamer application.

Here is a graph of the generation of the coded binary file and the streaming process for a embeded metadata over a transport stream.

Figure 1. Example flow diagram.

Links to buy RidgeRun products needed to run the example

LibMISB
GStreamer in-band metada support

The next steps will guide you to build the code and install the libraries

Libraries

Please install the following libraries:

sudo apt install -y gstreamer1.0-plugins-ugly gstreamer1.0-plugins-bad gstreamer1.0-libav

Code

Create a file called main.c in the same place where the libmisb examples are located inside the builddir directory, the path might luck like this one:

~/your-directory/libmisb/builddir/examples/libmisb/

Then, use this code to fill out the main.c file:

/* Copyright (C) 2022 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 <glib.h>
#include <glib-unix.h>
#include <gst/gst.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <arpa/inet.h>

#define METADATA_PERIOD_SECS 1
#define FILE_LOCATION "./metadata_misb_library.ts"

static guint8 klv_metadata[61];

//const char* filename = "<PATH>/klv.bin";
const char* filename = "klv.bin";

typedef struct _GstMetaDemo GstMetaDemo;
struct _GstMetaDemo {
  GstElement *pipeline;
  GstElement *metasrc;
  GstElement *filesink;
  GMainLoop *main_loop;
};

static gboolean create_pipeline(GstMetaDemo *metademo, const char *ip, const char *port);
static void start_pipeline(GstMetaDemo *metademot);
static void stop_pipeline(GstMetaDemo *metademo);
static void release_resources(GstMetaDemo *metademo);
static gboolean bus_call(GstBus *bus, GstMessage *msg, gpointer data);
static gboolean handle_signal(gpointer data);

int is_valid_ip(const char *ip) {
    struct sockaddr_in sa;
    return inet_pton(AF_INET, ip, &(sa.sin_addr)) != 0;
}

int is_valid_port(const char *port) {
    char *endptr;
    long port_num = strtol(port, &endptr, 10);

    // Check for conversion errors or if the number is out of valid port range
    return *endptr == '\0' && port_num > 0 && port_num <= 65535;
}

int main(int argc, char *argv[]) {
struct stat sb;
  const char *ip = argv[1];
  const char *port = argv[2];

  printf("IP: %s\n", ip);
  printf("IP: %s\n", port);

  FILE* in_file = fopen(filename, "rb");
    if (!in_file) {
        perror("fopen");
        exit(EXIT_FAILURE);
    }

    
    if (stat(filename, &sb) == -1) {
        perror("stat");
        exit(EXIT_FAILURE);
    }

    fread(klv_metadata, sizeof(klv_metadata), 1, in_file);

    for(uint i = 0; i<sizeof(klv_metadata); i++)
        printf("%u ", klv_metadata[i]);
    printf("\n");
    fclose(in_file);

  GstMetaDemo *metademo = g_malloc(sizeof(GstMetaDemo));
  if(!metademo){
    g_print("Could not create demo\n");
    return 1;
  }

  g_unix_signal_add(SIGINT, (GSourceFunc)handle_signal, metademo);

  /* Initialization */
  gst_init(&argc, &argv);

  if (argc != 3) {
    fprintf(stderr, "Usage: %s <IP address> <Port number>\n", argv[0]);
    return 1;
  }

  if (!is_valid_ip(ip) && !is_valid_port(port)) {
     g_print("Invalid IP address or port number!\n");
     g_print("Usage: %s <IP address> <Port number>\n", argv[0]);
  }
  if (!create_pipeline(metademo, ip, port)) {
    g_free(metademo);
    return 1;
  }

  /* Set the pipeline to "playing" state*/
  g_print("Playing pipeline\n");
  start_pipeline(metademo);

  /* Iterate */
  g_print("Running...\n");
  g_main_loop_run(metademo->main_loop);

  /* Out of the main loop, clean up nicely */
  g_print("Returned, stopping playback\n");
  release_resources(metademo);

  return 0;
}

static gboolean bus_call(GstBus *bus, GstMessage *msg, gpointer data) {
  GMainLoop *loop = (GMainLoop *)data;

  switch (GST_MESSAGE_TYPE(msg)) {

  case GST_MESSAGE_EOS:
    g_print("End of stream\n");
    g_main_loop_quit(loop);
    break;

  case GST_MESSAGE_ERROR: {
    gchar *debug;
    GError *error;

    gst_message_parse_error(msg, &error, &debug);
    g_free(debug);

    g_printerr("Error: %s\n", error->message);
    g_error_free(error);

    g_main_loop_quit(loop);
    break;
  }
  default:
    break;
  }

  return TRUE;
}

static gboolean create_pipeline(GstMetaDemo *metademo, const char *ip, const char *port) {

  GMainLoop *loop;

  GstElement *pipeline = NULL;
  GstElement *metasrc = NULL;
  GstElement *filesink = NULL;
  GstBus *bus = NULL;
  GByteArray *barray = NULL;
  guint8 *array_copy = NULL;
  guint metalen = 0;
  GError *error = NULL;

  if (!metademo) {
    return FALSE;
  }

  loop = g_main_loop_new(NULL, FALSE);

  char actual_pipeline[] = "metasrc name=meta ! meta/x-klv,stream_type=21 ! mpegtsmux name=mux ! "
                            "filesink name=sink location=./metadata_misb_library.ts videotestsrc "
                            "is-live=true ! video/x-raw,width=640,height=480,framerate=30/1 ! queue ! "
                            "x264enc ! rtph264pay ! udpsink host=%s port=%s";

  char modified_pipeline[1024];  // Adjust the size accordingly

  // Use snprintf to format the string with the new values
  snprintf(modified_pipeline, sizeof(modified_pipeline), actual_pipeline, ip, port);

  // Now modified_pipeline contains the modified GStreamer pipeline
  printf("Modified pipeline: %s\n", modified_pipeline);
  printf("IP: %s\n", ip);
  printf("IP: %s\n", port);

  pipeline = gst_parse_launch(modified_pipeline, &error);

  if (error) {
    g_printerr("Unable to build pipeline (%s)\n", error->message);
    g_clear_error(&error);
    return FALSE;
  }

  metasrc = gst_bin_get_by_name(GST_BIN(pipeline), "meta");
  if (!metasrc) {
    g_printerr("Could not get metadata element\n");
    return FALSE;
  }

  filesink = gst_bin_get_by_name(GST_BIN(pipeline), "sink");
  if (!filesink) {
    g_printerr("Could not get filesink element\n");
    return FALSE;
  }

  /*Prepare metadata*/

  /*We need to copy the array since the GByteArray will be the new owner and free it for us*/
  metalen = sizeof(klv_metadata);
  array_copy = g_malloc (metalen);
  memcpy (array_copy, klv_metadata, metalen);
  
  barray = g_byte_array_new_take(array_copy, metalen);
  g_object_set(metasrc, "metadata-binary", barray, NULL);
  g_boxed_free(G_TYPE_BYTE_ARRAY, barray);

  g_object_set(G_OBJECT(metasrc), "period", METADATA_PERIOD_SECS, NULL);
  g_object_set(G_OBJECT(filesink), "location", FILE_LOCATION, NULL);

  /* we add a message handler */
  bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
  gst_bus_add_watch(bus, bus_call, loop);
  gst_object_unref(bus);

  metademo->pipeline = pipeline;
  metademo->main_loop = loop;
  metademo->metasrc = metasrc;
  metademo->filesink = filesink;

  return TRUE;
}

static void start_pipeline(GstMetaDemo *metademo) {
  gst_element_set_state(metademo->pipeline, GST_STATE_PLAYING);
}
static void stop_pipeline(GstMetaDemo *metademo) {
  gst_element_set_state(metademo->pipeline, GST_STATE_NULL);
}

static void release_resources(GstMetaDemo *metademo) {
  if (!metademo) {
    return;
  }

  stop_pipeline(metademo);

  if (metademo->pipeline) {
    gst_object_unref(metademo->pipeline);
    metademo->pipeline = NULL;
  }

  if (metademo->metasrc) {
    gst_object_unref(metademo->metasrc);
    metademo->metasrc = NULL;
  }

  if (metademo->filesink) {
    gst_object_unref(metademo->filesink);
    metademo->filesink = NULL;
  }

  if (metademo->main_loop) {
    g_main_loop_unref(metademo->main_loop);
    metademo->main_loop = NULL;
  }
}

static gboolean handle_signal(gpointer data) {
  GstMetaDemo *metademo = (GstMetaDemo *)data;

  g_main_loop_quit(metademo->main_loop);

  return TRUE;
}

To compile this code run:

gcc -Wall main.c -o main `pkg-config --cflags --libs gstreamer-1.0`

How to execute the application

1. Execute the code to create the binary file to be streamed from the JSON file
1.1 Go to the same working directory where you created the main.c file. 1.2 Run this command:

./misb-converter --verbose --encode -i misb_ST0601_sample.json -o klv.bin

2. Open a terminal to run the GStreamer pipeline who will get the streaming from the application 2.1 Run this command:

gst-launch-1.0 udpscr port=5000 caps = "application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)H264, payload=(int)96" ! rtph254depay ! decodebin ! videoconvert ! autovideosink

3. Open a different terminal to run the GStreamer application. 3.1. To run it you must provide an IP address and port, for default we recommend to use 127.0.0.1 for the IP address, and 5000 for the port. The command to run it is:

./main 127.0.0.1 5000

Expected results

You will see a new Window with a videotestsrc pattern as this one:

Figure 2. Video test source pattern.

If that is the case, congrats, you succesfully finished this guide.