Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / multimedia

Processing Multimedia Content with GStreamer

5.00/5 (2 votes)
1 Sep 2024CPOL20 min read 2.9K   27  
This article introduces the GStreamer toolset and explains how to code applications that read and process multimedia content.
This article shows how to use GStreamer to read and process multimedia content. It starts by explaining how to perform GStreamer operations from the command line, and then presents the central data structures of the GStreamer API. The article ends by presenting two example applications that demonstrate how GStreamer can be used.

As multimedia devices become more popular, it becomes more necessary to build applications that can read and process their content. There are many programming libraries available, but GStreamer is one of the few open-source toolsets that can process all kinds of audio and video data. It's freely available, runs on multiple operating systems, and provides impressive performance and extensive documentation.

As I see it, there are two only drawbacks of using GStreamer. First, its data structures (pads, caps, buses, elements) are unlike anything I've encountered before. Second, it's written in C, so you have to deal with lengthy function names like gst_bus_timed_pop_filtered.

The goal of this article is to present GStreamer's usage and capabilities. It starts by explaining how to install the package and then shows how to perform operations from the command line. The last part presents the fundamental data structures of GStreamer and demonstrates how they can be used.

1. Installation

The main site for GStreamer is gstreamer.freedesktop.org. The installation process depends on your operating system, and you can find instructions by by visiting the GStreamer site and performing four steps:

  1. Click the Documentation button on the left.
  2. Click the Get Started button in the center.
  3. Open the Installing GStreamer option in the upper left.
  4. Click the link corresponding to your operating system.

Once you've installed GStreamer, you'll have several new libraries, header files, and executables. A large part of this article is concerned with the libraries and header files, but for newcomers, I recommend starting with the executables.

2. GStreamer from the Command Line

If you've never worked with GStreamer, I recommend using its command-line tools before writing code. They're easy to use, and once you're familiar with them, you'll have a much better idea of what GStreamer is all about.

On my Linux system, GStreamer installs its executables in /usr/bin. On my Windows system, they're in C:\gstreamer\1.0\msvc_x86_64\bin. Before proceeding further, it's a good idea to make sure you can access these utilities (particularly gst-launch-1.0) from a command prompt, which may require updating your PATH environment variable.

To get started, open a command prompt and execute the following command:

C++
gst-launch-1.0 audiotestsrc freq=200 ! audioconvert ! lamemp3enc ! filesink location=audio.mp3

This can take a long time, so press Ctrl-c after a few seconds. When the command is finished, you'll find a file named audio.mp3 in the current directory. If you play it, you'll hear a low-pitched tone corresponding to a 200 Hz sine wave.

This isn't exciting, but once you see how the command is structured, you'll be well on your way to understanding GStreamer. The text following gst-launch-1.0 is called a pipeline description because it describes how media (audio data in this case) should be processed. When you execute gst-launch-1.0 with a pipeline description, it creates a pipeline and executes it.

In essence, the goal of a GStreamer application is to create and execute pipelines. When it comes to pipeline descriptions, there are four points to be aware of:

  1. A pipeline description consists of elements that identify how media should be generated, processed, and consumed.
  2. Elements are separated by exclamation points (!), which represent links between elements.
  3. An element's properties can be set by following its name with name=value pairs, where name is a property's name and value is the desired value.
  4. In nearly all cases, elements are identified with names of GStreamer plugins.

With this in mind, let's look at the preceding command. The pipeline description contains four elements named audiotestsrc, audioconvert, lamemp3enc, and filesink. The audiotestsrc element has a property named freq and the filesink element has a property named location.

A GStreamer plugin is a library that performs an operation in a pipeline. Each of the four elements identifies a plugin, and when gst-launch-1.0 executes, it calls on each plugin to perform its operation.

Most plugins process streaming data in some way, but some read data from a source or generate new data. These are source plugins, and in the example command, audiotestsrc is a source plugin that generates audio data. Similarly, a sink plugin consumes or stores streaming data. In this preceding example, the filesink plugin stores the audio data to a file named audio.mp3.

The filesink plugin is one of the many plugins that come packaged with GStreamer. These are the core plugins, and Table 1 lists filesink and several other core plugins.

