はじめに
音声データをリアルタイムで処理するアプリケーションの需要は、音声認識や音声チャットの普及により増加しています。この記事では、OpenAI Realtime APIを活用し、リアルタイム音声データを処理する実装方法を解説します。
特に、Whisperモデルの仕様に基づいた音声データの送信方法、WebSocketを用いた双方向通信、そして問題解決のプロセスについて深掘りします。
背景と目的
このプロジェクトの目標は、音声カウンセリングアプリケーションの開発です。クライアントのマイク入力から送信される音声をリアルタイムで受け取り、OpenAI Realtime APIで解析して結果を返す仕組みを構築することを目的としました。
選んだ技術と理由
- OpenAI Realtime API: 高精度な音声認識と柔軟な会話生成が可能。
- WebSocket: 双方向通信に適しており、リアルタイム音声データ送信に必要。
- Whisperモデル: 24kHzの16-bit PCM Monoフォーマットの音声入力を想定したモデルで、高度な音声認識を提供。
実装概要
以下は、プロジェクトで使用した主な技術とその設定方法について説明します。
使用技術スタック
- Node.js: サーバーサイドの実装。
- WebSocket: クライアントとサーバー間のリアルタイム通信。
- OpenAI Realtime API: Whisperモデルを用いた音声認識。
環境設定
以下の手順で環境をセットアップします。
- Node.jsプロジェクトの初期化:
mkdir realtime-audio-app
cd realtime-audio-app
npm init -y
- 必要なパッケージのインストール:
npm install ws dotenv @openai/realtime-api-beta
.env
ファイルの作成:
OpenAI APIキーを設定します。
OPENAI_API_KEY=your_openai_api_key
実装コード
以下は、リアルタイム音声データを処理するNode.jsサーバーのコードです。
サーバーのセットアップ
import 'dotenv/config';
import { WebSocketServer } from 'ws';
import { RealtimeClient } from '@openai/realtime-api-beta';
const relayServer = new WebSocketServer({ port: 8081 });
let audioBuffer = new Int16Array(0);
console.log('Server initialized on port 8081');
relayServer.on('connection', async (clientSocket) => {
clientSocket.binaryType = 'arraybuffer';
console.log('Client connected.');
const realtimeClient = new RealtimeClient({
apiKey: process.env.OPENAI_API_KEY,
});
// 音声データの送信ロジック
function splitAndSendAudioWithDynamicBuffer(newData) {
const frameSize = 2400;
const combinedBuffer = new Int16Array(audioBuffer.length + newData.length);
combinedBuffer.set(audioBuffer);
combinedBuffer.set(newData, audioBuffer.length);
let i;
for (i = 0; i + frameSize <= combinedBuffer.length; i += frameSize) {
const slice = combinedBuffer.slice(i, i + frameSize);
console.log('Sending Frame:', { length: slice.length, min: Math.min(...slice), max: Math.max(...slice) });
realtimeClient.appendInputAudio(slice);
}
audioBuffer = combinedBuffer.slice(i);
}
function convertBufferToInt16Array(buffer) {
const dataView = new DataView(buffer);
const int16Array = new Int16Array(buffer.byteLength / 2);
for (let i = 0; i < int16Array.length; i++) {
int16Array[i] = dataView.getInt16(i * 2, true);
}
return int16Array;
}
try {
await realtimeClient.connect();
realtimeClient.updateSession({
instructions: 'Provide counseling as the best counselor in the world.',
turn_detection: { type: 'server_vad' },
input_audio_transcription: { model: 'whisper-1' },
});
console.log('Realtime API connected.');
clientSocket.on('message', (message) => {
if (message instanceof ArrayBuffer) {
const int16Array = convertBufferToInt16Array(message);
splitAndSendAudioWithDynamicBuffer(int16Array);
}
});
clientSocket.on('close', async () => {
console.log('Client disconnected.');
await realtimeClient.endInputAudio();
});
realtimeClient.on('conversation.updated', ({ item, delta }) => {
console.log('Response from OpenAI:', item);
});
realtimeClient.on('error', (error) => console.error('Realtime API Error:', error));
} catch (error) {
console.error('Error initializing Realtime API:', error);
}
});
トラブルシューティング
問題: conversation.updated が発火しない
- 原因: 音声データがWhisperの仕様(24kHz, 16-bit PCM, Mono)に適合していない。
- 解決策: 音声データのフレームを適切に分割し、バリデーションを追加。
function validateAudioData(audioData) {
const min = Math.min(...audioData);
const max = Math.max(...audioData);
if (min < -32768 || max > 32767) {
console.error('Audio data out of range:', { min, max });
return false;
}
return true;
}
学んだこと
- リアルタイム音声処理では、データフォーマットの適合性が極めて重要。
- Whisperモデルには2400サンプルのフレームサイズが必要であることを理解。
- WebSocketを用いたリアルタイム通信のデバッグには、データの範囲チェックが有効。
まとめと今後の展望
この記事では、OpenAI Realtime APIとWebSocketを使ったリアルタイム音声処理の基本的な実装方法とトラブルシューティングを紹介しました。今後は、複数クライアント対応や認識精度向上を目指した改善を進めます。
読者の方がこの記事を参考にして、より高度な音声処理アプリケーションを開発することを期待しています!
コメント