[GStreamer] Push, Pull 방식

GStreamer 2017.04.05 16:08 Posted by 김한별 behonestar


이 글은 GStreamer 튜토리얼 문서 Different scheduling modes를 요약한 글입니다.


엘리먼트의 Pad는 Push 방식과 Pull 방식의 스케쥴링 모드를 지원합니다. 대표적으로 아래와 같은 use-case가 존재합니다.


1. 모든 엘리먼트의 패드들이 push 모드인 경우

① 첫번째 엘리먼트는 다음 엘리먼트의 sinkpad에게 버퍼를 push하는 작업을 반복합니다.

② 두번째 엘리먼트는 sinkpad에 등록된 _chain() 함수를 통해 전달받은 버퍼를 처리합니다.

③ _chain() 함수는 gst_pad_push() 함수를 통해 다음 엘리먼트의 sinkpad에게 버퍼를 push합니다.



2. sinkpad는 pull 모드, srcpad는 push 모드인 경우

① sinkpad가 pull 모드로 활성화되면 사용자가 정의한 GstTask 스레드가 시작됩니다.

② 해당 스레드는 gst_pad_pull_range() 함수를 통해 이전 엘리먼트의 버퍼로부터 특정 범위의 데이터를 얻어와서 데이터를 처리합니다. sinkpad가 데이터의 흐름을 제어하게 되는 것이죠.

③ gst_pad_push() 함수를 통해 다음 엘리먼트의 sinkpad에게 버퍼를 push합니다.


* sinkpad를 pull 모드로 설정하는 방법

엘리먼트의 상태가 READY -> PAUSED로 변경될 때 패드가 활성화됩니다. 내부적으로는 _activate() 함수와 _activate_mode() 함수가 호출됩니다. pull 모드로 변경하려면 이 두가지 함수를 오버라이딩하여 스케쥴링 모드를 변경하면 됩니다. 오버라이딩하지 않으면 기본값인 push 모드로 동작합니다.


1) _activate() 함수 오버라이딩 

static void

gst_my_filter_init (GstMyFilter * filter)

{

  [...]

  gst_pad_set_activate_function (filter->sinkpad, gst_my_filter_activate);

  [...]

}


2) 이전 엘리먼트의 srcpad가 pull 모드를 지원하면, 현재 엘리먼트의 sinkpad도 pull 모드로 설정

static gboolean

gst_my_filter_activate (GstPad * pad, GstObject * parent)

{

  GstQuery *query;

  gboolean pull_mode;


  query = gst_query_new_scheduling ();


  if (gst_pad_peer_query (pad, query)) {

    pull_mode = gst_query_has_scheduling_mode_with_flags (query,

        GST_PAD_MODE_PULL, GST_SCHEDULING_FLAG_SEEKABLE);


    if (pull_mode) {

      gst_query_unref (query);

      return gst_pad_activate_mode (pad, GST_PAD_MODE_PULL, TRUE);

    }

  }


  gst_query_unref (query);

  return gst_pad_activate_mode (pad, GST_PAD_MODE_PUSH, TRUE);

}


3) _activate_mode() 함수 오버라이딩

static void

gst_my_filter_init (GstMyFilter * filter)

{

  [...]

  gst_pad_set_activatemode_function (filter->sinkpad, gst_my_filter_activate_mode);

  [...]

}


4) pad가 pull 모드로 활성화되면 GstTask 스레드 실행, 비활성화되면 GstTask 스레드 종료

static gboolean

gst_my_filter_activate_mode (GstPad    * pad,

                 GstObject * parent,

                 GstPadMode  mode,

                 gboolean    active)

{

  gboolean res;

  GstMyFilter *filter = GST_MY_FILTER (parent);


  switch (mode) {

    case GST_PAD_MODE_PUSH:

      res = TRUE;

      break;

    case GST_PAD_MODE_PULL:

      if (active) {

        filter->offset = 0;

        res = gst_pad_start_task (pad,

            (GstTaskFunction) gst_my_filter_loop, filter, NULL);

      } else {

        res = gst_pad_stop_task (pad);

      }

      break;

    default:

      /* unknown scheduling mode */

      res = FALSE;

      break;

  }

  return res;

}