Table 1: Core Plugins (Abridged)
Plugin Name Classification Description
filesrc Source/File Read data from a file
filesink Sink/File Write data to a file
dataurisrc Source Provides data from a URI
fdsrc Source/File Read data from a file descriptor
fdsink Sink/File Write data to a file descriptor
fakesrc Source False source for data
fakesink Sink False sink for data
funnel Generic N-to-1 stream connection
identity Generic Pass data without modification
input-selector Generic N-to-1 input stream selector
output-selector Generic N-to-1 output stream selector
queue Generic Simple data queue
queue2 Generic Simple data queue
tee Generic 1-to-N stream tee
typefind Generic Prints the stream's media type

In addition to the core plugins, you can install four open-source collections of GStreamer plugins:

  • gst-plugins-base - reliable, high-quality, well-documented and maintained
  • gst-plugins-good - good quality and well-maintained
  • gst-plugins-bad - deficiencies exist with quality and/or maintenance
  • gst-plugins-ugly - may have concerns with distribution due to licensing

The gst-plugins-base package provides a vast number of plugins in several categories, including audioconvert, encoding, and playback. The plugins in the playback category are particularly helpful, and Table 2 lists many of them.

Table 2: Playback Plugins (Abridged)
Plugin Name Classification Description
decodebin Generic/Bin/Decoder Decode data to raw media
decodebin3 Generic/Bin/Decoder Decode data to raw media
playbin Generic/Bin/Player Play media from a URI
playbin3 Generic/Bin/Player Play media from a URI
playsink Generic/Bin/Sink Convenience sink for multiple streams
subtitleoverlay Video/Overlay Overlays subtitles on video
uridecodebin Generic/Bin/Decoder Decode URI to raw media
uridecodebin3 Generic/Bin/Decoder Decode URI to raw media
urisourcebin Generic/Bin/Source Download and buffer data from a URI

Of these, playbin is popular because of its ability to play streaming media from a URI. To demonstrate, the following command plays a WebM video file from a free GStreamer site:

C++
gst-launch-1.0 playbin uri=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm

When you run this command, keep in mind that it may take time for the pipeline to load, buffer, and play the video. Also, pay attention to the different states that the pipeline goes through, such as PAUSED and PLAYING.

At this point, you should understand that GStreamer's purpose is to execute pipelines composed of elements. The gst-launch-1.0 utility is great for simple operations, but it's not suitable for sophisticated media processing. To create pipelines programmatically, you'll need to grapple with the GStreamer API and its data structures.

3. The GStreamer API

GStreamer is written in C, which is not an object-oriented language. Despite this, the official documentation refers to GStreamer's data structures as classes. The documentation also describes an inheritance hierarchy beginning with GObject. For example, the documentation states that GstElement is a subclass of GstObject, which is a subclass of GInitiallyOwned, which is a subclass of GObject.

In GStreamer, inheritance is based on aggregation. If Structure X is a subclass of Structure Y, then the first field of Structure X is an instance of Structure Y. For example, the first field of GstElement is an instance of GstObject and the first field of GstObject is an instance of GInitiallyOwned.

If you're familiar with traditional object-oriented programming, this will seem bizarre and unnatural. But if you want to understand GStreamer's API, it's crucial to accept this. Figure 1 presents the inheritance hierarchy of GStreamer's most important classes.

Image 1

Figure 1: Important Classes in the Inheritance Hierarchy

In this illustration, the two structures on top start with G and the structures below it start with Gst. This is because GObject and GInitiallyUnowned are provided by GLib and the other structures are provided by GStreamer. Similarly, functions that start with g_ perform GLib operations and functions that start with gst_ perform GStreamer operations.

This section looks at the shaded structures in this diagram: GObject, GstObject, GstElement, GstBin, GstPipeline, GstBus, and GstPad. Once you understand what these structures accomplish, you'll have no trouble using them to create and execute pipelines.

3.1  GObject

The GObject structure is the base structure in the GLib/GStreamer hierarchy. For this article, you only need to know two things about them:

  1. A GObject stores name-value pairs called properties.
  2. A GObject can emit signals that can be received by handlers.

