이번 포스팅에서는 이러한 GStreamer 프레임워크의 공식 Basic tutorials를 모두 진행해보는 시간을 가진다.
GStreamer가 무엇인지 모르거나, 기본적인 요소들에 대한 것들은 아래 글에 정리해두었다.
[GStreamer] GStreamer 기본 개념 및 명령어
튜토리얼들은 모두 Linux 환경에서 진행했다.
튜토리얼은 1~16강 까지. 16강으로 생각할 수 있지만 중간에 15강이 빠져, 총 15개의 튜토리얼이 있다.

GStreamer 공식 튜토리얼
1. Basic tutorial 1 : Hello World!
튜토리얼 1번은 uri로부터 영상을 가져와 재생한다.
gcc basic-tutorial-1.c -o basic-tutorial-1 `pkg-config --cflags --libs gstreamer-1.0`
./basic-tutorial-1
Basic tutorial 1: Hello world!
예제의 basic-tutorial-1.c 코드를 작성하고, 컴파일 후에 실행해보면 아래와 같은 영상이 재생됨을 확인할 수 있다.
해당 코드의 핵심 부분만 뽑아 정리해보자

① gst_init()
/* Initialize GStreamer */
gst_init (&argc, &argv);
항상 첫 번째 GStreamer 명령어야 하는 gst_init이다.
- 모든 내부 구조를 초기화
- 시스템에 설치된 사용 가능한 GStreamer 플러그인들을 탐색/등록
- CLI로 입력받은 옵션들을 자동으로 파싱해서 적용
이런 역할을 수행하며, GStreamer 기능 수행을 하는데 핵심이며 필수적이다.
다른 GStreamer 코드보다 반드시 먼저 수행되어야 한다.
② gst_parse_launch() + playbin
이 줄이 핵심이라고 할 수 있는데, gst_parse_launch()는 텍스트 문자열로 파이프라인을 바로 생성한다.
원래는 파이프라인을 만들려면 element를 하나씩 생성하고 gst_element_link()로 일일이 연결해주어야 하는데, 이 명령어는
텍스트로 파이프라인을 만들 수가 있다.
pipeline = gst_parse_launch("playbin uri=...", NULL);
CLI로는 보통 gst-launch-1.0과 함께 파이프라인을 생성한다.
# gst-launch-1.0 CLI
gst-launch-1.0 videotestsrc ! autovideosink
# gst_parse_launch() 코드에서
pipeline = gst_parse_launch("videotestsrc ! autovideosink", NULL);
이 두 코드는 완전히 같은 역할을 수행한다. 여기서 !는 element간 link(연결)을 의미한다.
playbin은?
// element이름 property=value 형식
pipeline = gst_parse_launch(
"videotestsrc pattern=ball ! autovideosink",
NULL
);
// playbin에 uri 프로퍼티 지정
pipeline = gst_parse_launch(
"playbin uri=https://..../video.webm",
NULL
);
쉽게 말해, uri만 주면 재생까지 처리해주는 element이다. 원래 파이프라인에서는 decoder, demuxer 등을 미디어 포맷마다 지정해주어야하지만 playbin을 사용하면 uri만 넘겨주면 해당 포맷에 맞게 처리해준다.
③ gst_element_set_state(pipeline, GST_STATE_PLAYING)
GstStateChangeReturn gst_element_set_state(
GstElement *element, // 상태를 바꿀 element (여기선 pipeline)
GstState state // 목표 state
);
GStreamer element의 상태를 지정하는 함수로, 인자로 element와 state가 전달된다.
State 종류
| State | 설명 |
| GST_STATE_NULL | 초기 상태, 리소스 미할당. element 생성 직후 이 상태 |
| GST_STATE_READY | 리소스 할당됨, 스트림 열기 전 대기 |
| GST_STATE_PAUSED | 데이터 흐름 시작했지만 출력은 멈춤. 첫 프레임 버퍼링 완료 |
| GST_STATE_PLAYING | 데이터 흐름 활성화, 클럭 동기화하며 실제 출력 중 |
이런 State 전환은 단계적으로 이루어진다.
GST_STATE_PLAYING의 상태를 인자로 호출 시,
NULL → READY → PAUSED → PLAYING 로 단계적으로 전환된다. (자동으로 처리됨)
반대로 GST_STATE_NULL로 설정 시,
PLAYING → PAUSED → READY → NULL 이렇게 역순으로 처리된다. (자동으로 처리됨)
이 gst_element_set_state 함수는 비동기로 처리되며, bus 대기 루프와 함께 동작된다.
④ Bus 대기 루프
Bus는 파이프라인 내부에서 발생한 이벤트를 앱의 메인 스레드로 안전하게 전달하는 메시지 큐다. GStreamer 파이프라인은 내부적으로 여러 스레드가 동시에 돌아가는데, 스레드 간 직접 통신은 위험하기 때문에 이런 Bus라는 채널을 통해 메시지를 주고 받는다.
/* Wait until error or EOS */
bus = gst_element_get_bus (pipeline);
msg =
gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
이런 식으로 먼저 파이프라인에서 bus 객체를 받아 생성한 후에, gst_bus_timed_pop_filtered 함수로 gst_element_set_state함수의 처리를 기다린다.
gst_bus_timed_pop_filtered 함수에 대해 자세히 설명하자면
GstMessage* gst_bus_timed_pop_filtered(
GstBus *bus,
GstClockTime timeout, // 대기 시간 (GST_CLOCK_TIME_NONE = 무한)
GstMessageType types // 받을 메시지 타입 필터
);
즉, Bus가 메인 스레드이고 get_element_set_state함수가 비동기로 백그라운드에서 처리가 되는데 이 때 모두 처리하여 반환하기 전까지는 메인 스레드 Bus는 GST_CLOCK_TIME_NONE으로 무한정 대기하며 MESSAGE들을 기다리는 것이다.
주요 메시지 타입
| 타입 | 발생 시점 |
| GST_MESSAGE_ERROR | 파이프라인 치명적 에러 발생 |
| GST_MESSAGE_EOS | 미디어 끝(End of Stream) 도달 |
| GST_MESSAGE_WARNING | 경고 (재생은 계속됨) |
| GST_MESSAGE_STATE_CHANGED | element의 State 전환 완료 |
| GST_MESSAGE_BUFFERING | 네트워크 버퍼링 진행률 |
| GST_MESSAGE_CLOCK_LOST | 파이프라인 클럭 유실 |
이러한 동작들을 통해 위의 영상이 최종적으로 재생되어 출력된다.
2. Basic tutorial 2 : GStreamer concepts
튜토리얼 2는 앞서 진행했던 튜토리얼 1을 playbin 없이 직접 element를 하나씩 생성해서 연결하여 파이프라인을 생성한다.
Basic tutorial 2: GStreamer concepts
전체 진행 과정은 동일하지만, 중간에 파이프라인 생성 전에 element들을 생성한다.
물론 맨처음 gst_init()이 우선이 돼야하고 이건 설명에서 생략하겠다.
/* 1. Element 생성 */
source = gst_element_factory_make("videotestsrc", "source");
sink = gst_element_factory_make("autovideosink", "sink");
여기서 videotestsrc는 GStreamer의 테스트용 비디오이다.
그 후, 빈 파이프라인을 생성한다.
/* 2. 빈 파이프라인 생성 */
pipeline = gst_pipeline_new("test-pipeline");
그 다음 생성한 파이프라인에 만들어두었던 element들을 추가한다.
이때, 파이프라인 객체를 GST_BIN 형태로 변환하여 인자로 전달해야 한다. gst_bin_add_many의 인자 타입이 GstBin*이기 때문
/* 3. 파이프라인에 element 추가 */
gst_bin_add_many(GST_BIN(pipeline), source, sink, NULL);
생성한 element들을 link해준다. link 방향은 데이터가 흐르는 방향으로 해주어야 한다.
/* 4. element 연결 */
if (gst_element_link(source, sink) != TRUE) {
g_printerr("Elements could not be linked.\n");
gst_object_unref(pipeline);
return -1;
}
이 부분은 프로퍼티 설정인데, 테스트용 비디오를 어떤 패턴으로 출력할 지 지정하는 것이다.
/* 5. 프로퍼티 설정 */
g_object_set(source, "pattern", 0, NULL);
이렇게 playbin으로 자동으로 처리해주던 걸 수동으로 일일이 생성해서 연결해주는 게 기본 패턴이다.
그래서 실제 컴파일 후 실행해보면
gcc basic-tutorial-2.c -o basic-tutorial-2 `pkg-config --cflags --libs gstreamer-1.0`
./basic-tutorial-2

