Spatial Video (공간동영상) in Swift

Spatial Video는 아이폰 15프로, 15프로맥스에서 지원하는 3차원 동영상 비디오 입니다. 아이폰 15프로 또는 맥스를 가지고 있다면 Spatial Video를 만들 수 있습니다. 하지만 현재 이렇게 녹화된 동영상은 애플 비젼프로, 오큘러스에서만 플레이 할수 있습니다.

Spatial Video의 기본 원리는 양쪽눈의 시각차이를 이용한 방법 이므로 이미 사용중인 3D 안경을 이용하는 방법으로 일반 동영상 플레이어에서도 3D 동영상을 볼 수 있습니다.

Swift를 이용한 동영상 분리

아래 링크의 프로젝트에서 Swift를 이용하여 Spatial Video를 분리하여 좌측, 우측 동영상을 분리하여 UIImage로 변환하는 기능을 볼수 있습니다.

Spatial Video Separate - Swift/SwiftUI
https://github.com/bipark/SpatialVideoSeparate-Swift

앱 다운로드

아래 링크의 앱은 Spatial Video의 좌측, 우측으로 분리하여 3D 안경을 이용하여 볼 수 있는 애너글리프 3D, 카드보드 3D 동영상으로 변환하는 기능을 가진 아이폰용 앱입니다.

https://apps.apple.com/us/app/spatial-video-converter-pro/id6479389968

BodyCam – 아이폰 동영상레코더

10년 넘게 앱 스토어에서 앱을 팔고(?) 있지만 최근엔 잘 안팔립니다. 시장도 포화되었고, 유저들 사용성도 고착화 되어 새로운 앱을 깔지 않죠. 게다가 유료라면 더욱 거들떠 보지도 않습니다.

그래도 스토어에 앱을 만들어 올리는 일은 개발자로서 긴장을 늦추지 않는 개인적인 방법입니다. 그러면서 평소같으면 하지 않을 프레임워크 공부도 하고 스토어 정책변화도 살피고 합니다. 그게 돈 버는 일 할때 도움이 되거든요. 재사용할 수 있는 코드도 미리 좀 만들어 놓고, 스토어 업로드 할때 당황(?)하지 않고.. ^^

올해 iOS의 AVFoundation을 많이 쓰게 되면서 공부삼아 만들어본 블랙박스 형식의 녹화 전용앱을 스토어에 업로드 했습니다. 단위 녹화시간, 해상도, fps를 조정할 수 있고, 애플워치로 녹화/중지도 되고, 앨범에 저장하지 않고 자체적으로 저장하거나 iCloud 에 저장할 수 있습니다만 유료입니다. 2.99달러. 물론 안사셔도 됩니다. 그냥 광고입니다 ^^;

자전거에 달고 액션캠으로 써보려고 만든건데 테스트 하다보니 아이폰 15프로를 사고 싶다는 생각이 간절하더군요.

다운로드 링크

https://apps.apple.com/us/app/bodycam-for-professional/id6473234309?platform=iphone

크랙 디텍터 – Crack Detector

크랙디텍터(Crack Detector) V3가 앱스토어에 릴리즈되었습니다. 크랙디텍터는 스마트폰 카메라에서 촬영한 콘크리트 벽면의 벌어진틈(크랙)을 찾아내고 크랙의 두께와 길이를 이미지 프로세싱 방법으로 측정하는 소프트웨어 서비스 입니다.

V3 – 머신러닝

크랙디텍터를 만들기 시작한건 거의 10년이 넘었습니다만 상용화 가능한 수준의 제품이 된건 이번 버전이 처음입니다.

V3에서 가장 큰 변화는 사진에서 크랙을 찾아내는 방법을 기존의 룰베이스 이미지 프로세싱에서 세그멘테이션 머신러닝을 도입한 것입니다. 기존의 이미지 프로세싱 방법은 현장의 위치, 조명, 시간등에 따른 이미지 품질 차이로 인해서 크랙을 못찾거나 그림자를 크랙으로 인식하는 등의 문제가 있었습니다만 머신러닝 도입후 크랙디텍션 성능이 매우 향상되었습니다.

마커

앱을 다운 받고 콘크리트 벽에 크랙을 촬영하면 이미지를 분석해서 크랙을 찾아냅니다만 크랙의 크기를 Pixel 단위로 표시합니다. 마커를 활용하면 Pixel 단위를 mm 단위로 정확하게 변환 할 수 있습니다.