Regarding properties, GLib provides two functions that read and modify the properties of a GObject:

  • g_object_get(GObject* obj, const gchar* name, void* val, NULL) - Copies the property of obj named name into the memory referenced by val
  • g_object_set(GObject* obj, const gchar* name, any val, NULL) - sets one or more properties of the object

In these signatures, each function only accepts one name/value pair. Additional pairs can be appended, but the last argument must be set to the NULL terminator. To demonstrate, the following code sets the name and color properties of the GObject* named gobj:

C++
g_object_set(gobj, "name", "RedObject", "color", "red", NULL);

GStreamer applications generally access information about a structure by calling the following function:

C++
g_signal_emit_by_name(GObject* obj, const gchar* signal, gint index, void* data)

In addition to storing regular properties, many GStreamer structures store metadata called tags in structures called GstTagLists. When an application calls g_signal_emit_by_name with the name of a structure's tag list, the last argument will point to the returned GstTagList.

3.2  GstObject

The GstObject structure is the base structure of the GStreamer hierarchy. Applications usually don't access GstObjects directly, but there are four functions worth knowing:

  1. gst_init(int argc, char** argv) - initializes the GStreamer environment
  2. gst_object_unref(GstObject* obj) - deallocates a GstObject
  3. gst_object_set_name(GstObject* obj, const gchar* name) - assigns a name to the object
  4. gst_object_get_name(GstObject* obj) - returns the object's name

Applications need to call gst_init before any other GStreamer function. This can accept the command-line arguments (argc and argv) passed into the application.

Many applications will declare a GstObject pointer variable and initialize the variable by calling a function. To free memory, the application should call gst_object_unref for each initialized variable.

3.3  GstElement

Each stage in a media-processing pipeline is represented by a GstElement. This section discusses three aspects of this important data structure:

  1. Fields of the GstElement
  2. Functions that create GstElements
  3. Functions that access GstElement properties

As you read this section, you don't need to remember every field and function. But you should learn enough to be comfortable with how GstElements are used.

3.3.1  GstElement Fields

To see what GstElement accomplishes, it's a good idea to become familiar with its fields. Table 3 lists many of these fields along with their data types.

Table 3: GstElement Fields
Field Data Type Description
object GstObject The element's "superclass"
contexts GList* The element's processing task
current_state GstState The element's current state
next_state GstState The element's following state
target_state GstState The element's goal state
srcpads GList* The element's source pads
numsrcpads guint16 Number of source pads
sinkpads GList* The element's sink pads
numsinkpads guint16 Number of sink pads
bus GstBus* The connected bus
clock GstClock* The element's clock
start_time GstClockTime Time since the last PAUSE state
base_time GstClockTimeDiff Time just before the PLAY state

The first field is a GstObject, which is the superclass of GstElement. The next field, contexts, stores the operation(s) to be performed by the element. The third field, current_state, identifies the element's processing state, which can take one of five values:

  • GST_STATE_NULL - the element's initial state
  • GST_STATE_READY - the element is ready to enter the paused state
  • GST_STATE_PAUSE - the element is ready to accept and process data
  • GST_STATE_PLAYING - the element is playing data
  • GST_STATE_VOID_PENDING - the element has no pending state

Elements always enter the PAUSE state before the PLAYING state. This makes it straightforward to measure the playing time.

An application can access information about an element through its GstBus. This is particularly useful for responding to errors, and I'll discuss GstBus structures shortly. 

3.3.2  Creating Elements

GStreamer's element factories make it easy to create different types of elements. The gst_element_factory_find function accepts a name and returns the corresponding element factory. For example, the following code accesses the GstElementFactory named videotestsrc:

C++
factory = gst_element_factory_find("videotestsrc");

An application can create a new GstElement from an element factory by calling gst_element_factory_make, which accepts the factory's ID and a name to be assigned to the GstElement. To demonstrate, the following code creates an element named elem from the factory named videotestsrc. Because its name is set to NULL, GStreamer will assign a unique name to it.

C++
elem = gst_element_factory_make("videotestsrc", NULL);

In these functions, the argument is commonly the name of a plugin, which means the new element is an instance of a GStreamer plugin. This will be made clear in the example code toward the end of the article.

3.3.3  Accessing Fields

