[Gstreamer] appsink
Linux/GSTREAMER

[Gstreamer] appsink

appsink는 애플리케이션이 파이프라인의 GStreamer 데이터를 처리할 수 있도록 다양한 방법을 지원하는 싱크 플러그인입니다. 대부분의 GStreamer 요소와 달리 Appsink는 외부 API 기능을 제공합니다.

https://gstreamer.freedesktop.org/documentation/applib/gstappsink.html?gi-language=c

 

GstAppSink

GstAppSink Appsink is a sink plugin that supports many different methods for making the application get a handle on the GStreamer data in a pipeline. Unlike most GStreamer elements, Appsink provides external API functions. appsink can be used by linking to

gstreamer.freedesktop.org


본 글에서는 videotestsrc를 통해 들어오는 테스트 패턴 비디오를 appsink를 통해 받는 과정에 대해 설명합니다.

 

Gstreaemer에 대한 자세한 내용은 참고 블로그를 참고하면 많은 도움이 됩니다.

 

Gstreamer 1.0 설치

sudo apt-get update
sudo apt-get install gstreamer1.0-tools gstreamer1.0-alsa \
     gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
     gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \
     gstreamer1.0-libav gstreamer1.0-gtk3
sudo apt-get install libgstreamer1.0-dev \
     libgstreamer-plugins-base1.0-dev \
     libgstreamer-plugins-good1.0-dev \
     libgstreamer-plugins-bad1.0-dev

Gstreamr 1.0 버전 확인

gst-inspect-1.0 --version

동작 확인

gst-launch-1.0 videotestsrc ! autovideosink

 

초기화 

GStreamer 라이브러리를 사용하기 전에, gst_init를 반드시 실행해야 하는 명령으로 다음과 같은 역할을 수행합니다.

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

 

  • 모든 내부 구조를 초기화합니다.
  • 어떤 플러그인을 사용할 수 있는지 확인
  • Gstreamer를 위한 모든 명령줄 옵션을 실행합니다.

요소 생성

다음에는 gst_element_factory_make()를 이용하여 요소(Elements)를 생성합니다.

    /* Create the elements */	
    data.video_source 		= gst_element_factory_make("videotestsrc", "video_source");
    data.app_sink 		= gst_element_factory_make("appsink", "app_sink");

 

위 함수를 이용하여 요소를 생성할 수 있습니다. 첫 번째 매개변수는 요소의 유형이고 두 번째 매개변수는 이 요소에 부여하려는 이름입니다. 두 번째 매개변수는 디버깅 시 요소를 검색하는 데 유용합니다.

 

본 글에서는 vidtestsrc, appsink라는 두 가지 요소를 생성합니다.

 

videotestsrc는 테스트 비디오 패턴을 생성하는 소스 요소입니다.

appsink는 videotestsrc 데이터를 처리할 수 있도록 다양한 방법을 지원하는 싱크 요소입니다.

파이프라인 생성

파이프라인은 포함하는 여러 요소의 동기화와 버스 메시지 관리를 위한 포괄적인 컨테이너로 Gstreamer의 모든 요소는 일반적으로 사용하기 전에 gst_pipeline_new()를 사용하여  파이프라인이 내부에 포함되어야 합니다.

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

속성

대부분의 Gstreamer 요소에는 사용자가 정의 가능한 속성이 있습니다. 속성은 g_objetc_set()을 통해 쓰고 g_object_get()을 통해 읽습니다.

 

g_object_set()는 속성-이름, 속성-값의 형태로 작성하고 마지막에 NULL 매개변수를 추가하여 여러 속성을 한 번에 변경할 수 있습니다.

    g_object_set(data.app_sink, "emit-signals", TRUE, NULL);
    g_signal_connect(data.app_sink, "new-sample", G_CALLBACK(new_sample), &data);

신호

appsink 에는 new-sample이라는 신호가 존재합니다. 이는 하나의 frame 이 읽혔을 때, 발생되는 신호입니다. 아래 new_sample 함수는 new-sample 신호에 의해서 callback 되는 함수입니다. new-sample은 appsink의 emit-siganls 속성이 TRUE인 경우에만 작동하게 되고 아래 new_sample 함수는 g_signal_emit_by_name() 함수의 pull-sample 쿼리를 통해 appsink로부터 들어오는 데이터를 가져옵니다. 