테스트 영상이 출력되는 걸 볼 수 있다.
3. Basic tutorial 3 : Dynamic pipelines
Basic tutorial 3: Dynamic pipelines
우선, 튜토리얼 3은 미디어 데이터를 가져와 그 미디어의 오디오 부분만 분리하여 재생하는 것을 목표로 한다.
MP4나 WebM같은 영상 파일들은 오디오 + 비디오가 하나로 묶인 컨테이너 포맷이다. 그렇기 때문에 오디오만 분리하여 재생하기 위해서는 demuxer가 필요하다.
demuxer는 실제로 파일을 읽어봐야 그 파일 안에 스트림이 몇 개 있는지 알 수 있다. 그래서 파이프라인 시작 전에 미리 연결이 불가능하고, 실행 중에 동적으로 연결해야 한다.
코드를 보며 분석해보면,
먼저 영상데이터를 담을 element를 생성하고,
gst_element_factory_make() : 빈 uridecodebin 객체 생성
data.source = gst_element_factory_make("uridecodebin", "source");
해당 element에 uri에서 받아온 영상 데이터 프로퍼티를 지정한다.
g_object_set (data.source, "uri",
"https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm",
NULL);
이후 파이프라인이 실행되고 해당 uri주소의 파일을 uridecodebin이 처리하는데, 이 때 파일을 읽으면서 몇 개의 pad를 생성할 지 결정한다.
여기서 중요한 개념이 또 하나 등장하는데 바로 signal이다.
g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler), &data);
Signal : g_signal_connect
GObject의 이벤트 콜백 시스템이다. 특정 이벤트가 발생했을 때 등록해둔 함수를 자동으로 호출해준다.
data.source가 감시할 element이고, 해당 element에 "pad-added"이벤트가 발생했을 때,
G_CALLBACK의 함수를 호출하며 &data를 넘기게 된다.
즉, 파일에서 pad가 생성될 때마다 콜백 함수를 호출하게 되는 것이고, 이때 pad의 갯수는 파일이 어떻게 구성되어 있는지에 따라 달라진다. 튜토리얼에서는 오디오+비디오로 구성돼있어 2개의 pad가 생성된다.
이 pad갯수는 파이프라인 실행 중간에 직접 파일을 읽어야 알 수 있기 때문에 Dynamic pipeline을 사용한다.
콜백 함수 흐름
static void pad_added_handler(GstElement *src, GstPad *new_pad, CustomData *data) {
// 1. convert의 sink pad 가져오기
GstPad *sink_pad = gst_element_get_static_pad(data->convert, "sink");
// 2. 이미 연결됐으면 무시 (uridecodebin은 pad를 여러 개 만들 수 있음)
if (gst_pad_is_linked(sink_pad)) goto exit;
// 3. 새 pad의 타입 확인 (오디오인지 비디오인지)
new_pad_caps = gst_pad_get_current_caps(new_pad);
new_pad_type = gst_structure_get_name(gst_caps_get_structure(new_pad_caps, 0));
if (!g_str_has_prefix(new_pad_type, "audio/x-raw")) goto exit; // 오디오 아니면 무시
// 4. pad 직접 연결
gst_pad_link(new_pad, sink_pad);
}
해당 코드는 "audio/x-raw" 오디오 타입 pad만 연결해서 파이프라인을 구성하는 것이다.
실제로 실행해보면 튜토리얼 1에서의 영상과 같은 오디오만 나오는 것을 알 수 있다.
gcc basic-tutorial-3.c -o basic-tutorial-3 `pkg-config --cflags --libs gstreamer-1.0`
./basic-tutorial-3
4. Basic tutorial 4 : Time Management
Basic tutorial 4: Time management
튜토리얼 4에서는 동영상 재생 중, 원하는 시간 위치로 점프시키는 Seek 기능을 사용해본다.
튜토리얼 1~3은 그냥 재생하고 끝날 때까지 기다리는 구조였다면, 튜토리얼 4에서 재생 중 파이프라인과 상호작용하는 과정이 추가됐다.
구체적으로 하는 일:
- 100ms마다 현재 재생 위치와 전체 길이 터미널에 출력
- 재생 위치가 10초를 넘으면 30초로 자동 seek
아래 터미널 출력을 보면 재생 후, 10초에 지났을 때, 30초로 Seek 하여 재생을 이어나가는 것을 볼 수 있다.
Position으로 100ms 마다 출력을 하여 현재 어느 지점을 재생 중인지 확인할 수 있다.

