簡易翻訳機を作ってみる その2 DeepLのWebAPIを使ってみる
今回は前回の続きです。
xr-physics-work-etc.hatenablog.com
DeepLのwebAPIを使用するための登録と、お試しで使用してみます。
「DEEPL API FREE」に登録する
まずは、DeepLのwebAPIを使用するために登録します。
今回は、試験的に利用するだけなので、月50万字制限のDeepL API Freeを使用します。
下記URLから「Sign Up for free」を選択します。
次にアカウントを作成するために、メールアドレスと、登録するパスワードを入力します。
次に住所とクレカの情報を登録します。
無料版ですが、不正利用を防ぐためにクレカ登録が必要なようです。
Pro版に手動でアップグレードしない限り、課金されることはないと記載されています。
言語はほぼ英語表記ですが、登録の際Last nameが先に来ていたり、県名が漢字で表記されていたりと日本人向けの登録フォームになっています。
登録すると、今回使用するサービス(DeepL API Free)についての概要が表示され、利用規約、翻訳が完全でない場合がある旨理解したことをチェックを入れます。
右下のSign Up for Free
を選択することでサインアップできます。
正しく利用規約などにチェックできていればアカウントの準備ができます。
作成されたDeepLのアカウント設定からは登録情報が確認できます。
Plan
からは契約しているプランの詳細と期間、Account
から登録情報とAPIを使用するための認証キー、Usage
からは月50万字のうち、どの程度使用しているか確認することができます。
WebAPIを使った翻訳を試す
アカウントの作成ができたので、さっそくWebAPIを使った翻訳を試してみます。
DeepLのAPI仕様は下記に記載されています。
今回は手動で実施するため、curlコマンドで動作確認します。
APIの使用に必要なDeepLの認証キーは登録情報のAccount
から下記項目を確認することで取得できます。
また、ログインしていると、API Reference中のExample中で認証キーは自動で補完されて表示されます。
APIのExampleを参考に下記コマンドを試します。
curl https://api-free.deepl.com/v2/translate auth_key=[DeepLの認証キー] -d "text=こんにちは、世界!" "target_lang=EN"
WindowsPowerShellの場合、パラメタ-d
が複数あることに文句を言われるので、コマンドプロンプトで動作確認しました。
- 1回目
下記エラーが発生しました。
curl: (35) schannel: next InitializeSecurityContext failed: Unknown error (0x80092012) - 失効の関数は証明書の失効を確認 できませんでした。
調査したところ、SSL 証明書の失効チェックが実行できないようです。そのため、curlに-k
オプションを追加しました。
- 2回目
応答は帰ってきましたが、言語が英語と判断されてしまい、うまく翻訳できていません。
入力:
curl -k https://api-free.deepl.com/v2/translate auth_key=[DeepLの認証キー] -d "text=こんにちは、世界!" "target_lang=EN"
応答:
{"translations":[{"detected_source_language":"EN","text":"ɂ́AE!"}]}
応答が文字化けていることから、日本語送信時、文字コードが一致していないようです。
そのため、送る内容をjson化して、utf-8でファイルに保存したものを送る方法に変更しました。
コマンドプロンプトの場合、UTF-8の文字コードで送るにはファイルを読み込ませるしかないようです。
zenn.dev
そのため、"text=こんにちは、世界!"
の部分のみファイル読み込みで再実施しました。
- 3回目
入力:
curl -k https://api-free.deepl.com/v2/translate auth_key=[DeepLの認証キー] -d @text.txt "target_lang=EN"
※text.txtの内容:text=こんにちは、世界!
応答:
{"translations":[{"detected_source_language":"JA","text":"Hello, world!"}]}
無事入力言語が日本語と認識され、英語に翻訳することができました。
Windowsのコマンドプロンプトの場合、curlで日本語を翻訳するのは不都合が多いので、アプリを作って運用するのがよさそうです。
なお、今回の動作確認で29文字ほど消費しました。
開発する分には月50万字を使い切ることはほぼないかな。
次回は、HoloLens2からDeepL APIを使用するための実装を行っていきます。
簡易翻訳機を作ってみる その1 方法検討および調査
最近小技だったり、SDKの使い方だったりが多いので、たまには小規模なアプリを作ってみようと思います。
今回は、HoloLensの音声入出力周りやweb APIをHoloLensから触る方法の学習も兼ねて、簡易翻訳機を作ってみようと思います。
※どこかで誰かがやっていそうな気はしますが、気にしない
翻訳後の文章はUnityEventなどに登録することでどこにでも出力できるようにして、様々なアプリに組み込むことができるようにしたいです。
そうすれば、HoloLensを用いたほかのアプリにも取り入れることができそうです。
せっかくMRTKを用いるので、Oculus Questなど別デバイスの対応もしたいですね。
内容の検討
今回の翻訳機はHoloLensを用いた以下の簡単な構成を検討しています。
追加で面白いものを思いついたら、別途追加しようと思います。
※画像認識と翻訳組み合わせられると面白そうですが、画像認識を結構ガチでやらないと精度でなさそう。
- HoloLensのマイクを用いて音声を入力する。
- 入力した音声を文章にする。
- 文章を翻訳する。
- 翻訳した結果を文章と音声として出力する。
技術検討
1, 2についてはHoloLensに標準搭載されているディクテーションの機能で実現できそうです。
ただし、ソフトウェアキーボードを毎度表示してディクテーションを開始するのはおっくうなので、ハンドメニューのボタンやボイスコマンドで翻訳開始、停止を実現したいです。
3についてはHoloLensのみでは難しいです。
外部のサービスに頼ることになりますが、翻訳精度の問題もあるので複数同時使用や、選択的に使用することを可能にしたいです。
現状である程度無料使用でき、HoloLensからweb APIをたたいて使用できるサービスは以下です。
ほかにもおもしろそう or 魅力的なものがあれば導入してみます。
Deepl www.deepl.com 月50万字まで無料(上限のため、課金される心配なし?)
Azure Translator azure.microsoft.com 毎月 200 万文字は無料
Google cloud.google.com 月に最初の50万字まで無料(それを超えると課金される模様)
下記を使用するとタダでできそうだけど、規約的に大丈夫か? qiita.com
4についてはとりあえずはMRTK 2.7で追加されたTextToSpeechを用いてみようと思います。
docs.microsoft.com
ただし、どの程度の言語に対応しているか、現時点では不明なため、別のものも模索します。
未対応言語はとりあえず文字列出力ですかね。
※日本語の場合下記も使ってみたいですが、基本的には有料のためまたの機会に。
とりあえず、週によって作ったり作らなかったりするかとは思いますがのんびり作ってみようと思います。
短いですが、今回はここまで。
HoloLens2のファイルIOを確認する (その2 テキスト、画像、動画読み込み)
今回は前回の続きです。
xr-physics-work-etc.hatenablog.com
今回は、テキスト、画像、動画の読み込みを3種の方法で読み込んでみます。
読み込み方法は以下の通りです。
- テキスト:WUPの
FileIO.ReadTextAsync
でFileStorage
オブジェクトから読み込み - 画像:ファイルパスから
System.IO.File.ReadAllBytes
を用いたByte列読み込み - 動画:VideoPlayerコンポーネントでURL(ファイルパス)指定での読み込み
読み込み対象は、前回読み込めなかったMediaServerDevices
とRemovableDevices
を除いた14種としました。
1. 準備
準備として読み込み用のコンポーネントとGameObjectを用意します。
読み込み用コンポーネントでは、プルダウンメニューから読み込み対象を選択し、対象ディレクトリのテキスト、画像、動画を読み込み対象とする構成です。
- StorageReadCheck.cs
クリックで展開
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Video; using TMPro; #if WINDOWS_UWP using System; using Windows.Storage; #endif public class StorageReadCheck : MonoBehaviour { /// <summary> /// ストレージ情報クラス /// </summary> [SerializeField] private StorageFolderInfo storageFolderInfo; /// <summary> /// タイトル /// </summary> [SerializeField] private TextMeshPro title; /// <summary> /// ビデオプレイヤー /// </summary> [SerializeField] private VideoPlayer videoPlayer; /// <summary> /// 画像表示用MeshRenderer /// </summary> [SerializeField] private MeshRenderer imageMeshRenderer; /// <summary> /// 文章読み込み用テキスト /// </summary> [SerializeField] private TextMeshPro text; // Start is called before the first frame update void Start() { LoadFiles(); } // Update is called once per frame void Update() { } private void LoadFiles() { title.text = storageFolderInfo.targetStorageType.ToString(); LoadVideo(); LoadImage(); LoadText(); } private void LoadVideo() { #if WINDOWS_UWP videoPlayer.url = storageFolderInfo.GetStorageFile("test.mp4").Path; #endif } private void LoadImage() { Texture2D texture = new Texture2D(2, 2); #if WINDOWS_UWP texture.LoadImage(System.IO.File.ReadAllBytes(storageFolderInfo.GetStorageFile("test.png").Path)); #endif imageMeshRenderer.material.mainTexture = texture; } private void LoadText() { #if WINDOWS_UWP text.text = FileIO.ReadTextAsync(storageFolderInfo.GetStorageFile("test.txt")).GetAwaiter().GetResult(); #endif } } /// <summary> /// ストレージ情報クラス /// </summary> [System.Serializable] public class StorageFolderInfo { public TargetStorageType targetStorageType; #if WINDOWS_UWP public Dictionary<TargetStorageType, StorageFolder> targetStorageDict = new Dictionary<TargetStorageType, StorageFolder>(); #endif /// <summary> /// コンストラクタ /// ディクショナリ設定 /// </summary> public StorageFolderInfo() { #if WINDOWS_UWP targetStorageDict[TargetStorageType.AppCaptures] = KnownFolders.AppCaptures; targetStorageDict[TargetStorageType.CameraRoll] = KnownFolders.CameraRoll; targetStorageDict[TargetStorageType.DocumentsLibrary] = KnownFolders.DocumentsLibrary; targetStorageDict[TargetStorageType.MediaServerDevices] = KnownFolders.MediaServerDevices; targetStorageDict[TargetStorageType.MusicLibrary] = KnownFolders.MusicLibrary; targetStorageDict[TargetStorageType.Objects3D] = KnownFolders.Objects3D; targetStorageDict[TargetStorageType.PicturesLibrary] = KnownFolders.PicturesLibrary; targetStorageDict[TargetStorageType.Playlists] = KnownFolders.Playlists; targetStorageDict[TargetStorageType.RecordedCalls] = KnownFolders.RecordedCalls; targetStorageDict[TargetStorageType.RemovableDevices] = KnownFolders.RemovableDevices; targetStorageDict[TargetStorageType.SavedPictures] = KnownFolders.SavedPictures; targetStorageDict[TargetStorageType.VideosLibrary] = KnownFolders.VideosLibrary; targetStorageDict[TargetStorageType.LocalFolder] = ApplicationData.Current.LocalFolder; targetStorageDict[TargetStorageType.LocalCacheFolder] = ApplicationData.Current.LocalCacheFolder; targetStorageDict[TargetStorageType.RoamingFolder] = ApplicationData.Current.RoamingFolder; targetStorageDict[TargetStorageType.TemporaryFolder] = ApplicationData.Current.TemporaryFolder; #endif } #if WINDOWS_UWP /// <summary> /// フォルダ情報取得 /// </summary> /// <returns></returns> public StorageFolder GetStorageFolder() { return targetStorageDict[targetStorageType]; } /// <summary> /// ファイル情報取得 /// </summary> /// <param name="fileName"></param> /// <returns></returns> public StorageFile GetStorageFile(string fileName) { return targetStorageDict[targetStorageType].GetFileAsync(fileName).GetAwaiter().GetResult(); } #endif } /// <summary> /// 対象ストレージタイプ /// </summary> public enum TargetStorageType { AppCaptures, CameraRoll, DocumentsLibrary, MediaServerDevices, MusicLibrary, Objects3D, PicturesLibrary, Playlists, RecordedCalls, RemovableDevices, SavedPictures, VideosLibrary, LocalFolder, LocalCacheFolder, RoamingFolder, TemporaryFolder, }
次に、表示するためのオブジェクトを用意します。
タイトルとテキスト読み込み用にTextMeshPro, 動画再生、画像表示用にQuadを空のGameObjectの配下に作成します。
Quadのマテリアルには、アプリ実行中にテクスチャを更新できるようにAssigned at Runtime
にチェックを入れておきます。
動画再生用のQuadにはVidoPlayerコンポーネントをアタッチします。
先ほど作成したStorageReadCheck
コンポーネントを親オブジェクトにアタッチして読み込み対象をプルダウンメニューから設定します。
当該オブジェクト(今回の場合FileLoadTest)をコピーし、それぞれ読み込みたい対象の読み込み対象をプルダウンメニューを変更して設定します。
最後に作成したFileLoadTest
を空オブジェクトの配下に変更し、GridObjectCollection
コンポーネントで整列させます。
2. 動作確認
各ディレクトリにtest.png, test.mp4, test.txtを配置してアプリを起動すると読み込み結果が確認できます。
※作成対象のディレクトリの対応は前回を対象xr-physics-work-etc.hatenablog.com
実際に読み込みを行った結果は以下の通りです。
テキスト、画像はどこからでも読み込みができ、パス指定でUnity標準コンポーネントに読み込ませている動画のみ、アプリ用ディレクトリ中のフォルダからのみ読み込みができる結果となりました。
StorageFolder | テキスト (UWP ReadTextAsync) | 画像 (バイト列) | 動画(ファイルパス読み込み) |
---|---|---|---|
KnownFolders.AppCaptures | ○ | ○ | × |
KnownFolders.CameraRoll | ○ | ○ | × |
KnownFolders.DocumentsLibrary | ○ | ○ | × |
KnownFolders.MusicLibrary | ○ | ○ | × |
KnownFolders.Objects3D | ○ | ○ | × |
KnownFolders.PicturesLibrary | ○ | ○ | × |
KnownFolders.Playlists | ○ | ○ | × |
KnownFolders.RecordedCalls | ○ | ○ | × |
KnownFolders.SavedPictures | ○ | ○ | × |
KnownFolders.VideosLibrary | ○ | ○ | × |
ApplicationData.Current.LocalFolder | ○ | ○ | ○ |
ApplicationData.Current.LocalCacheFolder | ○ | ○ | ○ |
ApplicationData.Current.RoamingFolder | ○ | ○ | ○ |
ApplicationData.Current.TemporaryFolder | ○ | ○ | ○ |
動画がアプリ固有ディレクトリ以外で読み込めない理由を確認してみます。
アプリ固有ディレクトリ中のTempState/UnityPlayer.log
を確認すると対象ディレクトリ中のファイル読み込み時に以下のメッセージが出力されていました。
WindowsMediaFoundation received empty file U:\USERS\[ユーザ名]\[対象ディレクトリ]\test.mp4
Unity標準のVideoPlayerの場合はファイルパス読み込みのため、UWPの一般フォルダの厳しい読み込み権限の問題で、ファイルの内容が読み込めないのかもしれません。
今のところ、動画読み込みにVideoPlayerを用いる場合はアプリ固有のディレクトリに動画ファイルを置くしかありませんが、バイト列で動画を読み込めるようなアセットがあれば、他のテキストや画像と同じようにKnownFoldersからも読み込めるかもしれません。
HoloLens2のファイルIOを確認する (その1 フォルダ、ファイル作成)
今回は、自分自身の備忘録もかねて、HoloLens2のファイルIOを確認します。
2回ほどに分けて、フォルダ、ファイル作成と、動画や画像、テキストの読み込みを各フォルダから行えるかどうか確認します。
このあたりの情報は、HoloLens2出てすぐのものも多く、最新OSバージョンで挙動が変わっているものもあるかもしれないので、まとめてみます。
その1では、各フォルダにフォルダ、ファイル作成を行えるかどうか確認します。
対象は、KnownFoldersと、ApplicationData中のアプリ固有のフォルダです。
1. 準備
まずは、確認を行うための準備の内容です。
フォルダ、ファイル作成用のスクリプトを作成しました。
ファイル作成については、作成したファイルの読み込みも行っています。
- StorageControlCheck.cs
クリックで展開
using System; using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; #if WINDOWS_UWP using Windows.Storage; #endif public class StorageControlCheck : MonoBehaviour { public async void EachStorageTest() { // https://docs.microsoft.com/ja-jp/uwp/api/windows.storage.knownfolders?view=winrt-19041 // https://docs.microsoft.com/ja-jp/windows/uwp/get-started/fileio-learning-track #if WINDOWS_UWP StorageTest(KnownFolders.AppCaptures); await Task.Delay(1000); StorageTest(KnownFolders.CameraRoll); await Task.Delay(1000); StorageTest(KnownFolders.DocumentsLibrary); await Task.Delay(1000); StorageTest(KnownFolders.MediaServerDevices); await Task.Delay(1000); StorageTest(KnownFolders.MusicLibrary); await Task.Delay(1000); StorageTest(KnownFolders.Objects3D); await Task.Delay(1000); StorageTest(KnownFolders.PicturesLibrary); await Task.Delay(1000); StorageTest(KnownFolders.Playlists); await Task.Delay(1000); StorageTest(KnownFolders.RecordedCalls); await Task.Delay(1000); StorageTest(KnownFolders.RemovableDevices); await Task.Delay(1000); StorageTest(KnownFolders.SavedPictures); await Task.Delay(1000); StorageTest(KnownFolders.VideosLibrary); await Task.Delay(1000); StorageTest(ApplicationData.Current.LocalFolder); await Task.Delay(1000); StorageTest(ApplicationData.Current.LocalCacheFolder); await Task.Delay(1000); StorageTest(ApplicationData.Current.RoamingFolder); await Task.Delay(1000); StorageTest(ApplicationData.Current.TemporaryFolder); await Task.Delay(1000); #endif } #if WINDOWS_UWP private async void StorageTest(StorageFolder storageFolder) { Debug.Log($"[{storageFolder.DisplayName}] storageFolder.Path:[{storageFolder.Path}]"); Debug.Log($"MakeDir result about {storageFolder.DisplayName} is {await MakeDir(storageFolder, "test")}"); Debug.Log($"MakeFile result about {storageFolder.DisplayName} is {await MakeFile(storageFolder, "test.txt")}"); } private async Task<bool> MakeDir(StorageFolder storageFolder, string folderName) { try { StorageFolder targetDir = await storageFolder.CreateFolderAsync(folderName); Debug.Log($"[{storageFolder.DisplayName}] targetDir.Path:[{targetDir.Path}]"); return true; } catch (System.Exception e) { Debug.Log($"[{storageFolder.DisplayName}] To make {storageFolder.Path + "/" + folderName}, raise exception {e}"); return false; } } private async Task<bool> MakeFile(StorageFolder storageFolder, string fileName) { try { StorageFile targetFile = await storageFolder.CreateFileAsync(fileName); Debug.Log($"[{storageFolder.DisplayName}] targetDir.Path:[{targetFile.Path}]"); await FileIO.WriteTextAsync(targetFile, "test"); StorageFile readFile = await storageFolder.GetFileAsync(fileName); Debug.Log($"[{storageFolder.DisplayName}] read file contetns:{await FileIO.ReadTextAsync(readFile)}"); return true; } catch (System.Exception e) { Debug.Log($"[{storageFolder.DisplayName}] To make {storageFolder.Path + "/" + fileName}, raise exception {e}"); return false; } } #endif }
EachStorageTestメソッドをボタンから呼ぶことで、作成を実施します。
次に、Project Setting
→Player
→Publishing Setting
→Capabilities
から各フォルダへのアクセス権を付与します。
今回付与した権限とその内容の対応は以下です。
- MusicLibrary : Musicフォルダ配下
- PicturesLibrary : Pictureフォルダ配下(カメラロールなど)
- RemovableStorage : リムーバブルストレージへのアクセス
- VideosLibrary : Videoフォルダ配下
- Objects3D : Objects3Dフォルダ配下
- RecordedCallsFolder : RecordedCallsフォルダ配下
なお、Documentsフォルダへの権限は、Unityから与えることはできません。
UnityでビルドしてVisualStudioソリューションファイルを作成した後、Package.appxmanifest
の </Capabilities>
の前に<DeviceCapability Name="documentsLibrary" />
を追加します。
<Capabilities> <Capability Name="internetClient" /> <uap:Capability Name="musicLibrary" /> <uap:Capability Name="picturesLibrary" /> <uap:Capability Name="removableStorage" /> <uap:Capability Name="videosLibrary" /> <uap:Capability Name="objects3D" /> <uap2:Capability Name="spatialPerception" /> <mobile:Capability Name="recordedCallsFolder" /> <DeviceCapability Name="microphone" /> <DeviceCapability Name="gazeinput" /> <DeviceCapability Name="documentsLibrary" /> <===== 追加 </Capabilities>
2. 結果
Windows Holographic for Business OSのバージョン21H1, OSビルド 20348.1014の時の結果は以下です。
今回作成したファイルはテキストファイルでしたが、Package.appxmanifest
のファイルの種類の関連付け
は特に設定しなくても問題なくファイルの作成、読み込みができました。
StorageFolder | 表示名取得 | パス取得 | ディレクトリ作成 | ファイル作成 (ファイル読み込み含む) | 補足 |
---|---|---|---|---|---|
KnownFolders.AppCaptures | キャプチャ | U:\USERS\[ユーザ名]\Videos\Captures | ○ | ○ | |
KnownFolders.CameraRoll | カメラ ロール | U:\USERS\[ユーザ名]\Pictures\Camera Roll | × | ○ | ディレクトリ作成のみ不可 |
KnownFolders.DocumentsLibrary | ドキュメント | 取得できず | ○ | ○ | フォルダ、ファイル作成時のStorageFolder/Fileからはパス取得可。 U:\USERS\[ユーザ名]\Documents\ |
KnownFolders.MediaServerDevices | Media Servers | 取得できず | × | × | Unspecified error。対象が未接続のためと思われる。 |
KnownFolders.MusicLibrary | ミュージック | 取得できず | ○ | ○ | フォルダ、ファイル作成時のStorageFolder/Fileからはパス取得可。 U:\USERS\[ユーザ名]\Music\ |
KnownFolders.Objects3D | 3D オブジェクト | U:\USERS\[ユーザ名]\3D Objects | ○ | ○ | |
KnownFolders.PicturesLibrary | ピクチャ | 取得できず | ○ | ○ | フォルダ、ファイル作成時のStorageFolder/Fileからはパス取得可。 U:\USERS\[ユーザ名]\Pictures\ |
KnownFolders.Playlists | プレイリスト | U:\USERS\[ユーザ名]\Music\Playlists | ○ | ○ | |
KnownFolders.RecordedCalls | 録音した通話 | U:\USERS\[ユーザ名]\Recorded Calls | ○ | ○ | |
KnownFolders.RemovableDevices | Removable Storage Devices | 取得できず | × | × | Unspecified error。対象が未接続のためと思われる。 |
KnownFolders.SavedPictures | 保存済みの写真 | U:\USERS\[ユーザ名]\Pictures\Saved Pictures | ○ | ○ | |
KnownFolders.VideosLibrary | ビデオ | 取得できず | ○ | ○ | フォルダ、ファイル作成時のStorageFolder/Fileからはパス取得可。 U:\USERS\[ユーザ名]\Videos\ |
ApplicationData.Current.LocalFolder | LocalState | U:\Users\[ユーザ名]\AppData\Local\Packages\[アプリフォルダ]\LocalState | ○ | ○ | |
ApplicationData.Current.LocalCacheFolder | LocalCache | U:\Users\[ユーザ名]\AppData\Local\Packages\[アプリフォルダ]\LocalCache | ○ | ○ | |
ApplicationData.Current.RoamingFolder | RoamingState | U:\Users\[ユーザ名]\AppData\Local\Packages\[アプリフォルダ]\RoamingState | ○ | ○ | |
ApplicationData.Current.TemporaryFolder | TempState | U:\Users\[ユーザ名]\AppData\Local\Packages\[アプリフォルダ]\TempState | ○ | ○ |
カメラロールのみ、ファイルの作成はできましたが、フォルダの作成はできませんでした。
ほかの多くのフォルダについては、ディレクトリ、ファイルの作成ができることが確認できました。
フォルダに対して権限を与えれば、アプリからファイル、フォルダのやり取りが比較的容易にできることがわかりました。
次回は、実際にUnity上で動画や画像、テキストの読み込みを比較していきます。
※ 今回初めて表を使ってみましたが、なんか見づらいですね。。。どうにかできないものか。。。
iOSアプリデプロイがうまくいかない場合の原因確認方法 ([端末名] is not available. Please reconnect the device.)
今回はMRTKとAR FoundationでiOSアプリを作成し動作確認する、をネタにする予定でしたがうまくいかなかったので原因確認の過程を残しておきます。
ビルドの準備から原因確認の過程のネタなので、Macの端末でUnityエディタでXcode用のプロジェクトを作成するまでの過程は割愛します。
ビルド実施まで
Unity上でビルドすると下記のようなファイル群がビルド時に指定したフォルダに作成されます。
フォルダ中の.xcodeproj
ファイルをダブルクリックすると、Xcodeが開かれます。
プロジェクトを開いた後、Xcodeビルドを行う前に設定する事項があります。
iOSアプリをビルドするには、アプリに署名が必要です。
XcodeのSigning & Capabilities
タブを選択するとステータスがエラーになっていると思います。
すでにApple IDをXcodeに登録済みの場合はTeamのプルダウンメニューから自分のアカウントを選択します。
登録していない場合は、Add an Account...
からアカウントを登録します。
なお、ベータ版のリリースや、App StoreにアプリをリリースするにはApple Developer Programに入る必要があります。
無事Teamの登録ができると、出ていた警告が消えます。
※消えない場合は自分のアカウントに紐づいている権限が、Build Settings
のSigning
に登録されているものと異なる可能性があります。
次にデプロイする端末を接続します。
LightningケーブルまたはUSB-CケーブルをMacに接続します。
端末を接続した後、Generic iOS Device
を選択すると、接続した端末を選択できるようになります。
この端末が、ビルド後のデプロイ先となる端末です。
デプロイ先の選択が終わったらビルドボタン(右向き三角)を押しビルドとデプロイを行います。
今回、ビルドはすんなり終わりましたが、端末が有効でなく再接続を求められるメッセージが表示されました。
ケーブルの再接続や、PCの再起動を試しましたが状況は変わりませんでした。
原因の確認
Xcodeでは、接続した端末の情報を見ることができます。
上部のメニューからWindows
→Device and Simulators
を選択します。
選択後出てきたウィンドウから接続しているデバイスの状態を確認できます。
今回はいくつかエラーが出ていました。
エラーの内容としては端末のiOS 14.7に対し、Xcodeのバージョンが古いためサポートしていないとのことでした。
インストール済みのXcodeのバージョンは11.4.1、対して最新バージョンは12.5.1と確かにメジャーバージョンが1古いのが原因のようです。
最新バージョンのXcodeはmacOS 11.0以降(Big Sur)が必要ですが、私の保有しているMac Book Air (11-inch Mid 2012)では対応していません。
残念ながら、新しいMac OSのPCを調達しないと、iOSの開発はできなさそうです。
一応、非対応端末にBig Surをインストールする方法もあるようですが、リソース的に無理にインストールするため、開発に使用できるかというと微妙です。
HoloLens2用アプリに再ビルド不要なデバッグモードを追加する(プロトコルでの実装)
今回は小技です。
前回、プロトコルから設定することでHoloLens2用アプリケーションでも起動時のモードを切り替えることができることがわかりました。
xr-physics-work-etc.hatenablog.com
今回はこのプロトコルで設定できるURIスキームを用いて、通常起動では実行されないデバッグモードを用意してみます。
URIスキームを用いたアプリケーションランチャーを作る
前回の記事で、新しいHoloLens2のOSではブラウザからURIスキーマを用いたアプリケーションの起動が行えませんでした。
そのため、まずはURIスキームを用いてアプリケーションを起動できる簡単なHoloLens2アプリケーションを作成します。
今回はシンプルにMRTK導入後、InputFiledとボタンだけで構成しました。
次にURIからアプリケーションを起動するためのスクリプトを作成します。
- LaunchApp.cs
クリックで展開
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.WSA; using TMPro; public class LaunchApp : MonoBehaviour { /// <summary> /// 実行するURIスキーム /// </summary> private string uriScheme = ""; /// <summary> /// 入力対象のInput field /// </summary> [SerializeField] private TMP_InputField inputFiled; /// <summary> /// URIスキームを設定する関数 /// </summary> public void SetScheme() { uriScheme = inputFiled.text; } /// <summary> /// URIスキームを元に実行する /// </summary> public void LaunchApplication() { try { Debug.Log($"Launch {uriScheme}"); Launcher.LaunchUri(uriScheme, true); } catch (System.Exception e) { Debug.Log(e); } } }
作成したスクリプトをコンポーネントとしてアタッチし、コンポーネントにInputFieldを参照させます。
また、InputFiledにURIスキームの設定(SetScheme)を、ボタンにURIスキームを元としたアプリケーションの実行(LaunchApplication)を設定します。
これで、InputFieldに入力したURIからアプリケーションを起動できるようになりました。
プロトコルでデバッグモードを実装する
次に、デバッグモード仕込むアプリケーションを作成します。
今回はアプリ名をURISchemeTest
にしています。
このアプリケーションがアプリケーションランチャーから起動されるものです。
MRTKを導入後、以下のようなHierarchyでシーンを設定します。
デバッグモードでない場合にも何か表示させるために、Cubeも配置しています。
デバッグモード用に、デバッグモードである旨を示すテキストとして、Debug Mode(TextMeshPro)、デバッグログ表示のためLogger(TextMeshPro)を用意しています。
今回は、Logger(TextMeshPro)でのログ出力にホロモンさんの下記を使用しました。
次に、URIを解釈し、必要があればデバッグモードに遷移するためのスクリプトを作成します。
今回は?
区切りでDebug
の文字列がURI中にある場合はデバッグモードとして扱うようにしました。
- DebugControlManager.cs
クリックで展開
using UnityEngine; using Microsoft.MixedReality.Toolkit; public class DebugControlManager : MonoBehaviour { /// <summary> /// 自インスタンス /// </summary> private static DebugControlManager instance; /// <summary> /// デバッグモードでのみ表示するオブジェクト /// </summary> [SerializeField] private GameObject[] targetObjects; /// <summary> /// デバッグモードかどうか /// </summary> public bool isDebugMode = false; /// <summary> /// デバッグモードにする場合のコマンドキーワード /// </summary> private static string debugKeyword = "Debug"; void Awake() { // シングルトン化 if (instance == null) { // 未作成の場合作成 instance = this; DontDestroyOnLoad(this.gameObject); } else { // すでに作成されている場合削除 Destroy(this.gameObject); } } // Start is called before the first frame update void Start() { Debug.Log("Start Check URI Schema"); Debug.Log($"URI:{Application.absoluteURL}"); // カスタム URI スキームを使用してアプリケーションが起動されている場合、 // Application.absoluteURLに値が入る if (!string.IsNullOrEmpty(Application.absoluteURL)) { GetURIInfo(Application.absoluteURL); } SetDebugState(); } // Update is called once per frame void Update() { } /// <summary> /// URI情報取得処理 /// </summary> /// <param name="uri"></param> void GetURIInfo(string uri) { string[] command; try { command = uri.Split('?'); } catch (System.Exception e) { Debug.Log(e); return; } DecodeURICommand(command); } /// <summary> /// URIコマンド解釈処理 /// </summary> /// <param name="args"></param> void DecodeURICommand(string[] args) { isDebugMode = false; foreach (var arg in args) { if (arg == debugKeyword) { Debug.Log("Exists debug command"); isDebugMode = true; } } } /// <summary> /// デバッグ状態の設定処理 /// </summary> void SetDebugState() { foreach (GameObject targetObject in targetObjects) { // 対象オブジェクトの有効/無効化 targetObject.SetActive(isDebugMode); } // MRTKの診断の有効/無効化 MixedRealityToolkit.DiagnosticsSystem.ShowDiagnostics = isDebugMode; } }
作成したDebugControlManager
を適当なオブジェクトにアタッチして、デバッグモードでのみ表示したいオブジェクトを設定します。
次に、URIスキームを使用するために必要な、プロトコルの設定を行います。
PlayerSetting
→Player
→Publishing Settings
のProtocolにURIスキーマとして設定するためのキーワードを設定します。
今回はuri-scheme-test
を設定しました。
これでURIスキームを使用するための設定ができました。
UnityアプリケーションをUWP用にビルドしてできたVisual StudioのソリューションファイルからPackage.appxmanifest
を確認すると、プロトコルの設定が正しくされていることを確認できます。
動作確認
動作確認として3パターン試してみます。
1. いつもアプリケーションを実行する場合と同じようにURISchemeTestを実行する
こちらはメニューからのいつも通りの起動のため、結果のみ記載すると、
[結果]
- 赤いCubeのみ表示される。
- MRTK標準の診断は表示されない。
2. アプリケーションランチャーから起動する
今回設定したURIスキームをアプリケーションランチャーに設定して起動します。
特に設定値はないので、適当に入力します。
uri-scheme-test://hoge?hoge
[結果]
3. URIにデバッグオプションを加えてアプリケーションランチャーから起動する
次にURIにデバッグオプションを追加して実行します。
uri-scheme-test://hoge?hoge?Debug
[結果]
- URIスキームに対応するアプリケーション(今回の場合は
URISchemeTest
)が実行される - デバッグモードでのみ表示設定した
Debug Mode
の文字列とデバッグログが表示される - (スクリーンショットに映っていないが)MRTKの診断が表示される
これで、プロトコル(URIスキーム)を用いたデバッグモードを仕込むことができました。
再ビルドしなくても、通常起動と、デバッグモードをHoloLens2のみで切り替えることができます。
他にも、アプリケーションへの引数の渡し方として応用できるような気がします。
Holographic Remotingでクライアント(HoloLens2)からサーバ(PC)に向けて接続する
今回はHolographic Remotingの小技をメモします。
Holographic Remotingを使用する場面として、UnityのHolographic Emulationや以前記事にしたUnityアプリケーションでのHolographic Remotingリモートアプリを作成する場合があります。
xr-physics-work-etc.hatenablog.com
これらの場合、どちらもPC側からHoloLens2に接続を行います。
HoloLens2側のアプリである、Holographic Remoting Playerも、普通にアプリを起動すると、接続を待ち受けるモード(Listen)で起動されます。
しかし、下記説明を見るとHoloLens2からPCに接続しに行くこと(Connect)もできるようです。
今回はHoloLens2からPCに接続しに行く方法についてメモします。
動作確認準備
1. サンプルを取得する
今回は、リモート側アプリとして、Holographic Remotingの公式サンプルを使用します。
gitを用いて、リポジトリをcloneするか、Download ZIPを選択しZIPファイルをダウンロードします。
HoloLens2側で使用するHolographic Remoting Playerは、公式サンプルのものとMicrosoft Storeにあるものとはほぼ同じため、Microsoft Storeものを使用します。
2. サンプルアプリをビルドする
今回、リモート側アプリはdesktopを使用します。
MixedReality-HolographicRemoting-Samples/remote/desktop/
中のSampleRemote.sln
をダブルクリックします。
ビルドの設定をRelease x64
に変更します。
上部メニューからビルド
→デバッグなしで開始
を選択しビルドを行います。
なお、私の環境では下記エラーが発生しました。
ソリューションのプロパティからSpectre軽減策
を無効化することで解消しました。
ビルドに成功すると、\MixedReality-HolographicRemoting-Samples\remote\desktop\bin\Release
にSampleRemote.exe
が生成されています。
これが、Holographic Remotingのリモート側アプリです。
3. HoloLens2→PCの接続方法を確認する
リモートアプリについて、ソリューションからSampleRemoteApp.c
の中身を確認すると、コマンドライン引数から様々な設定ができることがわかります。
L216から、-listen
をコマンドライン引数につけるとリモートアプリがHoloLens2からの受信待ちの状態で、アプリが起動することがわかります。
同様にHolographic Remoting Playerアプリ側も確認します。
HolographicRemoting\MixedReality-HolographicRemoting-Samples\player\sample
のSamplePlayerMain.cpp
を確認すると、特にコマンドライン引数などで設定されていない場合は、m_playerOptions
に格納されているデフォルトの設定が適用され、Listenモードで起動します。
SamplePlayerMain.cpp該当部分 github.com
m_playerOptions
デフォルト値 github.com
HoloLens2の場合は、コマンドライン引数で指定することは(私の知る限りは)できません。
SamplePlayerMain.cpp
のL650を確認すると、プロトコルを使用して設定できるようです。
HolographicRemoting\MixedReality-HolographicRemoting-Samples\player\sample
のPackage.appxmanifest
から宣言の項目を確認すると、プロトコルの設定がされています。
そのため、URIスキームを使用してHolographic Remoting Playerを起動することで、引数を与えた状態で起動をすることができます。
動作確認
まず、リモートアプリを引数を与えて実行します。
Windows Powershellで\MixedReality-HolographicRemoting-Samples\remote\desktop\bin\Release
に移動し、下記のコマンドでリモートアプリを実行します。
.\SampleRemote.exe -listen
特にポート番号を指定していないので、待ち受けているポートは8265
です。
実行するとウィンドウが表示されます。
タイトルバーを見るとPress Space To Connect
と表示されており、スペースキーを押すと接続が開始されることがわかります。
スペースキーを押下すると、接続対象のIPアドレスが表示されますが、0.0.0.0
で表示されているためListen状態で接続待ちしていることがわかります。
そのあと、PC側のIPアドレスをipconfigなどで確認しておきます。
次にHoloLens2側でブラウザを開き、URLを入力する欄に下記URIを設定します。
ms-holographic-remoting://[PCのIPアドレス]:8265
※どうやら2021年8月1日現在、最新のWindowsHolographic バージョン 21H1で使用できるCromium版のEdgeブラウザでは、ブラウザからURIスキームを使用してのアプリ起動ができないようです(私のやり方が悪いだけ?)。この場合は、別途HoloLens2用アプリに組み込んで実行する必要があります。
IPアドレスが正しく設定されていれば、Holographic Remoting Playerが起動され、PC側のリモート側アプリに接続されます。
通常の方法でHolographic Remoting Playerを起動すると①のような表示になりますが、今回の方法で起動すると②のようになり、HoloLens2側から接続しに行っていることがわかります。
① Waiting for connection on
② Connecting to
これでHoloLens2からPCに接続する方法でHolographicRemoting接続することができました。
なお、Unityから触れるHolographic RemotingのAPIではリモートアプリから接続しに行くAPIしかないようです。
UnityからもListenの設定ができればよりHolographic Remotingの用途が広がるような気がします。
何か方法はないものか。。。