반응형

Pad negotiation

Gstremaer에서 개발하는데 잘 모르고 개발이 잘 안되는 가장 큰 부분이 Pad Negotiation이다. 대충 Printf로 찍어보며 개발이 안된다. 원리를 이해를 해야 할거 같아서 페이지에서 정리할 겸 번역하며 정리를 해보았다.

Negotiation

Basic Rules

These simple rules must be followed:

  1. downstream suggests formats
  2. upstream decides on format

4 Queris/events used in negotiation

  1. GST_QUERY_CAPS: get possible formats
  2. GST_QUERY_ACCEPT_CAPS: check if format is possible
  3. GST_EVENT_CAPS: configure format (downstream)
  4. GST_EVENT_RECONFIGURE: inform upstream of possibly new caps

Query

Pad query는 GstCaps로 이용하며 재귀적으로 동작할 수 있음

  • filter (in) GST_TYPE_CAPS (default NULL): - a GstCaps to filter the results against
  • caps (out) GST_TYPE_CAPS (default NULL): - the result caps

Pad는 peer pad에 주어진 caps가 지원되는지 물어볼 수 잇따. 이것은 ACCEPT_CAPS query를 통해 이루어진다. 이때 caps는 반드시 fixed여야 한다. ACCEPT_CAPS는 Recursive로 동작하지 않는다. 단순히 accept되어지면 TRUE를 return한다.

  • caps (in) GST_TYPE_CAPS: - a GstCaps to check, must be fixed
  • result (out) G_TYPE_BOOLEAN (default FALSE): - TRUE if the caps are accepted

Event

mediaformat 이 negotiated 상태가 되면 peer element는 CAPS event로 notified된다. 이떄 caps는 반드시 fixed여야 한다.
caps GST_TYPE_CAPS: - the negotiated GstCaps, must be fixed

Operation

Gstreamer는 두가지 scheduling mode를 지원하며 이것은 push mode와 pull mode이다 두가지는 목표가 다른 서로 다른 메커니즘으로 동작한다.

Push-mode negotiation

Push-mode negotiation은 element가 buffer를 push하고 format을 결정하기를 원할때 이루어지며, 이는 downstream negotiation으로 불리는데 이는 upstream element는 downstream element에 대하여 format을 결정하기 때문이고 이것은 일반적인 케이스이다.

Negotiation은 또한 downstream element가 upstream element로 부터 다른 data format을 받기를 원할때도 이루어진다.

Basic negotiation은 아래와 같다.

  • GstCaps는 buffer의 contents에 대한 describe의 event를 push하기 이전에 참조 카운트를 한다.
  • Element는 이어질 Buffer의 processing이전에 CAPS event로서 새로운 format을 전달받기 위하여 스스로 reconfigure되어야 한다. 만약 data type이 not acceptable일 경우, element는 event 를 거절(refuse)해야 한다. Element는 chain function에서 buffer 유입에 대해서 GST_FLOW_NOT_NEGOTIATED를 return해야 한다.
  • Downstream element는 upstream 에 대해서 RECONFIGURE event를 보냄으로, stream format이 변경에 대해 요청을 할 수 있다. Upstream element은 RECONFIGURE event를 수신하면 new format에 대해서 다시 negotitation을 수행할 것이다.

아래는 source pad의 negotiation을 시작하는 일반적인 Flow이다.

            src              sink
             |                 |
             |  querycaps?     |
             |---------------->|
             |     caps        |
select caps  |< - - - - - - - -|
from the     |                 |
candidates   |                 |
             |                 |-.
             |  accepts?       | |
 type A      |---------------->| | optional
             |      yes        | |
             |< - - - - - - - -| |
             |                 |-'
             |  send_event()   |
send CAPS    |---------------->| Receive type A, reconfigure to
event A      |                 | process type A.
             |                 |
             |  push           |
push buffer  |---------------->| Process buffer of type A
             |                 |

아래는 구현에 대한 Pseudo code 예제이다.

