Stream Seek

2015. 3. 24. 10:58MultiMedia Framework/GStreamer

이번 포스팅에서는 스트림 위치 값 등을 얻기 위해 어떻게 파이프라인에 요청을 하는 지와 스트림의 위치를 어떻게 조작하는지에 대해 살펴볼 것이다.


일단 코드에 앞서 GstQuery 라는 객체가 새롭게 등장하는데, 이 GstQuery는 element나 pad에게 정보를 요청하기 위해 사용된다.

이전에는 파이프라인이 구성되면 EOS나 ERROR가 발생하기 전까지는 메인 function에서 계속 대기하는 것 위주로 설명을 하였는데, 이번 포스팅에서는 주기적으로 파이프라인을 깨워서 스트림 위치에 대한 정보를 요청하고 화면에 해당 위치를 출력하는 프로그램을 짤 것이다.


 

실행해보면 화면이 출력되고, console 창에 스트림 위치 값이 계속 변하는 것을 볼 수 있다.



typedef struct _CustomData{

    GstElement *playbin;

    gboolean playing;

    gboolean terminate;

    gboolean seek_enabled;

    gboolean seek_done;

    gint64 duration;

}CustomData;


이번에도 역시 CustomData 구조체를 하나 생성한다.


data.playbin = gst_element_factory_make("playbin", "playbin");

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


그 후 playbin이라는 element를 하나 생성한 후 uri 속성 값을 설정하였다. playbin은 [gstreamer] hello world 찍기 에서 다뤄본 바가 있다.


msg = gst_bus_timed_pop_filtered(bus, 100 * GST_MSECOND, (GstMessageType)(GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_DURATION));


timeout에 대해 이번 포스팅에서 새롭게 등장하였다. 위 코드에 대해 설명하자면, 10초(100 ms) 동안 아무런 메시지(CHANGED, ERROR, EOS, DURATION)가 도착하지 않으면 NULL을 리턴한다. (이전에는 메시지가 없으면 계속 홀딩 상태를 유지했었다.)

메시지가 NULL이 아니면 handle_message를 실행하고 NULL일 경우에는 스트림 위치 값을 계산하게 된다.


GstFormat fmt = GST_FORMAT_TIME;

gint64 current = -1;

if(!gst_element_query_position(data.playbin, fmt, &current)){

    g_printerr("Colud not query current position.\n");

}


gst_element_query_position을 통해 스크림 위치를 가져온다. 값은 0부터 스트림 길이까지 nanoseconds 형태로(GST_FORMAT_TIME) 리턴한다. 


if(!GST_CLOCK_TIME_IS_VALID(data.duration)){

    if(!gst_element_query_duration(data.playbin, fmt, &data.duration)){

        g_printerr("Colud not query current duration.\n");

    }

}


전체 스트림의 길이를 가져온다. 반복문 안에서 매번 가져올 필요 없이 GST_CLOCK_TIME_IS_VALID란 조건을 줘서 duration이 GST_CLOCK_TIME_NONE(시간이 정의되지 않았을 경우에만) 일 경우에만 전체 스트림의 길이를 가져온다.


g_print("Position %" GST_TIME_FORMAT "/ %" GST_TIME_FORMAT "\r", GST_TIME_ARGS(current), GST_TIME_ARGS(data.duration));


h:m:s 형태로 스트림 시간 데이터를 출력한다. GST_TIME_FORMAT은 아래와 같이 정의되어 있다.


#define GST_TIME_FORMAT "u:%02u:%02u.%09u"


if(data.seek_enabled && !data.seek_done && current > 10 * GST_SECOND){

    g_print("\nReached 10s, performing seek...\n");

    gst_element_seek_simple(data.playbin, GST_FORMAT_TIME, (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT), 30 * GST_SECOND);

    data.seek_done = TRUE;

}


스트림의 시작으로부터 상대 위치를 찾고자 할때 gst_element_seek_simple 함수를 사용한다. 

GST_FORMAT_TIME은 명시된 위치를 나타내기 위한 시간 포멧이며, SeekFlag 옵션을 제공한다. Flag 옵션의 종류는 아래와 같다.


- GST_SEEK_FLAG_FLUSH : 현재 파이프라인 안에 존재하는 모든 데이터를 버린다. 만약 이 flag가 없다면 파이프라인 끝에서 새로운 위치가 나타나지 전까지 이전 데이터가 계속 나타날 것이다.

- GST_SEEK_FLAG_KEY_UNIY : 가장 가까운 keyframe을 찾는다. 이 옵션은 빠르게 위치를 찾지만 정확도가 떨어질 수 있다.

- GST_SEEK_FLAG_ACCURATE : 정확한 위치가 요청될 때 사용한다. 몇몇 포멧에 있어서는 느리게 동작 할 수 있다.


그리고 마지막 파라미터로 찾고자 하는 시작 위치를 지정할 수 있다. 기본적으로 nano second이기 때문에 초로 바꾸고자 할 경우에 GST_SECOND를 곱해주자.


case GST_MESSAGE_DURATION:

    data->duration = GST_CLOCK_TIME_NONE;

    break;   


스트림의 길이가 변경되었을 때 해당 case문에 진입한다. 길이가 변경되면 data의 duration을 GST_CLOCK_TIME_NONE으로 설정해서 다시 한번 스트림의 길이를 체크할 수 있도록 한다.


if(data->playing){

    GstQuery *query;

    gint64 start, end;

    query = gst_query_new_seeking(GST_FORMAT_TIME);

    if(gst_element_query(data->playbin, query)){

        gst_query_parse_seeking(query, NULL, &data->seek_enabled, &start, &end);

        if(data->seek_enabled){

            g_print("Seeking is ENABLED from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n", GST_TIME_ARGS(start), GST_TIME_ARGS(end));

        }else{

            g_print("Seeking is DISABLED for this stream.\n");

        }

    }else{

        g_printerr("Seeking query failed.");

    }

    gst_query_unref(query);

}


파이프 상태가 GST_STATE_PLAYING일 때 조건문에 진입한다. gst_query_new_seeking 함수는 스트림의 seeking 속성을 물어보기 위해서 새로 query 객체를 생성한다. 이렇게 생성된 query 객체를 gst_element_query 함수에 파라미터로 넣음으로써 playbin에 대한 seeking 속성 정보를 요청한다.

gst_query_parse_seeking 함수는 query결과에 대해 파싱을 진행한다. seeking 가능 여부, seeking 시작 위치, seeking 끝 위치를 결과로 알려준다.

마지막으로 query 객체를 해제하는 것을 잊어서는 안된다.


이번 장으로 우리는 미디어 플레이어의 seek bar를 제어할 수 있을 것이다.(희망 -_-)