SPEECH APIを使ってみたお話

[新卒専用 Advent Calender 2017](https://qiita.com/advent-calendar/2017/fresh) 22日目です！

# はじめに
私は今年の4月に情報系の大学を卒業し、現在WEB系エンジニアとしてWEBサイトの開発や改修などをさせていただいております。
新卒研修の総合演習でSPEECH APIを用いました。
SPEECH APIの基本的な使い方と、苦戦したポイントを共有させて頂きます。
※今回Javaで実装したので、Javaでの説明になります。
手探りな部分が大きく、正直正解かわからないので、指摘点等あればコメントいただければ幸いです。


# SPEECH API
[SPEECH API](https://cloud.google.com/speech/?hl=ja)はGoogleCloudPlatformで使うことができる、音声認識サービスです。
音声データをGoogleのサーバーに送信するとJSON形式でテキストが返ってくる便利なAPIです。

# 使用方法
SPEECH APIというよりはGoogleのAPIを使うには主に2つ手法があります。

* ClientLibraryを用いたやり方
* HTTP通信でリクエストを送信するやり方

## ClientLibraryを用いたやり方
GoogleのAPIではLibraryがmaven管理で提供されています。[公式リファレンス](https://cloud.google.com/speech/docs/reference/libraries?hl=ja)
これはAPIのライブラリをビルドパスに登録して、便利なオブジェクトを使って音声認識ができちゃう優れものです！
しかも、ClaientLibraryで実装したものに関しては認証系の設定もAPP ENGINEに乗せていれば自動でやってくれちゃいます！！！

しかし！！…

こちらまだSPEECH APIに関しては、alpha版のため使える機能が中途半端だったりします…
今回私がやりたかった機能が出来ないことが判明したので違う方法を採用しました。
(こちらのやり方も一通り試したので、"気が向いたら"記事にします)

## HTTP通信でリクエストを送信するやり方
こちらは一般的なAPIでリクエストを送るやり方と同じになります。
SpeechAPIのURLにHTTP通信をして設定とかが書いてあるJSONファイルを渡してあげれば音声認識ができます。

今回はこちらのやり方を採用しました！！！


# 実際に音声認識をしてみる
 それでは、実際にSPEECH APIを使用する手順を説明していきます。
## APIKeyをURLのパラメーターに渡して実装してみる
[ここ](https://cloud.google.com/speech/docs/auth?hl=ja)の一番上の説明を見ると、URLの後ろにAPIKeyをパラメータで貼り付ければ実行できそうな気がします。

しかし…

これでやってみても、リクエストに失敗します…
※おそらく他のAPIと共通の説明を使っているから使えないけど書いてあるんでしょうね…

理由を探ってみると…
[ここ](https://cloud.google.com/speech/docs/common/auth?hl=ja)に答えがありました。
SPEECH APIを使うには、<strong>認証だけでなく承認も必要</strong>でした。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/206268/3416083c-6064-77df-9fcf-75c6796653d8.png)
APIには認証と承認の2つがあり、やろうとしていたAPIKeyだと認証はできるけど承認は出来ないんですね…

なので、SPEECH APIを使うには[ここ](https://cloud.google.com/speech/docs/auth?hl=ja)の2番目にもある<strong>サービスアカウント</strong>を用いて実装を行いました。

## サービスアカウントを使って実装してみる
SPEECH API を使うにはサービスアカウントを使う必要があるのが分かったので実装していきます。
サービスアカウントを使ってAPIを使用する流れは以下の流れになります。

* [GoogleCloudConsole](https://console.cloud.google.com)にてサービスアカウントをJSON形式で発行。
![スクリーンショット 2017-12-20 21.36.07.png](https://qiita-image-store.s3.amazonaws.com/0/206268/dbe43d67-dd95-31e5-feb0-02fc27c3dd2e.png)

* 発行したJSONをクライアントアプリに読み込ませてアクセストークンを発行する
[credntialクラス](https://developers.google.com/api-client-library/java/google-api-java-client/oauth2)がアクセストークンを管理するためのクラスなのでこちらを使います。
`credntial.fromstream` で先程発行したJSONを読み込ませると管理することができます。
スコープは`https://www.googleapis.com/auth/cloud-platform`を指定してください。

```java:アクセストークンの取得
URL resource = Resources.getResource("RecordingApplication-82436688a44b.json");
		
GoogleCredential credential = GoogleCredential
					.fromStream(resource.openStream())
					.createScoped(Arrays.asList("https://www.googleapis.com/auth/cloud-platform"));
					
credential.refreshToken();
return credential.getAccessToken();
```
ここで`refreshToken`をしていますが、アクセストークンの有効期限は発行してから1時間で発行したときにリフレッシュトークンと言うものを発行します。
本来であれば、アクセストークンとリフレッシュトークンを保持しておき、アクセストークンの有効期限が切れていなければそのまま使用し、切れていればリフレッシュトークンを取得して新しいアクセストークンを取得と言うかたちが適切になります。
詳しくは[ココ](https://developers.google.com/identity/protocols/OAuth2)のScenariosのところに書いてあります。

* 発行したアクセストークンをHTTP通信のヘッダーに貼り付ける
アクセストークンの発行が完了したらHTTPリクエストに貼り付けます！

```java:リクエスト
public String getSpeechAPIName(String fileName, String roomNum) {
		TokenManager tm = new TokenManager();
		String accessToken = "Bearer " + tm.getAccessToken();

		final String TARGET_URL = "https://speech.googleapis.com/v1/speech:longrunningrecognize";

		String json = "{\"audio\": {\"uri\":[cloudStorageのパス]
				+ fileName
				+ ".awb\"},\"config\": {\"encoding\": \"AMR_WB\",\"languageCode\": \"ja\",\"sampleRateHertz\": 16000}}";

		String name = null;
		HttpURLConnection httpConnection = null;

			URL serverUrl = new URL(TARGET_URL);
			URLConnection urlConnection;

			urlConnection = serverUrl.openConnection();
			httpConnection = (HttpURLConnection) urlConnection;
			httpConnection.setRequestMethod("POST");
			httpConnection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
			httpConnection.setRequestProperty("Authorization", accessToken);
			httpConnection.setDoOutput(true);
			httpConnection.setDoInput(true);

```
こんな感じのHTTPリクエストをGoogleのサーバーに送ると指定した音声データのテキストが格納されているJSONが帰ってきます。（ここでは音声データの指定をCloudStorage上の音声にしています）
無事テキストを取得することができました。

しかし…
次の日事件は起きるのです…

無事テキストを取得できたのに、次の日になると取得できなくっていました…
知識がない私は「ぇ　Googleにブロックされた？…」なんて思ってしまい、先輩に相談しました。
3日くらい考え悩みました…
その結果たどり着いた答えはコレでした。
[Access Token Response](https://www.oauth.com/oauth2-servers/access-tokens/access-token-response/)

```
When responding with an access token, the server must also
include the additional Cache-Control: no-store and 
Pragma: no-cache HTTP headers to ensure clients do not cache this request.
```
HTTPでAuth2を使う時は`Cach-Control`をno-storeにする必要がある的な文言…
どうやらアプリにアクセストークンのキャッシュが溜まっちゃって1時間の間はアクセストークンが再発行されないから良かったんですが、ある程度立つとキャッシュから読みに行ってしまってエラーが出てしまったんですね。
HTTP通信すら覚えたばかりでHTTPのヘッダーすらあまり理解しておらず、自分では結論に達せなかったです。先輩に感謝…

ということで下記を追加してあげます！

```java:HTTPヘッダー
httpConnection.setRequestProperty("Cache-Control", "no-store");
```

これで、無事SPEECH APIを使うことができます。

# 最後に
今回はSPEECH APIの基本的な使い方と使うにあたってつまづいた部分のご紹介をさせていただきました。

GCPは調べても公式ドキュメントが未完成だったり（そもそもないやつもある）、参考ページが基本英語だったりとかなり苦戦しました。
しかし、分からないなりに手探りでやってみて新しい技術や学びを習得し多くのことを学びました。
研修の段階で、このレベルの技術に触れさせていただけて本当に良い学びができました。
今回新卒専用カレンダーということで、今しかない新卒の時期に多くの学びができたことに感謝し今後も励んでいこうと思います。

だいぶ説明がわかりづらくなってしまいましたが、最後まで読んでくれた方ありがとうございます。