本日はUnity、AI枠です。
一か月前に登場したTextToSpeechのZonosはAPIが提供されており、月に100分間の生成は無料で使用することができます。
このサイクルは月初めにリセットされると思っていたのですが、筆者環境ではまだリセットされておらず、もうすぐ最初の使い始めから1か月なのでもしかしたらアクティブにしてから1か月で更新される可能性があります。
公式ドキュメントではPythonのコードはありますが、UnityのC#でのサンプルがなかったため今回Unityで動かしていきます。
〇コード
クリックして展開
using System;
using System.Collections;
using System.IO;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;
public class ZyphraTTSPlayer : MonoBehaviour
{
// APIキーとTTSエンドポイントの設定
[SerializeField] private string apiKey = "APIキーを入力";
private string apiUrl = "http://api.zyphra.com/v1/audio/text-to-speech";
// Resources内の元音源ファイル名(拡張子は不要)
[SerializeField] private string originalAudioResourceName = "";
// 再生するテキスト
public string textToSpeak = "こんにちは,
これはテストの音声です。";
// AudioSourceコンポーネント(なければ自動追加)
private AudioSource audioSource;
public void ttsStart()
{
// AudioSourceの取得または追加
audioSource = GetComponent<AudioSource>();
if (audioSource == null)
{
audioSource = gameObject.AddComponent<AudioSource>();
}
// ResourcesからAudioClipとして元音源をロード
AudioClip originalClip = Resources.Load<AudioClip>(originalAudioResourceName);
if (originalClip == null)
{
Debug.LogError("Resources内に元音源が見つかりません: " + originalAudioResourceName);
return;
}
// AudioClipからWav形式のバイト列に変換
byte[] wavBytes = AudioClipToWav(originalClip);
if (wavBytes == null)
{
Debug.LogError("AudioClipからWAVへの変換に失敗しました。");
return;
}
// Base64エンコード
string originalAudioBase64 = Convert.ToBase64String(wavBytes);
// TTSリクエスト開始
StartCoroutine(SendTTSRequest(textToSpeak, originalAudioBase64));
}
private IEnumerator SendTTSRequest(string textToSpeech, string speakerAudioBase64)
{
// リクエストデータの作成
//jaで日本語を指定
TTSRequest requestData = new TTSRequest
{
text = textToSpeech,
language_iso_code = "ja",
speaking_rate = 15,
mime_type = "audio/mp3",
speaker_audio = speakerAudioBase64
};
// JSON文字列に変換
string jsonPayload = JsonUtility.ToJson(requestData);
Debug.Log("JSON Payload: " + jsonPayload);
byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonPayload);
// UnityWebRequestによるPOSTリクエストの作成
UnityWebRequest request = new UnityWebRequest(apiUrl, "POST");
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
request.SetRequestHeader("X-API-Key", apiKey);
yield return request.SendWebRequest();
if (request.result != UnityWebRequest.Result.Success)
{
Debug.LogError("TTSリクエストエラー: " + request.error);
}
else
{
byte[] audioData = request.downloadHandler.data;
Debug.Log("受信した音声データサイズ: " + audioData.Length);
// ファイルを保存
string debugFilePath = System.IO.Path.Combine(Application.dataPath, "DebugAudio.mp3");
try
{
System.IO.File.WriteAllBytes(debugFilePath, audioData);
Debug.Log("WAVファイルを保存しました: " + debugFilePath);
}
catch (System.Exception ex)
{
Debug.LogError("WAVファイルの保存に失敗しました: " + ex.Message);
}
//debug用として保存したmp3をclipとして読み込む
using (UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip("file://" +debugFilePath, AudioType.MPEG))
{
yield return www.SendWebRequest();
if (www.result == UnityWebRequest.Result.Success)
{
// AudioClipを取得
AudioClip clip = DownloadHandlerAudioClip.GetContent(www);
clip.name = Path.GetFileNameWithoutExtension("file://" +debugFilePath);
audioSource.clip = clip;
// 再生開始
audioSource.Play();
}
else
{
Debug.LogError($"音声ファイルのロードに失敗しました: {www.error}");
}
}
}
}
[Serializable]
public class TTSRequest
{
public string text;
public string language_iso_code;
public int speaking_rate;
public string mime_type;
public string speaker_audio;
}
/// <summary>
/// AudioClipからWAV形式(PCM 16bit, ヘッダー44バイト)のバイト配列に変換する。
/// </summary>
/// <param name="clip">変換対象のAudioClip</param>
/// <returns>WAVファイル形式のバイト配列</returns>
public byte[] AudioClipToWav(AudioClip clip)
{
if (clip == null)
return null;
// AudioClipからサンプルデータを取得
float[] samples = new float[clip.samples * clip.channels];
clip.GetData(samples, 0);
// サンプルデータを16bit PCMに変換
Int16[] intData = new Int16[samples.Length];
byte[] bytesData = new byte[samples.Length * 2];
float rescaleFactor = 32767; // float→Int16変換用
for (int i = 0; i < samples.Length; i++)
{
intData[i] = (short)(samples[i] * rescaleFactor);
}
Buffer.BlockCopy(intData, 0, bytesData, 0, bytesData.Length);
// WAVヘッダーの作成
MemoryStream stream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(stream);
int headerSize = 44;
int fileSize = headerSize + bytesData.Length;
// RIFFヘッダー
writer.Write(Encoding.UTF8.GetBytes("RIFF"));
writer.Write(fileSize - 8);
writer.Write(Encoding.UTF8.GetBytes("WAVE"));
writer.Write(Encoding.UTF8.GetBytes("fmt "));
writer.Write(16); // Subchunk1Size (PCM)
writer.Write((short)1); // AudioFormat (PCM)
writer.Write((short)clip.channels);
writer.Write(clip.frequency);
writer.Write(clip.frequency * clip.channels * 2); // ByteRate = SampleRate * Channels * BitsPerSample/8
writer.Write((short)(clip.channels * 2)); // BlockAlign = Channels * BitsPerSample/8
writer.Write((short)16); // BitsPerSample
writer.Write(Encoding.UTF8.GetBytes("data"));
writer.Write(bytesData.Length);
writer.Write(bytesData);
writer.Flush();
return stream.ToArray();
}
}
このコードでは基本形はUntiyのWebrequestです。これでは指定されたURLに対してrequestを送ります。
Pythonで提供されているzyphraライブラリを読み取ってフォーマットを合わせています。
問題点としてエンドポイントがhttp://api.zyphra.com/v1/audio/text-to-speechとhttpsではなくhttpである点です。
Unityではデフォルトでhttpへのリクエストが推奨されておらず、そのままでは実行できません。
これを回避するためにはProjectSettingsのPlayerSettingsからOtherSettingsのAllow downloads over HTTPをAllways allowdに設定します。

これによってHTTP通信が許可されます。
このコードではResourcesフォルダ内に配置したwav音源の名前を指定してUploadすることでその声のクローンボイスが返されます。
mp3で指定しており、一度Assets内に保存、それをAudioClipとして読み込みAudioSourceで再生しています。
粗削りですがとりあえずUnityでもZonosのTTSを動かすことができました。
本日は以上です。