日本語TTSクラウドサービス生成速度比較

高速な音声対話を実現したいときに、各種TTSサービスがどの程度の時間がかかるのか調査したので共有します。

# プログラム
基本的に発話テキストを受け取ってwavファイルが保存されるまでを計測します。
一部 stream 再生機能を持つクラウドサービスがある場合は、その機能を作成してくださったことに敬意を評して一番最初の音声チャンクを受信したタイミングを採用します。

## amazon polly

アクセスキーとシークレットキーはAIMから生成できるので探してみてください。

```python
import boto3
import time

# text = "こんにちは"
text = "猫ミームは、インターネット上で広く愛されている猫をテーマにしたユーモラスな画像や動画のことです。これらのミームは、可愛らしい猫の写真や面白いシチュエーションを捉えたGIF、またはテキスト付きのコラージュなどが一般的です。猫ミームは社会的な共感やユーモアを共有する手段として非常に人気があります。"

client = boto3.client('polly',
        aws_access_key_id="add_your_access_key",
        aws_secret_access_key="add_your_secret_key",
        region_name='ap-northeast-1'
)

start_time = time.time()
response = client.synthesize_speech(
    OutputFormat='mp3',
    SampleRate='8000',
    Text=text,
    TextType='text',
    # Engine="standard",
    # VoiceId='Mizuki',
    Engine="neural",
    VoiceId='Tomoko',
)

b = response['AudioStream'].read()
with open('aws.mp3', "wb") as f:
    f.write(b)
print("elapsed ", time.time() - start_time)
```

## Azure
ポータルで音声サービスを有効化すると、認証情報が出てくるのでその情報を記載してください。
```python
from azure.cognitiveservices.speech import AudioDataStream, SpeechConfig, SpeechSynthesizer
import time

speech_config = SpeechConfig(
    subscription="add_your_key",
    region="add_your_region"
)
synthesizer = SpeechSynthesizer(speech_config=speech_config, audio_config=None)

# text = "こんにちは"
text = "猫ミームは、インターネット上で広く愛されている猫をテーマにしたユーモラスな画像や動画のことです。これらのミームは、可愛らしい猫の写真や面白いシチュエーションを捉えたGIF、またはテキスト付きのコラージュなどが一般的です。猫ミームは社会的な共感やユーモアを共有する手段として非常に人気があります。"

start_time = time.time()
ssml_string = f"""<speak version="1.0" xmlns="https://www.w3.org/2001/10/synthesis" xml:lang="ja-JP">
  <voice name="ja-JP-NanamiNeural">
    {text}
  </voice>
</speak>"""
result = synthesizer.speak_ssml_async(ssml_string).get()
stream = AudioDataStream(result)
stream.save_to_wav_file("azure.wav")
print(time.time() - start_time)
```

## GCP
GCPの認証はプロジェクトごとのサービスアカウントから作成します。jsonをダウンロードして環境変数にセットするタイプです。
```python
import time
import os
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = 'your_credentials_path.json'
from google.cloud import texttospeech

client = texttospeech.TextToSpeechClient()
voice = texttospeech.VoiceSelectionParams(
        name="ja-JP-Standard-B",
        language_code="ja-JP",
        ssml_gender=texttospeech.SsmlVoiceGender.NEUTRAL
)

audio_config = texttospeech.AudioConfig(
    audio_encoding=texttospeech.AudioEncoding.LINEAR16
)

# text = "こんにちは"
text = "猫ミームは、インターネット上で広く愛されている猫をテーマにしたユーモラスな画像や動画のことです。これらのミームは、可愛らしい猫の写真や面白いシチュエーションを捉えたGIF、またはテキスト付きのコラージュなどが一般的です。猫ミームは社会的な共感やユーモアを共有する手段として非常に人気があります。"

start_time = time.time()

synthesis_input = texttospeech.SynthesisInput(text=text)
response = client.synthesize_speech(
    input=synthesis_input, voice=voice, audio_config=audio_config
)

with open("output.wav", "wb") as out:
    out.write(response.audio_content)
    print('Audio content written to file "output.wav"')

print("elapsed ", time.time() - start_time)
```

## Elevenlabs

