LibMISB/Examples/Example Application: Difference between revisions

From RidgeRun Developer Wiki
Line 5: Line 5:
==Example Application==
==Example Application==


This application comes from the other usage applications, but applied in an scenario where you want to codify some information, then a coded decimal data from the output of the misb converter app will be used as the input for a GStreamer application, and then, stream the information over UDP by setting an 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.
This application comes from the other usage applications, but applied in an scenario where you want to codify a JSON MISB file, then the coded decimal data from the output of the misb converter app will be used as the input for a GStreamer application, and then, stream the information over UDP by setting an 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.
Here is a graph of the generation of the coded binary file and the streaming process for a embeded metadata over a transport stream.

Revision as of 15:38, 4 December 2023



Previous: Examples/GStreamer_Application Index Next: Evaluating





Example Application

This application comes from the other usage applications, but applied in an scenario where you want to codify a JSON MISB file, then the coded decimal data from the output of the misb converter app will be used as the input for a GStreamer application, and then, stream the information over UDP by setting an 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. This is due to the application needs a binary file called misb-converter that only comes in the build directory of the examples. 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) 2023 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

static guint8 klv_metadata[61];

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, 
                                char *output_converter);
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;
}

char *converter_caller(const char *json_path){
  int pipefd[2];
  char buffer[4096];
    pid_t child_pid;

    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    child_pid = fork();

    if (child_pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (access(json_path, F_OK) != 0) {
        printf("JSON File '%s' does not exist.\n", json_path);
        exit(EXIT_FAILURE);
    } 

    if (child_pid == 0) { // Child process
        close(pipefd[0]); // Close the read end of the pipe

        // Redirect stdout to the write end of the pipe
        dup2(pipefd[1], STDOUT_FILENO);

        // Execute the command
        execlp("./misb-converter", "./misb-converter", "--verbose", "--encode", "-i", json_path, "-o", "klv.bin", NULL);

        // If execlp fails
        perror("execlp");
        exit(EXIT_FAILURE);
    } else { // Parent process
        close(pipefd[1]); // Close the write end of the pipe

        // Read the output from the read end of the pipe
        ssize_t bytesRead = read(pipefd[0], buffer, sizeof(buffer));

        if (bytesRead == -1) {
            perror("read");
            exit(EXIT_FAILURE);
        }

        // Null-terminate the string
        buffer[bytesRead] = '\0';

        wait(NULL);
    }

    char* result = strdup(buffer);
    if (result == NULL) {
        perror("strdup");
        exit(EXIT_FAILURE);
    }

    return result;
}


int main(int argc, char *argv[]) {
  const char *ip = argv[1];
  const char *port = argv[2];
  const char *json_path = argv[3];

  printf("IP: %s\n", ip);
  printf("Port: %s\n", port);
  printf("JSON Path: %s\n", json_path);


  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 != 4) {
    fprintf(stderr, "Usage: %s <IP address> <Port number> <JSON file path>\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> <JSON file path>\n", argv[0]);
  }
  char *output_converter = converter_caller(json_path);
 
  if (!create_pipeline(metademo, ip, port, output_converter)) {
    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, char *output_converter ) {

  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 period=1 metadata='%s' ! meta/x-klv ! mpegtsmux name=mux ! "
                      "udpsink host=%s port=%s videotestsrc is-live=true ! "
                      "video/x-raw,format=(string)I420,width=320,height=240,framerate=(fraction)30/1 ! x264enc ! mux.";

  char modified_pipeline[1024];

  // Use snprintf to format the string with the new values
  memmove(output_converter, output_converter + 14, strlen(output_converter) - 14 + 1);
  output_converter[strlen(output_converter) - 2] = '\0';
  snprintf(modified_pipeline, sizeof(modified_pipeline), actual_pipeline, output_converter, ip, port);

  // Now modified_pipeline contains the modified GStreamer pipeline
  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;
  }

  /*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);

  /* 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. Open a terminal to run the GStreamer pipeline that will get the stream remotely from the application.
Run this command:

gst-launch-1.0 -e udpsrc port=5000 ! 'video/mpegts, systemstream=(boolean)true, packetsize=(int)188' ! tsdemux name=demux demux. ! queue !  h264parse ! 'video/x-h264, stream-format=byte-stream, alignment=au' ! avdec_h264 ! autovideosink sync=false demux. ! queue ! 'meta/x-klv' ! metasink async=false

2. Open a different terminal to run the GStreamer application.
To run it you must provide an IP address, port and the JSON file path where that you want to encode and send through the streaming, 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 misb_ST0601_sample.json

Expected results

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

Figure 1. Video test source pattern.
Figure 2. Output of the video and the commands output.

As you can see in the left, you will find the encoded data streamed. To decode this data you can use the code in the extra section of this wiki. That code code will decode the file and create a file with an extension .bin, then you can decode that binary to get the JSON file by using the libmisb decoder.

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

Extra code


/* Copyright (C) 2023 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 <stdio.h>
#include <stdlib.h>

void printUsage() {
    printf("Usage: ./decoder -n <name_of_file> decimal_values...\n");
}

int main(int argc, char *argv[]) {
    if (argc < 4 || strcmp(argv[1], "-n") != 0) {
        // Incorrect number of arguments or missing -n flag
        printUsage();
        return 1;
    }

    char *outputFileName = argv[2];
    FILE *outputFile = fopen(outputFileName, "wb");

    if (outputFile == NULL) {
        perror("Error opening output file");
        return 1;
    }

    // Parse and write decimal values to binary file
    for (int i = 3; i < argc; i++) {
        int decimalValue = atoi(argv[i]);
        fputc(decimalValue, outputFile);
    }

    fclose(outputFile);

    printf("Conversion successful. Output file: %s\n", outputFileName);

    return 0;
}

Save the file as decoder.c and then you can compile the code by using:

gcc decoder.c -o decoder

The usage is the following:

decoder -n <name_of_binary_output.bin> <group of decimal values representation>

Example:

./decoder -n output.bin 6 14 43 52 2 11 1 1 14 1 3 1 1 0 0 0 44 2 8 0 4 89 249 174 32 34 168 3 9 77 73 83 83 73 79 78 48 49 4 6 65 70 45 49 48 49 5 2 113 194 15 2 194 33 65 1 17 1 2 164 125

This will create a file called output.bin, to create the JSON file you can use:

./misb-converter --decode -i output.bin -o output.json


Previous: Examples/GStreamer_Application Index Next: Evaluating