본문 바로가기
Study/Gstreamer

[Gstreamer] Gstreamer 기본 튜토리얼 3

by 스테디코디스트 2024. 3. 22.
반응형
전체 소스코드
#include <gst/gst.h>

#ifdef __APPLE__
#include <TargetConditionals.h>
#endif

/* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData
{
  GstElement *pipeline;
  GstElement *source;
  GstElement *convert;
  GstElement *resample;
  GstElement *sink;
} CustomData;

/* Handler for the pad-added signal */
static void pad_added_handler (GstElement * src, GstPad * pad,
    CustomData * data);

int
tutorial_main (int argc, char *argv[])
{
  CustomData data;
  GstBus *bus;
  GstMessage *msg;
  GstStateChangeReturn ret;
  gboolean terminate = FALSE;

  /* Initialize GStreamer */
  gst_init (&argc, &argv);

  /* Create the elements */
  data.source = gst_element_factory_make ("uridecodebin", "source");
  data.convert = gst_element_factory_make ("audioconvert", "convert");
  data.resample = gst_element_factory_make ("audioresample", "resample");
  data.sink = gst_element_factory_make ("autoaudiosink", "sink");

  /* Create the empty pipeline */
  data.pipeline = gst_pipeline_new ("test-pipeline");

  if (!data.pipeline || !data.source || !data.convert || !data.resample
      || !data.sink) {
    g_printerr ("Not all elements could be created.\n");
    return -1;
  }

  /* Build the pipeline. Note that we are NOT linking the source at this
   * point. We will do it later. */
  gst_bin_add_many (GST_BIN (data.pipeline), data.source, data.convert,
      data.resample, data.sink, NULL);
  if (!gst_element_link_many (data.convert, data.resample, data.sink, NULL)) {
    g_printerr ("Elements could not be linked.\n");
    gst_object_unref (data.pipeline);
    return -1;
  }

  /* Set the URI to play */
  g_object_set (data.source, "uri",
      "https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm",
      NULL);

  /* Connect to the pad-added signal */
  g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler),
      &data);

  /* Start playing */
  ret = gst_element_set_state (data.pipeline, 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.pipeline);
    return -1;
  }

  /* Listen to the bus */
  bus = gst_element_get_bus (data.pipeline);
  do {
    msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
        GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

    /* Parse message */
    if (msg != NULL) {
      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);
          terminate = TRUE;
          break;
        case GST_MESSAGE_EOS:
          g_print ("End-Of-Stream reached.\n");
          terminate = TRUE;
          break;
        case GST_MESSAGE_STATE_CHANGED:
          /* We are only interested in state-changed messages from the pipeline */
          if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data.pipeline)) {
            GstState old_state, new_state, pending_state;
            gst_message_parse_state_changed (msg, &old_state, &new_state,
                &pending_state);
            g_print ("Pipeline state changed from %s to %s:\n",
                gst_element_state_get_name (old_state),
                gst_element_state_get_name (new_state));
          }
          break;
        default:
          /* We should not reach here */
          g_printerr ("Unexpected message received.\n");
          break;
      }
      gst_message_unref (msg);
    }
  } while (!terminate);

  /* Free resources */
  gst_object_unref (bus);
  gst_element_set_state (data.pipeline, GST_STATE_NULL);
  gst_object_unref (data.pipeline);
  return 0;
}

/* This function will be called by the pad-added signal */
static void
pad_added_handler (GstElement * src, GstPad * new_pad, CustomData * data)
{
  GstPad *sink_pad = gst_element_get_static_pad (data->convert, "sink");
  GstPadLinkReturn ret;
  GstCaps *new_pad_caps = NULL;
  GstStructure *new_pad_struct = NULL;
  const gchar *new_pad_type = NULL;

  g_print ("Received new pad '%s' from '%s':\n", GST_PAD_NAME (new_pad),
      GST_ELEMENT_NAME (src));

  /* If our converter is already linked, we have nothing to do here */
  if (gst_pad_is_linked (sink_pad)) {
    g_print ("We are already linked. Ignoring.\n");
    goto exit;
  }

  /* Check the new pad's type */
  new_pad_caps = gst_pad_get_current_caps (new_pad);
  new_pad_struct = gst_caps_get_structure (new_pad_caps, 0);
  new_pad_type = gst_structure_get_name (new_pad_struct);
  if (!g_str_has_prefix (new_pad_type, "audio/x-raw")) {
    g_print ("It has type '%s' which is not raw audio. Ignoring.\n",
        new_pad_type);
    goto exit;
  }

  /* Attempt the link */
  ret = gst_pad_link (new_pad, sink_pad);
  if (GST_PAD_LINK_FAILED (ret)) {
    g_print ("Type is '%s' but link failed.\n", new_pad_type);
  } else {
    g_print ("Link succeeded (type '%s').\n", new_pad_type);
  }

exit:
  /* Unreference the new pad's caps, if we got them */
  if (new_pad_caps != NULL)
    gst_caps_unref (new_pad_caps);

  /* Unreference the sink pad */
  gst_object_unref (sink_pad);
}

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 *pipeline;
    GstElement *source;
    GstElement *convert;
    GstElement *resample;
    GstElement *sink;
} CustomData;

