Python だけで ソラカメ の MPEG-DASH を再生してみた

目次

SORACOM Advent Calendar 2023

この記事は SORACOM Advent Calendar 2023 の11日目の記事です。

qiita.com

今回はソラカメの API を利用して、ストリーミング映像を取得する際に利用する MPEG-DASH (Dynamic Adaptive Streaming over HTTP) を、FFmpeg といった外部のツールを利用せずに Python だけで再生できるか試してみました。

sora-cam.com

users.soracom.io

Python だけで MPEG-DASH の操作ができると、AI / ML との連携やローカル環境で自前のモデルでいろいろな試行錯誤が行えるようになると思います。

あと毎回のお約束ですが、すべての内容は個人で検証したもの になり、保証や確証はないのでもし利用する場合は必ずご自身で確認の上でお願いします。文中の現時点は2023年12月10日を指しています。

MPEG-DASH とは?

MPEG-DASH はストリーミング方式の1つで、DASH は「Dynamic Adaptive Streaming over HTTP」の略です。標準化されており、動画を小さな単位に分割し、異なる品質でエンコードできます。これにより、さまざまな品質の動画をストリーミングしたり、動画の途中で品質を切り替えることができます。

詳細は以下を参照してください。

en.wikipedia.org

https://www.cloudflare.com/ja-jp/learning/video/what-is-mpeg-dash/www.cloudflare.com

ソラカメとは?

ソラカメ (Soracom Cloud Camera Services) は、クラウド環境への録画機能があるクラウド型カメラです。クラウドに保存した映像をAPI やアプリを使って確認できます。ソラカメは価格の面や設定面で導入が容易であるため、これまで活用が難しかった部分に設置して試してみることが可能となります。

詳細は以下を参照してください。

sora-cam.com

soracom.jp

ソラカメの API を利用して、ストリーミング映像を取得する場合、現時点では MPEG-DASH となります。

ソラカメの MPEG-DASH を再生するには?

ソラカメのストリーミング映像は、MPEG-DASH 方式で配信されています。そのためソラカメの映像を再生するには、スマホアプリ、SORACOMユーザーコンソールなど、すでに用意されたものを活用する方が手間がなく便利です。

users.soracom.io

その他の方法だと VLC media playerdash.js の Reference Client で再生したり、FFmpeg を利用することで、単純な再生だけでなく、再エンコード処理をはじめとした他の操作を行うこともできます。

www.videolan.org

dashif.org

reference.dashif.org

ffmpeg.org

しかし、状況によってはこれらの便利なツールが利用できない場合や、再エンコードや配信のために利用するわけではなく、AI / ML のインプットとして単純に映像ストリームを利用したい場合があるかもしれないです。そんな時のために、今回は Python だけを利用して MPEG-DASH の映像ストリームを取得して再生してみたいと思います。

MPEG-DASH の MPD ファイルとは?

MPEG-DASH の MPD ファイル(Media Presentation Description)は、DASH(Dynamic Adaptive Streaming over HTTP)プロトコルで使用される XML フォーマットのファイルです。ファイルには、動画ストリーミングのための重要な情報が含まれていて、DASH 対応のプレーヤーが動画のダウンロードと再生を行うために必要なファイルです。

