凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

Chrome の音声認識を Unity で利用してみた

はじめに

前回のエントリ(http://d.hatena.ne.jp/hecomi/20131201/1385907047)にて、Chrome に実装されている Web Speech API を利用した音声認識Unity 上で利用することについて触れました。本エントリではその内容を紹介します。

概要

Chrome で Web Speech API を動作させ、その結果を WebSocket で貰ってくるという形になります*1Google さんに対して、お行儀の良い使い方とは言えない気もするので、実験用に留めておくか、必要なときだけトリガを掛けて ON にする形にした方が良いと思います。公式には Chromium-dev 登録していれば Web Speech API をパーソナルユース限定で利用できるようです(未実験)。

先行事例

Oculus Game Jam Japan #1 で @haiattoC さんがゲームとして実装されていました。

環境

仲介サーバとして Node.js を利用しています(利用しなくても出来ますが、それは後述します)。

Mac ユーザの方は nodebrew 使ってインストールするとバージョン管理出来て便利だと思います。

Windows の方は Node.js 本家サイトからインストーラを引っ張ってきてポチポチ次へをクリックしてインストールすれば使えるようになります。また、今回利用する ws という WebSocket による通信を行うモジュールはコンパイルが必要なので、Mac なら xcode の command line tools、Windows なら Visual Stduio Express 2012 などを入れておいて下さい。

コード

まず、ws を作業ディレクトリ下でインストールします。

$ cd path_to_your_dir
$ npm install ws

このディレクトリ下で、以下のような Chrome と Unity をつなぐサーバを書きます。

server.js
var https = require('https');
var fs    = require('fs');
var ws    = require('ws').Server;

var CHROME_PORT = 12001;
var UNITY_PORT  = 12002;

// WebSpeech API を Chrome で走らせるための HTTPS サーバ
var server = https.createServer({
	key: fs.readFileSync('key.pem'),
	cert: fs.readFileSync('cert.pem')
}, function(req, res) {
	fs.readFile('index.html', function(err, data) {
		if (err) {
			res.writeHead(500);
			res.end('Internal Server Error');
		} else {
			res.writeHead(200);
			res.end(data.toString().replace('{CHROME_PORT}', CHROME_PORT));
		}
	});
}).listen(CHROME_PORT);

// Unity と WebSocket によるコネクションをはる
var unityWebSockets = [];
var unityServer = new ws({port: UNITY_PORT});
unityServer.on('connection', function(ws) {
	console.log('Unity connected!');
	unityWebSockets.push(ws);
	ws.on('close', function() {
		console.log('Unity disconnected...');
		unityWebSockets.splice(unityWebSockets.indexOf(ws), 1);
	});
});

// Web Speech API の結果を取ってくる
var chromeVoiceRecogServer = new ws({server: server});
chromeVoiceRecogServer.on('connection', function(ws) {
	console.log('Chrome connected!');
	ws.on('message', function(word) {
		console.log('recognized:', word);
		unityWebSockets.forEach(function(unityWebSocket) {
			unityWebSocket.send(word);
		});
	}).on('close', function() {
		console.log('Chrome disconnected...');
	});
});

HTTPS にしているのは、HTTP 下では、Web Speech API を利用するには毎回マイクの利用してもよいか確認されるからです。HTTPS なら初回のみの問い合わせになります。そのために、オレオレ SSL 証明書を作成することが必要になるので、以下のエントリを参考に作成して下さい。

次に、Chrome で開くための HTML を書きます。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Web Speech API WebSocket Sender</title>
  <script>
    (function() {
      // WebSocket でサーバと接続
      var ws = new WebSocket('wss://localhost:{CHROME_PORT}');

      // Web Speech API で音声認識
      var recognition = new webkitSpeechRecognition();

      // 連続音声認識
      recognition.continuous = true;

      // エラー表示
      recognition.onerror = function(err) {
          console.error(err);
      }

      // 無音停止時に自動で再開
      recognition.onaudioend = function() {
          recognition.stop();
          setTimeout(function() {
            recognition.start();
          }, 1000);
      }

      // 音声認識結果をサーバへ送信
      recognition.onresult = function(event) {
        for (var i = event.resultIndex; i < event.results.length; ++i) {
          var result = event.results[event.resultIndex][0].transcript;
          document.getElementById('result').innerHTML = result;
          ws.send(result);
        }
      }

      // 音声認識開始 
      recognition.start();
    })();
  </script>
</head>
<body>
  <div id="result"></div>
</body>
</html>

これで、サーバを以下のように立ち上げ、https://localhost:12001/ にアクセスすれば音声認識結果をサーバから扱えるようになります。

$ node server

Unity 側では websocket-sharp を利用します。

ソリューションファイルを開いて Release ビルドすれば websocket-sharp.dll が出来るので、これを Unity の Assets 以下のどこかに入れておきます。そして以下のスクリプトを書いて適当な GameObject にアタッチします。

WSVoiceRecognition.cs
using UnityEngine;
using WebSocketSharp;
using System.Collections;

public class WSVoiceRecognition : MonoBehaviour {
	private WebSocket ws_;

	void Awake()
	{
		ws_ =  new WebSocket("ws://127.0.0.1:12002");
		ws_.OnMessage += (sender, e) => {
			Debug.Log(e.Data); // 認識結果
		};
		ws_.Connect();
	}

	void OnApplicationQuit()
	{
		ws_.Close();
	}
}

そして、まずサーバ、続いてChrome、Unity を立ち上げれば Unity のコンソール上に認識結果が表示されるようになります。

Node.js 依存を省く

なお、上記例ではサーバに Node.js を利用しましたが、websocket-sharp が HTTPS によるサーバ機能を提供してくれているので、Node.js を省いて単純に Unity - Chrome 間通信にすることも可能です。私は、別の API と連携したいこともあり、その辺りが書きやすいので Node.js を中間層に挟んでいました。サーバの立ち上げ方法は詳細に websocket-sharp の README に書かれているので、そちらをご覧ください。

おわりに

単純に HTML だけだと実現が難しい物も、こういった連携で 3D と組み合わせたりすると色々とプロトの幅が広がって面白いですね。

*1:Unity 以外にも他のアプリケーションでも応用できるので、色々試してみると面白いと思います。