GStreamer User Book #2

본 연재는 SK Telecom의 후원으로 진행하는 “책책책 책을 만듭시다!” 프로그램으로 기획되었으며, 연재 종료 후 도서로 출간될 예정입니다. 연재 내용 중에 저자의 주관적 의견이 노출되는 경우, 해당 의견은 SK Telecom의 의견이 아님을 미리 밝힙니다.

GStreamer 구동 환경

실제로 GStreamer는 멀티 플랫폼을 지향하는 미디어 프레임워크이다. 대부분의 리눅스 배포본에 기본적으로 탑재해있으며, Android와 iOS와 같은 모바일 플랫폼을 위한 SDK를 제공하고 있다. 또한 MacOS X와 Windows에서도 사용이 가능하다.

하지만 이러한 멀티 플랫폼 지원은 GStreamer보다는 뒤에 논의할 GLib에서 각 OS의 의존적인 부분을 추상화하여 제공하기 때문에 가능한 것이고, 이러한 특성 때문에 태생적으로 리눅스에서 가장 편리하게 사용할수 있다. 유닉스 기반의 운영체제인 MacOS X도 리눅스와 유사하게 혹은 리눅스 환경 대비 큰 노력을 들이지 않고도 편리하게 사용할 수있다.

Windows에서는 MinGW(Minialist GNU for Windows)를 이용하여 리눅스에서 사용하던 GNU 도구와 컴파일러를 기반으로 빌드한 SDK를 사용하였으나, GStreamer 버전 1.16부터는 MSVC 기반으로 빌드한 SDK도 같이 제공하고 있다. 하지만 이는 GStreamer SDK이전에 MinGW나 MSVC에 대해 먼저 익숙하다는 전제가 필요하고, 그렇기 때문에 GStreamer를 이용하는 애플리케이션 작성법을 익히기 위한 출발점으로 Windows 환경은 권장하지 않는다. 그렇지만 GStreamer를 구동하는 환경을 정확히 제어하고, GStreamer가 제공하는 라이브러리와 플러그인을 어떻게 애플리케이션과 연결할 것인지를 이해하고 있다면, Windows 환경에서 GStreamer를 이용하는 것 자체가 어려운 일은 아닐 것이다.

본 서적에서는 리눅스 혹은 MacOS X를 사용하는 것을 가정하고 예제를 작성하고 있다.

Release version과 ABI version

리눅스를 연습하기 위한 환경으로 선택했다면, 리눅스 배포본에서 제공하는 패키지 명칭 때문에 GStreamer의 버전 체계에 의문이 생길 수 있다. gstreamer0.10 혹은 gstreamer1.0으로 명명된 패키지들을 볼 수 있는데, 이때 사용하는 숫자는 ABI(Application Binary Interface)에 대한 호환성을 표기하는 버전 체계이다. ABI 버전이 같은 경우에는, 라이브러리와 바이너리에 대한 하위 호환성을 유지한다는 뜻이며, 같은 ABI 버전 내에서 릴리즈 버전을 운영하는 형태로 진행된다. 즉, 현재 릴리즈 버전이 1.16.x이라면, 1.14.x 버전에서 빌드된 플러그인을 새롭게 빌드하지 않고도 사용이 가능하도록 하지만, 0.10.x 버전의 바이너리는 사용하지 못한다. 이러한 호환성을 강조하기 위하여 GStreamer는 각 라이브러리, 플러그인, CLI 도구들에 ABI 버전을 명시하는 정책을 사용하고 있다.

GStreamer 설치하기

최신 동향이나 논의되고 있는 기능들을 활용하고자 한다면 git 저장소에서 소스 코드를 가져와서 빌드하는 것이 맞겠지만, 개발 중인 코드들은 가끔씩 사용자를 당황하게 할때가 많이 있다. 최근에는 CI(Continuous Integration)의 발달로 인해서 master 브랜치에 제출된 코드에 대한 검사를 철저히 하기는 하지만, 어디까지나 빌드와 단위 기능에 대한 테스트까지 수행될 뿐 논리적인 혹은 런타임 오류까지 확인하지 못하기에 특별한 이유가 없다면 Pre-built 패키지를 사용하는 것을 권장한다.

권장 환경

  • GStreamer 1.10 버전 이상
  • Linux
    • 우분투의 경우 18.04 이후
    • 데비안의 경우 stretch
    • CenOS 7 이후
  • Mac OS X (HomeBrew 사용 권장)

Linux