実際にソラカメの API を利用して、ストリーム映像の取得を行うと URL が取得できますが、この取得できる URL は MPD ファイルの URL (https://xxxxxx.kinesisvideo.ap-northeast-1.amazonaws.com/dash/v1/getDASHManifest.mpd?SessionToken=xxxxx)となっています。通常は、この MPD ファイルの URL を DASH 対応のプレーヤーに与えることで、内部的には MPD ファイルを読み解いて必要な動画のダウンロードと再生が実行されています。

users.soracom.io

この動作は、比較的簡単に確認することができます。例えばソラカメの API で取得したストリーミング用の URL(MPD ファイルの URL)を、dash.js のReference Client で再生します。再生を実行している Web ブラウザのデベロッパーツールを立ち上げて、Network パネルを確認してみると、定期的に動画を取得している様子が確認できるかと思います。

tech.nri-net.com

ということで、MPD ファイルを読み解いて動画をダウンロードできれば Python でも同じことはできそうな気がしてきましたね。

MPEG-DASH の MPD ファイル を Python で parse してみる

まず初めに、MPD ファイルを parse していきます。MPD ファイルは XML なのでそんなに考えなくても対応できそうです。

実際の MPD ファイルは以下のようなファイルです。

<?xml version='1.0' encoding='UTF-8'?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:kvs="urn:aws:kinesisvideo:mpd:2019" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" type="static" availabilityStartTime="2023-12-09T07:58:21.212Z" mediaPresentationDuration="PT2M20.934S" minBufferTime="PT1S">
  <Period id="0" start="PT0S" duration="PT2M20.934S">
    <AdaptationSet segmentAlignment="true" mimeType="video/mp4" startWithSAP="1">
      <SegmentTemplate media="getMP4MediaFragment.mp4?SessionToken=xxxxx&amp;SequenceNumber=$Number$&amp;TrackNumber=1" initialization="getMP4InitFragment.mp4?SessionToken=xxxxx&amp;TrackNumber=1" timescale="1000" startNumber="1">
        <SegmentTimeline>
          <S t="0" d="1001" kvs:ts="2023-12-09T07:58:21.212Z" kvs:fn="91343852333215703524729639126345842704639415673"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:22.210Z" kvs:fn="91343852333215703529681399283487364038145840473"/>
          <S d="996" kvs:ts="2023-12-09T07:58:23.213Z" kvs:fn="91343852333215703534633159440628885421286199850"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:24.211Z" kvs:fn="91343852333215703539584919597770406769204217591"/>
          <S d="1000" kvs:ts="2023-12-09T07:58:25.210Z" kvs:fn="91343852333215703544536679754911928154339861911"/>
          <S d="996" kvs:ts="2023-12-09T07:58:26.213Z" kvs:fn="91343852333215703549488439912053449515427341534"/>
          <S d="1002" kvs:ts="2023-12-09T07:58:27.210Z" kvs:fn="91343852333215703554440200069194970873551962088"/>
          <S d="998" kvs:ts="2023-12-09T07:58:28.210Z" kvs:fn="91343852333215703559391960226336492251454191872"/>
          <S d="997" kvs:ts="2023-12-09T07:58:29.211Z" kvs:fn="91343852333215703564343720383478013608587928635"/>
          <S d="1004" kvs:ts="2023-12-09T07:58:30.208Z" kvs:fn="91343852333215703569295480540619534972485677069"/>
          <S d="1000" kvs:ts="2023-12-09T07:58:31.209Z" kvs:fn="91343852333215703574247240697761056341755407385"/>
          <S d="997" kvs:ts="2023-12-09T07:58:32.210Z" kvs:fn="91343852333215703579199000854902577713961791561"/>
          <S d="1002" kvs:ts="2023-12-09T07:58:33.208Z" kvs:fn="91343852333215703584150761012044099076278831377"/>
          <S d="1000" kvs:ts="2023-12-09T07:58:34.208Z" kvs:fn="91343852333215703589102521169185620452031428557"/>
          <S d="997" kvs:ts="2023-12-09T07:58:35.209Z" kvs:fn="91343852333215703594054281326327141822373531240"/>
          <S d="1003" kvs:ts="2023-12-09T07:58:36.207Z" kvs:fn="91343852333215703599006041483468663184031284812"/>
          <S d="1000" kvs:ts="2023-12-09T07:58:37.207Z" kvs:fn="91343852333215703603957801640610184559497023988"/>
          <S d="998" kvs:ts="2023-12-09T07:58:38.208Z" kvs:fn="91343852333215703608909561797751705925074656222"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:39.206Z" kvs:fn="91343852333215703613861321954893227288743741351"/>
          <S d="998" kvs:ts="2023-12-09T07:58:40.207Z" kvs:fn="91343852333215703618813082112034748659902149052"/>
          <S d="997" kvs:ts="2023-12-09T07:58:41.207Z" kvs:fn="91343852333215703623764842269176270031831648400"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:42.205Z" kvs:fn="91343852333215703628716602426317791388143694876"/>
          <S d="998" kvs:ts="2023-12-09T07:58:43.206Z" kvs:fn="91343852333215703633668362583459312764630836931"/>
          <S d="998" kvs:ts="2023-12-09T07:58:44.206Z" kvs:fn="91343852333215703638620122740600834126611228269"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:45.204Z" kvs:fn="91343852333215703643571882897742355500545349018"/>
          <S d="998" kvs:ts="2023-12-09T07:58:46.205Z" kvs:fn="91343852333215703648523643054883876869407937734"/>
          <S d="997" kvs:ts="2023-12-09T07:58:47.206Z" kvs:fn="91343852333215703653475403212025398237883696543"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:48.204Z" kvs:fn="91343852333215703658427163369166919596413601907"/>
          <S d="998" kvs:ts="2023-12-09T07:58:49.204Z" kvs:fn="91343852333215703663378923526308441007759581867"/>
          <S d="998" kvs:ts="2023-12-09T07:58:50.204Z" kvs:fn="91343852333215703668330683683449962347049124213"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:51.203Z" kvs:fn="91343852333215703673282443840591483699803353577"/>
          <S d="998" kvs:ts="2023-12-09T07:58:52.203Z" kvs:fn="91343852333215703678234203997733005072327464618"/>
          <S d="998" kvs:ts="2023-12-09T07:58:53.203Z" kvs:fn="91343852333215703683185964154874526465491531013"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:54.202Z" kvs:fn="91343852333215703688137724312016047804996799635"/>
          <S d="998" kvs:ts="2023-12-09T07:58:55.202Z" kvs:fn="91343852333215703693089484469157569198675301585"/>
          <S d="998" kvs:ts="2023-12-09T07:58:56.202Z" kvs:fn="91343852333215703698041244626299090566624458401"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:57.201Z" kvs:fn="91343852333215703702993004783440611913201513403"/>
          <S d="1000" kvs:ts="2023-12-09T07:58:58.200Z" kvs:fn="91343852333215703707944764940582133301407551807"/>
          <S d="997" kvs:ts="2023-12-09T07:58:59.202Z" kvs:fn="91343852333215703712896525097723654664921362969"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:00.200Z" kvs:fn="91343852333215703717848285254865176006879908031"/>
          <S d="997" kvs:ts="2023-12-09T07:59:01.201Z" kvs:fn="91343852333215703722800045412006697426114573620"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:02.200Z" kvs:fn="91343852333215703727751805569148218755816554703"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:03.199Z" kvs:fn="91343852333215703732703565726289740119145777387"/>
          <S d="998" kvs:ts="2023-12-09T07:59:04.199Z" kvs:fn="91343852333215703737655325883431261489097116733"/>
          <S d="998" kvs:ts="2023-12-09T07:59:05.200Z" kvs:fn="91343852333215703742607086040572782864226764134"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:06.198Z" kvs:fn="91343852333215703747558846197714304218227347320"/>
          <S d="996" kvs:ts="2023-12-09T07:59:07.201Z" kvs:fn="91343852333215703752510606354855825600711209580"/>
          <S d="998" kvs:ts="2023-12-09T07:59:08.200Z" kvs:fn="91343852333215703757462366511997346960412678389"/>
          <S d="996" kvs:ts="2023-12-09T07:59:09.202Z" kvs:fn="91343852333215703762414126669138868328263710307"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:10.197Z" kvs:fn="91343852333215703767365886826280389704922436148"/>
          <S d="998" kvs:ts="2023-12-09T07:59:11.198Z" kvs:fn="91343852333215703772317646983421911059565925392"/>
          <S d="1005" kvs:ts="2023-12-09T07:59:12.196Z" kvs:fn="91343852333215703777269407140563432429892186724"/>
          <S d="997" kvs:ts="2023-12-09T07:59:13.198Z" kvs:fn="91343852333215703782221167297704953813368133151"/>
          <S d="995" kvs:ts="2023-12-09T07:59:14.200Z" kvs:fn="91343852333215703787172927454846475169089211094"/>
          <S d="1004" kvs:ts="2023-12-09T07:59:15.195Z" kvs:fn="91343852333215703792124687611987996530657499624"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:16.195Z" kvs:fn="91343852333215703797076447769129517922095635738"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:17.196Z" kvs:fn="91343852333215703802028207926271039278930289553"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:18.196Z" kvs:fn="91343852333215703806979968083412560639125380573"/>
          <S d="998" kvs:ts="2023-12-09T07:59:19.195Z" kvs:fn="91343852333215703811931728240554082029047690586"/>
          <S d="997" kvs:ts="2023-12-09T07:59:20.196Z" kvs:fn="91343852333215703816883488397695603387750476174"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:21.194Z" kvs:fn="91343852333215703821835248554837124737404800217"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:22.193Z" kvs:fn="91343852333215703826787008711978646109146001286"/>
          <S d="997" kvs:ts="2023-12-09T07:59:23.195Z" kvs:fn="91343852333215703831738768869120167474017114838"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:24.193Z" kvs:fn="91343852333215703836690529026261688834698383107"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:25.193Z" kvs:fn="91343852333215703841642289183403210216519737681"/>
          <S d="998" kvs:ts="2023-12-09T07:59:26.193Z" kvs:fn="91343852333215703846594049340544731565600600899"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:27.192Z" kvs:fn="91343852333215703851545809497686252940163647626"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:28.193Z" kvs:fn="91343852333215703856497569654827774310034023472"/>
          <S d="998" kvs:ts="2023-12-09T07:59:29.192Z" kvs:fn="91343852333215703861449329811969295683212863901"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:30.191Z" kvs:fn="91343852333215703866401089969110817048856151526"/>
          <S d="998" kvs:ts="2023-12-09T07:59:31.192Z" kvs:fn="91343852333215703871352850126252338450170327197"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:32.191Z" kvs:fn="91343852333215703876304610283393859788926569260"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:33.190Z" kvs:fn="91343852333215703881256370440535381156069529009"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:34.190Z" kvs:fn="91343852333215703886208130597676902529391842243"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:35.191Z" kvs:fn="91343852333215703891159890754818423902612351553"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:36.190Z" kvs:fn="91343852333215703896111650911959945261759082449"/>
          <S d="1003" kvs:ts="2023-12-09T07:59:37.189Z" kvs:fn="91343852333215703901063411069101466647617216149"/>
          <S d="998" kvs:ts="2023-12-09T07:59:38.190Z" kvs:fn="91343852333215703906015171226242988017449587601"/>
          <S d="1002" kvs:ts="2023-12-09T07:59:39.188Z" kvs:fn="91343852333215703910966931383384509351344036949"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:40.188Z" kvs:fn="91343852333215703915918691540526030723932802908"/>
          <S d="998" kvs:ts="2023-12-09T07:59:41.189Z" kvs:fn="91343852333215703920870451697667552086417156042"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:42.187Z" kvs:fn="91343852333215703925822211854809073454287366705"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:43.187Z" kvs:fn="91343852333215703930773972011950594827839141141"/>
          <S d="996" kvs:ts="2023-12-09T07:59:44.190Z" kvs:fn="91343852333215703935725732169092116194411814209"/>
          <S d="1002" kvs:ts="2023-12-09T07:59:45.186Z" kvs:fn="91343852333215703940677492326233637557677318663"/>
          <S d="998" kvs:ts="2023-12-09T07:59:46.187Z" kvs:fn="91343852333215703945629252483375158940826714326"/>
          <S d="998" kvs:ts="2023-12-09T07:59:47.187Z" kvs:fn="91343852333215703950581012640516680294209549768"/>
          <S d="1003" kvs:ts="2023-12-09T07:59:48.186Z" kvs:fn="91343852333215703955532772797658201665798537520"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:49.186Z" kvs:fn="91343852333215703960484532954799723041304309232"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:50.186Z" kvs:fn="91343852333215703965436293111941244400480811698"/>
          <S d="1002" kvs:ts="2023-12-09T07:59:51.185Z" kvs:fn="91343852333215703970388053269082765787997272409"/>
          <S d="997" kvs:ts="2023-12-09T07:59:52.186Z" kvs:fn="91343852333215703975339813426224287150493119161"/>
          <S d="997" kvs:ts="2023-12-09T07:59:53.186Z" kvs:fn="91343852333215703980291573583365808529427439091"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:54.184Z" kvs:fn="91343852333215703985243333740507329872396810594"/>
          <S d="998" kvs:ts="2023-12-09T07:59:55.184Z" kvs:fn="91343852333215703990195093897648851249556434180"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:56.184Z" kvs:fn="91343852333215703995146854054790372604023498907"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:57.183Z" kvs:fn="91343852333215704000098614211931893984032624967"/>
          <S d="998" kvs:ts="2023-12-09T07:59:58.184Z" kvs:fn="91343852333215704005050374369073415341558035381"/>
          <S d="997" kvs:ts="2023-12-09T07:59:59.184Z" kvs:fn="91343852333215704010002134526214936707567304371"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:00.182Z" kvs:fn="91343852333215704014953894683356458084266234021"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:01.182Z" kvs:fn="91343852333215704019905654840497979454853954346"/>
          <S d="997" kvs:ts="2023-12-09T08:00:02.183Z" kvs:fn="91343852333215704024857414997639500815572031422"/>
          <S d="1003" kvs:ts="2023-12-09T08:00:03.182Z" kvs:fn="91343852333215704029809175154781022186570848159"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:04.181Z" kvs:fn="91343852333215704034760935311922543563899855522"/>
          <S d="998" kvs:ts="2023-12-09T08:00:05.182Z" kvs:fn="91343852333215704039712695469064064921908990389"/>
          <S d="1003" kvs:ts="2023-12-09T08:00:06.180Z" kvs:fn="91343852333215704044664455626205586295211281904"/>
          <S d="998" kvs:ts="2023-12-09T08:00:07.180Z" kvs:fn="91343852333215704049616215783347107670076647589"/>
          <S d="997" kvs:ts="2023-12-09T08:00:08.182Z" kvs:fn="91343852333215704054567975940488629031317371241"/>
          <S d="1003" kvs:ts="2023-12-09T08:00:09.179Z" kvs:fn="91343852333215704059519736097630150399754433787"/>
          <S d="1000" kvs:ts="2023-12-09T08:00:10.180Z" kvs:fn="91343852333215704064471496254771671781924511250"/>
          <S d="997" kvs:ts="2023-12-09T08:00:11.181Z" kvs:fn="91343852333215704069423256411913193131811709641"/>
          <S d="1002" kvs:ts="2023-12-09T08:00:12.178Z" kvs:fn="91343852333215704074375016569054714505589391101"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:13.178Z" kvs:fn="91343852333215704079326776726196235878240959274"/>
          <S d="998" kvs:ts="2023-12-09T08:00:14.179Z" kvs:fn="91343852333215704084278536883337757236853824140"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:15.178Z" kvs:fn="91343852333215704089230297040479278608994999830"/>
          <S d="998" kvs:ts="2023-12-09T08:00:16.178Z" kvs:fn="91343852333215704094182057197620799981135040031"/>
          <S d="997" kvs:ts="2023-12-09T08:00:17.179Z" kvs:fn="91343852333215704099133817354762321330080620038"/>
          <S d="1002" kvs:ts="2023-12-09T08:00:18.177Z" kvs:fn="91343852333215704104085577511903842701180337552"/>
          <S d="1000" kvs:ts="2023-12-09T08:00:19.177Z" kvs:fn="91343852333215704109037337669045364075483794638"/>
          <S d="998" kvs:ts="2023-12-09T08:00:20.177Z" kvs:fn="91343852333215704113989097826186885442173482996"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:21.176Z" kvs:fn="91343852333215704118940857983328406807819366983"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:22.175Z" kvs:fn="91343852333215704123892618140469928185224537147"/>
          <S d="997" kvs:ts="2023-12-09T08:00:23.177Z" kvs:fn="91343852333215704128844378297611449541890474942"/>
          <S d="1002" kvs:ts="2023-12-09T08:00:24.175Z" kvs:fn="91343852333215704133796138454752970912981491389"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:25.175Z" kvs:fn="91343852333215704138747898611894492288343747538"/>
          <S d="998" kvs:ts="2023-12-09T08:00:26.176Z" kvs:fn="91343852333215704143699658769036013653896960550"/>
          <S d="1003" kvs:ts="2023-12-09T08:00:27.174Z" kvs:fn="91343852333215704148651418926177535024101316267"/>
          <S d="998" kvs:ts="2023-12-09T08:00:28.175Z" kvs:fn="91343852333215704153603179083319056394402943758"/>
          <S d="998" kvs:ts="2023-12-09T08:00:29.175Z" kvs:fn="91343852333215704158554939240460577761622848457"/>
          <S d="1003" kvs:ts="2023-12-09T08:00:30.173Z" kvs:fn="91343852333215704163506699397602099127324849081"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:31.174Z" kvs:fn="91343852333215704168458459554743620513526051737"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:32.174Z" kvs:fn="91343852333215704173410219711885141863712428816"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:33.173Z" kvs:fn="91343852333215704178361979869026663236153824465"/>
          <S d="998" kvs:ts="2023-12-09T08:00:34.173Z" kvs:fn="91343852333215704183313740026168184616203150113"/>
          <S d="998" kvs:ts="2023-12-09T08:00:35.173Z" kvs:fn="91343852333215704188265500183309705966971460559"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:36.172Z" kvs:fn="91343852333215704193217260340451227332516294301"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:37.171Z" kvs:fn="91343852333215704198169020497592748714842021214"/>
          <S d="997" kvs:ts="2023-12-09T08:00:38.173Z" kvs:fn="91343852333215704203120780654734270073652079153"/>
          <S d="1002" kvs:ts="2023-12-09T08:00:39.170Z" kvs:fn="91343852333215704208072540811875791437991841660"/>
          <S d="1000" kvs:ts="2023-12-09T08:00:40.170Z" kvs:fn="91343852333215704213024300969017312839347172304"/>
          <S d="995" kvs:ts="2023-12-09T08:00:41.174Z" kvs:fn="91343852333215704217976061126158834177854986059"/>
        </SegmentTimeline>
      </SegmentTemplate>
      <Representation id="video" bandwidth="977582" width="1920" height="1080" frameRate="19.98" codecs="avc1.4d0029" scanType="progressive"/>
    </AdaptationSet>
    <AdaptationSet segmentAlignment="true" mimeType="audio/mp4" startWithSAP="1">
      <SegmentTemplate media="getMP4MediaFragment.mp4?SessionToken=xxxxx&amp;SequenceNumber=$Number$&amp;TrackNumber=2" initialization="getMP4InitFragment.mp4?SessionToken=xxxxx&amp;TrackNumber=2" timescale="1000" startNumber="1">
        <SegmentTimeline>
          <S t="0" d="1001" kvs:ts="2023-12-09T07:58:21.212Z" kvs:fn="91343852333215703524729639126345842704639415673"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:22.210Z" kvs:fn="91343852333215703529681399283487364038145840473"/>
          <S d="996" kvs:ts="2023-12-09T07:58:23.213Z" kvs:fn="91343852333215703534633159440628885421286199850"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:24.211Z" kvs:fn="91343852333215703539584919597770406769204217591"/>
          <S d="1000" kvs:ts="2023-12-09T07:58:25.210Z" kvs:fn="91343852333215703544536679754911928154339861911"/>
          <S d="996" kvs:ts="2023-12-09T07:58:26.213Z" kvs:fn="91343852333215703549488439912053449515427341534"/>
          <S d="1002" kvs:ts="2023-12-09T07:58:27.210Z" kvs:fn="91343852333215703554440200069194970873551962088"/>
          <S d="998" kvs:ts="2023-12-09T07:58:28.210Z" kvs:fn="91343852333215703559391960226336492251454191872"/>
          <S d="997" kvs:ts="2023-12-09T07:58:29.211Z" kvs:fn="91343852333215703564343720383478013608587928635"/>
          <S d="1004" kvs:ts="2023-12-09T07:58:30.208Z" kvs:fn="91343852333215703569295480540619534972485677069"/>
          <S d="1000" kvs:ts="2023-12-09T07:58:31.209Z" kvs:fn="91343852333215703574247240697761056341755407385"/>
          <S d="997" kvs:ts="2023-12-09T07:58:32.210Z" kvs:fn="91343852333215703579199000854902577713961791561"/>
          <S d="1002" kvs:ts="2023-12-09T07:58:33.208Z" kvs:fn="91343852333215703584150761012044099076278831377"/>
          <S d="1000" kvs:ts="2023-12-09T07:58:34.208Z" kvs:fn="91343852333215703589102521169185620452031428557"/>
          <S d="997" kvs:ts="2023-12-09T07:58:35.209Z" kvs:fn="91343852333215703594054281326327141822373531240"/>
          <S d="1003" kvs:ts="2023-12-09T07:58:36.207Z" kvs:fn="91343852333215703599006041483468663184031284812"/>
          <S d="1000" kvs:ts="2023-12-09T07:58:37.207Z" kvs:fn="91343852333215703603957801640610184559497023988"/>
          <S d="998" kvs:ts="2023-12-09T07:58:38.208Z" kvs:fn="91343852333215703608909561797751705925074656222"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:39.206Z" kvs:fn="91343852333215703613861321954893227288743741351"/>
          <S d="998" kvs:ts="2023-12-09T07:58:40.207Z" kvs:fn="91343852333215703618813082112034748659902149052"/>
          <S d="997" kvs:ts="2023-12-09T07:58:41.207Z" kvs:fn="91343852333215703623764842269176270031831648400"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:42.205Z" kvs:fn="91343852333215703628716602426317791388143694876"/>
          <S d="998" kvs:ts="2023-12-09T07:58:43.206Z" kvs:fn="91343852333215703633668362583459312764630836931"/>
          <S d="998" kvs:ts="2023-12-09T07:58:44.206Z" kvs:fn="91343852333215703638620122740600834126611228269"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:45.204Z" kvs:fn="91343852333215703643571882897742355500545349018"/>
          <S d="998" kvs:ts="2023-12-09T07:58:46.205Z" kvs:fn="91343852333215703648523643054883876869407937734"/>
          <S d="997" kvs:ts="2023-12-09T07:58:47.206Z" kvs:fn="91343852333215703653475403212025398237883696543"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:48.204Z" kvs:fn="91343852333215703658427163369166919596413601907"/>
          <S d="998" kvs:ts="2023-12-09T07:58:49.204Z" kvs:fn="91343852333215703663378923526308441007759581867"/>
          <S d="998" kvs:ts="2023-12-09T07:58:50.204Z" kvs:fn="91343852333215703668330683683449962347049124213"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:51.203Z" kvs:fn="91343852333215703673282443840591483699803353577"/>
          <S d="998" kvs:ts="2023-12-09T07:58:52.203Z" kvs:fn="91343852333215703678234203997733005072327464618"/>
          <S d="998" kvs:ts="2023-12-09T07:58:53.203Z" kvs:fn="91343852333215703683185964154874526465491531013"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:54.202Z" kvs:fn="91343852333215703688137724312016047804996799635"/>
          <S d="998" kvs:ts="2023-12-09T07:58:55.202Z" kvs:fn="91343852333215703693089484469157569198675301585"/>
          <S d="998" kvs:ts="2023-12-09T07:58:56.202Z" kvs:fn="91343852333215703698041244626299090566624458401"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:57.201Z" kvs:fn="91343852333215703702993004783440611913201513403"/>
          <S d="1000" kvs:ts="2023-12-09T07:58:58.200Z" kvs:fn="91343852333215703707944764940582133301407551807"/>
          <S d="997" kvs:ts="2023-12-09T07:58:59.202Z" kvs:fn="91343852333215703712896525097723654664921362969"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:00.200Z" kvs:fn="91343852333215703717848285254865176006879908031"/>
          <S d="997" kvs:ts="2023-12-09T07:59:01.201Z" kvs:fn="91343852333215703722800045412006697426114573620"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:02.200Z" kvs:fn="91343852333215703727751805569148218755816554703"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:03.199Z" kvs:fn="91343852333215703732703565726289740119145777387"/>
          <S d="998" kvs:ts="2023-12-09T07:59:04.199Z" kvs:fn="91343852333215703737655325883431261489097116733"/>
          <S d="998" kvs:ts="2023-12-09T07:59:05.200Z" kvs:fn="91343852333215703742607086040572782864226764134"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:06.198Z" kvs:fn="91343852333215703747558846197714304218227347320"/>
          <S d="996" kvs:ts="2023-12-09T07:59:07.201Z" kvs:fn="91343852333215703752510606354855825600711209580"/>
          <S d="998" kvs:ts="2023-12-09T07:59:08.200Z" kvs:fn="91343852333215703757462366511997346960412678389"/>
          <S d="996" kvs:ts="2023-12-09T07:59:09.202Z" kvs:fn="91343852333215703762414126669138868328263710307"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:10.197Z" kvs:fn="91343852333215703767365886826280389704922436148"/>
          <S d="998" kvs:ts="2023-12-09T07:59:11.198Z" kvs:fn="91343852333215703772317646983421911059565925392"/>
          <S d="1005" kvs:ts="2023-12-09T07:59:12.196Z" kvs:fn="91343852333215703777269407140563432429892186724"/>
          <S d="997" kvs:ts="2023-12-09T07:59:13.198Z" kvs:fn="91343852333215703782221167297704953813368133151"/>
          <S d="995" kvs:ts="2023-12-09T07:59:14.200Z" kvs:fn="91343852333215703787172927454846475169089211094"/>
          <S d="1004" kvs:ts="2023-12-09T07:59:15.195Z" kvs:fn="91343852333215703792124687611987996530657499624"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:16.195Z" kvs:fn="91343852333215703797076447769129517922095635738"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:17.196Z" kvs:fn="91343852333215703802028207926271039278930289553"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:18.196Z" kvs:fn="91343852333215703806979968083412560639125380573"/>
          <S d="998" kvs:ts="2023-12-09T07:59:19.195Z" kvs:fn="91343852333215703811931728240554082029047690586"/>
          <S d="997" kvs:ts="2023-12-09T07:59:20.196Z" kvs:fn="91343852333215703816883488397695603387750476174"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:21.194Z" kvs:fn="91343852333215703821835248554837124737404800217"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:22.193Z" kvs:fn="91343852333215703826787008711978646109146001286"/>
          <S d="997" kvs:ts="2023-12-09T07:59:23.195Z" kvs:fn="91343852333215703831738768869120167474017114838"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:24.193Z" kvs:fn="91343852333215703836690529026261688834698383107"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:25.193Z" kvs:fn="91343852333215703841642289183403210216519737681"/>
          <S d="998" kvs:ts="2023-12-09T07:59:26.193Z" kvs:fn="91343852333215703846594049340544731565600600899"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:27.192Z" kvs:fn="91343852333215703851545809497686252940163647626"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:28.193Z" kvs:fn="91343852333215703856497569654827774310034023472"/>
          <S d="998" kvs:ts="2023-12-09T07:59:29.192Z" kvs:fn="91343852333215703861449329811969295683212863901"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:30.191Z" kvs:fn="91343852333215703866401089969110817048856151526"/>
          <S d="998" kvs:ts="2023-12-09T07:59:31.192Z" kvs:fn="91343852333215703871352850126252338450170327197"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:32.191Z" kvs:fn="91343852333215703876304610283393859788926569260"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:33.190Z" kvs:fn="91343852333215703881256370440535381156069529009"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:34.190Z" kvs:fn="91343852333215703886208130597676902529391842243"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:35.191Z" kvs:fn="91343852333215703891159890754818423902612351553"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:36.190Z" kvs:fn="91343852333215703896111650911959945261759082449"/>
          <S d="1003" kvs:ts="2023-12-09T07:59:37.189Z" kvs:fn="91343852333215703901063411069101466647617216149"/>
          <S d="998" kvs:ts="2023-12-09T07:59:38.190Z" kvs:fn="91343852333215703906015171226242988017449587601"/>
          <S d="1002" kvs:ts="2023-12-09T07:59:39.188Z" kvs:fn="91343852333215703910966931383384509351344036949"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:40.188Z" kvs:fn="91343852333215703915918691540526030723932802908"/>
          <S d="998" kvs:ts="2023-12-09T07:59:41.189Z" kvs:fn="91343852333215703920870451697667552086417156042"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:42.187Z" kvs:fn="91343852333215703925822211854809073454287366705"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:43.187Z" kvs:fn="91343852333215703930773972011950594827839141141"/>
          <S d="996" kvs:ts="2023-12-09T07:59:44.190Z" kvs:fn="91343852333215703935725732169092116194411814209"/>
          <S d="1002" kvs:ts="2023-12-09T07:59:45.186Z" kvs:fn="91343852333215703940677492326233637557677318663"/>
          <S d="998" kvs:ts="2023-12-09T07:59:46.187Z" kvs:fn="91343852333215703945629252483375158940826714326"/>
          <S d="998" kvs:ts="2023-12-09T07:59:47.187Z" kvs:fn="91343852333215703950581012640516680294209549768"/>
          <S d="1003" kvs:ts="2023-12-09T07:59:48.186Z" kvs:fn="91343852333215703955532772797658201665798537520"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:49.186Z" kvs:fn="91343852333215703960484532954799723041304309232"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:50.186Z" kvs:fn="91343852333215703965436293111941244400480811698"/>
          <S d="1002" kvs:ts="2023-12-09T07:59:51.185Z" kvs:fn="91343852333215703970388053269082765787997272409"/>
          <S d="997" kvs:ts="2023-12-09T07:59:52.186Z" kvs:fn="91343852333215703975339813426224287150493119161"/>
          <S d="997" kvs:ts="2023-12-09T07:59:53.186Z" kvs:fn="91343852333215703980291573583365808529427439091"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:54.184Z" kvs:fn="91343852333215703985243333740507329872396810594"/>
          <S d="998" kvs:ts="2023-12-09T07:59:55.184Z" kvs:fn="91343852333215703990195093897648851249556434180"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:56.184Z" kvs:fn="91343852333215703995146854054790372604023498907"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:57.183Z" kvs:fn="91343852333215704000098614211931893984032624967"/>
          <S d="998" kvs:ts="2023-12-09T07:59:58.184Z" kvs:fn="91343852333215704005050374369073415341558035381"/>
          <S d="997" kvs:ts="2023-12-09T07:59:59.184Z" kvs:fn="91343852333215704010002134526214936707567304371"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:00.182Z" kvs:fn="91343852333215704014953894683356458084266234021"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:01.182Z" kvs:fn="91343852333215704019905654840497979454853954346"/>
          <S d="997" kvs:ts="2023-12-09T08:00:02.183Z" kvs:fn="91343852333215704024857414997639500815572031422"/>
          <S d="1003" kvs:ts="2023-12-09T08:00:03.182Z" kvs:fn="91343852333215704029809175154781022186570848159"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:04.181Z" kvs:fn="91343852333215704034760935311922543563899855522"/>
          <S d="998" kvs:ts="2023-12-09T08:00:05.182Z" kvs:fn="91343852333215704039712695469064064921908990389"/>
          <S d="1003" kvs:ts="2023-12-09T08:00:06.180Z" kvs:fn="91343852333215704044664455626205586295211281904"/>
          <S d="998" kvs:ts="2023-12-09T08:00:07.180Z" kvs:fn="91343852333215704049616215783347107670076647589"/>
          <S d="997" kvs:ts="2023-12-09T08:00:08.182Z" kvs:fn="91343852333215704054567975940488629031317371241"/>
          <S d="1003" kvs:ts="2023-12-09T08:00:09.179Z" kvs:fn="91343852333215704059519736097630150399754433787"/>
          <S d="1000" kvs:ts="2023-12-09T08:00:10.180Z" kvs:fn="91343852333215704064471496254771671781924511250"/>
          <S d="997" kvs:ts="2023-12-09T08:00:11.181Z" kvs:fn="91343852333215704069423256411913193131811709641"/>
          <S d="1002" kvs:ts="2023-12-09T08:00:12.178Z" kvs:fn="91343852333215704074375016569054714505589391101"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:13.178Z" kvs:fn="91343852333215704079326776726196235878240959274"/>
          <S d="998" kvs:ts="2023-12-09T08:00:14.179Z" kvs:fn="91343852333215704084278536883337757236853824140"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:15.178Z" kvs:fn="91343852333215704089230297040479278608994999830"/>
          <S d="998" kvs:ts="2023-12-09T08:00:16.178Z" kvs:fn="91343852333215704094182057197620799981135040031"/>
          <S d="997" kvs:ts="2023-12-09T08:00:17.179Z" kvs:fn="91343852333215704099133817354762321330080620038"/>
          <S d="1002" kvs:ts="2023-12-09T08:00:18.177Z" kvs:fn="91343852333215704104085577511903842701180337552"/>
          <S d="1000" kvs:ts="2023-12-09T08:00:19.177Z" kvs:fn="91343852333215704109037337669045364075483794638"/>
          <S d="998" kvs:ts="2023-12-09T08:00:20.177Z" kvs:fn="91343852333215704113989097826186885442173482996"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:21.176Z" kvs:fn="91343852333215704118940857983328406807819366983"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:22.175Z" kvs:fn="91343852333215704123892618140469928185224537147"/>
          <S d="997" kvs:ts="2023-12-09T08:00:23.177Z" kvs:fn="91343852333215704128844378297611449541890474942"/>
          <S d="1002" kvs:ts="2023-12-09T08:00:24.175Z" kvs:fn="91343852333215704133796138454752970912981491389"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:25.175Z" kvs:fn="91343852333215704138747898611894492288343747538"/>
          <S d="998" kvs:ts="2023-12-09T08:00:26.176Z" kvs:fn="91343852333215704143699658769036013653896960550"/>
          <S d="1003" kvs:ts="2023-12-09T08:00:27.174Z" kvs:fn="91343852333215704148651418926177535024101316267"/>
          <S d="998" kvs:ts="2023-12-09T08:00:28.175Z" kvs:fn="91343852333215704153603179083319056394402943758"/>
          <S d="998" kvs:ts="2023-12-09T08:00:29.175Z" kvs:fn="91343852333215704158554939240460577761622848457"/>
          <S d="1003" kvs:ts="2023-12-09T08:00:30.173Z" kvs:fn="91343852333215704163506699397602099127324849081"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:31.174Z" kvs:fn="91343852333215704168458459554743620513526051737"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:32.174Z" kvs:fn="91343852333215704173410219711885141863712428816"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:33.173Z" kvs:fn="91343852333215704178361979869026663236153824465"/>
          <S d="998" kvs:ts="2023-12-09T08:00:34.173Z" kvs:fn="91343852333215704183313740026168184616203150113"/>
          <S d="998" kvs:ts="2023-12-09T08:00:35.173Z" kvs:fn="91343852333215704188265500183309705966971460559"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:36.172Z" kvs:fn="91343852333215704193217260340451227332516294301"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:37.171Z" kvs:fn="91343852333215704198169020497592748714842021214"/>
          <S d="997" kvs:ts="2023-12-09T08:00:38.173Z" kvs:fn="91343852333215704203120780654734270073652079153"/>
          <S d="1002" kvs:ts="2023-12-09T08:00:39.170Z" kvs:fn="91343852333215704208072540811875791437991841660"/>
          <S d="1000" kvs:ts="2023-12-09T08:00:40.170Z" kvs:fn="91343852333215704213024300969017312839347172304"/>
          <S d="995" kvs:ts="2023-12-09T08:00:41.174Z" kvs:fn="91343852333215704217976061126158834177854986059"/>
        </SegmentTimeline>
      </SegmentTemplate>
      <Representation id="audio" bandwidth="64000" audioSamplingRate="8000" codecs="alaw">
        <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011"/>
      </Representation>
    </AdaptationSet>
  </Period>
</MPD>

この XML を parse するために、今回は以下のコードを使用しました。

import xml.etree.ElementTree as ET

def parse_mpd(mpd_url, mpd_content):
    root = ET.fromstring(mpd_content)
    base_url = mpd_url.rsplit('/', 1)[0] + '/'  
    segment_urls = []
    frame_rate = 0

    for adaptation_set in root.iter('{urn:mpeg:dash:schema:mpd:2011}AdaptationSet'):
        re_presentation = adaptation_set.find('{urn:mpeg:dash:schema:mpd:2011}Representation')
        if re_presentation is not None:
            media_type = re_presentation.attrib['id']
            if media_type != "video":
                continue
            
            frame_rate = float(re_presentation.attrib['frameRate'])

        segment_template = adaptation_set.find('{urn:mpeg:dash:schema:mpd:2011}SegmentTemplate')
        if segment_template is not None:
            media_url_template = segment_template.attrib['media']
            init_url = segment_template.attrib['initialization']
            segment_urls.append(base_url + init_url)
            seg_index = int(segment_template.attrib['startNumber'])

            for segment in segment_template.find('{urn:mpeg:dash:schema:mpd:2011}SegmentTimeline').iter('{urn:mpeg:dash:schema:mpd:2011}S'):
                segment_url = base_url + media_url_template.replace('$Number$', str(seg_index))
                segment_urls.append(segment_url)

                seg_index = seg_index + 1

    return segment_urls, frame_rate

今回はソラカメの API で取得した MPD ファイルを元にしています。その他の MPD ファイルに対して動作するかどうかはわかりません。動作としては単純に XML を parse して、必要な情報を取得しています。戻り値としては、セグメント単位の動画の URL 一覧と、配信される動画のフレームレートを返しています。これは、後で実際に動画を取得するのに必要な情報と、OpenCV を使って後で動画を作成するのにフレームレートが必要なので、この情報を戻しています。

MPD ファイルから取得した URL を使って動画をダウンロードしてみる

MPD ファイルを parse して取得した、セグメント単位の動画 URL から、実際に動画ファイルをダウンロードしてみます。parse の作業と同じく特別なことは何もなく、単純に Python でファイルをダウンロードすれば問題なさそうです。

def download_segment(url, filename):
    response = requests.get(url)
    if response.status_code == 200:
        with open(filename, 'wb') as file:
            file.write(response.content)
    else:
        print()
        print(f"Failed to download {url}. Status code: {response.status_code}. Response: {response.text}")

指定したフォルダに *.mp4 ファイルが作成されていますが、アイコンが動画ファイルのプレビューになってないことがわかるかと思います。この状態のファイルのままだと残念ながら再生できませんでした。

試しに OpenCV に放り込んでみましたが、以下のようなエラーが出てやはり読み込めませんでした。

[mov,mp4,m4a,3gp,3g2,mj2 @ 0x11f352af0] trun track id unknown, no tfhd was found
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x11f352af0] error reading header
OpenCV: Couldn't read video stream from file "_mp4_files/segment_31.mp4"
Error: Could not open video file: _mp4_files/segment_31.mp4

少し調べてみるとどうやら、動画のヘッダーと動画の本体が別々で配信されていて、個別配信された動画とヘッダー部分のファイルを結合してあげれば、問題なく再生できそうな雰囲気です。

stackoverflow.com

取得したファイルを結合して再生できるようにしてみる

基本的なところで、MPEG-DASH の配信で initialization で指定されている URL で配信されているファイルに、動画のヘッダー情報があり、各セグメントで配信されてるファイルは、その動画ファイルの実態ということがわかったので、手元にダウンロードしたファイルを結合して再生できるようにしようと思います。

試しに手元のコンソールで cat コマンドを使って結合してみたところ、問題なく再生できたので方向性は間違ってなさそうでした。

ただし

  • init + seq_1 = a
  • init + seq_2 = b
  • init + seq_1 + seq_2 = c
  • a + b = d

みたいなパターンで cat でファイル結合してみましたが、いずれの場合も結合結果のファイルは再生できました。一方で init + seq_$ の場合のみが正常で、連続で結合したり、結合したものを結合した場合は最初のセグメントだけしか再生されず、ファイルサイズだけ結合で大きくなるという状態でした。

これを踏まえて、Python で実装するコードは以下のようにしました。

def cat_mp4_files(in_path, out_path):
    files = [f for f in os.listdir(in_path) if f.endswith('.mp4')]
    files.sort()

    file_zero = ""
    for i, file in enumerate(files):
        in_file = os.path.join(in_path, file)
        out_file = os.path.join(out_path, file)

        if i == 0:
            file_zero = in_file
            continue

        combine_files = [file_zero, in_file]
        with open(out_file, 'wb') as outfile:
            for file in combine_files:
                with open(file, 'rb') as infile:
                    outfile.write(infile.read())

リストの先頭にヘッダーファイルがある状態で実行すると、指定されたフォルダに結合済みの *.mp4 ファイルが出力されます。結合したファイルが出力されているフォルダをみると、前回と違い今回は動画のサムネイルがアイコンとして設定されているのがわかると思います。実際にローカルに入ってる再生ソフトで再生してみると、1ファイル1秒の動画が問題なく再生できることがわかると思います。

OpenCV で動画ファイルを読み込んでみる

1ファイル1秒の動画が問題なく再生できることは確認できました。次が実際に OpenCV でこの動画ファイルを読み込んでみます。これが実現できると、AI / ML の連携を行う際に必要な部分は検証できたと言えそうです。

import cv2

def load_opencv(path):
    files = [f for f in os.listdir(path) if f.endswith('.mp4')]
    files.sort()

    frames = []
    for file in files:
        file_path = os.path.join(path, file)
        cap = cv2.VideoCapture(file_path)

        if not cap.isOpened():
            print("Error: Could not open video file:", file_path)
            continue

        while True:
            ret, frame = cap.read()
            if not ret:
                break

            frames.append(frame)
            cv2.imshow('frame', frame)

            if cv2.waitKey(1) & 0xFF == ord('q'):
                break

        cap.release()
    cv2.destroyAllWindows()

    return frames

opencv.org

フォルダにある *.mp4 ファイルを順番に VideoCapture メソッドに渡して読み出しています。読み出した動画ファイルからフレームを取得して、そのフレームを imshow で画面を出して表示しています。この方法だとすべての動画ファイルの内容は表示できますが、処理のスピードによっては動画が早送りされているように表示されるかと思います。

frame rate を指定して複数のセグメント動画をひとつにしてみる

OpenCV を利用して各セグメントの動画は読み出せたので、AI/MLで利用する分には問題なく利用できるかと思います。しかし、ここまで来たら細切れではなく、全部のセグメントの動画を繋げて見たくなる気持ちがでてきませんか?というわけで、セグメント単位の複数の動画ファイルを、単一の動画ファイルにしてみようと思います。

def create_result_mp4(frames, frame_rate, path):
    fourcc = cv2.VideoWriter_fourcc(*'mp4v') 

    if frames:
        height, width, _ = frames[0].shape
        out = cv2.VideoWriter(path, fourcc, frame_rate, (width, height))

        for frame in frames:
            out.write(frame)

        out.release()
    else:
        print("The list of frames is empty.")

    print(f"A video {path} has been created.")

frames には、各セグメント動画ファイルを読み出した時に取得した frame 情報がリストで入っているので、これを指定されたフレームレートで書き出して行きます。フレームレートは MPD ファイルに入っている値をそのまま利用しています。

実際に出来上がった動画ファイルを確認しましたが、普通に再生できましたし、取得する際に指定した時間範囲も間違いなかったです。

まとめ

  • MPEG-DASH なにそれ? という状態ではなくなったと思う。
  • MPEG-DASH を利用するなら、とりあえず FFmpeg 使えばいいのでは? という状態ではなくなったと思う。
  • 実際に Python (+OpenCV) で再生できる動画が取得できた。
  • OpneCV の VideoCapture で動画が読み出せてる。
  • これで、ローカルで自前のAIモデルで動作させることも難しくないはず。
  • 今回は試しただけなので、同期処理で処理している。
  • 実際に利用する場合には、MPEG-DASH の取得と、OpneCV での読み込みを非同期で処理すれば問題なさそう。
  • ローカルでないと試せないとか、試すのが難しいという方も一定そういそうなので、ぜひローカル派の方は試してみてください。
  • オンラインや Web ブラウザーで試す方が気楽と言う方は、API サンプルとして少しですが公開されていますので、以下を参考にしてみてください。

users.soracom.io

ソラカメ API で取得できるストリーミング用の URL を、そのまま Python で操作して再生するところまでやってみました。結果としては特に難しい処理もなく、MPEG-DASH の仕様や MPD ファイルの構成をわかっていれば問題なく利用できるような感じを受けました。

これでローカルでいろいろ試せるという感じになると思うので、ぜひソラカメ API を使って検証してみてください。

以上です。

SORACOM LTE-M Button for Enterprise と Make を使ってノーコードでメール送信してみた #SORACOM #soracomug #nocode #withMake

目次

SORACOM Advent Calendar 2022

この記事は SORACOM Advent Calendar 2022 カレンダー223日目の記事です。

qiita.com

今年はカレンダー19日目の記事を投稿しましたが

uchimanajet7.hatenablog.com

もうひとつ試してみたいことがあったことを思い出したので Just Do It でやってみました。
思ったより簡単にできたので、ブログを書く時間ができました。
試してみるのは大事ですね。

あと毎回のお約束ですが、すべての内容は個人で検証したものです。
文中の現時点は2022年12月22日を指しています

SORACOM LTE-M Button for Enterprise

SORACOM LTE-M Button for Enterprise(以下、ボタン)は、ボタンを押したことをクラウドへ通知できます。
この通知をトリガーにしてクラウド側を実装すると、メールを送信したり決まった動作を行ったりできます。
詳細は各リンク先を確認してください。

soracom.jp

users.soracom.io

ボタンには他にもシリーズがあり、利用シーンで選択できます。

users.soracom.io

今回 SORACOM LTE-M Button for Enterprise を選択した理由は
2023年1月10日までキャンペーンが行われており特別価格で割引販売されているからですw

blog.soracom.com

やはり実際に買って試せる方が楽しいですからねー
また、IoTデバイスというとセンシングしてデータを継続的にクラウドに送信するといったイメージがあると思いますが、ボタンは自分でアクションできるデバイスなので個人的に好きなデバイスであるのも理由のひとつです。

Make

Make は Integromat と呼ばれていた iPaaS(Integration Platform as a Service)です。
同様のサービスだと IFTTT や Zapier の方がメジャーかもしれないです。

https://www.make.com/enwww.make.com

ノーコードで複数のサービスをつないで、やりたいことを簡単に実現できます。
Make という名前がWeb検索しづらいのがちょっと困りますがw
公式のドキュメントが充実していますし、UIも直感的なので利用するのには困らないと思います。

ボタンをクリック際にメールを送信する

ボタンをクリックした際にメールを送信したい場合、SORACOMユーザーコンソールに用意されている機能を利用して送信できます。
詳細はリンク先を確認してください。

users.soracom.io

用意された機能なので、ドキュメントの内容に沿って必要な項目も埋めていくだけで簡単にメール送信が実行できます。

ボ タンのクリック種類は

1. SINGLE: シングルクリック
2. DOUBLE: ダブルクリック
3. LONG: 長押し

の3種類になります。
この種類に応じてメールの内容や宛先を切り替えて、メール送信できると便利に使えるのでは?と思ったことがある人がいるはずw

なので、今回は Make を利用して、ボタンのクリック種類に応じた内容や送信先にメールを送信してみようと思います。

Make を準備する

トップページにアクセスして、Freeプランでアカウント登録を行います。

https://www.make.com/en/pricingwww.make.com

Freeプランでも 1,000 / 月 の実行ができますし、今回はボタンがクリックされたら即時実行する使い方なので、定期実行間隔の制限やアクティブにできるシナリオの数など、Freeプランの制限は問題になりませんでした。

プロダクションで利用する場合や、制限が knockout 要件となる場合には、有償プランを検討するか別の方法で実現するなどを検討する必要があります。

Webhooks module を設定する

ボタンから通知を受け取る必要があるので、通知を受け取るためのWebhook URLを払い出します。

https://www.make.com/en/help/tools/webhookswww.make.com

ドキュメントの記載の通り、scenario を追加して Webhooks module を配置し、画面の案内に沿って必要な設定を行っていきます。

以下のようにWebhook の rate limit が設定されていますが、今回は気にしないで大丈夫そうです。

Webhook rate limit

Make can process up to 30 incoming webhook requests per second.
If you send more than 30 requests per second, the system returns an error with status code 429.

ref: https://www.make.com/en/help/tools/webhooks#webhook-rate-limit

URLが払い出されると、受付テストのためにリクエストを受け付け待ちの状態になります。
この状態になったら、Make 側の設定を離れてSORACOMユーザーコンソール側の設定に移ります。

SORACOM ユーザーコンソール で準備する

SORACOM ユーザーコンソールは、ボタンを利用中であればすでにログインできるはずです。
詳細はリンク先を確認してください。

users.soracom.io

Make へボタンのクリック情報を転送するために、SORACOM Beam を利用します。

SORACOM Beam

SORACOM Beam(以下、Beam)は、デバイスからSORACOMに送信されたデータを、任意のサーバーに転送するサービスです。
詳細はリンク先を確認してください。

soracom.jp

今回はボタンから送信されたデータをBeam で転送するために設定が必要です。
以下ドキュメントに設定例がありますので、手順を参考にして設定していきます。

users.soracom.io

ボタンが所属しているSIM グループの設定を編集していきます。

設定はドキュメントに記載がある通り
UDP → HTTP/HTTPS エントリポイント を選択することと
Make 側で発行したWebhook のURLをホスト名とパスに分解して表示されているダイアログに入力するだけで完了です。

ヘッダ操作の設定については、今回は検証のために「署名ヘッダ付与」以外は有効化しました。
合わせて簡易位置測位機能 も有効化して、ボタンから送信できる情報はなるべく送信するように設定しています。

設定が正しく完了している場合、この段階でボタンをクリックすれば Make 側で待ち受けていた Webhooks module がボタンからの通知を受け取ってテスト完了になるかと思います。

ボタンから正しくデータが送信されているかを確認するには、SORACOMユーザーコンソールでSORACOM Harvest Data を利用します。

SORACOM Harvest

SORACOM Harvest(以下、Harvest)は、デバイスから送信されたデータやファイルをSORACOMで収集、蓄積するサービスです。
詳細はリンク先を確認してください。

soracom.jp

Harvest Data を使ってボタンのデータを確認する手順は、以下ドキュメントを参照してください。

users.soracom.io

Harvest Data でデータが確認できれば、ボタンからSORACOMまでは問題なくデータが届いているということになります。

Make でメール送信する

SORACOM側の設定が正しく完了した後は、Make側でメールを送信できるように設定していきます。

Email module を設定する

Make からメールを送信するため Send an Email アクションの設定を行っていきます。

https://www.make.com/en/help/tools/emailwww.make.com

メール自体はMake から直接送信するわけではなく、GmailMicrosoft Outlook などのメールサービスと接続を行って送信します。

https://www.make.com/en/help/tools/email#connecting-email-to-make-935249www.make.com

今回の実行例では Microsoft SMTP/IMAP OAuth を利用しています。
こちらはドキュンと記載の通り、プルダウンメニューから該当の項目を選択すると、Microsoftへのログイン画面がWebブラウザーで表示されます。

このログイン画面に使用したいMicrosoft アカウントでログインすれば、Makeとの接続が完了します。
メール送信に必要な宛先やタイトル、本文を記載して設定を行います。

module 同士を接続する

通常はmodule を作成した際に自動で接続されているはずです。
もし接続されていないようであれば、Webhooks module と Email module を接続してください。
具体的には、module の端子を接続したいmoduleにドラッグすれば接続できます。

これでWebhook があると固定の宛先に、固定の文面でメールが送信されるようになりました。
実際にボタンをクリックしてMake 経由でメールが送信されることを確認してください。

条件によって異なるメールを送信する

ここまでで、Makeを経由したメール送信は、SORACOMユーサーコンソールで利用できるメール送信と同等のことが実現できています。
細かい値とかは置いておいてですがw

ここからは、ボタンのクリックに応じてメールの宛先、タイトル、本文が異なるメールを送信できるようにしていきます。

まず、Webhooks module と Email module が接続されているリンクに Router module を追加します。

Router module を設定する

条件によって分岐を作れる module になるので、このmodule でボタンのクリック種別を条件として module の分岐を作ります。

https://www.make.com/en/help/modules/routerwww.make.com

設定は簡単で、Router module から出ているリンクにあるスパナ アイコン をクリックして条件を入力します。
今回は Webhook で取得しているボタンのデータの中に clickType という値があり、これがクリックの種類によって変化するため、3つのリンクでそれぞれ1つの値とマッチするように条件を設定します。
加えてEmail module が1つしかないので、既存のmodule をコピーするか新規にmodule を追加して、3つのリンクにEmail module が接続されている状態にします。

users.soracom.io

これでボタンのクリック種別で、3つの接続先を呼び出し分られるようになりました。
あとは、それぞれのリンク先のEmail module で、個別の宛先、タイトル、本文を設定すれば完了となります。

ボタンから送られてきているデータを確認する意味もあるので、今回は以下のようなメール設定を行いました。

宛先 example+{{1.clickType}}@example.com
タイトル ボタンが [{{1.clickTypeName}}] クリックされました
本文 ボタンが [{{1.clickTypeName}}] = [{{1.clickType}}] クリックされました。

{{formatDate(now; "YYYY/MM/DD HH:mm:ss"; "Japan")}}

----
・バッテリーレベル:{{(1.batteryLevel * 100)}}%
・簡易位置測位情報:{{ifempty(replace(get(map(1.`__IMTHEADERS__`; "value"; "name"; "x-soracom-geo-position"); 1); "/;/"; ","); emptystring)}}
・SIM ID:{{ifempty(get(map(1.`__IMTHEADERS__`; "value"; "name"; "x-soracom-sim-id"); 1); emptystring)}}
・IMSI:{{ifempty(get(map(1.`__IMTHEADERS__`; "value"; "name"; "x-soracom-imsi"); 1); emptystring)}}
・MSISDN:{{ifempty(get(map(1.`__IMTHEADERS__`; "value"; "name"; "x-soracom-msisdn"); 1); emptystring)}}
・IMEI:{{ifempty(get(map(1.`__IMTHEADERS__`; "value"; "name"; "x-soracom-imei"); 1); emptystring)}}

宛先はクリック種別で別になるように設定しました。
タイトルも同様にクリック種別がわかるように設定しました。
本文は、クリック種別と送信日時、そして送信されてきたデータとBeam にてヘッダーに付加したデータを取り出して表示するように設定しました。

簡易位置測位情報は、Beam で送信する場合は以下のフォーマットとなり緯度・軽度の区切り文字に ; が使われています。

users.soracom.io

Google Map で緯度・軽度で検索する場合の区切り文字は , なので、今回はMake側で置換処理しています。
置換以外にもMakeの組み込み関数を使ってますが、以下のドキュメントを検索すれば詳細に書いてあるので迷わず使えました。

https://www.make.com/en/help/homewww.make.com

これで、ボタンのクリックに対応してメールが出し分られるようになりました。
SORACOMユーザーコンソールのメール送信機能も同時に有効にしている場合は内容を比較してみてください。
SORACOMユーザーコンソールから送信されるメールで使える情報と、Beamでデータを転送してMake から送信される情報に差異があります。
メールに必須な情報があるかどうかを確認して、どちらの方法で送信を検討してください。

users.soracom.io

ボタンのクリックで異なる動作を行う場合

実際にここまで手を動かして設定してみた方は実感してるかもしれませんが、ボタンのクリックで異なる動作を検討する場合、検討しなければならないポイントがあります。

ボタンにはLED表示しかなく、LEDは電池残量やデータ送信結果の表示に使われています。

users.soracom.io

https://soracom.jp/files/products/SORACOM_LTE-M_Button_manual.pdfsoracom.jp

このような仕様だと、ボタン自体でどのタイプのクリックを実行したのかを判別できません。 またボタンをクリックして、データ送信のシーケンスが始まるとボタン側は操作を受け付けないようです。

youtu.be

youtu.be

ボタンをクリックした側が自分でどんな動作をしているのかがわからないため、クリックミスやカバンの中で意図せずクリックされてしまった場合など、メッセージを処理する側か、運用で対応する必要があります。

ボタンがクリックされた」という単純なトリガーとは違い、この辺りを検討して実装する必要があります。

3種類のメール送信が必要なのであれば、ボタンを物理的に3つ用意して対応してしまうのが単純で良いのでは?と個人的には思います。

まとめ

  • メールを送信したい場合は、SORACOMで用意されている機能で実現できる。
  • Make を利用してもメールは送信できた。
  • しかし、SORACOMで用意されたメール送信とMake でのメール送信では、利用できる情報が異なる。
  • SORACOMユーザーコンソールの機能でメール送信する場合は、Tagの情報が付加できる。
  • Beam で転送する場合には Tag の情報は付加できない。
    • Tagの情報が必須となる場合は転送での送信は難しい。
    • 今後のアップデートに期待したい。
  • ボタンのクリック種類によって、メールの内容に変化がつけたい場合は工夫が必要となる。
  • そもそも、そのクリックを押し分けられるかがポイントとなる。
  • 技術的に「できる」と、本番で利用するような運用が「できる」で実装方法が異なるはず。
  • Make については、ノーコードでちょっとしたことを実現したい場合には便利そう。
  • 一方で、いろいろ値を操作したいとなってくると、素直にAWS Lambda みたいな自由度が高く作り込みできる環境の方が楽な感覚があった。
  • Make も設定した状態をJSON ファイルとしてexportできるので、このJSONファイルを管理すればバージョン管理などはできそう。
  • SORACOMとクラウドの連携は最近アップデートされたBeam のSigV4 対応でいろいろできそう

    blog.soracom.com

  • 持ち歩くような用途は問題ないかもしれないが設置の場合は動作温度帯は確認が必要となる。
    • マニュアルには 5 〜 40 °Cの記載がある。
  • ボタンクリック開始から送信が完了するまで、電波状況に依存すると思うが、待つ時間があった。
  • いろいろ試しながら変更したりしていると、この待ち時間が塵も積もれば山となる感があった。
  • ボタンは使い方次第でいろいろできるので、使ってみながら試していくのが楽しい。

以上です。

ATOM Cam 2 のリアルタイム映像をPC で確認する我流 #SORACOM #soracomug #ATOMcam

目次

SORACOM Advent Calendar 2022

この記事は SORACOM Advent Calendar 2022 の9日目の記事です。

qiita.com

SORACOM Advent Calendar に参加するのは 2016 / 2017 以来となり、個人的にはなかなか感慨深いものがありますねー

uchimanajet7.hatenablog.com

uchimanajet7.hatenablog.com

2017年分は公開日が1日遅れて気がするので、過去の自分もっと頑張っておけよ。。。

