Loading
BLOG 開発者ブログ

2019年12月24日

【WebRTC】Unified Plan(Plan A)のトリセツ

WebRTCタイトル

Plan Bのサポートがいよいよ終了するということで、
Unified PlanやRTCRtpTransceiverについて調べてみました。
その使い方、変更点について、VoIP通信系エンジニアなりにまとめてみます。

この記事は アイソルート Advent Calendar 24日目の記事です。
こんにちは。IPコミュニケーショングループのtakeuchi.wです。
通信(主にVoIP関連)が専門です。
#SIP #WebRTC #WebSocket通信
今回は、WebRTCのUnified Planについてまとめてみます。
これからWebRTCを触ってみようという人や、Unified Planへの移行を考えている方は
ご一読いただき、参考にしていただければ幸いです。

もくじ

  • Plan AとPlan B
  • Unified Planを使用する準備
  • Unified PlanのSDP
  • RTCRtpTransceiverの説明書
  • まとめ

Plan AとPlan B

Plan AとPlan Bは、SDPの記述方法に違いがあります。
WebRTCの発足当初はPlan AとPlan Bが混在しており、それらに互換性がなかったのですが、
Plan Aを標準規格(Unified Plan)とすることが決定し、今日に至ります。
Chrome/Safari/Edgeは従来Plan Bを採用してきましたが、
これらのブラウザもUnified Planを標準化し、Plan Bを廃止する方向で動いています。
まもなくChromiumベースのEdgeがリリースされ、EdgeもUnified Planの仲間入りですね。


Unified Planを使用する準備

Unified Planを使うための準備は簡単です。
→ ブラウザを最新にアップデートしましょう。
前述の通り、ブラウザがUnified Planを標準化していますので、特別な対応は不要です。
万が一、強制的にPlan Bで動作させていた場合は、その指定を解除してあげましょう。

// Plan Bで動作させる
new RTCPeerConnection({sdpSemantics: "plan-b"});
// 標準動作に従う
new RTCPeerConnection();

明示的にUnified Planを指定しておきたい場合は以下の通りに指定します。

// 明示的にUnified Planを使用する
new RTCPeerConnection({ sdpSemantics: "unified-plan" });

Unified PlanのSDP

ここからが本題です。
Unified PlanになることでSDPがどう変わるのか、まとめていきます。

[1] a=mid

Plan Bでは、メディア種別(audio/video)ごとにメディア記述セクション(“m=”)を用意し、
そのセクションごとに”a=mid:audio”/”a=mid:video”を設定していました。
複数トラックを使用する場合も、該当の”m=”内に情報を記述します。


a=group:BUNDLE audio video

m=audio xx UDP/TLS/RTP/SAVPF xxx xxx xxx xxx
a=mid:audio

m=video xx UDP/TLS/RTP/SAVPF xxx xxx xxx xxx
a=mid:video

一方、Unified Planの場合、トラックごとに”m=”を用意し、
a=midには(任意の)ユニークな文字列を設定します。(RFC5888上、文字列長の上限は規定なし)
複数トラックを使用する場合は、トラックの数だけ”m=”を記述します。


a=group:BUNDLE 0 1

m=audio x UDP/TLS/RTP/SAVPF xxx xxx xxx xxx
a=mid:0

m=video x UDP/TLS/RTP/SAVPF xxx xxx xxx xxx
a=mid:1

Unified Planでは、この”m=”単位にRTCRtpTransceiverが生成され、セッション管理を行います。
RTCRtpTransceiverでは、送信トラック(RTCRtpSender)と受信トラック(RTCRtpReceiver)とで
対で管理され、SDP上の”a=mid”の値が、RTCRtpTransceiver.midと一致します。
RTCPeerConnectionに対してaddTrack()すると、RTCRtpTransceiver(”m=”)が生成され、
同時にRTCRtpSenderに追加したトラックが登録されます。
相手側のトラック追加は、RTCPeerConnection.ontrackで検出できます。
送信トラックを変更したい場合は、RTCRtpSenderに対してreplaceTrack()をすると、
同じmid(”m=”)を使用したまま送信トラックを付け替えることができます。

// トラック(m=)を追加する
pc.addTrack( new_track );
// トラックを付け替える
var sender = pc.getSenders().find(function(sdr){
 return sdr.track.kind == new_track.kind;
});
sender.replaceTrack( new_track );

