ChatGPT APIでAI音声フィーディング実装|リアルタイムストリーミング完全ガイド【2026年版】
ChatGPT APIで音声ストリーミングを実装する意味
ChatGPT Voice Modeが一般化し、テキストだけでなく音声でAIと対話したいというニーズが急速に高まっています。従来のテキストベースのAPIとは異なり、リアルタイムで音声をストリーミング送受信する実装は、開発者にとって新しいスキルセットとなっています。
このガイドでは、ChatGPT APIを使ってリアルタイム音声フィーディング(音声の入出力)を実装する方法を、実行可能なコード例とともに解説します。完成させれば、ユーザーが話しかけた音声をリアルタイムでAIが理解し、音声で応答するアプリケーションが作成できます。
前提条件と必要な環境
必要なもの
- Node.js 18.0以上(サーバーサイド処理用)
- OpenAI APIキー(ChatGPT APIの利用権)
- ブラウザのWeb Audio API対応(音声キャプチャ用)
- マイク・スピーカーの接続(物理的な音声デバイス)
パッケージのインストール
リアルタイム音声ストリーミングを実装するには、以下のパッケージが必要になります。
npm install openai dotenv express
npm install --save-dev typescript ts-node @types/node
環境変数の設定
APIキーを安全に管理するため、.envファイルを作成してください。
# .env
OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
PORT=3000
ステップ1: サーバーサイドの音声受信エンドポイント構築
ChatGPT APIへの音声ストリーミングをハンドルするバックエンド処理を実装します。Express.jsを使い、クライアントから送信された音声バイナリデータを受け取るエンドポイントを構築します。
// server.js
const express = require('express');
const { OpenAI } = require('openai');
require('dotenv').config();
const app = express();
app.use(express.json());
app.use(express.raw({ type: 'audio/wav', limit: '50mb' }));
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
// 音声をテキストに変換し、ChatGPTで処理するエンドポイント
app.post('/api/voice-chat', async (req, res) => {
try {
// クライアントから送信された音声バイナリデータを取得
const audioBuffer = req.body;
// Whisper APIを使用して音声をテキストに変換
const transcription = await openai.audio.transcriptions.create({
file: new File([audioBuffer], 'audio.wav', { type: 'audio/wav' }),
model: 'whisper-1',
language: 'ja', // 日本語指定
});
const userMessage = transcription.text;
console.log('ユーザーの音声認識結果:', userMessage);
// ChatGPTにテキストを送信
const chatResponse = await openai.chat.completions.create({
model: 'gpt-4',
messages: [
{
role: 'system',
content: 'You are a helpful voice assistant. Respond concisely in Japanese.',
},
{
role: 'user',
content: userMessage,
},
],
});
const responseText = chatResponse.choices[0].message.content;
console.log('ChatGPTの応答:', responseText);
// テキストを音声に変換
const speechResponse = await openai.audio.speech.create({
model: 'tts-1',
voice: 'alloy',
input: responseText,
});
// 音声バイナリデータをクライアントに送信
const audioBuffer2 = await speechResponse.arrayBuffer();
res.setHeader('Content-Type', 'audio/wav');
res.send(Buffer.from(audioBuffer2));
} catch (error) {
console.error('エラー:', error);
res.status(500).json({ error: error.message });
}
});
app.listen(process.env.PORT || 3000, () => {
console.log(`サーバーが http://localhost:${process.env.PORT || 3000} で起動しました`);
});
このエンドポイントは以下の流れで動作します:
- クライアントから音声バイナリデータ(WAV形式)を受け取る
- Whisper APIでそれを日本語テキストに変換
- ChatGPT(gpt-4モデル)にテキストを送信して応答を取得
- Text-to-Speech APIで応答テキストを音声に変換
- 生成された音声をクライアントに返送
ステップ2: クライアント側の音声キャプチャ実装
ブラウザのWeb Audio APIを使用して、ユーザーのマイク入力をキャプチャし、サーバーに送信する処理を実装します。
<!-- index.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI音声アシスタント</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
}
button {
padding: 12px 24px;
font-size: 16px;
cursor: pointer;
border: none;
border-radius: 4px;
background: #0066cc;
color: white;
}
button:hover {
background: #0052a3;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
#status {
margin-top: 20px;
padding: 10px;
border-radius: 4px;
display: none;
}
#status.recording {
display: block;
background: #fff3cd;
color: #856404;
}
#status.processing {
display: block;
background: #cfe2ff;
color: #084298;
}
#status.error {
display: block;
background: #f8d7da;
color: #842029;
}
#transcript {
margin-top: 20px;
padding: 10px;
background: #f5f5f5;
border-radius: 4px;
min-height: 40px;
}
</style>
</head>
<body>
<h1>AI音声アシスタント</h1>
<button id="recordBtn">音声録音開始</button>
<button id="stopBtn" disabled>停止</button>
<div id="status"></div>
<div id="transcript">会話内容がここに表示されます</div>
<script>
let mediaRecorder;
let audioChunks = [];
const recordBtn = document.getElementById('recordBtn');
const stopBtn = document.getElementById('stopBtn');
const statusDiv = document.getElementById('status');
const transcriptDiv = document.getElementById('transcript');
// 音声録音開始
recordBtn.addEventListener('click', async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm' });
audioChunks = [];
mediaRecorder.addEventListener('dataavailable', (event) => {
audioChunks.push(event.data);
});
mediaRecorder.addEventListener('stop', async () => {
// 音声をWAV形式に変換して送信
const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
await sendAudioToServer(audioBlob);
});
mediaRecorder.start();
recordBtn.disabled = true;
stopBtn.disabled = false;
updateStatus('音声を録音中...', 'recording');
} catch (error) {
updateStatus(`マイクアクセスエラー: ${error.message}`, 'error');
}
});
// 音声録音停止
stopBtn.addEventListener('click', () => {
mediaRecorder.stop();
recordBtn.disabled = false;
stopBtn.disabled = true;
updateStatus('', '');
});
// サーバーに音声を送信
async function sendAudioToServer(audioBlob) {
try {
updateStatus('サーバーで処理中...', 'processing');
const response = await fetch('/api/voice-chat', {
method: 'POST',
headers: { 'Content-Type': 'audio/webm' },
body: audioBlob,
});
if (!response.ok) {
throw new Error(`サーバーエラー: ${response.status}`);
}
// サーバーから返された音声を再生
const responseAudio = await response.arrayBuffer();
playAudio(responseAudio);
updateStatus('', '');
} catch (error) {
updateStatus(`送信エラー: ${error.message}`, 'error');
}
}
// 音声を再生
function playAudio(audioBuffer) {
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
audioContext.decodeAudioData(audioBuffer, (buffer) => {
const source = audioContext.createBufferSource();
source.buffer = buffer;
source.connect(audioContext.destination);
source.start(0);
});
}
// ステータスメッセージを更新
function updateStatus(message, className) {
statusDiv.textContent = message;
statusDiv.className = className;
}
</script>
</body>
</html>
このクライアント実装のポイント:
- getUserMedia APIを使ってマイクのアクセスを要求
- MediaRecorder APIで音声ストリーミングを録音
- 音声データをサーバーに送信して処理
- 返ってきた音声データをAudioContextで再生
ステップ3: リアルタイムストリーミングの最適化
上記の基本実装では、音声を録音完了してからサーバーに送信する「バッチ処理」になっています。真の「リアルタイムストリーミング」を実現するには、録音中に音声チャンクを継続的にサーバーに送信する必要があります。
// server-streaming.js(改良版)
const express = require('express');
const { OpenAI } = require('openai');
require('dotenv').config();
const app = express();
app.use(express.raw({ type: 'audio/wav', limit: '50mb' }));
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
// リアルタイムストリーミング用エンドポイント
app.post('/api/voice-stream', async (req, res) => {
try {
const audioBuffer = req.body;
// ストリーミング応答を開始
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// 音声を認識(ストリーミング)
const transcription = await openai.audio.transcriptions.create({
file: new File([audioBuffer], 'audio.wav', { type: 'audio/wav' }),
model: 'whisper-1',
});
// ChatGPTに送信(ストリーミング応答)
const stream = await openai.chat.completions.create({
model: 'gpt-4',
messages: [
{
role: 'user',
content: transcription.text,
},
],
stream: true,
});
// チャンクごとにクライアントに送信
for await (const chunk of stream) {
if (chunk.choices[0].delta.content) {
res.write(`data: ${JSON.stringify({ text: chunk.choices[0].delta.content })}\n\n`);
}
}
res.end();
} catch (error) {
console.error('ストリーミングエラー:', error);
res.status(500).json({ error: error.message });
}
});
app.listen(process.env.PORT || 3000, () => {
console.log(`ストリーミングサーバーが起動しました`);
});
このアプローチでは、ChatGPTの応答をストリーミングで受け取り、テキストが確定する前にクライアントに送信できるため、ユーザー体験が大幅に向上します。
よくあるエラーと対処法
エラー1: 「Whisper APIが音声ファイルを認識できない」
症状
Error: Invalid file format. Supported formats: mp3, mp4, mpeg, mpga, m4a, ogg, opus, flac, wav
原因
Web Audio APIがキャプチャしたaudio/webm形式をそのままWhisper APIに送信している。Whisper APIは特定の形式のみをサポートしています。
対処法 クライアント側でWebMをWAV形式に変換してから送信するか、サーバー側でFFmpegを使って変換してください。
// クライアント側: WebM → WAV変換
async function convertWebMToWav(webmBlob) {
const arrayBuffer = await webmBlob.arrayBuffer();
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
// AudioBufferからWAV形式に変換(ライブラリを使用)
// 例: audiobuffer-to-wav パッケージを使うと簡単
return audioBufferToWav(audioBuffer);
}
エラー2: 「CORS エラーが発生している」
症状
Access to XMLHttpRequest at 'http://localhost:3000/api/voice-chat' from origin 'http://localhost:8000' has been blocked by CORS policy
原因 クライアントとサーバーが異なるオリジン(ポート番号など)で動作しており、ブラウザのCORS保護により通信がブロックされています。
対処法 Express.jsサーバーにCORS設定を追加してください。
const cors = require('cors');
app.use(cors({
origin: 'http://localhost:3000',
credentials: true,
}));
エラー3: 「マイクアクセス権限がない」
症状
NotAllowedError: Permission denied
原因 ユーザーがブラウザのマイクアクセスを拒否している、またはHTTPS環境でないため。
対処法 本番環境ではHTTPSを使用し、ユーザーに許可を再度求めるようUI設計をしてください。
// エラーハンドリング
navigator.mediaDevices.getUserMedia({ audio: true })
.catch(error => {
if (error.name === 'NotAllowedError') {
console.log('ユーザーが許可を拒否しました');
} else if (error.name === 'NotFoundError') {
console.log('マイクが見つかりません');
}
});
応用:複数ユーザーでの同時音声チャット
上記の基本実装を拡張することで、複数ユーザーがリアルタイムで音声チャットできるシステムも構築できます。WebSocketを使ってサーバーとクライアント間の双方向通信を実現し、チャット相手の音声をストリーミング配信すれば、遅延を最小化した音声会話アプリケーションが完成します。
また、OpenAI APIのモデルをカスタマイズすることで、特定の業界(医療、カスタマーサポート、教育など)向けの音声アシスタントも開発可能です。
次のステップ
- 長時間会話への対応:セッション管理を実装し、複数ターンの会話履歴を保持
- 音声の品質向上:ノイズキャンセレーション機能の追加
- 多言語サポート:Whisper APIの言語パラメータを動的に変更
- キャッシング戦略:同じ質問への応答をキャッシュして応答速度を改善
あわせて読みたい
- AIから正確な回答をもらう7つのプロンプト術【Claude・ChatGPT】
- Claude OpusとSonnetを1つのAPIで連携させる方法|コスト削減&高速化
- ClaudeとChatGPTで栄養管理アプリを作る|5ステップ実装ガイド【2026年版】