ããã³ããšã³ãWebRTCã³ãŒããã¯ããŽã·ãšãŒã·ã§ã³ã®å æ¬çã¬ã€ããSDPãåªå ã³ãŒããã¯ããã©ãŠã¶äºææ§ããªã¢ã«ã¿ã€ã éä¿¡ã¢ããªã§æé©ãªé³å£°ã»æ åå質ãå®çŸããããã®ãã¹ããã©ã¯ãã£ã¹ã解説ããŸãã
ããã³ããšã³ãWebRTCã³ãŒããã¯éžæïŒã¡ãã£ã¢ã³ãŒããã¯ããŽã·ãšãŒã·ã§ã³ããã¹ã¿ãŒãã
WebRTCïŒWeb Real-Time CommunicationïŒã¯ããŠã§ããã©ãŠã¶å ã§çŽæ¥ãªã¢ã«ã¿ã€ã ã®é³å£°ãšæ åãå¯èœã«ããããšã§ããªã³ã©ã€ã³ã³ãã¥ãã±ãŒã·ã§ã³ã«é©åœããããããŸããããããã倿§ãªãããã¯ãŒã¯ç¶æ³ãããã€ã¹éã§æé©ãªéä¿¡å質ãéæããã«ã¯ãã¡ãã£ã¢ã³ãŒããã¯ãšãã®ããŽã·ãšãŒã·ã§ã³ããã»ã¹ãæ éã«æ€èšããå¿ èŠããããŸãããã®å æ¬çãªã¬ã€ãã§ã¯ãããã³ããšã³ãWebRTCã³ãŒããã¯éžæã®è€éããæãäžããã»ãã·ã§ã³èšè¿°ãããã³ã«ïŒSDPïŒã®åºæ¬ååãåªå ã³ãŒããã¯ã®èšå®ããã©ãŠã¶ã®äºææ§ã®åŸ®åŠãªéãããããŠäžçäžã®ãŠãŒã¶ãŒã«ã·ãŒã ã¬ã¹ã§é«å質ãªãªã¢ã«ã¿ã€ã äœéšãä¿èšŒããããã®ãã¹ããã©ã¯ãã£ã¹ãæ¢ããŸãã
WebRTCãšã³ãŒããã¯ã®çè§£
WebRTCã¯ããã©ãŠã¶ãïŒåææ¥ç¶èšå®ã«ã¯ã·ã°ããªã³ã°ãµãŒããŒã䜿çšãããŸããïŒäžéãµãŒããŒãå¿ èŠãšããã«ããã¢ããŒãã¢ã§çŽæ¥éä¿¡ããããšãå¯èœã«ããŸããWebRTCã®äžæ žã«ã¯ãé³å£°ããã³æ åã¹ããªãŒã ããšã³ã³ãŒãïŒå§çž®ïŒããã³ãã³ãŒãïŒè§£åïŒããèœåããããããã«ããã€ã³ã¿ãŒãããçµç±ã§ã®éä¿¡ã«é©ãããã®ã«ãªããŸããããã§ã³ãŒããã¯ãç»å ŽããŸããã³ãŒããã¯ïŒã³ãŒããŒã»ãã³ãŒããŒïŒã¯ããã®ãšã³ã³ãŒãããã³ãã³ãŒãåŠçãå®è¡ããã¢ã«ãŽãªãºã ã§ããã³ãŒããã¯ã®éžæã¯ã垯åå¹ ã®äœ¿çšéãåŠçèœåããããŠæçµçã«ã¯é³å£°ããã³æ åã¹ããªãŒã ã®ç¥èŠå質ã«å€§ãã圱é¿ããŸãã
é«å質ãªWebRTCã¢ããªã±ãŒã·ã§ã³ãäœæããããã«ã¯ãé©åãªã³ãŒããã¯ãéžæããããšãæãéèŠã§ããç°ãªãã³ãŒããã¯ã«ã¯ãããããç°ãªãé·æãšçæããããŸãïŒ
- Opus: éåžžã«æ±çšæ§ãé«ããåºããµããŒããããŠããé³å£°ã³ãŒããã¯ã§ãäœãããã¬ãŒãã§ãåªããå質ã§ç¥ãããŠããŸããWebRTCã®ã»ãšãã©ã®é³å£°ã¢ããªã±ãŒã·ã§ã³ã§æšå¥šãããéžæè¢ã§ãã
- VP8: ãã€ã€ãªãã£ããªãŒã®æ åã³ãŒããã¯ã§ãWebRTCã«ãããŠæŽå²çã«éèŠã§ãããŸã ãµããŒããããŠããŸãããVP9ãAV1ã®æ¹ãå§çž®å¹çã¯åªããŠããŸãã
- VP9: VP8ãããåªããå§çž®ãæäŸãããããé«åºŠãªãã€ã€ãªãã£ããªãŒã®æ åã³ãŒããã¯ã§ã垯åå¹ ã®æ¶è²»ãæããå質ãåäžãããŸãã
- H.264: åºãå®è£ ãããŠããæ åã³ãŒããã¯ã§ãå€ãã®ããã€ã¹ã§ããŒããŠã§ã¢ã¢ã¯ã»ã©ã¬ãŒã·ã§ã³ãå©çšã§ããŸãããã ããã©ã€ã»ã³ã¹ãè€éãªå ŽåããããŸããH.264ã䜿çšããå Žåã¯ãã©ã€ã»ã³ã¹çŸ©åãçè§£ããããšãäžå¯æ¬ ã§ãã
- AV1: ææ°ãã€æãå é²çãªãã€ã€ãªãã£ããªãŒã®æ åã³ãŒããã¯ã§ãVP9ãããããã«åªããå§çž®ãçŽæããŸãããã©ãŠã¶ã®ãµããŒãã¯ãŸã é²åäžã§ãããæ¥éã«æ¡å€§ããŠããŸãã
SDPïŒã»ãã·ã§ã³èšè¿°ãããã³ã«ïŒã®åœ¹å²
ãã¢ãé³å£°ãšæ åã亀æããåã«ã䜿çšããã³ãŒããã¯ã«ã€ããŠåæããå¿ èŠããããŸãããã®åæã¯ãã»ãã·ã§ã³èšè¿°ãããã³ã«ïŒSDPïŒãéããŠä¿é²ãããŸããSDPã¯ããµããŒããããŠããã³ãŒããã¯ãã¡ãã£ã¢ã¿ã€ãïŒé³å£°ãæ åïŒããã©ã³ã¹ããŒããããã³ã«ããã®ä»ã®é¢é£ãã©ã¡ãŒã¿ãªã©ããã«ãã¡ãã£ã¢ã»ãã·ã§ã³ã®ç¹æ§ãèšè¿°ããããã¹ãããŒã¹ã®ãããã³ã«ã§ããããã¯ããã¢éã®æ¡æã®ãããªãã®ã§ãåèªãèœåã宣èšããçžäºã«åæå¯èœãªèšå®ã亀æžããŸãã
WebRTCã§ã¯ãSDP亀æã¯éåžžãã·ã°ããªã³ã°ãµãŒããŒã«ãã£ãŠèª¿æŽãããã·ã°ããªã³ã°ããã»ã¹äžã«è¡ãããŸãããã®ããã»ã¹ã«ã¯ãäžè¬çã«ä»¥äžã®ã¹ããããå«ãŸããŸãïŒ
- ãªãã¡ãŒã®äœæïŒ äžæ¹ã®ãã¢ïŒãªãã¡ãŒåŽïŒããèªèº«ã®ã¡ãã£ã¢èœåãšåªå ã³ãŒããã¯ãèšè¿°ããSDPãªãã¡ãŒãäœæããŸãããã®ãªãã¡ãŒã¯æååãšããŠãšã³ã³ãŒããããŸãã
- ã·ã°ããªã³ã°ïŒ ãªãã¡ãŒåŽã¯ãã·ã°ããªã³ã°ãµãŒããŒãä»ããŠããäžæ¹ã®ãã¢ïŒã¢ã³ãµãŒåŽïŒã«SDPãªãã¡ãŒãéä¿¡ããŸãã
- ã¢ã³ãµãŒã®äœæïŒ ã¢ã³ãµãŒåŽã¯ãªãã¡ãŒãåãåãããªãã¡ãŒã®äžããèªèº«ããµããŒãããã³ãŒããã¯ãšãã©ã¡ãŒã¿ãéžæããŠSDPã¢ã³ãµãŒãäœæããŸãã
- ã·ã°ããªã³ã°ïŒ ã¢ã³ãµãŒåŽã¯ãã·ã°ããªã³ã°ãµãŒããŒãä»ããŠãªãã¡ãŒåŽã«SDPã¢ã³ãµãŒãè¿éããŸãã
- æ¥ç¶ã®ç¢ºç«ïŒ ããã§äž¡æ¹ã®ãã¢ã¯ãWebRTCæ¥ç¶ã確ç«ããã¡ãã£ã¢ã®äº€æãéå§ããããã«å¿ èŠãªSDPæ å ±ãæã¡ãŸãã
SDPã®æ§é ãšäž»èŠãªå±æ§
SDPã¯ãåè¡ã«å±æ§ãšå€ã®ãã¢ã䞊ã¶åœ¢åŒã§æ§æãããŠããŸããã³ãŒããã¯ããŽã·ãšãŒã·ã§ã³ã«ãšã£ãŠæãéèŠãªå±æ§ã«ã¯ã以äžã®ãããªãã®ããããŸãïŒ
- v= (ãããã³ã«ããŒãžã§ã³): SDPã®ããŒãžã§ã³ãæå®ããŸããé垞㯠`v=0` ã§ãã
- o= (ãªãªãžã³): ãŠãŒã¶ãŒåãã»ãã·ã§ã³IDãããŒãžã§ã³ãªã©ãã»ãã·ã§ã³ã®åµå§è ã«é¢ããæ å ±ãå«ã¿ãŸãã
- s= (ã»ãã·ã§ã³å): ã»ãã·ã§ã³ã®èª¬æãæäŸããŸãã
- m= (ã¡ãã£ã¢èšè¿°): ã¡ãã£ã¢ã¿ã€ããããŒãããããã³ã«ããã©ãŒããããªã¹ããªã©ãã¡ãã£ã¢ã¹ããªãŒã ïŒé³å£°ãŸãã¯æ åïŒãèšè¿°ããŸãã
- a=rtpmap: (RTPããã): ãã€ããŒãã¿ã€ãçªå·ãç¹å®ã®ã³ãŒããã¯ãã¯ããã¯ã¬ãŒããããã³ãªãã·ã§ã³ã®ãã©ã¡ãŒã¿ã«ãããã³ã°ããŸããäŸïŒ`a=rtpmap:0 PCMU/8000` ã¯ããã€ããŒãã¿ã€ã0ãã¯ããã¯ã¬ãŒã8000 Hzã®PCMUé³å£°ã³ãŒããã¯ã衚ãããšã瀺ããŸãã
- a=fmtp: (ãã©ãŒããããã©ã¡ãŒã¿): ã³ãŒããã¯åºæã®ãã©ã¡ãŒã¿ãæå®ããŸããäŸãã°ãOpusã®å Žåãããã«ã¯ `stereo` ã `sprop-stereo` ãã©ã¡ãŒã¿ãå«ãŸããããšããããŸãã
- a=rtcp-fb: (RTCPãã£ãŒãããã¯): 茻茳å¶åŸ¡ãå質é©å¿ã«äžå¯æ¬ ãªãªã¢ã«ã¿ã€ã ãã©ã³ã¹ããŒãå¶åŸ¡ãããã³ã«ïŒRTCPïŒãã£ãŒãããã¯ã¡ã«ããºã ã®ãµããŒãã瀺ããŸãã
以äžã¯ãOpusãåªå ããé³å£°çšã®SDPãªãã¡ãŒã®ç°¡ç¥åãããäŸã§ãïŒ
v=0 o=- 1234567890 2 IN IP4 127.0.0.1 s=WebRTC Session t=0 0 m=audio 9 UDP/TLS/RTP/SAVPF 111 0 a=rtpmap:111 opus/48000/2 a=fmtp:111 minptime=10;useinbandfec=1 a=rtpmap:0 PCMU/8000 a=ptime:20 a=maxptime:60
ãã®äŸã§ã¯ïŒ
- `m=audio 9 UDP/TLS/RTP/SAVPF 111 0` ã¯ãRTP/SAVPFãããã³ã«ã䜿çšãããã€ããŒãã¿ã€ã111ïŒOpusïŒãš0ïŒPCMUïŒãæã€é³å£°ã¹ããªãŒã ã瀺ããŸãã
- `a=rtpmap:111 opus/48000/2` ã¯ããã€ããŒãã¿ã€ã111ãã¯ããã¯ã¬ãŒã48000 Hzã2ãã£ã³ãã«ïŒã¹ãã¬ãªïŒã®Opusã³ãŒããã¯ãšããŠå®çŸ©ããŸãã
- `a=rtpmap:0 PCMU/8000` ã¯ããã€ããŒãã¿ã€ã0ãã¯ããã¯ã¬ãŒã8000 HzïŒã¢ãã©ã«ïŒã®PCMUã³ãŒããã¯ãšããŠå®çŸ©ããŸãã
ããã³ããšã³ãã§ã®ã³ãŒããã¯éžæãã¯ããã¯
ãã©ãŠã¶ãSDPã®çæãšããŽã·ãšãŒã·ã§ã³ã®å€ããåŠçããŸãããããã³ããšã³ãéçºè ã«ã¯ã³ãŒããã¯éžæããã»ã¹ã«åœ±é¿ãäžããããã€ãã®ãã¯ããã¯ããããŸãã
1. ã¡ãã£ã¢å¶çŽ
ããã³ããšã³ãã§ã³ãŒããã¯éžæã«åœ±é¿ãäžããäž»ãªæ¹æ³ã¯ã`getUserMedia()` ãåŒã³åºãã `RTCPeerConnection` ãäœæããéã®ã¡ãã£ã¢å¶çŽãéããŠã§ããã¡ãã£ã¢å¶çŽã«ãããé³å£°ããã³æ åãã©ãã¯ã®æãŸããããããã£ãæå®ã§ããŸããæšæºã®å¶çŽã§ã¯ã³ãŒããã¯ãååã§çŽæ¥æå®ããããšã¯ã§ããŸããããç¹å®ã®ã³ãŒããã¯ã«æå©ãªä»ã®ããããã£ãæå®ããããšã§éžæã«åœ±é¿ãäžããããšãã§ããŸãã
äŸãã°ãããé«å質ãªé³å£°ãåªå ããããã«ã¯ã次ã®ãããªå¶çŽã䜿çšããããšããããŸãïŒ
const constraints = {
audio: {
echoCancellation: true,
noiseSuppression: true,
sampleRate: 48000, // é«ããµã³ãã«ã¬ãŒãã¯Opusã®ãããªã³ãŒããã¯ãåªå
ããŸã
channelCount: 2, // ã¹ãã¬ãªé³å£°
},
video: {
width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 480, ideal: 720, max: 1080 },
frameRate: { min: 24, ideal: 30, max: 60 },
}
};
navigator.mediaDevices.getUserMedia(constraints)
.then(stream => { /* ... */ })
.catch(error => { console.error("Error getting user media:", error); });
é³å£°ã«å¯ŸããŠããé«ã `sampleRate`ïŒ48000 HzïŒãæå®ããããšã§ãå€ãã³ãŒããã¯ïŒãã°ãã°8000 Hzã䜿çšããPCMU/PCMAãªã©ïŒãããé«ããµã³ãã«ã¬ãŒãã§åäœããOpusã®ãããªã³ãŒããã¯ããã©ãŠã¶ãéžæããããã«éæ¥çã«ä¿ããŸããåæ§ã«ã`width`ã`height`ã`frameRate` ã®ãããªæ åå¶çŽãæå®ããããšãããã©ãŠã¶ã®æ åã³ãŒããã¯éžæã«åœ±é¿ãäžããããšãã§ããŸãã
ãã©ãŠã¶ããããã®å¶çŽãæ£ç¢ºã«æºããããšã*ä¿èšŒãããŠãã*ããã§ã¯ãªãããšã«æ³šæããããšãéèŠã§ããå©çšå¯èœãªããŒããŠã§ã¢ãšã³ãŒããã¯ã®ãµããŒãã«åºã¥ããŠãã§ããéããããã«åãããããšããŸãã`ideal` å€ã¯ãã©ãŠã¶ã«å¥œã¿ãäŒãããã³ããæäŸãã`min` ãš `max` ã¯èš±å®¹ç¯å²ãå®çŸ©ããŸãã
2. SDPæäœïŒé«åºŠãªãã¯ããã¯ïŒ
ãããã现ããªå¶åŸ¡ãè¡ãã«ã¯ãSDPã®ãªãã¡ãŒãšã¢ã³ãµãŒã®æååã亀æãããåã«çŽæ¥æäœããããšãã§ããŸãããã®ãã¯ããã¯ã¯é«åºŠãªãã®ãšèŠãªãããSDPæ§æã®å®å šãªçè§£ãå¿ èŠã§ããããããããã«ããã³ãŒããã¯ã®é åºã倿ŽããããäžèŠãªã³ãŒããã¯ãåé€ããããã³ãŒããã¯åºæã®ãã©ã¡ãŒã¿ãä¿®æ£ãããããããšãå¯èœã«ãªããŸãã
éèŠãªã»ãã¥ãªãã£äžã®èæ ®äºé ïŒ SDPã®å€æŽã¯ãæ éã«è¡ããªããšã»ãã¥ãªãã£äžã®è匱æ§ãåŒãèµ·ããå¯èœæ§ããããŸããã€ã³ãžã§ã¯ã·ã§ã³æ»æããã®ä»ã®ã»ãã¥ãªãã£ãªã¹ã¯ãé²ãããã«ãSDPã®å€æŽã¯åžžã«æ€èšŒããã³ãµãã¿ã€ãºããŠãã ããã
以äžã¯ãSDPæååå ã®ã³ãŒããã¯ã®é åºã倿Žããç¹å®ã®ã³ãŒããã¯ïŒäŸïŒé³å£°çšã®OpusïŒãåªå ããæ¹æ³ã瀺ãJavaScript颿°ã§ãïŒ
function prioritizeCodec(sdp, codec, mediaType) {
const lines = sdp.split('\n');
let rtpmapLine = null;
let fmtpLine = null;
let rtcpFbLines = [];
let mediaDescriptionLineIndex = -1;
// ã³ãŒããã¯ã®rtpmapãfmtpãrtcp-fbè¡ãšã¡ãã£ã¢èšè¿°è¡ãèŠã€ããŸãã
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith('m=' + mediaType)) {
mediaDescriptionLineIndex = i;
} else if (lines[i].startsWith('a=rtpmap:') && lines[i].includes(codec + '/')) {
rtpmapLine = lines[i];
} else if (lines[i].startsWith('a=fmtp:') && lines[i].includes(codec)) {
fmtpLine = lines[i];
} else if (lines[i].startsWith('a=rtcp-fb:') && rtpmapLine && lines[i].includes(rtpmapLine.split(' ')[1])){
rtcpFbLines.push(lines[i]);
}
}
if (rtpmapLine) {
// ã¡ãã£ã¢èšè¿°è¡ã®ãã©ãŒããããªã¹ãããã³ãŒããã¯ãåé€ããŸãã
const mediaDescriptionLine = lines[mediaDescriptionLineIndex];
const formatList = mediaDescriptionLine.split(' ')[3].split(' ');
const codecPayloadType = rtpmapLine.split(' ')[1];
const newFormatList = formatList.filter(pt => pt !== codecPayloadType);
lines[mediaDescriptionLineIndex] = mediaDescriptionLine.replace(formatList.join(' '), newFormatList.join(' '));
// ãã©ãŒããããªã¹ãã®å
é ã«ã³ãŒããã¯ã远å ããŸã
lines[mediaDescriptionLineIndex] = lines[mediaDescriptionLineIndex].replace('m=' + mediaType, 'm=' + mediaType + ' ' + codecPayloadType);
// rtpmapãfmtpãrtcp-fbè¡ãã¡ãã£ã¢èšè¿°è¡ã®åŸã«ç§»åããŸãã
lines.splice(mediaDescriptionLineIndex + 1, 0, rtpmapLine);
if (fmtpLine) {
lines.splice(mediaDescriptionLineIndex + 2, 0, fmtpLine);
}
for(let i = 0; i < rtcpFbLines.length; i++) {
lines.splice(mediaDescriptionLineIndex + 3 + i, 0, rtcpFbLines[i]);
}
// å
ã®è¡ãåé€ããŸã
let indexToRemove = lines.indexOf(rtpmapLine, mediaDescriptionLineIndex + 1); // æ¿å
¥åŸã«æ€çŽ¢ãéå§ããŸã
if (indexToRemove > -1) {
lines.splice(indexToRemove, 1);
}
if (fmtpLine) {
indexToRemove = lines.indexOf(fmtpLine, mediaDescriptionLineIndex + 1); // æ¿å
¥åŸã«æ€çŽ¢ãéå§ããŸã
if (indexToRemove > -1) {
lines.splice(indexToRemove, 1);
}
}
for(let i = 0; i < rtcpFbLines.length; i++) {
indexToRemove = lines.indexOf(rtcpFbLines[i], mediaDescriptionLineIndex + 1); // æ¿å
¥åŸã«æ€çŽ¢ãéå§ããŸã
if (indexToRemove > -1) {
lines.splice(indexToRemove, 1);
}
}
return lines.join('\n');
} else {
return sdp;
}
}
// 䜿çšäŸïŒ
const pc = new RTCPeerConnection();
pc.createOffer()
.then(offer => {
let sdp = offer.sdp;
console.log("Original SDP:\n", sdp);
let modifiedSdp = prioritizeCodec(sdp, 'opus', 'audio');
console.log("Modified SDP:\n", modifiedSdp);
offer.sdp = modifiedSdp; // 倿ŽãããSDPã§ãªãã¡ãŒãæŽæ°ããŸã
return pc.setLocalDescription(offer);
})
.then(() => { /* ... */ })
.catch(error => { console.error("Error creating offer:", error); });
ãã®é¢æ°ã¯SDPæååãè§£æããæå®ãããã³ãŒããã¯ïŒäŸïŒ`opus`ïŒã«é¢é£ããè¡ãç¹å®ãããããã®è¡ã `m=`ïŒã¡ãã£ã¢èšè¿°ïŒã»ã¯ã·ã§ã³ã®å é ã«ç§»åãããããšã§ããã®ã³ãŒããã¯ã广çã«åªå ããŸãããŸããéè€ãé¿ããããã«ããã©ãŒããããªã¹ãå ã®å ã®äœçœ®ããã³ãŒããã¯ãåé€ããŸãããã®å€æŽã¯ããªãã¡ãŒã§ããŒã«ã«ãã£ã¹ã¯ãªãã·ã§ã³ãèšå®ãã*å*ã«é©çšããããšãå¿ããªãã§ãã ããã
ãã®é¢æ°ã䜿çšããã«ã¯ãæ¬¡ã®æé ãå®è¡ããŸãïŒ
- `RTCPeerConnection` ãäœæããŸãã
- `createOffer()` ãåŒã³åºããŠãåæã®SDPãªãã¡ãŒãçæããŸãã
- `prioritizeCodec()` ãåŒã³åºããŠãåªå ããã³ãŒããã¯ãåªå ããããã«SDPæååã倿ŽããŸãã
- 倿Žãããæååã§ãªãã¡ãŒã®SDPãæŽæ°ããŸãã
- `setLocalDescription()` ãåŒã³åºããŠã倿Žããããªãã¡ãŒãããŒã«ã«ãã£ã¹ã¯ãªãã·ã§ã³ãšããŠèšå®ããŸãã
åãååã¯ã`createAnswer()` ã¡ãœãããš `setRemoteDescription()` ãé©å®äœ¿çšããŠãã¢ã³ãµãŒSDPã«ãé©çšã§ããŸãã
3. ãã©ã³ã·ãŒããŒæ©èœïŒã¢ãã³ãªã¢ãããŒãïŒ
RTCRtpTransceiver APIã¯ãWebRTCã§ã³ãŒããã¯ãšã¡ãã£ã¢ã¹ããªãŒã ã管çããããã®ãããã¢ãã³ã§æ§é åãããæ¹æ³ãæäŸããŸãããã©ã³ã·ãŒããŒã¯ã¡ãã£ã¢ã®éåä¿¡ãã«ãã»ã«åããã¡ãã£ã¢ãããŒã®æ¹åïŒsendonlyãrecvonlyãsendrecvãinactiveïŒãå¶åŸ¡ããåžæããã³ãŒããã¯ã®åªå é äœãæå®ããããšãã§ããŸãã
ãããããã©ã³ã·ãŒããŒãä»ããçŽæ¥ã®ã³ãŒããã¯æäœã¯ããŸã ãã¹ãŠã®ãã©ãŠã¶ã§å®å šã«æšæºåãããŠããŸãããæãä¿¡é Œæ§ã®é«ãã¢ãããŒãã¯ãæå€§éã®äºææ§ãåŸãããã«ãã©ã³ã·ãŒããŒå¶åŸ¡ãšSDPæäœãçµã¿åãããããšã§ãã
以äžã¯ããã©ã³ã·ãŒããŒãSDPæäœãšçµã¿åãããŠäœ¿çšããæ¹æ³ã®äŸã§ãïŒSDPæäœã®éšåã¯äžèšã®äŸãšåæ§ã«ãªããŸãïŒïŒ
const pc = new RTCPeerConnection();
// é³å£°çšã®ãã©ã³ã·ãŒããŒã远å
const audioTransceiver = pc.addTransceiver('audio');
// ããŒã«ã«ã¹ããªãŒã ãååŸãããã©ãã¯ããã©ã³ã·ãŒããŒã«è¿œå
navigator.mediaDevices.getUserMedia({ audio: true, video: false })
.then(stream => {
stream.getTracks().forEach(track => {
audioTransceiver.addTrack(track, stream);
});
// 以åãšåæ§ã«SDPãªãã¡ãŒãäœæããã³å€æŽ
pc.createOffer()
.then(offer => {
let sdp = offer.sdp;
let modifiedSdp = prioritizeCodec(sdp, 'opus', 'audio');
offer.sdp = modifiedSdp;
return pc.setLocalDescription(offer);
})
.then(() => { /* ... */ })
.catch(error => { console.error("Error creating offer:", error); });
})
.catch(error => { console.error("Error getting user media:", error); });
ãã®äŸã§ã¯ãé³å£°ãã©ã³ã·ãŒããŒãäœæããããŒã«ã«ã¹ããªãŒã ããé³å£°ãã©ãã¯ã远å ããŠããŸãããã®ã¢ãããŒãã¯ãã¡ãã£ã¢ãããŒã«å¯Ÿããããå€ãã®å¶åŸ¡ãæäŸããç¹ã«è€æ°ã®ã¡ãã£ã¢ã¹ããªãŒã ãæ±ãå Žåã«ãã³ãŒããã¯ã管çããããã®ããæ§é åãããæ¹æ³ãæäŸããŸãã
ãã©ãŠã¶ã®äºææ§ã«é¢ããèæ ®äºé
ã³ãŒããã¯ã®ãµããŒãã¯ãã©ãŠã¶ã«ãã£ãŠç°ãªããŸããé³å£°ã§ã¯OpusãåºããµããŒããããŠããŸãããæ åã³ãŒããã¯ã®ãµããŒãã¯ããæçåãããŠããå¯èœæ§ããããŸãã以äžã¯ãã©ãŠã¶äºææ§ã®äžè¬çãªæŠèŠã§ãïŒ
- Opus: ãã¹ãŠã®äž»èŠãªãã©ãŠã¶ïŒChrome, Firefox, Safari, EdgeïŒã§åªãããµããŒãããããŸããäžè¬çã«WebRTCã§æšå¥šãããé³å£°ã³ãŒããã¯ã§ãã
- VP8: è¯å¥œãªãµããŒãããããŸãããäžè¬çã«VP9ãšAV1ã«åã£ãŠä»£ãããã€ã€ãããŸãã
- VP9: ChromeãFirefoxãããã³æ°ããããŒãžã§ã³ã®EdgeãšSafariã§ãµããŒããããŠããŸãã
- H.264: ã»ãšãã©ã®ãã©ãŠã¶ã§ãµããŒããããŠããããã°ãã°ããŒããŠã§ã¢ã¢ã¯ã»ã©ã¬ãŒã·ã§ã³ãå©çšã§ããããã人æ°ã®éžæè¢ã§ãããã ããã©ã€ã»ã³ã¹ãæžå¿µäºé ãšãªãããšããããŸãã
- AV1: ãµããŒãã¯æ¥éã«æ¡å€§ããŠããŸããChromeãFirefoxãããã³æ°ããããŒãžã§ã³ã®EdgeãšSafariãAV1ããµããŒãããŠããŸããæé«ã®å§çž®å¹çãæäŸããŸãããããå€ãã®åŠçèœåãå¿ èŠãšããå ŽåããããŸãã
äºææ§ãšæé©ãªããã©ãŒãã³ã¹ã確ä¿ããããã«ã¯ãã¢ããªã±ãŒã·ã§ã³ãç°ãªããã©ãŠã¶ãããã€ã¹ã§ãã¹ãããããšãäžå¯æ¬ ã§ããæ©èœæ€åºã䜿çšããŠããŠãŒã¶ãŒã®ãã©ãŠã¶ã§ã©ã®ã³ãŒããã¯ããµããŒããããŠãããã倿ã§ããŸããäŸãã°ã`RTCRtpSender.getCapabilities()` ã¡ãœããã䜿çšããŠAV1ã®ãµããŒãã確èªã§ããŸãïŒ
if (RTCRtpSender.getCapabilities('video').codecs.find(codec => codec.mimeType === 'video/AV1')) {
console.log('AV1 is supported!');
} else {
console.log('AV1 is not supported.');
}
æ€åºãããæ©èœã«åºã¥ããŠã³ãŒããã¯ã®åªå é äœãé©å¿ãããåãŠãŒã¶ãŒã«æé«ã®äœéšãæäŸããŠãã ãããïŒVP9ãAV1ããµããŒããããŠããªãå Žåã«H.264ã䜿çšãããªã©ïŒãã©ãŒã«ããã¯ã¡ã«ããºã ãæäŸããåžžã«éä¿¡ãå¯èœã§ããããšãä¿èšŒããŸãã
ããã³ããšã³ãWebRTCã³ãŒããã¯éžæã®ãã¹ããã©ã¯ãã£ã¹
WebRTCã¢ããªã±ãŒã·ã§ã³ã®ã³ãŒããã¯ãéžæããéã«åŸãã¹ããã¹ããã©ã¯ãã£ã¹ãããã€ã玹ä»ããŸãïŒ
- é³å£°ã«ã¯Opusãåªå ããïŒ Opusã¯äœãããã¬ãŒãã§åªããé³è³ªãæäŸããåºããµããŒããããŠããŸããé³å£°éä¿¡ã®ããã©ã«ãã®éžæè¢ãšãã¹ãã§ãã
- æ åã«ã¯VP9ãŸãã¯AV1ãæ€èšããïŒ ãããã®ãã€ã€ãªãã£ããªãŒã®ã³ãŒããã¯ã¯VP8ãããåªããå§çž®å¹çãæäŸãã垯åå¹ ã®æ¶è²»ãå€§å¹ ã«åæžã§ããŸãããã©ãŠã¶ã®ãµããŒããååã§ããã°ããããã®ã³ãŒããã¯ãåªå ããŠãã ããã
- H.264ããã©ãŒã«ããã¯ãšããŠäœ¿çšããïŒ H.264ã¯åºããµããŒããããŠããããã°ãã°ããŒããŠã§ã¢ã¢ã¯ã»ã©ã¬ãŒã·ã§ã³ãå©çšã§ããŸããVP9ãŸãã¯AV1ãå©çšã§ããªãå Žåã®ãã©ãŒã«ããã¯ãªãã·ã§ã³ãšããŠäœ¿çšããŠãã ãããã©ã€ã»ã³ã¹ã®åœ±é¿ã«æ³šæããŠãã ããã
- æ©èœæ€åºãå®è£ ããïŒ `RTCRtpSender.getCapabilities()` ã䜿çšããŠãç°ãªãã³ãŒããã¯ã«å¯Ÿãããã©ãŠã¶ã®ãµããŒããæ€åºããŸãã
- ãããã¯ãŒã¯ç¶æ³ã«é©å¿ããïŒ ãããã¯ãŒã¯ç¶æ³ã«åºã¥ããŠã³ãŒããã¯ãšãããã¬ãŒããé©å¿ãããã¡ã«ããºã ãå®è£ ããŸããRTCPãã£ãŒãããã¯ã¯ãã±ãããã¹ãé å»¶ã«é¢ããæ å ±ãæäŸããæé©ãªå質ãç¶æããããã«ã³ãŒããã¯ããããã¬ãŒããåçã«èª¿æŽããããšãã§ããŸãã
- ã¡ãã£ã¢å¶çŽãæé©åããïŒ ã¡ãã£ã¢å¶çŽã䜿çšããŠãã©ãŠã¶ã®ã³ãŒããã¯éžæã«åœ±é¿ãäžããŸããããã®å¶éã«æ³šæããŠãã ããã
- SDPã®å€æŽããµãã¿ã€ãºããïŒ SDPãçŽæ¥æäœããå Žåã¯ãã»ãã¥ãªãã£è匱æ§ãé²ãããã«å€æŽã培åºçã«æ€èšŒãããµãã¿ã€ãºããŠãã ããã
- 培åºçã«ãã¹ãããïŒ äºææ§ãšæé©ãªããã©ãŒãã³ã¹ã確ä¿ããããã«ãã¢ããªã±ãŒã·ã§ã³ãç°ãªããã©ãŠã¶ãããã€ã¹ããããã¯ãŒã¯ç¶æ³ã§ãã¹ãããŠãã ãããWiresharkã®ãããªããŒã«ã䜿çšããŠSDP亀æãåæããæ£ããã³ãŒããã¯ã䜿çšãããŠããããšã確èªããŸãã
- ããã©ãŒãã³ã¹ãç£èŠããïŒ WebRTCçµ±èšAPIïŒ`getStats()`ïŒã䜿çšããŠããããã¬ãŒãããã±ãããã¹ãé å»¶ãªã©ãWebRTCæ¥ç¶ã®ããã©ãŒãã³ã¹ãç£èŠããŸãããã®ããŒã¿ã¯ãããã©ãŒãã³ã¹ã®ããã«ããã¯ãç¹å®ãã察åŠããã®ã«åœ¹ç«ã¡ãŸãã
- Simulcast/SVCãæ€èšããïŒ å€äººæ°é話ããããã¯ãŒã¯ç¶æ³ãå€åããã·ããªãªã§ã¯ããŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ãåäžãããããã«ãSimulcastïŒåãæ åã¹ããªãŒã ãç°ãªãè§£å床ãšãããã¬ãŒãã§è€æ°ããŒãžã§ã³éä¿¡ããïŒãŸãã¯Scalable Video CodingïŒSVCãæ åãè€æ°ã®ã¬ã€ã€ãŒã«ãšã³ã³ãŒãããããé«åºŠãªæè¡ïŒã®äœ¿çšãæ€èšããŠãã ããã
çµè«
WebRTCã¢ããªã±ãŒã·ã§ã³ã«é©ããã³ãŒããã¯ãéžæããããšã¯ããŠãŒã¶ãŒã«é«å質ãªãªã¢ã«ã¿ã€ã ã³ãã¥ãã±ãŒã·ã§ã³äœéšãä¿èšŒããããã®éèŠãªã¹ãããã§ããSDPã®ååãçè§£ããã¡ãã£ã¢å¶çŽãšSDPæäœãã¯ããã¯ã掻çšãããã©ãŠã¶ã®äºææ§ãèæ ®ãããã¹ããã©ã¯ãã£ã¹ã«åŸãããšã§ãããã©ãŒãã³ã¹ãä¿¡é Œæ§ããããŠã°ããŒãã«ãªãªãŒãã®ããã«WebRTCã¢ããªã±ãŒã·ã§ã³ãæé©åã§ããŸããé³å£°ã«ã¯Opusãåªå ããæ åã«ã¯VP9ãŸãã¯AV1ãæ€èšããH.264ããã©ãŒã«ããã¯ãšããŠäœ¿çšããåžžã«ç°ãªããã©ãããã©ãŒã ãšãããã¯ãŒã¯ç¶æ³ã§åŸ¹åºçã«ãã¹ãããããšãå¿ããªãã§ãã ãããWebRTCæè¡ãé²åãç¶ããäžã§ãææ°ã®ã³ãŒããã¯éçºããã©ãŠã¶ã®æ©èœã«ã€ããŠåžžã«æ å ±ãåŸãããšããæå 端ã®ãªã¢ã«ã¿ã€ã ã³ãã¥ãã±ãŒã·ã§ã³ãœãªã¥ãŒã·ã§ã³ãæäŸããäžã§äžå¯æ¬ ã§ãã