今回は ATOM Cam 2 の リアルタイム映像 を PC で確認する方法を書いてみました。
[本流] とできるけどネタレベルの [我流] が書いてあるので、我流の方は生暖かく見ていただけると

あと毎回のお約束ですが、すべての内容は個人で検証したもの になります。文中の現時点は2022年12月8日を指しています。
利用しているATOMアプリと ATOM Cam 2 のファームウェアのバージョンは以下の画像を確認してください。

バージョンや利用するツールによっては期待している動作をしないことがあります。

本流と我流はこの番組が好きなのでw

www.fujitv.co.jp

Soracom Cloud Camera Services とATOM Cam 2

Soracom Cloud Camera Services(以下、ソラカメ)とATOM Cam 2(以下、AC2)の関係を簡単に紹介します。詳細は各リンク先を確認してください。

ソラカメはソラコムが提供しているクラウド型のカメラサービスです。

soracom.jp

AC2はアトムテックが販売してるネットワーク型のカメラです。 現時点でAC2を利用するにはスマートフォンアプリの ATOMアプリ を利用する必要があります。

www.atomtech.co.jp

soracom.jp

ソラカメ対応製品としてAC2が利用できるので、ソラカメのライセンスを購入することでAC2を使ったクラウドへの常時録画サービスを利用できることになります。

[本流] ソラカメを使う

ATOMアプリで登録されているカメラのリアルタイム映像を確認できます。
ソラカメを利用することにより「クラウドへの常時録画」が行われ、クラウド側に録画データが蓄積されます。

ソラカメでは、この録画されたデータをSORACOMユーザーコンソール上で視聴できるので、PCで映像を確認できるようになります。また、APIでの操作ができるため他のサービスへのインプットが行えます。

users.soracom.io

users.soracom.io


  • 良い点
    • 何より手軽に使える。
    • ライセンスを購入して割り当てすれば、クラウドへの常時録画ができる。
    • SORACOMユーザーコンソールから視聴できるので、Webブラウザだけあれば良い。
    • サービス利用なので自前で作り込んだりする必要がない上、サポートに問い合わせできる。
  • 注意点
    • サービス利用となるため月額の費用が発生する。
    • リアルタイムの定義によっては、ミスマッチとなる場合がある。
      • クラウド側に録画した映像を再生するので、厳密にはカメラのライブ映像とは異なる。
    • API経由の視聴に制限があるので、ユースケースによってはミスマッチとなる場合がある。

      soracom.jp

    • クラウドへの常時録画が前提となるため、電源やネットワーク利用量が大きくなる。

[我流-1] PCでスマホアプリを利用する我流

以前Blogに記載した内容ですが、PCにエミュレーターをインストールしてスマホアプリを利用する方法です。この方法ならPCでスマホアプリが利用できるため、PC上でリアルタイム映像を確認できます。

uchimanajet7.hatenablog.com


[我流-2] RTSPを中継機で転送する我流

こちらも以前Blogに記載した内容ですが、AC2はRTSPサーバーとして動作するので同一ネットワーク内であれば、RTSP経由でリアルタイム映像を視聴できます。
これを利用して同一ネットワーク内にRTSPを転送する中継機を置くことで、PCで閲覧できる場所に映像を転送できます。

uchimanajet7.hatenablog.com


  • 良い点
    • Amazon Kinesis Video Streams に映像データをインプットできる。

      aws.amazon.com

    • AWSによるドキュメントや動画があり、記載の通りに設定すれば比較的簡単にセットアップできる。

      www.youtube.com

    • 必要なソフトウェアがセットアップ済みのDocker イメージが存在する。

      docs.aws.amazon.com

    • 中継機が用意できれば、ユースケースによってはマッチする場合がある。
  • 注意点
    • AWSをはじめとした利用しているツール類について、ある程度の知識が必要。
    • ドキュメントや動画があっても、設定する項目や内容は理解する必要がある。
    • RTSPを使うためにAC2と同一ネットワークで中継機を動作させる必要がある。

      tex2e.github.io ja.wikipedia.org

    • 中継機がSPOFとなる可能性がある。

      www.nic.ad.jp

    • 中継機を含めた継続した運用を考慮する必要がある。
    • 電源やネットワーク利用については、常時クラウドへ映像が転送されているので大きくなる。
    • 別途クラウドで利用しているサービス利用料が必要となる。

[我流-3] SORACOM Arc と SORACOM Gate D2D でRTSPへリモートアクセスする我流

ここからは新作です。やっとSORACOMのサービスを利用しますw

SORACOM Gate D2D (Device to Device) とは

SORACOM Air のSIMを利用しているデバイス同士が、インターネットを経由せずに SORACOM網内で同一サブネットとしてプライベートIPで通信できるサービスです。詳細はリンク先を確認してください。

soracom.jp

SORACOM Arc とは

WireGuardを利用することで、既存の通信環境を使ってSORACOMへセキュアに接続できるサービスです。詳細はリンク先を確認してください。

soracom.jp

同一のネットワークになることで

AC2のRTSPは同一ネットワークであれば利用できるので、SORACOM Gate D2D を使うことで別のネットワークにあるデバイスでも、同一のネットワークと扱えるようになります。

ゲートウェイとなるルーターLTE / WireGuard が利用できる機種を選択することで、現在のWi-Fiでの接続が必須なAC2でも、SORACOM Gate D2D が利用できます。
今回はAC2の通信量が多いことと、Advent Calendar用の検証が目的のため WireGuard が利用可能なルーターを利用して、SORACOM Arc 経由で接続を行いました。

今回は手元にあるものを利用しましたが、 LTE / WireGuard を利用できるルーターを新規で探す場合は、以下を見てみてください。

soracom.jp

これで AC2が SORACOM Gate D2D のネットワークに参加できるようになります。
同じく、RTSPを視聴するPC側にUSBドングルや、SORACOM Arc を利用することで、同一ネットワーク内となります。
結果としてリモートからRTSPへアクセスできるようになります。

SORACOM Arc を準備する

SORACOMはドキュメントがとても丁寧に書かれています。
SORACOM Arcの利用に関しても、公式ドキュメントを参照して進めていきます。

users.soracom.io


  • 今回は単体でバーチャル SIMを作成します。

    users.soracom.io

  • ゲートウェイ用と視聴するPC用の合計2枚のバーチャル SIMを作成します。
    • バーチャル SIM を作成する際に、画面に表示される PrivateKey は後から再確認できないため、必ず記録してください。
  • SIM グループがない場合は作成します。

    users.soracom.io

  • 作った SIM グループ に 2枚のバーチャル SIMを所属させます。

    users.soracom.io

SORACOM Gate D2D を準備する

同様にSORACOM Gate D2Dの利用に関しても、公式ドキュメントを参照して進めていきます。

users.soracom.io


  • VPG を作成する際には インターネットゲートウェイを使う をONにする。
    • AC2を通常利用するためにインターネットアクセスが必要となるため。

      users.soracom.io

  • VPG を切り替えるのはグループ単位となります。

    users.soracom.io

  • SORACOM Arc で SORACOM Gate D2Dを利用するために、WireGuard 設定ファイルを編集する。
    • バイスサブネットのIPレンジを許可するために AllowedIPs に記載する。
  • VPG を切り替えた場合にはSIMのセッションリセットを行います。

    users.soracom.io

ゲートウェイ機器を設定する

詳細な設定は利用するゲートウェイ機器で異なるため、利用する機器のマニュアルを確認して設定してください。

users.soracom.io


  • VPNの設定を探し WireGuard クライアント の設定します。
  • バーチャル SIMを発行さいた際に、画面に表示されていた情報を WireGuard クライアント の設定として入力します。
  • SORACOM Gate D2D の準備で設定した、デバイスサブネットのIPレンジ は必ず設定に追加します。
  • すべての設定が完了したら、WireGuard クライアント を起動してSORACOMと通信できるかを確認します。

    users.soracom.io

  • SORACOMと通信できない場合には、ハートビート間隔 / PersistentKeepalive と記載されている設定を確認します。

    www.wireguard.com

  • リモートアクセスの宛先は、ゲートウェイ機器で利用している バーチャル SIM に割り振られている ローカルIP になります。
    • SORACOMユーザーコンソールで、該当のバーチャル SIMの ローカルIP を確認します。

      users.soracom.io

  • ゲートウェイ機器のファイヤーウォール設定で、WireGuard側からのポートフォワードを設定しておきます。
    • 上記の場合は、8554 のWireGuard側から 554 へのポートフォワードが設定してあります。
    • AC2のRTSPは 554 ポートで待ち受けしていることが確認できます。
      $ nmap -A 192.168.8.119
    
      Starting Nmap 7.93 ( https://nmap.org ) at 2022-12-06 15:56 JST
      Nmap scan report for ATOM.lan (192.168.8.119)
      Host is up (0.0059s latency).
      Not shown: 998 closed tcp ports (conn-refused)
      PORT     STATE SERVICE VERSION
      554/tcp  open  rtsp    DoorBird video doorbell rtspd
      |_rtsp-methods: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER
      9999/tcp open  abyss?
      Service Info: Device: webcam
    
      Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
      Nmap done: 1 IP address (1 host up) scanned in 9.67 seconds
    

視聴するPCを設定する

利用しているPCのプラットフォームにあわせたWireGuard をインストールして設定します。

www.wireguard.com


  • インストールが完了したら、WireGuardの接続情報を設定します。

    users.soracom.io

  • ゲートウェイ機器で設定した手順と変わらないはずです。
  • すべての設定が完了したら、WireGuard クライアント を起動してSORACOMと通信できるかを確認します。

    users.soracom.io

  • SORACOMと通信できない場合には、ハートビート間隔 / PersistentKeepalive と記載されている設定を確認します。

    www.wireguard.com

RTSPへリモートアクセスして確認してみる

ATOMアプリの設定画面を開いて「PCで再生する画面」を表示します。この画面には RTSPのアクセス先 が表示されています。

カメラがオンラインの時にだけ設定画面が表示できます。最初に機能を有効化する際には確認のダイアログが表示されます。

設定画面には
rtsp://5374:1261@192.168.0.11/live
のような rtsp から始まるURLが表示されます。

URLの内容を少し分解してみると
rtsp://<ユーザ名>:<パスワード>@<IPアドレス>:<ポート番号>/live
というフォーマットになっています。

視聴用のPCが、AC2と同一のネットワークにいる場合には、このURLを指定するだけで再生できます。

$ ffplay -i rtsp://5374:1261@192.168.8.119/live

ffplay version 5.1.2 Copyright (c) 2003-2022 the FFmpeg developers
  built with Apple clang version 14.0.0 (clang-1400.0.29.202)
  configuration: --prefix=/opt/homebrew/Cellar/ffmpeg/5.1.2_1 --enable-shared --enable-pthreads --enable-version3 --cc=clang --host-cflags= --host-ldflags= --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libbluray --enable-libdav1d --enable-libmp3lame --enable-libopus --enable-librav1e --enable-librist --enable-librubberband --enable-libsnappy --enable-libsrt --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libspeex --enable-libsoxr --enable-libzmq --enable-libzimg --disable-libjack --disable-indev=jack --enable-videotoolbox --enable-neon
  libavutil      57. 28.100 / 57. 28.100
  libavcodec     59. 37.100 / 59. 37.100
  libavformat    59. 27.100 / 59. 27.100
  libavdevice    59.  7.100 / 59.  7.100
  libavfilter     8. 44.100 /  8. 44.100
  libswscale      6.  7.100 /  6.  7.100
  libswresample   4.  7.100 /  4.  7.100
  libpostproc    56.  6.100 / 56.  6.100
Input #0, rtsp, from 'rtsp://5374:1261@192.168.8.119/live': f=0/0
  Metadata:
    title           : Session streamed by "Atom"
    comment         : live
  Duration: N/A, start: 0.000625, bitrate: N/A
  Stream #0:0: Video: h264 (Main), yuv420p(tv, bt709, progressive), 1920x1080, 20 fps, 20 tbr, 90k tbn
  Stream #0:1: Audio: pcm_alaw, 8000 Hz, 1 channels, s16, 64 kb/s
[rtsp @ 0x148906660] RTP: dropping old packet received too late/1
    Last message repeated 2 times
[rtsp @ 0x148906660] RTP: dropping old packet received too late/2
[h264 @ 0x148977930] left block unavailable for requested intra mode
[h264 @ 0x148977930] error while decoding MB 0 15, bytestream 40430
[h264 @ 0x148977930] concealing 6409 DC, 6409 AC, 6409 MV errors in I frame
  39.49 A-V:  0.025 fd=  17 aq=    0KB vq=   47KB sq=    0B f=2/2

リモートでアクセスするためには


  • SORACOMユーザーコンソールで、ゲートウェイ機器が利用しているバーチャル SIMのローカルIPを確認します。
    • ここで確認したローカルIPがリモートアクセス先です。

      users.soracom.io

  • ゲートウェイ機器のファイヤーウォール設定で、WireGuard側からのポートフォワードを確認します。
    • ここで確認したポートがリモートアクセス先のポートです。

あとは、ATOMアプリで確認した RTSP のURLを確認した情報を使って整理します。
rtsp://5374:1261@10.182.176.48:8554/live
という形がリモートアクセス用のURLとなります。

これでRTSPへリモートアクセスするための準備は整ったので、実際にアクセスしてみます。

$ ffplay -i rtsp://5374:1261@10.182.176.48:8554/live

ffplay version 5.1.2 Copyright (c) 2003-2022 the FFmpeg developers
  built with Apple clang version 14.0.0 (clang-1400.0.29.202)
  configuration: --prefix=/opt/homebrew/Cellar/ffmpeg/5.1.2_1 --enable-shared --enable-pthreads --enable-version3 --cc=clang --host-cflags= --host-ldflags= --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libbluray --enable-libdav1d --enable-libmp3lame --enable-libopus --enable-librav1e --enable-librist --enable-librubberband --enable-libsnappy --enable-libsrt --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libspeex --enable-libsoxr --enable-libzmq --enable-libzimg --disable-libjack --disable-indev=jack --enable-videotoolbox --enable-neon
  libavutil      57. 28.100 / 57. 28.100
  libavcodec     59. 37.100 / 59. 37.100
  libavformat    59. 27.100 / 59. 27.100
  libavdevice    59.  7.100 / 59.  7.100
  libavfilter     8. 44.100 /  8. 44.100
  libswscale      6.  7.100 /  6.  7.100
  libswresample   4.  7.100 /  4.  7.100
  libpostproc    56.  6.100 / 56.  6.100
[rtsp @ 0x10b204370] Could not find codec parameters for stream 0 (Video: h264, none): unspecified size
Consider increasing the value for the 'analyzeduration' (0) and 'probesize' (5000000) options
Input #0, rtsp, from 'rtsp://5374:1261@10.182.176.48:8554/live':
  Metadata:
    title           : Session streamed by "Atom"
    comment         : live
  Duration: N/A, start: -106052.347750, bitrate: 64 kb/s
  Stream #0:0: Video: h264, none, 90k tbr, 90k tbn
  Stream #0:1: Audio: pcm_alaw, 8000 Hz, 1 channels, s16, 64 kb/s
[rtsp @ 0x10b204370] max delay reached. need to consume packet0/0
[rtsp @ 0x10b204370] RTP: missed 25 packets
[rtsp @ 0x10b204370] max delay reached. need to consume packet
[rtsp @ 0x10b204370] RTP: missed 1 packets
[rtsp @ 0x10b204370] max delay reached. need to consume packet
[rtsp @ 0x10b204370] RTP: missed 2 packets
[rtsp @ 0x10b204370] max delay reached. need to consume packet
[rtsp @ 0x10b204370] RTP: missed 1 packets
[rtsp @ 0x10b204370] max delay reached. need to consume packet
[rtsp @ 0x10b204370] RTP: missed 2 packets
[rtsp @ 0x10b204370] max delay reached. need to consume packet
[rtsp @ 0x10b204370] RTP: missed 2 packets
[rtsp @ 0x10b204370] max delay reached. need to consume packet
[rtsp @ 0x10b204370] RTP: missed 2 packets
[rtsp @ 0x10b204370] max delay reached. need to consume packet
[rtsp @ 0x10b204370] RTP: missed 2 packets

... The following is the same and has been omitted.

コンソールの出力結果を見てみると、なんとなくネットワーク的にはAC2までリーチしてそうな雰囲気が!!
しかし実際にはカメラ映像の視聴はできませんでした。コンソールにはパケットロスのログが流れるだけでした。残念。
RTSPをUDPで受信してるっぽい感じ?なので、TCPのみで受信できれば良さそう?

調べてみると ffmpegrtsp_transport というオプションがありました。これを利用すれば UDP以外 を指定できるようです。

ffmpeg.org

ffmpeg.org

ということで、この rtsp_transport オプションを付けて tcp を指定して実行します。

$ ffplay -rtsp_transport tcp -i rtsp://5374:1261@10.182.176.48:8554/live

ffplay version 5.1.2 Copyright (c) 2003-2022 the FFmpeg developers
  built with Apple clang version 14.0.0 (clang-1400.0.29.202)
  configuration: --prefix=/opt/homebrew/Cellar/ffmpeg/5.1.2_1 --enable-shared --enable-pthreads --enable-version3 --cc=clang --host-cflags= --host-ldflags= --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libbluray --enable-libdav1d --enable-libmp3lame --enable-libopus --enable-librav1e --enable-librist --enable-librubberband --enable-libsnappy --enable-libsrt --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libspeex --enable-libsoxr --enable-libzmq --enable-libzimg --disable-libjack --disable-indev=jack --enable-videotoolbox --enable-neon
  libavutil      57. 28.100 / 57. 28.100
  libavcodec     59. 37.100 / 59. 37.100
  libavformat    59. 27.100 / 59. 27.100
  libavdevice    59.  7.100 / 59.  7.100
  libavfilter     8. 44.100 /  8. 44.100
  libswscale      6.  7.100 /  6.  7.100
  libswresample   4.  7.100 /  4.  7.100
  libpostproc    56.  6.100 / 56.  6.100
Input #0, rtsp, from 'rtsp://5374:1261@10.182.176.48:8554/live':0
  Metadata:
    title           : Session streamed by "Atom"
    comment         : live
  Duration: N/A, start: 0.000000, bitrate: N/A
  Stream #0:0: Video: h264 (Main), yuv420p(tv, bt709, progressive), 1920x1080, 20 fps, 20 tbr, 90k tbn
  Stream #0:1: Audio: pcm_alaw, 8000 Hz, 1 channels, s16, 64 kb/s
  91.05 A-V: -0.018 fd=  19 aq=    0KB vq=   60KB sq=    0B f=0/0

おぉぉーちゃんと受信できる!映像が見える!
ざっと確認した感じだと、さっきと違ってパケットロスのログも出てない。

音声は何度か試してみましたが、いづれも再生開始から少し経つと聞こえなくなってしまいました。 ローカル接続の場合でも音声は同様に不安定だったので、まーこんなものかなぁーと。残念。

youtu.be

というわけで、無事にRTSPへリモート接続ができました。
SORACOMのサービスを利用しているので設定が必要となりますが、 最初の1度設定してしまえば、あとはローカル接続と変わらず接続できます。


  • 良い点
    • RTSPへリモートアクセスできる。
    • SORACOMのサービスを利用することでセキュアに接続できる。
    • 1度設定してしまえば、あとはローカル接続と変わらず利用できる。
    • 必要な時だけ接続して映像を視聴できる。
      • 電源やネットワーク利用量を節約できる可能性がある。
    • ゲートウェイ機器さえあれば、後はオンラインですべて対応できる。
    • ATOMアプリで表示しているライブ映像と比較しても遅延が小さい。
      • クラウドへの常時録画を再生しているSORACOMユーザーコンソールと遜色がない。
  • 注意点
    • SORACOMのサービスごとの設定が必要となる。
    • SORACOMのサービス利用料が発生する。
    • リアルタイム映像を視聴するための構成なので、録画が必要な場合には別途仕組み検討することになる。
    • 各種設定はドキュメントを確認しながら進められるが、利用するサービスごとにある程度の知識は必要となる。
    • 利用サービスが多くなると仕組み自体が複雑になる。
    • ゲートウェイ機器の用意が必要となる。
    • ゲートウェイ機器も含めて、仕組み全体の運用を考える必要がある。
    • 複数名で見たい場合は、視聴するPCごとに設定が必要となる。
      • バーチャル SIMを台数分用意するため費用がかかる。

[我流-4] SORACOM Arc と SORACOM Napter でRTSPへリモートアクセスする我流

RTSPはUDPの利用が必須だと勘違いしていたので、最初に試さなかったのですが
[我流-3]rtsp_transport オプションを調べた際に、UDP以外の受信指定ができることを知りました。
TCPで受信できるのであれば SORACOM Napter を利用しても実現できるのでは?と思い[我流-3] に続けて検証してみました。

SORACOM Napter とは

SORACOM Air のSIMを利用しているデバイスへ、セキュアにリモートアクセスできるサービスです。詳細はリンク先を確認してください。

soracom.jp

つまりは

[我流-3] でSORACOM Gate D2Dを利用してAC2とPCで通信していた部分を、SORACOM Napter に置き換えてやれば、バーチャル SIM1枚とSORACOM Gate D2D の費用も節約でき、仕組みもシンプルになりそうです。

SORACOM Napter を準備する

公式ドキュメントを参照して進めていきます。

users.soracom.io


  • SORACOMユーザーコンソールにログインして、[我流-3]ゲートウェイ機器に設定したバーチャル SIM を探します。
  • 該当のバーチャル SIMを選択して オンデマンドリモートアクセス を作成します。

    users.soracom.io

  • オンデマンドリモートアクセス は、SORACOM CLI / API でも作成できます。

    users.soracom.io

  • オンデマンドリモートアクセス の デバイス側ポート は ゲートウェイ機器でポートフォワード設定したポートです。
    • 今回は 8554 ポートで設定しました。
  • すべての設定が完了するとアクセスに必要な情報がダイアログに表示されます。

RTSPへリモートアクセスして確認してみる

[我流-3] 確認したRTSPのURLフォーマットを利用します。
rtsp://<ユーザ名>:<パスワード>@<IPアドレス>:<ポート番号>/live

<IPアドレス>:<ポート番号>
の部分を SORACOM Napter の準備で確認した接続情報に置き換えます。ダイアログに表示されている IPアドレス の項目をそのままコピーして利用します。

IPアドレス をコピペして、URLを整理すると
rtsp://5374:1261@18.181.57.86:12374/live
という形がリモートアクセス用のURLとなります。

これでRTSPへリモートアクセスするための準備は整ったので、実際にアクセスしてみます。

$ ffplay -rtsp_transport tcp -i rtsp://5374:1261@18.181.57.86:12374/live

ffplay version 5.1.2 Copyright (c) 2003-2022 the FFmpeg developers
  built with Apple clang version 14.0.0 (clang-1400.0.29.202)
  configuration: --prefix=/opt/homebrew/Cellar/ffmpeg/5.1.2_1 --enable-shared --enable-pthreads --enable-version3 --cc=clang --host-cflags= --host-ldflags= --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libbluray --enable-libdav1d --enable-libmp3lame --enable-libopus --enable-librav1e --enable-librist --enable-librubberband --enable-libsnappy --enable-libsrt --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libspeex --enable-libsoxr --enable-libzmq --enable-libzimg --disable-libjack --disable-indev=jack --enable-videotoolbox --enable-neon
  libavutil      57. 28.100 / 57. 28.100
  libavcodec     59. 37.100 / 59. 37.100
  libavformat    59. 27.100 / 59. 27.100
  libavdevice    59.  7.100 / 59.  7.100
  libavfilter     8. 44.100 /  8. 44.100
  libswscale      6.  7.100 /  6.  7.100
  libswresample   4.  7.100 /  4.  7.100
  libpostproc    56.  6.100 / 56.  6.100
Input #0, rtsp, from 'rtsp://5374:1261@18.182.176.143:47995/live':
  Metadata:
    title           : Session streamed by "Atom"
    comment         : live
  Duration: N/A, start: 0.000000, bitrate: N/A
  Stream #0:0: Video: h264 (Main), yuv420p(tv, bt709, progressive), 1920x1080, 20 fps, 20 tbr, 90k tbn
  Stream #0:1: Audio: pcm_alaw, 8000 Hz, 1 channels, s16, 64 kb/s
  61.59 A-V: -0.027 fd=   0 aq=    0KB vq=   53KB sq=    0B f=0/0   /0

[我流-3]と同様にアクセスできました。
手元で確認した感じだと、SORACOM Napter の場合は音声も安定して聞こえていました。 試した回数が少ないので偶然という気もしますが。。。。

というわけで、SORACOM Napter を利用しても無事にRTSPへリモート接続ができました。
SORACOMのサービスを利用しているので設定が必要となりますが、 [我流-3] と比べると少しは手軽なのではないかと。

youtu.be


  • 良い点
    • RTSPへリモートアクセスできる。
    • SORACOMのサービスを利用することでセキュアに接続できる。
    • 必要な時だけ接続して映像を視聴できる。
      • 電源やネットワーク利用量を節約できる可能性がある。
      • SORACOM Napter は利用した月以外は費用がかからない。
    • ゲートウェイ機器さえあれば、後はオンラインですべて対応できる。
    • ATOMアプリで表示しているライブ映像と比較しても遅延が小さい。
      • クラウドへの常時録画を再生しているSORACOMユーザーコンソールと遜色がない。
  • 注意点
    • SORACOMのサービスごとの設定が必要となる。
    • SORACOMのサービス利用料が発生する。
    • リアルタイム映像を視聴するための構成なので、録画が必要な場合には別途仕組み検討することになる。
    • 各種設定はドキュメントを確認しながら進められるが、利用するサービスごとにある程度の知識は必要となる。
    • 利用サービスが多くなると仕組み自体が複雑になる。
    • ゲートウェイ機器の用意が必要となる。
    • ゲートウェイ機器も含めて、仕組み全体の運用を考える必要がある。
    • 複数名で見たい場合は、視聴するPCごとに設定が必要となる。
    • SORACOM Napter での接続は、有効期限があるため常時接続のユースケースにはミスマッチとなる。

[我流-5] AC2の映像をOBS Studio を使って配信する我流

世の中にはいろいろな配信プラットフォームがあり、誰でも無料で手軽に配信できるような世界線ですよね。
実際に ATOM Cam(無印)ライブ配信する方法が、ATOMテックさんのBlogに掲載されています。

community.atomtech.co.jp

記載されている方法で「Webカメラ化」できるのは ATOM Cam(無印) のみで、今回利用しているAC2は非対応となっている。

info.atomtech.co.jp

記載されているもうひとつの方法の、「PCにインストールできるATOMアプリを利用する」は、Windows / Mac 版共に β版 であるのと、そもそもAC2は非対応となっている。

info.atomtech.co.jp

しかし、ここまでネタBlogを読んでくれている優しい人なら気がついていると思いますが、PCでスマホアプリが動かせればいいだけという話に。
なので [我流-1] で利用した エミュレーターの画面を、ライブ配信ソフトで配信すれば良さそうです。
ちょっと前に話題になった、整理券の呼び出し番号画面を配信するのと同じ発想ですねw

togetter.com

ライブ配信ソフトは利用できるプラットフォームも広く、利用者が多いため情報も多い OBS Studio を利用します。

obsproject.com

というわけで、やることはとてもとても簡単です。


  • OBS Studio をダウンロードしてインストールします。
  • ライブ配信先のプラットフォームを選んで必要な設定します。
    • YouTube Live は初回のライブ配信を行う場合、設定から24時間後に利用可能となりました。
  • OBS Studio 自体の詳細な設定は、Webを検索するとたくさん出てくるので割愛します。
  • Android エミュレーターを起動して、リアルタイム映像を確認したいカメラを表示します。
  • すべての設定が完了したら、あとはAndroid エミュレーターの画面をOBS Studio に取り込んで配信します。

OBS Studio は特に躓くことはなかったのですが、YouTube Live の有効化がWebを検索した情報とちょっと違ってたので自分用にメモしておきます。
YouTube Live が有効化されていないと OBS Studio で以下のようなダイアログが表示されます。

ダイアログのリンクをクリックすると、YouTube Live の設定画面がWebブラウザで表示されます。

Webを検索して出てくる情報の多くが、この設定画面の話なのですが「中級者向け機能」というのが追加になってるっぽい雰囲気でした。
実際にはYouTube Live の機能を有効化するために、以下の画面のリンクをクリックする必要がありました。

ちゃんと設定さえできていれば、たったこれだけ簡単にでライブ配信が行えます。 あとは、配信されている映像をWebブラウザで視聴すればリアルタイム映像を確認できます。

youtu.be

今回はエミュレーターのウィンドウだけをOBS Studio に取り込みました。他にも例えばデスクトップ全体を取り込んで、複数のエミュレーターを表示すれば複数カメラのリアルタイム映像を配信できたり、OBS Studio を使うことで柔軟な配信ができます。


  • 良い点
    • 必要なツールをインストールするだけで簡単に使えます。
    • 配信プラットフォームの仕様に依存するが、ライブ配信は自動で録画がされることが多い。
    • 配信用のPCとAC2があれば実現できます。
    • 複数名が視聴する場合でも、Webで視聴ができる。
    • 利用するツールや配信プラットフォームは、現時点では無償で利用できる。
    • 複数カメラのライブ映像を配信できる。
      • 例えば拠点別といった分けでエミュレーター並べて、画面を取り込めば良い。
    • 利用するツールと配信プラットフォームの使い方次第となるので汎用性が高い。
  • 注意点
    • 利用するツールや配信プラットフォームの使い方を習得する必要がある。
    • 現時点では利用するツールも配信プラットフォームも無償だが、将来的に継続する保証がない。
    • 利用するツールや配信プラットフォームの仕様に依存することになる。
      • 利用しているものに仕様変更があった場合は、自力で確認と対応が必要となる。
      • 利用する内容によっては利用規約やライセンスを確認して、条件を満たす必要がある。
    • 配信用のPCがSPOFになる可能性がある。
    • 配信する対象を変更するためには、配信用のPCを操作する必要がある。
    • 配信ツールの性能に依存する可能性がある。
      • 今回は何の設定もしないと、30~50秒程度の遅延 があった。
      • ツールの設定で「超低遅延」を選択することで 5~10秒程度の遅延 になった。

[我流-6] 諸々をAWSに任せてみたかった我流(未解決)

[我流-5] でOBS Studio を利用しましたが、実行はローカルPCで行なっています。
実際の運用を考えると、配信する対象のカメラを変更したい場合や配信を止めたい場合など、その都度ローカルPCがある場所に行く必要が出てきます。

[我流-4] で利用した SORACOM Arc と SORACOM Napter を使えば、配信用のローカルPCに簡単にリモートアクセスできます。

users.soracom.io

これで現地に行かなくても設定が変更できます。 しかし、ローカルで配信を行うということは、そのローカルネットワークにライブ配信トラフィックが流れ続けます。

おそらく光回線を利用している人が多い世界線かつ、個人の場合にここを気にする必要はないのかも?ですが、ネットワークに負荷がかかっている状態ではあるので、ここのカイゼンも考えてみます。

すぐに思いつくのは、配信する環境をDaaSに押し込めてしまえば良いのでは?という考えです。これであればネットワークもDaaS側のものを利用できます。
せっかっくなのでAWSのDaaSである Amazon WorkSpaces を利用してみようと思います。

aws.amazon.com

Amazon WorkSpaces を準備する

Amazon WorkSpaces を用意して、OBS Studio とAndroidエミュレーター をインストールしてみました。 OBS Studio は問題なくインストールでき、起動も問題なかったのですが、Androidエミュレーターの方は...

まず、Androidエミュレーターのインストール時に以下のようなエラーが表示されていました。

Unable to install Intel® HAXM
Your CPU does not support VT-x.
Unfortunately, your computer does not support hardware accelerated virtualization.
Here are some of your options:
 1) Use a physical device for testing
 2) Develop on a Windows/OSX computer with an Intel processor that supports VT-x and NX
 3) Develop on a Linux computer that supports VT-x or SVM
 4) Use an Android Virtual Device based on an ARM system image
   (This is 10x slower than hardware accelerated virtualization)