/* The appsink has received a buffer */
static GstFlowReturn new_sample(GstElement *sink, CustomData *data)
{
    GstSample *sample; 
    GstFlowReturn ret = GST_FLOW_OK;

  /* Retrieve the buffer */
    g_signal_emit_by_name (data->app_sink, "pull-sample", &sample, NULL);

    if (sample)
    {        
        /* The only thing we do in this example is print a * to indicate a received buffer */
	g_print("*\n");
  	 
        gst_sample_unref (sample);

        return GST_FLOW_OK;

    }
    else
    {
        g_print ("could not make snapshot\n");
        
        return GST_FLOW_ERROR;
    }

    return GST_FLOW_ERROR;
}

 

그다음 gst_bin_add_many()를 사용하여 요소를 파이프라인에 추가합니다. 하지만 요소를 추가하였을 뿐 서로 연결하지는 않은 상태이므로 gst_element_link를 사용하여 요소끼리 연결합니다.

    /* Build the pipeline */
    gst_bin_add_many(GST_BIN(data.src_pipeline), data.video_source, data.app_sink, NULL);
    
    /* Link the elements together */
    if(gst_element_link(data.video_source, data.app_sink) != TRUE)
    {
        g_printerr("Elements could not be linked.\n");
        gst_object_unref(data.src_pipeline);
        return -1;
    }

버스

버스(bus)는 메시지를 스레드 컨텍스트에서 스트리밍 스레드에서 애플리케이션으로 전달하는 것을 관리하는 시스템입니다. 버스의 이점은 애플리케이션이 GStreamer가 많은 스레드로 동작함에도 GStreamer를 사용하는데 스레드 회피가 필요 없다는 것입니다.

GLib/GTK+의 메인 루프를 실행하고, 버스를 확인하는 시나리오를 추가합니다. GLib 메인 루프가 버스의 새로운 메시지를 확인하고, 새로운 메시지가 도착하면 바로 알려줍니다.

 

gst_bus_add_signal_watch() 함수를 이용하여 bus를 추가합니다. 해당 함수는 pipeline의 bus에 메시지 콜백 함수를 첨부시키는 함수입니다. bus에 메시지가 들어올 때마다 콜백 함수가 호출됩니다.

gst_bus_add_signal_watch() 함수 사용 시에는 아래와 같이 messeage type과 특정 callback 함수를 지정해주어야 합니다.

    /* Add function to watch bus */
    data.src_bus = gst_element_get_bus(data.src_pipeline);
    gst_bus_add_signal_watch(data.src_bus);
    g_signal_connect(G_OBJECT(data.src_bus), "message", (GCallback)src_message_cb, &data);
/* called when we get a GstMessage from the source pipeline when we get EOS, we
 * notify the appsrc of it. */
static void src_message_cb(GstBus *bus, GstMessage *msg, CustomData *data)
{
    switch (GST_MESSAGE_TYPE (msg)) {
    case GST_MESSAGE_ERROR: {
        GError *err;
        gchar *debug_info;

        gst_message_parse_error(msg, &err, &debug_info);
        g_print("Error: %s\n", err->message);
        g_error_free(err);
        g_free(debug_info);

        gst_element_set_state(data->src_pipeline, GST_STATE_READY);
        g_main_loop_quit(data->loop);
        break;
    }
    case GST_MESSAGE_EOS: {
        /* end-of-stream */
        gst_element_set_state(data->src_pipeline, GST_STATE_READY);
        g_main_loop_quit(data->loop);
        break;
    }
    default:
        /* Unhandled message */
        break;
    }
}

상태

엘리먼트 생성 후, 엘리먼트는 아직 어떠한 실제 행동을 하지 않습니다. 엘리먼트가 어떤 행동을 하도록 만들려면 엘리먼트의 상태를 변경해주어야 합니다. GStreamer는 특정한 의미를 담고 있는 4가지 상태를 가지고 있습니다.

 

그 4가지 상태는

