以下の内容はhttps://redhologerbera.hatenablog.com/entry/2025/03/11/231157より取得しました。


Zonos AIのAPIをUnityで使用する

本日はUnity、AI枠です。

一か月前に登場したTextToSpeechのZonosはAPIが提供されており、月に100分間の生成は無料で使用することができます。

このサイクルは月初めにリセットされると思っていたのですが、筆者環境ではまだリセットされておらず、もうすぐ最初の使い始めから1か月なのでもしかしたらアクティブにしてから1か月で更新される可能性があります。

  redhologerbera.hatenablog.com

redhologerbera.hatenablog.com

redhologerbera.hatenablog.com

公式ドキュメントでは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-speechhttpsではなくhttpである点です。

Unityではデフォルトでhttpへのリクエストが推奨されておらず、そのままでは実行できません。

これを回避するためにはProjectSettingsのPlayerSettingsからOtherSettingsのAllow downloads over HTTPAllways allowdに設定します。

これによってHTTP通信が許可されます。

このコードではResourcesフォルダ内に配置したwav音源の名前を指定してUploadすることでその声のクローンボイスが返されます。

mp3で指定しており、一度Assets内に保存、それをAudioClipとして読み込みAudioSourceで再生しています。

粗削りですがとりあえずUnityでもZonosのTTSを動かすことができました。

本日は以上です。




以上の内容はhttps://redhologerbera.hatenablog.com/entry/2025/03/11/231157より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14