// pad-added 신호를 위한 핸들러
static void pad_added_handler (GstElement *src, GstPad *pad, CustomData *data);

// 요소 생성
data.source = gst_element_factory_make ("uridecodebin", "source");
data.convert = gst_element_factory_make ("audioconvert", "convert");
data.resample = gst_element_factory_make ("audioresample", "resample");
data.sink = gst_element_factory_make ("autoaudiosink", "sink");

gst_element_factory_make(value1, value2)

- 새로운 요소(element) 생성 

- value1은 생성할 요소의 type

- value2는 특정 인스턴스에 부여하려는 이름 -> 요소 검색에 유용함.

uridecodebin

- 원시 오디오 또는 비디오를 스트림으로 변환하는 데 필요한 모든 요소(소스, 디먹서, 디코더)를 내부적으로 인스터화 함.

- 디먹서가 포함되어 있기 때문에 소스 패드는 처음부터 사용할 수 없으며 연결해서 사용한다.

audioconvert

- 오디오 디코더에서 생성된 형식이 오디오 싱크가 예상하는 형식과 다를 수 있으므로, 서로 다른 오디오 형식 간 변환하는데 유용.

audioresample

- 오디어 디코더에서 생성된 오디오 샘플 속도가 오디오 싱크가 지원하는 속도와 다를 수 있으므로, 서로 다른 오디오 샘플 속도 간에 변환하는 데 유용.

autovideosink

- 이미지를 창에 표시(데이터 사용)

- 운영체제에 따라 여러개의 videosink가 있는데 autovideosink로 설정하면 자동으로 가장 좋은 것을 선택해줌.

- 오디오 스트림을 오디오 카드로 렌더링.

 

// 파이프라인 생성
data.pipeline = gst_pipeline_new ("test-pipeline");

// 오류 확인
if (!data.pipeline || !data.source || !data.convert || !data.resample || !data.sink) 
{
  g_printerr ("Not all elements could be created.\n");
  return -1;
}

// 파이프라인 구축
gst_bin_add_many (GST_BIN (data.pipeline), data.source, data.convert,data.resample, data.sink, NULL);
    
// 파이프라인 연결
// source는 추후에 연결.
if (!gst_element_link_many (data.convert, data.resample, data.sink, NULL)) 
{
  g_printerr ("Elements could not be linked.\n");
  gst_object_unref (data.pipeline);
  return -1;
}

// 플레이 uri 설정
g_object_set (data.source, "uri",
	"https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm", NULL);

gst_pipeline_new

- 새로운 파이프라인 생성

gst_bin_add_many

- 파이프라인에 요소를 추가

gst_element_link_many

- 각 요소들을 서로 연결시켜줌.

- converter, resample, sink를 연결

- source는 연결하지 않음. -> 소스 패드가 없기 때문.

- branch(converter + sink)는 연결되지 않은채로 남겨둔다.

g_object_set

- 속성을 통해 재생할 파일의 URI를 설정

 

// pad-added 신호 연결
g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler), &data);

GSignals(신호)

- Gsignals는 Gstreamer에서 중요함.

- 어떤 일이 발생하면 (콜백을 통해) 알림을 받을 수 있음.

- 신호는 이름으로 식별되며, 각 GObject 마다 고유한 신호가 있음.

- 위의 코드에서, source에 "pad-added" 신호가 연결됨.(uridecodebin 요소 타입으로 연결)

g_signal_connect

- 데이터 주소(포인터)에 콜백 함수(pad_added_handler)를 연결

 

// 플레이 시작
ret = gst_element_set_state (data.pipeline, 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.pipeline);
  return -1;
}

gst_element_set_state

- playing 상태로 변경한 결과를 ret에 저장

- 반환 값 ret을 이용해 오류확인

 

// 에러가 발생하거나 EOS에 도달할 때까지 대기
bus = gst_element_get_bus (data.pipeline);