Unfortunately, your computer does not support hardware accelerated virtualization.

ハードウェアの仮想化ができないので使えないと。まーDaaSですしそんなもんかと。
少し調べてみるとAWSで ハードウェアの仮想化をサポートしてるのは、ベアメタルインスタンスだけっぽいという感じがw
ベアメタルインスタンスは金額も高いし、DaaSではなくIaaSなのでいろいろ設定が必要というところが面倒w

4) Use an Android Virtual Device based on an ARM system image (This is 10x slower than hardware accelerated virtualization)

10倍遅い!!とのこと。M1 Macを使ってるので普段もARM版のAndroid エミュレーターを利用しています。
なので、10倍遅くても動かないよりはだいぶマシなのがわかっているので、チャレンジしてみましたが...

起動しなかった際のコンソールの出力は

022-12-08 02:25:20,216 [ 634558]  ERROR -       Emulator: Nexus 5 API 31 - Android Studio Dolphin | 2021.3.1 Patch 1  Build #AI-213.7172.25.2113.9123335 
2022-12-08 02:25:20,216 [ 634558]  ERROR -       Emulator: Nexus 5 API 31 - JDK: 11.0.13; VM: OpenJDK 64-Bit Server VM; Vendor: JetBrains s.r.o. 
2022-12-08 02:25:20,216 [ 634558]  ERROR -       Emulator: Nexus 5 API 31 - OS: Linux 
2022-12-08 02:25:20,216 [ 634558]  ERROR -       Emulator: Nexus 5 API 31 - Last Action: WelcomeScreen.RunDeviceManager 

という感じで原因もいまいちわからず。

Amazon WorkSpaces は Ubuntu Desktop環境だけでなくWindows環境も利用できるので、Windows / Ubuntu 両方試してみましたが、残念ながらどちらもARM版のイメージは起動しませんでした。

Windowsの方はAndroid Studio 以外のエミュレーターも試したみましたが、ハードウェアの仮想化が必要なものが多く、ほとんどでダメでした。 ハードウェアの仮想化が必要じゃないものもありましたが、グラフィックドライバーが古くインストールできなかったり、インストールできても正常に動作しないといった感じでした。無念...

Google Cloud だと ネストされた仮想化 を許可するだけで使えそうだったり

cloud.google.com

そもそもWebでAndroidエミュレーターのUIを表示できたりするみたいなので

source.android.com

この辺を頑張れば動作はしそうですね。面白そうですが今回は本題からホームラン級で逸れるのでやめておきますw

Amazon WorkSpaces でエミュレーターが動作するのであれば、ライブ配信のプラットフォームにも 同じくAWSのサービスである AWS Media Services を利用してみたかったのが心残り。残念。

aws.amazon.com

OBS Studio での AWS Media Services 利用に関して、AWSのBlogに以下のような投稿があったので試してみたかったのと、AWSに録画データが蓄積できるのであれば、後で他のAWSのサービスを使って色々できそうかなーと思ったので。

aws.amazon.com

AWS Media Services の利用と、AWSに蓄積される録画データをいろいろと試してみるのは、また別の機会にやってみたいと思います。
エミュレーター動くよ!とか、他にも方法あるよ!とかあればぜひ教えてください。

まとめ

  • リアルタイムで視聴する「だけ」ならいろいろ方法はある
  • 実用する場合は、仕組みや運用も含めて考える必要がある
  • 単純にダッシュボード的に表示だけしたいなら、タブレット1台とモニター1台を用意してダブレットの画面を表示した方が簡単かつ安定する
  • 無償で利用できるサービスも多いが、制限や仕様が変更される可能性があるので実用するなら考慮はしておく方が良さそう
  • 有償のサービスを利用するのは心理的にハードルもあるが、細かいところを丸ごとお任せできるのと、サポートを受けられるというメリットもある
  • プラットフォームを利用することで、機能追加やカイゼンといった恩恵も受けられる
  • ソラカメが今後もアップデートを続けて、より魅力的になることを期待したい

というわけで、ネタ多めで長めの記事となってしまいました...
技術的に「できる」は、探すと意外に色々あることがわかります。
いっぽうで本番で利用するような運用が「できる」を考えると、なかなか難しいこともわかります。

使えるデバイスが手元にあるのであれば、必要に応じていろいろ試してみるのお勧めします。
「できる」が増えるのは楽しいですよ。くだらないことって大事ですよね。

以上です。

SORACOM ArcとSORACOM Napter でDockerコンテナと通信してみる #SORACOM #docker #IoT

目次

前提

2021年に発表された SORACOM Arc(以下、Arc) を利用すると、SORACOM Air を利用した通信回線を利用していなくてもWi-Fi 経由でもSORACOMプラットフォームに接続できるようになります。

blog.soracom.com

users.soracom.io

これは、SORACOMプラットフォームを利用して開発したことある人はわかると思うのですが、手軽にSORACOMプラットフォームにアクセスできるのはめちゃくちゃありがたいです。

さらに Arc はバーチャル SIM(以下、vSIM) として扱われるため、SORACOM Napter(以下、Napter) を利用してリモートアクセスを行えます。

users.soracom.io

users.soracom.io

例えば、SORACOMプラットフォームとしか通信できない環境でもリモートアクセスできます。 安定のクラスメソッドさんのBlogでも紹介されてますね。

dev.classmethod.jp

というわけで、Arc+Napterはいろいろ試してみたいので、今回はArcをDockerコンテナ側に設定してNapterでアクセスしてみたいと思います。

準備

Docker の準備とSORACOM側の準備が必要です。普段使っている人は読み飛ばしてください。

Docker

Docker コンテナ側はubuntu 20.04 (Tag: focal)を利用しました。

hub.docker.com

インストールしたのは、もちろん必須の WireGuard

www.wireguard.com

www.wireguard.com

あと、接続確認にPythonを利用したので、Pythonが動作する環境を設定しています。 ホスト側はMacで、Rancher DesktopをインストールしてDockerを実行しています。

rancherdesktop.io

SORACOM Arc

SORACOMのサービスはドキュメントがとてもよく書かれているので、ドキュメントに沿って設定を行います

users.soracom.io

vSIM 発行時にWireGuardの設定に必要な情報が確認できますので、メモするなどしておいてください。

試してみる

Docker コンテナを起動して、vSiM発行時にメモした情報をWireGuardの設定ファイル /etc/wireguard/wg0.conf に記載します。 こちらもArc のドキュメントに記載があるので内容を確認してください。

users.soracom.io

あとは、Docker コンテナ側でWireGuardを起動して、SORACOMプラットフォームへの接続を確認します。

$ docker ps
CONTAINER ID   IMAGE        COMMAND                  CREATED              STATUS              PORTS     NAMES
c4c4884529f0   dev-garden   "/bin/sh -c 'echo Co…"   About a minute ago   Up About a minute             dev-garden

