好きな言葉で呼びかけられるGoogle HomeをDIYする

[Sansan Advent Calendar 2017](https://adventar.org/calendars/2530#list-2017-12-07)の8日目の記事となります。

最近[半額なことで話題](https://akiba-pc.watch.impress.co.jp/docs/wakiba/find/1094753.html)のGoogle Homeを自作します。
再入荷の知らせを見てよっぽど買おうかと思いましたが、中身はほぼほぼ[Google Assistant SDK](https://developers.google.com/assistant/sdk/)なのでエンジニアらしくDIYすることにします。

# 今回やりたいこと
普通にラズパイでGoogle Assistant SDKでGoogle Homeっぽいものをつくるものよいですがせっかくなので、ウェイクワード(Google Assistantが声認識をスタートするキーとなる言葉)を好きなものに変更できるような仕組みを、Snowboyを使って一手間入れてみます。

# 仕組み
1. [Snowboy](https://snowboy.kitt.ai/)で事前に設定したウェイクワードを待ち受け
2. ウェイクワードを認識したらGoogle Assistant SDKを呼び出して声認識を開始し、都度フィードバック
3. 1に戻る


# 用意するもの
- Raspberry Pi 3
- USB接続のwebカメラ(マイクとして使用)
- イヤホン

GPIOは一切使ってないので、ラズパイである必要はあまりないですが、今後いろいろ改造することを考慮しての選択です。

というのも実は当初、Arduinoでアナログ(orデジタル)マイクから声拾ってコンバートしてごにょごにょして、本家よりも小さいGoogle Home作ろうと思ってました。
それを見越して、ESP8266やらESP32を買っていましたが、マイクの調達にアキバに行く暇がなかったので、今回お手軽にあるもので作るかとなった感じです。

スピーカーくらいは用意したかったのですが、手元にある3.5mm端子で繋がる出力機器はイヤホンくらいしかなかったのでしょうがないです。(一昔前ならデスクトップPCに3.5mm端子でアクティブスピーカーがたいてい刺さっていたりしていたので容易に流用できたのですが...)
webカメラをマイク代わりにするのは定番ですね。

# 

# ラズパイの設定とハードの準備
我が家ではネット回線の速度を1分毎に測定してBigqueryに記録する謎ロギングシステムがラズパイで動いていますので、そのマシンを流用します。
一から構築する場合は以下のような記事を参考にすると良さそうです。
https://qiita.com/Halhira/items/1da2ae543217be26988a
ハードの準備は指すだけなので簡単です。

![図1.jpg](https://qiita-image-store.s3.amazonaws.com/0/134618/41fdf088-1695-681e-1d4c-1b1c4284106d.jpeg)

ひどいやっつけ感がありますが、気にせず進みます。

# Snowboy他のインストール

## Snowboyについて
https://snowboy.kitt.ai/
DNNベースの音声中のワード検知ツールです。
商用利用以外の`Hackers`はフリーで使えます。

## インストール手順
基本的には以下の記事を参考にさせていただきました。
https://qiita.com/mayfair/items/d16d092328e60f0cac6b

実行したコマンドと簡単なコメントだけ記載していきます。
## 周辺ライブラリのインストール
まずは音声周りの環境を整えていきます。
```
sudo apt-get install swig3.0 python-pyaudio python3-pyaudio sox
pip install pyaudio
sudo apt-get install libatlas-base-dev
```

## 音声入力と出力の初期設定
### 出力デバイスの確認
```
aplay -l
```
接続中のデバイスがどの番号で認識されているか確認します。
### 入力デバイスの確認
```
arecord -l
```
同じく確認。
### 確認したポートを設定
```
vi ~/.asoundrc
```
上で確認したデバイス番号を指定します。
### 録音確認と再生確認
```
rec test.wav
play test.wav
```
### ゲイン調整
```
alsamixer
```
録音 → 再生して音量が小さい場合はゲインをあげてください。
音声での音量確認はイヤホン等のインピーダンスに関係しますが、たぶん参考記事通り80くらいに設定しておくと良さそうです。

## Snowboyのインストール
```
wget https://s3-us-west-2.amazonaws.com/snowboy/snowboy-releases/rpi-arm-raspbian-8.0-1.1.0.tar.bz2
tar -xvf rpi-arm-raspbian-8.0-1.1.0.tar.bz2 
cd rpi-arm-raspbian-8.0-1.1.0
```

こっちから持ってきても良いかもです
```
git clone https://github.com/Kitt-AI/snowboy
```

## Snowboyの実行確認
```
python demo.py resources/snowboy.umdl
```
「snowboy」と呼びかけて、ピーンと音がなって標準出力に認識した旨が表示されていればOKです。

## Snowboyのオリジナルウェイクワードを作成する
Google Assistantをウェイクするワードで作成してください。

# Gogole Assistant SDKのインストール
ここまでで声に反応して任意の処理を実行できる準備が整ったので、次にGogole Assistant SDKをインストールします。
本当はGoggle Assistant APIのほうも日本語に対応してもらえると大変うれしいのですが、現状は英語のみ(`en_US`)っぽいです。
https://developers.google.com/assistant/sdk/reference/rpc/google.assistant.embedded.v1alpha1#google.assistant.embedded.v1alpha1.AudioInConfig.Encoding
> The only language supported is "en-US".


インストールは以下の記事を参考にさせていただきました。
https://qiita.com/PonDad/items/52eaa93338496b06cb1a

大まかにやることは
1. GCPでGogole Assistant APIを有効にする
2. Google Assistant SDKをインストールする
です。

1に関しては省略します。
2も参考記事の通りで問題無いと思いますが、僕の環境では`pip`がPython 2系だったので`pip3`をインストールし読み替えてコマンド実行しました。

これで以下を実行することで「OK, Google」でいろいろ命令できるようになります。
```
python3 -m googlesamples.assistant
```
前述のとおり英語しか対応していないので、発音に自信のない僕はiPhoneのGoogle翻訳アプリに代わりにしゃべってもらってテストしました。

# SnowboyとGoogle Assistantを組み合わせる
Snowboyはデモコード(`demo.py`)を流用しました。
以下はコード後半を抜き出したものです。

```
...
detector.start(detected_callback=snowboydecoder.play_audio_file,
               interrupt_check=interrupt_callback,
               sleep_time=0.03)
...
```
動作の詳細は割愛しますが、ウェイクワードを認識すると`detector.start`の引数`detected_callback`に指定している処理を実行するので、そこにGoogle Assistant関連コードを埋め込む方針です。

次にGoogle Assistant SDK側の処理です。
こちらもデモコードを流用します。

```
#!/usr/bin/env python
from __future__ import print_function

import argparse
import os.path
import json
import google.oauth2.credentials

from google.assistant.library import Assistant
from google.assistant.library.event import EventType
from google.assistant.library.file_helpers import existing_file

def process_event(event, astnt):
    if event.type == ｃ:
        astnt.start_conversation()
    elif event.type == EventType.ON_CONVERSATION_TURN_STARTED:
        print('---READY TO TALKING')
    print(event)
    if (event.type == EventType.ON_CONVERSATION_TURN_FINISHED and
            event.args and not event.args['with_follow_on_turn']):
        print()
        a.start_conversation()

def main():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.RawTextHelpFormatter)
    parser.add_argument('--credentials', type=existing_file,
                        metavar='OAUTH2_CREDENTIALS_FILE',
                        default=os.path.join(
                            os.path.expanduser('~/.config'),
                            'google-oauthlib-tool',
                            'credentials.json'
                        ),
                        help='Path to store and read OAuth2 credentials')
    args = parser.parse_args()
    with open(args.credentials, 'r') as f:
        credentials = google.oauth2.credentials.Credentials(token=None,
                                                            **json.load(f))

    with Assistant(credentials) as assistant:
        for event in assistant.start():
            process_event(event, assistant)
```

ウェイクワード認識はSnowboy側で行うので、`start_conversation`を実行してすぐに音声認識を開始するように変更しています。
この `start_conversation`が微妙に曲者で、`assistant.start()`が実行中のみ有効なうえ、` event`に入ってくる`type`が`EventType.ON_START_FINISHED`のときにちょうど実行しないと勝手にウェイクワードガン待ち状態になってすこし手間取りました。

これらのコードをよしなにSnowboyと合わせればOKです。

## 動作確認
作成したウェイクワードで試してみます。
動画も撮ってみようかと思いましたがチープさがよりにじみ出そうなのと深夜だったのでやめました。

## まとめと今後
これで自分だけのオリジナルウェイクワードで起動するGoogle Homeもどきが完成しました。
この方法ならAlexaや他AIアシスタント系APIを複数同居できるので、年末年始に暇みて作ってみようかと思います。
今回はハード側は全くこだわらなかったですがそもそもラズパイ3ベースだと結構場所とるので、ラズパイゼロWかESP32とかで作るとかも面白そうです。
また、スピーカーがないと会話感もなく面白さも半減するので、ぜひご用意の上試すことをおすすめします。