5) GstTask 스레드 구현 : 이전 엘리먼트로부터 데이터를 읽어와서 다음 엘리먼트에게 push 

    static void

    gst_my_filter_loop (GstMyFilter * filter)

    {

      GstFlowReturn ret;

      guint64 len;

      GstFormat fmt = GST_FORMAT_BYTES;

      GstBuffer *buf = NULL;


      [...]


      /* now, read BLOCKSIZE bytes from byte offset filter->offset */

      ret = gst_pad_pull_range (filter->sinkpad, filter->offset,

          BLOCKSIZE, &buf);


      [...]


      /* now push buffer downstream */

      ret = gst_pad_push (filter->srcpad, buf);


      [...]

    }



3. 현재 엘리먼트의 모든 패드들이 pull 모드인 경우

srcpad가 pull 모드로 동작한다는 것은 다음 엘리먼트의 sinkpad가 gst_pad_pull_range()를 통해 데이터를 가져갈 수 있도록 지원하겠다는 것을 의미합니다.


sinkpad가 pull 모드로 동작한다는 것은 gst_pad_pull_range()를 통해 이전 엘리먼트로부터 데이터를 가져오겠다는 것을 의미합니다. 


그런데 sinkpad와 srcpad가 모두 pull 모드이면 엘리먼트는 다음 엘리먼트에 종속되어 버립니다. 다음 엘리먼트가 gst_pad_pull_range()를 호출하면, 현재 엘리먼트도 gst_pad_pull_range()를 호출하여 이전 엘리먼트로부터 데이터를 가져와야 합니다. 따라서 sinkpad를 pull 모드로 설정하되 GstTask 스레드는 실행되지 않도록 합니다.


* srcpad를 pull 모드로 설정하는 방법

다음 엘리먼트의 sinkpad가 gst_pad_pull_range()를 통해 데이터를 가져갈 수 있도록 _get_range() 함수를 등록해줍니다. 


1) _get_range() 함수 오버라이딩

static void

gst_my_filter_init (GstMyFilter * filter)

{

  [...]

  gst_pad_set_getrange_function (filter->srcpad, gst_my_filter_get_range);

  [...]

}


2) 이전 엘리먼트로부터 데이터를 읽어와서 outbuf에 담고 데이터 처리

static GstFlowReturn

gst_my_filter_get_range (GstPad     * pad,

             GstObject  * parent,

             guint64      offset,

             guint        length,

             GstBuffer ** outbuf)

{


  GstMyFilter *filter = GST_MY_FILTER (parent);

  [...]

  /* pull data */

  gst_pad_pull_range (filter->sinkpad, filter->offset, BLOCKSIZE, outbuf);
  

  /* process data */
  [...]


  return GST_FLOW_OK;

}


끝.

댓글을 달아 주세요

[GStreamer] RTSP의 H264 영상 Dump 방법

GStreamer 2017.03.29 10:03 Posted by 김한별 behonestar

RTSP로 스트림되는 H264 영상을 GStreamer로 Dump하는 방법입니다. rtph264depay를 통해 출력되는 h264 데이터의 stream-format을 byte-stream으로 지정해줘야 streameye와 같은 툴로 분석이 가능합니다.


gst-launch-1.0 rtspsrc location="rtsp://media.smart-streaming.com/mytest/mp4:sample.mp4" ! rtph264depay ! video/x-h264,stream-format=byte-stream ! h264parse ! filesink location="/path/to/video.h264"



*H264 Byte Stream 포맷?

- SPS > PPS > I/P/B 슬라이스 순서로 나열됩니다.

- 각 프레임은 SCP(0x000001 또는 0x00000001)로 시작합니다.

- SCP와 SCP 사이에는 완전한 프레임 한장이 들어갑니다.

- 각 Access Unit 앞에 AUD(Access Unit Delimiter)를 넣어서 Access Unit의 시작을 표현할 수 있습니다.

 (※ HTML5 video 태그로 h264 영상을 재생할 때 AUD가 없는 영상은 재생이 안되더라구요!)




*H264 Access Unit?

- MPEG 규격의 비디오와 오디오 데이터를 다룰 때 기본 단위입니다.

- Access Unit 하나의 구조는 아래와 같습니다.

- Access Unit Delimiter는 00 00 00 01 09 xx 포맷을 따릅니다. (I: 0x10, P: 0x30)



댓글을 달아 주세요

[GStreamer] 디버깅 로그 출력하기

GStreamer 2017.03.29 09:12 Posted by 김한별 behonestar

