Web Audio API を使ったブラウザでの音声録音
Web Audio APIでブラウザ録音を実装: getUserMediaでマイク入力を取得し、必要なら処理してMediaRecorderで保存します。
多くの開発者は Web Audio API が録音を担うものだと思い込んでいます。しかし、直接的にはそうではありません。Web Audio API は処理とルーティングのエンジンです。実際の録音作業は MediaRecorder API が担当します。この区別を理解することが、誤った実装を避ける最短の道です。
本記事では、ブラウザで音声録音を行うために現在推奨されているアーキテクチャを順を追って解説します。具体的には、getUserMedia() によるマイク入力のキャプチャ、必要に応じた Web Audio グラフ経由でのルーティング、そして MediaRecorder によるエンコードです。
重要なポイント
- Web Audio API は音声を処理・ルーティングするものであり、実際にファイルに録音するのは
MediaRecorderです。 - 完全な録音パイプラインは 3 段階で構成されます。
getUserMedia()によるキャプチャ、Web Audio ノードによる任意の処理、そしてMediaRecorderによるエンコードです。 getUserMedia()には安全なコンテキスト(HTTPS または localhost)が必要であり、ユーザーからの明示的なマイク許可が求められます。- カスタム DSP 処理には
AudioWorkletを使用してください。ScriptProcessorNodeは非推奨であり、負荷時にグリッチを発生させます。 - Safari と Chrome ではサポートされるフォーマットが異なるため、録音前に必ず
MediaRecorder.isTypeSupported()で MIME タイプのサポート状況を確認してください。
ブラウザでの音声録音の実際の仕組み
現代の録音パイプラインは、明確に区別される 3 つの段階で構成されます。
- キャプチャ —
getUserMedia()がマイクへのアクセスを要求し、MediaStreamを返します。 - 処理(任意) — Web Audio API がオーディオノードを通じてストリームを検査または変換します。
- 録音 —
MediaRecorderがストリームをファイルにエンコードします。
基本的な録音には Web Audio API は不要です。しかし、ノイズフィルタリング、ゲイン制御、可視化、あるいは AudioWorklet によるカスタムエフェクトが必要な場合は、このチェーンの中間にきれいに組み込めます。
細かいながらも重要な点として、MediaRecorder は MediaStream を直接録音します。Web Audio グラフの処理済み出力を録音したい場合は、audioContext.createMediaStreamDestination() を使ってグラフをストリームに戻し、そのノードの .stream を MediaRecorder に渡す必要があります。
ステップ 1: getUserMedia でマイクアクセスを要求する
getUserMedia() には、ブラウザがマイクアクセスを許可する前提として、安全なコンテキスト(HTTPS または localhost)と明示的なユーザー許可が必要です。
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
noiseSuppression: true,
sampleRate: 44100
}
});
} catch (err) {
console.error('Microphone access failed:', err.name, err.message);
}
これは必ず try/catch で囲んでください。ユーザーが許可を拒否することもあり、また一部の環境(HTTP ページなど)では NotAllowedError や SecurityError で呼び出しが直接拒否されます。なお、sampleRate のような制約はあくまでヒントであり、基盤となるハードウェアによってはブラウザがそれを無視することがあります。
ステップ 2: Web Audio API 経由でルーティングする(任意)
録音前に音声を解析または処理する必要がある場合は、AudioContext を作成してストリームをそこに通します。
const audioContext = new AudioContext();
const source = audioContext.createMediaStreamSource(stream);
// 例: 可視化のために AnalyserNode に接続
const analyser = audioContext.createAnalyser();
source.connect(analyser);
// 処理済み出力を録音するには MediaStreamDestination にルーティング
const destination = audioContext.createMediaStreamDestination();
analyser.connect(destination);
// destination.stream を MediaRecorder に渡す
ノイズゲートやリアルタイムレベルメーターのようなカスタム処理には、非推奨の ScriptProcessorNode ではなく AudioWorklet を使用してください。AudioWorklet はメインスレッド外で実行されるため、UI をブロックしたり、負荷時にオーディオサンプルを取りこぼすことがありません。
注意: iOS Safari では、
AudioContextは直接的なユーザー操作によってトリガーされるまで suspended 状態で起動します。ページ読み込み時ではなく、ボタンクリックハンドラー内で作成(またはaudioContext.resume()を呼び出す)してください。
Discover how at OpenReplay.com.
ステップ 3: MediaRecorder で録音とエクスポートを行う
MediaRecorder は MediaStream を受け取ってエンコードします。MIME タイプはハードコードせず、まずサポート状況を確認してください。
function pickMimeType() {
const candidates = [
'audio/webm;codecs=opus',
'audio/ogg;codecs=opus',
'audio/mp4'
];
return candidates.find(type => MediaRecorder.isTypeSupported(type)) || '';
}
const mimeType = pickMimeType();
const recorder = new MediaRecorder(stream, mimeType ? { mimeType } : undefined);
const chunks = [];
recorder.ondataavailable = (e) => {
if (e.data.size > 0) chunks.push(e.data);
};
recorder.onstop = () => {
const blob = new Blob(chunks, {
type: recorder.mimeType || mimeType || chunks[0]?.type || 'audio/webm'
});
const url = URL.createObjectURL(blob);
document.querySelector('audio').src = url;
};
recorder.start();
WebM/Opus は、現代的なブラウザにおいて通常最良のデフォルトです。ファイルサイズが小さく、音質も優れています。Safari はしばしば audio/mp4 を好みます。候補タイプのリストをフォールバックさせ、ブラウザが対応する最初のものを選ぶ方式が、最も信頼性の高い戦略です。MediaRecorder および関連フォーマットの現在のブラウザサポート状況は Can I Use で確認できます。
MediaRecorder と Web Audio API: クイックリファレンス
| 必要な機能 | 使用するもの |
|---|---|
| シンプルなマイク録音 | getUserMedia + MediaRecorder |
| リアルタイムのエフェクトやフィルター | Web Audio API ノード |
| カスタム DSP 処理 | AudioWorklet |
| 音声の可視化 | AnalyserNode |
| ファイルへのエンコード | MediaRecorder |
避けるべきこと
ScriptProcessorNode— 非推奨で、メインスレッドで動作し、負荷時にオーディオグリッチを引き起こします。- MIME タイプのハードコード — Safari や古い Firefox で気付かないうちに失敗します。
- ページ読み込み時の
AudioContext作成 — ブラウザはユーザー操作が発生するまで suspend するため、イベントハンドラー内で resume してください。 - ストリーム停止の忘れ — 終了時に
stream.getTracks().forEach(t => t.stop())を呼び出さないと、マイクのインジケーターが点灯したままになります。
まとめ
ブラウザでの音声録音は 2 つの API で完結します。getUserMedia() と MediaRecorder がキャプチャとエンコードを担当し、Web Audio API はその間のすべてを担います。要件を満たす最もシンプルなパイプラインから始め、必要なときだけ Web Audio 処理を追加し、MediaRecorder を構成する前には常に MIME タイプのサポートを確認してください。アーキテクチャ全体を一文に凝縮するなら、これに尽きます。
FAQ
ブラウザで音声を録音するには Web Audio API が必要ですか?
いいえ。基本的な録音には getUserMedia() と MediaRecorder で十分です。Web Audio API が必要になるのは、フィルターの適用、可視化の構築、AudioWorklet を介したカスタム DSP の実行など、録音前に音声を処理・解析・変換する必要がある場合のみです。
なぜ Safari では録音が失敗し、Chrome では動作するのですか?
Safari と Chrome ではサポートされる録音フォーマットが異なります。Safari は audio/mp4 を好み、Chrome は WebM/Opus をよく使用します。サポートされていない MIME タイプをハードコードすると、MediaRecorder がエラーを投げたり、使用不能な出力を生成することがあります。実行時に互換フォーマットを検出するため、必ず MediaRecorder.isTypeSupported() を使用してください。
生のマイク入力ではなく、Web Audio グラフの処理済み出力を録音するにはどうすればよいですか?
audioContext.createMediaStreamDestination() を使って MediaStreamAudioDestinationNode を作成し、オーディオグラフの最終ノードをそれに接続して、その .stream プロパティを MediaRecorder に渡します。これにより、getUserMedia() から得られる生のマイクストリームではなく、後処理された音声をキャプチャできます。
iOS で AudioContext が無音だったり起動しなかったりするのはなぜですか?
iOS Safari では、AudioContext はクリックやタッチイベントなどの直接的なユーザー操作内で作成または resume される必要があります。ページ読み込み中にインスタンス化すると suspended 状態のままになります。AudioContext の作成をボタンハンドラー内に移動するか、ハンドラー内で audioContext.resume() を呼び出して再生と処理をアンロックしてください。