GStreamer 실전 가이드 | 파이프라인·C/Python·gst-launch로 멀티미디어 다루기

GStreamer 실전 가이드 | 파이프라인·C/Python·gst-launch로 멀티미디어 다루기

이 글의 핵심

GStreamer 1.x 파이프라인(Element·Pad·Caps), gst-launch-1.0 CLI, C gst_parse_launch·Bus 처리, Python PyGObject 재생 예제와 GST_DEBUG 디버깅을 한 흐름으로 정리합니다.

들어가며

GStreamer오디오·영상을 처리하는 오픈소스 멀티미디어 프레임워크입니다. 재생·녹화·인코딩·스트리밍·효과 처리까지 파이프라인이라는 그래프로 조립해 동작합니다.

이 글에서 다루는 것

  • 파이프라인·Element·Pad·Caps의 최소 개념
  • gst-launch-1.0으로 빠르게 검증하는 방법
  • C와 Python에서 파이프라인을 돌리는 기본 패턴
  • 에러 확인·디버그 로그(GST_DEBUG)로 삽질 줄이기

GStreamer 1.x 기준으로 설명합니다.


목차

  1. 핵심 개념
  2. gst-launch-1.0으로 시작하기
  3. C 예제
  4. Python(PyGObject) 예제
  5. Bus 메시지와 종료 처리
  6. 디버깅과 자주 나는 실수
  7. 마무리

핵심 개념

파이프라인(Pipeline)

파이프라인연결된 Element들의 집합입니다. 데이터가 소스 → 필터 → 싱크 방향으로 흐릅니다.

[ filesrc ] → [ decodebin ] → [ audioconvert ] → [ autoaudiosink ]
   (파일)      (디코딩)         (포맷 맞춤)           (스피커)
  • Element: 실제 동작 단위(읽기, 디코딩, 변환, 출력).
  • Pad: Element의 입·출력 포트. Src pad는 데이터를 보내고 Sink pad는 받습니다.
  • Caps(Capabilities): 포맷 협상에 쓰이는 설명(예: video/x-raw, 샘플레이트, 픽셀 포맷). Pad가 연결될 때 호환 가능한 caps가 맞아야 합니다.

decodebin, playbin, uridecodebin 같은 빈(bin)은 내부에 여러 Element를 넣고 Pad를 동적으로 만들어 줍니다. 그래서 초보자는 이 셋부터 쓰면 삽질이 줄어듭니다.


gst-launch-1.0으로 시작하기

CLI로 파이프라인 문자열을 바로 실행해 플러그인·URI· caps를 검증합니다.

간단 재생

# 파일 재생(자동 디코딩·출력 선택)
gst-launch-1.0 playbin uri=file:///C:/Videos/sample.mp4
# HTTP 스트림
gst-launch-1.0 playbin uri=https://example.com/stream.mp4

명시적 체인 예시

오디오만 wav로 디코딩해 스피커로:

gst-launch-1.0 filesrc location=music.wav ! wavparse ! audioconvert ! audioresample ! autoaudiosink
  • !: Pad를 링크한다는 뜻(문자열 파싱 시).
  • 쉘에서 !가 특수 문자면 따옴표로 파이프라인 전체를 감싸세요.
gst-launch-1.0 "filesrc location=test.wav ! wavparse ! audioconvert ! audioresample ! autoaudiosink"

테스트 소스

설치가 됐는지 확인할 때 유용합니다.

gst-launch-1.0 videotestsrc ! autovideosink
gst-launch-1.0 audiotestsrc ! autoaudiosink

C 예제

playbin에 URI를 넣고 Bus에서 EOS/ERROR를 기다리는 최소 재생 예제입니다.

#include <gst/gst.h>