[element wants to create a buffer]
if not format
  # see what we can do
  ourcaps = gst_pad_query_caps (srcpad)
  # see what the peer can do filtered against our caps
  candidates = gst_pad_peer_query_caps (srcpad, ourcaps)

  foreach candidate in candidates
    # make sure the caps is fixed
    fixedcaps = gst_pad_fixate_caps (srcpad, candidate)

    # see if the peer accepts it
    if gst_pad_peer_accept_caps (srcpad, fixedcaps)
      # store the caps as the negotiated caps, this will
      # call the setcaps function on the pad
      gst_pad_push_event (srcpad, gst_event_new_caps (fixedcaps))
      break
    endif
  done
endif

Negotiate allocator/buffer pool with the ALLOCATION query

    buffer = gst_buffer_new_allocate (NULL, size, 0);
    # fill buffer and push

아래는 sink pad의 renegotiation을 시작하는 일반적인 Flow이다.

            src              sink
             |                 |
             |  accepts?       |
             |<----------------| type B
             |      yes        |
             |- - - - - - - - >|-.
             |                 | | suggest B caps next
             |                 |<'
             |                 |
             |   push_event()  |
 mark      .-|<----------------| send RECONFIGURE event
renegotiate| |                 |
           '>|                 |
             |  querycaps()    |
renegotiate  |---------------->|
             |  suggest B      |
             |< - - - - - - - -|
             |                 |
             |  send_event()   |
send CAPS    |---------------->| Receive type B, reconfigure to
event B      |                 | process type B.
             |                 |
             |  push           |
push buffer  |---------------->| Process buffer of type B
             |                 |

Pull-mode negotiation

Pull mode Pipeline 은 push mode와 negotiation 요구사항이 다름. Push mode는 아래의 두가지 Use cases에 최적화 되어 있음

  • Media file Playback에 대해서 어떤 정보가 전달되어야 하는 고나점에서의 decoder, demuxer의 관점
  • Live source recording에 대해서 source element 다음에 capsfilter를 추가하는데 익숙함, 그러므로 caps information flow는 Pipeline의 sink들을 향해서 source의 잠재적인 caps를 통해 user에 의해 결정됨.

그에 반해 Pull mode는 아래의 전형적인 use case들이 있음

  • RTP와 같은 손실이 있는 Source를 재생할 떄에 Latency에 대한 추가 정보로 품질을 향상 시킬 수가 있음.
  • 오디오 합성에서, 오디오 API는 필요한 샘플 수만 생성하도록 조정되며, 일반적으로 하드웨어 인터럽트에 의해 구동되어 DMA 버퍼나 Jack[0] 포트 버퍼를 채우는 방식으로 작동함.
  • 저지연 효과 처리를 위해, 필터는 데이터를 링 버퍼에서 싱크로 전송할 때 적용되어야 하며, 미리 적용되지 않아야 함. 예를 들어, alsasink의 내부 링버퍼 스레드를 푸시 모드에서 사용하는 대신, wavsrc ! volume ! alsasink와 같은 구성 대신 wavsrc ! audioringbuffer ! volume ! alsasink로 사운드 카드 쓰기 스레드 내에 볼륨을 배치하는 방식이 있음.

Pull mode에서 문제는 sink가 gst_pad_pull_range()를 통해 몇 바이트를 가져와야 하는지 알기 위해 format을 알아야 한다는 점이 있음. 이는 데이터를 가져오기 전에 sink가 negotiation을 시작하여 format을 결정해야 함을 의미함.

RTP 및 저지연 재생은 일반적인 재생과 유사하게 정보가 downstream으로 흐름.

오디오 합성에서는 정보가 가장 많은 부분이 sink 쪽에 있으며, 이때 caps는 완전히 지정되지 않고 사용자가 샘플 속도 등을 선택해야 합니다. 이 과정은 gstreamer 외부에서나 capsfilter를 통해 수행될 수 있음.

싱크가 소스의 입력을 필요로 하는 경우 협상 단계가 필요하며, 풀 모드의 저지연 특성상 풀링 스레드 내에서 협상을 피해야 함.

Pull thread는 보통 PAUSED에서 PLAYING으로 상태가 변경될 때 시작되므로, 이 상태 변경 전에 negotiation을 완료해야 함.

