Graffity でUnityエンジニアをしているcovaです。
今回はR&DチームでAppleVisionPro にてSpeechToText (音声の文字起こし)を行うためにLLMベースのAIであるGroq を用いて実装してみました。
実装環境
項目 | version |
---|---|
Unity | 6000.0.47f1 |
visionOS | 2.4 |
PolySpatial | 2.1.2 |
Unity-Groq | v0.24.0 |
STEP1. Groq について
Groq は groq 社が提供しているLLMベースのAI基盤です
基本無料でAPI key を作成でき、PythonSDK とREST APIを用意してあります。
groq 側の準備
STEP2. Unity でGroq を利用するには
Unityではcurlの代わりにUnityWebRequestを使えばいいのですが、ヘッダの設定やら色々行わないとうまく疎通できません。
そこで今回はGroq のAPIを簡単に呼べる Unity-Groq
を使って実装を簡略化しました。
(手前味噌ですが、弊社Graffityから出しているOSSになります)
導入自体は日本語DocumentのInstallation通りに行います。
Groq のSpeechToText API
groq 側は入力として .wavファイルを渡せばレスポンスとして文字起こし後のテキストを返却してくれます。
Graffity.Groq.Speech.Transcription class がSpeechToText のAPIラッパーになっているので
こちらを使います。
引数にはwavファイルのファイルパスとAPIKey を設定すればいいので、あとはこのwavファイル本体とファイルパスを用意できれば問題なさそうです
AppleVisionPro のマイク入力
入力の取り方
AppleVisionPro でUnityにおいてマイク入力を受け取るにはMicrophone コンポーネントで行えます。
public class AvpRecordingDemo : MonoBehaviour
{
/// <summary> サンプリングレート。ヘルツ </summary>
private const int SampleRate = 48000;
/// <summary> 生成WAVの既定ファイル名 </summary>
public const string DefaultWavName = "avp_record.wav";
/// <summary> 出力ビット深度 </summary>
private const int BitsPerSample = 16;
/// <summary> モノラルチャンネル数 </summary>
private const int ChannelCount = 1;
/// <summary> 録音秒数 </summary>
[SerializeField]
private float _recordSeconds = 10f;
private async UniTask RecordAndTranscribeAsync(CancellationToken token)
{
// 0番目のマイクを取得
var device = Microphone.devices.Length > 0 ? Microphone.devices[0] : null;
if (string.IsNullOrEmpty(device))
{
Debug.LogError("マイクが見つかりません");
return;
}
try
{
// 録音開始
var audioClip = Microphone.Start(device, false, Mathf.CeilToInt(_recordSeconds), SampleRate);
await UniTask.WaitUntil(() => !Microphone.IsRecording(device), cancellationToken: token);
var wavByteArray = ConvertToWavByteArray(audioClip);
var savedWavPath = Path.Combine(Application.persistentDataPath, DefaultWavName);
await File.WriteAllBytesAsync(savedWavPath, wavByteArray, cancellationToken:token);
}
catch (Exception e)
{
Debug.LogError($"WAV保存失敗: {e.Message}");
}
}
/// <summary>
/// AudioClipを16-bit PCM WAVのバイト配列に変換
/// </summary>
private byte[] ConvertToWavByteArray(AudioClip clip)
{
Debug.Log($"clip.samples: {clip.samples}, clip.channels: {clip.channels}");
var samples = new float[clip.samples * clip.channels];
clip.GetData(samples, 0);
var pcm = new byte[samples.Length * BytesPerSample];
for (int i = 0; i < samples.Length; i++)
{
// 音声振幅を short に変換(-1.0f~+1.0f → -32768~+32767)
var s = (short)Mathf.Clamp(samples[i] * Pcm16MaxAmplitude, short.MinValue, short.MaxValue);
// リトルエンディアン形式で 2バイトに分割して格納(LSB → MSB)
pcm[i * 2] = (byte)(s & 0xFF); // 下位の8bit
pcm[i * 2 + 1] = (byte)((s >> 8) & 0xFF); // 上位の8bit
}
using var ms = new MemoryStream();
WriteWavHeader(ms, ChannelCount, SampleRate, pcm.Length);
ms.Write(pcm, 0, pcm.Length);
var wav = ms.ToArray();
Debug.Log($"生成したWAVバイト数: {wav.Length}");
return wav;
}
/// <summary>
/// WAV ヘッダー (RIFF/WAVE) をストリームに書き込む
/// </summary>
private void WriteWavHeader(Stream stream, int channels, int sampleRate, int dataLength)
{
// 1秒あたりのバイト数
var byteRate = sampleRate * channels * BytesPerSample;
// 1フレームのバイト数
var blockAlign = channels * BytesPerSample;
using var writer = new BinaryWriter(stream, Encoding.UTF8, true);
writer.Write(Encoding.UTF8.GetBytes("RIFF"));
writer.Write(RiffHeaderSize + dataLength);
writer.Write(Encoding.UTF8.GetBytes("WAVE"));
writer.Write(Encoding.UTF8.GetBytes("fmt "));
writer.Write(Subchunk1Size);
writer.Write((short)AudioFormatPcm);
writer.Write((short)channels);
writer.Write(sampleRate);
writer.Write(byteRate);
writer.Write((short)blockAlign);
writer.Write((short)BitsPerSample);
writer.Write(Encoding.UTF8.GetBytes("data"));
writer.Write(dataLength);
}
こんな感じでWavファイルをApplication.persistentDataPath に出力できるScriptを用意します。
Apple Vision Pro 起動直後はマイク入力をうまくとれない場合があるため初回実行は起動直後から数秒待機した後の方が安定します
Groq APIに渡す時は
上のコードだと Path.Combine(Application.persistentDataPath, AvpRecordingDemo.DefaultWavName);
に保存しているので、
Transcription インスタンスの SetFilePath に↑のPathを渡してあげればOKです
最終出力
うまくいくと冒頭のようなSpeechToText が行えます
FAQ
Apple なら Apple Intelligence があるからそれでいいのでは?
A. 現状Unity側がすんなり使えるように環境は整備されていません。
そのため、自作でNativePlugin を書かない限りはUnityで使えないため、今回はREST APIで簡単に扱えるGroqを採用しました。
最後に
Graffity ではAppleVisionPro やMeta Quest3 を用いたAR/MRコンテンツを鋭意製作中ですので、開発に興味のある方は是非ご連絡ください