전체 소스코드
#include <gst/gst.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
/* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData
{
GstElement *playbin; /* Our one and only element */
gboolean playing; /* Are we in the PLAYING state? */
gboolean terminate; /* Should we terminate execution? */
gboolean seek_enabled; /* Is seeking enabled for this media? */
gboolean seek_done; /* Have we performed the seek already? */
gint64 duration; /* How long does this media last, in nanoseconds */
} CustomData;
/* Forward definition of the message processing function */
static void handle_message (CustomData * data, GstMessage * msg);
int
tutorial_main (int argc, char *argv[])
{
CustomData data;
GstBus *bus;
GstMessage *msg;
GstStateChangeReturn ret;
data.playing = FALSE;
data.terminate = FALSE;
data.seek_enabled = FALSE;
data.seek_done = FALSE;
data.duration = GST_CLOCK_TIME_NONE;
/* Initialize GStreamer */
gst_init (&argc, &argv);
/* Create the elements */
data.playbin = gst_element_factory_make ("playbin", "playbin");
if (!data.playbin) {
g_printerr ("Not all elements could be created.\n");
return -1;
}
/* Set the URI to play */
g_object_set (data.playbin, "uri",
"https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm",
NULL);
/* Start playing */
ret = gst_element_set_state (data.playbin, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
g_printerr ("Unable to set the pipeline to the playing state.\n");
gst_object_unref (data.playbin);
return -1;
}
/* Listen to the bus */
bus = gst_element_get_bus (data.playbin);
do {
msg = gst_bus_timed_pop_filtered (bus, 100 * GST_MSECOND,
GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS |
GST_MESSAGE_DURATION);
/* Parse message */
if (msg != NULL) {
handle_message (&data, msg);
} else {
/* We got no message, this means the timeout expired */
if (data.playing) {
gint64 current = -1;
/* Query the current position of the stream */
if (!gst_element_query_position (data.playbin, GST_FORMAT_TIME,
¤t)) {
g_printerr ("Could not query current position.\n");
}
/* If we didn't know it yet, query the stream duration */
if (!GST_CLOCK_TIME_IS_VALID (data.duration)) {
if (!gst_element_query_duration (data.playbin, GST_FORMAT_TIME,
&data.duration)) {
g_printerr ("Could not query current duration.\n");
}
}
/* Print current position and total duration */
g_print ("Position %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT "\r",
GST_TIME_ARGS (current), GST_TIME_ARGS (data.duration));
/* If seeking is enabled, we have not done it yet, and the time is right, seek */
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,
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, 30 * GST_SECOND);
data.seek_done = TRUE;
}
}
}
} while (!data.terminate);
/* Free resources */
gst_object_unref (bus);
gst_element_set_state (data.playbin, GST_STATE_NULL);
gst_object_unref (data.playbin);
return 0;
}
static void
handle_message (CustomData * data, GstMessage * msg)
{
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);
data->terminate = TRUE;
break;
case GST_MESSAGE_EOS:
g_print ("\nEnd-Of-Stream reached.\n");
data->terminate = TRUE;
break;
case GST_MESSAGE_DURATION:
/* The duration has changed, mark the current one as invalid */
data->duration = GST_CLOCK_TIME_NONE;
break;
case GST_MESSAGE_STATE_CHANGED:{
GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state,
&pending_state);
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) {
g_print ("Pipeline state changed from %s to %s:\n",
gst_element_state_get_name (old_state),
gst_element_state_get_name (new_state));
/* Remember whether we are in the PLAYING state or not */
data->playing = (new_state == GST_STATE_PLAYING);
if (data->playing) {
/* We just moved to PLAYING. Check if seeking is possible */
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);
}
}
}
break;
default:
/* We should not reach here */
g_printerr ("Unexpected message received.\n");
break;
}
gst_message_unref (msg);
}
int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main ((GstMainFunc) tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}
코드 설명
// 구조체 선언
typedef struct _CustomData
{
GstElement *playbin;
gboolean playing;
gboolean terminate;
gboolean seek_enabled;
gboolean seek_done;
gboolean duration;
} CustomData;
// 메세지 처리 핸들러 선언
static void handle_message (CustomData *data, GstMessage *msg);
// 메인함수 내부
CustomData data;
GstBus *bus;
GstMessage *msg;
GstStateChangeReturn ret;
data.playing = FALSE;
data.terminate = FALSE;
data.seek_enabled = FALSE;
data.seek_done = FALSE;
data.duration = GST_CLOCK_TIME_NONE;
// Gstreamer 초기화
gst_init (&argc, &argv);
// 요소 생성
data.playbin = gst_element_factory_make ("playbin", "playbin");
if (!data.playbin)
{
g_printerr ("Not all elements could be created.\n");
return -1;
}
// URI 설정
g_object_set (data.playbin, "uri",
"https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm",
NULL);
// 상태설정(영상시작)
ret = gst_element_set_state (data.playbin, GST_STATE_PLAYING);
// 오류 처리
if (ret == GST_STATE_CHANGE_FAILURE)
{
g_printerr ("Unable to set the pipeline to the playing state.\n");
gst_object_unref (data.playbin);
return -1;
}
- 위 부분은 앞선 튜토리얼에서 설명한 내용과 겹치므로 설명 생략.
// 버스의 정보를 playbin에서 받아옴.
bus = gst_element_get_bus (data.playbin);
do {
msg = gst_bus_timed_pop_filtered (bus, 100 * GST_MSECOND,
GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS |
GST_MESSAGE_DURATION);
// 메세지 확인절차
if (msg != NULL)
{
// 메세지를 올바르게 받은 경우
handle_message (&data, msg);
}
else
{
// 시간초과 -> 메세지 미표시
if (data.playing)
{
gint64 current = -1;
// 스트림의 현재 위치를 찾음
if (!gst_element_query_position (data.playbin, GST_FORMAT_TIME, ¤t))
{
g_printerr ("Could not query current position.\n");
}
// 아직도 못 찾은 경우, 현재까지의 시간을 측정
if (!GST_CLOCK_TIME_IS_VALID (data.duration))
{
if (!gst_element_query_duration (data.playbin, GST_FORMAT_TIME, &data.duration))
{
g_printerr ("Could not query current duration.\n");
}
}
// 현재 스트림의 위치와 탐색시간을 출력
g_print ("Position %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT "\r",
GST_TIME_ARGS (current), GST_TIME_ARGS (data.duration));
// 10초가 지나고, 탐색이 활성화된 경우이면서 아직 탐색을 수행하지 않은 경우
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,
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, 30 * GST_SECOND);
data.seek_done = TRUE;
}
}
}
} while (!data.terminate);
// 할당된 리소스 해제
gst_object_unref (bus);
gst_element_set_state (data.playbin, GST_STATE_NULL);
gst_object_unref (data.playbin);
return 0;
gst_bus_timed_pop_filtered
- 지난 튜토리얼까지는 두번째 인자인 시간에 대한 설정을 아무것도 설정하지 않았음.
- 이번엔 "100 * GST_MSECOND"를 사용하여 100 밀리초로 시간제한을 설정
- 즉, 0.1초 내에 메세지가 수신되지 않으면, NULL을 리턴함.
- UI 업데이트에 사용가능
- 이때, 시간 단위는 GstClockTime을 사용해야하므로, 나노초 단위로 설정.
- 따라서, 다른 시간 단위를 사용하여 표현하고 싶은 경우에는 GST_SECOND 또는 GST_MSCOND와 같은 매크로를 곱해주어야 함.
- 여기서 메세지를 받는데 성공하면 handle_message로 넘어가 처리함.
data.playing
- Plyaing 상태이면 화면을 새로고침 해야 함.
- 보통 대부분의 요청(쿼리) 실패하기 때문에 Playing 상태가 아니면 아무일도 일어나지 않음.
- 새로고침 빈도 초당 10회
gst_element_query_position
- 현재 요청의 위치가 있는지 확인
- 요청에 대한 결과만 제공
gst_element_query_duration
- 스트림의 길이를 알 수 있음.
gst_element_seek_simple
- 간단하게 탐색을 수행
GST_FORMAT_TIME
- 시간 단위로 목적지를 지정하고 있음을 나타냄.
<GstSeekFlags>
- GST_SEEK_FLAG_FLUSH : 검색 수행 전, 현재 파이프라인의 모든 데이터 삭제
- GST_SEEK_FLAG_KEY_UNIT : 실제로 가장 가까운 키 프레임으로 검색이 이동, 즉시 데이터 생성을 시작
- GST_SEEK_FLAG_ACCURATGE : 정확한 시간 탐색이 필요한 경우 사용.
// 메세지 핸들러 정의부
static void handle_message (CustomData * data, GstMessage * msg)
{
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);
data->terminate = TRUE;
break;
case GST_MESSAGE_EOS:
g_print ("\nEnd-Of-Stream reached.\n");
data->terminate = TRUE;
break;
// 스트림 지속시간이 변경된 경우
// -> 현재 기간을 유효하지 않은 것으로 처리하여 다음에 다시 요청
case GST_MESSAGE_DURATION:
data->duration = GST_CLOCK_TIME_NONE;
break;
// 상태가 변경된 경우
case GST_MESSAGE_STATE_CHANGED:
{
GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin))
{
g_print ("Pipeline state changed from %s to %s:\n",
gst_element_state_get_name (old_state),
gst_element_state_get_name (new_state));
// 현재 상태가 Playing 상태인지 여부
data->playing = (new_state == GST_STATE_PLAYING);
if (data->playing)
{
// 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);
}
}
}
break;
default:
g_printerr ("Unexpected message received.\n");
break;
}
gst_message_unref (msg);
}
handle_message
- 파이프라인의 버스를 통해 수신된 모든 메세지를 처리
- ERROR, EOS 처리에 대한 설명은 이전 튜토리얼과 동일하므로 생략.
case GST_MESSAGE_DURATION:
- 스트림 지속시간이 변경된 경우
- 현재 지속시간을 유효하지 않은 것으로 처리하여 다음에 다시 요청
case GST_MESSAGE_STATE_CHANGED:
- 일반적으로 탐색 및 시간 요청(쿼리)는 PAUSED 또는 PLAYING 상태에 있을때만 유효한 응답을 얻음.
- 여기서는 data의 playing 변수를 사용하여 현재 파이프라인의 상태를 추적
- 방금 PLAYING 상태에 진입한 경우, 첫 번째 요청(쿼리)을 수행
gst_query_new_seeking
- GST_FROMAT_TIME 형식을 사용해 seeking 유형의 새로운 쿼리 객체를 생성
- 이는 새로운 시간을 지정하여 해당 시간으로 이동할 수 있다는 것을 의미함.
gst_element_query
- 파이프라인 내부의 쿼리를 저장
gst_query_parse_seeking
- 탐색이 허용되는지 여부와 탐색이 가능한 범위를 찾아 저장
결과
정리
GstQuery : 파이프라인에서 정보를 쿼리
gst_element_query_position, gst_element_query_duration : 위치 및 기간과 같은 일반적인 정보를 얻음.
gst_element_seek_simple : 스트림에서 임의의 위치로 탐색.
참고자료