따라서 caps negotiation은 SCHEDULING Query가 성공한 후, pulling thread가 시작되기 전에 수행해야 함.

Machanism

Sink는 SCHEDULING query를 통해 Upstream element들이 Pull based scheduing을 지원하는지 확인함.

Sink는 자신의 sink pad와 연결된 src pad에서 gst_pad_query_caps()의 결과를 교차하여 Negotiation 과정을 시작합니다. 간단한 Passthrough의 경우, 피어 패드의 caps through는 모든 sink pad에 대해 get_allowed_caps()를 호출한 결과의 교집합을 반환해야 합니다. 이를 통해 sink element는 Pipeline 전체의 기능을 파악합니다.

필요한 경우 sink element는 결과 caps를 고정하여 flow caps를 생성합니다. 이 시점부터 sink pad의 caps query는 이 fixed caps만 반환하며, Upstream element들은 이 형식만 생성할 수 있게 됩니다.

sink element가 sink pad에 caps를 설정하지 못했다면, 버스에 오류 메시지를 게시하여 Negotiation이 불가능했음을 알립니다.

Negotiation이 성공하면, sink pad와 모든 내부적으로 연결된 Upstream Pad가 Pull mode로 활성화됩니다. 일반적으로 이 작업은 Downstream element에서 Negotiation을 trigger하여, 이제 Sink pad의 최종 Fixed caps와 Negotiation하게 됩니다.

이 단계가 완료된 후, Sink element는 상태 변경 함수에서 ASYNC를 반환합니다. 첫 번째 버퍼가 Sink에 도착하면 상태가 PAUSED로 전환됩니다. 이는 Sink에서 ASYNC 반환 값을 기대하는 application에 일관된 API를 제공하기 위해 필요하며, Polling thread의 Context 외부에서 나머지 negotiation을 수행할 수 있도록 합니다."

Pattern

Negotiation에서 3가지 패턴을 구분할 수 있음:

  1. 고정(Fixed): 출력 형식을 선택할 수 없음.
    • Stream에 인코딩된 caps
    • 비디오/오디오 decoder가 일반적으로 사용
    • gst_pad_use_fixed_caps()를 사용
  2. 변환(Transform): CAPS가 수정되지 않음(passthrough)
    • 요소 속성에 따라 CAPS 변환 가능
    • 고정된 caps가 고정된 caps로 변환
    • 예: videobox
  3. 동적(Dynamic): 출력 형식을 선택할 수 있음
    • 변환 요소
    • Downstream caps에 따라 다르며, CAPS 쿼리 필요
    • 일반적으로 동일한 변환을 선호
    • 고정된 caps가 고정되지 않은 caps로 변환 가능

출처

https://gstreamer.freedesktop.org/documentation/additional/design/negotiation.html?gi-language=c
반응형
반응형

Gstreamer 개발시 VPN같은것을 사용해서 원격으로 개발할 경우 display port나 설정같은게 귀찮고 복잡하다.
autovideosink등을 이용해서 개발 할 떄에는 실제 현재 환경에서의 DISPLAY 값을 가져가기 때문에,
X11등으로 할때에는 바로 네트워크의 상태에 따라 테스트가 용이하지 않기도 하다.

실제로 영상이 어떻게 나오는지 실시간으로 알 필요가 없을 경우 아래와 같이 file out을 할 경우 개발하기 편하다.

$ gst-launch-1.0 \
  filesrc location=<FILE PATH> ! qtdemux ! decodebin ! videoconvert ! x264enc ! mp4mux ! filesink location=output.mp4
반응형
반응형

1장의 JPEG image 반복으로 display하기

 $ gst-launch-1.0 multifilesrc location=<FILE LOCATION> ! jpegdec ! videoconvert  ! autovideosink

1장의 mp4파일 demux하여 video만 display하기

 $ gst-launch-1.0 filesrc location=<FILE LOCATION> ! qtdemux ! decodebin ! videoconvert ! autovideosink

output 비디오 FPS 변경하기

 $ gst-launch-1.0  ... ! videorate ! video/x-raw,framerate=10/1 ! ...

output 비디오 scale 변경하시

 $ gst-launch-1.0 ... ! videoscale ! video/x-raw,width=640,height=360 ! ...