대표적으로 많이 사용되는 우분투, 데비안 같은 경우에는 gstreamer1.0 이라는 패키지 명칭으로 제공되고 있다. 다만 주의할 점은 헤더와 디버깅 심볼들은 별도의 패키지로 제공되기 때문에 GStreamer 기반 사용자 애플리케이션을 작성하기 위해서는 반드시 개발용 패키지를 설치해야한다. 편의상 우분투 패키지 명칭을 통해서 살펴본다면 다음과 같은 패키지들이 필수이다.

  • 런타임 라이브러리 및 실행 파일
    • libgstreamer1.0-0
    • libgstreamer-plugins-base1.0-0
    • libgstreamer-plugins-bad1.0-0
    • gstreamer1.0-plugins-base
    • gstreamer1.0-plugins-base-apps
    • gstreamer1.0-plugins-good
    • gstreamer1.0-plugins-ugly
    • gstreamer1.0-plugins-bad
    • gstreamer1.0-libav
    • gstreamer1.0-tools
  • 개발용 헤더 파일
    • libgstreamer1.0-dev
    • libgstreamer-plugins-base1.0-dev
    • libgstreamer-plugins-good1.0-dev
    • libgstreamer-plugins-bad1.0-dev
  • 디버깅용 심볼 파일
    • libgstreamer1.0-0-dbg
    • gstreamer1.0-plugins-base-dbg
    • gstreamer1.0-plugins-good-dbg
    • gstreamer1.0-plugins-ugly-dbg
    • gstreamer1.0-plugins-bad-dbg
    • gstreamer1.0-libav-dbg

특히 이름이 -dbg로 끝나는 패키지들은 사용자 애플리케이션을 작성하는 도중 gdb와 같은 디버깅 도구로 내부 스택을 추적할 때 반드시 필요하기 때문에 Pre-built 패키지로 개발 환경을 구축할 때에는 반드시 함께 설치해야한다. 런타임, 개발, 디버깅이라는 용도에 따른 패키지를 구분하는 규칙은 다른 리눅스 배포본에서도 유사하다.

MacOS X