마커는 정밀측정이 가능한 4개의 마커를 가진 하드웨어 타입과 1개의 마커를 사용하는 스티커 타입을 쓸 수 있습니다.

앱다운로드

다운로드 - 아이폰
https://apps.apple.com/kr/app/crack-detector/id1530337221

무료다운로드 이며 안드로이드용은 준비 중입니다. 앱사용에 관한 문의는 rtlink.park@gmail.com

개발배경

OpenSource – Flutter WebRTC Camara

최근 WebRTC 관련 작업을 많이 하고 있습니다. 주로 Flutter를 사용해서 앱을 만들고 있는데 아래 플러터 패키지가 꾸준히 업그레이드 되고 있어서 도움을 많이 받고 있습니다.

플러터용 WebRTC 패키지
https://pub.dev/packages/flutter_webrtc
WebRTC를 이용한 반려동물 CCTV
http://practical.kr/?p=580

시그널링(Signaling)

WebRTC는 offer – answer – candidate 등의 데이터를 주고받는 과정을 거쳐야만 화상통신을 연결 할 수 있는데 이 과정을 시그널링이라고 하고 일반적으로 시그널 서버를 만들어서 카메라와 뷰어를 연결합니다. 규정된 방법은 없고 개발자가 원하는 방법으로 데이터를 전달 하기만 하면 됩니다. 주로 소켓을 많이 이용합니다.

저는 이런 방법을 사용해 보기도 했습니다.

MQTT 기반으로 WebRTC 연결
http://practical.kr/?p=521

이것을 위해 별도의 서버를 운영을 해야 합니다. 언젠가 이런 생각을 해 봤습니다. 로그인 하지 않고 서버 없이 연결 할 수 없을까? 원격은 좀 어렵겠지만 로컬 Wifi에서는 가능하지 않을까? 로컬 Wifi 에 연결된 모든 앱이 소켓을 열고 서로 데이터를 주고 받으면 WebRTC 서버 없이 연결할 수 있겠는데? … 그런데 일단 앱의 IP를 알아야만 되더군요.

IP 찾기( Discover)

Bonsoir 패키지
https://pub.dev/packages/bonsoir