dot 파일 생성하여, Pipeline 도식화 하기

 $ GST_DEBUG_DUMP_DOT_DIR=./<PATH> gst-launch-1.0 ...

dot 파일 생성

#!/bin/bash

DOT_FILES_DIR="."
PNG_FILES_DIR="."

DOT_FILES=`ls $DOT_FILES_DIR | grep dot`

for D in $DOT_FILES; do
        PNG_FILE=`echo $D | sed s/.dot/.png/`
        echo "dot -Tpng $DOT_FILES_DIR/$D > $PNG_FILES_DIR/$PNG_FILE"
        dot -Tpng $DOT_FILES_DIR/$D > $PNG_FILES_DIR/$PNG_FILE

done

현재 폴더 전체 DOT파일 JPEG파일로 변경

Memory Leak 테스트

 $ GST_TRACERS="leaks(GstBuffer)=TRUE" gst-launch-1.0 ...

Plugin latency 테스트

 $ GST_TRACERS="latency" gst-launch-1.0 ...

Log file 생성

 $ GST_DEBUG_FILE=<FILENAME.log> gst-launch-1.0
반응형
반응형

Gstreamer launcher valgrind debugging

Gstreamer Plugin을 개발하다보면 Gstreamer 내에서의 메모리 관리와 작성하고자 하는 코드의 메모리 관리가 일관되지 못해 의도치 않은 Memory leak을 유발하는 경우가 많다. 이럴 경우 어디에서 Memory의 관리를 놓쳤는지 찾아야 하는데 Gstreamer launcher로 테스트를 할 경우 valgrind를 물리는 순간 정상 동작이 안되는 경우가 많다.

Intall Debug Symbol

Gstreamer


sudo apt install gstreamer1.0-*dbg gstreamer1.0-tools

glib


sudo apt install libglib2.0-0-dbg

Symbol을 설치하기 어려울 경우 아래와 같이 실행


echo "deb [http://ddebs.ubuntu.com](http://ddebs.ubuntu.com/) $(lsb_release -cs) main restricted universe multiverse deb [http://ddebs.ubuntu.com](http://ddebs.ubuntu.com/) $(lsb_release -cs)-updates main restricted universe multiverse deb [http://ddebs.ubuntu.com](http://ddebs.ubuntu.com/) $(lsb_release -cs)-proposed main restricted universe multiverse" | \ sudo tee -a /etc/apt/sources.list.d/ddebs.list

sudo apt install ubuntu-dbgsym-keyring

sudo apt update

sudo apt install libglib2.0-bin-dbgsym libglib2.0-0-dbgsym libglib2.0-dev-bin-dbgsym

Code를 Debugging 옵션으로 빌드


# In the following line:# --prefix /usr --libdir /usr/lib/aarch64-linux-gnu#     This is required when building GStreamer plug-ins. Other applications may ignore this#     Note that aarch64-linux-gnu is for AARCH64, change accordingly# CFLAGS#     If using C# CXXFLAGS#     If using C++

./configure --prefix /usr --libdir /usr/lib/aarch64-linux-gnu CFLAGS="-g -O0" CXXFLAGS="-g -O0"
make
sudo make install

Suppressions 파일 준비

Suppression은 Valgrind에서 특정 Leak에 대해서 제외하기 위해 사용되는 파일.

gst suppressions 파일

https://gitlab.freedesktop.org/gstreamer/common/-/blob/master/gst.supp

glib suppressions 파일

https://github.com/GNOME/glib/blob/main/tools/glib.supp

Valgrind 실행


G_SLICE=always-malloc G_DEBUG=gc-friendly valgrind \
--leak-check=full --leak-resolution=high --num-callers=20 --trace-children=yes \
--suppressions=path/to/gst.supp \
--suppressions=path/to/glib.supp \
gst-launch-1.0 videotestsrc num-buffers=10 ! fakesinkz

출처

https://developer.ridgerun.com/wiki/index.php/How_to_Analyze_GStreamer_with_Valgrind

반응형
반응형