- 동작 원리
① GstQuery : 파이프라인에게 정보를 물어보는 매커니즘
- gst_element_query_position() : 지금 현재 몇 초를 재생하는지?
- gst_element_query_duration(): 영상의 전체 재생 길이는 몇 초인지?
- gst_element_query() + gst_query_new_seeking() : Seek가 가능한지?
② Seek : 원하는 시간 위치로 점프
gst_element_seek_simple(pipeline, GST_FORMAT_TIME, flags, 30 * GST_SECOND)
현재 코드에서는 30 초로 설정해둬서 30초로 Seek 하게 된다.
5. Basic tutorial 5 : GUI Toolkit Integration
튜토리얼5는 GTK+를 이용해서 Play/Pause/Stop 버튼, 재생 슬라이더, 스트림 정보가 있는 실제 미디어 플레이어 GUI를 만드는 튜토리얼이다.

- 동작 원리
① GTK+ 연동 : 영상을 GTK 위젯 안에 출력
// GStreamer가 이 GTK 위젯 안에 영상을 렌더링하도록 설정
gtkglsink = gst_element_factory_make("gtkglsink", "gtkglsink");
g_object_get(gtkglsink, "widget", &data.sink_widget, NULL);
// playbin의 video-sink를 GTK 위젯으로 교체
g_object_set(data.playbin, "video-sink", videosink, NULL);
gtkglsink를 쓰면 GTK 앱의 특정 위젯 안에 영상이 출력된다.
② GLib Main Loop : 이벤트 기반 루프
GUI의 슬라이딩 바를 1초마다 갱신하여 처리한다.
// 1초마다 refresh_ui 함수 자동 호출 등록
g_timeout_add_seconds(1, (GSourceFunc)refresh_ui, &data);
// GTK Main Loop 시작 (여기서 모든 이벤트 처리)
gtk_main();
③ 스레드 문제 : Bus로 우회
GTK는 메인 스레드에서만 위젯 조작이 가능하다. 그렇기 때문에 백그라운드 스레드에서 직접 라벨 등의 위젯을 업데이트할 시, 크래시가 발생한다. 그래서 백그라운드 스레드에서는 Bus에 GTK에게 위젯 업데이트를 하라는 메시지를 전달하여 GTK가 직접 위젯을 업데이트 한다.
④ Bus 메시지 콜백 방식
앞의 튜토리얼 4에서는 handle_message() 함수 하나에서 메시지 타입을 switch로 분기해서 처리했다.
튜토리얼 5에서는 GTK Main Loop를 사용하기 때문에 루프를 직접 돌리지 않고, Bus에 콜백을 등록해두고 메시지가 오면 GTK Main Loop가 호출해주는 방식을 사용한다.
// Tutorial 5 방식
gst_bus_add_signal_watch(bus); // Bus가 메시지마다 시그널 발생하게 설정
g_signal_connect(bus, "message::error", G_CALLBACK(error_cb), ...);
g_signal_connect(bus, "message::eos", G_CALLBACK(eos_cb), ...);
g_signal_connect(bus, "message::state-changed", G_CALLBACK(state_changed_cb), ...);
g_signal_connect(bus, "message::application", G_CALLBACK(application_cb), ...);
GTK를 사용하므로 컴파일 명령어가 조금 다르다.
gcc basic-tutorial-5.c -o basic-tutorial-5 `pkg-config --cflags --libs gtk+-3.0 gstreamer-1.0`
6. Basic tutorial 6 : Media formats and Pad Capabilities
튜토리얼 6은 Pad가 어떤 데이터를 주고받을 수 있는지 명세하는 Caps(Capabilities)에 대한 튜토리얼이다.
- Pad Caps란?
Pad는 element의 입출력 포트이고, Caps는 그 포트가 어떤 데이터를 받을 수 있는지 조건을 정의한다.
audio/x-raw
format: S16LE
rate: [ 1, 2147483647 ] ← 범위
channels: [ 1, 2 ]
두 element를 연결하려면 서로의 Caps에 겹치는 부분이 있어야 한다. 없으면 연결 자체를 거부함.
- Negotiation : 서로 맞는 포맷 합의하기
source와 sink가 서로 호환되는 포맷을 찾아 결정하는 과정이다.
예를 들어 source가 MP3 포맷을 전달하는데 sink가 MP3 포맷을 지원하지 않으면 재생이 안되기 때문에 연결 전, 이런 협상 과정을 거친다.
터미널 출력 결과를 보면 아래와 같이 출력되는 걸 볼 수 있다.


