スマートグラスの活用方法は開発者自ら開発しよう
昨年、個人でARCoreを色々と触った中で、「次はスマートグラスかな」と。
とはいえ、スマートグラスはまだまだBtoB向けの「遠隔作業支援ソリューション」として目立つくらい。
一般消費者向けにも「Vuzix Blade」のような製品も出てきていますが、どうしてもコンテンツ消費型の
使い方が先行している気がしています。
そこで、「スマートグラスの活用方法は開発者自ら開発しよう」と思い、
- 装着のハードルがなるべく低く
- スマートグラスが持つ機能を利用して開発できる・コードが書ける
製品で、色々試してみることにしました。
まずは技術の写経から
スマートグラスの機能を試しつつ自分が開発できることを知るため、
まずは世の中の模倣をして「技術の写経」を。
「遠隔作業支援ソリューション」ではお馴染みの「現場とセンターをカメラでつなぐ」
アプリを開発からトライしてみることにしました。
ハードの選定
スマートグラスに求めること
- カメラを搭載している
- センサを搭載している
- SDKを公開している
- 電源供給手段を持っている
- バッテリ搭載
- もしくは接続デバイスから供給可能
上記を満たす製品として「EPSON MOVERIO BT-35E/30E」を採用。
合わせて「DisplayPort Alternate Mode」に対応したUSB Type-CのAndroidスマートフォンも
必要なことがわかったので、「HTC U11」を別途中古で購入。
機能実現のための技術(ソフト)の選定
「現場とセンターをカメラでつなぐ」ために求められること
- 相手の顔や声をスマートグラスから確認できること
- 自分の目の前の景色を相手に表示できること
上記を実現する手段として「WebRTC」を採用。
今回は自前でシグナリングサーバーやSTUNサーバーは準備せず、
NTT CommunicationsさんのWebRTCプラットフォーム「SkyWay」を利用することにしました。
いざ開発
MoverioとWebRTCの組み合わせであっという間にできるな、と軽く考えていたら、
実際はハードルが多かったです・・。
まず前提として、UVCに対応したUSBカメラ(ウェブカメラ)であればWebRTC側で
GetUserMeidaするだけで簡単にMOVERIOのカメラを拾えるだろうと思っていました。
技術的なハードル
- AndroidにMOVERIOを接続してもUSBカメラとして認識しない。
- Androidは9.0以降USBカメラ対応しているはずだが、認識しない。
- スマホ自体がハードウェアとしてサポートしてなければダメなよう・・。
- サポートしているかどうかは
PackageManager.hasSystemFeature(FEATURE_CAMERA_EXTERNAL)
で確認できる。 - 今回購入した「HTC U11」は対応していなかった・・。
- サポートしているかどうかは
- 「SkyWay」のAndroid SDKは、Android(Java)内で
io.skyway.Peer.Browser.Navigator.getUserMedia
をしてカメラを取得しているが、MOVERIOはカメラとして認識されないので取得されない
打開策
AndroidのWebViewを利用してWebアプリケーション(HTML5とJavascript)で実装することにしました。
以降はMOVERIO SDKとSkyWay Javascript SDK を導入済の前提での説明となります。
WebRTC用Webページの実装
SkyWay Javascript SDKのページを参考に画面を実装します。
ただし、2-3.カメラ映像、マイク音声の取得にある「カメラ映像を表示する領域」はVideo
ではなく、canvas
に変更します。(canvasにMOVERIOの映像データを表示させることになります)
今回は以下のようなシンプルな構成です。
センター側の映像を表示するvideo
とMOVERIO側の映像データを表示するcanvas
をbody
内に用意するだけです。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>現場側画面</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script src="https://cdn.webrtc.ecl.ntt.com/skyway-latest.js"></script>
</head>
<body>
<video id="center-video" width="160px" height="160px" autoplay></video>
<canvas id="canvas" width="1920px" height="1080px"></canvas>
</body>
</html>
WebRTC用のJavascriptの準備
上記で作成したWebページの末尾にJavascriptを追加していくことになります。
MOVERIO側の映像データ表示
まずは、MOVERIOから映像データを取得してbody
内のcanvas
に表示するためのスクリプトを追加します。
(厳密には映像データではなく、画像に変換されたデータを表示することになります。詳細は後述。)
以下のようにimg
要素(moverioImage
)に対してaddEventListener
を用意することで、
loadImage()
によって画像が読み込まれる度にcanvas
へと描画することができるようになります。
loadImage()
はダミーのURL(以下ではhttps://xxxxxxxx/moverio.jpg
)にアクセスすることで
Android(Java)側の処理をフックできるようになります。詳細は後述。
<script>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext('2d');
var canvasStream = canvas.captureStream();
var moverioImage = new Image();
moverioImage.addEventListener("load", function () {
if (canvas && ctx) {
ctx.drawImage(moverioImage, 0, 0, canvas.width, canvas.height);
}
}, false);
// 画像からcanvasに描画する
function loadImage() {
moverioImage.src = "https://xxxxxxxx/moverio.jpg";
}
</script>
SkyWayのサービス利用
SkyWayのサービスを利用するためのJavascript実装は「WebRTC用のWebページの準備」と同様、
SkyWay Javascript SDKのページを参考に実装します。
ただし、2-6.発信処理内のpeer.call(theirID, localStream);
のlocalStream
と、
2-7.着信処理内のmediaConnection.answer(localStream);
のlocalStream
は
上記スクリプト内で定義しているcanvasStream
を渡してあげることになります。
以下は着信処理の場合の例となります。
<script>
//着信処理
peer.on('call', mediaConnection => {
mediaConnection.answer(canvasStream);
setEventListener(mediaConnection);
});
</script>
Androidアプリケーションの実装
WebRTC用Webページを呼び出しはWebViewで実装します。
onCreate
の中でWebViewインスタンスに各種設定を行います。
private WebView myWebView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample);
// WebViewClientを設定
myWebView.setWebViewClient(webViewClient);
// WebSettingsを設定
WebSettings settings = myWebView.getSettings();
settings.setJavaScriptEnabled(true); // Javascript有効化
});
}
WebViewClient
は以下のようにshouldInterceptRequest
をオーバーライドしておきます。
shouldInterceptRequest
を利用することで、Webページ内でレスポンスを差し替えられました。
以下のサイトが非常に参考になりました。
private WebViewClient webViewClient = new WebViewClient(){
@Override
public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request){
// ダミーで指定されたRequestに対してカメラ映像データのInputStreamをResponseとして返却する
if(request.getUrl().toString().equals("https://xxxxxxxx/moverio.jpg")){
WebResourceResponse webResourceResponse = new WebResourceResponse("image/jpeg", null, inputStream);
return webResourceResponse;
}
return super.shouldInterceptRequest(view, request);
}
};
カメラ映像データの取得
EPSONさんで公開している「Moverio SDK & サンプルプロジェクト」を利用して実装しました。
MOVERIO本体のカメラへアクセスし、カメラ映像データを取得します。
カメラ映像データのWebアプリへの引き渡し
取得したカメラ映像データをWebアプリに渡す際は、byte配列型で渡されるカメラ映像データをBitmapに変換します。
(Bitmapの高さと幅、Bitmap.Config
はカメラスペックから確認しました。)
その後、BitmapをByteArrayOutputStream ⇒ ByteArrayInputStreamへと変換します。
(ここはもしかしたらもう少し処理をシンプルにできるのかもしれません・・)
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Bitmap bitmap = Bitmap.createBitmap(1920, 1080, Bitmap.Config.RGB_565);
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(data));
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos );
byte[] bitmapData = bos.toByteArray();
inputStream = new ByteArrayInputStream(bitmapData);
変換した直後に画像ロード用のJavascriptを実行することで、shouldInterceptRequest
をフックできます。
なお、UIスレッド以外からJavascript実行すると以下のように怒られます。
java.lang.Throwable: A WebView method was called on thread 'XXXXXXX'. All WebView methods must be called on the same thread.
この場合別途Handlerを用意し、postしてあげることで実行できます。
private Handler handler = new Handler();
今回の場合はBitmapをByteArrayInputStreamに変換した直後に以下の処理を実行します。
直前のキャッシュクリアも重要です。これがないと画像が表示更新されませんでした。
// Handlerを使用してUIスレッドのWebViewを更新する
handler.post(new Runnable() {
@Override
public void run() {
myWebView.clearCache(true); // キャッシュクリア
myWebView.loadUrl("javascript:loadImage();");
}
});
WebRTC用のページへの初回アクセスはMOVERIOからカメラ映像データの取得を開始する前であれば、
任意のタイミングで問題ないと思います。
myWebView.loadUrl("https://xxxxxxxx/{WebRTC用のページ}");
動作確認
Androidアプリを起動し、スマホとMOVERIOの両者にカメラ映像が表示されればほぼ完了です。
WebRTCの接続先を用意します。
SkyWay Javascript SDKのページのサンプルを利用させてもらうのが楽です。
Webページのデザインは、相手の映像を全画面で表示したり、カメラ映像を表示しているcanvas自体を
非表示にしたり、用途に応じてデザインすればよいかと思います。
私は「現場とセンターをカメラでつなぐ」をイメージして、相手の映像が画面の端に表示されるようにしてみました。
また、自分の目の前を映しているカメラ映像をわざわざ画面に表示することもないと思い、canvasを非表示にしてみました。
まとめ
アプリ開発について
USBカメラ対応していないことで思わぬ実装が入りましたが、WebRTCやWebViewを知る良い勉強になりました。
USBカメラ対応している機種なら、ほとんどコーディングすることなくWebRTCの仕組みが
利用できてしまうのではないかと思いました。
MOVERIO SDK自体は癖のない、わかりやすく苦労することのない実装が可能でした。
カメラ機能だけでなく、センサ機能も試してみたいところです。
また、今回は画像だけでしたが、音声データもMediaStreamとして、映像データと合成して
WebRTCに指定できるようようなので、そちらも実装してみたいところです。
装着のハードル
ランニングなどの時に腕につけるアームバンドを利用することでコンパクトにまとめられ、
「装着のハードル」は想像よりは低くなりました。
実際に身に付けた時のイメージですw ファッション次第では外でも違和感なくいけるかもしれませんwwww
課題
スマホからMOVERIOに電源供給を行いながら画像処理も行うため、スマホ側の発熱とバッテリ消費が
激しいのが気になりました。