파이프라인 명령어를 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으로 게으르게 처리했다.
댓글 없음:
댓글 쓰기