① Pad Templates for Audio test source: 에서 Capabilities를 확인하면 포맷이 여러 개 출력된 걸 볼 수 있다.
② Pad Templates for Auto audio sink: 의 Capabilities는 ANY로 아무 포맷이나 가능하다는 것을 뜻한다.
③ 중간 중간 파이프라인의 상태가 NULL - READY - PAUSED로 바뀌어 가면서 sink pad의 포맷들이 구체화되며 최종 포맷이 결정되는 것을 볼 수 있다.
이러한 Negotiation 과정은 GStreamer의 내부 엔진이 자동으로 처리해준다.
튜토리얼 6의 목적은 이러한 Negotiation의 과정이 어떻게 진행되는지 확인하는 과정이다.
기존에는 gst_element_factory_make() 함수로 element객체를 생성했지만, 이렇게 생성할 시 객체의 Pad 템플릿 정보를 조회할 수 없다. 그래서 이 함수의 동작을 분리하여 element 객체를 생성한다.
// 기존 (shortcut)
GstElement *source = gst_element_factory_make("audiotestsrc", "source");
// Tutorial 6 (factory 분리)
GstElementFactory *source_factory = gst_element_factory_find("audiotestsrc");
GstElement *source = gst_element_factory_create(source_factory, "source");
최종적으로
// 1단계: factory 객체 가져오기
GstElementFactory *factory = gst_element_factory_find("audiotestsrc");
// 2단계: factory로 Pad 템플릿 조회 (이게 목적!)
gst_element_factory_get_static_pad_templates(factory);
// 3단계: element 생성
GstElement *source = gst_element_factory_create(factory, "source");
이렇게 세 단계에 걸쳐, 객체를 찾고 Pad 템플릿을 조회한 후에 element 객체를 생성한다.
추후 디버깅 과정에서 이런 Pad 템플릿, Caps를 출력하여 분석하는데 많이 사용한다.
- 컴파일
gcc basic-tutorial-6.c -o basic-tutorial-6 `pkg-config --cflags --libs gstreamer-1.0`
- 명령어를 통한 출력
gst-inspect-1.0 [element명]
위의 코드에서 구현했던 것들은 사실 명령어를 통해 더 자세하게 정보들을 출력할 수 있다.
7. Basic tutorial 7 : Multithreading and Pad Availability
튜토리얼 7은 하나의 오디오 소스를 tee로 두 갈래로 나눠서, 하나는 스피커로 재생하고 하나는 파형 시각화로 보내는 예제다.
먼저 파일을 실행해보면 일정한 오디오와 아래의 파형이 연속적으로 재생이 된다. 파형의 움직임은 재생되는 오디오의 소리를 시각적으로 표현한 것이다.