do {
  msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
      GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

  // 메세지 분석
  if (msg != NULL) {
    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);
        terminate = TRUE;
        break;
      case GST_MESSAGE_EOS:
        g_print ("End-Of-Stream reached.\n");
        terminate = TRUE;
        break;
      case GST_MESSAGE_STATE_CHANGED:        
        if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data.pipeline)) 
        {
          GstState old_state, new_state, pending_state;
          gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
          g_print ("Pipeline state changed from %s to %s:\n",
              gst_element_state_get_name (old_state),
              gst_element_state_get_name (new_state));
        }
        break;
      default:
        /* We should not reach here */
        g_printerr ("Unexpected message received.\n");
        break;
    }    
    gst_message_unref (msg);    
  }
} while (!terminate);

gst_bus_timed_pop_filtered

- 실행이 끝날 때까지 기다렸다가 이전에 무시했던 GstMessage를 반환.

- 어떤 일이 발생했는지 확인을 마친 후 만약 오류나 EOS를 만났다면 메세지 반환.

gst_message_parse_error

- 메세지에 오류가 포함되어 있다면, GError 오류 구조와 디버깅에 유효한 문자열을 반환.

 

// 콜백 함수
pad_added_handler(GstElement *src, GstPad *new_pad, CustomData *data)
{
    GstPad *sink_pad = gst_element_get_static_pad (data->convert, "sink");
    GstPadLinkReturn ret;
    GstCaps *new_pad_caps = NULL;
    GstStructure *new_pad_struct = NULL;
    const gchar *new_pad_type = NULL;
     
    g_print ("Received new pad '%s' from '%s':\n", 
     	GST_PAD_NAME (new_pad), GST_ELEMENT_NAME (src));
       
    // converter가 이미 연결된 경우 종료
    if (gst_pad_is_linked (sink_pad)) 
    {
    	g_print ("We are already linked. Ignoring.\n");
        goto exit;
    }
    
    // new_pad의 타입 확인
    new_pad_caps = gst_pad_get_current_caps (new_pad, NULL);
    new_pad_struct = gst_caps_get_structure (new_pad_caps, 0);
    new_pad_type = gst_structure_get_name (new_pad_struct);
    
    // 원하는 이름의 pad가 아닌 경우
    if (!g_str_has_prefix (new_pad_type, "audio/x-raw"))
    {
    	g_print ("It has type '%s' which is not raw audio. Ignoring.\n", new_pad_type);
        goto exit;
    }
    
    // converter 연결 시도
    ret = gst_pad_link (new_pad, sink_pad);
    
    if (GST_PAD_LINK_FAILED (ret))
    	g_print ("Type is '%s' but link failed.\n", new_pad_type);
    else
    	g_print ("Link succeeded (type '%s').\n", new_pad_type);
        
exit:
  if (new_pad_caps != NULL)
    gst_caps_unref (new_pad_caps);

  gst_object_unref (sink_pad);
}

gst_element_get_static_pad

- CustomData에서 converter의 "sink" 요소를 추출하여, sink pad에 할당.

- new_pad에 해당 패드를 연결해야 함.

gst_pad_is_linked

- 이미 패드가 연결되어 있는 경우, 새 패드에 연결하려는 시도를 방지.

gst_pad_get_current_caps

- new_pad에서 현재 GstCaps를 가져와 new_pad_caps에 할당.

gst_pad_get_structure

- 현재 오디오 기능만 있으므로 해당 기능만 가져오면 됨.

- 따라서, 첫번째 GstStructure의 기능만을 가져와 new_pad_sturct에 할당.

gst_structure_get_name

- 형식에 대한 주요 설명이 포함된 구조의 이름을 가져와 new_pad_type에 할당.

g_str_has_prefix

- 문자열 비교

- new_pad_type이 "audio/x-raw"가 아닌 경우 디코딩된 오디오 패드가 아니므로 넘어감.

gst_pad_link

- 두 개의 패드(new_pad, sink_pad)를 연결을 시도

 

Gstreamer 상태
상태 설명
NULL NULL 상태 또는 요소의 초기 상태
READY 요소가 PAUSED 상태로 전환될 준비가 됨
PAUSED 요소가 PAUSED 되어 데이터를 받아들이고 처리할 준비가 됨.
단, 싱크 요소는 하나의 버퍼만 허용한 다음 차단.
PLAYING 요소가 재생 중이고, 시계가 실행 중이며 데이터가 흐르는 상태

 

결과

(문서엔 영상이 나온다고 한거 같은데 나는 안나왔음. 뭐지..?)

 

정리

g_signal_connect : 콜백 함수 연결(신호 연결)

g_pad_ : pad 연결

Gstreamer 상태 : NULL -> READY -> PAUSED -> PLAYING

참고자료
 

Basic tutorial 3: Dynamic pipelines

Basic tutorial 3: Dynamic pipelines Please port this tutorial to python! Please port this tutorial to javascript! Goal This tutorial shows the rest of the basic concepts required to use GStreamer, which allow building the pipeline "on the fly", as informat

gstreamer.freedesktop.org