GStreamer applications usually don't access the fields of a GstElement directly. Instead, they call functions that read and modify these fields. Table 4 lists many of the available functions and provides a description of each.

Table 4: GstElement Field Functions
Function Description
gst_element_get_state(GstElement*,
   GstState*, GstState*, GstClockTime)
Reads the element's current/pending states
gst_element_set_state(GstElement*,
   GstState)
Sets the element's current state
gst_element_get_static_pad(GstElement*,
   const gchar*)
Returns the pad with the given name
gst_element_iterate_pads(GstElement*) Returns an iterator for the element's pads
gst_element_add_pad(GstElement*,
   GstPad*)
Adds the given pad to the element
gst_element_remove_pad(GstElement*,
   GstPad*)
Removes the given pad from the element
gst_element_get_bus(GstElement*) Returns the element's bus
gst_element_get_clock(GstElement*) Returns the element's clock
gst_element_set_clock(GstElement*,
   GstClock*)
Sets the element's clock
gst_element_get_start_time(GstElement*) Returns the time since the last pause state
gst_element_get_current_clock_time(
   GstElement*, GstClockTime)
Returns the current clock time

Most of these functions are easy to understand. Keep in mind that an element can have any number of pads, but always starts with none. It always has one bus for transferring data, but the bus may not be used.

3.4  GstPad and Capabilities

In a pipeline description, an exclamation point is used to connect one element to another. This is fine for simple topologies, but GStreamer makes it possible to create multiple connections between elements, and at a low level, these connections are established using pads. An element can have zero or more pads that provide data (source pads) and zero or more pads that receive data (sink pads).

Each pad has a set of capabilities (caps) that identify the nature of the data that it can transfer. One pad might have the capability to transfer video streams of a specific format while another has the capability to transfer a specific format of audio data.

In code, pads are represented by GstPad instances and a pad's set of capabilities is contained in a GstCaps instance. Table 5 lists many functions related to pads and their properties.

Table 5: GstPad Functions
Function Description
gst_pad_new(const gchar*, GstPadDirection) Creates a new pad
gst_pad_new_from_template(GstPadTemplate*,
   const gchar*)
Creates a new pad from a template
gst_pad_get_name(GstPad*) Returns the pad's name
gst_pad_get_direction(GstPad*) Returns the pad's direction
gst_pad_get_parent(GstPad*) Returns the element containing the pad
gst_pad_set_active(GstPad*, gboolean) Sets the pad as active or inactive
gst_pad_get_current_caps(GstPad*) Returns the pad's capabilities
gst_pad_link(GstPad*, GstPad*) Creates a link between two pads
gst_pad_unlink(GstPad*, GstPad*) Removes the link between two pads
gst_pad_is_linked(GstPad*) Identifies if the pad is linked or not

The first function, gst_pad_new, creates a new GstPad with a given name and direction. The second argument is a GstPadDirection, which can take one of three values:

  • GST_PAD_UNKNOWN - the pad's direction is unknown
  • GST_PAD_SRC - the pad sends data
  • GST_PAD_SINK - the pad receives data

Before a pad can transfer data, it needs to be made active. This is accomplished by calling the gst_pad_set_active function. Most applications activate an element's pads by setting the element's state to RUNNING.

The gst_pad_get_current_caps function returns a GstCaps instance containing the pad's capabilities. If pads in different elements have one or more capabilities in common, an application can create a link between them by calling gst_pad_link.

Another way to create a link between two elements is to call gst_element_link with pointers to the elements to be linked. When this is called, GStreamer will look for unlinked pads with similar capabilities and create a link between them. Another function for linking elements is given as follows:

C++
gst_element_link_filtered(GstElement* src, GstElement* dest, GstCaps* filter)

This creates a link between two elements if they have unlinked pads with similar capabilities given in the filter argument. This returns true if the link was established and false otherwise.

An application can create links between multiple elements by calling gst_element_link_many. This accepts a series of elements followed by the NULL terminator. As an example, the following code establishes a connection from elemA to elemB and from elemB to elemC.

gst_element_link_many(elemA, elemB, elemC, NULL);

In addition to pad functions, GStreamer also provides functions related to pad capabilities. Their names start with gst_caps_ and Table 6 lists eight of them.

