【UnityでWhisperAPIを使う！】録音したデータをテキストにおこす

# はじめに
今回は、OpenAIのWhisperAPIを使って、Unityで録音をしたデータをテキストにおこすという実装に挑戦しました！
私の前回のこちらの記事↓の続きです！


https://qiita.com/sayaka814cl/items/d074860539de4693c867


続きと言っても、録音の取り方などは前回のコードから持ってきた部分はあるのですが、今回は録音をした後Play(再生)するのではなく、WhisperAPIに渡してテキスト化しているので、若干実装が変わっています。
(スクリプト名も変わっています！)

Unity × WhisperAPIの記事はもういくつか皆様が書いてくださっていて、しかも結構簡単にできるとの旨が書いてあったのですが。。

私はめっっっっっちゃ手間取りました。爆笑

音声データをWhisperAPIに送る時は、まずは音声データをWavファイルに変換しなければなりません。
APIにリクエストを送るときの書き方や、音声データをWavファイルに変換するときの書き方が初心者には難しく。。

APIなどはリクエストの書き方が決まっているからコピペでいけるかと思ったものの、そううまくはいかず、、色んな記事を見漁ったり、ChatGPTに聞くなどしてとっても試行錯誤しました。（やはり、コードが何をやっているかを理解するに越したことはないですね。。）

# 全体のコード

それでは、早速全体のコードを貼ります！

```WhisperSpeechToText.cs
//音声を録音、Wavデータに変換してWhisperAPIでテキストに変換
using System;
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using System.Collections.Generic;
using System.IO;
public class WhisperSpeechToText : MonoBehaviour
{
    public int frequency = 16000; // 録音するオーディオクリップの周波数
    public int maxRecordingTime; // 最大録音時間（Inspectorから設定できる）
    [SerializeField] string micName;   //マイクデバイスの名前 nullを入れるとデフォルトのマイクを使用する

    AudioClip clip;
    AudioSource audioSource;
    private float recordingTime;

    void Start()
    {
        if (Microphone.devices.Length == 0)
        {
            Debug.Log("マイクが見つかりません");
            return;
        }

        Debug.Log("Name: " + Microphone.devices[0]);
        //マイク名
        micName = Microphone.devices[0];
    }

    void Update()
    {
        // Increment recording time
        if (IsRecording()) 
        {
            recordingTime += Time.deltaTime;
            // 録音時間が最大録音時間を過ぎてたら録音をストップ
            if (Mathf.FloorToInt(recordingTime) >= maxRecordingTime)
            {
                StopRecording();
            }
        }
    }

    public void StartRecording()
    {
        recordingTime = 0;
        // Stop recording if already recording
        if (IsRecording())
        {
            Microphone.End(null);
        }

        // 録音を開始
        Debug.Log("RecordingStart");
        clip = Microphone.Start(null, true, maxRecordingTime, frequency);

    }

    public bool IsRecording()
    {
        return Microphone.IsRecording(null);
    }

    public void StopRecording() 
    {
        Debug.Log("RecordingStop.");
        // 録音終了
        Microphone.End(null);

        audioSource = gameObject.GetComponent<AudioSource>();
        audioSource.clip = clip;

        // 音声データをWavファイルに変換
        var audioData = WavUtility.FromAudioClip(clip);

        // WhisperAPIに投げてテキストにしてもらう
        StartCoroutine(SendRequest(audioData));
    }

    //WhisperAPIにリクエストするときの処理
    IEnumerator SendRequest(byte[] audioData) 
    {
        string url = "https://api.openai.com/v1/audio/transcriptions";
        string accessToken = "YOUR_API_KEY";

        // Create form data
        var formData = new List<IMultipartFormSection>();
        formData.Add(new MultipartFormDataSection("model", "whisper-1"));
        formData.Add(new MultipartFormDataSection("language", "ja"));
        formData.Add(new MultipartFormFileSection("file", audioData, "audio.mp3", "multipart/form-data"));

        // Create UnityWebRequest object
        using (UnityWebRequest request = UnityWebRequest.Post(url, formData))
        {
            // Set headers
            request.SetRequestHeader("Authorization", "Bearer " + accessToken);

            // Send request
            yield return request.SendWebRequest();

            // Check for errors
            if (request.result != UnityWebRequest.Result.Success) 
            {
                Debug.LogError(request.error);
                yield break;
            }

            // Parse JSON response
            string jsonResponse = request.downloadHandler.text;
            string recognizedText = "";
            try 
            {
                recognizedText = JsonUtility.FromJson<WhisperResponseModel>(jsonResponse).text;
            } 
            catch (System.Exception e) 
            {
                Debug.LogError(e.Message);
            }
            
            Debug.Log("Input Text: " + recognizedText);
            
        }
    }
}

//音声データをWavファイルに変換する時の処理
public static class WavUtility 
{
    public static byte[] FromAudioClip(AudioClip clip)
    {
        using var stream = new MemoryStream();
        using var writer = new BinaryWriter(stream);
        // Write WAV header
        writer.Write(0x46464952); // "RIFF"
        writer.Write(0); // ChunkSize
        writer.Write(0x45564157); // "WAVE"
        writer.Write(0x20746d66); // "fmt "
        writer.Write(16); // Subchunk1Size
        writer.Write((ushort)1); // AudioFormat
        writer.Write((ushort)clip.channels); // NumChannels
        writer.Write(clip.frequency); // SampleRate
        writer.Write(clip.frequency * clip.channels * 2); // ByteRate
        writer.Write((ushort)(clip.channels * 2)); // BlockAlign
        writer.Write((ushort)16); // BitsPerSample
        writer.Write(0x61746164); // "data"
        writer.Write(0); // Subchunk2Size

        // Write audio data
        float[] samples = new float[clip.samples];
        clip.GetData(samples, 0);
        short[] intData = new short[samples.Length];
        for (int i = 0; i < samples.Length; i++) 
        {
            intData[i] = (short)(samples[i] * 32767f);
        }
        byte[] data = new byte[intData.Length * 2];
        Buffer.BlockCopy(intData, 0, data, 0, data.Length);
        writer.Write(data);

        // Update ChunkSize and Subchunk2Size fields
        writer.Seek(4, SeekOrigin.Begin);
        writer.Write((int)(stream.Length - 8));
        writer.Seek(40, SeekOrigin.Begin);
        writer.Write((int)(stream.Length - 44));

        // Close streams and return WAV data
        writer.Close();
        stream.Close();
        return stream.ToArray();
    }
}

public class WhisperResponseModel
{
    public string text;
}
```

