Overview
This guide walks through the complete implementation of a GStreamer-based object detection application using Qualcomm hardware-accelerated plugins. The application:- Reads an H.264-encoded MP4 file from disk
- Performs YOLOv8 object detection using a TFLite inference pipeline
- Overlays detection bounding boxes and labels onto video frames
- Renders the annotated output to a Wayland display
The complete application source code is available in the repository. For foundational concepts — including pipeline construction, state management, caps negotiation, bus handling, and dynamic pads — refer to Building a Native GStreamer Application.
Pipeline Diagram

Implementation Steps
Step 1 — Define the Application Context
The application consolidates all runtime state into a single context structure:
| Field | Description |
|---|---|
pipeline | Top-level GStreamer pipeline object for all pipeline operations |
mloop | GLib main loop — keeps the application alive and dispatches events |
file | Path to the input media file |
model | Path to the TFLite inference model |
labels | Path to the class label file |
parse | Reference to h264parse, stored explicitly for deferred dynamic pad linking |
This pattern avoids global variables, keeps application state self-contained, and simplifies callback implementations that require access to pipeline objects.
Step 2 — Create the Pipeline Object
GstPipeline object is created using gst_pipeline_new(), establishing the top-level container that owns and manages all elements added to it.As a GstBin subclass, it is responsible for:- Element lifecycle management
- State propagation across all contained elements
- Synchronization of clocks and timing
Step 3 — Instantiate Pipeline Elements
gst_element_factory_make(). The first argument identifies the element by its factory name; the second assigns a unique instance name (retrievable later via gst_bin_get_by_name()).| Element | Role |
|---|---|
filesrc | Reads the input MP4 file from disk |
qtdemux | Demuxes the MP4 container into elementary streams |
h264parse | Parses the H.264 bitstream for downstream decoding |
v4l2h264dec | Hardware-accelerated H.264 video decoder |
qtimlvideotflitebin | Runs TFLite-based YOLOv8 object detection inference |
qtivoverlay | Renders detection bounding boxes and labels onto video frames |
waylandsink | Displays annotated video output on a Wayland surface |
Step 4 — Configure Elements
Before the pipeline starts, element properties are set to define runtime behavior.Input source — configure AI inference — configure
Display sink — configure
filesrc with the path to the input file:qtimlvideotflitebin with delegate, post-processing module, model, and labels:| Property | Description |
|---|---|
inference-delegate | Selects the hardware backend for model execution |
postprocess-module | Specifies the post-processing algorithm (yolov8 decoding) |
inference-model | Path to the compiled TFLite model |
postprocess-labels | Path to the class label file |
waylandsink for synchronized, full-screen rendering:| Property | Description |
|---|---|
sync | Enables clock-synchronized rendering for smooth playback |
fullscreen | Instructs the sink to occupy the full display surface |
Step 5 — Add Elements to the Pipeline
gst_bin_add_many() transfers ownership of all elements to the pipeline, making it responsible for their lifecycle — including state transitions and resource cleanup. At this point, elements share the same execution context but are not yet connected.Step 6 — Link Static Pipeline Elements
The link between
qtdemux and h264parse is intentionally omitted here. qtdemux exposes its output pads dynamically at runtime — this connection is established in Step 7 via dynamic pad handling.Step 7 — Handle Dynamic Pads (Demuxer)
qtdemux determines the number and type of elementary streams only after parsing input data, dynamically creating output pads for each discovered stream. Since these pads do not exist at construction time, they cannot be linked statically.A callback is registered for the pad-added signal on the demuxer:h264parse if it carries an H.264 video stream — all other stream types (e.g., audio) are ignored:Step 8 — Set Up Bus Message Handling
The application retrieves the pipeline bus and registers callbacks for error, EOS, and warning messages:
| Signal | Trigger | Action |
|---|---|---|
message::error | Fatal error in a pipeline element | Log error and initiate shutdown |
message::eos | All data processed; stream end reached | Exit main loop and proceed to teardown |
message::warning | Non-fatal condition | Log warning; pipeline continues |
Step 9 — Handle Ctrl+C (Interrupt Signal)
A The signal handler checks the current pipeline state and responds accordingly:
SIGINT handler is registered using g_unix_signal_add() to route the OS signal safely into the GLib main loop:| Pipeline State | Action |
|---|---|
GST_STATE_PLAYING | Send EOS event — allows in-flight buffers to drain before stopping |
| Any other state | Quit main loop immediately |
Sending EOS before stopping is particularly important for file-based outputs where finalization steps (e.g., writing file headers/footers) must complete.
Step 10 — Start the Pipeline
READY and PAUSED states before reaching PLAYING, at which point data begins to flow from filesrc through to waylandsink.Step 11 — Run the Main Loop
- Blocks the main thread for the duration of pipeline execution
- Dispatches bus messages and invokes registered callbacks as events occur
- Exits only when explicitly quit by the EOS callback, error handler, or interrupt signal handler
Step 12 — Handle End-of-Stream
When the pipeline processes the last buffer and EOS propagates to the sink, an EOS bus message is delivered to the registered callback:The callback quits the main loop, allowing the main thread to resume and proceed with pipeline teardown and resource cleanup.
Step 13 — Handle Errors
If a fatal error occurs at any stage of pipeline execution, the error callback is invoked:Errors are treated as fatal. The callback:
- Logs the error message and debug information for diagnostics
- Quits the main loop to initiate controlled shutdown
- The application subsequently transitions the pipeline to
NULLand releases all resources
Step 14 — Teardown and Resource Cleanup
Once the main loop exits — whether due to EOS, error, or interrupt — the application performs an orderly teardown:
| Call | Effect |
|---|---|
gst_element_set_state(pipeline, GST_STATE_NULL) | Transitions pipeline and all elements to NULL — stops processing, releases device handles, closes files, frees internal memory |
gst_object_unref(pipeline) | Releases the application’s reference to the pipeline; destroys it and all owned elements if no other references are held |
g_main_loop_unref(mloop) | Releases the main loop object and frees associated resources |
This sequence ensures all resources are released in the correct order and that the application exits in a well-defined, clean state.
Summary
This guide covered the full implementation of a GStreamer YOLOv8 object detection application across 14 steps:| Phase | Steps | Key Operations |
|---|---|---|
| Initialization | 1—2 | Define app context, create pipeline |
| Element Setup | 3—5 | Instantiate, configure, and add elements |
| Linking | 6—7 | Static links + dynamic pad handling for qtdemux |
| Event Handling | 8—9 | Bus message callbacks, SIGINT handler |
| Execution | 10—11 | Set PLAYING state, run main loop |
| Shutdown | 12—14 | EOS/error handling, teardown and resource cleanup |