不必要にaddTrack()をすると、その分SDPのサイズが膨れ上がっていきますので、
送信する映像を切り替えるような場合は、replaceTrack()を使用するのが良いでしょう。

[2] direction

RTCRtpTransceiver.directionを指定してネゴすることで、
通話相手に送信方向の要望を伝えることができます。
一時的に映像の送信を止めたい場合、また再開したい場合などに使用できます。

  • sendrecv
  • sendonly
  • recvonly
  • inactive

a=group:BUNDLE 0 1

m=audio x UDP/TLS/RTP/SAVPF xxx xxx xxx xxx
a=mid:0
a=sendrecv ←コレ

m=video x UDP/TLS/RTP/SAVPF xxx xxx xxx xxx
a=mid:1
a=recvonly ←コレ

ネゴシエーションの結果(相手の応答結果)はRTCRtpTransceiver.currentDirectionに格納されます。

[3] 不要になったメディア

Unified Planでは、セッションの途中で不要になったメディアがあっても、
(例: ビデオ通話から音声通話に切り替わった場合など)
途中で”m=”セクションを削除することは禁止されています。
前述のdirectionを変更して、inactiveなメディアとして記述を維持します。
そのためUnified Planでは、リモート側のMediaStreamTrack.onended は発火しません。
代わりにMediaStreamTrack.onmuteによって変更を検知することができます。
なお、使用していないメディアは”a=group:BUNDLE”行から削除することができますが、
Offerで指定された”a=group:BUNDLE”行をAnswerで変更してはいけません。


a=group:BUNDLE 0

m=audio x UDP/TLS/RTP/SAVPF xxx xxx xxx xxx
a=mid:0
a=sendrecv

m=video 0 UDP/TLS/RTP/SAVPF
a=mid:1
a=inactive

RTCRtpTransceiverの説明書

Unified Planを扱うには、RTCRtpTransceiverの理解が重要です。

[1] Transceiverの作り方

RTCRtpTransceiverはaddTransceiver()またはaddTrack()で作成されます。

// 音声を受信するためのtransceiverを用意する
pc.addTransceiver('audio', {direction: 'recvonly'});
// 音声を送信するためにtransceiverを作成する
pc.addTrack( new_audio_track );

[2] MediaStreamの取り出し方

MediaStreamを取り出すには、まずRTCRtpTransceiverを参照する必要があります。

// transceiverを配列で取得する
var transceivers = pc.getTransceivers();

音声/映像の通信であれば、transceivers[0]にaudioトラック、
transceivers[1]にvideoトラックのTransceiverが格納されていたりします。
Transceiverの種別は、senderまたはreceiverのtrack.kindから判断できます。
Transceiverからsenderのトラックを集めてくれば、自身が送信しているMediaStreamを生成できます。
Transceiverからreceiverのトラックを集めてくれば、相手から受信しているMediaStreamを生成できます。

// MediaStreamを作成する
var local = new MediaStream;
var remote = new MediaStream;
pc.getTransceivers().forEach(function(trs){
 local.addTrack(trs.sender.track);
 remote.addTrack(trs.receiver.track);
});

[3] MediaStreamの切り替え方

以下の操作の後、再ネゴを走らせれば、MediaStreamを切り替えることができます。
replaceTrack()に関しては再ネゴせずとも出力は切り替えられますが、
再ネゴした方が、相手側で明示的に検出できるので良いかもしれません。
● 付け替える

// トラックを付け替える
var sender = pc.getSenders().find(function(sdr){
 return sdr.track.kind == new_track.kind;
});
sender.replaceTrack( new_track );

● 一時的にON/OFFする

// directionを変更して送信を止める
var transceiver = pc.getTransceivers().find(function(trs){
 return trs.sender.track.kind == kind;
});
transceiver.direction = 'recvonly';

まとめ

Unified Planを扱うには、RTCRtpTransceiverの理解が重要です。
APIを使用する分には、SDPまで意識することはないですが、
裏でどのようなやり取りが行われているかを把握することは、
APIを正しく使うことにもつながります。バックボーンを理解することは大事ですね。

早くも明日はAdvent Calendar最終日、kikuchi.sさん、sugita.mさんの
【チャットボットで業務効率化】申請テンプレートを提示するボットを30分でつくるです!
お楽しみに。

takeuchi.wのブログ