macOS の Firefox で MediaStreamAudioSourceNode 経由の mono MediaStream を PannerNode に通すと OS 出力でモノラル化される
はじめに
ブラウザ間で音声をやり取りする試作を作っているとき、受信側を macOS の Firefox にすると空間音響の左右パンニングが OS 出力に反映されない症状に当たりました。アプリ内に組んだ L/R モニタでは左右に動いています。

OBS のキャプチャ波形と聴感ではモノラルになります。

調査したところ、macOS の Firefox では、mono の MediaStream を MediaStreamAudioSourceNode 経由で PannerNode に通すと、アプリ内 Web Audio チェーンでパンニングが計算されているにもかかわらず、OS のオーディオ出力でモノラル化されることが分かりました。入力ソースの MediaStream を最初からステレオで作っておけば、同じ MediaStreamAudioSourceNode → PannerNode の経路でも回避できます。
検証環境
- macOS
- Firefox 150.0.3 (64-bit)
- Chrome 148.0.7778.168 (arm64)
- OBS Studio でデスクトップ音声をキャプチャ
- 内蔵スピーカで聴感を確認
- 検証日 2026-05-15
対象読者
- Web Audio API で空間音響を試したら、Firefox だけ左右パンニングが効かない症状に当たった人
- WebRTC で受信した音声を
PannerNodeに通している人 - ブラウザの問題だと決め込む前に、最小再現で切り分ける手順を確認したい人
参考
- MediaStreamAudioSourceNode - MDN
- PannerNode - MDN
- ChannelMergerNode - MDN
- Bug 1269019 - opus fmtp stereo=1 does not cause Firefox to send stereo
- Bug 1653259 - Down-mix multichannel input that are more than stereo to mono when applying input processing
最小再現で切り分ける
アプリ内に L/R モニタを組み、同時に OBS でデスクトップ音声をキャプチャすることで、Web Audio チェーン内部と OS 出力の挙動を独立に観察できる状態にしました。
3 つの経路を比べます。
これらに加え、マイク mono 経路の後段で ChannelMerger(2) を挟んでステレオ化した経路 (マイク mono + ChannelMerger 展開) と、getUserMedia の音声処理を切った経路 (マイク mono + 音声処理オフ) も用意しました。各経路を Firefox と Chrome で動かし、パナー X 座標を -3 と +3 に振ったときの L/R モニタと OBS の挙動を比較した結果が次の表です。
| 経路 | アプリ内 L/R モニタ | OBS の波形と聴感 |
|---|---|---|
| OscillatorNode 直結 | 左右に動く | 左右に動く |
| マイク mono 経路 | 左右に動く | モノラル |
| マイク mono + ChannelMerger 展開 | 左右に動く | モノラル |
マイク mono + 音声処理オフ ( echoCancellation: false, noiseSuppression: false, autoGainControl: false, channelCount: 2 ) |
左右に動く | モノラル |
| 合成ステレオ経路 | 左右に動く | 左右に動く |
ここから分かることは次の通りです。
- OscillatorNode 直結が両ブラウザで動いたので、
PannerNodeのパンニング計算自体は Firefox でも動いています。 - マイク mono 経路で症状が再現しました。OscillatorNode 直結との差は「OscillatorNode 直」か「
MediaStreamAudioSourceNode経由」か、ここだけです。 - マイク mono + ChannelMerger 展開 と マイク mono + 音声処理オフ で、後段のステレオ展開や
getUserMediaの音声処理 (AEC / NS / AGC) のオフは効果がありませんでした。macOS 内蔵マイクは mono ハードウェアなのでchannelCount: 2を要求してもsourceChannelCountは 1 のままになります。これらはgetSettings()で実際に確認した値です。
つまり、後段で何をやっても OS 出力モノラル化が解けない、と言えます。
マイク mono 経路の主要部
const micStream = await navigator.mediaDevices.getUserMedia({ audio: true });
const audioCtx = new AudioContext();
const { masterGain, analyserL, analyserR } = buildMasterChain(audioCtx);
const source = audioCtx.createMediaStreamSource(micStream);
const panner = audioCtx.createPanner();
panner.panningModel = 'equalpower';
panner.distanceModel = 'inverse';
source.connect(panner);
panner.connect(masterGain);
// パナー X 座標を動かすたびに panner.positionX 等を更新する
ステレオの MediaStream なら動く
合成ステレオ経路で経路を変えました。マイクは使わず、2 つの OscillatorNode (440Hz と 880Hz) を ChannelMerger(2) でステレオ化し、MediaStreamAudioDestinationNode に流すことで、入力ソースの段階からステレオな MediaStream を作ります。これを MediaStreamAudioSourceNode で受け直して PannerNode に通します。経路上の MediaStreamAudioSourceNode → PannerNode の部分はマイク mono 経路と同一で、入力 MediaStream のチャネル数だけが違います。
合成ステレオ経路の主要部
const oscL = audioCtx.createOscillator();
oscL.frequency.value = 440;
const oscR = audioCtx.createOscillator();
oscR.frequency.value = 880;
const merger = audioCtx.createChannelMerger(2);
oscL.connect(merger, 0, 0);
oscR.connect(merger, 0, 1);
oscL.start();
oscR.start();
const dest = audioCtx.createMediaStreamDestination();
merger.connect(dest);
// ChannelMerger で組み立てたステレオの MediaStream を、再度 MediaStreamAudioSourceNode で受け直す
const source = audioCtx.createMediaStreamSource(dest.stream);
const panner = audioCtx.createPanner();
panner.panningModel = 'equalpower';
panner.distanceModel = 'inverse';
source.connect(panner);
panner.connect(masterGain);
合成ステレオ経路を macOS の Firefox 150.0.3 で動かしたところ、アプリ内 L/R モニタも OBS の波形も左右に振り分けが出ました。つまり、同じ MediaStreamAudioSourceNode → PannerNode の経路でも、入力 MediaStream がステレオなら OS 出力でもパンニングが反映されます。
ここから原因が見えてきます。macOS の Firefox は、MediaStreamAudioSourceNode が mono の MediaStream を受け取った時点で「出力経路を mono に固定する」何らかの内部処理を持っており、後段の Web Audio チェーンでチャネル数を変えても無視している、と推測されます。PannerNode の W3C 仕様では出力は常にステレオと定義されているので、macOS の Firefox の挙動は仕様準拠の問題と言えます。
WebRTC で受け取った音声を空間音響化したい場合、標準の Opus codec はデフォルトで mono です。受信側が macOS の Firefox なら、まさにこの事象に直撃します。アプリ内 L/R モニタだけ見ているとパンニングは効いているように見えますが、OS 出力ではモノラルになります。
まとめ
macOS の Firefox では、mono の MediaStream を MediaStreamAudioSourceNode 経由で PannerNode に通すと、OS 出力でモノラル化される挙動を確認しました。入力ソースの MediaStream を最初からステレオで作っておけば、同じ経路でも回避できます。アプリ内モニタでパンニングが効いているように見えても OS 出力では別の挙動になっている可能性があるため、ブラウザ間の音声を扱う際は両方を独立に観察するのが安全です。
同様の問題に遭遇した方の参考になれば幸いです。なお、本記事の検証は macOS 上の Firefox 150.0.3 のみで実施しているため、他の OS や Firefox バージョンでは挙動が異なる可能性があります。








