Skip to main content
This guide walks through the essential steps and concepts required to build a native GStreamer application.

Overview

A GStreamer application is built around a pipeline — a runtime container that organizes elements into a coherent data path and manages their execution as a single unit. Elements are linked in data-flow order to form a complete processing chain, and execution is governed by a state model that propagates lifecycle transitions across all elements simultaneously. Pipeline Diagram A main loop keeps the application alive during execution, dispatching bus messages, signals, and callbacks while keeping control logic cleanly decoupled from data processing. Shutdown is handled gracefully by sending an End-of-Stream (EOS) event, ensuring all in-flight data is fully processed before the pipeline transitions to a stopped state and resources are released.

Application Lifecycle

A minimal GStreamer application follows a well-defined sequence of stages:

Concepts

The following steps and concepts are required to build a complete GStreamer application:
  • GStreamer Initialization — Preparing the library for use before any pipeline operations are performed.
  • Pipeline and Element Instantiation — Creating the pipeline container, instantiating individual elements, adding them to the pipeline, and linking them in the intended data-flow order.
  • Pipeline Configuration — Setting element properties programmatically or through pipeline strings to control runtime behavior.
  • Pipeline Execution States — Understanding the GStreamer state machine (NULL, READY, PAUSED, PLAYING), how state transitions are applied, and how they propagate to all contained elements.
  • Caps Negotiation — How GStreamer automatically negotiates compatible data formats between linked elements, and how caps filters are used to constrain or enforce specific formats.
  • Main Loop — Keeping the application alive during pipeline execution and serving as the event dispatcher for signals, callbacks, and bus messages.
  • Interaction with Pipeline during Runtime via Bus — The asynchronous communication channel through which the pipeline delivers runtime notifications — including errors, state changes, and end-of-stream conditions — to the application.
  • Dynamic Pads — Handling elements such as demuxers that expose output pads only at runtime, requiring deferred linking via the pad-added signal.
  • Tee and Queue — Splitting a stream into multiple parallel branches using tee, and decoupling branch execution using queue elements to prevent blocking and enable independent threading.
  • Error Handling — Responding to pipeline errors delivered through the bus and performing a controlled shutdown.
  • Application Shutdown and Teardown — Handling external interrupt signals cleanly using g_unix_signal_add(), with support for both immediate and graceful EOS-based shutdown strategies, followed by safely transitioning the pipeline to NULL and releasing all allocated resources.
  • Application Interaction via appsink — Accessing pipeline data directly from application code using the appsink element for custom processing, integration with external systems, or frame export.

1. GStreamer Initialization

Simple Initialization

Include gst/gst.h to access GStreamer library functions, and call gst_init(&argc, &argv) at the start of the application to initialize the library and parse GStreamer-specific command-line options.

The GOption Interface

GOption-based initialization can be used if the application needs to parse custom-defined input parameters.
A GOptionTable can be used to define application-specific command-line options and passed to the GLib initialization function along with the option group returned from gst_init_get_option_group(). This ensures that application options are parsed alongside the standard GStreamer options.

2. Pipeline and Element Instantiation

Constructing a pipeline follows three sequential steps:
  1. 2.1 Creating elements as independent GstElement objects
  2. 2.2 Adding them to the pipeline container
  3. 2.3 Linking them in the intended data-flow order
Since a pipeline is itself a GstBin, elements are added using gst_bin_add() or gst_bin_add_many(), which transfers ownership to the pipeline and enables lifecycle and state management across all contained elements.

2.1 Creating Elements

Elements are GstElement objects that form the functional building blocks of a GStreamer pipeline, each responsible for a specific role in the data flow:
  • Source — producing data
  • Transform — processing data
  • Sink — consuming data
An element is created using gst_element_factory_make("factory-name", "instance-name").

2.2 Adding Elements to the Pipeline

  • appctx->pipeline is a GstPipeline (a subclass of GstBin)
  • gst_bin_add_many transfers ownership of the elements to the pipeline
  • After this call, the pipeline manages their lifecycle and propagates state changes

2.3 Linking Elements

The data path for this example is:
  • v4l2src — produces video data (from a camera)
  • qtivtransform — processes or converts the data
  • filesink — consumes and writes data to a file

Constructing the Pipeline from a String

The entire process of creating, adding, and linking elements can also be done implicitly using a pipeline string:

3. Pipeline and Element Configuration

Once elements are created and placed in the pipeline, they must be configured to define their runtime behavior.

Programmatic Configuration

Configuring the Source

Configuring the Sink

Configuration via Pipeline String

When using gst_parse_launch(), element configuration is embedded directly in the pipeline string using key=value syntax:

Comparison: Programmatic vs. String Configuration

In practice, many applications combine both approaches — using a pipeline string to define the overall structure while retrieving specific elements by name for further programmatic configuration.

4. Pipeline Execution States

Once the pipeline is constructed and configured, its execution is governed by a well-defined state model.

4.1 The State Model

GStreamer defines a small set of states that represent different stages in the lifecycle of a pipeline: Pipeline Diagram

4.2 State Transitions

Pipeline execution is controlled through state transitions:
Transitioning from NULL to PLAYING implicitly passes through READY and PAUSED. The pipeline manages state propagation to all child elements automatically.

5. Caps Negotiation

Beyond configuring element properties, building a functional pipeline requires that linked elements agree on the format of the data they exchange. GStreamer handles this automatically through caps negotiation.

