2015. 3. 24. 10:58ㆍMultiMedia 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, ¤t)){
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를 제어할 수 있을 것이다.(희망 -_-)