elevenlabsは認証情報の取得はめちゃめちゃかんたんです。アカウント作成して画面いじってたらすぐ見つかると思います。
こちらは Asahi - Japanese male 様のモデルを使用する例です。
wavファイルを保存するように書いても良かったのですが、せっかく stream 再生機能があるのでこちらを使いましょう。

```python
from elevenlabs.client import ElevenLabs
from elevenlabs import stream
import time

# text = "こんにちは"
text = "猫ミームは、インターネット上で広く愛されている猫をテーマにしたユーモラスな画像や動画のことです。これらのミームは、可愛らしい猫の写真や面白いシチュエーションを捉えたGIF、またはテキスト付きのコラージュなどが一般的です。猫ミームは社会的な共感やユーモアを共有する手段として非常に人気があります。"

client = ElevenLabs(
  api_key="your api key",
)

print("start time ", time.time())
audio_stream = client.generate(
  text=text,
  voice="6XNSYkDqZ1blajSVtPok",
  stream=True
)

stream(audio_stream)
```

時間を計測するために、pypiパッケージの中を直接書き換えます。
公平にするために、音声の再生に関する部分はコメントアウトしておきましょう。

```python:site-packages/elevenlabs/play.py
def stream(audio_stream: Iterator[bytes]) -> bytes:
    if not is_installed("mpv"):
        message = (
            "mpv not found, necessary to stream audio. "
            "On mac you can install it with 'brew install mpv'. "
            "On linux and windows you can install it from https://mpv.io/"
        )
        raise ValueError(message)

    # mpv_command = ["mpv", "--no-cache", "--no-terminal", "--", "fd://0"]
    # mpv_process = subprocess.Popen(
    #     mpv_command,
    #     stdin=subprocess.PIPE,
    #     stdout=subprocess.DEVNULL,
    #     stderr=subprocess.DEVNULL,
    # )

    audio = b""

    flag_receive_first_chunk = False #(追加)
    for chunk in audio_stream:
        if chunk is not None:
            if flag_receive_first_chunk == False: #(追加)
                print(time.time()) #(追加)
                flag_receive_first_chunk = True #(追加)
            # mpv_process.stdin.write(chunk)  # type: ignore
            # mpv_process.stdin.flush()  # type: ignore
            audio += chunk
    # if mpv_process.stdin:
    #     mpv_process.stdin.close()
    # mpv_process.wait()

    return audio

```


# 生成速度


| 生成速度 | 短文初回（秒） | 短文2回目以降（秒） | 長文初回（秒） | 長文2回目以降（秒） |
| ---- | ---- | ---- | ---- | ---- |
| azure tts | 0.781 | 0.324 | 2.239 | 0.697 |
| elevenlabs | 1.606 | 1.041  | 3.139 | 2.872 |
| amazon polly (standard) | 0.207 | 0.126  | 0.865 | 0.216 |
| amazon polly (neural) | 0.324 | 0.154  | 1.240 | 0.804 |
| gcp tts | 0.703 | 0.25~0.5(安定しない)  | 0.72~1.0 (安定しない) | 0.72~1.0 (安定しない) |

短文だと理想は 0.3 とかがほしいですね。長文だと 0.8　くらいなイメージ。
しばらく放置してcallする場合と、連続で何度もcallした場合で速度が大きく違いました。多分キャッシュしています。同じ音声が何度もcallされるケースは例外的なので初回の数字で見ておいたほうがいい気がします。

# 生成された音声の自然さ

以下にアップロードしておきました。
https://drive.google.com/drive/folders/15Mfn_PTeL6Oo6bGF80l9Ga0E_cf5kiWt?usp=drive_link

Qiitaで音声をアップロードする方法ってあるのでしょうかね？

# 感想

生成速度だと amazon polly(standard) が頭一つ飛び抜けているのですが、これのクオリティはあんまり高くないです。といっても聞いていて耐え難いほどではなく、機械的だなーって感じる程度。速度に重きを置くならありですね。GPU動作しているときのVOICEVOX並の超高速応答です。

もうちょっと抑揚というか、人間味がほしいとなるとelevenlabs以外のモデルになるのですが、これのクオリティも似たり寄ったりです。amazon polly (standard)よりかは品質が高く、聞き間違いが起きにくい明瞭な音声ではあるのですが、アナウンサー感はやっぱり抜けません。

