00 GStreamer nedir — mimari ve temel kavramlar
GStreamer, LGPL lisanslı, açık kaynaklı bir multimedya framework'üdür. Video ve audio işleme pipeline'larını modüler plugin'lerle inşa etmeye olanak tanır. Gömülü Linux'tan masaüstüne, kameradan RTSP sunucusuna geniş bir kullanım alanına sahiptir.
Temel fikir: veri akışı, birbirine bağlı element'ler zincirinden (pipeline) geçer. Her element belirli bir işlevi yerine getirir: veri üretmek (source), dönüştürmek (filter) veya tüketmek (sink).
Mimari katmanları
┌──────────────────────────────────────────────────────────┐
│ Uygulama Katmanı │
│ gst-launch-1.0 · Python/C/C++ GStreamer API │
├──────────────────────────────────────────────────────────┤
│ GStreamer Core │
│ GstPipeline · GstBin · GstElement · GstPad · GstBus │
├──────────────────────────────────────────────────────────┤
│ Plugin Sistemi │
│ gstreamer-plugins-base (temel dönüşüm, audio/video) │
│ gstreamer-plugins-good (V4L2, RTP, FLAC, WebM...) │
│ gstreamer-plugins-bad (HLS, DASH, MSDK, NVCODEC...) │
│ gstreamer-plugins-ugly (x264, x265, MP3...) │
├──────────────────────────────────────────────────────────┤
│ Platform / Donanım │
│ V4L2 · ALSA · DRM/KMS · DMA-BUF · OpenGL/Vulkan │
└──────────────────────────────────────────────────────────┘
Temel kavramlar
Basit pipeline grafiği
source pad
sink/src pad
sink pad
GStreamer 1.x (1.0+) ve 0.10 arasında API uyumsuzluğu vardır. Gömülü Linux dağıtımlarında genellikle 1.18+ kullanılır. Bu rehber boyunca GStreamer 1.x API'si kullanılmaktadır.
01 Kurulum — araçlar ve plugin paketleri
GStreamer kurulumu dağıtıma göre değişir. Yocto/Buildroot gibi gömülü sistemlerde recipe/package seçimi kritiktir; masaüstü geliştirme ortamlarında apt/dnf ile hızlıca başlanabilir.
Debian/Ubuntu (geliştirme ortamı)
# Temel araçlar ve çekirdek kütüphane
sudo apt install \
gstreamer1.0-tools \
gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad \
gstreamer1.0-plugins-ugly \
libgstreamer1.0-dev \
libgstreamer-plugins-base1.0-dev
# V4L2 ve video desteği
sudo apt install \
gstreamer1.0-v4l2 \
v4l-utils
# Python bağlamları
sudo apt install python3-gst-1.0
# Doğrulama
gst-launch-1.0 --version
# GStreamer command line tool 1.20.3
Yocto — IMAGE_INSTALL
IMAGE_INSTALL:append = " \
gstreamer1.0 \
gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad \
gstreamer1.0-plugins-ugly \
gstreamer1.0-libav \
gstreamer1.0-meta-video \
"
gst-inspect-1.0 — element keşfi
# Yüklü tüm plugin'leri listele
gst-inspect-1.0
# Belirli bir element hakkında bilgi
gst-inspect-1.0 v4l2src
# Plugin Details:
# Name v4l2src
# Description Video (video4linux2) Source element
# ...
# Pad Templates:
# SRC template: 'src'
# Availability: Always
# Capabilities:
# video/x-raw ...
# Plugin içindeki tüm element'leri listele
gst-inspect-1.0 videotestsrc
# Belirli bir özelliği ara
gst-inspect-1.0 --exists v4l2h264enc && echo "donanım encode var"
GST_PLUGIN_PATH ortam değişkeni ile özel plugin dizini eklenebilir. Gömülü sistemlerde /usr/lib/gstreamer-1.0/ dizini standart konumdur.
02 gst-launch-1.0 ile ilk pipeline
gst-launch-1.0, komut satırından GStreamer pipeline'ı çalıştırmak için kullanılan temel araçtır. Hızlı prototipleme ve test için vazgeçilmezdir.
Temel syntax
# Genel form:
# gst-launch-1.0 [OPTIONS] ELEMENT [PROPERTY=VALUE ...] ! ELEMENT ...
# ! işareti iki element'i birleştirir (pipe benzeri)
# Özellikler KEY=VALUE biçiminde element'ten hemen sonra gelir
# Ekrana test videosu oynat
gst-launch-1.0 videotestsrc ! autovideosink
# Test sesi çal (hoparlör gerekli)
gst-launch-1.0 audiotestsrc ! autoaudiosink
# Test görüntüsü + belirli caps (format zorla)
gst-launch-1.0 videotestsrc ! \
video/x-raw,width=1280,height=720,framerate=30/1 ! \
autovideosink
# Dönen küpler ile farklı pattern (pattern=18 = ball)
gst-launch-1.0 videotestsrc pattern=ball ! autovideosink
Önemli gst-launch seçenekleri
Pratik pipeline örnekleri
# Test videosu → MP4 dosyasına yaz (5 saniye)
gst-launch-1.0 -e videotestsrc num-buffers=150 ! \
videoconvert ! \
x264enc ! \
mp4mux ! \
filesink location=test.mp4
# Kamera görüntüsünü ekranda göster
gst-launch-1.0 v4l2src device=/dev/video0 ! \
videoconvert ! \
autovideosink
# Mikrofon sesini WAV dosyasına kaydet
gst-launch-1.0 -e alsasrc device=hw:0 ! \
audioconvert ! \
wavenc ! \
filesink location=ses.wav
# UDP üzerinden video gönder (gönderici taraf)
gst-launch-1.0 videotestsrc ! \
videoconvert ! \
x264enc tune=zerolatency ! \
rtph264pay ! \
udpsink host=192.168.1.100 port=5000
# UDP'den video al ve göster (alıcı taraf)
gst-launch-1.0 udpsrc port=5000 ! \
application/x-rtp,encoding-name=H264 ! \
rtph264depay ! \
avdec_h264 ! \
autovideosink
Gömülü sistemlerde autovideosink her zaman çalışmayabilir. Wayland için waylandsink, framebuffer için fbdevsink, KMS/DRM için kmssink tercih edilmelidir.
03 Element türleri — source, filter, sink
GStreamer element'leri pad yapılarına göre üç ana kategoriye ayrılır. Hangi türde element kullandığınızı anlamak, pipeline tasarımının temelidir.
Caps negotiation
İki element birbirine link edildiğinde, GStreamer aralarında ortak bir format (caps) belirler. Bu süreç caps negotiation olarak adlandırılır:
1. Element A'nın src pad'i desteklenen caps'i ilan eder:
video/x-raw, format=(string){NV12, I420, YUY2}, width=[1,MAX], ...
2. Element B'nin sink pad'i kendi desteklediği caps'i ilan eder:
video/x-raw, format=(string){I420, YV12}, width=[320,1920], ...
3. GStreamer kesişim kümesini bulur:
video/x-raw, format=I420, width=[320,1920], ...
4. Gerekirse videoconvert gibi dönüştürücü otomatik eklenir (auto-plugging)
veya caps uyuşmazsa pipeline HATA verir.
GST_CAPS örneği
# Caps string yapısı:
# media-type, field1=value1, field2=value2, ...
# Video raw caps
video/x-raw,format=NV12,width=1920,height=1080,framerate=30/1
# H.264 caps (encode sonrası)
video/x-h264,stream-format=byte-stream,alignment=au,profile=high
# Pipeline'da capsfilter olarak kullan
gst-launch-1.0 v4l2src device=/dev/video0 ! \
video/x-raw,format=YUYV,width=1280,height=720 ! \
videoconvert ! \
video/x-raw,format=I420 ! \
x264enc ! \
filesink location=cikti.h264
# caps durumunu verbose modda incele
gst-launch-1.0 -v videotestsrc ! videoconvert ! autovideosink 2>&1 | grep caps
Gömülü platformlarda hardware encoder/decoder genellikle sadece NV12 formatını kabul eder. videoconvert ile I420 → NV12 dönüşümü yapılabilir; ancak bu CPU kullanımı demektir. DMA-BUF ile zero-copy aktarım için pad caps'lerini aynı tutmak gerekir.
04 C API ile pipeline oluşturma
Gerçek uygulamalarda gst-launch yeterli değildir. C API ile pipeline'ı programatik olarak oluşturmak; hata yönetimi, dinamik değişiklik ve uygulama entegrasyonu sağlar.
Temel C pipeline örneği
#include <gst/gst.h>
int main(int argc, char *argv[])
{
GstElement *pipeline, *source, *convert, *sink;
GstBus *bus;
GstMessage *msg;
GstStateChangeReturn ret;
/* GStreamer'ı başlat */
gst_init(&argc, &argv);
/* Element'leri oluştur */
source = gst_element_factory_make("videotestsrc", "source");
convert = gst_element_factory_make("videoconvert", "convert");
sink = gst_element_factory_make("autovideosink", "sink");
/* Pipeline (bin) oluştur */
pipeline = gst_pipeline_new("test-pipeline");
if (!pipeline || !source || !convert || !sink) {
g_printerr("Element oluşturulamadı.\n");
return -1;
}
/* Element'leri pipeline'a ekle */
gst_bin_add_many(GST_BIN(pipeline), source, convert, sink, NULL);
/* Element'leri birbirine bağla */
if (!gst_element_link_many(source, convert, sink, NULL)) {
g_printerr("Element'ler bağlanamadı.\n");
gst_object_unref(pipeline);
return -1;
}
/* Element özelliği ayarla */
g_object_set(source, "pattern", 0, NULL); /* 0 = SMPTE renk çubukları */
/* Pipeline'ı PLAYING durumuna geçir */
ret = gst_element_set_state(pipeline, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
g_printerr("Pipeline PLAYING durumuna geçemedi.\n");
gst_object_unref(pipeline);
return -1;
}
/* Bus mesajlarını bekle */
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("Hata: %s\n", err->message);
g_printerr("Debug: %s\n", debug_info ? debug_info : "yok");
g_clear_error(&err);
g_free(debug_info);
break;
case GST_MESSAGE_EOS:
g_print("Akış sona erdi.\n");
break;
default:
break;
}
gst_message_unref(msg);
}
/* Temizlik */
gst_object_unref(bus);
gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(pipeline);
return 0;
}
Derleme
CC = gcc
CFLAGS = $(shell pkg-config --cflags gstreamer-1.0)
LDFLAGS = $(shell pkg-config --libs gstreamer-1.0)
basic_pipeline: basic_pipeline.c
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
clean:
rm -f basic_pipeline
05 Bus ve mesaj yönetimi
GstBus, pipeline içindeki element'lerden uygulamaya asenkron mesaj iletmek için kullanılan thread-safe bir kuyruk mekanizmasıdır. Hata, EOS, state değişimi gibi olayları yakalamak için kritiktir.
GMainLoop ile asenkron mesaj izleme
#include <gst/gst.h>
typedef struct {
GstElement *pipeline;
GMainLoop *loop;
} AppData;
static gboolean bus_callback(GstBus *bus, GstMessage *msg, gpointer data)
{
AppData *app = (AppData *)data;
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_EOS:
g_print("[BUS] Akış sona erdi (EOS).\n");
g_main_loop_quit(app->loop);
break;
case GST_MESSAGE_ERROR: {
GError *err = NULL;
gchar *dbg = NULL;
gst_message_parse_error(msg, &err, &dbg);
g_printerr("[BUS] HATA: %s\n", err->message);
if (dbg) g_printerr("[BUS] DEBUG: %s\n", dbg);
g_error_free(err);
g_free(dbg);
g_main_loop_quit(app->loop);
break;
}
case GST_MESSAGE_WARNING: {
GError *err = NULL;
gchar *dbg = NULL;
gst_message_parse_warning(msg, &err, &dbg);
g_print("[BUS] UYARI: %s\n", err->message);
g_error_free(err);
g_free(dbg);
break;
}
case GST_MESSAGE_STATE_CHANGED:
if (GST_MESSAGE_SRC(msg) == GST_OBJECT(app->pipeline)) {
GstState old_state, new_state, pending;
gst_message_parse_state_changed(msg, &old_state, &new_state, &pending);
g_print("[BUS] State: %s → %s\n",
gst_element_state_get_name(old_state),
gst_element_state_get_name(new_state));
}
break;
default:
break;
}
return TRUE; /* mesaj izlemeye devam et */
}
int main(int argc, char *argv[])
{
AppData app = {0};
GstBus *bus;
guint watch_id;
gst_init(&argc, &argv);
app.pipeline = gst_parse_launch(
"videotestsrc num-buffers=100 ! videoconvert ! autovideosink",
NULL);
app.loop = g_main_loop_new(NULL, FALSE);
bus = gst_element_get_bus(app.pipeline);
watch_id = gst_bus_add_watch(bus, bus_callback, &app);
gst_object_unref(bus);
gst_element_set_state(app.pipeline, GST_STATE_PLAYING);
g_main_loop_run(app.loop); /* bloklar, bus event'leri işler */
gst_element_set_state(app.pipeline, GST_STATE_NULL);
g_source_remove(watch_id);
g_main_loop_unref(app.loop);
gst_object_unref(app.pipeline);
return 0;
}
06 State machine — NULL'dan PLAYING'e
Her GStreamer element bir state machine ile yönetilir. Doğru state geçişlerini anlamak; pipeline başlatma süresini optimize etmek, kaynak yönetimi ve hata kurtarma için gereklidir.
NULL ──→ READY ──→ PAUSED ──→ PLAYING
↑ ↑ ↑
└─────────────┴───────────┘
(geri geçiş de mümkün)
Geçiş sırası zorunludur: NULL→PLAYING doğrudan olmaz,
kernel ara adımları otomatik gerçekleştirir.
Async state change yönetimi
GstStateChangeReturn ret;
/* State geçişini başlat */
ret = gst_element_set_state(pipeline, GST_STATE_PLAYING);
switch (ret) {
case GST_STATE_CHANGE_SUCCESS:
g_print("Anlık state geçişi başarılı.\n");
break;
case GST_STATE_CHANGE_ASYNC:
/* Async geçiş — tamamlanmasını bekle (en fazla 5 sn) */
g_print("Async geçiş bekleniyor...\n");
ret = gst_element_get_state(pipeline, NULL, NULL, 5 * GST_SECOND);
if (ret == GST_STATE_CHANGE_SUCCESS)
g_print("Geçiş tamamlandı.\n");
else
g_printerr("Geçiş zaman aşımı!\n");
break;
case GST_STATE_CHANGE_FAILURE:
g_printerr("State geçişi başarısız!\n");
break;
case GST_STATE_CHANGE_NO_PREROLL:
/* Live kaynak (kamera) PAUSED'da veri üretmez — normal */
g_print("Live kaynak: NO_PREROLL (normal).\n");
break;
}
/* Mevcut state'i sorgula */
GstState current, pending;
gst_element_get_state(pipeline, ¤t, &pending, 0);
g_print("Mevcut: %s, Bekleyen: %s\n",
gst_element_state_get_name(current),
gst_element_state_get_name(pending));
v4l2src gibi live source'lar PAUSED durumunda GST_STATE_CHANGE_NO_PREROLL döner. Bu bir hata değildir; live kaynaklar buffer dolduramaz. Bu durumu doğru ele almak gerekir.
07 Pad ve caps — format müzakeresi
Pad'ler element'lerin bağlantı noktalarıdır. Static pad'ler her zaman mevcuttur; dynamic pad'ler ise çalışma zamanında (örneğin demux sırasında) oluşur. Caps negotiation bu pad'ler üzerinden yürütülür.
Dynamic pad — pad-added sinyali
#include <gst/gst.h>
/* qtdemux yeni pad açtığında çağrılır */
static void on_pad_added(GstElement *element, GstPad *pad, gpointer data)
{
GstElement *decoder = (GstElement *)data;
GstPad *sink_pad;
GstCaps *caps;
GstStructure *str;
const gchar *name;
/* Açılan pad'in caps'ini al */
caps = gst_pad_get_current_caps(pad);
str = gst_caps_get_structure(caps, 0);
name = gst_structure_get_name(str);
g_print("Yeni pad: %s, caps: %s\n", GST_PAD_NAME(pad), name);
/* Sadece video pad'ini bağla */
if (g_str_has_prefix(name, "video/x-h264")) {
sink_pad = gst_element_get_static_pad(decoder, "sink");
if (!gst_pad_is_linked(sink_pad)) {
if (gst_pad_link(pad, sink_pad) != GST_PAD_LINK_OK)
g_printerr("Pad link başarısız!\n");
else
g_print("Video pad bağlandı.\n");
}
gst_object_unref(sink_pad);
}
gst_caps_unref(caps);
}
int main(int argc, char *argv[])
{
GstElement *pipeline, *src, *demux, *decoder, *convert, *sink;
gst_init(&argc, &argv);
src = gst_element_factory_make("filesrc", "src");
demux = gst_element_factory_make("qtdemux", "demux");
decoder = gst_element_factory_make("avdec_h264", "decoder");
convert = gst_element_factory_make("videoconvert","convert");
sink = gst_element_factory_make("autovideosink","sink");
pipeline = gst_pipeline_new("file-pipeline");
gst_bin_add_many(GST_BIN(pipeline), src, demux, decoder, convert, sink, NULL);
g_object_set(src, "location", "video.mp4", NULL);
/* src → demux statik bağlantı */
gst_element_link(src, demux);
/* demux → decoder dinamik bağlantı */
g_signal_connect(demux, "pad-added", G_CALLBACK(on_pad_added), decoder);
/* decoder → convert → sink statik */
gst_element_link_many(decoder, convert, sink, NULL);
gst_element_set_state(pipeline, GST_STATE_PLAYING);
/* ... mesaj döngüsü ... */
return 0;
}
Caps filtreleme — capsfilter element
# capsfilter ile format zorla
gst-launch-1.0 v4l2src device=/dev/video0 ! \
video/x-raw,format=NV12,width=1920,height=1080,framerate=30/1 ! \
videoconvert ! \
video/x-raw,format=I420 ! \
x264enc bitrate=4000 ! \
filesink location=output.h264
# C API'de capsfilter
GstElement *capsfilter;
GstCaps *caps;
capsfilter = gst_element_factory_make("capsfilter", "filter");
caps = gst_caps_from_string(
"video/x-raw,format=NV12,width=1920,height=1080,framerate=30/1");
g_object_set(capsfilter, "caps", caps, NULL);
gst_caps_unref(caps);
gst_bin_add(GST_BIN(pipeline), capsfilter);
gst_element_link_many(v4l2src, capsfilter, videoconvert, NULL);
08 Pratik: v4l2src → videoconvert → autovideosink canlı kamera pipeline
Raspberry Pi veya i.MX8 üzerinde V4L2 kamerasından canlı görüntü alan, hem ekranda gösteren hem dosyaya yazan tam çalışan bir pipeline uygulaması.
Kamera bilgilerini keşfet
# Mevcut video aygıtları
v4l2-ctl --list-devices
# Kameranın desteklediği formatları listele
v4l2-ctl -d /dev/video0 --list-formats-ext
# ioctl: VIDIOC_ENUM_FMT
# Index : 0
# Type : Video Capture
# Pixel Format: 'YUYV'
# Name : YUYV 4:2:2
# Size: Discrete 1920x1080
# Interval: Discrete 0.033s (30.000 fps)
# Size: Discrete 1280x720
# Interval: Discrete 0.033s (30.000 fps)
# GStreamer ile kamera caps'lerini keşfet
gst-device-monitor-1.0 Video/Source
Canlı görüntü + eş zamanlı kayıt
# Sadece ekran gösterimi
gst-launch-1.0 \
v4l2src device=/dev/video0 ! \
video/x-raw,format=YUYV,width=1280,height=720,framerate=30/1 ! \
videoconvert ! \
autovideosink sync=false
# tee ile hem ekran hem H.264 dosya kaydı
gst-launch-1.0 -e \
v4l2src device=/dev/video0 ! \
video/x-raw,format=YUYV,width=1280,height=720,framerate=30/1 ! \
videoconvert ! \
tee name=t \
t. ! queue ! autovideosink sync=false \
t. ! queue ! x264enc bitrate=2000 ! mp4mux ! filesink location=kayit.mp4
C uygulaması — tam örnek
#include <gst/gst.h>
#include <signal.h>
static GMainLoop *main_loop = NULL;
static void sigint_handler(int sig)
{
g_print("\nCtrl+C yakalandı — EOS gönderiliyor...\n");
/* Pipeline'a EOS gönder — dosya düzgün kapansın */
if (main_loop) g_main_loop_quit(main_loop);
}
static gboolean bus_cb(GstBus *bus, GstMessage *msg, gpointer data)
{
GMainLoop *loop = (GMainLoop *)data;
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_EOS:
g_print("EOS alındı.\n");
g_main_loop_quit(loop);
break;
case GST_MESSAGE_ERROR: {
GError *err; gchar *dbg;
gst_message_parse_error(msg, &err, &dbg);
g_printerr("HATA: %s (%s)\n", err->message, dbg ? dbg : "-");
g_error_free(err); g_free(dbg);
g_main_loop_quit(loop);
break;
}
default: break;
}
return TRUE;
}
int main(int argc, char *argv[])
{
GstElement *pipeline;
GstBus *bus;
GError *err = NULL;
const gchar *pipe_desc =
"v4l2src device=/dev/video0 ! "
"video/x-raw,format=YUYV,width=1280,height=720,framerate=30/1 ! "
"videoconvert ! "
"tee name=t "
"t. ! queue leaky=2 max-size-buffers=3 ! autovideosink sync=false "
"t. ! queue ! x264enc bitrate=2000 speed-preset=ultrafast ! "
" mp4mux ! filesink location=kayit.mp4";
gst_init(&argc, &argv);
signal(SIGINT, sigint_handler);
pipeline = gst_parse_launch(pipe_desc, &err);
if (err) {
g_printerr("Pipeline hatası: %s\n", err->message);
g_error_free(err);
return 1;
}
main_loop = g_main_loop_new(NULL, FALSE);
bus = gst_element_get_bus(pipeline);
gst_bus_add_watch(bus, bus_cb, main_loop);
gst_object_unref(bus);
gst_element_set_state(pipeline, GST_STATE_PLAYING);
g_print("Pipeline başladı. Ctrl+C ile dur.\n");
g_main_loop_run(main_loop);
gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(pipeline);
g_main_loop_unref(main_loop);
g_print("Temizlik tamamlandı.\n");
return 0;
}
Raspberry Pi 4'te YUYV → I420 dönüşümü CPU'ya yük bindirdirir. RPi kamera modülünü libcamerasrc ile kullanarak NV12 natif çıkış alabilir, videoconvert'ten kaçınabilirsiniz. i.MX8MP üzerinde ise v4l2h264enc hardware encoder, CPU kullanımını %5'in altına indirir.