본문 바로가기
Study/Gstreamer

[Gstreamer] Gstreamer 기본 튜토리얼 4 : 시간 관리

by 스테디코디스트 2024. 3. 25.
반응형
전체 소스코드
#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,
                &current)) {
          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, &current)) 
      {
        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 : 스트림에서 임의의 위치로 탐색.

 

참고자료
 

Basic tutorial 4: Time management

Basic tutorial 4: Time management Please port this tutorial to python! Please port this tutorial to javascript! Goal This tutorial shows how to use GStreamer time-related facilities. In particular: How to query the pipeline for information like stream posi

gstreamer.freedesktop.org