Table 6: GstCaps Functions
Function Description
gst_caps_new_empty() Create a new empty GstCaps
gst_caps_new_empty_simple(const char*) Create a new GstCaps with the given media type
gst_caps_new_simple(const char*, 
   const char*, ...)
Create a new GstCaps with a given GstStructure
gst_caps_get_size(const GstCaps*) Returns the number of structures in the GstCaps
gst_caps_get_structure(const GstCaps*,
   guint index)
Returns the GstStructure at the given index
gst_caps_remove_structure(GstCaps*,
   guint index)
Removes the GstStructure at the given index
gst_caps_append(GstCaps*, GstCaps*) Appends one GstCaps to another
gst_caps_unref(GstCaps*) Frees the GstCaps memory

Each element of a GstCaps is a GstStructure, which is a general container of name/value pairs. When an application creates a new GstCaps with a function like gst_caps_new_simple, it needs to provide the name/value pairs in the GstStructure. As an example, the following code creates a GstCaps with a capability for processing video:

C++
GstCaps *caps = gst_caps_new_simple("video/x-raw",
    "format", G_TYPE_STRING, "I420",
    "framerate", GST_TYPE_FRACTION, 25, 1,
    "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1,
    "width", G_TYPE_INT, 320,
    "height", G_TYPE_INT, 240,
     NULL);

As a result of this code, the GstCaps contains a GStructure representing the capability of displaying raw video with a pixel aspect ratio of 1:1, a width of 320, and a height of 240.

In addition to defining structures, applications can also access structures with gst_caps_get_structure and remove structures with gst_caps_remove_structure. When an application is done processing a GstCaps, it should call gst_caps_unref to free its memory.

3.5  GstBus and Messages

Every GstElement has a GstBus that enables the application to extract information from the element. It's important to understand the difference between pads and buses—a pad transfers multimedia data between elements and a bus transfers status messages from an element to the application.

A GstBus stores its messages in a queue and each is message is an instance of the GstMessage structure. Table 7 lists functions related to the GstBus structure and its message queue.

Table 7: GstBus Functions
Function Description
gst_bus_post(GstBus*, GstMessage*) Places a message on the bus's queue
gst_bus_peek(GstBus*) Retrieve the first message without changing the queue
gst_bus_pop(GstBus*) Retrieve the first message, remove from queue
gst_bus_timed_pop(GstBus*, 
   GstClockTime)
Retrieve/remove the first message with timeout 
gst_bus_pop_filtered(GstBus*,
   GstMessageType)
Retrieve and remove the first message based on type
gst_bus_timed_pop_filtered(GstBus*, 
   GstClockTime, GstMessageType)
Retrieve/remove the first message based on type with timeout

gst_bus_post places a message on the queue, but this is rarely used. Instead, applications wait for error messages by calling one of the pop functions. gst_bus_pop returns immediately, returning the first message if the queue isn't empty and NULL if the queue is empty. In contrast, gst_bus_timed_pop returns after the amount of time given by the second argument.

gst_bus_pop_filtered will only return a message if it belongs to the type or types identified by the second argument. This can be set to one of the values of the GstMessageType enumerated type or an OR'ed combination thereof. Table 8 lists the first ten values of this type.

Table 8: Message Types (Abridged)
Message Type Value Description
GST_MESSAGE_UNKNOWN 0 Undefined message
GST_MESSAGE_EOS 1 The pipeline has reached the end of the stream
GST_MESSAGE_ERROR 2 An error message
GST_MESSAGE_WARNING 4 A warning message
GST_MESSAGE_INFO 8 An information message
GST_MESSAGE_TAG 16 A tag message
GST_MESSAGE_BUFFERING 32 The pipeline is buffering
GST_MESSAGE_STATE_CHANGED 64 The state has changed
GST_MESSAGE_STATE_DIRTY 128 An element changed state
GST_MESSAGE_STEP_DONE 256 A stepping operation finished

It's common for applications to wait for an error condition (GST_MESSAGE_ERROR) or the end of the stream (GST_MESSAGE_EOS). This can be accomplished by calling the gst_bus_timed_pop_filtered function in the following way:

C++
GstMessage* msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE,
    GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

