OpenAIのAPIとUnityで音声会話チャットボットを作る ~ 音声生成編 ~
はじめに
この記事は Panda株式会社 Advent Calendar 2023 6日目の記事です。
Panda株式会社として初めてのAdvent Calendarとなります。
Panda株式会社は東京大学松尾研究室・香川高専発のスタートアップで、AR技術とAI技術を駆使したシステム開発と研究に取り組んでいます。
このアドベントカレンダーでは、スタートアップとしての知見、AI・AR技術、バックエンドなど、さまざまな領域の記事を公開していきます。
本記事は、Panda株式会社 Advent Calendar 2023の5日目の記事「 OpenAIのAPIとUnityで音声会話チャットボットを作る ~ 音声入力編 ~」の続きです。
環境
- Unity 2021.3.9f1以降
システム構成
このシステムでは、ユーザーが音声で質問すると、Unity Technologies Japanが開発者のために提供しているオリジナルキャラクター「ユニティちゃん」が音声で応答するチャットボットを作成します。ユーザーの音声での入力はマイクを使用して録音され、その音声データはテキストに書き起こされます。次に、この書き起こされたテキストをOpenAIのchat completions APIに送信し、ユーザーの質問に対する適切な応答を生成します。chat completions APIに送信するテキストには、ユニティちゃんの喋り方を指定するプロンプトと感情分析を含めることで、返却された応答から「ユニティちゃん」の表情を操作します。さらに、生成された応答はGoogle Cloud Platform(GCP)のText-to-Speechサービスを使用して音声データに変換されます。そして、この音声データをUnity内で再生することで、アバターが音声で応答することを実現します。
それぞれの機能を実現するために本記事を含めて以下の4つの記事に分けて実装について説明します。アドベントカレンダーで公開される記事になっているため、投稿日現在(2023/12/06)、アクセスできない記事が含まれています。
- OpenAIのAPIとUnityで音声会話チャットボットを作る ~ チャット機能編 ~
- OpenAIのAPIとUnityで音声会話チャットボットを作る ~ 音声入力編 ~
- (本記事)OpenAIのAPIとUnityで音声会話チャットボットを作る ~ 音声生成編 ~
- OpenAIのAPIとUnityで音声会話チャットボットを作る ~ 喜怒哀楽編 ~
音声出力用のオブジェクトを作成する
前々回の記事でチャット機能を作成し、ユーザの入力に対する応答が返却されるようになりました。この応答を音声に変換し再生することで、ユーザがシステムに話しかけると音声で返事が来るチャットボットを実現します。
Unityで音声を再生するにはAudio Sourceを用います。
ヒエラルキーを右クリックし、Audio → Audio Source
の順で選択し、Buttonをシーンに追加します。
Google Cloud Platform(GCP)のText-to-speechサービスとの連携
ユーザの入力に対する応答テキストを音声データに変換するために、GCPが提供するText-to-Speechを用いて音声ファイルを生成することができます。Text-to-Speechを使用すると、入力されたテキストから自然に聞こえる人間の音声を合成し、再生可能な音声を生成することができます。
この機能を利用するには、音声合成したいテキストを含むリクエストをGCPのAPIに送信し、生成された音声ファイルを受け取る必要があります。UnityからAPIリクエストを送信するにはUnityWebRequestを使います。
1.Google Cloud Platform ConsoleでAPIを有効化する
Google Cloud Text-to-Speech APIを実行するために始める前にのページを参考に基本設定を行います。
必要な基本設定は以下の通りです。
- GCPプロジェクトでText-to-Speechを有効にする
-
- Text-to-Speechで課金を有効にする
-
- 1つ以上のサービスアカウントをText-to-Speechに割り当てる
-
- サービスアカウントのAPIキーをダウンロードする
-
- 認証情報の環境変数を設定する
2.APIに対するリクエストの送受信
UnityWebRequestを用いて、APIにリクエストを送るコードを作成します。リクエストには音声合成したいテキストを含める必要があります。
APIを呼び出すために必要なリクエストと返却される形式についてはGCPのtext-to-speech APIのドキュメントから確認することができます。
「https://texttospeech.googleapis.com/v1/text:synthesize 」エンドポイントに生成したいテキストを入れたJSONをPostすることでリクエストを送ることができます。
UnityWebRequestでPostするにはPostリクエストを作成する必要があります。以下は、UnityからAPIリクエストを送信・受信するためのスクリプトです。
GoogleTextToSpeech.csの全体
// 必要なライブラリをインポート
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
using System;
// AudioSourceコンポーネントが必要であることを指定
[RequireComponent(typeof(AudioSource))]
public class GoogleTextToSpeech : MonoBehaviour
{
[SerializeField]
private string apikey; // Google APIキー
private string URL; // Google Text-to-Speech APIのURL
private AudioSource _audioSource; // オーディオソースコンポーネント
// Google APIリクエストのためのデータ構造
[System.Serializable]
private class SynthesisInput
{
public string text; // 変換するテキスト
}
[System.Serializable]
private class VoiceSelectionParams
{
public string languageCode = "ja-JP"; // 言語コード(日本語)
public string name; // 音声の名前
}
[System.Serializable]
private class AudioConfig
{
public string audioEncoding = "LINEAR16"; // オーディオエンコーディング形式
public int speakingRate = 1; // 話速
public int pitch = 0; // ピッチ
public int sampleRateHertz = 16000; // サンプルレート
}
[System.Serializable]
private class SynthesisRequest
{
public SynthesisInput input; // 入力テキスト
public VoiceSelectionParams voice; // 音声の設定
public AudioConfig audioConfig; // オーディオ設定
}
[System.Serializable]
private class SynthesisResponse
{
public string audioContent; // 変換後のオーディオコンテンツ(Base64エンコーディング)
}
// 開始時の初期設定
private void Start()
{
_audioSource = GetComponent<AudioSource>(); // AudioSourceコンポーネントを取得
URL = "https://texttospeech.googleapis.com/v1/text:synthesize?key=" + apikey; // APIのURLを構築
}
// テキストを音声に変換して再生するメソッド
public void SynthesizeAndPlay(string text)
{
StartCoroutine(Synthesize(text)); // 同期処理を開始
}
// Google Text-to-Speech APIを呼び出して音声データを取得するコルーチン
private IEnumerator Synthesize(string text)
{
// リクエストデータを作成
SynthesisRequest requestData = new SynthesisRequest
{
input = new SynthesisInput { text = text },
voice = new VoiceSelectionParams { languageCode = "ja-JP", name = "ja-JP-Neural2-B" },
audioConfig = new AudioConfig { audioEncoding = "LINEAR16", speakingRate = 1, pitch = 0, sampleRateHertz = 16000 }
};
// UnityWebRequestを作成し、POSTリクエストを送信
using (UnityWebRequest www = new UnityWebRequest(URL, "POST"))
{
byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(JsonUtility.ToJson(requestData));
www.uploadHandler = new UploadHandlerRaw(bodyRaw);
www.downloadHandler = new DownloadHandlerBuffer();
www.SetRequestHeader("Content-Type", "application/json");
yield return www.SendWebRequest();
// リクエストが成功した場合
if (www.result == UnityWebRequest.Result.Success)
{
string response = www.downloadHandler.text;
SynthesisResponse synthesisResponse = JsonUtility.FromJson<SynthesisResponse>(response);
PlayAudioFromBase64(synthesisResponse.audioContent); // Base64エンコードされた音声データを再生
}
else // リクエストが失敗した場合
{
Debug.LogError("Google Text-to-Speech Error: " + www.error);
}
}
}
// Base64エンコードされたオーディオデータを再生するメソッド
private void PlayAudioFromBase64(string base64AudioData)
{
byte[] audioBytes = System.Convert.FromBase64String(base64AudioData);
LoadAudioClipAndPlay(audioBytes); // オーディオクリップをロードして再生
}
// オーディオデータをAudioClipに変換して再生するメソッド
private void LoadAudioClipAndPlay(byte[] audioData)
{
int sampleRate = 16000; // Google Text-to-Speechのデフォルトサンプルレートは16kHz
int channels = 1; // モノラル
// オーディオデータを浮動小数点配列に変換
int samplesCount = audioData.Length / 2; // 16-bit PCM, so 2 bytes per sample
float[] audioFloatData = new float[samplesCount];
// PCMバイトデータをfloat配列に変換
for (int i = 0; i < samplesCount; i++)
{
short sampleInt = BitConverter.ToInt16(audioData, i * 2); // 2バイトをshort intに変換
audioFloatData[i] = sampleInt / 32768.0f; // short int範囲(-32768 to 32767)をfloat範囲(-1 to 1)に変換
}
// AudioClipを作成し、オーディオデータを設定
AudioClip clip = AudioClip.Create("SynthesizedSpeech", samplesCount, channels, sampleRate, false);
clip.SetData(audioFloatData, 0);
// オーディオソースにクリップを設定し、再生
_audioSource.clip = clip;
_audioSource.Play();
}
}
全体のコードを元に、GCP text-to-speech APIにリクエストを送る方法を紹介します。
UnityでJSON形式のPOSTリクエストを送るには、JSONに変換したいクラスを定義し、そのクラスのインスタンスをJsonUtility.ToJson
メソッドを利用してJSON形式のオブジェクトに変換する必要があります。
以下の部分で、GCP text-to-speech APIにリクエストを行うためのJSONモデルを定義しています。GCP text-to-speech API referenceのリクエストボディの欄を確認すると、以下の二つが必須項目であることがわかります。
-
input object:
- text string:合成したいテキスト
-
voice object:
-
languageCode string: 言語コードを指定 今回は「
ja-JP
」 - name string: 声の名前
-
languageCode string: 言語コードを指定 今回は「
-
audioConfig object: 生成された音声の構成
-
audioEncoding enum: 音声ファイルの形式 今回は「
LINEAR16
」(WAV形式のことです) - speakingRate number: 話す速度 1が通常
- pitch number: 音声のピッチ 0が通常
-
sampleRateHertz number: 音声のサンプルレート 今回は
16000
-
audioEncoding enum: 音声ファイルの形式 今回は「
以上の要素を持つクラスをSynthesisRequest
、SynthesisInput
、VoiceSelectionParams
、AudioConfig
と定義し、[System.Serializable]
属性をつけます。これにより、APIサーバにPOSTリクエストをJSON形式で送るために、クラスのインスタンスをJsonUtility.ToJson
を用いてシリアライゼーションできるようになります。
// Google APIリクエストのためのデータ構造
[System.Serializable]
private class SynthesisInput
{
public string text; // 変換するテキスト
}
[System.Serializable]
private class VoiceSelectionParams
{
public string languageCode = "ja-JP"; // 言語コード(日本語)
public string name; // 音声の名前
}
[System.Serializable]
private class AudioConfig
{
public string audioEncoding = "LINEAR16"; // オーディオエンコーディング形式
public int speakingRate = 1; // 話速
public int pitch = 0; // ピッチ
public int sampleRateHertz = 16000; // サンプルレート
}
[System.Serializable]
private class SynthesisRequest
{
public SynthesisInput input; // 入力テキスト
public VoiceSelectionParams voice; // 音声の設定
public AudioConfig audioConfig; // オーディオ設定
}
以下コードで先ほど定義したSynthesisRequest
を元にインスタンスを作ります。この時、text
には音声合成したいテキストを、name
には女性の声を使いたいためja-JP-Neural2-B
を指定します。生成したい音声はこの「サポートされている音声と言語」という記事から確認することができます。
// リクエストデータを作成
SynthesisRequest requestData = new SynthesisRequest
{
input = new SynthesisInput { text = text },
voice = new VoiceSelectionParams { languageCode = "ja-JP", name = "ja-JP-Neural2-B" },
audioConfig = new AudioConfig { audioEncoding = "LINEAR16", speakingRate = 1, pitch = 0, sampleRateHertz = 16000 }
};
GCP speech-to-text APIにリクエストを送る際はエンドポイントにクエリパラメータでAPIキーを指定する必要があります。そのため、以下のコードでURLを指定します。
URL = "https://texttospeech.googleapis.com/v1/text:synthesize?key=" + apikey;
以下にGCP speech-to-text APIにPOSTリクエストを送るためのコードを記述します。まず、UnityWebRequest
オブジェクトを作成し、APIのエンドポイントとPOSTメソッドを指定します。リクエストボディには先ほど作ったSynthesisRequest
オブジェクトをJsonUtility.ToJson
でJSON形式のバイトデータに変換します。その後、リクエストのuploadHandler
に紐付けます。downloadHandler
にはAPIからのレスポンスを受け取るためのバッファを設定します。
// UnityWebRequestを作成し、POSTリクエストを送信
using (UnityWebRequest www = new UnityWebRequest(URL, "POST"))
{
byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(JsonUtility.ToJson(requestData));
www.uploadHandler = new UploadHandlerRaw(bodyRaw);
www.downloadHandler = new DownloadHandlerBuffer();
www.SetRequestHeader("Content-Type", "application/json");
}
ここまでで作成したリクエストを送信し、応答が返却されるまで待機します。
yield return www.SendWebRequest();
リクエストが返却されたら、その結果を処理します。リクエストがうまくいかなかった場合はエラー処理し、うまくいった場合は、レスポンスをクラスに変換し、Unity側から扱えるようにします。JsonUtility.FromJson
を用いることで、JSON形式のテキストをclassオブジェクトにシリアライズすることができます。
// リクエストが成功した場合
if (www.result == UnityWebRequest.Result.Success)
{
string response = www.downloadHandler.text;
SynthesisResponse synthesisResponse = JsonUtility.FromJson<SynthesisResponse>(response);
PlayAudioFromBase64(synthesisResponse.audioContent); // Base64エンコードされた音声データを再生
}
else // リクエストが失敗した場合
{
Debug.LogError("Google Text-to-Speech Error: " + www.error);
}
GCP text-to-speech APIから返却されるレスポンスは次のようになっています。
- audioContent string: リクエストで指定された通りにエンコードされた音声データのバイトデータ Base64でエンコードされた文字列
この要素を持つクラスをSynthesisResponse
として定義します。以下のように[System.Serializable]
属性を指定し、JSON形式のテキストをシリアライズできるようにします。
[System.Serializable]
private class SynthesisResponse
{
public string audioContent; // 変換後のオーディオコンテンツ(Base64エンコーディング)
}
PlayAudioFromBase64(synthesisResponse.audioContent);
を用いて、Base64でエンコードされたWAV形式の音声データを再生します。再生に用いるコードは以下の通りです。
// Base64エンコードされたオーディオデータを再生するメソッド
private void PlayAudioFromBase64(string base64AudioData)
{
byte[] audioBytes = System.Convert.FromBase64String(base64AudioData);
LoadAudioClipAndPlay(audioBytes); // オーディオクリップをロードして再生
}
Unityで音声を再生するにはAudioSource
にAudioClip
形式の音声を設定し再生する必要があります。そのため、LoadAudioClipAndPlay
という音声のバイトデータをAudioClip
に変換して再生するメソッドを定義し、実行します。
// オーディオデータをAudioClipに変換して再生するメソッド
private void LoadAudioClipAndPlay(byte[] audioData)
{
int sampleRate = 16000; // Google Text-to-Speechのデフォルトサンプルレートは16kHz
int channels = 1; // モノラル
// オーディオデータを浮動小数点配列に変換
int samplesCount = audioData.Length / 2; // 16-bit PCM, so 2 bytes per sample
float[] audioFloatData = new float[samplesCount];
// PCMバイトデータをfloat配列に変換
for (int i = 0; i < samplesCount; i++)
{
short sampleInt = BitConverter.ToInt16(audioData, i * 2); // 2バイトをshort intに変換
audioFloatData[i] = sampleInt / 32768.0f; // short int範囲(-32768 to 32767)をfloat範囲(-1 to 1)に変換
}
// AudioClipを作成し、オーディオデータを設定
AudioClip clip = AudioClip.Create("SynthesizedSpeech", samplesCount, channels, sampleRate, false);
clip.SetData(audioFloatData, 0);
// オーディオソースにクリップを設定し、再生
_audioSource.clip = clip;
_audioSource.Play();
}
GCP text-to-speech APIでリクエストの送受信を実現するスクリプトの解説は以上です。
GoogleTextToSpeech.cs
を最初に作成したAudio Source
オブジェクトにアタッチします。
インスペクターからApiKeyに先ほど取得したGCPのAPIキーを記述します。
これにより、ユーザのメッセージに対応する応答が音声で返ってくるようなチャットボットを実現することができます。
おわりに
今回は「OpenAIのAPIとUnityで音声会話チャットボットを作る ~ 音声入力編 ~」というテーマでPanda株式会社 Advent Calendar 2023 6日目を執筆させていただきました。
本記事では、UnityでGCPを用いてメッセージの応答を音声合成で出力できるチャットボットを作成しました。
明日の記事も私、Panda株式会社代表取締役の田貝奈央による「OpenAIのAPIとUnityで音声会話チャットボットを作る ~ 喜怒哀楽編 ~」です。この記事を読めばチャットボットの応答を音声で出力できるようになります。明日の記事をお楽しみに!
Discussion