Plugin 개발 할 때에 Memory Leak에 대해서 확인해보기 위해서 valgrind를 붙여보았는데, valgrind를 붙이면 launcher가 정상 동작 하지 않는다. 원인을 찾을 수 없어서 GStreamer에서 Memory leak에 대한 수단을 제공하지 않을까 해서 찾아보니 아래와 같이 Debugging을 할 수 있는 방법이 있다.

GST_DEBUG="GST_TRACER:7" GST_TRACERS="leaks" 

위와 같이 환경변수를 설정하고 동작시에 아래와 같이 종료 이후로 남아있는 메모리에 대해서 추적해준다.

0:00:33.676884568  6771 0x556451e88840 TRACE             GST_TRACER :0:: object-alive, type-name=(string)GstBuffer, address=(gpointer)0x7f835dc012c0, description=(string)buffer: 0x7f835dc012c0, pts 0:00:45.833522174, dts 99:99:99.999999999, dur 0:00:00.100000000, size 2764800, offset 458, offset_end 459, flags 0x0, ref-count=(uint)1, trace=(string);
0:00:33.676887333  6771 0x556451e88840 TRACE             GST_TRACER :0:: object-alive, type-name=(string)GstBuffer, address=(gpointer)0x7f834738a700, description=(string)buffer: 0x7f834738a700, pts 99:99:99.999999999, dts 99:99:99.999999999, dur 99:99:99.999999999, size 2764800, offset none, offset_end none, flags 0x0, ref-count=(uint)1, trace=(string);
0:00:33.676890357  6771 0x556451e88840 TRACE             GST_TRACER :0:: object-alive, type-name=(string)GstBuffer, address=(gpointer)0x7f86d44efe20, description=(string)buffer: 0x7f86d44efe20, pts 0:00:53.533522174, dts 99:99:99.999999999, dur 0:00:00.100000000, size 2764800, offset 535, offset_end 536, flags 0x0, ref-count=(uint)1, trace=(string);

Typename과 Address, ref-count 등을 남겨준다.
다만 이 메모리가 어떤것인지 자세히 나오지 않아서 이 방법에 대해서도 검토가 필요할 것 같다.

반응형
반응형
반응형
반응형

Buffer의 VideoMetadata 출력 방법

static void print_video_meta_data_all(GstBuffer *buf) {
  GstVideoMeta *meta = gst_buffer_get_video_meta(buf);
  if (meta) {
    g_print("Video Meta:\n");
    g_print("  format: %s\n", gst_video_format_to_string(meta->format));
    g_print("  width: %d\n", meta->width);
    g_print("  height: %d\n", meta->height);
    g_print("  flags: %d\n", meta->flags);
    g_print("  n_planes: %d\n", meta->n_planes);
    for (guint i = 0; i < meta->n_planes; i++) {
      g_print("  stride[%d]: %d\n", i, meta->stride[i]);
      g_print("  offset[%d]: %d\n", i, meta->offset[i]);
    }
    g_print("  map: %p\n", meta->map);
    g_print("  unmap: %p\n", meta->unmap);
  }
}

Buffer의 Metadata Count 출력 및 Metadata의 Type 출력 방법

static void print_all_meta_info(GstBuffer *buf){
  GstVideoMeta *meta = gst_buffer_get_video_meta(buf);
  if (meta)
  {
    gst_print("Video Meta Count : %d\n", meta->n_planes);
  }
  GstMeta *m;
  gpointer state = NULL;
  guint meta_count = 0;
  while ((m = gst_buffer_iterate_meta(buf, &state))) {
    g_print("Metadata API type: %s\n", g_type_name(m->info->api));
    meta_count++;
  }
  g_print("Meta count: %d\n", meta_count);
}

Buffer의 모든 Metadata 중에 LOCK 상태인 Metadata 찾는 방법

static void find_lock_meta(GstBuffer *buffer) {
  GstMeta *meta;
  gpointer state = NULL;

  while ((meta = gst_buffer_iterate_meta(buffer, &state))) {
    if (GST_META_FLAG_IS_SET(meta, GST_META_FLAG_LOCKED)) {
      g_print("Found locked meta: %s\n", g_type_name(meta->info->api));
    }
  }
}
반응형

+ Recent posts