The second argument, GST_CLOCK_TIME_NONE, represents an indefinite amount of time. Because of this argument, the function waits indefinitely for an error message or an end-of-stream message. This combination of the two types is given as GST_MESSAGE_ERROR | GST_MESSAGE_EOS.

3.6  GstBin and GstPipeline

As illustrated in Figure 1, GstBin is a sub-structure of GstElement and GstPipeline is a sub-structure of GstBin. A GstBin is an element that can contain other elements and a GstPipeline represents an entire pipeline. The functions for both structures are fairly simple.

An application can create an empty bin by calling gst_bin_new with a name for the new structure. Then it can add an element by calling gst_bin_add or add multiple elements by calling gst_bin_add_many. An application can remove an element from a bin by calling gst_bin_remove. An element in a bin can be retrieved by name by calling gst_bin_get_by_name.

An application can create an empty pipeline structure by calling gst_pipeline_new with a name for the pipeline. But it's more common to call gst_parse_launch, which accepts a pipeline description and a pointer to memory to store errors (GError**). It returns a GstPipeline with the characteristics given in the description.

For example, the following code creates a pipeline by using the filesrc plugin to access a file named image.svg:

C++
pipeline = gst_parse_launch("filesrc location=image.svg", NULL);

Once an application creates a GstPipeline, it can access the pipeline's fields by calling one of the functions in Table 9.

Table 9: GstPipeline Functions
Function Description
gst_pipeline_get_bus(GstPipeline*) Returns the bus of the pipeline
gst_pipeline_get_clock(GstPipeline*) Returns the pipeline's clock
gst_pipeline_set_clock(GstPipeline*, GstClock*) Set the clock associated with the pipeline
gst_pipeline_get_delay(GstPipeline*) Retrieve the pipeline's delay
gst_pipeline_set_delay(GstPipeline*, GstClockTime) Set the delay of the pipeline
gst_pipeline_get_latency(GstPipeline*) Retrieve the pipeline's latency
gst_pipeline_set_latency(GstPipeline*,
   GstClockTime)
Set the latency for pipeline processing

To add elements to a GstPipeline, an application can convert it to a GstBin using the GST_BIN macro. Then it call gst_bin_add or gst_bin_add_many. To demonstrate, the following code creates a GstBin from pipeline and adds three elements: elemA, elemB, and elemC:

C++
gst_bin_add_many(GST_BIN(pipeline), elemA, elemB, elemC, NULL);

As with many GStreamer functions, the last parameter of gst_bin_add_many is set to NULL. This terminates the list of elements added to the pipeline.

4.  Writing Applications

Now that you're familiar with GStreamer's main data structures of GStreamer, it's time to look at code. This article provides two example source files:

  1. simple_playback.c - Creates a pipeline that plays video from the GStreamer site
  2. audio_file.c - Creates a new MP3 file by connecting elements

To compile these files, you need to have GStreamer installed. If you have GCC available, you can compile the first source file with the following command:

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

In this command, pkg-config is a helper tool that produces the appropriate flags needed to compile with GStreamer. This type of command can be used with all of three of the example source files.

4.1  The Simple Playback Example

The code in the first source file, simple_playback.c, creates and executes a pipeline that displays a video (sintel_trailer-480p.webm) from the GStreamer site. Its code is given as follows:

C++
int main(int argc, char *argv[]) {
    GstElement *pipeline;
    GstBus *bus;
    GstMessage *msg;

    /* Initialize GStreamer */
    gst_init(&argc, &argv);

    /* Create the pipeline */
    pipeline = 
        gst_parse_launch("playbin uri=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm", NULL);

    /* Start playing */
    gst_element_set_state(pipeline, GST_STATE_PLAYING);

    /* Handle errors through bus */
    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 *dbg;
        switch (GST_MESSAGE_TYPE(msg)) {
            case GST_MESSAGE_ERROR:
                gst_message_parse_error(msg, &err, &dbg);
                g_printerr("Error from element %s: %s\n",
                    GST_OBJECT_NAME(msg->src), err->message);
                g_printerr("Debug: %s\n", dbg ? dbg : "none");
                g_clear_error(&err);
                g_free(dbg);
                break;
            case GST_MESSAGE_EOS:
                g_print("End-Of-Stream\n");
                break;
            default:
                g_printerr("Unexpected message\n");
                break;
        }
        gst_message_unref(msg);
    }

    /* Deallocate structures */
    gst_object_unref(bus);
    gst_element_set_state(pipeline, GST_STATE_NULL);
    gst_object_unref(pipeline);
    return 0;
}