$ wg-quick up wg0
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 10.248.37.231/32 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] ip -4 route add 100.127.0.0/16 dev wg0
[#] ip -4 route add 10.128.0.0/9 dev wg0

$ ping pong.soracom.io
PING pong.soracom.io (100.127.100.127) 56(84) bytes of data.
64 bytes from 100.127.100.127 (100.127.100.127): icmp_seq=1 ttl=64 time=10.9 ms
64 bytes from 100.127.100.127 (100.127.100.127): icmp_seq=2 ttl=64 time=11.5 ms
64 bytes from 100.127.100.127 (100.127.100.127): icmp_seq=3 ttl=64 time=10.9 ms
^Z
[1]+  Stopped                 ping pong.soracom.io

今回は接続確認するためにいろいろ設定するのが面倒だったので Dockerコンテナ側で、HTTPサーバーを立ち上げてNapterはこのHTTPサーバにアクセスしてみることにします。 HTTPサーバーも接続確認できるだけでいいので、Pythonのいつものやつで済ませます。

$ python -m http.server 8088
Serving HTTP on 0.0.0.0 port 8088 (http://0.0.0.0:8088/) ...
127.0.0.1 - - [18/Jun/2022 08:07:53] "GET / HTTP/1.1" 200 -

SORACOM Napter

これでローカルからDockerコンテナへの接続が確認できたので、続いてSORACOMユーザーコンソールからNapterの設定を行います。

users.soracom.io

バイスのポートはこのHTTPサーバーのポートを指定してください。今回は8088を使用しました。

払い出されたURLにブラウザでアクセスして、ローカルからアクセスしたものと同じ画面が見えれば成功です。 Dockerコンテナ側のターミナルにもアクセスログが出ているのを確認できるかと思います。

python -m http.server 8088
Serving HTTP on 0.0.0.0 port 8088 (http://0.0.0.0:8088/) ...
127.0.0.1 - - [18/Jun/2022 08:07:53] "GET / HTTP/1.1" 200 -
100.127.10.17 - - [18/Jun/2022 08:08:06] "GET / HTTP/1.1" 200 -
100.127.10.17 - - [18/Jun/2022 08:08:06] code 404, message File not found
100.127.10.17 - - [18/Jun/2022 08:08:06] "GET /favicon.ico HTTP/1.1" 404 -
100.127.10.17 - - [18/Jun/2022 08:08:35] "GET /Dockerfile HTTP/1.1" 200 -
100.127.10.17 - - [18/Jun/2022 08:08:41] "GET /memo.json HTTP/1.1" 200 -
100.127.10.17 - - [18/Jun/2022 08:08:58] "GET /README.md HTTP/1.1" 200 -
^Z
[2]+  Stopped                 python -m http.server 8088

Napter で接続できない時

Napterで接続できない時は、まずはNapter側の設定を再確認してください。 デバイス側ポートアクセス元IPアドレスレンジ など意図したものかどうか。

また、有償となりますが Napter 監査ログ を利用すると、接続の詳細を確認できます。

users.soracom.io

今回ちょっとハマったのが

  • Dockerコンテナ側からSORACOMへのPingは普通に通る
  • SORACOMユーザーコンソールからNapterを設定して、アクセスするとタイムアウトしてアクセスできない
  • なぜかDockerコンテナ側でSORACOMへPingしている間は、Napterでのアクセスも通る
  • 調べてみたらWireGuardの設定が足りなさそう

    www.wireguard.com

  • NAT越しの通信の場合は PersistentKeepalive の設定をしないとダメらしい

  • Arc のドキュメントにも記載がある

    users.soracom.io

  • 毎回書いている気がするが、ドキュメント特に公式のものをよく読んだ方が解決までが早いと思った

  • 同様の症状で悩んでる場合はドキュメントを確認してPersistentKeepalive の設定を追加してみてください

まとめ

  • SORACOM ArcとSORACOM Napter を利用してDockerコンテナにアクセスできた
  • ユースケースを考えると、Dockerで開発している画面を一時的に外部の人に見せるとかでワンチャン!
  • ngrok ほど自由はないけど時間制限やCIDRでの制限はかけられるので使えるかも? ngrok.com
  • Dockerコンテナ側は、WireGuardの設定と起動だけなので /etc/wireguard/wg0.conf をコンテナ作成するタイミングでコピーしても良さそう
  • ArcとNapterの組み合わせはいろいろ試せそうなので、今後も何かあれば試して書き留めようと思った
  • Wi-Fiで普通にSORACOMプラットフォームにアクセスできるのは本当に便利で助かる
  • SORACOMプラットフォームは従量課金なので、利用する前に料金の確認を忘れずに

以上になります。

ATOM Cam 2 を Amazon Kinesis Video Streams に繋げてみた #ATOMCam #aws

目次

前提

uchimanajet7.hatenablog.com

前回の最後でATOM Cam 2(以下、AC2)がRTSPに対応してるのがわかりました。 今回はこのRTSPサーバーで配信されている映像を、Amazon Kinesis Video Streams(以下、KVS)に流してみようと思います。

とは言っても、AC2-----> RTSPクライアント -----> KVS という感じで、途中に中継の役割を持ってもらうだけなんですけど。

RTSPとは?

そもそもRTSPとは?私自身も詳しくは知らなかったので調べてみました。

ja.wikipedia.org

Real Time Streaming Protocol(リアルタイム・ストリーミング・プロトコル、略称:RTSP)は IETF において標準化されたリアルタイム性のあるデータの配布 (ストリーミング) を制御するためのプロトコルである。ストリーミングデータ自体の配信を行なうためのプロトコルではない。1998年4月にその最初の版がRFC 2326として標準化されたが、様々な問題点があることが指摘され、改訂が続けられている。2016年にReal-Time Streaming Protocolバージョン2.0がRFC 7826として標準化された。

tex2e.github.io

実態としては rtsp:// で始まるURIで記載され、HTTPと同じようにクライアント側からのリクエストにサーバー側がレスポンスを返すという形で通信されているようです。

実際にAC2のRTSPサーバーの動作確認をしてみると

のような形でリアルタイムの映像を確認することができます。

動作確認ではFFmpeg をクライアントとして利用しています

ffmpeg.org

RTSPクライアントならなんでもOKなはずです。例えばVLC media playerでも再生可能でした

www.videolan.org

FFmpeg のインストールはHomebrewで行いましたが、公式にはMac用のStatic buildも用意されているようなので環境に合わせて利用してください。

trac.ffmpeg.org

中継機

中継機はなんでも良いのですが、AC2とセットで稼働してないとダメなので、PCで中継するよりは中継専用で動いているほうが都合がよさそう。

ということで、これも手元にあった Raspberry Pi 3 B+(以下、ラズパイ) を利用してみることにした

$ uname -a
Linux raspi 5.15.38-v7+ #1552 SMP Mon May 9 17:06:58 BST 2022 armv7l GNU/Linux

$ lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description:    Raspbian GNU/Linux 11 (bullseye)
Release:    11
Codename:   bullseye

今現在、ラズパイを手に入れようとすると価格が高騰しているようなので、動作確認するだけなら手持ちの他の機器かPCを使うことも検討してください

Amazon Kinesis Video Streams

KVSに接続するにはラズパイをプロデューサーとして動作させれば良さそう。 そして、このプロデューサーがアップロードするメディアを、RTSPクライアントで受信した映像にすれば希望の形になりそう

aws.amazon.com

dev.classmethod.jp

www.youtube.com

いろいろ調べてみると、公式の情報がすごい充実していた。さすがAWSGitHubリポジトリにもいろいろあって、動作確認するぐらいの話なら作ったりしなくて良さそうな雰囲気

github.com

Producer Libraries なるSDKがあるらしく、これを利用するだけで良さそう

docs.aws.amazon.com

設定

実際にラズパイにKVSのProducerをセットアップしてみる。今回利用するのは以下。

github.com

Google先生に聞いてみると、いろいろな記事が出てきましたが、READMEに記載の通り進めれば問題なくセットアップできました。

github.com

cmake が入ってないとか、ビルドに必要なツール系が不足していることがあるので、その場合は以下を確認して追加でインストールしました。

github.com

GStreamer のplugin として動作させる必要があるみたいなのでcmake のオプションは以下で実行しました。

cmake -DBUILD_GSTREAMER_PLUGIN=TRUE ..

github.com

gstreamer.freedesktop.org

ラズパイだとcmakeは終了までにそこそこ時間がかかりました。気長に待ちましょう。 そしてcmakeが終了したら make の実行を忘れずに。。。。

github.com

実行

ビルドが終了したら、ドキュメント記載の通りビルドされたライブラリにパスを通して、動作を確認します。

github.com

To load this plugin set the following environment variables. This should be run from the root of the repo, NOT the build directory.

コマンドをコピペで試す場合には、カレントディレクトリの位置だけ注意が必要って感じですね。 ここまで来れば、あとは実際に映像の転送を開始するだけです。

gst-launch-1.0 -v rtspsrc location="rtsp://YourAC2RtspUrl" short-header=TRUE ! rtph264depay ! video/x-h264, format=avc,alignment=au ! h264parse ! kvssink stream-name="YourStreamName"

docs.aws.amazon.com

当然ながら、実行にはAWSへの権限と認証が必要です。 公式ドキュメントだと実行時引数に必要な access-key/ secret-key/ aws-region を渡しています。

今回はビルドしたライブラリにパスを通すタイミングでAWSの接続に必要な情報を環境変数に設定しています。

export AWS_DEFAULT_REGION=ap-northeast-1
export AWS_ACCESS_KEY_ID=YourAccessKey
export AWS_SECRET_ACCESS_KEY=YourSecretKey

映像の確認

最後にAC2の映像を確認します。KVSのWebコンソールに簡易的なプレーヤーがあるので、これを利用すれば映像が確認できます。 今回試した私の環境だと、遅延はおおよそ10秒以内という感じでした。 ネットワーク環境や転送している機器の性能にも依存しそうなのであくまでも目安で。

この簡易的なプレーヤーだとAWSにログインしないと確認できない状態です もう少しゆるく共有できると良さそうですが、作り込むところまでは。。。

そこはさすがのAWSサービスと言ったところで、探してみると以下がありました。

github.com

このWeb Viewerで利用する読み取り専用のAWS認証情報を作成し、ストリーム名ををはじめとした必要な項目を入力すればライブ映像を確認することができました。とても便利。

まとめ

  • ATOM Cam 2 の映像を Amazon Kinesis Video Streams に流し込んでみた
  • 中継機が必要となるが、ドキュメント通りに設定すれば簡単に始められる
  • Dockerイメージが用意されているようなので、こっちのほうがお手軽そう

docs.aws.amazon.com

  • AWS側に映像データがアップロードできるので、クラウド側のアセットが利用できるようになる
  • AC2単体でクラウド側への映像アップロードができると幸せになれそう
  • クラウドまで映像が上がってるので ソラカメ のサービス拡張に期待

soracom.jp

  • 長期安定運用を考えると中継機の部分は考慮が必要そう
  • KVS周辺のドキュメンとや、サンプルコードが充実していた
  • 公式ドキュメントやサンプルなど非常に助かった。ドキュメント大事。
  • 試すのは簡単だったので、AC2 とラズパイその他中継用の機器を持て余している人がいれば是非
  • Atom Cam Swing にはRTSPがなかったので、RTSPが目的の場合はAC2が必要かも

ATOM Cam Swingwww.atomtech.co.jp

  • あと、まさに同じことをしているAWS公式の動画があったので見ながらハンズオンするのが良さそう

www.youtube.com

以上になります。


Following is in English

medium.com

ATOM Cam 2 をオンラインミーティングの手元カメラとして利用する #ATOMCam

目次

前提

すっかり放置してましたがまたゆるーくメモ書いていこうかと。まずは小ネタから 手元にATOM Cam 2(以下、AC2)があるのですが本来の用途で使うところがないという。。。撮れるの木々の揺れだけだからなぁ

ATOM Cam 2www.atomtech.co.jp

小型 / 安価で画質も必要十分なので何かに利用できないかなーと そこで、オンラインミーティングでバーチャル背景を利用していると、人以外をカメラで撮したい時に背景に同化して見えなくなることありますよね? あれ地味に困る感じするので、AC2を物撮りようにしてオンラインミーティングで利用してみたいと思います

本来の用途

本来の用途でもワンチャン!と思ってる人がいるのであれば、ソラコム社からAC2を使ったサービスが発表されていましたので是非チェックを

soracom.com

AC2にもとも付随している、モーション検知録画だけではなく常時録画を行ってクラウド側で保存してくれるといったサービスになっているようです

soracom.jp

Q. 手元に ATOM Cam 2 があるのですが、ライセンスだけ購入して利用することはできますか?
A. 現時点では、ソラコムから購入いただいた ATOM Cam 2 のみサポートしております。

残念ながら、既存AC2はこのサービスは利用できないと記載があるので、このクラウド常時録画サービスを利用する場合には上記のソラコム版を購入する必要があるようです

我が家ではモーション検知でも常時録画でも撮るのは木々の揺れなのでw

設定

難しい設定とか、アプリを複数入れて連携するとかしません。 結論書くと、Android studio をインストールして、Android エミュレーターにてAC2をセットアップするだけです

これだけでPC画面上にエミュレータの画面がある状態となるので、オンラインミーティングの際に エミュレータの画面を画面共有することで、人物を撮しているカメラと別のカメラとして利用できるという感じになります

Android studio のダウンロードとかは以下から行えます

developer.android.com

公式のドキュメントが充実しているのでインストールやそのほか設定も ドキュメントに沿って進めていけば特に問題ないかと思います

developer.android.com

エミュレーターについては、Google Play ストア が初めからインストールされている エミュレーターを選択して実行します

developer.android.com

AC2のアプリは難しいことせずにPlay ストアから最新のアプリをダンロードして利用します こんな感じでエミュレーター画面が表示されてAC2のライブ映像が確認できれば終了です

まとめ

  • AC2を持っていたのでオンラインミーティングで物撮りカメラとして使えるようにしてみた
  • 特に手間はなく、Android エミュレーターでAC2アプリを普通に利用するだけ
  • カメラの特性上、接写はピントが合わない時があるが、他は画質も必要十分
  • Webカメラを追加で付けるよりは手軽にできたので良かった
  • Android エミュレーターが重いのか、使ってるPCが非力なのか若干動作が重いのが残念
  • AC2のアプリをちゃんと見たら RTSP プロトコル でカメラが映像ストリーミングしている模様

  • カメラ側がサーバとして配信してるようなので、これを利用したほうが楽そう予感がする
  • AC2を持て余している人で、オンラインミーティングで物撮りする様な人がいれば是非

以上になります。

AWS CDK を使って ランダム選択できる Slack Bot を作ってみた #aws #slack #cdk

f:id:uchimanajet7:20200928112946p:plain

目次

前提

9月に入って時間が取れたので AWS Cloud Development Kit (AWS CDK) を触ってみることにしました。というのも、TypeScriptでの実装例は結構な数見かけるのですがPythonの実装例は探し方が悪いのかあまり見当たらず・・・だったら自分で少し触ってみようかと。

aws.amazon.com

github.com

AWS CDKを利用するシーンを考えると、構築や運用に深く関わる可能性が高いため AWS CDKのroadmapが公開されており、今後も継続して更新が続けられていく様子が確認できるところが非常に素晴らしいです。

aws.amazon.com

github.com

というわけで、触れるタイミングで使ってみたので簡単にですがまとめてみました。

Slack bot を作ることにする

CDKを使ってみると言っても、何か動くもので継続的に使ってアップデートできるものが題材にあるといい感じなので、普段利用しているSlackのbotを作ることにしました。

作るのであれば楽しく作って、実際に使えるものが良いので Amazon API GatewayAWS Lambda の定番構成で、AWS SDK for Go でLambda関数を作成しました。

aws.amazon.com

aws.amazon.com

aws.amazon.com

Slackへのメッセージ送信は次のライブラリを利用しました

github.com

今回一番苦労したのは、このライブラリの利用でした。 SlackのAPI側と別名で定義されているメソッドや、APIで必須指定がないものが 必須になっていたりと、ちゃんと確認しながら進めないとダメな感じでした。

slack.com

qiita.com

作ったbotAWS Chatbot と同様に、botにメンションを行いコマンドを実行する仕様としています。

aws.amazon.com

コマンドの詳細はGitHubのReadmeファイルを確認してください

github.com

github.com

スクリーンショットで利用しているbotアイコンについては以下のサイトから取得しました。

stampo.fun

次項からは簡単にコマンドの紹介を行いたいと思います。

hit コマンドについて

f:id:uchimanajet7:20200928113149p:plain

hit コマンドは、Slackのチャンネルに参加しているメンバーからランダムで指定の人数を選択できます。

想定しているユースケースは、レビューメンバーの選出や会議のファシリテーターの選出になります。

実際に、リモートワークにシフトしてから ランダムに誰かを選択する と言ったことが必要になることも多く、Slackのデフォルトアプリでも良い感じがします。

抽選のために専用チャンネルを作るのが運用としては良さそうな感じですが、既存のチャンネルで使いたい時に抽選対象ではないメンバーを指定できるような仕組みも実装しています。

translate コマンドについて

f:id:uchimanajet7:20200928113226p:plain

translate コマンドは、Amazon Translate を利用して日本語を英語に、日本語意外を日本語に翻訳できます。

想定しているユースケースは、SlackでRSS Feed を確認している場合に手軽に翻訳を行えるようにすることになります。

uchimanajet7.hatenablog.com

実際に、SlackでRSS Feedを確認していると場合によっては大量にステータスが更新されることもあり、手軽に翻訳できれば少しは便利になる感じがします。

以前に作ったSlackの国旗絵文字で翻訳する方法でも対応できるのですが、Slackのイベント発火がワークスペース全体となり、特定のチャンネルや条件で絞れないためワークスペースの参加者が多い、絵文字リアクションの利用が多い場合は、翻訳と関係ないイベントが大半となり Amazon API GatewayAWS Lambda の実行回数が増えることになります。

uchimanajet7.hatenablog.com

link コマンドについて

f:id:uchimanajet7:20200928113301p:plain

link コマンドは、botにメンションする際にSlackに添付したファイルをS3にアップロードして、ファイル取得が可能となる署名付きURL(Pre-Signed URL)を発行します。

f:id:uchimanajet7:20200928113405p:plain

想定しているユースケースは、メールに添付できないサイズのファイルの受け渡しや、ローカル環境からEC2インスタンスへのファイル持ち込みなどです。

実際に、EC2インスタンスにファイルを持ち込む場合は、対象のファイルをS3に一度アップロードして、EC2インスタンスでS3からファイルを取得する方法を取ることが多いのですが、対象ファイルをアップロードする手間がなくなるのは良い感じです。

署名付きURLの期限は指定可能となっていますが、今回はLambdaの実行にIAMロールを利用しているので、最大値はこの制限に依存します。

uchimanajet7.hatenablog.com

また、ファイルの添付は複数可能ですが署名付きURLについては1ファイル1URLとなります。これはS3の署名付きURLがオブジェクトに対して発行されているためです。 複数ファイルで1URLとしたい場合は、ファイル自体を圧縮するなど工夫をお願いします。

short コマンドについて

f:id:uchimanajet7:20200928113447p:plain

short コマンドは、短縮URLを発行することができます。

想定しているユースケースは、前述の link コマンドで発行した署名付きURLを短縮URLで短いURLに変換することになります。

実際に、署名付きURLはかなりの長さがあり、コピーミスで表示できない問題が起こったりしてました。短いURLにすることにより、単純なミスが減るのは良いか感じです。

署名付きURLに有効期限はあるのですが、短縮URLでも有効期限を指定することができます。

この短縮URLを発行する仕組みについては、以下のワークショップを参考にしています。 AWS CDKのワークショップとなるためCDK側のコードも非常に参考になります。

youtu.be

github.com

cdkworkshop.com

作成中に悩んだ部分

セキュリテイについて

Lambdaレイヤーの利用

Slackからのリクエスト処理

  • Slackからイベント内容が投げ込まれるが、3秒以内に応答しないと再送される
  • 対応するためには、Slackからイベントを受け取ったらすぐに応答するか、1度投げ込まれたイベントには反応しないようにする
  • API Gateway + Lambdaが同期処理されているため、今回は後者を採用した

Lambdaの非同期呼び出し

Slack APIについて

  • Slackに参加しているメンバーを取得しようとすると、先に見つかるのがワークスペースに参加する全員を対象としたAPI
  • このAPIだとSlack利用者が多い場合には時間がかかることが想定される
  • 調べてみると会話の単位(チャンネル・DMなど)に参加しているメンバーが取得できるAPIがあった
  • しかし、上記のAPIで取得できるのはユーザーIDだけ
  • 加えてbotやワークフロー実行ユーザーが一覧に入っている模様
  • ユーザー情報の詳細が取得できるので、その情報で人なのかを判別できる
  • 今回はユーザーIDだけ必要だったので、一覧取得の時にbotユーザーをフィルタできる機能が欲しかった

テストコードについて

  • LambdaとAWS CDKのテストコードをどうするか
  • Lambdaの方はgoogle先生に聞いてみるといろいろ出てくるしMockでの対応でもなんとかできる
  • CDKの部分はどうするばいいのか悩む
  • と思ってたら、ちゃんと公式ドキュメントに記載があった
  • 現状はどちらもテストコードがない状態なので対応が必要

利用する場合に注意する部分

今回は所属するチーム内での利用を想定し、チームで管理されてAWSアカウントに対してデプロイを行っています。そのため仕様上で考慮する部分などは共有できている前提で利用を想定しています。

もし、ご自身の環境で利用していただける場合には以下の部分に注意をお願いします。

AWS Lambdaの仕様に依存

docs.aws.amazon.com

メモリ容量に依存

実行時間に依存

  • Lambdaの現時点での最大実行時間は15分
  • 現在の実行時間設定も15分
  • Slackチャンネルの参加メンバーが多数の場合は実行時間をオーバーする可能性がある
  • 添付ファイルが極端に大きい場合は実行時間をオーバーする可能性がある
  • 実行時間はこれ以上延長はできないので、内部の仕組みを改善して対応する必要がある

Amazon DynamoDBの仕様に依存

docs.aws.amazon.com

キャパシティユニットに依存

TTLの仕様に依存

その他

構築について

  • AWSリソースはAWS CDKで構築される
  • 事前にRoute 53ドメインと利用するゾーンは作成する必要がある
  • CDKで構築を実行する際にはゾーン名とゾーンIDを環境変数に設定する必要がある

削除について

  • AWSリソースはAWS CDKで削除される
  • 事前に作成したRoute 53ドメインのゾーンは削除されない
  • CloudWatch Logsに出力されたログは削除されない
  • S3はbucketが空でないと削除できないため、削除を行わない
  • その他の関連リソースは自動削除される
  • データを保持する必要がある場合には、事前にバックアップを取得するころ
  • 完全に削除するには。上記の削除されないリソースを手動で削除する必要がある

まとめ

  • AWS Cloud Development Kit (AWS CDK) を使ってみたかった
  • せっかくだから普段使っているSlackのbotを作った
  • 作ったbotは普段使えて、ちょっと便利になる感じを目標にした
  • ランダム選択はそこそこ出番がある感じ
  • CDKはとても便利で楽しい
  • CDKの実装例はTypeScriptが多い気がする
  • 個人的にはPythonで書きたいのでもう少し実装例を増やして欲しい
  • CDKで書いたコードをどうテストするか悩んだ
  • CDKを利用することで、今回のようなSlack bot開発もほとんどマネジメントコンソールにアクセスせずに進められて捗る
  • 今後も機会があれば積極的にCDKは使っていきたいと思った
  • やっぱり手を動かしているのは楽しいです

以上になります。


Following is in English

medium.com