Bonsoir는 Zeroconf(https://ko.wikipedia.org/wiki/Zeroconf) 기반의 Discover 패키지입니다. 아래의 코드처럼 이벤트를 리스닝하고 있으면 브로드개스트를 하는 모든 기기를 찾을 수 있습니다. 애플은 봉주르라는 이름으로 기기들을 연결하고 있습니다.

  Future<void> _discoverService() async {
    _discovery = BonsoirDiscovery(type: _type);
    if (_discovery != null) {
      await _discovery!.ready;

      _discovery!.eventStream!.listen((event) {
        if (event.service != null) {
          ResolvedBonsoirService service = event.service as ResolvedBonsoirService;

          if (event.type == BonsoirDiscoveryEventType.discoveryServiceResolved) {
            final index = _resolvedServices.indexWhere((ResolvedBonsoirService item) => item.ip == service.ip);
            if (index == -1 && service.ip.toString() != "null") {
              _resolvedServices.add(service);
              setState(() {});
            }
          } else if (event.type == BonsoirDiscoveryEventType.discoveryServiceLost) {
            _resolvedServices.remove(service);
            setState(() {});
          }
        }
      });
      await _discovery!.start();
    }
  }

오픈소스 WebRTC 카메라

위의 몇가지 기술을 조립해서 앱을 만들어 봤습니다. 플러터의 장점인 멀티 플랫폼 지원 기능으로 아이폰 / 안드로이드 / 맥 에서 실행 가능 하도록 만들었습니다. 아래 링크에서 소스를 다운 받을 수 있습니다.

우선 맥앱을 빌드해서 맥에 실행하고 스마트폰에 앱을 빌드해서 실행하면 위의 그림과 같이 와이파이 무선 카메라를 실행 할 수 있습니다. 연결에 제한이 없으므로 많은 카메라를 한번에 연결 할 수도 있습니다.

코드를 수정하면 두대의 스마트폰으로 한대는 서버로 다른 스마트폰을 카메라로 사용할 수도 있습니다.

소스코드 링크
https://github.com/bipark/flutter_webrtc_wifi_camera

rtlink.park@gmail.com

EdgeDB와 GraphQL

전에 GraphQL을 Node.js기반으로 테스트 해보고 블로그를 한번 썼었다. 늘 그렇지만 이런 테스트의 목적은 어떻게 하면 코드를 적게 쓰고 목적을 달성하려는 의지이다. 좋게 말하면 생산성 향상.

http://practical.kr/?p=62

긱뉴스에 올라온 링크의 글(EdgeDB 관련)을 읽고 다시 호기심이 생겨서 한번 테스트를 해봤다.

EdgeDB 1년 사용 후기 - "다시는 SQL로 돌아가지 않을꺼야"

https://news.hada.io/topic?id=7052

EdgeDB는 Postgres 위에 만든 Graph/Relational DB 라고 한다. R-DB 이기는 하지만 SQL을 사용하지 않고 EgdeQL라는 새로 정의된 쿼리언어(?)를 사용하는데 SQL을 안다면 그리 어렵지는 않다.

개인적으로 쿼리언어를 바꾸는건 사실상 거의 불가능(? – 너무 오래 써서) 하다고 생각하고 있지만 의외로 GraphQL을 사용할 수 있는데 관심이 생겼다. 서버 코드 적게 쓰거나 안써도 될까? 이건 늘 관심사다. 그래도 DB 바꾸는 일은 쉬운일이 절대 아니다. 특히 SQL은 더더욱. EdgeDB의 GraphQL 부분만 한번 테스트 해봤다. 다음 프로젝트에서 써먹을 수 있을까 싶어서…

설치

설치는 매우 쉽다. 커맨드 라인에 명령어를 입력하면 자동으로 설치된다. 링크를 따라가면 Windows용 설치 명령어도 있다.

EdgeDB 설치
https://www.edgedb.com/install

스키마 만들기

설치가 완료되고 테스트용 스키마를 만들었다. 설치된 폴더 아래에 \dbschema 라는 폴더가 있고 거기에 default.esdl 파일이 생성되어 있다. 이 파일을 텍스트 편집기로 열고 아래 스키마를 입력하고 저장한다.

module default {
  type Person {
    required property first_name -> str;
    required property last_name -> str;
  }

  type Movie {
    required property title -> str;
    property year -> int64;
    link director -> Person;
    multi link actors -> Person;
  }
};

그리고 마이그레이션 과정을 거치면

마이그레이션 실행

> edgedb migration create
> edgedb migrate

스키마가 생성되고 입력, 수정, 삭제 등이 가능하다. 테스트를 위해 몇개의 데이터를 삽입했다.

https://www.edgedb.com/docs/guides/quickstart#insert-data

GraphQL 실행

테스트의 목적이 GraphQL을 통한 데이터 쿼리였으니 GraphQL 기능을 Enable 상태로 바꾸어야 한다. 방법은 위의 default.esd 파일의 첫줄에 아래 명령어를 추가 해주고 마이그레이션 명령을 한번 더 실행하는 것으로 완료된다.

using extension graphql;

실행하고 나서 edgedb instance list 커맨드를 실행하면 Name, Port, Status등을 확인 할 수 있다.

그리고 PostMan 같은 API Call 도구를 이용해서 http get을 통해서 아래와 같이 Json데이터를 얻을 수 있었다.

GraphQL 호출결과

결론

GraphQL은 마치 과거에 C/S 프로그램을 연상 시키지만 SQL을 사용하지 않는다는 점에서 보안상의 헛점을 해결한듯 보인다. 그러면서도 REST 서버 코드를 줄이고 생산성을 높인다는 점에서 매력적으로 보인다. EdgeDB는 SQL을 버리면서 또다른 방법을 보여준것 같아서 기대가 된다.

다음 프로젝트에 한번 써볼까? 2020.07.28 박병일

Velodyne 라이다와 파이썬 연결

최근 Velodyne 라이다 VLP-16을 사용해 볼 기회가 생겼다. 이 라이다는 자율주행 자동차에 많이 사용되고 있지만 나는 라이다를 고정하고 특정 목표물의 시간에 따른 위치변화(변이)를 확인하고자 테스트를 시작했고 결과물로 파이썬에서 라이다 데이터를 실시간으로 읽어서 파일 혹은 데이터 베이스에 저장할 목적으로 테스트를 했다.

ROS

라이다를 구입할 때 받은 VeloView라는 프로그램으로 라이다가 정상적으로 작동하고 PCAP 또는 CSV 형태의 파일로 저장 가능한 것을 확인했지만 그건 내가 원하는 방법이 아니었다.

VeloView

며칠의 삽질끝에 ROS를 이용하는 것이 가장(?) 효율적이라는 결론을 얻고 Ubuntu 20.04에 ROS – Noetic을 설치했다. 아래의 링크에서 ROS 설치 방법을 확인할 수 있다.

ROS - Noetic 설치
http://wiki.ros.org/noetic/Installation/Ubuntu

Velodyne Driver 설치

ROS를 이용하는 가장 큰 이유는 이미 만들어진 드라이버를 활용할 수 있기 때문인데 Velodyne Lidar 역시 버전별로 드라이버가 있다. 아래의 링크와 같이 드라이버를 설치했다. 링크에는 최초에 라이다를 연결하는 방법부터 rviz를 이용하여 VeloView와 같은 형식으로 라이다 데이터를 조회 할 수 있는 방법까지 알려준다.

아래 링크의 내용에서 
sudo apt-get install ros-VERSION-velodyne
부분은
sudo apt-get install ros-noetic-velodyne
으로 변경해서 설치해 준다


Velodyne Driver 설치
http://wiki.ros.org/velodyne/Tutorials/Getting%20Started%20with%20the%20Velodyne%20VLP16
RViz 측정결과 화면

ROS – RViz 에서 데이터를 확인할 수 있었지만 위치 데이터를 얻는 것이 목적이었기 때문에 VeloView와 동일하게 ROS에서 잘 연결되는구나를 확인하고 다음 삽질을 시작했다. 목표는 파이썬에서 각도별 거리를 얻어내는 것.

Publisher, Subscriber, PointCloud, Python

파이썬으로 라이다 데이터를 추출하기 위하여 ROS 튜토리얼을 스터디하던 과정에서 ROS 노드들의 통신 방식에 대해서 알게 되었다. 전에 내가 IOT 장비 연결에 주로 사용하던 MQTT 프로토콜과 거의 흡사한 구조.

발행자(Publisher)와 수신자(Subscriber)가 일정한 토픽(topic)을 공유하고 데이터를 송, 수신하는 방식으로 아래 링크를 참조하면 파이썬으로 데이터를 송, 수신하는 샘플을 확인할 수 있다.

위의 RViz 프로그램 역시 토픽을 Subscribe하여 라이다의 3차원 이미지를 그린다.

파이썬을 이용한 ROS 데이터 송수신 샘플
http://wiki.ros.org/ROS/Tutorials/WritingPublisherSubscriber%28python%29

아래의 커맨드를 실행해서 퍼블리셔(Publisher)를 실행시킨다. Velodyne Driver 설치 링크에서 이미 Publisher를 실행했었다. 그래서 RViz가 라이다 데이터를 수신해서 결과 화면을 그릴 수 있었다. 종료되었다면 아래와 같이 다시 실행할 수 있다.

$ roslaunch velodyne_pointcloud VLP16_points.launch

위의 샘플 코드를 응용하여 아래와 같이 벨로다인 토픽을 수신(Subscribe)한다.

if __name__ == '__main__':
    rospy.init_node("listener", anonymous=True)
    rospy.Subscriber('/velodyne_points', PointCloud2, callback)

    rospy.spin()

위의 코드에서 rospy.Subscriber() 의 파라메타는 토픽, 수신할 구조체 형식, 콜백 함수인데 콜백 함수의 구조는 아래와 같다.

import sensor_msgs.point_cloud2 as pc2

def callback(input_ros_msg):
    for data in pc2.read_points(input_ros_msg, skip_nans=True):
        x2 = data[0]
        y2 = data[1]
        z2 = data[2]
        intensity = data[3]
        ring = data[4]
        time1 = datetime.fromtimestamp(data[5])

        distance = math.sqrt((x2)**2+(y2)**2+(z2)**2)
        azimuth = math.degrees(math.atan2((x2),(y2)))

콜백을 통하여 전달된 데이터는 PointCloud 구조로 변환하고 변환된 좌표를 이용하여 거리와 방위각을 계산할 수 있다. 이제 데이터를 얻었으니 저장만하면 된다 . ^_^

거리와 방위각 계산방법은 아래 링크를 참고했다. 
https://gis.stackexchange.com/questions/108547/how-to-calculate-distance-azimuth-and-dip-from-two-xyz-coordinates

2022.07.07 박병일

Practical, a companion animal video player ‘Banzle’ officially opened in the App Store

Seoul–(Newswire) May 03, 2022 — Mobile app developer Practical (CEO Park Byeong-il) has developed its own ‘Banzle video player for pets’ on Google Play Store (Android) and App Store. It was announced on the 3rd that it was officially opened through (IOS).

‘Banzle’, a video player for companion animals developed by Practical, is a video player that allows companion animals to spend the day happily by playing the video on a smartphone that is not in use to the companion animal alone at home after the companion has gone to work. Smartphone-only apps and services.

As of 2022, there are 15 million domestic companions, and the reality is that one in four companion animals spends more than six hours a day alone. Companion owners turn on the lights or install air-conditioning, air-conditioning, automatic feeders, CCTV, etc. to soothe the loneliness of their companion animals and secure their safety. Accordingly, the pet tech market is growing significantly every year.

Banzle is a new pet tech product that allows you to check the current status of your companion animal using the camera on your smartphone, make a video call with your pet, and select a video to play on your pet at home from a remote location.

CEO Park Byeong-il said, “Currently, YouTube’s videos specialized for dogs and cats are provided through the Banzzle app, and we will invest more in developing videos for companion animals in the future.”

Banzzle can be downloaded for free from Google Play Store (Android) and App Store (IOS).

Practical Overview

Practical is a smartphone app developer established in 2018.

Google Play Store (Android): https://play.google.com/store/apps/details?id=com….
App Store (IOS): https://apps.apple.com/app/id1610958994

Website: http://practical.kr

반즐(Banzle) – 반려동물 전용 동영상 플레이어

반즐은 집에 혼자있는 반려친구를 위한 스마트폰용 동영상 플레이어입니다.

사용하지 않는 스마트폰을 활용해서 집에 혼자 있는 반려친구에게 전용 동영상을 틀어주세요.

반려견용, 반려묘용 동영상이 따로 준비되어 있어요. 밖에서도 원격으로 동영상을 바꿔서 틀어줄 수도 있어요.

그리고 혼자 있을때 무얼 하고 있는지 스마트폰 카메라로 화상통화를 할수 있어요.

아래 링크에서 다운받아 보세요. 무료앱이예요.

사용방법은 아래와 같아요. 그런데 주의할 점은 두대의 스마트폰에 반드시 같은 아이디로 로그인해야 원격 플레이와 화상통화를 할수 있어요.

◆ 주의사항

  1. 외출할때 꼭 “반즐”앱을 실행하고 나가야 해요
  2. 충전기에 연결되어 있어야해요
  3. 반려친구가 건드려서 앱이 꺼지지 않도록 적절한 위치에 두세요

브릿지(BritGi – Smart CCTV) 릴리즈

사용하지 않는 스마트폰을 CCTV로 활용할 수 있는 앱

브릿지(BritGi) V1.0

브릿지(BritGi)는 오래되서 사용하지 않는 스마트폰을 CCTV로 활용할 수 있는 앱입니다. 집을 비울때 반려견, 반려묘가 무었을 하고 있는지 내 스마트폰으로 실시간 조회 할 수 있습니다.

다운로드

아래 링크에서 무료로 다운로드 할 수 있습니다. 인앱결재 없이 계속 무료로 사용할 수 있습니다. 아이패드, 안드로이드 패드 지원합니다.

사용방법

브릿지(BritGi)는 오래된 스마트폰을 카메라로 사용하고 현재 쓰고 있는 스마트폰을 조회용으로 사용합니다.

  1. 카메라로 사용할 오래된 스마트폰과 지금쓰고 있는 스마트폰에 앱을 다운로드 하여 설치 합니다.
  2. 두개의 스마트폰에서 동일한 아이디(구글로그인 또는 애플로그인)로 로그인 합니다. (주의) 반드시 동일한 아이디로 로그인 해야 합니다. 카메라폰과 뷰어폰은 다른 종류라도 됩니다. – 예를들면 카메라는 안드로이드 뷰어는 아이폰
  3. 카메라 폰에서 카메라버튼을 눌러 카메라를 실행하고 뷰어폰에서 새로고침을 실행하면 카메라 목록이 화면에 나타나고 목록을 누르면 원격 영상을 볼수 있습니다.
  4. 카메라에 상시전원을 연결하고 고정하여 원격에서 전면, 후면 카메라를 선택하며 360도 거의 모든 방향을 볼 수 있습니다.

WebRTC(Peer-to-Peer)

브릿지(BritGi)는 카메라와 뷰어를 직접 연결하는 방식(WebRTC – Peer to Peer)으로 서버를 거치지 않고 영상정보를 전송합니다. 그래서 서버에 영상이 저장되지 않고 실시간으로만 조회가 가능합니다.

HD / Wifi, LTE, 5G

  • Wifi 연결에서는 HD 화질을 30 frame / sec 로 연결됩니다.
  • 5G/LTE 환경에서는 SD화질로 연결됩니다.
  • 가급적 Wifi 환경에서 사용하세요.

WebRTC, MQTT, Flutter

WebRTC를 이용한 화상/데이터 통신은 기본적으로 시그널 서버가 필요하다. 일반적으로 Websocket을 많이 사용하는데 서버는 클라이언트에서 전송되는 SDP, Candidate를 릴레이 하여 클라이언트가 연결할 상대방의 IP와 Port 정보를 주고 받은 다음 받은 정보를 이용하여 P2p 접속을 시도한다.

MQTT

MQTT는 발행/구독 기반으로 일대일, 일대다 데이터 통신에 적합하고 구독 채널을 트리구조로 구성 할 수 있기 때문에 Websocket에서 채팅방-서브 채팅방을 구현하는 기능을 아주 간단하게 구현 할 수 있다.

아이디어 – Mosquitto

WebRTC 화상통신앱을 만들며 처음부터 이 생각을 했다. WebSocket으로 채팅방을 만들지 않고 MQTT를 이용하면 안될까? Mosquitto 서버라면 보안성 문제도 쉽게 해결 할 수 있고 성능도 우수한 시그널 서버로 활용할 수 있지 않을까? 그래서 한번 해봤다.

Sample Code – Flutter

이 이미지는 대체 속성이 비어있습니다. 그 파일 이름은 1638688086230-1.jpg입니다

아래 링크의 소스 코드는 현재 만들고 있는 CCTV관련앱에서 기본적인 기능만 추출해서 Sample Project를 만들었다. Flutter로 만들어진 오픈소스이며 오픈된 Mosquitto 서버에 접속하여 동일한 토픽을 발행/구독하여 시그널을 주고 받은후 P2p를 연결하여 화상통신을 실행한다.

결론적으로 이 프로젝트에서 WebRTC 통신을 하기 위한 시그널 서버는 만들지않았다.

소스다운로드 링크

https://github.com/bipark/webrtc_mqtt_flutter_phone

P2p연결을 위해 Google Stun 서버를 사용하며 3G, LTE에서 연결이 되지 않을수도 있다. Stun 서버를 통한 P2p 연결이 불가능한 경우는 Turn 서버를 개별적으로 설치하고 운영해야 하는데 오픈소스인 Coturn을 활용하여 운용이 가능하다.

class MQTTManager {
  MqttServerClient? _client;

  MQTTManager(String clientId) {
    _client = MqttServerClient("test.mosquitto.org", clientId);
    _client!.autoReconnect = true;
    _client!.logging(on: false);
    _client!.keepAlivePeriod = 20;
  }

  Future&lt;void&gt; connect(String topic) async {
    try {
      await _client!.connect();
    } on Exception catch (e) {
      print('EXAMPLE::client exception - $e');
    }
    _client!.subscribe(topic, MqttQos.atMostOnce);
  }

  void publishMessage(String topic ,String message) {
    try {
      final builder1 = MqttClientPayloadBuilder();
      builder1.addString(message);
      _client!.publishMessage(topic, MqttQos.atLeastOnce, builder1.payload!);
    } on Exception catch (e) {
      print('EXAMPLE::client exception - $e');
    }
  }

  void subscribeTopic(String topic) {
    _client!.subscribe(topic, MqttQos.atMostOnce);
  }

  void unSubscribeTopic(String topic) {
    _client!.unsubscribe(topic);
  }

  void disconnect() {
    _client!.disconnect();
  }

  get client =&gt; _client;
}

WebRTC와 관련한 코드는 시그널 기능에 따른 많은 코드들이 요구되지만 셈플에서는 가장 기본적인 통신 기능만을 구현 했다.

  _mqtt = MQTTManager(_clientId);
  await _mqtt!.connect(_topic);
  
  ...
  var desc = await _localPc!.createOffer(offerSdpConstraints);
  await _localPc!.setLocalDescription(desc);
  _sendData("offer", desc.sdp);
  
  ...
  void _sendData(event, data) {
    var request = Map();
    request["command"] = "signal";
    request["clientid"] = _clientId;
    request["type"] = event;
    request["data"] = data;
    _mqtt!.publishMessage(_topic, jsonEncode(request).toString());
  }

박병일, 2021.12.5, rtlink.park@gmail.com