LoginSignup
2
1

More than 3 years have passed since last update.

WebRTC(SkyWay)でステレオ配信テストやってみた

Last updated at Posted at 2021-03-09

[2021/4/13:追記] ChromeがM91でようやくステレオに対応するようです。
参考) https://bugs.chromium.org/p/webrtc/issues/detail?id=8133

はじめに

WebRTC Platform SkyWayのプリセールスエンジニア、サポートエンジニアのBBこと馬場です。
たまにお客様からステレオ配信がうまく行かない(モノラルになる)というお問い合わせをいただきます。
WebRTC(SkyWay)でステレオ配信やるときには、いくつか注意点があるので、以前行った試験結果をまとめたものを公開します。

なんでモノラルになっちゃうの?

この原因は二つありました。

原因1: Chromeのバグ(?), 仕様(?)問題

Chromeでは gUMConstraintsechoCancellation=true がデフォルトになっているんですが、これがステレオに対応していないらしいです。
- Issue 8133: OPUS stereo audio over RTP is muxed to mono

echoCancellation=false にして、opusのfmtpパラメータに stereo=1 を追加するといいよとのこと。

原因2: SkyWayのSFUの仕様問題

原因1 についてはFirefoxで受ければステレオで再生できるのですが、SkyWayのSFUはステレオに対応していません。SFUでモノラルにMIXされます。そのため、ブラウザに依らずステレオ音源をそのまま配信することができません。

ステレオ配信するために試してみたことと結果

観点

  • echoCancellation=false は有効か
  • Firefoxを使えば問題ないのか
  • sfuを使うとどうなるのか
  • LR音源を複数トラックに分けて、受信側でステレオMIXするやり方は有効か
観点 配信元 配信先 room その他 結果
ベース ChromeM86 ChromeM86 mesh 何もせずそのまま ×モノラル
ベース ChromeM86 ChromeM86 sfu 何もせずそのまま ×モノラル
echoCancellation=false は有効か ChromeM86 ChromeM86 mesh echoCancellation=false わからず
Firefoxを使えば問題ないのか ChromeM86 Firefox68 mesh 何もせずそのまま ○ステレオ
sfuを使うとどうなるのか ChromeM86 Firefox68 SFU 何もせずそのまま ×モノラル
LR音源を複数トラックに分けて配信 ChromeM86 ChromeM86 mesh LRの音源を複数のMediaStreamTrackに分けて送信して、受信側でステレオMIX ○ステレオ

検証内容詳細

自宅にステレオマイクがなかったので、ステレオ音源を <audio> で再生してAudioContextのAPIを使って MediaStream として出力して使いました。
なお、WebRTCの部分はSkyWayのJavaScript SDKを使って実装しています。

Code
const localAudioFile = document.getElementById('js-local-audiofile');
const localStream = await getStereoStream(); 
function getStereoStream(){
  const audioCtx = new(window.AudioContext || window.webkitAudioContext);
  const source = audioCtx.createMediaElementSource(localAudioFile);
  const destination = audioCtx.createMediaStreamDestination();
  source.connect(destination);
  localAudioFile.play();
  return destination.stream;
}

にセットされた音源を、AudioContext.createMediaElementSource() に入力して、AudioContext.createMediaStreamDestination() で作成した出力ノードに接続して、MediaStream として取り出すという感じ。
※ ユーザ操作によるイベント配下におかないと再生されないので注意。
これをそのまま peer.joinRoom() のStreamに渡してやればOKです。

音源は http://www.gatelink.co.jp/hw/etc/audiotest/index.html からお借りしました。

echoCancellation=false は有効か

getUserMediaでステレオ入力がモノラルに変換されてしまう現象の回避@yakan10 によると、echoCancellation: false とすればステレオで再生できるで!と書いていたので、試してみたかったのですが。。。ステレオマイク内からできへんやん!

一応、MediaStreamTrack.applyConstraints(constraints)echoCancellation: false を適用してやってみましたが、やはり、OverconstrainedError がでて反映できずでした。
ステレオマイクないとだめですね。

Error
OverconstrainedError {name: "OverconstrainedError", message: "Cannot satisfy constraints", constraint: ""}
constraint: ""
message: "Cannot satisfy constraints"
name: "OverconstrainedError"
__proto__: OverconstrainedError
Code
const constraints = {
  echoCancellation: false
};

