peer.js(SkyWay版）でH.264によるビデオチャットを行う

# はじめに

Google ChromeがM52から`H.264`サポートを開始しました。すでに対応しているFirefoxと合わせて、やっと利用できる環境が整ってきました。しかし、普通にChromeやFirefoxを利用してもH.264は選択してくれません。WebRTCではシグナリングの過程で、SDPというプロトコルを利用して、ブラウザ同士しゃべることができるコーデックを列挙し、予め設定された優先度に従って最適な物を選択しているからです。

この記事ではSDPを書き換えて、強制的に `H.264` で繋がせる方法を解説します。検証などに役立てば幸いです。

## SDPについて

#### Chrome（52.0.2743.116）で生成したSDP

```
v=0
o=- 7873620119917935879 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS bYvp3dKb7ZtFACFh5eBeicol5WcOcpaEr5UB
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:Xcn23/VduEyHSDiF
a=ice-pwd:bVgn5UOhyAjHWySy9C0otLmi
a=fingerprint:sha-256 47:6D:93:03:82:4A:22:6F:A2:65:4E:26:7E:D9:ED:0E:51:54:8F:D8:EE:DF:62:C1:36:E4:0C:97:49:32:BE:C3
a=setup:actpass
a=mid:audio
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=sendrecv
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:126 telephone-event/8000
a=ssrc:3891783014 cname:W1HJvf2XgnidpBC3
a=ssrc:3891783014 msid:bYvp3dKb7ZtFACFh5eBeicol5WcOcpaEr5UB 3f5313a0-d017-44ba-b677-4a8fdbb02931
a=ssrc:3891783014 mslabel:bYvp3dKb7ZtFACFh5eBeicol5WcOcpaEr5UB
a=ssrc:3891783014 label:3f5313a0-d017-44ba-b677-4a8fdbb02931
m=video 9 UDP/TLS/RTP/SAVPF 100 101 107 116 117 96 97 99 98
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:Xcn23/VduEyHSDiF
a=ice-pwd:bVgn5UOhyAjHWySy9C0otLmi
a=fingerprint:sha-256 47:6D:93:03:82:4A:22:6F:A2:65:4E:26:7E:D9:ED:0E:51:54:8F:D8:EE:DF:62:C1:36:E4:0C:97:49:32:BE:C3
a=setup:actpass
a=mid:video
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:4 urn:3gpp:video-orientation
a=sendrecv
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:100 VP8/90000
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtpmap:101 VP9/90000
a=rtcp-fb:101 ccm fir
a=rtcp-fb:101 nack
a=rtcp-fb:101 nack pli
a=rtcp-fb:101 goog-remb
a=rtcp-fb:101 transport-cc
a=rtpmap:107 H264/90000
a=rtcp-fb:107 ccm fir
a=rtcp-fb:107 nack
a=rtcp-fb:107 nack pli
a=rtcp-fb:107 goog-remb
a=rtcp-fb:107 transport-cc
a=fmtp:107 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:116 red/90000
a=rtpmap:117 ulpfec/90000
a=rtpmap:96 rtx/90000
a=fmtp:96 apt=100
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=101
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=107
a=rtpmap:98 rtx/90000
a=fmtp:98 apt=116
a=ssrc-group:FID 4217613779 2917256657
a=ssrc:4217613779 cname:W1HJvf2XgnidpBC3
a=ssrc:4217613779 msid:bYvp3dKb7ZtFACFh5eBeicol5WcOcpaEr5UB 7a0e0d0a-5d70-4efc-b21a-9fa55e1167bb
a=ssrc:4217613779 mslabel:bYvp3dKb7ZtFACFh5eBeicol5WcOcpaEr5UB
a=ssrc:4217613779 label:7a0e0d0a-5d70-4efc-b21a-9fa55e1167bb
a=ssrc:2917256657 cname:W1HJvf2XgnidpBC3
a=ssrc:2917256657 msid:bYvp3dKb7ZtFACFh5eBeicol5WcOcpaEr5UB 7a0e0d0a-5d70-4efc-b21a-9fa55e1167bb
a=ssrc:2917256657 mslabel:bYvp3dKb7ZtFACFh5eBeicol5WcOcpaEr5UB
a=ssrc:2917256657 label:7a0e0d0a-5d70-4efc-b21a-9fa55e1167bb
```

- `m=video` の最後に並んでいる数値は利用可能なコーデックの番号である。左に行くほど優先度が高い
- `a=rtpmap:xx` / `a=fmtp:xx` のxxは上記のコーデック番号と紐付いている



## Firefox(48.0.3)

```
v=0
o=mozilla...THIS_IS_SDPARTA-48.0.2 3802126275164462711 0 IN IP4 0.0.0.0
s=-
t=0 0
a=fingerprint:sha-256 29:A1:9A:33:9C:E3:2A:B4:44:1F:A3:43:E0:63:F4:F0:0F:08:9D:E7:2A:01:60:55:47:C6:0C:E2:08:73:C7:5C
a=group:BUNDLE sdparta_0 sdparta_1
a=ice-options:trickle
a=msid-semantic:WMS *
m=audio 9 UDP/TLS/RTP/SAVPF 109 9 0 8
c=IN IP4 0.0.0.0
a=sendrecv
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=fmtp:109 maxplaybackrate=48000;stereo=1
a=ice-pwd:822186629cb33e040c095e688fa27f29
a=ice-ufrag:b9198a10
a=mid:sdparta_0
a=msid:{43a74c16-adc8-754f-841c-9b6c20377ca1} {8a30401d-b469-7b45-b89f-ccfc937be996}
a=rtcp-mux
a=rtpmap:109 opus/48000/2
a=rtpmap:9 G722/8000/1
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=setup:actpass
a=ssrc:2837427922 cname:{c5a2b7e1-0cb3-a546-b176-88d22b67064a}
m=video 9 UDP/TLS/RTP/SAVPF 120 126 97
c=IN IP4 0.0.0.0
a=sendrecv
a=fmtp:126 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1
a=fmtp:97 profile-level-id=42e01f;level-asymmetry-allowed=1
a=fmtp:120 max-fs=12288;max-fr=60
a=ice-pwd:822186629cb33e040c095e688fa27f29
a=ice-ufrag:b9198a10
a=mid:sdparta_1
a=msid:{43a74c16-adc8-754f-841c-9b6c20377ca1} {9e466081-83c7-344d-8b28-088a73dc512c}
a=rtcp-fb:120 nack
a=rtcp-fb:120 nack pli
a=rtcp-fb:120 ccm fir
a=rtcp-fb:126 nack
a=rtcp-fb:126 nack pli
a=rtcp-fb:126 ccm fir
a=rtcp-fb:97 nack
a=rtcp-fb:97 nack pli
a=rtcp-fb:97 ccm fir
a=rtcp-mux
a=rtpmap:120 VP8/90000
a=rtpmap:126 H264/90000
a=rtpmap:97 H264/90000
a=setup:actpass
a=ssrc:1210838478 cname:{c5a2b7e1-0cb3-a546-b176-88d22b67064a}
```

- `m=video`の文法はChromeと同じ
- `a=rtcp-fb:xx` / `a=rtpmap:xx` のxxは上記のコーデック番号と紐付いている


## peer.jsでSDPを書き換える


- 640行目辺りに追記

```js
      if(window.webkitRTCPeerConnection){
          var _sdp = offer.sdp;
          _sdp = _sdp.replace("m=video 9 UDP/TLS/RTP/SAVPF 100 101 107 116 117 96 97 99 98","m=video 9 UDP/TLS/RTP/SAVPF 107");
          _sdp = _sdp.replace("a=rtpmap:100 VP8/90000\r\na=rtcp-fb:100 ccm fir\r\na=rtcp-fb:100 nack\r\na=rtcp-fb:100 nack pli\r\na=rtcp-fb:100 goog-remb\r\na=rtcp-fb:100 transport-cc\r\na=rtpmap:101 VP9/90000\r\na=rtcp-fb:101 ccm fir\r\na=rtcp-fb:101 nack\r\na=rtcp-fb:101 nack pli\r\na=rtcp-fb:101 goog-remb\r\na=rtcp-fb:101 transport-cc\r\n","");
          _sdp = _sdp.replace("a=rtpmap:116 red/90000\r\na=rtpmap:117 ulpfec/90000\r\na=rtpmap:96 rtx/90000\r\na=fmtp:96 apt=100\r\na=rtpmap:97 rtx/90000\r\na=fmtp:97 apt=101\r\na=rtpmap:99 rtx/90000\r\na=fmtp:99 apt=107\r\na=rtpmap:98 rtx/90000\r\na=fmtp:98 apt=116\r\n","");
          offer.sdp = _sdp;
      }else if(window.mozRTCPeerConnection){
          var _sdp = offer.sdp;
          _sdp = _sdp.replace("m=video 9 UDP/TLS/RTP/SAVPF 120 126 97","m=video 9 UDP/TLS/RTP/SAVPF 126 97");
          _sdp = _sdp.replace("a=fmtp:120 max-fs=12288;max-fr=60\r\n","");
          _sdp = _sdp.replace("a=rtcp-fb:120 nack\r\na=rtcp-fb:120 nack pli\r\na=rtcp-fb:120 ccm fir\r\n","");
          _sdp = _sdp.replace("a=rtpmap:120 VP8/90000\r\n","");
          offer.sdp = _sdp;
      }

```

- H264以外のコーデックに関する情報を全て削除している
- 上記例ではOffer側のみ書き換えている（これで十分なはず）

## 動作確認

|Offer側|Answer側|結果|
|:--|:--|:--:|
|Chrome|Chrome|◯|
|Firefox|Firefox|◯|
|Chrome|Firefox|△<BR>メディアは通るがエラー発生|
|Firefox|Chrome|◯|

#### Chromeの`chrome://webrtc-internals/`の結果（Firefox→Chroneの場合）

- recv
![recv.png](https://qiita-image-store.s3.amazonaws.com/0/6651/69497a74-ac75-d1c7-38c1-5ae4dce76976.png)

- send
![send.png](https://qiita-image-store.s3.amazonaws.com/0/6651/576e6234-136f-b9c0-5cb0-d1745ddaee45.png)