Basic tutorials 2: GStreamer concepts
l 목표
이전 튜토리얼에선 파이프라인을 자동으로 만드는 방법을 알 수 있었다. 이번엔 수동으로 만들어 볼 것이다. 각각의 엘리먼트를 만들고 연결하면 된다. 알게 될 내용을 정리한다.
- GStreamer 엘리먼트란 무엇이고 어떻게 만드는가?
- 엘리먼트를 서로 어떻게 연결하는가?
- 엘리먼트의 동작을 내 마음대로 바꾸는 방법은?
- 버스를 통해 에러 상태를 감시하고 메시지에서 정보를 추출하는 방법은?
l 수동으로 작성한 Hello world
아래 코드를 복사하고 basic-tutorial-2.c 파일을 만들어 붙여 넣는다.
#include <gst/gst.h> int main(int argc, char** argv) { GstElement* pipeline, * source, * sink; GstBus* bus; GstMessage* msg; GstStateChangeReturn ret; /* initialize gstreamer */ gst_init(&argc, &argv); /* create elements */ source = gst_element_factory_make("videotestsrc", "source"); sink = gst_element_factory_make("autovideosink", "sink"); /* create the empty pipeline */ pipeline = gst_pipeline_new("test-pipeline"); if (!pipeline || !source || !sink) { g_printerr("Not all elements could be created.\n"); return -1; } /* build the pipeline */ gst_bin_add_many(GST_BIN(pipeline), source, sink, NULL); if (gst_element_link(source, sink) != TRUE) { g_printerr("Elements could not be linked.\n"); gst_object_unref(pipeline); return -1; } /* modify the source's properties */ g_object_set(source, "pattern", 0, NULL); /* start playing */ ret = gst_element_set_state(pipeline, GST_STATE_PLAYING); if (ret == GST_STATE_CHANGE_FAILURE) { g_printerr("Unable to set the pipeline to the playing state.\n"); return -1; } /* 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); /* parse message */ 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("Error received from element %s: %s\n", GST_OBJECT_NAME(msg->src), err->message); g_printerr("Debugging information: %s\n", debug_info ? debug_info : "none"); g_clear_error(&err); g_free(debug_info); break; case GST_MESSAGE_EOS: g_print("End-Of-Stream reached.\n"); break; default: /* we should not reach here because we only asked for errors and eos */ g_printerr("Unexpected message received.\n"); break; } gst_message_unref(msg); } /* free resources */ gst_object_unref(bus); gst_element_set_state(pipeline, GST_STATE_NULL); gst_object_unref(pipeline); return 0; } |
l 상세 설명
엘리먼트는 GStreamer의 기본 구성 블록이다. 데이터는 source 엘리먼트(데이터 생산자)에서 filter 엘리먼트를 지나 sink 엘리먼트(데이터 소비자) 방향으로 흐른다.
l 엘리먼트 생성
GStreamer 초기화는 이전 튜토리얼과 중복되는 내용이므로 생략한다.
/* create elements */ source = gst_element_factory_make("videotestsrc", "source"); sink = gst_element_factory_make("autovideosink", "sink"); |
코드에서 보이듯 gst_element_factory_make() 함수를 사용해서 새 엘리먼트를 생성한다. 첫 번째 함수 인자는 생성할 엘리먼트의 타입이다. (Basic tutorial 14: Handy elements에서 몇까지 주로 사용하는 엘리먼트 타입을 다룬다. Basic tutorial 10: GStreamer tools에선 사용할 수 있는 모든 엘리먼트 목록을 확인할 수 있다.) 두 번째 인자는 생성한 엘리먼트 객체에 부여하고자 하는 이름이다. 엘리먼트의 이름을 지음으로써 포인터를 따로 저장하지 않은 엘리먼트를 나중에 찾을 수 있다. (또한, 보다 의미 있는 디버그 출력을 확인할 수 있다.) NULL을 입력할 경우엔 GStreamer가 임의로 유일한 이름을 지어준다.
이 예제에선 “videotestsrc”와 “autovideosink” 두 엘리먼트를 만들었다. 필터 엘리먼트는 만들지 않았다. 따라서 파이프라인은 다음과 같다.
videotestsrc는 소스 엘리먼트로(데이터를 생산한다.) 테스트 비디오 패턴을 만든다. 이 엘리먼트는 디버깅이나 테스트 용도로 유용하게 사용된다.
autovideosink는 싱크 엘리먼트이다.(데이터를 소비한다.) 전달받은 이미지를 윈도우에 표시한다. 운영체제에 따라서 다양한 종류의 비디오 싱크가 있는데 autovideosink는 자동으로 가장 적절한 것을 찾아서 객체화한다. 따라서 어떤 비디오 싱크를 생성할 지 고민해서 구체적으로 명시할 필요가 없고 플랫폼에 독립적인 코드를 작성할 수 있다.
l 파이프라인 생성
/* create the empty pipeline */ pipeline = gst_pipeline_new("test-pipeline"); |
GStreamer의 모든 엘리먼트는 사용되기 전에 반드시 하나의 파이프라인에 포함되어야 한다. 왜냐하면, 파이프라인이 시간 조절이나 메시지 관련 함수를 관리하기 때문이다. 예제에선 파이프라인을 만들기 위해 gst_pipeline_new()함수를 사용했다.
/* build the pipeline */ gst_bin_add_many(GST_BIN(pipeline), source, sink, NULL); if (gst_element_link(source, sink) != TRUE) { g_printerr("Elements could not be linked.\n"); gst_object_unref(pipeline); return -1; } |
파이프라인은 bin의 일종이다. bin은 다른 엘리먼트를 포함하는 엘리먼트이다. 때문에 bin에 사용할 수 있는 모든 함수는 파이프라인에도 사용할 수 있다.
예제의 경우에 gst_bin_add_many()함수가 파이프라인에 엘리먼트를 넣는데 사용되었다. 이 함수는 추가할 엘리먼트의 목록을 인자로 받고 마지막에 NULL을 받는다. 각 엘리먼트는 gst_bin_add()함수를 써서 따로따로 추가할 수도 있다.
하지만 파이프라인에 추가한 엘리먼트들이 아직까지 서로 연결된 것은 아니다. gst_element_link()함수를 사용해서 엘리먼트들을 서로 연결한다. 이 함수는 첫 번째 인자는 소스 엘리먼트이고 두 번째 인자는 싱크 엘리먼트이다. 인자의 순서는 중요하다. 데이터의 흐름이 소스 엘리먼트에서 싱크 엘리먼트 방향으로 결정되기 때문이다. 동일한 bin에 들어있는 엘리먼트끼리만 연결될 수 있다는데 주의한다. 연결하기 전에 파이프라인에 엘리먼트들을 추가해야한다.
l 속성
GStreamer의 모든 엘리먼트는 GObject의 일종이다. GObject는 속성(property) 기능을 제공한다.
대부분의 엘리먼트는 속성을 가지며 엘리먼트의 행동을 바꾸기 위해 속성 값을 바꿀 수 있다. (값을 쓸 수 있는;writable 속성) 또는 속성을 통해 엘리먼트의 내부 상태 값을 읽을 수도 있다. (읽을 수 있는;readable 속성)
속성을 읽는 데는 g_object_get()함수를 사용하고 쓰는 데는 g_object_set()함수를 쓴다.
g_object_set()함수는 속성 이름, 값 쌍의 리스트를 인자로 받고 마지막에 NULL을 인자로 받는다. 덕분에 복수개의 속성을 한 번의 함수 호출로 설정할 수 있다.
속성을 다루는 함수들이 “g_” 접두사를 사용하는 이유이다.
예제에서 사용예를 찾아보면 아래와 같다.
/* modify the source's properties */ g_object_set(source, "pattern", 0, NULL); |
위 구문은 videotestsrc 엘리먼트의 “pattern” 속성 값을 0으로 바꾸고 있다. 엘리먼트가 출력하는테스트 비디오의 타입을 제어하는 속성이다. 한 번 다른 값으로 바꿔도 보자.
엘리먼트가 제공하는 모든 속성의 이름과 변경 가능한 값의 범위 등 정보는 gst-inspect-1.0 도구를 써서 찾을 수 있다. 또는, 사용할 엘리먼트의 문서를 보고 찾을 수도 있다.
l 에러 확인
이제까지 전체 파이프라인을 수동으로 구성하고 설정하였다. 남은 부분은 이전 튜토리얼과 비슷하다. 하지만 에러 확인하는 부분이 약간 추가되었다.
/* start playing */ ret = gst_element_set_state(pipeline, GST_STATE_PLAYING); if (ret == GST_STATE_CHANGE_FAILURE) { g_printerr("Unable to set the pipeline to the playing state.\n"); return -1; } |
이전과 같이 gst_element_set_state()함수를 호출했다. 하지만, 이번엔 에러 확인을 위해 반환 값을 추가로 확인한다. 상태를 변경하는건 전용 프로세스에서 수행된다. 보다 자세한 내용은 Basic tutorial 3: Dynamic pipelines를 참조한다.
/* 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); /* parse message */ 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("Error received from element %s: %s\n", GST_OBJECT_NAME(msg->src), err->message); g_printerr("Debugging information: %s\n", debug_info ? debug_info : "none"); g_clear_error(&err); g_free(debug_info); break; case GST_MESSAGE_EOS: g_print("End-Of-Stream reached.\n"); break; default: /* we should not reach here because we only asked for errors and eos */ g_printerr("Unexpected message received.\n"); break; } gst_message_unref(msg); } |
gst_bus_timed_pop_filtered() 함수는 실행 동안 대기했다가 종료되면 GstMessage를 반환한다. 우리는 gst_bus_timed_pop_filtered() 함수를 호출할 때 EOS나 Error가 발생하면 함수가 반환되도록 요청했다. 따라서, 반환된 값이 둘 중 무엇인지 확인하고 화면에 메시지를 출력할 필요가 있다. (아마 실제 앱에선 단순 출력이 아니라 더 복잡한 처리를 하게 될 것이다.)
GstMessage는 다양한 목적으로 사용할 수 있는 구조체이다. 어떤 종류의 정보든지 전달할 수 있다. 다행히도 GStreamer에서 GstMessage를 파싱할 수 있도록 몇 가지 함수를 제공한다.
예제에서는 GST_MESSAGE_TYPE()을 사용해서 GstMessage가 에러를 포함하고 있는지 확인하고 gst_message_parse_error() 함수를 사용한다. 이 함수는 GLib의 GError 구조체와 디버깅에 유용한 문자열을 반환한다. 사용되었다는 걸 알 수 있다. 이후에 이 GError 구조체와 문자열을 어떻게 사용하고 해제하는지 코드를 살펴보자.
l 버스
이즈음해서 GStreamer 버스를 살펴볼 필요가 있다. 버스의 객체는 엘리먼트에서 발생한 GstMessages를 앱으로 전달하는 역할을 한다. 발생 순서대로, 앱의 쓰레드로 전달한다. 마지막 언급한 부분이 중요하다. 실제 미디어의 스트리밍은 앱과 다른 쓰레드에서 수행된다.
메시지는 gst_bus_timed_pop_filtered() 함수를 통해 버스로부터 동기적으로 추출할 수 있다. 이와 비슷하게 시그널을 사용하면 비동기적으로 추출할 수 있다(다음 튜토리얼에서 다룬다). 앱은 항상 버스를 관찰하여 미디어 재생 중 에러나 재생관련 이슈가 발생했는지 확인해야한다.
나머지 코드는 사용한 객체를 정리하는 코드로 이전 튜토리얼의 예제와 동일하다.
l 연습
만약 연습이 필요하다고 느낀다면 다음을 수행한다: 비디오 필터 엘리먼트를 위 예제의 소스와 싱크 사이에 추가한다. 필터 엘리먼트는 “vertigotv”를 사용하도록 한다. 엘리먼트를 생성하고 파이프라인에 추가하고 다른 엘리먼트와 연결해야한다.
사용하는 플랫폼과 설치된 플러그인에 따라 “negotiation” 에러가 날 수도 있다. 이는 싱크가 필터의 출력을 이해하지 못하기 때문이다(negotiation에 대한 자세한 내용은 Basic tutorial 6: Media formats and Pad Capabilities를 참조한다). 이경우 “videoconvert” 필터를 “vertigotv” 엘리먼트 이후에 추가한다(결과적으로 4개 엘리먼트로 파이프라인을 구성한다.).
l 결론
이 튜토리얼에서 확인한 내용:
- 엘리먼트를 만든 방법은? gst_element_factory_make()
- 빈 파이프라인을 만든 방법은? gst_pipeline_new()
- 어떻게 파이프라인에 엘리먼트를 추가하였는가? gst_bin_add_many()
- 어떻게 엘리먼트들을 서로 연결했는가? gst_element_link()
예제를 실행하면 왼쪽과 같이 테스트 비디오가 나옵니다. 연습대로 필터를 추가하면 오른쪽과 같이 테스트 비디오에 약간 흐림 효과가 들어가서 나옵니다.
'개발 > c' 카테고리의 다른 글
[번역][gstreamer] basic tutorial 4: Time management (0) | 2022.02.09 |
---|---|
[번역][gstreamer] basic tutorial 3: Dynamic pipelines (0) | 2022.01.28 |
[번역][gstreamer] basic tutorial 1: Hello world! (0) | 2022.01.11 |
[번역][gstreamer] Tutorials을 시작하며... (0) | 2022.01.10 |
[gstreamer] windows에서 설치 및 예제 빌드 (0) | 2022.01.04 |