- 동작 원리
먼저 이 예제는 입력 audio 소스를 두 개로 복사하여 하나는 오디오, 하나는 파형 시각화 이렇게 동시에 두 작업을 처리하는 멀티 스레딩 방식으로 처리한다.
파이프라인의 구조는 이런 식이 된다.
audiotestsrc → tee → audio_queue → audioconvert → autoaudiosink (소리)
→ video_queue → wavescope → autovideosink (파형)
① 가장 먼저, 오디오 소스를 병렬로 처리하기 위해서 queue와 tee 등 element들을 생성해야 한다.
audio_source = gst_element_factory_make ("audiotestsrc", "audio_source");
tee = gst_element_factory_make ("tee", "tee");
audio_queue = gst_element_factory_make ("queue", "audio_queue");
audio_convert = gst_element_factory_make ("audioconvert", "audio_convert");
audio_resample = gst_element_factory_make ("audioresample", "audio_resample");
audio_sink = gst_element_factory_make ("autoaudiosink", "audio_sink");
video_queue = gst_element_factory_make ("queue", "video_queue");
visual = gst_element_factory_make ("wavescope", "visual");
video_convert = gst_element_factory_make ("videoconvert", "csp");
video_sink = gst_element_factory_make ("autovideosink", "video_sink");
② audiotestsrc를 tee로 두 개의 src pad 요청 생성
tee_audio_pad = gst_element_request_pad_simple(tee, "src_%u"); // src_0
tee_video_pad = gst_element_request_pad_simple(tee, "src_%u"); // src_1
② queue를 생성했을 때, 함께 생성된 sink pad들을 불러와 저장해준다.
queue_audio_pad = gst_element_get_static_pad(audio_queue, "sink");
queue_video_pad = gst_element_get_static_pad(video_queue, "sink");
이제 이 sink pad들은 tee에서 생성된 오디오 src pad 들을 받아 처리할 수 있게 된다.
여기서 조금 중요한 개념이 등장한다.
pad를 생성할 때 3가지 유형이 존재하는데, Always Pad, Sometimes Pad, Request Pad가 있다.
- Always Pad : element를 만들면 자동으로 생성되는 Pad
// 가져오기만 하면 됨 gst_element_get_static_pad(audio_queue, "sink"); - Sometimes Pad : 데이터가 흐를 때 런타임에 자동 생성되는 Pad
// 생성 시점을 모르니까 시그널로 감지 g_signal_connect(element, "pad-added", G_CALLBACK(pad_added_cb), ...); - Request Pad : 직접 요청해야 생성되는 Pad
// 요청해서 생성 gst_element_request_pad_simple(tee, "src_%u");
이런 식으로, Pad가 생성되는 방식이 다르다.
우리가 앞서 생성한 src 패드와 sink 패드는 Always Pad(audio_queue), Request Pad(tee) 두 가지이다.
이렇게 각각 다른 유형으로 생성된 pad들은 자동으로 link되지 않기 때문에 직접 연결해줘야 한다.
// Request + Always 조합이라 직접 연결
gst_pad_link(tee_audio_pad, queue_audio_pad);
gst_pad_link(tee_video_pad, queue_video_pad);
그리고 element들은 Always 생성 방식이므로 자동으로 연결이 된다.
gst_element_link_many(audiotestsrc, tee, NULL);
gst_element_link_many(audio_queue, audioconvert, audioresample, autoaudiosink, NULL);
gst_element_link_many(video_queue, wavescope, videoconvert, autovideosink, NULL);
최종적으로 코드를 실행해보면 아래처럼 출력이 나오게 되며 오디오/파형 비디오가 재생된다.

- 컴파일
gcc basic-tutorial-7.c -o tutorial7 `pkg-config --cflags --libs gstreamer-1.0`
'CV(Computer-Vision)' 카테고리의 다른 글
| [GStreamer] GStreamer 기본 개념 및 명령어 (0) | 2026.04.23 |
|---|