Introducing GStreamer: Elements and Containers

    image

    Hello again, habrayuzer interested in the GStreamer framework. In a previous article , I talked about how to initialize a library to fully work with it. Today we’ll take a look at the process of creating elements and assembling a pipeline. As a practical material, an audio player will be created, a simple copier of files (like cp ) - yes, GStreamer is so harsh that they can almost open beer for them. So go ahead!

    Creating an item using a factory


    The definition of the element is available and with pictures is set out here , however, some clarifications need to be given. The plug-in sets installed in the system (and they, by the way, are divided into Core, Base, Good, Ugly, Bad, etc., depending on the quality of the plug-in and the absence / presence of licensing problems) determine the list of factories for creating elements. Let's see which factories are available for elements of type Source.

    #include 
    int main (int argc, char * argv[]) {
    	/* Двунаправленный список, в который мы поместим список фабрик */
    	GList *list;
    	/* Инициализация GStreamer */
    	gst_init (NULL, NULL);
    	list = gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_SRC, GST_RANK_NONE);
    	GList *l;
    	for (l = list; l != NULL; l = l->next)
    	g_print ("%s\n", gst_object_get_name (l->data));
    	gst_plugin_feature_list_free (list);
    	gst_plugin_feature_list_free (l);
    	return 0;
    }
    

    Result:

    pulsesrc
    alsasrc
    dataurisrc
    filesrc
    jackaudiosrc
    rtmpsrc
    ...
    

    As you can see, there are quite a few sources (you can view the list of all factories using the macro GST_ELEMENT_FACTORY_TYPE_ANY). Pay attention to filesrc - we use it in the practical part.

    So, to create an element, we are looking for a factory with the desired name (for example, filesrc):

    GstElementFactory *factory;
    factory = gst_element_factory_find ("filesrc");
    

    And then directly create the element, giving it a name. By this name you can then access the element, and it’s also convenient for debugging.

    GstElement *element;
    element = gst_element_factory_create (factory, "elname");
    

    There is a shortcut for these two functions:

    GstElement *gst_element_factory_make (const gchar *factoryname, const gchar *name);
    

    Each item you create has a set of properties that you can control and thus customize the item. Setting and reading properties is performed by set- and get-methods:

    void g_object_set (gpointer object, const gchar *first_property_name, ...);
    void g_object_get (gpointer object, const gchar *first_property_name, ...);
    

    For an example we will set the element "name" property:

    g_object_set (G_OBJECT(element), "name", "another_name", NULL);
    

    Five cents about four states


    All created elements can be in one of four states:

    • GST_STATE_NULL
      An element goes into this state immediately after its creation.
    • GST_STATE_READY
      In this state, the necessary resources are allocated for the element, so it is prepared for transition to the GST_STATE_PAUSED state.
    • GST_STATE_PAUSED
      In this state, the item is fully open to the data stream, but data has not yet been transmitted.
    • GST_STATE_PLAYING
      This state is completely identical to the previous one, but data is transmitted.

    You can switch the states of an element using the function:

    GstStateChangeReturn gst_element_set_state (GstElement *element, GstState state);
    

    It is worth noting that switching can be end-to-end. Those. if an element in the NULL state is switched to PLAYING, it will automatically go through all the intermediate states.

    Special elements - containers and conveyors


    Now imagine that you have a set of ten elements and you want to switch each element, for example, to the PLAYING state. It would be ridiculous if you had to call the gst_element_set_state () function 10 times for this. There is a special element in which you can put other elements - a container (bin). Having placed several elements in the container, you can manage them as a whole, for example, to switch the state.

    No need to think that the container is something isolated. No, this is the same element of the GStreamer ecosystem as any other element. So you can create it using the factory:

    GstElement *bin;
    bin = gst_element_factory_make ("bin", "bin_name");
    

    There is also an auxiliary function for this operation:

    GstElement *gst_bin_new (const gchar *name);
    

    To control the synchronization and processing of messages from buses (we will talk about buses and messages next time), a top-level container is allocated - a pipeline. Any application using containers must have at least one pipeline.

    A pipeline is created either with the help of the factory (factory "pipeline"), or with an auxiliary function:

    GstElement *gst_pipeline_new (const gchar *name);
    

    Adding items to the container and binding


    You can add elements to the container (or conveyor) or remove them from there using the functions:

    gboolean gst_bin_add (GstBin *bin, GstElement *element);	
    void gst_bin_add_many (GstBin *bin, GstElement *element_1, ..., NULL);
    gboolean gst_bin_remove (GstBin *bin, GstElement *element);
    

    Each item created has a so-called. pads - points through which you can associate an element with other elements and, thus, create a working media pipeline. This concept is the core of GStreamer.

    Linking is carried out by functions:

    gboolean gst_element_link (GstElement *src, GstElement *dest);
    gboolean gst_element_link_many (GstElement *element_1, GstElement *element_2, ..., NULL);
    

    Not all pads are compatible with each other. Therefore, the process of checking for compatibility automatically occurs before linking elements.

    We must not forget that before linking the elements they must be added to the pipeline. Also, when adding elements to the pipeline, in which related elements are already located, their connections disappear.

    Practice


    To consolidate the theoretical material, we will write an application that copies from file to file. For this, we will use two elements from the Core set - filesrc and filesink. Our conveyor will schematically look like this:

    image

    So, let's go!

    #include 
    int main (int argc, char * argv[]) {
    	if (argc != 3) {
    		g_print ("Syntax error\n");
    		return -1;
    	}
    	GstElement *pipeline, *src, *dst;
    	/* Сюда будет читаться результат попытки запуска потока. */
    	GstStateChangeReturn ret;
    	/* bus - это шина конвейера. Через нее мы можем получать сообщения о событиях. */
    	GstBus *bus;
    	GstMessage *msg;
    	/* Инициализация GStreamer */
    	gst_init (NULL, NULL);
    	/* Создаем элементы */
    	pipeline = gst_element_factory_make ("pipeline", "pipe");
    	src = gst_element_factory_make ("filesrc", "src");
    	dst = gst_element_factory_make ("filesink", "dst");
    	if ( !pipeline || !src || !dst ) {
    		g_printerr ("Unable to create some elements\n");
    		return -1;
    	}
    	/* Добавляем элементы в конвейер */
    	gst_bin_add_many (GST_BIN(pipeline), src, dst, NULL);
    	/* И связываем их */
    	if ( gst_element_link (src, dst) != TRUE ) 	{
    		g_printerr ("Elements can not be linked\n");
    		gst_object_unref (pipeline);
    		return -1;
    	}
    	/* Задаем элементам свойства */
    	g_object_set (src, "location", argv[1], NULL);
    	g_object_set (dst, "location", argv[2], NULL);
    	/* Запускаем конвейер */
    	ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
    	if ( ret == GST_STATE_CHANGE_FAILURE ) {
    		g_printerr ("Unable to set pipeline to the playing state\n");
    		gst_object_unref (pipeline);
    		return -1;
    	}
    	/* Мало просто установить режим PLAYING. Нужно ждать либо конца потока, либо 
    	 * ошибок. Для начала подключаемся к шине конвейера (эти манипуляции будут 
    	 * описаны в следующей статье) */
    	bus = gst_element_get_bus (pipeline);
    	/* И ожидаем события на шине. Когда событие произойдет, функция вернет 
    	 * сообщение, которое мы будем парсить. */
    	msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
    	/* Парсим сообщение */
    	if (msg != NULL)
    	{
    		GError *err;
    		gchar *debug_info;
    		switch ( GST_MESSAGE_TYPE (msg) )
    		{
    			case GST_MESSAGE_ERROR:
    				gst_message_parse_error (msg, &err, &debug_info);
    				g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message);
    				g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");
    				g_clear_error (&err);
    				g_free (debug_info);
    				break;
    			case GST_MESSAGE_EOS:
    				g_print ("We reach End-Of-Stream\n");
    				break;
    			default:
    				g_printerr ("Unexpected message received\n");
    				break;
    		}
    		gst_message_unref (msg);
    	}
    	/* Освобождаем ресурсы */
    	gst_object_unref (bus);
    	gst_element_set_state (pipeline, GST_STATE_NULL);
    	gst_object_unref (pipeline);
    	return 0;
    }	
    

    Compile and run:

    $ gcc -Wall -o cp cp.c $(pkg-config --cflags --libs gstreamer-1.0)
    $ echo 'hello world' > file.txt
    $ ./cp file.txt another_file.txt
    We reach End-Of-Stream
    $ cat another_file.txt
    hello world
    

    Conclusion


    In the next article, buses and various types of messages that are transmitted through it will be considered. And for fixing we’ll write an application for karaoke lovers!

    Related Materials


    GStreamer Application Development Manual
    GStreamer 1.0 Core Reference Manual
    GStreamer Core Plugins Reference

    Also popular now: