MagicLeapにRTPで映像を送ってnative buildしたffmpegとOpenCVで画像を取り出して遊びたかった

更新日から1年以上経過しています。情報が古い可能性がございます。

この記事は Magic Leap Advent Calendar 2020 の22日目の記事です。
自称日本で10人に入るMagicLeapのC使いとして、今日ももりもり Native Plugin を build していこうと思います。

まず、大切なことですが、この記事でやろうとしたことは失敗しました。MagicLeap上で動かした際の Connection timeout の error が取れず、RTP信号を受信できませんでした。
悲しみ。。。以下はまだ元気だったころの記事をそのまま記載しておきます。

さて、今回の記事はすべて触れると本当に長くなるので、目次で全体像をお見せしたうえで、ちょこちょこ解説していこうと思います。

Magic Leap で RTP 映像を表示して Texture に張り付けるまで

  1. ffmpeg を MagicLeap 向けに build する

    OSS である ffmpeg を、MagicLeap 用に build して使えるようにします。

  2. OpenCV を MagicLeap 向けに build する

    OpenCV を build して使っていきます。

  3. Unity で使うための Native Plugin を作る

    Editor 上でも使えるようにちょっと変な構成で進めます。

  4. ffmpeg を用いて PC からカメラ映像を送信

    ffmpeg を使うだけなので、とりあえずはシェルで。C APIを叩いて好きに ffmpeg を使っても構いません。

  5. Magic Leap で カメラ映像を受信

    信号を受信!

それでは頑張っていきましょう。

0. 実施環境

初めに、今回使用した環境を載せておきます。

  • main OS : Windows 10 Pro 20H2 (19042.685)
  • sub OS : macOS Big Sur 11.1 (2018モデル)
  • Unity : 2020.1.17f1
  • Lumin OS : 0.98.10
  • Lumin SDK : 0.24.1
  • MagicLeap Unity Package : 0.24.2
  • Visual Studio : 2019 (16.8.3)
  • VS Code : 1.52.0
  • MagicLeap VS Code Extension : 0.9.24
  • cmake : 3.19.1
  • make : 4.3

ライブラリのバージョン

  • OpenCV : 4.5.0
  • ffmpeg : 4.3.1 (Editor, win), 3.4.8 (MagicLeap)

ffmpeg の MagicLeap 版の version が低いのは、最新の物を build しようとすると以下のような error が出たためです。

error: member reference base type '__be32' (aka 'unsigned int') is not a structure or union

ffmpeg-android を見ると、NDK の version が低いと出るようです。

1. ffmpeg を MagicLeap 向けに build する

当初、WSL2 上で configure を通し、さくっと作ったMakefile-to-mabu Converter で mabu を生成して MagicLeap 向けに build する方法をとっていたのですが、Mac を使えば簡単に cross-platform build できることに気付いたため、WSL2 を使用するのはやめて mac でサクッと build してしまいます。

それでは mac を起動して、ffmpeg-3.4.8 を download してきます。あ、Xcode command line tools が必要なので、Xcode を入れて導入しておきます。導入が済んでいるなら、ffmpeg のdirectory で configure を実行します。

./configure \
  --prefix=$(pwd)/magicleap \
  --enable-shared \
  --disable-static \
  --disable-doc \
  --disable-programs \
  --disable-avdevice \
  --disable-symver \
  --disable-x86asm \
  --cross-prefix=$HOME/Magicleap/mlsdk/v0.24.1/tools/toolchains/bin/aarch64-linux-android- \
  --target-os=android \
  --arch=aarch64 \
  --enable-cross-compile \
  --sysroot=$HOME/Magicleap/mlsdk/v0.24.1/lumin \
  --extra-cflags="-Os -fpic"

pkg-config が無いと言われますが、気にせず続けていきましょう。

make && make install

ffmpeg-3.4.8/magicleap に一式出来上がっているはずです。

余談ですが、WSL2 でも、exe を省略しても実行できるように設定を行えば同様に実行できると思われます。気になる方はやってみてください。

2. OpenCV を MagicLeap 向けに build する

以前に書いた記事と大きく変わらず実行していきます。場所としては3か所、以下のコメントが書かれている箇所の ANDROID を (ANDROID OR LUMIN) に書き換えていきます。

# ----------------------------------------------------------------------------
# Detect compiler and target platform architecture
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
#  Build & install layouts
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
#       CHECK FOR SYSTEM LIBRARIES, OPTIONS, ETC..
# ----------------------------------------------------------------------------

