Elixir Livebook から AmiVoice の音声認識 API を呼び出す

## はじめに

Elixir から AmiVoice API による音声認識を実行します

今回も Livebook を使っていきます

https://livebook.dev/

AmiVoice API の利用方法については公式のマニュアルを参照

https://docs.amivoice.com/amivoice-api/manual/

## AmiVoice とは

AmiVoice は株式会社アドバンストメディアが開発した音声認識ソフトウェアです

https://acp.amivoice.com/

https://www.advanced-media.co.jp/

特長はなんと言っても **「日本語に対する精度の高さ」** でしょう

世の中に音声認識サービスは数多ありますが、グローバルに展開しているものはどうしても日本語に非対応だったり、対応していても精度がいまいちだったりします

AmiVoice は私たち日本人が使うのに最適化されています

こちらから音声認識を実際に動かして試せるので使ってみましょう

https://acp.amivoice.com/amivoice/

例えば、以下のような文章もバッチリ認識できました

- 拝啓春暖の候、ますますご清栄のこととお喜び申し上げます
- 国民は、全ての基本的人権の享有を妨げられない
- OECグループは、技術と信頼真心をモットーに、高い技術力を持ったICTサービス企業として、 お客様のニーズにお応えし、社会に貢献してまいります
- 大分県大分市東春日町17番57号ソフトパーク内

更に医療系や金融・保険業界に特化したエンジンも選択できます

また、 API 利用量が安く、 **毎月 60 分無料** であることも魅力です

https://acp.amivoice.com/amivoice_api/price/

発話時間にしか課金されないため、無音が続くような場合にも無駄がありません

加えて、昨今生成 AI 周りでよく気にされることですが、いわゆる **オプアウト** = **学習用にデータを使わせない** 指定もできます（その分費用が高くなります）

音声データがサーバーに保存されるのはちょっと、、、という場合にも使えるのは嬉しいですね

## アカウント登録

AmiVoice の API を使うためには、まずアカウント登録が必要です

:::note warn
最初の 60 分が無料なので 0 円で試すことが可能ですが、クレジットカードの登録は必須です
:::

公式サイトのトップページから、右下の方にある「API を無料で利用開始」のボタンをクリックしましょう

https://acp.amivoice.com/

![スクリーンショット 2024-04-13 17.11.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/720ffc45-e74c-5a67-bdce-df97d990adcf.png)

メールアドレスを入力する画面が表示されるので、あとは指示に従っていきます

![スクリーンショット 2024-04-13 17.11.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/7fdc8261-7ab3-1fa6-6719-a379583c7576.png)

アカウントが登録できたら以下の URL からマイページにログインできます

https://acp.amivoice.com/account/

ただし、クレジットカードを登録していないと以下のような表示になるため、クレジットカードを登録しましょう

![スクリーンショット 2024-04-13 17.16.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/8110a510-c7b2-7195-ec40-9ba3aa02088e.png)

## 接続情報

マイページの左メニュー「接続情報」をクリックすると、 API 呼び出しに使う「APPKEY」が確認できます

![スクリーンショット 2024-04-13 17.21.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/aaec0e34-6bdf-0eac-3e98-d511799676dd.png)

## 環境構築

音声の形式変換のため、 Vice というモジュールを使います

https://github.com/G-Corp/vice

Vice には以下のモジュールが必要です

- cmake
- ffmpeg
- libav
- libopencv-dev
- imagemagick
- SoX

今回は音声を扱うため、少なくとも SoX が必須になります

Ubuntu の場合は以下もコマンドでインストールできます

```sh
apt install sox
```

私は以下のリポジトリーにある Docker コンテナで Livebook を立ち上げています
（今回、 SoX のインストールを追加しました）

https://github.com/RyoWakabayashi/elixir-learning

## セットアップ

Livebook を起動し、新しいノートブックのセットアップセルに以下のコードを入力、実行します

```elixir
Mix.install([
  {:kino, "~> 0.12"},
  {:multipart, "~> 0.4"},
  {:req, "~> 0.4"},
  {:vice, "~> 0.1"}
])
```

API の呼び出しに Req を使います

https://github.com/wojtekmach/req

音声データと認証情報、オプション等をマルチパートで送信するため、 Multipart を使用します

https://github.com/breakroom/multipart

## 接続情報の設定

API の認証に使う APPKEY を入力します

```elixir
appkey_input = Kino.Input.password("APPKEY")
```

APPKEY はAmiVoice マイページの接続情報からコピーしてください

![スクリーンショット 2024-04-14 10.08.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/f6fca820-9bf8-5d68-6380-b934a3cb2d2f.png)

APPKEY をマルチパートのデータに格納します

:::note info
`Kino.nothing()` はセルの出力結果に認証情報を表示しないためのコードです
:::

```elixir
auth_part =
  appkey_input
  |> Kino.Input.read()
  |> Multipart.Part.text_field("u")

Kino.nothing()
```

## 入力データの準備

AmiVoice API のエンドポイント URL を設定します