GST_STATE_NULL : 요소의 기본 상태입니다. 이 상태에서는 리소스가 할당되지 않습니다. 그래서 이 상태로 변경하면 모든 리소스는 해제됩니다. 
GST_STATE_READY : 요소의 준비 상태입니다. 엘리먼트는 모든 전역 리소스(스트림 내에서 유지될 수 있는 리소스)를 할당합니다. 이것은 디바이스를 열고, 버퍼를 할당하는 등의 행동으로 생각할 수 있습니다. 그러나 스트림은 이 상태에서 열지는 않습니다. 그래서, 스트림의 위치가 자동적으로 0이 됩니다. 스트림이 이전에 열렸을 경우, Ready 상태로 올 때 스트림을 닫습니다. 그리고 프로퍼티와 위치 정보 등은 초기화됩니다.
GST_STATE_PAUSED : 이 상태에서 엘리먼트는 스트림을 열지만 처리를 하지 않습니다. 엘리먼트가 스트림을 수정하는 것이 허용되면, 데이트를 읽고 처리하여 PLAYING 상태로 이동할 준비를 합니다. 그러나 클락이 동작할 때와 같이 데이터가 플레이되지 않습니다. 요약하자면, 클락이 동작하는 것만 제외하면 PAUSED 상태는 PLAYING 상태와 똑같습니다.엘리먼트는 PLAYING 상태로 최대한 빨리 이동하기 위하여 PAUSED 상태에서 관련된 준비를 합니다. 예를 들어, 비디오와 오디오 출력은 데이터가 도착하는 것을 기다리고, 큐에 데이터를 넣습니다. 그래서 상태가 변경될 때, 바로 재생을 시작할 수 있도록 준비합니다. 또한, 비디오 싱크는 이미 첫 프레임을 재생할 수 있습니다(비디오 싱크가 클락에 아직 영향을 주지 않았기 때문에). 오토 플러거는 이미 파이프라인으로 연결된 엘리먼트를 같은 상태로 만듭니다. 그러나, 코덱과 필터 같은 다른 엘리먼트 대부분은 PAUSED상태에서 아무것도 할 필요가 없습니다.
GST_STATE_PLAYING : PLAYING 상태에서는 클락이 동작하는 것을 제외한 모든 것이 PAUSED상태와 같습니다.

 

gst_element_set_state() 함수를 이용하여, 엘리먼트의 상태를 설정할 수 있습니다. 엘리먼트의 상태를 바꾸면, GStreamer는 내부적으로 모든 중간 상태를 거쳐 상태를 설정합니다. 그래서 NULL에서 PLAYING 상태로 설정하면, GStreamer는 내부적으로 READY와 PAUSED 상태를 거쳐서 상태를 변경합니다.

 

GST_STATE_PLAYING 상태로 전환하면, 파이프라인은 자동으로 데이터를 처리합니다. 어떠한 형태로든지 반복할 필요가 없습니다. 내부적으로 GStreamer는 작업을 수행할 스레드를 생성합니다. GStreamer는 GstBus를 이용해 파이프라인 스레드에서 애플리케이션 스레드로 메시지를 전달합니다.

    gst_element_set_state(data.src_pipeline, GST_STATE_PLAYING);

 

데이터 GMainLoop유형은 기본 이벤트 루프를 나타냅니다. GMainLoop은 g_main_loop_new(). 초기 이벤트 소스를 추가한 후 g_main_loop_run()호출됩니다. 이는 각 이벤트 소스에서 새 이벤트를 지속적으로 확인하고 전달합니다. 

/* Create a GLib Main Loop and set it to run */
data.main_loop = g_main_loop_new (NULL, FALSE);
g_main_loop_run (data.main_loop);

 

Gstreamer 사용 후 종료 시에는 element 상태를 GST_STATE_NULL 상태로 변경 후 파이프라인과 mainloop를 free 시켜 줍니다.

    gst_element_set_state(data.src_pipeline, GST_STATE_NULL);
    
    /* Free the buffer now that we are done with it */
    gst_object_unref(data.src_pipeline);

    g_main_loop_unref (data.loop);

전체 코드 :

#include <gst/gst.h>
#include <string.h>
#include <gst/app/gstappsrc.h>
#include <gst/app/gstappsink.h>

typedef struct _CustomData
{

	GMainLoop *loop;
	
//src
	GstBus *src_bus; GstElement *src_pipeline;
//common
	GstElement *app_sink, *video_source;


} CustomData;

/* The appsink has received a buffer */
static GstFlowReturn new_sample(GstElement *sink, CustomData *data)
{
    GstSample *sample; 
    GstFlowReturn ret = GST_FLOW_OK;

  /* Retrieve the buffer */
    g_signal_emit_by_name (data->app_sink, "pull-sample", &sample, NULL);

    if (sample)
    {        
        /* The only thing we do in this example is print a * to indicate a received buffer */
	g_print("*\n");
  	 
        gst_sample_unref (sample);

        return GST_FLOW_OK;

    }
    else
    {
        g_print ("could not make snapshot\n");
        
        return GST_FLOW_ERROR;
    }

    return GST_FLOW_ERROR;
}

