728x90

이전 포스팅에 이어 Tutorial 4을 진행하면서 elemtents에 query를 통해 control 하는 방법에 대해 살펴봅니다.

 

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

#include <QCoreApplication>
#include <QDebug>
#include "gst/gst.h"

/* 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 main(int argc, char *argv[])
{
    QCoreApplication a(argc, 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://www.freedesktop.org/software/gstreamer-sdk/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,
          (GstMessageType)(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,
                (GstSeekFlags)(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 a.exec();
}

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);
}

예제를 위해 첫 번째 튜토리얼에서 사용했던 playbin elements를 사용합니다.

/* 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://www.freedesktop.org/software/gstreamer-sdk/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;
}

이전 튜토리얼까지는 bus로부터 message를 받아오는 함수인 gst_bus_timed_pop_filtered에 timeout을 지정하지 않아 무한 대기하였고 2번째 파라미터에 시간을 지정해주면 timeout이 일어나면 NULL을 반환하게 됩니다.

bus = gst_element_get_bus (data.playbin);
do {
  msg = gst_bus_timed_pop_filtered (bus, 100 * GST_MSECOND,
      (GstMessageType)(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,
            (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT), 30 * GST_SECOND);
        data.seek_done = TRUE;
      }
    }
  }
} while (!data.terminate);

여기서 handle_message 함수를 먼저 보겠습니다. STATE_CHANGE case를 보면 playbin의 state를 체크하고 있습니다. 체크했을 때 state가 playing일 경우 영상의 시간 길이(범위)를 query를 통해 가져옵니다.

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);
}

다시 do-while쪽으로 넘어가보겠습니다. gst_bus_timed_pop_filtered에 등록한 메시지 타입이 발생하지 않으면 msg가 NULL 이되어 else로 넘어가게 됩니다.

handle_message에서 체크한대로 play 상태라면 gst_element_query_position를 통해 현재 영상의 position 값을 가져오고 

g_print로 현재 상태를 계속 출력합니다.

 

영상이 10초가 지났으면 gst_element_seek_simple 함수를 통해 영상의 position을 변경합니다.

변경하게 되면 playbin의 상태가 PAUSED -> PLAYING으로 변하기 때문에 다시 handle_message 함수를 타게 됩니다.

 

 

프로그램을 시작하면 아래와 같이 흘러갑니다.

10초 경과 후 영상의 position을 변경합니다.

그 후 영상이 종료될 때까지 재생합니다.

728x90

+ Recent posts