본문 바로가기
MultiMedia Framework/GStreamer

동적으로 파이프라인 구축하기

by 유주원 2015. 3. 21.

이번 포스팅을 통해 동적으로 파이프라인을 구축하는 방법을 살펴볼 수가 있다.



언제나 그렇듯이 실행 ㄱㄱ


$> g++ -o tutorial3 tutorial3.c `pkg-config --cflags --libs gstreamer-1.0`

$>./tutorial3

Pipeline state changed from NULL to READY:

Received new pad 'src_0' from 'source':

  It has type 'video/x-raw' which is not raw audio. Ignoring.

Received new pad 'src_1' from 'source':

  Link succeeded (type 'audio/x-raw').

Pipeline state changed from READY to PAUSED:

Pipeline state changed from PAUSED to PLAYING:

End-Of-Stream reached.


출력 결과를 보면 source element로부터 'src_0', 'src_1' 패드가 만들어졌고 Link가 된 후 파이프라인이 동작하는 것을 확인할 수 있다.

소스를 보면 알다시피 위 소스에서는 오디오만을 다루기 때문에 화면에는 아무런 출력도 발생하지 않는다.


typedef struct _CustomData{

    GstElement *pipeline;

    GstElement *source;

    GstElement *convert;

    GstElement *sink;

}CustomData;


데이터 전달을 보다 용이하게 할 수 있도록 CustomData 라는 구조체를 선언했다.


data.source = gst_element_factory_make("uridecodebin", "source");

data.convert = gst_element_factory_make("audioconvert", "convert");

data.sink = gst_element_factory_make("autoaudiosink", "sink");

data.pipeline =  gst_pipeline_new("test-pipeline");


"uridecodebin", "audioconvert", "autoaudiosink" 타입의 element들을 생성하고, 파이프라인을 생성하였다.

"uridecodebin"은 해당 uri에 위치한 미디어를 raw media로 디코딩 해주는 역할을 한다. 

개발자가 "uridecodebin"을 만든후 재생을 하게 되면, "uridecodebin"에서는 주어진 url를 처리할 수 있는 source element를 선택하고 해당 source element를 decodebin에 연결함으로써 역할을 수행하게 된다.

"audioconvert"는 audio를 다양한 포멧으로 변환해 주는 역할을 하며, integer to float 변환, width/depth 변환, 채널 변형등을 가능하게 해준다.

"autoaudiosink"는 해당 미디어에 대한 적절한 audio sink를 찾아주는 역할을 한다.   


gst_bin_add_many(GST_BIN(data.pipeline), data.source, data.convert, data.sink, NULL);

if(!gst_element_link(data.convert, data.sink)){

    g_printerr("Elements could not be linked.\n");

    gst_object_unref(data.pipeline);

    return -1;

}


필요한 element들을 다 생성하였으니 이제 파이프라인에 담고 해당 element들을 연결해야 한다.


'어 그런데 source element는 연결을 안하네???'


우리는 video가 아닌 audio만 다룰 것이기 때문에 source element에서는 오디오만 다룰 수 있도록 따로 조작해 주어야 한다.

그래서 일단 convert element와 sink element만 연결을 해 놓았다.


g_object_set(data.source, "uri", "http://docs.gstreamer.com/media/sintel_trailer-480p.webm", NULL);


source element의 uri 속성에 값을 입력함으로써 source element가 해당 데이터를 찾을 수 있도록 해준다.


g_signal_connect(data.source, "pad-added", G_CALLBACK(pad_added_handler), &data);


이번 포스팅에서 가장 핵심 내용이라고 할 수 있는데, 간략하게 설명해서  source element에서 pad가 만들어졌을 때 인터럽트 신호를 받아서 pad_added_handler callback 함수로 호출하라는 이야기다.


대부분의 element들 마다 pad가 존재하는데 이 pad라는 것이 element간의 연결할 때 필요한 요소이다. 가령 source element와 sink element가 연결된다 함은 source element의 src pad와 sink element의 sink pad가 연결된다는 것이다.

당연하게도 source element에서는 sink pad가 존재하지 않고, sink element에서는 src pad가 존재하지 않는다. (필요가 없으니깐..)


또한 source element는 1개 이상의 src pad 를 가질수 있는데, 예를 들어 지금 보는 uridecodebin source element 같은 경우에는 video와 연결하기 위한 pad가 한개, audio와 연결하기 위한 pad가 한개 이렇게 2개의 pad 를 가지고 있다.

그럼 소스에서는 어떻게 pad를 구분지은 후 연결하는지를 한 번 살펴보자.


GstPad *sink_pad = gst_element_get_static_pad(data->convert, "sink");


gst_element_get_static_pad 함수를 통해 convert element로부터 sink pad를 가져온다. sink pad를 가져오는 목적은 source element의 src pad와 연결을 시키기 위함이다.


if(gst_pad_is_linked(sink_pad)){

    g_print("We are already linked. Ignoring.\n");

    goto exit;

}


convert element로부터 추출한 sink pad가 현재 연결되어 있는지 여부를 확인한다. 이미 연결되어 있다면 callback을 종료한다.


new_pad_caps = gst_pad_get_current_caps(new_pad);

new_pad_struct = gst_caps_get_structure(new_pad_caps, 0);

new_pad_type = gst_structure_get_name(new_pad_struct);

if(!g_str_has_prefix(new_pad_type,"audio/x-raw")){

    g_print(" It has type '%s' which is not raw audio. Ignoring.\n", new_pad_type);

    goto exit;

}


코드가 길긴 하지만 그냥 callback으로 넘어온 pad의 type을 알기 위해서 작성된 코드이다. 아마도 type을 한번에 알수는 없고, pad로부터 cap정보를 가져오고, cap 정보로부터 struct 정보를 가져온 다음에 struct로부터 타입을 가져오는 것 같다.

조건 문에서 만약 해당 type이 audio가 아니면 callback을 종료시키는 것을 볼 수 있다.


ret = gst_pad_link(new_pad, sink_pad);


이 부분이 바로 source element를 연결 시키는 부분이다. 이렇게 audio를 걸러내서 연결해야 했기 때문에 아까 파이프라인에서 함께 연결시키지 않은 것이다.


case GST_MESSAGE_STATE_CHANGED:

    if(GST_MESSAGE_SRC(msg) == GST_OBJECT(data.pipeline)){

        GstState old_state, new_state, pending_state;

        gst_message_parse_state_changed(msg, &old_state, &new_state, &pending_state);

        g_print("Pipeline state changed from %s to %s:\n", gst_element_state_get_name(old_state), gst_element_state_get_name(new_state));

    }


switch 문 내에 case 구문이 추가가 되었다. 파이프의 상태 변경 시마다 메시지를 찍어주도록 구현한 코드이다.

현재 들어온 메시지가 pipeline으로부터 온 메시지가 맞을 경우에는 gst_message_parse_state_changed 함수를 통해 이전 상태와 현재 상태 정보를 가져온다. (pending_state에 대한 설명은 따로 없는데, 이건 나중에 다시 확인해 볼께요 -_-;;)

그리고 나선 g_print로 이전 상태와 현재 상태를 출력..


실제 실행해보면 pipeline이 어떤 상태 변화로 동작하는지 확인할 수가 있다.


이제 우리는 내맘대로 pad를 선택할 수 있게 되었다!!!