生成時間を気にしないのならば、elevenlabsが頭一つ抜けて高品質です。ところどころイントネーションが日本語を母国語としていない方の日本語感があるのですが、elevenlabsは音声合成モデルのマーケット機能のようなものがあるのでよくよく探せばそれすらもクリアできるかもしれません。

ただし長文だとelevenlabsのみかなり時間がかかるので、長文でcallしないような工夫をアプリケーション側で行う必要があると思います。

# 付加価値
amazon pollyは今はneuralとstandardしか選べませんが、将来的には生成AIベースのものも出したいという意図がUIから見えます。今後に期待しましょう。

![Screenshot from 2024-06-16 12-21-50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163759/6453d542-f7e6-ed37-11ec-1892598931dd.png)

azure cloud speechでも実はギャラリーがあったり、カスタム音声もできたりと自由度に幅があります。細かいニーズを満たすこともできそうですね。

![Screenshot from 2024-06-16 12-31-31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163759/68e873e4-3a8a-256f-493b-1694396c3392.png)

Elevenlabsもいくつかの種類のカスタム音声が可能です。5分程度の音声からできるものもあれば、数時間分の音声が必要なものまであります。

![Screenshot from 2024-06-16 12-34-52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163759/eed9a755-9864-8e7b-79f0-1042fcbe2fb5.png)


# その他のモデル
- VOICEVOX
言わずとしれた超有名日本語TTS。CPUモードとGPUモードを選択できてGPUモードだと爆速です。Ubuntu, Windows, mac OSなどで動かすことができます。これをクラウドサービス的に動かすとなると自前でサーバーを立てるそのあたりの処理が必要です。
ただ、開発方針的にAndroidやwasm（ブラウザ）などでも動かせるようにという方針も見え隠れするので、これが実現したらクラウドサービス系TTSとは全く違う方針で独自性が生まれてきそうな気がします。

- Web Speech API
ブラウザのTTSです。javascriptから叩けます。聞いてもらうとわかるのですが到底対話には向いていない品質です。ただし高速かつ手軽です。

- Style Bert Vit2 JP Extra
短文だとコンマ数秒くらいで十分対話に応用できます。音声の品質も学習やモデルマージで工夫次第でどうにかできる余地があるというのもポイントが高いです。他のクラウドTTSサービスが苦手な抑揚やエモーションがあるTTSが特に優れています。デメリットとしてはこちらもまたGPUクラウドサーバーを自分でメンテナンスしないといけない部分です。
- OpenAI系のTTS
こちらの記事が参考になるので見てみてください！
https://blog.jbs.co.jp/entry/2024/02/19/113914



# 結論どれがいいの？

プロダクトやサービス（WEBアプリやスマホアプリのイメージ）として活用したいのなら、安定性や開発工数を考えると、クラウドがやはり手軽です。


そのなかでも単に「音声で情報がやり取りできればいい」のか「対話というユーザ体験を良いものにしたい」のどちらにプロダクトとしての価値をおいているかで結論が変わりそうな気もします。

「音声で情報がやり取りできればいい」のであれば、お客様をまたせてイライラさせないことを重要指標としてamazon polly (standard)を採用するのが無難な気もします。

「対話というユーザ体験を良いものにしたい」というのであれば、elevenlabsは速度の面で、それ以外は品質の面で厳しいものがあります。azure系を採用することで自前でTTSモデルを構築するか、アプリケーションの作り方を工夫して、相槌を挟むなど「速度が遅いと感じる」ことを減らしつつelevenlabsを使用するかあたりが現実的な選択肢ではないでしょうか？ (azure系は速度も遅く品質も微妙ですが、カスタム性には優れています。)

WEBアプリやスマホアプリではなく、例えばPCごと（ハードウェアとソフトウェア抱きかかえで）顧客に納品するようなケースだと、Style Bert Vit2のようなローカルモデルが選択肢に上がってきます。ただStyle Bert Vit2を使って顧客が納得するようなクオリティにするためには、自社で音声データの収録を行うくらいの気概があったほうが良いかもです。もう少し手軽に実装したいのであればVOICEVOXの使用を検討してみるのも良いかもしれません。

ただしローカルモデルには権利関係の問題がつきものなので、そのあたりちゃんと解決できるように調査をしておく必要があります。