GStreamer Pad Probe
A GStreamer pad probe is a callback attached to a pad. It lets your application
observe or control data as it passes through a pipeline.
Pads are the connection points between elements:
| source element src pad -> sink pad next element
|
When you add a probe to a pad, GStreamer calls your function when matching
events, buffers, queries, or blocking conditions pass through that pad.
Common uses
Pad probes are useful when you need to:
- inspect buffers without changing the pipeline structure
- read metadata, timestamps, flags, or encoded bytes
- drop selected buffers
- modify buffers before they reach the next element
- debug caps, events, or pipeline flow
- block a pad briefly while relinking pipeline elements
Simple Python Example
This example prints the PTS and size of every buffer passing through an
identity element:
| #!/usr/bin/env python3
import gi
gi.require_version("Gst", "1.0")
from gi.repository import GLib, Gst
Gst.init(None)
def buffer_probe(pad, info, user_data):
buffer = info.get_buffer()
if buffer is None:
return Gst.PadProbeReturn.OK
print(f"buffer pts={buffer.pts}, size={buffer.get_size()}")
return Gst.PadProbeReturn.OK
pipeline = Gst.parse_launch(
"videotestsrc is-live=true ! "
"identity name=tap ! "
"videoconvert ! "
"autovideosink"
)
tap = pipeline.get_by_name("tap")
pad = tap.get_static_pad("sink")
pad.add_probe(Gst.PadProbeType.BUFFER, buffer_probe, None)
loop = GLib.MainLoop()
bus = pipeline.get_bus()
bus.add_signal_watch()
def on_message(bus, message, loop):
if message.type == Gst.MessageType.ERROR:
err, debug = message.parse_error()
print("ERROR:", err)
print("DEBUG:", debug)
loop.quit()
elif message.type == Gst.MessageType.EOS:
loop.quit()
bus.connect("message", on_message, loop)
pipeline.set_state(Gst.State.PLAYING)
try:
loop.run()
except KeyboardInterrupt:
pass
finally:
pipeline.set_state(Gst.State.NULL)
|
Return Values
The probe callback return value controls what happens next:
Gst.PadProbeReturn.OK: let the buffer/event continue
Gst.PadProbeReturn.DROP: drop this buffer/event
Gst.PadProbeReturn.REMOVE: remove this probe
Gst.PadProbeReturn.HANDLED: mark the item as handled
Most inspection probes should return OK.
A pad probe runs on the streaming thread. Keep the callback short.
Avoid slow work inside the probe, such as disk I/O, network calls, heavy JSON
parsing, or long locks. If the probe takes too long, it can slow the whole
pipeline, increase latency, or cause dropped frames. For expensive processing,
copy the small data you need and send it to another thread or queue.
Probe Types
pad.add_probe() receives a Gst.PadProbeType mask. Combine flags with |
when a callback should handle more than one kind of item.
Gst.PadProbeType.BUFFER: called for each Gst.Buffer. Use this for frame
or packet inspection, timestamps, metadata, encoded payload parsing, or
selectively dropping buffers.
Gst.PadProbeType.BUFFER_LIST: called for Gst.BufferList batches. Use this
when an element pushes grouped buffers and you need to inspect or drop the
whole batch.
Gst.PadProbeType.EVENT_DOWNSTREAM: called for downstream events such as
caps, segment, tag, EOS, or flush-stop. Use this to debug negotiation and
stream-control events moving from source to sink.
Gst.PadProbeType.EVENT_UPSTREAM: called for upstream events such as seek,
latency, navigation, or QoS. Use this to observe control requests moving from
sink to source.
Gst.PadProbeType.EVENT_BOTH: shorthand for upstream and downstream events.
Use this when the same callback should inspect all pad events.
Gst.PadProbeType.QUERY_DOWNSTREAM: called for downstream queries. Use this
to inspect queries sent toward sinks, such as allocation or scheduling.
Gst.PadProbeType.QUERY_UPSTREAM: called for upstream queries. Use this to
inspect queries sent toward sources, such as position, duration, latency, or
caps queries.
Gst.PadProbeType.QUERY_BOTH: shorthand for upstream and downstream queries.
Use this when the same callback should inspect all pad queries.
Gst.PadProbeType.BLOCK: blocks the pad before matching data continues. Use
this for short, controlled pipeline changes such as relinking or replacing an
element.
Gst.PadProbeType.IDLE: called when the pad is idle and keeps it blocked
until the probe is removed. Use this when you need a stable point for dynamic
pipeline edits.
Block and Idle
flowchart LR
A["videotestsrc<br/>is-live=true<br/>pattern=smpte"] --> B["videoconvert"]
B --> C["capsfilter<br/>video/x-raw,format=RGB"]
C --> D["queue<br/>upstream_queue"]
D --> Psrc pad probes<br/>BLOCK | BUFFER once<br/>IDLE every 5s
P --> E["identity<br/>normal_filter"]
P -. switch by IDLE probe .-> F["videobalance<br/>effect_filter<br/>saturation=0.0<br/>brightness=-0.15<br/>contrast=1.8"]
E --> G["queue<br/>downstream_queue"]
F -. active after switch .-> G
G --> H["videoconvert"]
H --> I["autovideosink"]
prob_block_idle_switch.py starts with the normal path linked as:
| upstream_queue -> normal_filter -> downstream_queue
|
The switch point is the src pad of upstream_queue. After two seconds the
example installs a BLOCK | BUFFER probe on that pad. It fires on the next
buffer, prints the buffer PTS, and returns REMOVE, so this first probe only
demonstrates where a buffer-blocking probe pauses data flow.
Every five seconds the example installs an IDLE probe on the same pad. An
idle probe runs when the pad has no data passing through it and keeps the pad
blocked while the callback runs, which gives the code a stable point to change
links. The callback chooses the current filter and the next filter from the
using_effect flag, unlinks the old path, sets the old filter to NULL, links
upstream_queue through the new filter to downstream_queue, syncs the new
filter state with the pipeline, flips using_effect, and returns REMOVE to
unblock the pad.
The result is a live pipeline that alternates between the identity path and the
videobalance grayscale effect without stopping the whole pipeline.
Demo
| #!/usr/bin/env python3
import gi
gi.require_version("Gst", "1.0")
from gi.repository import GLib, Gst
Gst.init(None)
class ProbeSwitchExample:
def __init__(self):
self.pipeline = Gst.Pipeline.new("probe-switch-example")
self.using_effect = False
self.source = Gst.ElementFactory.make("videotestsrc", "source")
self.pre_switch_converter = Gst.ElementFactory.make(
"videoconvert",
"pre_switch_converter",
)
self.pre_switch_caps = Gst.ElementFactory.make("capsfilter", "pre_switch_caps")
self.upstream_queue = Gst.ElementFactory.make("queue", "upstream_queue")
self.normal_filter = Gst.ElementFactory.make("identity", "normal_filter")
self.effect_filter = Gst.ElementFactory.make("videobalance", "effect_filter")
self.downstream_queue = Gst.ElementFactory.make("queue", "downstream_queue")
self.converter = Gst.ElementFactory.make("videoconvert", "converter")
self.sink = Gst.ElementFactory.make("autovideosink", "sink")
self.elements = (
self.source,
self.pre_switch_converter,
self.pre_switch_caps,
self.upstream_queue,
self.normal_filter,
self.effect_filter,
self.downstream_queue,
self.converter,
self.sink,
)
if any(element is None for element in self.elements):
raise RuntimeError("failed to create one or more GStreamer elements")
self.source.set_property("is-live", True)
self.source.set_property("pattern", "smpte")
self.pre_switch_caps.set_property(
"caps",
Gst.Caps.from_string("video/x-raw,format=RGB"),
)
self.effect_filter.set_property("brightness", -0.15)
self.effect_filter.set_property("contrast", 1.8)
self.effect_filter.set_property("saturation", 0.0)
for element in self.elements:
self.pipeline.add(element)
if not self.source.link(self.pre_switch_converter):
raise RuntimeError("failed to link source to pre-switch converter")
if not self.pre_switch_converter.link(self.pre_switch_caps):
raise RuntimeError("failed to link pre-switch converter to capsfilter")
if not self.pre_switch_caps.link(self.upstream_queue):
raise RuntimeError("failed to link capsfilter to upstream queue")
if not self.upstream_queue.link(self.normal_filter):
raise RuntimeError("failed to link upstream queue to normal filter")
if not self.normal_filter.link(self.downstream_queue):
raise RuntimeError("failed to link normal filter to downstream queue")
if not self.downstream_queue.link(self.converter):
raise RuntimeError("failed to link downstream queue to converter")
if not self.converter.link(self.sink):
raise RuntimeError("failed to link converter to sink")
self.switch_pad = self.upstream_queue.get_static_pad("src")
if self.switch_pad is None:
raise RuntimeError("failed to get upstream queue src pad")
def current_filter(self):
return self.effect_filter if self.using_effect else self.normal_filter
def next_filter(self):
return self.normal_filter if self.using_effect else self.effect_filter
def install_block_probe(self):
print("install BLOCK | BUFFER probe")
self.switch_pad.add_probe(
Gst.PadProbeType.BLOCK | Gst.PadProbeType.BUFFER,
self.block_buffer_probe,
None,
)
return GLib.SOURCE_REMOVE
def block_buffer_probe(self, pad, info, user_data):
# BLOCK holds this pad at the next buffer. REMOVE unblocks it immediately.
buffer = info.get_buffer()
pts = buffer.pts if buffer is not None else Gst.CLOCK_TIME_NONE
print(f"BLOCK | BUFFER probe fired, stream paused at buffer pts={pts}")
return Gst.PadProbeReturn.REMOVE
def install_idle_probe(self):
print("install IDLE probe for pipeline switch")
self.switch_pad.add_probe(Gst.PadProbeType.IDLE, self.idle_switch_probe, None)
return GLib.SOURCE_CONTINUE
def idle_switch_probe(self, pad, info, user_data):
# IDLE runs while the pad is blocked, giving a stable point to relink.
old_filter = self.current_filter()
new_filter = self.next_filter()
new_name = "grayscale effect" if not self.using_effect else "normal identity"
print(f"IDLE probe fired, switching to {new_name}")
old_filter.unlink(self.downstream_queue)
self.upstream_queue.unlink(old_filter)
old_filter.set_state(Gst.State.NULL)
if not self.upstream_queue.link(new_filter):
raise RuntimeError(f"failed to link upstream queue to {new_filter.name}")
if not new_filter.link(self.downstream_queue):
raise RuntimeError(f"failed to link {new_filter.name} to downstream queue")
new_filter.sync_state_with_parent()
self.using_effect = not self.using_effect
return Gst.PadProbeReturn.REMOVE
example = ProbeSwitchExample()
loop = GLib.MainLoop()
bus = example.pipeline.get_bus()
bus.add_signal_watch()
def on_message(bus, message, loop):
if message.type == Gst.MessageType.ERROR:
err, debug = message.parse_error()
print("ERROR:", err)
print("DEBUG:", debug)
loop.quit()
elif message.type == Gst.MessageType.EOS:
loop.quit()
bus.connect("message", on_message, loop)
GLib.timeout_add_seconds(2, example.install_block_probe)
GLib.timeout_add_seconds(5, example.install_idle_probe)
example.pipeline.set_state(Gst.State.PLAYING)
try:
loop.run()
except KeyboardInterrupt:
pass
finally:
example.pipeline.set_state(Gst.State.NULL)
|