Unity　のUIにボタンをふたつ作って、
StartRecordingButtonに”StartRecording”の関数、
StopRecordingButtonに"StopRecording"の関数をアタッチしてください！

accessTokenには、ご自身のOpenAIのアクセストークンを書き込む必要があります。
OpenAIのアカウントやアクセストークン発行の仕方はこちらの記事などを参考にしてみてください。

https://push.co.jp/archives/1147

初心者の私たちにはなかなか難しいコードなのですが。。
とりあえず、AudioClipで宣言したclipという変数に、録音したデータが入ってきます。

```.cs
// 音声データをWavファイルに変換
    var audioData = WavUtility.FromAudioClip(clip);
```

こちらでclipをWavファイルに変換してaudioDataという変数に入れ直し、

```.cs
// WhisperAPIに投げてテキストにしてもらう
    StartCoroutine(SendRequest(audioData));
```
こちらでWavファイル変換されたaudioDataをWhisperAPIに投げているんですね。

ちなみに後半の方でWavUtilityクラスやSendRequuest関数が定義されていますが、正直私もこの処理全てを理解しているわけではありません。。笑

# とりあえず動かしてみようっ

とりあえずこれで再生してみると。。
![3DB87758-1C8E-4551-9099-964546EF09FA.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2635943/82ccd35b-5730-8eba-9302-7d1cad0dff2a.jpeg)
音声がテキストで取得できている！！

やったあ〜〜〜〜！！！！

（外で作業していたので1人で喋ってるの恥ずかしいからあたかも打ち合わせがあるかのようにお疲れ様ですを連呼していたのは秘密）


![7F46149B-3FBA-4BC8-AB4B-8E1733BDBFA0.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2635943/1f28025b-d74b-9ed6-6a6a-bfc1652e0979.jpeg)
こちらInspectorからMax Recording Timeを入れてあげるのを忘れずに！
おそらく0より大きくないとエラーになると思います。。！


# 最後に
こちらのコードに行き着くにあたって、参考にさせていただいた記事をいくつかピックアップさせていただきます！

https://dev.classmethod.jp/articles/chatgpt-daughter-conversation/

https://zenn.dev/dara/scraps/b8e2c8e994b384

https://qiita.com/gyokuro338/items/c46a56549e3991926fb2