12k
All articles

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

Web Audio APIでブラウザ録音を実装: getUserMediaでマイク入力を取得し、必要なら処理してMediaRecorderで保存します。

OpenReplay Team
OpenReplay Team
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

ブラウザで音声を録音するには 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() を呼び出して再生と処理をアンロックしてください。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.