rtspsrc 플러그인의 GST_DEBUG ()에 의해 기록되는 로그를 출력하는 방법입니다.


export GST_DEBUG=rtspsrc:DEBUG


참고

https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer/html/gst-running.html

댓글을 달아 주세요

[GStreamer] h264 profile 확인하는 방법

GStreamer 2017.03.27 20:16 Posted by 김한별 behonestar

명령어 입력

1. rtsp에서 h264 파싱 후 디코딩

2. x264enc 사용하여 h264로 재인코딩

gst-launch-1.0 -v rtspsrc location="rtsp://media.smart-streaming.com/mytest/mp4:sample.mp4" ! rtph264depay ! video/x-h264,stream-format=avc ! h264parse ! identity silent=false name=before ! avdec_h264 ! x264enc dct8x8=false cabac=false bframes=0 ! identity silent=false name=after ! mpegtsmux ! hlssink max-files=5 location=/home/ubuntu/media/playok/segment%05d.ts playlist-location=/home/ubuntu/media/playok/playlist.m3u8


실행 결과

rtsp로 전달받은 데이터는 h264 baseline 프로파일이며, x264enc로 인코딩한 데이터는 h264 main 프로파일인 것을 확인할 수 있다. 

/GstPipeline:pipeline0/GstIdentity:before: last-message = event   ******* (before:sink) E (type: tag (20510), GstTagList-stream, taglist=(taglist)"taglist\,\ video-codec\=\(string\)\"H.264\\\ \\\(Constrained\\\ Baseline\\\ Profile\\\)\"\;";) 0x7f2a0c007f40


...


/GstPipeline:pipeline0/GstIdentity:after.GstPad:src: caps = "video/x-h264\,\ stream-format\=\(string\)byte-stream\,\ alignment\=\(string\)au\,\ level\=\(string\)2.1\,\ profile\=\(string\)main\,\ width\=\(int\)424\,\ height\=\(int\)240\,\ pixel-aspect-ratio\=\(fraction\)1/1\,\ framerate\=\(fraction\)0/1"



댓글을 달아 주세요

[GStreamer] 윈도우 Visual Studio 개발 환경 설정

GStreamer 2017.03.06 19:42 Posted by 김한별 behonestar

Windows7 64bit, Visual Studio 10 기준으로 기술하였습니다.



GStreamer 1.10.4 설치

1. 다운로드 

2. gstreamer-1.0-x86_64-1.10.4.msi / gstreamer-1.0-devel-x86_64-1.10.4.msi 설치

 ※ 설치 경로를 기억하세요. 저는 D:\gstreamer에 설치하였습니다.



환경변수 등록

1. 내 컴퓨터 > 속성 > 고급시스템 설정 > 환경변수 > 시스템 변수 > Path [편집]

2. ..;D:\gstreamer\1.0\x86_64\bin 덧붙이고 [확인]


SDK 템플릿 Visual Studio로 복사

1. D:\gstreamer\1.0\x86_64\share\vs\2010\wizard의 파일 3개 복사

 - gst-sdk-template.ico

 - gst-sdk-template.vsdir

 - gst-sdk-template.vsz

2. C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcprojects에 붙여넣기

3. D:\gstreamer\1.0\x86_64\share\vs\2010\gst-template 폴더 복사

4. C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\VCWizards에 붙여넣기



Windows DDK 7.1.0 설치

1. 다운로드

2. 설치 후 경로 확인 (C:\WinDDK\7600.16385.1)



Visual Studio 10 설정

1. Visual Studio 10 실행

2. New > Project > Visual C++ > gstreamer template 선택하고 프로젝트 생성


3. 프로젝트 속성 > 구성 속성 > 디버깅 > 작업 디렉토리 > 편집 >  $(GSTREAMER_1_0_ROOT_X86_64)\bin 입력 > 확인


4. 프로젝트 속성 > 구성 속성 > C/C++ > 일반 > 추가 포함 디렉터리 > 편집에서 GStreamer 경로가 포함된 것을 확인


만약 포함되어 있지 않다면, 속성 관리자 > 프로젝트 > 기존 속성 시트 추가 > D:\gstreamer\1.0\x86_64\share\vs\2010\msvc 폴더의 x86.props(32비트) 또는 x86_64.props(64비트) 열기




5. 속성 관리자 > Debug > x86_64 > config > DDK 경로 들어간 것을 확인


6. Hello World 코드 빌드 및 실행