await localStream.getAudioTracks().forEach((track) => {
  track.applyConstraints(constraints)
  .catch(console.error);
});

結論: echoCancellation=false が有効かはステレオマイクがないため検証できませんでした!!!

Firefoxを使えば問題ないのか

この問題は、ChromeとSafariでは確認されているもののFirefoxでは大丈夫とのことだったので、配信先をFirefoxで検証してみました。

  • 配信元: ChromeM86
  • 配信先: Firefox68
  • room: Mesh
  • そのほかはサンプルのまま

問題なく、ステレオで再生されました。

結論: 配信先がFirefoxであればステレオ再生可能

SkyWayのSFUを使うとどうなるのか

先ほどステレオ配信がうまくいった 配信先: Firefox でRoomのモードをSFUにして検証。

  • 配信元: ChromeM86
  • 配信先: Firefox68
  • room: SFU
  • そのほかはサンプルのまま

想定どおりモノラルMIXされて再生されました。

結論: SFUを使うとモノラル再生になる。

LR音源を複数トラックに分けて、受信側でステレオMIXするやり方は有効か

最後に、ステレオ音源そのまま投げてだめなら、マルチストリームでLRの音源を別で送って、受信側でステレオMIXすればいいんじゃない?ということで検証。
配信元にてステレオ音源を audioCtx.createChannelSplitter 使って、それぞれ別のMediaStreamTrackに分けてMediaStreamに乗せて送信し、配信先で audioCtx.createChannelMerger で2chにマージして再生してみました。

Code(配信側)
    const localStream = new MediaStream();
    const localStreamTracks = await getStereoStreamTrack();
    function getStereoStreamTrack(){
      const audioCtx = new(window.AudioContext || window.webkitAudioContext);
      const source = audioCtx.createMediaElementSource(localAudioFile);
      const destinationL = audioCtx.createMediaStreamDestination();
      const destinationR = audioCtx.createMediaStreamDestination();
      const splitter = audioCtx.createChannelSplitter(2);
      source.connect(splitter);
      splitter.connect(destinationL, 0);
      splitter.connect(destinationR, 1);
      localAudioFile.play();
      return [destinationL.stream, destinationR.stream];
    }
    localStream.addTrack(localStreamTracks[0].getTracks()[0]);
    localStream.addTrack(localStreamTracks[1].getTracks()[0]);
Code(受信側)
    room.on('stream', async stream => {
      const audioCtx = new(window.AudioContext || window.webkitAudioContext);
      const dest = audioCtx.createMediaStreamDestination();
      const merger = audioCtx.createChannelMerger(stream.getTracks().length);
      stream.getTracks().forEach((track, index) => {
        const tmpStream = new MediaStream([track]);
        const mutedAudio = new Audio();
        mutedAudio.muted = true;
        mutedAudio.srcObject = tmpStream;
        mutedAudio.play();
        const source = audioCtx.createMediaStreamSource(tmpStream);
        source.connect(merger, 0, index);
      });
      merger.connect(dest);

      const newAudio = document.createElement('audio');
      await newAudio.play().catch(console.error);
    });

Chromeでもステレオで再生されました!

環境は
- 配信元: ChromeM86
- 配信先: ChromeM86
- room: mesh
- LR音源を複数トラックに分けて、受信側でステレオMIX

結論: LR音源を複数トラックに分けて、受信側でステレオMIXする方法は有効!!!

ただし...
SkyWayはマルチストリームに完全には対応していないので注意が必要です。
SFURoomではだめだったり、あとからRoomに入ってきた人は片方しか受け取れなかったりします。
なので、これもかなり限定的な用途でしか使えませんね。
参考) FAQ: マルチストリームに対応していますか?

多分、PeerConnection自体分けて送って合成すればいけるとは思います。

最後に

SkyWayでステレオ配信をするのであればやり方は下記2つくらいが考えられるかなぁという感じです。

  1. MediaConnectionを使って音声だけ配信先にMeshにつないで、映像も必要なら別途Peerを作成してSFURoomで配信する
  2. SFURoomを複数作成して、それぞれにLRの音源を送って、受信側でステレオMIXする。

配信規模的には2のほうが良さそうですが、Room分けちゃうので左右の遅延値のズレがどの程度でるかがきになるところ。
この点に関しては、別途、検証することがあれば公開します。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1