ひつじTips

技術系いろいろつまみ食います。

「Intel.RealSense.unitypackage」を使ったUnityアプリを,Android上で使えるようにする方法

皆さまご存知のRealsenseですが,そのデバイス自身の素晴らしさもさることながら,SDKのプラットフォームの対応の幅広さも本当に素晴らしい点だと思います.

librealsenseレポジトリのReadmeも一行目から

Intel® RealSense™ SDK 2.0 is a cross-platform library (後略)

マルチプラットフォームを謳っております.

github.com


今回,AndroidからRealsenseを使いたいなぁと思って調べていて,もちろん公式対応はあるのですが,Androidのサポートはネイティブ(java)だけなのですね......

一方,Android向けにビルドもできるゲームエンジン,UnityにもRelasenseは公式に対応しているのですが,このUnityPackageはPC向けサポートのみということでAndroidには使えず......

AndroidなUnityでRealsenseを使いたい!ということで,「Android Unity Realsense」とかでググると,@UnlimitedBuildWorksさんの神記事が出てきます.

qiita.com

これで万事解決といえば解決なのですが,この方法だとjavaでRealsenseSDKを叩いてC#側に情報を渡すWrapperを自作しないといけないという......

つらい......日頃Unityでヌルく生きてるへっぽこエンジニアにはつらすぎる...…javaなんて書きたくないよう...…

ということで,Realsenseの公式UnityPackage「Intel.RealSense.unitypackage」をAndroid向けに使用できる方法を模索し,なんとか使えるようになったっぽいので,その方法をまとめます.

Realsense in Android
RealsenseのUnityPackageのサンプルシーンをAndroidバイス上で動かしてみた様子

とりあえずサンプルシーンが動いていることを確認しただけなので,何か問題あればごめんなさい.ウチではもろもろ責任取りません(免責事項)

方針

AndroidだってLinuxベースなんだから,Intel.RealSense.unitypackageをLinux向けに使えるようにすりゃいいんでしょ,というのが基本方針です.

Intel.RealSense.unitypackage の中身を見ると,C#のコードに加え,realsense2.dllIntel.RealSense.dllがあります.

C#実装はUnity様がなんかいい感じにAndroid向けにビルドしてくれます.なので残りはこの2つのライブラリ.ということで,まずはrealsense2.dllIntel.RealSense.dllAndroid向けに使えるようにします.

ただ,それだけだと真の問題Androidでのネイティブ(C/C++)ライブラリ経由なUSBデバイス使用ができない問題」にぶつかります.

