2021년 10월 27일 수요일

YAML + GStreamer로 Pipeline 만들기

 파이프라인 명령어를 parse_launch로 입력하거나 코드로 작성해 두는 방식들은 수정하기 불편하고, 에러가 눈에 쉽사리 들어오지 않아 시작 Pipeline을 yaml로 수정할 수 있도록 만들어 보았다. Pad 등의 기능은 추가하지 않았다. 비슷한 패턴을 이용하여 구현할 수 있을 것이다. 

YAML 파일은 다음과 같이 작성했다.

element:
  camerasrc:
    element: v4l2src

  srccap:
    element: CAPS
    property: 
        MIME: video/x-raw
        width: 640
        height: 480
        framerate: 30/1
        format: YUY2

  vidconv1:
    element: videoconvert

  vidcap:
    element: CAPS
    property:         
        MIME: video/x-raw
        width: 640
        height: 480
        format: BGRx

  vidconv2:
    element: videoconvert

  timestamp_queue1:
    element: queue
    property:
      leaky: downstream
      max-size-buffers: 1

  timestamp_text:
    element: textoverlay
    property:
      text: ''
      valignment: bottom
      halignment: left
      font-desc: Sans, 12

  timestamp_queue2:
    element: queue
    property:
      leaky: downstream
      max_size_buffers: 1

  app-file-tee:
    element: tee

  appsink_queue:
    element: queue
    property:
      leaky: downstream
      max-size-buffers: 1

  appsink:
    element: appsink
    property:
      max-buffers: 1

  videofilesink_queue:
    element: queue
    property:
      leaky: downstream
      max-size-buffers: 1

  h264encoder:
    element: omxh264enc

  h264cap:
    element: CAPS
    property: 
        MIME: video/x-h264
        stream-format: byte-stream

  h264parse:
    element: h264parse

  tomp4:
    element: mp4mux

  tovideofile:
    element: filesink
    property:
      sync: false
      location: ''

link:
  camerasrc: srccap
  srccap: vidconv1
  vidconv1: vidcap
  vidcap: vidconv2
  vidconv2: timestamp_queue1
  timestamp_queue1: timestamp_text
  timestamp_text: timestamp_queue2
  timestamp_queue2: app-file-tee

  appsink_queue: appsink
  
  videofilesink_queue: h264encoder
  h264encoder: h264cap
  h264cap: h264parse
  h264parse: tomp4
  tomp4: tovideofile

  app-file-tee: [appsink_queue, videofilesink_queue]
 element가 될 요소들은 element 아래에 작성하고, 각 element의 name을 먼저 나오도록 했다. 각 element를 연결하기 위해 element의 이름을 받아 왼쪽에서 오른쪽으로 연결할 수 있게 했다. tee와 같이 여러개를 link하는 요소는 배열로 이름을 받았다.

 CapsFilter를 파싱할때 MIME이라는 예약어를 만들었다. 여기에 Capability의 MIME 종류가 들어간다. 그 외의 Capabilities는 다른 element와 마찬가지로 입력을 받는다.


YAML 파일을 불러들이고, 이를 나누는 함수는 다음과 같이 구현했다.

def set_launch_option(self, path):
    with open(path) as f:
        config = yaml.load(f, Loader=yaml.FullLoader)
        elements = config['element']
        links = config['link']
        super().set_pipe(elements, links)
def set_pipe(self, elements: dict, links: dict):
        add_list = {}
        try:
            for key in elements.keys():
                name = key
                element = elements[key]['element']
                
                if element == 'CAPS':
                    prpty = elements[key]['property']
                    cap_string = ''
                    args = [prpty['MIME'], ]

                    for prpty_key in prpty.keys():
                        args.append(prpty_key + '=' + str(prpty[prpty_key]))
                    cap_string = ", ".join(args)
                    caps = Gst.caps_from_string(cap_string)
                    tmp = Gst.ElementFactory.make("capsfilter", name)
                    tmp.set_property("caps", caps)
                else:
                    try:
                        prpty = elements[key]['property'].items()
                    except:
                        prpty = []
                    tmp = Gst.ElementFactory.make(element, name)
                    for p in prpty:
                        tmp.set_property(p[0], p[1])
                add_list.update({name : tmp})
            
            for key in add_list.keys():
                element = add_list[key]  
                self.pipe.add(element)

            for key in links.keys():
                frm = add_list[key]
                link = links[key]
                if type(link) == list:
                    for lnk in link:
                        frm.link(add_list[lnk])
                else:
                    to = add_list[link]
                    frm.link(to)
        except KeyError as e:
            errmessage = str(getattr(e, 'message', repr(e)))
            logging.error(GStreamerTag('Failed to create a pipeline : Config error', e))
        except Exception as e:
            errmessage = str(getattr(e, 'message', repr(e)))
            logging.error(GStreamerTag('Unknown Error : ', e))
 YAML의 element와 link의 값을 가져온다. 그리고 set_pipe 함수에서 ElementFactory와 link를 통해 Pipeline을 생성한다.

 element로 입력된 요소들을 받아, CAPS일 경우 CapsFilter로 만들어 element로 생성할 수 있도록 처리한다. MIME는 처음에 붙이고, 나머지는 리스트에 담은 후 ', '의 패턴으로 문자열 join하여 caps_from_string 함수에 넣어 Capabilities를 생성한다.

 그 외의 일반 element는 property가 없이 주어진 경우를 따로 구분하여 AttributeError를 피하고, 생성된 element들은 이후 link하기 위해 Dictionary로 저장한다.

 마지막으로 link에서 받은 element의 이름에 따라 각 element들을 연결한다. 여러개로 연결되어야 하는 경우, 배열로 값이 들어오기 때문에 type을 확인한 후 연결될 해당 element에 각각 link한다. 필요하다면 type을 list가 아니라 Iterable로 바꾸어 유연하게 사용할 수도 있을 것이다.

 element나, MIME같은 예약어가 없거나, link에서 알 수 없는 element의 이름이 들어온 경우 등은  KeyError로 Exception이 발생할 것이고, 그 외 어떤 element 생성에 사용할 수 없는 property가 들어온 경우 등의 에러는 마지막 Exception으로 게으르게 처리했다.

댓글 없음:

댓글 쓰기

[번] Callback 지옥, Promises, 그리고 Async/Await

원문:  https://blog.avenuecode.com/callback-hell-promises-and-async/await  Callbacks와 promises, 그리고 async/await을 쓰는 비동기 자바스크립트는 반환하는데 시간이 소요되는...