前回同様簡単にするために world ライブラリにしておきます。変更箇所は当該記事を参照ください。

これでOpenCVの準備もできました。

3. Unity で使うための Native Plugin を作る

開発のための準備

VS Code の MagicLeap Extention にある Project Templates から、Shared Library Project Template を選択して作成します。

名前は何でも良いので、samplereceiver とでもしておきます。作成したら、inc 配下に上で作成した ffmpeg と OpenCV の header を入れておきます。
samplereceiver/inc/libavcodec/* のような構成になっていれば大丈夫です。libcodec に限らず、作成した header は全て入れておきましょう。opencv2 も忘れずに入れてください。

続いて lib directory を作成して、同じく作成した .so を全て入れておきます。
samplereceiver/lib/libavcodec.so のような感じの構成で全て入れておけば大丈夫です。

Editor 用の library の用意

Unity を動かす主戦場に選んだのは Windows なので、Windows 上の Editor でも動くようにしておきたいです。そうしないと debug が苦しくなります。

それぞれの library を Windows 用に build としたいところですが、面倒なので横着します。

Windows には vcpkg という、C/C++ 用の library を自動で build し、更には自動で Visual Studio と連携してくれる便利なものがあります。repository を見てもらえばわかる通り、Microsoft 製なのでありがたく使わせていただきましょう。

https://github.com/microsoft/vcpkg

vcpkg の注意点として、path が深くなりすぎると展開に失敗したりするので、C:\vcpkg か、D:\vcpkg 辺りに用意しておくのが良いかと思います。
また、最近の Windows10は、path長の制限を取り払えるので、その設定をしておくのも良いと思います。

https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation

vcpkg を使えるようにしたら、OpenCV と ffmpeg を用意してもらいましょう。

vcpkg install opencv[world]:x64-windows ffmpeg[ffmpeg,ffplay,nvcodec,x264]:x64-windows

とかで良かったはず。error が出たらそれに従ってください。ffmpeg のかっこの中の記述は、映像送信用の ffmpeg 実行ファイルと確認用の ffplay、hardware encode をしたいので nvcodec を入れています。x264 は何となくです。

vcpkg で入れた library は、vcpkgi/installed/x64-windows/ 以下に入っているので、そこの lib から ffmpeg と OpenCV の .lib を samplereceiver/lib/ 配下にコピーしてください。因みに実行時に必要となる .dll は bin の中に入っているので、そちらは作成する Unity Plugin と一緒に、Assets/Plugins/Windows/x64/ 辺りに放り込むことになります。

さて、これで準備が整いました。

Plugin の作成

まずは samplereceiver.mabu を直します。

stl/libgnustl

の記述があると gnustl を見に行ってしまい、MagicLeap 上で動きません。Linker error が出ます。なので消します。代わりに USES のところに stdc++ の追記をして、以下のようにしておきます。

USES = \
	ml_sdk \
	stdc++


OPTIONS = \
	standard-c++/11

試しに build してみましょう。
menu bar の Terminal を click すると、pull down menu に `Run Build Task…`という項目があるのでそこを click します。以下の menu が表示されます。

Host OS というのが Windows 向けの .dll を作成する項目で、Lumin OS というのが MagicLeap 向けの .so を作成する項目です。

以前書いた記事では Tasks.json を書き換えていましたが、いつの間にか書き換えずとも2019の compiler を使ってくれるようになっていました。

それでは code を書いていきましょう。まずは起点となる samplereceiver.h を書いていきます。ここを Unity 側に公開する entry point とします。

// samplereceiver.h

#ifdef _WIN32
#define UNITYEXPORT __declspec(dllexport)
#else
#define UNITYEXPORT
#endif

#include <stdio.h>

extern "C" {
    UNITYEXPORT void start(const char* input);
    UNITYEXPORT void close();
    UNITYEXPORT void receive(unsigned char*);
}

start の引数の input は、SDP の data か file path を想定しています。ここで Stream を待ち受ける準備をします。

close は終了処理時に呼ばれ、色々お掃除することを想定しています。

receive で Unity 側から受け取った Texture2D の pixel の pointer に、OpenCV の Mat のデータを入れ込みます。

簡単な API ですね。
では本体も準備していきましょう。

// samplereceiver.cpp

#include "libstereoreceiver.h"

void start(const char* input)
{
// TODO implement me
}

void close()
{
// TODO implement me
}

void receive(unsigned char* image)
{
// TODO implement me
}

で、ffmpeg と OpenCV の Document を見ながら実装していきます。ここに色々書いても執筆時点(2020/12/21)で MagicLeap で動かないので、動くものができたら GitHub 等で公開するようにします。

https://ffmpeg.org/doxygen/3.4/index.html

https://docs.opencv.org/4.5.0/

一式入れた Plugins はこんな感じになりました。

ML

Windows

Windows の方は、vcpkg で入れたので、OpenCV の依存ライブラリが必要になっているので多くなっています。

4. ffmpeg を用いて PC からカメラ映像を送信

今回 Stereo live streaming を目指す、ということで、ZED2 というカメラを用意しました。

https://www.stereolabs.com/zed-2/

そのまま camera device として読み込むと、Side by Side のステレオ映像が取り出せるカメラとなっております。そのため、ステレオ映像の Streaming は実に簡単に行えます。

vcpkg/installed/x64-windows/tools/ffmpeg/ にffmpeg.exe があるはずなので、それを使ってサクッと送信します。

ffmpeg.exe -f dshow -i video="ZED 2" -framerate 30 -video_size 2560x720 -c:v h264_nvenc -r 30 -an -f rtp rtp://239.1.1.5:23000 -sdp_file stereo.sdp

720p の Side by Side 映像のため、解像度は 2560×720 となっております。video codec に h264_nvenc を指定して、Hardware encode を行っています。出力は RTP を使って Multicast をしています。

ここで出力された stereo.sdp は以下のようになっています。

v=0
o=- 0 0 IN IP4 127.0.0.1
s=No Name
c=IN IP4 239.1.1.5
t=0 0
a=tool:libavformat 58.45.100
m=video 23000 RTP/AVP 96
b=AS:2000
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1

ではこの stereo.sdp を Assets/StreamingAssets/ に入れて読み込みましょう。

5. Magic Leap で カメラ映像を受信

Unity 上で Plane に Stereo Shader を貼り付けた状態で実行してみます。

この時点で Zero Iteration 上では ステレオ動画が確認できます。Stereo Shader を外すとこんな感じです。

左右で視差があるのがわかりますね。

Editor 上で動作が確認できたので MagicLeap に入れてみましょう。

……

あれ?

動かない!!!

え、なんで????

関数の返り値を見てみると、av_read_frame がConnection timeout を返している……

なんでや!!

5.5 試行錯誤

MagicLeap のドキュメントを読むと、TCP と UDP は使える。試しに Protocol を UDP にして送ってみる、が、Editorでは映るものの、実機では動かない。

え、RTP 塞いでる???でも WebRTC 対応したって言ってたし、そんなわけ、と思って SDK の中を見てみると、
$MLSDK/include/runtime/external/webrtc/media/base/rtp_utils.h
とかあるから RTP は塞いでないはず。

UDP も TCP の 570xx みたいに使える port が限られている?
と思い、MagicLeap example で使っている 23000 にしてみるも、ダメ。

どうやっても Connection Timeout が消えない。

試してダメだったこと

  • Port 570xx(TCPで開いてるとこ)とPort 23000(UDP の Example で使ってるところ)で待ち受けてみる
  • ffmpeg ビルドの configure を変えてみる(正解ないので適当なところまで)
  • RTP をやめて UDP で動画を送信してみる(mpegts を指定)
  • TCP で送ってみる(mpegts)
  • 受信 thread と decode thread を分けて non-blocking にしていたのを single thread に書き換え
  • 設定だけでなく、Privilege Requester も置いて確実な LAN の有効化

怪しいところ

TCP にしても、UDP に変えてもダメなことから ffmpeg の configure が正しくないのでは?←調査中

最後に

いや、本当にごめんなさい。エントリーした時点では Editor 上で動作確認取れていたし、ffmpeg の DllNotFound や undefined symbol も解決して油断していました。

本当にマジ分からんので、どなたか動いたらコメントなり Twitter なり、MagicLeap の Facebook や Slack で教えてください。お願いします。

敗因:慢心

2件のコメント

  1. LocalAreaNetworkのPrivilegesを設定していないとか…?
    (Project Settings→MagicLeap→Manifest Settings→Privileges)

コメントする

メールアドレスが公開されることはありません。