MacOS X는 HomeBrew(https://brew.sh/)라는 도구를 통해서 GStreamer를 설치할 수 있다.

$ brew install \
    gstreamer \
    gst-plugins-base \
    gst-plugins-good \
    gst-plugins-ubly \
    gst-plugins-bad \
    gst-libav

GStreamer CLIs

GStreamer가 기본적으로 제공하는 CLI 도구들은 애플리케이션을 작성하기 전에 사용 환경을 점검하고 사용하고자 하는 GStreamer Pipeline 구조를 시뮬레이션 하기 위한 용도로 사용할 수 있다.

gst-inspect-1.0

GStreamer의 상태를 점검하고 개별 플러그인과 Element에 대한 정보를 알려주는 역할을 수행한다.

$ gst-inspect-1.0
osxaudio:  osxaudiodeviceprovider (GstDeviceProviderFactory)
osxaudio:  osxaudiosrc: Audio Source (OSX)
osxaudio:  osxaudiosink: Audio Sink (OSX)
replaygain:  rgvolume: ReplayGain volume
replaygain:  rglimiter: ReplayGain limiter
... (중략) ...

아무런 인자없이 수행을 하게 되면, 현재 환경에서 사용할 수 있는 플러그인 목록을 출력한다. 개별 플러그인을 살펴보고 싶다면, 단순히 해당 플러그인의 이름을 인자로 사용하면 플러그인을 구성하고 있는 Element 리스트를 출력한다.

다음은 coreelements라는 이름의 플러그인 정보를 살펴본 예이다.

$ gst-inspect-1.0 coreelements
Plugin Details:
  Name                     coreelements
  Description              GStreamer core elements
  Filename                 /usr/local/lib/gstreamer-1.0/libgstcoreelements.so
  Version                  1.16.0
  License                  LGPL
  Source module            gstreamer
  Source release date      2019-04-19
  Binary package           GStreamer source release
  Origin URL               Unknown package origin

  capsfilter: CapsFilter
  concat: Concat
  dataurisrc: data: URI source element
  downloadbuffer: DownloadBuffer
  fakesrc: Fake Source
  fakesink: Fake Sink
  fdsrc: Filedescriptor Source
  fdsink: Filedescriptor Sink
  filesrc: File Source
  funnel: Funnel pipe fitting
  identity: Identity
  input-selector: Input selector
  output-selector: Output selector
  queue: Queue
  queue2: Queue 2
  filesink: File Sink
  tee: Tee pipe fitting
  typefind: TypeFind
  multiqueue: MultiQueue
  valve: Valve element
  streamiddemux: Streamid Demux

  21 features:
  +-- 21 elements

플러그인의 이름이 인자로 주어진 경우에는 부수적인 정보들을 확인할 수 있다. 여기서 개발자에게 중요한 것은 절대 경로로 표시된 해당 플러그인의 위치(Filename)와 버전 정보, 그리고 이 플러그인이 속한 모듈의 이름(Binary package)이다. 플러그인의 경로를 통해서 해당 플러그만 삭제하거나 업데이트를 하는 용도로도 사용할 수 있으며, 소속 모듈의 이름을 통해서 레퍼런스를 찾아보기가 쉽기 때문이다.

또한 하단에는 이 플러그인이 가지고 있는 최종 Element 들에 대한 정보를 가지고 있는데, coreelements의 경우에는 21개의 Element를 가지고 있음을 알수 있다. 각 개별 Element의 정보를 확인하기 위해서는 플러그인의 경우와 동일하게 해당 Element의 이름을 인자로 사용하면 된다.

다음은 identity Element에 대한 정보를 확인한 결과이다. 해당 Element의 속성을 표시해주는 것을 확인할 수 있는데, Element에 대한 설명외에도 개발자를 위한 직접적인 정보를 구성하고 있다. 이번 장에서는 각 항목들이 어떻게 구분되고 있는지 살펴보고 생략된 항목들은 이후 챕터에서 상세하게 다룰 예정이다.

$ gst-inspect-1.0 identity
Factory Details:
  Rank                     none (0)
  Long-name                Identity
  Klass                    Generic
  Description              Pass data without modification
  Author                   Erik Walthinsen <omega@cse.ogi.edu>

Plugin Details:
  Name                     coreelements
  Description              GStreamer core elements
  Filename                 /usr/local/lib/gstreamer-1.0/libgstcoreelements.so
  Version                  1.16.0
  License                  LGPL
  Source module            gstreamer
  Source release date      2019-04-19
  Binary package           GStreamer source release
  Origin URL               Unknown package origin

GObject
 +----GInitiallyUnowned
       +----GstObject
             +----GstElement
                   +----GstBaseTransform
                         +----GstIdentity

Pad Templates:
...(중략)...

Pads:
...(중략)...

Element Properties:
...(중략)...

Element Signals:
...(중략)...

GObject를 최상위 부모로 표현한 트리는 해당 Element가 어떠한 역할을 하는지 유추할 수 있게 하는 가장 간단한 정보 중에 하나이다. identity의 경우 GstBaseTransform을 상속하고 있음을 알수 있고, 이는 입력과 출력이 있다는 것으로 해석할 수 있다.

Pad Templates는 이 Element가 생성하는 Pad들이 어떤 스키마를 가지고 생성하는지 알려주는 역할을 하며, Pads 항목은 Pad를 생성할때 사용하는 이름 규칙과 어떠한 Pad template과 관련이 있는지를 알려주는 역할을 한다. 이 정보를 이용하여 Element 간 연결이 가능한지를 확인할 수 있고 gst-launch-1.0을 이용하여 Pipeline을 어떻게 구성할시 시뮬레이션을 할수 있다.

Element Properties는 Element가 가지고 있는 속성 값을 표현하며, 해당 속성의 타입, 읽기/쓰기 여부, 사용할 수 있는 값의 범위 등을 별도의 API를 사용하지 않고도 알수 있게 한다.

Element Signals는 Element에서 외부로 어떠한 이벤트를 보내는지 혹은 외부에서 어떻게 Element 내부의 함수를 호출할 수 있는지를 기술하고 있으며, Signal의 경우 제공하지 않는 Element도 많이 존재한다.

gst-inspect-1.0의 또 다른 숨겨진 기능 중에 하나는 플러그인 파일을 직접 분석하는 기능이다. 예를들어 coreelements는 위 정보에 따르면 /usr/local/lib/gstreamer-1.0/libgstcoreelements.so 파일로 제공되고 있는데, 해당 파일을 직접 읽어서 정보를 출력해주는 역할을 한다.

$ gst-inspect-1.0 /usr/local/lib/gstreamer-1.0/libgstcoreelements.so

위 명령의 결과는 gst-inspect-1.0 corelements를 사용했을 때와 동일한데, 내부적으로는 플러그인 파일 자체를 읽어서 정보를 표시해주는 것이기 때문에, 다른 경로에 설치된 동일한 이름의 플러그인을 제거한다던가, 실수로 동일한 이름으로 정의된 Element를 찾아야 하는 경우에 유용하게 사용할 수 있다.

또한 gst-inspect-1.0은 제공된 파일이 GStreamer 플러그인이 아닌지를 판별하는데 사용할수도 있다.

$ gst-inspect-1.0 /usr/local/lib/libgstreamer-1.0.0.dylib
Could not load plugin file: File "/usr/local/lib/libgstreamer-1.0.0.dylib" is not a GStreamer plugin

의외로 GStreamer를 이용한 애플리케이션 개발 프로젝트가 길어지는 경우에 이런 문제는 자주 발생하게 된다. 주요 원인 중에 하나는 이전 버전의 플러그인과 현재 버전의 플러그인의 동작성을 테스트하면서 Pre-built 패키지들이 설치된 공간을 편의상 패키지 관리자가 아닌 수동으로 교체하고 복원하는 것을 잊어버리는 경우인데, 그런 경우 대게 시스템이 꼬였다라고 하며 OS부터 다시 설치하는 것으로 마무리 짖곤 한다. 물론 이러한 문제는 GStreamer 환경 변수를 정교하게 조작하여 애초부터 방지할수는 있겠지만, 이미 문제가 발생한 경우, 단순한 플러그인 파일 중복이나 버전 호환성 문제 때문이라면 gst-inspect-1.0 도구로 해결할 확률이 매우 높다.

gst-launch-1.0

GStreamer는 Pipeline 구조를 통해서 데이터를 전달하고 가공하는 역할을 수행한다. 애플리케이션을 제작하기 위해서는 Pipeline 내에 적절한 Element를 배치하는 작업을 먼저 수행하게 되는데, 구상한 전체 Pipeline을 테스트하거나, Pipeline 내의 일부 구간에 대해서 동작을 확인하는 용도로 사용할 수 있는 도구이다.

다음은 gst-launch-1.0으로 w3.org에서 제공하는 영상을 playbin을 통해서 재생하는 명령이다. playbin은 GStreamer에서 Playback을 지원하기 위하여 작성한 Element로 내부에서는 소스 타입부터 화면출력까지 현재 시스템에서 사용할 수 있는 최적의 Element를 찾아 Pipeline을 구성하는 기능을 수행한다.

$ gst-launch-1.0 playbin uri=https://media.w3.org/2010/05/sintel/trailer.mp4
Setting pipeline to PAUSED ...
Pipeline is PREROLLING ...
Got context from element 'source': gst.soup.session=context, session=(SoupSession)NULL, force=(boolean)false;
Got context from element 'sink': gst.gl.GLDisplay=context, gst.gl.GLDisplay=(GstGLDisplay)"\(GstGLDisplayCocoa\)\ gldisplaycocoa0";
Redistribute latency...
Got context from element 'playsink': gst.gl.GLDisplay=context, gst.gl.GLDisplay=(GstGLDisplay)"\(GstGLDisplayCocoa\)\ gldisplaycocoa0";
Redistribute latency...
Prerolled, waiting for buffering to finish...
Pipeline is PREROLLED ...
Setting pipeline to PLAYING ...
New clock: GstAudioSinkClock

playbin 재생 화면 캡쳐

playbin은 GStreamer에서 제공하는 Element 중에도 State of the Art로 여겨질만큼 많은 노력의 결정체이다. 실제로 위에 주어진 미디어를 재생하기 위하여 다음과 같이 복잡한 Pipeline을 자동으로 생성하고있다.

playbin dot graph

gst-launch-1.0의 막강한 기능은 Element를 임의로 나열할 수 있다는 것이다. 다음은 테스트용 비디오 소스를 통해서 화면으로 출력하는 간단한 Pipeline이다.

$ gst-launch-1.0 videotestsrc ! autovideosink

위 Pipeline에 약간의 기능을 추가하여, 화면 크기를 조절하고, 재생 시간 정보를 표시하려면 다음과 같은 Pipeline을 사용할 수 있다.

$ gst-launch-1.0 \
    videotestsrc ! video/x-raw,width=320,height=240 ! \
    timeoverlay halignment=center valignment=center font-desc="Sans, 32" ! \
    autovideosink

위의 예제를 변형하여 화면 출력과 동시에 레코딩을 수행한다면 tee Element를 이용하여 스트림을 분기할 수 있다.

gst-launch-1.0 \
     videotestsrc ! video/x-raw,width=320,height=240 ! \
     timeoverlay halignment=center valignment=center font-desc="Sans, 32" ! \
     tee name=t \
     t. ! queue ! autovideosink \
     t. ! queue ! theoraenc bitrate=256 ! oggmux ! filesink location=test.ogg

위 Pipeline을 구동하면, 윈도우에 보이는 화면그대로 test.ogg 파일로 저장되어있음을 알 수 있고, 또다시 gst-launch-1.0 playbin uri=file://<절대경로>/test.ogg 를 이용하여 재생이 가능함을 확인할 수 있다.

gst-typefind-1.0

미디어 정보를 처리하는데 있어서 가장 어려운 부분중에 하나는 다양한 코덱과 다양한 포맷의 파일들을 다룰 수 있어야 한다는 것이다. GStreamer는 파일의 헤더 혹은 바이트 패턴을 분석해서 주어진 파일의 포맷을 추측하는 기능을 제공하는데, gst-typefind-1.0을 이용하여 해당 기능을 사용해볼 수 있다.

도구 자체로는 특별한 입력이 필요없기 때문에, 로컬에 준비된 미디어 파일을 인자로 사용하여 테스트 해볼 수 있다. 본 예제에서는 앞서 소개했던 w3c의 HTML5 Video Events and API 테스트 페이지에서 사용하고 있는 컨텐츠를 미리 다운로드 하여 사용하는 것으로 가정하였다.

$ wget https://media.w3.org/2010/05/sintel/trailer.mp4

gst-typefind-1.0에 파일 경로를 입력하여 실행하면 다음과 같이 주어진 파일의 포맷과 속성을 결과로 얻을 수 있다.

$ gst-typefind-1.0 ./trailer.mp4
./trailer.mp4 - video/quicktime, variant=(string)iso

파일의 확장자(여기서는 .mp4)를 이용하여 파일 포맷을 추정하는 것이 가장 간단하겠지만, GStreamer는 앞서 기술한 것처럼 내부 바이트 패턴을 분석하기 때문에 파일의 이름을 변경한다하더라도 동일한 결과를 얻을 수 있다.

$ mv trailer.mp4 dummy
$ gst-typefind-1.0 ./dummy
./dummy - video/quicktime, variant=(string)iso

사실 gst-typefind-1.0core 모듈의 typefind Element를 활용한 아주 간단한 Pipeline을 활용한 도구이기 때문에, 동일한 정보를 앞서 기술한 gst-launch-1.0의 디버깅 메시지를 통하여 얻을 수 있다. 타입 정보는 typefind Element 내부에 디버그 메시지 레벨 4에서 출력하기 때문에 --gst-debug=typefind:4를 인자로 주어 메시지를 확인할 수 있다.

$ gst-launch-1.0 \
    filesink location=./dummy ! typefind ! fakesink \
    --gst-debug=typefind:4
Setting pipeline to PAUSED ...
Pipeline is PREROLLING ...
0:00:00.042075000 18827 0x7f9c40019b70 INFO                typefind gsttypefindelement.c:850:gst_type_find_get_extension:<typefindelement0> could not find uri extension in file:///Users/justinkim/git/gst-build/dummy
0:00:00.048977000 18827 0x7f9c40019b70 INFO                typefind gsttypefindelement.c:181:gst_type_find_element_have_type:<typefindelement0> found caps video/quicktime, variant=(string)iso, probability=100
Pipeline is PREROLLED ...
Setting pipeline to PLAYING ...
New clock: GstSystemClock
Got EOS from element "pipeline0".
Execution ended after 0:00:00.004914000
Setting pipeline to PAUSED ...
Setting pipeline to READY ...
Setting pipeline to NULL ...
Freeing pipeline ...

그렇다면, typefind Element가 인식할 수 있는 파일 타입은 얼마나 될까? 엄밀히 말하자면, 사용자가 원하는 모든 포맷을 인식할 수 있다. 내부적으로는 typefind Element는 GstTypeFindFactory의 정의에 맞게 구현된 함수를 등록하여 호출하는 방식을 취하고 있기 때문에 현재 GStreamer가 인식하지 못하는 패턴일지라도 GStreamer 모듈을 수정하지 않고도 사용자 애플리케이션에서 몇몇 함수를 정의하고 등록하는 것만으로 typefind가 사용자가 정의한 파일을 인식할 수 있게 된다. 이에 대해서는 이후 GStreamer 애플리케이션을 작성하는 예제에서 상세하게 다룰 예정이다.

gst-discoverer-1.0

gst-typefind-1.0은 해당 파일의 포맷을 분석하는 역할에 그쳤다면, gst-discoverer-1.0은 미디어 정보를 추출하는 역할을 한다. 내부적으로 Pipeline을 구성하여 정보를 추출하고 있으며, 비디오와 오디오 그리고 부수적인 데이터 정보까지 분석하기 때문에, uridecodebin을 사용하고 있다. 이 때문에 gst-plugins-base 모듈에서 구현하고 있고, gst-typefind-1.0과 다르게 URI를 통해서 소스를 제공할 수 있다.

$ gst-discoverer-1.0 https://media.w3.org/2010/05/sintel/trailer.mp4
Analyzing https://media.w3.org/2010/05/sintel/trailer.mp4
Done discovering https://media.w3.org/2010/05/sintel/trailer.mp4

Topology:
  container: Quicktime
    audio: MPEG-4 AAC
    video: H.264 (High Profile)

Properties:
  Duration: 0:00:52.209000000
  Seekable: yes
  Live: no
  Tags:
      audio codec: MPEG-4 AAC audio
      maximum bitrate: 128000
      datetime: 1970-01-01T00:00:00Z
      title: Sintel Trailer
      artist: Durian Open Movie Team
      copyright: (c) copyright Blender Foundation | durian.blender.org
      description: Trailer for the Sintel open movie project
      encoder: Lavf52.62.0
      container format: ISO MP4/M4A
      video codec: H.264 / AVC
      bitrate: 535929

또한 미디어 파일의 정보를 추출하기 위해서 gst-discoverer-1.0은 재생이 가능한 온전한 Pipeline을 사용한다. Demuxer와 Decoder를 모두 사용하게 되며, 미디어 파일에 얻을 수 있는 메타데이터들을 수집하여 출력하고 있다. 다만 영상과 음성 출력을 목적으로 하는 것이 아니기 때문에 디코딩을 수행한 이후 발생한 데이터를 fakesink를 이용하여 버리게 된다.

다음은 실제로 위에 사용한 영상 파일을 분석하는데 사용한 Pipeline을 캡쳐한 이미지이다.

discoverer dot graph

Pipeline 내부에서 사용하는 정보들을 추출하는 것이기 때문에, gst-launch-1.0을 이용해서도 동일한 정보들을 추출할 수 있다. -t 옵션을 사용하게 되면 메타데이터를 출력하게 되는데, gst-discoverer-1.0이 출력하는 정보와 동일함을 알수 있다. 아래의 예제는 gst-discoverer-1.0이 사용하는 동일한 Pipeline을 이용하여 gst-launch-1.0에서 실행한 경우이다.

아래 예제에서 볼 수 있듯이 메타데이터(tag)는 중복해서 나타날 수 있고, gst-launch-1.0 명령 자체에는 동적으로 트랙을 구성하는 기능이 없기 때문에, 미디어 파일의 트랙 정보를 알고 있는 경우에만 Pipeline을 구성할 수 있다.

$ gst-launch-1.0 -t \
    uridecodebin uri=https://media.w3.org/2010/05/sintel/trailer.mp4 name=u \
    u. ! queue ! fakesink \
    u. ! queue ! fakesink
Setting pipeline to PAUSED ...
Pipeline is PREROLLING ...
Got context from element 'source': gst.soup.session=context, session=(SoupSession)NULL, force=(boolean)false;
Redistribute latency...
FOUND TAG      : found by element "fakesink1".
     audio codec: MPEG-4 AAC audio
 maximum bitrate: 128000
FOUND TAG      : found by element "fakesink1".
        datetime: 1970-01-01 00:00:00 (UTC)
           title: Sintel Trailer
          artist: Durian Open Movie Team
       copyright: (c) copyright Blender Foundation | durian.blender.org
     description: Trailer for the Sintel open movie project
         encoder: Lavf52.62.0
container format: ISO MP4/M4A
FOUND TAG      : found by element "fakesink1".
     audio codec: MPEG-4 AAC audio
 maximum bitrate: 128000
 ... (중략) ...

GStreamer User Book #1

본 연재는 SK Telecom의 후원으로 진행하는 “책책책 책을 만듭시다!” 프로그램으로 기획되었으며, 연재 종료 후 도서로 출간될 예정입니다. 연재 내용 중에 저자의 주관적 의견이 노출되는 경우, 해당 의견은 SK Telecom의 의견이 아님을 미리 밝힙니다.

GStreamer 소개

GStreamer는 미디어 정보를 처리하기 위한 소프트웨어 집합, 즉 미디어프레임워크이며, 가장 신사적인 오픈 소스 프로젝트 커뮤니티이기도 하다. 미디어 프레임워크로서 GStreamer는 사용자 애플리케이션이 영상, 음성 및 데이터를 제한된 컴퓨팅 자원과 주어진 시간 내에서 처리할 수 있도록 도와주는 역할을 수행한다. 이미 개발된 표준안과 기술들에 대해서는 소프트웨어적인 신뢰성을 제공하고,새로운 표준과 기술들에 대해서는 빠르게 적용하고 최적화할 수 있는 기본 토대를 마련해주는 것을 목표로 하고 있다.

GStreamer의 시작은 매우 단순하지만 소프트웨어 프레임워크들이 해결하고자 하는 공통적인 문제인, 코드의 재 사용성과 추상화된 계층 구조에 대한 요구 사항으로 부터 출발한다. 1999년 이전의 상황으로 잠시 돌아가서 생각해본다면, MP3 파일을 재생하는 사용자 애플리케이션 작성을 위해서는 MP3 디코더부터 구현해야만 했다. 성공적으로 MP3 재생기를 구현하였다고 하더라도, 다른 애플리케이션으로 확장을 하기 위하여 디코더 부분만을 재사용한다거나, 새로운 코덱을 지원하도록 기능을 추가한다던가 하는 작업은 온전히 소프트웨어를 구현하는 엔지니어의 역량에 의존할 수 밖에 없는 상황이었다.

그 당시에 사용할 수 있는, 재사용이 가능한 디코더 라이브러리, 혹은 미디어 재생기와 같은 프로그램은 1996년부터 사용가능 했던 Microsoft의 DirectShow나 그 이전에는 1991년부터 Apple이 공급한 QuickTime 정도에 지나지 않았으며, 특히 리눅스와 같은 환경에서 사용가능한 미디어 정보 처리를 위한 소프트웨어 라이브러리 혹은 애플리케이션은 전무한 상태였다. 당시의 설계자들이 해결하고자 했던 또다른 문제는 단순한 기능적인 요구사항뿐 아니라, 지속적인 발전이 가능한 라이센스 모델을 찾는 것이었다. VLC의 경우 GPL을 사용하여 프로젝트 공헌자들의 아이디어를 다시 오픈 소스 커뮤니티로 강제로 환원하는 정책을 선택하였으며, GStreamer는 그와 달리, 산업에서 사용할 때 보다 자유로운 LGPL을 채택함으로써 임베디드 시스템에서도 사용이 가능한 미디어프레임워크로 발전하게 되었다.

미디어프레임워크 주요 릴리즈 연도

GStreamer 구성 요소

GStreamer를 간단히 정의한다면, 플러그인들로 구성된 모듈의 집합이라고 할 수 있다. 일반적으로 인식하고 있는 모듈과 플러그인의 정의와 약간 다를 수는 있으나, GStreamer 커뮤니티에서는 모듈을 소스 코드 저장소로 정의하고 있고, 각 저장소는 기능을 모아둔 라이브러리와 플러그인으로 구성되어있다. 기능을 정의하기 위한 단위는 Element이며 하나의 기능을 구현하기 위한 기본 단위는 Element와 Pad이다. 각 Element는 다수의 Pad를 생성할 수 있고, Pad의 속성에 따라서 Element를 연결할 수 있는지가 결정된다.

각 Element를 연결하여 정보의 흐름을 제어하는 아이디어는 DirectShow에서 차용하였으나, GStreamer 프로젝트는 C 언어 기반으로 작성된 완전히 다른 미디어 프레임워크이다. 조금 더 덧붙이자면 형식적으로는 C 언어 기반으로 작성되었다고 말할 수 있으나, 정작 내부 구조는 너무나 철저하게도 객체지향적이기 때문에, 명확한 정의는 아니라고 할 수 있다. 이는 GStreamer의 기본 단위가 GObject에서 파생되었기 때문이며, 보충 설명을 위하여 연재의 한 챕터를 GObject를 설명하는데 할애하려고 한다.

GstObject 클래스 계층 구조

GStreamer 모듈

플러그인으로 나뉘어진 각 기능들을 전략적으로 관리하기 위하여 GStreamer는 다수의 모듈, 즉 저장소를 관리하고 있다. 사실 GStreamer SDK를 이용하여 애플리케이션을 작성한다고 하면 모듈 내부의 소스 코드들을 직접 분석할 필요는 없다. 그러나 단위 기능이나 Element에 대한 테스트와 예제를 tests 디렉토리에서 찾을 수 있기 때문에 코드 작성중에 올바른 사용 방법 혹은 불확실성에 대한 확신을 얻고자 한다면 SDK를 사용한다고 하더라도 저장소 코드는 직접 살펴보는 것이 검색 엔진을 통한 질의 보다 효율적이다.

gstreamer

커뮤니티에서는 core 라고 부르기도 하며, GStreamer를 구동하기 위한 필수 도구와 라이브러리를 제공한다. 특히 gst-launch, gst-inspect 처럼, CLI로 제공되는 도구들은 GStreamer의 기본적인 동작과 디버깅 기법을 이해하는데 매우 큰 도움이 된다.

gst-plugins-base

이름에서 유추할 수 있는 것처럼, 기본 라이브러리와 플러그인을 제공한다. 기능들에 대한 동작성이 철저하게 검토된 플러그인들을 관리하는 공간이며, base 모듈 내에서 버그 혹은 다른 어떠한 이슈가 발견된다면, 매우 즉각적으로 대응을 하고 있다. OpenGL을 통한 렌더링, CPU 기반의 오디오 및 비디오 처리 기능을 담당하는 Element들을 포함하고 있으며 gst-play 와 같은 High-Level Playback 기능을 사용할 수 있는 CLI를 제공하는 공간이기도 하다.

gst-plugins-good

사용자 애플리케이션을 작성하는 경우, 보통은 core, base, good 이 세 모듈을 기반으로 작성할 수 있다. 특히 임베디드 시스템에 탑재하는 경우 발생할 수 있는 라이센스의 충돌이나 의도치 않는 정책들에 대해서 비교적 자유로운 코드들이 good 모듈에 탑재된다. 기능 상으로도 누구나 좋다고 인정하는 수준의 동작성, 코드 상의 무결성 등이 검토된 라이브러리와 플러그인들이 위치한다. RTSP와 관련된 Element들이 대표적이며, V4L2 (Video for Linux) API를 이용한 기능들도 이곳에 있다.

gst-plugins-ugly

이제 이 모듈부터는 상용 소프트웨어를 제작하는데 주의가 필요하다고 보면 되는 공간이다. 모듈의 이름이 ugly이지만, 소스 코드의 품질이나 기능 동작 상태에 관련된 명칭은 아니며, 대체적으로 기능적으로는 잘 동작하지만 라이센스에 대해서 검토가 필요할 수도 있다는 정도의 의미로 해석하는 것이 좋다. RealMedia나 ASF에 대한 지원이 대표적인 기능이며, 다른 모듈과 비교하여 활동량이 적은 공간이다.

gst-plugins-bad

GStreamer 컨트리뷰터가 되고 싶지만 시작점을 찾기 어렵다거나, 새로운 플러그인을 제안하고 제작하는 것을 고려하고 있다거나, 최신 동향을 분석하기 위해서는 절대적으로 익숙해져야하는 모듈이 bad이다. ugly라는 모듈명을 정의할때 사용한 뉘앙스처럼, bad 또한 무언가 나쁜것이 있는 공간이다라고 해석해서는 안된다. 기능적으로 동작하고 검증되었지만 누군가 더 사용해서 유즈 케이스를 확보하고 싶다거나, 자신이 설계한 기능에 대한 여러 사용자들의 동의를 얻기 위한다던가, 아니면 라이센스에 대해서 검토가 이루어지지 않은 코드들에 대한 공간으로 인식하는 것이 바람직하다. 그렇기 때문에 새롭게 제작한 플러그인이 있다면 이 모듈을 타겟으로 제출하면 된다. 대표적으로 안드로이드와 iOS를 지원하는 라이브러리와 Element가 이 공간에 위치한다.

기타 저장소

core, base, good, ugly, bad는 GStreamer를 구성하는 모듈이자 저장소이지만, GStreamer 프로젝트는 이외에도 다수의 저장소로 구성되어 있다. 특정한 프로그래밍 언어를 지원하기 위한 바인딩 프로젝트와 SDK 빌드를 위한 저장소, 그리고 레퍼런스로 사용할 수 있는 예제들이 별도의 저장소에서 운영되고 있다.

  • 프로그래밍 언어 바인딩 - gst-python, gstreamer-rs, gstreamer-sharp
  • 코덱과 필터 - gst-libav
  • RTSP 서버 - gst-rtsp-server
  • 레퍼런스 애플리케이션 - gst-examples
  • SDK 빌드 - cerbero
  • GStreamer 모듈 빌드 - gst-build

GStreamer 아키텍쳐

벌써 GStreamer에 대한 필수 구성 요소들에 대한 이야기들을 많이 했지만, 정작 그들간의 관계와 어떻게 미디어 정보를 처리할 수 있는지에 대한 이야기는 아직 하지 못했다. GStreamer 아키텍쳐를 계층별 흐름대로 설명하자면, 오브젝트 단위로 구성된 (Element로 구성된) 기능들을 플러그인으로 만들고, 그 플러그인들을 런타임에 동적으로 사용할 수 있도록 하는 기능을 가진 core 프레임워크 위에 애플리케이션을 만들 수 있다.

앞서 잠깐 언급한 CLI 도구들도 GStreamer를 사용한 애플리케이션이며, 다른 형태의 미디어 처리기를 제작할 때에도 GStreamer 프레임워크를 사용할 수 있다. 다만 어떠한 플러그인을 사용할지, 어떠한 Element를 사용할지는 전적으로 GStreamer core가 담당하게 된다. 좀더 프로그래머의 언어로 설명을 하자면, 헤더 파일을 통해서 접근하고 링크를 만들 수 있는 함수는 core까지이며, 각각의 플러그인이나 Element에는 함수 단위의 접근이 불가능하다. 뒤에서 좀더 자세하게 다루겠지만, Element 단위의 접근을 하기 위해서는 Property나 Singal이 유일한 경로이며, 이 또한 Element가 명시적으로 제공하지 않는한 애플리케이션 계층에서 Element를 직접적으로 제어할 수 있는 방법은 없다. 또한 core는 가장 중요한 개념인 Pipeline을 관리할 수 있는 기능을 포함하며, 애플리케이션과 Pipeline 간의 대화가 가능한 채널인 Bus를 제공한다. 즉, core는 각 플러그인과 Element에 대한 접근을 은폐하고 추상화된 제어함수를 제공하게 되는 것이다.

GStreamer 아키텍쳐

플러그인 계층은 특수한 형태로 제작된 라이브러리의 집합이다. GStreamer는 기본적으로 런타임에 관리가 가능한 동적 로딩 방식으로 플러그인을 관리하고 있으며, 이 플러그인들은 파일 시스템 기준으로 본다면 특수하게 제작된 동적 라이브러리들이다. 그렇기 때문에 제작된 플러그인은 반드시 소스코드로 배포될 필요는 없고, 플러그인 제작자가 의도한다면 바이너리 형태의 배포가 가능하다. 이러한 플러그인 로딩 방식은 지적 재산권을 보호하기 위한 방편으로도 사용할 수 있다. 실제로 많은 SoC 벤더들이 하드웨어 코덱이나 보안을 강화한 플러그인을 배포할때 바이너리 배포 방식을 사용하고 있다.