Streaming RTMP To Local Using C++ And GStreamer

Introduction

Hello! ๐Ÿ˜Ž

Previously I shown how to stream the local webcam to an RTMP server, this tutorial can be found via: https://ethan-dev.com/post/streaming-webcam-to-rtmp-using-gstreamer

This time I will show you how to play the above sample (or any other RTMP stream) using C++ and GStreamer.


Requirements

  • Knowledge of C++
  • Basic Knowledge Of GStreamer
  • GStreamer dev libraries installed

Coding The Application

First we need to include the necessary imports, open a file called "main.cpp" and add the following one line to import GStreamer:

#include <gst/gst.h>

Next we need to add a callback method that listens for an "on_pad_added" event, unlike sending a stream when you are receiving it you make not get the stream instantly which will give you a linker problem when trying to link the elements.

When receiving it's best to add a callback that runs when the stream is actually received. Like this:

static void on_pad_added(GstElement *element, GstPad* pad, gpointer user_data)
{
  GstPad *sinkpad;
  GstElement *decoder = (GstElement *)user_data;

  sinkpad = gst_element_get_static_pad(decoder, "sink");
  gst_pad_link(pad, sinkpad);
  gst_object_unref(sinkpad);
}

The above will run when the pad has been added and will add element to another elements sinkpad (in this case h264parse).

Next we will create a main method and create the elements etc needed for the pipeline:

int main(int argc, char *argv[])
{
  GstElement *pipeline;
  GstElement *source;
  GstElement *demuxer;
  GstElement *parser;
  GstElement *decoder;
  GstElement *converter;
  GstElement *sink;
  GstBus *bus;
  GstMessage *message;
  GstStateChangeReturn ret;

The above just creates the elements, next we will need to initialize GStreamer via the following:

gst_init(&argc, &argv);

It's best to call the above before trying to initialize and other GStreamer elements.

Next we will initialize the other elements and the pipeline via the following:

source = gst_element_factory_make("rtmpsrc", NULL);
demuxer = gst_element_factory_make("flvdemux", NULL);
parser = gst_element_factory_make("h264parse", NULL);
decoder = gst_element_factory_make("avdec_h264", NULL);
converter = gst_element_factory_make("videoconvert", NULL);
sink = gst_element_factory_make("autovideosink", NULL);

pipeline = gst_pipeline_new("pipeline");

Next we will check to see if the elements have been created correctly or not, this is done via the following if statement, if the elements could not be created we end the program:

if (!pipeline || !source || !demuxer || !parser || !decoder || !sink)
{
	g_printerr("Not all elements could be created.\n");

	return -1; 
}

Next we add the elements to the pipeline via:

gst_bin_add_many(GST_BIN(pipeline), source, demuxer, parser, decoder, converter, sink, NULL);

Finally we can now link all the elements together via the following:

if (!gst_element_link(source, demuxer))
{
	g_printerr("Elements could not be linked\n");
    gst_object_unref(pipeline);

    return -1;
}

if (!gst_element_link_many(parser, decoder, converter, sink, NULL))
{
    g_printerr("Element could not be linked\n");
    gst_object_unref(pipeline);

    return -1;
}

The demuxer and the parser will be linked via the "on_pad_added" method so we don't do it here, doing it here could possibly lead to a linker error due to there being no stream yet.

Next we add the above "on_pad_added" callback to the demuxer:

g_signal_connect(demuxer, "pad-added", G_CALLBACK(on_pad_added), parser);

Next we set the rtmpsrc url with the following (note, make sure to replace the RTMP url with your own)

g_object_set(source, "location", "rtmp://localhost:1935/stream/video", NULL);

Finally we start the GStreamer pipeline by setting it to the playing state:

ret = gst_element_set_state(pipeline, GST_STATE_PLAYING);

To check if the change is successful or not we can use the following if statement:

if (ret == GST_STATE_CHANGE_FAILURE)
{
    g_printerr("Unable to set pipeline to the playing state\n");
    gst_object_unref(pipeline);

    return -1;
}

If it fails the program will halt.

Next we also need to listen for any error message and/or EOS messages, this can be done via the following:

bus = gst_element_get_bus(pipeline);
message = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, GstMessageType(GST_MESSAGE_ERROR | GST_MESSAGE_EOS));

if (message != NULL)
{
    GError * error;
    gchar *debug_info;

    switch (GST_MESSAGE_TYPE(message))
    {
	    case GST_MESSAGE_ERROR:
	        gst_message_parse_error(message, &error, &debug_info);
	        g_printerr("Error received from element %s: %s\n", GST_OBJECT_NAME(message->src), error->message);
	        g_printerr("Debugging information: %s\n", debug_info ? debug_info : "none");
	        g_clear_error(&error);
	        g_free(debug_info);
	        break;
	    case GST_MESSAGE_EOS:
	        g_print("End of stream");
	        break;
	    default:
	        g_printerr("Unknown message received\n");
	        break;
    }
    gst_message_unref(message);
  }

The above simply listens for any error and or EOS message and displays log information, depending on what you are trying to accomplish, you may need to implement some more logic. For example if you want to save RTMP to a video file.

Finally we clean up the code via the following:

gst_object_unref(bus);
gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(pipeline);

return 0;

Done! ๐Ÿ˜ƒ Now we need to build the project, I will show you how to do that via CMakeLists.


Creating The CMakeLists File

To build the project we need to create a CMakeLists.txt file, create the file and add the following:

cmake_minimum_required(VERSION 3.10)
project(rtmprecv)

set(CMAKE_CXX_STANDARD 14) 

find_package(PkgConfig REQUIRED)
pkg_check_modules(GST REQUIRED gstreamer-1.0)

include_directories(${GST_INCLUDE_DIRS})

add_executable(rtmprecv main.cpp)
target_link_libraries(rtmprecv ${GST_LIBRARIES})

The above just links the code with the dev GStreamer dependencies, now we can actually build the project!

This can be done via the following commands:

mkdir build && cd build
cmake ..
make

After running make you should see the "rtmprecv" executable in the build directory. ๐Ÿ˜†


Running The Example

To run the example you will need an RTMP source, this can be the previous sample I wrote about or your own RTMP source. Please note if you are using my previous example you will need to change the url to the following "rtmp://localhost:1935/stream/video".

./rtmprecv

If all goes well you should be able to see the RTMP stream locally. ๐Ÿ˜Ž


Conclusion

In this tutorial I have shown you how to create a GStreamer/C++ program that receives and displays a RTMP stream.

I hope it helps you as much as I had fun making it. ๐Ÿ˜†

If you have any questions or improvements etc. Please let me know in the comments, as always you can find the sample code on my repository: https://github.com/ethand91/rtmp-to-local

Happy Coding! ๐Ÿ˜Ž


Like me work? I post about a variety of topics, if you would like to see more please like and follow me. Also I love coffee.

โ€œBuy Me A Coffeeโ€

If you are looking to learn Algorithm Patterns to ace the coding interview I recommend the following course