After initializing GStreamer's operation with gst_init, this code creates a pipeline by calling gst_parse_launch with a pipeline description. Then it plays the pipeline's media by calling gst_element_set_state with GST_STATE_PLAYING.

Most of the code configures the pipeline to provide messages if any errors occur. This requires three steps:

  1. Access the pipeline's bus with gst_element_get_bus.
  2. Configure the bus to provide messages if an error condition or the end-of-stream (EOS) conditions occur.
  3. Print appropriate text if either condition occurs.

After the error handling is set, the application deallocates the pipeline's bus. Then it sets the pipeline's state to GST_STATE_NULL and deallocates the pipeline with gst_object_unref.

4.2  The Audio File Example

Toward the start of the article, I presented a basic pipeline description that creates an MP3 file containing a 200 Hz tone. The text for the pipeline was given as follows:

C++
audiotestsrc freq=200 ! audioconvert ! lamemp3enc ! filesink location=audio.mp3

The audio_file.c example accomplishes the same result by creating and connecting elements. The code in this file is given as follows:

C++
int main(int argc, char *argv[]) {
    GstElement *pipeline, *src, *convert, *encode, *sink;
    GstBus *bus;
    GstMessage *msg;

    /* Initialize GStreamer */
    gst_init(&argc, &argv);

    /* Create the elements */
    src = gst_element_factory_make("audiotestsrc", NULL);
    convert = gst_element_factory_make("audioconvert", NULL);
    encode = gst_element_factory_make("lamemp3enc", NULL);
    sink = gst_element_factory_make("filesink", NULL);

    /* Create pipeline */
    pipeline = gst_pipeline_new("pipeline");

    /* Add elements to pipeline */
    gst_bin_add_many(GST_BIN(pipeline), src, convert, encode, sink, NULL);
    gst_element_link_many(src, convert, encode, sink, NULL);

    /* Set element properties */
    g_object_set(G_OBJECT(src), "freq", 200.0f, NULL);
    g_object_set(G_OBJECT(sink), "location", "audio.mp3", NULL); 

    /* Start playing */
    gst_element_set_state(pipeline, GST_STATE_PLAYING);

    /* Handle errors through bus */
    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 *dbg;
        switch (GST_MESSAGE_TYPE(msg)) {
            case GST_MESSAGE_ERROR:
                gst_message_parse_error(msg, &err, &dbg);
                g_printerr("Error from element %s: %s\n",
                    GST_OBJECT_NAME(msg->src), err->message);
                g_printerr("Debug: %s\n", dbg ? dbg : "none");
                g_clear_error(&err);
                g_free(dbg);
                break;
            case GST_MESSAGE_EOS:
                g_print("End-Of-Stream\n");
                break;
            default:
                g_printerr("Unexpected message\n");
                break;
        }
        gst_message_unref(msg);
    }

    /* Deallocate structures */
    gst_object_unref(bus); 
    gst_element_set_state(pipeline, GST_STATE_NULL);
    gst_object_unref(pipeline);
    return 0;
}

After initializing GStreamer, the code calls gst_element_factory_make to create a GstElement for each element in the pipeline. Then it creates an empty pipeline with gst_pipeline_new and adds the elements to the pipeline with gst_bin_add_many. After the elements are added, the code creates connections between them by calling gst_element_link_many.

The code calls g_object_set twice to set element properties. The first call sets the frequency (freq) of the source element (src) to 200 Hz. The second call sets the file name (location) of the sink element (sink) to audio.mp3.

After the pipeline has been populated with connected elements, the source code executes the pipeline by calling gst_element_set_state with GST_STATE_PLAYING. Then it configures error handling using the same code discussed earlier.

When you run the executable, it will create the audio.mp3 file and continue writing data to it. Press Ctrl-c to halt the process.

History

This article was initially submitted on September 1, 2024.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)