int main(int argc, char *argv[]) {
  GstElement *pipeline;
  GstBus *bus;
  GstMessage *msg;
  GstStateChangeReturn ret;

  if (argc < 2) {
    g_printerr("Usage: %s <file path or URI>\n", argv[0]);
    return -1;
  }

  gst_init(&argc, &argv);

  pipeline = gst_element_factory_make("playbin", "play");
  if (!pipeline) {
    g_printerr("playbin 생성 실패. 플러그인·PKG_CONFIG_PATH 확인.\n");
    return -1;
  }

  g_object_set(pipeline, "uri", argv[1], NULL);

  ret = gst_element_set_state(pipeline, GST_STATE_PLAYING);
  if (ret == GST_STATE_CHANGE_FAILURE) {
    g_printerr("PLAYING 상태 전환 실패.\n");
    gst_object_unref(pipeline);
    return -1;
  }

  bus = gst_element_get_bus(pipeline);
  msg = gst_bus_timed_pop_filtered(
      bus,
      GST_CLOCK_TIME_NONE,
      GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

  if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ERROR) {
    GError *e = NULL;
    gchar *dbg = NULL;
    gst_message_parse_error(msg, &e, &dbg);
    g_printerr("에러: %s\n", e->message);
    if (dbg) g_printerr("Debug: %s\n", dbg);
    g_clear_error(&e);
    g_free(dbg);
  }

  gst_message_unref(msg);
  gst_object_unref(bus);
  gst_element_set_state(pipeline, GST_STATE_NULL);
  gst_object_unref(pipeline);
  return 0;
}

빌드 예 (Linux, pkg-config 사용):

gcc -o play play.c $(pkg-config --cflags --libs gstreamer-1.0)

Python(PyGObject) 예제

GStreamer는 GObject Introspection으로 Python에서도 동일한 개념을 씁니다.

import sys
import gi

gi.require_version("Gst", "1.0")
from gi.repository import Gst, GLib


def main():
    Gst.init(None)
    if len(sys.argv) < 2:
        print("Usage: python play.py <URI>")
        sys.exit(1)

    playbin = Gst.ElementFactory.make("playbin", None)
    playbin.set_property("uri", sys.argv[1])

    loop = GLib.MainLoop()

    bus = playbin.get_bus()
    bus.add_signal_watch()

    def on_message(bus, message):
        t = message.type
        if t == Gst.MessageType.EOS:
            loop.quit()
        elif t == Gst.MessageType.ERROR:
            err, dbg = message.parse_error()
            print(f"Error: {err.message}", file=sys.stderr)
            loop.quit()

    bus.connect("message", on_message)

    playbin.set_state(Gst.State.PLAYING)
    loop.run()
    playbin.set_state(Gst.State.NULL)


if __name__ == "__main__":
    main()

주의: 시스템에 python-gi와 GStreamer 1.0 typelib가 맞게 설치되어 있어야 합니다.


Bus 메시지와 종료 처리

앱에서 빠지기 쉬운 부분이 Bus 루프입니다.

  • EOS: 스트림 끝. 재생 완료 시 정상 종료 신호.
  • ERROR: 파이프라인이 복구 불가일 때 많이 옵니다. parse_error로 문자열을 꼭 출력하세요.
  • STATE_CHANGED: 디버깅·UI 연동에 사용.

메인 스레드를 막지 않으려면 add_signal_watch + GLib 메인 루프 또는 별도 스레드에서 pop 패턴을 선택합니다.


디버깅과 자주 나는 실수

GST_DEBUG

로그 레벨과 카테고리를 지정하면 Pad 링크 실패 원인을 빠르게 좁힐 수 있습니다.

GST_DEBUG=3 gst-launch-1.0 playbin uri=file:///path/to/file.mp4

자주 쓰는 패턴:

GST_DEBUG=*:2,*decode*:5

흔한 실수

  1. Pad 미연결: decodebin 뒤는 동적 Pad가 생깁니다. pad-added 시그널에서 다음 Element와 링크해야 하는 경우가 많습니다(직선 ! 체인만으로 안 될 때).
  2. Caps 불일치: 샘플레이트·채널·픽셀 포맷이 안 맞으면 audioconvert / videoscale / videoconvert 등으로 중간에 맞춰 줍니다.
  3. 잘못된 URI: file://는 절대 경로 3슬래시(file:///home/...) 형태에 익숙해지세요.
  4. 플러그인 누락: gst-inspect-1.0 요소이름으로 존재 여부를 확인합니다.
gst-inspect-1.0 playbin

마무리

GStreamer는 “Element를 조립해 데이터가 흐르게 만든다”는 한 가지 원리만 잡아도 입문이 쉬워집니다. 실무에서는 playbin / uridecodebin으로 먼저 성공 경로를 만든 뒤, 필요한 지점만 수동 파이프라인으로 바꿔 가는 방식이 안전합니다.

더 나아가려면 Pad 템플릿, 요소 상태 머신, 시각/클럭 동기화, appsrc/appsink로 애플리케이션 버퍼를 직접 붙이는 패턴을 문서와 튜토리얼로 확장해 보세요.


참고

---
... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3