realsense2.dllはUSBデバイスであるRealsense実機とのやり取りにlibusbを使っているようですが,AndroidではAndroidSDK経由ではないUSBデバイスとのやり取りは普通には難しいようです(参考1参考2
(このあたりは上記の@UnlimitedBuildWorksさんの記事にも詳しいです)

ということで,最後にUSBデバイス利用のパーミッション問題の解消をがんばります.

まとめると,大まかに

という手順で進めると,晴れてIntel.RealSense.unitypackage を使ったUnityアプリがAndroid上で使えるようになります.

準備

まず,Unity,Androidの開発環境は既にあることを前提とします.UnityEditor,VisualStudio,AndroidStudio,...などご自由にご準備ください.UnityはAndroid向けにビルドできるように「Android Build Support」を入れ,AndroidSDKなどの設定を済ませておいてください.

開発環境のOSはWindowsでやってます.Unity は2019.4.14で確認してますが,別にバージョンにあまりに依存した内容はないので2018.4以上とかならなんでも大丈夫だと思います(未検証だけど)

また,Realsense関連は以下を手元に用意してください

以前にダウンロード・クローンしていたとしても,最新のものを改めて持ってくることをおすすめします.
(自分の手元で,古いUnityPackageと最新のソースの組合せで作業してたらAPIが変わってたみたいでハマりました(同じv2系だったにも関わらず😭))

次に,作業用のUnityProjectを作成し,Intel.RealSense.unitypackageをインポートしておいてください.また,Build SettingsからAndroidにSwitch Platformしておいてください.

Realsense in android Unity package
Intel.RealSense.unitypackageをインポートしたUnityProject

手順詳細

1. Realsenseのライブラリ(realsense2.dll)のAndroid対応

これは,LibrealsenseのAndroidWrapperをビルドすることでlibrealsense.soが生成されるのでそれを流用します.

ということで,まずはLibrealsenseのAndroidWrapperのビルドから.

  1. AndroidStudioでlibrealsense/wrappers/androidを開く
    Open librealsense in AndroidStudio
    librealsenseをAndroidStudioで開いた様子
    • もし以下のようなエラーが出たら,手前味噌ですがこちらを参考にしてください
      A problem occurred configuring project ':camera'.
      > Failed to notify project evaluation listener.
         > org.gradle.api.file.ProjectLayout.fileProperty(Lorg/gradle/api/provider/Provider;)Lorg/gradle/api/file/RegularFileProperty;
  2. 適当なアプリに設定し,Build > Build Bundle(s) / APK(s)からアプリをビルドする
    • この時点では,Androidバイスを接続する必要はないです.アプリを動かすのではなく,あくまでsoファイルが目当てなので
    Build realsense app apk
    RealsenseのAndroidアプリのビルド

ビルドが終わると,librealsense/wrappers/android/build/jniLibs以下にarmeabi-v7aフォルダとarm64-v8aフォルダができていて,その中にlibrealsense2.soがあると思います.

librealsense2.so in Explorer
librealsense2.soが生成される


次に,このlibrealsense2.soをUnity側に入れていきます.RealsenseのUnityPackageを入れたUnityProjectで作業を進めます.

  1. Assets/RealSenseSDK2.0/Plugins以下に適当なフォルダ(ここではWindowsフォルダ)を作成し,そこにDLLを移動する
    Move dlls to Windows folder
    Windows用のDLLを別フォルダに移動
  2. 先ほど作成したlibrealsense2.soを,armeabi-v7aフォルダとarm64-v8aフォルダごと,Assets/RealSenseSDK2.0/Plugins以下にコピーする
    Copy librealsense.so to UnityProject
    librealsense.soをUnityProjectにコピー
  3. Assets/RealSenseSDK2.0/Plugins/Windows以下のrealsense2.dllを選択,Inspectorを開き,Include PlatformをEditorとStandaloneだけにチェックし,Applyする
    realsense.dll Settings
    realsense.dllの設定
  4. Assets/RealSenseSDK2.0/Plugins/armeabi-v7a以下のlibrealsense2.soを選択,Inspectorを開き,Include PlatformAndroidだけにチェックし,Applyする
    librealsense2.so Settings
    librealsense2.soの設定
  5. 同様にAssets/RealSenseSDK2.0/Plugins/arm64-v8a以下のlibrealsense2.soを選択,Inspectorを開き,Include PlatformAndroidだけにチェックし,Applyする
    librealsense2.so Settings
    librealsense2.soの設定

これでRealsenseのライブラリ(realsense2.dll)のAndroid対応ができました!

2. RealsenseのC#ラッパー(Intel.RealSense.dll)のAndroid対応

次はIntel.RealSense.dllですが,そもそもこのDLLって何なんでしょうか...?

Intel.RealSense.unitypackage のC#コードを読んでいるとそのヒントがあります.このUnityPackage内のC#コードには,どこにもDLLImportなどのネイティブプラグインを扱うコードが見当たりません

これより「どこかのマネージドプラグインでネイティブプラグインをラップしており,C#コードはそれを利用しているのだろうな🤔」という予想が立ちます.こうなると,もう「どこかのマネージドプラグイン」はIntel.RealSense.dllであろうことが分かります.

つまり,Intel.RealSense.dllの正体はLibrealsenseのC#ラッパーであり.そのソースはlibrealsense/wrappers/csharp/Intel.RealSenseにあります.これをAndroid向けにビルドできればよいのです.

C#Android向けビルドか~~ ざまりんとかか~~??😵😵」とかなるんですが,冷静に考えてC#Android向けにビルドしてくれるやつは身近にいます.そう,Unityにビルドさせればよいのです!

ということで,以下のようにやってみます.

  1. Assets/RealSenseSDK2.0/Plugins/Windows/Intel.RealSense.dllを消去する
    Remove Intel.RealSense.dll
    Intel.RealSense.dllをUnityProjectから消す
    • この時点でめちゃくちゃエラーが出ますが,一旦放置します
  2. librealsense/wrappers/csharp/Intel.RealSenseフォルダを,丸ごとUnityのAssets/RealSenseSDK2.0以下にコピーする
    Intel.RealSense in Unity
    Intel.RealSenseのソースをUnityProjectに持ってくる
    • まだエラーが出てますが,そのままで大丈夫です
  3. Assets/RealSenseSDK2.0/Intel.RealSenseに,AssemblyDefinitionファイル(ここではIntel.RealSense.asmdefとします)を作成する
    Create Asmdef file
    Asmdefファイルを作成
  4. Assets/RealSenseSDK2.0/ScriptsフォルダにあるRealSense.asmdefをInspectorから編集してAssemblyDefinitionReferenceにIntel.RealSense.asmdefを追加する
    Add Intel.RealSense to RealSense.asmdef reference
    Intel.RealSenseをRealSenseから参照できるようにする
  5. Assets/RealSenseSDK2.0/Misc/UtilsフォルダにあるUtils.asmdefに対しても同様にAssemblyDefinitionReferenceにIntel.RealSense.asmdefを追加する
    Add Intel.RealSense to Utils.asmdef reference
    Intel.RealSenseをUtilsから参照できるようにする
    • ここでエラーはすべてなくなるはずです
  6. 今回はネイティブプラグインデバッグビルドまでやっていないので,Assets/RealSenseSDK2.0/Intel.RealSense/NativeMethods.csdlNameをDEBUGのときもrealsense2としておく
    "realsense2d" to "realsense2"
    "realsense2d" を "realsense2" に変更
  7. Assets/RealSenseSDK2.0/Misc/EditorにあるCreateAssetBundles.csAndroid時の実装を追加する
    Add AssetBundles build code
    AssetBundlesのAndroid設定時のビルド実装を追加
  8. **Assets > Build AssetBundles** からアセットバンドルをビルドしておく
    Build AssetBundles
    AssetBundlesをビルド

この時点で,RealsenseSDKのサンプルシーンなどをUnityのAndroidビルド・デプロイすると,普通にapkファイルは作れてAndroidバイスでアプリを実行できます.

ただし,そのAndroidバイスにRealsenseを接続してもアプリ側で認識されず,期待通り機能しません.それはUSBデバイス利用のパーミッション問題が残っているからです.

3. AndroidでのUSBデバイス利用のパーミッション問題の解消

最後に,USBデバイスの認識を可能にしていきます.

上述の通り,そもそもの問題はlibrealsense.so内で使っているlibusbがそのままではAndroidでは使えない,AndroidでUSBデバイスを使うにはAndroidSDKのAPI経由でアクセスしないといけない,ということです.

LibrealsenseのAndroidWrapperがどのようにそこを解決しているのかを見ると,RsContext.init関数から始まり,いろいろ経てDeviceWatcherクラスのaddDevice関数が呼ばれ,そこでAndroidSDKのUsbManager経由でUSBデバイスをオープンし,そのファイルディスクリプタをネイティブに渡しています

このファイルディスクリプタをネイティブに渡すところまで実現できれば,あとはlibrealsense側でよしなにやってくれているはずだと踏んで,このあたりの処理をUnityに持っていくことを考えます.

  1. Assets/RealSenseSDK2.0/Scripts以下に適当なフォルダ(ここではDevicePermissionフォルダ)を作成し,以下のファイルをlibrealsenseのwrappers/android/librealsense/src/main/java/com/intel/realsense/librealsenseからコピーする
    • DeviceListener.javaDeviceWatcher.javaEnumerator.javaLrsClass.javaUsbDesc.javaUsbUtilities.java
    Import java codes
    librealsenseのUSBデバイス認識まわりのjava実装をUnityProjectへコピー
  2. DeviceWatcher.javaを叩くクラス(ここではDeviceConnect.java)を,同じくAssets/RealSenseSDK2.0/Scriptsに作成,以下のように実装する
    DeviceConnect.java

  3. C#側からAndroidのCameraのRuntimePermissionを取得し,DeviceConnect.javaを叩くスクリプト(ここではDeviceConnect.cs)を,同じくAssets/RealSenseSDK2.0/Scriptsに作成,以下のように実装する
    DeviceConnect.cs

  4. Assetsフォルダ以下にPlugins/Androidとフォルダを作成,さらにres/xmlファイルをその下に作成し,librealsenseのwrappers/android/librealsense/src/main/res/xmlにあるusb_filter.xmlをコピーする
    usb_filter.xml in Unity
    usb_filter.xmlをUnityProjectにコピー
  5. 先ほど作ったAssets/Plugins/Androidに以下のAndroidManifest.xmlを配置する
    AndroidManifest.xml

    • これは,UnityのデフォルトのAndroidManifest.xml(詳細はこちら)に対し, USBホストAPIを使用する設定(詳細はこちら)と, Cameraのパーミッションの設定(詳細はこちら)とを 追加したものになります

これで,Realsenseの公式UnityPackage「Intel.RealSense.unitypackage」をAndroid向けに使用することができるようになります!お疲れさまでした.

動作確認

では実際に「Intel.RealSense.unitypackage」のサンプルシーンをAndroidバイスで動かしてみます.

  1. Realsenseのサンプルシーン(ここでは「StartHere」シーン)を開く
    Sample scene "StartHere"
    サンプルシーン "StartHere"
  2. 空のGameObjectを作成し,先ほど作ったDeviceConnectコンポーネントを追加する
    Add "DeviceConnect"
    "DeviceConnect"をシーンに追加
  3. アプリをビルドし,Androidバイスへデプロイする
    • AndroidバイスをUSBケーブル or ネットワークでPCと接続した状態で,Build SettingsでStartHereシーンを追加,Run Deviceを適切に設定した上で,Build And Runをクリックする
      Build and deploy to your Android device
      ビルドしてAndroidバイスへデプロイ
    • ここで,カメラ使用のパーミッションが出るはずなので,カメラの使用を許可する
      User permission for camera
      カメラ使用のパーミッション
    • この時点では,「Connect A Realsense Device」のメッセージが出ているはず
      Apps before connecting Realsense device
      「Connect A Realsense Device」のメッセージ
  4. AndroidバイスにRealsenseデバイス(ここではD435i)を接続する
    Connect with Realsense device
    RealsenseデバイスAndroidに接続している様子

これでアプリがRealsenseデバイスを認識し,メニューが現れるはずです!

あとは適当にメニューを選べば,ちゃんとRealsenseデバイスからの情報を取得できる様子がわかると思います.

Realsense in Android
RealsenseのUnityPackageのサンプルシーンをAndroidバイス上で動かしてみた様子

また,初回以降は,RealsenseデバイスをUSBでAndroidバイスに接続したタイミングでポップアップが出て,アプリが起動できるようになるはずです.

さいごに

とりあえず速報的にここに書いたけど,将来的に自分でもこんがらがること必至なので,librealsense本線にプルリクしたいなぁ~~という気持ち.