Role of Caps

Caps (short for capabilities) describe the structure of the data flowing between elements. They typically include:
  • Media type (e.g., video, audio)
  • Encoding format
  • Resolution or sample rate
  • Pixel format or layout

Negotiation Process

When the pipeline transitions to PAUSED or PLAYING, GStreamer initiates the negotiation process:
  1. Upstream elements propose their supported output formats.
  2. Downstream elements select formats they can accept.
  3. A final format agreement is established for each pad link.

Influencing Negotiation with Caps Filters

Caps filters are used in the following scenarios:

Setting Caps Programmatically

Setting Caps via Pipeline String


6. Main Loop

Once the pipeline is transitioned to PLAYING, the application must remain active for the duration of execution using g_main_loop_run():
The main loop serves two purposes:
  • Thread synchronization — blocks the main thread, keeping the application alive.
  • Event dispatching — processes and routes events including signals, callbacks, and pipeline notifications.

Exiting the Main Loop

The loop is typically stopped under one of the following conditions:

7. Interaction During Runtime

While the pipeline is running, the application interacts through:
  1. State change requests
  2. Bus messages
  3. Callbacks

Bus

The GStreamer bus provides an asynchronous communication channel from the pipeline to the application.

Accessing the Bus

Types of Messages

Integration with the Main Loop

When a signal watch is registered, incoming bus messages are dispatched as callbacks through the main loop, allowing the application to react to pipeline events as part of the same event-driven model.

8. Dynamic Pads

Some elements do not expose all their pads at creation time — their pads appear dynamically during runtime. The most common example is a demuxer (e.g., qtdemux).

Static vs. Dynamic Pads

Linking Strategy

  1. Create and add all relevant elements to the pipeline.
  2. Link all statically available elements.
  3. Register a callback for the pad-added signal on the dynamic element.
  4. Perform the remaining links inside the callback when the pad becomes available.

Behavior in Pipeline Strings

Higher-level elements like decodebin handle dynamic pads internally:
When working with lower-level or specialized elements such as explicit demuxers, manual pad handling is required.
Care must be taken to avoid linking the same pad more than once, particularly in callbacks that may be invoked multiple times.

9. Tee and Queue — Branching the Pipeline

Many real-world applications require the same data to be processed in multiple ways simultaneously. GStreamer supports this through tee and queue.

Tee — Splitting the Stream

The tee element duplicates an incoming stream and forwards it to multiple output branches:

Queue — Decoupling Branch Execution

A queue element must be inserted at the start of each branch:
A queue element introduces two key behaviors:
  • Buffering — holds incoming buffers in internal storage, decoupling production and consumption rates.
  • Thread boundary — data is pushed and consumed on separate threads, allowing each branch to execute independently.

Queue Usage Beyond Branching

The queue element also benefits linear pipelines:
This pattern is particularly beneficial for:

10. Error Handling

Errors are delivered via the GStreamer bus as GST_MESSAGE_ERROR messages. Typical causes include:

Handling Error Messages

1

Log the Error

Record both the error message and debug information for troubleshooting.
2

Stop the Main Loop

Terminate the event loop to halt further message processing.
3

Set Pipeline to GST_STATE_NULL

Stops all elements, terminates internal threads, and releases all allocated resources.
4

Perform Application Cleanup

Free application-level resources, destroy pipeline objects, and exit gracefully.
Errors are asynchronous and can arrive at any time. They are typically fatal — in most cases, a current pipeline instance cannot continue execution. Recovery requires explicit design at the application level.

11. Application Shutdown and Teardown

Properly shutting down a GStreamer application involves two coordinated steps: handling the signal that initiates shutdown and stopping the pipeline in a controlled manner.

Shutdown Strategies

Quit the main loop directly — fast but does not guarantee all in-flight buffers are processed.
Use case: Live previews or non-persistent data streams.

Comparison

Typical Signal Handler

The main loop is then exited from the EOS bus message callback:

Teardown

Once the main loop exits, transition the pipeline to NULL and release all resources:

Handling Ctrl+C

Complete Graceful Shutdown Sequence

1

User presses Ctrl+C

SIGINT is triggered.
2

Signal routed to handler

g_unix_signal_add() routes SIGINT to the signal handler via the main loop.
3

Application sends EOS

gst_event_new_eos() is sent to the pipeline.
4

Pipeline finishes processing

All remaining in-flight data is processed.
5

Pipeline posts EOS message on bus

The pipeline notifies the application via the bus.
6

EOS callback quits the main loop

g_main_loop_quit() is called.
7

Application transitions pipeline to NULL

All resources are released and the application exits gracefully.

12. Application Interaction via appsink

The appsink element acts as a bridge between the pipeline’s internal data flow and application-level code, enabling direct access to individual buffers as they are produced.

How appsink Works

Register a callback for incoming samples:
Inside the callback, retrieve and process the sample:

Use Cases

Resource management: Always unmap buffers with gst_buffer_unmap() and unreference samples with gst_sample_unref() after processing. Failing to do so causes memory leaks or buffer pool exhaustion.Callback efficiency: The new-sample callback is invoked from within the pipeline’s streaming thread. Avoid heavy or blocking operations inside the callback — offload long-running tasks to a separate thread.Pipeline independence: If the application cannot consume samples fast enough, the appsink queue may fill up. Control this behavior via appsink properties such as max-buffers and drop.