#include <gst/gst.h>


int main(int argc, char *argv[]) {

  GstElement *pipeline;

  GstBus *bus;

  GstMessage *msg;


  /* Initialize GStreamer */

  gst_init (&argc, &argv);


  /* Build the pipeline */

  pipeline = gst_parse_launch ("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL);


  /* Start playing */

  gst_element_set_state (pipeline, GST_STATE_PLAYING);


  /* Wait until error or EOS */

  bus = gst_element_get_bus (pipeline);

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


  /* Free resources */

  if (msg != NULL)

    gst_message_unref (msg);

  gst_object_unref (bus);

  gst_element_set_state (pipeline, GST_STATE_NULL);

  gst_object_unref (pipeline);

  return 0;

}



참고

  • https://gstreamer.freedesktop.org/documentation/installing/on-windows.html


댓글을 달아 주세요

  1. 안녕하세요 2017.06.14 18:10  댓글주소  수정/삭제  댓글쓰기

    도움이 많이 되었습니다. 감사합니다.

[GStreamer] Pad-added Signal

GStreamer 2017.03.06 11:50 Posted by 김한별 behonestar

GStreamer로 RTSP 영상을 화면에 출력하기 위해 이렇게 작성하였습니다.


int main(int argc, char *argv[]) {

GstElement *pipeline, *source, *demux, *parse, *filter, *decodebin, *sink;

GstBus *bus;

GstMessage *msg;

GstStateChangeReturn ret;


gst_init (&argc, &argv);


// Pipeline과 Element 생성

pipeline = gst_pipeline_new ("video player");

source = gst_element_factory_make("rtspsrc", "source");

demux = gst_element_factory_make("rtph264depay", "depayl");

parse = gst_element_factory_make("h264parse", "parse");

filter = gst_element_factory_make("capsfilter", "filter");

decodebin = gst_element_factory_make ("openh264dec", "decode");

sink = gst_element_factory_make("autovideosink", "sink");


if (!pipeline || !source || !demux || !parse || !filter || !decodebin || !sink) {

g_printerr ("Not all elements could be created.\n");

return -1;

}


g_object_set (source, "location", "rtsp://mpv.cdn3.bigCDN.com:554/bigCDN/definst/mp4:bigbuckbunnyiphone_400.mp4", NULL);


// Pipeline에 Element 추가

gst_bin_add_many (GST_BIN (pipeline), source, demux, parse, filter, decodebin, sink, NULL);

// 각 Element들을 연결

if (gst_element_link_many (source, demux, parse, filter, decodebin, sink, NULL) != TRUE) {

g_printerr ("Elements could not be linked.\n");

gst_object_unref (pipeline);

return -1;

}

// RTSP Play

ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);


//... 생략 ...

}


그런데 Elements could not be linked 오류로 빠지네요. 그 이유는 바로 rtspsrc 플러그인의 Pad 타입이 'Sometimes'이기 때문입니다. 


$ gst-inspect-1.0 rtspsrc


Pad Templates:

  SRC template: 'stream_%u'

    Availability: Sometimes

    Capabilities:

      application/x-rtp

      application/x-rdt


즉, source 엘리먼트는 Pad가 없는 상태로 생성됩니다. Pad는 RTSP가 PLAY 될 때 추가됩니다. 그래서 source와 demux 엘리먼트 간 연결은 PLAY 이후에만 가능합니다.




source 엘리먼트에 Pad가 추가되는 것을 감지하여 demux 엘리먼트와 연결되도록 하려면 "pad-added" 시그널을 받아서 처리하면 됩니다.


int main(int argc, char *argv[]) {


// ...생략...


g_signal_connect_object(source, "pad-added", G_CALLBACK(on_pad_added), demux, G_CONNECT_AFTER);


ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);


// ...생략...

}


콜백되는 on_pad_added 함수에서는 인자로 전달된 demux 엘리먼트의 sink pad와 source의 src pad를 연결합니다.


static void on_pad_added (GstElement *element, GstPad *pad, gpointer data) {

GstPad *sinkpad;

GstElement *demux = (GstElement *) data;


sinkpad = gst_element_get_static_pad (demux, "sink");

gst_pad_link (pad, sinkpad);

gst_object_unref (sinkpad);

}




전체 소스는 파일로 첨부하였습니다.

rtsp_play.cpp



참고


댓글을 달아 주세요