今回はログが残らない（音声データを AmiVoice のサーバー上に保存しない）エンドポイントを指定しています

```elixir
api_endpoint = "https://acp-api.amivoice.com/v1/nolog/recognize"
```

音声入力を用意します

AmiVoice API は wav 形式に対応しているため、形式に `:wav` を指定します

16k 以上のサンプリングレートは意味がない（API に送っても 16k にダウンサンプリングされる）ため、サンプリングレートを `16_000` に指定します 

```elixir
audio_input = Kino.Input.audio("Audio", format: :wav, sampling_rate: 16_000)
```

![スクリーンショット 2024-04-14 10.16.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/a48c3ddf-f9c4-f910-e205-ade6c2cf4514.png)

「Record」ボタンをクリックするとマイクから録音できます

初回は以下のようなアラートがブラウザに表示されるので、マイクの使用を許可してください

![スクリーンショット 2024-04-14 10.17.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/29c89896-8036-f3c6-8f37-dc46c68995da.png)

録音できると以下のような表示になります

![スクリーンショット 2024-04-14 10.23.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/47c636f3-c184-5526-462f-8580aee58b0d.png)

以下のコードにより、音声データの保存先パスが取得できます

```elixir
audio_path =
  audio_input
  |> Kino.Input.read()
  |> Map.get(:file_ref)
  |> Kino.Input.file_path()
```

出力結果（一例）
```elixir
"/tmp/livebook/sessions/gfwl4emfac5g3ipnryin7zya57i3h236pahpnzqv5alt6z2l/registered_files/7c77otkkqjt7kjzm"
```

この音声ファイルはビットレート 32 になっていますが、 AmiVoice API はビットレート 16 に変換しないと「サポートしていない形式」としてエラーになってしまいます

https://docs.amivoice.com/amivoice-api/manual/user-guide/request/audio-format

そこで、 Vice を使ってビットレートを変換します

まず、一時ファイルとして `.wav` の拡張子を付けてコピーします

```elixir
File.copy!(audio_path, "/tmp/input.wav")
```

Vice のプロセスを起動します

```elixir
Vice.start()
```

Vice に変換を依頼します（非同期です）

```elixir
{:async, worker} = Vice.convert("/tmp/input.wav", "/tmp/input_16b.wav", output_bits: 16)
```

worker の状態は以下のコードで確認できるので、結果が `:done` になるまで待ちます

```elixir
Vice.status(worker)
```

変換した入力ファイルをマルチパートのデータに格納します

```elixir
audio_content = File.read!("/tmp/input_16b.wav")
audio_part = Multipart.Part.file_content_field("audio", audio_content, "a")
```

認証情報、エンジンの指定、音声データをマルチパートにまとめます
公式マニュアルにある通り、必ず音声データを一番最後に追加します

```elixir
multipart =
  Multipart.new()
  |> Multipart.add_part(auth_part)
  |> Multipart.add_part(Multipart.Part.text_field("-a-general", "d"))
  |> Multipart.add_part(audio_part)

Kino.nothing()
```

リクエスト時のヘッダーを設定します

```elixir
content_length = Multipart.content_length(multipart)
content_type = Multipart.content_type(multipart, "multipart/form-data")

headers = [
  {"Content-Type", content_type},
  {"Content-Length", to_string(content_length)}
]
```

リクエストを送信します

```elixir
{:ok, response} = Req.post(api_endpoint, headers: headers, body: Multipart.body_stream(multipart))
```

出力結果を確認してみます

```elixir
response.body
```

出力結果
```elixir
%{
  "code" => "",
  "message" => "",
  "results" => [
    %{
      "confidence" => 0.998,
      "endtime" => 2516,
      "rulename" => "",
      "starttime" => 900,
      "tags" => [],
      "text" => "こんにちは。",
      "tokens" => [
        %{
          "confidence" => 0.96,
          "endtime" => 2164,
          "spoken" => "こんにちは",
          "starttime" => 1204,
          "written" => "こんにちは"
        },
        %{
          "confidence" => 0.77,
          "endtime" => 2516,
          "spoken" => "_",
          "starttime" => 2164,
          "written" => "。"
        }
      ]
    }
  ],
  "text" => "こんにちは。",
  "utteranceid" => "20240414/10/018eda4081d80a301ee894c7_20240414_103724"
}
```

上記は「こんにちは」だけの例ですが、以下のような長文でも正しく認識できました
（一部、漢字がおかしい箇所がありますが、裁判に特化しているわけではないので致し方ないでしょう）

```
本日はこの裁判所において、被告桃太郎氏および彼の同行者犬猿雉に対する公判を改定いたします。
被告らは、不法侵入、盗難および暴力行為の罪に問われています。
```

## まとめ

マルチパートで送信する点、ビットレートを16に変換する点で少し躓きましたが、無事、 Elixir から AmiVoice API を呼び出して音声認識を実行できました

毎月 60 分無料であるため、気軽に試すことができました

また、他の音声認識サービスと比較して、かなり日本語の認識精度が高いことも確認できました

日本向けのサービスであれば、かなり利用できそうですね