/* called when we get a GstMessage from the source pipeline when we get EOS, we
 * notify the appsrc of it. */
static void src_message_cb(GstBus *bus, GstMessage *msg, CustomData *data)
{
    switch (GST_MESSAGE_TYPE (msg)) {
    case GST_MESSAGE_ERROR: {
        GError *err;
        gchar *debug_info;

        gst_message_parse_error(msg, &err, &debug_info);
        g_print("Error: %s\n", err->message);
        g_error_free(err);
        g_free(debug_info);

        gst_element_set_state(data->src_pipeline, GST_STATE_READY);
        g_main_loop_quit(data->loop);
        break;
    }
    case GST_MESSAGE_EOS: {
        /* end-of-stream */
        gst_element_set_state(data->src_pipeline, GST_STATE_READY);
        g_main_loop_quit(data->loop);
        break;
    }
    default:
        /* Unhandled message */
        break;
    }
}

int
main (int argc, char *argv[])
{
    CustomData data;
    
  /* Initialize GStreamer */
    gst_init(NULL, NULL);

  /* Create a GLib Main Loop and set it to run */
    data.loop = g_main_loop_new(NULL, FALSE);
        
    /* Create the elements */	
    data.video_source 		= gst_element_factory_make("videotestsrc", "video_source");
    data.app_sink 		= gst_element_factory_make("appsink", "app_sink");
    
    /* Create the empty pipeline */
    data.src_pipeline 	= gst_pipeline_new("src_pipeline");
    
    if(!data.src_pipeline || !data.video_source || !data.app_sink) {
        g_printerr ("Not all elements could be created.\n");
        return -1;
    }

    g_object_set(data.app_sink, "emit-signals", TRUE, NULL);
    g_signal_connect(data.app_sink, "new-sample", G_CALLBACK(new_sample), &data);
    
    /* Build the pipeline */
    gst_bin_add_many(GST_BIN(data.src_pipeline), data.video_source, data.app_sink, NULL);
    
    /* Link the elements together */
    if(gst_element_link(data.video_source, data.app_sink) != TRUE)
    {
        g_printerr("Elements could not be linked.\n");
        gst_object_unref(data.src_pipeline);
        return -1;
    }

    /* Add function to watch bus */
    data.src_bus = gst_element_get_bus(data.src_pipeline);
    gst_bus_add_signal_watch(data.src_bus);
    g_signal_connect(G_OBJECT(data.src_bus), "message", (GCallback)src_message_cb, &data);
    gst_object_unref(data.src_bus);
            
    gst_element_set_state(data.src_pipeline, GST_STATE_PLAYING);
    
    g_main_loop_run(data.loop);
    
    gst_element_set_state(data.src_pipeline, GST_STATE_NULL);
    
    /* Free the buffer now that we are done with it */
    gst_object_unref(data.src_pipeline);

    g_main_loop_unref (data.loop);
        
    return 0;
}

 

아래 명령어를 통해 build를 진행한다.

g++ main.cpp -o main `pkg-config --cflags --libs gstreamer-1.0`

 

실행 시 아래와 같이 출력되게 된다. 

 

참고 :

https://gstreamer.freedesktop.org/documentation/tutorials/basic/short-cutting-the-pipeline.html?gi-language=c

 

Basic tutorial 8: Short-cutting the pipeline

Basic tutorial 8: Short-cutting the pipeline Please port this tutorial to python! Please port this tutorial to javascript! Goal Pipelines constructed with GStreamer do not need to be completely closed. Data can be injected into the pipeline and extracted f

gstreamer.freedesktop.org

https://studyingdever.tistory.com/9

 

II. 애플리케이션 만들기 : 7장. 버스

버스는 메시지를 스레드 컨텍스트에서 스트리밍 스레드에서 애플리케이션으로 전달하는 것을 관리하는 시스템입니다. 버스의 이점은 애플리케이션이 GStreamer가 많은 스레드로 동작함에도 GStrea

studyingdever.tistory.com

https://studyingdever.tistory.com/7

 

II. 애플리케이션 만들기 : 5장. 엘리먼트 (3)

5.4. 엘리먼트 팩토리에 대한 자세한 내용이전 섹션에서, 엘리먼트 인스턴스를 만드는 방법으로 GstElementFactory에 대해 이미 간단하게 소개를 했습니다. 그러나 엘리먼트 팩토리는 그보다 더 많은

studyingdever.tistory.com