はじめに
今回は、OpenAIのWhisperAPIを使って、Unityで録音をしたデータをテキストにおこすという実装に挑戦しました!
私の前回のこちらの記事↓の続きです!
続きと言っても、録音の取り方などは前回のコードから持ってきた部分はあるのですが、今回は録音をした後Play(再生)するのではなく、WhisperAPIに渡してテキスト化しているので、若干実装が変わっています。
(スクリプト名も変わっています!)
Unity × WhisperAPIの記事はもういくつか皆様が書いてくださっていて、しかも結構簡単にできるとの旨が書いてあったのですが。。
私はめっっっっっちゃ手間取りました。爆笑
音声データをWhisperAPIに送る時は、まずは音声データをWavファイルに変換しなければなりません。
APIにリクエストを送るときの書き方や、音声データをWavファイルに変換するときの書き方が初心者には難しく。。
APIなどはリクエストの書き方が決まっているからコピペでいけるかと思ったものの、そううまくはいかず、、色んな記事を見漁ったり、ChatGPTに聞くなどしてとっても試行錯誤しました。(やはり、コードが何をやっているかを理解するに越したことはないですね。。)
全体のコード
それでは、早速全体のコードを貼ります!
//音声を録音、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のアカウントやアクセストークン発行の仕方はこちらの記事などを参考にしてみてください。
初心者の私たちにはなかなか難しいコードなのですが。。
とりあえず、AudioClipで宣言したclipという変数に、録音したデータが入ってきます。
// 音声データをWavファイルに変換
var audioData = WavUtility.FromAudioClip(clip);
こちらでclipをWavファイルに変換してaudioDataという変数に入れ直し、
// WhisperAPIに投げてテキストにしてもらう
StartCoroutine(SendRequest(audioData));
こちらでWavファイル変換されたaudioDataをWhisperAPIに投げているんですね。
ちなみに後半の方でWavUtilityクラスやSendRequuest関数が定義されていますが、正直私もこの処理全てを理解しているわけではありません。。笑
とりあえず動かしてみようっ
とりあえずこれで再生してみると。。
音声がテキストで取得できている!!
やったあ〜〜〜〜!!!!
(外で作業していたので1人で喋ってるの恥ずかしいからあたかも打ち合わせがあるかのようにお疲れ様ですを連呼していたのは秘密)
こちらInspectorからMax Recording Timeを入れてあげるのを忘れずに!
おそらく0より大きくないとエラーになると思います。。!
最後に
こちらのコードに行き着くにあたって、参考にさせていただいた記事をいくつかピックアップさせていただきます!