Back

Web Audio API を使ったブラウザでの音声録音

Web Audio API を使ったブラウザでの音声録音

多くの開発者は 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 つの段階で構成されます。

  1. キャプチャgetUserMedia() がマイクへのアクセスを要求し、MediaStream を返します。
  2. 処理(任意) — Web Audio API がオーディオノードを通じてストリームを検査または変換します。
  3. 録音MediaRecorder がストリームをファイルにエンコードします。

基本的な録音には Web Audio API は不要です。しかし、ノイズフィルタリング、ゲイン制御、可視化、あるいは AudioWorklet によるカスタムエフェクトが必要な場合は、このチェーンの中間にきれいに組み込めます。

細かいながらも重要な点として、MediaRecorderMediaStream を直接録音します。Web Audio グラフの処理済み出力を録音したい場合は、audioContext.createMediaStreamDestination() を使ってグラフをストリームに戻し、そのノードの .streamMediaRecorder に渡す必要があります。

ステップ 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 ページなど)では NotAllowedErrorSecurityError で呼び出しが直接拒否されます。なお、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() を呼び出す)してください。

ステップ 3: MediaRecorder で録音とエクスポートを行う

MediaRecorderMediaStream を受け取ってエンコードします。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

いいえ。基本的な録音には getUserMedia() と MediaRecorder で十分です。Web Audio API が必要になるのは、フィルターの適用、可視化の構築、AudioWorklet を介したカスタム DSP の実行など、録音前に音声を処理・解析・変換する必要がある場合のみです。

Safari と Chrome ではサポートされる録音フォーマットが異なります。Safari は audio/mp4 を好み、Chrome は WebM/Opus をよく使用します。サポートされていない MIME タイプをハードコードすると、MediaRecorder がエラーを投げたり、使用不能な出力を生成することがあります。実行時に互換フォーマットを検出するため、必ず MediaRecorder.isTypeSupported() を使用してください。

audioContext.createMediaStreamDestination() を使って MediaStreamAudioDestinationNode を作成し、オーディオグラフの最終ノードをそれに接続して、その .stream プロパティを MediaRecorder に渡します。これにより、getUserMedia() から得られる生のマイクストリームではなく、後処理された音声をキャプチャできます。

iOS Safari では、AudioContext はクリックやタッチイベントなどの直接的なユーザー操作内で作成または resume される必要があります。ページ読み込み中にインスタンス化すると suspended 状態のままになります。AudioContext の作成をボタンハンドラー内に移動するか、ハンドラー内で audioContext.resume() を呼び出して再生と処理をアンロックしてください。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay